First step in refactoring to allow for multiple levels. Next is to clean up the APIs and sort out how things will be notified that they have to switch levels.

main
Zed A. Shaw 2 weeks ago
parent 3344181a47
commit c14efee9ea
  1. 52
      gui.cpp
  2. 9
      gui.hpp
  3. 11
      levelmanager.cpp
  4. 4
      levelmanager.hpp
  5. 22
      main.cpp
  6. 64
      systems.cpp
  7. 19
      systems.hpp
  8. 12
      tests/gui.cpp
  9. 3
      tests/levelmanager.cpp

@ -193,13 +193,11 @@ void MapViewUI::resize_canvas() {
$canvas = Canvas(width * 2, height * 4); $canvas = Canvas(width * 2, height * 4);
} }
GUI::GUI(DinkyECS::World &world, Map& game_map) : GUI::GUI() :
$world(world), $level($level_manager.current()),
$game_map(game_map), $status_ui(*$level.world),
$status_ui(world), $map_view(*$level.world, *$level.lights, *$level.map),
$lights(game_map.width(), game_map.height()), $inventory_ui(*$level.world),
$map_view($world, $lights, $game_map),
$inventory_ui(world),
$sounds("./assets") $sounds("./assets")
{ {
// this needs a config file soon // this needs a config file soon
@ -219,8 +217,7 @@ void GUI::resize_map(int new_size) {
} }
void GUI::save_world() { void GUI::save_world() {
$status_ui.log("Game saved!"); $status_ui.log("SAVING BUSTED!");
save::to_file("./savefile.world", $world, $game_map);
} }
void GUI::create_renderer() { void GUI::create_renderer() {
@ -236,14 +233,16 @@ void GUI::create_renderer() {
} }
void GUI::handle_world_events() { void GUI::handle_world_events() {
auto& world = *$level.world;
using eGUI = Events::GUI; using eGUI = Events::GUI;
while($world.has_event<eGUI>()) {
auto [evt, entity, data] = $world.recv<eGUI>(); while(world.has_event<eGUI>()) {
auto [evt, entity, data] = world.recv<eGUI>();
switch(evt) { switch(evt) {
case eGUI::COMBAT: { case eGUI::COMBAT: {
auto &damage = std::any_cast<Events::Combat&>(data); auto &damage = std::any_cast<Events::Combat&>(data);
auto enemy_combat = $world.get<Combat>(entity); auto enemy_combat = world.get<Combat>(entity);
if(damage.enemy_did > 0) { if(damage.enemy_did > 0) {
$status_ui.log(format("Enemy HIT YOU for {} damage!", damage.enemy_did)); $status_ui.log(format("Enemy HIT YOU for {} damage!", damage.enemy_did));
@ -265,10 +264,10 @@ void GUI::handle_world_events() {
break; break;
case eGUI::DEATH: { case eGUI::DEATH: {
// auto &dead_data = std::any_cast<Events::Death&>(data); // auto &dead_data = std::any_cast<Events::Death&>(data);
auto player = $world.get_the<Player>(); auto player = world.get_the<Player>();
dbc::check(player.entity == entity, "received death event for something not the player"); dbc::check(player.entity == entity, "received death event for something not the player");
auto player_combat = $world.get<Combat>(entity); auto player_combat = world.get<Combat>(entity);
if(player_combat.dead) { if(player_combat.dead) {
toggle_modal(&$death_ui, $player_died); toggle_modal(&$death_ui, $player_died);
} }
@ -305,10 +304,12 @@ void GUI::shutdown() {
} }
bool GUI::game_ui_events() { bool GUI::game_ui_events() {
auto& world = *$level.world;
using KB = sf::Keyboard; using KB = sf::Keyboard;
auto player = $world.get_the<Player>();
auto player = world.get_the<Player>();
int map_font_size = $renderer.font_size(); int map_font_size = $renderer.font_size();
auto& player_motion = $world.get<Motion>(player.entity); auto& player_motion = world.get<Motion>(player.entity);
bool event_happened = false; bool event_happened = false;
if(KB::isKeyPressed(KB::Left)) { if(KB::isKeyPressed(KB::Left)) {
@ -328,12 +329,12 @@ bool GUI::game_ui_events() {
} else if(KB::isKeyPressed(KB::Hyphen)) { } else if(KB::isKeyPressed(KB::Hyphen)) {
resize_map(map_font_size - 10); resize_map(map_font_size - 10);
} else if(KB::isKeyPressed(KB::L)) { } else if(KB::isKeyPressed(KB::L)) {
auto &debug = $world.get_the<Debug>(); auto &debug = world.get_the<Debug>();
debug.LIGHT = !debug.LIGHT; debug.LIGHT = !debug.LIGHT;
} else if(KB::isKeyPressed(KB::I)) { } else if(KB::isKeyPressed(KB::I)) {
toggle_modal(&$inventory_ui, $inventory_open); toggle_modal(&$inventory_ui, $inventory_open);
} else if(KB::isKeyPressed(KB::P)) { } else if(KB::isKeyPressed(KB::P)) {
auto &debug = $world.get_the<Debug>(); auto &debug = world.get_the<Debug>();
debug.PATHS = !debug.PATHS; debug.PATHS = !debug.PATHS;
} else if(KB::isKeyPressed(KB::S)) { } else if(KB::isKeyPressed(KB::S)) {
save_world(); save_world();
@ -422,12 +423,11 @@ void GUI::draw_paused() {
} }
void GUI::run_systems() { void GUI::run_systems() {
auto player = $world.get_the<Player>(); System::motion($level);
System::motion($world, $game_map); System::enemy_pathing($level);
System::enemy_pathing($world, $game_map, player); System::lighting($level);
System::lighting($world, $game_map, $lights); System::collision($level);
System::collision($world, player); System::death($level);
System::death($world);
} }
void GUI::shake() { void GUI::shake() {
@ -481,7 +481,9 @@ void GUI::render_scene() {
} }
int GUI::main(bool run_once) { int GUI::main(bool run_once) {
$world.set_the<Debug>({}); auto &world = *$level.world;
world.set_the<Debug>({});
create_renderer(); create_renderer();
run_systems(); run_systems();

@ -19,6 +19,7 @@
#include "render.hpp" #include "render.hpp"
#include "panel.hpp" #include "panel.hpp"
#include "lights.hpp" #include "lights.hpp"
#include "levelmanager.hpp"
using std::string; using std::string;
using ftxui::Canvas, ftxui::Component, ftxui::Screen, ftxui::Button; using ftxui::Canvas, ftxui::Component, ftxui::Screen, ftxui::Button;
@ -122,10 +123,9 @@ class MapViewUI : public Panel {
}; };
class GUI { class GUI {
DinkyECS::World& $world; LevelManager $level_manager;
Map& $game_map; GameLevel &$level;
StatusUI $status_ui; StatusUI $status_ui;
LightRender $lights;
MapViewUI $map_view; MapViewUI $map_view;
InventoryUI $inventory_ui; InventoryUI $inventory_ui;
DeathUI $death_ui; DeathUI $death_ui;
@ -142,7 +142,8 @@ class GUI {
std::vector<Panel*> $active_panels; std::vector<Panel*> $active_panels;
public: public:
GUI(DinkyECS::World& world, Map& game_map); GUI();
// disable copying // disable copying
GUI(GUI &gui) = delete; GUI(GUI &gui) = delete;

@ -2,13 +2,16 @@
#include "worldbuilder.hpp" #include "worldbuilder.hpp"
#include "constants.hpp" #include "constants.hpp"
#include "save.hpp" #include "save.hpp"
#include "systems.hpp"
using lighting::LightRender; using lighting::LightRender;
using std::shared_ptr, std::make_shared; using std::shared_ptr, std::make_shared;
LevelManager::LevelManager() { LevelManager::LevelManager() {
create_level();
} }
size_t LevelManager::create_level() { size_t LevelManager::create_level() {
auto world = make_shared<DinkyECS::World>(); auto world = make_shared<DinkyECS::World>();
save::load_configs(*world); save::load_configs(*world);
@ -19,8 +22,14 @@ size_t LevelManager::create_level() {
size_t index = $levels.size(); size_t index = $levels.size();
auto collider = make_shared<SpatialMap>();
// not sure if this is still needed
world->set_the<SpatialMap>(*collider);
System::init_positions(*world, *collider);
$levels.emplace_back(index, map, world, $levels.emplace_back(index, map, world,
make_shared<LightRender>(map->width(), map->height())); make_shared<LightRender>(map->width(), map->height()),
collider);
dbc::check(index == $levels.size() - 1, "Level index is not the same as $levels.size() - 1, off by one error"); dbc::check(index == $levels.size() - 1, "Level index is not the same as $levels.size() - 1, off by one error");
return index; return index;

@ -5,6 +5,7 @@
#include "map.hpp" #include "map.hpp"
#include <vector> #include <vector>
#include <memory> #include <memory>
#include "spatialmap.hpp"
struct GameLevel { struct GameLevel {
@ -12,6 +13,7 @@ struct GameLevel {
std::shared_ptr<Map> map; std::shared_ptr<Map> map;
std::shared_ptr<DinkyECS::World> world; std::shared_ptr<DinkyECS::World> world;
std::shared_ptr<lighting::LightRender> lights; std::shared_ptr<lighting::LightRender> lights;
std::shared_ptr<SpatialMap> collision;
}; };
class LevelManager { class LevelManager {
@ -25,6 +27,6 @@ class LevelManager {
GameLevel &next(); GameLevel &next();
GameLevel &previous(); GameLevel &previous();
GameLevel &current(); GameLevel &current();
size_t current_index(); size_t current_index() { return $current_level; }
GameLevel &get(size_t index); GameLevel &get(size_t index);
}; };

@ -10,6 +10,7 @@
#include "save.hpp" #include "save.hpp"
#include "lights.hpp" #include "lights.hpp"
#include "worldbuilder.hpp" #include "worldbuilder.hpp"
#include "levelmanager.hpp"
#include "ftxui/screen/terminal.hpp" // for SetColorSupport, Color, TrueColor #include "ftxui/screen/terminal.hpp" // for SetColorSupport, Color, TrueColor
#include <filesystem> #include <filesystem>
#include <fcntl.h> #include <fcntl.h>
@ -20,25 +21,8 @@ using namespace components;
using lighting::LightSource; using lighting::LightSource;
namespace fs = std::filesystem; namespace fs = std::filesystem;
int main(int argc, char *argv[]) { int main() {
DinkyECS::World world; GUI gui;
Map game_map(GAME_MAP_X, GAME_MAP_Y);
save::load_configs(world);
if(argc == 2) {
fmt::println("Loading save file {}", argv[1]);
fs::path save_path{argv[1]};
save::from_file(save_path, world, game_map);
} else {
WorldBuilder builder(game_map);
builder.generate(world);
}
SpatialMap collider;
world.set_the<SpatialMap>(collider);
System::init_positions(world);
GUI gui(world, game_map);
return gui.main(); return gui.main();
} }

@ -16,25 +16,33 @@ using namespace components;
using ftxui::Color; using ftxui::Color;
using lighting::LightSource; using lighting::LightSource;
void System::lighting(DinkyECS::World &world, Map &game_map, LightRender &light) { void System::lighting(GameLevel &level) {
auto &light = *level.lights;
auto &world = *level.world;
auto &map = *level.map;
light.reset_light(); light.reset_light();
world.query<Position>([&](const auto &ent[[maybe_unused]], auto &position) { world.query<Position>([&](const auto &ent[[maybe_unused]], auto &position) {
light.set_light_target(position.location); light.set_light_target(position.location);
}); });
light.path_light(game_map.walls()); light.path_light(map.walls());
world.query<Position, LightSource>([&](const auto &ent[[maybe_unused]], auto &position, auto &lightsource) { world.query<Position, LightSource>([&](const auto &ent[[maybe_unused]], auto &position, auto &lightsource) {
light.render_light(lightsource, position.location); light.render_light(lightsource, position.location);
}); });
} }
void System::enemy_pathing(DinkyECS::World &world, Map &game_map, Player &player) { void System::enemy_pathing(GameLevel &level) {
auto &world = *level.world;
auto &map = *level.map;
auto player = world.get_the<Player>();
// TODO: this will be on each enemy not a global thing // TODO: this will be on each enemy not a global thing
const auto &player_position = world.get<Position>(player.entity); const auto &player_position = world.get<Position>(player.entity);
game_map.set_target(player_position.location); map.set_target(player_position.location);
game_map.make_paths(); map.make_paths();
world.query<Position, Motion>([&](const auto &ent, auto &position, auto &motion) { world.query<Position, Motion>([&](const auto &ent, auto &position, auto &motion) {
if(ent != player.entity) { if(ent != player.entity) {
@ -42,18 +50,16 @@ void System::enemy_pathing(DinkyECS::World &world, Map &game_map, Player &player
const auto &config = world.get<EnemyConfig>(ent); const auto &config = world.get<EnemyConfig>(ent);
Point out = position.location; // copy Point out = position.location; // copy
if(game_map.distance(out) < config.hearing_distance) { if(map.distance(out) < config.hearing_distance) {
game_map.neighbors(out, motion.random); map.neighbors(out, motion.random);
motion = { int(out.x - position.location.x), int(out.y - position.location.y)}; motion = { int(out.x - position.location.x), int(out.y - position.location.y)};
} }
} }
}); });
game_map.clear_target(player_position.location); map.clear_target(player_position.location);
} }
void System::init_positions(DinkyECS::World &world) { void System::init_positions(DinkyECS::World &world, SpatialMap &collider) {
auto &collider = world.get_the<SpatialMap>();
// BUG: instead of separate things maybe just one // BUG: instead of separate things maybe just one
// BUG: Collision component that references what is collide // BUG: Collision component that references what is collide
world.query<Position>([&](const auto &ent, auto &pos) { world.query<Position>([&](const auto &ent, auto &pos) {
@ -85,25 +91,28 @@ inline void move_entity(SpatialMap &collider, Map &game_map, Position &position,
position.location = move_to; position.location = move_to;
} }
void System::motion(DinkyECS::World &world, Map &game_map) { void System::motion(GameLevel &level) {
auto &collider = world.get_the<SpatialMap>(); auto &map = *level.map;
auto &world = *level.world;
auto &collider = *level.collision;
world.query<Position, Motion>([&](const auto &ent, auto &position, auto &motion) { world.query<Position, Motion>([&](const auto &ent, auto &position, auto &motion) {
// don't process entities that don't move // don't process entities that don't move
if(motion.dx != 0 || motion.dy != 0) { if(motion.dx != 0 || motion.dy != 0) {
move_entity(collider, game_map, position, motion, ent); move_entity(collider, map, position, motion, ent);
} }
}); });
} }
void System::death(DinkyECS::World &world) { void System::death(GameLevel &level) {
auto &world = *level.world;
auto &collider = *level.collision;
auto player = world.get_the<Player>();
// BUG: this is where entities can die on top of // BUG: this is where entities can die on top of
// BUG: eachother and overlap their corpse // BUG: eachother and overlap their corpse
// BUG: maybe that can be allowed and looting just shows // BUG: maybe that can be allowed and looting just shows
// BUG: all dead things there? // BUG: all dead things there?
auto &collider = world.get_the<SpatialMap>();
auto player = world.get_the<Player>();
world.query<Position, Combat>([&](const auto &ent, auto &position, auto &combat) { world.query<Position, Combat>([&](const auto &ent, auto &position, auto &combat) {
// bring out yer dead // bring out yer dead
if(combat.hp <= 0 && !combat.dead) { if(combat.hp <= 0 && !combat.dead) {
@ -121,8 +130,11 @@ void System::death(DinkyECS::World &world) {
}); });
} }
void System::collision(DinkyECS::World &world, Player &player) { void System::collision(GameLevel &level) {
auto& collider = world.get_the<SpatialMap>(); auto &collider = *level.collision;
auto &world = *level.world;
auto player = world.get_the<Player>();
const auto& player_position = world.get<Position>(player.entity); const auto& player_position = world.get<Position>(player.entity);
auto& player_combat = world.get<Combat>(player.entity); auto& player_combat = world.get<Combat>(player.entity);
@ -185,15 +197,19 @@ void System::collision(DinkyECS::World &world, Player &player) {
} }
} }
void System::draw_entities(DinkyECS::World &world, Map &game_map, const Matrix &lighting, ftxui::Canvas &canvas, const Point &cam_orig, size_t view_x, size_t view_y) { /*
auto &tiles = game_map.tiles(); * This one is called inside the MapViewUI very often so
* just avoide GameMap unlike the others.
*/
void System::draw_entities(DinkyECS::World &world, Map &map, const Matrix &lights, ftxui::Canvas &canvas, const Point &cam_orig, size_t view_x, size_t view_y) {
auto &tiles = map.tiles();
world.query<Position, Tile>([&](auto &ent[[maybe_unused]], auto &pos, auto &tile) { world.query<Position, Tile>([&](auto &ent[[maybe_unused]], auto &pos, auto &tile) {
if(pos.location.x >= cam_orig.x && pos.location.x <= cam_orig.x + view_x if(pos.location.x >= cam_orig.x && pos.location.x <= cam_orig.x + view_x
&& pos.location.y >= cam_orig.y && pos.location.y <= cam_orig.y + view_y) { && pos.location.y >= cam_orig.y && pos.location.y <= cam_orig.y + view_y) {
Point loc = game_map.map_to_camera(pos.location, cam_orig); Point loc = map.map_to_camera(pos.location, cam_orig);
float light_value = lighting[pos.location.y][pos.location.x] * PERCENT; float light_value = lights[pos.location.y][pos.location.x] * PERCENT;
const TileCell& cell = tiles.at(pos.location.x, pos.location.y); const TileCell& cell = tiles.at(pos.location.x, pos.location.y);
// the 2 and 4 are from ftxui::Canvas since it does a kind of "subpixel" drawing // the 2 and 4 are from ftxui::Canvas since it does a kind of "subpixel" drawing

@ -1,21 +1,20 @@
#pragma once #pragma once
#include "dinkyecs.hpp"
#include "map.hpp"
#include "components.hpp" #include "components.hpp"
#include "levelmanager.hpp"
#include <ftxui/dom/canvas.hpp> #include <ftxui/dom/canvas.hpp>
namespace System { namespace System {
using namespace components; using namespace components;
using namespace lighting;
void lighting(DinkyECS::World &world, Map &game_map, LightRender &light); void lighting(GameLevel &level);
void motion(DinkyECS::World &world, Map &game_map); void motion(GameLevel &level);
void collision(DinkyECS::World &world, Player &player); void collision(GameLevel &level);
void death(DinkyECS::World &world); void death(GameLevel &level);
void enemy_pathing(DinkyECS::World &world, Map &game_map, Player &player); void enemy_pathing(GameLevel &level);
void draw_entities(DinkyECS::World &world, Map &game_map, const Matrix &lighting, ftxui::Canvas &canvas, const Point &cam_orig, size_t view_x, size_t view_y);
void init_positions(DinkyECS::World &world); void init_positions(DinkyECS::World &world, SpatialMap &collider);
void pickup(DinkyECS::World &world, DinkyECS::Entity actor, DinkyECS::Entity item); void pickup(DinkyECS::World &world, DinkyECS::Entity actor, DinkyECS::Entity item);
void device(DinkyECS::World &world, DinkyECS::Entity actor, DinkyECS::Entity item); void device(DinkyECS::World &world, DinkyECS::Entity actor, DinkyECS::Entity item);
void draw_entities(DinkyECS::World &world, Map &map, const Matrix &lights, ftxui::Canvas &canvas, const Point &cam_orig, size_t view_x, size_t view_y);
} }

@ -14,16 +14,6 @@ using namespace components;
using std::string; using std::string;
TEST_CASE("load a basic gui run but don't loop", "[gui]") { TEST_CASE("load a basic gui run but don't loop", "[gui]") {
DinkyECS::World world; GUI gui;
save::load_configs(world);
Map game_map(40, 40);
WorldBuilder builder(game_map);
builder.generate(world);
SpatialMap collider;
world.set_the<SpatialMap>(collider);
System::init_positions(world);
GUI gui(world, game_map);
gui.main(true); // runs once gui.main(true); // runs once
} }

@ -15,7 +15,8 @@ using std::string;
TEST_CASE("basic level manager test", "[levelmanager]") { TEST_CASE("basic level manager test", "[levelmanager]") {
LevelManager lm; LevelManager lm;
size_t level1 = lm.create_level(); // starts off with one already but I need to change that
size_t level1 = lm.current_index();
size_t level2 = lm.create_level(); size_t level2 = lm.create_level();
auto& test1_level = lm.get(level1); auto& test1_level = lm.get(level1);

Loading…
Cancel
Save