Merge branch 'release/1.9.0'

This commit is contained in:
ntr 2019-11-21 11:04:51 +11:00
commit 816470e879
33 changed files with 384 additions and 312 deletions

View File

@ -1,4 +1,23 @@
## [1.8.3] - 2019-11-10 ## [1.9.0] - 2019-11-21
### Changed
- VBOX
- You can now buy and combine from the VBOX and your inventory at the same time.
- Performance
- Websocket messages are now compressed in transit.
### Added
- Concede Round
- You can now concede game rounds, coward.
## [1.8.5] - 2019-11-16
### Fixed
- Multiple ko event bug
## [1.8.4] - 2019-11-14
### Fixed
- Bug with tutorial introduced in last patch
## [1.8.3] - 2019-11-13
### Added ### Added
- Preview combos by hovering over recipes - Preview combos by hovering over recipes
- Condensed recipe display - Condensed recipe display

View File

@ -1 +1 @@
1.8.3 1.9.0

View File

@ -4,40 +4,53 @@
*PRODUCTION* *PRODUCTION*
* can't reset password without knowing password =\ * can't reset password without knowing password =\
* ws gzip encoding
* Graphics * Graphics
* Img * Img
* Skill Icons * Skill Icons
* Buttons / General UI Theming * Buttons / General UI Theming
* Front Page * Front Page
* Power / Speed / Life Icons - "Make use of piggybacking"
- Power (e.g. lightning bolt)
- Speed (e.g. boots)
- Life (e.g. heart)
* reduce inventory size and consolidate vbox and inventory on left side
* audio
* background music
* animation effects
* vbox combine / buy / equip etc
* reclaim change colour from red (clashes with red items)
* represent construct colours during game phase (coloured border?)
* supporter gold name in instance (anyone whos put any money into game)
* Give the bots some ai / make stronger so its a challenge for new people to beat
- train a few games of with some round losses to get them into the game
* Speed up animations slightly (3s per normal event too long)
- Improve combat text to start at the opposite end of construct and float towards health stats
- Show combat text for skill cast possibly? Watch some pokemans etc for modern combat smoothing
* skip faceoff on server side
## SOON ## SOON
* Graphical status effects instead of text
* Improve colour contrast / buttons
* supporter gold name in instance (anyone whos put any money into game) * buy from preview if you have the required bases in vbox / inventory
- a "buy" becomes available under the current info / preview section
- clicking the buy automatically purchases / combine items
- could also be used to upgrade already equipped skills / specs
- e.g. an equipped white power spec could be upgraded by clicking under preview
- if this was added we could reduce inventory size to 3 and rearrange vbox (see mockup img)
* change cooldowns to delay & recharge * change cooldowns to delay & recharge
- delay is cooldown before skill can first be used - delay is cooldown before skill can first be used
- recharge is cooldown after using skill - recharge is cooldown after using skill
- every x speed reduces delay of skills - every x speed reduces delay of skills
* audio
* elo + leaderboards
* reconnect based on time delta
* ACP
* essential
## LATER
* theme toasts
* rework vecs into sets
* remove names so games/instances are copy
* consolidate game and instance
* combo rework * combo rework
- reduce number of items for creating t2/t3 items from 3 -> 2 - reduce number of items for creating t2/t3 items from 3 -> 2
- add lost complexity by adding skill spec items - add lost complexity by adding skill spec items
@ -48,18 +61,46 @@
- Strike + SpeedRR -> StrikeSpeed (strike has Y% more speed) - Strike + SpeedRR -> StrikeSpeed (strike has Y% more speed)
- Strike + LifeRR -> StrikeLife (Strike recharges X% of damage as red life) - Strike + LifeRR -> StrikeLife (Strike recharges X% of damage as red life)
- Can also work as module style passive keystones
* troll life -> dmg -> Invert life spec?
* prince of peace
* bonus healing / no damage -> Heal power spec?
* fuck magic -> Some sort of reflect spec?
* empower on ko -> Amplify + Power spec
* elo + leaderboards
* reconnect based on time delta
* ACP
* essential
## LATER
* Graphical status effects instead of text
* theme toasts
* rework vecs into sets
* constructs
* effects
* parent
* children
* remove names so games/instances are copy
* consolidate game and instance
* Energy generators / spenders
- Current skills generate team wide red / blue / green energy
- New special skill base consumes team energy to do "special" powerful moves
- Working as cooldowns in reverse by building up to the skill rather than waiting
* constants * constants
* (maybe) return of the combat log (last few events with condensed descriptions) * (maybe) return of the combat log (last few events with condensed descriptions)
- click in to scroll - click in to scroll
* mnml tv * mnml tv
* modules
* troll life -> dmg
* prince of peace
* bonus healing / no damage
* fuck magic
* empower on ko
## $$$ ## $$$
* Items * Items

View File

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

View File

@ -162,6 +162,23 @@
} }
.construct-list { .construct-list {
button {
&.highlight {
color: black;
background: @white;
// border: 1px solid @white; (this bangs around the vbox)
// overwrite the classes on white svg elements
svg {
stroke-width: 0.75em;
}
.white {
stroke: black;
}
}
}
.name { .name {
grid-area: name; grid-area: name;
margin-bottom: 0.5em; margin-bottom: 0.5em;

View File

@ -188,19 +188,6 @@ input[type="button"] {
border: 1px solid #bbb; border: 1px solid #bbb;
cursor: pointer; cursor: pointer;
box-sizing: border-box; } box-sizing: border-box; }
.button:hover,
button:hover,
input[type="submit"]:hover,
input[type="reset"]:hover,
input[type="button"]:hover,
.button:focus,
button:focus,
input[type="submit"]:focus,
input[type="reset"]:focus,
input[type="button"]:focus {
color: #333;
border-color: #888;
outline: 0; }
.button.button-primary, .button.button-primary,
button.button-primary, button.button-primary,
input[type="submit"].button-primary, input[type="submit"].button-primary,

View File

@ -147,8 +147,8 @@ button, input {
&:focus { &:focus {
/*colour necesary to bash skellington*/ /*colour necesary to bash skellington*/
color: @gray-focus;
border-color: @gray-focus; outline: 0;
} }
} }

View File

@ -731,7 +731,21 @@ pre {
} }
} }
</style><title>CHANGELOG</title></head><body><article class="markdown-body"><h2> </style><title>CHANGELOG</title></head><body><article class="markdown-body"><h2>
<a id="user-content-183---2019-11-10" class="anchor" href="#183---2019-11-10" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.8.3] - 2019-11-10</h2> <a id="user-content-185---2019-11-16" class="anchor" href="#185---2019-11-16" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.8.5] - 2019-11-16</h2>
<h3>
<a id="user-content-fixed" class="anchor" href="#fixed" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Fixed</h3>
<ul>
<li>Multiple ko event bug</li>
</ul>
<h2>
<a id="user-content-184---2019-11-14" class="anchor" href="#184---2019-11-14" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.8.4] - 2019-11-14</h2>
<h3>
<a id="user-content-fixed-1" class="anchor" href="#fixed-1" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Fixed</h3>
<ul>
<li>Bug with tutorial introduced in last patch</li>
</ul>
<h2>
<a id="user-content-183---2019-11-13" class="anchor" href="#183---2019-11-13" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.8.3] - 2019-11-13</h2>
<h3> <h3>
<a id="user-content-added" class="anchor" href="#added" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Added</h3> <a id="user-content-added" class="anchor" href="#added" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Added</h3>
<ul> <ul>
@ -739,7 +753,7 @@ pre {
<li>Condensed recipe display</li> <li>Condensed recipe display</li>
</ul> </ul>
<h3> <h3>
<a id="user-content-fixed" class="anchor" href="#fixed" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Fixed</h3> <a id="user-content-fixed-2" class="anchor" href="#fixed-2" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Fixed</h3>
<ul> <ul>
<li>Construct display on info / demo page</li> <li>Construct display on info / demo page</li>
<li>Case where a skill could send multiple ko events to target</li> <li>Case where a skill could send multiple ko events to target</li>
@ -754,7 +768,7 @@ pre {
<li>Purify <li>Purify
<ul> <ul>
<li>Now removes all effects on target</li> <li>Now removes all effects on target</li>
<li>Applies "Purified" increasing healing taken</li> <li>Applies "Pure" increasing healing taken</li>
</ul> </ul>
</li> </li>
<li>Purge <li>Purge
@ -766,14 +780,14 @@ pre {
<h2> <h2>
<a id="user-content-182---2019-11-10" class="anchor" href="#182---2019-11-10" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.8.2] - 2019-11-10</h2> <a id="user-content-182---2019-11-10" class="anchor" href="#182---2019-11-10" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.8.2] - 2019-11-10</h2>
<h3> <h3>
<a id="user-content-fixed-1" class="anchor" href="#fixed-1" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Fixed</h3> <a id="user-content-fixed-3" class="anchor" href="#fixed-3" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Fixed</h3>
<ul> <ul>
<li>Duplicate button issue in reshape tab</li> <li>Duplicate button issue in reshape tab</li>
</ul> </ul>
<h2> <h2>
<a id="user-content-181---2019-11-09" class="anchor" href="#181---2019-11-09" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.8.1] - 2019-11-09</h2> <a id="user-content-181---2019-11-09" class="anchor" href="#181---2019-11-09" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.8.1] - 2019-11-09</h2>
<h3> <h3>
<a id="user-content-fixed-2" class="anchor" href="#fixed-2" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Fixed</h3> <a id="user-content-fixed-4" class="anchor" href="#fixed-4" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Fixed</h3>
<ul> <ul>
<li>An issue where skills would not be put on cooldown after being used.</li> <li>An issue where skills would not be put on cooldown after being used.</li>
</ul> </ul>
@ -1070,7 +1084,7 @@ pre {
<h2> <h2>
<a id="user-content-165---2019-10-25" class="anchor" href="#165---2019-10-25" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.6.5] - 2019-10-25</h2> <a id="user-content-165---2019-10-25" class="anchor" href="#165---2019-10-25" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.6.5] - 2019-10-25</h2>
<h3> <h3>
<a id="user-content-fixed-3" class="anchor" href="#fixed-3" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Fixed</h3> <a id="user-content-fixed-5" class="anchor" href="#fixed-5" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Fixed</h3>
<ul> <ul>
<li>Stripe being blocked no longer causes unrecoverable error</li> <li>Stripe being blocked no longer causes unrecoverable error</li>
<li>Automatic ready up is now throttled after abandons</li> <li>Automatic ready up is now throttled after abandons</li>
@ -1102,7 +1116,7 @@ pre {
</li> </li>
</ul> </ul>
<h3> <h3>
<a id="user-content-fixed-4" class="anchor" href="#fixed-4" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Fixed</h3> <a id="user-content-fixed-6" class="anchor" href="#fixed-6" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Fixed</h3>
<ul> <ul>
<li>Fixed issue where dots / hots would not trigger when reapplied at a higher speed</li> <li>Fixed issue where dots / hots would not trigger when reapplied at a higher speed</li>
<li>Changed layout of home page. UI elements for rerolling construct avatars moved to a separate tab.</li> <li>Changed layout of home page. UI elements for rerolling construct avatars moved to a separate tab.</li>
@ -1113,7 +1127,7 @@ pre {
<h2> <h2>
<a id="user-content-162---2019-10-20" class="anchor" href="#162---2019-10-20" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.6.2] - 2019-10-20</h2> <a id="user-content-162---2019-10-20" class="anchor" href="#162---2019-10-20" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.6.2] - 2019-10-20</h2>
<h3> <h3>
<a id="user-content-fixed-5" class="anchor" href="#fixed-5" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Fixed</h3> <a id="user-content-fixed-7" class="anchor" href="#fixed-7" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Fixed</h3>
<ul> <ul>
<li>Combiner bug where it would preview items for different combinations</li> <li>Combiner bug where it would preview items for different combinations</li>
<li>Skill descriptions are cleared when animations start</li> <li>Skill descriptions are cleared when animations start</li>
@ -1152,7 +1166,7 @@ pre {
</li> </li>
</ul> </ul>
<h3> <h3>
<a id="user-content-fixed-6" class="anchor" href="#fixed-6" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Fixed</h3> <a id="user-content-fixed-8" class="anchor" href="#fixed-8" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Fixed</h3>
<ul> <ul>
<li>Matchmaking bug where server matches you with yourself</li> <li>Matchmaking bug where server matches you with yourself</li>
</ul> </ul>

View File

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

View File

@ -16,7 +16,6 @@ export const setInstanceChat = value => ({ type: 'SET_INSTANCE_CHAT', value });
export const setActiveItem = value => ({ type: 'SET_ACTIVE_VAR', value }); export const setActiveItem = value => ({ type: 'SET_ACTIVE_VAR', value });
export const setActiveSkill = (constructId, skill) => ({ type: 'SET_ACTIVE_SKILL', value: constructId ? { constructId, skill } : null }); export const setActiveSkill = (constructId, skill) => ({ type: 'SET_ACTIVE_SKILL', value: constructId ? { constructId, skill } : null });
export const setCombiner = value => ({ type: 'SET_COMBINER', value: Array.from(value) });
export const setConstructEditId = value => ({ type: 'SET_CONSTRUCT_EDIT_ID', value }); export const setConstructEditId = value => ({ type: 'SET_CONSTRUCT_EDIT_ID', value });
export const setConstructs = value => ({ type: 'SET_CONSTRUCTS', value }); export const setConstructs = value => ({ type: 'SET_CONSTRUCTS', value });
export const setConstructRename = value => ({ type: 'SET_CONSTRUCT_RENAME', value }); export const setConstructRename = value => ({ type: 'SET_CONSTRUCT_RENAME', value });
@ -30,7 +29,6 @@ export const setEmail = value => ({ type: 'SET_EMAIL', value });
export const setInvite = value => ({ type: 'SET_INVITE', value }); export const setInvite = value => ({ type: 'SET_INVITE', value });
export const setInstance = value => ({ type: 'SET_INSTANCE', value }); export const setInstance = value => ({ type: 'SET_INSTANCE', value });
export const setInstances = value => ({ type: 'SET_INSTANCES', value }); export const setInstances = value => ({ type: 'SET_INSTANCES', value });
export const setItemEquip = value => ({ type: 'SET_ITEM_EQUIP', value });
export const setItemInfo = value => ({ type: 'SET_ITEM_INFO', value }); export const setItemInfo = value => ({ type: 'SET_ITEM_INFO', value });
export const setItemUnequip = value => ({ type: 'SET_ITEM_UNEQUIP', value }); export const setItemUnequip = value => ({ type: 'SET_ITEM_UNEQUIP', value });
export const setMtxActive = value => ({ type: 'SET_MTX_ACTIVE', value }); export const setMtxActive = value => ({ type: 'SET_MTX_ACTIVE', value });

View File

@ -33,11 +33,9 @@ const addState = connect(
function accountPage() { function accountPage() {
dispatch(actions.setGame(null)); dispatch(actions.setGame(null));
dispatch(actions.setInstance(null)); dispatch(actions.setInstance(null));
dispatch(actions.setCombiner([]));
dispatch(actions.setReclaiming(false)); dispatch(actions.setReclaiming(false));
dispatch(actions.setActiveSkill(null)); dispatch(actions.setActiveSkill(null));
dispatch(actions.setInfo(null)); dispatch(actions.setInfo(null));
dispatch(actions.setItemEquip(null));
dispatch(actions.setItemUnequip([])); dispatch(actions.setItemUnequip([]));
dispatch(actions.setVboxHighlight([])); dispatch(actions.setVboxHighlight([]));
return dispatch(actions.setNav('account')); return dispatch(actions.setNav('account'));

View File

@ -35,7 +35,7 @@ class ConstructAvatar extends Component {
class="avatar" class="avatar"
id={construct.id} id={construct.id}
onMouseDown={this.onClick.bind(this)} onMouseDown={this.onClick.bind(this)}
onMouseOver={mouseOver ? mouseOver : null} onMouseOver={mouseOver}
style={{ 'background-image': `url(/imgs/${construct.img}.svg)` }}> style={{ 'background-image': `url(/imgs/${construct.img}.svg)` }}>
<ConstructAnimation construct={construct} /> <ConstructAnimation construct={construct} />
</div> </div>
@ -86,11 +86,12 @@ class ConstructAvatar extends Component {
shouldComponentUpdate(newProps) { shouldComponentUpdate(newProps) {
const { animSource, animTarget, animText, construct } = newProps; const { animSource, animTarget, animText, construct, mouseOver } = newProps;
if (animSource !== this.props.animSource) return true; if (animSource !== this.props.animSource) return true;
if (animTarget !== this.props.animTarget) return true; if (animTarget !== this.props.animTarget) return true;
if (animText !== this.props.animText) return true; if (animText !== this.props.animText) return true;
if (construct !== this.props.construct) return true; if (construct !== this.props.construct) return true;
if (mouseOver !== this.props.mouseOver) return true;
return false; return false;
} }
} }

View File

@ -20,12 +20,17 @@ const addState = connect(
return ws.sendGameOfferDraw(game.id); return ws.sendGameOfferDraw(game.id);
} }
function sendConcede() {
return ws.sendGameConcede(game.id);
}
return { return {
game, game,
account, account,
sendAbandon, sendAbandon,
sendDraw, sendDraw,
sendConcede,
animating, animating,
}; };
}, },
@ -49,11 +54,12 @@ function GameCtrlTopBtns(args) {
leave, leave,
sendAbandon, sendAbandon,
sendDraw, sendDraw,
sendConcede,
animating, animating,
} = args; } = args;
const finished = game && game.phase === 'Finished'; const finished = game && game.phase === 'Finished';
const { abandonState, drawState } = this.state; const { abandonState, drawState, concedeState } = this.state;
const player = game.players.find(p => p.id === account.id); const player = game.players.find(p => p.id === account.id);
const drawOffered = player && player.draw_offered; const drawOffered = player && player.draw_offered;
@ -70,6 +76,12 @@ function GameCtrlTopBtns(args) {
setTimeout(() => this.setState({ drawState: false }), 2000); setTimeout(() => this.setState({ drawState: false }), 2000);
}; };
const concedeStateTrue = e => {
e.stopPropagation();
this.setState({ concedeState: true });
setTimeout(() => this.setState({ concedeState: false }), 2000);
};
const abandonClasses = `abandon ${abandonState ? 'confirming' : ''}`; const abandonClasses = `abandon ${abandonState ? 'confirming' : ''}`;
const abandonText = abandonState ? 'Confirm' : 'Abandon'; const abandonText = abandonState ? 'Confirm' : 'Abandon';
const abandonAction = abandonState ? sendAbandon : abandonStateTrue; const abandonAction = abandonState ? sendAbandon : abandonStateTrue;
@ -83,9 +95,15 @@ function GameCtrlTopBtns(args) {
const drawAction = drawState ? sendDraw : drawStateTrue; const drawAction = drawState ? sendDraw : drawStateTrue;
const drawBtn = <button class={drawClasses} disabled={finished || animating || drawOffered} onClick={drawAction}>{drawText}</button>; const drawBtn = <button class={drawClasses} disabled={finished || animating || drawOffered} onClick={drawAction}>{drawText}</button>;
const concedeClasses = `draw ${concedeState ? 'confirming' : ''}`;
const concedeText = concedeState ? 'Round' : 'Concede';
const concedeAction = concedeState ? sendConcede : concedeStateTrue;
const concedeBtn = <button class={concedeClasses} disabled={finished || animating } onClick={concedeAction}>{concedeText}</button>;
return ( return (
<div class="instance-ctrl-btns"> <div class="instance-ctrl-btns">
{abandonBtn} {abandonBtn}
{concedeBtn}
{drawBtn} {drawBtn}
</div> </div>
); );

View File

@ -32,11 +32,9 @@ const addState = connect(
function setNav(place) { function setNav(place) {
dispatch(actions.setGame(null)); dispatch(actions.setGame(null));
dispatch(actions.setInstance(null)); dispatch(actions.setInstance(null));
dispatch(actions.setCombiner([]));
dispatch(actions.setReclaiming(false)); dispatch(actions.setReclaiming(false));
dispatch(actions.setActiveSkill(null)); dispatch(actions.setActiveSkill(null));
dispatch(actions.setInfo(null)); dispatch(actions.setInfo(null));
dispatch(actions.setItemEquip(null));
dispatch(actions.setItemUnequip([])); dispatch(actions.setItemUnequip([]));
dispatch(actions.setVboxHighlight([])); dispatch(actions.setVboxHighlight([]));
dispatch(actions.setMtxActive(null)); dispatch(actions.setMtxActive(null));

View File

@ -7,7 +7,6 @@ const addState = connect(
function receiveState(state) { function receiveState(state) {
const { const {
ws, ws,
combiner,
info, info,
itemInfo, itemInfo,
instance, instance,
@ -18,7 +17,6 @@ const addState = connect(
return { return {
ws, ws,
combiner,
info, info,
itemInfo, itemInfo,
instance, instance,

View File

@ -27,12 +27,10 @@ const addState = connect(
function clearItems() { function clearItems() {
dispatch(actions.setCombiner([]));
dispatch(actions.setReclaiming(false)); dispatch(actions.setReclaiming(false));
dispatch(actions.setItemEquip(null));
dispatch(actions.setItemUnequip([])); dispatch(actions.setItemUnequip([]));
dispatch(actions.setVboxHighlight([])); dispatch(actions.setVboxHighlight([]));
dispatch(actions.setVboxSelected([])); dispatch(actions.setVboxSelected({ shopSelect: [], stashSelect: [] }));
return true; return true;
} }

View File

@ -19,24 +19,19 @@ const addState = connect(
player, player,
account, account,
itemInfo, itemInfo,
itemEquip,
itemUnequip, itemUnequip,
vboxSelected, vboxSelected,
tutorial, tutorial,
} = state; } = state;
function sendVboxAcceptEquip(constructId) { function sendVboxAcceptEquip(constructId) {
return ws.sendVboxAcceptEquip(instance.id, vboxSelected[0], vboxSelected[1], constructId); return ws.sendVboxAcceptEquip(instance.id, vboxSelected.shopSelect[0][0], vboxSelected.shopSelect[0][1], constructId);
} }
function sendVboxApply(constructId, i) { function sendVboxApply(constructId, i) {
return ws.sendVboxApply(instance.id, constructId, i); return ws.sendVboxApply(instance.id, constructId, i);
} }
function sendUnequip(constructId, item) {
return ws.sendVboxUnequip(instance.id, constructId, item);
}
function sendVboxUnequipApply(targetConstructId) { function sendVboxUnequipApply(targetConstructId) {
return ws.sendVboxUnequipApply(instance.id, itemUnequip[0], itemUnequip[1], targetConstructId); return ws.sendVboxUnequipApply(instance.id, itemUnequip[0], itemUnequip[1], targetConstructId);
} }
@ -49,9 +44,7 @@ const addState = connect(
sendVboxUnequipApply, sendVboxUnequipApply,
sendVboxApply, sendVboxApply,
itemInfo, itemInfo,
itemEquip,
itemUnequip, itemUnequip,
sendUnequip,
vboxSelected, vboxSelected,
tutorial, tutorial,
}; };
@ -66,15 +59,12 @@ const addState = connect(
dispatch(actions.setInfo(item)); dispatch(actions.setInfo(item));
} }
function setItemEquip(v) {
return dispatch(actions.setItemEquip(v));
}
function setItemUnequip(v) { function setItemUnequip(v) {
dispatch(actions.setVboxSelected({ shopSelect: [], stashSelect: [] }));
return dispatch(actions.setItemUnequip(v)); return dispatch(actions.setItemUnequip(v));
} }
return { quit, setInfo, setItemUnequip, setItemEquip }; return { quit, setInfo, setItemUnequip };
} }
); );
@ -84,7 +74,6 @@ function Construct(props) {
// Changing state variables // Changing state variables
construct, construct,
iter, iter,
itemEquip,
itemUnequip, itemUnequip,
instance, instance,
player, player,
@ -96,13 +85,16 @@ function Construct(props) {
sendVboxApply, sendVboxApply,
sendVboxAcceptEquip, sendVboxAcceptEquip,
sendVboxUnequipApply, sendVboxUnequipApply,
sendUnequip,
setItemUnequip, setItemUnequip,
setItemEquip,
setInfo, setInfo,
} = props; } = props;
const { vbox } = player; const { vbox } = player;
const itemEquip = vboxSelected.shopSelect.length === 0 && vboxSelected.stashSelect.length === 1
? vboxSelected.stashSelect[0]
: -1;
const duplicateSkill = construct.skills.length !== 0 && construct.skills.every(sk => { const duplicateSkill = construct.skills.length !== 0 && construct.skills.every(sk => {
if (!itemEquip && itemEquip !== 0) return false; if (!itemEquip && itemEquip !== 0) return false;
if (!sk) return false; if (!sk) return false;
@ -113,17 +105,16 @@ function Construct(props) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
if (duplicateSkill || tutorialDisableEquip) return true; if (duplicateSkill || tutorialDisableEquip) return true;
if (itemEquip !== null) return sendVboxApply(construct.id, itemEquip); if (itemEquip !== -1) return sendVboxApply(construct.id, itemEquip);
if (vboxSelected[0]) return sendVboxAcceptEquip(construct.id); if (vboxSelected.shopSelect.length === 1) return sendVboxAcceptEquip(construct.id);
if (itemUnequip.length && itemUnequip[0] !== construct.id) return sendVboxUnequipApply(construct.id); if (itemUnequip.length && itemUnequip[0] !== construct.id) return sendVboxUnequipApply(construct.id);
setItemEquip(null);
setItemUnequip([]); setItemUnequip([]);
return true; return true;
} }
function hoverInfo(e, info) { function hoverInfo(e, info) {
if (!info) return false;
e.stopPropagation(); e.stopPropagation();
if (!info) return false;
if (vboxSelected.shopSelect.length || vboxSelected.stashSelect.length) return false;
return setInfo(info); return setInfo(info);
} }
@ -138,20 +129,11 @@ function Construct(props) {
function skillClick(e) { function skillClick(e) {
if (!skill) return false; if (!skill) return false;
setItemUnequip([construct.id, skill.skill]); setItemUnequip([construct.id, skill.skill, i]);
e.stopPropagation(); e.stopPropagation();
return true; return true;
} }
function skillDblClick(e) {
if (!skill) return false;
sendUnequip(construct.id, skill.skill);
setItemUnequip([]);
e.stopPropagation();
e.preventDefault();
return true;
}
const equipping = skillList.includes(vbox.bound[itemEquip]) && !skill const equipping = skillList.includes(vbox.bound[itemEquip]) && !skill
&& !tutorialDisableEquip && !duplicateSkill && i === construct.skills.length; && !tutorialDisableEquip && !duplicateSkill && i === construct.skills.length;
const border = () => { const border = () => {
@ -161,7 +143,9 @@ function Construct(props) {
return borderFn(); return borderFn();
}; };
const classes = `${equipping ? 'equipping' : ''} ${!skill ? 'empty' : ''} ${border()}`; const highlight = itemUnequip[0] === construct.id && itemUnequip[1] === s ? 'highlight' : '';
const classes = `${highlight} ${equipping ? 'equipping' : ''} ${!skill ? 'empty' : ''} ${border()}`;
return ( return (
<label onDragStart={ev => { <label onDragStart={ev => {
ev.dataTransfer.setData('text', ''); ev.dataTransfer.setData('text', '');
@ -172,7 +156,6 @@ function Construct(props) {
disabled={!skill && !equipping} disabled={!skill && !equipping}
class={classes} class={classes}
onClick={skillClick} onClick={skillClick}
onDblClick={skillDblClick}
onMouseOver={e => hoverInfo(e, skill && skill.skill)} > onMouseOver={e => hoverInfo(e, skill && skill.skill)} >
{s} {s}
</button> </button>
@ -195,18 +178,10 @@ function Construct(props) {
function specClick(e) { function specClick(e) {
e.stopPropagation(); e.stopPropagation();
setItemUnequip([construct.id, s]); setItemUnequip([construct.id, s, i]);
} }
function specDblClick(e) { const highlight = itemUnequip[0] === construct.id && itemUnequip[1] === s && i === itemUnequip[2];
sendUnequip(construct.id, s);
setItemUnequip([]);
e.stopPropagation();
e.preventDefault();
return true;
}
return ( return (
<label onDragStart={ev => { <label onDragStart={ev => {
ev.dataTransfer.setData('text', ''); ev.dataTransfer.setData('text', '');
@ -214,8 +189,8 @@ function Construct(props) {
}} key={i} draggable="true" onDragEnd={() => setItemUnequip([])}> }} key={i} draggable="true" onDragEnd={() => setItemUnequip([])}>
<button <button
key={i} key={i}
class={`${highlight ? 'highlight' : ''}`}
onClick={specClick} onClick={specClick}
onDblClick={specDblClick}
onMouseOver={e => hoverInfo(e, s)} > onMouseOver={e => hoverInfo(e, s)} >
{shapes[s]()} {shapes[s]()}
</button> </button>
@ -259,7 +234,6 @@ function Construct(props) {
class InstanceConstructs extends preact.Component { class InstanceConstructs extends preact.Component {
shouldComponentUpdate(newProps) { shouldComponentUpdate(newProps) {
if (newProps.itemEquip !== this.props.itemEquip) return true;
if (newProps.itemUnequip !== this.props.itemUnequip) return true; if (newProps.itemUnequip !== this.props.itemUnequip) return true;
if (newProps.tutorial !== this.props.tutorial) return true; if (newProps.tutorial !== this.props.tutorial) return true;
// JSON or Array objects // JSON or Array objects
@ -272,7 +246,6 @@ class InstanceConstructs extends preact.Component {
render(props) { render(props) {
const { const {
// Changing state variables // Changing state variables
itemEquip,
itemUnequip, itemUnequip,
instance, instance,
player, player,
@ -287,8 +260,6 @@ class InstanceConstructs extends preact.Component {
sendVboxUnequipApply, sendVboxUnequipApply,
setVboxHighlight, setVboxHighlight,
setItemUnequip, setItemUnequip,
setItemEquip,
sendUnequip,
} = props; } = props;
if (!player) return false; if (!player) return false;
@ -301,11 +272,9 @@ class InstanceConstructs extends preact.Component {
return Construct({ return Construct({
iter: i, iter: i,
construct: player.constructs[i], construct: player.constructs[i],
itemEquip,
itemUnequip, itemUnequip,
instance, instance,
setItemUnequip, setItemUnequip,
setItemEquip,
player, player,
sendVboxApply, sendVboxApply,
sendVboxAcceptEquip, sendVboxAcceptEquip,
@ -313,7 +282,6 @@ class InstanceConstructs extends preact.Component {
setInfo, setInfo,
itemInfo, itemInfo,
setVboxHighlight, setVboxHighlight,
sendUnequip,
vboxSelected, vboxSelected,
tutorial, tutorial,
}); });

View File

@ -33,11 +33,9 @@ const addState = connect(
function setNav(place) { function setNav(place) {
dispatch(actions.setGame(null)); dispatch(actions.setGame(null));
dispatch(actions.setInstance(null)); dispatch(actions.setInstance(null));
dispatch(actions.setCombiner([]));
dispatch(actions.setReclaiming(false)); dispatch(actions.setReclaiming(false));
dispatch(actions.setActiveSkill(null)); dispatch(actions.setActiveSkill(null));
dispatch(actions.setInfo(null)); dispatch(actions.setInfo(null));
dispatch(actions.setItemEquip(null));
dispatch(actions.setItemUnequip([])); dispatch(actions.setItemUnequip([]));
dispatch(actions.setVboxHighlight([])); dispatch(actions.setVboxHighlight([]));

View File

@ -3,6 +3,7 @@ const { connect } = require('preact-redux');
const range = require('lodash/range'); const range = require('lodash/range');
const countBy = require('lodash/countBy'); const countBy = require('lodash/countBy');
const without = require('lodash/without'); const without = require('lodash/without');
const forEach = require('lodash/forEach');
const { removeTier } = require('../utils'); const { removeTier } = require('../utils');
const shapes = require('./shapes'); const shapes = require('./shapes');
@ -15,7 +16,6 @@ const addState = connect(
ws, ws,
instance, instance,
player, player,
combiner,
reclaiming, reclaiming,
vboxSelected, vboxSelected,
itemInfo, itemInfo,
@ -33,7 +33,7 @@ const addState = connect(
} }
function sendVboxCombine() { function sendVboxCombine() {
return ws.sendVboxCombine(instance.id, combiner); return ws.sendVboxCombine(instance.id, vboxSelected.stashSelect, vboxSelected.shopSelect);
} }
function sendVboxReclaim(i) { function sendVboxReclaim(i) {
@ -45,7 +45,6 @@ const addState = connect(
} }
return { return {
combiner,
instance, instance,
player, player,
reclaiming, reclaiming,
@ -62,11 +61,9 @@ const addState = connect(
}, },
function receiveDispatch(dispatch) { function receiveDispatch(dispatch) {
function setCombiner(c) {
return dispatch(actions.setCombiner(c));
}
function setReclaiming(v) { function setReclaiming(v) {
dispatch(actions.setItemUnequip([]));
dispatch(actions.setVboxSelected({ shopSelect: [], stashSelect: [] }));
return dispatch(actions.setReclaiming(v)); return dispatch(actions.setReclaiming(v));
} }
@ -75,28 +72,52 @@ const addState = connect(
} }
function setVboxSelected(v) { function setVboxSelected(v) {
dispatch(actions.setItemUnequip([]));
dispatch(actions.setVboxSelected(v));
return dispatch(actions.setVboxSelected(v)); return dispatch(actions.setVboxSelected(v));
} }
function setItemEquip(v) {
return dispatch(actions.setItemEquip(v));
}
return { return {
setCombiner,
setReclaiming, setReclaiming,
setInfo, setInfo,
setVboxSelected, setVboxSelected,
setItemEquip,
}; };
} }
); );
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 { class Vbox extends preact.Component {
shouldComponentUpdate(newProps) { shouldComponentUpdate(newProps) {
// Single variable props // Single variable props
if (newProps.combiner !== this.props.combiner) return true;
if (newProps.itemUnequip !== this.props.itemUnequip) return true; if (newProps.itemUnequip !== this.props.itemUnequip) return true;
if (newProps.reclaiming !== this.props.reclaiming) return true; if (newProps.reclaiming !== this.props.reclaiming) return true;
if (newProps.tutorial !== this.props.tutorial) return true; if (newProps.tutorial !== this.props.tutorial) return true;
@ -109,7 +130,6 @@ class Vbox extends preact.Component {
render(args) { render(args) {
const { const {
// Changing state variables // Changing state variables
combiner,
itemUnequip, itemUnequip,
player, player,
reclaiming, reclaiming,
@ -126,37 +146,29 @@ class Vbox extends preact.Component {
sendVboxDiscard, sendVboxDiscard,
sendVboxReclaim, sendVboxReclaim,
setVboxSelected, setVboxSelected,
setItemEquip,
setInfo, setInfo,
setCombiner,
setReclaiming, setReclaiming,
} = args; } = args;
if (!player) return false; if (!player) return false;
const { vbox } = player; const { vbox } = player;
const vboxSelecting = vboxSelected.length; const { shopSelect, stashSelect } = vboxSelected;
const vboxSelecting = shopSelect.length === 1 && stashSelect.length === 0;
function combinerChange(newCombiner) { function combinerChange(newStashSelect) {
setCombiner(newCombiner); return setVboxSelected({ shopSelect, stashSelect: newStashSelect });
if (newCombiner.length === 1) {
setItemEquip(newCombiner[0]);
} else {
setItemEquip(null);
}
return true;
} }
const vboxHighlight = validVboxSelect(vbox, itemInfo, shopSelect, stashSelect);
// //
// VBOX // VBOX
// //
function vboxHover(e, v) { function vboxHover(e, v) {
if (v) { if (v) {
e.stopPropagation(); e.stopPropagation();
if (vboxSelected[0]) return true; // There is a base skill or spec selected in the vbox if (shopSelect.find(c => c[0])) return true; // There is a base skill or spec selected in the vbox
if (combiner.length !== 0) { if (stashSelect.length !== 0) {
const base = combiner.find(c => !['Red', 'Blue', 'Green'].includes(vbox.bound[c])); const base = stashSelect.find(c => !['Red', 'Blue', 'Green'].includes(vbox.bound[c]));
if (base || base === 0) return true; if (base || base === 0) return true;
} }
setInfo(v); setInfo(v);
@ -165,50 +177,36 @@ class Vbox extends preact.Component {
} }
function clearVboxSelected() { function clearVboxSelected() {
setVboxSelected([]); setVboxSelected({ shopSelect: [], stashSelect: [] });
} }
function vboxBuySelected() { function vboxBuySelected() {
if (!vboxSelecting) return false; if (!vboxSelecting) return false;
document.activeElement.blur(); document.activeElement.blur();
clearVboxSelected(); sendVboxAccept(shopSelect[0][0], shopSelect[0][1]);
sendVboxAccept(vboxSelected[0], vboxSelected[1]);
return true; return true;
} }
function availableBtn(v, group, index) { function availableBtn(v, group, index) {
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 = vboxSelected[0] === group && vboxSelected[1] === index; const selected = shopSelect.length && shopSelect.some(vs => vs[0] === group && vs[1] === index);
// state not yet set in double click handler const comboHighlight = vboxHighlight && vboxHighlight.includes(v) ? 'combo-border' : '';
function onDblClick(e) {
clearVboxSelected();
sendVboxAccept(group, index);
e.stopPropagation();
}
function onClick(e) { function onClick(e) {
e.stopPropagation(); e.stopPropagation();
setItemEquip(null); if (!comboHighlight) setInfo(vbox.free[group][index]);
setCombiner([]); if (shopSelect.length && shopSelect.some(vs => vs[0] === group && vs[1] === index)) {
setInfo(vbox.free[group][index]); return setVboxSelected({ shopSelect: shopSelect.filter(vs => !(vs[0] === group && vs[1] === index)), stashSelect });
return setVboxSelected([group, index]); }
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 combinerItems = combiner.map(j => vbox.bound[j]);
const combinerCount = countBy(combinerItems, co => co);
const comboHighlight = combinerItems.length > 0 && itemInfo.combos.some(combo => {
if (combo.components.includes(v)) {
return combinerItems.every(c => {
if (!combo.components.includes(c)) return false;
const comboCount = countBy(combo.components, co => co);
if (combinerCount[c] > comboCount[c]) return false;
if (c === v && combinerCount[c] + 1 > comboCount[c]) return false;
return true;
});
} return false;
}) ? 'combo-border' : '';
const classes = `${v.toLowerCase()} ${selected ? 'highlight' : ''} ${comboHighlight}`; const classes = `${v.toLowerCase()} ${selected ? 'highlight' : ''} ${comboHighlight}`;
@ -216,10 +214,7 @@ class Vbox extends preact.Component {
const disabled = vbox.bits <= group; const disabled = vbox.bits <= group;
return ( return (
<label draggable='true' <label draggable='true'
onDragStart={ev => { onDragStart={ev => ev.dataTransfer.setData('text', '')}
onClick(ev);
ev.dataTransfer.setData('text', '')
}}
key={group * 10 + index} key={group * 10 + index}
onDragEnd={clearVboxSelected}> onDragEnd={clearVboxSelected}>
<button <button
@ -228,7 +223,6 @@ class Vbox extends preact.Component {
onMouseOver={e => vboxHover(e, v)} onMouseOver={e => vboxHover(e, v)}
onMouseDown={onClick} onMouseDown={onClick}
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
onDblClick={onDblClick}
> {vboxObject} > {vboxObject}
</button> </button>
</label> </label>
@ -239,7 +233,6 @@ class Vbox extends preact.Component {
function vboxElement() { function vboxElement() {
return ( return (
<div class='vbox-vbox' <div class='vbox-vbox'
onMouseDown={() => setReclaiming(false)}
onClick={e => e.stopPropagation()}> onClick={e => e.stopPropagation()}>
<div class="vbox-hdr"> <div class="vbox-hdr">
<h3 <h3
@ -258,7 +251,9 @@ class Vbox extends preact.Component {
<button <button
class='vbox-btn' class='vbox-btn'
onMouseOver={e => hoverInfo(e, 'refill')} onMouseOver={e => hoverInfo(e, 'refill')}
disabled={tutorial && tutorial < 7 && instance.time_control === 'Practice' && instance.rounds.length === 1} disabled={vbox.bits < 2
|| (tutorial && tutorial < 7 && instance.time_control === 'Practice' && instance.rounds.length === 1)
}
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
onMouseDown={() => sendVboxDiscard()}> onMouseDown={() => sendVboxDiscard()}>
refill - 2b refill - 2b
@ -275,60 +270,42 @@ class Vbox extends preact.Component {
return setReclaiming(!reclaiming); return setReclaiming(!reclaiming);
} }
const inventoryClass = `vbox-section ${reclaiming ? 'reclaiming' : ''}`;
function inventoryBtn(v, i) { function inventoryBtn(v, i) {
const inventoryHighlight = vboxSelecting || itemUnequip.length; const inventoryHighlight = vboxSelecting || itemUnequip.length;
if (!v && v !== 0) { if (!v && v !== 0) {
return <button key={i} disabled={!inventoryHighlight} class={inventoryHighlight ? 'receiving' : 'empty'} >&nbsp;</button>; const emptyInvClick = () => {
if (vboxSelecting) return vboxBuySelected();
return false;
};
return <button key={i} onClick={emptyInvClick} disabled={!inventoryHighlight} class={inventoryHighlight ? 'receiving' : 'empty'} >&nbsp;</button>;
} }
const combinerItems = combiner.map(j => vbox.bound[j]); const comboHighlight = vboxHighlight && vboxHighlight.includes(v) ? 'combo-border' : '';
const combinerCount = countBy(combinerItems, co => co);
const comboItem = itemInfo.combos.find(combo => {
if (combo.components.includes(v)) {
return combinerItems.every(c => {
if (!combo.components.includes(c)) return false;
const comboCount = countBy(combo.components, co => co);
if (combinerCount[c] > comboCount[c]) return false;
if (c === v && combinerCount[c] + 1 > comboCount[c]) return false;
return true;
});
} return false;
});
const comboHighlight = combinerItems.length > 0 && comboItem ? 'combo-border' : '';
function onClick(type) { function onClick(type) {
if (vboxSelecting) clearVboxSelected();
if (reclaiming) return sendVboxReclaim(i); if (reclaiming) return sendVboxReclaim(i);
const combinerContainsIndex = combiner.indexOf(i) > -1; const combinerContainsIndex = stashSelect.indexOf(i) > -1;
// 4 things selected
if (combiner.length > 2 && !combinerContainsIndex) {
setInfo(vbox.bound[i]);
return combinerChange([i]);
}
// removing // removing
if (combinerContainsIndex) { if (combinerContainsIndex) {
if (type === 'click') { if (type === 'click') {
return combinerChange(without(combiner, i)); return combinerChange(without(stashSelect, i));
} }
return true; return true;
} }
if (!comboHighlight && !combinerContainsIndex) { if (!comboHighlight) {
setInfo(vbox.bound[i]); setInfo(vbox.bound[i]);
return combinerChange([i]); return setVboxSelected({ shopSelect: [], stashSelect: [i] });
} }
combiner.push(i); stashSelect.push(i);
if (combiner.length === 3) setInfo(comboItem.item); // if (stashSelect.length === 3) setInfo(comboItem.item);
return combinerChange(combiner); return combinerChange(stashSelect);
} }
const highlighted = combiner.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} ${comboHighlight}`;
@ -355,37 +332,49 @@ class Vbox extends preact.Component {
function combinerBtn() { function combinerBtn() {
let text = ''; let text = '';
let comboItem = ''; let mouseEvent = false;
if (combiner.length < 3) { const combineLength = stashSelect.length + shopSelect.length;
for (let i = 0; i < 3; i++) { if (vboxHighlight && vboxHighlight.length === 0) {
if (combiner.length > i) { // The selected items can't be combined with additional items therefore valid combo
text += '■ '; const stashItems = stashSelect.map(j => vbox.bound[j]);
} else { const shopItems = shopSelect.map(j => vbox.free[j[0]][j[1]]);
text += '▫ '; const selectedItems = stashItems.concat(shopItems);
} const combinerCount = countBy(selectedItems, co => co);
}
} else { const comboItemObj = itemInfo.combos.find(combo => selectedItems.every(c => {
// Since theres 3 items in combiner and you can't have invalid combos we can preview it
const combinerItems = combiner.map(j => vbox.bound[j]);
const combinerCount = countBy(combinerItems, co => co);
const comboItemObj = itemInfo.combos.find(combo => combinerItems.every(c => {
if (!combo.components.includes(c)) return false; if (!combo.components.includes(c)) return false;
const comboCount = countBy(combo.components, co => co); const comboCount = countBy(combo.components, co => co);
if (combinerCount[c] > comboCount[c]) return false; if (combinerCount[c] > comboCount[c]) return false;
return true; return true;
})); }));
comboItem = comboItemObj ? comboItemObj.item : 'refine'; let comboItem = comboItemObj ? comboItemObj.item : 'refine';
setInfo(comboItem);
comboItem = comboItem.replace('Plus', '+'); comboItem = comboItem.replace('Plus', '+');
text = `Combine - ${comboItem}`; let bits = 0;
shopSelect.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 && shopSelect.length === 1) {
const item = shopSelect[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 ( return (
<button <button
class='vbox-btn' class='vbox-btn'
disabled={combiner.length !== 3} disabled={!mouseEvent}
onMouseOver={e => hoverInfo(e, comboItem)}
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
onMouseDown={() => sendVboxCombine()}> onMouseDown={() => mouseEvent()}>
{text} {text}
</button> </button>
); );
@ -394,21 +383,18 @@ class Vbox extends preact.Component {
function inventoryElement() { function inventoryElement() {
function inventoryClick(e) { function inventoryClick(e) {
e.stopPropagation(); e.stopPropagation();
setReclaiming(false);
if (vboxSelecting) return vboxBuySelected();
if (itemUnequip.length) return sendItemUnequip(itemUnequip); if (itemUnequip.length) return sendItemUnequip(itemUnequip);
return true; return true;
} }
return ( return (
<div class={inventoryClass} <div
onMouseDown={inventoryClick} onMouseDown={inventoryClick}
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
style={vboxSelecting || (itemUnequip.length) ? { cursor: 'pointer' } : null}
onDragOver={ev => ev.preventDefault()} onDragOver={ev => ev.preventDefault()}
onDrop={inventoryClick} onDrop={inventoryClick}
> >
<div class="vbox-hdr"> <div class={`vbox-hdr ${reclaiming ? 'reclaiming' : ''}`}>
<h3 <h3
onTouchStart={e => e.target.scrollIntoView(true)} onTouchStart={e => e.target.scrollIntoView(true)}
onMouseOver={e => hoverInfo(e, 'inventory')}> INVENTORY onMouseOver={e => hoverInfo(e, 'inventory')}> INVENTORY
@ -422,7 +408,8 @@ class Vbox extends preact.Component {
reclaim reclaim
</button> </button>
</div> </div>
<div class='vbox-items'>
<div class={`vbox-items ${reclaiming ? 'reclaiming' : ''}`}>
{range(0, 9).map(i => inventoryBtn(vbox.bound[i], i))} {range(0, 9).map(i => inventoryBtn(vbox.bound[i], i))}
</div> </div>
{combinerBtn()} {combinerBtn()}
@ -434,11 +421,15 @@ class Vbox extends preact.Component {
// EVERYTHING // EVERYTHING
// //
function hoverInfo(e, newInfo) { function hoverInfo(e, newInfo) {
e.stopPropagation(); 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 setInfo(newInfo);
} }
const classes = `vbox`; const classes = 'vbox';
return ( return (
<div class={classes}> <div class={classes}>
{vboxElement()} {vboxElement()}

View File

@ -157,11 +157,6 @@ function registerEvents(store) {
store.dispatch(actions.setShop(v)); store.dispatch(actions.setShop(v));
} }
function clearCombiner() {
store.dispatch(actions.setInfo([]));
store.dispatch(actions.setCombiner([]));
}
function clearConstructRename() { function clearConstructRename() {
store.dispatch(actions.setConstructRename(null)); store.dispatch(actions.setConstructRename(null));
} }
@ -184,14 +179,12 @@ function registerEvents(store) {
} }
function clearInstance() { function clearInstance() {
store.dispatch(actions.setCombiner([]));
store.dispatch(actions.setReclaiming(false)); store.dispatch(actions.setReclaiming(false));
store.dispatch(actions.setActiveSkill(null)); store.dispatch(actions.setActiveSkill(null));
store.dispatch(actions.setInfo(null)); store.dispatch(actions.setInfo(null));
store.dispatch(actions.setItemEquip(null));
store.dispatch(actions.setItemUnequip([])); store.dispatch(actions.setItemUnequip([]));
store.dispatch(actions.setVboxHighlight([])); store.dispatch(actions.setVboxHighlight([]));
store.dispatch(actions.setVboxSelected([])); store.dispatch(actions.setVboxSelected({ shopSelect: [], stashSelect: [] }));
} }
function setAccountInstances(v) { function setAccountInstances(v) {
@ -345,7 +338,6 @@ function registerEvents(store) {
window.addEventListener('hashchange', urlHashChange, false); window.addEventListener('hashchange', urlHashChange, false);
return { return {
clearCombiner,
clearConstructRename, clearConstructRename,
clearInfo, clearInfo,
clearInstance, clearInstance,

View File

@ -6,14 +6,12 @@ function setupKeys(store) {
key.unbind('esc'); key.unbind('esc');
key('esc', () => document.activeElement.blur()); key('esc', () => document.activeElement.blur());
key('esc', () => store.dispatch(actions.setCombiner([])));
key('esc', () => store.dispatch(actions.setReclaiming(false))); key('esc', () => store.dispatch(actions.setReclaiming(false)));
key('esc', () => store.dispatch(actions.setActiveSkill(null))); key('esc', () => store.dispatch(actions.setActiveSkill(null)));
key('esc', () => store.dispatch(actions.setInfo(null))); key('esc', () => store.dispatch(actions.setInfo(null)));
key('esc', () => store.dispatch(actions.setItemEquip(null)));
key('esc', () => store.dispatch(actions.setItemUnequip([]))); key('esc', () => store.dispatch(actions.setItemUnequip([])));
key('esc', () => store.dispatch(actions.setVboxHighlight([]))); key('esc', () => store.dispatch(actions.setVboxHighlight([])));
key('esc', () => store.dispatch(actions.setVboxSelected([]))); key('esc', () => store.dispatch(actions.setVboxSelected({ shopSelect: [], stashSelect: [] })));
key('esc', () => store.dispatch(actions.setMtxActive(null))); key('esc', () => store.dispatch(actions.setMtxActive(null)));
} }

View File

@ -26,7 +26,6 @@ module.exports = {
chatShow: createReducer(null, 'SET_CHAT_SHOW'), chatShow: createReducer(null, 'SET_CHAT_SHOW'),
chatWheel: createReducer([], 'SET_CHAT_WHEEL'), chatWheel: createReducer([], 'SET_CHAT_WHEEL'),
combiner: createReducer([], 'SET_COMBINER'),
constructs: createReducer([], 'SET_CONSTRUCTS'), constructs: createReducer([], 'SET_CONSTRUCTS'),
constructEditId: createReducer(null, 'SET_CONSTRUCT_EDIT_ID'), constructEditId: createReducer(null, 'SET_CONSTRUCT_EDIT_ID'),
constructRename: createReducer(null, 'SET_CONSTRUCT_RENAME'), constructRename: createReducer(null, 'SET_CONSTRUCT_RENAME'),
@ -39,7 +38,6 @@ module.exports = {
instance: createReducer(null, 'SET_INSTANCE'), instance: createReducer(null, 'SET_INSTANCE'),
instanceChat: createReducer(null, 'SET_INSTANCE_CHAT'), instanceChat: createReducer(null, 'SET_INSTANCE_CHAT'),
instances: createReducer([], 'SET_INSTANCES'), instances: createReducer([], 'SET_INSTANCES'),
itemEquip: createReducer(null, 'SET_ITEM_EQUIP'),
itemInfo: createReducer({ combos: [], items: [] }, 'SET_ITEM_INFO'), itemInfo: createReducer({ combos: [], items: [] }, 'SET_ITEM_INFO'),
itemUnequip: createReducer([], 'SET_ITEM_UNEQUIP'), itemUnequip: createReducer([], 'SET_ITEM_UNEQUIP'),
mtxActive: createReducer(null, 'SET_MTX_ACTIVE'), mtxActive: createReducer(null, 'SET_MTX_ACTIVE'),
@ -59,7 +57,7 @@ module.exports = {
tutorial: createReducer(1, 'SET_TUTORIAL'), tutorial: createReducer(1, 'SET_TUTORIAL'),
tutorialGame: createReducer(1, 'SET_TUTORIAL_GAME'), tutorialGame: createReducer(1, 'SET_TUTORIAL_GAME'),
vboxSelected: createReducer([], 'SET_VBOX_SELECTED'), vboxSelected: createReducer({ shopSelect: [], stashSelect: [] }, 'SET_VBOX_SELECTED'),
ws: createReducer(null, 'SET_WS'), ws: createReducer(null, 'SET_WS'),
}; };

View File

@ -107,8 +107,8 @@ function createSocket(events) {
events.clearInstance(); events.clearInstance();
} }
function sendVboxCombine(instanceId, indices) { function sendVboxCombine(instanceId, invIndicies, vboxIndicies) {
send(['VboxCombine', { instance_id: instanceId, indices }]); send(['VboxCombine', { instance_id: instanceId, inv_indices: invIndicies, vbox_indices: vboxIndicies }]);
events.clearInstance(); events.clearInstance();
} }
@ -138,6 +138,11 @@ function createSocket(events) {
events.setActiveSkill(null); events.setActiveSkill(null);
} }
function sendGameConcede(gameId) {
send(['GameConcede', { game_id: gameId }]);
events.setActiveSkill(null);
}
function sendGameTarget(gameId, constructId, skillId) { function sendGameTarget(gameId, constructId, skillId) {
send(['GameTarget', { game_id: gameId, construct_id: constructId, skill_id: skillId }]); send(['GameTarget', { game_id: gameId, construct_id: constructId, skill_id: skillId }]);
events.setActiveSkill(null); events.setActiveSkill(null);
@ -391,6 +396,7 @@ function createSocket(events) {
sendGameSkill, sendGameSkill,
sendGameSkillClear, sendGameSkillClear,
sendGameOfferDraw, sendGameOfferDraw,
sendGameConcede,
sendGameTarget, sendGameTarget,
sendInstanceAbandon, sendInstanceAbandon,

View File

@ -4,7 +4,7 @@ const actions = require('./actions');
function tutorialConstructDisplay(player, instance, tutorial, i) { function tutorialConstructDisplay(player, instance, tutorial, i) {
if (instance.time_control === 'Practice' && instance.rounds.length === 1 && tutorial && tutorial < 6) { if (instance.time_control === 'Practice' && instance.rounds.length === 1 && tutorial && tutorial < 6) {
if (tutorial <= 2 || (tutorial > 2 && i > 0)) { if (tutorial <= 2 || (tutorial > 2 && i > 0)) {
const classes = `instance-construct`; const classes = 'instance-construct';
return (<div key={player.constructs[i].id} class={classes}></div>); return (<div key={player.constructs[i].id} class={classes}></div>);
} }
} }
@ -21,7 +21,7 @@ function tutorialVbox(player, store, tutorial) {
let stage = tutorial; let stage = tutorial;
const { vbox } = player; const { vbox } = player;
if (stage === 1) { if (stage === 1) {
if (vbox.bits < 17) { if (vbox.bits < 29) {
stage += 1; stage += 1;
} else { } else {
vbox.free[0] = vbox.free[0].slice(0, 2); vbox.free[0] = vbox.free[0].slice(0, 2);
@ -54,7 +54,7 @@ function tutorialVbox(player, store, tutorial) {
} }
if (stage === 4) { if (stage === 4) {
if (!vbox.free[2][0] || vbox.bits < 12) { if (!vbox.free[2][0] || vbox.bits < 24) {
stage += 1; stage += 1;
} else { } else {
vbox.free[0] = []; vbox.free[0] = [];
@ -86,7 +86,7 @@ function tutorialVbox(player, store, tutorial) {
} }
if (stage === 7) { if (stage === 7) {
if (vbox.bits < 13) { if (vbox.bits < 25) {
stage += 1; stage += 1;
} else { } else {
vbox.free[0] = []; vbox.free[0] = [];
@ -112,9 +112,8 @@ function tutorialStage(tutorial, ws, clearTutorial, instance) {
<div class='info-item'> <div class='info-item'>
<h2>Tutorial</h2> <h2>Tutorial</h2>
<p> Welcome to the vbox phase tutorial.</p> <p> Welcome to the vbox phase tutorial.</p>
<p> Colours are used to create powerful combinations. </p> <p> Colours are used to create powerful combinations with base items. </p>
<p> Buy two colours from the vbox by double clicking. <br /> <p> Buy the two colours from the vbox to continue. </p>
You can also click the colour once and then click the inventory. </p>
</div> </div>
); );
} }
@ -151,8 +150,7 @@ function tutorialStage(tutorial, ws, clearTutorial, instance) {
<h2>Tutorial</h2> <h2>Tutorial</h2>
<p> You can also buy specialisation items for your constructs. <br /> <p> You can also buy specialisation items for your constructs. <br />
Specialisation items increase stats including power, speed and life. </p> Specialisation items increase stats including power, speed and life. </p>
<p> Buy the specialisation item from the vbox by double clicking. <br /> <p> Buy the specialisation item from the vbox to continue. </p>
You can also click the specialisation once and then click the inventory. </p>
</div> </div>
); );
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "mnml-ops", "name": "mnml-ops",
"version": "1.8.3", "version": "1.9.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -1,6 +1,6 @@
[package] [package]
name = "mnml" name = "mnml"
version = "1.8.3" version = "1.9.0"
authors = ["ntr <ntr@smokestack.io>"] authors = ["ntr <ntr@smokestack.io>"]
[dependencies] [dependencies]
@ -34,7 +34,7 @@ router = "0.6"
mount = "0.4" mount = "0.4"
cookie = "0.12" cookie = "0.12"
crossbeam-channel = "0.3" crossbeam-channel = "0.3"
ws = { version = "0.8", features = ["ssl"] } ws = { version = "0.8", features = ["ssl", "permessage-deflate"] }
lettre = "0.9" lettre = "0.9"
lettre_email = "0.9" lettre_email = "0.9"

View File

@ -349,6 +349,18 @@ impl Game {
return Ok(self); return Ok(self);
} }
fn concede(mut self, player_id: Uuid) -> Result<Game, Error> {
if self.phase != Phase::Skill {
return Err(err_msg("game not in skill phase"));
}
self.player_by_id(player_id)?
.forfeit();
return Ok(self.finish());
}
fn clear_skill(&mut self, player_id: Uuid) -> Result<&mut Game, Error> { fn clear_skill(&mut self, player_id: Uuid) -> Result<&mut Game, Error> {
self.player_by_id(player_id)?; self.player_by_id(player_id)?;
if self.phase != Phase::Skill { if self.phase != Phase::Skill {
@ -940,6 +952,16 @@ pub fn game_offer_draw(tx: &mut Transaction, account: &Account, game_id: Uuid) -
Ok(game) Ok(game)
} }
pub fn game_concede(tx: &mut Transaction, account: &Account, game_id: Uuid) -> Result<Game, Error> {
let game = game_get(tx, game_id)?
.concede(account.id)?;
game_update(tx, &game)?;
Ok(game)
}
pub fn game_skill_clear(tx: &mut Transaction, account: &Account, game_id: Uuid) -> Result<Game, Error> { pub fn game_skill_clear(tx: &mut Transaction, account: &Account, game_id: Uuid) -> Result<Game, Error> {
let mut game = game_get(tx, game_id)?; let mut game = game_get(tx, game_id)?;

View File

@ -480,10 +480,10 @@ impl Instance {
Ok(self) Ok(self)
} }
pub fn vbox_combine(mut self, account: Uuid, indices: Vec<usize>) -> Result<Instance, Error> { pub fn vbox_combine(mut self, account: Uuid, inv_indices: Vec<usize>, vbox_indices: Vec<Vec<usize>>) -> Result<Instance, Error> {
self.vbox_action_allowed(account)?; self.vbox_action_allowed(account)?;
self.account_player(account)? self.account_player(account)?
.vbox_combine(indices)?; .vbox_combine(inv_indices, vbox_indices)?;
Ok(self) Ok(self)
} }

View File

@ -62,6 +62,7 @@ use std::path::{Path};
use fern::colors::{Color, ColoredLevelConfig}; use fern::colors::{Color, ColoredLevelConfig};
use crossbeam_channel::{unbounded}; use crossbeam_channel::{unbounded};
#[cfg(unix)]
pub fn setup_logger() -> Result<(), fern::InitError> { pub fn setup_logger() -> Result<(), fern::InitError> {
let formatter = syslog::Formatter3164 { let formatter = syslog::Formatter3164 {
facility: syslog::Facility::LOG_USER, facility: syslog::Facility::LOG_USER,
@ -109,6 +110,7 @@ pub fn setup_logger() -> Result<(), fern::InitError> {
} }
pub fn start() { pub fn start() {
#[cfg(unix)]
setup_logger().unwrap(); setup_logger().unwrap();
dotenv::from_path(Path::new("/etc/mnml/gs.conf")).ok(); dotenv::from_path(Path::new("/etc/mnml/gs.conf")).ok();

View File

@ -229,7 +229,7 @@ impl Player {
}; };
// first 2 colours can be whatever // first 2 colours can be whatever
self.vbox_combine(vec![0, 1, combo_i]).ok(); self.vbox_combine(vec![0, 1, combo_i], vec![]).ok();
let item_i = self.vbox.bound.len() - 1; let item_i = self.vbox.bound.len() - 1;
self.vbox_apply(item_i, target_construct_id).ok(); self.vbox_apply(item_i, target_construct_id).ok();
} }
@ -257,8 +257,8 @@ impl Player {
Ok(self) Ok(self)
} }
pub fn vbox_combine(&mut self, indices: Vec<usize>) -> Result<&mut Player, Error> { pub fn vbox_combine(&mut self, inv_indices: Vec<usize>, vbox_indices: Vec<Vec<usize>>) -> Result<&mut Player, Error> {
self.vbox.combine(indices)?; self.vbox.combine(inv_indices, vbox_indices)?;
Ok(self) Ok(self)
} }

View File

@ -16,12 +16,13 @@ use stripe::{Client as StripeClient, Subscription};
use crossbeam_channel::{unbounded, Sender as CbSender}; use crossbeam_channel::{unbounded, Sender as CbSender};
use ws::{Builder, CloseCode, Message, Handler, Request, Response, Settings, Sender as WsSender}; use ws::{Builder, CloseCode, Message, Handler, Request, Response, Settings, Sender as WsSender};
use ws::deflate::DeflateHandler;
use account::{Account}; use account::{Account};
use account; use account;
use construct::{Construct}; use construct::{Construct};
use events::{Event}; use events::{Event};
use game::{Game, game_state, game_skill, game_skill_clear, game_ready, game_offer_draw}; use game::{Game, game_state, game_skill, game_skill_clear, game_ready, game_offer_draw, game_concede};
use instance::{Instance, ChatState, instance_state, instance_practice, instance_ready, instance_abandon, demo}; use instance::{Instance, ChatState, instance_state, instance_practice, instance_ready, instance_abandon, demo};
use item::{Item, ItemInfoCtr, item_info}; use item::{Item, ItemInfoCtr, item_info};
use mtx; use mtx;
@ -91,6 +92,7 @@ pub enum RpcRequest {
GameSkill { game_id: Uuid, construct_id: Uuid, target_construct_id: Uuid, skill: Skill }, GameSkill { game_id: Uuid, construct_id: Uuid, target_construct_id: Uuid, skill: Skill },
GameSkillClear { game_id: Uuid }, GameSkillClear { game_id: Uuid },
GameOfferDraw { game_id: Uuid }, GameOfferDraw { game_id: Uuid },
GameConcede { game_id: Uuid },
AccountState {}, AccountState {},
AccountShop {}, AccountShop {},
@ -115,7 +117,7 @@ pub enum RpcRequest {
VboxAccept { instance_id: Uuid, group: usize, index: usize }, VboxAccept { instance_id: Uuid, group: usize, index: usize },
VboxAcceptEquip { instance_id: Uuid, group: usize, index: usize, construct_id: Uuid }, VboxAcceptEquip { instance_id: Uuid, group: usize, index: usize, construct_id: Uuid },
VboxDiscard { instance_id: Uuid }, VboxDiscard { instance_id: Uuid },
VboxCombine { instance_id: Uuid, indices: Vec<usize> }, VboxCombine { instance_id: Uuid, inv_indices: Vec<usize>, vbox_indices: Vec<Vec<usize>> },
VboxApply { instance_id: Uuid, construct_id: Uuid, index: usize }, VboxApply { instance_id: Uuid, construct_id: Uuid, index: usize },
VboxUnequip { instance_id: Uuid, construct_id: Uuid, target: Item }, VboxUnequip { instance_id: Uuid, construct_id: Uuid, target: Item },
VboxUnequipApply { instance_id: Uuid, construct_id: Uuid, target: Item, target_construct_id: Uuid }, VboxUnequipApply { instance_id: Uuid, construct_id: Uuid, target: Item, target_construct_id: Uuid },
@ -226,6 +228,9 @@ impl Connection {
RpcRequest::GameReady { id } => RpcRequest::GameReady { id } =>
Ok(RpcMessage::GameState(game_ready(&mut tx, account, id)?)), Ok(RpcMessage::GameState(game_ready(&mut tx, account, id)?)),
RpcRequest::GameConcede { game_id } =>
Ok(RpcMessage::GameState(game_concede(&mut tx, account, game_id)?)),
RpcRequest::GameOfferDraw { game_id } => RpcRequest::GameOfferDraw { game_id } =>
Ok(RpcMessage::GameState(game_offer_draw(&mut tx, account, game_id)?)), Ok(RpcMessage::GameState(game_offer_draw(&mut tx, account, game_id)?)),
@ -249,8 +254,8 @@ impl Connection {
RpcRequest::VboxApply { instance_id, construct_id, index } => RpcRequest::VboxApply { instance_id, construct_id, index } =>
Ok(RpcMessage::InstanceState(vbox_apply(&mut tx, account, instance_id, construct_id, index)?)), Ok(RpcMessage::InstanceState(vbox_apply(&mut tx, account, instance_id, construct_id, index)?)),
RpcRequest::VboxCombine { instance_id, indices } => RpcRequest::VboxCombine { instance_id, inv_indices, vbox_indices } =>
Ok(RpcMessage::InstanceState(vbox_combine(&mut tx, account, instance_id, indices)?)), Ok(RpcMessage::InstanceState(vbox_combine(&mut tx, account, instance_id, inv_indices, vbox_indices)?)),
RpcRequest::VboxDiscard { instance_id } => RpcRequest::VboxDiscard { instance_id } =>
Ok(RpcMessage::InstanceState(vbox_discard(&mut tx, account, instance_id)?)), Ok(RpcMessage::InstanceState(vbox_discard(&mut tx, account, instance_id)?)),
@ -448,7 +453,8 @@ impl Handler for Connection {
pub fn start(pool: PgPool, events_tx: CbSender<Event>, stripe: StripeClient) { pub fn start(pool: PgPool, events_tx: CbSender<Event>, stripe: StripeClient) {
let mut rng = thread_rng(); let mut rng = thread_rng();
Builder::new()
let ws = Builder::new()
.with_settings(Settings { .with_settings(Settings {
max_connections: 10_000, max_connections: 10_000,
..Settings::default() ..Settings::default()
@ -478,14 +484,16 @@ pub fn start(pool: PgPool, events_tx: CbSender<Event>, stripe: StripeClient) {
} }
}); });
Connection { DeflateHandler::new(
id: rng.gen::<usize>(), Connection {
account: None, id: rng.gen::<usize>(),
ws: tx, account: None,
pool: pool.clone(), ws: tx,
stripe: stripe.clone(), pool: pool.clone(),
events: events_tx.clone(), stripe: stripe.clone(),
} events: events_tx.clone(),
}
)
}) })
.unwrap() .unwrap()
.listen("127.0.0.1:40055") .listen("127.0.0.1:40055")

View File

@ -348,15 +348,16 @@ fn post_resolve(_skill: Skill, game: &mut Game, mut resolutions: Resolutions) ->
}; };
} }
} }
if target.is_ko() && event_target.green == 0 {
// Make sure target ko is from this event
target.effects.clear();
resolutions.push(Resolution::new(&source, &target).event(Event::Ko()).stages(EventStages::PostOnly));
}
}, },
_ => (), _ => (),
}; };
if target.is_ko() && event_target.green == 0 {
// Make sure target ko is from this event
target.effects.clear();
resolutions.push(Resolution::new(&source, &target).event(Event::Ko()).stages(EventStages::PostOnly));
}
game.update_construct(&mut source); game.update_construct(&mut source);
game.update_construct(&mut target); game.update_construct(&mut target);

View File

@ -142,19 +142,20 @@ impl Vbox {
Ok(self) Ok(self)
} }
pub fn combine(&mut self, mut indices: Vec<usize>) -> Result<&mut Vbox, Error> { pub fn combine(&mut self, mut inv_indices: Vec<usize>, vbox_indicies: Vec<Vec<usize>>) -> Result<&mut Vbox, Error> {
if indices.len() != 3 { if !inv_indices.iter().all(|i| self.bound.get(*i).is_some()) {
return Err(err_msg("exactly 3 indices required"));
}
if !indices.iter().all(|i| self.bound.get(*i).is_some()) {
return Err(err_msg("item missing index")); return Err(err_msg("item missing index"));
} }
// try to buy up the vbox indicies and add them to the inventory indicies for combining
for vi in vbox_indicies.iter() {
inv_indices.push(self.bound.len());
self.accept(vi[0], vi[1], Some(Uuid::nil()))?;
}
// have to sort the indices and keep track of the iteration // have to sort the indices and keep track of the iteration
// because when removing the elements the array shifts // because when removing the elements the array shifts
indices.sort_unstable(); inv_indices.sort_unstable();
let mut input = indices let mut input = inv_indices
.iter() .iter()
.enumerate() .enumerate()
.map(|(i, index)| { .map(|(i, index)| {
@ -170,7 +171,9 @@ impl Vbox {
self.bound.push(combo.item); self.bound.push(combo.item);
// self.bound.sort_unstable(); // self.bound.sort_unstable();
if self.bound.len() >= 10 {
return Err(err_msg("too many items bound"));
}
Ok(self) Ok(self)
} }
} }
@ -187,9 +190,9 @@ pub fn vbox_accept(tx: &mut Transaction, account: &Account, instance_id: Uuid, g
return instance_update(tx, instance); return instance_update(tx, instance);
} }
pub fn vbox_combine(tx: &mut Transaction, account: &Account, instance_id: Uuid, indices: Vec<usize>) -> Result<Instance, Error> { pub fn vbox_combine(tx: &mut Transaction, account: &Account, instance_id: Uuid, inv_indices: Vec<usize>, vbox_indices: Vec<Vec<usize>>) -> Result<Instance, Error> {
let instance = instance_get(tx, instance_id)? let instance = instance_get(tx, instance_id)?
.vbox_combine(account.id, indices)?; .vbox_combine(account.id, inv_indices, vbox_indices)?;
return instance_update(tx, instance); return instance_update(tx, instance);
} }
@ -219,7 +222,7 @@ mod tests {
fn combine_test() { fn combine_test() {
let mut vbox = Vbox::new(); let mut vbox = Vbox::new();
vbox.bound = vec![Item::Attack, Item::Green, Item::Green]; vbox.bound = vec![Item::Attack, Item::Green, Item::Green];
vbox.combine(vec![1,2,0]).unwrap(); vbox.combine(vec![1,2,0], vec![]).unwrap();
assert_eq!(vbox.bound[0], Item::Heal); assert_eq!(vbox.bound[0], Item::Heal);
} }
@ -240,7 +243,7 @@ mod tests {
let mut vbox = Vbox::new(); let mut vbox = Vbox::new();
vbox.bound = vec![Item::Strike]; vbox.bound = vec![Item::Strike];
vbox.reclaim(0).unwrap(); vbox.reclaim(0).unwrap();
assert_eq!(vbox.bits, 20); assert_eq!(vbox.bits, 32);
} }
#[test] #[test]