Skip to content

Commit

Permalink
Small readme update. Update types submodule. Extended fixtures for tr…
Browse files Browse the repository at this point in the history
…acking. More unit tests for game utils.
  • Loading branch information
Neloreck committed Jul 5, 2023
1 parent f89f106 commit d29f266
Show file tree
Hide file tree
Showing 25 changed files with 474 additions and 194 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# 🎮 [Stalker XRF template](README.md)
# 🎮 [Stalker XRF](README.md)

![status](https://github.com/xray-forge/stalker-xrf-template/actions/workflows/build_and_test.yml/badge.svg)
[![language-ts](https://img.shields.io/badge/language-typescript-blue.svg?style=flat)](https://github.com/xray-forge/stalker-xrf-template/search?l=typescript)
[![types](https://img.shields.io/badge/docs-types-blue.svg?style=flat)](https://xray-forge.github.io/xray-16-types/index.html)
[![license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/Neloreck/dreamstate/blob/master/LICENSE)

<p>
XRF template for stalker mods and modded game packages. <br/>
Fully rewritten stalker script engine with typescript. <br/>
Template for stalker mods and modded game packages. <br/>
</p>

## 📦 In short
Expand All @@ -18,6 +19,7 @@ XRF template for stalker mods and modded game packages. <br/>
- Simple translations generation
- Game profiling / debugging tools
- Creation of custom modded game package
- Unit tests coverage

## 📍 Purpose

Expand Down
25 changes: 23 additions & 2 deletions src/engine/core/utils/object/object_location.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { alife, CSightParams, game_graph, level, move, sound_object } from "xray16";
import { alife, CSightParams, device, game_graph, level, move, sound_object } from "xray16";

import { registry } from "@/engine/core/database";
import { SmartTerrain } from "@/engine/core/objects";
import { LuaLogger } from "@/engine/core/utils/logging";
import { getObjectSmartTerrain } from "@/engine/core/utils/object/object_get";
import { copyVector, createEmptyVector, graphDistance, vectorToString } from "@/engine/core/utils/vector";
import { copyVector, createEmptyVector, graphDistance, vectorToString, yawDegree3d } from "@/engine/core/utils/vector";
import { logicsConfig } from "@/engine/lib/configs/LogicsConfig";
import { sounds } from "@/engine/lib/constants/sound/sounds";
import {
ClientObject,
Expand Down Expand Up @@ -127,6 +129,17 @@ export function sendToNearestAccessibleVertex(object: ClientObject, vertexId: TN
return vertexId;
}

/**
todo: Description
todo: Be more generic to object, do not rely on 'npc' part.
*/
export function isObjectInActorFrustum(object: ClientObject): boolean {
const actorDirection: Vector = device().cam_dir;
const objectDirection: Vector = object.position().sub(registry.actor.position());

return yawDegree3d(actorDirection, objectDirection) < logicsConfig.ACTOR_VISIBILITY_FRUSTUM;
}

/**
* todo;
*/
Expand Down Expand Up @@ -160,6 +173,14 @@ export function isGameVertexFromLevel(targetLevelName: TName, gameVertexId: TNum
return targetLevelName === alife().level_name(game_graph().vertex(gameVertexId).level_id());
}

/**
* todo
* todo
*/
export function getDistanceBetweenObjects(first: ClientObject, second: ClientObject): TDistance {
return first.position().distance_to(second.position());
}

/**
* Teleport actor to a specified point/direction with corresponding teleportation sound.
* todo;
Expand Down
11 changes: 5 additions & 6 deletions src/engine/core/utils/scheme/scheme_switch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ import { abort, assert } from "@/engine/core/utils/assertion";
import { pickSectionFromCondList } from "@/engine/core/utils/ini/ini_config";
import { LuaLogger } from "@/engine/core/utils/logging";
import { isActorSeenByObject } from "@/engine/core/utils/object/object_check";
import { isObjectInZone } from "@/engine/core/utils/object/object_location";
import { getDistanceBetweenObjects, isObjectInZone } from "@/engine/core/utils/object/object_location";
import { emitSchemeEvent } from "@/engine/core/utils/scheme/scheme_event";
import { activateSchemeBySection } from "@/engine/core/utils/scheme/scheme_logic";
import { getDistanceBetween } from "@/engine/core/utils/vector";
import { NIL } from "@/engine/lib/constants/words";
import {
ClientObject,
Expand Down Expand Up @@ -43,17 +42,17 @@ const SCHEME_LOGIC_SWITCH: Record<
[NIL]: () => abort("Error: 'scheme/switch': unknown condition encountered."),
[ESchemeCondition.ON_ACTOR_DISTANCE_LESS_THAN]: (actor, object, state, logic) =>
isActorSeenByObject(object) &&
getDistanceBetween(actor, object) <= (logic.v1 as TDistance) &&
getDistanceBetweenObjects(actor, object) <= (logic.v1 as TDistance) &&
switchObjectSchemeToSection(object, state.ini, pickSectionFromCondList(actor, object, logic.condlist)),
[ESchemeCondition.ON_ACTOR_DISTANCE_LESS_THAN_NOT_VISIBLE]: (actor, object, state, logic) =>
getDistanceBetween(actor, object) <= (logic.v1 as TDistance) &&
getDistanceBetweenObjects(actor, object) <= (logic.v1 as TDistance) &&
switchObjectSchemeToSection(object, state.ini, pickSectionFromCondList(actor, object, logic.condlist)),
[ESchemeCondition.ON_ACTOR_DISTANCE_GREATER_THAN]: (actor, object, state, logic) =>
isActorSeenByObject(object) &&
getDistanceBetween(actor, object) > (logic.v1 as TDistance) &&
getDistanceBetweenObjects(actor, object) > (logic.v1 as TDistance) &&
switchObjectSchemeToSection(object, state.ini, pickSectionFromCondList(actor, object, logic.condlist)),
[ESchemeCondition.ON_ACTOR_DISTANCE_GREATER_THAN_NOT_VISIBLE]: (actor, object, state, logic) =>
getDistanceBetween(actor, object) > (logic.v1 as TDistance) &&
getDistanceBetweenObjects(actor, object) > (logic.v1 as TDistance) &&
switchObjectSchemeToSection(object, state.ini, pickSectionFromCondList(actor, object, logic.condlist)),
[ESchemeCondition.ON_SIGNAL]: (actor, object, state, logic) =>
(state.signals?.get(logic.v1 as TName) &&
Expand Down
16 changes: 16 additions & 0 deletions src/engine/core/utils/string.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { describe, expect, it } from "@jest/globals";

import { trimString } from "@/engine/core/utils/string";

describe("'string' utils", () => {
it("'trimString' should correctly trim string values", () => {
expect(trimString("")).toBe("");
expect(trimString("abc")).toBe("abc");
expect(trimString("abc ")).toBe("abc");
expect(trimString(" abc")).toBe("abc");
expect(trimString(" abc ")).toBe("abc");
expect(trimString(" a b c ")).toBe("a b c");
expect(trimString("a b c ")).toBe("a b c");
expect(trimString(" a b c")).toBe("a b c");
});
});
31 changes: 3 additions & 28 deletions src/engine/core/utils/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,12 @@
* Trim provided string, remove spaces from start and end.
* todo: Description.
* todo: use from lua extensions string.trim
*
* @param target - string to trim
* @returns trimmed string
*/
export function trimString(target: string): string {
const [trimmed] = string.gsub(target, "^%s*(.-)%s*$", "%1");

return trimmed || "";
}

/**
* Explode string by provided separator char.
* todo: Description.
*/
export function explodeString(target: string, separator: string, trim: boolean): LuaTable<number, string> {
const result: LuaTable<number, string> = new LuaTable();
let [cpt] = string.find(target, separator, 1, true);

while (cpt !== null) {
if (trim) {
table.insert(result, trimString(string.sub(target, 1, cpt - 1)));
} else {
table.insert(result, string.sub(target, 1, cpt - 1));
}

target = string.sub(target, cpt + string.len(separator));
[cpt] = string.find(target, separator, 1, true);
}

if (trim) {
table.insert(result, trimString(target));
} else {
table.insert(result, target);
}

return result;
}
50 changes: 50 additions & 0 deletions src/engine/core/utils/table.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { describe, expect, it, jest } from "@jest/globals";

import { AnyObject, LuaArray, Optional } from "@/engine/lib/types";

describe("'table' utils", () => {
const tableUtils: {
isEmpty: (target: Optional<LuaTable<any>>) => boolean;
resetTable: (target: LuaTable<any>) => void;
getTableSize: (target: LuaTable<any>) => number;
copyTable: (first: AnyObject, second: AnyObject) => AnyObject;
} = jest.requireActual("@/engine/core/utils/table");

it("'resetTable' should correctly empty provided table", () => {
const first: LuaArray<number> = $fromArray([1, 2, 3, 4]);
const second: LuaTable<string, string> = $fromObject<string, string>({ a: "1", b: "2" });

tableUtils.resetTable(first);
tableUtils.resetTable(second);

expect(first).toEqualLuaArrays([]);
expect(second).toEqualLuaTables({});
});

it("'copyTable' should correctly copy table", () => {
const from: LuaTable<any> = new LuaTable();
const to: LuaTable<any> = $fromObject({ a: 1, b: 2, c: 3, d: { a: "a" } });

expect(tableUtils.copyTable(to, from)).toEqualLuaTables({ a: 1, b: 2, c: 3, d: { a: "a" } });
expect(to).toEqualLuaTables({ a: 1, b: 2, c: 3, d: { a: "a" } });
expect(to.get("d")).not.toBe(from.get("d"));
});

it("'getTableSize' should correctly get size of the table", () => {
expect(tableUtils.getTableSize($fromObject({ a: 1, b: 2 }))).toBe(2);
expect(tableUtils.getTableSize($fromArray(["a", "b", "c", "d", "e"]))).toBe(5);
expect(tableUtils.getTableSize(new LuaTable())).toBe(0);
expect(tableUtils.getTableSize($fromArray<unknown>([]))).toBe(0);
expect(tableUtils.getTableSize($fromObject<string, unknown>({}))).toBe(0);
});

it("'isEmpty' should correctly check table emptiness", () => {
expect(tableUtils.isEmpty($fromObject({ a: 1, b: 2 }))).toBe(false);
expect(tableUtils.isEmpty($fromArray(["a", "b", "c", "d", "e"]))).toBe(false);
expect(tableUtils.isEmpty($fromArray([1, 2, 3]))).toBe(false);
expect(tableUtils.isEmpty(new LuaTable())).toBe(true);
expect(tableUtils.isEmpty($fromArray<unknown>([]))).toBe(true);
expect(tableUtils.isEmpty($fromObject<string, unknown>({}))).toBe(true);
expect(tableUtils.isEmpty(null)).toBe(true);
});
});
40 changes: 27 additions & 13 deletions src/engine/core/utils/table.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,65 @@
import { assert } from "@/engine/core/utils/assertion";
import { LuaLogger } from "@/engine/core/utils/logging";
import { AnyObject, Optional, TCount } from "@/engine/lib/types";
import { Optional, TCount } from "@/engine/lib/types";

const logger: LuaLogger = new LuaLogger($filename);

/**
* Check if provided container is empty collection.
* Very lua-specific checks, do not apply TS logic here.
*
* @param target - object to check emptiness
* @returns whether target table is empty
*/
export function isEmpty(container: Optional<LuaTable<any>>): boolean {
if (container === null) {
export function isEmpty(target: Optional<LuaTable<any>>): boolean {
if (target === null) {
return true;
}

if (type(container) === "function") {
for (const [k, v] of container) {
if (type(target) === "function") {
for (const [k] of target) {
return false;
}

return true;
}

assert(type(container) === "table");
assert(type(target) === "table", "Received not table type for emptiness check.");

if ((container as AnyObject)[1] !== null) {
if (1 in target) {
return false;
}

for (const [k, v] of pairs(container)) {
for (const [k] of target) {
return false;
}

return true;
}

/**
* todo: description
* Get size of table content.
*
* @param target - table to check size
* @returns actual size of the table
*/
export function getTableSize(collection: LuaTable<any, any>): number {
export function getTableSize(target: LuaTable<any, any>): number {
let count: TCount = 0;

for (const [key, value] of collection) {
for (const [key, value] of target) {
count += 1;
}

return count;
}

/**
* todo: description
* Copy table values from one table to another.
* Tables are copied recursively.
*
* @param target - table to copy in
* @param source - table to copy from
* @returns target table with copied content
*/
export function copyTable<T extends Record<any, any>, D extends Record<any, any>>(target: T, source: D): T;
export function copyTable(
Expand All @@ -68,8 +80,10 @@ export function copyTable(

/**
* Reset table values in map-styled table.
*
* @param target - table to reset and empty
*/
export function resetTable(target: LuaTable<any, any>): void {
export function resetTable(target: LuaTable<any>): void {
for (const [k] of target) {
target.delete(k);
}
Expand Down
Loading

0 comments on commit d29f266

Please sign in to comment.