specs with values

This commit is contained in:
ntr 2019-01-16 20:05:31 +11:00
parent 17d71b2258
commit 61cbef6999
7 changed files with 190 additions and 57 deletions

View File

@ -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

View File

@ -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<Spec>,
uncommon: Vec<Spec>,
rare: Vec<Spec>,
}
impl CrypSpecs {
fn new() -> CrypSpecs {
CrypSpecs {
common: vec![],
uncommon: vec![],
rare: vec![],
}
}
fn affects(&self, stat: Stat) -> Vec<Spec> {
[&self.common, &self.uncommon, &self.rare]
.iter()
@ -130,6 +148,7 @@ pub struct Cryp {
pub lvl: u8,
pub skills: Vec<CrypSkill>,
pub effects: Vec<CrypEffect>,
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::<Vec<Effect>>();
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::<Vec<Effect>>();
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::<Vec<Effect>>();
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<ResolutionResult> {
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);

View File

@ -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();

View File

@ -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)?;

View File

@ -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));

54
server/src/spec.rs Normal file
View File

@ -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,
}
}
}

View File

@ -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());
}
}