diff --git a/cli/bin b/cli/bin index 337b2f3f6..f97ad0a97 160000 --- a/cli/bin +++ b/cli/bin @@ -1 +1 @@ -Subproject commit 337b2f3f60453c3d6edd657dc201c197506647dd +Subproject commit f97ad0a973e0ed326507d648f0b5ce18ce8e608b diff --git a/src/engine/core/objects/smart_terrain/SmartTerrain.test.ts b/src/engine/core/objects/smart_terrain/SmartTerrain.test.ts index 5bc89c952..2c745ab97 100644 --- a/src/engine/core/objects/smart_terrain/SmartTerrain.test.ts +++ b/src/engine/core/objects/smart_terrain/SmartTerrain.test.ts @@ -1,5 +1,5 @@ -import { beforeEach, describe, expect, it, jest } from "@jest/globals"; -import { time_global } from "xray16"; +import { beforeEach, describe, expect, fit, it, jest } from "@jest/globals"; +import { game, time_global } from "xray16"; import { getManager, registerActorServer, registerSimulator, registry } from "@/engine/core/database"; import { EGameEvent, EventsManager } from "@/engine/core/managers/events"; @@ -14,7 +14,7 @@ import { createObjectJobDescriptor } from "@/engine/core/objects/smart_terrain/j import { ESmartTerrainStatus } from "@/engine/core/objects/smart_terrain/smart_terrain_types"; import { parseConditionsList } from "@/engine/core/utils/ini"; import { TRUE } from "@/engine/lib/constants/words"; -import { IniFile, ServerHumanObject } from "@/engine/lib/types"; +import { GameObject, IniFile, ServerCreatureObject, ServerHumanObject, ServerObject } from "@/engine/lib/types"; import { MockSmartTerrain, resetRegistry } from "@/fixtures/engine"; import { replaceFunctionMock } from "@/fixtures/jest"; import { @@ -23,8 +23,10 @@ import { MockAlifeHumanStalker, MockCALifeSmartTerrainTask, MockCTime, + MockGameObject, MockIniFile, MockNetProcessor, + MockServerAlifeCreatureAbstract, } from "@/fixtures/xray"; describe("SmartTerrain generic logic", () => { @@ -423,6 +425,60 @@ describe("SmartTerrain generic logic", () => { expect(terrain.objectsToRegister.get(second.id)).toBe(second); }); + it("onObjectDeath should correctly handle dying of object assigned", () => { + const terrain: SmartTerrain = MockSmartTerrain.mockRegistered(); + const object: GameObject = MockGameObject.mock(); + const serverObject: ServerCreatureObject = MockServerAlifeCreatureAbstract.mock({ id: object.id() }); + + jest.spyOn(serverObject.position, "distance_to_sqr").mockImplementation(() => 100 * 100 - 1); + + terrain.register_npc(serverObject); + + expect(serverObject.m_smart_terrain_id).toBe(terrain.id); + expect(terrain.stayingObjectsCount).toBe(1); + expect(terrain.objectJobDescriptors.length()).toBe(1); + expect(terrain.jobDeadTimeById.length()).toBe(0); + expect(terrain.arrivingObjects.length()).toBe(0); + + terrain.onObjectDeath(serverObject); + + expect(terrain.objectJobDescriptors.length()).toBe(0); + expect(terrain.arrivingObjects.length()).toBe(0); + expect(terrain.jobDeadTimeById.length()).toBe(1); + expect(MockCTime.areEqual(terrain.jobDeadTimeById.get(1), game.get_game_time())).toBe(true); + expect(serverObject.clear_smart_terrain).toHaveBeenCalledTimes(1); + }); + + it("onObjectDeath should correctly handle dying of object arriving", () => { + const terrain: SmartTerrain = MockSmartTerrain.mockRegistered(); + const object: GameObject = MockGameObject.mock(); + const serverObject: ServerCreatureObject = MockServerAlifeCreatureAbstract.mock({ id: object.id() }); + + jest.spyOn(serverObject.position, "distance_to_sqr").mockImplementation(() => 100 * 100 + 1); + + terrain.register_npc(serverObject); + + expect(serverObject.m_smart_terrain_id).toBe(terrain.id); + expect(terrain.stayingObjectsCount).toBe(1); + expect(terrain.objectJobDescriptors.length()).toBe(0); + expect(terrain.arrivingObjects).toEqualLuaTables({ [serverObject.id]: serverObject }); + + terrain.onObjectDeath(serverObject); + + expect(terrain.arrivingObjects).toEqualLuaTables({}); + expect(serverObject.clear_smart_terrain).toHaveBeenCalledTimes(1); + }); + + it("onObjectDeath should correctly throw on unexpected conditions", () => { + const terrain: SmartTerrain = MockSmartTerrain.mock(); + const object: GameObject = MockGameObject.mock(); + const serverObject: ServerCreatureObject = MockServerAlifeCreatureAbstract.mock({ id: object.id() }); + + expect(() => terrain.onObjectDeath(serverObject)).toThrow( + `Smart terrain object is assigned, but not arriving or on job: '${serverObject.name()}', at '${terrain.name()}'.` + ); + }); + it.todo("register_npc should correctly count and assign objects after registration"); it.todo("should handle simulation callbacks"); diff --git a/src/engine/core/objects/smart_terrain/SmartTerrain.ts b/src/engine/core/objects/smart_terrain/SmartTerrain.ts index 5040a608c..c1403a115 100644 --- a/src/engine/core/objects/smart_terrain/SmartTerrain.ts +++ b/src/engine/core/objects/smart_terrain/SmartTerrain.ts @@ -681,30 +681,37 @@ export class SmartTerrain extends cse_alife_smart_zone implements ISimulationTar } /** - * todo: Description. + * Handle death of smart terrain assigned object. + * + * @param object - dying object assigned to terrain */ public onObjectDeath(object: ServerCreatureObject): void { - logger.info("Clear dead: %s %s", this.name(), object.name()); + logger.info("Clear assigned object on death: %s %s", this.name(), object.name()); + // Object arrived and aws assigned to job. if (this.objectJobDescriptors.get(object.id) as Optional) { this.jobDeadTimeById.set(this.objectJobDescriptors.get(object.id).jobId, game.get_game_time()); this.objectJobDescriptors.get(object.id).job!.objectId = null; this.objectJobDescriptors.delete(object.id); + object.clear_smart_terrain(); return; } + // Object was in process of arriving. if (this.arrivingObjects.get(object.id) as Optional) { this.arrivingObjects.delete(object.id); + object.clear_smart_terrain(); return; } - abort("this.npc_info[obj.id] = null !!! obj.id=%d", object.id); + abort("Smart terrain object is assigned, but not arriving or on job: '%s', at '%s'.", object.name(), this.name()); } + /** * todo: Description. */ diff --git a/src/engine/scripts/_g.ts b/src/engine/scripts/_g.ts index 28e799252..61563db40 100644 --- a/src/engine/scripts/_g.ts +++ b/src/engine/scripts/_g.ts @@ -1,14 +1,5 @@ -import { getFS } from "xray16"; - /** - * Enable modules requiring. - * Set root folder for lua scripts as $game_data filesystem path. - */ -// @ts-ignore lua globals package, conflicting with javascript strict mode internals. -package.path += getFS().update_path("$game_data$", "?.script;"); - -/** - * Initialize external references. + * Initialize external references (effects/callbacks/conditions). */ // eslint-disable-next-line @typescript-eslint/no-var-requires require("@/engine/scripts/register/externals_registrator").registerExternals(); diff --git a/src/fixtures/xray/mocks/objects/server/cse_alife_object.mock.ts b/src/fixtures/xray/mocks/objects/server/cse_alife_object.mock.ts index 5c064cd0d..610ae080b 100644 --- a/src/fixtures/xray/mocks/objects/server/cse_alife_object.mock.ts +++ b/src/fixtures/xray/mocks/objects/server/cse_alife_object.mock.ts @@ -153,6 +153,8 @@ export class MockAlifeObject extends MockLuabindClass { public update = jest.fn(); + public clear_smart_terrain = jest.fn(); + public STATE_Write(packet: NetPacket): void { packet.w_stringZ(`state_write_from_${this.constructor.name}`); }