Refactor Eval context
This commit is contained in:
parent
6b19eebd53
commit
a52762cc18
|
|
@ -304,10 +304,59 @@ export class SeededRandom {
|
||||||
*/
|
*/
|
||||||
clone(): SeededRandom {
|
clone(): SeededRandom {
|
||||||
const child = new SeededRandom(this.getState());
|
const child = new SeededRandom(this.getState());
|
||||||
this.next(); // diverge parent so the two streams never overlap
|
this.jump(); // diverge parent so the two streams never overlap
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advance the state by 2^64 steps (equivalent to 2^64 calls to next()).
|
||||||
|
* Use this to generate 2^64 non-overlapping subsequences for parallel work.
|
||||||
|
*/
|
||||||
|
jump(): void {
|
||||||
|
const JUMP = [0x8764000b, 0xf542d2d3, 0x6fa035c3, 0x77f2db5b];
|
||||||
|
let s0 = 0, s1 = 0, s2 = 0, s3 = 0;
|
||||||
|
for (const j of JUMP) {
|
||||||
|
for (let b = 0; b < 32; b++) {
|
||||||
|
if ((j >>> b) & 1) {
|
||||||
|
s0 ^= this.s[0];
|
||||||
|
s1 ^= this.s[1];
|
||||||
|
s2 ^= this.s[2];
|
||||||
|
s3 ^= this.s[3];
|
||||||
|
}
|
||||||
|
this.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.s[0] = s0 >>> 0;
|
||||||
|
this.s[1] = s1 >>> 0;
|
||||||
|
this.s[2] = s2 >>> 0;
|
||||||
|
this.s[3] = s3 >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advance the state by 2^96 steps (equivalent to 2^96 calls to next()).
|
||||||
|
* Generates 2^32 starting points; each jump() from those gives 2^32
|
||||||
|
* non-overlapping subsequences — useful for distributed computation.
|
||||||
|
*/
|
||||||
|
longJump(): void {
|
||||||
|
const LONG_JUMP = [0xb523952e, 0x0b6f099f, 0xccf5a0ef, 0x1c580662];
|
||||||
|
let s0 = 0, s1 = 0, s2 = 0, s3 = 0;
|
||||||
|
for (const j of LONG_JUMP) {
|
||||||
|
for (let b = 0; b < 32; b++) {
|
||||||
|
if ((j >>> b) & 1) {
|
||||||
|
s0 ^= this.s[0];
|
||||||
|
s1 ^= this.s[1];
|
||||||
|
s2 ^= this.s[2];
|
||||||
|
s3 ^= this.s[3];
|
||||||
|
}
|
||||||
|
this.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.s[0] = s0 >>> 0;
|
||||||
|
this.s[1] = s1 >>> 0;
|
||||||
|
this.s[2] = s2 >>> 0;
|
||||||
|
this.s[3] = s3 >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Serialisation
|
// Serialisation
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ abstract class BaseEffect extends Component<{
|
||||||
condition?: string;
|
condition?: string;
|
||||||
stacking?: 'stack' | 'unique' | 'replace';
|
stacking?: 'stack' | 'unique' | 'replace';
|
||||||
tag?: string;
|
tag?: string;
|
||||||
perSecond?: boolean;
|
|
||||||
}) {
|
}) {
|
||||||
super({
|
super({
|
||||||
target: typeof opts.target === 'string'
|
target: typeof opts.target === 'string'
|
||||||
|
|
@ -45,6 +44,18 @@ abstract class BaseEffect extends Component<{
|
||||||
stacking: opts.stacking ?? 'stack',
|
stacking: opts.stacking ?? 'stack',
|
||||||
tag: opts.tag ?? null,
|
tag: opts.tag ?? null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (opts.gradual && opts.duration == null) {
|
||||||
|
throw new Error('Effect cannot be gradual without a duration');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.duration != null && opts.duration <= 0) {
|
||||||
|
throw new Error('Effect duration must be greater than zero');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.permanent && opts.duration != null) {
|
||||||
|
throw new Error('Effect cannot be both permanent and have a duration');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,6 +68,13 @@ export class Effect extends BaseEffect {
|
||||||
override onAdd(): void {
|
override onAdd(): void {
|
||||||
const { stacking, tag } = this.state;
|
const { stacking, tag } = this.state;
|
||||||
|
|
||||||
|
if (this.state.permanent && this.state.condition) {
|
||||||
|
if (!evaluateCondition(this.state.condition, this.entity)) {
|
||||||
|
this.entity.remove(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (tag != null && stacking !== 'stack') {
|
if (tag != null && stacking !== 'stack') {
|
||||||
const siblings = this.entity.getAll(Effect).filter(e => e !== this && e.state.tag === tag);
|
const siblings = this.entity.getAll(Effect).filter(e => e !== this && e.state.tag === tag);
|
||||||
|
|
||||||
|
|
@ -79,6 +97,10 @@ export class Effect extends BaseEffect {
|
||||||
this.applyDelta(this.state.delta);
|
this.applyDelta(this.state.delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state.permanent) {
|
||||||
|
this.entity.remove(this); // permanent effects are removed immediately
|
||||||
|
}
|
||||||
|
|
||||||
if (this.state.remaining || this.state.condition) {
|
if (this.state.remaining || this.state.condition) {
|
||||||
this.ensureEffectSystem();
|
this.ensureEffectSystem();
|
||||||
}
|
}
|
||||||
|
|
@ -112,7 +134,7 @@ export class Effect extends BaseEffect {
|
||||||
this.emit('expired');
|
this.emit('expired');
|
||||||
}
|
}
|
||||||
|
|
||||||
update(dt: number, ctx: EvalContext): void {
|
update(dt: number): void {
|
||||||
if (this.state.remaining != null) {
|
if (this.state.remaining != null) {
|
||||||
if (this.state.remaining > 0) {
|
if (this.state.remaining > 0) {
|
||||||
this.state.remaining -= dt;
|
this.state.remaining -= dt;
|
||||||
|
|
@ -126,7 +148,7 @@ export class Effect extends BaseEffect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (this.state.condition != null) {
|
} else if (this.state.condition != null) {
|
||||||
if (!evaluateCondition(this.state.condition, ctx)) {
|
if (!evaluateCondition(this.state.condition, this.entity)) {
|
||||||
this.clear();
|
this.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,7 @@ export class Inventory extends Component<InventoryState> {
|
||||||
* `slotId` specifies which inventory slot to use from (otherwise any slot is used).
|
* `slotId` specifies which inventory slot to use from (otherwise any slot is used).
|
||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
use(arg?: string | { itemId?: string; slotId?: SlotId }, ctx?: EvalContext): boolean {
|
use(arg?: string | { itemId?: string; slotId?: SlotId }): boolean {
|
||||||
const resolved = this.#resolveItem(arg);
|
const resolved = this.#resolveItem(arg);
|
||||||
if (!resolved) return false;
|
if (!resolved) return false;
|
||||||
const { itemId, slotId } = resolved;
|
const { itemId, slotId } = resolved;
|
||||||
|
|
@ -257,7 +257,7 @@ export class Inventory extends Component<InventoryState> {
|
||||||
|
|
||||||
if (usable.consumeOnUse) this.remove({ itemId, amount: 1, slotId });
|
if (usable.consumeOnUse) this.remove({ itemId, amount: 1, slotId });
|
||||||
|
|
||||||
usable.use(ctx ?? this.context);
|
usable.use(this.entity);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,11 +46,9 @@ export class Usable extends Component<UsableState> {
|
||||||
@variable get consumeOnUse(): boolean { return this.state.consumeOnUse; }
|
@variable get consumeOnUse(): boolean { return this.state.consumeOnUse; }
|
||||||
|
|
||||||
@action
|
@action
|
||||||
use(arg?: EvalContext, ctx?: EvalContext): void {
|
use(user: EvalContext): void {
|
||||||
ctx = arg ?? ctx ?? this.context;
|
|
||||||
if (!ctx) return;
|
|
||||||
for (const action of this.state.actions) {
|
for (const action of this.state.actions) {
|
||||||
executeAction(action, ctx);
|
executeAction(action, user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,7 @@ interface WorldEvent<T = unknown> {
|
||||||
type EntityEventHandler = <T>(event: EntityEvent<T>) => void;
|
type EntityEventHandler = <T>(event: EntityEvent<T>) => void;
|
||||||
type WorldEventHandler = <T>(event: WorldEvent<T>) => void;
|
type WorldEventHandler = <T>(event: WorldEvent<T>) => void;
|
||||||
|
|
||||||
export interface EvalContext {
|
export type EvalContext = Entity | World;
|
||||||
self: Entity | World;
|
|
||||||
world: World;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Symbol used by Serialization to access World's entity counter. */
|
/** Symbol used by Serialization to access World's entity counter. */
|
||||||
export const WORLD_ENTITY_COUNTER = Symbol('rpg.world.entityCounter');
|
export const WORLD_ENTITY_COUNTER = Symbol('rpg.world.entityCounter');
|
||||||
|
|
@ -96,10 +93,6 @@ export abstract class Component<TState = Record<string, unknown>> {
|
||||||
}
|
}
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
get context(): EvalContext {
|
|
||||||
return this.entity.context;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class System {
|
export abstract class System {
|
||||||
|
|
@ -118,10 +111,6 @@ export class Entity {
|
||||||
readonly world: World,
|
readonly world: World,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
get context(): EvalContext {
|
|
||||||
return { self: this, world: this.world };
|
|
||||||
}
|
|
||||||
|
|
||||||
add<T extends Component<any>>(component: T, k?: string): T {
|
add<T extends Component<any>>(component: T, k?: string): T {
|
||||||
const key = k ?? Symbol();
|
const key = k ?? Symbol();
|
||||||
|
|
||||||
|
|
@ -291,10 +280,7 @@ export class World {
|
||||||
set [WORLD_ENTITY_COUNTER](n: number) { this.#entityCounter = n; }
|
set [WORLD_ENTITY_COUNTER](n: number) { this.#entityCounter = n; }
|
||||||
|
|
||||||
get id() { return 'world'; }
|
get id() { return 'world'; }
|
||||||
|
get world() { return this; }
|
||||||
get context(): EvalContext {
|
|
||||||
return { self: this, world: this };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new entity and add it to the world.
|
* Create a new entity and add it to the world.
|
||||||
|
|
@ -460,16 +446,6 @@ export class World {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isEvalContext(v: unknown): v is EvalContext {
|
export const isEvalContext = (x: unknown): x is EvalContext => (
|
||||||
return typeof v === 'object' && v != null
|
x instanceof Entity || x instanceof World
|
||||||
&& (
|
);
|
||||||
(v as EvalContext).self instanceof Entity
|
|
||||||
|| (v as EvalContext).self instanceof World
|
|
||||||
)
|
|
||||||
&& (v as EvalContext).world instanceof World;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Narrows an {@link EvalContext} to one where `self` is an `Entity`, not a `World`. */
|
|
||||||
export function isEntityContext(ctx: EvalContext): ctx is { self: Entity; world: World } {
|
|
||||||
return ctx.self instanceof Entity;
|
|
||||||
}
|
|
||||||
|
|
@ -231,7 +231,7 @@ export class DialogEngine {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getVariables(): RPGVariables {
|
private getVariables(): RPGVariables {
|
||||||
if (isEvalContext(this.options)) return resolveVariables(this.options.self);
|
if (isEvalContext(this.options)) return resolveVariables(this.options);
|
||||||
return this.options.variables;
|
return this.options.variables;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,11 @@ export class CombatSystem extends System {
|
||||||
if (!random) {
|
if (!random) {
|
||||||
random = getWorldRandom(world);
|
random = getWorldRandom(world);
|
||||||
}
|
}
|
||||||
damageAmount += random.use(r => r.nextInt(-variance, variance));
|
const variedDamage = random.use(r => r.nextInt(-variance, variance));
|
||||||
|
|
||||||
|
if (variedDamage > 0) {
|
||||||
|
damageAmount += variedDamage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const crit = source.get(Crit);
|
const crit = source.get(Crit);
|
||||||
|
|
@ -69,9 +73,11 @@ export class CombatSystem extends System {
|
||||||
|
|
||||||
const defense = target.get(Defense, (c) => c.state.damageType === damageType);
|
const defense = target.get(Defense, (c) => c.state.damageType === damageType);
|
||||||
if (defense) {
|
if (defense) {
|
||||||
damageAmount = Math.max(minDamage, damageAmount - defense.value);
|
damageAmount -= defense.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
damageAmount = Math.max(0, minDamage, damageAmount);
|
||||||
|
|
||||||
// Apply on-hit effects from source onto target
|
// Apply on-hit effects from source onto target
|
||||||
for (const component of source.getAll(EffectTemplate)) {
|
for (const component of source.getAll(EffectTemplate)) {
|
||||||
const s = component.state;
|
const s = component.state;
|
||||||
|
|
@ -82,6 +88,8 @@ export class CombatSystem extends System {
|
||||||
delta: s.delta,
|
delta: s.delta,
|
||||||
targetField: s.targetField,
|
targetField: s.targetField,
|
||||||
duration: s.duration ?? undefined,
|
duration: s.duration ?? undefined,
|
||||||
|
permanent: s.permanent,
|
||||||
|
gradual: s.gradual,
|
||||||
condition: s.condition ?? undefined,
|
condition: s.condition ?? undefined,
|
||||||
stacking: s.stacking,
|
stacking: s.stacking,
|
||||||
tag: s.tag ?? undefined,
|
tag: s.tag ?? undefined,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export class EffectSystem extends System {
|
||||||
const expired: [Entity, Component<any>][] = [];
|
const expired: [Entity, Component<any>][] = [];
|
||||||
|
|
||||||
for (const [entity, , effect] of world.query(Effect)) {
|
for (const [entity, , effect] of world.query(Effect)) {
|
||||||
effect.update(dt, entity.context);
|
effect.update(dt);
|
||||||
if (effect.state.remaining !== null && effect.state.remaining <= 0) {
|
if (effect.state.remaining !== null && effect.state.remaining <= 0) {
|
||||||
expired.push([entity, effect]);
|
expired.push([entity, effect]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,11 +132,10 @@ export class QuestSystem extends System {
|
||||||
const tracking = this.#tracking.get(entity.id);
|
const tracking = this.#tracking.get(entity.id);
|
||||||
if (!tracking || tracking.varToQuests.size === 0) return;
|
if (!tracking || tracking.varToQuests.size === 0) return;
|
||||||
|
|
||||||
const ctx: EvalContext = { self: entity, world };
|
|
||||||
const dirty = new Set<string>();
|
const dirty = new Set<string>();
|
||||||
|
|
||||||
for (const [varName, questIds] of tracking.varToQuests) {
|
for (const [varName, questIds] of tracking.varToQuests) {
|
||||||
const curr = resolveVariable(varName, ctx);
|
const curr = resolveVariable(varName, entity);
|
||||||
const prev = tracking.snapshot.get(varName);
|
const prev = tracking.snapshot.get(varName);
|
||||||
// Treat "not yet in snapshot" as dirty — catches conditions already met at init
|
// Treat "not yet in snapshot" as dirty — catches conditions already met at init
|
||||||
if (!tracking.snapshot.has(varName) || curr !== prev) {
|
if (!tracking.snapshot.has(varName) || curr !== prev) {
|
||||||
|
|
@ -152,8 +151,6 @@ export class QuestSystem extends System {
|
||||||
const questLog = entity.get(QuestLog);
|
const questLog = entity.get(QuestLog);
|
||||||
if (!questLog) return;
|
if (!questLog) return;
|
||||||
|
|
||||||
const ctx: EvalContext = { self: entity, world };
|
|
||||||
|
|
||||||
for (const [questId, { quest, state }] of questLog.entries()) {
|
for (const [questId, { quest, state }] of questLog.entries()) {
|
||||||
if (state.status !== 'active') continue;
|
if (state.status !== 'active') continue;
|
||||||
if (filter !== 'all' && !filter.has(questId)) continue;
|
if (filter !== 'all' && !filter.has(questId)) continue;
|
||||||
|
|
@ -164,14 +161,14 @@ export class QuestSystem extends System {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stage.failConditions?.some(c => evaluateCondition(c, ctx))) {
|
if (stage.failConditions?.some(c => evaluateCondition(c, entity))) {
|
||||||
questLog.fail(questId);
|
questLog.fail(questId);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!stage.objectives.every(o => evaluateCondition(o.condition, ctx))) continue;
|
if (!stage.objectives.every(o => evaluateCondition(o.condition, entity))) continue;
|
||||||
|
|
||||||
for (const action of stage.actions) executeAction(action, ctx);
|
for (const action of stage.actions) executeAction(action, entity);
|
||||||
questLog._advance(questId);
|
questLog._advance(questId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,12 @@ export const ACTION_KEYS = Symbol('rpg.actions');
|
||||||
export const VARIABLE_KEYS = Symbol('rpg.variables');
|
export const VARIABLE_KEYS = Symbol('rpg.variables');
|
||||||
export const STATE_KEYS = Symbol('rpg.state_variables');
|
export const STATE_KEYS = Symbol('rpg.state_variables');
|
||||||
|
|
||||||
export function action<T extends (arg?: any, ctx?: any) => unknown>(
|
export function action<T extends (arg?: any) => unknown>(
|
||||||
target: T,
|
_target: T,
|
||||||
context: ClassMethodDecoratorContext<unknown, T>
|
context: ClassMethodDecoratorContext<unknown, T>
|
||||||
): T {
|
): void {
|
||||||
const prev = context.metadata[ACTION_KEYS] as Set<string | symbol> | undefined;
|
const prev = context.metadata[ACTION_KEYS] as Set<string | symbol> | undefined;
|
||||||
context.metadata[ACTION_KEYS] = new Set(prev).add(context.name);
|
context.metadata[ACTION_KEYS] = new Set(prev).add(context.name);
|
||||||
return function (this: any, arg?: any, ctx?: any) {
|
|
||||||
return target.call(this, arg, ctx ?? this.context);
|
|
||||||
} as unknown as T;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type VariableContext<T extends RPGVariables[string]> =
|
type VariableContext<T extends RPGVariables[string]> =
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ export function resolveVariable(name: string, ctx: EvalContext): RPGVariables[st
|
||||||
return resolveVariables(entity)[varName];
|
return resolveVariables(entity)[varName];
|
||||||
}
|
}
|
||||||
// bare name → self entity
|
// bare name → self entity
|
||||||
return resolveVariables(ctx.self)[name];
|
return resolveVariables(ctx)[name];
|
||||||
}
|
}
|
||||||
export function resolveActions(target: Entity | World): RPGActions {
|
export function resolveActions(target: Entity | World): RPGActions {
|
||||||
const result: RPGActions = {};
|
const result: RPGActions = {};
|
||||||
|
|
@ -76,21 +76,14 @@ export function resolveActions(target: Entity | World): RPGActions {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Contextable {
|
export function executeAction(action: RPGAction, ctx: EvalContext): unknown {
|
||||||
readonly context: EvalContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function executeAction(action: RPGAction, ctx: EvalContext | Contextable): unknown {
|
|
||||||
if (typeof action === 'string') {
|
if (typeof action === 'string') {
|
||||||
action = { type: action };
|
action = { type: action };
|
||||||
}
|
}
|
||||||
if ('context' in ctx) {
|
|
||||||
ctx = ctx.context;
|
|
||||||
}
|
|
||||||
if (!action.type) {
|
if (!action.type) {
|
||||||
throw new TypeError(`[executeAction] action object is missing a 'type' property`);
|
throw new TypeError(`[executeAction] action object is missing a 'type' property`);
|
||||||
}
|
}
|
||||||
let entity = ctx.self;
|
let entity = ctx;
|
||||||
let actionType = action.type;
|
let actionType = action.type;
|
||||||
|
|
||||||
// @entityId.component.action → dispatch to another entity
|
// @entityId.component.action → dispatch to another entity
|
||||||
|
|
|
||||||
|
|
@ -227,19 +227,19 @@ describe('QuestLog — stage access', () => {
|
||||||
const { player, vars, questLog } = makePlayer(w, [simpleQuest()]);
|
const { player, vars, questLog } = makePlayer(w, [simpleQuest()]);
|
||||||
questLog.start('q1');
|
questLog.start('q1');
|
||||||
|
|
||||||
const before = questLog.getObjectiveProgress('q1', player.context);
|
const before = questLog.getObjectiveProgress('q1', player);
|
||||||
expect(before).toHaveLength(1);
|
expect(before).toHaveLength(1);
|
||||||
expect(before![0].done).toBeFalse();
|
expect(before![0].done).toBeFalse();
|
||||||
|
|
||||||
vars.set({ key: 'done', value: true });
|
vars.set({ key: 'done', value: true });
|
||||||
const after = questLog.getObjectiveProgress('q1', player.context);
|
const after = questLog.getObjectiveProgress('q1', player);
|
||||||
expect(after![0].done).toBeTrue();
|
expect(after![0].done).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getObjectiveProgress returns undefined when not active', () => {
|
it('getObjectiveProgress returns undefined when not active', () => {
|
||||||
const w = world();
|
const w = world();
|
||||||
const { player, questLog } = makePlayer(w, [simpleQuest()]);
|
const { player, questLog } = makePlayer(w, [simpleQuest()]);
|
||||||
expect(questLog.getObjectiveProgress('q1', player.context)).toBeUndefined();
|
expect(questLog.getObjectiveProgress('q1', player)).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -249,14 +249,14 @@ describe('QuestLog — availability', () => {
|
||||||
it('quest with no conditions is always available', () => {
|
it('quest with no conditions is always available', () => {
|
||||||
const w = world();
|
const w = world();
|
||||||
const { player, questLog } = makePlayer(w, [simpleQuest()]);
|
const { player, questLog } = makePlayer(w, [simpleQuest()]);
|
||||||
expect(questLog.isAvailable('q1', player.context)).toBeTrue();
|
expect(questLog.isAvailable('q1', player)).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('quest with unsatisfied condition is not available', () => {
|
it('quest with unsatisfied condition is not available', () => {
|
||||||
const w = world();
|
const w = world();
|
||||||
const quest: Quest = { ...simpleQuest(), conditions: ['Variables.unlocked == true'] };
|
const quest: Quest = { ...simpleQuest(), conditions: ['Variables.unlocked == true'] };
|
||||||
const { player, questLog } = makePlayer(w, [quest]);
|
const { player, questLog } = makePlayer(w, [quest]);
|
||||||
expect(questLog.isAvailable('q1', player.context)).toBeFalse();
|
expect(questLog.isAvailable('q1', player)).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('quest with satisfied condition is available', () => {
|
it('quest with satisfied condition is available', () => {
|
||||||
|
|
@ -264,7 +264,7 @@ describe('QuestLog — availability', () => {
|
||||||
const quest: Quest = { ...simpleQuest(), conditions: ['Variables.unlocked == true'] };
|
const quest: Quest = { ...simpleQuest(), conditions: ['Variables.unlocked == true'] };
|
||||||
const { player, vars, questLog } = makePlayer(w, [quest]);
|
const { player, vars, questLog } = makePlayer(w, [quest]);
|
||||||
vars.set({ key: 'unlocked', value: true });
|
vars.set({ key: 'unlocked', value: true });
|
||||||
expect(questLog.isAvailable('q1', player.context)).toBeTrue();
|
expect(questLog.isAvailable('q1', player)).toBeTrue();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue