diff --git a/client/package.json b/client/package.json index 5bb82290..127213a0 100755 --- a/client/package.json +++ b/client/package.json @@ -13,6 +13,7 @@ "license": "UNLICENSED", "dependencies": { "@orange-games/phaser-input": "^2.0.5", + "async": "^2.6.1", "borc": "^2.0.3", "docco": "^0.7.0", "izitoast": "^1.4.0", diff --git a/client/src/scenes/combat.cryps.js b/client/src/scenes/combat.cryps.js index 7e9a3dca..70a57ba3 100644 --- a/client/src/scenes/combat.cryps.js +++ b/client/src/scenes/combat.cryps.js @@ -169,14 +169,18 @@ class DrawCrypTeams extends Phaser.GameObjects.Group { constructor(scene, game) { super(scene); const account = scene.registry.get('account'); + const allyTeam = game.teams.find(t => t.id === account.id); + // in future there will be more than one + const [enemyTeam] = game.teams.filter(t => t.id !== account.id); + const renderTeam = (cryp, iter, team) => { const skillsObj = renderSkills(scene, this, cryp, account, game, team, iter); const crypObj = renderCryp(scene, this, cryp, skillsObj, game, team, iter); const addKeys = (game.phase === 'Skill' && !team) || (game.phase === 'Target' && team); if (addKeys) scene.crypKeyHandler(crypObj, iter); }; - scene.allyTeam.cryps.forEach((cryp, i) => renderTeam(cryp, i, 0)); - scene.enemyTeam.cryps.forEach((cryp, i) => renderTeam(cryp, i, 1)); + allyTeam.cryps.forEach((cryp, i) => renderTeam(cryp, i, 0)); + enemyTeam.cryps.forEach((cryp, i) => renderTeam(cryp, i, 1)); } } diff --git a/client/src/scenes/combat.js b/client/src/scenes/combat.js index 4bba9a87..e7965edb 100644 --- a/client/src/scenes/combat.js +++ b/client/src/scenes/combat.js @@ -3,8 +3,8 @@ const fs = require('fs'); const { POSITIONS: { COMBAT }, TEXT } = require('./constants'); const { DrawCrypTeams, CrypImage, CrypSkill } = require('./combat.cryps'); -const combatRender = require('./combat.render'); const CombatSkills = require('./combat.skills'); +const renderResolutions = require('./combat.render'); const CRYP_KEY_MAP = ['keydown_ONE', 'keydown_TWO', 'keydown_THREE']; const SKILL_KEY_MAP = ['keydown_Q', 'keydown_W', 'keydown_E', 'keydown_R']; @@ -45,9 +45,10 @@ class Combat extends Phaser.Scene { const logX = COMBAT.LOG.x(); const logY = COMBAT.LOG.y(); const logWidth = COMBAT.LOG.width(); - this.skills = new CombatSkills(this); - this.logIter = 0; this.resolvedIter = 0; - this.registry.set('resolve', false); + // this.skills = new CombatSkills(this); + this.renderedResolves = 0; + + this.registry.set('gameAnimating', false); this.account = this.registry.get('account'); this.log = this.add.text(logX, logY, '', TEXT.NORMAL); this.log.setWordWrapWidth(logWidth); @@ -56,44 +57,39 @@ class Combat extends Phaser.Scene { updateData(parent, key, data) { if (key === 'game') { - if (!this.registry.get('activeSkill') - && !this.registry.get('resolve')) { - this.renderLog(data); - } + this.redrawGame(data); } return true; } - renderLog(game) { + redrawGame(game) { if (!game) return false; - // Check game status first - while (game.log.length !== this.logIter) { - if (game.log[this.logIter] === '') { - this.registry.set('resolve', true); - this.logIter += 1; - combatRender(this, game, this.crypTeamRender); - return true; - } this.logIter += 1; + + const updatedNeeded = !this.registry.get('activeSkill') && !this.registry.get('gameAnimating'); + if (!updatedNeeded) return false; + + // do we need to render resolution animations? + if (game.resolved.length !== this.renderedResolves) { + const newResolutions = game.resolved.slice(this.renderedResolves); + renderResolutions(this, game, this.crypTeamRender, newResolutions); + this.renderedResolves = game.resolved.length; + return true; } - game.teams.forEach((t) => { - if (this.account.id === t.id) { - this.allyTeam = t; - } else { - this.enemyTeam = t; - } - }); - // If not animating render static skill / block phase - if (this.crypTeamRender) { - this.crypTeamRender.destroy(true); - } this.crypTeamRender = new DrawCrypTeams(this, game); + + // If not animating resolutions render static skill / block phase + if (this.crypTeamRender) this.crypTeamRender.destroy(true); + this.crypTeamRender = new DrawCrypTeams(this, game); + + // update log // shallow copy because reverse mutates this.log.setText(Array.from(game.log).slice(0, this.logIter).reverse()); + return true; } iterateLog(game) { this.logIter += 1; - this.resolvedIter += 1; + this.renderedResolves += 1; this.log.setText(Array.from(game.log).slice(0, this.logIter).reverse()); return this.resolvedIter === game.resolved.length; } diff --git a/client/src/scenes/combat.render.js b/client/src/scenes/combat.render.js index cbd6765d..ea0bd496 100644 --- a/client/src/scenes/combat.render.js +++ b/client/src/scenes/combat.render.js @@ -1,84 +1,105 @@ +const { eachSeries } = require('async'); + +const CombatSkills = require('./combat.skills'); const { CrypImage } = require('./combat.cryps'); -const { DELAYS, POSITIONS: { COMBAT } } = require('./constants'); +const { + DELAYS: { ANIMATION_DURATION, MOVE_CREEP, DAMAGE_TICK }, + POSITIONS: { COMBAT }, +} = require('./constants'); const randomSkill = () => { const skills = ['wall', 'spit', 'gravBlast', 'gravBomb', 'chargeBall']; return skills[Math.floor(Math.random() * 5)]; }; -function findResolveCryps(scene, group, game) { - const resolved = game.resolved[scene.resolvedIter]; - const allyCryp = scene.allyTeam.cryps.find( - c => c.id === resolved.source_cryp_id || c.id === resolved.target_cryp_id - ); - const enemyCryp = scene.enemyTeam.cryps.find( - c => c.id === resolved.source_cryp_id || c.id === resolved.target_cryp_id +function findResolutionCryps(scene, group, resolution, allyTeam, enemyTeam) { + const allyCryp = allyTeam.cryps.find( + c => c.id === resolution.source_cryp_id || c.id === resolution.target_cryp_id ); const allySpawn = group.children.entries .filter(obj => obj instanceof CrypImage) .find(c => c.cryp.id === allyCryp.id); + + const enemyCryp = enemyTeam.cryps.find( + c => c.id === resolution.source_cryp_id || c.id === resolution.target_cryp_id + ); const enemySpawn = group.children.entries .filter(obj => obj instanceof CrypImage) .find(c => c.cryp.id === enemyCryp.id); - const target = allyCryp.id === resolved.target_cryp_id ? allySpawn : enemySpawn; - return {allySpawn, enemySpawn, target }; + const target = allyCryp.id === resolution.target_cryp_id ? enemySpawn : allySpawn; + return { allySpawn, enemySpawn, target }; } -function animatePhase(scene, group, game, delay) { +function animatePhase(scene, group, game, resolution, cb) { + scene.skills = new CombatSkills(scene); + // Find cryps and targets const tweenParams = (targets, centreSpot, enemy) => { let x = centreSpot ? COMBAT.width() * 0.3 : targets.x; x = (enemy && centreSpot) ? x + COMBAT.width() * 0.4 : x; const y = centreSpot ? COMBAT.height() * 13.25 / 35 : targets.y; const ease = 'Power1'; - const duration = DELAYS.MOVE_CREEP; + const duration = MOVE_CREEP; return { targets, x, y, ease, duration }; }; - const { allySpawn, enemySpawn, target } = findResolveCryps(scene, group, game); + + // find the teams + const account = scene.registry.get('account'); + const allyTeam = game.teams.find(t => t.id === account.id); + // in future there will be more than one + const [enemyTeam] = game.teams.filter(t => t.id !== account.id); + const { allySpawn, enemySpawn, target } = findResolutionCryps(scene, group, resolution, allyTeam, enemyTeam); + const moveAllyBattle = tweenParams(allySpawn, true, false); const moveAllyOrig = tweenParams(allySpawn, false, false); const moveEnemyBattle = tweenParams(enemySpawn, true, true); const moveEnemyOrig = tweenParams(enemySpawn, false, true); + // Move cryps into posistion scene.tweens.add(moveAllyBattle); scene.tweens.add(moveEnemyBattle); - // Target cryp takes damage into posistion - scene.time.delayedCall(delay + DELAYS.MOVE_CREEP, () => { - target.takeDamage(100); - }); - // Move cryps back - scene.time.delayedCall(delay + DELAYS.MOVE_CREEP + DELAYS.DAMAGE_TICK, () => { - scene.tweens.add(moveAllyOrig); - scene.tweens.add(moveEnemyOrig); + + // animate skill + const skill = randomSkill(); + scene.time.delayedCall(MOVE_CREEP, () => { + const isAlly = resolution.target_team_id === account.id; + scene.skills[skill](isAlly); + + // Target cryp takes damage + scene.time.delayedCall(ANIMATION_DURATION, () => { + target.takeDamage(100); + + // Move cryps back + scene.time.delayedCall(DAMAGE_TICK, () => { + scene.tweens.add(moveAllyOrig); + scene.tweens.add(moveEnemyOrig); + + // all done + scene.time.delayedCall(MOVE_CREEP, () => { + scene.skills.cleanup(); + scene.skills.destroy(); + return cb(); + }); + }); + }); }); } +function renderResolutions(scene, game, group, resolutions) { + scene.registry.set('gameAnimating', true); -function combatRender(scene, game, group) { - const skill = randomSkill(); - const resolved = game.resolved[scene.resolvedIter]; - const account = scene.registry.get('account'); - const target = resolved.source_team_id === account.id; - const delay = DELAYS[skill]; - scene.time.delayedCall(DELAYS.MOVE_CREEP, () => { - scene.skills[skill](target); - }); - animatePhase(scene, group, game, delay[0]); + eachSeries( + resolutions, + (resolution, cb) => animatePhase(scene, group, game, resolution, cb), + (err) => { + if (err) return console.error(err); + scene.registry.set('gameAnimating', false); + return true; + } + ); - if (scene.iterateLog(game)) { - scene.time.delayedCall(delay[1] + DELAYS.MOVE_CREEP * 2, () => { - scene.registry.set('resolve', false); - scene.skills.cleanup(); - scene.renderLog(game); - }); - } else { - scene.time.delayedCall(delay[1] + DELAYS.MOVE_CREEP * 2, () => { - scene.skills.cleanup(); - combatRender(scene, game, group); - }); - } return true; } -module.exports = combatRender; +module.exports = renderResolutions; diff --git a/client/src/scenes/combat.skills.js b/client/src/scenes/combat.skills.js index c6893110..477d2e8c 100644 --- a/client/src/scenes/combat.skills.js +++ b/client/src/scenes/combat.skills.js @@ -1,17 +1,17 @@ const Phaser = require('phaser'); -const { POSITIONS: { COMBAT } } = require('./constants'); +const { POSITIONS: { COMBAT }, DELAYS } = require('./constants'); const randomColour = () => { const colours = ['green', 'blue', 'red', 'white', 'yellow']; return colours[Math.floor(Math.random() * 5)]; }; -const animationParams = (target) => { - const spawnLocation = target ? COMBAT.width() * 0.35 : COMBAT.width() * 0.65; - const speed = target ? 250 : -250; +const animationParams = (isAlly) => { + const spawnLocation = isAlly ? COMBAT.width() * 0.35 : COMBAT.width() * 0.65; + const speed = isAlly ? 250 : -250; const img = randomColour(); - const angleMin = target ? 320 : 180; - const angleMax = target ? 360 : 220; + const angleMin = isAlly ? 320 : 180; + const angleMax = isAlly ? 360 : 220; return { spawnLocation, speed, img, angleMin, angleMax }; }; @@ -21,44 +21,48 @@ class CombatSkills extends Phaser.GameObjects.Group { this.scene = scene; } - wall(target) { - const { spawnLocation, speed, img } = animationParams(target); + wall(isAlly) { + const lifespan = DELAYS.EFFECT_DURATION; + const { spawnLocation, speed, img } = animationParams(isAlly); const particles = this.scene.add.particles(img); const emitter = particles.createEmitter({ x: spawnLocation, y: { min: COMBAT.height() * 0.2, max: COMBAT.height() * 0.5 }, - lifespan: 2000, speedX: { min: speed, max: speed * 2 }, scale: { start: 0.4, end: 0 }, quantity: 4, blendMode: 'ADD', + lifespan, }); this.add(particles); - this.scene.time.delayedCall(1000, () => { emitter.stop(); }, [], this.scene); + this.scene.time.delayedCall(lifespan, () => { emitter.stop(); }, [], this.scene); } - spit(target) { - const { spawnLocation, speed, img, angleMin, angleMax } = animationParams(target); + spit(isAlly) { + const lifespan = DELAYS.EFFECT_DURATION; + const { spawnLocation, speed, img, angleMin, angleMax } = animationParams(isAlly); const particles = this.scene.add.particles(img); const emitter = particles.createEmitter({ x: spawnLocation, y: COMBAT.height() * 0.35, - lifespan: 2000, angle: { min: angleMin, max: angleMax }, speed: speed * 2, scale: { start: 0.4, end: 1 }, gravityY: 250, quantity: 4, blendMode: 'ADD', + lifespan, }); this.add(particles); - this.scene.time.delayedCall(1000, () => { emitter.stop(); }, [], this); + this.scene.time.delayedCall(lifespan, () => { emitter.stop(); }, [], this); } - gravBomb(target) { - const { spawnLocation, img } = animationParams(!target); + gravBomb(isAlly) { + const lifespan = DELAYS.EFFECT_DURATION; + + const { spawnLocation, img } = animationParams(isAlly); const particles = this.scene.add.particles(img); - const location = target ? COMBAT.width() * 0.65 : COMBAT.width() * 0.35; + const location = isAlly ? COMBAT.width() * 0.35 : COMBAT.width() * 0.65; const well = particles.createGravityWell({ x: location, y: COMBAT.height() * 0.25, @@ -68,21 +72,24 @@ class CombatSkills extends Phaser.GameObjects.Group { this.emitter = particles.createEmitter({ x: spawnLocation, y: COMBAT.height() * 0.25, - lifespan: 1500, speed: 1000, scale: { start: 0.7, end: 1 }, blendMode: 'ADD', + lifespan, }); this.add(particles); - this.scene.time.delayedCall(1000, () => { this.emitter.stop(); well.active = false; }, [], this.scene); + this.scene.time.delayedCall(lifespan, () => { this.emitter.stop(); well.active = false; }, [], this.scene); } - gravBlast(target) { + gravBlast(isAlly) { + const lifespan = DELAYS.EFFECT_DURATION; + const WELL_END = lifespan / 2; + const img = randomColour(); - const spawnLocation = target ? COMBAT.width() * 0.35 : COMBAT.width() * 0.65; - const targetLocation = target ? COMBAT.width() * 0.7 : COMBAT.width() * 0.3; + const spawnLocation = isAlly ? COMBAT.width() * 0.35 : COMBAT.width() * 0.65; + const isEnemyLocation = isAlly ? COMBAT.width() * 0.7 : COMBAT.width() * 0.3; const particles = this.scene.add.particles(img); - const bounds = target + const bounds = isAlly ? { x: COMBAT.width() * 0.3, y: COMBAT.height() * 0.2, w: COMBAT.width() * 0.5, h: COMBAT.height() * 0.2 } : { x: 0.2 * COMBAT.width(), y: COMBAT.height() * 0.2, w: COMBAT.width() * 0.5, h: COMBAT.height() * 0.2 }; const well = particles.createGravityWell({ @@ -94,25 +101,27 @@ class CombatSkills extends Phaser.GameObjects.Group { const emitter = particles.createEmitter({ x: spawnLocation, y: COMBAT.height() * 0.35, - lifespan: 2000, speed: 1000, scale: { start: 0.7, end: 1 }, blendMode: 'ADD', bounds, + lifespan, }); this.add(particles); - this.scene.time.delayedCall(1000, () => { emitter.stop(); well.x = targetLocation; }, [], this.scene); - this.scene.time.delayedCall(3000, () => { well.active = false; }, [], this.scene); + this.scene.time.delayedCall(WELL_END, () => { emitter.stop(); well.x = isEnemyLocation; }, [], this.scene); + this.scene.time.delayedCall(lifespan, () => { well.active = false; }, [], this.scene); } - chargeBall(target) { - const { img, spawnLocation } = animationParams(target); - const targetLocation = target ? 0.7 * COMBAT.width() : 0.25 * COMBAT.width(); + chargeBall(isAlly) { + const lifespan = DELAYS.EFFECT_DURATION; + const CHARGE_LIFESPAN = lifespan / 2; + + const { img, spawnLocation } = animationParams(isAlly); + const targetLocation = isAlly ? 0.7 * COMBAT.width() : 0.25 * COMBAT.width(); const particles = this.scene.add.particles(img); const emitter = particles.createEmitter({ x: 0, y: 0, - lifespan: 1000, moveToX: spawnLocation, moveToY: COMBAT.height() * 0.1, scale: 0.75, @@ -120,22 +129,23 @@ class CombatSkills extends Phaser.GameObjects.Group { _frequency: 20, blendMode: 'ADD', emitZone: { source: new Phaser.Geom.Rectangle(0, 0, COMBAT.width(), COMBAT.height()) }, + lifespan: CHARGE_LIFESPAN, }); const emitter2 = particles.createEmitter({ radial: false, x: { min: spawnLocation, max: targetLocation, steps: 256 }, y: { min: COMBAT.height() * 0.1, max: COMBAT.height() * 0.4, steps: 256 }, - lifespan: 1000, quantity: 4, gravityY: 0, scale: { start: 1.5, end: 0, ease: 'Power3' }, blendMode: 'ADD', active: false, + lifespan, }); this.add(particles); - this.scene.time.delayedCall(1000, () => { emitter.stop(); }, [], this.scene); - this.scene.time.delayedCall(2000, () => { emitter2.active = true; }, [], this.scene); - this.scene.time.delayedCall(3000, () => { emitter2.stop(); }, [], this.scene); + this.scene.time.delayedCall(CHARGE_LIFESPAN, () => { emitter.stop(); }, [], this.scene); + this.scene.time.delayedCall(CHARGE_LIFESPAN, () => { emitter2.active = true; }, [], this.scene); + this.scene.time.delayedCall(lifespan, () => { emitter2.stop(); }, [], this.scene); } cleanup() { diff --git a/client/src/scenes/constants.js b/client/src/scenes/constants.js index 374a0761..aabd6c39 100644 --- a/client/src/scenes/constants.js +++ b/client/src/scenes/constants.js @@ -104,13 +104,14 @@ module.exports = { }, DELAYS: { - MOVE_CREEP: 1000, - DAMAGE_TICK: 750, - wall: [1500, 3000], - spit: [1500, 3000], - gravBomb: [1500, 3000], - gravBlast: [1500, 3000], - chargeBall: [3000, 4000], + MOVE_CREEP: 500, + DAMAGE_TICK: 500, + ANIMATION_DURATION: 1000, + // wall: [500], + // spit: [300, 500], + // gravBomb: [300, 500], + // gravBlast: [300, 500], + // chargeBall: [300, 500], }, SKILLS: { diff --git a/client/src/socket.js b/client/src/socket.js index e9379cf6..de253799 100644 --- a/client/src/socket.js +++ b/client/src/socket.js @@ -29,7 +29,7 @@ function createSocket(events) { }); events.loginPrompt(); - // send({ method: 'account_login', params: { name: 'ntr', password: 'grepgrepgrep' } }); + send({ method: 'account_login', params: { name: 'ntr', password: 'grepgrepgrep' } }); }); // Listen for messages