447 lines
9.7 KiB
C
447 lines
9.7 KiB
C
/*
|
|
* DESCRIPTION:
|
|
* Main gameplay state.
|
|
*/
|
|
|
|
#include "s_play.h"
|
|
|
|
static s32 play_level_timer_get(struct Play* self);
|
|
static s32 play_spawn_timer_get(struct Play* self);
|
|
static void play_color_collide_delete_update(struct Play* self, struct CColorCollideDelete* colorCollideDelete);
|
|
static void play_damage_update(struct Play* self);
|
|
static void play_death_update(struct Play* self);
|
|
static void play_enemy_add(struct Play* self);
|
|
static void play_enemy_clear(struct Play* self, bool isParticle);
|
|
static void play_level_increment(struct Play* self);
|
|
static void play_score_set(struct Play* self, enum CColorType type);
|
|
static void play_score_update(struct Play* self);
|
|
static void play_spawn_update(struct Play* self);
|
|
static void play_start_update(struct Play* self);
|
|
|
|
/* Updates score graphic. */
|
|
static void
|
|
play_score_set(struct Play* self, enum CColorType type)
|
|
{
|
|
struct CScale* scale;
|
|
struct CColorChange* colorChange;
|
|
struct CText* text;
|
|
char scoreBuffer[PLAY_SCORE_BUFFER_SIZE];
|
|
|
|
memset(scoreBuffer, '\0', sizeof(char) * PLAY_SCORE_BUFFER_SIZE);
|
|
|
|
scale = ecs_get(self->scoreID, ECS_C_SCALE);
|
|
colorChange = ecs_get(self->scoreID, ECS_C_COLOR_CHANGE);
|
|
text = ecs_get(self->scoreID, ECS_C_TEXT);
|
|
|
|
snprintf(scoreBuffer, sizeof(char) * PLAY_SCORE_BUFFER_SIZE, "%u", self->score);
|
|
|
|
c_text_string_set(text, (char*)scoreBuffer, strlen(scoreBuffer));
|
|
|
|
c_scale_set
|
|
(
|
|
scale,
|
|
PLAY_SCORE_SCALE_BASE,
|
|
PLAY_SCORE_SCALE_VALUE,
|
|
PLAY_SCORE_SCALE_TIMER,
|
|
PLAY_SCORE_SCALE_TYPE
|
|
);
|
|
|
|
c_color_change_set
|
|
(
|
|
colorChange,
|
|
text->color,
|
|
C_COLOR_VALUES[type],
|
|
PLAY_SCORE_COLOR_CHANGE_TIMER,
|
|
PLAY_SCORE_COLOR_CHANGE_TYPE
|
|
);
|
|
}
|
|
|
|
/* What happens when a follower collides with an enemy? */
|
|
static void
|
|
play_color_collide_delete_update(struct Play* self, struct CColorCollideDelete* colorCollideDelete)
|
|
{
|
|
struct CColor* color;
|
|
enum CColorType type;
|
|
|
|
color = ecs_get(colorCollideDelete->id, ECS_C_COLOR);
|
|
type = color->type;
|
|
|
|
for (s32 i = 0; i < (s32)self->enemies.count; i++)
|
|
{
|
|
u32* id;
|
|
|
|
id = (u32*)vector_get(&self->enemies, i);
|
|
|
|
if (*id == colorCollideDelete->collideID)
|
|
vector_remove_from_data(&self->enemies, id);
|
|
}
|
|
|
|
self->score += self->level;
|
|
play_score_set(self, type);
|
|
}
|
|
|
|
/* Increment the level. */
|
|
static void
|
|
play_level_increment(struct Play* self)
|
|
{
|
|
struct CHealth* playerHealth;
|
|
struct CText* text;
|
|
char levelBuffer[PLAY_LEVEL_BUFFER_SIZE];
|
|
vec4 color;
|
|
|
|
memset(levelBuffer, '\0', sizeof(char) * PLAY_LEVEL_BUFFER_SIZE);
|
|
|
|
playerHealth = ecs_get(self->playerID, ECS_C_HEALTH);
|
|
text = ecs_get(self->levelID, ECS_C_TEXT);
|
|
|
|
if (playerHealth->isDead)
|
|
return;
|
|
|
|
if (self->level > 0)
|
|
sound_play(&game.sounds[SOUND_LEVEL], -1);
|
|
|
|
self->level++;
|
|
|
|
if (self->level <= C_COLOR_COUNT)
|
|
player_follower_add(self->playerID);
|
|
|
|
self->levelTimer = play_level_timer_get(self);
|
|
|
|
if (!text)
|
|
return;
|
|
|
|
snprintf(levelBuffer, sizeof(char) * PLAY_LEVEL_BUFFER_SIZE, "x%u", self->level);
|
|
|
|
c_text_string_set(text, (char*)levelBuffer, strlen(levelBuffer));
|
|
}
|
|
|
|
/* Updates play start state. */
|
|
static void
|
|
play_start_update(struct Play* self)
|
|
{
|
|
self->startTimer--;
|
|
|
|
if (self->startTimer <= 0)
|
|
{
|
|
self->isStart = false;
|
|
self->isSpawn = true;
|
|
}
|
|
}
|
|
|
|
/* Get level up timer. */
|
|
static s32
|
|
play_level_timer_get(struct Play* self)
|
|
{
|
|
return PLAY_LEVEL_TIMER_BASE;
|
|
}
|
|
|
|
/* Update play score. */
|
|
static void
|
|
play_score_update(struct Play* self)
|
|
{
|
|
/* Check each of the player's followers, did they collide with an enemy? */
|
|
struct COrbit* orbit;
|
|
|
|
orbit = ecs_get(self->playerID, ECS_C_ORBIT);
|
|
|
|
for (s32 i = 0; i < (s32)orbit->objects.count; i++)
|
|
{
|
|
struct CColorCollideDelete* colorCollideDelete;
|
|
u32 id;
|
|
|
|
id = *(u32*)vector_get(&orbit->objects, i);
|
|
|
|
colorCollideDelete = ecs_get(id, ECS_C_COLOR_COLLIDE_DELETE);
|
|
|
|
if (!colorCollideDelete)
|
|
continue;
|
|
|
|
if (colorCollideDelete->isCollided)
|
|
play_color_collide_delete_update(self, colorCollideDelete);
|
|
}
|
|
}
|
|
|
|
/* Update play level. */
|
|
static void
|
|
play_level_update(struct Play* self)
|
|
{
|
|
self->levelTimer--;
|
|
|
|
if (self->levelTimer <= 0)
|
|
play_level_increment(self);
|
|
}
|
|
|
|
/* Gets current spawn timer. */
|
|
static s32
|
|
play_spawn_timer_get(struct Play* self)
|
|
{
|
|
s32 timer;
|
|
|
|
timer = PLAY_SPAWN_TIMER_BASE;
|
|
|
|
timer -= (s32)(self->level * PLAY_LEVEL_SPAWN_TIMER_MULTIPLIER);
|
|
|
|
timer *= RANDOM_F32(PLAY_SPAWN_TIMER_MULTIPLIER_MIN, PLAY_SPAWN_TIMER_MULTIPLIER_MAX);
|
|
|
|
timer = MIN(timer, PLAY_SPAWN_TIMER_MIN);
|
|
|
|
return timer;
|
|
}
|
|
|
|
/* Updates spawn timer. */
|
|
static void
|
|
play_spawn_update(struct Play* self)
|
|
{
|
|
struct CStun* stun;
|
|
|
|
stun = ecs_get(self->playerID, ECS_C_STUN);
|
|
|
|
if (stun->isStun)
|
|
{
|
|
self->spawnTimer = PLAY_SPAWN_TIMER_STUN_TIME;
|
|
return;
|
|
}
|
|
|
|
self->spawnTimer--;
|
|
|
|
if (self->spawnTimer <= 0)
|
|
{
|
|
play_enemy_add(self);
|
|
self->spawnTimer = play_spawn_timer_get(self);
|
|
}
|
|
}
|
|
|
|
/* Updates damage; if player is damaged, remove all enemies. */
|
|
static void
|
|
play_damage_update(struct Play* self)
|
|
{
|
|
struct CDamage* damage;
|
|
struct CHealth* health;
|
|
|
|
damage = ecs_get(self->playerID, ECS_C_DAMAGE);
|
|
health = ecs_get(self->playerID, ECS_C_HEALTH);
|
|
|
|
if (damage->isDamageUpdate && !health->isDead)
|
|
play_enemy_clear(self, true);
|
|
}
|
|
|
|
/* Adds an enemy. */
|
|
static void
|
|
play_enemy_add(struct Play* self)
|
|
{
|
|
u32 enemyID;
|
|
u32 warningID;
|
|
vec3 position;
|
|
vec3 warningPosition;
|
|
enum CColorType color;
|
|
enum Direction direction;
|
|
u32 colorMax;
|
|
f32 speed;
|
|
|
|
enemyID = entity_add();
|
|
warningID = entity_add();
|
|
|
|
direction = (enum Direction)RANDOM_S32(0, DIRECTION_2D_COUNT - 1);
|
|
|
|
glm_vec3_zero(position);
|
|
glm_vec3_zero(warningPosition);
|
|
|
|
switch (direction)
|
|
{
|
|
case LEFT:
|
|
position[0] = game.bounds[0] - ENEMY_SIZE[0];
|
|
position[1] = RANDOM_F32(game.bounds[1] + ENEMY_SIZE[1], game.bounds[3] - ENEMY_SIZE[1]);
|
|
warningPosition[0] = game.bounds[0] + WARNING_SIZE[0];
|
|
warningPosition[1] = position[1];
|
|
break;
|
|
case RIGHT:
|
|
position[0] = game.bounds[2] + ENEMY_SIZE[0];
|
|
position[1] = RANDOM_F32(game.bounds[1] + ENEMY_SIZE[1], game.bounds[3] - ENEMY_SIZE[1]);
|
|
warningPosition[0] = game.bounds[2] - WARNING_SIZE[0];
|
|
warningPosition[1] = position[1];
|
|
break;
|
|
case UP:
|
|
position[0] = RANDOM_F32(game.bounds[0] + ENEMY_SIZE[0], game.bounds[2] - ENEMY_SIZE[0]);
|
|
position[1] = game.bounds[1] - ENEMY_SIZE[0];
|
|
warningPosition[0] = position[0];
|
|
warningPosition[1] = game.bounds[1] + WARNING_SIZE[0];
|
|
break;
|
|
case DOWN:
|
|
position[0] = RANDOM_F32(game.bounds[0] + ENEMY_SIZE[0], game.bounds[2] - ENEMY_SIZE[0]);
|
|
position[1] = game.bounds[3] + ENEMY_SIZE[0];
|
|
warningPosition[0] = position[0];
|
|
warningPosition[1] = game.bounds[3] - WARNING_SIZE[0];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
colorMax = self->level - 1;
|
|
colorMax = MAX(colorMax, C_COLOR_COUNT - 1);
|
|
|
|
color = (enum CColorType)RANDOM_S32(0, colorMax);
|
|
|
|
speed = PLAY_ENEMY_SPEED + ((self->level - 1) * PLAY_ENEMY_SPEED_LEVEL_MULTIPLIER);
|
|
|
|
enemy_init(enemyID, self->playerID, color, position, speed);
|
|
warning_init(warningID, warningPosition, color);
|
|
|
|
sound_play(&game.sounds[SOUND_WARNING], -1);
|
|
|
|
vector_push(&self->enemies, &enemyID);
|
|
}
|
|
|
|
/* Removes all enemies. */
|
|
static void
|
|
play_enemy_clear(struct Play* self, bool isSpawnParticle)
|
|
{
|
|
for (s32 i = 0; i < (s32)self->enemies.count; i++)
|
|
{
|
|
u32 id;
|
|
|
|
id = *(u32*)vector_get(&self->enemies, i);
|
|
|
|
if (isSpawnParticle)
|
|
{
|
|
struct CParticleSpawn* particleSpawn;
|
|
struct CPhysics* physics;
|
|
vec3 position;
|
|
|
|
particleSpawn = ecs_get(id, ECS_C_PARTICLE_SPAWN);
|
|
physics = ecs_get(id, ECS_C_PHYSICS);
|
|
|
|
if (!particleSpawn || !physics)
|
|
continue;
|
|
|
|
glm_vec3_copy(physics->position, position);
|
|
|
|
c_particle_spawn(particleSpawn, position);
|
|
}
|
|
|
|
entity_delete(id);
|
|
}
|
|
}
|
|
|
|
/* Check if the player's death animation has ended. */
|
|
static void
|
|
play_death_update(struct Play* self)
|
|
{
|
|
struct CPlayerDeath* playerDeath;
|
|
|
|
playerDeath = ecs_get(self->playerID, ECS_C_PLAYER_DEATH);
|
|
|
|
if (playerDeath->state == C_PLAYER_DEATH_END)
|
|
self->isOver = true;
|
|
}
|
|
|
|
/* Initialize play. */
|
|
void
|
|
play_init(struct Play* self)
|
|
{
|
|
struct CColorChange* messageColorChange;
|
|
|
|
memset(self, '\0', sizeof(struct Play));
|
|
|
|
self->playerID = entity_add();
|
|
|
|
self->scoreID = entity_add();
|
|
self->levelID = entity_add();
|
|
self->messageID = entity_add();
|
|
|
|
player_init(self->playerID, PLAY_PLAYER_POSITION);
|
|
|
|
text_init
|
|
(
|
|
self->scoreID,
|
|
PLAY_SCORE_ORIGIN,
|
|
game.fonts[PLAY_SCORE_FONT],
|
|
PLAY_SCORE_STRING,
|
|
strlen(PLAY_SCORE_STRING),
|
|
PLAY_SCORE_WRAP,
|
|
PLAY_SCORE_COLOR,
|
|
PLAY_SCORE_POSITION
|
|
);
|
|
|
|
play_score_set(self, C_COLOR_GREEN);
|
|
|
|
text_init
|
|
(
|
|
self->levelID,
|
|
PLAY_LEVEL_ORIGIN,
|
|
game.fonts[PLAY_LEVEL_FONT],
|
|
PLAY_LEVEL_STRING,
|
|
strlen(PLAY_LEVEL_STRING),
|
|
PLAY_LEVEL_WRAP,
|
|
PLAY_LEVEL_COLOR,
|
|
PLAY_LEVEL_POSITION
|
|
);
|
|
|
|
play_level_increment(self);
|
|
|
|
text_init
|
|
(
|
|
self->messageID,
|
|
PLAY_MESSAGE_ORIGIN,
|
|
game.fonts[PLAY_MESSAGE_FONT],
|
|
PLAY_MESSAGE_STRING,
|
|
strlen(PLAY_MESSAGE_STRING),
|
|
PLAY_MESSAGE_WRAP,
|
|
PLAY_MESSAGE_COLOR,
|
|
PLAY_MESSAGE_POSITION
|
|
);
|
|
|
|
messageColorChange = ecs_get(self->messageID, ECS_C_COLOR_CHANGE);
|
|
|
|
c_color_change_set
|
|
(
|
|
messageColorChange,
|
|
C_COLOR_VALUES[0],
|
|
COLOR_TRANSPARENT,
|
|
PLAY_START_TIMER_DEFAULT,
|
|
C_COLOR_CHANGE_TO_STICK
|
|
);
|
|
|
|
vector_init(&self->enemies, sizeof(u32), PLAY_ENEMY_LIMIT);
|
|
|
|
self->spawnTimer = PLAY_SPAWN_TIMER_DEFAULT;
|
|
self->startTimer = PLAY_START_TIMER_DEFAULT;
|
|
self->isSpawn = false;
|
|
self->isStart = true;
|
|
|
|
music_play(&game.music[MUSIC_ETHNICA], true);
|
|
}
|
|
|
|
/* Frees play. */
|
|
void
|
|
play_free(struct Play* self)
|
|
{
|
|
play_enemy_clear(self, false);
|
|
|
|
entity_delete(self->playerID);
|
|
entity_delete(self->scoreID);
|
|
entity_delete(self->levelID);
|
|
entity_delete(self->messageID);
|
|
}
|
|
|
|
/* Update play. */
|
|
void
|
|
play_update_f(struct Play* self)
|
|
{
|
|
if (keyboard_press(&game.keyboard, KEYBOARD_P))
|
|
game.isPaused = !game.isPaused;
|
|
|
|
if (game.isPaused)
|
|
return;
|
|
|
|
if (self->isStart)
|
|
play_start_update(self);
|
|
|
|
if (self->isSpawn)
|
|
play_spawn_update(self);
|
|
|
|
play_damage_update(self);
|
|
play_score_update(self);
|
|
play_level_update(self);
|
|
play_death_update(self);
|
|
}
|