merge with master

This commit is contained in:
Mashy 2018-11-13 14:56:41 +10:00
commit 4e0f2d90b0
12 changed files with 922 additions and 223 deletions

3
client/cryps.css Normal file
View File

@ -0,0 +1,3 @@
body {
background-color: #181818;
}

View File

@ -1,2 +1,4 @@
require('./cryps.css');
// kick it off
require('./src/main');

View File

@ -55,8 +55,8 @@ function GamePanel(props) {
);
});
const statuses = cryp.statuses.map((status, i) => (
<div key={i}>{status} for {status.turns}T</div>
const effects = cryp.effects.map((effect, i) => (
<div key={i}>{effect} for {effect.turns}T</div>
));
return (
@ -84,7 +84,7 @@ function GamePanel(props) {
<div className="has-text-centered">{cryp.xp} / {Math.pow(2, cryp.lvl + 1)} XP </div>
<progress className="progress is-dark" value={cryp.xp} max={Math.pow(2, cryp.lvl + 1)}></progress>
</div>
{statuses}
{effects}
{skills}
</div>
);
@ -101,8 +101,8 @@ function GamePanel(props) {
}
function OpponentCrypCard(cryp) {
const statuses = cryp.statuses.map((status, i) => (
<div key={i}>{status.status} for {status.turns}T</div>
const effects = cryp.effects.map((effect, i) => (
<div key={i}>{effect.effect} for {effect.turns}T</div>
));
return (
@ -127,7 +127,7 @@ function GamePanel(props) {
<progress className="progress is-dark" value={cryp.xp} max={Math.pow(2, cryp.lvl + 1)}></progress>
</div>
{statuses}
{effects}
</div>
);
}
@ -155,6 +155,9 @@ function GamePanel(props) {
return 'Game over';
}
}
const logs = game.log.reverse().map((l, i) => (<div key={i}>{l}</div>));
return (
<section className="columns">
<div className="column is-1">
@ -175,9 +178,8 @@ function GamePanel(props) {
</div>
</section>
</div>
<div className="column is-3">
<div className="title is-4">{phaseText(game.phase)}</div>
<div> log </div>
<div className="column is-2">
<div className="title is-4">{logs}</div>
</div>
</section>
)

View File

@ -11,6 +11,9 @@
"author": "",
"license": "UNLICENSED",
"dependencies": {
"ascii-tree": "^0.3.0",
"cli-ascii-tree": "0.0.4",
"inquirer": "^6.2.0",
"knex": "^0.15.2",
"pg": "^7.4.3"
}

View File

@ -12,6 +12,7 @@ serde_cbor = "0.9"
tungstenite = "0.6"
bcrypt = "0.2"
petgraph = "0.4"
dotenv = "0.9.0"
env_logger = "*"

View File

@ -1,6 +1,6 @@
# Cryps ("creeps") // Creeptography
# Cryps ("creeps")
## Setup
## Combat
skill phase:
1.1 -> block (sp 10) -> on self
@ -20,3 +20,122 @@ 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 Alignments
Natural Selection
================
Survival of the fittest / Strength of the Individual / Attack & Defense
-----------------------------------------------------------------------
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 and prejudices they have grown in order to survive.
* tactics and strategy
* rally
* physical damage
* rend / expose
* taunt
* martial arts and combat
* blocking
* evasion and redirection
* frenzy
Machine Cult
=====================
Everything Connected / Speed & Efficiency
-----------------------------------------
members of the Machine Cult worship artificial machines of any sort, from simple spring powered devices to vast self-aware networks.
they value speed and efficiency above all else.
the individual has little significance for the machine cult, its members are rushing headlong into state of complete connectedness
they long to transcend beyond their physical limitations and become vaster and more powerful than the sum of each part
rigid, often serving a very specific purpose, not as adaptable as the other alignments
they and their machinations do not think, they simply act.
no motivation / no emotions / no tricks; just action.
* efficiency
* reduced cooldowns
* increased speed
* replication
* drones / tokens
Non-Violence
===============
Enhancement & Preservation
--------------------------
the philosophy of nonviolence teaches that the sanctity of life is above all else
its adherants are defensive and gracious, seeking to minimise the damage done by others and doing no direct harm themselves.
they seek to prevent damage in any way possible
* healing
* hots
* direct healing
* defensive buffs
* protection from effects
* damage reduction
Path to Destruction
===================
Damage & Destruction
-------------------------
cryps walking the path to destruction have forsaken themselves in order to gain ruinous power.
no price is too high, they gladly harm themselves and allies to amplify the destruction they wreak on everything around them
specialise in magical damage dealing
* damage amplification
* nukes
* life leach
* life exchange
* poison
* aoe
The Tribunal
=============
Fuck Magic
-------------------
The Tribunal has ruled that magic is an abomination.
Its members now scour the lands in search of magic, censoring its teaching, purging its effects and slaying the heretics who wield it.
* Dispel, removal
* Silence
* Magic resistance
* Information gathering
* team composition
* available skills etc
Universal Chaos
===============
The only constant is change.
----------------------------
Cryps aligning themselves with the forces of chaos believe that constant change is the only truth in the universe.
They harness its power to manipulate physical reality as well as control and disrupt the flow of battle.
They blend between physical and astral forms, constantly shifting throughout time and space.
* Banish
* Chaos damage
* Time control (reverse turn outcomes)
* increase cooldowns
* increase durations
* Slow
* damage redirection
## Styles
* Aztec
* Yokai / ukiyo-e

View File

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

View File

@ -9,7 +9,8 @@ use failure::err_msg;
use account::Account;
use rpc::{CrypSpawnParams};
use skill::{Skill, Cooldown, Roll};
use skill::{Skill, Cooldown, Effect, Tick};
use game::{Log};
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub struct CrypSkill {
@ -29,16 +30,17 @@ impl CrypSkill {
}
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Status {
Stunned,
Silenced,
Blocking,
pub struct CrypEffect {
pub effect: Effect,
pub duration: u8,
pub tick: Option<Tick>,
}
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub struct CrypStatus {
status: Status,
duration: u8,
impl CrypEffect {
pub fn tick(&self, cryp: &mut Cryp, log: &mut Log) -> &CrypEffect {
self.effect.tick(self, cryp, log);
self
}
}
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
@ -46,6 +48,8 @@ pub enum Stat {
Str,
Agi,
Int,
PhysicalDmg,
SpellPower,
Hp,
Stam,
}
@ -57,30 +61,35 @@ pub struct CrypStat {
}
impl CrypStat {
fn set(&mut self, v: u64) -> &CrypStat {
pub fn set(&mut self, v: u64) -> &CrypStat {
self.value = v;
self
}
pub fn reduce(&mut self, dmg: u64) -> &mut CrypStat {
self.value = self.value.saturating_sub(dmg);
pub fn reduce(&mut self, amt: u64) -> &mut CrypStat {
self.value = self.value.saturating_sub(amt);
self
}
pub fn increase(&mut self, amt: u64) -> &mut CrypStat {
self.value = self.value.saturating_add(amt);
self
}
}
#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct Cryp {
pub id: Uuid,
pub account: Uuid,
pub str: CrypStat,
pub agi: CrypStat,
pub int: CrypStat,
pub stam: CrypStat,
pub phys_dmg: CrypStat,
pub spell_dmg: CrypStat,
pub stamina: CrypStat,
pub hp: CrypStat,
pub xp: u64,
pub lvl: u8,
pub skills: Vec<CrypSkill>,
pub statuses: Vec<CrypStatus>,
pub effects: Vec<CrypEffect>,
pub name: String,
}
@ -95,15 +104,14 @@ impl Cryp {
return Cryp {
id,
account: id,
str: CrypStat { value: 0, stat: Stat::Str },
agi: CrypStat { value: 0, stat: Stat::Agi },
int: CrypStat { value: 0, stat: Stat::Int },
stam: CrypStat { value: 0, stat: Stat::Stam },
phys_dmg: CrypStat { value: 0, stat: Stat::Str },
spell_dmg: CrypStat { value: 0, stat: Stat::Int },
stamina: CrypStat { value: 0, stat: Stat::Stam },
hp: CrypStat { value: 0, stat: Stat::Hp },
lvl: 0,
xp: 0,
skills: vec![CrypSkill::new(Skill::Attack)],
statuses: vec![],
effects: vec![],
name: String::new()
};
}
@ -149,13 +157,17 @@ impl Cryp {
false => 2_u64.pow(self.lvl.into()),
};
let min = match self.lvl == 1 {
true => 2_u64,
false => 2_u64.pow(self.lvl.saturating_sub(1).into()),
};
self.xp = max;
self.str.set(rng.gen_range(1, max));
self.agi.set(rng.gen_range(1, max));
self.int.set(rng.gen_range(1, max));
self.stam.set(rng.gen_range(1, max));
self.hp.set(self.stam.value);
self.phys_dmg.set(rng.gen_range(min, max));
self.spell_dmg.set(rng.gen_range(min, max));
self.stamina.set(rng.gen_range(min, max));
self.hp.set(self.stamina.value);
self
}
@ -164,8 +176,12 @@ impl Cryp {
self.hp.value == 0
}
pub fn immune(&self, skill: Skill) -> bool {
self.effects.iter().any(|e| e.effect.immune(skill))
}
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,62 +223,27 @@ impl Cryp {
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);
pub fn reduce_effect_durations(&mut self, log: &mut Log) -> &mut Cryp {
self.effects = self.effects.clone().into_iter().filter_map(|mut effect| {
if s.duration == 0 {
effect.tick(self, log);
effect.duration = effect.duration.saturating_sub(1);
if effect.duration == 0 {
return None;
}
println!("reduced status {:?}", s);
return Some(s);
}).collect::<Vec<CrypStatus>>();
println!("reduced effect {:?}", effect);
return Some(effect);
}).collect::<Vec<CrypEffect>>();
self
}
pub fn rez(&mut self) -> &mut Cryp {
self.hp.set(self.stam.value);
self.hp.set(self.stamina.value);
self
}
pub fn roll(&self, skill: Skill) -> Roll {
let mut rng = thread_rng();
let base: u64 = rng.gen();
let stat = skill.stat(self);
let mut roll = Roll { base, result: base };
println!("{:?}'s stats", self.name);
println!("{:064b} <- finalised", roll.result);
roll.result = roll.result & stat.value;
println!("{:064b} & <- attribute roll", stat.value);
println!("{:064b} = {:?}", roll.result, roll.result);
println!("");
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() });
}
self
}
pub fn attack(&mut self, roll: Roll) -> &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() });
self
}
}
pub fn cryp_get(tx: &mut Transaction, id: Uuid, account_id: Uuid) -> Result<Cryp, Error> {
@ -350,13 +331,13 @@ mod tests {
// pub fn assign_str(&mut self, opp: &Cryp, plr_t: &mut Turn, opp_t: &Turn) -> &mut Cryp {
// // let final_str = opp_t.str.result.saturating_sub(plr_t.agi.result);
// // let blocked = opp_t.str.result.saturating_sub(final_str);
// // let final_str = opp_t.phys_dmg.result.saturating_sub(plr_t.agi.result);
// // let blocked = opp_t.phys_dmg.result.saturating_sub(final_str);
// let final_str = opp_t.str.result & !plr_t.agi.result;
// let blocked = opp_t.str.result & plr_t.agi.result;
// let final_str = opp_t.phys_dmg.result & !plr_t.agi.result;
// let blocked = opp_t.phys_dmg.result & plr_t.agi.result;
// plr_t.log.push(format!("{:064b} <- attacking roll {:?}", opp_t.str.result, opp_t.str.result));
// plr_t.log.push(format!("{:064b} <- attacking roll {:?}", opp_t.phys_dmg.result, opp_t.phys_dmg.result));
// // plr_t.log.push(format!("{:064b} <- blocking roll {:?}", plr_t.agi.result, plr_t.agi.result));
// plr_t.log.push(format!("{:064b} <- final str {:?} ({:?} blocked)", final_str, final_str, blocked));

View File

@ -12,6 +12,8 @@ use rpc::{GameStateParams, GameSkillParams, GamePveParams, GamePvpParams, GameTa
use cryp::{Cryp, cryp_get};
use skill::{Skill, Cast};
pub type Log = Vec<String>;
#[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::<Vec<String>>().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("<Skill Phase>".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<Uuid>, skill: Skill) -> Result<Uuid, Error> {
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"));
}
}
@ -218,9 +229,10 @@ impl Game {
}
let skill = Cast::new(source_cryp_id, team_id, target_team_id, skill);
let skill_id = skill.id;
self.stack.push(skill);
return Ok(skill.id);
return Ok(skill_id);
}
fn skill_phase_finished(&self) -> bool {
@ -228,16 +240,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::<Vec<&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());
self.log.push("<Target Phase>".to_string());
if self.phase != Phase::Skill {
panic!("game not in skill phase");
}
@ -250,7 +262,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 +323,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("<Resolve Phase>".to_string());
self.resolve_skills();
@ -330,8 +344,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 +356,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.resolved.push(*resolution);
// self.log.push(format!("{:?} uses {:?} on {:?}", source.name, skill.skill, target.name));
skill.set_resolution(&mut source, &mut target, &mut self.log);
self.resolved.push(skill.clone());
self.update_cryp(&mut source);
self.update_cryp(&mut target);
return *resolution;
return skill.clone();
}).collect::<Vec<Cast>>();
// now damage has all been assigned
// now Resolve has all been assigned
// handle cooldowns and statuses
self.progress_durations();
@ -378,7 +394,7 @@ impl Game {
}
// always reduce durations
cryp.reduce_statuses();
cryp.reduce_effect_durations(&mut self.log);
self.update_cryp(&mut cryp);
}
@ -455,7 +471,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 +769,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 +796,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 +823,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);
@ -821,9 +837,6 @@ mod tests {
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());
@ -855,10 +868,10 @@ 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);
}
}

View File

@ -5,6 +5,7 @@ extern crate env_logger;
extern crate bcrypt;
extern crate dotenv;
extern crate petgraph;
extern crate postgres;
extern crate r2d2;
extern crate r2d2_postgres;
@ -21,6 +22,7 @@ mod cryp;
mod game;
mod net;
mod skill;
mod passives;
mod rpc;
mod account;
mod item;

70
server/src/passives.rs Normal file
View File

@ -0,0 +1,70 @@
use petgraph::graph::{Graph, UnGraph, NodeIndex};
use petgraph::dot::{Dot, Config};
#[derive(Debug,Clone,Copy,PartialEq,Eq,Hash,PartialOrd,Ord,Serialize,Deserialize)]
pub struct Passive {
id: &'static str,
allocated: bool,
effect: usize,
}
impl Passive {
fn new(id: &'static str) -> Passive {
return Passive {
id,
allocated: false,
effect: 0,
};
}
}
pub fn create_passive_graph() -> UnGraph<Passive, ()> {
let mut gr = Graph::new_undirected();
let start = gr.add_node(Passive::new("START"));
let mut last;
let mut next;
// Natural Selection nodes
next = gr.add_node(Passive::new("NS"));
gr.add_edge(start, next, ());
last = next;
next = gr.add_node(Passive::new("NSPD0000"));
gr.add_edge(last, next, ());
last = next;
next = gr.add_node(Passive::new("NSPD0001"));
gr.add_edge(last, next, ());
last = next;
next = gr.add_node(Passive::new("NSPD0002"));
gr.add_edge(last, next, ());
last = next;
next = gr.add_node(Passive::new("NSPD0003"));
gr.add_edge(last, next, ());
last = next;
next = gr.add_node(Passive::new("NSBLOCK"));
gr.add_edge(last, next, ());
last = next;
return gr;
}
#[cfg(test)]
mod tests {
use passives::*;
#[test]
fn create_graph() {
let _graph = create_passive_graph();
// good shit;
// let nodes = graph.node_indices().collect::<Vec<NodeIndex>>();
// println!("{:?}", nodes[0]);
// println!("{:?}", graph.node_weight(nodes[0]));
// println!("{:?}", Dot::with_config(&graph, &[Config::EdgeNoLabel]));
}
}

View File

@ -1,23 +1,199 @@
// use rand::prelude::*;
use rand::{thread_rng, Rng};
use uuid::Uuid;
use cryp::{Cryp, CrypSkill, CrypStat};
use game::{Log};
use cryp::{Cryp, CrypEffect};
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub struct Roll {
#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)]
pub struct Cast {
pub id: Uuid,
pub skill: Skill,
pub source_team_id: Uuid,
pub source_cryp_id: Uuid,
pub target_cryp_id: Option<Uuid>,
pub target_team_id: Uuid,
pub resolution: Resolution,
}
impl 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(),
source_cryp_id,
source_team_id,
target_cryp_id,
target_team_id,
skill,
resolution: Resolution { base: 0, result: None },
};
}
pub fn set_resolution(&mut self, cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) -> &mut Cast {
self.resolution = self.skill.resolve(cryp, target, log);
self
}
pub fn set_target(&mut self, cryp_id: Uuid) -> &mut Cast {
self.target_cryp_id = Some(cryp_id);
self
}
pub fn used_cooldown(&self) -> bool {
return self.skill.cd().is_some();
}
}
#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)]
pub struct Resolution {
pub base: u64,
pub result: u64,
pub result: Option<u64>,
}
pub type Cooldown = Option<u8>;
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Effect {
// physical
Stun,
Block,
Bleed,
Leech,
Airborne,
Untouchable,
Deadly,
Vulnerable,
Fury,
Evasion,
Blind,
Snare,
// magic
Hex,
Banish,
Slow,
Haste,
Enslave,
Mesmerise,
Amplify,
// magic immunity
Immune,
// effects over time
Triage,
Decay,
Regen,
Degen,
SpeedDrain,
SpeedIncrease,
}
impl Effect {
pub fn immune(&self, skill: Skill) -> bool {
match self {
Effect::Block => match skill {
Skill::Stun |
Skill::Attack => true,
_ => false,
},
_ => false,
}
}
pub fn tick(&self, cryp_effect: &CrypEffect, target: &mut Cryp, log: &mut Log) -> &Effect {
match self {
Effect::Decay => decay_tick(target, cryp_effect, log),
Effect::Triage => triage_tick(target, cryp_effect, log),
_ => (),
}
self
}
}
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub struct Tick {
amount: u64
}
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Skill {
Attack,
Block,
Heal,
// -----------------
// Nature
// -----------------
Block, // reduce dmg
Parry, // avoid all dmg
Snare,
Paralyse,
Strangle, // physical dot and disable
Stun,
Dodge,
Evade, // actively evade
Evasion, // adds evasion to cryp
// -----------------
// Technology
// -----------------
Replicate,
Swarm,
Orbit,
Repair,
Scan, // track?
// -----------------
// Nonviolence
// -----------------
Heal,
Triage, // hot
Throw, // no dmg stun, adds vulnerable
Charm,
Calm,
Rez,
// -------------------
// Destruction
// -------------------
Blast,
Amplify,
Decay, // dot
Drain,
Curse,
Plague, // aoe dot
Ruin, // aoe
// -----------------
// Purity
// -----------------
Precision,
Inspire,
Slay,
Shield,
Silence,
Inquiry,
Purify,
// -----------------
// Chaos
// -----------------
Banish,
Hex,
Fear,
Taunt,
Pause, // speed slow
// used by tests, no cd, no dmg
TestTouch,
TestStun,
@ -28,10 +204,76 @@ 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),
// -----------------
// Nature
// -----------------
Skill::Block => Some(1), // reduce dmg
Skill::Parry => Some(1), // avoid all dmg
Skill::Snare => Some(2),
Skill::Paralyse => Some(3),
Skill::Strangle => Some(3),
// Strangle
Skill::Stun => Some(1),
Skill::Evade => Some(2),
Skill::Evasion => Some(3), // additional layer of dmg avoidance
// -----------------
// Technology
// -----------------
Skill::Replicate => Some(1),
Skill::Swarm => Some(3),
Skill::Orbit => Some(2),
Skill::Repair => Some(1),
Skill::Scan => Some(2), // track?
// -----------------
// Preservation
// -----------------
Skill::Heal => Some(1),
Skill::Triage => Some(1), // hot
Skill::Throw => Some(2), // no dmg stun, adds vulnerable
Skill::Charm => Some(2),
Skill::Calm => Some(2),
Skill::Rez => Some(4),
// -----------------
// Destruction
// -----------------
Skill::Blast => Some(1),
Skill::Amplify => Some(2),
Skill::Decay => Some(1), // dot
Skill::Drain => Some(2),
Skill::Curse => Some(2),
Skill::Plague => Some(2), // aoe dot
Skill::Ruin => Some(3), // aoe
// -----------------
// Purity
// -----------------
Skill::Precision => Some(1),
Skill::Inspire => Some(2),
Skill::Slay => Some(1),
Skill::Shield => Some(1),
Skill::Silence => Some(2),
Skill::Inquiry => Some(2),
Skill::Purify => Some(1),
// -----------------
// Chaos
// -----------------
Skill::Banish => Some(2),
Skill::Hex => Some(1),
Skill::Fear => Some(1),
Skill::Taunt => Some(2),
Skill::Pause => Some(2), // speed slow
// -----------------
// Test
// -----------------
Skill::TestTouch => None,
Skill::TestStun => None,
Skill::TestBlock => None,
@ -40,38 +282,185 @@ impl Skill {
pub fn speed(&self) -> u8 {
match self {
Skill::Attack => 10,
Skill::Block => 5,
Skill::Dodge => 5,
Skill::Heal => 2,
Skill::Stun => 2,
Skill::Attack => 5,
// -----------------
// Nature
// -----------------
Skill::Block => 10, // reduce dmg
Skill::Evade => 10,
Skill::Parry => 10, // avoid all dmg
Skill::Snare => 10,
Skill::Paralyse => 5,
Skill::Strangle => 5,
// Strangle
Skill::Stun => 5,
Skill::Evasion => 3, // additional layer of dmg avoidance
// -----------------
// Technology
// -----------------
Skill::Replicate => 1,
Skill::Swarm => 3,
Skill::Orbit => 2,
Skill::Repair => 1,
Skill::Scan => 2, // track?
// -----------------
// Preservation
// -----------------
Skill::Heal => 1,
Skill::Triage => 1, // hot
Skill::Throw => 2, // no dmg stun, adds vulnerable
Skill::Charm => 2,
Skill::Calm => 2,
Skill::Rez => 4,
// -----------------
// Destruction
// -----------------
Skill::Blast => 1,
Skill::Amplify => 2,
Skill::Decay => 1, // dot
Skill::Drain => 2,
Skill::Curse => 2,
Skill::Plague => 2, // aoe dot
Skill::Ruin => 3, // aoe
// -----------------
// Purity
// -----------------
Skill::Precision => 1,
Skill::Inspire => 2,
Skill::Slay => 1,
Skill::Shield => 1,
Skill::Silence => 2,
Skill::Inquiry => 2,
Skill::Purify => 1,
// -----------------
// Chaos
// -----------------
Skill::Banish => 2,
Skill::Hex => 1,
Skill::Fear => 1,
Skill::Taunt => 2,
Skill::Pause => 2, // speed slow
// -----------------
// Test
// -----------------
Skill::TestTouch => 10,
Skill::TestStun => 2,
Skill::TestBlock => 5,
Skill::TestStun => 5,
Skill::TestBlock => 10,
}
}
pub fn stat(&self, cryp: &Cryp) -> CrypStat {
pub fn resolve(&self, cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) -> Resolution {
let mut rng = thread_rng();
let base: u64 = rng.gen();
let res = Resolution { base, result: None };
// println!("{:?}'s stats", self.name);
// println!("{:064b} <- finalised", roll.result);
// roll.result = roll.result & stat.value;
// println!("{:064b} & <- attribute roll", stat.value);
// println!("{:064b} = {:?}", roll.result, roll.result);
// println!("");
// return Some(roll);
match self {
Skill::Attack => cryp.str,
Skill::Block => cryp.str,
Skill::Stun => cryp.str,
Skill::Dodge => cryp.agi,
Skill::Heal => cryp.int,
Skill::Attack => attack(cryp, target, log),
// -----------------
// Nature
// -----------------
Skill::Block => block(cryp, target, log),
Skill::Evade => panic!("nyi"), //
Skill::Parry => panic!("nyi"), // avoid all dmg
Skill::Snare => snare(cryp, target, log), // TODO prevent physical moves
// test skills
Skill::TestTouch => cryp.int,
Skill::TestStun => cryp.str,
Skill::TestBlock => cryp.str,
}
Skill::Paralyse => panic!("nyi"), // no physical moves
Skill::Strangle => panic!("nyi"), // no physical moves
Skill::Stun => stun(cryp, target, log),
Skill::Evasion => panic!("nyi"), // additional layer of dmg avoidance
// -----------------
// Technology
// -----------------
Skill::Replicate => panic!("nyi"),
Skill::Swarm => panic!("nyi"),
Skill::Orbit => panic!("nyi"),
Skill::Repair => panic!("nyi"),
Skill::Scan => panic!("nyi"), // track?
// -----------------
// Preservation
// -----------------
Skill::Heal => heal(cryp, target, log),
Skill::Triage => triage(cryp, target, log), // hot
Skill::Throw => throw(cryp, target, log), // no dmg stun, adds vulnerable
Skill::Charm => panic!("nyi"),
Skill::Calm => panic!("nyi"),
Skill::Rez => panic!("nyi"),
// -----------------
// Destruction
// -----------------
Skill::Blast => blast(cryp, target, log),
Skill::Amplify => amplify(cryp, target, log), // TODO increase magic dmg
Skill::Decay => decay(cryp, target, log), // dot
Skill::Drain => panic!("nyi"),
Skill::Curse => panic!("nyi"),
Skill::Plague => panic!("nyi"), // aoe dot
Skill::Ruin => panic!("nyi"), // aoe
// -----------------
// Purity
// -----------------
Skill::Precision => panic!("nyi"),
Skill::Inspire => panic!("nyi"),
Skill::Slay => panic!("nyi"),
Skill::Shield => panic!("nyi"),
Skill::Silence => panic!("nyi"),
Skill::Inquiry => panic!("nyi"),
Skill::Purify => panic!("nyi"),
// -----------------
// Chaos
// -----------------
Skill::Banish => banish(cryp, target, log), // TODO prevent all actions
Skill::Hex => hex(cryp, target, log), // todo prevent casting
Skill::Fear => panic!("nyi"),
Skill::Taunt => panic!("nyi"),
Skill::Pause => panic!("nyi"), // speed slow
// -----------------
// Test
// -----------------
Skill::TestTouch => (),
Skill::TestStun => stun(cryp, target, log),
Skill::TestBlock => block(cryp, target, log),
};
return res;
}
pub fn duration(&self) -> u8 {
match self {
Skill::Dodge => 1,
Skill::Evade => 1,
Skill::Stun => 2,
Skill::Block => 1,
Skill::Decay => 3,
Skill::Triage => 3,
Skill::TestBlock => 1,
Skill::TestStun => 2,
_ => panic!("{:?} does not have a duration", self),
@ -94,70 +483,197 @@ impl Skill {
}
}
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub struct Cast {
pub id: Uuid,
pub skill: Skill,
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>,
fn attack(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) {
log.push(format!("{:?} -> {:?} | Attack for {:?}", cryp.name, target.name, cryp.phys_dmg));
target.hp.reduce(cryp.phys_dmg.value);
}
impl Cast {
pub fn new(source_cryp_id: Uuid, source_team_id: Uuid, target_team_id: Option<Uuid>, skill: Skill) -> Cast {
fn stun(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) {
if !target.immune(Skill::Stun) {
let stun = CrypEffect { effect: Effect::Stun, duration: Skill::Stun.duration(), tick: None };
target.effects.push(stun);
log.push(format!("{:?} -> {:?} | {:?} for {:?}T", cryp.name, target.name, stun.effect, stun.duration));
} else {
log.push(format!("{:?} -> {:?} | {:?} immune", cryp.name, target.name, target.name));
}
}
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())
fn throw(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) {
if !target.immune(Skill::Throw) {
let stun = CrypEffect { effect: Effect::Stun, duration: Skill::Stun.duration(), tick: None };
let vulnerable = CrypEffect { effect: Effect::Vulnerable, duration: Skill::Stun.duration(), tick: None };
target.effects.push(stun);
target.effects.push(vulnerable);
log.push(format!("{:?} -> {:?} | {:?} for {:?}T", cryp.name, target.name, stun.effect, stun.duration));
log.push(format!("{:?} -> {:?} | {:?} for {:?}T", cryp.name, target.name, vulnerable.effect, vulnerable.duration));
} else {
log.push(format!("{:?} -> {:?} | {:?} immune", cryp.name, target.name, target.name));
}
}
fn block(_cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) {
let effect = CrypEffect { effect: Effect::Block, duration: Skill::Block.duration(), tick: None };
target.effects.push(effect);
log.push(format!("{:?} is {:?} for {:?}T", target.name, effect.effect, effect.duration));
}
fn snare(_cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) {
let effect = CrypEffect { effect: Effect::Snare, duration: Skill::Snare.duration(), tick: None };
target.effects.push(effect);
log.push(format!("{:?} is {:?} for {:?}T", target.name, effect.effect, effect.duration));
}
fn heal(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) {
let new_hp = *[
target.hp.value.saturating_add(cryp.spell_dmg.value),
target.stamina.value
].iter().min().unwrap();
let healing = new_hp.saturating_sub(target.hp.value);
let overhealing = target.hp.value.saturating_add(cryp.phys_dmg.value).saturating_sub(target.stamina.value);
target.hp.value = new_hp;
log.push(format!("{:?} -> {:?} | Heal for {:?} ({:?} OH)", cryp.name, target.name, healing, overhealing));
}
fn triage(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) {
let effect = CrypEffect {
effect: Effect::Triage,
duration: Skill::Triage.duration(),
tick: Some(Tick { amount: cryp.spell_dmg.value.wrapping_div(2) })
};
target.effects.push(effect);
log.push(format!("{:?} is {:?} for {:?}T", target.name, effect.effect, effect.duration));
}
return Cast {
id: Uuid::new_v4(),
source_cryp_id,
source_team_id,
target_cryp_id,
target_team_id,
skill,
roll: None,
fn triage_tick(target: &mut Cryp, effect: &CrypEffect, log: &mut Log) {
let tick = effect.tick.expect("no tick for triage");
let new_hp = *[
target.hp.value.saturating_add(tick.amount),
target.stamina.value
].iter().min().unwrap();
let healing = new_hp.saturating_sub(target.hp.value);
let overhealing = target.hp.value + tick.amount - target.stamina.value;
log.push(format!("{:?} | Triage healing for {:?} ({:?} OH)", target.name, healing, overhealing));
target.hp.value = new_hp;
}
fn blast(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) {
let amount = cryp.spell_dmg.value;
log.push(format!("{:?} -> {:?} | Blast for {:?}", cryp.name, target.name, amount));
target.hp.reduce(amount);
}
fn amplify(_cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) {
let effect = CrypEffect { effect: Effect::Amplify, duration: Skill::Amplify.duration(), tick: None };
target.effects.push(effect);
log.push(format!("{:?} is {:?} for {:?}T", target.name, effect.effect, effect.duration));
}
fn decay(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) {
let effect = CrypEffect {
effect: Effect::Decay,
duration: Skill::Decay.duration(),
tick: Some(Tick { amount: cryp.spell_dmg.value.wrapping_div(2) })
};
target.effects.push(effect);
log.push(format!("{:?} is {:?} for {:?}T", target.name, effect.effect, effect.duration));
}
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),
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);
self.roll = Some(roll);
self
fn decay_tick(target: &mut Cryp, effect: &CrypEffect, log: &mut Log) {
let tick = effect.tick.expect("no tick for decay");
target.hp.reduce(tick.amount);
log.push(format!("{:?} | Decay damage for {:?}", target.name, tick.amount));
}
pub fn set_target(&mut self, cryp_id: Uuid) -> &mut Cast {
self.target_cryp_id = Some(cryp_id);
self
fn hex(_cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) {
let effect = CrypEffect { effect: Effect::Hex, duration: Skill::Hex.duration(), tick: None };
target.effects.push(effect);
log.push(format!("{:?} is {:?} for {:?}T", target.name, effect.effect, effect.duration));
}
pub fn used_cooldown(self) -> bool {
let cs = CrypSkill::new(self.skill);
return cs.cd.is_some();
fn banish(_cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) {
let effect = CrypEffect { effect: Effect::Banish, duration: Skill::Banish.duration(), tick: None };
target.effects.push(effect);
log.push(format!("{:?} is {:?} for {:?}T", target.name, effect.effect, effect.duration));
}
#[cfg(test)]
mod tests {
use skill::*;
#[test]
fn heal_test() {
let mut x = Cryp::new()
.named(&"muji".to_string())
.level(8)
.learn(Skill::Heal)
.create();
let mut y = Cryp::new()
.named(&"camel".to_string())
.level(8)
.learn(Skill::Heal)
.create();
x.hp.reduce(5);
let mut log = vec![];
heal(&mut y, &mut x, &mut log);
println!("{:?}", log);
}
#[test]
fn decay_test() {
let mut x = Cryp::new()
.named(&"muji".to_string())
.level(8)
.create();
let mut y = Cryp::new()
.named(&"camel".to_string())
.level(8)
.create();
let mut log = vec![];
decay(&mut x, &mut y, &mut log);
assert!(y.effects.iter().any(|e| e.effect == Effect::Decay));
y.reduce_effect_durations(&mut log);
let decay = y.effects.iter().find(|e| e.effect == Effect::Decay);
assert!(y.hp.value == y.stamina.value.saturating_sub(decay.unwrap().tick.unwrap().amount));
}
#[test]
fn triage_test() {
let mut x = Cryp::new()
.named(&"muji".to_string())
.level(8)
.create();
let mut y = Cryp::new()
.named(&"pretaliation".to_string())
.level(8)
.create();
let mut log = vec![];
// ensure it doesn't have 0 sd
x.spell_dmg.value = 50;
y.hp.reduce(5);
let prev_hp = y.hp.value;
triage(&mut x, &mut y, &mut log);
assert!(y.effects.iter().any(|e| e.effect == Effect::Triage));
y.reduce_effect_durations(&mut log);
assert!(y.hp.value > prev_hp);
}
}