diff --git a/client/cryps.css b/client/cryps.css index 69982099..cd190bad 100644 --- a/client/cryps.css +++ b/client/cryps.css @@ -589,10 +589,12 @@ header { .cryp-box.ko { animation: none; opacity: 0.5; + /*opacity: 0.35;*/ filter: grayscale(100%); } .cryp-box.unfocus { + /*opacity: 0.65;*/ filter: blur(5px); } @@ -648,6 +650,13 @@ text.combat-text { /* CRYP DAMAGE */ + +.cryp-box.active-skill { + filter: drop-shadow(0 0 0.2em silver); + border-width: 5px; + border-color: silver; +} + .cryp-box.red-damage { filter: drop-shadow(0 0 0.2em red); border-width: 5px; @@ -708,6 +717,27 @@ CRYP DAMAGE border-top: 3px solid green; } +.cryp-box.purple-damage { + filter: drop-shadow(0 0 0.2em purple); + border-width: 5px; + color: purple; + border-color: purple; +} + +.purple-damage button { + border: 3px solid purple; + color: purple; +} + +.purple-damage text { + fill: purple; +} + +.purple-damage .stats { + border-top: 3px solid purple; +} + + /* MOBILE */ diff --git a/client/src/components/game.cryp.jsx b/client/src/components/game.cryp.jsx index 2bcf6b0b..9d04f6c7 100644 --- a/client/src/components/game.cryp.jsx +++ b/client/src/components/game.cryp.jsx @@ -59,7 +59,6 @@ function GameCryp(props) { } = props; const ko = cryp.green_life.value === 0 ? 'ko' : ''; - const classes = eventClasses(resolution, cryp); const skills = range(0, 3) diff --git a/client/src/constants.jsx b/client/src/constants.jsx index 9582d8b6..882d8ce8 100644 --- a/client/src/constants.jsx +++ b/client/src/constants.jsx @@ -1,6 +1,9 @@ module.exports = { TIMES: { RESOLUTION_TIME_MS: 2000, + START_SKILL: 1000, + END_SKILL: 1000, + POST_SKILL: 1000, }, ITEMS: { diff --git a/client/src/events.jsx b/client/src/events.jsx index 14311ece..4aafa033 100644 --- a/client/src/events.jsx +++ b/client/src/events.jsx @@ -3,6 +3,7 @@ const eachSeries = require('async/eachSeries'); const actions = require('./actions'); const { TIMES } = require('./constants'); +const { getCombatSequence } = require('./utils'); function registerEvents(store) { function setCryps(cryps) { @@ -26,8 +27,18 @@ function registerEvents(store) { const newRes = game.resolved.slice(currentGame.resolved.length); return eachSeries(newRes, (r, cb) => { if (['Disable', 'TargetKo'].includes(r.event[0])) return cb(); - store.dispatch(actions.setResolution(r)); - return setTimeout(cb, TIMES.RESOLUTION_TIME_MS); + // Create sub events for combat animations + const sequence = getCombatSequence(r.event); + return eachSeries(sequence, (stage, sCb) => { + const stagedR = Object.create(r); + stagedR.stage = stage; + store.dispatch(actions.setResolution(stagedR)); + return setTimeout(sCb, TIMES[stage]); + }, err => { + if (err) return console.error(err); + // Finished this resolution + return cb(); + }); }, err => { if (err) return console.error(err); store.dispatch(actions.setResolution(null)); diff --git a/client/src/utils.jsx b/client/src/utils.jsx index 00a24307..b6bb2928 100644 --- a/client/src/utils.jsx +++ b/client/src/utils.jsx @@ -32,7 +32,7 @@ const numSort = (k, desc) => { }; }; -const genAvatar = (name) => { +const genAvatar = name => { let hash = 0; if (name.length === 0) return hash; // Probs don't need to hash using the whole string @@ -99,17 +99,22 @@ const COLOUR_ICONS = { function eventClasses(resolution, cryp) { if (!resolution) return ''; + const startSkill = resolution.stage === 'START_SKILL'; + const endSkill = resolution.stage === 'END_SKILL'; + const postSkill = resolution.stage === 'POST_SKILL'; + const source = cryp.id === resolution.source.id; + const target = cryp.id === resolution.target.id; // not involved at all. blur them - if (cryp.id !== resolution.source.id && cryp.id !== resolution.target.id) return 'unfocus'; + if (!(source || target)) return 'unfocus'; // not the target. just ignore for now - if (cryp.id !== resolution.target.id) return ''; + // if (cryp.id !== resolution.target.id) return ''; const [type, event] = resolution.event; if (type === 'Ko') { - return 'ko'; + if (target) return 'ko'; } if (type === 'Disable') { @@ -126,14 +131,34 @@ function eventClasses(resolution, cryp) { if (type === 'Damage') { const { skill, amount, mitigation, colour } = event; + // Highlight the flow of damage from source -> target + if (source && startSkill) return 'active-skill'; + if (target && endSkill) return 'active-skill'; + + // Deal damage to cryp and return effect + if (target && postSkill) { + cryp.green_life.value -= amount; + if (colour === 'RedDamage') { + cryp.red_life.value -= mitigation; + return 'red-damage'; + } + if (colour === 'BlueDamage') { + cryp.blue_life.value -= mitigation; + return 'blue-damage'; + } + if (colour === 'GreenDamage') return 'green-damage'; + } - if (colour === 'RedDamage') return 'red-damage'; - if (colour === 'BlueDamage') return 'blue-damage'; - if (colour === 'GreenDamage') return 'green-damage'; } if (type === 'Healing') { const { skill, amount, overhealing } = event; + if (source && startSkill) return 'active-skill'; + if (target && endSkill) return 'active-skill'; + if (target && postSkill) { + cryp.green_life.value += amount; + return 'green-damage'; + } } if (type === 'Inversion') { @@ -146,6 +171,8 @@ function eventClasses(resolution, cryp) { if (type === 'Effect') { const { skill, effect, duration } = event; + if (source && startSkill) return 'active-skill'; + if (target && endSkill) return 'active-skill'; } if (type === 'Removal') { @@ -154,6 +181,15 @@ function eventClasses(resolution, cryp) { if (type === 'Recharge') { const { skill, red, blue } = event; + if (source && startSkill) return 'active-skill'; + if (target && endSkill) return 'active-skill'; + if (target && postSkill) { + cryp.red_life.value += red; + cryp.blue_life.value += blue; + if (red > 0 && blue > 0) return 'purple-damage'; + if (red > 0) return 'red-damage'; + if (blue > 0) return 'blue-damage'; + } } if (type === 'Evasion') { @@ -163,27 +199,48 @@ function eventClasses(resolution, cryp) { return ''; } +function getCombatSequence(event) { + if (!event) return false; + // Skip combat animations depending on event type, expandable in future + const dotTicks = ['DecayTick', 'CorruptionTick', 'TriageTick', 'SiphonTick', 'StrangleTick']; + if (['Ko'].includes(event[0]) + || (event[1].skill === 'Throw' && event[1].effect === 'Vulnerable')) return ['POST_SKILL']; + if (dotTicks.includes(event[1].skill)) return ['END_SKILL', 'POST_SKILL']; + return ['START_SKILL', 'END_SKILL', 'POST_SKILL']; +} + function getCombatText(cryp, resolution) { if (!resolution) return ''; - if (cryp.id !== resolution.target.id) return ''; const [type, event] = resolution.event; + const source = cryp.id === resolution.source.id; + const target = cryp.id === resolution.target.id; + const startSkill = resolution.stage === 'START_SKILL'; + const endSkill = resolution.stage === 'END_SKILL'; + const postSkill = resolution.stage === 'POST_SKILL'; if (type === 'Ko') { - return 'KO!'; + if (postSkill && target) return 'KO!'; } if (type === 'Disable') { const { skill, disable } = event; + if (startSkill && source) return `${skill}`; + if (endSkill && target) return `${skill}`; + if (postSkill && target) return `${disable}`; } if (type === 'Immunity') { const { skill, immunity } = event; - return 'IMMUNE'; + if (startSkill && source) return `${skill}`; + if (endSkill && target) return `${skill}`; + if (postSkill && target) return 'IMMUNE'; } if (type === 'TargetKo') { const { skill } = event; + if (startSkill && source) return `${skill}`; + if (endSkill && target) return `${skill}`; } if (type === 'Damage') { @@ -191,32 +248,50 @@ function getCombatText(cryp, resolution) { const mitigationText = mitigation ? `(${mitigation})` : ''; - return `${amount} ${mitigationText}`; + if (startSkill && source) return `${skill}`; + if (endSkill && target) return `${skill}`; + if (postSkill && target) return `${amount} ${mitigationText}`; } if (type === 'Healing') { const { skill, amount, overhealing } = event; - return `${amount} (${overhealing}OH)`; + if (startSkill && source) return `${skill}`; + if (endSkill && target) return `${skill}`; + if (postSkill && target) return `${amount} (${overhealing} OH)`; } if (type === 'Inversion') { const { skill } = event; - return 'INVERT'; + if (startSkill && source) return `${skill}`; + if (endSkill && target) return `${skill}`; + if (postSkill && target) return 'INVERT'; } if (type === 'Reflection') { const { skill } = event; - return 'REFLECT'; + if (startSkill && source) return `${skill}`; + if (endSkill && target) return `${skill}`; + if (postSkill && target) return 'REFLECT'; } if (type === 'Effect') { const { skill, effect, duration } = event; - return `+ ${effect} ${duration}T`; + if (startSkill && source) return `${skill}`; + if (endSkill && target) return `${skill}`; + if (postSkill && target) return `+ ${effect} ${duration}T`; } + if (type === 'Recharge') { + const { skill, red, blue } = event; + if (startSkill && source) return `${skill}`; + if (endSkill && target) return `${skill}`; + if (postSkill && target) return `+${red}R ${blue}B`; + } + + if (type === 'Removal') { const { effect } = event; - return `- ${effect}`; + if (postSkill && target) return `- ${effect}`; } @@ -229,6 +304,7 @@ module.exports = { genAvatar, requestAvatar, eventClasses, + getCombatSequence, getCombatText, NULL_UUID, STATS,