450 lines
13 KiB
Rust
450 lines
13 KiB
Rust
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<VarEffect> {
|
|
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<Skill> {
|
|
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<Spec> {
|
|
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<Vec<Var>>,
|
|
pub bound: Vec<Var>,
|
|
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::<Vec<Var>>()
|
|
})
|
|
.collect::<Vec<Vec<Var>>>();
|
|
|
|
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<usize>) -> 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::<Vec<Var>>();
|
|
|
|
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<Player, Error> {
|
|
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<Player, Error> {
|
|
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<Player, Error> {
|
|
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<Player, Error> {
|
|
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<Player, Error> {
|
|
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);
|
|
}
|
|
} |