diff --git a/project/plugins.js b/project/plugins.js index fb47511..115cc9a 100644 --- a/project/plugins.js +++ b/project/plugins.js @@ -2600,11 +2600,11 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = (obj.clientWidth - obj.totalWidth) / 2 + "px"; core.dom.musicBtn.style.bottom = (obj.clientHeight - obj.totalHeight) / 2 - 27 + "px"; - let startBackground = core.domStyle.isVertical ? - core.getLocalStorage('end') ? 'project/images/winbackgroundVertical.webp' : main.styles.startVerticalBackground || main.styles.startBackground : - core.getLocalStorage('end') ? 'project/images/winbackground.webp' : main.styles.startBackground; - if (main.dom.startBackground.getAttribute("__src__") != startBackground) { - main.dom.startBackground.setAttribute("__src__", startBackground); + if (!core.isPlaying()) { + const end = core.getLocalStorage('end') + let startBackground = core.domStyle.isVertical ? + end ? 'project/images/winbackgroundVertical.webp' : main.styles.startVerticalBackground || main.styles.startBackground : + end ? 'project/images/winbackground.webp' : main.styles.startBackground; main.dom.startBackground.src = startBackground; } const span = document @@ -15182,7 +15182,13 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = main.core.ui.music.random = main.core.ui.music.randomList.indexOf(main.core.ui.music.musicMx[main.core.ui.music.selection[0]][main.core.ui.music.selection[1]]) page = 0; music.style.display = "block"; - core.registerAnimationFrame("music", null, () => { main.core.ui.music.update() }) + let time = 0 + core.registerAnimationFrame("music", null, (temptime) => { + if (temptime > time + 1000 / 60) { + time = temptime + main.core.ui.music.update() + } + }) }; }, "横屏切换": function () { diff --git a/project/types/action.d.ts b/project/types/action.d.ts new file mode 100644 index 0000000..17464a1 --- /dev/null +++ b/project/types/action.d.ts @@ -0,0 +1,137 @@ +/** + * 鼠标与触屏操作的函数 + */ +type MotaMouseFunc = (x: number, y: number, px: number, py: number) => boolean; + +/** + * 按键操作的函数 + */ +type MotaKeyboardFunc = (e: KeyboardEvent) => boolean; + +/** + * 没有最乱,只有更乱 + */ +interface RegisteredActionMap { + keyDown: (keyCode: number) => boolean; + keyDownCtrl: () => boolean; + keyUp: (keyCode: number, altKey: boolean, fromReplay: boolean) => boolean; + longClick: MotaMouseFunc; + onStatusBarClick: (px: number, py: number, vertical: boolean) => boolean; + ondown: MotaMouseFunc; + onkeyDown: MotaKeyboardFunc; + onkeyUp: MotaKeyboardFunc; + onmousewheel: (direct: 1 | -1) => boolean; + onmove: MotaMouseFunc; + onup: MotaMouseFunc; + pressKey: (keyCode: number) => boolean; +} + +type ActionKey = keyof RegisteredActionMap; + +/** + * 将注册的函数的返回值变成void就变成了actions上的函数... + */ +type VoidedActionFuncs = { + [P in ActionKey]: (...params: Parameters) => void; +}; + +/** + * 点击位置 + */ +interface ClickLoc extends Loc { + /** + * 格子的大小(这不是32还能是其它的吗?? + */ + size: 32; +} + +interface RegisteredActionOf { + /** + * 交互的类型 + */ + action: K; + + /** + * 交互的唯一标识符 + */ + name: string; + + /** + * 优先级,越高越优先执行 + */ + priority: number; + + /** + * 交互函数 + */ + func: RegisteredActionMap[K]; +} + +/** + * 交互模块 + */ +interface Actions extends VoidedActionFuncs { + /** + * 横向的最后一个格子的横坐标 + */ + readonly LAST: number; + + /** + * 格子长度的一半 + */ + readonly _HX_: number; + + /** + * 格子高度的一半 + */ + readonly _HY_: number; + + /** + * 脚本编辑中的交互函数 + */ + readonly actionsdata: ActionData; + + /** + * 所有已注册的交互操作 + */ + readonly actions: { + [P in ActionKey]: RegisteredActionOf

[]; + }; + + /** + * 此函数将注册一个用户交互行为。 + * @param action 要注册的交互类型 + * @param name 自定义名称,可被注销使用 + * @param func 执行函数,如果func返回true,则不会再继续执行其他的交互函数 + * @param priority 优先级,优先级高的将会被执行。此项可不填,默认为0 + */ + registerAction( + action: K, + name: string, + func: RegisteredActionMap[K], + priority?: number + ): void; + + /** + * 注销一个用户交互行为 + * @param action 要注销的交互类型 + * @param name 要注销的自定义名称 + */ + unregisterAction(action: ActionKey, name: string): void; + + /** + * 执行一个用户交互行为 + */ + doRegisteredAction( + action: K, + ...params: Parameters + ): void; + + /** + * 判断一个横坐标是否在(_HX_ - 2, _HX_ + 2)范围外 + * @param x 要判断的横坐标 + */ + _out(x: number): boolean; +} + +declare const actions: new () => Actions; diff --git a/project/types/control.d.ts b/project/types/control.d.ts new file mode 100644 index 0000000..321b45a --- /dev/null +++ b/project/types/control.d.ts @@ -0,0 +1,1104 @@ +/** + * 帧动画函数 + */ +type FrameFunc = (time: number) => void; + +/** + * 录像操作函数,返回true表示执行成功 + */ +type ReplayFunc = (action: string) => boolean; + +/** + * 游戏画面大小变化时执行的函数 + */ +type ResizeFunc = (obj: DeepReadonly) => void; + +/** + * 勇士属性中的数字属性 + */ +type NumbericHeroStatus = SelectType; + +/** + * 存读档类型 + */ +type SLType = + | 'save' + | 'load' + | 'reload' + | 'replayLoad' + | 'replayRemain' + | 'replaySince'; + +/** + * 天气等级 + */ +type WeatherLevel = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10; + +/** + * resize函数的参数 + */ +interface ResizeObj { + /** + * body元素的宽度 + */ + clientWidth: number; + + /** + * body元素的高度 + */ + clientHeight: number; + + /** + * 边框的宽度 + */ + BORDER: 3; + + /** + * 状态栏的宽度 + */ + BAR_WIDTH: number; + + /** + * 工具栏的高度 + */ + TOOLBAR_HEIGHT: 38; + + /** + * 计算边框之后的游戏画面的宽度 + */ + outerWidth: number; + + /** + * 计算边框之后的游戏画面的高度 + */ + outerHeight: number; + + /** + * 全局属性 + */ + globalAttribute: GlobalAttribute; + + /** + * 边框样式,css字符串 + */ + border: string; + + /** + * 状态栏显示的状态项 + */ + statusDisplayArr: string[]; + + /** + * 状态栏显示的状态项数 + */ + count: number; + + /** + * 状态栏显示的行数 + */ + col: number; + + /** + * 竖屏下状态栏的高度 + */ + statusBarHeightInVertical: number; + + /** + * 竖屏下工具栏的高度 + */ + toolbarHeightInVertical: number; + + /** + * 是否开启底部工具栏 + */ + extendToolbar: number; + + /** + * @deprecated + * 是否是15x15 + */ + is15x15: false; +} + +interface RenderFrame { + /** + * 帧动画的名称 + */ + name: string; + + /** + * 是否需要进入游戏后才执行 + */ + needPlaying: boolean; + + /** + * 每帧执行的函数 + */ + func: FrameFunc; +} + +interface ReplayAction { + /** + * 录像操作的名称 + */ + name: string; + + /** + * 录像操作执行的函数 + */ + func: ReplayFunc; +} + +interface ResizeAction { + /** + * resize操作的名称 + */ + name: string; + + /** + * 游戏画面变化时执行的函数 + */ + func: ResizeFunc; +} + +interface WeatherAction { + /** + * 天气每帧执行的函数 + */ + frameFunc?: (time: number, level: WeatherLevel) => void; + + /** + * 天气的初始化函数 + */ + initFunc: (level: WeatherLevel) => void; +} + +interface FrameObj { + angle: number; + index: number; + mirror: number; + opacity: number; + x: number; + y: number; + zoom: number; +} + +/** + * 主要用来进行游戏控制,比如行走控制、自动寻路、存读档等等游戏核心内容 + */ +interface Control { + /** + * 刷新状态栏时是否不执行自动事件 + */ + readonly noAutoEvent: boolean; + + /** + * 注册的帧动画 + */ + readonly renderFrameFunc: RenderFrame[]; + + /** + * 注册的录像操作 + */ + readonly replayActions: ReplayAction[]; + + /** + * 注册的resize操作 + */ + readonly resizes: ResizeAction[]; + + /** + * 注册的天气 + */ + readonly weathers: Record; + + /** + * 脚本编辑的control函数列表 + */ + readonly controlData: ControlData; + + /** + * 注册一个animationFrame + * @param name 名称,可用来作为注销使用 + * @param needPlaying 是否只在游戏运行时才执行(在标题界面不执行) + * @param func 要执行的函数,传入time(从页面加载完毕到当前所经过的时间)作为参数 + */ + registerAnimationFrame( + name: string, + needPlaying: boolean, + func: FrameFunc + ): void; + + /** + * 注销一个animationFrame + * @param name 要注销的函数名称 + */ + unregisterAnimationFrame(name: string): void; + + /** + * 进入标题画面 + * @example core.showStartAnimate(); // 重启游戏但不重置bgm + * @param noAnimate 是否不由黑屏淡入而是立即亮屏 + * @param callback 完全亮屏后的回调函数 + */ + showStartAnimate(noAnimate?: boolean, callback?: () => void): void; + + /** + * 淡出标题画面 + * @example core.hideStartAnimate(core.startGame); // 淡出标题画面并开始新游戏,跳过难度选择 + * @param callback 标题画面完全淡出后的回调函数 + */ + hideStartAnimate(callback?: () => void): void; + + /** + * 判断游戏是否已经开始 + */ + isPlaying(): boolean; + + /** + * 清除游戏状态和数据 + */ + clearStatus(): void; + + /** + * 清除地图上绘制的自动寻路路线 + */ + clearAutomaticRouteNode(x: number, y: number): void; + + /** + * 停止自动寻路操作 + */ + stopAutomaticRoute(): void; + + /** + * 保存剩下的寻路,并停止 + */ + saveAndStopAutomaticRoute(): void; + + /** + * 继续剩下的自动寻路操作 + */ + continueAutomaticRoute(): void; + + /** + * 清空剩下的自动寻路列表 + */ + clearContinueAutomaticRoute(callback?: () => any): void; + + /** + * 半自动寻路,用于鼠标或手指拖动 + * @param destX 鼠标或手指的起拖点横坐标 + * @param destY 鼠标或手指的起拖点纵坐标 + * @param stepPostfix 拖动轨迹的数组表示,每项为一步的方向和目标点。 + */ + setAutomaticRoute(destX: number, destY: number, stepPostfix: Loc[]): void; + + /** + * 连续行走 + * @param steps 压缩的步伐数组,每项表示朝某方向走多少步 + */ + setAutoHeroMove(steps: CompressedStep[]): void; + + /** + * 设置行走的效果动画 + */ + setHeroMoveInterval(callback?: () => any): void; + + /** + * 每移动一格后执行的函数 + */ + moveOneStep(callback?: () => any): void; + + /** + * 尝试前进一步,如果面前不可被踏入就会直接触发该点事件 + * @example core.moveAction(core.doAction); // 尝试前进一步,然后继续事件处理 + * @param callback 走一步后的回调函数 + */ + moveAction(callback?: () => void): void; + + /** + * 连续前进,不撞南墙不回头 + * @example core.moveHero(); // 连续前进 + * @param direction 移动的方向,不设置就是勇士当前的方向 + * @param callback 回调函数,设置了就只走一步 + */ + moveHero(direction?: Dir, callback?: () => void): void; + + /** + * 当前是否正在移动 + */ + isMoving(): boolean; + + /** + * 停止勇士的一切行动并等待勇士停下 + * @example core.waitHeroToStop(core.vibrate); // 等待勇士停下,然后视野左右抖动1秒 + * @param callback 勇士停止后的回调函数 + */ + waitHeroToStop(callback?: () => void): void; + + /** + * 主角转向并计入录像,不会导致跟随者聚集,会导致视野重置到以主角为中心 + * @example core.turnHero(); // 主角顺时针旋转,即单击主角或按下Z键的效果 + * @param direction 主角的新朝向,可为up, down, left, right, :left, :right, :back七种之一,不填视为:right + */ + turnHero(direction?: TurnDir): void; + + /** + * 瞬移到某一点 + * @param x 瞬移至的横坐标 + * @param y 瞬移至的纵坐标 + * @param ignoreSteps 忽略的步数,不填则会自动计算 + */ + moveDirectly(x: number, y: number, ignoreSteps?: number): boolean; + + /** + * 尝试瞬移,如果该点有图块/事件/阻激夹域捕则会瞬移到它旁边再走一步(不可踏入的话当然还是触发该点事件),这一步的方向优先和瞬移前主角的朝向一致 + * @example core.tryMoveDirectly(6, 0); // 尝试瞬移到地图顶部的正中央,以样板0层为例,实际效果是瞬移到了上楼梯下面一格然后向上走一步并触发上楼事件 + * @param destX 目标点的横坐标 + * @param destY 目标点的纵坐标 + */ + tryMoveDirectly(destX: number, destY: number): boolean; + + /** + * 绘制主角和跟随者并重置视野到以主角为中心 + * @example core.drawHero(); // 原地绘制主角的静止帧 + * @param status 绘制状态 + * @param offset 相对主角逻辑位置的偏移量,不填视为无偏移 + * @param frame 绘制第几帧 + */ + drawHero( + status?: Exclude, + offset?: number, + frame?: number + ): void; + + /** + * 改变勇士的不透明度 + * @param opacity 要设置成的不透明度 + * @param moveMode 动画的缓动模式 + * @param time 动画时间,不填视为无动画 + * @param callback 动画执行完毕的回调函数 + */ + setHeroOpacity( + opacity: number, + moveMode?: EaseMode, + time?: any, + callback?: () => any + ): void; + + /** + * 设置游戏系统画布的偏移量 + * @param canvasId 字符串或数字,根据ts的说法应该只能填数字,但是浏览器会提高字符串的方式。 + * 但是还是建议填数字,排列顺序一般是纵深从低到高排列 + * @param x 偏移横坐标 + * @param y 偏移纵坐标 + */ + setGameCanvasTranslate( + canvasId: string | number, + x: number, + y: number + ): void; + + /** + * 加减所有游戏系统画布的偏移 + * @param x 增加的横坐标 + * @param y 增加的纵坐标 + */ + addGameCanvasTranslate(x: number, y: number): void; + + /** + * 更新大地图的可见区域 + */ + updateViewport(): void; + + /** + * 设置视野范围 + * @param px 相对大地图左上角的偏移横坐标,单位像素 + * @param py 相对大地图左上角的偏移纵坐标,单位像素 + */ + setViewport(px?: number, py?: number): void; + + /** + * 移动视野范围,这东西真的有人用吗...高级动画 + setViewport就完事了( + * @param x 移动的横坐标,单位格子 + * @param y 移动的纵坐标,单位格子 + * @param moveMode 缓动方式 + * @param time 动画时间 + * @param callback 动画完毕后的回调函数 + */ + moveViewport( + x: number, + y: number, + moveMode?: EaseMode, + time?: number, + callback?: () => void + ): void; + + /** + * 获取主角面前第n格的横坐标 + * @example core.closeDoor(core.nextX(), core.nextY(), 'yellowDoor', core.turnHero); // 在主角面前关上一扇黄门,然后主角顺时针旋转90° + * @param n 目标格与主角的距离,面前为正数,背后为负数,脚下为0,不填视为1 + */ + nextX(n?: number): number; + + /** + * 获取主角面前第n格的纵坐标 + * @example core.jumpHero(core.nextX(2), core.nextY(2)); // 主角向前跃过一格,即跳跃靴道具的使用效果 + * @param n 目标格与主角的距离,面前为正数,背后为负数,脚下为0,不填视为1 + */ + nextY(n?: number): number; + + /** + * 判定主角是否身处某个点的锯齿领域(取曼哈顿距离) + * @example core.nearHero(6, 6, 6); // 判定主角是否身处点(6,6)的半径为6的锯齿领域 + * @param x 领域的中心横坐标 + * @param y 领域的中心纵坐标 + * @param n 领域的半径,不填视为1 + */ + nearHero(x: number, y: number, n?: number): boolean; + + /** + * 立刻聚集所有的跟随者 + */ + gatherFollowers(): void; + + /** + * 更新跟随者坐标 + */ + updateFollowers(): void; + + /** + * 更新领域、夹击、阻击的伤害地图 + * @param floorId 更新的地图id + */ + updateCheckBlock(floorId?: FloorIds): void; + + /** + * 检查并执行领域、夹击、阻击事件 + */ + checkBlock(): void; + + /** + * @deprecated 使用core.updateStatusBar代替。重算并绘制地图显伤 + * @example core.updateDamage(); // 更新当前地图的显伤,绘制在显伤层(废话) + * @param floorId 地图id,不填视为当前地图。预览地图时填写 + * @param ctx 绘制到的画布,如果填写了就会画在该画布而不是显伤层 + */ + updateDamage(floorId?: FloorIds, ctx?: CtxRefer): void; + + /** + * 重绘地图显伤 + * @param ctx 绘制到的画布 + */ + drawDamage(ctx?: CtxRefer): void; + + /** + * 选择录像文件 + */ + chooseReplayFile(): void; + + /** + * 开始播放一个录像 + */ + startReplay(list: string[]): void; + + /** + * 更改播放状态,暂停还是播放 + */ + triggerReplay(): void; + + /** + * 暂停播放 + */ + pauseReplay(): void; + + /** + * 恢复播放 + */ + resumeReplay(): void; + + /** + * 单步播放 + */ + stepReplay(): void; + + /** + * 加速播放 + */ + speedUpReplay(): void; + + /** + * 减速播放 + */ + speedDownReplay(): void; + + /** + * 设置播放速度 + */ + setReplaySpeed(speed: number): void; + + /** + * 停止录像播放 + * @param force 是否是强制停止播放(例如点击停止播放按钮) + */ + stopReplay(force?: boolean): void; + + /** + * 回退录像播放 + */ + rewindReplay(): void; + + /** + * 是否正在播放录像 + */ + isReplaying(): boolean; + + /** + * 回放下一个操作 + */ + replay(): void; + + /** + * 注册一个录像行为 + * @param name 自定义名称,可用于注销使用 + * @param func 具体执行录像的函数,可为一个函数或插件中的函数名; + * 需要接受一个action参数,代表录像回放时的下一个操作 + * func返回true代表成功处理了此录像行为,false代表没有处理此录像行为。 + */ + registerReplayAction(name: string, func: ReplayFunc): void; + + /** + * 注销一个录像行为 + * @param 要注销的录像行为 + */ + unregisterReplayAction(name: string): void; + + /** + * 自动存档 + * @param removeLast 是否移除位于自动存档栈底部的存档 + */ + autosave(removeLast?: any): void; + + /** + * 实际进行自动存档 + */ + checkAutosave(): void; + + /** + * 实际进行存读档事件 + */ + doSL(id: string, type: SLType): void; + + /** + * 同步存档到服务器 + * @param type 同步的类型,填all表示所有都同步,否则只同步当前存档 + */ + syncSave(type?: 'all'): void; + + /** + * 从服务器加载存档 + */ + syncLoad(): void; + + /** + * 存档到本地 + */ + saveData(): Save; + + /** + * 从本地读档 + */ + loadData(data: Save, callback?: () => void): void; + + /** + * 获得某个存档内容 + */ + getSave(index: number, callback?: (data?: Save) => void): void; + + /** + * 获得某些存档内容 + */ + getSaves(ids: number, callback?: (data?: Save) => void): void; + getSaves( + ids: number[], + callback?: (data?: Record) => void + ): void; + + /** + * 获得所有存档内容 + */ + getAllSaves(callback?: (data?: Save[]) => void): void; + + /** + * 获得所有存在存档的存档位 + */ + getSaveIndexes(callback?: (data: Record) => void): void; + + /** + * 判断某个存档位是否存在存档 + */ + hasSave(index: number): boolean; + + /** + * 删除某个存档 + */ + removeSave(index: number, callback?: () => void): void; + + /** + * 设置主角的某个属性 + * @example core.setStatus('loc', {x : 0, y : 0, direction : 'up'}); // 设置主角位置为地图左上角,脸朝上 + * @param name 属性名 + * @param value 属性的新值 + */ + setStatus(name: K, value: HeroStatus[K]): void; + + /** + * 增减主角的某个属性,等价于core.setStatus(name, core.getStatus(name) + value) + * @example core.addStatus('name', '酱'); // 在主角的名字后加一个“酱”字 + * @param name 属性名 + * @param value 属性的增量,请注意旧量和增量中只要有一个是字符串就会把两者连起来成为一个更长的字符串 + */ + addStatus>( + name: K, + value: HeroStatus[K] + ): void; + + /** + * 读取主角的某个属性,不包括百分比修正 + * @example core.getStatus('loc'); // 读取主角的坐标和朝向 + * @param name 属性名 + * @returns 属性值 + */ + getStatus(name: K): HeroStatus[K]; + + /** + * 从status中获得属性,如果不存在则从勇士属性中获取 + * @param status 要从中获取的属性对象 + * @param name 属性名 + */ + getStatusOrDefault( + status?: DeepPartial, + name?: K + ): HeroStatus[K]; + + /** + * 计算主角的某个属性,包括百分比修正 + * @example core.getRealStatus('atk'); // 计算主角的攻击力,包括百分比修正。战斗使用的就是这个值 + * @param name 属性名,注意只能用于数值类属性 + */ + getRealStatus(name: K): number; + + /** + * 从status中获得增幅后的属性,如果不存在则从勇士属性中获取 + * @param status 要从中获取的属性对象 + * @param name 属性名 + */ + getRealStatusOrDefault( + status?: DeepPartial, + name?: K + ): number; + + /** + * 获得勇士原始属性(无装备和衰弱影响) + * @param name 获取的属性名 + */ + getNakedStatus(name?: keyof NumbericHeroStatus): number; + + /** + * 获得某个状态的中文名 + * @param name 要获取的属性名 + */ + getStatusLabel(name: string): string; + + /** + * 设置主角某个属性的百分比修正倍率,初始值为1, + * 倍率存放在flag: `__${name}_${buff}__` 中 + * @example core.setBuff('atk', 0.5); // 主角能发挥出的攻击力减半 + * @param name 属性名,注意只能用于数值类属性 + * @param value 新的百分比修正倍率,不填(效果上)视为1 + */ + setBuff(name: K, value?: number): void; + + /** + * 增减主角某个属性的百分比修正倍率,加减法叠加和抵消。等价于 core.setBuff(name, core.getBuff(name) + value) + * @example core.addBuff('atk', -0.1); // 主角获得一层“攻击力减一成”的负面效果 + * @param name 属性名,注意只能用于数值类属性 + * @param value 倍率的增量 + */ + addBuff(name: K, value: number): void; + + /** + * 读取主角某个属性的百分比修正倍率,初始值为1 + * @example core.getBuff('atk'); // 主角当前能发挥出多大比例的攻击力 + * @param name 属性的英文名 + */ + getBuff(name: keyof NumbericHeroStatus): number; + + /** + * 变更勇士的debuff + * @param action 触发的类型,get表示获得debuff,remove表示移除debuff + * @param type 获取的debuff列表 + */ + triggerDebuff(action: string, type: string | string[]): void; + + /** + * 设置勇士位置 + * 值得注意的是,这句话虽然会使勇士改变位置,但并不会使界面重新绘制; + * 如需立刻重新绘制地图还需调用:core.clearMap('hero'); core.drawHero(); 来对界面进行更新。 + * @example core.setHeroLoc('x', 5) // 将勇士当前位置的横坐标设置为5。 + * @param name 要设置的坐标属性 + * @param value 新值 + * @param noGather 是否聚集跟随者 + */ + setHeroLoc(name: 'x' | 'y', value: number, noGather?: boolean): void; + setHeroLoc(name: 'direction', value: Dir, noGather?: boolean): void; + + /** + * 获取主角的位置,朝向 + * @example core.getHeroLoc(); // 获取主角的位置和朝向 + * @param name 要读取横坐标还是纵坐标还是朝向还是都读取 + */ + getHeroLoc(): Loc; + getHeroLoc(name: K): Loc[K]; + + /** + * 根据级别的数字获取对应的名称,后者定义在全塔属性 + * @example core.getLvName(); // 获取主角当前级别的名称,如“下级佣兵” + * @param lv 级别的数字,不填则视为主角当前的级别 + * @returns 级别的名称,如果不存在就还是返回字符串类型的数字 + */ + getLvName(lv?: number): string; + + /** + * 获得下次升级需要的经验值。 + * 升级扣除模式下会返回经验差值;非扣除模式下会返回总共需要的经验值。 + * 如果无法进行下次升级,返回null。 + */ + getNextLvUpNeed(): number | null; + + /** + * 设置一个flag变量 + * @example core.setFlag('poison', true); // 令主角中毒 + * @param name 变量名,支持中文,这东西用中文就是不规范( + * @param value 变量的新值,不填或填null视为删除 + */ + setFlag(name: string, value?: any): void; + + /** + * 增减一个flag变量,等价于 core.setFlag(name, core.getFlag(name, 0) + value) + * @example core.addFlag('hatred', 1); // 增加1点仇恨值 + * @param name 变量名,支持中文 + * @param value 变量的增量 + */ + addFlag(name: string, value: number | string): void; + + /** + * 获取一个flag变量 + * @param name 变量名,支持中文,这东西用中文就是不规范( + * @param defaultValue 当变量不存在时的返回值,可选(事件流中默认填0)。 + * @returns flags[name] ?? defaultValue + */ + getFlag(name: string, defaultValue?: T): T; + + /** + * 判定一个flag变量是否不为falsy + * @example core.hasFlag('poison'); // 判断主角当前是否中毒 + * @param name 变量名,支持中文,这东西用中文就是不规范( + */ + hasFlag(name: string): boolean; + + /** + * 删除某个flag + * @param name 要删除的变量名 + */ + removeFlag(name: string): void; + + /** + * 设置某个独立开关 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 楼层id + * @param name 独立开关的名称 + * @param value 要设置成的值 + */ + setSwitch( + x?: number, + y?: number, + floorId?: FloorIds, + name?: string, + value?: any + ): void; + + /** + * 获得某个独立开关 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 楼层id + * @param name 独立开关的名称 + * @param value 默认值 + */ + getSwitch( + x?: number, + y?: number, + floorId?: FloorIds, + name?: string, + defaultValue?: T + ): T; + + /** + * 增加某个独立开关 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 楼层id + * @param name 独立开关的名称 + * @param value 增加的值 + */ + addSwitch( + x?: number, + y?: number, + floorId?: FloorIds, + name?: string, + value?: number | string + ): void; + + /** + * 是否存在某个独立开关 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 楼层id + * @param name 独立开关的名称 + */ + hasSwitch( + x?: number, + y?: number, + floorId?: FloorIds, + name?: string + ): boolean; + + /** + * 删除某个独立开关 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 楼层id + * @param name 独立开关的名称 + */ + removeSwitch( + x?: number, + y?: number, + floorId?: FloorIds, + name?: string + ): void; + + /** + * 锁定用户控制,常常用于事件处理 + */ + lockControl(): void; + + /** + * 解锁用户控制 + */ + unlockControl(): void; + + /** + * 开启调试模式, 此模式下可以按Ctrl键进行穿墙, 并忽略一切事件。 + * 此模式下不可回放录像和上传成绩。 + */ + debug(): void; + + /** + * 清空录像折叠信息 + */ + clearRouteFolding(): void; + + /** + * 检查录像折叠信息 + */ + checkRouteFolding(): void; + + /** + * 获得映射文件名 + */ + getMappedName(name: K): NameMap[K]; + + /** + * 设置天气,不计入存档。如需长期生效请使用core.events._action_setWeather()函数 + * @example core.setWeather('fog', 10); // 设置十级大雾天 + * @param type 新天气的类型,不填视为无天气 + * @param level 新天气(晴天除外)的级别,必须为不大于10的正整数,不填视为5 + */ + setWeather(type?: string, level?: WeatherLevel): void; + + /** + * 注册一个天气 + * @param name 天气的名称 + * @param initFunc 初始化函数 + * @param frameFunc 每帧执行的函数 + */ + registerWeather( + name: string, + initFunc: WeatherAction['initFunc'], + frameFunc?: WeatherAction['frameFunc'] + ): void; + + /** + * 注销一个天气 + * @param name 要注销的天气名称 + */ + unregisterWeather(name: string): void; + + /** + * 更改画面色调,不计入存档。如需长期生效请使用core.events._action_setCurtain()函数 + * @example core.setCurtain(); // 恢复画面色调,用时四分之三秒 + * @param color 颜色数组,不填视为[0, 0, 0, 0] + * @param time 渐变时间,单位为毫秒。不填视为750ms + * @param moveMode 缓动模式 + * @param callback 更改完毕后的回调函数 + */ + setCurtain( + color?: RGBArray, + time?: number, + moveMode?: EaseMode, + callback?: () => void + ): void; + + /** + * 画面闪烁 + * @example core.screenFlash([255, 0, 0, 1], 3); // 红屏一闪而过 + * @param color 颜色数组 + * @param time 单次闪烁时长,实际闪烁效果为先花其三分之一的时间渐变到目标色调,再花剩余三分之二的时间渐变回去 + * @param times 闪烁的总次数,不填或填0都视为1 + * @param moveMode 缓动模式 + * @param callback 闪烁全部完毕后的回调函数 + */ + screenFlash( + color: RGBArray, + time: number, + times?: number, + moveMode?: string, + callback?: () => void + ): void; + + /** + * 播放背景音乐,中途开播但不计入存档且只会持续到下次场景切换。如需长期生效请将背景音乐的文件名赋值给flags.__bgm__ + * @example core.playBgm('bgm.mp3', 30); // 播放bgm.mp3,并跳过前半分钟 + * @param bgm 背景音乐的文件名,支持全塔属性中映射前的中文名 + * @param startTime 跳过前多少秒 + */ + playBgm(bgm: BgmIds | NameMapIn, startTime?: number): void; + + /** + * 设置背景音乐的播放速度和音调 + * @param speed 要设置到的速度,100是原速 + * @param usePitch 是否允许声调改变 + */ + setBgmSpeed(speed: number, usePitch?: boolean): void; + + /** + * 暂停背景音乐的播放 + */ + pauseBgm(): void; + + /** + * 恢复背景音乐的播放 + */ + resumeBgm(resumeTime?: number): void; + + /** + * 设置音乐图标的开启关闭状态 + */ + setMusicBtn(): void; + + /** + * 开启或关闭背景音乐的播放 + */ + triggerBgm(): void; + + /** + * 播放一个音效 + * @param sound 音效名 + * @param pitch 音调,同时会修改播放速度,100为原速 + * @param callback 回调函数 + * @returns 音效的唯一标识符,用于停止音效等操作 + */ + playSound( + sound: SoundIds | NameMapIn, + pitch?: number, + callback?: () => void + ): number; + + /** + * 停止音频 + * @param id 停止的音频标识符,不填则停止所有 + */ + stopSound(id?: number): void; + + /** + * 获得正在播放的所有音效的id列表 + * @param name 要获得的音效名 + */ + getPlayingSounds(name?: SoundIds | NameMapIn): number[]; + + /** + * 检查bgm状态,没有播放的话就播放 + */ + checkBgm(): void; + + /** + * 设置屏幕放缩 + * @param delta 在所有可用放缩数组中增加的下标数 + */ + setDisplayScale(delta: number): void; + + /** + * 清空状态栏 + */ + clearStatusBar(): void; + + /** + * 更新状态栏和地图显伤,会在下一个动画帧更新 + * @param doNotCheckAutoEvents 是否不检查自动事件 + * @param immediate 是否立刻刷新,而非延迟到下一动画帧刷新 + */ + updateStatusBar(doNotCheckAutoEvents?: boolean, immediate?: boolean): void; + + /** + * 显示状态栏 + */ + showStatusBar(): void; + + /** + * 隐藏状态栏 + * @param showToolbox 是否显示工具栏 + */ + hideStatusBar(showToolbox?: boolean): void; + + /** + * 更新状态栏的勇士图标 + */ + updateHeroIcon(name: ImageIds): void; + + /** + * 改变工具栏为按钮1-8 + * @param useButton 是否显示为按钮1-8 + */ + setToolbarButton(useButton?: boolean): void; + + /** + * 注册一个resize函数 + * @param name 名称,可供注销使用 + * @param func 游戏画面发生变化时执行的函数 + */ + registerResize(name: string, func: ResizeFunc): void; + + /** + * 注销一个resize函数 + */ + unregisterResize(name: string): void; + + /** + * 屏幕分辨率改变后执行的函数 + */ + resize(): void; +} + +declare const control: new () => Control; diff --git a/project/types/core.d.ts b/project/types/core.d.ts new file mode 100644 index 0000000..84c0492 --- /dev/null +++ b/project/types/core.d.ts @@ -0,0 +1,1450 @@ +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// + +type MaterialIcon = { + [C in Exclude]: { + [P in AllIdsOf]: number; + }; +} & { + /** + * 勇士的图标信息 + */ + hero: { + /** + * 各个方向的图标信息 + */ + [D in Dir2]: { + /** + * 行数 + */ + loc: number; + + /** + * 第一列 + */ + stop: number; + + /** + * 第二列 + */ + leftFoot: number; + + /** + * 第三列 + */ + midFoot: number; + + /** + * 第四列 + */ + rightFoot: number; + }; + } & { + /** + * 勇士的宽度 + */ + width: number; + + /** + * 勇士的高度 + */ + height: number; + }; +}; + +type MaterialImages = { + /** + * 各个类型的图块的图片 + */ + [C in Exclude]: HTMLImageElement; +} & { + /** + * 空气墙 + */ + airwall: HTMLImageElement; + + /** + * 自动元件 + */ + autotile: Record, HTMLImageElement>; + + /** + * 全塔属性注册的图片 + */ + images: Record; + + /** + * 额外素材 + */ + tilesets: Record; +}; + +interface Material { + /** + * 动画信息 + */ + readonly animates: Record; + + /** + * 素材的图片信息 + */ + readonly images: MaterialImages; + + /** + * 音乐信息 + */ + readonly bgms: Record; + + /** + * 音效信息 + */ + readonly sounds: Record; + + /** + * 怪物信息 + * @example core.material.enemys.greenSlime // 获得绿色史莱姆的属性数据 + */ + readonly enemys: { + [P in EnemyIds]: Enemy

; + }; + + /** + * 道具信息 + */ + readonly items: { + [P in AllIdsOf<'items'>]: Item

; + }; + + /** + * 图标信息 + */ + readonly icons: DeepReadonly; + + /** + * @deprecated + * core定义了,但在代码中完全没有找到这个东西?用处未知 + */ + readonly ground: CanvasRenderingContext2D; + + /** + * 楼层背景的画布context + */ + readonly groundCanvas: CanvasRenderingContext2D; + + /** + * 楼层背景的canvas样式 + */ + readonly groundPattern: CanvasPattern; + + /** + * 自动元件的父子关系 + */ + readonly autotileEdges: Record< + AllNumbersOf<'autotile'>, + AllNumbersOf<'autotile'>[] + >; +} + +interface Timeout { + /** + * 单击勇士时的计时器,用于判断双击 + */ + turnHeroTimeout?: number; + + /** + * 按住500ms后进行长按判定的计时器 + */ + onDownTimeout?: number; + + /** + * 长按跳过等待事件的计时器 + */ + sleepTimeout?: number; +} + +interface Interval { + /** + * 勇士移动的定时器 + */ + heroMoveInterval?: number; + + /** + * 长按计时器 + */ + onDownInterval?: number; +} + +interface AnimateFrame { + /** + * 游戏总时间 + */ + readonly totalTime: number; + + /** + * 本次游戏开始的时间 + */ + readonly totalTimeStart: number; + + /** + * @deprecated + * 样板没有出现过 + */ + globalAnimate: boolean; + + /** + * 当前raf的时间戳,即从游戏加载完毕到现在经过的时间 + */ + readonly globalTime: number; + + /** + * @deprecated + * 样板没有出现过 + */ + selectorTime: number; + + /** + * @deprecated + * 样板没有出现过 + */ + selectorUp: boolean; + + /** + * 上一次全局动画的时间(怪物、npc等抖动一下的时间) + */ + readonly animateTime: number; + + /** + * 勇士移动的时候上一次的换腿时间 + */ + readonly moveTime: number; + + /** + * @deprecated + * 样板没有出现过 + */ + lastLegTime: number; + + /** + * 当前是否在左腿上,使用了四帧插件时无效 + */ + readonly leftLeg: boolean; + + /** + * 当前天气信息 + */ + readonly weather: Weather; + + /** + * 左上角提示 + */ + readonly tip?: Readonly; + + /** + * 异步信息,想不到吧,这玩意是一个以number为索引的回调函数列表 + */ + readonly asyncId: Record void>; + + /** + * 上一个异步事件的id + */ + readonly lastAsyncId: number; +} + +interface Weather { + /** + * 当前的raf时间戳,同globalTime,但只有部分天气有用 + */ + time: number; + + /** + * 当前天气类型 + */ + type: string; + + /** + * 谁会去用这个玩意??? + */ + nodes: any[]; + + /** + * 谁会去用这个玩意??? + */ + data: any; + + /** + * 当前的天气等级 + */ + readonly level: number; + + /** + * 雾的图片信息 + */ + readonly fog: HTMLImageElement; + + /** + * 多云的图片信息 + */ + readonly cloud: HTMLImageElement; + + /** + * 晴天的图片信息 + */ + readonly sun: HTMLImageElement; +} + +interface Tip { + /** + * 显示的文字 + */ + text: string; + + /** + * 文字的左边像素位置 + */ + textX: 21 | 45; + + /** + * 提示的宽度 + */ + width: number; + + /** + * 当前的不透明度,会在显示提示时不断变化 + */ + opacity: number; + + /** + * 在显示阶段还是常亮阶段还是消失阶段 + */ + stage: number; + + /** + * 图标的帧数,即显示图标的第几帧 + */ + frame: number; + + /** + * 当前的raf时间戳 + */ + time: number; + + /** + * 在提示进入常亮阶段后经过了多长时间 + */ + displayTime: number; +} + +interface MusicStatus { + /** + * AudioContext信息,注意如果浏览器不支持的话会是null + */ + audioContext: AudioContext; + + /** + * 是否允许播放BGM + */ + bgmStatus: boolean; + + /** + * 是否允许播放SE + */ + soundStatus: boolean; + + /** + * 正在播放的BGM + */ + playingBgm: string; + + /** + * 上次播放的bgm + */ + lastBgm: string; + + /** + * 音量控制节点,只对音效有效,但为什么样板只有一个呢 + */ + gainNode: GainNode; + + /** + * 正在播放的SE,这个__name是音效名 + */ + playingSounds: Record; + + /** + * 用户音量 + */ + userVolume: number; + + /** + * 设计音量,好吧其实不能设计,只有淡入淡出的时候有用 + */ + designVolume: number; + + /** + * 音乐播放速度 + */ + bgmSpeed: number; + + /** + * 修改音乐播放速度时是否修改音调 + */ + bgmUsePitch: boolean; + + /** + * 缓存过BGM内容 + */ + cachedBgms: string[]; + + /** + * 缓存的bgm数量 + */ + cachedBgmCount: 8; +} + +interface CorePlatform { + /** + * 是否http + */ + isOnline: boolean; + + /** + * 是否是PC + */ + isPC: boolean; + + /** + * 是否是Android + */ + isAndroid: boolean; + + /** + * 是否是iOS + */ + isIOS: boolean; + + /** + * 平台信息 + */ + string: 'PC' | 'Android' | 'IOS' | ''; + + /** + * 是否是微信 + */ + isWeChat: boolean; + + /** + * 是否是QQ + */ + isQQ: boolean; + + /** + * 是否是Chrome + */ + isChrome: boolean; + + /** + * 是否是safari浏览器 + */ + isSafari: boolean; + + /** + * 是否支持复制到剪切板 + */ + supportCopy: boolean; + + /** + * 读取文件时的input元素(我也不知道干啥的 + */ + fileInput: HTMLInputElement; + + /** + * FileReader示例 + */ + fileReader: FileReader; + + /** + * 读取成功 + */ + successCallback?: (obj: any) => void; + + /** + * 读取失败 + */ + errorCallback?: () => void; +} + +/** + * dom信息,没有全部标注,只标注了一部分特例 + */ +type MainDom = { + /** + * 所有的状态信息 + */ + status: HTMLCollectionOf; + + /** + * 所有的工具栏图片 + */ + tools: HTMLCollectionOf; + + /** + * 所有的游戏画布 + */ + gameCanvas: HTMLCollectionOf; + + /** + * 所有的状态显示信息,有的是

有的是就挺离谱 + */ + statusLabels: HTMLCollectionOf; + + /** + *

标签的状态显示文字 + */ + statusText: HTMLCollectionOf; + + /** + * 自绘状态栏画布的context + */ + statusCanvasCtx: CanvasRenderingContext2D; + + [key: string]: HTMLElement; +}; + +interface DomStyle { + /** + * 当前缩放大小 + */ + scale: number; + + /** + * 就是window.devicePixelRatio + */ + ratio: number; + + /** + * 高清画布列表 + */ + hdCanvas: string[]; + + /** + * 可以缩放到的缩放比例,是 [1, 1.25, 1.5, 1.75, 2, 2.25, 2.5] 的子数组 + */ + availableScale: number[]; + + /** + * 是否是竖屏 + */ + isVertical: boolean; + + /** + * 是否显示状态栏 + */ + showStatusBar: boolean; + + /** + * 当前道具栏是否是数字键 + */ + toolbarBtn: boolean; +} + +interface CoreBigmap { + /** + * 大地图中会跟随勇士移动的画布 + */ + canvas: string[]; + + /** + * 大地图的横向偏移量,单位像素 + */ + offsetX: number; + + /** + * 大地图的纵向偏移量,单位像素 + */ + offsetY: number; + + /** + * v2优化下的横向偏移格子数 + */ + posX: number; + + /** + * v2优化下的纵向偏移格子数 + */ + posY: number; + + /** + * 地图宽度,单位格子 + */ + width: number; + + /** + * 地图高度,单位格子 + */ + height: number; + + /** + * 是否使用v2优化 + */ + v2: boolean; + + /** + * 判定为超大的图的地图面积临界,使用了显示宝石血瓶详细信息插件的话是256 + */ + threshold: 1024; + + /** + * v2优化下,显示超出的格子数,例如样板是10,那么13*13的样板就是33*33,还用于判断是否进行更新等 + */ + extend: 10; + + /** + * @deprecated + * 又出现了!样板中没有的东西 + */ + scale: 1; + + /** + * 绘制缩略图时的临时画布 + */ + tempCanvas: CanvasRenderingContext2D; + + /** + * 绘制地图时的双缓冲层 + */ + cacheCanvas: CanvasRenderingContext2D; +} + +interface CoreSave { + /** + * 当前存档页面显示的页码数 + */ + saveIndex: number; + + /** + * 当前存在存档的存档位 + */ + ids: Record; + + /** + * 自动存档信息 + */ + autosave: Readonly; + + /** + * 收藏的存档 + */ + favorite: number[]; + + /** + * 保存的存档名称 + */ + favoriteName: Record; +} + +interface Autosave { + /** + * 当前自动存档位 + */ + now: number; + + /** + * 当前存档信息 + */ + data?: Save[]; + + /** + * 自动存档位的最大值 + */ + max: 20; + + /** + * 是否将自动存档写入本地文件 + */ + storage: true; + + /** + * @deprecated + * 每5秒钟会被设置一次的raf时间戳,不知道干什么的。。。 + */ + time: number; + + /** + * @deprecated + * 样板在不停设置这个东西,但不知道什么用处,因为没有调用它的地方 + */ + updated: boolean; + + /** + * 不太清楚干什么的,看起来好像与存档无关,是与本地存储有关的 + */ + cache: Record; +} + +interface CoreValues { + /** + * 血网伤害 + */ + lavaDamage: number; + + /** + * 中毒伤害 + */ + poisonDamage: number; + + /** + * 衰弱状态下攻防减少的数值。如果此项不小于1,则作为实际下降的数值(比如10就是攻防各下降10 + * 如果在0到1之间则为下降的比例(比如0.3就是下降30%的攻防) + */ + weakValue: number; + + /** + * 红宝石加攻数值 + */ + redGem: number; + + /** + * 蓝宝石加防数值 + */ + blueGem: number; + + /** + * 绿宝石加魔防数值 + */ + greenGem: number; + + /** + * 红血瓶加血数值 + */ + redPotion: number; + + /** + * 蓝血瓶加血数值 + */ + bluePotion: number; + + /** + * 黄血瓶加血数值 + */ + yellowPotion: number; + + /** + * 绿血瓶加血数值 + */ + greenPotion: number; + + /** + * 默认的破甲比例 + */ + breakArmor: number; + + /** + * 默认的反击比例 + */ + counterAttack: number; + + /** + * 默认的净化比例 + */ + purify: number; + + /** + * 仇恨属性中,每杀一个怪增加的仇恨值 + */ + hatred: number; + + /** + * 全局动画速度 + */ + animateSpeed: number; + + /** + * 勇士移动速度 + */ + moveSpeed: number; + + /** + * 竖屏下状态栏显示行数 + */ + statusCanvasRowsOnMobile: 1 | 2 | 3 | 4 | 5; + + /** + * 楼层切换时间 + */ + floorChangeTime: number; +} + +type CoreStatusBarElements = { + /** + * 状态栏图标信息 + */ + readonly icons: Record; + + /** + * 状态栏的图标元素 + */ + readonly image: Record; + + readonly [key: string]: HTMLElement; +}; + +type Materails = [ + 'animates', + 'enemys', + 'items', + 'npcs', + 'terrains', + 'enemy48', + 'npc48', + 'icons' +]; + +type CoreFlagProperties = + | 'autoScale' + | 'betweenAttackMax' + | 'blurFg' + | 'canGoDeadZone' + | 'disableShopOnDamage' + | 'displayCritical' + | 'displayEnemyDamage' + | 'displayExtraDamage' + | 'enableAddPoint' + | 'enableEnemyPoint' + | 'enableGentleClick' + | 'enableHDCanvas' + | 'enableMoveDirectly' + | 'enableNegativeDamage' + | 'enableRouteFolding' + | 'equipboxButton' + | 'extendToolbar' + | 'flyNearStair' + | 'flyRecordPosition' + | 'ignoreChangeFloor' + | 'itemFirstText' + | 'leftHandPrefer' + | 'startUsingCanvas' + | 'statusCanvas'; + +type CoreFlags = { + [P in CoreFlagProperties]: boolean; +} & { + /** + * 地图伤害的显示模式 + */ + extraDamageType: number; + + /** + * 当前的状态栏显示项 + */ + statusBarItems: string[]; +}; + +type CoreDataFromMain = + | 'dom' + | 'statusBar' + | 'canvas' + | 'images' + | 'tilesets' + | 'materials' + | 'animates' + | 'bgms' + | 'sounds' + | 'floorIds' + | 'floors' + | 'floorPartitions'; + +/** + * 样板的core的类型,不得不感叹样板的结构真的很神奇(简称粪),两个看似毫无关联的东西都会有着千丝万缕的联系 + */ +interface Core extends Pick { + /** + * 地图的格子宽度 + */ + readonly _WIDTH_: number; + + /** + * 地图的格子高度 + */ + readonly _HEIGHT_: number; + + /** + * 地图的像素宽度 + */ + readonly _PX_: number; + + /** + * 地图的像素高度 + */ + readonly _PY_: number; + + /** + * 地图宽度的一半 + */ + readonly _HALF_WIDTH_: number; + + /** + * 地图高度的一半 + */ + readonly _HALF_HEIGHT_: number; + + /** + * @deprecated + * 地图可视部分大小 + */ + readonly __SIZE__: number; + + /** + * @deprecated + * 地图像素 + */ + readonly __PIXELS__: number; + + /** + * @deprecated + * 地图像素的一半 + */ + readonly __HALF_SIZE__: number; + + /** + * 游戏素材 + */ + readonly material: Material; + + /** + * 计时器(样板的神秘操作 + */ + readonly timeout: Timeout; + + /** + * 定时器 + */ + readonly interval: Interval; + + /** + * 全局动画信息 + */ + readonly animateFrame: AnimateFrame; + + /** + * 音乐状态 + */ + readonly musicStatus: Readonly; + + /** + * 当前游戏平台 + */ + readonly platform: Readonly; + + /** + * dom样式 + */ + readonly domStyle: Readonly; + + /** + * 大地图信息 + */ + readonly bigmap: Readonly; + + /** + * 存档信息 + */ + readonly saves: Readonly; + + /** + * 全局数值信息 + */ + readonly values: CoreValues; + + /** + * 游戏的初始状态 + */ + readonly initStatus: DeepReadonly; + + /** + * 所有的自定义画布 + */ + readonly dymCanvas: Record; + + /** + * 游戏状态 + */ + readonly status: GameStatus; + + /** + * 设置信息 + */ + readonly flags: CoreFlags; + + /** + * 获得所有楼层的信息 + * @example core.floors[core.status.floorId].events // 获得本楼层的所有自定义事件 + */ + readonly floors: DeepReadonly<{ + [P in FloorIds]: ResolvedFloor

; + }>; + + /** + * 游戏主要逻辑模块 + */ + readonly control: Control; + + /** + * 游戏的全塔属性信息 + */ + readonly data: Omit; + + /** + * 游戏加载模块 + */ + readonly loader: Loader; + + /** + * 游戏的事件模块 + */ + readonly events: Events; + + /** + * 游戏的怪物模块 + */ + readonly enemys: Enemys; + + /** + * 游戏的物品模块 + */ + readonly items: Items; + + /** + * 游戏的地图模块 + */ + readonly maps: Maps; + + /** + * 游戏的ui模块 + */ + readonly ui: Ui; + + /** + * 游戏的工具模块 + */ + readonly utils: Utils; + + /** + * 游戏的图标模块 + */ + readonly icons: Icons; + + /** + * 游戏的交互模块 + */ + readonly actions: Actions; + + /** + * 游戏的插件模块 + */ + readonly plugin: PluginDeclaration; + + /** + * 进行游戏初始化 + * @param coreData 初始化信息 + * @param callback 初始化完成后的回调函数 + */ + init(coreData: MainData, callback?: () => void): void; + + /** + * @deprecated + * 在一个上下文下执行函数(真的有人会用这个东西吗? + * @param func 要执行的函数 + * @param _this 执行函数的上下文 + * @param params 函数的参数 + */ + doFunc( + func: F, + _this: any, + ...params: Parameters + ): ReturnType; +} + +type CoreMixin = Core & + Forward & + Forward & + Forward & + Forward & + Forward & + Forward & + Forward & + Forward & + Forward & + Forward & + Forward; + +interface MainStyle extends Readonly { + /** + * 初始界面的背景图 + */ + readonly startBackground: string; + + /** + * 竖屏下初始界面的背景图 + */ + readonly startVerticalBackground: string; + + /** + * 初始界面的文字样式 + */ + readonly startLogoStyle: string; + + /** + * 初始界面的选项样式 + */ + readonly startButtonsStyle: string; +} + +interface SplitImageData { + /** + * 要切分的图片id + */ + readonly name: ImageIds; + + /** + * 每个小图的宽度 + */ + readonly width: number; + + /** + * 每个小图的高度 + */ + readonly height: number; + + /** + * 切分成的小图的前缀名 + */ + readonly prefix: string; +} + +interface Main extends MainData { + /** + * 是否在录像验证中 + */ + readonly replayChecking: boolean; + + /** + * @deprecated + * 就是core,应该没人会用main.core吧( + */ + readonly core: CoreMixin; + + /** + * 游戏的dom信息 + */ + readonly dom: Readonly; + + /** + * 游戏版本,发布后会被随机为数字,请勿使用该属性 + */ + readonly version: string; + + /** + * 是否使用压缩文件 + */ + readonly useCompress: boolean; + + /** + * 存档页数 + */ + readonly savePages: number; + + /** + * 循环临界的分界 + */ + readonly criticalUseLoop: number; + + /** + * 当前游戏模式,是编辑器还是游玩界面 + */ + readonly mode: 'play' | 'editor'; + + /** + * 是否使用远程bgm + */ + readonly bgmRemote: boolean; + + /** + * 远程bgm目录 + */ + readonly bgmRemoteRoot: string; + + /** + * 所有的系统画布 + */ + readonly canvas: Record; + + /** + * 获得所有楼层的信息,等同于core.floors,但两者不是引用关系 + */ + readonly floors: DeepReadonly<{ + [P in FloorIds]: ResolvedFloor

; + }>; + + /** + * 所有的素材图片名称 + */ + readonly materials: Materials; + + /** + * 要加载的project目录下的文件 + */ + readonly pureData: string[]; + + /** + * 要加载的libs目录下的文件 + */ + readonly loadList: string[]; + + /** + * 开始界面中当前选中的按钮 + */ + readonly selectedButton: number; + + /** + * 当前启动服务是否支持高层塔优化 + */ + readonly supportBunch: boolean; + + /** + * 状态栏的图标信息 + */ + readonly statusBar: CoreStatusBarElements; + + /** + * 游戏版本 + */ + readonly __VERSION__: string; + + /** + * 游戏版本代码 + */ + readonly __VERSION_CODE__: number; + + /** + * 初始化游戏 + * @param mode 初始化游戏的模式,游玩还是编辑器 + * @param callback 初始化完成后的回调函数 + */ + init(mode: 'play' | 'editor', callback: () => void): void; + + /** + * 动态加载js文件 + * @param dir 加载的js文件的目录 + * @param loadList 加载的js文件的文件名数组,不带后缀 + * @param callback 加载完毕后的回调函数 + */ + loadJs(dir: string, loadList: string[], callback: () => void): void; + + /** + * 动态加载一个js文件 + * @param dir 加载的js文件的目录 + * @param modName 加载的js文件的名称,不带后缀名,如果是使用压缩文件会自动加上.min + * @param callback 加载完毕后的回调函数,传入的参数是modName + */ + loadMod( + dir: string, + modName: string, + callback: (name: string) => void + ): void; + + /** + * 动态加载所有楼层 + * @param callback 加载完成后的回调函数 + */ + loadFloors(callback: () => void): void; + + /** + * 动态加载一个楼层 + * @param floorId 加载的楼层id + * @param callback 加载完成后的回调函数,传入的参数是加载的楼层id + */ + loadFloor( + floorId: F, + callback: (floorId: F) => void + ): void; + + /** + * 设置加载界面的加载提示文字 + */ + setMainTipsText(text: string): void; + + /** + * @deprecated + * 输出内容(极不好用,建议换成console,我甚至不知道样板为什么会有这个东西) + * @param e 输出内容 + * @param error 输出内容是否是报错 + */ + log(e: string | Error, error?: boolean): void; + + /** + * 生成选择光标的keyframes + */ + createOnChoiceAnimation(): void; + + /** + * 选中开始界面的一个按钮 + * @param index 要选中的按钮 + */ + selectButton(index: number): void; + + /** + * 加载一系列字体 + * @param fonts 要加载的字体列表 + */ + importFonts(fonts: FontIds[]): void; + + /** + * 执行样板的所有监听 + */ + listen(): void; +} + +interface Flags { + /** + * 当前的难度代码 + */ + readonly hard: number; + + /** + * 本次游戏的种子 + */ + readonly __seed__: number; + + /** + * 当前的随机数 + */ + readonly __rand__: number; + + /** + * 难度的颜色,css字符串 + */ + readonly __hardColor__: Color; + + /** + * 平面楼传下,每个楼层的离开位置 + */ + readonly __leaveLoc__: Record; + + /** + * 剧情文本属性 + */ + readonly textAttribute: TextAttribute; + + /** + * 楼层是否到达过 + */ + readonly __visited__: Record; + + [key: string]: any; +} + +interface MapDataOf { + /** + * 图块的id + */ + id: NumberToId[T]; + + /** + * 图块的类型 + */ + cls: ClsOf; +} + +/** + * 样板的主对象 + */ +declare const main: Main; + +/** + * 样板的核心对象 + */ +declare const core: CoreMixin; + +/** + * 所有的变量 + */ +declare let flags: Flags; + +/** + * 勇士信息 + */ +declare let hero: HeroStatus; + +// 让你总是拼错!(不过现在有ts了应该拼不错了 +declare const ture: true; +declare const flase: false; +declare const on: true; +declare const off: false; + +/** + * 全塔属性 + */ +declare const data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d: DataCore; + +/** + * 所有的怪物信息 + */ +declare const enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80: { + [P in EnemyIds]: Enemy

; +}; + +/** + * 所有的公共事件 + */ +declare const events_c12a15a8_c380_4b28_8144_256cba95f760: { + commonEvent: Record; +}; + +/** + * 脚本编辑 + */ +declare const functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a: FunctionsData; + +/** + * 所有的图标信息 + */ +declare const icons_4665ee12_3a1f_44a4_bea3_0fccba634dc1: MaterialIcon; + +/** + * 所有的道具信息 + */ +declare const items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a: { + [P in AllIdsOf<'items'>]: Item

; +}; + +/** + * 所有的图块信息 + */ +declare const maps_90f36752_8815_4be8_b32b_d7fad1d0542e: { + [P in keyof NumberToId]: MapDataOf

; +}; + +/** + * 插件信息 + */ +declare const plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1: PluginDeclaration; diff --git a/project/types/data.d.ts b/project/types/data.d.ts new file mode 100644 index 0000000..157fead --- /dev/null +++ b/project/types/data.d.ts @@ -0,0 +1,140 @@ +interface MainData { + /** + * 所有的楼层id + */ + readonly floorIds: FloorIds[]; + + /** + * 分区指定 + */ + readonly floorPatitions: [FloorIds, FloorIds?][]; + + /** + * 所有的额外素材 + */ + readonly tilesets: string[]; + + /** + * 所有的动画 + */ + readonly animates: AnimationIds[]; + + /** + * 所有的bgm + */ + readonly bgms: BgmIds[]; + + /** + * 所有的音效 + */ + readonly sounds: SoundIds[]; + + /** + * 所有的字体 + */ + readonly fonts: FontIds[]; + + /** + * 文件别名 + */ + readonly nameMap: NameMap; + + /** + * 难度选择 + */ + readonly levelChoose: LevelChooseEvent[]; + + /** + * 装备孔的名称 + */ + readonly equipName: string[]; + + /** + * 初始界面的bgm + */ + readonly startBgm: BgmIds; + + /** + * 主样式 + */ + readonly styles: MainStyle; + + /** + * 图片切分信息 + */ + readonly splitImages: SplitImageData; +} + +interface FirstData { + /** + * 游戏标题 + */ + title: string; + + /** + * 游戏英文名,应当与mota.config.ts中的一致 + */ + name: string; + + /** + * 游戏版本 + */ + version: string; + + /** + * 初始地图 + */ + floorId: FloorIds; + + /** + * 勇士的初始信息 + */ + hero: HeroStatus; + + /** + * 标题界面事件化 + */ + startCanvas: MotaEvent; + + /** + * 初始剧情 + */ + startText: MotaEvent; + + /** + * 全局商店信息 + */ + shops: ShopEventOf[]; + + /** + * 升级事件 + */ + levelUp: LevelUpEvent; +} + +/** + * 全塔属性信息 + */ +interface DataCore { + /** + * 全塔属性的main信息 + */ + readonly main: MainData; + + /** + * 初始化信息 + */ + readonly firstData: FirstData; + + /** + * 全局数值 + */ + readonly values: CoreValues; + + /** + * 全局变量 + */ + readonly flags: CoreFlags; +} + +declare const data: new () => Omit; diff --git a/project/types/enemy.d.ts b/project/types/enemy.d.ts new file mode 100644 index 0000000..543a099 --- /dev/null +++ b/project/types/enemy.d.ts @@ -0,0 +1,382 @@ +type PartialNumbericEnemyProperty = + | 'value' + | 'zone' + | 'repulse' + | 'laser' + | 'breakArmor' + | 'counterAttack' + | 'vampire' + | 'hpBuff' + | 'atkBuff' + | 'defBuff' + | 'range' + | 'haloRange' + | 'n' + | 'purify' + | 'atkValue' + | 'defValue' + | 'damage'; + +type BooleanEnemyProperty = + | 'zoneSquare' + | 'haloSquare' + | 'notBomb' + | 'add' + | 'haloAdd'; + +type Enemy = { + /** + * 怪物id + */ + id: I; + + /** + * 怪物名称 + */ + name: string; + + /** + * 在怪物手册中映射到的怪物ID。如果此项不为null,则在怪物手册中,将用目标ID来替换该怪物原本的ID。 + * 常被运用在同一个怪物的多朝向上 + */ + displayIdInBook: EnemyIds; + + /** + * 战前事件 + */ + beforeBattle: MotaEvent; + + /** + * 战后事件 + */ + afterBattle: MotaEvent; +} & { + [P in PartialNumbericEnemyProperty]?: number; +} & { + [P in BooleanEnemyProperty]: boolean; +} & EnemyInfoBase; + +/** + * 怪物的特殊属性定义 + */ +type EnemySpecialDeclaration = [ + id: number, + name: string | ((enemy: Enemy) => string), + desc: string | ((enemy: Enemy) => string), + color: Color, + extra?: number +]; + +interface DamageString { + /** + * 伤害字符串 + */ + damage: string; + + /** + * 伤害颜色 + */ + color: Color; +} + +interface EnemyInfoBase { + /** + * 生命值 + */ + hp: number; + + /** + * 攻击力 + */ + atk: number; + + /** + * 防御力 + */ + def: number; + + /** + * 金币 + */ + money: number; + + /** + * 经验 + */ + exp: number; + + /** + * 加点量 + */ + point: number; + + /** + * 特殊属性列表 + */ + special: number[]; +} + +interface EnemyInfo extends EnemyInfoBase { + /** + * 支援信息 + */ + guards: [x: number, y: number, id: EnemyIds]; +} + +interface DamageInfo { + /** + * 怪物生命值 + */ + mon_hp: number; + + /** + * 怪物攻击力 + */ + mon_atk: number; + + /** + * 怪物防御力 + */ + mon_def: number; + + /** + * 先攻伤害 + */ + init_damage: number; + + /** + * 怪物的每回合伤害 + */ + per_damage: number; + + /** + * 勇士的每回合伤害 + */ + hero_per_damage: number; + + /** + * 战斗的回合数 + */ + turn: number; + + /** + * 勇士损失的生命值 + */ + damage: number; +} + +interface BookEnemyInfo extends Enemy, EnemyInfo { + /** + * 怪物的坐标列表 + */ + locs?: [x: number, y: number][]; + + /** + * 怪物的中文名 + */ + name: string; + + /** + * 特殊属性名称列表 + */ + specialText: string[]; + + /** + * 特殊属性的颜色列表 + */ + specialColor: Color[]; + + /** + * 怪物的伤害 + */ + damage: number; + + /** + * 第一个临界的加攻的值 + */ + critical: number; + + /** + * 临界的减伤值 + */ + criticalDamage: number; + + /** + * ratio防减伤 + */ + defDamage: number; +} + +/** + * 怪物模块 + */ +interface Enemys extends EnemyData { + /** + * 所有的怪物信息 + */ + readonly enemys: { + [P in EnemyIds]: Enemy

; + }; + + /** + * 脚本编辑的怪物相关 + */ + readonly enemydata: EnemyData; + + /** + * 获得所有怪物原始数据的一个副本 + */ + getEnemys(): { + [P in EnemyIds]: Enemy

; + }; + + /** + * 获得某种敌人的全部特殊属性名称 + * @example core.getSpecialText('greenSlime') // ['先攻', '3连击', '破甲', '反击'] + * @param enemy 敌人id或敌人对象,如core.material.enemys.greenSlime + * @returns 字符串数组 + */ + getSpecialText(enemy: EnemyIds | Enemy): string[]; + + /** + * 获得所有特殊属性的颜色 + * @param enemy 敌人id或敌人对象,如core.material.enemys.greenSlime + */ + getSpecialColor(enemy: EnemyIds | Enemy): Color[]; + + /** + * 获得所有特殊属性的额外标记 + * @param enemy 敌人id或敌人对象,如core.material.enemys.greenSlime + */ + getSpecialFlag(enemy: EnemyIds | Enemy): number[]; + + /** + * 获得某种敌人的某种特殊属性的介绍 + * @example core.getSpecialHint('bat', 1) // '先攻:怪物首先攻击' + * @param enemy 敌人id或敌人对象,用于确定属性的具体数值 + * @param special 属性编号,可以是该敌人没有的属性 + * @returns 属性的介绍,以属性名加中文冒号开头 + */ + getSpecialHint(enemy: EnemyIds | Enemy, special: number): string; + + /** + * 获得某个敌人的某项属性值 + * @param enemy 敌人id或敌人对象 + * @param name 获取的敌人属性 + * @param x 敌人的横坐标 + * @param y 敌人的纵坐标 + * @param floorId 敌人所在楼层 + */ + getEnemyValue( + enemy: EnemyIds | Enemy, + name: K, + x?: number, + y?: number, + floorId?: FloorIds + ): Enemy[K]; + + /** + * 判定主角当前能否打败某只敌人 + * @example core.canBattle('greenSlime',0,0,'MT0') // 能否打败主塔0层左上角的绿头怪(假设有) + * @param enemy 敌人id或敌人对象 + * @param x 敌人的横坐标 + * @param y 敌人的纵坐标 + * @param floorId 敌人所在的地图 + * @returns true表示可以打败,false表示无法打败 + */ + canBattle( + enemy: EnemyIds | Enemy, + x?: number, + y?: number, + floorId?: FloorIds + ): boolean; + + /** + * 获得某只敌人的地图显伤,包括颜色 + * @example core.getDamageString('greenSlime', 0, 0, 'MT0') // 绿头怪的地图显伤 + * @param enemy 敌人id或敌人对象 + * @param x 敌人的横坐标 + * @param y 敌人的纵坐标 + * @param floorId 敌人所在的地图 + */ + getDamageString( + enemy: EnemyIds | Enemy, + x?: number, + y?: number, + floorId?: FloorIds + ): DamageString; + + /** + * 获得某只敌人接下来的若干个临界及其减伤,算法基于useLoop开关选择回合法或二分法 + * @example core.nextCriticals('greenSlime', 9, 0, 0, 'MT0') // 绿头怪接下来的9个临界 + * @param enemy 敌人id或敌人对象 + * @param number 要计算的临界数量,默认为1 + * @param x 敌人的横坐标 + * @param y 敌人的纵坐标 + * @param floorId 敌人所在的地图 + * @returns 两列的二维数组,每行表示一个临界及其减伤 + */ + nextCriticals( + enemy: EnemyIds | Enemy, + number?: number, + x?: number, + y?: number, + floorId?: FloorIds + ): [critical: number, damage: number][]; + + /** + * 计算再加若干点防御能使某只敌人对主角的总伤害降低多少 + * @example core.nextCriticals('greenSlime', 10, 0, 0, 'MT0') // 再加10点防御能使绿头怪的伤害降低多少 + * @param enemy 敌人id或敌人对象 + * @param k 假设主角增加的防御力,默认为1 + * @param x 敌人的横坐标 + * @param y 敌人的纵坐标 + * @param floorId 敌人所在的地图 + * @returns 总伤害的减少量 + */ + getDefDamage( + enemy: EnemyIds | Enemy, + k?: number, + x?: number, + y?: number, + floorId?: FloorIds + ): number; + + /** + * 获得某只敌人对主角的总伤害 + * @example core.getDamage('greenSlime',0,0,'MT0') // 绿头怪的总伤害 + * @param enemy 敌人id或敌人对象 + * @param x 敌人的横坐标 + * @param y 敌人的纵坐标 + * @param floorId 敌人所在的地图 + * @returns 总伤害,如果因为没有破防或无敌怪等其他原因无法战斗,则返回null + */ + getDamage( + enemy: EnemyIds | Enemy, + x?: number, + y?: number, + floorId?: FloorIds + ): number; + + /** + * 获得某张地图的敌人集合,用于手册绘制 + * @example core.getCurrentEnemys('MT0') // 主塔0层的敌人集合 + * @param floorId 地图id + * @returns 敌人集合,按伤害升序排列,支持多朝向怪合并 + */ + getCurrentEnemys(floorId?: FloorIds): Enemy[]; + + /** + * 检查某些楼层是否还有漏打的(某种)敌人 + * @example core.hasEnemyLeft('greenSlime', ['sample0', 'sample1']) // 样板0层和1层是否有漏打的绿头怪 + * @param enemyId 敌人id,可选,默认为任意敌人 + * @param floorId 地图id或其数组,可选,默认为当前地图 + * @returns true表示有敌人被漏打,false表示敌人已死光 + */ + hasEnemyLeft( + enemyId?: EnemyIds | EnemyIds[], + floorId?: FloorIds | FloorIds[] + ): boolean; +} + +declare const enemys: new () => Enemys; diff --git a/project/types/event.d.ts b/project/types/event.d.ts new file mode 100644 index 0000000..9b6b21d --- /dev/null +++ b/project/types/event.d.ts @@ -0,0 +1,763 @@ +/** + * 注册的系统事件函数 + */ +type SystemEventFunc = (data: any, callback: (...params: any[]) => any) => void; + +/** + * 注册的事件函数 + */ +type EventFunc = (data: any, x?: number, y?: number, prefix?: string) => void; + +/** + * 处理所有和事件相关的操作 + */ +interface Events extends EventData { + /** + * 脚本编辑中的事件相关内容 + */ + eventdata: EventData; + + /** + * 公共事件信息 + */ + commonEvent: Record; + + /** + * 所有的系统事件 + */ + systemEvent: Record; + + /** + * 注册的自定义事件 + */ + actions: Record; + + /** + * 开始新游戏 + * @example core.startGame('咸鱼乱撞', 0, ''); // 开始一局咸鱼乱撞难度的新游戏,随机种子为0 + * @param hard 难度名,会显示在左下角(横屏)或右下角(竖屏) + * @param seed 随机种子,相同的种子保证了录像的可重复性 + * @param route 经由base64压缩后的录像,用于从头开始的录像回放 + * @param callback 回调函数 + */ + startGame( + hard: string, + seed: number, + route?: string, + callback?: () => void + ): void; + + /** + * 游戏结束 + * @example core.gameOver(); // 游戏失败 + * @param ending 结局名,省略表示失败 + * @param fromReplay true表示在播放录像 + * @param norank true表示不计入榜单 + */ + gameOver(ending?: string, fromReplay?: boolean, norank?: boolean): void; + + /** + * 重新开始游戏;此函数将回到标题页面 + */ + restart(): void; + + /** + * 询问是否需要重新开始 + */ + confirmRestart(): void; + + /** + * 注册一个系统事件 + * @param type 事件名 + * @param func 为事件的处理函数,可接受(data,callback)参数 + */ + registerSystemEvent(type: string, func: SystemEventFunc): void; + + /** + * 注销一个系统事件 + * @param type 事件名 + */ + unregisterSystemEvent(type: string): void; + + /** + * 执行一个系统事件 + * @param type 执行的事件名 + * @param data 数据信息 + * @param callback 传入事件处理函数的回调函数 + */ + doSystemEvent( + type: string, + data: any, + callback?: (...params: any[]) => any + ): void; + + /** + * 触发(x,y)点的系统事件;会执行该点图块的script属性,同时支持战斗(会触发战后)、道具(会触发道具后)、楼层切换等等 + * @param x 横坐标 + * @param y 纵坐标 + * @param callback 回调函数 + */ + trigger(x: number, y: number, callback?: () => void): void; + + /** + * 战斗,如果填写了坐标就会删除该点的敌人并触发战后事件 + * @example core.battle('greenSlime'); // 和从天而降的绿头怪战斗(如果打得过) + * @param id 敌人id,必填 + * @param x 敌人的横坐标 + * @param y 敌人的纵坐标 + * @param force true表示强制战斗 + * @param callback 回调函数 + */ + battle( + id: AllIdsOf<'enemys' | 'enemy48'>, + x?: number, + y?: number, + force?: boolean, + callback?: () => void + ): void; + + /** + * 开门(包括三种基础墙) + * @example core.openDoor(0, 0, true, core.jumpHero); // 打开左上角的门,需要钥匙,然后主角原地跳跃半秒 + * @param x 门的横坐标 + * @param y 门的纵坐标 + * @param needKey true表示需要钥匙,会导致机关门打不开 + * @param callback 门完全打开后或打不开时的回调函数 + */ + openDoor( + x: number, + y: number, + needKey?: boolean, + callback?: () => void + ): void; + + /** + * 获得道具并提示,如果填写了坐标就会删除该点的该道具 + * @example core.getItem('book'); // 获得敌人手册并提示 + * @param id 道具id,必填 + * @param num 获得的数量,不填视为1,填了就别填坐标了 + * @param x 道具的横坐标 + * @param y 道具的纵坐标 + * @param callback 回调函数 + */ + getItem( + id: AllIdsOf<'items'>, + num?: number, + x?: number, + y?: number, + callback?: () => void + ): void; + + /** + * 轻按获得面前的物品或周围唯一物品 + * @param noRoute 若为true则不计入录像 + */ + getNextItem(noRoute?: boolean): void; + + /** + * 场景切换 + * @example core.changeFloor('MT0'); // 传送到主塔0层,主角坐标和朝向不变,黑屏时间取用户设置值 + * @param floorId 传送的目标地图id,可以填':before'和':after'分别表示楼下或楼上 + * @param stair 传送的位置,可以填':now'(保持不变,可省略),':symmetry'(中心对称),':symmetry_x'(左右对称),':symmetry_y'(上下对称)或图块id(该图块最好在目标层唯一,一般为'downFloor'或'upFloor') + * @param heroLoc 传送的坐标(如果填写了,就会覆盖上述的粗略目标位置)和传送后主角的朝向(不填表示保持不变) + * @param time 传送的黑屏时间,单位为毫秒。不填为用户设置值 + * @param callback 黑屏结束后的回调函数 + */ + changeFloor( + floorId: FloorIds, + stair?: FloorChangeStair | AllIds, + heroLoc?: Partial, + time?: number, + callback?: () => void + ): void; + + /** + * 是否到达过某个楼层 + * @param floorId 楼层id + */ + hasVisitedFloor(floorId?: FloorIds): boolean; + + /** + * 到达某楼层 + * @param floorId 楼层id + */ + visitFloor(floorId?: FloorIds): void; + + /** + * 推箱子 + * @param data 图块信息 + */ + pushBox(data?: Block): void; + + /** + * 当前是否在冰上 + */ + onSki(number?: number): boolean; + + /** + * 注册一个自定义事件 + * @param type 事件类型 + * @param func 事件的处理函数,可接受(data, x, y, prefix)参数 + * data为事件内容,x和y为当前点坐标(可为null),prefix为当前点前缀 + */ + registerEvent(type: string, func: EventFunc): void; + + /** + * 注销一个自定义事件 + * @param type 事件类型 + */ + unregisterEvent(type: string): void; + + /** + * 执行一个自定义事件 + * @param data 事件信息 + * @param x 事件横坐标 + * @param y 事件纵坐标 + * @param prefix 当前点前缀 + */ + doEvent(data: any, x?: number, y?: number, prefix?: string): void; + + /** + * 直接设置事件列表 + * @param list 事件信息 + * @param x 横坐标 + * @param y 纵坐标 + * @param callback 事件执行完毕后的回调函数 + */ + setEvents( + list: MotaEvent, + x?: number, + y?: number, + callback?: () => void + ): void; + + /** + * 开始执行一系列自定义事件 + * @param list 事件信息 + * @param x 横坐标 + * @param y 纵坐标 + * @param callback 事件执行完毕后的回调函数 + */ + startEvents( + list?: MotaEvent, + x?: number, + y?: number, + callback?: () => void + ): void; + + /** + * 执行下一个事件指令,常作为回调 + * @example + * // 事件中的原生脚本,配合勾选“不自动执行下一个事件”来达到此改变色调只持续到下次场景切换的效果 + * core.setCurtain([0,0,0,1], undefined, null, core.doAction); + */ + doAction(): void; + + /** + * 插入一段事件;此项不可插入公共事件,请用 core.insertCommonEvent + * @example core.insertAction('一段文字'); // 插入一个显示文章 + * @param action 单个事件指令,或事件指令数组 + * @param x 新的当前点横坐标 + * @param y 新的当前点纵坐标 + * @param callback 新的回调函数 + * @param addToLast 插入的位置,true表示插入到末尾,否则插入到开头 + */ + insertAction( + action: MotaEvent | MotaAction, + x?: number, + y?: number, + callback?: () => void, + addToLast?: boolean + ): void; + + /** + * 插入一个公共事件 + * @example core.insertCommonEvent('加点事件', [3]); + * @param name 公共事件名;如果公共事件不存在则直接忽略 + * @param args 参数列表,为一个数组,将依次赋值给 flag:arg1, flag:arg2, ... + * @param x 新的当前点横坐标 + * @param y 新的当前点纵坐标 + * @param callback 新的回调函数 + * @param addToLast 插入的位置,true表示插入到末尾,否则插入到开头 + */ + insertCommonEvent( + name: EventDeclaration, + args?: any[], + x?: number, + y?: number, + callback?: () => void, + addToLast?: boolean + ): void; + + /** + * 获得一个公共事件 + * @param name 公共事件名称 + */ + getCommonEvent(name: EventDeclaration): any; + + /** + * 恢复一个事件 + * @param data 事件信息 + */ + recoverEvents(data?: any): void; + + /** + * 检测自动事件 + */ + checkAutoEvents(): void; + + /** + * 当前是否在执行某个自动事件 + * @param symbol 自动事件的标识符 + * @param value 不太清楚有什么用 + */ + autoEventExecuting(symbol?: string, value?: any): boolean; + + /** + * 当前是否执行过某个自动事件 + * @param symbol 自动事件的标识符 + * @param value 不太清楚有什么用 + */ + autoEventExecuted(symbol?: string, value?: any): boolean; + + /** + * 将当前点坐标入栈 + */ + pushEventLoc(x: number, y: number, floorId?: FloorIds): void; + + /** + * 弹出事件坐标点 + */ + popEventLoc(): void; + + /** + * 预编辑事件 + * @param data 事件信息 + */ + precompile(data?: any): any; + + /** + * 点击怪物手册时的打开操作 + * @param fromUserAction 是否是用户开启的 + */ + openBook(fromUserAction?: boolean): void; + + /** + * 点击楼层传送器时的打开操作 + * @param fromUserAction 是否是用户开启的 + */ + useFly(fromUserAction?: boolean): void; + + /** 点击装备栏时的打开操作 */ + openEquipbox(fromUserAction?: boolean): void; + + /** + * 点击工具栏时的打开操作 + * @param fromUserAction 是否是用户开启的 + */ + openToolbox(fromUserAction?: boolean): void; + + /** + * 点击快捷商店按钮时的打开操作 + * @param fromUserAction 是否是用户开启的 + */ + openQuickShop(fromUserAction?: boolean): void; + + /** + * 点击虚拟键盘时的打开操作 + * @param fromUserAction 是否是用户开启的 + */ + openKeyBoard(fromUserAction?: boolean): void; + + /** + * 点击存档按钮时的打开操作 + * @param fromUserAction 是否是用户开启的 + */ + save(fromUserAction?: boolean): void; + + /** + * 点击读档按钮时的打开操作 + * @param fromUserAction 是否是用户开启的 + */ + load(fromUserAction?: boolean): void; + + /** + * 点击设置按钮时的操作 + * @param fromUserAction 是否是用户开启的 + */ + openSettings(fromUserAction?: boolean): void; + + /** + * 当前是否有未处理完毕的异步事件(不包含动画和音效) + */ + hasAsync(): boolean; + + /** + * 立刻停止所有异步事件 + */ + stopAsync(): void; + + /** + * 是否有异步动画 + */ + hasAsyncAnimate(): boolean; + + /** + * 跟随 + * @param name 要跟随的一个合法的4x4的行走图名称,需要在全塔属性注册 + */ + follow(name: ImageIds | NameMapIn): void; + + /** + * 取消跟随 + * @param name 取消跟随的行走图,不填则取消全部跟随者 + */ + unfollow(name?: ImageIds | NameMapIn): void; + + /** + * 数值操作 + * @param name 操作的数值的名称 + * @param operator 操作运算符 + * @param value 值 + * @param prefix 独立开关前缀 + */ + setValue( + name: `${EventValuePreffix}:${string}`, + operator: MotaOperator, + value: number, + prefix?: string + ): void; + + /** + * 设置一项敌人属性并计入存档 + * @example core.setEnemy('greenSlime', 'def', 0); // 把绿头怪的防御设为0 + * @param id 敌人id + * @param name 属性的英文缩写 + * @param value 属性的新值 + * @param operator 操作符 + * @param prefix 独立开关前缀,一般不需要 + * @param norefresh 是否不刷新状态栏 + */ + setEnemy( + id: AllIdsOf<'enemys' | 'enemy48'>, + name: K, + value: Enemy[K], + operator?: MotaOperator, + prefix?: string, + norefresh?: boolean + ): void; + + /** + * 设置某个点的敌人属性 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 楼层id + * @param name 属性的英文缩写 + * @param value 属性的新值 + * @param operator 操作符 + * @param prefix 独立开关前缀,一般不需要 + * @param norefresh 是否不刷新状态栏 + */ + setEnemyOnPoint( + x: number, + y: number, + floorId: FloorIds, + name: K, + value: Enemy[K], + operator?: MotaOperator, + prefix?: string, + norefresh?: boolean + ): void; + + /** + * 重置某个点的敌人属性 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 楼层id + * @param norefresh 是否不刷新状态栏 + */ + resetEnemyOnPoint( + x: number, + y: number, + floorId?: FloorIds, + norefresh?: boolean + ): void; + + /** + * 将某个点已经设置的敌人属性移动到其他点 + * @param fromX 起始横坐标 + * @param fromY 起始纵坐标 + * @param toX 目标横坐标 + * @param toY 目标纵坐标 + * @param floorId 楼层id + * @param norefresh 是否不刷新状态栏 + */ + moveEnemyOnPoint( + fromX: number, + fromY: number, + toX: number, + toY: number, + floorId?: FloorIds, + norefresh?: boolean + ): void; + + /** + * 设置一项楼层属性并刷新状态栏 + * @example core.setFloorInfo('ratio', 2, 'MT0'); // 把主塔0层的血瓶和宝石变为双倍效果 + * @param name 要求改的属性名 + * @param values 属性的新值 + * @param floorId 楼层id,不填视为当前层 + * @param prefix 独立开关前缀,一般不需要,下同 + */ + setFloorInfo( + name: K, + values?: Floor[K], + floorId?: FloorIds, + prefix?: string + ): void; + + /** + * 设置全塔属性 + * @param name 属性名 + * @param value 属性值 + */ + setGlobalAttribute( + name: K, + value: GlobalAttribute[K] + ): void; + + /** + * 设置一个系统开关 + * @example core.setGlobalFlag('steelDoorWithoutKey', true); // 使全塔的所有铁门都不再需要钥匙就能打开 + * @param name 系统开关的英文名 + * @param value 开关的新值,您可以用!core.flags[name]简单地表示将此开关反转 + */ + setGlobalFlag( + name: K, + value: CoreFlags[K] + ): void; + + /** + * 设置文件别名 + * @param name 别名 + * @param value 别名对应的文件名 + */ + setNameMap(name: string, value?: SourceIds): void; + + /** + * 设置剧情文本的属性 + */ + setTextAttribute(data: Partial): void; + + /** + * 移动对话框 + * @param code 对话框的代码 + * @param loc 目标位置 + * @param relative 是否是相对模式 + * @param moveMode 缓动模式 + * @param time 动画时间 + * @param callback 移动完毕的回调函数 + */ + moveTextBox( + code: number, + loc: LocArr, + relative?: boolean, + moveMode?: EaseMode, + time?: number, + callback?: () => void + ): void; + + /** + * 清除对话框 + * @param code 对话框的代码 + * @param callback 回调函数 + */ + clearTextBox(code: number, callback: () => void): void; + + /** + * 关门,目标点必须为空地 + * @example core.closeDoor(0, 0, 'yellowWall', core.jumpHero); // 在左上角关掉一堵黄墙,然后主角原地跳跃半秒 + * @param x 横坐标 + * @param y 纵坐标 + * @param id 门的id,也可以用三种基础墙 + * @param callback 门完全关上后的回调函数 + */ + closeDoor( + x: number, + y: number, + id: AllIdsOf>, + callback?: () => void + ): void; + + /** + * 显示一张图片 + * @example + * // 裁剪winskin.png的最左边128×128px,放大到铺满整个视野,1秒内淡入到50%透明,编号为1 + * core.showImage(1, core.material.images.images['winskin.png'], [0,0,128,128], [0,0,416,416], 0.5, 1000); + * @param code 图片编号,为不大于50的正整数,加上100后就是对应画布层的z值,较大的会遮罩较小的,注意色调层的z值为125,UI层为140 + * @param image 图片文件名(可以是全塔属性中映射前的中文名)或图片对象(见上面的例子) + * @param sloc 一行且至多四列的数组,表示从原图裁剪的左上角坐标和宽高 + * @param loc 一行且至多四列的数组,表示图片在视野中的左上角坐标和宽高 + * @param opacityVal 不透明度,为小于1的正数。不填视为1 + * @param time 淡入时间,单位为毫秒。不填视为0 + * @param callback 图片完全显示出来后的回调函数 + */ + showImage( + code: number, + image: ImageIds | NameMapIn | ImageSource, + sloc?: [number, number, number, number], + loc?: [number, number, number?, number?], + opacityVal?: number, + time?: number, + callback?: () => void + ): void; + + /** + * 隐藏一张图片 + * @example core.hideImage(1, 1000, core.jumpHero); // 1秒内淡出1号图片,然后主角原地跳跃半秒 + * @param code 图片编号 + * @param time 淡出时间,单位为毫秒 + * @param callback 图片完全消失后的回调函数 + */ + hideImage(code: number, time?: number, callback?: () => void): void; + + /** + * 移动一张图片并/或改变其透明度 + * @example core.moveImage(1, null, 0.5); // 1秒内把1号图片变为50%透明 + * @param code 图片编号 + * @param to 新的左上角坐标,省略表示原地改变透明度 + * @param opacityVal 新的透明度,省略表示不变 + * @param moveMode 移动模式 + * @param time 移动用时,单位为毫秒。不填视为1秒 + * @param callback 图片移动完毕后的回调函数 + */ + moveImage( + code: number, + to?: LocArr, + opacityVal?: number, + moveMode?: string, + time?: number, + callback?: () => void + ): void; + + /** + * 旋转一张图片 + * @param code 图片编号 + * @param center 旋转中心像素(以屏幕为基准);不填视为图片本身中心 + * @param angle 旋转角度;正数为顺时针,负数为逆时针 + * @param moveMode 旋转模式 + * @param time 移动用时,单位为毫秒。不填视为1秒 + * @param callback 图片移动完毕后的回调函数 + */ + rotateImage( + code: number, + center?: LocArr, + angle?: number, + moveMode?: EaseMode, + time?: number, + callback?: () => void + ): void; + + /** + * 放缩一张图片 + * @param code 图片编号 + * @param center 旋转中心像素(以屏幕为基准);不填视为图片本身中心 + * @param scale 放缩至的比例 + * @param moveMode 旋转模式 + * @param time 移动用时,单位为毫秒。不填视为1秒 + * @param callback 图片移动完毕后的回调函数 + */ + scaleImage( + code: number, + center?: LocArr, + scale?: number, + moveMode?: string, + time?: number, + callback?: () => void + ): void; + + /** + * 绘制一张动图或擦除所有动图 + * @example core.showGif(); // 擦除所有动图 + * @param name 动图文件名,可以是全塔属性中映射前的中文名 + * @param x 动图在视野中的左上角横坐标 + * @param y 动图在视野中的左上角纵坐标 + */ + showGif( + name?: + | Extract> + | NameMapIn>>, + x?: number, + y?: number + ): void; + + /** + * 调节bgm的音量 + * @example core.setVolume(0, 100, core.jumpHero); // 0.1秒内淡出bgm,然后主角原地跳跃半秒 + * @param value 新的音量,为0或不大于1的正数。注意系统设置中是这个值的平方根的十倍 + * @param time 渐变用时,单位为毫秒。不填或小于100毫秒都视为0 + * @param callback 渐变完成后的回调函数 + */ + setVolume(value: number, time?: number, callback?: () => void): void; + + /** + * 视野抖动 + * @example core.vibrate(); // 视野左右抖动1秒 + * @param direction 抖动方向 + * @param time 抖动时长,单位为毫秒 + * @param speed 抖动速度 + * @param power 抖动幅度 + * @param callback 抖动平息后的回调函数 + */ + vibrate( + direction?: string, + time?: number, + speed?: number, + power?: number, + callback?: () => void + ): void; + + /** + * 强制移动主角(包括后退),这个函数的作者已经看不懂这个函数了 + * @example core.eventMoveHero(['forward'], 125, core.jumpHero); // 主角强制前进一步,用时1/8秒,然后主角原地跳跃半秒 + * @param steps 步伐数组,注意后退时跟随者的行为会很难看 + * @param time 每步的用时,单位为毫秒。0或不填则取主角的移速,如果后者也不存在就取0.1秒 + * @param callback 移动完毕后的回调函数 + */ + eventMoveHero(steps: Step[], time?: number, callback?: () => void): void; + + /** + * 主角跳跃,跳跃勇士。ex和ey为目标点的坐标,可以为null表示原地跳跃。time为总跳跃时间。 + * @example core.jumpHero(); // 主角原地跳跃半秒 + * @param ex 跳跃后的横坐标 + * @param ey 跳跃后的纵坐标 + * @param time 跳跃时长,单位为毫秒。不填视为半秒 + * @param callback 跳跃完毕后的回调函数 + */ + jumpHero( + ex?: number, + ey?: number, + time?: number, + callback?: () => void + ): void; + + /** + * 更改主角行走图 + * @example core.setHeroIcon('npc48.png', true); // 把主角从阳光变成样板0层左下角的小姐姐,但不立即刷新 + * @param name 新的行走图文件名,可以是全塔属性中映射前的中文名。映射后会被存入core.status.hero.image + * @param noDraw true表示不立即刷新(刷新会导致大地图下视野重置到以主角为中心) + */ + setHeroIcon(name: string, noDraw?: boolean): void; + + /** 检查升级事件 */ + checkLvUp(): void; + + /** + * 尝试使用一个道具 + * @example core.tryUseItem('pickaxe'); // 尝试使用破墙镐 + * @param itemId 道具id,其中敌人手册、传送器和飞行器会被特殊处理 + */ + tryUseItem(itemId: ItemIdOf<'tools' | 'constants'>): void; +} + +declare const events: new () => Events; diff --git a/project/types/eventDec.d.ts b/project/types/eventDec.d.ts new file mode 100644 index 0000000..296ce3b --- /dev/null +++ b/project/types/eventDec.d.ts @@ -0,0 +1,313 @@ +type MotaAction = any; +type MotaEvent = any[]; + +/** + * 某种类型的商店 + */ +type ShopEventOf = ShopEventMap[T]; + +interface ShopEventMap { + /** + * 普通商店 + */ + common: CommonShopEvent; + + /** + * 道具商店 + */ + item: ItemShopEvent; + + /** + * 公共事件商店 + */ + event: CommonEventShopEvent; +} + +interface ShopEventBase { + /** + * 商店的id + */ + id: string; + + /** + * 商店快捷名称 + */ + textInList: string; + + /** + * 是否在未开启状态下快捷商店不显示该商店 + */ + mustEnable: boolean; + + /** + * 是否不可预览 + */ + disablePreview: boolean; +} + +/** + * 普通商店的一个商店选项 + */ +interface CommonShopChoice { + /** + * 选项文字 + */ + text: string; + + /** + * 选项需求,需要是一个表达式 + */ + need: string; + + /** + * 图标 + */ + icon: AllIds; + + /** + * 文字的颜色 + */ + color: Color; + + /** + * 该选项被选中时执行的事件 + */ + action: MotaEvent; +} + +/** + * 普通商店 + */ +interface CommonShopEvent extends ShopEventBase { + /** + * 商店中显示的文字 + */ + text: string; + + /** + * 普通商店的选项 + */ + choices: CommonShopChoice[]; +} + +/** + * 道具商店的一个选项 + */ +interface ItemShopChoice { + /** + * 该选项的道具id + */ + id: AllIdsOf<'items'>; + + /** + * 道具存量 + */ + number: number; + + /** + * 购买时消耗的资源数量,是字符串大概是因为这玩意可以用${} + */ + money: string; + + /** + * 卖出时获得的资源数量 + */ + sell: string; + + /** + * 出现条件 + */ + condition: string; +} + +/** + * 道具商店 + */ +interface ItemShopEvent extends ShopEventBase { + /** + * 道具商店标识 + */ + item: true; + + /** + * 购买消耗什么东西,金币还是经验 + */ + use: 'money' | 'exp'; + + /** + * 每个选项 + */ + choices: ItemShopChoice[]; +} + +interface CommonEventShopEvent { + /** + * 使用的公共事件 + */ + commonEvent: EventDeclaration; +} + +interface AutoEventBase { + /** + * 自动事件的触发条件 + */ + condition: string; + + /** + * 是否只在当前层检测 + */ + currentFloor: boolean; + + /** + * 优先级,优先级越高越优先执行 + */ + priority: number; + + /** + * 是否在事件流中延迟执行 + */ + delayExecute: boolean; + + /** + * 是否允许多次执行 + */ + multiExecute: boolean; + + /** + * 当条件满足时执行的事件 + */ + data: MotaEvent; +} + +interface AutoEvent extends AutoEventBase { + /** + * 当前的楼层id + */ + floorId: FloorIds; + + /** + * 自动事件的索引 + */ + index: string; + + /** + * 事件所在的横坐标 + */ + x: number; + + /** + * 事件所在的纵坐标 + */ + y: number; + + /** + * 事件的唯一标识符 + */ + symbol: string; +} + +interface LevelChooseEvent { + /** + * 难度名称 + */ + title: string; + + /** + * 难度简称 + */ + name: string; + + /** + * 难度的hard值 + */ + hard: number; + + /** + * 难度的颜色 + */ + color: RGBArray; + + /** + * 选择该难度时执行的事件 + */ + action: MotaEvent; +} + +interface LevelUpEvent { + /** + * 升级所需经验 + */ + need: number; + + /** + * 这个等级的等级名 + */ + title: string; + + /** + * 升级时执行的事件 + */ + action: MotaEvent; +} + +/** + * 门信息 + */ +interface DoorInfo { + /** + * 开门时间 + */ + time: number; + + /** + * 开门音效 + */ + openSound: SoundIds; + + /** + * 关门音效 + */ + closeSound: SoundIds; + + /** + * 需要的钥匙 + */ + keys: Partial | `${ItemIdOf<'tools'>}:o`, number>>; + + /** + * 开门后事件 + */ + afterOpenDoor?: MotaEvent; +} + +interface ChangeFloorEvent { + /** + * 到达的楼层 + */ + floorId: ':before' | ':after' | ':now' | FloorIds; + + /** + * 到达的坐标,填了的话stair就无效了 + */ + loc?: LocArr; + + /** + * 到达的坐标 + */ + stair?: FloorChangeStair; + + /** + * 勇士朝向 + */ + direction?: HeroTurnDir; + + /** + * 楼层转换时间 + */ + time?: number; + + /** + * 是否不可穿透 + */ + ignoreChangeFloor?: boolean; +} diff --git a/project/types/eventStatus.d.ts b/project/types/eventStatus.d.ts new file mode 100644 index 0000000..5a13d10 --- /dev/null +++ b/project/types/eventStatus.d.ts @@ -0,0 +1,456 @@ +interface EventStatusDataMap { + /** + * 执行事件中 + */ + action: ActionStatusData; + + /** + * 怪物手册的信息,是当前选择了哪个怪物 + */ + book: number; + + /** + * 楼层传送器中当前楼层索引 + */ + fly: number; + + /** + * 浏览地图时的信息 + */ + viewMaps: ViewMapStatusData; + + /** + * 装备栏的信息 + */ + equipbox: EquipboxStatusData; + + /** + * 道具栏的信息 + */ + toolbox: ToolboxStatusData; + + /** + * 存档界面的信息 + */ + save: SaveStatusData; + load: SaveStatusData; + replayLoad: SaveStatusData; + replayRemain: SaveStatusData; + replaySince: SaveStatusData; + + /** + * 文本框界面的信息 + */ + text: TextStatusData; + + /** + * 确认框界面的信息 + */ + confirmBox: ConfirmStatusData; + + /** + * 关于界面,帮助界面,怪物手册详细信息界面,虚拟键盘界面,系统设置界面,系统选项栏界面, + * 快捷商店界面,存档笔记界面,同步存档界面,光标界面,录像回放界面,游戏信息界面,没有东西 + */ + about: null; + help: null; + 'book-detail': null; + keyBoard: null; + switchs: null; + 'switchs-sounds': null; + 'switchs-display': null; + 'switchs-action': null; + settings: null; + selectShop: null; + notes: null; + syncSave: null; + syncSelect: null; + localSaveSelect: null; + storageRemove: null; + cursor: null; + replay: null; + gameInfo: null; +} + +interface _EventStatusSelectionMap { + /** + * 执行事件中,一般是选择框的当前选中项 + */ + action: number; + + /** + * 装备栏中当前选中了哪个装备 + */ + equipbox: number; + + /** + * 道具栏中当前选中了哪个道具 + */ + toolbox: number; + + /** + * 当前是否是删除模式 + */ + save: boolean; + load: boolean; + + /** + * 当前选择了确认(0)还是取消(1) + */ + confirmBox: 0 | 1; + + /** + * 系统设置界面,存档笔记界面,同步存档界面,录像回放界面,游戏信息界面,当前的选择项 + */ + switchs: number; + 'switchs-sounds': number; + 'switchs-display': number; + 'switchs-action': number; + settings: number; + notes: number; + syncSave: number; + syncSelect: number; + localSaveSelect: number; + storageRemove: number; + replay: number; + gameInfo: number; +} + +interface _EventStatusUiMap { + /** + * 执行事件中,一般是与选择框有关的 + */ + action: ActionStatusUi; + + /** + * 如果是从浏览地图界面呼出的怪物手册,该项是当前正在浏览的地图的索引(注意不是id) + */ + book: number; + + /** + * 确认框中显示的文字 + */ + confirmBox: string; + + /** + * 显示设置的选择信息 + */ + 'switchs-display': SwitchsStatusData; + + /** + * 系统选项栏的选择信息 + */ + settings: SwitchsStatusData; + + /** + * 快捷商店界面,存档笔记界面,同步存档界面,录像回放界面,游戏信息界面的绘制信息 + */ + selectShop: SelectShopStatusUi; + notes: SelectShopStatusUi; + syncSave: SelectShopStatusUi; + syncSelect: SelectShopStatusUi; + localSaveSelect: SelectShopStatusUi; + storageRemove: SelectShopStatusUi; + gameInfo: SelectShopStatusUi; +} + +interface _EventStatusIntervalMap { + /** + * 执行事件中,一般用于呼出某个界面时暂存当前事件信息(? + */ + action: ActionStatusData; + + /** + * 如果是从事件中呼出的,用于存储当前事件信息,当退出怪物手册时恢复事件 + */ + book: ActionStatusData; + + /** + * 如果是从事件中呼出的,用于存储当前事件信息,当退出存档节目时恢复事件 + */ + save: ActionStatusData; + load: ActionStatusData; +} + +interface _EventStatusTimeoutMap { + /** + * 执行事件中,一般是等待用户操作事件等事件中的超时时间的判定 + */ + action: number; +} + +interface _EventStatusAnimateUiMap { + /** + * 执行事件中,一般是对话框事件的动画定时器 + */ + action: number; +} + +type EventStatus = keyof EventStatusDataMap; + +type _FillEventStatus = { + [P in EventStatus]: P extends keyof T ? T[P] : null; +}; + +type EventStatusSelectionMap = _FillEventStatus<_EventStatusSelectionMap>; +type EventStatusUiMap = _FillEventStatus<_EventStatusUiMap>; +type EventStatusIntervalMap = _FillEventStatus<_EventStatusIntervalMap>; +type EventStatusTimeoutMap = _FillEventStatus<_EventStatusTimeoutMap>; +type EventStatusAnimateUiMap = _FillEventStatus<_EventStatusAnimateUiMap>; + +/** + * 某个事件状态下的信息 + */ +interface EventStatusOf { + /** + * 当前事件状态的类型 + */ + id: T; + + /** + * 当前事件状态的信息 + */ + data: EventStatusDataMap[T]; + + /** + * 当前事件状态的选择信息 + */ + selection: EventStatusSelectionMap[T]; + + /** + * 当前事件状态的ui信息 + */ + ui: EventStatusUiMap[T]; + + /** + * 当前事件状态的定时器信息 + */ + interval: EventStatusIntervalMap[T]; + + /** + * 当前事件状态的计时器信息 + */ + timeout: EventStatusTimeoutMap[T]; + + /** + * 当前事件状态的动画信息 + */ + animateUi: EventStatusAnimateUiMap[T]; +} + +interface ActionStatusEventList { + /** + * 要执行的事件列表 + */ + todo: MotaEvent; + + /** + * 全部的事件列表 + */ + total: MotaEvent; + + /** + * 执行条件 + */ + contidion: string; +} + +interface ActionLocStackInfo { + /** + * 横坐标 + */ + x: number; + + /** + * 纵坐标 + */ + y: number; + + /** + * 楼层id + */ + floorId: FloorIds; +} + +/** + * 执行事件中 + */ +interface ActionStatusData { + /** + * 当前的事件列表 + */ + list: DeepReadonly; + + /** + * 事件执行的横坐标,或者对话框的横坐标 + */ + x?: number; + + /** + * 事件执行的纵坐标,或者对话框的纵坐标 + */ + y?: number; + + /** + * 事件执行完毕的回调函数 + */ + callback?: () => void; + + /** + * 不太清楚用处,可能是与自动事件有关的 + */ + appendingEvents: MotaEvent[]; + + /** + * 事件的坐标栈 + */ + locStack: any[]; + + /** + * 当前的事件类型 + */ + type: EventType; + + /** + * 当前事件 + */ + current: MotaAction; +} + +interface ActionStatusUi { + /** + * 显示文字事件的文字,包括确认框等 + */ + text: string; + + /** + * 确认框中确定时执行的事件 + */ + yes?: MotaEvent; + + /** + * 确认框中取消时执行的事件 + */ + no?: MotaEvent; + + /** + * 当前是选择事件时所有的选项 + */ + choices?: string[]; + + /** + * 当前是选择事件时选项框的宽度 + */ + width?: number; +} + +interface ViewMapStatusData { + /** + * 当前浏览的楼层索引 + */ + index: number; + + /** + * 是否显示伤害 + */ + damage: boolean; + + /** + * 大地图是否显示全部地图 + */ + all: boolean; + + /** + * 大地图不显示全部地图时当前的横坐标,单位格子 + */ + x: number; + + /** + * 大地图不显示全部地图时当前的纵坐标,单位格子 + */ + y: number; +} + +interface EquipboxStatusData { + /** + * 拥有装备的当前页数 + */ + page: number; + + /** + * 当前选中的装备 + */ + selectId: ItemIdOf<'equips'>; +} + +interface ToolboxStatusData { + /** + * 消耗道具的当前页码数 + */ + toolsPage: number; + + /** + * 永久道具的当前页码数 + */ + constantsPage: number; + + /** + * 当前选中的道具id + */ + selectId: ItemIdOf<'constants' | 'tools'>; +} + +interface SaveStatusData { + /** + * 当前存读档界面的页码数 + */ + page: number; + + /** + * 选择的框的偏移量,在不同状态下意义不同 + */ + offset: number; + + /** + * 当前存读档界面的模式,fav表示收藏,all表示所有存档 + */ + mode: 'fav' | 'all'; +} + +interface TextStatusData { + /** + * 文本框要显示的文字列表 + */ + list: string[]; + + /** + * 文字显示完毕后的回调函数 + */ + callback: () => void; +} + +interface ConfirmStatusData { + /** + * 点击确认时 + */ + yes: () => void; + + /** + * 点击取消时 + */ + no: () => void; +} + +interface SwitchsStatusData { + /** + * 选择项 + */ + choices: string[]; +} + +interface SelectShopStatusUi { + /** + * 选择框的偏移量 + */ + offset: number; +} diff --git a/project/types/function.d.ts b/project/types/function.d.ts new file mode 100644 index 0000000..86c1e1c --- /dev/null +++ b/project/types/function.d.ts @@ -0,0 +1,282 @@ +interface ActionData { + /** + * 当按键弹起时 + * @param keyCode 按键的keyCode + * @param altKey 当前是否按下了alt键 + */ + onKeyUp(keyCode: number, altKey: boolean): boolean; + + /** + * 当点击状态栏时 + * @param px 点击的横坐标 + * @param py 点击的纵坐标 + * @param vertical 当前是否是竖屏 + */ + onClickStatusBar(px: number, py: number, vertical: boolean): boolean; +} + +interface ControlData { + /** + * 获取保存信息 + */ + saveData(): Save; + + /** + * 读取一个存档 + * @param data 存档信息 + * @param callback 读取完毕后的回调函数 + */ + loadData(data: Save, callback?: () => void): void; + + /** + * 获取一个属性对应的中文名 + * @param name 要获取的状态名称 + */ + getStatusLabel(name: string): string; + + /** + * 变更勇士的debuff + * @param action 触发的类型,get表示获得debuff,remove表示移除debuff + * @param type 获取的debuff列表 + */ + triggerDebuff(action: 'get' | 'remove', type: string | string[]): void; + + /** + * 立即仅更新状态栏 + */ + updateStatusBar(): void; + + /** + * 更新一个地图的地图伤害 + * @param floorId 要更新的楼层id + */ + updateCheckBlock(floorId: FloorIds): void; + + /** + * 每步移动后执行的函数 + * @param callback 回调函数(好像没什么必要吧 + */ + moveOneStep(callback?: () => void): void; + + /** + * 瞬移到某一点 + * @param x 瞬移至的横坐标 + * @param y 瞬移至的纵坐标 + * @param ignoreSteps 忽略的步数,不填则会自动计算 + */ + moveDirectly(x: number, y: number, ignoreSteps?: number): boolean; + + /** + * 并行脚本 + * @param time 距离游戏加载完毕经过的时间 + */ + parallelDo(time: number): void; +} + +interface EnemyData { + /** + * 获得所有特殊属性定义 + */ + getSpecials(): EnemySpecialDeclaration[]; + + /** + * 获得怪物真实属性 + * @param enemy 敌人id或敌人对象 + * @param hero 勇士信息,不填则从core.status.hero获取 + * @param x 敌人的横坐标 + * @param y 敌人的纵坐标 + * @param floorId 敌人所在的地图 + */ + getEnemyInfo( + enemy: EnemyIds | Enemy, + hero?: HeroStatus, + x?: number, + y?: number, + floorId?: FloorIds + ): EnemyInfo; + + /** + * 获得战斗伤害信息(实际伤害计算函数) + * @param enemy 敌人id或敌人对象 + * @param hero 勇士信息,不填则从core.status.hero获取 + * @param x 敌人的横坐标 + * @param y 敌人的纵坐标 + * @param floorId 敌人所在的地图 + */ + getDamageInfo( + enemy: EnemyIds | Enemy, + hero?: HeroStatus, + x?: number, + y?: number, + floorId?: FloorIds + ): DamageInfo; + + /** + * 判定某种特殊属性的有无 + * @example core.hasSpecial('greenSlime', 1) // 判定绿头怪有无先攻属性 + * @param special 敌人id或敌人对象或正整数数组或自然数 + * @param test 待检查的属性编号 + * @returns 若special为数组或数且含有test或相等、或special为敌人id或对象且具有此属性,则返回true + */ + hasSpecial( + special: number | number[] | EnemyIds | Enemy, + test: number + ): boolean; +} + +interface UiData { + /** + * 获取道具栏要显示的道具 + * @param cls 要获取的类型 + */ + getToolboxItems>(cls: T): ItemIdOf[]; + + /** + * 绘制状态栏 + */ + drawStatusBar(): void; + + /** + * 数据统计界面统计的道具数量 + */ + drawStatistics(): AllIdsOf<'items'>[]; + + /** + * 绘制关于界面 + */ + drawAbout(): void; +} + +interface EventData { + /** + * 重置游戏 + * @param hero 勇士信息 + * @param hard 难度信息 + * @param floorId 勇士所在楼层 + * @param maps 所有的地图信息 + * @param values 全局数值信息 + */ + resetGame( + hero: HeroStatus, + hard: string, + floorId: FloorIds, + maps: GameStatus['maps'], + values: Partial + ): void; + + /** + * 游戏获胜 + * @param reason 胜利原因 + * @param norank 是否不计榜 + * @param noexit 是否不退出 + */ + win(reason: string, norank?: boolean, noexit?: boolean): void; + + /** + * 游戏失败 + * @param reason 失败原因 + */ + lose(reason: string): void; + + /** + * 切换楼层中,即屏幕完全变黑的那一刻 + * @param floorId 目标楼层 + * @param heroLoc 勇士到达的位置 + */ + changingFloor(floorId: FloorIds, heroLoc: Loc): void; + + /** + * 切换楼层后 + * @param floorId 目标楼层 + */ + afterChangeFloor(floorId: FloorIds): void; + + /** + * 飞往某个楼层 + * @param toId 目标楼层 + * @param callback 飞到后的回调函数 + */ + flyTo(toId: FloorIds, callback: () => void): void; + + /** + * 与怪物战斗前 + * @param enemyId 打败的怪物 + * @param x 怪物横坐标 + * @param y 怪物纵坐标 + */ + beforeBattle( + enemyId: AllIdsOf<'enemys' | 'enemy48'>, + x?: number, + y?: number + ): void; + + /** + * 与怪物战斗后 + * @param enemyId 打败的怪物 + * @param x 怪物横坐标 + * @param y 怪物纵坐标 + */ + afterBattle( + enemyId: AllIdsOf<'enemys' | 'enemy48'>, + x?: number, + y?: number + ): void; + + /** + * 开门后 + * @param doorId 门的id + * @param x 门的横坐标 + * @param y 门的纵坐标 + */ + afterOpenDoor( + doorId: AllIdsOf>, + x: number, + y: number + ): void; + + /** + * 获得道具后 + * @param itemId 道具id + * @param x 道具横坐标 + * @param y 道具纵坐标 + * @param isGentleClick 是否是轻按 + */ + afterGetItem( + itemId: AllIdsOf<'items'>, + x: number, + y: number, + isGentleClick?: boolean + ): void; + + /** + * 推箱子后 + */ + afterPushBox(): void; +} + +interface FunctionsData { + /** + * 交互信息 + */ + actions: ActionData; + + /** + * 游戏的逻辑信息 + */ + control: ControlData; + + /** + * 怪物信息 + */ + enemys: EnemyData; + + /** + * ui信息 + */ + ui: UiData; + + /** + * 事件信息 + */ + events: EventData; +} diff --git a/project/types/icon.d.ts b/project/types/icon.d.ts new file mode 100644 index 0000000..47bfaef --- /dev/null +++ b/project/types/icon.d.ts @@ -0,0 +1,71 @@ +type IconIds = + | keyof MaterialIcon['animates'] + | keyof MaterialIcon['autotile'] + | keyof MaterialIcon['enemy48'] + | keyof MaterialIcon['enemys'] + | keyof MaterialIcon['hero'] + | keyof MaterialIcon['items'] + | keyof MaterialIcon['items'] + | keyof MaterialIcon['npc48'] + | keyof MaterialIcon['npcs'] + | keyof MaterialIcon['terrains']; + +interface IconOffsetInfo { + /** + * 图块所在额外素材的id + */ + image: string; + + /** + * 图块所在图片位于额外素材的横坐标 + */ + x: number; + + /** + * 图块所在图片位于额外素材的纵坐标 + */ + y: number; +} + +/** + * 和图标相关的内容 + */ +interface Icons { + /** + * 图标信息 + */ + readonly icons: MaterialIcon; + + /** + * 额外素材偏移起点 + */ + readonly tilesetStartOffset: 10000; + + /** + * 图标的id + */ + readonly allIconIds: IconIds; + + /** + * 获得所有图标类型 + */ + getIcons(): MaterialIcon; + + /** + * 根据ID获得图块类型 + */ + getClsFromId(id: T): ClsOf; + + /** + * 获得所有图标的ID + */ + getAllIconIds(): IconIds; + + /** + * 根据图块数字或ID获得所在的tileset和坐标信息 + * @param id 图块数字或id + */ + getTilesetOffset(id: string | number): IconOffsetInfo | null; +} + +declare const icons: new () => Icons; diff --git a/project/types/item.d.ts b/project/types/item.d.ts new file mode 100644 index 0000000..aca1067 --- /dev/null +++ b/project/types/item.d.ts @@ -0,0 +1,281 @@ +interface Item> { + /** + * 道具id + */ + id: I; + + /** + * 道具的类型 + */ + cls: ItemClsOf; + + /** + * 道具的名称 + */ + name: string; + + /** + * 道具的描述 + */ + text?: string; + + /** + * 是否在道具栏隐藏 + */ + hideInToolBox: boolean; + + /** + * 装备信息 + */ + equip: ItemClsOf extends 'equips' ? Equip : never; + + /** + * 回放使用时是否不先打开道具栏再使用 + */ + hideInReplay: boolean; + + /** + * 即捡即用效果 + */ + itemEffect?: string; + + /** + * 即捡即用道具捡过之后的提示 + */ + itemEffectTip?: string; + + /** + * 使用道具时执行的事件 + */ + useItemEvent?: MotaEvent; + + /** + * 使用道具时执行的代码 + */ + useItemEffect?: string; + + /** + * 能否使用道具 + */ + canUseItemEffect?: string | boolean; +} + +interface EquipBase { + /** + * 装备增加的数值 + */ + value: Record, number>; + + /** + * 装备增加的百分比 + */ + percentage: Record, number>; +} + +interface Equip extends EquipBase { + /** + * 可以装备到的装备孔 + */ + type: number | string; + + /** + * 穿上装备时执行的事件 + */ + equipEvent: MotaEvent; + + /** + * 脱下装备时执行的事件 + */ + unequipEvent: MotaEvent; +} + +/** + * 道具相关的内容 + */ +interface Items { + /** + * 获得所有道具 + */ + getItems(): { + [P in AllIdsOf<'items'>]: Item

; + }; + + /** + * 执行即捡即用类的道具获得时的效果 + * @example core.getItemEffect('redPotion', 10) // 执行获得10瓶红血的效果 + * @param itemId 道具id + * @param itemNum 道具数量,默认为1 + */ + getItemEffect(itemId: AllIdsOf<'items'>, itemNum?: number): void; + + /** + * 即捡即用类的道具获得时的额外提示 + * @example core.getItemEffectTip(redPotion) // (获得 红血瓶)',生命+100' + * @param itemId 道具id + * @returns 图块属性itemEffectTip的内容 + */ + getItemEffectTip(itemId: AllIdsOf<'items'>): string; + + /** + * 使用一个道具 + * @example core.useItem('pickaxe', true) // 使用破墙镐,不计入录像,无回调 + * @param itemId 道具id + * @param noRoute 是否不计入录像,快捷键使用的请填true,否则可省略 + * @param callback 道具使用完毕或使用失败后的回调函数,好像没什么意义吧( + */ + useItem( + itemId: ItemIdOf<'tools' | 'constants'>, + noRoute?: boolean, + callback?: () => void + ): void; + + /** + * 检查能否使用某种道具 + * @example core.canUseItem('pickaxe') // 能否使用破墙镐 + * @param itemId 道具id + * @returns true表示可以使用 + */ + canUseItem(itemId: AllIdsOf<'items'>): boolean; + + /** + * 统计某种道具的持有量 + * @example core.itemCount('yellowKey') // 持有多少把黄钥匙 + * @param itemId 道具id + * @returns 该种道具的持有量,不包括已穿戴的装备 + */ + itemCount(itemId: AllIdsOf<'items'>): number; + + /** + * 检查主角是否持有某种道具(不包括已穿戴的装备) + * @example core.hasItem('yellowKey') // 主角是否持有黄钥匙 + * @param itemId 道具id + * @returns true表示持有 + */ + hasItem(itemId: AllIdsOf<'items'>): boolean; + + /** + * 检查主角是否穿戴着某件装备 + * @example core.hasEquip('sword5') // 主角是否装备了神圣剑 + * @param itemId 装备id + * @returns true表示已装备 + */ + hasEquip(itemId: ItemIdOf<'equips'>): boolean; + + /** + * 检查主角某种类型的装备目前是什么 + * @example core.getEquip(1) // 主角目前装备了什么盾牌 + * @param equipType 装备类型,自然数 + * @returns 装备id,null表示未穿戴 + */ + getEquip(equipType: number): ItemIdOf<'equips'> | null; + + /** + * 设置某种道具的持有量 + * @example core.setItem('yellowKey', 3) // 设置黄钥匙为3把 + * @param itemId 道具id + * @param itemNum 新的持有量,可选,自然数,默认为0 + */ + setItem(itemId: AllIdsOf<'items'>, itemNum?: number): void; + + /** + * 静默增减某种道具的持有量 不会更新游戏画面或是显示提示 + * @example core.addItem('yellowKey', -2) // 没收两把黄钥匙 + * @param itemId 道具id + * @param itemNum 增加量,负数表示减少 + */ + addItem(itemId: AllIdsOf<'items'>, itemNum?: number): void; + + /** + * @deprecated 使用addItem代替。 + * 删除某个物品一定的数量,相当于addItem(itemId, -n); + * @param itemId 道具id + * @param itemNum 减少量,负数表示增加 + */ + removeItem(itemId?: AllIdsOf<'items'>, itemNum?: number): void; + + /** + * 根据类型获得一个可用的装备孔 + * @param equipId 道具名称 + */ + getEquipTypeByName(name?: ItemIdOf<'equips'>): number; + + /** + * 判定某件装备的类型 + * @example core.getEquipTypeById('shield5') // 1(盾牌) + * @param equipId 装备id + * @returns 类型编号,自然数 + */ + getEquipTypeById(equipId: ItemIdOf<'equips'>): number; + + /** + * 检查能否穿上某件装备 + * @example core.canEquip('sword5', true) // 主角可以装备神圣剑吗,如果不能会有提示 + * @param equipId 装备id + * @param hint 无法穿上时是否提示(比如是因为未持有还是别的什么原因) + * @returns true表示可以穿上,false表示无法穿上 + */ + canEquip(equipId: ItemIdOf<'equips'>, hint?: boolean): boolean; + + /** + * 尝试穿上某件背包里的装备并提示 + * @example core.loadEquip('sword5') // 尝试装备上背包里的神圣剑,无回调 + * @param equipId 装备id + * @param callback 穿戴成功或失败后的回调函数 + */ + loadEquip(equipId: ItemIdOf<'equips'>, callback?: () => void): void; + + /** + * 脱下某个类型的装备 + * @example core.unloadEquip(1) // 卸下盾牌,无回调 + * @param equipType 装备类型编号,自然数 + * @param callback 卸下装备后的回调函数 + */ + unloadEquip(equipType: number, callback?: () => void): void; + + /** + * 比较两件(类型可不同)装备的优劣 + * @example core.compareEquipment('sword5', 'shield5') // 比较神圣剑和神圣盾的优劣 + * @param compareEquipId 装备甲的id + * @param beComparedEquipId 装备乙的id + * @returns 两装备的各属性差,甲减乙,0省略 + */ + compareEquipment>( + compareEquipId: F, + beComparedEquipId: Exclude, F> + ): EquipBase; + + /** + * 保存当前套装 + * @example core.quickSaveEquip(1) // 将当前套装保存为1号套装 + * @param index 套装编号,自然数 + */ + quickSaveEquip(index: number): void; + + /** + * 快速换装 + * @example core.quickLoadEquip(1) // 快速换上1号套装 + * @param index 套装编号,自然数 + */ + quickLoadEquip(index: number): void; + + /** + * 设置某个装备的属性并计入存档 + * @example core.setEquip('sword1', 'value', 'atk', 300, '+='); // 设置铁剑的攻击力数值再加300 + * @param equipId 装备id + * @param valueType 增幅类型,只能是value(数值)或percentage(百分比) + * @param name 要修改的属性名称,如atk + * @param value 要修改到的属性数值 + * @param operator 操作符,如+=表示在原始值上增加 + * @param prefix 独立开关前缀,一般不需要 + */ + setEquip( + equipId: ItemIdOf<'equips'>, + valueType: 'value' | 'percentage', + name: keyof SelectType, + value: number, + operator?: MotaOperator, + prefix?: string + ): void; +} + +declare const items: new () => Items; diff --git a/project/types/loader.d.ts b/project/types/loader.d.ts new file mode 100644 index 0000000..ae8f95a --- /dev/null +++ b/project/types/loader.d.ts @@ -0,0 +1,65 @@ +/** + * 负责资源的加载 + */ +interface Loader { + /** + * 加载一系列图片 + * @param dir 图片所在目录 + * @param names 图片名称列表 + * @param toSave 要保存到的对象 + * @param callback 加载完毕后的回调函数 + */ + loadImages( + dir: string, + names: string[], + toSave: Record, + callback?: () => void + ): void; + + /** + * 加载某一张图片 + * @param dir 图片所在目录 + * @param imgName 图片名称 + * @param callback 加载完毕的回调函数 + */ + loadImage(dir: string, imgName: string, callback?: () => void): void; + + /** + * 从zip中加载一系列图片 + * @param url 图片所在目录 + * @param names 图片名称列表 + */ + loadImagesFromZip( + url: string, + names: string, + toSave: Record, + onprogress?: (loaded: number, total: number) => void, + onfinished?: () => void + ): void; + + /** + * 加载一个音乐 + * @param name 要加载的音乐的名称 + */ + loadOneMusic(name: BgmIds): void; + + /** + * 加载一个音效 + * @param name 要加载的音效的名称 + */ + loadOneSound(name: SoundIds): void; + + /** + * 加载一个bgm + * @param name 加载的bgm的id或名称 + */ + loadBgm(name: BgmIds | NameMapIn): void; + + /** + * 释放一个bgm的缓存 + * @param name 要释放的bgm的id或名称 + */ + freeBgm(name: BgmIds | NameMapIn): void; +} + +declare const loader: new () => Loader; diff --git a/project/types/map.d.ts b/project/types/map.d.ts new file mode 100644 index 0000000..42a05aa --- /dev/null +++ b/project/types/map.d.ts @@ -0,0 +1,1349 @@ +type NotCopyPropertyInCompressedMap = + | 'firstArrive' + | 'eachArrive' + | 'blocks' + | 'parallelDo' + | 'map' + | 'bgmap' + | 'fgmap' + | 'events' + | 'changeFloor' + | 'beforeBattle' + | 'afterBattle' + | 'afterGetItem' + | 'afterOpenDoor' + | 'cannotMove' + | 'cannotMoveIn'; + +/** + * 压缩后的地图 + */ +type CompressedMap = Omit< + Floor, + NotCopyPropertyInCompressedMap +>; + +interface Block { + /** + * 横坐标 + */ + x: number; + + /** + * 纵坐标 + */ + y: number; + + /** + * 图块数字 + */ + id: N; + + /** + * 事件信息 + */ + event: { + /** + * 图块类型 + */ + cls: ClsOf; + + /** + * 图块id + */ + id: NumberToId[N]; + + /** + * 图块动画帧数 + */ + animate: FrameOf>; + + /** + * 图块是否不可通行 + */ + nopass: boolean; + + /** + * 图块高度 + */ + height: 32 | 48; + + /** + * 触发器 + */ + trigger?: MotaTrigger; + + /** + * 是否可被破 + */ + canBreak?: boolean; + + /** + * 门信息 + */ + doorInfo?: DoorInfo; + }; +} + +interface FloorBase { + /** + * 楼层id + */ + floorId: T; + + /** + * 楼层在状态栏的名称 + */ + name: string; + + /** + * 地图宝石倍率 + */ + ratio: number; + + /** + * 地图的宽度 + */ + width: number; + + /** + * 地图的高度 + */ + height: number; + + /** + * 地板样式 + */ + defaultGround: AllIds; + + /** + * 楼层贴图 + */ + image: FloorAnimate[]; + + /** + * 楼层名称 + */ + title: string; + + /** + * 是否能飞出 + */ + canFlyFrom: boolean; + + /** + * 是否能飞到 + */ + canFlyTo: boolean; + + /** + * 是否能使用快捷商店 + */ + canUseQuickShop: boolean; + + /** + * 是否不可浏览 + */ + cannotViewMap?: boolean; + + /** + * 是否是地下层 + */ + underGround?: boolean; + + /** + * 自动事件 + */ + autoEvent: Record; + + /** + * 天气 + */ + weather?: [type: string, level: WeatherLevel]; + + /** + * 事件层地图 + */ + map: number[][]; + + /** + * 并行脚本 + */ + parallelDo?: string; + + /** + * 色调 + */ + color?: Color; + + /** + * 背景音乐 + */ + bgm?: BgmIds | BgmIds[]; +} + +interface Floor extends FloorBase { + /** + * 图块信息 + */ + blocks: Block[]; +} + +interface ResolvedFloor extends FloorBase { + /** + * 战后事件 + */ + afterBattle: Record; + + /** + * 获得道具后事件 + */ + afterGetItem: Record; + + /** + * 开门后事件 + */ + afterOpenDoor: Record; + + /** + * 战前事件 + */ + beforeBattle: Record; + + /** + * 不可出方向 + */ + cannotMove: Record; + + /** + * 不可入方向 + */ + cannotMoveIn: Record; + + /** + * 普通事件 + */ + events: Record; + + /** + * 背景层 + */ + bgmap: number[][]; + + /** + * 前景层 + */ + fgmap: number[][]; + + /** + * 楼层切换 + */ + changeFloor: Record; + + /** + * 首次到达事件 + */ + firstArrive?: MotaEvent; + + /** + * 每次到达事件 + */ + eachArrive?: MotaEvent; +} + +interface BlockInfo { + /** + * 图块数字 + */ + number: T; + + /** + * 图块id + */ + id: NumberToId[T]; + + /** + * 图块类型 + */ + cls: ClsOf; + + /** + * 图块名称 + */ + name: string; + + /** + * 图片信息 + */ + image: HTMLImageElement; + + /** + * 图块所在图片的横坐标 + */ + posX: number; + + /** + * 图块所在图片的纵坐标 + */ + posY: number; + + /** + * 门信息 + */ + doorInfo: DoorInfo; + + /** + * 图片的高度 + */ + height: 32 | 48; + + /** + * faceId信息 + */ + faceIds: Record; + + /** + * 动画帧数 + */ + animate: FrameOf>; + + /** + * 朝向 + */ + face: Dir; + + /** + * 大怪物信息 + */ + bigImage: HTMLImageElement; +} + +interface DrawMapConfig { + /** + * 是否是重绘 + */ + redraw: boolean; + + /** + * 要绘制到的画布 + */ + ctx: CtxRefer; + + /** + * 是否是在地图画布上绘制的 + */ + onMap: boolean; +} + +interface DrawThumbnailConfig { + /** + * 勇士的位置 + */ + heroLoc: LocArr; + + /** + * 勇士的图标 + */ + heroIcon: ImageIds; + + /** + * 是否绘制显伤 + */ + damage: boolean; + + /** + * 变量信息,存读档时使用,可以无视 + */ + flags: Flags; + + /** + * 绘制到的画布 + */ + ctx: CtxRefer; + + /** + * 绘制位置的横坐标 + */ + x: number; + + /** + * 绘制位置的纵坐标 + */ + y: number; + + /** + * 绘制大小,比例数字,例如1代表与实际地图大小相同 + */ + size: number; + + /** + * 绘制全图 + */ + all: boolean; + + /** + * 绘制的视野中心横坐标 + */ + centerX: number; + + /** + * 绘制的视野中心纵坐标 + */ + centerY: number; + + /** + * 是否不是高清画布,存读档时使用,可以无视 + */ + noHD: boolean; + + /** + * 是否使用v2优化 + */ + v2: boolean; +} + +interface BlockFilter { + /** + * 高斯模糊 + */ + blur: number; + + /** + * 色相旋转 + */ + hue: number; + + /** + * 饱和度 + */ + grayscale: number; + + /** + * 反色 + */ + invert: boolean; + + /** + * 阴影 + */ + shadow: boolean; +} + +interface SearchedBlock { + /** + * 横坐标 + */ + x: number; + + /** + * 纵坐标 + */ + y: number; + + /** + * 楼层id + */ + floorId: FloorIds; + + /** + * 图块信息 + */ + block: Block; +} + +/** + * 负责一切和地图相关的处理内容 + */ +interface Maps { + /** + * 图块信息 + */ + blocksInfo: { + [P in keyof NumberToId]: MapDataOf

; + }; + + /** + * 加载某个楼层 + * @param floorId 楼层id + * @param map 地图信息,不填表示直接从原地图中获取 + */ + loadFloor( + floorId: T, + map?: number[][] | { map: number[][] } + ): ResolvedFloor; + + /** + * 解析地图信息 + * @param map 地图id或地图对象 + */ + extractBlocks(map?: FloorIds | ResolvedFloor): void; + + /** + * 根据需求为UI解析出blocks + * @param map 地图信息 + * @param flags 变量信息 + */ + extractBlocksForUI(map?: ResolvedFloor, flags?: Record): void; + + /** + * 根据图块id得到数字(地图矩阵中的值) + * @example core.getNumberById('yellowWall'); // 1 + * @param id 图块id + * @returns 图块的数字 + */ + getNumberById(id: T): IdToNumber[T]; + + /** + * 根据数字获得图块 + * @param number 图块数字 + */ + getBlockByNumber(number: T): Block; + + /** + * 根据ID获得图块 + * @param id 图块的id + */ + getBlockById(id: T): Block; + + /** + * 获得当前事件点的ID + */ + getIdOfThis(id?: 'this' | AllIds): string; + + /** + * 初始化一个图块 + * @param x 横坐标 + * @param y 纵坐标 + * @param id 图块的id + * @param addInfo 是否添加触发器信息等 + * @param eventFloor 所在地图信息 + */ + initBlock( + x: number, + y: number, + id: AllIds | AllNumbers, + addInfo?: boolean, + eventFloor?: ResolvedFloor + ): Block; + + /** + * 压缩地图 + * @param mapArr 地图数组 + * @param floorId 地图id + */ + compressMap(mapArr?: number[][], floorId?: FloorIds): number[][]; + + /** + * 设置图块的不透明度 + * @param opacity 不透明度 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 楼层id + */ + setBlockOpacity( + opacity: number, + x: number, + y: number, + floorId?: FloorIds + ): void; + + /** + * 设置图块的滤镜 + * @param filter 滤镜信息 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 楼层id + */ + setBlockFilter( + filter: string, + x: number, + y: number, + floorId?: FloorIds + ): void; + + /** + * 获取某个图块是否被强制启用或禁用 + * @param floorId 楼层id + * @param x 横坐标 + * @param y 纵坐标 + * @param flags 变量信息 + */ + isMapBlockDisabled( + floorId: FloorIds, + x: number, + y: number, + flags?: Record + ); + + /** + * 设置某个点的图块强制启用/禁用状态 + * @param floorId 楼层id + * @param x 横坐标 + * @param y 纵坐标 + * @param disable 是否禁用 + */ + setMapBlockDisabled( + floorId: FloorIds, + x: number, + y: number, + disable: boolean + ); + + /** + * 解压缩地图 + * @param mapArr 地图信息 + * @param floorId 地图id + */ + decompressMap(mapArr?: number[][], floorId: FloorIds): number[][]; + + /** + * 将所有地图重新变成数字,以便于存档 + */ + saveMap(): { [P in FloorIds]?: Partial> }; + /** + * 将某个地图重新变成数字,以便于存档 + */ + saveMap(floorId: FloorIds): Partial; + + /** + * 将存档中的地图信息重新读取出来 + * @param data 多个楼层的信息 + * @param floorId 在这里没有用 + * @param flags 变量信息 + */ + loadMap( + data: { [P in T]: Partial> }, + floorId?: undefined, + flags?: Record + ): { [P in T]: ResolvedFloor }; + /** + * 加载某个楼层的信息 + * @param data 多个楼层的信息 + * @param floorId 加载的楼层 + */ + loadMap( + data: { [P in T]?: Partial> }, + floorId: T + ): ResolvedFloor; + + /** + * 更改地图画布的尺寸 + * @param floorId 楼层id + */ + resizeMap(floorId?: FloorIds): void; + + /** + * 生成事件层矩阵 + * @example core.getMapArray('MT0'); // 生成主塔0层的事件层矩阵,隐藏的图块视为0 + * @param floorId 地图id,不填视为当前地图 + * @param noCache 是否清空缓存 + * @returns 事件层矩阵,注意对其阵元的访问是[y][x] + */ + getMapArray(floorId?: FloorIds, noCache?: boolean): AllNumbers[][]; + + /** + * 获取图块的事件层数字 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 楼层id + * @param noCache 是否清空缓存 + */ + getMapNumber( + x: number, + y: number, + floorId?: FloorIds, + noCache?: boolean + ): AllNumbers; + + /** + * 以x,y的形式返回每个点的事件 + * @param floorId 楼层id + * @param noCache 是否不使用缓存 + */ + getMapBlocksObj( + floorId?: FloorIds, + noCache?: boolean + ): Record; + + /** + * 获取背景层的图块矩阵 + * @param floorId 楼层id + */ + getBgMapArray(floorId: FloorIds): AllNumbers[][]; + + /** + * 获取前景层的图块矩阵 + * @param floorId 楼层id + */ + getFgMapArray(floorId: FloorIds): AllNumbers[][]; + + /** + * 判定背景层的一个位置是什么 + * @example core.getBgNumber(); // 判断主角脚下的背景层图块的数字 + * @param x 横坐标,不填为当前勇士坐标 + * @param y 纵坐标,不填为当前勇士坐标 + * @param floorId 地图id,不填视为当前地图 + * @param noCache 是否不使用缓存 + */ + getBgNumber( + x?: number, + y?: number, + floorId?: FloorIds, + noCache?: boolean + ): AllNumbers; + + /** + * 判定前景层的一个位置是什么 + * @example core.getFgNumber(); // 判断主角脚下的前景层图块的数字 + * @param x 横坐标,不填为当前勇士坐标 + * @param y 纵坐标,不填为当前勇士坐标 + * @param floorId 地图id,不填视为当前地图 + * @param noCache 是否不使用缓存 + */ + getFgNumber( + x?: number, + y?: number, + floorId?: FloorIds, + noCache?: boolean + ): AllNumbers; + + /** + * 可通行性判定 + * @example core.generateMovableArray(); // 判断当前地图主角从各点能向何方向移动 + * @param floorId 地图id,不填视为当前地图 + * @returns 从各点可移动方向的三维数组 + */ + generateMovableArray(floorId?: FloorIds): Dir[][][]; + + /** + * 单点单朝向的可通行性判定,不判断nopass + * @exmaple core.canMoveHero(); // 判断主角是否可以前进一步 + * @param x 起点横坐标,不填视为主角当前的 + * @param y 起点纵坐标,不填视为主角当前的 + * @param direction 移动的方向,不填视为主角面对的方向 + * @param floorId 地图id,不填视为当前地图 + * @returns true表示可移动,false表示不可移动 + */ + canMoveHero( + x?: number, + y?: number, + direction?: Dir, + floorId?: FloorIds + ): boolean; + + /** + * 能否瞬移到某点,并求出节约的步数。 + * @example core.canMoveDirectly(0, 0); // 能否瞬移到地图左上角 + * @param destX 目标点的横坐标 + * @param destY 目标点的纵坐标 + * @returns 正数表示节约的步数,-1表示不可瞬移 + */ + canMoveDirectly(destX: number, destY: number): number; + + /** + * 获得某些点可否通行的信息 + * @param locs 目标路径 + * @param canMoveArray 可通行信息 + */ + canMoveDirectlyArray(locs: LocArr[], canMoveArray?: Dir[][][]): number[]; + + /** + * 自动寻路 + * @example core.automaticRoute(0, 0); // 自动寻路到地图左上角 + * @param destX 目标点的横坐标 + * @param destY 目标点的纵坐标 + * @returns 每步走完后主角的loc属性组成的一维数组 + */ + automaticRoute(destX: number, destY: number): DiredLoc[]; + + /** + * 绘制一个图块 + * @param block 要绘制的图块 + * @param animate 绘制图块的第几帧 + * @param ctx 绘制到的画布 + */ + drawBlock(block?: Block, animate?: number, ctx?: CtxRefer): void; + + /** + * 生成groundPattern + * @param floorId 楼层id + */ + generateGroundPattern(floorId?: FloorIds): void; + + /** + * 地图绘制 + * @example core.drawMap(); // 绘制当前地图 + * @param floorId 地图id,不填表示当前楼层 + */ + drawMap(floorId?: FloorIds): void; + + /** + * 重绘地图 + */ + redrawMap(): void; + + /** + * 绘制背景层(含贴图,其与背景层矩阵的绘制顺序可通过复写此函数来改变) + * @example core.drawBg(); // 绘制当前地图的背景层 + * @param floorId 地图id,不填视为当前地图 + * @param config 配置信息 + */ + drawBg(floorId?: FloorIds, config?: Partial): void; + + /** + * 绘制事件层 + * @example core.drawEvents(); // 绘制当前地图的事件层 + * @param floorId 地图id,不填视为当前地图 + * @param blocks 一般不需要 + * @param config 配置信息 + */ + drawEvents( + floorId?: FloorIds, + blocks?: Block[], + config?: Partial + ): void; + + /** + * 绘制前景层(含贴图,其与前景层矩阵的绘制顺序可通过复写此函数来改变) + * @example core.drawFg(); // 绘制当前地图的前景层 + * @param floorId 地图id,不填视为当前地图 + * @param config 配置信息 + */ + drawFg(floorId?: FloorIds, config?: Partial): void; + + /** + * 绘制缩略图 + * @example core.drawThumbnail(); // 绘制当前地图的缩略图 + * @param floorId 地图id,不填视为当前地图 + * @param blocks 一般不需要 + * @param options 额外的绘制项,可以增绘主角位置和朝向、采用不同于游戏中的主角行走图、增绘显伤、提供flags用于存读档 + */ + drawThumbnail( + floorId?: FloorIds, + blocks?: Block[], + options?: Partial + ): void; + + /** + * 判定某个点是否不可被踏入(不基于主角生命值和图块cannotIn属性) + * @example core.noPass(0, 0); // 判断地图左上角能否被踏入 + * @param x 目标点的横坐标 + * @param y 目标点的纵坐标 + * @param floorId 目标点所在的地图id,不填视为当前地图 + */ + noPass(x: number, y: number, floorId?: FloorIds): boolean; + + /** + * 某个点是否存在NPC + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 楼层id + */ + npcExists(x: number, y: number, floorId?: FloorIds): boolean; + + /** + * 某个点是否存在(指定的)地形 + * @param x 横坐标 + * @param y 纵坐标 + * @param id 地形的id + * @param floorId 楼层id + */ + terrainExists( + x: number, + y: number, + id?: AllIdsOf<'terrains'>, + floorId?: FloorIds + ): boolean; + + /** + * 某个点是否存在楼梯 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 楼层id + */ + stairExists(x: number, y: number, floorId?: FloorIds): boolean; + + /** + * 当前位置是否在楼梯边;在楼传平面塔模式下对箭头也有效 + */ + nearStair(): boolean; + + /** + * 某个点是否存在(指定的)怪物 + * @param x 横坐标 + * @param y 纵坐标 + * @param id 怪物的id + * @param floorId 楼层id + */ + enemyExists( + x: number, + y: number, + id?: AllIdsOf<'enemys' | 'enemy48'>, + floorId?: FloorIds + ): boolean; + + /** + * 获得某个点的block + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 楼层id + * @param showDisable 被禁用的图块是否也能被获取 + */ + getBlock( + x: number, + y: number, + floorId?: FloorIds, + showDisable?: boolean + ): Block; + + /** + * 判定某个点的图块id + * @example + * // 一个简单的机关门事件,打败或炸掉这一对绿头怪和红头怪就开门 + * if ( + * core.getBlockId(x1, y1) !== 'greenSlime' && + * core.getBlockId(x2, y2) !== 'redSlime' + * ) + * core.openDoor(x3, y3); + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 地图id,不填视为当前地图 + * @param showDisable 被禁用的图块是否也能被获取 + * @returns 图块id,该点无图块则返回null + */ + getBlockId( + x: number, + y: number, + floorId?: FloorIds, + showDisable?: boolean + ): AllIds | null; + + /** + * 判定某个点的图块数字 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 地图id,不填视为当前地图 + * @param showDisable 被禁用的图块是否也能被获取 + */ + getBlockNumber( + x: number, + y: number, + floorId?: FloorIds, + showDisable?: boolean + ): AllNumbers; + + /** + * 判定某个点的图块类型 + * @example + * // 另一个简单的机关门事件,打败或炸掉这一对不同身高的敌人就开门 + * if (core.getBlockCls(x1, y1) !== 'enemys' && core.getBlockCls(x2, y2) !== 'enemy48') core.openDoor(x3, y3); + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 地图id,不填视为当前地图 + * @param showDisable 被禁用的图块是否也能被获取 + */ + getBlockCls( + x: number, + y: number, + floorId?: FloorIds, + showDisable?: boolean + ): Cls | null; + + /** + * 获取图块的不透明度 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 地图id,不填视为当前地图 + * @param showDisable 被禁用的图块是否也能被获取 + */ + getBlockOpacity( + x: number, + y: number, + floorId?: FloorIds, + showDisable?: boolean + ): number | null; + + /** + * 获取图块的滤镜 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 地图id,不填视为当前地图 + * @param showDisable 被禁用的图块是否也能被获取 + */ + getBlockFilter( + x: number, + y: number, + floorId?: FloorIds, + showDisable?: boolean + ): BlockFilter | null; + + /** + * 获得某个图块或素材的信息 + * @param block 图块信息,可以填图块,数字,id + */ + getBlockInfo( + block?: Block | NumberToId[T] | T + ): BlockInfo; + + /** + * 搜索图块, 支持通配符 + * @example core.searchBlock('*Door'); // 搜索当前地图的所有门 + * @param id 图块id,支持星号表示任意多个(0个起)字符 + * @param floorId 地图id,不填视为当前地图 + * @param showDisable 隐藏点是否计入,true表示计入 + */ + searchBlock( + id: string, + floorId?: FloorIds | FloorIds[], + showDisable?: boolean + ): SearchedBlock[]; + + /** + * 根据给定的筛选函数搜索全部满足条件的图块 + * @example + * // 搜索当前地图的所有门 + * core.searchBlockWithFilter(function (block) { return block.event.id.endsWith('Door'); }); + * @param blockFilter 筛选函数,可接受block输入,应当返回一个boolean值 + * @param floorId 地图id,不填视为当前地图 + * @param showDisable 隐藏点是否计入,true表示计入 + * @returns 一个详尽的数组 + */ + searchBlockWithFilter( + blockFilter: (block: Block) => boolean, + floorId?: FloorIds | FloorIds[], + showDisable?: boolean + ): SearchedBlock[]; + + /** + * 获得某个图块对应行走图朝向向下的那一项的id,如果不存在行走图绑定则返回自身id + * @param block 要获得的图块 + */ + getFaceDownId(block: Block | AllIds | AllNumbers): AllIds; + + /** + * 显示(隐藏或显示的)图块,此函数将被“显示事件”指令和勾选了“不消失”的“移动/跳跃事件”指令(如阻击怪)的终点调用 + * @example core.showBlock(0, 0); // 显示地图左上角的图块 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 地图id,不填视为当前地图 + */ + showBlock(x: number, y: number, floorId?: FloorIds): void; + + /** + * 隐藏一个图块,对应于「隐藏事件」且不删除 + * @example core.hideBlock(0, 0); // 隐藏地图左上角的图块 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 地图id,不填视为当前地图 + */ + hideBlock(x: number, y: number, floorId?: FloorIds): void; + + /** + * 根据图块的索引来隐藏图块 + * @param index 要隐藏的图块的索引 + * @param floorId 地图id + */ + hideBlockByIndex(index: number, floorId?: FloorIds): void; + + /** + * 一次性隐藏多个block + * @param indexes 索引列表 + * @param floorId 地图id + */ + hideBlockByIndexes(indexes: number[], floorId?: FloorIds): void; + + /** + * 删除一个图块,对应于「隐藏事件」并同时删除 + * @example core.removeBlock(0, 0); // 尝试删除地图左上角的图块 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 地图id,不填视为当前地图 + * @returns 有没有删除成功 + */ + removeBlock(x: number, y: number, floorId?: FloorIds): boolean; + + /** + * 根据block的索引(尽可能)删除该块 + * @param index 要删除的图块的索引 + * @param floorId 地图id + */ + removeBlockByIndex(index: number, floorId?: FloorIds): void; + + /** + * 一次性删除多个block + * @param indexes 索引列表 + * @param floorId 地图id + */ + removeBlockByIndexes(indexes: number[], floorId?: FloorIds): void; + + /** + * 显示前景/背景地图 + * @param name 图层名 + * @param loc 要显示的坐标列表 + * @param floorId 楼层id + * @param callback 显示完毕的回调函数 + */ + showBgFgMap( + name: 'bg' | 'bg2' | 'fg' | 'fg2', + loc: LocArr | LocArr[], + floorId?: FloorIds, + callback?: () => void + ): void; + + /** + * 隐藏前景/背景地图 + * @param name 图层名 + * @param loc 要显示的坐标列表 + * @param floorId 楼层id + * @param callback 显示完毕的回调函数 + */ + hideBgFgMap( + name: 'bg' | 'bg2' | 'fg' | 'fg2', + loc: LocArr | LocArr[], + floorId?: FloorIds, + callback?: () => void + ): void; + + /** + * 显示一个楼层贴图 + * @param loc 楼层贴图的位置 + * @param floorId 楼层id + * @param callback 显示完毕后的回调函数 + */ + showFloorImage( + loc?: LocArr | LocArr[], + floorId?: FloorIds, + callback?: () => void + ): void; + + /** + * 隐藏一个楼层贴图 + * @param loc 楼层贴图的位置 + * @param floorId 楼层id + * @param callback 显示完毕后的回调函数 + */ + hideFloorImage( + loc?: LocArr | LocArr[], + floorId?: FloorIds, + callback?: () => void + ): void; + + /** + * 转变图块 + * @example core.setBlock(1, 0, 0); // 把地图左上角变成黄墙 + * @param number 新图块的数字或id + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 地图id,不填视为当前地图 + */ + setBlock( + number: AllIds | AllNumbers | `${AllNumbers}`, + x: number, + y: number, + floorId?: FloorIds + ): void; + + /** + * 动画形式转变某点图块 + * @param number 要转变成的图块的数字或id + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 楼层id + * @param time 转变时间 + * @param callback 转变开始或完成后的回调函数 + */ + animateSetBlock( + number: AllIds | AllNumbers | `${AllNumbers}`, + x: number, + y: number, + floorId?: FloorIds, + time?: number, + callback?: () => void + ): void; + + /** + * 动画形式转变若干点图块 + * @param number 要转变成的图块的数字或id + * @param locs 坐标数组 + * @param floorId 楼层id + * @param time 转变时间 + * @param callback 转变开始或完成后的回调函数 + */ + animateSetBlocks( + number: AllIds | AllNumbers | `${AllNumbers}`, + locs: LocArr | LocArr[], + floorId?: FloorIds, + time?: number, + callback?: () => void + ): void; + + /** + * 某个图块转向 + * @param direction 转向的方向 + * @param x 图块所在横坐标 + * @param y 图块所在纵坐标 + * @param floorId 楼层id + */ + turnBlock( + direction: HeroTurnDir, + x: number, + y: number, + floorId?: FloorIds + ): void; + + /** + * 批量替换图块 + * @example core.replaceBlock(21, 22, core.floorIds); // 把游戏中地上当前所有的黄钥匙都变成蓝钥匙 + * @param fromNumber 旧图块的数字 + * @param toNumber 新图块的数字 + * @param floorId 地图id或其数组,不填视为当前地图 + */ + replaceBlock( + fromNumber: AllNumbers, + toNumber: AllNumbers, + floorId?: FloorIds | FloorIds[] + ): void; + + /** + * 转变图层块 + * @example core.setBgFgBlock('bg', 167, 6, 6); // 把当前地图背景层的中心块改为滑冰 + * @param name 背景还是前景 + * @param number 新图层块的数字或id + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 地图id,不填视为当前地图 + */ + setBgFgBlock( + name: 'bg' | 'fg' | 'bg2' | 'fg2', + number: AllIds | AllNumbers | `${AllNumbers}`, + x: number, + y: number, + floorId?: FloorIds + ): void; + + /** + * 重置地图,注意该功能原则上只用于调试,游戏中不应当出现 + * @param floorId 楼层id或数组 + */ + resetMap(floorId?: FloorIds | FloorIds[]): void; + + /** + * 移动图块 + * @example core.moveBlock(0, 0, ['down']); // 令地图左上角的图块下移一格,用时半秒,再花半秒淡出 + * @param x 起点的横坐标 + * @param y 起点的纵坐标 + * @param steps 步伐数组 + * @param time 单步和淡出用时,单位为毫秒。不填视为半秒 + * @param keep 是否不淡出,true表示不淡出 + * @param callback 移动或淡出后的回调函数 + */ + moveBlock( + x: number, + y: number, + steps: Step[], + time?: number, + keep?: boolean, + callback?: () => void + ): void; + + /** + * 跳跃图块;从V2.7开始不再有音效 + * @example core.jumpBlock(0, 0, 0, 0); // 令地图左上角的图块原地跳跃半秒,再花半秒淡出 + * @param sx 起点的横坐标 + * @param sy 起点的纵坐标 + * @param ex 终点的横坐标 + * @param ey 终点的纵坐标 + * @param time 单步和淡出用时,单位为毫秒。不填视为半秒 + * @param keep 是否不淡出,true表示不淡出 + * @param callback 落地或淡出后的回调函数 + */ + jumpBlock( + sx: number, + sy: number, + ex: number, + ey: number, + time?: number, + keep?: boolean, + callback?: () => void + ): void; + + /** + * 显示/隐藏某个块时的动画效果 + * @param loc 要显示或隐藏的坐标数组 + * @param type 显示还是隐藏还是移除,填数字表示设置不透明度 + * @param time 动画时间 + * @param callback 动画完毕后的回调函数 + */ + animateBlock( + loc: LocArr | LocArr[], + type: 'show' | 'hide' | 'remove' | number, + time: number, + callback?: () => void + ): void; + + /** + * 添加一个全局动画 + * @param block 图块信息 + */ + addGlobalAnimate(block?: Block): void; + + /** + * 删除所有全局动画 + */ + removeGlobalAnimate(): void; + /** + * 删除一个全局动画 + * @param x 横坐标 + * @param y 纵坐标 + */ + removeGlobalAnimate(x?: number, y?: number): void; + + /** + * 绘制UI层的box动画 + */ + drawBoxAnimate(): void; + + /** + * 播放动画,注意即使指定了主角的坐标也不会跟随主角移动,如有需要请使用core.drawHeroAnimate(name, callback)函数 + * @example core.drawAnimate('attack', core.nextX(), core.nextY(), false, core.vibrate); // 在主角面前一格播放普攻动画,动画停止后视野左右抖动1秒 + * @param name 动画文件名,不含后缀 + * @param x 绝对横坐标 + * @param y 绝对纵坐标 + * @param alignWindow 是否是相对窗口的坐标 + * @param callback 动画停止后的回调函数 + * @returns 一个数字,可作为core.stopAnimate()的参数来立即停止播放(届时还可选择是否执行此次播放的回调函数) + */ + drawAnimate( + name: AnimationIds | NameMapIn, + x: number, + y: number, + alignWindow: boolean, + callback?: () => void + ): number; + + /** + * 播放跟随勇士的动画 + * @param name 动画名 + * @param callback 动画停止后的回调函数 + * @returns 一个数字,可作为core.stopAnimate()的参数来立即停止播放(届时还可选择是否执行此次播放的回调函数) + */ + drawHeroAnimate( + name: AnimationIds | NameMapIn, + callback?: () => void + ): number; + + /** + * 获得当前正在播放的所有(指定)动画的id列表 + * @param name 指定名称 + */ + getPlayingAnimates(name?: AnimationIds): number[]; + + /** + * 立刻停止一个动画播放 + * @param id 播放动画的编号,即drawAnimate或drawHeroAnimate返回值,不填则停止所有的 + * @param doCallback 是否执行该动画的回调函数 + */ + stopAnimate(id?: number, doCallback?: boolean): void; +} + +declare const maps: new () => Maps; diff --git a/project/types/plugin.d.ts b/project/types/plugin.d.ts new file mode 100644 index 0000000..1562327 --- /dev/null +++ b/project/types/plugin.d.ts @@ -0,0 +1,191 @@ +// 这里包含所有插件导出的函数及变量声明,声明的函数会在类型标注中标注到core上 + +interface PluginDeclaration { + /** + * 添加函数 例:添加弹出文字,像这个就可以使用core.addPop或core.plugin.addPop调用 + * @param px 弹出的横坐标 + * @param py 弹出的纵坐标 + * @param value 弹出的文字 + */ + addPop(px: number, py: number, value: string): void; + + /** + * 添加变量 例:所有的正在弹出的文字,像这个就可以使用core.plugin.pop获取 + */ + pop: any[]; +} + +type Forward = { + [K in keyof T as T[K] extends Function + ? K extends `_${string}` + ? never + : K + : never]: T[K]; +}; + +type ForwardKeys = keyof Forward; + +type SpriteMouseEvent = (px: number, py: number) => void; + +type SpritKeyEvent = ( + key: string, + keyCode: number, + altKey: boolean, + ctrlKey: boolean, + shiftKey: boolean +) => void; + +type SpriteWheelEvent = ( + deltaY: number, + deltaX: number, + deltaZ: number +) => void; + +type SpriteTouchEvent = (...locs: [number, number][]) => void; + +interface SpriteEvent { + click: SpriteMouseEvent; + mousedown: SpriteMouseEvent; + mouseup: SpriteMouseEvent; + mouseenter: SpriteMouseEvent; + mouseleave: SpriteMouseEvent; + mousemove: SpriteMouseEvent; + keydown: SpritKeyEvent; + keyup: SpritKeyEvent; + keypress: SpritKeyEvent; + wheel: SpriteWheelEvent; + touchstart: SpriteTouchEvent; + touchend: SpriteTouchEvent; + touchmove: SpriteTouchEvent; + touchcancel: SpriteTouchEvent; +} + +declare class Sprite { + /** + * 递增的sprite标识符 + */ + static count: number; + + /** + * sprite的左上角横坐标 + */ + x: number; + + /** + * sprite的左上角纵坐标 + */ + y: number; + + /** + * sprite的宽度 + */ + width: number; + + /** + * sprite的高度 + */ + height: number; + + /** + * sprite的纵深 + */ + zIndex: number; + + /** + * sprite的相对模式,相对游戏还是相对窗口 + */ + reference: 'game' | 'window'; + + /** + * sprite的canvas元素 + */ + canvas: HTMLCanvasElement; + + /** + * sprite的context + */ + context: CanvasRenderingContext2D; + + /** + * sprite的名称 + */ + name: string; + + /** + * spirte的唯一标识符 + */ + readonly count: number; + + /** + * 创建一个sprite画布 + * @param reference 参考系,游戏画面或者窗口 + * @param name 可选,sprite的名称,方便通过core.dymCanvas获取 + */ + constructor( + x: number, + y: number, + w: number, + h: number, + z: number, + reference?: 'game' | 'window', + name?: string + ); + + /** + * 初始化 + */ + init(): void; + + /** + * @deprecated + * 设置css特效,使用css()代替 + */ + setCss(css: string): Sprite; + + /** + * 设置css特效 + */ + css(css: string): Sprite; + + /** + * 移动sprite + * @param isDelta 是否是相对位置,如果是,那么sprite会相对于原先的位置进行移动 + */ + move(x: number, y: number, isDelta?: boolean): Sprite; + + /** + * 重新设置sprite的大小 + * @param {boolean} styleOnly 是否只修改css效果,如果是,那么将会不高清,如果不是,那么会清空画布 + */ + resize(w: number, h: number, styleOnly?: boolean): Sprite; + + /** + * 旋转画布 + */ + rotate(angle: number, cx?: number, cy?: number): Sprite; + + /** + * 擦除画布 + */ + clear(x?: number, y?: number, w?: number, h?: number): Sprite; + + /** + * 删除 + */ + destroy(): void; + + /** + * 监听事件,与registerAction类似 + */ + on(type: K, handler: SpriteEvent[K]): void; + + /** + * 添加事件监听器 + */ + addEventListener: HTMLCanvasElement['addEventListener']; + + /** + * 删除事件监听器 + */ + removeEventListenr: HTMLCanvasElement['removeEventListener']; +} diff --git a/project/types/source.d.ts b/project/types/source.d.ts new file mode 100644 index 0000000..4fbcebd --- /dev/null +++ b/project/types/source.d.ts @@ -0,0 +1,98 @@ +/** + * 图块类型 + */ +type Cls = + | 'autotile' + | 'animates' + | 'enemys' + | 'items' + | 'npcs' + | 'terrains' + | 'enemy48' + | 'npc48' + | 'tilesets'; + +/** + * 所有的可动画图块类型 + */ +type AnimatableCls = Exclude; + +/** + * 道具类型 + */ +type ItemCls = 'tools' | 'items' | 'equips' | 'constants'; + +/** + * 所有的道具id + */ +type AllIds = keyof IdToNumber; + +/** + * 所有的道具数字 + */ +type AllNumbers = keyof NumberToId | 0; + +/** + * 某种类型的图块的id + */ +type AllIdsOf = keyof { + [P in keyof IdToCls as IdToCls[P] extends T ? P : never]: P; +}; + +/** + * 某种类型的道具的id + */ +type ItemIdOf = keyof { + [P in keyof ItemDeclaration as ItemDeclaration[P] extends T ? P : never]: P; +}; + +/** + * 某个道具的类型 + */ +type ItemClsOf> = ItemDeclaration[T]; + +/** + * 获取某个图块的类型 + */ +type ClsOf = IdToCls[T]; + +/** + * 某种类型的图块数字 + */ +type AllNumbersOf = IdToNumber[AllIdsOf]; + +/** + * 选取在一段字符串中的映射名称 + */ +type NameMapIn = keyof { + [P in keyof NameMap as NameMap[P] extends T ? P : never]: NameMap[P]; +}; + +/** + * 所有的怪物id + */ +type EnemyIds = AllIdsOf<'enemys' | 'enemy48'>; + +/** + * 各种图块的动画数量 + */ +interface FrameNumbers { + autotile: 4; + animates: 4; + enemys: 2; + items: 1; + npcs: 2; + terrains: 1; + enemy48: 4; + npc48: 4; +} + +/** + * 动画帧数 + */ +type FrameOf = FrameNumbers[T]; + +/** + * 所有的文件名 + */ +type SourceIds = ImageIds | AnimationIds | SoundIds | BgmIds | FontIds; diff --git a/project/types/status.d.ts b/project/types/status.d.ts new file mode 100644 index 0000000..9cfce80 --- /dev/null +++ b/project/types/status.d.ts @@ -0,0 +1,963 @@ +/** + * 怪物buff缓存 + */ +interface EnemyBuffCache { + /** + * 生命值提升量 + */ + hp_buff: number; + + /** + * 攻击提升量 + */ + atk_buff: number; + + /** + * 防御提升量 + */ + def_buff: number; + + /** + * 支援信息 + */ + guards: [number, number, string][]; +} + +interface CheckBlockStatus { + /** + * 捕捉信息 + */ + ambush: Record; + + /** + * 阻击信息 + */ + repulse: Record; + + /** + * 每点的伤害,小于等于0会不显示 + */ + damage: Record; + + /** + * 是否需要重算 + */ + needCache: boolean; + + /** + * 每点的伤害类型 + */ + type: Record>; + + /** + * 缓存信息,是每个怪物受到的光环加成 + */ + cache: Record>; +} + +interface DamageStatus { + /** + * v2优化下当前的偏移横坐标,单位格子 + */ + posX: number; + + /** + * v2优化下当前的偏移纵坐标,单位格子 + */ + posY: number; + + /** + * 显示的伤害信息 + */ + data: DamageStatusData[]; + + /** + * 地图伤害或其它在地图上显示的文字 + */ + extraData: DamageStatusExtraData[]; +} + +interface DamageStatusData { + /** + * 显示的文字 + */ + text: string; + + /** + * 显示横坐标,单位像素 + */ + px: number; + + /** + * 显示纵坐标,单位像素 + */ + py: number; + + /** + * 文字的颜色 + */ + color: Color; +} + +interface DamageStatusExtraData extends DamageStatusData { + /** + * 文字的不透明度 + */ + alpha: number; +} + +interface AutomaticRouteStatus { + /** + * 勇士是否正在移动 + */ + autoHeroMove: boolean; + + /** + * 不太清楚用处 + */ + autoStep: number; + + /** + * 自动寻路中已经走过的步数 + */ + movedStep: number; + + /** + * 自动寻路的总步数 + */ + destStep: number; + + /** + * 自动寻路的目标横坐标 + */ + destX: number; + + /** + * 自动寻路的目标纵坐标 + */ + destY: number; + + /** + * 自动寻路绘图时的偏移横坐标,单位像素 + */ + offsetX: number; + + /** + * 自动寻路绘图时的偏移纵坐标,单位像素 + */ + offsetY: number; + + /** + * 自动寻路的路线 + */ + autoStepRoutes: AutoStep[]; + + /** + * 剩下的自动寻路路线 + */ + moveStepBeforeStop: AutoStep[]; + + /** + * 上一步的勇士方向 + */ + lastDirection: Dir; + + /** + * 光标界面(按下E时的界面)的光标横坐标 + */ + cursorX: number; + + /** + * 光标界面(按下E时的界面)的光标纵坐标 + */ + cursorY: number; + + /** + * 是否瞬间移动 + */ + moveDirectly: boolean; +} + +interface AutoStep { + /** + * 当前步的步数 + */ + step: number; + + /** + * 当前步走向的方向 + */ + direction: Dir; +} + +interface ReplaySaveBase { + /** + * 录像播放时,剩下的要播放的录像内容 + */ + toReplay: string[]; + + /** + * 录像播放时,录像的完整信息 + */ + totalList: string[]; + + /** + * 不太清楚用处,应该是与录像回退有关的 + */ + steps: number; +} + +interface ReplayStatus extends ReplaySaveBase { + /** + * 当前是否正在播放录像,同core.isReplaying() + */ + replaying: boolean; + + /** + * 当前录像有没有暂停 + */ + pausing: boolean; + + /** + * 当前是否正在某段动画中 + */ + animate: boolean; + + /** + * 录像播放是否失败 + */ + failed: boolean; + + /** + * 当前的录像播放速度 + */ + speed: number; + + /** + * 录像的回退信息 + */ + save: ReplaySave[]; +} + +interface ReplaySave { + /** + * 回退的存档信息 + */ + data: Save; + + /** + * 回退的录像信息 + */ + replay: ReplaySaveBase; +} + +interface TextAttribute { + /** + * 文本框的位置 + */ + position: TextPosition; + + /** + * 文本的左右对齐方式 + */ + align: 'left' | 'center' | 'right'; + + /** + * 偏移像素 + */ + offset: number; + + /** + * 标题颜色 + */ + title: RGBArray; + + /** + * 背景颜色 + */ + background: RGBArray | ImageIds; + + /** + * 文字颜色 + */ + text: RGBArray; + + /** + * 标题字体大小 + */ + titlefont: number; + + /** + * 正文字体大小 + */ + textfont: number; + + /** + * 是否加粗 + */ + bold: boolean; + + /** + * 打字机时间,每隔多少毫秒显示一个字 + */ + time: number; + + /** + * 字符间距 + */ + letterSpacing: number; + + /** + * 淡入淡出时间 + */ + animateTime: number; + + /** + * 行距 + */ + lineHeight: number; +} + +interface StatusStyle { + /** + * 左侧状态栏背景,css的background属性 + */ + statusLeftBackground: string; + + /** + * 上部状态栏背景,css的background属性 + */ + statusTopBackground: string; + + /** + * 竖屏下的工具栏背景,css的background属性 + */ + toolsBackground: string; + + /** + * 游戏的边框颜色 + */ + borderColor: Color; + + /** + * 状态栏文字的颜色 + */ + statusBarColor: Color; + + /** + * 楼层切换样式,css字符串 + */ + floorChangingStyle: string; + + /** + * 全局字体 + */ + font: string; +} + +interface GlobalAttribute extends StatusStyle { + /** + * 装备栏名称 + */ + equipName: string[]; +} + +interface FloorAnimate { + /** + * 图片的目标画布 + */ + canvas: 'bg' | 'fg'; + + /** + * 图片的名称 + */ + name: ImageIds; + + /** + * 绘制横坐标 + */ + x: number; + + /** + * 绘制纵坐标 + */ + y: number; + + /** + * 裁剪横坐标 + */ + sx?: number; + + /** + * 裁剪纵坐标 + */ + sy?: number; + + /** + * 裁剪宽度 + */ + w?: number; + + /** + * 裁剪高度 + */ + h?: number; + + /** + * 绘制帧数 + */ + frame?: number; + + /** + * 图片翻转 + */ + reverse?: ':x' | ':y' | ':o'; + + /** + * 是否禁用 + */ + disable?: boolean; +} + +interface BoxAnimate { + /** + * 动画的帧数 + */ + animate: number; + + /** + * 背景的高度 + */ + bgHeight: number; + + /** + * 背景的宽度 + */ + bgWidth: number; + + /** + * 背景的左上角横坐标 + */ + bgx: number; + + /** + * 背景的左上角纵坐标 + */ + bgy: number; + + /** + * 动画图片的高度 + */ + height: 32 | 48; + + /** + * 图片信息 + */ + img: HTMLImageElement; + + /** + * 这个图块的图片在其素材图片的纵坐标 + */ + pos: number; + + /** + * 图块的横坐标 + */ + x: number; + + /** + * 图块的纵坐标 + */ + y: number; +} + +interface BigImageBoxAnimate { + /** + * 大图片的贴图信息 + */ + bigImage: HTMLImageElement; + + /** + * 贴图的朝向 + */ + face: Dir; + + /** + * 中心横坐标 + */ + centerX: number; + + /** + * 中心纵坐标 + */ + centerY: number; + + /** + * 最大宽度 + */ + max_width: number; + + /** + * 绘制到的画布 + */ + ctx: CtxRefer; +} + +interface AnimateObj { + /** + * 动画名称 + */ + name: AnimationIds; + + /** + * 动画的唯一标识符 + */ + id: number; + + /** + * 动画信息 + */ + animate: Animate; + + /** + * 中心横坐标 + */ + centerX: number; + + /** + * 中心纵坐标 + */ + centerY: number; + + /** + * 当前帧数 + */ + index: number; + + /** + * 回调函数 + */ + callback: () => void; +} + +interface ActionsPreview { + /** + * 大地图中当前是否正在拖拽 + */ + dragging: boolean; + + /** + * 大地图中是否允许拖拽 + */ + enabled: boolean; + + /** + * 大地图中当前是否已经按下了鼠标 + */ + prepareDragging: boolean; + + /** + * 当前鼠标的横坐标 + */ + px: number; + + /** + * 当前鼠标的纵坐标 + */ + py: number; +} + +interface RouteFolding { + /** + * 录像折叠信息中的勇士信息 + */ + hero: Omit, 'steps'>; + + /** + * 折叠的长度 + */ + length: number; +} + +/** + * 初始游戏状态 + */ +interface InitGameStatus { + /** + * 是否开始了游戏 + */ + played: false; + + /** + * 游戏是否结束 + */ + gameOver: false; + + /** + * 当前勇士状态信息。例如core.status.hero.atk就是当前勇士的攻击力数值 + */ + hero: {}; + + /** + * 当前层的floorId + */ + floorId: null; + + /** + * 所有楼层的地图信息 + */ + maps: { + [P in FloorIds]: Floor

; + }; + + /** + * 获得当前楼层信息,等价于core.status.maps[core.status.floorId] + */ + thisMap: null; + + /** + * 背景图块 + */ + bgmaps: Record; + + /** + * 前景图块 + */ + fgmaps: Record; + + /** + * 以坐标列举的图块 + */ + mapBlockObjs: Record>; + + /** + * 地图伤害 + */ + checkBlock: {}; + + /** + * 伤害显示信息 + */ + damage: DeepReadonly; + + /** + * 是否锁定了用户控制 + */ + lockControl: false; + + /** + * 勇士移动状态,每个数字干啥的自己去libs翻,这东西太复杂了,不过应该不会有人用这个东西吧( + */ + heroMoving: number; + + /** + * 勇士是否停下了 + */ + heroStop: boolean; + + /** + * 自动寻路状态 + */ + automaticRoute: DeepReadonly; + + /** + * 按键按下的时间,用于判定双击 + */ + downTime: number; + + /** + * ctrl键是否倍按下 + */ + ctrlDown: boolean; + + /** + * 当前录像信息 + */ + route: string[]; + + /** + * 当前的回放状态 + */ + replay: DeepReadonly; + + /** + * 当前的所有全局商店 + */ + shops: Record>; + + /** + * 当前的事件状态,样板最大的败笔之一,离谱到逆天 + */ + event: EventStatusOf; + + /** + * 当前的所有自动事件 + */ + autoEvents: DeepReadonly; + + /** + * 当前的全局剧情文本设置 + */ + textAttribute: TextAttribute; + + /** + * 部分全局属性,会跟随存档 + */ + globalAttribute: GlobalAttribute; + + /** + * 色调的颜色 + */ + curtainColor: Color; + + /** + * 全局动画对象 + */ + globalAnimateObjs: Block>>[]; + + /** + * 楼层贴图 + */ + floorAnimateObjs: FloorAnimate[]; + + /** + * 所有的BoxAnimate信息 + */ + boxAnimateObjs: (BoxAnimate | BigImageBoxAnimate)[]; + + /** + * 所有的自动元件动画 + */ + autotileAnimateObjs: Block>[]; + + /** + * 全局动画状态,每经过一个全局动画时间便加一 + */ + globalAnimateStatus: number; + + /** + * 所有绘制的动画 + */ + animateObjs: AnimateObj[]; + + /** + * 当前难度 + */ + hard: string; + + /** + * 勇士的中心 + */ + heroCenter: Record<'px' | 'py', number>; + + /** + * 当前按下的按键 + */ + holdingKeys: number[]; + + /** + * id转数字 + */ + id2number: IdToNumber; + + /** + * 数字转图块 + */ + number2block: { + [P in AllNumbers]: Block

; + }; + + /** + * 大地图中的拖拽处理 + */ + preview: ActionsPreview; + + /** + * 录像折叠信息 + */ + routeFolding: Record<`${LocString},${FirstCharOf

}`, RouteFolding>; +} + +/** + * 运行时的游戏状态 + */ +interface GameStatus extends InitGameStatus { + played: boolean; + gameOver: boolean; + floorId: FloorIds; + thisMap: ResolvedFloor; + checkBlock: Readonly; + lockControl: boolean; + hero: HeroStatus; +} + +interface Follower { + /** + * 跟随者的图片id + */ + name: ImageIds; +} + +interface HeroStatistics { + /** + * 击败的怪物数量 + */ + battle: number; + + /** + * 由于战斗损失的生命值 + */ + battleDamage: number; + + /** + * 当前游戏时间 + */ + currentTime: number; + + /** + * 获得的总经验值 + */ + exp: number; + + /** + * 由于地图伤害损失的生命值 + */ + extraDamage: number; + + /** + * 总共损失的生命值 + */ + hp: number; + + /** + * 由于瞬移少走的步数 + */ + ignoreSteps: number; + + /** + * 总共获得的金币数 + */ + money: number; + + /** + * 瞬移次数 + */ + moveDirectly: number; + + /** + * 中毒损失的生命值 + */ + poisonDamage: number; + + /** + * 本次游戏的开始时间 + */ + startTime: number; + + /** + * 游戏总时间 + */ + totalTime: number; +} + +/** + * 勇士状态 + */ +interface HeroStatus { + /** + * 勇士停止时及对话框中是否启用帧动画 + */ + animate: boolean; + + /** + * 勇士生命值 + */ + hp: number; + + /** + * 勇士生命上限 + */ + hpmax: number; + + /** + * 勇士的攻击 + */ + atk: number; + + /** + * 勇士的防御 + */ + def: number; + + /** + * 勇士的魔防 + */ + mdef: number; + + /** + * 勇士的等级 + */ + lv: number; + + /** + * 勇士的经验 + */ + exp: number; + + /** + * 勇士的金币 + */ + money: number; + + /** + * 勇士的魔法 + */ + mana: number; + + /** + * 勇士的魔法上限 + */ + manamax: number; + + /** + * 勇士的名称 + */ + name: string; + + /** + * 勇士移动过的步数 + */ + steps: number; + + /** + * 勇士的图片 + */ + image: ImageIds; + + /** + * 当前勇士的装备 + */ + equipment: ItemIdOf<'equips'>[]; + + /** + * 勇士当前的位置 + */ + loc: Loc; + + /** + * 当前的变量 + */ + flags: Flags; + + /** + * 勇士的跟随者 + */ + followers: Follower[]; + + /** + * 勇士拥有的道具 + */ + items: { + [P in Exclude]: Record, number>; + }; +} diff --git a/project/types/ui.d.ts b/project/types/ui.d.ts new file mode 100644 index 0000000..fb301dc --- /dev/null +++ b/project/types/ui.d.ts @@ -0,0 +1,827 @@ +/** + * 可以设置成的画布填充描边样式 + */ +type CanvasStyle = string | CanvasGradient | CanvasPattern; + +type ImageSource = + | CanvasImageSource + | ImageIds + | `${ImageIds}${ImageReverse}` + | NameMapIn + | `${NameMapIn}${ImageReverse}`; + +interface BackgroundPosInfo { + /** + * 横坐标 + */ + px: number; + + /** + * 纵坐标 + */ + py: number; + + /** + * 是否没有位置 + */ + noPeak: boolean; + + /** + * 横坐标偏移值 + */ + xoffset: number; + + /** + * 纵坐标偏移值 + */ + yoffset: number; + + /** + * 画布,默认是ui + */ + ctx: CtxRefer; + + /** + * 箭头指向是朝上还是朝下 + */ + position: 'up' | 'bottom'; +} + +interface TextContentConfig { + left: number; + top: number; + + /** + * 最大宽度 + */ + maxWidth: number; + + /** + * 颜色,不影响\r + */ + color: Color; + + /** + * 对齐方式 + */ + align: 'left' | 'center' | 'right'; + + /** + * 字体大小 + */ + fontSize: number; + + /** + * 行高 + */ + lineHeight: number; + + /** + * 打字机间隔 + */ + time: number; + + /** + * 字体名 + */ + font: string; + + /** + * 字符间距 + */ + letterSpacing: number; + + /** + * 是否加粗 + */ + bold: boolean; + + /** + * 是否斜体 + */ + italic: boolean; +} + +interface TextContentBlock { + left: number; + top: number; + width: number; + height: number; + line: number; + marginLeft: number; + marginTop: number; +} + +interface ReturnedTextContentConfig extends TextContentConfig { + right: number; + + /** + * 默认字体 + */ + defaultFont: string; + + /** + * 当前绘制的文字索引 + */ + index: number; + + /** + * 当前颜色 + */ + currcolor: Color; + + /** + * 当前字体 + */ + currfont: string; + + /** + * 每一行的间距 + */ + lineMargin: number; + + /** + * 每一行间距的一半 + */ + topMargin: number; + + /** + * 横坐标偏移量 + */ + offsetX: number; + + /** + * 纵坐标偏移量 + */ + offsetY: number; + + /** + * 当前行数 + */ + line: number; + + /** + * 所有的文字 + */ + blocks: TextContentBlock[]; + + /** + * 是否是高清画布 + */ + isHD: boolean; + + /** + * 这一行的最大高度 + */ + lineMaxHeight: number; + + /** + * 是否是强制换行 + */ + forceChangeLine: boolean; +} + +interface TextBoxConfig { + /** + * 画布 + */ + ctx: CtxRefer; + + /** + * 对话框位置 + */ + pos: TextBoxPos; + + /** + * 是否一次性全部显示 + */ + showAll: boolean; + + /** + * 是否异步显示 + */ + async: boolean; +} + +/** + * UI窗口的绘制,如对话框、怪物手册、楼传器、存读档界面等 + */ +interface Ui { + /** + * ui数据 + */ + uidata: UiData; + + /** + * 根据画布名找到一个画布的context;支持系统画布和自定义画布。如果不存在画布返回null。 + * 也可以传画布的context自身,则返回自己。 + */ + getContextByName(canvas: CtxRefer): CanvasRenderingContext2D | null; + + /** + * 清空某个画布图层 + * name为画布名,可以是系统画布之一,也可以是任意自定义动态创建的画布名;还可以直接传画布的context本身。(下同) + * 如果name也可以是'all',若为all则为清空所有系统画布。 + */ + clearMap( + name: CtxRefer, + x?: number, + y?: number, + w?: number, + h?: number + ): void; + + /** + * 在某个画布上绘制一段文字 + * @param text 要绘制的文本 + * @param style 绘制的样式 + * @param font 绘制的字体 + * @param maxWidth 文字整体的最大宽度,如果超过会自动缩小文字使其宽度小于这个值 + */ + fillText( + name: CtxRefer, + text: string, + x: number, + y: number, + style?: CanvasStyle, + font?: string, + maxWidth?: number + ): void; + + /** + * 根据最大宽度自动缩小字体 + * @param name 画布 + * @param text 文字 + * @param maxWidth 最大和宽度 + * @param font 字体 + */ + setFontForMaxWidth( + name: CtxRefer, + text: string, + maxWidth: number, + font?: string + ): void; + + /** + * 在某个画布上绘制一个描边文字 + * @param text 要绘制的文本 + * @param style 绘制的样式 + * @param strokeStyle 绘制的描边颜色 + * @param font 绘制的字体 + * @param lineWidth 描边的线宽 + */ + fillBoldText( + name: CtxRefer, + text: string, + x: number, + y: number, + style?: CanvasStyle, + strokeStyle?: CanvasStyle, + font?: string, + maxWidth?: number, + lineWidth?: number + ): void; + + /** + * 绘制一个矩形 + * @param style 绘制的样式 + * @param angle 旋转角度,弧度制 + */ + fillRect( + name: CtxRefer, + x: number, + y: number, + width: number, + height: number, + style?: CanvasStyle, + angle?: number + ): void; + + /** + * 绘制一个矩形的边框 + * @param style 绘制的样式 + * @param angle 旋转角度,单位弧度 + */ + strokeRect( + name: CtxRefer, + x: number, + y: number, + width: number, + height: number, + style?: CanvasStyle, + lineWidth?: number, + angle?: number + ): void; + + /** + * 在某个canvas上绘制一个圆角矩形 + */ + fillRoundRect( + name: CtxRefer, + x: number, + y: number, + width: number, + height: number, + radius: number, + style?: CanvasStyle, + angle?: number + ): void; + + /** + * 在某个canvas上绘制一个圆角矩形的边框 + */ + strokeRoundRect( + name: CtxRefer, + x: number, + y: number, + width: number, + height: number, + radius: number, + style?: CanvasStyle, + lineWidth?: number, + angle?: number + ): void; + + /** + * 在某个canvas上绘制一个多边形 + */ + fillPolygon( + name: CtxRefer, + nodes?: [x: number, y: number][], + style?: CanvasStyle + ): void; + + /** + * 在某个canvas上绘制一个多边形的边框 + */ + strokePolygon( + name: CtxRefer, + nodes?: [x: number, y: number][], + style?: CanvasStyle, + lineWidth?: number + ): void; + + /** + * 在某个canvas上绘制一个椭圆 + * @param a 横轴长度的一半 + * @param b 纵轴长度的一半 + * @param angle 旋转角度 + */ + fillEllipse( + name: CtxRefer, + x: number, + y: number, + a: number, + b: number, + angle?: number, + style?: CanvasStyle + ): void; + + /** + * 在某个canvas上绘制一个圆 + */ + fillCircle( + name: CtxRefer, + x: number, + y: number, + r: number, + style?: CanvasStyle + ): void; + + /** + * 在某个canvas上绘制一个椭圆的边框 + * @param a 横轴长度的一半 + * @param b 纵轴长度的一半 + * @param angle 旋转角度 + */ + strokeEllipse( + name: CtxRefer, + x: number, + y: number, + a: number, + b: number, + angle?: number, + style?: CanvasStyle, + lineWidth?: number + ): void; + + /** + * 在某个canvas上绘制一个圆的边框 + */ + strokeCircle( + name: CtxRefer, + x: number, + y: number, + r: any, + style?: CanvasStyle, + lineWidth?: number + ): void; + + /** + * 在某个canvas上绘制一个扇形 + */ + fillArc( + name: CtxRefer, + x: number, + y: number, + r: number, + start: number, + end: number, + style?: CanvasStyle + ): void; + + /** + * 在某个canvas上绘制一段弧 + */ + strokeArc( + name: CtxRefer, + x: number, + y: number, + r: number, + start: number, + end: number, + style?: CanvasStyle, + lineWidth?: number + ): void; + + /** + * 在某个canvas上绘制一条线 + */ + drawLine( + name: CtxRefer, + x1: number, + y1: number, + x2: number, + y2: number, + style?: CanvasStyle, + lineWidth?: number + ): void; + + /** + * 在某个canvas上绘制一个箭头 + */ + drawArrow( + name: CtxRefer, + x1: number, + y1: number, + x2: number, + y2: number, + style?: CanvasStyle, + lineWidth?: number + ): void; + + /** + * 设置某个canvas的文字字体 + */ + setFont(name: CtxRefer, font: string): void; + + /** + * 设置某个canvas的线宽度 + */ + setLineWidth(name: CtxRefer, lineWidth: number): void; + + /** + * 保存某个canvas状态 + */ + saveCanvas(name: CtxRefer): void; + + /** + * 回退某个canvas状态 + */ + loadCanvas(name: CtxRefer): void; + + /** + * 设置某个canvas的绘制不透明度 + * @returns 之前画布的不透明度 + */ + setAlpha(name: CtxRefer, alpha: number): number; + + /** + * 设置画布元素的不透明度 + */ + setOpacity(name: CtxRefer, opacity: number): void; + + /** + * 设置某个canvas的滤镜 + */ + setFilter(name: CtxRefer, filter?: string): void; + + /** + * 设置某个canvas的填充样式 + */ + setFillStyle(name: CtxRefer, style: CanvasStyle): void; + + /** + * 设置某个canvas描边样式 + */ + setStrokeStyle(name: CtxRefer, style: CanvasStyle): void; + + /** + * 设置某个canvas的文字左右对齐方式 + */ + setTextAlign(name: CtxRefer, align: CanvasTextAlign): void; + + /** + * 设置某个canvas的文字上下对齐方式 + */ + setTextBaseline(name: CtxRefer, baseline: CanvasTextBaseline): void; + + /** + * 计算某段文字的宽度,注意该函数会设置画布的字体 + */ + calWidth(name: CtxRefer, text: string, font?: string): number; + + /** + * 字符串自动换行的分割 + */ + splitLines( + name: CtxRefer, + text: string, + maxWidth?: number, + font?: string + ): string[]; + + /** + * 绘制图片 + * @param dx 绘制的横坐标 + * @param dy 绘制的纵坐标 + */ + drawImage(name: CtxRefer, image: ImageSource, dx: number, dy: number): void; + /** + * 绘制图片 + * @param dx 绘制的横坐标 + * @param dy 绘制的纵坐标 + * @param dw 绘制的宽度 + * @param dh 绘制的高度 + */ + drawImage( + name: CtxRefer, + image: ImageSource, + dx: number, + dy: number, + dw: number, + dh: number + ): void; + /** + * 绘制图片 + * @param sx 裁剪的横坐标 + * @param sy 裁剪的纵坐标 + * @param sw 裁剪的宽度 + * @param sh 裁剪的高度 + * @param dx 绘制的横坐标 + * @param dy 绘制的纵坐标 + * @param dw 绘制的宽度 + * @param dh 绘制的高度 + */ + drawImage( + name: CtxRefer, + image: ImageSource, + sx: number, + sy: number, + sw: number, + sh: number, + dx: number, + dy: number, + dw: number, + dh: number + ): void; + + /** + * 在某个canvas上绘制一个图标 + * @param frame 图标的第几帧 + */ + drawIcon( + name: CtxRefer, + id: AllIds, + x: number, + y: number, + w?: number, + h?: number, + frame?: number + ): void; + + /** + * 结束一切事件和绘制,关闭UI窗口,返回游戏进程 + */ + closePanel(): void; + + /** + * 清空UI层内容 + */ + clearUI(): void; + + /** + * 左上角绘制一段提示 + * @param text 要提示的文字内容 + * @param id 要绘制的图标ID + * @param frame 要绘制图标的第几帧 + */ + drawTip(text: string, id?: AllIds, frame?: number): void; + + /** + * 地图中间绘制一段文字 + */ + drawText(contents: string, callback?: () => void): void; + + /** + * 自绘选择光标 + */ + drawUIEventSelector( + code: number, + background: RGBArray | ImageIds, + x: number, + y: number, + w: number, + h: number, + z?: number + ): void; + + /** + * 清除一个或多个选择光标 + * @param code 要清除的选择光标,不填表示清除所有 + */ + clearUIEventSelector(code?: number | number[]): void; + + /** + * 绘制WindowSkin + * @param direction 指向箭头的方向 + */ + drawWindowSkin( + background: any, + ctx: CtxRefer, + x: number, + y: number, + w: number, + h: number, + direction?: 'up' | 'down', + px?: number, + py?: number + ): void; + + /** + * 绘制一个背景图,可绘制winskin或纯色背景;支持小箭头绘制 + */ + drawBackground( + left: string, + top: string, + right: string, + bottom: string, + posInfo?: Partial + ): void; + + /** + * 绘制一段文字到某个画布上面 + * @param ctx 要绘制到的画布 + * @param content 要绘制的内容;转义字符只允许保留 \n, \r[...], \i[...], \c[...], \d, \e + * @param config 绘制配置项 + * @returns 绘制信息 + */ + drawTextContent( + ctx: CtxRefer, + content: string, + config: Partial + ): ReturnedTextContentConfig; + + /** + * 获得某段文字的预计绘制高度 + */ + getTextContentHeight( + content: string, + config: Partial + ): number; + + /** + * 绘制一个对话框 + */ + drawTextBox(content: string, config?: TextBoxConfig): void; + + /** + * 绘制滚动字幕 + */ + drawScrollText( + content: string, + time?: number, + lineHeight?: number, + callback?: () => void + ): void; + + /** + * 文本图片化 + */ + textImage(content: string, lineHeight?: number): HTMLCanvasElement; + + /** + * 绘制一个选项界面 + */ + drawChoices( + content: string, + choices: string[], + width?: number, + ctx?: CtxRefer + ): void; + + /** + * 绘制一个确认框 + */ + drawConfirmBox( + text: string, + yesCallback?: () => void, + noCallback?: () => void, + ctx?: CtxRefer + ): void; + + /** + * 绘制等待界面 + */ + drawWaiting(text: string): void; + + /** + * 绘制分页 + */ + drawPagination(page: number, totalPage: number, y?: number): void; + + /** + * 绘制怪物手册 + */ + drawBook(index: number): void; + + /** + * 绘制楼层传送器 + */ + drawFly(page: number): void; + + /** + * 获得所有应该在道具栏显示的某个类型道具 + */ + getToolboxItems>(cls: T): ItemIdOf[]; + + /** + * 绘制状态栏 + */ + drawStatusBar(): void; + + /** + * 动态创建一个画布 + * @param name 画布名称,如果已存在则会直接取用当前存在的 + * @param x 横坐标 + * @param y 纵坐标 + * @param width 宽度 + * @param height 高度 + * @param zIndex 纵深 + * @param nonAntiAliasing 是否取消抗锯齿 + */ + createCanvas( + name: string, + x: number, + y: number, + width: number, + height: number, + zIndex?: number, + nonAntiAliasing?: boolean + ): CanvasRenderingContext2D; + + /** + * 重新定位一个自定义画布 + */ + relocateCanvas( + name: string, + x: number, + y: number, + useDelta?: boolean + ): void; + + /** + * 设置一个自定义画布的旋转角度 + */ + rotateCanvas( + name: CtxRefer, + angle: number, + centerX?: number, + centerY?: number + ): void; + + /** + * 重新设置一个自定义画布的大小 + * @param styleOnly 是否只修改style,而不修改元素上的长宽,如果是true,会出现模糊现象 + * @param isTempCanvas 是否是临时画布,如果填true,会将临时画布修改为高清画布 + */ + resizeCanvas( + name: string, + x?: number, + y?: number, + styleOnly?: boolean, + isTempCanvas?: boolean + ): void; + + /** + * 删除一个自定义画布 + */ + deleteCanvas(name: string | ((name: string) => boolean)): void; + + /** + * 清空所有的自定义画布 + */ + deleteAllCanvas(): void; +} + +declare const ui: new () => Ui; diff --git a/project/types/util.d.ts b/project/types/util.d.ts new file mode 100644 index 0000000..a5f21f2 --- /dev/null +++ b/project/types/util.d.ts @@ -0,0 +1,884 @@ +/** 工具类 主要用来进行一些辅助函数的计算 */ +interface Utils { + /** + * 四个方向的坐标增量 + */ + readonly scan: DeepReadonly; + + /** + * 八个方向的坐标增量 + */ + readonly scan2: DeepReadonly; + + /** + * 将一段文字中的${}(表达式)进行替换。很多情况下可以用模板字符串替代 + * @example + * // 把主角的生命值和持有的黄钥匙数量代入这句话 + * core.replaceText('衬衫的价格是${status:hp}镑${item:yellowKey}便士。'); + * @param text 模板字符串,可以使用${}计算js表达式 + * @param prefix 独立开关前缀 + * @returns 替换完毕后的字符串 + */ + replaceText(text: string, prefix?: string): string; + + /** + * 对一个表达式中的特殊规则进行替换,如status:xxx等。 + * 其中变量和全局存储会替换中文冒号,其余的不会替换 + * @example + * // 把这两个冒号表达式替换为core.getStatus('hp')和core.itemCount('yellowKey')这样的函数调用 + * core.replaceValue('衬衫的价格是${status:hp}镑${item:yellowKey}便士。'); + * @param value 模板字符串,注意独立开关不会被替换 + * @returns 替换完毕后的字符串 + */ + replaceValue(value: string): string; + + /** + * 计算一个表达式的值,支持status:xxx等的计算。 + * @example core.calValue('status:hp + status:def'); // 计算主角的生命值加防御力 + * @param value 待求值的表达式 + * @param prefix 独立开关前缀,一般可省略 + * @returns 求出的值 + */ + calValue(value: string | Function, prefix?: string): any; + + /** + * @deprecated + * 将b(可以是另一个数组)插入数组a的开头,用Array.unshift就行 + * @example core.unshift(todo, {type: 'unfollow'}); // 在事件指令数组todo的开头插入“取消所有跟随者”指令 + * @param a 原数组 + * @param b 待插入的新首项或前缀数组 + * @returns 插入完毕后的新数组,它是改变原数组a本身得到的 + */ + unshift(a: A, b: B): [...B, ...A]; + + /** + * @deprecated + * 将b(可以是另一个数组)插入数组a的末尾,用Array.push就行 + * @example core.push(todo, {type: 'unfollow'}); // 在事件指令数组todo的末尾插入“取消所有跟随者”指令 + * @param a 原数组 + * @param b 待插入的新末项或后缀数组 + * @returns 插入完毕后的新数组,它是改变原数组a本身得到的 + */ + push(a: A, b: B): [...A, ...B]; + + /** + * 解压缩一个数据,我也不知道这个解压的是什么 + * @param 要解压的内容,字符串 + */ + decompress(value: string): any; + + /** + * 设置本地存储 + * @param key 本地存储的名称 + * @param value 本地存储的值,不填代表删除 + */ + setLocalStorage(key: string, value?: any): void; + + /** + * 获得本地存储 + * @param key 获取的本地存储的名称 + * @param defaultValue 当不存在的时候的默认值 + */ + getLocalStorage(key: string, defaultValue?: T): T; + + /** + * 移除本地存储 + * @param key 要移除的本地存储的值 + */ + removeLocalStorage(key: string): void; + + /** + * 异步写入localforage + * @param key 写入的键 + * @param value 写入的值 + * @param successCallback 写入成功的回调函数 + * @param errorCallback 写入出错的回调函数 + */ + setLocalForage( + key: string, + value?: any, + successCallback?: () => void, + errorCallback?: (err: Error) => void + ): void; + + /** + * 从localforage读出一段数据 + */ + getLocalForage( + key: string, + defaultValue?: T, + successCallback?: (data: T) => void, + errorCallback?: (err: Error) => void + ): void; + + /** + * 移除localforage的数据 + */ + removeLocalForage( + key: string, + successCallback?: () => void, + errorCallback?: (err: Error) => void + ): void; + + /** + * 清除localforage所有的数据 + * @param callback 清除完毕的回调函数 + */ + clearLocalForage(callback?: (err?: Error) => void): void; + + /** + * 迭代localforage的数据 + * @param iteratee 迭代器 + * @param callback 迭代完毕的回调函数 + */ + iterateLocalForage( + iteratee: (value: T, key: string, iterationNumber: number) => U, + callback?: (err: any, result: U) => void + ): void; + + /** + * 获取localforage数据的所有的键 + * @param callback 回调函数 + */ + keysLocalForage(callback?: (err: any, keys: string[]) => void): void; + + /** + * 获取localforage数据的数据量 + * @param callback 回调函数 + */ + lengthLocalForage( + callback?: (err: any, numberOfKeys: number) => void + ): void; + + /** + * 设置一个全局存储,适用于global:xxx,录像播放时将忽略此函数。 + * @example core.setBlobal('一周目已通关', true); // 设置全局存储“一周目已通关”为true,方便二周目游戏中的新要素。 + * @param key 全局变量名称,支持中文 + * @param value 全局变量的新值,不填或null表示清除此全局存储 + */ + setGlobal(key: string, value?: any): void; + + /** + * 读取一个全局存储,适用于global:xxx,支持录像。 + * @example if (core.getGlobal('一周目已通关', false) === true) core.getItem('dagger'); // 二周目游戏进行到此处时会获得一把屠龙匕首 + * @param key 全局变量名称,支持中文 + * @param defaultValue 可选,当此全局变量不存在或值为null、undefined时,用此值代替 + * @returns 全局变量的值 + */ + getGlobal(key: string, defaultValue?: T): T; + + /** + * 深拷贝一个对象(函数将原样返回) + * @example core.clone(core.status.hero, (name, value) => (name == 'items' || typeof value == 'number'), false); // 深拷贝主角的属性和道具 + * @param data 待拷贝对象 + * @param filter 过滤器,可选,表示data为数组或对象时拷贝哪些项或属性,true表示拷贝 + * @param recursion 过滤器是否递归,可选。true表示过滤器也被递归 + * @returns 拷贝的结果,注意函数将原样返回 + */ + clone( + data: T, + filter?: (name: string, value: any) => boolean, + recursion?: boolean + ): T; + + /** + * 深拷贝一个1D或2D的数组 + * @param data 要拷贝的数据 + */ + cloneArray(data: T): T; + + /** + * 等比例切分一张图片 + * @example core.splitImage(core.material.images.images['npc48.png'], 32, 48); // 把npc48.png切分成若干32×48px的小人 + * @param image 图片名(支持映射前的中文名)或图片对象(参见上面的例子),获取不到时返回[] + * @param width 子图的宽度,单位为像素。原图总宽度必须是其倍数,不填视为32 + * @param height 子图的高度,单位为像素。原图总高度必须是其倍数,不填视为正方形 + * @returns 子图组成的数组,在原图中呈先行后列,从左到右、从上到下排列。 + */ + splitImage( + image: NameMapIn | ImageIds | HTMLImageElement, + width?: number, + height?: number + ): HTMLImageElement[]; + + /** + * 格式化日期为字符串 + * @param date 时间,不填代表当前时间 + * @returns 格式: yyyy-mm-dd hh:mm:ss + */ + formatDate(date?: Date): string; + + /** + * 格式化日期为最简字符串 + * @param date 时间,不填代表当前时间 + * @returns 格式: yyyymmddhhmmss + */ + formatDate2(date?: Date): string; + + /** + * 格式化时间 + * @param time 时间 + * @returns 格式: hh:mm:ss + */ + formatTime(time: number): string; + + /** + * @deprecated + * 设置成两位数显示,请使用setDigits代替 + */ + setTwoDigits(x: number): string; + + /** + * 设置一个数为n位数显示 + * @param x 要设置的数 + * @param n 设置成的位数 + */ + setDigits(x: number, n: number): string; + + /** + * 格式化文件大小 + * @param size 大小,字节数 + * @returns 格式为xx.xxB KB MB + */ + formatSize(size: number): string; + + /** + * 大数字格式化,单位为10000的倍数(w,e,z,j,g),末尾四舍五入 + * @example core.formatBigNumber(123456789); // "12346w" + * @param x 原数字 + * @param onMap 显示的字符数 + * @returns 格式化结果 + */ + formatBigNumber(x: number, onMap?: number): string; + + /** + * @deprecated + * 变速移动,完全可以用mutate-animate代替 + * @param mode 缓动模式 + */ + applyEasing(mode?: EaseMode): (x: number) => number; + + /** + * 颜色数组转十六进制 + * @example core.arrayToRGB([102, 204, 255]); // "#66ccff",加载画面的宣传色 + * @param color 一行三列的数组,各元素必须为不大于255的自然数 + * @returns 该颜色的十六进制表示,使用小写字母 + */ + arrayToRGB(color: RGBArray): string; + + /** + * 颜色数组转字符串 + * @example core.arrayToRGBA([102, 204, 255]); // "rgba(102,204,255,1)" + * @param color 一行三列或一行四列的数组,前三个元素必须为不大于255的自然数。 + * 第四个元素(如果有)必须为0或不大于1的数字,第四个元素不填视为1 + * @returns 该颜色的字符串表示 + */ + arrayToRGBA(color: RGBArray): string; + + /** + * 录像一压,其结果会被再次base64压缩 + * @example core.encodeRoute(core.status.route); // 一压当前录像 + * @param route 原始录像,自定义内容(不予压缩,原样写入)必须由0-9A-Za-z和下划线、冒号组成, + * 所以中文和数组需要用JSON.stringify预处理再base64压缩才能交由一压 + * @returns 一压的结果 + */ + encodeRoute(route: string[]): string; + + /** + * 录像解压的最后一步,即一压的逆过程 + * @example core.decodeRoute(core.encodeRoute(core.status.route)); // 一压当前录像再解压-_-| + * @param route 录像解压倒数第二步的结果,即一压的结果 + * @returns 原始录像 + */ + decodeRoute(route: string): string[]; + + /** + * 判断一个值是否不为null,undefined和NaN + * @example core.isset(0/0); // false,因为0/0等于NaN + * @param v 待测值 + * @returns false表示待测值为null、undefined、NaN或未填写,true表示为其他值 + */ + isset(v?: any): boolean; + + /** + * 判定一个数组是否为另一个数组的前缀,用于录像接续播放 + * @example core.subarray(['ad', '米库', '小精灵', '小破草', '小艾'], ['ad', '米库', '小精灵']); // ['小破草', '小艾'] + * @param a 可能的母数组,不填或比b短将返回null + * @param b 可能的前缀,不填或比a长将返回null + * @returns 如果b不是a的前缀将返回null,否则将返回a去掉此前缀后的剩余数组 + */ + subarray(a: any[], b: any[]): any[] | null; + + /** + * @deprecated + * 判定array是不是一个数组,以及element是否在该数组中。使用Array.includes代替 + * @param array 可能的数组,不为数组或不填将导致返回值为false + * @param element 待查找的元素 + * @returns 如果array为数组且具有element这项,就返回true,否则返回false + */ + inArray(array?: any, element?: any): boolean; + + /** + * 将x限定在[a,b]区间内,注意a和b可交换 + * @example core.clamp(1200, 1, 1000); // 1000 + * @param x 原始值,!x为true时x一律视为0 + * @param a 下限值,大于b将导致与b交换 + * @param b 上限值,小于a将导致与a交换 + */ + clamp(x: number, a: number, b: number): number; + + /** + * 访问浏览器cookie + */ + getCookie(name: string): string; + + /** + * 填写非自绘状态栏 + * @example + * // 更新状态栏中的主角生命,使用加载画面的宣传色 + * core.setStatusBarInnerHTML('hp', core.status.hero.hp, 'color: #66CCFF'); + * @param name 状态栏项的名称,如'hp', 'atk', 'def'等。必须是core.statusBar中的一个合法项 + * @param value 要填写的内容,大数字会被格式化为至多6个字符,无中文的内容会被自动设为斜体 + * @param css 额外的css样式,可选。如更改颜色等 + */ + setStatusBarInnerHTML( + name: string, + value: string | number, + css?: string + ): void; + + /** + * 求字符串的国标码字节数,也可用于等宽字体下文本的宽度测算。请注意样板的默认字体Verdana不是等宽字体 + * @example core.strlen('无敌ad'); // 6 + * @param str 待测字符串 + * @returns 字符串的国标码字节数,每个汉字为2,每个ASCII字符为1 + */ + strlen(str: string): number; + + /** + * 计算应当转向某个方向 + * @param turn 转向的方向 + * @param direction 当前方向,不填视为当前方向 + */ + turnDirection(turn: HeroTurnDir, direction?: Dir): string; + + /** + * 通配符匹配,用于搜索图块等批量处理。 + * @example core.playSound(core.matchWildcard('*Key', itemId) ? 'item.mp3' : 'door.mp3'); // 判断捡到的是钥匙还是别的道具,从而播放不同的音效 + * @param pattern 模式串,每个星号表示任意多个(0个起)字符 + * @param string 待测串 + * @returns true表示匹配成功,false表示匹配失败 + */ + matchWildcard(pattern: string, string: string): boolean; + + /** + * 是否满足正则表达式,一般可以直接用/RegExp/.test(str)代替 + * @param pattern 正则表达式 + * @param string 要匹配的字符串 + */ + matchRegex(pattern: string, string: string): string; + + /** + * base64加密 + * @example + * core.encodeBase64('If you found this note in a small wooden box with a heart on it'); + * // "SWYgeW91IGZvdW5kIHRoaXMgbm90ZSBpbiBhIHNtYWxsIHdvb2RlbiBib3ggd2l0aCBhIGhlYXJ0IG9uIGl0" + * @param str 明文 + * @returns 密文 + */ + encodeBase64(str: string): string; + + /** + * base64解密 + * @example + * core.decodeBase64('SWYgeW91IGZvdW5kIHRoaXMgbm90ZSBpbiBhIHNtYWxsIHdvb2RlbiBib3ggd2l0aCBhIGhlYXJ0IG9uIGl0'); + * // "If you found this note in a small wooden box with a heart on it" + * @param str 密文 + * @returns 明文 + */ + decodeBase64(str: string): string; + + /** + * 不支持SL的随机数 + * @exmaple 1 + core.rand(6); // 随机生成一个小于7的正整数,模拟骰子的效果 + * @param num 填正数表示生成小于num的随机自然数,否则生成小于1的随机正数 + * @returns 随机数,即使读档也不会改变结果 + */ + rand(num?: number): number; + + /** + * 支持SL的随机数,并计入录像 + * @exmaple 1 + core.rand2(6); // 随机生成一个小于7的正整数,模拟骰子的效果 + * @param num 正整数,0或不填会被视为2147483648 + * @returns 属于 [0, num) 的随机数 + */ + rand2(num?: number): number; + + /** + * 尝试请求读取一个本地文件内容 [异步] + * @param success 成功后的回调 + * @param error 失败后的回调 + * @param accept input元素的accept属性 + * @param readType 不设置则以文本读取,否则以DataUrl形式读取 + */ + readFile( + success: (obj: any) => void, + error: () => void, + accept: string, + readType: boolean + ): void; + + /** + * 文件读取完毕后的内容处理 [异步] + * @param content 读取的内容 + */ + readFileContent(content: string): void; + + /** + * 弹窗请求下载一个文本文件 + * @example core.download('route.txt', core.status.route); // 弹窗请求下载录像 + * @param filename 文件名 + * @param content 文件内容 + */ + download(filename: string, content: string | string[]): void; + + /** + * 尝试复制一段文本到剪切板 + * @param data 赋值的东西 + */ + copy(data: string): void; + + /** + * 显示确认框,类似core.drawConfirmBox() + * @example core.myconfirm('重启游戏?', core.restart); // 弹窗询问玩家是否重启游戏 + * @param hint 弹窗的内容 + * @param yesCallback 确定后的回调函数 + * @param noCallback 取消后的回调函数 + */ + myconfirm( + hint: string, + yesCallback: () => void, + noCallback?: () => void + ): void; + + /** + * 让用户输入一段文字 + */ + myprompt( + hint: string, + value: string, + callback?: (data?: string) => void + ): void; + + /** + * @deprecated + * 动画显示某对象,有vue了,你还用这个?Transition组件和css的transition比这个强得多 + */ + showWithAnimate( + obj?: HTMLElement, + speed?: number, + callback?: () => any + ): void; + + /** + * @deprecated + * 动画使某对象消失 + */ + hideWithAnimate( + obj?: HTMLElement, + speed?: number, + callback?: () => any + ): void; + + /** + * 获得浏览器唯一的guid + */ + getGuid(): string; + + /** + * 获取一个对象的哈希值 + * @param obj 要获取的对象 + */ + hashCode(obj: any): number; + + /** + * 判定深层相等, 会逐层比较每个元素 + * @example core.same(['1', 2], ['1', 2]); // true + */ + same(a: any, b: any): boolean; + + /** + * 解压一段内容 + */ + unzip( + blobOrUrl: string | Blob, + success?: (data: any) => void, + error?: (error: string) => void, + convertToText?: boolean, + onprogress?: (loaded: number, total: number) => void + ): void; + + /** + * 发送一个HTTP请求 [异步] + * @param type 请求类型 + * @param url 目标地址 + * @param formData 如果是POST请求则为表单数据 + * @param success 成功后的回调 + * @param error 失败后的回调 + */ + http( + type: 'GET' | 'POST', + url: string, + formData?: FormData, + success?: (res: any) => void, + error?: (err: string) => void, + mimeType?: string, + responseType?: XMLHttpRequestResponseType, + onProgress?: (loaded: number, total: number) => void + ): void; +} + +declare const utils: new () => Utils; + +/** + * 移动的方向 + */ +type Step = Move | 'backward'; + +/** + * 坐标字符串 + */ +type LocString = `${number},${number}`; + +type _RGBA = + | `rgb(${number},${number},${number})` + | `rgba(${number},${number},${number},${number})`; + +/** + * RGBA颜色数组 + */ +type RGBArray = [number, number, number, number?]; + +/** + * 样板的颜色字符串 + */ +type Color = `#${string}` | _RGBA | RGBArray; + +/** + * 四个方向 + */ +type Dir = 'up' | 'down' | 'left' | 'right'; + +/** + * 八个方向 + */ +type Dir2 = Dir | 'leftup' | 'rightup' | 'leftdown' | 'rightdown'; + +/** + * 转向的方向 + */ +type TurnDir = Dir | ':left' | ':right' | ':back'; + +/** + * 勇士转向 + */ +type HeroTurnDir = TurnDir | ':hero' | ':backhero'; + +/** + * 对话框的位置 + */ +type TextPosition = 'up' | 'center' | 'down'; + +/** + * 移动的方向 + */ +type Move = 'forward' | Dir; + +/** + * 缓动模式,不过在高级动画插件面前不堪一击( + */ +type EaseMode = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut'; + +/** + * 事件执行的操作符\ + * += 增加并赋值\ + * -= 减少并赋值\ + * *= 相乘并赋值\ + * /= 相除并赋值\ + * //= 除以并取商\ + * **= 取幂\ + * %= 取余\ + * min= 取二者的最小值\ + * max= 取二者的最大值\ + * 其它的任意字符串都是赋值 + */ +type MotaOperator = + | '+=' + | '-=' + | '*=' + | '/=' + | '//=' + | '**=' + | '%=' + | 'min=' + | 'max=' + | '='; + +/** + * 位置数组 + */ +type LocArr = [x: number, y: number]; + +/** + * 位置 + */ +interface Loc { + /** + * 横坐标 + */ + x: number; + + /** + * 纵坐标 + */ + y: number; +} + +/** + * 带方向的位置 + */ +interface DiredLoc extends Loc { + /** + * 方向 + */ + direction: Dir; +} + +interface CompressedStep { + /** + * 移动方向 + */ + direction: Dir; + + /** + * 向该方向移动的步数 + */ + step: number; +} + +/** + * 四个方向的坐标增量 + */ +type Scan = { + [D in Dir]: Loc; +}; + +/** + * 八个方向的坐标增量 + */ +type Scan2 = { + [D in Dir2]: Loc; +}; + +/** + * 图片翻转 + */ +type ImageReverse = ':o' | ':x' | ':y'; + +/** + * 对话框的箭头方向 + */ +type TextBoxDir = 'up' | 'down'; + +/** + * 对话框的位置 + */ +type TextBoxPos = + | `${TextBoxDir},hero` + | `${TextBoxDir},${number},${number}` + | TextPosition; + +/** + * 画布信息 + */ +type CtxRefer = string | CanvasRenderingContext2D | HTMLCanvasElement; + +/** + * 触发器类型 + */ +type MotaTrigger = + | 'battle' + | 'pusBox' + | 'openDoor' + | 'ski' + | 'custom' + | 'getItem'; + +/** + * 切换楼层的目标坐标 + */ +type FloorChangeStair = + | 'upFloor' + | 'downFloor' + | ':symmetry' + | ':symmetry_x' + | ':symmetry_y' + | 'flyPoint'; + +/** + * 事件值的前缀 + */ +type EventValuePreffix = + | 'status' + | 'flag' + | 'item' + | 'buff' + | 'switch' + | 'temp' + | 'global'; + +interface Animate { + /** + * 动画的帧数s + */ + frame: number; + + /** + * 每帧的信息 + */ + frames: FrameObj[][]; + + /** + * 图片信息 + */ + images: HTMLImageElement[]; + + /** + * 缩放信息 + */ + ratio: number; + + /** + * 音效 + */ + se: string; +} + +type Save = DeepReadonly<{ + /** + * 存档所在的楼层id + */ + floorId: FloorIds; + + /** + * 存档的勇士信息 + */ + hero: HeroStatus; + + /** + * 难度信息 + */ + hard: number; + + /** + * 存档的地图信息,已经过压缩处理 + */ + maps: Record; + + /** + * 录像信息 + */ + route: string; + + /** + * 存档的全局变量信息 + */ + values: CoreValues; + + /** + * 游戏版本 + */ + version: string; + + /** + * 浏览器唯一guid + */ + guid: string; + + /** + * 存档时间 + */ + time: number; +}>; + +/** + * 深度只读一个对象,使其所有属性都只读 + */ +type DeepReadonly = { + readonly [P in keyof T]: T[P] extends number | string | boolean + ? T[P] + : DeepReadonly; +}; + +/** + * 深度可选一个对象,使其所有属性都 + */ +type DeepPartial = { + [P in keyof T]?: T[P] extends number | string | boolean + ? T[P] + : DeepReadonly; +}; + +/** + * 深度必选一个对象,使其所有属性都必选 + */ +type DeepRequired = { + [P in keyof T]-?: T[P] extends number | string | boolean + ? T[P] + : DeepReadonly; +}; + +/** + * 使一个对象的所有属性可写 + */ +type Writable = { + -readonly [P in keyof T]: P[T]; +}; + +/** + * 深度可写一个对象,使其所有属性都可写 + */ +type DeepWritable = { + -readonly [P in keyof T]: T[P] extends number | string | boolean + ? T[P] + : DeepReadonly; +}; + +/** + * 从一个对象中选择类型是目标类型的属性 + */ +type SelectType = { + [P in keyof R as R[P] extends T ? P : never]: R[P]; +}; + +/** + * 获取一段字符串的第一个字符 + */ +type FirstCharOf = T extends `${infer F}${infer A}` + ? F + : never; + +/** + * 非对象属性 + */ +type NonObject = number | string | boolean; + +/** + * 获取一个对象的非对象值 + */ +type NonObjectOf = SelectType; + +/** + * 以一个字符串结尾 + */ +type EndsWith = `${string}${T}`;