Skip to content

Commit

Permalink
Add getTableKeys/getTableValuesAsSet utils. Removed unused data f…
Browse files Browse the repository at this point in the history
…rom 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 <[email protected]>
  • Loading branch information
Neloreck committed Dec 27, 2024
1 parent c31730e commit 079bf9e
Show file tree
Hide file tree
Showing 18 changed files with 420 additions and 61 deletions.
13 changes: 13 additions & 0 deletions src/engine/configs/$scheme/game.scheme.ltx
Original file line number Diff line number Diff line change
Expand Up @@ -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
26 changes: 12 additions & 14 deletions src/engine/configs/game/game_maps_single.ltx
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
11 changes: 0 additions & 11 deletions src/engine/configs/managers/simulation/simulation.ltx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 12 additions & 25 deletions src/engine/core/managers/simulation/SimulationConfig.ts
Original file line number Diff line number Diff line change
@@ -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<TLevel, TNumberId> = $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<TName, boolean> = $fromObject<TName, boolean>({
[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,
Expand All @@ -45,4 +23,13 @@ export const simulationConfig = {
SQUADS: new LuaTable<TNumberId, Squad>(),
// Squads assigned to smart terrains before initialization of terrains.
TEMPORARY_ASSIGNED_SQUADS: new LuaTable<TNumberId, LuaArray<Squad>>(),
/**
* 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),
};
11 changes: 11 additions & 0 deletions src/engine/core/managers/simulation/SimulationManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
1 change: 1 addition & 0 deletions src/engine/core/managers/simulation/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
149 changes: 149 additions & 0 deletions src/engine/core/managers/simulation/utils/simulation_ini.test.ts
Original file line number Diff line number Diff line change
@@ -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"
);
});
});
59 changes: 59 additions & 0 deletions src/engine/core/managers/simulation/utils/simulation_ini.ts
Original file line number Diff line number Diff line change
@@ -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<TName, TNumberId> {
log.info("Initialize level simulation group IDs from: '%s', '%s' levels", ini.fname(), table.size(levels));

const availableLevels: LuaArray<TLevel> = getTableKeys($fromObject(levels));
const usedGroupIds: LuaTable<TNumberId, TName> = new LuaTable();
const groupIdsByLevels: LuaTable<TName, TNumberId> = 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<TNumberId> = 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;
}
Loading

0 comments on commit 079bf9e

Please sign in to comment.