Synced the combat from under the dome.
This commit is contained in:
parent
110612c33a
commit
a22564bb58
8 changed files with 106 additions and 27 deletions
|
|
@ -10,6 +10,7 @@
|
||||||
"[floor3|player1|player2|player3|player4|_]",
|
"[floor3|player1|player2|player3|player4|_]",
|
||||||
"[floor4|player5|player6|player7|player8|_]"
|
"[floor4|player5|player6|player7|player8|_]"
|
||||||
],
|
],
|
||||||
|
"buttons": [],
|
||||||
"background": "test_background",
|
"background": "test_background",
|
||||||
"actors": [
|
"actors": [
|
||||||
{
|
{
|
||||||
|
|
@ -62,7 +63,7 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{"_type": "Combat", "hp": 200, "max_hp": 200, "ap": 0, "ap_delta": 9, "max_ap": 20, "damage": 20, "dead": false},
|
{"_type": "Combat", "hp": 200, "max_hp": 200, "ap": 0, "ap_delta": 9, "max_ap": 20, "damage": 20, "dead": false, "attack_rating": 0.80, "toughness_rating": 0.30},
|
||||||
{"_type": "Sound", "attack": "Marmot_Scream_1", "death": "Creature_Death_1"},
|
{"_type": "Sound", "attack": "Marmot_Scream_1", "death": "Creature_Death_1"},
|
||||||
{"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"}
|
{"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"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},
|
{"_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": "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"},
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
"foreground": "enemies/fg:gold_savior",
|
"foreground": "enemies/fg:gold_savior",
|
||||||
"background": "color:transparent"
|
"background": "color:transparent"
|
||||||
},
|
},
|
||||||
{"_type": "Combat", "hp": 20, "max_hp": 20, "ap": 0, "max_ap": 12, "ap_delta": 6, "damage": 1, "dead": false},
|
{"_type": "Combat", "hp": 20, "max_hp": 20, "ap": 0, "max_ap": 12, "ap_delta": 6, "damage": 1, "dead": false, "attack_rating": 0.60, "toughness_rating": 0.10},
|
||||||
{"_type": "Collision", "has": true},
|
{"_type": "Collision", "has": true},
|
||||||
{"_type": "Motion", "dx": 0, "dy": 0, "random": false},
|
{"_type": "Motion", "dx": 0, "dy": 0, "random": false},
|
||||||
{"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
|
{"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
"foreground": "enemies/fg:knight",
|
"foreground": "enemies/fg:knight",
|
||||||
"background": "color:transparent"
|
"background": "color:transparent"
|
||||||
},
|
},
|
||||||
{"_type": "Combat", "hp": 20, "max_hp": 20, "ap": 0, "max_ap": 12, "ap_delta": 6,"damage": 1, "dead": false},
|
{"_type": "Combat", "hp": 20, "max_hp": 20, "ap": 0, "max_ap": 12, "ap_delta": 6,"damage": 1, "dead": false, "attack_rating": 0.60, "toughness_rating": 0.20},
|
||||||
{"_type": "Collision", "has": true},
|
{"_type": "Collision", "has": true},
|
||||||
{"_type": "Motion", "dx": 0, "dy": 0, "random": false},
|
{"_type": "Motion", "dx": 0, "dy": 0, "random": false},
|
||||||
{"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
|
{"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
|
||||||
|
|
@ -50,7 +50,7 @@
|
||||||
"foreground": "enemies/fg:axe_ranger",
|
"foreground": "enemies/fg:axe_ranger",
|
||||||
"background": "color:transparent"
|
"background": "color:transparent"
|
||||||
},
|
},
|
||||||
{"_type": "Combat", "hp": 40, "max_hp": 40, "ap": 0, "max_ap": 12, "ap_delta": 6,"damage": 10, "dead": false},
|
{"_type": "Combat", "hp": 40, "max_hp": 40, "ap": 0, "max_ap": 12, "ap_delta": 6,"damage": 10, "dead": false, "attack_rating": 0.60, "toughness_rating": 0.5},
|
||||||
{"_type": "Collision", "has": true},
|
{"_type": "Collision", "has": true},
|
||||||
{"_type": "Motion", "dx": 0, "dy": 0, "random": true},
|
{"_type": "Motion", "dx": 0, "dy": 0, "random": true},
|
||||||
{"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
|
{"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
|
||||||
|
|
@ -65,7 +65,7 @@
|
||||||
"foreground": "enemies/fg:rat_giant",
|
"foreground": "enemies/fg:rat_giant",
|
||||||
"background": "color:transparent"
|
"background": "color:transparent"
|
||||||
},
|
},
|
||||||
{"_type": "Combat", "hp": 50, "max_hp": 50, "ap": 0, "max_ap": 12, "ap_delta": 6,"damage": 2, "dead": false},
|
{"_type": "Combat", "hp": 50, "max_hp": 50, "ap": 0, "max_ap": 12, "ap_delta": 6,"damage": 2, "dead": false, "attack_rating": 0.60, "toughness_rating": 0.0},
|
||||||
{"_type": "Collision", "has": true},
|
{"_type": "Collision", "has": true},
|
||||||
{"_type": "Motion", "dx": 0, "dy": 0, "random": false},
|
{"_type": "Motion", "dx": 0, "dy": 0, "random": false},
|
||||||
{"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
|
{"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
|
||||||
|
|
@ -80,7 +80,7 @@
|
||||||
"foreground": "enemies/fg:spider_giant",
|
"foreground": "enemies/fg:spider_giant",
|
||||||
"background": "color:transparent"
|
"background": "color:transparent"
|
||||||
},
|
},
|
||||||
{"_type": "Combat", "hp": 20, "max_hp": 20, "ap": 0, "max_ap": 12, "ap_delta": 6,"damage": 20, "dead": false},
|
{"_type": "Combat", "hp": 20, "max_hp": 20, "ap": 0, "max_ap": 12, "ap_delta": 6,"damage": 20, "dead": false, "attack_rating": 0.60, "toughness_rating": 0.10},
|
||||||
{"_type": "Collision", "has": true},
|
{"_type": "Collision", "has": true},
|
||||||
{"_type": "Motion", "dx": 0, "dy": 0, "random": false},
|
{"_type": "Motion", "dx": 0, "dy": 0, "random": false},
|
||||||
{"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
|
{"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
|
||||||
|
|
|
||||||
2
assets/scenes.json
Normal file
2
assets/scenes.json
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -3,14 +3,61 @@
|
||||||
|
|
||||||
namespace components {
|
namespace components {
|
||||||
int Combat::attack(Combat &target) {
|
int Combat::attack(Combat &target) {
|
||||||
int attack = Random::uniform<int>(0,1);
|
float hit_prob = Random::uniform_real(0.0f, 1.0f);
|
||||||
|
|
||||||
int my_dmg = 0;
|
int my_dmg = 0;
|
||||||
|
|
||||||
if(attack) {
|
if(hit_prob < attack_rating) {
|
||||||
my_dmg = Random::uniform<int>(1, damage);
|
my_dmg = std::ceil(Random::uniform_real(1.0f, float(damage)) * (1.0f - toughness_rating));
|
||||||
target.hp -= my_dmg;
|
target.take_damage(my_dmg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
INVARIANT();
|
||||||
return my_dmg;
|
return my_dmg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Combat::take_damage(int my_dmg) {
|
||||||
|
// catch this bug
|
||||||
|
dbc::check(hp >= 0, "HP went negative");
|
||||||
|
|
||||||
|
// don't hit dead parts
|
||||||
|
if(hp == 0) return;
|
||||||
|
|
||||||
|
// don't go below 0
|
||||||
|
hp = std::max(0, hp - my_dmg);
|
||||||
|
|
||||||
|
has_died = hp <= 0;
|
||||||
|
INVARIANT();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Combat::less_than(int level) {
|
||||||
|
INVARIANT();
|
||||||
|
// originally this checked main body parts like
|
||||||
|
// head, stomach, and chest
|
||||||
|
return hp <= level;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Combat::is_dead() {
|
||||||
|
INVARIANT();
|
||||||
|
return has_died;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Combat::almost_dead() {
|
||||||
|
return less_than(max_hp / 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Combat::can_heal() {
|
||||||
|
INVARIANT();
|
||||||
|
return hp < max_hp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Combat::apply_healing(Curative& cure) {
|
||||||
|
INVARIANT();
|
||||||
|
hp += std::min(hp + cure.hp, max_hp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Combat::INVARIANT() {
|
||||||
|
dbc::check(!(hp <= 0 && has_died == false), "entity hp <= 0 && has_died==false, they should be dead");
|
||||||
|
dbc::check(!(hp > 0 && has_died == true), "entity has hp > 0 but is marked dead, should be alive");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,7 @@ namespace components {
|
||||||
std::vector<std::string> layout;
|
std::vector<std::string> layout;
|
||||||
json actors;
|
json actors;
|
||||||
json fixtures;
|
json fixtures;
|
||||||
|
json buttons;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Storyboard {
|
struct Storyboard {
|
||||||
|
|
@ -111,19 +112,28 @@ namespace components {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Combat {
|
struct Combat {
|
||||||
int hp;
|
int hp=1;
|
||||||
int max_hp;
|
int max_hp=1;
|
||||||
int ap_delta;
|
int ap_delta=1;
|
||||||
int max_ap;
|
int max_ap=1;
|
||||||
int damage;
|
int damage=1;
|
||||||
|
float attack_rating=0.1;
|
||||||
|
float toughness_rating=0.1;
|
||||||
|
|
||||||
// everyone starts at 0 but ap_delta is added each round
|
// everyone starts at 0 but ap_delta is added each round
|
||||||
int ap = 0;
|
int ap = 0;
|
||||||
|
|
||||||
/* NOTE: This is used to _mark_ entities as dead, to detect ones that have just died. Don't make attack automatically set it.*/
|
/* NOTE: This is used to _mark_ entities as dead, to detect ones that have just died. Don't make attack automatically set it.*/
|
||||||
bool dead = false;
|
bool has_died = false;
|
||||||
|
|
||||||
int attack(Combat &target);
|
int attack(Combat &target);
|
||||||
|
bool less_than(int level);
|
||||||
|
void take_damage(int my_dmg);
|
||||||
|
bool is_dead();
|
||||||
|
bool almost_dead();
|
||||||
|
bool can_heal();
|
||||||
|
void apply_healing(Curative& cure);
|
||||||
|
void INVARIANT();
|
||||||
};
|
};
|
||||||
|
|
||||||
struct LightSource {
|
struct LightSource {
|
||||||
|
|
@ -152,7 +162,7 @@ namespace components {
|
||||||
using ComponentMap = std::unordered_map<std::string, ReflFuncSignature>;
|
using ComponentMap = std::unordered_map<std::string, ReflFuncSignature>;
|
||||||
|
|
||||||
ENROLL_COMPONENT(Tile, display, foreground, background);
|
ENROLL_COMPONENT(Tile, display, foreground, background);
|
||||||
ENROLL_COMPONENT(AnimatedScene, background, layout, actors, fixtures);
|
ENROLL_COMPONENT(AnimatedScene, background, layout, actors, fixtures, buttons);
|
||||||
ENROLL_COMPONENT(Sprite, name, scale);
|
ENROLL_COMPONENT(Sprite, name, scale);
|
||||||
ENROLL_COMPONENT(Curative, hp);
|
ENROLL_COMPONENT(Curative, hp);
|
||||||
ENROLL_COMPONENT(LightSource, strength, radius);
|
ENROLL_COMPONENT(LightSource, strength, radius);
|
||||||
|
|
@ -160,7 +170,7 @@ namespace components {
|
||||||
ENROLL_COMPONENT(EnemyConfig, ai_script, ai_start_name, ai_goal_name);
|
ENROLL_COMPONENT(EnemyConfig, ai_script, ai_start_name, ai_goal_name);
|
||||||
ENROLL_COMPONENT(Personality, hearing_distance, tough);
|
ENROLL_COMPONENT(Personality, hearing_distance, tough);
|
||||||
ENROLL_COMPONENT(Motion, dx, dy, random);
|
ENROLL_COMPONENT(Motion, dx, dy, random);
|
||||||
ENROLL_COMPONENT(Combat, hp, max_hp, ap_delta, max_ap, damage, dead);
|
ENROLL_COMPONENT(Combat, hp, max_hp, ap_delta, max_ap, damage, attack_rating, toughness_rating);
|
||||||
ENROLL_COMPONENT(Device, config, events);
|
ENROLL_COMPONENT(Device, config, events);
|
||||||
ENROLL_COMPONENT(Storyboard, image, audio, layout, beats);
|
ENROLL_COMPONENT(Storyboard, image, audio, layout, beats);
|
||||||
ENROLL_COMPONENT(Sound, attack, death);
|
ENROLL_COMPONENT(Sound, attack, death);
|
||||||
|
|
@ -184,7 +194,7 @@ namespace components {
|
||||||
|
|
||||||
template <typename COMPONENT> void enroll(ComponentMap &m) {
|
template <typename COMPONENT> void enroll(ComponentMap &m) {
|
||||||
m[NameOf<COMPONENT>::name] = [](DinkyECS::World& world, DinkyECS::Entity ent, nlohmann::json &j) {
|
m[NameOf<COMPONENT>::name] = [](DinkyECS::World& world, DinkyECS::Entity ent, nlohmann::json &j) {
|
||||||
COMPONENT c;
|
COMPONENT c{};
|
||||||
from_json(j, c);
|
from_json(j, c);
|
||||||
world.set<COMPONENT>(ent, c);
|
world.set<COMPONENT>(ent, c);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -192,8 +192,7 @@ void System::death() {
|
||||||
|
|
||||||
world.query<Combat>([&](auto ent, auto &combat) {
|
world.query<Combat>([&](auto ent, auto &combat) {
|
||||||
// bring out yer dead
|
// bring out yer dead
|
||||||
if(combat.hp <= 0 && !combat.dead) {
|
if(combat.is_dead()) {
|
||||||
combat.dead = true;
|
|
||||||
if(ent != player.entity) {
|
if(ent != player.entity) {
|
||||||
// we won't change out the player's components later
|
// we won't change out the player's components later
|
||||||
dead_things.push_back(ent);
|
dead_things.push_back(ent);
|
||||||
|
|
@ -301,7 +300,7 @@ void System::collision() {
|
||||||
for(auto entity : nearby) {
|
for(auto entity : nearby) {
|
||||||
if(world.has<Combat>(entity)) {
|
if(world.has<Combat>(entity)) {
|
||||||
auto combat = world.get<Combat>(entity);
|
auto combat = world.get<Combat>(entity);
|
||||||
if(!combat.dead) {
|
if(!combat.is_dead()) {
|
||||||
combat_count++;
|
combat_count++;
|
||||||
world.send<game::Event>(game::Event::COMBAT_START, entity, entity);
|
world.send<game::Event>(game::Event::COMBAT_START, entity, entity);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ using namespace combat;
|
||||||
using namespace boss;
|
using namespace boss;
|
||||||
using namespace components;
|
using namespace components;
|
||||||
|
|
||||||
TEST_CASE("battle operations fantasy", "[combat-battle]") {
|
TEST_CASE("battle operations fantasy", "[fail]") {
|
||||||
ai::reset();
|
ai::reset();
|
||||||
ai::init("ai");
|
ai::init("ai");
|
||||||
|
|
||||||
|
|
@ -53,7 +53,10 @@ TEST_CASE("battle operations fantasy", "[combat-battle]") {
|
||||||
battle.set(host, "have_healing", false);
|
battle.set(host, "have_healing", false);
|
||||||
battle.set(host, "tough_personality", false);
|
battle.set(host, "tough_personality", false);
|
||||||
|
|
||||||
while(host_combat.hp > 0) {
|
int host_combat_loop = 0;
|
||||||
|
for(host_combat_loop = 0; host_combat_loop < 1000; host_combat_loop++) {
|
||||||
|
fmt::println("host HP is {}", host_combat.hp);
|
||||||
|
if(host_combat.is_dead()) break;
|
||||||
battle.set(host, "health_good", host_combat.hp > 20);
|
battle.set(host, "health_good", host_combat.hp > 20);
|
||||||
|
|
||||||
battle.player_request("use_healing");
|
battle.player_request("use_healing");
|
||||||
|
|
@ -61,8 +64,12 @@ TEST_CASE("battle operations fantasy", "[combat-battle]") {
|
||||||
|
|
||||||
battle.ap_refresh();
|
battle.ap_refresh();
|
||||||
battle.plan();
|
battle.plan();
|
||||||
|
int battle_count = 0;
|
||||||
|
|
||||||
|
for(int battle_count = 0; battle_count < 1000; battle_count++) {
|
||||||
|
auto act = battle.next();
|
||||||
|
if(!act) break;
|
||||||
|
|
||||||
while(auto act = battle.next()) {
|
|
||||||
auto& [enemy, wants_to, cost, enemy_state] = *act;
|
auto& [enemy, wants_to, cost, enemy_state] = *act;
|
||||||
|
|
||||||
// fmt::println(">>>>> entity: {} wants to {} cost={}; has {} HP; {} ap",
|
// fmt::println(">>>>> entity: {} wants to {} cost={}; has {} HP; {} ap",
|
||||||
|
|
@ -81,6 +88,7 @@ TEST_CASE("battle operations fantasy", "[combat-battle]") {
|
||||||
break;
|
break;
|
||||||
case BattleHostState::not_host:
|
case BattleHostState::not_host:
|
||||||
if(wants_to == "kill_enemy") {
|
if(wants_to == "kill_enemy") {
|
||||||
|
fmt::println("ATTACK!");
|
||||||
enemy.combat->attack(host_combat);
|
enemy.combat->attack(host_combat);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -91,9 +99,11 @@ TEST_CASE("battle operations fantasy", "[combat-battle]") {
|
||||||
}
|
}
|
||||||
|
|
||||||
REQUIRE(!battle.next());
|
REQUIRE(!battle.next());
|
||||||
|
dbc::check(battle_count < 1000, "infinite battle loop");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
dbc::check(host_combat_loop < 1000, "infinite host combat loop, host won't die!");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("boss/systems.cpp works", "[combat-battle]") {
|
TEST_CASE("boss/systems.cpp works", "[combat-battle]") {
|
||||||
components::init();
|
components::init();
|
||||||
|
|
|
||||||
10
wraps/magic_enum.wrap
Normal file
10
wraps/magic_enum.wrap
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[wrap-file]
|
||||||
|
directory = magic_enum-0.9.7
|
||||||
|
source_url = https://github.com/Neargye/magic_enum/archive/refs/tags/v0.9.7.tar.gz
|
||||||
|
source_filename = magic_enum-v0.9.7.tar.gz
|
||||||
|
source_hash = b403d3dad4ef542fdc3024fa37d3a6cedb4ad33c72e31b6d9bab89dcaf69edf7
|
||||||
|
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/magic_enum_0.9.7-1/magic_enum-v0.9.7.tar.gz
|
||||||
|
wrapdb_version = 0.9.7-1
|
||||||
|
|
||||||
|
[provide]
|
||||||
|
magic_enum = magic_enum_dep
|
||||||
Loading…
Add table
Add a link
Reference in a new issue