Can now run the full AI for all combatants and then sort by the action costs to make the action queue.
This commit is contained in:
parent
d244106981
commit
b48df3f4db
10 changed files with 104 additions and 59 deletions
6
Makefile
6
Makefile
|
|
@ -36,8 +36,8 @@ tracy_build:
|
||||||
meson setup --wipe builddir --buildtype debugoptimized -Dtracy_enable=true -Dtracy:on_demand=true
|
meson setup --wipe builddir --buildtype debugoptimized -Dtracy_enable=true -Dtracy:on_demand=true
|
||||||
meson compile -j 10 -C builddir
|
meson compile -j 10 -C builddir
|
||||||
|
|
||||||
test:
|
test: build
|
||||||
./builddir/runtests -d yes
|
./builddir/runtests -d yes "[combat-battle]"
|
||||||
|
|
||||||
run: build test
|
run: build test
|
||||||
ifeq '$(OS)' 'Windows_NT'
|
ifeq '$(OS)' 'Windows_NT'
|
||||||
|
|
@ -60,7 +60,7 @@ clean:
|
||||||
meson compile --clean -C builddir
|
meson compile --clean -C builddir
|
||||||
|
|
||||||
debug_test: build
|
debug_test: build
|
||||||
gdb --nx -x .gdbinit --ex run --ex bt --ex q --args builddir/runtests -e
|
gdb --nx -x .gdbinit --ex run --ex bt --ex q --args builddir/runtests -e "[combat-battle]"
|
||||||
|
|
||||||
win_installer:
|
win_installer:
|
||||||
powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" scripts\win_installer.ifp'
|
powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" scripts\win_installer.ifp'
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,6 @@
|
||||||
"name": "use_healing",
|
"name": "use_healing",
|
||||||
"cost": 1,
|
"cost": 1,
|
||||||
"needs": {
|
"needs": {
|
||||||
"have_item": true,
|
|
||||||
"have_healing": true,
|
"have_healing": true,
|
||||||
"health_good": false
|
"health_good": false
|
||||||
},
|
},
|
||||||
|
|
@ -133,6 +132,7 @@
|
||||||
"kill_enemy",
|
"kill_enemy",
|
||||||
"collect_items",
|
"collect_items",
|
||||||
"find_healing",
|
"find_healing",
|
||||||
|
"run_away",
|
||||||
"use_healing"],
|
"use_healing"],
|
||||||
"Enemy::actions":
|
"Enemy::actions":
|
||||||
["find_enemy", "run_away", "kill_enemy", "use_healing"]
|
["find_enemy", "run_away", "kill_enemy", "use_healing"]
|
||||||
|
|
|
||||||
38
battle.cpp
38
battle.cpp
|
|
@ -10,18 +10,30 @@ namespace combat {
|
||||||
int active = 0;
|
int active = 0;
|
||||||
|
|
||||||
for(auto& [entity, enemy] : combatants) {
|
for(auto& [entity, enemy] : combatants) {
|
||||||
enemy.ai.update();
|
enemy.ai->update();
|
||||||
active += enemy.ai.active();
|
active += enemy.ai->active();
|
||||||
|
|
||||||
if(enemy.ai.active()) {
|
if(enemy.ai->active()) {
|
||||||
if(enemy.ai.wants_to("kill_enemy")) {
|
for(auto& action : enemy.ai->plan.script) {
|
||||||
pending_actions.emplace_back(enemy, BattleAction::ATTACK);
|
if(enemy.ai->wants_to("kill_enemy")) {
|
||||||
} else if(enemy.ai.wants_to("run_away")) {
|
pending_actions.emplace_back(enemy, action, BattleAction::ATTACK);
|
||||||
pending_actions.emplace_back(enemy, BattleAction::ESCAPE);
|
} else if(enemy.ai->wants_to("run_away")) {
|
||||||
|
pending_actions.emplace_back(enemy, action, BattleAction::ESCAPE);
|
||||||
|
} else {
|
||||||
|
pending_actions.emplace_back(enemy, action, BattleAction::OTHER);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(pending_actions.size() > 0) {
|
||||||
|
std::sort(pending_actions.begin(), pending_actions.end(),
|
||||||
|
[](const auto& a, const auto& b) -> bool
|
||||||
|
{
|
||||||
|
return a.wants_to.cost > b.wants_to.cost;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return active > 0;
|
return active > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,25 +48,25 @@ namespace combat {
|
||||||
void BattleEngine::dump() {
|
void BattleEngine::dump() {
|
||||||
for(auto& [entity, enemy] : combatants) {
|
for(auto& [entity, enemy] : combatants) {
|
||||||
fmt::println("\n\n###### ENTITY #{}", entity);
|
fmt::println("\n\n###### ENTITY #{}", entity);
|
||||||
enemy.ai.dump();
|
enemy.ai->dump();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleEngine::set(DinkyECS::Entity entity, const std::string& state, bool setting) {
|
void BattleEngine::set(DinkyECS::Entity entity, const std::string& state, bool setting) {
|
||||||
dbc::check(combatants.contains(entity), "invalid combatant given to BattleEngine");
|
dbc::check(combatants.contains(entity), "invalid combatant given to BattleEngine");
|
||||||
auto& action = combatants.at(entity);
|
auto& action = combatants.at(entity);
|
||||||
action.ai.set_state(state, setting);
|
action.ai->set_state(state, setting);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleEngine::set_all(const std::string& state, bool setting) {
|
void BattleEngine::set_all(const std::string& state, bool setting) {
|
||||||
for(auto& [ent, action] : combatants) {
|
for(auto& [ent, action] : combatants) {
|
||||||
action.ai.set_state(state, setting);
|
action.ai->set_state(state, setting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleEngine::queue(DinkyECS::Entity entity, BattleAction action) {
|
Combatant& BattleEngine::get_enemy(DinkyECS::Entity entity) {
|
||||||
dbc::check(combatants.contains(entity), "invalid combatant given to BattleEngine");
|
dbc::check(combatants.contains(entity), "invalid combatant given to BattleEngine");
|
||||||
auto& enemy = combatants.at(entity);
|
|
||||||
pending_actions.emplace_back(enemy, action);
|
return combatants.at(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
battle.hpp
13
battle.hpp
|
|
@ -8,17 +8,18 @@
|
||||||
namespace combat {
|
namespace combat {
|
||||||
|
|
||||||
struct Combatant {
|
struct Combatant {
|
||||||
DinkyECS::Entity entity;
|
DinkyECS::Entity entity = DinkyECS::NONE;
|
||||||
ai::EntityAI &ai;
|
ai::EntityAI* ai = nullptr;
|
||||||
components::Combat &combat;
|
components::Combat* combat = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class BattleAction {
|
enum class BattleAction {
|
||||||
ATTACK, BLOCK, ESCAPE
|
ATTACK, BLOCK, ESCAPE, OTHER
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BattleResult {
|
struct BattleResult {
|
||||||
Combatant &state;
|
Combatant state;
|
||||||
|
ai::Action wants_to;
|
||||||
BattleAction action;
|
BattleAction action;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -27,11 +28,11 @@ namespace combat {
|
||||||
std::vector<BattleResult> pending_actions;
|
std::vector<BattleResult> pending_actions;
|
||||||
|
|
||||||
void add_enemy(Combatant ba);
|
void add_enemy(Combatant ba);
|
||||||
|
Combatant& get_enemy(DinkyECS::Entity entity);
|
||||||
bool plan();
|
bool plan();
|
||||||
std::optional<BattleResult> next();
|
std::optional<BattleResult> next();
|
||||||
void dump();
|
void dump();
|
||||||
void set(DinkyECS::Entity entity, const std::string& state, bool setting);
|
void set(DinkyECS::Entity entity, const std::string& state, bool setting);
|
||||||
void set_all(const std::string& state, bool setting);
|
void set_all(const std::string& state, bool setting);
|
||||||
void queue(DinkyECS::Entity entity, BattleAction action);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,11 +33,10 @@ namespace boss {
|
||||||
switch(ev) {
|
switch(ev) {
|
||||||
case MOUSE_CLICK: {
|
case MOUSE_CLICK: {
|
||||||
$ui.mouse(mouse_pos.x, mouse_pos.y, guecs::NO_MODS);
|
$ui.mouse(mouse_pos.x, mouse_pos.y, guecs::NO_MODS);
|
||||||
} break;
|
} break;
|
||||||
case MOUSE_MOVE: {
|
case MOUSE_MOVE: {
|
||||||
$ui.mouse(mouse_pos.x, mouse_pos.y, {1 << guecs::ModBit::hover});
|
$ui.mouse(mouse_pos.x, mouse_pos.y, {1 << guecs::ModBit::hover});
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
case MOUSE_DRAG:
|
case MOUSE_DRAG:
|
||||||
dbc::log("mouse drag");
|
dbc::log("mouse drag");
|
||||||
break;
|
break;
|
||||||
|
|
@ -64,12 +63,15 @@ namespace boss {
|
||||||
case BOSS_START:
|
case BOSS_START:
|
||||||
state(State::END);
|
state(State::END);
|
||||||
break;
|
break;
|
||||||
|
case KEY_PRESS:
|
||||||
|
fmt::println("KEY_PRESS");
|
||||||
|
break;
|
||||||
case ATTACK:
|
case ATTACK:
|
||||||
$ui.status(L"PLAYER TURN");
|
$ui.status(L"PLAYER TURN");
|
||||||
state(State::PLAYER_TURN);
|
state(State::PLAYER_TURN);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// fmt::println("BOSS_FIGHT unknown event {}", (int)ev);
|
fmt::println("BOSS_FIGHT:START unknown event {}", (int)ev);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -82,11 +84,16 @@ namespace boss {
|
||||||
case BOSS_START:
|
case BOSS_START:
|
||||||
state(State::END);
|
state(State::END);
|
||||||
break;
|
break;
|
||||||
|
case KEY_PRESS:
|
||||||
|
fmt::println("KEY_PRESS");
|
||||||
|
break;
|
||||||
case ATTACK: {
|
case ATTACK: {
|
||||||
|
int attack_id = std::any_cast<int>(data);
|
||||||
|
fmt::println("Player attack: {}", attack_id);
|
||||||
|
|
||||||
$ui.status(L"PLAYER TURN");
|
$ui.status(L"PLAYER TURN");
|
||||||
const std::string& player_pos = run % 10 < 5 ? "player1" : "player2";
|
const std::string& player_pos = run % 10 < 5 ? "player1" : "player2";
|
||||||
$ui.move_actor("player", player_pos);
|
$ui.move_actor("player", player_pos);
|
||||||
int attack_id = std::any_cast<int>(data);
|
|
||||||
boss::System::combat($world, $boss_id, attack_id);
|
boss::System::combat($world, $boss_id, attack_id);
|
||||||
$ui.update_stats();
|
$ui.update_stats();
|
||||||
state(State::PLAYER_TURN);
|
state(State::PLAYER_TURN);
|
||||||
|
|
@ -105,6 +112,9 @@ namespace boss {
|
||||||
case BOSS_START:
|
case BOSS_START:
|
||||||
state(State::END);
|
state(State::END);
|
||||||
break;
|
break;
|
||||||
|
case KEY_PRESS:
|
||||||
|
fmt::println("KEY_PRESS");
|
||||||
|
break;
|
||||||
case ATTACK: {
|
case ATTACK: {
|
||||||
$ui.status(L"BOSS TURN");
|
$ui.status(L"BOSS TURN");
|
||||||
const std::string &boss_at = run % 10 < 5 ? "boss5" : "boss6";
|
const std::string &boss_at = run % 10 < 5 ? "boss5" : "boss6";
|
||||||
|
|
@ -120,10 +130,8 @@ namespace boss {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Fight::END(gui::Event ev, std::any data) {
|
void Fight::END(gui::Event ev, std::any) {
|
||||||
// We need to clean up that world I think, but not sure how
|
fmt::println("BOSS_FIGHT:END event {}", (int)ev);
|
||||||
(void)ev;
|
|
||||||
(void)data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Fight::run_systems() {
|
void Fight::run_systems() {
|
||||||
|
|
|
||||||
|
|
@ -56,17 +56,17 @@ namespace boss {
|
||||||
auto& boss_ai = world->get<ai::EntityAI>(boss_id);
|
auto& boss_ai = world->get<ai::EntityAI>(boss_id);
|
||||||
|
|
||||||
combat::BattleEngine battle;
|
combat::BattleEngine battle;
|
||||||
battle.add_enemy({boss_id, boss_ai, boss_combat});
|
battle.add_enemy({boss_id, &boss_ai, &boss_combat});
|
||||||
|
|
||||||
battle.set_all("enemy_found", true);
|
battle.set_all("enemy_found", true);
|
||||||
battle.set_all("in_combat", true);
|
battle.set_all("in_combat", true);
|
||||||
battle.plan();
|
battle.plan();
|
||||||
|
|
||||||
while(auto act = battle.next()) {
|
while(auto act = battle.next()) {
|
||||||
auto [enemy, enemy_action] = *act;
|
auto [enemy, ai_action, enemy_action] = *act;
|
||||||
|
|
||||||
Events::Combat result {
|
Events::Combat result {
|
||||||
player_combat.attack(enemy.combat), 0
|
player_combat.attack(*enemy.combat), 0
|
||||||
};
|
};
|
||||||
|
|
||||||
if(result.player_did > 0) {
|
if(result.player_did > 0) {
|
||||||
|
|
@ -76,7 +76,7 @@ namespace boss {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(enemy_action == combat::BattleAction::ATTACK) {
|
if(enemy_action == combat::BattleAction::ATTACK) {
|
||||||
result.enemy_did = enemy.combat.attack(player_combat);
|
result.enemy_did = enemy.combat->attack(player_combat);
|
||||||
}
|
}
|
||||||
|
|
||||||
// need to replicate this in the boss UI
|
// need to replicate this in the boss UI
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,11 @@ project('raycaster', 'cpp',
|
||||||
])
|
])
|
||||||
|
|
||||||
# use this for common options only for our executables
|
# use this for common options only for our executables
|
||||||
cpp_args=[]
|
cpp_args=[
|
||||||
|
'-Wno-unused-parameter',
|
||||||
|
'-Wno-unused-function',
|
||||||
|
'-Wno-unused-variable',
|
||||||
|
]
|
||||||
link_args=[]
|
link_args=[]
|
||||||
# these are passed as override_defaults
|
# these are passed as override_defaults
|
||||||
exe_defaults = [ 'warning_level=2' ]
|
exe_defaults = [ 'warning_level=2' ]
|
||||||
|
|
|
||||||
|
|
@ -244,7 +244,7 @@ void System::combat(int attack_id) {
|
||||||
if(world.has<ai::EntityAI>(entity)) {
|
if(world.has<ai::EntityAI>(entity)) {
|
||||||
auto& enemy_ai = world.get<ai::EntityAI>(entity);
|
auto& enemy_ai = world.get<ai::EntityAI>(entity);
|
||||||
auto& enemy_combat = world.get<Combat>(entity);
|
auto& enemy_combat = world.get<Combat>(entity);
|
||||||
battle.add_enemy({entity, enemy_ai, enemy_combat});
|
battle.add_enemy({entity, &enemy_ai, &enemy_combat});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,10 +256,10 @@ void System::combat(int attack_id) {
|
||||||
battle.dump();
|
battle.dump();
|
||||||
|
|
||||||
while(auto act = battle.next()) {
|
while(auto act = battle.next()) {
|
||||||
auto [enemy, enemy_action] = *act;
|
auto [enemy, ai_action, enemy_action] = *act;
|
||||||
|
|
||||||
Events::Combat result {
|
Events::Combat result {
|
||||||
player_combat.attack(enemy.combat), 0
|
player_combat.attack(*enemy.combat), 0
|
||||||
};
|
};
|
||||||
|
|
||||||
if(result.player_did > 0) {
|
if(result.player_did > 0) {
|
||||||
|
|
@ -267,7 +267,7 @@ void System::combat(int attack_id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(enemy_action == combat::BattleAction::ATTACK) {
|
if(enemy_action == combat::BattleAction::ATTACK) {
|
||||||
result.enemy_did = enemy.combat.attack(player_combat);
|
result.enemy_did = enemy.combat->attack(player_combat);
|
||||||
animation::animate_entity(world, enemy.entity);
|
animation::animate_entity(world, enemy.entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <set>
|
||||||
#include "rituals.hpp"
|
#include "rituals.hpp"
|
||||||
#include "battle.hpp"
|
#include "battle.hpp"
|
||||||
#include "simplefsm.hpp"
|
#include "simplefsm.hpp"
|
||||||
|
|
@ -17,41 +18,60 @@ TEST_CASE("battle operations fantasy", "[combat-battle]") {
|
||||||
auto host_goal = ai::load_state("Host::final_state");
|
auto host_goal = ai::load_state("Host::final_state");
|
||||||
BattleEngine battle;
|
BattleEngine battle;
|
||||||
|
|
||||||
DinkyECS::Entity player = 0;
|
|
||||||
ai::EntityAI player_ai("Host::actions", host_start, host_goal);
|
DinkyECS::Entity host = 0;
|
||||||
components::Combat player_combat{100, 100, 20};
|
ai::EntityAI host_ai("Host::actions", host_start, host_goal);
|
||||||
battle.add_enemy({player, player_ai, player_combat});
|
components::Combat host_combat{100, 100, 20};
|
||||||
|
battle.add_enemy({host, &host_ai, &host_combat});
|
||||||
|
|
||||||
DinkyECS::Entity axe_ranger = 1;
|
DinkyECS::Entity axe_ranger = 1;
|
||||||
ai::EntityAI axe_ai("Enemy::actions", ai_start, ai_goal);
|
ai::EntityAI axe_ai("Enemy::actions", ai_start, ai_goal);
|
||||||
components::Combat axe_combat{100, 100, 20};
|
components::Combat axe_combat{100, 100, 20};
|
||||||
battle.add_enemy({axe_ranger, axe_ai, axe_combat});
|
battle.add_enemy({axe_ranger, &axe_ai, &axe_combat});
|
||||||
|
|
||||||
DinkyECS::Entity rat = 2;
|
DinkyECS::Entity rat = 2;
|
||||||
ai::EntityAI rat_ai("Enemy::actions", ai_start, ai_goal);
|
ai::EntityAI rat_ai("Enemy::actions", ai_start, ai_goal);
|
||||||
components::Combat rat_combat{10, 10, 2};
|
components::Combat rat_combat{10, 10, 2};
|
||||||
battle.add_enemy({rat, rat_ai, rat_combat});
|
battle.add_enemy({rat, &rat_ai, &rat_combat});
|
||||||
|
|
||||||
battle.set_all("enemy_found", true);
|
battle.set_all("enemy_found", true);
|
||||||
battle.set_all("in_combat", true);
|
battle.set_all("in_combat", true);
|
||||||
battle.set_all("tough_personality", true);
|
battle.set_all("tough_personality", true);
|
||||||
battle.set_all("health_good", true);
|
battle.set_all("health_good", true);
|
||||||
|
|
||||||
battle.set(rat, "tough_personality", false);
|
battle.set(rat, "tough_personality", false);
|
||||||
|
|
||||||
battle.queue(player, BattleAction::ATTACK);
|
battle.set(host, "have_healing", false);
|
||||||
battle.queue(player, BattleAction::BLOCK);
|
battle.set(host, "health_good", false);
|
||||||
|
battle.set(host, "tough_personality", false);
|
||||||
battle.queue(player, BattleAction::ESCAPE);
|
|
||||||
|
|
||||||
battle.plan();
|
battle.plan();
|
||||||
|
|
||||||
while(auto act = battle.next()) {
|
std::set<std::string> requests{
|
||||||
auto& [enemy, action] = *act;
|
"use_healing",
|
||||||
|
"kill_enemy"
|
||||||
|
};
|
||||||
|
|
||||||
fmt::println("entity: {} wants to {} action={} and has {} HP and {} damage",
|
while(auto act = battle.next()) {
|
||||||
enemy.entity, enemy.ai.wants_to(),
|
auto& [enemy, wants_to, action] = *act;
|
||||||
int(action), enemy.combat.hp,
|
|
||||||
enemy.combat.damage);
|
fmt::println(">>>>> entity: {} wants to {} cost={} and has {} HP and {} damage",
|
||||||
|
enemy.entity, wants_to.name,
|
||||||
|
wants_to.cost, enemy.combat->hp,
|
||||||
|
enemy.combat->damage);
|
||||||
|
|
||||||
|
if(enemy.entity == host) {
|
||||||
|
// negotiate between the player requested actions and the AI action
|
||||||
|
if(requests.contains(wants_to.name)) {
|
||||||
|
fmt::println("HOST and PLAYER requests match {}, doing it.",
|
||||||
|
wants_to.name);
|
||||||
|
requests.erase(wants_to.name);
|
||||||
|
} else {
|
||||||
|
fmt::println("REBELIOUS ACT: {}", wants_to.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt::println("<<<<<<<<<<<<<<<<");
|
||||||
}
|
}
|
||||||
|
|
||||||
REQUIRE(!battle.next());
|
REQUIRE(!battle.next());
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ int main(int, char*[]) {
|
||||||
|
|
||||||
sf::RenderWindow window(sf::VideoMode({SCREEN_WIDTH, SCREEN_HEIGHT}), "Bossfight Testing Arena");
|
sf::RenderWindow window(sf::VideoMode({SCREEN_WIDTH, SCREEN_HEIGHT}), "Bossfight Testing Arena");
|
||||||
window.setVerticalSyncEnabled(VSYNC);
|
window.setVerticalSyncEnabled(VSYNC);
|
||||||
|
|
||||||
if(FRAME_LIMIT) window.setFramerateLimit(FRAME_LIMIT);
|
if(FRAME_LIMIT) window.setFramerateLimit(FRAME_LIMIT);
|
||||||
window.setPosition({0,0});
|
window.setPosition({0,0});
|
||||||
|
|
||||||
|
|
@ -63,7 +64,6 @@ int main(int, char*[]) {
|
||||||
while(world->has_event<Events::GUI>()) {
|
while(world->has_event<Events::GUI>()) {
|
||||||
auto [evt, entity, data] = world->recv<Events::GUI>();
|
auto [evt, entity, data] = world->recv<Events::GUI>();
|
||||||
|
|
||||||
// FIX YOUR DAMN EVENTS
|
|
||||||
switch(evt) {
|
switch(evt) {
|
||||||
case Events::GUI::ATTACK:
|
case Events::GUI::ATTACK:
|
||||||
main->event(gui::Event::ATTACK, data);
|
main->event(gui::Event::ATTACK, data);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue