The player now has some starting items to craft a first weapon, and it is craftable in the UI.

master
Zed A. Shaw 1 week ago
parent c8a8d2b1af
commit bc557652ba
  1. 7
      assets/config.json
  2. 17
      assets/rituals.json
  3. 4
      combat_ui.cpp
  4. 68
      ritual_ui.cpp
  5. 4
      ritual_ui.hpp
  6. 64
      rituals.cpp
  7. 63
      rituals.hpp
  8. 4
      systems.cpp
  9. 120
      tests/rituals.cpp
  10. 14
      worldbuilder.cpp
  11. 1
      worldbuilder.hpp

@ -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 }
]
}
}

@ -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"
]
}

@ -35,7 +35,7 @@ namespace gui {
void CombatUI::init() {
$gui.world().set_the<Background>({$gui.$parser, ColorValue::DARK_MID});
auto& the_belt = $level.world->get<combat::RitualBelt>($level.player);
auto& the_belt = $level.world->get_the<ritual::Belt>();
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:

@ -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<std::string> 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<Effect>(button, {0.4f});
$gui.set<Sprite>(button, {"the_ritual_circle"});
$gui.set<Clickable>(button, {
[&](auto ent, auto){ ritual_circle_clicked(ent); }
});
} else if(name.starts_with("inv_slot")) {
$gui.set<Sprite>(button, {
fmt::format("{}-64", junk_list[button % junk_list.size()])});
$gui.set<Effect>(button, {0.4f});
$gui.set<Sound>(button, {"ui_click"});
$gui.set<Clickable>(button, {
[&](auto ent, auto){ inv_slot_clicked(ent); }
});
} else if(name == "ritual_ui") {
$gui.set<Clickable>(button, {
[&](auto, auto){ toggle(); }
});
$gui.set<Sound>(button, {"pickup"});
}
}
auto& blanket = $level.world->get_the<ritual::Blanket>();
int i = 0;
blanket.contents.query<ritual::JunkItem>([&](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<Sprite>(button, {sprite_name});
$gui.set<Effect>(button, {0.4f});
$gui.set<Sound>(button, {"ui_click"});
$gui.set<Clickable>(button, {
[&](auto ent, auto){ inv_slot_clicked(ent); }
});
});
auto circle = $gui.entity("circle_area");
$gui.set<Effect>(circle, {0.4f});
$gui.set<Sprite>(circle, {"the_ritual_circle"});
$gui.set<Clickable>(circle, {
[&](auto ent, auto){ ritual_circle_clicked(ent); }
});
auto open_close_toggle = $gui.entity("ritual_ui");
$gui.set<Clickable>(open_close_toggle, {[&](auto, auto){ toggle(); }});
$gui.set<Sound>(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<combat::RitualBelt>($level.player);
auto& the_belt = $level.world->get_the<ritual::Belt>();
the_belt.equip(0, ritual);
$level.world->send<Events::GUI>(Events::GUI::NEW_RITUAL, $level.player, {});
reset_inv_positions();

@ -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;

@ -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<JunkItem>(ent);
}
bool Blanket::has(DinkyECS::Entity ent) {
return contents.has<JunkItem>(ent);
}
void Blanket::remove(DinkyECS::Entity ent) {
contents.remove<JunkItem>(ent);
}
}

@ -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<std::string> names;
void dump();
};
struct RitualEngine {
struct Engine {
Config $config;
ai::AIProfile $profile;
std::unordered_map<std::string, ai::Action> $actions;
std::unordered_map<std::string, ai::State> $states;
std::unordered_map<std::string, std::vector<ai::Action>> $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<int, RitualAction> equipped;
struct Belt {
std::unordered_map<int, Action> 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);
};
}

@ -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<Player>();
auto& the_belt = world.get<combat::RitualBelt>(player.entity);
auto& the_belt = world.get_the<ritual::Belt>();
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(

@ -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));
}

@ -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<ritual::Blanket>();
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<GameConfig>();
// 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>(player);
world.set<combat::RitualBelt>(player.entity, {});
world.set_the<ritual::Belt>({});
world.set_the<ritual::Blanket>({});
configure_starting_items(world);
world.set<Inventory>(player.entity, {5});
world.make_constant(player.entity);
}

@ -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);
};

Loading…
Cancel
Save