From 2707c46df10a54a795f15096f0f9239c567d73df Mon Sep 17 00:00:00 2001 From: Doyle Thai Date: Wed, 20 Jul 2016 21:42:45 +1000 Subject: [PATCH] Add better support for multi-entity battles --- src/Debug.c | 116 ++++++++++++++++++++---------------- src/WorldTraveller.c | 99 +++++++++++++++++++++++------- src/include/Dengine/Debug.h | 6 +- 3 files changed, 146 insertions(+), 75 deletions(-) diff --git a/src/Debug.c b/src/Debug.c index 7cc2036..a4f8611 100644 --- a/src/Debug.c +++ b/src/Debug.c @@ -30,8 +30,64 @@ void debug_init(MemoryArena *arena, v2 windowSize, Font font) GLOBAL_debug.initialConsoleP = V2(consoleXPos, consoleYPos); } +void debug_consoleLog(char *string, char *file, int lineNum) +{ + i32 maxConsoleStrLen = ARRAY_COUNT(GLOBAL_debug.console[0]); + + i32 strIndex = 0; + i32 fileStrLen = common_strlen(file); + for (i32 count = 0; strIndex < maxConsoleStrLen; strIndex++, count++) + { + if (fileStrLen <= count) break; + GLOBAL_debug.console[GLOBAL_debug.consoleIndex][strIndex] = file[count]; + } + + if (strIndex < maxConsoleStrLen) + GLOBAL_debug.console[GLOBAL_debug.consoleIndex][strIndex++] = ':'; + + char line[12] = {0}; + common_itoa(lineNum, line, ARRAY_COUNT(line)); + i32 lineStrLen = common_strlen(line); + for (i32 count = 0; strIndex < maxConsoleStrLen; strIndex++, count++) + { + if (lineStrLen <= count) break; + GLOBAL_debug.console[GLOBAL_debug.consoleIndex][strIndex] = line[count]; + } + + if (strIndex < maxConsoleStrLen) + GLOBAL_debug.console[GLOBAL_debug.consoleIndex][strIndex++] = ':'; + + i32 stringStrLen = common_strlen(string); + for (i32 count = 0; strIndex < maxConsoleStrLen; strIndex++, count++) + { + if (stringStrLen <= count) break; + GLOBAL_debug.console[GLOBAL_debug.consoleIndex][strIndex] = string[count]; + } + + if (strIndex >= maxConsoleStrLen) + { + GLOBAL_debug.console[GLOBAL_debug.consoleIndex][maxConsoleStrLen-4] = '.'; + GLOBAL_debug.console[GLOBAL_debug.consoleIndex][maxConsoleStrLen-3] = '.'; + GLOBAL_debug.console[GLOBAL_debug.consoleIndex][maxConsoleStrLen-2] = '.'; + GLOBAL_debug.console[GLOBAL_debug.consoleIndex][maxConsoleStrLen-1] = 0; + } + + i32 maxConsoleLines = ARRAY_COUNT(GLOBAL_debug.console); + GLOBAL_debug.consoleIndex++; + + if (GLOBAL_debug.consoleIndex >= maxConsoleLines) + GLOBAL_debug.consoleIndex = 0; +} + void debug_pushString(char *formatString, void *data, char *dataType) { + if (GLOBAL_debug.numDebugStrings >= + ARRAY_COUNT(GLOBAL_debug.debugStrings)) + { + DEBUG_LOG("Debug string stack is full, DEBUG_PUSH() failed"); + return; + } + if (GLOBAL_debug.stringUpdateTimer <= 0) { i32 numDebugStrings = GLOBAL_debug.numDebugStrings; @@ -79,8 +135,6 @@ void debug_pushString(char *formatString, void *data, char *dataType) ASSERT(INVALID_CODE_PATH); } GLOBAL_debug.numDebugStrings++; - ASSERT(GLOBAL_debug.numDebugStrings < - ARRAY_COUNT(GLOBAL_debug.debugStrings[0])); } } @@ -178,6 +232,8 @@ void debug_drawUi(GameState *state, f32 dt) for (i32 i = 0; i < world->freeEntityIndex; i++) { Entity *const entity = &world->entities[i]; + if (entity->state == entitystate_dead) continue; + /* Render debug markers on entities */ v4 color = V4(1, 1, 1, 1); char *debugString = NULL; @@ -242,6 +298,13 @@ void debug_drawUi(GameState *state, f32 dt) entity->stats->actionTimer, entity->stats->actionRate); renderer_string(&state->renderer, &state->arena, cameraBounds, font, entityTimer, strPos, 0, color); + + strPos.y -= GLOBAL_debug.stringLineGap; + char entityIdTarget[32]; + snprintf(entityIdTarget, ARRAY_COUNT(entityIdTarget), + "Targetting ID: %d", entity->stats->entityIdToAttack); + renderer_string(&state->renderer, &state->arena, cameraBounds, + font, entityIdTarget, strPos, 0, color); } strPos.y -= GLOBAL_debug.stringLineGap; @@ -290,52 +353,3 @@ void debug_drawUi(GameState *state, f32 dt) renderConsole(&state->renderer, &state->arena); debug_clearCallCounter(); } - -void debug_consoleLog(char *string, char *file, int lineNum) -{ - i32 maxConsoleStrLen = ARRAY_COUNT(GLOBAL_debug.console[0]); - - i32 strIndex = 0; - i32 fileStrLen = common_strlen(file); - for (i32 count = 0; strIndex < maxConsoleStrLen; strIndex++, count++) - { - if (fileStrLen <= count) break; - GLOBAL_debug.console[GLOBAL_debug.consoleIndex][strIndex] = file[count]; - } - - if (strIndex < maxConsoleStrLen) - GLOBAL_debug.console[GLOBAL_debug.consoleIndex][strIndex++] = ':'; - - char line[12] = {0}; - common_itoa(lineNum, line, ARRAY_COUNT(line)); - i32 lineStrLen = common_strlen(line); - for (i32 count = 0; strIndex < maxConsoleStrLen; strIndex++, count++) - { - if (lineStrLen <= count) break; - GLOBAL_debug.console[GLOBAL_debug.consoleIndex][strIndex] = line[count]; - } - - if (strIndex < maxConsoleStrLen) - GLOBAL_debug.console[GLOBAL_debug.consoleIndex][strIndex++] = ':'; - - i32 stringStrLen = common_strlen(string); - for (i32 count = 0; strIndex < maxConsoleStrLen; strIndex++, count++) - { - if (stringStrLen <= count) break; - GLOBAL_debug.console[GLOBAL_debug.consoleIndex][strIndex] = string[count]; - } - - if (strIndex >= maxConsoleStrLen) - { - GLOBAL_debug.console[GLOBAL_debug.consoleIndex][maxConsoleStrLen-4] = '.'; - GLOBAL_debug.console[GLOBAL_debug.consoleIndex][maxConsoleStrLen-3] = '.'; - GLOBAL_debug.console[GLOBAL_debug.consoleIndex][maxConsoleStrLen-2] = '.'; - GLOBAL_debug.console[GLOBAL_debug.consoleIndex][maxConsoleStrLen-1] = 0; - } - - i32 maxConsoleLines = ARRAY_COUNT(GLOBAL_debug.console); - GLOBAL_debug.consoleIndex++; - - if (GLOBAL_debug.consoleIndex >= maxConsoleLines) - GLOBAL_debug.consoleIndex = 0; -} diff --git a/src/WorldTraveller.c b/src/WorldTraveller.c index 05dfee4..b1d7891 100644 --- a/src/WorldTraveller.c +++ b/src/WorldTraveller.c @@ -685,8 +685,20 @@ INTERNAL void entityStateSwitch(World *world, Entity *entity, entity->stats->entityIdToAttack = findBestEntityToAttack(world, *entity); break; - case entitystate_attack: + + // TODO(doyle): Corner case- if move out of range and entity has + // switched to idle mode, we reach the attacker entity and they continue + // attacking it since there's no check before attack if entity is idle + // or not (i.e. has moved out of frame last frame). case entitystate_dead: + updateWorldBattleEntities(world, entity, ENTITY_NOT_IN_BATTLE); + setActiveEntityAnim(entity, animlist_hero_idle); + entity->stats->actionTimer = entity->stats->actionRate; + entity->stats->queuedAttack = entityattack_invalid; + entity->stats->entityIdToAttack = ENTITY_NULL_ID; + break; + + case entitystate_attack: default: #ifdef DENGINE_DEBUG ASSERT(INVALID_CODE_PATH); @@ -696,18 +708,12 @@ INTERNAL void entityStateSwitch(World *world, Entity *entity, case entitystate_battle: switch (newState) { - case entitystate_idle: - updateWorldBattleEntities(world, entity, ENTITY_NOT_IN_BATTLE); - setActiveEntityAnim(entity, animlist_hero_idle); - entity->stats->actionTimer = entity->stats->actionRate; - entity->stats->queuedAttack = entityattack_invalid; - entity->stats->entityIdToAttack = ENTITY_NULL_ID; - break; case entitystate_attack: break; + case entitystate_idle: case entitystate_dead: updateWorldBattleEntities(world, entity, ENTITY_NOT_IN_BATTLE); - setActiveEntityAnim(entity, animlist_hero_wave); + setActiveEntityAnim(entity, animlist_hero_idle); entity->stats->actionTimer = entity->stats->actionRate; entity->stats->queuedAttack = entityattack_invalid; entity->stats->entityIdToAttack = ENTITY_NULL_ID; @@ -726,9 +732,6 @@ INTERNAL void entityStateSwitch(World *world, Entity *entity, entity->stats->busyDuration = 0; setActiveEntityAnim(entity, animlist_hero_battlePose); break; - case entitystate_idle: - return; - case entitystate_dead: // TODO(doyle): Repeated logic with battle -> dead updateWorldBattleEntities(world, entity, ENTITY_NOT_IN_BATTLE); @@ -737,6 +740,16 @@ INTERNAL void entityStateSwitch(World *world, Entity *entity, entity->stats->queuedAttack = entityattack_invalid; entity->stats->entityIdToAttack = ENTITY_NULL_ID; break; + + // NOTE(doyle): Entity has been forced out of an attack (out of range) + case entitystate_idle: + updateWorldBattleEntities(world, entity, ENTITY_NOT_IN_BATTLE); + setActiveEntityAnim(entity, animlist_hero_idle); + entity->stats->busyDuration = 0; + entity->stats->actionTimer = entity->stats->actionRate; + entity->stats->queuedAttack = entityattack_invalid; + entity->stats->entityIdToAttack = ENTITY_NULL_ID; + break; default: #ifdef DENGINE_DEBUG ASSERT(INVALID_CODE_PATH); @@ -820,19 +833,61 @@ INTERNAL void endAttack(World *world, Entity *attacker) } /* Compute attack damage */ - // TODO(doyle): Use attacker stats in battle equations - Entity *defender = &world->entities[attacker->stats->entityIdToAttack]; - if (attacker->type == entitytype_hero) - defender->stats->health -= 50; - else - defender->stats->health--; + Entity *defender = NULL; - if (defender->stats->health <= 0) + // TODO(doyle): Implement faster lookup for entity id in entity table + do { + /* Get target entity to attack */ + for (i32 i = 0; i < world->maxEntities; i++) + { + i32 entityIdToAttack = attacker->stats->entityIdToAttack; + if (world->entities[i].id == entityIdToAttack) + { + defender = &world->entities[i]; + } + } + + /* If no longer exists, find next best */ + if (!defender) + { + if (world->numEntitiesInBattle > 1) + { + attacker->stats->entityIdToAttack = + findBestEntityToAttack(world, *attacker); + } + else + { #ifdef DENGINE_DEBUG - DEBUG_LOG("Entity has died"); + ASSERT(INVALID_CODE_PATH); #endif - entityStateSwitch(world, defender, entitystate_dead); + } + } + } while (!defender); + + // TODO(doyle): Very susceptible to bugs- ensure if defender is dead the + // attacker immediately locates a new target or exits battle mode. But this + // is necessary since it's possible that by the time the hero is ready to + // apply attack damage another person may of killed it (i.e. party member) + if (defender) + { + // TODO(doyle): Use attacker stats in battle equations + if (attacker->type == entitytype_hero) + defender->stats->health -= 50; + else + { + //defender->stats->health--; + } + + if (defender->stats->health <= 0) + { +#ifdef DENGINE_DEBUG + DEBUG_LOG("Entity has died"); +#endif + entityStateSwitch(world, defender, entitystate_dead); + attacker->stats->entityIdToAttack = + findBestEntityToAttack(world, *attacker); + } } /* Return attacker back to non-attacking state */ @@ -878,11 +933,13 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt) // TODO(doyle): Accumulate all dead entities and delete at the // end. Hence resort/organise entity array once, not every time // an entity dies +#if 0 i32 entityIndexInArray = i; deleteEntity(&state->arena, world, entityIndexInArray); // TODO(doyle): DeleteEntity moves elements down 1, so account for i i--; +#endif continue; } diff --git a/src/include/Dengine/Debug.h b/src/include/Dengine/Debug.h index 8020dbf..31dd8c7 100644 --- a/src/include/Dengine/Debug.h +++ b/src/include/Dengine/Debug.h @@ -102,6 +102,9 @@ inline char *debug_entityattack_string(i32 val) void debug_init(MemoryArena *arena, v2 windowSize, Font font); +#define DEBUG_LOG(string) debug_consoleLog(string, __FILE__, __LINE__); +void debug_consoleLog(char *string, char *file, int lineNum); + #define DEBUG_PUSH_STRING(string) debug_pushString(string, NULL, "char") #define DEBUG_PUSH_VAR(formatString, data, type) \ debug_pushString(formatString, CAST(void *)&data, type) @@ -109,7 +112,4 @@ void debug_pushString(char *formatString, void *data, char *dataType); void debug_drawUi(GameState *state, f32 dt); -#define DEBUG_LOG(string) debug_consoleLog(string, __FILE__, __LINE__); -void debug_consoleLog(char *string, char *file, int lineNum); - #endif