Merge branch 'new-vbox-combiner' into new-vbox-resize

This commit is contained in:
ntr 2019-11-24 18:20:33 +11:00
commit 5f4659b6db
18 changed files with 519 additions and 520 deletions

View File

@ -5,6 +5,7 @@
@white: #f5f5f5; // whitesmoke @white: #f5f5f5; // whitesmoke
@purple: #9355b5; // 6lack - that far cover @purple: #9355b5; // 6lack - that far cover
@yellow: #ffa100; @yellow: #ffa100;
@silver: #c0c0c0;
@black: black; @black: black;
@gray: #222; @gray: #222;

View File

@ -27,6 +27,7 @@
position: absolute; position: absolute;
bottom: 0; bottom: 0;
margin-bottom: 0.5em;
height: 50%; height: 50%;
.avatar { .avatar {
@ -232,8 +233,8 @@
#targeting, .resolving-skill { #targeting, .resolving-skill {
position: absolute; position: absolute;
top: 35%; top: calc(35% + 0.5em); // calc for 0.5em top gap
height: 15%; height: calc(15% - 1em); // calc for 0.5em + 0.5em top / bottom gap
width: calc(90% - 1.25em); width: calc(90% - 1.25em);
z-index: 2; z-index: 2;
span { span {
@ -273,7 +274,7 @@
/* some stupid bug in chrome makes it fill the entire screen */ /* some stupid bug in chrome makes it fill the entire screen */
@media screen and (-webkit-min-device-pixel-ratio:0) { @media screen and (-webkit-min-device-pixel-ratio:0) {
#targeting { #targeting {
max-height: 10em; // max-height: 10em;
} }
} }

View File

@ -5,114 +5,17 @@
.instance { .instance {
overflow: hidden; overflow: hidden;
display: grid; display: grid;
grid-template-columns: 1fr minmax(min-content, 1fr);
grid-template-rows: min-content 1fr; grid-template-rows: min-content 1fr;
grid-template-areas: grid-template-areas:
"vbox info" "vbox"
"constructs constructs"; "constructs";
.info {
h2 {
text-transform: uppercase;
}
svg {
display: inline;
height: 1em;
}
figure {
display: inline;
height: 0.5em;
svg {
margin-right: 0.5em;
}
}
figcaption {
font-size: 1em;
display: inline-block;
vertical-align: middle;
}
margin-left: 1em;
grid-area: info;
display: grid;
grid-template-columns: 1fr min-content;
grid-template-areas:
"item combos";
.info-item {
grid-area: item;
}
.combos {
display: grid;
grid-area: combos;
margin-left: 0.5em;
grid-template-rows: min-content min-content;
grid-template-areas:
"comboHeader"
"comboList";
.combo-header {
text-align: center;
}
.combo-list {
display: grid;
grid-template-rows: min-content min-content min-content;
grid-template-columns: min-content min-content;
grid-gap: 0.5em;
margin-top: 0.5em;
width: 15.5em;
.table-button {
display: grid;
text-align: center;
align-content: center;
grid-template-areas:
"item"
"ingr";
cursor: pointer;
&:hover {
color: whitesmoke;
background-color: @gray;
}
.item {
border-top: 2px solid #222;
border-bottom: 2px solid #222;
flex: 1;
grid-area: item;
font-weight: bold;
div {
width: 5em;
}
}
div {
border-left: 2px solid #222;
border-right: 2px solid #222;
height: 1.75em;
width: 7.5em;
svg {
vertical-align: middle;
}
&:last-child {
border-bottom: 2px solid #222;
}
}
}
}
}
}
.constructs { .constructs {
grid-area: constructs; grid-area: constructs;
} }
} }
@keyframes action { @keyframes action {
0% { 0% {
color: palegoldenrod; color: palegoldenrod;
@ -152,7 +55,7 @@
button { button {
&.highlight { &.highlight {
color: black; color: black;
background: @white; background: @silver;
// border: 1px solid @white; (this bangs around the vbox) // border: 1px solid @white; (this bangs around the vbox)
// overwrite the classes on white svg elements // overwrite the classes on white svg elements

View File

@ -1,7 +1,6 @@
@media (max-width: 800px) { @media (max-width: 800px) {
body { body {
overflow-y: initial; overflow-y: initial;
} }
#mnml { #mnml {
@ -95,6 +94,10 @@
} }
} }
} }
.avatar {
opacity: 0.1;
}
} }
.game { .game {
@ -221,8 +224,8 @@
} }
// portrait menu // portrait menu or small size vertical in landscape
@media (max-width: 500px) { @media (max-width: 550px) {
#mnml { #mnml {
grid-template-columns: 1fr; grid-template-columns: 1fr;
grid-template-rows: 1fr; grid-template-rows: 1fr;
@ -289,6 +292,14 @@
} }
} }
.stats {
font-size: 6pt;
}
.skill-description {
font-size: 6pt;
}
section { section {
.list { .list {
grid-template-columns: 1fr; grid-template-columns: 1fr;

View File

@ -3,10 +3,10 @@
grid-area: vbox; grid-area: vbox;
display: grid; display: grid;
grid-template-rows: 3fr 2fr; grid-template-rows: 3fr 2fr;
grid-template-columns: 1fr 4fr 1fr; grid-template-columns: 1fr 4fr 6fr;
grid-template-areas: grid-template-areas:
"store-hdr store combiner" "store-hdr store info"
"stash-hdr stash combiner"; "stash-hdr stash info";
margin-bottom: 1em; margin-bottom: 1em;
line-height: 0; line-height: 0;
@ -17,8 +17,8 @@
.store { .store {
grid-area: store; grid-area: store;
border-right: 0.1em solid @gray; border-right: 0.15em solid @gray;
border-top: 0.1em solid @gray; border-top: 0.15em solid @gray;
} }
.store-hdr { .store-hdr {
@ -27,17 +27,16 @@
flex-flow: column; flex-flow: column;
text-align: center; text-align: center;
border-left: 0.1em solid @gray; border-left: 0.15em solid @gray;
border-top: 0.1em solid @gray; border-top: 0.15em solid @gray;
h1 { h1 {
margin-bottom: 0; margin-bottom: 0.5em;
} }
button { button {
margin: 1em 0 0 0;
line-height: 1.6; line-height: 1.6;
letter-spacing: 0.1em; letter-spacing: 0.15em;
background-color: #421010; background-color: #421010;
&:hover { &:hover {
background-color: @red; background-color: @red;
@ -46,38 +45,6 @@
} }
} }
.combiner {
grid-area: combiner;
display: flex;
flex-direction: column;
line-height: 1.3;
padding: 0;
.vbox-padding {
margin-right: 0em;
margin-bottom: 0em;
}
button {
line-height: 1.3;
font-size: 1.25em;
letter-spacing: 0.1em;
background-color: @yellow;
color: black;
border-radius: 0;
border: 0;
&[disabled] {
border: 0.1em solid @gray;
border-left: 0;
color: @gray-exists;
};
transition-property: 0;
}
}
.stash { .stash {
grid-area: stash; grid-area: stash;
display: grid; display: grid;
@ -85,37 +52,26 @@
grid-gap: 0.5em 1em; grid-gap: 0.5em 1em;
align-items: center; align-items: center;
border: 0.1em solid @gray; border: 0.15em solid @gray;
border-left: 0; border-left: 0;
} }
.stash-hdr { .stash-hdr {
grid-area: stash-hdr;
display: flex; display: flex;
flex-flow: column; flex-flow: column;
justify-content: center;
grid-area: stash-hdr;
text-align: center; text-align: center;
border: 0.1em solid @gray; border: 0.15em solid @gray;
border-right: 0; border-right: 0;
h3 { h2 {
margin-bottom: 2.5em; margin-bottom: 0.5em;
} }
}
.refund {
padding: 0.5em 0.5em 0 0;
button { button {
line-height: 1.4; line-height: 1.6;
letter-spacing: 0.1em; letter-spacing: 0.15em;
color: black;
background-color: @yellow;
}
.vbox-padding {
padding-left: 0;
} }
} }
@ -178,14 +134,17 @@
width: 100%; width: 100%;
// text-transform: none; // text-transform: none;
&.empty { &.empty {
border-style: dashed; border-style: dashed;
} }
&.fade {
opacity: 0.4;
}
&.highlight { &.highlight {
color: black; color: black;
background: @white; background: @silver;
// border: 1px solid @white; (this bangs around the vbox) // border: 1px solid @white; (this bangs around the vbox)
// overwrite the classes on white svg elements // overwrite the classes on white svg elements
@ -210,6 +169,127 @@
line-height: initial; line-height: initial;
} }
} }
.info {
line-height: 1.6;
h2 {
text-transform: uppercase;
}
svg {
display: inline;
height: 1em;
}
figure {
display: inline;
height: 0.5em;
svg {
margin-right: 0.5em;
}
}
figcaption {
font-size: 1em;
display: inline-block;
vertical-align: middle;
}
margin-left: 1em;
grid-area: info;
display: grid;
grid-template-columns: 1fr min-content;
grid-template-areas:
"details combos";
.info-details {
grid-area: details;
display: grid;
grid-template-rows: 1fr min-content;
grid-template-areas:
"item"
"combiner";
.combiner {
grid-area: combiner;
display: flex;
width: 60%;
button {
margin-top: 1em;
line-height: 1.3;
font-size: 1.25em;
letter-spacing: 0.1em;
&:hover {
border: 2px solid @gray-hover;
}
}
}
.info-item {
grid-area: item;
}
}
.combos {
display: grid;
grid-area: combos;
margin-left: 0.5em;
grid-template-rows: min-content min-content;
grid-template-areas:
"comboHeader"
"comboList";
.combo-header {
text-align: center;
}
.combo-list {
display: grid;
grid-template-rows: min-content min-content min-content;
grid-template-columns: min-content min-content;
grid-gap: 0.5em;
margin-top: 0.5em;
width: 15.5em;
.table-button {
display: grid;
text-align: center;
align-content: center;
grid-template-areas:
"item"
"ingr";
cursor: pointer;
&:hover {
color: whitesmoke;
background-color: @gray;
}
.item {
border-top: 2px solid #222;
border-bottom: 2px solid #222;
flex: 1;
grid-area: item;
font-weight: bold;
div {
width: 5em;
}
}
div {
border-left: 2px solid #222;
border-right: 2px solid #222;
height: 1.75em;
width: 7.5em;
svg {
vertical-align: middle;
}
&:last-child {
border-bottom: 2px solid #222;
}
}
}
}
}
}
} }
@media (min-width: 2000px) { @media (min-width: 2000px) {

View File

@ -1,191 +0,0 @@
const preact = require('preact');
const reactStringReplace = require('react-string-replace');
const specThresholds = require('./info.thresholds');
const { INFO } = require('./../constants');
const { convertItem, removeTier } = require('../utils');
const { tutorialStage } = require('../tutorial.utils');
const shapes = require('./shapes');
class InfoComponent extends preact.Component {
shouldComponentUpdate(newProps, newState) {
if (newProps.tutorial !== this.props.tutorial) return true;
// We don't care about info during tutorial
if (newProps.tutorial && this.props.instance.time_control === 'Practice'
&& this.props.instance.rounds.length === 1) return false;
if (newProps.info !== this.props.info) return true;
if (newState.comboItem !== this.state.comboItem) return true;
return false;
}
componentDidUpdate(prevProps) {
// Catch case where mouse events don't properly clear state and info changed
if (prevProps.info !== this.props.info && this.state.comboItem) this.setState({ comboItem: null });
}
render(args) {
const {
// Variables that will change
info,
tutorial,
// Static
player, // Only used for colour calcs which will be update if info changes
ws,
itemInfo,
instance, // Only used for instance id
// functions
setInfo,
setTutorialNull,
} = args;
const { comboItem } = this.state;
function Info() {
if (tutorial) {
const tutorialStageInfo = tutorialStage(tutorial, ws, setTutorialNull, instance);
if (tutorialStageInfo) return tutorialStageInfo;
}
if (!info) return false;
if (info.includes('constructName')) {
return (
<div class='info-item'>
<h2> {info.replace('constructName ', '')} </h2>
<p> This is the name of your construct. <br />
Names are randomly generated and are purely cosmetic. <br />
You can change change your construct name in the <b>RESHAPE</b> tab outside of games.
</p>
</div>
);
}
if (info.includes('constructAvatar')) {
return (
<div class='info-item'>
<h2> {info.replace('constructAvatar ', '')} </h2>
<p> This is your construct avatar. <br />
Avatars are randomly generated and are purely cosmetic. <br />
You can change your construct avatar in the <b>RESHAPE</b> tab outside of games.
</p>
</div>
);
}
const fullInfo = comboItem
? itemInfo.items.find(i => i.item === comboItem) || INFO[comboItem]
: itemInfo.items.find(i => i.item === info) || INFO[info];
if (!fullInfo) return false;
const isSkill = fullInfo.skill;
const isSpec = fullInfo.spec;
const itemDescription = () => {
const regEx = /(RedPower|BluePower|GreenPower|RedLife|BlueLife|GreenLife|SpeedStat|LIFE|SPEED|POWER)/;
const infoDescription = reactStringReplace(fullInfo.description, regEx, m => shapes[m]());
return <div>{reactStringReplace(infoDescription, '\n', () => <br />)}</div>;
};
if (isSkill || isSpec) {
let infoName = fullInfo.item;
while (infoName.includes('Plus')) infoName = infoName.replace('Plus', '+');
const itemSource = itemInfo.combos.filter(c => c.item === removeTier(fullInfo.item));
let itemSourceInfo = itemSource.length && !isSpec
? `${itemSource[0].components[0]} ${itemSource[0].components[1]} ${itemSource[0].components[2]}`
: false;
let header = null;
if (!itemSource.length) header = isSkill ? <h3> SKILL </h3> : <h3> SPEC </h3>;
if (itemSourceInfo) {
while (itemSourceInfo.includes('Plus')) itemSourceInfo = itemSourceInfo.replace('Plus', '+');
const itemRegEx = /(Red|Blue|Green)/;
itemSourceInfo = reactStringReplace(itemSourceInfo, itemRegEx, match => shapes[match]());
}
const cooldown = isSkill && fullInfo.cooldown ? <div>{fullInfo.cooldown} Turn delay</div> : null;
const speed = isSkill
? <div> Speed {shapes.SpeedStat()} multiplier {fullInfo.speed * 4}% </div>
: null;
const thresholds = isSpec ? specThresholds(player, fullInfo, info) : null;
return (
<div class={isSkill ? 'info-item' : 'info-item'}>
<h2>{infoName}</h2>
{header}
{itemSourceInfo}
{cooldown}
{itemDescription()}
{speed}
{thresholds}
</div>
);
}
return (
<div class="info-item">
<h2>{fullInfo.item}</h2>
{itemDescription()}
</div>
);
}
const Combos = () => {
if (tutorial && instance.time_control === 'Practice' && instance.rounds.length === 1) return false;
/* const generalNotes = (
<div>
<h2> General </h2>
<p>
You can preview combos by clicking the combined item when it appears in this section. <br />
Click the <b>READY</b> button to start the <b>GAME PHASE</b>.
</p>
</div>
);*/
if (!player) return false;
if (!info) return false;
const vboxCombos = itemInfo.combos.filter(c => c.components.includes(info));
if (vboxCombos.length > 6 || vboxCombos.length === 0) return false;
const comboTable = vboxCombos.map((c, i) => {
const mouseOver = e => {
e.stopPropagation();
this.setState({ comboItem: c.item });
};
const componentTable = (c.components.some(ci => ['Red', 'Blue', 'Green'].includes(ci)))
? [<div key="0">{convertItem(c.components[0])}&nbsp;{convertItem(c.components[1])}</div>,
<div key="1">{convertItem(c.components[2])}</div>]
: c.components.map((u, j) => <div key={j} >{convertItem(u)}</div>);
return (
<div key={i} onMouseOver={mouseOver} class="table-button">
<div class="item">
{convertItem(c.item)}
</div>
{componentTable}
</div>
);
});
const comboList = comboTable.length > 0 ? <div class="combo-list">{comboTable}</div> : false;
return (
<div class="combos">
<div class="combo-header">
<h2>COMBOS</h2>
Combine colours and items.
</div>
{comboList}
</div>
);
};
return (
<div class='info'>
<div onMouseOver={() => this.setState({ comboItem: null })}>
<Info />
</div>
<Combos />
</div>
);
}
}
module.exports = InfoComponent;

View File

@ -1,43 +0,0 @@
const { connect } = require('preact-redux');
const actions = require('../actions');
const Info = require('./info.component');
const addState = connect(
function receiveState(state) {
const {
ws,
info,
itemInfo,
instance,
player,
account,
tutorial,
} = state;
return {
ws,
info,
itemInfo,
instance,
player,
account,
tutorial,
};
},
function receiveDispatch(dispatch) {
function setTutorialNull() {
dispatch(actions.setTutorial(null));
}
function setInfo(info) {
dispatch(actions.setInfo(info));
}
return { setTutorialNull, setInfo };
}
);
module.exports = addState(Info);

View File

@ -2,7 +2,6 @@ const preact = require('preact');
const { connect } = require('preact-redux'); const { connect } = require('preact-redux');
const Vbox = require('./vbox.component'); const Vbox = require('./vbox.component');
const InfoContainer = require('./info.container');
const InstanceConstructsContainer = require('./instance.constructs'); const InstanceConstructsContainer = require('./instance.constructs');
const Faceoff = require('./faceoff'); const Faceoff = require('./faceoff');
@ -56,14 +55,9 @@ function Instance(args) {
clearItems(); clearItems();
} }
function onTouchMove(e) {
e.preventDefault();
}
return ( return (
<main id="instance" class='instance' onClick={instanceClick}> <main id="instance" class='instance' onClick={instanceClick}>
<Vbox /> <Vbox />
<InfoContainer />
<InstanceConstructsContainer /> <InstanceConstructsContainer />
</main> </main>
); );

View File

@ -129,8 +129,10 @@ function Construct(props) {
function skillClick(e) { function skillClick(e) {
if (!skill) return false; if (!skill) return false;
setItemUnequip([construct.id, skill.skill, i]);
e.stopPropagation(); e.stopPropagation();
if (itemUnequip.length && itemUnequip[0] === construct.id && skill.skill === itemUnequip[1]
&& i === itemUnequip[2]) return setItemUnequip([]);
setItemUnequip([construct.id, skill.skill, i]);
return true; return true;
} }
@ -177,8 +179,11 @@ function Construct(props) {
} }
function specClick(e) { function specClick(e) {
if (!s) return false;
e.stopPropagation(); e.stopPropagation();
setItemUnequip([construct.id, s, i]); if (itemUnequip.length && itemUnequip[0] === construct.id && itemUnequip[1] === s
&& i === itemUnequip[2]) return setItemUnequip([]);
return setItemUnequip([construct.id, s, i]);
} }
const highlight = itemUnequip[0] === construct.id && itemUnequip[1] === s && i === itemUnequip[2]; const highlight = itemUnequip[0] === construct.id && itemUnequip[1] === s && i === itemUnequip[2];

View File

@ -26,6 +26,9 @@ const addState = connect(
dispatch(actions.setNav('play')); dispatch(actions.setNav('play'));
dispatch(actions.setGame(null)); dispatch(actions.setGame(null));
dispatch(actions.setInstance(null)); dispatch(actions.setInstance(null));
dispatch(actions.setVboxSelected({ storeSelect: [], stashSelect: [] }));
dispatch(actions.setInfo(null));
dispatch(actions.setItemUnequip([]));
if (tutorial) dispatch(actions.setTutorial(1)); if (tutorial) dispatch(actions.setTutorial(1));
} }

View File

@ -110,8 +110,11 @@ class TargetSvg extends Component {
? playerTeam.constructs.findIndex(c => c.id === cast.target_construct_id) ? playerTeam.constructs.findIndex(c => c.id === cast.target_construct_id)
: otherTeam.constructs.findIndex(c => c.id === cast.target_construct_id); : otherTeam.constructs.findIndex(c => c.id === cast.target_construct_id);
const skillNumber = window.innerWidth <= 800 // mobile styling trigger
? playerTeam.constructs[source].skills.findIndex(s => s.skill === cast.skill)
: 0;
const sourceY = height; const sourceY = height;
const sourceX = (source * width / 3) + width / 24; const sourceX = (source * width / 3) + width / 18 + skillNumber * (width / 9);
const targetX = (target * width / 3) + width / 6 const targetX = (target * width / 3) + width / 6
+ (defensive ? width / 64 : 0) + (defensive ? width / 64 : 0)
+ (source * width / 18); + (source * width / 18);

View File

@ -1,66 +0,0 @@
const preact = require('preact');
const countBy = require('lodash/countBy');
function combinerBtn(props) {
const {
itemInfo,
sendVboxCombine,
setInfo,
stashSelect,
storeSelect,
vbox,
vboxBuySelected,
vboxHighlight,
} = props;
let text = '';
let mouseEvent = false;
const combineLength = stashSelect.length + storeSelect.length;
if (vboxHighlight && vboxHighlight.length === 0) {
// The selected items can't be combined with additional items therefore valid combo
const stashItems = stashSelect.map(j => vbox.bound[j]);
const shopItems = storeSelect.map(j => vbox.free[j[0]][j[1]]);
const selectedItems = stashItems.concat(shopItems);
const combinerCount = countBy(selectedItems, co => co);
const comboItemObj = itemInfo.combos.find(combo => selectedItems.every(c => {
if (!combo.components.includes(c)) return false;
const comboCount = countBy(combo.components, co => co);
if (combinerCount[c] > comboCount[c]) return false;
return true;
}));
let comboItem = comboItemObj ? comboItemObj.item : 'refine';
setInfo(comboItem);
comboItem = comboItem.replace('Plus', '+');
let bits = 0;
storeSelect.forEach(item => bits += item[0] + 1);
text = bits
? <p>Buy<br/>{comboItem}<br />{bits}b</p>
: <p>Combine<br />{comboItem}</p>;
if (vbox.bits >= bits) mouseEvent = sendVboxCombine;
} else if (stashSelect.length === 0 && storeSelect.length === 1) {
const item = storeSelect[0];
text = <p>Buy<br />{vbox.free[item[0]][item[1]]}<br />{item[0] + 1}b</p>;
mouseEvent = vboxBuySelected;
} else {
for (let i = 0; i < 3; i++) {
if (combineLength > i) {
text += '■ ';
} else {
text += '▫ ';
}
}
}
return (
<div class='combiner'>
<button
class='vbox-btn'
disabled={!mouseEvent}
onClick={e => e.stopPropagation()}
onMouseDown={() => mouseEvent()}>
{text}
</button>
</div>
);
}
module.exports = combinerBtn;

View File

@ -5,9 +5,9 @@ const forEach = require('lodash/forEach');
const actions = require('../actions'); const actions = require('../actions');
const InfoContainer = require('./vbox.info');
const StashElement = require('./vbox.stash'); const StashElement = require('./vbox.stash');
const StoreElement = require('./vbox.store'); const StoreElement = require('./vbox.store');
const CombinerElement = require('./vbox.combiner');
const addState = connect( const addState = connect(
function receiveState(state) { function receiveState(state) {
@ -16,6 +16,7 @@ const addState = connect(
instance, instance,
player, player,
vboxSelected, vboxSelected,
info,
itemInfo, itemInfo,
itemUnequip, itemUnequip,
tutorial, tutorial,
@ -30,10 +31,6 @@ const addState = connect(
return ws.sendVboxAccept(instance.id, group, index); return ws.sendVboxAccept(instance.id, group, index);
} }
function sendVboxCombine() {
return ws.sendVboxCombine(instance.id, vboxSelected.stashSelect, vboxSelected.storeSelect);
}
function sendVboxReclaim(i) { function sendVboxReclaim(i) {
return ws.sendVboxReclaim(instance.id, i); return ws.sendVboxReclaim(instance.id, i);
} }
@ -44,9 +41,9 @@ const addState = connect(
return { return {
instance, instance,
info,
player, player,
sendVboxAccept, sendVboxAccept,
sendVboxCombine,
sendVboxDiscard, sendVboxDiscard,
sendVboxReclaim, sendVboxReclaim,
vboxSelected, vboxSelected,
@ -130,7 +127,6 @@ class Vbox extends preact.Component {
// Function Calls // Function Calls
sendItemUnequip, sendItemUnequip,
sendVboxAccept, sendVboxAccept,
sendVboxCombine,
sendVboxDiscard, sendVboxDiscard,
sendVboxReclaim, sendVboxReclaim,
setVboxSelected, setVboxSelected,
@ -153,11 +149,7 @@ class Vbox extends preact.Component {
function vboxHover(e, v) { function vboxHover(e, v) {
if (v) { if (v) {
e.stopPropagation(); e.stopPropagation();
if (storeSelect.find(c => c[0])) return true; // There is a base skill or spec selected in the vbox if (stashSelect.length !== 0 || storeSelect.length !== 0) return true;
if (stashSelect.length !== 0) {
const base = stashSelect.find(c => !['Red', 'Blue', 'Green'].includes(vbox.bound[c]));
if (base || base === 0) return true;
}
setInfo(v); setInfo(v);
} }
return true; return true;
@ -177,10 +169,10 @@ class Vbox extends preact.Component {
function storeHdr() { function storeHdr() {
return ( return (
<div class="store-hdr"> <div class="store-hdr">
<h3 <h2
onTouchStart={e => e.target.scrollIntoView(true)} onTouchStart={e => e.target.scrollIntoView(true)}
onMouseOver={e => vboxHover(e, 'store')}> STORE onMouseOver={e => vboxHover(e, 'store')}> STORE
</h3> </h2>
<div class={`bits ${vbox.bits < 3 ? 'red' : false}`} onMouseOver={e => vboxHover(e, 'bits')}> <div class={`bits ${vbox.bits < 3 ? 'red' : false}`} onMouseOver={e => vboxHover(e, 'bits')}>
<h1> {vbox.bits}b </h1> <h1> {vbox.bits}b </h1>
</div> </div>
@ -222,9 +214,9 @@ class Vbox extends preact.Component {
return ( return (
<div class='stash-hdr'> <div class='stash-hdr'>
<h3 onTouchStart={e => e.target.scrollIntoView(true)} <h2 onTouchStart={e => e.target.scrollIntoView(true)}
onMouseOver={e => vboxHover(e, 'stash')}> STASH onMouseOver={e => vboxHover(e, 'stash')}> STASH
</h3> </h2>
{refundBtn} {refundBtn}
</div> </div>
); );
@ -235,6 +227,10 @@ class Vbox extends preact.Component {
<div class='vbox'> <div class='vbox'>
{storeHdr()} {storeHdr()}
{stashHdr()} {stashHdr()}
<InfoContainer
vboxHighlight={vboxHighlight}
vboxBuySelected={vboxBuySelected}
/>
<StoreElement <StoreElement
clearVboxSelected = {clearVboxSelected} clearVboxSelected = {clearVboxSelected}
setInfo = {setInfo} setInfo = {setInfo}
@ -258,16 +254,6 @@ class Vbox extends preact.Component {
setInfo = {setInfo} setInfo = {setInfo}
setVboxSelected = {setVboxSelected} setVboxSelected = {setVboxSelected}
/> />
<CombinerElement
itemInfo = {itemInfo}
sendVboxCombine = {sendVboxCombine}
setInfo = {setInfo}
stashSelect = {stashSelect}
storeSelect = {storeSelect}
vbox = {vbox}
vboxBuySelected = {vboxBuySelected}
vboxHighlight = {vboxHighlight}
/>
</div> </div>
); );
} }

View File

@ -0,0 +1,315 @@
const preact = require('preact');
const { connect } = require('preact-redux');
const reactStringReplace = require('react-string-replace');
const countBy = require('lodash/countBy');
const specThresholds = require('./vbox.info.thresholds');
const { INFO } = require('./../constants');
const { convertItem, removeTier } = require('../utils');
const { tutorialStage } = require('../tutorial.utils');
const shapes = require('./shapes');
const actions = require('../actions');
const addState = connect(
function receiveState(state) {
const {
ws,
info,
itemInfo,
itemUnequip,
instance,
player,
account,
tutorial,
vboxSelected,
} = state;
function sendVboxAccept(group, index) {
document.activeElement.blur();
return ws.sendVboxAccept(instance.id, group, index);
}
function sendVboxCombine() {
return ws.sendVboxCombine(instance.id, vboxSelected.stashSelect, vboxSelected.storeSelect);
}
return {
ws,
info,
itemInfo,
itemUnequip,
instance,
player,
account,
tutorial,
vboxSelected,
sendVboxAccept,
sendVboxCombine,
};
},
function receiveDispatch(dispatch) {
function setTutorialNull() {
dispatch(actions.setTutorial(null));
}
function setInfo(info) {
dispatch(actions.setInfo(info));
}
return { setTutorialNull, setInfo };
}
);
class InfoComponent extends preact.Component {
shouldComponentUpdate(newProps, newState) {
if (newProps.info !== this.props.info) return true;
if (newProps.itemUnequip !== this.props.itemUnequip) return true;
if (newProps.tutorial !== this.props.tutorial) return true;
if (newProps.vboxHighlight !== this.props.vboxHighlight) return true;
if (newProps.vboxSelected !== this.props.vboxSelected) return true;
if (newState.comboItem !== this.state.comboItem) return true;
return false;
}
componentDidUpdate(prevProps) {
// Catch case where mouse events don't properly clear state and info changed
if (prevProps.info !== this.props.info && this.state.comboItem) this.setState({ comboItem: null });
}
render(args) {
const {
// Variables that will change
info,
itemUnequip,
tutorial,
vboxHighlight,
vboxSelected,
// Static
player, // Only used for colour calcs which will be update if info changes
ws,
itemInfo,
instance, // Only used for instance id
// functions
sendVboxAccept,
sendVboxCombine,
setTutorialNull,
} = args;
const { comboItem } = this.state;
const { vbox } = player;
const { stashSelect, storeSelect } = vboxSelected;
const vboxCombo = () => {
if (!(vboxHighlight && vboxHighlight.length === 0)) return false;
// The selected items can't be combined with additional items therefore valid combo
const stashItems = stashSelect.map(j => vbox.bound[j]);
const shopItems = storeSelect.map(j => vbox.free[j[0]][j[1]]);
const selectedItems = stashItems.concat(shopItems);
const combinerCount = countBy(selectedItems, co => co);
const comboItemObj = itemInfo.combos.find(combo => selectedItems.every(c => {
if (!combo.components.includes(c)) return false;
const comboCount = countBy(combo.components, co => co);
if (combinerCount[c] > comboCount[c]) return false;
return true;
}));
return comboItemObj.item;
};
const combinerCombo = vboxCombo();
const checkVboxInfo = () => {
if (combinerCombo) return combinerCombo;
const stashBase = stashSelect.find(i => !(['Red', 'Blue', 'Green'].includes(vbox.bound[i])));
if (stashBase > -1) return vbox.bound[stashBase];
const storeBase = storeSelect.find(j => !(['Red', 'Blue', 'Green'].includes(vbox.free[j[0]][j[1]])));
if (storeBase) return vbox.free[storeBase[0]][storeBase[1]];
if (stashSelect.length > 0) return vbox.bound[stashSelect[0]];
if (storeSelect.length > 0) return vbox.free[storeSelect[0][0]][storeSelect[0][1]];
return false;
};
let vboxInfo = false;
if (itemUnequip.length) [, vboxInfo] = itemUnequip;
else if (stashSelect.length > 0 || storeSelect.length > 0) vboxInfo = checkVboxInfo();
function Info() {
if (tutorial) {
const tutorialStageInfo = tutorialStage(tutorial, ws, setTutorialNull, instance);
if (tutorialStageInfo) return tutorialStageInfo;
}
function genItemInfo(item) {
const fullInfo = itemInfo.items.find(i => i.item === item) || INFO[item];
const isSkill = fullInfo.skill;
const isSpec = fullInfo.spec;
const itemDescription = () => {
const regEx = /(RedPower|BluePower|GreenPower|RedLife|BlueLife|GreenLife|SpeedStat|LIFE|SPEED|POWER)/;
const infoDescription = reactStringReplace(fullInfo.description, regEx, m => shapes[m]());
return <div>{reactStringReplace(infoDescription, '\n', () => <br />)}</div>;
};
if (isSkill || isSpec) {
let infoName = fullInfo.item;
while (infoName.includes('Plus')) infoName = infoName.replace('Plus', '+');
const itemSource = itemInfo.combos.filter(c => c.item === removeTier(fullInfo.item));
let itemSourceInfo = itemSource.length && !isSpec
? `${itemSource[0].components[0]} ${itemSource[0].components[1]} ${itemSource[0].components[2]}`
: false;
let header = null;
if (!itemSource.length) header = isSkill ? <h3> SKILL </h3> : <h3> SPEC </h3>;
if (itemSourceInfo) {
while (itemSourceInfo.includes('Plus')) itemSourceInfo = itemSourceInfo.replace('Plus', '+');
const itemRegEx = /(Red|Blue|Green)/;
itemSourceInfo = reactStringReplace(itemSourceInfo, itemRegEx, match => shapes[match]());
}
const cooldown = isSkill && fullInfo.cooldown ? <div>{fullInfo.cooldown} Turn delay</div> : null;
const speed = isSkill
? <div> Speed {shapes.SpeedStat()} multiplier {fullInfo.speed * 4}% </div>
: null;
const thresholds = isSpec ? specThresholds(player, fullInfo, info) : null;
return (
<div class={isSkill ? 'info-item' : 'info-item'}>
<h2>{infoName}</h2>
{header}
{itemSourceInfo}
{cooldown}
{itemDescription()}
{speed}
{thresholds}
</div>
);
}
return (
<div class="info-item">
<h2>{fullInfo.item}</h2>
{itemDescription()}
</div>
);
}
const stateFullInfo = comboItem || vboxInfo;
if (stateFullInfo) return genItemInfo(stateFullInfo);
if (!info) return false;
if (info.includes('constructName')) {
return (
<div class='info-item'>
<h2> {info.replace('constructName ', '')} </h2>
<p> This is the name of your construct. <br />
Names are randomly generated and are purely cosmetic. <br />
You can change change your construct name in the <b>RESHAPE</b> tab outside of games.
</p>
</div>
);
}
if (info.includes('constructAvatar')) {
return (
<div class='info-item'>
<h2> {info.replace('constructAvatar ', '')} </h2>
<p> This is your construct avatar. <br />
Avatars are randomly generated and are purely cosmetic. <br />
You can change your construct avatar in the <b>RESHAPE</b> tab outside of games.
</p>
</div>
);
}
return genItemInfo(info);
}
const Combos = () => {
if (tutorial && instance.time_control === 'Practice' && instance.rounds.length === 1) return false;
if (!info && !vboxInfo) return false;
const comboInfo = vboxInfo || info;
const vboxCombos = itemInfo.combos.filter(c => c.components.includes(comboInfo));
if (vboxCombos.length > 6 || vboxCombos.length === 0) return false;
const comboTable = vboxCombos.map((c, i) => {
const mouseOver = e => {
e.stopPropagation();
this.setState({ comboItem: c.item });
};
const componentTable = (c.components.some(ci => ['Red', 'Blue', 'Green'].includes(ci)))
? [<div key="0">{convertItem(c.components[0])}&nbsp;{convertItem(c.components[1])}</div>,
<div key="1">{convertItem(c.components[2])}</div>]
: c.components.map((u, j) => <div key={j} >{convertItem(u)}</div>);
return (
<div key={i} onMouseOver={mouseOver} class="table-button">
<div class="item">
{convertItem(c.item)}
</div>
{componentTable}
</div>
);
});
const comboList = comboTable.length > 0 ? <div class="combo-list">{comboTable}</div> : false;
return (
<div class="combos">
<div class="combo-header">
<h2>COMBOS</h2>
Combine colours and items.
</div>
{comboList}
</div>
);
};
const Combiner = () => {
if (comboItem) return false;
function vboxBuySelected() {
if (!(storeSelect.length === 1 && stashSelect.length === 0)) return false;
document.activeElement.blur();
sendVboxAccept(storeSelect[0][0], storeSelect[0][1]);
return true;
}
let text = '';
let mouseEvent = false;
if (combinerCombo) {
const combinerComboText = combinerCombo.replace('Plus', '+');
let bits = 0;
storeSelect.forEach(item => bits += item[0] + 1);
text = bits
? `Buy ${combinerComboText} ${bits}b`
: `Combine ${combinerComboText}`;
if (vbox.bits >= bits) mouseEvent = sendVboxCombine;
} else if (stashSelect.length === 0 && storeSelect.length === 1) {
const item = storeSelect[0];
text = `Buy ${vbox.free[item[0]][item[1]]} ${item[0] + 1}b`;
mouseEvent = vboxBuySelected;
} else {
return false;
}
return (
<div class='combiner'>
<button
class='vbox-btn'
disabled={!mouseEvent}
onClick={e => e.stopPropagation()}
onMouseDown={() => mouseEvent()}>
{text}
</button>
</div>
);
};
return (
<div class='info'>
<div onMouseOver={() => this.setState({ comboItem: null })}>
<div class='info-details'>
<Info />
<Combiner />
</div>
</div>
<Combos />
</div>
);
}
}
module.exports = addState(InfoComponent);

View File

@ -89,11 +89,7 @@ function specThresholds(player, fullInfo, info) {
return ( return (
<div> <div>
<div class="thresholds"> <div class="thresholds">
{thresholds[0]} {thresholds}
{thresholds[1]}
</div>
<div class="thresholds">
{thresholds[2]}
</div> </div>
</div> </div>

View File

@ -44,7 +44,7 @@ function stashElement(props) {
class={stashHighlight ? 'receiving' : 'empty'} >&nbsp;</button>; class={stashHighlight ? 'receiving' : 'empty'} >&nbsp;</button>;
} }
const comboHighlight = vboxHighlight && vboxHighlight.includes(v) ? 'combo-border' : ''; const notValidCombo = vboxHighlight && !vboxHighlight.includes(v);
function onClick(type) { function onClick(type) {
const combinerContainsIndex = stashSelect.indexOf(i) > -1; const combinerContainsIndex = stashSelect.indexOf(i) > -1;
@ -56,7 +56,7 @@ function stashElement(props) {
return true; return true;
} }
if (!comboHighlight) { if (notValidCombo) {
setInfo(vbox.bound[i]); setInfo(vbox.bound[i]);
return setVboxSelected({ storeSelect: [], stashSelect: [i] }); return setVboxSelected({ storeSelect: [], stashSelect: [i] });
} }
@ -68,7 +68,9 @@ function stashElement(props) {
const highlighted = stashSelect.indexOf(i) > -1; const highlighted = stashSelect.indexOf(i) > -1;
const border = buttons[removeTier(v)] ? buttons[removeTier(v)]() : ''; const border = buttons[removeTier(v)] ? buttons[removeTier(v)]() : '';
const classes = `${highlighted ? 'highlight' : border} ${comboHighlight}`; const classes = highlighted
? 'highlight'
: `${border} ${notValidCombo ? 'fade' : ''}`;
const invObject = shapes[v] ? shapes[v]() : v; const invObject = shapes[v] ? shapes[v]() : v;

View File

@ -7,7 +7,6 @@ const shapes = require('./shapes');
function storeElement(props) { function storeElement(props) {
const { const {
clearVboxSelected, clearVboxSelected,
setInfo,
setVboxSelected, setVboxSelected,
storeSelect, storeSelect,
stashSelect, stashSelect,
@ -20,11 +19,10 @@ function storeElement(props) {
if (!v) return <button disabled class='empty' key={(group * 10) + index} >&nbsp;</button>; if (!v) return <button disabled class='empty' key={(group * 10) + index} >&nbsp;</button>;
const selected = storeSelect.length && storeSelect.some(vs => vs[0] === group && vs[1] === index); const selected = storeSelect.length && storeSelect.some(vs => vs[0] === group && vs[1] === index);
const comboHighlight = vboxHighlight && vboxHighlight.includes(v) ? 'combo-border' : ''; const notValidCombo = vboxHighlight && !vboxHighlight.includes(v);
function onClick(e) { function onClick(e) {
e.stopPropagation(); e.stopPropagation();
if (!comboHighlight) setInfo(vbox.free[group][index]);
if (storeSelect.length && storeSelect.some(vs => vs[0] === group && vs[1] === index)) { if (storeSelect.length && storeSelect.some(vs => vs[0] === group && vs[1] === index)) {
return setVboxSelected( return setVboxSelected(
{ storeSelect: storeSelect.filter(vs => !(vs[0] === group && vs[1] === index)), stashSelect } { storeSelect: storeSelect.filter(vs => !(vs[0] === group && vs[1] === index)), stashSelect }
@ -34,20 +32,21 @@ function storeElement(props) {
if (!storeSelect.length && !stashSelect.length) { if (!storeSelect.length && !stashSelect.length) {
return setVboxSelected({ storeSelect: [[group, index]], stashSelect }); return setVboxSelected({ storeSelect: [[group, index]], stashSelect });
} }
if (comboHighlight !== 'combo-border') { if (notValidCombo) {
return setVboxSelected({ storeSelect: [[group, index]], stashSelect: [] }); return setVboxSelected({ storeSelect: [[group, index]], stashSelect: [] });
} }
return setVboxSelected({ storeSelect: [...storeSelect, [group, index]], stashSelect }); return setVboxSelected({ storeSelect: [...storeSelect, [group, index]], stashSelect });
} }
const classes = `${v.toLowerCase()} ${selected ? 'highlight' : ''} ${comboHighlight}`; const classes = selected
? `${v.toLowerCase()} highlight`
: `${v.toLowerCase()} ${notValidCombo ? 'fade' : ''}`;
const vboxObject = shapes[v] ? shapes[v]() : v; const vboxObject = shapes[v] ? shapes[v]() : v;
const disabled = vbox.bits <= group; const disabled = vbox.bits <= group;
return ( return (
<label draggable='true' <label
onDragStart={ev => ev.dataTransfer.setData('text', '')}
key={group * 10 + index} key={group * 10 + index}
onDragEnd={clearVboxSelected}> onDragEnd={clearVboxSelected}>
<button <button

View File

@ -171,7 +171,7 @@ impl Vbox {
self.bound.push(combo.item); self.bound.push(combo.item);
// self.bound.sort_unstable(); // self.bound.sort_unstable();
if self.bound.len() >= 6 { if self.bound.len() > 6 {
return Err(err_msg("too many items bound")); return Err(err_msg("too many items bound"));
} }
Ok(self) Ok(self)