Compare commits

...

6 Commits

228 changed files with 3152 additions and 3697 deletions

3
.gitignore vendored
View File

@ -49,4 +49,5 @@ script/people.ts
user.ts
.antlr
graph.svg
docs/.vitepress
docs/.vitepress/cache
docs/.vitepress/dist

59
docs/.vitepress/config.ts Normal file
View File

@ -0,0 +1,59 @@
import { defineConfig } from 'vitepress';
// https://vitepress.dev/reference/site-config
export default defineConfig({
title: 'HTML5 魔塔样板 V2.B',
description: 'HTML5 魔塔样板 V2.B 帮助文档',
base: '/_docs/',
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: '主页', link: '/' },
{ text: '指南', link: '/guide/diff' },
{ text: 'API', link: '/api/' }
],
sidebar: {
'/guide/': [
{
text: '深度指南',
items: [
{ text: '差异说明', link: '/guide/diff' },
{ text: '系统说明', link: '/guide/system' }
]
}
]
},
socialLinks: [
{ icon: 'github', link: 'https://github.com/unanmed/HumanBreak' }
],
search: {
provider: 'local',
options: {
locales: {
zh: {
translations: {
button: {
buttonText: '搜索文档',
buttonAriaLabel: '搜索文档'
},
modal: {
noResultsText: '无法找到相关结果',
resetButtonTitle: '清除查询条件',
footer: {
selectText: '选择',
navigateText: '切换'
}
}
}
}
}
}
}
},
locales: {
root: {
lang: 'zh',
label: '中文'
}
}
});

5
docs/api/index.md Normal file
View File

@ -0,0 +1,5 @@
---
lang: zh-CN
---
# API 列表

34
docs/guide/diff.md Normal file
View File

@ -0,0 +1,34 @@
---
lang: zh-CN
---
# 差异说明
本文档暂时只会对新样板新增内容进行说明,其余请查看[旧样板文档](https://h5mota.com/games/template/_docs/#/)。
本指南建立在你已经大致了解 js 的基础语法的基础上。如果还不了解可以尝试对指南内容进行模仿,或者查看[人类塔解析](https://h5mota.com/bbs/thread/?tid=1018&p=1)
如果你有能力直接使用源码版样板进行创作,也可以直接 fork 或 clone 2.B 样板[存储库](https://github.com/unanmed/HumanBreak/tree/template-v2.B)。2.B 样板使用了 vite 作为了构建工具,同时使用了 ts 等作为了开发语言。
本文将描述 2.B 样板与 2.10.3 及 2.A 样板的差异。
## 注意事项
对于新样板,由于拥有了近乎完整的类型标注,因此更推荐使用 `VS Code` 进行代码编写,这样你可以获取到完整的类型标注,而由于类型标注的复杂性,样板编辑器完全无法部署,因此样板编辑器不会有任何新版的类型标注。在之后的更新中,样板 API 会进行大幅度的改动,因此每次更新都可能会弃用一部分 API同时这些 API 会在若干个版本后被彻底删除。因此如果你的代码中使用到了弃用的 API请尽快更换写法以保证可以向后接档。
## 主要差异
- 开发语言换为 TypeScript可以享受到完整的类型支持
- 使用全新的 UI 编写方式,速度快,效率高
- 模块化,可以使用 ES6 模块化语法
- 移除插件系统,可以自定义代码目录结构,更加自由
- 优化渲染端client 端与数据端data 端)的通讯,渲染段现在可以直接引用数据端,不过数据端还不能直接引用渲染端
## 差异内容
相比于 2.10.3 及 2.A有如下改动
- [系统说明](./system)
- [UI 编写](./ui)
- [UI 系统](./ui-system)
- [音频系统](./audio)

157
docs/guide/system.md Normal file
View File

@ -0,0 +1,157 @@
---
lang: zh-CN
---
# 系统说明
本文将介绍 2.B 的系统都做了哪些更改
## 模块化
2.B 样板现在已经迁移至了 monorepo将代码模块化共分为 20 余个模块,每个模块的具体内容可以参考 API 文档,模块列表如下:
- [@motajs/client](../api/motajs-client)
- [@motajs/client-base](../api/motajs-client-base)
- [@motajs/common](../api/motajs-common)
- [@motajs/legacy-client](../api/motajs-legacy-client)
- [@motajs/legacy-common](../api/motajs-legacy-common)
- [@motajs/legacy-data](../api/motajs-legacy-data)
- [@motajs/legacy-system](../api/motajs-legacy-system)
- [@motajs/legacy-ui](../api/motajs-legacy-ui)
- [@motajs/render](../api/motajs-render)
- [@motajs/render-core](../api/motajs-render-core)
- [@motajs/render-elements](../api/motajs-render-elements)
- [@motajs/render-style](../api/motajs-render-style)
- [@motajs/render-vue](../api/motajs-render-vue)
- [@motajs/system](../api/motajs-system)
- [@motajs/system-action](../api/motajs-system-action)
- [@motajs/system-ui](../api/motajs-system-ui)
- [@motajs/types](../api/types)
- [@user/client-modules](../api/user-client-modules)
- [@user/data-base](../api/user-data-base)
- [@user/data-fallback](../api/user-data-fallback)
- [@user/data-state](../api/user-data-state)
- [@user/data-utils](../api/user-data-utils)
- [@user/entry-client](../api/user-entry-client)
- [@user/entry-data](../api/user-entry-data)
- [@user/legacy-plugin-client](../api/user-legacy-plugin-client)
- [@user/legacy-plugin-data](../api/user-legacy-plugin-data)
## Mota 全局变量
与 2.A 不同2.B 对 `Mota` 全局变量做了简化,不再拥有 `Mota.Plugin` `Mota.Package` `Mota.requireAll` 属性与方法,它们全部整合至了 `Mota.require` 方法中,同时该方法的用法与 2.A 也不同,在 2.A 中,我们往往使用 `Mota.require('var', 'xxx')` 的方式调用,繁琐且不直观。在 2.B 中,我们可以直接填入模块名称,就可以获取到其内容了,例如:
```ts
const { hook, loading } = Mota.require('@user/data-base'); // 获取 hook 与 loading
const { Font } = Mota.require('@motajs/render'); // 获取 Font 字体类
```
我们只需要填写一个参数,而不需要填写两个参数了,更加直观,而且与 ES6 模块语法类似,便于转换。
多数情况下,我们是不需要使用 `Mota` 全局变量的。不过,还是有一些特殊情况需要使用该全局变量才可以,这些情况包括:
- 在数据端调用渲染端接口,数据端需要跑录像验证,因此不能直接引入渲染端接口,需要通过此全局变量才可以。
- 在 `libs` `functions.js` 中调用接口,这两个地方暂时还没有模块化,因此无法直接引入,需要通过此全局变量调用。
## 渲染端与数据端通信
一般情况下,渲染端**可以**直接引入数据端的内容,例如你可以在 `@user/client-modules` 里面直接引入 `@user/data-state` 的接口,这是没有问题的。不过,由于数据端需要在服务器上跑录像验证,因此**不能**直接引入渲染端的内容,否则会导致验证报错。如果需要在数据端引用渲染端接口,我们需要这么做:
```ts
// @user/data-state 中的某文件
const num = 100;
Mota.r(() => {
// 使用 r 方法包裹,这样这个函数就会在渲染端运行,可以有返回值,但是在录像验证中只会是 undefined
const { Font } = Mota.require('@motajs/render');
const font = new Font('Verdana', 18);
// 函数内也可以调用外部变量,例如这里就调用了外部的 num 变量,但是极度不推荐在渲染端修改数据端的内容
// 否则很可能导致录像不能运行,这里这个例子就会导致录像运行出错,因为录像验证时并不会执行这段代码,
// 勇士的血量也就不会变大,于是就出错了。
core.status.hero.hp += num;
});
```
除此之外,我们还可以使用钩子来进行数据通信。示例如下:
```ts
// 渲染端和数据端都可以使用这个方式引入
import { hook } from '@user/data-base';
// 也可以通过 Mota.require 方法引入
const { hook } = Mota.require('@user/data-base');
// 监听战后函数,每次与怪物战斗后,都会执行这个函数
// 每个钩子的参数定义可以参考 package-user/data-base/src/game.ts GameEvent 接口
hook.on('afterBattle', enemy => {
console.log('与怪物战斗:', enemy.id);
});
```
## 加载流程
与 2.A 相比,加载流程也不太一样,下面是 2.B 的加载流程:
1. 加载 `index.html`
2. 加载 2.x 样板的第三方库
3. 如果是游戏中,加载 `src/main.ts`
1. 加载渲染端入口
2. 加载数据端入口
3. 并行初始化数据端,写入 `Mota` 全局变量
4. 初始化完毕后执行 `loading.emit('dataRegistered')` 钩子
5. 并行初始化渲染端
6. 初始化完毕后执行 `loading.emit('clientRegistered')` 钩子
7. 二者都初始化完毕后执行 `loading.emit('registered')` 钩子
8. 执行数据端各个模块的初始化函数
9. 执行渲染段各个模块的初始化函数
4. 如果是录像验证中:
1. 加载数据端入口
2. 初始化数据端,写入 `Mota` 全局变量
3. 初始化完毕后执行 `loading.emit('dataRegistered')``loading.emit('registered')` 钩子
4. 执行数据端各个模块的初始化函数
5. 执行 `main.js` 初始化
6. 加载全塔属性
7. 加载 `core.js` 及其他 `libs` 中的脚本
8. 加载完毕后执行 `loading.emit('coreInit')` 钩子
9. 开始资源加载
10. 自动元件加载完毕后执行 `loading.emit('autotileLoaded')` 钩子
11. 资源加载完毕后执行 `loading.emit('loaded')` 钩子
12. 进入标题界面
## 函数重写
在 2.B 模式下,如果想改 `libs` 的内容,如果直接在里面改会很麻烦,而且两端通讯也不方便,因此我们建议在 `package-user` 中对函数重写这样的话就可以使用模块化语法更加方便。同时2.B 也提供了函数重写接口,他在 `@motajs/legacy-common` 模块中,我们可以这么使用它:
```ts
// 新建一个 ts 文件,例如叫做 override.ts放在 client-modules 文件夹下
import { Patch, PatchClass } from '@motajs/legacy-common';
// 新建函数,这个操作是必要的,我们不能直接在顶层使用这个接口
export function patchMyFunctions() {
// 创建 Patch 实例,参数表示这个 Patch 示例要重写哪个文件中的函数
// 如果需要复写两个文件,那么就需要创建两个实例
const patch = new Patch(PatchClass.Control);
// 使用 add 函数来重写,第一个参数会有自动补全
// 如果要重写的函数以下划线开头,可能会有报错
// 这时候需要去 types/declaration 中对应的文件中添加声明
patch.add('getFlag', (name, defaultValue) => {
// 重写 getFlag如果变量是数字那么 +100 后返回
const value = core.status.?hero.?flags[name] ?? defaultValue;
return typeof value === 'number' ? value + 100 : value;
});
}
```
然后,我们找到 `client-modules` 文件夹下的 `index.ts` 文件,然后在 `create` 函数中调用 `patchMyFunctions`,这样我们的函数重写就完成了。
::: warning
**注意**,在渲染端重写的函数在录像验证中将无效,因为录像验证不会执行任何渲染端内容!
:::
## 目录结构
我们建议每个文件夹中都有一个 `index.ts` 文件,将本文件夹中的其他文件经由此文件导出,这样方便管理,同时结构清晰。可以参考 `packages-user/client-modules` 文件夹中是如何做的。

0
docs/guide/ui-system.md Normal file
View File

0
docs/guide/ui.md Normal file
View File

View File

@ -4,7 +4,7 @@ layout: home
hero:
name: 'mota-js'
text: 'HTML5魔塔样板V2.A'
text: 'HTML5魔塔样板V2.B'
tagline: HTML5魔塔样板从 2.x 到 3.0 的过渡版本
actions:
- theme: brand

View File

@ -0,0 +1,8 @@
{
"name": "@user/client-modules",
"dependencies": {
"@motajs/render": "workspace:*",
"@motajs/legacy-ui": "workspace:*",
"@user/legacy-plugin-client": "workspace:*"
}
}

View File

@ -0,0 +1 @@
export * from './move';

View File

@ -258,7 +258,7 @@ export class BgmController<
export const bgmController = new BgmController<BgmIds>(audioPlayer);
export function loadAllBgm() {
const loading = Mota.require('var', 'loading');
const { loading } = Mota.require('@user/data-base');
loading.once('coreInit', () => {
const data = data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d;
for (const bgm of data.main.bgms) {

View File

@ -3,9 +3,11 @@ import { OpusDecoder, VorbisDecoder } from './decoder';
import { AudioType } from './support';
import { AudioDecoder } from './decoder';
export function createAudio() {
loadAllBgm();
AudioDecoder.registerDecoder(AudioType.Ogg, VorbisDecoder);
AudioDecoder.registerDecoder(AudioType.Opus, OpusDecoder);
}
export * from './support';
export * from './effect';

View File

@ -51,5 +51,3 @@ isAudioSupport(AudioType.Wav);
isAudioSupport(AudioType.Flac);
isAudioSupport(AudioType.Opus);
isAudioSupport(AudioType.Aac);
console.log(supportMap);

View File

@ -1,4 +1,3 @@
import { Patch } from '@motajs/legacy-common';
import { patchAudio } from './audio';
import { patchWeather } from './weather';
import { patchUI } from './ui';
@ -7,8 +6,4 @@ export function patchAll() {
patchAudio();
patchWeather();
patchUI();
const loading = Mota.require('var', 'loading');
loading.once('coreInit', () => {
Patch.patchAll();
});
}

View File

@ -0,0 +1,20 @@
import { loading } from '@user/data-base';
import { createAudio } from './audio';
import { patchAll } from './fallback';
import { createGameRenderer, createRender } from './render';
export function create() {
createAudio();
patchAll();
createRender();
loading.once('coreInit', () => {
createGameRenderer();
});
}
export * from './action';
export * from './weather';
export * from './audio';
export * from './loader';
export * from './fallback';
export * from './render';

View File

@ -8,17 +8,19 @@ import {
import { logger } from '@motajs/common';
import EventEmitter from 'eventemitter3';
import { isNil } from 'lodash-es';
import { LayerGroupFloorBinder } from './floor';
import {
BlockCacher,
CanvasCacheItem,
ICanvasCacheItem,
calNeedRenderOf,
ILayerGroupRenderExtends,
Layer,
LayerGroup
} from './layer';
import { BlockCacher, CanvasCacheItem, ICanvasCacheItem } from './block';
LayerGroup,
LayerGroupFloorBinder,
tagMap
} from '@motajs/render';
import { IDamageEnemy, IEnemyCollection, MapDamage } from '@motajs/types';
const ensureFloorDamage = Mota.require('fn', 'ensureFloorDamage');
import { UserEnemyInfo } from '@user/data-state';
/**
*
@ -55,6 +57,7 @@ export class FloorDamageExtends
if (!this.sprite || !floor) return;
const map = core.status.maps[floor];
this.sprite.setMapSize(map.width, map.height);
const { ensureFloorDamage } = Mota.require('@user/data-state');
ensureFloorDamage(floor);
const enemy = core.status.maps[floor].enemy;
@ -75,6 +78,7 @@ export class FloorDamageExtends
private onUpdate = (floor: FloorIds) => {
if (!this.floorBinder.bindThisFloor) {
const { ensureFloorDamage } = Mota.require('@user/data-state');
ensureFloorDamage(floor);
core.status.maps[floor].enemy.calRealAttribute();
}
@ -323,7 +327,7 @@ export class Damage extends RenderItem<EDamageEvent> {
const y = enemy.y!;
const { damage } = enemy.calDamage();
const cri = enemy.calCritical(1)[0]?.atkDelta ?? Infinity;
const real = enemy.getRealInfo();
const real = enemy.getRealInfo() as UserEnemyInfo;
const dam1: DamageRenderable = {
align: 'left',
@ -587,4 +591,9 @@ export class Damage extends RenderItem<EDamageEvent> {
}
}
// 注册为内部元素
tagMap.register<EDamageEvent, Damage>('damage', (_0, _1, _props) => {
return new Damage();
});
// const adapter = new RenderAdapter<Damage>('damage');

View File

@ -3,9 +3,12 @@ import { defineComponent } from 'vue';
import { UIController } from '@motajs/system-ui';
import { mainSceneUI } from './ui/main';
import { MAIN_HEIGHT, MAIN_WIDTH } from './shared';
import { TextboxStore } from './components';
import { hook } from '@user/data-base';
import { createItemDetail } from './itemDetail';
import { createLoopMap } from './loopMap';
import { createGameCanvas } from './legacy/gameCanvas';
export function create() {
export function createGameRenderer() {
const main = new MotaRenderer();
main.size(MAIN_WIDTH, MAIN_HEIGHT);
@ -23,20 +26,22 @@ export function create() {
main.hide();
createApp(App).mount(main);
Mota.require('var', 'hook').on('reset', () => {
hook.on('reset', () => {
main.show();
});
Mota.require('var', 'hook').on('restart', () => {
hook.on('restart', () => {
main.hide();
});
console.log(main);
}
Mota.register('module', 'MainUI', {
TextboxStore
});
export function createRender() {
createGameCanvas();
createItemDetail();
createLoopMap();
}
export * from './components';
export * from './ui';

View File

@ -1,13 +1,13 @@
import { logger } from '@motajs/common';
import { mainSetting } from '@motajs/legacy-ui';
import {
Damage,
DamageRenderable,
FloorDamageExtends,
LayerGroupFloorBinder,
ILayerGroupRenderExtends,
LayerGroup
} from '@motajs/render';
import { hook } from '@user/data-base';
import { ItemState } from '@user/data-state';
import { Damage, DamageRenderable, FloorDamageExtends } from './damage';
interface ItemDetailData {
x: number;
@ -21,12 +21,13 @@ interface ItemData {
y: number;
}
const ItemState = Mota.require('module', 'State').ItemState;
Mota.require('var', 'hook').on('setBlock', (x, y, floorId, block) => {
export function createItemDetail() {
hook.on('setBlock', (x, y, floorId, block) => {
FloorItemDetail.listened.forEach(v => {
v.setBlock(block, x, y);
});
});
}
export class FloorItemDetail implements ILayerGroupRenderExtends {
id: string = 'item-detail';
@ -199,8 +200,7 @@ export class FloorItemDetail implements ILayerGroupRenderExtends {
return;
}
// @ts-ignore
ItemState.item(id)?.itemEffectFn();
ItemState.item(id)?.itemEffectFn?.();
detail?.set(index, { x, y, diff });
});
});

View File

@ -4,6 +4,7 @@ import {
ILayerGroupRenderExtends,
LayerGroup
} from '@motajs/render';
import { loading } from '@user/data-base';
const filterMap: [FloorIds[], string][] = [];
@ -11,15 +12,19 @@ function getCanvasFilterByFloorId(floorId: FloorIds = core.status.floorId) {
return filterMap.find(v => v[0].includes(floorId))?.[1] ?? '';
}
Mota.require('var', 'loading').once('coreInit', () => {
export function createGameCanvas() {
loading.once('coreInit', () => {
filterMap.push(
[['MT50', 'MT60', 'MT61'], 'contrast(120%)'], // 童心佬的滤镜(
[
core.floorIds.slice(61, 70).concat(core.floorIds.slice(72, 107)),
core.floorIds
.slice(61, 70)
.concat(core.floorIds.slice(72, 107)),
'contrast(120%)'
] // 童心佬的滤镜(
);
});
}
export class LayerGroupFilter implements ILayerGroupRenderExtends {
id: string = 'filter';

View File

@ -8,8 +8,7 @@ import {
Sprite,
Transform
} from '@motajs/render';
const gameListener = Mota.require('var', 'gameListener');
import { gameListener, hook } from '@user/data-base';
export class LayerGroupHalo implements ILayerGroupRenderExtends {
id: string = 'halo';
@ -130,7 +129,7 @@ function updateHalo(block: Block) {
}
}
Mota.require('var', 'hook').on('enemyExtract', col => {
hook.on('enemyExtract', col => {
LayerGroupHalo.sprites.forEach(v => {
const floor = v.binder.getFloor();
if (col.floorId === floor) {

View File

@ -7,7 +7,7 @@ import {
LayerGroup,
Sprite
} from '@motajs/render';
import type { BluePalace } from '@/game/mechanism/misc';
import { BluePalace } from '@user/data-state';
/** 最大粒子数 */
const MAX_PARTICLES = 10;
@ -24,7 +24,7 @@ export class LayerGroupPortal implements ILayerGroupRenderExtends {
portal!: Portal;
private onFloorChange = (floor: FloorIds) => {
const data = Mota.require('module', 'Mechanism').BluePalace.portals;
const data = BluePalace.portals;
this.portal.cellSize = this.group.cellSize;
this.portal.setData(data[floor] ?? []);
};

View File

@ -1,25 +1,28 @@
import {
Container,
FloorDamageExtends,
LayerGroupFloorBinder,
FloorLayer,
LayerGroup,
FloorViewport,
MotaRenderer
} from '@motajs/render';
import { FloorItemDetail } from '@/plugin/fx/itemDetail';
import { hook } from '@user/data-base';
import { MiscData } from '@user/data-state';
import { FloorDamageExtends } from './damage';
import { FloorItemDetail } from './itemDetail';
const loopMaps = Mota.require('module', 'Mechanism').MiscData.loopMaps;
const loopMaps = MiscData.loopMaps;
let loopLayer: LayerGroup;
let show: boolean = false;
/** 循环式地图中更新视角的委托ticker */
let delegation: number = -1;
const hook = Mota.require('var', 'hook');
export function createLoopMap() {
hook.on('changingFloor', (floorId, heroLoc) => {
enableLoopMapElement(floorId);
});
}
function createLayer() {
const group = new LayerGroup();

View File

@ -1,7 +1,6 @@
import { LayerShadowExtends } from '@/core/fx/shadow';
import { LayerShadowExtends } from '@motajs/legacy-ui';
import {
ILayerGroupRenderExtends,
FloorDamageExtends,
LayerGroupAnimate,
FloorViewport,
ILayerRenderExtends,
@ -11,13 +10,7 @@ import {
LayerGroup,
Font
} from '@motajs/render';
import { WeatherController } from '@/module/weather';
import { FloorChange } from '@/plugin/fallback';
import { LayerGroupFilter } from '@/plugin/fx/gameCanvas';
import { LayerGroupHalo } from '@/plugin/fx/halo';
import { FloorItemDetail } from '@/plugin/fx/itemDetail';
import { PopText } from '@/plugin/fx/pop';
import { LayerGroupPortal } from '@/plugin/fx/portal';
import { WeatherController } from '../../weather';
import { defineComponent, onMounted, reactive, ref } from 'vue';
import { Textbox, Tip } from '../components';
import { GameUI, UIController } from '@motajs/system-ui';
@ -35,6 +28,16 @@ import {
} from './statusBar';
import { onLoaded } from '../use';
import { ReplayingStatus } from './toolbar';
import { getHeroStatusOn, HeroSkill, NightSpecial } from '@user/data-state';
import { jumpIgnoreFloor } from '@user/legacy-plugin-data';
import { hook } from '@user/data-base';
import { FloorDamageExtends } from '../damage';
import { FloorItemDetail } from '../itemDetail';
import { LayerGroupPortal } from '../legacy/portal';
import { LayerGroupFilter } from '../legacy/gameCanvas';
import { LayerGroupHalo } from '../legacy/halo';
import { FloorChange } from '../legacy/fallback';
import { PopText } from '../legacy/pop';
const MainScene = defineComponent(() => {
const layerGroupExtends: ILayerGroupRenderExtends[] = [
@ -112,8 +115,6 @@ const MainScene = defineComponent(() => {
night: 0
});
const { getHeroStatusOn } = Mota.requireAll('fn');
const updateStatus = () => {
if (!core.status || !core.status.hero || !core.status.floorId) return;
hideStatus.value = core.getFlag('hideStatusBar', false);
@ -135,7 +136,6 @@ const MainScene = defineComponent(() => {
leftStatus.exAtk = getHeroStatusOn('mana');
leftStatus.magicDef = getHeroStatusOn('magicDef');
const { HeroSkill, NightSpecial } = Mota.require('module', 'Mechanism');
rightStatus.autoSkill = HeroSkill.getAutoSkill();
rightStatus.skillName = HeroSkill.getSkillName();
rightStatus.skillDesc = HeroSkill.getSkillDesc();
@ -148,7 +148,7 @@ const MainScene = defineComponent(() => {
replayStatus.played = totalList.length - toReplay.length;
replayStatus.total = totalList.length;
if (HeroSkill.learnedSkill(HeroSkill.Jump)) {
if (Mota.Plugin.require('skill_g').jumpIgnoreFloor.has(floor)) {
if (jumpIgnoreFloor.has(floor)) {
rightStatus.jumpCount = -2;
} else {
rightStatus.jumpCount = 3 - (flags[`jump_${floor}`] ?? 0);
@ -168,7 +168,7 @@ const MainScene = defineComponent(() => {
loaded.value = true;
});
Mota.require('var', 'hook').on('statusBarUpdate', updateStatus);
hook.on('statusBarUpdate', updateStatus);
return () => (
<container id="main-scene" width={MAIN_WIDTH} height={MAIN_HEIGHT}>

View File

@ -14,7 +14,7 @@ import { mainUi } from '@motajs/legacy-ui';
import { gameKey } from '@motajs/system-action';
import { generateKeyboardEvent } from '@motajs/system-action';
import { getVitualKeyOnce } from '@motajs/legacy-ui';
import { getAllSavesData, getSaveData } from '@/module/utils';
import { getAllSavesData, getSaveData } from '../../utils';
export interface SettingsProps extends Partial<ChoicesProps>, UIComponentProps {
loc: ElementLocator;
@ -410,7 +410,9 @@ export const DownloadSaveSelect = defineComponent<SettingsProps>(props => {
{ text: '请等待处理完毕' }
);
core.download(
`${core.firstData.name}_${core.formatDate2(new Date())}.h5save`,
`${core.firstData.name}_${core.formatDate2(
new Date()
)}.h5save`,
data
);
}
@ -426,7 +428,9 @@ export const DownloadSaveSelect = defineComponent<SettingsProps>(props => {
if (confirm) {
const data = await getSaveData(core.saves.saveIndex);
core.download(
`${core.firstData.name}_${core.formatDate2(new Date())}.h5save`,
`${core.firstData.name}_${core.formatDate2(
new Date()
)}.h5save`,
data
);
}

View File

@ -18,6 +18,7 @@ import {
ReplayingStatus,
ReplayingToolbar
} from './toolbar';
import { HeroSkill } from '@user/data-state';
export interface ILeftHeroStatus {
hp: number;
@ -275,7 +276,6 @@ export const RightStatusBar = defineComponent<StatusBarProps<IRightHeroStatus>>(
};
const changeAutoSkill = () => {
const { HeroSkill } = Mota.require('module', 'Mechanism');
const auto = !s.autoSkill;
HeroSkill.setAutoSkill(auto);
core.status.route.push(`set:autoSkill:${auto}`);

View File

@ -13,17 +13,14 @@ import {
StepForward,
ViewMapIcon
} from '../components/icons';
import {
generateBinary,
getVitualKeyOnce,
openDanmakuPoster
} from '@motajs/legacy-ui';
import { getVitualKeyOnce, openDanmakuPoster } from '@motajs/legacy-ui';
import { gameKey } from '@motajs/system-action';
import { generateKeyboardEvent } from '@motajs/system-action';
import { transitioned } from '../use';
import { linear } from 'mutate-animate';
import { KeyCode } from '@motajs/client-base';
import { Progress } from '../components/misc';
import { generateBinary } from '@motajs/legacy-common';
interface ToolbarProps extends DefaultProps {
loc?: ElementLocator;

View File

@ -1,3 +1,4 @@
import { loading } from '@user/data-base';
import { TimingFn, Transition } from 'mutate-animate';
import {
ComponentInternalInstance,
@ -59,7 +60,6 @@ export function onOrientationChange(hook: OrientationHook) {
* @param hook
*/
export function onLoaded(hook: () => void) {
const loading = Mota.require('var', 'loading');
if (!loading.loaded) {
loading.once('loaded', hook);
} else {

View File

@ -10,6 +10,7 @@ import {
MotaOffscreenCanvas2D
} from '@motajs/render';
import { IWeather } from './weather';
import { loading } from '@user/data-base';
const snowVs = /* glsl */ `
in vec2 a_snowVertex;
@ -105,7 +106,7 @@ void main() {
/** 雨滴顶点坐标 */
const vertex = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
Mota.require('var', 'loading').once('coreInit', () => {
loading.once('coreInit', () => {
const shader = new SnowShader();
const gl = shader.gl;
shader.size(480, 480);

View File

@ -7,6 +7,9 @@ interface GameLoadEvent {
autotileLoaded: [];
coreInit: [];
loaded: [];
registered: [];
dataRegistered: [];
clientRegistered: [];
}
class GameLoading extends EventEmitter<GameLoadEvent> {
@ -58,6 +61,25 @@ class GameLoading extends EventEmitter<GameLoadEvent> {
}
export const loading = new GameLoading();
main.loading = loading;
let clientRegistered = false;
let dataRegistered = false;
function checkRegistered() {
if (clientRegistered && dataRegistered) {
loading.emit('registered');
}
}
loading.once('clientRegistered', () => {
clientRegistered = true;
checkRegistered();
});
loading.once('dataRegistered', () => {
dataRegistered = true;
checkRegistered();
});
export interface GameEvent {
/** Emitted in libs/events.js resetGame. */
@ -107,6 +129,8 @@ export interface GameEvent {
];
/** Emitted in lib/control.js */
replayStatus: [replaying: boolean];
/** Emitted in project/functions.js */
loadData: [];
}
export const hook = new EventEmitter<GameEvent>();

View File

@ -16,8 +16,9 @@ export interface CurrentEnemy {
onMapEnemy: DamageEnemy[];
}
function init() {
export function patchBattle() {
const patch = new Patch(PatchClass.Enemys);
const patch2 = new Patch(PatchClass.Events);
patch.add('canBattle', function (x, y, floorId) {
const enemy = typeof x === 'number' ? getEnemy(x, y!, floorId) : x;
@ -31,7 +32,7 @@ function init() {
return damage < core.status.hero.hp;
});
core.events.battle = function battle(
function battle(
x: number | DamageEnemy,
y: number,
force: boolean = false,
@ -59,7 +60,7 @@ function init() {
// 战后事件
core.afterBattle(enemy, isLoc ? x : enemy.x, y);
callback?.();
};
}
const getFacedId = (enemy: DamageEnemy) => {
const e = enemy.enemy;
@ -69,9 +70,7 @@ function init() {
return e.id;
};
core.enemys.getCurrentEnemys = function getCurrentEnemys(
floorId = core.status.floorId
) {
patch.add('getCurrentEnemys', function (floorId = core.status.floorId) {
floorId = floorId || core.status.floorId;
const enemys: CurrentEnemy[] = [];
const used: Record<string, DamageEnemy[]> = {};
@ -100,9 +99,9 @@ function init() {
const bd = b.enemy.calDamage().damage;
return ad - bd;
});
};
});
core.events._sys_battle = function (data: Block, callback?: () => void) {
patch2.add('_sys_battle', function (data: Block, callback?: () => void) {
// 检查战前事件
const floor = core.floors[core.status.floorId];
const beforeBattle: MotaEvent = [];
@ -126,11 +125,11 @@ function init() {
core.insertAction(beforeBattle, data.x, data.y, callback);
}
} else {
core.battle(data.x, data.y, false, callback);
battle(data.x, data.y, false, callback);
}
};
});
core.events._action_battle = function (data, x, y, prefix) {
patch2.add('_action_battle', function (data, x, y, prefix) {
if (data.id) {
const enemy = getSingleEnemy(data.id as EnemyIds);
// todo: 与不在地图上的怪物战斗
@ -139,21 +138,19 @@ function init() {
core.doAction();
return;
}
const [ex, ey] = this.__action_getLoc(
const [ex, ey] = core.events.__action_getLoc(
data.loc,
x,
y,
prefix
) as LocArr;
core.battle(ex, ey, true, core.doAction);
battle(ex, ey, true, core.doAction);
}
};
});
core.events.afterBattle = function afterBattle(
enemy: DamageEnemy,
x?: number,
y?: number
) {
patch2.add(
'afterBattle',
function (enemy: DamageEnemy, x?: number, y?: number) {
const floorId = core.status.floorId;
const special = enemy.info.special;
@ -165,7 +162,8 @@ function init() {
animate = core.material.items[equipId].equip.animate;
// 检查该动画是否存在SE如果不存在则使用默认音效
if (!core.material.animates[animate]?.se) core.playSound('attack.opus');
if (!core.material.animates[animate]?.se)
core.playSound('attack.opus');
// 战斗伤害
const info = enemy.calDamage(core.status.hero);
@ -218,7 +216,12 @@ function init() {
core.status.hero.statistics.exp += exp;
const hint =
'打败 ' + enemy.enemy.name + ',金币+' + money + ',经验+' + exp;
'打败 ' +
enemy.enemy.name +
',金币+' +
money +
',经验+' +
exp;
core.drawTip(hint, enemy.id);
HeroSkill.disableSkill();
@ -250,9 +253,10 @@ function init() {
core.checkAutoEvents();
hook.emit('afterBattle', enemy, x, y);
};
}
loading.once('coreInit', init);
);
}
loading.once('coreInit', patchBattle);
declare global {
interface Enemys {

View File

@ -0,0 +1,5 @@
import { patchBattle } from './battle';
export function patchAll() {
patchBattle();
}

View File

@ -1,9 +1,8 @@
import { getHeroStatusOf, getHeroStatusOn } from '../state/hero';
import { Range } from '@user/data-utils';
import { ensureArray, has, manhattan } from '@/plugin/game/utils';
import { Range, ensureArray, has, manhattan } from '@user/data-utils';
import EventEmitter from 'eventemitter3';
import { hook } from '@user/data-base';
import { HeroSkill, NightSpecial } from '../mechanism/misc';
import { HeroSkill, NightSpecial } from '../mechanism';
import {
EnemyInfo,
DamageInfo,
@ -14,8 +13,10 @@ import {
HaloFn,
IEnemyCollection,
IDamageEnemy,
HaloType
HaloType,
IEnemyCollectionEvent
} from '@motajs/types';
import { isNil } from 'lodash-es';
// todo: 光环划分优先级,从而可以实现光环的多级运算
@ -53,13 +54,8 @@ specialValue
.set(31, ['hpHalo'])
.set(32, ['assimilateRange']);
interface EnemyCollectionEvent {
extract: [];
calculated: [];
}
export class EnemyCollection
extends EventEmitter<EnemyCollectionEvent>
extends EventEmitter<IEnemyCollectionEvent>
implements IEnemyCollection
{
floorId: FloorIds;
@ -298,7 +294,7 @@ export class DamageEnemy implements IDamageEnemy {
// 融化融化不属于怪物光环因此不能用provide和inject计算需要在这里计算
const melt = flags[`melt_${floorId}`];
if (has(melt) && has(this.x) && has(this.y)) {
if (!isNil(melt) && !isNil(this.x) && !isNil(this.y)) {
for (const [loc, per] of Object.entries(melt)) {
const [mx, my] = loc.split(',').map(v => parseInt(v));
if (

View File

@ -1 +1,2 @@
export * from './misc';
export * from './skillTree';

View File

@ -1,7 +1,7 @@
import { backDir, has } from '@/plugin/game/utils';
import { backDir, has } from '@user/data-utils';
import { loading } from '@user/data-base';
import type { LayerDoorAnimate } from '@motajs/render';
import { getSkillLevel } from '@/plugin/game/skillTree';
import { getSkillLevel } from './skillTree';
/**
*
@ -48,131 +48,6 @@ export namespace NightSpecial {
}
}
export namespace HeroSkill {
export const enum Skill {
None,
/** 断灭之刃 */
Blade,
/** 铸剑为盾 */
Shield,
/** 跳跃 */
Jump
}
export const Blade = Skill.Blade;
export const Shield = Skill.Shield;
export const Jump = Skill.Jump;
const skillNameMap = new Map<Skill, string>([
[Skill.Blade, '断灭之刃'],
[Skill.Shield, '铸剑为盾'],
[Skill.Jump, '跳跃']
]);
const skillDesc = new Map<Skill, (level: number) => string>([
[
Skill.Blade,
level => `攻击上升 ${level * 10}%,防御下降 ${level * 10}%`
],
[
Skill.Shield,
level => `防御上升 ${level * 10}%,攻击下降 ${level * 10}%`
],
[Skill.Jump, () => `跳过前方障碍,或踢走面前的怪物`]
]);
interface SkillSave {
autoSkill: boolean;
learned: Skill[];
}
const learned = new Set<Skill>();
let autoSkill = true;
let enabled: Skill = Skill.None;
export function getLevel(skill: Skill = getEnabled()) {
switch (skill) {
case Blade:
return getSkillLevel(2);
case Jump:
return learned.has(Jump) ? 1 : 0;
case Shield:
return getSkillLevel(10);
}
return 0;
}
export function getSkillName(skill: Skill = getEnabled()) {
return skillNameMap.get(skill) ?? '未开启技能';
}
export function getSkillDesc(
skill: Skill = getEnabled(),
level: number = getLevel()
) {
return skillDesc.get(skill)?.(level) ?? '';
}
export function setAutoSkill(auto: boolean) {
autoSkill = auto;
}
export function getAutoSkill() {
return autoSkill;
}
export function learnedSkill(skill: Skill) {
return learned.has(skill);
}
export function learnSkill(skill: Skill) {
learned.add(skill);
}
export function forgetSkill(skill: Skill) {
learned.delete(skill);
}
export function clearSkill() {
learned.clear();
}
export function saveSkill(): SkillSave {
return { autoSkill, learned: [...learned] };
}
export function loadSkill(skills: SkillSave) {
learned.clear();
for (const skill of skills.learned) {
learned.add(skill);
}
autoSkill = skills.autoSkill;
}
export function getAll() {
return learned;
}
export function toggleSkill(skill: Skill) {
if (!learned.has(skill)) return;
if (enabled !== skill) enabled = skill;
else enabled = Skill.None;
}
export function enableSkill(skill: Skill) {
if (!learned.has(skill)) return;
enabled = skill;
}
export function disableSkill() {
enabled = Skill.None;
}
export function getEnabled() {
return enabled;
}
}
export namespace BluePalace {
type DoorConvertInfo = [id: AllIds, x: number, y: number];

View File

@ -1,5 +1,3 @@
import { HeroSkill } from '@/game/mechanism/misc';
let levels: number[] = [];
export type Chapter = 'chapter1' | 'chapter2';
@ -233,7 +231,7 @@ export function getSkillConsume(skill: number) {
export function openTree() {
if (main.replayChecking) return;
Mota.require('var', 'mainUi').open('skillTree');
Mota.require('@motajs/legacy-ui').mainUi.open('skillTree');
}
/**
@ -314,3 +312,128 @@ export function saveSkillTree() {
export function loadSkillTree(data: number[]) {
levels = data ?? [];
}
export namespace HeroSkill {
export const enum Skill {
None,
/** 断灭之刃 */
Blade,
/** 铸剑为盾 */
Shield,
/** 跳跃 */
Jump
}
export const Blade = Skill.Blade;
export const Shield = Skill.Shield;
export const Jump = Skill.Jump;
const skillNameMap = new Map<Skill, string>([
[Skill.Blade, '断灭之刃'],
[Skill.Shield, '铸剑为盾'],
[Skill.Jump, '跳跃']
]);
const skillDesc = new Map<Skill, (level: number) => string>([
[
Skill.Blade,
level => `攻击上升 ${level * 10}%,防御下降 ${level * 10}%`
],
[
Skill.Shield,
level => `防御上升 ${level * 10}%,攻击下降 ${level * 10}%`
],
[Skill.Jump, () => `跳过前方障碍,或踢走面前的怪物`]
]);
interface SkillSave {
autoSkill: boolean;
learned: Skill[];
}
const learned = new Set<Skill>();
let autoSkill = true;
let enabled: Skill = Skill.None;
export function getLevel(skill: Skill = getEnabled()) {
switch (skill) {
case Blade:
return getSkillLevel(2);
case Jump:
return learned.has(Jump) ? 1 : 0;
case Shield:
return getSkillLevel(10);
}
return 0;
}
export function getSkillName(skill: Skill = getEnabled()) {
return skillNameMap.get(skill) ?? '未开启技能';
}
export function getSkillDesc(
skill: Skill = getEnabled(),
level: number = getLevel()
) {
return skillDesc.get(skill)?.(level) ?? '';
}
export function setAutoSkill(auto: boolean) {
autoSkill = auto;
}
export function getAutoSkill() {
return autoSkill;
}
export function learnedSkill(skill: Skill) {
return learned.has(skill);
}
export function learnSkill(skill: Skill) {
learned.add(skill);
}
export function forgetSkill(skill: Skill) {
learned.delete(skill);
}
export function clearSkill() {
learned.clear();
}
export function saveSkill(): SkillSave {
return { autoSkill, learned: [...learned] };
}
export function loadSkill(skills: SkillSave) {
learned.clear();
for (const skill of skills.learned) {
learned.add(skill);
}
autoSkill = skills.autoSkill;
}
export function getAll() {
return learned;
}
export function toggleSkill(skill: Skill) {
if (!learned.has(skill)) return;
if (enabled !== skill) enabled = skill;
else enabled = Skill.None;
}
export function enableSkill(skill: Skill) {
if (!learned.has(skill)) return;
enabled = skill;
}
export function disableSkill() {
enabled = Skill.None;
}
export function getEnabled() {
return enabled;
}
}

View File

@ -1,7 +1,7 @@
import { logger } from '@motajs/common';
import { EventEmitter } from 'eventemitter3';
import { cloneDeep } from 'lodash-es';
import { HeroSkill, NightSpecial } from '../mechanism/misc';
import { HeroSkill, NightSpecial } from '../mechanism';
/**
*
@ -59,7 +59,7 @@ function getRealStatus(
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
floorId: FloorIds = core.status.floorId
): any {
const { getSkillLevel } = Mota.Plugin.require('skillTree_g');
const { getSkillLevel } = Mota.require('@user/legacy-plugin-data');
if (name instanceof Array) {
const res: any = {};
name.forEach(v => {

View File

@ -11,7 +11,7 @@ import type {
LayerMovingRenderable,
LayerFloorBinder
} from '@motajs/render';
import type { HeroKeyMover } from '@/module/action/move';
import type { HeroKeyMover } from '@user/client-modules';
import { BluePalace, MiscData } from '../mechanism/misc';
import { sleep } from '@motajs/common';
@ -939,7 +939,7 @@ export const heroMoveCollection: HeroMoveCollection = {
loading.once('coreInit', () => {
// 注册按键操作
Mota.r(() => {
const { HeroKeyMover } = Mota.require('@motajs/system-action');
const { HeroKeyMover } = Mota.require('@user/client-modules');
const { gameKey } = Mota.require('@motajs/system-action');
const keyMover = new HeroKeyMover(gameKey, heroMover);
heroMoveCollection.keyMover = keyMover;

View File

@ -1 +1,2 @@
export * from './range';
export * from './utils';

View File

@ -1,5 +1,5 @@
{
"name": "@motajs/entry-client",
"name": "@user/entry-client",
"dependencies": {
"@motajs/client": "workspace:*",
"@motajs/client-base": "workspace:*",
@ -16,6 +16,8 @@
"@motajs/legacy-client": "workspace:*",
"@motajs/legacy-data": "workspace:*",
"@motajs/legacy-ui": "workspace:*",
"@motajs/legacy-system": "workspace:*"
"@motajs/legacy-system": "workspace:*",
"@user/client-modules": "workspace:*",
"@user/legacy-plugin-client": "workspace:*"
}
}

View File

@ -14,8 +14,15 @@ import * as RenderVue from '@motajs/render-vue';
import * as System from '@motajs/system';
import * as SystemAction from '@motajs/system-action';
import * as SystemUI from '@motajs/system-ui';
import * as ClientModules from '@user/client-modules';
import * as LegacyPluginClient from '@user/legacy-plugin-client';
import * as MutateAnimate from 'mutate-animate';
import * as Vue from 'vue';
import { hook, loading } from '@user/data-base';
export function create() {
loading.once('registered', createModule);
Mota.register('@motajs/client', Client);
Mota.register('@motajs/client-base', ClientBase);
Mota.register('@motajs/common', Common);
@ -31,4 +38,20 @@ export function create() {
Mota.register('@motajs/system', System);
Mota.register('@motajs/system-action', SystemAction);
Mota.register('@motajs/system-ui', SystemUI);
Mota.register('@user/client-modules', ClientModules);
Mota.register('@user/legacy-plugin-client', LegacyPluginClient);
Mota.register('MutateAnimate', MutateAnimate);
Mota.register('Vue', Vue);
loading.emit('clientRegistered');
}
async function createModule() {
LegacyUI.create();
RenderElements.create();
ClientModules.create();
await import('ant-design-vue/dist/antd.dark.css');
main.renderLoaded = true;
hook.emit('renderLoaded');
}

View File

@ -1 +1,5 @@
export * from './create';
import { create } from './create';
export function createGame() {
create();
}

View File

@ -1,3 +1,11 @@
{
"name": "@user/entry-data"
"name": "@user/entry-data",
"dependencies": {
"@motajs/legacy-common": "workspace:*",
"@user/data-base": "workspace:*",
"@user/data-fallback": "workspace:*",
"@user/data-state": "workspace:*",
"@user/data-utils": "workspace:*",
"@user/legacy-plugin-data": "workspace:*"
}
}

View File

@ -0,0 +1,22 @@
import { Mota } from './mota';
import * as DataBase from '@user/data-base';
import * as DataFallback from '@user/data-fallback';
import * as DataState from '@user/data-state';
import * as DataUtils from '@user/data-utils';
import * as LegacyPluginData from '@user/legacy-plugin-data';
export function create() {
DataBase.loading.once('registered', createModule);
Mota.register('@user/data-base', DataBase);
Mota.register('@user/data-fallback', DataFallback);
Mota.register('@user/data-state', DataState);
Mota.register('@user/data-utils', DataUtils);
Mota.register('@user/legacy-plugin-data', LegacyPluginData);
DataBase.loading.emit('dataRegistered');
}
function createModule() {
LegacyPluginData.create();
}

View File

@ -1,5 +1,15 @@
import { createMota } from './mota';
import { create } from './create';
import { patchAll } from '@user/data-fallback';
import { loading } from '@user/data-base';
import { Patch } from '@motajs/legacy-common';
createMota();
patchAll();
create();
loading.once('coreInit', () => {
Patch.patchAll();
});
export * from './mota';

View File

@ -13,6 +13,16 @@ import type * as RenderVue from '@motajs/render-vue';
import type * as System from '@motajs/system';
import type * as SystemAction from '@motajs/system-action';
import type * as SystemUI from '@motajs/system-ui';
import type * as ClientModules from '@user/client-modules';
import type * as DataBase from '@user/data-base';
import type * as DataFallback from '@user/data-fallback';
import type * as DataState from '@user/data-state';
import type * as DataUtils from '@user/data-utils';
import type * as LegacyPluginClient from '@user/legacy-plugin-client';
import type * as LegacyPluginData from '@user/legacy-plugin-data';
// ---------- 必要的第三方库
import type * as MutateAnimate from 'mutate-animate';
import type * as Vue from 'vue';
interface ModuleInterface {
'@motajs/client': typeof Client;
@ -30,6 +40,16 @@ interface ModuleInterface {
'@motajs/system': typeof System;
'@motajs/system-action': typeof SystemAction;
'@motajs/system-ui': typeof SystemUI;
'@user/client-modules': typeof ClientModules;
'@user/data-base': typeof DataBase;
'@user/data-fallback': typeof DataFallback;
'@user/data-state': typeof DataState;
'@user/data-utils': typeof DataUtils;
'@user/legacy-plugin-client': typeof LegacyPluginClient;
'@user/legacy-plugin-data': typeof LegacyPluginData;
// ---------- 必要的第三方库
MutateAnimate: typeof MutateAnimate;
Vue: typeof Vue;
}
export interface IMota {
@ -73,10 +93,6 @@ class MotaSystem implements IMota {
r = r;
rf = rf;
constructor() {
throw new Error(`System interface class cannot be constructed.`);
}
require(key: string): any {
const data = this.modules[key];
if (data) return data;
@ -137,7 +153,7 @@ declare global {
}
}
export const Mota = new MotaSystem();
export const Mota: IMota = new MotaSystem();
export function createMota() {
window.Mota = Mota;

View File

@ -0,0 +1,7 @@
{
"name": "@user/legacy-plugin-client",
"dependencies": {
"@user/client-modules": "workspace:*",
"@user/data-state": "workspace:*"
}
}

View File

@ -4,7 +4,7 @@ import {
RenderItemPosition,
Transform
} from '@motajs/render';
import { IStateDamageable } from '@/game/state/interface';
import { IStateDamageable } from '@user/data-state';
import EventEmitter from 'eventemitter3';
import { Ticker } from 'mutate-animate';

View File

@ -1,3 +1,4 @@
import { hook } from '@user/data-base';
import { BarrageBoss } from './barrage';
import { TowerBoss } from './towerBoss';
@ -15,7 +16,7 @@ export function getBoss<T extends BarrageBoss>(): T | null {
return boss as T;
}
Mota.require('var', 'hook').on('reset', () => {
hook.on('reset', () => {
if (boss) {
boss.end();
}

View File

@ -1,4 +1,4 @@
import { IStateDamageable } from '@/game/state/interface';
import { IStateDamageable } from '@user/data-state';
import { BarrageBoss, BossSprite, Hitbox } from './barrage';
import {
Container,
@ -9,11 +9,13 @@ import {
Transform,
MotaOffscreenCanvas2D
} from '@motajs/render';
import { Pop } from '../fx/pop';
import { Pop } from '../../../client-modules/src/render/legacy/pop';
import { SplittableBall } from './palaceBossProjectile';
import { PointEffect } from '../fx/pointShader';
import { loading } from '@user/data-base';
import { clip } from '@user/legacy-plugin-data';
Mota.require('var', 'loading').once('coreInit', () => {
loading.once('coreInit', () => {
const shader = new Shader();
shader.size(480, 480);
shader.setHD(true);
@ -102,7 +104,7 @@ export class PalaceBoss extends BarrageBoss {
PalaceBoss.effect.end();
core.status.hero.hp = this.heroHp;
Mota.Plugin.require('replay_g').clip('choices:0');
clip('choices:0');
}
ai(time: number, frame: number): void {}

View File

@ -1,5 +1,5 @@
import { Transform, MotaOffscreenCanvas2D } from '@motajs/render';
import { IStateDamageable } from '@/game/state/interface';
import { IStateDamageable } from '@user/data-state';
import { Hitbox, Projectile } from './barrage';
import type { PalaceBoss } from './palaceBoss';
import { clamp } from '@motajs/legacy-ui';

View File

@ -21,11 +21,13 @@ import {
ThunderBallProjectile,
ThunderProjectile
} from './towerBossProjectile';
import { IStateDamageable } from '@/game/state/interface';
import { Pop } from '../fx/pop';
import { WeatherController } from '@/module';
import { IStateDamageable } from '@user/data-state';
import { Pop } from '../../../client-modules/src/render/legacy/pop';
import { loading } from '@user/data-base';
import { clip } from '@user/legacy-plugin-data';
import { WeatherController } from '@user/client-modules';
Mota.require('var', 'loading').once('coreInit', () => {
loading.once('coreInit', () => {
const shader = new Shader();
shader.size(480, 480);
shader.setHD(true);
@ -198,7 +200,7 @@ export class TowerBoss extends BarrageBoss {
TowerBoss.effect.end();
core.status.hero.hp = this.heroHp;
Mota.Plugin.require('replay_g').clip('choices:0');
clip('choices:0');
}
/**

View File

@ -2,7 +2,7 @@ import { hyper, power, TimingFn } from 'mutate-animate';
import { Hitbox, Projectile } from './barrage';
import { MotaOffscreenCanvas2D, Transform } from '@motajs/render';
import type { TowerBoss } from './towerBoss';
import { IStateDamageable } from '@/game/state/interface';
import { IStateDamageable } from '@user/data-state';
import { PointEffect, PointEffectType } from '../fx/pointShader';
import { isNil } from 'lodash-es';

View File

@ -9,7 +9,12 @@ import {
disableViewport,
enableViewport
} from '@motajs/render';
import type { HeroMover, MoveStep } from '@/game/state/move';
import { loading } from '@user/data-base';
import {
heroMoveCollection,
type HeroMover,
type MoveStep
} from '@user/data-state';
import EventEmitter from 'eventemitter3';
export interface IChaseController {
@ -108,7 +113,7 @@ export class Chase extends EventEmitter<ChaseEvent> {
const layer = render.getElementById('layer-main')! as LayerGroup;
this.layer = layer;
const mover = Mota.require('module', 'State').heroMoveCollection.mover;
const mover = heroMoveCollection.mover;
this.heroMove = mover;
mover.on('stepEnd', this.onStepEnd);
@ -345,7 +350,7 @@ export class Chase extends EventEmitter<ChaseEvent> {
}
}
Mota.require('var', 'loading').once('coreInit', () => {
loading.once('coreInit', () => {
const shader = new Shader();
Chase.shader = shader;
shader.size(480, 480);

View File

@ -1,6 +1,6 @@
import { Animation, hyper, linear, power, sleep } from 'mutate-animate';
import { Chase, ChaseData, IChaseController } from './chase';
import { completeAchievement } from '@motajs/legacy-ui';
// import { completeAchievement } from '@motajs/legacy-ui';
import {
Camera,
CameraAnimation,
@ -10,7 +10,9 @@ import {
Sprite
} from '@motajs/render';
import { PointEffect, PointEffectType } from '../fx/pointShader';
import { bgmController } from '@/module';
import { bgmController } from '@user/client-modules';
import { loading } from '@user/data-base';
import { chaseInit1, clip } from '@user/legacy-plugin-data';
const path: Partial<Record<FloorIds, LocArr[]>> = {
MT16: [
@ -107,7 +109,7 @@ let back: Sprite | undefined;
let contrastId: number = 0;
const effect = new PointEffect();
Mota.require('var', 'loading').once('loaded', () => {
loading.once('loaded', () => {
effect.create(Chase.shader, 40);
});
@ -204,7 +206,7 @@ export function initChase(): IChaseController {
core.removeFlag('chaseId');
if (success) {
completeAchievement('challenge', 0);
// completeAchievement('challenge', 0);
}
});
@ -215,7 +217,7 @@ export function initChase(): IChaseController {
para3(chase, ani);
processScale(chase, ani, scale, camera);
Mota.Plugin.require('chase_g').chaseInit1();
chaseInit1();
chase.on('end', () => {
effect.end();
@ -712,7 +714,7 @@ function para3(chase: Chase, ani: Animation) {
});
chase.onceLoc(21, 7, 'MT14', async () => {
flags.finishChase1 = true;
Mota.Plugin.require('replay_g').clip('choices:0');
clip('choices:0');
core.showStatusBar();
ani.time(750).apply('rect', 0);
chase.end(true);

View File

@ -1,5 +1,5 @@
import { Animation, linear, sleep } from 'mutate-animate';
import { has } from '@motajs/legacy-ui';
// import { has } from '@motajs/legacy-ui';
// todo: 移植到渲染树
@ -28,37 +28,37 @@ const FRAG_TIMING = linear();
export function init() {
return;
Mota.rewrite(core.events, 'afterBattle', 'add', (_, enemy, x, y) => {
// 打怪特效
const setting = Mota.require('var', 'mainSetting');
if (setting.getValue('fx.frag') && has(x) && has(y)) {
const frame = core.status.globalAnimateStatus % 2;
const canvas = document.createElement('canvas');
canvas.width = 32;
canvas.height = 32;
core.drawIcon(canvas, enemy.id, 0, 0, 32, 32, frame);
const manager = applyFragWith(canvas);
const frag = manager.canvas;
frag.style.imageRendering = 'pixelated';
frag.style.width = `${frag.width * core.domStyle.scale}px`;
frag.style.height = `${frag.height * core.domStyle.scale}px`;
const left =
(x * 32 + 16 - frag.width / 2 - core.bigmap.offsetX) *
core.domStyle.scale;
const top =
(y * 32 + 16 - frag.height / 2 - core.bigmap.offsetY) *
core.domStyle.scale;
frag.style.left = `${left}px`;
frag.style.top = `${top}px`;
frag.style.zIndex = '45';
frag.style.position = 'absolute';
frag.style.filter = 'sepia(20%)brightness(120%)';
core.dom.gameDraw.appendChild(frag);
manager.onEnd.then(() => {
frag.remove();
});
}
});
// Mota.rewrite(core.events, 'afterBattle', 'add', (_, enemy, x, y) => {
// // 打怪特效
// const setting = Mota.require('var', 'mainSetting');
// if (setting.getValue('fx.frag') && has(x) && has(y)) {
// const frame = core.status.globalAnimateStatus % 2;
// const canvas = document.createElement('canvas');
// canvas.width = 32;
// canvas.height = 32;
// core.drawIcon(canvas, enemy.id, 0, 0, 32, 32, frame);
// const manager = applyFragWith(canvas);
// const frag = manager.canvas;
// frag.style.imageRendering = 'pixelated';
// frag.style.width = `${frag.width * core.domStyle.scale}px`;
// frag.style.height = `${frag.height * core.domStyle.scale}px`;
// const left =
// (x * 32 + 16 - frag.width / 2 - core.bigmap.offsetX) *
// core.domStyle.scale;
// const top =
// (y * 32 + 16 - frag.height / 2 - core.bigmap.offsetY) *
// core.domStyle.scale;
// frag.style.left = `${left}px`;
// frag.style.top = `${top}px`;
// frag.style.zIndex = '45';
// frag.style.position = 'absolute';
// frag.style.filter = 'sepia(20%)brightness(120%)';
// core.dom.gameDraw.appendChild(frag);
// manager.onEnd.then(() => {
// frag.remove();
// });
// }
// });
}
export function applyFragWith(

View File

@ -0,0 +1 @@
export * from './pointShader';

View File

@ -0,0 +1,3 @@
export * from './boss';
export * from './chase';
export * from './fx';

View File

@ -0,0 +1,8 @@
{
"name": "@user/legacy-plugin-data",
"dependencies": {
"@user/data-state": "workspace:*",
"@user/data-base": "workspace:*",
"@user/data-utils": "workspace:*"
}
}

View File

@ -1,4 +1,5 @@
///<reference path="../../../../src/types/core.d.ts" />
// @ts-nocheck
export {};
/* @__PURE__ */ (function () {
@ -55,8 +56,8 @@ export {};
core.status.maps[data].enemy?.calRealAttribute();
core.updateStatusBar(true, true);
}
Mota.require('module', 'Shadow').Shadow.update(true);
const Binder = Mota.require('module', 'Render').LayerGroupFloorBinder;
Mota.require('@motajs/legacy-ui').Shadow.update(true);
const Binder = Mota.require('@motajs/render').LayerGroupFloorBinder;
Binder.activedBinder.forEach(v => {
if (v.getFloor() === core.status.floorId) {
v.updateBindData();

View File

@ -1,4 +1,4 @@
import { has, ofDir } from '@/plugin/game/utils';
import { has, ofDir } from '@user/data-utils';
export function init() {
// 伤害弹出
@ -11,11 +11,11 @@ export function init() {
const damage = info?.damage;
if (damage) {
if (!main.replayChecking) {
Mota.Plugin.require('pop_r').addPop(
(x - core.bigmap.offsetX / 32) * 32 + 12,
(y - core.bigmap.offsetY / 32) * 32 + 20,
(-damage).toString()
);
// addPop(
// (x - core.bigmap.offsetX / 32) * 32 + 12,
// (y - core.bigmap.offsetY / 32) * 32 + 20,
// (-damage).toString()
// );
}
core.status.hero.hp -= damage;
const type = [...info.type];

View File

@ -0,0 +1,6 @@
import { init as initCheckBlock } from './checkblock';
initCheckBlock();
export * from './checkblock';
export * from './remainEnemy';

View File

@ -0,0 +1,720 @@
import type {
RenderAdapter,
LayerDoorAnimate,
LayerGroupAnimate,
LayerFloorBinder,
HeroRenderer,
Layer,
LayerGroup,
FloorViewport
} from '@motajs/render';
import type { TimingFn } from 'mutate-animate';
import { BlockMover, heroMoveCollection, MoveStep } from '@user/data-state';
import { hook, loading } from '@user/data-base';
import { Patch, PatchClass } from '@motajs/legacy-common';
// 向后兼容用,会充当两个版本间过渡的作用
interface Adapters {
'hero-adapter'?: RenderAdapter<HeroRenderer>;
'door-animate'?: RenderAdapter<LayerDoorAnimate>;
animate?: RenderAdapter<LayerGroupAnimate>;
layer?: RenderAdapter<Layer>;
viewport?: RenderAdapter<FloorViewport>;
}
const adapters: Adapters = {};
export function initFallback() {
let fallbackIds: number = 1e8;
if (!main.replayChecking && main.mode === 'play') {
const Adapter = Mota.require('@motajs/render').RenderAdapter;
const hero = Adapter.get<HeroRenderer>('hero-adapter');
const doorAnimate = Adapter.get<LayerDoorAnimate>('door-animate');
const animate = Adapter.get<LayerGroupAnimate>('animate');
const layer = Adapter.get<Layer>('layer');
const viewport = Adapter.get<FloorViewport>('viewport');
adapters['hero-adapter'] = hero;
adapters['door-animate'] = doorAnimate;
adapters['animate'] = animate;
adapters['layer'] = layer;
adapters['viewport'] = viewport;
}
const { mover: heroMover } = heroMoveCollection;
// ----- 工具函数
/**
*
*/
function getMoveSteps(steps: string[]) {
const moveSteps: string[] = [];
steps.forEach(v => {
const [type, number] = v.split(':');
if (!number) moveSteps.push(type);
else {
if (type === 'speed') moveSteps.push(v);
else {
moveSteps.push(...Array(Number(number)).fill(type));
}
}
});
return moveSteps;
}
function setHeroDirection(dir: Dir) {
heroMover.setFaceDir(dir);
heroMover.setMoveDir(dir);
}
/**
*
*/
function generateJumpFn(dx: number, dy: number): TimingFn<3> {
const distance = Math.hypot(dx, dy);
const peak = 3 + distance;
return (progress: number) => {
const x = dx * progress;
const y = progress * dy + (progress ** 2 - progress) * peak;
return [x, y, Math.ceil(y)];
};
}
Mota.r(() => {
// ----- 引入
const { Camera, MotaRenderer: Renderer } =
Mota.require('@motajs/render');
const Animation = Mota.require('MutateAnimate');
const patch = new Patch(PatchClass.Control);
const patch2 = new Patch(PatchClass.Events);
const patch3 = new Patch(PatchClass.Maps);
//#region 勇士移动相关
patch.add('moveAction', async function (callback?: () => void) {
heroMover.clearMoveQueue();
heroMover.oneStep('forward');
const lock = core.status.lockControl;
const controller = heroMover.startMove(false, true, lock);
controller?.onEnd.then(() => {
callback?.();
});
heroMover.once('stepEnd', () => {
controller?.stop();
});
});
patch.add('_moveAction_moving', () => {});
patch2.add(
'_action_moveAction',
function (data: any, x: number, y: number, prefix: any) {
if (core.canMoveHero()) {
var nx = core.nextX(),
ny = core.nextY();
// 检查noPass决定是撞击还是移动
if (core.noPass(nx, ny)) {
core.insertAction([{ type: 'trigger', loc: [nx, ny] }]);
} else {
// 先移动一格,然后尝试触发事件
core.insertAction([
{
type: 'function',
function:
'function() { core.moveAction(core.doAction); }',
async: true
},
{ type: '_label' }
]);
}
}
core.doAction();
}
);
patch2.add(
'eventMoveHero',
async function (
steps: string[],
time: number = 500,
callback?: () => void
) {
if (heroMover.moving) return;
const moveSteps = getMoveSteps(steps);
const resolved = moveSteps.map<MoveStep>(v => {
if (v.startsWith('speed')) {
return { type: 'speed', value: Number(v.slice(6)) };
} else {
return { type: 'dir', value: v as Move2 };
}
});
const start: MoveStep = { type: 'speed', value: time };
heroMover.insertMove(...[start, ...resolved]);
const controller = heroMover.startMove(true, true, true, false);
if (!controller) {
callback?.();
return;
}
controller.onEnd.then(() => {
callback?.();
});
const animate = fallbackIds++;
core.animateFrame.lastAsyncId = animate;
core.animateFrame.asyncId[animate] = controller.stop;
}
);
patch.add(
'setHeroLoc',
function (
name: 'x' | 'y' | 'direction',
value: number | Dir,
noGather?: boolean
) {
if (!core.status.hero) return;
// @ts-ignore
core.status.hero.loc[name] = value;
if ((name === 'x' || name === 'y') && !noGather) {
core.control.gatherFollowers();
}
if (name === 'direction') {
adapters['hero-adapter']?.sync('turn', value);
adapters['hero-adapter']?.sync('setAnimateDir', value);
setHeroDirection(value as Dir);
} else if (name === 'x') {
// 为了防止逆天样板出问题
core.bigmap.posX = value as number;
adapters['hero-adapter']?.sync('setHeroLoc', value);
} else {
// 为了防止逆天样板出问题
core.bigmap.posY = value as number;
adapters['hero-adapter']?.sync('setHeroLoc', void 0, value);
}
}
);
patch.add('waitHeroToStop', function (callback?: () => void) {
core.stopAutomaticRoute();
core.clearContinueAutomaticRoute();
heroMover.controller?.stop();
if (callback) {
core.status.replay.animate = true;
core.lockControl();
core.status.automaticRoute.moveDirectly = false;
setTimeout(
function () {
core.status.replay.animate = false;
callback();
},
core.status.replay.speed === 24 ? 1 : 30
);
}
});
patch.add(
'moveHero',
async function (
direction?: Dir,
callback?: () => void,
noRoute: boolean = false
) {
if (heroMover.moving) return;
heroMover.clearMoveQueue();
heroMover.oneStep(direction ?? 'forward');
const lock = core.status.lockControl;
const controller = heroMover.startMove(false, noRoute, lock);
controller?.onEnd.then(() => {
callback?.();
});
heroMover.once('stepEnd', () => {
controller?.stop();
});
}
);
patch2.add('setHeroIcon', function (name: ImageIds) {
const img = core.material.images.images[name];
if (!img) return;
core.status.hero.image = name;
adapters['hero-adapter']?.sync('setImage', img);
});
patch.add('isMoving', function () {
return heroMover.moving;
});
patch.add(
'setAutomaticRoute',
function (destX: number, destY: number, stepPostfix: DiredLoc[]) {
if (heroMover.moving) return;
if (!core.status.played || core.status.lockControl) return;
if (core.control._setAutomaticRoute_isMoving(destX, destY))
return;
if (
core.control._setAutomaticRoute_isTurning(
destX,
destY,
stepPostfix
)
)
return;
if (
core.control._setAutomaticRoute_clickMoveDirectly(
destX,
destY,
stepPostfix
)
)
return;
// 找寻自动寻路路线
const moveStep = core.automaticRoute(destX, destY);
if (
moveStep.length == 0 &&
(destX != core.status.hero.loc.x ||
destY != core.status.hero.loc.y ||
stepPostfix.length == 0)
)
return;
moveStep.push(...stepPostfix);
core.status.automaticRoute.destX = destX;
core.status.automaticRoute.destY = destY;
core.control._setAutomaticRoute_drawRoute(moveStep);
core.control._setAutomaticRoute_setAutoSteps(moveStep);
// ???
core.setAutoHeroMove();
// 执行移动
const steps: MoveStep[] = moveStep.map(v => {
return { type: 'dir', value: v.direction };
});
heroMover.clearMoveQueue();
heroMover.insertMove(...steps);
heroMover.startMove();
}
);
//#region 开关门
patch2.add(
'openDoor',
function (
x: number,
y: number,
needKey: boolean,
callback?: () => void
) {
var block = core.getBlock(x, y);
core.saveAndStopAutomaticRoute();
if (!core.events._openDoor_check(block, x, y, needKey)) {
var locked = core.status.lockControl;
core.waitHeroToStop(function () {
if (!locked) core.unlockControl();
if (callback) callback();
});
return;
}
if (core.status.replay.speed === 24) {
core.status.replay.animate = true;
core.removeBlock(x, y);
setTimeout(function () {
core.status.replay.animate = false;
hook.emit(
'afterOpenDoor',
block.event.id as AllIdsOf<'animates'>,
x,
y
);
if (callback) callback();
}, 1); // +1是为了录像检测系统
} else {
const locked = core.status.lockControl;
core.lockControl();
core.status.replay.animate = true;
core.removeBlock(x, y);
const cb = () => {
core.maps._removeBlockFromMap(
core.status.floorId,
block
);
if (!locked) core.unlockControl();
core.status.replay.animate = false;
hook.emit(
'afterOpenDoor',
block.event.id as AllIdsOf<'animates'>,
x,
y
);
callback?.();
};
adapters['door-animate']?.all('openDoor', block).then(cb);
const animate = fallbackIds++;
core.animateFrame.lastAsyncId = animate;
core.animateFrame.asyncId[animate] = cb;
// this._openDoor_animate(block, x, y, callback);
}
}
);
patch2.add(
'closeDoor',
function (x: number, y: number, id: AllIds, callback?: () => void) {
id = id || '';
if (
// @ts-ignore
(core.material.icons.animates[id] == null &&
// @ts-ignore
core.material.icons.npc48[id] == null) ||
core.getBlock(x, y) != null
) {
if (callback) callback();
return;
}
var block = core.getBlockById(id);
var doorInfo = (block.event || {}).doorInfo;
if (!doorInfo) {
if (callback) callback();
return;
}
core.playSound(doorInfo.closeSound);
const locked = core.status.lockControl;
core.lockControl();
core.status.replay.animate = true;
const cb = function () {
if (!locked) core.unlockControl();
core.status.replay.animate = false;
core.setBlock(id, x, y);
core.showBlock(x, y);
callback?.();
};
if (core.status.replay.speed === 24) {
cb();
} else {
adapters['door-animate']
?.all('closeDoor', block)
.then(() => {
cb();
});
const animate = fallbackIds++;
core.animateFrame.lastAsyncId = animate;
core.animateFrame.asyncId[animate] = cb;
core.events._openDoor_animate(block, x, y, callback);
}
}
);
//#region 动画
patch3.add(
'drawAnimate',
function (
name: AnimationIds,
x: number,
y: number,
alignWindow?: boolean,
callback?: () => void
) {
// @ts-ignore
name = core.getMappedName(name);
// 正在播放录像:不显示动画
if (
core.isReplaying() ||
!core.material.animates[name] ||
x == null ||
y == null
) {
if (callback) callback();
return -1;
}
adapters.animate
?.all(
'drawAnimate',
name,
x * 32 + 16,
y * 32 + 16,
alignWindow ?? false
)
.then(() => {
callback?.();
});
}
);
patch3.add(
'drawHeroAnimate',
function (name: AnimationIds, callback?: () => void) {
// @ts-ignore
name = core.getMappedName(name);
// 正在播放录像或动画不存在:不显示动画
if (core.isReplaying() || !core.material.animates[name]) {
if (callback) callback();
return -1;
}
adapters.animate?.global('drawHeroAnimate', name).then(() => {
callback?.();
});
}
);
patch3.add(
'moveBlock',
async function (
x: number,
y: number,
steps: string[],
time: number = 500,
keep: boolean = false,
callback?: () => void
) {
if (!steps || steps.length === 0) {
callback?.();
return;
}
const block = core.getBlock(x, y);
if (!block) {
callback?.();
return;
}
const mover = new BlockMover(
x,
y,
core.status.floorId,
'event'
);
const moveSteps = getMoveSteps(steps);
const resolved = moveSteps.map<MoveStep>(v => {
if (v.startsWith('speed')) {
return { type: 'speed', value: Number(v.slice(6)) };
} else {
return { type: 'dir', value: v as Move2 };
}
});
const start: MoveStep = { type: 'speed', value: time };
mover.insertMove(...[start, ...resolved]);
const controller = mover.startMove();
if (controller) {
await controller.onEnd;
}
if (!keep) {
core.removeBlock(mover.x, mover.y);
}
callback?.();
}
);
patch3.add(
'jumpBlock',
async function (
sx: number,
sy: number,
ex: number,
ey: number,
time: number = 500,
keep: boolean = false,
callback?: () => void
) {
const block = core.getBlock(sx, sy);
if (!block) {
callback?.();
return;
}
time /= core.status.replay.speed;
if (core.status.replay.speed === 24) time = 1;
const dx = ex - sx;
const dy = ey - sy;
const fn = generateJumpFn(dx, dy);
const list = adapters.layer?.items ?? [];
const items = [...list].filter(v => {
if (v.layer !== 'event') return false;
const ex = v.getExtends('floor-binder') as LayerFloorBinder;
if (!ex) return false;
return ex.getFloor() === core.status.floorId;
});
const width = core.status.thisMap.width;
const index = sx + sy * width;
const promise = Promise.all(
items.map(v => {
return v.moveAs(index, ex, ey, fn, time, keep);
})
);
core.updateStatusBar();
core.removeBlock(sx, sy);
await promise;
if (keep) {
core.setBlock(block.id, ex, ey);
}
core.updateStatusBar();
callback?.();
}
);
patch2.add(
'jumpHero',
async function (
ex: number,
ey: number,
time: number = 500,
callback?: () => void
) {
if (heroMover.moving) return;
const sx = core.getHeroLoc('x');
const sy = core.getHeroLoc('y');
adapters.viewport?.all('mutateTo', ex, ey, time);
const locked = core.status.lockControl;
core.lockControl();
const list = adapters['hero-adapter']?.items ?? [];
const items = [...list];
time /= core.status.replay.speed;
if (core.status.replay.speed === 24) time = 1;
const fn = generateJumpFn(ex - sx, ey - sy);
await Promise.all(
items.map(v => {
if (!v.renderable) return Promise.reject();
return v.layer.moveRenderable(
v.renderable,
sx,
sy,
fn,
time
);
})
);
if (!locked) core.unlockControl();
core.setHeroLoc('x', ex);
core.setHeroLoc('y', ey);
callback?.();
}
);
//#region 视角处理
patch.add(
'moveDirectly',
function (destX: number, destY: number, ignoreSteps: number) {
const data = core.control.controldata;
const success = data.moveDirectly(destX, destY, ignoreSteps);
if (success) adapters.viewport?.all('mutateTo', destX, destY);
return success;
}
);
patch.add(
'moveViewport',
function (
x: number,
y: number,
_moveMode: EaseMode,
time: number = 1,
callback?: () => void
) {
const main = Renderer.get('render-main');
const layer = main?.getElementById('layer-main') as LayerGroup;
if (!layer) return;
const camera = Camera.for(layer);
camera.clearOperation();
const translate = camera.addTranslate();
const animateTime =
time / Math.max(core.status.replay.speed, 1);
const animate = new Animation.Animation();
animate
.absolute()
.time(1)
.mode(Animation.linear())
.move(core.bigmap.offsetX, core.bigmap.offsetY);
animate.time(animateTime).move(x * 32, y * 32);
camera.applyTranslateAnimation(
translate,
animate,
animateTime + 50
);
camera.transform = layer.camera;
const end = () => {
core.bigmap.offsetX = x * 32;
core.bigmap.offsetY = y * 32;
camera.destroy();
callback?.();
};
const timeout = window.setTimeout(end, animateTime + 50);
const id = fallbackIds++;
core.animateFrame.lastAsyncId = id;
core.animateFrame.asyncId[id] = () => {
end();
clearTimeout(timeout);
};
}
);
});
loading.once('loaded', () => {
for (const animate of Object.values(core.material.animates)) {
animate.se ??= {};
if (typeof animate.se === 'string') {
animate.se = { 1: animate.se };
}
animate.pitch ??= {};
}
});
loading.once('coreInit', () => {
const moveAction = new Set<string>(['up', 'down', 'left', 'right']);
// 复写录像的移动
core.registerReplayAction('move', action => {
if (moveAction.has(action)) {
if (!heroMover.moving) {
heroMover.startMove();
}
if (!heroMover.controller) {
return false;
}
heroMover.controller.push({
type: 'dir',
value: action as Dir
});
heroMover.controller.onEnd.then(() => {
core.replay();
});
return true;
} else {
return false;
}
});
});
}

Some files were not shown because too many files have changed in this diff Show More