Support player recloning + NPCs can wander and aggro.

This commit is contained in:
Condorra 2023-01-27 00:36:49 +11:00
parent 2d9fcf9850
commit a3ea381438
15 changed files with 397 additions and 572 deletions

View File

@ -15,6 +15,11 @@ pub struct Verb;
impl UserVerb for Verb { impl UserVerb for Verb {
async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, remaining: &str) -> UResult<()> { async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, remaining: &str) -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?; 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 { let attack_whom = search_item_for_user(ctx, &ItemSearchParams {
include_loc_contents: true, include_loc_contents: true,
..ItemSearchParams::base(&player_item, remaining) ..ItemSearchParams::base(&player_item, remaining)

View File

@ -143,7 +143,10 @@ impl UserVerb for Verb {
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
let rem_trim = remaining.trim().to_lowercase(); 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<Item> = if rem_trim == "" { let item: Arc<Item> = if rem_trim == "" {
ctx.trans.find_item_by_type_code(heretype, herecode).await? ctx.trans.find_item_by_type_code(heretype, herecode).await?
.ok_or_else(|| UserError("Sorry, that no longer exists".to_owned()))? .ok_or_else(|| UserError("Sorry, that no longer exists".to_owned()))?

View File

@ -23,27 +23,27 @@ use crate::{
broadcast_to_room, broadcast_to_room,
skills::skill_check_and_grind, skills::skill_check_and_grind,
combat::stop_attacking, combat::stop_attacking,
combat::handle_resurrect,
} }
}; };
use std::time; use std::time;
pub async fn announce_move(trans: &DBTrans, character: &Item, leaving: &Item, arriving: &Item) -> DResult<()> { 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", let msg_leaving_nonexp = format!("{} departs towards {}\n",
character.display_less_explicit character.display_for_sentence(true, 1, false),
.as_ref()
.unwrap_or(&character.display),
arriving.display_less_explicit arriving.display_less_explicit
.as_ref() .as_ref()
.unwrap_or(&arriving.display)); .unwrap_or(&arriving.display));
broadcast_to_room(trans, &format!("{}/{}", &leaving.item_type, &leaving.item_code), broadcast_to_room(trans, &format!("{}/{}", &leaving.item_type, &leaving.item_code),
None, &msg_leaving_exp, Some(&msg_leaving_nonexp)).await?; 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", let msg_arriving_nonexp = format!("{} arrives from {}\n",
character.display_less_explicit character.display_for_sentence(true, 1, false),
.as_ref()
.unwrap_or(&character.display),
leaving.display_less_explicit leaving.display_less_explicit
.as_ref() .as_ref()
.unwrap_or(&leaving.display)); .unwrap_or(&leaving.display));
@ -58,7 +58,15 @@ pub async fn attempt_move_immediate(
direction: &Direction, direction: &Direction,
mut player_ctx: Option<&mut VerbContext<'_>> mut player_ctx: Option<&mut VerbContext<'_>>
) -> UResult<()> { ) -> 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" { if heretype != "room" {
// Fix this when we have planes / boats / roomkits. // Fix this when we have planes / boats / roomkits.
user_error("Navigating outside rooms not yet supported.".to_owned())? 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, trans.queue_for_session(ctx.session,
Some(&format!("You successfully get away from {}\n", Some(&format!("You successfully get away from {}\n",
&attacker_names_str))).await?; &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 { } else {
if let Some(ctx) = player_ctx.as_ref() { 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.location = format!("{}/{}", "room", new_room.code);
mover.action_type = LocationActionType::Normal; mover.action_type = LocationActionType::Normal;
mover.active_combat = None; mover.active_combat = None;
trans.save_item_model(&mover).await?; trans.save_item_model(&mover).await?;
if let Some(ctx) = player_ctx { if let Some(ctx) = player_ctx {

View File

@ -54,6 +54,9 @@ impl UserVerb for Verb {
user_error("You need to provide a message to send.".to_owned())?; user_error("You need to provide a message to send.".to_owned())?;
} }
let player_item = get_player_item_or_fail(ctx).await?; 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_to_room(ctx.trans, &player_item, &player_item.location,
&say_what, is_likely_explicit(&say_what)).await &say_what, is_likely_explicit(&say_what)).await
} }

View File

@ -17,6 +17,9 @@ impl UserVerb for Verb {
user_error("You need to provide a message to send.".to_owned())?; user_error("You need to provide a message to send.".to_owned())?;
} }
let player_item = get_player_item_or_fail(ctx).await?; 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 { let to_whom = search_item_for_user(ctx, &ItemSearchParams {
include_loc_contents: true, include_loc_contents: true,
..ItemSearchParams::base(&player_item, &to_whom_name) ..ItemSearchParams::base(&player_item, &to_whom_name)

View File

@ -15,6 +15,12 @@ pub enum TaskDetails {
npc_code: String, npc_code: String,
say_code: String say_code: String
}, },
NPCWander {
npc_code: String,
},
NPCAggro {
npc_code: String,
},
AttackTick, AttackTick,
RecloneNPC { RecloneNPC {
npc_code: String npc_code: String
@ -29,6 +35,8 @@ impl TaskDetails {
match self { match self {
RunQueuedCommand => "RunQueuedCommand", RunQueuedCommand => "RunQueuedCommand",
NPCSay { .. } => "NPCSay", NPCSay { .. } => "NPCSay",
NPCWander { .. } => "NPCWander",
NPCAggro { .. } => "NPCAggro",
AttackTick => "AttackTick", AttackTick => "AttackTick",
RecloneNPC { .. } => "RecloneNPC", RecloneNPC { .. } => "RecloneNPC",
RotCorpse { .. } => "RotCorpse", RotCorpse { .. } => "RotCorpse",

View File

@ -34,6 +34,8 @@ fn task_handler_registry() -> &'static BTreeMap<&'static str, &'static (dyn Task
|| vec!( || vec!(
("RunQueuedCommand", queued_command::HANDLER.clone()), ("RunQueuedCommand", queued_command::HANDLER.clone()),
("NPCSay", npc::SAY_HANDLER.clone()), ("NPCSay", npc::SAY_HANDLER.clone()),
("NPCWander", npc::WANDER_HANDLER.clone()),
("NPCAggro", npc::AGGRO_HANDLER.clone()),
("AttackTick", combat::TASK_HANDLER.clone()), ("AttackTick", combat::TASK_HANDLER.clone()),
("RecloneNPC", npc::RECLONE_HANDLER.clone()), ("RecloneNPC", npc::RECLONE_HANDLER.clone()),
("RotCorpse", combat::ROT_CORPSE_HANDLER.clone()), ("RotCorpse", combat::ROT_CORPSE_HANDLER.clone()),

View File

@ -10,7 +10,7 @@ pub mod combat;
pub async fn broadcast_to_room(trans: &DBTrans, location: &str, from_item: Option<&Item>, pub async fn broadcast_to_room(trans: &DBTrans, location: &str, from_item: Option<&Item>,
message_explicit_ok: &str, message_nonexplicit: Option<&str>) -> DResult<()> { message_explicit_ok: &str, message_nonexplicit: Option<&str>) -> DResult<()> {
for item in trans.find_items_by_location(location).await? { for item in trans.find_items_by_location(location).await? {
if item.item_type != "player" { if item.item_type != "player" || item.is_dead {
continue; continue;
} }
if let Some((session, session_dat)) = trans.find_session_for_player(&item.item_code).await? { if let Some((session, session_dat)) = trans.find_session_for_player(&item.item_code).await? {

View File

@ -22,6 +22,7 @@ use chrono::Utc;
use async_recursion::async_recursion; use async_recursion::async_recursion;
use std::time; use std::time;
use ansi::ansi; use ansi::ansi;
use rand::prelude::IteratorRandom;
use rand_distr::{Normal, Distribution}; use rand_distr::{Normal, Distribution};
#[derive(Clone)] #[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<()> { pub async fn handle_death(trans: &DBTrans, whom: &mut Item) -> DResult<()> {
whom.is_dead = true;
let msg_exp = format!( let msg_exp = format!(
ansi!("<red>{} stiffens and collapses dead.<reset>\n"), ansi!("<red>{} stiffens and collapses dead.<reset>\n"),
&whom.display_for_sentence(true, 1, true) &whom.display_for_sentence(true, 1, true)
@ -139,14 +173,17 @@ pub async fn handle_death(trans: &DBTrans, whom: &mut Item) -> DResult<()> {
ansi!("<red>{} stiffens and collapses dead.<reset>\n"), ansi!("<red>{} stiffens and collapses dead.<reset>\n"),
&whom.display_for_sentence(false, 1, true) &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 { if let Some(ac) = &whom.active_combat {
let at_str = ac.attacking.clone(); let at_str = ac.attacking.clone();
for attacker in ac.attacked_by.clone().iter() { for attacker in ac.attacked_by.clone().iter() {
if let Some((atype, acode)) = attacker.split_once("/") { if let Some((atype, acode)) = attacker.split_once("/") {
if let Some(aitem) = trans.find_item_by_type_code(atype, acode).await? { if let Some(aitem) = trans.find_item_by_type_code(atype, acode).await? {
let mut new_aitem = (*aitem).clone(); 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?; 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((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? { if let Some(vitem) = trans.find_item_by_type_code(vtype, vcode).await? {
let mut new_vitem = (*vitem).clone(); 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?; trans.save_item_model(&new_vitem).await?;
} }
} }
@ -171,8 +208,29 @@ pub async fn handle_death(trans: &DBTrans, whom: &mut Item) -> DResult<()> {
} }
}).await?; }).await?;
} }
Ok(())
}
broadcast_to_room(trans, &whom.location, None, &msg_exp, Some(&msg_nonexp)).await pub async fn handle_resurrect(trans: &DBTrans, player: &mut Item) -> DResult<bool> {
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);
trans.save_user_model(&user).await?;
Ok(true)
} }
pub fn max_health(_whom: &Item) -> u64 { 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 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<()> DResult<()>
{ {
trans.delete_task("AttackTick", 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() { if let Some(ac) = new_by_whom.active_combat.as_mut() {
ac.attacking = None; 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; new_by_whom.action_type = LocationActionType::Normal;
Ok(()) 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<()> { 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_by_whom = (*by_whom).clone();
let mut new_to_whom = (*to_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_by_whom).await?;
trans.save_item_model(&new_to_whom).await?; trans.save_item_model(&new_to_whom).await?;
Ok(()) Ok(())
@ -228,6 +300,16 @@ fn attack_speed(_who: &Item) -> time::Duration {
#[async_recursion] #[async_recursion]
pub async fn start_attack(trans: &DBTrans, by_whom: &Item, to_whom: &Item) -> UResult<()> { 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_exp = String::new();
let mut msg_nonexp = String::new(); let mut msg_nonexp = String::new();
let mut verb: String = "attacks".to_string(); 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?; 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.active_combat.get_or_insert_with(|| Default::default()).attacking =
by_whom_for_update.active_combat.get_or_insert_with(|| Default::default()).attacking =
Some(format!("{}/{}", Some(format!("{}/{}",
&to_whom.item_type, &to_whom.item_code)); &to_whom.item_type, &to_whom.item_code));
by_whom_for_update.action_type = LocationActionType::Attacking(Subattack::Normal); by_whom.action_type = LocationActionType::Attacking(Subattack::Normal);
let mut to_whom_for_update = to_whom.clone(); to_whom.active_combat.get_or_insert_with(|| Default::default()).attacked_by.push(
to_whom_for_update.active_combat.get_or_insert_with(|| Default::default()).attacked_by.push(
format!("{}/{}", format!("{}/{}",
&by_whom.item_type, &by_whom.item_code) &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 details: TaskDetails::AttackTick
}).await?; }).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. // Auto-counterattack if victim isn't busy.
if to_whom_for_update.active_combat.as_ref().and_then(|ac| ac.attacking.as_ref()) == None { if to_whom.active_combat.as_ref().and_then(|ac| ac.attacking.as_ref()) == None {
start_attack(trans, &to_whom_for_update, &by_whom_for_update).await?; start_attack(trans, &to_whom, &by_whom).await?;
} }
Ok(()) Ok(())

View File

@ -50,6 +50,14 @@ fn static_task_registry() -> Vec<StaticThingTypeGroup<StaticTask>> {
thing_type: "NPCSay", thing_type: "NPCSay",
things: || npc::npc_say_tasks() things: || npc::npc_say_tasks()
}, },
StaticThingTypeGroup::<StaticTask> {
thing_type: "NPCWander",
things: || npc::npc_wander_tasks()
},
StaticThingTypeGroup::<StaticTask> {
thing_type: "NPCAggro",
things: || npc::npc_aggro_tasks()
},
) )
} }

View File

@ -2,7 +2,11 @@ use super::{
StaticItem, StaticItem,
StaticTask, StaticTask,
possession_type::PossessionType, possession_type::PossessionType,
species::SpeciesType species::SpeciesType,
room::{
room_map_by_code,
resolve_exit
}
}; };
use crate::models::{ use crate::models::{
item::{Item, Pronouns, SkillType}, item::{Item, Pronouns, SkillType},
@ -12,13 +16,15 @@ use crate::services::{
combat::{ combat::{
max_health, max_health,
corpsify_item, corpsify_item,
start_attack,
} }
}; };
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use crate::message_handler::user_commands::{ use crate::message_handler::user_commands::{
VerbContext, UResult, CommandHandlingError, VerbContext, UResult, CommandHandlingError,
say::say_to_room say::say_to_room,
movement::attempt_move_immediate,
}; };
use crate::DResult; use crate::DResult;
use async_trait::async_trait; use async_trait::async_trait;
@ -66,9 +72,12 @@ pub struct NPC {
pub aliases: Vec<&'static str>, pub aliases: Vec<&'static str>,
pub says: Vec<NPCSayInfo>, pub says: Vec<NPCSayInfo>,
pub attackable: bool, pub attackable: bool,
pub aggression: u64,
pub intrinsic_weapon: Option<PossessionType>, pub intrinsic_weapon: Option<PossessionType>,
pub total_xp: u64,
pub total_skills: BTreeMap<SkillType, f64>, pub total_skills: BTreeMap<SkillType, f64>,
pub species: SpeciesType, pub species: SpeciesType,
pub wander_zones: Vec<&'static str>
} }
impl Default for NPC { impl Default for NPC {
@ -82,10 +91,13 @@ impl Default for NPC {
message_handler: None, message_handler: None,
aliases: vec!(), aliases: vec!(),
says: vec!(), says: vec!(),
total_xp: 1000,
total_skills: SkillType::values().into_iter().map(|sk| (sk, 10.0)).collect(), total_skills: SkillType::values().into_iter().map(|sk| (sk, 10.0)).collect(),
attackable: false, attackable: false,
aggression: 0,
intrinsic_weapon: None, intrinsic_weapon: None,
species: SpeciesType::Human, species: SpeciesType::Human,
wander_zones: vec!()
} }
} }
} }
@ -174,6 +186,54 @@ pub fn npc_say_tasks() -> Box<dyn Iterator<Item = StaticTask>> {
}))) })))
} }
pub fn npc_wander_tasks() -> Box<dyn Iterator<Item = StaticTask>> {
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<dyn Iterator<Item = StaticTask>> {
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; pub struct NPCSayTaskHandler;
#[async_trait] #[async_trait]
impl TaskHandler for NPCSayTaskHandler { impl TaskHandler for NPCSayTaskHandler {
@ -226,6 +286,105 @@ impl TaskHandler for NPCSayTaskHandler {
} }
pub static SAY_HANDLER: &'static (dyn TaskHandler + Sync + Send) = &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<Option<time::Duration>> {
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<Option<time::Duration>> {
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; pub struct NPCRecloneTaskHandler;
#[async_trait] #[async_trait]
impl TaskHandler for NPCRecloneTaskHandler { impl TaskHandler for NPCRecloneTaskHandler {

View File

@ -1,4 +1,5 @@
use super::{NPC, NPCSayInfo, NPCSayType}; use super::{NPC, NPCSayInfo, NPCSayType};
use crate::models::item::Pronouns;
pub fn npc_list() -> Vec<NPC> { pub fn npc_list() -> Vec<NPC> {
use NPCSayType::FromFixedList; use NPCSayType::FromFixedList;
@ -17,547 +18,82 @@ pub fn npc_list() -> Vec<NPC> {
)) ))
}; };
vec!( macro_rules! citizen {
NPC { ($code: expr, $name: expr, $spawn: expr, $pronouns: expr) => {
code: "melbs_citizen_1", NPC {
name: "Matthew Thomas", code: concat!("melbs_citizen_", $code),
description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", name: $name,
spawn_location: "room/melbs_kingst_latrobest", pronouns: $pronouns,
message_handler: None, description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life",
says: vec!(melbs_citizen_stdsay.clone()), spawn_location: concat!("room/melbs_", $spawn),
..Default::default() message_handler: None,
}, wander_zones: vec!("melbs"),
NPC { says: vec!(melbs_citizen_stdsay.clone()),
code: "melbs_citizen_2", ..Default::default()
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()
},
NPC { vec!(
code: "melbs_citizen_3", citizen!("1", "Matthew Thomas", "kingst_latrobest", Pronouns::default_male()),
name: "Kimberly Jackson", citizen!("2", "Matthew Perez", "kingst_20", Pronouns::default_male()),
description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", citizen!("3", "Kimberly Jackson", "kingst_40", Pronouns::default_female()),
spawn_location: "room/melbs_kingst_40", citizen!("4", "Michael Sanchez", "kingst_50", Pronouns::default_male()),
message_handler: None, citizen!("5", "Jessica Davis", "kingst_bourkest", Pronouns::default_female()),
says: vec!(melbs_citizen_stdsay.clone()), citizen!("6", "Robert Davis", "kingst_70", Pronouns::default_male()),
..Default::default() citizen!("7", "Paul Lewis", "kingst_90", Pronouns::default_male()),
}, citizen!("8", "Andrew Moore", "kingst_collinsst", Pronouns::default_male()),
NPC { citizen!("9", "Betty Thomas", "kingst_100", Pronouns::default_female()),
code: "melbs_citizen_4", citizen!("10", "Mary Robinson", "kingst_110", Pronouns::default_female()),
name: "Michael Sanchez", citizen!("11", "Lisa Lopez", "kingst_flinderst", Pronouns::default_female()),
description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", citizen!("12", "Kimberly Martinez", "flindersst_200", Pronouns::default_female()),
spawn_location: "room/melbs_kingst_50", citizen!("13", "Anthony Nguyen", "flindersst_190", Pronouns::default_male()),
message_handler: None, citizen!("14", "Joshua Green", "flindersst_180", Pronouns::default_male()),
says: vec!(melbs_citizen_stdsay.clone()), citizen!("15", "Emily Wright", "flindersst_170", Pronouns::default_female()),
..Default::default() citizen!("16", "Ashley Thomas", "lonsdalest_130", Pronouns::default_male()),
}, citizen!("17", "Jessica Miller", "kingst_80", Pronouns::default_female()),
NPC { citizen!("18", "Anthony Lopez", "lonsdalest_140", Pronouns::default_male()),
code: "melbs_citizen_5", citizen!("19", "John Lopez", "elizabethst_lonsdalest", Pronouns::default_male()),
name: "Jessica Davis", citizen!("20", "Thomas Garcia", "williamsst_120", Pronouns::default_male()),
description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", citizen!("21", "Donna Thompson", "elizabethst_60", Pronouns::default_female()),
spawn_location: "room/melbs_kingst_bourkest", citizen!("22", "Matthew Davis", "williamsst_100", Pronouns::default_male()),
message_handler: None, citizen!("23", "Steven Jones", "swanstonst_120", Pronouns::default_male()),
says: vec!(melbs_citizen_stdsay.clone()), citizen!("24", "Linda Smith", "swanstonst_lonsdalest", Pronouns::default_male()),
..Default::default() citizen!("25", "Karen Rodriguez", "bourkest_180", Pronouns::default_female()),
}, citizen!("26", "Paul Scott", "swanstonst_70", Pronouns::default_male()),
NPC { citizen!("27", "Ashley Thomas", "lonsdalest_130", Pronouns::default_male()),
code: "melbs_citizen_6", citizen!("28", "Sandra Scott", "elizabethst_30", Pronouns::default_female()),
name: "Robert Davis", citizen!("29", "Michael Rodriguez", "swanstonst_70", Pronouns::default_male()),
description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", citizen!("30", "Donald Miller", "elizabethst_30", Pronouns::default_male()),
spawn_location: "room/melbs_kingst_70", citizen!("31", "Charles Moore", "lonsdalest_160", Pronouns::default_male()),
message_handler: None, citizen!("32", "Ashley Sanchez", "kingst_100", Pronouns::default_male()),
says: vec!(melbs_citizen_stdsay.clone()), citizen!("33", "Margaret Lewis", "flindersst_180", Pronouns::default_female()),
..Default::default() citizen!("34", "Sandra Thompson", "swanstonst_80", Pronouns::default_female()),
}, citizen!("35", "Sandra King", "lonsdalest_150", Pronouns::default_female()),
NPC { citizen!("36", "Lisa Anderson", "lonsdalest_210", Pronouns::default_female()),
code: "melbs_citizen_7", citizen!("37", "Kimberly Martin", "kingst_80", Pronouns::default_female()),
name: "Paul Lewis", citizen!("38", "Susan Smith", "latrobest_190", Pronouns::default_female()),
description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", citizen!("39", "Susan Martin", "collinsst_150", Pronouns::default_female()),
spawn_location: "room/melbs_kingst_90", citizen!("40", "Linda Scott", "williamsst_30", Pronouns::default_female()),
message_handler: None, citizen!("41", "Donald Miller", "elizabethst_80", Pronouns::default_male()),
says: vec!(melbs_citizen_stdsay.clone()), citizen!("42", "Mark Hill", "collinsst_120", Pronouns::default_male()),
..Default::default() citizen!("43", "William Perez", "queenst_90", Pronouns::default_male()),
}, citizen!("44", "Donald Perez", "queenst_lonsdalest", Pronouns::default_male()),
NPC { citizen!("45", "Lisa Rodriguez", "collinsst_100", Pronouns::default_female()),
code: "melbs_citizen_8", citizen!("46", "James Adams", "latrobest_150", Pronouns::default_male()),
name: "Andrew Moore", citizen!("47", "James Moore", "latrobest_130", Pronouns::default_male()),
description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", citizen!("48", "Joseph Martin", "bourkest_150", Pronouns::default_male()),
spawn_location: "room/melbs_kingst_collinsst", citizen!("49", "Matthew Jones", "kingst_60", Pronouns::default_male()),
message_handler: None, citizen!("50", "Michael Sanchez", "queenst_100", Pronouns::default_male()),
says: vec!(melbs_citizen_stdsay.clone()), citizen!("51", "Donna Torres", "flindersst_150", Pronouns::default_female()),
..Default::default() citizen!("52", "Barbara Garcia", "swanstonst_50", Pronouns::default_female()),
}, citizen!("53", "Daniel Miller", "bourkest_110", Pronouns::default_male()),
NPC { citizen!("54", "Robert Young", "kingst_collinsst", Pronouns::default_male()),
code: "melbs_citizen_9", citizen!("55", "Donald Flores", "swanstonst_40", Pronouns::default_male()),
name: "Betty Thomas", citizen!("56", "Charles Thomas", "flindersst_110", Pronouns::default_male()),
description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life", citizen!("57", "William Torres", "swanstonst_60", Pronouns::default_male()),
spawn_location: "room/melbs_kingst_100", citizen!("58", "Barbara Gonzalez", "collinsst_190", Pronouns::default_female()),
message_handler: None, citizen!("59", "Mary Smith", "bourkest_180", Pronouns::default_female()),
says: vec!(melbs_citizen_stdsay.clone()), citizen!("60", "Michael John", "williamsst_110", Pronouns::default_male()),
..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()
}
) )
} }

View File

@ -12,6 +12,8 @@ macro_rules! dog {
name: concat!($adj, " dog"), name: concat!($adj, " dog"),
pronouns: Pronouns { is_proper: false, ..Pronouns::default_inanimate() }, pronouns: Pronouns { is_proper: false, ..Pronouns::default_inanimate() },
attackable: true, 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.", 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"), aliases: vec!("dog"),
spawn_location: concat!("room/", $spawn), spawn_location: concat!("room/", $spawn),

View File

@ -157,6 +157,7 @@ pub struct Room {
pub description_less_explicit: Option<&'static str>, pub description_less_explicit: Option<&'static str>,
pub exits: Vec<Exit>, pub exits: Vec<Exit>,
pub should_caption: bool, pub should_caption: bool,
pub repel_npc: bool,
pub item_flags: Vec<ItemFlag> pub item_flags: Vec<ItemFlag>
} }
@ -173,6 +174,7 @@ impl Default for Room {
description_less_explicit: None, description_less_explicit: None,
exits: vec!(), exits: vec!(),
should_caption: true, should_caption: true,
repel_npc: false,
item_flags: vec!(), item_flags: vec!(),
} }
} }

View File

@ -181,6 +181,7 @@ pub fn room_list() -> Vec<Room> {
), ),
should_caption: true, should_caption: true,
item_flags: vec!(ItemFlag::NoSay, ItemFlag::NoSeeContents), item_flags: vec!(ItemFlag::NoSay, ItemFlag::NoSeeContents),
repel_npc: true,
..Default::default() ..Default::default()
}, },
Room { Room {