From 188050586984dfbb559fd01a5c32b374affebaf1 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 16 Nov 2024 18:31:54 +0000 Subject: [PATCH 1/3] Unsync zone --- skymp5-client/src/index.ts | 2 + skymp5-client/src/services/events/events.ts | 5 +- .../src/services/events/querySuspendSync.ts | 3 + .../services/sweetTaffyPlayerCombatService.ts | 2 + .../sweetTaffySuspendSyncInTutorialService.ts | 129 ++++++++++++++++++ skymp5-client/src/services/spApiInteractor.ts | 2 +- skymp5-client/src/view/formViewArray.ts | 24 +++- 7 files changed, 162 insertions(+), 5 deletions(-) create mode 100644 skymp5-client/src/services/events/querySuspendSync.ts create mode 100644 skymp5-client/src/services/services/sweetTaffySuspendSyncInTutorialService.ts diff --git a/skymp5-client/src/index.ts b/skymp5-client/src/index.ts index 397c322823..c913e75a95 100644 --- a/skymp5-client/src/index.ts +++ b/skymp5-client/src/index.ts @@ -50,6 +50,7 @@ import { BlockedAnimationsService } from "./services/services/blockedAnimationsS import { WorldView } from "./view/worldView"; import { KeyboardEventsService } from "./services/services/keyboardEventsService"; import { MagicSyncService } from "./services/services/magicSyncService"; +import { SweetTaffySuspendSyncInTutorialService } from "./services/services/sweetTaffySuspendSyncInTutorialService"; once("update", () => { Utility.setINIBool("bAlwaysActive:General", true); @@ -86,6 +87,7 @@ const main = () => { new SweetTaffySweetCantDropService(sp, controller), new SweetTaffyPlayerCombatService(sp, controller), new SweetTaffySkillMenuService(sp, controller), + new SweetTaffySuspendSyncInTutorialService(sp, controller), new DisableSkillAdvanceService(sp, controller), new DisableFastTravelService(sp, controller), new DisableDifficultySelectionService(sp, controller), diff --git a/skymp5-client/src/services/events/events.ts b/skymp5-client/src/services/events/events.ts index ea5c797102..1e42a32c21 100644 --- a/skymp5-client/src/services/events/events.ts +++ b/skymp5-client/src/services/events/events.ts @@ -36,7 +36,7 @@ import { QueryBlockSetInventoryEvent } from "./queryBlockSetInventoryEvent"; import { QueryKeyCodeBindings } from "./queryKeyCodeBindings"; import { SpellCastMessage } from "../messages/spellCastMessage"; import { UpdateAnimVariablesMessage } from "../messages/updateAnimVariablesMessage"; - +import { QuerySuspendSyncEvent } from "./querySuspendSync"; type EventTypes = { 'gameLoad': [GameLoadEvent], @@ -79,7 +79,8 @@ type EventTypes = { 'anyMessage': [ConnectionMessage], 'newLocalLagValueCalculated': [NewLocalLagValueCalculatedEvent], 'queryBlockSetInventoryEvent': [QueryBlockSetInventoryEvent], - 'queryKeyCodeBindings': [QueryKeyCodeBindings] + 'queryKeyCodeBindings': [QueryKeyCodeBindings], + 'querySuspendSync': [QuerySuspendSyncEvent], } // https://blog.makerx.com.au/a-type-safe-event-emitter-in-node-js/ diff --git a/skymp5-client/src/services/events/querySuspendSync.ts b/skymp5-client/src/services/events/querySuspendSync.ts new file mode 100644 index 0000000000..d2c98e5413 --- /dev/null +++ b/skymp5-client/src/services/events/querySuspendSync.ts @@ -0,0 +1,3 @@ +export interface QuerySuspendSyncEvent { + suspend: () => void +} diff --git a/skymp5-client/src/services/services/sweetTaffyPlayerCombatService.ts b/skymp5-client/src/services/services/sweetTaffyPlayerCombatService.ts index 78b7ddfa2f..4b8919d95e 100644 --- a/skymp5-client/src/services/services/sweetTaffyPlayerCombatService.ts +++ b/skymp5-client/src/services/services/sweetTaffyPlayerCombatService.ts @@ -243,6 +243,8 @@ export class SweetTaffyPlayerCombatService extends ClientListener { // It's well-tested though, so we can enable it once we have a protection mechanism. // const weaponTimings = this.getSettingsFromFile(); + + // See also SweetTaffySuspendSyncInTutorialService.ts const weaponTimings = this.getSettingsDefault(); if (!weaponTimings) { return null; diff --git a/skymp5-client/src/services/services/sweetTaffySuspendSyncInTutorialService.ts b/skymp5-client/src/services/services/sweetTaffySuspendSyncInTutorialService.ts new file mode 100644 index 0000000000..cb89320475 --- /dev/null +++ b/skymp5-client/src/services/services/sweetTaffySuspendSyncInTutorialService.ts @@ -0,0 +1,129 @@ +import { logError, logTrace } from "src/logging"; +import { QuerySuspendSyncEvent } from "../events/querySuspendSync"; +import { ClientListener, Sp, CombinedController } from "./clientListener"; +import { ObjectReferenceEx } from "src/extensions/objectReferenceEx"; + +interface SuspendZonePoint { + pos: number[]; + radius: number; + worldOrCell: string; +} + +interface SuspendZoneSettings { + points: SuspendZonePoint[]; + keywordImmuneNoSyncZone?: string; +} + +export class SweetTaffySuspendSyncInTutorialService extends ClientListener { + constructor(private sp: Sp, private controller: CombinedController) { + super(); + + if (!this.hasSweetPie()) { + logTrace(this, "SweetTaffy features disabled"); + return; + } + + logTrace(this, "SweetTaffy features enabled"); + + controller.emitter.on("querySuspendSync", (e) => this.onQuerySuspendSync(e)); + } + + private onQuerySuspendSync(e: QuerySuspendSyncEvent) { + // See also sweetTaffySuspendSyncInTutorialService.ts + // const suspendZoneSettings = this.getSettingsFromFile(); + const suspendZoneSettings = this.getSettingsDefault(); + + if (!suspendZoneSettings) { + return; + } + + const { points, keywordImmuneNoSyncZone } = suspendZoneSettings; + const suspendNeeded = this.isSyncSuspendNeeded(points, keywordImmuneNoSyncZone) + + if (suspendNeeded) { + e.suspend(); + } + } + + private isSyncSuspendNeeded(points: SuspendZonePoint[], keywordImmuneNoSyncZone?: string) { + // TODO: de-duplicate implementation with Green Zone implementation in gamemode + let player = this.sp.Game.getPlayer()!; + + let isNoSyncZone = false; + + let pos = [ + player.getPositionX(), + player.getPositionY(), + player.getPositionZ(), + ]; + + const worldOrCell = ObjectReferenceEx.getWorldOrCell(player); + + for (let point of points) { + if (parseInt(point.worldOrCell) !== worldOrCell) { + continue; + } + + let distance = Math.sqrt( + Math.pow(pos[0] - point.pos[0], 2) + + Math.pow(pos[1] - point.pos[1], 2) + + Math.pow(pos[2] - point.pos[2], 2) + ); + if (distance < point.radius) { + isNoSyncZone = true; + break; + } + } + + const isImmune = !keywordImmuneNoSyncZone || !player.wornHasKeyword(this.sp.Keyword.getKeyword(keywordImmuneNoSyncZone)); + + if (isImmune !== this.wasImmune) { + this.wasImmune = isImmune; + logTrace(this, "Immune to NoSync zone:", isImmune); + } + + if (isNoSyncZone !== this.wasInNoSyncZone) { + this.wasInNoSyncZone = isNoSyncZone; + logTrace(this, "NoSync zone:", isNoSyncZone); + } + + return isNoSyncZone && !isImmune; + } + + private getSettingsFromFile(): SuspendZoneSettings | null { + const sweetTaffySuspendSyncInTutorialService = this.sp.settings["skymp5-client"]["sweetTaffySuspendSyncInTutorialService"]; + + if (!sweetTaffySuspendSyncInTutorialService || typeof sweetTaffySuspendSyncInTutorialService !== "object") { + logError(this, `No sweetTaffySuspendSyncInTutorialService settings found`); + return null; + } + + const suspendZoneSettings = (sweetTaffySuspendSyncInTutorialService as Record).suspendZoneSettings; + if (!suspendZoneSettings || typeof suspendZoneSettings !== "object") { + logError(this, `No suspendZoneSettings settings found`); + return null; + } + + return suspendZoneSettings as SuspendZoneSettings; + } + + private getSettingsDefault(): SuspendZoneSettings | null { + return { + points: [], + keywordImmuneNoSyncZone: "", + }; + } + + private hasSweetPie(): boolean { + const modCount = this.sp.Game.getModCount(); + for (let i = 0; i < modCount; ++i) { + if (this.sp.Game.getModName(i).toLowerCase().includes('sweetpie')) { + return true; + } + } + return false; + } + + private wasInNoSyncZone = false; + private wasImmune = false; +} diff --git a/skymp5-client/src/services/spApiInteractor.ts b/skymp5-client/src/services/spApiInteractor.ts index f551bfd06b..0ab1695d0c 100644 --- a/skymp5-client/src/services/spApiInteractor.ts +++ b/skymp5-client/src/services/spApiInteractor.ts @@ -19,7 +19,7 @@ export class SpApiInteractor { lookupListener(constructor: ClientListenerConstructor): T { const listener = SpApiInteractor.listenersForLookupByName.get(constructor); if (listener === undefined) { - throw new Error(`listener not found for name '${constructor.name}'`); + throw new Error(`listener not found for name '${onsctructor.name}'`); } if (!(listener instanceof constructor)) { throw new Error(`listener class mismatch for name '${constructor.name}'`); diff --git a/skymp5-client/src/view/formViewArray.ts b/skymp5-client/src/view/formViewArray.ts index 830361f43f..d5e917c994 100644 --- a/skymp5-client/src/view/formViewArray.ts +++ b/skymp5-client/src/view/formViewArray.ts @@ -30,15 +30,34 @@ export class FormViewArray { } updateAll(model: WorldModel, showMe: boolean, isCloneView: boolean) { - const gamemodeUpdateService = SpApiInteractor.getControllerInstance().lookupListener(GamemodeUpdateService); + const controller = SpApiInteractor.getControllerInstance(); + + const gamemodeUpdateService = controller.lookupListener(GamemodeUpdateService); gamemodeUpdateService.setFormViewArray(this); + this.isSyncSuspended = false; + controller.emitter.emit("querySuspendSync", { + suspend: () => { + this.isSyncSuspended = true; + } + }); + const forms = model.forms; const n = forms.length; for (let i = 0; i < n; ++i) { const form = forms[i]; - if (!form || (model.playerCharacterFormIdx === i && !showMe)) { + if (form === undefined) { + this.destroyForm(i); + continue; + } + + if (this.isSyncSuspended) { + this.destroyForm(i); + continue; + } + + if (model.playerCharacterFormIdx === i && !showMe) { this.destroyForm(i); continue; } @@ -111,4 +130,5 @@ export class FormViewArray { } private formViews = new Array(); + private isSyncSuspended = false; } From e4034e4c9e2188496ac17bca755c3ce0d66b1580 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 16 Nov 2024 23:53:24 +0500 Subject: [PATCH 2/3] Update skymp5-client/src/services/spApiInteractor.ts Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --- skymp5-client/src/services/spApiInteractor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skymp5-client/src/services/spApiInteractor.ts b/skymp5-client/src/services/spApiInteractor.ts index 0ab1695d0c..f551bfd06b 100644 --- a/skymp5-client/src/services/spApiInteractor.ts +++ b/skymp5-client/src/services/spApiInteractor.ts @@ -19,7 +19,7 @@ export class SpApiInteractor { lookupListener(constructor: ClientListenerConstructor): T { const listener = SpApiInteractor.listenersForLookupByName.get(constructor); if (listener === undefined) { - throw new Error(`listener not found for name '${onsctructor.name}'`); + throw new Error(`listener not found for name '${constructor.name}'`); } if (!(listener instanceof constructor)) { throw new Error(`listener class mismatch for name '${constructor.name}'`); From 85218cd1bd4024b52a7966872a26717e2a6d94df Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 16 Nov 2024 18:56:20 +0000 Subject: [PATCH 3/3] . --- .../services/sweetTaffySuspendSyncInTutorialService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skymp5-client/src/services/services/sweetTaffySuspendSyncInTutorialService.ts b/skymp5-client/src/services/services/sweetTaffySuspendSyncInTutorialService.ts index cb89320475..5eb4608b9f 100644 --- a/skymp5-client/src/services/services/sweetTaffySuspendSyncInTutorialService.ts +++ b/skymp5-client/src/services/services/sweetTaffySuspendSyncInTutorialService.ts @@ -1,7 +1,7 @@ -import { logError, logTrace } from "src/logging"; +import { logError, logTrace } from "../../logging"; import { QuerySuspendSyncEvent } from "../events/querySuspendSync"; import { ClientListener, Sp, CombinedController } from "./clientListener"; -import { ObjectReferenceEx } from "src/extensions/objectReferenceEx"; +import { ObjectReferenceEx } from "../../extensions/objectReferenceEx"; interface SuspendZonePoint { pos: number[]; @@ -25,7 +25,7 @@ export class SweetTaffySuspendSyncInTutorialService extends ClientListener { logTrace(this, "SweetTaffy features enabled"); - controller.emitter.on("querySuspendSync", (e) => this.onQuerySuspendSync(e)); + this.controller.emitter.on("querySuspendSync", (e) => this.onQuerySuspendSync(e)); } private onQuerySuspendSync(e: QuerySuspendSyncEvent) {