Merge tag '1.13.0' into develop
1.13.0
This commit is contained in:
commit
40f3bb5be6
@ -5,6 +5,9 @@ _ntr_
|
|||||||
* can't reset password without knowing password =\
|
* can't reset password without knowing password =\
|
||||||
* hard reload client on version change
|
* hard reload client on version change
|
||||||
|
|
||||||
|
decay reflected not applied
|
||||||
|
black out timer when game finished
|
||||||
|
|
||||||
* audio
|
* audio
|
||||||
* animation effects
|
* animation effects
|
||||||
* vbox combine / buy / equip etc
|
* vbox combine / buy / equip etc
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mnml-client",
|
"name": "mnml-client",
|
||||||
"version": "1.12.4",
|
"version": "1.13.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -27,6 +27,3 @@ ssh -q "$TARGET" ls -lah "$CLIENT_DIST_DIR"
|
|||||||
|
|
||||||
echo "restarting mnml service"
|
echo "restarting mnml service"
|
||||||
ssh -q -t "$TARGET" sudo service mnml restart && sleep 1 && systemctl --no-pager status mnml
|
ssh -q -t "$TARGET" sudo service mnml restart && sleep 1 && systemctl --no-pager status mnml
|
||||||
|
|
||||||
echo "restarting nginx service"
|
|
||||||
ssh -q -t "$TARGET" sudo service nginx restart && sleep 1 && systemctl --no-pager status nginx
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mnml-client",
|
"name": "mnml-client",
|
||||||
"version": "1.12.4",
|
"version": "1.13.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -45,7 +45,7 @@ function Controls(args) {
|
|||||||
const now = animating ? zero : Date.now();
|
const now = animating ? zero : Date.now();
|
||||||
const end = Date.parse(game.phase_end);
|
const end = Date.parse(game.phase_end);
|
||||||
|
|
||||||
const timerPct = game.phase_end
|
const timerPct = game.phase_end || !game.phase == 'Finished'
|
||||||
? ((now - zero) / (end - zero) * 100)
|
? ((now - zero) / (end - zero) * 100)
|
||||||
: 100;
|
: 100;
|
||||||
|
|
||||||
|
|||||||
@ -316,6 +316,7 @@ function createSocket(events) {
|
|||||||
case 'no constructs selected': return events.errorPrompt('select_constructs');
|
case 'no constructs selected': return events.errorPrompt('select_constructs');
|
||||||
case 'node requirements not met': return events.errorPrompt('complete_nodes');
|
case 'node requirements not met': return events.errorPrompt('complete_nodes');
|
||||||
case 'construct at max skills (4)': return events.errorPrompt('max_skills');
|
case 'construct at max skills (4)': return events.errorPrompt('max_skills');
|
||||||
|
case 'instance missing': return window.location.reload();
|
||||||
|
|
||||||
default: return errorToast(error);
|
default: return errorToast(error);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mnml_core"
|
name = "mnml_core"
|
||||||
version = "1.12.4"
|
version = "1.13.0"
|
||||||
authors = ["ntr <ntr@smokestack.io>", "mashy <mashy@mnml.gg>"]
|
authors = ["ntr <ntr@smokestack.io>", "mashy <mashy@mnml.gg>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@ -65,7 +65,7 @@ impl ConstructSkill {
|
|||||||
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
|
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
|
||||||
pub enum EffectMeta {
|
pub enum EffectMeta {
|
||||||
CastOnHit(Skill), // maybe needs source/target
|
CastOnHit(Skill), // maybe needs source/target
|
||||||
CastTick { source: Uuid, target: Uuid, skill: Skill, speed: usize, amount: usize },
|
CastTick { source: Uuid, target: Uuid, skill: Skill, speed: usize, amount: usize, id: Uuid },
|
||||||
AddedDamage(usize),
|
AddedDamage(usize),
|
||||||
Multiplier(usize),
|
Multiplier(usize),
|
||||||
}
|
}
|
||||||
@ -100,12 +100,19 @@ impl ConstructEffect {
|
|||||||
|
|
||||||
pub fn get_skill(&self) -> Option<Skill> {
|
pub fn get_skill(&self) -> Option<Skill> {
|
||||||
match self.meta {
|
match self.meta {
|
||||||
Some(EffectMeta::CastTick { source: _, target: _, skill, speed: _, amount: _ }) => Some(skill),
|
Some(EffectMeta::CastTick { source: _, target: _, skill, speed: _, amount: _, id: _ }) => Some(skill),
|
||||||
Some(EffectMeta::CastOnHit(s)) => Some(s),
|
Some(EffectMeta::CastOnHit(s)) => Some(s),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_tick_id(&self) -> Option<Uuid> {
|
||||||
|
match self.meta {
|
||||||
|
Some(EffectMeta::CastTick { source: _, target: _, skill: _, speed: _, amount: _, id }) => Some(id),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
|
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
|
||||||
@ -561,7 +568,7 @@ impl Construct {
|
|||||||
fn tick_damage(&self, effect: Effect) -> usize {
|
fn tick_damage(&self, effect: Effect) -> usize {
|
||||||
match self.effects.iter().find_map(|ce| match ce.effect == effect {
|
match self.effects.iter().find_map(|ce| match ce.effect == effect {
|
||||||
true => match ce.meta {
|
true => match ce.meta {
|
||||||
Some(EffectMeta::CastTick { source: _, target: _, skill: _, speed: _, amount }) => Some(amount),
|
Some(EffectMeta::CastTick { source: _, target: _, skill: _, speed: _, amount, id: _ }) => Some(amount),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
false => None,
|
false => None,
|
||||||
@ -1033,8 +1040,6 @@ impl Construct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn damage_trigger_casts(&mut self, cast: &Cast, event: &Event) -> Vec<Cast> {
|
pub fn damage_trigger_casts(&mut self, cast: &Cast, event: &Event) -> Vec<Cast> {
|
||||||
if self.is_ko() { return vec![] }
|
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::Damage { construct: _, colour, amount: _, mitigation: _, display: _ } => {
|
Event::Damage { construct: _, colour, amount: _, mitigation: _, display: _ } => {
|
||||||
let mut casts = vec![];
|
let mut casts = vec![];
|
||||||
@ -1049,6 +1054,9 @@ impl Construct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// electrocute is a special case, so we only return after we check it when ko
|
||||||
|
if self.is_ko() { return casts }
|
||||||
|
|
||||||
if self.affected(Effect::Absorb) {
|
if self.affected(Effect::Absorb) {
|
||||||
let ConstructEffect { effect: _, duration: _, meta } =
|
let ConstructEffect { effect: _, duration: _, meta } =
|
||||||
self.effects.iter().find(|e| e.effect == Effect::Absorb).unwrap();
|
self.effects.iter().find(|e| e.effect == Effect::Absorb).unwrap();
|
||||||
|
|||||||
@ -125,6 +125,12 @@ impl Effect {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// electrocute always goes off baybee
|
||||||
|
// even if you are stunned particularly
|
||||||
|
if [Skill::Electrocute, Skill::ElectrocutePlus, Skill::ElectrocutePlusPlus].contains(&skill) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Effect::Stun => true,
|
Effect::Stun => true,
|
||||||
Effect::Banish => true,
|
Effect::Banish => true,
|
||||||
|
|||||||
170
core/src/game.rs
170
core/src/game.rs
@ -428,8 +428,12 @@ impl Game {
|
|||||||
.cloned()
|
.cloned()
|
||||||
.filter_map(|e| e.meta)
|
.filter_map(|e| e.meta)
|
||||||
.filter_map(move |m| match m {
|
.filter_map(move |m| match m {
|
||||||
EffectMeta::CastTick { source, target, skill, speed, amount: _ } =>
|
EffectMeta::CastTick { source, target, skill, speed, amount: _, id } =>
|
||||||
Some(Cast::new(source, c.account, target, skill).set_speed(speed)),
|
Some(
|
||||||
|
Cast::new(source, c.account, target, skill)
|
||||||
|
.set_speed(speed)
|
||||||
|
.set_id(id)
|
||||||
|
),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -506,6 +510,20 @@ impl Game {
|
|||||||
fn resolve(&mut self, cast: Cast) -> &mut Game {
|
fn resolve(&mut self, cast: Cast) -> &mut Game {
|
||||||
if self.finished() { return self }
|
if self.finished() { return self }
|
||||||
|
|
||||||
|
// match tick skills with the effect on the target
|
||||||
|
// if no match is found the effect must have been removed during this turn
|
||||||
|
// and the skill should no longer resolve
|
||||||
|
if cast.skill.is_tick() {
|
||||||
|
let effect_match = self.construct(cast.target).effects.iter()
|
||||||
|
.filter_map(|ce| ce.get_tick_id())
|
||||||
|
.find(|id| cast.id == *id)
|
||||||
|
.is_some();
|
||||||
|
|
||||||
|
if !effect_match {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If the skill is disabled for source nothing else will happen
|
// If the skill is disabled for source nothing else will happen
|
||||||
if let Some(effects) = self.construct(cast.source).disabled(cast.skill) {
|
if let Some(effects) = self.construct(cast.source).disabled(cast.skill) {
|
||||||
self.add_resolution(&cast, &Event::Disable { construct: cast.source, effects });
|
self.add_resolution(&cast, &Event::Disable { construct: cast.source, effects });
|
||||||
@ -1054,6 +1072,7 @@ mod tests {
|
|||||||
.learn(Skill::Block)
|
.learn(Skill::Block)
|
||||||
.learn(Skill::Counter)
|
.learn(Skill::Counter)
|
||||||
.learn(Skill::Siphon)
|
.learn(Skill::Siphon)
|
||||||
|
.learn(Skill::Purify)
|
||||||
.learn(Skill::Amplify)
|
.learn(Skill::Amplify)
|
||||||
.learn(Skill::Stun)
|
.learn(Skill::Stun)
|
||||||
.learn(Skill::Ruin)
|
.learn(Skill::Ruin)
|
||||||
@ -1069,6 +1088,7 @@ mod tests {
|
|||||||
.learn(Skill::Block)
|
.learn(Skill::Block)
|
||||||
.learn(Skill::Counter)
|
.learn(Skill::Counter)
|
||||||
.learn(Skill::Siphon)
|
.learn(Skill::Siphon)
|
||||||
|
.learn(Skill::Purify)
|
||||||
.learn(Skill::Amplify)
|
.learn(Skill::Amplify)
|
||||||
.learn(Skill::Stun)
|
.learn(Skill::Stun)
|
||||||
.learn(Skill::Block);
|
.learn(Skill::Block);
|
||||||
@ -1874,85 +1894,6 @@ mod tests {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn tick_removal_test() {
|
|
||||||
// let mut game = create_test_game();
|
|
||||||
|
|
||||||
// let x_player = game.players[0].clone();
|
|
||||||
// let y_player = game.players[1].clone();
|
|
||||||
|
|
||||||
// let x_construct = x_player.constructs[0].clone();
|
|
||||||
// let y_construct = y_player.constructs[0].clone();
|
|
||||||
|
|
||||||
// // make the purify construct super fast so it beats out decay
|
|
||||||
// game.construct_by_id(y_construct.id).unwrap().speed.force(10000000);
|
|
||||||
|
|
||||||
// game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Decay);
|
|
||||||
// while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Decay).is_some() {
|
|
||||||
// game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Siphon);
|
|
||||||
// while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Siphon).is_some() {
|
|
||||||
// game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// game.construct_by_id(y_construct.id).unwrap().learn_mut(Skill::Purify);
|
|
||||||
// while game.construct_by_id(y_construct.id).unwrap().skill_on_cd(Skill::Purify).is_some() {
|
|
||||||
// game.construct_by_id(y_construct.id).unwrap().reduce_cooldowns();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // apply buff
|
|
||||||
// game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Decay).unwrap();
|
|
||||||
// game.player_ready(x_player.id).unwrap();
|
|
||||||
// game.player_ready(y_player.id).unwrap();
|
|
||||||
// game = game.resolve_phase_start();
|
|
||||||
// assert!(game.construct_by_id(y_construct.id).unwrap().affected(Effect::Decay));
|
|
||||||
|
|
||||||
// let Resolution { source: _, target: _, Resolution, stages: _ } = game.Resolutions.last().unwrap().pop().unwrap();
|
|
||||||
// match Resolution {
|
|
||||||
// Resolution::Damage { amount: _, skill, mitigation: _, colour: _ } => assert_eq!(skill, Skill::DecayTick),
|
|
||||||
// _ => panic!("not decay"),
|
|
||||||
// };
|
|
||||||
|
|
||||||
// game.Resolutions.clear();
|
|
||||||
|
|
||||||
// // remove
|
|
||||||
// game.add_skill(y_player.id, y_construct.id, y_construct.id, Skill::Purify).unwrap();
|
|
||||||
// game.player_ready(x_player.id).unwrap();
|
|
||||||
// game.player_ready(y_player.id).unwrap();
|
|
||||||
// game = game.resolve_phase_start();
|
|
||||||
|
|
||||||
// while let Some(Resolution { source: _, target: _, Resolution, stages: _ }) = game.Resolutions.last().unwrap().pop() {
|
|
||||||
// match Resolution {
|
|
||||||
// Resolution::Damage { amount: _, skill: _, mitigation: _, colour: _ } =>
|
|
||||||
// panic!("{:?} damage Resolution", Resolution),
|
|
||||||
// _ => (),
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// game.add_skill(y_player.id, x_construct.id, y_construct.id, Skill::Siphon).unwrap();
|
|
||||||
// game.player_ready(x_player.id).unwrap();
|
|
||||||
// game.player_ready(y_player.id).unwrap();
|
|
||||||
// game = game.resolve_phase_start();
|
|
||||||
|
|
||||||
// game.Resolutions.clear();
|
|
||||||
|
|
||||||
// game.add_skill(y_player.id, y_construct.id, y_construct.id, Skill::Purify).unwrap();
|
|
||||||
// game.player_ready(x_player.id).unwrap();
|
|
||||||
// game.player_ready(y_player.id).unwrap();
|
|
||||||
// game = game.resolve_phase_start();
|
|
||||||
|
|
||||||
// while let Some(Resolution { source: _, target: _, Resolution, stages: _ }) = game.Resolutions.last().unwrap().pop() {
|
|
||||||
// match Resolution {
|
|
||||||
// Resolution::Damage { amount: _, skill: _, mitigation: _, colour: _ } =>
|
|
||||||
// panic!("{:#?} {:#?} damage Resolution", game.Resolutions, Resolution),
|
|
||||||
// _ => (),
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn upkeep_test() {
|
fn upkeep_test() {
|
||||||
let mut game = create_2v2_test_game();
|
let mut game = create_2v2_test_game();
|
||||||
@ -2269,8 +2210,41 @@ mod tests {
|
|||||||
|
|
||||||
assert!(effect_events == 2);
|
assert!(effect_events == 2);
|
||||||
assert!(electrocute_dmg_events == 1); // second electrocute application deals no damage
|
assert!(electrocute_dmg_events == 1); // second electrocute application deals no damage
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn electrocute_ko_test() {
|
||||||
|
let mut game = create_2v2_test_game();
|
||||||
|
let player_id = game.players[0].id;
|
||||||
|
let source = game.players[0].constructs[0].id;
|
||||||
|
let target = game.players[1].constructs[0].id;
|
||||||
|
|
||||||
|
game.players[1].constructs[0].blue_life.force(0);
|
||||||
|
game.players[1].constructs[0].green_life.force(1);
|
||||||
|
|
||||||
|
game.resolve(Cast::new(source, player_id, target, Skill::Electrify));
|
||||||
|
game.resolve(Cast::new(source, player_id, target, Skill::Blast));
|
||||||
|
|
||||||
|
let last = game.resolutions.len() - 1;
|
||||||
|
let resolutions = &game.resolutions[last];
|
||||||
|
|
||||||
|
// println!("{:#?}", resolutions);
|
||||||
|
|
||||||
|
assert!(resolutions.iter().any(|r| match r.event {
|
||||||
|
Event::Damage { construct, colour, amount, mitigation: _, display: _ } =>
|
||||||
|
r.skill == Skill::Electrocute && construct == source && amount > 0 && colour == Colour::Blue,
|
||||||
|
_ => false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let effect_events = resolutions.iter().filter(|r| match r.event {
|
||||||
|
Event::Effect { construct, effect, duration: _, display: _ } =>
|
||||||
|
construct == source && effect == Effect::Electrocute,
|
||||||
|
_ => false,
|
||||||
|
}).count();
|
||||||
|
|
||||||
|
// println!("{:?}", effect_events);
|
||||||
|
|
||||||
|
assert!(effect_events == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -2524,4 +2498,38 @@ mod tests {
|
|||||||
assert_eq!(siphon_tick_dmg, siphon_dmg);
|
assert_eq!(siphon_tick_dmg, siphon_dmg);
|
||||||
assert_eq!(siphon_tick_speed, siphon_speed);
|
assert_eq!(siphon_tick_speed, siphon_speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tick_removal_test() {
|
||||||
|
let mut game = create_test_game();
|
||||||
|
let player_id = game.players[0].id;
|
||||||
|
let opponent_id = game.players[1].id;
|
||||||
|
let source = game.players[0].constructs[0].id;
|
||||||
|
let target = game.players[1].constructs[0].id;
|
||||||
|
|
||||||
|
game.add_skill(player_id, source, target, Skill::Siphon).unwrap();
|
||||||
|
|
||||||
|
game.player_ready(player_id).unwrap();
|
||||||
|
game.player_ready(opponent_id).unwrap();
|
||||||
|
|
||||||
|
game = game.resolve_phase_start();
|
||||||
|
|
||||||
|
game.add_skill(player_id, source, target, Skill::Purify).unwrap();
|
||||||
|
|
||||||
|
game.player_ready(player_id).unwrap();
|
||||||
|
game.player_ready(opponent_id).unwrap();
|
||||||
|
|
||||||
|
game = game.resolve_phase_start();
|
||||||
|
|
||||||
|
// println!("{:#?}", game.resolutions);
|
||||||
|
|
||||||
|
let last = game.resolutions.len() - 1;
|
||||||
|
let resolutions = &game.resolutions[last];
|
||||||
|
|
||||||
|
// There should be no damage events on the target
|
||||||
|
assert!(resolutions.iter().any(|r| match r.event {
|
||||||
|
Event::Damage { construct: _, colour: _, amount: _, mitigation: _, display: _ } => true,
|
||||||
|
_ => false,
|
||||||
|
}) == false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,6 +41,15 @@ impl Cast {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// used for ticks to match
|
||||||
|
// a cast with an effect
|
||||||
|
pub fn set_id(self, id: Uuid) -> Cast {
|
||||||
|
Cast {
|
||||||
|
id,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn resolve(self, game: &mut Game) {
|
pub fn resolve(self, game: &mut Game) {
|
||||||
match self.skill {
|
match self.skill {
|
||||||
Skill::Attack => attack(self, game, Attack::Base),
|
Skill::Attack => attack(self, game, Attack::Base),
|
||||||
@ -729,6 +738,13 @@ impl Skill {
|
|||||||
|
|
||||||
pub fn ko_castable(&self) -> bool {
|
pub fn ko_castable(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
|
||||||
|
// electrocute always goes off
|
||||||
|
Skill::Electrocute|
|
||||||
|
Skill::ElectrocutePlus |
|
||||||
|
Skill::ElectrocutePlusPlus |
|
||||||
|
|
||||||
|
// ticks happen after death
|
||||||
Skill::ElectrocuteTick |
|
Skill::ElectrocuteTick |
|
||||||
Skill::DecayTick |
|
Skill::DecayTick |
|
||||||
Skill::SiphonTick |
|
Skill::SiphonTick |
|
||||||
@ -1134,7 +1150,7 @@ fn siphon(cast: Cast, game: &mut Game, values: Siphon) {
|
|||||||
Action::Effect {
|
Action::Effect {
|
||||||
construct: cast.target,
|
construct: cast.target,
|
||||||
effect: ConstructEffect { effect: Effect::Siphon, duration: values.duration(), meta:
|
effect: ConstructEffect { effect: Effect::Siphon, duration: values.duration(), meta:
|
||||||
Some(EffectMeta::CastTick { source: cast.source, target: cast.target, skill: Skill::SiphonTick, speed: cast.speed, amount }) },
|
Some(EffectMeta::CastTick { id: Uuid::new_v4(), source: cast.source, target: cast.target, skill: Skill::SiphonTick, speed: cast.speed, amount }) },
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1729,7 +1745,7 @@ fn decay(cast: Cast, game: &mut Game, values: Decay) {
|
|||||||
Action::Effect {
|
Action::Effect {
|
||||||
construct: cast.target,
|
construct: cast.target,
|
||||||
effect: ConstructEffect { effect: Effect::Decay, duration: values.decay_duration(), meta:
|
effect: ConstructEffect { effect: Effect::Decay, duration: values.decay_duration(), meta:
|
||||||
Some(EffectMeta::CastTick { source: cast.source, target: cast.target, skill: Skill::DecayTick, speed: cast.speed, amount }) },
|
Some(EffectMeta::CastTick { id: Uuid::new_v4(), source: cast.source, target: cast.target, skill: Skill::DecayTick, speed: cast.speed, amount }) },
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1837,7 +1853,7 @@ fn electrocute(cast: Cast, game: &mut Game, values: Electrocute) {
|
|||||||
Action::Effect {
|
Action::Effect {
|
||||||
construct: cast.target,
|
construct: cast.target,
|
||||||
effect: ConstructEffect { effect: Effect::Electrocute, duration: values.duration(), meta:
|
effect: ConstructEffect { effect: Effect::Electrocute, duration: values.duration(), meta:
|
||||||
Some(EffectMeta::CastTick { source: cast.source, target: cast.target, skill: Skill::ElectrocuteTick, speed: cast.speed, amount }) },
|
Some(EffectMeta::CastTick { id: Uuid::new_v4(), source: cast.source, target: cast.target, skill: Skill::ElectrocuteTick, speed: cast.speed, amount }) },
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -2362,7 +2378,7 @@ fn triage(cast: Cast, game: &mut Game, values: Triage) {
|
|||||||
Action::Effect {
|
Action::Effect {
|
||||||
construct: cast.target,
|
construct: cast.target,
|
||||||
effect: ConstructEffect { effect: Effect::Triage, duration: values.duration(), meta:
|
effect: ConstructEffect { effect: Effect::Triage, duration: values.duration(), meta:
|
||||||
Some(EffectMeta::CastTick { source: cast.source, target: cast.target, skill: Skill::TriageTick, speed: cast.speed, amount }) },
|
Some(EffectMeta::CastTick { id: Uuid::new_v4(), source: cast.source, target: cast.target, skill: Skill::TriageTick, speed: cast.speed, amount }) },
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mnml-ops",
|
"name": "mnml-ops",
|
||||||
"version": "1.12.4",
|
"version": "1.13.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mnml"
|
name = "mnml"
|
||||||
version = "1.12.4"
|
version = "1.13.0"
|
||||||
authors = ["ntr <ntr@smokestack.io>"]
|
authors = ["ntr <ntr@smokestack.io>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@ -17,7 +17,7 @@ use rpc::RpcMessage;
|
|||||||
use warden::{GameEvent};
|
use warden::{GameEvent};
|
||||||
|
|
||||||
pub type EventsTx = Sender<Event>;
|
pub type EventsTx = Sender<Event>;
|
||||||
type Id = Uuid;
|
type Id = usize;
|
||||||
|
|
||||||
// this is pretty heavyweight
|
// this is pretty heavyweight
|
||||||
// but it makes the ergonomics easy
|
// but it makes the ergonomics easy
|
||||||
@ -60,8 +60,13 @@ pub enum Event {
|
|||||||
ChatClear(Id, Uuid),
|
ChatClear(Id, Uuid),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// id and account are seperate
|
||||||
|
// multiple tabs etc can cause the same account to be connected twice
|
||||||
|
// so even though each client has the same subs etc
|
||||||
|
// they are treated independently
|
||||||
struct WsClient {
|
struct WsClient {
|
||||||
id: Id,
|
id: Id,
|
||||||
|
account: Uuid,
|
||||||
tx: Sender<RpcMessage>,
|
tx: Sender<RpcMessage>,
|
||||||
subs: HashSet<Uuid>,
|
subs: HashSet<Uuid>,
|
||||||
chat: Option<(Uuid, String)>,
|
chat: Option<(Uuid, String)>,
|
||||||
@ -103,7 +108,9 @@ impl Events {
|
|||||||
Event::Connect(id, account, tx) => {
|
Event::Connect(id, account, tx) => {
|
||||||
info!("connect id={:?} account={:?}", id, account);
|
info!("connect id={:?} account={:?}", id, account);
|
||||||
|
|
||||||
let client = WsClient { id,
|
let client = WsClient {
|
||||||
|
id,
|
||||||
|
account: account.id,
|
||||||
tx,
|
tx,
|
||||||
subs: HashSet::new(),
|
subs: HashSet::new(),
|
||||||
pvp: false,
|
pvp: false,
|
||||||
@ -163,8 +170,8 @@ impl Events {
|
|||||||
subs += 1;
|
subs += 1;
|
||||||
|
|
||||||
let redacted = match msg {
|
let redacted = match msg {
|
||||||
RpcMessage::InstanceState(ref i) => RpcMessage::InstanceState(i.clone().redact(client.id)),
|
RpcMessage::InstanceState(ref i) => RpcMessage::InstanceState(i.clone().redact(client.account)),
|
||||||
RpcMessage::GameState(ref i) => RpcMessage::GameState(i.clone().redact(client.id)),
|
RpcMessage::GameState(ref i) => RpcMessage::GameState(i.clone().redact(client.account)),
|
||||||
_ => msg.clone(),
|
_ => msg.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -203,7 +210,7 @@ impl Events {
|
|||||||
if let Some(opp_req) = match self.clients.iter_mut().find(|(c_id, c)| c.pvp && **c_id != id) {
|
if let Some(opp_req) = match self.clients.iter_mut().find(|(c_id, c)| c.pvp && **c_id != id) {
|
||||||
Some((q_id, q)) => {
|
Some((q_id, q)) => {
|
||||||
q.pvp = false;
|
q.pvp = false;
|
||||||
Some(PvpRequest { id: *q_id, account: q.id, tx: q.tx.clone() })
|
Some(PvpRequest { id: *q_id, account: q.account, tx: q.tx.clone() })
|
||||||
},
|
},
|
||||||
None => None,
|
None => None,
|
||||||
} {
|
} {
|
||||||
@ -211,7 +218,7 @@ impl Events {
|
|||||||
let c = self.clients.get_mut(&id)
|
let c = self.clients.get_mut(&id)
|
||||||
.ok_or(format_err!("connection not found id={:?}", id))?;
|
.ok_or(format_err!("connection not found id={:?}", id))?;
|
||||||
|
|
||||||
let player_req = PvpRequest { id: c.id, account: c.id, tx: c.tx.clone() };
|
let player_req = PvpRequest { id: c.id, account: c.account, tx: c.tx.clone() };
|
||||||
self.warden.send(GameEvent::Match((opp_req, player_req)))?;
|
self.warden.send(GameEvent::Match((opp_req, player_req)))?;
|
||||||
|
|
||||||
return Ok(())
|
return Ok(())
|
||||||
@ -221,7 +228,7 @@ impl Events {
|
|||||||
let requester = self.clients.get_mut(&id).unwrap();
|
let requester = self.clients.get_mut(&id).unwrap();
|
||||||
requester.pvp = true;
|
requester.pvp = true;
|
||||||
requester.tx.send(RpcMessage::QueueJoined(()))?;
|
requester.tx.send(RpcMessage::QueueJoined(()))?;
|
||||||
info!("joined game queue id={:?} account={:?}", requester.id, requester.id);
|
info!("joined game queue id={:?} account={:?}", requester.id, requester.account);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -231,7 +238,7 @@ impl Events {
|
|||||||
.ok_or(format_err!("connection not found id={:?}", id))?;
|
.ok_or(format_err!("connection not found id={:?}", id))?;
|
||||||
|
|
||||||
let code = names::name().split_whitespace().collect::<Vec<&str>>().join("-");
|
let code = names::name().split_whitespace().collect::<Vec<&str>>().join("-");
|
||||||
info!("pvp invite request id={:?} account={:?} code={:?}", c.id, c.id, code);
|
info!("pvp invite request id={:?} account={:?} code={:?}", c.id, c.account, code);
|
||||||
c.invite = Some(code.clone());
|
c.invite = Some(code.clone());
|
||||||
c.tx.send(RpcMessage::Invite(code))?;
|
c.tx.send(RpcMessage::Invite(code))?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -250,10 +257,10 @@ impl Events {
|
|||||||
Some(ref c) => *c == code,
|
Some(ref c) => *c == code,
|
||||||
None => false,
|
None => false,
|
||||||
})
|
})
|
||||||
.map(|(_id, c)| PvpRequest { id: c.id, account: c.id, tx: c.tx.clone() })
|
.map(|(_id, c)| PvpRequest { id: c.id, account: c.account, tx: c.tx.clone() })
|
||||||
.ok_or(format_err!("invite expired code={:?}", code))?;
|
.ok_or(format_err!("invite expired code={:?}", code))?;
|
||||||
|
|
||||||
let join = PvpRequest { id: c.id, account: c.id, tx: c.tx.clone() };
|
let join = PvpRequest { id: c.id, account: c.account, tx: c.tx.clone() };
|
||||||
|
|
||||||
self.warden.send(GameEvent::Match((join, inv)))?;
|
self.warden.send(GameEvent::Match((join, inv)))?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -276,7 +283,7 @@ impl Events {
|
|||||||
|
|
||||||
c.pvp = false;
|
c.pvp = false;
|
||||||
c.tx.send(RpcMessage::QueueLeft(()))?;
|
c.tx.send(RpcMessage::QueueLeft(()))?;
|
||||||
info!("left game queue id={:?} account={:?}", c.id, c.id);
|
info!("left game queue id={:?} account={:?}", c.id, c.account);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -307,7 +314,7 @@ impl Events {
|
|||||||
Some(ref chat) => chat.0 == instance,
|
Some(ref chat) => chat.0 == instance,
|
||||||
None => false,
|
None => false,
|
||||||
})
|
})
|
||||||
.map(|(_id, c)| (c.id, c.chat.clone().unwrap().1))
|
.map(|(_id, c)| (c.account, c.chat.clone().unwrap().1))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
return self.event(Event::Push(instance, RpcMessage::InstanceChat(chat_state)));
|
return self.event(Event::Push(instance, RpcMessage::InstanceChat(chat_state)));
|
||||||
@ -326,7 +333,7 @@ impl Events {
|
|||||||
Some(ref chat) => chat.0 == instance,
|
Some(ref chat) => chat.0 == instance,
|
||||||
None => false,
|
None => false,
|
||||||
})
|
})
|
||||||
.map(|(_id, c)| (c.id, c.chat.clone().unwrap().1))
|
.map(|(_id, c)| (c.account, c.chat.clone().unwrap().1))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
return self.event(Event::Push(instance, RpcMessage::InstanceChat(chat_state)));
|
return self.event(Event::Push(instance, RpcMessage::InstanceChat(chat_state)));
|
||||||
|
|||||||
@ -109,6 +109,7 @@ pub fn start() {
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
setup_logger().unwrap();
|
setup_logger().unwrap();
|
||||||
dotenv::from_path(Path::new("/etc/mnml/gs.conf")).ok();
|
dotenv::from_path(Path::new("/etc/mnml/gs.conf")).ok();
|
||||||
|
info!("starting server");
|
||||||
|
|
||||||
let pool = pg::create_pool();
|
let pool = pg::create_pool();
|
||||||
let http_pool = pool.clone();
|
let http_pool = pool.clone();
|
||||||
|
|||||||
@ -248,8 +248,8 @@ pub fn set(tx: &mut Transaction, account: Uuid, email: &String) -> Result<(Uuid,
|
|||||||
let existing = tx.query(select_query, &[&id])?;
|
let existing = tx.query(select_query, &[&id])?;
|
||||||
|
|
||||||
let result = match existing.iter().next() {
|
let result = match existing.iter().next() {
|
||||||
Some(_) => tx.query(insert_query, &[&id, &account, &email, &confirm_token, &recover_token])?,
|
Some(_) => tx.query(update_query, &[&email, &confirm_token, &recover_token, &account])?,
|
||||||
None => tx.query(update_query, &[&email, &confirm_token, &recover_token, &account])?,
|
None => tx.query(insert_query, &[&id, &account, &email, &confirm_token, &recover_token])?,
|
||||||
};
|
};
|
||||||
|
|
||||||
match result.iter().next() {
|
match result.iter().next() {
|
||||||
|
|||||||
@ -17,6 +17,8 @@ use crossbeam_channel::{unbounded, Sender as CbSender};
|
|||||||
use ws::{Builder, CloseCode, Message, Handler, Request, Response, Settings, Sender as WsSender};
|
use ws::{Builder, CloseCode, Message, Handler, Request, Response, Settings, Sender as WsSender};
|
||||||
use ws::deflate::DeflateHandler;
|
use ws::deflate::DeflateHandler;
|
||||||
|
|
||||||
|
use rand::prelude::*;
|
||||||
|
|
||||||
use account::{Account};
|
use account::{Account};
|
||||||
use account;
|
use account;
|
||||||
use events::{Event};
|
use events::{Event};
|
||||||
@ -132,11 +134,10 @@ pub trait User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct Connection {
|
struct Connection {
|
||||||
pub id: Uuid,
|
pub id: usize,
|
||||||
pub ws: CbSender<RpcMessage>,
|
pub ws: CbSender<RpcMessage>,
|
||||||
pool: PgPool,
|
pool: PgPool,
|
||||||
stripe: StripeClient,
|
stripe: StripeClient,
|
||||||
// account: Option<Account>,
|
|
||||||
user: Box<dyn User>,
|
user: Box<dyn User>,
|
||||||
events: CbSender<Event>,
|
events: CbSender<Event>,
|
||||||
}
|
}
|
||||||
@ -199,8 +200,7 @@ impl Handler for Connection {
|
|||||||
let db = self.pool.get().unwrap();
|
let db = self.pool.get().unwrap();
|
||||||
match account::from_token(&db, &cookie.value().to_string()) {
|
match account::from_token(&db, &cookie.value().to_string()) {
|
||||||
Ok(a) => {
|
Ok(a) => {
|
||||||
self.id = a.id;
|
self.user = Box::new(Authenticated::new(self.id, a, self.ws.clone(), self.events.clone(), self.pool.clone()));
|
||||||
self.user = Box::new(Authenticated::new(a, self.ws.clone(), self.events.clone(), self.pool.clone()));
|
|
||||||
},
|
},
|
||||||
Err(_) => return unauth(),
|
Err(_) => return unauth(),
|
||||||
}
|
}
|
||||||
@ -243,12 +243,12 @@ pub fn start(pool: PgPool, events_tx: CbSender<Event>, stripe: StripeClient) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let mut rng = thread_rng();
|
||||||
let anon_account = Account::anonymous();
|
let anon_account = Account::anonymous();
|
||||||
let id = anon_account.id;
|
|
||||||
|
|
||||||
DeflateHandler::new(
|
DeflateHandler::new(
|
||||||
Connection {
|
Connection {
|
||||||
id,
|
id: rng.gen::<usize>(),
|
||||||
ws: tx.clone(),
|
ws: tx.clone(),
|
||||||
pool: pool.clone(),
|
pool: pool.clone(),
|
||||||
stripe: stripe.clone(),
|
stripe: stripe.clone(),
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
use mnml_core::mob::anim_test_game;
|
use mnml_core::mob::anim_test_game;
|
||||||
use mnml_core::item::item_info;
|
use mnml_core::item::item_info;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use failure::err_msg;
|
use failure::err_msg;
|
||||||
@ -44,7 +43,7 @@ use rpc::{RpcMessage, RpcRequest, User};
|
|||||||
#[derive(Debug,Clone)]
|
#[derive(Debug,Clone)]
|
||||||
pub struct Authenticated {
|
pub struct Authenticated {
|
||||||
pub account: Account,
|
pub account: Account,
|
||||||
pub id: Uuid,
|
pub id: usize,
|
||||||
|
|
||||||
events: CbSender<Event>,
|
events: CbSender<Event>,
|
||||||
ws: CbSender<RpcMessage>,
|
ws: CbSender<RpcMessage>,
|
||||||
@ -52,9 +51,9 @@ pub struct Authenticated {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Authenticated {
|
impl Authenticated {
|
||||||
pub fn new(account: Account, ws: CbSender<RpcMessage>, events: CbSender<Event>, pool: PgPool) -> Authenticated {
|
pub fn new(id: usize, account: Account, ws: CbSender<RpcMessage>, events: CbSender<Event>, pool: PgPool) -> Authenticated {
|
||||||
Authenticated {
|
Authenticated {
|
||||||
id: account.id,
|
id,
|
||||||
account,
|
account,
|
||||||
ws,
|
ws,
|
||||||
events,
|
events,
|
||||||
@ -81,10 +80,10 @@ impl User for Authenticated {
|
|||||||
|
|
||||||
// last minute processing
|
// last minute processing
|
||||||
let msg = match msg {
|
let msg = match msg {
|
||||||
RpcMessage::InstanceState(v) => RpcMessage::InstanceState(v.redact(self.id)),
|
RpcMessage::InstanceState(v) => RpcMessage::InstanceState(v.redact(self.account.id)),
|
||||||
RpcMessage::AccountInstances(v) =>
|
RpcMessage::AccountInstances(v) =>
|
||||||
RpcMessage::AccountInstances(v.into_iter().map(|i| i.redact(self.id)).collect()),
|
RpcMessage::AccountInstances(v.into_iter().map(|i| i.redact(self.account.id)).collect()),
|
||||||
RpcMessage::GameState(v) => RpcMessage::GameState(v.redact(self.id)),
|
RpcMessage::GameState(v) => RpcMessage::GameState(v.redact(self.account.id)),
|
||||||
_ => msg,
|
_ => msg,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mnml-studios",
|
"name": "mnml-studios",
|
||||||
"version": "1.12.4",
|
"version": "1.13.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user