Give some weapons a chance at causing crit status effects.
This commit is contained in:
parent
2350e22f5f
commit
6ac3f676be
@ -1122,6 +1122,14 @@ impl DBTrans {
|
|||||||
.get(0))
|
.get(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn alloc_task_code(&self) -> DResult<i64> {
|
||||||
|
Ok(self
|
||||||
|
.pg_trans()?
|
||||||
|
.query_one("SELECT NEXTVAL('task_seq')", &[])
|
||||||
|
.await?
|
||||||
|
.get(0))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_online_info(&self) -> DResult<Vec<OnlineInfo>> {
|
pub async fn get_online_info(&self) -> DResult<Vec<OnlineInfo>> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.pg_trans()?
|
.pg_trans()?
|
||||||
|
@ -228,7 +228,11 @@ pub async fn describe_normal_item(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.active_effects.contains(&EffectType::Bandages) {
|
if item
|
||||||
|
.active_effects
|
||||||
|
.iter()
|
||||||
|
.any(|e| e.0 == EffectType::Bandages)
|
||||||
|
{
|
||||||
contents_desc.push_str(&format!(
|
contents_desc.push_str(&format!(
|
||||||
"{} is wrapped up in bandages.\n",
|
"{} is wrapped up in bandages.\n",
|
||||||
&language::caps_first(&item.pronouns.subject)
|
&language::caps_first(&item.pronouns.subject)
|
||||||
|
@ -252,9 +252,8 @@ impl QueueCommandHandler for QueueHandler {
|
|||||||
&actual_effects,
|
&actual_effects,
|
||||||
ctx.item,
|
ctx.item,
|
||||||
&item,
|
&item,
|
||||||
&mut target_mut,
|
target_mut.as_mut(),
|
||||||
skilllvl,
|
skilllvl,
|
||||||
use_data.task_ref,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
pub enum EffectType {
|
pub enum EffectType {
|
||||||
Ephemeral, // i.e. no enduring impact to show in status.
|
Ephemeral, // i.e. no enduring impact to show in status.
|
||||||
Bandages,
|
Bandages,
|
||||||
|
Bleed,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EffectSet {
|
pub struct EffectSet {
|
||||||
|
@ -495,7 +495,7 @@ pub struct Item {
|
|||||||
pub queue: VecDeque<QueueCommand>,
|
pub queue: VecDeque<QueueCommand>,
|
||||||
pub urges: Option<Urges>,
|
pub urges: Option<Urges>,
|
||||||
pub liquid_details: Option<LiquidDetails>,
|
pub liquid_details: Option<LiquidDetails>,
|
||||||
pub active_effects: Vec<EffectType>,
|
pub active_effects: Vec<(EffectType, i64)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Item {
|
impl Item {
|
||||||
|
@ -35,7 +35,9 @@ use chrono::Utc;
|
|||||||
use mockall_double::double;
|
use mockall_double::double;
|
||||||
use rand::{prelude::IteratorRandom, Rng};
|
use rand::{prelude::IteratorRandom, Rng};
|
||||||
use rand_distr::{Distribution, Normal};
|
use rand_distr::{Distribution, Normal};
|
||||||
use std::time;
|
use std::{sync::Arc, time};
|
||||||
|
|
||||||
|
use super::effect::{default_effects_for_type, run_effects};
|
||||||
|
|
||||||
pub async fn soak_damage<DamageDist: DamageDistribution>(
|
pub async fn soak_damage<DamageDist: DamageDistribution>(
|
||||||
trans: &DBTrans,
|
trans: &DBTrans,
|
||||||
@ -129,6 +131,7 @@ pub async fn soak_damage<DamageDist: DamageDistribution>(
|
|||||||
async fn process_attack(
|
async fn process_attack(
|
||||||
ctx: &mut TaskRunContext<'_>,
|
ctx: &mut TaskRunContext<'_>,
|
||||||
attacker_item: &mut Item,
|
attacker_item: &mut Item,
|
||||||
|
weapon_item: &Item,
|
||||||
victim_item: &mut Item,
|
victim_item: &mut Item,
|
||||||
attack: &WeaponAttackData,
|
attack: &WeaponAttackData,
|
||||||
weapon: &WeaponData,
|
weapon: &WeaponData,
|
||||||
@ -226,8 +229,6 @@ async fn process_attack(
|
|||||||
// Determine body part...
|
// Determine body part...
|
||||||
let part = victim_item.species.sample_body_part();
|
let part = victim_item.species.sample_body_part();
|
||||||
|
|
||||||
// TODO: Armour / soaks
|
|
||||||
|
|
||||||
let mut mean_damage: f64 = attack.mean_damage;
|
let mut mean_damage: f64 = attack.mean_damage;
|
||||||
for scaling in attack.skill_scaling.iter() {
|
for scaling in attack.skill_scaling.iter() {
|
||||||
let skill = *attacker_item
|
let skill = *attacker_item
|
||||||
@ -287,6 +288,29 @@ async fn process_attack(
|
|||||||
ctx.trans.save_item_model(victim_item).await?;
|
ctx.trans.save_item_model(victim_item).await?;
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Consider applying a crit effect...
|
||||||
|
let mut crit_rand = rand::thread_rng().gen::<f64>();
|
||||||
|
for (p, eff_type) in &attack.crit_effects {
|
||||||
|
if victim_item.active_effects.iter().any(|e| e.0 == *eff_type) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if *p >= crit_rand {
|
||||||
|
if let Some(effect_set) = default_effects_for_type().get(eff_type) {
|
||||||
|
run_effects(
|
||||||
|
ctx.trans,
|
||||||
|
&effect_set,
|
||||||
|
attacker_item,
|
||||||
|
weapon_item,
|
||||||
|
Some(victim_item),
|
||||||
|
0.0,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
crit_rand -= *p;
|
||||||
|
}
|
||||||
|
}
|
||||||
ctx.trans.save_item_model(victim_item).await?;
|
ctx.trans.save_item_model(victim_item).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,11 +338,11 @@ impl TaskHandler for AttackTaskHandler {
|
|||||||
.task_code
|
.task_code
|
||||||
.split_once("/")
|
.split_once("/")
|
||||||
.ok_or("Invalid AttackTick task code")?;
|
.ok_or("Invalid AttackTick task code")?;
|
||||||
let mut attacker_item = match ctx.trans.find_item_by_type_code(ctype, ccode).await? {
|
let attacker_item = match ctx.trans.find_item_by_type_code(ctype, ccode).await? {
|
||||||
None => {
|
None => {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
} // Player is gone
|
} // Player is gone
|
||||||
Some(item) => (*item).clone(),
|
Some(item) => item,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (vtype, vcode) = match attacker_item
|
let (vtype, vcode) = match attacker_item
|
||||||
@ -341,11 +365,13 @@ impl TaskHandler for AttackTaskHandler {
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let weapon = what_wielded(ctx.trans, &attacker_item).await?;
|
let (weapon_it, weapon) = what_wielded(ctx.trans, &attacker_item).await?;
|
||||||
|
|
||||||
|
let mut attacker_item_mut = (*attacker_item).clone();
|
||||||
process_attack(
|
process_attack(
|
||||||
ctx,
|
ctx,
|
||||||
&mut attacker_item,
|
&mut attacker_item_mut,
|
||||||
|
&weapon_it,
|
||||||
&mut victim_item,
|
&mut victim_item,
|
||||||
&weapon.normal_attack,
|
&weapon.normal_attack,
|
||||||
&weapon,
|
&weapon,
|
||||||
@ -355,7 +381,7 @@ impl TaskHandler for AttackTaskHandler {
|
|||||||
// We re-check this on the next tick, rather than going off if the victim
|
// We re-check this on the next tick, rather than going off if the victim
|
||||||
// died. That prevents a bug when re-focusing where we re-schedule and then
|
// died. That prevents a bug when re-focusing where we re-schedule and then
|
||||||
// re-delete the task.
|
// re-delete the task.
|
||||||
Ok(Some(attack_speed(&attacker_item)))
|
Ok(Some(attack_speed(&attacker_item_mut)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -685,7 +711,10 @@ 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> {
|
async fn what_wielded(
|
||||||
|
trans: &DBTrans,
|
||||||
|
who: &Arc<Item>,
|
||||||
|
) -> DResult<(Arc<Item>, &'static WeaponData)> {
|
||||||
if let Some(item) = trans
|
if let Some(item) = trans
|
||||||
.find_by_action_and_location(&who.refstr(), &LocationActionType::Wielded)
|
.find_by_action_and_location(&who.refstr(), &LocationActionType::Wielded)
|
||||||
.await?
|
.await?
|
||||||
@ -697,7 +726,7 @@ async fn what_wielded(trans: &DBTrans, who: &Item) -> DResult<&'static WeaponDat
|
|||||||
.and_then(|pt| possession_data().get(&pt))
|
.and_then(|pt| possession_data().get(&pt))
|
||||||
.and_then(|pd| pd.weapon_data.as_ref())
|
.and_then(|pd| pd.weapon_data.as_ref())
|
||||||
{
|
{
|
||||||
return Ok(dat);
|
return Ok((item.clone(), dat));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -711,11 +740,11 @@ async fn what_wielded(trans: &DBTrans, who: &Item) -> DResult<&'static WeaponDat
|
|||||||
.get(intrinsic)
|
.get(intrinsic)
|
||||||
.and_then(|p| p.weapon_data.as_ref())
|
.and_then(|p| p.weapon_data.as_ref())
|
||||||
{
|
{
|
||||||
return Ok(weapon);
|
return Ok((who.clone(), weapon));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(fist())
|
Ok((who.clone(), fist()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn attack_speed(_who: &Item) -> time::Duration {
|
fn attack_speed(_who: &Item) -> time::Duration {
|
||||||
@ -786,7 +815,7 @@ pub async fn start_attack_mut(
|
|||||||
&to_whom.display_for_sentence(false, 1, false)
|
&to_whom.display_for_sentence(false, 1, false)
|
||||||
));
|
));
|
||||||
|
|
||||||
let wielded = what_wielded(trans, by_whom).await?;
|
let (_, wielded) = what_wielded(trans, &Arc::new(by_whom.clone())).await?;
|
||||||
msg_exp.push_str(&(wielded.normal_attack.start_message(by_whom, to_whom, true) + ".\n"));
|
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"));
|
msg_nonexp.push_str(&(wielded.normal_attack.start_message(by_whom, to_whom, false) + ".\n"));
|
||||||
|
|
||||||
|
@ -8,12 +8,15 @@ use crate::{
|
|||||||
task::{Task, TaskDetails, TaskMeta},
|
task::{Task, TaskDetails, TaskMeta},
|
||||||
},
|
},
|
||||||
regular_tasks::{TaskHandler, TaskRunContext},
|
regular_tasks::{TaskHandler, TaskRunContext},
|
||||||
|
static_content::species::SpeciesType,
|
||||||
DResult,
|
DResult,
|
||||||
};
|
};
|
||||||
|
use ansi::ansi;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use log::info;
|
use log::info;
|
||||||
use mockall_double::double;
|
use mockall_double::double;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::{BTreeMap, VecDeque};
|
use std::collections::{BTreeMap, VecDeque};
|
||||||
use std::time;
|
use std::time;
|
||||||
@ -183,7 +186,7 @@ impl TaskHandler for DispelEffectTaskHandler {
|
|||||||
target_mut
|
target_mut
|
||||||
.active_effects
|
.active_effects
|
||||||
.iter()
|
.iter()
|
||||||
.position(|e| e == effect_type)
|
.position(|e| e.0 == *effect_type)
|
||||||
.map(|p| target_mut.active_effects.remove(p));
|
.map(|p| target_mut.active_effects.remove(p));
|
||||||
ctx.trans.save_item_model(&target_mut).await?;
|
ctx.trans.save_item_model(&target_mut).await?;
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@ -195,37 +198,43 @@ pub static DISPEL_EFFECT_HANDLER: &'static (dyn TaskHandler + Sync + Send) =
|
|||||||
pub async fn run_effects(
|
pub async fn run_effects(
|
||||||
trans: &DBTrans,
|
trans: &DBTrans,
|
||||||
effects: &EffectSet,
|
effects: &EffectSet,
|
||||||
player: &mut Item,
|
mut player: &mut Item,
|
||||||
item: &Item,
|
item: &Item,
|
||||||
// None if target is player
|
// None if target is player
|
||||||
target: &mut Option<Item>,
|
mut target: Option<&mut Item>,
|
||||||
level: f64,
|
level: f64,
|
||||||
task_ref: &str,
|
|
||||||
) -> DResult<()> {
|
) -> DResult<()> {
|
||||||
let mut target_health_series = BTreeMap::<String, VecDeque<DelayedHealthEffect>>::new();
|
let mut target_health_series = BTreeMap::<String, VecDeque<DelayedHealthEffect>>::new();
|
||||||
let mut target_message_series = BTreeMap::<String, VecDeque<DelayedMessageEffect>>::new();
|
let mut target_message_series = BTreeMap::<String, VecDeque<DelayedMessageEffect>>::new();
|
||||||
let mut dispel_time_secs: u64 = 0;
|
let mut dispel_time_secs: u64 = 0;
|
||||||
|
|
||||||
for effect in &effects.effects {
|
for effect in &effects.effects {
|
||||||
match effect {
|
match effect {
|
||||||
Effect::BroadcastMessage {
|
Effect::BroadcastMessage {
|
||||||
delay_secs,
|
delay_secs,
|
||||||
messagef,
|
messagef,
|
||||||
} => {
|
} => {
|
||||||
let (msg_exp, msg_nonexp) =
|
let (msg_exp, msg_nonexp) = messagef(
|
||||||
messagef(player, item, target.as_ref().unwrap_or(player));
|
player,
|
||||||
|
item,
|
||||||
|
target.as_ref().map(|t| &**t).unwrap_or(player),
|
||||||
|
);
|
||||||
if *delay_secs == 0 {
|
if *delay_secs == 0 {
|
||||||
broadcast_to_room(trans, &player.location, None, &msg_exp, Some(&msg_nonexp))
|
broadcast_to_room(trans, &player.location, None, &msg_exp, Some(&msg_nonexp))
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
dispel_time_secs = dispel_time_secs.max(*delay_secs);
|
dispel_time_secs = dispel_time_secs.max(*delay_secs);
|
||||||
let target_it = target.as_ref().unwrap_or(player);
|
|
||||||
let fx = DelayedMessageEffect {
|
let fx = DelayedMessageEffect {
|
||||||
delay: *delay_secs,
|
delay: *delay_secs,
|
||||||
message: msg_exp,
|
message: msg_exp,
|
||||||
message_nonexp: msg_nonexp,
|
message_nonexp: msg_nonexp,
|
||||||
};
|
};
|
||||||
|
let actual_target: &mut Item = *target.as_mut().unwrap_or(&mut player);
|
||||||
target_message_series
|
target_message_series
|
||||||
.entry(format!("{}/{}", target_it.item_type, target_it.item_code))
|
.entry(format!(
|
||||||
|
"{}/{}",
|
||||||
|
actual_target.item_type, actual_target.item_code
|
||||||
|
))
|
||||||
.and_modify(|l| l.push_back(fx.clone()))
|
.and_modify(|l| l.push_back(fx.clone()))
|
||||||
.or_insert(VecDeque::from([fx]));
|
.or_insert(VecDeque::from([fx]));
|
||||||
}
|
}
|
||||||
@ -238,20 +247,20 @@ pub async fn run_effects(
|
|||||||
message,
|
message,
|
||||||
} => {
|
} => {
|
||||||
let health_impact =
|
let health_impact =
|
||||||
(*base_effect + ((skill_multiplier * level) as i64).min(*max_effect)) as i64;
|
(*base_effect + ((skill_multiplier * level) as i64)).min(*max_effect) as i64;
|
||||||
let (msg, msg_nonexp) = message(target.as_ref().unwrap_or(player));
|
let (msg, msg_nonexp) = message(target.as_ref().map(|t| &**t).unwrap_or(player));
|
||||||
if *delay_secs == 0 {
|
if *delay_secs == 0 {
|
||||||
change_health(
|
change_health(
|
||||||
trans,
|
trans,
|
||||||
health_impact,
|
health_impact,
|
||||||
target.as_mut().unwrap_or(player),
|
*target.as_mut().unwrap_or(&mut player),
|
||||||
&msg,
|
&msg,
|
||||||
&msg_nonexp,
|
&msg_nonexp,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
dispel_time_secs = dispel_time_secs.max(*delay_secs);
|
dispel_time_secs = dispel_time_secs.max(*delay_secs);
|
||||||
let target_it = target.as_ref().unwrap_or(player);
|
let target_it = target.as_ref().map(|t| &**t).unwrap_or(player);
|
||||||
let fx = DelayedHealthEffect {
|
let fx = DelayedHealthEffect {
|
||||||
magnitude: health_impact,
|
magnitude: health_impact,
|
||||||
delay: *delay_secs,
|
delay: *delay_secs,
|
||||||
@ -267,19 +276,22 @@ pub async fn run_effects(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let task_ref = trans.alloc_task_code().await?;
|
||||||
if dispel_time_secs > 0 && effects.effect_type != EffectType::Ephemeral {
|
if dispel_time_secs > 0 && effects.effect_type != EffectType::Ephemeral {
|
||||||
let target_item = target.as_mut().unwrap_or(player);
|
let actual_target: &mut Item = *target.as_mut().unwrap_or(&mut player);
|
||||||
target_item.active_effects.push(effects.effect_type.clone());
|
actual_target
|
||||||
|
.active_effects
|
||||||
|
.push((effects.effect_type.clone(), task_ref.clone()));
|
||||||
|
|
||||||
trans
|
trans
|
||||||
.upsert_task(&Task {
|
.upsert_task(&Task {
|
||||||
meta: TaskMeta {
|
meta: TaskMeta {
|
||||||
task_code: format!("{}/{}", &target_item.refstr(), task_ref),
|
task_code: format!("{}/{}", &actual_target.refstr(), task_ref),
|
||||||
next_scheduled: Utc::now() + chrono::Duration::seconds(dispel_time_secs as i64),
|
next_scheduled: Utc::now() + chrono::Duration::seconds(dispel_time_secs as i64),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
details: TaskDetails::DispelEffect {
|
details: TaskDetails::DispelEffect {
|
||||||
target: target_item.refstr(),
|
target: actual_target.refstr(),
|
||||||
effect_type: effects.effect_type.clone(),
|
effect_type: effects.effect_type.clone(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -290,7 +302,7 @@ pub async fn run_effects(
|
|||||||
trans
|
trans
|
||||||
.upsert_task(&Task {
|
.upsert_task(&Task {
|
||||||
meta: TaskMeta {
|
meta: TaskMeta {
|
||||||
task_code: format!("{}/{}", eff_item, task_ref),
|
task_code: format!("{}/{}", eff_item, trans.alloc_task_code().await?),
|
||||||
next_scheduled: Utc::now() + chrono::Duration::seconds(l[0].delay as i64),
|
next_scheduled: Utc::now() + chrono::Duration::seconds(l[0].delay as i64),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@ -319,3 +331,121 @@ pub async fn run_effects(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn default_effects_for_type() -> &'static BTreeMap<EffectType, EffectSet> {
|
||||||
|
static MAP: OnceCell<BTreeMap<EffectType, EffectSet>> = OnceCell::new();
|
||||||
|
MAP.get_or_init(|| {
|
||||||
|
vec![(
|
||||||
|
EffectType::Bleed,
|
||||||
|
EffectSet {
|
||||||
|
effect_type: EffectType::Bleed,
|
||||||
|
effects: vec![
|
||||||
|
Effect::BroadcastMessage {
|
||||||
|
delay_secs: 0,
|
||||||
|
messagef: Box::new(|_player, _item, target| (
|
||||||
|
format!(ansi!("<red>You notice that {} has a fresh gaping wound that looks like it's about to {}!<reset>\n"),
|
||||||
|
target.display_for_sentence(true, 1, false),
|
||||||
|
if target.species == SpeciesType::Robot { "leak coolant" } else { "bleed" }
|
||||||
|
),
|
||||||
|
format!(ansi!("<red>You notice that {} has a fresh gaping wound that looks like it's about to {}!<reset>\n"),
|
||||||
|
target.display_for_sentence(false, 1, false),
|
||||||
|
if target.species == SpeciesType::Robot { "leak coolant" } else { "bleed" }
|
||||||
|
)))
|
||||||
|
},
|
||||||
|
Effect::ChangeTargetHealth {
|
||||||
|
delay_secs: 10,
|
||||||
|
base_effect: -12,
|
||||||
|
skill_multiplier: 0.0,
|
||||||
|
max_effect: -12,
|
||||||
|
message: Box::new(|target| (
|
||||||
|
format!("{} pulses from {}'s wound",
|
||||||
|
if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" },
|
||||||
|
target.display_for_sentence(true, 1, false),
|
||||||
|
),
|
||||||
|
format!("{} pulses from {}'s wound",
|
||||||
|
if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" },
|
||||||
|
target.display_for_sentence(false, 1, false),
|
||||||
|
)))
|
||||||
|
},
|
||||||
|
Effect::ChangeTargetHealth {
|
||||||
|
delay_secs: 20,
|
||||||
|
base_effect: -10,
|
||||||
|
skill_multiplier: 0.0,
|
||||||
|
max_effect: -10,
|
||||||
|
message: Box::new(|target| (
|
||||||
|
format!("{} pulses from {}'s wound",
|
||||||
|
if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" },
|
||||||
|
target.display_for_sentence(true, 1, false),
|
||||||
|
),
|
||||||
|
format!("{} pulses from {}'s wound",
|
||||||
|
if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" },
|
||||||
|
target.display_for_sentence(false, 1, false),
|
||||||
|
)))
|
||||||
|
},
|
||||||
|
Effect::ChangeTargetHealth {
|
||||||
|
delay_secs: 30,
|
||||||
|
base_effect: -8,
|
||||||
|
skill_multiplier: 0.0,
|
||||||
|
max_effect: -8,
|
||||||
|
message: Box::new(|target| (
|
||||||
|
format!("{} pulses from {}'s wound",
|
||||||
|
if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" },
|
||||||
|
target.display_for_sentence(true, 1, false),
|
||||||
|
),
|
||||||
|
format!("{} pulses from {}'s wound",
|
||||||
|
if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" },
|
||||||
|
target.display_for_sentence(false, 1, false),
|
||||||
|
)))
|
||||||
|
},
|
||||||
|
Effect::ChangeTargetHealth {
|
||||||
|
delay_secs: 40,
|
||||||
|
base_effect: -6,
|
||||||
|
skill_multiplier: 0.0,
|
||||||
|
max_effect: -6,
|
||||||
|
message: Box::new(|target| (
|
||||||
|
format!("{} pulses from {}'s wound",
|
||||||
|
if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" },
|
||||||
|
target.display_for_sentence(true, 1, false),
|
||||||
|
),
|
||||||
|
format!("{} pulses from {}'s wound",
|
||||||
|
if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" },
|
||||||
|
target.display_for_sentence(false, 1, false),
|
||||||
|
)))
|
||||||
|
},
|
||||||
|
Effect::ChangeTargetHealth {
|
||||||
|
delay_secs: 50,
|
||||||
|
base_effect: -4,
|
||||||
|
skill_multiplier: 0.0,
|
||||||
|
max_effect: -4,
|
||||||
|
message: Box::new(|target| (
|
||||||
|
format!("{} pulses from {}'s wound",
|
||||||
|
if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" },
|
||||||
|
target.display_for_sentence(true, 1, false),
|
||||||
|
),
|
||||||
|
format!("{} pulses from {}'s wound",
|
||||||
|
if target.species == SpeciesType::Robot { "Coolant" } else { "Blood" },
|
||||||
|
target.display_for_sentence(false, 1, false),
|
||||||
|
)))
|
||||||
|
},
|
||||||
|
Effect::ChangeTargetHealth {
|
||||||
|
delay_secs: 60,
|
||||||
|
base_effect: -2,
|
||||||
|
skill_multiplier: 0.0,
|
||||||
|
max_effect: -2,
|
||||||
|
message: Box::new(|target| (
|
||||||
|
format!("A final tiny drop of {} oozes from {}'s wound as it heals",
|
||||||
|
if target.species == SpeciesType::Robot { "coolant" } else { "blood" },
|
||||||
|
target.display_for_sentence(true, 1, false),
|
||||||
|
),
|
||||||
|
format!("A final tiny drop of {} oozes from {}'s wound as it clots",
|
||||||
|
if target.species == SpeciesType::Robot { "coolant" } else { "blood" },
|
||||||
|
target.display_for_sentence(false, 1, false),
|
||||||
|
)))
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
.into_iter()
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -4,7 +4,7 @@ use crate::{
|
|||||||
message_handler::user_commands::{UResult, VerbContext},
|
message_handler::user_commands::{UResult, VerbContext},
|
||||||
models::{
|
models::{
|
||||||
consent::ConsentType,
|
consent::ConsentType,
|
||||||
effect::EffectSet,
|
effect::{EffectSet, EffectType},
|
||||||
item::{Item, ItemFlag, LiquidType, Pronouns, SkillType},
|
item::{Item, ItemFlag, LiquidType, Pronouns, SkillType},
|
||||||
},
|
},
|
||||||
regular_tasks::queued_command::QueuedCommandContext,
|
regular_tasks::queued_command::QueuedCommandContext,
|
||||||
@ -82,6 +82,7 @@ pub struct WeaponAttackData {
|
|||||||
pub stdev_damage: f64,
|
pub stdev_damage: f64,
|
||||||
pub base_damage_type: DamageType,
|
pub base_damage_type: DamageType,
|
||||||
pub other_damage_types: Vec<(f64, DamageType)>, // Allocation fractions.
|
pub other_damage_types: Vec<(f64, DamageType)>, // Allocation fractions.
|
||||||
|
pub crit_effects: Vec<(f64, EffectType)>, // Probability, add up to <1 unless one guaranteed.
|
||||||
pub skill_scaling: Vec<SkillScaling>,
|
pub skill_scaling: Vec<SkillScaling>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,6 +109,7 @@ impl Default for WeaponAttackData {
|
|||||||
stdev_damage: 2.0,
|
stdev_damage: 2.0,
|
||||||
base_damage_type: DamageType::Slash,
|
base_damage_type: DamageType::Slash,
|
||||||
other_damage_types: vec![],
|
other_damage_types: vec![],
|
||||||
|
crit_effects: vec![],
|
||||||
skill_scaling: vec![],
|
skill_scaling: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -170,7 +172,6 @@ pub struct UseData {
|
|||||||
pub fail_effects: Option<EffectSet>,
|
pub fail_effects: Option<EffectSet>,
|
||||||
pub success_effects: Option<EffectSet>,
|
pub success_effects: Option<EffectSet>,
|
||||||
pub errorf: Box<dyn Fn(&Item, &Item) -> Option<String> + Sync + Send>,
|
pub errorf: Box<dyn Fn(&Item, &Item) -> Option<String> + Sync + Send>,
|
||||||
pub task_ref: &'static str,
|
|
||||||
pub needs_consent_check: Option<ConsentType>,
|
pub needs_consent_check: Option<ConsentType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +184,6 @@ impl Default for UseData {
|
|||||||
fail_effects: None,
|
fail_effects: None,
|
||||||
success_effects: None,
|
success_effects: None,
|
||||||
errorf: Box::new(|_it, _target| None),
|
errorf: Box::new(|_it, _target| None),
|
||||||
task_ref: "set me",
|
|
||||||
needs_consent_check: None,
|
needs_consent_check: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -238,14 +238,13 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
task_ref: "bandage",
|
|
||||||
errorf: Box::new(
|
errorf: Box::new(
|
||||||
|item, target|
|
|item, target|
|
||||||
if target.death_data.is_some() {
|
if target.death_data.is_some() {
|
||||||
Some(format!("It is too late, {}'s dead", target.pronouns.subject))
|
Some(format!("It is too late, {}'s dead", target.pronouns.subject))
|
||||||
} else if target.item_type != "player" && target.item_type != "npc" {
|
} else if target.item_type != "player" && target.item_type != "npc" {
|
||||||
Some("It only works on animals.".to_owned())
|
Some("It only works on animals.".to_owned())
|
||||||
} else if target.active_effects.contains(&EffectType::Bandages) {
|
} else if target.active_effects.iter().any(|e| e.0 == EffectType::Bandages) {
|
||||||
Some(format!(
|
Some(format!(
|
||||||
"You see no reason to use {} on {}",
|
"You see no reason to use {} on {}",
|
||||||
item.display_for_sentence(false, 1, false),
|
item.display_for_sentence(false, 1, false),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use super::{DamageType, PossessionData, PossessionType, WeaponAttackData, WeaponData};
|
use super::{DamageType, PossessionData, PossessionType, WeaponAttackData, WeaponData};
|
||||||
use crate::models::item::SkillType;
|
use crate::models::{effect::EffectType, item::SkillType};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
|
pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
|
||||||
@ -76,6 +76,7 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
|
|||||||
mean_damage: 4.0,
|
mean_damage: 4.0,
|
||||||
stdev_damage: 2.0,
|
stdev_damage: 2.0,
|
||||||
base_damage_type: DamageType::Beat,
|
base_damage_type: DamageType::Beat,
|
||||||
|
crit_effects: vec![(0.05, EffectType::Bleed)],
|
||||||
other_damage_types: vec!((0.25, DamageType::Slash)),
|
other_damage_types: vec!((0.25, DamageType::Slash)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
@ -168,9 +168,8 @@ impl TaskHandler for SeePatientTaskHandler {
|
|||||||
},
|
},
|
||||||
&mut who_mut,
|
&mut who_mut,
|
||||||
&who,
|
&who,
|
||||||
&mut None,
|
None,
|
||||||
0.0,
|
0.0,
|
||||||
"bandages",
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
ctx.trans.save_item_model(&who_mut).await?;
|
ctx.trans.save_item_model(&who_mut).await?;
|
||||||
|
@ -70,6 +70,8 @@ CREATE UNIQUE INDEX tasks_by_code_type ON tasks((details->>'task_code'), (detail
|
|||||||
CREATE INDEX tasks_by_static ON tasks((cast(details->>'is_static' as boolean)));
|
CREATE INDEX tasks_by_static ON tasks((cast(details->>'is_static' as boolean)));
|
||||||
CREATE INDEX tasks_by_scheduled ON tasks((details->>'next_scheduled'));
|
CREATE INDEX tasks_by_scheduled ON tasks((details->>'next_scheduled'));
|
||||||
|
|
||||||
|
CREATE SEQUENCE task_seq;
|
||||||
|
|
||||||
CREATE TABLE corps (
|
CREATE TABLE corps (
|
||||||
corp_id BIGSERIAL NOT NULL PRIMARY KEY,
|
corp_id BIGSERIAL NOT NULL PRIMARY KEY,
|
||||||
details JSONB NOT NULL
|
details JSONB NOT NULL
|
||||||
|
Loading…
Reference in New Issue
Block a user