@vylos/core 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/menu/SaveLoadMenu.vue +1 -0
- package/src/engine/core/Engine.ts +22 -6
- package/src/engine/managers/EventManager.ts +28 -23
- package/src/engine/types/events.ts +8 -8
- package/src/setup.ts +2 -0
- package/tests/engine/EventManager.test.ts +106 -40
- package/tests/integration/game-loop.test.ts +3 -0
package/package.json
CHANGED
|
@@ -138,18 +138,23 @@ export class Engine {
|
|
|
138
138
|
* Restores game state, history, event lock state, and sets up mid-event resume if needed.
|
|
139
139
|
*/
|
|
140
140
|
loadSave(saveData: SaveSlot, setState: (state: VylosGameState) => void): void {
|
|
141
|
+
logger.info('Loading save...');
|
|
142
|
+
|
|
141
143
|
// Restore game state
|
|
142
144
|
setState(JSON.parse(JSON.stringify(saveData.gameState)));
|
|
145
|
+
logger.debug(`Restored game state (location: ${saveData.gameState.locationId})`);
|
|
143
146
|
|
|
144
147
|
// Restore event lock state
|
|
145
148
|
this.eventManager.resetAll();
|
|
146
149
|
if (saveData.lockedEventIds) {
|
|
147
150
|
this.eventManager.restoreLockedIds(saveData.lockedEventIds);
|
|
151
|
+
logger.debug(`Restored ${saveData.lockedEventIds.length} locked events`);
|
|
148
152
|
}
|
|
149
153
|
|
|
150
154
|
// Restore history
|
|
151
155
|
if (saveData.history) {
|
|
152
156
|
this.historyManager.restore(saveData.history, saveData.historyIndex ?? -1);
|
|
157
|
+
logger.debug(`Restored history (${saveData.history.length} entries, index ${saveData.historyIndex ?? -1})`);
|
|
153
158
|
} else {
|
|
154
159
|
this.historyManager.clear();
|
|
155
160
|
}
|
|
@@ -161,12 +166,14 @@ export class Engine {
|
|
|
161
166
|
checkpoints: saveData.checkpoints,
|
|
162
167
|
initialState: saveData.initialState,
|
|
163
168
|
};
|
|
169
|
+
logger.debug(`Pending resume: ${saveData.eventId} (${saveData.checkpoints.length} checkpoints)`);
|
|
164
170
|
}
|
|
165
171
|
|
|
166
172
|
// Interrupt current execution so the loop picks up the new state
|
|
167
173
|
this.loadInterrupted = true;
|
|
168
174
|
this.eventRunner.interrupt('load');
|
|
169
175
|
this.navigationManager.cancel();
|
|
176
|
+
logger.info('Save loaded');
|
|
170
177
|
}
|
|
171
178
|
|
|
172
179
|
/** Execute a single event, handling jumps */
|
|
@@ -183,18 +190,18 @@ export class Engine {
|
|
|
183
190
|
// Skip lock/push if interrupted by a load
|
|
184
191
|
if (this.loadInterrupted) return;
|
|
185
192
|
|
|
186
|
-
// Event completed
|
|
193
|
+
// Event completed — check locked() to decide fate
|
|
187
194
|
const state = getState();
|
|
188
|
-
this.
|
|
195
|
+
this.finishEvent(currentEvent, state);
|
|
189
196
|
this.historyManager.push(currentEvent.id, this.eventRunner.checkpoints.getAll());
|
|
190
197
|
currentEvent = undefined;
|
|
191
198
|
} catch (error) {
|
|
192
199
|
if (this.loadInterrupted) return;
|
|
193
200
|
|
|
194
201
|
if (error instanceof JumpSignal) {
|
|
195
|
-
//
|
|
202
|
+
// Finish current event, find jump target
|
|
196
203
|
const state = getState();
|
|
197
|
-
this.
|
|
204
|
+
this.finishEvent(currentEvent, state);
|
|
198
205
|
this.historyManager.push(currentEvent.id, this.eventRunner.checkpoints.getAll());
|
|
199
206
|
|
|
200
207
|
const target = this.eventManager.get(error.targetEventId);
|
|
@@ -234,14 +241,14 @@ export class Engine {
|
|
|
234
241
|
if (this.loadInterrupted) return;
|
|
235
242
|
|
|
236
243
|
const state = getState();
|
|
237
|
-
this.
|
|
244
|
+
this.finishEvent(event, state);
|
|
238
245
|
this.historyManager.push(resume.eventId, this.eventRunner.checkpoints.getAll());
|
|
239
246
|
} catch (error) {
|
|
240
247
|
if (this.loadInterrupted) return;
|
|
241
248
|
|
|
242
249
|
if (error instanceof JumpSignal) {
|
|
243
250
|
const state = getState();
|
|
244
|
-
this.
|
|
251
|
+
this.finishEvent(event, state);
|
|
245
252
|
this.historyManager.push(resume.eventId, this.eventRunner.checkpoints.getAll());
|
|
246
253
|
|
|
247
254
|
const target = this.eventManager.get(error.targetEventId);
|
|
@@ -255,6 +262,15 @@ export class Engine {
|
|
|
255
262
|
}
|
|
256
263
|
}
|
|
257
264
|
|
|
265
|
+
/** After event execution: lock permanently or return to Ready */
|
|
266
|
+
private finishEvent(event: VylosEvent, state: VylosGameState): void {
|
|
267
|
+
if (event.locked?.(state) === true) {
|
|
268
|
+
this.eventManager.setLocked(event.id);
|
|
269
|
+
} else {
|
|
270
|
+
this.eventManager.setReady(event.id);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
258
274
|
private handleBack(): void {
|
|
259
275
|
const entry = this.historyManager.goBack();
|
|
260
276
|
if (entry) {
|
|
@@ -10,11 +10,11 @@ interface EventEntry {
|
|
|
10
10
|
/**
|
|
11
11
|
* Manages event lifecycle: registration, condition evaluation, status transitions.
|
|
12
12
|
*
|
|
13
|
-
* Event lifecycle: NotReady →
|
|
14
|
-
* - NotReady:
|
|
15
|
-
* -
|
|
13
|
+
* Event lifecycle: NotReady → Ready → Running → Ready (or Locked)
|
|
14
|
+
* - NotReady: unlocked() gate not yet passed
|
|
15
|
+
* - Ready: available — conditions() checked each tick
|
|
16
16
|
* - Running: currently executing
|
|
17
|
-
* - Locked:
|
|
17
|
+
* - Locked: permanently done (locked() returned true)
|
|
18
18
|
*/
|
|
19
19
|
export class EventManager {
|
|
20
20
|
private events = new Map<string, EventEntry>();
|
|
@@ -45,46 +45,44 @@ export class EventManager {
|
|
|
45
45
|
return this.events.get(id)?.status;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
/** Evaluate
|
|
48
|
+
/** Evaluate unlocked() gate for NotReady events. Returns newly ready events. */
|
|
49
49
|
evaluate(state: VylosGameState): VylosEvent[] {
|
|
50
|
-
const
|
|
50
|
+
const newlyReady: VylosEvent[] = [];
|
|
51
51
|
|
|
52
52
|
for (const [id, entry] of this.events) {
|
|
53
53
|
if (entry.status !== EventStatus.NotReady) continue;
|
|
54
54
|
|
|
55
|
-
//
|
|
56
|
-
if (entry.event.
|
|
55
|
+
// unlocked() gate: if not defined or returns !== false → Ready
|
|
56
|
+
if (entry.event.unlocked && entry.event.unlocked(state) === false) continue;
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
entry.event.unlocked?.(state);
|
|
62
|
-
unlocked.push(entry.event);
|
|
63
|
-
logger.debug(`Event unlocked: ${id}`);
|
|
64
|
-
}
|
|
58
|
+
entry.status = EventStatus.Ready;
|
|
59
|
+
newlyReady.push(entry.event);
|
|
60
|
+
logger.debug(`Event ready: ${id}`);
|
|
65
61
|
}
|
|
66
62
|
|
|
67
|
-
return
|
|
63
|
+
return newlyReady;
|
|
68
64
|
}
|
|
69
65
|
|
|
70
|
-
/** Get the next
|
|
66
|
+
/** Get the next ready event matching the current location whose conditions are met. Skips drawable events. */
|
|
71
67
|
getNextUnlocked(state: VylosGameState): VylosEvent | undefined {
|
|
72
68
|
for (const entry of this.events.values()) {
|
|
73
|
-
if (entry.status !== EventStatus.
|
|
69
|
+
if (entry.status !== EventStatus.Ready) continue;
|
|
74
70
|
if (entry.event.draw) continue; // Drawable events don't auto-trigger
|
|
75
71
|
if (entry.event.locationId && entry.event.locationId !== state.locationId) continue;
|
|
72
|
+
if (entry.event.conditions && !entry.event.conditions(state)) continue;
|
|
76
73
|
return entry.event;
|
|
77
74
|
}
|
|
78
75
|
return undefined;
|
|
79
76
|
}
|
|
80
77
|
|
|
81
|
-
/** Get all
|
|
78
|
+
/** Get all ready drawable events at the current location whose conditions are met */
|
|
82
79
|
getDrawableEvents(state: VylosGameState, resolveText?: (text: string | Record<string, string>) => string): DrawableEventEntry[] {
|
|
83
80
|
const result: DrawableEventEntry[] = [];
|
|
84
81
|
for (const entry of this.events.values()) {
|
|
85
|
-
if (entry.status !== EventStatus.
|
|
82
|
+
if (entry.status !== EventStatus.Ready) continue;
|
|
86
83
|
if (!entry.event.draw) continue;
|
|
87
84
|
if (entry.event.locationId && entry.event.locationId !== state.locationId) continue;
|
|
85
|
+
if (entry.event.conditions && !entry.event.conditions(state)) continue;
|
|
88
86
|
const draw = entry.event.draw;
|
|
89
87
|
const label = typeof draw.label === 'string'
|
|
90
88
|
? draw.label
|
|
@@ -107,16 +105,23 @@ export class EventManager {
|
|
|
107
105
|
}
|
|
108
106
|
}
|
|
109
107
|
|
|
110
|
-
/** Mark event as locked (
|
|
111
|
-
setLocked(id: string
|
|
108
|
+
/** Mark event as locked (permanently done) */
|
|
109
|
+
setLocked(id: string): void {
|
|
112
110
|
const entry = this.events.get(id);
|
|
113
111
|
if (entry) {
|
|
114
112
|
entry.status = EventStatus.Locked;
|
|
115
|
-
entry.event.locked?.(state);
|
|
116
113
|
logger.debug(`Event locked: ${id}`);
|
|
117
114
|
}
|
|
118
115
|
}
|
|
119
116
|
|
|
117
|
+
/** Mark event as ready (skip unlocked() gate) */
|
|
118
|
+
setReady(id: string): void {
|
|
119
|
+
const entry = this.events.get(id);
|
|
120
|
+
if (entry) {
|
|
121
|
+
entry.status = EventStatus.Ready;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
120
125
|
/** Reset an event back to NotReady */
|
|
121
126
|
reset(id: string): void {
|
|
122
127
|
const entry = this.events.get(id);
|
|
@@ -109,11 +109,11 @@ export interface VylosEvent<TState extends VylosGameState = VylosGameState> {
|
|
|
109
109
|
/** Whether this event should trigger — checked each game loop tick */
|
|
110
110
|
conditions?(state: TState): boolean;
|
|
111
111
|
|
|
112
|
-
/**
|
|
113
|
-
unlocked?(state: TState):
|
|
112
|
+
/** Gate from NotReady → Ready. Return false to stay NotReady, anything else = Ready. */
|
|
113
|
+
unlocked?(state: TState): boolean;
|
|
114
114
|
|
|
115
|
-
/** Called
|
|
116
|
-
locked?(state: TState):
|
|
115
|
+
/** Called after execution. Return true to permanently lock, anything else = stays Ready. */
|
|
116
|
+
locked?(state: TState): boolean;
|
|
117
117
|
|
|
118
118
|
/** The event's narrative logic */
|
|
119
119
|
execute(engine: VylosAPI, state: TState): Promise<void>;
|
|
@@ -121,12 +121,12 @@ export interface VylosEvent<TState extends VylosGameState = VylosGameState> {
|
|
|
121
121
|
|
|
122
122
|
/** Event lifecycle status */
|
|
123
123
|
export enum EventStatus {
|
|
124
|
-
/**
|
|
124
|
+
/** unlocked() gate not yet passed */
|
|
125
125
|
NotReady = 'not_ready',
|
|
126
|
-
/**
|
|
127
|
-
|
|
126
|
+
/** Ready to execute when conditions met */
|
|
127
|
+
Ready = 'ready',
|
|
128
128
|
/** Currently executing */
|
|
129
129
|
Running = 'running',
|
|
130
|
-
/**
|
|
130
|
+
/** Permanently locked (locked() returned true) */
|
|
131
131
|
Locked = 'locked',
|
|
132
132
|
}
|
package/src/setup.ts
CHANGED
|
@@ -106,6 +106,8 @@ export function setupVylos(options: SetupOptions): void {
|
|
|
106
106
|
|
|
107
107
|
const loopCallbacks: EngineLoopCallbacks = {
|
|
108
108
|
onTick(state) {
|
|
109
|
+
engineState.setLocation(state.locationId);
|
|
110
|
+
|
|
109
111
|
const locs = locationManager.getAccessibleFrom(state.locationId, state);
|
|
110
112
|
engineState.setLocations(locs.map(l => ({
|
|
111
113
|
id: l.id,
|
|
@@ -43,68 +43,121 @@ describe('EventManager', () => {
|
|
|
43
43
|
expect(em.getStatus('ev1')).toBe(EventStatus.NotReady);
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
describe('evaluate()', () => {
|
|
47
|
-
it('
|
|
46
|
+
describe('evaluate() — unlocked() gate', () => {
|
|
47
|
+
it('moves events with no unlocked() to Ready', () => {
|
|
48
48
|
em.register(makeEvent('ev1'));
|
|
49
|
-
const
|
|
50
|
-
expect(
|
|
51
|
-
expect(
|
|
52
|
-
expect(em.getStatus('ev1')).toBe(EventStatus.
|
|
49
|
+
const ready = em.evaluate(makeState());
|
|
50
|
+
expect(ready).toHaveLength(1);
|
|
51
|
+
expect(ready[0].id).toBe('ev1');
|
|
52
|
+
expect(em.getStatus('ev1')).toBe(EventStatus.Ready);
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
it('
|
|
55
|
+
it('moves events when unlocked() returns true', () => {
|
|
56
56
|
em.register(makeEvent('ev1', {
|
|
57
|
-
|
|
57
|
+
unlocked: () => true,
|
|
58
|
+
}));
|
|
59
|
+
const ready = em.evaluate(makeState());
|
|
60
|
+
expect(ready).toHaveLength(1);
|
|
61
|
+
expect(em.getStatus('ev1')).toBe(EventStatus.Ready);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('keeps events NotReady when unlocked() returns false', () => {
|
|
65
|
+
em.register(makeEvent('ev1', {
|
|
66
|
+
unlocked: (state) => state.flags['intro_done'] === true,
|
|
58
67
|
}));
|
|
59
68
|
|
|
60
69
|
expect(em.evaluate(makeState())).toHaveLength(0);
|
|
61
70
|
expect(em.getStatus('ev1')).toBe(EventStatus.NotReady);
|
|
62
71
|
|
|
63
|
-
const
|
|
64
|
-
expect(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
it('calls unlocked() callback when transitioning', () => {
|
|
68
|
-
const unlockedFn = vi.fn();
|
|
69
|
-
em.register(makeEvent('ev1', { unlocked: unlockedFn }));
|
|
70
|
-
em.evaluate(makeState());
|
|
71
|
-
expect(unlockedFn).toHaveBeenCalledOnce();
|
|
72
|
+
const ready = em.evaluate(makeState({ flags: { intro_done: true } }));
|
|
73
|
+
expect(ready).toHaveLength(1);
|
|
74
|
+
expect(em.getStatus('ev1')).toBe(EventStatus.Ready);
|
|
72
75
|
});
|
|
73
76
|
|
|
74
|
-
it('does not re-evaluate already
|
|
77
|
+
it('does not re-evaluate already Ready events', () => {
|
|
75
78
|
em.register(makeEvent('ev1'));
|
|
76
79
|
em.evaluate(makeState());
|
|
77
80
|
const second = em.evaluate(makeState());
|
|
78
81
|
expect(second).toHaveLength(0);
|
|
79
82
|
});
|
|
83
|
+
|
|
84
|
+
it('does not check conditions — only unlocked() gate', () => {
|
|
85
|
+
em.register(makeEvent('ev1', {
|
|
86
|
+
conditions: () => false,
|
|
87
|
+
}));
|
|
88
|
+
// evaluate() ignores conditions — event goes Ready anyway
|
|
89
|
+
const ready = em.evaluate(makeState());
|
|
90
|
+
expect(ready).toHaveLength(1);
|
|
91
|
+
expect(em.getStatus('ev1')).toBe(EventStatus.Ready);
|
|
92
|
+
});
|
|
80
93
|
});
|
|
81
94
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
95
|
+
describe('getNextUnlocked() — conditions checked at query time', () => {
|
|
96
|
+
it('returns first Ready event with no conditions', () => {
|
|
97
|
+
em.register(makeEvent('ev1'));
|
|
98
|
+
em.evaluate(makeState());
|
|
99
|
+
const next = em.getNextUnlocked(makeState());
|
|
100
|
+
expect(next?.id).toBe('ev1');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('skips Ready event when conditions return false', () => {
|
|
104
|
+
em.register(makeEvent('ev1', {
|
|
105
|
+
conditions: () => false,
|
|
106
|
+
}));
|
|
107
|
+
em.register(makeEvent('ev2'));
|
|
108
|
+
em.evaluate(makeState());
|
|
109
|
+
|
|
110
|
+
const next = em.getNextUnlocked(makeState());
|
|
111
|
+
expect(next?.id).toBe('ev2');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('returns Ready event when conditions return true', () => {
|
|
115
|
+
em.register(makeEvent('ev1', {
|
|
116
|
+
conditions: (state) => state.flags['go'] === true,
|
|
117
|
+
}));
|
|
118
|
+
em.evaluate(makeState());
|
|
88
119
|
|
|
89
|
-
|
|
90
|
-
|
|
120
|
+
expect(em.getNextUnlocked(makeState())).toBeUndefined();
|
|
121
|
+
expect(em.getNextUnlocked(makeState({ flags: { go: true } }))?.id).toBe('ev1');
|
|
122
|
+
});
|
|
91
123
|
});
|
|
92
124
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
125
|
+
describe('lifecycle: NotReady → Ready → Running → Ready/Locked', () => {
|
|
126
|
+
it('repeatable event: Ready → Running → Ready', () => {
|
|
127
|
+
em.register(makeEvent('ev1'));
|
|
96
128
|
|
|
97
|
-
|
|
129
|
+
expect(em.getStatus('ev1')).toBe(EventStatus.NotReady);
|
|
98
130
|
|
|
99
|
-
|
|
100
|
-
|
|
131
|
+
em.evaluate(makeState());
|
|
132
|
+
expect(em.getStatus('ev1')).toBe(EventStatus.Ready);
|
|
101
133
|
|
|
102
|
-
|
|
103
|
-
|
|
134
|
+
em.setRunning('ev1');
|
|
135
|
+
expect(em.getStatus('ev1')).toBe(EventStatus.Running);
|
|
104
136
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
137
|
+
em.setReady('ev1');
|
|
138
|
+
expect(em.getStatus('ev1')).toBe(EventStatus.Ready);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('one-shot event: Ready → Running → Locked', () => {
|
|
142
|
+
em.register(makeEvent('ev1'));
|
|
143
|
+
|
|
144
|
+
em.evaluate(makeState());
|
|
145
|
+
em.setRunning('ev1');
|
|
146
|
+
em.setLocked('ev1');
|
|
147
|
+
expect(em.getStatus('ev1')).toBe(EventStatus.Locked);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('setLocked is a pure status setter (no callback)', () => {
|
|
151
|
+
const lockedFn = vi.fn();
|
|
152
|
+
em.register(makeEvent('ev1', { locked: lockedFn }));
|
|
153
|
+
|
|
154
|
+
em.evaluate(makeState());
|
|
155
|
+
em.setRunning('ev1');
|
|
156
|
+
em.setLocked('ev1');
|
|
157
|
+
expect(em.getStatus('ev1')).toBe(EventStatus.Locked);
|
|
158
|
+
// locked() is NOT called by setLocked — Engine calls it explicitly
|
|
159
|
+
expect(lockedFn).not.toHaveBeenCalled();
|
|
160
|
+
});
|
|
108
161
|
});
|
|
109
162
|
|
|
110
163
|
it('getByLocation filters events', () => {
|
|
@@ -123,8 +176,8 @@ describe('EventManager', () => {
|
|
|
123
176
|
em.register(makeEvent('ev3'));
|
|
124
177
|
|
|
125
178
|
em.evaluate(makeState());
|
|
126
|
-
em.setLocked('ev1'
|
|
127
|
-
em.setLocked('ev3'
|
|
179
|
+
em.setLocked('ev1');
|
|
180
|
+
em.setLocked('ev3');
|
|
128
181
|
|
|
129
182
|
const ids = em.getLockedIds();
|
|
130
183
|
expect(ids).toEqual(['ev1', 'ev3']);
|
|
@@ -139,8 +192,21 @@ describe('EventManager', () => {
|
|
|
139
192
|
it('reset puts event back to NotReady', () => {
|
|
140
193
|
em.register(makeEvent('ev1'));
|
|
141
194
|
em.evaluate(makeState());
|
|
142
|
-
expect(em.getStatus('ev1')).toBe(EventStatus.
|
|
195
|
+
expect(em.getStatus('ev1')).toBe(EventStatus.Ready);
|
|
143
196
|
em.reset('ev1');
|
|
144
197
|
expect(em.getStatus('ev1')).toBe(EventStatus.NotReady);
|
|
145
198
|
});
|
|
199
|
+
|
|
200
|
+
it('setReady puts event directly to Ready (skips gate)', () => {
|
|
201
|
+
em.register(makeEvent('ev1', {
|
|
202
|
+
unlocked: () => false,
|
|
203
|
+
}));
|
|
204
|
+
// Gate blocks it
|
|
205
|
+
em.evaluate(makeState());
|
|
206
|
+
expect(em.getStatus('ev1')).toBe(EventStatus.NotReady);
|
|
207
|
+
|
|
208
|
+
// setReady bypasses gate
|
|
209
|
+
em.setReady('ev1');
|
|
210
|
+
expect(em.getStatus('ev1')).toBe(EventStatus.Ready);
|
|
211
|
+
});
|
|
146
212
|
});
|
|
@@ -69,6 +69,7 @@ describe('Game loop integration', () => {
|
|
|
69
69
|
|
|
70
70
|
const event: VylosEvent = {
|
|
71
71
|
id: 'intro',
|
|
72
|
+
locked: () => true,
|
|
72
73
|
async execute(api: VylosAPI) {
|
|
73
74
|
await api.say('Hello');
|
|
74
75
|
await api.say('World');
|
|
@@ -130,6 +131,7 @@ describe('Game loop integration', () => {
|
|
|
130
131
|
const events: VylosEvent[] = [
|
|
131
132
|
{
|
|
132
133
|
id: 'first',
|
|
134
|
+
locked: () => true,
|
|
133
135
|
async execute(api: VylosAPI) {
|
|
134
136
|
await api.say('In first event');
|
|
135
137
|
api.jump('second');
|
|
@@ -137,6 +139,7 @@ describe('Game loop integration', () => {
|
|
|
137
139
|
},
|
|
138
140
|
{
|
|
139
141
|
id: 'second',
|
|
142
|
+
locked: () => true,
|
|
140
143
|
async execute(api: VylosAPI) {
|
|
141
144
|
await api.say('In second event');
|
|
142
145
|
},
|