From 1d2672f6e47318cfece79150b889657d4194af29 Mon Sep 17 00:00:00 2001 From: krypek Date: Thu, 19 Oct 2023 21:40:51 +0200 Subject: [PATCH] Fix multi step timings --- package-lock.json | 2 +- src/plugin.ts | 21 +++-- src/puzzle.ts | 197 +++++++++++++++++++++++++++---------------- src/sound-manager.ts | 45 +++++++--- 4 files changed, 171 insertions(+), 94 deletions(-) diff --git a/package-lock.json b/package-lock.json index 65e3a63..bec71f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -835,7 +835,7 @@ }, "node_modules/cc-blitzkrieg": { "version": "0.4.0", - "resolved": "git+ssh://git@github.com/krypciak/cc-blitzkrieg.git#229c269946da2f7f7a348729b86b4b27f338d32a", + "resolved": "git+ssh://git@github.com/krypciak/cc-blitzkrieg.git#dc8fb56cc5882d59acdc0e8b0fb2e9a5aff8ccd7", "dev": true, "license": "GPLv3" }, diff --git a/src/plugin.ts b/src/plugin.ts index 68cd6f7..2aef518 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -6,15 +6,12 @@ import { PuzzleBeeper } from './puzzle' import { LoudWalls } from './loudwalls' import { SoundManager } from './sound-manager' -function addVimBindings() { - if (window.vim) { /* optional dependency https://github.com/krypciak/cc-vim */ - } -} - export default class CrossedEyes { dir: string mod: Mod1 + puzzleBeeper!: PuzzleBeeper + constructor(mod: Mod1) { this.dir = mod.baseDirectory this.mod = mod @@ -23,15 +20,25 @@ export default class CrossedEyes { } async prestart() { - addVimBindings() + this.addVimAliases() MenuOptions.initPrestart() SoundManager.preloadSounds() new SpacialAudio().initSpacialAudio() new LoudWalls().initLoudWalls() - new PuzzleBeeper().initPrestart() + + this.puzzleBeeper = new PuzzleBeeper() + this.puzzleBeeper.initPrestart() } async poststart() { MenuOptions.initPoststart() } + + addVimAliases() { + if (window.vim) { /* optional dependency https://github.com/krypciak/cc-vim */ + vim.addAlias('crossedeyes', 'reset-puzzle', 'Reset puzzle step index', (ingame: boolean) => ingame && blitzkrieg.currSel.name == 'puzzle', () => { + this.puzzleBeeper.stepI = 0 + }) + } + } } diff --git a/src/puzzle.ts b/src/puzzle.ts index 6e948e0..dd7cefa 100644 --- a/src/puzzle.ts +++ b/src/puzzle.ts @@ -1,16 +1,23 @@ import type * as _ from 'cc-blitzkrieg' import { MenuOptions } from './options' import { mapNumber } from './spacial-audio' -import { PuzzleSelection, PuzzleSelectionStep } from 'cc-blitzkrieg/types/puzzle-selection' +import type { PuzzleSelection, PuzzleSelectionStep } from 'cc-blitzkrieg/types/puzzle-selection' import { SoundManager } from './sound-manager' +function isAiming(): boolean { + return ig.input.state('aim') || ig.gamepad.isRightStickDown() +} + class AimHandler { constructor(public pb: PuzzleBeeper) {} + puzzleStartTime!: number + farAwayFreq: number = 1300 maxFreq: number = 0 lastBeepTime: number = 0 + shootSoundPlayed: boolean = false minDegDistToSpeedup: number = 110 lockInDegDist: number = 10 @@ -30,58 +37,84 @@ class AimHandler { } } }) - ig.ENTITY.Player.inject({ - update() { - this.parent() - if (self.lockedIn && sc.control.thrown()) { - self.lockedIn = false - self.pb.stepI++ - } - } - }) } handleAim(step: PuzzleSelectionStep) { - if (!ig.game || !ig.game.playerEntity || !this.pb.moveToHandler.lockedIn || !MenuOptions.puzzleEnabled) { return } + if (!this.pb.moveToHandler.lockedIn) { this.lockedIn = false; return } + if (!ig.game || !ig.game.playerEntity || !MenuOptions.puzzleEnabled) { return } + + const nowt: number = sc.stats.getMap('player', 'playtime') * 1000 + + if (this.lockedIn) { + if (sc.control.thrown()) { + this.lockedIn = false + this.pb.moveToHandler.lockedIn = false + this.pb.stepI++ + if (this.pb.stepI == 1) { + this.puzzleStartTime = nowt + } + return + } + const wait = 200 + if (! this.shootSoundPlayed) { + const lastStep = this.pb.currentSel.data.recordLog!.steps[this.pb.stepI - 1] + let play: boolean = false + if (lastStep && !lastStep.split) { + const realDiffTime = Math.round((nowt - this.puzzleStartTime) * sc.options.get('assist-puzzle-speed')) + const diff = realDiffTime - step.endFrame + // console.log(diff, step.endFrame, realDiffTime) + if (diff > -wait) { + play = true + } + } else { play = true } + + if (!this.shootSoundPlayed && play) { + this.shootSoundPlayed = true + + SoundManager.appendQueue([ + { wait, name: 'hitCounterEcho', speed: 1.1, condition: () => this.lockedIn }, + ]) + } + } + } else { + this.shootSoundPlayed = false + } + if (! step || ! step.pos) { return } - const targetDeg = step.shootAngle + const targetDeg = (step.shootAngle! + 360) % 360 if (! targetDeg) { return } const r: number = 15 const theta: number = targetDeg * (Math.PI / 180) - this.newAim = Vec2.createC( - r * Math.cos(theta), - r * Math.sin(theta), - ) + this.newAim = Vec2.createC(r * Math.cos(theta), r * Math.sin(theta)) - - const aiming: boolean = ig.input.state('aim') || ig.gamepad.isRightStickDown() - if (! aiming) { + if (! isAiming()) { this.lockedIn = false return } if (!ig.game || !ig.game.playerEntity || !MenuOptions.puzzleEnabled) { return } - const deg = ig.game.playerEntity.aimDegrees /* set by cc-blitzkrieg */ + const deg = (ig.game.playerEntity.aimDegrees + 360) % 360 /* set by cc-blitzkrieg */ if (! deg) { return } - const dist: number = /* distance between target and current aim */ - Math.min(Math.abs(deg - targetDeg), - Math.abs(360 - deg - targetDeg)) + const dist: number = Math.min( + Math.abs(deg - targetDeg), + 360 - Math.abs(deg - targetDeg) + ) /* distance between target and current aim */ if (dist <= this.lockInDegDist) { if (! this.lockedIn) { this.lockedIn = true this.normalBeepSlience = true - SoundManager.playSoundQueue([ - { name: 'countdown1', relativePos: true, pos: this.newAim, wait: 200 }, - { name: 'countdown2', relativePos: true, pos: this.newAim, wait: 100 }, - { name: SoundManager.getElementName(step.element), speed: 1.2, - condition: () => step.element !== sc.model.player.currentElementMode, + SoundManager.appendQueue([ + { name: 'countdown1', relativePos: true, pos: this.newAim }, + { wait: 200, name: 'countdown2', relativePos: true, pos: this.newAim }, + { wait: 100, name: SoundManager.getElementName(step.element), speed: 1.2, + condition: () => isAiming() && step.element !== sc.model.player.currentElementMode, action: () => { this.normalBeepSlience = false - this.lastBeepTime = Date.now() + this.lastBeepTime = ig.game.now }, }, ]) @@ -89,20 +122,21 @@ class AimHandler { } else if (this.lockedIn) { this.lockedIn = false this.normalBeepSlience = true - SoundManager.playSoundQueue([ - { name: 'countdown2', relativePos: true, pos: this.newAim, wait: 200 }, - { name: 'countdown2', relativePos: true, pos: this.newAim, + + SoundManager.clearQueue() + SoundManager.appendQueue([ + { name: 'countdown2', relativePos: true, pos: this.newAim }, + { wait: 200, name: 'countdown1', relativePos: true, pos: this.newAim, action: () => { this.normalBeepSlience = false - this.lastBeepTime = Date.now() + this.lastBeepTime = ig.game.now }}, ]) return } if (this.lockedIn || this.normalBeepSlience) { return } - const now: number = Date.now() - const timeDiff = now - this.lastBeepTime + const timeDiff = nowt - this.lastBeepTime const time: number = dist >= this.minDegDistToSpeedup ? this.farAwayFreq : Math.max( mapNumber(deg, targetDeg - this.minDegDistToSpeedup, targetDeg, this.farAwayFreq, this.maxFreq), @@ -111,7 +145,7 @@ class AimHandler { if (timeDiff >= time) { const speed: number = 1 SoundManager.playSoundAtRelative('computerBeep', speed, this.newAim) - this.lastBeepTime = now + this.lastBeepTime = nowt } } } @@ -123,13 +157,15 @@ class MoveToHandler { maxFreq: number = 0 lastBeepTime: number = 0 + normalBeepSlience: boolean = false minDistToSpeedup: number = 8 * 16 - lockInDist: number = 2 * 16 + lockinDist: number = 2 * 16 + relockDist: number = 0.5 * 16 lockedIn: boolean = false - normalBeepSlience: boolean = false - freezePlayer: boolean = false + softLockout: boolean = false + allowRelock: boolean = false handleMoveTo(step: PuzzleSelectionStep) { const pos: Vec3 & { level: number } = step.pos @@ -138,50 +174,66 @@ class MoveToHandler { const dist: number = Vec3.distance(pos, playerPos) const self = this - if (dist <= this.lockInDist) { - if (! this.lockedIn) { - this.lockedIn = true - ig.game.playerEntity.setPos(pos.x, pos.y, pos.z) - this.normalBeepSlience = true - SoundManager.playSound('countdown1', 1.2, pos) - ig.game.playerEntity.coll.vel = Vec3.create() - sc.model.player.setCore(sc.PLAYER_CORE.MOVE, false) - - - SoundManager.playSoundQueue([ - { name: 'countdown1', pos, speed: 1.2, wait: 150 }, - { name: 'countdown2', pos, speed: 1.2, - action() { - self.normalBeepSlience = false - self.lastBeepTime = Date.now() - },}, - ]) - setTimeout(() => sc.model.player.setCore(sc.PLAYER_CORE.MOVE, true), 1000) + let lockout: boolean = false + if (dist <= this.lockinDist && pos.z == playerPos.z) { + Vec3.add(playerPos, ig.game.playerEntity.coll.vel) + const distWithVel: number = Vec3.distance(pos, playerPos) + if (! this.softLockout || distWithVel <= this.relockDist) { + if (! this.lockedIn) { + this.lockedIn = true + this.softLockout = false + + ig.game.playerEntity.setPos(pos.x, pos.y, pos.z) + ig.game.playerEntity.coll.vel = Vec3.create() + sc.model.player.setCore(sc.PLAYER_CORE.MOVE, false) + setTimeout(() => sc.model.player.setCore(sc.PLAYER_CORE.MOVE, true), 700) + + this.normalBeepSlience = true + SoundManager.appendQueue([ + { name: 'countdown1', pos, speed: 1.2, }, + { wait: 150, name: 'countdown2', pos, speed: 1.2, + action() { + self.normalBeepSlience = false + self.lastBeepTime = ig.game.now + },}, + ]) + return + } else if (dist !== 0) { + lockout = true + this.softLockout = true + } } - } else if (this.lockedIn) { + } else { + if (this.lockedIn) { + lockout = true + } + this.softLockout = false + } + if (lockout) { this.lockedIn = false this.normalBeepSlience = true - SoundManager.playSoundQueue([ - { name: 'countdown2', pos, speed: 1.2, wait: 150 }, - { name: 'countdown1', pos, speed: 1.2, action: () => { + SoundManager.appendQueue([ + { name: 'countdown2', pos, speed: 1.2 }, + { wait: 150, name: 'countdown1', pos, speed: 1.2, action: () => { this.normalBeepSlience = false - this.lastBeepTime = Date.now() + this.lastBeepTime = ig.game.now }}, ]) return } - if (this.lockedIn || this.normalBeepSlience) { return } + if (this.lockedIn) { return } + if (this.normalBeepSlience) { return } - const now: number = Date.now() - const timeDiff = now - this.lastBeepTime + const nowt: number = ig.game.now + const timeDiff = nowt - this.lastBeepTime const time: number = dist >= this.minDistToSpeedup ? this.farAwayFreq : mapNumber(dist, this.minDistToSpeedup, 0, this.farAwayFreq, this.maxFreq) if (timeDiff >= time) { const speed: number = 1 SoundManager.playSound('trainCudeHide', speed, pos) - this.lastBeepTime = now + this.lastBeepTime = nowt } } } @@ -213,17 +265,18 @@ export class PuzzleBeeper { self.currentSel = sel self.stepI = 0 if (self.currentSel.data.completionType === blitzkrieg.PuzzleCompletionType.Normal) { - self.cachedSolution = blitzkrieg.PuzzleSelectionManager.getPuzzleSolveCondition(sel) as keyof ig.KnownVars + self.cachedSolution = blitzkrieg.PuzzleSelectionManager.getPuzzleSolveCondition(sel).substring(1) as keyof ig.KnownVars } } switch (self.currentSel.data.completionType) { case blitzkrieg.PuzzleCompletionType.Normal: { if (ig.vars.get(self.cachedSolution!)) { if (self.stepI > 0) { - SoundManager.playSoundQueue([ - { name: 'botSuccess', wait: 150 }, - { name: 'counter', wait: 20 }, - { name: 'botSuccess' }, + SoundManager.clearQueue() + SoundManager.appendQueue([ + { name: 'botSuccess' }, + { wait: 20, name: 'counter' }, + { wait: 150, name: 'botSuccess' }, ]) self.stepI = 0 } diff --git a/src/sound-manager.ts b/src/sound-manager.ts index ad75db8..7801f9c 100644 --- a/src/sound-manager.ts +++ b/src/sound-manager.ts @@ -16,6 +16,8 @@ export type SoundQueueEntry = { }) export class SoundManager { + private static soundQueue: SoundQueueEntry[] = [] + static sounds = { countdown1: 'media/sound/misc/countdown-1.ogg', countdown2: 'media/sound/misc/countdown-2.ogg', @@ -29,6 +31,7 @@ export class SoundManager { heatMode: 'media/sound/move/heat-mode.ogg', waveMode: 'media/sound/move/wave-mode.ogg', shockMode: 'media/sound/move/shock-mode.ogg', + hitCounterEcho: 'media/sound/battle/hit-counter-echo.ogg', } static getElementName(element: sc.ELEMENT): 'neutralMode' | 'coldMode' | 'heatMode' | 'waveMode' | 'shockMode' { switch(element) { @@ -63,24 +66,38 @@ export class SoundManager { SoundManager.playSound(name, speed, soundPos) } - static playSoundQueue(queue: SoundQueueEntry[]) { - if (queue.length === 0) { return } - const e = queue[0] - const play: boolean = e.condition ? e.condition() : true - if (play) { - if (e.relativePos) { - SoundManager.playSoundAtRelative(e.name, e.speed ?? 1, e.pos) - } else { - SoundManager.playSound(e.name, e.speed ?? 1, e.pos) + static appendQueue(queue: SoundQueueEntry[]) { + const isPlaying: boolean = SoundManager.soundQueue.length != 0 + this.soundQueue.push(...queue) + if (! isPlaying) { + SoundManager.playQueueEntry(this.soundQueue[0], SoundManager.soundQueue) + } + } + + static clearQueue() { SoundManager.soundQueue = [] } + + static playQueueEntry(e: SoundQueueEntry, queue: SoundQueueEntry[]) { + if (!e || queue.length === 0) { return } + + const action = () => { + const play: boolean = e.condition ? e.condition() : true + if (play) { + if (e.relativePos) { + SoundManager.playSoundAtRelative(e.name, e.speed ?? 1, e.pos) + } else { + SoundManager.playSound(e.name, e.speed ?? 1, e.pos) + } } + e.action && e.action(play) + queue.shift() + if (queue.length === 0) { return } + SoundManager.playQueueEntry(queue[0], queue) } - e.action && e.action(play) - queue.shift() - if (queue.length === 0) { return } + if (e.wait === undefined || e.wait === 0) { - SoundManager.playSoundQueue(queue) + action() } else { - setTimeout(() => SoundManager.playSoundQueue(queue), e.wait) + setTimeout(action, e.wait) } } }