Skip to content

Commit

Permalink
fix(state): handle not-yet-valid verbs better (#99), invoke room verb…
Browse files Browse the repository at this point in the history
…s correctly, do not overwrite signal scripts
  • Loading branch information
ssube committed Jun 3, 2021
1 parent c7bfa2d commit ab595f3
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 55 deletions.
8 changes: 7 additions & 1 deletion data/base.yml
Expand Up @@ -145,8 +145,10 @@ worlds:
{{item.meta.name}} has been used by {{actor.meta.name}}!
verbs:
world:
slog: slog
swing: swing
verbs:
- verbs.world.slog
- verbs.world.swing
meta:
id: test
Expand Down Expand Up @@ -393,4 +395,8 @@ worlds:
signal.step:
data: !map {}
name:
base: room-step
base: room-step
verbs.world.slog:
data: !map {}
name:
base: verb-move
6 changes: 4 additions & 2 deletions src/script/signal/actor/ActorStep.ts
Expand Up @@ -4,6 +4,7 @@ import { ActorType, isActor } from '../../../model/entity/Actor';
import { ScriptContext, ScriptTarget } from '../../../service/script';
import { getKey } from '../../../util/collection/map';
import { STAT_HEALTH } from '../../../util/constants';
import { getScripts } from '../../../util/state';

export async function ActorStep(this: ScriptTarget, context: ScriptContext): Promise<void> {
context.logger.debug({
Expand Down Expand Up @@ -31,7 +32,8 @@ export async function ActorStep(this: ScriptTarget, context: ScriptContext): Pro

const { command } = context;

if (this.scripts.has(command.verb) === false) {
const scripts = getScripts(context.state, this);
if (scripts.has(command.verb) === false) {
await context.stateHelper.show('actor.step.command.unknown', { actor: this, command });
context.logger.warn({ command }, 'unknown verb');
return;
Expand All @@ -45,5 +47,5 @@ export async function ActorStep(this: ScriptTarget, context: ScriptContext): Pro
}
}

await context.script.invoke(this, command.verb, context);
return context.script.invoke(this, command.verb, context);
}
37 changes: 23 additions & 14 deletions src/service/script/LocalScript.ts
Expand Up @@ -19,7 +19,7 @@ import {
ActorStepUse,
ActorStepWait,
} from '../../script/verb/common';
import { SearchParams, searchState } from '../../util/state';
import { getScripts, SearchParams, searchState } from '../../util/state';

/**
* Common scripts, built into the engine and always available.
Expand Down Expand Up @@ -61,27 +61,36 @@ export class LocalScriptService implements ScriptService {
public async invoke(target: ScriptTarget, slot: string, scope: SuppliedScope): Promise<void> {
this.logger.debug({ slot, target }, 'trying to invoke slot on target');

const scriptRef = target.scripts.get(slot);
const scripts = getScripts(scope.state, target);
const scriptRef = scripts.get(slot);

if (isNil(scriptRef)) {
this.logger.debug({ slot, target }, 'target does not have a script defined for slot');
this.logger.debug({ slot, scripts, target }, 'target does not have a script defined for slot');
return;
}

const script = this.scripts.get(scriptRef.name);
if (isNil(script)) {
this.logger.error({ scriptName: scriptRef }, 'unknown script name');
const scriptName = this.scripts.get(scriptRef.name);
if (isNil(scriptName)) {
this.logger.error({
scriptRef,
scripts: Array.from(scripts.keys()),
}, 'unknown script name');
return;
}

this.logger.debug({ script: scriptRef.name, target: target.meta.id }, 'invoking script on target');
this.logger.debug({ scriptRef, target }, 'invoking script on target');

await script.call(target, {
...scope,
logger: this.logger.child({
script: scriptRef,
}),
script: this,
});
try {
await scriptName.call(target, {
...scope,
logger: this.logger.child({
script: scriptRef.name,
}),
script: this,
});
} catch (err) {
this.logger.error(err, 'error invoking script');
}
}

public async broadcast(filter: Partial<SearchParams>, slot: string, scope: SuppliedScope): Promise<void> {
Expand Down
56 changes: 27 additions & 29 deletions src/service/state/TurnState.ts
Expand Up @@ -9,12 +9,11 @@ import { DataFile } from '../../model/file/Data';
import { WorldState } from '../../model/world/State';
import { WorldTemplate } from '../../model/world/Template';
import { INJECT_COUNTER, INJECT_EVENT, INJECT_LOGGER, INJECT_RANDOM, INJECT_SCRIPT } from '../../module';
import { StateSource, ShowVolume } from '../../util/actor';
import { ShowVolume, StateSource } from '../../util/actor';
import { catchAndLog, onceEvent } from '../../util/async/event';
import { randomItem } from '../../util/collection/array';
import { StackMap } from '../../util/collection/StackMap';
import {
COMMON_VERBS,
EVENT_ACTOR_COMMAND,
EVENT_ACTOR_JOIN,
EVENT_COMMON_QUIT,
Expand All @@ -36,8 +35,10 @@ import {
META_SAVE,
META_WORLDS,
SLOT_STEP,
VERB_PREFIX,
VERB_WAIT,
} from '../../util/constants';
import { getScripts } from '../../util/state';
import { debugState, graphState } from '../../util/state/debug';
import { StateEntityGenerator } from '../../util/state/EntityGenerator';
import { StateEntityTransfer } from '../../util/state/EntityTransfer';
Expand Down Expand Up @@ -276,18 +277,31 @@ export class LocalStateService implements StateService {
await this.doWorlds();
break;
default: {
// TODO: proper wait, don't assume player goes last
if (mustExist(event.actor).actorType !== ActorType.PLAYER) {
return;
}

// step world
const result = await this.step();
this.event.emit('state-step', result);
await this.doStep(actor);
}
}
}

public async doStep(actor: Optional<Actor>): Promise<void> {
// TODO: proper wait, don't assume player goes last
if (isNil(actor)) {
return;
}

if (actor.actorType !== ActorType.PLAYER) {
return;
}

if (isNil(this.state)) {
return;
}

// step world
const result = await this.step();
this.event.emit('state-step', result);

}

public async doCreate(target: string, depth: number): Promise<void> {
const [id, seed] = target.split(' ');
const state = await this.create({
Expand Down Expand Up @@ -350,26 +364,10 @@ export class LocalStateService implements StateService {
});
}

/**
* @todo collect verbs from room, items
* @todo prevent/remove verbs
*/
public async getVerbs(actor: Optional<Actor>): Promise<ReadonlyArray<string>> {
if (doesExist(actor)) {
const verbs = new Set<string>(COMMON_VERBS);
for (const key of actor.scripts.keys()) {
if (key.startsWith('verbs.')) {
verbs.add(key);
}
}
return Array.from(verbs);
} else {
return COMMON_VERBS;
}
}

public async doHelp(actor: Optional<Actor>): Promise<void> {
const verbs = (await this.getVerbs(actor))
const scripts = getScripts(this.state, actor);
const verbs = Array.from(scripts.keys())
.filter((it) => it.startsWith(VERB_PREFIX))
.map((it) => `$t(${it})`)
.join(', ');

Expand Down
18 changes: 12 additions & 6 deletions src/util/constants.ts
Expand Up @@ -51,6 +51,8 @@ export const SLOT_HIT = 'signal.hit';
export const SLOT_STEP = 'signal.step';
export const SLOT_USE = 'signal.use';

export const VERB_PREFIX = 'verbs.';

// common verbs
export const VERB_DROP = 'verbs.common.drop';
export const VERB_HIT = 'verbs.common.hit';
Expand All @@ -70,12 +72,7 @@ export const META_QUIT = 'verbs.meta.quit';
export const META_SAVE = 'verbs.meta.save';
export const META_WORLDS = 'verbs.meta.worlds';

/**
* Common verbs and meta commands.
*
* Should include all `META_*` and `VERB_*` constants from this file.
*/
export const COMMON_VERBS = [
export const META_VERBS = [
META_CREATE,
META_DEBUG,
META_GRAPH,
Expand All @@ -84,6 +81,15 @@ export const COMMON_VERBS = [
META_QUIT,
META_SAVE,
META_WORLDS,
];

/**
* Common verbs and meta commands.
*
* Should include all `META_*` and `VERB_*` constants from this file.
*/
export const COMMON_VERBS = [
...META_VERBS,
VERB_DROP,
VERB_HIT,
VERB_LOOK,
Expand Down
57 changes: 54 additions & 3 deletions src/util/state/index.ts
@@ -1,13 +1,15 @@
import { doesExist } from '@apextoaster/js-utils';
import { doesExist, mergeMap, mustExist, Optional } from '@apextoaster/js-utils';

import { WorldEntity, WorldEntityType } from '../../model/entity';
import { Actor } from '../../model/entity/Actor';
import { Entity } from '../../model/entity/Base';
import { Room } from '../../model/entity/Room';
import { isItem } from '../../model/entity/Item';
import { isRoom, Room } from '../../model/entity/Room';
import { Metadata } from '../../model/Metadata';
import { WorldState } from '../../model/world/State';
import { META_VERBS, VERB_PREFIX } from '../constants';
import { DEFAULT_MATCHERS } from '../entity';
import { Immutable } from '../types';
import { Immutable, ScriptMap } from '../types';

export interface SearchMatchers {
entity: (entity: Immutable<Entity>, search: Partial<SearchParams>, matchers?: SearchMatchers) => boolean;
Expand Down Expand Up @@ -135,3 +137,52 @@ export function findContainer(state: WorldState, search: Partial<SearchParams>,

return Array.from(results);
}

/**
* @todo remove existing verbs
*/
export function getScripts(state: Optional<Immutable<WorldState>>, target: Optional<WorldEntity>): ScriptMap {
const scripts: ScriptMap = new Map();

// TODO: this should only be in getVerbs, not getScripts
for (const verb of META_VERBS) {
scripts.set(verb, {
data: new Map(),
name: '',
});
}

if (doesExist(target)) {
// TODO: bad cast
if (!isRoom(target)) {
const [room] = findRoom(mustExist(state) as WorldState, {
actor: {
id: target.meta.id,
},
});

if (isRoom(room)) {
for (const [name, script] of room.scripts) {
if (name.startsWith(VERB_PREFIX)) {
scripts.set(name, script);
}
}
}
}

if (!isItem(target)) {
for (const item of target.items) {
for (const [name, script] of item.scripts) {
if (name.startsWith(VERB_PREFIX)) {
scripts.set(name, script);
}
}
}
}

// merge everything, including signals, from the target
mergeMap(scripts, target.scripts);
}

return scripts;
}

0 comments on commit ab595f3

Please sign in to comment.