feat: 图块信息接口

This commit is contained in:
unanmed 2026-06-28 19:32:46 +08:00
parent 3e150c4a1d
commit eb34d63e96
8 changed files with 176 additions and 24 deletions

View File

@ -4,14 +4,18 @@ import {
IDataCommon,
IMoverController,
IObjectMover,
IRoleFaceBinder
IRoleFaceBinder,
ITileRawData
} from '@user/data-common';
import { IDynamicLayer, IDynamicTile } from './types';
import { DynamicTileMover } from './mover';
import { logger } from '@motajs/common';
export class DynamicTile implements IDynamicTile {
readonly state: IDataCommon;
readonly mover: IObjectMover<IDynamicTile>;
raw: ITileRawData | null;
triggerType: number;
/** 当前的朝向绑定对象 */
@ -26,6 +30,13 @@ export class DynamicTile implements IDynamicTile {
this.state = layer.state;
this.mover = new DynamicTileMover(this);
this.triggerType = -1;
const data = this.state.tileStore.getData(num);
if (!data) {
logger.warn(143, num.toString());
this.raw = null;
} else {
this.raw = data;
}
}
setFaceBinder(binder: IRoleFaceBinder | null): void {

View File

@ -1,6 +1,7 @@
import { isNil } from 'lodash-es';
import {
IDynamicLayer,
ILayerLocation,
ILayerState,
IMapLayer,
IMapLayerData,
@ -55,6 +56,10 @@ export class MapLayer
this.dynamicLayer = new DynamicLayer(this);
}
inMap(x: number, y: number): boolean {
return x >= 0 && y >= 0 && x < this.width && y < this.height;
}
/**
*
*/
@ -150,15 +155,32 @@ export class MapLayer
}
getBlock(x: number, y: number): number {
if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
// 不在地图内,返回 -1
if (!this.inMap(x, y)) {
return -1;
}
return this.mapArray[y * this.width + x];
}
getLocationData(x: number, y: number): ILayerLocation | null {
if (!this.inMap(x, y)) return null;
const index = y * this.width + x;
const num = this.mapArray[index];
const raw = this.state.tileStore.getData(num);
const dynamics = this.dynamicLayer.getDynamicTilesAt(x, y);
const trigger = this.triggerMap.get(index) ?? -1;
const data: ILayerLocation = {
tile: num,
raw,
trigger,
dynamics
};
return data;
}
getTriggerType(x: number, y: number): number {
if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
if (!this.inMap(x, y)) {
return -1;
}
const index = y * this.width + x;
@ -169,7 +191,7 @@ export class MapLayer
}
setTriggerType(type: number, x: number, y: number): void {
if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
if (!this.inMap(x, y)) {
return;
}
const index = y * this.width + x;
@ -181,7 +203,7 @@ export class MapLayer
}
revertTrigger(x: number, y: number): void {
if (x >= 0 && y >= 0 && x < this.width && y < this.height) {
if (this.inMap(x, y)) {
this.triggerMap.delete(y * this.width + x);
}
}

View File

@ -6,7 +6,8 @@ import {
IObjectMovable,
IObjectMover,
IRoleFaceBinder,
ISaveableContent
ISaveableContent,
ITileRawData
} from '@user/data-common';
import { ITileStore } from '@user/data-common';
@ -15,10 +16,21 @@ import { ITileStore } from '@user/data-common';
export interface IMapLayerData {
/** 当前引用是否过期,当地图图层内部的地图数组引用更新时,此项会变为 `true` */
expired: boolean;
/** 地图图块数组,是对内部存储的直接引用 */
/** 地图图块数组,是对内部存储的直接引用,仅建议读取,不建议修改 */
array: Uint32Array;
}
export interface ILayerLocation {
/** 该点的静态图块数字 */
readonly tile: number;
/** 该点的静态图块信息 */
readonly raw: ITileRawData | null;
/** 该点的静态触发器,-1 表示未设置(即使用图块本身的触发器),否则表示该点的静态触发器,覆盖图块本身的触发器 */
readonly trigger: number;
/** 该点包含的所有动态图块 */
readonly dynamics: Iterable<IDynamicTile>;
}
export interface IMapLayerHooks extends IHookBase {
/**
* `resize`
@ -91,6 +103,13 @@ export interface IMapLayer
/** 此图层对应的动态图块图层z 层级与静态图块一致 */
readonly dynamicLayer: IDynamicLayer;
/**
*
* @param x
* @param y
*/
inMap(x: number, y: number): boolean;
/**
*
* @param block
@ -107,6 +126,13 @@ export interface IMapLayer
*/
getBlock(x: number, y: number): number;
/**
*
* @param x
* @param y
*/
getLocationData(x: number, y: number): ILayerLocation | null;
/**
* 退
* @param x
@ -512,21 +538,21 @@ export interface IDynamicLayerHooks extends IHookBase {
* @param tile
* @param layer
*/
onCreateTile(tile: IDynamicTile, layer: IDynamicLayer): void;
onCreateTile?(tile: IDynamicTile, layer: IDynamicLayer): void;
/**
*
* @param tile
* @param layer
*/
onDeleteTile(tile: IDynamicTile, layer: IDynamicLayer): Promise<void>;
onDeleteTile?(tile: IDynamicTile, layer: IDynamicLayer): Promise<void>;
/**
* 使 `mover`
* @param tile
* @param layer
*/
onUpdateTilePosition(tile: IDynamicTile, layer: IDynamicLayer): void;
onUpdateTilePosition?(tile: IDynamicTile, layer: IDynamicLayer): void;
}
export interface IDynamicLayer
@ -613,6 +639,8 @@ export interface IDynamicTile extends IObjectMovable, IDataCommonExtended {
readonly layer: IDynamicLayer;
/** 当前动态图块的移动器 */
readonly mover: IObjectMover<IDynamicTile>;
/** 当前动态图块的图块数据 */
readonly raw: ITileRawData | null;
/**
* {@link num}

View File

@ -1,3 +1,5 @@
//#region tile
export const enum TileType {
/** 未知或尚未归类的图块 */
Unknown,
@ -19,6 +21,26 @@ export const enum TileType {
Tileset
}
export const enum PassBit {
/** 上方向掩码 */
Up = 0b0001,
/** 右方向掩码 */
Right = 0b0010,
/** 下方向掩码 */
Down = 0b0100,
/** 左方向掩码 */
Left = 0b1000
}
export interface ITilePassData {
/** 是否仅当图块处在事件层时生效 */
readonly onlyEvents: boolean;
/** 可以离开的方向 */
readonly outPass: number;
/** 可以进入的方向 */
readonly inPass: number;
}
export interface ITileRawData {
/** 图块数字 */
readonly num: number;
@ -28,6 +50,8 @@ export interface ITileRawData {
readonly trigger: number;
/** 图块逻辑类型 */
readonly type: TileType;
/** 图块的通行性对象 */
readonly pass: ITilePassData;
}
export interface ITileLegacyConverter<TLegacy> {
@ -89,3 +113,5 @@ export interface ITileStore<TLegacy = unknown> {
*/
fromLegacy(num: number, legacy: TLegacy): ITileRawData;
}
//#endregion

View File

@ -17,7 +17,6 @@ import {
} from '@user/data-common';
import {
EnemyManager,
HeroMoveController,
IEnemyManager,
HeroAttribute,
HeroState,
@ -129,9 +128,8 @@ export class CoreState implements ICoreState {
this.maps = new MapStore(tileStore, this);
// 勇士
const heroMover = new HeroMoveController();
const heroAttribute = new HeroAttribute(HERO_DEFAULT_ATTRIBUTE);
const heroState = new HeroState(heroMover, heroAttribute);
const heroState = new HeroState(this, dir8, heroAttribute);
this.hero = heroState;
this.loadProgress = new LoadProgressTotal();

View File

@ -1,21 +1,14 @@
import {
ITileLegacyConverter,
ITilePassData,
ITileRawData,
PassBit,
TileType
} from '@user/data-common';
export type LegacyTileData = MapDataOf<keyof NumberToId>;
export class TileLegacyBridge implements ITileLegacyConverter<LegacyTileData> {
fromLegacy(num: number, legacy: LegacyTileData): ITileRawData {
return {
num,
id: legacy.id,
trigger: -1,
type: this.getTileType(num, legacy)
};
}
private getTileType(num: number, legacy: LegacyTileData): TileType {
if (num === 0) return TileType.None;
switch (legacy.cls) {
@ -40,4 +33,76 @@ export class TileLegacyBridge implements ITileLegacyConverter<LegacyTileData> {
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.noPass) {
return {
onlyEvents: true,
inPass: 0b0000,
outPass: 0b1111
};
} 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
};
}
}
}
fromLegacy(num: number, legacy: LegacyTileData): ITileRawData {
return {
num,
id: legacy.id,
trigger: -1,
type: this.getTileType(num, legacy),
pass: this.getPass(num, legacy)
};
}
}

View File

@ -199,6 +199,7 @@
"139": "Expected $1 binding before start battle flow, but got a null object.",
"140": "Different priority of combat script is expected, but got a same one.",
"141": "Some issue may occur in binded damage system on combat flow, please check the console.",
"142": "Expected a specific tile number after convert id '$1' to number, but got null."
"142": "Expected a specific tile number after convert id '$1' to number, but got null.",
"143": "Cannot create dynamic tile for $1 since its raw data is not found."
}
}

View File

@ -1282,6 +1282,7 @@ interface MapDataOf<T extends keyof NumberToId> {
*/
cls: ClsOf<NumberToId[T]>;
noPass?: boolean;
bigImage?: ImageIds;
faceIds?: Record<Dir, AllIds>;
animate?: number;