diff --git a/client/assets/styles/colours.less b/client/assets/styles/colours.less index a1ae5dcb..16e972af 100644 --- a/client/assets/styles/colours.less +++ b/client/assets/styles/colours.less @@ -5,6 +5,7 @@ @white: #f5f5f5; // whitesmoke @purple: #9355b5; // 6lack - that far cover @yellow: #ffa100; +@silver: #c0c0c0; @black: black; @gray: #222; diff --git a/client/assets/styles/game.less b/client/assets/styles/game.less index 9b19db2c..ea48cede 100644 --- a/client/assets/styles/game.less +++ b/client/assets/styles/game.less @@ -27,6 +27,7 @@ position: absolute; bottom: 0; + margin-bottom: 0.5em; height: 50%; .avatar { @@ -232,8 +233,8 @@ #targeting, .resolving-skill { position: absolute; - top: 35%; - height: 15%; + top: calc(35% + 0.5em); // calc for 0.5em top gap + height: calc(15% - 1em); // calc for 0.5em + 0.5em top / bottom gap width: calc(90% - 1.25em); z-index: 2; span { @@ -273,7 +274,7 @@ /* some stupid bug in chrome makes it fill the entire screen */ @media screen and (-webkit-min-device-pixel-ratio:0) { #targeting { - max-height: 10em; + // max-height: 10em; } } diff --git a/client/assets/styles/instance.less b/client/assets/styles/instance.less index 055ee56e..fa1e1485 100644 --- a/client/assets/styles/instance.less +++ b/client/assets/styles/instance.less @@ -5,114 +5,17 @@ .instance { overflow: hidden; display: grid; - grid-template-columns: 1fr minmax(min-content, 1fr); grid-template-rows: min-content 1fr; grid-template-areas: - "vbox info" - "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; - } - } - } - } - } - } + "vbox" + "constructs"; .constructs { grid-area: constructs; } } - - - @keyframes action { 0% { color: palegoldenrod; @@ -152,7 +55,7 @@ button { &.highlight { color: black; - background: @white; + background: @silver; // border: 1px solid @white; (this bangs around the vbox) // overwrite the classes on white svg elements diff --git a/client/assets/styles/styles.mobile.less b/client/assets/styles/styles.mobile.less index 49c716ae..12501705 100644 --- a/client/assets/styles/styles.mobile.less +++ b/client/assets/styles/styles.mobile.less @@ -1,7 +1,6 @@ @media (max-width: 800px) { body { overflow-y: initial; - } #mnml { @@ -95,6 +94,10 @@ } } } + + .avatar { + opacity: 0.1; + } } .game { @@ -221,8 +224,8 @@ } -// portrait menu -@media (max-width: 500px) { +// portrait menu or small size vertical in landscape +@media (max-width: 550px) { #mnml { grid-template-columns: 1fr; grid-template-rows: 1fr; @@ -289,6 +292,14 @@ } } + .stats { + font-size: 6pt; + } + + .skill-description { + font-size: 6pt; + } + section { .list { grid-template-columns: 1fr; diff --git a/client/assets/styles/vbox.less b/client/assets/styles/vbox.less index 6b51f9de..76416c5c 100644 --- a/client/assets/styles/vbox.less +++ b/client/assets/styles/vbox.less @@ -3,10 +3,10 @@ grid-area: vbox; display: grid; grid-template-rows: 3fr 2fr; - grid-template-columns: 1fr 4fr 1fr; + grid-template-columns: 1fr 4fr 6fr; grid-template-areas: - "store-hdr store combiner" - "stash-hdr stash combiner"; + "store-hdr store info" + "stash-hdr stash info"; margin-bottom: 1em; line-height: 0; @@ -17,8 +17,8 @@ .store { grid-area: store; - border-right: 0.1em solid @gray; - border-top: 0.1em solid @gray; + border-right: 0.15em solid @gray; + border-top: 0.15em solid @gray; } .store-hdr { @@ -27,17 +27,16 @@ flex-flow: column; text-align: center; - border-left: 0.1em solid @gray; - border-top: 0.1em solid @gray; + border-left: 0.15em solid @gray; + border-top: 0.15em solid @gray; h1 { - margin-bottom: 0; + margin-bottom: 0.5em; } button { - margin: 1em 0 0 0; line-height: 1.6; - letter-spacing: 0.1em; + letter-spacing: 0.15em; background-color: #421010; &:hover { 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 { grid-area: stash; display: grid; @@ -85,37 +52,26 @@ grid-gap: 0.5em 1em; align-items: center; - border: 0.1em solid @gray; + border: 0.15em solid @gray; border-left: 0; } .stash-hdr { + grid-area: stash-hdr; display: flex; flex-flow: column; - justify-content: center; - - grid-area: stash-hdr; + text-align: center; - border: 0.1em solid @gray; + border: 0.15em solid @gray; border-right: 0; - h3 { - margin-bottom: 2.5em; + h2 { + margin-bottom: 0.5em; } - } - - .refund { - padding: 0.5em 0.5em 0 0; button { - line-height: 1.4; - letter-spacing: 0.1em; - color: black; - background-color: @yellow; - } - - .vbox-padding { - padding-left: 0; + line-height: 1.6; + letter-spacing: 0.15em; } } @@ -178,14 +134,17 @@ width: 100%; // text-transform: none; - &.empty { border-style: dashed; } + &.fade { + opacity: 0.4; + } + &.highlight { color: black; - background: @white; + background: @silver; // border: 1px solid @white; (this bangs around the vbox) // overwrite the classes on white svg elements @@ -210,6 +169,127 @@ 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) { diff --git a/client/src/components/info.component.jsx b/client/src/components/info.component.jsx deleted file mode 100644 index eec7b710..00000000 --- a/client/src/components/info.component.jsx +++ /dev/null @@ -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 ( -
-

{info.replace('constructName ', '')}

-

This is the name of your construct.
- Names are randomly generated and are purely cosmetic.
- You can change change your construct name in the RESHAPE tab outside of games. -

-
- ); - } - - if (info.includes('constructAvatar')) { - return ( -
-

{info.replace('constructAvatar ', '')}

-

This is your construct avatar.
- Avatars are randomly generated and are purely cosmetic.
- You can change your construct avatar in the RESHAPE tab outside of games. -

-
- ); - } - 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
{reactStringReplace(infoDescription, '\n', () =>
)}
; - }; - - 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 ?

SKILL

:

SPEC

; - 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 ?
{fullInfo.cooldown} Turn delay
: null; - - const speed = isSkill - ?
Speed {shapes.SpeedStat()} multiplier {fullInfo.speed * 4}%
- : null; - - const thresholds = isSpec ? specThresholds(player, fullInfo, info) : null; - - return ( -
-

{infoName}

- {header} - {itemSourceInfo} - {cooldown} - {itemDescription()} - {speed} - {thresholds} -
- ); - } - - return ( -
-

{fullInfo.item}

- {itemDescription()} -
- ); - } - - const Combos = () => { - if (tutorial && instance.time_control === 'Practice' && instance.rounds.length === 1) return false; -/* const generalNotes = ( -
-

General

-

- You can preview combos by clicking the combined item when it appears in this section.
- Click the READY button to start the GAME PHASE. -

-
- );*/ - 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))) - ? [
{convertItem(c.components[0])} {convertItem(c.components[1])}
, -
{convertItem(c.components[2])}
] - : c.components.map((u, j) =>
{convertItem(u)}
); - return ( -
-
- {convertItem(c.item)} -
- {componentTable} -
- ); - }); - const comboList = comboTable.length > 0 ?
{comboTable}
: false; - return ( -
-
-

COMBOS

- Combine colours and items. -
- {comboList} -
- ); - }; - - return ( -
-
this.setState({ comboItem: null })}> - -
- -
- ); - } -} - -module.exports = InfoComponent; diff --git a/client/src/components/info.container.jsx b/client/src/components/info.container.jsx deleted file mode 100644 index 8a8dbb6f..00000000 --- a/client/src/components/info.container.jsx +++ /dev/null @@ -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); diff --git a/client/src/components/instance.component.jsx b/client/src/components/instance.component.jsx index a8abcac0..c59b5833 100644 --- a/client/src/components/instance.component.jsx +++ b/client/src/components/instance.component.jsx @@ -2,7 +2,6 @@ const preact = require('preact'); const { connect } = require('preact-redux'); const Vbox = require('./vbox.component'); -const InfoContainer = require('./info.container'); const InstanceConstructsContainer = require('./instance.constructs'); const Faceoff = require('./faceoff'); @@ -56,14 +55,9 @@ function Instance(args) { clearItems(); } - function onTouchMove(e) { - e.preventDefault(); - } - return (
-
); diff --git a/client/src/components/instance.constructs.jsx b/client/src/components/instance.constructs.jsx index 88589efe..c0a0b36d 100644 --- a/client/src/components/instance.constructs.jsx +++ b/client/src/components/instance.constructs.jsx @@ -129,8 +129,10 @@ function Construct(props) { function skillClick(e) { if (!skill) return false; - setItemUnequip([construct.id, skill.skill, i]); 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; } @@ -177,8 +179,11 @@ function Construct(props) { } function specClick(e) { + if (!s) return false; 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]; diff --git a/client/src/components/instance.ctrl.top.btns.jsx b/client/src/components/instance.ctrl.top.btns.jsx index 108485f7..7fc4f018 100644 --- a/client/src/components/instance.ctrl.top.btns.jsx +++ b/client/src/components/instance.ctrl.top.btns.jsx @@ -26,6 +26,9 @@ const addState = connect( dispatch(actions.setNav('play')); dispatch(actions.setGame(null)); dispatch(actions.setInstance(null)); + dispatch(actions.setVboxSelected({ storeSelect: [], stashSelect: [] })); + dispatch(actions.setInfo(null)); + dispatch(actions.setItemUnequip([])); if (tutorial) dispatch(actions.setTutorial(1)); } diff --git a/client/src/components/targeting.arrows.jsx b/client/src/components/targeting.arrows.jsx index f93966c5..a7a7e4b3 100644 --- a/client/src/components/targeting.arrows.jsx +++ b/client/src/components/targeting.arrows.jsx @@ -110,8 +110,11 @@ class TargetSvg extends Component { ? playerTeam.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 sourceX = (source * width / 3) + width / 24; + const sourceX = (source * width / 3) + width / 18 + skillNumber * (width / 9); const targetX = (target * width / 3) + width / 6 + (defensive ? width / 64 : 0) + (source * width / 18); diff --git a/client/src/components/vbox.combiner.jsx b/client/src/components/vbox.combiner.jsx deleted file mode 100644 index 5b02b88e..00000000 --- a/client/src/components/vbox.combiner.jsx +++ /dev/null @@ -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 - ?

Buy
{comboItem}
{bits}b

- :

Combine
{comboItem}

; - 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 { - for (let i = 0; i < 3; i++) { - if (combineLength > i) { - text += '■ '; - } else { - text += '▫ '; - } - } - } - return ( -
- -
- ); -} - -module.exports = combinerBtn; diff --git a/client/src/components/vbox.component.jsx b/client/src/components/vbox.component.jsx index e51b94be..b4c2f42b 100644 --- a/client/src/components/vbox.component.jsx +++ b/client/src/components/vbox.component.jsx @@ -5,9 +5,9 @@ const forEach = require('lodash/forEach'); const actions = require('../actions'); +const InfoContainer = require('./vbox.info'); const StashElement = require('./vbox.stash'); const StoreElement = require('./vbox.store'); -const CombinerElement = require('./vbox.combiner'); const addState = connect( function receiveState(state) { @@ -16,6 +16,7 @@ const addState = connect( instance, player, vboxSelected, + info, itemInfo, itemUnequip, tutorial, @@ -30,10 +31,6 @@ const addState = connect( return ws.sendVboxAccept(instance.id, group, index); } - function sendVboxCombine() { - return ws.sendVboxCombine(instance.id, vboxSelected.stashSelect, vboxSelected.storeSelect); - } - function sendVboxReclaim(i) { return ws.sendVboxReclaim(instance.id, i); } @@ -44,9 +41,9 @@ const addState = connect( return { instance, + info, player, sendVboxAccept, - sendVboxCombine, sendVboxDiscard, sendVboxReclaim, vboxSelected, @@ -130,7 +127,6 @@ class Vbox extends preact.Component { // Function Calls sendItemUnequip, sendVboxAccept, - sendVboxCombine, sendVboxDiscard, sendVboxReclaim, setVboxSelected, @@ -153,11 +149,7 @@ class Vbox extends preact.Component { function vboxHover(e, v) { if (v) { 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) { - const base = stashSelect.find(c => !['Red', 'Blue', 'Green'].includes(vbox.bound[c])); - if (base || base === 0) return true; - } + if (stashSelect.length !== 0 || storeSelect.length !== 0) return true; setInfo(v); } return true; @@ -177,10 +169,10 @@ class Vbox extends preact.Component { function storeHdr() { return (
-

e.target.scrollIntoView(true)} onMouseOver={e => vboxHover(e, 'store')}> STORE -

+
vboxHover(e, 'bits')}>

{vbox.bits}b

@@ -222,9 +214,9 @@ class Vbox extends preact.Component { return (
-

e.target.scrollIntoView(true)} +

e.target.scrollIntoView(true)} onMouseOver={e => vboxHover(e, 'stash')}> STASH -

+ {refundBtn}
); @@ -235,6 +227,10 @@ class Vbox extends preact.Component {
{storeHdr()} {stashHdr()} + -
); } diff --git a/client/src/components/vbox.info.jsx b/client/src/components/vbox.info.jsx new file mode 100644 index 00000000..f8ea6157 --- /dev/null +++ b/client/src/components/vbox.info.jsx @@ -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
{reactStringReplace(infoDescription, '\n', () =>
)}
; + }; + 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 ?

SKILL

:

SPEC

; + 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 ?
{fullInfo.cooldown} Turn delay
: null; + + const speed = isSkill + ?
Speed {shapes.SpeedStat()} multiplier {fullInfo.speed * 4}%
+ : null; + + const thresholds = isSpec ? specThresholds(player, fullInfo, info) : null; + + return ( +
+

{infoName}

+ {header} + {itemSourceInfo} + {cooldown} + {itemDescription()} + {speed} + {thresholds} +
+ ); + } + return ( +
+

{fullInfo.item}

+ {itemDescription()} +
+ ); + } + const stateFullInfo = comboItem || vboxInfo; + if (stateFullInfo) return genItemInfo(stateFullInfo); + if (!info) return false; + if (info.includes('constructName')) { + return ( +
+

{info.replace('constructName ', '')}

+

This is the name of your construct.
+ Names are randomly generated and are purely cosmetic.
+ You can change change your construct name in the RESHAPE tab outside of games. +

+
+ ); + } + + if (info.includes('constructAvatar')) { + return ( +
+

{info.replace('constructAvatar ', '')}

+

This is your construct avatar.
+ Avatars are randomly generated and are purely cosmetic.
+ You can change your construct avatar in the RESHAPE tab outside of games. +

+
+ ); + } + + 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))) + ? [
{convertItem(c.components[0])} {convertItem(c.components[1])}
, +
{convertItem(c.components[2])}
] + : c.components.map((u, j) =>
{convertItem(u)}
); + return ( +
+
+ {convertItem(c.item)} +
+ {componentTable} +
+ ); + }); + const comboList = comboTable.length > 0 ?
{comboTable}
: false; + return ( +
+
+

COMBOS

+ Combine colours and items. +
+ {comboList} +
+ ); + }; + + 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 ( +
+ +
+ ); + }; + + return ( +
+
this.setState({ comboItem: null })}> +
+ + +
+
+ +
+ ); + } +} + +module.exports = addState(InfoComponent); diff --git a/client/src/components/info.thresholds.jsx b/client/src/components/vbox.info.thresholds.jsx similarity index 94% rename from client/src/components/info.thresholds.jsx rename to client/src/components/vbox.info.thresholds.jsx index 930882bd..6785380e 100644 --- a/client/src/components/info.thresholds.jsx +++ b/client/src/components/vbox.info.thresholds.jsx @@ -89,11 +89,7 @@ function specThresholds(player, fullInfo, info) { return (
- {thresholds[0]} - {thresholds[1]} -
-
- {thresholds[2]} + {thresholds}
diff --git a/client/src/components/vbox.stash.jsx b/client/src/components/vbox.stash.jsx index bfca1d29..18dd6b78 100644 --- a/client/src/components/vbox.stash.jsx +++ b/client/src/components/vbox.stash.jsx @@ -44,7 +44,7 @@ function stashElement(props) { class={stashHighlight ? 'receiving' : 'empty'} > ; } - const comboHighlight = vboxHighlight && vboxHighlight.includes(v) ? 'combo-border' : ''; + const notValidCombo = vboxHighlight && !vboxHighlight.includes(v); function onClick(type) { const combinerContainsIndex = stashSelect.indexOf(i) > -1; @@ -56,7 +56,7 @@ function stashElement(props) { return true; } - if (!comboHighlight) { + if (notValidCombo) { setInfo(vbox.bound[i]); return setVboxSelected({ storeSelect: [], stashSelect: [i] }); } @@ -68,7 +68,9 @@ function stashElement(props) { const highlighted = stashSelect.indexOf(i) > -1; 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; diff --git a/client/src/components/vbox.store.jsx b/client/src/components/vbox.store.jsx index 20a5a402..4dfda70a 100644 --- a/client/src/components/vbox.store.jsx +++ b/client/src/components/vbox.store.jsx @@ -7,7 +7,6 @@ const shapes = require('./shapes'); function storeElement(props) { const { clearVboxSelected, - setInfo, setVboxSelected, storeSelect, stashSelect, @@ -20,11 +19,10 @@ function storeElement(props) { if (!v) return ; 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) { e.stopPropagation(); - if (!comboHighlight) setInfo(vbox.free[group][index]); if (storeSelect.length && storeSelect.some(vs => vs[0] === group && vs[1] === index)) { return setVboxSelected( { storeSelect: storeSelect.filter(vs => !(vs[0] === group && vs[1] === index)), stashSelect } @@ -34,20 +32,21 @@ function storeElement(props) { if (!storeSelect.length && !stashSelect.length) { return setVboxSelected({ storeSelect: [[group, index]], stashSelect }); } - if (comboHighlight !== 'combo-border') { + if (notValidCombo) { return setVboxSelected({ 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 disabled = vbox.bits <= group; return ( -