Compare commits

...

2 Commits

Author SHA1 Message Date
c22a51829d refactor: IHeroState 2026-04-16 17:15:04 +08:00
89936416e1 chore: 添加必要注释 2026-04-16 13:30:52 +08:00
20 changed files with 821 additions and 428 deletions

View File

@ -5,10 +5,9 @@ import {
HeroAnimateDirection,
IHeroState,
IHeroStateHooks,
IMapLayer,
nextFaceDirection,
state
} from '@user/data-state';
nextFaceDirection
} from '@user/data-base';
import { IMapLayer, state } from '@user/data-state';
import { IMapRenderer, IMapRendererTicker, IMovingBlock } from '../types';
import { isNil } from 'lodash-es';
import { IHookController, logger } from '@motajs/common';

View File

@ -1,4 +1,5 @@
import { IHeroState, IMapLayer } from '@user/data-state';
import { IHeroState } from '@user/data-base';
import { IMapLayer } from '@user/data-state';
import {
IMapDoorRenderer,
IMapExtensionManager,

View File

@ -2,9 +2,9 @@ import { ITexture, Font } from '@motajs/render';
import {
FaceDirection,
HeroAnimateDirection,
IHeroState,
IMapLayer
} from '@user/data-state';
IHeroState
} from '@user/data-base';
import { IMapLayer } from '@user/data-state';
import { IMapRenderResult } from '../types';

View File

@ -67,16 +67,19 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
this.width = width;
this.height = height;
this.indexer.setWidth(width);
this.needUpdate = true;
}
registerAuraConverter(converter: IAuraConverter<TAttr>): void {
this.auraConverter.add(converter);
this.converterStatus.set(converter, true);
this.needUpdate = true;
}
unregisterAuraConverter(converter: IAuraConverter<TAttr>): void {
this.auraConverter.delete(converter);
this.converterStatus.delete(converter);
this.needUpdate = true;
}
setAuraConverterEnabled(
@ -85,6 +88,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
): void {
if (!this.auraConverter.has(converter)) return;
this.converterStatus.set(converter, enabled);
this.needUpdate = true;
}
registerCommonQueryEffect(
@ -94,6 +98,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
const array = this.commonQueryMap.getOrInsert(code, []);
array.push(effect);
array.sort((a, b) => b.priority - a.priority);
this.needUpdate = true;
}
unregisterCommonQueryEffect(
@ -105,11 +110,13 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
const index = array.indexOf(effect);
if (index === -1) return;
array.splice(index, 1);
this.needUpdate = true;
}
registerSpecialQueryEffect(effect: IEnemySpecialQueryEffect<TAttr>): void {
const list = this.specialQueryEffects.getOrInsert(effect.priority, []);
list.push(effect);
this.needUpdate = true;
}
unregisterSpecialQueryEffect(
@ -124,11 +131,13 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
if (list.length === 0) {
this.specialQueryEffects.delete(effect.priority);
}
this.needUpdate = true;
}
registerFinalEffect(effect: IEnemyFinalEffect<TAttr>): void {
this.finalEffects.push(effect);
this.finalEffects.sort((a, b) => b.priority - a.priority);
this.needUpdate = true;
}
unregisterFinalEffect(effect: IEnemyFinalEffect<TAttr>): void {
@ -136,6 +145,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
if (index !== -1) {
this.finalEffects.splice(index, 1);
}
this.needUpdate = true;
}
getEnemyLocator(enemy: IEnemy<TAttr>): Readonly<ITileLocator> | null {
@ -166,6 +176,10 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
return this.computedToView.get(enemy) ?? null;
}
/**
*
* @param index
*/
private deleteEnemyAt(index: number) {
const view = this.enemyViewMap.get(index);
const enemy = this.enemyMap.get(index);
@ -201,6 +215,13 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
this.locatorViewMap.set(view, index);
this.computedToView.set(view.getComputingEnemy(), view);
if (this.mapDamage) {
this.mapDamage.markEnemyDirty(view);
}
if (this.damageSystem) {
this.damageSystem.markDirty(view);
}
this.needUpdate = true;
}
@ -209,6 +230,11 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
this.deleteEnemyAt(index);
}
/**
*
* @param range
* @param param
*/
private *internalScanRange<T>(
range: IRange<T>,
param: T
@ -257,15 +283,23 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
return this.mapDamage;
}
attachDamageSystem(system: IDamageSystem<TAttr, unknown>): void {
attachDamageSystem(system: IDamageSystem<TAttr, unknown> | null): void {
this.damageSystem = system;
system.markAllDirty();
if (system) {
system.markAllDirty();
}
}
getDamageSystem<THero>(): IDamageSystem<TAttr, THero> | null {
return this.damageSystem as IDamageSystem<TAttr, THero> | null;
}
/**
*
* @param special
* @param enemy
* @param locator
*/
private convertSpecial(
special: ISpecial<any>,
enemy: IReadonlyEnemy<TAttr>,
@ -288,6 +322,10 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
return matched.convert(special, enemy, locator, this);
}
/**
*
* @param aura
*/
private insertIntoSortedAura(aura: IAuraView<TAttr>): void {
const set = this.sortedAura.getOrInsertComputed(
aura.priority,
@ -296,6 +334,10 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
set.add(aura);
}
/**
*
* @param aura
*/
private removeFromSortedAura(aura: IAuraView<TAttr>): void {
const set = this.sortedAura.get(aura.priority);
if (set) {
@ -306,6 +348,13 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
}
}
/**
*
* @param modifier
* @param enemy
* @param locator
* @param currentPriority
*/
private processSpecialModifier(
modifier: IEnemySpecialModifier<TAttr>,
enemy: IEnemy<TAttr>,
@ -325,6 +374,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
for (const adding of toAdd) {
const aura = this.convertSpecial(adding, enemy, locator);
if (aura) {
// 新生成的光环只能影响之后的阶段,不能反过来影响当前优先级链。
if (import.meta.env.DEV && aura.priority > currentPriority) {
logger.warn(
99,
@ -344,6 +394,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
enemy.deleteSpecial(deleting);
const aura = this.convertedAura.get(deleting);
if (aura) {
// 当前阶段不允许删除同优先级或更高优先级的已生效光环。
if (import.meta.env.DEV && aura.priority >= currentPriority) {
logger.warn(
98,
@ -377,6 +428,11 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
return affectedAuras;
}
/**
*
* @param effect
* @param currentPriority
*/
private processSpecialQuery(
effect: IEnemySpecialQueryEffect<TAttr>,
currentPriority: number
@ -404,6 +460,11 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
}
}
/**
*
* @param aura
* @param currentPriority
*/
private processAuraSpecial(
aura: IAuraView<TAttr>,
currentPriority: number
@ -431,6 +492,9 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
}
}
/**
*
*/
private buildupSpecials(): void {
for (const aura of this.globalAuraList) {
this.insertIntoSortedAura(aura);
@ -450,6 +514,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
const processedPriorities = new Set<number>();
// 由于期间可能会产生新优先级的光环,所以要用 while (true) 而不是直接遍历
while (true) {
let maxPriority: number | null = null;
for (const priority of this.sortedAura.keys()) {
@ -488,6 +553,9 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
}
}
/**
*
*/
private buildupBase(): void {
const priorities = [...this.sortedAura.keys()].sort((a, b) => b - a);
for (const p of priorities) {
@ -505,6 +573,9 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
}
}
/**
*
*/
private buildupQuery(): void {
for (const [index, view] of this.enemyViewMap) {
const enemy = view.getComputingEnemy();
@ -527,6 +598,9 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
}
}
/**
*
*/
private buildupFinal(): void {
for (const [index, view] of this.enemyViewMap) {
const enemy = view.getComputingEnemy();
@ -575,6 +649,12 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
}
}
/**
*
* @param modifier
* @param enemy
* @param locator
*/
private refreshSpecialModifier(
modifier: IEnemySpecialModifier<TAttr>,
enemy: IEnemy<TAttr>,
@ -619,6 +699,10 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
}
}
/**
*
* @param view
*/
private refreshEnemy(view: EnemyView<TAttr>): void {
const locator = this.getEnemyLocatorByView(view);
if (!locator) return;
@ -646,6 +730,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
if (!aura.couldApplySpecial) continue;
const param = aura.getRangeParam();
aura.range.bindHost(this);
// 局部刷新只重新判断“这个怪物是否被该光环命中”。
const inRange = aura.range.inRange(
locator.x,
locator.y,
@ -756,7 +841,7 @@ export class EnemyContext<TAttr> implements IEnemyContext<TAttr> {
destroy(): void {
this.clear();
this.attachMapDamage(null);
this.damageSystem = null;
this.attachDamageSystem(null);
this.auraConverter.clear();
this.commonQueryMap.clear();
this.specialQueryEffects.clear();

View File

@ -847,7 +847,7 @@ export interface IEnemyContext<TAttr> {
*
* @param system
*/
attachDamageSystem(system: IDamageSystem<TAttr, unknown>): void;
attachDamageSystem(system: IDamageSystem<TAttr, unknown> | null): void;
/**
*

View File

@ -0,0 +1,152 @@
import { logger } from '@motajs/common';
import { IHeroAttribute, IHeroModifier } from './types';
export abstract class BaseHeroModifier<T, V> implements IHeroModifier<T, V> {
abstract readonly priority: number;
owner: IHeroAttribute<unknown> | null = null;
constructor(private currentValue: V) {}
get value(): V {
return this.currentValue;
}
setValue(value: V): void {
this.currentValue = value;
this.owner?.markModifierDirty(this);
}
getValue(): V {
return this.currentValue;
}
bindAttribute(attribute: IHeroAttribute<unknown> | null): void {
this.owner = attribute;
}
abstract modify(value: T, baseValue: T, name: string): T;
abstract clone(): IHeroModifier<T, V>;
}
export class HeroAttribute<THero> implements IHeroAttribute<THero> {
/** 当前勇士属性修饰器 */
private readonly modifier: Map<keyof THero, IHeroModifier[]> = new Map();
/** 当前每个修饰器对应的属性值 */
private readonly modifierName: Map<IHeroModifier, keyof THero> = new Map();
/** 当前勇士最终属性 */
private readonly finalAttribute: THero;
/**
* @param attribute
*/
constructor(private readonly attribute: THero) {
this.finalAttribute = structuredClone(attribute);
}
/**
*
* @param curr
* @param next
*/
private isSameReference(curr: unknown, next: unknown) {
return typeof curr === 'object' && curr !== null && curr === next;
}
/**
*
* @param name
*/
private recalculateAttribute<K extends keyof THero>(name: K): void {
const modifierList = this.modifier.get(name);
if (!modifierList) return;
const baseValue = this.attribute[name];
let value = baseValue;
for (const modifier of modifierList as IHeroModifier<THero[K]>[]) {
const nextValue = modifier.modify(value, baseValue, name);
// 部署之后就没必要弹这个警告了,额外判断反而可能会有一定的性能损失,直接 tree-shaking 优化掉
if (import.meta.env.DEV && this.isSameReference(value, nextValue)) {
const modiferName = modifier.constructor.name;
logger.warn(109, modiferName, String(name));
}
value = nextValue;
}
this.finalAttribute[name] = value;
}
getBaseAttribute<K extends keyof THero>(name: K): THero[K] {
return this.attribute[name];
}
getFinalAttribute<K extends keyof THero>(name: K): THero[K] {
return this.finalAttribute[name];
}
setBaseAttribute<K extends keyof THero>(name: K, value: THero[K]): void {
this.attribute[name] = value;
this.markDirty(name);
}
addModifier<K extends keyof THero>(
name: K,
modifier: IHeroModifier<THero[K], unknown>
): void {
if (modifier.owner) {
const modiferName = modifier.constructor.name;
logger.warn(108, modiferName, String(name));
return;
}
const modifierList = this.modifier.getOrInsert(name, []);
modifierList.push(modifier);
modifierList.sort((left, right) => right.priority - left.priority);
this.modifierName.set(modifier, name);
modifier.bindAttribute(this);
this.markDirty(name);
}
deleteModifier<K extends keyof THero>(
name: K,
modifier: IHeroModifier<THero[K], unknown>
): void {
const modifierList = this.modifier.get(name);
if (!modifierList) return;
const index = modifierList.indexOf(modifier);
if (index === -1) return;
modifier.bindAttribute(null);
modifierList.splice(index, 1);
this.modifierName.delete(modifier);
this.markDirty(name);
}
markDirty(name: keyof THero): void {
this.recalculateAttribute(name);
}
markModifierDirty(modifier: IHeroModifier): void {
const name = this.modifierName.get(modifier);
if (name === undefined) return;
this.markDirty(name);
}
clone(cloneModifier: boolean = true): IHeroAttribute<THero> {
const cloned = new HeroAttribute<THero>(
structuredClone(this.attribute)
);
if (!cloneModifier) return cloned;
// 拷贝修饰器
for (const [modifier, name] of this.modifierName) {
cloned.addModifier(
name,
modifier.clone() as IHeroModifier<THero[keyof THero]>
);
}
return cloned;
}
}

View File

@ -0,0 +1,4 @@
export * from './attribute';
export * from './state';
export * from './types';
export * from './utils';

View File

@ -1,8 +1,14 @@
import { Hookable, HookController, IHookController } from '@motajs/common';
import { IHeroFollower, IHeroState, IHeroStateHooks } from './types';
import { FaceDirection, getFaceMovement, nextFaceDirection } from '../common';
import { isNil } from 'lodash-es';
import { DEFAULT_HERO_IMAGE } from '../shared';
import { getFaceMovement, nextFaceDirection } from './utils';
import {
FaceDirection,
IHeroFollower,
IHeroState,
IHeroStateHooks
} from './types';
const DEFAULT_HERO_IMAGE: ImageIds = 'hero.png';
export class HeroState extends Hookable<IHeroStateHooks> implements IHeroState {
x: number = 0;

View File

@ -0,0 +1,330 @@
import { IHookBase, IHookable } from '@motajs/common';
//#region 勇士属性
export interface IHeroModifier<T = unknown, V = unknown> {
/** 修饰器优先级 */
readonly priority: number;
/** 修饰器参数值 */
readonly value: V;
/** 当前修饰器所属的勇士属性对象 */
readonly owner: IHeroAttribute<unknown> | null;
/**
*
* @param value
*/
setValue(value: V): void;
/**
*
*/
getValue(): V;
/**
*
* @param attribute
*/
bindAttribute(attribute: IHeroAttribute<unknown> | null): void;
/**
*
* @param value
* @param baseValue
* @param name
*/
modify(value: T, baseValue: T, name: PropertyKey): T;
/**
*
*/
clone(): IHeroModifier<T, V>;
}
export interface IHeroAttribute<THero> {
/**
* Buff
* @param name
*/
getBaseAttribute<K extends keyof THero>(name: K): THero[K];
/**
* Buff
* @param name
*/
getFinalAttribute<K extends keyof THero>(name: K): THero[K];
/**
*
* @param name
* @param value
*/
setBaseAttribute<K extends keyof THero>(name: K, value: THero[K]): void;
/**
*
* @param name
* @param modifier
*/
addModifier<K extends keyof THero>(
name: K,
modifier: IHeroModifier<THero[K], unknown>
): void;
/**
*
* @param name
* @param modifier
*/
deleteModifier<K extends keyof THero>(
name: K,
modifier: IHeroModifier<THero[K], unknown>
): void;
/**
*
* @param name
*/
markDirty(name: keyof THero): void;
/**
*
* @param modifier
*/
markModifierDirty(modifier: IHeroModifier): void;
/**
*
* @param cloneModifier
*/
clone(cloneModifier?: boolean): IHeroAttribute<THero>;
}
//#endregion
//#region 朝向
export const enum FaceDirection {
Unknown,
Left,
Up,
Right,
Down,
LeftUp,
RightUp,
LeftDown,
RightDown
}
export interface IFaceData {
/** 图块数字 */
readonly identifier: number;
/** 图块朝向 */
readonly face: FaceDirection;
}
//#endregion
//#region 勇士状态
export const enum HeroAnimateDirection {
/** 正向播放动画 */
Forward,
/** 反向播放动画 */
Backward
}
export interface IHeroFollower {
/** 跟随者的图块数字 */
readonly num: number;
/** 跟随者的标识符 */
readonly identifier: string;
/** 跟随者的不透明度 */
alpha: number;
}
export interface IHeroStateHooks extends IHookBase {
/**
*
* @param x
* @param y
*/
onSetPosition(x: number, y: number): void;
/**
*
* @param direction
*/
onTurnHero(direction: FaceDirection): void;
/**
*
*/
onStartMove(): void;
/**
*
* @param direction
* @param time
*/
onMoveHero(direction: FaceDirection, time: number): Promise<void>;
/**
*
*/
onEndMove(): Promise<void>;
/**
*
* @param x
* @param y
* @param time
* @param waitFollower
*/
onJumpHero(
x: number,
y: number,
time: number,
waitFollower: boolean
): Promise<void>;
/**
*
* @param image id
*/
onSetImage(image: ImageIds): void;
/**
*
* @param alpha
*/
onSetAlpha(alpha: number): void;
/**
*
* @param follower
* @param identifier
*/
onAddFollower(follower: number, identifier: string): void;
/**
*
* @param identifier
* @param animate `true` 使
*/
onRemoveFollower(identifier: string, animate: boolean): void;
/**
*
*/
onRemoveAllFollowers(): void;
/**
*
* @param identifier
* @param alpha
*/
onSetFollowerAlpha(identifier: string, alpha: number): void;
}
export interface IHeroState extends IHookable<IHeroStateHooks> {
/** 勇士横坐标 */
readonly x: number;
/** 勇士纵坐标 */
readonly y: number;
/** 勇士朝向 */
readonly direction: FaceDirection;
/** 勇士图片 */
readonly image?: ImageIds;
/** 跟随者列表 */
readonly followers: readonly IHeroFollower[];
/** 勇士当前的不透明度 */
readonly alpha: number;
/**
*
* @param x
* @param y
*/
setPosition(x: number, y: number): void;
/**
*
* @param direction
*/
turn(direction?: FaceDirection): void;
/**
*
*/
startMove(): void;
/**
*
* @param dir
* @param time 100ms
* @returns `Promise`
*/
move(dir: FaceDirection, time?: number): Promise<void>;
/**
*
* @returns `Promise`
*/
endMove(): Promise<void>;
/**
*
* @param x
* @param y
* @param time 500ms
* @param waitFollower
* @returns `Promise`
*/
jumpHero(
x: number,
y: number,
time?: number,
waitFollower?: boolean
): Promise<void>;
/**
*
* @param image id
*/
setImage(image: ImageIds): void;
/**
*
* @param alpha
*/
setAlpha(alpha: number): void;
/**
*
* @param follower
* @param identifier
*/
addFollower(follower: number, identifier: string): void;
/**
*
* @param identifier
* @param animate `true` 使
*/
removeFollower(identifier: string, animate?: boolean): void;
/**
*
*/
removeAllFollowers(): void;
/**
*
* @param identifier
* @param alpha
*/
setFollowerAlpha(identifier: string, alpha: number): void;
}
//#endregion

View File

@ -0,0 +1,182 @@
import { FaceDirection } from './types';
/**
*
* @param dir
*/
export function getFaceMovement(dir: FaceDirection): Loc {
switch (dir) {
case FaceDirection.Left:
return { x: -1, y: 0 };
case FaceDirection.Right:
return { x: 1, y: 0 };
case FaceDirection.Up:
return { x: 0, y: -1 };
case FaceDirection.Down:
return { x: 0, y: 1 };
case FaceDirection.LeftUp:
return { x: -1, y: -1 };
case FaceDirection.RightUp:
return { x: 1, y: -1 };
case FaceDirection.LeftDown:
return { x: -1, y: 1 };
case FaceDirection.RightDown:
return { x: 1, y: 1 };
case FaceDirection.Unknown:
return { x: 0, y: 0 };
}
}
/**
*
* @param dir
* @param unknown `FaceDirection.Unknown`
*/
export function degradeFace(
dir: FaceDirection,
unknown: FaceDirection = FaceDirection.Unknown
): FaceDirection {
switch (dir) {
case FaceDirection.LeftUp:
return FaceDirection.Left;
case FaceDirection.LeftDown:
return FaceDirection.Left;
case FaceDirection.RightUp:
return FaceDirection.Right;
case FaceDirection.RightDown:
return FaceDirection.Right;
case FaceDirection.Unknown:
return unknown;
}
return dir;
}
/**
*
* @param dir
* @param anticlockwise
* @param face8 使 `false` ->->->->->->
* `true` ->->->->->->->
*/
export function nextFaceDirection(
dir: FaceDirection,
anticlockwise: boolean = false,
face8: boolean = false
): FaceDirection {
if (face8) {
if (anticlockwise) {
switch (dir) {
case FaceDirection.Left:
return FaceDirection.LeftDown;
case FaceDirection.LeftDown:
return FaceDirection.Down;
case FaceDirection.Down:
return FaceDirection.RightDown;
case FaceDirection.RightDown:
return FaceDirection.Right;
case FaceDirection.Right:
return FaceDirection.RightUp;
case FaceDirection.RightUp:
return FaceDirection.Up;
case FaceDirection.Up:
return FaceDirection.LeftUp;
case FaceDirection.LeftUp:
return FaceDirection.Left;
case FaceDirection.Unknown:
return FaceDirection.Unknown;
}
} else {
switch (dir) {
case FaceDirection.Left:
return FaceDirection.LeftUp;
case FaceDirection.LeftUp:
return FaceDirection.Up;
case FaceDirection.Up:
return FaceDirection.RightUp;
case FaceDirection.RightUp:
return FaceDirection.Right;
case FaceDirection.Right:
return FaceDirection.RightDown;
case FaceDirection.RightDown:
return FaceDirection.Down;
case FaceDirection.Down:
return FaceDirection.LeftDown;
case FaceDirection.LeftDown:
return FaceDirection.Left;
case FaceDirection.Unknown:
return FaceDirection.Unknown;
}
}
} else {
if (anticlockwise) {
switch (dir) {
case FaceDirection.Left:
return FaceDirection.Down;
case FaceDirection.Down:
return FaceDirection.Right;
case FaceDirection.Right:
return FaceDirection.Up;
case FaceDirection.Up:
return FaceDirection.Left;
case FaceDirection.LeftUp:
return FaceDirection.LeftDown;
case FaceDirection.LeftDown:
return FaceDirection.RightDown;
case FaceDirection.RightDown:
return FaceDirection.RightUp;
case FaceDirection.RightUp:
return FaceDirection.LeftUp;
case FaceDirection.Unknown:
return FaceDirection.Unknown;
}
} else {
switch (dir) {
case FaceDirection.Left:
return FaceDirection.Up;
case FaceDirection.Up:
return FaceDirection.Right;
case FaceDirection.Right:
return FaceDirection.Down;
case FaceDirection.Down:
return FaceDirection.Left;
case FaceDirection.LeftUp:
return FaceDirection.RightUp;
case FaceDirection.RightUp:
return FaceDirection.RightDown;
case FaceDirection.RightDown:
return FaceDirection.LeftDown;
case FaceDirection.LeftDown:
return FaceDirection.LeftUp;
case FaceDirection.Unknown:
return FaceDirection.Unknown;
}
}
}
}
/**
*
* @param dir
*/
export function fromDirectionString(dir: Dir2): FaceDirection {
switch (dir) {
case 'left':
return FaceDirection.Left;
case 'right':
return FaceDirection.Right;
case 'up':
return FaceDirection.Up;
case 'down':
return FaceDirection.Down;
case 'leftup':
return FaceDirection.LeftUp;
case 'rightup':
return FaceDirection.RightUp;
case 'leftdown':
return FaceDirection.LeftDown;
case 'rightdown':
return FaceDirection.RightDown;
default:
return FaceDirection.Unknown;
}
}

View File

@ -1,4 +1,5 @@
export * from './enemy';
export * from './hero';
export * from './load';
export * from './game';

View File

@ -1,21 +1,7 @@
export const enum FaceDirection {
Unknown,
Left,
Up,
Right,
Down,
LeftUp,
RightUp,
LeftDown,
RightDown
}
import { FaceDirection, type IFaceData } from '@user/data-base';
export interface IFaceData {
/** 图块数字 */
readonly identifier: number;
/** 图块朝向 */
readonly face: FaceDirection;
}
export { FaceDirection };
export type { IFaceData } from '@user/data-base';
export interface IRoleFaceBinder {
/**

View File

@ -1,182 +1,6 @@
import { FaceDirection } from './types';
/**
*
* @param dir
*/
export function getFaceMovement(dir: FaceDirection): Loc {
switch (dir) {
case FaceDirection.Left:
return { x: -1, y: 0 };
case FaceDirection.Right:
return { x: 1, y: 0 };
case FaceDirection.Up:
return { x: 0, y: -1 };
case FaceDirection.Down:
return { x: 0, y: 1 };
case FaceDirection.LeftUp:
return { x: -1, y: -1 };
case FaceDirection.RightUp:
return { x: 1, y: -1 };
case FaceDirection.LeftDown:
return { x: -1, y: 1 };
case FaceDirection.RightDown:
return { x: 1, y: 1 };
case FaceDirection.Unknown:
return { x: 0, y: 0 };
}
}
/**
*
* @param dir
* @param unknown `FaceDirection.Unknown`
*/
export function degradeFace(
dir: FaceDirection,
unknown: FaceDirection = FaceDirection.Unknown
): FaceDirection {
switch (dir) {
case FaceDirection.LeftUp:
return FaceDirection.Left;
case FaceDirection.LeftDown:
return FaceDirection.Left;
case FaceDirection.RightUp:
return FaceDirection.Right;
case FaceDirection.RightDown:
return FaceDirection.Right;
case FaceDirection.Unknown:
return unknown;
}
return dir;
}
/**
*
* @param dir
* @param anticlockwise
* @param face8 使 `false` ->->->->->->
* `true` ->->->->->->->
*/
export function nextFaceDirection(
dir: FaceDirection,
anticlockwise: boolean = false,
face8: boolean = false
): FaceDirection {
if (face8) {
if (anticlockwise) {
switch (dir) {
case FaceDirection.Left:
return FaceDirection.LeftDown;
case FaceDirection.LeftDown:
return FaceDirection.Down;
case FaceDirection.Down:
return FaceDirection.RightDown;
case FaceDirection.RightDown:
return FaceDirection.Right;
case FaceDirection.Right:
return FaceDirection.RightUp;
case FaceDirection.RightUp:
return FaceDirection.Up;
case FaceDirection.Up:
return FaceDirection.LeftUp;
case FaceDirection.LeftUp:
return FaceDirection.Left;
case FaceDirection.Unknown:
return FaceDirection.Unknown;
}
} else {
switch (dir) {
case FaceDirection.Left:
return FaceDirection.LeftUp;
case FaceDirection.LeftUp:
return FaceDirection.Up;
case FaceDirection.Up:
return FaceDirection.RightUp;
case FaceDirection.RightUp:
return FaceDirection.Right;
case FaceDirection.Right:
return FaceDirection.RightDown;
case FaceDirection.RightDown:
return FaceDirection.Down;
case FaceDirection.Down:
return FaceDirection.LeftDown;
case FaceDirection.LeftDown:
return FaceDirection.Left;
case FaceDirection.Unknown:
return FaceDirection.Unknown;
}
}
} else {
if (anticlockwise) {
switch (dir) {
case FaceDirection.Left:
return FaceDirection.Down;
case FaceDirection.Down:
return FaceDirection.Right;
case FaceDirection.Right:
return FaceDirection.Up;
case FaceDirection.Up:
return FaceDirection.Left;
case FaceDirection.LeftUp:
return FaceDirection.LeftDown;
case FaceDirection.LeftDown:
return FaceDirection.RightDown;
case FaceDirection.RightDown:
return FaceDirection.RightUp;
case FaceDirection.RightUp:
return FaceDirection.LeftUp;
case FaceDirection.Unknown:
return FaceDirection.Unknown;
}
} else {
switch (dir) {
case FaceDirection.Left:
return FaceDirection.Up;
case FaceDirection.Up:
return FaceDirection.Right;
case FaceDirection.Right:
return FaceDirection.Down;
case FaceDirection.Down:
return FaceDirection.Left;
case FaceDirection.LeftUp:
return FaceDirection.RightUp;
case FaceDirection.RightUp:
return FaceDirection.RightDown;
case FaceDirection.RightDown:
return FaceDirection.LeftDown;
case FaceDirection.LeftDown:
return FaceDirection.LeftUp;
case FaceDirection.Unknown:
return FaceDirection.Unknown;
}
}
}
}
/**
*
* @param dir
*/
export function fromDirectionString(dir: Dir2): FaceDirection {
switch (dir) {
case 'left':
return FaceDirection.Left;
case 'right':
return FaceDirection.Right;
case 'up':
return FaceDirection.Up;
case 'down':
return FaceDirection.Down;
case 'leftup':
return FaceDirection.LeftUp;
case 'rightup':
return FaceDirection.RightUp;
case 'leftdown':
return FaceDirection.LeftDown;
case 'rightdown':
return FaceDirection.RightDown;
default:
return FaceDirection.Unknown;
}
}
export {
degradeFace,
fromDirectionString,
getFaceMovement,
nextFaceDirection
} from '@user/data-base';

View File

@ -1,11 +1,12 @@
import { ICoreState, IStateSaveData } from './types';
import { IHeroState, HeroState } from './hero';
import { ILayerState, LayerState } from './map';
import { IRoleFaceBinder, RoleFaceBinder } from './common';
import {
DamageSystem,
EnemyContext,
EnemyManager,
HeroState,
IHeroState,
IEnemyContext,
IEnemyManager,
MapDamage
@ -76,7 +77,7 @@ export class CoreState implements ICoreState {
loadState(data: IStateSaveData): void {
this.hero.removeAllFollowers();
data?.followers.forEach(v => {
data.followers.forEach(v => {
this.hero.addFollower(v.num, v.identifier);
});
}

View File

@ -11,16 +11,16 @@ export class MainEnemyFinalEffect implements IEnemyFinalEffect<IEnemyAttributes>
readonly priority: number = 0;
apply(enemy: IEnemy<IEnemyAttributes>, _locator: ITileLocator): void {
// 3-坚固
if (enemy.hasSpecial(3)) {
enemy.setAttribute(
'def',
Math.max(
enemy.getAttribute('def'),
HERO_STATUS_PLACEHOLDER.atk - 1
)
const target = Math.max(
enemy.getAttribute('def'),
HERO_STATUS_PLACEHOLDER.atk - 1
);
enemy.setAttribute('def', target);
}
// 10-模仿
if (enemy.hasSpecial(10)) {
enemy.setAttribute('atk', HERO_STATUS_PLACEHOLDER.atk);
enemy.setAttribute('def', HERO_STATUS_PLACEHOLDER.def);

View File

@ -1,2 +1 @@
export * from './state';
export * from './types';

View File

@ -1,204 +1,22 @@
import { IHookBase, IHookable } from '@motajs/common';
import { FaceDirection } from '../common/types';
//#region 勇士属性
export const enum HeroAnimateDirection {
/** 正向播放动画 */
Forward,
/** 反向播放动画 */
Backward
export interface IHeroAttributeObject {
/** 勇士名称 */
name: string;
/** 勇士生命值 */
hp: number;
/** 勇士生命值上限 */
hpmax: number;
/** 勇士攻击力 */
atk: number;
/** 勇士防御力 */
def: number;
/** 勇士护盾 */
mdef: number;
/** 勇士魔法值 */
mana: number;
/** 勇士魔法上限 */
manamax: number;
}
export interface IHeroFollower {
/** 跟随者的图块数字 */
readonly num: number;
/** 跟随者的标识符 */
readonly identifier: string;
/** 跟随者的不透明度 */
alpha: number;
}
export interface IHeroStateHooks extends IHookBase {
/**
*
* @param controller
* @param x
* @param y
*/
onSetPosition(x: number, y: number): void;
/**
*
* @param direction
*/
onTurnHero(direction: FaceDirection): void;
/**
*
*/
onStartMove(): void;
/**
*
* @param controller
* @param direction
* @param time
*/
onMoveHero(direction: FaceDirection, time: number): Promise<void>;
/**
*
*/
onEndMove(): Promise<void>;
/**
*
* @param x
* @param y
* @param time
* @param waitFollower
*/
onJumpHero(
x: number,
y: number,
time: number,
waitFollower: boolean
): Promise<void>;
/**
*
* @param image id
*/
onSetImage(image: ImageIds): void;
/**
*
* @param alpha
*/
onSetAlpha(alpha: number): void;
/**
*
* @param follower
* @param identifier
*/
onAddFollower(follower: number, identifier: string): void;
/**
*
* @param identifier
* @param animate `true` 使
*/
onRemoveFollower(identifier: string, animate: boolean): void;
/**
*
*/
onRemoveAllFollowers(): void;
/**
*
* @param identifier
* @param alpha
*/
onSetFollowerAlpha(identifier: string, alpha: number): void;
}
export interface IHeroState extends IHookable<IHeroStateHooks> {
/** 勇士横坐标 */
readonly x: number;
/** 勇士纵坐标 */
readonly y: number;
/** 勇士朝向 */
readonly direction: FaceDirection;
/** 勇士图片 */
readonly image?: ImageIds;
/** 跟随者列表 */
readonly followers: readonly IHeroFollower[];
/** 勇士当前的不透明度 */
readonly alpha: number;
/**
*
* @param x
* @param y
*/
setPosition(x: number, y: number): void;
/**
*
* @param direction
*/
turn(direction?: FaceDirection): void;
/**
*
*/
startMove(): void;
/**
*
* @param dir
* @param time 100ms
* @returns `Promise`
*/
move(dir: FaceDirection, time?: number): Promise<void>;
/**
*
* @returns `Promise`
*/
endMove(): Promise<void>;
/**
*
* @param x
* @param y
* @param time 500ms
* @param waitFollower
* @returns `Promise`
*/
jumpHero(
x: number,
y: number,
time?: number,
waitFollower?: boolean
): Promise<void>;
/**
*
* @param image id
*/
setImage(image: ImageIds): void;
/**
*
* @param alpha
*/
setAlpha(alpha: number): void;
/**
*
* @param follower
* @param identifier
*/
addFollower(follower: number, identifier: string): void;
/**
*
* @param identifier
* @param animate `true` 使
*/
removeFollower(identifier: string, animate?: boolean): void;
/**
*
*/
removeAllFollowers(): void;
/**
*
* @param identifier
* @param alpha
*/
setFollowerAlpha(identifier: string, alpha: number): void;
}
//#endregion

View File

@ -1,7 +1,11 @@
import { ILayerState } from './map';
import { IHeroFollower, IHeroState } from './hero';
import { IRoleFaceBinder } from './common';
import { IEnemyContext, IEnemyManager } from '@user/data-base';
import {
IEnemyContext,
IEnemyManager,
IHeroFollower,
IHeroState
} from '@user/data-base';
import { IEnemyAttributes } from './enemy/types';
export interface IGameDataState {

View File

@ -4,8 +4,7 @@ import { IHookable, IHookBase, IHookController, IHookObject } from './types';
export abstract class Hookable<
H extends IHookBase = IHookBase,
C extends IHookController<H> = IHookController<H>
> implements IHookable<H, C>
{
> implements IHookable<H, C> {
/** 加载完成的钩子列表 */
protected readonly loadedList: Set<IHookObject<H, C>> = new Set();

View File

@ -163,6 +163,8 @@
"105": "No specific map damage view stored, which seems like an internal bug of map damage system.",
"106": "Damage calculator is missing, damage calculation is unavailable.",
"107": "Hero status is not bound, damage calculation is unavailable.",
"108": "Hero modifier '$1' has already been added once. Ignore repeated add for attribute '$2'.",
"109": "Expected a different object reference returned, but got a same reference at modifier '$1' for property '$2'.",
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency."
}
}