mirror of
https://github.com/motajs/template.git
synced 2026-10-05 11:10:17 +08:00
refactor: 删除 data-state 中的 legacy 文件夹
This commit is contained in:
parent
5f97352f94
commit
e9e9939bd8
@ -1,5 +1,6 @@
|
||||
import { KeyCode } from '@motajs/client-base';
|
||||
import { Hotkey, HotkeyData } from '@motajs/system';
|
||||
// @ts-expect-error 需要重构
|
||||
import { HeroMover, IMoveController } from '@user/data-state';
|
||||
import { Ticker } from 'mutate-animate';
|
||||
import { mainScope } from './hotkey';
|
||||
|
||||
@ -25,6 +25,7 @@ import {
|
||||
RightStatusBar
|
||||
} from './statusBar';
|
||||
import { ReplayingStatus } from './toolbar';
|
||||
// @ts-expect-error 需要重构
|
||||
import { getHeroStatusOn, state } from '@user/data-state';
|
||||
import { hook } from '@user/data-base';
|
||||
import { mainUIController } from './controller';
|
||||
|
||||
@ -145,7 +145,7 @@ function createItem(
|
||||
/** 构造一个装配勇士装备对象的测试环境 */
|
||||
function createEnv(): TestEnv {
|
||||
const tileStore = new TileStore();
|
||||
const itemStore = new ItemStore<IHeroAttr, unknown>();
|
||||
const itemStore = new ItemStore<IHeroAttr>();
|
||||
// 录像系统桩,用于满足装备/卸下时的 route.add 记录与临时禁用录像
|
||||
const replaySystem = createReplaySystem();
|
||||
const state = { tileStore, itemStore, replaySystem } as never;
|
||||
|
||||
@ -57,7 +57,7 @@ interface ItemFixture {
|
||||
/** 构造一个装配勇士道具对象的测试环境 */
|
||||
function createEnv(): TestEnv {
|
||||
const tileStore = new TileStore();
|
||||
const itemStore = new ItemStore<IHeroAttr, unknown>();
|
||||
const itemStore = new ItemStore<IHeroAttr>();
|
||||
// 录像系统桩,仅用于满足道具使用时的 route.add 记录
|
||||
const replaySystem = { route: { add: vi.fn() } };
|
||||
const state = { tileStore, itemStore, replaySystem } as never;
|
||||
|
||||
@ -74,7 +74,7 @@ interface EquipEnv {
|
||||
/** 极E<E69E81><45>一个含图块、E<E38081><45>具存储与假录像系统的公共层假对象 */
|
||||
function createState(): IDataCommon {
|
||||
const tileStore = new TileStore();
|
||||
const itemStore = new ItemStore<IHeroAttr, unknown>();
|
||||
const itemStore = new ItemStore<IHeroAttr>();
|
||||
const route = { add: vi.fn() };
|
||||
return {
|
||||
tileStore,
|
||||
|
||||
@ -1,18 +1,9 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import {
|
||||
IItemLegacyConverter,
|
||||
IItemRawData,
|
||||
IItemStore,
|
||||
ItemCategory
|
||||
} from './types';
|
||||
import { IItemRawData, IItemStore, ItemCategory } from './types';
|
||||
|
||||
export class ItemStore<THero, TLegacy> implements IItemStore<THero, TLegacy> {
|
||||
export class ItemStore<THero> implements IItemStore<THero> {
|
||||
/** 以道具图块数字为键的原始道具定义表 */
|
||||
private readonly dataMap: Map<number, IItemRawData<THero>> = new Map();
|
||||
|
||||
/** 当前挂载的旧样板道具转换器 */
|
||||
private converter: IItemLegacyConverter<THero, TLegacy> | null = null;
|
||||
|
||||
getData(num: number): IItemRawData<THero> | null {
|
||||
return this.dataMap.get(num) ?? null;
|
||||
}
|
||||
@ -24,21 +15,4 @@ export class ItemStore<THero, TLegacy> implements IItemStore<THero, TLegacy> {
|
||||
addItem(data: IItemRawData<THero>): void {
|
||||
this.dataMap.set(data.num, data);
|
||||
}
|
||||
|
||||
attachLegacyConverter(
|
||||
converter: IItemLegacyConverter<THero, TLegacy>
|
||||
): void {
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
fromLegacy(num: number, legacy: TLegacy): IItemRawData<THero> {
|
||||
const converter = this.converter;
|
||||
if (!converter) {
|
||||
logger.error(57);
|
||||
throw new Error('Expected a item legacy converter.');
|
||||
}
|
||||
const data = converter.fromLegacy(num, legacy);
|
||||
this.addItem(data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,8 @@
|
||||
import { isNil } from 'lodash-es';
|
||||
import { logger } from '@motajs/common';
|
||||
import {
|
||||
ITileLegacyConverter,
|
||||
ITileRawData,
|
||||
ITileStore,
|
||||
TileType
|
||||
} from './types';
|
||||
import { ITileRawData, ITileStore, TileType } from './types';
|
||||
|
||||
export class TileStore<TLegacy = unknown> implements ITileStore<TLegacy> {
|
||||
export class TileStore implements ITileStore {
|
||||
/** 以图块数字为键的原始图块定义表 */
|
||||
private readonly dataMap: Map<number, ITileRawData> = new Map();
|
||||
|
||||
@ -20,9 +15,6 @@ export class TileStore<TLegacy = unknown> implements ITileStore<TLegacy> {
|
||||
/** 由图块数字保存的默认事件映射 */
|
||||
private readonly eventMap: Map<number, Map<number, string>> = new Map();
|
||||
|
||||
/** 当前挂载的旧样板图块转换器 */
|
||||
private legacyConverter: ITileLegacyConverter<TLegacy> | null = null;
|
||||
|
||||
getData(num: number): ITileRawData | null {
|
||||
return this.dataMap.get(num) ?? null;
|
||||
}
|
||||
@ -83,21 +75,6 @@ export class TileStore<TLegacy = unknown> implements ITileStore<TLegacy> {
|
||||
}
|
||||
}
|
||||
|
||||
attachLegacyConverter(converter: ITileLegacyConverter<TLegacy>): void {
|
||||
this.legacyConverter = converter;
|
||||
}
|
||||
|
||||
fromLegacy(num: number, legacy: TLegacy): ITileRawData {
|
||||
const converter = this.legacyConverter;
|
||||
if (!converter) {
|
||||
logger.error(56);
|
||||
throw new Error('Expected a tile legacy converter.');
|
||||
}
|
||||
const data = converter.fromLegacy(num, legacy);
|
||||
this.addTile(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
/** 删除一组旧的图块定义及其双向索引 */
|
||||
private deleteBy(num: number, id: string): void {
|
||||
this.dataMap.delete(num);
|
||||
|
||||
@ -62,16 +62,7 @@ export interface ITileRawData {
|
||||
readonly eventPass: boolean;
|
||||
}
|
||||
|
||||
export interface ITileLegacyConverter<TLegacy> {
|
||||
/**
|
||||
* 将旧样板图块定义转换为新的图块原始数据
|
||||
* @param num 图块数字
|
||||
* @param legacy 旧样板图块定义
|
||||
*/
|
||||
fromLegacy(num: number, legacy: TLegacy): ITileRawData;
|
||||
}
|
||||
|
||||
export interface ITileStore<TLegacy = unknown> {
|
||||
export interface ITileStore {
|
||||
/**
|
||||
* 获取指定图块数字对应的完整原始定义
|
||||
* @param num 图块数字
|
||||
@ -119,19 +110,6 @@ export interface ITileStore<TLegacy = unknown> {
|
||||
* @param token 图块数字或 id
|
||||
*/
|
||||
id(token: number | string): string | undefined;
|
||||
|
||||
/**
|
||||
* 挂载一个旧样板转换器
|
||||
* @param converter 旧样板转换器
|
||||
*/
|
||||
attachLegacyConverter(converter: ITileLegacyConverter<TLegacy>): void;
|
||||
|
||||
/**
|
||||
* 使用当前转换器转换并写入一个旧样板图块定义
|
||||
* @param num 图块数字
|
||||
* @param legacy 旧样板图块定义
|
||||
*/
|
||||
fromLegacy(num: number, legacy: TLegacy): ITileRawData;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@ -208,16 +186,7 @@ export interface IItemRawData<THero> {
|
||||
readonly equip: IItemEquipData<THero>;
|
||||
}
|
||||
|
||||
export interface IItemLegacyConverter<THero, TLegacy> {
|
||||
/**
|
||||
* 将旧样板道具定义转换为新的道具原始数据
|
||||
* @param num 道具图块数字
|
||||
* @param legacy 旧样板道具定义
|
||||
*/
|
||||
fromLegacy(num: number, legacy: TLegacy): IItemRawData<THero>;
|
||||
}
|
||||
|
||||
export interface IItemStore<THero, TLegacy> {
|
||||
export interface IItemStore<THero> {
|
||||
/**
|
||||
* 获取指定图块数字对应的道具原始数据
|
||||
* @param num 道具图块数字
|
||||
@ -235,21 +204,6 @@ export interface IItemStore<THero, TLegacy> {
|
||||
* @param data 道具原始数据
|
||||
*/
|
||||
addItem(data: IItemRawData<THero>): void;
|
||||
|
||||
/**
|
||||
* 挂载一个旧样板转换器
|
||||
* @param converter 旧样板转换器
|
||||
*/
|
||||
attachLegacyConverter(
|
||||
converter: IItemLegacyConverter<THero, TLegacy>
|
||||
): void;
|
||||
|
||||
/**
|
||||
* 使用当前转换器转换并写入一个旧样板道具定义
|
||||
* @param num 道具图块数字
|
||||
* @param legacy 旧样板道具定义
|
||||
*/
|
||||
fromLegacy(num: number, legacy: TLegacy): IItemRawData<THero>;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@ -45,9 +45,9 @@ export interface IHeroAttr {
|
||||
|
||||
export interface IDataCommon {
|
||||
/** 图块定义存储 */
|
||||
readonly tileStore: ITileStore<MapDataOf<keyof NumberToId>>;
|
||||
readonly tileStore: ITileStore;
|
||||
/** 道具定义存储 */
|
||||
readonly itemStore: IItemStore<IHeroAttr, Item<AllIdsOf<'items'>>>;
|
||||
readonly itemStore: IItemStore<IHeroAttr>;
|
||||
/** 地图定义存储 */
|
||||
readonly mapStore: IMapStore;
|
||||
/** 游戏事件存储 */
|
||||
|
||||
@ -35,8 +35,6 @@ import {
|
||||
FlagSystem,
|
||||
IMotaDataLoader,
|
||||
MotaDataLoader,
|
||||
loading,
|
||||
IReadonlyEnemy,
|
||||
IMapState,
|
||||
MapState
|
||||
} from '@user/data-base';
|
||||
@ -62,23 +60,7 @@ import {
|
||||
registerSpecials,
|
||||
MainEnemyComparer
|
||||
} from './enemy';
|
||||
import {
|
||||
BG2_ZINDEX,
|
||||
BG_ZINDEX,
|
||||
EVENT_ZINDEX,
|
||||
FG2_ZINDEX,
|
||||
FG_ZINDEX,
|
||||
HERO_DEFAULT_ATTRIBUTE,
|
||||
TILE_HEIGHT,
|
||||
TILE_WIDTH
|
||||
} from './shared';
|
||||
import {
|
||||
ItemLegacyBridge,
|
||||
LegacyItemData,
|
||||
LegacyTileData,
|
||||
TileLegacyBridge
|
||||
} from './legacy';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { HERO_DEFAULT_ATTRIBUTE, TILE_HEIGHT, TILE_WIDTH } from './shared';
|
||||
import { DefaultHeroMoveTopImpl, DefaultPassPredicateImpl } from './hero';
|
||||
import { createEventRegistrations } from './event/registrations';
|
||||
import {
|
||||
@ -93,8 +75,8 @@ export class CoreState implements ICoreState {
|
||||
// Layer 0 公共层,最底层的接口,不会依赖任何其他内容,一般是工具性接口及不需要存档的数据
|
||||
readonly roleFace: IRoleFaceBinder;
|
||||
readonly faceManager: IFaceManager;
|
||||
readonly tileStore: ITileStore<LegacyTileData>;
|
||||
readonly itemStore: IItemStore<IHeroAttr, LegacyItemData>;
|
||||
readonly tileStore: ITileStore;
|
||||
readonly itemStore: IItemStore<IHeroAttr>;
|
||||
readonly mapStore: IMapStore;
|
||||
readonly eventStore: IGameEventStore;
|
||||
readonly directionMapper: IDirectionMapper;
|
||||
@ -139,19 +121,13 @@ export class CoreState implements ICoreState {
|
||||
this.faceManager.registerById('dir8', dir8);
|
||||
|
||||
// 图块
|
||||
const tileStore = new TileStore<LegacyTileData>();
|
||||
tileStore.attachLegacyConverter(new TileLegacyBridge());
|
||||
this.tileStore = tileStore;
|
||||
this.tileStore = new TileStore();
|
||||
// 道具
|
||||
const itemStore = new ItemStore<IHeroAttr, LegacyItemData>();
|
||||
itemStore.attachLegacyConverter(new ItemLegacyBridge(this));
|
||||
this.itemStore = itemStore;
|
||||
this.itemStore = new ItemStore<IHeroAttr>();
|
||||
// 地图
|
||||
const mapStore = new MapStore();
|
||||
this.mapStore = mapStore;
|
||||
this.mapStore = new MapStore();
|
||||
// 游戏事件
|
||||
const eventStore = new GameEventStore();
|
||||
this.eventStore = eventStore;
|
||||
this.eventStore = new GameEventStore();
|
||||
this.directionMapper = new DirectionMapper();
|
||||
|
||||
//#endregion
|
||||
@ -162,7 +138,7 @@ export class CoreState implements ICoreState {
|
||||
this.flags = new FlagSystem();
|
||||
|
||||
// 地图
|
||||
this.maps = new MapState(tileStore, this);
|
||||
this.maps = new MapState(this.tileStore, this);
|
||||
|
||||
// 勇士
|
||||
const heroAttribute = new HeroAttribute(HERO_DEFAULT_ATTRIBUTE);
|
||||
@ -237,17 +213,6 @@ export class CoreState implements ICoreState {
|
||||
this.addSaveableContent('@system/enemy', this.enemyManager);
|
||||
this.addSaveableContent('@system/replay', this.replaySystem);
|
||||
|
||||
// 加载初始化,先使用兼容层实现
|
||||
loading.once('loaded', () => {
|
||||
this.initTileStore(core.maps.blocksInfo);
|
||||
this.initItemStore(core.items.items);
|
||||
this.initEnemyManager(enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80);
|
||||
this.initMapState(
|
||||
core.floorIds,
|
||||
core.floors as Record<FloorIds, ResolvedFloor>
|
||||
);
|
||||
});
|
||||
|
||||
// 勇士顶层初始化
|
||||
const heroMoveTopImpl = new DefaultHeroMoveTopImpl(this);
|
||||
this.hero.location.mover.useTopImplementation(heroMoveTopImpl);
|
||||
@ -285,168 +250,6 @@ export class CoreState implements ICoreState {
|
||||
replay.registerCommand(ReplayCode.Unequip, unequip);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化图块存储对象
|
||||
* @param data 旧样板图块定义对象
|
||||
*/
|
||||
private initTileStore(data: typeof core.maps.blocksInfo) {
|
||||
const entries = Object.entries(data);
|
||||
for (const [key, block] of entries) {
|
||||
this.tileStore.fromLegacy(Number(key), block);
|
||||
}
|
||||
|
||||
for (const [key, block] of entries) {
|
||||
if (!block.faceIds) continue;
|
||||
const { down, up, left, right } = block.faceIds;
|
||||
const downNum = this.tileStore.idToNumber(down);
|
||||
if (downNum !== Number(key)) continue;
|
||||
const upNum = this.tileStore.idToNumber(up);
|
||||
const leftNum = this.tileStore.idToNumber(left);
|
||||
const rightNum = this.tileStore.idToNumber(right);
|
||||
this.roleFace.malloc(downNum, FaceDirection.Down);
|
||||
if (!isNil(upNum)) {
|
||||
this.roleFace.bind(upNum, downNum, FaceDirection.Up);
|
||||
}
|
||||
if (!isNil(leftNum)) {
|
||||
this.roleFace.bind(leftNum, downNum, FaceDirection.Left);
|
||||
}
|
||||
if (!isNil(rightNum)) {
|
||||
this.roleFace.bind(rightNum, downNum, FaceDirection.Right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化道具存储对象
|
||||
* @param data 旧样板道具定义对象
|
||||
*/
|
||||
private initItemStore(data: typeof core.items.items) {
|
||||
const entries = Object.entries(data);
|
||||
for (const [id, legacy] of entries) {
|
||||
const num = this.tileStore.idToNumber(id);
|
||||
if (isNil(num)) {
|
||||
logger.warn(145, id);
|
||||
continue;
|
||||
}
|
||||
this.itemStore.fromLegacy(num, legacy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化怪物管理器对象
|
||||
* @param data 旧样板怪物存储对象
|
||||
*/
|
||||
private initEnemyManager(data: Record<EnemyIds, Enemy>) {
|
||||
const manager = this.enemyManager;
|
||||
const reference = new Map<number, IReadonlyEnemy<IEnemyAttr>>();
|
||||
for (const [id, enemy] of Object.entries(structuredClone(data))) {
|
||||
const num = this.tileStore.idToNumber(id);
|
||||
if (isNil(num)) continue;
|
||||
if (enemy.faceIds) {
|
||||
// 有 faceId 的要把其他的也映射到当前怪物
|
||||
const { left, up, right, down } = enemy.faceIds;
|
||||
const leftCode = this.tileStore.idToNumber(left)!;
|
||||
const upCode = this.tileStore.idToNumber(up)!;
|
||||
const rightCode = this.tileStore.idToNumber(right)!;
|
||||
const downCode = this.tileStore.idToNumber(down)!;
|
||||
const prefab = manager.fromLegacyEnemy(downCode, enemy);
|
||||
reference.set(downCode, prefab);
|
||||
manager.addPrefab(prefab);
|
||||
this.roleFace.malloc(downCode, FaceDirection.Down);
|
||||
this.roleFace.bind(leftCode, downCode, FaceDirection.Left);
|
||||
this.roleFace.bind(upCode, downCode, FaceDirection.Up);
|
||||
this.roleFace.bind(rightCode, downCode, FaceDirection.Down);
|
||||
manager.reusePrefab(num, leftCode, left);
|
||||
manager.reusePrefab(num, upCode, up);
|
||||
manager.reusePrefab(num, rightCode, right);
|
||||
} else {
|
||||
const prefab = manager.fromLegacyEnemy(num, enemy);
|
||||
reference.set(num, prefab);
|
||||
manager.addPrefab(prefab);
|
||||
}
|
||||
}
|
||||
manager.compareWith(reference);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化地图状态
|
||||
* @param floors 楼层列表
|
||||
* @param data 每个楼层对应的旧版地图信息
|
||||
*/
|
||||
private initMapState(
|
||||
floors: FloorIds[],
|
||||
data: Record<FloorIds, ResolvedFloor>
|
||||
) {
|
||||
const reference = new Map<string, Map<number, Uint32Array>>();
|
||||
for (const id of floors) {
|
||||
const floor = data[id];
|
||||
const state = this.maps.createMap(id, floor.width, floor.height);
|
||||
const bg = state.addLayer();
|
||||
const bg2 = state.addLayer();
|
||||
const event = state.addLayer();
|
||||
const fg = state.addLayer();
|
||||
const fg2 = state.addLayer();
|
||||
bg.setZIndex(BG_ZINDEX);
|
||||
bg2.setZIndex(BG2_ZINDEX);
|
||||
event.setZIndex(EVENT_ZINDEX);
|
||||
fg.setZIndex(FG_ZINDEX);
|
||||
fg2.setZIndex(FG2_ZINDEX);
|
||||
state.setLayerAlias(bg, 'bg');
|
||||
state.setLayerAlias(bg2, 'bg2');
|
||||
state.setLayerAlias(event, 'event');
|
||||
state.setEventLayer(event);
|
||||
state.setLayerAlias(fg, 'fg');
|
||||
state.setLayerAlias(fg2, 'fg2');
|
||||
state.setActiveStatus(false);
|
||||
|
||||
const size = floor.width * floor.height;
|
||||
const ref = new Map<number, Uint32Array>();
|
||||
|
||||
if (floor.bgmap && floor.bgmap.length > 0) {
|
||||
const arr = new Uint32Array(floor.bgmap.flat());
|
||||
bg.setMapRef(arr);
|
||||
ref.set(BG_ZINDEX, new Uint32Array(arr));
|
||||
} else {
|
||||
ref.set(BG_ZINDEX, new Uint32Array(size));
|
||||
}
|
||||
|
||||
if (floor.bg2map && floor.bg2map.length > 0) {
|
||||
const arr = new Uint32Array(floor.bg2map.flat());
|
||||
bg2.setMapRef(arr);
|
||||
ref.set(BG2_ZINDEX, new Uint32Array(arr));
|
||||
} else {
|
||||
ref.set(BG2_ZINDEX, new Uint32Array(size));
|
||||
}
|
||||
|
||||
if (floor.map && floor.map.length > 0) {
|
||||
const arr = new Uint32Array(floor.map.flat());
|
||||
event.setMapRef(arr);
|
||||
ref.set(EVENT_ZINDEX, new Uint32Array(arr));
|
||||
} else {
|
||||
ref.set(EVENT_ZINDEX, new Uint32Array(size));
|
||||
}
|
||||
|
||||
if (floor.fgmap && floor.fgmap.length > 0) {
|
||||
const arr = new Uint32Array(floor.fgmap.flat());
|
||||
fg.setMapRef(arr);
|
||||
ref.set(FG_ZINDEX, new Uint32Array(arr));
|
||||
} else {
|
||||
ref.set(FG_ZINDEX, new Uint32Array(size));
|
||||
}
|
||||
|
||||
if (floor.fg2map && floor.fg2map.length > 0) {
|
||||
const arr = new Uint32Array(floor.fg2map.flat());
|
||||
fg2.setMapRef(arr);
|
||||
ref.set(FG2_ZINDEX, new Uint32Array(arr));
|
||||
} else {
|
||||
ref.set(FG2_ZINDEX, new Uint32Array(size));
|
||||
}
|
||||
|
||||
reference.set(id, ref);
|
||||
}
|
||||
this.maps.compareWith(reference);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 存档方法
|
||||
|
||||
@ -3,7 +3,6 @@ import {
|
||||
defineNonePropertySpecial,
|
||||
IEnemyManager
|
||||
} from '@user/data-base';
|
||||
import { getHeroStatusOn } from '../legacy/hero';
|
||||
import { IEnemyAttr } from '@user/data-common';
|
||||
|
||||
//#region 复合属性值类型
|
||||
@ -176,6 +175,7 @@ export function registerSpecials(manager: IEnemyManager<IEnemyAttr>): void {
|
||||
const { vampire, add } = special.value;
|
||||
return (
|
||||
`战斗前,怪物首先吸取角色的${vampire}%生命` +
|
||||
// @ts-expect-error 需要重构
|
||||
`(约${Math.floor((vampire / 100) * getHeroStatusOn('hp'))}点)作为伤害` +
|
||||
(add ? `,并把伤害数值加到自身生命上。` : `。`)
|
||||
);
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
export * from './enemy';
|
||||
export * from './event';
|
||||
export * from './hero';
|
||||
export * from './legacy';
|
||||
export * from './replay';
|
||||
|
||||
export * from './core';
|
||||
|
||||
@ -1,115 +0,0 @@
|
||||
/**
|
||||
* 获取勇士在某一点的属性
|
||||
* @param name 要获取的勇士属性
|
||||
* @param floorId 勇士所在楼层
|
||||
*/
|
||||
export function getHeroStatusOn(name: 'all', floorId?: FloorIds): HeroStatus;
|
||||
export function getHeroStatusOn(
|
||||
name: (keyof HeroStatus)[],
|
||||
floorId?: FloorIds
|
||||
): Partial<HeroStatus>;
|
||||
export function getHeroStatusOn<K extends keyof HeroStatus>(
|
||||
name: K,
|
||||
floorId?: FloorIds
|
||||
): HeroStatus[K];
|
||||
export function getHeroStatusOn(
|
||||
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
|
||||
floorId?: FloorIds
|
||||
) {
|
||||
// @ts-expect-error 暂时无法推导
|
||||
return getHeroStatusOf(core.status.hero, name, floorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一定状态下的勇士在某一点的属性
|
||||
* @param status 勇士的状态
|
||||
* @param name 要获取的勇士属性
|
||||
* @param floorId 勇士所在楼层
|
||||
*/
|
||||
export function getHeroStatusOf(
|
||||
status: Partial<HeroStatus>,
|
||||
name: 'all',
|
||||
floorId?: FloorIds
|
||||
): HeroStatus;
|
||||
export function getHeroStatusOf(
|
||||
status: Partial<HeroStatus>,
|
||||
name: (keyof HeroStatus)[],
|
||||
floorId?: FloorIds
|
||||
): Partial<HeroStatus>;
|
||||
export function getHeroStatusOf<K extends keyof HeroStatus>(
|
||||
status: Partial<HeroStatus>,
|
||||
name: K,
|
||||
floorId?: FloorIds
|
||||
): HeroStatus[K];
|
||||
export function getHeroStatusOf(
|
||||
status: DeepPartial<HeroStatus>,
|
||||
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
|
||||
floorId?: FloorIds
|
||||
) {
|
||||
return getRealStatus(status, name, floorId);
|
||||
}
|
||||
|
||||
function getRealStatus(
|
||||
status: DeepPartial<HeroStatus>,
|
||||
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
|
||||
floorId: FloorIds = core.status.floorId
|
||||
): any {
|
||||
if (name instanceof Array) {
|
||||
const res: any = {};
|
||||
name.forEach(v => {
|
||||
res[v] = getRealStatus(status, v, floorId);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
if (name === 'all') {
|
||||
const res: any = {};
|
||||
for (const [key, value] of Object.entries(core.status.hero)) {
|
||||
if (typeof value === 'number') {
|
||||
res[key] = getRealStatus(
|
||||
status,
|
||||
key as keyof HeroStatus,
|
||||
floorId
|
||||
);
|
||||
} else {
|
||||
res[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
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 (typeof s !== 'number') return s;
|
||||
|
||||
// buff
|
||||
s *= core.status.hero.buff[name] ?? 1;
|
||||
s = Math.floor(s);
|
||||
|
||||
// 衰弱效果
|
||||
if ((name === 'atk' || name === 'def') && flags.weak) {
|
||||
const weak = core.values.weakValue;
|
||||
if (weak < 1) {
|
||||
// 百分比衰弱
|
||||
s *= 1 - weak;
|
||||
} else {
|
||||
s -= weak;
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// 下面的内容暂时无用
|
||||
|
||||
export interface IHeroStatusDefault {
|
||||
atk: number;
|
||||
def: number;
|
||||
hp: number;
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
export * from './hero';
|
||||
export * from './interface';
|
||||
export * from './item';
|
||||
export * from './move';
|
||||
export * from './tile';
|
||||
export * from './utils';
|
||||
@ -1,4 +0,0 @@
|
||||
export interface IStateDamageable {
|
||||
/** 生命值 */
|
||||
hp: number;
|
||||
}
|
||||
@ -1,139 +0,0 @@
|
||||
import {
|
||||
IHeroAttr,
|
||||
IItemEffect,
|
||||
IItemEquipData,
|
||||
IItemLegacyConverter,
|
||||
IItemRawData,
|
||||
ItemCategory
|
||||
} from '@user/data-common';
|
||||
import { IStateSystem } from '@user/data-system';
|
||||
|
||||
export type LegacyItemData = Item<AllIdsOf<'items'>>;
|
||||
|
||||
type LegacyItemEffectFn = (
|
||||
state: IStateSystem,
|
||||
item: IItemRawData<IHeroAttr>
|
||||
) => void;
|
||||
type LegacyItemCanUseFn = (
|
||||
state: IStateSystem,
|
||||
item: IItemRawData<IHeroAttr>
|
||||
) => boolean;
|
||||
|
||||
class LegacyItemEffect implements IItemEffect<IHeroAttr> {
|
||||
pickEvent: unknown;
|
||||
useEvent: unknown;
|
||||
|
||||
/** 道具使用效果 */
|
||||
private readonly useFn: LegacyItemEffectFn | null = null;
|
||||
/** 是否能够使用道具 */
|
||||
private readonly canUseFn: LegacyItemCanUseFn | null = null;
|
||||
/** 全局状态对象 */
|
||||
private readonly state: IStateSystem;
|
||||
|
||||
constructor(legacy: LegacyItemData, state: IStateSystem) {
|
||||
this.state = state;
|
||||
this.pickEvent = undefined;
|
||||
this.useEvent = legacy.useItemEvent;
|
||||
|
||||
if (legacy.cls === 'items') {
|
||||
if (legacy.itemEffect) {
|
||||
this.useFn = new Function(
|
||||
'state',
|
||||
'item',
|
||||
legacy.itemEffect
|
||||
) as LegacyItemEffectFn;
|
||||
}
|
||||
} else if (legacy.cls === 'constants' || legacy.cls === 'tools') {
|
||||
if (legacy.useItemEffect) {
|
||||
this.useFn = new Function(
|
||||
'state',
|
||||
'item',
|
||||
legacy.useItemEffect
|
||||
) as LegacyItemEffectFn;
|
||||
}
|
||||
if (typeof legacy.canUseItemEffect === 'string') {
|
||||
this.canUseFn = new Function(
|
||||
'state',
|
||||
'item',
|
||||
legacy.canUseItemEffect
|
||||
) as LegacyItemCanUseFn;
|
||||
} else {
|
||||
this.canUseFn = () => !!legacy.canUseItemEffect;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(item: IItemRawData<IHeroAttr>): void {
|
||||
this.useFn?.(this.state, item);
|
||||
}
|
||||
|
||||
canUse(item: IItemRawData<IHeroAttr>): boolean {
|
||||
if (!this.canUseFn) return true;
|
||||
else return this.canUseFn(this.state, item);
|
||||
}
|
||||
}
|
||||
|
||||
export class ItemLegacyBridge implements IItemLegacyConverter<
|
||||
IHeroAttr,
|
||||
LegacyItemData
|
||||
> {
|
||||
constructor(private readonly state: IStateSystem) {}
|
||||
|
||||
fromLegacy(num: number, legacy: LegacyItemData): IItemRawData<IHeroAttr> {
|
||||
const effect = new LegacyItemEffect(legacy, this.state);
|
||||
return {
|
||||
num,
|
||||
id: legacy.id,
|
||||
category: this.mapCategory(legacy.cls),
|
||||
name: legacy.name,
|
||||
text: legacy.text ?? '',
|
||||
hideInToolbox: legacy.hideInToolBox,
|
||||
effect,
|
||||
equip: this.convertEquip(legacy.equip)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将旧样板装备信息转换为新样板接口
|
||||
* @param legacy 旧样板格式的装备信息
|
||||
*/
|
||||
private convertEquip(legacy: Equip): IItemEquipData<IHeroAttr> {
|
||||
const valueMap = new Map<SelectKey<IHeroAttr, number>, number>();
|
||||
const perMap = new Map<SelectKey<IHeroAttr, number>, number>();
|
||||
|
||||
for (const [key, value] of Object.entries(legacy.value)) {
|
||||
valueMap.set(key as SelectKey<IHeroAttr, number>, value);
|
||||
}
|
||||
for (const [key, value] of Object.entries(legacy.percentage)) {
|
||||
perMap.set(key as SelectKey<IHeroAttr, number>, value);
|
||||
}
|
||||
|
||||
return {
|
||||
slots: [legacy.type],
|
||||
animate: legacy.animate,
|
||||
value: valueMap,
|
||||
percentage: perMap,
|
||||
loadEvent: legacy.equipEvent,
|
||||
unloadEvent: legacy.unequipEvent
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 从旧样板道具类型获取新接口对应的类型
|
||||
* @param cls 旧样板道具类型
|
||||
*/
|
||||
private mapCategory(cls: ItemCls): ItemCategory {
|
||||
switch (cls) {
|
||||
case 'constants':
|
||||
return ItemCategory.Constant;
|
||||
case 'tools':
|
||||
return ItemCategory.Consumable;
|
||||
case 'equips':
|
||||
return ItemCategory.Equipment;
|
||||
case 'items':
|
||||
return ItemCategory.Pick;
|
||||
default:
|
||||
return ItemCategory.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,523 +0,0 @@
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { backDir, toDir } from './utils';
|
||||
import { loading } from '@user/data-base';
|
||||
import type { HeroKeyMover } from '@user/client-modules';
|
||||
import { sleep } from '@motajs/common';
|
||||
import { fromDirectionString } from '@user/data-common';
|
||||
|
||||
function getDataStateMover() {
|
||||
if (typeof Mota === 'undefined') return null;
|
||||
return Mota.require('@user/data-state').state.hero.location.mover;
|
||||
}
|
||||
|
||||
// todo: 转身功能
|
||||
|
||||
interface MoveStepDir {
|
||||
type: 'dir';
|
||||
value: Move2;
|
||||
}
|
||||
|
||||
interface MoveStepSpeed {
|
||||
type: 'speed';
|
||||
value: number;
|
||||
}
|
||||
|
||||
export type MoveStep = MoveStepDir | MoveStepSpeed;
|
||||
|
||||
export interface IMoveController {
|
||||
/** 本次移动是否完成 */
|
||||
done: boolean;
|
||||
/** 当前移动队列 */
|
||||
queue: MoveStep[];
|
||||
/** 当本次移动完成时兑现 */
|
||||
onEnd: Promise<void>;
|
||||
|
||||
/**
|
||||
* 停止本次移动,停止后将不再能继续移动
|
||||
*/
|
||||
stop(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 向队列末尾添加移动信息
|
||||
* @param step 移动信息
|
||||
*/
|
||||
push(...step: MoveStep[]): void;
|
||||
}
|
||||
|
||||
interface EObjectMovingEvent {
|
||||
stepEnd: [step: MoveStep];
|
||||
moveEnd: [];
|
||||
moveStart: [queue: MoveStep[]];
|
||||
}
|
||||
|
||||
export abstract class ObjectMoverBase extends EventEmitter<EObjectMovingEvent> {
|
||||
/** 移动队列 */
|
||||
private moveQueue: MoveStep[] = [];
|
||||
|
||||
/** 当前的移动速度 */
|
||||
moveSpeed: number = 100;
|
||||
/** 当前的移动方向 */
|
||||
moveDir: Dir2 = 'down';
|
||||
/** 当前是否正在移动 */
|
||||
moving: boolean = false;
|
||||
/** 面朝方向 */
|
||||
faceDir: Dir2 = 'down';
|
||||
|
||||
/** 当前的控制对象 */
|
||||
controller?: IMoveController;
|
||||
|
||||
/**
|
||||
* 当本次移动开始时执行的函数
|
||||
* @param controller 本次移动的控制对象
|
||||
*/
|
||||
protected abstract onMoveStart(controller: IMoveController): Promise<void>;
|
||||
|
||||
/**
|
||||
* 当本次移动结束时执行的函数
|
||||
* @param controller 本次移动的控制对象
|
||||
*/
|
||||
protected abstract onMoveEnd(controller: IMoveController): Promise<void>;
|
||||
|
||||
/**
|
||||
* 当这一步开始移动时执行的函数,可以用来播放移动动画等
|
||||
* @param step 这一步的移动信息
|
||||
* @param controller 本次移动的控制对象
|
||||
* @returns 一个Promise,其值表示本次移动的代码,并将其值传入这一步移动后的操作,即{@link onStepEnd}
|
||||
*/
|
||||
protected abstract onStepStart(
|
||||
step: MoveStepDir,
|
||||
controller: IMoveController
|
||||
): Promise<number>;
|
||||
|
||||
/**
|
||||
* 当这一步移动结束后执行的函数(即{@link onStepStart}成功兑现后),可以用于设置物体坐标等
|
||||
* @param step 这一步的移动信息
|
||||
* @param code 本步移动的代码,即onMoveStart的返回值
|
||||
* @param controller 本次移动的控制对象
|
||||
* @returns 一个Promise,当其兑现时,本步移动全部完成,将会开始下一步的移动
|
||||
*/
|
||||
protected abstract onStepEnd(
|
||||
step: MoveStepDir,
|
||||
code: number,
|
||||
controller: IMoveController
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* 当移动速度被设置时执行的函数
|
||||
* @param speed 设置为的移动速度
|
||||
* @param controller 本次移动的控制对象
|
||||
*/
|
||||
protected abstract onSetMoveSpeed(
|
||||
speed: number,
|
||||
controller: IMoveController
|
||||
): void;
|
||||
|
||||
private getMoveDir(move: Move2): Dir2 {
|
||||
if (move === 'forward') return this.moveDir;
|
||||
if (move === 'backward') return backDir(this.faceDir);
|
||||
return move;
|
||||
}
|
||||
|
||||
private getFaceDir(move: Move2): Dir2 {
|
||||
if (move === 'forward' || move === 'backward') return this.faceDir;
|
||||
return move;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前面朝方向,移动中设置无效
|
||||
* @param dir 面朝方向
|
||||
*/
|
||||
setFaceDir(dir: Dir2) {
|
||||
if (!this.moving) this.faceDir = dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前移动方向,移动中设置无效
|
||||
* @param dir 移动方向
|
||||
*/
|
||||
setMoveDir(dir: Dir2) {
|
||||
if (!this.moving) this.moveDir = dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始移动,这个操作后,本次移动将不能通过{@link insertMove}添加移动步骤,
|
||||
* 只能通过{@link IMoveController.push}添加,而通过前者添加的移动步骤会在下次移动生效
|
||||
*/
|
||||
startMove(): IMoveController | null {
|
||||
if (this.moving) return null;
|
||||
const queue = this.moveQueue.slice();
|
||||
this.clearMoveQueue();
|
||||
|
||||
this.moving = true;
|
||||
let stopMove = false;
|
||||
|
||||
const start = async () => {
|
||||
// 等待宏任务执行完成,不然controller会在首次调用中未定义
|
||||
await new Promise<void>(res => res());
|
||||
await this.onMoveStart(controller);
|
||||
this.emit('moveStart', queue);
|
||||
while (queue.length > 0) {
|
||||
if (stopMove || !this.moving) break;
|
||||
const step = queue.shift();
|
||||
if (!step) break;
|
||||
if (step.type === 'dir') {
|
||||
this.moveDir = this.getMoveDir(step.value);
|
||||
this.faceDir = this.getFaceDir(step.value);
|
||||
const code = await this.onStepStart(step, controller);
|
||||
await this.onStepEnd(step, code, controller);
|
||||
} else {
|
||||
const speed = step.value;
|
||||
this.moveSpeed = speed;
|
||||
this.onSetMoveSpeed(speed, controller);
|
||||
}
|
||||
this.emit('stepEnd', step);
|
||||
}
|
||||
this.moving = false;
|
||||
this.emit('moveEnd');
|
||||
|
||||
await this.onMoveEnd(controller);
|
||||
};
|
||||
|
||||
const onEnd = start().then(() => {
|
||||
this.controller = void 0;
|
||||
controller.done = true;
|
||||
});
|
||||
const controller: IMoveController = {
|
||||
done: false,
|
||||
onEnd,
|
||||
queue,
|
||||
push(...step) {
|
||||
queue.push(...step);
|
||||
},
|
||||
stop() {
|
||||
stopMove = true;
|
||||
return onEnd;
|
||||
}
|
||||
};
|
||||
this.controller = controller;
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向移动队列末尾插入移动实例
|
||||
* @param move 移动实例
|
||||
*/
|
||||
insertMove(...move: MoveStep[]) {
|
||||
this.moveQueue.push(...move);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空移动队列
|
||||
*/
|
||||
clearMoveQueue() {
|
||||
this.moveQueue = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 向移动队列末尾插入一个移动操作
|
||||
* @param step 移动方向
|
||||
*/
|
||||
oneStep(step: Move2) {
|
||||
this.moveQueue.push({ type: 'dir', value: step });
|
||||
}
|
||||
|
||||
/**
|
||||
* 按照传入的数组移动物体,插入至移动队列末尾
|
||||
* @param steps 移动步骤
|
||||
*/
|
||||
moveAs(steps: MoveStep[]) {
|
||||
this.moveQueue.push(...steps);
|
||||
}
|
||||
}
|
||||
|
||||
// todo: refactor
|
||||
|
||||
interface CanMoveStatus {
|
||||
/** 由CannotIn和CannotOut计算出的信息,不可移动时不会触发触发器 */
|
||||
canMove: boolean;
|
||||
/** 由图块的noPass计算出的信息,不可移动时会触发触发器 */
|
||||
noPass: boolean;
|
||||
}
|
||||
|
||||
const enum HeroMoveCode {
|
||||
Step,
|
||||
Stop,
|
||||
/** 不能移动,并撞击前面一格的图块,触发其触发器 */
|
||||
Hit,
|
||||
/** 不能移动,同时当前格有CannotOut,或目标格有CannotIn,不会触发前面一格的触发器 */
|
||||
CannotMove
|
||||
}
|
||||
|
||||
export class HeroMover extends ObjectMoverBase {
|
||||
/** 当前移动是否忽略地形 */
|
||||
private ignoreTerrain: boolean = false;
|
||||
/** 当前移动是否不计入录像 */
|
||||
private noRoute: boolean = false;
|
||||
/** 当前移动是否是在lockControl条件下开始的 */
|
||||
private inLockControl: boolean = false;
|
||||
/** 是否会在特殊时刻进行自动存档 */
|
||||
private autoSave: boolean = false;
|
||||
|
||||
/** 本次移动开始时的移动速度 */
|
||||
private beforeMoveSpeed: number = 100;
|
||||
|
||||
override startMove(
|
||||
ignoreTerrain: boolean = false,
|
||||
noRoute: boolean = false,
|
||||
inLockControl: boolean = false,
|
||||
autoSave: boolean = false
|
||||
): IMoveController | null {
|
||||
if (this.moving) return null;
|
||||
this.ignoreTerrain = ignoreTerrain;
|
||||
this.noRoute = noRoute;
|
||||
this.inLockControl = inLockControl;
|
||||
this.autoSave = autoSave;
|
||||
return super.startMove();
|
||||
}
|
||||
|
||||
private checkAutoSave(_x: number, _y: number, _nx: number, _ny: number) {
|
||||
// const index = `${x},${y}`;
|
||||
// const nIndex = `${nx},${ny}`;
|
||||
// const map = core.status.thisMap.enemy.mapDamage;
|
||||
// const dam = map[index];
|
||||
// const nextDam = map[nIndex];
|
||||
// if (!dam || !nextDam) return;
|
||||
// 可以在这里判断地图伤害,并进行自动存档,例如在进入或离开地图伤害时存档
|
||||
// if (dam.damage > 0 || nextDam.damage > 0) {
|
||||
// core.autosave()
|
||||
// }
|
||||
}
|
||||
|
||||
protected async onMoveStart(controller: IMoveController): Promise<void> {
|
||||
this.beforeMoveSpeed = this.moveSpeed;
|
||||
if (!core.isReplaying() || core.status.replay.speed <= 12) {
|
||||
getDataStateMover()?.start();
|
||||
}
|
||||
// 这里要检查前面那一格能不能走,不能走则不触发平滑视角,以避免撞墙上视角卡住
|
||||
if (!this.ignoreTerrain) {
|
||||
const { x, y } = core.status.hero.loc;
|
||||
const firstDir = controller.queue.find(
|
||||
v => v.type === 'dir'
|
||||
)?.value;
|
||||
if (firstDir && firstDir !== 'backward' && firstDir !== 'forward') {
|
||||
const data = this.checkCanMove(x, y, toDir(firstDir as Dir));
|
||||
if (data.canMove && !data.noPass) {
|
||||
// viewport.sync('startMove');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// viewport.sync('startMove');
|
||||
}
|
||||
}
|
||||
|
||||
protected async onMoveEnd(controller: IMoveController): Promise<void> {
|
||||
this.moveSpeed = this.beforeMoveSpeed;
|
||||
this.onSetMoveSpeed(this.moveSpeed, controller);
|
||||
// viewport.sync('endMove');
|
||||
core.clearContinueAutomaticRoute();
|
||||
core.stopAutomaticRoute();
|
||||
}
|
||||
|
||||
protected async onStepStart(
|
||||
_step: MoveStepDir,
|
||||
controller: IMoveController
|
||||
): Promise<HeroMoveCode> {
|
||||
const showDir = toDir(this.faceDir);
|
||||
const dir4Move = toDir(this.moveDir);
|
||||
core.setHeroLoc('direction', showDir);
|
||||
|
||||
const { x, y } = core.status.hero.loc;
|
||||
const { x: nx, y: ny } = this.nextLoc(x, y, this.moveDir);
|
||||
|
||||
if (this.autoSave && !this.ignoreTerrain) {
|
||||
this.checkAutoSave(x, y, nx, ny);
|
||||
}
|
||||
|
||||
if (!this.inLockControl && core.status.lockControl) {
|
||||
controller.stop();
|
||||
return HeroMoveCode.Stop;
|
||||
}
|
||||
|
||||
if (!this.ignoreTerrain || !this.noRoute) {
|
||||
this.moveDir = dir4Move;
|
||||
}
|
||||
|
||||
const dir = this.moveDir;
|
||||
if (!this.ignoreTerrain) {
|
||||
const { noPass, canMove } = this.checkCanMove(x, y, dir4Move);
|
||||
|
||||
if (!canMove) {
|
||||
return HeroMoveCode.CannotMove;
|
||||
}
|
||||
// 不能移动
|
||||
if (noPass) {
|
||||
return HeroMoveCode.Hit;
|
||||
}
|
||||
}
|
||||
|
||||
// 可以移动,显示移动动画
|
||||
|
||||
await this.moveAnimate(nx, ny, showDir, dir);
|
||||
|
||||
return HeroMoveCode.Step;
|
||||
}
|
||||
|
||||
protected async onStepEnd(
|
||||
_step: MoveStepDir,
|
||||
code: HeroMoveCode,
|
||||
controller: IMoveController
|
||||
): Promise<void> {
|
||||
const { x, y } = core.status.hero.loc;
|
||||
const { x: nx, y: ny } = this.nextLoc(x, y, this.moveDir);
|
||||
const showDir = toDir(this.faceDir);
|
||||
|
||||
// 前方不能移动
|
||||
if (code === HeroMoveCode.CannotMove || code === HeroMoveCode.Hit) {
|
||||
controller.stop();
|
||||
this.onCannotMove(showDir);
|
||||
if (code === HeroMoveCode.Hit) {
|
||||
core.trigger(nx, ny);
|
||||
}
|
||||
core.checkRouteFolding();
|
||||
return;
|
||||
}
|
||||
|
||||
// 本次移动停止
|
||||
if (code === HeroMoveCode.Stop) {
|
||||
core.clearContinueAutomaticRoute();
|
||||
core.stopAutomaticRoute();
|
||||
controller.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
// 本次移动正常完成
|
||||
if (code === HeroMoveCode.Step) {
|
||||
core.setHeroLoc('x', nx, true);
|
||||
core.setHeroLoc('y', ny, true);
|
||||
|
||||
if (!this.ignoreTerrain) {
|
||||
const direction = core.getHeroLoc('direction');
|
||||
core.control._moveAction_popAutomaticRoute();
|
||||
if (!this.noRoute) core.status.route.push(direction);
|
||||
|
||||
// 中毒处理
|
||||
if (core.hasFlag('poison')) {
|
||||
core.status.hero.hp -= core.values.poisonDamage;
|
||||
if (core.status.hero.hp <= 0) {
|
||||
core.status.hero.hp = 0;
|
||||
core.events.lose();
|
||||
}
|
||||
core.updateStatusBar();
|
||||
}
|
||||
|
||||
core.moveOneStep();
|
||||
core.checkRouteFolding();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected onSetMoveSpeed(
|
||||
speed: number,
|
||||
_controller: IMoveController
|
||||
): void {
|
||||
this.moveSpeed = speed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动动画
|
||||
* @param x 目标横坐标
|
||||
* @param y 目标纵坐标
|
||||
* @param _showDir 显示方向
|
||||
* @param moveDir 移动方向
|
||||
*/
|
||||
private async moveAnimate(
|
||||
x: number,
|
||||
y: number,
|
||||
_showDir: Dir,
|
||||
moveDir: Dir2
|
||||
) {
|
||||
// const viewport = HeroMover.viewport;
|
||||
// if (!viewport) return;
|
||||
const replay = core.status.replay.speed;
|
||||
const speed = replay === 24 ? 1 : this.moveSpeed / replay;
|
||||
// viewport.all('moveTo', x, y, speed * 1.6);
|
||||
const replaying = core.isReplaying();
|
||||
if (replaying) {
|
||||
if (core.status.replay.speed > 12) {
|
||||
await sleep(speed);
|
||||
getDataStateMover()?.setPos(x, y);
|
||||
} else {
|
||||
const mover = getDataStateMover();
|
||||
mover?.step(fromDirectionString(moveDir));
|
||||
await mover?.start()?.onEnd;
|
||||
}
|
||||
} else {
|
||||
const mover = getDataStateMover();
|
||||
mover?.step(fromDirectionString(moveDir));
|
||||
await mover?.start()?.onEnd;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前方一格不能走时,执行的函数
|
||||
* @param dir 移动方向
|
||||
*/
|
||||
private onCannotMove(dir: Dir) {
|
||||
if (!this.noRoute) core.status.route.push(dir);
|
||||
core.status.automaticRoute.moveStepBeforeStop = [];
|
||||
core.status.automaticRoute.lastDirection = dir;
|
||||
|
||||
if (core.status.automaticRoute.moveStepBeforeStop.length === 0) {
|
||||
core.clearContinueAutomaticRoute();
|
||||
core.stopAutomaticRoute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查前方是否可以移动
|
||||
* @param x 当前横坐标
|
||||
* @param y 当前纵坐标
|
||||
* @param dir 移动方向
|
||||
*/
|
||||
private checkCanMove(x: number, y: number, dir: Dir): CanMoveStatus {
|
||||
// 如果是循环式地图
|
||||
const { x: nx, y: ny } = this.nextLoc(x, y, dir);
|
||||
const noPass = core.noPass(nx, ny);
|
||||
const canMove = core.canMoveHero(x, y, dir);
|
||||
return { noPass, canMove };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取前方一格的坐标
|
||||
* @param x 当前横坐标
|
||||
* @param y 当前纵坐标
|
||||
* @param dir 移动方向
|
||||
*/
|
||||
private nextLoc(x: number, y: number, dir: Dir2): Loc {
|
||||
const { x: dx, y: dy } = core.utils.scan2[dir];
|
||||
const nx = x + dx;
|
||||
const ny = y + dy;
|
||||
return { x: nx, y: ny };
|
||||
}
|
||||
}
|
||||
|
||||
interface HeroMoveCollection {
|
||||
mover: HeroMover;
|
||||
keyMover?: HeroKeyMover;
|
||||
}
|
||||
|
||||
// 初始化基础勇士移动实例
|
||||
const heroMover = new HeroMover();
|
||||
export const heroMoveCollection: HeroMoveCollection = {
|
||||
mover: heroMover
|
||||
};
|
||||
loading.once('coreInit', () => {
|
||||
// 注册按键操作
|
||||
Mota.r(() => {
|
||||
const { HeroKeyMover } = Mota.require('@user/client-modules');
|
||||
const { gameKey } = Mota.require('@motajs/system');
|
||||
const keyMover = new HeroKeyMover(gameKey, heroMover);
|
||||
heroMoveCollection.keyMover = keyMover;
|
||||
});
|
||||
});
|
||||
@ -1,123 +0,0 @@
|
||||
import {
|
||||
ITileLegacyConverter,
|
||||
ITilePassData,
|
||||
ITileRawData,
|
||||
PassBit,
|
||||
TileType
|
||||
} from '@user/data-common';
|
||||
|
||||
interface ILegacyTileEventData {
|
||||
/** 旧样板图块携带的默认事件映射 */
|
||||
readonly events?: Record<number, string>;
|
||||
}
|
||||
|
||||
export type LegacyTileData = MapDataOf<keyof NumberToId> & ILegacyTileEventData;
|
||||
|
||||
export class TileLegacyBridge implements ITileLegacyConverter<LegacyTileData> {
|
||||
private getTileType(num: number, legacy: LegacyTileData): TileType {
|
||||
if (num === 0) return TileType.None;
|
||||
switch (legacy.cls) {
|
||||
case 'terrains':
|
||||
return TileType.Terrain;
|
||||
case 'autotile':
|
||||
return TileType.Autotile;
|
||||
case 'animates':
|
||||
return TileType.Animate;
|
||||
case 'items':
|
||||
return TileType.Item;
|
||||
case 'enemys':
|
||||
case 'enemy48':
|
||||
return TileType.Enemy;
|
||||
case 'npcs':
|
||||
case 'npc48':
|
||||
return TileType.Npc;
|
||||
// @ts-expect-error 动态类型声明导致的错误,忽略即可
|
||||
case 'tileset':
|
||||
return TileType.Tileset;
|
||||
default:
|
||||
return TileType.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
private getPass(num: number, legacy: LegacyTileData): ITilePassData {
|
||||
if (num === 0) {
|
||||
// 空图块
|
||||
return {
|
||||
onlyEvents: true,
|
||||
inPass: 0b1111,
|
||||
outPass: 0b1111
|
||||
};
|
||||
} else if (num === 17) {
|
||||
// 空气墙
|
||||
return {
|
||||
onlyEvents: false,
|
||||
inPass: 0b0000,
|
||||
outPass: 0b0000
|
||||
};
|
||||
} else {
|
||||
// 正常图块
|
||||
if (legacy.cannotIn && legacy.cannotOut) {
|
||||
let inPass = 0b1111;
|
||||
let outPass = 0b1111;
|
||||
if (legacy.cannotIn.includes('up')) {
|
||||
inPass &= ~PassBit.Up;
|
||||
}
|
||||
if (legacy.cannotIn.includes('right')) {
|
||||
inPass &= ~PassBit.Right;
|
||||
}
|
||||
if (legacy.cannotIn.includes('down')) {
|
||||
inPass &= ~PassBit.Down;
|
||||
}
|
||||
if (legacy.cannotIn.includes('left')) {
|
||||
inPass &= ~PassBit.Left;
|
||||
}
|
||||
if (legacy.cannotOut.includes('up')) {
|
||||
outPass &= ~PassBit.Up;
|
||||
}
|
||||
if (legacy.cannotOut.includes('right')) {
|
||||
outPass &= ~PassBit.Right;
|
||||
}
|
||||
if (legacy.cannotOut.includes('down')) {
|
||||
outPass &= ~PassBit.Down;
|
||||
}
|
||||
if (legacy.cannotOut.includes('left')) {
|
||||
outPass &= ~PassBit.Left;
|
||||
}
|
||||
return {
|
||||
onlyEvents: false,
|
||||
inPass,
|
||||
outPass
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
onlyEvents: true,
|
||||
inPass: 0b1111,
|
||||
outPass: 0b1111
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 将旧样板事件输入复制为新的默认事件对象 */
|
||||
private getEvents(legacy: LegacyTileData): Record<number, string> {
|
||||
const events: Record<number, string> = {};
|
||||
for (const [priority, event] of Object.entries(legacy.events ?? {})) {
|
||||
const priorityNum = Number(priority);
|
||||
if (Number.isInteger(priorityNum) && typeof event === 'string') {
|
||||
events[priorityNum] = event;
|
||||
}
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
fromLegacy(num: number, legacy: LegacyTileData): ITileRawData {
|
||||
return {
|
||||
num,
|
||||
id: legacy.id,
|
||||
events: this.getEvents(legacy),
|
||||
type: this.getTileType(num, legacy),
|
||||
pass: this.getPass(num, legacy),
|
||||
eventPass: !legacy.noPass
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,107 +0,0 @@
|
||||
const dirMap: Record<Dir2, Dir> = {
|
||||
down: 'down',
|
||||
left: 'left',
|
||||
leftdown: 'left',
|
||||
leftup: 'left',
|
||||
right: 'right',
|
||||
rightdown: 'right',
|
||||
rightup: 'right',
|
||||
up: 'up'
|
||||
};
|
||||
|
||||
/**
|
||||
* 将八方向转换为四方向,一般用于斜角移动时的方向显示
|
||||
* @param dir 八方向
|
||||
*/
|
||||
export function toDir(dir: Dir2): Dir {
|
||||
return dirMap[dir];
|
||||
}
|
||||
|
||||
const backDirMap: Record<Dir2, Dir2> = {
|
||||
up: 'down',
|
||||
down: 'up',
|
||||
left: 'right',
|
||||
right: 'left',
|
||||
leftup: 'rightdown',
|
||||
rightup: 'leftdown',
|
||||
leftdown: 'rightup',
|
||||
rightdown: 'leftup'
|
||||
};
|
||||
|
||||
export function backDir(dir: Dir): Dir;
|
||||
export function backDir(dir: Dir2): Dir2;
|
||||
export function backDir(dir: Dir2): Dir2 {
|
||||
return backDirMap[dir];
|
||||
}
|
||||
|
||||
export function locInMap(x: number, y: number, floorId: FloorIds) {
|
||||
const { width, height } = core.status.maps[floorId];
|
||||
return x >= 0 && y >= 0 && x < width && y < height;
|
||||
}
|
||||
|
||||
/**
|
||||
* 广义检查能否移动,指定两个点以及行走方向,返回能否执行上述移动。
|
||||
* 可以传入地图之外的点,视为可以随意移动。仅检查 cannotIn 和 cannotOut,不检查 noPass
|
||||
* @param fx 出点横坐标
|
||||
* @param fy 出点纵坐标
|
||||
* @param tx 入点横坐标
|
||||
* @param ty 入点纵坐标
|
||||
* @param dir 行走方向
|
||||
* @param fromFloorId 出点楼层id
|
||||
* @param toFloorId 入点楼层id
|
||||
*/
|
||||
export function checkCanMoveExtended(
|
||||
fx: number,
|
||||
fy: number,
|
||||
tx: number,
|
||||
ty: number,
|
||||
dir: Dir,
|
||||
fromFloorId: FloorIds = core.status.floorId,
|
||||
toFloorId: FloorIds = core.status.floorId
|
||||
) {
|
||||
const fromInMap = locInMap(fx, fy, fromFloorId);
|
||||
const toInMap = locInMap(tx, ty, toFloorId);
|
||||
const map = maps_90f36752_8815_4be8_b32b_d7fad1d0542e;
|
||||
|
||||
if (fromInMap) {
|
||||
const cannotMove = core.floors[fromFloorId].cannotMove;
|
||||
const fromIndex: LocString = `${fx},${fy}`;
|
||||
// 检查当前点是否有不可出
|
||||
if (cannotMove[fromIndex]?.includes(dir)) return false;
|
||||
const blocks = getBlockForLoc(fx, fy, fromFloorId);
|
||||
const can = blocks.some(v => {
|
||||
if (v === 0) return false;
|
||||
const out = map[v as Exclude<AllNumbers, 0>]?.cannotOut;
|
||||
return out?.includes(dir);
|
||||
});
|
||||
if (can) return false;
|
||||
}
|
||||
if (toInMap) {
|
||||
const cannotMoveIn = core.floors[toFloorId].cannotMoveIn;
|
||||
const toIndex: LocString = `${tx},${ty}`;
|
||||
const back = backDir(dir);
|
||||
// 检查目标点是否有不可入
|
||||
if (cannotMoveIn[toIndex]?.includes(back)) return false;
|
||||
const blocks = getBlockForLoc(tx, ty, toFloorId);
|
||||
const can = blocks.some(v => {
|
||||
if (v === 0) return false;
|
||||
const out = map[v as Exclude<AllNumbers, 0>]?.cannotIn;
|
||||
return out?.includes(back);
|
||||
});
|
||||
if (can) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function getBlockForLoc(x: number, y: number, floorId: FloorIds) {
|
||||
const map = core.status.maps[floorId];
|
||||
const floor = core.floors[floorId];
|
||||
return [
|
||||
floor.bgmap[y][x],
|
||||
floor.bg2map[y][x],
|
||||
map.map[y][x],
|
||||
floor.bgmap[y][x],
|
||||
floor.bg2map[y][x]
|
||||
];
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { TileStore } from '@user/data-common';
|
||||
import { LegacyTileData, TileLegacyBridge } from '../src/legacy/tile';
|
||||
|
||||
describe('TileLegacyBridge events-map contract', () => {
|
||||
// 验证旧样板事件对象会转换为 TileStore 可消费的默认事件映射
|
||||
it('converts legacy events into the TileStore event map', () => {
|
||||
const bridge = new TileLegacyBridge();
|
||||
const store = new TileStore<LegacyTileData>();
|
||||
store.attachLegacyConverter(bridge);
|
||||
const legacy: LegacyTileData = {
|
||||
id: 'yellowWall',
|
||||
cls: 'terrains',
|
||||
events: { 10: 'onEnter', 20: 'onTouch' },
|
||||
noPass: true
|
||||
};
|
||||
|
||||
const converted = store.fromLegacy(1, legacy);
|
||||
|
||||
expect(converted.events).toEqual({ 10: 'onEnter', 20: 'onTouch' });
|
||||
expect(store.getEvent(1)).toEqual(
|
||||
new Map([
|
||||
[10, 'onEnter'],
|
||||
[20, 'onTouch']
|
||||
])
|
||||
);
|
||||
expect(converted.eventPass).toBe(false);
|
||||
expect(converted).not.toHaveProperty('trigger');
|
||||
});
|
||||
|
||||
// 验证旧样板缺少事件输入时仍生成合法的空事件映射
|
||||
it('uses an empty events map when legacy input has no events', () => {
|
||||
const bridge = new TileLegacyBridge();
|
||||
const legacy: LegacyTileData = {
|
||||
id: 'whiteWall',
|
||||
cls: 'terrains'
|
||||
};
|
||||
|
||||
const converted = bridge.fromLegacy(2, legacy);
|
||||
|
||||
expect(converted.events).toEqual({});
|
||||
expect(converted).not.toHaveProperty('trigger');
|
||||
});
|
||||
});
|
||||
@ -62,6 +62,7 @@ export function getNowStatus(nowEquip?: Equip, onCol: boolean = false) {
|
||||
'hpmax',
|
||||
'money'
|
||||
] as (keyof SelectType<HeroStatus, number>)[];
|
||||
// @ts-expect-error 需要重构
|
||||
const { getHeroStatusOn } = Mota.require('@user/data-state');
|
||||
|
||||
return (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user