Skip to content

Commit

Permalink
Extended mocks. Added relations mocks. Removed duplicated methods rel…
Browse files Browse the repository at this point in the history
…ated to relations checks. Relation checkers tests added. Added get/set relations testing placeholders.
  • Loading branch information
Neloreck committed Jun 22, 2023
1 parent 33f326a commit e8ae576
Show file tree
Hide file tree
Showing 23 changed files with 672 additions and 237 deletions.
4 changes: 2 additions & 2 deletions src/engine/core/database/story_objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export function registerObjectStoryLinks(serverObject: ServerObject): void {
const spawnIni: IniFile = serverObject.spawn_ini();

if (spawnIni.section_exist("story_object")) {
const [result, id, value] = spawnIni.r_line("story_object", 0, "", "");
const [, field, value] = spawnIni.r_line("story_object", 0, "", "");

assert(id === "story_id", "There is no 'story_id' field in [story_object] section [%s].", serverObject.name());
assert(field === "story_id", "There is no 'story_id' field in [story_object] section [%s].", serverObject.name());
assert(value !== "", "Field 'story_id' in [story_object] section got no value: [%s].", serverObject.name());

registerStoryLink(serverObject.id, value);
Expand Down
2 changes: 1 addition & 1 deletion src/engine/core/managers/debug/DebugManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { EStateActionId } from "@/engine/core/objects/state";
import { StalkerStateManager } from "@/engine/core/objects/state/StalkerStateManager";
import { EActionId } from "@/engine/core/schemes";
import { LuaLogger } from "@/engine/core/utils/logging";
import { getNumberRelationBetweenCommunities } from "@/engine/core/utils/relation/relation";
import { getNumberRelationBetweenCommunities } from "@/engine/core/utils/relation/get";
import { gameTimeToString } from "@/engine/core/utils/time";
import { toJSON } from "@/engine/core/utils/transform/json";
import { stalkerCommunities, TCommunity } from "@/engine/lib/constants/communities";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
import { StatisticsManager } from "@/engine/core/managers/interface/StatisticsManager";
import { giveInfo, hasAlifeInfo, hasAlifeInfos, hasFewAlifeInfos } from "@/engine/core/utils/info_portion";
import { LuaLogger } from "@/engine/core/utils/logging";
import { increaseNumberRelationBetweenCommunityAndId } from "@/engine/core/utils/relation/relation";
import { increaseNumberRelationBetweenCommunityAndId } from "@/engine/core/utils/relation";
import { spawnItemsForObjectFromList } from "@/engine/core/utils/spawn";
import { readTimeFromPacket, writeTimeToPacket } from "@/engine/core/utils/time";
import { captions } from "@/engine/lib/constants/captions/captions";
Expand Down
4 changes: 2 additions & 2 deletions src/engine/core/objects/server/squad/Squad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ import {
import { LuaLogger } from "@/engine/core/utils/logging";
import { areObjectsOnSameLevel } from "@/engine/core/utils/object/object_general";
import {
areCommunitiesEnemies,
ERelation,
getSquadMembersRelationToActor,
getSquadRelationToActor,
isFactionsEnemies,
setClientObjectsRelation,
setObjectSympathy,
setServerObjectsRelation,
Expand Down Expand Up @@ -1065,7 +1065,7 @@ export class Squad extends cse_alife_online_offline_group implements ISimulation
if (
targetSquad &&
this.position.distance_to_sqr(targetSquad.position) < 150 * 150 &&
isFactionsEnemies(currentCommunity, targetSquad.getCommunity())
areCommunitiesEnemies(currentCommunity, targetSquad.getCommunity())
) {
return enemySquadId;
}
Expand Down
2 changes: 1 addition & 1 deletion src/engine/core/schemes/meet/SchemeMeet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { pickSectionFromCondList } from "@/engine/core/utils/ini/config";
import { parseConditionsList } from "@/engine/core/utils/ini/parse";
import { readIniString } from "@/engine/core/utils/ini/read";
import { LuaLogger } from "@/engine/core/utils/logging";
import { getObjectsRelationSafe } from "@/engine/core/utils/relation/relation";
import { getObjectsRelationSafe } from "@/engine/core/utils/relation/get";
import { logicsConfig } from "@/engine/lib/configs/LogicsConfig";
import { FALSE, NIL, TRUE } from "@/engine/lib/constants/words";
import {
Expand Down
3 changes: 3 additions & 0 deletions src/engine/core/utils/object/object_general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,14 @@ export function getObjectPositioning(object: AnyGameObject): LuaMultiReturn<[TNu
export function getObjectSquad(object: Optional<ClientObject | ServerCreatureObject>): Optional<Squad> {
assertDefined(object, "Attempt to get squad object from null value.");

// Get for client object.
if (type(object.id) === "function") {
const serverObject: Optional<ServerCreatureObject> = alife().object((object as ClientObject).id());

return !serverObject || serverObject.group_id === MAX_U16 ? null : alife().object<Squad>(serverObject.group_id);
} else {
// Get for server object.

return (object as ServerCreatureObject).group_id === MAX_U16
? null
: alife().object<Squad>((object as ServerCreatureObject).group_id);
Expand Down
181 changes: 181 additions & 0 deletions src/engine/core/utils/relation/check.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { beforeEach, describe, expect, it } from "@jest/globals";

import { registerStoryLink, registry } from "@/engine/core/database";
import { Squad } from "@/engine/core/objects";
import {
areCommunitiesEnemies,
areCommunitiesFriendly,
isAnySquadMemberEnemyToActor,
isAnySquadMemberFriendToActor,
} from "@/engine/core/utils/relation/check";
import { getSquadCommunityRelationToActor } from "@/engine/core/utils/relation/get";
import { ERelation } from "@/engine/core/utils/relation/types";
import { communities } from "@/engine/lib/constants/communities";
import { ACTOR_ID } from "@/engine/lib/constants/ids";
import { ServerGroupObject, ServerHumanObject } from "@/engine/lib/types";
import {
MockAlifeOnlineOfflineGroup,
MockAlifeSimulator,
mockServerAlifeCreatureActor,
mockServerAlifeHumanStalker,
mockServerAlifeOnlineOfflineGroup,
} from "@/fixtures/xray";
import { mockCharactersGoodwill } from "@/fixtures/xray/mocks/relations";

describe("'relation/check' utils", () => {
beforeEach(() => {
registry.actor = null as any;
registry.storyLink.sidById = new LuaTable();
registry.storyLink.idBySid = new LuaTable();
MockAlifeSimulator.removeFromRegistry(ACTOR_ID);
});

it("'isSquadCommunityEnemyToActor' should correctly check relation", () => {
expect(() => getSquadCommunityRelationToActor("not-existing")).toThrow(
"Squad with story id 'not-existing' was not found."
);

mockServerAlifeCreatureActor({ community: () => communities.actor });

const enemy: ServerGroupObject = mockServerAlifeOnlineOfflineGroup();

(enemy as Squad).getCommunity = () => communities.monster;
registerStoryLink(enemy.id, "existing-enemy");

expect(getSquadCommunityRelationToActor("existing-enemy")).toBe(ERelation.ENEMY);
});

it("'isSquadCommunityFriendToActor' should correctly check relation", () => {
expect(() => getSquadCommunityRelationToActor("not-existing")).toThrow(
"Squad with story id 'not-existing' was not found."
);

mockServerAlifeCreatureActor({ community: () => communities.actor });

const friend: ServerGroupObject = mockServerAlifeOnlineOfflineGroup();

(friend as Squad).getCommunity = () => communities.army;
registerStoryLink(friend.id, "existing-friend");

expect(getSquadCommunityRelationToActor("existing-friend")).toBe(ERelation.FRIEND);
});

it("'isSquadCommunityNeutralToActor' should correctly check neutral relation", () => {
expect(() => getSquadCommunityRelationToActor("not-existing")).toThrow(
"Squad with story id 'not-existing' was not found."
);

mockServerAlifeCreatureActor({ community: () => communities.actor });

const neutral: ServerGroupObject = mockServerAlifeOnlineOfflineGroup();

(neutral as Squad).getCommunity = () => communities.bandit;
registerStoryLink(neutral.id, "existing-neutral");

expect(getSquadCommunityRelationToActor("existing-neutral")).toBe(ERelation.NEUTRAL);
});

it("'areCommunitiesFriendly' should correctly check communities friendly state", () => {
expect(areCommunitiesFriendly(communities.actor, communities.army)).toBe(true);
expect(areCommunitiesFriendly(communities.army, communities.actor)).toBe(true);
expect(areCommunitiesFriendly(communities.bandit, communities.bandit)).toBe(true);
expect(areCommunitiesFriendly(communities.monster, communities.monster)).toBe(true);
expect(areCommunitiesFriendly(communities.monster, communities.actor)).toBe(false);
expect(areCommunitiesFriendly(communities.actor, communities.monster)).toBe(false);
expect(areCommunitiesFriendly(communities.actor, communities.stalker)).toBe(false);
expect(areCommunitiesFriendly(communities.stalker, communities.actor)).toBe(false);
});

it("'areCommunitiesEnemies' should correctly check communities enemy state", () => {
expect(areCommunitiesEnemies(communities.actor, communities.monolith)).toBe(true);
expect(areCommunitiesEnemies(communities.monolith, communities.stalker)).toBe(true);
expect(areCommunitiesEnemies(communities.monster, communities.actor)).toBe(true);
expect(areCommunitiesEnemies(communities.actor, communities.monster)).toBe(true);
expect(areCommunitiesEnemies(communities.bandit, communities.bandit)).toBe(false);
expect(areCommunitiesEnemies(communities.monster, communities.monster)).toBe(false);
expect(areCommunitiesEnemies(communities.actor, communities.stalker)).toBe(false);
expect(areCommunitiesEnemies(communities.stalker, communities.actor)).toBe(false);
});

it("'isAnySquadMemberEnemyToActor' should correctly check relation of squad members", () => {
const emptyGroup: MockAlifeOnlineOfflineGroup = new MockAlifeOnlineOfflineGroup("test");
const friendlyGroup: MockAlifeOnlineOfflineGroup = new MockAlifeOnlineOfflineGroup("test");
const neutralGroup: MockAlifeOnlineOfflineGroup = new MockAlifeOnlineOfflineGroup("test");
const mixedGroup: MockAlifeOnlineOfflineGroup = new MockAlifeOnlineOfflineGroup("test");
const enemyGroup: MockAlifeOnlineOfflineGroup = new MockAlifeOnlineOfflineGroup("test");

const friend: ServerHumanObject = mockServerAlifeHumanStalker();
const enemy: ServerHumanObject = mockServerAlifeHumanStalker();
const neutral: ServerHumanObject = mockServerAlifeHumanStalker();
const almostEnemy: ServerHumanObject = mockServerAlifeHumanStalker();
const almostFriend: ServerHumanObject = mockServerAlifeHumanStalker();

mockCharactersGoodwill(friend.id, ACTOR_ID, 1000);
mockCharactersGoodwill(enemy.id, ACTOR_ID, -1000);
mockCharactersGoodwill(neutral.id, ACTOR_ID, 0);
mockCharactersGoodwill(almostEnemy.id, ACTOR_ID, -999);
mockCharactersGoodwill(almostFriend.id, ACTOR_ID, 999);

friendlyGroup.addSquadMember(friend);
friendlyGroup.addSquadMember(friend);
friendlyGroup.addSquadMember(friend);

neutralGroup.addSquadMember(neutral);
neutralGroup.addSquadMember(almostEnemy);
neutralGroup.addSquadMember(almostFriend);

enemyGroup.addSquadMember(enemy);
enemyGroup.addSquadMember(enemy);

mixedGroup.addSquadMember(friend);
mixedGroup.addSquadMember(neutral);
mixedGroup.addSquadMember(enemy);

expect(isAnySquadMemberEnemyToActor(emptyGroup.asSquad())).toBeFalsy();
expect(isAnySquadMemberEnemyToActor(neutralGroup.asSquad())).toBeFalsy();
expect(isAnySquadMemberEnemyToActor(friendlyGroup.asSquad())).toBeFalsy();
expect(isAnySquadMemberEnemyToActor(enemyGroup.asSquad())).toBeTruthy();
expect(isAnySquadMemberEnemyToActor(mixedGroup.asSquad())).toBeTruthy();
});

it("'isAnySquadMemberFriendToActor' should correctly check relation of squad members", () => {
const emptyGroup: MockAlifeOnlineOfflineGroup = new MockAlifeOnlineOfflineGroup("test");
const friendlyGroup: MockAlifeOnlineOfflineGroup = new MockAlifeOnlineOfflineGroup("test");
const neutralGroup: MockAlifeOnlineOfflineGroup = new MockAlifeOnlineOfflineGroup("test");
const mixedGroup: MockAlifeOnlineOfflineGroup = new MockAlifeOnlineOfflineGroup("test");
const enemyGroup: MockAlifeOnlineOfflineGroup = new MockAlifeOnlineOfflineGroup("test");

const friend: ServerHumanObject = mockServerAlifeHumanStalker();
const enemy: ServerHumanObject = mockServerAlifeHumanStalker();
const neutral: ServerHumanObject = mockServerAlifeHumanStalker();
const almostEnemy: ServerHumanObject = mockServerAlifeHumanStalker();
const almostFriend: ServerHumanObject = mockServerAlifeHumanStalker();

mockCharactersGoodwill(friend.id, ACTOR_ID, 1000);
mockCharactersGoodwill(enemy.id, ACTOR_ID, -1000);
mockCharactersGoodwill(neutral.id, ACTOR_ID, 0);
mockCharactersGoodwill(almostEnemy.id, ACTOR_ID, -999);
mockCharactersGoodwill(almostFriend.id, ACTOR_ID, 999);

friendlyGroup.addSquadMember(friend);
friendlyGroup.addSquadMember(friend);
friendlyGroup.addSquadMember(friend);

neutralGroup.addSquadMember(neutral);
neutralGroup.addSquadMember(almostEnemy);
neutralGroup.addSquadMember(almostFriend);

enemyGroup.addSquadMember(enemy);
enemyGroup.addSquadMember(enemy);

mixedGroup.addSquadMember(friend);
mixedGroup.addSquadMember(neutral);
mixedGroup.addSquadMember(enemy);

expect(isAnySquadMemberFriendToActor(emptyGroup.asSquad())).toBeFalsy();
expect(isAnySquadMemberFriendToActor(neutralGroup.asSquad())).toBeFalsy();
expect(isAnySquadMemberFriendToActor(enemyGroup.asSquad())).toBeFalsy();
expect(isAnySquadMemberFriendToActor(friendlyGroup.asSquad())).toBeTruthy();
expect(isAnySquadMemberFriendToActor(mixedGroup.asSquad())).toBeTruthy();
});
});
89 changes: 46 additions & 43 deletions src/engine/core/utils/relation/check.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,77 @@
import { relation_registry } from "xray16";

import { getServerObjectByStoryId, registry } from "@/engine/core/database";
import { Squad } from "@/engine/core/objects";
import { getSquadCommunityRelationToActor } from "@/engine/core/utils/relation/relation";
import { getSquadCommunityRelationToActor } from "@/engine/core/utils/relation/get";
import { EGoodwill, ERelation } from "@/engine/core/utils/relation/types";
import { communities, TCommunity } from "@/engine/lib/constants/communities";
import { ACTOR_ID } from "@/engine/lib/constants/ids";
import { ClientObject, Optional, TName, TStringId } from "@/engine/lib/types";
import { Optional, TStringId } from "@/engine/lib/types";

/**
* todo;
* Check whether squad is enemy to actor.
*
* @param squadStoryId - squad story id
* @returns whether actor is enemy to squad
*/
export function isSquadEnemyToActor(storyId: TStringId): boolean {
return getSquadCommunityRelationToActor(storyId) === ERelation.ENEMY;
export function isSquadCommunityEnemyToActor(squadStoryId: TStringId): boolean {
return getSquadCommunityRelationToActor(squadStoryId) === ERelation.ENEMY;
}

/**
* todo;
* Check whether squad is friend to actor.
*
* @param squadStoryId - squad story id
* @returns whether actor is friend to squad
*/
export function isSquadFriendToActor(storyId: TStringId): boolean {
return getSquadCommunityRelationToActor(storyId) === ERelation.FRIEND;
export function isSquadCommunityFriendToActor(squadStoryId: TStringId): boolean {
return getSquadCommunityRelationToActor(squadStoryId) === ERelation.FRIEND;
}

/**
* todo;
* Check whether squad is neutral to actor.
*
* @param squadStoryId - squad story id
* @returns whether actor is neutral to squad
*/
export function isSquadNeutralToActor(storyId: TName): boolean {
return getSquadCommunityRelationToActor(storyId) === ERelation.NEUTRAL;
export function isSquadCommunityNeutralToActor(squadStoryId: TStringId): boolean {
return getSquadCommunityRelationToActor(squadStoryId) === ERelation.NEUTRAL;
}

/**
* todo;
* Check general goodwill level between factions and assume whether they are friends.
*
* @param from - community relation check from
* @param to - community relation check to
* @returns whether faction `from` considers `to` friend
*/
export function isFactionsFriends(faction: Optional<TCommunity>, factionTo: TCommunity): boolean {
if (faction !== null && faction !== communities.none && factionTo !== communities.none) {
return relation_registry.community_relation(faction, factionTo) >= EGoodwill.FRIENDS;
export function areCommunitiesFriendly(from: Optional<TCommunity>, to: TCommunity): boolean {
if (from !== null && from !== communities.none && to !== communities.none) {
return relation_registry.community_relation(from, to) >= EGoodwill.FRIENDS;
} else {
return false;
}
}

/**
* todo;
* Check general goodwill level between factions and assume whether they are enemies.
*
* @param from - community relation check from
* @param to - community relation check to
* @returns whether faction `from` considers `to` enemy
*/
export function isFactionsEnemies(faction: Optional<TCommunity>, factionTo: TCommunity): boolean {
if (faction !== null && faction !== communities.none && factionTo !== communities.none) {
return relation_registry.community_relation(faction, factionTo) <= EGoodwill.ENEMIES;
export function areCommunitiesEnemies(from: Optional<TCommunity>, to: TCommunity): boolean {
if (from !== null && from !== communities.none && to !== communities.none) {
return relation_registry.community_relation(from, to) <= EGoodwill.ENEMIES;
} else {
return false;
}
}

/**
* Is enemy any squad member.
* Check if anyone from squad is enemy to actor.
*
* @param squad - target squad to check
* @returns whether any member is enemy to actor
*/
export function isAnySquadMemberEnemyToActor(squad: Squad): boolean {
for (const squadMember of squad.squad_members()) {
Expand All @@ -65,30 +84,14 @@ export function isAnySquadMemberEnemyToActor(squad: Squad): boolean {
}

/**
* todo;
* Check if anyone from squad is friend to actor.
*
* @param squad - target squad to check
* @returns whether any member is friend to actor
*/
export function isSquadRelationBetweenActorAndRelation(squadName: TName, relation: ERelation): boolean {
const squad: Optional<Squad> = getServerObjectByStoryId(squadName);
const actor: Optional<ClientObject> = registry.actor;

if (!squad || !actor) {
return false;
}

export function isAnySquadMemberFriendToActor(squad: Squad): boolean {
for (const squadMember of squad.squad_members()) {
let isEnemy: boolean;

if (relation === ERelation.ENEMY) {
const goodwill: Optional<number> = registry.objects.get(squadMember.id)?.object.general_goodwill(actor);

isEnemy = goodwill === null ? false : goodwill <= EGoodwill.ENEMIES;
} else {
const goodwill: Optional<number> = registry.objects.get(squadMember.id)?.object.general_goodwill(actor);

isEnemy = goodwill === null ? false : goodwill >= EGoodwill.ENEMIES;
}

if (isEnemy) {
if (relation_registry.get_general_goodwill_between(squadMember.id, ACTOR_ID) >= EGoodwill.FRIENDS) {
return true;
}
}
Expand Down
Loading

0 comments on commit e8ae576

Please sign in to comment.