Skip to content

Commit

Permalink
Fix animation issues when slot was not picked correctly. Update actio…
Browse files Browse the repository at this point in the history
…n help wounded/search corpse to handle many targets at once.
  • Loading branch information
Neloreck committed Aug 23, 2023
1 parent 035d728 commit e266ba4
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 127 deletions.
25 changes: 3 additions & 22 deletions src/engine/core/objects/state/weapon/EvaluatorWeaponLocked.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { LuabindClass, property_evaluator } from "xray16";

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

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

Expand All @@ -19,28 +19,9 @@ export class EvaluatorWeaponLocked extends property_evaluator {
}

/**
* todo: Description.
* Check if weapon state is locked right now and it cannot be changed / used.
*/
public override evaluate(): boolean {
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 || isWeaponUnstrapped)) {
return true;
}

return false;
return isObjectWeaponLocked(this.object);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { action_base, anim, look, LuabindClass, move, object } from "xray16";
import { action_base, anim, look, LuabindClass, move } from "xray16";

import { GlobalSoundManager } from "@/engine/core/managers/sounds/GlobalSoundManager";
import { EAnimationType, EStalkerState } from "@/engine/core/objects/animation";
import { StalkerAnimationManager } from "@/engine/core/objects/state/StalkerAnimationManager";
import { ISchemePostCombatIdleState } from "@/engine/core/schemes/combat_idle/ISchemePostCombatIdleState";
import { LuaLogger } from "@/engine/core/utils/logging";
import { ClientObject, Optional } from "@/engine/lib/types";
import { isObjectWeaponLocked, setObjectBestWeapon } from "@/engine/core/utils/object/object_weapon";

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

Expand All @@ -30,7 +30,8 @@ export class ActionPostCombatIdleWait extends action_base {

super.initialize();

this.object.set_item(object.idle, this.object.best_weapon());
setObjectBestWeapon(this.object);

this.object.set_mental_state(anim.danger);
this.object.set_body_state(move.crouch);
this.object.set_movement_type(move.stand);
Expand Down Expand Up @@ -69,51 +70,26 @@ export class ActionPostCombatIdleWait extends action_base {
public override execute(): void {
super.execute();

if (!this.object.in_smart_cover()) {
if (this.isAnimationStarted === false && !this.isWeaponLocked(this.object)) {
this.isAnimationStarted = true;
(this.state.animation as StalkerAnimationManager).setState(EStalkerState.HIDE);
(this.state.animation as StalkerAnimationManager).setControl();
}
}

GlobalSoundManager.getInstance().playSound(this.object.id(), "post_combat_wait", null, null);
}

/**
* todo: Move to utils as pure function?
*
* @param object - object to check weapon staet
* @returns whether weapon is in locked state
*/
public isWeaponLocked(object: ClientObject): boolean {
const isWeaponStrapped: boolean = object.weapon_strapped();
const isWeaponUnstrapped: boolean = object.weapon_unstrapped();

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

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

if (bestWeapon === null) {
return false;
if (this.isAnimationStarted) {
return;
}

if (object.active_item() === null) {
return false;
if (this.object.in_smart_cover()) {
return;
}

const isWeaponGoingToBeStrapped: boolean = object.is_weapon_going_to_be_strapped(bestWeapon);
if (isObjectWeaponLocked(this.object)) {
logger.info("Waiting for weapon unlock:", this.object.active_item()?.name());

if (isWeaponGoingToBeStrapped && !isWeaponStrapped) {
return true;
return;
} else {
logger.info("Weapon unlocked unlock:", this.object.active_item()?.name());
}

if (!isWeaponGoingToBeStrapped && !isWeaponUnstrapped) {
return true;
}
this.isAnimationStarted = true;
(this.state.animation as StalkerAnimationManager).setState(EStalkerState.HIDE);
(this.state.animation as StalkerAnimationManager).setControl();

return false;
GlobalSoundManager.getInstance().playSound(this.object.id(), "post_combat_wait", null, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,7 @@ export class ActionSearchCorpse extends action_base {

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;
this.sendObjectToCorpse();
}

/**
Expand Down Expand Up @@ -70,27 +62,48 @@ export class ActionSearchCorpse extends action_base {
public override execute(): void {
super.execute();

// Do not execute if already play same animation or if target is not defined.
if (this.state.selectedCorpseId === null || getStalkerState(this.object) === EStalkerState.SEARCH_CORPSE) {
if (this.lootingObjectId === null) {
return;
}

// Start playing looting animation when actually reach destination point.
if (this.object.position().distance_to_sqr(this.state.selectedCorpseVertexPosition as Vector) <= 2) {
setStalkerState(this.object, EStalkerState.SEARCH_CORPSE, null, null, {
lookPosition: this.state.selectedCorpseVertexPosition,
lookObjectId: null,
});

// Play looting start sound once.
if (!this.isLootingSoundPlayed) {
GlobalSoundManager.getInstance().playSound(this.object.id(), scriptSounds.corpse_loot_begin);
this.isLootingSoundPlayed = true;
}
if (
getStalkerState(this.object) !== EStalkerState.SEARCH_CORPSE &&
this.object.position().distance_to_sqr(this.state.selectedCorpseVertexPosition as Vector) <= 2
) {
this.startSearchingCorpse();
} else if (this.lootingObjectId !== this.state.selectedCorpseId) {
setStalkerState(this.object, EStalkerState.PATROL);
this.sendObjectToCorpse();
}
}

/**
* Start searching corpse.
*/
public startSearchingCorpse(): void {
setStalkerState(this.object, EStalkerState.SEARCH_CORPSE, null, null, {
lookPosition: this.state.selectedCorpseVertexPosition,
lookObjectId: null,
});

// Play looting start sound once.
if (!this.isLootingSoundPlayed) {
GlobalSoundManager.getInstance().playSound(this.object.id(), scriptSounds.corpse_loot_begin);
this.isLootingSoundPlayed = true;
}
}

/**
* Send stalker to corpse.
*/
public sendObjectToCorpse(): void {
this.lootingObjectId = 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);
}
}
82 changes: 48 additions & 34 deletions src/engine/core/schemes/help_wounded/actions/ActionHelpWounded.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,7 @@ export class ActionHelpWounded extends action_base {

logger.info("Start helping wounded:", this.object.name(), tostring(this.state.selectedWoundedVertexId));

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(
sendToNearestAccessibleVertex(this.object, this.state.selectedWoundedVertexId)
);

setStalkerState(this.object, EStalkerState.RUN);
this.sendObjectToWounded();
}

public override finalize(): void {
Expand All @@ -62,38 +54,60 @@ export class ActionHelpWounded extends action_base {
public override execute(): void {
super.execute();

// Do not execute if already play same animation or if target is not defined.
if (
this.state.selectedWoundedId === null ||
getStalkerState(this.object) === EStalkerState.HELP_WOUNDED_WITH_MEDKIT
) {
// Do not execute if target is not defined.
if (this.helpingTargetId === null) {
return;
}

// Reach destination vertex for healing.
if (this.object.position().distance_to_sqr(this.state.selectedWoundedVertexPosition as Vector) < 2) {
// Start heal wounded animation, heal target on finish.
setStalkerState(
this.object,
EStalkerState.HELP_WOUNDED_WITH_MEDKIT,
null,
null,
{
lookPosition: this.state.selectedWoundedVertexPosition,
lookObjectId: null,
},
{ isForced: true }
);

// Say That everything will be ok once per healing action.
if (!this.isHelpingSoundPlayed) {
GlobalSoundManager.getInstance().playSound(this.object.id(), scriptSounds.wounded_medkit);
this.isHelpingSoundPlayed = true;
}
if (
getStalkerState(this.object) !== EStalkerState.HELP_WOUNDED_WITH_MEDKIT &&
this.object.position().distance_to_sqr(this.state.selectedWoundedVertexPosition as Vector) < 2
) {
this.startHelpingWounded();
} else if (this.helpingTargetId !== this.state.selectedWoundedId) {
setStalkerState(this.object, EStalkerState.RUN);
this.sendObjectToWounded();
}
}

/**
* Send stalker to wounded.
*/
public sendObjectToWounded(): void {
this.helpingTargetId = this.state.selectedWoundedId;

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(
sendToNearestAccessibleVertex(this.object, this.state.selectedWoundedVertexId)
);

setStalkerState(this.object, EStalkerState.RUN);
}

/**
* Start helping wounded.
*/
public startHelpingWounded(): void {
// Start heal wounded animation, heal target on finish.
setStalkerState(
this.object,
EStalkerState.HELP_WOUNDED_WITH_MEDKIT,
null,
null,
{
lookPosition: this.state.selectedWoundedVertexPosition,
lookObjectId: null,
},
{ isForced: true }
);

// Say That everything will be ok once per healing action.
if (!this.isHelpingSoundPlayed) {
GlobalSoundManager.getInstance().playSound(this.object.id(), scriptSounds.wounded_medkit);
this.isHelpingSoundPlayed = true;
}
}
}
8 changes: 3 additions & 5 deletions src/engine/core/utils/animation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TAnimationSequenceElement, TAnimationSequenceElements } from "@/engine/core/objects/animation";
import { LuaArray, Optional, TIndex } from "@/engine/lib/types";
import { LuaArray, Optional } from "@/engine/lib/types";

/**
* Create animation sequence.
Expand All @@ -13,11 +13,9 @@ export function createSequence(
>
): LuaArray<TAnimationSequenceElements> {
const list: LuaArray<TAnimationSequenceElements> = new LuaTable();
let index: TIndex = 0;

for (const it of sequence) {
list.set(index, it as TAnimationSequenceElement);
index++;
for (const [index, it] of $fromArray(sequence)) {
list.set(index - 1, it as TAnimationSequenceElement);
}

return list;
Expand Down
5 changes: 3 additions & 2 deletions src/engine/core/utils/object/object_loot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,13 @@ export function findNearestCorpseToLoot(
// Is not looted by anyone or looted by current object.
(isLootedBy === null || isLootedBy === object.id()) &&
// Seen dead object recently.
object.memory_position(corpseObject) !== null
object.memory_position(corpseObject) !== null &&
isObjectWithValuableLoot(corpseObject)
) {
const distanceBetween: TDistance = object.position().distance_to_sqr(corpseObject.position());

// Is near enough and has valuable loot.
if (distanceBetween < nearestCorpseDistSqr && isObjectWithValuableLoot(corpseObject)) {
if (distanceBetween < nearestCorpseDistSqr) {
const corpseVertex: TNumberId = level.vertex_id(corpseObject.position());

// Can be reached by object.
Expand Down
39 changes: 39 additions & 0 deletions src/engine/core/utils/object/object_weapon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { object } from "xray16";

import { isWeapon } from "@/engine/core/utils/object/object_class";
import { ClientObject, Optional } from "@/engine/lib/types";

/**
* todo;
*/
export function isObjectWeaponLocked(object: ClientObject): boolean {
const bestWeapon: Optional<ClientObject> = object.best_weapon();

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

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

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

if (object.is_weapon_going_to_be_strapped(bestWeapon) && (!isWeaponStrapped || isWeaponUnstrapped)) {
return true;
}

return false;
}

/**
* todo;
*/
export function setObjectBestWeapon(target: ClientObject): void {
const bestWeapon: Optional<ClientObject> = target.best_weapon();

if (bestWeapon && isWeapon(bestWeapon)) {
target.set_item(object.idle, bestWeapon);
}
}

0 comments on commit e266ba4

Please sign in to comment.