Skip to content

Commit

Permalink
Extended mocks. Added tests for object set/location utils. Removed un…
Browse files Browse the repository at this point in the history
…used utils.
  • Loading branch information
Neloreck committed Jul 7, 2023
1 parent 67b54a3 commit 0e76e4f
Show file tree
Hide file tree
Showing 14 changed files with 219 additions and 85 deletions.
6 changes: 3 additions & 3 deletions src/engine/core/objects/state/StalkerMoveManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { pickSectionFromCondList } from "@/engine/core/utils/ini/ini_config";
import { parseConditionsList } from "@/engine/core/utils/ini/ini_parse";
import { IWaypointData, TConditionList } from "@/engine/core/utils/ini/ini_types";
import { LuaLogger } from "@/engine/core/utils/logging";
import { isStalkerAtWaypoint } from "@/engine/core/utils/object";
import { isObjectAtWaypoint } from "@/engine/core/utils/object";
import { TRUE } from "@/engine/lib/constants/words";
import {
AnyCallable,
Expand Down Expand Up @@ -385,7 +385,7 @@ export class StalkerMoveManager {
*/
public isStandingOnTerminalWaypoint(): LuaMultiReturn<[boolean, Optional<TIndex>]> {
for (const idx of $range(0, this.patrolWalk!.count() - 1)) {
if (isStalkerAtWaypoint(this.object, this.patrolWalk!, idx) && this.patrolWalk!.terminal(idx)) {
if (isObjectAtWaypoint(this.object, this.patrolWalk!, idx) && this.patrolWalk!.terminal(idx)) {
return $multi(true, idx);
}
}
Expand Down Expand Up @@ -446,7 +446,7 @@ export class StalkerMoveManager {
}

if (this.lastIndex && this.patrolWalk!.terminal(this.lastIndex)) {
if (isStalkerAtWaypoint(this.object, this.patrolWalk!, this.lastIndex)) {
if (isObjectAtWaypoint(this.object, this.patrolWalk!, this.lastIndex)) {
this.onWaypoint(this.object, null, this.lastIndex);

return;
Expand Down
4 changes: 2 additions & 2 deletions src/engine/core/schemes/camper/actions/ActionCamperPatrol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { StalkerMoveManager } from "@/engine/core/objects/state/StalkerMoveManag
import { ICampPoint, ISchemeCamperState } from "@/engine/core/schemes/camper/ISchemeCamperState";
import { abort } from "@/engine/core/utils/assertion";
import { parseWaypointsData } from "@/engine/core/utils/ini";
import { isObjectFacingDanger, isStalkerAtWaypoint } from "@/engine/core/utils/object";
import { isObjectAtWaypoint, isObjectFacingDanger } from "@/engine/core/utils/object";
import { createVector } from "@/engine/core/utils/vector";
import { ClientObject, DangerObject, Optional, Vector } from "@/engine/lib/types";

Expand Down Expand Up @@ -523,7 +523,7 @@ export class ActionCamperPatrol extends action_base {

if (path !== null) {
for (const k of $range(0, path.count() - 1)) {
if (isStalkerAtWaypoint(this.object, new patrol(this.state.path_walk), k)) {
if (isObjectAtWaypoint(this.object, new patrol(this.state.path_walk), k)) {
for (const i of $range(0, 31)) {
if (path.flag(k, i)) {
this.state.wp_flag = i;
Expand Down
6 changes: 3 additions & 3 deletions src/engine/core/schemes/mob_walker/MobWalkerManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { AbstractSchemeManager } from "@/engine/core/schemes/base";
import { ISchemeMobWalkerState } from "@/engine/core/schemes/mob_walker/ISchemeMobWalkerState";
import { abort } from "@/engine/core/utils/assertion";
import { IWaypointData, parseWaypointsData, pickSectionFromCondList } from "@/engine/core/utils/ini";
import { isStalkerAtWaypoint } from "@/engine/core/utils/object";
import { isObjectAtWaypoint } from "@/engine/core/utils/object";
import { isMonsterScriptCaptured, scriptCaptureMonster, scriptCommandMonster } from "@/engine/core/utils/scheme";
import { copyVector } from "@/engine/core/utils/vector";
import { EMonsterState } from "@/engine/lib/constants/monsters";
Expand Down Expand Up @@ -117,7 +117,7 @@ export class MobWalkerManager extends AbstractSchemeManager<ISchemeMobWalkerStat
if (!this.object.action()) {
const patrolWalkCount = this.patrolWalk!.count();

if (patrolWalkCount === 1 && isStalkerAtWaypoint(this.object, this.patrolWalk!, 0)) {
if (patrolWalkCount === 1 && isObjectAtWaypoint(this.object, this.patrolWalk!, 0)) {
this.mobState = STATE_MOVING;
this.onWaypoint(this.object, null, this.lastIndex);
} else {
Expand Down Expand Up @@ -193,7 +193,7 @@ export class MobWalkerManager extends AbstractSchemeManager<ISchemeMobWalkerStat
} else {
const patrolWalkCount = this.patrolWalk!.count();

if (patrolWalkCount === 1 && isStalkerAtWaypoint(this.object, this.patrolWalk!, 0)) {
if (patrolWalkCount === 1 && isObjectAtWaypoint(this.object, this.patrolWalk!, 0)) {
this.ptWaitTime = 100_000_000;
} else {
this.ptWaitTime = DEFAULT_WAIT_TIME;
Expand Down
1 change: 0 additions & 1 deletion src/engine/core/utils/object/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ export * from "@/engine/core/utils/object/object_anomaly";
export * from "@/engine/core/utils/object/object_check";
export * from "@/engine/core/utils/object/object_danger";
export * from "@/engine/core/utils/object/object_find";
export * from "@/engine/core/utils/object/object_general";
export * from "@/engine/core/utils/object/object_get";
export * from "@/engine/core/utils/object/object_info_portion";
export * from "@/engine/core/utils/object/object_location";
Expand Down
24 changes: 0 additions & 24 deletions src/engine/core/utils/object/object_general.ts

This file was deleted.

111 changes: 108 additions & 3 deletions src/engine/core/utils/object/object_location.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
import { describe, expect, it, jest } from "@jest/globals";
import { alife, game_graph } from "xray16";
import { alife, game_graph, patrol, sound_object } from "xray16";

import {
areObjectsOnSameLevel,
getDistanceBetween,
getDistanceBetweenSqr,
getServerDistanceBetween,
isDistanceBetweenObjectsGreaterOrEqual,
isDistanceBetweenObjectsLessOrEqual,
isGameVertexFromLevel,
isObjectAtWaypoint,
isObjectInActorFrustum,
isObjectInSmartTerrain,
isObjectInZone,
isObjectOnLevel,
sendToNearestAccessibleVertex,
teleportActorWithEffects,
} from "@/engine/core/utils/object/object_location";
import { ClientObject, ServerObject } from "@/engine/lib/types";
import { ClientObject, ServerObject, Vector } from "@/engine/lib/types";
import { mockRegisteredActor } from "@/fixtures/engine";
import { mockClientGameObject, mockServerAlifeObject, mockServerAlifeSmartZone } from "@/fixtures/xray";
import { replaceFunctionMock } from "@/fixtures/utils";
import {
mockActorClientGameObject,
mockClientGameObject,
mockServerAlifeObject,
mockServerAlifeSmartZone,
patrols,
} from "@/fixtures/xray";
import { MockVector } from "@/fixtures/xray/mocks/vector.mock";

describe("object location utils", () => {
it("'isObjectInSmartTerrain' check object inside smart terrain", () => {
Expand Down Expand Up @@ -97,4 +112,94 @@ describe("object location utils", () => {
jest.spyOn(game_graph().vertex(501).game_point(), "distance_to").mockImplementation(() => 255);
expect(getServerDistanceBetween(second, mockServerAlifeObject())).toBe(255);
});

it("'getDistanceBetween' should correctly get distance for offline objects", () => {
const first: ClientObject = mockClientGameObject();
const second: ClientObject = mockClientGameObject();

expect(getDistanceBetween(first, second)).toBe(20);

jest.spyOn(first.position(), "distance_to").mockImplementation(() => 600);
expect(getDistanceBetween(first, second)).toBe(600);
});

it("'getDistanceBetweenSqr' should correctly get distance for offline objects", () => {
const first: ClientObject = mockClientGameObject();
const second: ClientObject = mockClientGameObject();

expect(getDistanceBetweenSqr(first, second)).toBe(400);

jest.spyOn(first.position(), "distance_to_sqr").mockImplementation(() => 1600);
expect(getDistanceBetweenSqr(first, second)).toBe(1600);
});

it("'sendToNearestAccessibleVertex' should correctly send object to nearest accesible vertex", () => {
const first: ClientObject = mockClientGameObject();

expect(sendToNearestAccessibleVertex(first, 150)).toBe(150);
expect(first.accessible).toHaveBeenCalled();
expect(first.set_dest_level_vertex_id).toHaveBeenCalledWith(150);

const second: ClientObject = mockClientGameObject({
accessible: jest.fn(() => false),
accessible_nearest: jest.fn(() => 14325),
});

expect(sendToNearestAccessibleVertex(second, 150)).toBe(14325);
expect(second.accessible).toHaveBeenCalled();
expect(second.accessible_nearest).toHaveBeenCalledWith({ x: 15, y: 14, z: 16 }, { x: 0, y: 0, z: 0 });
expect(second.set_dest_level_vertex_id).toHaveBeenCalledWith(14325);
});

it("'teleportActorWithEffects' should correctly teleport actor", () => {
const actor: ClientObject = mockActorClientGameObject();
const destination: Vector = MockVector.mock(15, 14, 16);
const direction: Vector = MockVector.mock(3, 5, 4);

teleportActorWithEffects(actor, destination, direction);

expect(actor.set_actor_position).toHaveBeenCalledWith(destination);
expect(actor.set_actor_direction).toHaveBeenCalledWith(-direction.getH());
});

it("'isGameVertexFromLevel' should correctly check level name", () => {
expect(isGameVertexFromLevel("pripyat", 50)).toBe(true);
expect(isGameVertexFromLevel("zaton", 51)).toBe(false);
});

it("'isObjectAtWaypoint' should correctly check whether object is at waypoint", () => {
const object: ClientObject = mockClientGameObject();

jest.spyOn(object.position(), "distance_to_sqr").mockImplementation(() => 0.131);

expect(isObjectAtWaypoint(object, new patrol("test-wp"), 1)).toBe(false);
expect(object.position().distance_to_sqr).toHaveBeenCalledWith(patrols["test-wp"].points[1].position);

jest.spyOn(object.position(), "distance_to_sqr").mockImplementation(() => 0.13);

expect(isObjectAtWaypoint(object, new patrol("test-wp"), 2)).toBe(true);
expect(object.position().distance_to_sqr).toHaveBeenCalledWith(patrols["test-wp"].points[2].position);
});

it("'isObjectInActorFrustum' should correctly check whether object is in actor frustum", () => {
const object: ClientObject = mockClientGameObject();

jest.spyOn(object, "position").mockImplementation(() => MockVector.mock(0.6, 0, 0.6));
expect(isObjectInActorFrustum(object)).toBe(true);

jest.spyOn(object, "position").mockImplementation(() => MockVector.mock(0.5, 0, 0.9));
expect(isObjectInActorFrustum(object)).toBe(true);

jest.spyOn(object, "position").mockImplementation(() => MockVector.mock(0.5, 1, 0.9));
expect(isObjectInActorFrustum(object)).toBe(false);

jest.spyOn(object, "position").mockImplementation(() => MockVector.mock(0.4, 0, 0.9));
expect(isObjectInActorFrustum(object)).toBe(false);

jest.spyOn(object, "position").mockImplementation(() => MockVector.mock(-0.6, 0, -0.6));
expect(isObjectInActorFrustum(object)).toBe(false);

jest.spyOn(object, "position").mockImplementation(() => MockVector.mock(0, 0, 0));
expect(isObjectInActorFrustum(object)).toBe(false);
});
});
92 changes: 54 additions & 38 deletions src/engine/core/utils/object/object_location.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { alife, CSightParams, device, game_graph, level, move, sound_object } from "xray16";
import { alife, device, game_graph, level, 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, yawDegree3d } from "@/engine/core/utils/vector";
import { 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 {
Expand Down Expand Up @@ -103,21 +103,47 @@ export function areObjectsOnSameLevel(first: ServerObject, second: ServerObject)
}

/**
* Get absolute distance for objects based on game graphs.
* Get distance for objects based on game graphs.
* Approximately calculates distance for servers that are offline and may be on different levels.
*
* todo: Use table memo for storing distance between different static vertexes.
*
* @param first - object to check
* @param second - object to check
* @returns game distance between two objects
* @returns graph distance between two objects
*/
export function getServerDistanceBetween(first: ServerObject, second: ServerObject): TDistance {
return graphDistance(first.m_game_vertex_id, second.m_game_vertex_id);
}

/**
* todo: description
* Get distance for objects based on game vectors.
*
* @param first - object to check
* @param second - object to check
* @returns vector distance between two objects
*/
export function getDistanceBetween(first: ClientObject, second: ClientObject): TDistance {
return first.position().distance_to(second.position());
}

/**
* Get squared distance for objects based on game vectors.
*
* @param first - object to check
* @param second - object to check
* @returns squared vector distance between two objects
*/
export function getDistanceBetweenSqr(first: ClientObject, second: ClientObject): TDistance {
return first.position().distance_to_sqr(second.position());
}

/**
* Send object to desired vertex or nearest accessible one.
*
* @param object - target object to send
* @param vertexId - destination vertex id
* @returns actual vertex id to send object
*/
export function sendToNearestAccessibleVertex(object: ClientObject, vertexId: TNumberId): TNumberId {
if (!object.accessible(vertexId)) {
Expand All @@ -130,8 +156,10 @@ export function sendToNearestAccessibleVertex(object: ClientObject, vertexId: TN
}

/**
todo: Description
todo: Be more generic to object, do not rely on 'npc' part.
* Check whether object is actor frustum.
*
* @param object - target object to check
* @returns whether object is in visibility frustum
*/
export function isObjectInActorFrustum(object: ClientObject): boolean {
const actorDirection: Vector = device().cam_dir;
Expand All @@ -141,49 +169,37 @@ export function isObjectInActorFrustum(object: ClientObject): boolean {
}

/**
* todo;
* Check whether object reached patrol point with specific index.
*
* @param object - target object to check
* @param patrolPath - target patrol to check
* @param patrolPointIndex - index of patrol to check
* @returns whether object reached patrol point
*/
export function isStalkerAtWaypoint(object: ClientObject, patrolPath: Patrol, pathPointIndex: TIndex): boolean {
export function isObjectAtWaypoint(object: ClientObject, patrolPath: Patrol, patrolPointIndex: TIndex): boolean {
const objectPosition: Vector = object.position();
const distance: TDistance = objectPosition.distance_to_sqr(patrolPath.point(pathPointIndex));
const distance: TDistance = objectPosition.distance_to_sqr(patrolPath.point(patrolPointIndex));

return distance <= 0.13;
}

/**
* todo;
*/
export function stalkerStopMovement(object: ClientObject): void {
object.set_movement_type(move.stand);
}

/**
* todo;
*/
export function stalkerLookAtStalker(object: ClientObject, objectToLook: ClientObject): void {
const lookPoint: Vector = copyVector(objectToLook.position().sub(object.position()));

object.set_sight(CSightParams.eSightTypeDirection, lookPoint, 0);
}

/**
* todo;
*/
export function isGameVertexFromLevel(targetLevelName: TName, gameVertexId: TNumberId): boolean {
return targetLevelName === alife().level_name(game_graph().vertex(gameVertexId).level_id());
}

/**
* todo
* todo
* Check whether provided vertex ID is from level.
*
* @param levelName - target level to expect
* @param gameVertexId - game vertex id to check level
* @returns whether gameVertexId is part of level with name `levelName`
*/
export function getDistanceBetweenObjects(first: ClientObject, second: ClientObject): TDistance {
return first.position().distance_to(second.position());
export function isGameVertexFromLevel(levelName: TName, gameVertexId: TNumberId): boolean {
return levelName === alife().level_name(game_graph().vertex(gameVertexId).level_id());
}

/**
* Teleport actor to a specified point/direction with corresponding teleportation sound.
* todo;
*
* @param actor - client actor object to teleport
* @param position - vector destination
* @param direction - vector direction
*/
export function teleportActorWithEffects(actor: ClientObject, position: Vector, direction: Vector): void {
logger.info("Teleporting actor:", vectorToString(position));
Expand Down
Loading

0 comments on commit 0e76e4f

Please sign in to comment.