Merge branch 'release/1.8.0' into develop

This commit is contained in:
Mashy 2019-11-07 10:09:24 +10:00
commit a220529ac7
38 changed files with 3087 additions and 224 deletions

View File

@ -2,6 +2,53 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/). This project adheres to [Semantic Versioning](http://semver.org/).
## [1.8.0] - 2019-10-31
# Added
- Drag and drop for vbox interactions can be used instead of single click / double click
- Base white items and be directly equipped from vbox rather than going through the inventory
- Useful if you want to equip an item without further combining with colours
- You can swap skills and specs between constructs without using the inventory
# Changed
- Construct life changed
- You now start with 800 green life (down from 950)
- You now start with 125 red life and blue life (up from 0)
- Game phase layout
- Enemy team effects appear under your construct instead of next to it
- Game phase constructs line up symmetrically now
- Mobile
- Tutorial is disabled for mobile view
- Landscape is now default view
- Vbox phase everything is now in one view
- Game constructs and animations are much larger in mobile view
- Amplify
Now increases green power
- Absorb
- Reduced duration and cooldown from 2T -> 1T (Absorption duration unchanged)
- Absorption damage is now based on all damage taken (previously only green damage taken)
- Now recharges blue life based on 95 / 120 / 155 blue power
- Banish
Reduced cooldown to 1T
- Decay
Removed cooldown
- Haste / Hybrid
Fixed issue when hybridblast and hastestrike wouldn't trigger from upgraded + skills
- Intercept
- Reduced duration to 1T down from 2T
- Reduced cooldown to 1T down from 2T
## [1.7.0] - 2019-10-31 ## [1.7.0] - 2019-10-31
# Added # Added
- Step by step tutorial - Step by step tutorial

View File

@ -1 +1 @@
1.7.1 1.8.0

View File

@ -4,16 +4,7 @@
*PRODUCTION* *PRODUCTION*
* can't reset password without knowing password =\ * can't reset password without knowing password =\
* ws gzip encoding
* mobile
- force to landscape view and try make everything fit
OR
- 2 pages vbox / equip
- vbox page as current with equip button at bottom
- equip page with inventory and all 3 construct avatars
- click one of the avatars to expand out skill / spec slots
- show the info pane at the bottom or as an overlay
* mobile info page * mobile info page
@ -36,8 +27,6 @@
- Strike + SpeedRR -> StrikeSpeed (strike has Y% more speed) - Strike + SpeedRR -> StrikeSpeed (strike has Y% more speed)
- Strike + LifeRR -> StrikeLife (Strike recharges X% of damage as red life) - Strike + LifeRR -> StrikeLife (Strike recharges X% of damage as red life)
* move item from one construct to another
* ACP * ACP
* essential * essential

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

2593
client/assets/rotate.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 96 KiB

View File

@ -4,7 +4,7 @@
grid-template-columns: 1fr 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr 1fr;
div { div {
padding-right: 2em; padding-right: 1em;
} }
button { button {

View File

@ -169,3 +169,15 @@ button {
color: @green; color: @green;
} }
} }
@keyframes rb-text {
0% {
color: @red;
}
50% {
color: @white;
}
100% {
color: @blue;
}
}

View File

@ -96,6 +96,20 @@ aside {
border-color: forestgreen; border-color: forestgreen;
} }
} }
// all green
// &:enabled {
// background: forestgreen;
// color: black;
// border-color: forestgreen;
// &:hover {
// color: forestgreen;
// border-color: forestgreen;
// background: 0;
// }
// }
} }
.team-page-ctrl { .team-page-ctrl {

View File

@ -10,6 +10,10 @@
// "opponent" // "opponent"
// "target " // "target "
// "player "; // "player ";
.skill-description {
font-size: 75%;
}
} }
.game .team, .faceoff .team { .game .team, .faceoff .team {
@ -43,8 +47,11 @@
.game-construct { .game-construct {
align-items: flex-start; align-items: flex-start;
grid-template-columns: 1fr 2fr; grid-template-rows: 2fr min-content;
grid-template-rows: 1fr; grid-template-rows: 1fr;
grid-template-areas:
"right"
"left";
.right { .right {
height: 100%; height: 100%;
@ -79,12 +86,16 @@
justify-items: center; justify-items: center;
grid-template-columns: 1fr; grid-template-columns: 1fr;
grid-template-rows: min-content 1fr; grid-template-rows: 1fr 2fr;
grid-template-areas:
"left"
"right";
.left { .left {
width: 100%; width: 100%;
display: grid; display: grid;
grid-area: left;
grid-template-columns: 1fr 2fr; grid-template-columns: 1fr 2fr;
grid-template-areas: grid-template-areas:
"skills effects"; "skills effects";
@ -92,6 +103,7 @@
.right { .right {
display: grid; display: grid;
grid-area: right;
grid-template-rows: minmax(min-content, 1fr) min-content min-content; grid-template-rows: minmax(min-content, 1fr) min-content min-content;
grid-template-areas: grid-template-areas:
"avatar" "avatar"
@ -121,6 +133,7 @@
} }
.skills { .skills {
z-index: 2;
button { button {
width: 100%; width: 100%;
height: 2em; height: 2em;
@ -132,6 +145,7 @@
} }
.effects { .effects {
z-index: 2;
grid-area: effects; grid-area: effects;
white-space: nowrap; white-space: nowrap;
width: 100%; width: 100%;
@ -261,14 +275,6 @@
color: #a52a2a; color: #a52a2a;
} }
.red-damage text {
fill: #a52a2a;
}
.red-damage .stats {
/*border-top: 1px solid #a52a2a;*/
}
.game-construct.blue-damage { .game-construct.blue-damage {
color: #3050f8; color: #3050f8;
opacity: 1; opacity: 1;
@ -278,13 +284,6 @@
color: #3050f8; color: #3050f8;
} }
.blue-damage text {
fill: #3050f8;
}
.blue-damage .stats {
}
.game-construct.green-damage { .game-construct.green-damage {
color: #1FF01F; color: #1FF01F;
opacity: 1; opacity: 1;
@ -294,31 +293,8 @@
color: #1FF01F; color: #1FF01F;
} }
.green-damage text { .game-construct.rb-damage {
fill: #1FF01F; animation: rb-text 1s cubic-bezier(0.5, 0, 0.5, 1) 0s infinite;
}
.green-damage .stats {
/*border-top: 1px solid #1FF01F;*/
}
.game-construct.purple-damage {
/* filter: drop-shadow(0 0 0.2em purple);
*/ color: purple;
border-color: purple;
}
.purple-damage button {
border: 1px solid purple;
color: purple;
}
.purple-damage text {
fill: purple;
}
.purple-damage .stats {
border-top: 1px solid purple;
} }
.game .img, .faceoff .img { .game .img, .faceoff .img {

View File

@ -9,10 +9,6 @@
grid-template-areas: grid-template-areas:
"vbox info" "vbox info"
"constructs constructs"; "constructs constructs";
hr {
grid-area: rule;
}
} }
@media (max-width: 1920px) { @media (max-width: 1920px) {
@ -88,65 +84,6 @@
border-right-width: 0; border-right-width: 0;
} }
/* VBOX */
.vbox {
align-content: space-between;
grid-area: vbox;
display: grid;
grid-template-rows: min-content min-content min-content;
grid-template-columns: 1fr min-content 1fr;
grid-template-areas:
"vbox varrow inventory"
"vbox varrow combiner";
}
.vbox-inventory {
grid-area: inventory;
}
.vbox-combiner {
grid-area: combiner;
display: flex;
flex-flow: column;
justify-content: flex-end;
}
.vbox-arrow, .vbox-arrow-mobile {
display: flex;
justify-content:center;
align-content:center;
flex-direction:column;
margin: 1em 0.25em 0 0.25em;
grid-area: varrow;
font-size: 2em;
color: @gray-hint;
}
.vbox-combiner button {
flex: 0;
}
.vbox-hdr {
display: flex;
}
.vbox-hdr h3 {
flex: 1;
}
.vbox-hdr .bits {
font-size: 2em;
line-height: 1em;
animation: bits 1s ease-out;
}
.arrow {
grid-area: arrow;
color: @gray-hint;
}
@keyframes action { @keyframes action {
0% { 0% {
color: palegoldenrod; color: palegoldenrod;
@ -195,6 +132,7 @@
grid-area: skills; grid-area: skills;
padding: 0 0.5em; padding: 0 0.5em;
margin-bottom: 0.75em; margin-bottom: 0.75em;
z-index: 2;
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
@ -212,6 +150,7 @@
grid-area: specs; grid-area: specs;
padding: 0 0.5em; padding: 0 0.5em;
margin-bottom: 0.75em; margin-bottom: 0.75em;
z-index: 2;
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);

View File

@ -142,6 +142,19 @@ section {
border-color: forestgreen; border-color: forestgreen;
} }
} }
// // all green
// button.ready:enabled {
// background: forestgreen;
// color: black;
// border-color: forestgreen;
// &:hover {
// color: forestgreen;
// border-color: forestgreen;
// background: 0;
// }
// }
} }
} }

View File

@ -309,6 +309,7 @@ li {
background-size: contain; background-size: contain;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
z-index: 0;
// pointer-events: none; // pointer-events: none;
} }
@ -317,3 +318,28 @@ li {
height: 1px; height: 1px;
padding: 0px; padding: 0px;
} }
#rotate {
display: none;
z-index: 1000;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-image: url("./../rotate.svg");
background-size: cover;
background-repeat: no-repeat;
background-position: center;
&.show {
display: flex;
}
h1 {
flex: 1;
}
}

View File

@ -7,29 +7,29 @@
font-size: 8pt; font-size: 8pt;
padding: 0.25em; padding: 0.25em;
.menu {
.logo {
display: none;
}
.team {
height: 20em;
}
}
.instance { .instance {
grid-template-columns: 1fr;
grid-template-rows: min-content 1fr;
grid-template-areas:
"vbox"
"constructs";
svg { svg {
stroke-width: 1.25em; stroke-width: 1.25em;
} }
} }
.game { .game {
.team, #targeting, .resolving-skill { .team, #targeting, .resolving-skill {
width: calc(90% - 3em); width: calc(90% - 3em);
} }
.game-construct { .game-construct {
grid-template-columns: 1fr;
grid-template-rows: min-content 1fr;
.avatar { .avatar {
grid-area: initial; grid-area: initial;
position: absolute; position: absolute;
@ -43,6 +43,51 @@
height: 1em; height: 1em;
} }
} }
.skills {
button[disabled] {
display: none;
}
}
.effects {
font-size: 1em;
}
.player {
.game-construct {
grid-template-areas:
"left"
"right";
.left {
grid-template-columns: 1fr;
grid-template-rows: 1fr min-content;
grid-template-areas:
"skills"
"effects";
}
}
}
.opponent {
.game-construct {
grid-template-rows: 2fr min-content;
grid-template-rows: 1fr;
grid-template-areas:
"right"
"left";
.left {
grid-template-columns: min-content 1fr;
}
}
.avatar {
bottom: 0px;
}
}
} }
.instance-construct { .instance-construct {
@ -62,10 +107,86 @@
} }
} }
.opponent { aside {
.avatar { button {
bottom: 0; margin-bottom: 0.5em;
} }
} }
} }
} }
// portrait menu
@media (max-width: 600px) {
#mnml {
grid-template-columns: 1fr;
grid-template-rows: 1fr;
grid-template-areas:
"main"
}
section {
grid-template-columns: 1fr;
.list {
grid-template-columns: 1fr 1fr;
&.play {
grid-template-columns: 1fr;
}
}
}
.account {
grid-template-columns: 1fr;
div {
padding: 0;
}
}
.play-ctrl {
display: none;
}
.menu {
height: auto;
display: block;
.options {
display: grid;
grid-template-columns: repeat(2, 1fr);
button:not(:last-child) {
border: 2px solid #222;
}
button.logo {
grid-column-end: span 2;
border: none;
margin-right: 0;
margin-top: 0.5em;
background-position: center;
}
}
.team {
grid-template-columns: 1fr;
.construct {
height: 10em;
}
}
.news {
padding: 0;
}
}
section {
.list {
grid-template-columns: 1fr;
}
}
}

View File

@ -22,10 +22,6 @@
grid-gap: 0.5em 1em; grid-gap: 0.5em 1em;
align-items: center; align-items: center;
margin-bottom: 0.5em; margin-bottom: 0.5em;
button {
width: 100%;
}
} }
.vbox-btn { .vbox-btn {
@ -73,6 +69,7 @@
button { button {
height: 4em; height: 4em;
margin: 0; margin: 0;
width: 100%;
// text-transform: none; // text-transform: none;
@ -108,3 +105,62 @@
} }
} }
} }
/* VBOX */
.vbox {
align-content: space-between;
grid-area: vbox;
display: grid;
grid-template-rows: min-content min-content min-content;
grid-template-columns: 1fr min-content 1fr;
grid-template-areas:
"vbox varrow inventory"
"vbox varrow combiner";
}
.vbox-inventory {
grid-area: inventory;
}
.vbox-combiner {
grid-area: combiner;
display: flex;
flex-flow: column;
justify-content: flex-end;
}
.vbox-arrow, .vbox-arrow-mobile {
display: flex;
justify-content:center;
align-content:center;
flex-direction:column;
margin: 1em 0.25em 0 0.25em;
grid-area: varrow;
font-size: 2em;
color: @gray-hint;
}
.vbox-combiner button {
flex: 0;
}
.vbox-hdr {
display: flex;
}
.vbox-hdr h3 {
flex: 1;
}
.vbox-hdr .bits {
font-size: 2em;
line-height: 1em;
animation: bits 1s ease-out;
}
.arrow {
grid-area: arrow;
color: @gray-hint;
}

View File

@ -15,6 +15,8 @@
<link href="https://fonts.googleapis.com/css?family=Jura" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Jura" rel="stylesheet">
<link rel="stylesheet" href="assets/styles/normalize.css"> <link rel="stylesheet" href="assets/styles/normalize.css">
<link rel="stylesheet" href="assets/styles/skeleton.css"> <link rel="stylesheet" href="assets/styles/skeleton.css">
<link rel="icon" sizes="512x512" href="assets/icons/mnml.png">
<link rel="apple-touch-icon" href="assets/icons/mnml.png">
</head> </head>
</head> </head>
<body> <body>

View File

@ -1,7 +1,7 @@
{ {
"name": "mnml", "name": "MNML",
"description": "mnml pvp atbs", "description": "abstract strategy",
"short_name": "mnml", "short_name": "MNML",
"icons": [ "icons": [
{ {
"src": "./assets/icons/mnml.png", "src": "./assets/icons/mnml.png",
@ -15,7 +15,8 @@
} }
], ],
"start_url": "/index.html", "start_url": "/index.html",
"display": "fullscreen", "display": "standalone",
"orientation": "landscape",
"theme_color": "#000000", "theme_color": "#000000",
"background_color": "#000000" "background_color": "#000000"
} }

View File

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

View File

@ -160,7 +160,7 @@ function getText(resolution) {
if (type === 'Recharge') { if (type === 'Recharge') {
const { red, blue } = event; const { red, blue } = event;
if (red > 0 && blue > 0) return { text: [`+${red}R +${blue}B`, ''], css: 'purple-damage' }; if (red > 0 && blue > 0) return { text: [`+${red}R +${blue}B`, ''], css: 'rb-damage' };
if (red > 0) return { text: [`+${red}R`, ''], css: 'red-damage' }; if (red > 0) return { text: [`+${red}R`, ''], css: 'red-damage' };
if (blue > 0) return { text: [`+${blue}B`, ''], css: 'blue-damage' }; if (blue > 0) return { text: [`+${blue}B`, ''], css: 'blue-damage' };
return nullText; return nullText;

View File

@ -217,7 +217,10 @@ function Construct(props) {
return ( return (
<label onDragStart={specClick} key={i} draggable="true" onDragEnd={() => setItemUnequip([])}> <label onDragStart={ev => {
ev.dataTransfer.setData('text', '');
specClick(ev);
}} key={i} draggable="true" onDragEnd={() => setItemUnequip([])}>
<button <button
key={i} key={i}
onClick={specClick} onClick={specClick}

View File

@ -7,15 +7,26 @@ const Controls = require('./controls');
const Footer = require('./footer'); const Footer = require('./footer');
const addState = connect( const addState = connect(
state => ({ showNav: state.showNav }) ({ game, instance }) => ({ game, instance })
); );
function Mnml(args) { function Mnml(args) {
const {
game,
instance,
} = args;
const rotateClass = (game || instance) && window.innerWidth < window.innerHeight
? 'show'
: '';
return ( return (
<div id="mnml"> <div id="mnml">
<Main /> <Main />
<Controls /> <Controls />
<Footer /> <Footer />
<div id="rotate" class={rotateClass} >
</div>
</div> </div>
); );
} }

View File

@ -60,10 +60,10 @@ function Scoreboard(args) {
} = args; } = args;
const scoreText = () => { const scoreText = () => {
if (player.score === 'Zero' || player.score === 'Lose') return [<span i={0}></span>, <span i={1}></span>, <span i={2}></span>]; if (player.score === 'Zero' || player.score === 'Lose') return [<span i={0}>&#x25AB;</span>, <span i={1}>&#x25AB;</span>, <span i={2}>&#x25AB;</span>];
if (player.score === 'One') return [<span i={0}></span>, <span i={1}></span>, <span i={2}></span>]; if (player.score === 'One') return [<span i={0}>&#x25A0;</span>, <span i={1}>&#x25AB;</span>, <span i={2}>&#x25AB;</span>];
if (player.score === 'Two') return [<span i={0}></span>, <span i={1}></span>, <span i={2}></span>]; if (player.score === 'Two') return [<span i={0}>&#x25A0;</span>, <span i={1}>&#x25A0;</span>, <span i={2}>&#x25AB;</span>];
if (player.score === 'Win') return [<span i={0}></span>, <span i={1}></span>, <span i={2}></span>]; if (player.score === 'Win') return [<span i={0}>&#x25A0;</span>, <span i={1}>&#x25A0;</span>, <span i={2}>&#x25A0;</span>];
return ''; return '';
}; };

View File

@ -59,7 +59,7 @@ class TargetSvg extends Component {
if (tutorialGame) { if (tutorialGame) {
return ( return (
<div class="resolving-skill"> <div class="resolving-skill">
<h2> Select your skills, click on targets and then hit <b>ready</b>.</h2> <h2> Select your skills, click on targets and then hit <b>READY</b>.</h2>
</div> </div>
); );
} }

View File

@ -177,7 +177,7 @@ class Vbox extends preact.Component {
} }
function availableBtn(v, group, index) { function availableBtn(v, group, index) {
if (!v) return <button disabled class='empty' >&nbsp;</button>; if (!v) return <button disabled class='empty' key={(group * 10) + index} >&nbsp;</button>;
const selected = vboxSelected[0] === group && vboxSelected[1] === index; const selected = vboxSelected[0] === group && vboxSelected[1] === index;
// state not yet set in double click handler // state not yet set in double click handler
@ -215,7 +215,11 @@ class Vbox extends preact.Component {
const vboxObject = shapes[v] ? shapes[v]() : v; const vboxObject = shapes[v] ? shapes[v]() : v;
return ( return (
<label draggable='true' <label draggable='true'
onDragStart={ev => ev.dataTransfer.setData('text', '')} onDragStart={ev => {
onClick(ev);
ev.dataTransfer.setData('text', '')
}}
key={group * 10 + index}
onDragEnd={clearVboxSelected}> onDragEnd={clearVboxSelected}>
<button <button
class={classes} class={classes}
@ -275,7 +279,7 @@ class Vbox extends preact.Component {
const inventoryHighlight = vboxSelecting || itemUnequip.length; const inventoryHighlight = vboxSelecting || itemUnequip.length;
if (!v && v !== 0) { if (!v && v !== 0) {
return <button disabled={!inventoryHighlight} class={inventoryHighlight ? 'receiving' : 'empty'} >&nbsp;</button>; return <button key={i} disabled={!inventoryHighlight} class={inventoryHighlight ? 'receiving' : 'empty'} >&nbsp;</button>;
} }
const combinerItems = combiner.map(j => vbox.bound[j]); const combinerItems = combiner.map(j => vbox.bound[j]);
@ -293,29 +297,30 @@ class Vbox extends preact.Component {
} return false; } return false;
}) ? 'combo-border' : ''; }) ? 'combo-border' : '';
function onClick(e) { function onClick(type) {
if (vboxSelecting) clearVboxSelected(); if (vboxSelecting) clearVboxSelected();
if (reclaiming) return sendVboxReclaim(i); if (reclaiming) return sendVboxReclaim(i);
const combinerContainsIndex = combiner.indexOf(i) > -1;
// 4 things selected // 4 things selected
if (combiner.length > 2) { if (combiner.length > 2 && !combinerContainsIndex) {
setInfo(vbox.bound[i]); setInfo(vbox.bound[i]);
return combinerChange([i]); return combinerChange([i]);
} }
// removing // removing
const combinerIndex = combiner.indexOf(i); if (combinerContainsIndex) {
if (combinerIndex > -1) { if (type === 'click') {
return combinerChange(without(combiner, i));
}
return true; return true;
// return combinerChange(without(combiner, i));
} }
combiner.push(i); if (!comboHighlight && !combinerContainsIndex) {
if (!comboHighlight) {
setInfo(vbox.bound[i]); setInfo(vbox.bound[i]);
return combinerChange([i]); return combinerChange([i]);
} }
combiner.push(i);
return combinerChange(combiner); return combinerChange(combiner);
} }
@ -327,16 +332,17 @@ class Vbox extends preact.Component {
return ( return (
<label <label
key={i}
draggable='true' draggable='true'
onDragStart={ev => ev.dataTransfer.setData('text', '')} onDragStart={ev => {
onDragEnd={() => { onClick('drag');
if (combiner.length === 1) combinerChange([]); ev.dataTransfer.setData('text', '');
}}> }}>
<button <button
class={classes} class={classes}
onMouseOver={e => vboxHover(e, v)} onMouseOver={e => vboxHover(e, v)}
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
onMouseDown={onClick}> onMouseDown={() => onClick('click')}>
{invObject} {invObject}
</button> </button>
</label> </label>

View File

@ -12,7 +12,7 @@ const { tutorialVbox } = require('./tutorial.utils');
function registerEvents(store) { function registerEvents(store) {
function notify(msg) { function notify(msg) {
if (Notification && Notification.permission === 'granted') { if (window.Notification && window.Notification.permission === 'granted') {
const n = new Notification('MNML', { const n = new Notification('MNML', {
body: msg, body: msg,
tag: 'MNML', tag: 'MNML',
@ -134,12 +134,12 @@ function registerEvents(store) {
} }
function setAccount(account) { function setAccount(account) {
if (account) { if (account && process.env.NODE_ENV !== 'development') {
LogRocket.init('yh0dy3/mnml'); LogRocket.init('yh0dy3/mnml');
LogRocket.identify(account.id, account); LogRocket.identify(account.id, account);
if (Notification) { if (window.Notification) {
Notification.requestPermission(); window.Notification.requestPermission();
} }
} }
@ -224,7 +224,9 @@ function registerEvents(store) {
if (v.phase === 'Finished') { if (v.phase === 'Finished') {
ws.sendAccountInstances(); ws.sendAccountInstances();
} }
if (localStorage.getItem('tutorial-complete')) {
// instance.mobile.less hides info at @media 1000
if (localStorage.getItem('tutorial-complete') || window.innerWidth <= 1100) {
store.dispatch(actions.setTutorial(null)); store.dispatch(actions.setTutorial(null));
} else if (v.time_control === 'Practice' && v.rounds.length === 1 && tutorial) { } else if (v.time_control === 'Practice' && v.rounds.length === 1 && tutorial) {
tutorialVbox(player, store, tutorial); tutorialVbox(player, store, tutorial);

View File

@ -199,13 +199,16 @@ function tutorialStage(tutorial, ws, clearTutorial, instance) {
} }
if (tutorial === 8) { if (tutorial === 8) {
if (window.innerWidth < 1000) {
return exit();
}
return ( return (
<div> <div>
<h2>Tutorial</h2> <h2>Tutorial</h2>
<p>You've completed the tutorial! Try to create more skill and spec combinations. </p> <p>You've completed the tutorial! Try to create more skill and spec combinations. </p>
<p>You can unequip skills and specs back into the inventory by double clicking. <br /> <p>You can unequip skills and specs back into the inventory by double clicking. <br />
Reclaim can be used to refund the cost of items in your inventory. </p> Reclaim can be used to refund the cost of items in your inventory. </p>
<p>Click the <b>EXIT TUTORIAL</b> button to replace this section with more information.</p>
</div> </div>
); );
} }
@ -213,10 +216,11 @@ function tutorialStage(tutorial, ws, clearTutorial, instance) {
}; };
const classes = tutorial === 8 ? 'focus' : ''; const classes = tutorial === 8 ? 'focus' : '';
const text = tutorial === 8 ? 'Continue' : 'Close Tutorial'
const exitTutorial = <button const exitTutorial = <button
class={classes} class={classes}
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
onMouseDown={exit}> Exit Tutorial </button>; onMouseDown={exit}> {text} </button>;
return ( return (
<div class='tutorial'> <div class='tutorial'>

View File

@ -264,7 +264,7 @@ function effectInfo(i) {
} }
switch (i.effect) { switch (i.effect) {
case 'Amplify': return `Increases construct RedPower and BluePower by ${i.meta[1] - 100}%`; case 'Amplify': return `Increases construct RedPower BluePower GreenPower by ${i.meta[1] - 100}%`;
case 'Banish': return 'Banished construct cannot cast or take damage'; case 'Banish': return 'Banished construct cannot cast or take damage';
case 'Block': return `Reduces construct red damage and blue damage taken by ${100 - i.meta[1]}%`; case 'Block': return `Reduces construct red damage and blue damage taken by ${100 - i.meta[1]}%`;
case 'Buff': return `Increases construct RedPower BluePower SpeedStat by ${i.meta[1] - 100}%`; case 'Buff': return `Increases construct RedPower BluePower SpeedStat by ${i.meta[1] - 100}%`;

View File

@ -15,6 +15,12 @@ map $http_upgrade $connection_upgrade {
server { server {
server_name mnml.gg; server_name mnml.gg;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/atom+xml image/svg+xml;
location / { location / {
root /var/lib/mnml/public/current; root /var/lib/mnml/public/current;
index index.html; index index.html;

View File

@ -15,8 +15,14 @@ map $http_upgrade $connection_upgrade {
server { server {
server_name sixtysix.pro; server_name sixtysix.pro;
auth_basic "who dis"; gzip on;
auth_basic_user_file /etc/mnml/htpasswd.users; gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/atom+xml image/svg+xml;
# auth_basic "who dis";
# auth_basic_user_file /etc/mnml/htpasswd.users;
location / { location / {
root /var/lib/mnml/public/current; root /var/lib/mnml/public/current;

View File

@ -1,6 +1,6 @@
{ {
"name": "mnml-ops", "name": "mnml-ops",
"version": "1.7.1", "version": "1.8.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.7.1" version = "1.8.0"
authors = ["ntr <ntr@smokestack.io>"] authors = ["ntr <ntr@smokestack.io>"]
[dependencies] [dependencies]

View File

@ -220,11 +220,11 @@ impl Construct {
account: id, account: id,
img: Uuid::new_v4(), img: Uuid::new_v4(),
red_power: ConstructStat { base: 320, value: 320, max: 320, stat: Stat::RedPower }, red_power: ConstructStat { base: 320, value: 320, max: 320, stat: Stat::RedPower },
red_life: ConstructStat { base: 0, value: 0, max: 0, stat: Stat::RedLife }, red_life: ConstructStat { base: 125, value: 125, max: 125, stat: Stat::RedLife },
blue_power: ConstructStat { base: 320, value: 320, max: 320, stat: Stat::BluePower }, blue_power: ConstructStat { base: 320, value: 320, max: 320, stat: Stat::BluePower },
blue_life: ConstructStat { base: 0, value: 0, max: 0, stat: Stat::BlueLife }, blue_life: ConstructStat { base: 125, value: 125, max: 125, stat: Stat::BlueLife },
green_power: ConstructStat { base: 300, value: 300, max: 300, stat: Stat::GreenPower }, green_power: ConstructStat { base: 300, value: 300, max: 300, stat: Stat::GreenPower },
green_life: ConstructStat { base: 950, value: 950, max: 950, stat: Stat::GreenLife }, green_life: ConstructStat { base: 800, value: 800, max: 800, stat: Stat::GreenLife },
speed: ConstructStat { base: 100, value: 100, max: 100, stat: Stat::Speed }, speed: ConstructStat { base: 100, value: 100, max: 100, stat: Stat::Speed },
// evasion: ConstructStat { base: 0, value: 0, max: 0, stat: Stat::Evasion }, // evasion: ConstructStat { base: 0, value: 0, max: 0, stat: Stat::Evasion },
skills: vec![], skills: vec![],

View File

@ -105,7 +105,7 @@ impl Effect {
Effect::Absorption => vec![Stat::RedPower, Stat::BluePower], Effect::Absorption => vec![Stat::RedPower, Stat::BluePower],
Effect::Amplify => vec![Stat::RedPower, Stat::BluePower], Effect::Amplify => vec![Stat::GreenPower, Stat::RedPower, Stat::BluePower],
Effect::Curse => vec![Stat::RedDamageTaken, Stat::BlueDamageTaken], Effect::Curse => vec![Stat::RedDamageTaken, Stat::BlueDamageTaken],
Effect::Hybrid => vec![Stat::GreenPower], Effect::Hybrid => vec![Stat::GreenPower],

View File

@ -1229,7 +1229,7 @@ mod tests {
assert!(game.player_by_id(x_player.id).unwrap().constructs[0].is_stunned() == false); assert!(game.player_by_id(x_player.id).unwrap().constructs[0].is_stunned() == false);
// riposte // riposte
assert_eq!(game.player_by_id(y_player.id).unwrap().constructs[0].green_life(), ( assert_eq!(game.player_by_id(y_player.id).unwrap().constructs[0].green_life(), (
y_construct.green_life() - x_construct.red_power().pct(Skill::CounterAttack.multiplier()))); y_construct.green_life() + y_construct.red_life() - x_construct.red_power().pct(Skill::CounterAttack.multiplier())));
} }
#[test] #[test]

View File

@ -302,7 +302,7 @@ pub fn smile(id: Uuid) -> Result<Uuid, Error> {
// 100W 25H // 100W 25H
let mouths = [ let mouths = [
("M50,100 L150,100", 1), // _ ("M50,100 L150,100", 1), // _
("M50,75 L150,75 L150,100 L50,100 L50,75", 1), // box ("M50,75 L150,75 L125,100 L75,100 L50,75", 1), // D
("M50,75 L75,100 L100,75 L125,100 L150,75", 1), // w ("M50,75 L75,100 L100,75 L125,100 L150,75", 1), // w
("M50,75 L75,75 L75,87.5 M75,75 L125,75 L125,87.5 M125,75 L150,75", 1), // vamp ("M50,75 L75,75 L75,87.5 M75,75 L125,75 L125,87.5 M125,75 L150,75", 1), // vamp
("M50,75 L150,75 M50,75 L50,87.5 M75,75 L75,87.5 M100,75 L100,87.5 M125,75 L125,87.5 M150,75 L150,87.5", 1), // mm ("M50,75 L150,75 M50,75 L50,87.5 M75,75 L75,87.5 M100,75 L100,87.5 M125,75 L125,87.5 M150,75 L150,87.5", 1), // mm

View File

@ -712,14 +712,14 @@ impl Item {
// Skills <- need to move effect mulltipliers into skills // Skills <- need to move effect mulltipliers into skills
Item::Amplify| Item::Amplify|
Item::AmplifyPlus | Item::AmplifyPlus |
Item::AmplifyPlusPlus => format!("Increase RedPower and BluePower by {:?}%. Lasts {:?}T.", Item::AmplifyPlusPlus => format!("Increase RedPower BluePower GreenPower by {:?}%. Lasts {:?}T.",
self.into_skill().unwrap().effect()[0].get_multiplier() - 100, self.into_skill().unwrap().effect()[0].get_multiplier() - 100,
self.into_skill().unwrap().effect()[0].get_duration()), self.into_skill().unwrap().effect()[0].get_duration()),
Item::Banish| Item::Banish|
Item::BanishPlus | Item::BanishPlus |
Item::BanishPlusPlus => format!("Banish target for {:?}T. Item::BanishPlusPlus => format!("Banish target for {:?}T.
Deal blue damage and red damage equal to {:?}% target red and blue life. Deal {:?}% target RedLife and BlueLife as red and blue damage respectively.
Banished constructs are immune to all skills and effects.", Banished constructs are immune to all skills and effects.",
self.into_skill().unwrap().effect()[0].get_duration(), self.into_skill().unwrap().effect()[0].get_duration(),
self.into_skill().unwrap().multiplier()), self.into_skill().unwrap().multiplier()),
@ -773,11 +773,12 @@ impl Item {
Item::Absorb| Item::Absorb|
Item::AbsorbPlus | Item::AbsorbPlus |
Item::AbsorbPlusPlus => format!( Item::AbsorbPlusPlus => format!(
"Gain Absorb for {:?}T. When attacked with Absorb you gain Absorption. "Gain Absorb for {:?}T. Taking damage replaces Absorb with Absorption.
Absorption increases RedPower and BluePower based on Damage taken. Absorption increases RedPower and BluePower based on damage taken.
Absorption lasts {:?}T.", Absorption lasts {:?}T. Recharges BlueLife based on {:?}% BluePower.",
self.into_skill().unwrap().effect()[0].get_duration(), self.into_skill().unwrap().effect()[0].get_duration(),
self.into_skill().unwrap().effect()[0].get_skill().unwrap().effect()[0].get_duration()), self.into_skill().unwrap().effect()[0].get_skill().unwrap().effect()[0].get_duration(),
self.into_skill().unwrap().multiplier()),
Item::Haste| Item::Haste|
Item::HastePlus | Item::HastePlus |

View File

@ -89,10 +89,15 @@ pub fn resolve(skill: Skill, source: &mut Construct, target: &mut Construct, mut
if source.affected(Effect::Haste) { if source.affected(Effect::Haste) {
match skill { match skill {
Skill::Attack | Skill::Slay |
Skill::Slay| Skill::SlayPlus |
Skill::Chaos| Skill::SlayPlusPlus |
Skill::Strike=> { Skill::Chaos |
Skill::ChaosPlus |
Skill::ChaosPlusPlus |
Skill::Strike |
Skill::StrikePlus |
Skill::StrikePlusPlus => {
let amount = source.speed().pct(Skill::HasteStrike.multiplier()); let amount = source.speed().pct(Skill::HasteStrike.multiplier());
target.deal_red_damage(Skill::HasteStrike, amount) target.deal_red_damage(Skill::HasteStrike, amount)
.into_iter() .into_iter()
@ -105,8 +110,14 @@ pub fn resolve(skill: Skill, source: &mut Construct, target: &mut Construct, mut
if source.affected(Effect::Hybrid) { if source.affected(Effect::Hybrid) {
match skill { match skill {
Skill::Blast| Skill::Blast|
Skill::Chaos| Skill::BlastPlus |
Skill::Siphon=> { Skill::BlastPlusPlus |
Skill::Chaos |
Skill::ChaosPlus |
Skill::ChaosPlusPlus |
Skill::Siphon |
Skill::SiphonPlus |
Skill::SiphonPlusPlus => {
let amount = source.green_power().pct(Skill::HybridBlast.multiplier()); let amount = source.green_power().pct(Skill::HybridBlast.multiplier());
target.deal_blue_damage(Skill::HybridBlast, amount) target.deal_blue_damage(Skill::HybridBlast, amount)
.into_iter() .into_iter()
@ -293,7 +304,7 @@ fn post_resolve(_skill: Skill, game: &mut Game, mut resolutions: Resolutions) ->
let mut target = game.construct_by_id(target.id).unwrap().clone(); let mut target = game.construct_by_id(target.id).unwrap().clone();
match event { match event {
Event::Damage { amount, skill, mitigation: _, colour: c } => { Event::Damage { amount, skill, mitigation, colour: c } => {
if target.affected(Effect::Electric) && !skill.is_tick() { if target.affected(Effect::Electric) && !skill.is_tick() {
let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter()
.find(|e| e.effect == Effect::Electric).unwrap().clone(); .find(|e| e.effect == Effect::Electric).unwrap().clone();
@ -320,7 +331,7 @@ fn post_resolve(_skill: Skill, game: &mut Game, mut resolutions: Resolutions) ->
.find(|e| e.effect == Effect::Absorb).unwrap().clone(); .find(|e| e.effect == Effect::Absorb).unwrap().clone();
match meta { match meta {
Some(EffectMeta::Skill(s)) => { Some(EffectMeta::Skill(s)) => {
resolutions = absorption(&mut source, &mut target, resolutions, skill, amount, s); resolutions = absorption(&mut source, &mut target, resolutions, skill, amount + mitigation, s);
}, },
_ => panic!("no absorb skill"), _ => panic!("no absorb skill"),
}; };
@ -841,6 +852,10 @@ impl Skill {
Skill::HasteStrike => 60, Skill::HasteStrike => 60,
Skill::Absorb=> 95,
Skill::AbsorbPlus => 120,
Skill::AbsorbPlusPlus => 155,
Skill::Intercept => 80, Skill::Intercept => 80,
Skill::InterceptPlus => 110, Skill::InterceptPlus => 110,
Skill::InterceptPlusPlus => 150, Skill::InterceptPlusPlus => 150,
@ -918,11 +933,11 @@ impl Skill {
Skill::HastePlusPlus => vec![ConstructEffect {effect: Effect::Haste, duration: 5, Skill::HastePlusPlus => vec![ConstructEffect {effect: Effect::Haste, duration: 5,
meta: Some(EffectMeta::Multiplier(225)), tick: None }], meta: Some(EffectMeta::Multiplier(225)), tick: None }],
Skill::Absorb => vec![ConstructEffect {effect: Effect::Absorb, duration: 2, Skill::Absorb => vec![ConstructEffect {effect: Effect::Absorb, duration: 1,
meta: Some(EffectMeta::Skill(Skill::Absorption)), tick: None}], meta: Some(EffectMeta::Skill(Skill::Absorption)), tick: None}],
Skill::AbsorbPlus => vec![ConstructEffect {effect: Effect::Absorb, duration: 3, Skill::AbsorbPlus => vec![ConstructEffect {effect: Effect::Absorb, duration: 1,
meta: Some(EffectMeta::Skill(Skill::AbsorptionPlus)), tick: None}], meta: Some(EffectMeta::Skill(Skill::AbsorptionPlus)), tick: None}],
Skill::AbsorbPlusPlus => vec![ConstructEffect {effect: Effect::Absorb, duration: 4, Skill::AbsorbPlusPlus => vec![ConstructEffect {effect: Effect::Absorb, duration: 1,
meta: Some(EffectMeta::Skill(Skill::AbsorptionPlusPlus)), tick: None}], meta: Some(EffectMeta::Skill(Skill::AbsorptionPlusPlus)), tick: None}],
Skill::Absorption => vec![ConstructEffect {effect: Effect::Absorption, duration: 3, meta: None, tick: None}], Skill::Absorption => vec![ConstructEffect {effect: Effect::Absorption, duration: 3, meta: None, tick: None}],
@ -1000,9 +1015,9 @@ impl Skill {
meta: Some(EffectMeta::Skill(Skill::BashPlusPlus)), tick: None}], meta: Some(EffectMeta::Skill(Skill::BashPlusPlus)), tick: None}],
Skill::Stun => vec![ConstructEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None}], Skill::Stun => vec![ConstructEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None}],
Skill::Intercept => vec![ConstructEffect {effect: Effect::Intercept, duration: 2, meta: None, tick: None}], Skill::Intercept => vec![ConstructEffect {effect: Effect::Intercept, duration: 1, meta: None, tick: None}],
Skill::InterceptPlus => vec![ConstructEffect {effect: Effect::Intercept, duration: 3, meta: None, tick: None}], Skill::InterceptPlus => vec![ConstructEffect {effect: Effect::Intercept, duration: 1, meta: None, tick: None}],
Skill::InterceptPlusPlus => vec![ConstructEffect {effect: Effect::Intercept, duration: 4, meta: None, tick: None}], Skill::InterceptPlusPlus => vec![ConstructEffect {effect: Effect::Intercept, duration: 1, meta: None, tick: None}],
Skill::Triage => vec![ConstructEffect {effect: Effect::Triage, duration: 2, Skill::Triage => vec![ConstructEffect {effect: Effect::Triage, duration: 2,
meta: Some(EffectMeta::Skill(Skill::TriageTick)), tick: None}], meta: Some(EffectMeta::Skill(Skill::TriageTick)), tick: None}],
@ -1072,11 +1087,11 @@ impl Skill {
Skill::InvertPlus | Skill::InvertPlus |
Skill::InvertPlusPlus => Some(2), Skill::InvertPlusPlus => Some(2),
Skill::Decay | Skill::Decay => None, // dot
Skill::DecayPlus | Skill::DecayPlus => None,
Skill::DecayPlusPlus => Some(1), Skill::DecayPlusPlus => None,
Skill::Siphon | Skill::Siphon|
Skill::SiphonPlus | Skill::SiphonPlus |
Skill::SiphonPlusPlus => None, Skill::SiphonPlusPlus => None,
@ -1102,7 +1117,7 @@ impl Skill {
Skill::Banish | Skill::Banish |
Skill::BanishPlus | Skill::BanishPlus |
Skill::BanishPlusPlus => Some(2), Skill::BanishPlusPlus => Some(1),
Skill::Haste | Skill::Haste |
Skill::HastePlus | Skill::HastePlus |
@ -1132,9 +1147,9 @@ impl Skill {
Skill::SustainPlus | Skill::SustainPlus |
Skill::SustainPlusPlus => Some(1), Skill::SustainPlusPlus => Some(1),
Skill::Intercept | Skill::Intercept => Some(1),
Skill::InterceptPlus | Skill::InterceptPlus => Some(1),
Skill::InterceptPlusPlus => Some(2), Skill::InterceptPlusPlus => Some(1),
Skill::Electrify | Skill::Electrify |
Skill::ElectrifyPlus | Skill::ElectrifyPlus |
@ -1689,6 +1704,19 @@ fn ruin(source: &mut Construct, target: &mut Construct, mut results: Resolutions
fn absorb(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { fn absorb(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0])));
let blue_amount = source.blue_power().pct(skill.multiplier());
let e = target.recharge(skill, 0, blue_amount);
let stages = match e {
Event::Recharge { red, blue, skill: _ } => {
if red > 0 || blue > 0 { EventStages::PostOnly }
else { EventStages::NoStages }
}
_ => {
warn!("no recharge event found {:?}", e);
EventStages::NoStages
}
};
results.push(Resolution::new(source, target).event(e).stages(stages));
return results;; return results;;
} }
@ -1808,13 +1836,19 @@ fn link(source: &mut Construct, target: &mut Construct, mut results: Resolutions
None => 0 None => 0
}; };
target.deal_blue_damage(skill, swap) let link_events = target.deal_blue_damage(skill, swap);
.into_iter() for e in link_events {
.for_each(|e| results.push(Resolution::new(source, target).event(e))); match e {
Event::Damage { amount, mitigation: _, colour: _, skill: _ } => {
source.deal_green_damage(skill, swap) results.push(Resolution::new(source, target).event(e));
.into_iter() let heal = source.deal_green_damage(skill, amount);
.for_each(|e| results.push(Resolution::new(source, source).event(e).stages(EventStages::PostOnly))); for h in heal {
results.push(Resolution::new(source, source).event(h).stages(EventStages::PostOnly));
};
},
_ => results.push(Resolution::new(source, target).event(e)),
}
}
results.push(Resolution::new(source, source) results.push(Resolution::new(source, source)
.event(source.add_effect(skill, skill.effect()[0])).stages(EventStages::PostOnly)); .event(source.add_effect(skill, skill.effect()[0])).stages(EventStages::PostOnly));
@ -2090,6 +2124,7 @@ mod tests {
x.blue_power.force(256); x.blue_power.force(256);
x.green_power.force(220); x.green_power.force(220);
x.green_life.force(1024); x.green_life.force(1024);
y.blue_life.force(0);
x.green_life.reduce(512); x.green_life.reduce(512);
let mut results = resolve(Skill::Siphon, &mut x, &mut y, vec![]); let mut results = resolve(Skill::Siphon, &mut x, &mut y, vec![]);