From: Stan_Lewry Date: Sun, 25 Aug 2024 09:45:51 +0000 (+0100) Subject: initial commit X-Git-Url: https://stanlewry.com/index.cgi?a=commitdiff_plain;h=b02e26a328074bc44970e6bb93ba69b01ae14aaf;p=dragonsurvivors.git initial commit --- b02e26a328074bc44970e6bb93ba69b01ae14aaf diff --git a/combat.cpp b/combat.cpp new file mode 100644 index 0000000..529c159 --- /dev/null +++ b/combat.cpp @@ -0,0 +1,665 @@ +#define _CRT_SECURE_NO_WARNINGS + +#include "combat.h" +#include "globals.h" +#include "ui.h" +#include "tilemap.h" + +#include +#include + + +struct Character { + bool allied; + std::string name; + int currentHP; + int maxHP; + int currentMP; + int maxMP; + + float agility; // to determine move order + float moveDist; + + // animation stuff? + SDL_Texture* texture; + S2DE::Vec2 position; + // direction vector? + // how to handle "moves" + S2DE::Vec2 combatMoveDestination = { 0.0f, 0.0f }; + + int basicAttackRange = 1.0f; + + bool canMove = true; + bool canAttack = true; +}; + +std::vector combatants; +int currentCombatantIndex = -1; +Character* activeCombatant; + +std::vector targets; +int currentTargetIdx = 0; + +enum CombatState { + TURN_ORDER, + ACTION_SELECT, + PLAYER_MOVE, + SELECT_TARGET, + ENEMY_MOVE, + MOVE_TO_POSITION, +}; + +CombatState currentCombatState = TURN_ORDER; + + +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 moveCursorPos; +S2DE::Vec2 arenaCenter = { 23, 11 }; + +//S2DE::Vec2* 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* entityPos, S2DE::Vec2 destination, float t, float speed); +void moveToPosition(Character* c, S2DE::Vec2 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 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; + default: assert(false); break; + } +} + +float distance(S2DE::Vec2 p1, S2DE::Vec2 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; + renderString(portraitX + portraitWidth + innerPad + valPad, portraitY, "50/100"); + + renderMeter(portraitX + portraitWidth + innerPad, portraitY + innerPad, panelWidth - (innerPad * 3) - portraitWidth, 20, 0.5, hpColour, 2); + + renderString(portraitX + portraitWidth + innerPad, portraitY + innerPad + 22, "MP:"); + + renderString(portraitX + portraitWidth + innerPad + valPad, portraitY + innerPad + 22, "50/100"); + + renderMeter(portraitX + portraitWidth + innerPad, portraitY + innerPad + 42, panelWidth - (innerPad * 3) - portraitWidth, 20, 0.5, 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) { + S2DE::Vec2 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); + + renderMeter(charDRect.x + 6, charDRect.y - 6, charDRect.w - 12, 6, 0.5, hpColour, 1); + } + + // render the little arrow + S2DE::Vec2 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 }; + + 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 }; + + Character enemy; + enemy.allied = false; + enemy.name = "Bird"; + enemy.maxHP = 25; + enemy.currentHP = player.maxHP; + enemy.maxMP = 10; + enemy.currentMP = player.maxMP; + enemy.agility = 5.0f; + enemy.moveDist = 1.5f; + enemy.texture = bird; + enemy.position = { arenaCenter.x, arenaCenter.y - 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 }; + + 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* entityPos, S2DE::Vec2 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 destination, float speed) { + float dist = distance(c->position, destination); + + S2DE::Vec2 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 + targets.clear(); + //float range = activeCombatant->basicAttackRange; + std::copy_if(combatants.begin(), combatants.end(), std::back_inserter(targets), + [](const Character& c) { + if (!c.allied) { + float dist = distance(activeCombatant->position, c.position); + if (dist <= activeCombatant->basicAttackRange) { + return true; + } + } + return false; + }); + 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 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; + } + + } + + SDL_RenderClear(renderer); + + renderTilemap(arena, arenaWidth, arenaHeight, combatCam); + + + S2DE::Vec2 playerCenterWorld = { player.position.x + 0.5, player.position.y + 0.5 }; + S2DE::Vec2 playerCenterScreen = S2DE::worldToScreenPoint(&combatCam, &playerCenterWorld, 64, WINDOW_WIDTH, WINDOW_HEIGHT); + + S2DE::renderCircle(&renderer, playerCenterScreen, (player.moveDist) * 64, { 0, 255, 255, 125 }); + + + S2DE::Vec2 lineEndWorld = { moveCursorPos.x + 0.5, moveCursorPos.y + 0.5 }; + S2DE::Vec2 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); + + renderCombatants(); + + S2DE::Rect cursorDRect = S2DE::worldToScreenRect(&combatCam, &moveCursorPos, 4, WINDOW_WIDTH, WINDOW_HEIGHT, 16, 16); + 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; + 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); + + + S2DE::Vec2 playerCenterWorld = { activeCombatant->position.x + 0.5, activeCombatant->position.y + 0.5 }; + S2DE::Vec2 playerCenterScreen = S2DE::worldToScreenPoint(&combatCam, &playerCenterWorld, 64, WINDOW_WIDTH, WINDOW_HEIGHT); + + S2DE::renderCircle(&renderer, playerCenterScreen, (activeCombatant->basicAttackRange) * 64, { 255, 0, 0, 125 }); + + + renderCombatants(); + + + // render the little red arrow above all valid targets + //for (auto target : targets) { + if (targets.size() > 0) { + S2DE::Vec2 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 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 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 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->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 diff --git a/combat.h b/combat.h new file mode 100644 index 0000000..71bd349 --- /dev/null +++ b/combat.h @@ -0,0 +1,5 @@ +#pragma once + +void initCombatScene(); + +void runCombat(double deltaTime); \ No newline at end of file diff --git a/globals.cpp b/globals.cpp new file mode 100644 index 0000000..e3db2d1 --- /dev/null +++ b/globals.cpp @@ -0,0 +1,41 @@ +#include "globals.h" + +SDL_Texture* environment_sheet = nullptr; +SDL_Texture* player_sheet = nullptr; +SDL_Texture* water_test_sheet = nullptr; +SDL_Texture* font_sheet = nullptr; +SDL_Texture* debug_font = nullptr; +SDL_Texture* font_grey = nullptr; +SDL_Texture* ui_frame_sheet = nullptr; +SDL_Texture* bird = nullptr; +SDL_Texture* monica_texture = nullptr; +SDL_Texture* player_forward_texture = nullptr; +SDL_Texture* frog_texture = nullptr; +Mix_Music* background_music = nullptr; + + +static const char* environment_sheet_path = "assets/dragon_survivors_environment_1.png"; +static const char* player_sheet_path = "assets/player.png"; +static const char* water_test_sheet_path = "assets/water_test.png"; +static const char* font_sheet_path = "assets/simple_6x8_black.png"; +static const char* ui_frame_sheet_path = "assets/frame.png"; +static const char* bird_path = "assets/bird_32.png"; +static const char* bg_mus_path = "assets/TownTheme.mp3"; + +void initResources() +{ + environment_sheet = S2DE::loadTexture(environment_sheet_path, &renderer); + player_sheet = S2DE::loadTexture(player_sheet_path, &renderer); + water_test_sheet = S2DE::loadTexture(water_test_sheet_path, &renderer); + font_sheet = S2DE::loadTexture(font_sheet_path, &renderer); + debug_font = S2DE::loadTexture("assets/simple_6x8_red.png", &renderer); + font_grey = S2DE::loadTexture("assets/simple_6x8_grey.png", &renderer); + ui_frame_sheet = S2DE::loadTexture(ui_frame_sheet_path, &renderer); + bird = S2DE::loadTexture(bird_path, &renderer); + + monica_texture = S2DE::loadTexture("assets/Monica_sprite.png", &renderer); + player_forward_texture = S2DE::loadTexture("assets/player_forward.png", &renderer); + frog_texture = S2DE::loadTexture("assets/Frog_sprite.png", &renderer); + background_music = Mix_LoadMUS(bg_mus_path); +} + diff --git a/globals.h b/globals.h new file mode 100644 index 0000000..9d25677 --- /dev/null +++ b/globals.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include +#include <2DEngine.h> + + +#define WINDOW_WIDTH 1280 +#define WINDOW_HEIGHT 720 + +extern SDL_Window* window; +extern SDL_Renderer* renderer; + + +// maybe a resources.h? +extern SDL_Texture* environment_sheet; +extern SDL_Texture* player_sheet; +extern SDL_Texture* water_test_sheet; +extern SDL_Texture* font_sheet; +extern SDL_Texture* debug_font; +extern SDL_Texture* font_grey; +extern SDL_Texture* ui_frame_sheet; +extern SDL_Texture* bird; +extern SDL_Texture* monica_texture; +extern SDL_Texture* player_forward_texture; +extern SDL_Texture* frog_texture; + +extern Mix_Music* background_music; + +void initResources(); + +extern S2DE::InputState inputState; + +// These things should be defined in the overworld +extern S2DE::Vec2 playerPos; +extern int playerFrame; +extern int playerDir; + +extern S2DE::Vec2 birdPos; +extern bool enemyDirection; // 0 = left, 1 = right + +#define WORLD_HEIGHT 200 +#define WORLD_WIDTH 200 + +//extern WorldCell world[WORLD_HEIGHT][WORLD_WIDTH]; + +extern S2DE::Camera camera; + +enum GameState +{ + MAIN_MENU, + OVERWORLD, + COMBAT +}; + +extern GameState gameState; \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..fe47b97 --- /dev/null +++ b/main.cpp @@ -0,0 +1,229 @@ +#include +#include +#include +#include +#include +#include + +#include <2DEngine.h> + +#include "globals.h" +#include "main_menu.h" +#include "combat.h" +#include "tilemap.h" + +GameState gameState = MAIN_MENU; +//GameState gameState = COMBAT; + +using json = nlohmann::json; + +SDL_Window* window = nullptr; +SDL_Renderer* renderer = nullptr; + +S2DE::InputState inputState; + + +WorldCell** world; + +S2DE::Camera camera; + +// These things should be defined in the overworld +S2DE::Vec2 playerPos = { 30, 19 }; +int playerFrame = 0; +int playerDir = 4; + +S2DE::Vec2 birdPos = { 31, 16 }; +bool enemyDirection = 0; // 0 = left, 1 = right + +//void loadWorld(const char* worldPath, WorldCell world[WORLD_HEIGHT][WORLD_WIDTH]); + +//S2DE::Rect renderString(int startX, int startY, const char* string); +void renderWorld(); +//void renderUI(); + +//bool checkCollision(S2DE::Vec2 worldPos); + + +void renderWorld() +{ + + // render enemy + + S2DE::Rect enemyDRect = S2DE::worldToScreenRect(&camera, &birdPos, 2, WINDOW_WIDTH, WINDOW_HEIGHT, 32, 32); + S2DE::Rect enemySRect = { 0, 32 * enemyDirection, 32, 32 }; + S2DE::renderTexture(&renderer, &bird, &enemySRect, &enemyDRect); + + + // render player + + S2DE::Rect playerDRect = S2DE::worldToScreenRect(&camera, &playerPos, 2, WINDOW_WIDTH, WINDOW_HEIGHT, 32, 32); + + S2DE::Rect playerSRect = { playerFrame * 32, playerDir * 32, 32, 32 }; + S2DE::renderTexture(&renderer, &player_sheet, &playerSRect, &playerDRect); + +} + +int main(int argc, char** args) +{ + S2DE::initRendering("Dragon Survivors", WINDOW_WIDTH, WINDOW_HEIGHT, &window, &renderer); + initResources(); + initCombatScene(); + + world = new WorldCell * [WORLD_HEIGHT]; + for (int i = 0; i < WORLD_HEIGHT; ++i) world[i] = new WorldCell[WORLD_WIDTH]; + + loadWorld("maps/overworld_1.tmj", world, WORLD_WIDTH, WORLD_HEIGHT); + + bool running = true; + + double currentTime = 0.0; + double last = 0.0; + double delta = 0.0; + + double frameCounter = 0; + double frameTime = 1.0 / 60; // 60 hrz + + double animCounter = 0; + double animTime = 1.0 / 3; + + + double enemyWalkTime = 0.5; // walks each dir for 2 seconds + double enemyWalkTimer = 0; + + + Mix_VolumeMusic(MIX_MAX_VOLUME / 4); + //Mix_PlayMusic(background_music, -1); + + while (inputState.quit != true) + { + currentTime = SDL_GetTicks(); + delta = (currentTime - last) / 1000.0f; + last = currentTime; + + if (gameState == MAIN_MENU) + { + mainMenu(delta); + } + else if (gameState == OVERWORLD) + { + + frameCounter += delta; + animCounter += delta; + + enemyWalkTimer += delta; + + S2DE::updateInputState(&inputState); + + if (frameCounter > frameTime) + { + frameCounter = 0; + + // update player + float walkSpeed = 0.08; + + float newX = playerPos.x; + float newY = playerPos.y; + float finalX = newX; + float finalY = newY; + + if (inputState.right) { + newX += walkSpeed; + playerDir = 2; + } + if (inputState.left) { + newX -= walkSpeed; + playerDir = 6; + } + if (inputState.up) { + newY -= walkSpeed; + playerDir = 0; + } + if (inputState.down) { + newY += walkSpeed; + playerDir = 4; + } + + if (!checkCollisionRect({ playerPos.x, newY }, world)) + { + // can move on the y + finalY = newY; + } + + if (!checkCollisionRect({ newX, playerPos.y }, world)) + { + // can move on the X + finalX = newX; + } + + playerPos.x = finalX; + playerPos.y = finalY; + + camera = playerPos; + + if (enemyWalkTimer > enemyWalkTime) + { + // flip the direction and reset the timer + enemyDirection = !enemyDirection; + enemyWalkTimer = 0; + } + + float enemyWalkSpeed = 0.04; + if (enemyDirection) // true == 1 == going right + { + birdPos.x += enemyWalkSpeed; + } + else // going left + { + birdPos.x -= enemyWalkSpeed; + } + + } + + + static bool forward = true; + if (animCounter >= animTime) + { + animCounter = 0; + + if (forward) + { + playerFrame += 1; + if (playerFrame >= 2) + { + forward = false; + } + } + else + { + playerFrame -= 1; + if (playerFrame <= 0) + { + forward = true; + } + } + } + + SDL_RenderClear(renderer); + { + renderTilemap(world, WORLD_WIDTH, WORLD_HEIGHT, camera); + renderWorld(); + } + SDL_RenderPresent(renderer); + } + else if (gameState == COMBAT) { + runCombat(delta); + } + } + + + for (int i = 0; i < WORLD_HEIGHT; ++i) delete[] world[i]; + delete[] world; + + S2DE::quitRendering(&renderer, &window); + Mix_Quit(); + + SDL_DestroyTexture(environment_sheet); + // destroy the other shit here + // add mix_quit to dll + return 0; +} \ No newline at end of file diff --git a/main_menu.cpp b/main_menu.cpp new file mode 100644 index 0000000..cc3608f --- /dev/null +++ b/main_menu.cpp @@ -0,0 +1,66 @@ +#include "main_menu.h" +#include "globals.h" +#include "ui.h" + +static const char* menuItems[] = { "Overworld", "Combat", "Load Game", "Options", "Quit" }; +static constexpr int numMenuItems = 5; +int currentMenuItem = 0; + + +void renderMainPanel(); + +void renderMainPanel() { + + int panelWidth = 200; + int panelHeight = 150; + + int panelX = WINDOW_WIDTH / 2 - panelWidth / 2; + int panelY = WINDOW_HEIGHT / 2 - panelHeight / 2; + + renderBasicPanel(panelX, panelY, panelWidth, panelHeight); + + int leftPad = 40; + int topPad = 20; + int itemSpacing = 30; + + for (int i = 0; i < numMenuItems; ++i) { + renderString(panelX + leftPad, panelY + topPad + itemSpacing * i, menuItems[i]); + } + + renderString(panelX + 20, panelY + topPad + (itemSpacing * currentMenuItem), ">"); + +} + +void mainMenu(double deltaTime) { + S2DE::updateInputState(&inputState); + + if (inputState.up) { + inputState.up = false; + currentMenuItem--; + if (currentMenuItem < 0) currentMenuItem = numMenuItems - 1; + } + else if (inputState.down) { + inputState.down = false; + currentMenuItem++; + if (currentMenuItem > numMenuItems - 1) currentMenuItem = 0; + } + else if (inputState.rtrn) { + inputState.rtrn = false; + // select the current menu item + switch (currentMenuItem) { + case 0: + gameState = OVERWORLD; + break; + case 1: + gameState = COMBAT; + break; + default: + inputState.quit = true; + break; + } + } + + SDL_RenderClear(renderer); + renderMainPanel(); + SDL_RenderPresent(renderer); +} \ No newline at end of file diff --git a/main_menu.h b/main_menu.h new file mode 100644 index 0000000..5e48827 --- /dev/null +++ b/main_menu.h @@ -0,0 +1,4 @@ +#pragma once + + +void mainMenu(double deltaTime); \ No newline at end of file diff --git a/tilemap.cpp b/tilemap.cpp new file mode 100644 index 0000000..abe76d6 --- /dev/null +++ b/tilemap.cpp @@ -0,0 +1,83 @@ +#include "tilemap.h" +#include "globals.h" + + +using json = nlohmann::json; + +void loadWorld(const char* mapPath, WorldCell** world, int worldWidth, int worldHeight) +{ + json jsonData = S2DE::loadJson(mapPath); + + json groundData = jsonData["layers"][0]["data"]; + + for (int y = 0; y < worldHeight; ++y) + { + for (int x = 0; x < worldWidth; ++x) + { + int jsonIdx = (y * worldHeight) + x; + + int groundType = groundData[jsonIdx]; + + bool solid = groundType > 30; // lazy hack + + world[y][x] = { groundType, solid }; + } + } +} + +void renderTilemap(WorldCell** world, int worldWidth, int worldHeight, S2DE::Camera camera) +{ + for (int y = 0; y < worldHeight; ++y) + { + for (int x = 0; x < worldWidth; ++x) + { + + float scale = 4; + S2DE::Vec2 worldPos = { x, y }; + SDL_Rect dRect = S2DE::worldToScreenRect(&camera, &worldPos, scale, WINDOW_WIDTH, WINDOW_HEIGHT, 16, 16); + + //if (dRect.x >= 0 && dRect.x + dRect.w <= WINDOW_WIDTH - (16 * scale) && dRect.y >= 0 && dRect.y + dRect.h <= WINDOW_HEIGHT - (16 * scale)) + if (world[y][x].ground != 160 && dRect.x + dRect.w >= 0 && dRect.x <= WINDOW_WIDTH && dRect.y + dRect.h >= 0 && dRect.y <= WINDOW_HEIGHT) + { + + if (world[y][x].ground == 71) + { + // render water animated + + S2DE::Rect waterSRect = { playerFrame * 16, 0, 16, 16 }; + + S2DE::renderTexture(&renderer, &water_test_sheet, &waterSRect, &dRect); + } + else + { + + int tile_idx = world[y][x].ground - 1; + + int sheet_width = 10; + + int tile_x = (tile_idx % sheet_width); + int tile_y = (tile_idx / sheet_width); + + SDL_Rect sRect = { tile_x * 16, tile_y * 16, 16, 16 }; + + + S2DE::renderTexture(&renderer, &environment_sheet, &sRect, &dRect); + } + + } + + } + } +} + +bool checkCollisionRect(S2DE::Vec2 worldPos, WorldCell** world) { + return (world[(int)(worldPos.y + 0.55)][(int)(worldPos.x + 0.25)].solid || + world[(int)(worldPos.y + 0.55)][(int)(worldPos.x + 0.75)].solid || + world[(int)(worldPos.y + 0.95)][(int)(worldPos.x + 0.25)].solid || + world[(int)(worldPos.y + 0.95)][(int)(worldPos.x + 0.75)].solid); + +} + +bool checkCollisionPoint(S2DE::Vec2 worldPos, WorldCell** world) { + return world[(int)worldPos.y][(int)worldPos.x].solid; +} \ No newline at end of file diff --git a/tilemap.h b/tilemap.h new file mode 100644 index 0000000..8aed93f --- /dev/null +++ b/tilemap.h @@ -0,0 +1,18 @@ +#pragma once + +#include <2DEngine.h> + +// same here +struct WorldCell +{ + int ground; + bool solid; +}; + + +void loadWorld(const char* mapPath, WorldCell** world, int worldWidth, int worldHeight); + +void renderTilemap(WorldCell** world, int worldWidth, int worldHeight, S2DE::Camera camera); + +bool checkCollisionRect(S2DE::Vec2 worldPos, WorldCell** world); +bool checkCollisionPoint(S2DE::Vec2 worldPos, WorldCell** world); \ No newline at end of file diff --git a/ui.cpp b/ui.cpp new file mode 100644 index 0000000..80070cd --- /dev/null +++ b/ui.cpp @@ -0,0 +1,91 @@ +#include "ui.h" +#include "globals.h" + + + +static constexpr int panelDrawSize = 16 * 2; + +S2DE::Rect topLeftCorner = { 0, 0, 16, 16 }; +S2DE::Rect topRightCorner = { 32, 0, 16, 16 }; +S2DE::Rect bottomLeftCorner = { 0, 32, 16, 16 }; +S2DE::Rect bottomRightCorner = { 32, 32, 16, 16 }; +S2DE::Rect leftSide = { 0, 16, 16, 1 }; +S2DE::Rect rightSide = { 32, 16, 16, 1 }; +S2DE::Rect topSide = { 16, 0, 1, 16 }; +S2DE::Rect bottomSide = { 16, 32, 1, 16 }; +S2DE::Rect fill = { 10, 10, 1, 1 }; + +S2DE::Rect renderString(int startX, int startY, const char* string, SDL_Texture* font) { + // this part should be in DLL + + //SDL_Texture* fontTX = debugFont ? debug_font : font_sheet; + if (!font) { + font = font_sheet; + } + + int glyphWidth = 6; + int glyphHeight = 8; + + int glyphScale = 2; + + S2DE::Rect finalRect = { startX, startY, 0, glyphHeight * glyphScale }; + + int i = 0; + while (string[i] != '\0') + { + int sheetX = (int)string[i] - 32; + + + S2DE::Rect sRect = { sheetX * glyphWidth, 0, glyphWidth, glyphHeight }; + S2DE::Rect dRect = { startX + (i * glyphWidth * glyphScale), startY, glyphWidth * glyphScale, glyphHeight * glyphScale }; + + S2DE::renderTexture(&renderer, &font, &sRect, &dRect); + + i++; + } + + finalRect.w = i * glyphWidth * glyphScale; + + return finalRect; +} + +void renderBasicPanel(int x, int y, int width, int height) { + // smallest supported size? 40? + if (width < 40) width = 40; + if (height < 40) height = 40; + + S2DE::Rect dRect; + + // top left corner + dRect = { x, y, panelDrawSize, panelDrawSize }; + S2DE::renderTexture(&renderer, &ui_frame_sheet, &topLeftCorner, &dRect); + + // top right corner + dRect = { x + (width - panelDrawSize), y, panelDrawSize, panelDrawSize }; + S2DE::renderTexture(&renderer, &ui_frame_sheet, &topRightCorner, &dRect); + + // bottom left + dRect = { x, y + height - panelDrawSize, panelDrawSize, panelDrawSize }; + S2DE::renderTexture(&renderer, &ui_frame_sheet, &bottomLeftCorner, &dRect); + + // bottom right + dRect = { x + width - panelDrawSize, y + height - panelDrawSize, panelDrawSize, panelDrawSize }; + S2DE::renderTexture(&renderer, &ui_frame_sheet, &bottomRightCorner, &dRect); + + // sides + dRect = { x, y + panelDrawSize, panelDrawSize, height - 2 * panelDrawSize}; + S2DE::renderTexture(&renderer, &ui_frame_sheet, &leftSide, &dRect); + + dRect = { x + width - panelDrawSize, y + panelDrawSize, panelDrawSize, height - 2 * panelDrawSize }; + S2DE::renderTexture(&renderer, &ui_frame_sheet, &rightSide, &dRect); + + dRect = { x + panelDrawSize, y, width - 2 * panelDrawSize, panelDrawSize }; + S2DE::renderTexture(&renderer, &ui_frame_sheet, &topSide, &dRect); + + dRect = { x + panelDrawSize, y + height - panelDrawSize, width - 2 * panelDrawSize, panelDrawSize }; + S2DE::renderTexture(&renderer, &ui_frame_sheet, &bottomSide, &dRect); + + // fill + dRect = { x + panelDrawSize, y + panelDrawSize, width - 2 * panelDrawSize, height - 2 * panelDrawSize}; + S2DE::renderTexture(&renderer, &ui_frame_sheet, &fill, &dRect); +} \ No newline at end of file diff --git a/ui.h b/ui.h new file mode 100644 index 0000000..9ef01bf --- /dev/null +++ b/ui.h @@ -0,0 +1,7 @@ +#pragma once + +#include <2DEngine.h> + +S2DE::Rect renderString(int startX, int startY, const char* string, SDL_Texture* font = nullptr); + +void renderBasicPanel(int x, int y, int width, int height); \ No newline at end of file