Skip to content

Commit

Permalink
[CoC] Integrating features/APIs from Call Of Chornobyl merge into Ope…
Browse files Browse the repository at this point in the history
…nXRay (#108)

Signed-off-by: Neloreck <[email protected]>
  • Loading branch information
Neloreck authored Dec 20, 2024
1 parent b90a2fe commit d892bf5
Show file tree
Hide file tree
Showing 36 changed files with 709 additions and 121 deletions.
2 changes: 1 addition & 1 deletion cli/bin
Submodule bin updated 60 files
+17 −17 engines/gold/License.txt
+24 −13 engines/gold/README.md
+ engines/gold/bin/LuaJIT.dll
+1 −1 engines/gold/bin/bin.json
+ engines/gold/bin/xrAICore.dll
+ engines/gold/bin/xrCDB.dll
+ engines/gold/bin/xrCore.dll
+ engines/gold/bin/xrEngine.dll
+ engines/gold/bin/xrEngine.exe
+ engines/gold/bin/xrGame.dll
+ engines/gold/bin/xrMaterialSystem.dll
+ engines/gold/bin/xrNetServer.dll
+ engines/gold/bin/xrParticles.dll
+ engines/gold/bin/xrRender_GL.dll
+ engines/gold/bin/xrRender_R4.dll
+ engines/gold/bin/xrScriptEngine.dll
+ engines/gold/bin/xrSound.dll
+ engines/gold/bin/xrUICore.dll
+17 −17 engines/mixed/License.txt
+24 −13 engines/mixed/README.md
+ engines/mixed/bin/LuaJIT.dll
+ engines/mixed/bin/OPCODE.dll
+1 −1 engines/mixed/bin/bin.json
+ engines/mixed/bin/xrAICore.dll
+ engines/mixed/bin/xrCDB.dll
+ engines/mixed/bin/xrCore.dll
+ engines/mixed/bin/xrEngine.dll
+ engines/mixed/bin/xrEngine.exe
+ engines/mixed/bin/xrGame.dll
+ engines/mixed/bin/xrGameSpy.dll
+ engines/mixed/bin/xrMaterialSystem.dll
+ engines/mixed/bin/xrNetServer.dll
+ engines/mixed/bin/xrParticles.dll
+ engines/mixed/bin/xrPhysics.dll
+ engines/mixed/bin/xrRender_GL.dll
+ engines/mixed/bin/xrRender_R4.dll
+ engines/mixed/bin/xrScriptEngine.dll
+ engines/mixed/bin/xrSound.dll
+ engines/mixed/bin/xrUICore.dll
+17 −17 engines/release/License.txt
+24 −13 engines/release/README.md
+ engines/release/bin/LuaJIT.dll
+ engines/release/bin/OPCODE.dll
+1 −1 engines/release/bin/bin.json
+ engines/release/bin/xrAICore.dll
+ engines/release/bin/xrCDB.dll
+ engines/release/bin/xrCore.dll
+ engines/release/bin/xrEngine.dll
+ engines/release/bin/xrEngine.exe
+ engines/release/bin/xrGame.dll
+ engines/release/bin/xrGameSpy.dll
+ engines/release/bin/xrMaterialSystem.dll
+ engines/release/bin/xrNetServer.dll
+ engines/release/bin/xrParticles.dll
+ engines/release/bin/xrPhysics.dll
+ engines/release/bin/xrRender_GL.dll
+ engines/release/bin/xrRender_R4.dll
+ engines/release/bin/xrScriptEngine.dll
+ engines/release/bin/xrSound.dll
+ engines/release/bin/xrUICore.dll
55 changes: 29 additions & 26 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@
"help": "ts-node -P ./cli/tsconfig.json cli/run.ts -h"
},
"dependencies": {
"xray16": "1.1.2"
"xray16": "1.2.2"
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"@types/ini": "^4.1.1",
"@types/jsdom": "^21.1.7",
"@types/node": "^22.0.2",
"@types/node": "^22.10.2",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@typescript-to-lua/language-extensions": "^1.19.0",
Expand Down Expand Up @@ -55,8 +55,8 @@
"ts-jest": "^29.2.3",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.6.2",
"typescript-to-lua": "^1.27.1"
"typescript": "^5.7.2",
"typescript-to-lua": "^1.28.1"
},
"bin": {
"xrf": "./cli/run.ts"
Expand Down
38 changes: 38 additions & 0 deletions src/engine/core/ai/combat/combat_visibility_calculation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { beforeEach, describe, expect, it } from "@jest/globals";

import { calculateObjectVisibility } from "@/engine/core/ai/combat/combat_visibility_calculation";
import { weatherConfig } from "@/engine/core/managers/weather/WeatherConfig";
import { MockGameObject } from "@/fixtures/xray";

describe("calculateObjectVisibility", () => {
beforeEach(() => {
weatherConfig.IS_UNDERGROUND_WEATHER = false;
});

it("should correctly calculate outside values with negative distance / luminosity", () => {
expect(calculateObjectVisibility(MockGameObject.mock(), MockGameObject.mock(), 100, 10, -1, 5, 7, -1, 5, 10)).toBe(
-1799.9964000000002
);
expect(
calculateObjectVisibility(MockGameObject.mock(), MockGameObject.mock(), 100, 10, 0.00001, 5, 7, 0.00001, 5, 10)
).toBe(-1799.9964000000002);
});

it("should correctly calculate outside and inside", () => {
weatherConfig.IS_UNDERGROUND_WEATHER = false;
expect(
calculateObjectVisibility(MockGameObject.mock(), MockGameObject.mock(), 100, 10, 1, 5, 7, 150, 100, 10)
).toBe(120);
expect(
calculateObjectVisibility(MockGameObject.mock(), MockGameObject.mock(), 25.5, 5.5, 10.5, 8.5, 4.5, 2.5, 1.5, 0.5)
).toBe(764.3045454545455);

weatherConfig.IS_UNDERGROUND_WEATHER = true;
expect(
calculateObjectVisibility(MockGameObject.mock(), MockGameObject.mock(), 100, 10, 1, 5, 7, 150, 100, 10)
).toBe(42);
expect(
calculateObjectVisibility(MockGameObject.mock(), MockGameObject.mock(), 25.5, 5.5, 10.5, 8.5, 4.5, 2.5, 1.5, 0.5)
).toBe(267.50659090909096);
});
});
46 changes: 46 additions & 0 deletions src/engine/core/ai/combat/combat_visibility_calculation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { weatherConfig } from "@/engine/core/managers/weather/WeatherConfig";
import { GameObject, Optional, TDistance, TDuration, TRate } from "@/engine/lib/types";

/**
* If value >= visiblity_threshold then object is considered visible.
* `visibility_threshold` is configured in LTX files for each monster / stalker separately.
*
* @param object - target object checking visibility
* @param target - target checking visibility for from perspective of object
* @param timeDelta - ?
* @param timeQuantity - ?
* @param luminosity - level of brightness outside
* @param velocityFactor - ?
* @param velocity - ?
* @param distance - ?
* @param objectDistance - ?
* @param alwaysVisibleDistance -?
* @returns visibility rate
*/
export function calculateObjectVisibility(
object: Optional<GameObject>,
target: Optional<GameObject>,
timeDelta: TDuration,
timeQuantity: TDuration,
luminosity: TRate,
velocityFactor: TRate,
velocity: TRate,
distance: TDistance,
objectDistance: TDistance,
alwaysVisibleDistance: TDistance
): TRate {
luminosity = luminosity <= 0 ? 0.00001 : luminosity;
distance = distance <= 0 ? 0.00001 : distance;

if (weatherConfig.IS_UNDERGROUND_WEATHER) {
luminosity *= 0.35;
}

// Unaltered formula from engine:
// time_delta / time_quant * luminocity * (1 + velocity_factor*velocity) * (distance - object_distance) /
// (distance - always_visible_distance)

return (
((timeDelta / timeQuantity) * luminosity * (1 + velocityFactor * velocity) * (distance - objectDistance)) / distance
);
}
139 changes: 139 additions & 0 deletions src/engine/core/ai/combat/combat_weapon_select.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { beforeEach, describe, expect, it } from "@jest/globals";
import { clsid } from "xray16";

import { selectBestStalkerWeapon } from "@/engine/core/ai/combat/combat_weapon_select";
import { getManager, registerSimulator } from "@/engine/core/database";
import { EGameEvent, EventsManager } from "@/engine/core/managers/events";
import { AnyObject, GameObject } from "@/engine/lib/types";
import { resetRegistry } from "@/fixtures/engine";
import { MockAlifeObject, MockGameObject } from "@/fixtures/xray";

describe("selectBestStalkerWeapon util", () => {
beforeEach(() => {
resetRegistry();
registerSimulator();
});

it("should fallback to null if no handlers found", () => {
expect(selectBestStalkerWeapon(MockGameObject.mock(), MockGameObject.mock())).toBeNull();
expect(selectBestStalkerWeapon(MockGameObject.mock(), null)).toBeNull();
});

it("should handle exceptional cases from callback handlers without throwing", () => {
const eventsManager: EventsManager = getManager(EventsManager);

eventsManager.registerCallback(
EGameEvent.STALKER_WEAPON_SELECT,
(_: GameObject, __: GameObject, data: AnyObject) => {
data.weaponId = true;
}
);

expect(selectBestStalkerWeapon(MockGameObject.mock(), MockGameObject.mock())).toBeNull();

eventsManager.registerCallback(
EGameEvent.STALKER_WEAPON_SELECT,
(_: GameObject, __: GameObject, data: AnyObject) => {
data.weaponId = "test-string";
}
);

expect(selectBestStalkerWeapon(MockGameObject.mock(), MockGameObject.mock())).toBeNull();

eventsManager.registerCallback(
EGameEvent.STALKER_WEAPON_SELECT,
(_: GameObject, __: GameObject, data: AnyObject) => {
data.weaponId = {};
}
);

expect(selectBestStalkerWeapon(MockGameObject.mock(), MockGameObject.mock())).toBeNull();
});

it("should use weapon from latest event handler", () => {
const object: GameObject = MockGameObject.mock();
const weapon: GameObject = MockGameObject.mock();

const firstBestWeapon: GameObject = MockGameObject.mock();
const secondBestWeapon: GameObject = MockGameObject.mock();

MockAlifeObject.mock({
id: firstBestWeapon.id(),
parentId: object.id(),
clsid: clsid.wpn_svd,
});

MockAlifeObject.mock({
id: secondBestWeapon.id(),
parentId: object.id(),
clsid: clsid.wpn_ak74,
});

const eventsManager: EventsManager = getManager(EventsManager);

eventsManager.registerCallback(
EGameEvent.STALKER_WEAPON_SELECT,
(_: GameObject, __: GameObject, data: AnyObject) => {
expect(data).toEqual({ weaponId: null });
data.weaponId = firstBestWeapon.id();
}
);

eventsManager.registerCallback(
EGameEvent.STALKER_WEAPON_SELECT,
(_: GameObject, __: GameObject, data: AnyObject) => {
expect(data).toEqual({ weaponId: firstBestWeapon.id() });
data.weaponId = secondBestWeapon.id();
}
);

expect(selectBestStalkerWeapon(object, weapon)).toBe(secondBestWeapon);
});

it("should require weapon ownership to use it", () => {
const object: GameObject = MockGameObject.mock();
const weapon: GameObject = MockGameObject.mock();

const bestWeapon: GameObject = MockGameObject.mock();

MockAlifeObject.mock({
id: bestWeapon.id(),
clsid: clsid.wpn_svd,
});

const eventsManager: EventsManager = getManager(EventsManager);

eventsManager.registerCallback(
EGameEvent.STALKER_WEAPON_SELECT,
(_: GameObject, __: GameObject, data: AnyObject) => {
data.weaponId = bestWeapon.id();
}
);

expect(selectBestStalkerWeapon(object, weapon)).toBeNull();
});

it("should require weapon cls id to use it", () => {
const object: GameObject = MockGameObject.mock();
const weapon: GameObject = MockGameObject.mock();

const bestWeapon: GameObject = MockGameObject.mock();

MockAlifeObject.mock({
id: bestWeapon.id(),
parentId: object.id(),
clsid: clsid.obj_food,
});

const eventsManager: EventsManager = getManager(EventsManager);

eventsManager.registerCallback(
EGameEvent.STALKER_WEAPON_SELECT,
(_: GameObject, __: GameObject, data: AnyObject) => {
data.weaponId = bestWeapon.id();
}
);

expect(selectBestStalkerWeapon(object, weapon)).toBeNull();
});
});
Loading

0 comments on commit d892bf5

Please sign in to comment.