mirror of
https://github.com/motajs/template.git
synced 2026-10-16 10:30:18 +08:00
refactor: hero
This commit is contained in:
parent
bbdea04393
commit
122567b050
@ -199,10 +199,9 @@ export class HeroAttribute<THero> implements IHeroAttribute<THero> {
|
||||
name: K,
|
||||
index: number
|
||||
): IHeroModifier<THero[K]> | null {
|
||||
const arr = this.modifier.get(name);
|
||||
const arr = this.modifier.get(name) as IHeroModifier<THero[K]>[];
|
||||
if (!arr) return null;
|
||||
const modifier = arr[index] as IHeroModifier<THero[K]> | undefined;
|
||||
// 越界与负索引一律不删除任何修饰器,避免旧 splice 语义下误删末尾元素
|
||||
const modifier = arr[index];
|
||||
if (!modifier) return null;
|
||||
// 删除簿记统一由 deleteModifier 承担,故需先取出修饰器再委托以保留返回值
|
||||
this.deleteModifier(name, modifier);
|
||||
|
||||
@ -23,6 +23,8 @@ import {
|
||||
} from './types';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
// TODO: 挪到渲染端
|
||||
|
||||
export class HeroFollower implements IHeroFollower {
|
||||
readonly num: number;
|
||||
readonly state: IDataCommon;
|
||||
|
||||
@ -13,13 +13,11 @@ import {
|
||||
IHeroItemsSave,
|
||||
IHeroItemState
|
||||
} from './types';
|
||||
import { logger } from '@motajs/common';
|
||||
|
||||
export class HeroItems<THero> implements IHeroItems<THero> {
|
||||
/** 永久道具 */
|
||||
private readonly constants: Map<number, IHeroItemState<THero>> = new Map();
|
||||
/** 消耗道具 */
|
||||
private readonly consumables: Map<number, IHeroItemState<THero>> =
|
||||
new Map();
|
||||
/** 当前勇士拥有的所有道具 */
|
||||
private readonly items: Map<number, IHeroItemState<THero>> = new Map();
|
||||
|
||||
readonly equipment: HeroEquipsStore<THero>;
|
||||
|
||||
@ -27,30 +25,6 @@ export class HeroItems<THero> implements IHeroItems<THero> {
|
||||
this.equipment = new HeroEquipsStore<THero>(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 item 参数解析为 num,字符串 id 通过 tileStore 转换为 num
|
||||
* @param item 道具图块数字或 id
|
||||
*/
|
||||
private resolveNum(item: number | string): number | undefined {
|
||||
if (typeof item === 'number') return item;
|
||||
return this.state.tileStore.idToNumber(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据道具分类获取对应的分表
|
||||
* @param category 道具分类枚举
|
||||
*/
|
||||
private getMap(category: ItemCategory): Map<number, IHeroItemState<THero>> {
|
||||
switch (category) {
|
||||
case ItemCategory.Constant:
|
||||
return this.constants;
|
||||
case ItemCategory.Consumable:
|
||||
return this.consumables;
|
||||
default:
|
||||
return this.constants;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定道具的内部状态,返回可修改引用供内部逻辑使用
|
||||
* @param item 道具图块数字或 id
|
||||
@ -58,12 +32,9 @@ export class HeroItems<THero> implements IHeroItems<THero> {
|
||||
private internalGetItemState(
|
||||
item: number | string
|
||||
): IHeroItemState<THero> | null {
|
||||
const num = this.resolveNum(item);
|
||||
const num = this.state.tileStore.num(item);
|
||||
if (isNil(num)) return null;
|
||||
|
||||
const category = this.state.itemStore.getCategory(num);
|
||||
const map = this.getMap(category);
|
||||
return map.get(num) ?? null;
|
||||
return this.items.get(num) ?? null;
|
||||
}
|
||||
|
||||
getItemState(
|
||||
@ -78,8 +49,8 @@ export class HeroItems<THero> implements IHeroItems<THero> {
|
||||
|
||||
@shouldReplay('Adding item to hero should be replayed.')
|
||||
addItem(item: number | string, count: number = 1): void {
|
||||
const num = this.resolveNum(item);
|
||||
if (isNil(num)) return;
|
||||
const [num, id] = this.state.tileStore.identity(item);
|
||||
if (isNil(num) || isNil(id)) return;
|
||||
|
||||
const raw = this.state.itemStore.getData(num);
|
||||
if (!raw) return;
|
||||
@ -98,23 +69,18 @@ export class HeroItems<THero> implements IHeroItems<THero> {
|
||||
return;
|
||||
}
|
||||
|
||||
const map = this.getMap(raw.category);
|
||||
const existing = map.get(num);
|
||||
const existing = this.items.get(num);
|
||||
if (existing) {
|
||||
existing.count += count;
|
||||
if (existing.count <= 0) {
|
||||
map.delete(num);
|
||||
this.items.delete(num);
|
||||
}
|
||||
} else if (count > 0) {
|
||||
const store = this.state.tileStore;
|
||||
map.set(num, { id: store.id(raw.num)!, num: raw.num, raw, count });
|
||||
const consumable = raw.category === ItemCategory.Consumable;
|
||||
this.items.set(num, { id, num, raw, count, consumable });
|
||||
}
|
||||
}
|
||||
|
||||
getItem(item: number | string): void {
|
||||
this.addItem(item, 1);
|
||||
}
|
||||
|
||||
@shouldReplay('Using item should be replayed.')
|
||||
useItem(item: number | string): boolean {
|
||||
const state = this.internalGetItemState(item);
|
||||
@ -136,10 +102,10 @@ export class HeroItems<THero> implements IHeroItems<THero> {
|
||||
|
||||
raw.effect.useEffect?.(raw, this.state);
|
||||
|
||||
if (raw.category === ItemCategory.Consumable) {
|
||||
if (state.consumable) {
|
||||
state.count--;
|
||||
if (state.count <= 0) {
|
||||
this.consumables.delete(raw.num);
|
||||
this.items.delete(raw.num);
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,19 +139,24 @@ export class HeroItems<THero> implements IHeroItems<THero> {
|
||||
const raw = this.state.itemStore.getData(save.num);
|
||||
if (!raw) continue;
|
||||
const store = this.state.tileStore;
|
||||
const id = store.id(raw.num);
|
||||
if (isNil(id)) {
|
||||
logger.warn(193, raw.num.toString());
|
||||
continue;
|
||||
}
|
||||
map.set(save.num, {
|
||||
id: store.id(raw.num)!,
|
||||
id,
|
||||
num: raw.num,
|
||||
raw,
|
||||
count: save.count
|
||||
count: save.count,
|
||||
consumable: raw.category === ItemCategory.Consumable
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
saveState(compression: SaveCompression): IHeroItemsSave<THero> {
|
||||
return {
|
||||
constants: this.mapToSave(this.constants),
|
||||
consumables: this.mapToSave(this.consumables),
|
||||
items: this.mapToSave(this.items),
|
||||
equipStore: this.equipment.saveState(compression)
|
||||
};
|
||||
}
|
||||
@ -194,10 +165,8 @@ export class HeroItems<THero> implements IHeroItems<THero> {
|
||||
state: IHeroItemsSave<THero>,
|
||||
compression: SaveCompression
|
||||
): void {
|
||||
this.constants.clear();
|
||||
this.consumables.clear();
|
||||
this.loadMap(this.constants, state.constants);
|
||||
this.loadMap(this.consumables, state.consumables);
|
||||
this.items.clear();
|
||||
this.loadMap(this.items, state.items);
|
||||
this.equipment.loadState(state.equipStore, compression);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@ import {
|
||||
IHeroRenderingSave
|
||||
} from './types';
|
||||
|
||||
// TODO: 挪到渲染端
|
||||
|
||||
export class HeroRendering
|
||||
extends Hookable<IHeroRenderingHooks>
|
||||
implements IHeroRendering
|
||||
|
||||
@ -1,16 +1,12 @@
|
||||
import { HeroEquipment } from './equipment';
|
||||
import { HeroFollowersController } from './follower';
|
||||
import { HeroItems } from './items';
|
||||
import { HeroLocation } from './location';
|
||||
import { HeroRendering } from './rendering';
|
||||
import {
|
||||
IHeroAttribute,
|
||||
IHeroFollowersController,
|
||||
IHeroEquipment,
|
||||
IHeroItems,
|
||||
IHeroLocation,
|
||||
IHeroModifier,
|
||||
IHeroRendering,
|
||||
IHeroState,
|
||||
IHeroStateSave,
|
||||
IReadonlyHeroAttribute,
|
||||
@ -35,8 +31,6 @@ export class HeroState<THero>
|
||||
implements IHeroState<THero>
|
||||
{
|
||||
readonly location: IHeroLocation;
|
||||
readonly rendering: IHeroRendering;
|
||||
readonly followers: IHeroFollowersController;
|
||||
readonly items: IHeroItems<THero>;
|
||||
readonly equip: IHeroEquipment<THero>;
|
||||
|
||||
@ -46,18 +40,12 @@ export class HeroState<THero>
|
||||
public readonly attribute: IHeroAttribute<THero>
|
||||
) {
|
||||
super();
|
||||
this.rendering = new HeroRendering(state);
|
||||
const defaultLoc: IFacedTileLocator = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
direction: FaceDirection.Down
|
||||
};
|
||||
this.location = new HeroLocation(state, defaultLoc, faceHandler);
|
||||
this.followers = new HeroFollowersController(
|
||||
state,
|
||||
this.location,
|
||||
faceHandler
|
||||
);
|
||||
this.items = new HeroItems(state);
|
||||
this.equip = new HeroEquipment(this.items.equipment, this.attribute);
|
||||
}
|
||||
@ -125,15 +113,9 @@ export class HeroState<THero>
|
||||
}
|
||||
|
||||
saveState(compression: SaveCompression): IHeroStateSave<THero> {
|
||||
const followerSaves = this.followers
|
||||
.getAllFollowers()
|
||||
.map(v => v.saveState(compression));
|
||||
|
||||
return {
|
||||
attribute: this.attribute.saveState(compression),
|
||||
location: this.location.saveState(compression),
|
||||
rendering: this.rendering.saveState(compression),
|
||||
followers: followerSaves,
|
||||
items: this.items.saveState(compression),
|
||||
equip: this.equip.saveState(compression)
|
||||
};
|
||||
@ -146,10 +128,7 @@ export class HeroState<THero>
|
||||
// 属性原地读档:不替换实例,使装备与战斗侧持有的引用跨读档始终有效
|
||||
this.attribute.loadState(state.attribute, compression);
|
||||
this.location.loadState(state.location, compression);
|
||||
this.rendering.loadState(state.rendering, compression);
|
||||
this.items.loadState(state.items, compression);
|
||||
this.equip.loadState(state.equip, compression);
|
||||
// 跟随者保留式恢复:同索引同图块数字的实例原地读档,其余按存档替换或删除
|
||||
this.followers.restoreFollowers(state.followers, compression);
|
||||
}
|
||||
}
|
||||
|
||||
@ -579,9 +579,7 @@ export interface IHeroItemSave {
|
||||
|
||||
export interface IHeroItemsSave<THero> {
|
||||
/** 永久道具存档 */
|
||||
readonly constants: readonly IHeroItemSave[];
|
||||
/** 消耗道具存档 */
|
||||
readonly consumables: readonly IHeroItemSave[];
|
||||
readonly items: readonly IHeroItemSave[];
|
||||
/** 装备实例仓库存档 */
|
||||
readonly equipStore: IHeroEquipsStoreSave<THero>;
|
||||
}
|
||||
@ -593,6 +591,8 @@ export interface IHeroItemState<THero> {
|
||||
readonly num: number;
|
||||
/** 道具原始定义数据引用 */
|
||||
readonly raw: IItemRawData<THero>;
|
||||
/** 是否为可消耗道具 */
|
||||
readonly consumable: boolean;
|
||||
/** 道具持有数量 */
|
||||
count: number;
|
||||
}
|
||||
@ -609,13 +609,6 @@ export interface IHeroItems<THero>
|
||||
*/
|
||||
addItem(item: number | string, count?: number): void;
|
||||
|
||||
/**
|
||||
* 勇士获取指定道具,当道具为 `Pick` 类型时会立刻执行其效果,否则使背包中的数量加一。
|
||||
* 是 `addItem(item, 1)` 的另一种写法。
|
||||
* @param item 带锯图块数字或 id
|
||||
*/
|
||||
getItem(item: number | string): void;
|
||||
|
||||
/**
|
||||
* 获取指定道具的状态
|
||||
* @param item 道具图块数字或字符串 id
|
||||
@ -640,12 +633,6 @@ export interface IHeroItems<THero>
|
||||
|
||||
//#region 勇士装备
|
||||
|
||||
/**
|
||||
* 装备实例的存档。装备自身的数值与百分比加成是加成的唯一事实源,
|
||||
* 存档持久化的正是这两个表:无压缩档全量存储、压缩档只存与原始定义的差异;
|
||||
* 读档也由它们重建装备修饰器。故运行时要改变装备加成,必须改装备自身属性,
|
||||
* 而不是修改它的修饰器
|
||||
*/
|
||||
export interface IEquipmentStateSave<THero> {
|
||||
/** 装备实例 uid */
|
||||
readonly uid: number;
|
||||
@ -914,10 +901,6 @@ export interface IHeroStateSave<THero> {
|
||||
readonly attribute: IHeroAttributeSave<THero>;
|
||||
/** 勇士当前位置 */
|
||||
readonly location: IHeroLocationSave;
|
||||
/** 勇士渲染状态 */
|
||||
readonly rendering: IHeroRenderingSave;
|
||||
/** 勇士当前的跟随者 */
|
||||
readonly followers: readonly IHeroFollowerSave[];
|
||||
/** 勇士道具背包状态 */
|
||||
readonly items: IHeroItemsSave<THero>;
|
||||
/** 勇士装备状态 */
|
||||
@ -932,10 +915,6 @@ export interface IHeroState<THero>
|
||||
readonly location: IHeroLocation;
|
||||
/** 勇士属性对象 */
|
||||
readonly attribute: IReadonlyHeroAttribute<THero>;
|
||||
/** 勇士跟随者对象 */
|
||||
readonly followers: IHeroFollowersController;
|
||||
/** 勇士的渲染对象,包含一些必要渲染信息,存在于数据端,并非渲染端 */
|
||||
readonly rendering: IHeroRendering;
|
||||
/** 勇士道具背包 */
|
||||
readonly items: IHeroItems<THero>;
|
||||
/** 勇士装备系统 */
|
||||
|
||||
@ -110,6 +110,12 @@ export interface ITileStore {
|
||||
* @param token 图块数字或 id
|
||||
*/
|
||||
id(token: number | string): string | undefined;
|
||||
|
||||
/**
|
||||
* 传入图块数字或 id,返回图块数字和 id
|
||||
* @param token 图块数字或 id
|
||||
*/
|
||||
identity(token: number | string): [number, string] | [];
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@ -275,6 +275,7 @@
|
||||
"190": "Cannot flatten autotile since its width or height cannot be divided evenly. Autotile frame size: [$1,$2].",
|
||||
"191": "Cannot cache autotile since no its frame or type data stored.",
|
||||
"192": "A FaceGroup.Dir8 face handler registration is expected.",
|
||||
"193": "Cannot load item state since no item id registration for item num $1.",
|
||||
"1001": "Event(setBlock): Unknown tile '$1' when setting block."
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user