Merge branch 'master' into battleui

This commit is contained in:
Mashy 2018-10-31 12:50:45 +10:00
commit 8441ec43be
11 changed files with 515 additions and 288 deletions

View File

@ -616,11 +616,11 @@ module.exports = {
avoidQuotes: true, avoidQuotes: true,
}], }],
// suggest using arrow functions as callbacks // // suggest using arrow functions as callbacks
'prefer-arrow-callback': ['error', { // 'prefer-arrow-callback': ['error', {
allowNamedFunctions: false, // allowNamedFunctions: false,
allowUnboundThis: true, // allowUnboundThis: true,
}], // }],
// suggest using of const declaration for variables that are never modified after declared // suggest using of const declaration for variables that are never modified after declared
'prefer-const': ['error', { 'prefer-const': ['error', {

View File

@ -10,11 +10,16 @@ const addState = connect(
function selectSkillTarget(targetTeamId) { function selectSkillTarget(targetTeamId) {
if (activeSkill) { 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; 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) { function selectIncomingTarget(crypId) {
if (activeIncoming) { if (activeIncoming) {
return ws.sendGameTarget(game.id, crypId, activeIncoming); return ws.sendGameTarget(game.id, crypId, activeIncoming);
@ -27,11 +32,11 @@ const addState = connect(
function receiveDispatch(dispatch) { function receiveDispatch(dispatch) {
function setActiveSkill(crypId, skill) { function setActiveSkill(crypId, skill) {
dispatch(actions.setActiveSkill(crypId, skill)) dispatch(actions.setActiveSkill(crypId, skill));
} }
function setActiveIncoming(skillId) { function setActiveIncoming(skillId) {
dispatch(actions.setActiveIncoming(skillId)) dispatch(actions.setActiveIncoming(skillId));
} }

View File

@ -21,7 +21,7 @@ function GamePanel(props) {
const playerTeam = game.teams.find(t => t.id === account.id); 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.unbind('1');
key('1', () => setActiveIncoming(inc.id)); key('1', () => setActiveIncoming(inc.id));
return ( return (
@ -41,14 +41,14 @@ function GamePanel(props) {
const skills = cryp.skills.map((skill, i) => { const skills = cryp.skills.map((skill, i) => {
const hotkey = SKILL_HOT_KEYS[i]; const hotkey = SKILL_HOT_KEYS[i];
key.unbind(hotkey); key.unbind(hotkey);
key(hotkey, () => setActiveSkill(cryp.id, skill.skill)); key(hotkey, () => setActiveSkill(cryp.id, skill));
return ( return (
<button <button
key={i} key={i}
className="button is-dark" className="button is-dark"
type="submit" type="submit"
onClick={() => setActiveSkill(cryp.id, skill.skill)} onClick={() => setActiveSkill(cryp.id, skill)}
> >
({hotkey}) {skill.skill} {skill.cd && `(${skill.cd}T)`} ({hotkey}) {skill.skill} {skill.cd && `(${skill.cd}T)`}
</button> </button>
@ -59,7 +59,6 @@ function GamePanel(props) {
<div key={i}>{status} for {status.turns}T</div> <div key={i}>{status} for {status.turns}T</div>
)); ));
if (activeIncoming) console.log('should be a pointer');
return ( return (
<div <div
key={cryp.id} key={cryp.id}

View File

@ -99,6 +99,7 @@ function createSocket(store) {
// Outgoing // Outgoing
// ------------- // -------------
function send(msg) { function send(msg) {
console.log('outgoing msg', msg);
msg.token = account && account.token; msg.token = account && account.token;
ws.send(cbor.encode(msg)); ws.send(cbor.encode(msg));
} }
@ -142,7 +143,7 @@ function createSocket(store) {
} }
function sendGameTarget(gameId, crypId, skillId) { function sendGameTarget(gameId, crypId, skillId) {
send({ method: 'game_target', params: { game_id: gameId, cryp_id: crypId, cast_id: skillId } }); send({ method: 'game_target', params: { game_id: gameId, cryp_id: crypId, skill_id: skillId } });
store.dispatch(actions.setActiveIncoming(null)); store.dispatch(actions.setActiveIncoming(null));
} }

View File

@ -1,11 +0,0 @@
## 02-09-2018
* went full circle through the last 20 years of the web's problems
* debated using vanilla tcp sockets but realised would be very time consuming
* Struggled a lot with google/tarpc
found the documentation absolutely mad, macros perform most of the functionality
couldn't find any way to keep server running, client stub appears to direcly rely on the server structs
needed a specific version of the rust nightly from several months ago to compile
* found wa-rs, hope was restored, had a websocket server up and running in seconds
* lost hope again when its client doesn't compile into wasm due to unix dependencies in mio
this also prevents any tokio based futures client from working
* realised i'd been reading very out of date documentation and there was plenty of work happening on `stdweb` and `cargo web`

View File

@ -2,96 +2,21 @@
## Setup ## Setup
``` skill phase:
rustup default nightly-2018-06-09-x86_64-unknown-linux-gnu 1.1 -> 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 target phase:
stat & rng team 2 targets 1.2 on 2.2
block hash or totally random
roll server that prints a roll every second?
friendship on ties? team 1 targets 2.1 on 1.1
team 1 targets 2.2 on 1.1
Def resolve phase:
0001011011001100110101000000101111100110101111110100100001000001 - roll 1.1 <- block
0000000000000000000000000000000000000000000000000000000000001111 ^ steel armour 1.1 <- attack (no effect because of block)
0000000000000000000000000000000000000000000000000000001111000000 ^ stoney trait 2.2 <- attack (normal resolve)
0001011011001100110101000000101111100110101111110100101111001111 = modified roll 1.1 <- hexed (no skills for the rest of this turn and next)
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

View File

@ -4,30 +4,20 @@
* Prove something * Prove something
# WORK WORK # 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 * Global rolls
* Logins ✔️
* Cryp Ownership ✔
* Matchmaking
* Lobbies
* Create
* Join
* Resolve
* Stats * Stats
* Scrabble grid * Scrabble grid
* skills * skills
* offensive -> choose target ✔ * offensive -> choose target ✔
* private fields for opponents * private fields for opponents
* cooldowns reduce each turn ✔
* statuses reduce each turn
* teach cyps skills * teach cyps skills
* can you attack yourself? * can you attack yourself?
* fetch existing battles * fetch existing battles
@ -50,6 +40,17 @@
* Cryp Generation * Cryp Generation
* *
* ws reconnect ✔
* Levelling ✔
* Logins ✔️
* Cryp Ownership ✔
* Matchmaking ✔
* Lobbies ✔
* Create ✔
* Join ✔
* Resolve ✔
# Db maintenance # Db maintenance
* delete games when a cryp is deleted * delete games when a cryp is deleted
* does this need to happen? can have historical games * 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 * 17: You don't have to change much to change everything
* 18: Restrictions breed creativity * 18: Restrictions breed creativity
* 19: Your audience is good at recognizing problems and bad at solving them * 19: Your audience is good at recognizing problems and bad at solving them
* 20: All the lessons connect * 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)

View File

@ -9,50 +9,36 @@ use failure::err_msg;
use account::Account; use account::Account;
use rpc::{CrypSpawnParams}; use rpc::{CrypSpawnParams};
use skill::{Skill, Roll}; use skill::{Skill, Cooldown, Roll};
type Cooldown = Option<u8>;
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub struct CrypSkill { pub struct CrypSkill {
skill: Skill, pub skill: Skill,
cd: Cooldown, pub self_targeting: bool,
pub cd: Cooldown,
} }
impl CrypSkill { impl CrypSkill {
pub fn new(skill: Skill) -> 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 { CrypSkill {
skill, 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)] #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Status { pub enum Status {
Stunned, Stunned,
Silenced, Silenced,
Blocking,
} }
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub struct CrypStatus { pub struct CrypStatus {
status: Status, status: Status,
turns: u8, duration: u8,
} }
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
@ -183,19 +169,57 @@ impl Cryp {
} }
pub fn available_skills(&self) -> Vec<&CrypSkill> { 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 { pub fn knows(&self, skill: Skill) -> bool {
self.skills.iter().any(|s| s.skill == skill) self.skills.iter().any(|s| s.skill == skill)
} }
pub fn skill_on_cd(&self, skill: Skill) -> bool { pub fn skill_on_cd(&self, skill: Skill) -> Option<&CrypSkill> {
self.skills.iter().any(|s| s.skill == skill && s.cd.is_some()) self.skills.iter().find(|s| s.skill == skill && s.cd.is_some())
} }
pub fn skill_can_cast(&self, skill: Skill) -> bool { pub fn skill_set_cd(&mut self, skill: Skill) -> &mut Cryp {
self.skills.iter().any(|s| s.skill == skill && !s.can_cast(self)) 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::<Vec<CrypStatus>>();
self
} }
pub fn rez(&mut self) -> &mut Cryp { pub fn rez(&mut self) -> &mut Cryp {
@ -207,20 +231,10 @@ impl Cryp {
let mut rng = thread_rng(); let mut rng = thread_rng();
let base: u64 = rng.gen(); let base: u64 = rng.gen();
let stat = match skill { let stat = skill.stat(self);
Skill::Attack => self.str,
Skill::Block => self.str,
Skill::Stun => self.str,
Skill::Dodge => self.agi,
Skill::Heal => self.int,
};
let mut roll = Roll { base, result: base }; 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!("{:?}'s stats", self.name);
println!("{:064b} <- finalised", roll.result); println!("{:064b} <- finalised", roll.result);
roll.result = roll.result & stat.value; roll.result = roll.result & stat.value;
@ -232,8 +246,10 @@ impl Cryp {
return roll; return roll;
} }
pub fn stun(&mut self, roll: Roll) -> &mut Cryp { pub fn stun(&mut self, _roll: Roll) -> &mut Cryp {
self.statuses.push(CrypStatus { status: Status::Stunned, turns: 1 }); if !self.statuses.iter().any(|s| s.status == Status::Blocking) {
self.statuses.push(CrypStatus { status: Status::Stunned, duration: Skill::Stun.duration() });
}
self self
} }
@ -242,6 +258,11 @@ impl Cryp {
self 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<Cryp, Error> { pub fn cryp_get(tx: &mut Transaction, id: Uuid, account_id: Uuid) -> Result<Cryp, Error> {
@ -267,6 +288,7 @@ pub fn cryp_spawn(params: CrypSpawnParams, tx: &mut Transaction, account: &Accou
.named(&params.name) .named(&params.name)
.level(10) .level(10)
.learn(Skill::Stun) .learn(Skill::Stun)
.learn(Skill::Block)
.set_account(account.id) .set_account(account.id)
.create(); .create();

View File

@ -16,8 +16,6 @@ use skill::{Skill, Cast};
pub struct Team { pub struct Team {
id: Uuid, id: Uuid,
cryps: Vec<Cryp>, cryps: Vec<Cryp>,
skills: Vec<Cast>,
incoming: Vec<Cast>,
} }
impl Team { impl Team {
@ -25,8 +23,6 @@ impl Team {
return Team { return Team {
id: account, id: account,
cryps: vec![], cryps: vec![],
skills: vec![],
incoming: vec![],
}; };
} }
@ -44,13 +40,6 @@ impl Team {
pub fn cryp_by_id(&mut self, id: Uuid) -> Option<&mut Cryp> { pub fn cryp_by_id(&mut self, id: Uuid) -> Option<&mut Cryp> {
self.cryps.iter_mut().find(|c| c.id == id) 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)] #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
@ -70,6 +59,8 @@ pub struct Game {
pub teams: Vec<Team>, pub teams: Vec<Team>,
pub is_pve: bool, pub is_pve: bool,
pub phase: Phase, pub phase: Phase,
pub stack: Vec<Cast>,
pub resolved: Vec<Cast>,
pub log: Vec<String>, pub log: Vec<String>,
} }
@ -82,6 +73,8 @@ impl Game {
teams: vec![], teams: vec![],
is_pve: true, is_pve: true,
phase: Phase::Start, phase: Phase::Start,
stack: vec![],
resolved: vec![],
log: vec![], log: vec![],
}; };
} }
@ -119,35 +112,25 @@ impl Game {
} }
} }
fn cryp_by_id(&mut self, id: Uuid) -> &mut Cryp { fn cryp_by_id(&mut self, id: Uuid) -> Option<&mut Cryp> {
for team in self.teams.iter_mut() { match self.teams.iter_mut().find(|t| t.cryps.iter().any(|c| c.id == id)) {
let in_team = team.cryp_by_id(id); Some(team) => {
if in_team.is_some() { return team.cryps.iter_mut().find(|c| c.id == id);
return in_team.unwrap(); },
} None => panic!("cryp not in game"),
} };
panic!("cryp not in game");
} }
// fn update_team(&mut self, updated: Team) -> &mut Game { fn update_cryp(&mut self, cryp: &mut Cryp) -> &mut Game {
// match self.teams.iter().position(|t| t.id == updated.id) { match self.teams.iter_mut().find(|t| t.cryps.iter().any(|c| c.id == cryp.id)) {
// Some(index) => { Some(team) => {
// self.teams.remove(index); let index = team.cryps.iter().position(|t| t.id == cryp.id).unwrap();
// 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) {
team.cryps.remove(index); team.cryps.remove(index);
team.cryps.push(updated); team.cryps.push(cryp.clone());
break; },
} None => panic!("cryp not in game"),
} };
self self
} }
@ -166,10 +149,8 @@ impl Game {
} }
self.phase = Phase::Skill; self.phase = Phase::Skill;
for team in self.teams.iter_mut() {
team.skills.clear(); self.stack.clear();
team.incoming.clear();
}
if self.is_pve { if self.is_pve {
self.pve_add_skills(); self.pve_add_skills();
@ -181,13 +162,13 @@ impl Game {
fn pve_add_skills(&mut self) -> &mut Game { fn pve_add_skills(&mut self) -> &mut Game {
{ {
let mob_team_id = Uuid::nil(); let mob_team_id = Uuid::nil();
let teams = self.teams.clone();
let mobs = self.team_by_id(mob_team_id).clone(); let mobs = self.team_by_id(mob_team_id).clone();
// TODO attack multiple players based on some criteria // 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 { for mob in &mobs.cryps {
// doesn't matter if the cryp can't cast // 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 // 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 // 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<Uuid, Error> { fn add_skill(&mut self, team_id: Uuid, source_cryp_id: Uuid, target_team_id: Option<Uuid>, skill: Skill) -> Result<Uuid, 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"));
} }
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, Some(c) => c,
None => return Err(err_msg("cryp not in team")), 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")); 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")); 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")); return Err(err_msg("cryp cannot cast spell"));
} }
} }
// replace cryp skill // replace cryp skill
if let Some(s) = team.skills.iter_mut().position(|s| s.cryp_id == cryp_id) { if let Some(s) = self.stack.iter_mut().position(|s| s.source_cryp_id == source_cryp_id) {
team.skills.remove(s); self.stack.remove(s);
} }
let skill = Cast::new(cryp_id, target_team_id, skill); let skill = Cast::new(source_cryp_id, team_id, target_team_id, skill);
team.skills.push(skill); self.stack.push(skill);
return Ok(skill.id); return Ok(skill.id);
} }
fn skill_phase_finished(&self) -> bool { 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::<Vec<&Cast>>()
// should equal the number required this turn
.len() == t.skills_required()
)
} }
// move all skills into their target team's targets list // move all skills into their target team's targets list
fn target_phase_start(&mut self) -> &mut Game { fn target_phase_start(&mut self) -> &mut Game {
assert!(self.skill_phase_finished());
if self.phase != Phase::Skill { if self.phase != Phase::Skill {
panic!("game not in skill phase"); panic!("game not in skill phase");
} }
self.phase = Phase::Target; 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 { if self.is_pve {
self.pve_add_targets(); self.pve_add_targets();
} }
@ -273,33 +261,52 @@ impl Game {
let mob_team_id = Uuid::nil(); let mob_team_id = Uuid::nil();
let mobs = self.team_by_id(mob_team_id).clone(); let mobs = self.team_by_id(mob_team_id).clone();
// TODO attack multiple players based on some criteria // TODO attack multiple players based on some criteria
for incoming in &mobs.incoming { for incoming_skill_id in self.stack.clone().iter()
self.add_target(mob_team_id, mobs.cryps[0].id, incoming.id).unwrap(); .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 self
} }
// targets can only be added by the owner of the team // 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> { fn add_target(&mut self, team_id: Uuid, cryp_id: Uuid, skill_id: Uuid) -> Result<&mut Cast, Error> {
// whose team is this? if self.phase != Phase::Target {
let team = self.team_by_id(team_id); return Err(err_msg("game not in target phase"));
}
// is the target in the team? {
match team.cryp_by_id(cryp_id) { // whose team is this?
Some(c) => c, let team = self.team_by_id(team_id);
None => return Err(err_msg("cryp not in team")),
}; // 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 // 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)) Ok(cast.set_target(cryp_id))
} }
fn target_phase_finished(&self) -> bool { 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 // requires no input
@ -327,20 +334,58 @@ impl Game {
panic!("game not in damage phase"); panic!("game not in damage phase");
} }
// sometimes... you just gotta self.stack.sort_unstable_by_key(|s| s.skill.speed());
for team in self.teams.clone().iter_mut() { self.stack.reverse();
for incoming in team.incoming.clone().iter_mut() {
// they better fuckin be there // update the stack with the resolved skills
let mut cryp = self.cryp_by_id(incoming.cryp_id).clone(); self.stack = self.stack.clone().iter_mut().map(|skill| {
let mut target_cryp = self.cryp_by_id(incoming.target_cryp_id.unwrap()).clone(); let mut source = self.cryp_by_id(skill.source_cryp_id).unwrap().clone();
println!("{:?} is attacking {:?}", cryp.name, target_cryp.name); let mut target = self.cryp_by_id(skill.target_cryp_id.unwrap()).unwrap().clone();
incoming.resolve(&mut cryp, &mut target_cryp);
self.update_cryp(target_cryp); 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::<Vec<Cast>>();
// now damage has all been assigned
// handle cooldowns and statuses
self.progress_durations();
self 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::<Vec<Cryp>>() {
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 { fn is_finished(&self) -> bool {
self.teams.iter().any(|t| t.cryps.iter().all(|c| c.is_ko())) 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 { fn finish(&mut self) -> &mut Game {
self.phase = Phase::Finish; self.phase = Phase::Finish;
for team in self.teams.iter_mut() { self.stack.clear();
team.skills.clear();
team.incoming.clear();
}
self self
} }
@ -376,6 +418,10 @@ pub fn game_skill(params: GameSkillParams, tx: &mut Transaction, account: &Accou
let game_bytes: Vec<u8> = returned.get("data"); let game_bytes: Vec<u8> = returned.get("data");
let mut game = from_slice::<Game>(&game_bytes)?; let mut game = from_slice::<Game>(&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)?; game.add_skill(account.id, params.cryp_id, params.target_team_id, params.skill)?;
if game.skill_phase_finished() { if game.skill_phase_finished() {
@ -406,7 +452,7 @@ pub fn game_target(params: GameTargetParams, tx: &mut Transaction, account: &Acc
let game_bytes: Vec<u8> = returned.get("data"); let game_bytes: Vec<u8> = returned.get("data");
let mut game = from_slice::<Game>(&game_bytes)?; let mut game = from_slice::<Game>(&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() { if game.target_phase_finished() {
game.damage_phase_start(); game.damage_phase_start();
@ -513,7 +559,7 @@ fn generate_mob(plr: &Cryp) -> Cryp {
// rng panics on min == max // rng panics on min == max
let mob_lvl: u8 = match plr.lvl { let mob_lvl: u8 = match plr.lvl {
1 => 1, 1 => 1,
_ => rng.gen_range(1, plr.lvl) _ => rng.gen_range(plr.lvl.saturating_sub(2), plr.lvl)
}; };
return Cryp::new() return Cryp::new()
@ -638,22 +684,25 @@ mod tests {
use game::*; use game::*;
use cryp::*; use cryp::*;
#[test] fn create_test_game() -> Game {
fn game_test() {
let x = Cryp::new() let x = Cryp::new()
.named(&"pronounced \"creeep\"".to_string()) .named(&"pronounced \"creeep\"".to_string())
.level(8) .level(8)
.learn(Skill::TestStun)
.learn(Skill::TestTouch)
.learn(Skill::TestBlock)
.learn(Skill::Block)
.create(); .create();
let x_id = x.id;
let y = Cryp::new() let y = Cryp::new()
.named(&"lemongrass tea".to_string()) .named(&"lemongrass tea".to_string())
.level(8) .level(8)
.learn(Skill::TestStun)
.learn(Skill::TestTouch)
.learn(Skill::TestBlock)
.learn(Skill::Block)
.create(); .create();
let y_id = y.id;
let mut game = Game::new(); let mut game = Game::new();
game game
@ -679,17 +728,28 @@ mod tests {
game.start(); game.start();
let x_attack_id = game.add_skill(x_team_id, x_id, y_team_id, Skill::Attack); return game;
let y_attack_id = game.add_skill(y_team_id, y_id, x_team_id, Skill::Attack); }
#[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()); assert!(game.skill_phase_finished());
game.target_phase_start(); game.target_phase_start();
println!("{:?}", game); 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();
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();
assert!(game.target_phase_finished()); assert!(game.target_phase_finished());
@ -697,8 +757,108 @@ mod tests {
assert!([Phase::Skill, Phase::Finish].contains(&game.phase)); assert!([Phase::Skill, Phase::Finish].contains(&game.phase));
println!("{:?}", game);
return; 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);
}
} }

View File

@ -355,7 +355,7 @@ struct GameTargetMsg {
pub struct GameTargetParams { pub struct GameTargetParams {
pub game_id: Uuid, pub game_id: Uuid,
pub cryp_id: Uuid, pub cryp_id: Uuid,
pub cast_id: Uuid, pub skill_id: Uuid,
} }
#[derive(Debug,Clone,Serialize,Deserialize)] #[derive(Debug,Clone,Serialize,Deserialize)]
@ -368,7 +368,7 @@ struct GameSkillMsg {
pub struct GameSkillParams { pub struct GameSkillParams {
pub game_id: Uuid, pub game_id: Uuid,
pub cryp_id: Uuid, pub cryp_id: Uuid,
pub target_team_id: Uuid, pub target_team_id: Option<Uuid>,
pub skill: Skill, pub skill: Skill,
} }

View File

@ -1,7 +1,7 @@
use rand::prelude::*; // use rand::prelude::*;
use uuid::Uuid; use uuid::Uuid;
use cryp::{Cryp}; use cryp::{Cryp, CrypSkill, CrypStat};
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub struct Roll { pub struct Roll {
@ -9,6 +9,8 @@ pub struct Roll {
pub result: u64, pub result: u64,
} }
pub type Cooldown = Option<u8>;
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Skill { pub enum Skill {
Attack, Attack,
@ -16,6 +18,80 @@ pub enum Skill {
Heal, Heal,
Stun, Stun,
Dodge, 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 struct Cast {
pub id: Uuid, pub id: Uuid,
pub skill: Skill, pub skill: Skill,
pub cryp_id: Uuid, pub source_team_id: Uuid,
pub source_cryp_id: Uuid,
pub target_cryp_id: Option<Uuid>, pub target_cryp_id: Option<Uuid>,
pub target_team_id: Uuid, pub target_team_id: Uuid,
pub roll: Option<Roll>,
} }
impl Cast { 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<Uuid>, 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 { return Cast {
id: Uuid::new_v4(), id: Uuid::new_v4(),
cryp_id, source_cryp_id,
target_cryp_id: None, source_team_id,
target_cryp_id,
target_team_id, target_team_id,
skill, skill,
roll: None,
}; };
} }
pub fn resolve(&mut self, cryp: &mut Cryp, target: &mut Cryp) -> &mut Cast { pub fn resolve(&mut self, cryp: &mut Cryp, target: &mut Cryp) -> &mut Cast {
let roll = cryp.roll(self.skill); let roll = cryp.roll(self.skill);
println!("{:?} -> {:?} -> {:?}", cryp.name, self.skill, target.name);
match self.skill { match self.skill {
// the real deal
Skill::Stun => target.stun(roll), 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 self
} }
@ -55,6 +154,11 @@ impl Cast {
self.target_cryp_id = Some(cryp_id); self.target_cryp_id = Some(cryp_id);
self 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)] // #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]