initial commit
authorStan_Lewry <stanley.jml@gmail.com>
Sun, 25 Aug 2024 09:45:51 +0000 (10:45 +0100)
committerStan Lewry <stanley.jml@gmail.com>
Sun, 25 Aug 2024 11:04:04 +0000 (12:04 +0100)
combat.cpp [new file with mode: 0644]
combat.h [new file with mode: 0644]
globals.cpp [new file with mode: 0644]
globals.h [new file with mode: 0644]
main.cpp [new file with mode: 0644]
main_menu.cpp [new file with mode: 0644]
main_menu.h [new file with mode: 0644]
tilemap.cpp [new file with mode: 0644]
tilemap.h [new file with mode: 0644]
ui.cpp [new file with mode: 0644]
ui.h [new file with mode: 0644]

diff --git a/combat.cpp b/combat.cpp
new file mode 100644 (file)
index 0000000..529c159
--- /dev/null
@@ -0,0 +1,665 @@
+#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
diff --git a/combat.h b/combat.h
new file mode 100644 (file)
index 0000000..71bd349
--- /dev/null
+++ b/combat.h
@@ -0,0 +1,5 @@
+#pragma once\r
+\r
+void initCombatScene();\r
+\r
+void runCombat(double deltaTime);
\ No newline at end of file
diff --git a/globals.cpp b/globals.cpp
new file mode 100644 (file)
index 0000000..e3db2d1
--- /dev/null
@@ -0,0 +1,41 @@
+#include "globals.h"\r
+\r
+SDL_Texture* environment_sheet = nullptr;\r
+SDL_Texture* player_sheet = nullptr;\r
+SDL_Texture* water_test_sheet = nullptr;\r
+SDL_Texture* font_sheet = nullptr;\r
+SDL_Texture* debug_font = nullptr;\r
+SDL_Texture* font_grey = nullptr;\r
+SDL_Texture* ui_frame_sheet = nullptr;\r
+SDL_Texture* bird = nullptr;\r
+SDL_Texture* monica_texture = nullptr;\r
+SDL_Texture* player_forward_texture = nullptr;\r
+SDL_Texture* frog_texture = nullptr;\r
+Mix_Music* background_music = nullptr;\r
+\r
+\r
+static const char* environment_sheet_path = "assets/dragon_survivors_environment_1.png";\r
+static const char* player_sheet_path = "assets/player.png";\r
+static const char* water_test_sheet_path = "assets/water_test.png";\r
+static const char* font_sheet_path = "assets/simple_6x8_black.png";\r
+static const char* ui_frame_sheet_path = "assets/frame.png";\r
+static const char* bird_path = "assets/bird_32.png";\r
+static const char* bg_mus_path = "assets/TownTheme.mp3";\r
+\r
+void initResources()\r
+{\r
+       environment_sheet = S2DE::loadTexture(environment_sheet_path, &renderer);\r
+       player_sheet = S2DE::loadTexture(player_sheet_path, &renderer);\r
+       water_test_sheet = S2DE::loadTexture(water_test_sheet_path, &renderer);\r
+       font_sheet = S2DE::loadTexture(font_sheet_path, &renderer);\r
+       debug_font = S2DE::loadTexture("assets/simple_6x8_red.png", &renderer);\r
+       font_grey = S2DE::loadTexture("assets/simple_6x8_grey.png", &renderer);\r
+       ui_frame_sheet = S2DE::loadTexture(ui_frame_sheet_path, &renderer);\r
+       bird = S2DE::loadTexture(bird_path, &renderer);\r
+\r
+       monica_texture = S2DE::loadTexture("assets/Monica_sprite.png", &renderer);\r
+       player_forward_texture = S2DE::loadTexture("assets/player_forward.png", &renderer);\r
+       frog_texture = S2DE::loadTexture("assets/Frog_sprite.png", &renderer);\r
+       background_music = Mix_LoadMUS(bg_mus_path);\r
+}\r
+\r
diff --git a/globals.h b/globals.h
new file mode 100644 (file)
index 0000000..9d25677
--- /dev/null
+++ b/globals.h
@@ -0,0 +1,57 @@
+#pragma once\r
+\r
+#include <SDL.h>\r
+#include <SDL_image.h>\r
+#include <SDL_mixer.h>\r
+#include <2DEngine.h>\r
+\r
+\r
+#define WINDOW_WIDTH 1280\r
+#define WINDOW_HEIGHT 720\r
+\r
+extern SDL_Window* window;\r
+extern SDL_Renderer* renderer;\r
+\r
+\r
+// maybe a resources.h? \r
+extern SDL_Texture* environment_sheet;\r
+extern SDL_Texture* player_sheet;\r
+extern SDL_Texture* water_test_sheet;\r
+extern SDL_Texture* font_sheet;\r
+extern SDL_Texture* debug_font;\r
+extern SDL_Texture* font_grey;\r
+extern SDL_Texture* ui_frame_sheet;\r
+extern SDL_Texture* bird;\r
+extern SDL_Texture* monica_texture;\r
+extern SDL_Texture* player_forward_texture;\r
+extern SDL_Texture* frog_texture;\r
+\r
+extern Mix_Music* background_music;\r
+\r
+void initResources();\r
+\r
+extern S2DE::InputState inputState;\r
+\r
+// These things should be defined in the overworld\r
+extern S2DE::Vec2<float> playerPos;\r
+extern int playerFrame;\r
+extern int playerDir;\r
+\r
+extern S2DE::Vec2<float> birdPos;\r
+extern bool enemyDirection; // 0 = left, 1 = right\r
+\r
+#define WORLD_HEIGHT 200\r
+#define WORLD_WIDTH 200\r
+\r
+//extern WorldCell world[WORLD_HEIGHT][WORLD_WIDTH];\r
+\r
+extern S2DE::Camera camera;\r
+\r
+enum GameState\r
+{\r
+       MAIN_MENU,\r
+       OVERWORLD,\r
+       COMBAT\r
+};\r
+\r
+extern GameState gameState;
\ No newline at end of file
diff --git a/main.cpp b/main.cpp
new file mode 100644 (file)
index 0000000..fe47b97
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,229 @@
+#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
diff --git a/main_menu.cpp b/main_menu.cpp
new file mode 100644 (file)
index 0000000..cc3608f
--- /dev/null
@@ -0,0 +1,66 @@
+#include "main_menu.h"\r
+#include "globals.h"\r
+#include "ui.h"\r
+\r
+static const char* menuItems[] = { "Overworld", "Combat", "Load Game", "Options", "Quit" };\r
+static constexpr int numMenuItems = 5;\r
+int currentMenuItem = 0;\r
+\r
+\r
+void renderMainPanel();\r
+\r
+void renderMainPanel() {\r
+\r
+       int panelWidth = 200;\r
+       int panelHeight = 150;\r
+\r
+       int panelX = WINDOW_WIDTH / 2 - panelWidth / 2;\r
+       int panelY = WINDOW_HEIGHT / 2 - 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 < numMenuItems; ++i) {\r
+               renderString(panelX + leftPad, panelY + topPad + itemSpacing * i, menuItems[i]);\r
+       }\r
+\r
+       renderString(panelX + 20, panelY + topPad + (itemSpacing * currentMenuItem), ">");\r
+\r
+}\r
+\r
+void mainMenu(double deltaTime) {\r
+       S2DE::updateInputState(&inputState);\r
+\r
+       if (inputState.up) {\r
+               inputState.up = false;\r
+               currentMenuItem--;\r
+               if (currentMenuItem < 0) currentMenuItem = numMenuItems - 1;\r
+       }\r
+       else if (inputState.down) {\r
+               inputState.down = false;\r
+               currentMenuItem++;\r
+               if (currentMenuItem > numMenuItems - 1) currentMenuItem = 0;\r
+       }\r
+       else if (inputState.rtrn) {\r
+               inputState.rtrn = false;\r
+               // select the current menu item\r
+               switch (currentMenuItem) {\r
+               case 0:\r
+                       gameState = OVERWORLD;\r
+                       break;\r
+               case 1:\r
+                       gameState = COMBAT;\r
+                       break;\r
+               default:\r
+                       inputState.quit = true;\r
+                       break;\r
+               }\r
+       }\r
+\r
+       SDL_RenderClear(renderer);\r
+       renderMainPanel();\r
+       SDL_RenderPresent(renderer);\r
+}
\ No newline at end of file
diff --git a/main_menu.h b/main_menu.h
new file mode 100644 (file)
index 0000000..5e48827
--- /dev/null
@@ -0,0 +1,4 @@
+#pragma once\r
+\r
+\r
+void mainMenu(double deltaTime);
\ No newline at end of file
diff --git a/tilemap.cpp b/tilemap.cpp
new file mode 100644 (file)
index 0000000..abe76d6
--- /dev/null
@@ -0,0 +1,83 @@
+#include "tilemap.h"\r
+#include "globals.h"\r
+\r
+\r
+using json = nlohmann::json;\r
+\r
+void loadWorld(const char* mapPath, WorldCell** world, int worldWidth, int worldHeight)\r
+{\r
+       json jsonData = S2DE::loadJson(mapPath);\r
+\r
+       json groundData = jsonData["layers"][0]["data"];\r
+\r
+       for (int y = 0; y < worldHeight; ++y)\r
+       {\r
+               for (int x = 0; x < worldWidth; ++x)\r
+               {\r
+                       int jsonIdx = (y * worldHeight) + x;\r
+\r
+                       int groundType = groundData[jsonIdx];\r
+\r
+                       bool solid = groundType > 30; // lazy hack\r
+\r
+                       world[y][x] = { groundType, solid };\r
+               }\r
+       }\r
+}\r
+\r
+void renderTilemap(WorldCell** world, int worldWidth, int worldHeight, S2DE::Camera camera)\r
+{\r
+       for (int y = 0; y < worldHeight; ++y)\r
+       {\r
+               for (int x = 0; x < worldWidth; ++x)\r
+               {\r
+\r
+                       float scale = 4;\r
+                       S2DE::Vec2<float> worldPos = { x, y };\r
+                       SDL_Rect dRect = S2DE::worldToScreenRect(&camera, &worldPos, scale, WINDOW_WIDTH, WINDOW_HEIGHT, 16, 16);\r
+\r
+                       //if (dRect.x >= 0 && dRect.x + dRect.w <= WINDOW_WIDTH - (16 * scale) && dRect.y  >= 0 && dRect.y + dRect.h <= WINDOW_HEIGHT - (16 * scale))\r
+                       if (world[y][x].ground != 160 && dRect.x + dRect.w >= 0 && dRect.x <= WINDOW_WIDTH && dRect.y + dRect.h >= 0 && dRect.y <= WINDOW_HEIGHT)\r
+                       {\r
+\r
+                               if (world[y][x].ground == 71)\r
+                               {\r
+                                       // render water animated\r
+\r
+                                       S2DE::Rect waterSRect = { playerFrame * 16, 0, 16, 16 };\r
+\r
+                                       S2DE::renderTexture(&renderer, &water_test_sheet, &waterSRect, &dRect);\r
+                               }\r
+                               else\r
+                               {\r
+\r
+                                       int tile_idx = world[y][x].ground - 1;\r
+\r
+                                       int sheet_width = 10;\r
+\r
+                                       int tile_x = (tile_idx % sheet_width);\r
+                                       int tile_y = (tile_idx / sheet_width);\r
+\r
+                                       SDL_Rect sRect = { tile_x * 16, tile_y * 16, 16, 16 };\r
+\r
+\r
+                                       S2DE::renderTexture(&renderer, &environment_sheet, &sRect, &dRect);\r
+                               }\r
+\r
+                       }\r
+\r
+               }\r
+       }\r
+}\r
+\r
+bool checkCollisionRect(S2DE::Vec2<float> worldPos, WorldCell** world) {\r
+       return (world[(int)(worldPos.y + 0.55)][(int)(worldPos.x + 0.25)].solid ||\r
+               world[(int)(worldPos.y + 0.55)][(int)(worldPos.x + 0.75)].solid ||\r
+               world[(int)(worldPos.y + 0.95)][(int)(worldPos.x + 0.25)].solid ||\r
+               world[(int)(worldPos.y + 0.95)][(int)(worldPos.x + 0.75)].solid);\r
+\r
+}\r
+\r
+bool checkCollisionPoint(S2DE::Vec2<float> worldPos, WorldCell** world) {\r
+       return world[(int)worldPos.y][(int)worldPos.x].solid;\r
+}
\ No newline at end of file
diff --git a/tilemap.h b/tilemap.h
new file mode 100644 (file)
index 0000000..8aed93f
--- /dev/null
+++ b/tilemap.h
@@ -0,0 +1,18 @@
+#pragma once\r
+\r
+#include <2DEngine.h>\r
+\r
+// same here \r
+struct WorldCell\r
+{\r
+       int ground;\r
+       bool solid;\r
+};\r
+\r
+\r
+void loadWorld(const char* mapPath, WorldCell** world, int worldWidth, int worldHeight);\r
+\r
+void renderTilemap(WorldCell** world, int worldWidth, int worldHeight, S2DE::Camera camera);\r
+\r
+bool checkCollisionRect(S2DE::Vec2<float> worldPos, WorldCell** world);\r
+bool checkCollisionPoint(S2DE::Vec2<float> worldPos, WorldCell** world);
\ No newline at end of file
diff --git a/ui.cpp b/ui.cpp
new file mode 100644 (file)
index 0000000..80070cd
--- /dev/null
+++ b/ui.cpp
@@ -0,0 +1,91 @@
+#include "ui.h"\r
+#include "globals.h"\r
+\r
+\r
+\r
+static constexpr int panelDrawSize = 16 * 2;\r
+\r
+S2DE::Rect topLeftCorner = { 0, 0, 16, 16 };\r
+S2DE::Rect topRightCorner = { 32, 0, 16, 16 };\r
+S2DE::Rect bottomLeftCorner = { 0, 32, 16, 16 };\r
+S2DE::Rect bottomRightCorner = { 32, 32, 16, 16 };\r
+S2DE::Rect leftSide = { 0, 16, 16, 1 };\r
+S2DE::Rect rightSide = { 32, 16, 16, 1 };\r
+S2DE::Rect topSide = { 16, 0, 1, 16 };\r
+S2DE::Rect bottomSide = { 16, 32, 1, 16 };\r
+S2DE::Rect fill = { 10, 10, 1, 1 };\r
+\r
+S2DE::Rect renderString(int startX, int startY, const char* string, SDL_Texture* font) {\r
+       // this part should be in DLL\r
+\r
+       //SDL_Texture* fontTX = debugFont ? debug_font : font_sheet;\r
+       if (!font) {\r
+               font = font_sheet;\r
+       }\r
+\r
+       int glyphWidth = 6;\r
+       int glyphHeight = 8;\r
+\r
+       int glyphScale = 2;\r
+\r
+       S2DE::Rect finalRect = { startX, startY, 0, glyphHeight * glyphScale };\r
+\r
+       int i = 0;\r
+       while (string[i] != '\0')\r
+       {\r
+               int sheetX = (int)string[i] - 32;\r
+\r
+\r
+               S2DE::Rect sRect = { sheetX * glyphWidth, 0, glyphWidth, glyphHeight };\r
+               S2DE::Rect dRect = { startX + (i * glyphWidth * glyphScale), startY, glyphWidth * glyphScale, glyphHeight * glyphScale };\r
+\r
+               S2DE::renderTexture(&renderer, &font, &sRect, &dRect);\r
+\r
+               i++;\r
+       }\r
+\r
+       finalRect.w = i * glyphWidth * glyphScale;\r
+\r
+       return finalRect;\r
+}\r
+\r
+void renderBasicPanel(int x, int y, int width, int height) {\r
+       // smallest supported size? 40?\r
+       if (width < 40) width = 40;\r
+       if (height < 40) height = 40;\r
+       \r
+       S2DE::Rect dRect;\r
+\r
+       // top left corner\r
+       dRect = { x, y, panelDrawSize, panelDrawSize };\r
+       S2DE::renderTexture(&renderer, &ui_frame_sheet, &topLeftCorner, &dRect);\r
+\r
+       // top right corner\r
+       dRect = { x + (width - panelDrawSize), y, panelDrawSize, panelDrawSize };\r
+       S2DE::renderTexture(&renderer, &ui_frame_sheet, &topRightCorner, &dRect);\r
+\r
+       // bottom left\r
+       dRect = { x, y + height - panelDrawSize, panelDrawSize, panelDrawSize };\r
+       S2DE::renderTexture(&renderer, &ui_frame_sheet, &bottomLeftCorner, &dRect);\r
+\r
+       // bottom right\r
+       dRect = { x + width - panelDrawSize, y + height - panelDrawSize, panelDrawSize, panelDrawSize };\r
+       S2DE::renderTexture(&renderer, &ui_frame_sheet, &bottomRightCorner, &dRect);\r
+\r
+       // sides\r
+       dRect = { x, y + panelDrawSize, panelDrawSize, height - 2 * panelDrawSize};\r
+       S2DE::renderTexture(&renderer, &ui_frame_sheet, &leftSide, &dRect);\r
+\r
+       dRect = { x + width - panelDrawSize, y + panelDrawSize, panelDrawSize, height - 2 * panelDrawSize };\r
+       S2DE::renderTexture(&renderer, &ui_frame_sheet, &rightSide, &dRect);\r
+\r
+       dRect = { x + panelDrawSize, y, width - 2 * panelDrawSize, panelDrawSize };\r
+       S2DE::renderTexture(&renderer, &ui_frame_sheet, &topSide, &dRect);\r
+\r
+       dRect = { x + panelDrawSize, y + height - panelDrawSize, width - 2 * panelDrawSize, panelDrawSize };\r
+       S2DE::renderTexture(&renderer, &ui_frame_sheet, &bottomSide, &dRect);\r
+\r
+       // fill\r
+       dRect = { x + panelDrawSize, y + panelDrawSize, width - 2 * panelDrawSize, height - 2 * panelDrawSize};\r
+       S2DE::renderTexture(&renderer, &ui_frame_sheet, &fill, &dRect);\r
+}
\ No newline at end of file
diff --git a/ui.h b/ui.h
new file mode 100644 (file)
index 0000000..9ef01bf
--- /dev/null
+++ b/ui.h
@@ -0,0 +1,7 @@
+#pragma once\r
+\r
+#include <2DEngine.h>\r
+\r
+S2DE::Rect renderString(int startX, int startY, const char* string, SDL_Texture* font = nullptr);\r
+\r
+void renderBasicPanel(int x, int y, int width, int height);
\ No newline at end of file