Implement more combat capability.
This commit is contained in:
parent
c26a4768c5
commit
09db1a6ed9
@ -4,22 +4,87 @@ use async_trait::async_trait;
|
|||||||
use ansi::ansi;
|
use ansi::ansi;
|
||||||
use std::time;
|
use std::time;
|
||||||
use crate::{
|
use crate::{
|
||||||
services::broadcast_to_room,
|
services::{broadcast_to_room, skills::skill_check_and_grind},
|
||||||
db::{DBTrans, ItemSearchParams},
|
db::{DBTrans, ItemSearchParams},
|
||||||
models::{item::{Item, LocationActionType, Subattack}},
|
models::{
|
||||||
|
item::{Item, LocationActionType, Subattack, SkillType},
|
||||||
|
task::{Task, TaskMeta, TaskDetails}
|
||||||
|
},
|
||||||
|
static_content::{
|
||||||
|
possession_type::{WeaponData, BodyPart, possession_data, fist},
|
||||||
|
npc::npc_by_code,
|
||||||
|
},
|
||||||
regular_tasks::{TaskRunContext, TaskHandler},
|
regular_tasks::{TaskRunContext, TaskHandler},
|
||||||
DResult
|
DResult
|
||||||
};
|
};
|
||||||
use async_recursion::async_recursion;
|
use async_recursion::async_recursion;
|
||||||
|
use chrono::Utc;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct AttackTaskHandler;
|
pub struct AttackTaskHandler;
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl TaskHandler for AttackTaskHandler {
|
impl TaskHandler for AttackTaskHandler {
|
||||||
async fn do_task(&self, _ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
|
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
|
||||||
todo!("AttackTaskHandler");
|
let (ctype, ccode) = ctx.task.meta.task_code.split_once("/")
|
||||||
|
.ok_or("Invalid AttackTick task code")?;
|
||||||
|
let mut attacker_item = match ctx.trans.find_item_by_type_code(ctype, ccode).await? {
|
||||||
|
None => { return Ok(None); } // Player is gone
|
||||||
|
Some(item) => (*item).clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (vtype, vcode) =
|
||||||
|
match attacker_item.active_combat.as_ref().and_then(|ac| ac.attacking.as_ref()).and_then(|v| v.split_once("/")) {
|
||||||
|
None => return Ok(None),
|
||||||
|
Some(x) => x
|
||||||
|
};
|
||||||
|
let mut victim_item = match ctx.trans.find_item_by_type_code(vtype, vcode).await? {
|
||||||
|
None => { return Ok(None); }
|
||||||
|
Some(item) => (*item).clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let weapon = what_wielded(ctx.trans, &attacker_item).await?;
|
||||||
|
|
||||||
|
let attack_skill = *attacker_item.total_skills.get(&weapon.uses_skill).unwrap_or(&0.0);
|
||||||
|
let victim_dodge_skill = *victim_item.total_skills.get(&SkillType::Dodge).unwrap_or(&0.0);
|
||||||
|
|
||||||
|
let dodge_result = skill_check_and_grind(ctx.trans, &mut victim_item, &SkillType::Dodge,
|
||||||
|
attack_skill).await?;
|
||||||
|
let attack_result = skill_check_and_grind(ctx.trans, &mut attacker_item, &weapon.uses_skill,
|
||||||
|
victim_dodge_skill).await?;
|
||||||
|
|
||||||
|
if dodge_result > attack_result {
|
||||||
|
let msg_exp = format!("{} dodges out of the way of {}'s attack.\n",
|
||||||
|
victim_item.display_for_sentence(true, 1, true),
|
||||||
|
attacker_item.display_for_sentence(true, 1, false));
|
||||||
|
let msg_nonexp = format!("{} dodges out of the way of {}'s attack.\n",
|
||||||
|
victim_item.display_for_sentence(false, 1, true),
|
||||||
|
attacker_item.display_for_sentence(false, 1, false));
|
||||||
|
broadcast_to_room(ctx.trans, &attacker_item.location, None, &msg_exp, Some(&msg_nonexp)).await?;
|
||||||
|
} else {
|
||||||
|
// TODO: Parry system of some kind?
|
||||||
|
|
||||||
|
// Determine body part...
|
||||||
|
let part = BodyPart::sample();
|
||||||
|
|
||||||
|
// TODO: Armour / soaks
|
||||||
|
|
||||||
|
// TODO: Calculate damage etc... and display health impact.
|
||||||
|
let msg_exp = weapon.normal_attack_success_message(&attacker_item, &victim_item, &part, true) + ".\n";
|
||||||
|
let msg_nonexp = weapon.normal_attack_success_message(&attacker_item, &victim_item, &part, false) + ".\n";
|
||||||
|
broadcast_to_room(ctx.trans, &attacker_item.location, None, &msg_exp, Some(&msg_nonexp)).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg_exp = &(weapon.normal_attack_start_message(&attacker_item, &victim_item, true) + ".\n");
|
||||||
|
let msg_nonexp = &(weapon.normal_attack_start_message(&attacker_item, &victim_item, false) + ".\n");
|
||||||
|
broadcast_to_room(ctx.trans, &attacker_item.location, None, msg_exp, Some(msg_nonexp)).await?;
|
||||||
|
ctx.trans.save_item_model(&attacker_item).await?;
|
||||||
|
ctx.trans.save_item_model(&victim_item).await?;
|
||||||
|
Ok(Some(attack_speed(&attacker_item)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub static TASK_HANDLER: &(dyn TaskHandler + Sync + Send) = &AttackTaskHandler;
|
||||||
|
|
||||||
pub async fn stop_attacking(trans: &DBTrans, by_whom: &Item, to_whom: &Item) -> UResult<()> {
|
pub async fn stop_attacking(trans: &DBTrans, by_whom: &Item, to_whom: &Item) -> UResult<()> {
|
||||||
let mut new_to_whom = (*to_whom).clone();
|
let mut new_to_whom = (*to_whom).clone();
|
||||||
if let Some(ac) = new_to_whom.active_combat.as_mut() {
|
if let Some(ac) = new_to_whom.active_combat.as_mut() {
|
||||||
@ -37,6 +102,23 @@ pub async fn stop_attacking(trans: &DBTrans, by_whom: &Item, to_whom: &Item) ->
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn what_wielded(_trans: &DBTrans, who: &Item) -> DResult<&'static WeaponData> {
|
||||||
|
// TODO: Search inventory for wielded item first.
|
||||||
|
if who.item_type == "npc" {
|
||||||
|
if let Some(intrinsic) = npc_by_code().get(who.item_code.as_str())
|
||||||
|
.and_then(|npc| npc.intrinsic_weapon.as_ref()) {
|
||||||
|
if let Some(weapon) = possession_data().get(intrinsic).and_then(|p| p.weapon_data.as_ref()) {
|
||||||
|
return Ok(weapon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(fist())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attack_speed(_who: &Item) -> time::Duration {
|
||||||
|
time::Duration::from_secs(5)
|
||||||
|
}
|
||||||
|
|
||||||
#[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 msg_exp = String::new();
|
let mut msg_exp = String::new();
|
||||||
@ -63,6 +145,7 @@ pub async fn start_attack(trans: &DBTrans, by_whom: &Item, to_whom: &Item) -> UR
|
|||||||
},
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
msg_exp.push_str(&format!(
|
msg_exp.push_str(&format!(
|
||||||
ansi!("<red>{} {} {}.<reset>\n"),
|
ansi!("<red>{} {} {}.<reset>\n"),
|
||||||
&by_whom.display_for_sentence(true, 1, true),
|
&by_whom.display_for_sentence(true, 1, true),
|
||||||
@ -75,6 +158,11 @@ pub async fn start_attack(trans: &DBTrans, by_whom: &Item, to_whom: &Item) -> UR
|
|||||||
verb,
|
verb,
|
||||||
&to_whom.display_for_sentence(false, 1, false))
|
&to_whom.display_for_sentence(false, 1, false))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let wielded = what_wielded(trans, by_whom).await?;
|
||||||
|
msg_exp.push_str(&(wielded.normal_attack_start_message(by_whom, to_whom, true) + ".\n"));
|
||||||
|
msg_nonexp.push_str(&(wielded.normal_attack_start_message(by_whom, to_whom, false) + ".\n"));
|
||||||
|
|
||||||
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();
|
let mut by_whom_for_update = by_whom.clone();
|
||||||
@ -88,6 +176,15 @@ pub async fn start_attack(trans: &DBTrans, by_whom: &Item, to_whom: &Item) -> UR
|
|||||||
&by_whom.item_type, &by_whom.item_code)
|
&by_whom.item_type, &by_whom.item_code)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
trans.upsert_task(&Task {
|
||||||
|
meta: TaskMeta {
|
||||||
|
task_code: format!("{}/{}", by_whom.item_type, by_whom.item_code),
|
||||||
|
next_scheduled: Utc::now() + chrono::Duration::milliseconds(
|
||||||
|
attack_speed(by_whom).as_millis() as i64),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
details: TaskDetails::AttackTick
|
||||||
|
}).await?;
|
||||||
trans.save_item_model(&by_whom_for_update).await?;
|
trans.save_item_model(&by_whom_for_update).await?;
|
||||||
trans.save_item_model(&to_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.
|
||||||
@ -95,7 +192,7 @@ pub async fn start_attack(trans: &DBTrans, by_whom: &Item, to_whom: &Item) -> UR
|
|||||||
start_attack(trans, &to_whom_for_update, &by_whom_for_update).await?;
|
start_attack(trans, &to_whom_for_update, &by_whom_for_update).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Verb;
|
pub struct Verb;
|
||||||
@ -120,7 +217,7 @@ impl UserVerb for Verb {
|
|||||||
|
|
||||||
if attack_whom.is_challenge_attack_only {
|
if attack_whom.is_challenge_attack_only {
|
||||||
// Add challenge check here.
|
// Add challenge check here.
|
||||||
user_error(ansi!("<blue>Your wristpad vibrates and blocks you from doing that.<reset> You get a feeling that while the empire might be gone, the system to stop subjects with working wristpads from fighting each unless they have an accepted challenge very much functional. [Try <bold>help challenge<reset>]").to_string())?
|
user_error(ansi!("<blue>Your wristpad vibrates and blocks you from doing that.<reset> You get a feeling that while the empire might be gone, the system to stop subjects with working wristpads from fighting each unless they have an accepted challenge is very much functional. [Try <bold>help challenge<reset>]").to_string())?
|
||||||
}
|
}
|
||||||
|
|
||||||
start_attack(&ctx.trans, &player_item, &attack_whom).await
|
start_attack(&ctx.trans, &player_item, &attack_whom).await
|
||||||
|
@ -14,7 +14,8 @@ pub enum TaskDetails {
|
|||||||
NPCSay {
|
NPCSay {
|
||||||
npc_code: String,
|
npc_code: String,
|
||||||
say_code: String
|
say_code: String
|
||||||
}
|
},
|
||||||
|
AttackTick
|
||||||
}
|
}
|
||||||
impl TaskDetails {
|
impl TaskDetails {
|
||||||
pub fn name(self: &Self) -> &'static str {
|
pub fn name(self: &Self) -> &'static str {
|
||||||
@ -22,6 +23,7 @@ impl TaskDetails {
|
|||||||
match self {
|
match self {
|
||||||
RunQueuedCommand => "RunQueuedCommand",
|
RunQueuedCommand => "RunQueuedCommand",
|
||||||
NPCSay { .. } => "NPCSay",
|
NPCSay { .. } => "NPCSay",
|
||||||
|
AttackTick => "AttackTick"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
use tokio::{task, time, sync::oneshot};
|
use tokio::{task, time, sync::oneshot};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use crate::{DResult, db, models::task::{Task, TaskParse, TaskRecurrence}};
|
use crate::{
|
||||||
use crate::listener::{ListenerMap, ListenerSend};
|
DResult,
|
||||||
|
db,
|
||||||
|
models::task::{Task, TaskParse, TaskRecurrence},
|
||||||
|
listener::{ListenerMap, ListenerSend},
|
||||||
|
static_content::npc,
|
||||||
|
message_handler::user_commands::attack,
|
||||||
|
};
|
||||||
use blastmud_interfaces::MessageToListener;
|
use blastmud_interfaces::MessageToListener;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use std::ops::AddAssign;
|
use std::ops::AddAssign;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use crate::static_content::npc;
|
|
||||||
|
|
||||||
pub mod queued_command;
|
pub mod queued_command;
|
||||||
|
|
||||||
@ -29,6 +34,7 @@ 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()),
|
||||||
|
("AttackTick", attack::TASK_HANDLER.clone())
|
||||||
).into_iter().collect()
|
).into_iter().collect()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ use log::info;
|
|||||||
|
|
||||||
pub mod room;
|
pub mod room;
|
||||||
pub mod npc;
|
pub mod npc;
|
||||||
|
pub mod possession_type;
|
||||||
mod fixed_item;
|
mod fixed_item;
|
||||||
|
|
||||||
pub struct StaticItem {
|
pub struct StaticItem {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use super::{StaticItem, StaticTask};
|
use super::{StaticItem, StaticTask, possession_type::PossessionType};
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
item::{Item, Pronouns},
|
item::{Item, Pronouns, SkillType},
|
||||||
task::{Task, TaskMeta, TaskRecurrence, TaskDetails}
|
task::{Task, TaskMeta, TaskRecurrence, TaskDetails}
|
||||||
};
|
};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
@ -54,7 +54,9 @@ pub struct NPC {
|
|||||||
pub message_handler: Option<&'static (dyn NPCMessageHandler + Sync + Send)>,
|
pub message_handler: Option<&'static (dyn NPCMessageHandler + Sync + Send)>,
|
||||||
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 intrinsic_weapon: Option<PossessionType>,
|
||||||
|
pub total_skills: BTreeMap<SkillType, f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NPC {
|
impl Default for NPC {
|
||||||
@ -68,7 +70,9 @@ impl Default for NPC {
|
|||||||
message_handler: None,
|
message_handler: None,
|
||||||
aliases: vec!(),
|
aliases: vec!(),
|
||||||
says: vec!(),
|
says: vec!(),
|
||||||
attackable: false
|
total_skills: SkillType::values().into_iter().map(|sk| (sk, 8.0)).collect(),
|
||||||
|
attackable: false,
|
||||||
|
intrinsic_weapon: None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,6 +130,7 @@ pub fn npc_static_items() -> Box<dyn Iterator<Item = StaticItem>> {
|
|||||||
is_static: true,
|
is_static: true,
|
||||||
pronouns: c.pronouns.clone(),
|
pronouns: c.pronouns.clone(),
|
||||||
is_challenge_attack_only: !c.attackable,
|
is_challenge_attack_only: !c.attackable,
|
||||||
|
total_skills: c.total_skills.clone(),
|
||||||
aliases: c.aliases.iter().map(|a| (*a).to_owned()).collect::<Vec<String>>(),
|
aliases: c.aliases.iter().map(|a| (*a).to_owned()).collect::<Vec<String>>(),
|
||||||
..Item::default()
|
..Item::default()
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use super::NPC;
|
use super::NPC;
|
||||||
use crate::models::item::Pronouns;
|
use crate::models::item::Pronouns;
|
||||||
|
use crate::static_content::possession_type::PossessionType;
|
||||||
|
|
||||||
macro_rules! dog {
|
macro_rules! dog {
|
||||||
($code:expr, $adj:expr, $spawn: expr) => {
|
($code:expr, $adj:expr, $spawn: expr) => {
|
||||||
@ -11,6 +12,7 @@ macro_rules! dog {
|
|||||||
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),
|
||||||
|
intrinsic_weapon: Some(PossessionType::Fangs),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
201
blastmud_game/src/static_content/possession_type.rs
Normal file
201
blastmud_game/src/static_content/possession_type.rs
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use crate::{
|
||||||
|
models::item::{SkillType, Item, Sex}
|
||||||
|
};
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use rand::seq::SliceRandom;
|
||||||
|
use rand::seq::IteratorRandom;
|
||||||
|
|
||||||
|
pub type AttackMessageChoice = Vec<Box<dyn Fn(&Item, &Item, bool) -> String + 'static + Sync + Send>>;
|
||||||
|
pub type AttackMessageChoicePart = Vec<Box<dyn Fn(&Item, &Item, &BodyPart, bool) -> String + 'static + Sync + Send>>;
|
||||||
|
|
||||||
|
#[derive(Eq, Ord, Clone, PartialEq, PartialOrd, Debug)]
|
||||||
|
pub enum BodyPart {
|
||||||
|
Head,
|
||||||
|
Face,
|
||||||
|
Chest,
|
||||||
|
Back,
|
||||||
|
Groin,
|
||||||
|
Arms,
|
||||||
|
Feet
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BodyPart {
|
||||||
|
pub fn display(&self, sex: Option<Sex>) -> &'static str {
|
||||||
|
use BodyPart::*;
|
||||||
|
match self {
|
||||||
|
Head => "head",
|
||||||
|
Face => "face",
|
||||||
|
Chest => match sex {
|
||||||
|
Some(Sex::Female) => "breasts",
|
||||||
|
_ => "chest",
|
||||||
|
},
|
||||||
|
Back => "back",
|
||||||
|
Groin => match sex {
|
||||||
|
Some(Sex::Male) => "penis",
|
||||||
|
Some(Sex::Female) => "vagina",
|
||||||
|
_ => "groin"
|
||||||
|
},
|
||||||
|
Arms => "arms",
|
||||||
|
Feet => "feet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn items() -> Vec<Self> {
|
||||||
|
use BodyPart::*;
|
||||||
|
vec!(
|
||||||
|
Head,
|
||||||
|
Face,
|
||||||
|
Chest,
|
||||||
|
Back,
|
||||||
|
Groin,
|
||||||
|
Arms,
|
||||||
|
Feet
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample() -> Self {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
Self::items().into_iter().choose(&mut rng).unwrap_or(BodyPart::Head)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WeaponData {
|
||||||
|
pub uses_skill: SkillType,
|
||||||
|
pub raw_min_to_learn: f64,
|
||||||
|
pub raw_max_to_learn: f64,
|
||||||
|
pub normal_attack_start_messages: AttackMessageChoice,
|
||||||
|
pub normal_attack_success_messages: AttackMessageChoicePart,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WeaponData {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
uses_skill: SkillType::Blades,
|
||||||
|
raw_min_to_learn: 0.0,
|
||||||
|
raw_max_to_learn: 15.0,
|
||||||
|
normal_attack_start_messages:
|
||||||
|
vec!(Box::new(|attacker, victim, exp| format!(
|
||||||
|
"{} makes an attack on {}",
|
||||||
|
&attacker.display_for_sentence(exp, 1, true),
|
||||||
|
&victim.display_for_sentence(exp,1, false)))),
|
||||||
|
normal_attack_success_messages:
|
||||||
|
vec!(Box::new(|attacker, victim, part, exp|
|
||||||
|
format!("{}'s attack on {} hits {} {}",
|
||||||
|
&attacker.display_for_sentence(exp, 1, true),
|
||||||
|
&victim.display_for_sentence(exp, 1, false),
|
||||||
|
&victim.pronouns.possessive,
|
||||||
|
part.display(victim.sex.clone())
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PossessionData {
|
||||||
|
pub weapon_data: Option<WeaponData>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PossessionData {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
weapon_data: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WeaponData {
|
||||||
|
pub fn normal_attack_start_message(
|
||||||
|
&self,
|
||||||
|
attacker: &Item, victim: &Item, explicit_ok: bool) -> String {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
self.normal_attack_start_messages[..].choose(&mut rng).map(
|
||||||
|
|f| f(attacker, victim, explicit_ok)).unwrap_or(
|
||||||
|
"No message defined yet".to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn normal_attack_success_message(
|
||||||
|
&self, attacker: &Item, victim: &Item,
|
||||||
|
part: &BodyPart, explicit_ok: bool
|
||||||
|
) -> String {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
self.normal_attack_success_messages[..].choose(&mut rng).map(
|
||||||
|
|f| f(attacker, victim, part, explicit_ok)).unwrap_or(
|
||||||
|
"No message defined yet".to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum PossessionType {
|
||||||
|
// Special values that substitute for possessions.
|
||||||
|
Fangs, // Default weapon for certain animals
|
||||||
|
// Real possessions from here on:
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fist() -> &'static WeaponData {
|
||||||
|
static FIST_WEAPON: OnceCell<WeaponData> = OnceCell::new();
|
||||||
|
FIST_WEAPON.get_or_init(|| {
|
||||||
|
WeaponData {
|
||||||
|
uses_skill: SkillType::Fists,
|
||||||
|
raw_min_to_learn: 0.0,
|
||||||
|
raw_max_to_learn: 2.0,
|
||||||
|
normal_attack_start_messages: vec!(
|
||||||
|
Box::new(|attacker, victim, exp|
|
||||||
|
format!("{} swings at {} with {} fists",
|
||||||
|
&attacker.display_for_sentence(exp, 1, true),
|
||||||
|
&victim.display_for_sentence(exp, 1, false),
|
||||||
|
&attacker.pronouns.possessive
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
normal_attack_success_messages: vec!(
|
||||||
|
Box::new(|attacker, victim, part, exp|
|
||||||
|
format!("{}'s fists smash into {}'s {}",
|
||||||
|
&attacker.display_for_sentence(exp, 1, true),
|
||||||
|
&victim.display_for_sentence(exp, 1, false),
|
||||||
|
&part.display(victim.sex.clone())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn possession_data() -> &'static BTreeMap<PossessionType, PossessionData> {
|
||||||
|
static POSSESSION_DATA: OnceCell<BTreeMap<PossessionType, PossessionData>> = OnceCell::new();
|
||||||
|
use PossessionType::*;
|
||||||
|
&POSSESSION_DATA.get_or_init(|| {
|
||||||
|
vec!(
|
||||||
|
(Fangs, PossessionData {
|
||||||
|
weapon_data: Some(WeaponData {
|
||||||
|
uses_skill: SkillType::Fists,
|
||||||
|
raw_min_to_learn: 0.0,
|
||||||
|
raw_max_to_learn: 2.0,
|
||||||
|
normal_attack_start_messages: vec!(
|
||||||
|
Box::new(|attacker, victim, exp|
|
||||||
|
format!("{} bares {} teeth and lunges at {}",
|
||||||
|
&attacker.display_for_sentence(exp, 1, true),
|
||||||
|
&attacker.pronouns.possessive,
|
||||||
|
&victim.display_for_sentence(exp, 1, false),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
normal_attack_success_messages: vec!(
|
||||||
|
Box::new(|attacker, victim, part, exp|
|
||||||
|
format!("{}'s teeth connect and tear at the flesh of {}'s {}",
|
||||||
|
&attacker.display_for_sentence(exp, 1, true),
|
||||||
|
&victim.display_for_sentence(exp, 1, false),
|
||||||
|
&part.display(victim.sex.clone())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
).into_iter().collect()
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user