Skip to content

Commit

Permalink
Fix tests.
Browse files Browse the repository at this point in the history
Adjusted logics of help wounded/search corpse when few corpses exist.
Removed unused imports / comments.
Merge latest dev changes.
Updated logics schemes.
Update state manager generation tests.
  • Loading branch information
Neloreck committed Aug 23, 2023
1 parent 2b6400e commit fb828ee
Show file tree
Hide file tree
Showing 13 changed files with 204 additions and 123 deletions.
7 changes: 0 additions & 7 deletions src/engine/core/database/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,6 @@ export interface IRegistryObjectState extends Record<EScheme, Optional<IBaseSche
* todo;
*/
mute: Optional<boolean>;
/**
* ID of object currently looting object.
* Used to prevent looting of same object by multiple objects at once.
*
* todo: Move to loot scheme state, not store it in global. Probaly pstore is correct place.
*/
lootedByObject: Optional<TNumberId>;
/**
* todo;
*/
Expand Down
9 changes: 5 additions & 4 deletions src/engine/core/objects/binders/creature/StalkerBinder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,9 @@ export class StalkerBinder extends object_binder {
this.state.stateManager!.animation.setState(null, true);
}

this.updateLightState(this.object);
DropManager.getInstance().onObjectDeath(this.object);

if (this.state[EScheme.REACH_TASK]) {
emitSchemeEvent(this.object, this.state[EScheme.REACH_TASK], ESchemeEvent.DEATH, victim, who);
}
Expand All @@ -507,9 +510,6 @@ export class StalkerBinder extends object_binder {
emitSchemeEvent(this.object, this.state[this.state.activeScheme!]!, ESchemeEvent.DEATH, victim, who);
}

this.updateLightState(this.object);
DropManager.getInstance().onObjectDeath(this.object);

unregisterHelicopterEnemy(this.helicopterEnemyIndex!);
unregisterStalker(this, false);

Expand All @@ -526,14 +526,15 @@ export class StalkerBinder extends object_binder {
}

EventsManager.emitEvent(EGameEvent.STALKER_KILLED, this.object, who);

ReleaseBodyManager.getInstance().addDeadBody(this.object);
}

/**
* todo: Description.
*/
public onUse(object: ClientObject, who: ClientObject): void {
logger.info("Stalker use:", this.object.name(), "by", who.name());
logger.info("Stalker used:", this.object.name(), "by", who.name());

if (this.object.alive()) {
EventsManager.emitEvent(EGameEvent.STALKER_INTERACTION, object, who);
Expand Down
12 changes: 11 additions & 1 deletion src/engine/core/objects/state/add_state_manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ describe("add_state_manager util", () => {
"ToIdleItems",
[
[EEvaluatorId.IS_STATE_IDLE_ITEMS, false],
[EEvaluatorId.IS_WOUNDED, false],
[EEvaluatorId.IS_WOUNDED_EXISTING, false],
[mockStalkerIds.property_items, true],
[mockStalkerIds.property_enemy, false],
],
Expand All @@ -198,7 +200,15 @@ describe("add_state_manager util", () => {
[[EEvaluatorId.IS_STATE_IDLE_ALIFE, true]]
);

checkAction(planner.action(EActionId.ALIFE), "generic", [[EEvaluatorId.IS_STATE_IDLE_ALIFE, true]], []);
checkAction(
planner.action(EActionId.ALIFE),
"generic",
[
[EEvaluatorId.IS_STATE_IDLE_ALIFE, true],
[mockStalkerIds.property_items, false],
],
[]
);
checkAction(
planner.action(mockStalkerIds.action_gather_items),
"generic",
Expand Down
3 changes: 3 additions & 0 deletions src/engine/core/objects/state/add_state_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export function addStateManager(object: ClientObject): StalkerStateManager {
actionItemsToIdle.add_precondition(new world_property(EEvaluatorId.IS_STATE_IDLE_ITEMS, false));
actionItemsToIdle.add_precondition(new world_property(stalker_ids.property_items, true));
actionItemsToIdle.add_precondition(new world_property(stalker_ids.property_enemy, false));
actionItemsToIdle.add_precondition(new world_property(EEvaluatorId.IS_WOUNDED, false));
actionItemsToIdle.add_precondition(new world_property(EEvaluatorId.IS_WOUNDED_EXISTING, false));
actionItemsToIdle.add_effect(new world_property(EEvaluatorId.IS_STATE_IDLE_ITEMS, true));

planner.add_action(EActionId.STATE_TO_IDLE_ITEMS, actionItemsToIdle);
Expand All @@ -68,6 +70,7 @@ export function addStateManager(object: ClientObject): StalkerStateManager {
planner.add_action(EActionId.STATE_TO_IDLE_ALIFE, actionAlifeToIdle);

planner.action(EActionId.ALIFE).add_precondition(new world_property(EEvaluatorId.IS_STATE_IDLE_ALIFE, true));
planner.action(EActionId.ALIFE).add_precondition(new world_property(stalker_ids.property_items, false));

planner
.action(stalker_ids.action_gather_items)
Expand Down
6 changes: 6 additions & 0 deletions src/engine/core/objects/state/state/ActionStateToIdle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ export class ActionStateToIdle extends action_base {
sendToNearestAccessibleVertex(this.object, this.object.level_vertex_id());
}

public override finalize(): void {
super.finalize();

logger.info("End state to idle for:", this.object.name(), this.name);
}

/**
* Rest object state to idle.
*/
Expand Down
21 changes: 12 additions & 9 deletions src/engine/core/objects/state/weapon/EvaluatorWeaponLocked.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { LuabindClass, property_evaluator } from "xray16";

import { StalkerStateManager } from "@/engine/core/objects/state/StalkerStateManager";
import { LuaLogger } from "@/engine/core/utils/logging";
import { isWeapon } from "@/engine/core/utils/object";
import { ClientObject, Optional } from "@/engine/lib/types";

const logger: LuaLogger = new LuaLogger($filename);
Expand All @@ -22,24 +23,26 @@ export class EvaluatorWeaponLocked extends property_evaluator {
* todo: Description.
*/
public override evaluate(): boolean {
const bestWeapon: Optional<ClientObject> = this.object.best_weapon();

if (bestWeapon === null) {
return false;
}

if (!isWeapon(bestWeapon)) {
return false;
}

const isWeaponStrapped: boolean = this.object.weapon_strapped();
const isWeaponUnstrapped: boolean = this.object.weapon_unstrapped();

if (!(isWeaponUnstrapped || isWeaponStrapped)) {
return true;
}

const bestWeapon: Optional<ClientObject> = this.object.best_weapon();

if (bestWeapon === null) {
return false;
}

const isWeaponGoingToBeStrapped: boolean = this.object.is_weapon_going_to_be_strapped(bestWeapon);

if (isWeaponGoingToBeStrapped && !isWeaponStrapped) {
return true;
} else if (!isWeaponGoingToBeStrapped && !isWeaponUnstrapped && this.object.active_item() !== null) {
if (isWeaponGoingToBeStrapped && (!isWeaponStrapped || isWeaponUnstrapped)) {
return true;
}

Expand Down
2 changes: 1 addition & 1 deletion src/engine/core/schemes/combat_idle/PostCombatIdle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class PostCombatIdle {
* todo: Description.
*/
public static addPostCombatIdleWait(object: ClientObject): void {
logger.info("Add post-combat idle for:", object.name());
// logger.info("Add post-combat idle for:", object.name());

const actionPlanner: ActionPlanner = object.motivation_action_manager();
const combatAction: ActionBase = actionPlanner.action(stalker_ids.action_combat_planner);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ClientObject, Optional, TDistance, TTimestamp } from "@/engine/lib/type
const logger: LuaLogger = new LuaLogger($filename);

/**
* Evaluator to check whether have any active enemy.
* Evaluator to check whether any enemy exists.
*/
@LuabindClass()
export class EvaluatorHasEnemy extends property_evaluator {
Expand Down
23 changes: 11 additions & 12 deletions src/engine/core/schemes/corpse_detection/SchemeCorpseDetection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,20 @@ export class SchemeCorpseDetection extends AbstractScheme {
// Add evaluator to check if anything can be looted.
planner.add_evaluator(EEvaluatorId.IS_CORPSE_EXISTING, new EvaluatorCorpseDetect(state));

const actionSearchCorpse: ActionSearchCorpse = new ActionSearchCorpse(state);
const action: ActionSearchCorpse = new ActionSearchCorpse(state);

actionSearchCorpse.add_precondition(new world_property(stalker_ids.property_alive, true));
actionSearchCorpse.add_precondition(new world_property(stalker_ids.property_enemy, false));
actionSearchCorpse.add_precondition(new world_property(stalker_ids.property_danger, false));
actionSearchCorpse.add_precondition(new world_property(stalker_ids.property_anomaly, false));
actionSearchCorpse.add_precondition(new world_property(stalker_ids.property_items, false));
actionSearchCorpse.add_precondition(new world_property(EEvaluatorId.IS_CORPSE_EXISTING, true));
actionSearchCorpse.add_precondition(new world_property(EEvaluatorId.IS_WOUNDED, false));
actionSearchCorpse.add_precondition(new world_property(EEvaluatorId.IS_WOUNDED_EXISTING, false));
// Mark as corpse not existing after search end.
actionSearchCorpse.add_effect(new world_property(EEvaluatorId.IS_CORPSE_EXISTING, false));
action.add_precondition(new world_property(stalker_ids.property_alive, true));
action.add_precondition(new world_property(stalker_ids.property_enemy, false));
action.add_precondition(new world_property(stalker_ids.property_danger, false));
action.add_precondition(new world_property(stalker_ids.property_anomaly, false));
action.add_precondition(new world_property(EEvaluatorId.IS_CORPSE_EXISTING, true));
action.add_precondition(new world_property(EEvaluatorId.IS_WOUNDED_EXISTING, false));
action.add_precondition(new world_property(EEvaluatorId.IS_MEET_CONTACT, false));

action.add_effect(new world_property(EEvaluatorId.IS_CORPSE_EXISTING, false));

// Add alife action for execution when evaluator allows to do so.
planner.add_action(EActionId.SEARCH_CORPSE, actionSearchCorpse);
planner.add_action(EActionId.SEARCH_CORPSE, action);

// Do not return to alife when searching for corpse.
planner.action(EActionId.ALIFE).add_precondition(new world_property(EEvaluatorId.IS_CORPSE_EXISTING, false));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { action_base, LuabindClass } from "xray16";

import { IRegistryObjectState, registry, setStalkerState } from "@/engine/core/database";
import { setStalkerState } from "@/engine/core/database";
import { GlobalSoundManager } from "@/engine/core/managers/sounds/GlobalSoundManager";
import { EStalkerState } from "@/engine/core/objects/animation";
import { ISchemeCorpseDetectionState } from "@/engine/core/schemes/corpse_detection";
import { ISchemeCorpseDetectionState } from "@/engine/core/schemes/corpse_detection/ISchemeCorpseDetectionState";
import { freeSelectedLootedObjectSpot } from "@/engine/core/schemes/corpse_detection/utils";
import { LuaLogger } from "@/engine/core/utils/logging";
import { scriptSounds } from "@/engine/lib/constants/sound/script_sounds";
import { Optional, Vector } from "@/engine/lib/types";
import { EClientObjectPath, Optional, TNumberId, Vector } from "@/engine/lib/types";

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

/**
* Action to go loot corpse by stalkers.
Expand All @@ -14,39 +18,48 @@ import { Optional, Vector } from "@/engine/lib/types";
export class ActionSearchCorpse extends action_base {
public readonly state: ISchemeCorpseDetectionState;

public isLootingSoundPlayed: boolean = false;
public lootingObjectId: Optional<TNumberId> = null;

public constructor(state: ISchemeCorpseDetectionState) {
super(null, ActionSearchCorpse.__name);
this.state = state;
}

/**
* Clean up action states.
*/
public override finalize(): void {
// Unmark corpse as selected by an object, if any exist.
if (this.state.selectedCorpseId !== null) {
const corpseState: Optional<IRegistryObjectState> = registry.objects.get(this.state.selectedCorpseId);

if (corpseState !== null) {
corpseState.lootedByObject = null;
}
}

super.finalize();
}

/**
* Initialize object logics when it is capture by corpse loot action.
*/
public override initialize(): void {
super.initialize();

logger.info("Start search corpse:", this.object.name(), tostring(this.state.selectedCorpseId));

this.object.set_desired_position();
this.object.set_desired_direction();
this.object.set_path_type(EClientObjectPath.LEVEL_PATH);

this.object.set_dest_level_vertex_id(this.state.selectedCorpseVertexId);

setStalkerState(this.object, EStalkerState.PATROL);

this.lootingObjectId = this.state.selectedCorpseId;
}

/**
* Clean up action states.
*/
public override finalize(): void {
logger.info("Stop search corpse:", this.object.name(), tostring(this.state.selectedCorpseId));

// Unmark corpse as selected by an object, if any exist.
if (this.state.selectedCorpseId !== null) {
freeSelectedLootedObjectSpot(this.state.selectedCorpseId);
}

this.isLootingSoundPlayed = false;
this.lootingObjectId = null;

super.finalize();
}

/**
Expand All @@ -63,7 +76,16 @@ export class ActionSearchCorpse extends action_base {
lookPosition: this.state.selectedCorpseVertexPosition,
lookObjectId: null,
});
GlobalSoundManager.getInstance().playSound(this.object.id(), scriptSounds.corpse_loot_begin);

// Play looting start sound once.
if (!this.isLootingSoundPlayed) {
GlobalSoundManager.getInstance().playSound(this.object.id(), scriptSounds.corpse_loot_begin);
this.isLootingSoundPlayed = true;
}
} else if (this.lootingObjectId !== this.state.selectedCorpseId) {
setStalkerState(this.object, EStalkerState.PATROL);
}

this.lootingObjectId = this.state.selectedCorpseId;
}
}
Loading

0 comments on commit fb828ee

Please sign in to comment.