Support wielding weapons.

This commit is contained in:
Condorra 2023-02-19 01:18:08 +11:00
parent dc38d87beb
commit 2850b66bee
6 changed files with 236 additions and 43 deletions

View File

@ -12,7 +12,10 @@ use crate::message_handler::user_commands::parsing::parse_offset;
use crate::models::{ use crate::models::{
session::Session, session::Session,
user::User, user::User,
item::Item, item::{
Item,
LocationActionType,
},
task::{Task, TaskParse} task::{Task, TaskParse}
}; };
use tokio_postgres::types::ToSql; use tokio_postgres::types::ToSql;
@ -630,11 +633,46 @@ impl DBTrans {
pub async fn get_location_stats(&self, location: &str) -> DResult<LocationStats> { pub async fn get_location_stats(&self, location: &str) -> DResult<LocationStats> {
Ok(serde_json::from_value(self.pg_trans()?.query_one( Ok(serde_json::from_value(self.pg_trans()?.query_one(
"SELECT COUNT(*) AS total_count, SUM(details->>'weight') AS total_weight \ "SELECT JSON_BUILD_OBJECT('total_count', COUNT(*), 'total_weight', COALESCE(SUM(CAST(details->>'weight' AS NUMERIC)), 0)) \
FROM items WHERE location = $1", &[&location] FROM items WHERE details->>'location' = $1", &[&location]
).await?.get(0))?) ).await?.get(0))?)
} }
pub async fn set_exclusive_action_type_to(&self, item: &Item,
new_action_type: &LocationActionType,
other_item_action_type: &LocationActionType) -> DResult<()> {
self.pg_trans()?.execute("UPDATE items SET details=\
JSONB_SET(details, '{action_type}', $1) \
WHERE details->>'location' = $2 AND \
details->>'action_type' = $3",
&[&serde_json::to_value(other_item_action_type)?,
&item.location,
&serde_json::to_value(new_action_type)?
.as_str().unwrap()
]).await?;
self.pg_trans()?.execute("UPDATE items SET details=\
JSONB_SET(details, '{action_type}', $1) \
WHERE details->>'item_type' = $2 AND \
details->>'item_code' = $3",
&[&serde_json::to_value(new_action_type)?,
&item.item_type,
&item.item_code
]).await?;
Ok(())
}
pub async fn find_by_action_and_location(&self, location: &str, action_type: &LocationActionType) -> DResult<Option<Arc<Item>>> {
if let Some(item) = self.pg_trans()?.query_opt(
"SELECT details FROM items WHERE \
details->>'location' = $1 AND \
details->>'action_type' = $2",
&[&location,
&serde_json::to_value(action_type)?.as_str().unwrap()]).await? {
return Ok(Some(Arc::new(serde_json::from_value::<Item>(item.get("details"))?)));
}
Ok(None)
}
pub async fn commit(mut self: Self) -> DResult<()> { pub async fn commit(mut self: Self) -> DResult<()> {
let trans_opt = self.with_trans_mut(|t| std::mem::replace(t, None)); let trans_opt = self.with_trans_mut(|t| std::mem::replace(t, None));
if let Some(trans) = trans_opt { if let Some(trans) = trans_opt {

View File

@ -28,6 +28,7 @@ mod register;
pub mod say; pub mod say;
mod whisper; mod whisper;
mod who; mod who;
pub mod wield;
pub struct VerbContext<'l> { pub struct VerbContext<'l> {
pub session: &'l ListenerSession, pub session: &'l ListenerSession,
@ -125,6 +126,7 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
"whisper" => whisper::VERB, "whisper" => whisper::VERB,
"tell" => whisper::VERB, "tell" => whisper::VERB,
"wield" => wield::VERB,
"who" => who::VERB, "who" => who::VERB,
}; };

View File

@ -0,0 +1,140 @@
use super::{
VerbContext,
UserVerb,
UserVerbRef,
UResult,
ItemSearchParams,
user_error,
get_player_item_or_fail,
search_item_for_user,
};
use crate::{
static_content::possession_type::possession_data,
regular_tasks::queued_command::{
QueueCommandHandler,
QueueCommand,
queue_command
},
models::item::{
LocationActionType,
SkillType,
},
services::{
broadcast_to_room,
skills::skill_check_and_grind,
},
};
use async_trait::async_trait;
use std::time;
pub struct QueueHandler;
#[async_trait]
impl QueueCommandHandler for QueueHandler {
async fn start_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
-> UResult<time::Duration> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead {
user_error("You try to wield it, but your ghostly hands slip through it uselessly".to_owned())?;
}
let item_id = match command {
QueueCommand::Wield { possession_id } => possession_id,
_ => user_error("Unexpected command".to_owned())?
};
let item = match ctx.trans.find_item_by_type_code("possession", &item_id).await? {
None => user_error("Item not found".to_owned())?,
Some(it) => it
};
if item.location != format!("player/{}", player_item.item_code) {
user_error("You try to wield it but realise you no longer have it".to_owned())?
}
let msg_exp = format!("{} fumbles around with {} {}\n",
&player_item.display_for_sentence(true, 1, true),
&player_item.pronouns.possessive,
&item.display_for_sentence(true, 1, false));
let msg_nonexp = format!("{} fumbles around with {} {}\n",
&player_item.display_for_sentence(false, 1, true),
&player_item.pronouns.possessive,
&item.display_for_sentence(false, 1, false));
broadcast_to_room(ctx.trans, &player_item.location, None, &msg_exp, Some(&msg_nonexp)).await?;
let mut draw_level: f64 = *player_item.total_skills.get(&SkillType::Quickdraw).to_owned().unwrap_or(&8.0);
let mut player_item_mut = (*player_item).clone();
let skill_result =
skill_check_and_grind(ctx.trans, &mut player_item_mut, &SkillType::Quickdraw, draw_level).await?;
if skill_result < -0.5 {
draw_level -= 2.0;
} else if skill_result < -0.25 {
draw_level -= 1.0;
} else if skill_result > 0.5 {
draw_level += 2.0;
} else if skill_result > 0.25 {
draw_level += 1.0;
}
ctx.trans.save_item_model(&player_item_mut).await?;
let wait_ticks = (12.0 - (draw_level / 2.0)).min(8.0).max(1.0);
Ok(time::Duration::from_millis((wait_ticks * 500.0).round() as u64))
}
#[allow(unreachable_patterns)]
async fn finish_command(&self, ctx: &mut VerbContext<'_>, command: &QueueCommand)
-> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.is_dead {
user_error("You try to wield it, but your ghostly hands slip through it uselessly".to_owned())?;
}
let item_id = match command {
QueueCommand::Wield { possession_id } => possession_id,
_ => user_error("Unexpected command".to_owned())?
};
let item = match ctx.trans.find_item_by_type_code("possession", &item_id).await? {
None => user_error("Item not found".to_owned())?,
Some(it) => it
};
if item.location != format!("player/{}", player_item.item_code) {
user_error("You try to wield it but realise you no longer have it".to_owned())?
}
let msg_exp = format!("{} wields {} {}\n",
&player_item.display_for_sentence(true, 1, true),
&player_item.pronouns.possessive,
&item.display_for_sentence(true, 1, false));
let msg_nonexp = format!("{} wields {} {}\n",
&player_item.display_for_sentence(false, 1, true),
&player_item.pronouns.possessive,
&item.display_for_sentence(false, 1, false));
broadcast_to_room(ctx.trans, &player_item.location, None, &msg_exp, Some(&msg_nonexp)).await?;
ctx.trans.set_exclusive_action_type_to(&item,
&LocationActionType::Wielded,
&LocationActionType::Normal).await?;
Ok(())
}
}
pub struct Verb;
#[async_trait]
impl UserVerb for Verb {
async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, remaining: &str) -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?;
let weapon = search_item_for_user(ctx, &ItemSearchParams {
include_contents: true,
..ItemSearchParams::base(&player_item, &remaining)
}).await?;
if player_item.is_dead {
user_error("You try to wield it, but your ghostly hands slip through it uselessly".to_owned())?;
}
if weapon.action_type == LocationActionType::Wielded {
user_error("You're actually already wielding it.".to_owned())?;
}
if weapon.item_type != "possession" ||
weapon.possession_type.as_ref()
.and_then(|poss_type| possession_data().get(&poss_type))
.and_then(|poss_data| poss_data.weapon_data.as_ref())
.is_none() {
user_error("You can't wield that!".to_owned())?;
}
queue_command(ctx, &QueueCommand::Wield { possession_id: weapon.item_code.clone() }).await?;
Ok(())
}
}
static VERB_INT: Verb = Verb;
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;

View File

@ -15,6 +15,7 @@ use crate::message_handler::user_commands::{
CommandHandlingError, CommandHandlingError,
UResult, UResult,
movement, movement,
wield,
user_error, user_error,
get_user_or_fail get_user_or_fail
}; };
@ -24,12 +25,14 @@ use once_cell::sync::OnceCell;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum QueueCommand { pub enum QueueCommand {
Movement { direction: Direction }, Movement { direction: Direction },
Wield { possession_id: String }
} }
impl QueueCommand { impl QueueCommand {
pub fn name(&self) -> &'static str { pub fn name(&self) -> &'static str {
use QueueCommand::*; use QueueCommand::*;
match self { match self {
Movement {..} => "Movement" Movement {..} => "Movement",
Wield {..} => "Wield",
} }
} }
} }
@ -44,7 +47,8 @@ fn queue_command_registry() -> &'static BTreeMap<&'static str, &'static (dyn Que
static REGISTRY: OnceCell<BTreeMap<&'static str, &'static (dyn QueueCommandHandler + Sync + Send)>> = static REGISTRY: OnceCell<BTreeMap<&'static str, &'static (dyn QueueCommandHandler + Sync + Send)>> =
OnceCell::new(); OnceCell::new();
REGISTRY.get_or_init(|| vec!( REGISTRY.get_or_init(|| vec!(
("Movement", &movement::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)) ("Movement", &movement::QueueHandler as &(dyn QueueCommandHandler + Sync + Send)),
("Wield", &wield::QueueHandler as &(dyn QueueCommandHandler + Sync + Send))
).into_iter().collect()) ).into_iter().collect())
} }

View File

@ -291,7 +291,16 @@ 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: &Item) -> DResult<&'static WeaponData> {
if let Some(item) = trans.find_by_action_and_location(
&format!("{}/{}", &who.item_type, &who.item_code), &LocationActionType::Wielded).await? {
if let Some(dat) = item.possession_type.as_ref()
.and_then(|pt| possession_data().get(&pt))
.and_then(|pd| pd.weapon_data.as_ref()) {
return Ok(dat);
}
}
// TODO: Search inventory for wielded item first. // TODO: Search inventory for wielded item first.
if who.item_type == "npc" { if who.item_type == "npc" {
if let Some(intrinsic) = npc_by_code().get(who.item_code.as_str()) if let Some(intrinsic) = npc_by_code().get(who.item_code.as_str())

View File

@ -46,105 +46,105 @@ pub fn calculate_total_stats_skills_for_user(target_item: &mut Item, user: &User
target_item.total_skills.entry(SkillType::Appraise) target_item.total_skills.entry(SkillType::Appraise)
.and_modify(|sk| *sk += brn * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += brn * 0.5).or_insert(brn * 0.5);
target_item.total_skills.entry(SkillType::Appraise) target_item.total_skills.entry(SkillType::Appraise)
.and_modify(|sk| *sk += sen * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += sen * 0.5).or_insert(sen * 0.5);
target_item.total_skills.entry(SkillType::Blades) target_item.total_skills.entry(SkillType::Blades)
.and_modify(|sk| *sk += refl * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += refl * 0.5).or_insert(refl * 0.5);
target_item.total_skills.entry(SkillType::Blades) target_item.total_skills.entry(SkillType::Blades)
.and_modify(|sk| *sk += col * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += col * 0.5).or_insert(col * 0.5);
target_item.total_skills.entry(SkillType::Bombs) target_item.total_skills.entry(SkillType::Bombs)
.and_modify(|sk| *sk += brn * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += brn * 0.5).or_insert(brn * 0.5);
target_item.total_skills.entry(SkillType::Bombs) target_item.total_skills.entry(SkillType::Bombs)
.and_modify(|sk| *sk += col * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += col * 0.5).or_insert(col * 0.5);
target_item.total_skills.entry(SkillType::Chemistry) target_item.total_skills.entry(SkillType::Chemistry)
.and_modify(|sk| *sk += brn).or_insert(brn); .and_modify(|sk| *sk += brn).or_insert(brn);
target_item.total_skills.entry(SkillType::Climb) target_item.total_skills.entry(SkillType::Climb)
.and_modify(|sk| *sk += refl * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += refl * 0.5).or_insert(refl * 0.5);
target_item.total_skills.entry(SkillType::Climb) target_item.total_skills.entry(SkillType::Climb)
.and_modify(|sk| *sk += end * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += end * 0.5).or_insert(end * 0.5);
target_item.total_skills.entry(SkillType::Clubs) target_item.total_skills.entry(SkillType::Clubs)
.and_modify(|sk| *sk += brw * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += brw * 0.5).or_insert(brw * 0.5);
target_item.total_skills.entry(SkillType::Clubs) target_item.total_skills.entry(SkillType::Clubs)
.and_modify(|sk| *sk += refl * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += refl * 0.5).or_insert(refl * 0.5);
target_item.total_skills.entry(SkillType::Craft) target_item.total_skills.entry(SkillType::Craft)
.and_modify(|sk| *sk += brn).or_insert(brn); .and_modify(|sk| *sk += brn).or_insert(brn);
target_item.total_skills.entry(SkillType::Dodge) target_item.total_skills.entry(SkillType::Dodge)
.and_modify(|sk| *sk += sen * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += sen * 0.5).or_insert(sen * 0.5);
target_item.total_skills.entry(SkillType::Dodge) target_item.total_skills.entry(SkillType::Dodge)
.and_modify(|sk| *sk += refl * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += refl * 0.5).or_insert(refl * 0.5);
target_item.total_skills.entry(SkillType::Fish) target_item.total_skills.entry(SkillType::Fish)
.and_modify(|sk| *sk += end * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += end * 0.5).or_insert(end * 0.5);
target_item.total_skills.entry(SkillType::Fish) target_item.total_skills.entry(SkillType::Fish)
.and_modify(|sk| *sk += col * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += col * 0.5).or_insert(col * 0.5);
target_item.total_skills.entry(SkillType::Fists) target_item.total_skills.entry(SkillType::Fists)
.and_modify(|sk| *sk += brw * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += brw * 0.5).or_insert(brw * 0.5);
target_item.total_skills.entry(SkillType::Fists) target_item.total_skills.entry(SkillType::Fists)
.and_modify(|sk| *sk += end * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += end * 0.5).or_insert(end * 0.5);
target_item.total_skills.entry(SkillType::Focus) target_item.total_skills.entry(SkillType::Focus)
.and_modify(|sk| *sk += sen * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += sen * 0.5).or_insert(sen * 0.5);
target_item.total_skills.entry(SkillType::Focus) target_item.total_skills.entry(SkillType::Focus)
.and_modify(|sk| *sk += end * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += end * 0.5).or_insert(end * 0.5);
target_item.total_skills.entry(SkillType::Fuck) target_item.total_skills.entry(SkillType::Fuck)
.and_modify(|sk| *sk += sen * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += sen * 0.5).or_insert(sen * 0.5);
target_item.total_skills.entry(SkillType::Fuck) target_item.total_skills.entry(SkillType::Fuck)
.and_modify(|sk| *sk += end * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += end * 0.5).or_insert(end * 0.5);
target_item.total_skills.entry(SkillType::Hack) target_item.total_skills.entry(SkillType::Hack)
.and_modify(|sk| *sk += brn).or_insert(brn); .and_modify(|sk| *sk += brn).or_insert(brn);
target_item.total_skills.entry(SkillType::Locksmith) target_item.total_skills.entry(SkillType::Locksmith)
.and_modify(|sk| *sk += brn * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += brn * 0.5).or_insert(brn * 0.5);
target_item.total_skills.entry(SkillType::Locksmith) target_item.total_skills.entry(SkillType::Locksmith)
.and_modify(|sk| *sk += refl * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += refl * 0.5).or_insert(refl * 0.5);
target_item.total_skills.entry(SkillType::Medic) target_item.total_skills.entry(SkillType::Medic)
.and_modify(|sk| *sk += brn).or_insert(brn); .and_modify(|sk| *sk += brn).or_insert(brn);
target_item.total_skills.entry(SkillType::Persuade) target_item.total_skills.entry(SkillType::Persuade)
.and_modify(|sk| *sk += brn * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += brn * 0.5).or_insert(brn * 0.5);
target_item.total_skills.entry(SkillType::Persuade) target_item.total_skills.entry(SkillType::Persuade)
.and_modify(|sk| *sk += col * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += col * 0.5).or_insert(col * 0.5);
target_item.total_skills.entry(SkillType::Pilot) target_item.total_skills.entry(SkillType::Pilot)
.and_modify(|sk| *sk += brn * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += brn * 0.5).or_insert(brn * 0.5);
target_item.total_skills.entry(SkillType::Pilot) target_item.total_skills.entry(SkillType::Pilot)
.and_modify(|sk| *sk += refl * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += refl * 0.5).or_insert(refl * 0.5);
target_item.total_skills.entry(SkillType::Pistols) target_item.total_skills.entry(SkillType::Pistols)
.and_modify(|sk| *sk += refl * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += refl * 0.5).or_insert(refl * 0.5);
target_item.total_skills.entry(SkillType::Pistols) target_item.total_skills.entry(SkillType::Pistols)
.and_modify(|sk| *sk += col * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += col * 0.5).or_insert(col * 0.5);
target_item.total_skills.entry(SkillType::Quickdraw) target_item.total_skills.entry(SkillType::Quickdraw)
.and_modify(|sk| *sk += refl * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += refl * 0.5).or_insert(refl * 0.5);
target_item.total_skills.entry(SkillType::Quickdraw) target_item.total_skills.entry(SkillType::Quickdraw)
.and_modify(|sk| *sk += col * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += col * 0.5).or_insert(col * 0.5);
target_item.total_skills.entry(SkillType::Repair) target_item.total_skills.entry(SkillType::Repair)
.and_modify(|sk| *sk += brn).or_insert(brn); .and_modify(|sk| *sk += brn).or_insert(brn);
target_item.total_skills.entry(SkillType::Rifles) target_item.total_skills.entry(SkillType::Rifles)
.and_modify(|sk| *sk += refl * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += refl * 0.5).or_insert(refl * 0.5);
target_item.total_skills.entry(SkillType::Rifles) target_item.total_skills.entry(SkillType::Rifles)
.and_modify(|sk| *sk += col * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += col * 0.5).or_insert(col * 0.5);
target_item.total_skills.entry(SkillType::Scavenge) target_item.total_skills.entry(SkillType::Scavenge)
.and_modify(|sk| *sk += sen * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += sen * 0.5).or_insert(sen * 0.5);
target_item.total_skills.entry(SkillType::Scavenge) target_item.total_skills.entry(SkillType::Scavenge)
.and_modify(|sk| *sk += end * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += end * 0.5).or_insert(end * 0.5);
target_item.total_skills.entry(SkillType::Science) target_item.total_skills.entry(SkillType::Science)
.and_modify(|sk| *sk += brn).or_insert(brn); .and_modify(|sk| *sk += brn).or_insert(brn);
target_item.total_skills.entry(SkillType::Sneak) target_item.total_skills.entry(SkillType::Sneak)
.and_modify(|sk| *sk += sen * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += sen * 0.5).or_insert(sen * 0.5);
target_item.total_skills.entry(SkillType::Sneak) target_item.total_skills.entry(SkillType::Sneak)
.and_modify(|sk| *sk += col * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += col * 0.5).or_insert(col * 0.5);
target_item.total_skills.entry(SkillType::Spears) target_item.total_skills.entry(SkillType::Spears)
.and_modify(|sk| *sk += refl * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += refl * 0.5).or_insert(refl * 0.5);
target_item.total_skills.entry(SkillType::Spears) target_item.total_skills.entry(SkillType::Spears)
.and_modify(|sk| *sk += end * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += end * 0.5).or_insert(end * 0.5);
target_item.total_skills.entry(SkillType::Swim) target_item.total_skills.entry(SkillType::Swim)
.and_modify(|sk| *sk += end).or_insert(brn); .and_modify(|sk| *sk += end).or_insert(brn);
target_item.total_skills.entry(SkillType::Teach) target_item.total_skills.entry(SkillType::Teach)
.and_modify(|sk| *sk += brn).or_insert(brn); .and_modify(|sk| *sk += brn).or_insert(brn);
target_item.total_skills.entry(SkillType::Throw) target_item.total_skills.entry(SkillType::Throw)
.and_modify(|sk| *sk += sen * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += sen * 0.5).or_insert(sen * 0.5);
target_item.total_skills.entry(SkillType::Throw) target_item.total_skills.entry(SkillType::Throw)
.and_modify(|sk| *sk += brw * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += brw * 0.5).or_insert(brw * 0.5);
target_item.total_skills.entry(SkillType::Track) target_item.total_skills.entry(SkillType::Track)
.and_modify(|sk| *sk += sen).or_insert(brn); .and_modify(|sk| *sk += sen).or_insert(brn);
target_item.total_skills.entry(SkillType::Whips) target_item.total_skills.entry(SkillType::Whips)
.and_modify(|sk| *sk += sen * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += sen * 0.5).or_insert(sen * 0.5);
target_item.total_skills.entry(SkillType::Whips) target_item.total_skills.entry(SkillType::Whips)
.and_modify(|sk| *sk += refl * 0.5).or_insert(brn * 0.5); .and_modify(|sk| *sk += refl * 0.5).or_insert(refl * 0.5);
// 5: Apply skill (de)buffs... // 5: Apply skill (de)buffs...
for buff in &target_item.temporary_buffs { for buff in &target_item.temporary_buffs {
for impact in &buff.impacts { for impact in &buff.impacts {