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);
}
GUI::GUI(DinkyECS::World &world, Map& game_map) :
$world(world),
$game_map(game_map),
$status_ui(world),
$lights(game_map.width(), game_map.height()),
$map_view($world, $lights, $game_map),
$inventory_ui(world),
GUI::GUI() :
$level($level_manager.current()),
$status_ui(*$level.world),
$map_view(*$level.world, *$level.lights, *$level.map),
$inventory_ui(*$level.world),
$sounds("./assets")
{
// this needs a config file soon
@ -219,8 +217,7 @@ void GUI::resize_map(int new_size) {
}
void GUI::save_world() {
$status_ui.log("Game saved!");
save::to_file("./savefile.world", $world, $game_map);
$status_ui.log("SAVING BUSTED!");
}
void GUI::create_renderer() {
@ -236,14 +233,16 @@ void GUI::create_renderer() {
}
void GUI::handle_world_events() {
auto& world = *$level.world;
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) {
case eGUI::COMBAT: {
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) {
$status_ui.log(format("Enemy HIT YOU for {} damage!", damage.enemy_did));
@ -265,10 +264,10 @@ void GUI::handle_world_events() {
break;
case eGUI::DEATH: {
// 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");
auto player_combat = $world.get<Combat>(entity);
auto player_combat = world.get<Combat>(entity);
if(player_combat.dead) {
toggle_modal(&$death_ui, $player_died);
}
@ -305,10 +304,12 @@ void GUI::shutdown() {
}
bool GUI::game_ui_events() {
auto& world = *$level.world;
using KB = sf::Keyboard;
auto player = $world.get_the<Player>();
auto player = world.get_the<Player>();
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;
if(KB::isKeyPressed(KB::Left)) {
@ -328,12 +329,12 @@ bool GUI::game_ui_events() {
} else if(KB::isKeyPressed(KB::Hyphen)) {
resize_map(map_font_size - 10);
} else if(KB::isKeyPressed(KB::L)) {
auto &debug = $world.get_the<Debug>();
auto &debug = world.get_the<Debug>();
debug.LIGHT = !debug.LIGHT;
} else if(KB::isKeyPressed(KB::I)) {
toggle_modal(&$inventory_ui, $inventory_open);
} else if(KB::isKeyPressed(KB::P)) {
auto &debug = $world.get_the<Debug>();
auto &debug = world.get_the<Debug>();
debug.PATHS = !debug.PATHS;
} else if(KB::isKeyPressed(KB::S)) {
save_world();
@ -422,12 +423,11 @@ void GUI::draw_paused() {
}
void GUI::run_systems() {
auto player = $world.get_the<Player>();
System::motion($world, $game_map);
System::enemy_pathing($world, $game_map, player);
System::lighting($world, $game_map, $lights);
System::collision($world, player);
System::death($world);
System::motion($level);
System::enemy_pathing($level);
System::lighting($level);
System::collision($level);
System::death($level);
}
void GUI::shake() {
@ -481,7 +481,9 @@ void GUI::render_scene() {
}
int GUI::main(bool run_once) {
$world.set_the<Debug>({});
auto &world = *$level.world;
world.set_the<Debug>({});
create_renderer();
run_systems();

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

@ -2,13 +2,16 @@
#include "worldbuilder.hpp"
#include "constants.hpp"
#include "save.hpp"
#include "systems.hpp"
using lighting::LightRender;
using std::shared_ptr, std::make_shared;
LevelManager::LevelManager() {
create_level();
}
size_t LevelManager::create_level() {
auto world = make_shared<DinkyECS::World>();
save::load_configs(*world);
@ -19,8 +22,14 @@ size_t LevelManager::create_level() {
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,
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");
return index;

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

@ -10,6 +10,7 @@
#include "save.hpp"
#include "lights.hpp"
#include "worldbuilder.hpp"
#include "levelmanager.hpp"
#include "ftxui/screen/terminal.hpp" // for SetColorSupport, Color, TrueColor
#include <filesystem>
#include <fcntl.h>
@ -20,25 +21,8 @@ using namespace components;
using lighting::LightSource;
namespace fs = std::filesystem;
int main(int argc, char *argv[]) {
DinkyECS::World world;
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);
int main() {
GUI gui;
return gui.main();
}

@ -16,25 +16,33 @@ using namespace components;
using ftxui::Color;
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();
world.query<Position>([&](const auto &ent[[maybe_unused]], auto &position) {
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) {
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
const auto &player_position = world.get<Position>(player.entity);
game_map.set_target(player_position.location);
game_map.make_paths();
map.set_target(player_position.location);
map.make_paths();
world.query<Position, Motion>([&](const auto &ent, auto &position, auto &motion) {
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);
Point out = position.location; // copy
if(game_map.distance(out) < config.hearing_distance) {
game_map.neighbors(out, motion.random);
if(map.distance(out) < config.hearing_distance) {
map.neighbors(out, motion.random);
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) {
auto &collider = world.get_the<SpatialMap>();
void System::init_positions(DinkyECS::World &world, SpatialMap &collider) {
// BUG: instead of separate things maybe just one
// BUG: Collision component that references what is collide
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;
}
void System::motion(DinkyECS::World &world, Map &game_map) {
auto &collider = world.get_the<SpatialMap>();
void System::motion(GameLevel &level) {
auto &map = *level.map;
auto &world = *level.world;
auto &collider = *level.collision;
world.query<Position, Motion>([&](const auto &ent, auto &position, auto &motion) {
// don't process entities that don't move
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: eachother and overlap their corpse
// BUG: maybe that can be allowed and looting just shows
// 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) {
// bring out yer 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) {
auto& collider = world.get_the<SpatialMap>();
void System::collision(GameLevel &level) {
auto &collider = *level.collision;
auto &world = *level.world;
auto player = world.get_the<Player>();
const auto& player_position = world.get<Position>(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) {
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) {
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);
// the 2 and 4 are from ftxui::Canvas since it does a kind of "subpixel" drawing

@ -1,21 +1,20 @@
#pragma once
#include "dinkyecs.hpp"
#include "map.hpp"
#include "components.hpp"
#include "levelmanager.hpp"
#include <ftxui/dom/canvas.hpp>
namespace System {
using namespace components;
using namespace lighting;
void lighting(DinkyECS::World &world, Map &game_map, LightRender &light);
void motion(DinkyECS::World &world, Map &game_map);
void collision(DinkyECS::World &world, Player &player);
void death(DinkyECS::World &world);
void enemy_pathing(DinkyECS::World &world, Map &game_map, Player &player);
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 lighting(GameLevel &level);
void motion(GameLevel &level);
void collision(GameLevel &level);
void death(GameLevel &level);
void enemy_pathing(GameLevel &level);
void init_positions(DinkyECS::World &world, SpatialMap &collider);
void pickup(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;
TEST_CASE("load a basic gui run but don't loop", "[gui]") {
DinkyECS::World world;
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 gui;
gui.main(true); // runs once
}

@ -15,7 +15,8 @@ using std::string;
TEST_CASE("basic level manager test", "[levelmanager]") {
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();
auto& test1_level = lm.get(level1);

Loading…
Cancel
Save