--- /dev/null
+#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
+ \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
+\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
+ ENEMY_MOVE,\r
+ MOVE_TO_POSITION,\r
+};\r
+\r
+CombatState currentCombatState = TURN_ORDER;\r
+\r
+\r
+static double frameTimer = 0;\r
+static const double frameTime = 1.0f / 60.0f;\r
+\r
+\r
+//bool playerTurn = true;\r
+\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
+\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
+ 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
+ renderString(portraitX + portraitWidth + innerPad + valPad, portraitY, "50/100");\r
+\r
+ renderMeter(portraitX + portraitWidth + innerPad, portraitY + innerPad, panelWidth - (innerPad * 3) - portraitWidth, 20, 0.5, hpColour, 2);\r
+\r
+ renderString(portraitX + portraitWidth + innerPad, portraitY + innerPad + 22, "MP:");\r
+\r
+ renderString(portraitX + portraitWidth + innerPad + valPad, portraitY + innerPad + 22, "50/100");\r
+ \r
+ renderMeter(portraitX + portraitWidth + innerPad, portraitY + innerPad + 42, panelWidth - (innerPad * 3) - portraitWidth, 20, 0.5, 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
+ 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
+ renderMeter(charDRect.x + 6, charDRect.y - 6, charDRect.w - 12, 6, 0.5, hpColour, 1);\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
+\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
+\r
+ Character enemy;\r
+ enemy.allied = false;\r
+ enemy.name = "Bird";\r
+ enemy.maxHP = 25;\r
+ enemy.currentHP = player.maxHP;\r
+ enemy.maxMP = 10;\r
+ enemy.currentMP = player.maxMP;\r
+ enemy.agility = 5.0f;\r
+ enemy.moveDist = 1.5f;\r
+ enemy.texture = bird;\r
+ enemy.position = { arenaCenter.x, arenaCenter.y - 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
+\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\r
+ targets.clear();\r
+ //float range = activeCombatant->basicAttackRange;\r
+ std::copy_if(combatants.begin(), combatants.end(), std::back_inserter(targets),\r
+ [](const Character& c) {\r
+ if (!c.allied) {\r
+ float dist = distance(activeCombatant->position, c.position);\r
+ if (dist <= activeCombatant->basicAttackRange) {\r
+ return true;\r
+ }\r
+ }\r
+ return false;\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
+ 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
+ renderCombatants();\r
+\r
+ S2DE::Rect cursorDRect = S2DE::worldToScreenRect(&combatCam, &moveCursorPos, 4, WINDOW_WIDTH, WINDOW_HEIGHT, 16, 16);\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
+ currentCombatState = ACTION_SELECT;\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
+\r
+ S2DE::Vec2<float> playerCenterWorld = { activeCombatant->position.x + 0.5, activeCombatant->position.y + 0.5 };\r
+ S2DE::Vec2<int> playerCenterScreen = S2DE::worldToScreenPoint(&combatCam, &playerCenterWorld, 64, WINDOW_WIDTH, WINDOW_HEIGHT);\r
+\r
+ S2DE::renderCircle(&renderer, playerCenterScreen, (activeCombatant->basicAttackRange) * 64, { 255, 0, 0, 125 });\r
+\r
+\r
+ renderCombatants();\r
+ \r
+\r
+ // render the little red arrow above all valid targets\r
+ //for (auto target : 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
+\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 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->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
+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
+}
\ No newline at end of file
--- /dev/null
+#include <iostream>\r
+#include <fstream>\r
+#include <SDL.h>\r
+#include <SDL_image.h>\r
+#include <SDL_mixer.h>\r
+#include <nlohmann/json.hpp>\r
+\r
+#include <2DEngine.h>\r
+\r
+#include "globals.h"\r
+#include "main_menu.h"\r
+#include "combat.h"\r
+#include "tilemap.h"\r
+\r
+GameState gameState = MAIN_MENU;\r
+//GameState gameState = COMBAT;\r
+\r
+using json = nlohmann::json;\r
+\r
+SDL_Window* window = nullptr;\r
+SDL_Renderer* renderer = nullptr;\r
+\r
+S2DE::InputState inputState;\r
+\r
+\r
+WorldCell** world;\r
+\r
+S2DE::Camera camera;\r
+\r
+// These things should be defined in the overworld\r
+S2DE::Vec2<float> playerPos = { 30, 19 };\r
+int playerFrame = 0;\r
+int playerDir = 4;\r
+\r
+S2DE::Vec2<float> birdPos = { 31, 16 };\r
+bool enemyDirection = 0; // 0 = left, 1 = right\r
+\r
+//void loadWorld(const char* worldPath, WorldCell world[WORLD_HEIGHT][WORLD_WIDTH]);\r
+\r
+//S2DE::Rect renderString(int startX, int startY, const char* string);\r
+void renderWorld();\r
+//void renderUI();\r
+\r
+//bool checkCollision(S2DE::Vec2<float> worldPos);\r
+\r
+\r
+void renderWorld()\r
+{\r
+\r
+ // render enemy\r
+\r
+ S2DE::Rect enemyDRect = S2DE::worldToScreenRect(&camera, &birdPos, 2, WINDOW_WIDTH, WINDOW_HEIGHT, 32, 32);\r
+ S2DE::Rect enemySRect = { 0, 32 * enemyDirection, 32, 32 };\r
+ S2DE::renderTexture(&renderer, &bird, &enemySRect, &enemyDRect);\r
+\r
+\r
+ // render player\r
+\r
+ S2DE::Rect playerDRect = S2DE::worldToScreenRect(&camera, &playerPos, 2, WINDOW_WIDTH, WINDOW_HEIGHT, 32, 32);\r
+\r
+ S2DE::Rect playerSRect = { playerFrame * 32, playerDir * 32, 32, 32 };\r
+ S2DE::renderTexture(&renderer, &player_sheet, &playerSRect, &playerDRect);\r
+\r
+}\r
+\r
+int main(int argc, char** args)\r
+{\r
+ S2DE::initRendering("Dragon Survivors", WINDOW_WIDTH, WINDOW_HEIGHT, &window, &renderer);\r
+ initResources();\r
+ initCombatScene();\r
+\r
+ world = new WorldCell * [WORLD_HEIGHT];\r
+ for (int i = 0; i < WORLD_HEIGHT; ++i) world[i] = new WorldCell[WORLD_WIDTH];\r
+\r
+ loadWorld("maps/overworld_1.tmj", world, WORLD_WIDTH, WORLD_HEIGHT);\r
+\r
+ bool running = true;\r
+\r
+ double currentTime = 0.0;\r
+ double last = 0.0;\r
+ double delta = 0.0;\r
+\r
+ double frameCounter = 0;\r
+ double frameTime = 1.0 / 60; // 60 hrz\r
+\r
+ double animCounter = 0;\r
+ double animTime = 1.0 / 3;\r
+\r
+\r
+ double enemyWalkTime = 0.5; // walks each dir for 2 seconds\r
+ double enemyWalkTimer = 0;\r
+ \r
+\r
+ Mix_VolumeMusic(MIX_MAX_VOLUME / 4);\r
+ //Mix_PlayMusic(background_music, -1);\r
+\r
+ while (inputState.quit != true)\r
+ {\r
+ currentTime = SDL_GetTicks();\r
+ delta = (currentTime - last) / 1000.0f;\r
+ last = currentTime;\r
+\r
+ if (gameState == MAIN_MENU)\r
+ {\r
+ mainMenu(delta); \r
+ }\r
+ else if (gameState == OVERWORLD)\r
+ {\r
+\r
+ frameCounter += delta;\r
+ animCounter += delta;\r
+\r
+ enemyWalkTimer += delta;\r
+\r
+ S2DE::updateInputState(&inputState);\r
+\r
+ if (frameCounter > frameTime)\r
+ {\r
+ frameCounter = 0;\r
+\r
+ // update player\r
+ float walkSpeed = 0.08;\r
+\r
+ float newX = playerPos.x;\r
+ float newY = playerPos.y;\r
+ float finalX = newX;\r
+ float finalY = newY;\r
+\r
+ if (inputState.right) {\r
+ newX += walkSpeed;\r
+ playerDir = 2;\r
+ }\r
+ if (inputState.left) {\r
+ newX -= walkSpeed;\r
+ playerDir = 6;\r
+ }\r
+ if (inputState.up) {\r
+ newY -= walkSpeed;\r
+ playerDir = 0;\r
+ }\r
+ if (inputState.down) {\r
+ newY += walkSpeed;\r
+ playerDir = 4;\r
+ }\r
+\r
+ if (!checkCollisionRect({ playerPos.x, newY }, world))\r
+ {\r
+ // can move on the y\r
+ finalY = newY;\r
+ }\r
+\r
+ if (!checkCollisionRect({ newX, playerPos.y }, world))\r
+ {\r
+ // can move on the X\r
+ finalX = newX;\r
+ }\r
+\r
+ playerPos.x = finalX;\r
+ playerPos.y = finalY;\r
+\r
+ camera = playerPos;\r
+\r
+ if (enemyWalkTimer > enemyWalkTime)\r
+ {\r
+ // flip the direction and reset the timer\r
+ enemyDirection = !enemyDirection;\r
+ enemyWalkTimer = 0;\r
+ }\r
+\r
+ float enemyWalkSpeed = 0.04;\r
+ if (enemyDirection) // true == 1 == going right\r
+ {\r
+ birdPos.x += enemyWalkSpeed;\r
+ }\r
+ else // going left\r
+ {\r
+ birdPos.x -= enemyWalkSpeed;\r
+ }\r
+\r
+ }\r
+ \r
+\r
+ static bool forward = true;\r
+ if (animCounter >= animTime)\r
+ {\r
+ animCounter = 0;\r
+\r
+ if (forward)\r
+ {\r
+ playerFrame += 1;\r
+ if (playerFrame >= 2)\r
+ {\r
+ forward = false;\r
+ }\r
+ }\r
+ else\r
+ {\r
+ playerFrame -= 1;\r
+ if (playerFrame <= 0)\r
+ {\r
+ forward = true;\r
+ }\r
+ }\r
+ }\r
+\r
+ SDL_RenderClear(renderer);\r
+ {\r
+ renderTilemap(world, WORLD_WIDTH, WORLD_HEIGHT, camera);\r
+ renderWorld();\r
+ }\r
+ SDL_RenderPresent(renderer);\r
+ }\r
+ else if (gameState == COMBAT) {\r
+ runCombat(delta);\r
+ }\r
+ }\r
+\r
+\r
+ for (int i = 0; i < WORLD_HEIGHT; ++i) delete[] world[i];\r
+ delete[] world;\r
+\r
+ S2DE::quitRendering(&renderer, &window);\r
+ Mix_Quit();\r
+\r
+ SDL_DestroyTexture(environment_sheet);\r
+ // destroy the other shit here\r
+ // add mix_quit to dll\r
+ return 0;\r
+}
\ No newline at end of file