2023-01-22 22:43:44 +11:00
|
|
|
use super::{
|
|
|
|
possession_type::PossessionType,
|
2023-06-20 22:53:46 +10:00
|
|
|
room::{resolve_exit, room_map_by_code},
|
2023-01-27 00:36:49 +11:00
|
|
|
species::SpeciesType,
|
2023-06-20 22:53:46 +10:00
|
|
|
StaticItem, StaticTask,
|
2023-01-23 22:52:01 +11:00
|
|
|
};
|
2023-07-06 22:34:01 +10:00
|
|
|
#[double]
|
|
|
|
use crate::db::DBTrans;
|
2023-06-20 22:53:46 +10:00
|
|
|
use crate::{
|
2023-07-06 22:34:01 +10:00
|
|
|
message_handler::{
|
|
|
|
user_commands::{say::say_to_room, CommandHandlingError, UResult, VerbContext},
|
|
|
|
ListenerSession,
|
2023-06-20 22:53:46 +10:00
|
|
|
},
|
|
|
|
models::{
|
|
|
|
consent::ConsentType,
|
2023-07-11 21:23:34 +10:00
|
|
|
item::{Item, ItemFlag, Pronouns, SkillType, StatType},
|
2023-06-20 22:53:46 +10:00
|
|
|
task::{Task, TaskDetails, TaskMeta, TaskRecurrence},
|
|
|
|
},
|
|
|
|
regular_tasks::{
|
|
|
|
queued_command::{queue_command_for_npc_and_save, MovementSource, QueueCommand},
|
|
|
|
TaskHandler, TaskRunContext,
|
|
|
|
},
|
|
|
|
services::combat::{corpsify_item, start_attack},
|
|
|
|
DResult,
|
2023-01-07 23:06:02 +11:00
|
|
|
};
|
2022-12-31 00:59:14 +11:00
|
|
|
use async_trait::async_trait;
|
2023-01-07 23:06:02 +11:00
|
|
|
use chrono::Utc;
|
|
|
|
use log::info;
|
2023-07-06 22:34:01 +10:00
|
|
|
use mockall_double::double;
|
2023-06-20 22:53:46 +10:00
|
|
|
use once_cell::sync::OnceCell;
|
|
|
|
use rand::{prelude::*, thread_rng, Rng};
|
|
|
|
use std::collections::BTreeMap;
|
2023-01-07 23:06:02 +11:00
|
|
|
use std::time;
|
2023-06-30 23:46:38 +10:00
|
|
|
use uuid::Uuid;
|
2022-12-31 00:59:14 +11:00
|
|
|
|
2023-01-11 22:51:17 +11:00
|
|
|
mod melbs_citizen;
|
|
|
|
mod melbs_dog;
|
2023-07-06 22:34:01 +10:00
|
|
|
mod roboporter;
|
2023-06-20 22:53:46 +10:00
|
|
|
pub mod statbot;
|
2022-12-31 00:59:14 +11:00
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
pub trait NPCMessageHandler {
|
|
|
|
async fn handle(
|
|
|
|
self: &Self,
|
|
|
|
ctx: &mut VerbContext,
|
|
|
|
source: &Item,
|
|
|
|
target: &Item,
|
2023-06-20 22:53:46 +10:00
|
|
|
message: &str,
|
2022-12-31 00:59:14 +11:00
|
|
|
) -> UResult<()>;
|
|
|
|
}
|
|
|
|
|
2023-01-07 23:06:02 +11:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub enum NPCSayType {
|
|
|
|
// Bool is true if it should be filtered for less-explicit.
|
2023-06-20 22:53:46 +10:00
|
|
|
FromFixedList(Vec<(bool, &'static str)>),
|
2023-01-07 23:06:02 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct NPCSayInfo {
|
|
|
|
pub say_code: &'static str,
|
|
|
|
pub frequency_secs: u64,
|
2023-06-20 22:53:46 +10:00
|
|
|
pub talk_type: NPCSayType,
|
2023-01-07 23:06:02 +11:00
|
|
|
}
|
2022-12-29 23:16:52 +11:00
|
|
|
|
2023-02-08 22:35:05 +11:00
|
|
|
pub struct KillBonus {
|
|
|
|
pub msg: &'static str,
|
|
|
|
pub payment: u64,
|
|
|
|
}
|
|
|
|
|
2023-07-06 22:34:01 +10:00
|
|
|
#[async_trait]
|
|
|
|
pub trait HireHandler {
|
|
|
|
async fn hire_handler(
|
|
|
|
&self,
|
|
|
|
trans: &DBTrans,
|
|
|
|
session: &ListenerSession,
|
|
|
|
hirer: &Item,
|
|
|
|
target: &mut Item,
|
|
|
|
) -> UResult<()>;
|
|
|
|
async fn fire_handler(&self, trans: &DBTrans, firer: &Item, target: &mut Item) -> DResult<()>;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct HireData {
|
|
|
|
pub price: u64,
|
|
|
|
pub frequency_secs: u64,
|
|
|
|
pub handler: &'static (dyn HireHandler + Sync + Send),
|
|
|
|
}
|
|
|
|
|
2022-12-29 23:16:52 +11:00
|
|
|
pub struct NPC {
|
|
|
|
pub code: &'static str,
|
|
|
|
pub name: &'static str,
|
2023-01-12 23:12:50 +11:00
|
|
|
pub pronouns: Pronouns,
|
2022-12-29 23:16:52 +11:00
|
|
|
pub description: &'static str,
|
|
|
|
pub spawn_location: &'static str,
|
2023-01-07 23:06:02 +11:00
|
|
|
pub message_handler: Option<&'static (dyn NPCMessageHandler + Sync + Send)>,
|
2023-01-20 23:38:57 +11:00
|
|
|
pub aliases: Vec<&'static str>,
|
2023-01-15 17:30:23 +11:00
|
|
|
pub says: Vec<NPCSayInfo>,
|
2023-01-27 00:36:49 +11:00
|
|
|
pub aggression: u64,
|
2023-02-23 22:55:02 +11:00
|
|
|
pub max_health: u64,
|
2023-01-22 01:16:00 +11:00
|
|
|
pub intrinsic_weapon: Option<PossessionType>,
|
2023-01-27 00:36:49 +11:00
|
|
|
pub total_xp: u64,
|
2023-01-22 01:16:00 +11:00
|
|
|
pub total_skills: BTreeMap<SkillType, f64>,
|
2023-07-11 21:23:34 +10:00
|
|
|
pub total_stats: BTreeMap<StatType, f64>,
|
2023-01-22 22:43:44 +11:00
|
|
|
pub species: SpeciesType,
|
2023-02-08 22:35:05 +11:00
|
|
|
pub wander_zones: Vec<&'static str>,
|
|
|
|
pub kill_bonus: Option<KillBonus>,
|
2023-03-13 15:23:07 +11:00
|
|
|
pub player_consents: Vec<ConsentType>,
|
2023-07-06 22:34:01 +10:00
|
|
|
pub hire_data: Option<HireData>,
|
|
|
|
pub extra_flags: Vec<ItemFlag>,
|
2022-12-29 23:16:52 +11:00
|
|
|
}
|
|
|
|
|
2023-01-11 22:51:17 +11:00
|
|
|
impl Default for NPC {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
code: "DEFAULT",
|
|
|
|
name: "default",
|
2023-01-12 23:12:50 +11:00
|
|
|
pronouns: Pronouns::default_animate(),
|
2023-01-11 22:51:17 +11:00
|
|
|
description: "default",
|
|
|
|
spawn_location: "default",
|
|
|
|
message_handler: None,
|
2023-06-20 22:53:46 +10:00
|
|
|
aliases: vec![],
|
|
|
|
says: vec![],
|
2023-01-27 00:36:49 +11:00
|
|
|
total_xp: 1000,
|
2023-06-20 22:53:46 +10:00
|
|
|
total_skills: SkillType::values()
|
|
|
|
.into_iter()
|
|
|
|
.map(|sk| {
|
|
|
|
(
|
|
|
|
sk.clone(),
|
|
|
|
if &sk == &SkillType::Dodge { 8.0 } else { 10.0 },
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.collect(),
|
2023-07-11 21:23:34 +10:00
|
|
|
total_stats: vec![].into_iter().collect(),
|
2023-01-27 00:36:49 +11:00
|
|
|
aggression: 0,
|
2023-02-23 22:55:02 +11:00
|
|
|
max_health: 24,
|
2023-01-22 22:43:44 +11:00
|
|
|
intrinsic_weapon: None,
|
|
|
|
species: SpeciesType::Human,
|
2023-06-20 22:53:46 +10:00
|
|
|
wander_zones: vec![],
|
2023-02-08 22:35:05 +11:00
|
|
|
kill_bonus: None,
|
2023-06-20 22:53:46 +10:00
|
|
|
player_consents: vec![],
|
2023-07-06 22:34:01 +10:00
|
|
|
hire_data: None,
|
|
|
|
extra_flags: vec![],
|
2023-01-11 22:51:17 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-29 23:16:52 +11:00
|
|
|
pub fn npc_list() -> &'static Vec<NPC> {
|
2022-12-31 00:59:14 +11:00
|
|
|
static NPC_LIST: OnceCell<Vec<NPC>> = OnceCell::new();
|
2023-06-20 22:53:46 +10:00
|
|
|
NPC_LIST.get_or_init(|| {
|
|
|
|
let mut npcs = vec![NPC {
|
|
|
|
code: "repro_xv_chargen_statbot",
|
|
|
|
name: "Statbot",
|
|
|
|
description:
|
|
|
|
"A silvery shiny metal mechanical being. It lets out a whirring sound as it moves.",
|
|
|
|
spawn_location: "room/repro_xv_chargen",
|
|
|
|
message_handler: Some(&statbot::StatbotMessageHandler),
|
|
|
|
says: vec![],
|
|
|
|
..Default::default()
|
|
|
|
}];
|
|
|
|
npcs.append(&mut melbs_citizen::npc_list());
|
|
|
|
npcs.append(&mut melbs_dog::npc_list());
|
2023-07-06 22:34:01 +10:00
|
|
|
npcs.append(&mut roboporter::npc_list());
|
2023-06-20 22:53:46 +10:00
|
|
|
npcs
|
|
|
|
})
|
2022-12-29 23:16:52 +11:00
|
|
|
}
|
|
|
|
|
2022-12-31 00:59:14 +11:00
|
|
|
pub fn npc_by_code() -> &'static BTreeMap<&'static str, &'static NPC> {
|
|
|
|
static NPC_CODE_MAP: OnceCell<BTreeMap<&'static str, &'static NPC>> = OnceCell::new();
|
2023-06-20 22:53:46 +10:00
|
|
|
NPC_CODE_MAP.get_or_init(|| npc_list().iter().map(|npc| (npc.code, npc)).collect())
|
2022-12-31 00:59:14 +11:00
|
|
|
}
|
|
|
|
|
2023-06-20 22:53:46 +10:00
|
|
|
pub fn npc_say_info_by_npc_code_say_code(
|
|
|
|
) -> &'static BTreeMap<(&'static str, &'static str), &'static NPCSayInfo> {
|
|
|
|
static NPC_SAYINFO_MAP: OnceCell<BTreeMap<(&'static str, &'static str), &'static NPCSayInfo>> =
|
|
|
|
OnceCell::new();
|
|
|
|
NPC_SAYINFO_MAP.get_or_init(|| {
|
|
|
|
npc_list()
|
|
|
|
.iter()
|
|
|
|
.flat_map(|npc| {
|
|
|
|
npc.says
|
|
|
|
.iter()
|
|
|
|
.map(|says| ((npc.code, says.say_code), says))
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
})
|
2023-01-07 23:06:02 +11:00
|
|
|
}
|
|
|
|
|
2022-12-29 23:16:52 +11:00
|
|
|
pub fn npc_static_items() -> Box<dyn Iterator<Item = StaticItem>> {
|
|
|
|
Box::new(npc_list().iter().map(|c| StaticItem {
|
|
|
|
item_code: c.code,
|
2023-06-20 22:53:46 +10:00
|
|
|
initial_item: Box::new(|| {
|
2023-07-06 22:34:01 +10:00
|
|
|
let mut flags: Vec<ItemFlag> = c.extra_flags.clone();
|
|
|
|
if c.hire_data.is_some() {
|
|
|
|
flags.push(ItemFlag::Hireable);
|
|
|
|
// Revise if we ever want NPCs to attack for-hire NPCs.
|
|
|
|
flags.push(ItemFlag::NPCsDontAttack);
|
|
|
|
}
|
2023-06-20 22:53:46 +10:00
|
|
|
Item {
|
|
|
|
item_code: c.code.to_owned(),
|
|
|
|
item_type: "npc".to_owned(),
|
|
|
|
display: c.name.to_owned(),
|
|
|
|
details: Some(c.description.to_owned()),
|
|
|
|
location: c.spawn_location.to_owned(),
|
|
|
|
is_static: true,
|
|
|
|
pronouns: c.pronouns.clone(),
|
|
|
|
total_xp: c.total_xp.clone(),
|
|
|
|
total_skills: c.total_skills.clone(),
|
2023-07-11 21:23:34 +10:00
|
|
|
total_stats: c.total_stats.clone(),
|
2023-06-20 22:53:46 +10:00
|
|
|
species: c.species.clone(),
|
|
|
|
health: c.max_health.clone(),
|
2023-07-06 22:34:01 +10:00
|
|
|
flags,
|
2023-06-20 22:53:46 +10:00
|
|
|
aliases: c
|
|
|
|
.aliases
|
|
|
|
.iter()
|
|
|
|
.map(|a| (*a).to_owned())
|
|
|
|
.collect::<Vec<String>>(),
|
|
|
|
..Item::default()
|
|
|
|
}
|
|
|
|
}),
|
2022-12-29 23:16:52 +11:00
|
|
|
}))
|
|
|
|
}
|
2023-01-07 23:06:02 +11:00
|
|
|
|
|
|
|
pub fn npc_say_tasks() -> Box<dyn Iterator<Item = StaticTask>> {
|
2023-06-20 22:53:46 +10:00
|
|
|
Box::new(npc_list().iter().flat_map(|c| {
|
|
|
|
c.says.iter().map(|say| StaticTask {
|
|
|
|
task_code: c.code.to_owned() + "_" + say.say_code,
|
|
|
|
initial_task: Box::new(|| {
|
|
|
|
let mut rng = thread_rng();
|
|
|
|
Task {
|
|
|
|
meta: TaskMeta {
|
|
|
|
task_code: c.code.to_owned() + "_" + say.say_code,
|
|
|
|
is_static: true,
|
|
|
|
recurrence: Some(TaskRecurrence::FixedDuration {
|
|
|
|
seconds: say.frequency_secs as u32,
|
|
|
|
}),
|
|
|
|
next_scheduled: Utc::now()
|
|
|
|
+ chrono::Duration::seconds(
|
|
|
|
rng.gen_range(0..say.frequency_secs) as i64
|
|
|
|
),
|
|
|
|
..TaskMeta::default()
|
|
|
|
},
|
|
|
|
details: TaskDetails::NPCSay {
|
|
|
|
npc_code: c.code.to_owned(),
|
|
|
|
say_code: say.say_code.to_owned(),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}),
|
2023-01-07 23:06:02 +11:00
|
|
|
})
|
2023-06-20 22:53:46 +10:00
|
|
|
}))
|
2023-01-07 23:06:02 +11:00
|
|
|
}
|
|
|
|
|
2023-01-27 00:36:49 +11:00
|
|
|
pub fn npc_wander_tasks() -> Box<dyn Iterator<Item = StaticTask>> {
|
2023-06-20 22:53:46 +10:00
|
|
|
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(250..350) as u32,
|
|
|
|
}),
|
|
|
|
next_scheduled: Utc::now()
|
|
|
|
+ chrono::Duration::seconds(rng.gen_range(0..300) as i64),
|
|
|
|
..TaskMeta::default()
|
|
|
|
},
|
|
|
|
details: TaskDetails::NPCWander {
|
|
|
|
npc_code: c.code.to_owned(),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
)
|
2023-01-27 00:36:49 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn npc_aggro_tasks() -> Box<dyn Iterator<Item = StaticTask>> {
|
2023-06-20 22:53:46 +10:00
|
|
|
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(450..550) 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(),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
)
|
2023-01-27 00:36:49 +11:00
|
|
|
}
|
|
|
|
|
2023-01-07 23:06:02 +11:00
|
|
|
pub struct NPCSayTaskHandler;
|
|
|
|
#[async_trait]
|
|
|
|
impl TaskHandler for NPCSayTaskHandler {
|
|
|
|
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
|
|
|
|
let (npc_code, say_code) = match &ctx.task.details {
|
|
|
|
TaskDetails::NPCSay { npc_code, say_code } => (npc_code.clone(), say_code.clone()),
|
2023-06-20 22:53:46 +10:00
|
|
|
_ => Err("Expected NPC say task to be NPCSay type")?,
|
2023-01-07 23:06:02 +11:00
|
|
|
};
|
|
|
|
|
|
|
|
let say_info = match npc_say_info_by_npc_code_say_code().get(&(&npc_code, &say_code)) {
|
|
|
|
None => {
|
2023-06-20 22:53:46 +10:00
|
|
|
info!(
|
|
|
|
"NPCSayTaskHandler can't find NPCSayInfo for npc {} say_code {}",
|
|
|
|
npc_code, say_code
|
|
|
|
);
|
2023-01-07 23:06:02 +11:00
|
|
|
return Ok(None);
|
|
|
|
}
|
2023-06-20 22:53:46 +10:00
|
|
|
Some(r) => r,
|
2023-01-07 23:06:02 +11:00
|
|
|
};
|
|
|
|
let npc_item = match ctx.trans.find_item_by_type_code("npc", &npc_code).await? {
|
|
|
|
None => {
|
|
|
|
info!("NPCSayTaskHandler can't find NPC {}", npc_code);
|
|
|
|
return Ok(None);
|
|
|
|
}
|
2023-06-20 22:53:46 +10:00
|
|
|
Some(r) => r,
|
2023-01-07 23:06:02 +11:00
|
|
|
};
|
|
|
|
|
2023-05-25 22:51:52 +10:00
|
|
|
if npc_item.death_data.is_some() {
|
2023-02-22 21:43:18 +11:00
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
|
2023-01-07 23:06:02 +11:00
|
|
|
let (is_explicit, say_what) = match &say_info.talk_type {
|
|
|
|
NPCSayType::FromFixedList(l) => {
|
|
|
|
let mut rng = thread_rng();
|
|
|
|
match l[..].choose(&mut rng) {
|
|
|
|
None => {
|
2023-06-20 22:53:46 +10:00
|
|
|
info!(
|
|
|
|
"NPCSayTaskHandler NPCSayInfo for npc {} say_code {} has no choices",
|
|
|
|
npc_code, say_code
|
|
|
|
);
|
2023-01-07 23:06:02 +11:00
|
|
|
return Ok(None);
|
|
|
|
}
|
2023-06-20 22:53:46 +10:00
|
|
|
Some(r) => r.clone(),
|
2023-01-07 23:06:02 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2023-06-20 22:53:46 +10:00
|
|
|
|
|
|
|
match say_to_room(
|
|
|
|
ctx.trans,
|
|
|
|
&npc_item,
|
|
|
|
&npc_item.location,
|
|
|
|
say_what,
|
|
|
|
is_explicit,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
{
|
2023-01-07 23:06:02 +11:00
|
|
|
Ok(()) => {}
|
|
|
|
Err(CommandHandlingError::UserError(e)) => {
|
2023-06-20 22:53:46 +10:00
|
|
|
info!(
|
|
|
|
"NPCSayHandler couldn't send for npc {} say_code {}: {}",
|
|
|
|
npc_code, say_code, e
|
|
|
|
);
|
2023-01-07 23:06:02 +11:00
|
|
|
}
|
2023-06-20 22:53:46 +10:00
|
|
|
Err(CommandHandlingError::SystemError(e)) => Err(e)?,
|
2023-01-07 23:06:02 +11:00
|
|
|
}
|
|
|
|
Ok(None)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pub static SAY_HANDLER: &'static (dyn TaskHandler + Sync + Send) = &NPCSayTaskHandler;
|
2023-01-23 22:52:01 +11:00
|
|
|
|
2023-01-27 00:36:49 +11:00
|
|
|
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(),
|
2023-06-20 22:53:46 +10:00
|
|
|
_ => Err("Expected NPCWander type")?,
|
2023-01-27 00:36:49 +11:00
|
|
|
};
|
|
|
|
let npc = match npc_by_code().get(npc_code.as_str()) {
|
|
|
|
None => {
|
2023-06-20 22:53:46 +10:00
|
|
|
info!(
|
|
|
|
"NPC {} is gone / not yet in static items, ignoring in wander handler",
|
|
|
|
&npc_code
|
|
|
|
);
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
Some(r) => r,
|
2023-01-27 00:36:49 +11:00
|
|
|
};
|
|
|
|
let item = match ctx.trans.find_item_by_type_code("npc", &npc_code).await? {
|
|
|
|
None => {
|
2023-06-20 22:53:46 +10:00
|
|
|
info!(
|
|
|
|
"NPC {} is gone / not yet in DB, ignoring in wander handler",
|
|
|
|
&npc_code
|
|
|
|
);
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
Some(r) => r,
|
2023-01-27 00:36:49 +11:00
|
|
|
};
|
2023-04-23 22:31:31 +10:00
|
|
|
if item.death_data.is_some() {
|
2023-06-20 22:53:46 +10:00
|
|
|
return Ok(None);
|
2023-01-27 00:36:49 +11:00
|
|
|
}
|
|
|
|
let (ltype, lcode) = match item.location.split_once("/") {
|
|
|
|
None => return Ok(None),
|
2023-06-20 22:53:46 +10:00
|
|
|
Some(r) => r,
|
2023-01-27 00:36:49 +11:00
|
|
|
};
|
|
|
|
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);
|
2023-06-20 22:53:46 +10:00
|
|
|
}
|
|
|
|
Some(r) => r,
|
2023-01-27 00:36:49 +11:00
|
|
|
};
|
2023-06-20 22:53:46 +10:00
|
|
|
if !item.queue.is_empty() {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
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();
|
2023-01-27 00:36:49 +11:00
|
|
|
if let Some(dir) = dir_opt {
|
2023-06-20 22:53:46 +10:00
|
|
|
queue_command_for_npc_and_save(
|
|
|
|
&ctx.trans,
|
|
|
|
&item,
|
|
|
|
&QueueCommand::Movement {
|
|
|
|
direction: dir.clone(),
|
2023-06-30 23:46:38 +10:00
|
|
|
source: MovementSource::Command {
|
|
|
|
event_id: Uuid::new_v4(),
|
|
|
|
},
|
2023-06-20 22:53:46 +10:00
|
|
|
},
|
|
|
|
)
|
|
|
|
.await?;
|
2023-01-27 00:36:49 +11:00
|
|
|
}
|
|
|
|
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(),
|
2023-06-20 22:53:46 +10:00
|
|
|
_ => Err("Expected NPCAggro type")?,
|
2023-01-27 00:36:49 +11:00
|
|
|
};
|
|
|
|
let item = match ctx.trans.find_item_by_type_code("npc", &npc_code).await? {
|
|
|
|
None => {
|
2023-06-20 22:53:46 +10:00
|
|
|
info!(
|
|
|
|
"NPC {} is gone / not yet in DB, ignoring in aggro handler",
|
|
|
|
&npc_code
|
|
|
|
);
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
Some(r) => r,
|
2023-01-27 00:36:49 +11:00
|
|
|
};
|
2023-06-20 22:53:46 +10:00
|
|
|
if item.death_data.is_some()
|
|
|
|
|| item
|
|
|
|
.active_combat
|
|
|
|
.as_ref()
|
|
|
|
.map(|ac| ac.attacking.is_some())
|
|
|
|
.unwrap_or(false)
|
|
|
|
{
|
2023-01-27 00:36:49 +11:00
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
let items_loc = ctx.trans.find_items_by_location(&item.location).await?;
|
|
|
|
let vic_opt = items_loc
|
|
|
|
.iter()
|
2023-06-20 22:53:46 +10:00
|
|
|
.filter(|it| {
|
|
|
|
(it.item_type == "player" || it.item_type == "npc")
|
|
|
|
&& it.death_data.is_none()
|
2023-07-06 22:34:01 +10:00
|
|
|
&& !it.flags.contains(&ItemFlag::NPCsDontAttack)
|
2023-06-20 22:53:46 +10:00
|
|
|
&& (it.item_type != item.item_type || it.item_code != item.item_code)
|
|
|
|
})
|
2023-01-27 00:36:49 +11:00
|
|
|
.choose(&mut thread_rng());
|
|
|
|
if let Some(victim) = vic_opt {
|
|
|
|
match start_attack(ctx.trans, &item, victim).await {
|
|
|
|
Ok(()) | Err(CommandHandlingError::UserError(_)) => {}
|
2023-06-20 22:53:46 +10:00
|
|
|
Err(CommandHandlingError::SystemError(e)) => Err(e)?,
|
2023-01-27 00:36:49 +11:00
|
|
|
}
|
|
|
|
}
|
2023-06-20 22:53:46 +10:00
|
|
|
|
2023-01-27 00:36:49 +11:00
|
|
|
Ok(None)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pub static AGGRO_HANDLER: &'static (dyn TaskHandler + Sync + Send) = &NPCAggroTaskHandler;
|
|
|
|
|
2023-01-23 22:52:01 +11:00
|
|
|
pub struct NPCRecloneTaskHandler;
|
|
|
|
#[async_trait]
|
|
|
|
impl TaskHandler for NPCRecloneTaskHandler {
|
|
|
|
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
|
|
|
|
let npc_code = match &ctx.task.details {
|
|
|
|
TaskDetails::RecloneNPC { npc_code } => npc_code.clone(),
|
2023-06-20 22:53:46 +10:00
|
|
|
_ => Err("Expected RecloneNPC type")?,
|
2023-01-23 22:52:01 +11:00
|
|
|
};
|
|
|
|
let mut npc_item = match ctx.trans.find_item_by_type_code("npc", &npc_code).await? {
|
2023-06-20 22:53:46 +10:00
|
|
|
None => return Ok(None),
|
|
|
|
Some(r) => (*r).clone(),
|
2023-01-23 22:52:01 +11:00
|
|
|
};
|
|
|
|
|
|
|
|
let npc = match npc_by_code().get(npc_code.as_str()) {
|
2023-06-20 22:53:46 +10:00
|
|
|
None => return Ok(None),
|
|
|
|
Some(r) => r,
|
2023-01-23 22:52:01 +11:00
|
|
|
};
|
2023-06-20 22:53:46 +10:00
|
|
|
|
2023-04-23 22:31:31 +10:00
|
|
|
if npc_item.death_data.is_none() {
|
2023-01-23 22:52:01 +11:00
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
|
|
|
|
corpsify_item(ctx.trans, &npc_item).await?;
|
|
|
|
|
2023-04-23 22:31:31 +10:00
|
|
|
npc_item.death_data = None;
|
2023-02-23 22:55:02 +11:00
|
|
|
npc_item.health = npc.max_health;
|
2023-01-23 22:52:01 +11:00
|
|
|
npc_item.location = npc.spawn_location.to_owned();
|
|
|
|
ctx.trans.save_item_model(&npc_item).await?;
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pub static RECLONE_HANDLER: &'static (dyn TaskHandler + Sync + Send) = &NPCRecloneTaskHandler;
|