Merge branch 'core' of ssh://git.mnml.gg:40022/~/mnml into core
This commit is contained in:
commit
da9736256e
@ -1,6 +1,8 @@
|
|||||||
# FIXME
|
# FIXME
|
||||||
|
|
||||||
|
game ready not auto starting resolve phase
|
||||||
remove immunity
|
remove immunity
|
||||||
|
banish dmg
|
||||||
aoe event
|
aoe event
|
||||||
cooldown events leak skills
|
cooldown events leak skills
|
||||||
hit event
|
hit event
|
||||||
|
|||||||
@ -57,9 +57,9 @@ pub struct Game {
|
|||||||
pub stack: Vec<Cast>,
|
pub stack: Vec<Cast>,
|
||||||
pub events: Vec<Vec<Event>>,
|
pub events: Vec<Vec<Event>>,
|
||||||
pub instance: Option<Uuid>,
|
pub instance: Option<Uuid>,
|
||||||
time_control: TimeControl,
|
pub time_control: TimeControl,
|
||||||
phase_start: DateTime<Utc>,
|
pub phase_start: DateTime<Utc>,
|
||||||
phase_end: Option<DateTime<Utc>>,
|
pub phase_end: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Game {
|
impl Game {
|
||||||
@ -285,7 +285,7 @@ impl Game {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_skill(&mut self, player_id: Uuid, source: Uuid, target: Uuid, skill: Skill) -> Result<&mut Game, Error> {
|
pub fn add_skill(&mut self, player_id: Uuid, source: Uuid, target: Uuid, skill: Skill) -> Result<&mut Game, Error> {
|
||||||
// check player in game
|
// check player in game
|
||||||
self.player_by_id(player_id)?;
|
self.player_by_id(player_id)?;
|
||||||
|
|
||||||
@ -343,7 +343,7 @@ impl Game {
|
|||||||
return Ok(self);
|
return Ok(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn offer_draw(mut self, player_id: Uuid) -> Result<Game, Error> {
|
pub fn offer_draw(mut self, player_id: Uuid) -> Result<Game, Error> {
|
||||||
if self.phase != Phase::Skill {
|
if self.phase != Phase::Skill {
|
||||||
return Err(err_msg("game not in skill phase"));
|
return Err(err_msg("game not in skill phase"));
|
||||||
}
|
}
|
||||||
@ -367,7 +367,7 @@ impl Game {
|
|||||||
return Ok(self);
|
return Ok(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn concede(mut self, player_id: Uuid) -> Result<Game, Error> {
|
pub fn concede(mut self, player_id: Uuid) -> Result<Game, Error> {
|
||||||
if self.phase != Phase::Skill {
|
if self.phase != Phase::Skill {
|
||||||
return Err(err_msg("game not in skill phase"));
|
return Err(err_msg("game not in skill phase"));
|
||||||
}
|
}
|
||||||
@ -379,7 +379,7 @@ impl Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn clear_skill(&mut self, player_id: Uuid) -> Result<&mut Game, Error> {
|
pub fn clear_skill(&mut self, player_id: Uuid) -> Result<&mut Game, Error> {
|
||||||
self.player_by_id(player_id)?;
|
self.player_by_id(player_id)?;
|
||||||
if self.phase != Phase::Skill {
|
if self.phase != Phase::Skill {
|
||||||
return Err(err_msg("game not in skill phase"));
|
return Err(err_msg("game not in skill phase"));
|
||||||
@ -390,7 +390,7 @@ impl Game {
|
|||||||
return Ok(self);
|
return Ok(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn player_ready(&mut self, player_id: Uuid) -> Result<&mut Game, Error> {
|
pub fn player_ready(&mut self, player_id: Uuid) -> Result<&mut Game, Error> {
|
||||||
if self.phase != Phase::Skill {
|
if self.phase != Phase::Skill {
|
||||||
return Err(err_msg("game not in skill phase"));
|
return Err(err_msg("game not in skill phase"));
|
||||||
}
|
}
|
||||||
@ -401,7 +401,7 @@ impl Game {
|
|||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn skill_phase_finished(&self) -> bool {
|
pub fn skill_phase_finished(&self) -> bool {
|
||||||
self.players.iter().all(|t| t.ready)
|
self.players.iter().all(|t| t.ready)
|
||||||
// self.players.iter()
|
// self.players.iter()
|
||||||
// // for every player
|
// // for every player
|
||||||
@ -413,7 +413,7 @@ impl Game {
|
|||||||
// )
|
// )
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_phase_start(mut self) -> Game {
|
pub fn resolve_phase_start(mut self) -> Game {
|
||||||
if self.phase != Phase::Skill {
|
if self.phase != Phase::Skill {
|
||||||
panic!("game not in skill phase");
|
panic!("game not in skill phase");
|
||||||
}
|
}
|
||||||
@ -722,11 +722,11 @@ mod tests {
|
|||||||
|
|
||||||
let x_player_id = Uuid::new_v4();
|
let x_player_id = Uuid::new_v4();
|
||||||
x.account = x_player_id;
|
x.account = x_player_id;
|
||||||
let x_player = Player::new(x_player_id, &"ntr".to_string(), vec![x]);
|
let x_player = Player::new(x_player_id, None, &"ntr".to_string(), vec![x]);
|
||||||
|
|
||||||
let y_player_id = Uuid::new_v4();
|
let y_player_id = Uuid::new_v4();
|
||||||
y.account = y_player_id;
|
y.account = y_player_id;
|
||||||
let y_player = Player::new(y_player_id, &"mash".to_string(), vec![y]);
|
let y_player = Player::new(y_player_id, None, &"mash".to_string(), vec![y]);
|
||||||
|
|
||||||
game
|
game
|
||||||
.player_add(x_player).unwrap()
|
.player_add(x_player).unwrap()
|
||||||
@ -767,12 +767,12 @@ mod tests {
|
|||||||
let i_player_id = Uuid::new_v4();
|
let i_player_id = Uuid::new_v4();
|
||||||
i.account = i_player_id;
|
i.account = i_player_id;
|
||||||
j.account = i_player_id;
|
j.account = i_player_id;
|
||||||
let i_player = Player::new(i_player_id, &"ntr".to_string(), vec![i, j]);
|
let i_player = Player::new(i_player_id, None, &"ntr".to_string(), vec![i, j]);
|
||||||
|
|
||||||
let x_player_id = Uuid::new_v4();
|
let x_player_id = Uuid::new_v4();
|
||||||
x.account = x_player_id;
|
x.account = x_player_id;
|
||||||
y.account = x_player_id;
|
y.account = x_player_id;
|
||||||
let x_player = Player::new(x_player_id, &"mashy".to_string(), vec![x, y]);
|
let x_player = Player::new(x_player_id, None, &"mashy".to_string(), vec![x, y]);
|
||||||
|
|
||||||
game
|
game
|
||||||
.player_add(i_player).unwrap()
|
.player_add(i_player).unwrap()
|
||||||
|
|||||||
@ -99,14 +99,14 @@ pub struct Instance {
|
|||||||
time_control: TimeControl,
|
time_control: TimeControl,
|
||||||
|
|
||||||
phase: InstancePhase,
|
phase: InstancePhase,
|
||||||
phase_end: Option<DateTime<Utc>>,
|
pub phase_end: Option<DateTime<Utc>>,
|
||||||
phase_start: DateTime<Utc>,
|
pub phase_start: DateTime<Utc>,
|
||||||
|
|
||||||
winner: Option<Uuid>,
|
winner: Option<Uuid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
fn new() -> Instance {
|
pub fn new() -> Instance {
|
||||||
Instance {
|
Instance {
|
||||||
id: Uuid::new_v4(),
|
id: Uuid::new_v4(),
|
||||||
players: vec![],
|
players: vec![],
|
||||||
@ -170,7 +170,7 @@ impl Instance {
|
|||||||
(self, new_game)
|
(self, new_game)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_name(mut self, name: String) -> Result<Instance, Error> {
|
pub fn set_name(mut self, name: String) -> Result<Instance, Error> {
|
||||||
if name.len() == 0 {
|
if name.len() == 0 {
|
||||||
return Err(err_msg("name must have a length"));
|
return Err(err_msg("name must have a length"));
|
||||||
}
|
}
|
||||||
@ -179,12 +179,12 @@ impl Instance {
|
|||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_time_control(mut self, tc: TimeControl) -> Instance {
|
pub fn set_time_control(mut self, tc: TimeControl) -> Instance {
|
||||||
self.time_control = tc;
|
self.time_control = tc;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_player(&mut self, player: Player) -> Result<&mut Instance, Error> {
|
pub fn add_player(&mut self, player: Player) -> Result<&mut Instance, Error> {
|
||||||
if self.players.len() >= self.max_players {
|
if self.players.len() >= self.max_players {
|
||||||
return Err(err_msg("game full"))
|
return Err(err_msg("game full"))
|
||||||
}
|
}
|
||||||
@ -198,7 +198,7 @@ impl Instance {
|
|||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn player_ready(&mut self, player_id: Uuid) -> Result<Option<Game>, Error> {
|
pub fn player_ready(&mut self, player_id: Uuid) -> Result<Option<Game>, Error> {
|
||||||
if ![InstancePhase::InProgress, InstancePhase::Lobby].contains(&self.phase) {
|
if ![InstancePhase::InProgress, InstancePhase::Lobby].contains(&self.phase) {
|
||||||
return Err(err_msg("instance not in start or vbox phase"));
|
return Err(err_msg("instance not in start or vbox phase"));
|
||||||
}
|
}
|
||||||
@ -300,7 +300,7 @@ impl Instance {
|
|||||||
self.next_round()
|
self.next_round()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_round(&mut self) -> &mut Instance {
|
pub fn next_round(&mut self) -> &mut Instance {
|
||||||
if self.finish_condition() {
|
if self.finish_condition() {
|
||||||
return self.finish();
|
return self.finish();
|
||||||
}
|
}
|
||||||
@ -342,7 +342,7 @@ impl Instance {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finished(&self) -> bool {
|
pub fn finished(&self) -> bool {
|
||||||
self.phase == InstancePhase::Finished
|
self.phase == InstancePhase::Finished
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,7 +370,7 @@ impl Instance {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_game_id(&self) -> Option<Uuid> {
|
pub fn current_game_id(&self) -> Option<Uuid> {
|
||||||
if self.phase != InstancePhase::InProgress {
|
if self.phase != InstancePhase::InProgress {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@ -386,7 +386,7 @@ impl Instance {
|
|||||||
return current_round.game_id;
|
return current_round.game_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> {
|
pub fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> {
|
||||||
{
|
{
|
||||||
let current_round = self.rounds
|
let current_round = self.rounds
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
@ -428,14 +428,14 @@ impl Instance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PLAYER ACTIONS
|
// PLAYER ACTIONS
|
||||||
fn account_player(&mut self, account: Uuid) -> Result<&mut Player, Error> {
|
pub fn account_player(&mut self, account: Uuid) -> Result<&mut Player, Error> {
|
||||||
self.players
|
self.players
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.find(|p| p.id == account)
|
.find(|p| p.id == account)
|
||||||
.ok_or(err_msg("account not in instance"))
|
.ok_or(err_msg("account not in instance"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn account_opponent(&mut self, account: Uuid) -> Result<&mut Player, Error> {
|
pub fn account_opponent(&mut self, account: Uuid) -> Result<&mut Player, Error> {
|
||||||
self.players
|
self.players
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.find(|p| p.id != account)
|
.find(|p| p.id != account)
|
||||||
@ -529,7 +529,7 @@ mod tests {
|
|||||||
let _instance = Instance::new();
|
let _instance = Instance::new();
|
||||||
let player_account = Uuid::new_v4();
|
let player_account = Uuid::new_v4();
|
||||||
let constructs = instance_mobs(player_account);
|
let constructs = instance_mobs(player_account);
|
||||||
let _player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true);
|
let _player = Player::new(player_account, None, &"test".to_string(), constructs).set_bot(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -540,7 +540,7 @@ mod tests {
|
|||||||
|
|
||||||
let player_account = Uuid::new_v4();
|
let player_account = Uuid::new_v4();
|
||||||
let constructs = instance_mobs(player_account);
|
let constructs = instance_mobs(player_account);
|
||||||
let player = Player::new(player_account, &"a".to_string(), constructs);
|
let player = Player::new(player_account, None, &"a".to_string(), constructs);
|
||||||
let a_id = player.id;
|
let a_id = player.id;
|
||||||
|
|
||||||
instance.add_player(player).expect("could not add player");
|
instance.add_player(player).expect("could not add player");
|
||||||
@ -548,7 +548,7 @@ mod tests {
|
|||||||
|
|
||||||
let player_account = Uuid::new_v4();
|
let player_account = Uuid::new_v4();
|
||||||
let constructs = instance_mobs(player_account);
|
let constructs = instance_mobs(player_account);
|
||||||
let player = Player::new(player_account, &"b".to_string(), constructs);
|
let player = Player::new(player_account, None, &"b".to_string(), constructs);
|
||||||
let b_id = player.id;
|
let b_id = player.id;
|
||||||
|
|
||||||
instance.add_player(player).expect("could not add player");
|
instance.add_player(player).expect("could not add player");
|
||||||
@ -577,7 +577,7 @@ mod tests {
|
|||||||
|
|
||||||
let player_account = Uuid::new_v4();
|
let player_account = Uuid::new_v4();
|
||||||
let constructs = instance_mobs(player_account);
|
let constructs = instance_mobs(player_account);
|
||||||
let player = Player::new(player_account, &"a".to_string(), constructs);
|
let player = Player::new(player_account, None, &"a".to_string(), constructs);
|
||||||
let a_id = player.id;
|
let a_id = player.id;
|
||||||
|
|
||||||
instance.add_player(player).expect("could not add player");
|
instance.add_player(player).expect("could not add player");
|
||||||
@ -585,7 +585,7 @@ mod tests {
|
|||||||
|
|
||||||
let player_account = Uuid::new_v4();
|
let player_account = Uuid::new_v4();
|
||||||
let constructs = instance_mobs(player_account);
|
let constructs = instance_mobs(player_account);
|
||||||
let player = Player::new(player_account, &"b".to_string(), constructs);
|
let player = Player::new(player_account, None, &"b".to_string(), constructs);
|
||||||
let b_id = player.id;
|
let b_id = player.id;
|
||||||
instance.add_player(player).expect("could not add player");
|
instance.add_player(player).expect("could not add player");
|
||||||
|
|
||||||
@ -617,7 +617,7 @@ mod tests {
|
|||||||
|
|
||||||
let player_account = Uuid::new_v4();
|
let player_account = Uuid::new_v4();
|
||||||
let constructs = instance_mobs(player_account);
|
let constructs = instance_mobs(player_account);
|
||||||
let player = Player::new(player_account, &"a".to_string(), constructs);
|
let player = Player::new(player_account, None, &"a".to_string(), constructs);
|
||||||
let _a_id = player.id;
|
let _a_id = player.id;
|
||||||
|
|
||||||
instance.add_player(player).expect("could not add player");
|
instance.add_player(player).expect("could not add player");
|
||||||
|
|||||||
@ -25,6 +25,6 @@ pub fn instance_mobs(player_id: Uuid) -> Vec<Construct> {
|
|||||||
pub fn bot_player() -> Player {
|
pub fn bot_player() -> Player {
|
||||||
let bot_id = Uuid::new_v4();
|
let bot_id = Uuid::new_v4();
|
||||||
let constructs = instance_mobs(bot_id);
|
let constructs = instance_mobs(bot_id);
|
||||||
Player::new(bot_id, &name(), constructs).set_bot(true)
|
Player::new(bot_id, None, &name(), constructs).set_bot(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -66,10 +66,10 @@ pub struct Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
pub fn new(account: Uuid, name: &String, constructs: Vec<Construct>) -> Player {
|
pub fn new(account: Uuid, img: Option<Uuid>, name: &String, constructs: Vec<Construct>) -> Player {
|
||||||
Player {
|
Player {
|
||||||
id: account,
|
id: account,
|
||||||
img: Some(account),
|
img,
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
vbox: Vbox::new(),
|
vbox: Vbox::new(),
|
||||||
constructs,
|
constructs,
|
||||||
@ -401,7 +401,7 @@ mod tests {
|
|||||||
fn player_bot_vbox_test() {
|
fn player_bot_vbox_test() {
|
||||||
let player_account = Uuid::new_v4();
|
let player_account = Uuid::new_v4();
|
||||||
let constructs = instance_mobs(player_account);
|
let constructs = instance_mobs(player_account);
|
||||||
let mut player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true);
|
let mut player = Player::new(player_account, None, &"test".to_string(), constructs).set_bot(true);
|
||||||
|
|
||||||
player.vbox.fill();
|
player.vbox.fill();
|
||||||
player.autobuy();
|
player.autobuy();
|
||||||
@ -413,7 +413,7 @@ mod tests {
|
|||||||
fn player_score_test() {
|
fn player_score_test() {
|
||||||
let player_account = Uuid::new_v4();
|
let player_account = Uuid::new_v4();
|
||||||
let constructs = instance_mobs(player_account);
|
let constructs = instance_mobs(player_account);
|
||||||
let mut player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true);
|
let mut player = Player::new(player_account, None, &"test".to_string(), constructs).set_bot(true);
|
||||||
|
|
||||||
player.score = player.score.add_win(&Score::Zero);
|
player.score = player.score.add_win(&Score::Zero);
|
||||||
player.score = player.score.add_win(&Score::Zero);
|
player.score = player.score.add_win(&Score::Zero);
|
||||||
|
|||||||
@ -176,34 +176,23 @@ impl Cast {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
// fn bash(source: &mut Construct, target: &mut Construct, skill: Skill) {
|
Skill::Blast |
|
||||||
// skill.effect().into_iter()
|
Skill::BlastPlus |
|
||||||
// .for_each(|e| (game.event(Event::new(source, target).event(target.add_effect(skill, e)))));
|
Skill::BlastPlusPlus => vec![
|
||||||
|
Action::Damage {
|
||||||
|
construct: self.target,
|
||||||
|
colour: Colour::Blue,
|
||||||
|
values: vec![Value::Stat { construct: self.source, stat: Stat::BluePower, mult: self.skill.multiplier() }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// if resolutions.iter().any(|r| match r.event {
|
|
||||||
// Event::Effect { effect, skill: effect_skill, duration: _, construct_effects: _ }
|
|
||||||
// => effect == Effect::Stun && skill == effect_skill,
|
|
||||||
// _ => false,
|
|
||||||
// }) {
|
|
||||||
// let mut cds = 0;
|
|
||||||
// for cs in target.skills.iter_mut() {
|
|
||||||
// if cs.skill.base_cd().is_some() {
|
|
||||||
// cs.cd = match cs.cd {
|
|
||||||
// None => Some(1),
|
|
||||||
// Some(i) => Some(i + 1),
|
|
||||||
// };
|
|
||||||
|
|
||||||
// cds += 1;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let amount = source.red_power().pct(skill.multiplier().pct(100 + 45usize.saturating_mul(cds)));
|
|
||||||
// target.deal_red_damage(skill, amount)
|
|
||||||
// .into_iter()
|
|
||||||
// .for_each(|e| game.event(Event::new(source, target).event(e).stages(EventStages::PostOnly)));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
Skill::Strike |
|
Skill::Strike |
|
||||||
Skill::StrikePlus |
|
Skill::StrikePlus |
|
||||||
@ -1349,13 +1338,6 @@ impl Skill {
|
|||||||
// .for_each(|e| game.event(Event::new(source, target).event(e).stages(EventStages::PostOnly)));
|
// .for_each(|e| game.event(Event::new(source, target).event(e).stages(EventStages::PostOnly)));
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fn blast(source: &mut Construct, target: &mut Construct, skill: Skill) {
|
|
||||||
// let amount = source.blue_power().pct(skill.multiplier());
|
|
||||||
// target.deal_blue_damage(skill, amount)
|
|
||||||
// .into_iter()
|
|
||||||
// .for_each(|e| game.event(Event::new(source, target).event(e)));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn amplify(source: &mut Construct, target: &mut Construct, skill: Skill) {
|
// fn amplify(source: &mut Construct, target: &mut Construct, skill: Skill) {
|
||||||
// game.event(Event::new(source, target).event(target.add_effect(skill, skill.effect()[0])));
|
// game.event(Event::new(source, target).event(target.add_effect(skill, skill.effect()[0])));
|
||||||
// }
|
// }
|
||||||
|
|||||||
@ -44,3 +44,5 @@ stripe-rust = "0.10"
|
|||||||
|
|
||||||
reqwest = "0.9"
|
reqwest = "0.9"
|
||||||
url = "1"
|
url = "1"
|
||||||
|
|
||||||
|
mnml_core = { path = "./../core" }
|
||||||
|
|||||||
@ -8,19 +8,19 @@ use serde_cbor::{from_slice};
|
|||||||
|
|
||||||
use postgres::transaction::Transaction;
|
use postgres::transaction::Transaction;
|
||||||
|
|
||||||
use http::MnmlHttpError;
|
|
||||||
use names::{name as generate_name};
|
|
||||||
use construct::{Construct, ConstructSkeleton, construct_spawn};
|
|
||||||
use instance::{Instance, instance_delete};
|
|
||||||
use instance;
|
|
||||||
use mtx::{Mtx, FREE_MTX};
|
|
||||||
use pg::Db;
|
|
||||||
use img;
|
|
||||||
|
|
||||||
|
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use failure::{err_msg, format_err};
|
use failure::{err_msg, format_err};
|
||||||
|
|
||||||
|
use mnml_core::construct::{Construct, ConstructSkeleton};
|
||||||
|
use mnml_core::instance::{Instance};
|
||||||
|
use mnml_core::player::{Player};
|
||||||
|
|
||||||
|
use http::MnmlHttpError;
|
||||||
|
use names::{name as generate_name};
|
||||||
|
use mtx::{Mtx, FREE_MTX};
|
||||||
|
use pg::{Db, instance_delete, construct_spawn, instance_practice};
|
||||||
|
use img;
|
||||||
|
|
||||||
static PASSWORD_MIN_LEN: usize = 3;
|
static PASSWORD_MIN_LEN: usize = 3;
|
||||||
static PASSWORD_ROUNDS: u32 = 10;
|
static PASSWORD_ROUNDS: u32 = 10;
|
||||||
|
|
||||||
@ -33,6 +33,19 @@ pub struct Account {
|
|||||||
pub subscribed: bool,
|
pub subscribed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Account {
|
||||||
|
pub fn to_player(&self, tx: &mut Transaction) -> Result<Player, Error> {
|
||||||
|
let constructs = team(tx, self)?;
|
||||||
|
|
||||||
|
let img = match self.subscribed {
|
||||||
|
true => Some(self.img),
|
||||||
|
false => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Player::new(self.id, Some(self.img), &self.name, constructs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<postgres::rows::Row<'a>> for Account {
|
impl<'a> TryFrom<postgres::rows::Row<'a>> for Account {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
@ -523,7 +536,7 @@ pub fn tutorial(tx: &mut Transaction, account: &Account) -> Result<Option<Instan
|
|||||||
let count: i64 = row.get(0);
|
let count: i64 = row.get(0);
|
||||||
|
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
return Ok(Some(instance::instance_practice(tx, account)?));
|
return Ok(Some(instance_practice(tx, account)?));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,209 +0,0 @@
|
|||||||
use construct::{Stat, EffectMeta};
|
|
||||||
use skill::{Skill};
|
|
||||||
use util::{IntPct};
|
|
||||||
|
|
||||||
pub type Cooldown = Option<u8>;
|
|
||||||
|
|
||||||
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
|
|
||||||
pub enum Effect {
|
|
||||||
Amplify,
|
|
||||||
Banish,
|
|
||||||
Block,
|
|
||||||
Buff,
|
|
||||||
Counter,
|
|
||||||
Curse,
|
|
||||||
Haste,
|
|
||||||
Hybrid,
|
|
||||||
Intercept,
|
|
||||||
Invert,
|
|
||||||
Pure,
|
|
||||||
Purge,
|
|
||||||
Reflect,
|
|
||||||
Restrict,
|
|
||||||
Silence,
|
|
||||||
Slow,
|
|
||||||
Stun,
|
|
||||||
Sustain,
|
|
||||||
Vulnerable,
|
|
||||||
Wither, // Reduce green dmg (healing) taken
|
|
||||||
|
|
||||||
// electric is the buff that applies
|
|
||||||
// electrocute the dmg debuff
|
|
||||||
Electric,
|
|
||||||
Electrocute,
|
|
||||||
|
|
||||||
// absorbtion is the buff
|
|
||||||
// absorb is the increased damage
|
|
||||||
Absorb,
|
|
||||||
Absorption,
|
|
||||||
|
|
||||||
// magic immunity
|
|
||||||
|
|
||||||
// effects over time
|
|
||||||
Triage,
|
|
||||||
Decay,
|
|
||||||
Regen,
|
|
||||||
Siphon,
|
|
||||||
|
|
||||||
// Airborne,
|
|
||||||
// Boost
|
|
||||||
// Bleed,
|
|
||||||
// Blind,
|
|
||||||
// Deadly,
|
|
||||||
// Enslave,
|
|
||||||
// Fury,
|
|
||||||
// Injured,
|
|
||||||
// Leech,
|
|
||||||
// Mesmerise,
|
|
||||||
// Untouchable,
|
|
||||||
// SpeedSiphon,
|
|
||||||
// SpeedIncrease,
|
|
||||||
|
|
||||||
Ko,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Effect {
|
|
||||||
pub fn immune(&self, skill: Skill) -> bool {
|
|
||||||
match self {
|
|
||||||
Effect::Banish => true,
|
|
||||||
Effect::Sustain => [
|
|
||||||
Skill::Stun,
|
|
||||||
Skill::Silence,
|
|
||||||
Skill::SilencePlus,
|
|
||||||
Skill::SilencePlusPlus,
|
|
||||||
Skill::Ruin,
|
|
||||||
Skill::RuinPlus,
|
|
||||||
Skill::RuinPlusPlus,
|
|
||||||
Skill::Restrict,
|
|
||||||
Skill::RestrictPlus,
|
|
||||||
Skill::RestrictPlusPlus
|
|
||||||
].contains(&skill),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disables_skill(&self, skill: Skill) -> bool {
|
|
||||||
if skill.is_tick() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
match self {
|
|
||||||
Effect::Stun => true,
|
|
||||||
Effect::Banish => true,
|
|
||||||
Effect::Silence => skill.colours().contains(&Colour::Blue),
|
|
||||||
Effect::Restrict => skill.colours().contains(&Colour::Red),
|
|
||||||
Effect::Purge => skill.colours().contains(&Colour::Green),
|
|
||||||
Effect::Ko => skill.ko_castable(),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn modifications(&self) -> Vec<Stat> {
|
|
||||||
match self {
|
|
||||||
// Bases
|
|
||||||
Effect::Block => vec![Stat::RedDamageTaken, Stat::BlueDamageTaken],
|
|
||||||
Effect::Buff => vec![Stat::BluePower, Stat::RedPower, Stat::Speed],
|
|
||||||
Effect::Slow => vec![Stat::Speed],
|
|
||||||
|
|
||||||
// Power changes
|
|
||||||
Effect::Absorption => vec![Stat::RedPower, Stat::BluePower],
|
|
||||||
Effect::Amplify => vec![Stat::RedPower, Stat::BluePower],
|
|
||||||
Effect::Hybrid => vec![Stat::GreenPower],
|
|
||||||
|
|
||||||
// Damage taken changes
|
|
||||||
Effect::Curse => vec![Stat::RedDamageTaken, Stat::BlueDamageTaken],
|
|
||||||
Effect::Pure => vec![Stat::GreenDamageTaken], // increased green taken
|
|
||||||
Effect::Vulnerable => vec![Stat::RedDamageTaken],
|
|
||||||
Effect::Wither => vec![Stat::GreenDamageTaken], // reduced green taken
|
|
||||||
|
|
||||||
// Speed
|
|
||||||
Effect::Haste => vec![Stat::Speed],
|
|
||||||
|
|
||||||
_ => vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply(&self, value: u64, meta: Option<EffectMeta>) -> u64 {
|
|
||||||
match self {
|
|
||||||
Effect::Amplify |
|
|
||||||
Effect::Vulnerable |
|
|
||||||
Effect::Block |
|
|
||||||
Effect::Buff |
|
|
||||||
Effect::Curse |
|
|
||||||
Effect::Haste |
|
|
||||||
Effect::Slow |
|
|
||||||
Effect::Hybrid |
|
|
||||||
Effect::Pure |
|
|
||||||
Effect::Wither => value.pct(match meta {
|
|
||||||
Some(EffectMeta::Multiplier(d)) => d,
|
|
||||||
_ => 100,
|
|
||||||
}),
|
|
||||||
|
|
||||||
Effect::Absorption => value + match meta {
|
|
||||||
Some(EffectMeta::AddedDamage(d)) => d,
|
|
||||||
_ => {
|
|
||||||
warn!("absorb meta not damage");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_ => {
|
|
||||||
warn!("{:?} does not have a mod effect", self);
|
|
||||||
return value;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn colour(&self) -> Option<Colour> {
|
|
||||||
match self {
|
|
||||||
// physical
|
|
||||||
Effect::Stun => Some(Colour::Red),
|
|
||||||
Effect::Block => Some(Colour::Green),
|
|
||||||
Effect::Buff => Some(Colour::Green),
|
|
||||||
Effect::Counter => Some(Colour::Green),
|
|
||||||
Effect::Vulnerable => Some(Colour::Red),
|
|
||||||
Effect::Restrict => Some(Colour::Red),
|
|
||||||
Effect::Sustain => Some(Colour::Green),
|
|
||||||
Effect::Intercept => Some(Colour::Green),
|
|
||||||
|
|
||||||
// magic
|
|
||||||
Effect::Curse => Some(Colour::Blue),
|
|
||||||
Effect::Banish => None,
|
|
||||||
// Effect::Banish => rng.gen_bool(0.5),
|
|
||||||
|
|
||||||
Effect::Slow => Some(Colour::Blue),
|
|
||||||
Effect::Haste => Some(Colour::Green),
|
|
||||||
Effect::Absorption => Some(Colour::Green),
|
|
||||||
Effect::Reflect => Some(Colour::Green),
|
|
||||||
Effect::Amplify => Some(Colour::Green),
|
|
||||||
Effect::Silence => Some(Colour::Blue),
|
|
||||||
Effect::Wither => Some(Colour::Blue),
|
|
||||||
Effect::Purge => Some(Colour::Blue),
|
|
||||||
|
|
||||||
Effect::Electric => Some(Colour::Green),
|
|
||||||
Effect::Electrocute => Some(Colour::Blue),
|
|
||||||
|
|
||||||
Effect::Absorb => Some(Colour::Green),
|
|
||||||
|
|
||||||
// magic
|
|
||||||
Effect::Hybrid => Some(Colour::Green),
|
|
||||||
Effect::Invert => Some(Colour::Green),
|
|
||||||
|
|
||||||
// effects over time
|
|
||||||
Effect::Triage => Some(Colour::Green),
|
|
||||||
Effect::Decay => Some(Colour::Blue),
|
|
||||||
Effect::Regen => Some(Colour::Green),
|
|
||||||
Effect::Siphon => Some(Colour::Blue),
|
|
||||||
Effect::Pure => Some(Colour::Green),
|
|
||||||
|
|
||||||
Effect::Ko => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
|
|
||||||
pub enum Colour {
|
|
||||||
Red,
|
|
||||||
Blue,
|
|
||||||
Green,
|
|
||||||
}
|
|
||||||
@ -1,107 +0,0 @@
|
|||||||
// This example illustrates the error flow of a Request in the middleware Chain.
|
|
||||||
// Here is the chain used and the path of the request through the middleware pieces:
|
|
||||||
//
|
|
||||||
// Normal Flow : __[ErrorProducer::before]__ [ErrorRecover::before] __[handle::HelloWorldHandler]__[ErrorProducer::after]__ [ErrorRecover::after] __ ...
|
|
||||||
// Error Flow : [ErrorProducer::catch ] |__[ErrorRecover::catch ]__| [ErrorProducer::catch] |__[ErrorRecover::catch]__|
|
|
||||||
//
|
|
||||||
// --------------- BEFORE MIDDLEWARE ----------------- || --------- HANDLER -------- || ---------------- AFTER MIDDLEWARE --------------
|
|
||||||
|
|
||||||
extern crate iron;
|
|
||||||
|
|
||||||
use iron::prelude::*;
|
|
||||||
use iron::StatusCode;
|
|
||||||
use iron::{AfterMiddleware, BeforeMiddleware, Handler};
|
|
||||||
|
|
||||||
use std::error::Error;
|
|
||||||
use std::fmt::{self, Debug};
|
|
||||||
|
|
||||||
struct HelloWorldHandler;
|
|
||||||
struct ErrorProducer;
|
|
||||||
struct ErrorRecover;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct StringError(String);
|
|
||||||
|
|
||||||
impl fmt::Display for StringError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
Debug::fmt(self, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for StringError {
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
&*self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Handler for HelloWorldHandler {
|
|
||||||
fn handle(&self, _: &mut Request) -> IronResult<Response> {
|
|
||||||
// This will be called since we are in the normal flow before reaching the Handler.
|
|
||||||
// However, the AfterMiddleware chain will override the Response.
|
|
||||||
println!("The HelloWorldHandler has been called !");
|
|
||||||
Ok(Response::with((StatusCode::OK, "Hello world !")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BeforeMiddleware for ErrorProducer {
|
|
||||||
fn before(&self, _: &mut Request) -> IronResult<()> {
|
|
||||||
// The error produced here switches to the error flow.
|
|
||||||
// The catch method of following middleware pieces will be called.
|
|
||||||
// The Handler will be skipped unless the error is handled by another middleware piece.
|
|
||||||
// IronError::error tells the next middleware what went wrong.
|
|
||||||
// IronError::response is the Response that will be sent back to the client if this error is not handled.
|
|
||||||
// Here status::BadRequest acts as modifier, thus we can put more there than just a status.
|
|
||||||
Err(IronError::new(
|
|
||||||
StringError("Error in ErrorProducer BeforeMiddleware".to_string()),
|
|
||||||
StatusCode::BAD_REQUEST,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AfterMiddleware for ErrorProducer {
|
|
||||||
fn after(&self, _: &mut Request, _: Response) -> IronResult<Response> {
|
|
||||||
// The behavior here is the same as in ErrorProducer::before.
|
|
||||||
// The previous response (from the Handler) is discarded and replaced with a new response (created from the modifier).
|
|
||||||
Err(IronError::new(
|
|
||||||
StringError("Error in ErrorProducer AfterMiddleware".to_string()),
|
|
||||||
(StatusCode::BAD_REQUEST, "Response created in ErrorProducer"),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BeforeMiddleware for ErrorRecover {
|
|
||||||
fn catch(&self, _: &mut Request, err: IronError) -> IronResult<()> {
|
|
||||||
// We can use the IronError from previous middleware to decide what to do.
|
|
||||||
// Returning Ok() from a catch method resumes the normal flow and
|
|
||||||
// passes the Request forward to the next middleware piece in the chain (here the HelloWorldHandler).
|
|
||||||
println!("{} caught in ErrorRecover BeforeMiddleware.", err.error);
|
|
||||||
match err.response.status {
|
|
||||||
Some(StatusCode::BAD_REQUEST) => Ok(()),
|
|
||||||
_ => Err(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AfterMiddleware for ErrorRecover {
|
|
||||||
fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> {
|
|
||||||
// Just like in the BeforeMiddleware, we can return Ok(Response) here to return to the normal flow.
|
|
||||||
// In this case, ErrorRecover is the last middleware in the chain
|
|
||||||
// and the Response created in the ErrorProducer is modified and sent back to the client.
|
|
||||||
println!("{} caught in ErrorRecover AfterMiddleware.", err.error);
|
|
||||||
match err.response.status {
|
|
||||||
Some(StatusCode::BAD_REQUEST) => Ok(err.response.set(StatusCode::OK)),
|
|
||||||
_ => Err(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut chain = Chain::new(HelloWorldHandler);
|
|
||||||
chain.link_before(ErrorProducer);
|
|
||||||
chain.link_before(ErrorRecover);
|
|
||||||
|
|
||||||
chain.link_after(ErrorProducer);
|
|
||||||
chain.link_after(ErrorRecover);
|
|
||||||
|
|
||||||
Iron::new(chain).http("localhost:3000");
|
|
||||||
}
|
|
||||||
@ -10,13 +10,9 @@ use failure::{err_msg, format_err};
|
|||||||
|
|
||||||
use crossbeam_channel::{Sender, Receiver};
|
use crossbeam_channel::{Sender, Receiver};
|
||||||
|
|
||||||
use account;
|
|
||||||
use account::Account;
|
use account::Account;
|
||||||
use game;
|
|
||||||
use instance;
|
|
||||||
use names;
|
use names;
|
||||||
|
|
||||||
use pg::{Db, PgPool};
|
|
||||||
use rpc::RpcMessage;
|
use rpc::RpcMessage;
|
||||||
use warden::{GameEvent};
|
use warden::{GameEvent};
|
||||||
use mail::Mail;
|
use mail::Mail;
|
||||||
|
|||||||
1557
server/src/game.rs
1557
server/src/game.rs
File diff suppressed because it is too large
Load Diff
@ -14,9 +14,9 @@ use persistent::{Read, Write};
|
|||||||
use router::Router;
|
use router::Router;
|
||||||
use mount::{Mount};
|
use mount::{Mount};
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use lettre::{SendableEmail, SmtpClient, SmtpTransport, Transport};
|
use lettre::{SmtpTransport};
|
||||||
|
|
||||||
use acp;
|
// use acp;
|
||||||
use account;
|
use account;
|
||||||
use mail;
|
use mail;
|
||||||
use mail::Mail;
|
use mail::Mail;
|
||||||
@ -517,7 +517,7 @@ pub fn start(pool: PgPool, mailer: SmtpTransport) {
|
|||||||
|
|
||||||
mounts.mount("/api/account/", account_mount());
|
mounts.mount("/api/account/", account_mount());
|
||||||
mounts.mount("/api/payments/", payment_mount());
|
mounts.mount("/api/payments/", payment_mount());
|
||||||
mounts.mount("/api/acp/", acp::acp_mount());
|
// mounts.mount("/api/acp/", acp::acp_mount());
|
||||||
|
|
||||||
let mut chain = Chain::new(mounts);
|
let mut chain = Chain::new(mounts);
|
||||||
chain.link(Read::<State>::both(State { pool }));
|
chain.link(Read::<State>::both(State { pool }));
|
||||||
|
|||||||
@ -1,959 +0,0 @@
|
|||||||
use std::fs::File;
|
|
||||||
use std::collections::{HashMap};
|
|
||||||
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use serde_cbor::{from_slice, to_vec};
|
|
||||||
|
|
||||||
use postgres::transaction::Transaction;
|
|
||||||
|
|
||||||
use failure::Error;
|
|
||||||
use failure::err_msg;
|
|
||||||
|
|
||||||
// timekeeping
|
|
||||||
use chrono::prelude::*;
|
|
||||||
use chrono::Duration;
|
|
||||||
|
|
||||||
use account::Account;
|
|
||||||
use vbox;
|
|
||||||
|
|
||||||
use player::{Player, Score, player_create};
|
|
||||||
|
|
||||||
use mob::{bot_player, instance_mobs};
|
|
||||||
use game::{Game, Phase, game_get, game_write, game_update};
|
|
||||||
use item::{Item};
|
|
||||||
use rpc::{RpcMessage};
|
|
||||||
use img;
|
|
||||||
|
|
||||||
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
|
|
||||||
enum InstancePhase {
|
|
||||||
Lobby,
|
|
||||||
InProgress,
|
|
||||||
Finished,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type ChatState = HashMap<Uuid, String>;
|
|
||||||
|
|
||||||
#[derive(Debug,Clone,Serialize,Deserialize)]
|
|
||||||
struct Round {
|
|
||||||
game_id: Option<Uuid>,
|
|
||||||
finished: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Round {
|
|
||||||
fn new() -> Round {
|
|
||||||
Round { game_id: None, finished: false }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug,Clone,Copy,Serialize,Deserialize)]
|
|
||||||
pub enum TimeControl {
|
|
||||||
Standard,
|
|
||||||
Slow,
|
|
||||||
Practice,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TimeControl {
|
|
||||||
fn vbox_time_seconds(&self) -> i64 {
|
|
||||||
match self {
|
|
||||||
TimeControl::Standard => 180,
|
|
||||||
TimeControl::Slow => 240,
|
|
||||||
TimeControl::Practice => panic!("practice vbox seconds called"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn game_time_seconds(&self) -> i64 {
|
|
||||||
match self {
|
|
||||||
TimeControl::Standard => 60,
|
|
||||||
TimeControl::Slow => 120,
|
|
||||||
TimeControl::Practice => panic!("practice game seconds called"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_phase_end(&self) -> Option<DateTime<Utc>> {
|
|
||||||
match self {
|
|
||||||
TimeControl::Practice => None,
|
|
||||||
_ => Some(Utc::now()
|
|
||||||
.checked_add_signed(Duration::seconds(self.vbox_time_seconds()))
|
|
||||||
.expect("could not set vbox phase end")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lobby_timeout(&self) -> DateTime<Utc> {
|
|
||||||
Utc::now()
|
|
||||||
.checked_add_signed(Duration::seconds(15))
|
|
||||||
.expect("could not set phase end")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn game_phase_end(&self, resolution_time_ms: i64) -> Option<DateTime<Utc>> {
|
|
||||||
match self {
|
|
||||||
TimeControl::Practice => None,
|
|
||||||
_ => Some(Utc::now()
|
|
||||||
.checked_add_signed(Duration::milliseconds(self.game_time_seconds() * 1000 + resolution_time_ms))
|
|
||||||
.expect("could not set game phase end")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug,Clone,Serialize,Deserialize)]
|
|
||||||
pub struct Instance {
|
|
||||||
pub id: Uuid,
|
|
||||||
pub name: String,
|
|
||||||
|
|
||||||
players: Vec<Player>,
|
|
||||||
rounds: Vec<Round>,
|
|
||||||
|
|
||||||
max_players: usize,
|
|
||||||
time_control: TimeControl,
|
|
||||||
|
|
||||||
phase: InstancePhase,
|
|
||||||
phase_end: Option<DateTime<Utc>>,
|
|
||||||
phase_start: DateTime<Utc>,
|
|
||||||
|
|
||||||
winner: Option<Uuid>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Instance {
|
|
||||||
fn new() -> Instance {
|
|
||||||
Instance {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
players: vec![],
|
|
||||||
rounds: vec![],
|
|
||||||
phase: InstancePhase::Lobby,
|
|
||||||
max_players: 2,
|
|
||||||
name: String::new(),
|
|
||||||
time_control: TimeControl::Standard,
|
|
||||||
phase_start: Utc::now(),
|
|
||||||
phase_end: Some(TimeControl::Standard.lobby_timeout()),
|
|
||||||
winner: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn redact(mut self, account: Uuid) -> Instance {
|
|
||||||
self.players = self.players.into_iter()
|
|
||||||
.map(|p| p.redact(account))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn phase_timed_out(&self) -> bool {
|
|
||||||
match self.phase_end {
|
|
||||||
Some(t) => Utc::now().signed_duration_since(t).num_milliseconds() > 0,
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timed_out_players(&self) -> Vec<Uuid> {
|
|
||||||
self.players
|
|
||||||
.iter()
|
|
||||||
.filter(|p| !p.ready)
|
|
||||||
.map(|p| p.id)
|
|
||||||
.collect::<Vec<Uuid>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn upkeep(mut self) -> (Instance, Option<Game>) {
|
|
||||||
// time out lobbies that have been open too long
|
|
||||||
if self.phase == InstancePhase::Lobby && self.phase_timed_out() {
|
|
||||||
self.finish();
|
|
||||||
return (self, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.phase != InstancePhase::InProgress {
|
|
||||||
return (self, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.phase_timed_out() {
|
|
||||||
return (self, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_game = self
|
|
||||||
.timed_out_players()
|
|
||||||
.iter()
|
|
||||||
.filter_map(|p| self.player_ready(*p).unwrap())
|
|
||||||
.collect::<Vec<Game>>()
|
|
||||||
.into_iter()
|
|
||||||
.next();
|
|
||||||
|
|
||||||
(self, new_game)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_name(mut self, name: String) -> Result<Instance, Error> {
|
|
||||||
if name.len() == 0 {
|
|
||||||
return Err(err_msg("name must have a length"));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.name = name;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_time_control(mut self, tc: TimeControl) -> Instance {
|
|
||||||
self.time_control = tc;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_player(&mut self, player: Player) -> Result<&mut Instance, Error> {
|
|
||||||
if self.players.len() >= self.max_players {
|
|
||||||
return Err(err_msg("game full"))
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.players.iter().find(|p| p.id == player.id) {
|
|
||||||
Some(_p) => return Err(err_msg("already joined")),
|
|
||||||
None => (),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.players.push(player);
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn player_ready(&mut self, player_id: Uuid) -> Result<Option<Game>, Error> {
|
|
||||||
if ![InstancePhase::InProgress, InstancePhase::Lobby].contains(&self.phase) {
|
|
||||||
return Err(err_msg("instance not in start or vbox phase"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// LOBBY CHECKS
|
|
||||||
if self.phase == InstancePhase::Lobby {
|
|
||||||
let i = self.players
|
|
||||||
.iter_mut()
|
|
||||||
.position(|p| p.id == player_id)
|
|
||||||
.ok_or(err_msg("player_id not found"))?;
|
|
||||||
|
|
||||||
let v = !self.players[i].ready;
|
|
||||||
self.players[i].set_ready(v);
|
|
||||||
|
|
||||||
match self.can_start() {
|
|
||||||
true => {
|
|
||||||
self.start();
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
false => return Ok(None),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// GAME PHASE READY
|
|
||||||
let i = self.players
|
|
||||||
.iter_mut()
|
|
||||||
.position(|p| p.id == player_id)
|
|
||||||
.ok_or(err_msg("player_id not found"))?;
|
|
||||||
|
|
||||||
let v = !self.players[i].ready;
|
|
||||||
self.players[i].set_ready(v);
|
|
||||||
|
|
||||||
// start the game even if afk noobs have no skills
|
|
||||||
if !self.phase_timed_out() && self.players[i].constructs.iter().all(|c| c.skills.len() == 0) {
|
|
||||||
return Err(err_msg("your constructs have no skills"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a game object if both players are ready
|
|
||||||
// this should only happen once
|
|
||||||
|
|
||||||
let all_ready = self.round_ready_check();
|
|
||||||
|
|
||||||
if !all_ready {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let game = self.create_round_game();
|
|
||||||
|
|
||||||
let current_round = self.rounds
|
|
||||||
.last_mut()
|
|
||||||
.expect("instance does not have any rounds");
|
|
||||||
|
|
||||||
current_round.game_id = Some(game.id);
|
|
||||||
|
|
||||||
return Ok(Some(game));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn round_ready_check(&mut self) -> bool {
|
|
||||||
self.players
|
|
||||||
.iter()
|
|
||||||
.all(|p| p.ready)
|
|
||||||
}
|
|
||||||
|
|
||||||
// maybe just embed the games in the instance
|
|
||||||
// but seems hella inefficient
|
|
||||||
fn create_round_game(&mut self) -> Game {
|
|
||||||
let current_round = self.rounds
|
|
||||||
.last_mut()
|
|
||||||
.expect("instance does not have any rounds");
|
|
||||||
|
|
||||||
|
|
||||||
let mut game = Game::new();
|
|
||||||
current_round.game_id = Some(game.id);
|
|
||||||
|
|
||||||
// disable upkeep until players finish their game
|
|
||||||
self.phase_end = None;
|
|
||||||
|
|
||||||
game
|
|
||||||
.set_player_num(2)
|
|
||||||
.set_player_constructs(3)
|
|
||||||
.set_time_control(self.time_control)
|
|
||||||
.set_instance(self.id);
|
|
||||||
|
|
||||||
for player in self.players.clone().into_iter() {
|
|
||||||
game.player_add(player).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(game.can_start());
|
|
||||||
return game.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn can_start(&self) -> bool {
|
|
||||||
self.players.len() == self.max_players && self.all_ready()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start(&mut self) -> &mut Instance {
|
|
||||||
// self.players.sort_unstable_by_key(|p| p.id);
|
|
||||||
self.next_round()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_round(&mut self) -> &mut Instance {
|
|
||||||
if self.finish_condition() {
|
|
||||||
return self.finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.phase = InstancePhase::InProgress;
|
|
||||||
self.phase_start = Utc::now();
|
|
||||||
self.phase_end = self.time_control.vbox_phase_end();
|
|
||||||
|
|
||||||
let bits = match self.rounds.len() > 0 {
|
|
||||||
true => 30,
|
|
||||||
false => 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.players.iter_mut().for_each(|p| {
|
|
||||||
p.vbox.balance_add(bits);
|
|
||||||
p.set_ready(false);
|
|
||||||
p.vbox.fill();
|
|
||||||
});
|
|
||||||
|
|
||||||
self.rounds.push(Round::new());
|
|
||||||
self.bot_round_actions();
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finish_condition(&mut self) -> bool {
|
|
||||||
self.players.iter().any(|p| p.score == Score::Win)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finish(&mut self) -> &mut Instance {
|
|
||||||
self.phase = InstancePhase::Finished;
|
|
||||||
|
|
||||||
for player in self.players.iter() {
|
|
||||||
if player.score == Score::Win {
|
|
||||||
self.winner = Some(player.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finished(&self) -> bool {
|
|
||||||
self.phase == InstancePhase::Finished
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bot_round_actions(&mut self) -> &mut Instance {
|
|
||||||
for bot in self.players.iter_mut().filter(|p| p.bot) {
|
|
||||||
bot.vbox.fill();
|
|
||||||
bot.autobuy();
|
|
||||||
}
|
|
||||||
|
|
||||||
let games = self.players
|
|
||||||
.clone()
|
|
||||||
.iter()
|
|
||||||
.filter(|b| b.bot)
|
|
||||||
.filter_map(|b| self.player_ready(b.id).unwrap())
|
|
||||||
.collect::<Vec<Game>>();
|
|
||||||
|
|
||||||
for game in games {
|
|
||||||
if game.finished() {
|
|
||||||
self.game_finished(&game).unwrap();
|
|
||||||
} else {
|
|
||||||
info!("{:?} unfishededes", game);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn current_game_id(&self) -> Option<Uuid> {
|
|
||||||
if self.phase != InstancePhase::InProgress {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let current_round = self.rounds
|
|
||||||
.last()
|
|
||||||
.expect("instance does not have any rounds");
|
|
||||||
|
|
||||||
if current_round.finished || current_round.game_id.is_none() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
return current_round.game_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> {
|
|
||||||
{
|
|
||||||
let current_round = self.rounds
|
|
||||||
.iter_mut()
|
|
||||||
.filter(|r| r.game_id.is_some())
|
|
||||||
.find(|r| r.game_id.unwrap() == game.id);
|
|
||||||
|
|
||||||
match current_round {
|
|
||||||
Some(c) => c.finished = true,
|
|
||||||
None => return Err(err_msg("instance does not have a round for this game")),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// if you don't win, you lose
|
|
||||||
// ties can happen if both players agree to a draw
|
|
||||||
// or ticks fire and knock everybody out
|
|
||||||
if let Some(winner) = game.winner() {
|
|
||||||
let winner = self.players.iter_mut()
|
|
||||||
.find(|p| p.id == winner.id)
|
|
||||||
.unwrap();
|
|
||||||
winner.score = winner.score.add_win(&Score::Zero);
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.all_games_finished() {
|
|
||||||
self.next_round();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn all_ready(&self) -> bool {
|
|
||||||
self.players.iter().all(|p| p.ready)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn all_games_finished(&self) -> bool {
|
|
||||||
match self.rounds.last() {
|
|
||||||
Some(r) => r.finished,
|
|
||||||
None => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PLAYER ACTIONS
|
|
||||||
fn account_player(&mut self, account: Uuid) -> Result<&mut Player, Error> {
|
|
||||||
self.players
|
|
||||||
.iter_mut()
|
|
||||||
.find(|p| p.id == account)
|
|
||||||
.ok_or(err_msg("account not in instance"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn account_opponent(&mut self, account: Uuid) -> Result<&mut Player, Error> {
|
|
||||||
self.players
|
|
||||||
.iter_mut()
|
|
||||||
.find(|p| p.id != account)
|
|
||||||
.ok_or(err_msg("opponent not in instance"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_action_allowed(&self, account: Uuid) -> Result<(), Error> {
|
|
||||||
if self.players.iter().find(|p| p.id == account).is_none() {
|
|
||||||
return Err(err_msg("player not in this instance"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.phase == InstancePhase::Lobby {
|
|
||||||
return Err(err_msg("game not yet started"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.current_game_id().is_some() {
|
|
||||||
return Err(err_msg("you cannot perform vbox actions while in a game"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_refill(mut self, account: Uuid) -> Result<Instance, Error> {
|
|
||||||
self.vbox_action_allowed(account)?;
|
|
||||||
self.account_player(account)?
|
|
||||||
.vbox_refill()?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_buy(mut self, account: Uuid, group: vbox::ItemType, index: String, construct_id: Option<Uuid>) -> Result<Instance, Error> {
|
|
||||||
self.vbox_action_allowed(account)?;
|
|
||||||
self.account_player(account)?
|
|
||||||
.vbox_buy(group, index, construct_id)?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_combine(mut self, account: Uuid, inv_indices: Vec<String>, vbox_indices: vbox::VboxIndices) -> Result<Instance, Error> {
|
|
||||||
self.vbox_action_allowed(account)?;
|
|
||||||
self.account_player(account)?
|
|
||||||
.vbox_combine(inv_indices, vbox_indices)?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_refund(mut self, account: Uuid, index: String) -> Result<Instance, Error> {
|
|
||||||
self.vbox_action_allowed(account)?;
|
|
||||||
self.account_player(account)?
|
|
||||||
.vbox_refund(index)?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_apply(mut self, account: Uuid, index: String, construct_id: Uuid) -> Result<Instance, Error> {
|
|
||||||
self.vbox_action_allowed(account)?;
|
|
||||||
self.account_player(account)?
|
|
||||||
.vbox_equip(index, construct_id)?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_unequip(mut self, account: Uuid, target: Item, construct_id: Uuid, target_construct_id: Option<Uuid>) -> Result<Instance, Error> {
|
|
||||||
self.vbox_action_allowed(account)?;
|
|
||||||
self.account_player(account)?
|
|
||||||
.vbox_unequip(target, construct_id, target_construct_id)?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn instance_create(tx: &mut Transaction, instance: Instance) -> Result<Instance, Error> {
|
|
||||||
let instance_bytes = to_vec(&instance)?;
|
|
||||||
|
|
||||||
let query = "
|
|
||||||
INSERT INTO instances (id, data, upkeep)
|
|
||||||
VALUES ($1, $2, $3)
|
|
||||||
RETURNING id;
|
|
||||||
";
|
|
||||||
|
|
||||||
let result = tx
|
|
||||||
.query(query, &[&instance.id, &instance_bytes, &instance.phase_end])?;
|
|
||||||
|
|
||||||
result.iter().next().ok_or(format_err!("no instances written"))?;
|
|
||||||
|
|
||||||
return Ok(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn instance_update(tx: &mut Transaction, instance: Instance) -> Result<Instance, Error> {
|
|
||||||
let instance_bytes = to_vec(&instance)?;
|
|
||||||
|
|
||||||
let query = "
|
|
||||||
UPDATE instances
|
|
||||||
SET data = $1, finished = $2, upkeep = $3, updated_at = now()
|
|
||||||
WHERE id = $4
|
|
||||||
RETURNING id, data;
|
|
||||||
";
|
|
||||||
|
|
||||||
let result = tx
|
|
||||||
.query(query, &[&instance_bytes, &instance.finished(), &instance.phase_end, &instance.id])?;
|
|
||||||
|
|
||||||
result.iter().next().ok_or(err_msg("no instance row returned"))?;
|
|
||||||
|
|
||||||
trace!("{:?} wrote instance", instance.id);
|
|
||||||
|
|
||||||
if instance.finished() {
|
|
||||||
info!("finished id={:?}", instance.id);
|
|
||||||
|
|
||||||
match instance_json_file_write(&instance) {
|
|
||||||
Ok(dest) => info!("wrote dest={:?}", dest),
|
|
||||||
Err(e) => error!("json write error={:?}", e),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn instance_json_file_write(g: &Instance) -> Result<String, Error> {
|
|
||||||
let dest = format!("/var/lib/mnml/data/instances/{}.mnml.instance.json", g.id);
|
|
||||||
serde_json::to_writer(File::create(&dest)?, g)?;
|
|
||||||
Ok(dest)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn instance_get(tx: &mut Transaction, instance_id: Uuid) -> Result<Instance, Error> {
|
|
||||||
let query = "
|
|
||||||
SELECT *
|
|
||||||
FROM instances
|
|
||||||
WHERE id = $1
|
|
||||||
FOR UPDATE;
|
|
||||||
";
|
|
||||||
|
|
||||||
let result = tx
|
|
||||||
.query(query, &[&instance_id])?;
|
|
||||||
|
|
||||||
let returned = match result.iter().next() {
|
|
||||||
Some(row) => row,
|
|
||||||
None => return Err(err_msg("instance not found")),
|
|
||||||
};
|
|
||||||
|
|
||||||
let instance_bytes: Vec<u8> = returned.get("data");
|
|
||||||
let instance = from_slice::<Instance>(&instance_bytes)?;
|
|
||||||
|
|
||||||
return Ok(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn instance_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> {
|
|
||||||
let query = "
|
|
||||||
DELETE
|
|
||||||
FROM instances
|
|
||||||
WHERE id = $1;
|
|
||||||
";
|
|
||||||
|
|
||||||
let result = tx
|
|
||||||
.execute(query, &[&id])?;
|
|
||||||
|
|
||||||
if result != 1 {
|
|
||||||
return Err(format_err!("unable to delete instance {:?}", id));
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("instance deleted {:?}", id);
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn _instance_list(tx: &mut Transaction) -> Result<Vec<Instance>, Error> {
|
|
||||||
let query = "
|
|
||||||
SELECT data, id
|
|
||||||
FROM instances
|
|
||||||
AND finished = false;
|
|
||||||
";
|
|
||||||
|
|
||||||
let result = tx
|
|
||||||
.query(query, &[])?;
|
|
||||||
|
|
||||||
let mut list = vec![];
|
|
||||||
|
|
||||||
for row in result.into_iter() {
|
|
||||||
let bytes: Vec<u8> = row.get(0);
|
|
||||||
let id = row.get(1);
|
|
||||||
|
|
||||||
match from_slice::<Instance>(&bytes) {
|
|
||||||
Ok(i) => list.push(i),
|
|
||||||
Err(_e) => {
|
|
||||||
instance_delete(tx, id)?;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn instances_need_upkeep(tx: &mut Transaction) -> Result<Vec<Instance>, Error> {
|
|
||||||
let query = "
|
|
||||||
SELECT data, id
|
|
||||||
FROM instances
|
|
||||||
WHERE finished = false
|
|
||||||
AND upkeep < now()
|
|
||||||
FOR UPDATE;
|
|
||||||
";
|
|
||||||
|
|
||||||
let result = tx
|
|
||||||
.query(query, &[])?;
|
|
||||||
|
|
||||||
let mut list = vec![];
|
|
||||||
|
|
||||||
for row in result.into_iter() {
|
|
||||||
let bytes: Vec<u8> = row.get(0);
|
|
||||||
let id = row.get(1);
|
|
||||||
|
|
||||||
match from_slice::<Instance>(&bytes) {
|
|
||||||
Ok(i) => list.push(i),
|
|
||||||
Err(_e) => {
|
|
||||||
instance_delete(tx, id)?;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
// timed out instances with no time control
|
|
||||||
pub fn instances_idle(tx: &mut Transaction) -> Result<Vec<Instance>, Error> {
|
|
||||||
let query = "
|
|
||||||
SELECT data, id
|
|
||||||
FROM instances
|
|
||||||
WHERE finished = false
|
|
||||||
AND updated_at < now() - interval '1 hour'
|
|
||||||
FOR UPDATE;
|
|
||||||
";
|
|
||||||
|
|
||||||
let result = tx
|
|
||||||
.query(query, &[])?;
|
|
||||||
|
|
||||||
let mut list = vec![];
|
|
||||||
|
|
||||||
for row in result.into_iter() {
|
|
||||||
let bytes: Vec<u8> = row.get(0);
|
|
||||||
let id = row.get(1);
|
|
||||||
|
|
||||||
match from_slice::<Instance>(&bytes) {
|
|
||||||
Ok(i) => list.push(i),
|
|
||||||
Err(_e) => {
|
|
||||||
instance_delete(tx, id)?;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn instance_practice(tx: &mut Transaction, account: &Account) -> Result<Instance, Error> {
|
|
||||||
let bot = bot_player();
|
|
||||||
let bot_id = bot.id;
|
|
||||||
|
|
||||||
// generate bot imgs for the client to see
|
|
||||||
for c in bot.constructs.iter() {
|
|
||||||
img::shapes_write(c.img)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut instance = Instance::new()
|
|
||||||
.set_time_control(TimeControl::Practice)
|
|
||||||
.set_name(bot.name.clone())?;
|
|
||||||
|
|
||||||
let player = Player::from_account(tx, account)?;
|
|
||||||
|
|
||||||
instance.add_player(player.clone())?;
|
|
||||||
instance.add_player(bot)?;
|
|
||||||
|
|
||||||
instance.player_ready(bot_id)?;
|
|
||||||
// skip faceoff
|
|
||||||
instance.player_ready(player.id)?;
|
|
||||||
|
|
||||||
instance = instance_create(tx, instance)?;
|
|
||||||
player_create(tx, player, instance.id, account)?;
|
|
||||||
|
|
||||||
Ok(instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pvp(tx: &mut Transaction, a: &Account, b: &Account) -> Result<Instance, Error> {
|
|
||||||
let mut instance = Instance::new()
|
|
||||||
// TODO generate nice game names
|
|
||||||
.set_name("PVP".to_string())?;
|
|
||||||
|
|
||||||
instance = instance_create(tx, instance)?;
|
|
||||||
|
|
||||||
for account in [a, b].iter() {
|
|
||||||
let acc_p = Player::from_account(tx, &account)?;
|
|
||||||
let player = player_create(tx, acc_p, instance.id, account)?;
|
|
||||||
instance.add_player(player)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
instance_update(tx, instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn instance_abandon(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result<RpcMessage, Error> {
|
|
||||||
let mut instance = instance_get(tx, instance_id)?;
|
|
||||||
|
|
||||||
if let Some(game_id) = instance.current_game_id() {
|
|
||||||
let mut game = game_get(tx, game_id)?;
|
|
||||||
game.player_by_id(account.id)?.forfeit();
|
|
||||||
game = game.start(); // actually finishes it...
|
|
||||||
game_update(tx, &game)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.account_player(account.id)?.set_lose();
|
|
||||||
instance.account_opponent(account.id)?.set_win();
|
|
||||||
instance.next_round();
|
|
||||||
|
|
||||||
Ok(RpcMessage::InstanceState(instance_update(tx, instance)?))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn instance_ready(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result<RpcMessage, Error> {
|
|
||||||
let mut instance = instance_get(tx, instance_id)?;
|
|
||||||
let player_id = instance.account_player(account.id)?.id;
|
|
||||||
|
|
||||||
if let Some(game) = instance.player_ready(player_id)? {
|
|
||||||
game_write(tx, &game)?;
|
|
||||||
|
|
||||||
// ensures cleanup for warden etc is done
|
|
||||||
game_update(tx, &game)?;
|
|
||||||
|
|
||||||
instance_update(tx, instance)?;
|
|
||||||
return Ok(RpcMessage::GameState(game));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(RpcMessage::InstanceState(instance_update(tx, instance)?))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn instance_state(tx: &mut Transaction, instance_id: Uuid) -> Result<RpcMessage, Error> {
|
|
||||||
let instance = instance_get(tx, instance_id)?;
|
|
||||||
|
|
||||||
if let Some(game_id) = instance.current_game_id() {
|
|
||||||
let game = game_get(tx, game_id)?;
|
|
||||||
|
|
||||||
// return the game until it's finished
|
|
||||||
if game.phase != Phase::Finished {
|
|
||||||
return Ok(RpcMessage::GameState(game))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(RpcMessage::InstanceState(instance))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn instance_game_finished(tx: &mut Transaction, game: &Game, instance_id: Uuid) -> Result<(), Error> {
|
|
||||||
let mut instance = instance_get(tx, instance_id)?;
|
|
||||||
instance.game_finished(game)?;
|
|
||||||
// info!("{:?}", instance_get(tx, instance_id)?);
|
|
||||||
|
|
||||||
instance_update(tx, instance)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bot_instance() -> Instance {
|
|
||||||
let mut instance = Instance::new();
|
|
||||||
|
|
||||||
let bot_player = bot_player();
|
|
||||||
let bot = bot_player.id;
|
|
||||||
instance.add_player(bot_player).unwrap();
|
|
||||||
|
|
||||||
let player_account = Uuid::new_v4();
|
|
||||||
let constructs = instance_mobs(player_account);
|
|
||||||
let player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true);
|
|
||||||
|
|
||||||
instance.add_player(player).expect("could not add player");
|
|
||||||
instance.player_ready(player_account).unwrap();
|
|
||||||
instance.player_ready(bot).unwrap();
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn demo() -> Result<Vec<Player>, Error> {
|
|
||||||
let bot = bot_player();
|
|
||||||
|
|
||||||
// generate bot imgs for the client to see
|
|
||||||
for c in bot.constructs.iter() {
|
|
||||||
img::shapes_write(c.img)?;
|
|
||||||
};
|
|
||||||
|
|
||||||
let bot2 = bot_player();
|
|
||||||
|
|
||||||
// generate bot imgs for the client to see
|
|
||||||
for c in bot2.constructs.iter() {
|
|
||||||
img::shapes_write(c.img)?;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
Ok(vec![bot, bot2])
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn instance_pve_test() {
|
|
||||||
let mut instance = Instance::new();
|
|
||||||
|
|
||||||
let bot = bot_player();
|
|
||||||
let bot_one = bot.id;
|
|
||||||
instance.add_player(bot).unwrap();
|
|
||||||
|
|
||||||
let bot = bot_player();
|
|
||||||
let bot_two = bot.id;
|
|
||||||
instance.add_player(bot).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(instance.phase, InstancePhase::Lobby);
|
|
||||||
instance.player_ready(bot_one).unwrap();
|
|
||||||
instance.player_ready(bot_two).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(instance.phase, InstancePhase::Finished);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn instance_bot_vbox_test() {
|
|
||||||
let _instance = Instance::new();
|
|
||||||
let player_account = Uuid::new_v4();
|
|
||||||
let constructs = instance_mobs(player_account);
|
|
||||||
let _player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn instance_start_test() {
|
|
||||||
let mut instance = Instance::new();
|
|
||||||
|
|
||||||
assert_eq!(instance.max_players, 2);
|
|
||||||
|
|
||||||
let player_account = Uuid::new_v4();
|
|
||||||
let constructs = instance_mobs(player_account);
|
|
||||||
let player = Player::new(player_account, &"a".to_string(), constructs);
|
|
||||||
let a_id = player.id;
|
|
||||||
|
|
||||||
instance.add_player(player).expect("could not add player");
|
|
||||||
assert!(!instance.can_start());
|
|
||||||
|
|
||||||
let player_account = Uuid::new_v4();
|
|
||||||
let constructs = instance_mobs(player_account);
|
|
||||||
let player = Player::new(player_account, &"b".to_string(), constructs);
|
|
||||||
let b_id = player.id;
|
|
||||||
|
|
||||||
instance.add_player(player).expect("could not add player");
|
|
||||||
|
|
||||||
assert_eq!(instance.phase, InstancePhase::Lobby);
|
|
||||||
instance.player_ready(a_id).expect("a ready");
|
|
||||||
assert!(!instance.can_start());
|
|
||||||
|
|
||||||
instance.player_ready(b_id).expect("b ready");
|
|
||||||
assert_eq!(instance.phase, InstancePhase::InProgress);
|
|
||||||
|
|
||||||
assert!(!instance.can_start());
|
|
||||||
|
|
||||||
instance.players[0].autobuy();
|
|
||||||
instance.players[1].autobuy();
|
|
||||||
|
|
||||||
instance.player_ready(a_id).expect("a ready");
|
|
||||||
let game = instance.player_ready(b_id).expect("b ready");
|
|
||||||
|
|
||||||
assert!(game.is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn instance_upkeep_test() {
|
|
||||||
let mut instance = Instance::new();
|
|
||||||
|
|
||||||
let player_account = Uuid::new_v4();
|
|
||||||
let constructs = instance_mobs(player_account);
|
|
||||||
let player = Player::new(player_account, &"a".to_string(), constructs);
|
|
||||||
let a_id = player.id;
|
|
||||||
|
|
||||||
instance.add_player(player).expect("could not add player");
|
|
||||||
assert!(!instance.can_start());
|
|
||||||
|
|
||||||
let player_account = Uuid::new_v4();
|
|
||||||
let constructs = instance_mobs(player_account);
|
|
||||||
let player = Player::new(player_account, &"b".to_string(), constructs);
|
|
||||||
let b_id = player.id;
|
|
||||||
instance.add_player(player).expect("could not add player");
|
|
||||||
|
|
||||||
instance.players[0].autobuy();
|
|
||||||
|
|
||||||
instance.player_ready(a_id).expect("a ready");
|
|
||||||
instance.player_ready(b_id).expect("b ready");
|
|
||||||
|
|
||||||
instance.phase_end = Some(Utc::now().checked_sub_signed(Duration::seconds(500)).unwrap());
|
|
||||||
|
|
||||||
let (mut instance, new_games) = instance.upkeep();
|
|
||||||
|
|
||||||
assert!(new_games.is_some());
|
|
||||||
|
|
||||||
let game = new_games.unwrap();
|
|
||||||
assert!(game.finished());
|
|
||||||
|
|
||||||
instance.game_finished(&game).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(instance.rounds.len(), 2);
|
|
||||||
assert!(instance.players.iter().all(|p| !p.ready));
|
|
||||||
|
|
||||||
// info!("{:#?}", instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn instance_upkeep_idle_lobby_test() {
|
|
||||||
let mut instance = Instance::new();
|
|
||||||
|
|
||||||
let player_account = Uuid::new_v4();
|
|
||||||
let constructs = instance_mobs(player_account);
|
|
||||||
let player = Player::new(player_account, &"a".to_string(), constructs);
|
|
||||||
let _a_id = player.id;
|
|
||||||
|
|
||||||
instance.add_player(player).expect("could not add player");
|
|
||||||
assert!(!instance.can_start());
|
|
||||||
|
|
||||||
instance.phase_end = Some(Utc::now().checked_sub_signed(Duration::minutes(61)).unwrap());
|
|
||||||
let (instance, _new_games) = instance.upkeep();
|
|
||||||
|
|
||||||
assert!(instance.finished());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1607
server/src/item.rs
1607
server/src/item.rs
File diff suppressed because it is too large
Load Diff
@ -33,28 +33,19 @@ extern crate lettre_email;
|
|||||||
extern crate ws;
|
extern crate ws;
|
||||||
extern crate crossbeam_channel;
|
extern crate crossbeam_channel;
|
||||||
|
|
||||||
|
extern crate mnml_core;
|
||||||
|
|
||||||
mod account;
|
mod account;
|
||||||
mod acp;
|
// mod acp;
|
||||||
mod construct;
|
|
||||||
mod effect;
|
|
||||||
mod game;
|
|
||||||
mod instance;
|
|
||||||
mod item;
|
|
||||||
mod img;
|
mod img;
|
||||||
mod mail;
|
mod mail;
|
||||||
mod mob;
|
|
||||||
mod mtx;
|
mod mtx;
|
||||||
mod names;
|
mod names;
|
||||||
mod http;
|
mod http;
|
||||||
mod payments;
|
mod payments;
|
||||||
mod pg;
|
mod pg;
|
||||||
mod player;
|
|
||||||
mod events;
|
mod events;
|
||||||
pub mod rpc;
|
pub mod rpc;
|
||||||
mod skill;
|
|
||||||
mod spec;
|
|
||||||
mod util;
|
|
||||||
mod vbox;
|
|
||||||
mod warden;
|
mod warden;
|
||||||
|
|
||||||
use std::thread::{spawn};
|
use std::thread::{spawn};
|
||||||
|
|||||||
@ -1,30 +0,0 @@
|
|||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use std::iter;
|
|
||||||
|
|
||||||
use construct::{Construct};
|
|
||||||
use names::{name};
|
|
||||||
use player::{Player};
|
|
||||||
|
|
||||||
pub fn generate_mob() -> Construct {
|
|
||||||
let mob = Construct::new()
|
|
||||||
.named(&name());
|
|
||||||
|
|
||||||
return mob;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn instance_mobs(player_id: Uuid) -> Vec<Construct> {
|
|
||||||
iter::repeat_with(||
|
|
||||||
generate_mob()
|
|
||||||
.set_account(player_id))
|
|
||||||
// .learn(Skill::Attack))
|
|
||||||
.take(3)
|
|
||||||
.collect::<Vec<Construct>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bot_player() -> Player {
|
|
||||||
let bot_id = Uuid::new_v4();
|
|
||||||
let constructs = instance_mobs(bot_id);
|
|
||||||
Player::new(bot_id, &name(), constructs).set_bot(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -10,7 +10,9 @@ use failure::err_msg;
|
|||||||
use account;
|
use account;
|
||||||
use account::Account;
|
use account::Account;
|
||||||
|
|
||||||
use construct::{Construct, construct_select, construct_write, construct_spawn};
|
use mnml_core::construct::{Construct};
|
||||||
|
|
||||||
|
use pg::{construct_select, construct_write, construct_spawn};
|
||||||
use names::{name as generate_name};
|
use names::{name as generate_name};
|
||||||
use img;
|
use img;
|
||||||
|
|
||||||
|
|||||||
@ -1,70 +0,0 @@
|
|||||||
use petgraph::graph::{Graph, UnGraph, NodeIndex};
|
|
||||||
use petgraph::dot::{Dot, Config};
|
|
||||||
|
|
||||||
#[derive(Debug,Clone,Copy,PartialEq,Eq,Hash,PartialOrd,Ord,Serialize,Deserialize)]
|
|
||||||
pub struct Passive {
|
|
||||||
id: &'static str,
|
|
||||||
allocated: bool,
|
|
||||||
effect: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Passive {
|
|
||||||
fn new(id: &'static str) -> Passive {
|
|
||||||
return Passive {
|
|
||||||
id,
|
|
||||||
allocated: false,
|
|
||||||
effect: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_passive_graph() -> UnGraph<Passive, ()> {
|
|
||||||
let mut gr = Graph::new_undirected();
|
|
||||||
|
|
||||||
let start = gr.add_node(Passive::new("START"));
|
|
||||||
let mut last;
|
|
||||||
let mut next;
|
|
||||||
|
|
||||||
// Natural Selection nodes
|
|
||||||
next = gr.add_node(Passive::new("NS"));
|
|
||||||
gr.add_edge(start, next, ());
|
|
||||||
last = next;
|
|
||||||
|
|
||||||
next = gr.add_node(Passive::new("NSPD0000"));
|
|
||||||
gr.add_edge(last, next, ());
|
|
||||||
last = next;
|
|
||||||
|
|
||||||
next = gr.add_node(Passive::new("NSPD0001"));
|
|
||||||
gr.add_edge(last, next, ());
|
|
||||||
last = next;
|
|
||||||
|
|
||||||
next = gr.add_node(Passive::new("NSPD0002"));
|
|
||||||
gr.add_edge(last, next, ());
|
|
||||||
last = next;
|
|
||||||
|
|
||||||
next = gr.add_node(Passive::new("NSPD0003"));
|
|
||||||
gr.add_edge(last, next, ());
|
|
||||||
last = next;
|
|
||||||
|
|
||||||
next = gr.add_node(Passive::new("NSBLOCK"));
|
|
||||||
gr.add_edge(last, next, ());
|
|
||||||
last = next;
|
|
||||||
|
|
||||||
return gr;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use passives::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn create_graph() {
|
|
||||||
let _graph = create_passive_graph();
|
|
||||||
// good shit;
|
|
||||||
// let nodes = graph.node_indices().collect::<Vec<NodeIndex>>();
|
|
||||||
// info!("{:?}", nodes[0]);
|
|
||||||
// info!("{:?}", graph.node_weight(nodes[0]));
|
|
||||||
|
|
||||||
// info!("{:?}", Dot::with_config(&graph, &[Config::EdgeNoLabel]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
745
server/src/pg.rs
745
server/src/pg.rs
@ -1,22 +1,35 @@
|
|||||||
|
use std::fs::File;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::thread::spawn;
|
use std::thread::spawn;
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use failure::err_msg;
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
|
|
||||||
use r2d2::{Pool};
|
use r2d2::{Pool, PooledConnection};
|
||||||
use r2d2::{PooledConnection};
|
|
||||||
use r2d2_postgres::{TlsMode, PostgresConnectionManager};
|
use r2d2_postgres::{TlsMode, PostgresConnectionManager};
|
||||||
use fallible_iterator::{FallibleIterator};
|
use fallible_iterator::{FallibleIterator};
|
||||||
|
use postgres::transaction::Transaction;
|
||||||
|
|
||||||
use crossbeam_channel::{Sender};
|
use crossbeam_channel::{Sender};
|
||||||
|
use serde_cbor::{from_slice, to_vec};
|
||||||
|
|
||||||
|
use mnml_core::construct::{Construct, ConstructSkeleton};
|
||||||
|
use mnml_core::game::{Game, Phase};
|
||||||
|
use mnml_core::player::Player;
|
||||||
|
use mnml_core::mob::instance_mobs;
|
||||||
|
use mnml_core::vbox::{ItemType, VboxIndices};
|
||||||
|
use mnml_core::item::Item;
|
||||||
|
use mnml_core::skill::Skill;
|
||||||
|
use mnml_core::mob::bot_player;
|
||||||
|
use mnml_core::instance::{Instance, TimeControl};
|
||||||
|
|
||||||
use events::{Event};
|
use events::{Event};
|
||||||
use account;
|
|
||||||
use game;
|
|
||||||
use instance;
|
|
||||||
use rpc::RpcMessage;
|
use rpc::RpcMessage;
|
||||||
|
use account;
|
||||||
|
use account::{Account};
|
||||||
|
use img;
|
||||||
|
|
||||||
pub type Db = PooledConnection<PostgresConnectionManager>;
|
pub type Db = PooledConnection<PostgresConnectionManager>;
|
||||||
pub type PgPool = Pool<PostgresConnectionManager>;
|
pub type PgPool = Pool<PostgresConnectionManager>;
|
||||||
@ -88,9 +101,9 @@ fn handle_notification(n: Notification, pool: &PgPool, events: &Sender<Event>) {
|
|||||||
Table::Accounts =>
|
Table::Accounts =>
|
||||||
Some(Event::Push(n.id, RpcMessage::AccountState(account::select(&db, n.id).unwrap()))),
|
Some(Event::Push(n.id, RpcMessage::AccountState(account::select(&db, n.id).unwrap()))),
|
||||||
Table::Instances =>
|
Table::Instances =>
|
||||||
Some(Event::Push(n.id, instance::instance_state(&mut tx, n.id).unwrap())),
|
Some(Event::Push(n.id, instance_state(&mut tx, n.id).unwrap())),
|
||||||
Table::Games =>
|
Table::Games =>
|
||||||
Some(Event::Push(n.id, RpcMessage::GameState(game::game_get(&mut tx, n.id).unwrap()))),
|
Some(Event::Push(n.id, RpcMessage::GameState(game_get(&mut tx, n.id).unwrap()))),
|
||||||
_ => {
|
_ => {
|
||||||
// warn!("unimplemented update notification {:?}", n);
|
// warn!("unimplemented update notification {:?}", n);
|
||||||
None
|
None
|
||||||
@ -128,3 +141,721 @@ pub fn listen(pool: PgPool, events: Sender<Event>) -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn construct_delete(tx: &mut Transaction, id: Uuid, account_id: Uuid) -> Result<(), Error> {
|
||||||
|
let query = "
|
||||||
|
DELETE
|
||||||
|
FROM constructs
|
||||||
|
WHERE id = $1
|
||||||
|
and account = $2;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = tx
|
||||||
|
.execute(query, &[&id, &account_id])?;
|
||||||
|
|
||||||
|
if result != 1 {
|
||||||
|
return Err(format_err!("unable to delete construct {:?}", id));
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("construct deleted {:?}", id);
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn construct_get(tx: &mut Transaction, id: Uuid, account_id: Uuid) -> Result<Construct, Error> {
|
||||||
|
let query = "
|
||||||
|
SELECT data
|
||||||
|
FROM constructs
|
||||||
|
WHERE id = $1
|
||||||
|
AND account = $2;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = tx
|
||||||
|
.query(query, &[&id, &account_id])?;
|
||||||
|
|
||||||
|
let result = result.iter().next().ok_or(format_err!("construct {:} not found", id))?;
|
||||||
|
let construct_bytes: Vec<u8> = result.get(0);
|
||||||
|
let skeleton = from_slice::<ConstructSkeleton>(&construct_bytes)?;
|
||||||
|
|
||||||
|
return Ok(Construct::from_skeleton(&skeleton));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn construct_select(tx: &mut Transaction, id: Uuid, account_id: Uuid) -> Result<Construct, Error> {
|
||||||
|
let query = "
|
||||||
|
SELECT data
|
||||||
|
FROM constructs
|
||||||
|
WHERE id = $1
|
||||||
|
AND account = $2
|
||||||
|
FOR UPDATE;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = tx
|
||||||
|
.query(query, &[&id, &account_id])?;
|
||||||
|
|
||||||
|
let result = result.iter().next().ok_or(format_err!("construct {:} not found", id))?;
|
||||||
|
let construct_bytes: Vec<u8> = result.get(0);
|
||||||
|
let skeleton = from_slice::<ConstructSkeleton>(&construct_bytes)?;
|
||||||
|
|
||||||
|
return Ok(Construct::from_skeleton(&skeleton));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn construct_spawn(tx: &mut Transaction, account: Uuid, name: String, team: bool) -> Result<Construct, Error> {
|
||||||
|
let construct = Construct::new()
|
||||||
|
.named(&name)
|
||||||
|
.set_account(account);
|
||||||
|
|
||||||
|
let construct_bytes = to_vec(&construct)?;
|
||||||
|
|
||||||
|
let query = "
|
||||||
|
INSERT INTO constructs (id, account, data, team)
|
||||||
|
VALUES ($1, $2, $3, $4)
|
||||||
|
RETURNING id, account;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = tx
|
||||||
|
.query(query, &[&construct.id, &account, &construct_bytes, &team])?;
|
||||||
|
|
||||||
|
let _returned = result.iter().next().ok_or(err_msg("no row returned"))?;
|
||||||
|
|
||||||
|
img::shapes_write(construct.img)?;
|
||||||
|
|
||||||
|
info!("spawned construct account={:} name={:?}", account, construct.name);
|
||||||
|
return Ok(construct);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn construct_write(tx: &mut Transaction, construct: Construct) -> Result<Construct, Error> {
|
||||||
|
let construct_bytes = to_vec(&construct.to_skeleton())?;
|
||||||
|
|
||||||
|
let query = "
|
||||||
|
UPDATE constructs
|
||||||
|
SET data = $1, updated_at = now()
|
||||||
|
WHERE id = $2
|
||||||
|
RETURNING id, account, data;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = tx
|
||||||
|
.query(query, &[&construct_bytes, &construct.id])?;
|
||||||
|
|
||||||
|
let _returned = result.iter().next().expect("no row returned");
|
||||||
|
|
||||||
|
// info!("{:?} wrote construct", construct.id);
|
||||||
|
|
||||||
|
return Ok(construct);
|
||||||
|
}
|
||||||
|
pub fn game_write(tx: &mut Transaction, game: &Game) -> Result<(), Error> {
|
||||||
|
let game_bytes = to_vec(&game)?;
|
||||||
|
|
||||||
|
let query = "
|
||||||
|
INSERT INTO games (id, data, upkeep)
|
||||||
|
VALUES ($1, $2, $3)
|
||||||
|
RETURNING id;
|
||||||
|
";
|
||||||
|
|
||||||
|
// no games should be sent to db that are not in progress
|
||||||
|
let result = tx
|
||||||
|
.query(query, &[&game.id, &game_bytes, &game.phase_end])?;
|
||||||
|
|
||||||
|
result.iter().next().ok_or(format_err!("no game written"))?;
|
||||||
|
|
||||||
|
// info!("{:} wrote game", game.id);
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn game_state(tx: &mut Transaction, account: &Account, id: Uuid) -> Result<Game, Error> {
|
||||||
|
Ok(game_get(tx, id)?.redact(account.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn game_get(tx: &mut Transaction, id: Uuid) -> Result<Game, Error> {
|
||||||
|
let query = "
|
||||||
|
SELECT *
|
||||||
|
FROM games
|
||||||
|
WHERE id = $1
|
||||||
|
FOR UPDATE;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = tx
|
||||||
|
.query(query, &[&id])?;
|
||||||
|
|
||||||
|
let returned = match result.iter().next() {
|
||||||
|
Some(row) => row,
|
||||||
|
None => return Err(err_msg("game not found")),
|
||||||
|
};
|
||||||
|
|
||||||
|
// tells from_slice to cast into a construct
|
||||||
|
let game_bytes: Vec<u8> = returned.get("data");
|
||||||
|
let game = from_slice::<Game>(&game_bytes)?;
|
||||||
|
|
||||||
|
return Ok(game);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select(db: &Db, id: Uuid) -> Result<Game, Error> {
|
||||||
|
let query = "
|
||||||
|
SELECT *
|
||||||
|
FROM games
|
||||||
|
WHERE id = $1;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = db
|
||||||
|
.query(query, &[&id])?;
|
||||||
|
|
||||||
|
let returned = match result.iter().next() {
|
||||||
|
Some(row) => row,
|
||||||
|
None => return Err(err_msg("game not found")),
|
||||||
|
};
|
||||||
|
|
||||||
|
// tells from_slice to cast into a construct
|
||||||
|
let game_bytes: Vec<u8> = returned.get("data");
|
||||||
|
let game = from_slice::<Game>(&game_bytes)?;
|
||||||
|
|
||||||
|
return Ok(game);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list(db: &Db, number: u32) -> Result<Vec<Game>, Error> {
|
||||||
|
let query = "
|
||||||
|
SELECT data
|
||||||
|
FROM games
|
||||||
|
ORDER BY created_at
|
||||||
|
LIMIT $1;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = db
|
||||||
|
.query(query, &[&number])?;
|
||||||
|
|
||||||
|
let mut list = vec![];
|
||||||
|
|
||||||
|
for row in result.into_iter() {
|
||||||
|
let bytes: Vec<u8> = row.get(0);
|
||||||
|
|
||||||
|
match from_slice::<Game>(&bytes) {
|
||||||
|
Ok(i) => list.push(i),
|
||||||
|
Err(e) => {
|
||||||
|
warn!("{:?}", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn games_need_upkeep(tx: &mut Transaction) -> Result<Vec<Game>, Error> {
|
||||||
|
let query = "
|
||||||
|
SELECT data, id
|
||||||
|
FROM games
|
||||||
|
WHERE finished = false
|
||||||
|
AND upkeep < now()
|
||||||
|
FOR UPDATE;
|
||||||
|
";
|
||||||
|
let result = tx
|
||||||
|
.query(query, &[])?;
|
||||||
|
|
||||||
|
let mut list = vec![];
|
||||||
|
|
||||||
|
for row in result.into_iter() {
|
||||||
|
let bytes: Vec<u8> = row.get(0);
|
||||||
|
let id = row.get(1);
|
||||||
|
|
||||||
|
match from_slice::<Game>(&bytes) {
|
||||||
|
Ok(i) => list.push(i),
|
||||||
|
Err(_e) => {
|
||||||
|
game_delete(tx, id)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn game_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> {
|
||||||
|
let query = "
|
||||||
|
DELETE
|
||||||
|
FROM games
|
||||||
|
WHERE id = $1;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = tx
|
||||||
|
.execute(query, &[&id])?;
|
||||||
|
|
||||||
|
if result != 1 {
|
||||||
|
return Err(format_err!("unable to delete player {:?}", id));
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("game deleted {:?}", id);
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn game_update(tx: &mut Transaction, game: &Game) -> Result<(), Error> {
|
||||||
|
let game_bytes = to_vec(&game)?;
|
||||||
|
|
||||||
|
let query = "
|
||||||
|
UPDATE games
|
||||||
|
SET data = $1, finished = $2, upkeep = $3, updated_at = now()
|
||||||
|
WHERE id = $4
|
||||||
|
RETURNING id, data;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = tx
|
||||||
|
.query(query, &[&game_bytes, &game.finished(), &game.phase_end, &game.id])?;
|
||||||
|
|
||||||
|
result.iter().next().ok_or(format_err!("game {:?} could not be written", game))?;
|
||||||
|
|
||||||
|
if game.finished() {
|
||||||
|
info!("finished id={:?}", game.id);
|
||||||
|
|
||||||
|
match game_json_file_write(&game) {
|
||||||
|
Ok(dest) => info!("wrote dest={:?}", dest),
|
||||||
|
Err(e) => error!("json write error={:?}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(i) = game.instance {
|
||||||
|
instance_game_finished(tx, &game, i)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn game_json_file_write(g: &Game) -> Result<String, Error> {
|
||||||
|
let dest = format!("/var/lib/mnml/data/games/{}.mnml.game.json", g.id);
|
||||||
|
serde_json::to_writer(File::create(&dest)?, g)?;
|
||||||
|
Ok(dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn game_skill(tx: &mut Transaction, account: &Account, game_id: Uuid, construct_id: Uuid, target_construct_id: Uuid, skill: Skill) -> Result<Game, Error> {
|
||||||
|
let mut game = game_get(tx, game_id)?;
|
||||||
|
|
||||||
|
game.add_skill(account.id, construct_id, target_construct_id, skill)?;
|
||||||
|
|
||||||
|
if game.skill_phase_finished() {
|
||||||
|
game = game.resolve_phase_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
game_update(tx, &game)?;
|
||||||
|
|
||||||
|
Ok(game)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn game_offer_draw(tx: &mut Transaction, account: &Account, game_id: Uuid) -> Result<Game, Error> {
|
||||||
|
let game = game_get(tx, game_id)?
|
||||||
|
.offer_draw(account.id)?;
|
||||||
|
|
||||||
|
game_update(tx, &game)?;
|
||||||
|
|
||||||
|
Ok(game)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn game_concede(tx: &mut Transaction, account: &Account, game_id: Uuid) -> Result<Game, Error> {
|
||||||
|
let game = game_get(tx, game_id)?
|
||||||
|
.concede(account.id)?;
|
||||||
|
|
||||||
|
game_update(tx, &game)?;
|
||||||
|
|
||||||
|
Ok(game)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn game_skill_clear(tx: &mut Transaction, account: &Account, game_id: Uuid) -> Result<Game, Error> {
|
||||||
|
let mut game = game_get(tx, game_id)?;
|
||||||
|
game.clear_skill(account.id)?;
|
||||||
|
game_update(tx, &game)?;
|
||||||
|
|
||||||
|
Ok(game)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn game_ready(tx: &mut Transaction, account: &Account, id: Uuid) -> Result<Game, Error> {
|
||||||
|
let mut game = game_get(tx, id)?;
|
||||||
|
|
||||||
|
game.player_ready(account.id)?;
|
||||||
|
|
||||||
|
if game.skill_phase_finished() {
|
||||||
|
game = game.resolve_phase_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
game_update(tx, &game)?;
|
||||||
|
|
||||||
|
Ok(game)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pub fn instance_create(tx: &mut Transaction, instance: Instance) -> Result<Instance, Error> {
|
||||||
|
let instance_bytes = to_vec(&instance)?;
|
||||||
|
|
||||||
|
let query = "
|
||||||
|
INSERT INTO instances (id, data, upkeep)
|
||||||
|
VALUES ($1, $2, $3)
|
||||||
|
RETURNING id;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = tx
|
||||||
|
.query(query, &[&instance.id, &instance_bytes, &instance.phase_end])?;
|
||||||
|
|
||||||
|
result.iter().next().ok_or(format_err!("no instances written"))?;
|
||||||
|
|
||||||
|
return Ok(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn instance_update(tx: &mut Transaction, instance: Instance) -> Result<Instance, Error> {
|
||||||
|
let instance_bytes = to_vec(&instance)?;
|
||||||
|
|
||||||
|
let query = "
|
||||||
|
UPDATE instances
|
||||||
|
SET data = $1, finished = $2, upkeep = $3, updated_at = now()
|
||||||
|
WHERE id = $4
|
||||||
|
RETURNING id, data;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = tx
|
||||||
|
.query(query, &[&instance_bytes, &instance.finished(), &instance.phase_end, &instance.id])?;
|
||||||
|
|
||||||
|
result.iter().next().ok_or(err_msg("no instance row returned"))?;
|
||||||
|
|
||||||
|
trace!("{:?} wrote instance", instance.id);
|
||||||
|
|
||||||
|
if instance.finished() {
|
||||||
|
info!("finished id={:?}", instance.id);
|
||||||
|
|
||||||
|
match instance_json_file_write(&instance) {
|
||||||
|
Ok(dest) => info!("wrote dest={:?}", dest),
|
||||||
|
Err(e) => error!("json write error={:?}", e),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instance_json_file_write(g: &Instance) -> Result<String, Error> {
|
||||||
|
let dest = format!("/var/lib/mnml/data/instances/{}.mnml.instance.json", g.id);
|
||||||
|
serde_json::to_writer(File::create(&dest)?, g)?;
|
||||||
|
Ok(dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn instance_get(tx: &mut Transaction, instance_id: Uuid) -> Result<Instance, Error> {
|
||||||
|
let query = "
|
||||||
|
SELECT *
|
||||||
|
FROM instances
|
||||||
|
WHERE id = $1
|
||||||
|
FOR UPDATE;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = tx
|
||||||
|
.query(query, &[&instance_id])?;
|
||||||
|
|
||||||
|
let returned = match result.iter().next() {
|
||||||
|
Some(row) => row,
|
||||||
|
None => return Err(err_msg("instance not found")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let instance_bytes: Vec<u8> = returned.get("data");
|
||||||
|
let instance = from_slice::<Instance>(&instance_bytes)?;
|
||||||
|
|
||||||
|
return Ok(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn instance_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> {
|
||||||
|
let query = "
|
||||||
|
DELETE
|
||||||
|
FROM instances
|
||||||
|
WHERE id = $1;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = tx
|
||||||
|
.execute(query, &[&id])?;
|
||||||
|
|
||||||
|
if result != 1 {
|
||||||
|
return Err(format_err!("unable to delete instance {:?}", id));
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("instance deleted {:?}", id);
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _instance_list(tx: &mut Transaction) -> Result<Vec<Instance>, Error> {
|
||||||
|
let query = "
|
||||||
|
SELECT data, id
|
||||||
|
FROM instances
|
||||||
|
AND finished = false;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = tx
|
||||||
|
.query(query, &[])?;
|
||||||
|
|
||||||
|
let mut list = vec![];
|
||||||
|
|
||||||
|
for row in result.into_iter() {
|
||||||
|
let bytes: Vec<u8> = row.get(0);
|
||||||
|
let id = row.get(1);
|
||||||
|
|
||||||
|
match from_slice::<Instance>(&bytes) {
|
||||||
|
Ok(i) => list.push(i),
|
||||||
|
Err(_e) => {
|
||||||
|
instance_delete(tx, id)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn instances_need_upkeep(tx: &mut Transaction) -> Result<Vec<Instance>, Error> {
|
||||||
|
let query = "
|
||||||
|
SELECT data, id
|
||||||
|
FROM instances
|
||||||
|
WHERE finished = false
|
||||||
|
AND upkeep < now()
|
||||||
|
FOR UPDATE;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = tx
|
||||||
|
.query(query, &[])?;
|
||||||
|
|
||||||
|
let mut list = vec![];
|
||||||
|
|
||||||
|
for row in result.into_iter() {
|
||||||
|
let bytes: Vec<u8> = row.get(0);
|
||||||
|
let id = row.get(1);
|
||||||
|
|
||||||
|
match from_slice::<Instance>(&bytes) {
|
||||||
|
Ok(i) => list.push(i),
|
||||||
|
Err(_e) => {
|
||||||
|
instance_delete(tx, id)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
// timed out instances with no time control
|
||||||
|
pub fn instances_idle(tx: &mut Transaction) -> Result<Vec<Instance>, Error> {
|
||||||
|
let query = "
|
||||||
|
SELECT data, id
|
||||||
|
FROM instances
|
||||||
|
WHERE finished = false
|
||||||
|
AND updated_at < now() - interval '1 hour'
|
||||||
|
FOR UPDATE;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = tx
|
||||||
|
.query(query, &[])?;
|
||||||
|
|
||||||
|
let mut list = vec![];
|
||||||
|
|
||||||
|
for row in result.into_iter() {
|
||||||
|
let bytes: Vec<u8> = row.get(0);
|
||||||
|
let id = row.get(1);
|
||||||
|
|
||||||
|
match from_slice::<Instance>(&bytes) {
|
||||||
|
Ok(i) => list.push(i),
|
||||||
|
Err(_e) => {
|
||||||
|
instance_delete(tx, id)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn instance_practice(tx: &mut Transaction, account: &Account) -> Result<Instance, Error> {
|
||||||
|
let bot = bot_player();
|
||||||
|
let bot_id = bot.id;
|
||||||
|
|
||||||
|
// generate bot imgs for the client to see
|
||||||
|
for c in bot.constructs.iter() {
|
||||||
|
img::shapes_write(c.img)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut instance = Instance::new()
|
||||||
|
.set_time_control(TimeControl::Practice)
|
||||||
|
.set_name(bot.name.clone())?;
|
||||||
|
|
||||||
|
let player = account.to_player(tx)?;
|
||||||
|
|
||||||
|
instance.add_player(player.clone())?;
|
||||||
|
instance.add_player(bot)?;
|
||||||
|
|
||||||
|
instance.player_ready(bot_id)?;
|
||||||
|
// skip faceoff
|
||||||
|
instance.player_ready(player.id)?;
|
||||||
|
|
||||||
|
instance = instance_create(tx, instance)?;
|
||||||
|
player_create(tx, player, instance.id, account)?;
|
||||||
|
|
||||||
|
Ok(instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pvp(tx: &mut Transaction, a: &Account, b: &Account) -> Result<Instance, Error> {
|
||||||
|
let mut instance = Instance::new()
|
||||||
|
// TODO generate nice game names
|
||||||
|
.set_name("PVP".to_string())?;
|
||||||
|
|
||||||
|
instance = instance_create(tx, instance)?;
|
||||||
|
|
||||||
|
for account in [a, b].iter() {
|
||||||
|
let acc_p = account.to_player(tx)?;
|
||||||
|
let player = player_create(tx, acc_p, instance.id, account)?;
|
||||||
|
instance.add_player(player)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance_update(tx, instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn player_create(tx: &mut Transaction, player: Player, instance: Uuid, account: &Account) -> Result<Player, Error> {
|
||||||
|
let query = "
|
||||||
|
INSERT INTO players (id, instance, account)
|
||||||
|
VALUES ($1, $2, $3)
|
||||||
|
RETURNING id, account;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = tx
|
||||||
|
.query(query, &[&Uuid::new_v4(), &instance, &account.id])?;
|
||||||
|
|
||||||
|
let _returned = result.iter().next().expect("no row written");
|
||||||
|
|
||||||
|
info!("wrote player {:} joined instance: {:}", account.name, instance);
|
||||||
|
|
||||||
|
return Ok(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn instance_abandon(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result<RpcMessage, Error> {
|
||||||
|
let mut instance = instance_get(tx, instance_id)?;
|
||||||
|
|
||||||
|
if let Some(game_id) = instance.current_game_id() {
|
||||||
|
let mut game = game_get(tx, game_id)?;
|
||||||
|
game.player_by_id(account.id)?.forfeit();
|
||||||
|
game = game.start(); // actually finishes it...
|
||||||
|
game_update(tx, &game)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.account_player(account.id)?.set_lose();
|
||||||
|
instance.account_opponent(account.id)?.set_win();
|
||||||
|
instance.next_round();
|
||||||
|
|
||||||
|
Ok(RpcMessage::InstanceState(instance_update(tx, instance)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn instance_ready(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result<RpcMessage, Error> {
|
||||||
|
let mut instance = instance_get(tx, instance_id)?;
|
||||||
|
let player_id = instance.account_player(account.id)?.id;
|
||||||
|
|
||||||
|
if let Some(game) = instance.player_ready(player_id)? {
|
||||||
|
game_write(tx, &game)?;
|
||||||
|
|
||||||
|
// ensures cleanup for warden etc is done
|
||||||
|
game_update(tx, &game)?;
|
||||||
|
|
||||||
|
instance_update(tx, instance)?;
|
||||||
|
return Ok(RpcMessage::GameState(game));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RpcMessage::InstanceState(instance_update(tx, instance)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn instance_state(tx: &mut Transaction, instance_id: Uuid) -> Result<RpcMessage, Error> {
|
||||||
|
let instance = instance_get(tx, instance_id)?;
|
||||||
|
|
||||||
|
if let Some(game_id) = instance.current_game_id() {
|
||||||
|
let game = game_get(tx, game_id)?;
|
||||||
|
|
||||||
|
// return the game until it's finished
|
||||||
|
if game.phase != Phase::Finished {
|
||||||
|
return Ok(RpcMessage::GameState(game))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RpcMessage::InstanceState(instance))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn instance_game_finished(tx: &mut Transaction, game: &Game, instance_id: Uuid) -> Result<(), Error> {
|
||||||
|
let mut instance = instance_get(tx, instance_id)?;
|
||||||
|
instance.game_finished(game)?;
|
||||||
|
// info!("{:?}", instance_get(tx, instance_id)?);
|
||||||
|
|
||||||
|
instance_update(tx, instance)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bot_instance() -> Instance {
|
||||||
|
let mut instance = Instance::new();
|
||||||
|
|
||||||
|
let bot_player = bot_player();
|
||||||
|
let bot = bot_player.id;
|
||||||
|
instance.add_player(bot_player).unwrap();
|
||||||
|
|
||||||
|
let player_account = Uuid::new_v4();
|
||||||
|
let constructs = instance_mobs(player_account);
|
||||||
|
let player = Player::new(player_account, None, &"test".to_string(), constructs).set_bot(true);
|
||||||
|
|
||||||
|
instance.add_player(player).expect("could not add player");
|
||||||
|
instance.player_ready(player_account).unwrap();
|
||||||
|
instance.player_ready(bot).unwrap();
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn demo() -> Result<Vec<Player>, Error> {
|
||||||
|
let bot = bot_player();
|
||||||
|
|
||||||
|
// generate bot imgs for the client to see
|
||||||
|
for c in bot.constructs.iter() {
|
||||||
|
img::shapes_write(c.img)?;
|
||||||
|
};
|
||||||
|
|
||||||
|
let bot2 = bot_player();
|
||||||
|
|
||||||
|
// generate bot imgs for the client to see
|
||||||
|
for c in bot2.constructs.iter() {
|
||||||
|
img::shapes_write(c.img)?;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Ok(vec![bot, bot2])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn vbox_refill(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result<Instance, Error> {
|
||||||
|
let instance = instance_get(tx, instance_id)?
|
||||||
|
.vbox_refill(account.id)?;
|
||||||
|
return instance_update(tx, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vbox_buy(tx: &mut Transaction, account: &Account, instance_id: Uuid, group: ItemType, index: String, construct_id: Option<Uuid>) -> Result<Instance, Error> {
|
||||||
|
let instance = instance_get(tx, instance_id)?
|
||||||
|
.vbox_buy(account.id, group, index, construct_id)?;
|
||||||
|
return instance_update(tx, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vbox_combine(tx: &mut Transaction, account: &Account, instance_id: Uuid, stash_indices: Vec<String>, vbox_indices: VboxIndices) -> Result<Instance, Error> {
|
||||||
|
let instance = instance_get(tx, instance_id)?
|
||||||
|
.vbox_combine(account.id, stash_indices, vbox_indices)?;
|
||||||
|
return instance_update(tx, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vbox_refund(tx: &mut Transaction, account: &Account, instance_id: Uuid, index: String) -> Result<Instance, Error> {
|
||||||
|
let instance = instance_get(tx, instance_id)?
|
||||||
|
.vbox_refund(account.id, index)?;
|
||||||
|
return instance_update(tx, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vbox_apply(tx: &mut Transaction, account: &Account, instance_id: Uuid, construct_id: Uuid, index: String) -> Result<Instance, Error> {
|
||||||
|
let instance = instance_get(tx, instance_id)?
|
||||||
|
.vbox_apply(account.id, index, construct_id)?;
|
||||||
|
return instance_update(tx, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vbox_unequip(tx: &mut Transaction, account: &Account, instance_id: Uuid, construct_id: Uuid, target: Item, target_construct_id: Option<Uuid>) -> Result<Instance, Error> {
|
||||||
|
let instance = instance_get(tx, instance_id)?
|
||||||
|
.vbox_unequip(account.id, target, construct_id, target_construct_id)?;
|
||||||
|
return instance_update(tx, instance);
|
||||||
|
}
|
||||||
@ -1,490 +0,0 @@
|
|||||||
use std::collections::{HashMap};
|
|
||||||
|
|
||||||
use uuid::Uuid;
|
|
||||||
use rand::prelude::*;
|
|
||||||
|
|
||||||
use postgres::transaction::Transaction;
|
|
||||||
|
|
||||||
use failure::Error;
|
|
||||||
use failure::err_msg;
|
|
||||||
|
|
||||||
use account;
|
|
||||||
use account::Account;
|
|
||||||
use construct::{Construct, Colours};
|
|
||||||
use vbox::{Vbox, ItemType, VboxIndices};
|
|
||||||
use item::{Item, ItemEffect};
|
|
||||||
use effect::{Effect};
|
|
||||||
|
|
||||||
const DISCARD_COST: usize = 2;
|
|
||||||
|
|
||||||
#[derive(Debug,Copy,Clone,Serialize,Deserialize,Eq,PartialEq)]
|
|
||||||
pub enum Score {
|
|
||||||
Zero,
|
|
||||||
One,
|
|
||||||
Two,
|
|
||||||
Three,
|
|
||||||
Adv,
|
|
||||||
|
|
||||||
Win,
|
|
||||||
Lose,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Score {
|
|
||||||
pub fn add_win(self, _opp: &Score) -> Score {
|
|
||||||
match self {
|
|
||||||
Score::Zero => Score::One,
|
|
||||||
Score::One => Score::Two,
|
|
||||||
Score::Two => Score::Win,
|
|
||||||
// Tennis scoring
|
|
||||||
// Score::Three => match opp {
|
|
||||||
// Score::Adv => Score::Three,
|
|
||||||
// Score::Three => Score::Adv,
|
|
||||||
// _ => Score::Win,
|
|
||||||
// }
|
|
||||||
// Score::Adv => Score::Win,
|
|
||||||
|
|
||||||
_ => panic!("faulty score increment {:?}", self),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_loss(self) -> Score {
|
|
||||||
match self {
|
|
||||||
// Score::Adv => Score::Three,
|
|
||||||
_ => self,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug,Clone,Serialize,Deserialize)]
|
|
||||||
pub struct Player {
|
|
||||||
pub id: Uuid,
|
|
||||||
pub img: Option<Uuid>,
|
|
||||||
pub name: String,
|
|
||||||
pub vbox: Vbox,
|
|
||||||
pub constructs: Vec<Construct>,
|
|
||||||
pub bot: bool,
|
|
||||||
pub ready: bool,
|
|
||||||
pub draw_offered: bool,
|
|
||||||
pub score: Score,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Player {
|
|
||||||
pub fn from_account(tx: &mut Transaction, account: &Account) -> Result<Player, Error> {
|
|
||||||
let constructs = account::team(tx, account)?;
|
|
||||||
|
|
||||||
let img = match account.subscribed {
|
|
||||||
true => Some(account.img),
|
|
||||||
false => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Player {
|
|
||||||
id: account.id,
|
|
||||||
img,
|
|
||||||
name: account.name.clone(),
|
|
||||||
vbox: Vbox::new(),
|
|
||||||
constructs,
|
|
||||||
bot: false,
|
|
||||||
ready: false,
|
|
||||||
draw_offered: false,
|
|
||||||
score: Score::Zero,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(account: Uuid, name: &String, constructs: Vec<Construct>) -> Player {
|
|
||||||
Player {
|
|
||||||
id: account,
|
|
||||||
img: Some(account),
|
|
||||||
name: name.clone(),
|
|
||||||
vbox: Vbox::new(),
|
|
||||||
constructs,
|
|
||||||
bot: false,
|
|
||||||
ready: false,
|
|
||||||
draw_offered: false,
|
|
||||||
score: Score::Zero,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn redact(mut self, account: Uuid) -> Player {
|
|
||||||
// all g
|
|
||||||
if account == self.id {
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove vbox
|
|
||||||
self.vbox = Vbox::new();
|
|
||||||
|
|
||||||
// hide skills
|
|
||||||
for construct in self.constructs.iter_mut() {
|
|
||||||
construct.skills = vec![];
|
|
||||||
construct.specs = vec![];
|
|
||||||
}
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_bot(mut self, bot: bool) -> Player {
|
|
||||||
self.bot = bot;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_ready(&mut self, ready: bool) -> &mut Player {
|
|
||||||
self.ready = ready;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn forfeit(&mut self) -> &mut Player {
|
|
||||||
for construct in self.constructs.iter_mut() {
|
|
||||||
construct.force_ko();
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_win(&mut self) -> &mut Player {
|
|
||||||
self.score = Score::Win;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_lose(&mut self) -> &mut Player {
|
|
||||||
self.score = Score::Lose;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn construct_get(&mut self, id: Uuid) -> Result<&mut Construct, Error> {
|
|
||||||
self.constructs.iter_mut().find(|c| c.id == id).ok_or(err_msg("construct not found"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn autobuy(&mut self) -> &mut Player {
|
|
||||||
let mut rng = thread_rng();
|
|
||||||
|
|
||||||
// skill buying phase
|
|
||||||
while self.constructs.iter().any(|c| c.skills.len() < 3) {
|
|
||||||
// find the construct with the smallest number of skills
|
|
||||||
let construct_id = match self.constructs.iter().min_by_key(|c| c.skills.len()) {
|
|
||||||
None => panic!("no constructs in autobuy"),
|
|
||||||
Some(c) => c.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
let i = self.vbox.stash.iter()
|
|
||||||
.find(|(_i, v)| v.into_skill().is_some())
|
|
||||||
.map(|(i, _v)| i.clone());
|
|
||||||
|
|
||||||
// got a skill in stash
|
|
||||||
if let Some(i) = i {
|
|
||||||
// AAAAAAAAAAAAAAAAAAAA
|
|
||||||
// there's a bad bug here where if this apply fails
|
|
||||||
// the item in question will be silently dropped
|
|
||||||
let item = self.vbox.stash.remove(&i).unwrap();
|
|
||||||
self.vbox_apply(item, construct_id).ok();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// need to buy one
|
|
||||||
else {
|
|
||||||
|
|
||||||
// do we have any colours in store?
|
|
||||||
let colours = self.vbox.store[&ItemType::Colours].keys()
|
|
||||||
.cloned()
|
|
||||||
.take(2)
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
|
|
||||||
// how about a base skill?
|
|
||||||
let base = match self.vbox.store[&ItemType::Skills].iter().next() {
|
|
||||||
Some(b) => Some(b.0.clone()),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// if no: try to refill and start again
|
|
||||||
match colours.len() != 2 || base.is_none() {
|
|
||||||
true => {
|
|
||||||
match self.vbox_refill() {
|
|
||||||
Ok(_) => continue,
|
|
||||||
Err(_) => break, // give up
|
|
||||||
};
|
|
||||||
}
|
|
||||||
false => {
|
|
||||||
let mut vbox_items = HashMap::new();
|
|
||||||
vbox_items.insert(ItemType::Colours, colours);
|
|
||||||
vbox_items.insert(ItemType::Skills, vec![base.unwrap()]);
|
|
||||||
|
|
||||||
match self.vbox_combine(vec![], Some(vbox_items)) {
|
|
||||||
Ok(_) => continue,
|
|
||||||
Err(_) => break, // give up
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// spec buying phase
|
|
||||||
while self.constructs.iter().any(|c| c.specs.len() < 3) {
|
|
||||||
// find the construct with the smallest number of skills
|
|
||||||
let construct_id = match self.constructs.iter().min_by_key(|c| c.specs.len()) {
|
|
||||||
None => panic!("no constructs in autobuy"),
|
|
||||||
Some(c) => c.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
let i = self.vbox.stash.iter()
|
|
||||||
.find(|(_i, v)| v.into_spec().is_some())
|
|
||||||
.map(|(i, _v)| i.clone());
|
|
||||||
|
|
||||||
// got a skill in stash
|
|
||||||
if let Some(i) = i {
|
|
||||||
// AAAAAAAAAAAAAAAAAAAA
|
|
||||||
// there's a bad bug here where if this apply fails
|
|
||||||
// the item in question will be silently dropped
|
|
||||||
let item = self.vbox.stash.remove(&i).unwrap();
|
|
||||||
self.vbox_apply(item, construct_id).unwrap();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// need to buy one
|
|
||||||
else {
|
|
||||||
// do we have any colours in store?
|
|
||||||
let colours = self.vbox.store[&ItemType::Colours].keys()
|
|
||||||
.cloned()
|
|
||||||
.take(2)
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
|
|
||||||
// how about a base spec?
|
|
||||||
let base = match self.vbox.store[&ItemType::Specs].iter().next() {
|
|
||||||
Some(b) => Some(b.0.clone()),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// if no: try to refill and start again
|
|
||||||
match colours.len() != 2 || base.is_none() {
|
|
||||||
true => match self.vbox_refill() {
|
|
||||||
Ok(_) => continue,
|
|
||||||
Err(_) => break, // give up
|
|
||||||
},
|
|
||||||
false => {
|
|
||||||
let mut vbox_items = HashMap::new();
|
|
||||||
vbox_items.insert(ItemType::Colours, colours);
|
|
||||||
vbox_items.insert(ItemType::Specs, vec![base.unwrap()]);
|
|
||||||
|
|
||||||
match self.vbox_combine(vec![], Some(vbox_items)) {
|
|
||||||
Ok(_) => continue,
|
|
||||||
Err(_) => break, // give up
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// upgrading phase
|
|
||||||
// NYI
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_refill(&mut self) -> Result<&mut Player, Error> {
|
|
||||||
self.vbox.balance_sub(DISCARD_COST)?;
|
|
||||||
self.vbox.fill();
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bot_vbox_accept(&mut self, group: ItemType) -> Result<&mut Player, Error> {
|
|
||||||
let item = self.vbox.bot_buy(group)?;
|
|
||||||
self.vbox.stash_add(item, None)?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_buy(&mut self, group: ItemType, index: String, construct_id: Option<Uuid>) -> Result<&mut Player, Error> {
|
|
||||||
let item = self.vbox.buy(group, &index)?;
|
|
||||||
|
|
||||||
match construct_id {
|
|
||||||
Some(id) => { self.vbox_apply(item, id)?; },
|
|
||||||
None => { self.vbox.stash_add(item, None)?; },
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_combine(&mut self, inv_indices: Vec<String>, vbox_indices: VboxIndices) -> Result<&mut Player, Error> {
|
|
||||||
self.vbox.combine(inv_indices, vbox_indices)?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_refund(&mut self, index: String) -> Result<&mut Player, Error> {
|
|
||||||
self.vbox.refund(index)?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_equip(&mut self, index: String, construct_id: Uuid) -> Result<&mut Player, Error> {
|
|
||||||
let item = self.vbox.stash.remove(&index)
|
|
||||||
.ok_or(format_err!("no item at index {:?} {:}", self, &index))?;
|
|
||||||
|
|
||||||
self.vbox_apply(item, construct_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_apply(&mut self, item: Item, construct_id: Uuid) -> Result<&mut Player, Error> {
|
|
||||||
match item.effect() {
|
|
||||||
Some(ItemEffect::Skill) => {
|
|
||||||
let skill = item.into_skill().ok_or(format_err!("item {:?} has no associated skill", item))?;
|
|
||||||
let construct = self.construct_get(construct_id)?;
|
|
||||||
// done here because i teach them a tonne of skills for tests
|
|
||||||
let max_skills = 3;
|
|
||||||
if construct.skills.len() >= max_skills {
|
|
||||||
return Err(format_err!("construct at max skills ({:?})", max_skills));
|
|
||||||
}
|
|
||||||
|
|
||||||
if construct.knows(skill) {
|
|
||||||
return Err(format_err!("construct already knows skill ({:?})" , skill));
|
|
||||||
}
|
|
||||||
|
|
||||||
construct.learn_mut(skill);
|
|
||||||
},
|
|
||||||
Some(ItemEffect::Spec) => {
|
|
||||||
let spec = item.into_spec().ok_or(format_err!("item {:?} has no associated spec", item))?;
|
|
||||||
let construct = self.construct_get(construct_id)?;
|
|
||||||
construct.spec_add(spec)?;
|
|
||||||
|
|
||||||
},
|
|
||||||
None => return Err(err_msg("item has no effect on constructs")),
|
|
||||||
}
|
|
||||||
|
|
||||||
// now the item has been applied
|
|
||||||
// recalculate the stats of the whole player
|
|
||||||
let player_colours = self.constructs.iter().fold(Colours::new(), |tc, c| {
|
|
||||||
Colours {
|
|
||||||
red: tc.red + c.colours.red,
|
|
||||||
green: tc.green + c.colours.green,
|
|
||||||
blue: tc.blue + c.colours.blue
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for construct in self.constructs.iter_mut() {
|
|
||||||
construct.apply_modifiers(&player_colours);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_unequip(&mut self, target: Item, construct_id: Uuid, target_construct_id: Option<Uuid>) -> Result<&mut Player, Error> {
|
|
||||||
if self.vbox.stash.len() >= 9 && !target_construct_id.is_some() {
|
|
||||||
return Err(err_msg("too many items stash"));
|
|
||||||
}
|
|
||||||
|
|
||||||
match target.effect() {
|
|
||||||
Some(ItemEffect::Skill) => {
|
|
||||||
let skill = target.into_skill().ok_or(format_err!("item {:?} has no associated skill", target))?;
|
|
||||||
let construct = self.construct_get(construct_id)?;
|
|
||||||
construct.forget(skill)?;
|
|
||||||
},
|
|
||||||
Some(ItemEffect::Spec) => {
|
|
||||||
let spec = target.into_spec().ok_or(format_err!("item {:?} has no associated spec", target))?;
|
|
||||||
let construct = self.construct_get(construct_id)?;
|
|
||||||
construct.spec_remove(spec)?;
|
|
||||||
},
|
|
||||||
None => return Err(err_msg("item has no effect on constructs")),
|
|
||||||
}
|
|
||||||
|
|
||||||
// now the item has been applied
|
|
||||||
// recalculate the stats of the whole player
|
|
||||||
let player_colours = self.constructs.iter().fold(Colours::new(), |tc, c| {
|
|
||||||
Colours {
|
|
||||||
red: tc.red + c.colours.red,
|
|
||||||
green: tc.green + c.colours.green,
|
|
||||||
blue: tc.blue + c.colours.blue
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for construct in self.constructs.iter_mut() {
|
|
||||||
construct.apply_modifiers(&player_colours);
|
|
||||||
}
|
|
||||||
|
|
||||||
match target_construct_id {
|
|
||||||
Some(cid) => { self.vbox_apply(target, cid)?; },
|
|
||||||
None => { self.vbox.stash_add(target, None)?; },
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GAME METHODS
|
|
||||||
pub fn skills_required(&self) -> usize {
|
|
||||||
let required = self.constructs.iter()
|
|
||||||
.filter(|c| !c.is_ko())
|
|
||||||
.filter(|c| c.available_skills().len() > 0)
|
|
||||||
.collect::<Vec<&Construct>>().len();
|
|
||||||
// info!("{:} requires {:} skills this turn", self.id, required);
|
|
||||||
return required;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn intercepting(&self) -> Option<&Construct> {
|
|
||||||
self.constructs.iter()
|
|
||||||
.find(|c| c.affected(Effect::Intercept))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn construct_by_id(&mut self, id: Uuid) -> Option<&mut Construct> {
|
|
||||||
self.constructs.iter_mut().find(|c| c.id == id)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn player_create(tx: &mut Transaction, player: Player, instance: Uuid, account: &Account) -> Result<Player, Error> {
|
|
||||||
let query = "
|
|
||||||
INSERT INTO players (id, instance, account)
|
|
||||||
VALUES ($1, $2, $3)
|
|
||||||
RETURNING id, account;
|
|
||||||
";
|
|
||||||
|
|
||||||
let result = tx
|
|
||||||
.query(query, &[&Uuid::new_v4(), &instance, &account.id])?;
|
|
||||||
|
|
||||||
let _returned = result.iter().next().expect("no row written");
|
|
||||||
|
|
||||||
info!("wrote player {:} joined instance: {:}", account.name, instance);
|
|
||||||
|
|
||||||
return Ok(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use mob::instance_mobs;
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn player_bot_vbox_test() {
|
|
||||||
let player_account = Uuid::new_v4();
|
|
||||||
let constructs = instance_mobs(player_account);
|
|
||||||
let mut player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true);
|
|
||||||
|
|
||||||
player.vbox.fill();
|
|
||||||
player.vbox.bits = 100;
|
|
||||||
player.autobuy();
|
|
||||||
|
|
||||||
assert!(player.constructs.iter().all(|c| c.skills.len() > 1));
|
|
||||||
assert!(player.constructs.iter().all(|c| c.specs.len() >= 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn player_score_test() {
|
|
||||||
let player_account = Uuid::new_v4();
|
|
||||||
let constructs = instance_mobs(player_account);
|
|
||||||
let mut player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true);
|
|
||||||
|
|
||||||
player.score = player.score.add_win(&Score::Zero);
|
|
||||||
player.score = player.score.add_win(&Score::Zero);
|
|
||||||
player.score = player.score.add_win(&Score::Zero);
|
|
||||||
assert_eq!(player.score, Score::Win); // 40 / 0
|
|
||||||
|
|
||||||
// Bo7 tennis scoring
|
|
||||||
/*assert_eq!(player.score, Score::Three); // 40 / 0
|
|
||||||
|
|
||||||
player.score = player.score.add_loss(); // adv -> deuce
|
|
||||||
assert_eq!(player.score, Score::Three);
|
|
||||||
|
|
||||||
player.score = player.score.add_loss(); // adv -> deuce
|
|
||||||
assert_eq!(player.score, Score::Three);
|
|
||||||
|
|
||||||
player.score = player.score.add_win(&Score::Adv); // opp adv -> stays deuce
|
|
||||||
assert_eq!(player.score, Score::Three);
|
|
||||||
|
|
||||||
player.score = player.score.add_win(&Score::Three);
|
|
||||||
assert_eq!(player.score, Score::Adv);
|
|
||||||
|
|
||||||
player.score = player.score.add_win(&Score::Three);
|
|
||||||
assert_eq!(player.score, Score::Win);*/
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,3 +1,6 @@
|
|||||||
|
use mnml_core::item::ItemInfoCtr;
|
||||||
|
use mnml_core::instance::ChatState;
|
||||||
|
use mnml_core::item::item_info;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::{Instant};
|
use std::time::{Instant};
|
||||||
use std::thread::{spawn};
|
use std::thread::{spawn};
|
||||||
@ -19,23 +22,47 @@ use crossbeam_channel::{unbounded, Sender as CbSender};
|
|||||||
use ws::{Builder, CloseCode, Message, Handler, Request, Response, Settings, Sender as WsSender};
|
use ws::{Builder, CloseCode, Message, Handler, Request, Response, Settings, Sender as WsSender};
|
||||||
use ws::deflate::DeflateHandler;
|
use ws::deflate::DeflateHandler;
|
||||||
|
|
||||||
|
use pg::{
|
||||||
|
demo,
|
||||||
|
game_concede,
|
||||||
|
game_offer_draw,
|
||||||
|
game_ready,
|
||||||
|
game_skill,
|
||||||
|
game_skill_clear,
|
||||||
|
game_state,
|
||||||
|
instance_abandon,
|
||||||
|
instance_practice,
|
||||||
|
instance_ready,
|
||||||
|
instance_state,
|
||||||
|
vbox_apply,
|
||||||
|
vbox_buy,
|
||||||
|
vbox_combine,
|
||||||
|
vbox_refill,
|
||||||
|
vbox_refund,
|
||||||
|
vbox_unequip,
|
||||||
|
};
|
||||||
|
|
||||||
use account::{Account};
|
use account::{Account};
|
||||||
use account;
|
use account;
|
||||||
use construct::{Construct};
|
|
||||||
use events::{Event};
|
use events::{Event};
|
||||||
use game::{Game, game_state, game_skill, game_skill_clear, game_ready, game_offer_draw, game_concede};
|
|
||||||
use instance::{Instance, ChatState, instance_state, instance_practice, instance_ready, instance_abandon, demo};
|
use mnml_core::construct::{Construct};
|
||||||
use item::{Item, ItemInfoCtr, item_info};
|
use mnml_core::game::{Game};
|
||||||
|
use mnml_core::player::Player;
|
||||||
|
|
||||||
|
use mnml_core::vbox::{ItemType};
|
||||||
|
use mnml_core::item::Item;
|
||||||
|
use mnml_core::skill::Skill;
|
||||||
|
// use mnml_core::skill::{dev_resolve, Resolutions};
|
||||||
|
use mnml_core::instance::{Instance};
|
||||||
|
|
||||||
use mtx;
|
use mtx;
|
||||||
use mail;
|
use mail;
|
||||||
|
|
||||||
use player::{Player};
|
|
||||||
use payments;
|
use payments;
|
||||||
use mail::Email;
|
use mail::Email;
|
||||||
use pg::{Db};
|
use pg::{Db};
|
||||||
use pg::{PgPool};
|
use pg::{PgPool};
|
||||||
use skill::{Skill, dev_resolve, Resolutions};
|
|
||||||
use vbox::{ItemType, vbox_buy, vbox_apply, vbox_refill, vbox_combine, vbox_refund, vbox_unequip};
|
|
||||||
use http::{AUTH_CLEAR, TOKEN_HEADER};
|
use http::{AUTH_CLEAR, TOKEN_HEADER};
|
||||||
|
|
||||||
#[derive(Debug,Clone,Serialize)]
|
#[derive(Debug,Clone,Serialize)]
|
||||||
@ -61,7 +88,7 @@ pub enum RpcMessage {
|
|||||||
|
|
||||||
Pong(()),
|
Pong(()),
|
||||||
|
|
||||||
DevResolutions(Resolutions),
|
// DevResolutions(Resolutions),
|
||||||
|
|
||||||
QueueRequested(()),
|
QueueRequested(()),
|
||||||
QueueJoined(()),
|
QueueJoined(()),
|
||||||
@ -144,8 +171,8 @@ impl Connection {
|
|||||||
match v {
|
match v {
|
||||||
RpcRequest::Ping {} => return Ok(RpcMessage::Pong(())),
|
RpcRequest::Ping {} => return Ok(RpcMessage::Pong(())),
|
||||||
RpcRequest::ItemInfo {} => return Ok(RpcMessage::ItemInfo(item_info())),
|
RpcRequest::ItemInfo {} => return Ok(RpcMessage::ItemInfo(item_info())),
|
||||||
RpcRequest::DevResolve {a, b, skill } =>
|
// RpcRequest::DevResolve {a, b, skill } =>
|
||||||
return Ok(RpcMessage::DevResolutions(dev_resolve(a, b, skill))),
|
// return Ok(RpcMessage::DevResolutions(dev_resolve(a, b, skill))),
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
2188
server/src/skill.rs
2188
server/src/skill.rs
File diff suppressed because it is too large
Load Diff
@ -1,734 +0,0 @@
|
|||||||
use construct::{Stat, Colours};
|
|
||||||
use util::{IntPct};
|
|
||||||
|
|
||||||
#[derive(Debug,Clone,Serialize,Deserialize)]
|
|
||||||
pub struct SpecBonus {
|
|
||||||
pub req: Colours,
|
|
||||||
pub bonus: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpecBonus {
|
|
||||||
pub fn get_bonus(&self, c: &Colours) -> u64 {
|
|
||||||
if c.red >= self.req.red && c.blue >= self.req.blue && c.green >= self.req.green {
|
|
||||||
return self.bonus;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug,Clone,Serialize,Deserialize)]
|
|
||||||
pub struct SpecValues {
|
|
||||||
pub base: u64,
|
|
||||||
pub bonuses: Vec<SpecBonus>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpecValues {
|
|
||||||
pub fn max_value (&self, c: &Colours) -> u64 {
|
|
||||||
self.bonuses.iter().fold(self.base, |acc, s| acc + s.get_bonus(c))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn base (self) -> u64 {
|
|
||||||
self.base
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug,Copy,Clone,Serialize,Deserialize,PartialEq,PartialOrd,Ord,Eq)]
|
|
||||||
pub enum Spec {
|
|
||||||
Speed,
|
|
||||||
SpeedRR,
|
|
||||||
SpeedBB,
|
|
||||||
SpeedGG,
|
|
||||||
SpeedRG,
|
|
||||||
SpeedGB,
|
|
||||||
SpeedRB,
|
|
||||||
|
|
||||||
SpeedRRPlus,
|
|
||||||
SpeedBBPlus,
|
|
||||||
SpeedGGPlus,
|
|
||||||
SpeedRGPlus,
|
|
||||||
SpeedGBPlus,
|
|
||||||
SpeedRBPlus,
|
|
||||||
|
|
||||||
SpeedRRPlusPlus,
|
|
||||||
SpeedBBPlusPlus,
|
|
||||||
SpeedGGPlusPlus,
|
|
||||||
SpeedRGPlusPlus,
|
|
||||||
SpeedGBPlusPlus,
|
|
||||||
SpeedRBPlusPlus,
|
|
||||||
|
|
||||||
Life,
|
|
||||||
LifeGG,
|
|
||||||
LifeRR,
|
|
||||||
LifeBB,
|
|
||||||
LifeRG,
|
|
||||||
LifeGB,
|
|
||||||
LifeRB,
|
|
||||||
LifeGGPlus,
|
|
||||||
LifeRRPlus,
|
|
||||||
LifeBBPlus,
|
|
||||||
LifeRGPlus,
|
|
||||||
LifeGBPlus,
|
|
||||||
LifeRBPlus,
|
|
||||||
LifeGGPlusPlus,
|
|
||||||
LifeRRPlusPlus,
|
|
||||||
LifeBBPlusPlus,
|
|
||||||
LifeRGPlusPlus,
|
|
||||||
LifeGBPlusPlus,
|
|
||||||
LifeRBPlusPlus,
|
|
||||||
|
|
||||||
Power,
|
|
||||||
PowerRR,
|
|
||||||
PowerGG,
|
|
||||||
PowerBB,
|
|
||||||
PowerRG,
|
|
||||||
PowerGB,
|
|
||||||
PowerRB,
|
|
||||||
PowerRRPlus,
|
|
||||||
PowerGGPlus,
|
|
||||||
PowerBBPlus,
|
|
||||||
PowerRGPlus,
|
|
||||||
PowerGBPlus,
|
|
||||||
PowerRBPlus,
|
|
||||||
PowerRRPlusPlus,
|
|
||||||
PowerGGPlusPlus,
|
|
||||||
PowerBBPlusPlus,
|
|
||||||
PowerRGPlusPlus,
|
|
||||||
PowerGBPlusPlus,
|
|
||||||
PowerRBPlusPlus,
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Spec {
|
|
||||||
pub fn affects(&self) -> Vec<Stat> {
|
|
||||||
match *self {
|
|
||||||
Spec::Power => vec![Stat::BluePower, Stat::RedPower, Stat::GreenPower],
|
|
||||||
Spec::PowerRR => vec![Stat::RedPower],
|
|
||||||
Spec::PowerGG => vec![Stat::GreenPower],
|
|
||||||
Spec::PowerBB => vec![Stat::BluePower],
|
|
||||||
Spec::PowerRG => vec![Stat::GreenPower, Stat::RedPower],
|
|
||||||
Spec::PowerGB => vec![Stat::GreenPower, Stat::BluePower],
|
|
||||||
Spec::PowerRB => vec![Stat::RedPower, Stat::BluePower],
|
|
||||||
Spec::PowerRRPlus => vec![Stat::RedPower],
|
|
||||||
Spec::PowerGGPlus => vec![Stat::GreenPower],
|
|
||||||
Spec::PowerBBPlus => vec![Stat::BluePower],
|
|
||||||
Spec::PowerRGPlus => vec![Stat::GreenPower, Stat::RedPower],
|
|
||||||
Spec::PowerGBPlus => vec![Stat::GreenPower, Stat::BluePower],
|
|
||||||
Spec::PowerRBPlus => vec![Stat::RedPower, Stat::BluePower],
|
|
||||||
Spec::PowerRRPlusPlus => vec![Stat::RedPower],
|
|
||||||
Spec::PowerGGPlusPlus => vec![Stat::GreenPower],
|
|
||||||
Spec::PowerBBPlusPlus => vec![Stat::BluePower],
|
|
||||||
Spec::PowerRGPlusPlus => vec![Stat::GreenPower, Stat::RedPower],
|
|
||||||
Spec::PowerGBPlusPlus => vec![Stat::GreenPower, Stat::BluePower],
|
|
||||||
Spec::PowerRBPlusPlus => vec![Stat::RedPower, Stat::BluePower],
|
|
||||||
|
|
||||||
Spec::Speed => vec![Stat::Speed],
|
|
||||||
Spec::SpeedRR => vec![Stat::Speed],
|
|
||||||
Spec::SpeedBB => vec![Stat::Speed],
|
|
||||||
Spec::SpeedGG => vec![Stat::Speed],
|
|
||||||
Spec::SpeedRG => vec![Stat::Speed],
|
|
||||||
Spec::SpeedGB => vec![Stat::Speed],
|
|
||||||
Spec::SpeedRB => vec![Stat::Speed],
|
|
||||||
Spec::SpeedRRPlus => vec![Stat::Speed],
|
|
||||||
Spec::SpeedBBPlus => vec![Stat::Speed],
|
|
||||||
Spec::SpeedGGPlus => vec![Stat::Speed],
|
|
||||||
Spec::SpeedRGPlus => vec![Stat::Speed],
|
|
||||||
Spec::SpeedGBPlus => vec![Stat::Speed],
|
|
||||||
Spec::SpeedRBPlus => vec![Stat::Speed],
|
|
||||||
Spec::SpeedRRPlusPlus => vec![Stat::Speed],
|
|
||||||
Spec::SpeedBBPlusPlus => vec![Stat::Speed],
|
|
||||||
Spec::SpeedGGPlusPlus => vec![Stat::Speed],
|
|
||||||
Spec::SpeedRGPlusPlus => vec![Stat::Speed],
|
|
||||||
Spec::SpeedGBPlusPlus => vec![Stat::Speed],
|
|
||||||
Spec::SpeedRBPlusPlus => vec![Stat::Speed],
|
|
||||||
|
|
||||||
Spec::Life => vec![Stat::GreenLife],
|
|
||||||
Spec::LifeRR => vec![Stat::RedLife],
|
|
||||||
Spec::LifeBB => vec![Stat::BlueLife],
|
|
||||||
Spec::LifeGG => vec![Stat::GreenLife],
|
|
||||||
Spec::LifeRG => vec![Stat::GreenLife, Stat::RedLife],
|
|
||||||
Spec::LifeGB => vec![Stat::GreenLife, Stat::BlueLife],
|
|
||||||
Spec::LifeRB => vec![Stat::BlueLife, Stat::RedLife],
|
|
||||||
Spec::LifeRRPlus => vec![Stat::RedLife],
|
|
||||||
Spec::LifeBBPlus => vec![Stat::BlueLife],
|
|
||||||
Spec::LifeGGPlus => vec![Stat::GreenLife],
|
|
||||||
Spec::LifeRGPlus => vec![Stat::GreenLife, Stat::RedLife],
|
|
||||||
Spec::LifeGBPlus => vec![Stat::GreenLife, Stat::BlueLife],
|
|
||||||
Spec::LifeRBPlus => vec![Stat::BlueLife, Stat::RedLife],
|
|
||||||
Spec::LifeRRPlusPlus => vec![Stat::RedLife],
|
|
||||||
Spec::LifeBBPlusPlus => vec![Stat::BlueLife],
|
|
||||||
Spec::LifeGGPlusPlus => vec![Stat::GreenLife],
|
|
||||||
Spec::LifeRGPlusPlus => vec![Stat::GreenLife, Stat::RedLife],
|
|
||||||
Spec::LifeGBPlusPlus => vec![Stat::GreenLife, Stat::BlueLife],
|
|
||||||
Spec::LifeRBPlusPlus => vec![Stat::BlueLife, Stat::RedLife],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn values(&self) -> SpecValues {
|
|
||||||
match *self {
|
|
||||||
Spec::Power => SpecValues {
|
|
||||||
base: 10,
|
|
||||||
bonuses: vec![]
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::PowerRR=> SpecValues {
|
|
||||||
base: 10,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 5 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 9 },
|
|
||||||
SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 13 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::PowerGG=> SpecValues {
|
|
||||||
base: 10,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 5 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 9 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 13 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::PowerBB=> SpecValues {
|
|
||||||
base: 10,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 5 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 9 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 13 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::PowerRG=> SpecValues {
|
|
||||||
base: 10,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 3 },
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 6 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 9 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::PowerGB=> SpecValues {
|
|
||||||
base: 10,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 3 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 6 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 9 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::PowerRB=> SpecValues {
|
|
||||||
base: 10,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 3 },
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 6 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 9 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::PowerRRPlus => SpecValues {
|
|
||||||
base: 15,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 6 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 12 },
|
|
||||||
SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 18 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::PowerGGPlus => SpecValues {
|
|
||||||
base: 15,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 6 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 12 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 18 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::PowerBBPlus => SpecValues {
|
|
||||||
base: 15,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 6 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 12 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 18 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::PowerRGPlus => SpecValues {
|
|
||||||
base: 15,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 4 },
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 8 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 12 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::PowerGBPlus => SpecValues {
|
|
||||||
base: 15,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 4 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 8 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 12 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::PowerRBPlus => SpecValues {
|
|
||||||
base: 15,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 4 },
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 8 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 12 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
Spec::PowerRRPlusPlus => SpecValues {
|
|
||||||
base: 25,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 8 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 16 },
|
|
||||||
SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 24 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::PowerGGPlusPlus => SpecValues {
|
|
||||||
base: 25,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 8 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 16 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 24 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::PowerBBPlusPlus => SpecValues {
|
|
||||||
base: 25,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 8 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 16 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 24 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::PowerRGPlusPlus => SpecValues {
|
|
||||||
base: 25,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 5 },
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 10 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 15 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::PowerGBPlusPlus => SpecValues {
|
|
||||||
base: 25,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 5 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 10 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 15 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::PowerRBPlusPlus => SpecValues {
|
|
||||||
base: 25,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 5 },
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 10 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 15 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::Speed => SpecValues {
|
|
||||||
base: 40,
|
|
||||||
bonuses: vec![]
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::SpeedRR=> SpecValues {
|
|
||||||
base: 80,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 80 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 80 },
|
|
||||||
SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 80 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::SpeedGG=> SpecValues {
|
|
||||||
base: 80,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 80 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 80 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 80 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::SpeedBB=> SpecValues {
|
|
||||||
base: 80,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 80 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 80 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 80 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::SpeedRG=> SpecValues {
|
|
||||||
base: 60,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 60 },
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 60 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 60 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::SpeedGB=> SpecValues {
|
|
||||||
base: 60,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 60 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 60 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 60 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::SpeedRB=> SpecValues {
|
|
||||||
base: 60,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 60 },
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 60 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 60 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::SpeedRRPlus => SpecValues {
|
|
||||||
base: 120,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 120 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 120 },
|
|
||||||
SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 120 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::SpeedGGPlus => SpecValues {
|
|
||||||
base: 120,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 120 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 120 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 120 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::SpeedBBPlus => SpecValues {
|
|
||||||
base: 120,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 120 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 120 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 120 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::SpeedRGPlus => SpecValues {
|
|
||||||
base: 80,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 80 },
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 80 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 80 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::SpeedGBPlus => SpecValues {
|
|
||||||
base: 80,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 80 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 80 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 80 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::SpeedRBPlus => SpecValues {
|
|
||||||
base: 80,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 80 },
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 80 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 80 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::SpeedRRPlusPlus => SpecValues {
|
|
||||||
base: 160,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 160 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 160 },
|
|
||||||
SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 160 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::SpeedGGPlusPlus => SpecValues {
|
|
||||||
base: 160,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 160 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 160 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 160 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::SpeedBBPlusPlus => SpecValues {
|
|
||||||
base: 160,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 160 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 160 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 160 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::SpeedRGPlusPlus => SpecValues {
|
|
||||||
base: 120,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 120 },
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 120 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 120 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::SpeedGBPlusPlus => SpecValues {
|
|
||||||
base: 120,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 120 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 120 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 120 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::SpeedRBPlusPlus => SpecValues {
|
|
||||||
base: 120,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 120 },
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 120 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 120 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::Life => SpecValues {
|
|
||||||
base: 125,
|
|
||||||
bonuses: vec![]},
|
|
||||||
|
|
||||||
Spec::LifeRR=> SpecValues {
|
|
||||||
base: 275,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 75 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 125 },
|
|
||||||
SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 175 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::LifeGG=> SpecValues {
|
|
||||||
base: 225,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 50 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 75 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 125 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::LifeBB=> SpecValues {
|
|
||||||
base: 275,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 75 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 125 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 175 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::LifeRG=> SpecValues {
|
|
||||||
base: 125,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 50 },
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 75 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 125 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::LifeGB=> SpecValues {
|
|
||||||
base: 125,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 50 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 75 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 125 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::LifeRB=> SpecValues {
|
|
||||||
base: 175,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 50 },
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 75 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 125 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::LifeRRPlus => SpecValues {
|
|
||||||
base: 500,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 125 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 225 },
|
|
||||||
SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 300 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::LifeGGPlus => SpecValues {
|
|
||||||
base: 400,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 90 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 130 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 225 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::LifeBBPlus => SpecValues {
|
|
||||||
base: 500,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 125 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 225 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 300 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::LifeRGPlus => SpecValues {
|
|
||||||
base: 225,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 100 },
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 150 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 225 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::LifeGBPlus => SpecValues {
|
|
||||||
base: 225,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 100 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 150 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 225 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::LifeRBPlus => SpecValues {
|
|
||||||
base: 350,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 100 },
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 150 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 225 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
Spec::LifeRRPlusPlus => SpecValues {
|
|
||||||
base: 875,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 225 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 400 },
|
|
||||||
SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 525 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::LifeGGPlusPlus => SpecValues {
|
|
||||||
base: 475,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 130 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 225 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 300 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::LifeBBPlusPlus => SpecValues {
|
|
||||||
base: 875,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 225 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 400 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 525 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::LifeRGPlusPlus => SpecValues {
|
|
||||||
base: 400,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 175 },
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 275 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 400 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::LifeGBPlusPlus => SpecValues {
|
|
||||||
base: 625,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 175 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 275 },
|
|
||||||
SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 400 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Spec::LifeRBPlusPlus => SpecValues {
|
|
||||||
base: 400,
|
|
||||||
bonuses: vec![
|
|
||||||
SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 175 },
|
|
||||||
SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 275 },
|
|
||||||
SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 400 }
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply(&self, modified: u64, base: u64, player_colours: &Colours) -> u64 {
|
|
||||||
match *self {
|
|
||||||
// Percentage multipliers based on base value
|
|
||||||
Spec::Power |
|
|
||||||
Spec::Speed => modified + base.pct(self.values().base),
|
|
||||||
Spec::PowerRR|
|
|
||||||
Spec::PowerGG|
|
|
||||||
Spec::PowerBB|
|
|
||||||
Spec::PowerRG|
|
|
||||||
Spec::PowerGB|
|
|
||||||
Spec::PowerRB|
|
|
||||||
Spec::PowerRRPlus |
|
|
||||||
Spec::PowerGGPlus |
|
|
||||||
Spec::PowerBBPlus |
|
|
||||||
Spec::PowerRGPlus |
|
|
||||||
Spec::PowerGBPlus |
|
|
||||||
Spec::PowerRBPlus |
|
|
||||||
Spec::PowerRRPlusPlus |
|
|
||||||
Spec::PowerGGPlusPlus |
|
|
||||||
Spec::PowerBBPlusPlus |
|
|
||||||
Spec::PowerRGPlusPlus |
|
|
||||||
Spec::PowerGBPlusPlus |
|
|
||||||
Spec::PowerRBPlusPlus |
|
|
||||||
|
|
||||||
Spec::SpeedRR|
|
|
||||||
Spec::SpeedGG|
|
|
||||||
Spec::SpeedBB|
|
|
||||||
Spec::SpeedRG|
|
|
||||||
Spec::SpeedGB|
|
|
||||||
Spec::SpeedRB|
|
|
||||||
Spec::SpeedRRPlus |
|
|
||||||
Spec::SpeedGGPlus |
|
|
||||||
Spec::SpeedBBPlus |
|
|
||||||
Spec::SpeedRGPlus |
|
|
||||||
Spec::SpeedGBPlus |
|
|
||||||
Spec::SpeedRBPlus |
|
|
||||||
Spec::SpeedRRPlusPlus |
|
|
||||||
Spec::SpeedGGPlusPlus |
|
|
||||||
Spec::SpeedBBPlusPlus |
|
|
||||||
Spec::SpeedRGPlusPlus |
|
|
||||||
Spec::SpeedGBPlusPlus |
|
|
||||||
Spec::SpeedRBPlusPlus => modified + base.pct(self.values().max_value(player_colours)),
|
|
||||||
|
|
||||||
// Flat bonus
|
|
||||||
Spec::Life => modified + self.values().base,
|
|
||||||
Spec::LifeRR|
|
|
||||||
Spec::LifeGG|
|
|
||||||
Spec::LifeBB|
|
|
||||||
Spec::LifeRG|
|
|
||||||
Spec::LifeGB|
|
|
||||||
Spec::LifeRB|
|
|
||||||
Spec::LifeRRPlus |
|
|
||||||
Spec::LifeGGPlus |
|
|
||||||
Spec::LifeBBPlus |
|
|
||||||
Spec::LifeRGPlus |
|
|
||||||
Spec::LifeGBPlus |
|
|
||||||
Spec::LifeRBPlus |
|
|
||||||
Spec::LifeRRPlusPlus |
|
|
||||||
Spec::LifeGGPlusPlus |
|
|
||||||
Spec::LifeBBPlusPlus |
|
|
||||||
Spec::LifeRGPlusPlus |
|
|
||||||
Spec::LifeGBPlusPlus |
|
|
||||||
Spec::LifeRBPlusPlus => modified + self.values().max_value(player_colours),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
// use net::Db;
|
|
||||||
// Db Commons
|
|
||||||
// use failure::Error;
|
|
||||||
|
|
||||||
// pub fn startup(db: Db) -> Result<(), Error> {
|
|
||||||
// let tx = db.transaction()?;
|
|
||||||
|
|
||||||
// info!("running startup fns");
|
|
||||||
|
|
||||||
// match tx.commit() {
|
|
||||||
// Ok(_) => {
|
|
||||||
// info!("startup processes completed");
|
|
||||||
// Ok(())
|
|
||||||
// },
|
|
||||||
// Err(e) => Err(format_err!("failed to commit startup tx {:?}", e)),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub trait IntPct {
|
|
||||||
fn pct(self, pct: u64) -> u64;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntPct for u64 {
|
|
||||||
fn pct(self, pct: u64) -> u64 {
|
|
||||||
self * pct / 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn int_pct_test() {
|
|
||||||
assert_eq!(100.pct(110), 110);
|
|
||||||
assert_eq!(100.pct(50), 50);
|
|
||||||
assert_eq!(1.pct(200), 2);
|
|
||||||
assert_eq!(1.pct(50), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,345 +0,0 @@
|
|||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use std::iter;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
// refunds
|
|
||||||
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 instance::{Instance, instance_get, instance_update};
|
|
||||||
use construct::{Colours};
|
|
||||||
|
|
||||||
use item::*;
|
|
||||||
|
|
||||||
pub type VboxIndices = Option<HashMap<ItemType, Vec<String>>>;
|
|
||||||
|
|
||||||
#[derive(Debug,Clone,Serialize,Deserialize)]
|
|
||||||
pub struct Vbox {
|
|
||||||
pub bits: usize,
|
|
||||||
pub store: HashMap<ItemType, HashMap<String, Item>>,
|
|
||||||
pub stash: HashMap<String, Item>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug,Copy,Clone,Serialize,Deserialize,Hash,PartialEq,Eq)]
|
|
||||||
pub enum ItemType {
|
|
||||||
Colours,
|
|
||||||
Skills,
|
|
||||||
Specs,
|
|
||||||
}
|
|
||||||
|
|
||||||
const STORE_COLOURS_CAPACITY: usize = 6;
|
|
||||||
const STORE_SKILLS_CAPACITY: usize = 3;
|
|
||||||
const STORE_SPECS_CAPACITY: usize = 3;
|
|
||||||
const STASH_CAPACITY: usize = 6;
|
|
||||||
const STARTING_ATTACK_COUNT: usize = 3;
|
|
||||||
|
|
||||||
impl Vbox {
|
|
||||||
pub fn new() -> Vbox {
|
|
||||||
let mut colours: HashMap<String, Item> = HashMap::new();
|
|
||||||
let mut skills: HashMap<String, Item> = HashMap::new();
|
|
||||||
let mut specs: HashMap<String, Item> = HashMap::new();
|
|
||||||
|
|
||||||
let store = [
|
|
||||||
(ItemType::Colours, colours),
|
|
||||||
(ItemType::Skills, skills),
|
|
||||||
(ItemType::Colours, specs),
|
|
||||||
].iter().cloned().collect();
|
|
||||||
|
|
||||||
let mut stash = HashMap::new();
|
|
||||||
for i in 0..STARTING_ATTACK_COUNT {
|
|
||||||
stash.insert(i.to_string(), Item::Attack);
|
|
||||||
}
|
|
||||||
|
|
||||||
Vbox {
|
|
||||||
store,
|
|
||||||
stash,
|
|
||||||
bits: 30,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn balance_sub(&mut self, amount: usize) -> Result<&mut Vbox, Error> {
|
|
||||||
let new_balance = self.bits
|
|
||||||
.checked_sub(amount)
|
|
||||||
.ok_or(format_err!("insufficient balance: {:?}", self.bits))?;
|
|
||||||
|
|
||||||
self.bits = new_balance;
|
|
||||||
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn balance_add(&mut self, amount: usize) -> &mut Vbox {
|
|
||||||
self.bits = self.bits.saturating_add(amount);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fill(&mut self) -> &mut Vbox {
|
|
||||||
let mut rng = thread_rng();
|
|
||||||
|
|
||||||
let colours = vec![
|
|
||||||
(Item::Red, 1),
|
|
||||||
(Item::Green, 1),
|
|
||||||
(Item::Blue, 1),
|
|
||||||
];
|
|
||||||
let colour_dist = WeightedIndex::new(colours.iter().map(|item| item.1)).unwrap();
|
|
||||||
|
|
||||||
let skills = vec![
|
|
||||||
(Item::Attack, 1),
|
|
||||||
(Item::Block, 1),
|
|
||||||
(Item::Buff, 1),
|
|
||||||
(Item::Debuff, 1),
|
|
||||||
(Item::Stun, 1),
|
|
||||||
];
|
|
||||||
let skill_dist = WeightedIndex::new(skills.iter().map(|item| item.1)).unwrap();
|
|
||||||
|
|
||||||
let specs = vec![
|
|
||||||
(Item::Power, 1),
|
|
||||||
(Item::Life, 1),
|
|
||||||
(Item::Speed, 1),
|
|
||||||
];
|
|
||||||
let spec_dist = WeightedIndex::new(specs.iter().map(|item| item.1)).unwrap();
|
|
||||||
|
|
||||||
for item_type in [ItemType::Colours, ItemType::Skills, ItemType::Specs].iter() {
|
|
||||||
let (items, num, dist) = match item_type {
|
|
||||||
ItemType::Colours => (&colours, STORE_COLOURS_CAPACITY, &colour_dist),
|
|
||||||
ItemType::Skills => (&skills, STORE_SKILLS_CAPACITY, &skill_dist),
|
|
||||||
ItemType::Specs => (&specs, STORE_SPECS_CAPACITY, &spec_dist),
|
|
||||||
};
|
|
||||||
|
|
||||||
let drops = iter::repeat_with(|| items[dist.sample(&mut rng)].0)
|
|
||||||
.take(num)
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, item)| (i.to_string(), item))
|
|
||||||
.collect::<HashMap<String, Item>>();
|
|
||||||
|
|
||||||
self.store.insert(*item_type, drops);
|
|
||||||
}
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn buy(&mut self, item: ItemType, i: &String) -> Result<Item, Error> {
|
|
||||||
// check item exists
|
|
||||||
let selection = self.store
|
|
||||||
.get_mut(&item).ok_or(format_err!("no item group {:?}", item))?
|
|
||||||
.remove(i).ok_or(format_err!("no item at index {:?} {:}", self, i))?;
|
|
||||||
|
|
||||||
self.balance_sub(selection.cost())?;
|
|
||||||
|
|
||||||
Ok(selection)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stash_add(&mut self, item: Item, index: Option<&String>) -> Result<String, Error> {
|
|
||||||
if self.stash.len() >= STASH_CAPACITY {
|
|
||||||
return Err(err_msg("stash full"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(index) = index {
|
|
||||||
if self.stash.contains_key(index) {
|
|
||||||
return Err(format_err!("slot occupied {:?}", index));
|
|
||||||
}
|
|
||||||
self.stash.insert(index.clone(), item);
|
|
||||||
return Ok(index.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in (0..STASH_CAPACITY).map(|i| i.to_string()) {
|
|
||||||
if !self.stash.contains_key(&i) {
|
|
||||||
self.stash.insert(i.clone(), item);
|
|
||||||
return Ok(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Err(err_msg("stash full"));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bot_buy(&mut self, item: ItemType) -> Result<Item, Error> {
|
|
||||||
let buy_index = self.store[&item]
|
|
||||||
.keys()
|
|
||||||
.next()
|
|
||||||
.ok_or(format_err!("no item in group {:?}", item))?
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
self.buy(item, &buy_index)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn refund(&mut self, i: String) -> Result<&mut Vbox, Error> {
|
|
||||||
let refunded = self.stash.remove(&i)
|
|
||||||
.ok_or(format_err!("no item at index {:?} {:?}", self.stash, i))?;
|
|
||||||
|
|
||||||
let refund = refunded.cost();
|
|
||||||
// info!("refunding {:?} for {:?}", refund, refunded);
|
|
||||||
self.balance_add(refund);
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn combine(&mut self, stash_indices: Vec<String>, store_indices: Option<HashMap<ItemType, Vec<String>>>) -> Result<&mut Vbox, Error> {
|
|
||||||
// find base item for index to insert into
|
|
||||||
let base_index = stash_indices.iter()
|
|
||||||
.find(|i| match self.stash.get(i.clone()) {
|
|
||||||
Some(item) => item.into_skill().is_some(),
|
|
||||||
None => false,
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut input = stash_indices
|
|
||||||
.iter()
|
|
||||||
.map(|i| self.stash.remove(i)
|
|
||||||
.ok_or(format_err!("no item at index {:?} {:?}", self.stash, i)))
|
|
||||||
.collect::<Result<Vec<Item>, Error>>()?;
|
|
||||||
|
|
||||||
if let Some(store_indices) = store_indices {
|
|
||||||
let mut purchased = store_indices.iter()
|
|
||||||
.map(|(g, list)|
|
|
||||||
list.iter()
|
|
||||||
.map(|i| self.buy(*g, i))
|
|
||||||
.collect::<Result<Vec<Item>, Error>>()
|
|
||||||
)
|
|
||||||
.collect::<Result<Vec<Vec<Item>>, Error>>()?
|
|
||||||
.into_iter()
|
|
||||||
.flatten()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
input.append(&mut purchased);
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort the input to align with the combinations
|
|
||||||
// combos are sorted when created
|
|
||||||
input.sort_unstable();
|
|
||||||
let combos = get_combos();
|
|
||||||
let combo = combos.iter().find(|c| c.components == input).ok_or(err_msg("not a combo"))?;
|
|
||||||
|
|
||||||
self.stash_add(combo.item, base_index)?;
|
|
||||||
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_refill(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result<Instance, Error> {
|
|
||||||
let instance = instance_get(tx, instance_id)?
|
|
||||||
.vbox_refill(account.id)?;
|
|
||||||
return instance_update(tx, instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_buy(tx: &mut Transaction, account: &Account, instance_id: Uuid, group: ItemType, index: String, construct_id: Option<Uuid>) -> Result<Instance, Error> {
|
|
||||||
let instance = instance_get(tx, instance_id)?
|
|
||||||
.vbox_buy(account.id, group, index, construct_id)?;
|
|
||||||
return instance_update(tx, instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_combine(tx: &mut Transaction, account: &Account, instance_id: Uuid, stash_indices: Vec<String>, vbox_indices: VboxIndices) -> Result<Instance, Error> {
|
|
||||||
let instance = instance_get(tx, instance_id)?
|
|
||||||
.vbox_combine(account.id, stash_indices, vbox_indices)?;
|
|
||||||
return instance_update(tx, instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_refund(tx: &mut Transaction, account: &Account, instance_id: Uuid, index: String) -> Result<Instance, Error> {
|
|
||||||
let instance = instance_get(tx, instance_id)?
|
|
||||||
.vbox_refund(account.id, index)?;
|
|
||||||
return instance_update(tx, instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_apply(tx: &mut Transaction, account: &Account, instance_id: Uuid, construct_id: Uuid, index: String) -> Result<Instance, Error> {
|
|
||||||
let instance = instance_get(tx, instance_id)?
|
|
||||||
.vbox_apply(account.id, index, construct_id)?;
|
|
||||||
return instance_update(tx, instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vbox_unequip(tx: &mut Transaction, account: &Account, instance_id: Uuid, construct_id: Uuid, target: Item, target_construct_id: Option<Uuid>) -> Result<Instance, Error> {
|
|
||||||
let instance = instance_get(tx, instance_id)?
|
|
||||||
.vbox_unequip(account.id, target, construct_id, target_construct_id)?;
|
|
||||||
return instance_update(tx, instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn combine_test() {
|
|
||||||
let mut vbox = Vbox::new();
|
|
||||||
vbox.stash.insert(0.to_string(), Item::Attack);
|
|
||||||
vbox.stash.insert(1.to_string(), Item::Green);
|
|
||||||
vbox.stash.insert(2.to_string(), Item::Green);
|
|
||||||
vbox.combine(vec![0.to_string(), 1.to_string(), 2.to_string()], None).unwrap();
|
|
||||||
assert_eq!(vbox.stash["0"], Item::Heal);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn buy_test() {
|
|
||||||
let mut vbox = Vbox::new();
|
|
||||||
vbox.fill();
|
|
||||||
|
|
||||||
// cannot rebuy same
|
|
||||||
vbox.buy(ItemType::Skills, &0.to_string()).unwrap();
|
|
||||||
assert!(vbox.store[&ItemType::Skills].get(&0.to_string()).is_none());
|
|
||||||
assert!(vbox.buy(ItemType::Skills, &0.to_string()).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn capacity_test() {
|
|
||||||
let mut vbox = Vbox::new();
|
|
||||||
vbox.fill();
|
|
||||||
vbox.stash_add(Item::Red, None).unwrap();
|
|
||||||
vbox.stash_add(Item::Red, None).unwrap();
|
|
||||||
vbox.stash_add(Item::Red, None).unwrap();
|
|
||||||
assert!(vbox.stash_add(Item::Red, None).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn store_and_stash_combine_test() {
|
|
||||||
let mut vbox = Vbox::new();
|
|
||||||
vbox.fill();
|
|
||||||
|
|
||||||
let mut skill_combine_args = HashMap::new();
|
|
||||||
skill_combine_args.insert(ItemType::Colours, vec![0.to_string(), 1.to_string()]);
|
|
||||||
skill_combine_args.insert(ItemType::Skills, vec![0.to_string()]);
|
|
||||||
|
|
||||||
let mut spec_combine_args = HashMap::new();
|
|
||||||
spec_combine_args.insert(ItemType::Colours, vec![2.to_string(), 3.to_string()]);
|
|
||||||
spec_combine_args.insert(ItemType::Specs, vec![0.to_string()]);
|
|
||||||
|
|
||||||
vbox.combine(vec![], Some(skill_combine_args)).unwrap();
|
|
||||||
vbox.combine(vec![], Some(spec_combine_args)).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn combos_test() {
|
|
||||||
let mut input = vec![Item::Green, Item::Attack, Item::Green];
|
|
||||||
let combos = get_combos();
|
|
||||||
|
|
||||||
// sort input so they align
|
|
||||||
input.sort_unstable();
|
|
||||||
|
|
||||||
let combo = combos.iter().find(|c| c.components == input);
|
|
||||||
assert!(combo.is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn refund_test() {
|
|
||||||
let mut vbox = Vbox::new();
|
|
||||||
vbox.stash.insert(0.to_string(), Item::Strike);
|
|
||||||
vbox.refund(0.to_string()).unwrap();
|
|
||||||
assert_eq!(vbox.bits, 32);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn colours_count_test() {
|
|
||||||
let strike = Item::Strike;
|
|
||||||
|
|
||||||
let mut count = Colours::new();
|
|
||||||
strike.colours(&mut count);
|
|
||||||
assert_eq!(count.red, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn item_info_test() {
|
|
||||||
// info!("{:#?}", item_info());
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@ -9,12 +9,20 @@ use postgres::transaction::Transaction;
|
|||||||
use failure::Error;
|
use failure::Error;
|
||||||
|
|
||||||
use account;
|
use account;
|
||||||
use game::{games_need_upkeep, game_update, game_write, game_delete};
|
|
||||||
use instance;
|
|
||||||
use instance::{instances_need_upkeep, instances_idle, instance_update, instance_delete};
|
|
||||||
use pg::{Db, PgPool};
|
|
||||||
use events::{Event, EventsTx, PvpRequest};
|
use events::{Event, EventsTx, PvpRequest};
|
||||||
use rpc::{RpcMessage};
|
use rpc::{RpcMessage};
|
||||||
|
use pg::{
|
||||||
|
PgPool,
|
||||||
|
|
||||||
|
games_need_upkeep,
|
||||||
|
game_update,
|
||||||
|
game_write,
|
||||||
|
game_delete,
|
||||||
|
instances_need_upkeep,
|
||||||
|
instances_idle,
|
||||||
|
instance_update,
|
||||||
|
pvp,
|
||||||
|
};
|
||||||
|
|
||||||
type Id = usize;
|
type Id = usize;
|
||||||
type Pair = (PvpRequest, PvpRequest);
|
type Pair = (PvpRequest, PvpRequest);
|
||||||
@ -100,7 +108,7 @@ impl Warden {
|
|||||||
let a = account::select(&db, pair.0.account)?;
|
let a = account::select(&db, pair.0.account)?;
|
||||||
let b = account::select(&db, pair.1.account)?;
|
let b = account::select(&db, pair.1.account)?;
|
||||||
|
|
||||||
let instance = instance::pvp(&mut tx, &a, &b)?;
|
let instance = pvp(&mut tx, &a, &b)?;
|
||||||
tx.commit()?;
|
tx.commit()?;
|
||||||
|
|
||||||
// subscribe users to instance events
|
// subscribe users to instance events
|
||||||
|
|||||||
@ -1,417 +0,0 @@
|
|||||||
use uuid::Uuid;
|
|
||||||
use petgraph::graph::{Graph, UnGraph, NodeIndex};
|
|
||||||
// use petgraph::dot::{Dot, Config};
|
|
||||||
|
|
||||||
// Db Commons
|
|
||||||
use account::Account;
|
|
||||||
use serde_cbor::{from_slice, to_vec};
|
|
||||||
use postgres::transaction::Transaction;
|
|
||||||
use failure::Error;
|
|
||||||
use failure::err_msg;
|
|
||||||
|
|
||||||
// shapes
|
|
||||||
use rand::prelude::*;
|
|
||||||
use rand::{thread_rng};
|
|
||||||
use rand::distributions::{WeightedIndex};
|
|
||||||
|
|
||||||
use game::{Game, GameMode, game_pve_new, game_write};
|
|
||||||
use rpc::{ZoneJoinParams, ZoneCloseParams};
|
|
||||||
|
|
||||||
#[derive(Debug,Clone,Serialize,Deserialize)]
|
|
||||||
pub struct Zone {
|
|
||||||
id: Uuid,
|
|
||||||
account: Uuid,
|
|
||||||
active: bool,
|
|
||||||
graph: UnGraph<Encounter, ()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug,Clone,Copy)]
|
|
||||||
enum Shape {
|
|
||||||
Diamond,
|
|
||||||
Line,
|
|
||||||
Plus,
|
|
||||||
Diode,
|
|
||||||
Domino,
|
|
||||||
Kite,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type ZoneGraph = UnGraph<Encounter, ()>;
|
|
||||||
|
|
||||||
#[derive(Debug,Clone,PartialEq,Eq,Hash,PartialOrd,Ord,Serialize,Deserialize)]
|
|
||||||
pub struct Encounter {
|
|
||||||
tag: String,
|
|
||||||
game_id: Option<Uuid>,
|
|
||||||
success: bool,
|
|
||||||
x: i8,
|
|
||||||
y: i8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encounter {
|
|
||||||
fn new(tag: &'static str, x: i8, y: i8) -> Encounter {
|
|
||||||
return Encounter {
|
|
||||||
tag: tag.to_string(),
|
|
||||||
success: false,
|
|
||||||
game_id: None,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
fn start() -> Encounter {
|
|
||||||
return Encounter {
|
|
||||||
tag: "START".to_string(),
|
|
||||||
success: true,
|
|
||||||
game_id: None,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn zone_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> {
|
|
||||||
let query = "
|
|
||||||
DELETE
|
|
||||||
FROM zones
|
|
||||||
WHERE id = $1;
|
|
||||||
";
|
|
||||||
|
|
||||||
let result = tx
|
|
||||||
.execute(query, &[&id])?;
|
|
||||||
|
|
||||||
if result != 1 {
|
|
||||||
return Err(format_err!("unable to delete zone {:?}", id));
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("zone deleted {:?}", id);
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn zone_get(tx: &mut Transaction, id: Uuid) -> Result<Zone, Error> {
|
|
||||||
let query = "
|
|
||||||
SELECT *
|
|
||||||
FROM zones
|
|
||||||
WHERE id = $1
|
|
||||||
";
|
|
||||||
|
|
||||||
let result = tx
|
|
||||||
.query(query, &[&id])?;
|
|
||||||
|
|
||||||
let returned = match result.iter().next() {
|
|
||||||
Some(row) => row,
|
|
||||||
None => return Err(err_msg("zone not found")),
|
|
||||||
};
|
|
||||||
|
|
||||||
// tells from_slice to cast into a construct
|
|
||||||
let bytes: Vec<u8> = returned.get("data");
|
|
||||||
let zone = match from_slice::<Zone>(&bytes) {
|
|
||||||
Ok(z) => z,
|
|
||||||
Err(_) => {
|
|
||||||
zone_delete(tx, id)?;
|
|
||||||
return Err(err_msg("invalid zone removed"))
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return Ok(zone);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn zone_create(tx: &mut Transaction, account: &Account) -> Result<Zone, Error> {
|
|
||||||
let id = Uuid::new_v4();
|
|
||||||
let graph = create_zone_graph();
|
|
||||||
|
|
||||||
let zone = Zone {
|
|
||||||
id,
|
|
||||||
account: account.id,
|
|
||||||
graph,
|
|
||||||
active: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
let bytes = to_vec(&zone)?;
|
|
||||||
|
|
||||||
let query = "
|
|
||||||
INSERT INTO zones (id, data, account)
|
|
||||||
VALUES ($1, $2, $3)
|
|
||||||
RETURNING id;
|
|
||||||
";
|
|
||||||
|
|
||||||
let result = tx
|
|
||||||
.query(query, &[&id, &bytes, &account.id])?;
|
|
||||||
|
|
||||||
result.iter().next().ok_or(format_err!("no zone written"))?;
|
|
||||||
|
|
||||||
return Ok(zone);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn zone_update(zone: &Zone, tx: &mut Transaction) -> Result<(), Error> {
|
|
||||||
let bytes = to_vec(&zone)?;
|
|
||||||
|
|
||||||
let query = "
|
|
||||||
UPDATE zones
|
|
||||||
SET data = $1, active = $2
|
|
||||||
WHERE id = $3
|
|
||||||
RETURNING id, data;
|
|
||||||
";
|
|
||||||
|
|
||||||
let result = tx
|
|
||||||
.query(query, &[&bytes, &zone.active, &zone.id])?;
|
|
||||||
|
|
||||||
result.iter().next().ok_or(format_err!("zone {:?} could not be written", zone))?;
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn zone_join(params: ZoneJoinParams, tx: &mut Transaction, account: &Account) -> Result<Game, Error> {
|
|
||||||
let mut zone = zone_get(tx, params.zone_id)?;
|
|
||||||
let mut game;
|
|
||||||
|
|
||||||
// check node joinable
|
|
||||||
node_joinable(&zone.graph, NodeIndex::from(params.node_id))?;
|
|
||||||
|
|
||||||
// borrow zone.graph to make the game
|
|
||||||
{
|
|
||||||
let node_index = NodeIndex::from(params.node_id);
|
|
||||||
let encounter = zone.graph
|
|
||||||
.node_weight_mut(node_index)
|
|
||||||
.ok_or(err_msg("invalid encounter id"))?;
|
|
||||||
|
|
||||||
let mode = match encounter.tag.as_ref() {
|
|
||||||
"NORMAL" => GameMode::Zone3v2Attack,
|
|
||||||
"CASTER" => GameMode::Zone2v2Caster,
|
|
||||||
"MINIBOSS" => GameMode::Zone3v3MeleeMiniboss,
|
|
||||||
"BOSS" => GameMode::Zone3v3HealerBoss,
|
|
||||||
_ => return Err(err_msg("unknown zone tag")),
|
|
||||||
};
|
|
||||||
game = game_pve_new(params.construct_ids, mode, tx, account)?;
|
|
||||||
game.set_zone(zone.id, params.node_id);
|
|
||||||
|
|
||||||
encounter.game_id = Some(game.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// persist
|
|
||||||
game_write(tx, &game)?;
|
|
||||||
zone_update(&zone, tx)?;
|
|
||||||
|
|
||||||
return Ok(game);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn zone_close(params: ZoneCloseParams, tx: &mut Transaction, _account: &Account) -> Result<(), Error> {
|
|
||||||
let mut zone = zone_get(tx, params.zone_id)?;
|
|
||||||
zone.active = false;
|
|
||||||
zone_update(&zone, tx)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// shapes should always add the exit normal node
|
|
||||||
fn add_shape(shape: Shape, gr: &mut ZoneGraph, start: NodeIndex, x: i8, y: i8) -> (NodeIndex, i8, i8) {
|
|
||||||
match shape {
|
|
||||||
Shape::Line => {
|
|
||||||
let mut next = gr.add_node(Encounter::new("CASTER", x, y));
|
|
||||||
gr.add_edge(start, next, ());
|
|
||||||
|
|
||||||
let exit = gr.add_node(Encounter::new("NORMAL", x + 1, y));
|
|
||||||
gr.add_edge(next, exit, ());
|
|
||||||
|
|
||||||
return (exit, x + 1, y);
|
|
||||||
},
|
|
||||||
Shape::Plus => {
|
|
||||||
let top = gr.add_node(Encounter::new("MINIBOSS", x, y + 1));
|
|
||||||
gr.add_edge(start, top, ());
|
|
||||||
|
|
||||||
let bottom = gr.add_node(Encounter::new("MINIBOSS", x, y - 1));
|
|
||||||
gr.add_edge(start, bottom, ());
|
|
||||||
|
|
||||||
let exit = gr.add_node(Encounter::new("NORMAL", x + 1, y));
|
|
||||||
gr.add_edge(start, exit, ());
|
|
||||||
|
|
||||||
return (exit, x + 1, y);
|
|
||||||
},
|
|
||||||
Shape::Diode => {
|
|
||||||
let top = gr.add_node(Encounter::new("MINIBOSS", x, y + 1));
|
|
||||||
gr.add_edge(start, top, ());
|
|
||||||
|
|
||||||
let bottom = gr.add_node(Encounter::new("MINIBOSS", x, y - 1));
|
|
||||||
gr.add_edge(start, bottom, ());
|
|
||||||
|
|
||||||
let exit = gr.add_node(Encounter::new("NORMAL", x + 1, y));
|
|
||||||
gr.add_edge(start, exit, ());
|
|
||||||
|
|
||||||
// connect top and exit for an extra chance
|
|
||||||
gr.add_edge(top, exit, ());
|
|
||||||
gr.add_edge(bottom, exit, ());
|
|
||||||
|
|
||||||
return (exit, x + 1, y);
|
|
||||||
},
|
|
||||||
Shape::Kite => {
|
|
||||||
let top = gr.add_node(Encounter::new("MINIBOSS", x + 1, y + 1));
|
|
||||||
gr.add_edge(start, top, ());
|
|
||||||
|
|
||||||
let top_tip = gr.add_node(Encounter::new("BOSS", x + 1, y + 2));
|
|
||||||
gr.add_edge(top, top_tip, ());
|
|
||||||
|
|
||||||
let bottom = gr.add_node(Encounter::new("MINIBOSS", x + 1, y - 1));
|
|
||||||
gr.add_edge(start, bottom, ());
|
|
||||||
|
|
||||||
let bottom_tip = gr.add_node(Encounter::new("BOSS", x + 1, y - 2));
|
|
||||||
gr.add_edge(bottom, bottom_tip, ());
|
|
||||||
|
|
||||||
let side = gr.add_node(Encounter::new("CASTER", x + 2, y));
|
|
||||||
gr.add_edge(start, side, ());
|
|
||||||
gr.add_edge(top, side, ());
|
|
||||||
gr.add_edge(bottom, side, ());
|
|
||||||
|
|
||||||
let exit = gr.add_node(Encounter::new("NORMAL", x + 3, y));
|
|
||||||
gr.add_edge(side, exit, ());
|
|
||||||
|
|
||||||
return (exit, x + 3, y);
|
|
||||||
},
|
|
||||||
Shape::Domino => {
|
|
||||||
let top = gr.add_node(Encounter::new("NORMAL", x, y + 1));
|
|
||||||
gr.add_edge(start, top, ());
|
|
||||||
|
|
||||||
let top_tip = gr.add_node(Encounter::new("MINIBOSS", x + 1, y + 1));
|
|
||||||
gr.add_edge(top, top_tip, ());
|
|
||||||
|
|
||||||
let bottom = gr.add_node(Encounter::new("MINIBOSS", x, y - 1));
|
|
||||||
gr.add_edge(start, bottom, ());
|
|
||||||
|
|
||||||
let bottom_tip = gr.add_node(Encounter::new("NORMAL", x + 1, y - 1));
|
|
||||||
gr.add_edge(bottom, bottom_tip, ());
|
|
||||||
|
|
||||||
let side = gr.add_node(Encounter::new("CASTER", x + 1, y));
|
|
||||||
gr.add_edge(top_tip, side, ());
|
|
||||||
gr.add_edge(bottom_tip, side, ());
|
|
||||||
|
|
||||||
let exit = gr.add_node(Encounter::new("NORMAL", x + 2, y));
|
|
||||||
gr.add_edge(side, exit, ());
|
|
||||||
|
|
||||||
return (exit, x + 2, y);
|
|
||||||
},
|
|
||||||
Shape::Diamond => {
|
|
||||||
let top = gr.add_node(Encounter::new("NORMAL", x + 1, y + 1));
|
|
||||||
gr.add_edge(start, top, ());
|
|
||||||
|
|
||||||
let top_tip = gr.add_node(Encounter::new("MINIBOSS", x + 2, y + 1));
|
|
||||||
gr.add_edge(top, top_tip, ());
|
|
||||||
|
|
||||||
let bottom = gr.add_node(Encounter::new("MINIBOSS", x + 1, y - 1));
|
|
||||||
gr.add_edge(start, bottom, ());
|
|
||||||
|
|
||||||
let bottom_tip = gr.add_node(Encounter::new("NORMAL", x + 2, y - 1));
|
|
||||||
gr.add_edge(bottom, bottom_tip, ());
|
|
||||||
|
|
||||||
let exit = gr.add_node(Encounter::new("CASTER", x + 3, y));
|
|
||||||
gr.add_edge(top_tip, exit, ());
|
|
||||||
gr.add_edge(bottom_tip, exit, ());
|
|
||||||
|
|
||||||
return (exit, x + 3, y);
|
|
||||||
},
|
|
||||||
// _ => panic!("nyi shape"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_zone_graph() -> ZoneGraph {
|
|
||||||
let mut gr = Graph::new_undirected();
|
|
||||||
let mut rng = thread_rng();
|
|
||||||
|
|
||||||
let mut last = gr.add_node(Encounter::start());
|
|
||||||
let mut x = 0;
|
|
||||||
let mut y = 0;
|
|
||||||
|
|
||||||
for _i in 0..4 {
|
|
||||||
let shapes = vec![
|
|
||||||
(Shape::Line, 1),
|
|
||||||
(Shape::Diamond, 1),
|
|
||||||
(Shape::Diode, 1),
|
|
||||||
(Shape::Kite, 1),
|
|
||||||
(Shape::Domino, 1),
|
|
||||||
(Shape::Plus, 1),
|
|
||||||
];
|
|
||||||
|
|
||||||
let dist = WeightedIndex::new(shapes.iter().map(|item| item.1)).unwrap();
|
|
||||||
let shape = shapes[dist.sample(&mut rng)].0;
|
|
||||||
|
|
||||||
let result = add_shape(shape, &mut gr, last, x, y);
|
|
||||||
last = result.0;
|
|
||||||
x = result.1;
|
|
||||||
y = result.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return gr;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn node_joinable(graph: &ZoneGraph, target_index: NodeIndex) -> Result<(), Error> {
|
|
||||||
// early return for already attempted
|
|
||||||
{
|
|
||||||
let target_encounter = match graph.node_weight(target_index) {
|
|
||||||
Some(encounter) => encounter,
|
|
||||||
None => panic!("{:?} has no weight for {:?}", graph, target_index),
|
|
||||||
};
|
|
||||||
|
|
||||||
if target_encounter.game_id.is_some() {
|
|
||||||
return Err(err_msg("node already attempted"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let success_indices = graph.node_indices().filter(|i| {
|
|
||||||
match graph.node_weight(*i) {
|
|
||||||
Some(encounter) => encounter.success,
|
|
||||||
None => panic!("no weight for {:?}", i),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// if a node is a neighbour of that graph
|
|
||||||
// and hasn't been attempted
|
|
||||||
// it is joinable
|
|
||||||
for i in success_indices {
|
|
||||||
match graph.neighbors(i).find(|n| *n == target_index) {
|
|
||||||
Some(_n) => return Ok(()),
|
|
||||||
None => continue,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return Err(err_msg("node requirements not met"));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn node_finish(game: &Game, zone_id: Uuid, node_index: u32, tx: &mut Transaction) -> Result<Zone, Error> {
|
|
||||||
let mut zone = zone_get(tx, zone_id)?;
|
|
||||||
|
|
||||||
let winner_id = match game.winner() {
|
|
||||||
Some(w) => w.id,
|
|
||||||
None => return Ok(zone),
|
|
||||||
};
|
|
||||||
|
|
||||||
if zone.account == winner_id {
|
|
||||||
{
|
|
||||||
let encounter = zone.graph
|
|
||||||
.node_weight_mut(NodeIndex::from(node_index))
|
|
||||||
.ok_or(err_msg("encounter not found for game zone update"))?;
|
|
||||||
|
|
||||||
encounter.success = true;
|
|
||||||
}
|
|
||||||
zone_update(&zone, tx)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(zone)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use zone::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn create_zone_test() {
|
|
||||||
let _graph = create_zone_graph();
|
|
||||||
// good shit;
|
|
||||||
// let nodes = graph.node_indices().collect::<Vec<NodeIndex>>();
|
|
||||||
// info!("{:?}", nodes[0]);
|
|
||||||
// info!("{:?}", graph.node_weight(nodes[0]));
|
|
||||||
|
|
||||||
// info!("{:?}", Dot::with_config(&graph, &[Config::EdgeNoLabel]));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn zone_joinable_test() {
|
|
||||||
let graph = create_zone_graph();
|
|
||||||
assert!(node_joinable(&graph, NodeIndex::from(1)).is_ok());
|
|
||||||
assert!(node_joinable(&graph, NodeIndex::from(graph.node_count() as u32 - 1)).is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user