459 lines
16 KiB
JavaScript
459 lines
16 KiB
JavaScript
const preact = require('preact');
|
|
const { connect } = require('preact-redux');
|
|
const range = require('lodash/range');
|
|
const countBy = require('lodash/countBy');
|
|
const without = require('lodash/without');
|
|
const forEach = require('lodash/forEach');
|
|
|
|
const { removeTier } = require('../utils');
|
|
const shapes = require('./shapes');
|
|
const actions = require('../actions');
|
|
const buttons = require('./buttons');
|
|
|
|
const addState = connect(
|
|
function receiveState(state) {
|
|
const {
|
|
ws,
|
|
instance,
|
|
player,
|
|
reclaiming,
|
|
vboxSelected,
|
|
itemInfo,
|
|
itemUnequip,
|
|
tutorial,
|
|
} = state;
|
|
|
|
function sendVboxDiscard() {
|
|
return ws.sendVboxDiscard(instance.id);
|
|
}
|
|
|
|
function sendVboxAccept(group, index) {
|
|
document.activeElement.blur();
|
|
return ws.sendVboxAccept(instance.id, group, index);
|
|
}
|
|
|
|
function sendVboxCombine() {
|
|
return ws.sendVboxCombine(instance.id, vboxSelected.stashSelect, vboxSelected.shopSelect);
|
|
}
|
|
|
|
function sendVboxReclaim(i) {
|
|
return ws.sendVboxReclaim(instance.id, i);
|
|
}
|
|
|
|
function sendItemUnequip([constructId, item]) {
|
|
return ws.sendVboxUnequip(instance.id, constructId, item);
|
|
}
|
|
|
|
return {
|
|
instance,
|
|
player,
|
|
reclaiming,
|
|
sendVboxAccept,
|
|
sendVboxCombine,
|
|
sendVboxDiscard,
|
|
sendVboxReclaim,
|
|
vboxSelected,
|
|
itemInfo,
|
|
itemUnequip,
|
|
sendItemUnequip,
|
|
tutorial,
|
|
};
|
|
},
|
|
|
|
function receiveDispatch(dispatch) {
|
|
function setReclaiming(v) {
|
|
dispatch(actions.setItemUnequip([]));
|
|
dispatch(actions.setVboxSelected({ shopSelect: [], stashSelect: [] }));
|
|
return dispatch(actions.setReclaiming(v));
|
|
}
|
|
|
|
function setInfo(item) {
|
|
return dispatch(actions.setInfo(item));
|
|
}
|
|
|
|
function setVboxSelected(v) {
|
|
dispatch(actions.setItemUnequip([]));
|
|
dispatch(actions.setVboxSelected(v));
|
|
return dispatch(actions.setVboxSelected(v));
|
|
}
|
|
|
|
return {
|
|
setReclaiming,
|
|
setInfo,
|
|
setVboxSelected,
|
|
};
|
|
}
|
|
);
|
|
|
|
function validVboxSelect(vbox, itemInfo, shopSelect, stashSelect) {
|
|
if (shopSelect.length === 0 && stashSelect.length === 0) return false;
|
|
|
|
const validSelects = [];
|
|
|
|
const stashItems = stashSelect.map(j => vbox.bound[j]);
|
|
const shopItems = shopSelect.map(j => vbox.free[j[0]][j[1]]);
|
|
|
|
const selectedItems = stashItems.concat(shopItems);
|
|
const itemCount = countBy(selectedItems, co => co);
|
|
|
|
itemInfo.combos.forEach(combo => {
|
|
const comboCount = countBy(combo.components, co => co);
|
|
const buyCount = countBy(combo.components, co => co);
|
|
const valid = selectedItems.every(c => {
|
|
if (!combo.components.includes(c)) return false;
|
|
if (itemCount[c] > comboCount[c]) return false;
|
|
buyCount[c] -= itemCount[c];
|
|
return true;
|
|
});
|
|
if (valid) {
|
|
forEach(buyCount, (value, key) => {
|
|
if (value > 0 && !validSelects.includes(key)) validSelects.push(key);
|
|
});
|
|
}
|
|
});
|
|
|
|
return validSelects;
|
|
}
|
|
|
|
class Vbox extends preact.Component {
|
|
shouldComponentUpdate(newProps) {
|
|
// Single variable props
|
|
if (newProps.itemUnequip !== this.props.itemUnequip) return true;
|
|
if (newProps.reclaiming !== this.props.reclaiming) return true;
|
|
if (newProps.tutorial !== this.props.tutorial) return true;
|
|
if (newProps.vboxSelected !== this.props.vboxSelected) return true;
|
|
if (newProps.player !== this.props.player) return true;
|
|
if (newProps.instance !== this.props.instance) return true;
|
|
return false;
|
|
}
|
|
|
|
render(args) {
|
|
const {
|
|
// Changing state variables
|
|
itemUnequip,
|
|
player,
|
|
reclaiming,
|
|
tutorial,
|
|
vboxSelected,
|
|
instance,
|
|
|
|
// Static
|
|
itemInfo,
|
|
// Function Calls
|
|
sendItemUnequip,
|
|
sendVboxAccept,
|
|
sendVboxCombine,
|
|
sendVboxDiscard,
|
|
sendVboxReclaim,
|
|
setVboxSelected,
|
|
setInfo,
|
|
setReclaiming,
|
|
} = args;
|
|
|
|
if (!player) return false;
|
|
const { vbox } = player;
|
|
const { shopSelect, stashSelect } = vboxSelected;
|
|
const vboxSelecting = shopSelect.length === 1 && stashSelect.length === 0;
|
|
|
|
function combinerChange(newStashSelect) {
|
|
return setVboxSelected({ shopSelect, stashSelect: newStashSelect });
|
|
}
|
|
|
|
const vboxHighlight = validVboxSelect(vbox, itemInfo, shopSelect, stashSelect);
|
|
//
|
|
// VBOX
|
|
//
|
|
function vboxHover(e, v) {
|
|
if (v) {
|
|
e.stopPropagation();
|
|
if (shopSelect.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;
|
|
}
|
|
setInfo(v);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function clearVboxSelected() {
|
|
setVboxSelected({ shopSelect: [], stashSelect: [] });
|
|
}
|
|
|
|
function vboxBuySelected() {
|
|
if (!vboxSelecting) return false;
|
|
document.activeElement.blur();
|
|
sendVboxAccept(shopSelect[0][0], shopSelect[0][1]);
|
|
return true;
|
|
}
|
|
|
|
function availableBtn(v, group, index) {
|
|
if (!v) return <button disabled class='empty' key={(group * 10) + index} > </button>;
|
|
const selected = shopSelect.length && shopSelect.some(vs => vs[0] === group && vs[1] === index);
|
|
|
|
const comboHighlight = vboxHighlight && vboxHighlight.includes(v) ? 'combo-border' : '';
|
|
|
|
function onClick(e) {
|
|
e.stopPropagation();
|
|
if (!comboHighlight) setInfo(vbox.free[group][index]);
|
|
if (shopSelect.length && shopSelect.some(vs => vs[0] === group && vs[1] === index)) {
|
|
return setVboxSelected({ shopSelect: shopSelect.filter(vs => !(vs[0] === group && vs[1] === index)), stashSelect });
|
|
}
|
|
|
|
if (!shopSelect.length && !stashSelect.length) return setVboxSelected({ shopSelect: [[group, index]], stashSelect });
|
|
if (comboHighlight !== 'combo-border') {
|
|
return setVboxSelected({ shopSelect: [[group, index]], stashSelect: [] });
|
|
}
|
|
return setVboxSelected({ shopSelect: [...shopSelect, [group, index]], stashSelect });
|
|
}
|
|
|
|
|
|
const classes = `${v.toLowerCase()} ${selected ? 'highlight' : ''} ${comboHighlight}`;
|
|
|
|
const vboxObject = shapes[v] ? shapes[v]() : v;
|
|
const disabled = vbox.bits <= group;
|
|
return (
|
|
<label draggable='true'
|
|
onDragStart={ev => ev.dataTransfer.setData('text', '')}
|
|
key={group * 10 + index}
|
|
onDragEnd={clearVboxSelected}>
|
|
<button
|
|
class={classes}
|
|
disabled={disabled}
|
|
onMouseOver={e => vboxHover(e, v)}
|
|
onMouseDown={onClick}
|
|
onClick={e => e.stopPropagation()}
|
|
> {vboxObject}
|
|
</button>
|
|
</label>
|
|
);
|
|
}
|
|
|
|
|
|
function vboxElement() {
|
|
return (
|
|
<div class='shop'
|
|
onClick={e => e.stopPropagation()}>
|
|
<div class="vbox-colours">
|
|
{range(0, 6).map(i => availableBtn(vbox.free[0][i], 0, i))}
|
|
</div>
|
|
<div class="vbox-items">
|
|
{range(0, 3).map(i => availableBtn(vbox.free[1][i], 1, i))}
|
|
{range(0, 3).map(i => availableBtn(vbox.free[2][i], 2, i))}
|
|
</div>
|
|
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function vboxHdr() {
|
|
return (
|
|
<div class="shop-hdr">
|
|
<h3
|
|
onTouchStart={e => e.target.scrollIntoView(true)}
|
|
onMouseOver={e => hoverInfo(e, 'vbox')}> VBOX
|
|
</h3>
|
|
<div class={`bits ${vbox.bits < 3 ? 'red' : false}`} onMouseOver={e => hoverInfo(e, 'bits')}>
|
|
<h1> {vbox.bits}b </h1>
|
|
</div>
|
|
<button
|
|
class='vbox-btn'
|
|
onMouseOver={e => hoverInfo(e, 'refill')}
|
|
disabled={vbox.bits < 2
|
|
|| (tutorial && tutorial < 7 && instance.time_control === 'Practice' && instance.rounds.length === 1)
|
|
}
|
|
onClick={e => e.stopPropagation()}
|
|
onMouseDown={() => sendVboxDiscard()}>
|
|
refill <br />
|
|
2b
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
//
|
|
// INVENTORY
|
|
//
|
|
|
|
function inventoryBtn(v, i) {
|
|
const inventoryHighlight = vboxSelecting || itemUnequip.length;
|
|
|
|
if (!v && v !== 0) {
|
|
const emptyInvClick = () => {
|
|
if (vboxSelecting) return vboxBuySelected();
|
|
return false;
|
|
};
|
|
return <button key={i} onClick={emptyInvClick} disabled={!inventoryHighlight} class={inventoryHighlight ? 'receiving' : 'empty'} > </button>;
|
|
}
|
|
|
|
const comboHighlight = vboxHighlight && vboxHighlight.includes(v) ? 'combo-border' : '';
|
|
|
|
function onClick(type) {
|
|
if (reclaiming) return sendVboxReclaim(i);
|
|
|
|
const combinerContainsIndex = stashSelect.indexOf(i) > -1;
|
|
// removing
|
|
if (combinerContainsIndex) {
|
|
if (type === 'click') {
|
|
return combinerChange(without(stashSelect, i));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (!comboHighlight) {
|
|
setInfo(vbox.bound[i]);
|
|
return setVboxSelected({ shopSelect: [], stashSelect: [i] });
|
|
}
|
|
|
|
stashSelect.push(i);
|
|
// if (stashSelect.length === 3) setInfo(comboItem.item);
|
|
return combinerChange(stashSelect);
|
|
}
|
|
|
|
const highlighted = stashSelect.indexOf(i) > -1;
|
|
const border = buttons[removeTier(v)] ? buttons[removeTier(v)]() : '';
|
|
const classes = `${highlighted ? 'highlight' : border} ${comboHighlight}`;
|
|
|
|
const invObject = shapes[v] ? shapes[v]() : v;
|
|
|
|
return (
|
|
<label
|
|
key={i}
|
|
draggable='true'
|
|
onDragStart={ev => {
|
|
onClick('drag');
|
|
ev.dataTransfer.setData('text', '');
|
|
}}>
|
|
<button
|
|
class={classes}
|
|
onMouseOver={e => vboxHover(e, v)}
|
|
onClick={e => e.stopPropagation()}
|
|
onMouseDown={() => onClick('click')}>
|
|
{invObject}
|
|
</button>
|
|
</label>
|
|
);
|
|
}
|
|
|
|
function combinerBtn() {
|
|
let text = '';
|
|
let mouseEvent = false;
|
|
const combineLength = stashSelect.length + shopSelect.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 = shopSelect.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;
|
|
shopSelect.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 && shopSelect.length === 1) {
|
|
const item = shopSelect[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>
|
|
);
|
|
}
|
|
|
|
function inventoryElement() {
|
|
function inventoryClick(e) {
|
|
e.stopPropagation();
|
|
if (itemUnequip.length) return sendItemUnequip(itemUnequip);
|
|
return true;
|
|
}
|
|
|
|
return (
|
|
<div
|
|
class='stash'
|
|
onMouseDown={inventoryClick}
|
|
onClick={e => e.stopPropagation()}
|
|
onDragOver={ev => ev.preventDefault()}
|
|
onDrop={inventoryClick}
|
|
>
|
|
<div class={`vbox-items ${reclaiming ? 'reclaiming' : ''}`}>
|
|
{range(0, 6).map(i => inventoryBtn(vbox.bound[i], i))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function inventoryHdr() {
|
|
return (
|
|
<div class={`stash-hdr ${reclaiming ? 'reclaiming' : ''}`}>
|
|
<h3
|
|
onTouchStart={e => e.target.scrollIntoView(true)}
|
|
onMouseOver={e => hoverInfo(e, 'inventory')}> INVENTORY
|
|
</h3>
|
|
<button
|
|
disabled={tutorial && tutorial < 8 && instance.time_control === 'Practice' && instance.rounds.length === 1}
|
|
class='vbox-btn reclaim'
|
|
onMouseOver={e => hoverInfo(e, 'reclaim')}
|
|
onClick={e => e.stopPropagation()}
|
|
onMouseDown={e => {
|
|
e.stopPropagation()
|
|
setReclaiming(!reclaiming)
|
|
}}>
|
|
refund
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
|
|
//
|
|
// EVERYTHING
|
|
//
|
|
function hoverInfo(e, newInfo) {
|
|
if (shopSelect.find(c => c[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;
|
|
}
|
|
return setInfo(newInfo);
|
|
}
|
|
|
|
return (
|
|
<div class='vbox'>
|
|
{vboxHdr()}
|
|
{vboxElement()}
|
|
{inventoryHdr()}
|
|
{inventoryElement()}
|
|
{combinerBtn()}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
module.exports = addState(Vbox);
|