Compare commits

..

No commits in common. "05e254fee05f3aaaa670f59ea68a8c75ade95998" and "cca10a429c2c262b5bf2bf3612d53d412b01714f" have entirely different histories.

228 changed files with 3697 additions and 3152 deletions

3
.gitignore vendored
View File

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

View File

@ -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: '中文'
}
}
});

View File

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

View File

View File

@ -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)

View File

@ -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` 文件夹中是如何做的。

View File

View File

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

View File

View File

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

View File

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

View File

@ -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';

View File

@ -7,9 +7,6 @@ interface GameLoadEvent {
autotileLoaded: []; autotileLoaded: [];
coreInit: []; coreInit: [];
loaded: []; loaded: [];
registered: [];
dataRegistered: [];
clientRegistered: [];
} }
class GameLoading extends EventEmitter<GameLoadEvent> { class GameLoading extends EventEmitter<GameLoadEvent> {
@ -61,25 +58,6 @@ class GameLoading extends EventEmitter<GameLoadEvent> {
} }
export const loading = new GameLoading(); 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 { export interface GameEvent {
/** Emitted in libs/events.js resetGame. */ /** Emitted in libs/events.js resetGame. */
@ -129,8 +107,6 @@ export interface GameEvent {
]; ];
/** Emitted in lib/control.js */ /** Emitted in lib/control.js */
replayStatus: [replaying: boolean]; replayStatus: [replaying: boolean];
/** Emitted in project/functions.js */
loadData: [];
} }
export const hook = new EventEmitter<GameEvent>(); export const hook = new EventEmitter<GameEvent>();

View File

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

View File

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

View File

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

View File

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

View File

@ -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 { loading } from '@user/data-base';
import type { LayerDoorAnimate } from '@motajs/render'; 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 { export namespace BluePalace {
type DoorConvertInfo = [id: AllIds, x: number, y: number]; type DoorConvertInfo = [id: AllIds, x: number, y: number];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
}

View File

@ -1,15 +1,5 @@
import { createMota } from './mota'; 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(); createMota();
patchAll();
create();
loading.once('coreInit', () => {
Patch.patchAll();
});
export * from './mota'; export * from './mota';

View File

@ -13,16 +13,6 @@ import type * as RenderVue from '@motajs/render-vue';
import type * as System from '@motajs/system'; import type * as System from '@motajs/system';
import type * as SystemAction from '@motajs/system-action'; import type * as SystemAction from '@motajs/system-action';
import type * as SystemUI from '@motajs/system-ui'; 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 { interface ModuleInterface {
'@motajs/client': typeof Client; '@motajs/client': typeof Client;
@ -40,16 +30,6 @@ interface ModuleInterface {
'@motajs/system': typeof System; '@motajs/system': typeof System;
'@motajs/system-action': typeof SystemAction; '@motajs/system-action': typeof SystemAction;
'@motajs/system-ui': typeof SystemUI; '@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 { export interface IMota {
@ -93,6 +73,10 @@ class MotaSystem implements IMota {
r = r; r = r;
rf = rf; rf = rf;
constructor() {
throw new Error(`System interface class cannot be constructed.`);
}
require(key: string): any { require(key: string): any {
const data = this.modules[key]; const data = this.modules[key];
if (data) return data; if (data) return data;
@ -153,7 +137,7 @@ declare global {
} }
} }
export const Mota: IMota = new MotaSystem(); export const Mota = new MotaSystem();
export function createMota() { export function createMota() {
window.Mota = Mota; window.Mota = Mota;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
}
});
});
}

View File

@ -1,5 +0,0 @@
import { init as initItemDetail } from './itemDetail';
initItemDetail();
export * from './itemDetail';

View File

@ -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获得的道具IDx和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();
});
}

View File

@ -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';

View File

@ -2,4 +2,3 @@ export * from './patch';
export * from './disposable'; export * from './disposable';
export * from './eventEmitter'; export * from './eventEmitter';
export * from './resource'; export * from './resource';
export * from './utils';

View File

@ -529,7 +529,7 @@ export function loadDefaultResource() {
const res = LoadTask.add('byte', `byte/project/sounds/${v}`); const res = LoadTask.add('byte', `byte/project/sounds/${v}`);
Mota.r(() => { Mota.r(() => {
res.once('load', res => { res.once('load', res => {
const { soundPlayer } = Mota.require('@user/client-modules'); const { soundPlayer } = Mota.require('module', 'Audio');
soundPlayer.add(v, res.resource!); soundPlayer.add(v, res.resource!);
}); });
}); });
@ -548,7 +548,7 @@ export function loadDefaultResource() {
const res = LoadTask.add('image', `image/project/autotiles/${v}.png`); const res = LoadTask.add('image', `image/project/autotiles/${v}.png`);
res.once('load', res => { res.once('load', res => {
autotiles[v as AllIdsOf<'autotile'>] = res.resource; autotiles[v as AllIdsOf<'autotile'>] = res.resource;
const { loading } = Mota.require('@user/data-base'); const loading = Mota.require('var', 'loading');
loading.addAutotileLoaded(); loading.addAutotileLoaded();
loading.onAutotileLoaded(autotiles); loading.onAutotileLoaded(autotiles);
core.material.images.autotile[v as AllIdsOf<'autotile'>] = core.material.images.autotile[v as AllIdsOf<'autotile'>] =
@ -675,7 +675,7 @@ export async function loadCompressedResource() {
autotiles[ autotiles[
name.slice(0, -4) as AllIdsOf<'autotile'> name.slice(0, -4) as AllIdsOf<'autotile'>
] = image; ] = image;
const { loading } = Mota.require('@user/data-base'); const loading = Mota.require('var', 'loading');
loading.addAutotileLoaded(); loading.addAutotileLoaded();
loading.onAutotileLoaded(autotiles); loading.onAutotileLoaded(autotiles);
core.material.images.autotile[ core.material.images.autotile[
@ -717,10 +717,8 @@ export async function loadCompressedResource() {
new FontFace(name.slice(0, -4), font) new FontFace(name.slice(0, -4), font)
); );
} else if (usage === 'sound') { } else if (usage === 'sound') {
const { soundPlayer } = Mota.require( const { soundPlayer } = Mota.require('module', 'Audio');
'@user/client-modules' soundPlayer.add(v, value);
);
soundPlayer.add(name as SoundIds, value as Uint8Array);
} else if (usage === 'animate') { } else if (usage === 'animate') {
const ani = value as string; const ani = value as string;
core.material.animates[ core.material.animates[

View File

@ -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];
}

View File

@ -1,5 +1,5 @@
import { Keyboard } from '@motajs/system-action'; import { Keyboard } from '@motajs/system-action';
import KeyboardUI from './keyboard.vue'; import KeyboardUI from '../panel/keyboard.vue';
interface VirtualKeyProps { interface VirtualKeyProps {
keyboard: Keyboard; keyboard: Keyboard;

View File

@ -49,13 +49,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUnmounted, onUpdated, ref, watch } from 'vue'; import { onMounted, onUnmounted, onUpdated, ref, watch } from 'vue';
import { ArrowsAltOutlined, DragOutlined } from '@ant-design/icons-vue'; import { ArrowsAltOutlined, DragOutlined } from '@ant-design/icons-vue';
import { import { isMobile, useDrag, cancelGlobalDrag } from '../use';
isMobile, import { has, requireUniqueSymbol } from '../utils';
useDrag,
cancelGlobalDrag,
requireUniqueSymbol
} from '../use';
import { isNil } from 'lodash-es';
// todo: // todo:
@ -205,10 +200,10 @@ function resize() {
if (!main) return; if (!main) return;
if (!isNil(props.width)) width.value = props.width; if (has(props.width)) width.value = props.width;
if (!isNil(props.height)) height.value = props.height; if (has(props.height)) height.value = props.height;
if (!isNil(props.left)) left.value = props.left; if (has(props.left)) left.value = props.left;
if (!isNil(props.top)) top.value = props.top; if (has(props.top)) top.value = props.top;
const beforeWidth = width.value; const beforeWidth = width.value;
const beforeHeight = height.value; const beforeHeight = height.value;

View File

@ -9,8 +9,7 @@
<script lang="tsx" setup> <script lang="tsx" setup>
import { onMounted, onUnmounted, onUpdated } from 'vue'; import { onMounted, onUnmounted, onUpdated } from 'vue';
import { addAnimate, removeAnimate } from '../animateController'; import { addAnimate, removeAnimate } from '../animateController';
import { requireUniqueSymbol } from '../use'; import { has, requireUniqueSymbol } from '../utils';
import { isNil } from 'lodash-es';
const id = requireUniqueSymbol().toFixed(0); const id = requireUniqueSymbol().toFixed(0);
@ -28,7 +27,7 @@ let ctx: CanvasRenderingContext2D;
let drawFn: () => void; let drawFn: () => void;
function draw() { function draw() {
if (!isNil(drawFn)) removeAnimate(drawFn); if (has(drawFn)) removeAnimate(drawFn);
const cls = core.getClsFromId(props.id as AllIds); const cls = core.getClsFromId(props.id as AllIds);
const frames = core.getAnimateFrames(cls); const frames = core.getAnimateFrames(cls);

View File

@ -26,8 +26,8 @@
import { onMounted, onUpdated } from 'vue'; import { onMounted, onUpdated } from 'vue';
import { LeftOutlined } from '@ant-design/icons-vue'; import { LeftOutlined } from '@ant-design/icons-vue';
import Scroll from './scroll.vue'; import Scroll from './scroll.vue';
import { isMobile, requireUniqueSymbol } from '../use'; import { isMobile } from '../use';
import { isNil } from 'lodash-es'; import { has, requireUniqueSymbol } from '../utils';
const emits = defineEmits<{ const emits = defineEmits<{
(e: 'close'): void; (e: 'close'): void;
@ -51,10 +51,10 @@ function resize() {
left = document.getElementById(`column-left-${id}`) as HTMLDivElement; left = document.getElementById(`column-left-${id}`) as HTMLDivElement;
right = document.getElementById(`column-right-${id}`) as HTMLDivElement; right = document.getElementById(`column-right-${id}`) as HTMLDivElement;
if (!isNil(props.width) && !isMobile) main.style.width = `${props.width}%`; if (has(props.width) && !isMobile) main.style.width = `${props.width}%`;
if (!isNil(props.height)) main.style.height = `${props.height}%`; if (has(props.height)) main.style.height = `${props.height}%`;
if (!isNil(props.left)) left.style.flexBasis = `${props.left}%`; if (has(props.left)) left.style.flexBasis = `${props.left}%`;
if (!isNil(props.right)) right.style.flexBasis = `${props.right}%`; if (has(props.right)) right.style.flexBasis = `${props.right}%`;
} }
onMounted(async () => { onMounted(async () => {

View File

@ -16,7 +16,7 @@
></BoxAnimate> ></BoxAnimate>
<div <div
class="special-text" 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"> <template v-for="(text, i) in enemy.showSpecial">
<span v-if="i < (isMobile ? 1 : 2)" <span v-if="i < (isMobile ? 1 : 2)"
@ -110,10 +110,10 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { has } from '../utils';
import BoxAnimate from '../components/boxAnimate.vue'; import BoxAnimate from '../components/boxAnimate.vue';
import { isMobile } from '../use'; import { isMobile } from '../use';
import { ToShowEnemy } from '../tools/book'; import { ToShowEnemy } from '../tools/book';
import { isNil } from 'lodash-es';
const props = defineProps<{ const props = defineProps<{
enemy: ToShowEnemy; enemy: ToShowEnemy;

View File

@ -4,10 +4,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUnmounted } from 'vue'; import { onMounted, onUnmounted } from 'vue';
import { requireUniqueSymbol } from '../utils';
import { MinimapDrawer, getArea } from '../tools/fly'; import { MinimapDrawer, getArea } from '../tools/fly';
import { useDrag, useWheel, requireUniqueSymbol } from '../use'; import { useDrag, useWheel } from '../use';
import { debounce } from 'lodash-es'; import { debounce } from 'lodash-es';
import { mainSetting } from '../preset/settingIns'; import { mainSetting } from '../preset/ui';
const props = defineProps<{ const props = defineProps<{
action?: boolean; action?: boolean;
@ -133,7 +134,7 @@ onMounted(() => {
} }
drawer.drawMap(); drawer.drawMap();
const { hook } = Mota.require('@user/data-base'); const hook = Mota.require('var', 'hook');
hook.on('afterChangeFloor', onChange); hook.on('afterChangeFloor', onChange);
hook.on('afterBattle', afterBattle); hook.on('afterBattle', afterBattle);
@ -167,7 +168,7 @@ onMounted(() => {
}); });
onUnmounted(() => { onUnmounted(() => {
const { hook } = Mota.require('@user/data-base'); const hook = Mota.require('var', 'hook');
hook.off('afterChangeFloor', onChange); hook.off('afterChangeFloor', onChange);
hook.off('afterBattle', afterBattle); hook.off('afterBattle', afterBattle);
}); });

View File

@ -12,12 +12,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import { sleep } from 'mutate-animate'; import { sleep } from 'mutate-animate';
import { onMounted, onUnmounted, onUpdated } from 'vue'; import { onMounted, onUnmounted, onUpdated } from 'vue';
import { import { cancelGlobalDrag, useDrag, useWheel } from '../use';
cancelGlobalDrag, import { requireUniqueSymbol } from '../utils';
useDrag,
useWheel,
requireUniqueSymbol
} from '../use';
let main: HTMLDivElement; let main: HTMLDivElement;

View File

@ -1,6 +1,5 @@
import { Component, shallowReactive } from 'vue'; import { Component, shallowReactive } from 'vue';
import { EventEmitter } from '@motajs/legacy-common'; import { EventEmitter } from '@motajs/legacy-common';
import { IGameUi, IUiController } from './interface';
interface FocusEvent<T> { interface FocusEvent<T> {
focus: (before: T | null, after: T) => void; focus: (before: T | null, after: T) => void;
@ -129,7 +128,7 @@ interface MountedVBind {
[x: string]: any; [x: string]: any;
} }
export class GameUi extends EventEmitter<GameUiEvent> implements IGameUi { export class GameUi extends EventEmitter<GameUiEvent> {
static uiList: GameUi[] = []; static uiList: GameUi[] = [];
component: Component; component: Component;
@ -162,10 +161,7 @@ interface HoldOnController {
end(noClosePanel?: boolean): void; end(noClosePanel?: boolean): void;
} }
export class UiController export class UiController extends Focus<IndexedGameUi> {
extends Focus<IndexedGameUi>
implements IUiController
{
static list: UiController[] = []; static list: UiController[] = [];
list: Record<string, GameUi> = {}; list: Record<string, GameUi> = {};
num: number = 0; num: number = 0;

View File

@ -1,11 +1,9 @@
import { EventEmitter } from 'eventemitter3'; import { EventEmitter } from 'eventemitter3';
import { logger } from '@motajs/common'; import { logger } from '@motajs/common';
import { deleteWith, ensureArray, parseCss, tip } from '@motajs/legacy-ui';
import { ResponseBase } from '@motajs/client-base'; import { ResponseBase } from '@motajs/client-base';
import axios, { AxiosResponse, toFormData } from 'axios'; import axios, { AxiosResponse, toFormData } from 'axios';
import { VNode, h, shallowReactive } from 'vue'; 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'; // /* @__PURE__ */ import { id, password } from '../../../../user';
type CSSObj = Partial<Record<CanParseCss, string>>; type CSSObj = Partial<Record<CanParseCss, string>>;

View File

@ -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": { "special1": {
"text": "第一章怪物特技", "text": "第一章怪物特技",
"condition": "flags.chapter > 0", "condition": "flags.chapter > 0",

View File

@ -26,7 +26,11 @@
"<br>", "<br>",
"注当鼠标移动到怪物上时经过200毫秒才会显示信息防止误操作。" "注当鼠标移动到怪物上时经过200毫秒才会显示信息防止误操作。"
], ],
"hotkey": ["设置游戏中会用到的一些快捷键"] "hotkey": ["设置游戏中会用到的一些快捷键"],
"toolbar": [
"允许你在工具栏上自定义按钮,包括使用道具、开关技能、按下某个按键等。",
"推荐手机进行一些设置"
]
}, },
"utils": { "utils": {
"betterLoad": [ "betterLoad": [

View File

@ -1,8 +0,0 @@
import { createShadow } from './shadow';
export function createFx() {
createShadow();
}
export * from './shadow';
export * from './webgl';

View File

@ -75,10 +75,9 @@ function addLightFromBlock(
}); });
} }
export function createShadow() { const hook = Mota.require('var', 'hook');
const { hook } = Mota.require('@user/data-base');
hook.once('reset', () => { hook.once('reset', () => {
Shadow.init(); Shadow.init();
addLightFromBlock( addLightFromBlock(
core.floorIds core.floorIds
@ -101,26 +100,27 @@ export function createShadow() {
}, },
{ background: [0, 0, 0, 0.3] } { background: [0, 0, 0, 0.3] }
); );
hook.on('loadData', () => { Mota.rewrite(core.control, 'loadData', 'add', () => {
if (!main.replayChecking) {
Shadow.update(true); Shadow.update(true);
LayerShadowExtends.shadowList.forEach(v => v.update()); LayerShadowExtends.shadowList.forEach(v => v.update());
}
}); });
}); });
hook.on('reset', () => { hook.on('reset', () => {
Shadow.update(true); Shadow.update(true);
LayerShadowExtends.shadowList.forEach(v => v.update()); LayerShadowExtends.shadowList.forEach(v => v.update());
}); });
hook.on('setBlock', () => { hook.on('setBlock', () => {
Shadow.update(true); Shadow.update(true);
LayerShadowExtends.shadowList.forEach(v => v.update()); LayerShadowExtends.shadowList.forEach(v => v.update());
}); });
hook.on('changingFloor', floorId => { hook.on('changingFloor', floorId => {
Shadow.clearBuffer(); Shadow.clearBuffer();
Shadow.update(true); Shadow.update(true);
// setCanvasFilterByFloorId(floorId); // setCanvasFilterByFloorId(floorId);
LayerShadowExtends.shadowList.forEach(v => v.update()); LayerShadowExtends.shadowList.forEach(v => v.update());
}); });
}
// 深度测试着色器 // 深度测试着色器

View File

@ -1,7 +1,6 @@
import { ensureArray } from '../utils'; import { ensureArray, tip } from '@motajs/legacy-ui';
import { sleep } from 'mutate-animate'; import { sleep } from 'mutate-animate';
import { logger } from '@motajs/common'; import { logger } from '@motajs/common';
import { tip } from '../use';
const { gl, gl2 } = checkSupport(); const { gl, gl2 } = checkSupport();

View File

@ -1,22 +1,12 @@
import { createFx } from './fx';
import { createPreset } from './preset';
export function create() {
createFx();
createPreset();
}
export * as UI from './ui'; export * as UI from './ui';
export * as Components from './components'; export * as Components from './components';
export * from './preset'; export * from './preset';
export * from './tools'; export * from './tools';
export * from './fx';
export * from './animateController'; export * from './animateController';
export * from './controller'; export * from './controller';
export * from './danmaku'; export * from './danmaku';
// export * from './mark'; export * from './mark';
export * from './setting'; export * from './setting';
export * from './use'; export * from './use';
export * from './utils'; export * from './utils';
export * from './uiUtils';

View File

@ -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;
/**
* uiui都会同时关闭掉
* @param num ui的唯一标识符
*/
close(num: number): void;
/**
* id关闭所有同id的uiui后的所有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;
}

View 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/31/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();
});

View File

@ -67,12 +67,12 @@
> >
<span class="changable" :change="nowDamageChangable" <span class="changable" :change="nowDamageChangable"
><span style="font-family: 'FiraCode'">{{ ><span style="font-family: 'FiraCode'">{{
(nowDamage[0] as number) < 0 && isNil(enemy.damage) (nowDamage[0] as number) < 0 && !has(enemy.damage)
? '=>' ? '=>'
: '' : ''
}}</span }}</span
>{{ >{{
(nowDamage[0] as number) < 0 && isNil(enemy.damage) (nowDamage[0] as number) < 0 && !has(enemy.damage)
? format(-nowDamage[0]) ? format(-nowDamage[0])
: format(nowDamage[0]) : format(nowDamage[0])
}}</span }}</span
@ -96,8 +96,8 @@
import { computed, onMounted, ref, watch } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
import { detailInfo, getCriticalDamage, getDefDamage } from '../tools/book'; import { detailInfo, getCriticalDamage, getDefDamage } from '../tools/book';
import Chart, { ChartConfiguration } from 'chart.js/auto'; import Chart, { ChartConfiguration } from 'chart.js/auto';
import { setCanvasSize } from '../utils'; import { has, setCanvasSize } from '../utils';
import { debounce, isNil } from 'lodash-es'; import { debounce } from 'lodash-es';
import { isMobile } from '../use'; import { isMobile } from '../use';
import { createChangable } from '../tools/common'; import { createChangable } from '../tools/common';
@ -113,10 +113,10 @@ const ceil = Math.ceil;
const x = ref<number>(); const x = ref<number>();
const y = ref<number>(); const y = ref<number>();
x.value = !isNil(x.value) x.value = has(x.value)
? Math.round(x.value + core.bigmap.offsetX / 32) ? Math.round(x.value + core.bigmap.offsetX / 32)
: void 0; : void 0;
y.value = !isNil(y.value) y.value = has(y.value)
? Math.round(y.value + core.bigmap.offsetY / 32) ? Math.round(y.value + core.bigmap.offsetY / 32)
: void 0; : void 0;

View File

@ -0,0 +1,85 @@
<template>
<div id="enemy-target">
<div id="enemy-desc">
<span>怪物描述</span>
<Scroll id="enemy-desc-scroll">
<span
>&nbsp;&nbsp;&nbsp;&nbsp;{{
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>

View File

@ -1,6 +1,6 @@
import { Danmaku } from '../danmaku'; import { Danmaku } from '../danmaku';
import { Component, h } from 'vue'; import { Component, h } from 'vue';
import { mainSetting } from './settingIns'; import { mainSetting } from './ui';
import { getIconHeight } from '../utils'; import { getIconHeight } from '../utils';
import { BoxAnimate } from '../components'; import { BoxAnimate } from '../components';
@ -21,15 +21,12 @@ if (import.meta.env.DEV) {
Danmaku.backend = `/danmaku`; Danmaku.backend = `/danmaku`;
} }
export function createDanmaku() { Mota.require('var', 'hook').once('reset', () => {
const { hook } = Mota.require('@user/data-base');
hook.once('reset', () => {
Danmaku.fetch(); Danmaku.fetch();
}); });
// 勇士移动后显示弹幕 // 勇士移动后显示弹幕
hook.on('moveOneStep', (x, y, floor) => { Mota.require('var', 'hook').on('moveOneStep', (x, y, floor) => {
const enabled = mainSetting.getValue('ui.danmaku', true); const enabled = mainSetting.getValue('ui.danmaku', true);
if (!enabled) return; if (!enabled) return;
const f = Danmaku.allInPos[floor]; const f = Danmaku.allInPos[floor];
@ -43,5 +40,4 @@ export function createDanmaku() {
}); });
} }
} }
}); });
}

View File

@ -1,4 +1,5 @@
import { debounce } from 'lodash-es'; import { debounce } from 'lodash-es';
import { fixedUi, mainUi } from './ui';
import { ref } from 'vue'; import { ref } from 'vue';
import { sleep } from 'mutate-animate'; import { sleep } from 'mutate-animate';
@ -15,7 +16,6 @@ const showFixed = debounce((block: Block) => {
if (!e) return; if (!e) return;
const enemy = core.status.thisMap.enemy.get(block.x, block.y); const enemy = core.status.thisMap.enemy.get(block.x, block.y);
if (!enemy) return; if (!enemy) return;
const { fixedUi } = Mota.require('@motajs/legacy-ui');
fixedUi.open( fixedUi.open(
'fixed', 'fixed',
{ enemy, close, loc: [cx, cy], hovered }, { enemy, close, loc: [cx, cy], hovered },
@ -29,7 +29,6 @@ const showFixed = debounce((block: Block) => {
const closeFixed = () => { const closeFixed = () => {
close.value = true; close.value = true;
sleep(200).then(() => { sleep(200).then(() => {
const { fixedUi } = Mota.require('@motajs/legacy-ui');
fixedUi.closeByName('fixed'); fixedUi.closeByName('fixed');
close.value = false; close.value = false;
}); });
@ -38,32 +37,28 @@ const closeFixed = () => {
// todo: 应当在这里实现查看临界与特殊属性的功能 // todo: 应当在这里实现查看临界与特殊属性的功能
export let hovered: Block | null; export let hovered: Block | null;
export function createFixed() { const { hook, gameListener } = Mota.requireAll('var');
const { hook, gameListener } = Mota.require('@user/data-base'); gameListener.on('hoverBlock', block => {
gameListener.on('hoverBlock', block => {
closeFixed(); closeFixed();
hovered = block; hovered = block;
}); });
gameListener.on('leaveBlock', (_, __, leaveGame) => { gameListener.on('leaveBlock', (_, __, leaveGame) => {
showFixed.cancel(); showFixed.cancel();
if (!leaveGame) closeFixed(); if (!leaveGame) closeFixed();
hovered = null; hovered = null;
}); });
gameListener.on('mouseMove', e => { gameListener.on('mouseMove', e => {
cx = e.clientX; cx = e.clientX;
cy = e.clientY; cy = e.clientY;
showFixed.cancel(); showFixed.cancel();
if (hovered) { if (hovered) {
showFixed(hovered); showFixed(hovered);
} }
}); });
hook.once('mounted', () => { hook.once('mounted', () => {
const { mainUi } = Mota.require('@motajs/legacy-ui');
mainUi.on('start', () => { mainUi.on('start', () => {
showFixed.cancel(); showFixed.cancel();
closeFixed(); closeFixed();
}); });
}); });
}

View File

@ -1,11 +1,15 @@
import { KeyCode } from '@motajs/client-base'; import { KeyCode } from '@motajs/client-base';
import { gameKey, HotkeyJSON } from '@motajs/system-action'; import { gameKey, HotkeyJSON } from '@motajs/system-action';
import {
openDanmakuPoster,
tip,
hasMarkedEnemy,
markEnemy,
unmarkEnemy
} from '@motajs/legacy-ui';
import { hovered } from './fixed'; import { hovered } from './fixed';
import { mainUi } from './uiIns'; import { mainUi } from './ui';
import { GameStorage } from '@motajs/legacy-system'; 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'); export const mainScope = Symbol.for('@key_main');
@ -502,9 +506,9 @@ gameKey
.realize('mark', () => { .realize('mark', () => {
const cls = hovered?.event.cls; const cls = hovered?.event.cls;
if (cls === 'enemys' || cls === 'enemy48') { if (cls === 'enemys' || cls === 'enemy48') {
// const id = hovered!.event.id as EnemyIds; const id = hovered!.event.id as EnemyIds;
// if (hasMarkedEnemy(id)) unmarkEnemy(id); if (hasMarkedEnemy(id)) unmarkEnemy(id);
// else markEnemy(id); else markEnemy(id);
} }
}) })
.realize('special', () => { .realize('special', () => {
@ -531,7 +535,7 @@ gameKey
core.actions._clickGameInfo_openComments(); core.actions._clickGameInfo_openComments();
}) })
.realize('skill1', () => { .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.learnedSkill(HeroSkill.Blade)) return;
if (HeroSkill.getAutoSkill()) { if (HeroSkill.getAutoSkill()) {
tip('error', '已开启自动切换技能!'); tip('error', '已开启自动切换技能!');
@ -543,13 +547,13 @@ gameKey
core.updateStatusBar(); core.updateStatusBar();
}) })
.realize('skill2', () => { .realize('skill2', () => {
const HeroSkill = Mota.require('@user/data-state').HeroSkill; const HeroSkill = Mota.require('module', 'Mechanism').HeroSkill;
if ( if (
!flags.onChase && !flags.onChase &&
!core.status.floorId.startsWith('tower') && !core.status.floorId.startsWith('tower') &&
HeroSkill.learnedSkill(HeroSkill.Jump) HeroSkill.learnedSkill(HeroSkill.Jump)
) { ) {
Mota.require('@user/legacy-plugin-data').jumpSkill(); Mota.Plugin.require('skill_g').jumpSkill();
core.status.route.push('useSkill:Jump'); core.status.route.push('useSkill:Jump');
} else { } else {
if (core.hasItem('pickaxe')) { if (core.hasItem('pickaxe')) {
@ -558,7 +562,7 @@ gameKey
} }
}) })
.realize('skill3', () => { .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.learnedSkill(HeroSkill.Shield)) return;
if (HeroSkill.getAutoSkill()) { if (HeroSkill.getAutoSkill()) {
tip('error', '已开启自动切换技能!'); tip('error', '已开启自动切换技能!');

View File

@ -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 './ui';
export * from './settings'; export * from './settings';
export * from './danmaku'; export * from './danmaku';
export * from './fixed'; export * from './fixed';
export * from './hotkey'; export * from './hotkey';
export * from './keyboard'; export * from './keyboard';
export * from './uiIns';
export * from './settingIns';

View File

@ -1,3 +0,0 @@
import { MotaSetting } from '../setting';
export const mainSetting = new MotaSetting();

View File

@ -1,6 +1,6 @@
import type { SettingComponent, SettingComponentProps } from '../setting'; import type { SettingComponent, SettingComponentProps } from '../setting';
import { Button, InputNumber, Radio } from 'ant-design-vue'; import { Button, InputNumber, Radio } from 'ant-design-vue';
import { mainUi } from './uiIns'; import { mainUi } from './ui';
import { gameKey } from '@motajs/system-action'; import { gameKey } from '@motajs/system-action';
interface Components { interface Components {

View File

@ -1,17 +1,54 @@
import { GameStorage } from '@motajs/legacy-system'; import { GameStorage, VirtualKey } from '@motajs/legacy-system';
import { createSettingComponents } from './settings'; import {
import { isMobile } from '../use'; createSettingComponents,
import { MotaSetting } from '../setting'; GameUi,
import { triggerFullscreen } from '../utils'; isMobile,
MotaSetting,
triggerFullscreen,
UI,
UiController
} from '@motajs/legacy-ui';
import { bgmController, soundPlayer } from '@/module';
import settingsText from '../data/settings.json'; import settingsText from '../data/settings.json';
import { fixedUi, mainUi } from './uiIns';
import { mainSetting } from './settingIns';
//#region legacy-ui //#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() { export const fixedUi = new UiController(true);
const { hook } = Mota.require('@user/data-base'); fixedUi.register(
hook.once('mounted', () => { 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 ui = document.getElementById('ui-main')!;
const fixed = document.getElementById('ui-fixed')!; const fixed = document.getElementById('ui-fixed')!;
@ -40,8 +77,7 @@ export function createUI() {
fixedUi.on('end', () => { fixedUi.on('end', () => {
fixed.style.display = 'none'; fixed.style.display = 'none';
}); });
}); });
}
//#endregion //#endregion
@ -49,6 +85,7 @@ export function createUI() {
const COM = createSettingComponents(); const COM = createSettingComponents();
export const mainSetting = new MotaSetting();
// 添加不参与全局存储的设置 // 添加不参与全局存储的设置
MotaSetting.noStorage.push('action.autoSkill', 'screen.fullscreen'); MotaSetting.noStorage.push('action.autoSkill', 'screen.fullscreen');
@ -106,7 +143,7 @@ function handleActionSetting<T extends number | boolean>(
) { ) {
if (key === 'autoSkill') { if (key === 'autoSkill') {
// 自动切换技能 // 自动切换技能
const HeroSkill = Mota.require('@user/data-state').HeroSkill; const HeroSkill = Mota.require('module', 'Mechanism').HeroSkill;
HeroSkill.setAutoSkill(n as boolean); HeroSkill.setAutoSkill(n as boolean);
core.status.route.push(`set:autoSkill:${n}`); core.status.route.push(`set:autoSkill:${n}`);
} }
@ -117,7 +154,6 @@ function handleAudioSetting<T extends number | boolean>(
n: T, n: T,
_o: T _o: T
) { ) {
const { bgmController, soundPlayer } = Mota.require('@user/client-modules');
if (key === 'bgmEnabled') { if (key === 'bgmEnabled') {
bgmController.setEnabled(n as boolean); bgmController.setEnabled(n as boolean);
core.checkBgm(); core.checkBgm();
@ -147,6 +183,7 @@ function handleUiSetting<T extends number | boolean>(key: string, n: T, _o: T) {
} }
// ----- 游戏的所有设置项 // ----- 游戏的所有设置项
// todo: 虚拟键盘缩放,小地图楼传缩放
mainSetting mainSetting
.register( .register(
'screen', 'screen',
@ -224,7 +261,7 @@ mainSetting
.register('tips', '小贴士', true, COM.Boolean) .register('tips', '小贴士', true, COM.Boolean)
); );
const { loading } = Mota.require('@user/data-base'); const loading = Mota.require('var', 'loading');
loading.once('coreInit', () => { loading.once('coreInit', () => {
mainSetting.reset({ mainSetting.reset({
'screen.fullscreen': !!document.fullscreenElement, 'screen.fullscreen': !!document.fullscreenElement,
@ -254,6 +291,10 @@ loading.once('coreInit', () => {
isMobile ? 300 : Math.floor(window.innerWidth / 600) * 50 isMobile ? 300 : Math.floor(window.innerWidth / 600) * 50
), ),
'ui.mapLazy': storage.getValue('ui.mapLazy', false), '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.bookScale': storage.getValue('ui.bookScale', isMobile ? 100 : 80),
'ui.danmaku': storage.getValue('ui.danmaku', true), 'ui.danmaku': storage.getValue('ui.danmaku', true),
'ui.danmakuSpeed': storage.getValue( 'ui.danmakuSpeed': storage.getValue(
@ -268,6 +309,18 @@ interface SettingTextData {
[x: string]: string[] | 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 mainSetting
.setDescription('audio.bgmEnabled', `是否开启背景音乐`) .setDescription('audio.bgmEnabled', `是否开启背景音乐`)
.setDescription('audio.bgmVolume', `背景音乐的音量`) .setDescription('audio.bgmVolume', `背景音乐的音量`)
@ -278,6 +331,7 @@ mainSetting
'ui.mapLazy', 'ui.mapLazy',
`是否启用小地图懒更新模式,此模式下剩余怪物数量不会实时更新而变成切换地图后更新,打开小地图时出现卡顿可以尝试开启此设置` `是否启用小地图懒更新模式,此模式下剩余怪物数量不会实时更新而变成切换地图后更新,打开小地图时出现卡顿可以尝试开启此设置`
) )
.setDescription('ui.toolbarScale', `自定义工具栏的缩放比例`)
.setDescription( .setDescription(
'ui.bookScale', 'ui.bookScale',
`怪物手册界面中每个怪物框体的高度缩放,最小值限定为 20% 屏幕高度` `怪物手册界面中每个怪物框体的高度缩放,最小值限定为 20% 屏幕高度`
@ -305,18 +359,6 @@ function setFontSize() {
} }
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', () => { window.addEventListener('resize', () => {
setFontSize(); setFontSize();
}); });

View File

@ -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();

View File

@ -1,6 +1,9 @@
import { FunctionalComponent, reactive } from 'vue'; import { FunctionalComponent, reactive } from 'vue';
import { EventEmitter } from '@motajs/legacy-common'; 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 { export interface SettingComponentProps {
item: MotaSettingItem; item: MotaSettingItem;
@ -82,7 +85,7 @@ export class MotaSetting extends EventEmitter<SettingEvent> {
key: string, key: string,
name: string, name: string,
value: MotaSettingType, value: MotaSettingType,
com: SettingComponent, com: SettingComponent = COM.Default,
step: [number, number, number] = [0, 100, 1] step: [number, number, number] = [0, 100, 1]
) { ) {
const setting: MotaSettingItem = { const setting: MotaSettingItem = {
@ -159,12 +162,12 @@ export class MotaSetting extends EventEmitter<SettingEvent> {
defaultValue?: T defaultValue?: T
): T | undefined { ): T | undefined {
const setting = this.getSetting(key); 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 (setting instanceof MotaSetting) {
if (!isNil(setting)) return defaultValue; if (has(setting)) return defaultValue;
return void 0; return void 0;
} else { } else {
return !isNil(setting) ? (setting.value as T) : (defaultValue as T); return has(setting) ? (setting.value as T) : (defaultValue as T);
} }
} }

View 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);
}
}

View File

@ -1,4 +1,4 @@
import { isNil } from 'lodash-es'; import { has } from '../utils';
import { IDamageEnemy } from '@motajs/types'; import { IDamageEnemy } from '@motajs/types';
export interface CurrentEnemy { export interface CurrentEnemy {
@ -73,7 +73,7 @@ export function getDefDamage(
if (res.length === 0) { if (res.length === 0) {
origin = dam.damage; origin = dam.damage;
if (!isNil(origin)) { if (has(origin)) {
res.push([addDef + i * ratio, origin]); res.push([addDef + i * ratio, origin]);
last = origin; last = origin;
} }
@ -113,7 +113,7 @@ export function getCriticalDamage(
if (res.length === 0) { if (res.length === 0) {
origin = dam.damage; origin = dam.damage;
if (!isNil(origin)) { if (has(origin)) {
res.push([addAtk + i * ratio, origin]); res.push([addAtk + i * ratio, origin]);
last = origin; last = origin;
} }

View 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]);
}
});
}

View File

@ -1,5 +1,4 @@
import { isNil } from 'lodash-es'; import { getStatusLabel, has } from '../utils';
import { getStatusLabel } from '../utils';
/** /**
* *
@ -62,7 +61,7 @@ export function getNowStatus(nowEquip?: Equip, onCol: boolean = false) {
'hpmax', 'hpmax',
'money' 'money'
] as (keyof SelectType<HeroStatus, number>)[]; ] as (keyof SelectType<HeroStatus, number>)[];
const { getHeroStatusOn } = Mota.require('@user/data-state'); const { getHeroStatusOn } = Mota.requireAll('fn');
return ( return (
<div id="hero-status"> <div id="hero-status">
@ -72,7 +71,7 @@ export function getNowStatus(nowEquip?: Equip, onCol: boolean = false) {
else status = getHeroStatusOn(v)?.toString(); else status = getHeroStatusOn(v)?.toString();
let add = 0; let add = 0;
if (!isNil(nowEquip)) { if (has(nowEquip)) {
add += Math.floor( add += Math.floor(
(nowEquip.value[v] ?? 0) * core.getBuff(v) (nowEquip.value[v] ?? 0) * core.getBuff(v)
); );

View File

@ -31,7 +31,7 @@ export function getDetailedEnemy(
const special: [string, string, string][] = [...enemy.info.special] const special: [string, string, string][] = [...enemy.info.special]
.filter(v => !enemy.info.specialHalo?.includes(v)) .filter(v => !enemy.info.specialHalo?.includes(v))
.map(vv => { .map(vv => {
const s = Mota.require('@user/data-state').specials[vv]; const s = Mota.require('var', 'enemySpecials')[vv];
return [ return [
fromFunc(s.name, enemy.info), fromFunc(s.name, enemy.info),
fromFunc(s.desc, enemy.info), fromFunc(s.desc, enemy.info),

View File

@ -1,7 +1,5 @@
import { isNil } from 'lodash-es'; import { mainSetting } from '../preset/ui';
import { mainSetting } from '../preset/settingIns'; import { downloadCanvasImage, has, tip } from '../utils';
import { tip } from '../use';
import { downloadCanvasImage } from '../utils';
type BFSFromString = `${FloorIds},${number},${number},${Dir}`; type BFSFromString = `${FloorIds},${number},${number},${Dir}`;
type BFSToString = `${FloorIds},${number},${number}`; type BFSToString = `${FloorIds},${number},${number}`;
@ -165,7 +163,7 @@ export function getMapData(
noCache: boolean = false noCache: boolean = false
): MapBFSResult { ): MapBFSResult {
if (!floorId) return { maps: [], link: {} }; 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 queue = [floorId];
const used: Partial<Record<FloorIds, boolean>> = { const used: Partial<Record<FloorIds, boolean>> = {
@ -521,7 +519,7 @@ export class MinimapDrawer {
ctx.textBaseline = 'middle'; ctx.textBaseline = 'middle';
ctx.font = `3px "normal"`; ctx.font = `3px "normal"`;
ctx.strokeStyle = 'black'; ctx.strokeStyle = 'black';
Mota.require('@user/data-state').ensureFloorDamage(floorId); Mota.require('fn', 'ensureFloorDamage')(floorId);
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)'; ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
ctx.fillRect(x - 6, y - 2, 12, 4); ctx.fillRect(x - 6, y - 2, 12, 4);
ctx.fillStyle = 'white'; ctx.fillStyle = 'white';

View File

@ -1,10 +1,7 @@
// import { init } from './achievement'; export * from './achievement';
// init();
// export * from './achievement';
export * from './book'; export * from './book';
export * from './common'; export * from './common';
export * from './completion';
export * from './equipbox'; export * from './equipbox';
export * from './fixed'; export * from './fixed';
export * from './fly'; export * from './fly';

View 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>

View File

@ -41,17 +41,21 @@
import { onUnmounted, ref } from 'vue'; import { onUnmounted, ref } from 'vue';
import EnemyOne from '../components/enemyOne.vue'; import EnemyOne from '../components/enemyOne.vue';
import Scroll from '../components/scroll.vue'; import Scroll from '../components/scroll.vue';
import { has } from '../utils';
import BookDetail from './bookDetail.vue'; import BookDetail from './bookDetail.vue';
import { LeftOutlined } from '@ant-design/icons-vue'; import { LeftOutlined } from '@ant-design/icons-vue';
import { ToShowEnemy, detailInfo } from '../tools/book'; import { ToShowEnemy, detailInfo } from '../tools/book';
import { getDetailedEnemy } from '../tools/fixed'; import { getDetailedEnemy } from '../tools/fixed';
import { GameUi } from '../controller';
import { gameKey } from '@motajs/system-action'; 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 { 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 = const floorId =
// @ts-ignore // @ts-ignore
@ -118,12 +122,12 @@ async function show() {
* 退出怪物手册 * 退出怪物手册
*/ */
async function exit() { async function exit() {
const hold = props.controller.holdOn(); const hold = mainUi.holdOn();
props.controller.close(props.num); mainUi.close(props.num);
if (core.events.recoverEvents(core.status.event.interval)) { if (core.events.recoverEvents(core.status.event.interval)) {
hold.end(true); hold.end(true);
return; return;
} else if (!isNil(core.status.event.ui)) { } else if (has(core.status.event.ui)) {
core.status.boxAnimateObjs = []; core.status.boxAnimateObjs = [];
// @ts-ignore // @ts-ignore
core.ui._drawViewMaps(core.status.event.ui); core.ui._drawViewMaps(core.status.event.ui);

View File

@ -22,7 +22,7 @@
:from-book="fromBook" :from-book="fromBook"
v-else-if="panel === 'critical'" v-else-if="panel === 'critical'"
></EnemyCritical> ></EnemyCritical>
<!-- <EnemyTarget v-else-if="panel === 'target'"></EnemyTarget> --> <EnemyTarget v-else-if="panel === 'target'"></EnemyTarget>
</Transition> </Transition>
<div id="detail-more"> <div id="detail-more">
<Transition name="detail"> <Transition name="detail">
@ -35,8 +35,8 @@
id="enemy-target" id="enemy-target"
class="button-text more" class="button-text more"
@click="changePanel($event, 'target')" @click="changePanel($event, 'target')"
><LeftOutlined /> ><LeftOutlined /> 怪物更多信息</span
</span> >
<span <span
id="critical-more" id="critical-more"
class="button-text more" class="button-text more"
@ -81,7 +81,7 @@ import { useDrag } from '../use';
import EnemySpecial from '../panel/enemySpecial.vue'; import EnemySpecial from '../panel/enemySpecial.vue';
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue'; import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
import EnemyCritical from '../panel/enemyCritical.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 { detailInfo } from '../tools/book';
import { gameKey } from '@motajs/system-action'; import { gameKey } from '@motajs/system-action';

View File

@ -1,17 +1,22 @@
<template> <template>
<div id="chapter"> <div id="chapter">
<canvas id="chapter-back"></canvas> <canvas id="chapter-back"></canvas>
<span id="chapter-text">{{ props.chapter }}</span> <span id="chapter-text">{{ chapter }}</span>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Animation, hyper, sleep } from 'mutate-animate'; import { Animation, hyper, sleep } from 'mutate-animate';
import { onMounted } from 'vue'; import { onMounted } from 'vue';
import { IMountedVBind } from '../interface'; import { has } from '../utils';
import { isNil } from 'lodash-es'; 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 can: HTMLCanvasElement;
let ctx: CanvasRenderingContext2D; let ctx: CanvasRenderingContext2D;
@ -45,14 +50,14 @@ onMounted(async () => {
let started = false; let started = false;
ani.ticker.add(time => { ani.ticker.add(time => {
if (isNil(time) || isNaN(time)) return; if (!has(time) || isNaN(time)) return;
if (!started) { if (!started) {
started = true; started = true;
return; return;
} }
if (time >= 4050) { if (time >= 4050) {
props.controller.close(props.num); fixedUi.close(props.num);
ani.ticker.destroy(); ani.ticker.destroy();
} }

View File

@ -0,0 +1,113 @@
<template>
<Box id="complete-box">
<div id="complete">
<span>完成成就&nbsp;&nbsp;&nbsp;&nbsp;{{ 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>

View File

@ -30,7 +30,7 @@
import { nextTick, onUnmounted, reactive, watch } from 'vue'; import { nextTick, onUnmounted, reactive, watch } from 'vue';
import { Danmaku } from '../danmaku'; import { Danmaku } from '../danmaku';
import { LikeFilled } from '@ant-design/icons-vue'; import { LikeFilled } from '@ant-design/icons-vue';
import { mainSetting } from '../preset/settingIns'; import { mainSetting } from '../preset/ui';
import { debounce } from 'lodash-es'; import { debounce } from 'lodash-es';
interface ElementMap { interface ElementMap {
@ -205,7 +205,10 @@ onUnmounted(() => {});
} }
.danmaku-info { .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; -1px -1px 1px black;
} }

View File

@ -159,17 +159,21 @@ import {
UpOutlined UpOutlined
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
import { Danmaku } from '../danmaku'; import { Danmaku } from '../danmaku';
import { GameUi } from '../controller';
import { sleep } from 'mutate-animate'; 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 { gameKey } from '@motajs/system-action';
import { isNil } from 'lodash-es'; import { isNil } from 'lodash-es';
import { stringifyCSS, parseCss, getIconHeight } from '../utils';
import { logger, LogLevel } from '@motajs/common'; import { logger, LogLevel } from '@motajs/common';
import Scroll from '../components/scroll.vue'; import Scroll from '../components/scroll.vue';
import BoxAnimate from '../components/boxAnimate.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}`)[] = [ const frequentlyIcon: (AllIds | 'hero' | `X${number}`)[] = [
'hero', 'hero',
@ -297,7 +301,7 @@ function close() {
mainDiv.classList.remove('danmaku-startup'); mainDiv.classList.remove('danmaku-startup');
mainDiv.classList.add('danmaku-close'); mainDiv.classList.add('danmaku-close');
sleep(200).then(() => { sleep(200).then(() => {
props.controller.close(props.num); fixedUi.close(props.num);
}); });
} }

View File

@ -22,17 +22,21 @@ import { computed, onUnmounted, ref } from 'vue';
import desc from '../data/desc.json'; import desc from '../data/desc.json';
import { splitText } from '../utils'; import { splitText } from '../utils';
import Colomn from '../components/colomn.vue'; import Colomn from '../components/colomn.vue';
import { GameUi } from '../controller';
import { gameKey } from '@motajs/system-action'; 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; type DescKey = keyof typeof desc;
const selected = ref(Object.keys(desc)[0] as DescKey); const selected = ref(Object.keys(desc)[0] as DescKey);
function exit() { function exit() {
props.controller.close(props.num); mainUi.close(props.num);
} }
const content = computed(() => { const content = computed(() => {

View File

@ -125,8 +125,8 @@
<BoxAnimate <BoxAnimate
:id=" :id="
isCol isCol
? equiped[selected] ?? 'none' ? (equiped[selected] ?? 'none')
: toShow[selected]?.[0] ?? 'none' : (toShow[selected]?.[0] ?? 'none')
" "
></BoxAnimate> ></BoxAnimate>
<span>{{ equip.name }}</span> <span>{{ equip.name }}</span>
@ -183,14 +183,18 @@ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
import Scroll from '../components/scroll.vue'; import Scroll from '../components/scroll.vue';
import { getAddStatus, getEquips, getNowStatus } from '../tools/equipbox'; import { getAddStatus, getEquips, getNowStatus } from '../tools/equipbox';
import BoxAnimate from '../components/boxAnimate.vue'; import BoxAnimate from '../components/boxAnimate.vue';
import { type, getStatusLabel } from '../utils'; import { has, tip, type } from '../utils';
import { cancelGlobalDrag, isMobile, tip, useDrag } from '../use'; import { cancelGlobalDrag, isMobile, useDrag } from '../use';
import { hyper } from 'mutate-animate'; import { hyper } from 'mutate-animate';
import { GameUi } from '../controller';
import { gameKey } from '@motajs/system-action'; import { gameKey } from '@motajs/system-action';
import { IMountedVBind } from '../interface'; import { getStatusLabel } from '../utils';
import { isNil } from 'lodash-es'; import { mainUi } from '../preset/ui';
const props = defineProps<IMountedVBind>(); const props = defineProps<{
num: number;
ui: GameUi;
}>();
const equips = ref(getEquips()); const equips = ref(getEquips());
const col = ref('all'); const col = ref('all');
@ -238,10 +242,10 @@ const equip = computed(() => {
if (isCol.value) { if (isCol.value) {
const id = equiped.value[selected.value]; const id = equiped.value[selected.value];
const e = core.material.items[id]; const e = core.material.items[id];
if (isNil(e)) return none; if (!has(e)) return none;
return e; return e;
} }
if (isNil(index)) return none; if (!has(index)) return none;
return all[index[0]]; return all[index[0]];
}); });
@ -272,7 +276,7 @@ const toShow = computed(() => {
const e = all[v[0]].equip!; const e = all[v[0]].equip!;
const t = e.type; const t = e.type;
if (sortNorm !== 'none') { if (sortNorm !== 'none') {
if (isNil(e[sortBy][sortNorm])) return false; if (!has(e[sortBy][sortNorm])) return false;
} }
if (col.value === 'all') return true; if (col.value === 'all') return true;
if (typeof t === 'string') return t === col.value; if (typeof t === 'string') return t === col.value;
@ -305,7 +309,7 @@ function changeSort() {
} }
function exit() { function exit() {
props.controller.close(props.num); mainUi.close(props.num);
} }
function clickList(i: number) { function clickList(i: number) {
@ -346,7 +350,7 @@ function canDragin(type: number) {
if (type < 0) return false; if (type < 0) return false;
const et = equip.value.equip?.type; const et = equip.value.equip?.type;
if (!core.canEquip(toShow.value[selected.value]?.[0])) return false; 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; if (typeof et === 'number') return type === et;
return equipCol[type] === et; return equipCol[type] === et;
} }
@ -423,10 +427,10 @@ function dragout(e: Event) {
} }
function toTool() { function toTool() {
props.controller.holdOn(); mainUi.holdOn();
exit(); exit();
nextTick(() => { nextTick(() => {
props.controller.open('toolbox'); mainUi.open('toolbox');
}); });
} }

View File

@ -31,12 +31,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUpdated, Ref, ref, watch } from 'vue'; import { onMounted, onUpdated, Ref, ref, watch } from 'vue';
import Box from '../components/box.vue'; import Box from '../components/box.vue';
import { GameUi } from '../controller';
import type { DamageEnemy, EnemyInfo } from '@/game/enemy/damage';
import { nextFrame } from '../utils'; import { nextFrame } from '../utils';
import { EnemyInfo, IDamageEnemy } from '@motajs/types';
const props = defineProps<{ const props = defineProps<{
num: number; num: number;
enemy: IDamageEnemy; ui: GameUi;
enemy: DamageEnemy;
close: Ref<boolean>; close: Ref<boolean>;
loc: [x: number, y: number]; loc: [x: number, y: number];
}>(); }>();
@ -81,7 +83,7 @@ const special = (() => {
}; };
const show = s.slice(0, 2).map(v => { 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]; return [fromFunc(s.name, enemy.info), s.color];
}); });
if (s.length > 2) show.push(['...', 'white']); if (s.length > 2) show.push(['...', 'white']);

View File

@ -13,9 +13,14 @@ import { getDetailedEnemy } from '../tools/fixed';
import BookDetail from './bookDetail.vue'; import BookDetail from './bookDetail.vue';
import { detailInfo } from '../tools/book'; import { detailInfo } from '../tools/book';
import { hovered } from '../preset/fixed'; 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'; const panel = props.panel ?? 'special';
@ -35,7 +40,7 @@ if (hovered) {
} }
function close() { function close() {
props.controller.close(props.num); mainUi.close(props.num);
} }
</script> </script>

View File

@ -98,14 +98,18 @@ import {
DoubleRightOutlined DoubleRightOutlined
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
import { debounce } from 'lodash-es'; import { debounce } from 'lodash-es';
import { tip } from '../use'; import { tip } from '../utils';
import { GameUi } from '../controller';
import { gameKey } from '@motajs/system-action'; import { gameKey } from '@motajs/system-action';
import { createChangable } from '../tools/common'; 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 { 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]; type Loc2 = [number, number, number, number];
@ -158,7 +162,7 @@ let thumb: HTMLCanvasElement;
let thumbCtx: CanvasRenderingContext2D; let thumbCtx: CanvasRenderingContext2D;
function exit() { function exit() {
props.controller.close(props.num); mainUi.close(props.num);
} }
const title = computed(() => { const title = computed(() => {
@ -580,7 +584,10 @@ onUnmounted(() => {
max-width: 50%; max-width: 50%;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; 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; -1px -1px 1px black;
} }

View File

@ -37,13 +37,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Hotkey } from '@motajs/system-action'; import { Hotkey } from '@motajs/system-action';
import { GameUi } from '../controller';
import Column from '../components/colomn.vue'; import Column from '../components/colomn.vue';
import { mainUi } from '../preset/ui';
import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'; import { computed, onMounted, onUnmounted, reactive, ref } from 'vue';
import { KeyCode, KeyCodeUtils } from '@motajs/client-base'; import { KeyCode, KeyCodeUtils } from '@motajs/client-base';
import { generateBinary, keycode } from '../utils';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { gameKey } from '@motajs/system-action'; import { gameKey } from '@motajs/system-action';
import { generateBinary, keycode } from '@motajs/legacy-common';
import { IMountedVBind } from '../interface';
interface HotkeyKeys { interface HotkeyKeys {
index: number; index: number;
@ -63,12 +64,16 @@ interface SelectedKey {
index: number; index: number;
} }
const props = defineProps<IMountedVBind & { hotkey: Hotkey }>(); const props = defineProps<{
num: number;
ui: GameUi;
hotkey: Hotkey;
}>();
const hotkey = props.hotkey; const hotkey = props.hotkey;
function close() { function close() {
props.controller.close(props.num); mainUi.close(props.num);
} }
const selectedGroup = ref('ui'); const selectedGroup = ref('ui');

View File

@ -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 Book } from './book.vue';
export { default as BookDetail } from './bookDetail.vue'; export { default as BookDetail } from './bookDetail.vue';
export { default as Chapter } from './chapter.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 Desc } from './desc.vue';
export { default as Equipbox } from './equipbox.vue'; export { default as Equipbox } from './equipbox.vue';
export { default as Fixed } from './fixed.vue'; export { default as Fixed } from './fixed.vue';
export { default as FixedDetail } from './fixedDetail.vue'; export { default as FixedDetail } from './fixedDetail.vue';
export { default as Fly } from './fly.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 Settings } from './settings.vue';
export { default as Shop } from './shop.vue'; export { default as Shop } from './shop.vue';
export { default as Skill } from './skill.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 Start } from './start.vue';
export { default as Toolbox } from './toolbox.vue'; export { default as Toolbox } from './toolbox.vue';
export { default as Hotkey } from './hotkey.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 Load } from './load.vue';
export { default as Danmaku } from './danmaku.vue'; export { default as Danmaku } from './danmaku.vue';
export { default as DanmakuEditor } from './danmakuEditor.vue'; export { default as DanmakuEditor } from './danmakuEditor.vue';

View File

@ -34,10 +34,14 @@ import {
import { GameUi } from '../controller'; import { GameUi } from '../controller';
import { formatSize } from '../utils'; import { formatSize } from '../utils';
import { logger } from '@motajs/common'; import { logger } from '@motajs/common';
import { fixedUi } from '../preset/ui';
import { sleep } from 'mutate-animate'; 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 loading = ref(0);
const loaded = ref(0); const loaded = ref(0);
@ -70,10 +74,10 @@ onMounted(async () => {
core._afterLoadResources(props.callback); core._afterLoadResources(props.callback);
logger.log(`Resource load end.`); logger.log(`Resource load end.`);
loadDiv.style.opacity = '0'; loadDiv.style.opacity = '0';
Mota.require('@user/data-base').loading.emit('loaded'); Mota.require('var', 'loading').emit('loaded');
await sleep(1000); await sleep(1000);
props.controller.close(props.num); fixedUi.close(props.num);
props.controller.open('start'); fixedUi.open('start');
}); });
loadDiv = document.getElementById('load') as HTMLDivElement; loadDiv = document.getElementById('load') as HTMLDivElement;
}); });

View 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>

View File

@ -73,7 +73,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onUnmounted, ref, shallowRef } from 'vue'; import { computed, onUnmounted, ref, shallowRef } from 'vue';
import { mainSetting } from '../preset/settingIns'; import { mainSetting } from '../preset/ui';
import { import {
MotaSetting, MotaSetting,
MotaSettingItem, MotaSettingItem,
@ -85,9 +85,14 @@ import { splitText } from '../utils';
import Scroll from '../components/scroll.vue'; import Scroll from '../components/scroll.vue';
import { isMobile } from '../use'; import { isMobile } from '../use';
import { gameKey } from '@motajs/system-action'; 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 setting = props.info ?? mainSetting;
const display = shallowRef<SettingDisplayInfo[]>([]); const display = shallowRef<SettingDisplayInfo[]>([]);
@ -140,7 +145,7 @@ function click(key: string, index: number, item: MotaSettingItem) {
} }
function exit() { function exit() {
props.controller.close(props.num); mainUi.close(props.num);
} }
gameKey.use(props.ui.symbol); gameKey.use(props.ui.symbol);

View File

@ -169,14 +169,18 @@ import {
RightOutlined, RightOutlined,
DoubleRightOutlined DoubleRightOutlined
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
import { splitText } from '../utils'; import { splitText, tip } from '../utils';
import Scroll from '../components/scroll.vue'; import Scroll from '../components/scroll.vue';
import BoxAnimate from '../components/boxAnimate.vue'; import BoxAnimate from '../components/boxAnimate.vue';
import { GameUi } from '../controller';
import { gameKey } from '@motajs/system-action'; import { gameKey } from '@motajs/system-action';
import { IMountedVBind } from '../interface'; import { mainUi } from '../preset/ui';
import { tip } from '../use';
const props = defineProps<IMountedVBind>(); const props = defineProps<{
num: number;
ui: GameUi;
shopId: string;
}>();
const id = props.shopId; const id = props.shopId;
const shop = core.status.shops[id] as ItemShopEvent; const shop = core.status.shops[id] as ItemShopEvent;
@ -311,7 +315,7 @@ gameKey
function exit() { function exit() {
if (bought) core.status.route.push('closeShop'); if (bought) core.status.route.push('closeShop');
props.controller.close(props.num); mainUi.close(props.num);
} }
onMounted(async () => { onMounted(async () => {

View File

@ -19,11 +19,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import skills from '../data/skill.json'; import skills from '../data/skill.json';
import { has } from '../utils';
import Column from '../components/colomn.vue'; import Column from '../components/colomn.vue';
import { IMountedVBind } from '../interface'; import { mainUi } from '../preset/ui';
import { isNil } from 'lodash-es';
const props = defineProps<IMountedVBind>(); const props = defineProps<{
num: number;
}>();
type Skills = keyof typeof skills; type Skills = keyof typeof skills;
@ -45,7 +47,7 @@ const content = computed(() => {
.map((v, i, a) => { .map((v, i, a) => {
if (/^\d+\./.test(v)) return `${'&nbsp;'.repeat(12)}${v}`; if (/^\d+\./.test(v)) return `${'&nbsp;'.repeat(12)}${v}`;
else if ( else if (
(!isNil(a[i - 1]) && (has(a[i - 1]) &&
v !== '<br>' && v !== '<br>' &&
a[i - 1] === '<br>') || a[i - 1] === '<br>') ||
i === 0 i === 0
@ -56,14 +58,14 @@ const content = computed(() => {
.join('') .join('')
.replace( .replace(
/level:(\d+)/g, /level:(\d+)/g,
'Mota.require("@user/data-state").getSkillLevel($1)' 'Mota.Plugin.require("skillTree_g").getSkillLevel($1)'
) + ) +
'`' '`'
); );
}); });
function exit() { function exit() {
props.controller.close(props.num); mainUi.close(props.num);
} }
</script> </script>

View File

@ -81,34 +81,38 @@
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'; import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue'; import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
import Scroll from '../components/scroll.vue'; import Scroll from '../components/scroll.vue';
import { splitText } from '../utils'; import { has, splitText, tip } from '../utils';
import { isMobile, tip } from '../use'; import { isMobile } from '../use';
import { sleep } from 'mutate-animate'; import { sleep } from 'mutate-animate';
import { gameKey } from '@motajs/system-action'; import { gameKey } from '@motajs/system-action';
import { IMountedVBind } from '../interface'; import { GameUi } from '../controller';
import { isNil } from 'lodash-es'; 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 canvas: HTMLCanvasElement;
let ctx: CanvasRenderingContext2D; let ctx: CanvasRenderingContext2D;
const selected = ref(0);
const chapter = ref<Chapter>('chapter1');
const update = ref(false);
const chapterDict = { const chapterDict = {
chapter1: '第一章', chapter1: '第一章',
chapter2: '第二章' chapter2: '第二章'
}; };
const selected = ref(0);
const chapter = ref<keyof typeof chapterDict>('chapter1');
const update = ref(false);
flags.skillTree ??= 0; 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; selected.value = s[chapterList[flags.skillTree]][0].index;
chapter.value = chapterList[flags.skillTree]; chapter.value = chapterList[flags.skillTree];
@ -176,7 +180,7 @@ const level = computed(() => {
}); });
function exit() { function exit() {
props.controller.close(props.num); mainUi.close(props.num);
} }
function resize() { function resize() {
@ -295,7 +299,7 @@ function selectChapter(delta: number) {
const now = chapterList.indexOf(chapter.value); const now = chapterList.indexOf(chapter.value);
const to = now + delta; 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; selected.value = s[chapterList[to]][0].index;
chapter.value = chapterList[to]; chapter.value = chapterList[to];
update.value = !update.value; update.value = !update.value;

View File

@ -64,14 +64,21 @@ import {
FullscreenExitOutlined FullscreenExitOutlined
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
import { sleep } from 'mutate-animate'; import { sleep } from 'mutate-animate';
import { doByInterval, triggerFullscreen } from '../utils'; import { doByInterval } from '../utils';
import { triggerFullscreen } from '../utils';
import { isMobile } from '../use'; import { isMobile } from '../use';
import { GameUi } from '../controller';
import { gameKey } from '@motajs/system-action'; 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 { 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']; const bg = core.material.images.images['bg.webp'];
@ -83,7 +90,7 @@ let background: HTMLImageElement;
let buttons: HTMLSpanElement[] = []; let buttons: HTMLSpanElement[] = [];
let played: boolean = false; let played: boolean;
const soundChecked = ref(false); const soundChecked = ref(false);
const fullscreen = ref(!!document.fullscreenElement); const fullscreen = ref(!!document.fullscreenElement);
@ -153,7 +160,7 @@ async function clickStartButton(id: string) {
} }
if (id === 'replay') core.chooseReplayFile(); if (id === 'replay') core.chooseReplayFile();
if (id === 'achievement') { if (id === 'achievement') {
props.controller.open('achievement'); mainUi.open('achievement');
} }
} }
@ -320,7 +327,6 @@ onMounted(async () => {
resize(); resize();
soundChecked.value = mainSetting.getValue('audio.bgmEnabled', true); soundChecked.value = mainSetting.getValue('audio.bgmEnabled', true);
const { bgmController } = Mota.require('@user/client-modules');
bgmController.play('title.opus'); bgmController.play('title.opus');
start.style.opacity = '1'; start.style.opacity = '1';
@ -419,7 +425,8 @@ onUnmounted(() => {
); );
background-clip: text; background-clip: text;
-webkit-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), -1px -1px 3px rgba(255, 255, 255, 0.3),
5px 5px 5px rgba(0, 0, 0, 0.4); 5px 5px 5px rgba(0, 0, 0, 0.4);
filter: brightness(1.8); filter: brightness(1.8);
@ -442,14 +449,17 @@ onUnmounted(() => {
position: absolute; position: absolute;
opacity: 0; opacity: 0;
animation: cursor 2.5s linear 0s infinite normal running; 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; opacity 1.5s ease-out;
} }
.start-button { .start-button {
position: relative; position: relative;
font: bold 1.5em 'normal'; 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); 0px 0px 1px rgba(255, 255, 255, 0.3);
background-clip: text; background-clip: text;
-webkit-background-clip: text; -webkit-background-clip: text;

View File

@ -85,8 +85,8 @@
<span>{{ <span>{{
selected === 'none' selected === 'none'
? '永久道具' ? '永久道具'
: getClsName(all[selected].cls as ItemMode) ?? : (getClsName(all[selected].cls as ItemMode) ??
'永久道具' '永久道具')
}}</span> }}</span>
</div> </div>
</div> </div>
@ -113,14 +113,17 @@ import Scroll from '../components/scroll.vue';
import BoxAnimate from '../components/boxAnimate.vue'; import BoxAnimate from '../components/boxAnimate.vue';
import { getClsName, getItems } from '../tools/toolbox'; import { getClsName, getItems } from '../tools/toolbox';
import { isMobile } from '../use'; import { isMobile } from '../use';
import { type } from '../utils'; import { type, has } from '../utils';
import { hyper } from 'mutate-animate'; import { hyper } from 'mutate-animate';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { GameUi } from '../controller';
import { gameKey } from '@motajs/system-action'; import { gameKey } from '@motajs/system-action';
import { IMountedVBind } from '../interface'; import { mainUi } from '../preset/ui';
import { isNil } from 'lodash-es';
const props = defineProps<IMountedVBind>(); const props = defineProps<{
num: number;
ui: GameUi;
}>();
type ItemMode = 'tools' | 'constants'; type ItemMode = 'tools' | 'constants';
type ShowItemIds = ItemIdOf<'constants' | 'tools'> | 'none'; type ShowItemIds = ItemIdOf<'constants' | 'tools'> | 'none';
@ -143,7 +146,7 @@ watch(index, n => {
}); });
watch(mode, n => { watch(mode, n => {
if (isNil(items[n][index.value])) { if (!has(items[n][index.value])) {
selected.value = 'none'; selected.value = 'none';
return; return;
} }
@ -169,17 +172,17 @@ async function select(id: ShowItemIds, nouse: boolean = false) {
} }
function exit() { function exit() {
props.controller.close(props.num); mainUi.close(props.num);
} }
function use(id: ShowItemIds) { function use(id: ShowItemIds) {
if (id === 'none') return; if (id === 'none') return;
if (core.canUseItem(id)) { if (core.canUseItem(id)) {
const hold = props.controller.holdOn(); const hold = mainUi.holdOn();
exit(); exit();
nextTick(() => { nextTick(() => {
core.tryUseItem(id, false, () => { core.tryUseItem(id, false, () => {
if (props.controller.stack.length === 0) { if (mainUi.stack.length === 0) {
hold.end(core.status.event.id !== 'toolbox'); hold.end(core.status.event.id !== 'toolbox');
} }
}); });
@ -193,10 +196,10 @@ function use(id: ShowItemIds) {
} }
async function toEquip() { async function toEquip() {
props.controller.holdOn(); mainUi.holdOn();
exit(); exit();
nextTick(() => { nextTick(() => {
props.controller.open('equipbox'); mainUi.open('equipbox');
}); });
} }

View File

@ -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