Refactor the inventory
This commit is contained in:
parent
b7bba137a1
commit
f6b3b5b66e
|
|
@ -1,7 +1,8 @@
|
||||||
import type { InventorySlotInput, SlotId } from "../types";
|
import type { InventorySlotInput, RPGVariables, SlotId } from "../types";
|
||||||
import { action } from "../utils/decorators";
|
import { action } from "../utils/decorators";
|
||||||
import { Component } from "../core/world";
|
import { Component, type EvalContext } from "../core/world";
|
||||||
import { Stackable, Usable } from "./item";
|
import { Stackable, Usable } from "./item";
|
||||||
|
import { resolveVariables } from "../utils/variables";
|
||||||
|
|
||||||
interface SlotEntry {
|
interface SlotEntry {
|
||||||
readonly slotId: SlotId;
|
readonly slotId: SlotId;
|
||||||
|
|
@ -138,7 +139,7 @@ export class Inventory extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async use(itemId?: string): Promise<boolean> {
|
async use(itemId?: string, ctx?: EvalContext): Promise<boolean> {
|
||||||
if (!itemId) return false;
|
if (!itemId) return false;
|
||||||
|
|
||||||
if (this.getAmount(itemId) === 0) {
|
if (this.getAmount(itemId) === 0) {
|
||||||
|
|
@ -162,7 +163,8 @@ export class Inventory extends Component {
|
||||||
this.remove({ itemId, amount: 1 });
|
this.remove({ itemId, amount: 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
await usable.use({ self: this.entity, world: this.entity.world });
|
const resolvedCtx = ctx ?? { self: this.entity, world: this.entity.world };
|
||||||
|
await usable.use(resolvedCtx);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,7 +190,17 @@ export class Inventory extends Component {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
override getVariables(): Record<string, number> {
|
override getVariables(): RPGVariables {
|
||||||
return Object.fromEntries(this.getItems());
|
const result: RPGVariables = {};
|
||||||
|
for (const [itemId, amount] of this.getItems()) {
|
||||||
|
result[itemId] = amount;
|
||||||
|
const itemEntity = this.entity.world.getEntity(itemId);
|
||||||
|
if (itemEntity) {
|
||||||
|
for (const [key, value] of Object.entries(resolveVariables(itemEntity))) {
|
||||||
|
result[`${itemId}.${key}`] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Component, type EvalContext } from "../core/world";
|
import { Component, type EvalContext, type World } from "../core/world";
|
||||||
import type { RPGAction } from "../types";
|
import type { RPGAction } from "../types";
|
||||||
import { action, variable } from "../utils/decorators";
|
import { action, variable } from "../utils/decorators";
|
||||||
import { executeAction } from "../utils/variables";
|
import { executeAction } from "../utils/variables";
|
||||||
|
|
@ -28,9 +28,29 @@ export class Usable extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async use(ctx: EvalContext): Promise<void> {
|
async use(arg?: EvalContext, ctx?: EvalContext): Promise<void> {
|
||||||
|
ctx = arg ?? ctx ?? this.context;
|
||||||
|
if (!ctx) return;
|
||||||
for (const action of this.actions) {
|
for (const action of this.actions) {
|
||||||
await executeAction(action, ctx);
|
await executeAction(action, ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export namespace Items {
|
||||||
|
export interface RegisterOptions {
|
||||||
|
maxStack?: number;
|
||||||
|
description?: string;
|
||||||
|
usable?: { actions: RPGAction[]; consumeOnUse?: boolean };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function register(world: World, id: string, name: string, options?: RegisterOptions) {
|
||||||
|
const entity = world.createEntity(id);
|
||||||
|
entity.add('item', new Item(name, options?.description));
|
||||||
|
if (options?.maxStack !== undefined)
|
||||||
|
entity.add('stackable', new Stackable(options.maxStack));
|
||||||
|
if (options?.usable)
|
||||||
|
entity.add('usable', new Usable(options.usable.actions, options.usable.consumeOnUse));
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,10 @@ export abstract class Component {
|
||||||
}
|
}
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get context(): EvalContext {
|
||||||
|
return { self: this.entity, world: this.entity.world };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class System {
|
export abstract class System {
|
||||||
|
|
@ -277,6 +281,10 @@ export class World {
|
||||||
map.get(key)!.add(handler);
|
map.get(key)!.add(handler);
|
||||||
return () => map.get(key)?.delete(handler);
|
return () => map.get(key)?.delete(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*[Symbol.iterator]() {
|
||||||
|
yield* this.#entities.values();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isEvalContext(v: unknown): v is EvalContext {
|
export function isEvalContext(v: unknown): v is EvalContext {
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,15 @@ import type { RPGVariables } from "../types";
|
||||||
export const ACTION_KEYS = Symbol('rpg.actions');
|
export const ACTION_KEYS = Symbol('rpg.actions');
|
||||||
export const VARIABLE_KEYS = Symbol('rpg.variables');
|
export const VARIABLE_KEYS = Symbol('rpg.variables');
|
||||||
|
|
||||||
export function action<T extends (arg?: any) => unknown>(
|
export function action<T extends (arg?: any, ctx?: any) => unknown>(
|
||||||
target: T,
|
target: T,
|
||||||
context: ClassMethodDecoratorContext<unknown, T>
|
context: ClassMethodDecoratorContext<unknown, T>
|
||||||
): T {
|
): T {
|
||||||
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 target;
|
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]> =
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,21 @@
|
||||||
import type { RPGAction, RPGActions, RPGVariables } from "../types";
|
import type { RPGAction, RPGActions, RPGVariables } from "../types";
|
||||||
|
import { World } from "../core/world";
|
||||||
import type { EvalContext, Entity } from "../core/world";
|
import type { EvalContext, Entity } from "../core/world";
|
||||||
|
|
||||||
export function resolveVariables(entity: Entity): RPGVariables {
|
export function resolveVariables(entity: Entity): RPGVariables;
|
||||||
|
export function resolveVariables(world: World): RPGVariables;
|
||||||
|
export function resolveVariables(entityOrWorld: Entity | World): RPGVariables {
|
||||||
|
if (entityOrWorld instanceof World) {
|
||||||
|
const result: RPGVariables = {};
|
||||||
|
for (const entity of entityOrWorld) {
|
||||||
|
for (const [key, value] of Object.entries(resolveVariables(entity))) {
|
||||||
|
result[`${entity.id}.${key}`] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
const result: RPGVariables = {};
|
const result: RPGVariables = {};
|
||||||
for (const [key, component] of entity) {
|
for (const [key, component] of entityOrWorld) {
|
||||||
for (const [varKey, value] of Object.entries(component.getVariables())) {
|
for (const [varKey, value] of Object.entries(component.getVariables())) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
if (varKey && varKey !== '.') {
|
if (varKey && varKey !== '.') {
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,16 @@ import { Health } from "@common/rpg/components/stat";
|
||||||
import { Variables } from "@common/rpg/components/variables";
|
import { Variables } from "@common/rpg/components/variables";
|
||||||
import { QuestLog } from "@common/rpg/components/questLog";
|
import { QuestLog } from "@common/rpg/components/questLog";
|
||||||
import { QuestSystem } from "@common/rpg/systems/questSystem";
|
import { QuestSystem } from "@common/rpg/systems/questSystem";
|
||||||
|
import { Items } from "@common/rpg/components/item";
|
||||||
import { resolveVariables, resolveActions } from "@common/rpg/utils/variables";
|
import { resolveVariables, resolveActions } from "@common/rpg/utils/variables";
|
||||||
|
|
||||||
export default async function main() {
|
export default async function main() {
|
||||||
const world = new World();
|
const world = new World();
|
||||||
world.addSystem(new QuestSystem());
|
world.addSystem(new QuestSystem());
|
||||||
|
|
||||||
|
Items.register(world, 'helmet', 'Iron Helmet');
|
||||||
|
Items.register(world, 'boots', 'Leather Boots', { maxStack: 2 });
|
||||||
|
|
||||||
const player = world.createEntity('player');
|
const player = world.createEntity('player');
|
||||||
player.add('inventory', new Inventory(['head', 'legs']));
|
player.add('inventory', new Inventory(['head', 'legs']));
|
||||||
player.add('health', new Health(100, 100));
|
player.add('health', new Health(100, 100));
|
||||||
|
|
@ -24,7 +28,7 @@ export default async function main() {
|
||||||
stages: [],
|
stages: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(resolveVariables(player));
|
console.log(resolveVariables(world));
|
||||||
|
|
||||||
const inventory = player.get(Inventory)!;
|
const inventory = player.get(Inventory)!;
|
||||||
inventory.add({ itemId: 'helmet', amount: 1, slotId: 'head' });
|
inventory.add({ itemId: 'helmet', amount: 1, slotId: 'head' });
|
||||||
|
|
@ -33,5 +37,5 @@ export default async function main() {
|
||||||
actions['inventory.add']({ itemId: 'boots', amount: 2 });
|
actions['inventory.add']({ itemId: 'boots', amount: 2 });
|
||||||
|
|
||||||
console.log(actions);
|
console.log(actions);
|
||||||
console.log(resolveVariables(player));
|
console.log(resolveVariables(world));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue