From bc557652baf952688f9d26e326ff74d95d27f315 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Sat, 26 Apr 2025 13:18:43 -0400 Subject: [PATCH] The player now has some starting items to craft a first weapon, and it is craftable in the UI. --- assets/config.json | 7 +-- assets/rituals.json | 17 ++++--- combat_ui.cpp | 4 +- ritual_ui.cpp | 68 ++++++++++++------------- ritual_ui.hpp | 4 +- rituals.cpp | 64 ++++++++++++++--------- rituals.hpp | 63 +++++++++++++---------- systems.cpp | 4 +- tests/rituals.cpp | 120 ++++++++++++++++++++++++-------------------- worldbuilder.cpp | 14 +++++- worldbuilder.hpp | 1 + 11 files changed, 205 insertions(+), 161 deletions(-) diff --git a/assets/config.json b/assets/config.json index 885e979..5db2b83 100644 --- a/assets/config.json +++ b/assets/config.json @@ -328,10 +328,5 @@ "SW": 8665, "W": 8592, "NW": 8598 - }, - "test_rituals": [ - { "has_spikes": true, "active": true }, - { "has_magick": true, "active": true }, - { "has_magick": true, "shiny_bauble": true, "active": true } - ] + } } diff --git a/assets/rituals.json b/assets/rituals.json index 35545f8..91758e3 100644 --- a/assets/rituals.json +++ b/assets/rituals.json @@ -170,30 +170,33 @@ "probability": 1.0 } }, - "junk": [ - { + "junk": { + "chess_pawn": { "name": "chess_pawn", "provides": ["cursed_item"] }, - { + "dirty_kerchief": { "name": "dirty_kerchief", "provides": ["has_magick"] }, - { + "mushroom": { "name": "mushroom", "provides": ["has_magick"] }, - { + "pocket_watch": { "name": "pocket_watch", "provides": ["shiny_bauble"] }, - { + "rusty_nails": { "name": "rusty_nails", "provides": ["has_spikes"] }, - { + "severed_finger": { "name": "severed_finger", "provides": ["cursed_item"] } + }, + "starting_junk": [ + "mushroom", "rusty_nails" ] } diff --git a/combat_ui.cpp b/combat_ui.cpp index 2806ccb..10dbe5a 100644 --- a/combat_ui.cpp +++ b/combat_ui.cpp @@ -35,7 +35,7 @@ namespace gui { void CombatUI::init() { $gui.world().set_the({$gui.$parser, ColorValue::DARK_MID}); - auto& the_belt = $level.world->get($level.player); + auto& the_belt = $level.world->get_the(); for(int slot = 0; slot < 4; slot++) { if(the_belt.has(slot)) { @@ -43,7 +43,7 @@ namespace gui { std::wstring label = fmt::format(L"Attack {}", slot+1); auto& ritual = the_belt.get(slot); - using enum combat::RitualElement; + using enum ritual::Element; switch(ritual.element) { case FIRE: diff --git a/ritual_ui.cpp b/ritual_ui.cpp index 6fa33e2..b69a3ef 100644 --- a/ritual_ui.cpp +++ b/ritual_ui.cpp @@ -16,10 +16,10 @@ namespace gui { $gui.position(STATUS_UI_X, STATUS_UI_Y, STATUS_UI_WIDTH, STATUS_UI_HEIGHT); $gui.layout( "[_]" - "[inv_slot9 | inv_slot10 | inv_slot11| inv_slot12]" - "[inv_slot13 | inv_slot14 | inv_slot15| inv_slot16]" - "[inv_slot17 | inv_slot18 | inv_slot19| inv_slot20]" - "[inv_slot21 | inv_slot22 | inv_slot23| inv_slot24]" + "[inv_slot0 | inv_slot1 | inv_slot2| inv_slot3]" + "[inv_slot4 | inv_slot5 | inv_slot6| inv_slot7]" + "[inv_slot8 | inv_slot9 | inv_slot10| inv_slot11]" + "[inv_slot12 | inv_slot13 | inv_slot14| inv_slot15]" "[*%(100,600)circle_area]" "[_]" "[_]" @@ -30,38 +30,32 @@ namespace gui { } void RitualUI::init() { - Config config("assets/rituals.json"); - std::vector junk_list; - - for(auto& el : config["junk"]) { - std::string name = el["name"]; - junk_list.push_back(name); - }; - - for(auto& [name, cell] : $gui.cells()) { - auto button = $gui.entity(name); - - if(name == "circle_area") { - $gui.set(button, {0.4f}); - $gui.set(button, {"the_ritual_circle"}); - $gui.set(button, { - [&](auto ent, auto){ ritual_circle_clicked(ent); } - }); - } else if(name.starts_with("inv_slot")) { - $gui.set(button, { - fmt::format("{}-64", junk_list[button % junk_list.size()])}); - $gui.set(button, {0.4f}); - $gui.set(button, {"ui_click"}); - $gui.set(button, { - [&](auto ent, auto){ inv_slot_clicked(ent); } - }); - } else if(name == "ritual_ui") { - $gui.set(button, { - [&](auto, auto){ toggle(); } - }); - $gui.set(button, {"pickup"}); - } - } + auto& blanket = $level.world->get_the(); + int i = 0; + + blanket.contents.query([&](const auto, auto& item) { + std::string slot = fmt::format("inv_slot{}", i++); + std::string sprite_name = fmt::format("{}-64", item); + + auto button = $gui.entity(slot); + $gui.set(button, {sprite_name}); + $gui.set(button, {0.4f}); + $gui.set(button, {"ui_click"}); + $gui.set(button, { + [&](auto ent, auto){ inv_slot_clicked(ent); } + }); + }); + + auto circle = $gui.entity("circle_area"); + $gui.set(circle, {0.4f}); + $gui.set(circle, {"the_ritual_circle"}); + $gui.set(circle, { + [&](auto ent, auto){ ritual_circle_clicked(ent); } + }); + + auto open_close_toggle = $gui.entity("ritual_ui"); + $gui.set(open_close_toggle, {[&](auto, auto){ toggle(); }}); + $gui.set(open_close_toggle, {"pickup"}); $ritual_ui = textures::get("ritual_crafting_area"); $ritual_ui.sprite->setPosition({0,0}); @@ -121,7 +115,7 @@ namespace gui { if($blanket.is_combined()) { // add it to the belt auto ritual = $engine.finalize($blanket); - auto& the_belt = $level.world->get($level.player); + auto& the_belt = $level.world->get_the(); the_belt.equip(0, ritual); $level.world->send(Events::GUI::NEW_RITUAL, $level.player, {}); reset_inv_positions(); diff --git a/ritual_ui.hpp b/ritual_ui.hpp index 09e971d..8158285 100644 --- a/ritual_ui.hpp +++ b/ritual_ui.hpp @@ -18,8 +18,8 @@ namespace gui { public: sf::IntRect $ritual_closed_rect{{0,0},{380,720}}; sf::IntRect $ritual_open_rect{{380 * 2,0},{380,720}}; - combat::RitualEngine $engine; - combat::RitualBlanket $blanket; + ritual::Engine $engine; + ritual::CraftingState $blanket; RitualUIState $ritual_state = RitualUIState::CLOSED; textures::SpriteTexture $ritual_ui; components::Animation $ritual_anim; diff --git a/rituals.cpp b/rituals.cpp index a5e4eb9..4839c03 100644 --- a/rituals.cpp +++ b/rituals.cpp @@ -2,8 +2,8 @@ #include "ai_debug.hpp" #include "ai.hpp" -namespace combat { - RitualEngine::RitualEngine(std::string config_path) : +namespace ritual { + Engine::Engine(std::string config_path) : $config(config_path) { $profile = $config["profile"]; @@ -31,21 +31,21 @@ namespace combat { } } - ai::State RitualEngine::load_state(std::string name) { + ai::State Engine::load_state(std::string name) { return $states.at(name); } - ai::Action RitualEngine::load_action(std::string name) { + ai::Action Engine::load_action(std::string name) { return $actions.at(name); } - RitualBlanket RitualEngine::start() { + CraftingState Engine::start() { auto start = load_state("initial"); auto goal = load_state("final"); return {"actions", start, goal}; } - void RitualEngine::set_state(RitualBlanket& ritual, std::string name, bool setting) { + void Engine::set_state(CraftingState& ritual, std::string name, bool setting) { dbc::check($profile.contains(name), fmt::format("ritual action named {} is not in profile, look in {} config", name, $config.$src_path)); @@ -53,41 +53,41 @@ namespace combat { ritual.start.set($profile.at(name), setting); } - void RitualEngine::reset(RitualBlanket& ritual) { - ritual.start = ritual.original; + void CraftingState::reset() { + start = original; } - void RitualEngine::plan(RitualBlanket& ritual) { + void Engine::plan(CraftingState& ritual) { ritual.plan = ai::plan_actions($scripts.at(ritual.script), ritual.start, ritual.goal); } - bool RitualBlanket::will_do(std::string name) { + bool CraftingState::will_do(std::string name) { if(plan.script.size() == 0) return false; return plan.script[0].name == name; } - ai::Action RitualBlanket::pop() { + ai::Action CraftingState::pop() { auto result = plan.script.front(); plan.script.pop_front(); return result; } - // BUG: maybe this should be called RitualBlanket instead? - void RitualBlanket::dump() { + // BUG: maybe this should be called CraftingState instead? + void CraftingState::dump() { ai::dump_script(script, start, plan.script); } - bool RitualBlanket::is_combined() { + bool CraftingState::is_combined() { dbc::check(!plan.script.empty(), "you are attempting to check an empty plan"); auto& last = plan.script.back(); return plan.script.size() > 1 && last.name == "combined"; } - RitualAction RitualEngine::finalize(RitualBlanket& ritual) { + Action Engine::finalize(CraftingState& ritual) { (void)ritual; - RitualAction result; + Action result; auto effects = $config["effects"]; for(auto action : ritual.plan.script) { @@ -95,15 +95,15 @@ namespace combat { auto& effect = effects[action.name]; result.damage += int(effect["damage"]); result.probability *= float(effect["probability"]); - if(effect.contains("kind")) result.kind = RitualKind(int(effect["kind"])); - if(effect.contains("element")) result.element = RitualElement(int(effect["element"])); + if(effect.contains("kind")) result.kind = Kind(int(effect["kind"])); + if(effect.contains("element")) result.element = Element(int(effect["element"])); } } return result; } - void RitualAction::dump() { + void Action::dump() { fmt::print("ritual has damage {}, prob: {}, kind: {}, element: {}; named: ", damage, probability, int(kind), int(element)); @@ -114,19 +114,37 @@ namespace combat { fmt::println("\n"); } - RitualAction& RitualBelt::get(int index) { + Action& Belt::get(int index) { return equipped.at(index); } - void RitualBelt::equip(int index, RitualAction& action) { + void Belt::equip(int index, Action& action) { equipped.insert_or_assign(index, action); } - bool RitualBelt::has(int index) { + bool Belt::has(int index) { return equipped.contains(index); } - void RitualBelt::unequip(int index) { + void Belt::unequip(int index) { equipped.erase(index); } + + DinkyECS::Entity Blanket::add(JunkItem name) { + auto ent = contents.entity(); + contents.set(ent, name); + return ent; + } + + std::string& Blanket::get(DinkyECS::Entity ent) { + return contents.get(ent); + } + + bool Blanket::has(DinkyECS::Entity ent) { + return contents.has(ent); + } + + void Blanket::remove(DinkyECS::Entity ent) { + contents.remove(ent); + } } diff --git a/rituals.hpp b/rituals.hpp index 5836ce1..2f12729 100644 --- a/rituals.hpp +++ b/rituals.hpp @@ -5,69 +5,80 @@ #include "dinkyecs.hpp" #include "components.hpp" -namespace combat { - struct RitualBlanket { +namespace ritual { + enum class Element { + NONE=0, FIRE=1, LIGHTNING=2 + }; + + enum class Kind { + NONE=0, PHYSICAL=1, MAGICK=2 + }; + + struct CraftingState { std::string script; ai::State start; ai::State original; ai::State goal; ai::ActionPlan plan; - RitualBlanket(std::string script, ai::State start, ai::State goal) : + CraftingState(std::string script, ai::State start, ai::State goal) : script(script), start(start), original(start), goal(goal) { } - RitualBlanket() {}; + CraftingState() {}; bool will_do(std::string name); void dump(); ai::Action pop(); bool is_combined(); + void reset(); }; - enum class RitualElement { - NONE=0, FIRE=1, LIGHTNING=2 - }; - - enum class RitualKind { - NONE=0, PHYSICAL=1, MAGICK=2 - }; - - struct RitualAction { + struct Action { float probability = 1.0f; int damage = 0; - RitualKind kind{RitualKind::NONE}; - RitualElement element{RitualElement::NONE}; + Kind kind{Kind::NONE}; + Element element{Element::NONE}; std::vector names; void dump(); }; - struct RitualEngine { + struct Engine { Config $config; ai::AIProfile $profile; std::unordered_map $actions; std::unordered_map $states; std::unordered_map> $scripts; - RitualEngine(std::string config_path="assets/rituals.json"); + Engine(std::string config_path="assets/rituals.json"); ai::State load_state(std::string name); ai::Action load_action(std::string name); - RitualBlanket start(); - void reset(RitualBlanket& ritual); - void set_state(RitualBlanket& ritual, std::string name, bool setting); - void plan(RitualBlanket& ritual); - RitualAction finalize(RitualBlanket& ritual); + CraftingState start(); + void set_state(CraftingState& ritual, std::string name, bool setting); + void plan(CraftingState& ritual); + Action finalize(CraftingState& ritual); }; - struct RitualBelt { - std::unordered_map equipped; + struct Belt { + std::unordered_map equipped; - RitualAction& get(int index); - void equip(int index, RitualAction& action); + Action& get(int index); + void equip(int index, Action& action); bool has(int index); void unequip(int index); }; + + using JunkItem = std::string; + + struct Blanket { + DinkyECS::World contents; + + DinkyECS::Entity add(JunkItem name); + JunkItem& get(DinkyECS::Entity ent); + bool has(DinkyECS::Entity ent); + void remove(DinkyECS::Entity ent); + }; } diff --git a/systems.cpp b/systems.cpp index 8612a30..b0370f1 100644 --- a/systems.cpp +++ b/systems.cpp @@ -208,7 +208,7 @@ void System::combat(GameLevel &level, int attack_id) { auto &collider = *level.collision; auto &world = *level.world; auto player = world.get_the(); - auto& the_belt = world.get(player.entity); + auto& the_belt = world.get_the(); dbc::check(the_belt.has(attack_id), fmt::format("the_belt does not have an attack with id={}", attack_id)); @@ -243,7 +243,7 @@ void System::combat(GameLevel &level, int attack_id) { }; if(result.player_did > 0) { - using enum combat::RitualElement; + using enum ritual::Element; if(ritual.element == FIRE || ritual.element == LIGHTNING) { auto effect = shaders::get( diff --git a/tests/rituals.cpp b/tests/rituals.cpp index 0424fb3..2ce4813 100644 --- a/tests/rituals.cpp +++ b/tests/rituals.cpp @@ -5,83 +5,81 @@ #include "dinkyecs.hpp" #include "levelmanager.hpp" -using namespace combat; +TEST_CASE("ritual::Engine basic tests", "[rituals]") { + ritual::Engine re("assets/rituals.json"); + auto craft_state = re.start(); -TEST_CASE("RitualEngine basic tests", "[rituals]") { - RitualEngine re("assets/rituals.json"); - auto blanket = re.start(); - - re.set_state(blanket, "has_spikes", true); - re.plan(blanket); + re.set_state(craft_state, "has_spikes", true); + re.plan(craft_state); fmt::println("\n\n------------ TEST WILL DO PIERCE"); - blanket.dump(); - REQUIRE(blanket.will_do("pierce_type")); + craft_state.dump(); + REQUIRE(craft_state.will_do("pierce_type")); - REQUIRE(blanket.start != blanket.original); - re.reset(blanket); - REQUIRE(blanket.start == blanket.original); + REQUIRE(craft_state.start != craft_state.original); + craft_state.reset(); + REQUIRE(craft_state.start == craft_state.original); - re.set_state(blanket, "has_magick", true); - re.set_state(blanket, "has_spikes", true); - re.plan(blanket); + re.set_state(craft_state, "has_magick", true); + re.set_state(craft_state, "has_spikes", true); + re.plan(craft_state); fmt::println("\n\n------------ TEST WILL DO MAGICK TOO"); - blanket.dump(); - REQUIRE(blanket.will_do("pierce_type")); - - blanket.pop(); - REQUIRE(blanket.will_do("magick_type")); - - re.reset(blanket); - re.set_state(blanket, "has_magick", true); - re.set_state(blanket, "has_spikes", true); - re.set_state(blanket, "shiny_bauble", true); - REQUIRE(blanket.is_combined()); - re.plan(blanket); + craft_state.dump(); + REQUIRE(craft_state.will_do("pierce_type")); + + craft_state.pop(); + REQUIRE(craft_state.will_do("magick_type")); + + craft_state.reset(); + re.set_state(craft_state, "has_magick", true); + re.set_state(craft_state, "has_spikes", true); + re.set_state(craft_state, "shiny_bauble", true); + REQUIRE(craft_state.is_combined()); + re.plan(craft_state); fmt::println("\n\n------------ TEST WILL DO DAMAGE BOOST"); - blanket.dump(); - - re.reset(blanket); - re.set_state(blanket, "has_magick", true); - re.set_state(blanket, "cursed_item", true); - re.set_state(blanket, "shiny_bauble", true); - REQUIRE(blanket.is_combined()); - re.plan(blanket); + craft_state.dump(); + + craft_state.reset(); + re.set_state(craft_state, "has_magick", true); + re.set_state(craft_state, "cursed_item", true); + re.set_state(craft_state, "shiny_bauble", true); + REQUIRE(craft_state.is_combined()); + re.plan(craft_state); fmt::println("\n\n------------ TEST WILL DO LARGE DAMAGE BOOST"); - blanket.dump(); + craft_state.dump(); } -TEST_CASE("blanket can be finalized for the end result", "[rituals]") { - RitualEngine re("assets/rituals.json"); - auto blanket = re.start(); +TEST_CASE("craft_state can be finalized for the end result", "[rituals]") { + ritual::Engine re("assets/rituals.json"); + auto craft_state = re.start(); - re.set_state(blanket, "has_magick", true); - re.set_state(blanket, "cursed_item", true); - re.set_state(blanket, "shiny_bauble", true); - re.plan(blanket); - REQUIRE(blanket.is_combined()); + re.set_state(craft_state, "has_magick", true); + re.set_state(craft_state, "cursed_item", true); + re.set_state(craft_state, "shiny_bauble", true); + re.plan(craft_state); + REQUIRE(craft_state.is_combined()); fmt::println("\n\n------------ CYCLES AVOIDED"); - blanket.dump(); + craft_state.dump(); - auto ritual = re.finalize(blanket); + auto ritual = re.finalize(craft_state); ritual.dump(); } -TEST_CASE("the ritual belt works", "[rituals-belt]") { - RitualBelt the_belt; - RitualEngine re; - auto blanket = re.start(); +TEST_CASE("the ritual belt works", "[rituals]") { + ritual::Belt the_belt; + ritual::Engine re; + auto craft_state = re.start(); - re.set_state(blanket, "has_magick", true); - re.plan(blanket); - REQUIRE(blanket.is_combined()); - blanket.dump(); + re.set_state(craft_state, "has_magick", true); + re.plan(craft_state); + REQUIRE(craft_state.is_combined()); + craft_state.dump(); { - auto ritual = re.finalize(blanket); + auto ritual = re.finalize(craft_state); the_belt.equip(0, ritual); REQUIRE(the_belt.has(0)); } @@ -96,3 +94,15 @@ TEST_CASE("the ritual belt works", "[rituals-belt]") { REQUIRE(!the_belt.has(0)); } } + +TEST_CASE("ritual blanket basic operations", "[rituals]") { + ritual::Blanket blanket; + + DinkyECS::Entity ent = blanket.add("severed_finger"); + auto& name = blanket.get(ent); + REQUIRE(name == "severed_finger"); + + REQUIRE(blanket.has(ent)); + blanket.remove(ent); + REQUIRE(!blanket.has(ent)); +} diff --git a/worldbuilder.cpp b/worldbuilder.cpp index 1a8a6da..ee29490 100644 --- a/worldbuilder.cpp +++ b/worldbuilder.cpp @@ -241,6 +241,16 @@ void WorldBuilder::place_stairs(DinkyECS::World& world, GameConfig& config) { configure_entity_in_map(world, entity_data, last_room); } +void WorldBuilder::configure_starting_items(DinkyECS::World &world) { + auto& blanket = world.get_the(); + Config config("assets/rituals.json"); + + for(auto& el : config["starting_junk"]) { + ritual::JunkItem name = el; + blanket.add(name); + }; +} + void WorldBuilder::place_entities(DinkyECS::World &world) { auto &config = world.get_the(); // configure a player as a fact of the world @@ -258,7 +268,9 @@ void WorldBuilder::place_entities(DinkyECS::World &world) { // configure player in the world Player player{player_ent}; world.set_the(player); - world.set(player.entity, {}); + world.set_the({}); + world.set_the({}); + configure_starting_items(world); world.set(player.entity, {5}); world.make_constant(player.entity); } diff --git a/worldbuilder.hpp b/worldbuilder.hpp index 61eb51e..d415ea5 100644 --- a/worldbuilder.hpp +++ b/worldbuilder.hpp @@ -30,4 +30,5 @@ class WorldBuilder { void generate(DinkyECS::World &world); void randomize_entities(DinkyECS::World &world, components::GameConfig &config); void place_stairs(DinkyECS::World& world, components::GameConfig& config); + void configure_starting_items(DinkyECS::World &world); };