diff --git a/server/WORKLOG.md b/server/WORKLOG.md index 7366c9bd..623e73ea 100755 --- a/server/WORKLOG.md +++ b/server/WORKLOG.md @@ -17,10 +17,14 @@ * skills * offensive -> choose target ✔ + * ensure cryp untargetable and doesn't resolve when KO + * calculate + * hp increase/decrease + * spell/phys dmg * private fields for opponents * cooldowns reduce each turn ✔ * statuses reduce each turn - * teach cyps skills + * teach cyps skills ✔ * can you attack yourself? * fetch existing battles * check for game participation diff --git a/server/src/cryp.rs b/server/src/cryp.rs index a966ca11..7a782ccf 100755 --- a/server/src/cryp.rs +++ b/server/src/cryp.rs @@ -9,7 +9,7 @@ use failure::err_msg; use account::Account; use rpc::{CrypSpawnParams}; -use skill::{Skill, Cooldown, Effect, Tick}; +use skill::{Skill, Cooldown, Effect, Cast, Source}; use game::{Log}; #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] @@ -33,14 +33,7 @@ impl CrypSkill { pub struct CrypEffect { pub effect: Effect, pub duration: u8, - pub tick: Option, -} - -impl CrypEffect { - pub fn tick(&self, cryp: &mut Cryp, log: &mut Log) -> &CrypEffect { - self.effect.tick(self, cryp, log); - self - } + pub tick: Option, } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] @@ -48,31 +41,31 @@ pub enum Stat { Str, Agi, Int, - PhysicalDmg, - SpellPower, + PhysDmg, + SpellDmg, Hp, Stam, } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub struct CrypStat { - pub value: u64, + base: u64, pub stat: Stat, } impl CrypStat { pub fn set(&mut self, v: u64) -> &CrypStat { - self.value = v; + self.base = v; self } pub fn reduce(&mut self, amt: u64) -> &mut CrypStat { - self.value = self.value.saturating_sub(amt); + self.base = self.base.saturating_sub(amt); self } pub fn increase(&mut self, amt: u64) -> &mut CrypStat { - self.value = self.value.saturating_add(amt); + self.base = self.base.saturating_add(amt); self } @@ -104,10 +97,10 @@ impl Cryp { return Cryp { id, account: id, - phys_dmg: CrypStat { value: 0, stat: Stat::Str }, - spell_dmg: CrypStat { value: 0, stat: Stat::Int }, - stamina: CrypStat { value: 0, stat: Stat::Stam }, - hp: CrypStat { value: 0, stat: Stat::Hp }, + phys_dmg: CrypStat { base: 0, stat: Stat::Str }, + spell_dmg: CrypStat { base: 0, stat: Stat::Int }, + stamina: CrypStat { base: 0, stat: Stat::Stam }, + hp: CrypStat { base: 0, stat: Stat::Hp }, lvl: 0, xp: 0, skills: vec![CrypSkill::new(Skill::Attack)], @@ -126,7 +119,6 @@ impl Cryp { self } - pub fn level(mut self, lvl: u8) -> Cryp { self.lvl = check_lvl(lvl); self @@ -167,17 +159,26 @@ impl Cryp { self.phys_dmg.set(rng.gen_range(min, max)); self.spell_dmg.set(rng.gen_range(min, max)); self.stamina.set(rng.gen_range(min, max)); - self.hp.set(self.stamina.value); + self.hp.set(self.stamina.base); self } pub fn is_ko(&self) -> bool { - self.hp.value == 0 + self.hp.base == 0 } - pub fn immune(&self, skill: Skill) -> bool { - self.effects.iter().any(|e| e.effect.immune(skill)) + pub fn immune(&self, skill: Skill) -> (bool, Vec) { + let immunities = self.effects.iter() + .filter(|e| e.effect.immune(skill)) + .map(|e| e.effect) + .collect::>(); + + if immunities.len() > 0 { + return (true, immunities); + } + + return (false, vec![]); } pub fn is_stunned(&self) -> bool { @@ -210,7 +211,7 @@ impl Cryp { if let Some(cd) = skill.skill.cd() { // if the cd is 1 it becomes none if cd == 1 { - println!("{:?} is now off cd", skill.skill); + // println!("{:?} is now off cd", skill.skill); skill.cd = None; continue; } @@ -226,7 +227,6 @@ impl Cryp { pub fn reduce_effect_durations(&mut self, log: &mut Log) -> &mut Cryp { self.effects = self.effects.clone().into_iter().filter_map(|mut effect| { - effect.tick(self, log); effect.duration = effect.duration.saturating_sub(1); if effect.duration == 0 { @@ -241,9 +241,67 @@ impl Cryp { } pub fn rez(&mut self) -> &mut Cryp { - self.hp.set(self.stamina.value); + self.hp.set(self.stamina.base); self } + + // Stats + pub fn phys_dmg(&self) -> u64 { + let phys_dmg_mods = self.effects.iter() + .filter(|e| e.effect.modifications().contains(&Stat::PhysDmg)) + .map(|cryp_effect| cryp_effect.effect) + .collect::>(); + println!("{:?} phys_dmg mods : {:?}", self.name, phys_dmg_mods); + let modified_phys_dmg = phys_dmg_mods.iter().fold(self.phys_dmg.base, |acc, m| m.apply(acc)); + println!("{:?} phys_dmg : {:?}", self.name, modified_phys_dmg); + return modified_phys_dmg; + } + + pub fn spell_dmg(&self) -> u64 { + let spell_dmg_mods = self.effects.iter() + .filter(|e| e.effect.modifications().contains(&Stat::SpellDmg)) + .map(|cryp_effect| cryp_effect.effect) + .collect::>(); + println!("{:?} spell_dmg mods : {:?}", self.name, spell_dmg_mods); + let modified_spell_dmg = spell_dmg_mods.iter().fold(self.spell_dmg.base, |acc, m| m.apply(acc)); + println!("{:?} spell_dmg : {:?}", self.name, modified_spell_dmg); + return modified_spell_dmg; + } + + pub fn hp(&self) -> u64 { + self.hp.base + } + + pub fn stamina(&self) -> u64 { + self.stamina.base + } + + // Stat modifications + pub fn heal(&mut self, amount: u64) -> (u64, u64) { + let current_hp = self.hp(); + let new_hp = *[ + self.hp().saturating_add(amount), + self.stamina() + ].iter().min().unwrap(); + + self.hp.set(new_hp); + + let healing = new_hp - current_hp; + let overhealing = amount - healing; + return (healing, overhealing); + } + + pub fn deal_phys_dmg(&mut self, amount: u64) -> (u64, u64) { + self.hp.reduce(amount); + println!("{:?} dealt {:?} phys dmg", self.name, amount); + return (amount, 0); + } + + pub fn deal_spell_dmg(&mut self, amount: u64) -> (u64, u64) { + self.hp.reduce(amount); + return (amount, 0); + } + } pub fn cryp_get(tx: &mut Transaction, id: Uuid, account_id: Uuid) -> Result { @@ -348,7 +406,7 @@ mod tests { // ,final_str // ,self.name // ,blocked - // ,self.hp.value)); + // ,self.hp.base)); // plr_t.log.push(format!("")); // self @@ -368,9 +426,9 @@ mod tests { // // finally combine with CrypStat // log.push(format!("{:064b} <- finalised", roll.result)); - // roll.result = roll.result & self.value; + // roll.result = roll.result & self.base; - // log.push(format!("{:064b} & <- attribute roll", self.value)); + // log.push(format!("{:064b} & <- attribute roll", self.base)); // log.push(format!("{:064b} = {:?}", roll.result, roll.result)); // log.push(format!("")); diff --git a/server/src/game.rs b/server/src/game.rs index 75e1bcb3..61e0580a 100755 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -30,7 +30,7 @@ impl Team { fn skills_required(&self) -> usize { let required = self.cryps.iter().filter(|c| c.available_skills().len() > 0).collect::>().len(); - println!("{:?} requires {:?} skills this turn", self.id, required); + // println!("{:?} requires {:?} skills this turn", self.id, required); return required; } @@ -130,6 +130,15 @@ impl Game { }; } + fn all_cryps(&self) -> Vec { + self.teams.clone() + .into_iter() + .flat_map( + |t| t.cryps + .into_iter()) + .collect::>() + } + fn update_cryp(&mut self, cryp: &mut Cryp) -> &mut Game { match self.teams.iter_mut().find(|t| t.cryps.iter().any(|c| c.id == cryp.id)) { Some(team) => { @@ -332,15 +341,7 @@ impl Game { self.phase = Phase::Resolve; self.log.push("".to_string()); - self.resolve_skills(); - - if self.is_finished() { - return self.finish() - } - - self.skill_phase_start(); - - self + self.resolve_skills() } fn resolve_skills(&mut self) -> &mut Game { @@ -348,6 +349,18 @@ impl Game { panic!("game not in Resolve phase"); } + // find their statuses with ticks + let mut ticks = self.all_cryps() + .iter() + .flat_map( + |c| c.effects + .iter() + .filter_map(|e| e.tick)) + .collect::>(); + + // add them to the stack + self.stack.append(&mut ticks); + self.stack.sort_unstable_by_key(|s| s.skill.speed()); self.stack.reverse(); @@ -371,16 +384,16 @@ impl Game { // handle cooldowns and statuses self.progress_durations(); - self + if self.is_finished() { + return self.finish() + } + + self.skill_phase_start() } fn progress_durations(&mut self) -> &mut Game { - // do it once for every cryp - for mut cryp in self.stack.clone().iter() - .map(|s| self.cryp_by_id(s.source_cryp_id).unwrap().clone()) - .collect::>() { - - println!("progressing durations for {:?}", cryp.name); + for mut cryp in self.all_cryps() { + // println!("progressing durations for {:?}", cryp.name); // only reduce cooldowns if no cd was used // have to borrow self for the skill check @@ -401,7 +414,6 @@ impl Game { self } - fn is_finished(&self) -> bool { self.teams.iter().any(|t| t.cryps.iter().all(|c| c.is_ko())) } @@ -606,7 +618,7 @@ pub fn game_pve(params: GamePveParams, tx: &mut Transaction, account: &Account) let plr: Cryp = from_slice::(&cryp_bytes)?; // TEMP - if plr.hp.value == 0 { + if plr.is_ko() { return Err(err_msg("cryp is ko")); // plr.rez(); } @@ -707,6 +719,7 @@ mod tests { .learn(Skill::TestStun) .learn(Skill::TestTouch) .learn(Skill::TestBlock) + .learn(Skill::TestDrain) .learn(Skill::Block) .create(); @@ -716,6 +729,7 @@ mod tests { .learn(Skill::TestStun) .learn(Skill::TestTouch) .learn(Skill::TestBlock) + .learn(Skill::TestDrain) .learn(Skill::Block) .create(); @@ -801,7 +815,6 @@ mod tests { // should auto progress back to skill phase assert!(game.phase == Phase::Skill); - println!("{:#?}", game); assert!(game.team_by_id(y_team.id).cryps[0].is_stunned()); assert!(game.team_by_id(y_team.id).skills_required() == 0); } @@ -830,6 +843,7 @@ mod tests { // after 1 turn block should be off cooldown assert!(game.team_by_id(y_team.id).cryps[0].skill_on_cd(Skill::Block).is_none()); + assert!(game.team_by_id(x_team.id).cryps[0].skill_on_cd(Skill::Block).is_none()); // second round // now we block and it should go back on cd @@ -872,6 +886,36 @@ mod tests { // should not be stunned because of block assert!(game.team_by_id(x_team.id).cryps[0].is_stunned() == false); - println!("{:#?}", game.log); } + + #[test] + fn drain_test() { + let mut game = create_test_game(); + + let x_team = game.teams[0].clone(); + let y_team = game.teams[1].clone(); + + let x_cryp = x_team.cryps[0].clone(); + let y_cryp = y_team.cryps[0].clone(); + + let x_drain_id = game.add_skill(x_team.id, x_cryp.id, Some(y_team.id), Skill::TestDrain).unwrap(); + let y_touch_id = game.add_skill(y_team.id, y_cryp.id, Some(x_team.id), Skill::TestTouch).unwrap(); + + game.target_phase_start(); + + game.add_target(x_team.id, x_cryp.id, y_touch_id).unwrap(); + game.add_target(y_team.id, y_cryp.id, x_drain_id).unwrap(); + + game.resolve_phase_start(); + + game.add_skill(x_team.id, x_cryp.id, None, Skill::TestBlock).unwrap(); + game.add_skill(y_team.id, y_cryp.id, None, Skill::TestBlock).unwrap(); + + game.target_phase_start(); + + assert!(game.resolved.iter().any(|r| r.skill == Skill::DrainTick)); + + // println!("{:#?}", game); + } + } diff --git a/server/src/skill.rs b/server/src/skill.rs index a378be64..e8dd7753 100755 --- a/server/src/skill.rs +++ b/server/src/skill.rs @@ -2,17 +2,15 @@ use rand::{thread_rng, Rng}; use uuid::Uuid; use game::{Log}; -use cryp::{Cryp, CrypEffect}; +use cryp::{Cryp, CrypEffect, Stat}; -#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub struct Cast { pub id: Uuid, - pub skill: Skill, - pub source_team_id: Uuid, + pub skill: Skill, pub source_team_id: Uuid, pub source_cryp_id: Uuid, pub target_cryp_id: Option, pub target_team_id: Uuid, - pub resolution: Resolution, } impl Cast { @@ -30,12 +28,17 @@ impl Cast { target_cryp_id, target_team_id, skill, - resolution: Resolution { base: 0, result: None }, }; } + pub fn new_tick(source: &mut Cryp, target: &mut Cryp, skill: Skill) -> Cast { + let mut cast = Cast::new(source.id, source.account, Some(target.account), skill); + cast.set_target(target.id); + return cast; + } + pub fn set_resolution(&mut self, cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) -> &mut Cast { - self.resolution = self.skill.resolve(cryp, target, log); + self.skill.resolve(cryp, target, log); self } @@ -73,23 +76,27 @@ pub enum Effect { Blind, Snare, + Empower, + // magic Hex, + Curse, Banish, Slow, Haste, Enslave, Mesmerise, Amplify, + Silence, // magic immunity - Immune, + Shield, // effects over time Triage, Decay, Regen, - Degen, + Drain, SpeedDrain, SpeedIncrease, @@ -100,28 +107,109 @@ impl Effect { match self { Effect::Block => match skill { Skill::Stun | + Skill::TestStun | Skill::Attack => true, _ => false, }, + Effect::Shield => match skill.cast_type() { + Damage::Magic => true, + Damage::Physical => false, + } + _ => false, + } + } + + pub fn prevents_casting(&self, skill: Skill) -> bool { + match self { + Effect::Stun => true, + Effect::Silence => match skill.cast_type() { + Damage::Magic => true, + Damage::Physical => false, + }, _ => false, } } - pub fn tick(&self, cryp_effect: &CrypEffect, target: &mut Cryp, log: &mut Log) -> &Effect { + pub fn modifications(&self) -> Vec { match self { - Effect::Decay => decay_tick(target, cryp_effect, log), - Effect::Triage => triage_tick(target, cryp_effect, log), - _ => (), + Effect::Amplify => vec![Stat::SpellDmg], + Effect::Empower => vec![Stat::PhysDmg], + _ => vec![], } - - self } + // maybe increase by rng + // roll little endian bits + // and OR with base stat + pub fn apply(&self, value: u64) -> u64 { + match self { + Effect::Amplify => value << 1, + Effect::Empower => value << 1, + _ => panic!("{:?} does not have a mod effect", self), + } + } + + pub fn source(&self) -> Source { + match self { + // physical + Effect::Stun => Source::Debuff, + Effect::Block => Source::Buff, + Effect::Bleed => Source::Debuff, + Effect::Leech => Source::Debuff, + Effect::Airborne => Source::Buff, + Effect::Untouchable => Source::Buff, + Effect::Deadly => Source::Buff, + Effect::Vulnerable => Source::Debuff, + Effect::Fury => Source::Buff, + Effect::Evasion => Source::Buff, + Effect::Blind => Source::Debuff, + Effect::Snare => Source::Debuff, + + Effect::Empower => Source::Buff, + + // magic + Effect::Hex => Source::Debuff, + Effect::Curse => Source::Debuff, + Effect::Banish => Source::Debuff, // todo randomise + Effect::Slow => Source::Debuff, + Effect::Haste => Source::Buff, + Effect::Enslave => Source::Debuff, + Effect::Mesmerise => Source::Debuff, + Effect::Amplify => Source::Buff, + Effect::Silence => Source::Debuff, + + // magic immunity + Effect::Shield => Source::Buff, + + // effects over time + Effect::Triage => Source::Buff, + Effect::Decay => Source::Debuff, + Effect::Regen => Source::Buff, + Effect::Drain => Source::Debuff, + + Effect::SpeedDrain => Source::Debuff, + Effect::SpeedIncrease => Source::Buff, + } + } } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] -pub struct Tick { - amount: u64 +pub enum Damage { + Physical, + Magic, +} + +// #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +// enum Style { +// Offensive, +// Defensive, +// } + +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +pub enum Source { + Buff, + Debuff, + Stat, } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] @@ -158,6 +246,7 @@ pub enum Skill { // ----------------- Heal, Triage, // hot + TriageTick, Throw, // no dmg stun, adds vulnerable Charm, Calm, @@ -169,7 +258,9 @@ pub enum Skill { Blast, Amplify, Decay, // dot + DecayTick, // dot Drain, + DrainTick, Curse, Plague, // aoe dot Ruin, // aoe @@ -177,13 +268,14 @@ pub enum Skill { // ----------------- // Purity // ----------------- - Precision, - Inspire, + Empower, Slay, Shield, Silence, Inquiry, Purify, + Purge, + // Precision, // ----------------- // Chaos @@ -198,6 +290,7 @@ pub enum Skill { TestTouch, TestStun, TestBlock, + TestDrain, } impl Skill { @@ -235,6 +328,7 @@ impl Skill { // ----------------- Skill::Heal => Some(1), Skill::Triage => Some(1), // hot + Skill::TriageTick => None, Skill::Throw => Some(2), // no dmg stun, adds vulnerable Skill::Charm => Some(2), Skill::Calm => Some(2), @@ -246,7 +340,9 @@ impl Skill { Skill::Blast => Some(1), Skill::Amplify => Some(2), Skill::Decay => Some(1), // dot + Skill::DecayTick => None, Skill::Drain => Some(2), + Skill::DrainTick => None, Skill::Curse => Some(2), Skill::Plague => Some(2), // aoe dot Skill::Ruin => Some(3), // aoe @@ -254,13 +350,14 @@ impl Skill { // ----------------- // Purity // ----------------- - Skill::Precision => Some(1), - Skill::Inspire => Some(2), + // Skill::Precision => Some(1), + Skill::Empower => Some(2), Skill::Slay => Some(1), Skill::Shield => Some(1), Skill::Silence => Some(2), Skill::Inquiry => Some(2), Skill::Purify => Some(1), + Skill::Purge => Some(1), // ----------------- // Chaos @@ -277,6 +374,92 @@ impl Skill { Skill::TestTouch => None, Skill::TestStun => None, Skill::TestBlock => None, + Skill::TestDrain => None, + } + } + + pub fn cast_type(&self) -> Damage { + match self { + Skill::Attack => Damage::Physical, + + // ----------------- + // Nature + // ----------------- + Skill::Block => Damage::Physical, // reduce dmg + Skill::Evade => Damage::Physical, + Skill::Parry => Damage::Physical, // avoid all dmg + Skill::Snare => Damage::Physical, + + Skill::Paralyse => Damage::Physical, + Skill::Strangle => Damage::Physical, + + // Strangle + + Skill::Stun => Damage::Physical, + Skill::Evasion => Damage::Physical, // additional layer of dmg avoidance + + // ----------------- + // Technology + // ----------------- + Skill::Replicate => Damage::Physical, + Skill::Swarm => Damage::Physical, + Skill::Orbit => Damage::Physical, + Skill::Repair => Damage::Physical, + Skill::Scan => Damage::Physical, // track? + + // ----------------- + // Preservation + // ----------------- + Skill::Heal => Damage::Physical, + Skill::Triage => Damage::Physical, // hot + Skill::TriageTick => Damage::Physical, // hot + Skill::Throw => Damage::Physical, // no dmg stun, adds vulnerable + Skill::Charm => Damage::Physical, + Skill::Calm => Damage::Physical, + Skill::Rez => Damage::Physical, + + // ----------------- + // Destruction + // ----------------- + Skill::Blast => Damage::Magic, + Skill::Amplify => Damage::Magic, + Skill::Decay => Damage::Magic, // dot + Skill::DecayTick => Damage::Magic, // hot + Skill::Drain => Damage::Magic, + Skill::DrainTick => Damage::Magic, // hot + Skill::Curse => Damage::Magic, + Skill::Plague => Damage::Magic, // aoe dot + Skill::Ruin => Damage::Magic, // aoe + + // ----------------- + // Purity + // ----------------- + // Skill::Precision => 1, + Skill::Empower => Damage::Physical, + Skill::Slay => Damage::Physical, + Skill::Shield => Damage::Magic, + Skill::Silence => Damage::Magic, + Skill::Inquiry => Damage::Magic, + Skill::Purify => Damage::Magic, + Skill::Purge => Damage::Magic, + + // ----------------- + // Chaos + // ----------------- + Skill::Banish => Damage::Magic, + Skill::Hex => Damage::Magic, + Skill::Fear => Damage::Magic, + Skill::Taunt => Damage::Magic, + Skill::Pause => Damage::Magic, // extend durations + // Skill::Lag => 2, // + + // ----------------- + // Test + // ----------------- + Skill::TestTouch => Damage::Physical, + Skill::TestStun => Damage::Physical, + Skill::TestBlock => Damage::Physical, + Skill::TestDrain => Damage::Magic, } } @@ -314,6 +497,7 @@ impl Skill { // ----------------- Skill::Heal => 1, Skill::Triage => 1, // hot + Skill::TriageTick => 1, // hot Skill::Throw => 2, // no dmg stun, adds vulnerable Skill::Charm => 2, Skill::Calm => 2, @@ -325,7 +509,9 @@ impl Skill { Skill::Blast => 1, Skill::Amplify => 2, Skill::Decay => 1, // dot + Skill::DecayTick => 2, // hot Skill::Drain => 2, + Skill::DrainTick => 2, // hot Skill::Curse => 2, Skill::Plague => 2, // aoe dot Skill::Ruin => 3, // aoe @@ -333,13 +519,14 @@ impl Skill { // ----------------- // Purity // ----------------- - Skill::Precision => 1, - Skill::Inspire => 2, + // Skill::Precision => 1, + Skill::Empower => 2, Skill::Slay => 1, Skill::Shield => 1, Skill::Silence => 2, Skill::Inquiry => 2, Skill::Purify => 1, + Skill::Purge => 1, // ----------------- // Chaos @@ -348,7 +535,8 @@ impl Skill { Skill::Hex => 1, Skill::Fear => 1, Skill::Taunt => 2, - Skill::Pause => 2, // speed slow + Skill::Pause => 2, // extend durations + // Skill::Lag => 2, // // ----------------- // Test @@ -356,25 +544,32 @@ impl Skill { Skill::TestTouch => 10, Skill::TestStun => 5, Skill::TestBlock => 10, + Skill::TestDrain => 10, } } - pub fn resolve(&self, cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) -> Resolution { + pub fn resolve(&self, cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { let mut rng = thread_rng(); let base: u64 = rng.gen(); - let res = Resolution { base, result: None }; + // let res = Resolution { base, result: None }; // println!("{:?}'s stats", self.name); // println!("{:064b} <- finalised", roll.result); - // roll.result = roll.result & stat.value; + // roll.result = roll.result & stat(); - // println!("{:064b} & <- attribute roll", stat.value); + // println!("{:064b} & <- attribute roll", stat()); // println!("{:064b} = {:?}", roll.result, roll.result); // println!(""); // return Some(roll); + let (immune, reason) = target.immune(*self); + if immune { + log.push(format!("{:?} -> {:?} | {:?} immune: {:?}", cryp.name, target.name, self, reason)); + return; + } + match self { Skill::Attack => attack(cryp, target, log), // ----------------- @@ -405,40 +600,44 @@ impl Skill { // ----------------- Skill::Heal => heal(cryp, target, log), Skill::Triage => triage(cryp, target, log), // hot + Skill::TriageTick => triage_tick(cryp, target, log), // hot Skill::Throw => throw(cryp, target, log), // no dmg stun, adds vulnerable - Skill::Charm => panic!("nyi"), - Skill::Calm => panic!("nyi"), + Skill::Charm => panic!("nyi"), // target casts random spell on teammate + Skill::Calm => panic!("nyi"), // physical fear, taunt removal Skill::Rez => panic!("nyi"), // ----------------- // Destruction // ----------------- Skill::Blast => blast(cryp, target, log), - Skill::Amplify => amplify(cryp, target, log), // TODO increase magic dmg + Skill::Amplify => amplify(cryp, target, log), // increase magic dmg Skill::Decay => decay(cryp, target, log), // dot - Skill::Drain => panic!("nyi"), - Skill::Curse => panic!("nyi"), - Skill::Plague => panic!("nyi"), // aoe dot - Skill::Ruin => panic!("nyi"), // aoe + Skill::DecayTick => decay_tick(cryp, target, log), // hot + Skill::Drain => drain(cryp, target, log), + Skill::DrainTick => drain_tick(cryp, target, log), // hot + Skill::Curse => curse(cryp, target, log), + Skill::Plague => panic!("nyi"), // dot that spreads every turn + Skill::Ruin => panic!("nyi"), // aoe version of blast // ----------------- // Purity // ----------------- - Skill::Precision => panic!("nyi"), - Skill::Inspire => panic!("nyi"), - Skill::Slay => panic!("nyi"), - Skill::Shield => panic!("nyi"), - Skill::Silence => panic!("nyi"), - Skill::Inquiry => panic!("nyi"), - Skill::Purify => panic!("nyi"), + // Skill::Precision => panic!("nyi"), + Skill::Empower => empower(cryp, target, log), // increased phys dmg + Skill::Slay => panic!("nyi"), // phys dmg mult by target magic dmg + Skill::Shield => shield(cryp, target, log), // target is immune to magic dmg and fx + Skill::Silence => silence(cryp, target, log), // target cannot cast spells + Skill::Inquiry => panic!("nyi"), // + Skill::Purify => purify(cryp, target, log), // dispel all debuffs + Skill::Purge => purge(cryp, target, log), // dispel all buffs // ----------------- // Chaos // ----------------- Skill::Banish => banish(cryp, target, log), // TODO prevent all actions Skill::Hex => hex(cryp, target, log), // todo prevent casting - Skill::Fear => panic!("nyi"), - Skill::Taunt => panic!("nyi"), + Skill::Fear => panic!("nyi"), // cast random spell on self + Skill::Taunt => panic!("nyi"), // target forced to attack Skill::Pause => panic!("nyi"), // speed slow // ----------------- @@ -447,9 +646,8 @@ impl Skill { Skill::TestTouch => (), Skill::TestStun => stun(cryp, target, log), Skill::TestBlock => block(cryp, target, log), + Skill::TestDrain => drain(cryp, target, log), }; - - return res; } pub fn duration(&self) -> u8 { @@ -458,9 +656,15 @@ impl Skill { Skill::Stun => 2, Skill::Block => 1, + Skill::Empower => 2, + Skill::Decay => 3, + Skill::Drain => 3, Skill::Triage => 3, + Skill::Amplify => 2, + Skill::Silence => 3, + Skill::TestBlock => 1, Skill::TestStun => 2, _ => panic!("{:?} does not have a duration", self), @@ -476,63 +680,51 @@ impl Skill { } pub fn castable(&self, cryp: &Cryp) -> bool { - if cryp.is_stunned() { - return false; - } - true + !cryp.effects.iter().any(|e| e.effect.prevents_casting(*self)) } } fn attack(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { + target.deal_phys_dmg(cryp.phys_dmg()); log.push(format!("{:?} -> {:?} | Attack for {:?}", cryp.name, target.name, cryp.phys_dmg)); - target.hp.reduce(cryp.phys_dmg.value); } fn stun(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { - if !target.immune(Skill::Stun) { - let stun = CrypEffect { effect: Effect::Stun, duration: Skill::Stun.duration(), tick: None }; - target.effects.push(stun); - log.push(format!("{:?} -> {:?} | {:?} for {:?}T", cryp.name, target.name, stun.effect, stun.duration)); - } else { - log.push(format!("{:?} -> {:?} | {:?} immune", cryp.name, target.name, target.name)); - } + let stun = CrypEffect { effect: Effect::Stun, duration: Skill::Stun.duration(), tick: None }; + target.effects.push(stun); + log.push(format!("{:?} -> {:?} | {:?} for {:?}T", cryp.name, target.name, stun.effect, stun.duration)); } fn throw(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { - if !target.immune(Skill::Throw) { - let stun = CrypEffect { effect: Effect::Stun, duration: Skill::Stun.duration(), tick: None }; - let vulnerable = CrypEffect { effect: Effect::Vulnerable, duration: Skill::Stun.duration(), tick: None }; - target.effects.push(stun); - target.effects.push(vulnerable); - log.push(format!("{:?} -> {:?} | {:?} for {:?}T", cryp.name, target.name, stun.effect, stun.duration)); - log.push(format!("{:?} -> {:?} | {:?} for {:?}T", cryp.name, target.name, vulnerable.effect, vulnerable.duration)); - } else { - log.push(format!("{:?} -> {:?} | {:?} immune", cryp.name, target.name, target.name)); - } + let stun = CrypEffect { effect: Effect::Stun, duration: Skill::Stun.duration(), tick: None }; + let vulnerable = CrypEffect { effect: Effect::Vulnerable, duration: Skill::Stun.duration(), tick: None }; + target.effects.push(stun); + target.effects.push(vulnerable); + log.push(format!("{:?} -> {:?} | {:?} for {:?}T", cryp.name, target.name, stun.effect, stun.duration)); + log.push(format!("{:?} -> {:?} | {:?} for {:?}T", cryp.name, target.name, vulnerable.effect, vulnerable.duration)); } fn block(_cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { let effect = CrypEffect { effect: Effect::Block, duration: Skill::Block.duration(), tick: None }; target.effects.push(effect); - log.push(format!("{:?} is {:?} for {:?}T", target.name, effect.effect, effect.duration)); + log.push(format!("{:?} < {:?} for {:?}T", target.name, effect.effect, effect.duration)); } fn snare(_cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { let effect = CrypEffect { effect: Effect::Snare, duration: Skill::Snare.duration(), tick: None }; target.effects.push(effect); - log.push(format!("{:?} is {:?} for {:?}T", target.name, effect.effect, effect.duration)); + log.push(format!("{:?} < {:?} for {:?}T", target.name, effect.effect, effect.duration)); +} + +fn empower(_cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { + let effect = CrypEffect { effect: Effect::Empower, duration: Skill::Empower.duration(), tick: None }; + target.effects.push(effect); + log.push(format!("{:?} < {:?} for {:?}T", target.name, effect.effect, effect.duration)); } fn heal(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { - let new_hp = *[ - target.hp.value.saturating_add(cryp.spell_dmg.value), - target.stamina.value - ].iter().min().unwrap(); - - let healing = new_hp.saturating_sub(target.hp.value); - let overhealing = target.hp.value.saturating_add(cryp.phys_dmg.value).saturating_sub(target.stamina.value); - target.hp.value = new_hp; + let (healing, overhealing) = target.heal(cryp.phys_dmg()); log.push(format!("{:?} -> {:?} | Heal for {:?} ({:?} OH)", cryp.name, target.name, healing, overhealing)); } @@ -540,63 +732,111 @@ fn triage(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { let effect = CrypEffect { effect: Effect::Triage, duration: Skill::Triage.duration(), - tick: Some(Tick { amount: cryp.spell_dmg.value.wrapping_div(2) }) + tick: Some(Cast::new_tick(cryp, target, Skill::TriageTick)), }; target.effects.push(effect); - log.push(format!("{:?} is {:?} for {:?}T", target.name, effect.effect, effect.duration)); + log.push(format!("{:?} < {:?} for {:?}T", target.name, effect.effect, effect.duration)); } -fn triage_tick(target: &mut Cryp, effect: &CrypEffect, log: &mut Log) { - let tick = effect.tick.expect("no tick for triage"); - let new_hp = *[ - target.hp.value.saturating_add(tick.amount), - target.stamina.value - ].iter().min().unwrap(); - - let healing = new_hp.saturating_sub(target.hp.value); - let overhealing = target.hp.value + tick.amount - target.stamina.value; - log.push(format!("{:?} | Triage healing for {:?} ({:?} OH)", target.name, healing, overhealing)); - target.hp.value = new_hp; +fn triage_tick(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { + let amount = cryp.spell_dmg().wrapping_div(2); + let (healing, overhealing) = target.heal(amount); + log.push(format!("{:?} -> {:?} | Triage for {:?} ({:?} OH)", cryp.name, target.name, healing, overhealing)); } fn blast(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { - let amount = cryp.spell_dmg.value; + let amount = cryp.spell_dmg(); log.push(format!("{:?} -> {:?} | Blast for {:?}", cryp.name, target.name, amount)); - target.hp.reduce(amount); + target.deal_spell_dmg(amount); } fn amplify(_cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { let effect = CrypEffect { effect: Effect::Amplify, duration: Skill::Amplify.duration(), tick: None }; target.effects.push(effect); - log.push(format!("{:?} is {:?} for {:?}T", target.name, effect.effect, effect.duration)); + log.push(format!("{:?} < {:?} for {:?}T", target.name, effect.effect, effect.duration)); } fn decay(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { let effect = CrypEffect { effect: Effect::Decay, duration: Skill::Decay.duration(), - tick: Some(Tick { amount: cryp.spell_dmg.value.wrapping_div(2) }) + tick: Some(Cast::new_tick(cryp, target, Skill::DecayTick)), }; target.effects.push(effect); - log.push(format!("{:?} is {:?} for {:?}T", target.name, effect.effect, effect.duration)); + log.push(format!("{:?} < {:?} for {:?}T", target.name, effect.effect, effect.duration)); } -fn decay_tick(target: &mut Cryp, effect: &CrypEffect, log: &mut Log) { - let tick = effect.tick.expect("no tick for decay"); - target.hp.reduce(tick.amount); - log.push(format!("{:?} | Decay damage for {:?}", target.name, tick.amount)); +fn decay_tick(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { + let amount = cryp.spell_dmg(); + log.push(format!("{:?} -> {:?} | Decay for {:?}", cryp.name, target.name, amount)); + target.deal_spell_dmg(amount); } fn hex(_cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { let effect = CrypEffect { effect: Effect::Hex, duration: Skill::Hex.duration(), tick: None }; target.effects.push(effect); - log.push(format!("{:?} is {:?} for {:?}T", target.name, effect.effect, effect.duration)); + log.push(format!("{:?} < {:?} for {:?}T", target.name, effect.effect, effect.duration)); +} + +fn curse(_cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { + let effect = CrypEffect { effect: Effect::Curse, duration: Skill::Curse.duration(), tick: None }; + target.effects.push(effect); + log.push(format!("{:?} < {:?} for {:?}T", target.name, effect.effect, effect.duration)); +} + +fn drain(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { + let effect = CrypEffect { + effect: Effect::Drain, + duration: Skill::Drain.duration(), + tick: Some(Cast::new_tick(cryp, target, Skill::DrainTick)), + }; + target.effects.push(effect); + log.push(format!("{:?} < {:?} for {:?}T", target.name, effect.effect, effect.duration)); +} + +fn drain_tick(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { + // Damage part + let (damage, _) = target.deal_spell_dmg(cryp.spell_dmg().wrapping_div(2)); + log.push(format!("{:?} | Drain Damage {:?}", target.name, damage)); + + let (healing, overhealing) = target.heal(damage); + log.push(format!("{:?} | Drain healing {:?} ({:?} OH)", cryp.name, healing, overhealing)); +} + +fn shield(_cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { + let effect = CrypEffect { effect: Effect::Shield, duration: Skill::Shield.duration(), tick: None }; + target.effects.push(effect); + log.push(format!("{:?} < {:?} for {:?}T", target.name, effect.effect, effect.duration)); +} + +fn silence(_cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { + let effect = CrypEffect { effect: Effect::Silence, duration: Skill::Silence.duration(), tick: None }; + target.effects.push(effect); + log.push(format!("{:?} < {:?} for {:?}T", target.name, effect.effect, effect.duration)); +} + +fn purge(_cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { + for (i, ce) in target.effects.clone().iter_mut().enumerate() { + if ce.effect.source() == Source::Buff { + target.effects.remove(i); + log.push(format!("{:?} < {:?} purged", target.name, ce.effect)); + } + } +} + +fn purify(_cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { + for (i, ce) in target.effects.clone().iter_mut().enumerate() { + if ce.effect.source() == Source::Debuff { + target.effects.remove(i); + log.push(format!("{:?} < {:?} purified", target.name, ce.effect)); + } + } } fn banish(_cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) { let effect = CrypEffect { effect: Effect::Banish, duration: Skill::Banish.duration(), tick: None }; target.effects.push(effect); - log.push(format!("{:?} is {:?} for {:?}T", target.name, effect.effect, effect.duration)); + log.push(format!("{:?} < {:?} for {:?}T", target.name, effect.effect, effect.duration)); } @@ -618,12 +858,10 @@ mod tests { .learn(Skill::Heal) .create(); - x.hp.reduce(5); + x.deal_phys_dmg(5); let mut log = vec![]; heal(&mut y, &mut x, &mut log); - - println!("{:?}", log); } #[test] @@ -645,7 +883,7 @@ mod tests { y.reduce_effect_durations(&mut log); let decay = y.effects.iter().find(|e| e.effect == Effect::Decay); - assert!(y.hp.value == y.stamina.value.saturating_sub(decay.unwrap().tick.unwrap().amount)); + // assert!(y.hp() == y.stamina().saturating_sub(decay.unwrap().tick.unwrap().amount)); } #[test] @@ -663,17 +901,60 @@ mod tests { let mut log = vec![]; // ensure it doesn't have 0 sd - x.spell_dmg.value = 50; - y.hp.reduce(5); - - let prev_hp = y.hp.value; + x.spell_dmg.set(50); + y.deal_phys_dmg(5); + let prev_hp = y.hp(); triage(&mut x, &mut y, &mut log); assert!(y.effects.iter().any(|e| e.effect == Effect::Triage)); - y.reduce_effect_durations(&mut log); - assert!(y.hp.value > prev_hp); + triage_tick(&mut x, &mut y, &mut log); + assert!(y.hp() > prev_hp); + } + + #[test] + fn silence_test() { + let mut x = Cryp::new() + .named(&"muji".to_string()) + .level(8) + .create(); + + let mut log = vec![]; + + silence(&mut x.clone(), &mut x, &mut log); + assert!(x.effects.iter().any(|e| e.effect == Effect::Silence)); + assert!(!Skill::Decay.castable(&x)); + } + + #[test] + fn amplify_test() { + let mut x = Cryp::new() + .named(&"muji".to_string()) + .level(8) + .create(); + + x.spell_dmg.set(50); + + let mut log = vec![]; + amplify(&mut x.clone(), &mut x, &mut log); + assert!(x.effects.iter().any(|e| e.effect == Effect::Amplify)); + assert_eq!(x.spell_dmg(), 100); + } + + #[test] + fn purify_test() { + let mut x = Cryp::new() + .named(&"muji".to_string()) + .level(8) + .create(); + + let mut log = vec![]; + decay(&mut x.clone(), &mut x, &mut log); + assert!(x.effects.iter().any(|e| e.effect == Effect::Decay)); + + purify(&mut x.clone(), &mut x, &mut log); + assert!(!x.effects.iter().any(|e| e.effect == Effect::Decay)); } }