Merge branch 'develop' into face-imgs

This commit is contained in:
ntr 2019-10-31 17:37:38 +11:00
commit 17af7ceb03
65 changed files with 1573 additions and 1185 deletions

View File

@ -2,6 +2,87 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/). This project adheres to [Semantic Versioning](http://semver.org/).
## [1.6.7] - 2019-10-27
# Added
- Step by step tutorial
- Will activate during the learn game for the first round
- There is a button which will exit tutorial so you can continune the normal practice mode
- Skill combo previews
- You can now preview what item combos will create!
- Click into the item in the info section table (top right) and it will be replaced with the new item
# Changed
- Vbox phase
- Made general performance improvements
- Removed the default info state (should be smoother to navigate now)
- When combining or selecting base items (skills/specs) the base item info won't be replaced with other vbox items
- Added info text for construct names and avatars
- Changed a number of info descriptions for clarity
- Game phase
- Made general performance improvements
- Now has default tutorial text for the first round (tells the player to select skills and then the targets)
- Moved the login page demo to a new info tab, increased the speed of demo and it now creates random combos
- Banish
- Cooldown reduced to 2T (was 3T)
- Bash
- Skill multiplier reduced from 65/95/140 -> 45/65/100
- Blast
- Damage multiplier reduced 110/145/210 -> 105/140/200
- Block
- Previously reduced red damage taken by 50%
- Now reduces red damage and blue damage taken by 65%
- Buff
- Previously increased red power and speed stat by 25%
- Now increases red power, blue power and speed stat by 30%
- Increased duration from 2T -> 3T
- Counter
- No longer applies block effect
- CounterAttack multiplier increased from 70/95/120 -> 120/160/230
- Will now animate an attack animation on counter attack
- Curse
- Now created by combining Debuff + RB was Debuff + RG
- Fixed issue where curse was only increasing blue damage taken instead of red and blue
- Fixed an issue where the animation would not display if it occured already
- Haste
- Cooldown reduced to 1T (was 2T)
- Duration increased from 2/3/4T -> 3/4/5T
- HasteStrike damage multiplier changed from 30% -> 60%
- Hybrid
- Cooldown reduced to 1T (was 3T)
- Duration increased from 2/3/4T -> 3/4/5T
- HybridBlast damage multiplier changed from 25% -> 50%
- Invert
- Now created by combining Debuff + RG was debuff + RB
- Silence
- Cooldown reduced to 2T (was 3T)
- Siphon
- Skill multiplier increased from 20/25/30 -> 25/30/40
- Reworded part of description "Construct heals self for 100% of damage dealt to target construct GreenLife."
- Slay
- Self healing is now 50% of damage dealt to green life (was 100%)
- Damage multiplier increased from (40/60/90)% -> (45/65/100)%
- Sleep
- Cooldown reduced to 2T (was 3T)
- Skill multiplier reduced from 240/300/400 -> 200/290/400
## [1.6.6] - 2019-10-27 ## [1.6.6] - 2019-10-27
# Added # Added
- Offering of draws - Offering of draws

View File

@ -446,6 +446,17 @@
} }
} }
.tutorial {
button {
width: 100%;
}
button.focus {
animation: co 0.75s cubic-bezier(0, 0, 1, 1) 0s infinite alternate;
}
}
@keyframes faceoff { @keyframes faceoff {
from { from {
color: @black; color: @black;

View File

@ -50,6 +50,9 @@ export const setTeam = value => ({ type: 'SET_TEAM', value: Array.from(value) })
export const setTeamPage = value => ({ type: 'SET_TEAM_PAGE', value }); export const setTeamPage = value => ({ type: 'SET_TEAM_PAGE', value });
export const setTeamSelect = value => ({ type: 'SET_TEAM_SELECT', value: Array.from(value) }); export const setTeamSelect = value => ({ type: 'SET_TEAM_SELECT', value: Array.from(value) });
export const setTutorial = value => ({ type: 'SET_TUTORIAL', value });
export const setTutorialGame = value => ({ type: 'SET_TUTORIAL_GAME', value });
export const setVboxHighlight = value => ({ type: 'SET_VBOX_HIGHLIGHT', value }); export const setVboxHighlight = value => ({ type: 'SET_VBOX_HIGHLIGHT', value });
export const setVboxSelected = value => ({ type: 'SET_VBOX_SELECTED', value }); export const setVboxSelected = value => ({ type: 'SET_VBOX_SELECTED', value });

View File

@ -39,7 +39,6 @@ const Stun = require('./anims/stun');
const Triage = require('./anims/triage'); const Triage = require('./anims/triage');
const TriageTick = require('./anims/triage.tick'); const TriageTick = require('./anims/triage.tick');
const actions = require('../actions');
const { removeTier } = require('../utils'); const { removeTier } = require('../utils');
@ -47,10 +46,16 @@ const addState = connect(
function receiveState(state) { function receiveState(state) {
const { animTarget } = state; const { animTarget } = state;
return { animTarget }; return { animTarget };
}, }
); );
class ConstructAnimation extends Component { class ConstructAnimation extends Component {
shouldComponentUpdate(nextProps) {
if (nextProps.animTarget !== this.props.animTarget) return true;
if (nextProps.construct !== this.props.construct) return true;
return false;
}
render(props) { render(props) {
const { const {
animTarget, animTarget,
@ -70,7 +75,7 @@ class ConstructAnimation extends Component {
// find target animation // find target animation
const chooseAnim = (animSkill) => { const chooseAnim = () => {
switch (animSkill) { switch (animSkill) {
// Attack base // Attack base
case 'Attack': return <Attack direction={direction}/>; case 'Attack': return <Attack direction={direction}/>;
@ -124,7 +129,7 @@ class ConstructAnimation extends Component {
case 'Reflect': return <Refl player={player} />; case 'Reflect': return <Refl player={player} />;
default: return false; default: return false;
}; }
}; };
const anim = chooseAnim(animSkill); const anim = chooseAnim(animSkill);

View File

@ -36,7 +36,7 @@ class Blast extends Component {
render() { render() {
return ( return (
<svg <svg
id='blast' id="blast"
class="skill-animation" class="skill-animation"
version="1.1" version="1.1"
transform-box='fill-box' transform-box='fill-box'

View File

@ -21,7 +21,7 @@ class Buff extends Component {
render() { render() {
return ( return (
<svg <svg
id='buff' id="buff"
class="skill-animation white" class="skill-animation white"
version="1.1" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -52,7 +52,7 @@ class Chaos extends Component {
<feMergeNode in="SourceGraphic" /> <feMergeNode in="SourceGraphic" />
</feMerge> </feMerge>
</filter> </filter>
<filter id='chaosRedFilter'> <filter id="chaosRedFilter">
<feTurbulence type="turbulence" baseFrequency="0" numOctaves="1" result="turbulence"></feTurbulence> <feTurbulence type="turbulence" baseFrequency="0" numOctaves="1" result="turbulence"></feTurbulence>
<feDisplacementMap in2="turbulence" in="SourceGraphic" scale="1" xChannelSelector="R" yChannelSelector="G"></feDisplacementMap> <feDisplacementMap in2="turbulence" in="SourceGraphic" scale="1" xChannelSelector="R" yChannelSelector="G"></feDisplacementMap>
</filter> </filter>

View File

@ -21,13 +21,13 @@ class Counter extends Component {
render({ team }) { render({ team }) {
return ( return (
<svg <svg
class='skill-animation red' class="skill-animation red"
version="1.1" version="1.1"
id="counter" id="counter"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
style={{ transform: team ? 'rotate3d(1, 0, 0, 180deg)' : '' }} style={{ transform: team ? 'rotate3d(1, 0, 0, 180deg)' : '' }}
viewBox="0 0 256 256"> viewBox="0 0 256 256">
<filter id='counterFilter'> <filter id="counterFilter">
<feTurbulence type="turbulence" baseFrequency="0" numOctaves="1" result="turbulence"></feTurbulence> <feTurbulence type="turbulence" baseFrequency="0" numOctaves="1" result="turbulence"></feTurbulence>
<feDisplacementMap in2="turbulence" in="SourceGraphic" scale="1" xChannelSelector="R" yChannelSelector="G"></feDisplacementMap> <feDisplacementMap in2="turbulence" in="SourceGraphic" scale="1" xChannelSelector="R" yChannelSelector="G"></feDisplacementMap>
</filter> </filter>
@ -60,7 +60,7 @@ class Counter extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#counter'], targets: ['#counter'],
rotateX: 180, rotateX: 180,
delay: TIMES.TARGET_DELAY_MS * 2, delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS / 3,
duration: TIMES.TARGET_DURATION_MS / 2, duration: TIMES.TARGET_DURATION_MS / 2,
easing: 'easeOutSine', easing: 'easeOutSine',
})); }));

View File

@ -21,9 +21,9 @@ class Curse extends Component {
render() { render() {
return ( return (
<svg <svg
id='curse'
class="skill-animation" class="skill-animation"
version="1.1" version="1.1"
id="curse"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 198 200"> viewBox="0 0 198 200">
<defs> <defs>
@ -36,15 +36,15 @@ class Curse extends Component {
</filter> </filter>
</defs> </defs>
<g filter="url(#curseFilter)" class="blue" > <g filter="url(#curseFilter)" class="blue" >
<circle cx="100" cy="100" r="30" /> <circle id="curseFilterOne" cx="100" cy="100" r="0" />
<circle cx="100" cy="100" r="60" /> <circle id="curseFilterTwo" cx="100" cy="100" r="0" />
<circle cx="100" cy="100" r="90" /> <circle id="curseFilterThree" cx="100" cy="100" r="0" />
</g> </g>
<g class="white" style={{ strokeWidth: '2px' }}> <g class="white" style={{ strokeWidth: '2px' }}>
<circle cx="100" cy="100" r="30" /> <circle id="curseCircleOne" cx="100" cy="100" r="0" />
<circle cx="100" cy="100" r="60" /> <circle id="curseCircleTwo" cx="100" cy="100" r="0" />
<circle cx="100" cy="100" r="90" /> <circle id="curseCircleThree" cx="100" cy="100" r="0" />
</g> </g>
</svg> </svg>
); );
@ -61,12 +61,27 @@ class Curse extends Component {
})); }));
this.animations.push(anime({ this.animations.push(anime({
targets: ['#curse circle'], targets: ['#curseCircleOne', '#curseFilterOne'],
r: 0, r: 30,
easing: 'easeInOutSine', easing: 'easeInOutSine',
delay: TIMES.TARGET_DURATION_MS / 2, delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
this.animations.push(anime({
targets: ['#curseCircleTwo', '#curseFilterTwo'],
r: 60,
easing: 'easeInOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
this.animations.push(anime({
targets: ['#curseCircleThree', '#curseFilterThree'],
r: 90,
easing: 'easeInOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
direction: 'reverse',
})); }));
} }

View File

@ -21,7 +21,7 @@ class Debuff extends Component {
render() { render() {
return ( return (
<svg <svg
id='debuff' id="debuff"
class="skill-animation white" class="skill-animation white"
version="1.1" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -22,7 +22,7 @@ class Decay extends Component {
render() { render() {
return ( return (
<svg <svg
id='decay' id="decay"
class="skill-animation" class="skill-animation"
version="1.1" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -23,7 +23,7 @@ class Electrify extends Component {
render() { render() {
return ( return (
<svg <svg
class='skill-animation blue' class="skill-animation blue"
version="1.1" version="1.1"
id="electrify" id="electrify"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -23,7 +23,7 @@ class Electrocute extends Component {
render() { render() {
return ( return (
<svg <svg
class='skill-animation blue' class="skill-animation blue"
version="1.1" version="1.1"
id="electrify" id="electrify"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -21,7 +21,7 @@ class Haste extends Component {
render() { render() {
return ( return (
<svg <svg
id='haste' id="haste"
class="skill-animation" class="skill-animation"
version="1.1" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -37,8 +37,8 @@ class Heal extends Component {
render() { render() {
return ( return (
<svg <svg
class='skill-animation' class="skill-animation"
id='heal' id="heal"
version="1.1" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 300 400"> viewBox="0 0 300 400">

View File

@ -23,7 +23,7 @@ class Hex extends Component {
render() { render() {
return ( return (
<svg <svg
class='skill-animation blue' class="skill-animation blue"
version="1.1" version="1.1"
id="hex" id="hex"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -21,8 +21,8 @@ class Hybrid extends Component {
render() { render() {
return ( return (
<svg <svg
class='skill-animation' class="skill-animation"
id='hybrid' id="hybrid"
version="1.1" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 300 300"> viewBox="0 0 300 300">

View File

@ -25,7 +25,7 @@ class Intercept extends Component {
render() { render() {
return ( return (
<svg <svg
class='skill-animation' class="skill-animation"
version="1.1" version="1.1"
id="intercept" id="intercept"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -22,7 +22,7 @@ class Link extends Component {
render() { render() {
return ( return (
<svg <svg
class='skill-animation blue' class="skill-animation blue"
version="1.1" version="1.1"
id="link" id="link"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -21,7 +21,7 @@ class Purge extends Component {
render() { render() {
return ( return (
<svg <svg
class='skill-animation green' class="skill-animation green"
version="1.1" version="1.1"
id="purge" id="purge"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -44,7 +44,7 @@ class Purify extends Component {
render() { render() {
return ( return (
<svg <svg
class='skill-animation green' class="skill-animation green"
version="1.1" version="1.1"
id="purify" id="purify"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -21,7 +21,7 @@ class Recharge extends Component {
render() { render() {
return ( return (
<svg <svg
class='skill-animation red' class="skill-animation red"
version="1.1" version="1.1"
id="recharge" id="recharge"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -21,7 +21,7 @@ class Block extends Component {
render() { render() {
return ( return (
<svg <svg
class='skill-animation red' class="skill-animation red"
version="1.1" version="1.1"
id="block" id="block"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -21,7 +21,7 @@ class Intercept extends Component {
render({ player }) { render({ player }) {
return ( return (
<svg <svg
class='skill-animation red' class="skill-animation red"
version="1.1" version="1.1"
id="intercept" id="intercept"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -24,7 +24,7 @@ class Refl extends Component {
return ( return (
<svg <svg
class='skill-animation' class="skill-animation"
version="1.1" version="1.1"
id="reflect" id="reflect"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -21,7 +21,7 @@ class Restrict extends Component {
render() { render() {
return ( return (
<svg <svg
class='skill-animation red' class="skill-animation red"
version="1.1" version="1.1"
id="restrict" id="restrict"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -23,7 +23,7 @@ class Ruin extends Component {
return ( return (
<svg <svg
class='skill-animation blue' class="skill-animation blue"
version="1.1" version="1.1"
id="ruin" id="ruin"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -21,7 +21,7 @@ class Silence extends Component {
render() { render() {
return ( return (
<svg <svg
class='skill-animation' class="skill-animation"
version="1.1" version="1.1"
id="silence" id="silence"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -43,7 +43,7 @@ class Sleep extends Component {
return ( return (
<svg <svg
class='skill-animation green' class="skill-animation green"
version="1.1" version="1.1"
id="sleep" id="sleep"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -28,7 +28,7 @@ class Strike extends Component {
return ( return (
<svg <svg
class='strike-anim' class="strike-anim"
version="1.1" version="1.1"
id="strike" id="strike"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -26,7 +26,7 @@ class Stun extends Component {
return ( return (
<svg <svg
class='skill-animation white' class="skill-animation white"
version="1.1" version="1.1"
id="stun" id="stun"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -21,7 +21,7 @@ class Sustain extends Component {
render({ team }) { render({ team }) {
return ( return (
<svg <svg
class='skill-animation green' class="skill-animation green"
version="1.1" version="1.1"
id="sustain" id="sustain"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -37,8 +37,8 @@ class Triage extends Component {
render() { render() {
return ( return (
<svg <svg
class='skill-animation' class="skill-animation"
id='triage' id="triage"
version="1.1" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 300 400"> viewBox="0 0 300 400">

View File

@ -37,8 +37,8 @@ class TriageTick extends Component {
render() { render() {
return ( return (
<svg <svg
class='skill-animation' class="skill-animation"
id='triageTick' id="triageTick"
version="1.1" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 300 400"> viewBox="0 0 300 400">

View File

@ -29,12 +29,13 @@ class ConstructAvatar extends Component {
} }
render() { render() {
const { construct } = this.props; const { construct, mouseOver } = this.props;
return ( return (
<div <div
class="avatar" class="avatar"
id={construct.id} id={construct.id}
onMouseDown={this.onClick.bind(this)} onMouseDown={this.onClick.bind(this)}
onMouseOver={mouseOver ? mouseOver : null}
style={{ 'background-image': `url(/imgs/${construct.img}.svg)` }}> style={{ 'background-image': `url(/imgs/${construct.img}.svg)` }}>
<ConstructAnimation construct={construct} /> <ConstructAnimation construct={construct} />
</div> </div>
@ -60,42 +61,36 @@ class ConstructAvatar extends Component {
this.resetAnimations(); this.resetAnimations();
} }
shouldComponentUpdate(newProps) { componentDidUpdate(prevProps) {
const { animSource, animTarget, animText, construct } = newProps; const { animSource, animTarget, animText, construct } = this.props;
// a different text object and text construct
if (construct !== this.props.construct) { if (animText && animText !== prevProps.animText && animText.constructId === construct.id) {
return true;
}
if (animText && animText !== this.props.animText && animText.constructId === construct.id) {
return wiggle(construct.id, this.idle); return wiggle(construct.id, this.idle);
} }
if (animSource === this.props.animSource && animTarget === this.props.animTarget) { // different source object and source construct
// console.warn(construct.name, 'thinks its same props') if (animSource && animSource !== prevProps.animSource && animSource.constructId === construct.id) {
return false;
}
// something has changed
// what do?
// this is the source
if (animSource && animSource.constructId === construct.id) {
// console.warn(construct.name, 'should update')
return sourceCast(animSource.constructId, animSource.direction, this.idle); return sourceCast(animSource.constructId, animSource.direction, this.idle);
} }
// different target object and target construct
// this is the target if (animTarget && animTarget !== prevProps.animTarget && animTarget.constructId.includes(construct.id)) {
if (animTarget && animTarget.constructId.includes(construct.id)) {
// console.warn(construct.name, 'should update')
switch (animTarget.skill) { switch (animTarget.skill) {
case 'Banish': return banish(construct.id, this.idle); case 'Banish': return banish(construct.id, this.idle);
case 'Invert': return invert(construct.id, this.idle); case 'Invert': return invert(construct.id, this.idle);
default: return null; default: return null;
} }
} }
return true;
}
shouldComponentUpdate(newProps) {
const { animSource, animTarget, animText, construct } = newProps;
if (animSource !== this.props.animSource) return true;
if (animTarget !== this.props.animTarget) return true;
if (animText !== this.props.animText) return true;
if (construct !== this.props.construct) return true;
return false; return false;
} }
} }

View File

@ -1,11 +1,11 @@
const { connect } = require('preact-redux'); const { connect } = require('preact-redux');
const preact = require('preact'); const preact = require('preact');
const actions = require('../actions'); // const actions = require('../actions');
const shapes = require('./shapes'); const shapes = require('./shapes');
const { ConstructAvatar } = require('./construct'); const { ConstructAvatar } = require('./construct');
const { ConstructAnimation } = require('./animations'); // const { ConstructAnimation } = require('./animations');
const addState = connect( const addState = connect(
function receiveState(state) { function receiveState(state) {
@ -20,15 +20,15 @@ const addState = connect(
itemInfo, itemInfo,
demo, demo,
}; };
}, }
function receiveDispatch(dispatch) { /* function receiveDispatch(dispatch) {
function setAnimTarget(anim) { function setAnimTarget(anim) {
dispatch(actions.setAnimTarget(anim)); dispatch(actions.setAnimTarget(anim));
} }
return { setAnimTarget }; return { setAnimTarget };
} } */
); );
@ -38,12 +38,12 @@ function Demo(args) {
itemInfo, itemInfo,
account, account,
setAnimTarget, // setAnimTarget,
} = args; } = args;
if (!demo || !itemInfo.items.length || account) return false; if (!demo || !itemInfo.items.length || account) return false;
const { combiner, items, equipping, equipped, players } = demo; const { combiner, items, equipping, equipped, players, combo } = demo;
const vboxDemo = () => { const vboxDemo = () => {
function inventoryBtn(i, j) { function inventoryBtn(i, j) {
@ -90,7 +90,7 @@ function Demo(args) {
VBOX PHASE {shapes.Red()} {shapes.Green()} {shapes.Blue()} VBOX PHASE {shapes.Red()} {shapes.Green()} {shapes.Blue()}
</h2> </h2>
<p> <p>
Combine the colour base items with an array of skills and specialisations to build powerful variants. Combine colours with base skills and specialisations to build an array of powerful variants.
</p> </p>
</div> </div>
<div>&nbsp;</div> <div>&nbsp;</div>
@ -122,7 +122,7 @@ function Demo(args) {
<ConstructAvatar construct={c} /> <ConstructAvatar construct={c} />
<div class="skills"> <div class="skills">
{equipped {equipped
? <button>Strike</button> ? <button>{combo}</button>
: <button disabled={!equipping} class={btnClass}>SKILL</button> : <button disabled={!equipping} class={btnClass}>SKILL</button>
} }
<button disabled={!equipping} class={btnClass}>SKILL</button> <button disabled={!equipping} class={btnClass}>SKILL</button>
@ -179,10 +179,10 @@ function Demo(args) {
}; };
return ( return (
<section class='demo news bottom'> <section class='demo news top'>
{gameDemo()}
{vboxDemo()} {vboxDemo()}
{vboxConstructs()} {vboxConstructs()}
{gameDemo()}
</section> </section>
); );
} }

View File

@ -1,34 +1,25 @@
const preact = require('preact'); const preact = require('preact');
const { connect } = require('preact-redux'); const { connect } = require('preact-redux');
const actions = require('../actions');
const { ConstructAvatar } = require('./construct'); const { ConstructAvatar } = require('./construct');
const Controls = require('./controls');
const addState = connect( const addState = connect(
function receiveState(state) { function receiveState(state) {
const { const {
ws,
instance, instance,
account, account,
} = state; } = state;
function sendInstanceReady() {
return ws.sendInstanceReady(instance.id);
}
return { return {
instance, instance,
account, account,
sendInstanceReady,
}; };
}, }
); );
function FaceoffConstruct(args) { function FaceoffConstruct(args) {
const { const {
construct construct,
} = args; } = args;
return ( return (
@ -39,14 +30,20 @@ function FaceoffConstruct(args) {
<ConstructAvatar construct={construct} /> <ConstructAvatar construct={construct} />
</div> </div>
</div> </div>
) );
} }
function Faceoff(props) { class Faceoff extends preact.Component {
shouldComponentUpdate(newProps) {
if (newProps.instance !== this.props.instance) return true;
if (newProps.account !== this.props.account) return true;
return false;
}
render(props) {
const { const {
instance, instance,
account, account,
sendInstanceReady,
} = props; } = props;
if (!instance) return <div>...</div>; if (!instance) return <div>...</div>;
@ -59,7 +56,7 @@ function Faceoff(props) {
<FaceoffConstruct key={c.id} construct={c}/>); <FaceoffConstruct key={c.id} construct={c}/>);
const winner = instance.winner === team.id; const winner = instance.winner === team.id;
const classes = `team player ${winner ? 'winner' : team.ready ? 'ready' : ''}` const classes = `team player ${winner ? 'winner' : team.ready ? 'ready' : ''}`;
return ( return (
<div class={classes}> <div class={classes}>
{constructs} {constructs}
@ -73,7 +70,7 @@ function Faceoff(props) {
<FaceoffConstruct key={c.id} construct={c}/>); <FaceoffConstruct key={c.id} construct={c}/>);
const winner = instance.winner === team.id; const winner = instance.winner === team.id;
const classes = `team opponent ${winner ? 'winner' : team.ready ? 'ready' : ''}` const classes = `team opponent ${winner ? 'winner' : team.ready ? 'ready' : ''}`;
return ( return (
<div class={classes}> <div class={classes}>
@ -109,5 +106,6 @@ function Faceoff(props) {
</main> </main>
); );
} }
}
module.exports = addState(Faceoff); module.exports = addState(Faceoff);

View File

@ -24,6 +24,7 @@ const addState = connect(
animText, animText,
gameSkillInfo, gameSkillInfo,
itemInfo, itemInfo,
tutorialGame,
} = state; } = state;
function selectSkillTarget(targetConstructId) { function selectSkillTarget(targetConstructId) {
@ -33,10 +34,6 @@ const addState = connect(
return false; return false;
} }
// intercept self casting skills
if (activeSkill && activeSkill.skill.self_targeting) {
ws.sendGameSkill(game.id, activeSkill.constructId, null, activeSkill.skill.skill);
}
return { return {
game, game,
account, account,
@ -47,6 +44,7 @@ const addState = connect(
selectSkillTarget, selectSkillTarget,
gameSkillInfo, gameSkillInfo,
itemInfo, itemInfo,
tutorialGame,
}; };
}, },
@ -55,7 +53,11 @@ const addState = connect(
dispatch(actions.setGameEffectInfo(info)); dispatch(actions.setGameEffectInfo(info));
} }
return { setGameEffectInfo }; function setTutorialGameClear(activeSkill, tutorialGame) {
if (activeSkill && tutorialGame) dispatch(actions.setTutorialGame(null));
}
return { setGameEffectInfo, setTutorialGameClear };
} }
); );
@ -83,20 +85,36 @@ class GameConstruct extends Component {
this.resolvedLength = 0; this.resolvedLength = 0;
} }
shouldComponentUpdate(newProps) {
if (newProps.activeSkill !== this.props.activeSkill) return true;
if (newProps.animFocus !== this.props.animFocus) return true;
if (newProps.animText !== this.props.animText) return true;
if (newProps.animating !== this.props.animating) return true;
if (newProps.construct !== this.props.construct) return true;
if (newProps.player !== this.props.player) return true;
if (newProps.tutorialGame !== this.props.tutorialGame) return true;
if (newProps.gameSkillInfo !== this.props.gameSkillInfo) return true;
return false;
}
render() { render() {
const { const {
i, // Changing state variables
activeSkill,
animFocus,
animText,
animating, animating,
construct, construct,
player, player,
activeSkill, tutorialGame,
selectSkillTarget,
animFocus,
animText,
setGameEffectInfo,
gameSkillInfo, gameSkillInfo,
// Constants
i,
itemInfo, itemInfo,
// Functions
selectSkillTarget,
setTutorialGameClear,
setGameEffectInfo,
} = this.props; } = this.props;
const ko = construct.green_life.value === 0 ? 'ko' : ''; const ko = construct.green_life.value === 0 ? 'ko' : '';
@ -142,11 +160,14 @@ class GameConstruct extends Component {
> {c.effect} - {c.duration}T</div>) > {c.effect} - {c.duration}T</div>)
: null; : null;
return (<div class="effects"> {effects} </div>); return (<div class="effects"> {effects} </div>);
} };
return ( return (
<div <div
onClick={() => selectSkillTarget(construct.id)} onClick={() => {
selectSkillTarget(construct.id);
setTutorialGameClear(activeSkill, tutorialGame);
}}
style={ activeSkill ? { cursor: 'pointer' } : {}} style={ activeSkill ? { cursor: 'pointer' } : {}}
class={`game-construct ${ko} ${classes}`} > class={`game-construct ${ko} ${classes}`} >
<div class="left"> <div class="left">

View File

@ -46,7 +46,6 @@ const addState = connect(
function quit() { function quit() {
dispatch(actions.setNav('transition')); dispatch(actions.setNav('transition'));
dispatch(actions.setGame(null)); dispatch(actions.setGame(null));
dispatch(actions.setInstance(null));
} }
function setChatShow(v) { function setChatShow(v) {

View File

@ -8,6 +8,7 @@ const addState = connect(
const { const {
ws, ws,
game, game,
animating,
account, account,
} = state; } = state;
@ -25,17 +26,19 @@ const addState = connect(
sendAbandon, sendAbandon,
sendDraw, sendDraw,
animating,
}; };
}, },
function receiveDispatch(dispatch) { function receiveDispatch(dispatch) {
function leave() { function leave() {
dispatch(actions.setNav('play')); dispatch(actions.setNav('play'));
dispatch(actions.setGame(null)); dispatch(actions.setGame(null));
dispatch(actions.setInstance(null));
} }
return { leave }; return { leave };
} }
); );
function GameCtrlTopBtns(args) { function GameCtrlTopBtns(args) {
@ -46,6 +49,7 @@ function GameCtrlTopBtns(args) {
leave, leave,
sendAbandon, sendAbandon,
sendDraw, sendDraw,
animating,
} = args; } = args;
const finished = game && game.phase === 'Finished'; const finished = game && game.phase === 'Finished';
@ -70,14 +74,14 @@ function GameCtrlTopBtns(args) {
const abandonText = abandonState ? 'Confirm' : 'Abandon'; const abandonText = abandonState ? 'Confirm' : 'Abandon';
const abandonAction = abandonState ? sendAbandon : abandonStateTrue; const abandonAction = abandonState ? sendAbandon : abandonStateTrue;
const abandonBtn = <button class={abandonClasses} disabled={finished} onClick={abandonAction}>{abandonText}</button>; const abandonBtn = <button class={abandonClasses} disabled={finished || animating} onClick={abandonAction}>{abandonText}</button>;
const drawClasses = `draw ${drawState || drawOffered ? 'confirming' : ''}`; const drawClasses = `draw ${drawState || drawOffered ? 'confirming' : ''}`;
const drawText = drawOffered const drawText = drawOffered
? 'Offered' ? 'Offered'
: drawState ? 'Draw' : 'Offer'; : drawState ? 'Draw' : 'Offer';
const drawAction = drawState ? sendDraw : drawStateTrue; const drawAction = drawState ? sendDraw : drawStateTrue;
const drawBtn = <button class={drawClasses} disabled={finished || drawOffered} onClick={drawAction}>{drawText}</button>; const drawBtn = <button class={drawClasses} disabled={finished || animating || drawOffered} onClick={drawAction}>{drawText}</button>;
return ( return (
<div class="instance-ctrl-btns"> <div class="instance-ctrl-btns">

View File

@ -1,34 +1,65 @@
const preact = require('preact'); const preact = require('preact');
const range = require('lodash/range');
const reactStringReplace = require('react-string-replace'); const reactStringReplace = require('react-string-replace');
const specThresholds = require('./info.thresholds');
const { INFO } = require('./../constants'); const { INFO } = require('./../constants');
const { convertItem, removeTier } = require('../utils'); const { convertItem, removeTier } = require('../utils');
const { tutorialStage } = require('../tutorial.utils');
const shapes = require('./shapes'); const shapes = require('./shapes');
function InfoComponent(args) {
class InfoComponent extends preact.Component {
shouldComponentUpdate(newProps) {
if (newProps.tutorial !== this.props.tutorial) return true;
// We don't care about info during tutorial
if (newProps.tutorial && this.props.instance.time_control === 'Practice'
&& this.props.instance.rounds.length === 1) return false;
if (newProps.info !== this.props.info) return true;
return false;
}
render(args) {
const { const {
itemInfo, // Variables that will change
player,
info, info,
tutorial,
// Static
player, // Only used for colour calcs which will be update if info changes
ws,
itemInfo,
instance, // Only used for instance id
// functions
setInfo,
setTutorialNull,
} = args; } = args;
// args.info = 'Life';
// const { info } = args;
function Info() { function Info() {
if (!info) { if (tutorial) {
const tutorialStageInfo = tutorialStage(tutorial, ws, setTutorialNull, instance);
if (tutorialStageInfo) return tutorialStageInfo;
}
if (!info) return false;
if (info.includes('constructName')) {
return ( return (
<div> <div>
<h2>VBOX phase</h2> <h2> {info.replace('constructName ', '')} </h2>
<p>Strengthen and specialise your constructs by equipping items to them.</p> <p> This is the name of your construct. <br />
<p>Double click to purchase items in the <b>VBOX</b> and move them to your <b>INVENTORY</b>.</p> Names are randomly generated and are purely cosmetic. <br />
<p> You can change change your construct name in the <b>RESHAPE</b> tab outside of games.
Combine a <b>SKILL</b> or <b>SPEC</b> with 2 <b>COLOURS</b> to create an item.<br /> </p>
Combine <b>3 of the same item</b> to upgrade it.<br /> </div>
Click an item and then click a construct to <b>equip</b> that item to it.<br /> );
}
if (info.includes('constructAvatar')) {
return (
<div>
<h2> {info.replace('constructAvatar ', '')} </h2>
<p> This is your construct avatar. <br />
Avatars are randomly generated and are purely cosmetic. <br />
You can change your construct avatar in the <b>RESHAPE</b> tab outside of games.
</p> </p>
<p>Click the <b>READY</b> button for the <b>GAME PHASE</b>.</p>
</div> </div>
); );
} }
@ -37,157 +68,81 @@ function InfoComponent(args) {
const isSkill = fullInfo.skill; const isSkill = fullInfo.skill;
const isSpec = fullInfo.spec; const isSpec = fullInfo.spec;
if (isSkill) { const itemDescription = () => {
const regEx = /(RedPower|BluePower|GreenPower|RedLife|BlueLife|GreenLife|SpeedStat)/; const regEx = /(RedPower|BluePower|GreenPower|RedLife|BlueLife|GreenLife|SpeedStat|LIFE|SPEED|POWER)/;
const infoDescription = reactStringReplace(fullInfo.description, regEx, match => shapes[match]()); const infoDescription = reactStringReplace(fullInfo.description, regEx, m => shapes[m]());
const itemSource = itemInfo.combos.filter(c => c.item === removeTier(info)); return <div>{reactStringReplace(infoDescription, '\n', () => <br />)}</div>;
const itemSourceInfo = itemSource.length
? `${itemSource[0].components[0]} ${itemSource[0].components[1]} ${itemSource[0].components[2]}`
: false;
const itemRegEx = /(Red|Blue|Green)/;
const itemSourceDescription = reactStringReplace(itemSourceInfo, itemRegEx, match => shapes[match]());
const speed = <div> Speed {shapes.SpeedStat()} multiplier {fullInfo.speed * 4}% </div>;
const cooldown = fullInfo.cooldown ? `${fullInfo.cooldown} Turn delay` : null;
return (
<div class="info-skill">
<h2>{fullInfo.item} - {fullInfo.cost}b</h2>
<h3> SKILL </h3>
{itemSourceDescription}
<div> {cooldown} </div>
<div>{infoDescription}</div>
{speed}
</div>
);
}
if (isSpec) {
let red = 0;
let blue = 0;
let green = 0;
player.constructs.forEach(construct => {
red += construct.colours.red;
blue += construct.colours.blue;
green += construct.colours.green;
});
const teamColours = { red, blue, green };
const colourReqs = fullInfo.values.bonuses || [];
const thresholds = colourReqs.map((bonus, i) => {
const colours = ['red', 'green', 'blue'];
const colourGoals = {
red: [],
green: [],
blue: [],
}; };
const overFlow = [];
colours.forEach(c => { if (isSkill || isSpec) {
const colourReq = bonus.req[c]; let infoName = info;
if (colourReqs === 0) return false; while (infoName.includes('Plus')) infoName = infoName.replace('Plus', '+');
const start = i === 0 const header = isSkill ? <h3> SKILL </h3> : <h3> SPEC </h3>;
? 0
: colourReqs[i - 1].req[c];
const dots = range(start, colourReq).map(j => { const itemSource = itemInfo.combos.filter(c => c.item === removeTier(info));
const unmet = teamColours[c] < j + 1; let itemSourceInfo = itemSource.length
const reqClass = unmet
? 'unmet'
: '';
if (j - start > 4) {
overFlow.push(
<figure key={j} alt={c.colour} class={reqClass} >
{shapes.vboxColour(c)}
</figure>
);
} else {
colourGoals[c].push(
<figure key={j} alt={c.colour} class={reqClass} >
{shapes.vboxColour(c)}
</figure>
);
}
return true;
});
return dots;
});
const reqsMet = colours.every(c => teamColours[c] >= bonus.req[c]);
const reqClass = reqsMet
? ''
: 'unmet';
const goals = colours.map((c, j) => {
if (colourGoals[c].length) {
return (
<div key={j}>{colourGoals[c]}</div>
);
}
return false;
});
const bonusObj = info.includes('Life')
? <div class={`${reqClass} bonus`} > + {bonus.bonus}</div>
: <div class={`${reqClass} bonus`} > + {bonus.bonus}%</div>;
const overFlowObj = overFlow.length ? <div> {overFlow} </div> : null;
return (
<div key={i} class="spec-goal">
{goals}
{overFlowObj}
{bonusObj}
</div>
);
});
const regEx = /(RedPower|BluePower|GreenPower|RedLife|BlueLife|GreenLife|SpeedStat)/;
const infoDescription = reactStringReplace(fullInfo.description, regEx, match => shapes[match]());
const itemSource = itemInfo.combos.filter(c => c.item === info);
const itemSourceInfo = itemSource.length
? `${itemSource[0].components[0]} ${itemSource[0].components[1]} ${itemSource[0].components[2]}` ? `${itemSource[0].components[0]} ${itemSource[0].components[1]} ${itemSource[0].components[2]}`
: false; : false;
if (itemSourceInfo) {
while (itemSourceInfo.includes('Plus')) itemSourceInfo = itemSourceInfo.replace('Plus', '+');
const itemRegEx = /(Red|Blue|Green)/; const itemRegEx = /(Red|Blue|Green)/;
const itemSourceDescription = reactStringReplace(itemSourceInfo, itemRegEx, match => shapes[match]()); itemSourceInfo = reactStringReplace(itemSourceInfo, itemRegEx, match => shapes[match]());
const infoText = info.replace('Plus', '+'); }
const cooldown = isSkill && fullInfo.cooldown ? <div>{fullInfo.cooldown} Turn delay</div> : null;
const speed = isSkill
? <div> Speed {shapes.SpeedStat()} multiplier {fullInfo.speed * 4}% </div>
: null;
const thresholds = isSpec ? specThresholds(player, fullInfo, info) : null;
return ( return (
<div class="info-spec"> <div class={isSkill ? 'info-skill' : 'info-spec'}>
<h2>{infoText} - {fullInfo.cost}b</h2> <h2>{infoName} {fullInfo.cost}b</h2>
<h3>SPEC</h3> {header}
{itemSourceDescription} {itemSourceInfo}
<div>{infoDescription}</div> {cooldown}
<div class="thresholds"> {itemDescription()}
{speed}
{thresholds} {thresholds}
</div> </div>
</div>
); );
} }
const cost = fullInfo.cost ? `- ${fullInfo.cost}b` : false; const cost = fullInfo.cost ? `- ${fullInfo.cost}b` : false;
return ( return (
<div class="info-item"> <div class="info-item">
<h2>{fullInfo.item} {cost}</h2> <h2>{fullInfo.item} {cost}</h2>
<div>{fullInfo.description}</div> {itemDescription()}
</div> </div>
); );
} }
function Combos() { function Combos() {
if (!player) return false; if (tutorial && instance.time_control === 'Practice' && instance.rounds.length === 1) return false;
if (!info) return false; const generalNotes = (
<div>
<h2> General </h2>
<p>
You can preview combos by clicking the combined item when it appears in this section. <br />
Click the <b>READY</b> button to start the <b>GAME PHASE</b>.
</p>
</div>
);
if (!player) return generalNotes;
if (!info) return generalNotes;
const vboxCombos = itemInfo.combos.filter(c => c.components.includes(info)); const vboxCombos = itemInfo.combos.filter(c => c.components.includes(info));
if (vboxCombos.length > 6) return false; if (vboxCombos.length > 6 || vboxCombos.length === 0) return generalNotes;
return ( return (
<table class="combos"> <table class="combos">
<tbody> <tbody>
{vboxCombos.map((c, i) => {vboxCombos.map((c, i) =>
<tr key={i} > <tr key={i} >
<td class="highlight" >{convertItem(c.item)}</td> <td class="highlight" onClick={() => setInfo(c.item)} >{convertItem(c.item)}</td>
{c.components.map((u, j) => <td key={j} >{convertItem(u)}</td>)} {c.components.map((u, j) => <td key={j} >{convertItem(u)}</td>)}
</tr> </tr>
)} )}
@ -203,5 +158,6 @@ function InfoComponent(args) {
</div> </div>
); );
} }
}
module.exports = InfoComponent; module.exports = InfoComponent;

View File

@ -13,6 +13,7 @@ const addState = connect(
instance, instance,
player, player,
account, account,
tutorial,
} = state; } = state;
return { return {
@ -23,8 +24,22 @@ const addState = connect(
instance, instance,
player, player,
account, account,
tutorial,
}; };
}, },
function receiveDispatch(dispatch) {
function setTutorialNull() {
dispatch(actions.setTutorial(null));
}
function setInfo(info) {
dispatch(actions.setInfo(info));
}
return { setTutorialNull, setInfo };
}
); );
module.exports = addState(Info); module.exports = addState(Info);

View File

@ -0,0 +1,96 @@
const preact = require('preact');
const range = require('lodash/range');
const shapes = require('./shapes');
function specThresholds(player, fullInfo, info) {
let red = 0;
let blue = 0;
let green = 0;
player.constructs.forEach(construct => {
red += construct.colours.red;
blue += construct.colours.blue;
green += construct.colours.green;
});
const teamColours = { red, blue, green };
const colourReqs = fullInfo.values.bonuses || [];
const thresholds = colourReqs.map((bonus, i) => {
const colours = ['red', 'green', 'blue'];
const colourGoals = {
red: [],
green: [],
blue: [],
};
const overFlow = [];
colours.forEach(c => {
const colourReq = bonus.req[c];
if (colourReqs === 0) return false;
const start = i === 0
? 0
: colourReqs[i - 1].req[c];
const dots = range(start, colourReq).map(j => {
const unmet = teamColours[c] < j + 1;
const reqClass = unmet
? 'unmet'
: '';
if (j - start > 4) {
overFlow.push(
<figure key={j} alt={c.colour} class={reqClass} >
{shapes.vboxColour(c)}
</figure>
);
} else {
colourGoals[c].push(
<figure key={j} alt={c.colour} class={reqClass} >
{shapes.vboxColour(c)}
</figure>
);
}
return true;
});
return dots;
});
const reqsMet = colours.every(c => teamColours[c] >= bonus.req[c]);
const reqClass = reqsMet
? ''
: 'unmet';
const goals = colours.map((c, j) => {
if (colourGoals[c].length) {
return (
<div key={j}>{colourGoals[c]}</div>
);
}
return false;
});
const bonusObj = info.includes('Life')
? <div class={`${reqClass} bonus`} > + {bonus.bonus}</div>
: <div class={`${reqClass} bonus`} > + {bonus.bonus}%</div>;
const overFlowObj = overFlow.length ? <div> {overFlow} </div> : null;
return (
<div key={i} class="spec-goal">
{goals}
{overFlowObj}
{bonusObj}
</div>
);
});
return (
<div class="thresholds">
{thresholds}
</div>
);
}
module.exports = specThresholds;

View File

@ -7,18 +7,19 @@ const Hammer = require('hammerjs');
const Vbox = require('./vbox.component'); const Vbox = require('./vbox.component');
const InfoContainer = require('./info.container'); const InfoContainer = require('./info.container');
const InstanceConstructsContainer = require('./instance.constructs'); const InstanceConstructsContainer = require('./instance.constructs');
// const EquipmentContainer = require('./instance.equip');
const Faceoff = require('./faceoff'); const Faceoff = require('./faceoff');
const actions = require('../actions'); const actions = require('../actions');
const addState = connect( const addState = connect(
function receiveState(state) { function receiveState(state) {
const { instance, const {
instance,
nav, nav,
navInstance, navInstance,
} = state; } = state;
return { instance, return {
instance,
nav, nav,
navInstance, navInstance,
}; };
@ -53,10 +54,14 @@ const addState = connect(
); );
class Instance extends Component { class Instance extends Component {
shouldComponentUpdate(newProps) {
if (newProps.instance !== this.props.instance) return true;
return false;
}
render(args) { render(args) {
const { const {
instance, instance,
setInfo,
clearItems, clearItems,
} = args; } = args;
@ -76,7 +81,7 @@ class Instance extends Component {
} }
return ( return (
<main id="instance" class='instance' onClick={instanceClick} onMouseOver={() => setInfo(null)}> <main id="instance" class='instance' onClick={instanceClick}>
<Vbox /> <Vbox />
<InfoContainer /> <InfoContainer />
<InstanceConstructsContainer /> <InstanceConstructsContainer />

View File

@ -8,6 +8,7 @@ const { STATS } = require('../utils');
const { ConstructAvatar } = require('./construct'); const { ConstructAvatar } = require('./construct');
const actions = require('../actions'); const actions = require('../actions');
const { removeTier } = require('../utils'); const { removeTier } = require('../utils');
const { tutorialConstructDisplay, tutorialShouldDisableEquip } = require('../tutorial.utils.jsx');
const addState = connect( const addState = connect(
function receiveState(state) { function receiveState(state) {
@ -18,8 +19,8 @@ const addState = connect(
account, account,
itemInfo, itemInfo,
itemEquip, itemEquip,
activeConstruct,
navInstance, navInstance,
tutorial,
} = state; } = state;
function sendVboxApply(constructId, i) { function sendVboxApply(constructId, i) {
@ -38,8 +39,8 @@ const addState = connect(
itemInfo, itemInfo,
itemEquip, itemEquip,
navInstance, navInstance,
activeConstruct,
sendUnequip, sendUnequip,
tutorial,
}; };
}, },
@ -56,10 +57,6 @@ const addState = connect(
dispatch(actions.setActiveConstruct(value)); dispatch(actions.setActiveConstruct(value));
} }
function clearInfo() {
return dispatch(actions.setInfo(null));
}
function setItemEquip(v) { function setItemEquip(v) {
return dispatch(actions.setItemEquip(v)); return dispatch(actions.setItemEquip(v));
} }
@ -68,29 +65,45 @@ const addState = connect(
return dispatch(actions.setItemUnequip(v)); return dispatch(actions.setItemUnequip(v));
} }
return { quit, clearInfo, setInfo, setActiveConstruct, setItemUnequip, setItemEquip }; return { quit, setInfo, setActiveConstruct, setItemUnequip, setItemEquip };
} }
); );
function Construct(props) { function Construct(props) {
const { const {
itemEquip, // Changing state variables
construct, construct,
iter,
itemEquip,
instance,
mobileVisible,
player, player,
tutorial,
// Static Info
itemInfo,
// Function Calls
sendVboxApply, sendVboxApply,
sendUnequip,
setActiveConstruct, setActiveConstruct,
setItemUnequip, setItemUnequip,
setItemEquip, setItemEquip,
itemInfo,
setInfo, setInfo,
sendUnequip,
mobileVisible,
} = props; } = props;
const { vbox } = player;
const duplicateSkill = construct.skills.length !== 0 && construct.skills.every(sk => {
if (!itemEquip && itemEquip !== 0) return false;
if (!sk) return false;
return sk.skill === vbox.bound[itemEquip];
});
const tutorialDisableEquip = tutorialShouldDisableEquip(tutorial, iter, instance, construct);
function onClick(e) { function onClick(e) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
if (duplicateSkill || tutorialDisableEquip) return true;
if (itemEquip !== null) sendVboxApply(construct.id, itemEquip); if (itemEquip !== null) sendVboxApply(construct.id, itemEquip);
setItemEquip(null); setItemEquip(null);
return setActiveConstruct(construct); return setActiveConstruct(construct);
@ -102,7 +115,6 @@ function Construct(props) {
return setInfo(info); return setInfo(info);
} }
const { vbox } = player;
const skillList = itemInfo.items.filter(v => v.skill).map(v => v.item); const skillList = itemInfo.items.filter(v => v.skill).map(v => v.item);
const specList = itemInfo.items.filter(v => v.spec).map(v => v.item); const specList = itemInfo.items.filter(v => v.spec).map(v => v.item);
@ -130,8 +142,8 @@ function Construct(props) {
return true; return true;
} }
// const action = skill ? '' : 'action'; const equipping = skillList.includes(vbox.bound[itemEquip]) && !skill
const equipping = skillList.includes(vbox.bound[itemEquip]) && !skill; && !tutorialDisableEquip && !duplicateSkill;
const border = () => { const border = () => {
if (!skill) return ''; if (!skill) return '';
const borderFn = buttons[removeTier(skill.skill)]; const borderFn = buttons[removeTier(skill.skill)];
@ -166,8 +178,6 @@ function Construct(props) {
); );
} }
const specInfo = itemInfo.items.find(i => i.item === s);
function specClick(e) { function specClick(e) {
e.stopPropagation(); e.stopPropagation();
setItemUnequip([construct.id, s]); setItemUnequip([construct.id, s]);
@ -211,11 +221,11 @@ function Construct(props) {
}); });
const classes = `instance-construct ${mobileVisible ? 'visible' : ''}`; const classes = `instance-construct ${mobileVisible ? 'visible' : ''}`;
const avatarMouseOver = e => hoverInfo(e, `constructAvatar ${construct.name}`);
return ( return (
<div key={construct.id} class={classes} onClick={onClick}> <div key={construct.id} class={classes} onClick={onClick}>
<ConstructAvatar construct={construct} /> <ConstructAvatar construct={construct} mouseOver={avatarMouseOver}/>
<h2 class="name" >{construct.name}</h2> <h2 class="name" onMouseOver={e => hoverInfo(e, `constructName ${construct.name}`)}>{construct.name}</h2>
<div class="skills" onMouseOver={e => hoverInfo(e, 'constructSkills')} > <div class="skills" onMouseOver={e => hoverInfo(e, 'constructSkills')} >
{skills} {skills}
</div> </div>
@ -229,32 +239,49 @@ function Construct(props) {
); );
} }
function InstanceConstructs(props) { class InstanceConstructs extends preact.Component {
shouldComponentUpdate(newProps) {
if (newProps.itemEquip !== this.props.itemEquip) return true;
if (newProps.tutorial !== this.props.tutorial) return true;
if (newProps.navInstance !== this.props.navInstance) return true;
// JSON or Array objects
if (newProps.player !== this.props.player) return true;
if (newProps.instance !== this.props.instance) return true;
return false;
}
render(props) {
const { const {
activeConstruct, // Changing state variables
itemEquip, itemEquip,
player,
instance, instance,
// clearInfo, navInstance,
player,
tutorial,
// Static data
itemInfo,
// Function calls
setInfo, setInfo,
setActiveConstruct, setActiveConstruct,
sendVboxApply, sendVboxApply,
itemInfo,
setVboxHighlight, setVboxHighlight,
setItemUnequip, setItemUnequip,
setItemEquip, setItemEquip,
sendUnequip, sendUnequip,
navInstance,
} = props; } = props;
if (!player) return false; if (!player) return false;
if (instance.phase === 'Lobby') return false; if (instance.phase === 'Lobby') return false;
const constructs = player.constructs.map((c, i) => Construct({ const constructs = range(0, 3).map(i => {
construct: c, const tutorialConstruct = tutorialConstructDisplay(player, instance, tutorial, navInstance, i);
activeConstruct, if (tutorialConstruct) return (tutorialConstruct);
return Construct({
iter: i,
construct: player.constructs[i],
itemEquip, itemEquip,
instance,
setItemUnequip, setItemUnequip,
setItemEquip, setItemEquip,
player, player,
@ -264,15 +291,17 @@ function InstanceConstructs(props) {
itemInfo, itemInfo,
setVboxHighlight, setVboxHighlight,
sendUnequip, sendUnequip,
tutorial,
mobileVisible: navInstance === i + 1, mobileVisible: navInstance === i + 1,
})); });
});
const classes = `construct-list`;
return ( return (
<div class={classes} onClick={() => setActiveConstruct(null)}> <div class='construct-list' onClick={() => setActiveConstruct(null)}>
{constructs} {constructs}
</div> </div>
); );
} }
}
module.exports = addState(InstanceConstructs); module.exports = addState(InstanceConstructs);

View File

@ -8,6 +8,7 @@ const addState = connect(
const { const {
ws, ws,
instance, instance,
tutorial,
} = state; } = state;
function sendAbandon() { function sendAbandon() {
@ -16,15 +17,16 @@ const addState = connect(
return { return {
instance, instance,
tutorial,
sendAbandon, sendAbandon,
}; };
}, },
function receiveDispatch(dispatch) { function receiveDispatch(dispatch) {
function leave() { function leave(tutorial) {
dispatch(actions.setNav('play')); dispatch(actions.setNav('play'));
dispatch(actions.setGame(null)); dispatch(actions.setGame(null));
dispatch(actions.setInstance(null)); dispatch(actions.setInstance(null));
if (tutorial) dispatch(actions.setTutorial(1));
} }
return { leave }; return { leave };
@ -37,6 +39,7 @@ function InstanceTopBtns(args) {
leave, leave,
sendAbandon, sendAbandon,
tutorial,
} = args; } = args;
const finished = instance && instance.phase === 'Finished'; const finished = instance && instance.phase === 'Finished';
@ -53,7 +56,7 @@ function InstanceTopBtns(args) {
const abandonAction = abandonState ? sendAbandon : abandonStateTrue; const abandonAction = abandonState ? sendAbandon : abandonStateTrue;
const abandonBtn = <button class={abandonClasses} disabled={finished} onClick={abandonAction}>{abandonText}</button>; const abandonBtn = <button class={abandonClasses} disabled={finished} onClick={abandonAction}>{abandonText}</button>;
const leaveBtn = <button class='abandon confirming' onClick={leave}>Leave</button>; const leaveBtn = <button class='abandon confirming' onClick={() => leave(tutorial)}>Leave</button>;
return ( return (
<div class="instance-ctrl-btns"> <div class="instance-ctrl-btns">

View File

@ -1,144 +0,0 @@
const { connect } = require('preact-redux');
const preact = require('preact');
const range = require('lodash/range');
const actions = require('../actions');
const shapes = require('./shapes');
const { convertItem } = require('./../utils');
const addState = connect(
function receiveState(state) {
const { account, activeConstruct, itemInfo, info, ws, instance, player, itemUnequip } = state;
function sendUnequip(constructId, item) {
return ws.sendVboxUnequip(instance.id, constructId, item);
}
return { player, itemInfo, instance, info, sendUnequip, activeConstruct, itemUnequip };
},
function receiveDispatch(dispatch) {
function setInfo(item) {
dispatch(actions.setInfo(item));
}
function clearInfo() {
return dispatch(actions.setInfo(null));
}
function setItemEquip(v) {
return dispatch(actions.setItemEquip(v));
}
function setItemUnequip(v) {
return dispatch(actions.setItemUnequip(v));
}
return { setInfo, setItemEquip, setItemUnequip, clearInfo };
}
);
function Equipment(props) {
const {
player,
instance,
itemUnequip,
setItemEquip,
setItemUnequip,
activeConstruct,
itemInfo,
sendUnequip,
setInfo,
} = props;
if (instance.phase === 'Lobby') return false;
const { vbox } = player;
const fullInfo = itemInfo.items.find(i => i.item === itemUnequip);
const isSkill = fullInfo && fullInfo.skill;
const isSpec = fullInfo && fullInfo.spec;
function skillClick(e, i) {
if (itemUnequip && activeConstruct) return false;
// const value = vbox.bound[i];
setItemEquip(i);
return false;
}
function unequipClick(e) {
e.stopPropagation();
if (!itemUnequip) return false;
if (!activeConstruct) return false;
setItemUnequip([]);
return sendUnequip(activeConstruct.id, itemUnequip);
}
function hoverInfo(e, info) {
e.stopPropagation();
return setInfo(info);
}
const skillClass = isSkill ? 'skills highlight' : 'skills';
const specClass = isSpec ? 'specs highlight' : 'specs';
const skills = range(0, 9).map(i => {
const item = convertItem(vbox.bound[i]);
const skillInfo = itemInfo.items.find(i => i.item === item);
if (skillInfo && skillInfo.skill) {
return (
<button key={i} onClick={e => skillClick(e, i)} onMouseOver={e => hoverInfo(e, item)}>
{item}
</button>
);
}
return false;
});
const specs = range(0, 9).map(i => {
const item = convertItem(vbox.bound[i]);
const specInfo = itemInfo.items.find(i => i.item === item);
if (specInfo && specInfo.spec) {
return (
<figure key={i} onClick={e => skillClick(e, i)} onMouseOver={e => hoverInfo(e, item)} >
{shapes[item]()}
<figcaption>{item ? item : '-'}</figcaption>
</figure>
);
}
return false;
});
if (skills.every(s => !s)) skills.push(<button disabled={true}>&nbsp;</button>);
if (specs.every(s => !s)) {
specs.push(
<figure>
{shapes.diamond('gray')}
<figcaption>&nbsp;</figcaption>
</figure>
);
}
return (
<div class="equip" >
<div class={skillClass} onClick={e => unequipClick(e)} onMouseOver={e => hoverInfo(e, 'equipSkills')} >
<h3>Skills</h3>
<div class ="items">
{skills}
</div>
</div>
<div class={specClass} onClick={e => unequipClick(e)} onMouseOver={e => hoverInfo(e, 'equipSpecs')} >
<h3>Specs</h3>
<div class ="items">
{specs}
</div>
</div>
</div>
);
}
module.exports = addState(Equipment);

View File

@ -16,7 +16,16 @@ const addState = connect(
} }
); );
function Main(props) { class Main extends preact.Component {
shouldComponentUpdate(newProps) {
if (newProps.game !== this.props.game) return true;
if (newProps.instance !== this.props.instance) return true;
if (newProps.account !== this.props.account) return true;
if (newProps.nav !== this.props.nav) return true;
return false;
}
render(props) {
const { const {
game, game,
instance, instance,
@ -46,5 +55,6 @@ function Main(props) {
</main> </main>
); );
} }
}
module.exports = addState(Main); module.exports = addState(Main);

View File

@ -10,11 +10,21 @@ const addState = connect(
state => ({ showNav: state.showNav }) state => ({ showNav: state.showNav })
); );
const Mnml = ({ showNav }) => class Mnml extends preact.Component {
<div id="mnml" class={showNav ? 'nav-visible' : ''}> shouldComponentUpdate(newProps) {
if (newProps.showNav !== this.props.showNav) return true;
return false;
}
render(args) {
return (
<div id="mnml" class={args.showNav ? 'nav-visible' : ''}>
<Main /> <Main />
<Controls /> <Controls />
<Footer /> <Footer />
</div>; </div>
);
}
}
module.exports = addState(Mnml); module.exports = addState(Mnml);

View File

@ -1,41 +0,0 @@
const config = {
particles: {
number: { value: 300, density: { enable: true, value_area: 800 } },
color: { value: '#ffffff' },
shape: {
type: 'circle',
stroke: { width: 0, color: '#000000' },
polygon: { nb_sides: 5 },
image: { src: 'img/github.svg', width: 100, height: 100 }
},
opacity: {
value: 0.5,
random: false,
anim: { enable: false, speed: 1, opacity_min: 0.1, sync: false },
},
size: {
value: 3,
random: true,
anim: { enable: false, speed: 40, size_min: 0.1, sync: false },
},
line_linked: {
enable: true,
distance: 150,
color: '#ffffff',
opacity: 0.4,
width: 1,
},
move: {
enable: true,
speed: 6,
direction: 'none',
random: false,
straight: false,
out_mode: 'out',
bounce: false,
attract: { enable: false, rotateX: 600, rotateY: 1200 },
},
},
};
module.exports = config;

View File

@ -139,7 +139,8 @@ function Play(args) {
</button>; </button>;
const list = () => { const list = () => {
if (!instances.length) return ( if (!instances.length) {
return (
<div class='list play'> <div class='list play'>
<figure> <figure>
<button <button
@ -168,6 +169,7 @@ function Play(args) {
</figure> </figure>
</div> </div>
); );
}
return ( return (
<div class='list play rejoin'> <div class='list play rejoin'>
@ -181,7 +183,7 @@ function Play(args) {
</figure> </figure>
</div> </div>
); );
} };
return ( return (
<section class="top"> <section class="top">

View File

@ -35,7 +35,9 @@ module.exports = {
BluePower: () => circle(['blue']), BluePower: () => circle(['blue']),
SpeedStat: () => triangle(['white']), SpeedStat: () => triangle(['white']),
POWER: () => circle(['white']),
LIFE: () => square(['white']),
SPEED: () => triangle(['white']),
// specs // specs
// Base // Base

View File

@ -9,8 +9,8 @@ const shapes = require('./shapes');
const { effectInfo, removeTier } = require('../utils'); const { effectInfo, removeTier } = require('../utils');
const addState = connect( const addState = connect(
({ game, account, animSkill, animating, itemInfo, gameEffectInfo }) => ({ game, account, animSkill, animating, itemInfo, gameEffectInfo, tutorialGame }) =>
({ game, account, animSkill, animating, itemInfo, gameEffectInfo }) ({ game, account, animSkill, animating, itemInfo, gameEffectInfo, tutorialGame })
); );
class TargetSvg extends Component { class TargetSvg extends Component {
@ -27,10 +27,42 @@ class TargetSvg extends Component {
}, 500); }, 500);
} }
shouldComponentUpdate(newProps, newState) {
if (newProps.game !== this.props.game) return true;
if (newProps.account !== this.props.account) return true;
if (newProps.animating !== this.props.animating) return true;
if (newProps.animSkill !== this.props.animSkill) return true;
if (newProps.gameEffectInfo !== this.props.gameEffectInfo) return true;
if (newProps.tutorialGame !== this.props.tutorialGame) return true;
if (newState.width !== this.state.width) return true;
if (newState.height !== this.state.height) return true;
return false;
}
render(props, state) { render(props, state) {
const { game, account, animating, animSkill, itemInfo, gameEffectInfo } = props; const {
// Changing State Variables
account,
animating,
animSkill,
game,
gameEffectInfo,
tutorialGame,
// Static
itemInfo,
} = props;
const { width, height } = state; const { width, height } = state;
if (!game) return false; // game will be null when battle ends if (!game) return false; // game will be null when battle ends
if (game.phase === 'Finished') return false; // Clear everything if its over (in case of abandon)
// First time joining game phase
if (tutorialGame) {
return (
<div class="resolving-skill">
<h2> Select your skills, click on targets and then hit <b>ready</b>.</h2>
</div>
);
}
// Whenever someones looking at effects throw it up here // Whenever someones looking at effects throw it up here
if (gameEffectInfo) { if (gameEffectInfo) {

View File

@ -1,8 +1,9 @@
const preact = require('preact'); const preact = require('preact');
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 { connect } = require('preact-redux');
const { removeTier } = require('../utils'); const { removeTier } = require('../utils');
const shapes = require('./shapes'); const shapes = require('./shapes');
const actions = require('../actions'); const actions = require('../actions');
@ -20,7 +21,7 @@ const addState = connect(
itemInfo, itemInfo,
itemUnequip, itemUnequip,
navInstance, navInstance,
info, tutorial,
} = state; } = state;
function sendVboxDiscard() { function sendVboxDiscard() {
@ -57,7 +58,7 @@ const addState = connect(
itemUnequip, itemUnequip,
sendItemUnequip, sendItemUnequip,
navInstance, navInstance,
info, tutorial,
}; };
}, },
@ -93,31 +94,45 @@ const addState = connect(
); );
function Vbox(args) { class Vbox extends preact.Component {
shouldComponentUpdate(newProps) {
// Single variable props
if (newProps.combiner !== this.props.combiner) return true;
if (newProps.itemUnequip !== this.props.itemUnequip) return true;
if (newProps.reclaiming !== this.props.reclaiming) return true;
if (newProps.navInstance !== this.props.navInstance) 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 { const {
// Changing state variables
combiner, combiner,
navInstance, itemUnequip,
instance,
itemInfo,
player, player,
reclaiming, reclaiming,
tutorial,
navInstance,
vboxSelected,
instance,
// Static
itemInfo,
// Function Calls
sendItemUnequip,
sendVboxAccept, sendVboxAccept,
sendVboxCombine, sendVboxCombine,
sendVboxDiscard, sendVboxDiscard,
sendVboxReclaim, sendVboxReclaim,
setCombiner,
setInfo,
vboxSelected,
setVboxSelected, setVboxSelected,
setItemEquip, setItemEquip,
itemUnequip, setInfo,
sendItemUnequip, setCombiner,
setReclaiming, setReclaiming,
info,
} = args; } = args;
if (!player) return false; if (!player) return false;
@ -141,8 +156,13 @@ function Vbox(args) {
// //
function vboxHover(e, v) { function vboxHover(e, v) {
if (v) { if (v) {
if (info !== v) setInfo(v);
e.stopPropagation(); e.stopPropagation();
if (vboxSelected[0]) return true; // There is a base skill or spec selected in the vbox
if (combiner.length !== 0) {
const base = combiner.find(c => !['Red', 'Blue', 'Green'].includes(vbox.bound[c]));
if (base || base === 0) return true;
}
setInfo(v);
} }
return true; return true;
} }
@ -161,15 +181,6 @@ function Vbox(args) {
function availableBtn(v, group, index) { function availableBtn(v, group, index) {
if (!v) return <button disabled class='empty' >&nbsp;</button>; if (!v) return <button disabled class='empty' >&nbsp;</button>;
const tutorial = instance.time_control === 'Practice'
&& instance.rounds.length === 1
&& group === 0
&& combiner.length === 0
&& vboxSelected.length === 0
&& vbox.bits > 10
&& vbox.free[0].filter(c => c).length > 4
? 'combo-border' : null;
const selected = vboxSelected[0] === group && vboxSelected[1] === index; const selected = vboxSelected[0] === group && vboxSelected[1] === index;
// state not yet set in double click handler // state not yet set in double click handler
@ -185,6 +196,7 @@ function Vbox(args) {
setCombiner([]); setCombiner([]);
if (selected) return clearVboxSelected(); if (selected) return clearVboxSelected();
setInfo(vbox.free[group][index]);
return setVboxSelected([group, index]); return setVboxSelected([group, index]);
} }
@ -203,7 +215,7 @@ function Vbox(args) {
} return false; } return false;
}) ? 'combo-border' : ''; }) ? 'combo-border' : '';
const classes = `${v.toLowerCase()} ${selected ? 'highlight' : ''} ${comboHighlight} ${tutorial}`; const classes = `${v.toLowerCase()} ${selected ? 'highlight' : ''} ${comboHighlight}`;
if (shapes[v]) { if (shapes[v]) {
return ( return (
@ -235,10 +247,12 @@ function Vbox(args) {
return ( return (
<div class='vbox-vbox' <div class='vbox-vbox'
onMouseDown={() => setReclaiming(false)} onMouseDown={() => setReclaiming(false)}
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}>
onMouseOver={e => hoverInfo(e, 'vbox')}>
<div class="vbox-hdr"> <div class="vbox-hdr">
<h3 onTouchStart={e => e.target.scrollIntoView(true)}>VBOX</h3> <h3
onTouchStart={e => e.target.scrollIntoView(true)}
onMouseOver={e => hoverInfo(e, 'vbox')}> VBOX
</h3>
<div class="bits" onMouseOver={e => hoverInfo(e, 'bits')} >{vbox.bits}b</div> <div class="bits" onMouseOver={e => hoverInfo(e, 'bits')} >{vbox.bits}b</div>
</div> </div>
<div class="vbox-colours"> <div class="vbox-colours">
@ -251,6 +265,7 @@ function Vbox(args) {
<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}
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
onMouseDown={() => sendVboxDiscard()}> onMouseDown={() => sendVboxDiscard()}>
refill - 2b refill - 2b
@ -276,16 +291,6 @@ function Vbox(args) {
return <button disabled={!inventoryHighlight} class={inventoryHighlight ? 'receiving' : 'empty'} >&nbsp;</button>; return <button disabled={!inventoryHighlight} class={inventoryHighlight ? 'receiving' : 'empty'} >&nbsp;</button>;
} }
const tutorial = instance.time_control === 'Practice'
&& instance.rounds.length === 1
&& i === 0
&& combiner.length === 0
&& vboxSelected.length === 0
&& vbox.bits === 16
&& vbox.bound.length === 5
&& vbox.free[0].filter(c => c).length === 4
? 'combo-border' : null;
const combinerItems = combiner.map(j => vbox.bound[j]); const combinerItems = combiner.map(j => vbox.bound[j]);
const combinerCount = countBy(combinerItems, co => co); const combinerCount = countBy(combinerItems, co => co);
@ -306,8 +311,10 @@ function Vbox(args) {
if (reclaiming) return sendVboxReclaim(i); if (reclaiming) return sendVboxReclaim(i);
// 4 things selected // 4 things selected
if (combiner.length > 2) return combinerChange([i]); if (combiner.length > 2) {
setInfo(vbox.bound[i]);
return combinerChange([i]);
}
// removing // removing
const combinerIndex = combiner.indexOf(i); const combinerIndex = combiner.indexOf(i);
if (combinerIndex > -1) { if (combinerIndex > -1) {
@ -317,6 +324,7 @@ function Vbox(args) {
combiner.push(i); combiner.push(i);
if (!comboHighlight) { if (!comboHighlight) {
setInfo(vbox.bound[i]);
return combinerChange([i]); return combinerChange([i]);
} }
@ -325,7 +333,7 @@ function Vbox(args) {
const highlighted = combiner.indexOf(i) > -1; const highlighted = combiner.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} ${tutorial}`; const classes = `${highlighted ? 'highlight' : border} ${comboHighlight}`;
if (shapes[v]) { if (shapes[v]) {
return ( return (
<button <button
@ -400,11 +408,14 @@ function Vbox(args) {
<div class={inventoryClass} <div class={inventoryClass}
onMouseDown={inventoryClick} onMouseDown={inventoryClick}
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
style={vboxSelecting || itemUnequip.length ? { cursor: 'pointer' } : null} style={vboxSelecting || (itemUnequip.length) ? { cursor: 'pointer' } : null}>
onMouseOver={e => hoverInfo(e, 'inventory')}>
<div class="vbox-hdr"> <div class="vbox-hdr">
<h3 onTouchStart={e => e.target.scrollIntoView(true)}>INVENTORY</h3> <h3
onTouchStart={e => e.target.scrollIntoView(true)}
onMouseOver={e => hoverInfo(e, 'inventory')}> INVENTORY
</h3>
<button <button
disabled={tutorial && tutorial < 8 && instance.time_control === 'Practice' && instance.rounds.length === 1}
class='vbox-btn reclaim' class='vbox-btn reclaim'
onMouseOver={e => hoverInfo(e, 'reclaim')} onMouseOver={e => hoverInfo(e, 'reclaim')}
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
@ -425,7 +436,6 @@ function Vbox(args) {
// //
function hoverInfo(e, newInfo) { function hoverInfo(e, newInfo) {
e.stopPropagation(); e.stopPropagation();
if (info === newInfo) return true;
return setInfo(newInfo); return setInfo(newInfo);
} }
@ -438,5 +448,6 @@ function Vbox(args) {
</div> </div>
); );
} }
}
module.exports = addState(Vbox); module.exports = addState(Vbox);

View File

@ -10,7 +10,6 @@ const Demo = require('./demo');
function Welcome() { function Welcome() {
const page = this.state.page || 'register'; const page = this.state.page || 'register';
const navRegister = () => this.setState({ page: 'register' });
const pageEl = () => { const pageEl = () => {
if (page === 'login') return <Login />; if (page === 'login') return <Login />;
if (page === 'register') return <Register />; if (page === 'register') return <Register />;
@ -18,6 +17,26 @@ function Welcome() {
return false; return false;
}; };
const news = (
<div class="news">
<p> Welcome to mnml.</p>
<p> MNML is a turn-based 1v1 strategy game in an abstract setting. </p>
<p>
Build a unique team of 3 constructs from a range of skills and specialisations.<br />
Outplay your opponent in multiple rounds by adapting to an always shifting meta. <br />
Simple rules, complex interactions and unique mechanics.<br />
</p>
<p> Free to play, no pay to win. Register to start playing.<br /></p>
<a href='https://www.youtube.com/watch?v=VtZLlkpJuS8'>Tutorial Playthrough on YouTube</a>
</div>
);
const main = (['login', 'register', 'help'].includes(page))
? <section>{news}{pageEl()}</section>
: <Demo />;
return ( return (
<main class="menu welcome"> <main class="menu welcome">
<header> <header>
@ -39,6 +58,12 @@ function Welcome() {
onClick={() => this.setState({ page: 'register' })}> onClick={() => this.setState({ page: 'register' })}>
Register Register
</button> </button>
<button
class={`login-btn ${page === 'info' ? 'highlight' : ''}`}
disabled={page === 'info'}
onClick={() => this.setState({ page: 'info' })}>
Info
</button>
<button <button
class={`login-btn ${page === 'help' ? 'highlight' : ''}`} class={`login-btn ${page === 'help' ? 'highlight' : ''}`}
disabled={page === 'help'} disabled={page === 'help'}
@ -48,26 +73,8 @@ function Welcome() {
</div> </div>
</header> </header>
<div class="top"> <div class="top">
<section> {main}
<div class="news">
<p>
Welcome to mnml.
</p>
<p> MNML is a turn-based 1v1 strategy game in an abstract setting. </p>
<p>
Build a unique team of 3 constructs from a range of skills and specialisations.<br />
Outplay your opponent in multiple rounds by adapting to an always shifting meta. <br />
Simple rules, complex interactions and unique mechanics.<br />
</p>
<p>
Free to play, no pay to win. Register to start playing.<br />
</p>
<a href='https://www.youtube.com/watch?v=VtZLlkpJuS8'>Tutorial Playthrough on YouTube</a>
</div> </div>
{pageEl()}
</section>
</div>
<Demo />
</main> </main>
); );
} }

View File

@ -26,7 +26,8 @@ module.exports = {
INFO: { INFO: {
vbox: { vbox: {
item: 'VBOX', item: 'VBOX',
description: <p><b>ITEMS</b> that are available to buy.<br />The <b>VBOX</b> is refilled every round.<br />Click <b>REFILL</b> at the bottom to purchase a refill. </p>, description: <p><b>ITEMS</b> that are available to buy.<br />
The <b>VBOX</b> is refilled every round.<br />Click <b>REFILL</b> at the bottom to purchase a refill. </p>,
}, },
inventory: { inventory: {
item: 'INVENTORY', item: 'INVENTORY',
@ -34,7 +35,11 @@ module.exports = {
}, },
bits: { bits: {
item: 'BITS', item: 'BITS',
description: 'The VBOX currency.\nColours: 1b\nSkills: 2b\nSpecs: 3b\nAt the beginning of a round you receive 12 + 6 * round_number bits.', description: <p>The VBOX currency.<br />
Colours - 1b<br />
Skills - 2b<br />
Specs - 3b<br />
At the beginning of each round you receive 18 bits increasing by 6 bits per round.</p>,
}, },
ready: { ready: {
item: 'READY', item: 'READY',
@ -46,43 +51,32 @@ module.exports = {
}, },
reclaim: { reclaim: {
item: 'RECLAIM', item: 'RECLAIM',
description: 'Reclaim ITEMS for half the purchase cost of their combined ITEMS.\nClick to enable and click ITEM to reclaim.', description: <p>Reclaim items refunding the listed cost of the item.<br />
}, Click to enable and then click the item to reclaim.</p>,
refine: {
item: 'COMBINE',
description: <p>combine the selected items.<br />hover over an item to see <b>RECIPES</b>.</p>,
}, },
refill: { refill: {
item: 'REFILL', item: 'REFILL',
description: 'Refill the VBOX with new items.', description: 'Refill the VBOX with new items.',
}, },
equipSkills: {
item: 'QUICK ACCESS - SKILLS',
description: 'Click to select \nClick target CONSTRUCT to equip',
},
equipSpecs: {
item: 'QUICK ACCESS - SPECS',
description: 'Click to select \nClick target CONSTRUCT to equip',
},
constructSkills: { constructSkills: {
item: 'SKILLS', item: 'SKILLS',
description: 'Skills are used by Constructs in-game.\nClick a SKILL above and select a CONSTRUCT to equip.\nDouble-click to unequip.', description: 'Skills are used by constructs in the game phase.\nBase skills can be bought from the VBOX.\nEquip skills from the inventory. Double-click to unequip.',
}, },
constructSpecs: { constructSpecs: {
item: 'SPECS', item: 'SPECS',
description: 'SPECS increase the STATS of a CONSTRUCT.\nSPECS have increased effect once they reach a THRESHOLD across your whole team.\nClick a SPEC above and select a CONSTRUCT to equip.\nDouble-click to unequip.', description: 'Specialisations increase the stats of a construct.\nIncreased effect when your team meets the colour threshold.\nDouble-click to unequip.',
}, },
powerStat: { powerStat: {
item: 'POWER', item: 'POWER',
description: 'Power determines the base damage and healing of your construct skills. Combine POWER specs to increase construct power.', description: 'Power determines the damage and healing of your construct skills.\nRed power RedPower is used by red skills.\nGreen power GreenPower is used by green skills.\nBlue power BluePower is used by blue skills.\nCombine coloured POWER specs to increase specific construct power.',
}, },
lifeStat: { lifeStat: {
item: 'LIFE', item: 'LIFE',
description: 'The life of your construct.\n When your construct reaches 0 green life it is knocked out and cannot cast skills.\nRed life mitigates red damage and blue life mitigates blue damage. Combine LIFE specs to increase life totals.', description: 'The life of your construct.\nWhen your construct reaches 0 GreenLife it is knocked out.\nRed life RedLife mitigates red damage.\nBlue life BlueLife mitigates blue damage.\nCombine coloured LIFE specs to increase specific construct life.',
}, },
speedStat: { speedStat: {
item: 'SPEED', item: 'SPEED',
description: 'Speed determines the order in which skills resolve. Combine SPEED specs to increase speed.', description: 'Speed determines the order in which skills resolve.\nCombine SPEED specs to increase speed.',
}, },
}, },
}; };

View File

@ -8,6 +8,7 @@ const actions = require('./actions');
const { TIMES } = require('./constants'); const { TIMES } = require('./constants');
const animations = require('./animations.utils'); const animations = require('./animations.utils');
const { infoToast, errorToast } = require('./utils'); const { infoToast, errorToast } = require('./utils');
const { tutorialVbox } = require('./tutorial.utils');
function registerEvents(store) { function registerEvents(store) {
function notify(msg) { function notify(msg) {
@ -196,7 +197,7 @@ function registerEvents(store) {
} }
function setInstance(v) { function setInstance(v) {
const { account, instance, ws } = store.getState(); const { account, instance, ws, tutorial } = store.getState();
if (v) { if (v) {
setInvite(null); setInvite(null);
const player = v.players.find(p => p.id === account.id); const player = v.players.find(p => p.id === account.id);
@ -207,11 +208,16 @@ function registerEvents(store) {
const first = player.constructs[0]; const first = player.constructs[0];
store.dispatch(actions.setActiveConstruct(first)); store.dispatch(actions.setActiveConstruct(first));
} }
}
if (v.phase === 'Finished') { if (v.phase === 'Finished') {
setGame(null);
ws.sendAccountInstances(); ws.sendAccountInstances();
} }
if (localStorage.getItem('tutorial-complete')) {
store.dispatch(actions.setTutorial(null));
} else if (v.time_control === 'Practice' && v.rounds.length === 1 && tutorial) {
tutorialVbox(player, store, tutorial);
}
}
return store.dispatch(actions.setInstance(v)); return store.dispatch(actions.setInstance(v));
} }
@ -230,48 +236,51 @@ function registerEvents(store) {
function setDemo(d) { function setDemo(d) {
const initial = { const vboxDemo = {
players: d, players: d,
combiner: [], combiner: [],
items: ['Red', 'Red', 'Attack'],
equipped: false, equipped: false,
equipping: false, equipping: false,
}; };
const startDemo = () => { const startDemo = () => {
const { account } = store.getState(); const { account, itemInfo } = store.getState();
if (account) return false; if (account) return false;
store.dispatch(actions.setDemo(initial)); if (!itemInfo || itemInfo.items.length === 0) return setTimeout(startDemo, 500);
store.dispatch(actions.setAnimTarget(null)); store.dispatch(actions.setAnimTarget(null));
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, initial, { combiner: [0] }))), 2000); const bases = ['Attack', 'Stun', 'Buff', 'Debuff', 'Block'];
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, initial, { combiner: [0, 1] }))), 4000); const combo = sample(itemInfo.combos.filter(i => bases.some(b => i.components.includes(b))));
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, initial, { combiner: [0, 1, 2] }))), 6000); vboxDemo.combo = combo.item;
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, initial, { combiner: [], items: ['Strike', '', ''] }))), 8000); vboxDemo.items = combo.components;
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, initial, { combiner: [0], items: ['Strike', '', ''], equipping: true }))), 10000); store.dispatch(actions.setDemo(vboxDemo));
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, initial, { combiner: [], items: ['', '', ''], equipped: true, equipping: false }))), 12000);
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, initial, { items: ['', '', ''], equipped: true, equipping: false }))), 12000);
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [0] }))), 500);
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [0, 1] }))), 1000);
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [0, 1, 2] }))), 1500);
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [], items: [vboxDemo.combo, '', ''] }))), 2500);
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [0], items: [vboxDemo.combo, '', ''], equipping: true }))), 3000);
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [], items: ['', '', ''], equipped: true, equipping: false }))), 4000);
setTimeout(() => { setTimeout(() => {
const { itemInfo } = store.getState();
return store.dispatch(actions.setAnimTarget({ return store.dispatch(actions.setAnimTarget({
skill: sample(itemInfo.items.filter(i => i.skill)).item, skill: sample(itemInfo.items.filter(i => i.skill)).item,
constructId: d[1].constructs[0].id, constructId: d[1].constructs[0].id,
player: false, player: false,
direction: 0, direction: 0,
})); }));
}, 14000); }, 500);
setTimeout(() => { setTimeout(() => {
const { itemInfo } = store.getState();
return store.dispatch(actions.setAnimTarget({ return store.dispatch(actions.setAnimTarget({
skill: sample(itemInfo.items.filter(i => i.skill)).item, skill: sample(itemInfo.items.filter(i => i.skill)).item,
constructId: d[1].constructs[1].id, constructId: d[1].constructs[1].id,
player: true, player: true,
direction: 0, direction: 0,
})); }));
}, 16000); }, 3000);
setTimeout(startDemo, 20000); return setTimeout(startDemo, 5000);
}; };
startDemo(); startDemo();

View File

@ -59,6 +59,9 @@ module.exports = {
teamPage: createReducer(0, 'SET_TEAM_PAGE'), teamPage: createReducer(0, 'SET_TEAM_PAGE'),
teamSelect: createReducer([null, null, null], 'SET_TEAM_SELECT'), teamSelect: createReducer([null, null, null], 'SET_TEAM_SELECT'),
tutorial: createReducer(1, 'SET_TUTORIAL'),
tutorialGame: createReducer(1, 'SET_TUTORIAL_GAME'),
vboxSelected: createReducer([], 'SET_VBOX_SELECTED'), vboxSelected: createReducer([], 'SET_VBOX_SELECTED'),
ws: createReducer(null, 'SET_WS'), ws: createReducer(null, 'SET_WS'),

View File

@ -0,0 +1,236 @@
const preact = require('preact');
const actions = require('./actions');
function tutorialConstructDisplay(player, instance, tutorial, navInstance, i) {
if (instance.time_control === 'Practice' && instance.rounds.length === 1 && tutorial && tutorial < 6) {
if (tutorial <= 2 || (tutorial > 2 && i > 0)) {
const mobileVisible = navInstance === i + 1;
const classes = `instance-construct ${mobileVisible ? 'visible' : ''}`;
return (<div key={player.constructs[i].id} class={classes}></div>);
}
}
return false;
}
function tutorialShouldDisableEquip(tutorial, iter, instance, construct) {
return tutorial && tutorial === 6 && iter === 0 && construct.skills.length !== 0
&& instance.time_control === 'Practice' && instance.rounds.length === 1;
}
function tutorialVbox(player, store, tutorial) {
let stage = tutorial;
const { vbox } = player;
if (stage === 1) {
if (vbox.bits < 17) {
stage += 1;
} else {
vbox.free[0] = vbox.free[0].slice(0, 2);
vbox.free[1] = [];
vbox.free[2] = [];
vbox.bound.fill(null, 0, 3);
}
}
if (stage === 2) {
if (!(vbox.bound.slice(0, 3).every(i => i === 'Attack') && vbox.bound.length >= 3)) {
stage += 1;
} else {
vbox.free[0] = vbox.free[0].slice(0, 2);
vbox.free[1] = [];
vbox.free[2] = [];
vbox.bound.fill(null, 1, 3);
}
}
if (stage === 3) {
if (player.constructs[0].skills.length !== 0) {
stage += 1;
} else {
vbox.free[0] = vbox.free[0].slice(0, 2);
vbox.free[1] = [];
vbox.free[2] = [];
vbox.bound.fill(null, 0, 2);
}
}
if (stage === 4) {
if (!vbox.free[2][0] || vbox.bits < 12) {
stage += 1;
} else {
vbox.free[0] = [];
vbox.free[1] = [];
vbox.free[2] = vbox.free[2].slice(0, 1);
vbox.bound.fill(null, 0, 2);
}
}
if (stage === 5) {
if (player.constructs[0].specs.length !== 0) {
stage += 1;
} else {
vbox.free[0] = [];
vbox.free[1] = [];
vbox.free[2] = vbox.free[2].slice(0, 1);
vbox.bound.fill(null, 0, 2);
}
}
if (stage === 6) {
if (player.constructs.every(c => c.skills.length !== 0)) {
stage += 1;
} else {
vbox.free[0] = [];
vbox.free[1] = [];
vbox.free[2] = [];
}
}
if (stage === 7) {
if (vbox.bits < 13) {
stage += 1;
} else {
vbox.free[0] = [];
vbox.free[1] = [];
vbox.free[2] = [];
}
}
store.dispatch(actions.setTutorial(stage));
}
function tutorialStage(tutorial, ws, clearTutorial, instance) {
if (!(instance.time_control === 'Practice' && instance.rounds.length === 1)) return false;
const exit = () => {
clearTutorial();
localStorage.setItem('tutorial-complete', true);
ws.sendInstanceState(instance.id);
};
const tutorialText = () => {
if (tutorial === 1) {
return (
<div>
<h2>Tutorial</h2>
<p> Welcome to the vbox phase tutorial.</p>
<p> Colours are used to create powerful combinations. </p>
<p> Buy two colours from the vbox by double clicking. <br />
You can also click the colour once and then click the inventory. </p>
</div>
);
}
if (tutorial === 2) {
return (
<div>
<h2>Tutorial</h2>
<p> In a normal game you start with three base <b>Attack</b> skill items. </p>
<p> The <b>Attack</b> item can be combined with <b>colours</b> to create a new skill. </p>
<p> Select the Attack item along with two colours. <br />
Once selected press <b>COMBINE</b> to create a new combo.
</p>
</div>
);
}
if (tutorial === 3) {
const constructOne = instance.players[0].constructs[0].name;
return (
<div>
<h2>Tutorial</h2>
<p> The first construct on your team is <b>{constructOne}</b>. </p>
<p> Skill items can be equipped to your constructs to be used in the combat phase. </p>
<p> Click the newly combined skill item in the top right of the inventory. <br />
Once selected click the construct <b>SKILL</b> slot to equip the skill. </p>
</div>
);
}
if (tutorial === 4) {
return (
<div>
<h2>Tutorial</h2>
<p> You can also buy specialisation items for your constructs. <br />
Specialisation items increase stats including power, speed and life. </p>
<p> Buy the specialisation item from the vbox by double clicking. <br />
You can also click the specialisation once and then click the inventory. </p>
</div>
);
}
if (tutorial === 5) {
return (
<div>
<h2>Tutorial</h2>
<p> Equipping specialisation items will increase the stats of your constructs.</p>
<p> These can also be combined with colours for further specialisation. </p>
<p> Click the specialisation item in the top right of the inventory.<br />
Once selected click the construct <b>SPEC</b> slot to equip the specialisation. </p>
</div>
);
}
if (tutorial === 6) {
const constructTwo = instance.players[0].constructs[1].name;
const constructThree = instance.players[0].constructs[2].name;
return (
<div>
<h2>Tutorial</h2>
<p> You have now created a construct with an upgraded skill and base spec. </p>
<p> The goal is to create three powerful constructs for combat. </p>
<p> Equip your other constructs <b>{constructTwo}</b> and <b>{constructThree}</b> with the Attack skill. <br />
Ensure each construct has a single skill to continue. </p>
</div>
);
}
if (tutorial === 7) {
return (
<div>
<h2>Tutorial</h2>
<p> Each round you start with a vbox full of different skills, specs and colours. </p>
<p> Bits are your currency for buying skills, specs and colours from the vbox. <br />
Colours cost 1b, Skills cost 2b and specs cost 3b. <br />
You can refill the vbox by pressing the refill button for 2b. <br />
After each combat round you get more bits to further upgrade your team.
</p>
<p> Press the <b>REFILL</b> button to get a new vbox and continue. </p>
</div>
);
}
if (tutorial === 8) {
return (
<div>
<h2>Tutorial</h2>
<p>You've completed the tutorial! Try to create more skill and spec combinations. </p>
<p>You can unequip skills and specs back into the inventory by double clicking. <br />
Reclaim can be used to refund the cost of items in your inventory. </p>
<p>Click the <b>EXIT TUTORIAL</b> button to replace this section with more information.</p>
</div>
);
}
return false;
};
const classes = tutorial === 8 ? 'focus' : '';
const exitTutorial = <button
class={classes}
onClick={e => e.stopPropagation()}
onMouseDown={exit}> Exit Tutorial </button>;
return (
<div class='tutorial'>
{tutorialText()}
<figure>
{exitTutorial}
</figure>
</div>);
}
module.exports = {
tutorialConstructDisplay,
tutorialVbox,
tutorialStage,
tutorialShouldDisableEquip
};

View File

@ -143,6 +143,7 @@ function randomPoints(numPoints, radius, dimensions) {
const removeTier = skill => { const removeTier = skill => {
if (!skill) return skill; if (!skill) return skill;
if (skill.includes('CounterAttack')) return 'CounterAttack';
if (skill.includes('SiphonTick')) return 'SiphonTick'; if (skill.includes('SiphonTick')) return 'SiphonTick';
if (skill.includes('TriageTick')) return 'TriageTick'; if (skill.includes('TriageTick')) return 'TriageTick';
if (skill.includes('DecayTick')) return 'DecayTick'; if (skill.includes('DecayTick')) return 'DecayTick';
@ -239,20 +240,18 @@ function convertItem(v) {
} }
function effectInfo(i) { function effectInfo(i) {
console.log(i);
function multiplier(s) { // Update later to use server info in future function multiplier(s) { // Update later to use server info in future
if (s === 'CounterAttack') return 70; if (s === 'CounterAttack') return 120;
if (s === 'CounterAttack+') return 95; if (s === 'CounterAttack+') return 160;
if (s === 'CounterAttack++') return 120; if (s === 'CounterAttack++') return 230;
if (s === 'DecayTick') return 33; if (s === 'DecayTick') return 33;
if (s === 'DecayTick+') return 45; if (s === 'DecayTick+') return 45;
if (s === 'DecayTick++') return 70; if (s === 'DecayTick++') return 70;
if (s === 'SiphonTick') return 20; if (s === 'SiphonTick') return 25;
if (s === 'SiphonTick+') return 25; if (s === 'SiphonTick+') return 30;
if (s === 'SiphonTick++') return 30; if (s === 'SiphonTick++') return 40;
if (s === 'TriageTick') return 75; if (s === 'TriageTick') return 75;
if (s === 'TriageTick+') return 110; if (s === 'TriageTick+') return 110;
@ -267,8 +266,8 @@ function effectInfo(i) {
switch (i.effect) { switch (i.effect) {
case 'Amplify': return `Increases construct RedPower and BluePower by ${i.meta[1] - 100}%`; case 'Amplify': return `Increases construct RedPower and BluePower by ${i.meta[1] - 100}%`;
case 'Banish': return 'Banished construct cannot cast or take damage'; case 'Banish': return 'Banished construct cannot cast or take damage';
case 'Block': return `Reduces construct red damage taken by ${100 - i.meta[1]}%`; case 'Block': return `Reduces construct red damage and blue damage taken by ${100 - i.meta[1]}%`;
case 'Buff': return `Increases construct RedPower and SpeedStat by ${i.meta[1] - 100}%`; case 'Buff': return `Increases construct RedPower BluePower SpeedStat by ${i.meta[1] - 100}%`;
case 'Sustain': return 'Construct cannot be KO while active. Additionally provides immunity to disables'; case 'Sustain': return 'Construct cannot be KO while active. Additionally provides immunity to disables';
case 'Curse': return `Construct will take ${i.meta[1] - 100}% increased red and blue damage`; case 'Curse': return `Construct will take ${i.meta[1] - 100}% increased red and blue damage`;
case 'Haste': return `Construct has ${i.meta[1] - 100}% increased SpeedStat. Red attack skills will trigger a HasteStrike dealing 30% SpeedStat as red damage.`; case 'Haste': return `Construct has ${i.meta[1] - 100}% increased SpeedStat. Red attack skills will trigger a HasteStrike dealing 30% SpeedStat as red damage.`;

View File

@ -100,13 +100,13 @@ impl Effect {
pub fn modifications(&self) -> Vec<Stat> { pub fn modifications(&self) -> Vec<Stat> {
match self { match self {
Effect::Vulnerable => vec![Stat::RedDamageTaken], Effect::Vulnerable => vec![Stat::RedDamageTaken],
Effect::Block => vec![Stat::RedDamageTaken], Effect::Block => vec![Stat::RedDamageTaken, Stat::BlueDamageTaken],
Effect::Buff => vec![Stat::RedPower, Stat::Speed], Effect::Buff => vec![Stat::BluePower, Stat::RedPower, Stat::Speed],
Effect::Absorption => vec![Stat::RedPower, Stat::BluePower], Effect::Absorption => vec![Stat::RedPower, Stat::BluePower],
Effect::Amplify => vec![Stat::RedPower, Stat::BluePower], Effect::Amplify => vec![Stat::RedPower, Stat::BluePower],
Effect::Curse => vec![Stat::BlueDamageTaken], Effect::Curse => vec![Stat::RedDamageTaken, Stat::BlueDamageTaken],
Effect::Hybrid => vec![Stat::GreenPower], Effect::Hybrid => vec![Stat::GreenPower],
Effect::Wither => vec![Stat::GreenDamageTaken], Effect::Wither => vec![Stat::GreenDamageTaken],

View File

@ -584,26 +584,31 @@ impl Item {
pub fn into_description(&self) -> String { pub fn into_description(&self) -> String {
match self { match self {
// colours // colours
Item::Blue => format!("Combine with skills and specs to create upgraded items. \n Deterrents and destruction."), Item::Blue => format!("Combine two colours with a white base item to create a new combo. \n Slow speed, magical type. Deterrents and destruction."),
Item::Green => format!("Combine with skills and specs to create upgraded items.\n Protection and trickery."), Item::Green => format!("Combine two colours with a white base item to create a new combo.\n Normal speed, healing type. Protection and trickery."),
Item::Red => format!("Combine with skills and specs to create upgraded items. \n Speed and chaos."), Item::Red => format!("Combine two colours with a white base item to create a new combo. \n Fast speed, physical type. Chaos and momentum."),
// base skills // base skills
Item::Attack => format!("Deal {:?}% RedPower as red damage.", Item::Attack => format!("Deal {:?}% RedPower as red damage.",
self.into_skill().unwrap().multiplier()), self.into_skill().unwrap().multiplier()),
Item::Block => format!("Reduce red damage taken by {:?}%.", Item::Block => format!("Reduce red damage and blue damage taken by {:?}%. Block lasts {:?}T",
100 - self.into_skill().unwrap().effect()[0].get_multiplier()), 100 - self.into_skill().unwrap().effect()[0].get_multiplier(),
self.into_skill().unwrap().effect()[0].get_duration()),
Item::Stun => format!("Stun target construct for {:?}T.", Item::Stun => format!("Stun target construct for {:?}T.",
self.into_skill().unwrap().effect()[0].get_duration()), self.into_skill().unwrap().effect()[0].get_duration()),
Item::Buff => format!("Increase target construct RedPower and SpeedStat by {:?}%.", Item::Buff => format!("Increase target construct RedPower BluePower SpeedStat by {:?}%. Buff lasts {:?}T",
self.into_skill().unwrap().effect()[0].get_multiplier() - 100), self.into_skill().unwrap().effect()[0].get_multiplier() - 100,
Item::Debuff => format!("Slows the target reducing SpeedStat by {:?}%.", self.into_skill().unwrap().effect()[0].get_duration()),
100 - self.into_skill().unwrap().effect()[0].get_multiplier()),
Item::Debuff => format!("Slows the target reducing SpeedStat by {:?}%. Debuff lasts {:?}T",
100 - self.into_skill().unwrap().effect()[0].get_multiplier(),
self.into_skill().unwrap().effect()[0].get_duration()),
// specs // specs
// Base // Base
Item::Power => format!("Increases all power stats by {:?}%. Power determines the base damage and healing of your construct skills.", Item::Power => format!("Increases all power stats by {:?}%.
Power determines the base damage and healing of your construct skills.",
self.into_spec().unwrap().values().base()), self.into_spec().unwrap().values().base()),
Item::Life => format!("Increases construct GreenLife by {:?}. Item::Life => format!("Increases construct GreenLife by {:?}.
When your construct reaches 0 GreenLife it is knocked out and cannot cast skills.", When your construct reaches 0 GreenLife it is knocked out and cannot cast skills.",
@ -713,7 +718,8 @@ impl Item {
Item::Banish| Item::Banish|
Item::BanishPlus | Item::BanishPlus |
Item::BanishPlusPlus => format!("Banish target for {:?}T. Deal blue damage and red damage equal to {:?}% target red and blue life. Item::BanishPlusPlus => format!("Banish target for {:?}T.
Deal blue damage and red damage equal to {:?}% target red and blue life.
Banished constructs are immune to all skills and effects.", Banished constructs are immune to all skills and effects.",
self.into_skill().unwrap().effect()[0].get_duration(), self.into_skill().unwrap().effect()[0].get_duration(),
self.into_skill().unwrap().multiplier()), self.into_skill().unwrap().multiplier()),
@ -725,20 +731,24 @@ impl Item {
Item::Chaos| Item::Chaos|
Item::ChaosPlus | Item::ChaosPlus |
Item::ChaosPlusPlus => format!( Item::ChaosPlusPlus => format!(
"Hits twice for red and blue damage. Damage {:?}% RedPower and BluePower. Randomly deals 0 to 30% more damage.", "Hits twice for red and blue damage. Damage {:?}% RedPower and BluePower.
Randomly deals 0 to 30% more damage.",
self.into_skill().unwrap().multiplier()), self.into_skill().unwrap().multiplier()),
Item::Sustain| Item::Sustain|
Item::SustainPlus | Item::SustainPlus |
Item::SustainPlusPlus => format!("Construct cannot be KO'd while active. Additionally provides immunity to disables. \ Item::SustainPlusPlus => format!(
Lasts {:?}T", "Construct cannot be KO'd while active and provides immunity to disables. Lasts {:?}T.
self.into_skill().unwrap().effect()[0].get_duration()), Recharges target RedLife based on {:?}% RedPower.",
self.into_skill().unwrap().effect()[0].get_duration(),
self.into_skill().unwrap().multiplier()),
Item::Electrify| Item::Electrify|
Item::ElectrifyPlus | Item::ElectrifyPlus |
Item::ElectrifyPlusPlus => format!( Item::ElectrifyPlusPlus => format!(
"Applies electrify for {:?}T. If a construct with electrify takes damage they will apply an electrocute debuff to the caster dealing {:?}% \ "Applies electrify for {:?}T.
BluePower as BlueDamage per turn for {:?}T.", If a construct with electrify takes direct damage they will apply an electrocute debuff to the caster.
Electrocute deals {:?}% BluePower as BlueDamage per turn for {:?}T.",
self.into_skill().unwrap().effect()[0].get_duration(), self.into_skill().unwrap().effect()[0].get_duration(),
self.into_skill().unwrap().effect()[0].get_skill().unwrap().effect()[0].get_skill().unwrap().multiplier(), self.into_skill().unwrap().effect()[0].get_skill().unwrap().effect()[0].get_skill().unwrap().multiplier(),
self.into_skill().unwrap().effect()[0].get_skill().unwrap().effect()[0].get_duration()), self.into_skill().unwrap().effect()[0].get_skill().unwrap().effect()[0].get_duration()),
@ -753,7 +763,8 @@ impl Item {
Item::Decay| Item::Decay|
Item::DecayPlus | Item::DecayPlus |
Item::DecayPlusPlus => format!( Item::DecayPlusPlus => format!(
"Reduces healing taken by {:?}% for {:?}T. Deals blue damage {:?}% BluePower each turn for {:?}T.", "Reduces healing taken by {:?}% for {:?}T.
Deals blue damage {:?}% BluePower each turn for {:?}T.",
100 - self.into_skill().unwrap().effect()[0].get_multiplier(), 100 - self.into_skill().unwrap().effect()[0].get_multiplier(),
self.into_skill().unwrap().effect()[0].get_duration(), self.into_skill().unwrap().effect()[0].get_duration(),
self.into_skill().unwrap().effect()[1].get_skill().unwrap().multiplier(), self.into_skill().unwrap().effect()[1].get_skill().unwrap().multiplier(),
@ -762,15 +773,18 @@ impl Item {
Item::Absorb| Item::Absorb|
Item::AbsorbPlus | Item::AbsorbPlus |
Item::AbsorbPlusPlus => format!( Item::AbsorbPlusPlus => format!(
"Gain Absorb for {:?}T. {} Absorption lasts {:?}T.", "Gain Absorb for {:?}T. When attacked with Absorb you gain Absorption.
Absorption increases RedPower and BluePower based on Damage taken.
Absorption lasts {:?}T.",
self.into_skill().unwrap().effect()[0].get_duration(), self.into_skill().unwrap().effect()[0].get_duration(),
"When attacked with Absorb you gain Absorption which increases RedPower and BluePower based on Damage taken.",
self.into_skill().unwrap().effect()[0].get_skill().unwrap().effect()[0].get_duration()), self.into_skill().unwrap().effect()[0].get_skill().unwrap().effect()[0].get_duration()),
Item::Haste| Item::Haste|
Item::HastePlus | Item::HastePlus |
Item::HastePlusPlus => format!( Item::HastePlusPlus => format!(
"Haste increases SpeedStat by {:?}%, Red Attack based skills will strike again dealing {:?}% SpeedStat as red damage. Lasts {:?}T", "Haste increases SpeedStat by {:?}%.
Red Attack based skills will strike again dealing {:?}% SpeedStat as red damage.
Lasts {:?}T",
self.into_skill().unwrap().effect()[0].get_multiplier() - 100, self.into_skill().unwrap().effect()[0].get_multiplier() - 100,
Skill::HasteStrike.multiplier(), Skill::HasteStrike.multiplier(),
self.into_skill().unwrap().effect()[0].get_duration()), self.into_skill().unwrap().effect()[0].get_duration()),
@ -782,7 +796,9 @@ impl Item {
Item::Hybrid| Item::Hybrid|
Item::HybridPlus | Item::HybridPlus |
Item::HybridPlusPlus => format!( Item::HybridPlusPlus => format!(
"Hybrid increases GreenPower by {:?}%, Blue based Attack skills will blast again dealing {:?}% GreenPower as blue damage. Lasts {:?}T.", "Hybrid increases GreenPower by {:?}%.
Blue based Attack skills will blast again dealing {:?}% GreenPower as blue damage.
Lasts {:?}T.",
self.into_skill().unwrap().effect()[0].get_multiplier() - 100, self.into_skill().unwrap().effect()[0].get_multiplier() - 100,
Skill::HybridBlast.multiplier(), Skill::HybridBlast.multiplier(),
self.into_skill().unwrap().effect()[0].get_duration()), self.into_skill().unwrap().effect()[0].get_duration()),
@ -790,20 +806,25 @@ impl Item {
Item::Invert| Item::Invert|
Item::InvertPlus | Item::InvertPlus |
Item::InvertPlusPlus => format!( Item::InvertPlusPlus => format!(
"Reverse healing into damage and damage into healing. Any excess red or blue damage is converted into shield recharge. Lasts {:?}T.", "Reverse healing into damage and damage into healing.
Any excess red or blue damage is converted into shield recharge.
Lasts {:?}T.",
self.into_skill().unwrap().effect()[0].get_duration()), self.into_skill().unwrap().effect()[0].get_duration()),
Item::Counter| Item::Counter|
Item::CounterPlus | Item::CounterPlus |
Item::CounterPlusPlus => format!("Applies counter and block {:?}T. Block reduces incoming red damage by {:?}%. Item::CounterPlusPlus => format!(
Red damage taken during counter will trigger a counter attack dealing {:?}% RedPower as red damage.", "Applies counter for {:?}T.
Red damage taken during counter will trigger a counter attack.
Counter attack deals {:?}% RedPower as red damage.",
self.into_skill().unwrap().effect()[0].get_duration(), self.into_skill().unwrap().effect()[0].get_duration(),
100 - self.into_skill().unwrap().effect()[1].get_multiplier(),
self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier()), self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier()),
Item::Purge| Item::Purge|
Item::PurgePlus | Item::PurgePlus |
Item::PurgePlusPlus => format!("Remove buffs from target construct. Applies purge disabling target green skills for {:?}T.", Item::PurgePlusPlus => format!(
"Remove buffs from target construct.
Applies purge disabling target green skills for {:?}T.",
self.into_skill().unwrap().effect()[0].get_duration()), self.into_skill().unwrap().effect()[0].get_duration()),
Item::Purify| Item::Purify|
@ -815,8 +836,10 @@ impl Item {
Item::Reflect| Item::Reflect|
Item::ReflectPlus | Item::ReflectPlus |
Item::ReflectPlusPlus => format!( Item::ReflectPlusPlus => format!(
"Reflect incoming blue skills to source. Lasts {:?}T.", "Reflect incoming blue skills to source. Lasts {:?}T.
self.into_skill().unwrap().effect()[0].get_duration()), Recharges target BlueLife based on {:?}% BluePower.",
self.into_skill().unwrap().effect()[0].get_duration(),
self.into_skill().unwrap().multiplier()),
Item::Recharge| Item::Recharge|
Item::RechargePlus | Item::RechargePlus |
@ -833,8 +856,8 @@ impl Item {
Item::Link| Item::Link|
Item::LinkPlus | Item::LinkPlus |
Item::LinkPlusPlus => format!( Item::LinkPlusPlus => format!(
"Form a link with target swapping relative life values. "Swap {:?}% of green life difference as blue damage to the target and healing to the caster.
If the target construct has more green life than caster, {:?}% of green life difference as blue damage to the target and heal to the caster. The swap only occurs if the target construct has more green life than caster.
Stuns caster for {:?}T in the process.", Stuns caster for {:?}T in the process.",
self.into_skill().unwrap().multiplier(), self.into_skill().unwrap().multiplier(),
self.into_skill().unwrap().effect()[0].get_duration()), self.into_skill().unwrap().effect()[0].get_duration()),
@ -842,7 +865,7 @@ impl Item {
Item::Silence| Item::Silence|
Item::SilencePlus | Item::SilencePlus |
Item::SilencePlusPlus => format!( Item::SilencePlusPlus => format!(
"Block the target from using blue skills for {:?}T and deals {:?}% BluePower as blue damage. "Disable the target from using blue skills for {:?}T and deals {:?}% BluePower as blue damage.
Deals 45% more Damage per blue skill on target.", Deals 45% more Damage per blue skill on target.",
self.into_skill().unwrap().effect()[0].get_duration(), self.into_skill().unwrap().effect()[0].get_duration(),
self.into_skill().unwrap().multiplier()), self.into_skill().unwrap().multiplier()),
@ -850,7 +873,8 @@ impl Item {
Item::Slay| Item::Slay|
Item::SlayPlus | Item::SlayPlus |
Item::SlayPlusPlus => format!( Item::SlayPlusPlus => format!(
"Deals {:?}% RedPower + {:?}% GreenPower as red damage and provides self healing based on damage dealt.", "Deals {:?}% RedPower + {:?}% GreenPower as red damage.
Construct heals self for 50% of damage dealt to target construct GreenLife.",
self.into_skill().unwrap().multiplier(), self.into_skill().unwrap().multiplier(),
self.into_skill().unwrap().multiplier()), self.into_skill().unwrap().multiplier()),
@ -864,7 +888,8 @@ impl Item {
Item::Restrict| Item::Restrict|
Item::RestrictPlus | Item::RestrictPlus |
Item::RestrictPlusPlus => format!( Item::RestrictPlusPlus => format!(
"Block the target from using red skills for {:?}T and deals {:?}% RedPower as red damage. Deals 35% more damage per red skill on target.", "Disable the target from using red skills for {:?}T and deals {:?}% RedPower as red damage.
Deals 35% more damage per red skill on target.",
self.into_skill().unwrap().effect()[0].get_duration(), self.into_skill().unwrap().effect()[0].get_duration(),
self.into_skill().unwrap().multiplier()), self.into_skill().unwrap().multiplier()),
@ -872,7 +897,8 @@ impl Item {
Item::BashPlus | Item::BashPlus |
Item::BashPlusPlus => format!( Item::BashPlusPlus => format!(
"Bash the target increasing the cooldowns of target skills by 1T. "Bash the target increasing the cooldowns of target skills by 1T.
Deals {:?}% RedPower as red damage and 45% more damage per cooldown increased. Stuns for {:?}T.", Deals {:?}% RedPower as red damage and 45% more damage per cooldown increased.
Stuns for {:?}T.",
self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier(), self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier(),
self.into_skill().unwrap().effect()[0].get_duration()), self.into_skill().unwrap().effect()[0].get_duration()),
@ -885,14 +911,17 @@ impl Item {
Item::Siphon| Item::Siphon|
Item::SiphonPlus | Item::SiphonPlus |
Item::SiphonPlusPlus => format!( Item::SiphonPlusPlus => format!(
"Deals {:?}% BluePower + {:?}% GreenPower as blue damage each turn and heals caster based on damage dealt. Lasts {:?}T.", "Deals {:?}% BluePower + {:?}% GreenPower as blue damage each turn.
Construct heals self for 100% of damage dealt to target construct GreenLife.
Lasts {:?}T.",
self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier(), self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier(),
self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier(), self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier(),
self.into_skill().unwrap().effect()[0].get_duration()), self.into_skill().unwrap().effect()[0].get_duration()),
Item::Intercept| Item::Intercept|
Item::InterceptPlus | Item::InterceptPlus |
Item::InterceptPlusPlus => format!("Intercept redirects skills against the team to target, lasts {:?}T.\ Item::InterceptPlusPlus => format!(
"Intercept redirects skills against the team to target, lasts {:?}T.
Recharges RedLife for {:?} RedPower.", Recharges RedLife for {:?} RedPower.",
self.into_skill().unwrap().effect()[0].get_duration(), self.into_skill().unwrap().effect()[0].get_duration(),
self.into_skill().unwrap().multiplier()), self.into_skill().unwrap().multiplier()),
@ -936,10 +965,10 @@ impl Item {
Item::AmplifyPlusPlus => vec![Item::AmplifyPlus, Item::AmplifyPlus, Item::AmplifyPlus], Item::AmplifyPlusPlus => vec![Item::AmplifyPlus, Item::AmplifyPlus, Item::AmplifyPlus],
Item::Purge => vec![Item::Debuff, Item::Green, Item::Green], // Needs flavour Item::Purge => vec![Item::Debuff, Item::Green, Item::Green], // Needs flavour
Item::Invert => vec![Item::Debuff, Item::Red, Item::Blue], Item::Invert => vec![Item::Debuff, Item::Red, Item::Green],
Item::Restrict => vec![Item::Debuff, Item::Red, Item::Red], Item::Restrict => vec![Item::Debuff, Item::Red, Item::Red],
Item::Silence => vec![Item::Debuff, Item::Blue, Item::Blue], Item::Silence => vec![Item::Debuff, Item::Blue, Item::Blue],
Item::Curse => vec![Item::Debuff, Item::Red, Item::Green], Item::Curse => vec![Item::Debuff, Item::Red, Item::Blue],
Item::Decay => vec![Item::Debuff, Item::Green, Item::Blue], Item::Decay => vec![Item::Debuff, Item::Green, Item::Blue],
Item::RestrictPlus => vec![Item::Restrict, Item::Restrict, Item::Restrict], Item::RestrictPlus => vec![Item::Restrict, Item::Restrict, Item::Restrict],
Item::RestrictPlusPlus => vec![Item::RestrictPlus, Item::RestrictPlus, Item::RestrictPlus], Item::RestrictPlusPlus => vec![Item::RestrictPlus, Item::RestrictPlus, Item::RestrictPlus],

View File

@ -342,7 +342,7 @@ fn post_resolve(_skill: Skill, game: &mut Game, mut resolutions: Resolutions) ->
}; };
if target.is_ko() { if target.is_ko() {
resolutions.push(Resolution::new(&source, &target).event(Event::Ko()).stages(EventStages::PostOnly)); // resolutions.push(Resolution::new(&source, &target).event(Event::Ko()).stages(EventStages::PostOnly));
target.effects.clear(); target.effects.clear();
} }
@ -753,9 +753,9 @@ impl Skill {
// Attack Base // Attack Base
Skill::Attack => 80, // Base Skill::Attack => 80, // Base
Skill::Blast=> 110, // BB Skill::Blast=> 105, // BB
Skill::BlastPlus => 145, // BB Skill::BlastPlus => 140, // BB
Skill::BlastPlusPlus => 210, // BB Skill::BlastPlusPlus => 200, // BB
Skill::Chaos=> 40, // BR Skill::Chaos=> 40, // BR
Skill::ChaosPlus => 65, // BR Skill::ChaosPlus => 65, // BR
@ -765,13 +765,13 @@ impl Skill {
Skill::HealPlus => 185, //GG Skill::HealPlus => 185, //GG
Skill::HealPlusPlus => 270, //GG Skill::HealPlusPlus => 270, //GG
Skill::SiphonTick=> 20, // GB Skill::SiphonTick=> 25, // GB
Skill::SiphonTickPlus => 25, Skill::SiphonTickPlus => 30,
Skill::SiphonTickPlusPlus => 30, Skill::SiphonTickPlusPlus => 40,
Skill::Slay=> 40, // RG Skill::Slay=> 45, // RG
Skill::SlayPlus => 60, Skill::SlayPlus => 65,
Skill::SlayPlusPlus => 90, Skill::SlayPlusPlus => 100,
Skill::Strike=> 90, //RR Skill::Strike=> 90, //RR
Skill::StrikePlus => 140, Skill::StrikePlus => 140,
@ -782,9 +782,9 @@ impl Skill {
Skill::ElectrocuteTickPlus => 100, Skill::ElectrocuteTickPlus => 100,
Skill::ElectrocuteTickPlusPlus => 130, Skill::ElectrocuteTickPlusPlus => 130,
Skill::CounterAttack=> 70, Skill::CounterAttack=> 120,
Skill::CounterAttackPlus => 95, Skill::CounterAttackPlus => 160,
Skill::CounterAttackPlusPlus => 120, Skill::CounterAttackPlusPlus => 230,
Skill::Purify=> 45, //Green dmg (heal) Skill::Purify=> 45, //Green dmg (heal)
Skill::PurifyPlus => 70, Skill::PurifyPlus => 70,
@ -803,18 +803,21 @@ impl Skill {
Skill::SustainPlusPlus => 230, Skill::SustainPlusPlus => 230,
// Stun Base // Stun Base
Skill::Sleep=> 240, //Green dmg (heal) Skill::Sleep=> 200, //Green dmg (heal)
Skill::SleepPlus => 300, Skill::SleepPlus => 290,
Skill::SleepPlusPlus => 400, Skill::SleepPlusPlus => 400,
Skill::Banish=> 40, //Green dmg (heal) Skill::Banish=> 40, //Green dmg (heal)
Skill::BanishPlus => 75, Skill::BanishPlus => 75,
Skill::BanishPlusPlus => 125, Skill::BanishPlusPlus => 125,
Skill::Bash=> 65, Skill::Bash=> 45,
Skill::BashPlus => 95, Skill::BashPlus => 65,
Skill::BashPlusPlus => 140, Skill::BashPlusPlus => 100,
Skill::Link=> 75,
Skill::LinkPlus => 100,
Skill::LinkPlusPlus => 150,
// Debuff Base // Debuff Base
Skill::DecayTick=> 33, Skill::DecayTick=> 33,
Skill::DecayTickPlus => 45, Skill::DecayTickPlus => 45,
@ -827,14 +830,14 @@ impl Skill {
Skill::RestrictPlusPlus => 100, Skill::RestrictPlusPlus => 100,
// Buff base // Buff base
Skill::HybridBlast => 25, Skill::HybridBlast => 50,
Skill::HasteStrike => 30,
Skill::Link=> 75, Skill::HasteStrike => 60,
Skill::LinkPlus => 100,
Skill::LinkPlusPlus => 150,
Skill::Intercept=> 80, Skill::Intercept=> 80,
Skill::InterceptPlus => 110, Skill::InterceptPlus => 110,
Skill::InterceptPlusPlus => 150, Skill::InterceptPlusPlus => 150,
Skill::TriageTick=> 75, Skill::TriageTick=> 75,
Skill::TriageTickPlus => 110, Skill::TriageTickPlus => 110,
Skill::TriageTickPlusPlus => 140, Skill::TriageTickPlusPlus => 140,
@ -857,9 +860,9 @@ impl Skill {
Skill::BanishPlus => vec![ConstructEffect {effect: Effect::Banish, duration: 2, meta: None, tick: None}], Skill::BanishPlus => vec![ConstructEffect {effect: Effect::Banish, duration: 2, meta: None, tick: None}],
Skill::BanishPlusPlus => vec![ConstructEffect {effect: Effect::Banish, duration: 2, meta: None, tick: None}], Skill::BanishPlusPlus => vec![ConstructEffect {effect: Effect::Banish, duration: 2, meta: None, tick: None}],
Skill::Block => vec![ConstructEffect {effect: Effect::Block, duration: 1, Skill::Block => vec![ConstructEffect {effect: Effect::Block, duration: 1,
meta: Some(EffectMeta::Multiplier(50)), tick: None}], meta: Some(EffectMeta::Multiplier(35)), tick: None}],
Skill::Buff => vec![ConstructEffect {effect: Effect::Buff, duration: 2, Skill::Buff => vec![ConstructEffect {effect: Effect::Buff, duration: 3,
meta: Some(EffectMeta::Multiplier(125)), tick: None }], meta: Some(EffectMeta::Multiplier(130)), tick: None }],
Skill::Electrify => vec![ConstructEffect {effect: Effect::Electric, duration: 1, Skill::Electrify => vec![ConstructEffect {effect: Effect::Electric, duration: 1,
meta: Some(EffectMeta::Skill(Skill::Electrocute)), tick: None}], meta: Some(EffectMeta::Skill(Skill::Electrocute)), tick: None}],
@ -901,11 +904,11 @@ impl Skill {
ConstructEffect {effect: Effect::Decay, duration: 4, ConstructEffect {effect: Effect::Decay, duration: 4,
meta: Some(EffectMeta::Skill(Skill::DecayTickPlusPlus)), tick: None}], meta: Some(EffectMeta::Skill(Skill::DecayTickPlusPlus)), tick: None}],
Skill::Haste => vec![ConstructEffect {effect: Effect::Haste, duration: 2, Skill::Haste => vec![ConstructEffect {effect: Effect::Haste, duration: 3,
meta: Some(EffectMeta::Multiplier(150)), tick: None }], meta: Some(EffectMeta::Multiplier(150)), tick: None }],
Skill::HastePlus => vec![ConstructEffect {effect: Effect::Haste, duration: 3, Skill::HastePlus => vec![ConstructEffect {effect: Effect::Haste, duration: 4,
meta: Some(EffectMeta::Multiplier(175)), tick: None }], meta: Some(EffectMeta::Multiplier(175)), tick: None }],
Skill::HastePlusPlus => vec![ConstructEffect {effect: Effect::Haste, duration: 4, Skill::HastePlusPlus => vec![ConstructEffect {effect: Effect::Haste, duration: 5,
meta: Some(EffectMeta::Multiplier(225)), tick: None }], meta: Some(EffectMeta::Multiplier(225)), tick: None }],
Skill::Absorb => vec![ConstructEffect {effect: Effect::Absorb, duration: 2, Skill::Absorb => vec![ConstructEffect {effect: Effect::Absorb, duration: 2,
@ -919,11 +922,11 @@ impl Skill {
Skill::AbsorptionPlus => vec![ConstructEffect {effect: Effect::Absorption, duration: 5, meta: None, tick: None}], Skill::AbsorptionPlus => vec![ConstructEffect {effect: Effect::Absorption, duration: 5, meta: None, tick: None}],
Skill::AbsorptionPlusPlus => vec![ConstructEffect {effect: Effect::Absorption, duration: 7, meta: None, tick: None}], Skill::AbsorptionPlusPlus => vec![ConstructEffect {effect: Effect::Absorption, duration: 7, meta: None, tick: None}],
Skill::Hybrid => vec![ConstructEffect {effect: Effect::Hybrid, duration: 2, Skill::Hybrid => vec![ConstructEffect {effect: Effect::Hybrid, duration: 3,
meta: Some(EffectMeta::Multiplier(150)), tick: None }], meta: Some(EffectMeta::Multiplier(150)), tick: None }],
Skill::HybridPlus => vec![ConstructEffect {effect: Effect::Hybrid, duration: 3, Skill::HybridPlus => vec![ConstructEffect {effect: Effect::Hybrid, duration: 4,
meta: Some(EffectMeta::Multiplier(175)), tick: None }], meta: Some(EffectMeta::Multiplier(175)), tick: None }],
Skill::HybridPlusPlus => vec![ConstructEffect {effect: Effect::Hybrid, duration: 4, Skill::HybridPlusPlus => vec![ConstructEffect {effect: Effect::Hybrid, duration: 5,
meta: Some(EffectMeta::Multiplier(225)), tick: None }], meta: Some(EffectMeta::Multiplier(225)), tick: None }],
Skill::Invert => vec![ConstructEffect {effect: Effect::Invert, duration: 2, meta: None, tick: None}], Skill::Invert => vec![ConstructEffect {effect: Effect::Invert, duration: 2, meta: None, tick: None}],
@ -931,17 +934,11 @@ impl Skill {
Skill::InvertPlusPlus => vec![ConstructEffect {effect: Effect::Invert, duration: 4, meta: None, tick: None}], Skill::InvertPlusPlus => vec![ConstructEffect {effect: Effect::Invert, duration: 4, meta: None, tick: None}],
Skill::Counter => vec![ConstructEffect {effect: Effect::Counter, duration: 1, Skill::Counter => vec![ConstructEffect {effect: Effect::Counter, duration: 1,
meta: Some(EffectMeta::Skill(Skill::CounterAttack)), tick: None}, meta: Some(EffectMeta::Skill(Skill::CounterAttack)), tick: None}],
ConstructEffect {effect: Effect::Block, duration: 1,
meta: Some(EffectMeta::Multiplier(60)), tick: None}],
Skill::CounterPlus => vec![ConstructEffect {effect: Effect::Counter, duration: 1, Skill::CounterPlus => vec![ConstructEffect {effect: Effect::Counter, duration: 1,
meta: Some(EffectMeta::Skill(Skill::CounterAttackPlus)), tick: None}, meta: Some(EffectMeta::Skill(Skill::CounterAttackPlus)), tick: None}],
ConstructEffect {effect: Effect::Block, duration: 1,
meta: Some(EffectMeta::Multiplier(40)), tick: None}],
Skill::CounterPlusPlus => vec![ConstructEffect {effect: Effect::Counter, duration: 1, Skill::CounterPlusPlus => vec![ConstructEffect {effect: Effect::Counter, duration: 1,
meta: Some(EffectMeta::Skill(Skill::CounterAttackPlusPlus)), tick: None}, meta: Some(EffectMeta::Skill(Skill::CounterAttackPlusPlus)), tick: None}],
ConstructEffect {effect: Effect::Block, duration: 1,
meta: Some(EffectMeta::Multiplier(20)), tick: None}],
Skill::Reflect => vec![ConstructEffect {effect: Effect::Reflect, duration: 1, meta: None, tick: None }], Skill::Reflect => vec![ConstructEffect {effect: Effect::Reflect, duration: 1, meta: None, tick: None }],
Skill::ReflectPlus => vec![ConstructEffect {effect: Effect::Reflect, duration: 1, meta: None, tick: None }], Skill::ReflectPlus => vec![ConstructEffect {effect: Effect::Reflect, duration: 1, meta: None, tick: None }],
@ -1060,7 +1057,7 @@ impl Skill {
Skill::AmplifyPlusPlus => Some(1), Skill::AmplifyPlusPlus => Some(1),
Skill::Hybrid| Skill::Hybrid|
Skill::HybridPlus | Skill::HybridPlus |
Skill::HybridPlusPlus => Some(3), Skill::HybridPlusPlus => Some(1),
Skill::Invert=> Some(2), Skill::Invert=> Some(2),
Skill::InvertPlus => Some(2), Skill::InvertPlus => Some(2),
@ -1080,7 +1077,7 @@ impl Skill {
Skill::LinkPlus => Some(2), Skill::LinkPlus => Some(2),
Skill::LinkPlusPlus => Some(2), Skill::LinkPlusPlus => Some(2),
Skill::Silence=> Some(3), Skill::Silence=> Some(2),
Skill::SilencePlus => Some(2), Skill::SilencePlus => Some(2),
Skill::SilencePlusPlus => Some(2), Skill::SilencePlusPlus => Some(2),
@ -1094,11 +1091,11 @@ impl Skill {
Skill::Banish | Skill::Banish |
Skill::BanishPlus | Skill::BanishPlus |
Skill::BanishPlusPlus => Some(3), Skill::BanishPlusPlus => Some(2),
Skill::Haste=> Some(2), Skill::Haste=> Some(1),
Skill::HastePlus => Some(2), Skill::HastePlus => Some(1),
Skill::HastePlusPlus => Some(2), Skill::HastePlusPlus => Some(1),
Skill::Reflect | Skill::Reflect |
Skill::ReflectPlus | Skill::ReflectPlus |
@ -1116,9 +1113,9 @@ impl Skill {
Skill::SlayPlus => None, Skill::SlayPlus => None,
Skill::SlayPlusPlus => None, Skill::SlayPlusPlus => None,
Skill::Sleep=> Some(3), Skill::Sleep=> Some(2),
Skill::SleepPlus => Some(3), Skill::SleepPlus => Some(2),
Skill::SleepPlusPlus => Some(3), Skill::SleepPlusPlus => Some(2),
Skill::Sustain | Skill::Sustain |
Skill::SustainPlus | Skill::SustainPlus |
@ -1445,10 +1442,6 @@ fn counter(source: &mut Construct, target: &mut Construct, mut results: Resoluti
results.push(Resolution::new(source, target) results.push(Resolution::new(source, target)
.event(target.add_effect(skill, skill.effect()[0]))); .event(target.add_effect(skill, skill.effect()[0])));
results.push(Resolution::new(source, target)
.event(target.add_effect(skill, skill.effect()[1]))
.stages(EventStages::PostOnly));
return results; return results;
} }
@ -1456,7 +1449,7 @@ fn counter_attack(source: &mut Construct, target: &mut Construct, mut results: R
let amount = source.red_power().pct(skill.multiplier()); let amount = source.red_power().pct(skill.multiplier());
target.deal_red_damage(skill, amount) target.deal_red_damage(skill, amount)
.into_iter() .into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::StartPost))); .for_each(|e| results.push(Resolution::new(source, target).event(e)));
return results; return results;
} }
@ -1489,7 +1482,7 @@ fn slay(source: &mut Construct, target: &mut Construct, mut results: Resolutions
match e { match e {
Event::Damage { amount, mitigation: _, colour: _, skill: _ } => { Event::Damage { amount, mitigation: _, colour: _, skill: _ } => {
results.push(Resolution::new(source, target).event(e)); results.push(Resolution::new(source, target).event(e));
let heal = source.deal_green_damage(skill, amount); let heal = source.deal_green_damage(skill, amount.pct(50));
for h in heal { for h in heal {
results.push(Resolution::new(source, source).event(h).stages(EventStages::PostOnly)); results.push(Resolution::new(source, source).event(h).stages(EventStages::PostOnly));
}; };

View File

@ -169,7 +169,7 @@ impl Vbox {
let combo = combos.iter().find(|c| c.components == input).ok_or(err_msg("not a combo"))?; let combo = combos.iter().find(|c| c.components == input).ok_or(err_msg("not a combo"))?;
self.bound.push(combo.item); self.bound.push(combo.item);
self.bound.sort_unstable(); // self.bound.sort_unstable();
Ok(self) Ok(self)
} }