Merge tag '1.13.0' into develop

1.13.0
This commit is contained in:
ntr 2020-01-31 10:52:17 +10:00
commit 40f3bb5be6
20 changed files with 178 additions and 132 deletions

View File

@ -1 +1 @@
1.12.4
1.13.0

View File

@ -5,6 +5,9 @@ _ntr_
* can't reset password without knowing password =\
* hard reload client on version change
decay reflected not applied
black out timer when game finished
* audio
* animation effects
* vbox combine / buy / equip etc

View File

@ -1,6 +1,6 @@
{
"name": "mnml-client",
"version": "1.12.4",
"version": "1.13.0",
"description": "",
"main": "index.js",
"scripts": {

View File

@ -27,6 +27,3 @@ ssh -q "$TARGET" ls -lah "$CLIENT_DIST_DIR"
echo "restarting mnml service"
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

View File

@ -1,6 +1,6 @@
{
"name": "mnml-client",
"version": "1.12.4",
"version": "1.13.0",
"description": "",
"main": "index.js",
"scripts": {

View File

@ -45,7 +45,7 @@ function Controls(args) {
const now = animating ? zero : Date.now();
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)
: 100;

View File

@ -316,6 +316,7 @@ function createSocket(events) {
case 'no constructs selected': return events.errorPrompt('select_constructs');
case 'node requirements not met': return events.errorPrompt('complete_nodes');
case 'construct at max skills (4)': return events.errorPrompt('max_skills');
case 'instance missing': return window.location.reload();
default: return errorToast(error);

View File

@ -1,6 +1,6 @@
[package]
name = "mnml_core"
version = "1.12.4"
version = "1.13.0"
authors = ["ntr <ntr@smokestack.io>", "mashy <mashy@mnml.gg>"]
[dependencies]

View File

@ -65,7 +65,7 @@ impl ConstructSkill {
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum EffectMeta {
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),
Multiplier(usize),
}
@ -100,12 +100,19 @@ impl ConstructEffect {
pub fn get_skill(&self) -> Option<Skill> {
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),
_ => 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)]
@ -561,7 +568,7 @@ impl Construct {
fn tick_damage(&self, effect: Effect) -> usize {
match self.effects.iter().find_map(|ce| match ce.effect == effect {
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,
},
false => None,
@ -1033,8 +1040,6 @@ impl Construct {
}
pub fn damage_trigger_casts(&mut self, cast: &Cast, event: &Event) -> Vec<Cast> {
if self.is_ko() { return vec![] }
match event {
Event::Damage { construct: _, colour, amount: _, mitigation: _, display: _ } => {
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) {
let ConstructEffect { effect: _, duration: _, meta } =
self.effects.iter().find(|e| e.effect == Effect::Absorb).unwrap();

View File

@ -81,7 +81,7 @@ impl Effect {
Skill::CounterAttackPlus,
Skill::CounterAttackPlusPlus,
].contains(&skill),
// these provide immunity for the ticks
// the base skills will still resolve
// but they have early return checks
@ -125,6 +125,12 @@ impl Effect {
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 {
Effect::Stun => true,
Effect::Banish => true,

View File

@ -428,8 +428,12 @@ impl Game {
.cloned()
.filter_map(|e| e.meta)
.filter_map(move |m| match m {
EffectMeta::CastTick { source, target, skill, speed, amount: _ } =>
Some(Cast::new(source, c.account, target, skill).set_speed(speed)),
EffectMeta::CastTick { source, target, skill, speed, amount: _, id } =>
Some(
Cast::new(source, c.account, target, skill)
.set_speed(speed)
.set_id(id)
),
_ => None,
})
)
@ -506,6 +510,20 @@ impl Game {
fn resolve(&mut self, cast: Cast) -> &mut Game {
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 let Some(effects) = self.construct(cast.source).disabled(cast.skill) {
self.add_resolution(&cast, &Event::Disable { construct: cast.source, effects });
@ -1054,6 +1072,7 @@ mod tests {
.learn(Skill::Block)
.learn(Skill::Counter)
.learn(Skill::Siphon)
.learn(Skill::Purify)
.learn(Skill::Amplify)
.learn(Skill::Stun)
.learn(Skill::Ruin)
@ -1069,6 +1088,7 @@ mod tests {
.learn(Skill::Block)
.learn(Skill::Counter)
.learn(Skill::Siphon)
.learn(Skill::Purify)
.learn(Skill::Amplify)
.learn(Skill::Stun)
.learn(Skill::Block);
@ -1874,85 +1894,6 @@ mod tests {
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]
fn upkeep_test() {
let mut game = create_2v2_test_game();
@ -2269,8 +2210,41 @@ mod tests {
assert!(effect_events == 2);
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]
@ -2524,4 +2498,38 @@ mod tests {
assert_eq!(siphon_tick_dmg, siphon_dmg);
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);
}
}

View File

@ -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) {
match self.skill {
Skill::Attack => attack(self, game, Attack::Base),
@ -729,6 +738,13 @@ impl Skill {
pub fn ko_castable(&self) -> bool {
match self {
// electrocute always goes off
Skill::Electrocute|
Skill::ElectrocutePlus |
Skill::ElectrocutePlusPlus |
// ticks happen after death
Skill::ElectrocuteTick |
Skill::DecayTick |
Skill::SiphonTick |
@ -1134,7 +1150,7 @@ fn siphon(cast: Cast, game: &mut Game, values: Siphon) {
Action::Effect {
construct: cast.target,
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 {
construct: cast.target,
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,10 +1853,10 @@ fn electrocute(cast: Cast, game: &mut Game, values: Electrocute) {
Action::Effect {
construct: cast.target,
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 }) },
},
);
if !game.affected(cast.target, Effect::Electrocuted) {
game.action(cast,
Action::Damage {
@ -2362,7 +2378,7 @@ fn triage(cast: Cast, game: &mut Game, values: Triage) {
Action::Effect {
construct: cast.target,
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 }) },
}
);

View File

@ -1,6 +1,6 @@
{
"name": "mnml-ops",
"version": "1.12.4",
"version": "1.13.0",
"description": "",
"main": "index.js",
"scripts": {

View File

@ -1,6 +1,6 @@
[package]
name = "mnml"
version = "1.12.4"
version = "1.13.0"
authors = ["ntr <ntr@smokestack.io>"]
[dependencies]

View File

@ -17,7 +17,7 @@ use rpc::RpcMessage;
use warden::{GameEvent};
pub type EventsTx = Sender<Event>;
type Id = Uuid;
type Id = usize;
// this is pretty heavyweight
// but it makes the ergonomics easy
@ -60,8 +60,13 @@ pub enum Event {
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 {
id: Id,
account: Uuid,
tx: Sender<RpcMessage>,
subs: HashSet<Uuid>,
chat: Option<(Uuid, String)>,
@ -103,7 +108,9 @@ impl Events {
Event::Connect(id, account, tx) => {
info!("connect id={:?} account={:?}", id, account);
let client = WsClient { id,
let client = WsClient {
id,
account: account.id,
tx,
subs: HashSet::new(),
pvp: false,
@ -163,8 +170,8 @@ impl Events {
subs += 1;
let redacted = match msg {
RpcMessage::InstanceState(ref i) => RpcMessage::InstanceState(i.clone().redact(client.id)),
RpcMessage::GameState(ref i) => RpcMessage::GameState(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.account)),
_ => 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) {
Some((q_id, q)) => {
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,
} {
@ -211,7 +218,7 @@ impl Events {
let c = self.clients.get_mut(&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)))?;
return Ok(())
@ -221,7 +228,7 @@ impl Events {
let requester = self.clients.get_mut(&id).unwrap();
requester.pvp = true;
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(());
},
@ -231,7 +238,7 @@ impl Events {
.ok_or(format_err!("connection not found id={:?}", id))?;
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.tx.send(RpcMessage::Invite(code))?;
return Ok(());
@ -250,10 +257,10 @@ impl Events {
Some(ref c) => *c == code,
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))?;
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)))?;
return Ok(());
@ -276,7 +283,7 @@ impl Events {
c.pvp = false;
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(());
},
@ -307,7 +314,7 @@ impl Events {
Some(ref chat) => chat.0 == instance,
None => false,
})
.map(|(_id, c)| (c.id, c.chat.clone().unwrap().1))
.map(|(_id, c)| (c.account, c.chat.clone().unwrap().1))
.collect();
return self.event(Event::Push(instance, RpcMessage::InstanceChat(chat_state)));
@ -326,7 +333,7 @@ impl Events {
Some(ref chat) => chat.0 == instance,
None => false,
})
.map(|(_id, c)| (c.id, c.chat.clone().unwrap().1))
.map(|(_id, c)| (c.account, c.chat.clone().unwrap().1))
.collect();
return self.event(Event::Push(instance, RpcMessage::InstanceChat(chat_state)));

View File

@ -109,6 +109,7 @@ pub fn start() {
#[cfg(unix)]
setup_logger().unwrap();
dotenv::from_path(Path::new("/etc/mnml/gs.conf")).ok();
info!("starting server");
let pool = pg::create_pool();
let http_pool = pool.clone();

View File

@ -248,8 +248,8 @@ pub fn set(tx: &mut Transaction, account: Uuid, email: &String) -> Result<(Uuid,
let existing = tx.query(select_query, &[&id])?;
let result = match existing.iter().next() {
Some(_) => tx.query(insert_query, &[&id, &account, &email, &confirm_token, &recover_token])?,
None => tx.query(update_query, &[&email, &confirm_token, &recover_token, &account])?,
Some(_) => 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() {

View File

@ -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::deflate::DeflateHandler;
use rand::prelude::*;
use account::{Account};
use account;
use events::{Event};
@ -132,11 +134,10 @@ pub trait User {
}
struct Connection {
pub id: Uuid,
pub id: usize,
pub ws: CbSender<RpcMessage>,
pool: PgPool,
stripe: StripeClient,
// account: Option<Account>,
user: Box<dyn User>,
events: CbSender<Event>,
}
@ -199,8 +200,7 @@ impl Handler for Connection {
let db = self.pool.get().unwrap();
match account::from_token(&db, &cookie.value().to_string()) {
Ok(a) => {
self.id = a.id;
self.user = Box::new(Authenticated::new(a, self.ws.clone(), self.events.clone(), self.pool.clone()));
self.user = Box::new(Authenticated::new(self.id, a, self.ws.clone(), self.events.clone(), self.pool.clone()));
},
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 id = anon_account.id;
DeflateHandler::new(
Connection {
id,
id: rng.gen::<usize>(),
ws: tx.clone(),
pool: pool.clone(),
stripe: stripe.clone(),

View File

@ -1,7 +1,6 @@
use mnml_core::mob::anim_test_game;
use mnml_core::item::item_info;
use std::time::Instant;
use uuid::Uuid;
use failure::Error;
use failure::err_msg;
@ -44,7 +43,7 @@ use rpc::{RpcMessage, RpcRequest, User};
#[derive(Debug,Clone)]
pub struct Authenticated {
pub account: Account,
pub id: Uuid,
pub id: usize,
events: CbSender<Event>,
ws: CbSender<RpcMessage>,
@ -52,9 +51,9 @@ pub struct 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 {
id: account.id,
id,
account,
ws,
events,
@ -81,10 +80,10 @@ impl User for Authenticated {
// last minute processing
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.into_iter().map(|i| i.redact(self.id)).collect()),
RpcMessage::GameState(v) => RpcMessage::GameState(v.redact(self.id)),
RpcMessage::AccountInstances(v.into_iter().map(|i| i.redact(self.account.id)).collect()),
RpcMessage::GameState(v) => RpcMessage::GameState(v.redact(self.account.id)),
_ => msg,
};

View File

@ -1,6 +1,6 @@
{
"name": "mnml-studios",
"version": "1.12.4",
"version": "1.13.0",
"description": "",
"main": "index.js",
"scripts": {