mirror of
https://github.com/motajs/template.git
synced 2026-07-17 01:11:09 +08:00
Compare commits
5 Commits
931f610d37
...
eb34d63e96
| Author | SHA1 | Date | |
|---|---|---|---|
| eb34d63e96 | |||
| 3e150c4a1d | |||
| 867a3c480f | |||
| 13e778d072 | |||
| abaa5206c5 |
2
dev.md
2
dev.md
@ -135,7 +135,7 @@
|
|||||||
|
|
||||||
| 包 | 层级 | 说明 |
|
| 包 | 层级 | 说明 |
|
||||||
| ------------------- | ------- | ------------------------------------------------------------------------------- |
|
| ------------------- | ------- | ------------------------------------------------------------------------------- |
|
||||||
| `@user/data-common | Layer 0 | 公共层,定义 `IDataCommon` 及公共无依赖接口 |
|
| `@user/data-common` | Layer 0 | 公共层,定义 `IDataCommon` 及公共无依赖接口 |
|
||||||
| `@user/data-base` | Layer 1 | 数据层,定义 `IStateBase` 及可存档游戏数据(地图、怪物、玩家属性等) |
|
| `@user/data-base` | Layer 1 | 数据层,定义 `IStateBase` 及可存档游戏数据(地图、怪物、玩家属性等) |
|
||||||
| `@user/data-system` | Layer 2 | 执行层,定义 `ICoreState`,依赖数据层实现玩家控制、战斗计算等影响游戏进程的动作 |
|
| `@user/data-system` | Layer 2 | 执行层,定义 `ICoreState`,依赖数据层实现玩家控制、战斗计算等影响游戏进程的动作 |
|
||||||
| `@user/data-state` | Layer 3 | 数据端的顶层模块,一般仅用于初始化以及仅供渲染端调用的顶层模块 |
|
| `@user/data-state` | Layer 3 | 数据端的顶层模块,一般仅用于初始化以及仅供渲染端调用的顶层模块 |
|
||||||
|
|||||||
@ -43,8 +43,11 @@ export abstract class BaseHeroModifier<T, V> implements IHeroModifier<T, V, V> {
|
|||||||
export class HeroAttribute<THero> implements IHeroAttribute<THero> {
|
export class HeroAttribute<THero> implements IHeroAttribute<THero> {
|
||||||
/** 当前勇士属性修饰器 */
|
/** 当前勇士属性修饰器 */
|
||||||
private readonly modifier: Map<keyof THero, IHeroModifier[]> = new Map();
|
private readonly modifier: Map<keyof THero, IHeroModifier[]> = new Map();
|
||||||
/** 当前每个修饰器对应的属性值 */
|
/** 当前每个修饰器对应的属性名称 */
|
||||||
private readonly modifierName: Map<IHeroModifier, keyof THero> = new Map();
|
private readonly modifierName: Map<
|
||||||
|
IHeroModifier<THero[keyof THero]>,
|
||||||
|
keyof THero
|
||||||
|
> = new Map();
|
||||||
/** 当前勇士最终属性 */
|
/** 当前勇士最终属性 */
|
||||||
private readonly finalAttribute: THero;
|
private readonly finalAttribute: THero;
|
||||||
|
|
||||||
@ -144,7 +147,7 @@ export class HeroAttribute<THero> implements IHeroAttribute<THero> {
|
|||||||
|
|
||||||
addModifier<K extends keyof THero>(
|
addModifier<K extends keyof THero>(
|
||||||
name: K,
|
name: K,
|
||||||
modifier: IHeroModifier<THero[K], unknown>
|
modifier: IHeroModifier<THero[K]>
|
||||||
): void {
|
): void {
|
||||||
if (modifier.owner) {
|
if (modifier.owner) {
|
||||||
const modiferName = modifier.constructor.name;
|
const modiferName = modifier.constructor.name;
|
||||||
@ -181,7 +184,7 @@ export class HeroAttribute<THero> implements IHeroAttribute<THero> {
|
|||||||
this.recalculateAttribute(name);
|
this.recalculateAttribute(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
markModifierDirty(modifier: IHeroModifier): void {
|
markModifierDirty(modifier: IHeroModifier<THero[keyof THero]>): void {
|
||||||
const name = this.modifierName.get(modifier);
|
const name = this.modifierName.get(modifier);
|
||||||
if (name === undefined) return;
|
if (name === undefined) return;
|
||||||
this.markDirty(name);
|
this.markDirty(name);
|
||||||
@ -210,9 +213,16 @@ export class HeroAttribute<THero> implements IHeroAttribute<THero> {
|
|||||||
return structuredClone(this.attribute);
|
return structuredClone(this.attribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
*iterateModifiers(): IterableIterator<[keyof THero, IHeroModifier]> {
|
*iterateModifiers(): IterableIterator<[PropertyKey, IHeroModifier]> {
|
||||||
for (const [modifier, name] of this.modifierName) {
|
for (const [modifier, name] of this.modifierName) {
|
||||||
yield [name, modifier];
|
yield [name, modifier];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getModifiers<K extends keyof THero>(
|
||||||
|
name: K
|
||||||
|
): Iterable<IHeroModifier<THero[K]>> {
|
||||||
|
const arr = this.modifier.get(name) as IHeroModifier<THero[K]>[];
|
||||||
|
return arr ?? [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,10 @@
|
|||||||
import { IFacedTileLocator, logger } from '@motajs/common';
|
import {
|
||||||
|
Hookable,
|
||||||
|
HookController,
|
||||||
|
IFacedTileLocator,
|
||||||
|
IHookController,
|
||||||
|
logger
|
||||||
|
} from '@motajs/common';
|
||||||
import {
|
import {
|
||||||
FaceDirection,
|
FaceDirection,
|
||||||
IDataCommon,
|
IDataCommon,
|
||||||
@ -11,6 +17,7 @@ import {
|
|||||||
IHeroFollower,
|
IHeroFollower,
|
||||||
IHeroFollowerSave,
|
IHeroFollowerSave,
|
||||||
IHeroFollowersController,
|
IHeroFollowersController,
|
||||||
|
IHeroFollowersControllerHooks,
|
||||||
IHeroLocation,
|
IHeroLocation,
|
||||||
IHeroRendering
|
IHeroRendering
|
||||||
} from './types';
|
} from './types';
|
||||||
@ -65,6 +72,7 @@ export class HeroFollower implements IHeroFollower {
|
|||||||
|
|
||||||
saveState(compression: SaveCompression): IHeroFollowerSave {
|
saveState(compression: SaveCompression): IHeroFollowerSave {
|
||||||
return {
|
return {
|
||||||
|
num: this.num,
|
||||||
rendering: this.rendering.saveState(compression),
|
rendering: this.rendering.saveState(compression),
|
||||||
location: this.location.saveState(compression)
|
location: this.location.saveState(compression)
|
||||||
};
|
};
|
||||||
@ -75,3 +83,135 @@ export class HeroFollower implements IHeroFollower {
|
|||||||
this.location.loadState(state.location, compression);
|
this.location.loadState(state.location, compression);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class HeroFollowersController
|
||||||
|
extends Hookable<IHeroFollowersControllerHooks>
|
||||||
|
implements IHeroFollowersController
|
||||||
|
{
|
||||||
|
readonly state: IDataCommon;
|
||||||
|
/** 跟随者列表 */
|
||||||
|
private readonly followers: HeroFollower[] = [];
|
||||||
|
/** 勇士位置对象,用于获取聚集目标位置 */
|
||||||
|
private readonly heroLocation: IHeroLocation;
|
||||||
|
/** 朝向处理器,传递给新创建的跟随者 */
|
||||||
|
private readonly faceHandler: IFaceHandler<FaceDirection>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
state: IDataCommon,
|
||||||
|
heroLocation: IHeroLocation,
|
||||||
|
faceHandler: IFaceHandler<FaceDirection>
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.state = state;
|
||||||
|
this.heroLocation = heroLocation;
|
||||||
|
this.faceHandler = faceHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createController(
|
||||||
|
hook: Partial<IHeroFollowersControllerHooks>
|
||||||
|
): IHookController<IHeroFollowersControllerHooks> {
|
||||||
|
return new HookController(this, hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
addFollower(num: number | string): IHeroFollower {
|
||||||
|
const loc: IFacedTileLocator = {
|
||||||
|
x: this.heroLocation.x,
|
||||||
|
y: this.heroLocation.y,
|
||||||
|
direction: this.heroLocation.mover.faceDirection
|
||||||
|
};
|
||||||
|
const follower = new HeroFollower(num, loc, this.faceHandler, this);
|
||||||
|
this.followers.push(follower);
|
||||||
|
const index = this.followers.length - 1;
|
||||||
|
this.forEachHook(hook => {
|
||||||
|
hook.onAddFollower?.(follower, index);
|
||||||
|
});
|
||||||
|
return follower;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFollower(index: number): IHeroFollower | null {
|
||||||
|
return this.followers[index] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
*getFollowersById(
|
||||||
|
num: number | string
|
||||||
|
): IterableIterator<[number, IHeroFollower]> {
|
||||||
|
const store = this.state.tileStore;
|
||||||
|
const target = typeof num === 'string' ? store.idToNumber(num)! : num;
|
||||||
|
for (let i = 0; i < this.followers.length; i++) {
|
||||||
|
if (this.followers[i].num === target) {
|
||||||
|
yield [i, this.followers[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllFollowers(): IHeroFollower[] {
|
||||||
|
return this.followers.slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeFollower(index: number): Promise<void> {
|
||||||
|
const removed = this.followers.splice(index, 1);
|
||||||
|
if (removed.length > 0) {
|
||||||
|
this.forEachHook(hook => {
|
||||||
|
hook.onRemoveFollower?.(removed[0], index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeAllFollowers(): Promise<void> {
|
||||||
|
const snapshot = this.followers.slice();
|
||||||
|
this.followers.length = 0;
|
||||||
|
this.forEachHook(hook => {
|
||||||
|
snapshot.forEach((v, i) => {
|
||||||
|
hook.onRemoveFollower?.(v, i);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async gatherFollowers(): Promise<void> {
|
||||||
|
const { x, y, mover } = this.heroLocation;
|
||||||
|
|
||||||
|
for (let i = 0; i < this.followers.length; i++) {
|
||||||
|
const follower = this.followers[i];
|
||||||
|
follower.location.mover.clear();
|
||||||
|
|
||||||
|
let skip = false;
|
||||||
|
for (let j = i - 1; j >= 0; j--) {
|
||||||
|
const dir = this.followers[j].location.mover.moveDirection;
|
||||||
|
if (dir === FaceDirection.Unknown) {
|
||||||
|
follower.location.mover.clear();
|
||||||
|
follower.location.mover.tp(x, y);
|
||||||
|
follower.location.mover.face(mover.faceDirection);
|
||||||
|
skip = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
follower.location.mover.step(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skip) {
|
||||||
|
follower.location.mover.step(mover.moveDirection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const controllers = this.followers.map(
|
||||||
|
v => v.location.mover.start()?.onEnd
|
||||||
|
);
|
||||||
|
await Promise.all(controllers);
|
||||||
|
this.forEachHook(hook => {
|
||||||
|
hook.onGatherFollowers?.(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
gatherFollowersSync(): void {
|
||||||
|
const { x, y } = this.heroLocation;
|
||||||
|
const dir = this.heroLocation.mover.faceDirection;
|
||||||
|
for (const follower of this.followers) {
|
||||||
|
const mover = follower.location.mover;
|
||||||
|
mover.setPos(x, y);
|
||||||
|
mover.setFaceDir(dir);
|
||||||
|
}
|
||||||
|
this.forEachHook(hook => {
|
||||||
|
hook.onGatherFollowers?.(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,146 +0,0 @@
|
|||||||
import {
|
|
||||||
Hookable,
|
|
||||||
HookController,
|
|
||||||
IFacedTileLocator,
|
|
||||||
IHookController
|
|
||||||
} from '@motajs/common';
|
|
||||||
import { FaceDirection, IDataCommon, IFaceHandler } from '@user/data-common';
|
|
||||||
import { HeroFollower } from './follower';
|
|
||||||
import {
|
|
||||||
IHeroFollowersController,
|
|
||||||
IHeroFollowersControllerHooks,
|
|
||||||
IHeroFollower,
|
|
||||||
IHeroLocation
|
|
||||||
} from './types';
|
|
||||||
|
|
||||||
export class HeroFollowersController
|
|
||||||
extends Hookable<IHeroFollowersControllerHooks>
|
|
||||||
implements IHeroFollowersController
|
|
||||||
{
|
|
||||||
readonly state: IDataCommon;
|
|
||||||
/** 跟随者列表 */
|
|
||||||
private readonly followers: HeroFollower[] = [];
|
|
||||||
/** 勇士位置对象,用于获取聚集目标位置 */
|
|
||||||
private readonly heroLocation: IHeroLocation;
|
|
||||||
/** 朝向处理器,传递给新创建的跟随者 */
|
|
||||||
private readonly faceHandler: IFaceHandler<FaceDirection>;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
state: IDataCommon,
|
|
||||||
heroLocation: IHeroLocation,
|
|
||||||
faceHandler: IFaceHandler<FaceDirection>
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
this.state = state;
|
|
||||||
this.heroLocation = heroLocation;
|
|
||||||
this.faceHandler = faceHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected createController(
|
|
||||||
hook: Partial<IHeroFollowersControllerHooks>
|
|
||||||
): IHookController<IHeroFollowersControllerHooks> {
|
|
||||||
return new HookController(this, hook);
|
|
||||||
}
|
|
||||||
|
|
||||||
addFollower(num: number | string): IHeroFollower {
|
|
||||||
const loc: IFacedTileLocator = {
|
|
||||||
x: this.heroLocation.x,
|
|
||||||
y: this.heroLocation.y,
|
|
||||||
direction: this.heroLocation.mover.faceDirection
|
|
||||||
};
|
|
||||||
const follower = new HeroFollower(num, loc, this.faceHandler, this);
|
|
||||||
this.followers.push(follower);
|
|
||||||
const index = this.followers.length - 1;
|
|
||||||
this.forEachHook(hook => {
|
|
||||||
hook.onAddFollower?.(follower, index);
|
|
||||||
});
|
|
||||||
return follower;
|
|
||||||
}
|
|
||||||
|
|
||||||
getFollower(index: number): IHeroFollower | null {
|
|
||||||
return this.followers[index] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
*getFollowersById(
|
|
||||||
num: number | string
|
|
||||||
): IterableIterator<[number, IHeroFollower]> {
|
|
||||||
const store = this.state.tileStore;
|
|
||||||
const target = typeof num === 'string' ? store.idToNumber(num)! : num;
|
|
||||||
for (let i = 0; i < this.followers.length; i++) {
|
|
||||||
if (this.followers[i].num === target) {
|
|
||||||
yield [i, this.followers[i]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getAllFollowers(): IHeroFollower[] {
|
|
||||||
return this.followers.slice();
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeFollower(index: number): Promise<void> {
|
|
||||||
const removed = this.followers.splice(index, 1);
|
|
||||||
if (removed.length > 0) {
|
|
||||||
this.forEachHook(hook => {
|
|
||||||
hook.onRemoveFollower?.(removed[0], index);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeAllFollowers(): Promise<void> {
|
|
||||||
const snapshot = this.followers.slice();
|
|
||||||
this.followers.length = 0;
|
|
||||||
this.forEachHook(hook => {
|
|
||||||
snapshot.forEach((v, i) => {
|
|
||||||
hook.onRemoveFollower?.(v, i);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async gatherFollowers(): Promise<void> {
|
|
||||||
const { x, y, mover } = this.heroLocation;
|
|
||||||
|
|
||||||
for (let i = 0; i < this.followers.length; i++) {
|
|
||||||
const follower = this.followers[i];
|
|
||||||
follower.location.mover.clear();
|
|
||||||
|
|
||||||
let skip = false;
|
|
||||||
for (let j = i - 1; j >= 0; j--) {
|
|
||||||
const dir = this.followers[j].location.mover.moveDirection;
|
|
||||||
if (dir === FaceDirection.Unknown) {
|
|
||||||
follower.location.mover.clear();
|
|
||||||
follower.location.mover.tp(x, y);
|
|
||||||
follower.location.mover.face(mover.faceDirection);
|
|
||||||
skip = true;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
follower.location.mover.step(dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!skip) {
|
|
||||||
follower.location.mover.step(mover.moveDirection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const controllers = this.followers.map(
|
|
||||||
v => v.location.mover.start()?.onEnd
|
|
||||||
);
|
|
||||||
await Promise.all(controllers);
|
|
||||||
this.forEachHook(hook => {
|
|
||||||
hook.onGatherFollowers?.(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
gatherFollowersSync(): void {
|
|
||||||
const targetX = this.heroLocation.x;
|
|
||||||
const targetY = this.heroLocation.y;
|
|
||||||
const targetDir = this.heroLocation.mover.faceDirection;
|
|
||||||
for (const follower of this.followers) {
|
|
||||||
follower.location.setPos(targetX, targetY);
|
|
||||||
follower.location.mover.face(targetDir).start();
|
|
||||||
}
|
|
||||||
this.forEachHook(hook => {
|
|
||||||
hook.onGatherFollowers?.(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +1,5 @@
|
|||||||
export * from './attribute';
|
export * from './attribute';
|
||||||
export * from './follower';
|
export * from './follower';
|
||||||
export * from './followersController';
|
|
||||||
export * from './location';
|
export * from './location';
|
||||||
export * from './rendering';
|
export * from './rendering';
|
||||||
export * from './mover';
|
export * from './mover';
|
||||||
|
|||||||
@ -43,6 +43,6 @@ export class HeroLocation implements IHeroLocation {
|
|||||||
loadState(state: IHeroLocationSave): void {
|
loadState(state: IHeroLocationSave): void {
|
||||||
this.x = state.x;
|
this.x = state.x;
|
||||||
this.y = state.y;
|
this.y = state.y;
|
||||||
this.mover.face(state.direction).start();
|
this.mover.setFaceDir(state.direction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,17 +57,12 @@ export class HeroMover<T extends IHeroLocation>
|
|||||||
protected async onMoveStart(
|
protected async onMoveStart(
|
||||||
_tile: IHeroLocation,
|
_tile: IHeroLocation,
|
||||||
_controller: Readonly<IMoverController>
|
_controller: Readonly<IMoverController>
|
||||||
): Promise<void> {
|
): Promise<void> {}
|
||||||
// TODO: 通知渲染端开始移动,同步平滑视角
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async onMoveEnd(
|
protected async onMoveEnd(
|
||||||
_tile: IHeroLocation,
|
_tile: IHeroLocation,
|
||||||
_controller: Readonly<IMoverController>
|
_controller: Readonly<IMoverController>
|
||||||
): Promise<void> {
|
): Promise<void> {}
|
||||||
// TODO: 通知渲染端移动结束
|
|
||||||
// TODO: 清除自动寻路状态
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async onStepStart(
|
protected async onStepStart(
|
||||||
step: ObjectMoveStep,
|
step: ObjectMoveStep,
|
||||||
|
|||||||
@ -1,39 +1,66 @@
|
|||||||
import { HeroAttribute } from './attribute';
|
import { HeroAttribute } from './attribute';
|
||||||
|
import { HeroFollowersController } from './follower';
|
||||||
|
import { HeroLocation } from './location';
|
||||||
|
import { HeroRendering } from './rendering';
|
||||||
import {
|
import {
|
||||||
IHeroAttribute,
|
IHeroAttribute,
|
||||||
|
IHeroFollowersController,
|
||||||
|
IHeroLocation,
|
||||||
IHeroModifier,
|
IHeroModifier,
|
||||||
IHeroMoveController,
|
IHeroRendering,
|
||||||
IHeroState,
|
IHeroState,
|
||||||
IHeroStateSave,
|
IHeroStateSave,
|
||||||
IModifierStateSave,
|
IModifierStateSave,
|
||||||
IReadonlyHeroAttribute
|
IReadonlyHeroAttribute
|
||||||
} from './types';
|
} from './types';
|
||||||
import { SaveCompression } from '@user/data-common';
|
import {
|
||||||
|
FaceDirection,
|
||||||
|
IDataCommon,
|
||||||
|
IFaceHandler,
|
||||||
|
SaveCompression
|
||||||
|
} from '@user/data-common';
|
||||||
import { IFacedTileLocator, logger } from '@motajs/common';
|
import { IFacedTileLocator, logger } from '@motajs/common';
|
||||||
|
|
||||||
export class HeroState<THero> implements IHeroState<THero> {
|
export class HeroState<THero> implements IHeroState<THero> {
|
||||||
/** 修饰器工厂函数注册表 */
|
/** 修饰器工厂函数注册表 */
|
||||||
private readonly registry: Map<string, () => IHeroModifier> = new Map();
|
private readonly registry: Map<
|
||||||
|
string,
|
||||||
|
<K extends keyof THero>() => IHeroModifier<THero[K]>
|
||||||
|
> = new Map();
|
||||||
|
|
||||||
|
readonly location: IHeroLocation;
|
||||||
|
readonly rendering: IHeroRendering;
|
||||||
|
readonly followers: IHeroFollowersController;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public mover: IHeroMoveController,
|
state: IDataCommon,
|
||||||
|
faceHandler: IFaceHandler<FaceDirection>,
|
||||||
public attribute: IHeroAttribute<THero>
|
public attribute: IHeroAttribute<THero>
|
||||||
) {}
|
) {
|
||||||
|
this.rendering = new HeroRendering(state);
|
||||||
attachMover(mover: IHeroMoveController): void {
|
const defaultLoc: IFacedTileLocator = {
|
||||||
this.mover = mover;
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
direction: FaceDirection.Down
|
||||||
|
};
|
||||||
|
this.location = new HeroLocation(state, defaultLoc, faceHandler);
|
||||||
|
this.followers = new HeroFollowersController(
|
||||||
|
state,
|
||||||
|
this.location,
|
||||||
|
faceHandler
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
attachAttribute(attribute: IHeroAttribute<THero>): void {
|
attachAttribute(attribute: IHeroAttribute<THero>): void {
|
||||||
this.attribute = attribute;
|
this.attribute = attribute;
|
||||||
}
|
}
|
||||||
|
|
||||||
getHeroMover(): IHeroMoveController {
|
|
||||||
return this.mover;
|
|
||||||
}
|
|
||||||
|
|
||||||
getLocation(): IFacedTileLocator {
|
getLocation(): IFacedTileLocator {
|
||||||
return this.mover;
|
return {
|
||||||
|
x: this.location.x,
|
||||||
|
y: this.location.y,
|
||||||
|
direction: this.location.mover.faceDirection
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getModifiableAttribute(): IHeroAttribute<THero> {
|
getModifiableAttribute(): IHeroAttribute<THero> {
|
||||||
@ -48,7 +75,10 @@ export class HeroState<THero> implements IHeroState<THero> {
|
|||||||
return this.attribute.getModifiableClone();
|
return this.attribute.getModifiableClone();
|
||||||
}
|
}
|
||||||
|
|
||||||
registerModifier(type: string, cons: () => IHeroModifier): void {
|
registerModifier(
|
||||||
|
type: string,
|
||||||
|
cons: <K extends keyof THero>() => IHeroModifier<THero[K]>
|
||||||
|
): void {
|
||||||
this.registry.set(type, cons);
|
this.registry.set(type, cons);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,9 +87,8 @@ export class HeroState<THero> implements IHeroState<THero> {
|
|||||||
if (!cons) {
|
if (!cons) {
|
||||||
logger.warn(116, type);
|
logger.warn(116, type);
|
||||||
return null;
|
return null;
|
||||||
} else {
|
|
||||||
return cons() as IHeroModifier<T, V>;
|
|
||||||
}
|
}
|
||||||
|
return cons() as IHeroModifier<T, V>;
|
||||||
}
|
}
|
||||||
|
|
||||||
createAndInsertModifier<K extends keyof THero, V>(
|
createAndInsertModifier<K extends keyof THero, V>(
|
||||||
@ -73,22 +102,23 @@ export class HeroState<THero> implements IHeroState<THero> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveState(compression: SaveCompression): IHeroStateSave<THero> {
|
saveState(compression: SaveCompression): IHeroStateSave<THero> {
|
||||||
const modifiers: IModifierStateSave[] = [];
|
const modifiers: IModifierStateSave<THero>[] = [];
|
||||||
for (const [name, modifier] of this.attribute.iterateModifiers()) {
|
for (const [name, modifier] of this.attribute.iterateModifiers()) {
|
||||||
modifiers.push({
|
modifiers.push({
|
||||||
name,
|
name: name as keyof THero,
|
||||||
type: modifier.type,
|
type: modifier.type,
|
||||||
state: modifier.saveState(compression)
|
state: modifier.saveState(compression)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const followerSaves = this.followers
|
||||||
|
.getAllFollowers()
|
||||||
|
.map(v => v.saveState(compression));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
attribute: this.attribute.toStructured(),
|
attribute: this.attribute.toStructured(),
|
||||||
locator: {
|
location: this.location.saveState(compression),
|
||||||
x: this.mover.x,
|
rendering: this.rendering.saveState(compression),
|
||||||
y: this.mover.y,
|
followers: followerSaves,
|
||||||
direction: this.mover.direction
|
|
||||||
},
|
|
||||||
followers: structuredClone(this.mover.followers),
|
|
||||||
modifiers
|
modifiers
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -102,19 +132,16 @@ export class HeroState<THero> implements IHeroState<THero> {
|
|||||||
const cons = this.registry.get(save.type);
|
const cons = this.registry.get(save.type);
|
||||||
if (!cons) continue;
|
if (!cons) continue;
|
||||||
const modifier = cons();
|
const modifier = cons();
|
||||||
modifier.loadState(save.state as never, compression);
|
modifier.loadState(save.state, compression);
|
||||||
newAttribute.addModifier(
|
newAttribute.addModifier(save.name, modifier);
|
||||||
save.name as keyof THero,
|
|
||||||
modifier as unknown as IHeroModifier<THero[keyof THero]>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
this.attribute = newAttribute;
|
this.attribute = newAttribute;
|
||||||
this.mover.setPosition(state.locator.x, state.locator.y);
|
this.location.loadState(state.location, compression);
|
||||||
this.mover.turn(state.locator.direction);
|
this.rendering.loadState(state.rendering, compression);
|
||||||
this.mover.removeAllFollowers();
|
void this.followers.removeAllFollowers();
|
||||||
state.followers.forEach(follower => {
|
for (const save of state.followers) {
|
||||||
this.mover.addFollower(follower.num, follower.identifier);
|
const follower = this.followers.addFollower(save.num);
|
||||||
this.mover.setFollowerAlpha(follower.identifier, follower.alpha);
|
follower.loadState(save, compression);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,9 +9,9 @@ import {
|
|||||||
//#region 勇士属性
|
//#region 勇士属性
|
||||||
|
|
||||||
export interface IHeroModifier<
|
export interface IHeroModifier<
|
||||||
K = unknown,
|
H = unknown, // 属性值类型
|
||||||
V = unknown,
|
V = unknown, // 修饰器参数类型
|
||||||
Save = unknown
|
Save = unknown // 存档类型
|
||||||
> extends ISaveableContent<Save> {
|
> extends ISaveableContent<Save> {
|
||||||
/** 修饰器类型 */
|
/** 修饰器类型 */
|
||||||
readonly type: string;
|
readonly type: string;
|
||||||
@ -45,17 +45,17 @@ export interface IHeroModifier<
|
|||||||
* @param baseValue 该属性值的基础属性值
|
* @param baseValue 该属性值的基础属性值
|
||||||
* @param name 属性名称
|
* @param name 属性名称
|
||||||
*/
|
*/
|
||||||
modify(value: K, baseValue: K, name: PropertyKey): K;
|
modify(value: H, baseValue: H, name: PropertyKey): H;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 深拷贝此修饰器
|
* 深拷贝此修饰器
|
||||||
*/
|
*/
|
||||||
clone(): IHeroModifier<K, V>;
|
clone(): IHeroModifier<H, V>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IModifierStateSave {
|
export interface IModifierStateSave<THero> {
|
||||||
/** 属性名称 */
|
/** 属性名称 */
|
||||||
readonly name: PropertyKey;
|
readonly name: keyof THero;
|
||||||
/** 修饰器类型 */
|
/** 修饰器类型 */
|
||||||
readonly type: string;
|
readonly type: string;
|
||||||
/** 修饰器存档数据 */
|
/** 修饰器存档数据 */
|
||||||
@ -107,6 +107,14 @@ export interface IReadonlyHeroAttribute<THero> {
|
|||||||
* 遍历所有已挂载的属性修饰器
|
* 遍历所有已挂载的属性修饰器
|
||||||
*/
|
*/
|
||||||
iterateModifiers(): Iterable<[PropertyKey, IHeroModifier]>;
|
iterateModifiers(): Iterable<[PropertyKey, IHeroModifier]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定属性名称的所有修饰器
|
||||||
|
* @param name 属性名称
|
||||||
|
*/
|
||||||
|
getModifiers<K extends keyof THero>(
|
||||||
|
name: K
|
||||||
|
): Iterable<IHeroModifier<THero[K]>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IHeroAttribute<THero> extends IReadonlyHeroAttribute<THero> {
|
export interface IHeroAttribute<THero> extends IReadonlyHeroAttribute<THero> {
|
||||||
@ -263,6 +271,8 @@ export interface IHeroRendering
|
|||||||
//#region 勇士跟随者
|
//#region 勇士跟随者
|
||||||
|
|
||||||
export interface IHeroFollowerSave {
|
export interface IHeroFollowerSave {
|
||||||
|
/** 跟随者图块数字 */
|
||||||
|
readonly num: number;
|
||||||
/** 跟随者渲染对象保存 */
|
/** 跟随者渲染对象保存 */
|
||||||
readonly rendering: IHeroRenderingSave;
|
readonly rendering: IHeroRenderingSave;
|
||||||
/** 跟随者位置保存 */
|
/** 跟随者位置保存 */
|
||||||
@ -369,11 +379,13 @@ export interface IHeroStateSave<THero> {
|
|||||||
/** 勇士属性状态 */
|
/** 勇士属性状态 */
|
||||||
readonly attribute: THero;
|
readonly attribute: THero;
|
||||||
/** 勇士当前位置 */
|
/** 勇士当前位置 */
|
||||||
readonly locator: IHeroLocationSave;
|
readonly location: IHeroLocationSave;
|
||||||
|
/** 勇士渲染状态 */
|
||||||
|
readonly rendering: IHeroRenderingSave;
|
||||||
/** 勇士当前的跟随者 */
|
/** 勇士当前的跟随者 */
|
||||||
readonly followers: readonly IHeroFollowerSave[];
|
readonly followers: readonly IHeroFollowerSave[];
|
||||||
/** 勇士属性修饰器状态 */
|
/** 勇士属性修饰器状态 */
|
||||||
readonly modifiers: readonly IModifierStateSave[];
|
readonly modifiers: readonly IModifierStateSave<THero>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IHeroState<THero> extends ISaveableContent<
|
export interface IHeroState<THero> extends ISaveableContent<
|
||||||
|
|||||||
@ -4,14 +4,18 @@ import {
|
|||||||
IDataCommon,
|
IDataCommon,
|
||||||
IMoverController,
|
IMoverController,
|
||||||
IObjectMover,
|
IObjectMover,
|
||||||
IRoleFaceBinder
|
IRoleFaceBinder,
|
||||||
|
ITileRawData
|
||||||
} from '@user/data-common';
|
} from '@user/data-common';
|
||||||
import { IDynamicLayer, IDynamicTile } from './types';
|
import { IDynamicLayer, IDynamicTile } from './types';
|
||||||
import { DynamicTileMover } from './mover';
|
import { DynamicTileMover } from './mover';
|
||||||
|
import { logger } from '@motajs/common';
|
||||||
|
|
||||||
export class DynamicTile implements IDynamicTile {
|
export class DynamicTile implements IDynamicTile {
|
||||||
readonly state: IDataCommon;
|
readonly state: IDataCommon;
|
||||||
readonly mover: IObjectMover<IDynamicTile>;
|
readonly mover: IObjectMover<IDynamicTile>;
|
||||||
|
|
||||||
|
raw: ITileRawData | null;
|
||||||
triggerType: number;
|
triggerType: number;
|
||||||
|
|
||||||
/** 当前的朝向绑定对象 */
|
/** 当前的朝向绑定对象 */
|
||||||
@ -26,6 +30,13 @@ export class DynamicTile implements IDynamicTile {
|
|||||||
this.state = layer.state;
|
this.state = layer.state;
|
||||||
this.mover = new DynamicTileMover(this);
|
this.mover = new DynamicTileMover(this);
|
||||||
this.triggerType = -1;
|
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 {
|
setFaceBinder(binder: IRoleFaceBinder | null): void {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { isNil } from 'lodash-es';
|
import { isNil } from 'lodash-es';
|
||||||
import {
|
import {
|
||||||
IDynamicLayer,
|
IDynamicLayer,
|
||||||
|
ILayerLocation,
|
||||||
ILayerState,
|
ILayerState,
|
||||||
IMapLayer,
|
IMapLayer,
|
||||||
IMapLayerData,
|
IMapLayerData,
|
||||||
@ -55,6 +56,10 @@ export class MapLayer
|
|||||||
this.dynamicLayer = new DynamicLayer(this);
|
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 {
|
getBlock(x: number, y: number): number {
|
||||||
if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
|
if (!this.inMap(x, y)) {
|
||||||
// 不在地图内,返回 -1
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return this.mapArray[y * this.width + x];
|
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 {
|
getTriggerType(x: number, y: number): number {
|
||||||
if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
|
if (!this.inMap(x, y)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
const index = y * this.width + x;
|
const index = y * this.width + x;
|
||||||
@ -169,7 +191,7 @@ export class MapLayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
setTriggerType(type: number, x: number, y: number): void {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
const index = y * this.width + x;
|
const index = y * this.width + x;
|
||||||
@ -181,7 +203,7 @@ export class MapLayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
revertTrigger(x: number, y: number): void {
|
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);
|
this.triggerMap.delete(y * this.width + x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,8 @@ import {
|
|||||||
IObjectMovable,
|
IObjectMovable,
|
||||||
IObjectMover,
|
IObjectMover,
|
||||||
IRoleFaceBinder,
|
IRoleFaceBinder,
|
||||||
ISaveableContent
|
ISaveableContent,
|
||||||
|
ITileRawData
|
||||||
} from '@user/data-common';
|
} from '@user/data-common';
|
||||||
import { ITileStore } from '@user/data-common';
|
import { ITileStore } from '@user/data-common';
|
||||||
|
|
||||||
@ -15,10 +16,21 @@ import { ITileStore } from '@user/data-common';
|
|||||||
export interface IMapLayerData {
|
export interface IMapLayerData {
|
||||||
/** 当前引用是否过期,当地图图层内部的地图数组引用更新时,此项会变为 `true` */
|
/** 当前引用是否过期,当地图图层内部的地图数组引用更新时,此项会变为 `true` */
|
||||||
expired: boolean;
|
expired: boolean;
|
||||||
/** 地图图块数组,是对内部存储的直接引用 */
|
/** 地图图块数组,是对内部存储的直接引用,仅建议读取,不建议修改 */
|
||||||
array: Uint32Array;
|
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 {
|
export interface IMapLayerHooks extends IHookBase {
|
||||||
/**
|
/**
|
||||||
* 当地图大小发生变化时执行,如果调用了地图的 `resize` 方法,但是地图大小没变,则不会触发
|
* 当地图大小发生变化时执行,如果调用了地图的 `resize` 方法,但是地图大小没变,则不会触发
|
||||||
@ -91,6 +103,13 @@ export interface IMapLayer
|
|||||||
/** 此图层对应的动态图块图层,z 层级与静态图块一致 */
|
/** 此图层对应的动态图块图层,z 层级与静态图块一致 */
|
||||||
readonly dynamicLayer: IDynamicLayer;
|
readonly dynamicLayer: IDynamicLayer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断指定坐标是否在地图内
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
*/
|
||||||
|
inMap(x: number, y: number): boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置某一点的图块
|
* 设置某一点的图块
|
||||||
* @param block 图块数字
|
* @param block 图块数字
|
||||||
@ -107,6 +126,13 @@ export interface IMapLayer
|
|||||||
*/
|
*/
|
||||||
getBlock(x: number, y: number): number;
|
getBlock(x: number, y: number): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定点的所有图块信息
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
*/
|
||||||
|
getLocationData(x: number, y: number): ILayerLocation | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定点的静态图块对应的有效触发器类型,若手动覆盖不存在则回退到图块默认触发器
|
* 获取指定点的静态图块对应的有效触发器类型,若手动覆盖不存在则回退到图块默认触发器
|
||||||
* @param x 图块横坐标
|
* @param x 图块横坐标
|
||||||
@ -512,21 +538,21 @@ export interface IDynamicLayerHooks extends IHookBase {
|
|||||||
* @param tile 被创建的动态图块
|
* @param tile 被创建的动态图块
|
||||||
* @param layer 所属的动态图层
|
* @param layer 所属的动态图层
|
||||||
*/
|
*/
|
||||||
onCreateTile(tile: IDynamicTile, layer: IDynamicLayer): void;
|
onCreateTile?(tile: IDynamicTile, layer: IDynamicLayer): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 当图块被删除时触发
|
* 当图块被删除时触发
|
||||||
* @param tile 被删除的动态图块
|
* @param tile 被删除的动态图块
|
||||||
* @param layer 所属的动态图层
|
* @param layer 所属的动态图层
|
||||||
*/
|
*/
|
||||||
onDeleteTile(tile: IDynamicTile, layer: IDynamicLayer): Promise<void>;
|
onDeleteTile?(tile: IDynamicTile, layer: IDynamicLayer): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 当更新动态图块的位置时触发(包括使用 `mover` 触发的移动)
|
* 当更新动态图块的位置时触发(包括使用 `mover` 触发的移动)
|
||||||
* @param tile 更新位置的图块
|
* @param tile 更新位置的图块
|
||||||
* @param layer 所属的动态图层
|
* @param layer 所属的动态图层
|
||||||
*/
|
*/
|
||||||
onUpdateTilePosition(tile: IDynamicTile, layer: IDynamicLayer): void;
|
onUpdateTilePosition?(tile: IDynamicTile, layer: IDynamicLayer): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDynamicLayer
|
export interface IDynamicLayer
|
||||||
@ -613,6 +639,8 @@ export interface IDynamicTile extends IObjectMovable, IDataCommonExtended {
|
|||||||
readonly layer: IDynamicLayer;
|
readonly layer: IDynamicLayer;
|
||||||
/** 当前动态图块的移动器 */
|
/** 当前动态图块的移动器 */
|
||||||
readonly mover: IObjectMover<IDynamicTile>;
|
readonly mover: IObjectMover<IDynamicTile>;
|
||||||
|
/** 当前动态图块的图块数据 */
|
||||||
|
readonly raw: ITileRawData | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置图块朝向,会一并修改 {@link num},返回设置后的当前图块数字
|
* 设置图块朝向,会一并修改 {@link num},返回设置后的当前图块数字
|
||||||
|
|||||||
@ -205,6 +205,31 @@ export interface IObjectMoverHooks<T extends IObjectMovable> extends IHookBase {
|
|||||||
tile: T,
|
tile: T,
|
||||||
mover: IObjectMover<T>
|
mover: IObjectMover<T>
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当设置移动对象的位置时触发,仅 `setPos` 可以触发
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
* @param tile 移动器绑定的图块
|
||||||
|
* @param mover 当前移动器
|
||||||
|
*/
|
||||||
|
onSetPos?(x: number, y: number, tile: T, mover: IObjectMover<T>): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当设置移动对象的朝向时触发,仅 `setFaceDir` 可以触发
|
||||||
|
* @param dir 面朝朝向
|
||||||
|
* @param tile 移动器绑定的图块
|
||||||
|
* @param mover 当前移动器
|
||||||
|
*/
|
||||||
|
onSetFaceDir?(dir: FaceDirection, tile: T, mover: IObjectMover<T>): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当设置移动对象的朝向时触发,仅 `setMoveDir` 可以触发
|
||||||
|
* @param dir 移动朝向
|
||||||
|
* @param tile 移动器绑定的图块
|
||||||
|
* @param mover 当前移动器
|
||||||
|
*/
|
||||||
|
onSetMoveDir?(dir: FaceDirection, tile: T, mover: IObjectMover<T>): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IObjectMover<T extends IObjectMovable> extends IHookable<
|
export interface IObjectMover<T extends IObjectMovable> extends IHookable<
|
||||||
@ -223,6 +248,25 @@ export interface IObjectMover<T extends IObjectMovable> extends IHookable<
|
|||||||
/** 当前移动速度,单位毫秒 */
|
/** 当前移动速度,单位毫秒 */
|
||||||
readonly currentSpeed: number;
|
readonly currentSpeed: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 立刻设置移动对象的位置
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
*/
|
||||||
|
setPos(x: number, y: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 立刻设置移动对象的朝向
|
||||||
|
* @param dir 朝向
|
||||||
|
*/
|
||||||
|
setFaceDir(dir: FaceDirection): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 立刻设置移动对象的移动方向
|
||||||
|
* @param dir 朝向
|
||||||
|
*/
|
||||||
|
setMoveDir(dir: FaceDirection): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加一个瞬移步,到这一步时目标将会瞬移至指定位置
|
* 添加一个瞬移步,到这一步时目标将会瞬移至指定位置
|
||||||
* @param x 瞬移目标横坐标
|
* @param x 瞬移目标横坐标
|
||||||
@ -424,7 +468,7 @@ export abstract class ObjectMover<T extends IObjectMovable>
|
|||||||
if (step.direction === ObjectSpecialStep.Backward) {
|
if (step.direction === ObjectSpecialStep.Backward) {
|
||||||
const opposite = this.faceHandler.opposite(dir);
|
const opposite = this.faceHandler.opposite(dir);
|
||||||
this.moveDirection = opposite;
|
this.moveDirection = opposite;
|
||||||
this.faceDirection = opposite;
|
this.faceDirection = dir;
|
||||||
} else {
|
} else {
|
||||||
this.moveDirection = dir;
|
this.moveDirection = dir;
|
||||||
this.faceDirection = dir;
|
this.faceDirection = dir;
|
||||||
@ -440,6 +484,27 @@ export abstract class ObjectMover<T extends IObjectMovable>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setPos(x: number, y: number): void {
|
||||||
|
this.tile.setPos(x, y);
|
||||||
|
this.forEachHook(hook => {
|
||||||
|
hook.onSetPos?.(x, y, this.tile, this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setFaceDir(dir: FaceDirection): void {
|
||||||
|
this.faceDirection = dir;
|
||||||
|
this.forEachHook(hook => {
|
||||||
|
hook.onSetFaceDir?.(dir, this.tile, this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setMoveDir(dir: FaceDirection): void {
|
||||||
|
this.moveDirection = dir;
|
||||||
|
this.forEachHook(hook => {
|
||||||
|
hook.onSetMoveDir?.(dir, this.tile, this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
tp(x: number, y: number, rel: boolean = false): this {
|
tp(x: number, y: number, rel: boolean = false): this {
|
||||||
this.pushStep({
|
this.pushStep({
|
||||||
type: ObjectMoveStepType.Teleport,
|
type: ObjectMoveStepType.Teleport,
|
||||||
@ -561,7 +626,9 @@ export abstract class ObjectMover<T extends IObjectMovable>
|
|||||||
);
|
);
|
||||||
await Promise.all(stepStartHooks);
|
await Promise.all(stepStartHooks);
|
||||||
const loc = await this.onStepEnd(code, step, this.tile, controller);
|
const loc = await this.onStepEnd(code, step, this.tile, controller);
|
||||||
|
if (this.tile.x !== loc.x && this.tile.y !== loc.y) {
|
||||||
this.tile.setPos(loc.x, loc.y);
|
this.tile.setPos(loc.x, loc.y);
|
||||||
|
}
|
||||||
const stepEndHooks = this.forEachHook(hook =>
|
const stepEndHooks = this.forEachHook(hook =>
|
||||||
hook.onStepEnd?.(code, step, this.tile, this)
|
hook.onStepEnd?.(code, step, this.tile, this)
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
//#region tile
|
||||||
|
|
||||||
export const enum TileType {
|
export const enum TileType {
|
||||||
/** 未知或尚未归类的图块 */
|
/** 未知或尚未归类的图块 */
|
||||||
Unknown,
|
Unknown,
|
||||||
@ -19,6 +21,26 @@ export const enum TileType {
|
|||||||
Tileset
|
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 {
|
export interface ITileRawData {
|
||||||
/** 图块数字 */
|
/** 图块数字 */
|
||||||
readonly num: number;
|
readonly num: number;
|
||||||
@ -28,6 +50,8 @@ export interface ITileRawData {
|
|||||||
readonly trigger: number;
|
readonly trigger: number;
|
||||||
/** 图块逻辑类型 */
|
/** 图块逻辑类型 */
|
||||||
readonly type: TileType;
|
readonly type: TileType;
|
||||||
|
/** 图块的通行性对象 */
|
||||||
|
readonly pass: ITilePassData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITileLegacyConverter<TLegacy> {
|
export interface ITileLegacyConverter<TLegacy> {
|
||||||
@ -89,3 +113,5 @@ export interface ITileStore<TLegacy = unknown> {
|
|||||||
*/
|
*/
|
||||||
fromLegacy(num: number, legacy: TLegacy): ITileRawData;
|
fromLegacy(num: number, legacy: TLegacy): ITileRawData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import {
|
|||||||
} from '@user/data-common';
|
} from '@user/data-common';
|
||||||
import {
|
import {
|
||||||
EnemyManager,
|
EnemyManager,
|
||||||
HeroMoveController,
|
|
||||||
IEnemyManager,
|
IEnemyManager,
|
||||||
HeroAttribute,
|
HeroAttribute,
|
||||||
HeroState,
|
HeroState,
|
||||||
@ -129,9 +128,8 @@ export class CoreState implements ICoreState {
|
|||||||
this.maps = new MapStore(tileStore, this);
|
this.maps = new MapStore(tileStore, this);
|
||||||
|
|
||||||
// 勇士
|
// 勇士
|
||||||
const heroMover = new HeroMoveController();
|
|
||||||
const heroAttribute = new HeroAttribute(HERO_DEFAULT_ATTRIBUTE);
|
const heroAttribute = new HeroAttribute(HERO_DEFAULT_ATTRIBUTE);
|
||||||
const heroState = new HeroState(heroMover, heroAttribute);
|
const heroState = new HeroState(this, dir8, heroAttribute);
|
||||||
this.hero = heroState;
|
this.hero = heroState;
|
||||||
|
|
||||||
this.loadProgress = new LoadProgressTotal();
|
this.loadProgress = new LoadProgressTotal();
|
||||||
|
|||||||
@ -1,21 +1,14 @@
|
|||||||
import {
|
import {
|
||||||
ITileLegacyConverter,
|
ITileLegacyConverter,
|
||||||
|
ITilePassData,
|
||||||
ITileRawData,
|
ITileRawData,
|
||||||
|
PassBit,
|
||||||
TileType
|
TileType
|
||||||
} from '@user/data-common';
|
} from '@user/data-common';
|
||||||
|
|
||||||
export type LegacyTileData = MapDataOf<keyof NumberToId>;
|
export type LegacyTileData = MapDataOf<keyof NumberToId>;
|
||||||
|
|
||||||
export class TileLegacyBridge implements ITileLegacyConverter<LegacyTileData> {
|
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 {
|
private getTileType(num: number, legacy: LegacyTileData): TileType {
|
||||||
if (num === 0) return TileType.None;
|
if (num === 0) return TileType.None;
|
||||||
switch (legacy.cls) {
|
switch (legacy.cls) {
|
||||||
@ -40,4 +33,76 @@ export class TileLegacyBridge implements ITileLegacyConverter<LegacyTileData> {
|
|||||||
return TileType.Unknown;
|
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)
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -700,7 +700,7 @@ export interface IEnemyContext<TEnemy, THero> extends IReadonlyEnemyContext<
|
|||||||
* 绑定伤害计算系统
|
* 绑定伤害计算系统
|
||||||
* @param system 伤害系统
|
* @param system 伤害系统
|
||||||
*/
|
*/
|
||||||
attachDamageSystem(system: IDamageSystem<TEnemy, unknown> | null): void;
|
attachDamageSystem(system: IDamageSystem<TEnemy, THero> | null): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重建当前上下文中的全部怪物计算结果
|
* 重建当前上下文中的全部怪物计算结果
|
||||||
|
|||||||
@ -199,6 +199,7 @@
|
|||||||
"139": "Expected $1 binding before start battle flow, but got a null object.",
|
"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.",
|
"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.",
|
"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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
46
prompt.md
46
prompt.md
@ -7,30 +7,29 @@
|
|||||||
3. **以目的驱动,而非以接口驱动**:实现前先想清楚我为什么要这样设计接口、这个接口设计的目的是什么,而不是单纯地以将接口填满为目标。
|
3. **以目的驱动,而非以接口驱动**:实现前先想清楚我为什么要这样设计接口、这个接口设计的目的是什么,而不是单纯地以将接口填满为目标。
|
||||||
4. **遇到歧义立即提问**:若思考或实现时遇到任何问题——例如描述模糊、接口不清晰、某些地方存在歧义等——应立即向我提问,而不是按自己的想法去写。
|
4. **遇到歧义立即提问**:若思考或实现时遇到任何问题——例如描述模糊、接口不清晰、某些地方存在歧义等——应立即向我提问,而不是按自己的想法去写。
|
||||||
5. **接口缺失时停止并提问**:若完成需求所需的接口尚不存在,应立即停止实现,向我提出疑问,而不是擅自新增接口来推进。
|
5. **接口缺失时停止并提问**:若完成需求所需的接口尚不存在,应立即停止实现,向我提出疑问,而不是擅自新增接口来推进。
|
||||||
6. **按我说的方式重构**:若目标是重构某个接口,按照我指定的方式执行:
|
6. **按我说的方式重构**:重构时**不要**擅自新增任何参数、方法或接口,**不要**仅通过新增兼容层应对重构。
|
||||||
- **彻底性重构**(新旧接口完全没有重合):按正常方式全新实现,旧代码仅作逻辑与思路上的参考。
|
- **彻底性重构**(新旧接口完全没有重合):按正常方式全新实现,旧代码仅作逻辑参考。
|
||||||
- **结构性重构**(新旧接口基本一致,细节有差距):将旧代码搬移到新接口上后进行微调。**不要**擅自新增任何参数、方法或接口,**不要**仅通过新增兼容层的方式应对重构。
|
- **结构性重构**(新旧接口基本一致,细节有差距):将旧代码搬移到新接口上后进行微调。
|
||||||
7. **不要有任何“顺手”的想法**:任何时候,都不要出现顺手的想法,包括但不限于发现了一个 bug,然后**顺手**修复、发现一处类型错误,然后**顺手**修复等,这种情况下应当遵循规则 1。
|
7. **不要有任何"顺手"的想法**:任何时候,都不要出现顺手的想法,包括但不限于发现了一个 bug 然后**顺手**修复、发现一处类型错误然后**顺手**修复等,这种情况下应当遵循规则 1。
|
||||||
8. **写完代码后说明修改内容**:写完代码后,必须在对话中说明自己修改了哪些内容,包括所有的文件及修改了每个文件的哪些东西。
|
8. **修改文件前重读文件,修改后说明内容**:修改文件前务必重新阅读以确保内容是最新版。修改后必须在对话中说明所有改动的文件及具体改动内容。在修改文件时,**不得修改**文档的 `涉及文件` 节未提及的文件。
|
||||||
9. **关于 `dev.md` 中的一些补充**:为了提高代码质量和可读性,所有包含“一般不建议”或“尽量避免”等字样的,在此处要求为绝对不能使用,如果必须使用,需在使用前向我确认,`as` 关键字除外。所有包含“建议怎么做”或“最好怎么做”的内容,在此处要求为必须使用。
|
9. **关于 `dev.md` 的强度升级**:`dev.md` 中"一般不建议/尽量避免"的条目在此处视为**绝对禁止**;"建议/最好"的条目在此处视为**必须遵守**。`as` 关键字不受此升级约束。
|
||||||
10. **关于文件修改**:任何时候要进行文件修改时,务必重新阅读文件以确保上下文中的文件是最新版,因为我可能会在两次对话间修改文件。
|
10. **参数最小化 / 级联获取**:不从外部传入可通过已有引用获取的对象。若对象已持有对父对象或相关对象的引用(如 `controller`),应直接通过该引用获取所需数据(如 `controller.state`),不应再单独传递参数。即"非必要不传参"。
|
||||||
11. **参数最小化 / 级联获取**:不从外部传入可通过已有引用获取的对象。若对象已持有对父对象或相关对象的引用(如 `controller`),应直接通过该引用获取所需数据(如 `controller.state`),不应再单独传递参数。即“非必要不传参”。
|
11. **转换下沉**:数据的格式或类型转换(如 `string` 的 id 转 `number`)应放在最终消费该值的对象中完成,而非在调用方预先转换后再传入。这样可以减少调用方的职责并避免重复转换。
|
||||||
12. **转换下沉**:数据的格式或类型转换(如 `string` 的 id 转 `number`)应放在最终消费该值的对象中完成,而非在调用方预先转换后再传入。这样可以减少调用方的职责并避免重复转换。
|
12. **通过公有接口操作,不直接赋值内部属性**:对某个对象内部状态的修改,必须通过该对象对外暴露的公有方法进行(如用 `mover.face(dir).start()` 设置朝向并发起移动),而不是通过类型断言访问其内部属性直接赋值(如 `mover.faceDirection = dir`)。
|
||||||
13. **通过公有接口操作,不直接赋值内部属性**:对某个对象内部状态的修改,必须通过该对象对外暴露的公有方法进行(如用 `mover.face(dir).start()` 设置朝向并发起移动),而不是通过类型断言访问其内部属性直接赋值(如 `mover.faceDirection = dir`)。
|
13. **map/filter 优于 for 循环**:对数组进行数据收集或转换时(如收集 Promise),使用 `map`、`filter` 等函数式写法,比 `for` 循环 + `push` 的可读性更高。
|
||||||
14. **map/filter 优于 for 循环**:对数组进行数据收集或转换时(如收集 Promise),使用 `map`、`filter` 等函数式写法,比 `for` 循环 + `push` 的可读性更高。
|
14. **渲染端永远处于被动**:任何情况下都不可能会出现数据端主动通知渲染端进行更新的场景,渲染端仅通过钩子与数据端通信,通过钩子被动获取信息,并在某些情况下影响数据端行为。
|
||||||
|
15. **按设计实现,勿自由发挥**:我已想好功能的基本思路和代码结构,你的任务是还原它,而非自行创造。
|
||||||
|
|
||||||
# 建议规则
|
# 建议规则
|
||||||
|
|
||||||
以下规则为建议性,尽量遵守,特殊情况下可灵活处理。
|
以下规则为建议性,尽量遵守,特殊情况下可灵活处理。
|
||||||
|
|
||||||
1. **合理参考建议**:我有时会在对话中给出实现建议,应合理参考,切忌滥用。
|
1. **参考但不盲从建议**:我给出的实现建议可作为方向参考,但不应不加思考地照搬。
|
||||||
2. **以类型标注为参考依据**:实现与类型标注有冲突时,以类型标注(一般是 `types.ts`)中的内容为准。
|
2. **以类型标注为参考依据**:实现与类型标注有冲突时,以类型标注(一般是 `types.ts`)中的内容为准。
|
||||||
3. **发现接口问题时提问**:若认为类型标注中的接口设计有问题,或在实现中发现缺少某些接口,应向我提问是否添加,经我同意后方可添加。
|
3. **发现接口问题时提问**:若认为类型标注中的接口设计有问题,或在实现中发现缺少某些接口,应向我提问是否添加,经我同意后方可添加。
|
||||||
4. **接口设计兼顾合理性与便捷性**:设计接口时不仅要考虑合理性,还要考虑使用便捷性。罕见场景应当被支持,但不应与常见场景共用同一接口——这只会增加常见场景的使用难度。
|
4. **接口设计兼顾合理性与便捷性**:设计接口时不仅要考虑合理性,还要考虑使用便捷性。罕见场景应当被支持,但不应与常见场景共用同一接口——这只会增加常见场景的使用难度。
|
||||||
5. **避免多余的非空判断与类型守卫**:若某个类型已满足目标的类型约束,不应再对其添加任何判断或过滤操作。典型例子:`Promise.all` 对数组元素类型没有任何限制,传入 `(Promise<unknown> | void)[]` 完全合法,无需多此一举地写成 `Promise.all(arr.filter(v => !!v))`。
|
5. **避免多余的非空判断与类型守卫**:类型已满足约束时不应再额外判断或过滤。例如 `Promise.all` 接受 `(Promise<unknown> | void)[]`,无需写成 `Promise.all(arr.filter(v => !!v))`。
|
||||||
6. **关于整个仓库的类型检查**:一般情况下**不需要**跑全仓库的类型检查,每个需求都是独立的,不会影响外部内容。只有在进行重构的时候才有此需求。
|
6. **关于整个仓库的类型检查**:单个需求**不需要**跑全仓库的类型检查,仅在重构时需要。
|
||||||
|
|
||||||
**时刻谨记上述要求,避免一个需求反复修改仍无法满足预期。**
|
|
||||||
|
|
||||||
# 开发流程
|
# 开发流程
|
||||||
|
|
||||||
@ -41,7 +40,12 @@
|
|||||||
|
|
||||||
## 示例文档
|
## 示例文档
|
||||||
|
|
||||||
对于新增接口/彻底性地重构接口,按照以下格式编写,其余需求按照自己的理解去写即可。如某部分需要详细描述,可单独开设标题;若某个接口内容较多,也可以在文档中为其单独开一个章节进行讲述。我会使用引用块的形式在文档中提出建议或回答。Markdown 文档不需要刻意换行,我的编辑器有自动换行功能,正常写没有问题。文档保证简洁,不要过于啰嗦,但必须包含所有的必要信息,控制在 100-250 行,格式清晰,不要擅自修改示例文档的格式。一般情况下不需要画任何流程图,如果必须要画,使用 `mermaid` 图。不要在文档中使用引用块,因为这是我在给文档添加批注时使用的东西。
|
对于新增接口/彻底性地重构接口,按照以下格式编写,其余需求自行组织。
|
||||||
|
|
||||||
|
- 如有需要可单独开设标题章节详述。
|
||||||
|
- 我会使用 `>` 引用块在文档中批注,因此**不要在文档中使用引用块**。
|
||||||
|
- 文档控制在 100-250 行,简洁但包含所有必要信息,不擅自修改示例文档格式。
|
||||||
|
- 一般不需要流程图;若必须画,使用 `mermaid`。
|
||||||
|
|
||||||
```md
|
```md
|
||||||
# 需求综述
|
# 需求综述
|
||||||
@ -50,11 +54,11 @@
|
|||||||
|
|
||||||
# 接口设计分析
|
# 接口设计分析
|
||||||
|
|
||||||
这是相当重要的一章。需要按接口逐一列出其方法与成员,分析每个成员和方法的预期使用频率(高 / 中 / 低)并说明判断依据;对于中频和高频成员,还需列出至少一个典型使用场景。必须认真分析频率,想明白**用户**调用的频率,而不是**引擎**调用的频率,比如战斗函数在引擎中可能是中频,但是对用户来说,用户一般会使用系统默认行为来战斗,而**不会**自己调用,因此属于低频。
|
这是相当重要的一章。按接口逐一分析成员与方法的预期使用频率(高/中/低,指**用户编写此调用的频率**而非运行时频率),中高频成员需列出至少一个典型使用场景。
|
||||||
|
|
||||||
**命名长度原则**:频率越高,成员名应越短。高频成员以一个单词为宜,最多不超过两个单词;中频成员不超过三个单词;低频成员可以稍长,但不宜过长。
|
**命名长度原则**:高频 -> 一个单词;中频 -> 不超过三个单词;低频 -> 可稍长但不宜过长。
|
||||||
|
|
||||||
**频率的定义**:此处频率指**用户编写此调用的可能性或频率**,而非运行时的调用频率。例如某成员每秒被调用千次,但整个游戏中只在一处出现过,仍属低频。
|
我可能不会给完整接口设计,你需要自行分析需求补充设计,并在对话中明确指出哪些接口是你补充的。
|
||||||
|
|
||||||
示例如下:
|
示例如下:
|
||||||
|
|
||||||
@ -81,7 +85,7 @@
|
|||||||
|
|
||||||
### 可能风险
|
### 可能风险
|
||||||
|
|
||||||
在任何时候,需要修改已有接口时,必须在文档中写明修改这一接口的风险。这里的已有接口指的是本次设计新增接口之外的接口,即所有已存在于代码中的接口。本章节**只写**关于修改已有接口可能导致的风险,例如导致大面积的类型污染、或部分其他接口会出现类型错误等,**不写任何关于实现的风险**,关于实现的风险应该写到最后的问题节,而不是这里。一些细小的内容不要写到这。如果没有任何风险,不要写无风险,直接跳过此章节。
|
若需修改已有接口(即本次新增之外的接口),在此写明修改风险(如类型污染、其他接口出错等)。只写修改已有接口的风险,不写实现风险(实现风险放到问题节)。无风险则跳过此章节。
|
||||||
|
|
||||||
# 实现思路
|
# 实现思路
|
||||||
|
|
||||||
|
|||||||
1
src/types/declaration/core.d.ts
vendored
1
src/types/declaration/core.d.ts
vendored
@ -1282,6 +1282,7 @@ interface MapDataOf<T extends keyof NumberToId> {
|
|||||||
*/
|
*/
|
||||||
cls: ClsOf<NumberToId[T]>;
|
cls: ClsOf<NumberToId[T]>;
|
||||||
|
|
||||||
|
noPass?: boolean;
|
||||||
bigImage?: ImageIds;
|
bigImage?: ImageIds;
|
||||||
faceIds?: Record<Dir, AllIds>;
|
faceIds?: Record<Dir, AllIds>;
|
||||||
animate?: number;
|
animate?: number;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user