use std::iter; use uuid::Uuid; // drops use rand::prelude::*; use rand::{thread_rng}; use rand::distributions::{WeightedIndex}; use postgres::transaction::Transaction; use failure::Error; use failure::err_msg; use account::Account; use rpc::{VboxAcceptParams, VboxDiscardParams, VboxCombineParams, VboxApplyParams, VboxDropParams}; use skill::{Skill}; use spec::{Spec}; use player::{Player, player_get, player_update}; #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum Var { // colours Blue, Green, Red, // base skills Attack, Block, Stun, Buff, Debuff, // specs Damage, Protection, Speed, Hp, StaminaI, RedDamageI, BlueDamageI, GreenDamageI, RedShieldI, BlueShieldI, SpeedI, Amplify, Banish, Blast, Curse, Empower, Haste, Heal, Hex, Parry, Purge, Purify, Reflect, Ruin, Shield, Silence, Slay, Slow, Snare, Strangle, Strike, Siphon, Clutch, Taunt, Throw, Toxic, Triage, } enum VarEffect { Skill, Spec, } impl Var { fn is_base(&self) -> bool { match self { Var::Attack | Var::Block | Var::Stun | Var::Debuff | Var::Buff => true, Var::Protection | Var::Damage => true, _ => false, } } fn effect(&self) -> Result { match self { Var::Amplify | Var::Banish | Var::Blast | Var::Curse | Var::Empower | Var::Haste | Var::Heal | Var::Hex | Var::Parry | Var::Purge | Var::Purify | // Var::Reflect | Var::Ruin | Var::Shield | Var::Silence | Var::Slay | Var::Slow | Var::Snare | Var::Strangle | Var::Strike | // Var::Clutch | // Var::Taunt | Var::Throw | // Var::Toxic | Var::Triage => Ok(VarEffect::Skill), Var::StaminaI | Var::RedShieldI | Var::BlueShieldI | Var::SpeedI => Ok(VarEffect::Spec), _ => Err(err_msg("var has no effect on cryps")) } } fn skill(&self) -> Skill { match self { Var::Amplify => Skill::Amplify, Var::Banish => Skill::Banish, Var::Blast => Skill::Blast, Var::Curse => Skill::Curse, Var::Empower => Skill::Empower, Var::Haste => Skill::Haste, Var::Heal => Skill::Heal, Var::Hex => Skill::Hex, Var::Parry => Skill::Parry, Var::Purge => Skill::Purge, Var::Purify => Skill::Purify, // Var::Reflect => Skill::Reflect, Var::Ruin => Skill::Ruin, Var::Shield => Skill::Shield, Var::Silence => Skill::Silence, Var::Slay => Skill::Slay, Var::Slow => Skill::Slow, Var::Snare => Skill::Snare, Var::Strangle => Skill::Strangle, Var::Strike => Skill::Strike, // Var::Clutch => Skill::Clutch, // Var::Taunt => Skill::Taunt, Var::Throw => Skill::Throw, // Var::Toxic => Skill::Toxic, Var::Triage => Skill::Triage, _ => panic!("not a skill var"), } } fn spec(&self) -> Spec { match *self { Var::StaminaI => Spec::StaminaI, Var::SpeedI => Spec::SpeedI, Var::RedDamageI => Spec::RedDamageI, Var::BlueDamageI => Spec::BlueDamageI, Var::GreenDamageI => Spec::GreenDamageI, Var::RedShieldI => Spec::RedShieldI, Var::BlueShieldI => Spec::BlueShieldI, _ => panic!("not a spec var"), } } } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] enum ColourCode { RR, GG, BB, RG, BR, GB, } #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Vbox { pub id: Uuid, pub balance: u16, pub free: Vec>, pub bound: Vec, pub instance: Uuid, pub account: Uuid, } impl Vbox { pub fn new(account_id: Uuid, instance_id: Uuid) -> Vbox { let starting_items = vec![ Var::Attack, Var::Attack, Var::Attack, Var::StaminaI, Var::SpeedI, Var::Damage, Var::Red, Var::Green, Var::Blue, ]; let mut vbox = Vbox { id: Uuid::new_v4(), account: account_id, instance: instance_id, free: vec![], bound: starting_items, balance: 9, }; vbox.fill(); return vbox; } pub fn fill(&mut self) -> &mut Vbox { let colours = vec![ (Var::Red, 1), (Var::Green, 1), (Var::Blue, 1), ]; let skills = vec![ (Var::Attack, 1), (Var::Block, 1), (Var::Buff, 1), (Var::Debuff, 1), (Var::Stun, 1), ]; let specs = vec![ (Var::Damage, 1), (Var::Protection, 1), (Var::Speed, 1), (Var::Hp, 1), ]; let mut rng = thread_rng(); self.free = [&colours, &skills, &specs].iter() .map(|vars| { let dist = WeightedIndex::new(vars.iter().map(|item| item.1)).unwrap(); iter::repeat_with(|| { vars[dist.sample(&mut rng)].0 }) .take(6) .collect::>() }) .collect::>>(); self } pub fn accept(&mut self, i: usize, j: usize) -> Result<&mut Vbox, Error> { if self.bound.len() >= 9 { return Err(err_msg("too many vars bound")); } self.free .get(i).ok_or(format_err!("no var group at index {:?}", i))? .get(j).ok_or(format_err!("no var at index {:?}", j))?; self.bound.push(self.free[i].remove(j)); Ok(self) } pub fn drop(&mut self, i: usize) -> Result<&mut Vbox, Error> { self.bound.get(i).ok_or(format_err!("no var at index {:?}", i))?; self.bound.remove(i); // balance update Ok(self) } pub fn combine(&mut self, mut indices: Vec) -> Result<&mut Vbox, Error> { if indices.len() != 3 { return Err(err_msg("exactly 3 indices required")); } if !indices.iter().all(|i| self.bound.get(*i).is_some()) { return Err(err_msg("var missing index")); } // have to sort the indices and keep track of the iteration // because when removing the elements the array shifts indices.sort(); let mut vars = indices .iter() .enumerate() .map(|(i, index)| { self.bound.remove(*index - i) }) .collect::>(); let base_index = vars .iter() .position(|v| v.is_base()) .ok_or(err_msg("no base item selected"))?; let base = vars.remove(base_index); // fold colours into RGB let colours = vars .iter() .fold([0, 0, 0], |mut acc, c| { match c { Var::Red => acc[0] += 1, Var::Green => acc[1] += 1, Var::Blue => acc[2] += 1, _ => (), }; acc }); let colour_code = match colours { [2,0,0] => ColourCode::RR, [0,2,0] => ColourCode::GG, [0,0,2] => ColourCode::BB, [1,1,0] => ColourCode::RG, [0,1,1] => ColourCode::GB, [1,0,1] => ColourCode::BR, _ => return Err(err_msg("not a combo")), }; let new = match base { Var::Attack => match colour_code { ColourCode::RR => Var::Strike, ColourCode::GG => Var::Heal, ColourCode::BB => Var::Blast, ColourCode::RG => Var::Slay, // ColourCode::GB => return Err(err_msg("unhandled skill combo")), ColourCode::BR => Var::Banish, // }, Var::Block => match colour_code { ColourCode::RR => Var::Parry, ColourCode::GG => Var::Reflect, ColourCode::BB => Var::Toxic, ColourCode::RG => Var::Taunt, ColourCode::GB => Var::Shield, ColourCode::BR => return Err(err_msg("unhandled skill combo")), }, Var::Buff => match colour_code { ColourCode::RR => Var::Empower, ColourCode::GG => Var::Triage, ColourCode::BB => Var::Amplify, ColourCode::RG => Var::Clutch, ColourCode::GB => return Err(err_msg("unhandled skill combo")), ColourCode::BR => Var::Haste, }, Var::Debuff => match colour_code { ColourCode::RR => Var::Snare, ColourCode::GG => Var::Purge, ColourCode::BB => Var::Curse, ColourCode::RG => return Err(err_msg("unhandled skill combo")), ColourCode::GB => Var::Siphon, ColourCode::BR => Var::Slow, }, Var::Stun => match colour_code { ColourCode::RR => Var::Strangle, ColourCode::GG => Var::Throw, ColourCode::BB => Var::Ruin, ColourCode::RG => return Err(err_msg("unhandled skill combo")), ColourCode::GB => Var::Silence, ColourCode::BR => Var::Hex, }, // SPECS Var::Damage => match colour_code { ColourCode::RR => Var::RedDamageI, ColourCode::GG => Var::GreenDamageI, ColourCode::BB => Var::BlueDamageI, ColourCode::RG => return Err(err_msg("unhandled skill combo")), ColourCode::GB => return Err(err_msg("unhandled skill combo")), ColourCode::BR => return Err(err_msg("unhandled skill combo")), }, Var::Protection => match colour_code { ColourCode::RR => Var::RedShieldI, ColourCode::GG => return Err(err_msg("unhandled skill combo")), ColourCode::BB => Var::BlueShieldI, ColourCode::RG => return Err(err_msg("unhandled skill combo")), ColourCode::GB => return Err(err_msg("unhandled skill combo")), ColourCode::BR => return Err(err_msg("unhandled skill combo")), }, _ => panic!("wrong base {:?}", base), }; self.bound.push(new); Ok(self) } } pub fn vbox_discard(params: VboxDiscardParams, tx: &mut Transaction, account: &Account) -> Result { let mut player = player_get(tx, account.id, params.instance_id)?; player.vbox.fill(); return player_update(tx, player, false); } pub fn vbox_accept(params: VboxAcceptParams, tx: &mut Transaction, account: &Account) -> Result { let mut player = player_get(tx, account.id, params.instance_id)?; player.vbox.accept(params.group, params.index)?; return player_update(tx, player, false); } pub fn vbox_combine(params: VboxCombineParams, tx: &mut Transaction, account: &Account) -> Result { let mut player = player_get(tx, account.id, params.instance_id)?; player.vbox.combine(params.indices)?; return player_update(tx, player, false); } pub fn vbox_drop(params: VboxDropParams, tx: &mut Transaction, account: &Account) -> Result { let mut player = player_get(tx, account.id, params.instance_id)?; player.vbox.drop(params.index)?; return player_update(tx, player, false); } pub fn vbox_apply(params: VboxApplyParams, tx: &mut Transaction, account: &Account) -> Result { let mut player = player_get(tx, account.id, params.instance_id)?; let var = player.vbox.bound.remove(params.index); match var.effect()? { VarEffect::Skill => { let skill = var.skill(); let cryp = player.cryp_get(params.cryp_id)?; // done here because i teach them a tonne of skills for tests let max_skills = 4; if cryp.skills.len() >= max_skills { return Err(format_err!("cryp at max skills ({:?})", max_skills)); } cryp.learn_mut(skill); }, VarEffect::Spec => { let spec = var.spec(); let cryp = player.cryp_get(params.cryp_id)?; cryp.spec_add(spec)?; }, } return player_update(tx, player, false); } #[cfg(test)] mod tests { use super::*; #[test] fn combine_test() { let mut vbox = Vbox::new(Uuid::new_v4(), Uuid::new_v4()); vbox.bound = vec![Var::Attack, Var::Green, Var::Green]; vbox.combine(vec![1,2,0]).unwrap(); assert_eq!(vbox.bound[0], Var::Heal); } }