From 61cbef6999df4411b219e48a29d1c8f7711ee385 Mon Sep 17 00:00:00 2001 From: ntr Date: Wed, 16 Jan 2019 20:05:31 +1100 Subject: [PATCH] specs with values --- server/WORKLOG.md | 5 ++ server/src/cryp.rs | 147 ++++++++++++++++++++++++++++++++------------ server/src/game.rs | 6 +- server/src/item.rs | 19 ++++-- server/src/skill.rs | 14 ++--- server/src/spec.rs | 54 ++++++++++++++++ server/src/zone.rs | 2 +- 7 files changed, 190 insertions(+), 57 deletions(-) create mode 100644 server/src/spec.rs diff --git a/server/WORKLOG.md b/server/WORKLOG.md index 8ffbb3e4..7704e553 100644 --- a/server/WORKLOG.md +++ b/server/WORKLOG.md @@ -34,6 +34,11 @@ strangle ## NOW * check zone completion * recalc evasion rating +* serialize modified stats + * items add specs + * add effects to spec + * remove spec from cryp + * spec limits per category ## SOON * clean up categories diff --git a/server/src/cryp.rs b/server/src/cryp.rs index 3834207e..715666bd 100644 --- a/server/src/cryp.rs +++ b/server/src/cryp.rs @@ -10,7 +10,7 @@ use failure::err_msg; use account::Account; use rpc::{CrypSpawnParams, CrypLearnParams, CrypForgetParams}; use skill::{Skill, Cooldown, Effect, Cast, Category, Immunity, Disable, ResolutionResult}; -use spec::{Spec}; +use spec::{Spec, SpecLevel}; use game::{Log}; #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] @@ -59,43 +59,61 @@ pub enum Stat { #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub struct CrypStat { base: u64, + value: u64, pub stat: Stat, } impl CrypStat { - pub fn set(&mut self, v: u64) -> &CrypStat { + pub fn set(&mut self, v: u64, specs: &CrypSpecs) -> &mut CrypStat { self.base = v; + self.recalculate(specs) + } + + pub fn recalculate(&mut self, specs: &CrypSpecs) -> &mut CrypStat { + let specs = specs.affects(self.stat); + // applied with fold because it can be zeroed or multiplied + // but still needs access to the base amount + let value = specs.iter().fold(self.base, |acc, s| s.apply(acc, self.base)); + self.value = value; + self } pub fn reduce(&mut self, amt: u64) -> &mut CrypStat { - self.base = self.base.saturating_sub(amt); + self.value = self.value.saturating_sub(amt); self } pub fn increase(&mut self, amt: u64) -> &mut CrypStat { - self.base = self.base.saturating_add(amt); + self.value = self.value.saturating_add(amt); self } - fn modified(&self, specs: &CrypSpecs) -> u64 { - let specs = specs.affects(self.stat); - // applied with fold because it can be zeroed or multiplied - // but still needs access to the base amount - let modified = specs.iter().fold(self.base, |acc, s| s.apply(acc, self.base)); - return modified; + pub fn force(&mut self, v: u64) -> &mut CrypStat { + self.base = v; + self.value = v; + + self } } #[derive(Debug,Clone,Serialize,Deserialize)] -struct CrypSpecs { +pub struct CrypSpecs { common: Vec, uncommon: Vec, rare: Vec, } impl CrypSpecs { + fn new() -> CrypSpecs { + CrypSpecs { + common: vec![], + uncommon: vec![], + rare: vec![], + } + } + fn affects(&self, stat: Stat) -> Vec { [&self.common, &self.uncommon, &self.rare] .iter() @@ -130,6 +148,7 @@ pub struct Cryp { pub lvl: u8, pub skills: Vec, pub effects: Vec, + pub specs: CrypSpecs, pub name: String, pub ko_logged: bool, } @@ -145,18 +164,19 @@ impl Cryp { return Cryp { id, account: id, - phys_dmg: CrypStat { base: 0, stat: Stat::PhysicalDamage }, - spell_dmg: CrypStat { base: 0, stat: Stat::SpellDamage }, - speed: CrypStat { base: 0, stat: Stat::Speed }, - stamina: CrypStat { base: 0, stat: Stat::Stamina }, - hp: CrypStat { base: 0, stat: Stat::Hp }, - armour: CrypStat { base: 0, stat: Stat::Armour }, - spell_shield: CrypStat { base: 0, stat: Stat::SpellShield }, - evasion: CrypStat { base: 0, stat: Stat::Evasion }, + phys_dmg: CrypStat { base: 0, value: 0, stat: Stat::PhysicalDamage }, + spell_dmg: CrypStat { base: 0, value: 0, stat: Stat::SpellDamage }, + speed: CrypStat { base: 0, value: 0, stat: Stat::Speed }, + stamina: CrypStat { base: 0, value: 0, stat: Stat::Stamina }, + hp: CrypStat { base: 0, value: 0, stat: Stat::Hp }, + armour: CrypStat { base: 0, value: 0, stat: Stat::Armour }, + spell_shield: CrypStat { base: 0, value: 0, stat: Stat::SpellShield }, + evasion: CrypStat { base: 0, value: 0, stat: Stat::Evasion }, lvl: 0, xp: 0, skills: vec![], effects: vec![], + specs: CrypSpecs::new(), name: String::new(), ko_logged: false, }; @@ -215,19 +235,19 @@ impl Cryp { let stat_max = 256u64.saturating_mul(self.lvl.into()); let evasion_min = 1u64; - let evasion_max = 25; + let evasion_max = 5; match stat { - Stat::PhysicalDamage => self.phys_dmg.set(rng.gen_range(stat_min, stat_max)), - Stat::SpellDamage => self.spell_dmg.set(rng.gen_range(stat_min, stat_max)), - Stat::Speed => self.speed.set(rng.gen_range(stat_min, stat_max)), + Stat::PhysicalDamage => self.phys_dmg.set(rng.gen_range(stat_min, stat_max), &self.specs), + Stat::SpellDamage => self.spell_dmg.set(rng.gen_range(stat_min, stat_max), &self.specs), + Stat::Speed => self.speed.set(rng.gen_range(stat_min, stat_max), &self.specs), Stat::Stamina => { - self.stamina.set(rng.gen_range(stam_min, stam_max)); - self.hp.set(self.stamina.base) + self.stamina.set(rng.gen_range(stam_min, stam_max), &self.specs); + self.hp.set(self.stamina.base, &self.specs) }, - Stat::SpellShield => self.spell_shield.set(rng.gen_range(stat_min, stat_max)), - Stat::Armour => self.armour.set(rng.gen_range(stat_min, stat_max)), - Stat::Evasion => self.evasion.set(rng.gen_range(evasion_min, evasion_max)), + Stat::SpellShield => self.spell_shield.set(rng.gen_range(stat_min, stat_max), &self.specs), + Stat::Armour => self.armour.set(rng.gen_range(stat_min, stat_max), &self.specs), + Stat::Evasion => self.evasion.set(rng.gen_range(evasion_min, evasion_max), &self.specs), _ => panic!("{:?} not a rollable stat", stat), }; @@ -250,8 +270,54 @@ impl Cryp { self } + pub fn spec_apply(&mut self, spec: Spec) -> Result<&mut Cryp, Error> { + let max_common = 20; + let max_uncommon = 10; + let max_rare = 5; + + match spec.level { + SpecLevel::Common => { + if self.specs.common.len() >= max_common { + return Err(format_err!("cryp at maximum common specalisations ({:})", max_common)) + } + + self.specs.common.push(spec); + }, + SpecLevel::Uncommon => { + if self.specs.uncommon.len() >= max_uncommon { + return Err(format_err!("cryp at maximum uncommon specalisations ({:})", max_uncommon)) + } + + self.specs.uncommon.push(spec); + }, + SpecLevel::Rare => { + if self.specs.rare.len() >= max_rare { + return Err(format_err!("cryp at maximum rare specalisations ({:})", max_rare)) + } + + // check dupes + self.specs.rare.push(spec); + }, + }; + + return Ok(self.recalculate_stats()); + } + + fn recalculate_stats(&mut self) -> &mut Cryp { + self.stamina.recalculate(&self.specs); + self.hp.recalculate(&self.specs); + self.phys_dmg.recalculate(&self.specs); + self.spell_dmg.recalculate(&self.specs); + self.evasion.recalculate(&self.specs); + self.armour.recalculate(&self.specs); + self.spell_shield.recalculate(&self.specs); + self.speed.recalculate(&self.specs); + + self + } + pub fn is_ko(&self) -> bool { - self.hp.base == 0 + self.hp.value == 0 } pub fn immune(&self, skill: Skill) -> Immunity { @@ -383,7 +449,7 @@ impl Cryp { .map(|cryp_effect| cryp_effect.effect) .collect::>(); - let modified_phys_dmg = phys_dmg_mods.iter().fold(self.phys_dmg.base, |acc, m| m.apply(acc)); + let modified_phys_dmg = phys_dmg_mods.iter().fold(self.phys_dmg.value, |acc, m| m.apply(acc)); return modified_phys_dmg; } @@ -393,7 +459,7 @@ impl Cryp { .map(|cryp_effect| cryp_effect.effect) .collect::>(); - let modified_spell_dmg = spell_dmg_mods.iter().fold(self.spell_dmg.base, |acc, m| m.apply(acc)); + let modified_spell_dmg = spell_dmg_mods.iter().fold(self.spell_dmg.value, |acc, m| m.apply(acc)); return modified_spell_dmg; } @@ -407,16 +473,16 @@ impl Cryp { .map(|cryp_effect| cryp_effect.effect) .collect::>(); - let modified_speed = speed_mods.iter().fold(self.speed.base, |acc, m| m.apply(acc)); + let modified_speed = speed_mods.iter().fold(self.speed.value, |acc, m| m.apply(acc)); return modified_speed; } pub fn hp(&self) -> u64 { - self.hp.base + self.hp.value } pub fn stamina(&self) -> u64 { - self.stamina.base + self.stamina.value } pub fn heal(&mut self, skill: Skill, amount: u64) -> ResolutionResult { @@ -450,7 +516,7 @@ impl Cryp { let healing = new_hp - current_hp; let overhealing = amount - healing; - self.hp.set(new_hp); + self.hp.increase(healing); return ResolutionResult::Healing { amount: healing, @@ -486,7 +552,7 @@ impl Cryp { // eg 50 armour 25 dmg -> 0 remainder 25 mitigation // 50 armour 100 dmg -> 50 remainder 50 mitigation // 50 armour 5 dmg -> 0 remainder 5 mitigation - let remainder = modified_dmg.saturating_sub(self.armour.base); + let remainder = modified_dmg.saturating_sub(self.armour.value); let mitigation = modified_dmg.saturating_sub(remainder); // reduce armour by mitigation amount @@ -524,7 +590,7 @@ impl Cryp { // println!("{:?}", spell_dmg_mods); let modified_dmg = spell_dmg_mods.iter().fold(amount, |acc, m| m.apply(acc)); - let remainder = modified_dmg.saturating_sub(self.armour.base); + let remainder = modified_dmg.saturating_sub(self.armour.value); let mitigation = modified_dmg.saturating_sub(remainder); self.armour.reduce(mitigation); @@ -550,6 +616,7 @@ impl Cryp { }; if !immune { + // println!("{:?} {:?} adding effect", self.name, effect.effect); self.effects.push(effect); } @@ -557,13 +624,13 @@ impl Cryp { } pub fn evade(&self, skill: Skill) -> Option { - if self.evasion.base == 0 { + if self.evasion.value == 0 { return None; } let mut rng = thread_rng(); - let hp_pct = (self.hp.base * 100) / self.stamina.base; - let evasion_rating = (self.evasion.base * hp_pct) / 100; + let hp_pct = (self.hp.value * 100) / self.stamina.value; + let evasion_rating = (self.evasion.value * hp_pct) / 100; let roll = rng.gen_range(0, 100); println!("{:} < {:?}", roll, evasion_rating); diff --git a/server/src/game.rs b/server/src/game.rs index 68d372c5..cec580b3 100644 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -1014,12 +1014,12 @@ mod tests { let x_cryp = x_team.cryps[0].clone(); let y_cryp = y_team.cryps[0].clone(); - game.team_by_id(y_team.id).cryp_by_id(y_cryp.id).unwrap().phys_dmg.set(u64::max_value()); - game.team_by_id(y_team.id).cryp_by_id(y_cryp.id).unwrap().speed.set(u64::max_value()); + game.team_by_id(y_team.id).cryp_by_id(y_cryp.id).unwrap().phys_dmg.force(u64::max_value()); + game.team_by_id(y_team.id).cryp_by_id(y_cryp.id).unwrap().speed.force(u64::max_value()); // just in case // remove all mitigation - game.team_by_id(x_team.id).cryp_by_id(x_cryp.id).unwrap().armour.set(0); + game.team_by_id(x_team.id).cryp_by_id(x_cryp.id).unwrap().armour.force(0); let _x_stun_id = game.add_skill(x_team.id, x_cryp.id, Some(y_cryp.id), Skill::TestStun).unwrap(); game.add_skill(y_team.id, y_cryp.id, Some(x_cryp.id), Skill::Attack).unwrap(); diff --git a/server/src/item.rs b/server/src/item.rs index f42021b2..1e5765c9 100644 --- a/server/src/item.rs +++ b/server/src/item.rs @@ -13,6 +13,7 @@ use account::Account; use rpc::{ItemUseParams}; use cryp::{Stat, cryp_get, cryp_write}; use game::{GameMode}; +use spec::{Spec, SpecType}; #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum ItemAction { @@ -23,6 +24,8 @@ pub enum ItemAction { RerollArmour, RerollSpellShield, RerollEvasion, + SpecPhysDmg5, + SpecSpellDmg5, } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] @@ -52,16 +55,20 @@ impl Item { ItemAction::RerollArmour => reroll(self, tx, target, Stat::Armour), ItemAction::RerollSpellShield => reroll(self, tx, target, Stat::SpellShield), ItemAction::RerollEvasion => reroll(self, tx, target, Stat::Evasion), + + ItemAction::SpecPhysDmg5 => spec_apply(self, tx, target, SpecType::PhysDamage5), + ItemAction::SpecSpellDmg5 => spec_apply(self, tx, target, SpecType::SpellDamage5), } } } -// fn revive(item: &mut Item, tx: &mut Transaction, target: Uuid) -> Result<(), Error> { -// let mut cryp = cryp_get(tx, target, item.account)?; -// cryp.rez(); -// cryp_write(cryp, tx)?; -// return Ok(()); -// } +fn spec_apply(item: &mut Item, tx: &mut Transaction, target: Uuid, spec_type: SpecType) -> Result<(), Error> { + let mut cryp = cryp_get(tx, target, item.account)?; + let spec = Spec::new(spec_type); + cryp.spec_apply(spec)?; + cryp_write(cryp, tx)?; + return Ok(()); +} fn reroll(item: &mut Item, tx: &mut Transaction, target: Uuid, stat: Stat) -> Result<(), Error> { let mut cryp = cryp_get(tx, target, item.account)?; diff --git a/server/src/skill.rs b/server/src/skill.rs index bc35c846..19fe728d 100644 --- a/server/src/skill.rs +++ b/server/src/skill.rs @@ -260,7 +260,7 @@ impl Effect { pub fn duration(&self) -> u8 { match self { - Effect::Stun => 1, + Effect::Stun => 2, Effect::Block => 1, Effect::Parry => 1, @@ -1051,8 +1051,8 @@ mod tests { .create(); // ensure it doesn't have 0 pd - x.phys_dmg.set(100); - y.hp.set(500); + x.phys_dmg.force(100); + y.hp.force(500); block(&mut y.clone(), &mut y, Resolution::new(Skill::Block)); assert!(y.effects.iter().any(|e| e.effect == Effect::Block)); @@ -1078,11 +1078,11 @@ mod tests { .create(); // ensure it doesn't have 0 sd - x.spell_dmg.set(50); + x.spell_dmg.force(50); // remove all mitigation - y.armour.set(0); - y.spell_shield.set(0); + y.armour.force(0); + y.spell_shield.force(0); y.deal_phys_dmg(Skill::Attack, 5); let prev_hp = y.hp(); @@ -1118,7 +1118,7 @@ mod tests { .level(8) .create(); - x.spell_dmg.set(50); + x.spell_dmg.force(50); amplify(&mut x.clone(), &mut x, Resolution::new(Skill::Amplify)); assert!(x.effects.iter().any(|e| e.effect == Effect::Amplify)); diff --git a/server/src/spec.rs b/server/src/spec.rs new file mode 100644 index 00000000..78fc154c --- /dev/null +++ b/server/src/spec.rs @@ -0,0 +1,54 @@ +use cryp::{Stat}; + +#[derive(Debug,Copy,Clone,Serialize,Deserialize)] +pub enum SpecLevel { + Common, + Uncommon, + Rare, +} + +#[derive(Debug,Copy,Clone,Serialize,Deserialize)] +pub struct Spec { + pub affects: Stat, + pub spec: SpecType, + pub level: SpecLevel, +} + +impl Spec { + pub fn new(spec_type: SpecType) -> Spec { + Spec { + affects: spec_type.affects(), + level: spec_type.level(), + spec: spec_type, + } + } + + pub fn apply(&self, modified: u64, base: u64) -> u64 { + match self.spec { + SpecType::PhysDamage5 => modified + ( base * 105 / 100), + SpecType::SpellDamage5 => modified + ( base * 105 / 100), + } + } +} + +#[derive(Debug,Copy,Clone,Serialize,Deserialize)] +pub enum SpecType { + PhysDamage5, + SpellDamage5, +} + +impl SpecType { + fn affects(&self) -> Stat { + match *self { + SpecType::PhysDamage5 => Stat::PhysicalDamage, + SpecType::SpellDamage5 => Stat::SpellDamage, + } + } + + fn level(&self) -> SpecLevel { + match *self { + SpecType::PhysDamage5 => SpecLevel::Common, + SpecType::SpellDamage5 => SpecLevel::Common, + } + } +} diff --git a/server/src/zone.rs b/server/src/zone.rs index 6633a83f..b70eaab8 100644 --- a/server/src/zone.rs +++ b/server/src/zone.rs @@ -412,6 +412,6 @@ mod tests { fn zone_joinable_test() { let graph = create_zone_graph(); assert!(node_joinable(&graph, NodeIndex::from(1)).is_ok()); - assert!(node_joinable(&graph, NodeIndex::from(2)).is_err()); + assert!(node_joinable(&graph, NodeIndex::from(graph.node_count() as u32 - 1)).is_err()); } }