From 9b037dceebbb31058b95db9ba43618c4a4843685 Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Tue, 15 Aug 2023 16:29:01 +0100 Subject: [PATCH] faction-based adjacent ai --- raws/mobs.json | 36 +++++++------- src/ai/adjacent_ai_system.rs | 91 ++++++++++++++++++++++++++++++++++++ src/ai/mod.rs | 2 + src/main.rs | 2 + src/raws/mod.rs | 3 +- src/raws/rawmaster.rs | 24 ++++++++-- 6 files changed, 135 insertions(+), 23 deletions(-) create mode 100644 src/ai/adjacent_ai_system.rs diff --git a/raws/mobs.json b/raws/mobs.json index 177bace..22d912c 100644 --- a/raws/mobs.json +++ b/raws/mobs.json @@ -3,7 +3,7 @@ "id": "npc_barkeep", "name": "barkeep", "renderable": { "glyph": "@", "fg": "#EE82EE", "bg": "#000000", "order": 1 }, - "flags": ["BYSTANDER", "BLOCKS_TILE"], + "flags": ["NEUTRAL", "BYSTANDER", "BLOCKS_TILE"], "vision_range": 4, "quips": ["Drink?", "Something to eat?", "Don't go out on an empty stomach."] }, @@ -11,7 +11,7 @@ "id": "npc_townsperson", "name": "townsperson", "renderable": { "glyph": "@", "fg": "#9fa86c", "bg": "#000000", "order": 1 }, - "flags": ["BYSTANDER", "BLOCKS_TILE"], + "flags": ["NEUTRAL", "BYSTANDER", "BLOCKS_TILE"], "vision_range": 4, "quips": ["Hello!", "Good morning.", ""] }, @@ -19,7 +19,7 @@ "id": "npc_drunk", "name": "drunk", "renderable": { "glyph": "@", "fg": "#a0a83c", "bg": "#000000", "order": 1 }, - "flags": ["BYSTANDER", "BLOCKS_TILE"], + "flags": ["NEUTRAL", "BYSTANDER", "BLOCKS_TILE"], "vision_range": 4, "quips": ["Hic!", "H-Hic'.", "Get me 'nother, would you?"] }, @@ -27,7 +27,7 @@ "id": "npc_fisher", "name": "fisher", "renderable": { "glyph": "@", "fg": "#3ca3a8", "bg": "#000000", "order": 1 }, - "flags": ["BYSTANDER", "BLOCKS_TILE"], + "flags": ["NEUTRAL", "BYSTANDER", "BLOCKS_TILE"], "vision_range": 4, "quips": ["Hey."] }, @@ -35,7 +35,7 @@ "id": "npc_dockworker", "name": "dock worker", "renderable": { "glyph": "@", "fg": "#68d8de", "bg": "#000000", "order": 1 }, - "flags": ["BYSTANDER", "BLOCKS_TILE"], + "flags": ["NEUTRAL", "BYSTANDER", "BLOCKS_TILE"], "vision_range": 4, "quips": ["No boat for a few days.", "Not much for us to do."] }, @@ -43,7 +43,7 @@ "id": "npc_priest", "name": "priest", "renderable": { "glyph": "@", "fg": "#FFFFFF", "bg": "#000000", "order": 1 }, - "flags": ["BYSTANDER", "BLOCKS_TILE"], + "flags": ["NEUTRAL", "BYSTANDER", "BLOCKS_TILE"], "vision_range": 4, "quips": ["Light's givings.", "", "Bless you."] }, @@ -51,7 +51,7 @@ "id": "npc_miner", "name": "miner", "renderable": { "glyph": "@", "fg": "#946123", "bg": "#000000", "order": 1 }, - "flags": ["BYSTANDER", "BLOCKS_TILE"], + "flags": ["NEUTRAL", "BYSTANDER", "BLOCKS_TILE"], "vision_range": 4, "attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d8" }], "quips": ["You're not borrowing my pick."] @@ -60,7 +60,7 @@ "id": "npc_guard", "name": "smalltown guard", "renderable": { "glyph": "@", "fg": "#034efc", "bg": "#000000", "order": 1 }, - "flags": ["BYSTANDER", "BLOCKS_TILE"], + "flags": ["NEUTRAL", "BYSTANDER", "BLOCKS_TILE"], "level": 2, "vision_range": 4, "attacks": [{ "name": "hits", "hit_bonus": 0, "damage": "1d8" }], @@ -81,7 +81,7 @@ "id": "chicken", "name": "chicken", "renderable": { "glyph": "c", "fg": "#BB6000", "bg": "#000000", "order": 1 }, - "flags": ["BYSTANDER", "BLOCKS_TILE"], + "flags": ["HERBIVORE", "BYSTANDER", "BLOCKS_TILE"], "bac": 8, "vision_range": 4, "attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }] @@ -90,7 +90,7 @@ "id": "deer_little", "name": "fawn", "renderable": { "glyph": "q", "fg": "#a57037", "bg": "#000000", "order": 1 }, - "flags": ["BYSTANDER", "BLOCKS_TILE"], + "flags": ["HERBIVORE", "BYSTANDER", "BLOCKS_TILE"], "bac": 8, "vision_range": 8, "attacks": [{ "name": "kicks", "hit_bonus": 0, "damage": "1d2" }] @@ -99,7 +99,7 @@ "id": "sheep_little", "name": "lamb", "renderable": { "glyph": "q", "fg": "#e7e7e7", "bg": "#000000", "order": 1 }, - "flags": ["BYSTANDER", "BLOCKS_TILE", "SMALL_GROUP"], + "flags": ["HERBIVORE", "BYSTANDER", "BLOCKS_TILE", "SMALL_GROUP"], "bac": 10, "vision_range": 4, "attacks": [{ "name": "kicks", "hit_bonus": 0, "damage": "1d2" }] @@ -108,7 +108,7 @@ "id": "chicken_little", "name": "chick", "renderable": { "glyph": "c", "fg": "#fae478", "bg": "#000000", "order": 1 }, - "flags": ["BYSTANDER", "BLOCKS_TILE"], + "flags": ["HERBIVORE", "BYSTANDER", "BLOCKS_TILE"], "bac": 10, "vision_range": 4, "attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }] @@ -117,7 +117,7 @@ "id": "horse_little", "name": "pony", "renderable": { "glyph": "u", "fg": "#b36c29", "bg": "#000000", "order": 1 }, - "flags": ["BYSTANDER", "BLOCKS_TILE", "MULTIATTACK"], + "flags": ["HERBIVORE", "BYSTANDER", "BLOCKS_TILE", "MULTIATTACK"], "level": 3, "bac": 6, "speed": 16, @@ -171,7 +171,7 @@ "id": "dog_little", "name": "little dog", "renderable": { "glyph": "d", "fg": "#FFFFFF", "bg": "#000000", "order": 1 }, - "flags": ["BYSTANDER", "BLOCKS_TILE"], + "flags": ["NEUTRAL", "BYSTANDER", "BLOCKS_TILE"], "level": 2, "bac": 6, "speed": 18, @@ -330,7 +330,7 @@ "id": "jackal", "name": "jackal", "renderable": { "glyph": "d", "fg": "#AA5500", "bg": "#000000", "order": 1 }, - "flags": ["MONSTER", "BLOCKS_TILE", "SMALL_GROUP"], + "flags": ["CARNIVORE", "MONSTER", "BLOCKS_TILE", "SMALL_GROUP"], "bac": 7, "vision_range": 12, "attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d2" }] @@ -339,7 +339,7 @@ "id": "fox", "name": "fox", "renderable": { "glyph": "d", "fg": "#FF0000", "bg": "#000000", "order": 1 }, - "flags": ["MONSTER", "BLOCKS_TILE"], + "flags": ["CARNIVORE", "MONSTER", "BLOCKS_TILE"], "bac": 7, "vision_range": 12, "attacks": [{ "name": "bites", "hit_bonus": 0, "damage": "1d3" }] @@ -348,7 +348,7 @@ "id": "coyote", "name": "coyote", "renderable": { "glyph": "d", "fg": "#6E3215", "bg": "#000000", "order": 1 }, - "flags": ["MONSTER", "BLOCKS_TILE", "SMALL_GROUP"], + "flags": ["CARNIVORE", "MONSTER", "BLOCKS_TILE", "SMALL_GROUP"], "level": 1, "bac": 7, "vision_range": 12, @@ -358,7 +358,7 @@ "id": "wolf", "name": "wolf", "renderable": { "glyph": "d", "fg": "#5E4225", "bg": "#000000", "order": 1 }, - "flags": ["MONSTER", "BLOCKS_TILE"], + "flags": ["CARNIVORE", "MONSTER", "BLOCKS_TILE"], "level": 5, "bac": 4, "vision_range": 12, diff --git a/src/ai/adjacent_ai_system.rs b/src/ai/adjacent_ai_system.rs new file mode 100644 index 0000000..0cbdd7c --- /dev/null +++ b/src/ai/adjacent_ai_system.rs @@ -0,0 +1,91 @@ +use crate::{raws::Reaction, Faction, Map, Position, TakingTurn, WantsToMelee}; +use rltk::prelude::*; +use specs::prelude::*; + +pub struct AdjacentAI {} + +impl<'a> System<'a> for AdjacentAI { + #[allow(clippy::type_complexity)] + type SystemData = ( + WriteStorage<'a, TakingTurn>, + ReadStorage<'a, Faction>, + ReadStorage<'a, Position>, + ReadExpect<'a, Map>, + WriteStorage<'a, WantsToMelee>, + Entities<'a>, + ReadExpect<'a, Entity>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (mut turns, factions, positions, map, mut wants_to_melee, entities, player) = data; + + let mut turn_done: Vec = Vec::new(); + for (entity, _turn, faction, pos) in (&entities, &turns, &factions, &positions).join() { + if entity == *player { + continue; + } + let mut reactions: Vec<(Entity, Reaction)> = Vec::new(); + let idx = map.xy_idx(pos.x, pos.y); + let (w, h) = (map.width, map.height); + // Evaluate adjacent squares, add possible reactions + if pos.x > 0 { + evaluate(idx - 1, &map, &factions, &faction.name, &mut reactions); + } + if pos.x < w - 1 { + evaluate(idx + 1, &map, &factions, &faction.name, &mut reactions); + } + if pos.y > 0 { + evaluate(idx - w as usize, &map, &factions, &faction.name, &mut reactions); + } + if pos.y < h - 1 { + evaluate(idx + w as usize, &map, &factions, &faction.name, &mut reactions); + } + if pos.y > 0 && pos.x > 0 { + evaluate((idx - w as usize) - 1, &map, &factions, &faction.name, &mut reactions); + } + if pos.y > 0 && pos.x < w - 1 { + evaluate((idx - w as usize) + 1, &map, &factions, &faction.name, &mut reactions); + } + if pos.y < h - 1 && pos.x > 0 { + evaluate((idx + w as usize) - 1, &map, &factions, &faction.name, &mut reactions); + } + if pos.y < h - 1 && pos.x < w - 1 { + evaluate((idx + w as usize) + 1, &map, &factions, &faction.name, &mut reactions); + } + let mut done = false; + for reaction in reactions.iter() { + if let Reaction::Attack = reaction.1 { + wants_to_melee + .insert(entity, WantsToMelee { target: reaction.0 }) + .expect("Error inserting WantsToMelee"); + done = true; + } + } + if done { + turn_done.push(entity); + } + } + // Remove turn from entities that are done + for done in turn_done.iter() { + turns.remove(*done); + } + } +} + +/// Evaluates all possible reactions between this faction and all entities on a given tile idx. +fn evaluate( + idx: usize, + map: &Map, + factions: &ReadStorage, + this_faction: &str, + reactions: &mut Vec<(Entity, Reaction)>, +) { + for other_entity in map.tile_content[idx].iter() { + if let Some(faction) = factions.get(*other_entity) { + reactions.push(( + *other_entity, + crate::raws::faction_reaction(this_faction, &faction.name, &crate::raws::RAWS.lock().unwrap()), + )); + } + } +} diff --git a/src/ai/mod.rs b/src/ai/mod.rs index 6c37a5f..5415dfd 100644 --- a/src/ai/mod.rs +++ b/src/ai/mod.rs @@ -12,3 +12,5 @@ mod bystander_ai_system; pub use bystander_ai_system::BystanderAI; mod monster_ai_system; pub use monster_ai_system::MonsterAI; +mod adjacent_ai_system; +pub use adjacent_ai_system::AdjacentAI; diff --git a/src/main.rs b/src/main.rs index 1d51263..dd3193b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -96,6 +96,7 @@ impl State { let mut encumbrance_system = ai::EncumbranceSystem {}; let mut turn_status_system = ai::TurnStatusSystem {}; let mut quip_system = ai::QuipSystem {}; + let mut adjacent_ai = ai::AdjacentAI {}; let mut mob = ai::MonsterAI {}; let mut bystanders = ai::BystanderAI {}; let mut trigger_system = trigger_system::TriggerSystem {}; @@ -116,6 +117,7 @@ impl State { energy.run_now(&self.ecs); turn_status_system.run_now(&self.ecs); quip_system.run_now(&self.ecs); + adjacent_ai.run_now(&self.ecs); mob.run_now(&self.ecs); bystanders.run_now(&self.ecs); trigger_system.run_now(&self.ecs); diff --git a/src/raws/mod.rs b/src/raws/mod.rs index 7982146..e46ddeb 100644 --- a/src/raws/mod.rs +++ b/src/raws/mod.rs @@ -12,7 +12,8 @@ use spawn_table_structs::*; mod loot_table_structs; use loot_table_structs::*; mod faction_structs; -use faction_structs::{FactionData, Reaction}; +use faction_structs::FactionData; +pub use faction_structs::Reaction; use std::sync::Mutex; lazy_static! { diff --git a/src/raws/rawmaster.rs b/src/raws/rawmaster.rs index 94be7c2..b1d27ca 100644 --- a/src/raws/rawmaster.rs +++ b/src/raws/rawmaster.rs @@ -290,6 +290,7 @@ pub fn spawn_named_mob( eb = eb.with(get_renderable_component(renderable)); } let mut has_mind = true; + let mut has_faction = false; if let Some(flags) = &mob_template.flags { for flag in flags.iter() { match flag.as_str() { @@ -298,12 +299,21 @@ pub fn spawn_named_mob( "MONSTER" => eb = eb.with(Monster {}), "MINDLESS" => { eb = eb.with(Faction { name: "mindless".to_string() }); + has_faction = true; has_mind = false; } - "NEUTRAL" => eb = eb.with(Faction { name: "neutral".to_string() }), - "HOSTILE" => eb = eb.with(Faction { name: "hostile".to_string() }), - "HERBIVORE" => eb = eb.with(Faction { name: "herbivore".to_string() }), - "CARNIVORE" => eb = eb.with(Faction { name: "carnivore".to_string() }), + "NEUTRAL" => { + eb = eb.with(Faction { name: "neutral".to_string() }); + has_faction = true; + } + "HERBIVORE" => { + eb = eb.with(Faction { name: "herbivore".to_string() }); + has_faction = true; + } + "CARNIVORE" => { + eb = eb.with(Faction { name: "carnivore".to_string() }); + has_faction = true; + } "SMALL_GROUP" => {} // These flags are for region spawning, "LARGE_GROUP" => {} // and don't matter here (yet)? "MULTIATTACK" => { @@ -314,9 +324,15 @@ pub fn spawn_named_mob( } } } + // If we're anything other than MINDLESS, add a mind. if has_mind { eb = eb.with(Mind {}); } + // If we didn't add a faction flag, default to hostile (attacks everything except other hostiles). + if !has_faction { + eb = eb.with(Faction { name: "hostile".to_string() }); + } + // Add quips, if we have some listed. if let Some(quips) = &mob_template.quips { eb = eb.with(Quips { available: quips.clone() }); }