actions reactored. multi-tick action support

This commit is contained in:
azykov@mail.ru
2026-06-12 11:46:42 +03:00
parent 36f76efdd7
commit 20a4edad5a
35 changed files with 1047 additions and 373 deletions
@@ -0,0 +1,32 @@
import { ScriptAction, type ScriptCoroutineContext, type V3 } from "../../types";
export class ApplyImpulseScriptAction extends ScriptAction {
private targetPosition: V3;
private amplitude: number;
constructor(coroutine: ScriptCoroutineContext, targetPosition: V3, amplitude: number) {
super(coroutine);
this.targetPosition = targetPosition;
this.amplitude = amplitude;
}
public tick(_deltaTime: number): boolean {
const rb = this.coroutine.thisObject.rb();
if (!rb)
return true;
const { x: px, y: py, z: pz } = rb.translation();
const dx = this.targetPosition[0] - px;
const dy = this.targetPosition[1] - py;
const dz = this.targetPosition[2] - pz;
const len = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (len > 1e-6) {
const scale = (this.amplitude * 100) / len;
rb.applyImpulse({ x: dx * scale, y: dy * scale, z: dz * scale }, true);
}
return true;
}
}
+4
View File
@@ -0,0 +1,4 @@
export * from './waitConditionAction';
export * from './waitFramesAction';
export * from './waitSecondsAction';
export * from './applyImpulseAction';
@@ -0,0 +1,15 @@
import { ScriptAction, type ScriptCoroutineContext } from "../../types";
export class WaitConditionScriptAction extends ScriptAction {
private condition: () => boolean;
constructor(coroutine: ScriptCoroutineContext, condition: () => boolean) {
super(coroutine);
this.condition = condition;
}
public tick(_deltaTime: number): boolean {
return this.condition();
}
}
@@ -0,0 +1,16 @@
import { ScriptAction, type ScriptCoroutineContext } from "../../types";
export class WaitFramesScriptAction extends ScriptAction {
private remaining: number;
constructor(coroutine: ScriptCoroutineContext, frames: number) {
super(coroutine);
this.remaining = frames;
}
public tick(_deltaTime: number): boolean {
this.remaining--;
return this.remaining <= 0;
}
}
@@ -0,0 +1,16 @@
import { ScriptAction, type ScriptCoroutineContext } from "../../types";
export class WaitSecondsScriptAction extends ScriptAction {
private remaining: number;
constructor(coroutine: ScriptCoroutineContext, seconds: number) {
super(coroutine);
this.remaining = seconds;
}
public tick(deltaTime: number): boolean {
this.remaining -= deltaTime; // TODO should only consume as much time as needed and pass the remainder to next actions
return this.remaining <= 0;
}
}
+46
View File
@@ -0,0 +1,46 @@
import type { ScriptAction, ScriptCoroutine } from "../types";
export class ScriptCoroutines {
private items: { coroutine: ScriptCoroutine, currentAction?: ScriptAction }[] = [];
public push(coroutines: ScriptCoroutine[]) {
this.items.push(
...coroutines
.map((c) => ({ coroutine: c })),
);
}
public tick(deltaTime: number): void {
// execute coroutines one by one and remove the ones that have finished
this.items = this.items
.filter((c) => !this.tickItem(c, deltaTime));
}
private tickItem(item: { coroutine: ScriptCoroutine, currentAction?: ScriptAction }, deltaTime: number): boolean {
if (!item.currentAction) {
try {
const yieldResult = item.coroutine.next();
if (yieldResult.done)
return true; // coroutine exhausted
item.currentAction = yieldResult.value;
}
catch (err) {
console.error(err);
return true; // coroutine failed to yield an action or return
}
}
try {
const done = item.currentAction.tick(deltaTime);
if (done)
item.currentAction = undefined;
}
catch (err) {
console.error(err);
item.currentAction = undefined;
}
return false;
}
}
+52
View File
@@ -0,0 +1,52 @@
import type { ScriptContext, ScriptCoroutineContext, ScriptGameEventHandler } from "../types";
import type { ScriptCoroutines } from "./coroutines";
export type ScriptCoroutineGameEventHandler = {
handler: ScriptGameEventHandler;
coroutine: ScriptCoroutineContext;
}
export class ScriptGameEventBus {
private coroutines: ScriptCoroutines;
private handlers: Record<string, ScriptCoroutineGameEventHandler[]> = {};
constructor(coroutines: ScriptCoroutines) {
this.coroutines = coroutines;
}
on(event: string, handler: ScriptGameEventHandler, coroutine: ScriptCoroutineContext): void {
console.log('subscribing to ' + event, handler, coroutine);
let set = this.handlers[event];
if (!set) {
set = [];
this.handlers[event] = set;
}
if (!set.some((h) => h.handler === handler))
set.push({ handler, coroutine });
}
off(event: string, handler: ScriptGameEventHandler): void {
console.log('unubscribing from ' + event, handler);
const handlers = this.handlers[event];
if (!handlers)
return;
this.handlers[event] = handlers.filter((h) => h.handler !== handler);
}
emit(event: string, context: Omit<ScriptContext, keyof ScriptCoroutineContext>): void {
console.log('emitting ' + event);
const coroutines = this.handlers[event]
?.map((h) => {
console.log(`passing ${event} to ${h.handler}(${JSON.stringify(context)}, ${JSON.stringify(h.coroutine)})`);
return h.handler({ ...context, ...h.coroutine });
});
console.log(coroutines);
if (coroutines)
this.coroutines.push(coroutines);
}
}