diff --git a/src/engine/core/managers/base/SaveManager.ts b/src/engine/core/managers/base/SaveManager.ts index 9ca083e91..9dc6021f9 100644 --- a/src/engine/core/managers/base/SaveManager.ts +++ b/src/engine/core/managers/base/SaveManager.ts @@ -93,7 +93,7 @@ export class SaveManager extends AbstractCoreManager { public onBeforeGameSave(saveName: TName): void { logger.info("Before game save:", saveName); - EventsManager.getInstance().emitEvent(EGameEvent.GAME_SAVE, this.dynamicData.generic); + EventsManager.emitEvent(EGameEvent.GAME_SAVE, this.dynamicData.generic); saveDynamicGameSave(saveName, this.dynamicData); } @@ -119,7 +119,7 @@ export class SaveManager extends AbstractCoreManager { this.dynamicData = data ? data : this.dynamicData; - EventsManager.getInstance().emitEvent(EGameEvent.GAME_LOAD, this.dynamicData.generic); + EventsManager.emitEvent(EGameEvent.GAME_LOAD, this.dynamicData.generic); } /** diff --git a/src/engine/core/managers/events/EventsManager.test.ts b/src/engine/core/managers/events/EventsManager.test.ts index ccd293144..2d94b58d1 100644 --- a/src/engine/core/managers/events/EventsManager.test.ts +++ b/src/engine/core/managers/events/EventsManager.test.ts @@ -13,7 +13,7 @@ describe("EventsManager class", () => { it("should correctly initialize", () => { const manager: EventsManager = getManagerInstance(EventsManager); - expect(MockLuaTable.getMockSize(manager.callbacks)).toBe(31); + expect(MockLuaTable.getMockSize(manager.callbacks)).toBe(33); Object.keys(manager.callbacks).forEach((it) => { expect(MockLuaTable.getMockSize(manager.callbacks[it as unknown as EGameEvent])).toBe(0); diff --git a/src/engine/core/managers/events/types.ts b/src/engine/core/managers/events/types.ts index ad63479ff..aa31868c4 100644 --- a/src/engine/core/managers/events/types.ts +++ b/src/engine/core/managers/events/types.ts @@ -64,10 +64,22 @@ export enum EGameEvent { * Surge skipped (happened during sleep or more important events). */ SURGE_SKIPPED, + /** + * Surge survived with usage of anabiotic. + */ + SURGE_SURVIVED_WITH_ANABIOTIC, /** * Task state updated. */ TASK_STATE_UPDATE, + /** + * Task completed. + */ + TASK_COMPLETED, + /** + * Actor found treasure. + */ + TREASURE_FOUND, /** * Generic notification event. */ @@ -79,27 +91,35 @@ export enum EGameEvent { /** * On interaction with NPC, when player 'uses' game object. */ - NPC_INTERACTION, + STALKER_INTERACTION, /** - * todo; + * On stalker hit. */ - MONSTER_HIT, + STALKER_HIT, /** - * todo; + * On stalker killed. + * Client side death event. */ - NPC_HIT, + STALKER_KILLED, /** - * todo; + * Stalker death. + * Server side death event. */ - ENEMY_SEE_ACTOR, + STALKER_DEATH, /** - * todo; + * On monster hit. */ - ACTOR_SEE_ENEMY, + MONSTER_HIT, /** - * todo; + * On monster killed. + * Client side death event. */ - NPC_SHOT_ACTOR, + MONSTER_KILLED, + /** + * Monster death. + * Server side death event. + */ + MONSTER_DEATH, /** * Main menu turned on. */ @@ -108,14 +128,6 @@ export enum EGameEvent { * Main menu turned off. */ MAIN_MENU_OFF, - /** - * Monster death. - */ - MONSTER_DEATH, - /** - * Stalker death. - */ - STALKER_DEATH, /** * Game started. */ diff --git a/src/engine/core/managers/interaction/SleepManager.ts b/src/engine/core/managers/interaction/SleepManager.ts index 3c6831509..44266c3e7 100644 --- a/src/engine/core/managers/interaction/SleepManager.ts +++ b/src/engine/core/managers/interaction/SleepManager.ts @@ -91,7 +91,7 @@ export class SleepManager extends AbstractCoreManager { registry.actor.power = 1; - EventsManager.getInstance().emitEvent(EGameEvent.ACTOR_START_SLEEP); + EventsManager.emitEvent(EGameEvent.ACTOR_START_SLEEP); } /** @@ -112,6 +112,6 @@ export class SleepManager extends AbstractCoreManager { disableInfo(infoPortions.actor_is_sleeping); disableInfo(infoPortions.sleep_active); - EventsManager.getInstance().emitEvent(EGameEvent.ACTOR_FINISH_SLEEP); + EventsManager.emitEvent(EGameEvent.ACTOR_FINISH_SLEEP); } } diff --git a/src/engine/core/managers/interaction/achievements/AchievementManager.test.ts b/src/engine/core/managers/interaction/achievements/AchievementManager.test.ts index b2409fa64..9c033f33d 100644 --- a/src/engine/core/managers/interaction/achievements/AchievementManager.test.ts +++ b/src/engine/core/managers/interaction/achievements/AchievementManager.test.ts @@ -552,11 +552,11 @@ describe("AchievementManager class", () => { disableInfo(infoPortions.actor_marked_by_zone_3_times); expect(achievementsManager.checkAchievedMarkedByZone()).toBeFalsy(); - statisticsManager.onAnabioticUsed(); - statisticsManager.onAnabioticUsed(); + statisticsManager.onSurvivedSurgeWithAnabiotic(); + statisticsManager.onSurvivedSurgeWithAnabiotic(); expect(achievementsManager.checkAchievedMarkedByZone()).toBeFalsy(); - statisticsManager.onAnabioticUsed(); + statisticsManager.onSurvivedSurgeWithAnabiotic(); expect(achievementsManager.checkAchievedMarkedByZone()).toBeTruthy(); expect(onNotification).toHaveBeenCalledTimes(1); expect(hasAlifeInfo(infoPortions.actor_marked_by_zone_3_times)).toBeTruthy(); diff --git a/src/engine/core/managers/interaction/achievements/AchievementsManager.ts b/src/engine/core/managers/interaction/achievements/AchievementsManager.ts index 8a1efaf72..73809c678 100644 --- a/src/engine/core/managers/interaction/achievements/AchievementsManager.ts +++ b/src/engine/core/managers/interaction/achievements/AchievementsManager.ts @@ -118,7 +118,7 @@ export class AchievementsManager extends AbstractCoreManager { ) { giveInfo(infoPortions.pioneer_achievement_gained); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_pioneer, senderId: achievementIcons[EAchievement.PIONEER], @@ -143,7 +143,7 @@ export class AchievementsManager extends AbstractCoreManager { ) { giveInfo(infoPortions.mutant_hunter_achievement_gained); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_mutant_hunter, senderId: achievementIcons[EAchievement.MUTANT_HUNTER], @@ -162,7 +162,7 @@ export class AchievementsManager extends AbstractCoreManager { if (hasAlifeInfo(infoPortions.zat_b22_barmen_gave_reward)) { giveInfo(infoPortions.detective_achievement_gained); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_detective, senderId: achievementIcons[EAchievement.DETECTIVE], @@ -181,7 +181,7 @@ export class AchievementsManager extends AbstractCoreManager { if (hasAlifeInfos([infoPortions.zat_b30_sultan_loose, infoPortions.zat_b7_actor_help_stalkers])) { giveInfo(infoPortions.one_of_the_lads_gained); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_one_of_the_lads, senderId: achievementIcons[EAchievement.ONE_OF_THE_LADS], @@ -200,7 +200,7 @@ export class AchievementsManager extends AbstractCoreManager { if (hasAlifeInfos([infoPortions.zat_b30_barmen_under_sultan, infoPortions.zat_b7_actor_help_bandits])) { giveInfo(infoPortions.kingpin_gained); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_kingpin, senderId: achievementIcons[EAchievement.KINGPIN], @@ -225,7 +225,7 @@ export class AchievementsManager extends AbstractCoreManager { ) { giveInfo(infoPortions.herald_of_justice_achievement_gained); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_herald_of_justice, senderId: achievementIcons[EAchievement.HERALD_OF_JUSTICE], @@ -257,7 +257,7 @@ export class AchievementsManager extends AbstractCoreManager { giveInfo(infoPortions.sim_bandit_attack_harder); increaseCommunityGoodwillToId(communities.stalker, registry.actor.id(), 200); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_seeker, senderId: achievementIcons[EAchievement.SEEKER], @@ -274,7 +274,7 @@ export class AchievementsManager extends AbstractCoreManager { if (hasAlifeInfo(infoPortions.zat_b3_all_instruments_brought)) { giveInfo(infoPortions.battle_systems_master_achievement_gained); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_battle_systems_master, senderId: achievementIcons[EAchievement.BATTLE_SYSTEMS_MASTER], @@ -299,7 +299,7 @@ export class AchievementsManager extends AbstractCoreManager { ) { giveInfo(infoPortions.high_tech_master_achievement_gained); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_high_tech_master, senderId: achievementIcons[EAchievement.HIGH_TECH_MASTER], @@ -318,7 +318,7 @@ export class AchievementsManager extends AbstractCoreManager { if (hasAlifeInfo(infoPortions.actor_was_in_many_bad_places)) { giveInfo(infoPortions.skilled_stalker_achievement_gained); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_skilled_stalker, senderId: achievementIcons[EAchievement.SKILLED_STALKER], @@ -343,7 +343,7 @@ export class AchievementsManager extends AbstractCoreManager { ) { giveInfo(infoPortions.leader_achievement_gained); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_leader, senderId: achievementIcons[EAchievement.LEADER], @@ -378,7 +378,7 @@ export class AchievementsManager extends AbstractCoreManager { increaseCommunityGoodwillToId(communities.dolg, actorId, 200); increaseCommunityGoodwillToId(communities.bandit, actorId, 200); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_diplomat, senderId: achievementIcons[EAchievement.DIPLOMAT], @@ -411,7 +411,7 @@ export class AchievementsManager extends AbstractCoreManager { ) { giveInfo(infoPortions.research_man_gained); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_research_man, senderId: achievementIcons[EAchievement.RESEARCH_MAN], @@ -437,7 +437,7 @@ export class AchievementsManager extends AbstractCoreManager { ) { giveInfo(infoPortions.sim_duty_help_harder); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_friend_of_duty, senderId: achievementIcons[EAchievement.FRIEND_OF_DUTY], @@ -463,7 +463,7 @@ export class AchievementsManager extends AbstractCoreManager { ) { giveInfo(infoPortions.sim_freedom_help_harder); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_friend_of_freedom, senderId: achievementIcons[EAchievement.FRIEND_OF_FREEDOM], @@ -488,7 +488,7 @@ export class AchievementsManager extends AbstractCoreManager { ) { giveInfo(infoPortions.balance_advocate_gained); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_balance_advocate, senderId: achievementIcons[EAchievement.BALANCE_ADVOCATE], @@ -506,7 +506,7 @@ export class AchievementsManager extends AbstractCoreManager { if (!hasAlifeInfo(infoPortions.actor_wealthy) && registry.actor.money() >= 100_000) { giveInfo(infoPortions.actor_wealthy); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_wealthy, senderId: achievementIcons[EAchievement.WEALTHY], @@ -524,7 +524,7 @@ export class AchievementsManager extends AbstractCoreManager { if (hasAlifeInfo(infoPortions.pri_b305_all_strelok_notes_given)) { giveInfo(infoPortions.keeper_of_secrets_achievement_gained); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_keeper_of_secrets, senderId: achievementIcons[EAchievement.KEEPER_OF_SECRETS], @@ -546,7 +546,7 @@ export class AchievementsManager extends AbstractCoreManager { if (StatisticsManager.getInstance().getUsedAnabioticsCount() >= 3) { giveInfo(infoPortions.actor_marked_by_zone_3_times); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_marked_by_zone, senderId: achievementIcons[EAchievement.MARKED_BY_ZONE], @@ -588,7 +588,7 @@ export class AchievementsManager extends AbstractCoreManager { ) { giveInfo(infoPortions.actor_information_dealer); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_information_dealer, senderId: achievementIcons[EAchievement.INFORMATION_DEALER], @@ -616,7 +616,7 @@ export class AchievementsManager extends AbstractCoreManager { giveInfo(infoPortions.sim_stalker_help_harder); increaseCommunityGoodwillToId(communities.stalker, registry.actor.id(), 100); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_ach_friend_of_stalkers, senderId: achievementIcons[EAchievement.FRIEND_OF_STALKERS], @@ -655,7 +655,7 @@ export class AchievementsManager extends AbstractCoreManager { 4 ); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_detective_news, senderId: notificationManagerIcons.got_medicine, @@ -685,7 +685,7 @@ export class AchievementsManager extends AbstractCoreManager { 5 ); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.TIP, caption: captions.st_mutant_hunter_news, senderId: notificationManagerIcons.got_ammo, diff --git a/src/engine/core/managers/interaction/dialog/DialogManager.ts b/src/engine/core/managers/interaction/dialog/DialogManager.ts index 7a8937174..0ac809c4c 100644 --- a/src/engine/core/managers/interaction/dialog/DialogManager.ts +++ b/src/engine/core/managers/interaction/dialog/DialogManager.ts @@ -79,7 +79,7 @@ export class DialogManager extends AbstractCoreManager { const eventsManager: EventsManager = EventsManager.getInstance(); - eventsManager.registerCallback(EGameEvent.NPC_INTERACTION, this.onInteractWithObject, this); + eventsManager.registerCallback(EGameEvent.STALKER_INTERACTION, this.onInteractWithObject, this); for (const it of $range(0, DIALOG_MANAGER_LTX.line_count("list") - 1)) { const [, id] = DIALOG_MANAGER_LTX.r_line("list", it, "", ""); @@ -139,7 +139,7 @@ export class DialogManager extends AbstractCoreManager { const eventsManager: EventsManager = EventsManager.getInstance(); - eventsManager.unregisterCallback(EGameEvent.NPC_INTERACTION, this.onInteractWithObject); + eventsManager.unregisterCallback(EGameEvent.STALKER_INTERACTION, this.onInteractWithObject); const actorTable: LuaArray = $fromArray([ EGenericDialogCategory.JOB, diff --git a/src/engine/core/managers/interaction/tasks/TaskManager.ts b/src/engine/core/managers/interaction/tasks/TaskManager.ts index 8abf51915..9c374ee73 100644 --- a/src/engine/core/managers/interaction/tasks/TaskManager.ts +++ b/src/engine/core/managers/interaction/tasks/TaskManager.ts @@ -7,7 +7,6 @@ import { EGameEvent, EventsManager } from "@/engine/core/managers/events"; import { TaskObject } from "@/engine/core/managers/interaction/tasks/TaskObject"; import { ETaskState } from "@/engine/core/managers/interaction/tasks/types"; import { NotificationManager } from "@/engine/core/managers/interface/notifications"; -import { StatisticsManager } from "@/engine/core/managers/interface/statistics/StatisticsManager"; import { assert } from "@/engine/core/utils/assertion"; import { LuaLogger } from "@/engine/core/utils/logging"; import { getTableSize } from "@/engine/core/utils/table"; @@ -63,7 +62,7 @@ export class TaskManager extends AbstractCoreManager { } else { if (task.checkTaskState() === ETaskState.COMPLETED) { task.giveTaskReward(); - StatisticsManager.getInstance().onTaskCompleted(task); + EventsManager.emitEvent(EGameEvent.TASK_COMPLETED, task); return true; } else { diff --git a/src/engine/core/managers/interface/PdaManager.ts b/src/engine/core/managers/interface/PdaManager.ts index 90272bce4..ab0d310c0 100644 --- a/src/engine/core/managers/interface/PdaManager.ts +++ b/src/engine/core/managers/interface/PdaManager.ts @@ -56,7 +56,7 @@ export class PdaManager extends AbstractCoreManager { case EStatSection.SURGES: return tostring(statisticsManager.actorStatistics.surgesCount); case EStatSection.COMPLETED_QUESTS: - return tostring(statisticsManager.actorStatistics.completedQuestsCount); + return tostring(statisticsManager.actorStatistics.completedTasksCount); case EStatSection.KILLED_MONSTERS: return tostring(statisticsManager.actorStatistics.killedMonstersCount); case EStatSection.KILLED_STALKERS: diff --git a/src/engine/core/managers/interface/statistics/StatisticsManager.test.ts b/src/engine/core/managers/interface/statistics/StatisticsManager.test.ts index 1d75baadf..1f681dffb 100644 --- a/src/engine/core/managers/interface/statistics/StatisticsManager.test.ts +++ b/src/engine/core/managers/interface/statistics/StatisticsManager.test.ts @@ -2,11 +2,11 @@ import { beforeEach, describe, expect, it } from "@jest/globals"; import { clsid } from "xray16"; import { disposeManager, getManagerInstance, registerActor, registry } from "@/engine/core/database"; -import { TaskObject } from "@/engine/core/managers/interaction/tasks"; +import { EGameEvent, EventsManager } from "@/engine/core/managers"; import { StatisticsManager } from "@/engine/core/managers/interface/statistics/StatisticsManager"; -import { ITreasureDescriptor } from "@/engine/core/managers/world/TreasureManager"; +import { classIds } from "@/engine/lib/constants/class_ids"; import { weapons } from "@/engine/lib/constants/items/weapons"; -import { ClientObject, TName } from "@/engine/lib/types"; +import { ClientObject, TClassId, TName } from "@/engine/lib/types"; import { MockLuaTable } from "@/fixtures/lua"; import { replaceFunctionMock } from "@/fixtures/utils"; import { @@ -17,19 +17,44 @@ import { mockServerAlifeMonsterBase, mockServerAlifeObject, } from "@/fixtures/xray"; +import { MockVector } from "@/fixtures/xray/mocks/vector.mock"; describe("StatisticsManager class", () => { beforeEach(() => { registry.managers = new LuaTable(); }); + it("should correctly initialize and destroy", () => { + const statisticsManager: StatisticsManager = getManagerInstance(StatisticsManager); + const eventsManager: EventsManager = getManagerInstance(EventsManager); + + expect(eventsManager.getSubscribersCount()).toBe(10); + expect(statisticsManager.takenArtefacts).toEqualLuaTables(new LuaTable()); + expect(statisticsManager.actorStatistics).toEqual({ + surgesCount: 0, + completedTasksCount: 0, + killedMonstersCount: 0, + killedStalkersCount: 0, + collectedTreasuresCount: 0, + collectedArtefactsCount: 0, + bestKilledMonster: null, + bestKilledMonsterRank: 0, + favoriteWeapon: null, + collectedArtefacts: new LuaTable(), + }); + + disposeManager(StatisticsManager); + + expect(eventsManager.getSubscribersCount()).toBe(0); + }); + it("should correctly handle surges", () => { const manager: StatisticsManager = StatisticsManager.getInstance(); expect(manager.actorStatistics.surgesCount).toBe(0); - manager.onSurgePassed(); - manager.onSurgePassed(); + EventsManager.emitEvent(EGameEvent.SURGE_ENDED); + EventsManager.emitEvent(EGameEvent.SURGE_SKIPPED); expect(manager.actorStatistics.surgesCount).toBe(2); }); @@ -39,19 +64,22 @@ describe("StatisticsManager class", () => { expect(manager.actorStatistics.collectedTreasuresCount).toBe(0); - manager.onTreasureFound({} as ITreasureDescriptor); - manager.onTreasureFound({} as ITreasureDescriptor); + EventsManager.emitEvent(EGameEvent.TREASURE_FOUND, {}); + EventsManager.emitEvent(EGameEvent.TREASURE_FOUND, {}); expect(manager.actorStatistics.collectedTreasuresCount).toBe(2); }); it("should correctly handle stalkers killing", () => { const manager: StatisticsManager = StatisticsManager.getInstance(); + const actor: ClientObject = mockActorClientGameObject(); expect(manager.actorStatistics.killedStalkersCount).toBe(0); - manager.onStalkerKilledByActor(mockClientGameObject()); - manager.onStalkerKilledByActor(mockClientGameObject()); + EventsManager.emitEvent(EGameEvent.STALKER_KILLED, mockClientGameObject(), actor); + EventsManager.emitEvent(EGameEvent.STALKER_KILLED, mockClientGameObject(), actor); + EventsManager.emitEvent(EGameEvent.STALKER_KILLED, mockClientGameObject(), null); + EventsManager.emitEvent(EGameEvent.STALKER_KILLED, mockClientGameObject(), mockClientGameObject()); expect(manager.actorStatistics.killedStalkersCount).toBe(2); }); @@ -59,23 +87,12 @@ describe("StatisticsManager class", () => { it("should correctly handle tasks", () => { const manager: StatisticsManager = StatisticsManager.getInstance(); - expect(manager.actorStatistics.completedQuestsCount).toBe(0); + expect(manager.actorStatistics.completedTasksCount).toBe(0); - manager.onTaskCompleted({} as TaskObject); - manager.onTaskCompleted({} as TaskObject); + EventsManager.emitEvent(EGameEvent.TASK_COMPLETED, {}); + EventsManager.emitEvent(EGameEvent.TASK_COMPLETED, {}); - expect(manager.actorStatistics.completedQuestsCount).toBe(2); - }); - - it("should correctly handle completed quests", () => { - const manager: StatisticsManager = StatisticsManager.getInstance(); - - expect(manager.actorStatistics.completedQuestsCount).toBe(0); - - manager.onTaskCompleted({} as TaskObject); - manager.onTaskCompleted({} as TaskObject); - - expect(manager.actorStatistics.completedQuestsCount).toBe(2); + expect(manager.actorStatistics.completedTasksCount).toBe(2); }); it("should correctly handle anabiotics", () => { @@ -85,17 +102,17 @@ describe("StatisticsManager class", () => { expect(manager.getUsedAnabioticsCount()).toBe(0); - manager.onAnabioticUsed(); - manager.onAnabioticUsed(); + EventsManager.emitEvent(EGameEvent.SURGE_SURVIVED_WITH_ANABIOTIC); + EventsManager.emitEvent(EGameEvent.SURGE_SURVIVED_WITH_ANABIOTIC); expect(manager.getUsedAnabioticsCount()).toBe(2); }); it("should correctly handle taking artefacts", () => { const manager: StatisticsManager = StatisticsManager.getInstance(); - const firstClient: ClientObject = mockClientGameObject(); - const secondClient: ClientObject = mockClientGameObject(); - const thirdClient: ClientObject = mockClientGameObject(); + const firstClient: ClientObject = mockClientGameObject({ clsid: () => classIds.art_black_drops as TClassId }); + const secondClient: ClientObject = mockClientGameObject({ clsid: () => classIds.art_bast_artefact as TClassId }); + const thirdClient: ClientObject = mockClientGameObject({ clsid: () => classIds.art_zuda as TClassId }); mockServerAlifeObject({ id: firstClient.id(), sectionOverride: "af_first" }); mockServerAlifeObject({ id: secondClient.id(), sectionOverride: "af_first" }); @@ -103,15 +120,16 @@ describe("StatisticsManager class", () => { expect(manager.actorStatistics.collectedArtefactsCount).toBe(0); - manager.onArtefactCollected(firstClient); - manager.onArtefactCollected(firstClient); - manager.onArtefactCollected(firstClient); - manager.onArtefactCollected(firstClient); - manager.onArtefactCollected(secondClient); - manager.onArtefactCollected(secondClient); - manager.onArtefactCollected(secondClient); - manager.onArtefactCollected(thirdClient); - manager.onArtefactCollected(thirdClient); + EventsManager.emitEvent(EGameEvent.ACTOR_ITEM_TAKE, mockClientGameObject()); + EventsManager.emitEvent(EGameEvent.ACTOR_ITEM_TAKE, firstClient); + EventsManager.emitEvent(EGameEvent.ACTOR_ITEM_TAKE, firstClient); + EventsManager.emitEvent(EGameEvent.ACTOR_ITEM_TAKE, firstClient); + EventsManager.emitEvent(EGameEvent.ACTOR_ITEM_TAKE, firstClient); + EventsManager.emitEvent(EGameEvent.ACTOR_ITEM_TAKE, secondClient); + EventsManager.emitEvent(EGameEvent.ACTOR_ITEM_TAKE, secondClient); + EventsManager.emitEvent(EGameEvent.ACTOR_ITEM_TAKE, secondClient); + EventsManager.emitEvent(EGameEvent.ACTOR_ITEM_TAKE, thirdClient); + EventsManager.emitEvent(EGameEvent.ACTOR_ITEM_TAKE, thirdClient); expect(manager.actorStatistics.collectedArtefactsCount).toBe(3); expect(manager.actorStatistics.collectedArtefacts.length()).toBe(2); @@ -133,7 +151,8 @@ describe("StatisticsManager class", () => { mockServerAlifeObject({ id: desertEagle.id(), sectionOverride: weapons.wpn_desert_eagle }); mockServerAlifeObject({ id: desertEagleNimble.id(), sectionOverride: weapons.wpn_desert_eagle_nimble }); - manager.onObjectHitByActor(100, target); + EventsManager.emitEvent(EGameEvent.MONSTER_HIT, target, 100, MockVector.mock(), actor); + EventsManager.emitEvent(EGameEvent.MONSTER_HIT, target, 100, MockVector.mock(), mockClientGameObject()); expect(manager.weaponsStatistics.length()).toBe(36); (manager.weaponsStatistics as unknown as MockLuaTable).forEach((value, key) => { @@ -143,8 +162,9 @@ describe("StatisticsManager class", () => { replaceFunctionMock(actor.active_item, () => ak74); - manager.onObjectHitByActor(100, target); - manager.onObjectHitByActor(150, target); + EventsManager.emitEvent(EGameEvent.MONSTER_HIT, target, 100, MockVector.mock(), actor); + EventsManager.emitEvent(EGameEvent.STALKER_HIT, target, 150, MockVector.mock(), actor); + EventsManager.emitEvent(EGameEvent.MONSTER_HIT, target, 150, MockVector.mock(), mockClientGameObject()); expect(manager.weaponsStatistics.get("ak74")).toBe(250); expect(manager.weaponsStatistics.get("desert")).toBe(0); @@ -152,9 +172,10 @@ describe("StatisticsManager class", () => { replaceFunctionMock(actor.active_item, () => desertEagle); - manager.onObjectHitByActor(100, target); + EventsManager.emitEvent(EGameEvent.MONSTER_HIT, target, 100, MockVector.mock(), actor); expect(manager.actorStatistics.favoriteWeapon).toBe("wpn_ak74"); - manager.onObjectHitByActor(300, target); + EventsManager.emitEvent(EGameEvent.MONSTER_HIT, target, 300, MockVector.mock(), actor); + EventsManager.emitEvent(EGameEvent.MONSTER_HIT, target, 300, MockVector.mock(), mockClientGameObject()); expect(manager.weaponsStatistics.get("ak74")).toBe(250); expect(manager.weaponsStatistics.get("desert")).toBe(400); @@ -162,7 +183,7 @@ describe("StatisticsManager class", () => { replaceFunctionMock(actor.active_item, () => desertEagleNimble); - manager.onObjectHitByActor(100, target); + EventsManager.emitEvent(EGameEvent.STALKER_HIT, target, 100, MockVector.mock(), actor); expect(manager.weaponsStatistics.get("ak74")).toBe(250); expect(manager.weaponsStatistics.get("desert")).toBe(500); @@ -175,7 +196,9 @@ describe("StatisticsManager class", () => { const firstMonster: ClientObject = mockClientGameObject({ clsid: () => clsid.flesh_s, rank: () => 3 }); const secondMonster: ClientObject = mockClientGameObject({ clsid: () => clsid.bloodsucker_s, rank: () => 15 }); const thirdMonster: ClientObject = mockClientGameObject({ clsid: () => clsid.bloodsucker_s, rank: () => 16 }); + const actor: ClientObject = mockActorClientGameObject(); + registerActor(actor); mockServerAlifeMonsterBase({ id: firstMonster.id(), rank: () => 3 }); mockServerAlifeMonsterBase({ id: secondMonster.id(), rank: () => 15 }); mockServerAlifeMonsterBase({ id: thirdMonster.id(), rank: () => 16 }); @@ -184,25 +207,28 @@ describe("StatisticsManager class", () => { expect(manager.actorStatistics.bestKilledMonster).toBeNull(); expect(manager.actorStatistics.bestKilledMonsterRank).toBe(0); - expect(() => manager.onMonsterKilledByActor(mockClientGameObject())).toThrow(); + expect(() => EventsManager.emitEvent(EGameEvent.MONSTER_KILLED, mockClientGameObject(), actor)).toThrow(); + expect(() => { + EventsManager.emitEvent(EGameEvent.MONSTER_KILLED, mockClientGameObject(), mockClientGameObject()); + }).not.toThrow(); expect(manager.actorStatistics.killedMonstersCount).toBe(0); expect(manager.actorStatistics.bestKilledMonster).toBeNull(); expect(manager.actorStatistics.bestKilledMonsterRank).toBe(0); - manager.onMonsterKilledByActor(firstMonster); + EventsManager.emitEvent(EGameEvent.MONSTER_KILLED, firstMonster, actor); expect(manager.actorStatistics.killedMonstersCount).toBe(1); expect(manager.actorStatistics.bestKilledMonster).toBe("flesh_strong"); expect(manager.actorStatistics.bestKilledMonsterRank).toBe(3); - manager.onMonsterKilledByActor(secondMonster); + EventsManager.emitEvent(EGameEvent.MONSTER_KILLED, secondMonster, actor); expect(manager.actorStatistics.killedMonstersCount).toBe(2); expect(manager.actorStatistics.bestKilledMonster).toBe("bloodsucker_normal"); expect(manager.actorStatistics.bestKilledMonsterRank).toBe(15); - manager.onMonsterKilledByActor(thirdMonster); + EventsManager.emitEvent(EGameEvent.MONSTER_KILLED, thirdMonster, actor); expect(manager.actorStatistics.killedMonstersCount).toBe(3); expect(manager.actorStatistics.bestKilledMonster).toBe("bloodsucker_strong"); @@ -221,7 +247,7 @@ describe("StatisticsManager class", () => { killedStalkersCount: 4, bestKilledMonsterRank: 16, surgesCount: 1, - completedQuestsCount: 40, + completedTasksCount: 40, favoriteWeapon: "wpn_ak74", bestKilledMonster: "bloodsucker_strong", }; @@ -419,7 +445,7 @@ describe("StatisticsManager class", () => { expect(netProcessor.dataList).toHaveLength(0); expect(newManager).not.toBe(oldManager); - expect(newManager.actorStatistics).toEqual({ + expect(newManager.actorStatistics).toEqualLuaTables({ collectedArtefacts: $fromObject({ af_1: true, af_2: true }), collectedArtefactsCount: 10, collectedTreasuresCount: 24, @@ -427,7 +453,7 @@ describe("StatisticsManager class", () => { killedStalkersCount: 4, bestKilledMonsterRank: 16, surgesCount: 1, - completedQuestsCount: 40, + completedTasksCount: 40, favoriteWeapon: "wpn_ak74", bestKilledMonster: "bloodsucker_strong", }); diff --git a/src/engine/core/managers/interface/statistics/StatisticsManager.ts b/src/engine/core/managers/interface/statistics/StatisticsManager.ts index 36aa0708d..72cda535a 100644 --- a/src/engine/core/managers/interface/statistics/StatisticsManager.ts +++ b/src/engine/core/managers/interface/statistics/StatisticsManager.ts @@ -1,13 +1,16 @@ import { alife, clsid } from "xray16"; import { getPortableStoreValue, registry, setPortableStoreValue } from "@/engine/core/database"; +import { EGameEvent, EventsManager } from "@/engine/core/managers"; import { AbstractCoreManager } from "@/engine/core/managers/base/AbstractCoreManager"; import type { TaskObject } from "@/engine/core/managers/interaction/tasks"; import type { IActorStatistics } from "@/engine/core/managers/interface/statistics/statistics_types"; import type { ITreasureDescriptor } from "@/engine/core/managers/world/TreasureManager"; import { assert } from "@/engine/core/utils/assertion"; import { LuaLogger } from "@/engine/core/utils/logging"; +import { isArtefact } from "@/engine/core/utils/object"; import { getTableSize } from "@/engine/core/utils/table"; +import { ACTOR_ID } from "@/engine/lib/constants/ids"; import { TInventoryItem } from "@/engine/lib/constants/items"; import { TArtefact } from "@/engine/lib/constants/items/artefacts"; import { TWeapon, weapons } from "@/engine/lib/constants/items/weapons"; @@ -27,6 +30,7 @@ import { TName, TNumberId, TRate, + Vector, } from "@/engine/lib/types"; const logger: LuaLogger = new LuaLogger($filename); @@ -39,7 +43,7 @@ export class StatisticsManager extends AbstractCoreManager { public actorStatistics: IActorStatistics = { surgesCount: 0, - completedQuestsCount: 0, + completedTasksCount: 0, killedMonstersCount: 0, killedStalkersCount: 0, collectedTreasuresCount: 0, @@ -107,6 +111,36 @@ export class StatisticsManager extends AbstractCoreManager { [clsid.tushkano_s]: "tushkano", }; + public override initialize(): void { + const eventsManager: EventsManager = EventsManager.getInstance(); + + eventsManager.registerCallback(EGameEvent.TASK_COMPLETED, this.onTaskCompleted, this); + eventsManager.registerCallback(EGameEvent.SURGE_SURVIVED_WITH_ANABIOTIC, this.onSurvivedSurgeWithAnabiotic, this); + eventsManager.registerCallback(EGameEvent.ACTOR_ITEM_TAKE, this.onActorCollectedItem, this); + eventsManager.registerCallback(EGameEvent.SURGE_SKIPPED, this.onSurgePassed, this); + eventsManager.registerCallback(EGameEvent.SURGE_ENDED, this.onSurgePassed, this); + eventsManager.registerCallback(EGameEvent.TREASURE_FOUND, this.onTreasureFound, this); + eventsManager.registerCallback(EGameEvent.STALKER_HIT, this.onObjectHit, this); + eventsManager.registerCallback(EGameEvent.STALKER_KILLED, this.onStalkerKilled, this); + eventsManager.registerCallback(EGameEvent.MONSTER_HIT, this.onObjectHit, this); + eventsManager.registerCallback(EGameEvent.MONSTER_KILLED, this.onMonsterKilled, this); + } + + public override destroy(): void { + const eventsManager: EventsManager = EventsManager.getInstance(); + + eventsManager.unregisterCallback(EGameEvent.TASK_COMPLETED, this.onTaskCompleted); + eventsManager.unregisterCallback(EGameEvent.SURGE_SURVIVED_WITH_ANABIOTIC, this.onSurvivedSurgeWithAnabiotic); + eventsManager.unregisterCallback(EGameEvent.ACTOR_ITEM_TAKE, this.onActorCollectedItem); + eventsManager.unregisterCallback(EGameEvent.SURGE_SKIPPED, this.onSurgePassed); + eventsManager.unregisterCallback(EGameEvent.SURGE_ENDED, this.onSurgePassed); + eventsManager.unregisterCallback(EGameEvent.TREASURE_FOUND, this.onTreasureFound); + eventsManager.unregisterCallback(EGameEvent.STALKER_HIT, this.onObjectHit); + eventsManager.unregisterCallback(EGameEvent.STALKER_KILLED, this.onStalkerKilled); + eventsManager.unregisterCallback(EGameEvent.MONSTER_HIT, this.onObjectHit); + eventsManager.unregisterCallback(EGameEvent.MONSTER_KILLED, this.onMonsterKilled); + } + /** * Get count of used anabiotics from pstore. * @@ -119,7 +153,7 @@ export class StatisticsManager extends AbstractCoreManager { /** * Handle usage of anabiotic during emission. */ - public onAnabioticUsed(): void { + public onSurvivedSurgeWithAnabiotic(): void { logger.info("Increment used anabiotics count"); setPortableStoreValue( @@ -136,18 +170,22 @@ export class StatisticsManager extends AbstractCoreManager { */ public onTaskCompleted(task: TaskObject): void { logger.info("Increment completed quests count"); - this.actorStatistics.completedQuestsCount += 1; + this.actorStatistics.completedTasksCount += 1; } /** - * Handle artefact pick up event. + * Handle item pick up event. * - * @param artefact - target client object picked up + * @param item - target client object picked up */ - public onArtefactCollected(artefact: ClientObject): void { + public onActorCollectedItem(item: ClientObject): void { + if (!isArtefact(item)) { + return; + } + logger.info("Increment collected artefacts count"); - const artefactId: TNumberId = artefact.id(); + const artefactId: TNumberId = item.id(); if (!this.takenArtefacts.has(artefactId)) { this.actorStatistics.collectedArtefactsCount += 1; @@ -184,19 +222,28 @@ export class StatisticsManager extends AbstractCoreManager { /** * Handle stalker kill event and update stats. * - * @param object - object killed by an actor + * @param object - object killed + * @param killer - object killer */ - public onStalkerKilledByActor(object: ClientObject): void { - this.actorStatistics.killedStalkersCount += 1; + public onStalkerKilled(object: ClientObject, killer: Optional): void { + if (killer?.id() === ACTOR_ID) { + this.actorStatistics.killedStalkersCount += 1; + } } /** * Handle object hit by an actor and collect statistics. * - * @param amount - amount of damage done * @param object - client object + * @param amount - amount of damage done + * @param direction - direction of object hit + * @param who - source object of hit */ - public onObjectHitByActor(amount: TCount, object: ClientObject): void { + public onObjectHit(object: ClientObject, amount: TRate, direction: Vector, who: Optional): void { + if (who?.id() !== ACTOR_ID) { + return; + } + const activeActorItem: Optional = registry.actor.active_item(); if (activeActorItem) { @@ -239,9 +286,14 @@ export class StatisticsManager extends AbstractCoreManager { /** * Handle monster kill event and update stats. * - * @param object - object killed by an actor + * @param object - object killed + * @param who - object killer */ - public onMonsterKilledByActor(object: ClientObject): void { + public onMonsterKilled(object: ClientObject, who: Optional): void { + if (who?.id() !== ACTOR_ID) { + return; + } + let community: Optional = this.monsterClassesMap[object.clsid()] as Optional; assert( @@ -305,7 +357,7 @@ export class StatisticsManager extends AbstractCoreManager { public override load(reader: NetProcessor): void { this.actorStatistics = {} as IActorStatistics; this.actorStatistics.surgesCount = reader.r_u16(); - this.actorStatistics.completedQuestsCount = reader.r_u16(); + this.actorStatistics.completedTasksCount = reader.r_u16(); this.actorStatistics.killedMonstersCount = reader.r_u32(); this.actorStatistics.killedStalkersCount = reader.r_u32(); this.actorStatistics.collectedTreasuresCount = reader.r_u16(); @@ -355,7 +407,7 @@ export class StatisticsManager extends AbstractCoreManager { public override save(packet: NetPacket): void { packet.w_u16(this.actorStatistics.surgesCount); - packet.w_u16(this.actorStatistics.completedQuestsCount); + packet.w_u16(this.actorStatistics.completedTasksCount); packet.w_u32(this.actorStatistics.killedMonstersCount); packet.w_u32(this.actorStatistics.killedStalkersCount); packet.w_u16(this.actorStatistics.collectedTreasuresCount); @@ -368,18 +420,18 @@ export class StatisticsManager extends AbstractCoreManager { packet.w_u8(weaponsCount); - for (const [k, v] of this.weaponsStatistics) { - packet.w_stringZ(tostring(k)); - packet.w_float(v); + for (const [section, damageDone] of this.weaponsStatistics) { + packet.w_stringZ(tostring(section)); + packet.w_float(damageDone); } const artefactsCount: TCount = getTableSize(this.actorStatistics.collectedArtefacts); packet.w_u8(artefactsCount); - for (const [k, v] of this.actorStatistics.collectedArtefacts) { - packet.w_stringZ(tostring(k)); - packet.w_bool(v === true); + for (const [section, isCollected] of this.actorStatistics.collectedArtefacts) { + packet.w_stringZ(tostring(section)); + packet.w_bool(isCollected); } const takenArtefactsCount: TCount = getTableSize(this.takenArtefacts); diff --git a/src/engine/core/managers/interface/statistics/statistics_types.ts b/src/engine/core/managers/interface/statistics/statistics_types.ts index 5c876f054..353267b29 100644 --- a/src/engine/core/managers/interface/statistics/statistics_types.ts +++ b/src/engine/core/managers/interface/statistics/statistics_types.ts @@ -7,7 +7,7 @@ import { Optional, TCount, TName } from "@/engine/lib/types"; */ export interface IActorStatistics { surgesCount: TCount; - completedQuestsCount: TCount; + completedTasksCount: TCount; killedMonstersCount: TCount; killedStalkersCount: TCount; favoriteWeapon: Optional; diff --git a/src/engine/core/managers/world/SurgeManager.ts b/src/engine/core/managers/world/SurgeManager.ts index 924714a9b..d69e207cd 100644 --- a/src/engine/core/managers/world/SurgeManager.ts +++ b/src/engine/core/managers/world/SurgeManager.ts @@ -15,7 +15,6 @@ import { SimulationBoardManager } from "@/engine/core/managers/interaction/Simul import { TaskManager } from "@/engine/core/managers/interaction/tasks"; import { ActorInputManager } from "@/engine/core/managers/interface"; import { MapDisplayManager } from "@/engine/core/managers/interface/MapDisplayManager"; -import { StatisticsManager } from "@/engine/core/managers/interface/statistics/StatisticsManager"; import { GlobalSoundManager } from "@/engine/core/managers/sounds/GlobalSoundManager"; import { WeatherManager } from "@/engine/core/managers/world/WeatherManager"; import { AnomalyZoneBinder, SmartTerrain } from "@/engine/core/objects"; @@ -390,8 +389,7 @@ export class SurgeManager extends AbstractCoreManager { this.respawnArtefactsAndReplaceAnomalyZones(); - StatisticsManager.getInstance().onSurgePassed(); - EventsManager.getInstance().emitEvent(EGameEvent.SURGE_SKIPPED, !this.isSkipMessageToggled); + EventsManager.emitEvent(EGameEvent.SURGE_SKIPPED, !this.isSkipMessageToggled); this.isSkipMessageToggled = true; } @@ -450,8 +448,7 @@ export class SurgeManager extends AbstractCoreManager { this.respawnArtefactsAndReplaceAnomalyZones(); - StatisticsManager.getInstance().onSurgePassed(); - EventsManager.getInstance().emitEvent(EGameEvent.SURGE_ENDED); + EventsManager.emitEvent(EGameEvent.SURGE_ENDED); } /** @@ -531,7 +528,7 @@ export class SurgeManager extends AbstractCoreManager { if (registry.actor.alive()) { if (!coverObject?.inside(registry.actor.position())) { if (hasAlifeInfo(infoPortions.anabiotic_in_process)) { - StatisticsManager.getInstance().onAnabioticUsed(); + EventsManager.emitEvent(EGameEvent.SURGE_SURVIVED_WITH_ANABIOTIC); } ActorInputManager.getInstance().disableGameUiOnly(registry.actor); @@ -874,8 +871,6 @@ export class SurgeManager extends AbstractCoreManager { } object.get_artefact().FollowByPath("NULL", 0, createVector(500, 500, 500)); - - StatisticsManager.getInstance().onArtefactCollected(object); } } diff --git a/src/engine/core/managers/world/TreasureManager.ts b/src/engine/core/managers/world/TreasureManager.ts index 8c82b26ac..08ac22674 100644 --- a/src/engine/core/managers/world/TreasureManager.ts +++ b/src/engine/core/managers/world/TreasureManager.ts @@ -11,7 +11,6 @@ import { import { AbstractCoreManager } from "@/engine/core/managers/base/AbstractCoreManager"; import { EGameEvent, EventsManager } from "@/engine/core/managers/events"; import { ETreasureState, NotificationManager } from "@/engine/core/managers/interface/notifications"; -import { StatisticsManager } from "@/engine/core/managers/interface/statistics/StatisticsManager"; import { assert, assertDefined } from "@/engine/core/utils/assertion"; import { ISpawnDescriptor, @@ -366,7 +365,7 @@ export class TreasureManager extends AbstractCoreManager { if (section === TRUE && !treasureDescriptor.checked) { level.map_remove_object_spot(this.secretsRestrictorByName.get(treasureId), "treasure"); - StatisticsManager.getInstance().onTreasureFound(treasureDescriptor); + EventsManager.emitEvent(EGameEvent.TREASURE_FOUND, treasureDescriptor); treasureDescriptor.empty = null; treasureDescriptor.checked = true; @@ -409,13 +408,13 @@ export class TreasureManager extends AbstractCoreManager { if (treasureId) { logger.info("Treasure item taken:", objectId); - const secret: ITreasureDescriptor = this.secrets.get(treasureId); + const treasureDescriptor: ITreasureDescriptor = this.secrets.get(treasureId); - secret.itemsToFindRemain -= 1; + treasureDescriptor.itemsToFindRemain -= 1; if (this.secrets.get(treasureId).itemsToFindRemain === 0) { level.map_remove_object_spot(this.secretsRestrictorByName.get(treasureId), "treasure"); - StatisticsManager.getInstance().onTreasureFound(secret); + EventsManager.emitEvent(EGameEvent.TREASURE_FOUND, treasureDescriptor); this.secrets.get(treasureId).checked = true; NotificationManager.getInstance().sendTreasureNotification(ETreasureState.FOUND_TREASURE); diff --git a/src/engine/core/objects/binders/creature/MonsterBinder.ts b/src/engine/core/objects/binders/creature/MonsterBinder.ts index fe0e91dcb..49d5b4622 100644 --- a/src/engine/core/objects/binders/creature/MonsterBinder.ts +++ b/src/engine/core/objects/binders/creature/MonsterBinder.ts @@ -14,7 +14,7 @@ import { saveObjectLogic, unregisterObject, } from "@/engine/core/database"; -import { StatisticsManager } from "@/engine/core/managers/interface/statistics/StatisticsManager"; +import { EGameEvent, EventsManager } from "@/engine/core/managers"; import { GlobalSoundManager } from "@/engine/core/managers/sounds/GlobalSoundManager"; import { setupSmartJobsAndLogicOnSpawn } from "@/engine/core/objects/server/smart_terrain/jobs_general"; import { SmartTerrain } from "@/engine/core/objects/server/smart_terrain/SmartTerrain"; @@ -272,10 +272,6 @@ export class MonsterBinder extends object_binder { this.onHit(victim, 1, createEmptyVector(), killer, "from_death_callback"); - if (killer.id() === ACTOR_ID) { - StatisticsManager.getInstance().onMonsterKilledByActor(this.object); - } - if (this.state[EScheme.MOB_DEATH]) { emitSchemeEvent(this.object, this.state[EScheme.MOB_DEATH], ESchemeEvent.DEATH, victim, killer); } @@ -306,6 +302,8 @@ export class MonsterBinder extends object_binder { alife().release(targetServerObject, true); } } + + EventsManager.emitEvent(EGameEvent.MONSTER_KILLED, this.object, killer); } /** @@ -318,13 +316,21 @@ export class MonsterBinder extends object_binder { who: ClientObject, boneIndex: TLabel | TIndex ): void { - if (who.id() === ACTOR_ID) { - StatisticsManager.getInstance().onObjectHitByActor(amount, this.object); - } - if (this.state[EScheme.HIT]) { emitSchemeEvent(this.object, this.state.hit, ESchemeEvent.HIT, object, amount, direction, who, boneIndex); } + + EventsManager.emitEvent( + EGameEvent.MONSTER_HIT, + this.object, + this.state.hit, + ESchemeEvent.HIT, + object, + amount, + direction, + who, + boneIndex + ); } /** diff --git a/src/engine/core/objects/binders/creature/StalkerBinder.ts b/src/engine/core/objects/binders/creature/StalkerBinder.ts index 725b57a7d..517ad13d0 100644 --- a/src/engine/core/objects/binders/creature/StalkerBinder.ts +++ b/src/engine/core/objects/binders/creature/StalkerBinder.ts @@ -36,7 +36,6 @@ import { DialogManager } from "@/engine/core/managers/interaction/dialog/DialogM import { SimulationBoardManager } from "@/engine/core/managers/interaction/SimulationBoardManager"; import { TradeManager } from "@/engine/core/managers/interaction/TradeManager"; import { MapDisplayManager } from "@/engine/core/managers/interface/MapDisplayManager"; -import { StatisticsManager } from "@/engine/core/managers/interface/statistics/StatisticsManager"; import { GlobalSoundManager } from "@/engine/core/managers/sounds/GlobalSoundManager"; import { DropManager } from "@/engine/core/managers/world/DropManager"; import { ReleaseBodyManager } from "@/engine/core/managers/world/ReleaseBodyManager"; @@ -417,10 +416,6 @@ export class StalkerBinder extends object_binder { MapDisplayManager.getInstance().removeObjectMapSpot(npc, state); - if (who?.id() === ACTOR_ID) { - StatisticsManager.getInstance().onStalkerKilledByActor(this.object); - } - const knownInfo: Optional = readIniString(state.ini!, state.sectionLogic, "known_info", false, "", null); this.initializeInfoPortions(state.ini!, knownInfo); @@ -459,6 +454,7 @@ export class StalkerBinder extends object_binder { } } + EventsManager.emitEvent(EGameEvent.STALKER_KILLED, this.object, who); ReleaseBodyManager.getInstance().addDeadBody(this.object); } @@ -469,7 +465,7 @@ export class StalkerBinder extends object_binder { logger.info("Stalker use:", this.object.name(), "by", who.name()); if (this.object.alive()) { - EventsManager.getInstance().emitEvent(EGameEvent.NPC_INTERACTION, object, who); + EventsManager.emitEvent(EGameEvent.STALKER_INTERACTION, object, who); DialogManager.getInstance().resetForObject(this.object); SchemeMeet.onMeetWithObject(object); @@ -497,7 +493,7 @@ export class StalkerBinder extends object_binder { public onHit( object: ClientObject, amount: TRate, - localDirection: Vector, + direction: Vector, who: Optional, boneIndex: string | number ): void { @@ -505,7 +501,6 @@ export class StalkerBinder extends object_binder { // -- FIXME: �������� ������� ���� �� �������������� � ����� storage, � �� ��������... if (who?.id() === ACTOR_ID) { - StatisticsManager.getInstance().onObjectHitByActor(amount, this.object); if (amount > 0) { for (const [, descriptor] of SimulationBoardManager.getInstance().getSmartTerrainDescriptors()) { const smartTerrain: SmartTerrain = descriptor.smartTerrain; @@ -531,7 +526,7 @@ export class StalkerBinder extends object_binder { ESchemeEvent.HIT, object, amount, - localDirection, + direction, who, boneIndex ); @@ -545,18 +540,18 @@ export class StalkerBinder extends object_binder { ESchemeEvent.HIT, object, amount, - localDirection, + direction, who, boneIndex ); } if (this.state.combat) { - emitSchemeEvent(this.object, this.state.combat, ESchemeEvent.HIT, object, amount, localDirection, who, boneIndex); + emitSchemeEvent(this.object, this.state.combat, ESchemeEvent.HIT, object, amount, direction, who, boneIndex); } if (this.state.hit) { - emitSchemeEvent(this.object, this.state.hit, ESchemeEvent.HIT, object, amount, localDirection, who, boneIndex); + emitSchemeEvent(this.object, this.state.hit, ESchemeEvent.HIT, object, amount, direction, who, boneIndex); } if (boneIndex !== 15 && amount > this.object.health * 100) { @@ -566,6 +561,8 @@ export class StalkerBinder extends object_binder { if (amount > 0) { SchemeWounded.onHit(object.id()); } + + EventsManager.emitEvent(EGameEvent.STALKER_HIT, this.object, amount, direction, who, boneIndex); } } diff --git a/src/engine/core/objects/sounds/playable_sounds/ActorSound.ts b/src/engine/core/objects/sounds/playable_sounds/ActorSound.ts index af965c1f3..2e10ea7b0 100644 --- a/src/engine/core/objects/sounds/playable_sounds/ActorSound.ts +++ b/src/engine/core/objects/sounds/playable_sounds/ActorSound.ts @@ -124,7 +124,7 @@ export class ActorSound extends AbstractPlayableSound { this.soundObject.volume = 0.8; this.canPlaySound = false; - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.SOUND, faction, point, diff --git a/src/engine/core/objects/sounds/playable_sounds/NpcSound.ts b/src/engine/core/objects/sounds/playable_sounds/NpcSound.ts index 7bbb045f1..338dda562 100644 --- a/src/engine/core/objects/sounds/playable_sounds/NpcSound.ts +++ b/src/engine/core/objects/sounds/playable_sounds/NpcSound.ts @@ -312,7 +312,7 @@ export class NpcSound extends AbstractPlayableSound { } } - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.SOUND, object, faction, @@ -322,7 +322,7 @@ export class NpcSound extends AbstractPlayableSound { delay: this.delay, }); } else { - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.SOUND, object, faction, diff --git a/src/engine/core/objects/sounds/playable_sounds/ObjectSound.ts b/src/engine/core/objects/sounds/playable_sounds/ObjectSound.ts index 941dc1e87..37fb7d67a 100644 --- a/src/engine/core/objects/sounds/playable_sounds/ObjectSound.ts +++ b/src/engine/core/objects/sounds/playable_sounds/ObjectSound.ts @@ -129,7 +129,7 @@ export class ObjectSound extends AbstractPlayableSound { this.soundObject = new sound_object(soundPath); this.soundObject.play_at_pos(object, object.position(), 0, sound_object.s3d); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.SOUND, faction, point, diff --git a/src/engine/core/ui/debug/sections/DebugTeleportSection.ts b/src/engine/core/ui/debug/sections/DebugTeleportSection.ts index 4d05db729..bdb990a66 100644 --- a/src/engine/core/ui/debug/sections/DebugTeleportSection.ts +++ b/src/engine/core/ui/debug/sections/DebugTeleportSection.ts @@ -159,6 +159,6 @@ export class DebugTeleportSection extends AbstractDebugSection { public closeMenu(): void { executeConsoleCommand(consoleCommands.main_menu, "off"); - EventsManager.getInstance().emitEvent(EGameEvent.MAIN_MENU_OFF); + EventsManager.emitEvent(EGameEvent.MAIN_MENU_OFF); } } diff --git a/src/engine/core/ui/menu/MainMenu.ts b/src/engine/core/ui/menu/MainMenu.ts index 25e859002..d4fa7baed 100644 --- a/src/engine/core/ui/menu/MainMenu.ts +++ b/src/engine/core/ui/menu/MainMenu.ts @@ -96,7 +96,7 @@ export class MainMenu extends CUIScriptWnd { this.initControls(); this.initCallBacks(); - EventsManager.getInstance().emitEvent(EGameEvent.MAIN_MENU_ON); + EventsManager.emitEvent(EGameEvent.MAIN_MENU_ON); } /** @@ -169,7 +169,7 @@ export class MainMenu extends CUIScriptWnd { */ public close(): void { executeConsoleCommand(consoleCommands.main_menu, "off"); - EventsManager.getInstance().emitEvent(EGameEvent.MAIN_MENU_OFF); + EventsManager.emitEvent(EGameEvent.MAIN_MENU_OFF); } /** diff --git a/src/engine/core/utils/object/object_task_reward.ts b/src/engine/core/utils/object/object_task_reward.ts index 9030b421c..e0ee40cf0 100644 --- a/src/engine/core/utils/object/object_task_reward.ts +++ b/src/engine/core/utils/object/object_task_reward.ts @@ -28,7 +28,7 @@ export function giveMoneyToActor(amount: TCount): void { registry.actor.give_money(amount); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.MONEY, direction: ENotificationDirection.IN, amount, @@ -46,7 +46,7 @@ export function transferMoneyFromActor(to: ClientObject, amount: TCount): void { registry.actor.transfer_money(amount, to); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.MONEY, direction: ENotificationDirection.OUT, amount, @@ -115,7 +115,7 @@ export function transferItemsFromActor(to: ClientObject, itemSection: TSection, count = count * boxSize; } - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.ITEM, direction: ENotificationDirection.OUT, itemSection, @@ -161,7 +161,7 @@ export function transferItemsToActor(from: ClientObject, itemSection: TSection, count = count * SYSTEM_INI.r_s32(itemSection, "box_size"); } - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.ITEM, direction: ENotificationDirection.IN, itemSection, @@ -178,7 +178,7 @@ export function transferItemsToActor(from: ClientObject, itemSection: TSection, export function giveItemsToActor(itemSection: TSection, count: TCount = 1): void { const itemsSpawned: TCount = spawnItemsForObject(registry.actor, itemSection, count); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.ITEM, direction: ENotificationDirection.IN, itemSection, @@ -198,7 +198,7 @@ export function takeItemFromActor(itemSection: TSection): void { alife().release(alife().object(inventoryItem.id()), true); - EventsManager.getInstance().emitEvent(EGameEvent.NOTIFICATION, { + EventsManager.emitEvent(EGameEvent.NOTIFICATION, { type: ENotificationType.ITEM, direction: ENotificationDirection.OUT, itemSection,