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 =\
* 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.3",
"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

@ -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 {
color: @green;
stroke: @green;

View File

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

View File

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

View File

@ -15,7 +15,7 @@ const addState = connect(
ws,
game,
authenticated,
instance,
activeSkill,
animFocus,
resolution,
@ -30,7 +30,7 @@ const addState = connect(
return {
game,
authenticated,
instance,
activeSkill,
animFocus,
resolution,
@ -54,7 +54,7 @@ class GameConstruct extends preact.Component {
render() {
const {
// Changing state variables
authenticated,
instance,
game,
activeSkill,
animFocus,
@ -81,7 +81,9 @@ class GameConstruct extends preact.Component {
const unfocus = animFocus && !animFocus.includes(construct.id) ? 'unfocus' : '';
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
? <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,
account,
authenticated,
instance,
chatShow,
animating,
} = state;
@ -35,6 +36,7 @@ const addState = connect(
game,
account,
authenticated,
instance,
chatShow,
sendAbandon,
sendGameSkillClear,
@ -68,6 +70,7 @@ function GameCtrlBtns(args) {
account,
chatShow,
authenticated,
instance,
getInstanceState,
sendGameSkillClear,
@ -86,7 +89,8 @@ function GameCtrlBtns(args) {
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 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 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

@ -60,6 +60,10 @@ const addState = connect(
}
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: [] }));
return dispatch(actions.setItemUnequip(v));
}

View File

@ -50,7 +50,7 @@ function InstanceCtrlBtns(args) {
} = args;
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 (
<div class="instance-ctrl-btns">
<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/vbox.colour');
const speed = require('./svgs/speed');
const power = require('./svgs/power');
const life = require('./svgs/life');
module.exports = {
circle,
diamond,
@ -19,6 +23,7 @@ module.exports = {
square,
squircle,
triangle,
speed,
saw,
vboxColour,
@ -27,17 +32,17 @@ module.exports = {
Blue: () => vboxColour('blue'),
// stats
RedLife: () => square(['red']),
GreenLife: () => square(['green']),
BlueLife: () => square(['blue']),
RedPower: () => circle(['red']),
GreenPower: () => circle(['green']),
BluePower: () => circle(['blue']),
SpeedStat: () => triangle(['white']),
RedLife: () => life(['red']),
GreenLife: () => life(['green']),
BlueLife: () => life(['blue']),
RedPower: () => power(['red']),
GreenPower: () => power(['green']),
BluePower: () => power(['blue']),
SpeedStat: () => speed(['white']),
POWER: () => circle(['white']),
LIFE: () => square(['white']),
SPEED: () => triangle(['white']),
POWER: () => power(['white']),
LIFE: () => life(['white']),
SPEED: () => speed(['white']),
// specs
// Base
@ -50,343 +55,343 @@ module.exports = {
Power: () =>
<figure>
{circle(['white'])}
{power(['white'])}
<figcaption>Power</figcaption>
</figure>,
Life: () =>
<figure>
{square(['white'])}
{life(['white'])}
<figcaption>Life</figcaption>
</figure>,
Speed: () =>
<figure>
{triangle(['white'])}
{speed(['white'])}
<figcaption>Speed</figcaption>
</figure>,
// Lifes Upgrades
LifeGG: () =>
<figure>
{square(['green'])}
{life(['green'])}
<figcaption>Life</figcaption>
</figure>,
LifeRR: () =>
<figure>
{square(['red'])}
{life(['red'])}
<figcaption>Life</figcaption>
</figure>,
LifeBB:() =>
<figure>
{square(['blue'])}
{life(['blue'])}
<figcaption>Life</figcaption>
</figure>,
LifeRG: () =>
<figure>
{square(['red', 'green'])}
{life(['red', 'green'])}
<figcaption>Life</figcaption>
</figure>,
LifeGB: () =>
<figure>
{square(['green', 'blue'])}
{life(['green', 'blue'])}
<figcaption>Life</figcaption>
</figure>,
LifeRB:() =>
<figure>
{square(['red', 'blue'])}
{life(['red', 'blue'])}
<figcaption>Life</figcaption>
</figure>,
LifeGGPlus: () =>
<figure>
{square(['green'])}
{life(['green'])}
<figcaption>Life+</figcaption>
</figure>,
LifeRRPlus: () =>
<figure>
{square(['red'])}
{life(['red'])}
<figcaption>Life+</figcaption>
</figure>,
LifeBBPlus:() =>
<figure>
{square(['blue'])}
{life(['blue'])}
<figcaption>Life+</figcaption>
</figure>,
LifeRGPlus: () =>
<figure>
{square(['red', 'green'])}
{life(['red', 'green'])}
<figcaption>Life+</figcaption>
</figure>,
LifeGBPlus: () =>
<figure>
{square(['green', 'blue'])}
{life(['green', 'blue'])}
<figcaption>Life+</figcaption>
</figure>,
LifeRBPlus:() =>
<figure>
{square(['red', 'blue'])}
{life(['red', 'blue'])}
<figcaption>Life+</figcaption>
</figure>,
LifeGGPlusPlus: () =>
<figure>
{square(['green'])}
{life(['green'])}
<figcaption>Life++</figcaption>
</figure>,
LifeRRPlusPlus: () =>
<figure>
{square(['red'])}
{life(['red'])}
<figcaption>Life++</figcaption>
</figure>,
LifeBBPlusPlus:() =>
<figure>
{square(['blue'])}
{life(['blue'])}
<figcaption>Life++</figcaption>
</figure>,
LifeRGPlusPlus: () =>
<figure>
{square(['red', 'green'])}
{life(['red', 'green'])}
<figcaption>Life++</figcaption>
</figure>,
LifeGBPlusPlus: () =>
<figure>
{square(['green', 'blue'])}
{life(['green', 'blue'])}
<figcaption>Life++</figcaption>
</figure>,
LifeRBPlusPlus:() =>
<figure>
{square(['red', 'blue'])}
{life(['red', 'blue'])}
<figcaption>Life++</figcaption>
</figure>,
// Powers Upgrades
PowerGG: () =>
<figure>
{circle(['green'])}
{power(['green'])}
<figcaption>Power</figcaption>
</figure>,
PowerRR: () =>
<figure>
{circle(['red'])}
{power(['red'])}
<figcaption>Power</figcaption>
</figure>,
PowerBB:() =>
<figure>
{circle(['blue'])}
{power(['blue'])}
<figcaption>Power</figcaption>
</figure>,
PowerRG: () =>
<figure>
{circle(['red', 'green'])}
{power(['red', 'green'])}
<figcaption>Power</figcaption>
</figure>,
PowerGB: () =>
<figure>
{circle(['green', 'blue'])}
{power(['green', 'blue'])}
<figcaption>Power</figcaption>
</figure>,
PowerRB:() =>
<figure>
{circle(['red', 'blue'])}
{power(['red', 'blue'])}
<figcaption>Power</figcaption>
</figure>,
PowerGGPlus: () =>
<figure>
{circle(['green'])}
{power(['green'])}
<figcaption>Power+</figcaption>
</figure>,
PowerRRPlus: () =>
<figure>
{circle(['red'])}
{power(['red'])}
<figcaption>Power+</figcaption>
</figure>,
PowerBBPlus:() =>
<figure>
{circle(['blue'])}
{power(['blue'])}
<figcaption>Power+</figcaption>
</figure>,
PowerRGPlus: () =>
<figure>
{circle(['red', 'green'])}
{power(['red', 'green'])}
<figcaption>Power+</figcaption>
</figure>,
PowerGBPlus: () =>
<figure>
{circle(['green', 'blue'])}
{power(['green', 'blue'])}
<figcaption>Power+</figcaption>
</figure>,
PowerRBPlus:() =>
<figure>
{circle(['red', 'blue'])}
{power(['red', 'blue'])}
<figcaption>Power+</figcaption>
</figure>,
PowerGGPlusPlus: () =>
<figure>
{circle(['green'])}
{power(['green'])}
<figcaption>Power++</figcaption>
</figure>,
PowerRRPlusPlus: () =>
<figure>
{circle(['red'])}
{power(['red'])}
<figcaption>Power++</figcaption>
</figure>,
PowerBBPlusPlus:() =>
<figure>
{circle(['blue'])}
{power(['blue'])}
<figcaption>Power++</figcaption>
</figure>,
PowerRGPlusPlus: () =>
<figure>
{circle(['red', 'green'])}
{power(['red', 'green'])}
<figcaption>Power++</figcaption>
</figure>,
PowerGBPlusPlus: () =>
<figure>
{circle(['green', 'blue'])}
{power(['green', 'blue'])}
<figcaption>Power++</figcaption>
</figure>,
PowerRBPlusPlus:() =>
<figure>
{circle(['red', 'blue'])}
{power(['red', 'blue'])}
<figcaption>Power++</figcaption>
</figure>,
// Speeds Upgrades
SpeedGG: () =>
<figure>
{triangle(['green'])}
{speed(['green'])}
<figcaption>Speed</figcaption>
</figure>,
SpeedRR: () =>
<figure>
{triangle(['red'])}
{speed(['red'])}
<figcaption>Speed</figcaption>
</figure>,
SpeedBB:() =>
<figure>
{triangle(['blue'])}
{speed(['blue'])}
<figcaption>Speed</figcaption>
</figure>,
SpeedRG: () =>
<figure>
{triangle(['red', 'green'])}
{speed(['red', 'green'])}
<figcaption>Speed</figcaption>
</figure>,
SpeedGB: () =>
<figure>
{triangle(['green', 'blue'])}
{speed(['green', 'blue'])}
<figcaption>Speed</figcaption>
</figure>,
SpeedRB:() =>
<figure>
{triangle(['red', 'blue'])}
{speed(['red', 'blue'])}
<figcaption>Speed</figcaption>
</figure>,
SpeedGGPlus: () =>
<figure>
{triangle(['green'])}
{speed(['green'])}
<figcaption>Speed+</figcaption>
</figure>,
SpeedRRPlus: () =>
<figure>
{triangle(['red'])}
{speed(['red'])}
<figcaption>Speed+</figcaption>
</figure>,
SpeedBBPlus:() =>
<figure>
{triangle(['blue'])}
{speed(['blue'])}
<figcaption>Speed+</figcaption>
</figure>,
SpeedRGPlus: () =>
<figure>
{triangle(['red', 'green'])}
{speed(['red', 'green'])}
<figcaption>Speed+</figcaption>
</figure>,
SpeedGBPlus: () =>
<figure>
{triangle(['green', 'blue'])}
{speed(['green', 'blue'])}
<figcaption>Speed+</figcaption>
</figure>,
SpeedRBPlus:() =>
<figure>
{triangle(['red', 'blue'])}
{speed(['red', 'blue'])}
<figcaption>Speed+</figcaption>
</figure>,
SpeedGGPlusPlus: () =>
<figure>
{triangle(['green'])}
{speed(['green'])}
<figcaption>Speed++</figcaption>
</figure>,
SpeedRRPlusPlus: () =>
<figure>
{triangle(['red'])}
{speed(['red'])}
<figcaption>Speed++</figcaption>
</figure>,
SpeedBBPlusPlus:() =>
<figure>
{triangle(['blue'])}
{speed(['blue'])}
<figcaption>Speed++</figcaption>
</figure>,
SpeedRGPlusPlus: () =>
<figure>
{triangle(['red', 'green'])}
{speed(['red', 'green'])}
<figcaption>Speed++</figcaption>
</figure>,
SpeedGBPlusPlus: () =>
<figure>
{triangle(['green', 'blue'])}
{speed(['green', 'blue'])}
<figcaption>Speed++</figcaption>
</figure>,
SpeedRBPlusPlus:() =>
<figure>
{triangle(['red', 'blue'])}
{speed(['red', 'blue'])}
<figcaption>Speed++</figcaption>
</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,
game,
gameEffectInfo,
authenticated,
} = props;
const { width, height } = state;
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)
// First time joining game phase
if (!authenticated && game.stack.length === 0) {
// First round of a game
if (!game.resolutions.length && game.stack.length === 0) {
return (
<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>

View File

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

View File

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

View File

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

View File

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

View File

@ -299,7 +299,7 @@ function createSocket(events) {
ChatWheel: wheel => events.setChatWheel(wheel),
// Joining: () => events.notify('Searching for instance...'),
StartTutorial: () => events.startTutorial(),
// StartTutorial: () => events.startTutorial(),
PromptRegister: () => events.promptRegister(),
Processing: () => true,
@ -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.3"
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.3",
"version": "1.13.0",
"description": "",
"main": "index.js",
"scripts": {

View File

@ -1,6 +1,6 @@
[package]
name = "mnml"
version = "1.12.3"
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};
@ -61,7 +63,6 @@ pub enum RpcMessage {
SubscriptionState(Option<Subscription>),
Pong(()),
StartTutorial(()),
PromptRegister(()),
QueueRequested(()),
@ -133,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>,
}
@ -200,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(),
}
@ -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 id = anon_account.id;
DeflateHandler::new(
Connection {
id,
id: rng.gen::<usize>(),
ws: tx.clone(),
pool: pool.clone(),
stripe: stripe.clone(),

View File

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

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.3",
"version": "1.13.0",
"description": "",
"main": "index.js",
"scripts": {