diff --git a/client/assets/styles/vbox.less b/client/assets/styles/vbox.less index 582899a0..963e16b4 100644 --- a/client/assets/styles/vbox.less +++ b/client/assets/styles/vbox.less @@ -3,12 +3,11 @@ grid-area: vbox; display: grid; grid-template-rows: 3fr 2fr; - grid-template-columns: 1fr 4fr 6fr min-content; // =\ + grid-template-columns: 1fr 4fr 6fr 17em; // =\ grid-template-areas: "store-hdr store info combos" "stash-hdr stash combiner combos"; margin-bottom: 1em; - line-height: 0; // immediate children > div { @@ -198,23 +197,6 @@ 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"; - - .info-item { - grid-area: item; - } - } } .combiner { diff --git a/client/src/components/vbox.combos.jsx b/client/src/components/vbox.combos.jsx new file mode 100644 index 00000000..c822af1c --- /dev/null +++ b/client/src/components/vbox.combos.jsx @@ -0,0 +1,144 @@ +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 Combos(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(); + + 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} +
+ ); +} + +module.exports = addState(Combos); diff --git a/client/src/components/vbox.component.jsx b/client/src/components/vbox.component.jsx index bf5391aa..2f808f08 100644 --- a/client/src/components/vbox.component.jsx +++ b/client/src/components/vbox.component.jsx @@ -252,7 +252,10 @@ class Vbox extends preact.Component { setInfo = {setInfo} setVboxSelected = {setVboxSelected} /> -
+ - ); } diff --git a/client/src/components/vbox.info.jsx b/client/src/components/vbox.info.jsx index f8ea6157..3936260d 100644 --- a/client/src/components/vbox.info.jsx +++ b/client/src/components/vbox.info.jsx @@ -61,255 +61,148 @@ const addState = connect( } ); -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; +function Info(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(); + + if (tutorial) { + const tutorialStageInfo = tutorialStage(tutorial, ws, setTutorialNull, instance); + if (tutorialStageInfo) return tutorialStageInfo; } - 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; + 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 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(); + const itemSource = itemInfo.combos.filter(c => c.item === removeTier(fullInfo.item)); - function Info() { - if (tutorial) { - const tutorialStageInfo = tutorialStage(tutorial, ws, setTutorialNull, instance); - if (tutorialStageInfo) return tutorialStageInfo; + 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]()); } - 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 cooldown = isSkill && fullInfo.cooldown ?
{fullInfo.cooldown} Turn delay
: null; - const itemSource = itemInfo.combos.filter(c => c.item === removeTier(fullInfo.item)); + const speed = isSkill + ?
Speed {shapes.SpeedStat()} multiplier {fullInfo.speed * 4}%
+ : null; - let itemSourceInfo = itemSource.length && !isSpec - ? `${itemSource[0].components[0]} ${itemSource[0].components[1]} ${itemSource[0].components[2]}` - : false; + const thresholds = isSpec ? specThresholds(player, fullInfo, info) : null; - 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); + return ( +
+

{infoName}

+ {header} + {itemSourceInfo} + {cooldown} + {itemDescription()} + {speed} + {thresholds} +
+ ); } - - 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 })}> -
- - -
-
- +
+

{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); } -module.exports = addState(InfoComponent); +module.exports = addState(Info);