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,
}],
// 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', {

View File

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

View File

@ -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 (
<button
key={i}
className="button is-dark"
type="submit"
onClick={() => setActiveSkill(cryp.id, skill.skill)}
onClick={() => setActiveSkill(cryp.id, skill)}
>
({hotkey}) {skill.skill} {skill.cd && `(${skill.cd}T)`}
</button>
@ -59,7 +59,6 @@ function GamePanel(props) {
<div key={i}>{status} for {status.turns}T</div>
));
if (activeIncoming) console.log('should be a pointer');
return (
<div
key={cryp.id}

View File

@ -99,6 +99,7 @@ function createSocket(store) {
// Outgoing
// -------------
function send(msg) {
console.log('outgoing msg', msg);
msg.token = account && account.token;
ws.send(cbor.encode(msg));
}
@ -142,7 +143,7 @@ function createSocket(store) {
}
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));
}

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
```
rustup default nightly-2018-06-09-x86_64-unknown-linux-gnu
```
skill phase:
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
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
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

@ -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
@ -113,3 +114,24 @@ 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)

View File

@ -9,50 +9,36 @@ use failure::err_msg;
use account::Account;
use rpc::{CrypSpawnParams};
use skill::{Skill, Roll};
type Cooldown = Option<u8>;
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::<Vec<CrypStatus>>();
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<Cryp, Error> {
@ -267,6 +288,7 @@ pub fn cryp_spawn(params: CrypSpawnParams, tx: &mut Transaction, account: &Accou
.named(&params.name)
.level(10)
.learn(Skill::Stun)
.learn(Skill::Block)
.set_account(account.id)
.create();

View File

@ -16,8 +16,6 @@ use skill::{Skill, Cast};
pub struct Team {
id: Uuid,
cryps: Vec<Cryp>,
skills: Vec<Cast>,
incoming: Vec<Cast>,
}
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<Team>,
pub is_pve: bool,
pub phase: Phase,
pub stack: Vec<Cast>,
pub resolved: Vec<Cast>,
pub log: Vec<String>,
}
@ -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<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 {
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::<Vec<&Cast>>()
// 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::<Vec<Cast>>();
// 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::<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 {
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<u8> = returned.get("data");
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)?;
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 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() {
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);
}
}

View File

@ -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<Uuid>,
pub skill: Skill,
}

View File

@ -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<u8>;
#[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<Uuid>,
pub target_team_id: Uuid,
pub roll: Option<Roll>,
}
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 {
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)]