diff --git a/server/src/cryp.rs b/server/src/cryp.rs index 7cdc4af6..647dbc64 100644 --- a/server/src/cryp.rs +++ b/server/src/cryp.rs @@ -64,6 +64,7 @@ impl CrypSkill { pub enum EffectMeta { TickAmount(u64), AddedDamage(u64), + ScatterTarget(Uuid), } #[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] @@ -470,7 +471,7 @@ impl Cryp { .fold(self.speed.value, |acc, fx| fx.0.apply(acc, fx.1)); return modified_speed; } - + pub fn red_life(&self) -> u64 { self.red_life.value } @@ -563,6 +564,11 @@ impl Cryp { } } + if self.is_ko() { + events.push(Event::Ko { skill }); + self.effects.clear(); + } + return events; } @@ -636,6 +642,12 @@ impl Cryp { } }; + if self.is_ko() { + events.push(Event::Ko { skill }); + self.effects.clear(); + } + + return events; } @@ -705,6 +717,11 @@ impl Cryp { } }; + if self.is_ko() { + events.push(Event::Ko { skill }); + self.effects.clear(); + } + return events; } diff --git a/server/src/game.rs b/server/src/game.rs index 8487140b..3ded010d 100644 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -14,7 +14,7 @@ use failure::err_msg; use account::Account; use rpc::{GameStateParams, GameSkillParams}; use cryp::{Cryp}; -use skill::{Skill, Effect, Cast, Resolution, Event, resolve}; +use skill::{Skill, Effect, Cast, Resolution, Event, resolution_steps}; use player::{Player}; use instance::{instance_game_finished, global_game_finished}; use util::{IntPct}; @@ -133,7 +133,7 @@ impl Game { .collect::>() } - fn update_cryp(&mut self, cryp: &mut Cryp) -> &mut Game { + pub fn update_cryp(&mut self, cryp: &mut Cryp) -> &mut Game { match self.players.iter_mut().find(|t| t.cryps.iter().any(|c| c.id == cryp.id)) { Some(player) => { let index = player.cryps.iter().position(|t| t.id == cryp.id).unwrap(); @@ -373,7 +373,7 @@ impl Game { .collect() } - fn get_targets(&self, skill: Skill, source: &Cryp, target_cryp_id: Uuid) -> Vec { + pub fn get_targets(&self, skill: Skill, source: &Cryp, target_cryp_id: Uuid) -> Vec { let target_player = self.players.iter() .find(|t| t.cryps.iter().any(|c| c.id == target_cryp_id)) .unwrap(); @@ -415,19 +415,7 @@ impl Game { while let Some(cast) = self.stack.pop() { // println!("{:} casts ", cast); - let mut resolutions = vec![]; - let mut source = self.cryp_by_id(cast.source_cryp_id).unwrap().clone(); - - let targets = self.get_targets(cast.skill, &source, cast.target_cryp_id); - for target_id in targets { - let mut source = self.cryp_by_id(cast.source_cryp_id).unwrap().clone(); - let mut target = self.cryp_by_id(target_id).unwrap().clone(); - resolutions = resolve(cast.skill, &mut source, &mut target, resolutions); - // save the clones - self.update_cryp(&mut source); - self.update_cryp(&mut target); - } - + let mut resolutions = resolution_steps(&cast, &mut self); resolutions.reverse(); while let Some(resolution) = resolutions.pop() { self.log_resolution(cast.speed, &resolution); @@ -1175,9 +1163,124 @@ mod tests { // should not be stunned because of parry assert!(game.player_by_id(x_player.id).unwrap().cryps[0].is_stunned() == false); // riposte - assert!(game.player_by_id(y_player.id).unwrap().cryps[0].green_life() == (1024 - x_cryp.red_damage().pct(Skill::Riposte.multiplier()))); + assert_eq!(game.player_by_id(y_player.id).unwrap().cryps[0].green_life(), (1024 - x_cryp.red_damage().pct(Skill::Riposte.multiplier()))); } + #[test] + fn corrupt_test() { + let mut game = create_test_game(); + + let x_player = game.players[0].clone(); + let y_player = game.players[1].clone(); + + let x_cryp = x_player.cryps[0].clone(); + let y_cryp = y_player.cryps[0].clone(); + + game.cryp_by_id(x_cryp.id).unwrap().learn_mut(Skill::Corrupt); + + while game.cryp_by_id(x_cryp.id).unwrap().skill_on_cd(Skill::Corrupt).is_some() { + game.cryp_by_id(x_cryp.id).unwrap().reduce_cooldowns(); + } + + // apply buff + game.add_skill(x_player.id, x_cryp.id, None, Skill::Corrupt).unwrap(); + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); + game = game.resolve_phase_start(); + assert!(game.cryp_by_id(x_cryp.id).unwrap().affected(Effect::Corrupt)); + + // attack and receive debuff + game.add_skill(y_player.id, y_cryp.id, Some(x_cryp.id), Skill::TestTouch).unwrap(); + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); + game = game.resolve_phase_start(); + + assert!(game.cryp_by_id(y_cryp.id).unwrap().affected(Effect::Corruption)); + } + + #[test] + fn scatter_test() { + let mut game = create_test_game(); + + let x_player = game.players[0].clone(); + let y_player = game.players[1].clone(); + + let x_cryp = x_player.cryps[0].clone(); + let y_cryp = y_player.cryps[0].clone(); + + game.cryp_by_id(x_cryp.id).unwrap().learn_mut(Skill::Scatter); + + while game.cryp_by_id(x_cryp.id).unwrap().skill_on_cd(Skill::Scatter).is_some() { + game.cryp_by_id(x_cryp.id).unwrap().reduce_cooldowns(); + } + + // apply buff + game.add_skill(x_player.id, x_cryp.id, Some(y_cryp.id), Skill::Scatter).unwrap(); + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); + game = game.resolve_phase_start(); + assert!(game.cryp_by_id(x_cryp.id).unwrap().affected(Effect::Scatter)); + + let Resolution { source: _, target: _, event } = game.resolved.pop().unwrap(); + match event { + Event::Effect { effect, skill: _, duration: _ } => assert_eq!(effect, Effect::Scatter), + _ => panic!("not siphon"), + }; + + let Resolution { source: _, target: _, event } = game.resolved.pop().unwrap(); + match event { + Event::Recharge { red: _, blue: _, skill: _ } => (), + _ => panic!("scatter result was not recharge"), + } + + // attack and receive scatter hit + game.add_skill(y_player.id, y_cryp.id, Some(x_cryp.id), Skill::Attack).unwrap(); + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); + game = game.resolve_phase_start(); + + let Resolution { source: _, target, event } = game.resolved.pop().unwrap(); + assert_eq!(target.id, y_cryp.id); + match event { + Event::Damage { amount, skill: _, mitigation: _, colour: _} => + assert_eq!(amount, 256.pct(Skill::Attack.multiplier()) >> 1), + _ => panic!("not damage scatter"), + }; + } + + // #[test] + // fn hatred_test() { + // let mut game = create_test_game(); + + // let x_player = game.players[0].clone(); + // let y_player = game.players[1].clone(); + + // let x_cryp = x_player.cryps[0].clone(); + // let y_cryp = y_player.cryps[0].clone(); + + // game.cryp_by_id(x_cryp.id).unwrap().learn_mut(Skill::Hostility); + + // while game.cryp_by_id(x_cryp.id).unwrap().skill_on_cd(Skill::Hostility).is_some() { + // game.cryp_by_id(x_cryp.id).unwrap().reduce_cooldowns(); + // } + + // // apply buff + // game.add_skill(x_player.id, x_cryp.id, Some(x_cryp.id), Skill::Hostility).unwrap(); + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + // game = game.resolve_phase_start(); + // assert!(game.cryp_by_id(x_cryp.id).unwrap().affected(Effect::Hostility)); + + // // attack and receive debuff + // game.add_skill(y_player.id, y_cryp.id, Some(x_cryp.id), Skill::TestAttack).unwrap(); + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + // game = game.resolve_phase_start(); + + // println!("{:#?}", game); + // assert!(game.cryp_by_id(y_cryp.id).unwrap().affected(Effect::Hatred)); + // } + #[test] fn aoe_test() { let mut game = create_2v2_test_game(); diff --git a/server/src/skill.rs b/server/src/skill.rs index 2d56f922..54d2c69d 100644 --- a/server/src/skill.rs +++ b/server/src/skill.rs @@ -5,6 +5,37 @@ use util::{IntPct}; use cryp::{Cryp, CrypEffect, EffectMeta, Stat}; use vbox::{Var}; +use game::{Game}; + +pub fn resolution_steps(cast: &Cast, game: &mut Game) -> Resolutions { + let mut resolutions = vec![]; + + resolutions = pre_resolve(cast, game, resolutions); + + return resolutions; +} + +pub fn pre_resolve(cast: &Cast, game: &mut Game, mut resolutions: Resolutions) -> Resolutions { + let skill = cast.skill; + let source = game.cryp_by_id(cast.source_cryp_id).unwrap().clone(); + let targets = game.get_targets(cast.skill, &source, cast.target_cryp_id); + + for target_id in targets { + let mut source = game.cryp_by_id(cast.source_cryp_id).unwrap().clone(); + let mut target = game.cryp_by_id(target_id).unwrap().clone(); + resolutions = resolve(cast.skill, &mut source, &mut target, resolutions); + + // save the clones + game.update_cryp(&mut source); + game.update_cryp(&mut target); + + // do additional steps + resolutions = post_resolve(cast.skill, game, resolutions); + } + + return resolutions; +} + pub fn resolve(skill: Skill, source: &mut Cryp, target: &mut Cryp, mut resolutions: Vec) -> Resolutions { if let Some(disable) = source.disabled(skill) { resolutions.push(Resolution::new(source, target).event(Event::Disable { disable, skill })); @@ -126,36 +157,43 @@ pub fn resolve(skill: Skill, source: &mut Cryp, target: &mut Cryp, mut resolutio Skill::TestSiphon => siphon(source, target, resolutions, Skill::Siphon), }; - // if any event dealt damage to target cryp - // hit them with corruption + return resolutions; +} - // on damage events - // todo not sure if this fucks up with multiple calls to resolve - // have to think - for r in resolutions.clone() { - match r.event { +fn post_resolve(_skill: Skill, game: &mut Game, mut resolutions: Resolutions) -> Resolutions { + for Resolution { source, target, event } in resolutions.clone() { + let mut source = game.cryp_by_id(source.id).unwrap().clone(); + let mut target = game.cryp_by_id(target.id).unwrap().clone(); + + match event { Event::Damage { amount, skill, mitigation: _, colour: _ } => { if target.affected(Effect::Corrupt) { - resolutions = corruption(target, source, resolutions, Skill::Corrupt); + resolutions = corruption(&mut target, &mut source, resolutions, Skill::Corrupt); } if target.affected(Effect::Hostility) { - resolutions = hatred(source, target, resolutions, skill, amount, Skill::Hostility); + resolutions = hatred(&mut source, &mut target, resolutions, skill, amount, Skill::Hostility); + } + + // beware that scatter doesn't cause any damage + // because then applying it will proc this + if target.affected(Effect::Scatter) { + resolutions = scatter_hit(&source, &target, resolutions, game, event) } }, + Event::Immunity { skill: _, immunity } => match immunity.contains(&Effect::Parry) { - true => resolutions = riposte(target, source, resolutions, Skill::Riposte), + true => { + resolutions = riposte(&mut target, &mut source, resolutions, Skill::Riposte); + } false => (), }, _ => (), - } - }; + }; - // i don't think we need to check the source being ko - if target.is_ko() { - resolutions.push(Resolution::new(source, target).event(Event::Ko { skill })); - target.effects.clear(); - } + game.update_cryp(&mut source); + game.update_cryp(&mut target); + }; return resolutions; } @@ -381,6 +419,8 @@ impl Effect { Effect::Haste => vec![Stat::Speed], Effect::Slow => vec![Stat::Speed], + Effect::Scatter => vec![Stat::BlueDamageTaken, Stat::GreenDamageTaken, Stat::RedDamageTaken], + _ => vec![], } } @@ -399,6 +439,8 @@ impl Effect { Effect::Impurity => value.pct(150), Effect::Wither => value.pct(50), + Effect::Scatter => value >> 1, + Effect::Hatred => value + match meta { Some(EffectMeta::AddedDamage(d)) => d, _ => panic!("hatred meta not damage"), @@ -452,7 +494,7 @@ impl Effect { Effect::Hostility => Category::Buff, - // magic + // magic Effect::Impurity => Category::Buff, Effect::Scatter => Category::Buff, Effect::Invert => Category::Buff, @@ -585,7 +627,7 @@ impl Skill { match self { // Attack Base Skill::Attack => 80, // Base - + Skill::Blast => 110, // BB Skill::Chaos => 40, // BR Skill::Heal => 130, //GG @@ -600,7 +642,7 @@ impl Skill { Skill::Purify => 45, //Green dmg (heal) Skill::Recharge => 85, //restore red and blue life (heal) Skill::Reflect => 45, //restore blue life (heal) - + Skill::Parry => 110, Skill::Riposte => 70, @@ -618,7 +660,7 @@ impl Skill { Skill::HasteStrike => 30, Skill::Taunt => 80, Skill::TriageTick => 75, - + Skill::Scatter => 140, _ => 100, } @@ -783,7 +825,7 @@ impl Skill { Skill::Impurity => Category::Green, Skill::Invert => Category::Green, Skill::Sleep => Category::Green, - + Skill::ImpureBlast => Category::Blue, Skill::Scatter => Category::Blue, Skill::Blast => Category::Blue, @@ -1035,7 +1077,7 @@ fn riposte(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill fn snare(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions { let snare = CrypEffect::new(Effect::Snare, skill.duration()); results.push(Resolution::new(source, target).event(target.add_effect(skill, snare))); - + let s_multi = target.skills .iter() .fold(100, |acc, cs| match cs.skill.category() { @@ -1043,7 +1085,6 @@ fn snare(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: _ => acc, }); - let amount = source.red_damage().pct(skill.multiplier()).pct(s_multi); target.deal_red_damage(skill, amount) .into_iter() @@ -1142,7 +1183,7 @@ fn decay(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: let wither = CrypEffect::new(Effect::Wither, skill.duration()); let decay = CrypEffect::new(Effect::Decay, skill.duration()) .set_tick(Cast::new_tick(source, target, Skill::DecayTick)); - + results.push(Resolution::new(source, target).event(target.add_effect(skill, decay))); results.push(Resolution::new(source, target).event(target.add_effect(skill, wither))); return decay_tick(source, target, results, Skill::DecayTick); @@ -1229,7 +1270,7 @@ fn invert(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: fn reflect(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions { let effect = CrypEffect::new(Effect::Reflect, skill.duration()); results.push(Resolution::new(source, target).event(target.add_effect(skill, effect))); - + let blue_amount = source.blue_damage().pct(skill.multiplier()); results.push(Resolution::new(source, target).event(target.recharge(skill, 0, blue_amount))); @@ -1273,15 +1314,47 @@ fn siphon_tick(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, s } fn scatter(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions { - let scatter = CrypEffect::new(Effect::Scatter, skill.duration()); - results.push(Resolution::new(source, target).event(target.add_effect(skill, scatter))); + let effect = CrypEffect::new(Effect::Scatter, skill.duration()) + .set_meta(EffectMeta::ScatterTarget(target.id)); + + let blue_amount = source.blue_damage().pct(skill.multiplier()); + results.push(Resolution::new(source, target).event(target.recharge(skill, 0, blue_amount))); + + results.push(Resolution::new(source, target).event(source.add_effect(skill, effect))); return results; } +fn scatter_hit(source: &Cryp, target: &Cryp, mut results: Resolutions, game: &mut Game, event: Event) -> Resolutions { + match event { + Event::Damage { amount, skill, mitigation: _, colour } => { + let scatter = target.effects.iter().find(|e| e.effect == Effect::Scatter).unwrap(); + + if let Some(EffectMeta::ScatterTarget(scatter_target_id)) = scatter.meta { + let mut scatter_target = game.cryp_by_id(scatter_target_id).unwrap(); + + let res = match colour { + Category::RedDamage => scatter_target.deal_red_damage(skill, amount), + Category::BlueDamage => scatter_target.deal_blue_damage(skill, amount), + Category::GreenDamage => scatter_target.deal_green_damage(skill, amount), + _ => panic!("{:?} unknown damage type", colour), + }; + + results.push(Resolution::new(target, scatter_target).event(Event::Skill { skill: Skill::Scatter })); + res.into_iter().for_each(|e| results.push(Resolution::new(&source, &scatter_target).event(e))); + } else { + panic!("not a scatter target {:?}", scatter); + } + + return results; + }, + _ => panic!("{:?} scatter hit not damage event", event), + } +} + fn silence(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions { let silence = CrypEffect::new(Effect::Silence, skill.duration()); results.push(Resolution::new(source, target).event(target.add_effect(skill, silence))); - + let s_multi = target.skills .iter() .fold(100, |acc, cs| match cs.skill.category() { @@ -1531,47 +1604,6 @@ mod tests { }; } - - #[test] - fn corrupt_test() { - let mut x = Cryp::new() - .named(&"muji".to_string()); - - let mut y = Cryp::new() - .named(&"camel".to_string()); - - corrupt(&mut y.clone(), &mut y, vec![], Skill::Corrupt); - assert!(y.affected(Effect::Corrupt)); - - resolve(Skill::Attack, &mut x, &mut y, vec![]); - - assert!(x.affected(Effect::Corruption)); - } - - #[test] - fn hatred_test() { - let mut x = Cryp::new() - .named(&"muji".to_string()); - - let mut y = Cryp::new() - .named(&"camel".to_string()); - - hostility(&mut y.clone(), &mut y, vec![], Skill::Hostility); - assert!(y.affected(Effect::Hostility)); - - resolve(Skill::TestAttack, &mut x, &mut y, vec![]); - - assert!(y.affected(Effect::Hatred)); - - let mut results = resolve(Skill::TestAttack, &mut y, &mut x, vec![]); - - let Resolution { source: _, target: _, event } = results.remove(0); - match event { - Event::Damage { amount, skill: _, mitigation: _, colour: _} => assert_eq!(amount, 512), - _ => panic!("not damage hatred"), - }; - } - #[test] fn triage_test() { let mut x = Cryp::new() @@ -1657,93 +1689,3 @@ mod tests { assert!(!x.effects.iter().any(|e| e.effect == Effect::Decay)); } } - - - - - - -// pub enum Skill { -// Attack, - -// // ----------------- -// // Nature -// // ----------------- -// Block, // reduce damage -// Parry, // avoid all damage -// Snare, - -// Paralyse, - -// Strangle, // physical dot and disable -// Strike, -// Stun, -// // Evade, // actively evade - - -// // ----------------- -// // Technology -// // ----------------- -// Replicate, -// Swarm, -// Orbit, -// Repair, -// Scan, // track? - -// // ----------------- -// // Nonviolence -// // ----------------- -// Heal, -// Triage, // hot -// TriageTick, -// Throw, // no damage stun, adds vulnerable -// Charm, -// Calm, -// Rez, - -// // Sleep, -// // Nightmare, - -// // ------------------- -// // Destruction -// // ------------------- -// Blast, -// Amplify, -// Decay, // dot -// DecayTick, // dot -// Siphon, -// SiphonTick, -// Curse, -// Corrupt, // aoe dot -// Ruin, // aoe - -// // ----------------- -// // Purity -// // ----------------- -// Empower, -// Slay, -// Shield, -// Silence, -// Inquiry, -// Purify, -// Purge, -// // Precision, - -// // ----------------- -// // Chaos -// // ----------------- -// Banish, -// Hex, -// Fear, -// Taunt, -// Pause, // speed slow -// Haste, -// Slow, - -// // used by tests, no cd, no damage -// TestTouch, -// TestStun, -// TestBlock, -// TestParry, -// TestSiphon, -// } diff --git a/server/src/vbox.rs b/server/src/vbox.rs index bc8a6b07..9433cc96 100644 --- a/server/src/vbox.rs +++ b/server/src/vbox.rs @@ -350,16 +350,16 @@ pub struct Combo { fn get_combos() -> Vec { let mut combinations = vec![ - Combo { units: vec![Var::Buff, Var::Red, Var::Red], var: Var::Taunt }, + Combo { units: vec![Var::Buff, Var::Red, Var::Red], var: Var::Taunt }, Combo { units: vec![Var::Buff, Var::Green, Var::Green], var: Var::Triage }, Combo { units: vec![Var::Buff, Var::Blue, Var::Blue], var: Var::Scatter }, //To be impl - Combo { units: vec![Var::Buff, Var::Red, Var::Green], var: Var::Haste }, - Combo { units: vec![Var::Buff, Var::Green, Var::Blue], var: Var::Impurity }, - Combo { units: vec![Var::Buff, Var::Red, Var::Blue], var: Var::Amplify }, // Some flavour + Combo { units: vec![Var::Buff, Var::Red, Var::Green], var: Var::Haste }, + Combo { units: vec![Var::Buff, Var::Green, Var::Blue], var: Var::Impurity }, + Combo { units: vec![Var::Buff, Var::Red, Var::Blue], var: Var::Amplify }, // Some flavour - Combo { units: vec![Var::Debuff, Var::Red, Var::Red], var: Var::Snare }, + Combo { units: vec![Var::Debuff, Var::Red, Var::Red], var: Var::Snare }, Combo { units: vec![Var::Debuff, Var::Green, Var::Green], var: Var::Purge }, // Needs flavour - Combo { units: vec![Var::Debuff, Var::Blue, Var::Blue], var: Var::Silence }, + Combo { units: vec![Var::Debuff, Var::Blue, Var::Blue], var: Var::Silence }, Combo { units: vec![Var::Debuff, Var::Red, Var::Green], var: Var::Curse }, // To be reworked Combo { units: vec![Var::Debuff, Var::Green, Var::Blue], var: Var::Decay }, Combo { units: vec![Var::Debuff, Var::Red, Var::Blue], var: Var::Invert }, @@ -675,8 +675,8 @@ mod tests { assert_eq!(count.red, 2); } - #[test] - fn vbox_info_test() { - println!("{:#?}", vbox_info()); - } + // #[test] + // fn vbox_info_test() { + // println!("{:#?}", vbox_info()); + // } } \ No newline at end of file