-#define _CRT_SECURE_NO_WARNINGS\r
-\r
-#include "combat.h"\r
-#include "globals.h"\r
-#include "ui.h"\r
-#include "tilemap.h"\r
-\r
-#include <cmath>\r
-#include <algorithm>\r
-\r
-\r
-struct Character {\r
- bool allied;\r
- std::string name;\r
- int currentHP;\r
- int maxHP;\r
- int currentMP;\r
- int maxMP;\r
- int attack;\r
- float agility; // to determine move order\r
- float moveDist;\r
-\r
- // animation stuff?\r
- SDL_Texture* texture;\r
- S2DE::Vec2<float> position;\r
- // direction vector?\r
- // how to handle "moves"\r
- S2DE::Vec2<float> combatMoveDestination = { 0.0f, 0.0f };\r
-\r
- int basicAttackRange = 1.0f;\r
-\r
- bool canMove = true;\r
- bool canAttack = true;\r
-\r
- bool alive = true;\r
-};\r
-\r
-std::vector<Character> combatants;\r
-int currentCombatantIndex = -1;\r
-Character* activeCombatant;\r
-\r
-std::vector<Character*> targets;\r
-int currentTargetIdx = 0;\r
-\r
-enum CombatState {\r
- TURN_ORDER,\r
- ACTION_SELECT,\r
- PLAYER_MOVE,\r
- SELECT_TARGET,\r
- DO_ATTACK,\r
- ENEMY_MOVE,\r
- MOVE_TO_POSITION,\r
-};\r
-\r
-CombatState currentCombatState = TURN_ORDER;\r
-\r
-enum Spell {\r
- BASIC_ATTACK\r
-};\r
-\r
-Spell currentSpell = BASIC_ATTACK;\r
-\r
-static double frameTimer = 0;\r
-static const double frameTime = 1.0f / 60.0f;\r
-\r
-//bool playerTurn = true;\r
-\r
-SDL_Texture* arrowSprite = nullptr;\r
-SDL_Texture* indicatorSprite = nullptr;\r
-SDL_Texture* indicatorRedSprite = nullptr;\r
-\r
-static constexpr int arenaWidth = 100;\r
-static constexpr int arenaHeight = 100;\r
-WorldCell** arena;\r
-WorldCell** fogLayer;\r
-S2DE::Camera combatCam = { 23.5, 11.5 };\r
-\r
-S2DE::Vec2<float> moveCursorPos;\r
-S2DE::Vec2<float> arenaCenter = { 23, 11 };\r
-\r
-//S2DE::Vec2<float>* currentMoveDest = &moveCursorPos;\r
-\r
-static const int numActions = 4;\r
-static const char* actions[numActions] = {"Move", "Attack", "End", "FLEE"};\r
-int currentAction = 0;\r
-\r
-SDL_Colour hpColour = { 136, 8, 8, 255 };\r
-SDL_Colour mpColour = { 0, 150, 255, 255 };\r
-\r
-void renderCombat();\r
-\r
-void renderUIFull();\r
-void renderActionsPanel();\r
-void renderCombatants();\r
-void renderStatsPanel();\r
-\r
-// lerp functions\r
-float interpolatePosition(S2DE::Vec2<float>* entityPos, S2DE::Vec2<float> destination, float t, float speed);\r
-void moveToPosition(Character* c, S2DE::Vec2<float> destination, float speed);\r
-\r
-void runStateFunction(CombatState state, double delta);\r
-void actionSelectState(double delta);\r
-void playerMoveActionState(double delta);\r
-void enemyMoveActionState(double delta);\r
-void movingCharacterState(double delta);\r
-void determineNextTurnState(double delta);\r
-void selectAttackTarget(double delta);\r
-void doAttack(double delta); // plays attack animation, resolves and applies damage.\r
-\r
-void runStateFunction(CombatState state, double delta) {\r
- switch (state) {\r
- case TURN_ORDER: determineNextTurnState(delta); break;\r
- case ACTION_SELECT: actionSelectState(delta); break;\r
- case PLAYER_MOVE: playerMoveActionState(delta); break;\r
- case SELECT_TARGET: selectAttackTarget(delta); break;\r
- case ENEMY_MOVE: enemyMoveActionState(delta); break;\r
- case MOVE_TO_POSITION: movingCharacterState(delta); break;\r
- case DO_ATTACK: doAttack(delta); break;\r
- default: assert(false); break;\r
- }\r
-}\r
-\r
-float distance(S2DE::Vec2<float> p1, S2DE::Vec2<float> p2) {\r
- return std::sqrt(std::pow(p2.x - p1.x, 2) + std::powf(p2.y - p1.y, 2));\r
-}\r
-\r
-void renderMeter(int x, int y, int width, int height, float percentage, SDL_Colour fillColour, int style) {\r
- if (style == 1) {\r
- SDL_SetRenderDrawColor(renderer, 0, 0, 0, 180);\r
- SDL_Rect bgRect = { x, y, width, height };\r
- SDL_RenderFillRect(renderer, &bgRect);\r
- }\r
- else if (style == 2) {\r
- SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);\r
- SDL_RenderDrawLine(renderer, x, y, x + width, y);\r
- SDL_RenderDrawLine(renderer, x, y + height, x + width, y + height);\r
- \r
- SDL_RenderDrawLine(renderer, x, y, x, y + height);\r
- SDL_RenderDrawLine(renderer, x + width, y, x + width, y + height);\r
- }\r
-\r
- SDL_SetRenderDrawColor(renderer, fillColour.r, fillColour.g, fillColour.b, fillColour.a);\r
- int panelWidth = width * percentage;\r
- SDL_Rect fillRect = { x+1, y+1, panelWidth -1, height-1 };\r
- SDL_RenderFillRect(renderer, &fillRect);\r
-}\r
-\r
-void renderStatsPanel() {\r
- int panelWidth = 300;\r
- int panelHeight = 200;\r
- int outerPad = 2;\r
- int innerPad = 16;\r
-\r
- int panelX = WINDOW_WIDTH - panelWidth - outerPad;\r
- int panelY = WINDOW_HEIGHT - panelHeight - outerPad;\r
-\r
- renderBasicPanel(panelX, panelY, panelWidth, panelHeight);\r
- renderString(panelX + innerPad, panelY + innerPad, activeCombatant->name.c_str());\r
-\r
- int portraitWidth = 80;\r
- int portraitHeight = 80;\r
-\r
- int portraitX = panelX + 16;\r
- int portraitY = panelY + 50;\r
- renderBasicPanel(portraitX, portraitY, portraitWidth, portraitHeight);\r
- SDL_Rect characterDRect = { portraitX + 8, portraitY + 8, portraitWidth - 16, portraitHeight - 16};\r
- SDL_Rect characterSRect = { 0 , 0, 32, 32 };\r
- SDL_Texture* portraitTexture = activeCombatant->texture;\r
- S2DE::renderTexture(&renderer, &portraitTexture, &characterSRect, &characterDRect);\r
-\r
-\r
- renderString(portraitX + portraitWidth + innerPad, portraitY, "HP:");\r
- int valPad = 100;\r
- char hpStr[10];\r
- sprintf(hpStr, "%d/%d", activeCombatant->currentHP, activeCombatant->maxHP);\r
- renderString(portraitX + portraitWidth + innerPad + valPad, portraitY, hpStr);\r
-\r
- float hpLvl = (float)activeCombatant->currentHP / (float)activeCombatant->maxHP;\r
- renderMeter(portraitX + portraitWidth + innerPad, portraitY + innerPad, panelWidth - (innerPad * 3) - portraitWidth, 20, hpLvl, hpColour, 2);\r
-\r
- renderString(portraitX + portraitWidth + innerPad, portraitY + innerPad + 22, "MP:");\r
-\r
- char mpStr[10];\r
- sprintf(mpStr, "%d/%d", activeCombatant->currentMP, activeCombatant->maxMP);\r
- renderString(portraitX + portraitWidth + innerPad + valPad, portraitY + innerPad + 22, mpStr);\r
- \r
- float mpLvl = (float)activeCombatant->currentMP / (float)activeCombatant->maxMP;\r
- renderMeter(portraitX + portraitWidth + innerPad, portraitY + innerPad + 42, panelWidth - (innerPad * 3) - portraitWidth, 20, mpLvl, mpColour, 2);\r
- \r
-}\r
-\r
-void renderActionsPanel() {\r
- int panelWidth = 150;\r
- int panelHeight = 150;\r
-\r
- //int panelX = WINDOW_WIDTH / 2 - panelWidth / 2;\r
- int panelX = 100;\r
- int panelY = WINDOW_HEIGHT - panelHeight - 2;\r
-\r
- renderBasicPanel(panelX, panelY, panelWidth, panelHeight);\r
-\r
- int leftPad = 40;\r
- int topPad = 20;\r
- int itemSpacing = 30;\r
-\r
- for (int i = 0; i < numActions; ++i) {\r
- // a bit of a brittle way of doing this\r
- if (i == 0 && !activeCombatant->canMove) {\r
- renderString(panelX + leftPad, panelY + topPad + itemSpacing * i, actions[i], font_grey);\r
- }\r
- else if (i == 1 && !activeCombatant->canAttack) {\r
- renderString(panelX + leftPad, panelY + topPad + itemSpacing * i, actions[i], font_grey);\r
- }\r
- else {\r
- renderString(panelX + leftPad, panelY + topPad + itemSpacing * i, actions[i]);\r
- }\r
- }\r
-\r
- renderString(panelX + 20, panelY + topPad + (itemSpacing * currentAction), ">");\r
-}\r
-\r
-void renderUIFull() {\r
- renderActionsPanel();\r
- renderStatsPanel();\r
-}\r
-\r
-void renderCombat() {\r
- renderTilemap(arena, arenaWidth, arenaHeight, combatCam);\r
- renderCombatants();\r
- renderTilemap(fogLayer, arenaWidth, arenaHeight, combatCam); // render the fog layer\r
-}\r
-\r
-void renderCombatants() {\r
-\r
- for (Character c : combatants) {\r
-\r
- if (c.alive) {\r
- S2DE::Vec2<float> pos = c.position;\r
- S2DE::Rect charDRect = S2DE::worldToScreenRect(&combatCam, &pos, 2, WINDOW_WIDTH, WINDOW_HEIGHT, 32, 32);\r
- S2DE::Rect charSRect = { 0, 0, 32, 32 };\r
-\r
- // disabled for now\r
- //if (c.allied) {\r
- // // do the looking logic only for allied characters\r
- // int playerFrame = 1;\r
- // int playerDir = 0;\r
-\r
- // Character enemy = combatants.at(1);\r
-\r
- // int lookDeltaX = (enemy.position.x + 0.5) - (c.position.x + 0.5);\r
- // int lookDeltaY = (enemy.position.y + 0.5) - (c.position.y + 0.5);\r
-\r
- // if (abs(lookDeltaX) > abs(lookDeltaY)) {\r
- // if (lookDeltaX > 0) playerDir = 2;\r
- // else playerDir = 6;\r
- // }\r
- // else {\r
- // if (lookDeltaY > 0) playerDir = 4;\r
- // else playerDir = 0;\r
- // }\r
-\r
- // charSRect = { playerFrame * 32, playerDir * 32, 32, 32 };\r
- //}\r
-\r
- SDL_Texture* texture = c.texture;\r
- S2DE::renderTexture(&renderer, &texture, &charSRect, &charDRect);\r
-\r
- float hpLvl = (float)c.currentHP / (float)c.maxHP;\r
- renderMeter(charDRect.x + 6, charDRect.y - 6, charDRect.w - 12, 6, hpLvl, hpColour, 1);\r
- }\r
- }\r
-\r
- // render the little arrow\r
- S2DE::Vec2<float> arrowPos = activeCombatant->position;\r
- \r
- arrowPos.y -= 0.7;\r
- S2DE::Rect arrowDRect = S2DE::worldToScreenRect(&combatCam, &arrowPos, 2, WINDOW_WIDTH, WINDOW_HEIGHT, 32, 32);\r
- S2DE::renderTexture(&renderer, &indicatorSprite, NULL, &arrowDRect);\r
-}\r
-\r
-void initCombatScene()\r
-{\r
- // I think all resources should be initialized and cleaned up in the globals tbh\r
- arena = new WorldCell * [arenaHeight];\r
- for (int i = 0; i < arenaHeight; ++i) arena[i] = new WorldCell[arenaWidth];\r
- \r
- fogLayer = new WorldCell * [arenaHeight];\r
- for (int i = 0; i < arenaHeight; ++i) fogLayer[i] = new WorldCell[arenaWidth];\r
-\r
- loadWorld("maps/combat_arena_1.tmj", arena, arenaWidth, arenaHeight);\r
- loadWorld("maps/combat_arena_1_fog.tmj", fogLayer, arenaWidth, arenaHeight);\r
-\r
- arrowSprite = S2DE::loadTexture("assets/arrow.png", &renderer);\r
- indicatorSprite = S2DE::loadTexture("assets/little_indicator.png", &renderer);\r
- indicatorRedSprite = S2DE::loadTexture("assets/little_indicator_red.png", &renderer);\r
-\r
- //Character player = { true, "Player", 100, 100, 50, 50, 10.0f, 3.0f, player_sheet, { 23.0f, 13.0f } };\r
- Character player;\r
- player.allied = true;\r
- player.name = "Player";\r
- player.maxHP = 100;\r
- player.currentHP = player.maxHP;\r
- player.maxMP = 50;\r
- player.currentMP = player.maxMP;\r
- player.agility = 10.0f;\r
- player.moveDist = 3.0f;\r
- player.texture = player_forward_texture;\r
- player.position = { arenaCenter.x, arenaCenter.y + 2 };\r
- player.attack = 5;\r
-\r
-\r
- Character monica;\r
- monica.allied = true;\r
- monica.name = "Monica";\r
- monica.maxHP = 100;\r
- monica.currentHP = monica.maxHP;\r
- monica.maxMP = 50;\r
- monica.currentMP = monica.maxMP;\r
- monica.agility = 8.0f;\r
- monica.moveDist = 6.0f;\r
- monica.texture = monica_texture;\r
- monica.position = { player.position.x - 2, player.position.y };\r
- monica.attack = 6;\r
-\r
- Character enemy;\r
- enemy.allied = false;\r
- enemy.name = "Bird";\r
- enemy.maxHP = 25;\r
- enemy.currentHP = enemy.maxHP;\r
- enemy.maxMP = 10;\r
- enemy.currentMP = enemy.maxMP;\r
- enemy.agility = 5.0f;\r
- enemy.moveDist = 1.5f;\r
- enemy.texture = bird;\r
- enemy.position = { arenaCenter.x, arenaCenter.y - 2 };\r
- enemy.attack = 2;\r
-\r
- Character frog;\r
- frog.allied = false;\r
- frog.name = "Frog";\r
- frog.currentHP = frog.maxHP = 10;\r
- frog.currentMP = frog.maxMP = 10;\r
- frog.agility = 2;\r
- frog.moveDist = 2.0f;\r
- frog.texture = frog_texture;\r
- frog.position = { arenaCenter.x - 1, arenaCenter.y - 2 };\r
- frog.attack = 2;\r
-\r
- combatants.push_back(player);\r
- combatants.push_back(monica);\r
- combatants.push_back(enemy);\r
- combatants.push_back(frog);\r
-\r
-\r
- // calculates turn order. Can expand the lambda to support other things\r
- std::sort(combatants.begin(), combatants.end(), [](const Character& a, const Character& b) {return a.agility > b.agility; });\r
-\r
- activeCombatant = &combatants.at(0);\r
- moveCursorPos = activeCombatant->position;\r
-}\r
-\r
-\r
-void interpolatePosition(S2DE::Vec2<float>* entityPos, S2DE::Vec2<float> destination, float* t, float speed) {\r
- *t += speed;\r
-\r
- entityPos->x = (1.0f - *t) * entityPos->x + *t * destination.x;\r
- entityPos->y = (1.0f - *t) * entityPos->y + *t * destination.y;\r
-}\r
-\r
-void moveToPosition(Character* c, S2DE::Vec2<float> destination, float speed) {\r
- float dist = distance(c->position, destination);\r
-\r
- S2DE::Vec2<float> direction {destination.x - c->position.x, destination.y - c->position.y};\r
-\r
- direction.x /= dist;\r
- direction.y /= dist;\r
-\r
- if (dist <= speed) {\r
- c->position.x = destination.x;\r
- c->position.y = destination.y;\r
- }\r
- else {\r
- c->position.x += direction.x * speed;\r
- c->position.y += direction.y * speed;\r
- }\r
-}\r
-\r
-void actionSelectState(double delta) {\r
- S2DE::updateInputState(&inputState);\r
-\r
- // handle inputs\r
- if (inputState.up) {\r
- inputState.up = false;\r
- currentAction--;\r
- if (currentAction < 0) currentAction = numActions - 1;\r
- }\r
- else if (inputState.down) {\r
- inputState.down = false;\r
- currentAction++;\r
- if (currentAction > numActions - 1) currentAction = 0;\r
- }\r
- else if (inputState.rtrn) {\r
- inputState.rtrn = false;\r
- switch (currentAction) {\r
- case 0:\r
- if (activeCombatant->canMove) {\r
- currentCombatState = PLAYER_MOVE;\r
- moveCursorPos = activeCombatant->position;\r
- }\r
- break;\r
- case 1: // attack selected\r
- if (activeCombatant->canAttack) {\r
- // filter the targets - Have to do it here too in case the player attacks without moving?\r
- targets.clear();\r
- for (auto& c : combatants) {\r
- if (!c.allied && c.alive) {\r
- float dist = distance(activeCombatant->position, c.position);\r
- if (dist <= activeCombatant->basicAttackRange) {\r
- targets.push_back(&c);\r
- }\r
- }\r
- }\r
-\r
- currentCombatState = SELECT_TARGET;\r
- }\r
- break;\r
- case 2: // end selected\r
- currentCombatState = TURN_ORDER;\r
- break;\r
-\r
- default:\r
- inputState.quit = true;\r
- break;\r
- }\r
- }\r
-\r
- SDL_RenderClear(renderer);\r
- renderCombat();\r
- renderUIFull();\r
- SDL_RenderPresent(renderer);\r
-}\r
-\r
-void playerMoveActionState(double delta) {\r
- S2DE::updateInputState(&inputState);\r
-\r
- frameTimer += delta;\r
- \r
- Character player = *activeCombatant;\r
-\r
- if (frameTimer > frameTime) {\r
-\r
- frameTimer = 0;\r
-\r
- float cursorSpeed = 0.05;\r
-\r
- S2DE::Vec2<float> newCursorPos = moveCursorPos;\r
-\r
- float newY = moveCursorPos.y;\r
- float newX = moveCursorPos.x;\r
-\r
- if (inputState.right) {\r
- newX += cursorSpeed;\r
- }\r
- if (inputState.left) {\r
- newX -= cursorSpeed;\r
- }\r
- if (inputState.up) {\r
- newY -= cursorSpeed;\r
- }\r
- if (inputState.down) {\r
- newY += cursorSpeed;\r
- }\r
-\r
- if (inputState.rtrn) {\r
- inputState.rtrn = false;\r
-\r
- activeCombatant->combatMoveDestination = moveCursorPos;\r
- currentCombatState = MOVE_TO_POSITION;\r
- }\r
-\r
- if (distance({ moveCursorPos.x, newY }, player.position) <= player.moveDist\r
- && checkCollisionPoint({ moveCursorPos.x + 0.5f, newY + 0.5f }, arena) == false) {\r
- moveCursorPos.y = newY;\r
- }\r
- if (distance({ newX, moveCursorPos.y }, player.position) <= player.moveDist\r
- && checkCollisionPoint({ newX + 0.5f, moveCursorPos.y + 0.5f }, arena) == false) {\r
- moveCursorPos.x = newX;\r
- }\r
-\r
- }\r
-\r
-\r
- // compute characters in melee range\r
- targets.clear();\r
- for (auto& c : combatants) {\r
- if (!c.allied && c.alive) {\r
- float dist = distance(moveCursorPos, c.position);\r
- if (dist <= activeCombatant->basicAttackRange) {\r
- targets.push_back(&c);\r
- }\r
- }\r
- }\r
-\r
-\r
- SDL_RenderClear(renderer);\r
-\r
- renderTilemap(arena, arenaWidth, arenaHeight, combatCam);\r
-\r
- \r
- S2DE::Vec2<float> playerCenterWorld = { player.position.x + 0.5, player.position.y + 0.5 };\r
- S2DE::Vec2<int> playerCenterScreen = S2DE::worldToScreenPoint(&combatCam, &playerCenterWorld, 64, WINDOW_WIDTH, WINDOW_HEIGHT);\r
-\r
- S2DE::renderCircle(&renderer, playerCenterScreen, (player.moveDist) * 64, { 0, 255, 255, 125 });\r
-\r
-\r
- S2DE::Vec2<float> lineEndWorld = { moveCursorPos.x + 0.5, moveCursorPos.y + 0.5 };\r
- S2DE::Vec2<int> lineEndPos = S2DE::worldToScreenPoint(&combatCam, &lineEndWorld, 64, WINDOW_WIDTH, WINDOW_HEIGHT);\r
-\r
- SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);\r
- SDL_RenderDrawLine(renderer, playerCenterScreen.x, playerCenterScreen.y, lineEndPos.x, lineEndPos.y);\r
-\r
- // render lines to targets in range\r
- SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);\r
- for (auto t : targets) {\r
- S2DE::Vec2<float> targetCenter = { t->position.x + 0.5, t->position.y + 0.5 };\r
- S2DE::Vec2<int> targetCenterScreen = S2DE::worldToScreenPoint(&combatCam, &targetCenter, 64, WINDOW_WIDTH, WINDOW_HEIGHT);\r
- SDL_RenderDrawLine(renderer, lineEndPos.x, lineEndPos.y, targetCenterScreen.x, targetCenterScreen.y);\r
- }\r
-\r
-\r
- renderCombatants();\r
-\r
- S2DE::Rect cursorDRect = S2DE::worldToScreenRect(&combatCam, &moveCursorPos, 2, WINDOW_WIDTH, WINDOW_HEIGHT, 32, 32);\r
- S2DE::renderTexture(&renderer, &arrowSprite, NULL, &cursorDRect);\r
-\r
- renderTilemap(fogLayer, arenaWidth, arenaHeight, combatCam); // draw the fog layer\r
-\r
- renderActionsPanel();\r
- renderStatsPanel();\r
- SDL_RenderPresent(renderer);\r
-}\r
-\r
-void selectAttackTarget(double delta) {\r
- S2DE::updateInputState(&inputState);\r
-\r
- frameTimer += delta;\r
-\r
- if (frameTimer > frameTime) {\r
- frameTimer = 0;\r
- if (inputState.rtrn) {\r
- inputState.rtrn = false;\r
- if (targets.size() > 0) {\r
- currentSpell = BASIC_ATTACK;\r
- currentCombatState = DO_ATTACK;\r
- }\r
- else {\r
- currentCombatState = ACTION_SELECT;\r
- }\r
-\r
- }\r
-\r
- if (inputState.right) {\r
- // cycle up through targets\r
- inputState.right = false; // gotta find a better way to do this\r
-\r
- currentTargetIdx += 1;\r
- if (currentTargetIdx > targets.size() - 1) currentTargetIdx = 0;\r
- }\r
- else if (inputState.left) {\r
- inputState.left = false;\r
- // cycle "down"through targets\r
- currentTargetIdx -= 1;\r
- if (currentTargetIdx < 0) currentTargetIdx = targets.size() - 1;\r
- }\r
- }\r
-\r
- SDL_RenderClear(renderer);\r
- \r
-\r
- renderTilemap(arena, arenaWidth, arenaHeight, combatCam);\r
-\r
- renderCombatants();\r
-\r
- // render the little red arrow above all valid targets\r
- if (targets.size() > 0) {\r
- S2DE::Vec2<float> arrowPos = targets.at(currentTargetIdx)->position;\r
-\r
- arrowPos.y -= 0.7;\r
- S2DE::Rect arrowDRect = S2DE::worldToScreenRect(&combatCam, &arrowPos, 2, WINDOW_WIDTH, WINDOW_HEIGHT, 32, 32);\r
- S2DE::renderTexture(&renderer, &indicatorRedSprite, NULL, &arrowDRect);\r
- }\r
- \r
-\r
- renderTilemap(fogLayer, arenaWidth, arenaHeight, combatCam); // draw the fog layer\r
-\r
-\r
-\r
- renderUIFull();\r
- renderString(10, 10, "SELECT TARGET STATE", debug_font);\r
-\r
-\r
- if (targets.size() == 0) {\r
- renderString(10, 64, "NO TARGETS IN RANGE", debug_font);\r
- }\r
- else {\r
- int num_targets = targets.size();\r
- char str[22];\r
- sprintf(str, "AVAILABLE_TARGETS: %i", num_targets);\r
- renderString(10, 64, str, debug_font);\r
- }\r
-\r
- SDL_RenderPresent(renderer);\r
-\r
-}\r
-\r
-void doAttack(double delta) {\r
- // for now just subtract the combatants' dmg from the targets hp and move on\r
- \r
-\r
- if (currentTargetIdx == -1) {\r
- // AOE, apply to all targets in range ?\r
- }\r
- else {\r
- Character* target = targets.at(currentTargetIdx);\r
- target->currentHP -= activeCombatant->attack;\r
-\r
- if (target->currentHP <= 0) {\r
- target->alive = false;\r
- target->currentHP = 0;\r
- }\r
- }\r
-\r
-\r
-\r
- targets.clear();\r
- currentCombatState = ACTION_SELECT;\r
- activeCombatant->canAttack = false;\r
-}\r
-\r
-\r
-void enemyMoveActionState(double delta) {\r
- S2DE::updateInputState(&inputState);\r
-\r
- Character enemy = *activeCombatant;\r
-\r
- // implicitly grabs the character with the highest agility for now\r
- Character agro = *std::find_if(combatants.begin(), combatants.end(), [](const Character& c) {return c.allied; });\r
-\r
- S2DE::Vec2<float> dirToAgro = { agro.position.x - enemy.position.x, agro.position.y - enemy.position.y };\r
- float distToAgro = distance(enemy.position, agro.position);\r
-\r
- dirToAgro.x /= distToAgro;\r
- dirToAgro.y /= distToAgro;\r
-\r
- S2DE::Vec2<float> movePos = { enemy.position.x + dirToAgro.x * enemy.moveDist, enemy.position.y + dirToAgro.y * enemy.moveDist};\r
-\r
- activeCombatant->combatMoveDestination = movePos;\r
- currentCombatState = MOVE_TO_POSITION;\r
-}\r
-\r
-void movingCharacterState(double delta) {\r
- S2DE::updateInputState(&inputState);\r
-\r
- frameTimer += delta;\r
-\r
- if (frameTimer > frameTime) {\r
- frameTimer = 0;\r
-\r
- static float speed = 0.05f;\r
-\r
- if (inputState.rtrn) {\r
- // skip\r
- inputState.rtrn = false;\r
- activeCombatant->position = activeCombatant->combatMoveDestination;\r
- }\r
-\r
- moveToPosition(activeCombatant, activeCombatant->combatMoveDestination, speed);\r
-\r
- if (activeCombatant->position.x == activeCombatant->combatMoveDestination.x && activeCombatant->position.y == activeCombatant->combatMoveDestination.y) {\r
- // move completed. if allied, return to select action\r
- if (activeCombatant->allied) {\r
- activeCombatant->canMove = false; // TODO: support characters that can move twice?\r
- currentCombatState = ACTION_SELECT;\r
- }\r
- else {\r
- currentCombatState = TURN_ORDER;\r
- }\r
- }\r
-\r
- // TODO: Ensure all rendering is happening at the same FPS\r
- SDL_RenderClear(renderer);\r
- renderCombat();\r
- // dont show the ui in this state\r
- SDL_RenderPresent(renderer);\r
- }\r
-\r
-}\r
-\r
-\r
-void determineNextTurnState(double delta) {\r
-\r
- currentCombatantIndex += 1;\r
-\r
- // WARNING: this system does not support the case where agility changes mid-fight. will need to re-sort if that happens\r
- if (currentCombatantIndex > combatants.size() - 1) currentCombatantIndex = 0;\r
-\r
- activeCombatant = &combatants.at(currentCombatantIndex);\r
-\r
- if (activeCombatant->alive) { // skip dead characters\r
- if (activeCombatant->allied) {\r
- // reset combatants actions\r
- activeCombatant->canAttack = true;\r
- activeCombatant->canMove = true;\r
- currentAction = 0;\r
- currentCombatState = ACTION_SELECT;\r
- }\r
- else {\r
- //currentCombatState = ENEMY_MOVE;\r
- }\r
- }\r
-\r
-}\r
-\r
-\r
-void runCombat(double delta) {\r
- \r
- static double frameTimer = 0;\r
- static const double frameTime = 1.0f / 60.0f;\r
-\r
- runStateFunction(currentCombatState, delta);\r
-\r
+#define _CRT_SECURE_NO_WARNINGS
+
+#include "combat.h"
+#include "globals.h"
+#include "ui.h"
+#include "tilemap.h"
+
+#include <cmath>
+#include <algorithm>
+
+
+struct Character {
+ bool allied;
+ std::string name;
+ int currentHP;
+ int maxHP;
+ int currentMP;
+ int maxMP;
+ int attack;
+ float agility; // to determine move order
+ float moveDist;
+
+ // animation stuff?
+ SDL_Texture* texture;
+ S2DE::Vec2<float> position;
+ // direction vector?
+ // how to handle "moves"
+ S2DE::Vec2<float> combatMoveDestination = { 0.0f, 0.0f };
+
+ int basicAttackRange = 1.0f;
+
+ bool canMove = true;
+ bool canAttack = true;
+
+ bool alive = true;
+};
+
+std::vector<Character> combatants;
+int currentCombatantIndex = -1;
+Character* activeCombatant;
+
+std::vector<Character*> targets;
+int currentTargetIdx = 0;
+
+enum CombatState {
+ TURN_ORDER,
+ ACTION_SELECT,
+ PLAYER_MOVE,
+ SELECT_TARGET,
+ DO_ATTACK,
+ ENEMY_MOVE,
+ MOVE_TO_POSITION,
+};
+
+CombatState currentCombatState = TURN_ORDER;
+
+enum Spell {
+ BASIC_ATTACK
+};
+
+Spell currentSpell = BASIC_ATTACK;
+
+static double frameTimer = 0;
+static const double frameTime = 1.0f / 60.0f;
+
+//bool playerTurn = true;
+
+SDL_Texture* arrowSprite = nullptr;
+SDL_Texture* indicatorSprite = nullptr;
+SDL_Texture* indicatorRedSprite = nullptr;
+
+static constexpr int arenaWidth = 100;
+static constexpr int arenaHeight = 100;
+WorldCell** arena;
+WorldCell** fogLayer;
+S2DE::Camera combatCam = { 23.5, 11.5 };
+
+S2DE::Vec2<float> moveCursorPos;
+S2DE::Vec2<float> arenaCenter = { 23, 11 };
+
+//S2DE::Vec2<float>* currentMoveDest = &moveCursorPos;
+
+static const int numActions = 4;
+static const char* actions[numActions] = {"Move", "Attack", "End", "FLEE"};
+int currentAction = 0;
+
+SDL_Colour hpColour = { 136, 8, 8, 255 };
+SDL_Colour mpColour = { 0, 150, 255, 255 };
+
+void renderCombat();
+
+void renderUIFull();
+void renderActionsPanel();
+void renderCombatants();
+void renderStatsPanel();
+
+// lerp functions
+float interpolatePosition(S2DE::Vec2<float>* entityPos, S2DE::Vec2<float> destination, float t, float speed);
+void moveToPosition(Character* c, S2DE::Vec2<float> destination, float speed);
+
+void runStateFunction(CombatState state, double delta);
+void actionSelectState(double delta);
+void playerMoveActionState(double delta);
+void enemyMoveActionState(double delta);
+void movingCharacterState(double delta);
+void determineNextTurnState(double delta);
+void selectAttackTarget(double delta);
+void doAttack(double delta); // plays attack animation, resolves and applies damage.
+
+void runStateFunction(CombatState state, double delta) {
+ switch (state) {
+ case TURN_ORDER: determineNextTurnState(delta); break;
+ case ACTION_SELECT: actionSelectState(delta); break;
+ case PLAYER_MOVE: playerMoveActionState(delta); break;
+ case SELECT_TARGET: selectAttackTarget(delta); break;
+ case ENEMY_MOVE: enemyMoveActionState(delta); break;
+ case MOVE_TO_POSITION: movingCharacterState(delta); break;
+ case DO_ATTACK: doAttack(delta); break;
+ default: assert(false); break;
+ }
+}
+
+float distance(S2DE::Vec2<float> p1, S2DE::Vec2<float> p2) {
+ return std::sqrt(std::pow(p2.x - p1.x, 2) + std::powf(p2.y - p1.y, 2));
+}
+
+void renderMeter(int x, int y, int width, int height, float percentage, SDL_Colour fillColour, int style) {
+ if (style == 1) {
+ SDL_SetRenderDrawColor(renderer, 0, 0, 0, 180);
+ SDL_Rect bgRect = { x, y, width, height };
+ SDL_RenderFillRect(renderer, &bgRect);
+ }
+ else if (style == 2) {
+ SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
+ SDL_RenderDrawLine(renderer, x, y, x + width, y);
+ SDL_RenderDrawLine(renderer, x, y + height, x + width, y + height);
+
+ SDL_RenderDrawLine(renderer, x, y, x, y + height);
+ SDL_RenderDrawLine(renderer, x + width, y, x + width, y + height);
+ }
+
+ SDL_SetRenderDrawColor(renderer, fillColour.r, fillColour.g, fillColour.b, fillColour.a);
+ int panelWidth = width * percentage;
+ SDL_Rect fillRect = { x+1, y+1, panelWidth -1, height-1 };
+ SDL_RenderFillRect(renderer, &fillRect);
+}
+
+void renderStatsPanel() {
+ int panelWidth = 300;
+ int panelHeight = 200;
+ int outerPad = 2;
+ int innerPad = 16;
+
+ int panelX = WINDOW_WIDTH - panelWidth - outerPad;
+ int panelY = WINDOW_HEIGHT - panelHeight - outerPad;
+
+ renderBasicPanel(panelX, panelY, panelWidth, panelHeight);
+ renderString(panelX + innerPad, panelY + innerPad, activeCombatant->name.c_str());
+
+ int portraitWidth = 80;
+ int portraitHeight = 80;
+
+ int portraitX = panelX + 16;
+ int portraitY = panelY + 50;
+ renderBasicPanel(portraitX, portraitY, portraitWidth, portraitHeight);
+ SDL_Rect characterDRect = { portraitX + 8, portraitY + 8, portraitWidth - 16, portraitHeight - 16};
+ SDL_Rect characterSRect = { 0 , 0, 32, 32 };
+ SDL_Texture* portraitTexture = activeCombatant->texture;
+ S2DE::renderTexture(&renderer, &portraitTexture, &characterSRect, &characterDRect);
+
+
+ renderString(portraitX + portraitWidth + innerPad, portraitY, "HP:");
+ int valPad = 100;
+ char hpStr[10];
+ sprintf(hpStr, "%d/%d", activeCombatant->currentHP, activeCombatant->maxHP);
+ renderString(portraitX + portraitWidth + innerPad + valPad, portraitY, hpStr);
+
+ float hpLvl = (float)activeCombatant->currentHP / (float)activeCombatant->maxHP;
+ renderMeter(portraitX + portraitWidth + innerPad, portraitY + innerPad, panelWidth - (innerPad * 3) - portraitWidth, 20, hpLvl, hpColour, 2);
+
+ renderString(portraitX + portraitWidth + innerPad, portraitY + innerPad + 22, "MP:");
+
+ char mpStr[10];
+ sprintf(mpStr, "%d/%d", activeCombatant->currentMP, activeCombatant->maxMP);
+ renderString(portraitX + portraitWidth + innerPad + valPad, portraitY + innerPad + 22, mpStr);
+
+ float mpLvl = (float)activeCombatant->currentMP / (float)activeCombatant->maxMP;
+ renderMeter(portraitX + portraitWidth + innerPad, portraitY + innerPad + 42, panelWidth - (innerPad * 3) - portraitWidth, 20, mpLvl, mpColour, 2);
+
+}
+
+void renderActionsPanel() {
+ int panelWidth = 150;
+ int panelHeight = 150;
+
+ //int panelX = WINDOW_WIDTH / 2 - panelWidth / 2;
+ int panelX = 100;
+ int panelY = WINDOW_HEIGHT - panelHeight - 2;
+
+ renderBasicPanel(panelX, panelY, panelWidth, panelHeight);
+
+ int leftPad = 40;
+ int topPad = 20;
+ int itemSpacing = 30;
+
+ for (int i = 0; i < numActions; ++i) {
+ // a bit of a brittle way of doing this
+ if (i == 0 && !activeCombatant->canMove) {
+ renderString(panelX + leftPad, panelY + topPad + itemSpacing * i, actions[i], font_grey);
+ }
+ else if (i == 1 && !activeCombatant->canAttack) {
+ renderString(panelX + leftPad, panelY + topPad + itemSpacing * i, actions[i], font_grey);
+ }
+ else {
+ renderString(panelX + leftPad, panelY + topPad + itemSpacing * i, actions[i]);
+ }
+ }
+
+ renderString(panelX + 20, panelY + topPad + (itemSpacing * currentAction), ">");
+}
+
+void renderUIFull() {
+ renderActionsPanel();
+ renderStatsPanel();
+}
+
+void renderCombat() {
+ renderTilemap(arena, arenaWidth, arenaHeight, combatCam);
+ renderCombatants();
+ renderTilemap(fogLayer, arenaWidth, arenaHeight, combatCam); // render the fog layer
+}
+
+void renderCombatants() {
+
+ for (Character c : combatants) {
+
+ if (c.alive) {
+ S2DE::Vec2<float> pos = c.position;
+ S2DE::Rect charDRect = S2DE::worldToScreenRect(&combatCam, &pos, 2, WINDOW_WIDTH, WINDOW_HEIGHT, 32, 32);
+ S2DE::Rect charSRect = { 0, 0, 32, 32 };
+
+ // disabled for now
+ //if (c.allied) {
+ // // do the looking logic only for allied characters
+ // int playerFrame = 1;
+ // int playerDir = 0;
+
+ // Character enemy = combatants.at(1);
+
+ // int lookDeltaX = (enemy.position.x + 0.5) - (c.position.x + 0.5);
+ // int lookDeltaY = (enemy.position.y + 0.5) - (c.position.y + 0.5);
+
+ // if (abs(lookDeltaX) > abs(lookDeltaY)) {
+ // if (lookDeltaX > 0) playerDir = 2;
+ // else playerDir = 6;
+ // }
+ // else {
+ // if (lookDeltaY > 0) playerDir = 4;
+ // else playerDir = 0;
+ // }
+
+ // charSRect = { playerFrame * 32, playerDir * 32, 32, 32 };
+ //}
+
+ SDL_Texture* texture = c.texture;
+ S2DE::renderTexture(&renderer, &texture, &charSRect, &charDRect);
+
+ float hpLvl = (float)c.currentHP / (float)c.maxHP;
+ renderMeter(charDRect.x + 6, charDRect.y - 6, charDRect.w - 12, 6, hpLvl, hpColour, 1);
+ }
+ }
+
+ // render the little arrow
+ S2DE::Vec2<float> arrowPos = activeCombatant->position;
+
+ arrowPos.y -= 0.7;
+ S2DE::Rect arrowDRect = S2DE::worldToScreenRect(&combatCam, &arrowPos, 2, WINDOW_WIDTH, WINDOW_HEIGHT, 32, 32);
+ S2DE::renderTexture(&renderer, &indicatorSprite, NULL, &arrowDRect);
+}
+
+void initCombatScene()
+{
+ // I think all resources should be initialized and cleaned up in the globals tbh
+ arena = new WorldCell * [arenaHeight];
+ for (int i = 0; i < arenaHeight; ++i) arena[i] = new WorldCell[arenaWidth];
+
+ fogLayer = new WorldCell * [arenaHeight];
+ for (int i = 0; i < arenaHeight; ++i) fogLayer[i] = new WorldCell[arenaWidth];
+
+ loadWorld("maps/combat_arena_1.tmj", arena, arenaWidth, arenaHeight);
+ loadWorld("maps/combat_arena_1_fog.tmj", fogLayer, arenaWidth, arenaHeight);
+
+ arrowSprite = S2DE::loadTexture("assets/arrow.png", &renderer);
+ indicatorSprite = S2DE::loadTexture("assets/little_indicator.png", &renderer);
+ indicatorRedSprite = S2DE::loadTexture("assets/little_indicator_red.png", &renderer);
+
+ //Character player = { true, "Player", 100, 100, 50, 50, 10.0f, 3.0f, player_sheet, { 23.0f, 13.0f } };
+ Character player;
+ player.allied = true;
+ player.name = "Player";
+ player.maxHP = 100;
+ player.currentHP = player.maxHP;
+ player.maxMP = 50;
+ player.currentMP = player.maxMP;
+ player.agility = 10.0f;
+ player.moveDist = 3.0f;
+ player.texture = player_forward_texture;
+ player.position = { arenaCenter.x, arenaCenter.y + 2 };
+ player.attack = 5;
+
+
+ Character monica;
+ monica.allied = true;
+ monica.name = "Monica";
+ monica.maxHP = 100;
+ monica.currentHP = monica.maxHP;
+ monica.maxMP = 50;
+ monica.currentMP = monica.maxMP;
+ monica.agility = 8.0f;
+ monica.moveDist = 6.0f;
+ monica.texture = monica_texture;
+ monica.position = { player.position.x - 2, player.position.y };
+ monica.attack = 6;
+
+ Character enemy;
+ enemy.allied = false;
+ enemy.name = "Bird";
+ enemy.maxHP = 25;
+ enemy.currentHP = enemy.maxHP;
+ enemy.maxMP = 10;
+ enemy.currentMP = enemy.maxMP;
+ enemy.agility = 5.0f;
+ enemy.moveDist = 1.5f;
+ enemy.texture = bird;
+ enemy.position = { arenaCenter.x, arenaCenter.y - 2 };
+ enemy.attack = 2;
+
+ Character frog;
+ frog.allied = false;
+ frog.name = "Frog";
+ frog.currentHP = frog.maxHP = 10;
+ frog.currentMP = frog.maxMP = 10;
+ frog.agility = 2;
+ frog.moveDist = 2.0f;
+ frog.texture = frog_texture;
+ frog.position = { arenaCenter.x - 1, arenaCenter.y - 2 };
+ frog.attack = 2;
+
+ combatants.push_back(player);
+ combatants.push_back(monica);
+ combatants.push_back(enemy);
+ combatants.push_back(frog);
+
+
+ // calculates turn order. Can expand the lambda to support other things
+ std::sort(combatants.begin(), combatants.end(), [](const Character& a, const Character& b) {return a.agility > b.agility; });
+
+ activeCombatant = &combatants.at(0);
+ moveCursorPos = activeCombatant->position;
+}
+
+
+void interpolatePosition(S2DE::Vec2<float>* entityPos, S2DE::Vec2<float> destination, float* t, float speed) {
+ *t += speed;
+
+ entityPos->x = (1.0f - *t) * entityPos->x + *t * destination.x;
+ entityPos->y = (1.0f - *t) * entityPos->y + *t * destination.y;
+}
+
+void moveToPosition(Character* c, S2DE::Vec2<float> destination, float speed) {
+ float dist = distance(c->position, destination);
+
+ S2DE::Vec2<float> direction {destination.x - c->position.x, destination.y - c->position.y};
+
+ direction.x /= dist;
+ direction.y /= dist;
+
+ if (dist <= speed) {
+ c->position.x = destination.x;
+ c->position.y = destination.y;
+ }
+ else {
+ c->position.x += direction.x * speed;
+ c->position.y += direction.y * speed;
+ }
+}
+
+void actionSelectState(double delta) {
+ S2DE::updateInputState(&inputState);
+
+ // handle inputs
+ if (inputState.up) {
+ inputState.up = false;
+ currentAction--;
+ if (currentAction < 0) currentAction = numActions - 1;
+ }
+ else if (inputState.down) {
+ inputState.down = false;
+ currentAction++;
+ if (currentAction > numActions - 1) currentAction = 0;
+ }
+ else if (inputState.rtrn) {
+ inputState.rtrn = false;
+ switch (currentAction) {
+ case 0:
+ if (activeCombatant->canMove) {
+ currentCombatState = PLAYER_MOVE;
+ moveCursorPos = activeCombatant->position;
+ }
+ break;
+ case 1: // attack selected
+ if (activeCombatant->canAttack) {
+ // filter the targets - Have to do it here too in case the player attacks without moving?
+ targets.clear();
+ for (auto& c : combatants) {
+ if (!c.allied && c.alive) {
+ float dist = distance(activeCombatant->position, c.position);
+ if (dist <= activeCombatant->basicAttackRange) {
+ targets.push_back(&c);
+ }
+ }
+ }
+
+ currentCombatState = SELECT_TARGET;
+ }
+ break;
+ case 2: // end selected
+ currentCombatState = TURN_ORDER;
+ break;
+
+ default:
+ inputState.quit = true;
+ break;
+ }
+ }
+
+ SDL_RenderClear(renderer);
+ renderCombat();
+ renderUIFull();
+ SDL_RenderPresent(renderer);
+}
+
+void playerMoveActionState(double delta) {
+ S2DE::updateInputState(&inputState);
+
+ frameTimer += delta;
+
+ Character player = *activeCombatant;
+
+ if (frameTimer > frameTime) {
+
+ frameTimer = 0;
+
+ float cursorSpeed = 0.05;
+
+ S2DE::Vec2<float> newCursorPos = moveCursorPos;
+
+ float newY = moveCursorPos.y;
+ float newX = moveCursorPos.x;
+
+ if (inputState.right) {
+ newX += cursorSpeed;
+ }
+ if (inputState.left) {
+ newX -= cursorSpeed;
+ }
+ if (inputState.up) {
+ newY -= cursorSpeed;
+ }
+ if (inputState.down) {
+ newY += cursorSpeed;
+ }
+
+ if (inputState.rtrn) {
+ inputState.rtrn = false;
+
+ activeCombatant->combatMoveDestination = moveCursorPos;
+ currentCombatState = MOVE_TO_POSITION;
+ }
+
+ if (distance({ moveCursorPos.x, newY }, player.position) <= player.moveDist
+ && checkCollisionPoint({ moveCursorPos.x + 0.5f, newY + 0.5f }, arena) == false) {
+ moveCursorPos.y = newY;
+ }
+ if (distance({ newX, moveCursorPos.y }, player.position) <= player.moveDist
+ && checkCollisionPoint({ newX + 0.5f, moveCursorPos.y + 0.5f }, arena) == false) {
+ moveCursorPos.x = newX;
+ }
+
+ }
+
+
+ // compute characters in melee range
+ targets.clear();
+ for (auto& c : combatants) {
+ if (!c.allied && c.alive) {
+ float dist = distance(moveCursorPos, c.position);
+ if (dist <= activeCombatant->basicAttackRange) {
+ targets.push_back(&c);
+ }
+ }
+ }
+
+
+ SDL_RenderClear(renderer);
+
+ renderTilemap(arena, arenaWidth, arenaHeight, combatCam);
+
+
+ S2DE::Vec2<float> playerCenterWorld = { player.position.x + 0.5, player.position.y + 0.5 };
+ S2DE::Vec2<int> playerCenterScreen = S2DE::worldToScreenPoint(&combatCam, &playerCenterWorld, 64, WINDOW_WIDTH, WINDOW_HEIGHT);
+
+ S2DE::renderCircle(&renderer, playerCenterScreen, (player.moveDist) * 64, { 0, 255, 255, 125 });
+
+
+ S2DE::Vec2<float> lineEndWorld = { moveCursorPos.x + 0.5, moveCursorPos.y + 0.5 };
+ S2DE::Vec2<int> lineEndPos = S2DE::worldToScreenPoint(&combatCam, &lineEndWorld, 64, WINDOW_WIDTH, WINDOW_HEIGHT);
+
+ SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
+ SDL_RenderDrawLine(renderer, playerCenterScreen.x, playerCenterScreen.y, lineEndPos.x, lineEndPos.y);
+
+ // render lines to targets in range
+ SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
+ for (auto t : targets) {
+ S2DE::Vec2<float> targetCenter = { t->position.x + 0.5, t->position.y + 0.5 };
+ S2DE::Vec2<int> targetCenterScreen = S2DE::worldToScreenPoint(&combatCam, &targetCenter, 64, WINDOW_WIDTH, WINDOW_HEIGHT);
+ SDL_RenderDrawLine(renderer, lineEndPos.x, lineEndPos.y, targetCenterScreen.x, targetCenterScreen.y);
+ }
+
+
+ renderCombatants();
+
+ S2DE::Rect cursorDRect = S2DE::worldToScreenRect(&combatCam, &moveCursorPos, 2, WINDOW_WIDTH, WINDOW_HEIGHT, 32, 32);
+ S2DE::renderTexture(&renderer, &arrowSprite, NULL, &cursorDRect);
+
+ renderTilemap(fogLayer, arenaWidth, arenaHeight, combatCam); // draw the fog layer
+
+ renderActionsPanel();
+ renderStatsPanel();
+ SDL_RenderPresent(renderer);
+}
+
+void selectAttackTarget(double delta) {
+ S2DE::updateInputState(&inputState);
+
+ frameTimer += delta;
+
+ if (frameTimer > frameTime) {
+ frameTimer = 0;
+ if (inputState.rtrn) {
+ inputState.rtrn = false;
+ if (targets.size() > 0) {
+ currentSpell = BASIC_ATTACK;
+ currentCombatState = DO_ATTACK;
+ }
+ else {
+ currentCombatState = ACTION_SELECT;
+ }
+
+ }
+
+ if (inputState.right) {
+ // cycle up through targets
+ inputState.right = false; // gotta find a better way to do this
+
+ currentTargetIdx += 1;
+ if (currentTargetIdx > targets.size() - 1) currentTargetIdx = 0;
+ }
+ else if (inputState.left) {
+ inputState.left = false;
+ // cycle "down"through targets
+ currentTargetIdx -= 1;
+ if (currentTargetIdx < 0) currentTargetIdx = targets.size() - 1;
+ }
+ }
+
+ SDL_RenderClear(renderer);
+
+
+ renderTilemap(arena, arenaWidth, arenaHeight, combatCam);
+
+ renderCombatants();
+
+ // render the little red arrow above all valid targets
+ if (targets.size() > 0) {
+ S2DE::Vec2<float> arrowPos = targets.at(currentTargetIdx)->position;
+
+ arrowPos.y -= 0.7;
+ S2DE::Rect arrowDRect = S2DE::worldToScreenRect(&combatCam, &arrowPos, 2, WINDOW_WIDTH, WINDOW_HEIGHT, 32, 32);
+ S2DE::renderTexture(&renderer, &indicatorRedSprite, NULL, &arrowDRect);
+ }
+
+
+ renderTilemap(fogLayer, arenaWidth, arenaHeight, combatCam); // draw the fog layer
+
+
+
+ renderUIFull();
+ renderString(10, 10, "SELECT TARGET STATE", debug_font);
+
+
+ if (targets.size() == 0) {
+ renderString(10, 64, "NO TARGETS IN RANGE", debug_font);
+ }
+ else {
+ int num_targets = targets.size();
+ char str[22];
+ sprintf(str, "AVAILABLE_TARGETS: %i", num_targets);
+ renderString(10, 64, str, debug_font);
+ }
+
+ SDL_RenderPresent(renderer);
+
+}
+
+void doAttack(double delta) {
+ // for now just subtract the combatants' dmg from the targets hp and move on
+
+
+ if (currentTargetIdx == -1) {
+ // AOE, apply to all targets in range ?
+ }
+ else {
+ Character* target = targets.at(currentTargetIdx);
+ target->currentHP -= activeCombatant->attack;
+
+ if (target->currentHP <= 0) {
+ target->alive = false;
+ target->currentHP = 0;
+ }
+ }
+
+
+
+ targets.clear();
+ currentCombatState = ACTION_SELECT;
+ activeCombatant->canAttack = false;
+}
+
+
+void enemyMoveActionState(double delta) {
+ S2DE::updateInputState(&inputState);
+
+ Character enemy = *activeCombatant;
+
+ // implicitly grabs the character with the highest agility for now
+ Character agro = *std::find_if(combatants.begin(), combatants.end(), [](const Character& c) {return c.allied; });
+
+ S2DE::Vec2<float> dirToAgro = { agro.position.x - enemy.position.x, agro.position.y - enemy.position.y };
+ float distToAgro = distance(enemy.position, agro.position);
+
+ dirToAgro.x /= distToAgro;
+ dirToAgro.y /= distToAgro;
+
+ S2DE::Vec2<float> movePos = { enemy.position.x + dirToAgro.x * enemy.moveDist, enemy.position.y + dirToAgro.y * enemy.moveDist};
+
+ activeCombatant->combatMoveDestination = movePos;
+ currentCombatState = MOVE_TO_POSITION;
+}
+
+void movingCharacterState(double delta) {
+ S2DE::updateInputState(&inputState);
+
+ frameTimer += delta;
+
+ if (frameTimer > frameTime) {
+ frameTimer = 0;
+
+ static float speed = 0.05f;
+
+ if (inputState.rtrn) {
+ // skip
+ inputState.rtrn = false;
+ activeCombatant->position = activeCombatant->combatMoveDestination;
+ }
+
+ moveToPosition(activeCombatant, activeCombatant->combatMoveDestination, speed);
+
+ if (activeCombatant->position.x == activeCombatant->combatMoveDestination.x && activeCombatant->position.y == activeCombatant->combatMoveDestination.y) {
+ // move completed. if allied, return to select action
+ if (activeCombatant->allied) {
+ activeCombatant->canMove = false; // TODO: support characters that can move twice?
+ currentCombatState = ACTION_SELECT;
+ }
+ else {
+ currentCombatState = TURN_ORDER;
+ }
+ }
+
+ // TODO: Ensure all rendering is happening at the same FPS
+ SDL_RenderClear(renderer);
+ renderCombat();
+ // dont show the ui in this state
+ SDL_RenderPresent(renderer);
+ }
+
+}
+
+
+void determineNextTurnState(double delta) {
+
+ currentCombatantIndex += 1;
+
+ // WARNING: this system does not support the case where agility changes mid-fight. will need to re-sort if that happens
+ if (currentCombatantIndex > combatants.size() - 1) currentCombatantIndex = 0;
+
+ activeCombatant = &combatants.at(currentCombatantIndex);
+
+ if (activeCombatant->alive) { // skip dead characters
+ if (activeCombatant->allied) {
+ // reset combatants actions
+ activeCombatant->canAttack = true;
+ activeCombatant->canMove = true;
+ currentAction = 0;
+ currentCombatState = ACTION_SELECT;
+ }
+ else {
+ //currentCombatState = ENEMY_MOVE;
+ }
+ }
+
+}
+
+
+void runCombat(double delta) {
+
+ static double frameTimer = 0;
+ static const double frameTime = 1.0f / 60.0f;
+
+ runStateFunction(currentCombatState, delta);
+
}
\ No newline at end of file