First cut of pulling out the relevant parts of my original game to make a little framework.

This commit is contained in:
Zed A. Shaw 2026-03-22 10:37:45 -04:00
commit 6a0c9e8d46
177 changed files with 18197 additions and 0 deletions

127
src/combat/battle.cpp Normal file
View file

@ -0,0 +1,127 @@
#include "combat/battle.hpp"
namespace combat {
void BattleEngine::add_enemy(Combatant enemy) {
$combatants.try_emplace(enemy.entity, enemy);
if(enemy.is_host) {
dbc::check($host_combat == nullptr, "added the host twice!");
$host_combat = enemy.combat;
}
}
bool BattleEngine::player_request(const std::string& request) {
auto action = ai::load_action(request);
bool can_go = player_pending_ap() >= action.cost;
if(can_go) {
$player_requests.try_emplace(request, action);
}
return can_go;
}
void BattleEngine::clear_requests() {
$player_requests.clear();
}
void BattleEngine::ap_refresh() {
for(auto& [entity, enemy] : $combatants) {
if(enemy.combat->ap < enemy.combat->max_ap) {
int new_ap = std::min(enemy.combat->max_ap, enemy.combat->ap_delta + enemy.combat->ap);
enemy.combat->ap = new_ap;
}
}
}
int BattleEngine::player_pending_ap() {
dbc::check($host_combat != nullptr, "didn't set host before checking AP");
int pending_ap = $host_combat->ap;
for(auto& [name, action] : $player_requests) {
pending_ap -= action.cost;
}
return pending_ap;
}
bool BattleEngine::plan() {
using enum BattleHostState;
int active = 0;
bool had_host = false;
for(auto& [entity, enemy] : $combatants) {
//NOTE: this is just for asserting I'm using things right
if(enemy.is_host) had_host = true;
enemy.ai->update();
active += enemy.ai->active();
if(enemy.ai->active()) {
for(auto& action : enemy.ai->plan.script) {
BattleHostState host_state = not_host;
if(action.cost > enemy.combat->ap) {
host_state = out_of_ap;
} else if(enemy.is_host) {
host_state = $player_requests.contains(action.name) ? agree : disagree;
}
if(host_state != out_of_ap) {
enemy.combat->ap -= action.cost;
}
$pending_actions.emplace_back(enemy, action.name, action.cost, host_state);
}
dbc::check(enemy.combat->ap >= 0, "enemy's AP went below 0");
dbc::check(enemy.combat->ap <= enemy.combat->max_ap, "enemy's AP went above max");
}
}
dbc::check(had_host, "FAIL, you forgot to set enemy.is_host=true for one entity");
if($pending_actions.size() > 0) {
std::sort($pending_actions.begin(), $pending_actions.end(),
[](const auto& a, const auto& b) -> bool
{
return a.cost > b.cost;
});
}
return active > 0;
}
std::optional<BattleResult> BattleEngine::next() {
if($pending_actions.size() == 0) return std::nullopt;
auto ba = $pending_actions.back();
$pending_actions.pop_back();
return std::make_optional(ba);
}
void BattleEngine::dump() {
for(auto& [entity, enemy] : $combatants) {
fmt::println("\n\n###### ENTITY #{}", entity);
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);
}
void BattleEngine::set_all(const std::string& state, bool setting) {
for(auto& [ent, action] : $combatants) {
action.ai->set_state(state, setting);
}
}
Combatant& BattleEngine::get_enemy(DinkyECS::Entity entity) {
dbc::check($combatants.contains(entity), "invalid combatant given to BattleEngine");
return $combatants.at(entity);
}
}

50
src/combat/battle.hpp Normal file
View file

@ -0,0 +1,50 @@
#pragma once
#include "game/config.hpp"
#include "algos/dinkyecs.hpp"
#include <optional>
#include "game/components.hpp"
#include <unordered_map>
#include "ai/ai.hpp"
namespace combat {
enum class BattleHostState {
not_host = 0,
agree = 1,
disagree = 2,
out_of_ap = 3
};
struct Combatant {
DinkyECS::Entity entity = DinkyECS::NONE;
ai::EntityAI* ai = nullptr;
components::Combat* combat = nullptr;
bool is_host=false;
};
struct BattleResult {
Combatant enemy;
std::string wants_to;
int cost;
BattleHostState host_state;
};
struct BattleEngine {
std::unordered_map<DinkyECS::Entity, Combatant> $combatants;
std::vector<BattleResult> $pending_actions;
std::unordered_map<std::string, ai::Action> $player_requests;
components::Combat* $host_combat = nullptr;
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);
bool player_request(const std::string& request);
int player_pending_ap();
void clear_requests();
void ap_refresh();
};
}

16
src/combat/combat.cpp Normal file
View file

@ -0,0 +1,16 @@
#include "game/components.hpp"
#include "algos/rand.hpp"
namespace components {
int Combat::attack(Combat &target) {
int attack = Random::uniform<int>(0,1);
int my_dmg = 0;
if(attack) {
my_dmg = Random::uniform<int>(1, damage);
target.hp -= my_dmg;
}
return my_dmg;
}
}