Skip to content

Commit

Permalink
Merge pull request #431 from charles-m-knox/npc-random-movements
Browse files Browse the repository at this point in the history
Implements random npc movement via a loopable queueable task
  • Loading branch information
Promises authored Sep 2, 2024
2 parents 7f44935 + ba92aba commit 179e299
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 5 deletions.
109 changes: 109 additions & 0 deletions src/engine/action/pipe/task/queueable-task.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { ActorTask } from '@engine/task/impl';
import { Actor, Player } from '@engine/world/actor';
import { ObjectInteractionAction } from '../object-interaction.action';
import { ItemOnObjectAction } from '../item-on-object.action';
import { ActionHook } from '@engine/action/hook';
import { Task } from '@engine/task';

/**
* The result of running a callback function will be recorded here so that the
* `QueueableTask` can make a choice to continue looping and/or to enqueue
* the desired task.
*/
export interface QueueableTaskEval {
callbackResult: boolean;
shouldContinueLooping: boolean;
}

/**
* All actions supported by this plugin task.
*/
type ObjectAction = ObjectInteractionAction | ItemOnObjectAction;

/**
* An ActionHook for a supported ObjectAction.
*/
type ObjectActionHook<TAction extends ObjectAction> = ActionHook<TAction, (data: TAction) => void>;

/**
* The data unique to the action being executed (i.e. excluding shared data)
*/
type ObjectActionData<TAction extends ObjectAction> = Omit<TAction, 'player' | 'actor' | 'object' | 'position'>;

/**
* Processes the provided callback function on every single tick, and also allows any
* arbitrary base task to be queued at the next tick. Useful for queuing random
* movements, as well as other things.
*
* Can loop infinitely based on the result of the passed in `callback` function.
*/
export class QueueableTask<TAction extends ObjectAction> extends ActorTask <Player | Actor> {
/**
* The plugins to execute on each tick. These will not get called if the
* `callback` indicates a halting condition.
*/
private plugins: ObjectActionHook<TAction>[];

private data: ObjectActionData<TAction> | null;

/**
* Optionally provided base task to enqueue on each tick. Can be `null`.
*/
private task: Task | null;

/**
* This callback function will be executed on every tick. It must return a
* `QueueableTaskEval` so that this loop can determine if it should
* terminate or continue looping.
*/
private callback: () => QueueableTaskEval;

constructor(plugins: ObjectActionHook<TAction>[], actor: Player | Actor, callback: () => QueueableTaskEval, task: Task | null, data: ObjectActionData<TAction> | null) {
super(
actor,
);

this.plugins = plugins;
this.data = data;
this.task = task;
this.callback = callback;
}

/**
* Executed every tick. Depending on the callback value, this task can stop
* future executions.
*/
public execute(): void {
const ev = this.callback();
if (!ev.callbackResult) {
if (!ev.shouldContinueLooping) {
this.stop()
}
return;
}

if (this.task) { // only gets executed if the callback returns true for its result
this.actor.enqueueBaseTask(this.task)
}

// call the relevant plugins on each tick, if provided
this.plugins.forEach(plugin => {
if (!plugin || !plugin.handler) {
return;
}

const action = {
player: this.actor,
...this.data
} as TAction;

plugin.handler(action);
});


if (!ev.shouldContinueLooping) {
this.stop();
return
}
}
}
16 changes: 12 additions & 4 deletions src/engine/world/actor/actor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { Pathfinding } from './pathfinding';
import { ActorMetadata } from './metadata';
import { Task, TaskScheduler } from '@engine/task';
import { logger } from '@runejs/common';
import { ObjectConfig } from '@runejs/filestore';
import { QueueableTask } from '@engine/action/pipe/task/queueable-task';


export type ActorType = 'player' | 'npc';
Expand Down Expand Up @@ -333,13 +335,19 @@ export abstract class Actor {
}

public canMove(): boolean {
return !this.busy;
// In the future, there will undoubtedly be various reasons for the
// actor to not be able to move, but for now we are returning true.
return true;
}

public initiateRandomMovement(): void {
// this used to use `setInterval` but will need rewriting to be synced with ticks
// see https://github.com/runejs/server/issues/417
// this.randomMovementInterval = setInterval(() => this.moveSomewhere(), 1000);
this.enqueueBaseTask(new QueueableTask([], this, () => {
this.moveSomewhere();
return {
callbackResult: true,
shouldContinueLooping: true,
};
}, null, null))
}

public moveSomewhere(): void {
Expand Down
2 changes: 1 addition & 1 deletion src/engine/world/actor/npc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ export class Npc extends Actor {
if(this.metadata.following) {
return false;
}
return this.updateFlags.faceActor === undefined && this.updateFlags.animation === undefined;
return this.updateFlags.faceActor === null && this.updateFlags.animation === null;
}

/**
Expand Down

0 comments on commit 179e299

Please sign in to comment.