mnml/server/src/skill.rs
2018-11-14 16:10:27 +11:00

874 lines
26 KiB
Rust
Executable File

use rand::{thread_rng, Rng};
use uuid::Uuid;
use game::{Log};
use cryp::{Cryp, CrypEffect};
#[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,
}
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,
};
}
pub fn new_tick(source: &mut Cryp, target: &mut Cryp, skill: Skill) -> Cast {
let mut cast = Cast::new(source.id, source.account, Some(target.account), skill);
cast.set_target(target.id);
return cast;
}
pub fn set_resolution(&mut self, cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) -> &mut Cast {
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: 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,
Curse,
Banish,
Slow,
Haste,
Enslave,
Mesmerise,
Amplify,
Silence,
// magic immunity
Immune,
// effects over time
Triage,
Decay,
Regen,
Drain,
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 prevents_casting(&self, skill: Skill) -> bool {
match self {
Effect::Stun => true,
Effect::Silence => match skill.cast_type() {
Damage::Magic => true,
Damage::Physical => false,
},
_ => false,
}
}
}
pub enum Damage {
Physical,
Magic,
}
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Skill {
Attack,
// -----------------
// Nature
// -----------------
Block, // reduce dmg
Parry, // avoid all dmg
Snare,
Paralyse,
Strangle, // physical dot and disable
Stun,
Evade, // actively evade
Evasion, // adds evasion to cryp
// -----------------
// Technology
// -----------------
Replicate,
Swarm,
Orbit,
Repair,
Scan, // track?
// -----------------
// Nonviolence
// -----------------
Heal,
Triage, // hot
TriageTick,
Throw, // no dmg stun, adds vulnerable
Charm,
Calm,
Rez,
// -------------------
// Destruction
// -------------------
Blast,
Amplify,
Decay, // dot
DecayTick, // dot
Drain,
DrainTick,
Curse,
Plague, // aoe dot
Ruin, // aoe
// -----------------
// Purity
// -----------------
Inspire,
Slay,
Shield,
Silence,
Inquiry,
Purify,
// Precision,
// -----------------
// Chaos
// -----------------
Banish,
Hex,
Fear,
Taunt,
Pause, // speed slow
// used by tests, no cd, no dmg
TestTouch,
TestStun,
TestBlock,
TestDrain,
}
impl Skill {
pub fn cd(&self) -> Cooldown {
match self {
Skill::Attack => None,
// -----------------
// 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::TriageTick => None,
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::DecayTick => None,
Skill::Drain => Some(2),
Skill::DrainTick => None,
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,
Skill::TestDrain => None,
}
}
pub fn cast_type(&self) -> Damage {
match self {
Skill::Attack => Damage::Physical,
// -----------------
// Nature
// -----------------
Skill::Block => Damage::Physical, // reduce dmg
Skill::Evade => Damage::Physical,
Skill::Parry => Damage::Physical, // avoid all dmg
Skill::Snare => Damage::Physical,
Skill::Paralyse => Damage::Physical,
Skill::Strangle => Damage::Physical,
// Strangle
Skill::Stun => Damage::Physical,
Skill::Evasion => Damage::Physical, // additional layer of dmg avoidance
// -----------------
// Technology
// -----------------
Skill::Replicate => Damage::Physical,
Skill::Swarm => Damage::Physical,
Skill::Orbit => Damage::Physical,
Skill::Repair => Damage::Physical,
Skill::Scan => Damage::Physical, // track?
// -----------------
// Preservation
// -----------------
Skill::Heal => Damage::Physical,
Skill::Triage => Damage::Physical, // hot
Skill::TriageTick => Damage::Physical, // hot
Skill::Throw => Damage::Physical, // no dmg stun, adds vulnerable
Skill::Charm => Damage::Physical,
Skill::Calm => Damage::Physical,
Skill::Rez => Damage::Physical,
// -----------------
// Destruction
// -----------------
Skill::Blast => Damage::Magic,
Skill::Amplify => Damage::Magic,
Skill::Decay => Damage::Magic, // dot
Skill::DecayTick => Damage::Magic, // hot
Skill::Drain => Damage::Magic,
Skill::DrainTick => Damage::Magic, // hot
Skill::Curse => Damage::Magic,
Skill::Plague => Damage::Magic, // aoe dot
Skill::Ruin => Damage::Magic, // aoe
// -----------------
// Purity
// -----------------
// Skill::Precision => 1,
Skill::Inspire => Damage::Physical,
Skill::Slay => Damage::Physical,
Skill::Shield => Damage::Magic,
Skill::Silence => Damage::Magic,
Skill::Inquiry => Damage::Magic,
Skill::Purify => Damage::Magic,
// -----------------
// Chaos
// -----------------
Skill::Banish => Damage::Magic,
Skill::Hex => Damage::Magic,
Skill::Fear => Damage::Magic,
Skill::Taunt => Damage::Magic,
Skill::Pause => Damage::Magic, // extend durations
// Skill::Lag => 2, //
// -----------------
// Test
// -----------------
Skill::TestTouch => Damage::Physical,
Skill::TestStun => Damage::Physical,
Skill::TestBlock => Damage::Physical,
Skill::TestDrain => Damage::Magic,
}
}
pub fn speed(&self) -> u8 {
match self {
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::TriageTick => 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::DecayTick => 2, // hot
Skill::Drain => 2,
Skill::DrainTick => 2, // hot
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, // extend durations
// Skill::Lag => 2, //
// -----------------
// Test
// -----------------
Skill::TestTouch => 10,
Skill::TestStun => 5,
Skill::TestBlock => 10,
Skill::TestDrain => 10,
}
}
pub fn resolve(&self, cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) {
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 => 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
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::TriageTick => triage_tick(cryp, target, log), // hot
Skill::Throw => throw(cryp, target, log), // no dmg stun, adds vulnerable
Skill::Charm => panic!("nyi"), // cast random spell on teammate
Skill::Calm => panic!("nyi"), // remove fear, taunt
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::DecayTick => decay_tick(cryp, target, log), // hot
Skill::Drain => drain(cryp, target, log),
Skill::DrainTick => drain_tick(cryp, target, log), // hot
Skill::Curse => curse(cryp, target, log),
Skill::Plague => panic!("nyi"), // aoe dot
Skill::Ruin => panic!("nyi"), // aoe
// -----------------
// Purity
// -----------------
// Skill::Precision => panic!("nyi"),
Skill::Inspire => panic!("nyi"), // increased phys dmg
Skill::Slay => panic!("nyi"), // phys dmg mult by target magic dmg
Skill::Shield => panic!("nyi"),
Skill::Silence => silence(cryp, target, log),
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"), // cast random spell on self
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),
Skill::TestDrain => drain(cryp, target, log),
};
}
pub fn duration(&self) -> u8 {
match self {
Skill::Evade => 1,
Skill::Stun => 2,
Skill::Block => 1,
Skill::Decay => 3,
Skill::Drain => 3,
Skill::Triage => 3,
Skill::Silence => 3,
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 {
!cryp.effects.iter().any(|e| e.effect.prevents_casting(*self))
}
}
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);
}
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));
}
}
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!("{:?} < {:?} 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!("{:?} < {:?} 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(Cast::new_tick(cryp, target, Skill::TriageTick)),
};
target.effects.push(effect);
log.push(format!("{:?} < {:?} for {:?}T", target.name, effect.effect, effect.duration));
}
fn triage_tick(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!("{:?} -> {:?} | Triage for {:?} ({:?} OH)", cryp.name, 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!("{:?} < {:?} 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(Cast::new_tick(cryp, target, Skill::DecayTick)),
};
target.effects.push(effect);
log.push(format!("{:?} < {:?} for {:?}T", target.name, effect.effect, effect.duration));
}
fn decay_tick(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) {
let amount = cryp.spell_dmg.value;
log.push(format!("{:?} -> {:?} | Decay for {:?}", cryp.name, target.name, amount));
target.hp.reduce(amount);
}
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!("{:?} < {:?} for {:?}T", target.name, effect.effect, effect.duration));
}
fn curse(_cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) {
let effect = CrypEffect { effect: Effect::Curse, duration: Skill::Curse.duration(), tick: None };
target.effects.push(effect);
log.push(format!("{:?} < {:?} for {:?}T", target.name, effect.effect, effect.duration));
}
fn drain(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) {
let effect = CrypEffect {
effect: Effect::Drain,
duration: Skill::Drain.duration(),
tick: Some(Cast::new_tick(cryp, target, Skill::DrainTick)),
};
target.effects.push(effect);
log.push(format!("{:?} < {:?} for {:?}T", target.name, effect.effect, effect.duration));
}
fn drain_tick(cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) {
// damage part
let damage = cryp.spell_dmg.value;
target.hp.reduce(damage);
log.push(format!("{:?} | Drain damage {:?}", target.name, damage));
// healing part
let new_hp = *[
cryp.hp.value.saturating_add(damage),
cryp.stamina.value
].iter().min().unwrap();
cryp.hp.value = new_hp;
let healing = new_hp.saturating_sub(cryp.hp.value);
let overhealing = cryp.hp.value + damage - cryp.stamina.value;
log.push(format!("{:?} | Drain healing {:?} ({:?} OH)", cryp.name, healing, overhealing));
}
fn silence(_cryp: &mut Cryp, target: &mut Cryp, log: &mut Log) {
let effect = CrypEffect { effect: Effect::Silence, duration: Skill::Silence.duration(), tick: None };
target.effects.push(effect);
log.push(format!("{:?} < {:?} for {:?}T", target.name, effect.effect, effect.duration));
}
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!("{:?} < {:?} 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);
}
#[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);
}
#[test]
fn silence_test() {
let mut x = Cryp::new()
.named(&"muji".to_string())
.level(8)
.create();
let mut log = vec![];
silence(&mut x.clone(), &mut x, &mut log);
assert!(x.effects.iter().any(|e| e.effect == Effect::Silence));
assert!(!Skill::Decay.castable(&x));
}
}
// #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
// pub enum Skill {
// Stoney,
// Evasive,
// }
// impl Skill {
// pub fn apply(&self, roll: Roll) -> Roll {
// match self {
// Skill::Stoney => stoney(self, roll),
// Skill::Evasive => evasive(self, roll),
// }
// }
// }
// fn stoney(_s: &Skill, mut roll: Roll) -> Roll {
// let effect = 0b11110000;
// match roll.kind {
// StatKind::Def => {
// // println!("{:064b} | <- {:?}", effect, s);
// roll.result = roll.result | effect;
// roll
// },
// _ => roll,
// }
// }
// fn evasive(_s: &Skill, mut roll: Roll) -> Roll {
// match roll.kind {
// StatKind::Def => {
// if roll.result.is_power_of_two() {
// roll.result = u64::max_value()
// }
// roll
// },
// _ => roll,
// }
// }