Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SYNC] AI goap graph updates. Rework help wounded and search corpse schemes. #20

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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