diff --git a/client/.eslintrc.js b/client/.eslintrc.js index 2970baf5..507d7a3a 100755 --- a/client/.eslintrc.js +++ b/client/.eslintrc.js @@ -616,11 +616,11 @@ module.exports = { avoidQuotes: true, }], - // suggest using arrow functions as callbacks - 'prefer-arrow-callback': ['error', { - allowNamedFunctions: false, - allowUnboundThis: true, - }], + // // suggest using arrow functions as callbacks + // 'prefer-arrow-callback': ['error', { + // allowNamedFunctions: false, + // allowUnboundThis: true, + // }], // suggest using of const declaration for variables that are never modified after declared 'prefer-const': ['error', { diff --git a/client/src/components/game.container.js b/client/src/components/game.container.js index 10009920..8adac3fd 100644 --- a/client/src/components/game.container.js +++ b/client/src/components/game.container.js @@ -10,11 +10,16 @@ const addState = connect( function selectSkillTarget(targetTeamId) { if (activeSkill) { - return ws.sendGameSkill(game.id, activeSkill.crypId, targetTeamId, activeSkill.skill); + return ws.sendGameSkill(game.id, activeSkill.crypId, targetTeamId, activeSkill.skill.skill); } return false; } + // intercept self casting skills + if (activeSkill && activeSkill.skill.self_targeting) { + ws.sendGameSkill(game.id, activeSkill.crypId, null, activeSkill.skill.skill); + } + function selectIncomingTarget(crypId) { if (activeIncoming) { return ws.sendGameTarget(game.id, crypId, activeIncoming); @@ -27,11 +32,11 @@ const addState = connect( function receiveDispatch(dispatch) { function setActiveSkill(crypId, skill) { - dispatch(actions.setActiveSkill(crypId, skill)) + dispatch(actions.setActiveSkill(crypId, skill)); } function setActiveIncoming(skillId) { - dispatch(actions.setActiveIncoming(skillId)) + dispatch(actions.setActiveIncoming(skillId)); } diff --git a/client/src/components/game.jsx b/client/src/components/game.jsx index 66b4b386..f4c2d80e 100755 --- a/client/src/components/game.jsx +++ b/client/src/components/game.jsx @@ -21,7 +21,7 @@ function GamePanel(props) { const playerTeam = game.teams.find(t => t.id === account.id); - const incoming = playerTeam.incoming.map((inc) => { + const incoming = game.stack.filter(s => s.target_team_id === playerTeam.id).map((inc) => { key.unbind('1'); key('1', () => setActiveIncoming(inc.id)); return ( @@ -41,14 +41,14 @@ function GamePanel(props) { const skills = cryp.skills.map((skill, i) => { const hotkey = SKILL_HOT_KEYS[i]; key.unbind(hotkey); - key(hotkey, () => setActiveSkill(cryp.id, skill.skill)); + key(hotkey, () => setActiveSkill(cryp.id, skill)); return ( @@ -59,7 +59,6 @@ function GamePanel(props) {
{status} for {status.turns}T
)); - if (activeIncoming) console.log('should be a pointer'); return (
block (sp 10) -> on self +1.2 -> attack (sp 5) -> on team 2 -## Items +2.1 -> hex (sp 3) -> on team 1 +2.2 -> attack (sp 5) -> on team 1 -## Rolling -stat & rng -block hash or totally random -roll server that prints a roll every second? +target phase: +team 2 targets 1.2 on 2.2 -friendship on ties? +team 1 targets 2.1 on 1.1 +team 1 targets 2.2 on 1.1 -Def -0001011011001100110101000000101111100110101111110100100001000001 - roll -0000000000000000000000000000000000000000000000000000000000001111 ^ steel armour -0000000000000000000000000000000000000000000000000000001111000000 ^ stoney trait -0001011011001100110101000000101111100110101111110100101111001111 = modified roll -0000000000000000000000000000000000000000000000000000000111000010 & def attribute -0000000000000000000000000000000000000000000000000000000111000010 = roll w/ stats - -0000000000000000000000000000000000000000000000000000000001000000 = roll w/out stats - - -## missions -also the idea is like -the currency is kinda like path right -you're trying to get like chaos -to reroll some stat -or some item -so maybe like a sword -has 5 bits of damage it can guarantee -but a badman-sabre has 16 bits -and you keep blowing chaos on it til it gets 1111111111 -MashyToday at 9:04 PM -yeah that would be cool -natureToday at 9:05 PM -i feel like that would make it kinda p2w -so probably needs limits -MashyToday at 9:05 PM -I was thinking the p2w more just like making the needs and missions quicker right -so like instead of feeding your dude you get some item where hes fed for 2 days or something -or you could reduce the mission time which is like kinda pay to win but not really -natureToday at 9:06 PM -well what do the missions give you -MashyToday at 9:06 PM -well thats what i was thinking you'd do to get items -so instead of doing a zone in an rpg and getting all this loot -you send your dude out and you got a notification 4 hours later like success / failure this is what you got back -natureToday at 9:07 PM -i was thinking aobut that -i don't really like the possibility of failure on a mission -imagine sending your dude out on a mission for 2 days and it ceoms back like "nope wrong" -i'd just fucken rq -MashyToday at 9:08 PM -Yeah -natureToday at 9:08 PM -BUT -a better thing is like -playing off crypto -MashyToday at 9:08 PM -its something like this https://www.youtube.com/watch?v=bXLW9dF7LN8 -YouTube -Tommy J -WoW: Garrison Mission Chance - How it Works - -natureToday at 9:08 PM -missions are like -MashyToday at 9:08 PM -so you get told before hand the % chance for success -natureToday at 9:08 PM -go and do 2000 overkill damage to some monster -and your dude is out there using his items and rolling -so it can happen quick if you're lucky -or slow if you're not -but still will eventually happen -and then you get like easy limits for missions right -MashyToday at 9:09 PM -yeah that would be better actually -natureToday at 9:09 PM -like a creep that onl deals 100 dmg can't finish that mission -i feel like that would be good -cause then if you have like a defensive cryp -it could have a defensive kinda mission -like go and block 40000 damage in pve -and then it gets some baller shield -and is like speccing into defense -MashyToday at 9:10 PM -sounds better -natureToday at 9:10 PM -and like an offensive cryp could do that too -but it might keep getting KOd -and you hvae to pay to revive it \ No newline at end of file +resolve phase: +1.1 <- block +1.1 <- attack (no effect because of block) +2.2 <- attack (normal resolve) +1.1 <- hexed (no skills for the rest of this turn and next) diff --git a/server/WORKLOG.md b/server/WORKLOG.md index a82abd4b..9b68c58e 100755 --- a/server/WORKLOG.md +++ b/server/WORKLOG.md @@ -4,30 +4,20 @@ * Prove something # WORK WORK -* QOL - * auto login - * cryp list on spawn - * ws reconnect ✔ -* Levelling ✔ +* move rpc functions out + * unwrap account for all functions except list * Global rolls -* Logins ✔️ - * Cryp Ownership ✔ - -* Matchmaking - * Lobbies - * Create - * Join - * Resolve - * Stats * Scrabble grid * skills * offensive -> choose target ✔ * private fields for opponents + * cooldowns reduce each turn ✔ + * statuses reduce each turn * teach cyps skills * can you attack yourself? * fetch existing battles @@ -50,6 +40,17 @@ * Cryp Generation * +* ws reconnect ✔ +* Levelling ✔ +* Logins ✔️ + * Cryp Ownership ✔ +* Matchmaking ✔ + * Lobbies ✔ + * Create ✔ + * Join ✔ + * Resolve ✔ + + # Db maintenance * delete games when a cryp is deleted * does this need to happen? can have historical games @@ -112,4 +113,25 @@ gem td style attr combinations * 17: You don't have to change much to change everything * 18: Restrictions breed creativity * 19: Your audience is good at recognizing problems and bad at solving them -* 20: All the lessons connect \ No newline at end of file +* 20: All the lessons connect + + + +skill phase: +1.1 -> block (sp 10) -> on self +1.2 -> attack (sp 5) -> on team 2 + +2.1 -> hex (sp 3) -> on team 1 +2.2 -> attack (sp 5) -> on team 1 + +target phase: +team 2 targets 1.2 on 2.2 + +team 1 targets 2.1 on 1.1 +team 1 targets 2.2 on 1.1 + +resolve phase: +1.1 <- block +1.1 <- attack (no effect because of block) +2.2 <- attack (normal resolve) +1.1 <- hexed (no skills for the rest of this turn and next) diff --git a/server/src/cryp.rs b/server/src/cryp.rs index dd018cea..da8fef4c 100755 --- a/server/src/cryp.rs +++ b/server/src/cryp.rs @@ -9,50 +9,36 @@ use failure::err_msg; use account::Account; use rpc::{CrypSpawnParams}; -use skill::{Skill, Roll}; - -type Cooldown = Option; +use skill::{Skill, Cooldown, Roll}; #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub struct CrypSkill { - skill: Skill, - cd: Cooldown, + pub skill: Skill, + pub self_targeting: bool, + pub cd: Cooldown, } impl CrypSkill { pub fn new(skill: Skill) -> CrypSkill { - let turns = match skill { - Skill::Attack => None, - Skill::Block => Some(1), - Skill::Dodge => Some(1), - Skill::Heal => Some(2), - Skill::Stun => None, - }; - CrypSkill { skill, - cd: turns, + self_targeting: skill.self_targeting(), + cd: skill.cd(), } } - - fn can_cast(&self, cryp: &Cryp) -> bool { - if cryp.is_stunned() { - return false; - } - true - } } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum Status { Stunned, Silenced, + Blocking, } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub struct CrypStatus { status: Status, - turns: u8, + duration: u8, } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] @@ -183,19 +169,57 @@ impl Cryp { } pub fn available_skills(&self) -> Vec<&CrypSkill> { - self.skills.iter().filter(|s| s.can_cast(self)).collect() + self.skills.iter().filter(|s| s.skill.castable(self)).collect() } pub fn knows(&self, skill: Skill) -> bool { self.skills.iter().any(|s| s.skill == skill) } - pub fn skill_on_cd(&self, skill: Skill) -> bool { - self.skills.iter().any(|s| s.skill == skill && s.cd.is_some()) + pub fn skill_on_cd(&self, skill: Skill) -> Option<&CrypSkill> { + self.skills.iter().find(|s| s.skill == skill && s.cd.is_some()) } - pub fn skill_can_cast(&self, skill: Skill) -> bool { - self.skills.iter().any(|s| s.skill == skill && !s.can_cast(self)) + pub fn skill_set_cd(&mut self, skill: Skill) -> &mut Cryp { + let i = self.skills.iter().position(|s| s.skill == skill).unwrap(); + self.skills.remove(i); + self.skills.push(CrypSkill::new(skill)); + + self + } + + pub fn reduce_cooldowns(&mut self) -> &mut Cryp { + for skill in self.skills.iter_mut() { + // if used cooldown + if let Some(cd) = skill.skill.cd() { + // if the cd is 1 it becomes none + if cd == 1 { + println!("{:?} is now off cd", skill.skill); + skill.cd = None; + continue; + } + + // otherwise decrement it + skill.cd = Some(cd.saturating_sub(1)); + } + } + + self + } + + pub fn reduce_statuses(&mut self) -> &mut Cryp { + self.statuses = self.statuses.clone().into_iter().filter_map(|mut s| { + s.duration = s.duration.saturating_sub(1); + + if s.duration == 0 { + return None; + } + + println!("reduced status {:?}", s); + return Some(s); + }).collect::>(); + + self } pub fn rez(&mut self) -> &mut Cryp { @@ -207,20 +231,10 @@ impl Cryp { let mut rng = thread_rng(); let base: u64 = rng.gen(); - let stat = match skill { - Skill::Attack => self.str, - Skill::Block => self.str, - Skill::Stun => self.str, - Skill::Dodge => self.agi, - Skill::Heal => self.int, - }; + let stat = skill.stat(self); let mut roll = Roll { base, result: base }; - // // apply skills - // roll = c.skills.iter().fold(roll, |roll, s| s.apply(roll)); - - // finally combine with stat println!("{:?}'s stats", self.name); println!("{:064b} <- finalised", roll.result); roll.result = roll.result & stat.value; @@ -232,8 +246,10 @@ impl Cryp { return roll; } - pub fn stun(&mut self, roll: Roll) -> &mut Cryp { - self.statuses.push(CrypStatus { status: Status::Stunned, turns: 1 }); + pub fn stun(&mut self, _roll: Roll) -> &mut Cryp { + if !self.statuses.iter().any(|s| s.status == Status::Blocking) { + self.statuses.push(CrypStatus { status: Status::Stunned, duration: Skill::Stun.duration() }); + } self } @@ -242,6 +258,11 @@ impl Cryp { self } + pub fn block(&mut self, _roll: Roll) -> &mut Cryp { + self.statuses.push(CrypStatus { status: Status::Blocking, duration: Skill::Block.duration() }); + self + } + } pub fn cryp_get(tx: &mut Transaction, id: Uuid, account_id: Uuid) -> Result { @@ -267,6 +288,7 @@ pub fn cryp_spawn(params: CrypSpawnParams, tx: &mut Transaction, account: &Accou .named(¶ms.name) .level(10) .learn(Skill::Stun) + .learn(Skill::Block) .set_account(account.id) .create(); diff --git a/server/src/game.rs b/server/src/game.rs index 413484ec..728dd219 100755 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -16,8 +16,6 @@ use skill::{Skill, Cast}; pub struct Team { id: Uuid, cryps: Vec, - skills: Vec, - incoming: Vec, } impl Team { @@ -25,8 +23,6 @@ impl Team { return Team { id: account, cryps: vec![], - skills: vec![], - incoming: vec![], }; } @@ -44,13 +40,6 @@ impl Team { pub fn cryp_by_id(&mut self, id: Uuid) -> Option<&mut Cryp> { self.cryps.iter_mut().find(|c| c.id == id) } - - pub fn cast_by_id(&mut self, id: Uuid) -> &mut Cast { - match self.incoming.iter_mut().find(|a| a.id == id) { - Some(a) => a, - None => panic!("abiltity not in game"), - } - } } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] @@ -70,6 +59,8 @@ pub struct Game { pub teams: Vec, pub is_pve: bool, pub phase: Phase, + pub stack: Vec, + pub resolved: Vec, pub log: Vec, } @@ -82,6 +73,8 @@ impl Game { teams: vec![], is_pve: true, phase: Phase::Start, + stack: vec![], + resolved: vec![], log: vec![], }; } @@ -119,35 +112,25 @@ impl Game { } } - fn cryp_by_id(&mut self, id: Uuid) -> &mut Cryp { - for team in self.teams.iter_mut() { - let in_team = team.cryp_by_id(id); - if in_team.is_some() { - return in_team.unwrap(); - } - } - panic!("cryp not in game"); + fn cryp_by_id(&mut self, id: Uuid) -> Option<&mut Cryp> { + match self.teams.iter_mut().find(|t| t.cryps.iter().any(|c| c.id == id)) { + Some(team) => { + return team.cryps.iter_mut().find(|c| c.id == id); + }, + None => panic!("cryp not in game"), + }; } - // fn update_team(&mut self, updated: Team) -> &mut Game { - // match self.teams.iter().position(|t| t.id == updated.id) { - // Some(index) => { - // self.teams.remove(index); - // self.teams.push(updated); - // self - // } - // None => panic!("team not in game"), - // } - // } - - fn update_cryp(&mut self, updated: Cryp) -> &mut Game { - for team in self.teams.iter_mut() { - if let Some(index) = team.cryps.iter().position(|c| c.id == updated.id) { + fn update_cryp(&mut self, cryp: &mut Cryp) -> &mut Game { + match self.teams.iter_mut().find(|t| t.cryps.iter().any(|c| c.id == cryp.id)) { + Some(team) => { + let index = team.cryps.iter().position(|t| t.id == cryp.id).unwrap(); team.cryps.remove(index); - team.cryps.push(updated); - break; - } - } + team.cryps.push(cryp.clone()); + }, + None => panic!("cryp not in game"), + }; + self } @@ -166,10 +149,8 @@ impl Game { } self.phase = Phase::Skill; - for team in self.teams.iter_mut() { - team.skills.clear(); - team.incoming.clear(); - } + + self.stack.clear(); if self.is_pve { self.pve_add_skills(); @@ -181,13 +162,13 @@ impl Game { fn pve_add_skills(&mut self) -> &mut Game { { let mob_team_id = Uuid::nil(); - let teams = self.teams.clone(); let mobs = self.team_by_id(mob_team_id).clone(); + // TODO attack multiple players based on some criteria - let player_team = teams.iter().find(|t| t.id != mob_team_id).unwrap(); + let player_team_id = self.teams.iter().find(|t| t.id != mob_team_id).unwrap().id; for mob in &mobs.cryps { // doesn't matter if the cryp can't cast - self.add_skill(mob_team_id, mob.id, player_team.id, Skill::Attack).ok(); + self.add_skill(mob_team_id, mob.id, Some(player_team_id), Skill::Attack).ok(); } } @@ -196,15 +177,13 @@ impl Game { // skills can target any team, but we have to check if the caller is the owner of the cryp // and that the cryp has the skill they are trying to add - fn add_skill(&mut self, team_id: Uuid, cryp_id: Uuid, target_team_id: Uuid, skill: Skill) -> Result { + fn add_skill(&mut self, team_id: Uuid, source_cryp_id: Uuid, target_team_id: Option, skill: Skill) -> Result { if self.phase != Phase::Skill { return Err(err_msg("game not in skill phase")); } - let team = self.team_by_id(team_id); - { - let cryp = match team.cryp_by_id(cryp_id) { + let cryp = match self.cryp_by_id(source_cryp_id) { Some(c) => c, None => return Err(err_msg("cryp not in team")), }; @@ -214,48 +193,57 @@ impl Game { return Err(err_msg("cryp does not have that skill")); } - if cryp.skill_on_cd(skill) { + if cryp.skill_on_cd(skill).is_some() { return Err(err_msg("abiltity on cooldown")); } - if cryp.skill_can_cast(skill) { + if skill.self_targeting() && target_team_id.is_some() { + return Err(err_msg("skill is self targeting")); + } + + if !skill.self_targeting() && target_team_id.is_none() { + return Err(err_msg("skill requires a target")); + } + + // check here as well so uncastable spells don't go on the stack + if !skill.castable(&cryp) { return Err(err_msg("cryp cannot cast spell")); } } + // replace cryp skill - if let Some(s) = team.skills.iter_mut().position(|s| s.cryp_id == cryp_id) { - team.skills.remove(s); + if let Some(s) = self.stack.iter_mut().position(|s| s.source_cryp_id == source_cryp_id) { + self.stack.remove(s); } - let skill = Cast::new(cryp_id, target_team_id, skill); - team.skills.push(skill); + let skill = Cast::new(source_cryp_id, team_id, target_team_id, skill); + self.stack.push(skill); return Ok(skill.id); } fn skill_phase_finished(&self) -> bool { - self.teams.iter().all(|t| t.skills.len() == t.skills_required()) + self.teams.iter() + // for every team + .all(|t| self.stack.iter() + // the number of skills they have cast + .filter(|s| s.source_team_id == t.id) + .collect::>() + // should equal the number required this turn + .len() == t.skills_required() + ) } // move all skills into their target team's targets list fn target_phase_start(&mut self) -> &mut Game { + assert!(self.skill_phase_finished()); if self.phase != Phase::Skill { panic!("game not in skill phase"); } self.phase = Phase::Target; - // have to clone the teams because we change them while iterating - let teams = self.teams.clone(); - - for mut team in teams { - for skill in team.skills.iter_mut() { - let target_team = self.team_by_id(skill.target_team_id); - target_team.incoming.push(*skill); - } - } - if self.is_pve { self.pve_add_targets(); } @@ -273,33 +261,52 @@ 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 - for incoming in &mobs.incoming { - self.add_target(mob_team_id, mobs.cryps[0].id, incoming.id).unwrap(); - } + for incoming_skill_id in self.stack.clone().iter() + .filter(|s| s.target_cryp_id.is_none() && s.target_team_id == mob_team_id) + .map(|s| s.id) { + self.add_target(mob_team_id, mobs.cryps[0].id, incoming_skill_id).unwrap(); + } } self } - // targets can only be added by the owner of the team - fn add_target(&mut self, team_id: Uuid, cryp_id: Uuid, cast_id: Uuid) -> Result<&mut Cast, Error> { - // whose team is this? - let team = self.team_by_id(team_id); + 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")); + } - // is the target in the team? - match team.cryp_by_id(cryp_id) { - Some(c) => c, - None => return Err(err_msg("cryp not in team")), - }; + { + // whose team is this? + let team = self.team_by_id(team_id); + + // is the target in the team? + match team.cryp_by_id(cryp_id) { + Some(c) => c, + None => return Err(err_msg("cryp not in team")), + }; + } // set the target - let cast = team.cast_by_id(cast_id); + 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.teams.iter().all(|t| t.incoming.iter().all(|i| i.target_cryp_id.is_some())) + self.stack.iter().all(|s| s.target_cryp_id.is_some()) } // requires no input @@ -327,20 +334,58 @@ impl Game { panic!("game not in damage phase"); } - // sometimes... you just gotta - for team in self.teams.clone().iter_mut() { - for incoming in team.incoming.clone().iter_mut() { - // they better fuckin be there - let mut cryp = self.cryp_by_id(incoming.cryp_id).clone(); - let mut target_cryp = self.cryp_by_id(incoming.target_cryp_id.unwrap()).clone(); - println!("{:?} is attacking {:?}", cryp.name, target_cryp.name); - incoming.resolve(&mut cryp, &mut target_cryp); - self.update_cryp(target_cryp); - } - } + self.stack.sort_unstable_by_key(|s| s.skill.speed()); + self.stack.reverse(); + + // update the stack with the resolved skills + self.stack = self.stack.clone().iter_mut().map(|skill| { + let mut source = self.cryp_by_id(skill.source_cryp_id).unwrap().clone(); + let mut target = self.cryp_by_id(skill.target_cryp_id.unwrap()).unwrap().clone(); + + let resolution = skill.resolve(&mut source, &mut target); + self.resolved.push(*resolution); + + self.update_cryp(&mut source); + self.update_cryp(&mut target); + + return *resolution; + }).collect::>(); + + // now damage has all been assigned + // handle cooldowns and statuses + self.progress_durations(); + self } + fn progress_durations(&mut self) -> &mut Game { + // do it once for every cryp + for mut cryp in self.stack.clone().iter() + .map(|s| self.cryp_by_id(s.source_cryp_id).unwrap().clone()) + .collect::>() { + + println!("progressing durations for {:?}", cryp.name); + + // only reduce cooldowns if no cd was used + // have to borrow self for the skill check + { + let skill = self.stack.iter_mut().find(|s| s.source_cryp_id == cryp.id).unwrap(); + if skill.used_cooldown() { + cryp.skill_set_cd(skill.skill); + } else { + cryp.reduce_cooldowns(); + } + } + + // always reduce durations + cryp.reduce_statuses(); + self.update_cryp(&mut cryp); + } + + self + } + + fn is_finished(&self) -> bool { self.teams.iter().any(|t| t.cryps.iter().all(|c| c.is_ko())) } @@ -348,10 +393,7 @@ impl Game { fn finish(&mut self) -> &mut Game { self.phase = Phase::Finish; - for team in self.teams.iter_mut() { - team.skills.clear(); - team.incoming.clear(); - } + self.stack.clear(); self } @@ -376,6 +418,10 @@ pub fn game_skill(params: GameSkillParams, tx: &mut Transaction, account: &Accou let game_bytes: Vec = returned.get("data"); let mut game = from_slice::(&game_bytes)?; + if game.phase != Phase::Skill { + return Err(err_msg("game not in skill phase")) + } + game.add_skill(account.id, params.cryp_id, params.target_team_id, params.skill)?; if game.skill_phase_finished() { @@ -406,7 +452,7 @@ pub fn game_target(params: GameTargetParams, tx: &mut Transaction, account: &Acc let game_bytes: Vec = returned.get("data"); let mut game = from_slice::(&game_bytes)?; - game.add_target(account.id, params.cryp_id, params.cast_id)?; + game.add_target(account.id, params.cryp_id, params.skill_id)?; if game.target_phase_finished() { game.damage_phase_start(); @@ -513,7 +559,7 @@ fn generate_mob(plr: &Cryp) -> Cryp { // rng panics on min == max let mob_lvl: u8 = match plr.lvl { 1 => 1, - _ => rng.gen_range(1, plr.lvl) + _ => rng.gen_range(plr.lvl.saturating_sub(2), plr.lvl) }; return Cryp::new() @@ -638,22 +684,25 @@ mod tests { use game::*; use cryp::*; - #[test] - fn game_test() { + fn create_test_game() -> Game { let x = Cryp::new() .named(&"pronounced \"creeep\"".to_string()) .level(8) + .learn(Skill::TestStun) + .learn(Skill::TestTouch) + .learn(Skill::TestBlock) + .learn(Skill::Block) .create(); - let x_id = x.id; - let y = Cryp::new() .named(&"lemongrass tea".to_string()) .level(8) + .learn(Skill::TestStun) + .learn(Skill::TestTouch) + .learn(Skill::TestBlock) + .learn(Skill::Block) .create(); - let y_id = y.id; - let mut game = Game::new(); game @@ -679,17 +728,28 @@ mod tests { game.start(); - let x_attack_id = game.add_skill(x_team_id, x_id, y_team_id, Skill::Attack); - let y_attack_id = game.add_skill(y_team_id, y_id, x_team_id, Skill::Attack); + return game; + } + + #[test] + fn phase_test() { + let mut game = create_test_game(); + + let x_team = game.teams[0].clone(); + let y_team = game.teams[1].clone(); + + let x_cryp = x_team.cryps[0].clone(); + let y_cryp = y_team.cryps[0].clone(); + + let x_attack_id = game.add_skill(x_team.id, x_cryp.id, Some(y_team.id), Skill::Attack).unwrap(); + let y_attack_id = game.add_skill(y_team.id, y_cryp.id, Some(x_team.id), Skill::Attack).unwrap(); assert!(game.skill_phase_finished()); game.target_phase_start(); - println!("{:?}", game); - - game.add_target(x_team_id, x_id, y_attack_id.unwrap()).unwrap(); - game.add_target(y_team_id, y_id, x_attack_id.unwrap()).unwrap(); + game.add_target(x_team.id, x_cryp.id, y_attack_id).unwrap(); + game.add_target(y_team.id, y_cryp.id, x_attack_id).unwrap(); assert!(game.target_phase_finished()); @@ -697,8 +757,108 @@ mod tests { assert!([Phase::Skill, Phase::Finish].contains(&game.phase)); - println!("{:?}", game); - return; } + + #[test] + fn stun_test() { + let mut game = create_test_game(); + + let x_team = game.teams[0].clone(); + let y_team = game.teams[1].clone(); + + let x_cryp = x_team.cryps[0].clone(); + let y_cryp = y_team.cryps[0].clone(); + + let x_stun_id = game.add_skill(x_team.id, x_cryp.id, Some(y_team.id), Skill::TestStun).unwrap(); + let y_attack_id = game.add_skill(y_team.id, y_cryp.id, Some(x_team.id), Skill::TestTouch).unwrap(); + + assert!(game.skill_phase_finished()); + game.target_phase_start(); + + game.add_target(x_team.id, x_cryp.id, y_attack_id).unwrap(); + game.add_target(y_team.id, y_cryp.id, x_stun_id).unwrap(); + + assert!(game.target_phase_finished()); + game.damage_phase_start(); + + // should auto progress back to skill phase + assert!(game.phase == Phase::Skill); + + println!("{:#?}", game); + assert!(game.team_by_id(y_team.id).cryps[0].is_stunned()); + assert!(game.team_by_id(y_team.id).skills_required() == 0); + } + + #[test] + fn cooldown_test() { + let mut game = create_test_game(); + + let x_team = game.teams[0].clone(); + let y_team = game.teams[1].clone(); + + let x_cryp = x_team.cryps[0].clone(); + let y_cryp = y_team.cryps[0].clone(); + + let x_stun_id = game.add_skill(x_team.id, x_cryp.id, Some(y_team.id), Skill::TestTouch).unwrap(); + let y_attack_id = game.add_skill(y_team.id, y_cryp.id, Some(x_team.id), Skill::TestTouch).unwrap(); + + game.target_phase_start(); + game.add_target(x_team.id, x_cryp.id, y_attack_id).unwrap(); + game.add_target(y_team.id, y_cryp.id, x_stun_id).unwrap(); + + game.damage_phase_start(); + + // should auto progress back to skill phase + assert!(game.phase == Phase::Skill); + + // after 1 turn block should be off cooldown + assert!(game.team_by_id(y_team.id).cryps[0].skill_on_cd(Skill::Block).is_none()); + + // second round + // now we block and it should go back on cd + let _x_block_id = game.add_skill(x_team.id, x_cryp.id, None, Skill::Block).unwrap(); + let _y_block_id = game.add_skill(y_team.id, y_cryp.id, None, Skill::Block).unwrap(); + + game.target_phase_start(); + // game.add_target(x_team.id, x_cryp.id, y_block_id).unwrap(); + // game.add_target(y_team.id, y_cryp.id, x_block_id).unwrap(); + // game.damage_phase_start(); + + assert!(game.team_by_id(y_team.id).cryps[0].skill_on_cd(Skill::Block).is_some()); + assert!(game.team_by_id(x_team.id).cryps[0].skill_on_cd(Skill::Block).is_some()); + } + + #[test] + fn block_test() { + let mut game = create_test_game(); + + let x_team = game.teams[0].clone(); + let y_team = game.teams[1].clone(); + + let x_cryp = x_team.cryps[0].clone(); + let y_cryp = y_team.cryps[0].clone(); + + // ensure that you can't pass a target for a block + assert!(game.add_skill(x_team.id, x_cryp.id, Some(y_cryp.id), Skill::TestBlock).is_err()); + + let x_block_id = game.add_skill(x_team.id, x_cryp.id, None, Skill::TestBlock).unwrap(); + let y_attack_id = game.add_skill(y_team.id, y_cryp.id, Some(x_team.id), Skill::TestStun).unwrap(); + + game.target_phase_start(); + + // ensure you can't target a self targeting skill + assert!(game.add_target(y_team.id, y_cryp.id, x_block_id).is_err()); + + // ensure you can't target another team's skills + assert!(game.add_target(x_team.id, y_cryp.id, y_attack_id).is_err()); + + game.add_target(x_team.id, x_cryp.id, y_attack_id).unwrap(); + + game.damage_phase_start(); + + // should not be stunned because of block + assert!(game.team_by_id(x_team.id).cryps[0].is_stunned() == false); + } + } diff --git a/server/src/rpc.rs b/server/src/rpc.rs index a7b3b08a..2bf60067 100755 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -355,7 +355,7 @@ struct GameTargetMsg { pub struct GameTargetParams { pub game_id: Uuid, pub cryp_id: Uuid, - pub cast_id: Uuid, + pub skill_id: Uuid, } #[derive(Debug,Clone,Serialize,Deserialize)] @@ -368,7 +368,7 @@ struct GameSkillMsg { pub struct GameSkillParams { pub game_id: Uuid, pub cryp_id: Uuid, - pub target_team_id: Uuid, + pub target_team_id: Option, pub skill: Skill, } diff --git a/server/src/skill.rs b/server/src/skill.rs index ab9b5953..63e710f9 100755 --- a/server/src/skill.rs +++ b/server/src/skill.rs @@ -1,7 +1,7 @@ -use rand::prelude::*; +// use rand::prelude::*; use uuid::Uuid; -use cryp::{Cryp}; +use cryp::{Cryp, CrypSkill, CrypStat}; #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub struct Roll { @@ -9,6 +9,8 @@ pub struct Roll { pub result: u64, } +pub type Cooldown = Option; + #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum Skill { Attack, @@ -16,6 +18,80 @@ pub enum Skill { Heal, Stun, Dodge, + // used by tests, no cd, no dmg + TestTouch, + TestStun, + TestBlock, +} + +impl Skill { + pub fn cd(&self) -> Cooldown { + match self { + Skill::Attack => None, + Skill::Block => Some(1), + Skill::Dodge => Some(1), + Skill::Heal => Some(2), + Skill::Stun => Some(2), + Skill::TestTouch => None, + Skill::TestStun => None, + Skill::TestBlock => None, + } + } + + pub fn speed(&self) -> u8 { + match self { + Skill::Attack => 10, + Skill::Block => 5, + Skill::Dodge => 5, + Skill::Heal => 2, + Skill::Stun => 2, + Skill::TestTouch => 10, + Skill::TestStun => 2, + Skill::TestBlock => 5, + } + } + + pub fn stat(&self, cryp: &Cryp) -> CrypStat { + match self { + Skill::Attack => cryp.str, + Skill::Block => cryp.str, + Skill::Stun => cryp.str, + Skill::Dodge => cryp.agi, + Skill::Heal => cryp.int, + + // test skills + Skill::TestTouch => cryp.int, + Skill::TestStun => cryp.str, + Skill::TestBlock => cryp.str, + } + } + + pub fn duration(&self) -> u8 { + match self { + Skill::Dodge => 1, + Skill::Stun => 2, + Skill::Block => 1, + + Skill::TestBlock => 1, + Skill::TestStun => 2, + _ => panic!("{:?} does not have a duration", self), + } + } + + pub fn self_targeting(&self) -> bool { + match self { + Skill::Block => true, + Skill::TestBlock => true, + _ => false, + } + } + + pub fn castable(&self, cryp: &Cryp) -> bool { + if cryp.is_stunned() { + return false; + } + true + } } @@ -23,31 +99,54 @@ pub enum Skill { pub struct Cast { pub id: Uuid, pub skill: Skill, - pub cryp_id: Uuid, + pub source_team_id: Uuid, + pub source_cryp_id: Uuid, pub target_cryp_id: Option, pub target_team_id: Uuid, + pub roll: Option, } impl Cast { - pub fn new(cryp_id: Uuid, target_team_id: Uuid, skill: Skill) -> Cast { + pub fn new(source_cryp_id: Uuid, source_team_id: Uuid, target_team_id: Option, skill: Skill) -> Cast { + + 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()) + }; + return Cast { id: Uuid::new_v4(), - cryp_id, - target_cryp_id: None, + source_cryp_id, + source_team_id, + target_cryp_id, target_team_id, skill, + roll: None, }; } pub fn resolve(&mut self, cryp: &mut Cryp, target: &mut Cryp) -> &mut Cast { let roll = cryp.roll(self.skill); + println!("{:?} -> {:?} -> {:?}", cryp.name, self.skill, target.name); + match self.skill { + // the real deal Skill::Stun => target.stun(roll), - _ => target.attack(roll), + Skill::Attack => target.attack(roll), + Skill::Block => target.block(roll), + Skill::Heal => target, + Skill::Dodge => target, + + // Test Skills + Skill::TestStun => target.stun(roll), + Skill::TestBlock => target.block(roll), + Skill::TestTouch => target, }; - println!("{:?} gettin clapped for {:?}", target.name, roll.result); + // println!("{:?} gettin clapped for {:?}", target.name, roll.result); + + self.roll = Some(roll); self } @@ -55,6 +154,11 @@ impl Cast { self.target_cryp_id = Some(cryp_id); self } + + pub fn used_cooldown(self) -> bool { + let cs = CrypSkill::new(self.skill); + return cs.cd.is_some(); + } } // #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]