diff --git a/client/assets/styles/game.css b/client/assets/styles/game.css index f0935718..d6ccd3d8 100644 --- a/client/assets/styles/game.css +++ b/client/assets/styles/game.css @@ -185,7 +185,7 @@ .game-construct.ko { animation: none; - opacity: 0.35; + opacity: 0.20; /* filter: grayscale(100%); */} @@ -199,6 +199,7 @@ */} .game-construct.unfocus.ko { + opacity: 0.20; /* filter: blur(5px) grayscale(100%); */} @@ -453,4 +454,8 @@ #mnml .game .skills button, #mnml .game .stats { font-size: 75%; } + + .game-construct .name { + display: none; + } } \ No newline at end of file diff --git a/client/assets/styles/instance.css b/client/assets/styles/instance.css index d1b666b5..71d0b229 100644 --- a/client/assets/styles/instance.css +++ b/client/assets/styles/instance.css @@ -6,12 +6,10 @@ overflow-x: hidden; display: grid; grid-template-columns: 2fr minmax(min-content, 1fr); - grid-template-rows: min-content min-content min-content 1fr; + grid-template-rows: min-content 1fr; grid-template-areas: - "top info" "vbox info" - "equip info" "constructs info"; } @@ -81,10 +79,6 @@ "vbox . combiner"; } -.vbox-box { - grid-area: vbox; -} - .vbox-inventory { grid-area: inventory; } @@ -121,19 +115,6 @@ flex: 0; } -.vbox-btn:active, .vbox-btn:hover, .vbox-btn:focus { - color: white; -} - -.vbox-btn.reclaiming, .vbox-btn.reclaim.reclaiming:hover { - background: #a52a2a; - color: black; - box-shadow: inset 0.5em 0 0 0 #a52a2a; -} - -.vbox-btn.reclaim:hover { - color: #a52a2a; -} .vbox-hdr { display: flex; @@ -164,26 +145,6 @@ } } -.vbox button { - width: 100%; - margin: 0; - background-color: #333; - border-width: 0; -} - -.vbox-table td { - transition-property: color, background, border; - transition-duration: 0.5s; - transition-delay: 0; - transition-timing-function: linear; - cursor: pointer; -} - -.vbox-table td:active { - background: whitesmoke; - color: black; -} - /* CONSTRUCT LIST */ .construct-list { diff --git a/client/assets/styles/styles.css b/client/assets/styles/styles.css index a222660a..138c51a6 100644 --- a/client/assets/styles/styles.css +++ b/client/assets/styles/styles.css @@ -146,7 +146,7 @@ button, input { color: whitesmoke; height: auto; border-width: 2px; - border-color: #222; + border-color: #333; border-radius: 0; letter-spacing: 0.25em; box-sizing: border-box; @@ -234,8 +234,8 @@ table .highlight { } button[disabled] { - color: #333; - border-color: #333; + color: #222; + border-color: #222; } /* diff --git a/client/assets/styles/vbox.less b/client/assets/styles/vbox.less new file mode 100644 index 00000000..6371c2df --- /dev/null +++ b/client/assets/styles/vbox.less @@ -0,0 +1,104 @@ +@green: #1FF01F; +@red: #a52a2a; +@blue: #3498db; +@white: #f5f5f5; // whitesmoke +@purple: #9355b5; // 6lack - that far cover + +@darkgray: #222; +@gray: #333; + +.vbox { + margin-bottom: 2em; + + .vbox-section { + // border: 2px solid #444; + } + + .vbox-hdr { + margin-bottom: 1em; + height: 2em; + } + + .vbox-colours { + display: grid; + grid-template-columns: repeat(6, 1fr); + grid-gap: 1em 2em; + align-items: center; + margin-bottom: 1em; + } + + .vbox-items { + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-gap: 1em 2em; + align-items: center; + margin-bottom: 1em; + + button { + width: 100%; + } + } + + .vbox-btn { + width: 100%; + margin: 0; + background-color: @gray; + border-width: 0; + + :active, :hover, :focus { + color: white; + } + + &.reclaim { + height: auto; + + &:hover { + color: @red; + }; + } + + &.reclaiming, &.reclaiming:hover, &.reclaiming:active { + background: @red; + color: black; + } + + &[disabled] { + background: black; + border-width: 1px; + }; + } + + button { + height: 3em; + margin: 0; + + &.empty { + border-style: dashed; + } + + &.highlight { + color: black; + background: @white; + border: 1px solid @white; + } + } + + svg { + stroke: none; + margin: 0 auto; + display: block; + + &.red { + fill: @red; + } + + &.green { + fill: @green; + } + + &.blue { + fill: @blue; + } + } + +} diff --git a/client/index.js b/client/index.js index 8f409800..636aa1df 100644 --- a/client/index.js +++ b/client/index.js @@ -1,6 +1,7 @@ require('./assets/styles/styles.css'); require('./assets/styles/styles.mobile.css'); require('./assets/styles/instance.css'); +require('./assets/styles/vbox.less'); require('./assets/styles/instance.mobile.css'); require('./assets/styles/game.css'); diff --git a/client/package.json b/client/package.json index 2182bf77..842310a9 100644 --- a/client/package.json +++ b/client/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "start": "parcel watch index.html --out-dir /var/lib/mnml/public/current", - "anims": "parcel animations.html --host 0.0.0.0 --port 40080 --no-source-maps", + "anims": "parcel watch animations.html --out-dir /var/lib/mnml/public/current", "build": "parcel build index.html", "scss": "node-sass --watch assets/scss -o assets/styles", "lint": "eslint --fix --ext .jsx src/", @@ -40,7 +40,8 @@ "eslint-config-airbnb-base": "^13.1.0", "eslint-plugin-import": "^2.14.0", "eslint-plugin-react": "^7.11.1", - "jest": "^18.0.0" + "jest": "^18.0.0", + "less": "^3.9.0" }, "alias": { "react": "preact-compat", diff --git a/client/src/actions.jsx b/client/src/actions.jsx index e4f6fb2b..4f93f33d 100644 --- a/client/src/actions.jsx +++ b/client/src/actions.jsx @@ -1,6 +1,11 @@ export const setAccount = value => ({ type: 'SET_ACCOUNT', value }); export const setActiveConstruct = value => ({ type: 'SET_ACTIVE_CONSTRUCT', value }); -export const setAvatarAnimation = value => ({ type: 'SET_AVATAR_ANIMATION', value }); + +export const setAnimating = value => ({ type: 'SET_ANIMATING', value }); +export const setAnimSource = value => ({ type: 'SET_ANIM_SOURCE', value }); +export const setAnimTarget = value => ({ type: 'SET_ANIM_TARGET', value }); +export const setAnimText = value => ({ type: 'SET_ANIM_TEXT', value }); + export const setActiveItem = value => ({ type: 'SET_ACTIVE_VAR', value }); export const setActiveSkill = (constructId, skill) => ({ type: 'SET_ACTIVE_SKILL', value: constructId ? { constructId, skill } : null }); export const setCombiner = value => ({ type: 'SET_COMBINER', value: Array.from(value) }); @@ -27,4 +32,7 @@ export const setSkip = value => ({ type: 'SET_SKIP', value }); export const setShop = value => ({ type: 'SET_SHOP', value }); export const setTeam = value => ({ type: 'SET_SELECTED_CONSTRUCTS', value: Array.from(value) }); export const setVboxHighlight = value => ({ type: 'SET_VBOX_HIGHLIGHT', value }); + +export const setVboxSelected = value => ({ type: 'SET_VBOX_SELECTED', value }); + export const setWs = value => ({ type: 'SET_WS', value }); diff --git a/client/src/animations.socket.jsx b/client/src/animations.socket.jsx index b1a10305..fb0666b4 100644 --- a/client/src/animations.socket.jsx +++ b/client/src/animations.socket.jsx @@ -5,7 +5,7 @@ const eachSeries = require('async/eachSeries'); const actions = require('./actions'); const { TIMES } = require('./constants'); -const { getCombatSequence } = require('./utils'); +const animations = require('./animations.utils'); const SOCKET_URL = process.env.NODE_ENV === 'production' ? 'wss://mnml.gg/api/ws' : 'ws://localhost/api/ws'; @@ -20,47 +20,57 @@ function createSocket(store) { } function sendDevResolve(a, b, skill) { - send({ method: 'dev_game_resolve', params: { a, b, skill } }); + send(['DevResolve', { a, b, skill }]); } function onDevResolutions(newRes) { - const { game: currentGame } = store.getState(); - let id = 0; + const { game, account, animating } = store.getState(); + + if (animating) return false; + store.dispatch(actions.setAnimating(true)); + return eachSeries(newRes, (r, cb) => { if (['Disable', 'TargetKo'].includes(r.event[0])) return cb(); - // Create sub events for combat animations - const sequence = getCombatSequence(r); - id += 1; - return eachSeries(sequence, (stages, sCb) => { - const stagedR = Object.create(r); - stagedR.sequence = sequence; - stagedR.stages = stages; - stagedR.id = id; + store.dispatch(actions.setResolution(r)); - let timeout = 0; - if (stages.includes('START_SKILL') && stages.includes('END_SKILL')) { - timeout = TIMES.SOURCE_AND_TARGET_TOTAL_DURATION; - } else if (stages.includes('START_SKILL')) timeout = TIMES.SOURCE_DURATION_MS; - else if (stages.includes('END_SKILL')) timeout = TIMES.TARGET_DURATION_MS; - else if (stages.includes('POST_SKILL')) timeout = TIMES.POST_SKILL_DURATION_MS; - store.dispatch(actions.setResolution(stagedR)); + // convert server enum into anims keywords + // todo make serersideonly + const sequence = animations.getSequence(r); + const timeout = animations.getTime(sequence); + const anims = animations.getObjects(r, sequence, game, account); + const text = animations.getText(r, sequence); + + if (sequence.includes('START_SKILL')) store.dispatch(actions.setAnimSource(anims.animSource)); + if (sequence.includes('END_SKILL')) store.dispatch(actions.setAnimTarget(anims.animTarget)); + if (sequence.includes('POST_SKILL')) { + // timeout to prevent text classes from being added too soon + setTimeout( + () => store.dispatch(actions.setAnimText(text)), + timeout - 1000, + ); + } + + return setTimeout(() => { + store.dispatch(actions.setAnimSource(null)); + store.dispatch(actions.setAnimTarget(null)); + store.dispatch(actions.setAnimText(null)); + return setTimeout(cb, 50); + }, timeout); - return setTimeout(sCb, timeout); - }, err => { - if (err) console.error(err); - store.dispatch(actions.setAvatarAnimation({ id, source: false, target: false })); - // Finished this resolution - return cb(); - }); }, err => { if (err) return console.error(err); - store.dispatch(actions.setAvatarAnimation({ id: -1, source: false, target: false })); - store.dispatch(actions.setResolution(null)); - // stop skipping resolutions + // clear animation state + store.dispatch(actions.setAnimSource(null)); + store.dispatch(actions.setAnimTarget(null)); + store.dispatch(actions.setAnimText(null)); + store.dispatch(actions.setAnimating(false)); + store.dispatch(actions.setSkip(false)); - // update the game - store.dispatch(actions.setGame(currentGame)); + store.dispatch(actions.setResolution(null)); + + // set the game state so resolutions don't fire twice + store.dispatch(actions.setGame(game)); return true; }); } @@ -75,8 +85,13 @@ function createSocket(store) { // decode binary msg from server const blob = new Uint8Array(event.data); const res = cbor.decode(blob); + // if (res.err) return errHandler(res.err); + const [msgType, params] = res; - if (!handlers[msgType]) return false; + if (msgType !== 'Pong') console.log(res); + + // check for error and split into response type and data + if (!handlers[msgType]) return console.error(`${msgType} handler missing`); return handlers[msgType](params); } diff --git a/client/src/animations.test.jsx b/client/src/animations.test.jsx index 203e3976..3704c4f6 100644 --- a/client/src/animations.test.jsx +++ b/client/src/animations.test.jsx @@ -20,10 +20,10 @@ const testAccount = { // Redux Store const store = createStore( - combineReducers(reducers) + combineReducers(reducers), + window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), ); -store.subscribe(() => console.log(store.getState())); store.dispatch(actions.setAccount(testAccount)); store.dispatch(actions.setGame(testGame)); diff --git a/client/src/animations.utils.jsx b/client/src/animations.utils.jsx new file mode 100644 index 00000000..70d4cc68 --- /dev/null +++ b/client/src/animations.utils.jsx @@ -0,0 +1,185 @@ +const { removeTier } = require('./utils'); +const { TIMES } = require('./constants'); + +function none() { + return { + animSource: null, + animTarget: null, + }; +} + +function getObjects(resolution, stages, game, account) { + if (!resolution) return none(); + if (!resolution.target) return none(); + + const [, event] = resolution.event; + if (!event || !event.skill) return none(); + + const playerTeam = game.players.find(t => t.id === account.id); + const playerTeamIds = playerTeam.constructs.map(c => c.id); + const otherTeam = game.players.find(t => t.id !== account.id); + const otherTeamIds = otherTeam.constructs.map(c => c.id); + const sourceIsPlayer = playerTeamIds.includes(resolution.source.id); + const targetIsPlayer = playerTeamIds.includes(resolution.target.id); + + const sameTeam = (sourceIsPlayer && targetIsPlayer) || (!sourceIsPlayer && !targetIsPlayer); + let y = 0; + if (!sameTeam) y = targetIsPlayer ? 1 : -1; + + const i = sourceIsPlayer + ? playerTeamIds.findIndex(c => c === resolution.source.id) + : otherTeamIds.findIndex(c => c === resolution.source.id); + + const j = targetIsPlayer + ? playerTeamIds.findIndex(c => c === resolution.target.id) + : otherTeamIds.findIndex(c => c === resolution.target.id); + const x = j - i; + const direction = { x, y }; + // const targetTeam = targetIsPlayer ? playerTeamIds : otherTeamIds; + + const createSourceAnim = () => { + return { + animation: 'sourceCast', + constructId: resolution.source.id, + direction, + }; + }; + + const skipSource = !stages.includes('START_SKILL') + || resolution.source.id === resolution.target.id; + + const animSource = skipSource + ? null + : createSourceAnim(); + + const skill = removeTier(event.skill); + + const animTarget = { + skill, + constructId: resolution.target.id, + player: playerTeamIds.includes(resolution.target.id), + direction, + }; + + return { + animSource, + animTarget, + }; +} + +function getSequence(resolution) { + if (!resolution.event) return []; + if (resolution.event[0] === 'Inversion') return []; + if (['Skill', 'AoeSkill'].includes(resolution.event[0])) return ['START_SKILL', 'END_SKILL']; + if (resolution.event[0] === 'Ko') return ['POST_SKILL']; + + switch (resolution.stages) { + case 'AllStages': return ['START_SKILL', 'END_SKILL', 'POST_SKILL']; + case 'StartEnd': return ['START_SKILL', 'END_SKILL']; + case 'StartPost': return ['START_SKILL', 'POST_SKILL']; + case 'StartOnly': return ['START_SKILL']; + case 'EndPost': return ['END_SKILL', 'POST_SKILL']; + case 'EndOnly': return ['END_SKILL']; + case 'PostOnly': return ['POST_SKILL']; + case 'None': return []; + default: return ['START_SKILL', 'END_SKILL', 'POST_SKILL']; + } +} + +const SOURCE_DURATION_MS = 1000; +const TARGET_DELAY_MS = 500; +const TARGET_DURATION_MS = 1500; +const POST_SKILL_DELAY_MS = 2000; +const POST_SKILL_DURATION_MS = 1000; +const SOURCE_AND_TARGET_TOTAL_DURATION = TARGET_DELAY_MS + TARGET_DURATION_MS + POST_SKILL_DURATION_MS; + +function getTime(stages) { + if (stages.includes('START_SKILL') && stages.includes('END_SKILL')) { + return TIMES.SOURCE_AND_TARGET_TOTAL_DURATION; + } + + let time = 0; + + if (stages.includes('START_SKILL')) time += TIMES.SOURCE_DURATION_MS; + if (stages.includes('END_SKILL')) time += TIMES.TARGET_DURATION_MS; + if (stages.includes('POST_SKILL')) time += TIMES.POST_SKILL_DURATION_MS; + + return time; +} + +function getText(resolution, sequence) { + if (!resolution) return { text: null, constructId: null }; + if (!sequence.includes('POST_SKILL')) return { text: null, constructId: null }; + + function generateText() { + const [type, event] = resolution.event; + if (type === 'Ko') { + return 'KO!'; + } + + if (type === 'Disable') { + const { disable } = event; + return `${disable}`; + } + + if (type === 'Immunity') { + return 'IMMUNE'; + } + + if (type === 'Damage') { + const { mitigation, colour } = event; + let { amount } = event; + if (colour === 'Green') amount *= -1; + const mitigationText = mitigation + ? `(${mitigation})` + : ''; + return `${amount} ${mitigationText}`; + } + + if (type === 'Healing') { + const { amount, overhealing } = event; + return `${amount} (${overhealing} OH)`; + } + + if (type === 'Inversion') { + return 'INVERT'; + } + + if (type === 'Reflection') { + return 'REFLECT'; + } + + if (type === 'Effect') { + const { effect, duration } = event; + return `+ ${effect} ${duration}T`; + } + + if (type === 'Recharge') { + const { red, blue } = event; + return [`+${red}R ${blue}B`, '']; + } + + if (type === 'Removal') { + const { effect } = event; + return `-${effect}`; + } + + return false; + } + + return { + text: generateText(), + constructId: resolution.target.id, + }; +} + +module.exports = { + getObjects, + getTime, + getSequence, + getText, +}; + + + // if (!(resolution.target.id === construct.id) + // && !(resolution.event[0] === 'AoeSkill' && targetTeam.includes(construct.id))) return false; diff --git a/client/src/app.jsx b/client/src/app.jsx index 20586dc7..2e64724f 100644 --- a/client/src/app.jsx +++ b/client/src/app.jsx @@ -15,7 +15,8 @@ const Mnml = require('./components/mnml'); // Redux Store const store = createStore( - combineReducers(reducers) + combineReducers(reducers), + window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), ); document.fonts.load('16pt "Jura"').then(() => { diff --git a/client/src/components/animations.jsx b/client/src/components/animations.jsx index bab9c2a6..1a4e3ee6 100644 --- a/client/src/components/animations.jsx +++ b/client/src/components/animations.jsx @@ -1,4 +1,6 @@ const preact = require('preact'); +const { Component } = require('preact'); +const { connect } = require('preact-redux'); const Amplify = require('./anims/amplify'); const Absorb = require('./anims/absorb'); @@ -36,130 +38,102 @@ const Stun = require('./anims/stun'); const Triage = require('./anims/triage'); const TriageTick = require('./anims/triage.tick'); -// const Test = require('./anims/test'); +const actions = require('../actions'); -const { removeTier } = require('../utils'); +const addState = connect( + function receiveState(state) { + const { animTarget } = state; + return { animTarget }; + }, +); -function animations(props) { - const { game, account, resolution, player, construct, avatarAnimation, setAvatarAnimation } = props; - if (!resolution || resolution === 'clear') return false; - const [, event] = resolution.event; - if (!event || !event.skill) return false; - if (!resolution.target) return false; +class ConstructAnimation extends Component { + render(props) { + const { + animTarget, + construct, + } = props; - // source animation - const playerTeam = game.players.find(t => t.id === account.id); - const playerTeamIds = playerTeam.constructs.map(c => c.id); - const otherTeam = game.players.find(t => t.id !== account.id); - const otherTeamIds = otherTeam.constructs.map(c => c.id); + if (!animTarget) return false; - const sourceIsPlayer = playerTeamIds.includes(resolution.source.id); - const targetIsPlayer = playerTeamIds.includes(resolution.target.id); + const { + skill, + player, + direction, + constructId, + } = animTarget; - const getDirection = () => { - const sameTeam = (sourceIsPlayer && targetIsPlayer) || (!sourceIsPlayer && !targetIsPlayer); - let y = 0; - if (!sameTeam) y = targetIsPlayer ? 1 : -1; + if (construct.id !== constructId) return false; - const i = sourceIsPlayer - ? playerTeamIds.findIndex(c => c === resolution.source.id) - : otherTeamIds.findIndex(c => c === resolution.source.id); + // find target animation + const chooseAnim = (skill) => { + switch (skill) { + // Attack base + case 'Attack': return ; + case 'Blast': return ; + case 'Siphon': return ; + case 'SiphonTick': return ; + case 'Strike': return ; + case 'Chaos': return ; + case 'Slay': return ; + case 'Heal': return ; - const j = targetIsPlayer - ? playerTeamIds.findIndex(c => c === resolution.target.id) - : otherTeamIds.findIndex(c => c === resolution.target.id); - const x = j - i; - return { x, y }; - }; + // Buff Base + case 'Buff': return ; + case 'Amplify': return ; + case 'Haste': return ; + case 'Triage': return ; + case 'TriageTick': return ; + case 'Link': return ; + case 'Hybrid': return ; + case 'Intercept': return ; - const skipSource = (resolution.source.id === resolution.target.id - && ['Invert', 'Banish'].includes(removeTier(event.skill))); + // Debuff base + case 'Debuff': return ; + case 'Curse': return ; + case 'Decay': return ; + case 'DecayTick': return ; + // case 'Invert': return setAvatarAnimation(true, true, resolution.id, construct.id, 'invert', null); + case 'Purge': return ; + case 'Silence': return ; + case 'Restrict': return ; - // Play Source Animation - if (!skipSource && resolution.source.id === construct.id && resolution.stages.includes('START_SKILL')) { - if (!avatarAnimation.source) { - setAvatarAnimation(true, avatarAnimation.target, resolution.id, - construct.id, 'sourceCast', getDirection()); - } + // Stun Base + case 'Stun': return ; + // case 'Banish': return setAvatarAnimation(true, true, resolution.id, construct.id, 'banish', null); + case 'Bash': return ; + case 'Absorb': return ; + case 'Sleep': return ; + case 'Break': return ; + case 'Ruin': return ; + + // Block Base + case 'Block': return ; + case 'Sustain': return ; + case 'Electrify': return ; + case 'Electrocute': return ; + case 'ElectrocuteTick': return ; + case 'Counter': return ; + case 'Purify': return ; + case 'Recharge': return ; + case 'Reflect': return ; + + default: return false; + }; + }; + + const anim = chooseAnim(skill); + if (!anim) return false; + + return ( +
+ {anim} +
+ ); } - const targetTeam = targetIsPlayer ? playerTeamIds : otherTeamIds; - if (!(resolution.target.id === construct.id) - && !(resolution.event[0] === 'AoeSkill' && targetTeam.includes(construct.id))) return false; - - // Play Target animation - const anim = text => { - if (!text || !resolution.sequence[0].includes('END_SKILL')) return false; - const skill = removeTier(text); - switch (skill) { - // Attack base - case 'Attack': return ; - case 'Blast': return ; - case 'Siphon': return ; - case 'SiphonTick': return ; - case 'Strike': return ; - case 'Chaos': return ; - case 'Slay': return ; - case 'Heal': return ; - - // Buff Base - case 'Buff': return ; - case 'Amplify': return ; - case 'Haste': return ; - case 'Triage': return ; - case 'TriageTick': return ; - case 'Link': return ; - case 'Hybrid': return ; - case 'Intercept': return ; - - // Debuff base - case 'Debuff': return ; - case 'Curse': return ; - case 'Decay': return ; - case 'DecayTick': return ; - case 'Invert': { - if (!avatarAnimation.target) { - setAvatarAnimation(avatarAnimation.source, true, resolution.id, construct.id, 'invert', null); - } return false; - } - case 'Purge': return ; - case 'Silence': return ; - case 'Restrict': return ; - - // Stun Base - case 'Stun': return ; - case 'Banish': { - if (!avatarAnimation.target) { - setAvatarAnimation(avatarAnimation.source, true, resolution.id, construct.id, 'banish', null); - } return false; - } - case 'Bash': return ; - case 'Absorb': return ; - case 'Sleep': return ; - case 'Break': return ; - case 'Ruin': return ; - - // Block Base - case 'Block': return ; - case 'Sustain': return ; - case 'Electrify': return ; - case 'Electrocute': return ; - case 'ElectrocuteTick': return ; - case 'Counter': return ; - case 'Purify': return ; - case 'Recharge': return ; - case 'Reflect': return ; - - default: return false; - } - }; - - const combatAnim = anim(event.skill); - if (!combatAnim) return false; - return ( -
- {combatAnim} -
- ); } -module.exports = animations; + +module.exports = { + ConstructAnimation: addState(ConstructAnimation), +}; diff --git a/client/src/components/anims/hybrid.jsx b/client/src/components/anims/hybrid.jsx index 5b0bce31..ddcb8a9e 100644 --- a/client/src/components/anims/hybrid.jsx +++ b/client/src/components/anims/hybrid.jsx @@ -33,7 +33,7 @@ class Hybrid extends Component { class="green-one" cx='50' cy='150' - r='10' + r="15" fill={COLOURS.GREEN} stroke="none" /> @@ -41,7 +41,7 @@ class Hybrid extends Component { class="green-two" cx='250' cy='150' - r='10' + r="15" fill={COLOURS.GREEN} stroke="none" /> @@ -49,7 +49,7 @@ class Hybrid extends Component { class="bluewhite-one" cx='150' cy='50' - r='10' + r="15" fill={COLOURS.BLUE} stroke="none" /> @@ -65,7 +65,7 @@ class Hybrid extends Component { class="bluewhite-two" cx='150' cy='250' - r='10' + r="15" fill={COLOURS.BLUE} stroke="none" /> @@ -77,7 +77,6 @@ class Hybrid extends Component { fill={COLOURS.WHITE} stroke="none" /> - ); @@ -100,6 +99,7 @@ class Hybrid extends Component { })); this.animations.push(anime({ + r: [10, anime.random(10, 30)], targets: ['#hybrid circle.green-one'], cx: [50, 250, 50, 250], delay: TIMES.TARGET_DELAY_MS, @@ -109,6 +109,7 @@ class Hybrid extends Component { })); this.animations.push(anime({ + r: [10, anime.random(10, 30)], targets: ['#hybrid circle.green-two'], cy: [250, 50, 250, 50], delay: TIMES.TARGET_DELAY_MS, @@ -118,6 +119,7 @@ class Hybrid extends Component { })); this.animations.push(anime({ + r: [10, anime.random(10, 30)], targets: ['#hybrid circle.bluewhite-one'], fill: [COLOURS.WHITE, COLOURS.BLUE], cy: [50, 250, 50, 250], @@ -128,6 +130,7 @@ class Hybrid extends Component { })); this.animations.push(anime({ + r: [10, anime.random(10, 30)], targets: ['#hybrid circle.bluewhite-two'], cx: [250, 50, 250, 50], fill: [COLOURS.WHITE, COLOURS.BLUE], diff --git a/client/src/components/anims/sleep.jsx b/client/src/components/anims/sleep.jsx index f426d83b..a7d6be6b 100644 --- a/client/src/components/anims/sleep.jsx +++ b/client/src/components/anims/sleep.jsx @@ -70,10 +70,6 @@ class Sleep extends Component { } componentDidMount() { - anime.set('#sleep', { - translateY: 75, - }); - this.animations.push(anime({ targets: ['#sleep'], opacity: [ diff --git a/client/src/components/anims/source.cast.jsx b/client/src/components/anims/source.cast.jsx index c6690ca9..25cb8ae8 100644 --- a/client/src/components/anims/source.cast.jsx +++ b/client/src/components/anims/source.cast.jsx @@ -2,12 +2,12 @@ const anime = require('animejs').default; const { TIMES } = require('../../constants'); -function sourceCast(id, params) { - const { x, y } = params; +function sourceCast(id, direction) { + const { x, y } = direction; return anime({ targets: [document.getElementById(id)], - translateX: x * 200, - translateY: y * 200, + translateX: [0, x * 200], + translateY: [0, y * 200], easing: 'easeInOutElastic', direction: 'alternate', duration: TIMES.SOURCE_DURATION_MS, diff --git a/client/src/components/construct.jsx b/client/src/components/construct.jsx index 9c669810..b6793d82 100644 --- a/client/src/components/construct.jsx +++ b/client/src/components/construct.jsx @@ -1,7 +1,8 @@ const preact = require('preact'); const { Component } = require('preact'); const { connect } = require('preact-redux'); -const anime = require('animejs').default; + +// const { match } = require('./../utils'); const banish = require('./anims/banish'); const idleAnimation = require('./anims/idle'); @@ -10,8 +11,8 @@ const sourceCast = require('./anims/source.cast'); const addState = connect( function receiveState(state) { - const { avatarAnimation } = state; - return { avatarAnimation }; + const { animSource, animTarget } = state; + return { animSource, animTarget }; } ); @@ -21,9 +22,8 @@ class ConstructAvatar extends Component { // The animation ids are a check to ensure that animations are not repeated // When a new construct animation is communicated with state it will have a corresponding Id // which is a count of how many resoluttions have passed - this.animId = 0; - this.source = false; this.animations = []; + this.resetAnimations = this.resetAnimations.bind(this); } render() { @@ -37,38 +37,139 @@ class ConstructAvatar extends Component { } componentDidMount() { - this.idle = idleAnimation(this.props.construct.id); - this.animations.push(this.idle); - } + const { animSource, animTarget, construct } = this.props; - componentWillReceiveProps(nextProps) { - if (nextProps.avatarAnimation.id === -1) this.animId = 0; // The current set of resolutions ended reset to 0 - if (nextProps.avatarAnimation.id !== this.animId && nextProps.avatarAnimation.animTargetId === this.props.construct.id) { - this.animId = nextProps.avatarAnimation.id; - const selectAnim = () => { - switch (nextProps.avatarAnimation.type) { - case 'banish': return banish(this.props.construct.id); - case 'invert': return invert(this.props.construct.id); - case 'sourceCast': return sourceCast(this.props.construct.id, nextProps.avatarAnimation.params); - default: return null; - } - }; - const anim = selectAnim(); - if (anim) { - this.idle.pause(); - this.animations.push(anim); - anim.finished.then(this.idle.play); + // back to idle + if (!animTarget && !animSource) { + if (!this.idle) { + this.idle = idleAnimation(this.props.construct.id); + this.animations.push(this.idle); } + + return this.idle.play(); } + + const isSource = animSource && animSource.constructId === construct.id; + + const selectAnim = () => { + if (isSource) { + console.warn(construct.name, animSource); + return sourceCast(animSource.constructId, animSource.direction); + } + + switch (animTarget.skill) { + case 'banish': return banish(construct.id); + case 'invert': return invert(construct.id); + default: return null; + } + }; + + const anim = selectAnim(); + if (!anim) return false; + + this.idle.pause(); + this.animations.push(anim); + return true; } - componentWillUnmount() { + componentDidUpdate(prevProps) { + const { animSource, animTarget, construct } = this.props; + + // back to idle + if (!animTarget && !animSource) { + return this.idle.play(); + } + + const isSource = animSource && animSource.constructId === construct.id; + + const selectAnim = () => { + if (isSource) { + return sourceCast(animSource.constructId, animSource.direction); + } + + switch (animTarget.skill) { + case 'Banish': return banish(construct.id); + case 'Invert': return invert(construct.id); + default: return null; + } + }; + + const anim = selectAnim(); + if (!anim) return false; + + this.idle.pause(); + this.animations.push(anim); + return true; + } + + resetAnimations() { for (let i = this.animations.length - 1; i >= 0; i--) { this.animations[i].reset(); } } + + componentWillUnmount() { + this.resetAnimations(); + } + + shouldComponentUpdate({ animSource, animTarget, construct }) { + if (construct !== this.props.construct) { + return true; + } + + if (animSource === this.props.animSource && animTarget === this.props.animTarget) { + // console.warn(construct.name, 'thinks its same props') + return false; + } + + // something has changed + // what do? + + // this is the source + if (animSource && animSource.constructId === construct.id) { + // console.warn(construct.name, 'should update') + return true; + } + + // this is the target + if (animTarget && animTarget.constructId === construct.id) { + // console.warn(construct.name, 'should update') + return true; + } + + // we were previously doing src anim + const prevSrc = this.props.animSource && this.props.animSource.constructId === construct.id; + if (prevSrc && !animSource) return true; + + const prevTarget = this.props.animTarget && this.props.animTarget.constructId === construct.id; + if (prevTarget && !animTarget) return true; + + // console.warn(construct.name, 'not updating'); + + return false; + } +} + + +const addStateText = connect( + function receiveState(state) { + const { animText } = state; + return { animText }; + } +); + +function constructText(props) { + const { construct, animText } = props; + if (!construct || !animText) return false; + + const text = animText.constructId === construct.id + ? animText.text + : null; + + return
{text}
; } module.exports = { ConstructAvatar: addState(ConstructAvatar), + ConstructText: addStateText(constructText), }; diff --git a/client/src/components/game.construct.jsx b/client/src/components/game.construct.jsx index 4ed97b38..41c30b8b 100644 --- a/client/src/components/game.construct.jsx +++ b/client/src/components/game.construct.jsx @@ -1,10 +1,11 @@ const { connect } = require('preact-redux'); +const { Component } = require('preact'); const preact = require('preact'); const range = require('lodash/range'); -const { STATS, eventClasses, getCombatText } = require('../utils'); -const { ConstructAvatar } = require('./construct'); -const animations = require('./animations'); +const { STATS, eventClasses } = require('../utils'); +const { ConstructAvatar, ConstructText } = require('./construct'); +const { ConstructAnimation } = require('./animations'); const shapes = require('./shapes'); @@ -19,9 +20,10 @@ const addState = connect( ws, game, account, - resolution, activeSkill, - avatarAnimation, + resolution, + + animText, } = state; function selectSkillTarget(targetConstructId) { @@ -39,76 +41,72 @@ const addState = connect( game, account, resolution, + animText, activeSkill, - avatarAnimation, selectSkillTarget, }; }, - - function receiveDispatch(dispatch) { - function setAvatarAnimation(source, target, id, animTargetId, type, params) { - return dispatch(actions.setAvatarAnimation({ source, target, id, animTargetId, type, params })); - } - - return { - setAvatarAnimation, - }; - } - ); -function GameConstruct(props) { - const { - i, - game, - account, - construct, - player, - resolution, - activeSkill, - avatarAnimation, - setAvatarAnimation, - selectSkillTarget, - } = props; +class GameConstruct extends Component { + constructor() { + super(); + this.resolvedLength = 0; + } + render() { + const { + i, + game, + account, + construct, + player, + activeSkill, + selectSkillTarget, - const ko = construct.green_life.value === 0 ? 'ko' : ''; - const classes = eventClasses(game, account, resolution, construct); + // todo remove dep + resolution, - const stats = ['RedLife', 'GreenLife', 'BlueLife'].map((s, j) => ( -
- {shapes[s]()} -
{construct[STATS[s].stat].value} / {construct[STATS[s].stat].max}
-
{construct[STATS[s].stat].value}
-
- )); + animText, + } = this.props; - const skills = range(0, 3) - .map(j => ); + const ko = construct.green_life.value === 0 ? 'ko' : ''; + const classes = eventClasses(game, account, resolution, construct, animText); - let crypSkills =
 
; - if (player) crypSkills = (
{skills}
); + const stats = ['RedLife', 'GreenLife', 'BlueLife'].map((s, j) => ( +
+ {shapes[s]()} +
{construct[STATS[s].stat].value} / {construct[STATS[s].stat].max}
+
{construct[STATS[s].stat].value}
+
+ )); - const effects = construct.effects.length - ? construct.effects.map(c =>
{c.effect} - {c.duration}T
) - :
 
; - const combatAnim = animations({ game, account, resolution, player, construct, avatarAnimation, setAvatarAnimation }); - const combatText = getCombatText(resolution, construct); - return ( -
selectSkillTarget(construct.id)} - style={ activeSkill ? { cursor: 'pointer' } : {}} - class={`game-construct ${ko} ${classes}`} > -

{construct.name}

- {crypSkills} -
{stats}
- - {combatAnim} -
{combatText}
-
{effects}
-
- ); + const skills = range(0, 3) + .map(j => ); + + let crypSkills =
 
; + if (player) crypSkills = (
{skills}
); + + const effects = construct.effects.length + ? construct.effects.map(c =>
{c.effect} - {c.duration}T
) + :
 
; + + return ( +
selectSkillTarget(construct.id)} + style={ activeSkill ? { cursor: 'pointer' } : {}} + class={`game-construct ${ko} ${classes}`} > +

{construct.name}

+ {crypSkills} +
{stats}
+ + + +
{effects}
+
+ ); + } } module.exports = addState(GameConstruct); diff --git a/client/src/components/game.jsx b/client/src/components/game.jsx index 9c00c291..eddde654 100644 --- a/client/src/components/game.jsx +++ b/client/src/components/game.jsx @@ -65,7 +65,6 @@ function Game(props) { if (!game) return
...
; - console.log('running game'); const otherTeams = game.players.filter(t => t.id !== account.id); const playerTeam = game.players.find(t => t.id === account.id); diff --git a/client/src/components/instance.component.jsx b/client/src/components/instance.component.jsx index 718c062a..eab67020 100644 --- a/client/src/components/instance.component.jsx +++ b/client/src/components/instance.component.jsx @@ -49,7 +49,6 @@ function Instance(args) {
setInfo(null)} > -
); diff --git a/client/src/components/instance.constructs.jsx b/client/src/components/instance.constructs.jsx index a7eb6b53..5f92db4c 100644 --- a/client/src/components/instance.constructs.jsx +++ b/client/src/components/instance.constructs.jsx @@ -81,11 +81,15 @@ function Construct(props) { sendUnequip, } = props; + const fullInfo = itemInfo.items.find(i => i.item === itemEquip); + const isSkill = fullInfo && fullInfo.skill; + const isSpec = fullInfo && fullInfo.spec; + const equipping = itemEquip && (isSkill || isSpec); + function onClick(e) { e.stopPropagation(); e.preventDefault(); - console.log('const click eA') - if (itemEquip !== null) sendVboxApply(construct.id, itemEquip); + if (equipping) sendVboxApply(construct.id, itemEquip); setItemEquip(null); return setActiveConstruct(construct); } @@ -124,13 +128,11 @@ function Construct(props) { return true; } - // const action = skill ? '' : 'action'; - const equip = skillList.includes(vbox.bound[itemEquip]) && !skill ? 'equipping' : ''; - const classes = `${equip}`; + const classes = `${equipping ? 'equipping' : ''}`; return ( -
-
-
{ + // const value = vbox.bound[i]; + // if (combiner.indexOf(i) > -1) { + // return ( + // + //   + // + // ); + // } + + // const highlighted = value && vboxHighlight.includes(value); + + // return ( + // vboxHover(e, value)} + // onClick={e => boundClick(e, i, highlighted) }> + // {convertItem(value)} + // + // ); + // }); + + + function reclaimClick(e) { + e.stopPropagation(); + return setReclaiming(!reclaiming); + } + + const reclaimClass = `vbox-btn reclaim ${reclaiming ? 'reclaiming' : ''}`; + + function inventoryBtn(v, i) { + if (!v && v !== 0) return ; + + function onClick(e) { + if (reclaiming) return sendVboxReclaim(i); + + const combinerIndex = combiner.indexOf(i); + if (combinerIndex > -1) { + return combinerChange(without(combiner, i)); + } + + combiner.push(i); + return combinerChange(combiner); + } + + const highlighted = combiner.indexOf(i) > -1; + const classes = `${highlighted ? 'highlight' : ''}`; + + if (['Red', 'Green', 'Blue'].includes(v)) { + return ( + + ); + } + + return ( + + ); + } + + function combinerBtn() { + let text = ''; + + if (combiner.length < 3) { + for (let i = 0; i < 3; i++) { + if (combiner.length > i) { + text += '■ '; + } else { + text += '▫ '; + } + } + } else { + text = 'combine'; + } + + return ( + + ); + } + + function inventoryElement() { + return ( +
setReclaiming(false)} onMouseOver={e => hoverInfo(e, 'inventory')}>
@@ -338,21 +332,30 @@ function Vbox(args) { reclaim
- - - {boundRows} - -
-
-
e.target.scrollIntoView(true)}>⮟⮝
-
hoverInfo(e, 'combiner')} > - {combinerElement} - +
+ {range(0, 9).map(i => inventoryBtn(vbox.bound[i], i))} +
+ {combinerBtn()}
+ ); + } + + // + // EVERYTHING + // + + + function hoverInfo(e, info) { + e.stopPropagation(); + return setInfo(info); + } + + const classes = 'vbox'; + return ( +
+ {vboxElement()} +
+ {inventoryElement()}
); } diff --git a/client/src/constants.jsx b/client/src/constants.jsx index f8c0ecff..2818ba63 100644 --- a/client/src/constants.jsx +++ b/client/src/constants.jsx @@ -1,15 +1,16 @@ - const SOURCE_DURATION_MS = 1000; const TARGET_DELAY_MS = 500; const TARGET_DURATION_MS = 1500; +const POST_SKILL_DELAY_MS = 2000; const POST_SKILL_DURATION_MS = 1000; -const SOURCE_AND_TARGET_TOTAL_DURATION = TARGET_DELAY_MS + TARGET_DURATION_MS; +const SOURCE_AND_TARGET_TOTAL_DURATION = TARGET_DELAY_MS + TARGET_DURATION_MS + POST_SKILL_DURATION_MS; module.exports = { TIMES: { SOURCE_DURATION_MS, TARGET_DELAY_MS, TARGET_DURATION_MS, + POST_SKILL_DELAY_MS, POST_SKILL_DURATION_MS, SOURCE_AND_TARGET_TOTAL_DURATION, }, diff --git a/client/src/events.jsx b/client/src/events.jsx index 69d8ec4a..3aad7443 100644 --- a/client/src/events.jsx +++ b/client/src/events.jsx @@ -3,7 +3,7 @@ const eachSeries = require('async/eachSeries'); const actions = require('./actions'); const { TIMES } = require('./constants'); -const { getCombatSequence } = require('./utils'); +const animations = require('./animations.utils'); function registerEvents(store) { function setPing(ping) { @@ -42,57 +42,65 @@ function registerEvents(store) { } function setGame(game) { - const { game: currentGame, ws } = store.getState(); + const { game: currentGame, account, ws, animating } = store.getState(); + + if (animating) return false; if (game && currentGame) { if (game.resolved.length !== currentGame.resolved.length) { + store.dispatch(actions.setAnimating(true)); + // stop fetching the game state til animations are done const newRes = game.resolved.slice(currentGame.resolved.length); - let id = game.resolved.length - currentGame.resolved.length; return eachSeries(newRes, (r, cb) => { if (['Disable', 'TargetKo'].includes(r.event[0])) return cb(); - // Create sub events for combat animations - const sequence = getCombatSequence(r); - id += 1; - return eachSeries(sequence, (stages, sCb) => { - const stagedR = Object.create(r); - stagedR.sequence = sequence; - stagedR.stages = stages; - stagedR.id = id; - let timeout = 0; - if (stages.includes('START_SKILL') && stages.includes('END_SKILL')) { - timeout = TIMES.SOURCE_AND_TARGET_TOTAL_DURATION; - } else if (stages.includes('START_SKILL')) timeout = TIMES.SOURCE_DURATION_MS; - else if (stages.includes('END_SKILL')) timeout = TIMES.TARGET_DURATION_MS; - else if (stages.includes('POST_SKILL')) timeout = TIMES.POST_SKILL_DURATION_MS; - store.dispatch(actions.setResolution(stagedR)); - return setTimeout(sCb, timeout); - }, err => { - if (err) console.error(err); - // Clear the anim classes - store.dispatch(actions.setResolution('clear')); - store.dispatch(actions.setAvatarAnimation({ id, source: false, target: false })); - // Finished this resolution small delay for reset - return setTimeout(cb, 5); - }); + store.dispatch(actions.setResolution(r)); + + // convert server enum into anims keywords + // todo make serersideonly + const sequence = animations.getSequence(r); + const timeout = animations.getTime(sequence); + const anims = animations.getObjects(r, sequence, game, account); + const text = animations.getText(r, sequence); + + if (sequence.includes('START_SKILL')) store.dispatch(actions.setAnimSource(anims.animSource)); + if (sequence.includes('END_SKILL')) store.dispatch(actions.setAnimTarget(anims.animTarget)); + if (sequence.includes('POST_SKILL')) { + // timeout to prevent text classes from being added too soon + setTimeout( + () => store.dispatch(actions.setAnimText(text)), + timeout - 1000, + ); + } + + return setTimeout(() => { + store.dispatch(actions.setAnimSource(null)); + store.dispatch(actions.setAnimTarget(null)); + store.dispatch(actions.setAnimText(null)); + return cb(); + }, timeout); + }, err => { if (err) return console.error(err); - store.dispatch(actions.setAvatarAnimation({ id: -1, source: false, target: false })); - store.dispatch(actions.setResolution(null)); - // stop skipping resolutions + // clear animation state + store.dispatch(actions.setAnimSource(null)); + store.dispatch(actions.setAnimTarget(null)); + store.dispatch(actions.setAnimText(null)); + store.dispatch(actions.setAnimating(false)); + store.dispatch(actions.setSkip(false)); - // update the game + store.dispatch(actions.setResolution(null)); + + // set the game state so resolutions don't fire twice store.dispatch(actions.setGame(game)); - // get the latest state and restart polling - ws.sendGameState(currentGame.id); + ws.sendGameState(game.id); return true; }); } } return store.dispatch(actions.setGame(game)); - return console.log('EVENT ->', 'game', game); } function setAccount(account) { @@ -105,7 +113,7 @@ function registerEvents(store) { function clearCombiner() { store.dispatch(actions.setInfo([])); - store.dispatch(actions.setCombiner([null, null, null])); + store.dispatch(actions.setCombiner([])); } function clearConstructRename() { @@ -131,7 +139,7 @@ function registerEvents(store) { } function clearInstance() { - store.dispatch(actions.setCombiner([null, null, null])); + store.dispatch(actions.setCombiner([])); store.dispatch(actions.setReclaiming(false)); store.dispatch(actions.setActiveSkill(null)); store.dispatch(actions.setActiveConstruct(null)); diff --git a/client/src/keyboard.jsx b/client/src/keyboard.jsx index 9e8629b4..8964cec5 100644 --- a/client/src/keyboard.jsx +++ b/client/src/keyboard.jsx @@ -4,7 +4,7 @@ const actions = require('./actions'); function setupKeys(store) { console.log('binding keys'); key.unbind('esc'); - key('esc', () => store.dispatch(actions.setCombiner([null, null, null]))); + key('esc', () => store.dispatch(actions.setCombiner([]))); key('esc', () => store.dispatch(actions.setReclaiming(false))); key('esc', () => store.dispatch(actions.setActiveSkill(null))); key('esc', () => store.dispatch(actions.setActiveConstruct(null))); diff --git a/client/src/reducers.jsx b/client/src/reducers.jsx index 288fca18..539c5473 100644 --- a/client/src/reducers.jsx +++ b/client/src/reducers.jsx @@ -15,8 +15,13 @@ module.exports = { activeConstruct: createReducer(null, 'SET_ACTIVE_CONSTRUCT'), activeItem: createReducer(null, 'SET_ACTIVE_VAR'), activeSkill: createReducer(null, 'SET_ACTIVE_SKILL'), - avatarAnimation: createReducer({ id: -1, source: false, target: false }, 'SET_AVATAR_ANIMATION'), - combiner: createReducer([null, null, null], 'SET_COMBINER'), + + animating: createReducer(false, 'SET_ANIMATING'), + animSource: createReducer(null, 'SET_ANIM_SOURCE'), + animTarget: createReducer(null, 'SET_ANIM_TARGET'), + animText: createReducer(null, 'SET_ANIM_TEXT'), + + combiner: createReducer([], 'SET_COMBINER'), constructs: createReducer([], 'SET_CONSTRUCTS'), constructEditId: createReducer(null, 'SET_CONSTRUCT_EDIT_ID'), constructRename: createReducer(null, 'SET_CONSTRUCT_RENAME'), @@ -39,5 +44,8 @@ module.exports = { shop: createReducer(false, 'SET_SHOP'), team: createReducer([null, null, null], 'SET_SELECTED_CONSTRUCTS'), vboxHighlight: createReducer([], 'SET_VBOX_HIGHLIGHT'), + + vboxSelected: createReducer([], 'SET_VBOX_SELECTED'), + ws: createReducer(null, 'SET_WS'), }; diff --git a/client/src/test.game.js b/client/src/test.game.js index c36174ed..e17cb8e0 100644 --- a/client/src/test.game.js +++ b/client/src/test.game.js @@ -35,6 +35,7 @@ function testGame(uuid) { "constructs": [ { "id": "82e8b940-411c-42a1-8fc2-484ec7207734", + "img": "8446736d-d682-4588-b8a0-5b7ba53bdb55", "account": "8552e0bf-340d-4fc8-b6fc-3d56b68fe2a1", "red_damage": { "base": 256, @@ -108,6 +109,7 @@ function testGame(uuid) { }, { "id": "96ca4a0e-fed2-4ea2-9ec5-ae308f8dde4b", + "img": "8446736d-d682-4588-b8a0-5b7ba53bdb55", "account": "8552e0bf-340d-4fc8-b6fc-3d56b68fe2a1", "red_damage": { "base": 256, @@ -184,6 +186,7 @@ function testGame(uuid) { { "id": "ea302c35-d326-475c-a867-8ad5b162165a", "account": "8552e0bf-340d-4fc8-b6fc-3d56b68fe2a1", + "img": "8446736d-d682-4588-b8a0-5b7ba53bdb55", "red_damage": { "base": 256, "value": Math.floor(Math.random() * 10000), @@ -299,6 +302,7 @@ function testGame(uuid) { { "id": "3aa0f284-1e1b-4054-b38a-b2d50db471bd", "account": uuid, + "img": "8446736d-d682-4588-b8a0-5b7ba53bdb55", "red_damage": { "base": 256, "value": Math.floor(Math.random() * 10000), @@ -385,6 +389,7 @@ function testGame(uuid) { { "id": "50e5d94e-8ebe-495c-a916-3eb509ff4683", "account": uuid, + "img": "8446736d-d682-4588-b8a0-5b7ba53bdb55", "red_damage": { "base": 256, "value": Math.floor(Math.random() * 10000), @@ -465,6 +470,7 @@ function testGame(uuid) { { "id": "5d49fe65-27f0-4372-90a3-334ef906a0f5", "account": uuid, + "img": "8446736d-d682-4588-b8a0-5b7ba53bdb55", "red_damage": { "base": 256, "value": Math.floor(Math.random() * 10000), diff --git a/client/src/utils.jsx b/client/src/utils.jsx index 6a3bb804..bb0717ab 100644 --- a/client/src/utils.jsx +++ b/client/src/utils.jsx @@ -4,6 +4,17 @@ const toast = require('izitoast'); const shapes = require('./components/shapes'); +function match(value, patterns) { + for (let i = 0; i < patterns.length; i++) { + if (value === patterns[i][0]) { + return patterns[i][1](); + } + } + + console.warn('default match - return null', value, patterns); + return null; +} + const stringSort = (k, desc) => { if (desc) { return (a, b) => { @@ -74,9 +85,8 @@ const STATS = { }, }; -function eventClasses(game, account, resolution, construct) { - if (!resolution || resolution === 'clear') return ''; - const postSkill = resolution.stages.includes('POST_SKILL'); +function eventClasses(game, account, resolution, construct, postSkill) { + if (!resolution) return ''; const source = construct.id === resolution.source.id; const target = construct.id === resolution.target.id; // not involved at all. blur them @@ -102,7 +112,7 @@ function eventClasses(game, account, resolution, construct) { if (type === 'Damage') { const { colour } = event; - if (target && postSkill) { + if (target) { construct.green_life.value = resolution.target.green; if (colour === 'Red') { construct.red_life.value = resolution.target.red; @@ -158,98 +168,6 @@ function eventClasses(game, account, resolution, construct) { return ''; } -function getCombatSequence(resolution) { - if (!resolution.event) return false; - if (resolution.event[0] === 'Inversion') return false; - if (['Skill', 'AoeSkill'].includes(resolution.event[0])) return [['START_SKILL', 'END_SKILL']]; - if (resolution.event[0] === 'Ko') return [['POST_SKILL']]; - - switch (resolution.stages) { - case 1: return [['START_SKILL', 'END_SKILL']]; - case 2: return [['START_SKILL'], ['POST_SKILL']]; - case 3: return [['START_SKILL']]; - case 4: return [['END_SKILL'], ['POST_SKILL']]; - case 5: return [['END_SKILL']]; - case 6: return [['POST_SKILL']]; - case 7: return false; - default: return [['START_SKILL', 'END_SKILL'], ['POST_SKILL']]; - } -} - -function getCombatText(resolution, construct) { - if (!resolution || resolution === 'clear') return false; - if (!resolution.stages.includes('POST_SKILL')) return false; - if (construct.id !== resolution.target.id) return false; - - const [type, event] = resolution.event; - if (type === 'Ko') { - return 'KO!'; - } - - if (type === 'Disable') { - const { disable } = event; - return `${disable}`; - } - - if (type === 'Immunity') { - return 'IMMUNE'; - } - - if (type === 'Damage') { - const { mitigation, colour } = event; - let { amount } = event; - if (colour === 'Green') amount *= -1; - const mitigationText = mitigation - ? `(${mitigation})` - : ''; - return `${amount} ${mitigationText}`; - } - - if (type === 'Healing') { - const { amount, overhealing } = event; - return `${amount} (${overhealing} OH)`; - } - - if (type === 'Inversion') { - return 'INVERT'; - } - - if (type === 'Reflection') { - return 'REFLECT'; - } - - if (type === 'Effect') { - const { effect, duration } = event; - return `+ ${effect} ${duration}T`; - } - - if (type === 'Recharge') { - const { red, blue } = event; - return [`+${red}R ${blue}B`, '']; - } - - if (type === 'Removal') { - const { effect } = event; - return `-${effect}`; - } - - return false; -} - -function convertItem(v) { - if (['Red', 'Green', 'Blue'].includes(v)) { - return ( - shapes.vboxColour(v.toLowerCase()) - ); - } - return v ||  ; - // uncomment for double borders in vbox; - // if (v) { - // return
{v}
; - // } - // return; -} - const COLOURS = [ '#a52a2a', '#1FF01F', @@ -379,15 +297,26 @@ function errorToast(message) { }); } +function convertItem(v) { + if (['Red', 'Green', 'Blue'].includes(v)) { + return ( + shapes.vboxColour(v.toLowerCase()) + ); + } + return v ||  ; + // uncomment for double borders in vbox; + // if (v) { + // return
{v}
; + // } + // return; +} module.exports = { stringSort, - convertItem, numSort, eventClasses, - getCombatSequence, - getCombatText, postData, + convertItem, errorToast, NULL_UUID, STATS, @@ -395,4 +324,5 @@ module.exports = { TARGET_COLOURS, randomPoints, removeTier, + match, }; diff --git a/server/src/construct.rs b/server/src/construct.rs index 8c247368..4d268128 100644 --- a/server/src/construct.rs +++ b/server/src/construct.rs @@ -277,7 +277,7 @@ impl Construct { } pub fn spec_add(&mut self, spec: Spec) -> Result<&mut Construct, Error> { - if self.specs.len() >= 6 { + if self.specs.len() >= 3 { return Err(err_msg("maximum specs equipped")); } diff --git a/server/src/names.rs b/server/src/names.rs index d9604f14..67c6e918 100644 --- a/server/src/names.rs +++ b/server/src/names.rs @@ -1,11 +1,14 @@ use rand::prelude::*; use rand::{thread_rng}; -const FIRSTS: [&'static str; 25] = [ +const FIRSTS: [&'static str; 33] = [ + "artificial", + "ambient", "borean", "brewing", "bristling", "compressed", + "chromatic", "concave", "convex", "distorted", @@ -15,9 +18,13 @@ const FIRSTS: [&'static str; 25] = [ "leafy", "lurking", "metallic", + "mossy", "mighty", + "modulated", "nocturnal", + "noisy", "nutritious", + "powerful", "obscure", "organic", "piscine", @@ -25,39 +32,47 @@ const FIRSTS: [&'static str; 25] = [ "recalcitrant", "rogue", "subterranean", + "synthetic", "sweet", "weary", ]; -const LASTS: [&'static str; 34] = [ +const LASTS: [&'static str; 41] = [ "artifact", "assembly", "console", "construct", "design", + "drone", + "energy", "entropy", "foilage", "forest", "form", + "fossil", "frequency", "function", "information", "insulator", - "kaffe", "layout", "lens", "mechanism", "mountain", "nectar", "oak", + "pattern", "plant", "poseidon", "problem", + "receiver", "replicant", "river", "river", + "scaffold", "shape", "signal", + "synthesiser", + "system", "tower", "transmitter", "traveller", diff --git a/server/src/skill.rs b/server/src/skill.rs index 3b9f180f..206a3f83 100644 --- a/server/src/skill.rs +++ b/server/src/skill.rs @@ -405,15 +405,15 @@ pub struct EventConstruct { } #[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] -pub enum LogStages { - AllStages, // 0 Anim Anim Anim - StartEnd, // 1 Anim Anim Skip - StartPost, // 2 Anim Skip Anim - StartOnly, // 3 Anim Skip Skip - EndPost, // 4 Skip Anim Anim - EndOnly, // 5 Skip Anim Skip - PostOnly, // 6 Skip Skip Anim - None, // 7 Skip Skip Skip +pub enum EventStages { + AllStages, // Anim Anim Anim + StartEnd, // Anim Anim Skip + StartPost, // Anim Skip Anim + StartOnly, // Anim Skip Skip + EndPost, // Skip Anim Anim + EndOnly, // Skip Anim Skip + PostOnly, // Skip Skip Anim + None, // Skip Skip Skip } #[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] @@ -421,7 +421,7 @@ pub struct Resolution { pub source: EventConstruct, pub target: EventConstruct, pub event: Event, - pub stages: u8, + pub stages: EventStages, } impl Resolution { @@ -440,7 +440,7 @@ impl Resolution { blue: target.blue_life(), }, event: Event::Incomplete, - stages: LogStages::AllStages as u8, + stages: EventStages::AllStages, } } @@ -449,8 +449,8 @@ impl Resolution { self } - fn stages(mut self, s: LogStages) -> Resolution { - self.stages = s as u8; + fn stages(mut self, s: EventStages) -> Resolution { + self.stages = s; self } } @@ -1268,7 +1268,7 @@ fn bash(source: &mut Construct, target: &mut Construct, mut results: Resolutions let amount = source.red_power().pct(skill.multiplier().pct(100 + 45u64.saturating_mul(cds))); target.deal_red_damage(skill, amount) .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(LogStages::PostOnly))); + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); } return results; @@ -1282,7 +1282,7 @@ fn sleep(source: &mut Construct, target: &mut Construct, mut results: Resolution let amount = source.green_power().pct(skill.multiplier()); target.deal_green_damage(skill, amount) .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(LogStages::PostOnly))); + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); return results; } @@ -1298,7 +1298,7 @@ fn intercept(source: &mut Construct, target: &mut Construct, mut results: Resolu results.push(Resolution::new(source, target).event(target.recharge(skill, red_amount, 0))); let intercept = skill.effect()[0]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, intercept)).stages(LogStages::PostOnly)); + results.push(Resolution::new(source, target).event(target.add_effect(skill, intercept)).stages(EventStages::PostOnly)); return results; } @@ -1306,7 +1306,7 @@ fn break_(source: &mut Construct, target: &mut Construct, mut results: Resolutio let stun = skill.effect()[0]; results.push(Resolution::new(source, target).event(target.add_effect(skill, stun))); let vuln = skill.effect()[1]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, vuln)).stages(LogStages::PostOnly)); + results.push(Resolution::new(source, target).event(target.add_effect(skill, vuln)).stages(EventStages::PostOnly)); return results; } @@ -1314,7 +1314,7 @@ fn break_(source: &mut Construct, target: &mut Construct, mut results: Resolutio fn block(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { results.push(Resolution::new(source, target) .event(target.add_effect(skill, skill.effect()[0])) - .stages(LogStages::StartEnd)); + .stages(EventStages::StartEnd)); return results; } @@ -1328,11 +1328,11 @@ fn counter(source: &mut Construct, target: &mut Construct, mut results: Resoluti let red_amount = source.red_power().pct(skill.multiplier()); results.push(Resolution::new(source, target) .event(target.recharge(skill, red_amount, 0)) - .stages(LogStages::StartEnd)); + .stages(EventStages::StartEnd)); results.push(Resolution::new(source, target) .event(target.add_effect(skill, skill.effect()[0])) - .stages(LogStages::PostOnly)); + .stages(EventStages::PostOnly)); return results; } @@ -1341,7 +1341,7 @@ fn riposte(source: &mut Construct, target: &mut Construct, mut results: Resoluti let amount = source.red_power().pct(skill.multiplier()); target.deal_red_damage(skill, amount) .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(LogStages::StartPost))); + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::StartPost))); return results; } @@ -1360,7 +1360,7 @@ fn restrict(source: &mut Construct, target: &mut Construct, mut results: Resolut let amount = source.red_power().pct(skill.multiplier()).pct(s_multi); target.deal_red_damage(skill, amount) .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(LogStages::PostOnly))); + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); return results; @@ -1376,7 +1376,7 @@ fn slay(source: &mut Construct, target: &mut Construct, mut results: Resolutions results.push(Resolution::new(source, target).event(e)); let heal = source.deal_green_damage(skill, amount); for h in heal { - results.push(Resolution::new(source, source).event(h).stages(LogStages::PostOnly)); + results.push(Resolution::new(source, source).event(h).stages(EventStages::PostOnly)); }; }, _ => results.push(Resolution::new(source, target).event(e)), @@ -1409,7 +1409,7 @@ fn triage_tick(source: &mut Construct, target: &mut Construct, mut results: Reso let amount = source.green_power().pct(skill.multiplier()); target.deal_green_damage(skill, amount) .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(LogStages::EndPost))); + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); return results; } @@ -1424,7 +1424,7 @@ fn chaos(source: &mut Construct, target: &mut Construct, mut results: Resolution let amount = source.red_power().pct(skill.multiplier()).pct(r_rng); target.deal_red_damage(skill, amount) .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(LogStages::PostOnly))); + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); return results; } @@ -1464,7 +1464,7 @@ fn decay(source: &mut Construct, target: &mut Construct, mut results: Resolution let decay = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); results.push(Resolution::new(source, target) .event(target.add_effect(skill, decay)) - .stages(LogStages::PostOnly)); + .stages(EventStages::PostOnly)); return decay_tick(source, target, results, tick_skill); } @@ -1473,7 +1473,7 @@ fn decay_tick(source: &mut Construct, target: &mut Construct, mut results: Resol let amount = source.blue_power().pct(skill.multiplier()); target.deal_blue_damage(skill, amount) .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(LogStages::EndPost))); + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); return results; } @@ -1495,7 +1495,7 @@ fn electrocute(source: &mut Construct, target: &mut Construct, mut results: Reso let electrocute = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); results.push(Resolution::new(source, target) .event(target.add_effect(skill, electrocute)) - .stages(LogStages::StartPost)); + .stages(EventStages::StartPost)); return electrocute_tick(source, target, results, tick_skill); } @@ -1503,14 +1503,14 @@ fn electrocute_tick(source: &mut Construct, target: &mut Construct, mut results: let amount = source.blue_power().pct(skill.multiplier()); target.deal_blue_damage(skill, amount) .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(LogStages::EndPost))); + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); return results; } fn ruin(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { results.push(Resolution::new(source, target) .event(target.add_effect(skill, skill.effect()[0])) - .stages(LogStages::PostOnly)); + .stages(EventStages::PostOnly)); return results;; } @@ -1529,7 +1529,7 @@ fn absorption(source: &mut Construct, target: &mut Construct, mut results: Resol let absorb = skill.effect()[0].set_meta(EffectMeta::AddedDamage(amount)); results.push(Resolution::new(source, target) .event(target.add_effect(reflect_skill, absorb)) - .stages(LogStages::PostOnly)); + .stages(EventStages::PostOnly)); return results;; } @@ -1554,7 +1554,7 @@ fn reflect(source: &mut Construct, target: &mut Construct, mut results: Resoluti let blue_amount = source.blue_power().pct(skill.multiplier()); results.push(Resolution::new(source, target) .event(target.recharge(skill, 0, blue_amount)) - .stages(LogStages::PostOnly)); + .stages(EventStages::PostOnly)); return results;; } @@ -1586,13 +1586,13 @@ fn siphon_tick(source: &mut Construct, target: &mut Construct, mut results: Reso for e in siphon_events { match e { Event::Damage { amount, mitigation: _, colour: _, skill: _ } => { - results.push(Resolution::new(source, target).event(e).stages(LogStages::EndPost)); + results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost)); let heal = source.deal_green_damage(skill, amount); for h in heal { - results.push(Resolution::new(source, source).event(h).stages(LogStages::PostOnly)); + results.push(Resolution::new(source, source).event(h).stages(EventStages::PostOnly)); }; }, - _ => results.push(Resolution::new(source, target).event(e).stages(LogStages::EndPost)), + _ => results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost)), } } @@ -1604,7 +1604,7 @@ fn link(source: &mut Construct, target: &mut Construct, mut results: Resolutions results.push(Resolution::new(source, target).event(target.recharge(skill, 0, blue_amount))); let link = skill.effect()[0].set_meta(EffectMeta::LinkTarget(target.id)); - results.push(Resolution::new(source, target).event(source.add_effect(skill, link)).stages(LogStages::PostOnly)); + results.push(Resolution::new(source, target).event(source.add_effect(skill, link)).stages(EventStages::PostOnly)); return results; } @@ -1625,7 +1625,7 @@ fn link_hit(source: &Construct, target: &Construct, mut results: Resolutions, ga results.push(Resolution::new(target, link_target).event(Event::Skill { skill: Skill::LinkI })); res.into_iter().for_each(|e| results.push(Resolution::new(&source, &link_target) - .event(e).stages(LogStages::EndPost))); + .event(e).stages(EventStages::EndPost))); } else { panic!("not a link target {:?}", link); } @@ -1649,7 +1649,7 @@ fn silence(source: &mut Construct, target: &mut Construct, mut results: Resoluti let amount = source.blue_power().pct(skill.multiplier()).pct(s_multi); target.deal_blue_damage(skill, amount) .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(LogStages::PostOnly))); + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); return results; } @@ -1700,10 +1700,10 @@ fn purify(source: &mut Construct, target: &mut Construct, mut results: Resolutio let ce = target.effects.remove(i); results.push(Resolution::new(source, target) .event(Event::Removal { effect: ce.effect, construct_effects: target.effects.clone() }) - .stages(LogStages::PostOnly)); + .stages(EventStages::PostOnly)); target.deal_green_damage(skill, amount) .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(LogStages::PostOnly))); + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); } return results;