From a3ea381438e80dc34d0194cccc2968d46cbb272d Mon Sep 17 00:00:00 2001 From: Shagnor Date: Fri, 27 Jan 2023 00:36:49 +1100 Subject: [PATCH] Support player recloning + NPCs can wander and aggro. --- .../message_handler/user_commands/attack.rs | 5 + .../src/message_handler/user_commands/look.rs | 5 +- .../message_handler/user_commands/movement.rs | 39 +- .../src/message_handler/user_commands/say.rs | 3 + .../message_handler/user_commands/whisper.rs | 3 + blastmud_game/src/models/task.rs | 8 + blastmud_game/src/regular_tasks.rs | 2 + blastmud_game/src/services.rs | 2 +- blastmud_game/src/services/combat.rs | 108 ++- blastmud_game/src/static_content.rs | 8 + blastmud_game/src/static_content/npc.rs | 163 ++++- .../src/static_content/npc/melbs_citizen.rs | 618 +++--------------- .../src/static_content/npc/melbs_dog.rs | 2 + blastmud_game/src/static_content/room.rs | 2 + .../src/static_content/room/melbs.rs | 1 + 15 files changed, 397 insertions(+), 572 deletions(-) diff --git a/blastmud_game/src/message_handler/user_commands/attack.rs b/blastmud_game/src/message_handler/user_commands/attack.rs index 9c900e2..36b34f9 100644 --- a/blastmud_game/src/message_handler/user_commands/attack.rs +++ b/blastmud_game/src/message_handler/user_commands/attack.rs @@ -15,6 +15,11 @@ pub struct Verb; impl UserVerb for Verb { async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, remaining: &str) -> UResult<()> { let player_item = get_player_item_or_fail(ctx).await?; + + if player_item.is_dead { + user_error("It doesn't really seem fair, but you realise you won't be able to attack anyone while you're dead!".to_string())?; + } + let attack_whom = search_item_for_user(ctx, &ItemSearchParams { include_loc_contents: true, ..ItemSearchParams::base(&player_item, remaining) diff --git a/blastmud_game/src/message_handler/user_commands/look.rs b/blastmud_game/src/message_handler/user_commands/look.rs index 2493c34..113280b 100644 --- a/blastmud_game/src/message_handler/user_commands/look.rs +++ b/blastmud_game/src/message_handler/user_commands/look.rs @@ -143,7 +143,10 @@ impl UserVerb for Verb { let player_item = get_player_item_or_fail(ctx).await?; let rem_trim = remaining.trim().to_lowercase(); - let (heretype, herecode) = player_item.location.split_once("/").unwrap_or(("room", "repro_xv_chargen")); + let use_location = if player_item.is_dead { "room/repro_xv_respawn" } else { + &player_item.location + }; + let (heretype, herecode) = use_location.split_once("/").unwrap_or(("room", "repro_xv_chargen")); let item: Arc = if rem_trim == "" { ctx.trans.find_item_by_type_code(heretype, herecode).await? .ok_or_else(|| UserError("Sorry, that no longer exists".to_owned()))? diff --git a/blastmud_game/src/message_handler/user_commands/movement.rs b/blastmud_game/src/message_handler/user_commands/movement.rs index 1a1c57a..14ff377 100644 --- a/blastmud_game/src/message_handler/user_commands/movement.rs +++ b/blastmud_game/src/message_handler/user_commands/movement.rs @@ -23,27 +23,27 @@ use crate::{ broadcast_to_room, skills::skill_check_and_grind, combat::stop_attacking, + combat::handle_resurrect, } }; use std::time; pub async fn announce_move(trans: &DBTrans, character: &Item, leaving: &Item, arriving: &Item) -> DResult<()> { - let msg_leaving_exp = format!("{} departs towards {}\n", &character.display, &leaving.display); + let msg_leaving_exp = format!("{} departs towards {}\n", + &character.display_for_sentence(true, 1, true), + &arriving.display); let msg_leaving_nonexp = format!("{} departs towards {}\n", - character.display_less_explicit - .as_ref() - .unwrap_or(&character.display), + character.display_for_sentence(true, 1, false), arriving.display_less_explicit .as_ref() .unwrap_or(&arriving.display)); broadcast_to_room(trans, &format!("{}/{}", &leaving.item_type, &leaving.item_code), None, &msg_leaving_exp, Some(&msg_leaving_nonexp)).await?; - let msg_arriving_exp = format!("{} arrives from {}\n", &character.display, &leaving.display); + let msg_arriving_exp = format!("{} arrives from {}\n", &character.display_for_sentence(true, 1, true), + &leaving.display); let msg_arriving_nonexp = format!("{} arrives from {}\n", - character.display_less_explicit - .as_ref() - .unwrap_or(&character.display), + character.display_for_sentence(true, 1, false), leaving.display_less_explicit .as_ref() .unwrap_or(&leaving.display)); @@ -58,7 +58,15 @@ pub async fn attempt_move_immediate( direction: &Direction, mut player_ctx: Option<&mut VerbContext<'_>> ) -> UResult<()> { - let (heretype, herecode) = orig_mover.location.split_once("/").unwrap_or(("room", "repro_xv_chargen")); + let use_location = if orig_mover.is_dead { + if orig_mover.item_type != "player" { + user_error("Dead players don't move".to_owned())?; + } + "room/repro_xv_respawn" + } else { + &orig_mover.location + }; + let (heretype, herecode) = use_location.split_once("/").unwrap_or(("room", "repro_xv_chargen")); if heretype != "room" { // Fix this when we have planes / boats / roomkits. user_error("Navigating outside rooms not yet supported.".to_owned())? @@ -115,9 +123,9 @@ pub async fn attempt_move_immediate( trans.queue_for_session(ctx.session, Some(&format!("You successfully get away from {}\n", &attacker_names_str))).await?; - for item in &attacker_items[..] { - stop_attacking(trans, &item, &mover).await?; - } + } + for item in &attacker_items[..] { + stop_attacking(trans, &item, &mover).await?; } } else { if let Some(ctx) = player_ctx.as_ref() { @@ -131,9 +139,16 @@ pub async fn attempt_move_immediate( } } + if mover.is_dead { + if !handle_resurrect(trans, &mut mover).await? { + user_error("You couldn't be resurrected.".to_string())?; + } + } + mover.location = format!("{}/{}", "room", new_room.code); mover.action_type = LocationActionType::Normal; mover.active_combat = None; + trans.save_item_model(&mover).await?; if let Some(ctx) = player_ctx { diff --git a/blastmud_game/src/message_handler/user_commands/say.rs b/blastmud_game/src/message_handler/user_commands/say.rs index 6a746d4..dfb59ed 100644 --- a/blastmud_game/src/message_handler/user_commands/say.rs +++ b/blastmud_game/src/message_handler/user_commands/say.rs @@ -54,6 +54,9 @@ impl UserVerb for Verb { user_error("You need to provide a message to send.".to_owned())?; } let player_item = get_player_item_or_fail(ctx).await?; + if player_item.is_dead { + user_error("Shush, the dead can't talk!".to_string())?; + } say_to_room(ctx.trans, &player_item, &player_item.location, &say_what, is_likely_explicit(&say_what)).await } diff --git a/blastmud_game/src/message_handler/user_commands/whisper.rs b/blastmud_game/src/message_handler/user_commands/whisper.rs index ec6f775..4a65a49 100644 --- a/blastmud_game/src/message_handler/user_commands/whisper.rs +++ b/blastmud_game/src/message_handler/user_commands/whisper.rs @@ -17,6 +17,9 @@ impl UserVerb for Verb { user_error("You need to provide a message to send.".to_owned())?; } let player_item = get_player_item_or_fail(ctx).await?; + if player_item.is_dead { + user_error("Shush, the dead can't talk!".to_string())?; + } let to_whom = search_item_for_user(ctx, &ItemSearchParams { include_loc_contents: true, ..ItemSearchParams::base(&player_item, &to_whom_name) diff --git a/blastmud_game/src/models/task.rs b/blastmud_game/src/models/task.rs index a6bdc8e..acaf127 100644 --- a/blastmud_game/src/models/task.rs +++ b/blastmud_game/src/models/task.rs @@ -15,6 +15,12 @@ pub enum TaskDetails { npc_code: String, say_code: String }, + NPCWander { + npc_code: String, + }, + NPCAggro { + npc_code: String, + }, AttackTick, RecloneNPC { npc_code: String @@ -29,6 +35,8 @@ impl TaskDetails { match self { RunQueuedCommand => "RunQueuedCommand", NPCSay { .. } => "NPCSay", + NPCWander { .. } => "NPCWander", + NPCAggro { .. } => "NPCAggro", AttackTick => "AttackTick", RecloneNPC { .. } => "RecloneNPC", RotCorpse { .. } => "RotCorpse", diff --git a/blastmud_game/src/regular_tasks.rs b/blastmud_game/src/regular_tasks.rs index 31dea0a..43f390f 100644 --- a/blastmud_game/src/regular_tasks.rs +++ b/blastmud_game/src/regular_tasks.rs @@ -34,6 +34,8 @@ fn task_handler_registry() -> &'static BTreeMap<&'static str, &'static (dyn Task || vec!( ("RunQueuedCommand", queued_command::HANDLER.clone()), ("NPCSay", npc::SAY_HANDLER.clone()), + ("NPCWander", npc::WANDER_HANDLER.clone()), + ("NPCAggro", npc::AGGRO_HANDLER.clone()), ("AttackTick", combat::TASK_HANDLER.clone()), ("RecloneNPC", npc::RECLONE_HANDLER.clone()), ("RotCorpse", combat::ROT_CORPSE_HANDLER.clone()), diff --git a/blastmud_game/src/services.rs b/blastmud_game/src/services.rs index 95396f3..63910b9 100644 --- a/blastmud_game/src/services.rs +++ b/blastmud_game/src/services.rs @@ -10,7 +10,7 @@ pub mod combat; pub async fn broadcast_to_room(trans: &DBTrans, location: &str, from_item: Option<&Item>, message_explicit_ok: &str, message_nonexplicit: Option<&str>) -> DResult<()> { for item in trans.find_items_by_location(location).await? { - if item.item_type != "player" { + if item.item_type != "player" || item.is_dead { continue; } if let Some((session, session_dat)) = trans.find_session_for_player(&item.item_code).await? { diff --git a/blastmud_game/src/services/combat.rs b/blastmud_game/src/services/combat.rs index 151e5bb..aab45c5 100644 --- a/blastmud_game/src/services/combat.rs +++ b/blastmud_game/src/services/combat.rs @@ -22,6 +22,7 @@ use chrono::Utc; use async_recursion::async_recursion; use std::time; use ansi::ansi; +use rand::prelude::IteratorRandom; use rand_distr::{Normal, Distribution}; #[derive(Clone)] @@ -129,8 +130,41 @@ impl TaskHandler for AttackTaskHandler { } } +pub async fn consider_xp_gain_for(trans: &DBTrans, by_item: &mut Item, for_item: &Item) -> DResult<()> { + if by_item.item_type != "player" { + return Ok(()); + } + let (session, _) = match trans.find_session_for_player(&by_item.item_code).await? { + None => return Ok(()), + Some(r) => r + }; + if by_item.total_xp >= for_item.total_xp { + trans.queue_for_session(&session, Some("[You didn't gain any experience for that]\n")).await?; + return Ok(()); + } + let xp_gain = + (((for_item.total_xp - by_item.total_xp) as f64 * 10.0 / (by_item.total_xp + 1) as f64) as u64) + .min(100); + + if xp_gain == 0 { + trans.queue_for_session(&session, Some("[You didn't gain any experience for that]\n")).await?; + return Ok(()); + } + + by_item.total_xp += xp_gain; + let mut user = match trans.find_by_username(&by_item.item_code).await? { + None => return Ok(()), + Some(r) => r + }; + user.experience.xp_change_for_this_reroll += xp_gain as i64; + trans.save_user_model(&user).await?; + trans.queue_for_session(&session, Some(&format!("You gained {} experience points!\n", xp_gain))).await?; + + + Ok(()) +} + pub async fn handle_death(trans: &DBTrans, whom: &mut Item) -> DResult<()> { - whom.is_dead = true; let msg_exp = format!( ansi!("{} stiffens and collapses dead.\n"), &whom.display_for_sentence(true, 1, true) @@ -139,14 +173,17 @@ pub async fn handle_death(trans: &DBTrans, whom: &mut Item) -> DResult<()> { ansi!("{} stiffens and collapses dead.\n"), &whom.display_for_sentence(false, 1, true) ); + broadcast_to_room(trans, &whom.location, None, &msg_exp, Some(&msg_nonexp)).await?; + whom.is_dead = true; if let Some(ac) = &whom.active_combat { let at_str = ac.attacking.clone(); for attacker in ac.attacked_by.clone().iter() { if let Some((atype, acode)) = attacker.split_once("/") { if let Some(aitem) = trans.find_item_by_type_code(atype, acode).await? { let mut new_aitem = (*aitem).clone(); - stop_attacking_mut(trans, &mut new_aitem, whom).await?; + consider_xp_gain_for(trans, &mut new_aitem, &whom).await?; + stop_attacking_mut(trans, &mut new_aitem, whom, true).await?; trans.save_item_model(&new_aitem).await?; } } @@ -154,7 +191,7 @@ pub async fn handle_death(trans: &DBTrans, whom: &mut Item) -> DResult<()> { if let Some((vtype, vcode)) = at_str.as_ref().and_then(|a| a.split_once("/")) { if let Some(vitem) = trans.find_item_by_type_code(vtype, vcode).await? { let mut new_vitem = (*vitem).clone(); - stop_attacking_mut(trans, whom, &mut new_vitem).await?; + stop_attacking_mut(trans, whom, &mut new_vitem, false).await?; trans.save_item_model(&new_vitem).await?; } } @@ -171,8 +208,29 @@ pub async fn handle_death(trans: &DBTrans, whom: &mut Item) -> DResult<()> { } }).await?; } + Ok(()) +} + +pub async fn handle_resurrect(trans: &DBTrans, player: &mut Item) -> DResult { + corpsify_item(trans, &player).await?; + player.is_dead = false; + let lost_xp = (player.total_xp / 200).max(10).min(player.total_xp); + let (session, _) = match trans.find_session_for_player(&player.item_code).await? { + None => return Ok(false), + Some(r) => r + }; + let mut user = match trans.find_by_username(&player.item_code).await? { + None => return Ok(false), + Some(r) => r + }; + trans.queue_for_session(&session, + Some(&format!("You lost {} experience points by dying.\n", lost_xp))).await?; + player.total_xp -= lost_xp; + user.experience.xp_change_for_this_reroll -= lost_xp as i64; + player.health = max_health(&player); - broadcast_to_room(trans, &whom.location, None, &msg_exp, Some(&msg_nonexp)).await + trans.save_user_model(&user).await?; + Ok(true) } pub fn max_health(_whom: &Item) -> u64 { @@ -181,7 +239,8 @@ pub fn max_health(_whom: &Item) -> u64 { pub static TASK_HANDLER: &(dyn TaskHandler + Sync + Send) = &AttackTaskHandler; -pub async fn stop_attacking_mut(trans: &DBTrans, new_by_whom: &mut Item, new_to_whom: &mut Item) -> +pub async fn stop_attacking_mut(trans: &DBTrans, new_by_whom: &mut Item, new_to_whom: &mut Item, + auto_refocus: bool) -> DResult<()> { trans.delete_task("AttackTick", @@ -194,6 +253,19 @@ pub async fn stop_attacking_mut(trans: &DBTrans, new_by_whom: &mut Item, new_to_ } if let Some(ac) = new_by_whom.active_combat.as_mut() { ac.attacking = None; + if auto_refocus { + let old_vic = format!("{}/{}", new_to_whom.item_type, new_to_whom.item_code); + let new_vic_opt = ac.attacked_by.iter().filter(|i| **i != old_vic).choose(&mut rand::thread_rng()); + if let Some(new_vic) = new_vic_opt { + if let Some((vtype, vcode)) = new_vic.split_once("/") { + if let Some(vic_item) = trans.find_item_by_type_code(vtype, vcode).await? { + let mut new_vic_item = (*vic_item).clone(); + start_attack_mut(trans, new_by_whom, &mut new_vic_item); + trans.save_item_model(&new_vic_item).await?; + } + } + } + } } new_by_whom.action_type = LocationActionType::Normal; Ok(()) @@ -203,7 +275,7 @@ pub async fn stop_attacking_mut(trans: &DBTrans, new_by_whom: &mut Item, new_to_ pub async fn stop_attacking(trans: &DBTrans, by_whom: &Item, to_whom: &Item) -> DResult<()> { let mut new_by_whom = (*by_whom).clone(); let mut new_to_whom = (*to_whom).clone(); - stop_attacking_mut(trans, &mut new_by_whom, &mut new_to_whom).await?; + stop_attacking_mut(trans, &mut new_by_whom, &mut new_to_whom, false).await?; trans.save_item_model(&new_by_whom).await?; trans.save_item_model(&new_to_whom).await?; Ok(()) @@ -228,6 +300,16 @@ fn attack_speed(_who: &Item) -> time::Duration { #[async_recursion] pub async fn start_attack(trans: &DBTrans, by_whom: &Item, to_whom: &Item) -> UResult<()> { + let mut by_whom_for_update = by_whom.clone(); + let mut to_whom_for_update = to_whom.clone(); + start_attack_mut(trans, &mut by_whom_for_update, &mut to_whom_for_update).await?; + trans.save_item_model(&by_whom_for_update).await?; + trans.save_item_model(&to_whom_for_update).await?; + Ok(()) +} + +#[async_recursion] +pub async fn start_attack_mut(trans: &DBTrans, by_whom: &mut Item, to_whom: &mut Item) -> UResult<()> { let mut msg_exp = String::new(); let mut msg_nonexp = String::new(); let mut verb: String = "attacks".to_string(); @@ -272,13 +354,11 @@ pub async fn start_attack(trans: &DBTrans, by_whom: &Item, to_whom: &Item) -> UR broadcast_to_room(trans, &by_whom.location, None::<&Item>, &msg_exp, Some(msg_nonexp.as_str())).await?; - let mut by_whom_for_update = by_whom.clone(); - by_whom_for_update.active_combat.get_or_insert_with(|| Default::default()).attacking = + by_whom.active_combat.get_or_insert_with(|| Default::default()).attacking = Some(format!("{}/{}", &to_whom.item_type, &to_whom.item_code)); - by_whom_for_update.action_type = LocationActionType::Attacking(Subattack::Normal); - let mut to_whom_for_update = to_whom.clone(); - to_whom_for_update.active_combat.get_or_insert_with(|| Default::default()).attacked_by.push( + by_whom.action_type = LocationActionType::Attacking(Subattack::Normal); + to_whom.active_combat.get_or_insert_with(|| Default::default()).attacked_by.push( format!("{}/{}", &by_whom.item_type, &by_whom.item_code) ); @@ -292,11 +372,9 @@ pub async fn start_attack(trans: &DBTrans, by_whom: &Item, to_whom: &Item) -> UR }, details: TaskDetails::AttackTick }).await?; - trans.save_item_model(&by_whom_for_update).await?; - trans.save_item_model(&to_whom_for_update).await?; // Auto-counterattack if victim isn't busy. - if to_whom_for_update.active_combat.as_ref().and_then(|ac| ac.attacking.as_ref()) == None { - start_attack(trans, &to_whom_for_update, &by_whom_for_update).await?; + if to_whom.active_combat.as_ref().and_then(|ac| ac.attacking.as_ref()) == None { + start_attack(trans, &to_whom, &by_whom).await?; } Ok(()) diff --git a/blastmud_game/src/static_content.rs b/blastmud_game/src/static_content.rs index c2fe90e..b9428b9 100644 --- a/blastmud_game/src/static_content.rs +++ b/blastmud_game/src/static_content.rs @@ -50,6 +50,14 @@ fn static_task_registry() -> Vec> { thing_type: "NPCSay", things: || npc::npc_say_tasks() }, + StaticThingTypeGroup:: { + thing_type: "NPCWander", + things: || npc::npc_wander_tasks() + }, + StaticThingTypeGroup:: { + thing_type: "NPCAggro", + things: || npc::npc_aggro_tasks() + }, ) } diff --git a/blastmud_game/src/static_content/npc.rs b/blastmud_game/src/static_content/npc.rs index 57dd6d9..f127d39 100644 --- a/blastmud_game/src/static_content/npc.rs +++ b/blastmud_game/src/static_content/npc.rs @@ -2,7 +2,11 @@ use super::{ StaticItem, StaticTask, possession_type::PossessionType, - species::SpeciesType + species::SpeciesType, + room::{ + room_map_by_code, + resolve_exit + } }; use crate::models::{ item::{Item, Pronouns, SkillType}, @@ -12,13 +16,15 @@ use crate::services::{ combat::{ max_health, corpsify_item, + start_attack, } }; use once_cell::sync::OnceCell; use std::collections::BTreeMap; use crate::message_handler::user_commands::{ VerbContext, UResult, CommandHandlingError, - say::say_to_room + say::say_to_room, + movement::attempt_move_immediate, }; use crate::DResult; use async_trait::async_trait; @@ -66,9 +72,12 @@ pub struct NPC { pub aliases: Vec<&'static str>, pub says: Vec, pub attackable: bool, + pub aggression: u64, pub intrinsic_weapon: Option, + pub total_xp: u64, pub total_skills: BTreeMap, pub species: SpeciesType, + pub wander_zones: Vec<&'static str> } impl Default for NPC { @@ -82,10 +91,13 @@ impl Default for NPC { message_handler: None, aliases: vec!(), says: vec!(), + total_xp: 1000, total_skills: SkillType::values().into_iter().map(|sk| (sk, 10.0)).collect(), attackable: false, + aggression: 0, intrinsic_weapon: None, species: SpeciesType::Human, + wander_zones: vec!() } } } @@ -174,6 +186,54 @@ pub fn npc_say_tasks() -> Box> { }))) } +pub fn npc_wander_tasks() -> Box> { + Box::new(npc_list().iter().filter(|c| !c.wander_zones.is_empty()) + .map(|c| StaticTask { + task_code: c.code.to_owned(), + initial_task: Box::new( + || { + let mut rng = thread_rng(); + Task { + meta: TaskMeta { + task_code: c.code.to_owned(), + is_static: true, + recurrence: Some(TaskRecurrence::FixedDuration { seconds: rng.gen_range(100..150) as u32 }), + next_scheduled: Utc::now() + chrono::Duration::seconds(rng.gen_range(0..30) as i64), + ..TaskMeta::default() + }, + details: TaskDetails::NPCWander { + npc_code: c.code.to_owned(), + }, + } + }) + })) +} + +pub fn npc_aggro_tasks() -> Box> { + Box::new(npc_list().iter().filter(|c| c.aggression != 0) + .map(|c| StaticTask { + task_code: c.code.to_owned(), + initial_task: Box::new( + || { + let mut rng = thread_rng(); + let aggro_time = (rng.gen_range(100..150) as u64) / c.aggression; + Task { + meta: TaskMeta { + task_code: c.code.to_owned(), + is_static: true, + recurrence: Some(TaskRecurrence::FixedDuration { seconds: aggro_time as u32 }), + next_scheduled: Utc::now() + chrono::Duration::seconds(rng.gen_range(0..aggro_time) as i64), + ..TaskMeta::default() + }, + details: TaskDetails::NPCAggro { + npc_code: c.code.to_owned(), + }, + } + }) + })) +} + + pub struct NPCSayTaskHandler; #[async_trait] impl TaskHandler for NPCSayTaskHandler { @@ -226,6 +286,105 @@ impl TaskHandler for NPCSayTaskHandler { } pub static SAY_HANDLER: &'static (dyn TaskHandler + Sync + Send) = &NPCSayTaskHandler; +pub struct NPCWanderTaskHandler; +#[async_trait] +impl TaskHandler for NPCWanderTaskHandler { + async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult> { + let npc_code = match &ctx.task.details { + TaskDetails::NPCWander { npc_code } => npc_code.clone(), + _ => Err("Expected NPCWander type")? + }; + let npc = match npc_by_code().get(npc_code.as_str()) { + None => { + info!("NPC {} is gone / not yet in static items, ignoring in wander handler", &npc_code); + return Ok(None) + }, + Some(r) => r + }; + let item = match ctx.trans.find_item_by_type_code("npc", &npc_code).await? { + None => { + info!("NPC {} is gone / not yet in DB, ignoring in wander handler", &npc_code); + return Ok(None) + }, + Some(r) => r + }; + if item.is_dead { + return Ok(None) + } + let (ltype, lcode) = match item.location.split_once("/") { + None => return Ok(None), + Some(r) => r + }; + if ltype != "room" { + let mut new_item = (*item).clone(); + new_item.location = npc.spawn_location.to_owned(); + ctx.trans.save_item_model(&new_item).await?; + return Ok(None); + } + let room = match room_map_by_code().get(lcode) { + None => { + let mut new_item = (*item).clone(); + new_item.location = npc.spawn_location.to_owned(); + ctx.trans.save_item_model(&new_item).await?; + return Ok(None); + }, + Some(r) => r + }; + let ex_iter = room.exits + .iter() + .filter( + |ex| resolve_exit(room, ex).map( + |new_room| npc.wander_zones.contains(&new_room.zone) && + !new_room.repel_npc).unwrap_or(false) + ); + let dir_opt = ex_iter.choose(&mut thread_rng()).map(|ex| ex.direction.clone()).clone(); + if let Some(dir) = dir_opt { + match attempt_move_immediate(ctx.trans, &item, &dir, None).await { + Ok(()) | Err(CommandHandlingError::UserError(_)) => {}, + Err(CommandHandlingError::SystemError(e)) => Err(e)? + } + } + Ok(None) + } +} +pub static WANDER_HANDLER: &'static (dyn TaskHandler + Sync + Send) = &NPCWanderTaskHandler; + +pub struct NPCAggroTaskHandler; +#[async_trait] +impl TaskHandler for NPCAggroTaskHandler { + async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult> { + let npc_code = match &ctx.task.details { + TaskDetails::NPCAggro { npc_code } => npc_code.clone(), + _ => Err("Expected NPCAggro type")? + }; + let item = match ctx.trans.find_item_by_type_code("npc", &npc_code).await? { + None => { + info!("NPC {} is gone / not yet in DB, ignoring in aggro handler", &npc_code); + return Ok(None) + }, + Some(r) => r + }; + if item.is_dead || item.active_combat.as_ref().map(|ac| ac.attacking.is_some()).unwrap_or(false) { + return Ok(None); + } + let items_loc = ctx.trans.find_items_by_location(&item.location).await?; + let vic_opt = items_loc + .iter() + .filter(|it| (it.item_type == "player" || it.item_type == "npc") && + !it.is_dead && (it.item_type != item.item_type || it.item_code != item.item_code)) + .choose(&mut thread_rng()); + if let Some(victim) = vic_opt { + match start_attack(ctx.trans, &item, victim).await { + Ok(()) | Err(CommandHandlingError::UserError(_)) => {} + Err(CommandHandlingError::SystemError(e)) => Err(e)? + } + } + + Ok(None) + } +} +pub static AGGRO_HANDLER: &'static (dyn TaskHandler + Sync + Send) = &NPCAggroTaskHandler; + pub struct NPCRecloneTaskHandler; #[async_trait] impl TaskHandler for NPCRecloneTaskHandler { diff --git a/blastmud_game/src/static_content/npc/melbs_citizen.rs b/blastmud_game/src/static_content/npc/melbs_citizen.rs index 25df4d0..09df0fd 100644 --- a/blastmud_game/src/static_content/npc/melbs_citizen.rs +++ b/blastmud_game/src/static_content/npc/melbs_citizen.rs @@ -1,4 +1,5 @@ use super::{NPC, NPCSayInfo, NPCSayType}; +use crate::models::item::Pronouns; pub fn npc_list() -> Vec { use NPCSayType::FromFixedList; @@ -17,547 +18,82 @@ pub fn npc_list() -> Vec { )) }; - vec!( - NPC { - code: "melbs_citizen_1", - name: "Matthew Thomas", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_kingst_latrobest", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_2", - name: "Matthew Perez", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_kingst_20", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, + macro_rules! citizen { + ($code: expr, $name: expr, $spawn: expr, $pronouns: expr) => { + NPC { + code: concat!("melbs_citizen_", $code), + name: $name, + pronouns: $pronouns, + description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", + spawn_location: concat!("room/melbs_", $spawn), + message_handler: None, + wander_zones: vec!("melbs"), + says: vec!(melbs_citizen_stdsay.clone()), + ..Default::default() + } + } + } - NPC { - code: "melbs_citizen_3", - name: "Kimberly Jackson", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_kingst_40", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_4", - name: "Michael Sanchez", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_kingst_50", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_5", - name: "Jessica Davis", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_kingst_bourkest", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_6", - name: "Robert Davis", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_kingst_70", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_7", - name: "Paul Lewis", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_kingst_90", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_8", - name: "Andrew Moore", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_kingst_collinsst", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_9", - name: "Betty Thomas", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_kingst_100", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_10", - name: "Mary Robinson", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_kingst_110", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_11", - name: "Lisa Lopez", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_kingst_flinderst", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_12", - name: "Kimberly Martinez", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_flindersst_200", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_13", - name: "Anthony Nguyen", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_flindersst_190", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_14", - name: "Joshua Green", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_flindersst_180", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_15", - name: "Emily Wright", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_flindersst_170", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_16", - name: "Ashley Thomas", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_lonsdalest_130", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_17", - name: "Jessica Miller", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_kingst_80", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_18", - name: "Anthony Lopez", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_lonsdalest_140", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_19", - name: "John Lopez", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_elizabethst_lonsdalest", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_20", - name: "Thomas Garcia", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_williamsst_120", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_21", - name: "Donna Thompson", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_elizabethst_60", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_22", - name: "Matthew Davis", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_williamsst_100", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_23", - name: "Steven Jones", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_swanstonst_120", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_24", - name: "Linda Smith", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_swanstonst_lonsdalest", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_25", - name: "Karen Rodriguez", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_bourkest_180", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_26", - name: "Paul Scott", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_swanstonst_70", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_27", - name: "Ashley Thomas", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_lonsdalest_130", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_28", - name: "Sandra Scott", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_elizabethst_30", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_29", - name: "Michael Rodriguez", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_swanstonst_70", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_30", - name: "Donald Miller", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_elizabethst_30", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_31", - name: "Charles Moore", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_lonsdalest_160", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_32", - name: "Ashley Sanchez", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_kingst_100", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_33", - name: "Margaret Lewis", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_flindersst_180", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_34", - name: "Sandra Thompson", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_swanstonst_80", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_35", - name: "Sandra King", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_lonsdalest_150", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_36", - name: "Lisa Anderson", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_lonsdalest_210", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_37", - name: "Kimberly Martin", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_kingst_80", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_38", - name: "Susan Smith", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_latrobest_190", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_39", - name: "Susan Martin", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_collinsst_150", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_40", - name: "Linda Scott", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_williamsst_30", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_41", - name: "Donald Miller", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_elizabethst_80", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_42", - name: "Mark Hill", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_collinsst_120", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_43", - name: "William Perez", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_queenst_90", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_44", - name: "Donald Perez", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_queenst_lonsdalest", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_45", - name: "Lisa Rodriguez", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_collinsst_100", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_46", - name: "James Adams", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_latrobest_150", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_47", - name: "James Moore", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_latrobest_130", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_48", - name: "Joseph Martin", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_bourkest_150", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_49", - name: "Matthew Jones", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_kingst_60", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_50", - name: "Michael Sanchez", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_queenst_100", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_51", - name: "Donna Torres", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_flindersst_150", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_52", - name: "Barbara Garcia", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_swanstonst_50", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_53", - name: "Daniel Miller", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_bourkest_110", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_54", - name: "Robert Young", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_kingst_collinsst", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_55", - name: "Donald Flores", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_swanstonst_40", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_56", - name: "Charles Thomas", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_flindersst_110", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_57", - name: "William Torres", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_swanstonst_60", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_58", - name: "Barbara Gonzalez", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_collinsst_190", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_59", - name: "Mary Smith", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_bourkest_180", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - }, - NPC { - code: "melbs_citizen_60", - name: "Michael Jackson", - description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", - spawn_location: "room/melbs_williamsst_110", - message_handler: None, - says: vec!(melbs_citizen_stdsay.clone()), - ..Default::default() - } + vec!( + citizen!("1", "Matthew Thomas", "kingst_latrobest", Pronouns::default_male()), + citizen!("2", "Matthew Perez", "kingst_20", Pronouns::default_male()), + citizen!("3", "Kimberly Jackson", "kingst_40", Pronouns::default_female()), + citizen!("4", "Michael Sanchez", "kingst_50", Pronouns::default_male()), + citizen!("5", "Jessica Davis", "kingst_bourkest", Pronouns::default_female()), + citizen!("6", "Robert Davis", "kingst_70", Pronouns::default_male()), + citizen!("7", "Paul Lewis", "kingst_90", Pronouns::default_male()), + citizen!("8", "Andrew Moore", "kingst_collinsst", Pronouns::default_male()), + citizen!("9", "Betty Thomas", "kingst_100", Pronouns::default_female()), + citizen!("10", "Mary Robinson", "kingst_110", Pronouns::default_female()), + citizen!("11", "Lisa Lopez", "kingst_flinderst", Pronouns::default_female()), + citizen!("12", "Kimberly Martinez", "flindersst_200", Pronouns::default_female()), + citizen!("13", "Anthony Nguyen", "flindersst_190", Pronouns::default_male()), + citizen!("14", "Joshua Green", "flindersst_180", Pronouns::default_male()), + citizen!("15", "Emily Wright", "flindersst_170", Pronouns::default_female()), + citizen!("16", "Ashley Thomas", "lonsdalest_130", Pronouns::default_male()), + citizen!("17", "Jessica Miller", "kingst_80", Pronouns::default_female()), + citizen!("18", "Anthony Lopez", "lonsdalest_140", Pronouns::default_male()), + citizen!("19", "John Lopez", "elizabethst_lonsdalest", Pronouns::default_male()), + citizen!("20", "Thomas Garcia", "williamsst_120", Pronouns::default_male()), + citizen!("21", "Donna Thompson", "elizabethst_60", Pronouns::default_female()), + citizen!("22", "Matthew Davis", "williamsst_100", Pronouns::default_male()), + citizen!("23", "Steven Jones", "swanstonst_120", Pronouns::default_male()), + citizen!("24", "Linda Smith", "swanstonst_lonsdalest", Pronouns::default_male()), + citizen!("25", "Karen Rodriguez", "bourkest_180", Pronouns::default_female()), + citizen!("26", "Paul Scott", "swanstonst_70", Pronouns::default_male()), + citizen!("27", "Ashley Thomas", "lonsdalest_130", Pronouns::default_male()), + citizen!("28", "Sandra Scott", "elizabethst_30", Pronouns::default_female()), + citizen!("29", "Michael Rodriguez", "swanstonst_70", Pronouns::default_male()), + citizen!("30", "Donald Miller", "elizabethst_30", Pronouns::default_male()), + citizen!("31", "Charles Moore", "lonsdalest_160", Pronouns::default_male()), + citizen!("32", "Ashley Sanchez", "kingst_100", Pronouns::default_male()), + citizen!("33", "Margaret Lewis", "flindersst_180", Pronouns::default_female()), + citizen!("34", "Sandra Thompson", "swanstonst_80", Pronouns::default_female()), + citizen!("35", "Sandra King", "lonsdalest_150", Pronouns::default_female()), + citizen!("36", "Lisa Anderson", "lonsdalest_210", Pronouns::default_female()), + citizen!("37", "Kimberly Martin", "kingst_80", Pronouns::default_female()), + citizen!("38", "Susan Smith", "latrobest_190", Pronouns::default_female()), + citizen!("39", "Susan Martin", "collinsst_150", Pronouns::default_female()), + citizen!("40", "Linda Scott", "williamsst_30", Pronouns::default_female()), + citizen!("41", "Donald Miller", "elizabethst_80", Pronouns::default_male()), + citizen!("42", "Mark Hill", "collinsst_120", Pronouns::default_male()), + citizen!("43", "William Perez", "queenst_90", Pronouns::default_male()), + citizen!("44", "Donald Perez", "queenst_lonsdalest", Pronouns::default_male()), + citizen!("45", "Lisa Rodriguez", "collinsst_100", Pronouns::default_female()), + citizen!("46", "James Adams", "latrobest_150", Pronouns::default_male()), + citizen!("47", "James Moore", "latrobest_130", Pronouns::default_male()), + citizen!("48", "Joseph Martin", "bourkest_150", Pronouns::default_male()), + citizen!("49", "Matthew Jones", "kingst_60", Pronouns::default_male()), + citizen!("50", "Michael Sanchez", "queenst_100", Pronouns::default_male()), + citizen!("51", "Donna Torres", "flindersst_150", Pronouns::default_female()), + citizen!("52", "Barbara Garcia", "swanstonst_50", Pronouns::default_female()), + citizen!("53", "Daniel Miller", "bourkest_110", Pronouns::default_male()), + citizen!("54", "Robert Young", "kingst_collinsst", Pronouns::default_male()), + citizen!("55", "Donald Flores", "swanstonst_40", Pronouns::default_male()), + citizen!("56", "Charles Thomas", "flindersst_110", Pronouns::default_male()), + citizen!("57", "William Torres", "swanstonst_60", Pronouns::default_male()), + citizen!("58", "Barbara Gonzalez", "collinsst_190", Pronouns::default_female()), + citizen!("59", "Mary Smith", "bourkest_180", Pronouns::default_female()), + citizen!("60", "Michael John", "williamsst_110", Pronouns::default_male()), ) } diff --git a/blastmud_game/src/static_content/npc/melbs_dog.rs b/blastmud_game/src/static_content/npc/melbs_dog.rs index 66159cf..ba66cd1 100644 --- a/blastmud_game/src/static_content/npc/melbs_dog.rs +++ b/blastmud_game/src/static_content/npc/melbs_dog.rs @@ -12,6 +12,8 @@ macro_rules! dog { name: concat!($adj, " dog"), pronouns: Pronouns { is_proper: false, ..Pronouns::default_inanimate() }, attackable: true, + aggression: 12, + wander_zones: vec!("melbs"), description: "A malnourished looking dog. Its skeleton is visible through its thin and patchy fur. It smells terrible, and certainly doesn't look tame.", aliases: vec!("dog"), spawn_location: concat!("room/", $spawn), diff --git a/blastmud_game/src/static_content/room.rs b/blastmud_game/src/static_content/room.rs index 05c008d..4d4063e 100644 --- a/blastmud_game/src/static_content/room.rs +++ b/blastmud_game/src/static_content/room.rs @@ -157,6 +157,7 @@ pub struct Room { pub description_less_explicit: Option<&'static str>, pub exits: Vec, pub should_caption: bool, + pub repel_npc: bool, pub item_flags: Vec } @@ -173,6 +174,7 @@ impl Default for Room { description_less_explicit: None, exits: vec!(), should_caption: true, + repel_npc: false, item_flags: vec!(), } } diff --git a/blastmud_game/src/static_content/room/melbs.rs b/blastmud_game/src/static_content/room/melbs.rs index b8c5ad8..8e8138b 100644 --- a/blastmud_game/src/static_content/room/melbs.rs +++ b/blastmud_game/src/static_content/room/melbs.rs @@ -181,6 +181,7 @@ pub fn room_list() -> Vec { ), should_caption: true, item_flags: vec!(ItemFlag::NoSay, ItemFlag::NoSeeContents), + repel_npc: true, ..Default::default() }, Room {