AI is now mostly working. Enemies will attack the player, and some of them are marked as not tough so they'll run away when they get low health.
This commit is contained in:
parent
c4e01775bc
commit
75db188dc6
6 changed files with 56 additions and 27 deletions
|
@ -8,7 +8,8 @@
|
||||||
"in_combat": 5,
|
"in_combat": 5,
|
||||||
"have_item": 6,
|
"have_item": 6,
|
||||||
"have_healing": 7,
|
"have_healing": 7,
|
||||||
"detect_enemy": 8
|
"detect_enemy": 8,
|
||||||
|
"tough_personality": 9
|
||||||
},
|
},
|
||||||
"actions": [
|
"actions": [
|
||||||
{
|
{
|
||||||
|
@ -28,7 +29,7 @@
|
||||||
"name": "kill_enemy",
|
"name": "kill_enemy",
|
||||||
"cost": 5,
|
"cost": 5,
|
||||||
"needs": {
|
"needs": {
|
||||||
"health_good": true,
|
"tough_personality": true,
|
||||||
"no_more_enemies": false,
|
"no_more_enemies": false,
|
||||||
"enemy_found": true,
|
"enemy_found": true,
|
||||||
"enemy_dead": false
|
"enemy_dead": false
|
||||||
|
@ -66,6 +67,7 @@
|
||||||
"name": "run_away",
|
"name": "run_away",
|
||||||
"cost": 0,
|
"cost": 0,
|
||||||
"needs": {
|
"needs": {
|
||||||
|
"tough_personality": false,
|
||||||
"in_combat": true,
|
"in_combat": true,
|
||||||
"have_healing": false,
|
"have_healing": false,
|
||||||
"health_good": false
|
"health_good": false
|
||||||
|
@ -97,6 +99,7 @@
|
||||||
},
|
},
|
||||||
"Enemy::initial_state": {
|
"Enemy::initial_state": {
|
||||||
"detect_enemy": false,
|
"detect_enemy": false,
|
||||||
|
"tough_personality": true,
|
||||||
"enemy_found": false,
|
"enemy_found": false,
|
||||||
"enemy_dead": false,
|
"enemy_dead": false,
|
||||||
"health_good": true,
|
"health_good": true,
|
||||||
|
|
|
@ -19,7 +19,8 @@
|
||||||
},
|
},
|
||||||
{"_type": "Combat", "hp": 20, "max_hp": 20, "damage": 1, "dead": false},
|
{"_type": "Combat", "hp": 20, "max_hp": 20, "damage": 1, "dead": false},
|
||||||
{"_type": "Motion", "dx": 0, "dy": 0, "random": false},
|
{"_type": "Motion", "dx": 0, "dy": 0, "random": false},
|
||||||
{"_type": "EnemyConfig", "hearing_distance": 5, "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
|
{"_type": "EnemyAI", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
|
||||||
|
{"_type": "Personality", "hearing_distance": 5, "tough": true},
|
||||||
{"_type": "Animation", "easing": 1, "ease_rate": 0.2, "scale": 0.1, "simple": true, "frames": 10, "speed": 0.3, "stationary": false},
|
{"_type": "Animation", "easing": 1, "ease_rate": 0.2, "scale": 0.1, "simple": true, "frames": 10, "speed": 0.3, "stationary": false},
|
||||||
{"_type": "Sprite", "name": "armored_knight", "width": 256, "height": 256, "width": 256, "height": 256, "scale": 1.0},
|
{"_type": "Sprite", "name": "armored_knight", "width": 256, "height": 256, "width": 256, "height": 256, "scale": 1.0},
|
||||||
{"_type": "Sound", "attack": "Sword_Hit_2", "death": "Humanoid_Death_1"}
|
{"_type": "Sound", "attack": "Sword_Hit_2", "death": "Humanoid_Death_1"}
|
||||||
|
@ -33,7 +34,8 @@
|
||||||
},
|
},
|
||||||
{"_type": "Combat", "hp": 40, "max_hp": 40, "damage": 10, "dead": false},
|
{"_type": "Combat", "hp": 40, "max_hp": 40, "damage": 10, "dead": false},
|
||||||
{"_type": "Motion", "dx": 0, "dy": 0, "random": true},
|
{"_type": "Motion", "dx": 0, "dy": 0, "random": true},
|
||||||
{"_type": "EnemyConfig", "hearing_distance": 5, "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
|
{"_type": "EnemyAI", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
|
||||||
|
{"_type": "Personality", "hearing_distance": 5, "tough": true},
|
||||||
{"_type": "Sprite", "name": "axe_ranger", "width": 256, "height": 256, "scale": 1.0},
|
{"_type": "Sprite", "name": "axe_ranger", "width": 256, "height": 256, "scale": 1.0},
|
||||||
{"_type": "Animation", "easing": 3, "ease_rate": 0.5, "scale": 0.1, "simple": false, "frames": 2, "speed": 0.6, "stationary": false},
|
{"_type": "Animation", "easing": 3, "ease_rate": 0.5, "scale": 0.1, "simple": false, "frames": 2, "speed": 0.6, "stationary": false},
|
||||||
{"_type": "Sound", "attack": "Sword_Hit_2", "death": "Ranger_1"}
|
{"_type": "Sound", "attack": "Sword_Hit_2", "death": "Ranger_1"}
|
||||||
|
@ -47,7 +49,8 @@
|
||||||
},
|
},
|
||||||
{"_type": "Combat", "hp": 20, "max_hp": 20, "damage": 20, "dead": false},
|
{"_type": "Combat", "hp": 20, "max_hp": 20, "damage": 20, "dead": false},
|
||||||
{"_type": "Motion", "dx": 0, "dy": 0, "random": false},
|
{"_type": "Motion", "dx": 0, "dy": 0, "random": false},
|
||||||
{"_type": "EnemyConfig", "hearing_distance": 10, "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
|
{"_type": "EnemyAI", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
|
||||||
|
{"_type": "Personality", "hearing_distance": 5, "tough": false},
|
||||||
{"_type": "Animation", "easing": 3, "ease_rate": 0.5, "scale": 0.1, "simple": true, "frames": 10, "speed": 1.0, "stationary": false},
|
{"_type": "Animation", "easing": 3, "ease_rate": 0.5, "scale": 0.1, "simple": true, "frames": 10, "speed": 1.0, "stationary": false},
|
||||||
{"_type": "Sprite", "name": "rat_with_sword", "width": 256, "height": 256, "scale": 1.0},
|
{"_type": "Sprite", "name": "rat_with_sword", "width": 256, "height": 256, "scale": 1.0},
|
||||||
{"_type": "Sound", "attack": "Small_Rat", "death": "Creature_Death_1"}
|
{"_type": "Sound", "attack": "Small_Rat", "death": "Creature_Death_1"}
|
||||||
|
@ -61,7 +64,8 @@
|
||||||
},
|
},
|
||||||
{"_type": "Combat", "hp": 20, "max_hp": 20, "damage": 20, "dead": false},
|
{"_type": "Combat", "hp": 20, "max_hp": 20, "damage": 20, "dead": false},
|
||||||
{"_type": "Motion", "dx": 0, "dy": 0, "random": false},
|
{"_type": "Motion", "dx": 0, "dy": 0, "random": false},
|
||||||
{"_type": "EnemyConfig", "hearing_distance": 10, "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
|
{"_type": "EnemyAI", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
|
||||||
|
{"_type": "Personality", "hearing_distance": 5, "tough": true},
|
||||||
{"_type": "Animation", "easing": 2, "ease_rate": 0.5, "scale": 0.1, "simple": true, "frames": 10, "speed": 1.0, "stationary": false},
|
{"_type": "Animation", "easing": 2, "ease_rate": 0.5, "scale": 0.1, "simple": true, "frames": 10, "speed": 1.0, "stationary": false},
|
||||||
{"_type": "Sprite", "name": "hairy_spider", "width": 256, "height": 256, "scale": 1.0},
|
{"_type": "Sprite", "name": "hairy_spider", "width": 256, "height": 256, "scale": 1.0},
|
||||||
{"_type": "Sound", "attack": "Spider_1", "death": "Spider_2"}
|
{"_type": "Sound", "attack": "Spider_1", "death": "Spider_2"}
|
||||||
|
|
|
@ -18,7 +18,8 @@ namespace components {
|
||||||
components::enroll<Position>(component_map);
|
components::enroll<Position>(component_map);
|
||||||
components::enroll<Weapon>(component_map);
|
components::enroll<Weapon>(component_map);
|
||||||
components::enroll<Curative>(component_map);
|
components::enroll<Curative>(component_map);
|
||||||
components::enroll<EnemyConfig>(component_map);
|
components::enroll<EnemyAI>(component_map);
|
||||||
|
components::enroll<Personality>(component_map);
|
||||||
components::enroll<Tile>(component_map);
|
components::enroll<Tile>(component_map);
|
||||||
components::enroll<Motion>(component_map);
|
components::enroll<Motion>(component_map);
|
||||||
components::enroll<LightSource>(component_map);
|
components::enroll<LightSource>(component_map);
|
||||||
|
|
|
@ -44,8 +44,12 @@ namespace components {
|
||||||
Config bosses;
|
Config bosses;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EnemyConfig {
|
struct Personality {
|
||||||
int hearing_distance = 10;
|
int hearing_distance = 10;
|
||||||
|
bool tough = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EnemyAI {
|
||||||
std::string ai_script;
|
std::string ai_script;
|
||||||
std::string ai_start_name;
|
std::string ai_start_name;
|
||||||
std::string ai_goal_name;
|
std::string ai_goal_name;
|
||||||
|
@ -142,8 +146,8 @@ namespace components {
|
||||||
ENROLL_COMPONENT(Weapon, damage);
|
ENROLL_COMPONENT(Weapon, damage);
|
||||||
ENROLL_COMPONENT(Loot, amount);
|
ENROLL_COMPONENT(Loot, amount);
|
||||||
ENROLL_COMPONENT(Position, location.x, location.y);
|
ENROLL_COMPONENT(Position, location.x, location.y);
|
||||||
ENROLL_COMPONENT(EnemyConfig, hearing_distance,
|
ENROLL_COMPONENT(EnemyAI, ai_script, ai_start_name, ai_goal_name);
|
||||||
ai_script, ai_start_name, ai_goal_name);
|
ENROLL_COMPONENT(Personality, hearing_distance, tough);
|
||||||
ENROLL_COMPONENT(Motion, dx, dy, random);
|
ENROLL_COMPONENT(Motion, dx, dy, random);
|
||||||
ENROLL_COMPONENT(Combat, hp, max_hp, damage, dead);
|
ENROLL_COMPONENT(Combat, hp, max_hp, damage, dead);
|
||||||
ENROLL_COMPONENT(Device, config, events);
|
ENROLL_COMPONENT(Device, config, events);
|
||||||
|
|
30
systems.cpp
30
systems.cpp
|
@ -48,20 +48,24 @@ void System::enemy_ai_initialize(GameLevel &level) {
|
||||||
auto &world = *level.world;
|
auto &world = *level.world;
|
||||||
auto &map = *level.map;
|
auto &map = *level.map;
|
||||||
|
|
||||||
world.query<Position, EnemyConfig>([&](const auto ent, auto& pos, auto& config) {
|
world.query<Position, EnemyAI>([&](const auto ent, auto& pos, auto& config) {
|
||||||
if(world.has<ai::EntityAI>(ent)) {
|
if(world.has<ai::EntityAI>(ent)) {
|
||||||
auto&enemy = world.get<ai::EntityAI>(ent);
|
auto&enemy = world.get<ai::EntityAI>(ent);
|
||||||
enemy.set_state("detect_enemy", map.distance(pos.location) < config.hearing_distance);
|
auto&personality = world.get<Personality>(ent);
|
||||||
|
|
||||||
|
enemy.set_state("detect_enemy", map.distance(pos.location) < personality.hearing_distance);
|
||||||
enemy.update();
|
enemy.update();
|
||||||
} else {
|
} else {
|
||||||
auto ai_start = ai::load_state(config.ai_start_name);
|
auto ai_start = ai::load_state(config.ai_start_name);
|
||||||
auto ai_goal = ai::load_state(config.ai_goal_name);
|
auto ai_goal = ai::load_state(config.ai_goal_name);
|
||||||
|
|
||||||
ai::EntityAI enemy(config.ai_script, ai_start, ai_goal);
|
ai::EntityAI enemy(config.ai_script, ai_start, ai_goal);
|
||||||
enemy.set_state("detect_enemy", map.distance(pos.location) < config.hearing_distance);
|
auto&personality = world.get<Personality>(ent);
|
||||||
|
|
||||||
|
enemy.set_state("tough_personality", personality.tough);
|
||||||
|
enemy.set_state("detect_enemy", map.distance(pos.location) < personality.hearing_distance);
|
||||||
enemy.update();
|
enemy.update();
|
||||||
|
|
||||||
ai::dump_script("\n\n\n-----ENEMY SCRIPT", enemy.start, enemy.plan.script);
|
|
||||||
world.set<ai::EntityAI>(ent, enemy);
|
world.set<ai::EntityAI>(ent, enemy);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -77,18 +81,18 @@ void System::enemy_pathing(GameLevel &level) {
|
||||||
world.query<Position, Motion>([&](auto ent, auto &position, auto &motion) {
|
world.query<Position, Motion>([&](auto ent, auto &position, auto &motion) {
|
||||||
if(ent != player.entity) {
|
if(ent != player.entity) {
|
||||||
auto& enemy_ai = world.get<ai::EntityAI>(ent);
|
auto& enemy_ai = world.get<ai::EntityAI>(ent);
|
||||||
|
Point out = position.location; // copy
|
||||||
|
|
||||||
if(enemy_ai.wants_to("find_enemy")) {
|
if(enemy_ai.wants_to("find_enemy")) {
|
||||||
Point out = position.location; // copy
|
map.neighbors(out, motion.random, PATHING_TOWARD);
|
||||||
map.neighbors(out, motion.random);
|
|
||||||
motion = { int(out.x - position.location.x), int(out.y - position.location.y)};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt::println("------- ARE THEY SCARED? {}", ent);
|
|
||||||
enemy_ai.dump();
|
|
||||||
if(enemy_ai.wants_to("run_away")) {
|
if(enemy_ai.wants_to("run_away")) {
|
||||||
dbc::log("ENEMY IS SCARED");
|
fmt::println("ENEMY {} wants to run away", ent);
|
||||||
|
map.neighbors(out, motion.random, PATHING_AWAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
motion = { int(out.x - position.location.x), int(out.y - position.location.y)};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -173,7 +177,8 @@ void System::death(GameLevel &level, components::ComponentMap& components) {
|
||||||
// remove their enemy setting
|
// remove their enemy setting
|
||||||
world.remove<Motion>(ent);
|
world.remove<Motion>(ent);
|
||||||
world.remove<Combat>(ent);
|
world.remove<Combat>(ent);
|
||||||
world.remove<EnemyConfig>(ent);
|
world.remove<EnemyAI>(ent);
|
||||||
|
world.remove<Personality>(ent);
|
||||||
world.remove<ai::EntityAI>(ent);
|
world.remove<ai::EntityAI>(ent);
|
||||||
world.remove<Animation>(ent);
|
world.remove<Animation>(ent);
|
||||||
|
|
||||||
|
@ -214,12 +219,13 @@ void System::combat(GameLevel &level) {
|
||||||
player_combat.attack(enemy_combat), 0
|
player_combat.attack(enemy_combat), 0
|
||||||
};
|
};
|
||||||
|
|
||||||
if(!enemy_combat.dead && 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);
|
||||||
enemy_ai.set_state("in_combat", true);
|
enemy_ai.set_state("in_combat", true);
|
||||||
enemy_ai.update();
|
enemy_ai.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enemy_ai.dump();
|
||||||
if(enemy_ai.wants_to("kill_enemy")) {
|
if(enemy_ai.wants_to("kill_enemy")) {
|
||||||
result.enemy_did = enemy_combat.attack(player_combat);
|
result.enemy_did = enemy_combat.attack(player_combat);
|
||||||
|
|
||||||
|
|
21
tests/ai.cpp
21
tests/ai.cpp
|
@ -187,14 +187,25 @@ TEST_CASE("Confirm EntityAI behaves as expected", "[ai-enemy]") {
|
||||||
enemy.update();
|
enemy.update();
|
||||||
REQUIRE(enemy.wants_to("kill_enemy"));
|
REQUIRE(enemy.wants_to("kill_enemy"));
|
||||||
|
|
||||||
|
enemy.set_state("have_item", true);
|
||||||
|
enemy.set_state("have_healing", true);
|
||||||
|
enemy.set_state("in_combat", false);
|
||||||
|
enemy.set_state("health_good", false);
|
||||||
|
enemy.update();
|
||||||
|
REQUIRE(enemy.wants_to("use_healing"));
|
||||||
|
|
||||||
|
enemy.set_state("have_healing", false);
|
||||||
|
enemy.set_state("tough_personality", true);
|
||||||
|
enemy.set_state("in_combat", true);
|
||||||
|
enemy.set_state("health_good", true);
|
||||||
|
enemy.update();
|
||||||
|
REQUIRE(enemy.wants_to("kill_enemy"));
|
||||||
|
|
||||||
|
enemy.set_state("have_healing", false);
|
||||||
|
enemy.set_state("tough_personality", false);
|
||||||
enemy.set_state("in_combat", true);
|
enemy.set_state("in_combat", true);
|
||||||
enemy.set_state("health_good", false);
|
enemy.set_state("health_good", false);
|
||||||
enemy.update();
|
enemy.update();
|
||||||
REQUIRE(enemy.wants_to("run_away"));
|
REQUIRE(enemy.wants_to("run_away"));
|
||||||
|
|
||||||
enemy.set_state("have_item", true);
|
|
||||||
enemy.set_state("have_healing", true);
|
|
||||||
enemy.set_state("in_combat", false);
|
|
||||||
enemy.update();
|
|
||||||
REQUIRE(enemy.wants_to("use_healing"));
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue