mob modes

This commit is contained in:
ntr 2019-01-04 01:00:13 +11:00
parent aaf2991a13
commit 9fa2a94ade
7 changed files with 233 additions and 204 deletions

View File

@ -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

View File

@ -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<Skill> {
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<Cryp
pub fn cryp_spawn(params: CrypSpawnParams, tx: &mut Transaction, account: &Account) -> Result<Cryp, Error> {
let cryp = Cryp::new()
.named(&params.name)
.learn(Skill::Attack)
.level(10)
.set_account(account.id)
.create();

View File

@ -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<String>;
#[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("<Target Phase>".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::<Vec<&Cryp>>();
// 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<Cryp>) -> 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::<Vec<Cryp>>();
mob_team.set_cryps(mobs);
return mob_team;
}
pub fn game_pve_new(cryp_ids: Vec<Uuid>, mode: PveMode, tx: &mut Transaction, account: &Account) -> Result<Game, Error> {
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;
}
}

View File

@ -27,6 +27,7 @@ mod rpc;
mod account;
mod item;
mod zone;
mod mob;
use dotenv::dotenv;
use net::{start};

152
server/src/mob.rs Normal file
View File

@ -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<Cryp> {
iter::repeat_with(||
generate_mob(mob_lvl)
.set_account(Uuid::nil())
.learn(Skill::Attack))
.take(team_size)
.collect::<Vec<Cryp>>()
}
fn zone_3v2_attack(player_lvl: u8) -> Vec<Cryp> {
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<Cryp> {
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<Cryp> {
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<Cryp> {
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<Cryp>) -> 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;
}

View File

@ -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;

View File

@ -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<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))?;
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);
}