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",
"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": "Collision", "has": true},
{"_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": "LightSource", "strength": 35, "radius": 2.0}
{"_type": "LightSource", "strength": 20, "radius": 1.0}
]
},
"GOLD_SAVIOR": {

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
#include "game/rituals.hpp"
#include "ai/ai_debug.hpp"
#include "ai/ai.hpp"
#include <magic_enum/magic_enum.hpp>
namespace ritual {
Engine::Engine(std::string config_path) :
@ -129,6 +130,12 @@ namespace ritual {
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) {
return equipped.at(index);
}

View file

@ -49,6 +49,7 @@ namespace ritual {
std::vector<std::string> names;
void dump();
std::string name();
};
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& collider = *level.collision;
auto& world = *level.world;
@ -285,8 +285,9 @@ void System::combat(int attack_id) {
.enemy_did=0
};
// TODO: Need a "miss" animation/sound
if(result.player_did > 0) {
spawn_attack(world, attack_id, enemy.entity);
spawn_attack(world, belt_id, enemy.entity);
}
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
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();
world.set<Sprite>(effect_id, {effect, 1.0f});
world.set<Temporary>(effect_id, {true});
components::configure_entity(world, attack_id, attack_config["components"]);
auto shader = shaders::get(ritual.element == FIRE ? "flame" : "lightning");
world.set<SpriteEffect>(effect_id, {100, shader});
auto shader = shaders::get(attack_config["shader"]);
world.set<SpriteEffect>(attack_id, {100, shader});
// also add the same effect to the enemy
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();
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) {

View file

@ -25,7 +25,7 @@ namespace System {
void drop_item(Entity item);
void enemy_ai();
void combat(int attack_id);
void combat(int belt_id);
std::shared_ptr<sf::Shader> sprite_effect(Entity entity);
void player_status();
@ -70,7 +70,7 @@ namespace System {
}
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 restart();
}

View file

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

View file

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