feat: 将 HumanBreak 新内容移植入该样板

This commit is contained in:
AncTe committed 2026-02-28 22:56:59 +08:00
1 parent 6bf32174d8
commit bea2725d6a
128 files changed
+11984 -573

No files matched your search

+133
View File
@@ -0,0 +1,133 @@
import { isNil } from 'lodash-es';
import { IDirtyMark, IDirtyTracker } from './types';
/**
* 布尔类型的脏标记追踪器。当传入 `dirtySince` 的标记不属于当前的追踪器时,会返回 `true`
*/
export class PrivateBooleanDirtyTracker implements IDirtyTracker<boolean> {
/** 标记映射 */
private markMap: WeakMap<IDirtyMark, number> = new WeakMap();
/** 脏标记 */
private dirtyFlag: number = 0;
mark(): IDirtyMark {
const symbol = {};
this.markMap.set(symbol, this.dirtyFlag);
return symbol;
}
unmark(mark: IDirtyMark): void {
this.markMap.delete(mark);
}
dirtySince(mark: IDirtyMark): boolean {
const num = this.markMap.get(mark);
if (isNil(num)) return true;
return num < this.dirtyFlag;
}
hasMark(symbol: IDirtyMark): boolean {
return this.markMap.has(symbol);
}
/**
* 将数据标记为脏
*/
protected dirty() {
this.dirtyFlag++;
}
}
/**
* 列表的脏标记追踪器。当传入 `dirtySince` 的标记不属于当前的追踪器时,会返回空集合
*/
export class PrivateListDirtyTracker<T extends number>
implements IDirtyTracker<Set<T>>
{
/** 标记映射,键表示在索引,值表示其对应的标记数字 */
private readonly markMap: Map<T, number> = new Map();
/** 标记 symbol 映射,值表示这个 symbol 对应的标记数字 */
private readonly symbolMap: WeakMap<{}, number> = new WeakMap();
/** 脏标记数字 */
private dirtyFlag: number = 0;
constructor(protected length: number) {}
mark(): IDirtyMark {
const symbol = {};
this.symbolMap.set(symbol, this.dirtyFlag);
return symbol;
}
unmark(mark: IDirtyMark): void {
this.symbolMap.delete(mark);
}
dirtySince(mark: IDirtyMark): Set<T> {
const num = this.symbolMap.get(mark);
const res = new Set<T>();
if (isNil(num)) return res;
this.markMap.forEach((v, k) => {
if (v > num) res.add(k);
});
return res;
}
hasMark(symbol: IDirtyMark): boolean {
return this.symbolMap.has(symbol);
}
protected dirty(data: T): void {
if (data >= this.length) return;
this.dirtyFlag++;
this.markMap.set(data, this.dirtyFlag);
}
protected updateLength(length: number) {
this.length = length;
}
}
export class PrivateMapDirtyTracker<T extends string>
implements IDirtyTracker<Record<T, boolean>>
{
/** 标记映射,键表示名称,值表示其对应的标记数字 */
private readonly markMap: Map<T, number> = new Map();
/** 标记 symbol 映射,值表示这个 symbol 对应的标记数字 */
private readonly symbolMap: WeakMap<{}, number> = new WeakMap();
/** 脏标记数字 */
private dirtyFlag: number = 0;
constructor(protected length: number) {}
mark(): IDirtyMark {
const symbol = {};
this.symbolMap.set(symbol, this.dirtyFlag);
return symbol;
}
unmark(mark: IDirtyMark): void {
this.symbolMap.delete(mark);
}
dirtySince(mark: IDirtyMark): Record<T, boolean> {
const num = this.symbolMap.get(mark) ?? 0;
const obj: Partial<Record<T, boolean>> = {};
this.markMap.forEach((v, k) => {
if (v > num) obj[k] = true;
else obj[k] = false;
});
return obj as Record<T, boolean>;
}
hasMark(symbol: IDirtyMark): boolean {
return this.symbolMap.has(symbol);
}
protected dirty(data: T): void {
this.dirtyFlag++;
this.markMap.set(data, this.dirtyFlag);
}
}
+86
View File
@@ -0,0 +1,86 @@
import { logger } from './logger';
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>
{
/** 加载完成的钩子列表 */
protected readonly loadedList: Set<IHookObject<H, C>> = new Set();
/** 钩子列表 */
private readonly hookList: Set<IHookObject<H, C>> = new Set();
/** 钩子对象到钩子存储对象的映射 */
private readonly hookMap: Map<Partial<H>, IHookObject<H, C>> = new Map();
/** 钩子控制器到钩子存储对象的映射 */
private readonly controllerMap: Map<C, IHookObject<H, C>> = new Map();
/**
* 创建钩子对象的控制器
* @param hook 钩子对象
*/
protected abstract createController(hook: Partial<H>): C;
addHook(hook: Partial<H>): C {
const controller = this.createController(hook);
const obj: IHookObject<H, C> = { hook, controller };
this.hookMap.set(hook, obj);
this.controllerMap.set(controller, obj);
this.hookList.add(obj);
return controller;
}
loadHook(hook: Partial<H>): void {
const obj = this.hookMap.get(hook);
if (!obj) {
logger.warn(85);
return;
}
hook.awake?.(obj.controller);
this.loadedList.add(obj);
}
removeHook(hook: Partial<H>): void {
const obj = this.hookMap.get(hook);
if (!obj) return;
obj.hook.destroy?.(obj.controller);
this.hookList.delete(obj);
this.loadedList.delete(obj);
this.hookMap.delete(hook);
this.controllerMap.delete(obj.controller);
}
removeHookByController(hook: C): void {
const obj = this.controllerMap.get(hook);
if (!obj) return;
obj.hook.destroy?.(obj.controller);
this.hookList.delete(obj);
this.loadedList.delete(obj);
this.controllerMap.delete(hook);
this.hookMap.delete(obj.hook);
}
forEachHook<T>(fn: (hook: Partial<H>, controller: C) => T): T[] {
const arr: T[] = [];
this.loadedList.forEach(v => {
arr.push(fn(v.hook, v.controller));
});
return arr;
}
}
export class HookController<H extends IHookBase> implements IHookController<H> {
constructor(
readonly hookable: IHookable<H, IHookController<H>>,
readonly hook: Partial<H>
) {}
load(): void {
this.hookable.loadHook(this.hook);
}
unload(): void {
this.hookable.removeHookByController(this);
}
}
+3
View File
@@ -1,2 +1,5 @@
export * from './dirtyTracker';
export * from './hook';
export * from './logger';
export * from './utils';
export * from './types';
+48
View File
@@ -26,6 +26,26 @@
"24": "Cannot decode source type of '$1', since there is no registered decoder for that type.",
"25": "Unknown audio type. Header: '$1'",
"26": "Uncaught error when fetching stream data from '$1'. Error info: $2.",
"27": "No autotile connection data, please ensure you have created autotile connection map.",
"28": "Cannot compile map render shader.",
"29": "Cannot get uniform location of map render shader.",
"30": "",
"31": "No asset data is specified when rending map.",
"32": "Every layer added to map renderer must share the same size. Different layer: $1.",
"33": "Map layer transfered to vertex generator must belong to the renderer that the generator use.",
"34": "The texture of moving block must be a part of built asset.",
"35": "Tile background transfered to map renderer does not exists.",
"36": "Tile background transfered to map renderer has no frame data.",
"37": "Frame of tile background transfered to map renderer must share the same size.",
"38": "Cached texture cannot be convert to asset. This is likely an internal bug, please contact us.",
"39": "Offset pool size exceeds WebGL2 limitation, ensure size type of your big image is less than $1.",
"40": "Material used by map block $1 is not found in built asset. Please ensure you have pack it into asset.",
"41": "You are trying to use a texture on moving block whose offset is not in the offset pool, please build it into asset after loading.",
"42": "The '$1' property of map-render element is required.",
"43": "Cannot bind face direction to main block $1, please call malloc in advance.",
"44": "Cannot bind face direction to main block $1, since main direction cannot be override.",
"45": "Cannot add $1 map renderer extension, since $1 already exists for the given state.",
"46": "Cannot execute close door action on $1,$2, since the given position is not empty.",
"1201": "Floor-damage extension needs 'floor-binder' extension as dependency."
},
"warn": {
@@ -94,6 +114,34 @@
"63": "Uncaught promise error in waiting box component. Error reason: $1",
"64": "Text node type and block type mismatch: '$1' vs '$2'",
"65": "Cannot bind a weather controller twice.",
"66": "Texture identifier repeated: $1",
"67": "Cannot set alias '$1' for texture $2, since there is already an alias '$3' for it.",
"68": "Cannot set alias '$1' for texture $2, since '$1' is already an alias for $3.",
"69": "Clip totally exceeds texture's dimensions, no operation will be alppied.",
"70": "An TextureAnimater can only be created once.",
"71": "Cannot start animate when animater not created on texture.",
"72": "Frame count delivered to frame-based animater need to exactly divide texture's height.",
"73": "Consistent size is needed when using WebGL2 composer.",
"74": "Frame size not match texture's size, incomplete data will be skipped.",
"75": "Material count splitted by grid does not match alias count, some material may have no alias or some alias may not map to a texture. Splitted: $1, alias: $2.",
"76": "Cannot pipe texture store when asset builder has been started.",
"77": "Texture is clipped to an area of 0. Ensure that the texture contains your clip rect and clip rect's area is not zero.",
"78": "Adding tileset source must follow index order.",
"79": "Assets can only be built once.",
"80": "Parameter count of MapLayer.getMapData must be 0 or 4.",
"81": "Map data to get is partially (or totally) out of range. Overflowed area will be filled with zero.",
"82": "Big image offset size is larger than 64. Considier reduce big image offset bucket.",
"83": "It seems that you call '$1' too frequently. This will extremely affect game's performance, so considering integrate them into one 'updateArea' or 'updateBlockList' call.",
"84": "Cannot set alias '$1' for layer, since '$1' is already an alias for another layer.",
"85": "Hook to load does not belong to current hookable object.",
"86": "Cannot restore vertex data since delivered state does not belong to current vertex generator instance.",
"87": "Map texture asset cache not hit. This has no effect on rendering, but may extremely affect performance, please see the following link to find solution.",
"88": "No hero image is specified, please set image in advance.",
"89": "Cannot set hero image, since there is no image named '$1'.",
"90": "Cannot set hero image, since delivered image has incorrect format or size.",
"91": "Cannot add follower, since specified follower number $1 does not exist.",
"92": "Followers can only be added when the last follower is not moving.",
"93": "Followers can only be removed when the last follower is not moving.",
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency."
}
}
+11 -6
View File
@@ -72,8 +72,15 @@ export class Logger {
let paramNum = '';
while (++pointer < text.length) {
const char = text[pointer];
const next = text[pointer + 1];
if (char === '$' && text[pointer - 1] !== '\\') {
if (char === '\\' && next === '$') {
str += '$';
pointer++;
continue;
}
if (char === '$' && nums.has(next)) {
inParam = true;
continue;
}
@@ -82,7 +89,7 @@ export class Logger {
if (nums.has(char)) {
paramNum += char;
}
if (!nums.has(text[pointer + 1])) {
if (!nums.has(next)) {
inParam = false;
const num = Number(paramNum);
paramNum = '';
@@ -125,8 +132,7 @@ export class Logger {
hideTipText();
}
const n = Math.floor(code / 50) + 1;
const n2 = code % 50;
const url = `${location.origin}/_docs/logger/error/error${n}.html#error-code-${n2}`;
const url = `${location.origin}/_docs/logger/error/error${n}.html#error-code-${code}`;
console.error(`[ERROR Code ${code}] ${text} See ${url}`);
}
}
@@ -159,8 +165,7 @@ export class Logger {
hideTipText();
}
const n = Math.floor(code / 50) + 1;
const n2 = code % 50;
const url = `${location.origin}/_docs/logger/warn/warn${n}.html#warn-code-${n2}`;
const url = `${location.origin}/_docs/logger/warn/warn${n}.html#warn-code-${code}`;
console.warn(`[WARNING Code ${code}] ${text} See ${url}`);
}
}
+117
View File
@@ -0,0 +1,117 @@
//#region 脏标记
export interface IDirtyMarker<T> {
/**
* 标记为脏,即进行了一次更新
* @param data 传递给追踪器的数据
*/
dirty(data: T): void;
}
export interface IDirtyMark {}
export interface IDirtyTracker<T> {
/**
* 对状态进行标记
*/
mark(): IDirtyMark;
/**
* 取消指定标记符号
* @param mark 标记符号
*/
unmark(mark: IDirtyMark): void;
/**
* 从指定标记符号开始,数据是否发生了变动
* @param mark 标记符号
*/
dirtySince(mark: IDirtyMark): T;
/**
* 当前追踪器是否包含指定标记符号
* @param symbol 标记符号
*/
hasMark(symbol: IDirtyMark): boolean;
}
//#endregion
//#region 钩子
export interface IHookObject<
H extends IHookBase,
C extends IHookController<H>
> {
/** 钩子对象 */
readonly hook: Partial<H>;
/** 钩子控制器 */
readonly controller: C;
}
export interface IHookController<H extends IHookBase = IHookBase> {
/** 控制器的钩子对象 */
readonly hook: Partial<H>;
/**
* 加载此控制器对应的钩子对象
*/
load(): void;
/**
* 卸载此控制器对应的钩子对象,之后此钩子将不会再被触发
*/
unload(): void;
}
export interface IHookBase {
/**
* 加载此钩子对象
* @param controller 钩子控制器对象
*/
awake(controller: IHookController<this>): void;
/**
* 摧毁此钩子对象
* @param controller 钩子控制器对象
*/
destroy(controller: IHookController<this>): void;
}
export interface IHookable<
H extends IHookBase = IHookBase,
C extends IHookController<H> = IHookController<H>
> {
/**
* 添加钩子对象,返回控制该钩子对象的控制器
* @param hook 钩子对象
*/
addHook(hook: Partial<H>): C;
/**
* 加载指定的钩子对象
* @param hook 钩子对象
*/
loadHook(hook: Partial<H>): void;
/**
* 移除钩子对象,会调用钩子对象的 `destroy` 方法
* @param hook 钩子对象
*/
removeHook(hook: Partial<H>): void;
/**
* 传入钩子控制器,移除对应的钩子对象
* @param hook 钩子控制器
*/
removeHookByController(hook: C): void;
/**
* 遍历每个钩子,执行顺序不固定
* @param fn 对每个钩子执行的函数
* @returns 每个钩子的返回值组成的数组,顺序不固定
*/
forEachHook<T>(fn: (hook: Partial<H>, controller: C) => T): T[];
}
//#endregion