diff --git a/combat.cpp b/combat.cpp index 419150b..62b6371 100644 --- a/combat.cpp +++ b/combat.cpp @@ -13,6 +13,4 @@ namespace components { return my_dmg; } - - } diff --git a/guecs.cpp b/guecs.cpp index 1305a58..ea243a6 100644 --- a/guecs.cpp +++ b/guecs.cpp @@ -1,6 +1,70 @@ #include "guecs.hpp" namespace guecs { + + void Textual::init(lel::Cell &cell, shared_ptr font_ptr) { + dbc::check(font_ptr != nullptr, "you failed to initialize this WideText"); + if(font == nullptr) font = font_ptr; + if(text == nullptr) text = make_shared(*font, content, size); + text->setFillColor(color); + + if(centered) { + auto bounds = text->getLocalBounds(); + auto text_cell = lel::center(bounds.size.x, bounds.size.y, cell); + // this stupid / 2 is because SFML renders from baseline rather than from the claimed bounding box + text->setPosition({float(text_cell.x), float(text_cell.y) - text_cell.h / 2}); + } else { + text->setPosition({float(cell.x + padding * 2), float(cell.y + padding * 2)}); + } + + text->setCharacterSize(size); + } + + void Textual::update(std::wstring& new_content) { + content = new_content; + text->setString(content); + } + + + void Sprite::init(lel::Cell &cell) { + auto sprite_texture = textures::get(name); + texture = sprite_texture.texture; + sprite = make_shared(*texture); + + sprite->setPosition({ + float(cell.x + padding), + float(cell.y + padding)}); + + auto bounds = sprite->getGlobalBounds(); + + sprite->setScale({ + float(cell.w - padding * 2) / bounds.size.x, + float(cell.h - padding * 2) / bounds.size.y}); + } + + void Rectangle::init(lel::Cell& cell) { + sf::Vector2f size{float(cell.w) - padding * 2, float(cell.h) - padding * 2}; + if(shape == nullptr) shape = make_shared(size); + shape->setPosition({float(cell.x + padding), float(cell.y + padding)}); + shape->setFillColor(color); + shape->setOutlineColor(border_color); + shape->setOutlineThickness(border_px); + } + + + void Meter::init(lel::Cell& cell) { + bar.init(cell); + } + + + void Background::init() { + sf::Vector2f size{float(w), float(h)}; + if(shape == nullptr) shape = make_shared(size); + shape->setPosition({float(x), float(y)}); + shape->setFillColor(color); + } + + UI::UI() { $font = make_shared(FONT_FILE_NAME); } diff --git a/guecs.hpp b/guecs.hpp index a0737c6..6947953 100644 --- a/guecs.hpp +++ b/guecs.hpp @@ -24,33 +24,11 @@ namespace guecs { shared_ptr font = nullptr; shared_ptr text = nullptr; - void init(lel::Cell &cell, shared_ptr font_ptr) { - dbc::check(font_ptr != nullptr, "you failed to initialize this WideText"); - if(font == nullptr) font = font_ptr; - if(text == nullptr) text = make_shared(*font, content, size); - text->setFillColor(color); - - if(centered) { - dbc::log("TEXTUAL IS CENTERED"); - auto bounds = text->getLocalBounds(); - auto text_cell = lel::center(bounds.size.x, bounds.size.y, cell); - // this stupid / 2 is because SFML renders from baseline rather than from the claimed bounding box - text->setPosition({float(text_cell.x), float(text_cell.y) - text_cell.h / 2}); - } else { - text->setPosition({float(cell.x + padding * 2), float(cell.y + padding * 2)}); - } - - text->setCharacterSize(size); - } - - void update(std::wstring& new_content) { - content = new_content; - text->setString(content); - } + void init(lel::Cell &cell, shared_ptr font_ptr); + void update(std::wstring& new_content); }; struct Label : public Textual { - template Label(Args... args) : Textual(args...) { @@ -75,19 +53,7 @@ namespace guecs { std::shared_ptr sprite = nullptr; std::shared_ptr texture = nullptr; - void init(lel::Cell &cell) { - auto sprite_texture = textures::get(name); - texture = sprite_texture.texture; - sprite = make_shared(*texture); - sprite->setPosition({ - float(cell.x + padding), - float(cell.y + padding)}); - - auto size = texture->getSize(); - sprite->setScale({ - float(cell.w - padding * 2) / size.x, - float(cell.h - padding * 2) / size.y}); - } + void init(lel::Cell &cell); }; struct Rectangle { @@ -97,23 +63,14 @@ namespace guecs { int border_px = GUECS_BORDER_PX; shared_ptr shape = nullptr; - void init(lel::Cell& cell) { - sf::Vector2f size{float(cell.w) - padding * 2, float(cell.h) - padding * 2}; - if(shape == nullptr) shape = make_shared(size); - shape->setPosition({float(cell.x + padding), float(cell.y + padding)}); - shape->setFillColor(color); - shape->setOutlineColor(border_color); - shape->setOutlineThickness(border_px); - } + void init(lel::Cell& cell); }; struct Meter { float percent = 1.0f; Rectangle bar; - void init(lel::Cell& cell) { - bar.init(cell); - } + void init(lel::Cell& cell); }; struct ActionData { @@ -130,7 +87,6 @@ namespace guecs { float w = 0.0f; float h = 0.0f; sf::Color color = GUECS_BG_COLOR; - shared_ptr shape = nullptr; Background(lel::Parser& parser, sf::Color bg_color=GUECS_BG_COLOR) : @@ -143,12 +99,7 @@ namespace guecs { Background() {} - void init() { - sf::Vector2f size{float(w), float(h)}; - if(shape == nullptr) shape = make_shared(size); - shape->setPosition({float(x), float(y)}); - shape->setFillColor(color); - } + void init(); }; class UI { diff --git a/gui_fsm.cpp b/gui_fsm.cpp index 5d49159..5557041 100644 --- a/gui_fsm.cpp +++ b/gui_fsm.cpp @@ -282,6 +282,11 @@ namespace gui { case KEY::O: autowalking = true; break; + case KEY::Equal: + $levels.spawn_enemy("KNIGHT"); + $main_ui.update_level($level); + run_systems(); + break; case KEY::L: event(Event::STAIRS_DOWN); break; diff --git a/levelmanager.cpp b/levelmanager.cpp index ded7ed3..92f57cc 100644 --- a/levelmanager.cpp +++ b/levelmanager.cpp @@ -51,6 +51,36 @@ shared_ptr LevelManager::create_bossfight(shared_ptr(world, boss_id); } +DinkyECS::Entity LevelManager::spawn_enemy(std::string named) { + (void)named; + auto& level = current(); + + auto &config = level.world->get_the(); + auto entity_data = config.enemies[named]; + + WorldBuilder builder(*level.map, $components); + + auto entity_id = builder.configure_entity_in_map(*level.world, entity_data, 0); + + auto& entity_pos = level.world->get(entity_id); + auto player_pos = level.world->get(level.player); + + for(matrix::box it{level.map->walls(), + player_pos.location.x, player_pos.location.y, 1}; it.next();) + { + if(level.map->can_move({it.x, it.y})) { + // this is where we move it closer to the player + entity_pos.location.x = it.x; + entity_pos.location.y = it.y; + break; + } + } + + level.collision->insert(entity_pos.location, entity_id); + + return entity_id; +} + size_t LevelManager::create_level(shared_ptr prev_world) { auto world = clone_load_world(prev_world); auto scaling = scale_level(); diff --git a/levelmanager.hpp b/levelmanager.hpp index 09e7f05..a51faff 100644 --- a/levelmanager.hpp +++ b/levelmanager.hpp @@ -41,4 +41,6 @@ class LevelManager { size_t current_index() { return $current_level; } GameLevel &get(size_t index); LevelScaling scale_level(); + + DinkyECS::Entity spawn_enemy(std::string named); }; diff --git a/meson.build b/meson.build index debab18..f4a0bff 100644 --- a/meson.build +++ b/meson.build @@ -161,3 +161,14 @@ executable('zedcaster', link_args: link_args, override_options: exe_defaults, dependencies: dependencies) + + +executable('arena', + sources + [ + 'tools/arena.cpp', + 'tools/arena_ui.cpp', + 'tools/arena_fsm.cpp' ], + cpp_args: cpp_args, + link_args: link_args, + override_options: exe_defaults, + dependencies: dependencies) diff --git a/textures.cpp b/textures.cpp index 354041c..198b24d 100644 --- a/textures.cpp +++ b/textures.cpp @@ -52,9 +52,18 @@ namespace textures { } SpriteTexture get(std::string name) { + dbc::check(initialized, "you forgot to call textures::init()"); dbc::check(TMGR.sprite_textures.contains(name), fmt::format("!!!!! texture pack does not contain {} sprite", name)); - return TMGR.sprite_textures.at(name); + + auto result = TMGR.sprite_textures.at(name); + + dbc::check(result.sprite != nullptr, + fmt::format("bad sprite from textures::get named {}", name)); + dbc::check(result.texture != nullptr, + fmt::format("bad texture from textures::get named {}", name)); + + return result; } sf::Image load_image(std::string filename) { diff --git a/tools/arena.cpp b/tools/arena.cpp new file mode 100644 index 0000000..40eb826 --- /dev/null +++ b/tools/arena.cpp @@ -0,0 +1,45 @@ +#include "arena_fsm.hpp" +#include "textures.hpp" +#include "sound.hpp" +#include "ai.hpp" +#include "animation.hpp" +#include + +int main(int argc, char* argv[]) { + try { + dbc::check(argc == 2, "USAGE: arena enemy_name"); + std::string enemy_name{argv[1]}; + + textures::init(); + sound::init(); + ai::init("assets/ai.json"); + animation::init(); + + sound::mute(false); + sound::play("ambient_1", true); + + arena::FSM main(enemy_name); + + main.event(arena::Event::STARTED); + + while(main.active()) { + main.render(); + + // ZED: need to sort out how to deal with this in the FSM + if(main.in_state(arena::State::IDLE)) { + main.event(arena::Event::TICK); + } + + main.keyboard_mouse(); + + main.handle_world_events(); + } + + return 0; + } catch(const std::system_error& e) { + std::cout << "WARNING: On OSX you'll get this error on shutdown.\n"; + std::cout << "Caught system_error with code " + "[" << e.code() << "] meaning " + "[" << e.what() << "]\n"; + } +} diff --git a/tools/arena_fsm.cpp b/tools/arena_fsm.cpp new file mode 100644 index 0000000..90c8ecf --- /dev/null +++ b/tools/arena_fsm.cpp @@ -0,0 +1,120 @@ +#include "gui_fsm.hpp" +#include +#include +#include +#include +#include "components.hpp" +#include +#include "systems.hpp" +#include "events.hpp" +#include "sound.hpp" +#include +#include "arena_fsm.hpp" + +namespace arena { + using namespace components; + + FSM::FSM(std::string enemy_name) : + $enemy_name(enemy_name), + $window(sf::VideoMode({SCREEN_WIDTH, SCREEN_HEIGHT}), "Arena Battle Tester"), + $font{FONT_FILE_NAME} + { + } + + void FSM::event(Event ev) { + switch($state) { + FSM_STATE(State, START, ev); + FSM_STATE(State, IDLE, ev); + FSM_STATE(State, END, ev); + } + } + + void FSM::START(Event ) { + run_systems(); + $level = $level_mgr.current(); + + auto entity_id = $level_mgr.spawn_enemy($enemy_name); + + $arena_ui = make_shared($level.world, entity_id); + $arena_ui->init(); + state(State::IDLE); + } + + void FSM::END(Event ev) { + dbc::log(fmt::format("END: received event after done: {}", int(ev))); + } + + void FSM::IDLE(Event ev) { + using enum Event; + + switch(ev) { + case QUIT: + $window.close(); + state(State::END); + return; // done + case CLOSE: + dbc::log("Nothing to close."); + break; + case TICK: + // do nothing + break; + case ATTACK: + dbc::log("ATTACK!"); + break; + default: + dbc::sentinel("unhandled event in IDLE"); + } + } + + void FSM::keyboard_mouse() { + while(const auto ev = $window.pollEvent()) { + if(ev->is()) { + event(Event::QUIT); + } + + if(const auto* mouse = ev->getIf()) { + if(mouse->button == sf::Mouse::Button::Left) { + sf::Vector2f pos = $window.mapPixelToCoords(mouse->position); + (void)pos; + } + } + + if(const auto* key = ev->getIf()) { + using KEY = sf::Keyboard::Scan; + + switch(key->scancode) { + case KEY::Escape: + event(Event::CLOSE); + break; + case KEY::Space: + event(Event::ATTACK); + break; + default: + break; // ignored + } + } + } + } + + void FSM::draw_gui() { + if($arena_ui != nullptr) { + $arena_ui->render($window); + } + } + + void FSM::render() { + $window.clear(); + draw_gui(); + $window.display(); + } + + void FSM::run_systems() { + } + + bool FSM::active() { + return !in_state(State::END); + } + + void FSM::handle_world_events() { + } +} diff --git a/tools/arena_fsm.hpp b/tools/arena_fsm.hpp new file mode 100644 index 0000000..9cce3e1 --- /dev/null +++ b/tools/arena_fsm.hpp @@ -0,0 +1,52 @@ +#pragma once +#include "constants.hpp" +#include "stats.hpp" +#include "levelmanager.hpp" +#include "fsm.hpp" +#include "main_ui.hpp" +#include "combat_ui.hpp" +#include "status_ui.hpp" +#include "arena_ui.hpp" +#include "map_view.hpp" +#include "mini_map.hpp" + +namespace arena { + enum class State { + START, + IDLE, + END + }; + + enum class Event { + STARTED=0, + TICK=1, + CLOSE = 7, + ATTACK = 10, + QUIT = 14 + }; + + class FSM : public DeadSimpleFSM { + public: + std::string $enemy_name; + sf::RenderWindow $window; + sf::Font $font; + LevelManager $level_mgr; + GameLevel $level; + shared_ptr $arena_ui = nullptr; + + FSM(std::string enemy_name); + + void event(Event ev); + void START(Event ); + void IDLE(Event ev); + void END(Event ev); + + void try_move(int dir, bool strafe); + void keyboard_mouse(); + void draw_gui(); + void render(); + bool active(); + void run_systems(); + void handle_world_events(); + }; +} diff --git a/tools/arena_ui.cpp b/tools/arena_ui.cpp new file mode 100644 index 0000000..d38a6ff --- /dev/null +++ b/tools/arena_ui.cpp @@ -0,0 +1,133 @@ +#include "arena_ui.hpp" +#include "easings.hpp" +#include "sound.hpp" +#include + +namespace arena { + using namespace guecs; + + ArenaUI::ArenaUI(shared_ptr world, DinkyECS::Entity entity_id) + : $world(world), + $entity_id(entity_id), + $config(world->get_the()) + { + $status.position(0, 0, BOSS_VIEW_X, SCREEN_HEIGHT); + $status.layout( + "[main_status]" + "[(150)status_3|(150)status_4]" + "[(150)status_5|(150)status_6]" + "[(150)status_7|(150)status_8]"); + + $overlay.position(BOSS_VIEW_X, BOSS_VIEW_Y, + BOSS_VIEW_WIDTH, BOSS_VIEW_HEIGHT); + + $overlay.layout("[_|=*%(200)enemy|_|_]"); + + $sounds = $world->get($entity_id); + $combat = $world->get($entity_id); + $sprite_config = $world->get($entity_id); + } + + void ArenaUI::configure_sprite() { + $animation = $world->get($entity_id); + $animation.frame_width = $sprite_config.width; + + auto enemy_id = $overlay.entity("enemy"); + auto& enemy_image = $overlay.get(enemy_id); + + sf::IntRect frame_rect{{0,0},{$sprite_config.width, $sprite_config.height}}; + enemy_image.sprite->setTextureRect(frame_rect); + } + + void ArenaUI::configure_background() { + if($world->has($entity_id)) { + auto& boss = $world->get($entity_id); + + $entity_background = textures::get(boss.background); + $entity_background.sprite->setPosition({BOSS_VIEW_X, BOSS_VIEW_Y}); + $status.world().set_the({$status.$parser}); + + $entity_has_stage = true; + + if(boss.stage) { + $entity_stage = textures::get(*boss.stage); + } else { + $entity_stage = textures::get("devils_fingers_background"); + } + + $entity_stage.sprite->setPosition({BOSS_VIEW_X, BOSS_VIEW_Y}); + } else { + $entity_has_stage = false; + $entity_background = textures::get("devils_fingers_background"); + $entity_background.sprite->setPosition({BOSS_VIEW_X, BOSS_VIEW_Y}); + $status.world().set_the({$status.$parser}); + } + } + + void ArenaUI::configure_gui() { + for(auto& [name, cell] : $status.cells()) { + auto button = $status.entity(name); + $status.set(button, {}); + $status.set(button, { + [this, name](auto, auto){ + dbc::log(fmt::format("STATUS: {}", name)); + } + }); + if(name == "main_status") { + $status.set(button, {fmt::format(L"HP: {}", $combat.hp)}); + } else { + $status.set