333 lines
9.6 KiB
Rust
333 lines
9.6 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 player::{Player, player_get, player_update};
|
|
use cryp::{cryp_get, cryp_write};
|
|
|
|
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
|
|
pub enum Var {
|
|
Blue,
|
|
Green,
|
|
Red,
|
|
|
|
Attack,
|
|
Block,
|
|
Stun,
|
|
Buff,
|
|
Debuff,
|
|
|
|
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,
|
|
}
|
|
|
|
impl Var {
|
|
fn is_base(&self) -> bool {
|
|
match self {
|
|
Var::Attack |
|
|
Var::Block |
|
|
Var::Stun |
|
|
Var::Debuff |
|
|
Var::Buff => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
fn skill(&self) -> Result<Skill, Error> {
|
|
match self {
|
|
Var::Amplify => Ok(Skill::Amplify),
|
|
Var::Banish => Ok(Skill::Banish),
|
|
Var::Blast => Ok(Skill::Blast),
|
|
Var::Curse => Ok(Skill::Curse),
|
|
Var::Empower => Ok(Skill::Empower),
|
|
Var::Haste => Ok(Skill::Haste),
|
|
Var::Heal => Ok(Skill::Heal),
|
|
Var::Hex => Ok(Skill::Hex),
|
|
Var::Parry => Ok(Skill::Parry),
|
|
Var::Purge => Ok(Skill::Purge),
|
|
Var::Purify => Ok(Skill::Purify),
|
|
// Var::Reflect => Ok(Skill::Reflect),
|
|
Var::Ruin => Ok(Skill::Ruin),
|
|
Var::Shield => Ok(Skill::Shield),
|
|
Var::Silence => Ok(Skill::Silence),
|
|
Var::Slay => Ok(Skill::Slay),
|
|
Var::Slow => Ok(Skill::Slow),
|
|
Var::Snare => Ok(Skill::Snare),
|
|
Var::Strangle => Ok(Skill::Strangle),
|
|
// Var::Strike => Ok(Skill::Strike),
|
|
// Var::Clutch => Ok(Skill::Clutch),
|
|
// Var::Taunt => Ok(Skill::Taunt),
|
|
Var::Throw => Ok(Skill::Throw),
|
|
// Var::Toxic => Ok(Skill::Toxic),
|
|
Var::Triage => Ok(Skill::Triage),
|
|
_ => Err(err_msg("not a usable 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<Var>,
|
|
pub bound: Vec<Var>,
|
|
pub instance: Uuid,
|
|
pub account: Uuid,
|
|
}
|
|
|
|
impl Vbox {
|
|
pub fn new(account_id: Uuid, instance_id: Uuid) -> Vbox {
|
|
Vbox {
|
|
id: Uuid::new_v4(),
|
|
account: account_id,
|
|
instance: instance_id,
|
|
free: vec![],
|
|
bound: vec![],
|
|
balance: 0,
|
|
}
|
|
}
|
|
|
|
pub fn fill(&mut self) -> &mut Vbox {
|
|
let vars = vec![
|
|
(Var::Red, 1),
|
|
(Var::Green, 1),
|
|
(Var::Blue, 1),
|
|
|
|
(Var::Attack, 1),
|
|
(Var::Block, 1),
|
|
(Var::Buff, 1),
|
|
(Var::Debuff, 1),
|
|
(Var::Stun, 1),
|
|
];
|
|
|
|
self.free = iter::
|
|
repeat_with(|| {
|
|
let mut rng = thread_rng();
|
|
let dist = WeightedIndex::new(vars.iter().map(|item| item.1)).unwrap();
|
|
return vars[dist.sample(&mut rng)].0;
|
|
})
|
|
.take(8)
|
|
.collect::<Vec<Var>>();
|
|
|
|
self
|
|
}
|
|
|
|
pub fn accept(&mut self, i: 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 at index {:?}", i))?;
|
|
self.bound.push(self.free.remove(i));
|
|
|
|
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,
|
|
},
|
|
_ => 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);
|
|
}
|
|
|
|
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.index)?;
|
|
return player_update(tx, player);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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 mut cryp = cryp_get(tx, params.cryp_id, account.id)?;
|
|
|
|
let var = player.vbox.bound.remove(params.index);
|
|
|
|
// 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));
|
|
}
|
|
|
|
let skill = var.skill()?;
|
|
cryp = cryp.learn(skill);
|
|
cryp_write(cryp, tx)?;
|
|
return player_update(tx, player);
|
|
}
|
|
|
|
|
|
#[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);
|
|
}
|
|
} |