Merge branch 'release/1.13.0'

This commit is contained in:
ntr 2020-01-31 10:52:12 +10:00
commit 229f97e3d2
36 changed files with 406 additions and 232 deletions

View File

@ -1 +1 @@
1.12.3 1.13.0

View File

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

View File

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

View File

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

View File

@ -33,6 +33,25 @@ svg {
} }
} }
// icons from ethan are fills not strokes
svg.ethan-icon {
fill: @white;
stroke: none;
.red {
fill: @red;
}
.green {
fill: @green;
}
.blue {
fill: @blue;
}
}
.green { .green {
color: @green; color: @green;
stroke: @green; stroke: @green;

View File

@ -271,7 +271,6 @@ figure.gray {
.stats svg, .specs svg { .stats svg, .specs svg {
height: 2em; height: 2em;
fill: none;
} }
.credits { .credits {

View File

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

View File

@ -15,7 +15,7 @@ const addState = connect(
ws, ws,
game, game,
authenticated, instance,
activeSkill, activeSkill,
animFocus, animFocus,
resolution, resolution,
@ -30,7 +30,7 @@ const addState = connect(
return { return {
game, game,
authenticated, instance,
activeSkill, activeSkill,
animFocus, animFocus,
resolution, resolution,
@ -54,7 +54,7 @@ class GameConstruct extends preact.Component {
render() { render() {
const { const {
// Changing state variables // Changing state variables
authenticated, instance,
game, game,
activeSkill, activeSkill,
animFocus, animFocus,
@ -81,7 +81,9 @@ class GameConstruct extends preact.Component {
const unfocus = animFocus && !animFocus.includes(construct.id) ? 'unfocus' : ''; const unfocus = animFocus && !animFocus.includes(construct.id) ? 'unfocus' : '';
const targeted = game.stack.find(c => c.target === construct.id); const targeted = game.stack.find(c => c.target === construct.id);
const highlight = !authenticated && !targeted && activeSkill ? 'highlight' : '';
const firstRoundTutorial = instance && instance.time_control === 'Practice' && !game.resolutions.length;
const highlight = firstRoundTutorial && !targeted && activeSkill ? 'highlight' : '';
const crypSkills = player const crypSkills = player
? <div class="skills"> {range(0, 3).map(j => <SkillBtn key={j} construct={construct} i={j} />)} </div> ? <div class="skills"> {range(0, 3).map(j => <SkillBtn key={j} construct={construct} i={j} />)} </div>

View File

@ -10,6 +10,7 @@ const addState = connect(
game, game,
account, account,
authenticated, authenticated,
instance,
chatShow, chatShow,
animating, animating,
} = state; } = state;
@ -35,6 +36,7 @@ const addState = connect(
game, game,
account, account,
authenticated, authenticated,
instance,
chatShow, chatShow,
sendAbandon, sendAbandon,
sendGameSkillClear, sendGameSkillClear,
@ -68,6 +70,7 @@ function GameCtrlBtns(args) {
account, account,
chatShow, chatShow,
authenticated, authenticated,
instance,
getInstanceState, getInstanceState,
sendGameSkillClear, sendGameSkillClear,
@ -86,7 +89,8 @@ function GameCtrlBtns(args) {
quit(); quit();
} }
const noTargets = !authenticated && game.stack.length === 0; const firstRoundTutorial = instance && instance.time_control === 'Practice' && !game.resolutions.length;
const noTargets = firstRoundTutorial && game.stack.length === 0;
const readyBtn = <button disabled={animating || noTargets} class="ready" onClick={() => sendReady()}>Ready</button>; const readyBtn = <button disabled={animating || noTargets} class="ready" onClick={() => sendReady()}>Ready</button>;
const quitBtn = <button disabled={animating} class="quit" onClick={quitClick}>Back</button>; const quitBtn = <button disabled={animating} class="quit" onClick={quitClick}>Back</button>;

View File

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

View File

@ -60,6 +60,10 @@ const addState = connect(
} }
function setItemUnequip(v) { function setItemUnequip(v) {
const info = v.length ? v[1] : null;
dispatch(actions.setVboxInfo(info));
dispatch(actions.setVboxCombiner(null));
dispatch(actions.setVboxHighlight(false));
dispatch(actions.setVboxSelected({ storeSelect: [], stashSelect: [] })); dispatch(actions.setVboxSelected({ storeSelect: [], stashSelect: [] }));
return dispatch(actions.setItemUnequip(v)); return dispatch(actions.setItemUnequip(v));
} }

View File

@ -50,7 +50,7 @@ function InstanceCtrlBtns(args) {
} = args; } = args;
const finished = instance && instance.phase === 'Finished'; const finished = instance && instance.phase === 'Finished';
const tutorialDisable = tutorial && tutorial < 8; const tutorialDisable = instance.time_control === 'Practice' && tutorial && tutorial < 8 && instance.rounds.length === 1;
return ( return (
<div class="instance-ctrl-btns"> <div class="instance-ctrl-btns">
<button disabled={!account.subscribed} onClick={() => setChatShow(!chatShow)}>Chat</button> <button disabled={!account.subscribed} onClick={() => setChatShow(!chatShow)}>Chat</button>

View File

@ -11,6 +11,10 @@ const triangle = require('./svgs/triangle');
// const vboxColour = require('./svgs/colour'); // const vboxColour = require('./svgs/colour');
const vboxColour = require('./svgs/vbox.colour'); const vboxColour = require('./svgs/vbox.colour');
const speed = require('./svgs/speed');
const power = require('./svgs/power');
const life = require('./svgs/life');
module.exports = { module.exports = {
circle, circle,
diamond, diamond,
@ -19,6 +23,7 @@ module.exports = {
square, square,
squircle, squircle,
triangle, triangle,
speed,
saw, saw,
vboxColour, vboxColour,
@ -27,17 +32,17 @@ module.exports = {
Blue: () => vboxColour('blue'), Blue: () => vboxColour('blue'),
// stats // stats
RedLife: () => square(['red']), RedLife: () => life(['red']),
GreenLife: () => square(['green']), GreenLife: () => life(['green']),
BlueLife: () => square(['blue']), BlueLife: () => life(['blue']),
RedPower: () => circle(['red']), RedPower: () => power(['red']),
GreenPower: () => circle(['green']), GreenPower: () => power(['green']),
BluePower: () => circle(['blue']), BluePower: () => power(['blue']),
SpeedStat: () => triangle(['white']), SpeedStat: () => speed(['white']),
POWER: () => circle(['white']), POWER: () => power(['white']),
LIFE: () => square(['white']), LIFE: () => life(['white']),
SPEED: () => triangle(['white']), SPEED: () => speed(['white']),
// specs // specs
// Base // Base
@ -50,343 +55,343 @@ module.exports = {
Power: () => Power: () =>
<figure> <figure>
{circle(['white'])} {power(['white'])}
<figcaption>Power</figcaption> <figcaption>Power</figcaption>
</figure>, </figure>,
Life: () => Life: () =>
<figure> <figure>
{square(['white'])} {life(['white'])}
<figcaption>Life</figcaption> <figcaption>Life</figcaption>
</figure>, </figure>,
Speed: () => Speed: () =>
<figure> <figure>
{triangle(['white'])} {speed(['white'])}
<figcaption>Speed</figcaption> <figcaption>Speed</figcaption>
</figure>, </figure>,
// Lifes Upgrades // Lifes Upgrades
LifeGG: () => LifeGG: () =>
<figure> <figure>
{square(['green'])} {life(['green'])}
<figcaption>Life</figcaption> <figcaption>Life</figcaption>
</figure>, </figure>,
LifeRR: () => LifeRR: () =>
<figure> <figure>
{square(['red'])} {life(['red'])}
<figcaption>Life</figcaption> <figcaption>Life</figcaption>
</figure>, </figure>,
LifeBB:() => LifeBB:() =>
<figure> <figure>
{square(['blue'])} {life(['blue'])}
<figcaption>Life</figcaption> <figcaption>Life</figcaption>
</figure>, </figure>,
LifeRG: () => LifeRG: () =>
<figure> <figure>
{square(['red', 'green'])} {life(['red', 'green'])}
<figcaption>Life</figcaption> <figcaption>Life</figcaption>
</figure>, </figure>,
LifeGB: () => LifeGB: () =>
<figure> <figure>
{square(['green', 'blue'])} {life(['green', 'blue'])}
<figcaption>Life</figcaption> <figcaption>Life</figcaption>
</figure>, </figure>,
LifeRB:() => LifeRB:() =>
<figure> <figure>
{square(['red', 'blue'])} {life(['red', 'blue'])}
<figcaption>Life</figcaption> <figcaption>Life</figcaption>
</figure>, </figure>,
LifeGGPlus: () => LifeGGPlus: () =>
<figure> <figure>
{square(['green'])} {life(['green'])}
<figcaption>Life+</figcaption> <figcaption>Life+</figcaption>
</figure>, </figure>,
LifeRRPlus: () => LifeRRPlus: () =>
<figure> <figure>
{square(['red'])} {life(['red'])}
<figcaption>Life+</figcaption> <figcaption>Life+</figcaption>
</figure>, </figure>,
LifeBBPlus:() => LifeBBPlus:() =>
<figure> <figure>
{square(['blue'])} {life(['blue'])}
<figcaption>Life+</figcaption> <figcaption>Life+</figcaption>
</figure>, </figure>,
LifeRGPlus: () => LifeRGPlus: () =>
<figure> <figure>
{square(['red', 'green'])} {life(['red', 'green'])}
<figcaption>Life+</figcaption> <figcaption>Life+</figcaption>
</figure>, </figure>,
LifeGBPlus: () => LifeGBPlus: () =>
<figure> <figure>
{square(['green', 'blue'])} {life(['green', 'blue'])}
<figcaption>Life+</figcaption> <figcaption>Life+</figcaption>
</figure>, </figure>,
LifeRBPlus:() => LifeRBPlus:() =>
<figure> <figure>
{square(['red', 'blue'])} {life(['red', 'blue'])}
<figcaption>Life+</figcaption> <figcaption>Life+</figcaption>
</figure>, </figure>,
LifeGGPlusPlus: () => LifeGGPlusPlus: () =>
<figure> <figure>
{square(['green'])} {life(['green'])}
<figcaption>Life++</figcaption> <figcaption>Life++</figcaption>
</figure>, </figure>,
LifeRRPlusPlus: () => LifeRRPlusPlus: () =>
<figure> <figure>
{square(['red'])} {life(['red'])}
<figcaption>Life++</figcaption> <figcaption>Life++</figcaption>
</figure>, </figure>,
LifeBBPlusPlus:() => LifeBBPlusPlus:() =>
<figure> <figure>
{square(['blue'])} {life(['blue'])}
<figcaption>Life++</figcaption> <figcaption>Life++</figcaption>
</figure>, </figure>,
LifeRGPlusPlus: () => LifeRGPlusPlus: () =>
<figure> <figure>
{square(['red', 'green'])} {life(['red', 'green'])}
<figcaption>Life++</figcaption> <figcaption>Life++</figcaption>
</figure>, </figure>,
LifeGBPlusPlus: () => LifeGBPlusPlus: () =>
<figure> <figure>
{square(['green', 'blue'])} {life(['green', 'blue'])}
<figcaption>Life++</figcaption> <figcaption>Life++</figcaption>
</figure>, </figure>,
LifeRBPlusPlus:() => LifeRBPlusPlus:() =>
<figure> <figure>
{square(['red', 'blue'])} {life(['red', 'blue'])}
<figcaption>Life++</figcaption> <figcaption>Life++</figcaption>
</figure>, </figure>,
// Powers Upgrades // Powers Upgrades
PowerGG: () => PowerGG: () =>
<figure> <figure>
{circle(['green'])} {power(['green'])}
<figcaption>Power</figcaption> <figcaption>Power</figcaption>
</figure>, </figure>,
PowerRR: () => PowerRR: () =>
<figure> <figure>
{circle(['red'])} {power(['red'])}
<figcaption>Power</figcaption> <figcaption>Power</figcaption>
</figure>, </figure>,
PowerBB:() => PowerBB:() =>
<figure> <figure>
{circle(['blue'])} {power(['blue'])}
<figcaption>Power</figcaption> <figcaption>Power</figcaption>
</figure>, </figure>,
PowerRG: () => PowerRG: () =>
<figure> <figure>
{circle(['red', 'green'])} {power(['red', 'green'])}
<figcaption>Power</figcaption> <figcaption>Power</figcaption>
</figure>, </figure>,
PowerGB: () => PowerGB: () =>
<figure> <figure>
{circle(['green', 'blue'])} {power(['green', 'blue'])}
<figcaption>Power</figcaption> <figcaption>Power</figcaption>
</figure>, </figure>,
PowerRB:() => PowerRB:() =>
<figure> <figure>
{circle(['red', 'blue'])} {power(['red', 'blue'])}
<figcaption>Power</figcaption> <figcaption>Power</figcaption>
</figure>, </figure>,
PowerGGPlus: () => PowerGGPlus: () =>
<figure> <figure>
{circle(['green'])} {power(['green'])}
<figcaption>Power+</figcaption> <figcaption>Power+</figcaption>
</figure>, </figure>,
PowerRRPlus: () => PowerRRPlus: () =>
<figure> <figure>
{circle(['red'])} {power(['red'])}
<figcaption>Power+</figcaption> <figcaption>Power+</figcaption>
</figure>, </figure>,
PowerBBPlus:() => PowerBBPlus:() =>
<figure> <figure>
{circle(['blue'])} {power(['blue'])}
<figcaption>Power+</figcaption> <figcaption>Power+</figcaption>
</figure>, </figure>,
PowerRGPlus: () => PowerRGPlus: () =>
<figure> <figure>
{circle(['red', 'green'])} {power(['red', 'green'])}
<figcaption>Power+</figcaption> <figcaption>Power+</figcaption>
</figure>, </figure>,
PowerGBPlus: () => PowerGBPlus: () =>
<figure> <figure>
{circle(['green', 'blue'])} {power(['green', 'blue'])}
<figcaption>Power+</figcaption> <figcaption>Power+</figcaption>
</figure>, </figure>,
PowerRBPlus:() => PowerRBPlus:() =>
<figure> <figure>
{circle(['red', 'blue'])} {power(['red', 'blue'])}
<figcaption>Power+</figcaption> <figcaption>Power+</figcaption>
</figure>, </figure>,
PowerGGPlusPlus: () => PowerGGPlusPlus: () =>
<figure> <figure>
{circle(['green'])} {power(['green'])}
<figcaption>Power++</figcaption> <figcaption>Power++</figcaption>
</figure>, </figure>,
PowerRRPlusPlus: () => PowerRRPlusPlus: () =>
<figure> <figure>
{circle(['red'])} {power(['red'])}
<figcaption>Power++</figcaption> <figcaption>Power++</figcaption>
</figure>, </figure>,
PowerBBPlusPlus:() => PowerBBPlusPlus:() =>
<figure> <figure>
{circle(['blue'])} {power(['blue'])}
<figcaption>Power++</figcaption> <figcaption>Power++</figcaption>
</figure>, </figure>,
PowerRGPlusPlus: () => PowerRGPlusPlus: () =>
<figure> <figure>
{circle(['red', 'green'])} {power(['red', 'green'])}
<figcaption>Power++</figcaption> <figcaption>Power++</figcaption>
</figure>, </figure>,
PowerGBPlusPlus: () => PowerGBPlusPlus: () =>
<figure> <figure>
{circle(['green', 'blue'])} {power(['green', 'blue'])}
<figcaption>Power++</figcaption> <figcaption>Power++</figcaption>
</figure>, </figure>,
PowerRBPlusPlus:() => PowerRBPlusPlus:() =>
<figure> <figure>
{circle(['red', 'blue'])} {power(['red', 'blue'])}
<figcaption>Power++</figcaption> <figcaption>Power++</figcaption>
</figure>, </figure>,
// Speeds Upgrades // Speeds Upgrades
SpeedGG: () => SpeedGG: () =>
<figure> <figure>
{triangle(['green'])} {speed(['green'])}
<figcaption>Speed</figcaption> <figcaption>Speed</figcaption>
</figure>, </figure>,
SpeedRR: () => SpeedRR: () =>
<figure> <figure>
{triangle(['red'])} {speed(['red'])}
<figcaption>Speed</figcaption> <figcaption>Speed</figcaption>
</figure>, </figure>,
SpeedBB:() => SpeedBB:() =>
<figure> <figure>
{triangle(['blue'])} {speed(['blue'])}
<figcaption>Speed</figcaption> <figcaption>Speed</figcaption>
</figure>, </figure>,
SpeedRG: () => SpeedRG: () =>
<figure> <figure>
{triangle(['red', 'green'])} {speed(['red', 'green'])}
<figcaption>Speed</figcaption> <figcaption>Speed</figcaption>
</figure>, </figure>,
SpeedGB: () => SpeedGB: () =>
<figure> <figure>
{triangle(['green', 'blue'])} {speed(['green', 'blue'])}
<figcaption>Speed</figcaption> <figcaption>Speed</figcaption>
</figure>, </figure>,
SpeedRB:() => SpeedRB:() =>
<figure> <figure>
{triangle(['red', 'blue'])} {speed(['red', 'blue'])}
<figcaption>Speed</figcaption> <figcaption>Speed</figcaption>
</figure>, </figure>,
SpeedGGPlus: () => SpeedGGPlus: () =>
<figure> <figure>
{triangle(['green'])} {speed(['green'])}
<figcaption>Speed+</figcaption> <figcaption>Speed+</figcaption>
</figure>, </figure>,
SpeedRRPlus: () => SpeedRRPlus: () =>
<figure> <figure>
{triangle(['red'])} {speed(['red'])}
<figcaption>Speed+</figcaption> <figcaption>Speed+</figcaption>
</figure>, </figure>,
SpeedBBPlus:() => SpeedBBPlus:() =>
<figure> <figure>
{triangle(['blue'])} {speed(['blue'])}
<figcaption>Speed+</figcaption> <figcaption>Speed+</figcaption>
</figure>, </figure>,
SpeedRGPlus: () => SpeedRGPlus: () =>
<figure> <figure>
{triangle(['red', 'green'])} {speed(['red', 'green'])}
<figcaption>Speed+</figcaption> <figcaption>Speed+</figcaption>
</figure>, </figure>,
SpeedGBPlus: () => SpeedGBPlus: () =>
<figure> <figure>
{triangle(['green', 'blue'])} {speed(['green', 'blue'])}
<figcaption>Speed+</figcaption> <figcaption>Speed+</figcaption>
</figure>, </figure>,
SpeedRBPlus:() => SpeedRBPlus:() =>
<figure> <figure>
{triangle(['red', 'blue'])} {speed(['red', 'blue'])}
<figcaption>Speed+</figcaption> <figcaption>Speed+</figcaption>
</figure>, </figure>,
SpeedGGPlusPlus: () => SpeedGGPlusPlus: () =>
<figure> <figure>
{triangle(['green'])} {speed(['green'])}
<figcaption>Speed++</figcaption> <figcaption>Speed++</figcaption>
</figure>, </figure>,
SpeedRRPlusPlus: () => SpeedRRPlusPlus: () =>
<figure> <figure>
{triangle(['red'])} {speed(['red'])}
<figcaption>Speed++</figcaption> <figcaption>Speed++</figcaption>
</figure>, </figure>,
SpeedBBPlusPlus:() => SpeedBBPlusPlus:() =>
<figure> <figure>
{triangle(['blue'])} {speed(['blue'])}
<figcaption>Speed++</figcaption> <figcaption>Speed++</figcaption>
</figure>, </figure>,
SpeedRGPlusPlus: () => SpeedRGPlusPlus: () =>
<figure> <figure>
{triangle(['red', 'green'])} {speed(['red', 'green'])}
<figcaption>Speed++</figcaption> <figcaption>Speed++</figcaption>
</figure>, </figure>,
SpeedGBPlusPlus: () => SpeedGBPlusPlus: () =>
<figure> <figure>
{triangle(['green', 'blue'])} {speed(['green', 'blue'])}
<figcaption>Speed++</figcaption> <figcaption>Speed++</figcaption>
</figure>, </figure>,
SpeedRBPlusPlus:() => SpeedRBPlusPlus:() =>
<figure> <figure>
{triangle(['red', 'blue'])} {speed(['red', 'blue'])}
<figcaption>Speed++</figcaption> <figcaption>Speed++</figcaption>
</figure>, </figure>,
}; };

View File

@ -0,0 +1,33 @@
const preact = require('preact');
module.exports = function triangle(colours) {
if (colours.length === 1) {
return (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" class="ethan-icon" viewBox="0 0 2004 2004" >
<g>
<path class={colours[0]}
d="m 1538.9368,216.704 c -121.3837,0 -235.4065,47.09566 -321.0578,132.63951 l -215.6823,215.68237 -0.7502,0.75034 L 784.978,349.34351 C 699.3626,263.83546 585.33974,216.73979 463.99169,216.73979 c -121.38377,0 -235.40662,47.09567 -321.05776,132.63951 C 57.139877,435.17333 9.901325,549.232 9.901325,670.50846 c 0,121.31246 47.238552,235.29958 133.032605,320.98638 l 749.88518,749.88506 c 28.97915,28.9793 67.53467,44.9161 108.59159,44.9161 h 0.786 c 40.7711,-0.2144 79.0408,-16.1512 107.8414,-44.9161 l 749.9207,-749.92085 c 85.8656,-85.86541 133.1399,-199.88832 133.1399,-321.05778 0,-121.16927 -47.2743,-235.19214 -133.1755,-321.09336 C 1774.3077,263.79966 1660.2848,216.704 1538.9368,216.704 Z m 216.4327,670.12997 -749.9209,749.92093 c -1.0722,1.0719 -2.2871,1.4651 -3.2519,1.5722 -0.2857,0.072 -0.5359,0.072 -0.7502,0.072 -1.0721,0 -2.64423,-0.2858 -4.00224,-1.6436 L 247.48765,886.79819 c -57.81543,-57.74402 -89.68894,-134.56916 -89.68894,-216.32532 0,-81.75637 31.87351,-158.65294 89.68894,-216.46831 57.7082,-57.63678 134.60485,-89.40313 216.46832,-89.40313 81.86348,0 158.72437,31.73055 216.43258,89.36733 L 920.9764,694.55665 c 21.47533,21.4752 50.06144,33.33861 80.4701,33.33861 0.2499,0 0.5001,0 0.7502,-0.035 30.1229,-0.14401 58.3873,-11.97061 79.7196,-33.30282 l 240.5522,-240.55312 c 57.708,-57.63657 134.6048,-89.40292 216.4682,-89.40292 81.8635,0.035 158.7243,31.76635 216.4327,89.36733 57.9227,57.92256 89.8317,134.78353 89.8317,216.43251 0,81.64918 -31.9091,158.51019 -89.8316,216.43272 z"/>
</g>
</svg>
);
}
return (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" class="ethan-icon" viewBox="0 0 2004 2004" >
<clipPath id="firstColour">
<rect x="0" y="0" width="1002" height="2004" />
</clipPath>
<clipPath id="secondColour">
<rect x="1002" y="0" width="1002" height="2004" />
</clipPath>
<g>
<path clip-path="url(#firstColour)" class={colours[0]}
d="m 1538.9368,216.704 c -121.3837,0 -235.4065,47.09566 -321.0578,132.63951 l -215.6823,215.68237 -0.7502,0.75034 L 784.978,349.34351 C 699.3626,263.83546 585.33974,216.73979 463.99169,216.73979 c -121.38377,0 -235.40662,47.09567 -321.05776,132.63951 C 57.139877,435.17333 9.901325,549.232 9.901325,670.50846 c 0,121.31246 47.238552,235.29958 133.032605,320.98638 l 749.88518,749.88506 c 28.97915,28.9793 67.53467,44.9161 108.59159,44.9161 h 0.786 c 40.7711,-0.2144 79.0408,-16.1512 107.8414,-44.9161 l 749.9207,-749.92085 c 85.8656,-85.86541 133.1399,-199.88832 133.1399,-321.05778 0,-121.16927 -47.2743,-235.19214 -133.1755,-321.09336 C 1774.3077,263.79966 1660.2848,216.704 1538.9368,216.704 Z m 216.4327,670.12997 -749.9209,749.92093 c -1.0722,1.0719 -2.2871,1.4651 -3.2519,1.5722 -0.2857,0.072 -0.5359,0.072 -0.7502,0.072 -1.0721,0 -2.64423,-0.2858 -4.00224,-1.6436 L 247.48765,886.79819 c -57.81543,-57.74402 -89.68894,-134.56916 -89.68894,-216.32532 0,-81.75637 31.87351,-158.65294 89.68894,-216.46831 57.7082,-57.63678 134.60485,-89.40313 216.46832,-89.40313 81.86348,0 158.72437,31.73055 216.43258,89.36733 L 920.9764,694.55665 c 21.47533,21.4752 50.06144,33.33861 80.4701,33.33861 0.2499,0 0.5001,0 0.7502,-0.035 30.1229,-0.14401 58.3873,-11.97061 79.7196,-33.30282 l 240.5522,-240.55312 c 57.708,-57.63657 134.6048,-89.40292 216.4682,-89.40292 81.8635,0.035 158.7243,31.76635 216.4327,89.36733 57.9227,57.92256 89.8317,134.78353 89.8317,216.43251 0,81.64918 -31.9091,158.51019 -89.8316,216.43272 z" />
<path clip-path="url(#secondColour)" class={colours[1]}
d="m 1538.9368,216.704 c -121.3837,0 -235.4065,47.09566 -321.0578,132.63951 l -215.6823,215.68237 -0.7502,0.75034 L 784.978,349.34351 C 699.3626,263.83546 585.33974,216.73979 463.99169,216.73979 c -121.38377,0 -235.40662,47.09567 -321.05776,132.63951 C 57.139877,435.17333 9.901325,549.232 9.901325,670.50846 c 0,121.31246 47.238552,235.29958 133.032605,320.98638 l 749.88518,749.88506 c 28.97915,28.9793 67.53467,44.9161 108.59159,44.9161 h 0.786 c 40.7711,-0.2144 79.0408,-16.1512 107.8414,-44.9161 l 749.9207,-749.92085 c 85.8656,-85.86541 133.1399,-199.88832 133.1399,-321.05778 0,-121.16927 -47.2743,-235.19214 -133.1755,-321.09336 C 1774.3077,263.79966 1660.2848,216.704 1538.9368,216.704 Z m 216.4327,670.12997 -749.9209,749.92093 c -1.0722,1.0719 -2.2871,1.4651 -3.2519,1.5722 -0.2857,0.072 -0.5359,0.072 -0.7502,0.072 -1.0721,0 -2.64423,-0.2858 -4.00224,-1.6436 L 247.48765,886.79819 c -57.81543,-57.74402 -89.68894,-134.56916 -89.68894,-216.32532 0,-81.75637 31.87351,-158.65294 89.68894,-216.46831 57.7082,-57.63678 134.60485,-89.40313 216.46832,-89.40313 81.86348,0 158.72437,31.73055 216.43258,89.36733 L 920.9764,694.55665 c 21.47533,21.4752 50.06144,33.33861 80.4701,33.33861 0.2499,0 0.5001,0 0.7502,-0.035 30.1229,-0.14401 58.3873,-11.97061 79.7196,-33.30282 l 240.5522,-240.55312 c 57.708,-57.63657 134.6048,-89.40292 216.4682,-89.40292 81.8635,0.035 158.7243,31.76635 216.4327,89.36733 57.9227,57.92256 89.8317,134.78353 89.8317,216.43251 0,81.64918 -31.9091,158.51019 -89.8316,216.43272 z" />
</g>
</svg>
);
};

View File

@ -0,0 +1,23 @@
const preact = require('preact');
module.exports = function triangle(colours) {
if (colours.length === 1) {
return (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" class="ethan-icon" viewBox="0 0 2000 2000" >
<g transform="matrix(0,1.6438212,-1.6438212,0,2610.8872,-705.22614)">
<path class={colours[0]} d="M 1618.09,942.41 830.04,469.5 l -0.28,-0.17 -0.29,-0.15 -0.33,-0.17 -6.23,-3.08 -3.92,5.74 -0.04,0.06 c -1.23,1.8 -2.5,3.67 -3.68,5.44 l -3.63,5.45 4.62,4.64 0.25,0.25 319.17,319.17 -477.7,-177.04 -6.75,-2.5 -3.2,6.45 -5.26,10.61 -3.24,6.53 6.2,3.83 360.78,222.84 -355.81,52.26 -7.56,1.11 0.76,7.6 1.17,11.69 0.72,7.2 h 7.24 960.94 28.88 z m -423.5,-83.11 4.81,1.77 3.62,-3.62 5.68,-5.68 5.65,-5.66 -5.65,-5.66 -278.07,-278.12 611.78,367.08 H 825.43 l 227.48,-32.44 6.87,-0.98 v -6.94 -7.6 -4.46 l -3.79,-2.35 L 790.83,710.4 Z"/>
<path class={colours[0]} d="m 445.35,1002.67 h -7.24 l -0.72,7.2 -1.17,11.69 -0.76,7.6 7.56,1.11 355.82,52.27 -360.78,222.84 -6.2,3.83 3.24,6.53 5.26,10.61 3.2,6.45 6.75,-2.5 477.69,-177.05 -319.16,319.16 -0.26,0.26 -4.62,4.64 3.63,5.45 c 1.2,1.81 2.48,3.68 3.72,5.5 l 3.92,5.74 6.23,-3.08 0.33,-0.17 0.29,-0.14 0.28,-0.17 788.05,-472.91 24.76,-14.86 h -28.88 z m 555.66,116.81 5.65,-5.66 -5.65,-5.66 -5.68,-5.68 -3.62,-3.62 -4.8,1.77 -403.76,148.91 265.16,-164.25 3.79,-2.35 v -4.45 -7.6 -6.94 l -6.87,-0.98 -227.48,-32.44 h 716.98 l -611.78,367.08 z" />
</g>
</svg>
);
}
return (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" class="ethan-icon" viewBox="0 0 2000 2000" >
<g transform="matrix(0,1.6438212,-1.6438212,0,2610.8872,-705.22614)">
<path class={colours[1]} d="M 1618.09,942.41 830.04,469.5 l -0.28,-0.17 -0.29,-0.15 -0.33,-0.17 -6.23,-3.08 -3.92,5.74 -0.04,0.06 c -1.23,1.8 -2.5,3.67 -3.68,5.44 l -3.63,5.45 4.62,4.64 0.25,0.25 319.17,319.17 -477.7,-177.04 -6.75,-2.5 -3.2,6.45 -5.26,10.61 -3.24,6.53 6.2,3.83 360.78,222.84 -355.81,52.26 -7.56,1.11 0.76,7.6 1.17,11.69 0.72,7.2 h 7.24 960.94 28.88 z m -423.5,-83.11 4.81,1.77 3.62,-3.62 5.68,-5.68 5.65,-5.66 -5.65,-5.66 -278.07,-278.12 611.78,367.08 H 825.43 l 227.48,-32.44 6.87,-0.98 v -6.94 -7.6 -4.46 l -3.79,-2.35 L 790.83,710.4 Z"/>
<path class={colours[0]} d="m 445.35,1002.67 h -7.24 l -0.72,7.2 -1.17,11.69 -0.76,7.6 7.56,1.11 355.82,52.27 -360.78,222.84 -6.2,3.83 3.24,6.53 5.26,10.61 3.2,6.45 6.75,-2.5 477.69,-177.05 -319.16,319.16 -0.26,0.26 -4.62,4.64 3.63,5.45 c 1.2,1.81 2.48,3.68 3.72,5.5 l 3.92,5.74 6.23,-3.08 0.33,-0.17 0.29,-0.14 0.28,-0.17 788.05,-472.91 24.76,-14.86 h -28.88 z m 555.66,116.81 5.65,-5.66 -5.65,-5.66 -5.68,-5.68 -3.62,-3.62 -4.8,1.77 -403.76,148.91 265.16,-164.25 3.79,-2.35 v -4.45 -7.6 -6.94 l -6.87,-0.98 -227.48,-32.44 h 716.98 l -611.78,367.08 z" />
</g>
</svg>
);
};

View File

@ -0,0 +1,37 @@
const preact = require('preact');
module.exports = function triangle(colours) {
if (colours.length === 1) {
return (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" class="ethan-icon" viewBox="0 0 2010 2010" >
<g transform="matrix(1.5649456,0,0,1.5649456,-567.76837,-567.79923)">
<path class={colours[0]} d="M1639.23,1018.92l-531.7,531.7c-3.77,3.77-8.8,5.77-13.92,5.77c-2.54,0-5.1-0.49-7.53-1.5 c-7.37-3.03-12.16-10.23-12.16-18.19v-334.77H739.16c-10.89,0-19.69-8.8-19.69-19.69c0-10.88,8.8-19.69,19.69-19.69h354.46 c10.89,0,19.69,8.81,19.69,19.69v306.93l484.16-484.16l-484.16-484.15v306.93c0,10.88-8.8,19.69-19.69,19.69H739.16 c-10.89,0-19.69-8.81-19.69-19.69c0-10.89,8.8-19.69,19.69-19.69h334.76V473.33c0-7.96,4.79-15.16,12.16-18.19 c7.38-3.04,15.83-1.35,21.45,4.27l531.7,531.7C1646.92,998.77,1646.92,1011.23,1639.23,1018.92z"/>
<path class={colours[0]} d="M542.23,1004.99c0-10.88,8.81-19.69,19.69-19.69h708.92c10.89,0,19.69,8.81,19.69,19.69 c0,10.89-8.8,19.7-19.69,19.7H561.92C551.04,1024.69,542.23,1015.88,542.23,1004.99z"/>
<path class={colours[0]} d="M680.08,1182.23c0,10.89-8.81,19.69-19.7,19.69H384.69c-10.88,0-19.69-8.8-19.69-19.69 c0-10.88,8.81-19.69,19.69-19.69h275.69C671.27,1162.53,680.08,1171.34,680.08,1182.23z"/>
</g>
</svg>
);
}
return (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" class="ethan-icon" viewBox="0 0 2010 2010" >
<clipPath id="firstColour">
<rect x="0" y="0" width="1005" height="2010" />
</clipPath>
<clipPath id="secondColour">
<rect x="1005" y="0" width="1005" height="2010" />
</clipPath>
<g transform="matrix(1.5649456,0,0,1.5649456,-567.76837,-567.79923)">
<path clip-path="url(#firstColour)" class={colours[0]} d="M1639.23,1018.92l-531.7,531.7c-3.77,3.77-8.8,5.77-13.92,5.77c-2.54,0-5.1-0.49-7.53-1.5 c-7.37-3.03-12.16-10.23-12.16-18.19v-334.77H739.16c-10.89,0-19.69-8.8-19.69-19.69c0-10.88,8.8-19.69,19.69-19.69h354.46 c10.89,0,19.69,8.81,19.69,19.69v306.93l484.16-484.16l-484.16-484.15v306.93c0,10.88-8.8,19.69-19.69,19.69H739.16 c-10.89,0-19.69-8.81-19.69-19.69c0-10.89,8.8-19.69,19.69-19.69h334.76V473.33c0-7.96,4.79-15.16,12.16-18.19 c7.38-3.04,15.83-1.35,21.45,4.27l531.7,531.7C1646.92,998.77,1646.92,1011.23,1639.23,1018.92z"/>
<path clip-path="url(#firstColour)" class={colours[0]} d="M542.23,1004.99c0-10.88,8.81-19.69,19.69-19.69h708.92c10.89,0,19.69,8.81,19.69,19.69 c0,10.89-8.8,19.7-19.69,19.7H561.92C551.04,1024.69,542.23,1015.88,542.23,1004.99z"/>
<path clip-path="url(#firstColour)" class={colours[0]} d="M680.08,1182.23c0,10.89-8.81,19.69-19.7,19.69H384.69c-10.88,0-19.69-8.8-19.69-19.69 c0-10.88,8.81-19.69,19.69-19.69h275.69C671.27,1162.53,680.08,1171.34,680.08,1182.23z"/>
<path clip-path="url(#secondColour)" class={colours[1]} d="M1639.23,1018.92l-531.7,531.7c-3.77,3.77-8.8,5.77-13.92,5.77c-2.54,0-5.1-0.49-7.53-1.5 c-7.37-3.03-12.16-10.23-12.16-18.19v-334.77H739.16c-10.89,0-19.69-8.8-19.69-19.69c0-10.88,8.8-19.69,19.69-19.69h354.46 c10.89,0,19.69,8.81,19.69,19.69v306.93l484.16-484.16l-484.16-484.15v306.93c0,10.88-8.8,19.69-19.69,19.69H739.16 c-10.89,0-19.69-8.81-19.69-19.69c0-10.89,8.8-19.69,19.69-19.69h334.76V473.33c0-7.96,4.79-15.16,12.16-18.19 c7.38-3.04,15.83-1.35,21.45,4.27l531.7,531.7C1646.92,998.77,1646.92,1011.23,1639.23,1018.92z"/>
<path clip-path="url(#secondColour)" class={colours[1]} d="M542.23,1004.99c0-10.88,8.81-19.69,19.69-19.69h708.92c10.89,0,19.69,8.81,19.69,19.69 c0,10.89-8.8,19.7-19.69,19.7H561.92C551.04,1024.69,542.23,1015.88,542.23,1004.99z"/>
<path clip-path="url(#secondColour)" class={colours[1]} d="M680.08,1182.23c0,10.89-8.81,19.69-19.7,19.69H384.69c-10.88,0-19.69-8.8-19.69-19.69 c0-10.88,8.81-19.69,19.69-19.69h275.69C671.27,1162.53,680.08,1171.34,680.08,1182.23z"/>
</g>
</svg>
);
};

View File

@ -44,15 +44,14 @@ class TargetSvg extends Component {
animating, animating,
game, game,
gameEffectInfo, gameEffectInfo,
authenticated,
} = props; } = props;
const { width, height } = state; const { width, height } = state;
if (!game) return false; // game will be null when battle ends if (!game) return false; // game will be null when battle ends
if (game.phase === 'Finished') return false; // Clear everything if its over (in case of abandon) if (game.phase === 'Finished') return false; // Clear everything if its over (in case of abandon)
// First time joining game phase // First round of a game
if (!authenticated && game.stack.length === 0) { if (!game.resolutions.length && game.stack.length === 0) {
return ( return (
<div class="resolving-skill"> <div class="resolving-skill">
<h2><b>Select a skill</b> from each construct, <b>click a target</b> for that skill and then click <b>READY</b>.</h2> <h2><b>Select a skill</b> from each construct, <b>click a target</b> for that skill and then click <b>READY</b>.</h2>

View File

@ -9,7 +9,7 @@ const buttons = require('./buttons');
const { removeTier } = require('../utils'); const { removeTier } = require('../utils');
const addState = connect( const addState = connect(
({ itemUnequip, vboxHighlight, vboxSelected, tutorial }) => ({ itemUnequip, vboxHighlight, vboxSelected, tutorial })); ({ itemUnequip, vboxHighlight, vboxSelected, tutorial, instance }) => ({ itemUnequip, vboxHighlight, vboxSelected, tutorial, instance }));
class stashElement extends preact.Component { class stashElement extends preact.Component {
shouldComponentUpdate(newProps) { shouldComponentUpdate(newProps) {
@ -41,6 +41,7 @@ class stashElement extends preact.Component {
vboxHighlight, vboxHighlight,
vboxSelected, vboxSelected,
tutorial, tutorial,
instance,
} = props; } = props;
const { storeSelect, stashSelect } = vboxSelected; const { storeSelect, stashSelect } = vboxSelected;
@ -97,7 +98,7 @@ class stashElement extends preact.Component {
: `${border} ${notValidCombo ? 'fade' : ''}`; : `${border} ${notValidCombo ? 'fade' : ''}`;
const invObject = shapes[v] ? shapes[v]() : v; const invObject = shapes[v] ? shapes[v]() : v;
const tutorialDisable = tutorial === 1; const tutorialDisable = tutorial === 1 && instance.time_control === 'Practice' && instance.rounds.length === 1;
return ( return (
<label <label
key={i} key={i}

View File

@ -14,7 +14,7 @@ const addState = connect(
} = state; } = state;
return { return {
promptRegister: tutorial === 99, // see events promptRegister: !tutorial,
}; };
}, },
); );

View File

@ -38,9 +38,13 @@ function Register(args) {
const registerSubmit = (event) => { const registerSubmit = (event) => {
event.preventDefault(); event.preventDefault();
this.setState({ name: '', password: '', confirm: '' });
submitRegister(name, password); submitRegister(name, password);
} }
const pwLen = () =>
!password || password && password.length > 3;
const registerConfirm = () => const registerConfirm = () =>
password === confirm; password === confirm;
@ -50,24 +54,30 @@ function Register(args) {
return ( return (
<div class="login"> <div class="login">
<label for="username">Username</label> <label class="login-input" for="username">Username </label>
<input <input
class="login-input" class="login-input"
type="email" type="text"
placeholder="username" placeholder="username"
value={this.state.name} value={this.state.name}
onInput={linkState(this, 'name')} onInput={linkState(this, 'name')}
/> />
<label for="password">Password - min 4 chars</label> <label
class={`${pwLen() ? '' : 'red'} login-input`}
for="password">Password - min 4 chars
</label>
<input <input
class="login-input" class={`${pwLen() ? '' : 'red'} login-input`}
type="password" type="password"
placeholder="password" placeholder="password"
autocomplete="new-password" autocomplete="new-password"
value={this.state.password} value={this.state.password}
onInput={linkState(this, 'password')} onInput={linkState(this, 'password')}
/> />
<label for="confirm">Confirm Password</label> <label
class={`${registerConfirm() ? '' : 'red'} login-input`}
for="confirm">Confirm Password
</label>
<input <input
class={`${registerConfirm() ? '' : 'red'} login-input`} class={`${registerConfirm() ? '' : 'red'} login-input`}
type="password" type="password"

View File

@ -165,9 +165,9 @@ function registerEvents(store) {
function setInvite(code) { function setInvite(code) {
if (!code) return store.dispatch(actions.setInvite(null)); if (!code) return store.dispatch(actions.setInvite(null));
const link = `${document.location.origin}#join=${code}`;
navigator.clipboard.writeText(code).then(() => { navigator.clipboard.writeText(link).then(() => {
notify(`your invite code ${code} was copied to the clipboard.`); notify('Your invite code was copied to the clipboard.');
}, () => {}); }, () => {});
return store.dispatch(actions.setInvite(code)); return store.dispatch(actions.setInvite(code));
@ -180,8 +180,8 @@ function registerEvents(store) {
setPvp(false); setPvp(false);
const player = v.players.find(p => p.id === account.id); const player = v.players.find(p => p.id === account.id);
store.dispatch(actions.setPlayer(player)); store.dispatch(actions.setPlayer(player));
if (tutorial) tutorialVbox(player, store, tutorial); if (tutorial && v.rounds.length === 1 && v.time_control === 'Practice') tutorialVbox(player, store, tutorial);
if (v.phase === 'Finished') { if (v.phase === 'Finished') {
ws.sendAccountInstances(); ws.sendAccountInstances();
@ -211,12 +211,8 @@ function registerEvents(store) {
return true; return true;
} }
function startTutorial() {
store.dispatch(actions.setTutorial(1));
}
function promptRegister() { function promptRegister() {
store.dispatch(actions.setTutorial(99)); store.dispatch(actions.setTutorial(null));
store.dispatch(actions.setInstance(null)); store.dispatch(actions.setInstance(null));
} }
@ -249,7 +245,6 @@ function registerEvents(store) {
setSubscription, setSubscription,
setWs, setWs,
startTutorial,
promptRegister, promptRegister,
urlHashChange, urlHashChange,

View File

@ -299,7 +299,7 @@ function createSocket(events) {
ChatWheel: wheel => events.setChatWheel(wheel), ChatWheel: wheel => events.setChatWheel(wheel),
// Joining: () => events.notify('Searching for instance...'), // Joining: () => events.notify('Searching for instance...'),
StartTutorial: () => events.startTutorial(), // StartTutorial: () => events.startTutorial(),
PromptRegister: () => events.promptRegister(), PromptRegister: () => events.promptRegister(),
Processing: () => true, Processing: () => true,
@ -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);

View File

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

View File

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

View File

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

View File

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

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) { 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,10 +1853,10 @@ 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 }) },
}, },
); );
if !game.affected(cast.target, Effect::Electrocuted) { if !game.affected(cast.target, Effect::Electrocuted) {
game.action(cast, game.action(cast,
Action::Damage { Action::Damage {
@ -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 }) },
} }
); );

View File

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

View File

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

View File

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

View File

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

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 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() {

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::{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};
@ -61,7 +63,6 @@ pub enum RpcMessage {
SubscriptionState(Option<Subscription>), SubscriptionState(Option<Subscription>),
Pong(()), Pong(()),
StartTutorial(()),
PromptRegister(()), PromptRegister(()),
QueueRequested(()), QueueRequested(()),
@ -133,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>,
} }
@ -200,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(),
} }
@ -244,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(),

View File

@ -69,8 +69,6 @@ impl User for Anonymous {
info!("anonymous connection"); info!("anonymous connection");
self.ws.send(RpcMessage::AccountState(self.account.clone()))?; self.ws.send(RpcMessage::AccountState(self.account.clone()))?;
self.ws.send(RpcMessage::StartTutorial(()))?;
Ok(()) Ok(())
} }

View File

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

View File

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