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