mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-25 00:23:25 +08:00
Compare commits
No commits in common. "05e254fee05f3aaaa670f59ea68a8c75ade95998" and "cca10a429c2c262b5bf2bf3612d53d412b01714f" have entirely different histories.
05e254fee0
...
cca10a429c
3
.gitignore
vendored
3
.gitignore
vendored
@ -49,5 +49,4 @@ script/people.ts
|
||||
user.ts
|
||||
.antlr
|
||||
graph.svg
|
||||
docs/.vitepress/cache
|
||||
docs/.vitepress/dist
|
||||
docs/.vitepress
|
@ -1,59 +0,0 @@
|
||||
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: '中文'
|
||||
}
|
||||
}
|
||||
});
|
@ -1,5 +0,0 @@
|
||||
---
|
||||
lang: zh-CN
|
||||
---
|
||||
|
||||
# API 列表
|
@ -1,34 +0,0 @@
|
||||
---
|
||||
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)
|
@ -1,157 +0,0 @@
|
||||
---
|
||||
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` 文件夹中是如何做的。
|
@ -4,7 +4,7 @@ layout: home
|
||||
|
||||
hero:
|
||||
name: 'mota-js'
|
||||
text: 'HTML5魔塔样板V2.B'
|
||||
text: 'HTML5魔塔样板V2.A'
|
||||
tagline: HTML5魔塔样板从 2.x 到 3.0 的过渡版本
|
||||
actions:
|
||||
- theme: brand
|
||||
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "@user/client-modules",
|
||||
"dependencies": {
|
||||
"@motajs/render": "workspace:*",
|
||||
"@motajs/legacy-ui": "workspace:*",
|
||||
"@user/legacy-plugin-client": "workspace:*"
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './move';
|
@ -1,20 +0,0 @@
|
||||
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';
|
@ -7,9 +7,6 @@ interface GameLoadEvent {
|
||||
autotileLoaded: [];
|
||||
coreInit: [];
|
||||
loaded: [];
|
||||
registered: [];
|
||||
dataRegistered: [];
|
||||
clientRegistered: [];
|
||||
}
|
||||
|
||||
class GameLoading extends EventEmitter<GameLoadEvent> {
|
||||
@ -61,25 +58,6 @@ 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. */
|
||||
@ -129,8 +107,6 @@ export interface GameEvent {
|
||||
];
|
||||
/** Emitted in lib/control.js */
|
||||
replayStatus: [replaying: boolean];
|
||||
/** Emitted in project/functions.js */
|
||||
loadData: [];
|
||||
}
|
||||
|
||||
export const hook = new EventEmitter<GameEvent>();
|
||||
|
@ -16,9 +16,8 @@ export interface CurrentEnemy {
|
||||
onMapEnemy: DamageEnemy[];
|
||||
}
|
||||
|
||||
export function patchBattle() {
|
||||
function init() {
|
||||
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;
|
||||
@ -32,7 +31,7 @@ export function patchBattle() {
|
||||
return damage < core.status.hero.hp;
|
||||
});
|
||||
|
||||
function battle(
|
||||
core.events.battle = function battle(
|
||||
x: number | DamageEnemy,
|
||||
y: number,
|
||||
force: boolean = false,
|
||||
@ -60,7 +59,7 @@ export function patchBattle() {
|
||||
// 战后事件
|
||||
core.afterBattle(enemy, isLoc ? x : enemy.x, y);
|
||||
callback?.();
|
||||
}
|
||||
};
|
||||
|
||||
const getFacedId = (enemy: DamageEnemy) => {
|
||||
const e = enemy.enemy;
|
||||
@ -70,7 +69,9 @@ export function patchBattle() {
|
||||
return e.id;
|
||||
};
|
||||
|
||||
patch.add('getCurrentEnemys', function (floorId = core.status.floorId) {
|
||||
core.enemys.getCurrentEnemys = function getCurrentEnemys(
|
||||
floorId = core.status.floorId
|
||||
) {
|
||||
floorId = floorId || core.status.floorId;
|
||||
const enemys: CurrentEnemy[] = [];
|
||||
const used: Record<string, DamageEnemy[]> = {};
|
||||
@ -99,9 +100,9 @@ export function patchBattle() {
|
||||
const bd = b.enemy.calDamage().damage;
|
||||
return ad - bd;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
patch2.add('_sys_battle', function (data: Block, callback?: () => void) {
|
||||
core.events._sys_battle = function (data: Block, callback?: () => void) {
|
||||
// 检查战前事件
|
||||
const floor = core.floors[core.status.floorId];
|
||||
const beforeBattle: MotaEvent = [];
|
||||
@ -125,11 +126,11 @@ export function patchBattle() {
|
||||
core.insertAction(beforeBattle, data.x, data.y, callback);
|
||||
}
|
||||
} else {
|
||||
battle(data.x, data.y, false, callback);
|
||||
core.battle(data.x, data.y, false, callback);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
patch2.add('_action_battle', function (data, x, y, prefix) {
|
||||
core.events._action_battle = function (data, x, y, prefix) {
|
||||
if (data.id) {
|
||||
const enemy = getSingleEnemy(data.id as EnemyIds);
|
||||
// todo: 与不在地图上的怪物战斗
|
||||
@ -138,19 +139,21 @@ export function patchBattle() {
|
||||
core.doAction();
|
||||
return;
|
||||
}
|
||||
const [ex, ey] = core.events.__action_getLoc(
|
||||
const [ex, ey] = this.__action_getLoc(
|
||||
data.loc,
|
||||
x,
|
||||
y,
|
||||
prefix
|
||||
) as LocArr;
|
||||
battle(ex, ey, true, core.doAction);
|
||||
core.battle(ex, ey, true, core.doAction);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
patch2.add(
|
||||
'afterBattle',
|
||||
function (enemy: DamageEnemy, x?: number, y?: number) {
|
||||
core.events.afterBattle = function afterBattle(
|
||||
enemy: DamageEnemy,
|
||||
x?: number,
|
||||
y?: number
|
||||
) {
|
||||
const floorId = core.status.floorId;
|
||||
const special = enemy.info.special;
|
||||
|
||||
@ -162,8 +165,7 @@ export function patchBattle() {
|
||||
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);
|
||||
@ -216,12 +218,7 @@ export function patchBattle() {
|
||||
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();
|
||||
@ -253,10 +250,9 @@ export function patchBattle() {
|
||||
core.checkAutoEvents();
|
||||
|
||||
hook.emit('afterBattle', enemy, x, y);
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
loading.once('coreInit', patchBattle);
|
||||
loading.once('coreInit', init);
|
||||
|
||||
declare global {
|
||||
interface Enemys {
|
||||
|
@ -1,5 +0,0 @@
|
||||
import { patchBattle } from './battle';
|
||||
|
||||
export function patchAll() {
|
||||
patchBattle();
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
import { getHeroStatusOf, getHeroStatusOn } from '../state/hero';
|
||||
import { Range, ensureArray, has, manhattan } from '@user/data-utils';
|
||||
import { Range } from '@user/data-utils';
|
||||
import { ensureArray, has, manhattan } from '@/plugin/game/utils';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { hook } from '@user/data-base';
|
||||
import { HeroSkill, NightSpecial } from '../mechanism';
|
||||
import { HeroSkill, NightSpecial } from '../mechanism/misc';
|
||||
import {
|
||||
EnemyInfo,
|
||||
DamageInfo,
|
||||
@ -13,10 +14,8 @@ import {
|
||||
HaloFn,
|
||||
IEnemyCollection,
|
||||
IDamageEnemy,
|
||||
HaloType,
|
||||
IEnemyCollectionEvent
|
||||
HaloType
|
||||
} from '@motajs/types';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
// todo: 光环划分优先级,从而可以实现光环的多级运算
|
||||
|
||||
@ -54,8 +53,13 @@ specialValue
|
||||
.set(31, ['hpHalo'])
|
||||
.set(32, ['assimilateRange']);
|
||||
|
||||
interface EnemyCollectionEvent {
|
||||
extract: [];
|
||||
calculated: [];
|
||||
}
|
||||
|
||||
export class EnemyCollection
|
||||
extends EventEmitter<IEnemyCollectionEvent>
|
||||
extends EventEmitter<EnemyCollectionEvent>
|
||||
implements IEnemyCollection
|
||||
{
|
||||
floorId: FloorIds;
|
||||
@ -294,7 +298,7 @@ export class DamageEnemy implements IDamageEnemy {
|
||||
|
||||
// 融化,融化不属于怪物光环,因此不能用provide和inject计算,需要在这里计算
|
||||
const melt = flags[`melt_${floorId}`];
|
||||
if (!isNil(melt) && !isNil(this.x) && !isNil(this.y)) {
|
||||
if (has(melt) && has(this.x) && has(this.y)) {
|
||||
for (const [loc, per] of Object.entries(melt)) {
|
||||
const [mx, my] = loc.split(',').map(v => parseInt(v));
|
||||
if (
|
||||
|
@ -1,2 +1 @@
|
||||
export * from './misc';
|
||||
export * from './skillTree';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { backDir, has } from '@user/data-utils';
|
||||
import { backDir, has } from '@/plugin/game/utils';
|
||||
import { loading } from '@user/data-base';
|
||||
import type { LayerDoorAnimate } from '@motajs/render';
|
||||
import { getSkillLevel } from './skillTree';
|
||||
import { getSkillLevel } from '@/plugin/game/skillTree';
|
||||
|
||||
/**
|
||||
* 一些零散机制的数据
|
||||
@ -48,6 +48,131 @@ 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];
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { HeroSkill, NightSpecial } from '../mechanism';
|
||||
import { HeroSkill, NightSpecial } from '../mechanism/misc';
|
||||
|
||||
/**
|
||||
* 获取勇士在某一点的属性
|
||||
@ -59,7 +59,7 @@ function getRealStatus(
|
||||
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
|
||||
floorId: FloorIds = core.status.floorId
|
||||
): any {
|
||||
const { getSkillLevel } = Mota.require('@user/legacy-plugin-data');
|
||||
const { getSkillLevel } = Mota.Plugin.require('skillTree_g');
|
||||
if (name instanceof Array) {
|
||||
const res: any = {};
|
||||
name.forEach(v => {
|
||||
|
@ -11,7 +11,7 @@ import type {
|
||||
LayerMovingRenderable,
|
||||
LayerFloorBinder
|
||||
} from '@motajs/render';
|
||||
import type { HeroKeyMover } from '@user/client-modules';
|
||||
import type { HeroKeyMover } from '@/module/action/move';
|
||||
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('@user/client-modules');
|
||||
const { HeroKeyMover } = Mota.require('@motajs/system-action');
|
||||
const { gameKey } = Mota.require('@motajs/system-action');
|
||||
const keyMover = new HeroKeyMover(gameKey, heroMover);
|
||||
heroMoveCollection.keyMover = keyMover;
|
||||
|
@ -1,2 +1 @@
|
||||
export * from './range';
|
||||
export * from './utils';
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@user/entry-client",
|
||||
"name": "@motajs/entry-client",
|
||||
"dependencies": {
|
||||
"@motajs/client": "workspace:*",
|
||||
"@motajs/client-base": "workspace:*",
|
||||
@ -16,8 +16,6 @@
|
||||
"@motajs/legacy-client": "workspace:*",
|
||||
"@motajs/legacy-data": "workspace:*",
|
||||
"@motajs/legacy-ui": "workspace:*",
|
||||
"@motajs/legacy-system": "workspace:*",
|
||||
"@user/client-modules": "workspace:*",
|
||||
"@user/legacy-plugin-client": "workspace:*"
|
||||
"@motajs/legacy-system": "workspace:*"
|
||||
}
|
||||
}
|
@ -14,15 +14,8 @@ 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);
|
||||
@ -38,20 +31,4 @@ 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');
|
||||
}
|
||||
|
@ -1,5 +1 @@
|
||||
import { create } from './create';
|
||||
|
||||
export function createGame() {
|
||||
create();
|
||||
}
|
||||
export * from './create';
|
||||
|
@ -1,11 +1,3 @@
|
||||
{
|
||||
"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:*"
|
||||
}
|
||||
"name": "@user/entry-data"
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
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();
|
||||
}
|
@ -1,15 +1,5 @@
|
||||
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';
|
||||
|
@ -13,16 +13,6 @@ 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;
|
||||
@ -40,16 +30,6 @@ 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 {
|
||||
@ -93,6 +73,10 @@ 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;
|
||||
@ -153,7 +137,7 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export const Mota: IMota = new MotaSystem();
|
||||
export const Mota = new MotaSystem();
|
||||
|
||||
export function createMota() {
|
||||
window.Mota = Mota;
|
||||
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "@user/legacy-plugin-client",
|
||||
"dependencies": {
|
||||
"@user/client-modules": "workspace:*",
|
||||
"@user/data-state": "workspace:*"
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './pointShader';
|
@ -1,3 +0,0 @@
|
||||
export * from './boss';
|
||||
export * from './chase';
|
||||
export * from './fx';
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "@user/legacy-plugin-data",
|
||||
"dependencies": {
|
||||
"@user/data-state": "workspace:*",
|
||||
"@user/data-base": "workspace:*",
|
||||
"@user/data-utils": "workspace:*"
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
import { init as initCheckBlock } from './checkblock';
|
||||
|
||||
initCheckBlock();
|
||||
|
||||
export * from './checkblock';
|
||||
export * from './remainEnemy';
|
@ -1,720 +0,0 @@
|
||||
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;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
import { init as initItemDetail } from './itemDetail';
|
||||
|
||||
initItemDetail();
|
||||
|
||||
export * from './itemDetail';
|
@ -1,65 +0,0 @@
|
||||
import { hook } from '@user/data-base';
|
||||
|
||||
const potionItems: AllIdsOf<'items'>[] = [
|
||||
'redPotion',
|
||||
'bluePotion',
|
||||
'yellowPotion',
|
||||
'greenPotion',
|
||||
'I482',
|
||||
'I484',
|
||||
'I487',
|
||||
'I491'
|
||||
];
|
||||
|
||||
export function createHook() {
|
||||
hook.on('afterGetItem', (itemId, x, y, isGentleClick) => {
|
||||
// 获得一个道具后触发的事件
|
||||
// itemId:获得的道具ID;x和y是该道具所在的坐标
|
||||
// isGentleClick:是否是轻按触发的
|
||||
if (potionItems.includes(itemId)) core.playSound('回血');
|
||||
else core.playSound('获得道具');
|
||||
|
||||
const todo: any[] = [];
|
||||
// 检查该点的获得道具后事件。
|
||||
if (core.status.floorId == null) return;
|
||||
const event =
|
||||
core.floors[core.status.floorId].afterGetItem[`${x},${y}`];
|
||||
if (
|
||||
event &&
|
||||
(event instanceof Array ||
|
||||
!isGentleClick ||
|
||||
!event.disableOnGentleClick)
|
||||
) {
|
||||
core.unshift(todo, event as any[]);
|
||||
}
|
||||
if (core.hasFlag('spring')) {
|
||||
if (!core.hasFlag('springCount')) core.setFlag('springCount', 0);
|
||||
if (potionItems.includes(itemId)) {
|
||||
core.addFlag('springCount', 1);
|
||||
}
|
||||
if (core.getFlag<number>('springCount', 0) === 50) {
|
||||
core.setFlag('springCount', 0);
|
||||
core.status.hero.hpmax += core.getNakedStatus('hpmax') * 0.1;
|
||||
}
|
||||
core.updateStatusBar();
|
||||
}
|
||||
|
||||
if (todo.length > 0) core.insertAction(todo, x, y);
|
||||
});
|
||||
|
||||
hook.on('afterOpenDoor', (doorId, x, y) => {
|
||||
// 开一个门后触发的事件
|
||||
|
||||
const todo: any[] = [];
|
||||
// 检查该点的获得开门后事件。
|
||||
if (core.status.floorId == null) return;
|
||||
const event =
|
||||
core.floors[core.status.floorId].afterOpenDoor[`${x},${y}`];
|
||||
if (event) core.unshift(todo, event as any[]);
|
||||
|
||||
if (todo.length > 0) core.insertAction(todo, x, y);
|
||||
|
||||
if (core.status.event.id == null) core.continueAutomaticRoute();
|
||||
else core.clearContinueAutomaticRoute();
|
||||
});
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
import { loading } from '@user/data-base';
|
||||
import { initFallback } from './fallback';
|
||||
import { initFiveLayer } from './fiveLayer';
|
||||
import { createHook } from './hook';
|
||||
import { initReplay } from './replay';
|
||||
import { initUI } from './ui';
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
import('./dev/hotReload');
|
||||
}
|
||||
|
||||
export function create() {
|
||||
initFallback();
|
||||
loading.once('coreInit', () => {
|
||||
initFiveLayer();
|
||||
createHook();
|
||||
initReplay();
|
||||
initUI();
|
||||
});
|
||||
}
|
||||
|
||||
export * from './chase';
|
||||
export * from './fallback';
|
||||
export * from './fiveLayer';
|
||||
export * from './removeMap';
|
||||
export * from './replay';
|
||||
export * from './shop';
|
||||
export * from './skill';
|
||||
export * from './ui';
|
@ -2,4 +2,3 @@ export * from './patch';
|
||||
export * from './disposable';
|
||||
export * from './eventEmitter';
|
||||
export * from './resource';
|
||||
export * from './utils';
|
||||
|
@ -529,7 +529,7 @@ export function loadDefaultResource() {
|
||||
const res = LoadTask.add('byte', `byte/project/sounds/${v}`);
|
||||
Mota.r(() => {
|
||||
res.once('load', res => {
|
||||
const { soundPlayer } = Mota.require('@user/client-modules');
|
||||
const { soundPlayer } = Mota.require('module', 'Audio');
|
||||
soundPlayer.add(v, res.resource!);
|
||||
});
|
||||
});
|
||||
@ -548,7 +548,7 @@ export function loadDefaultResource() {
|
||||
const res = LoadTask.add('image', `image/project/autotiles/${v}.png`);
|
||||
res.once('load', res => {
|
||||
autotiles[v as AllIdsOf<'autotile'>] = res.resource;
|
||||
const { loading } = Mota.require('@user/data-base');
|
||||
const loading = Mota.require('var', 'loading');
|
||||
loading.addAutotileLoaded();
|
||||
loading.onAutotileLoaded(autotiles);
|
||||
core.material.images.autotile[v as AllIdsOf<'autotile'>] =
|
||||
@ -675,7 +675,7 @@ export async function loadCompressedResource() {
|
||||
autotiles[
|
||||
name.slice(0, -4) as AllIdsOf<'autotile'>
|
||||
] = image;
|
||||
const { loading } = Mota.require('@user/data-base');
|
||||
const loading = Mota.require('var', 'loading');
|
||||
loading.addAutotileLoaded();
|
||||
loading.onAutotileLoaded(autotiles);
|
||||
core.material.images.autotile[
|
||||
@ -717,10 +717,8 @@ export async function loadCompressedResource() {
|
||||
new FontFace(name.slice(0, -4), font)
|
||||
);
|
||||
} else if (usage === 'sound') {
|
||||
const { soundPlayer } = Mota.require(
|
||||
'@user/client-modules'
|
||||
);
|
||||
soundPlayer.add(name as SoundIds, value as Uint8Array);
|
||||
const { soundPlayer } = Mota.require('module', 'Audio');
|
||||
soundPlayer.add(v, value);
|
||||
} else if (usage === 'animate') {
|
||||
const ani = value as string;
|
||||
core.material.animates[
|
||||
|
@ -1,48 +0,0 @@
|
||||
import { EVENT_KEY_CODE_MAP } from '@motajs/client-base';
|
||||
|
||||
export function flipBinary(num: number, col: number) {
|
||||
const n = 1 << col;
|
||||
if (num & n) return num & ~n;
|
||||
else return num | n;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据布尔值数组转换成一个二进制数
|
||||
* @param arr 要转换的布尔值数组
|
||||
*/
|
||||
export function generateBinary(arr: boolean[]) {
|
||||
let num = 0;
|
||||
arr.forEach((v, i) => {
|
||||
if (v) {
|
||||
num |= 1 << i;
|
||||
}
|
||||
});
|
||||
return num;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数组内的某个项,返回删除后的数组
|
||||
* @param arr 要操作的数组
|
||||
* @param ele 要删除的项
|
||||
*/
|
||||
export function deleteWith<T>(arr: T[], ele: T): T[] {
|
||||
const index = arr.indexOf(ele);
|
||||
if (index === -1) return arr;
|
||||
arr.splice(index, 1);
|
||||
return arr;
|
||||
}
|
||||
|
||||
export function spliceBy<T>(arr: T[], from: T): T[] {
|
||||
const index = arr.indexOf(from);
|
||||
if (index === -1) return arr;
|
||||
arr.splice(index);
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取事件中的keycode对应的键
|
||||
* @param key 要获取的键
|
||||
*/
|
||||
export function keycode(key: number) {
|
||||
return EVENT_KEY_CODE_MAP[key];
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { Keyboard } from '@motajs/system-action';
|
||||
import KeyboardUI from './keyboard.vue';
|
||||
import KeyboardUI from '../panel/keyboard.vue';
|
||||
|
||||
interface VirtualKeyProps {
|
||||
keyboard: Keyboard;
|
||||
|
@ -49,13 +49,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, onUpdated, ref, watch } from 'vue';
|
||||
import { ArrowsAltOutlined, DragOutlined } from '@ant-design/icons-vue';
|
||||
import {
|
||||
isMobile,
|
||||
useDrag,
|
||||
cancelGlobalDrag,
|
||||
requireUniqueSymbol
|
||||
} from '../use';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { isMobile, useDrag, cancelGlobalDrag } from '../use';
|
||||
import { has, requireUniqueSymbol } from '../utils';
|
||||
|
||||
// todo: 重写
|
||||
|
||||
@ -205,10 +200,10 @@ function resize() {
|
||||
|
||||
if (!main) return;
|
||||
|
||||
if (!isNil(props.width)) width.value = props.width;
|
||||
if (!isNil(props.height)) height.value = props.height;
|
||||
if (!isNil(props.left)) left.value = props.left;
|
||||
if (!isNil(props.top)) top.value = props.top;
|
||||
if (has(props.width)) width.value = props.width;
|
||||
if (has(props.height)) height.value = props.height;
|
||||
if (has(props.left)) left.value = props.left;
|
||||
if (has(props.top)) top.value = props.top;
|
||||
|
||||
const beforeWidth = width.value;
|
||||
const beforeHeight = height.value;
|
||||
|
@ -9,8 +9,7 @@
|
||||
<script lang="tsx" setup>
|
||||
import { onMounted, onUnmounted, onUpdated } from 'vue';
|
||||
import { addAnimate, removeAnimate } from '../animateController';
|
||||
import { requireUniqueSymbol } from '../use';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { has, requireUniqueSymbol } from '../utils';
|
||||
|
||||
const id = requireUniqueSymbol().toFixed(0);
|
||||
|
||||
@ -28,7 +27,7 @@ let ctx: CanvasRenderingContext2D;
|
||||
let drawFn: () => void;
|
||||
|
||||
function draw() {
|
||||
if (!isNil(drawFn)) removeAnimate(drawFn);
|
||||
if (has(drawFn)) removeAnimate(drawFn);
|
||||
|
||||
const cls = core.getClsFromId(props.id as AllIds);
|
||||
const frames = core.getAnimateFrames(cls);
|
||||
|
@ -26,8 +26,8 @@
|
||||
import { onMounted, onUpdated } from 'vue';
|
||||
import { LeftOutlined } from '@ant-design/icons-vue';
|
||||
import Scroll from './scroll.vue';
|
||||
import { isMobile, requireUniqueSymbol } from '../use';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { isMobile } from '../use';
|
||||
import { has, requireUniqueSymbol } from '../utils';
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
@ -51,10 +51,10 @@ function resize() {
|
||||
left = document.getElementById(`column-left-${id}`) as HTMLDivElement;
|
||||
right = document.getElementById(`column-right-${id}`) as HTMLDivElement;
|
||||
|
||||
if (!isNil(props.width) && !isMobile) main.style.width = `${props.width}%`;
|
||||
if (!isNil(props.height)) main.style.height = `${props.height}%`;
|
||||
if (!isNil(props.left)) left.style.flexBasis = `${props.left}%`;
|
||||
if (!isNil(props.right)) right.style.flexBasis = `${props.right}%`;
|
||||
if (has(props.width) && !isMobile) main.style.width = `${props.width}%`;
|
||||
if (has(props.height)) main.style.height = `${props.height}%`;
|
||||
if (has(props.left)) left.style.flexBasis = `${props.left}%`;
|
||||
if (has(props.right)) right.style.flexBasis = `${props.right}%`;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
|
@ -16,7 +16,7 @@
|
||||
></BoxAnimate>
|
||||
<div
|
||||
class="special-text"
|
||||
v-if="!isNil(enemy.special) && enemy.special.length > 0"
|
||||
v-if="has(enemy.special) && enemy.special.length > 0"
|
||||
>
|
||||
<template v-for="(text, i) in enemy.showSpecial">
|
||||
<span v-if="i < (isMobile ? 1 : 2)"
|
||||
@ -110,10 +110,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { has } from '../utils';
|
||||
import BoxAnimate from '../components/boxAnimate.vue';
|
||||
import { isMobile } from '../use';
|
||||
import { ToShowEnemy } from '../tools/book';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
const props = defineProps<{
|
||||
enemy: ToShowEnemy;
|
||||
|
@ -4,10 +4,11 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted } from 'vue';
|
||||
import { requireUniqueSymbol } from '../utils';
|
||||
import { MinimapDrawer, getArea } from '../tools/fly';
|
||||
import { useDrag, useWheel, requireUniqueSymbol } from '../use';
|
||||
import { useDrag, useWheel } from '../use';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { mainSetting } from '../preset/settingIns';
|
||||
import { mainSetting } from '../preset/ui';
|
||||
|
||||
const props = defineProps<{
|
||||
action?: boolean;
|
||||
@ -133,7 +134,7 @@ onMounted(() => {
|
||||
}
|
||||
drawer.drawMap();
|
||||
|
||||
const { hook } = Mota.require('@user/data-base');
|
||||
const hook = Mota.require('var', 'hook');
|
||||
hook.on('afterChangeFloor', onChange);
|
||||
hook.on('afterBattle', afterBattle);
|
||||
|
||||
@ -167,7 +168,7 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
const { hook } = Mota.require('@user/data-base');
|
||||
const hook = Mota.require('var', 'hook');
|
||||
hook.off('afterChangeFloor', onChange);
|
||||
hook.off('afterBattle', afterBattle);
|
||||
});
|
||||
|
@ -12,12 +12,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { sleep } from 'mutate-animate';
|
||||
import { onMounted, onUnmounted, onUpdated } from 'vue';
|
||||
import {
|
||||
cancelGlobalDrag,
|
||||
useDrag,
|
||||
useWheel,
|
||||
requireUniqueSymbol
|
||||
} from '../use';
|
||||
import { cancelGlobalDrag, useDrag, useWheel } from '../use';
|
||||
import { requireUniqueSymbol } from '../utils';
|
||||
|
||||
let main: HTMLDivElement;
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Component, shallowReactive } from 'vue';
|
||||
import { EventEmitter } from '@motajs/legacy-common';
|
||||
import { IGameUi, IUiController } from './interface';
|
||||
|
||||
interface FocusEvent<T> {
|
||||
focus: (before: T | null, after: T) => void;
|
||||
@ -129,7 +128,7 @@ interface MountedVBind {
|
||||
[x: string]: any;
|
||||
}
|
||||
|
||||
export class GameUi extends EventEmitter<GameUiEvent> implements IGameUi {
|
||||
export class GameUi extends EventEmitter<GameUiEvent> {
|
||||
static uiList: GameUi[] = [];
|
||||
|
||||
component: Component;
|
||||
@ -162,10 +161,7 @@ interface HoldOnController {
|
||||
end(noClosePanel?: boolean): void;
|
||||
}
|
||||
|
||||
export class UiController
|
||||
extends Focus<IndexedGameUi>
|
||||
implements IUiController
|
||||
{
|
||||
export class UiController extends Focus<IndexedGameUi> {
|
||||
static list: UiController[] = [];
|
||||
list: Record<string, GameUi> = {};
|
||||
num: number = 0;
|
||||
|
@ -1,11 +1,9 @@
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { logger } from '@motajs/common';
|
||||
import { deleteWith, ensureArray, parseCss, tip } from '@motajs/legacy-ui';
|
||||
import { ResponseBase } from '@motajs/client-base';
|
||||
import axios, { AxiosResponse, toFormData } from 'axios';
|
||||
import { VNode, h, shallowReactive } from 'vue';
|
||||
import { ensureArray, parseCss } from './utils';
|
||||
import { deleteWith } from '@motajs/legacy-common';
|
||||
import { tip } from './use';
|
||||
// /* @__PURE__ */ import { id, password } from '../../../../user';
|
||||
|
||||
type CSSObj = Partial<Record<CanParseCss, string>>;
|
||||
|
@ -482,6 +482,46 @@
|
||||
"这些技能一般需要尽早点出。"
|
||||
]
|
||||
},
|
||||
"study": {
|
||||
"text": "学习",
|
||||
"condition": "Mota.Plugin.require('skillTree_g').getSkillLevel(11) > 0",
|
||||
"desc": [
|
||||
"本条目会详细说明学习的机制与所有可以被学习的技能被学习后的效果。当前已经学习的技能会以与状态栏类似的盒子展示出来。",
|
||||
"<br>",
|
||||
"<br>",
|
||||
"首先,学习技能消耗的智慧点会越来越多,初始消耗的智慧点为500,每学习一次增加250。",
|
||||
"学习的技能可以持续5场战斗,在技能树界面每升级一次增加3场,",
|
||||
"<span style=\"color: gold\">当前为${Mota.Plugin.require('skillTree_g').getSkillLevel(11) * 3 + 2}场</span>。",
|
||||
"学习后对应属性的值,例如抱团怪增加的属性百分比,会与被学习的怪物相同。学习界面可以使用背包中的道具或点击状态栏打开。",
|
||||
"<br>",
|
||||
"<br>",
|
||||
"下面会详细说明每一种可以被学习的技能被学习后的效果,没有列出的均不可学习。",
|
||||
"<br>",
|
||||
"<br>",
|
||||
"<span style=\"color: #fc3\">1. 致命一击</span>:勇士每5回合对怪物造成一次强力攻击。",
|
||||
"<br>",
|
||||
"<span style=\"color: #bbb0ff\">2. 恶毒</span>:勇士攻击无视怪物的防御。",
|
||||
"<br>",
|
||||
"<span style=\"color: #c0b088\">3. 坚固</span>:勇士防御不低于怪物的攻击-1。",
|
||||
"<br>",
|
||||
"<span style=\"color: #fe7\">4. n连击</span>:勇士每回合攻击n次",
|
||||
"<br>",
|
||||
"<span style=\"color: #b30000\">5. 饥渴</span>:勇士在战前吸取怪物一定量的攻击加载自己身上,",
|
||||
"同时减少怪物相应量的攻击,优先于怪物。",
|
||||
"<br>",
|
||||
"<span style=\"color: #fa4\">6. 抱团</span>:勇士周围每有一个拥有抱团属性的怪物,勇士的属性便增加一定值。",
|
||||
"相应地,拥有抱团属性的怪物也会受到勇士的加成。",
|
||||
"<br>",
|
||||
"<span style=\"color: #b0c0dd\">7. 勇气之刃</span>:勇士第一回合造成一定量的伤害,之后正常。",
|
||||
"<br>",
|
||||
"<span style=\"color: #ff00d2\">8. 勇气冲锋</span>:勇士首先发动冲锋,造成一定量的伤害,眩晕怪物5回合。",
|
||||
"学习该技能后,勇士无条件先手。",
|
||||
"<br>",
|
||||
"<span style=\"color: #bbb0ff\">9. 魔攻</span>:勇士攻击无视怪物的防御。",
|
||||
"<br>",
|
||||
"<span style=\"color: #b0b666\">10. 先攻</span>:勇士无条件先手。"
|
||||
]
|
||||
},
|
||||
"special1": {
|
||||
"text": "第一章怪物特技",
|
||||
"condition": "flags.chapter > 0",
|
||||
|
@ -26,7 +26,11 @@
|
||||
"<br>",
|
||||
"注:当鼠标移动到怪物上时,经过200毫秒才会显示信息,防止误操作。"
|
||||
],
|
||||
"hotkey": ["设置游戏中会用到的一些快捷键"]
|
||||
"hotkey": ["设置游戏中会用到的一些快捷键"],
|
||||
"toolbar": [
|
||||
"允许你在工具栏上自定义按钮,包括使用道具、开关技能、按下某个按键等。",
|
||||
"推荐手机进行一些设置"
|
||||
]
|
||||
},
|
||||
"utils": {
|
||||
"betterLoad": [
|
||||
|
@ -1,8 +0,0 @@
|
||||
import { createShadow } from './shadow';
|
||||
|
||||
export function createFx() {
|
||||
createShadow();
|
||||
}
|
||||
|
||||
export * from './shadow';
|
||||
export * from './webgl';
|
@ -75,8 +75,7 @@ function addLightFromBlock(
|
||||
});
|
||||
}
|
||||
|
||||
export function createShadow() {
|
||||
const { hook } = Mota.require('@user/data-base');
|
||||
const hook = Mota.require('var', 'hook');
|
||||
|
||||
hook.once('reset', () => {
|
||||
Shadow.init();
|
||||
@ -101,9 +100,11 @@ export function createShadow() {
|
||||
},
|
||||
{ background: [0, 0, 0, 0.3] }
|
||||
);
|
||||
hook.on('loadData', () => {
|
||||
Mota.rewrite(core.control, 'loadData', 'add', () => {
|
||||
if (!main.replayChecking) {
|
||||
Shadow.update(true);
|
||||
LayerShadowExtends.shadowList.forEach(v => v.update());
|
||||
}
|
||||
});
|
||||
});
|
||||
hook.on('reset', () => {
|
||||
@ -120,7 +121,6 @@ export function createShadow() {
|
||||
// setCanvasFilterByFloorId(floorId);
|
||||
LayerShadowExtends.shadowList.forEach(v => v.update());
|
||||
});
|
||||
}
|
||||
|
||||
// 深度测试着色器
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { ensureArray } from '../utils';
|
||||
import { ensureArray, tip } from '@motajs/legacy-ui';
|
||||
import { sleep } from 'mutate-animate';
|
||||
import { logger } from '@motajs/common';
|
||||
import { tip } from '../use';
|
||||
|
||||
const { gl, gl2 } = checkSupport();
|
||||
|
||||
|
@ -1,22 +1,12 @@
|
||||
import { createFx } from './fx';
|
||||
import { createPreset } from './preset';
|
||||
|
||||
export function create() {
|
||||
createFx();
|
||||
createPreset();
|
||||
}
|
||||
|
||||
export * as UI from './ui';
|
||||
export * as Components from './components';
|
||||
export * from './preset';
|
||||
export * from './tools';
|
||||
export * from './fx';
|
||||
|
||||
export * from './animateController';
|
||||
export * from './controller';
|
||||
export * from './danmaku';
|
||||
// export * from './mark';
|
||||
export * from './mark';
|
||||
export * from './setting';
|
||||
export * from './use';
|
||||
export * from './utils';
|
||||
export * from './uiUtils';
|
||||
|
@ -1,99 +0,0 @@
|
||||
export interface IGameUi {
|
||||
id: string;
|
||||
symbol: symbol;
|
||||
}
|
||||
|
||||
export interface IMountedVBind {
|
||||
num: number;
|
||||
ui: IGameUi;
|
||||
controller: IUiController;
|
||||
[x: string]: any;
|
||||
}
|
||||
|
||||
interface HoldOnController {
|
||||
end(noClosePanel?: boolean): void;
|
||||
}
|
||||
|
||||
type UiVOn = Record<string, (param?: any) => void>;
|
||||
type UiVBind = Record<string, any>;
|
||||
|
||||
export interface IUiController {
|
||||
stack: any[];
|
||||
|
||||
/**
|
||||
* 设置为仅显示最后一个ui
|
||||
*/
|
||||
showEnd(): void;
|
||||
|
||||
/**
|
||||
* 设置为显示所有ui
|
||||
*/
|
||||
showAll(): void;
|
||||
|
||||
/**
|
||||
* 根据id获取到ui
|
||||
* @param id ui的id
|
||||
*/
|
||||
get(id: string): void;
|
||||
|
||||
/**
|
||||
* 暂时保持下一次删除ui不会导致ui整体被关闭,引起ui背景闪烁。
|
||||
* 例如可以用于道具栏,打开道具时就应当 holdOn,然后通过道具使用钩子来判断接下来是否要隐藏 app:
|
||||
* ```txt
|
||||
* hold on -> close -> use item -> hook -> stack.length === 0 ? end(): no action
|
||||
* ```
|
||||
*/
|
||||
holdOn(): HoldOnController;
|
||||
|
||||
/**
|
||||
* 关闭一个ui,注意如果不是平等模式,在其之后的ui都会同时关闭掉
|
||||
* @param num 要关闭的ui的唯一标识符
|
||||
*/
|
||||
close(num: number): void;
|
||||
|
||||
/**
|
||||
* 根据id关闭所有同id的ui,注意非平等模式下,会将第一个ui后的所有ui都关闭掉
|
||||
* @param id 要关闭的ui的id
|
||||
*/
|
||||
closeByName(id: string): void;
|
||||
|
||||
/**
|
||||
* 打开一个新的ui
|
||||
* @param id 要打开的ui的id
|
||||
* @param vOn 监听的事件
|
||||
* @param vBind 绑定的数据
|
||||
* @returns ui的唯一标识符
|
||||
*/
|
||||
open(id: string, vBind?: UiVBind, vOn?: UiVOn): void;
|
||||
|
||||
/**
|
||||
* 注册一个ui
|
||||
* @param id ui的id
|
||||
* @param ui 对应的GameUi实例
|
||||
*/
|
||||
register(...ui: IGameUi[]): void;
|
||||
|
||||
/**
|
||||
* 取消注册一个ui
|
||||
* @param id 要取消注册的ui的id
|
||||
*/
|
||||
unregister(...id: string[]): void;
|
||||
|
||||
/**
|
||||
* 根据ui的唯一标识符进行聚焦
|
||||
* @param num 要聚焦于的ui的唯一标识符
|
||||
*/
|
||||
focusByNum(num: number): void;
|
||||
|
||||
/**
|
||||
* 根据唯一标识符获取对应的ui
|
||||
* @param num ui的唯一标识符
|
||||
*/
|
||||
getByNum(num: number): void;
|
||||
|
||||
/**
|
||||
* 根据ui的唯一标识符来判断当前是否存在某个ui
|
||||
* @param id ui的唯一标识符
|
||||
*/
|
||||
hasName(id: string): void;
|
||||
}
|
164
packages/legacy-ui/src/mark.ts
Normal file
164
packages/legacy-ui/src/mark.ts
Normal file
@ -0,0 +1,164 @@
|
||||
import { IDamageEnemy } from '@motajs/types';
|
||||
import { fixedUi } from './preset/ui';
|
||||
import { tip } from './utils';
|
||||
import { ref, Ref } from 'vue';
|
||||
|
||||
export interface MarkInfo<T extends EnemyIds> {
|
||||
id: T;
|
||||
enemy: IDamageEnemy;
|
||||
/**
|
||||
* 提示模式,从低到高位数分别为:
|
||||
* 1. 踩临界时
|
||||
* 2. 能打过怪物时
|
||||
* 3. 小于勇士生命值的2/3时
|
||||
* 4. 小于勇士生命值的1/3时
|
||||
* 5. 零伤时
|
||||
* 6. 小于指定伤害时
|
||||
*/
|
||||
mode: number;
|
||||
/** 当前提示状态,提示模式的 2-6 */
|
||||
status: number;
|
||||
lastAtk: number;
|
||||
lastDamage: number;
|
||||
markDamage?: number;
|
||||
/** 数据更新用,取反更新标记信息 */
|
||||
update: Ref<boolean>;
|
||||
}
|
||||
|
||||
const uiMap = new Map<EnemyIds, number>();
|
||||
const marked: MarkInfo<EnemyIds>[] = [];
|
||||
|
||||
/**
|
||||
* 标记一个怪物,标记后的怪物会在勇士刚好能打过怪物时、伤害刚好小于勇士生命值的2/3和1/3时、踩到临界时提示
|
||||
* @param id 标记的怪物id
|
||||
*/
|
||||
export function markEnemy(id: EnemyIds) {
|
||||
if (hasMarkedEnemy(id)) return;
|
||||
const DamageEnemy = Mota.require('class', 'DamageEnemy');
|
||||
const enemy = new DamageEnemy(core.material.enemys[id]);
|
||||
enemy.calAttribute();
|
||||
enemy.getRealInfo();
|
||||
|
||||
const info: MarkInfo<EnemyIds> = {
|
||||
id,
|
||||
enemy,
|
||||
mode: 0b011111,
|
||||
lastAtk: Mota.requireAll('fn').getHeroStatusOn('atk', 'empty'),
|
||||
lastDamage: enemy.calDamage().damage,
|
||||
status: 0b0,
|
||||
update: ref(true)
|
||||
};
|
||||
marked.push(info);
|
||||
|
||||
uiMap.set(id, fixedUi.open('markedEnemy', { enemy: info }));
|
||||
|
||||
tip('success', `已标记 ${enemy.enemy.name}!`);
|
||||
}
|
||||
|
||||
export function unmarkEnemy(id: EnemyIds) {
|
||||
fixedUi.close(uiMap.get(id) ?? -1);
|
||||
uiMap.delete(id);
|
||||
const index = marked.findIndex(v => v.id === id);
|
||||
if (index === -1) return;
|
||||
tip('success', `已取消标记 ${marked[index].enemy.enemy.name}!`);
|
||||
marked.splice(index, 1);
|
||||
}
|
||||
|
||||
export function checkMarkedEnemy() {
|
||||
const { getHeroStatusOn } = Mota.requireAll('fn');
|
||||
marked.forEach(v => {
|
||||
const { id, enemy, mode, lastAtk, lastDamage, markDamage } = v;
|
||||
const atk = getHeroStatusOn('atk', 'empty');
|
||||
let tip = 0;
|
||||
if (mode & 0b11110) {
|
||||
const damage = enemy.calDamage().damage;
|
||||
const hp = core.status.hero.hp;
|
||||
v.lastDamage = damage;
|
||||
if (damage > lastDamage) return;
|
||||
// 重置标记状态
|
||||
if (damage > hp) {
|
||||
v.status &= 0b100001;
|
||||
}
|
||||
if (damage > (markDamage ?? Infinity)) {
|
||||
v.status &= 0b1;
|
||||
}
|
||||
// 能打过怪物提示、2/3提示、1/3提示、零伤提示、指定伤害提示
|
||||
if (mode & (1 << 1) && damage < hp && damage > (hp * 2) / 3) {
|
||||
if (!(v.status & (1 << 1))) {
|
||||
v.status &= 0b100001;
|
||||
v.status |= 1 << 1;
|
||||
tip |= 1 << 1;
|
||||
}
|
||||
} else if (mode & (1 << 2) && damage > hp / 3) {
|
||||
if (!(v.status & (1 << 2))) {
|
||||
v.status &= 0b100011;
|
||||
v.status |= 1 << 2;
|
||||
tip |= 1 << 2;
|
||||
}
|
||||
} else if (mode & (1 << 3) && damage > 0) {
|
||||
if (!(v.status & (1 << 3))) {
|
||||
v.status &= 0b100111;
|
||||
v.status |= 1 << 3;
|
||||
tip |= 1 << 3;
|
||||
}
|
||||
} else if (mode & (1 << 4)) {
|
||||
if (!(v.status & (1 << 4))) {
|
||||
v.status &= 0b101111;
|
||||
v.status |= 1 << 4;
|
||||
tip |= 1 << 4;
|
||||
}
|
||||
}
|
||||
if (mode & (1 << 5) && damage < (markDamage ?? Infinity)) {
|
||||
if (!(v.status & (1 << 5))) {
|
||||
if (damage < (markDamage ?? Infinity)) {
|
||||
v.status |= 1 << 5;
|
||||
} else {
|
||||
v.status &= 0b011111;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 临界提示
|
||||
if (mode & (1 << 0)) {
|
||||
const critical = enemy.calCritical(1)[0]?.atkDelta ?? Infinity;
|
||||
v.lastAtk = atk + critical;
|
||||
if (critical + atk > lastAtk) {
|
||||
tip |= 1 << 0;
|
||||
}
|
||||
}
|
||||
makeTip(id, tip, v);
|
||||
v.update.value = !v.update.value;
|
||||
});
|
||||
}
|
||||
|
||||
function makeTip(enemy: EnemyIds, mode: number, info: MarkInfo<EnemyIds>) {
|
||||
const name = core.material.enemys[enemy].name;
|
||||
if (mode & (1 << 0)) {
|
||||
tip('success', `已踩到 ${name} 的临界!`);
|
||||
}
|
||||
if (mode & (1 << 1)) {
|
||||
tip('success', `已能打过 ${name}!`);
|
||||
}
|
||||
if (mode & (1 << 2)) {
|
||||
tip('success', `${name} 的伤害已降至 2/3!`);
|
||||
}
|
||||
if (mode & (1 << 3)) {
|
||||
tip('success', `${name} 的伤害已降至 1/3!`);
|
||||
}
|
||||
if (mode & (1 << 4)) {
|
||||
tip('success', `${name} 已零伤!`);
|
||||
}
|
||||
if (mode & (1 << 5)) {
|
||||
const damage = core.formatBigNumber(info.markDamage ?? Infinity);
|
||||
tip('success', `${name} 的伤害已降至 ${damage}!`);
|
||||
}
|
||||
}
|
||||
|
||||
export function hasMarkedEnemy(id: EnemyIds) {
|
||||
return marked.some(v => v.id === id);
|
||||
}
|
||||
|
||||
const hook = Mota.require('var', 'hook');
|
||||
hook.on('statusBarUpdate', () => {
|
||||
checkMarkedEnemy();
|
||||
});
|
@ -67,12 +67,12 @@
|
||||
>
|
||||
<span class="changable" :change="nowDamageChangable"
|
||||
><span style="font-family: 'FiraCode'">{{
|
||||
(nowDamage[0] as number) < 0 && isNil(enemy.damage)
|
||||
(nowDamage[0] as number) < 0 && !has(enemy.damage)
|
||||
? '=>'
|
||||
: ''
|
||||
}}</span
|
||||
>{{
|
||||
(nowDamage[0] as number) < 0 && isNil(enemy.damage)
|
||||
(nowDamage[0] as number) < 0 && !has(enemy.damage)
|
||||
? format(-nowDamage[0])
|
||||
: format(nowDamage[0])
|
||||
}}</span
|
||||
@ -96,8 +96,8 @@
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { detailInfo, getCriticalDamage, getDefDamage } from '../tools/book';
|
||||
import Chart, { ChartConfiguration } from 'chart.js/auto';
|
||||
import { setCanvasSize } from '../utils';
|
||||
import { debounce, isNil } from 'lodash-es';
|
||||
import { has, setCanvasSize } from '../utils';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { isMobile } from '../use';
|
||||
import { createChangable } from '../tools/common';
|
||||
|
||||
@ -113,10 +113,10 @@ const ceil = Math.ceil;
|
||||
|
||||
const x = ref<number>();
|
||||
const y = ref<number>();
|
||||
x.value = !isNil(x.value)
|
||||
x.value = has(x.value)
|
||||
? Math.round(x.value + core.bigmap.offsetX / 32)
|
||||
: void 0;
|
||||
y.value = !isNil(y.value)
|
||||
y.value = has(y.value)
|
||||
? Math.round(y.value + core.bigmap.offsetY / 32)
|
||||
: void 0;
|
||||
|
||||
|
85
packages/legacy-ui/src/panel/enemyTarget.vue
Normal file
85
packages/legacy-ui/src/panel/enemyTarget.vue
Normal file
@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div id="enemy-target">
|
||||
<div id="enemy-desc">
|
||||
<span>怪物描述</span>
|
||||
<Scroll id="enemy-desc-scroll">
|
||||
<span
|
||||
> {{
|
||||
enemy.enemy.enemy.description
|
||||
}}</span
|
||||
>
|
||||
</Scroll>
|
||||
</div>
|
||||
<a-divider dashed style="border-color: #ddd4"></a-divider>
|
||||
<div>
|
||||
<div id="mark-target">
|
||||
<span
|
||||
id="mark-info"
|
||||
:style="{ color: marked ? 'lightgreen' : 'lightcoral' }"
|
||||
>{{ marked ? '已标记该怪物' : '未标记该怪物' }}</span
|
||||
>
|
||||
<span class="button-text" @click.stop="mark">{{
|
||||
marked ? '取消标记该怪物' : '标记该怪物为目标'
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import { detailInfo } from '../tools/book';
|
||||
import { hasMarkedEnemy, markEnemy } from '../mark';
|
||||
|
||||
const enemy = detailInfo.enemy!;
|
||||
const marked = ref(hasMarkedEnemy(enemy.enemy.id));
|
||||
|
||||
function mark() {
|
||||
markEnemy(enemy.enemy.id);
|
||||
marked.value = hasMarkedEnemy(enemy.enemy.id);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
#enemy-target {
|
||||
width: 100%;
|
||||
font-size: 160%;
|
||||
}
|
||||
|
||||
#enemy-desc {
|
||||
width: 100%;
|
||||
height: 30vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#enemy-desc-scroll {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#mark-target {
|
||||
margin-top: 10%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
font-size: 3.3vh;
|
||||
}
|
||||
|
||||
#mark-info {
|
||||
transition: color 0.2s linear;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
#enemy-desc {
|
||||
font-size: 70%;
|
||||
}
|
||||
|
||||
#mark-target {
|
||||
font-size: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,6 +1,6 @@
|
||||
import { Danmaku } from '../danmaku';
|
||||
import { Component, h } from 'vue';
|
||||
import { mainSetting } from './settingIns';
|
||||
import { mainSetting } from './ui';
|
||||
import { getIconHeight } from '../utils';
|
||||
import { BoxAnimate } from '../components';
|
||||
|
||||
@ -21,15 +21,12 @@ if (import.meta.env.DEV) {
|
||||
Danmaku.backend = `/danmaku`;
|
||||
}
|
||||
|
||||
export function createDanmaku() {
|
||||
const { hook } = Mota.require('@user/data-base');
|
||||
|
||||
hook.once('reset', () => {
|
||||
Mota.require('var', 'hook').once('reset', () => {
|
||||
Danmaku.fetch();
|
||||
});
|
||||
|
||||
// 勇士移动后显示弹幕
|
||||
hook.on('moveOneStep', (x, y, floor) => {
|
||||
Mota.require('var', 'hook').on('moveOneStep', (x, y, floor) => {
|
||||
const enabled = mainSetting.getValue('ui.danmaku', true);
|
||||
if (!enabled) return;
|
||||
const f = Danmaku.allInPos[floor];
|
||||
@ -44,4 +41,3 @@ export function createDanmaku() {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { debounce } from 'lodash-es';
|
||||
import { fixedUi, mainUi } from './ui';
|
||||
import { ref } from 'vue';
|
||||
import { sleep } from 'mutate-animate';
|
||||
|
||||
@ -15,7 +16,6 @@ const showFixed = debounce((block: Block) => {
|
||||
if (!e) return;
|
||||
const enemy = core.status.thisMap.enemy.get(block.x, block.y);
|
||||
if (!enemy) return;
|
||||
const { fixedUi } = Mota.require('@motajs/legacy-ui');
|
||||
fixedUi.open(
|
||||
'fixed',
|
||||
{ enemy, close, loc: [cx, cy], hovered },
|
||||
@ -29,7 +29,6 @@ const showFixed = debounce((block: Block) => {
|
||||
const closeFixed = () => {
|
||||
close.value = true;
|
||||
sleep(200).then(() => {
|
||||
const { fixedUi } = Mota.require('@motajs/legacy-ui');
|
||||
fixedUi.closeByName('fixed');
|
||||
close.value = false;
|
||||
});
|
||||
@ -38,9 +37,7 @@ const closeFixed = () => {
|
||||
// todo: 应当在这里实现查看临界与特殊属性的功能
|
||||
export let hovered: Block | null;
|
||||
|
||||
export function createFixed() {
|
||||
const { hook, gameListener } = Mota.require('@user/data-base');
|
||||
|
||||
const { hook, gameListener } = Mota.requireAll('var');
|
||||
gameListener.on('hoverBlock', block => {
|
||||
closeFixed();
|
||||
hovered = block;
|
||||
@ -60,10 +57,8 @@ export function createFixed() {
|
||||
});
|
||||
|
||||
hook.once('mounted', () => {
|
||||
const { mainUi } = Mota.require('@motajs/legacy-ui');
|
||||
mainUi.on('start', () => {
|
||||
showFixed.cancel();
|
||||
closeFixed();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -1,11 +1,15 @@
|
||||
import { KeyCode } from '@motajs/client-base';
|
||||
import { gameKey, HotkeyJSON } from '@motajs/system-action';
|
||||
import {
|
||||
openDanmakuPoster,
|
||||
tip,
|
||||
hasMarkedEnemy,
|
||||
markEnemy,
|
||||
unmarkEnemy
|
||||
} from '@motajs/legacy-ui';
|
||||
import { hovered } from './fixed';
|
||||
import { mainUi } from './uiIns';
|
||||
import { mainUi } from './ui';
|
||||
import { GameStorage } from '@motajs/legacy-system';
|
||||
// import { hasMarkedEnemy, markEnemy, unmarkEnemy } from '../mark';
|
||||
import { openDanmakuPoster } from '../uiUtils';
|
||||
import { tip } from '../use';
|
||||
|
||||
export const mainScope = Symbol.for('@key_main');
|
||||
|
||||
@ -502,9 +506,9 @@ gameKey
|
||||
.realize('mark', () => {
|
||||
const cls = hovered?.event.cls;
|
||||
if (cls === 'enemys' || cls === 'enemy48') {
|
||||
// const id = hovered!.event.id as EnemyIds;
|
||||
// if (hasMarkedEnemy(id)) unmarkEnemy(id);
|
||||
// else markEnemy(id);
|
||||
const id = hovered!.event.id as EnemyIds;
|
||||
if (hasMarkedEnemy(id)) unmarkEnemy(id);
|
||||
else markEnemy(id);
|
||||
}
|
||||
})
|
||||
.realize('special', () => {
|
||||
@ -531,7 +535,7 @@ gameKey
|
||||
core.actions._clickGameInfo_openComments();
|
||||
})
|
||||
.realize('skill1', () => {
|
||||
const HeroSkill = Mota.require('@user/data-state').HeroSkill;
|
||||
const HeroSkill = Mota.require('module', 'Mechanism').HeroSkill;
|
||||
if (!HeroSkill.learnedSkill(HeroSkill.Blade)) return;
|
||||
if (HeroSkill.getAutoSkill()) {
|
||||
tip('error', '已开启自动切换技能!');
|
||||
@ -543,13 +547,13 @@ gameKey
|
||||
core.updateStatusBar();
|
||||
})
|
||||
.realize('skill2', () => {
|
||||
const HeroSkill = Mota.require('@user/data-state').HeroSkill;
|
||||
const HeroSkill = Mota.require('module', 'Mechanism').HeroSkill;
|
||||
if (
|
||||
!flags.onChase &&
|
||||
!core.status.floorId.startsWith('tower') &&
|
||||
HeroSkill.learnedSkill(HeroSkill.Jump)
|
||||
) {
|
||||
Mota.require('@user/legacy-plugin-data').jumpSkill();
|
||||
Mota.Plugin.require('skill_g').jumpSkill();
|
||||
core.status.route.push('useSkill:Jump');
|
||||
} else {
|
||||
if (core.hasItem('pickaxe')) {
|
||||
@ -558,7 +562,7 @@ gameKey
|
||||
}
|
||||
})
|
||||
.realize('skill3', () => {
|
||||
const HeroSkill = Mota.require('@user/data-state').HeroSkill;
|
||||
const HeroSkill = Mota.require('module', 'Mechanism').HeroSkill;
|
||||
if (!HeroSkill.learnedSkill(HeroSkill.Shield)) return;
|
||||
if (HeroSkill.getAutoSkill()) {
|
||||
tip('error', '已开启自动切换技能!');
|
||||
|
@ -1,18 +1,6 @@
|
||||
import { createDanmaku } from './danmaku';
|
||||
import { createFixed } from './fixed';
|
||||
import { createUI } from './ui';
|
||||
|
||||
export function createPreset() {
|
||||
createDanmaku();
|
||||
createFixed();
|
||||
createUI();
|
||||
}
|
||||
|
||||
export * from './ui';
|
||||
export * from './settings';
|
||||
export * from './danmaku';
|
||||
export * from './fixed';
|
||||
export * from './hotkey';
|
||||
export * from './keyboard';
|
||||
export * from './uiIns';
|
||||
export * from './settingIns';
|
||||
|
@ -1,3 +0,0 @@
|
||||
import { MotaSetting } from '../setting';
|
||||
|
||||
export const mainSetting = new MotaSetting();
|
@ -1,6 +1,6 @@
|
||||
import type { SettingComponent, SettingComponentProps } from '../setting';
|
||||
import { Button, InputNumber, Radio } from 'ant-design-vue';
|
||||
import { mainUi } from './uiIns';
|
||||
import { mainUi } from './ui';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
|
||||
interface Components {
|
||||
|
@ -1,16 +1,53 @@
|
||||
import { GameStorage } from '@motajs/legacy-system';
|
||||
import { createSettingComponents } from './settings';
|
||||
import { isMobile } from '../use';
|
||||
import { MotaSetting } from '../setting';
|
||||
import { triggerFullscreen } from '../utils';
|
||||
import { GameStorage, VirtualKey } from '@motajs/legacy-system';
|
||||
import {
|
||||
createSettingComponents,
|
||||
GameUi,
|
||||
isMobile,
|
||||
MotaSetting,
|
||||
triggerFullscreen,
|
||||
UI,
|
||||
UiController
|
||||
} from '@motajs/legacy-ui';
|
||||
import { bgmController, soundPlayer } from '@/module';
|
||||
import settingsText from '../data/settings.json';
|
||||
import { fixedUi, mainUi } from './uiIns';
|
||||
import { mainSetting } from './settingIns';
|
||||
|
||||
//#region legacy-ui
|
||||
export const mainUi = new UiController();
|
||||
mainUi.register(
|
||||
new GameUi('book', UI.Book),
|
||||
new GameUi('toolbox', UI.Toolbox),
|
||||
new GameUi('equipbox', UI.Equipbox),
|
||||
new GameUi('settings', UI.Settings),
|
||||
new GameUi('desc', UI.Desc),
|
||||
new GameUi('skill', UI.Skill),
|
||||
new GameUi('skillTree', UI.SkillTree),
|
||||
new GameUi('fly', UI.Fly),
|
||||
new GameUi('fixedDetail', UI.FixedDetail),
|
||||
new GameUi('shop', UI.Shop),
|
||||
new GameUi('achievement', UI.Achievement),
|
||||
new GameUi('hotkey', UI.Hotkey),
|
||||
new GameUi('toolEditor', UI.ToolEditor),
|
||||
new GameUi('virtualKey', VirtualKey)
|
||||
// todo: 把游戏主 div 加入到 mainUi 里面
|
||||
);
|
||||
mainUi.showAll();
|
||||
|
||||
export function createUI() {
|
||||
const { hook } = Mota.require('@user/data-base');
|
||||
export const fixedUi = new UiController(true);
|
||||
fixedUi.register(
|
||||
new GameUi('markedEnemy', UI.Marked),
|
||||
new GameUi('fixed', UI.Fixed),
|
||||
new GameUi('chapter', UI.Chapter),
|
||||
new GameUi('completeAchi', UI.CompleteAchi),
|
||||
new GameUi('start', UI.Start),
|
||||
new GameUi('toolbar', UI.Toolbar),
|
||||
new GameUi('load', UI.Load),
|
||||
new GameUi('danmaku', UI.Danmaku),
|
||||
new GameUi('danmakuEditor', UI.DanmakuEditor),
|
||||
new GameUi('tips', UI.Tips)
|
||||
);
|
||||
fixedUi.showAll();
|
||||
|
||||
const hook = Mota.require('var', 'hook');
|
||||
hook.once('mounted', () => {
|
||||
const ui = document.getElementById('ui-main')!;
|
||||
const fixed = document.getElementById('ui-fixed')!;
|
||||
@ -41,7 +78,6 @@ export function createUI() {
|
||||
fixed.style.display = 'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@ -49,6 +85,7 @@ export function createUI() {
|
||||
|
||||
const COM = createSettingComponents();
|
||||
|
||||
export const mainSetting = new MotaSetting();
|
||||
// 添加不参与全局存储的设置
|
||||
MotaSetting.noStorage.push('action.autoSkill', 'screen.fullscreen');
|
||||
|
||||
@ -106,7 +143,7 @@ function handleActionSetting<T extends number | boolean>(
|
||||
) {
|
||||
if (key === 'autoSkill') {
|
||||
// 自动切换技能
|
||||
const HeroSkill = Mota.require('@user/data-state').HeroSkill;
|
||||
const HeroSkill = Mota.require('module', 'Mechanism').HeroSkill;
|
||||
HeroSkill.setAutoSkill(n as boolean);
|
||||
core.status.route.push(`set:autoSkill:${n}`);
|
||||
}
|
||||
@ -117,7 +154,6 @@ function handleAudioSetting<T extends number | boolean>(
|
||||
n: T,
|
||||
_o: T
|
||||
) {
|
||||
const { bgmController, soundPlayer } = Mota.require('@user/client-modules');
|
||||
if (key === 'bgmEnabled') {
|
||||
bgmController.setEnabled(n as boolean);
|
||||
core.checkBgm();
|
||||
@ -147,6 +183,7 @@ function handleUiSetting<T extends number | boolean>(key: string, n: T, _o: T) {
|
||||
}
|
||||
|
||||
// ----- 游戏的所有设置项
|
||||
// todo: 虚拟键盘缩放,小地图楼传缩放
|
||||
mainSetting
|
||||
.register(
|
||||
'screen',
|
||||
@ -224,7 +261,7 @@ mainSetting
|
||||
.register('tips', '小贴士', true, COM.Boolean)
|
||||
);
|
||||
|
||||
const { loading } = Mota.require('@user/data-base');
|
||||
const loading = Mota.require('var', 'loading');
|
||||
loading.once('coreInit', () => {
|
||||
mainSetting.reset({
|
||||
'screen.fullscreen': !!document.fullscreenElement,
|
||||
@ -254,6 +291,10 @@ loading.once('coreInit', () => {
|
||||
isMobile ? 300 : Math.floor(window.innerWidth / 600) * 50
|
||||
),
|
||||
'ui.mapLazy': storage.getValue('ui.mapLazy', false),
|
||||
'ui.toolbarScale': storage.getValue(
|
||||
'ui.toolbarScale',
|
||||
isMobile ? 50 : Math.floor((window.innerWidth / 1700) * 10) * 10
|
||||
),
|
||||
'ui.bookScale': storage.getValue('ui.bookScale', isMobile ? 100 : 80),
|
||||
'ui.danmaku': storage.getValue('ui.danmaku', true),
|
||||
'ui.danmakuSpeed': storage.getValue(
|
||||
@ -268,6 +309,18 @@ interface SettingTextData {
|
||||
[x: string]: string[] | SettingTextData;
|
||||
}
|
||||
|
||||
function getSettingText(obj: SettingTextData, key?: string) {
|
||||
for (const [k, value] of Object.entries(obj)) {
|
||||
const setKey = key ? key + '.' + k : k;
|
||||
if (value instanceof Array) {
|
||||
mainSetting.setDescription(setKey, value.join('\n'));
|
||||
} else {
|
||||
getSettingText(value, setKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
getSettingText(settingsText);
|
||||
|
||||
mainSetting
|
||||
.setDescription('audio.bgmEnabled', `是否开启背景音乐`)
|
||||
.setDescription('audio.bgmVolume', `背景音乐的音量`)
|
||||
@ -278,6 +331,7 @@ mainSetting
|
||||
'ui.mapLazy',
|
||||
`是否启用小地图懒更新模式,此模式下剩余怪物数量不会实时更新而变成切换地图后更新,打开小地图时出现卡顿可以尝试开启此设置`
|
||||
)
|
||||
.setDescription('ui.toolbarScale', `自定义工具栏的缩放比例`)
|
||||
.setDescription(
|
||||
'ui.bookScale',
|
||||
`怪物手册界面中每个怪物框体的高度缩放,最小值限定为 20% 屏幕高度`
|
||||
@ -305,18 +359,6 @@ function setFontSize() {
|
||||
}
|
||||
setFontSize();
|
||||
|
||||
function getSettingText(obj: SettingTextData, key?: string) {
|
||||
for (const [k, value] of Object.entries(obj)) {
|
||||
const setKey = key ? key + '.' + k : k;
|
||||
if (value instanceof Array) {
|
||||
mainSetting.setDescription(setKey, value.join('\n'));
|
||||
} else {
|
||||
getSettingText(value, setKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
getSettingText(settingsText);
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
setFontSize();
|
||||
});
|
||||
|
@ -1,33 +0,0 @@
|
||||
import { GameUi, UiController } from '../controller';
|
||||
import * as UI from '../ui';
|
||||
import { VirtualKey } from '@motajs/legacy-system';
|
||||
|
||||
export const mainUi = new UiController();
|
||||
mainUi.register(
|
||||
new GameUi('book', UI.Book),
|
||||
new GameUi('toolbox', UI.Toolbox),
|
||||
new GameUi('equipbox', UI.Equipbox),
|
||||
new GameUi('settings', UI.Settings),
|
||||
new GameUi('desc', UI.Desc),
|
||||
new GameUi('skill', UI.Skill),
|
||||
new GameUi('skillTree', UI.SkillTree),
|
||||
new GameUi('fly', UI.Fly),
|
||||
new GameUi('fixedDetail', UI.FixedDetail),
|
||||
new GameUi('shop', UI.Shop),
|
||||
// new GameUi('achievement', UI.Achievement),
|
||||
new GameUi('hotkey', UI.Hotkey),
|
||||
new GameUi('virtualKey', VirtualKey)
|
||||
);
|
||||
mainUi.showAll();
|
||||
|
||||
export const fixedUi = new UiController(true);
|
||||
fixedUi.register(
|
||||
new GameUi('fixed', UI.Fixed),
|
||||
new GameUi('chapter', UI.Chapter),
|
||||
new GameUi('start', UI.Start),
|
||||
new GameUi('load', UI.Load),
|
||||
new GameUi('danmaku', UI.Danmaku),
|
||||
new GameUi('danmakuEditor', UI.DanmakuEditor),
|
||||
new GameUi('tips', UI.Tips)
|
||||
);
|
||||
fixedUi.showAll();
|
@ -1,6 +1,9 @@
|
||||
import { FunctionalComponent, reactive } from 'vue';
|
||||
import { EventEmitter } from '@motajs/legacy-common';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { has } from './utils';
|
||||
import { createSettingComponents } from './preset';
|
||||
|
||||
const COM = createSettingComponents();
|
||||
|
||||
export interface SettingComponentProps {
|
||||
item: MotaSettingItem;
|
||||
@ -82,7 +85,7 @@ export class MotaSetting extends EventEmitter<SettingEvent> {
|
||||
key: string,
|
||||
name: string,
|
||||
value: MotaSettingType,
|
||||
com: SettingComponent,
|
||||
com: SettingComponent = COM.Default,
|
||||
step: [number, number, number] = [0, 100, 1]
|
||||
) {
|
||||
const setting: MotaSettingItem = {
|
||||
@ -159,12 +162,12 @@ export class MotaSetting extends EventEmitter<SettingEvent> {
|
||||
defaultValue?: T
|
||||
): T | undefined {
|
||||
const setting = this.getSetting(key);
|
||||
if (isNil(setting) && isNil(defaultValue)) return void 0;
|
||||
if (!has(setting) && !has(defaultValue)) return void 0;
|
||||
if (setting instanceof MotaSetting) {
|
||||
if (!isNil(setting)) return defaultValue;
|
||||
if (has(setting)) return defaultValue;
|
||||
return void 0;
|
||||
} else {
|
||||
return !isNil(setting) ? (setting.value as T) : (defaultValue as T);
|
||||
return has(setting) ? (setting.value as T) : (defaultValue as T);
|
||||
}
|
||||
}
|
||||
|
||||
|
120
packages/legacy-ui/src/tools/achievement.ts
Normal file
120
packages/legacy-ui/src/tools/achievement.ts
Normal file
@ -0,0 +1,120 @@
|
||||
import list from '../data/achievement.json';
|
||||
import { achiDict, checkCompletionAchievement } from './completion';
|
||||
import { changeLocalStorage, has } from '../utils';
|
||||
import { fixedUi } from '../preset';
|
||||
|
||||
type AchievementList = typeof list;
|
||||
export type AchievementType = keyof AchievementList;
|
||||
|
||||
type AchievementData = Record<AchievementType, boolean[]>;
|
||||
|
||||
export interface Achievement {
|
||||
name: string;
|
||||
text: string[];
|
||||
point: number;
|
||||
hide?: string;
|
||||
progress?: string;
|
||||
percent?: boolean;
|
||||
}
|
||||
|
||||
export default function init() {
|
||||
return { completeAchievement, hasCompletedAchievement, addMountSign };
|
||||
}
|
||||
|
||||
export const totalPoint = Object.values(list)
|
||||
.map((v: Achievement[]) =>
|
||||
v.reduce((prev, curr) => {
|
||||
return curr.point + prev;
|
||||
}, 0)
|
||||
)
|
||||
.reduce((prev, curr) => prev + curr);
|
||||
|
||||
/**
|
||||
* 完成一个成就
|
||||
* @param type 成就类型
|
||||
* @param index 成就索引
|
||||
*/
|
||||
export function completeAchievement(type: AchievementType, index: number) {
|
||||
if (flags.debug || hasCompletedAchievement(type, index)) return;
|
||||
changeLocalStorage<AchievementData>(
|
||||
'achievement',
|
||||
data => {
|
||||
data[type][index] = true;
|
||||
return data;
|
||||
},
|
||||
{
|
||||
normal: [],
|
||||
challenge: [],
|
||||
explore: []
|
||||
}
|
||||
);
|
||||
if (type === 'explore' && !Object.values(achiDict).includes(index)) {
|
||||
checkCompletionAchievement();
|
||||
}
|
||||
fixedUi.open('completeAchi', {
|
||||
complete: `${type},${index}`
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否完成了某个成就
|
||||
* @param type 成就类型
|
||||
* @param index 成就索引
|
||||
*/
|
||||
export function hasCompletedAchievement(type: AchievementType, index: number) {
|
||||
let data = core.getLocalStorage<AchievementData>('achievement');
|
||||
if (!has(data)) {
|
||||
const d = {
|
||||
normal: [],
|
||||
challenge: [],
|
||||
explore: []
|
||||
};
|
||||
data = d;
|
||||
core.setLocalStorage('achievement', d);
|
||||
}
|
||||
return data[type][index] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前成就点数
|
||||
*/
|
||||
export function getNowPoint() {
|
||||
let res = 0;
|
||||
for (const [type, achi] of Object.entries(list)) {
|
||||
achi.forEach((v, i) => {
|
||||
if (hasCompletedAchievement(type as AchievementType, i)) {
|
||||
res += v.point;
|
||||
}
|
||||
});
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// ----- 各个成就相关的函数
|
||||
|
||||
/**
|
||||
* 山路木牌
|
||||
* @param id 木牌id
|
||||
*/
|
||||
export function addMountSign(id: number) {
|
||||
if (flags.debug) return;
|
||||
if (
|
||||
!core.getLocalStorage(`mountSign_${id}`, false) &&
|
||||
!hasCompletedAchievement('explore', 1)
|
||||
) {
|
||||
changeLocalStorage(
|
||||
'mountSign',
|
||||
n => {
|
||||
if (n + 1 >= 5) {
|
||||
completeAchievement('explore', 1);
|
||||
for (const i of [1, 2, 3, 4, 5]) {
|
||||
core.removeLocalStorage(`mountSign_${i}`);
|
||||
}
|
||||
}
|
||||
return n + 1;
|
||||
},
|
||||
0
|
||||
);
|
||||
core.setLocalStorage(`mountSign_${id}`, true);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { isNil } from 'lodash-es';
|
||||
import { has } from '../utils';
|
||||
import { IDamageEnemy } from '@motajs/types';
|
||||
|
||||
export interface CurrentEnemy {
|
||||
@ -73,7 +73,7 @@ export function getDefDamage(
|
||||
|
||||
if (res.length === 0) {
|
||||
origin = dam.damage;
|
||||
if (!isNil(origin)) {
|
||||
if (has(origin)) {
|
||||
res.push([addDef + i * ratio, origin]);
|
||||
last = origin;
|
||||
}
|
||||
@ -113,7 +113,7 @@ export function getCriticalDamage(
|
||||
|
||||
if (res.length === 0) {
|
||||
origin = dam.damage;
|
||||
if (!isNil(origin)) {
|
||||
if (has(origin)) {
|
||||
res.push([addAtk + i * ratio, origin]);
|
||||
last = origin;
|
||||
}
|
||||
|
113
packages/legacy-ui/src/tools/completion.ts
Normal file
113
packages/legacy-ui/src/tools/completion.ts
Normal file
@ -0,0 +1,113 @@
|
||||
import {
|
||||
AchievementType,
|
||||
completeAchievement,
|
||||
hasCompletedAchievement
|
||||
} from './achievement';
|
||||
import { changeLocalStorage } from '../utils';
|
||||
import list from '../data/achievement.json';
|
||||
|
||||
export const floors: Record<number, FloorIds[]> = {
|
||||
1: ['MT0', 'tower7']
|
||||
};
|
||||
const achis: Record<number, Record<AchievementType, number[]>> = {
|
||||
1: {
|
||||
normal: [0, 1],
|
||||
challenge: [0],
|
||||
explore: [1]
|
||||
}
|
||||
};
|
||||
|
||||
export const achiDict: Record<number, number> = {
|
||||
1: 0
|
||||
};
|
||||
|
||||
export function init() {
|
||||
Object.values(floors).forEach((v, i) => {
|
||||
const from = core.floorIds.indexOf(v[0]);
|
||||
const to = core.floorIds.indexOf(v[1]);
|
||||
const all = core.floorIds.slice(from, to + 1);
|
||||
floors[i + 1] = all;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查所有到达过的楼层,用于成就的计算
|
||||
*/
|
||||
export function checkVisitedFloor() {
|
||||
changeLocalStorage<Partial<Record<FloorIds, boolean>>>(
|
||||
'visitedFloor',
|
||||
data => {
|
||||
let needUpdate = false;
|
||||
core.floorIds.forEach(v => {
|
||||
if (core.hasVisitedFloor(v)) {
|
||||
data[v] = true;
|
||||
needUpdate = true;
|
||||
}
|
||||
});
|
||||
if (needUpdate) {
|
||||
checkCompletionAchievement();
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个章节的完成度
|
||||
* @param num 章节
|
||||
*/
|
||||
export function getChapterCompletion(num: number) {
|
||||
if (!achis[num]) return 0;
|
||||
let res = 0;
|
||||
const all = floors[num];
|
||||
const achiNum = Object.values(achis[num]).reduce(
|
||||
(pre, cur) => pre + cur.length,
|
||||
0
|
||||
);
|
||||
|
||||
// 计算到达过的楼层
|
||||
let visitedFloor = 0;
|
||||
const visited = core.getLocalStorage<Partial<Record<FloorIds, boolean>>>(
|
||||
'visitedFloor',
|
||||
{}
|
||||
);
|
||||
all.forEach(v => {
|
||||
if (visited[v]) visitedFloor++;
|
||||
});
|
||||
const floorRatio = all.length / (all.length + achiNum);
|
||||
const floorPoint = (floorRatio * visitedFloor) / all.length;
|
||||
|
||||
let completedPoint = 0;
|
||||
let totalPoint = 0;
|
||||
|
||||
// 计算成就,占比按成就点走
|
||||
for (const [type, achi] of Object.entries(achis[num]) as [
|
||||
AchievementType,
|
||||
number[]
|
||||
][]) {
|
||||
achi.forEach(v => {
|
||||
totalPoint += list[type][v].point;
|
||||
if (hasCompletedAchievement(type, v)) {
|
||||
completedPoint += list[type][v].point;
|
||||
}
|
||||
});
|
||||
}
|
||||
const achiPoint = (completedPoint / totalPoint) * (1 - floorRatio);
|
||||
|
||||
res = floorPoint + achiPoint;
|
||||
|
||||
return Math.floor(res * 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查完成度成就是否完成
|
||||
*/
|
||||
export function checkCompletionAchievement() {
|
||||
[1].forEach(v => {
|
||||
if (getChapterCompletion(v) >= 100) {
|
||||
completeAchievement('explore', achiDict[v]);
|
||||
}
|
||||
});
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import { isNil } from 'lodash-es';
|
||||
import { getStatusLabel } from '../utils';
|
||||
import { getStatusLabel, has } from '../utils';
|
||||
|
||||
/**
|
||||
* 获取所有装备
|
||||
@ -62,7 +61,7 @@ export function getNowStatus(nowEquip?: Equip, onCol: boolean = false) {
|
||||
'hpmax',
|
||||
'money'
|
||||
] as (keyof SelectType<HeroStatus, number>)[];
|
||||
const { getHeroStatusOn } = Mota.require('@user/data-state');
|
||||
const { getHeroStatusOn } = Mota.requireAll('fn');
|
||||
|
||||
return (
|
||||
<div id="hero-status">
|
||||
@ -72,7 +71,7 @@ export function getNowStatus(nowEquip?: Equip, onCol: boolean = false) {
|
||||
else status = getHeroStatusOn(v)?.toString();
|
||||
|
||||
let add = 0;
|
||||
if (!isNil(nowEquip)) {
|
||||
if (has(nowEquip)) {
|
||||
add += Math.floor(
|
||||
(nowEquip.value[v] ?? 0) * core.getBuff(v)
|
||||
);
|
||||
|
@ -31,7 +31,7 @@ export function getDetailedEnemy(
|
||||
const special: [string, string, string][] = [...enemy.info.special]
|
||||
.filter(v => !enemy.info.specialHalo?.includes(v))
|
||||
.map(vv => {
|
||||
const s = Mota.require('@user/data-state').specials[vv];
|
||||
const s = Mota.require('var', 'enemySpecials')[vv];
|
||||
return [
|
||||
fromFunc(s.name, enemy.info),
|
||||
fromFunc(s.desc, enemy.info),
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { isNil } from 'lodash-es';
|
||||
import { mainSetting } from '../preset/settingIns';
|
||||
import { tip } from '../use';
|
||||
import { downloadCanvasImage } from '../utils';
|
||||
import { mainSetting } from '../preset/ui';
|
||||
import { downloadCanvasImage, has, tip } from '../utils';
|
||||
|
||||
type BFSFromString = `${FloorIds},${number},${number},${Dir}`;
|
||||
type BFSToString = `${FloorIds},${number},${number}`;
|
||||
@ -165,7 +163,7 @@ export function getMapData(
|
||||
noCache: boolean = false
|
||||
): MapBFSResult {
|
||||
if (!floorId) return { maps: [], link: {} };
|
||||
if (!isNil(bfsCache[floorId]) && !noCache) return bfsCache[floorId]!;
|
||||
if (has(bfsCache[floorId]) && !noCache) return bfsCache[floorId]!;
|
||||
|
||||
const queue = [floorId];
|
||||
const used: Partial<Record<FloorIds, boolean>> = {
|
||||
@ -521,7 +519,7 @@ export class MinimapDrawer {
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.font = `3px "normal"`;
|
||||
ctx.strokeStyle = 'black';
|
||||
Mota.require('@user/data-state').ensureFloorDamage(floorId);
|
||||
Mota.require('fn', 'ensureFloorDamage')(floorId);
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
|
||||
ctx.fillRect(x - 6, y - 2, 12, 4);
|
||||
ctx.fillStyle = 'white';
|
||||
|
@ -1,10 +1,7 @@
|
||||
// import { init } from './achievement';
|
||||
|
||||
// init();
|
||||
|
||||
// export * from './achievement';
|
||||
export * from './achievement';
|
||||
export * from './book';
|
||||
export * from './common';
|
||||
export * from './completion';
|
||||
export * from './equipbox';
|
||||
export * from './fixed';
|
||||
export * from './fly';
|
||||
|
339
packages/legacy-ui/src/ui/achievement.vue
Normal file
339
packages/legacy-ui/src/ui/achievement.vue
Normal file
@ -0,0 +1,339 @@
|
||||
<template>
|
||||
<div id="achievement">
|
||||
<div id="tools">
|
||||
<span id="back" class="button-text tools" @click="exit"
|
||||
><left-outlined />返回游戏</span
|
||||
>
|
||||
</div>
|
||||
<div id="column">
|
||||
<div class="achievement-column" v-for="c of column">
|
||||
<span
|
||||
class="column-text button-text"
|
||||
:active="selectedColumn === c"
|
||||
@click="selectedColumn = c"
|
||||
>{{ columnName[c] }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider dashed id="divider"></a-divider>
|
||||
<div id="list">
|
||||
<div id="achievement-list" :style="{ left: `-${offset}%` }">
|
||||
<div v-for="t of column" class="achievement-one">
|
||||
<Scroll class="list-scroll" :width="isMobile ? 10 : 20">
|
||||
<div class="list-div">
|
||||
<div
|
||||
v-for="a of getAllAchievements(t)"
|
||||
class="list-one"
|
||||
>
|
||||
<div
|
||||
class="list-content"
|
||||
:complete="a.complete"
|
||||
>
|
||||
<span class="list-name">{{ a.name }}</span>
|
||||
<span
|
||||
class="list-text"
|
||||
v-html="a.text"
|
||||
></span>
|
||||
<div class="list-end">
|
||||
<div class="end-info">
|
||||
<span
|
||||
class="complete"
|
||||
:complete="a.complete"
|
||||
>完成情况:
|
||||
{{
|
||||
a.complete
|
||||
? '已完成'
|
||||
: '未完成'
|
||||
}}</span
|
||||
>
|
||||
<span class="point"
|
||||
>成就点数: {{ a.point }}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-if="a.progress"
|
||||
class="list-progress"
|
||||
>
|
||||
<a-progress
|
||||
:percent="a.percent"
|
||||
:strokeColor="{
|
||||
'0%': '#108ee9',
|
||||
'100%': '#87d068'
|
||||
}"
|
||||
:strokeWidth="height / 150"
|
||||
:format="
|
||||
() =>
|
||||
a.usePercent
|
||||
? `${a.percent}%`
|
||||
: a.progress
|
||||
"
|
||||
></a-progress>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider id="divider" dashed></a-divider>
|
||||
</div>
|
||||
</div>
|
||||
</Scroll>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="total-progress">
|
||||
<a-progress
|
||||
id="point-progress"
|
||||
:percent="(nowPoint / totalPoint) * 100"
|
||||
:strokeColor="{
|
||||
'0%': '#108ee9',
|
||||
'100%': '#87d068'
|
||||
}"
|
||||
:strokeWidth="height / 150"
|
||||
:showInfo="false"
|
||||
></a-progress>
|
||||
<span id="point-number"
|
||||
>成就点: {{ nowPoint }} / {{ totalPoint }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { LeftOutlined } from '@ant-design/icons-vue';
|
||||
import list from '../data/achievement.json';
|
||||
import {
|
||||
Achievement,
|
||||
getNowPoint,
|
||||
hasCompletedAchievement
|
||||
} from '../tools/achievement';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import { isMobile } from '../use';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
}>();
|
||||
|
||||
type AchievementList = typeof list;
|
||||
type AchievementType = keyof AchievementList;
|
||||
|
||||
interface ResolvedAchievement {
|
||||
name: string;
|
||||
text: string;
|
||||
complete: boolean;
|
||||
point: number;
|
||||
/** number / number */
|
||||
progress?: string;
|
||||
percent?: number;
|
||||
usePercent?: boolean;
|
||||
}
|
||||
|
||||
const column: AchievementType[] = ['normal', 'challenge', 'explore'];
|
||||
const columnName = {
|
||||
normal: '普通成就',
|
||||
challenge: '挑战成就',
|
||||
explore: '探索成就'
|
||||
};
|
||||
|
||||
const selectedColumn = ref<AchievementType>('normal');
|
||||
|
||||
const offset = computed(() => {
|
||||
return column.indexOf(selectedColumn.value) * 100;
|
||||
});
|
||||
|
||||
const height = window.innerHeight;
|
||||
const width = window.innerWidth;
|
||||
|
||||
const totalPoint = Object.values(list)
|
||||
.map((v: Achievement[]) =>
|
||||
v.reduce((prev, curr) => {
|
||||
return curr.point + prev;
|
||||
}, 0)
|
||||
)
|
||||
.reduce((prev, curr) => prev + curr);
|
||||
const nowPoint = getNowPoint();
|
||||
|
||||
/**
|
||||
* 获取一个类型的所有成就
|
||||
* @param type 成就类型
|
||||
*/
|
||||
function getAllAchievements(type: AchievementType): ResolvedAchievement[] {
|
||||
return list[type].map<ResolvedAchievement>((v: Achievement, i) => {
|
||||
const complete = hasCompletedAchievement(type, i);
|
||||
const text = v.hide && !complete ? v.hide : v.text.join('');
|
||||
const res: ResolvedAchievement = {
|
||||
text,
|
||||
name: v.name,
|
||||
point: v.point,
|
||||
complete
|
||||
};
|
||||
if (v.progress) {
|
||||
const p = eval('`' + v.progress + '`') as string;
|
||||
res.progress = p;
|
||||
res.percent = Math.floor(eval(p) * 100);
|
||||
if (v.percent) res.usePercent = true;
|
||||
}
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
function exit() {
|
||||
Mota.require('var', 'mainUi').close(props.num);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
#achievement {
|
||||
width: 90vh;
|
||||
height: 90vh;
|
||||
font-family: 'normal';
|
||||
font-size: 150%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#divider {
|
||||
margin: 1% 0;
|
||||
border-color: #ddd4;
|
||||
}
|
||||
|
||||
#tools {
|
||||
height: 5vh;
|
||||
font-size: 3.2vh;
|
||||
}
|
||||
|
||||
#column {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
margin-top: 3%;
|
||||
font-size: 130%;
|
||||
}
|
||||
|
||||
.list-scroll {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#list {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
#achievement-list {
|
||||
position: relative;
|
||||
width: 300%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
transition: left 0.4s ease;
|
||||
}
|
||||
|
||||
.achievement-one {
|
||||
width: 90vh;
|
||||
}
|
||||
|
||||
.list-div {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list-one {
|
||||
width: 70%;
|
||||
|
||||
.list-content {
|
||||
height: 18vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border: 2px double rgba(132, 132, 132, 0.17);
|
||||
border-radius: 10px;
|
||||
margin: 2% 0 2.5% 0;
|
||||
background-color: rgba(59, 59, 59, 0.281);
|
||||
}
|
||||
|
||||
.list-content[complete='true'] {
|
||||
background-color: rgba(239, 255, 63, 0.205);
|
||||
}
|
||||
|
||||
.list-name {
|
||||
border-bottom: 1px solid #ddd4;
|
||||
}
|
||||
|
||||
.list-text {
|
||||
font-size: 100%;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.list-end {
|
||||
width: 90%;
|
||||
height: 95%;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
font-size: 90%;
|
||||
|
||||
.end-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.complete {
|
||||
color: lightcoral;
|
||||
}
|
||||
|
||||
.complete[complete='true'] {
|
||||
color: lightgreen;
|
||||
}
|
||||
}
|
||||
|
||||
.list-progress {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.progress {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#total-progress {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
#point-progress {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#point-number {
|
||||
font-size: 70%;
|
||||
margin-left: 2%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
#achievement {
|
||||
width: 90vw;
|
||||
height: 90vh;
|
||||
}
|
||||
|
||||
.list-one {
|
||||
width: 90%;
|
||||
|
||||
.list-content {
|
||||
height: 15vh;
|
||||
}
|
||||
|
||||
.list-end {
|
||||
margin-bottom: 0.8vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -41,17 +41,21 @@
|
||||
import { onUnmounted, ref } from 'vue';
|
||||
import EnemyOne from '../components/enemyOne.vue';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import { has } from '../utils';
|
||||
import BookDetail from './bookDetail.vue';
|
||||
import { LeftOutlined } from '@ant-design/icons-vue';
|
||||
import { ToShowEnemy, detailInfo } from '../tools/book';
|
||||
import { getDetailedEnemy } from '../tools/fixed';
|
||||
import { GameUi } from '../controller';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { mainSetting } from '../preset/settingIns';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import { mainSetting } from '../preset/ui';
|
||||
import { isMobile } from '../use';
|
||||
import { IMountedVBind } from '../interface';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
const props = defineProps<IMountedVBind>();
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
}>();
|
||||
|
||||
const floorId =
|
||||
// @ts-ignore
|
||||
@ -118,12 +122,12 @@ async function show() {
|
||||
* 退出怪物手册
|
||||
*/
|
||||
async function exit() {
|
||||
const hold = props.controller.holdOn();
|
||||
props.controller.close(props.num);
|
||||
const hold = mainUi.holdOn();
|
||||
mainUi.close(props.num);
|
||||
if (core.events.recoverEvents(core.status.event.interval)) {
|
||||
hold.end(true);
|
||||
return;
|
||||
} else if (!isNil(core.status.event.ui)) {
|
||||
} else if (has(core.status.event.ui)) {
|
||||
core.status.boxAnimateObjs = [];
|
||||
// @ts-ignore
|
||||
core.ui._drawViewMaps(core.status.event.ui);
|
||||
|
@ -22,7 +22,7 @@
|
||||
:from-book="fromBook"
|
||||
v-else-if="panel === 'critical'"
|
||||
></EnemyCritical>
|
||||
<!-- <EnemyTarget v-else-if="panel === 'target'"></EnemyTarget> -->
|
||||
<EnemyTarget v-else-if="panel === 'target'"></EnemyTarget>
|
||||
</Transition>
|
||||
<div id="detail-more">
|
||||
<Transition name="detail">
|
||||
@ -35,8 +35,8 @@
|
||||
id="enemy-target"
|
||||
class="button-text more"
|
||||
@click="changePanel($event, 'target')"
|
||||
><LeftOutlined />
|
||||
</span>
|
||||
><LeftOutlined /> 怪物更多信息</span
|
||||
>
|
||||
<span
|
||||
id="critical-more"
|
||||
class="button-text more"
|
||||
@ -81,7 +81,7 @@ import { useDrag } from '../use';
|
||||
import EnemySpecial from '../panel/enemySpecial.vue';
|
||||
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
|
||||
import EnemyCritical from '../panel/enemyCritical.vue';
|
||||
// import EnemyTarget from '../panel/enemyTarget.vue';
|
||||
import EnemyTarget from '../panel/enemyTarget.vue';
|
||||
import { detailInfo } from '../tools/book';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
|
||||
|
@ -1,17 +1,22 @@
|
||||
<template>
|
||||
<div id="chapter">
|
||||
<canvas id="chapter-back"></canvas>
|
||||
<span id="chapter-text">{{ props.chapter }}</span>
|
||||
<span id="chapter-text">{{ chapter }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Animation, hyper, sleep } from 'mutate-animate';
|
||||
import { onMounted } from 'vue';
|
||||
import { IMountedVBind } from '../interface';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { has } from '../utils';
|
||||
import { GameUi } from '../controller';
|
||||
import { fixedUi } from '../preset/ui';
|
||||
|
||||
const props = defineProps<IMountedVBind>();
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
chapter: string;
|
||||
}>();
|
||||
|
||||
let can: HTMLCanvasElement;
|
||||
let ctx: CanvasRenderingContext2D;
|
||||
@ -45,14 +50,14 @@ onMounted(async () => {
|
||||
let started = false;
|
||||
|
||||
ani.ticker.add(time => {
|
||||
if (isNil(time) || isNaN(time)) return;
|
||||
if (!has(time) || isNaN(time)) return;
|
||||
if (!started) {
|
||||
started = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (time >= 4050) {
|
||||
props.controller.close(props.num);
|
||||
fixedUi.close(props.num);
|
||||
ani.ticker.destroy();
|
||||
}
|
||||
|
||||
|
113
packages/legacy-ui/src/ui/completeAchievement.vue
Normal file
113
packages/legacy-ui/src/ui/completeAchievement.vue
Normal file
@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<Box id="complete-box">
|
||||
<div id="complete">
|
||||
<span>完成成就 {{ achi.name }}</span>
|
||||
<a-progress
|
||||
id="progress"
|
||||
:percent="progress * 100"
|
||||
:strokeColor="{
|
||||
'0%': '#108ee9',
|
||||
'100%': '#87d068'
|
||||
}"
|
||||
:strokeWidth="height / 200"
|
||||
:showInfo="false"
|
||||
></a-progress>
|
||||
<span id="point-number">成就点: {{ now }} / {{ totalPoint }}</span>
|
||||
</div>
|
||||
</Box>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { sleep, Ticker } from 'mutate-animate';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import Box from '../components/box.vue';
|
||||
import list from '../data/achievement.json';
|
||||
import { AchievementType, getNowPoint, totalPoint } from '../tools/achievement';
|
||||
import { GameUi } from '../controller';
|
||||
import { fixedUi } from '../preset/ui';
|
||||
|
||||
const height = window.innerHeight;
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
complete: string;
|
||||
}>();
|
||||
|
||||
const c = props.complete.split(',');
|
||||
const type = c[0] as AchievementType;
|
||||
const index = parseInt(c[1]);
|
||||
|
||||
const achi = list[type][index];
|
||||
const point = achi.point;
|
||||
|
||||
const nowPoint = getNowPoint() - point;
|
||||
const now = ref(nowPoint);
|
||||
const progress = computed(() => now.value / totalPoint);
|
||||
|
||||
onMounted(async () => {
|
||||
await sleep(500);
|
||||
const ticker = new Ticker();
|
||||
const time = Date.now();
|
||||
ticker.add(() => {
|
||||
const nowTime = Date.now();
|
||||
if (nowTime - time > 1000) {
|
||||
now.value = nowPoint + point;
|
||||
ticker.destroy();
|
||||
}
|
||||
const ratio = (nowTime - time) / 1000;
|
||||
now.value = Math.floor(nowPoint + point * ratio);
|
||||
});
|
||||
await sleep(4600);
|
||||
fixedUi.close(props.num);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
#complete-box {
|
||||
width: 30vw;
|
||||
height: 13vh;
|
||||
left: 35vw;
|
||||
position: fixed;
|
||||
background-color: #000d;
|
||||
animation: ani 5s ease 0s 1 forwards running;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
#complete {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: 'normal';
|
||||
font-size: 2.2vh;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#progress {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
@keyframes ani {
|
||||
0% {
|
||||
top: -30vh;
|
||||
}
|
||||
20% {
|
||||
top: 4vh;
|
||||
}
|
||||
80% {
|
||||
top: 4vh;
|
||||
}
|
||||
100% {
|
||||
top: -30vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
#complete-box {
|
||||
width: 90vw;
|
||||
left: 5%;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -30,7 +30,7 @@
|
||||
import { nextTick, onUnmounted, reactive, watch } from 'vue';
|
||||
import { Danmaku } from '../danmaku';
|
||||
import { LikeFilled } from '@ant-design/icons-vue';
|
||||
import { mainSetting } from '../preset/settingIns';
|
||||
import { mainSetting } from '../preset/ui';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
interface ElementMap {
|
||||
@ -205,7 +205,10 @@ onUnmounted(() => {});
|
||||
}
|
||||
|
||||
.danmaku-info {
|
||||
text-shadow: 1px 1px 1px black, 1px -1px 1px black, -1px 1px 1px black,
|
||||
text-shadow:
|
||||
1px 1px 1px black,
|
||||
1px -1px 1px black,
|
||||
-1px 1px 1px black,
|
||||
-1px -1px 1px black;
|
||||
}
|
||||
|
||||
|
@ -159,17 +159,21 @@ import {
|
||||
UpOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { Danmaku } from '../danmaku';
|
||||
import { GameUi } from '../controller';
|
||||
import { sleep } from 'mutate-animate';
|
||||
import { calStringSize, stringifyCSS, parseCss, getIconHeight } from '../utils';
|
||||
import { fixedUi } from '../preset/ui';
|
||||
import { calStringSize, tip } from '../utils';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { stringifyCSS, parseCss, getIconHeight } from '../utils';
|
||||
import { logger, LogLevel } from '@motajs/common';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import BoxAnimate from '../components/boxAnimate.vue';
|
||||
import { IMountedVBind } from '../interface';
|
||||
import { tip } from '../use';
|
||||
|
||||
const props = defineProps<IMountedVBind>();
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
}>();
|
||||
|
||||
const frequentlyIcon: (AllIds | 'hero' | `X${number}`)[] = [
|
||||
'hero',
|
||||
@ -297,7 +301,7 @@ function close() {
|
||||
mainDiv.classList.remove('danmaku-startup');
|
||||
mainDiv.classList.add('danmaku-close');
|
||||
sleep(200).then(() => {
|
||||
props.controller.close(props.num);
|
||||
fixedUi.close(props.num);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -22,17 +22,21 @@ import { computed, onUnmounted, ref } from 'vue';
|
||||
import desc from '../data/desc.json';
|
||||
import { splitText } from '../utils';
|
||||
import Colomn from '../components/colomn.vue';
|
||||
import { GameUi } from '../controller';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { IMountedVBind } from '../interface';
|
||||
import { mainUi } from '../preset/ui';
|
||||
|
||||
const props = defineProps<IMountedVBind>();
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
}>();
|
||||
|
||||
type DescKey = keyof typeof desc;
|
||||
|
||||
const selected = ref(Object.keys(desc)[0] as DescKey);
|
||||
|
||||
function exit() {
|
||||
props.controller.close(props.num);
|
||||
mainUi.close(props.num);
|
||||
}
|
||||
|
||||
const content = computed(() => {
|
||||
|
@ -125,8 +125,8 @@
|
||||
<BoxAnimate
|
||||
:id="
|
||||
isCol
|
||||
? equiped[selected] ?? 'none'
|
||||
: toShow[selected]?.[0] ?? 'none'
|
||||
? (equiped[selected] ?? 'none')
|
||||
: (toShow[selected]?.[0] ?? 'none')
|
||||
"
|
||||
></BoxAnimate>
|
||||
<span>{{ equip.name }}</span>
|
||||
@ -183,14 +183,18 @@ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import { getAddStatus, getEquips, getNowStatus } from '../tools/equipbox';
|
||||
import BoxAnimate from '../components/boxAnimate.vue';
|
||||
import { type, getStatusLabel } from '../utils';
|
||||
import { cancelGlobalDrag, isMobile, tip, useDrag } from '../use';
|
||||
import { has, tip, type } from '../utils';
|
||||
import { cancelGlobalDrag, isMobile, useDrag } from '../use';
|
||||
import { hyper } from 'mutate-animate';
|
||||
import { GameUi } from '../controller';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { IMountedVBind } from '../interface';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { getStatusLabel } from '../utils';
|
||||
import { mainUi } from '../preset/ui';
|
||||
|
||||
const props = defineProps<IMountedVBind>();
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
}>();
|
||||
|
||||
const equips = ref(getEquips());
|
||||
const col = ref('all');
|
||||
@ -238,10 +242,10 @@ const equip = computed(() => {
|
||||
if (isCol.value) {
|
||||
const id = equiped.value[selected.value];
|
||||
const e = core.material.items[id];
|
||||
if (isNil(e)) return none;
|
||||
if (!has(e)) return none;
|
||||
return e;
|
||||
}
|
||||
if (isNil(index)) return none;
|
||||
if (!has(index)) return none;
|
||||
return all[index[0]];
|
||||
});
|
||||
|
||||
@ -272,7 +276,7 @@ const toShow = computed(() => {
|
||||
const e = all[v[0]].equip!;
|
||||
const t = e.type;
|
||||
if (sortNorm !== 'none') {
|
||||
if (isNil(e[sortBy][sortNorm])) return false;
|
||||
if (!has(e[sortBy][sortNorm])) return false;
|
||||
}
|
||||
if (col.value === 'all') return true;
|
||||
if (typeof t === 'string') return t === col.value;
|
||||
@ -305,7 +309,7 @@ function changeSort() {
|
||||
}
|
||||
|
||||
function exit() {
|
||||
props.controller.close(props.num);
|
||||
mainUi.close(props.num);
|
||||
}
|
||||
|
||||
function clickList(i: number) {
|
||||
@ -346,7 +350,7 @@ function canDragin(type: number) {
|
||||
if (type < 0) return false;
|
||||
const et = equip.value.equip?.type;
|
||||
if (!core.canEquip(toShow.value[selected.value]?.[0])) return false;
|
||||
if (isNil(et)) return false;
|
||||
if (!has(et)) return false;
|
||||
if (typeof et === 'number') return type === et;
|
||||
return equipCol[type] === et;
|
||||
}
|
||||
@ -423,10 +427,10 @@ function dragout(e: Event) {
|
||||
}
|
||||
|
||||
function toTool() {
|
||||
props.controller.holdOn();
|
||||
mainUi.holdOn();
|
||||
exit();
|
||||
nextTick(() => {
|
||||
props.controller.open('toolbox');
|
||||
mainUi.open('toolbox');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -31,12 +31,14 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUpdated, Ref, ref, watch } from 'vue';
|
||||
import Box from '../components/box.vue';
|
||||
import { GameUi } from '../controller';
|
||||
import type { DamageEnemy, EnemyInfo } from '@/game/enemy/damage';
|
||||
import { nextFrame } from '../utils';
|
||||
import { EnemyInfo, IDamageEnemy } from '@motajs/types';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
enemy: IDamageEnemy;
|
||||
ui: GameUi;
|
||||
enemy: DamageEnemy;
|
||||
close: Ref<boolean>;
|
||||
loc: [x: number, y: number];
|
||||
}>();
|
||||
@ -81,7 +83,7 @@ const special = (() => {
|
||||
};
|
||||
|
||||
const show = s.slice(0, 2).map(v => {
|
||||
const s = Mota.require('@user/data-state').specials[v];
|
||||
const s = Mota.require('var', 'enemySpecials')[v];
|
||||
return [fromFunc(s.name, enemy.info), s.color];
|
||||
});
|
||||
if (s.length > 2) show.push(['...', 'white']);
|
||||
|
@ -13,9 +13,14 @@ import { getDetailedEnemy } from '../tools/fixed';
|
||||
import BookDetail from './bookDetail.vue';
|
||||
import { detailInfo } from '../tools/book';
|
||||
import { hovered } from '../preset/fixed';
|
||||
import { IMountedVBind } from '../interface';
|
||||
import { GameUi } from '../controller';
|
||||
import { mainUi } from '../preset/ui';
|
||||
|
||||
const props = defineProps<IMountedVBind>();
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
panel?: 'special' | 'critical' | 'target';
|
||||
}>();
|
||||
|
||||
const panel = props.panel ?? 'special';
|
||||
|
||||
@ -35,7 +40,7 @@ if (hovered) {
|
||||
}
|
||||
|
||||
function close() {
|
||||
props.controller.close(props.num);
|
||||
mainUi.close(props.num);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -98,14 +98,18 @@ import {
|
||||
DoubleRightOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { tip } from '../use';
|
||||
import { tip } from '../utils';
|
||||
import { GameUi } from '../controller';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { createChangable } from '../tools/common';
|
||||
import { mainSetting } from '../preset/settingIns';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import { mainSetting } from '../preset/ui';
|
||||
import { GameStorage } from '@motajs/legacy-system';
|
||||
import { IMountedVBind } from '../interface';
|
||||
|
||||
const props = defineProps<IMountedVBind>();
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
}>();
|
||||
|
||||
type Loc2 = [number, number, number, number];
|
||||
|
||||
@ -158,7 +162,7 @@ let thumb: HTMLCanvasElement;
|
||||
let thumbCtx: CanvasRenderingContext2D;
|
||||
|
||||
function exit() {
|
||||
props.controller.close(props.num);
|
||||
mainUi.close(props.num);
|
||||
}
|
||||
|
||||
const title = computed(() => {
|
||||
@ -580,7 +584,10 @@ onUnmounted(() => {
|
||||
max-width: 50%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-shadow: 1px 1px 1px black, 1px -1px 1px black, -1px 1px 1px black,
|
||||
text-shadow:
|
||||
1px 1px 1px black,
|
||||
1px -1px 1px black,
|
||||
-1px 1px 1px black,
|
||||
-1px -1px 1px black;
|
||||
}
|
||||
|
||||
|
@ -37,13 +37,14 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Hotkey } from '@motajs/system-action';
|
||||
import { GameUi } from '../controller';
|
||||
import Column from '../components/colomn.vue';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import { computed, onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||
import { KeyCode, KeyCodeUtils } from '@motajs/client-base';
|
||||
import { generateBinary, keycode } from '../utils';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { generateBinary, keycode } from '@motajs/legacy-common';
|
||||
import { IMountedVBind } from '../interface';
|
||||
|
||||
interface HotkeyKeys {
|
||||
index: number;
|
||||
@ -63,12 +64,16 @@ interface SelectedKey {
|
||||
index: number;
|
||||
}
|
||||
|
||||
const props = defineProps<IMountedVBind & { hotkey: Hotkey }>();
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
hotkey: Hotkey;
|
||||
}>();
|
||||
|
||||
const hotkey = props.hotkey;
|
||||
|
||||
function close() {
|
||||
props.controller.close(props.num);
|
||||
mainUi.close(props.num);
|
||||
}
|
||||
|
||||
const selectedGroup = ref('ui');
|
||||
|
@ -1,11 +1,15 @@
|
||||
export { default as Achievement } from './achievement.vue';
|
||||
export { default as BgmList } from './bgmList.vue';
|
||||
export { default as Book } from './book.vue';
|
||||
export { default as BookDetail } from './bookDetail.vue';
|
||||
export { default as Chapter } from './chapter.vue';
|
||||
export { default as CompleteAchi } from './completeAchievement.vue';
|
||||
export { default as Desc } from './desc.vue';
|
||||
export { default as Equipbox } from './equipbox.vue';
|
||||
export { default as Fixed } from './fixed.vue';
|
||||
export { default as FixedDetail } from './fixedDetail.vue';
|
||||
export { default as Fly } from './fly.vue';
|
||||
export { default as Marked } from './markedEnemy.vue';
|
||||
export { default as Settings } from './settings.vue';
|
||||
export { default as Shop } from './shop.vue';
|
||||
export { default as Skill } from './skill.vue';
|
||||
@ -13,6 +17,8 @@ export { default as SkillTree } from './skillTree.vue';
|
||||
export { default as Start } from './start.vue';
|
||||
export { default as Toolbox } from './toolbox.vue';
|
||||
export { default as Hotkey } from './hotkey.vue';
|
||||
export { default as Toolbar } from './toolbar.vue';
|
||||
export { default as ToolEditor } from './toolEditor.vue';
|
||||
export { default as Load } from './load.vue';
|
||||
export { default as Danmaku } from './danmaku.vue';
|
||||
export { default as DanmakuEditor } from './danmakuEditor.vue';
|
||||
|
@ -34,10 +34,14 @@ import {
|
||||
import { GameUi } from '../controller';
|
||||
import { formatSize } from '../utils';
|
||||
import { logger } from '@motajs/common';
|
||||
import { fixedUi } from '../preset/ui';
|
||||
import { sleep } from 'mutate-animate';
|
||||
import { IMountedVBind } from '../interface';
|
||||
|
||||
const props = defineProps<IMountedVBind>();
|
||||
const props = defineProps<{
|
||||
ui: GameUi;
|
||||
num: number;
|
||||
callback?: () => void;
|
||||
}>();
|
||||
|
||||
const loading = ref(0);
|
||||
const loaded = ref(0);
|
||||
@ -70,10 +74,10 @@ onMounted(async () => {
|
||||
core._afterLoadResources(props.callback);
|
||||
logger.log(`Resource load end.`);
|
||||
loadDiv.style.opacity = '0';
|
||||
Mota.require('@user/data-base').loading.emit('loaded');
|
||||
Mota.require('var', 'loading').emit('loaded');
|
||||
await sleep(1000);
|
||||
props.controller.close(props.num);
|
||||
props.controller.open('start');
|
||||
fixedUi.close(props.num);
|
||||
fixedUi.open('start');
|
||||
});
|
||||
loadDiv = document.getElementById('load') as HTMLDivElement;
|
||||
});
|
||||
|
157
packages/legacy-ui/src/ui/markedEnemy.vue
Normal file
157
packages/legacy-ui/src/ui/markedEnemy.vue
Normal file
@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<div id="marked-enemy">
|
||||
<Box
|
||||
v-model:left="boxPos.left"
|
||||
v-model:top="boxPos.top"
|
||||
v-model:width="boxPos.width"
|
||||
v-model:height="boxPos.height"
|
||||
:resizable="true"
|
||||
:dragable="true"
|
||||
>
|
||||
<Scroll class="box-scroll" :no-scroll="true">
|
||||
<div class="marked-main">
|
||||
<div class="marked-info">
|
||||
<BoxAnimate
|
||||
:id="enemy.id"
|
||||
:width="24"
|
||||
:height="24"
|
||||
></BoxAnimate>
|
||||
<span class="marked-name marked-item">{{
|
||||
getName()
|
||||
}}</span>
|
||||
</div>
|
||||
<span class="marked-damage marked-item"
|
||||
>伤害:{{ format(info.damage) }}</span
|
||||
>
|
||||
<span class="marked-critical marked-item"
|
||||
>临界:{{ format(info.critical) }}</span
|
||||
>
|
||||
<span class="marked-critical-damage marked-item"
|
||||
>减伤:{{ format(info.criticalDam) }}</span
|
||||
>
|
||||
<span class="marked-def marked-item"
|
||||
>{{ ratio }}防:{{ format(info.defDamage) }}</span
|
||||
>
|
||||
<div class="marked-button">
|
||||
<span
|
||||
class="marked-hide button-text"
|
||||
@click.stop="hidden = true"
|
||||
>隐藏盒子</span
|
||||
>
|
||||
<span
|
||||
class="marked-cancel button-text"
|
||||
@click.stop="unmarkEnemy(enemy.id)"
|
||||
>取消标记</span
|
||||
>
|
||||
</div>
|
||||
</div></Scroll
|
||||
>
|
||||
</Box>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { MarkInfo, unmarkEnemy } from '../mark';
|
||||
import Box from '../components/box.vue';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import BoxAnimate from '../components/boxAnimate.vue';
|
||||
import { GameUi } from '../controller';
|
||||
import { fixedUi } from '../preset/ui';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
enemy: MarkInfo<EnemyIds>;
|
||||
}>();
|
||||
|
||||
interface BoxPos {
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
interface MarkedEnemy {
|
||||
damage: number;
|
||||
critical: number;
|
||||
criticalDam: number;
|
||||
defDamage: number;
|
||||
}
|
||||
|
||||
const enemy = props.enemy;
|
||||
const ratio = core.status.thisMap?.ratio ?? 1;
|
||||
|
||||
const format = core.formatBigNumber;
|
||||
|
||||
const boxPos = reactive<BoxPos>({
|
||||
left: window.innerWidth - 300,
|
||||
top: 100,
|
||||
width: 200,
|
||||
height: 150
|
||||
});
|
||||
const info = reactive<MarkedEnemy>({
|
||||
damage: 0,
|
||||
critical: 0,
|
||||
criticalDam: 0,
|
||||
defDamage: 0
|
||||
});
|
||||
|
||||
const hidden = ref(false);
|
||||
|
||||
watch(hidden, n => {
|
||||
if (n) fixedUi.close(props.num);
|
||||
});
|
||||
watch(enemy.update, update);
|
||||
|
||||
function update() {
|
||||
info.damage = enemy.enemy.calDamage().damage;
|
||||
const critical = enemy.enemy.calCritical()[0];
|
||||
info.critical = critical?.atkDelta ?? 0;
|
||||
info.criticalDam = critical?.delta ?? 0;
|
||||
info.defDamage = enemy.enemy.calDefDamage(ratio).delta;
|
||||
}
|
||||
|
||||
function getName() {
|
||||
return enemy.enemy.enemy.name;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
#marked-enemy {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.box-scroll {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.marked-main {
|
||||
padding: 1vh 0;
|
||||
background-color: #0009;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.marked-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.marked-item {
|
||||
margin-left: 10%;
|
||||
}
|
||||
|
||||
.marked-button {
|
||||
align-self: center;
|
||||
width: 80%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
}
|
||||
</style>
|
@ -73,7 +73,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onUnmounted, ref, shallowRef } from 'vue';
|
||||
import { mainSetting } from '../preset/settingIns';
|
||||
import { mainSetting } from '../preset/ui';
|
||||
import {
|
||||
MotaSetting,
|
||||
MotaSettingItem,
|
||||
@ -85,9 +85,14 @@ import { splitText } from '../utils';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import { isMobile } from '../use';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { IMountedVBind } from '../interface';
|
||||
import { GameUi } from '../controller';
|
||||
import { mainUi } from '../preset/ui';
|
||||
|
||||
const props = defineProps<IMountedVBind>();
|
||||
const props = defineProps<{
|
||||
info?: MotaSetting;
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
}>();
|
||||
|
||||
const setting = props.info ?? mainSetting;
|
||||
const display = shallowRef<SettingDisplayInfo[]>([]);
|
||||
@ -140,7 +145,7 @@ function click(key: string, index: number, item: MotaSettingItem) {
|
||||
}
|
||||
|
||||
function exit() {
|
||||
props.controller.close(props.num);
|
||||
mainUi.close(props.num);
|
||||
}
|
||||
|
||||
gameKey.use(props.ui.symbol);
|
||||
|
@ -169,14 +169,18 @@ import {
|
||||
RightOutlined,
|
||||
DoubleRightOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { splitText } from '../utils';
|
||||
import { splitText, tip } from '../utils';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import BoxAnimate from '../components/boxAnimate.vue';
|
||||
import { GameUi } from '../controller';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { IMountedVBind } from '../interface';
|
||||
import { tip } from '../use';
|
||||
import { mainUi } from '../preset/ui';
|
||||
|
||||
const props = defineProps<IMountedVBind>();
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
shopId: string;
|
||||
}>();
|
||||
|
||||
const id = props.shopId;
|
||||
const shop = core.status.shops[id] as ItemShopEvent;
|
||||
@ -311,7 +315,7 @@ gameKey
|
||||
|
||||
function exit() {
|
||||
if (bought) core.status.route.push('closeShop');
|
||||
props.controller.close(props.num);
|
||||
mainUi.close(props.num);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
|
@ -19,11 +19,13 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import skills from '../data/skill.json';
|
||||
import { has } from '../utils';
|
||||
import Column from '../components/colomn.vue';
|
||||
import { IMountedVBind } from '../interface';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { mainUi } from '../preset/ui';
|
||||
|
||||
const props = defineProps<IMountedVBind>();
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
}>();
|
||||
|
||||
type Skills = keyof typeof skills;
|
||||
|
||||
@ -45,7 +47,7 @@ const content = computed(() => {
|
||||
.map((v, i, a) => {
|
||||
if (/^\d+\./.test(v)) return `${' '.repeat(12)}${v}`;
|
||||
else if (
|
||||
(!isNil(a[i - 1]) &&
|
||||
(has(a[i - 1]) &&
|
||||
v !== '<br>' &&
|
||||
a[i - 1] === '<br>') ||
|
||||
i === 0
|
||||
@ -56,14 +58,14 @@ const content = computed(() => {
|
||||
.join('')
|
||||
.replace(
|
||||
/level:(\d+)/g,
|
||||
'Mota.require("@user/data-state").getSkillLevel($1)'
|
||||
'Mota.Plugin.require("skillTree_g").getSkillLevel($1)'
|
||||
) +
|
||||
'`'
|
||||
);
|
||||
});
|
||||
|
||||
function exit() {
|
||||
props.controller.close(props.num);
|
||||
mainUi.close(props.num);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -81,34 +81,38 @@
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import { splitText } from '../utils';
|
||||
import { isMobile, tip } from '../use';
|
||||
import { has, splitText, tip } from '../utils';
|
||||
import { isMobile } from '../use';
|
||||
import { sleep } from 'mutate-animate';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { IMountedVBind } from '../interface';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { GameUi } from '../controller';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import type { Chapter } from '@/plugin/game/skillTree';
|
||||
|
||||
const props = defineProps<IMountedVBind>();
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
}>();
|
||||
|
||||
const skillTree = Mota.require('@user/legacy-plugin-data');
|
||||
const skillTree = Mota.Plugin.require('skillTree_g');
|
||||
|
||||
let canvas: HTMLCanvasElement;
|
||||
let ctx: CanvasRenderingContext2D;
|
||||
|
||||
const selected = ref(0);
|
||||
const chapter = ref<Chapter>('chapter1');
|
||||
const update = ref(false);
|
||||
|
||||
const chapterDict = {
|
||||
chapter1: '第一章',
|
||||
chapter2: '第二章'
|
||||
};
|
||||
|
||||
const selected = ref(0);
|
||||
const chapter = ref<keyof typeof chapterDict>('chapter1');
|
||||
const update = ref(false);
|
||||
|
||||
flags.skillTree ??= 0;
|
||||
|
||||
const s = skillTree.skills;
|
||||
const s = Mota.Plugin.require('skillTree_g').skills;
|
||||
|
||||
const chapterList = Object.keys(s) as (keyof typeof chapterDict)[];
|
||||
const chapterList = Object.keys(s) as Chapter[];
|
||||
|
||||
selected.value = s[chapterList[flags.skillTree]][0].index;
|
||||
chapter.value = chapterList[flags.skillTree];
|
||||
@ -176,7 +180,7 @@ const level = computed(() => {
|
||||
});
|
||||
|
||||
function exit() {
|
||||
props.controller.close(props.num);
|
||||
mainUi.close(props.num);
|
||||
}
|
||||
|
||||
function resize() {
|
||||
@ -295,7 +299,7 @@ function selectChapter(delta: number) {
|
||||
const now = chapterList.indexOf(chapter.value);
|
||||
const to = now + delta;
|
||||
|
||||
if (!isNil(chapterList[to]) && flags.chapter > to) {
|
||||
if (has(chapterList[to]) && flags.chapter > to) {
|
||||
selected.value = s[chapterList[to]][0].index;
|
||||
chapter.value = chapterList[to];
|
||||
update.value = !update.value;
|
||||
|
@ -64,14 +64,21 @@ import {
|
||||
FullscreenExitOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { sleep } from 'mutate-animate';
|
||||
import { doByInterval, triggerFullscreen } from '../utils';
|
||||
import { doByInterval } from '../utils';
|
||||
import { triggerFullscreen } from '../utils';
|
||||
import { isMobile } from '../use';
|
||||
import { GameUi } from '../controller';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { mainSetting } from '../preset/settingIns';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import { mainSetting } from '../preset/ui';
|
||||
import { mat4 } from 'gl-matrix';
|
||||
import { IMountedVBind } from '../interface';
|
||||
// todo: 改了
|
||||
import { bgmController } from '@/module';
|
||||
|
||||
const props = defineProps<IMountedVBind>();
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
}>();
|
||||
|
||||
const bg = core.material.images.images['bg.webp'];
|
||||
|
||||
@ -83,7 +90,7 @@ let background: HTMLImageElement;
|
||||
|
||||
let buttons: HTMLSpanElement[] = [];
|
||||
|
||||
let played: boolean = false;
|
||||
let played: boolean;
|
||||
const soundChecked = ref(false);
|
||||
const fullscreen = ref(!!document.fullscreenElement);
|
||||
|
||||
@ -153,7 +160,7 @@ async function clickStartButton(id: string) {
|
||||
}
|
||||
if (id === 'replay') core.chooseReplayFile();
|
||||
if (id === 'achievement') {
|
||||
props.controller.open('achievement');
|
||||
mainUi.open('achievement');
|
||||
}
|
||||
}
|
||||
|
||||
@ -320,7 +327,6 @@ onMounted(async () => {
|
||||
resize();
|
||||
|
||||
soundChecked.value = mainSetting.getValue('audio.bgmEnabled', true);
|
||||
const { bgmController } = Mota.require('@user/client-modules');
|
||||
bgmController.play('title.opus');
|
||||
|
||||
start.style.opacity = '1';
|
||||
@ -419,7 +425,8 @@ onUnmounted(() => {
|
||||
);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.5),
|
||||
text-shadow:
|
||||
1px 1px 4px rgba(0, 0, 0, 0.5),
|
||||
-1px -1px 3px rgba(255, 255, 255, 0.3),
|
||||
5px 5px 5px rgba(0, 0, 0, 0.4);
|
||||
filter: brightness(1.8);
|
||||
@ -442,14 +449,17 @@ onUnmounted(() => {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
animation: cursor 2.5s linear 0s infinite normal running;
|
||||
transition: left 0.4s ease-out, top 0.4s ease-out,
|
||||
transition:
|
||||
left 0.4s ease-out,
|
||||
top 0.4s ease-out,
|
||||
opacity 1.5s ease-out;
|
||||
}
|
||||
|
||||
.start-button {
|
||||
position: relative;
|
||||
font: bold 1.5em 'normal';
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.4),
|
||||
text-shadow:
|
||||
1px 1px 2px rgba(0, 0, 0, 0.4),
|
||||
0px 0px 1px rgba(255, 255, 255, 0.3);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
|
@ -85,8 +85,8 @@
|
||||
<span>{{
|
||||
selected === 'none'
|
||||
? '永久道具'
|
||||
: getClsName(all[selected].cls as ItemMode) ??
|
||||
'永久道具'
|
||||
: (getClsName(all[selected].cls as ItemMode) ??
|
||||
'永久道具')
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -113,14 +113,17 @@ import Scroll from '../components/scroll.vue';
|
||||
import BoxAnimate from '../components/boxAnimate.vue';
|
||||
import { getClsName, getItems } from '../tools/toolbox';
|
||||
import { isMobile } from '../use';
|
||||
import { type } from '../utils';
|
||||
import { type, has } from '../utils';
|
||||
import { hyper } from 'mutate-animate';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { GameUi } from '../controller';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { IMountedVBind } from '../interface';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { mainUi } from '../preset/ui';
|
||||
|
||||
const props = defineProps<IMountedVBind>();
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
}>();
|
||||
|
||||
type ItemMode = 'tools' | 'constants';
|
||||
type ShowItemIds = ItemIdOf<'constants' | 'tools'> | 'none';
|
||||
@ -143,7 +146,7 @@ watch(index, n => {
|
||||
});
|
||||
|
||||
watch(mode, n => {
|
||||
if (isNil(items[n][index.value])) {
|
||||
if (!has(items[n][index.value])) {
|
||||
selected.value = 'none';
|
||||
return;
|
||||
}
|
||||
@ -169,17 +172,17 @@ async function select(id: ShowItemIds, nouse: boolean = false) {
|
||||
}
|
||||
|
||||
function exit() {
|
||||
props.controller.close(props.num);
|
||||
mainUi.close(props.num);
|
||||
}
|
||||
|
||||
function use(id: ShowItemIds) {
|
||||
if (id === 'none') return;
|
||||
if (core.canUseItem(id)) {
|
||||
const hold = props.controller.holdOn();
|
||||
const hold = mainUi.holdOn();
|
||||
exit();
|
||||
nextTick(() => {
|
||||
core.tryUseItem(id, false, () => {
|
||||
if (props.controller.stack.length === 0) {
|
||||
if (mainUi.stack.length === 0) {
|
||||
hold.end(core.status.event.id !== 'toolbox');
|
||||
}
|
||||
});
|
||||
@ -193,10 +196,10 @@ function use(id: ShowItemIds) {
|
||||
}
|
||||
|
||||
async function toEquip() {
|
||||
props.controller.holdOn();
|
||||
mainUi.holdOn();
|
||||
exit();
|
||||
nextTick(() => {
|
||||
props.controller.open('equipbox');
|
||||
mainUi.open('equipbox');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,47 +0,0 @@
|
||||
import { KeyCode } from '@motajs/client-base';
|
||||
import { KeyboardEmits, Keyboard, isAssist } from '@motajs/system-action';
|
||||
import { mainUi, fixedUi } from './preset/uiIns';
|
||||
|
||||
/**
|
||||
* 唤起虚拟键盘,并获取到一次按键操作
|
||||
* @param emitAssist 是否可以获取辅助按键,为true时,如果按下辅助按键,那么会立刻返回该按键,
|
||||
* 否则会视为开关辅助按键
|
||||
* @param assist 初始化的辅助按键
|
||||
*/
|
||||
export function getVitualKeyOnce(
|
||||
emitAssist: boolean = false,
|
||||
assist: number = 0,
|
||||
emittable: KeyCode[] = []
|
||||
): Promise<KeyboardEmits> {
|
||||
// todo: 正确触发后删除监听器
|
||||
return new Promise(res => {
|
||||
const key = Keyboard.get('full')!;
|
||||
key.withAssist(assist);
|
||||
const id = mainUi.open('virtualKey', { keyboard: key });
|
||||
key.on('emit', (item, assist, _index, ev) => {
|
||||
ev.preventDefault();
|
||||
if (emitAssist) {
|
||||
if (emittable.length === 0 || emittable.includes(item.key)) {
|
||||
res({ key: item.key, assist: 0 });
|
||||
key.disposeScope();
|
||||
mainUi.close(id);
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
!isAssist(item.key) &&
|
||||
(emittable.length === 0 || emittable.includes(item.key))
|
||||
) {
|
||||
res({ key: item.key, assist });
|
||||
key.disposeScope();
|
||||
mainUi.close(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function openDanmakuPoster() {
|
||||
if (!fixedUi.hasName('danmakuEditor')) {
|
||||
fixedUi.open('danmakuEditor');
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user