The attack is now fully configurable in attacks.json and the name for each attack is generated from the ritual crafting.

This commit is contained in:
Zed A. Shaw 2026-08-13 13:51:38 -04:00
parent adcfbb883e
commit 09b61f17a7
11 changed files with 77 additions and 45 deletions

28
assets/attacks.json Normal file
View file

@ -0,0 +1,28 @@
{
"MAGICK_FIRE": {
"animation": "burning_animation",
"shader": "flame",
"components": [
{"_type": "Sprite", "name": "burning_animation", "scale": 1.0},
{"_type": "LightSource", "strength": 25, "radius": 2.0},
{"_type": "Temporary", "is": true}
]
},
"MAGICK_LIGHTNING": {
"animation": "lightning_animation",
"shader": "lightning",
"components": [
{"_type": "Sprite", "name": "lightning_animation", "scale": 1.0},
{"_type": "LightSource", "strength": 35, "radius": 2.5},
{"_type": "Temporary", "is": true}
]
},
"PHYSICAL_NONE": {
"animation": "lightning_animation",
"shader": "flame",
"components": [
{"_type": "Sprite", "name": "lightning_animation", "scale": 1.0},
{"_type": "Temporary", "is": true}
]
}
}

View file

@ -6,12 +6,12 @@
"foreground": "enemies/fg:player", "foreground": "enemies/fg:player",
"background": "color:transparent" "background": "color:transparent"
}, },
{"_type": "Combat", "hp": 200, "max_hp": 200, "ap": 0, "max_ap": 12, "ap_delta": 6, "damage": 50, "dead": false, "attack_rating": 0.60, "toughness_rating": 0.0}, {"_type": "Combat", "hp": 200, "max_hp": 200, "ap": 0, "max_ap": 12, "ap_delta": 6, "damage": 500, "dead": false, "attack_rating": 1.0, "toughness_rating": 0.0},
{"_type": "Motion", "dx": 0, "dy": 0, "random": false}, {"_type": "Motion", "dx": 0, "dy": 0, "random": false},
{"_type": "Collision", "has": true}, {"_type": "Collision", "has": true},
{"_type": "EnemyConfig", "ai_script": "Host::actions", "ai_start_name": "Host::initial_state", "ai_goal_name": "Host::final_state"}, {"_type": "EnemyConfig", "ai_script": "Host::actions", "ai_start_name": "Host::initial_state", "ai_goal_name": "Host::final_state"},
{"_type": "Personality", "hearing_distance": 5, "tough": false}, {"_type": "Personality", "hearing_distance": 5, "tough": false},
{"_type": "LightSource", "strength": 35, "radius": 2.0} {"_type": "LightSource", "strength": 20, "radius": 1.0}
] ]
}, },
"GOLD_SAVIOR": { "GOLD_SAVIOR": {

View file

@ -30,6 +30,7 @@ namespace components {
components::enroll<Sprite>(MAP); components::enroll<Sprite>(MAP);
components::enroll<Sound>(MAP); components::enroll<Sound>(MAP);
components::enroll<Collision>(MAP); components::enroll<Collision>(MAP);
components::enroll<Temporary>(MAP);
MAP_configured = true; MAP_configured = true;
} }
} }

View file

@ -74,6 +74,7 @@ namespace components {
settings::Config rituals; settings::Config rituals;
settings::Config stories; settings::Config stories;
settings::Config scenes; settings::Config scenes;
settings::Config attacks;
}; };
struct Personality { struct Personality {
@ -174,6 +175,7 @@ namespace components {
ENROLL_COMPONENT(Storyboard, image, audio, layout, beats); ENROLL_COMPONENT(Storyboard, image, audio, layout, beats);
ENROLL_COMPONENT(Sound, attack, death); ENROLL_COMPONENT(Sound, attack, death);
ENROLL_COMPONENT(Collision, has); ENROLL_COMPONENT(Collision, has);
ENROLL_COMPONENT(Temporary, is);
template<typename COMPONENT> COMPONENT convert(nlohmann::json &data) { template<typename COMPONENT> COMPONENT convert(nlohmann::json &data) {
COMPONENT result; COMPONENT result;

View file

@ -130,6 +130,7 @@ namespace GameDB {
} }
void load_configs(DinkyECS::World &world) { void load_configs(DinkyECS::World &world) {
// TODO: Could have a simple meta-config file that loads these....
world.set_the<GameConfig>({ world.set_the<GameConfig>({
settings::get("config"), settings::get("config"),
settings::get("enemies"), settings::get("enemies"),
@ -139,7 +140,8 @@ namespace GameDB {
settings::get("bosses"), settings::get("bosses"),
settings::get("rituals"), settings::get("rituals"),
settings::get("stories"), settings::get("stories"),
settings::get("scenes") settings::get("scenes"),
settings::get("attacks")
}); });
} }
} }

View file

@ -1,6 +1,7 @@
#include "game/rituals.hpp" #include "game/rituals.hpp"
#include "ai/ai_debug.hpp" #include "ai/ai_debug.hpp"
#include "ai/ai.hpp" #include "ai/ai.hpp"
#include <magic_enum/magic_enum.hpp>
namespace ritual { namespace ritual {
Engine::Engine(std::string config_path) : Engine::Engine(std::string config_path) :
@ -129,6 +130,12 @@ namespace ritual {
fmt::println("\n"); fmt::println("\n");
} }
std::string Action::name() {
return fmt::format("{}_{}",
magic_enum::enum_name(kind),
magic_enum::enum_name(element));
}
Action& Belt::get(int index) { Action& Belt::get(int index) {
return equipped.at(index); return equipped.at(index);
} }

View file

@ -49,6 +49,7 @@ namespace ritual {
std::vector<std::string> names; std::vector<std::string> names;
void dump(); void dump();
std::string name();
}; };
struct Engine { struct Engine {

View file

@ -241,7 +241,7 @@ void System::death() {
} }
} }
void System::combat(int attack_id) { void System::combat(int belt_id) {
auto& level = GameDB::current_level(); auto& level = GameDB::current_level();
auto& collider = *level.collision; auto& collider = *level.collision;
auto& world = *level.world; auto& world = *level.world;
@ -285,8 +285,9 @@ void System::combat(int attack_id) {
.enemy_did=0 .enemy_did=0
}; };
// TODO: Need a "miss" animation/sound
if(result.player_did > 0) { if(result.player_did > 0) {
spawn_attack(world, attack_id, enemy.entity); spawn_attack(world, belt_id, enemy.entity);
} }
if(enemy_action == "kill_enemy") { if(enemy_action == "kill_enemy") {
@ -666,31 +667,34 @@ void System::clear_attack() {
} }
} }
void System::spawn_attack(World& world, int attack_id, DinkyECS::Entity enemy) { void System::spawn_attack(World& world, int belt_id, DinkyECS::Entity enemy) {
auto& config = world.get_the<GameConfig>().attacks;
using enum ritual::Element; // for FIRE vs LIGHTNING using enum ritual::Element; // for FIRE vs LIGHTNING
auto& the_belt = world.get_the<ritual::Belt>(); auto& the_belt = world.get_the<ritual::Belt>();
dbc::check(the_belt.has(attack_id), "STOP passing invalid attack IDs to the system."); dbc::check(the_belt.has(belt_id), "STOP passing invalid attack IDs to the system.");
auto& ritual = the_belt.get(attack_id); auto& ritual = the_belt.get(belt_id);
fmt::println("RITUAL NAME: {}", ritual.name());
auto effect = ritual.element == FIRE ? "burning_animation" : "lightning_animation"; auto attack_id = world.entity();
auto& attack_config = config[ritual.name()];
auto effect_id = world.entity(); components::configure_entity(world, attack_id, attack_config["components"]);
world.set<Sprite>(effect_id, {effect, 1.0f});
world.set<Temporary>(effect_id, {true});
auto shader = shaders::get(ritual.element == FIRE ? "flame" : "lightning"); auto shader = shaders::get(attack_config["shader"]);
world.set<SpriteEffect>(effect_id, {100, shader}); world.set<SpriteEffect>(attack_id, {100, shader});
// also add the same effect to the enemy // also add the same effect to the enemy
world.set<SpriteEffect>(enemy, {50, shader}); world.set<SpriteEffect>(enemy, {50, shader});
auto anim = animation::load("assets/animation.json", effect); auto anim = animation::load("assets/animation.json", attack_config["animation"]);
anim.play(); anim.play();
world.set<animation::Animation>(effect_id, anim); world.set<animation::Animation>(attack_id, anim);
drop_item(effect_id); //BUG: THIS IS BULLSHIT! Should have a generic place item thing.
drop_item(attack_id);
} }
void System::init(Registry& reg) { void System::init(Registry& reg) {

View file

@ -25,7 +25,7 @@ namespace System {
void drop_item(Entity item); void drop_item(Entity item);
void enemy_ai(); void enemy_ai();
void combat(int attack_id); void combat(int belt_id);
std::shared_ptr<sf::Shader> sprite_effect(Entity entity); std::shared_ptr<sf::Shader> sprite_effect(Entity entity);
void player_status(); void player_status();
@ -70,7 +70,7 @@ namespace System {
} }
void clear_attack(); void clear_attack();
void spawn_attack(World& world, int attack_id, DinkyECS::Entity enemy); void spawn_attack(World& world, int belt_id, DinkyECS::Entity enemy);
void init(Registry& reg); void init(Registry& reg);
void restart(); void restart();
} }

View file

@ -1,4 +1,3 @@
#define FSM_DEBUG 1
#include "gui/fsm.hpp" #include "gui/fsm.hpp"
#include <iostream> #include <iostream>
#include <chrono> #include <chrono>
@ -14,6 +13,7 @@
#include "gui/guecstra.hpp" #include "gui/guecstra.hpp"
#include "game/level.hpp" #include "game/level.hpp"
#include "boss/system.hpp" #include "boss/system.hpp"
#include <magic_enum/magic_enum.hpp>
namespace gui { namespace gui {
using namespace components; using namespace components;
@ -60,8 +60,6 @@ namespace gui {
$map_ui.init(); $map_ui.init();
$map_ui.log(L"Welcome to the game!"); $map_ui.log(L"Welcome to the game!");
run_systems();
state(State::IDLE); state(State::IDLE);
} }
@ -69,7 +67,6 @@ namespace gui {
// this should be an optional that returns a point // this should be an optional that returns a point
if(auto move_to = $main_ui.play_move()) { if(auto move_to = $main_ui.play_move()) {
$systems.runMoving(*move_to); $systems.runMoving(*move_to);
run_systems();
$main_ui.dirty(); $main_ui.dirty();
state(State::IDLE); state(State::IDLE);
} }
@ -90,8 +87,6 @@ namespace gui {
something_died(std::any_cast<DinkyECS::Entity>(data)); something_died(std::any_cast<DinkyECS::Entity>(data));
break; break;
case TICK: case TICK:
run_systems();
if(!$main_ui.hands_playing()) { if(!$main_ui.hands_playing()) {
// run combat one more time // run combat one more time
state(State::IDLE); state(State::IDLE);
@ -159,11 +154,7 @@ namespace gui {
$map_open = !$map_open; $map_open = !$map_open;
break; break;
case ATTACK: case ATTACK:
$main_ui.play_hands(); play_attack(std::any_cast<int>(data));
$main_ui.dirty();
$systems.runCombat(0);
run_systems();
$status_ui.update();
state(State::ATTACKING); state(State::ATTACKING);
break; break;
case CLOSE: case CLOSE:
@ -238,7 +229,6 @@ namespace gui {
auto& sprite = world->get<components::Sprite>(entity); auto& sprite = world->get<components::Sprite>(entity);
$main_ui.$rayview->update_sprite(entity, sprite); $main_ui.$rayview->update_sprite(entity, sprite);
$main_ui.dirty(); $main_ui.dirty();
run_systems();
} }
void FSM::something_died(DinkyECS::Entity entity) { void FSM::something_died(DinkyECS::Entity entity) {
@ -254,6 +244,13 @@ namespace gui {
} }
} }
void FSM::play_attack(int attack_id) {
$main_ui.play_hands();
$main_ui.dirty();
$systems.runCombat(attack_id);
$status_ui.update();
}
void FSM::CUT_SCENE_PLAYING(Event ev, std::any data) { void FSM::CUT_SCENE_PLAYING(Event ev, std::any data) {
if($boss_scene->playing()) { if($boss_scene->playing()) {
if(ev == game::Event::MOUSE_CLICK) { if(ev == game::Event::MOUSE_CLICK) {
@ -404,22 +401,18 @@ namespace gui {
} }
void FSM::update() { void FSM::update() {
if(in_state(State::BOSS_FIGHT)) { $systems.runUpdate();
$boss_fight->update();
} else if(in_state(State::CUT_SCENE_PLAYING)) {
$boss_scene->update();
} else {
// BUG: look at draw_gui for the update for here
}
} }
void FSM::render() { void FSM::render() {
$window.clear(); $window.clear();
if(in_state(State::BOSS_FIGHT)) { if(in_state(State::BOSS_FIGHT)) {
$boss_fight->update();
$boss_fight->render($window); $boss_fight->render($window);
// this clears any attack animations, like fire // this clears any attack animations, like fire
} else if(in_state(State::CUT_SCENE_PLAYING)) { } else if(in_state(State::CUT_SCENE_PLAYING)) {
$boss_scene->update();
$boss_scene->render($window); $boss_scene->render($window);
} else { } else {
// this clears any attack animations, like fire // this clears any attack animations, like fire
@ -431,10 +424,6 @@ namespace gui {
$window.display(); $window.display();
} }
void FSM::run_systems() {
$systems.runUpdate();
}
bool FSM::active() { bool FSM::active() {
return !in_state(State::END); return !in_state(State::END);
} }
@ -487,7 +476,5 @@ namespace gui {
$main_ui.update_level(); $main_ui.update_level();
$loot_ui.update_level(); $loot_ui.update_level();
} }
run_systems();
} }
} }

View file

@ -74,7 +74,6 @@ namespace gui {
void update(); void update();
void render(); void render();
bool active(); bool active();
void run_systems();
void handle_events(); void handle_events();
void handle_boss_fight_events(); void handle_boss_fight_events();
void next_level(bool bossfight); void next_level(bool bossfight);
@ -82,5 +81,6 @@ namespace gui {
void take_screenshot(); void take_screenshot();
void something_died(DinkyECS::Entity entity); void something_died(DinkyECS::Entity entity);
void entity_spawned(DinkyECS::Entity entity); void entity_spawned(DinkyECS::Entity entity);
void play_attack(int attack_id);
}; };
} }