Now has an actual game loop where you die and start over.

This commit is contained in:
Zed A. Shaw 2026-04-03 12:44:35 -04:00
parent 514bfec0ea
commit 009c5c1cd2
10 changed files with 124 additions and 86 deletions

View file

@ -6,7 +6,7 @@
"foreground": "enemies/fg:player",
"background": "color:transparent"
},
{"_type": "Combat", "max_hp": 200, "max_ap": 12, "ap_delta": 6, "damage": 200,
{"_type": "Combat", "max_hp": 200, "max_ap": 12, "ap_delta": 6, "damage": 20,
"body_parts": {
"head": 200,
"chest": 200,

View file

@ -65,18 +65,6 @@
}
],
"fixtures": [
{
"name": "ag_bot_speech",
"sprite": "ag_bot_speech",
"scale_x": 1.0,
"scale_y": 1.0,
"flipped": false,
"cell": "speech",
"x": 0,
"y": 0,
"at_mid": false,
"flipped": false
}
],
"buttons": {
"layout": [

View file

@ -34,6 +34,21 @@ namespace GameDB {
};
}
void init() {
components::init();
textures::init();
if(!initialized) {
reset();
}
}
void reset() {
LDB = make_shared<LevelDB>();
initialized = true;
new_level(NULL);
}
shared_ptr<DinkyECS::World> clone_load_world(shared_ptr<DinkyECS::World> prev_world) {
auto world = make_shared<DinkyECS::World>();
@ -82,17 +97,6 @@ namespace GameDB {
.collision=collision});
}
void init() {
components::init();
textures::init();
if(!initialized) {
LDB = make_shared<LevelDB>();
initialized = true;
new_level(NULL);
}
}
shared_ptr<DinkyECS::World> current_world() {
dbc::check(initialized, "Forgot to call GameDB::init()");
return current_level().world;

View file

@ -23,7 +23,10 @@ namespace GameDB {
Level& create_level();
void init();
void reset();
Level &current_level();
void new_level(std::shared_ptr<DinkyECS::World> prev_world);
std::shared_ptr<DinkyECS::World> current_world();
components::Position& player_position();
DinkyECS::Entity the_player();

View file

@ -538,6 +538,10 @@ void System::clear_attack() {
void System::spawn_attack(World& world, int attack_id, DinkyECS::Entity enemy) {
}
void System::restart() {
// reset game db
GameDB::reset();
}
void System::init(Registry& reg) {
reg.addRender(System::clear_attack);

View file

@ -13,6 +13,9 @@ namespace System {
using namespace DinkyECS;
using std::string, matrix::Matrix;
void init(Registry& reg);
void restart();
void lighting();
void motion();
void collision();
@ -70,5 +73,4 @@ namespace System {
void clear_attack();
void spawn_attack(World& world, int attack_id, DinkyECS::Entity enemy);
void init(Registry& reg);
}

View file

@ -17,15 +17,12 @@ namespace gui {
using namespace components;
using game::Event;
FSM::FSM() :
$window(sf::VideoMode({SCREEN_WIDTH, SCREEN_HEIGHT}), "Zed's Raycaster Thing"),
FSM::FSM(sf::RenderWindow& window) :
$window(window),
$main_ui($window),
$dnd_loot($status_ui, $loot_ui, $window, $router)
{
$window.setVerticalSyncEnabled(VSYNC);
if(FRAME_LIMIT) $window.setFramerateLimit(FRAME_LIMIT);
$window.setPosition({0,0});
System::init($systems);
}
void FSM::event(Event ev, std::any data) {
@ -41,7 +38,9 @@ namespace gui {
FSM_STATE(State, ROTATING, ev);
FSM_STATE(State, IDLE, ev, data);
FSM_STATE(State, LOOTING, ev, data);
FSM_STATE(State, END, ev);
FSM_STATE(State, END_QUIT, ev);
FSM_STATE(State, END_PLAY_AGAIN, ev);
FSM_STATE(State, END_PLAYER_DIED, ev);
}
}
@ -59,9 +58,9 @@ namespace gui {
$debug_ui.init(cell);
$status_ui.init();
run_systems();
$story = std::make_shared<storyboard::UI>("intro_story");
$story->init();
state(State::INTRO);
show_scene("STARTING");
state(State::START_SCENE);
}
void FSM::INTRO(Event ev) {
@ -73,8 +72,7 @@ namespace gui {
}
} else {
$story = nullptr;
show_scene("STARTING");
state(State::START_SCENE);
state(State::IDLE);
}
}
@ -90,7 +88,9 @@ namespace gui {
} break;
case START:
close_scene();
state(State::IDLE);
$story = std::make_shared<storyboard::UI>("intro_story");
$story->init();
state(State::INTRO);
break;
case QUIT:
FSM::IDLE(ev, {});
@ -115,7 +115,7 @@ namespace gui {
} break;
case START:
close_scene();
state(State::IDLE);
state(State::END_PLAYER_DIED);
break;
case QUIT:
FSM::IDLE(ev, {});
@ -137,7 +137,7 @@ namespace gui {
} break;
case START:
close_scene();
state(State::IDLE);
state(State::END_PLAY_AGAIN);
break;
case QUIT:
FSM::IDLE(ev, {});
@ -232,7 +232,7 @@ namespace gui {
switch(ev) {
case QUIT:
$window.close();
state(State::END);
state(State::END_QUIT);
return; // done
case MOVE_FORWARD:
try_move(1, false);
@ -317,8 +317,16 @@ namespace gui {
}
}
void FSM::END(Event ev) {
dbc::log($F("END: received event after done: {}", int(ev)));
void FSM::END_QUIT(Event ev) {
dbc::log($F("END_QUIT: received event after done: {}", int(ev)));
}
void FSM::END_PLAYER_DIED(Event ev) {
dbc::log($F("END_PLAYER_DIED: received event after done: {}", int(ev)));
}
void FSM::END_PLAY_AGAIN(Event ev) {
dbc::log($F("END_PLAY_AGAIN: received event after done: {}", int(ev)));
}
sf::Vector2f FSM::mouse_position() {
@ -458,7 +466,9 @@ namespace gui {
}
bool FSM::active() {
return !in_state(State::END);
return !(in_state(State::END_QUIT)
|| in_state(State::END_PLAY_AGAIN)
|| in_state(State::END_PLAYER_DIED));
}
void FSM::handle_world_events() {

View file

@ -27,12 +27,14 @@ namespace gui {
ROTATING=__LINE__,
LOOTING=__LINE__,
IDLE=__LINE__,
END=__LINE__,
END_QUIT=__LINE__,
END_PLAYER_DIED=__LINE__,
END_PLAY_AGAIN=__LINE__,
};
class FSM : public DeadSimpleFSM<State, game::Event> {
public:
sf::RenderWindow $window;
sf::RenderWindow& $window;
bool $draw_stats = false;
DebugUI $debug_ui;
MainUI $main_ui;
@ -51,7 +53,7 @@ namespace gui {
std::shared_ptr<gui::SceneUI> $cur_scene = nullptr;
std::shared_ptr<storyboard::UI> $story = nullptr;
FSM();
FSM(sf::RenderWindow& window);
void event(game::Event ev, std::any data={});
@ -67,7 +69,9 @@ namespace gui {
void MAPPING(game::Event ev);
void ROTATING(game::Event ev);
void LOOTING(game::Event ev, std::any data);
void END(game::Event ev);
void END_QUIT(game::Event ev);
void END_PLAYER_DIED(game::Event ev);
void END_PLAY_AGAIN(game::Event ev);
void try_move(int dir, bool strafe);
sf::Vector2f mouse_position();

View file

@ -7,10 +7,19 @@
namespace gui {
using namespace guecs;
LootUI::LootUI() :
$temp_loot(GameDB::current_world()->entity()),
$target($temp_loot)
LootUI::LootUI()
{
}
void LootUI::make_button(const std::string &name, const std::wstring& label, game::Event event) {
auto button = $gui.entity(name);
$gui.set<guecs::Rectangle>(button, {});
$gui.set<guecs::Text>(button, {label});
$gui.set<guecs::Clickable>(button,
guecs::make_action(button, event));
}
void LootUI::init() {
$gui.position(RAY_VIEW_X+RAY_VIEW_WIDTH/2-200,
RAY_VIEW_Y+RAY_VIEW_HEIGHT/2-200, 400, 400);
@ -21,21 +30,14 @@ namespace gui {
"[=item_12| =item_13|=item_14|=item_15 ]"
"[ =take_all | =close| =destroy]");
// setup a fake container for our loot
$temp_loot = GameDB::current_world()->entity();
$target = $temp_loot;
auto world = GameDB::current_world();
world->set<inventory::Model>($temp_loot, {});
world->make_constant($temp_loot);
}
void LootUI::make_button(const std::string &name, const std::wstring& label, game::Event event) {
auto button = $gui.entity(name);
$gui.set<guecs::Rectangle>(button, {});
$gui.set<guecs::Text>(button, {label});
$gui.set<guecs::Clickable>(button,
guecs::make_action(button, event));
}
void LootUI::init() {
using guecs::THEME;
auto bg_color = THEME.DARK_LIGHT;
bg_color.a = 140;
@ -65,7 +67,7 @@ namespace gui {
auto world = GameDB::current_world();
dbc::check(world->has<inventory::Model>($target),
"update called but $target isn't in world");
$F("update called but $target isn't in world: {}", $target));
auto& contents = world->get<inventory::Model>($target);

View file

@ -9,6 +9,33 @@
#include "graphics/camera.hpp"
#include "game/systems.hpp"
void play_game(std::shared_ptr<gui::FSM> main) {
sound::play("ambient_1", true);
main->event(game::Event::START);
while(main->active()) {
main->update();
main->render();
// BUG: need to sort out how to deal with this in the FSM
if(main->in_state(gui::State::IDLE)
|| main->in_state(gui::State::START_SCENE)
|| main->in_state(gui::State::INTRO)
|| main->in_state(gui::State::WIN_SCENE)
|| main->in_state(gui::State::DEATH_SCENE)
|| main->in_state(gui::State::NEXT_LEVEL_SCENE)
|| main->in_state(gui::State::LOOTING))
{
main->handle_keyboard_mouse();
} else{
main->event(game::Event::TICK);
}
main->handle_world_events();
}
}
int main(int argc, char* argv[]) {
try {
gui::Backend backend;
@ -21,34 +48,28 @@ int main(int argc, char* argv[]) {
cinematic::init();
sound::mute(true);
gui::FSM main;
System::init(main.$systems);
main.event(game::Event::START);
sf::RenderWindow window(sf::VideoMode({SCREEN_WIDTH, SCREEN_HEIGHT}), "Zed's Raycaster Thing");
window.setVerticalSyncEnabled(VSYNC);
if(FRAME_LIMIT) window.setFramerateLimit(FRAME_LIMIT);
window.setPosition({0,0});
sound::play("ambient_1", true);
auto main = std::make_shared<gui::FSM>(window);
while(main.active()) {
main.update();
main.render();
while(true) {
play_game(main);
// BUG: need to sort out how to deal with this in the FSM
if(main.in_state(gui::State::IDLE)
|| main.in_state(gui::State::START_SCENE)
|| main.in_state(gui::State::INTRO)
|| main.in_state(gui::State::WIN_SCENE)
|| main.in_state(gui::State::DEATH_SCENE)
|| main.in_state(gui::State::NEXT_LEVEL_SCENE)
|| main.in_state(gui::State::LOOTING))
{
main.handle_keyboard_mouse();
} else{
main.event(game::Event::TICK);
if(main->in_state(gui::State::END_QUIT)) {
return 0;
} else if(main->in_state(gui::State::END_PLAYER_DIED)) {
System::restart();
main = std::make_shared<gui::FSM>(window);
} else if(main->in_state(gui::State::END_PLAY_AGAIN)) {
System::restart();
main = std::make_shared<gui::FSM>(window);
} else {
dbc::sentinel("Unknown game end state");
}
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 "