From 079bf9e7d3bd9c7b0f4480929b47a6d3656c366e Mon Sep 17 00:00:00 2001 From: Neloreck Date: Fri, 27 Dec 2024 03:08:31 +0200 Subject: [PATCH] Add `getTableKeys`/`getTableValuesAsSet` utils. Removed unused data from level configs. Corrected level configs schema / added typing in ltx. Add new `simulation_group_id` field for levels. Add `initializeLevelSimulationGroupIds` util. Removed hardcoded `GROUP_ID_BY_LEVEL_NAME` and `VALID_SMART_TERRAINS_SIMULATION_ROLES` constants. Smart terrain role validation test. More ltx mocks added. Signed-off-by: Neloreck --- src/engine/configs/$scheme/game.scheme.ltx | 13 ++ src/engine/configs/game/game_maps_single.ltx | 26 ++- .../managers/simulation/simulation.ltx | 11 -- .../managers/simulation/SimulationConfig.ts | 37 ++--- .../simulation/SimulationManager.test.ts | 11 ++ .../core/managers/simulation/utils/index.ts | 1 + .../simulation/utils/simulation_ini.test.ts | 149 ++++++++++++++++++ .../simulation/utils/simulation_ini.ts | 59 +++++++ .../simulation/utils/simulation_squads.ts | 4 +- .../smart_terrain/SmartTerrain.test.ts | 26 +++ .../objects/smart_terrain/SmartTerrain.ts | 6 +- src/engine/core/utils/table.test.ts | 25 ++- src/engine/core/utils/table.ts | 36 ++++- .../ui_actor_multiplayer_menu_screen.xml | 3 - src/engine/lib/constants/levels.ts | 12 ++ src/fixtures/engine/mocks/table.mock.ts | 26 +++ src/fixtures/xray/mocks/ini/files.mock.ts | 4 +- .../ini/files/game_maps_single.ltx.mock.ts | 32 ++++ 18 files changed, 420 insertions(+), 61 deletions(-) create mode 100644 src/engine/core/managers/simulation/utils/simulation_ini.test.ts create mode 100644 src/engine/core/managers/simulation/utils/simulation_ini.ts create mode 100644 src/fixtures/xray/mocks/ini/files/game_maps_single.ltx.mock.ts diff --git a/src/engine/configs/$scheme/game.scheme.ltx b/src/engine/configs/$scheme/game.scheme.ltx index 39c5b1bc2..6147485c3 100644 --- a/src/engine/configs/$scheme/game.scheme.ltx +++ b/src/engine/configs/$scheme/game.scheme.ltx @@ -5,3 +5,16 @@ caption = string offset = vector weathers = string id = u32 + +[$level_single] +strict = true +global_rect = tuple:f32,f32,f32,f32 +music_tracks = string +weathers = string +simulation_group_id = u8 + +[$map] +strict = true +bound_rect = tuple:f32,f32,f32,f32 +texture = string +max_zoom = ?f32 diff --git a/src/engine/configs/game/game_maps_single.ltx b/src/engine/configs/game/game_maps_single.ltx index f59e45306..9c98d43e0 100644 --- a/src/engine/configs/game/game_maps_single.ltx +++ b/src/engine/configs/game/game_maps_single.ltx @@ -1,12 +1,10 @@ -[___level_music_tracks] -music\marsh_night = 0, 8, 0.25, 10, 20 -music\marsh_2 = 8, 24, 0.25, 5, 25 - [def_map] +$scheme = map bound_rect = -10000.0f, -10000.0f, 10000.0f, 10000.0f texture = ui\ui_nomap2 [global_map] +$scheme = map bound_rect = 0.0, 0.0, 1024, 1024.0 max_zoom = 6.0 texture = ui\ui_global_map @@ -18,37 +16,37 @@ jupiter_underground = pripyat = labx8 = -[stohe_selo] -global_rect = 0.0, 0.0, 100.0, 100.0 - -[plecha_selo] -global_rect = 100.0, 0.0, 200.0, 100.0 - -[peacemaker_selo] -global_rect = 200.0, 0.0, 300.0, 100.0 -music_tracks = zaton_musics - [zaton] +$scheme = $level_single global_rect = 307.0, 90.0, 717.0, 500.000000 music_tracks = zaton_musics weathers = atmosfear +simulation_group_id = 1 [jupiter] +$scheme = $level_single global_rect = 68.0, 563.0, 478.0, 973.000000 music_tracks = jupiter_musics weathers = atmosfear +simulation_group_id = 3 [jupiter_underground] +$scheme = $level_single global_rect = 570.0, 884.0, 571.0, 885.0 music_tracks = underground_musics weathers = indoor_ambient +simulation_group_id = 5 [pripyat] +$scheme = $level_single global_rect = 580.0, 564.0, 954.0, 938.000000 music_tracks = pripyat_musics weathers = atmosfear +simulation_group_id = 2 [labx8] +$scheme = $level_single global_rect = 746.0, 719.0, 747.0, 720.0 music_tracks = underground_musics weathers = indoor +simulation_group_id = 4 diff --git a/src/engine/configs/managers/simulation/simulation.ltx b/src/engine/configs/managers/simulation/simulation.ltx index 6ea214682..680175bae 100644 --- a/src/engine/configs/managers/simulation/simulation.ltx +++ b/src/engine/configs/managers/simulation/simulation.ltx @@ -221,17 +221,6 @@ pri_b36_monolith_guard_squad = pri_b36_smart_terrain [start_position_pripyat_a17] pri_a17_recon_squad = pri_a16 -[start_position_plecha_selo] -sim_stalker_squad_1 = sim_smart_1 -sim_stalker_squad_2 = sim_smart_2 -; sim_stalker_squad_4 = sim_smart_8 -; sim_snork_4 = sim_smart_6 -; sim_monolith_squad_3= sim_smart_base -; run_string bind_camfire.campfire_table_by_smart_names["sim_smart_5"][40]:turn_on() - -[start_position_stohe_selo] -; test_duty = smart_1 - [start_position_labx8] lx8_snork_down_squad = lx8_smart_terrain lx8_snork_up_squad = lx8_smart_terrain diff --git a/src/engine/core/managers/simulation/SimulationConfig.ts b/src/engine/core/managers/simulation/SimulationConfig.ts index c8cf99819..2522c429f 100644 --- a/src/engine/core/managers/simulation/SimulationConfig.ts +++ b/src/engine/core/managers/simulation/SimulationConfig.ts @@ -1,40 +1,18 @@ import { ini_file } from "xray16"; import { ESimulationTerrainRole, ISmartTerrainDescriptor } from "@/engine/core/managers/simulation/types"; +import { initializeLevelSimulationGroupIds } from "@/engine/core/managers/simulation/utils/simulation_ini"; import type { SmartTerrain } from "@/engine/core/objects/smart_terrain"; import type { Squad } from "@/engine/core/objects/squad"; -import { levels, TLevel } from "@/engine/lib/constants/levels"; +import { getTableValuesAsSet } from "@/engine/core/utils/table"; import type { IniFile, LuaArray, TName, TNumberId } from "@/engine/lib/types"; export const SIMULATION_LTX: IniFile = new ini_file("managers\\simulation\\simulation.ltx"); +export const GAME_MAPS_SINGLE_LTX: IniFile = new ini_file("game\\game_maps_single.ltx"); export const SIMULATION_OBJECTS_PROPERTIES_LTX: IniFile = new ini_file( "managers\\simulation\\simulation_objects_props.ltx" ); -/** - * todo: Remove / use strings / simplify and make more scalable - */ -export const GROUP_ID_BY_LEVEL_NAME: LuaTable = $fromObject({ - [levels.zaton]: 1, - [levels.pripyat]: 2, - [levels.jupiter]: 3, - [levels.labx8]: 4, - [levels.jupiter_underground]: 5, -}); - -/** - * Set of valid roles used for simulation terrains. - * Limiting options and validating configuration of game data. - */ -export const VALID_SMART_TERRAINS_SIMULATION_ROLES: LuaTable = $fromObject({ - [ESimulationTerrainRole.DEFAULT]: true, - [ESimulationTerrainRole.BASE]: true, - [ESimulationTerrainRole.SURGE]: true, - [ESimulationTerrainRole.RESOURCE]: true, - [ESimulationTerrainRole.TERRITORY]: true, - [ESimulationTerrainRole.LAIR]: true, -}); - export const simulationConfig = { ALIFE_DISTANCE_NEAR: 150, ALIFE_DAY_START_HOUR: 6, @@ -45,4 +23,13 @@ export const simulationConfig = { SQUADS: new LuaTable(), // Squads assigned to smart terrains before initialization of terrains. TEMPORARY_ASSIGNED_SQUADS: new LuaTable>(), + /** + * Unique identifiers to group simulation groups by level. + */ + GROUP_ID_BY_LEVEL_NAME: initializeLevelSimulationGroupIds(GAME_MAPS_SINGLE_LTX), + /** + * Set of valid roles used for simulation terrains. + * Limiting options and validating configuration of game data. + */ + VALID_SMART_TERRAINS_SIMULATION_ROLES: getTableValuesAsSet(ESimulationTerrainRole), }; diff --git a/src/engine/core/managers/simulation/SimulationManager.test.ts b/src/engine/core/managers/simulation/SimulationManager.test.ts index 9b78cf138..cb1b56779 100644 --- a/src/engine/core/managers/simulation/SimulationManager.test.ts +++ b/src/engine/core/managers/simulation/SimulationManager.test.ts @@ -25,6 +25,17 @@ describe("SimulationManager", () => { simulationConfig.IS_SIMULATION_INITIALIZED = false; }); + it("should have correct initial config", () => { + expect(simulationConfig.VALID_SMART_TERRAINS_SIMULATION_ROLES).toEqualLuaTables({ + base: true, + default: true, + lair: true, + resource: true, + surge: true, + territory: true, + }); + }); + it("should correctly initialize", () => { const eventsManager: EventsManager = getManager(EventsManager); diff --git a/src/engine/core/managers/simulation/utils/index.ts b/src/engine/core/managers/simulation/utils/index.ts index b828bc36e..6b251e31c 100644 --- a/src/engine/core/managers/simulation/utils/index.ts +++ b/src/engine/core/managers/simulation/utils/index.ts @@ -3,3 +3,4 @@ export * from "@/engine/core/managers/simulation/utils/simulation_initialization export * from "@/engine/core/managers/simulation/utils/simulation_priority"; export * from "@/engine/core/managers/simulation/utils/simulation_squads"; export * from "@/engine/core/managers/simulation/utils/simulation_terrains"; +export * from "@/engine/core/managers/simulation/utils/simulation_ini"; diff --git a/src/engine/core/managers/simulation/utils/simulation_ini.test.ts b/src/engine/core/managers/simulation/utils/simulation_ini.test.ts new file mode 100644 index 000000000..cd10e22bb --- /dev/null +++ b/src/engine/core/managers/simulation/utils/simulation_ini.test.ts @@ -0,0 +1,149 @@ +import { beforeEach, describe, expect, it } from "@jest/globals"; + +import { registerSimulator } from "@/engine/core/database"; +import { GAME_MAPS_SINGLE_LTX } from "@/engine/core/managers/simulation/SimulationConfig"; +import { initializeLevelSimulationGroupIds } from "@/engine/core/managers/simulation/utils/simulation_ini"; +import { destroySimulationData } from "@/engine/core/managers/simulation/utils/simulation_initialization"; +import { resetRegistry } from "@/fixtures/engine"; +import { MockIniFile } from "@/fixtures/xray"; + +describe("initializeLevelSimulationGroupIds util", () => { + beforeEach(() => { + resetRegistry(); + registerSimulator(); + destroySimulationData(); + }); + + it("should correctly initialize with default maps ltx", async () => { + expect( + initializeLevelSimulationGroupIds( + MockIniFile.mock("test.ltx", { + agroprom: 128, + agroprom_underground: 129, + darkvalley: 130, + escape: 131, + garbage: 132, + hospital: 133, + jupiter: 134, + jupiter_underground: 135, + labx8: 136, + limansk: 137, + marsh: 138, + military: 139, + pripyat: 140, + red_forest: 141, + stancia_2: 142, + yantar: 143, + zaton: 144, + }) + ) + ).toEqualLuaTables({ + agroprom: 128, + agroprom_underground: 129, + darkvalley: 130, + escape: 131, + garbage: 132, + hospital: 133, + jupiter: 134, + jupiter_underground: 135, + labx8: 136, + limansk: 137, + marsh: 138, + military: 139, + pripyat: 140, + red_forest: 141, + stancia_2: 142, + yantar: 143, + zaton: 144, + }); + + expect(initializeLevelSimulationGroupIds(GAME_MAPS_SINGLE_LTX)).toEqualLuaTables({ + agroprom: 128, + agroprom_underground: 129, + darkvalley: 130, + escape: 131, + garbage: 132, + hospital: 133, + jupiter: 3, + jupiter_underground: 5, + labx8: 4, + limansk: 134, + marsh: 135, + military: 136, + pripyat: 2, + red_forest: 137, + stancia_2: 138, + yantar: 139, + zaton: 1, + }); + }); + + it("should throw exception on IDs out of 1-255 range", () => { + expect(() => { + initializeLevelSimulationGroupIds( + MockIniFile.mock("test.ltx", { + agroprom: { + simulation_group_id: -100, + }, + }) + ); + }).toThrow("[JEST_TEST] Failed to assign level 'agroprom' group id '-100', it is not in range [1:255]."); + + expect(() => { + initializeLevelSimulationGroupIds( + MockIniFile.mock("test.ltx", { + agroprom: { + simulation_group_id: -1, + }, + }) + ); + }).toThrow("[JEST_TEST] Failed to assign level 'agroprom' group id '-1', it is not in range [1:255]."); + + expect(() => { + initializeLevelSimulationGroupIds( + MockIniFile.mock("test.ltx", { + agroprom: { + simulation_group_id: 0, + }, + }) + ); + }).toThrow("[JEST_TEST] Failed to assign level 'agroprom' group id '0', it is not in range [1:255]."); + + expect(() => { + initializeLevelSimulationGroupIds( + MockIniFile.mock("test.ltx", { + agroprom: { + simulation_group_id: 255, + }, + }) + ); + }).toThrow("[JEST_TEST] Failed to assign level 'agroprom' group id '255', it is not in range [1:255]."); + + expect(() => { + initializeLevelSimulationGroupIds( + MockIniFile.mock("test.ltx", { + agroprom: { + simulation_group_id: 1_000, + }, + }) + ); + }).toThrow("[JEST_TEST] Failed to assign level 'agroprom' group id '1000', it is not in range [1:255]."); + }); + + it("should throw exception on duplicate IDs", () => { + expect(() => { + initializeLevelSimulationGroupIds( + MockIniFile.mock("test.ltx", { + agroprom: { + simulation_group_id: 10, + }, + agroprom_underground: { + simulation_group_id: 10, + }, + }) + ); + }).toThrow( + "[JEST_TEST] Found duplicate group id '10' usage for level 'agroprom_underground', 'agroprom' already using it" + ); + }); +}); diff --git a/src/engine/core/managers/simulation/utils/simulation_ini.ts b/src/engine/core/managers/simulation/utils/simulation_ini.ts new file mode 100644 index 000000000..1e6135c44 --- /dev/null +++ b/src/engine/core/managers/simulation/utils/simulation_ini.ts @@ -0,0 +1,59 @@ +import { assert } from "@/engine/core/utils/assertion"; +import { readIniNumber } from "@/engine/core/utils/ini"; +import { LuaLogger } from "@/engine/core/utils/logging"; +import { getTableKeys } from "@/engine/core/utils/table"; +import { levels, TLevel } from "@/engine/lib/constants/levels"; +import { MAX_U8 } from "@/engine/lib/constants/memory"; +import { IniFile, LuaArray, Optional, TName, TNumberId } from "@/engine/lib/types"; + +const log: LuaLogger = new LuaLogger($filename); + +/** + * Assign simulation group IDs for levels based on data from ini file. + * Try to assign placeholder IDs if group is not defined in ini file. + * + * @param ini - target ini file to read data from + * @returns map of simulation group IDs by level name + */ +export function initializeLevelSimulationGroupIds(ini: IniFile): LuaTable { + log.info("Initialize level simulation group IDs from: '%s', '%s' levels", ini.fname(), table.size(levels)); + + const availableLevels: LuaArray = getTableKeys($fromObject(levels)); + const usedGroupIds: LuaTable = new LuaTable(); + const groupIdsByLevels: LuaTable = new LuaTable(); + + let freeGroupId: TNumberId = 127; + + // Sort to make things more deterministic in terms of keys iteration / assigning ordered IDs. + table.sort(availableLevels, (left, right) => left < right); + + for (const [, level] of availableLevels) { + const simulationGroupId: Optional = readIniNumber(ini, level, "simulation_group_id"); + const nextId: TNumberId = typeof simulationGroupId === "number" ? simulationGroupId : ++freeGroupId; + + // Maintain u8 boundaries / overflow. + assert( + nextId > 0 && nextId < MAX_U8, + "[%s] Failed to assign level '%s' group id '%s', it is not in range [1:%s].", + $filename, + level, + simulationGroupId, + MAX_U8 + ); + + // Avoid duplicates of simulation IDs. + assert( + !usedGroupIds.has(nextId), + "[%s] Found duplicate group id '%s' usage for level '%s', '%s' already using it", + $filename, + nextId, + level, + usedGroupIds.get(nextId) + ); + + usedGroupIds.set(nextId, level); + groupIdsByLevels.set(level, nextId); + } + + return groupIdsByLevels; +} diff --git a/src/engine/core/managers/simulation/utils/simulation_squads.ts b/src/engine/core/managers/simulation/utils/simulation_squads.ts index 3d4d51b17..240de9e5c 100644 --- a/src/engine/core/managers/simulation/utils/simulation_squads.ts +++ b/src/engine/core/managers/simulation/utils/simulation_squads.ts @@ -3,7 +3,7 @@ import { clsid, level, patrol } from "xray16"; import { registry, SYSTEM_INI } from "@/engine/core/database"; import { removeSquadMapSpot, updateSquadMapSpot } from "@/engine/core/managers/map/utils/map_spot_squad"; import { updateTerrainMapSpot } from "@/engine/core/managers/map/utils/map_spot_terrain"; -import { GROUP_ID_BY_LEVEL_NAME, simulationConfig } from "@/engine/core/managers/simulation/SimulationConfig"; +import { simulationConfig } from "@/engine/core/managers/simulation/SimulationConfig"; import { ISmartTerrainDescriptor } from "@/engine/core/managers/simulation/types"; import { getSimulationTerrainAssignedSquadsCount } from "@/engine/core/managers/simulation/utils/simulation_data"; import { SmartTerrain } from "@/engine/core/objects/smart_terrain"; @@ -204,7 +204,7 @@ export function releaseSimulationSquad(squad: Squad): void { */ export function setupSimulationObjectSquadAndGroup(object: ServerCreatureObject): void { const levelName: TLevel = level.name(); - const groupId: TNumberId = GROUP_ID_BY_LEVEL_NAME.get(levelName) || 0; + const groupId: TNumberId = simulationConfig.GROUP_ID_BY_LEVEL_NAME.get(levelName) ?? 0; // Reload, probably not needed. object = registry.simulator.object(object.id)!; diff --git a/src/engine/core/objects/smart_terrain/SmartTerrain.test.ts b/src/engine/core/objects/smart_terrain/SmartTerrain.test.ts index 6afff5330..5bc89c952 100644 --- a/src/engine/core/objects/smart_terrain/SmartTerrain.test.ts +++ b/src/engine/core/objects/smart_terrain/SmartTerrain.test.ts @@ -165,6 +165,32 @@ describe("SmartTerrain generic logic", () => { expect(terrain.isRegistered).toBe(false); }); + it("should fail on validate simulation roles", () => { + const correctTerrain: SmartTerrain = new SmartTerrain("test_correct_role"); + const incorrectTerrain: SmartTerrain = new SmartTerrain("test_incorrect_role"); + + jest.spyOn(correctTerrain, "spawn_ini").mockImplementation(() => { + return MockIniFile.mock("test.ltx", { + smart_terrain: { + sim_type: "territory", + }, + }); + }); + + jest.spyOn(incorrectTerrain, "spawn_ini").mockImplementation(() => { + return MockIniFile.mock("test.ltx", { + smart_terrain: { + sim_type: "unexpected", + }, + }); + }); + + expect(() => correctTerrain.initialize()).not.toThrow(); + expect(() => incorrectTerrain.initialize()).toThrow( + `Wrong simulation role value (sim_type) 'unexpected' in smart terrain '${incorrectTerrain.name()}' configuration.` + ); + }); + it("should correctly save and load data when have meaningful info", () => { const terrain: SmartTerrain = new SmartTerrain("test_init"); const processor: MockNetProcessor = new MockNetProcessor(); diff --git a/src/engine/core/objects/smart_terrain/SmartTerrain.ts b/src/engine/core/objects/smart_terrain/SmartTerrain.ts index 6d7e98088..5040a608c 100644 --- a/src/engine/core/objects/smart_terrain/SmartTerrain.ts +++ b/src/engine/core/objects/smart_terrain/SmartTerrain.ts @@ -26,7 +26,7 @@ import { import { EGameEvent, EventsManager } from "@/engine/core/managers/events"; import { updateTerrainMapSpot } from "@/engine/core/managers/map/utils"; import { simulationActivities } from "@/engine/core/managers/simulation/activity/simulation_activities"; -import { VALID_SMART_TERRAINS_SIMULATION_ROLES } from "@/engine/core/managers/simulation/SimulationConfig"; +import { simulationConfig } from "@/engine/core/managers/simulation/SimulationConfig"; import { ESimulationTerrainRole, ISimulationActivityDescriptor, @@ -545,9 +545,9 @@ export class SmartTerrain extends cse_alife_smart_zone implements ISimulationTar ) as ESimulationTerrainRole; // Check if role is defined in enum. - if (VALID_SMART_TERRAINS_SIMULATION_ROLES.get(this.simulationRole) === null) { + if (simulationConfig.VALID_SMART_TERRAINS_SIMULATION_ROLES.get(this.simulationRole) === null) { abort( - "Wrong simulation role value (sim_type) '%s' in smart terrain '%s'.", + "Wrong simulation role value (sim_type) '%s' in smart terrain '%s' configuration.", this.simulationRole, smartTerrainName ); diff --git a/src/engine/core/utils/table.test.ts b/src/engine/core/utils/table.test.ts index 26bcdc243..4679d367f 100644 --- a/src/engine/core/utils/table.test.ts +++ b/src/engine/core/utils/table.test.ts @@ -1,12 +1,16 @@ import { describe, expect, it, jest } from "@jest/globals"; -import { AnyObject, LuaArray, Optional } from "@/engine/lib/types"; +import { AnyObject, LuaArray, Optional, TName } from "@/engine/lib/types"; const tableUtils: { isEmpty: (target: Optional>) => boolean; resetTable: (target: LuaTable) => void; copyTable: (first: AnyObject, second: AnyObject) => AnyObject; mergeTables: (base: LuaTable, ...rest: Array>) => AnyObject; + getTableKeys: (target: LuaTable) => LuaArray; + getTableValuesAsSet: ( + target: LuaTable + ) => LuaTable; } = jest.requireActual("@/engine/core/utils/table"); describe("resetTable util", () => { @@ -68,3 +72,22 @@ describe("isEmpty util", () => { expect(tableUtils.isEmpty(null)).toBe(true); }); }); + +describe("getTableKeys util", () => { + it("should correctly return list of keys", () => { + expect(tableUtils.getTableKeys($fromObject({}))).toEqualLuaArrays([]); + expect(tableUtils.getTableKeys($fromObject({ a: 1, b: 2, c: 3 }))).toEqualLuaArrays(["a", "b", "c"]); + expect(tableUtils.getTableKeys($fromObject({ x: "1", [1]: "2" }))).toEqualLuaArrays([1, "x"]); + }); +}); + +describe("getTableValuesAsSet util", () => { + it("should correctly return list of keys", () => { + expect(tableUtils.getTableValuesAsSet($fromObject({}))).toEqualLuaTables({}); + expect(tableUtils.getTableValuesAsSet($fromObject({ a: 1, b: 2, c: "3" }))).toEqualLuaTables({ + [1]: true, + [2]: true, + "3": true, + }); + }); +}); diff --git a/src/engine/core/utils/table.ts b/src/engine/core/utils/table.ts index ff912f319..aeaf0f367 100644 --- a/src/engine/core/utils/table.ts +++ b/src/engine/core/utils/table.ts @@ -1,4 +1,4 @@ -import { Optional } from "@/engine/lib/types"; +import { AnyObject, LuaArray, Optional } from "@/engine/lib/types"; /** * Check if provided container is empty collection. @@ -72,3 +72,37 @@ export function resetTable(target: LuaTable): void { target.delete(k); } } + +/** + * Get list of table keys as new table. + * + * @param target - table to get list of keys from + * @returns list of table keys + */ +export function getTableKeys(target: LuaTable): LuaArray { + const keys: LuaArray = new LuaTable(); + + for (const [key] of target) { + table.insert(keys, key); + } + + return keys; +} + +/** + * Get table values as set object. + * + * @param target - table to create set from + * @returns set of target table values + */ +export function getTableValuesAsSet( + target: LuaTable | AnyObject +): LuaTable { + const set: LuaTable = new LuaTable(); + + for (const [, value] of target as LuaTable) { + set.set(value, true); + } + + return set; +} diff --git a/src/engine/forms/textures_descr/ui_actor_multiplayer_menu_screen.xml b/src/engine/forms/textures_descr/ui_actor_multiplayer_menu_screen.xml index adc161fe6..edcaf8af9 100644 --- a/src/engine/forms/textures_descr/ui_actor_multiplayer_menu_screen.xml +++ b/src/engine/forms/textures_descr/ui_actor_multiplayer_menu_screen.xml @@ -146,6 +146,3 @@ - - - diff --git a/src/engine/lib/constants/levels.ts b/src/engine/lib/constants/levels.ts index d61a354c3..66c647a4e 100644 --- a/src/engine/lib/constants/levels.ts +++ b/src/engine/lib/constants/levels.ts @@ -4,10 +4,22 @@ * List of available levels. */ export const levels = { + agroprom: "agroprom", + agroprom_underground: "agroprom_underground", + darkvalley: "darkvalley", + escape: "escape", + garbage: "garbage", + hospital: "hospital", jupiter: "jupiter", jupiter_underground: "jupiter_underground", labx8: "labx8", + limansk: "limansk", + marsh: "marsh", + military: "military", pripyat: "pripyat", + red_forest: "red_forest", + stancia_2: "stancia_2", + yantar: "yantar", zaton: "zaton", } as const; diff --git a/src/fixtures/engine/mocks/table.mock.ts b/src/fixtures/engine/mocks/table.mock.ts index 2405c44c8..8f0a55cfb 100644 --- a/src/fixtures/engine/mocks/table.mock.ts +++ b/src/fixtures/engine/mocks/table.mock.ts @@ -51,4 +51,30 @@ export const mockTableUtils = { return destination; }, + getTableKeys: (from: LuaTable | AnyObject) => { + if (from instanceof MockLuaTable) { + return MockLuaTable.fromArray([...from.keys()]); + } else { + return MockLuaTable.fromArray(Object.keys(from)); + } + }, + getTableValuesAsSet: (from: LuaTable | AnyObject) => { + if (from instanceof MockLuaTable) { + const result = MockLuaTable.mock(); + + for (const [, value] of from) { + result.set(value, true); + } + + return result; + } else { + const result = MockLuaTable.mock(); + + for (const value of Object.values(from)) { + result.set(value, true); + } + + return result; + } + }, }; diff --git a/src/fixtures/xray/mocks/ini/files.mock.ts b/src/fixtures/xray/mocks/ini/files.mock.ts index b98abdff6..836ee49fd 100644 --- a/src/fixtures/xray/mocks/ini/files.mock.ts +++ b/src/fixtures/xray/mocks/ini/files.mock.ts @@ -3,6 +3,7 @@ import { config as forgeConfig } from "@/engine/configs/forge"; import { AnyObject, TPath } from "@/engine/lib/types"; import { mockDialogManager } from "@/fixtures/xray/mocks/ini/files/dialog_manager.ltx.mock"; import { mockDropManager } from "@/fixtures/xray/mocks/ini/files/drop_manager.ltx.mock"; +import { mockGameMapsSingle } from "@/fixtures/xray/mocks/ini/files/game_maps_single.ltx.mock"; import { mockMapDisplayManager } from "@/fixtures/xray/mocks/ini/files/map_display_manager.ltx.mock"; import { mockBoxGeneric } from "@/fixtures/xray/mocks/ini/files/ph_box_generic.ltx.mock"; import { mockScriptSound } from "@/fixtures/xray/mocks/ini/files/script_sound.ltx.mock"; @@ -28,13 +29,14 @@ import { mockUpgradesManager } from "@/fixtures/xray/mocks/ini/files/upgrades_ma export const FILES_MOCKS: Record = { "alife.ltx": alifeConfig, "forge.ltx": forgeConfig, + "game\\game_maps_single.ltx": mockGameMapsSingle, "item_upgrades.ltx": mockUpgradesLtx, "managers\\box_manager.ltx": mockBoxGeneric, "managers\\dialog_manager.ltx": mockDialogManager, "managers\\drop_manager.ltx": mockDropManager, "managers\\map_display_manager.ltx": mockMapDisplayManager, - "managers\\simulation\\simulation_objects_props.ltx": mockSimulationObjectsProps, "managers\\simulation\\simulation.ltx": mockSimulation, + "managers\\simulation\\simulation_objects_props.ltx": mockSimulationObjectsProps, "managers\\simulation\\squad_behaviours.ltx": mockSquadBehaviours, "managers\\sounds\\script_sound.ltx": mockScriptSound, "managers\\sounds\\sound_stories.ltx": mockSoundStories, diff --git a/src/fixtures/xray/mocks/ini/files/game_maps_single.ltx.mock.ts b/src/fixtures/xray/mocks/ini/files/game_maps_single.ltx.mock.ts new file mode 100644 index 000000000..5d52f4a75 --- /dev/null +++ b/src/fixtures/xray/mocks/ini/files/game_maps_single.ltx.mock.ts @@ -0,0 +1,32 @@ +export const mockGameMapsSingle = { + zaton: { + global_rect: "307.0, 90.0, 717.0, 500.000000", + music_tracks: "zaton_musics", + weathers: "atmosfear", + simulation_group_id: 1, + }, + jupiter: { + global_rect: "570.0, 884.0, 571.0, 885.0", + music_tracks: "jupiter_musics", + weathers: "atmosfear", + simulation_group_id: 3, + }, + jupiter_underground: { + global_rect: "307.0, 90.0, 717.0, 500.000000", + music_tracks: "underground_musics", + weathers: "atmosfear", + simulation_group_id: 5, + }, + pripyat: { + global_rect: "580.0, 564.0, 954.0, 938.000000", + music_tracks: "pripyat_musics", + weathers: "atmosfear", + simulation_group_id: 2, + }, + labx8: { + global_rect: "746.0, 719.0, 747.0, 720.0", + music_tracks: "zaton_musics", + weathers: "underground_musics", + simulation_group_id: 4, + }, +};