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, Hp, Speed, LifeI, LRSI, LBSI, RBSI, RedDamageI, BlueDamageI, GreenDamageI, RedShieldI, BlueShieldI, 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::Hp | Var::Damage => true, _ => false, } } fn effect(&self) -> Option { if let Some(_skill) = self.skill() { return Some(VarEffect::Skill); } if let Some(_spec) = self.spec() { return Some(VarEffect::Spec); } return None; } fn skill(&self) -> Option { match self { Var::Amplify => Some(Skill::Amplify), Var::Banish => Some(Skill::Banish), Var::Blast => Some(Skill::Blast), Var::Curse => Some(Skill::Curse), Var::Empower => Some(Skill::Empower), Var::Haste => Some(Skill::Haste), Var::Heal => Some(Skill::Heal), Var::Hex => Some(Skill::Hex), Var::Parry => Some(Skill::Parry), Var::Purge => Some(Skill::Purge), Var::Purify => Some(Skill::Purify), // Var::Reflect => Some(Skill::Reflect), Var::Ruin => Some(Skill::Ruin), Var::Shield => Some(Skill::Shield), Var::Silence => Some(Skill::Silence), Var::Slay => Some(Skill::Slay), Var::Slow => Some(Skill::Slow), Var::Snare => Some(Skill::Snare), Var::Strangle => Some(Skill::Strangle), Var::Strike => Some(Skill::Strike), // Var::Clutch => Some(Skill::Clutch), // Var::Taunt => Some(Skill::Taunt), Var::Throw => Some(Skill::Throw), // Var::Toxic => Some(Skill::Toxic), Var::Triage => Some(Skill::Triage), _ => None, } } fn spec(&self) -> Option { match *self { Var::Speed => Some(Spec::SpeedI), Var::RedDamageI => Some(Spec::RedDamageI), Var::BlueDamageI => Some(Spec::BlueDamageI), Var::GreenDamageI => Some(Spec::GreenDamageI), Var::LifeI => Some(Spec::LifeI), Var::LRSI => Some(Spec::LRSI), Var::LBSI => Some(Spec::LBSI), Var::RBSI => Some(Spec::RBSI), Var::RedShieldI => Some(Spec::RedShieldI), Var::BlueShieldI => Some(Spec::BlueShieldI), _ => None, } } } #[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::Hp, Var::Speed, 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::Hp, 1), (Var::Speed, 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::Hp => match colour_code { ColourCode::RR => Var::RedShieldI, ColourCode::GG => Var::LifeI, ColourCode::BB => Var::BlueShieldI, ColourCode::RG => Var::LRSI, ColourCode::GB => Var::LBSI, ColourCode::BR => Var::RBSI, }, _ => 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() { Some(VarEffect::Skill) => { let skill = var.skill().ok_or(format_err!("var {:?} has no associated skill", var))?; 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); }, Some(VarEffect::Spec) => { let spec = var.spec().ok_or(format_err!("var {:?} has no associated spec", var))?; let cryp = player.cryp_get(params.cryp_id)?; cryp.spec_add(spec)?; }, None => return Err(err_msg("var has no effect on cryps")), } 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); } }