From 9fa2a94adee4af9055e2ecf207a778b415aba30e Mon Sep 17 00:00:00 2001 From: ntr Date: Fri, 4 Jan 2019 01:00:13 +1100 Subject: [PATCH] mob modes --- server/WORKLOG.md | 6 ++ server/src/cryp.rs | 20 +++- server/src/game.rs | 236 ++++++++------------------------------------- server/src/main.rs | 1 + server/src/mob.rs | 152 +++++++++++++++++++++++++++++ server/src/rpc.rs | 3 +- server/src/zone.rs | 19 +++- 7 files changed, 233 insertions(+), 204 deletions(-) create mode 100644 server/src/mob.rs diff --git a/server/WORKLOG.md b/server/WORKLOG.md index 2e58a248..981ada3c 100644 --- a/server/WORKLOG.md +++ b/server/WORKLOG.md @@ -17,6 +17,10 @@ strangle ## SOON * aoe skills +* hash cryp name for avatar + +* order cryps based on id + * keep track of games joined * concede game on leave * ko all cryps on team, check status @@ -28,6 +32,8 @@ strangle * skills * private fields for opponents +* flavour text + ## LATER * redis for game events * chat diff --git a/server/src/cryp.rs b/server/src/cryp.rs index 95bf63d2..42a45a5f 100644 --- a/server/src/cryp.rs +++ b/server/src/cryp.rs @@ -117,7 +117,7 @@ impl Cryp { hp: CrypStat { base: 0, stat: Stat::Hp }, lvl: 0, xp: 0, - skills: vec![CrypSkill::new(Skill::Attack)], + skills: vec![], effects: vec![], name: String::new(), ko_logged: false, @@ -268,6 +268,23 @@ impl Cryp { .collect() } + pub fn mob_select_skill(&self) -> Option { + let available = self.available_skills(); + + if available.len() == 0 { + return None; + } + + let highest_cd = available.iter() + .filter(|s| s.skill.base_cd().is_some()) + .max_by_key(|s| s.skill.base_cd().unwrap()); + + return match highest_cd { + Some(s) => Some(s.skill), + None => Some(available[0].skill), + }; + } + pub fn knows(&self, skill: Skill) -> bool { self.skills.iter().any(|s| s.skill == skill) } @@ -499,6 +516,7 @@ pub fn cryp_get(tx: &mut Transaction, id: Uuid, account_id: Uuid) -> Result Result { let cryp = Cryp::new() .named(¶ms.name) + .learn(Skill::Attack) .level(10) .set_account(account.id) .create(); diff --git a/server/src/game.rs b/server/src/game.rs index 43bd6cba..4e351697 100644 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -1,8 +1,5 @@ -use uuid::Uuid; use rand::prelude::*; -use rand::distributions::Alphanumeric; - -use std::iter; +use uuid::Uuid; // Db Commons use serde_cbor::{from_slice, to_vec}; @@ -16,15 +13,10 @@ use cryp::{Cryp, cryp_get}; use skill::{Skill, Cast, ResolutionResult}; use item::{item_drop}; use zone::{node_finish}; +use mob::{PveMode, generate_mob_team}; pub type Log = Vec; -#[derive(Debug,Clone,Serialize,Deserialize)] -pub enum PveMode { - Boss, - Normal, -} - #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Team { pub id: Uuid, @@ -214,13 +206,27 @@ impl Game { let mob_team_id = Uuid::nil(); let mobs = self.team_by_id(mob_team_id).clone(); - // TODO attack multiple players based on some criteria let player_team = self.teams.iter().find(|t| t.id != mob_team_id).unwrap().clone(); let player_len = player_team.cryps.len(); - for (i, mob) in mobs.cryps.iter().enumerate() { - // doesn't matter if the cryp can't cast - self.add_skill(mob_team_id, mob.id, Some(player_team.cryps[player_len % (i + 1)].id), Skill::Attack).ok(); + for mob in mobs.cryps.iter() { + let skill = mob.mob_select_skill(); + // println!("{:?} {:?}", mob.name, skill); + match skill { + Some(s) => { + let mut rng = thread_rng(); + let mut target = &player_team.cryps[rng.gen_range(0, player_len)]; + while target.is_ko() { + target = &player_team.cryps[rng.gen_range(0, player_len)]; + } + match self.add_skill(mob_team_id, mob.id, Some(target.id), s) { + Ok(_) => (), + Err(e) => println!("{:?} could not add pve skill", e), + } + // println!("{:?}", cast); + }, + None => continue, + }; } } @@ -242,10 +248,15 @@ impl Game { // target checks { - let _target = match self.cryp_by_id(final_target_id) { + let target = match self.cryp_by_id(final_target_id) { Some(c) => c, None => return Err(err_msg("target cryp not in game")), }; + + // fixme for rez + if target.is_ko() { + return Err(err_msg("target cryp is ko")); + } } // cryp checks @@ -275,12 +286,6 @@ impl Game { } } - // FIXME - // let (target_cryp_id, target_team_id) = match skill.self_targeting() { - // true => (Some(source_cryp_id), source_team_id), - // false => (None, target_team_id.unwrap()) - // }; - // replace cryp skill if let Some(s) = self.stack.iter_mut().position(|s| s.source_cryp_id == source_cryp_id) { self.stack.remove(s); @@ -304,129 +309,6 @@ impl Game { ) } - // fn target_phase_start(&mut self) -> &mut Game { - // assert!(self.skill_phase_finished()); - // self.log.push("".to_string()); - - // if self.phase != Phase::Skill { - // panic!("game not in skill phase"); - // } - - // self.phase = Phase::Target; - - // if self.is_pve { - // self.pve_add_targets(); - // } - - // // all cryps are stunned or otherwise inactive - // if self.target_phase_finished() { - // self.resolve_phase_start(); - // } - - // self - // } - - // fn pve_add_targets(&mut self) -> &mut Game { - // { - // let mob_team_id = Uuid::nil(); - // let mobs = self.team_by_id(mob_team_id).clone(); - - // // TODO attack multiple players based on some criteria - // for (i, incoming_skill_id) in self.stack.clone().iter() - // .filter(|s| s.target_cryp_id.is_none() && s.target_team_id == mob_team_id) - // .enumerate() - // .map(|(i, s)| (i, s.id)) { - // let targets = mobs.cryps - // .iter() - // .filter(|c| self.cryp_targetable(mob_team_id, c.id).is_ok()) - // .collect::>(); - - // if targets.len() == 0 { - // panic!("could not find a targetable pve cryp"); - // } - - // let target_id = targets[i % targets.len()].id; - // self.add_target(mob_team_id, target_id, incoming_skill_id).unwrap(); - // } - // } - - // self - // } - - // each cryp can be the target of - // incomingSkills / activeCryps rounded up - // maybe a problem with friendly / self targeting skills - fn cryp_targetable(&mut self, team_id: Uuid, cryp_id: Uuid) -> Result<(), Error> { - // whose team is this? - let team = self.teams.iter() - .find(|t| t.id == team_id) - .ok_or(err_msg("team not found"))?; - - // is the target in the team? - let cryp = team.cryps.iter() - .find(|c| c.id == cryp_id) - .ok_or(err_msg("cryp not in team"))?; - - if cryp.is_ko() { - return Err(err_msg("you cannot target ko cryps")); - } - - // let incoming = self.stack.iter() - // .filter(|i| i.target_team_id == team.id) - // .count(); - - // let incoming = incoming as u32 as f64; - - // let active_cryps = team.cryps.iter() - // .filter(|c| !c.is_ko()) - // .count(); - - // let active_cryps = active_cryps as u32 as f64; - // let max_targets = (incoming / active_cryps).ceil(); - - // // println!("targets {:} / {:} = {:}", incoming, active_cryps, max_targets); - - // let targeted = self.stack.iter() - // .filter(|s| s.target_cryp_id.is_some()) - // .filter(|s| s.target_cryp_id.unwrap() == cryp_id) - // .count(); - - // if targeted >= max_targets as usize { - // return Err(format_err!("cryp target of maximum number of skills ({:})", max_targets)); - // } - - return Ok(()); - } - - // targets can only be added by the owner of the team - // fn add_target(&mut self, team_id: Uuid, cryp_id: Uuid, skill_id: Uuid) -> Result<&mut Cast, Error> { - // if self.phase != Phase::Target { - // return Err(err_msg("game not in target phase")); - // } - - // self.cryp_targetable(team_id, cryp_id)?; - - // // set the target - // let cast = match self.stack.iter_mut().find(|s| s.id == skill_id) { - // Some(c) => c, - // None => return Err(err_msg("skill_id not found")), - // }; - - // if cast.skill.self_targeting() { - // return Err(err_msg("skill is self targeting")); - // } - - // if cast.target_team_id != team_id { - // return Err(err_msg("you cannot target that skill")); - // } - - // Ok(cast.set_target(cryp_id)) - // } - - // fn target_phase_finished(&self) -> bool { - // self.stack.iter().all(|s| s.target_cryp_id.is_some()) - // } - // requires no input // just do it fn resolve_phase_start(&mut self) -> &mut Game { @@ -760,52 +642,6 @@ pub fn game_update(game: &Game, tx: &mut Transaction) -> Result<(), Error> { return Ok(()); } -fn generate_mob(lvl: u8) -> Cryp { - let mut rng = thread_rng(); - - let name: String = iter::repeat(()) - .map(|()| rng.sample(Alphanumeric)) - .take(8) - .collect(); - - // rng panics on min == max - // let mob_lvl: u8 = match lvl { - // 1 => 1, - // _ => rng.gen_range(lvl.saturating_sub(2), lvl) - // }; - - return Cryp::new() - .named(&name) - .level(lvl) - .create(); - -} - -fn generate_mob_team(mode: PveMode, cryps: &Vec) -> Team { - let mut mob_team = Team::new(Uuid::nil()); - - // Default settings - let mut team_size = 1; - - // Modify the NPC cryps for game mode settings - let mob_lvl = match mode { - PveMode::Normal => { - team_size = cryps.len(); - cryps.iter().max_by_key(|c| c.lvl).unwrap().lvl - }, - PveMode::Boss => cryps.iter().max_by_key(|c| c.lvl).unwrap().lvl + 2, - }; - - // Generate and return the NPC team based on settings - let mobs = iter::repeat_with(|| generate_mob(mob_lvl).set_account(Uuid::nil())) - .take(team_size) - .collect::>(); - mob_team.set_cryps(mobs); - - return mob_team; - -} - pub fn game_pve_new(cryp_ids: Vec, mode: PveMode, tx: &mut Transaction, account: &Account) -> Result { let cryps = cryp_ids .iter() @@ -948,6 +784,7 @@ mod tests { let x = Cryp::new() .named(&"pronounced \"creeep\"".to_string()) .level(8) + .learn(Skill::Attack) .learn(Skill::TestStun) .learn(Skill::TestTouch) .learn(Skill::TestBlock) @@ -960,6 +797,7 @@ mod tests { let y = Cryp::new() .named(&"lemongrass tea".to_string()) .level(8) + .learn(Skill::Attack) .learn(Skill::TestStun) .learn(Skill::TestTouch) .learn(Skill::TestBlock) @@ -1001,24 +839,28 @@ mod tests { let i = Cryp::new() .named(&"pretaliate".to_string()) .level(8) + .learn(Skill::Attack) .learn(Skill::TestTouch) .create(); let j = Cryp::new() .named(&"poy sian".to_string()) .level(8) + .learn(Skill::Attack) .learn(Skill::TestTouch) .create(); let x = Cryp::new() .named(&"pronounced \"creeep\"".to_string()) .level(8) + .learn(Skill::Attack) .learn(Skill::TestTouch) .create(); let y = Cryp::new() .named(&"lemongrass tea".to_string()) .level(8) + .learn(Skill::Attack) .learn(Skill::TestTouch) .create(); @@ -1113,8 +955,6 @@ mod tests { assert!(game.skill_phase_finished()); game.resolve_phase_start(); - // resolution should have been prevented by KO - // println!("{:#?}", game); assert!(!game.team_by_id(y_team.id).cryps[0].is_stunned()); assert!(game.phase == Phase::Finish); } @@ -1220,18 +1060,20 @@ mod tests { // kill a cryp game.team_by_id(i_team.id).cryp_by_id(i_cryp.id).unwrap().hp.reduce(u64::max_value()); + assert!(game.team_by_id(i_team.id).skills_required() == 1); + assert!(game.team_by_id(x_team.id).skills_required() == 2); + // add some more skills game.add_skill(i_team.id, j_cryp.id, Some(x_cryp.id), Skill::TestTouch).unwrap(); - game.add_skill(x_team.id, x_cryp.id, Some(i_cryp.id), Skill::TestTouch).unwrap(); - game.add_skill(x_team.id, y_cryp.id, Some(i_cryp.id), Skill::TestTouch).unwrap(); + game.add_skill(x_team.id, x_cryp.id, Some(j_cryp.id), Skill::TestTouch).unwrap(); + game.add_skill(x_team.id, y_cryp.id, Some(j_cryp.id), Skill::TestTouch).unwrap(); + assert!(game.add_skill(x_team.id, x_cryp.id, Some(i_cryp.id), Skill::TestTouch).is_err()); assert!(game.skill_phase_finished()); game.resolve_phase_start(); assert!(game.team_by_id(i_team.id).skills_required() == 1); - assert!(game.cryp_targetable(i_team.id, i_cryp.id).is_err()); - assert!(game.cryp_targetable(i_team.id, j_cryp.id).is_ok()); - + assert!(game.team_by_id(x_team.id).skills_required() == 2); return; } } diff --git a/server/src/main.rs b/server/src/main.rs index e5601c00..7180e527 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -27,6 +27,7 @@ mod rpc; mod account; mod item; mod zone; +mod mob; use dotenv::dotenv; use net::{start}; diff --git a/server/src/mob.rs b/server/src/mob.rs new file mode 100644 index 00000000..8594f69b --- /dev/null +++ b/server/src/mob.rs @@ -0,0 +1,152 @@ +use uuid::Uuid; + +use rand::prelude::*; +use rand::distributions::Alphanumeric; +use std::iter; + +use cryp::{Cryp}; +use game::{Team}; +use skill::{Skill}; + +#[derive(Debug,Clone,Serialize,Deserialize)] +pub enum PveMode { + Boss, + Normal, + Zone3v2Attack, + Zone2v2Caster, + Zone3v3MeleeMiniboss, + Zone3v3HealerBoss, +} + +fn generate_mob(lvl: u8) -> Cryp { + let mut rng = thread_rng(); + + let name: String = iter::repeat(()) + .map(|()| rng.sample(Alphanumeric)) + .take(8) + .collect(); + + // rng panics on min == max + // let mob_lvl: u8 = match lvl { + // 1 => 1, + // _ => rng.gen_range(lvl.saturating_sub(2), lvl) + // }; + + return Cryp::new() + .named(&name) + .level(lvl) + .create(); +} + +fn quick_game(mob_lvl: u8, team_size: usize) -> Vec { + iter::repeat_with(|| + generate_mob(mob_lvl) + .set_account(Uuid::nil()) + .learn(Skill::Attack)) + .take(team_size) + .collect::>() +} + +fn zone_3v2_attack(player_lvl: u8) -> Vec { + let x = Cryp::new() + .named(&"hench".to_string()) + .level(player_lvl) + .learn(Skill::Attack) + .create(); + + let y = Cryp::new() + .named(&"bench".to_string()) + .level(player_lvl) + .learn(Skill::Attack) + .create(); + + return vec![x, y]; +} + +fn zone_2v2_caster(player_lvl: u8) -> Vec { + let x = Cryp::new() + .named(&"robe".to_string()) + .level(player_lvl) + .learn(Skill::Blast) + .create(); + + let y = Cryp::new() + .named(&"wizard hat".to_string()) + .level(player_lvl) + .learn(Skill::Blast) + .create(); + + return vec![x, y]; +} + +fn zone_3v3_melee_miniboss(player_lvl: u8) -> Vec { + let x = Cryp::new() + .named(&"jungle juice".to_string()) + .level(player_lvl) + .learn(Skill::Attack) + .create(); + + let y = Cryp::new() + .named(&"bamboo basher".to_string()) + .level(player_lvl) + .learn(Skill::Attack) + .learn(Skill::Stun) + .create(); + + let z = Cryp::new() + .named(&"lemongrass tea".to_string()) + .level(player_lvl) + .learn(Skill::Attack) + .create(); + + return vec![x, y, z]; +} + +fn zone_3v3_healer_boss(player_lvl: u8) -> Vec { + let x = Cryp::new() + .named(&"coinage".to_string()) + .level(player_lvl) + .learn(Skill::Attack) + .learn(Skill::Block) + .create(); + + let y = Cryp::new() + .named(&"wololo".to_string()) + .level(player_lvl + 1) + // big strong + // .learn(Skill::Blast) + .learn(Skill::Heal) + .learn(Skill::Triage) + .create(); + + let z = Cryp::new() + .named(&"quarry".to_string()) + .level(player_lvl) + .learn(Skill::Attack) + .learn(Skill::Stun) + .create(); + + return vec![x, y, z]; +} + + +pub fn generate_mob_team(mode: PveMode, cryps: &Vec) -> Team { + let mut mob_team = Team::new(Uuid::nil()); + + let cryp_lvl = cryps.iter().max_by_key(|c| c.lvl).unwrap().lvl; + let team_size = cryps.len(); + + let mobs = match mode { + PveMode::Normal => quick_game(cryp_lvl, team_size), + PveMode::Boss => quick_game(cryp_lvl + 2, 1), + PveMode::Zone3v2Attack => zone_3v2_attack(cryp_lvl), + PveMode::Zone2v2Caster => zone_2v2_caster(cryp_lvl), + PveMode::Zone3v3MeleeMiniboss => zone_3v3_melee_miniboss(cryp_lvl), + PveMode::Zone3v3HealerBoss => zone_3v3_healer_boss(cryp_lvl), + }; + + mob_team.set_cryps(mobs); + + return mob_team; + +} diff --git a/server/src/rpc.rs b/server/src/rpc.rs index 571a1537..5fca1655 100644 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -16,11 +16,12 @@ use failure::err_msg; use net::Db; use cryp::{Cryp, cryp_spawn, cryp_learn, cryp_forget}; -use game::{Game, PveMode, game_state, game_pve, game_pvp, game_join, game_joinable_list, game_skill}; +use game::{Game, game_state, game_pve, game_pvp, game_join, game_joinable_list, game_skill}; use account::{Account, account_create, account_login, account_from_token, account_cryps, account_zone}; use item::{Item, items_list, item_use}; use skill::{Skill}; use zone::{Zone, zone_create, zone_join, zone_close}; +use mob::{PveMode}; pub struct Rpc; diff --git a/server/src/zone.rs b/server/src/zone.rs index 4866005f..4754ca74 100644 --- a/server/src/zone.rs +++ b/server/src/zone.rs @@ -9,8 +9,9 @@ use postgres::transaction::Transaction; use failure::Error; use failure::err_msg; -use game::{Game, PveMode, game_pve_new, game_write}; +use game::{Game, game_pve_new, game_write}; use rpc::{ZoneJoinParams, ZoneCloseParams}; +use mob::{PveMode}; #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Zone { @@ -140,20 +141,28 @@ pub fn zone_update(zone: &Zone, tx: &mut Transaction) -> Result<(), Error> { pub fn zone_join(params: ZoneJoinParams, tx: &mut Transaction, account: &Account) -> Result { let mut zone = zone_get(tx, params.zone_id)?; + let mut game; // check node joinable node_joinable(&zone.graph, NodeIndex::from(params.node_id))?; - let mut game = game_pve_new(params.cryp_ids, PveMode::Normal, tx, account)?; - game.set_zone(zone.id, params.node_id); - - // borrow zone to update the encounter + // 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() { + "ZONE0" => PveMode::Zone3v2Attack, + "ZONE1" => PveMode::Zone2v2Caster, + "ZONE2" => PveMode::Zone3v3MeleeMiniboss, + "BOSS" => PveMode::Zone3v3HealerBoss, + _ => return Err(err_msg("unknown zone tag")), + }; + game = game_pve_new(params.cryp_ids, mode, tx, account)?; + game.set_zone(zone.id, params.node_id); + encounter.game_id = Some(game.id); }