From 1df2bc66f44cb0352c123232b6f564573498d970 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Thu, 1 Aug 2024 21:39:33 +0800 Subject: [PATCH] feat: Hero state & Item state --- package.json | 2 + pnpm-lock.yaml | 12 + src/core/common/eventEmitter.ts | 2 +- src/core/render/preset/layer.ts | 2 +- src/game/hero.ts | 113 -------- src/game/index.ts | 2 +- src/game/state/hero.ts | 474 ++++++++++++++++++++++++++++++++ src/game/state/item.ts | 145 ++++++++++ src/game/state/state.ts | 91 ++++++ src/game/system.ts | 2 +- src/types/action.d.ts | 4 + src/types/control.d.ts | 4 + src/types/core.d.ts | 10 +- 13 files changed, 741 insertions(+), 122 deletions(-) delete mode 100644 src/game/hero.ts create mode 100644 src/game/state/hero.ts create mode 100644 src/game/state/item.ts create mode 100644 src/game/state/state.ts diff --git a/package.json b/package.json index 2ae5c3f..2abe7bf 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,11 @@ "@ant-design/icons-vue": "^6.1.0", "@emotion/css": "^11.11.2", "@vueuse/core": "^10.4.1", + "anon-tokyo": "0.0.0-alpha.0", "ant-design-vue": "^3.2.20", "axios": "^1.5.0", "chart.js": "^4.4.0", + "eventemitter3": "^5.0.1", "gl-matrix": "^3.4.3", "jszip": "^3.10.1", "lodash-es": "^4.17.21", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3591f66..54046f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ dependencies: '@vueuse/core': specifier: ^10.4.1 version: 10.4.1(vue@3.3.4) + anon-tokyo: + specifier: 0.0.0-alpha.0 + version: 0.0.0-alpha.0 ant-design-vue: specifier: ^3.2.20 version: 3.2.20(vue@3.3.4) @@ -23,6 +26,9 @@ dependencies: chart.js: specifier: ^4.4.0 version: 4.4.0 + eventemitter3: + specifier: ^5.0.1 + version: 5.0.1 gl-matrix: specifier: ^3.4.3 version: 3.4.3 @@ -2696,6 +2702,12 @@ packages: indent-string: 4.0.0 dev: true + /anon-tokyo@0.0.0-alpha.0: + resolution: {integrity: sha512-4kq9NOB56RUC6YqZAkkuA2mLhfzdLa39RSi+dUOk6geL4rldWspBZP2XbKv3hhG8nf+HDL2LSOTb7opSbqY/gg==} + dependencies: + lodash-es: 4.17.21 + dev: false + /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} diff --git a/src/core/common/eventEmitter.ts b/src/core/common/eventEmitter.ts index e6a8d80..64ea62d 100644 --- a/src/core/common/eventEmitter.ts +++ b/src/core/common/eventEmitter.ts @@ -22,7 +22,7 @@ type EmitFn any> = ( type Key = number | string | symbol; -export class EventEmitter> { +export class EventEmitter = {}> { protected events: { [x: Key]: Listener[]; } = {}; diff --git a/src/core/render/preset/layer.ts b/src/core/render/preset/layer.ts index 83a2102..b8be31e 100644 --- a/src/core/render/preset/layer.ts +++ b/src/core/render/preset/layer.ts @@ -1200,7 +1200,7 @@ export class Damage extends Sprite { /** 描边样式 */ strokeColor: CanvasStyle = '#000'; /** 描边粗细 */ - strokeWidth: number = 1.5; + strokeWidth: number = 2; constructor(floor?: FloorIds) { super(); diff --git a/src/game/hero.ts b/src/game/hero.ts deleted file mode 100644 index 0ae6317..0000000 --- a/src/game/hero.ts +++ /dev/null @@ -1,113 +0,0 @@ -/** - * 获取勇士在某一点的属性 - * @param name 要获取的勇士属性 - * @param floorId 勇士所在楼层 - */ -export function getHeroStatusOn(name: 'all', floorId?: FloorIds): HeroStatus; -export function getHeroStatusOn( - name: (keyof HeroStatus)[], - floorId?: FloorIds -): Partial; -export function getHeroStatusOn( - name: K, - floorId?: FloorIds -): HeroStatus[K]; -export function getHeroStatusOn( - name: keyof HeroStatus | 'all' | (keyof HeroStatus)[], - floorId?: FloorIds -) { - // @ts-ignore - return getHeroStatusOf(core.status.hero, name, floorId); -} - -/** - * 获取一定状态下的勇士在某一点的属性 - * @param status 勇士的状态 - * @param name 要获取的勇士属性 - * @param floorId 勇士所在楼层 - */ -export function getHeroStatusOf( - status: Partial, - name: 'all', - floorId?: FloorIds -): HeroStatus; -export function getHeroStatusOf( - status: Partial, - name: (keyof HeroStatus)[], - floorId?: FloorIds -): Partial; -export function getHeroStatusOf( - status: Partial, - name: K, - floorId?: FloorIds -): HeroStatus[K]; -export function getHeroStatusOf( - status: DeepPartial, - name: keyof HeroStatus | 'all' | (keyof HeroStatus)[], - floorId?: FloorIds -) { - return getRealStatus(status, name, floorId); -} - -function getRealStatus( - status: DeepPartial, - name: keyof HeroStatus | 'all' | (keyof HeroStatus)[], - floorId: FloorIds = core.status.floorId -): any { - const { getSkillLevel } = Mota.Plugin.require('skillTree_g'); - if (name instanceof Array) { - return Object.fromEntries( - name.map(v => [v, getRealStatus(status, v, floorId)]) - ); - } - - if (name === 'all') { - return Object.fromEntries( - Object.keys(core.status.hero).map(v => [ - v, - v !== 'all' && - getRealStatus(status, v as keyof HeroStatus, floorId) - ]) - ); - } - - let s = (status?.[name] ?? core.status.hero[name]) as number; - if (s === null || s === void 0) { - throw new ReferenceError( - `Wrong hero status property name is delivered: ${name}` - ); - } - - // 永夜、极昼 - if (name === 'atk' || name === 'def') { - s += window.flags?.[`night_${floorId}`] ?? 0; - } - - // 技能 - if (flags.bladeOn && flags.blade) { - const level = getSkillLevel(2); - if (name === 'atk') { - s *= 1 + 0.1 * level; - } - if (name === 'def') { - s *= 1 - 0.1 * level; - } - } - if (flags.shield && flags.shieldOn) { - const level = getSkillLevel(10); - if (name === 'atk') { - s *= 1 - 0.1 * level; - } - if (name === 'def') { - s *= 1 + 0.1 * level; - } - } - - // buff - if (typeof s === 'number') - s *= core.getBuff(name as keyof NumbericHeroStatus); - - // 取整 - if (typeof s === 'number') s = Math.floor(s); - return s; -} diff --git a/src/game/index.ts b/src/game/index.ts index 53b6e80..001818d 100644 --- a/src/game/index.ts +++ b/src/game/index.ts @@ -6,7 +6,7 @@ import { Range } from '@/plugin/game/range'; import { specials } from './enemy/special'; import { gameListener, hook, loading } from './game'; import * as battle from './enemy/battle'; -import * as hero from './hero'; +import * as hero from './state/hero'; import * as miscMechanism from './mechanism/misc'; import * as study from './mechanism/study'; diff --git a/src/game/state/hero.ts b/src/game/state/hero.ts new file mode 100644 index 0000000..a0fa5db --- /dev/null +++ b/src/game/state/hero.ts @@ -0,0 +1,474 @@ +import { logger } from '@/core/common/logger'; +import { EventEmitter } from 'eventemitter3'; +import { cloneDeep, isNil } from 'lodash-es'; +import { GameState, ISerializable } from './state'; +import { ItemState } from './item'; + +/** + * 获取勇士在某一点的属性 + * @param name 要获取的勇士属性 + * @param floorId 勇士所在楼层 + */ +export function getHeroStatusOn(name: 'all', floorId?: FloorIds): HeroStatus; +export function getHeroStatusOn( + name: (keyof HeroStatus)[], + floorId?: FloorIds +): Partial; +export function getHeroStatusOn( + name: K, + floorId?: FloorIds +): HeroStatus[K]; +export function getHeroStatusOn( + name: keyof HeroStatus | 'all' | (keyof HeroStatus)[], + floorId?: FloorIds +) { + // @ts-ignore + return getHeroStatusOf(core.status.hero, name, floorId); +} + +/** + * 获取一定状态下的勇士在某一点的属性 + * @param status 勇士的状态 + * @param name 要获取的勇士属性 + * @param floorId 勇士所在楼层 + */ +export function getHeroStatusOf( + status: Partial, + name: 'all', + floorId?: FloorIds +): HeroStatus; +export function getHeroStatusOf( + status: Partial, + name: (keyof HeroStatus)[], + floorId?: FloorIds +): Partial; +export function getHeroStatusOf( + status: Partial, + name: K, + floorId?: FloorIds +): HeroStatus[K]; +export function getHeroStatusOf( + status: DeepPartial, + name: keyof HeroStatus | 'all' | (keyof HeroStatus)[], + floorId?: FloorIds +) { + return getRealStatus(status, name, floorId); +} + +function getRealStatus( + status: DeepPartial, + name: keyof HeroStatus | 'all' | (keyof HeroStatus)[], + floorId: FloorIds = core.status.floorId +): any { + const { getSkillLevel } = Mota.Plugin.require('skillTree_g'); + if (name instanceof Array) { + return Object.fromEntries( + name.map(v => [v, getRealStatus(status, v, floorId)]) + ); + } + + if (name === 'all') { + return Object.fromEntries( + Object.keys(core.status.hero).map(v => [ + v, + v !== 'all' && + getRealStatus(status, v as keyof HeroStatus, floorId) + ]) + ); + } + + let s = (status?.[name] ?? core.status.hero[name]) as number; + if (s === null || s === void 0) { + throw new ReferenceError( + `Wrong hero status property name is delivered: ${name}` + ); + } + + // 永夜、极昼 + if (name === 'atk' || name === 'def') { + s += window.flags?.[`night_${floorId}`] ?? 0; + } + + // 技能 + if (flags.bladeOn && flags.blade) { + const level = getSkillLevel(2); + if (name === 'atk') { + s *= 1 + 0.1 * level; + } + if (name === 'def') { + s *= 1 - 0.1 * level; + } + } + if (flags.shield && flags.shieldOn) { + const level = getSkillLevel(10); + if (name === 'atk') { + s *= 1 - 0.1 * level; + } + if (name === 'def') { + s *= 1 + 0.1 * level; + } + } + + // buff + if (typeof s === 'number') + s *= core.getBuff(name as keyof NumbericHeroStatus); + + // 取整 + if (typeof s === 'number') s = Math.floor(s); + return s; +} + +export interface IHeroStatusDefault { + atk: number; + def: number; + hp: number; +} + +interface HeroStateEvent { + set: [key: string | number | symbol, value: any]; +} + +type HeroStatusCalculate = (key: K, value: T[K]) => T[K]; + +export class HeroState< + T extends object = IHeroStatusDefault +> extends EventEmitter { + readonly status: T; + readonly computedStatus: T; + + readonly buffable: Set = new Set(); + readonly buffMap: Map = new Map(); + + private cal: HeroStatusCalculate = (_, value) => value; + + constructor(init: T) { + super(); + this.status = init; + this.computedStatus = cloneDeep(init); + } + + /** + * 设置某个属性的值 + * @param key 要设置的属性 + * @param value 属性值 + * @returns 是否设置成功 + */ + setStatus(key: K, value: T[K]): boolean { + this.status[key] = value; + this.emit('set', key, value); + return this.refreshStatus(key); + } + + /** + * 增加或减少一个属性的值,只对数字型的属性有效 + * @param key 要修改的属性 + * @param value 属性的增量 + * @returns 是否设置成功 + */ + addStatus>(key: K, value: number): boolean { + if (typeof this.status[key] !== 'number') { + logger.warn( + 14, + `Cannot add status of non-number status. Key: ${String(key)}` + ); + return false; + } + return this.setStatus(key, (this.status[key] + value) as T[K]); + } + + /** + * 获取某个属性的原始值 + * @param key 要获取的属性 + * @returns 属性的值 + */ + getStatus(key: K): T[K] { + return this.status[key]; + } + + /** + * 获取一个属性计算后的值,也就是2.x所说的勇士真实属性 + * @param key 要获取的属性值 + */ + getComputedStatus(key: K): T[K] { + return this.computedStatus[key]; + } + + /** + * 标记某个属性为可以被buff加成 + */ + markBuffable(key: SelectKey): void { + if (typeof this.status[key] !== 'number') { + logger.warn( + 12, + `Cannot mark buffable with a non-number status. Key: ${String( + key + )}.` + ); + return; + } + this.buffable.add(key); + this.buffMap.set(key, 1); + } + + /** + * 设置某个属性的buff值 + * @param key 要设置buff的属性 + * @param value buff值 + * @returns 是否设置成功 + */ + setBuff(key: SelectKey, value: number): boolean { + if (!this.buffable.has(key) || typeof this.status[key] !== 'number') { + logger.warn( + 13, + `Cannot set buff non-number status. Key: ${String(key)}.` + ); + return false; + } + this.buffMap.set(key, value); + return this.refreshStatus(key); + } + + /** + * 增加或减少一个buff值 + * @param key 要修改的buff属性 + * @param value buff增量 + * @returns 是否修改成功 + */ + addBuff(key: SelectKey, value: number): boolean { + if (!this.buffable.has(key) || typeof this.status[key] !== 'number') { + logger.warn( + 13, + `Cannot set buff non-number status. Key: ${String(key)}.` + ); + return false; + } + return this.setBuff(key, this.buffMap.get(key)! + value); + } + + /** + * 刷新某个或所有属性,重新进行计算 + * @param key 要刷新的属性名,不填表示刷新所有属性 + * @returns 是否计算成功 + */ + refreshStatus(key?: keyof T): boolean { + if (key === void 0) { + for (const [key, value] of Object.entries(this.status)) { + // @ts-ignore + this.computedStatus[key] = this.cal(key, value); + } + return true; + } + this.computedStatus[key] = this.cal(key, this.status[key]); + return true; + } + + /** + * 计算所有可以buff加成的属性 + * @returns 是否计算成功 + */ + refreshBuffable(): boolean { + for (const key of this.buffable) { + this.computedStatus[key] = this.cal(key, this.status[key]); + } + return true; + } + + /** + * 复写属性计算函数,默认函数不进行计算,直接将原属性返回 + * @param fn 计算函数,传入两个参数,key表示属性名,value表示属性值,返回值表示计算结果 + */ + overrideCalculate(fn: HeroStatusCalculate) { + this.cal = fn; + } +} + +interface IHeroItem { + items: Map, number>; + + /** + * 设置勇士拥有的物品数量 + * @param item 物品id + * @param value 物品数量 + * @returns 是否设置成功 + */ + setItem(item: AllIdsOf<'items'>, value: number): boolean; + + /** + * 增加或减少勇士拥有的物品数量 + * @param item 物品id + * @param value 物品数量增量 + * @returns 是否设置成功 + */ + addItem(item: AllIdsOf<'items'>, value: number): boolean; + + /** + * 使用一个物品 + * @param item 物品id + * @returns 是否使用成功 + */ + useItem(item: AllIdsOf<'items'>, x?: number, y?: number): boolean; + + /** + * 获得一个物品 + * @param item 物品id + * @param num 获得的数量 + */ + getItem(item: AllIdsOf<'items'>, num: number): void; + /** + * 获得一个物品 + * @param item 物品id + * @param x 物品所在x坐标 + * @param y 物品所在y坐标 + * @param floorId 物品所在楼层 + * @param num 获得的数量 + */ + getItem( + item: AllIdsOf<'items'>, + x: number, + y: number, + floorId?: FloorIds, + num?: number + ): void; + + /** + * 获取某个物品的数量 + * @param item 物品id + */ + itemCount(item: AllIdsOf<'items'>): number; + + /** + * 判断勇士是否拥有这个物品 + * @param item 物品id + */ + hasItem(item: AllIdsOf<'items'>): boolean; +} + +interface HeroEvent { + beforeMove: [dir: Dir2]; + afterMove: [dir: Dir2]; + beforeMoveDirectly: [x: number, y: number]; + afterMoveDirectly: [x: number, y: number]; + stateChange: [state: HeroState]; +} + +export class Hero + extends EventEmitter + implements IHeroItem, ISerializable +{ + x: number; + y: number; + floorId: FloorIds; + id: string; + + state: HeroState; + items: Map, number> = new Map(); + + constructor( + id: string, + x: number, + y: number, + floorId: FloorIds, + state: HeroState, + gameState: GameState + ) { + super(); + this.id = id; + this.x = x; + this.y = y; + this.floorId = floorId; + this.state = state; + + const list = gameState.state.hero.list; + if (list.has(id)) { + logger.warn(11, `Repeated hero: ${id}.`); + } + list.set(id, this); + } + + /** + * 设置勇士状态,效果为直接将一个状态替换为另一个状态,不应当经常使用,仅应当在不得不使用的时候使用 + * @param state 要设置为的勇士状态 + */ + setState(state: HeroState): void { + this.state = state; + this.emit('stateChange', state); + } + + /** + * 获取勇士状态 + */ + getState(): HeroState { + return this.state; + } + + /** + * 见 {@link HeroState.refreshStatus} + */ + refreshState(key?: keyof T) { + return this.state.refreshStatus(key); + } + + setItem(item: AllIdsOf<'items'>, value: number): boolean { + this.items.set(item, value < 0 ? 0 : value); + return true; + } + + addItem(item: AllIdsOf<'items'>, value: number): boolean { + return this.setItem(item, (this.items.get(item) ?? 0) + value); + } + + useItem(item: AllIdsOf<'items'>): boolean { + const state = ItemState.item(item); + return !!state?.use(this); + } + + /** + * 获得一个物品 + * @param item 物品id + * @param num 获得的数量 + */ + getItem(item: AllIdsOf<'items'>, num: number): boolean; + /** + * 获得一个物品 + * @param item 物品id,填写坐标时无效 + * @param x 物品所在x坐标 + * @param y 物品所在y坐标 + * @param floorId 物品所在楼层 + * @param num 获得的数量 + */ + getItem(x: number, y: number, floorId?: FloorIds, num?: number): boolean; + getItem( + item: AllIdsOf<'items'> | number, + y: number, + floorId: FloorIds = this.floorId, + num: number = 1 + ): boolean { + if (!isNil(floorId) && typeof item === 'number') { + // 如果指定了坐标 + const block = core.getBlock(item as number, y, floorId); + const id = block.event.id as AllIdsOf<'items'>; + const cls = core.material.items[id]?.cls; + if (cls === void 0) { + logger.warn( + 15, + `Cannot get item of a non-item block on loc: ${item},${y},${floorId}` + ); + return false; + } + return this.addItem(id, num!); + } + return this.addItem(item as AllIdsOf<'items'>, num!); + } + + itemCount(item: AllIdsOf<'items'>): number { + return this.items.get(item) ?? 0; + } + + hasItem(item: AllIdsOf<'items'>): boolean { + return this.itemCount(item) > 0; + } + + toJSON(): string { + return ''; + } +} diff --git a/src/game/state/item.ts b/src/game/state/item.ts new file mode 100644 index 0000000..33969a1 --- /dev/null +++ b/src/game/state/item.ts @@ -0,0 +1,145 @@ +import EventEmitter from 'eventemitter3'; +import type { Hero } from './hero'; +import { GameState, gameStates, IGameState } from './state'; +import { loading } from '../game'; + +type EffectFn = (state: IGameState, hero: Hero) => void; +type CanUseEffectFn = (state: IGameState, hero: Hero) => boolean; + +interface ItemStateEvent { + use: [hero: Hero]; +} + +export class ItemState< + I extends AllIdsOf<'items'> = AllIdsOf<'items'> +> extends EventEmitter { + static items: Map, ItemState> = new Map(); + + id: I; + cls: ItemClsOf; + name: string; + text?: string; + hideInToolBox: boolean; + hideInReplay: boolean; + + /** 即捡即用效果 */ + itemEffect?: string; + /** 即捡即用道具捡过之后的提示 */ + itemEffectTip?: string; + /** 使用道具时执行的事件 */ + useItemEvent?: MotaEvent; + /** 使用道具时执行的代码 */ + useItemEffect?: string; + /** 能否使用道具 */ + canUseItemEffect?: string | boolean; + + private noRoute: boolean = false; + + itemEffectFn?: EffectFn; + useItemEffectFn?: EffectFn; + canUseItemEffectFn?: CanUseEffectFn; + + constructor(id: I) { + super(); + const items = items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a; + this.id = id; + const item = items[id]; + this.cls = item.cls; + this.name = item.name; + this.text = item.text; + this.hideInToolBox = item.hideInToolBox; + this.hideInReplay = item.hideInReplay; + this.itemEffect = item.itemEffect; + this.itemEffectTip = item.itemEffectTip; + this.useItemEvent = item.useItemEvent; + this.useItemEffect = item.useItemEffect; + this.canUseItemEffect = item.canUseItemEffect; + + this.compileFunction(); + this.compileEvent(); + } + + private compileFunction() { + if (this.itemEffect) { + this.itemEffectFn = new Function( + `state`, + this.itemEffect + ) as EffectFn; + } + if (this.useItemEffect) { + this.useItemEffectFn = new Function( + `state`, + this.useItemEffect + ) as EffectFn; + } + if (this.canUseItemEffect) { + if (typeof this.canUseItemEffect === 'boolean') { + this.canUseItemEffectFn = () => + this.canUseItemEffect as boolean; + } else { + this.useItemEffectFn = new Function( + `state`, + this.canUseItemEffect + ) as CanUseEffectFn; + } + } + } + + private compileEvent() { + // todo + } + + /** + * 使用这个物品 + * @param state 游戏状态 + * @param num 使用的数量,仅对tools和items有效 + */ + use(hero: Hero): boolean { + if (!this.canUse(hero)) return false; + if (!gameStates.now) return false; + const state = gameStates.now.state; + this.useItemEffectFn?.(state, hero); + if (this.useItemEvent) core.insertAction(this.useItemEvent); + if (!this.noRoute) state.route.push(`item:${this.id}`); + + hero.addItem(this.id, -1); + this.emit('use', hero); + + return true; + } + + /** + * 判断是否可以使用一个物品 + * @param hero 使用物品的勇士 + */ + canUse(hero: Hero, num: number = 1): boolean { + if (num <= 0) return false; + if (hero.itemCount(this.id) < num) return false; + if (!gameStates.now) return false; + return !!this.canUseItemEffectFn?.(gameStates.now.state, hero); + } + + /** + * 标记当前物品为不进入录像,也就是录像中不会使用该道具 + */ + markNoRoute() { + this.noRoute = true; + } + + /** + * 获取一个道具的信息 + * @param id 要获取的道具id + */ + static item>(id: I): ItemState | undefined { + return this.items.get(id) as ItemState; + } +} + +loading.once('coreInit', () => { + for (const key of Object.keys(items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a)) { + ItemState.items.set( + key as AllIdsOf<'items'>, + new ItemState(key as AllIdsOf<'items'>) + ); + } +}); diff --git a/src/game/state/state.ts b/src/game/state/state.ts new file mode 100644 index 0000000..bdb041e --- /dev/null +++ b/src/game/state/state.ts @@ -0,0 +1,91 @@ +import { Undoable } from '@/core/interface'; +import { Hero, HeroState } from './hero'; +import EventEmitter from 'eventemitter3'; + +export interface ISerializable { + toJSON(): string; +} + +export interface IGameState { + hero: MonoStore>; + route: string[]; +} + +export class GameState implements ISerializable { + state: IGameState; + + constructor(state: IGameState) { + this.state = state; + } + + toJSON() { + return ''; + } + + static loadState(state: GameState) {} + + static fromJSON(json: string) {} + + static loadStateFromJSON(json: string) {} +} + +interface MonoStoreEvent { + change: [before: T | undefined, after: T | undefined]; +} + +export class MonoStore + extends EventEmitter> + implements ISerializable +{ + list: Map = new Map(); + using?: T; + + use(id: string) { + const before = this.using; + this.using = this.list.get(id); + this.emit('change', before, this.using); + } + + toJSON() { + return ''; + } +} + +interface StateStoreEvent { + undo: [state: GameState]; + redo: [state: GameState]; + change: [before: GameState | undefined, now: GameState]; +} + +class StateStore + extends EventEmitter + implements Undoable +{ + now?: GameState; + stack: GameState[] = []; + redoStack: GameState[] = []; + + undo(): GameState | undefined { + const state = this.stack.pop(); + if (!state) return void 0; + this.redoStack.push(state); + this.emit('undo', state); + return state; + } + + redo(): GameState | undefined { + const state = this.redoStack.pop(); + if (!state) return void 0; + this.stack.push(state); + this.emit('redo', state); + return state; + } + + use(state: GameState) { + const before = this.now; + this.now = state; + this.emit('change', before, state); + } +} + +export const gameStates = new StateStore(); diff --git a/src/game/system.ts b/src/game/system.ts index 0d07f80..dae60d3 100644 --- a/src/game/system.ts +++ b/src/game/system.ts @@ -20,7 +20,7 @@ import type { Range } from '@/plugin/game/range'; import type { KeyCode } from '@/plugin/keyCodes'; import type { Ref } from 'vue'; import type * as battle from './enemy/battle'; -import type * as hero from './hero'; +import type * as hero from './state/hero'; import type * as damage from './enemy/damage'; import type { Logger } from '@/core/common/logger'; import type { Danmaku } from '@/core/main/custom/danmaku'; diff --git a/src/types/action.d.ts b/src/types/action.d.ts index 88a18e7..7ef7ab4 100644 --- a/src/types/action.d.ts +++ b/src/types/action.d.ts @@ -94,6 +94,7 @@ interface Actions extends VoidedActionFuncs { }; /** + * @deprecated * 此函数将注册一个用户交互行为。 * @param action 要注册的交互类型 * @param name 自定义名称,可被注销使用 @@ -108,6 +109,7 @@ interface Actions extends VoidedActionFuncs { ): void; /** + * @deprecated * 注销一个用户交互行为 * @param action 要注销的交互类型 * @param name 要注销的自定义名称 @@ -115,6 +117,7 @@ interface Actions extends VoidedActionFuncs { unregisterAction(action: ActionKey, name: string): void; /** + * @deprecated * 执行一个用户交互行为 */ doRegisteredAction( @@ -123,6 +126,7 @@ interface Actions extends VoidedActionFuncs { ): void; /** + * @deprecated * 判断一个横坐标是否在(_HX_ - 2, _HX_ + 2)范围外 * @param x 要判断的横坐标 */ diff --git a/src/types/control.d.ts b/src/types/control.d.ts index bce4592..b6b1d48 100644 --- a/src/types/control.d.ts +++ b/src/types/control.d.ts @@ -407,11 +407,13 @@ interface Control { addGameCanvasTranslate(x: number, y: number): void; /** + * @deprecated * 更新大地图的可见区域 */ updateViewport(): void; /** + * @deprecated * 设置视野范围 * @param px 相对大地图左上角的偏移横坐标,单位像素 * @param py 相对大地图左上角的偏移纵坐标,单位像素 @@ -419,6 +421,7 @@ interface Control { setViewport(px?: number, py?: number): void; /** + * @deprecated * 移动视野范围,这东西真的有人用吗...高级动画 + setViewport就完事了( * @param x 移动的横坐标,单位格子 * @param y 移动的纵坐标,单位格子 @@ -481,6 +484,7 @@ interface Control { updateDamage(floorId?: FloorIds, ctx?: CtxRefer): void; /** + * @deprecated * 重绘地图显伤 * @param ctx 绘制到的画布 */ diff --git a/src/types/core.d.ts b/src/types/core.d.ts index e5eba13..f296127 100644 --- a/src/types/core.d.ts +++ b/src/types/core.d.ts @@ -1261,11 +1261,6 @@ interface Main extends MainData { */ readonly __VERSION_CODE__: number; - readonly RESOURCE_INDEX: Record; - readonly RESOURCE_URL: string; - readonly RESOURCE_SYMBOL: string; - readonly RESOURCE_TYPE: 'dev' | 'dist' | 'gh' | 'local'; - /** * 初始化游戏 * @param mode 初始化游戏的模式,游玩还是编辑器 @@ -1316,6 +1311,7 @@ interface Main extends MainData { ): void; /** + * @deprecated * 设置加载界面的加载提示文字 */ setMainTipsText(text: string): void; @@ -1334,23 +1330,27 @@ interface Main extends MainData { createOnChoiceAnimation(): void; /** + * @deprecated * 选中开始界面的一个按钮 * @param index 要选中的按钮 */ selectButton(index: number): void; /** + * @deprecated * 加载一系列字体 * @param fonts 要加载的字体列表 */ importFonts(fonts: FontIds[]): void; /** + * @deprecated * 执行样板的所有监听 */ listen(): void; /** + * @deprecated * 执行ts的插件转发 */ forward(): void;