Nimbin[12]?SDL & Graphics / mapcreator / src/game/state_playing.cpp

mapcreator git · master

SDL3 2.5D game and engine using assets, with map editor

sdl3 c++ game engine map-editor cmake · first commit 2025-10-31 · last commit 2026-09-07 (1 month ago) · synced 3 days ago · upstream: git.ide3.de/hsnr/sdl-spieleentwicklung/map_creator

C++ 99.3%
git clone https://git.christianimmanuel.de/sdl-graphics/mapcreator.gitwget https://git.christianimmanuel.de/sdl-graphics/mapcreator/archive/mapcreator.tar.gz
src/game/state_playing.cpp 13 KB · 335 lines raw
#include "state_playing.hpp"

#include "inventory/weapon.hpp"
#include "map/map.hpp"
#include "building/research.hpp"
#include "building/craft.hpp"
#include "game/game.hpp"
#include "defaults/ui.hpp"
#include "character/message.hpp"
#include "defaults/shuffle_texture.hpp"

StatePlaying::StatePlaying() { 
    std::cout << "CREATE STATE_PLAYING " << this << std::endl;
}
StatePlaying::~StatePlaying() {
    std::cout << "STATEPL DTOR " << this << std::endl;
}

StatePlaying::StatePlaying(StatePlaying&&) {
    std::cout << "[StatePlaying] Move constructor called!" << std::endl;
}

bool StatePlaying::Init(Game* game) {
    std::cout << "Call init state_game\n";
    Default::now = SDL_GetPerformanceCounter();
    Default::last = 0;
    Default::delta_time = 0.0;

    cursor = game->cursor;
    cursor->set_State(CursorState::HIDDEN);

    // INIT Bird
    Bird::Init(game->renderer);

    player = std::make_unique<Character>();
    player->Init(game->renderer);

    std::cout << "PLAYER:: " << this << " " << player << std::endl;

    // INIT Weapons
    Weapon::Init(game->renderer);

    // INIT Building
    BuildingData::Init(game->renderer);

    // INIT Enemy
    Enemy::init_All(game->renderer);

    Map::tile_map = Map::load_MapTiles(Map::save_path);
    if (Map::tile_map.size() == 0) Map::Init();

    Map::load_TileCollision(Map::save_collission_path);
    if (Map::tile_collission_map.size() == 0) Map::init_Collider();

    Map::offset = {0, 0};
    Default::game_scale = 4.0;


    // START CHEAT
    if (game->is_cheating) {
        player->inventory->add_Resource(ResourceType::WOOD,  120, player->messages.get());
        player->inventory->add_Resource(ResourceType::STONE, 120, player->messages.get());
        player->inventory->add_Resource(ResourceType::CLAY,  120, player->messages.get());
        player->inventory->add_Resource(ResourceType::IRON,  120, player->messages.get());
        player->set_ResourceDiscovery(ResourceType::WOOD);
        player->set_ResourceDiscovery(ResourceType::STONE);
        player->set_ResourceDiscovery(ResourceType::CLAY);
        player->set_ResourceDiscovery(ResourceType::IRON);
        player->researched_items[static_cast<size_t>(ItemType::SWORD_WOOD)]    = true;
        player->researched_items[static_cast<size_t>(ItemType::WOODAXE_STONE)] = true;
        player->researched_items[static_cast<size_t>(ItemType::CULTIVATOR)]    = true;
        player->researched_buildings[static_cast<size_t>(BuildingType::CRAFTING_WOOD_1)] = true;
        player->set_BuildingDiscovery();
        player->inventory->add_Item(ItemType::SWORD_WOOD, player->messages.get());
        player->inventory->add_Item(ItemType::WOODAXE_STONE, player->messages.get());
        player->inventory->add_Item(ItemType::BUILDING_TOOL, player->messages.get());
        player->inventory->add_Item(ItemType::CULTIVATOR, player->messages.get());
        player->inventory->add_Vegetable(VegetableData::Type::CARROT, 4, player->messages.get());
        player->inventory->add_Vegetable(VegetableData::Type::CABBAGE, 4, player->messages.get());
        player->is_attackable = true;
    }
    // END CHEAT


    // Enemy
    Enemy::enemies.clear();
    Enemy::enemies_to_spawn.clear();
    /*
    */
    Enemy::init_SpawnEnemy(0, WeaponType::SWORD, WeaponMaterial::WOOD, {850, 480}, {16,16,2,false,false,0,1.2});
    Enemy::init_SpawnEnemy(0, WeaponType::SWORD, WeaponMaterial::WOOD, {450, 480}, {16,16,2,false,false,0,1.2});
    Enemy::init_SpawnEnemy(0, WeaponType::SWORD, WeaponMaterial::WOOD, {150, 480}, {16,16,2,false,false,0,1.2});
    /*
    Enemy::init_SpawnEnemy(1, WeaponType::SWORD, WeaponMaterial::WOOD, {140, 100}, {6,6,1,false,false,0,1.2});
    Enemy::init_SpawnEnemy(1, WeaponType::SWORD, WeaponMaterial::WOOD, {100,  10}, {6,6,1,false,false,0,1.2});
    Enemy::init_SpawnEnemy(1, WeaponType::SWORD, WeaponMaterial::WOOD, {100, 130}, {6,6,1,false,false,0,1.2});
    Enemy::init_SpawnEnemy(1, WeaponType::SWORD, WeaponMaterial::WOOD, {200, 100}, {6,6,1,false,false,0,1.2});
    Enemy::init_SpawnEnemy(1, WeaponType::SWORD, WeaponMaterial::WOOD, {600,100},  {6,6,1,false,false,0,1.2});
    Enemy::init_SpawnEnemy(1, WeaponType::SWORD, WeaponMaterial::WOOD, {600,100},  {6,6,1,false,false,0,1.2});
    */
    //Enemy::init_SpawnEnemy(1, WeaponType::SWORD, WeaponMaterial::WOOD, {150, 100}, {6,6,1,false,false,0,1.2});

    //for (int i = 0; i < 12; i++) Enemy::init_SpawnEnemy(1, WeaponType::SWORD, WeaponMaterial::WOOD, {600,100}, {6,6,1,false,false,0,1.2});

    return true;
}

inline void setActiveMenu(CharacterMenuType& active_menu, const CharacterMenuType& new_menu, AnimatedCursor* cursor) {
    switch (active_menu) {
        case CharacterMenuType::NONE:
            active_menu = new_menu;
            cursor->set_State(CursorState::ANIMATED);
            break;
        default:
            active_menu = CharacterMenuType::NONE;
            cursor->set_State(CursorState::HIDDEN);
            break;
    }
}

void StatePlaying::handle_Events(SDL_Event& event, Game* game) {
    if (game->input.cursor_over_tileset) {
        game->input.mouse_game_offset = {(int)Tile::tileset_offset.x,
                            (int)Tile::tileset_offset.y };
    }

    game->input.clicked_on_map = false;
    //std::cout << "ET: " << (int)event.type << std::endl;
    switch (event.type) {
        case SDL_EVENT_KEY_DOWN: {
                 if (event.key.scancode >= SDL_SCANCODE_1 && event.key.scancode <= SDL_SCANCODE_6) {
                     player->inventory->selected_hotbar_last = player->inventory->selected_hotbar;
                     player->inventory->selected_hotbar = (int)(event.key.scancode - SDL_SCANCODE_1) + 1;
                 } if (event.key.scancode == SDL_SCANCODE_0) {
                     Default::game_scale = 4.0;
                 }
                 break;
             }

        case SDL_EVENT_KEY_UP:
            switch (event.key.scancode) {
                case SDL_SCANCODE_B: {
                    InventoryItem *sel_item = nullptr;
                    if (player->selected_item->object_type == InventoryObjectType::ITEM) {
                        sel_item = static_cast<InventoryItem*>(player->selected_item);
                        if (sel_item->type == ItemType::BUILDING_TOOL)
                            setActiveMenu(player->active_menu, CharacterMenuType::BUILDING, cursor);
                        else
                            player->messages->Add(MessageKey::BUILDING_TOOL_REQUIRED);
                    } else
                        player->messages->Add(MessageKey::BUILDING_TOOL_REQUIRED);
                    break;
                }
                case SDL_SCANCODE_R:
                    Inventory::selected_hotbar = !Inventory::selected_hotbar ? Inventory::selected_hotbar_last : 0;
                    break;
                case SDL_SCANCODE_E:
                    setActiveMenu(player->active_menu, CharacterMenuType::INVENTORY, cursor);
                    Inventory::draw_menu = !Inventory::draw_menu;
                    break;
                case SDL_SCANCODE_K: {
                    Building::construction_mode = Building::construction_mode == BuildingStatus::DECONSTRUCT ?
                                                  BuildingStatus::CONSTRUCTING:
                                                  BuildingStatus::DECONSTRUCT;
                    InventoryItem *it = static_cast<InventoryItem*>(player->selected_item);
                    if (it && it->type == ItemType::CULTIVATOR)
                        player->cultivation_logic.is_removing = !player->cultivation_logic.is_removing;
                    break;
                }
                case SDL_SCANCODE_F5:
                    player->health_Reduce(100000000);
                    break;
                case SDL_SCANCODE_F11: // FOR TESTING
                    Enemy::init_SpawnEnemy(0, WeaponType::SWORD, WeaponMaterial::WOOD, {150, 100}, {16,16,2,false,false,0,1.2});
                    return;
                default:
                    break;
            }
            break;
        case SDL_EVENT_MOUSE_BUTTON_UP:
            if (!game->input.pressed_ctrl && !game->input.pressed_shift)
                game->input.clicked_on_map = true;
            break;
        default:
            break;
    }
}

void update_CameraOffset(
    const SDL_FPoint& player_world,
    const SDL_Rect& win_size
) {
    const float inv_scale = 1.0f / Default::game_scale;

    const float logical_w = win_size.w * inv_scale;
    const float logical_h = win_size.h * inv_scale;

    const float margin_x = logical_w * 0.4f;
    const float margin_y = logical_h * 0.4f;

    const float player_screen_x = player_world.x + Map::offset.x;
    const float player_screen_y = player_world.y + Map::offset.y;

    float target_offset_x = Map::offset.x;
    float target_offset_y = Map::offset.y;

    // Horizontal deadzone
    if (player_screen_x < margin_x) {
        target_offset_x = margin_x - player_world.x;
    }
    else if (player_screen_x > logical_w - margin_x) {
        target_offset_x = (logical_w - margin_x) - player_world.x;
    }

    // Vertical deadzone
    if (player_screen_y < margin_y) {
        target_offset_y = margin_y - player_world.y;
    }
    else if (player_screen_y > logical_h - margin_y) {
        target_offset_y = (logical_h - margin_y) - player_world.y;
    }

    // --- Smooth interpolation ---
    const float follow_speed = 10.0f; // higher = snappier
    const float t = 1.0f - std::exp(-follow_speed * Default::delta_time);

    Map::offset.x += (target_offset_x - Map::offset.x) * t;
    Map::offset.y += (target_offset_y - Map::offset.y) * t;
}

void StatePlaying::Update(Game* game) {

    // SET Map offset
    Map::set_Offset(game->input);

    // VEGETABLES
    for (Vegetable* veg : VegetableData::all_vegetables) {
        veg->Grow(Default::get_TimeSinceStartInSec());
    }

    // CHARACTER
    player->handle_Input(game->input, game->win_size);
    player->Update();
    if (player->status == CharacterStatus::DEAD && player->death_shuffler == nullptr) {
        player->death_shuffler = new PixelShuffler(game->renderer, player->active_anim->surface);
        player->shuffle_timer = 0.0f;
        Animation::audio->Play("character.dead");
        return;
    }

    // MESSAGES
    player->messages->Update();

    // START ENEMY HANDLING
    // spawn
    for (size_t i = 0; i < Enemy::enemies_to_spawn.size(); ) {
        SpawnParams &sp = Enemy::enemies_to_spawn[i];
        if (Enemy::spawn_Enemy(sp))
            Enemy::enemies_to_spawn.erase(Enemy::enemies_to_spawn.begin() + static_cast<long>(i));
        else
            ++i;
    }


    // get moves
    Enemy::run_GetMovesThreads();

    // update moves
    for (Enemy* enemy : Enemy::enemies)
        enemy->Update(*player);

    // clear dead
    Enemy::remove_Dead();
    // END ENEMY HANDLING


    // CAMERA
    update_CameraOffset({player->x+player->tile_size/2, player->y+player->tile_size/2}, game->win_size);
}

void StatePlaying::Render(Game* game) {
    if (!game->renderer)
        std::cerr << "ERROR in StatePlayin::Render\n";

    player->messages->render_InventoryMessages(game, player->inventory.get());

    switch (player->active_menu) {
        case CharacterMenuType::NONE: break;
        case CharacterMenuType::BUILDING:
            BuildingData::draw_WorkbenchMenu(game->renderer, game->win_size, player->discovered_buildings);
            break;
        case CharacterMenuType::INVENTORY:
            player->inventory->draw_Menu( game->renderer, game->win_size, Ui::font_menu, player->researched_items, Inventory::cursor_OverCraftButton(game->input.mouse_state_raw));
            break;
        case CharacterMenuType::RESEARCH:
            Research::draw_ReserachMenu(game->renderer, game->win_size, Building::selected_building_type, player->researched_buildings, player->researched_items, Research::cursor_OverResearchButton(game->input.mouse_state_raw));
            break;
        case CharacterMenuType::WORKBENCH:
            Craft::draw_CraftMenu(game, Building::selected_building_type, Craft::cursor_OverCraftButton(game->input.mouse_state_raw));
            break;
    }

    player->inventory->draw_Hotbar(game->renderer, Ui::font_menu);

    //player->messages->render_ResourceMessages(game, player->inventory->resources);

    Ui::draw_BottomLeftBars(game->renderer, game->win_size, *player);
}

void StatePlaying::Load(SDL_Renderer* renderer, std::string path) {
    Map::tile_map = Map::load_MapTiles(path + "/map");
    std::cout << "STATE LOAD\n";
    Map::load_TileCollision(path + "/collisions");
    if (!renderer)
        std::cerr << "FUCK NO RENDERER..\n";


    Enemy::load_All(path+"/enemies");
    for (size_t i = 0; i < Enemy::enemies_to_spawn.size(); ) {
        SpawnParams &sp = Enemy::enemies_to_spawn[i];
        if (Enemy::spawn_Enemy(sp))
            Enemy::enemies_to_spawn.erase(Enemy::enemies_to_spawn.begin() + static_cast<long>(i));
        else
            ++i;
    }

    player->Load(path);
}

void StatePlaying::Save(std::string path) {
    Map::save_MapTiles(Map::tile_map, path + "/map");
    Map::save_TileCollision(path + "/collisions");
    Enemy::save_All(path+"/enemies");
    player->Save(path);
}