diff --git a/client/src/components/game.jsx b/client/src/components/game.jsx index 71eeea98..82b8f954 100755 --- a/client/src/components/game.jsx +++ b/client/src/components/game.jsx @@ -155,6 +155,9 @@ function GamePanel(props) { return 'Game over'; } } + + const logs = game.log.reverse().map((l, i) => (
{l}
)); + return (
@@ -172,7 +175,7 @@ function GamePanel(props) {
-
log
+
{logs}
); diff --git a/server/README.md b/server/README.md index 07b9e5a4..dedf97d6 100755 --- a/server/README.md +++ b/server/README.md @@ -1,6 +1,6 @@ -# Cryps ("creeps") // Creeptography +# Cryps ("creeps") -## Setup +## Combat skill phase: 1.1 -> block (sp 10) -> on self @@ -20,3 +20,127 @@ resolve phase: 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) + +## Dmg Chart + +| Physical | Magic | Modifiers | +| ------ | ------ | ------ | +| dmg | dmg | speed | +| evasion | resistance | cooldowns | +| reduction | absorption? | durations | + + +## Cryp Classes / Paths / Specialisations + +Organic / Combat +================ +Strength of the Individual / Attack & Defense +-------------------------- + +from viruses and bacteria to civilisations all organic cryps are contained within this group. +they are the original form of cryps to inhabit the universe. +they value individual strength and the ability to defend one's self. +having undergone natural selection they are combative by nature and feel threatened from all sides. +magic and advanced technology disturbs them as they are unable to understand it; +their response is to try and crush it and restore their place at the apex. +their fear is a manifestation of the emotions that are unique to organic cryps. +can be social and cooperative (primates) as well as isolated and agressive (wolves / snow leopards) + +* tactics and strategy + * rally + * physical damage + * rend / expose + * taunt +* martial arts and combat + * blocking + * evasion and redirection +* frenzy + +Artificial / Machines +===================== +Speed & Efficiency +------------------ + +artificial cryps are machines of any sort, from simple spring powered devices to vast self-aware networks. +whether created by organic cryps as tools or as ancient automated systems discovered deep in space, +artificial cryps are ubiquitous. +their artificial nature lends them to efficiency and instant reactions. +artificial cryps do not think, they simply act. +no motivation / emotions; just outcomes + +* efficiency + * reduced cooldowns + * increased speed +* replication + * drones / tokens +* technology + * information and analysis (other teams' makeup etc) + +Life / Vitality +=============== +Enhancement & Preservation +-------------------------- + +life based cryps value the sanctity of life above all else +they are defensive and gracious, seeking to minimise the damage done by others. +healing cryps they have allied themselves with, preventing incoming damage and rendering enemies harmless + +* healing + * hots + * direct healing +* buffs + * protection from effects + * damage reduction + +Death / Destruction +=================== +Damage & Destruction +------------------------- + +cryps that have given themselves over to death are obsessed with destruction in all of its forms +no price is too high, death centred cryps will gladly harm themselves and amplify this sacrifice +in the hopes of inflicting ruin on everything around them. + +* damage amplification +* nukes +* life leach +* life exchange +* poison + +Form / Matter +============= +Structure & Control +------------------- + +form focused cryps both enhance and exploit everything around them, +seeking to improve their allies abilities and expose weaknesses in their opponents, +they are builders and breakers, chasing perfection + +* Dispel, removal +* + +Void +===== +Power from an alternate reality +------------------------------- + +The void cryps originally entered into our universe from alternate reality. +their physical form is embedded with advanced technologies which allows manipulation of time and space. +the void cryps have been evolving in form since their arrival into the universe + +they value power over all else, their goal is to convert all sentient life into their void form + +manipulate physical reality to control and disrupt the flow of battle + +can take physical and astral forms + +* Banish +* Shadow damage +* Time control (reverse turn outcomes) +* increase cooldowns +* increase durations +* Slow + +## Styles +* Aztec +* Yokai / ukiyo-e \ No newline at end of file diff --git a/server/WORKLOG.md b/server/WORKLOG.md index 9b68c58e..7366c9bd 100755 --- a/server/WORKLOG.md +++ b/server/WORKLOG.md @@ -8,6 +8,8 @@ * move rpc functions out * unwrap account for all functions except list +* handle unserializable cryps + * Global rolls * Stats @@ -57,6 +59,11 @@ * run nginx as not root +# Art Styles +* Aztec +* Pixel +* Industrial + # Mechanic Ideas teams 1v1 2v2 3v3 @@ -89,6 +96,7 @@ gem td style attr combinations techno artists for the soundtrack + slimey ghostly @@ -114,24 +122,3 @@ gem td style attr combinations * 18: Restrictions breed creativity * 19: Your audience is good at recognizing problems and bad at solving them * 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 da8fef4c..282675db 100755 --- a/server/src/cryp.rs +++ b/server/src/cryp.rs @@ -9,7 +9,8 @@ use failure::err_msg; use account::Account; use rpc::{CrypSpawnParams}; -use skill::{Skill, Cooldown, Roll}; +use game::{Log}; +use skill::{Skill, Cooldown, Roll, Effect}; #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub struct CrypSkill { @@ -29,15 +30,8 @@ impl CrypSkill { } #[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, +pub struct CrypEffect { + effect: Effect, duration: u8, } @@ -80,7 +74,7 @@ pub struct Cryp { pub xp: u64, pub lvl: u8, pub skills: Vec, - pub statuses: Vec, + pub effects: Vec, pub name: String, } @@ -103,7 +97,7 @@ impl Cryp { lvl: 0, xp: 0, skills: vec![CrypSkill::new(Skill::Attack)], - statuses: vec![], + effects: vec![], name: String::new() }; } @@ -165,7 +159,7 @@ impl Cryp { } pub fn is_stunned(&self) -> bool { - self.statuses.iter().any(|s| s.status == Status::Stunned) + self.effects.iter().any(|s| s.effect == Effect::Stun) } pub fn available_skills(&self) -> Vec<&CrypSkill> { @@ -207,17 +201,17 @@ impl Cryp { self } - pub fn reduce_statuses(&mut self) -> &mut Cryp { - self.statuses = self.statuses.clone().into_iter().filter_map(|mut s| { + pub fn reduce_effect_durations(&mut self) -> &mut Cryp { + self.effects = self.effects.clone().into_iter().filter_map(|mut s| { s.duration = s.duration.saturating_sub(1); if s.duration == 0 { return None; } - println!("reduced status {:?}", s); + println!("reduced effect {:?}", s); return Some(s); - }).collect::>(); + }).collect::>(); self } @@ -246,20 +240,26 @@ impl Cryp { return roll; } - 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() }); + pub fn stun(&mut self, _roll: Roll, log: &mut Log) -> &mut Cryp { + if !self.effects.iter().any(|s| s.effect == Effect::Block) { + let stun = CrypEffect { effect: Effect::Stun, duration: Skill::Stun.duration() }; + self.effects.push(stun); + log.push(format!("{:?} is {:?} for {:?}T", self.name, stun.effect, stun.duration)) + } else { + log.push(format!("{:?} blocks.", self.name)) } self } - pub fn attack(&mut self, roll: Roll) -> &mut Cryp { + pub fn attack(&mut self, roll: Roll, log: &mut Log) -> &mut Cryp { self.hp.reduce(roll.result); self } - pub fn block(&mut self, _roll: Roll) -> &mut Cryp { - self.statuses.push(CrypStatus { status: Status::Blocking, duration: Skill::Block.duration() }); + pub fn block(&mut self, _roll: Roll, log: &mut Log) -> &mut Cryp { + let effect = CrypEffect { effect: Effect::Block, duration: Skill::Block.duration() }; + self.effects.push(effect); + log.push(format!("{:?} is {:?} for {:?}T", self.name, effect.effect, effect.duration)); self } diff --git a/server/src/game.rs b/server/src/game.rs index 728dd219..2241d983 100755 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -12,6 +12,8 @@ use rpc::{GameStateParams, GameSkillParams, GamePveParams, GamePvpParams, GameTa use cryp::{Cryp, cryp_get}; use skill::{Skill, Cast}; +pub type Log = Vec; + #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Team { id: Uuid, @@ -47,7 +49,7 @@ pub enum Phase { Start, Skill, Target, - Damage, + Resolve, Finish, } @@ -94,11 +96,18 @@ impl Game { self } - // check team not already in fn add_team(&mut self, team: Team) -> Result<&mut Game, Error> { if self.teams.len() == self.team_num { return Err(err_msg("maximum number of teams")); } + + if self.teams.iter().any(|t| t.id == team.id) { + return Err(err_msg("team already in game")); + } + + let team_description = team.cryps.iter().map(|c| c.name.clone()).collect::>().join(", "); + self.log.push(format!("{:?} has joined the game.", team_description)); + self.teams.push(team); Ok(self) @@ -139,13 +148,17 @@ impl Game { } fn start(&mut self) -> &mut Game { + self.log.push("Game starting...".to_string()); + self.skill_phase_start(); self } fn skill_phase_start(&mut self) -> &mut Game { - if ![Phase::Start, Phase::Damage].contains(&self.phase) { - panic!("game not in damage or start phase"); + self.log.push("".to_string()); + + if ![Phase::Start, Phase::Resolve].contains(&self.phase) { + panic!("game not in Resolve or start phase"); } self.phase = Phase::Skill; @@ -175,8 +188,6 @@ impl Game { self } - // 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, 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")); @@ -207,7 +218,7 @@ impl Game { // 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")); + return Err(err_msg("cryp cannot cast that skill")); } } @@ -228,16 +239,16 @@ impl Game { // for every team .all(|t| self.stack.iter() // the number of skills they have cast - .filter(|s| s.source_team_id == t.id) - .collect::>() + .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()); + self.log.push("".to_string()); + if self.phase != Phase::Skill { panic!("game not in skill phase"); } @@ -250,7 +261,7 @@ impl Game { // all cryps are stunned or otherwise inactive if self.target_phase_finished() { - self.damage_phase_start(); + self.resolve_phase_start(); } self @@ -311,12 +322,14 @@ impl Game { // requires no input // just do it - fn damage_phase_start(&mut self) -> &mut Game { + fn resolve_phase_start(&mut self) -> &mut Game { if self.phase != Phase::Target { panic!("game not in target phase"); } + assert!(self.target_phase_finished()); - self.phase = Phase::Damage; + self.phase = Phase::Resolve; + self.log.push("".to_string()); self.resolve_skills(); @@ -330,8 +343,8 @@ impl Game { } fn resolve_skills(&mut self) -> &mut Game { - if self.phase != Phase::Damage { - panic!("game not in damage phase"); + if self.phase != Phase::Resolve { + panic!("game not in Resolve phase"); } self.stack.sort_unstable_by_key(|s| s.skill.speed()); @@ -342,16 +355,18 @@ impl Game { 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.log.push(format!("{:?} uses {:?} on {:?}", source.name, skill.skill, target.name)); + let resolution = skill.resolve(&mut source, &mut target, &mut self.log); self.resolved.push(*resolution); + self.update_cryp(&mut source); self.update_cryp(&mut target); return *resolution; }).collect::>(); - // now damage has all been assigned + // now Resolve has all been assigned // handle cooldowns and statuses self.progress_durations(); @@ -378,7 +393,7 @@ impl Game { } // always reduce durations - cryp.reduce_statuses(); + cryp.reduce_effect_durations(); self.update_cryp(&mut cryp); } @@ -455,7 +470,7 @@ pub fn game_target(params: GameTargetParams, tx: &mut Transaction, account: &Acc game.add_target(account.id, params.cryp_id, params.skill_id)?; if game.target_phase_finished() { - game.damage_phase_start(); + game.resolve_phase_start(); } game_update(&game, tx)?; @@ -753,7 +768,7 @@ mod tests { assert!(game.target_phase_finished()); - game.damage_phase_start(); + game.resolve_phase_start(); assert!([Phase::Skill, Phase::Finish].contains(&game.phase)); @@ -780,7 +795,7 @@ mod tests { game.add_target(y_team.id, y_cryp.id, x_stun_id).unwrap(); assert!(game.target_phase_finished()); - game.damage_phase_start(); + game.resolve_phase_start(); // should auto progress back to skill phase assert!(game.phase == Phase::Skill); @@ -807,7 +822,7 @@ mod tests { 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(); + game.resolve_phase_start(); // should auto progress back to skill phase assert!(game.phase == Phase::Skill); @@ -823,7 +838,7 @@ mod tests { 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(); + // game.resolve_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()); @@ -855,10 +870,11 @@ mod tests { game.add_target(x_team.id, x_cryp.id, y_attack_id).unwrap(); - game.damage_phase_start(); + game.resolve_phase_start(); // should not be stunned because of block assert!(game.team_by_id(x_team.id).cryps[0].is_stunned() == false); + println!("{:#?}", game.log); } } diff --git a/server/src/skill.rs b/server/src/skill.rs index 63e710f9..12fc63d2 100755 --- a/server/src/skill.rs +++ b/server/src/skill.rs @@ -2,6 +2,7 @@ use uuid::Uuid; use cryp::{Cryp, CrypSkill, CrypStat}; +use game::{Log}; #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub struct Roll { @@ -15,9 +16,16 @@ pub type Cooldown = Option; pub enum Skill { Attack, Block, - Heal, Stun, Dodge, + Banish, + Heal, + Accuracy, + Evasion, + Amplify, + HoT, + DoT, + // used by tests, no cd, no dmg TestTouch, TestStun, @@ -28,10 +36,20 @@ impl Skill { pub fn cd(&self) -> Cooldown { match self { Skill::Attack => None, + Skill::Block => Some(1), Skill::Dodge => Some(1), + Skill::Amplify => Some(1), + Skill::Evasion => Some(1), + Skill::Accuracy => Some(1), + Skill::Heal => Some(2), Skill::Stun => Some(2), + Skill::DoT => Some(2), + Skill::HoT => Some(2), + + Skill::Banish => Some(3), + Skill::TestTouch => None, Skill::TestStun => None, Skill::TestBlock => None, @@ -40,11 +58,21 @@ impl Skill { pub fn speed(&self) -> u8 { match self { + Skill::Dodge => 12, Skill::Attack => 10, + Skill::Block => 5, - Skill::Dodge => 5, + Skill::Amplify => 5, + Skill::Accuracy => 5, + Skill::Evasion => 5, + Skill::HoT => 5, + Skill::DoT => 5, + Skill::Heal => 2, Skill::Stun => 2, + Skill::Banish => 2, + + // test skills Skill::TestTouch => 10, Skill::TestStun => 2, Skill::TestBlock => 5, @@ -58,6 +86,12 @@ impl Skill { Skill::Stun => cryp.str, Skill::Dodge => cryp.agi, Skill::Heal => cryp.int, + Skill::Banish => cryp.str, + Skill::Evasion => cryp.str, + Skill::Accuracy => cryp.str, + Skill::Amplify => cryp.str, + Skill::HoT => cryp.str, + Skill::DoT => cryp.str, // test skills Skill::TestTouch => cryp.int, @@ -125,27 +159,29 @@ impl Cast { }; } - pub fn resolve(&mut self, cryp: &mut Cryp, target: &mut Cryp) -> &mut Cast { + pub fn resolve(&mut self, cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) -> &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), - Skill::Attack => target.attack(roll), - Skill::Block => target.block(roll), + Skill::Stun => target.stun(roll, log), + Skill::Attack => target.attack(roll, log), + Skill::Block => target.block(roll, log), Skill::Heal => target, Skill::Dodge => target, + Skill::Banish => target, + Skill::Accuracy => target, + Skill::Evasion => target, + Skill::Amplify => target, + Skill::HoT => target, + Skill::DoT => target, // Test Skills - Skill::TestStun => target.stun(roll), - Skill::TestBlock => target.block(roll), + Skill::TestStun => target.stun(roll, log), + Skill::TestBlock => target.block(roll, log), Skill::TestTouch => target, }; - // println!("{:?} gettin clapped for {:?}", target.name, roll.result); - self.roll = Some(roll); self } @@ -161,6 +197,28 @@ impl Cast { } } +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +pub enum Effect { + // Skill Effects + Stun, + Silence, + Banish, + Block, + Haste, + Slow, + Regen, + Degen, + Bleed, + Leech, + Airborne, + Immune, + + // Passives / items + Ghost, + Stone, + Evasion, +} + // #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] // pub enum Skill { // Stoney,