mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-11 15:47:06 +08:00
refactor: 修复所有的循环引用
This commit is contained in:
parent
4fda246a9a
commit
23a8c0bf3a
@ -11,13 +11,6 @@ import {
|
||||
Font
|
||||
} from '@motajs/render';
|
||||
import { WeatherController } from '../../weather';
|
||||
import {
|
||||
FloorChange,
|
||||
LayerGroupFilter,
|
||||
LayerGroupHalo,
|
||||
LayerGroupPortal,
|
||||
PopText
|
||||
} from '@user/legacy-plugin-client';
|
||||
import { defineComponent, onMounted, reactive, ref } from 'vue';
|
||||
import { Textbox, Tip } from '../components';
|
||||
import { GameUI, UIController } from '@motajs/system-ui';
|
||||
@ -40,6 +33,11 @@ import { jumpIgnoreFloor } from '@user/legacy-plugin-data';
|
||||
import { hook } from '@user/data-base';
|
||||
import { FloorDamageExtends } from '../damage';
|
||||
import { FloorItemDetail } from '../itemDetail';
|
||||
import { LayerGroupPortal } from '../legacy/portal';
|
||||
import { LayerGroupFilter } from '../legacy/gameCanvas';
|
||||
import { LayerGroupHalo } from '../legacy/halo';
|
||||
import { FloorChange } from '../legacy/fallback';
|
||||
import { PopText } from '../legacy/pop';
|
||||
|
||||
const MainScene = defineComponent(() => {
|
||||
const layerGroupExtends: ILayerGroupRenderExtends[] = [
|
||||
|
@ -13,17 +13,14 @@ import {
|
||||
StepForward,
|
||||
ViewMapIcon
|
||||
} from '../components/icons';
|
||||
import {
|
||||
generateBinary,
|
||||
getVitualKeyOnce,
|
||||
openDanmakuPoster
|
||||
} from '@motajs/legacy-ui';
|
||||
import { getVitualKeyOnce, openDanmakuPoster } from '@motajs/legacy-ui';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { generateKeyboardEvent } from '@motajs/system-action';
|
||||
import { transitioned } from '../use';
|
||||
import { linear } from 'mutate-animate';
|
||||
import { KeyCode } from '@motajs/client-base';
|
||||
import { Progress } from '../components/misc';
|
||||
import { generateBinary } from '@motajs/legacy-common';
|
||||
|
||||
interface ToolbarProps extends DefaultProps {
|
||||
loc?: ElementLocator;
|
||||
|
@ -2,7 +2,7 @@ import { getHeroStatusOf, getHeroStatusOn } from '../state/hero';
|
||||
import { Range, ensureArray, has, manhattan } from '@user/data-utils';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { hook } from '@user/data-base';
|
||||
import { HeroSkill, NightSpecial } from '../mechanism/misc';
|
||||
import { HeroSkill, NightSpecial } from '../mechanism';
|
||||
import {
|
||||
EnemyInfo,
|
||||
DamageInfo,
|
||||
@ -16,6 +16,7 @@ import {
|
||||
HaloType,
|
||||
IEnemyCollectionEvent
|
||||
} from '@motajs/types';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
// todo: 光环划分优先级,从而可以实现光环的多级运算
|
||||
|
||||
@ -293,7 +294,7 @@ export class DamageEnemy implements IDamageEnemy {
|
||||
|
||||
// 融化,融化不属于怪物光环,因此不能用provide和inject计算,需要在这里计算
|
||||
const melt = flags[`melt_${floorId}`];
|
||||
if (has(melt) && has(this.x) && has(this.y)) {
|
||||
if (!isNil(melt) && !isNil(this.x) && !isNil(this.y)) {
|
||||
for (const [loc, per] of Object.entries(melt)) {
|
||||
const [mx, my] = loc.split(',').map(v => parseInt(v));
|
||||
if (
|
||||
|
@ -1 +1,2 @@
|
||||
export * from './misc';
|
||||
export * from './skillTree';
|
||||
|
@ -48,131 +48,6 @@ export namespace NightSpecial {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace HeroSkill {
|
||||
export const enum Skill {
|
||||
None,
|
||||
/** 断灭之刃 */
|
||||
Blade,
|
||||
/** 铸剑为盾 */
|
||||
Shield,
|
||||
/** 跳跃 */
|
||||
Jump
|
||||
}
|
||||
|
||||
export const Blade = Skill.Blade;
|
||||
export const Shield = Skill.Shield;
|
||||
export const Jump = Skill.Jump;
|
||||
|
||||
const skillNameMap = new Map<Skill, string>([
|
||||
[Skill.Blade, '断灭之刃'],
|
||||
[Skill.Shield, '铸剑为盾'],
|
||||
[Skill.Jump, '跳跃']
|
||||
]);
|
||||
|
||||
const skillDesc = new Map<Skill, (level: number) => string>([
|
||||
[
|
||||
Skill.Blade,
|
||||
level => `攻击上升 ${level * 10}%,防御下降 ${level * 10}%`
|
||||
],
|
||||
[
|
||||
Skill.Shield,
|
||||
level => `防御上升 ${level * 10}%,攻击下降 ${level * 10}%`
|
||||
],
|
||||
[Skill.Jump, () => `跳过前方障碍,或踢走面前的怪物`]
|
||||
]);
|
||||
|
||||
interface SkillSave {
|
||||
autoSkill: boolean;
|
||||
learned: Skill[];
|
||||
}
|
||||
|
||||
const learned = new Set<Skill>();
|
||||
let autoSkill = true;
|
||||
let enabled: Skill = Skill.None;
|
||||
|
||||
export function getLevel(skill: Skill = getEnabled()) {
|
||||
switch (skill) {
|
||||
case Blade:
|
||||
return getSkillLevel(2);
|
||||
case Jump:
|
||||
return learned.has(Jump) ? 1 : 0;
|
||||
case Shield:
|
||||
return getSkillLevel(10);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function getSkillName(skill: Skill = getEnabled()) {
|
||||
return skillNameMap.get(skill) ?? '未开启技能';
|
||||
}
|
||||
|
||||
export function getSkillDesc(
|
||||
skill: Skill = getEnabled(),
|
||||
level: number = getLevel()
|
||||
) {
|
||||
return skillDesc.get(skill)?.(level) ?? '';
|
||||
}
|
||||
|
||||
export function setAutoSkill(auto: boolean) {
|
||||
autoSkill = auto;
|
||||
}
|
||||
|
||||
export function getAutoSkill() {
|
||||
return autoSkill;
|
||||
}
|
||||
|
||||
export function learnedSkill(skill: Skill) {
|
||||
return learned.has(skill);
|
||||
}
|
||||
|
||||
export function learnSkill(skill: Skill) {
|
||||
learned.add(skill);
|
||||
}
|
||||
|
||||
export function forgetSkill(skill: Skill) {
|
||||
learned.delete(skill);
|
||||
}
|
||||
|
||||
export function clearSkill() {
|
||||
learned.clear();
|
||||
}
|
||||
|
||||
export function saveSkill(): SkillSave {
|
||||
return { autoSkill, learned: [...learned] };
|
||||
}
|
||||
|
||||
export function loadSkill(skills: SkillSave) {
|
||||
learned.clear();
|
||||
for (const skill of skills.learned) {
|
||||
learned.add(skill);
|
||||
}
|
||||
autoSkill = skills.autoSkill;
|
||||
}
|
||||
|
||||
export function getAll() {
|
||||
return learned;
|
||||
}
|
||||
|
||||
export function toggleSkill(skill: Skill) {
|
||||
if (!learned.has(skill)) return;
|
||||
if (enabled !== skill) enabled = skill;
|
||||
else enabled = Skill.None;
|
||||
}
|
||||
|
||||
export function enableSkill(skill: Skill) {
|
||||
if (!learned.has(skill)) return;
|
||||
enabled = skill;
|
||||
}
|
||||
|
||||
export function disableSkill() {
|
||||
enabled = Skill.None;
|
||||
}
|
||||
|
||||
export function getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace BluePalace {
|
||||
type DoorConvertInfo = [id: AllIds, x: number, y: number];
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { HeroSkill } from './misc';
|
||||
|
||||
let levels: number[] = [];
|
||||
|
||||
export type Chapter = 'chapter1' | 'chapter2';
|
||||
@ -314,3 +312,128 @@ export function saveSkillTree() {
|
||||
export function loadSkillTree(data: number[]) {
|
||||
levels = data ?? [];
|
||||
}
|
||||
|
||||
export namespace HeroSkill {
|
||||
export const enum Skill {
|
||||
None,
|
||||
/** 断灭之刃 */
|
||||
Blade,
|
||||
/** 铸剑为盾 */
|
||||
Shield,
|
||||
/** 跳跃 */
|
||||
Jump
|
||||
}
|
||||
|
||||
export const Blade = Skill.Blade;
|
||||
export const Shield = Skill.Shield;
|
||||
export const Jump = Skill.Jump;
|
||||
|
||||
const skillNameMap = new Map<Skill, string>([
|
||||
[Skill.Blade, '断灭之刃'],
|
||||
[Skill.Shield, '铸剑为盾'],
|
||||
[Skill.Jump, '跳跃']
|
||||
]);
|
||||
|
||||
const skillDesc = new Map<Skill, (level: number) => string>([
|
||||
[
|
||||
Skill.Blade,
|
||||
level => `攻击上升 ${level * 10}%,防御下降 ${level * 10}%`
|
||||
],
|
||||
[
|
||||
Skill.Shield,
|
||||
level => `防御上升 ${level * 10}%,攻击下降 ${level * 10}%`
|
||||
],
|
||||
[Skill.Jump, () => `跳过前方障碍,或踢走面前的怪物`]
|
||||
]);
|
||||
|
||||
interface SkillSave {
|
||||
autoSkill: boolean;
|
||||
learned: Skill[];
|
||||
}
|
||||
|
||||
const learned = new Set<Skill>();
|
||||
let autoSkill = true;
|
||||
let enabled: Skill = Skill.None;
|
||||
|
||||
export function getLevel(skill: Skill = getEnabled()) {
|
||||
switch (skill) {
|
||||
case Blade:
|
||||
return getSkillLevel(2);
|
||||
case Jump:
|
||||
return learned.has(Jump) ? 1 : 0;
|
||||
case Shield:
|
||||
return getSkillLevel(10);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function getSkillName(skill: Skill = getEnabled()) {
|
||||
return skillNameMap.get(skill) ?? '未开启技能';
|
||||
}
|
||||
|
||||
export function getSkillDesc(
|
||||
skill: Skill = getEnabled(),
|
||||
level: number = getLevel()
|
||||
) {
|
||||
return skillDesc.get(skill)?.(level) ?? '';
|
||||
}
|
||||
|
||||
export function setAutoSkill(auto: boolean) {
|
||||
autoSkill = auto;
|
||||
}
|
||||
|
||||
export function getAutoSkill() {
|
||||
return autoSkill;
|
||||
}
|
||||
|
||||
export function learnedSkill(skill: Skill) {
|
||||
return learned.has(skill);
|
||||
}
|
||||
|
||||
export function learnSkill(skill: Skill) {
|
||||
learned.add(skill);
|
||||
}
|
||||
|
||||
export function forgetSkill(skill: Skill) {
|
||||
learned.delete(skill);
|
||||
}
|
||||
|
||||
export function clearSkill() {
|
||||
learned.clear();
|
||||
}
|
||||
|
||||
export function saveSkill(): SkillSave {
|
||||
return { autoSkill, learned: [...learned] };
|
||||
}
|
||||
|
||||
export function loadSkill(skills: SkillSave) {
|
||||
learned.clear();
|
||||
for (const skill of skills.learned) {
|
||||
learned.add(skill);
|
||||
}
|
||||
autoSkill = skills.autoSkill;
|
||||
}
|
||||
|
||||
export function getAll() {
|
||||
return learned;
|
||||
}
|
||||
|
||||
export function toggleSkill(skill: Skill) {
|
||||
if (!learned.has(skill)) return;
|
||||
if (enabled !== skill) enabled = skill;
|
||||
else enabled = Skill.None;
|
||||
}
|
||||
|
||||
export function enableSkill(skill: Skill) {
|
||||
if (!learned.has(skill)) return;
|
||||
enabled = skill;
|
||||
}
|
||||
|
||||
export function disableSkill() {
|
||||
enabled = Skill.None;
|
||||
}
|
||||
|
||||
export function getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { HeroSkill, NightSpecial } from '../mechanism/misc';
|
||||
import { HeroSkill, NightSpecial } from '../mechanism';
|
||||
|
||||
/**
|
||||
* 获取勇士在某一点的属性
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
Transform,
|
||||
MotaOffscreenCanvas2D
|
||||
} from '@motajs/render';
|
||||
import { Pop } from '../fx/pop';
|
||||
import { Pop } from '../../../client-modules/src/render/legacy/pop';
|
||||
import { SplittableBall } from './palaceBossProjectile';
|
||||
import { PointEffect } from '../fx/pointShader';
|
||||
import { loading } from '@user/data-base';
|
||||
|
@ -22,10 +22,10 @@ import {
|
||||
ThunderProjectile
|
||||
} from './towerBossProjectile';
|
||||
import { IStateDamageable } from '@user/data-state';
|
||||
import { Pop } from '../fx/pop';
|
||||
import { WeatherController } from '@user/client-modules';
|
||||
import { Pop } from '../../../client-modules/src/render/legacy/pop';
|
||||
import { loading } from '@user/data-base';
|
||||
import { clip } from '@user/legacy-plugin-data';
|
||||
import { WeatherController } from '@user/client-modules';
|
||||
|
||||
loading.once('coreInit', () => {
|
||||
const shader = new Shader();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Animation, hyper, linear, power, sleep } from 'mutate-animate';
|
||||
import { Chase, ChaseData, IChaseController } from './chase';
|
||||
import { completeAchievement } from '@motajs/legacy-ui';
|
||||
// import { completeAchievement } from '@motajs/legacy-ui';
|
||||
import {
|
||||
Camera,
|
||||
CameraAnimation,
|
||||
@ -206,7 +206,7 @@ export function initChase(): IChaseController {
|
||||
core.removeFlag('chaseId');
|
||||
|
||||
if (success) {
|
||||
completeAchievement('challenge', 0);
|
||||
// completeAchievement('challenge', 0);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,5 +1 @@
|
||||
export * from './gameCanvas';
|
||||
export * from './halo';
|
||||
export * from './pointShader';
|
||||
export * from './pop';
|
||||
export * from './portal';
|
||||
|
@ -1,6 +1,3 @@
|
||||
export * from './boss';
|
||||
export * from './chase';
|
||||
export * from './fx';
|
||||
|
||||
export * from './fallback';
|
||||
export * from '../../client-modules/src/render/loopMap';
|
||||
|
@ -21,5 +21,4 @@ export * from './removeMap';
|
||||
export * from './replay';
|
||||
export * from './shop';
|
||||
export * from './skill';
|
||||
export * from '../../data-state/src/mechanism/skillTree';
|
||||
export * from './ui';
|
||||
|
@ -1,6 +1,6 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { HeroSkill } from '@/game/mechanism/misc';
|
||||
import { HeroSkill } from '@user/data-state';
|
||||
|
||||
// 所有的主动技能效果
|
||||
var ignoreInJump = {
|
||||
|
@ -2,3 +2,4 @@ export * from './patch';
|
||||
export * from './disposable';
|
||||
export * from './eventEmitter';
|
||||
export * from './resource';
|
||||
export * from './utils';
|
||||
|
48
packages/legacy-common/src/utils.ts
Normal file
48
packages/legacy-common/src/utils.ts
Normal file
@ -0,0 +1,48 @@
|
||||
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];
|
||||
}
|
@ -49,8 +49,13 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, onUpdated, ref, watch } from 'vue';
|
||||
import { ArrowsAltOutlined, DragOutlined } from '@ant-design/icons-vue';
|
||||
import { isMobile, useDrag, cancelGlobalDrag } from '../use';
|
||||
import { has, requireUniqueSymbol } from '../utils';
|
||||
import {
|
||||
isMobile,
|
||||
useDrag,
|
||||
cancelGlobalDrag,
|
||||
requireUniqueSymbol
|
||||
} from '../use';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
// todo: 重写
|
||||
|
||||
@ -200,10 +205,10 @@ function resize() {
|
||||
|
||||
if (!main) return;
|
||||
|
||||
if (has(props.width)) width.value = props.width;
|
||||
if (has(props.height)) height.value = props.height;
|
||||
if (has(props.left)) left.value = props.left;
|
||||
if (has(props.top)) top.value = props.top;
|
||||
if (!isNil(props.width)) width.value = props.width;
|
||||
if (!isNil(props.height)) height.value = props.height;
|
||||
if (!isNil(props.left)) left.value = props.left;
|
||||
if (!isNil(props.top)) top.value = props.top;
|
||||
|
||||
const beforeWidth = width.value;
|
||||
const beforeHeight = height.value;
|
||||
|
@ -9,7 +9,8 @@
|
||||
<script lang="tsx" setup>
|
||||
import { onMounted, onUnmounted, onUpdated } from 'vue';
|
||||
import { addAnimate, removeAnimate } from '../animateController';
|
||||
import { has, requireUniqueSymbol } from '../utils';
|
||||
import { requireUniqueSymbol } from '../use';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
const id = requireUniqueSymbol().toFixed(0);
|
||||
|
||||
@ -27,7 +28,7 @@ let ctx: CanvasRenderingContext2D;
|
||||
let drawFn: () => void;
|
||||
|
||||
function draw() {
|
||||
if (has(drawFn)) removeAnimate(drawFn);
|
||||
if (!isNil(drawFn)) removeAnimate(drawFn);
|
||||
|
||||
const cls = core.getClsFromId(props.id as AllIds);
|
||||
const frames = core.getAnimateFrames(cls);
|
||||
|
@ -26,8 +26,8 @@
|
||||
import { onMounted, onUpdated } from 'vue';
|
||||
import { LeftOutlined } from '@ant-design/icons-vue';
|
||||
import Scroll from './scroll.vue';
|
||||
import { isMobile } from '../use';
|
||||
import { has, requireUniqueSymbol } from '../utils';
|
||||
import { isMobile, requireUniqueSymbol } from '../use';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
@ -51,10 +51,10 @@ function resize() {
|
||||
left = document.getElementById(`column-left-${id}`) as HTMLDivElement;
|
||||
right = document.getElementById(`column-right-${id}`) as HTMLDivElement;
|
||||
|
||||
if (has(props.width) && !isMobile) main.style.width = `${props.width}%`;
|
||||
if (has(props.height)) main.style.height = `${props.height}%`;
|
||||
if (has(props.left)) left.style.flexBasis = `${props.left}%`;
|
||||
if (has(props.right)) right.style.flexBasis = `${props.right}%`;
|
||||
if (!isNil(props.width) && !isMobile) main.style.width = `${props.width}%`;
|
||||
if (!isNil(props.height)) main.style.height = `${props.height}%`;
|
||||
if (!isNil(props.left)) left.style.flexBasis = `${props.left}%`;
|
||||
if (!isNil(props.right)) right.style.flexBasis = `${props.right}%`;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
|
@ -16,7 +16,7 @@
|
||||
></BoxAnimate>
|
||||
<div
|
||||
class="special-text"
|
||||
v-if="has(enemy.special) && enemy.special.length > 0"
|
||||
v-if="!isNil(enemy.special) && enemy.special.length > 0"
|
||||
>
|
||||
<template v-for="(text, i) in enemy.showSpecial">
|
||||
<span v-if="i < (isMobile ? 1 : 2)"
|
||||
@ -110,10 +110,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { has } from '../utils';
|
||||
import BoxAnimate from '../components/boxAnimate.vue';
|
||||
import { isMobile } from '../use';
|
||||
import { ToShowEnemy } from '../tools/book';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
const props = defineProps<{
|
||||
enemy: ToShowEnemy;
|
||||
|
@ -4,11 +4,10 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted } from 'vue';
|
||||
import { requireUniqueSymbol } from '../utils';
|
||||
import { MinimapDrawer, getArea } from '../tools/fly';
|
||||
import { useDrag, useWheel } from '../use';
|
||||
import { useDrag, useWheel, requireUniqueSymbol } from '../use';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { mainSetting } from '../preset/ui';
|
||||
import { mainSetting } from '../preset/settingIns';
|
||||
|
||||
const props = defineProps<{
|
||||
action?: boolean;
|
||||
|
@ -12,8 +12,12 @@
|
||||
<script lang="ts" setup>
|
||||
import { sleep } from 'mutate-animate';
|
||||
import { onMounted, onUnmounted, onUpdated } from 'vue';
|
||||
import { cancelGlobalDrag, useDrag, useWheel } from '../use';
|
||||
import { requireUniqueSymbol } from '../utils';
|
||||
import {
|
||||
cancelGlobalDrag,
|
||||
useDrag,
|
||||
useWheel,
|
||||
requireUniqueSymbol
|
||||
} from '../use';
|
||||
|
||||
let main: HTMLDivElement;
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Component, shallowReactive } from 'vue';
|
||||
import { EventEmitter } from '@motajs/legacy-common';
|
||||
import { IGameUi, IUiController } from './interface';
|
||||
|
||||
interface FocusEvent<T> {
|
||||
focus: (before: T | null, after: T) => void;
|
||||
@ -128,7 +129,7 @@ interface MountedVBind {
|
||||
[x: string]: any;
|
||||
}
|
||||
|
||||
export class GameUi extends EventEmitter<GameUiEvent> {
|
||||
export class GameUi extends EventEmitter<GameUiEvent> implements IGameUi {
|
||||
static uiList: GameUi[] = [];
|
||||
|
||||
component: Component;
|
||||
@ -161,7 +162,10 @@ interface HoldOnController {
|
||||
end(noClosePanel?: boolean): void;
|
||||
}
|
||||
|
||||
export class UiController extends Focus<IndexedGameUi> {
|
||||
export class UiController
|
||||
extends Focus<IndexedGameUi>
|
||||
implements IUiController
|
||||
{
|
||||
static list: UiController[] = [];
|
||||
list: Record<string, GameUi> = {};
|
||||
num: number = 0;
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { logger } from '@motajs/common';
|
||||
import { deleteWith, ensureArray, parseCss, tip } from '@motajs/legacy-ui';
|
||||
import { ResponseBase } from '@motajs/client-base';
|
||||
import axios, { AxiosResponse, toFormData } from 'axios';
|
||||
import { VNode, h, shallowReactive } from 'vue';
|
||||
import { ensureArray, parseCss } from './utils';
|
||||
import { deleteWith } from '@motajs/legacy-common';
|
||||
import { tip } from './use';
|
||||
// /* @__PURE__ */ import { id, password } from '../../../../user';
|
||||
|
||||
type CSSObj = Partial<Record<CanParseCss, string>>;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { ensureArray, tip } from '@motajs/legacy-ui';
|
||||
import { ensureArray } from '../utils';
|
||||
import { sleep } from 'mutate-animate';
|
||||
import { logger } from '@motajs/common';
|
||||
import { tip } from '../use';
|
||||
|
||||
const { gl, gl2 } = checkSupport();
|
||||
|
||||
@ -54,10 +55,10 @@ type UniformFunc<
|
||||
type UniformBinderValue<N extends UniformBinderNum> = N extends 1
|
||||
? number
|
||||
: N extends 2
|
||||
? [number, number]
|
||||
: N extends 3
|
||||
? [number, number, number]
|
||||
: [number, number, number, number];
|
||||
? [number, number]
|
||||
: N extends 3
|
||||
? [number, number, number]
|
||||
: [number, number, number, number];
|
||||
|
||||
interface UniformBinder<
|
||||
N extends UniformBinderNum,
|
||||
|
@ -9,7 +9,8 @@ export * from './fx';
|
||||
export * from './animateController';
|
||||
export * from './controller';
|
||||
export * from './danmaku';
|
||||
export * from './mark';
|
||||
// export * from './mark';
|
||||
export * from './setting';
|
||||
export * from './use';
|
||||
export * from './utils';
|
||||
export * from './uiUtils';
|
||||
|
99
packages/legacy-ui/src/interface.ts
Normal file
99
packages/legacy-ui/src/interface.ts
Normal file
@ -0,0 +1,99 @@
|
||||
export interface IGameUi {
|
||||
id: string;
|
||||
symbol: symbol;
|
||||
}
|
||||
|
||||
export interface IMountedVBind {
|
||||
num: number;
|
||||
ui: IGameUi;
|
||||
controller: IUiController;
|
||||
[x: string]: any;
|
||||
}
|
||||
|
||||
interface HoldOnController {
|
||||
end(noClosePanel?: boolean): void;
|
||||
}
|
||||
|
||||
type UiVOn = Record<string, (param?: any) => void>;
|
||||
type UiVBind = Record<string, any>;
|
||||
|
||||
export interface IUiController {
|
||||
stack: any[];
|
||||
|
||||
/**
|
||||
* 设置为仅显示最后一个ui
|
||||
*/
|
||||
showEnd(): void;
|
||||
|
||||
/**
|
||||
* 设置为显示所有ui
|
||||
*/
|
||||
showAll(): void;
|
||||
|
||||
/**
|
||||
* 根据id获取到ui
|
||||
* @param id ui的id
|
||||
*/
|
||||
get(id: string): void;
|
||||
|
||||
/**
|
||||
* 暂时保持下一次删除ui不会导致ui整体被关闭,引起ui背景闪烁。
|
||||
* 例如可以用于道具栏,打开道具时就应当 holdOn,然后通过道具使用钩子来判断接下来是否要隐藏 app:
|
||||
* ```txt
|
||||
* hold on -> close -> use item -> hook -> stack.length === 0 ? end(): no action
|
||||
* ```
|
||||
*/
|
||||
holdOn(): HoldOnController;
|
||||
|
||||
/**
|
||||
* 关闭一个ui,注意如果不是平等模式,在其之后的ui都会同时关闭掉
|
||||
* @param num 要关闭的ui的唯一标识符
|
||||
*/
|
||||
close(num: number): void;
|
||||
|
||||
/**
|
||||
* 根据id关闭所有同id的ui,注意非平等模式下,会将第一个ui后的所有ui都关闭掉
|
||||
* @param id 要关闭的ui的id
|
||||
*/
|
||||
closeByName(id: string): void;
|
||||
|
||||
/**
|
||||
* 打开一个新的ui
|
||||
* @param id 要打开的ui的id
|
||||
* @param vOn 监听的事件
|
||||
* @param vBind 绑定的数据
|
||||
* @returns ui的唯一标识符
|
||||
*/
|
||||
open(id: string, vBind?: UiVBind, vOn?: UiVOn): void;
|
||||
|
||||
/**
|
||||
* 注册一个ui
|
||||
* @param id ui的id
|
||||
* @param ui 对应的GameUi实例
|
||||
*/
|
||||
register(...ui: IGameUi[]): void;
|
||||
|
||||
/**
|
||||
* 取消注册一个ui
|
||||
* @param id 要取消注册的ui的id
|
||||
*/
|
||||
unregister(...id: string[]): void;
|
||||
|
||||
/**
|
||||
* 根据ui的唯一标识符进行聚焦
|
||||
* @param num 要聚焦于的ui的唯一标识符
|
||||
*/
|
||||
focusByNum(num: number): void;
|
||||
|
||||
/**
|
||||
* 根据唯一标识符获取对应的ui
|
||||
* @param num ui的唯一标识符
|
||||
*/
|
||||
getByNum(num: number): void;
|
||||
|
||||
/**
|
||||
* 根据ui的唯一标识符来判断当前是否存在某个ui
|
||||
* @param id ui的唯一标识符
|
||||
*/
|
||||
hasName(id: string): void;
|
||||
}
|
@ -1,164 +0,0 @@
|
||||
import { IDamageEnemy } from '@motajs/types';
|
||||
import { fixedUi } from './preset/ui';
|
||||
import { tip } from './utils';
|
||||
import { ref, Ref } from 'vue';
|
||||
|
||||
export interface MarkInfo<T extends EnemyIds> {
|
||||
id: T;
|
||||
enemy: IDamageEnemy;
|
||||
/**
|
||||
* 提示模式,从低到高位数分别为:
|
||||
* 1. 踩临界时
|
||||
* 2. 能打过怪物时
|
||||
* 3. 小于勇士生命值的2/3时
|
||||
* 4. 小于勇士生命值的1/3时
|
||||
* 5. 零伤时
|
||||
* 6. 小于指定伤害时
|
||||
*/
|
||||
mode: number;
|
||||
/** 当前提示状态,提示模式的 2-6 */
|
||||
status: number;
|
||||
lastAtk: number;
|
||||
lastDamage: number;
|
||||
markDamage?: number;
|
||||
/** 数据更新用,取反更新标记信息 */
|
||||
update: Ref<boolean>;
|
||||
}
|
||||
|
||||
const uiMap = new Map<EnemyIds, number>();
|
||||
const marked: MarkInfo<EnemyIds>[] = [];
|
||||
|
||||
/**
|
||||
* 标记一个怪物,标记后的怪物会在勇士刚好能打过怪物时、伤害刚好小于勇士生命值的2/3和1/3时、踩到临界时提示
|
||||
* @param id 标记的怪物id
|
||||
*/
|
||||
export function markEnemy(id: EnemyIds) {
|
||||
if (hasMarkedEnemy(id)) return;
|
||||
const { DamageEnemy, getHeroStatusOn } = Mota.require('@user/data-state');
|
||||
const enemy = new DamageEnemy(core.material.enemys[id]);
|
||||
enemy.calAttribute();
|
||||
enemy.getRealInfo();
|
||||
|
||||
const info: MarkInfo<EnemyIds> = {
|
||||
id,
|
||||
enemy,
|
||||
mode: 0b011111,
|
||||
lastAtk: 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.require('@user/data-state');
|
||||
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('@user/data-base');
|
||||
hook.on('statusBarUpdate', () => {
|
||||
checkMarkedEnemy();
|
||||
});
|
@ -67,12 +67,12 @@
|
||||
>
|
||||
<span class="changable" :change="nowDamageChangable"
|
||||
><span style="font-family: 'FiraCode'">{{
|
||||
(nowDamage[0] as number) < 0 && !has(enemy.damage)
|
||||
(nowDamage[0] as number) < 0 && isNil(enemy.damage)
|
||||
? '=>'
|
||||
: ''
|
||||
}}</span
|
||||
>{{
|
||||
(nowDamage[0] as number) < 0 && !has(enemy.damage)
|
||||
(nowDamage[0] as number) < 0 && isNil(enemy.damage)
|
||||
? format(-nowDamage[0])
|
||||
: format(nowDamage[0])
|
||||
}}</span
|
||||
@ -96,8 +96,8 @@
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { detailInfo, getCriticalDamage, getDefDamage } from '../tools/book';
|
||||
import Chart, { ChartConfiguration } from 'chart.js/auto';
|
||||
import { has, setCanvasSize } from '../utils';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { setCanvasSize } from '../utils';
|
||||
import { debounce, isNil } from 'lodash-es';
|
||||
import { isMobile } from '../use';
|
||||
import { createChangable } from '../tools/common';
|
||||
|
||||
@ -113,10 +113,10 @@ const ceil = Math.ceil;
|
||||
|
||||
const x = ref<number>();
|
||||
const y = ref<number>();
|
||||
x.value = has(x.value)
|
||||
x.value = !isNil(x.value)
|
||||
? Math.round(x.value + core.bigmap.offsetX / 32)
|
||||
: void 0;
|
||||
y.value = has(y.value)
|
||||
y.value = !isNil(y.value)
|
||||
? Math.round(y.value + core.bigmap.offsetY / 32)
|
||||
: void 0;
|
||||
|
||||
|
@ -1,85 +0,0 @@
|
||||
<template>
|
||||
<div id="enemy-target">
|
||||
<div id="enemy-desc">
|
||||
<span>怪物描述</span>
|
||||
<Scroll id="enemy-desc-scroll">
|
||||
<span
|
||||
> {{
|
||||
enemy.enemy.enemy.description
|
||||
}}</span
|
||||
>
|
||||
</Scroll>
|
||||
</div>
|
||||
<a-divider dashed style="border-color: #ddd4"></a-divider>
|
||||
<div>
|
||||
<div id="mark-target">
|
||||
<span
|
||||
id="mark-info"
|
||||
:style="{ color: marked ? 'lightgreen' : 'lightcoral' }"
|
||||
>{{ marked ? '已标记该怪物' : '未标记该怪物' }}</span
|
||||
>
|
||||
<span class="button-text" @click.stop="mark">{{
|
||||
marked ? '取消标记该怪物' : '标记该怪物为目标'
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import { detailInfo } from '../tools/book';
|
||||
import { hasMarkedEnemy, markEnemy } from '../mark';
|
||||
|
||||
const enemy = detailInfo.enemy!;
|
||||
const marked = ref(hasMarkedEnemy(enemy.enemy.id));
|
||||
|
||||
function mark() {
|
||||
markEnemy(enemy.enemy.id);
|
||||
marked.value = hasMarkedEnemy(enemy.enemy.id);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
#enemy-target {
|
||||
width: 100%;
|
||||
font-size: 160%;
|
||||
}
|
||||
|
||||
#enemy-desc {
|
||||
width: 100%;
|
||||
height: 30vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#enemy-desc-scroll {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#mark-target {
|
||||
margin-top: 10%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
font-size: 3.3vh;
|
||||
}
|
||||
|
||||
#mark-info {
|
||||
transition: color 0.2s linear;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
#enemy-desc {
|
||||
font-size: 70%;
|
||||
}
|
||||
|
||||
#mark-target {
|
||||
font-size: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,6 +1,6 @@
|
||||
import { Danmaku } from '../danmaku';
|
||||
import { Component, h } from 'vue';
|
||||
import { mainSetting } from './ui';
|
||||
import { mainSetting } from './settingIns';
|
||||
import { getIconHeight } from '../utils';
|
||||
import { BoxAnimate } from '../components';
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { debounce } from 'lodash-es';
|
||||
import { fixedUi, mainUi } from './ui';
|
||||
import { ref } from 'vue';
|
||||
import { sleep } from 'mutate-animate';
|
||||
|
||||
@ -16,6 +15,7 @@ const showFixed = debounce((block: Block) => {
|
||||
if (!e) return;
|
||||
const enemy = core.status.thisMap.enemy.get(block.x, block.y);
|
||||
if (!enemy) return;
|
||||
const { fixedUi } = Mota.require('@motajs/legacy-ui');
|
||||
fixedUi.open(
|
||||
'fixed',
|
||||
{ enemy, close, loc: [cx, cy], hovered },
|
||||
@ -29,6 +29,7 @@ const showFixed = debounce((block: Block) => {
|
||||
const closeFixed = () => {
|
||||
close.value = true;
|
||||
sleep(200).then(() => {
|
||||
const { fixedUi } = Mota.require('@motajs/legacy-ui');
|
||||
fixedUi.closeByName('fixed');
|
||||
close.value = false;
|
||||
});
|
||||
@ -58,6 +59,7 @@ gameListener.on('mouseMove', e => {
|
||||
});
|
||||
|
||||
hook.once('mounted', () => {
|
||||
const { mainUi } = Mota.require('@motajs/legacy-ui');
|
||||
mainUi.on('start', () => {
|
||||
showFixed.cancel();
|
||||
closeFixed();
|
||||
|
@ -1,15 +1,11 @@
|
||||
import { KeyCode } from '@motajs/client-base';
|
||||
import { gameKey, HotkeyJSON } from '@motajs/system-action';
|
||||
import {
|
||||
openDanmakuPoster,
|
||||
tip,
|
||||
hasMarkedEnemy,
|
||||
markEnemy,
|
||||
unmarkEnemy
|
||||
} from '@motajs/legacy-ui';
|
||||
import { hovered } from './fixed';
|
||||
import { mainUi } from './ui';
|
||||
import { mainUi } from './uiIns';
|
||||
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');
|
||||
|
||||
@ -506,9 +502,9 @@ gameKey
|
||||
.realize('mark', () => {
|
||||
const cls = hovered?.event.cls;
|
||||
if (cls === 'enemys' || cls === 'enemy48') {
|
||||
const id = hovered!.event.id as EnemyIds;
|
||||
if (hasMarkedEnemy(id)) unmarkEnemy(id);
|
||||
else markEnemy(id);
|
||||
// const id = hovered!.event.id as EnemyIds;
|
||||
// if (hasMarkedEnemy(id)) unmarkEnemy(id);
|
||||
// else markEnemy(id);
|
||||
}
|
||||
})
|
||||
.realize('special', () => {
|
||||
|
@ -4,3 +4,5 @@ export * from './danmaku';
|
||||
export * from './fixed';
|
||||
export * from './hotkey';
|
||||
export * from './keyboard';
|
||||
export * from './uiIns';
|
||||
export * from './settingIns';
|
||||
|
3
packages/legacy-ui/src/preset/settingIns.ts
Normal file
3
packages/legacy-ui/src/preset/settingIns.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { MotaSetting } from '../setting';
|
||||
|
||||
export const mainSetting = new MotaSetting();
|
@ -1,6 +1,6 @@
|
||||
import type { SettingComponent, SettingComponentProps } from '../setting';
|
||||
import { Button, InputNumber, Radio } from 'ant-design-vue';
|
||||
import { mainUi } from './ui';
|
||||
import { mainUi } from './uiIns';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
|
||||
interface Components {
|
||||
|
@ -1,50 +1,13 @@
|
||||
import { GameStorage, VirtualKey } from '@motajs/legacy-system';
|
||||
import {
|
||||
createSettingComponents,
|
||||
GameUi,
|
||||
isMobile,
|
||||
MotaSetting,
|
||||
triggerFullscreen,
|
||||
UI,
|
||||
UiController
|
||||
} from '@motajs/legacy-ui';
|
||||
import { GameStorage } from '@motajs/legacy-system';
|
||||
import { createSettingComponents } from './settings';
|
||||
import { isMobile } from '../use';
|
||||
import { MotaSetting } from '../setting';
|
||||
import { triggerFullscreen } from '../utils';
|
||||
import settingsText from '../data/settings.json';
|
||||
import { fixedUi, mainUi } from './uiIns';
|
||||
import { mainSetting } from './settingIns';
|
||||
|
||||
//#region legacy-ui
|
||||
export const mainUi = new UiController();
|
||||
mainUi.register(
|
||||
new GameUi('book', UI.Book),
|
||||
new GameUi('toolbox', UI.Toolbox),
|
||||
new GameUi('equipbox', UI.Equipbox),
|
||||
new GameUi('settings', UI.Settings),
|
||||
new GameUi('desc', UI.Desc),
|
||||
new GameUi('skill', UI.Skill),
|
||||
new GameUi('skillTree', UI.SkillTree),
|
||||
new GameUi('fly', UI.Fly),
|
||||
new GameUi('fixedDetail', UI.FixedDetail),
|
||||
new GameUi('shop', UI.Shop),
|
||||
new GameUi('achievement', UI.Achievement),
|
||||
new GameUi('hotkey', UI.Hotkey),
|
||||
new GameUi('toolEditor', UI.ToolEditor),
|
||||
new GameUi('virtualKey', VirtualKey)
|
||||
// todo: 把游戏主 div 加入到 mainUi 里面
|
||||
);
|
||||
mainUi.showAll();
|
||||
|
||||
export const fixedUi = new UiController(true);
|
||||
fixedUi.register(
|
||||
new GameUi('markedEnemy', UI.Marked),
|
||||
new GameUi('fixed', UI.Fixed),
|
||||
new GameUi('chapter', UI.Chapter),
|
||||
new GameUi('completeAchi', UI.CompleteAchi),
|
||||
new GameUi('start', UI.Start),
|
||||
new GameUi('toolbar', UI.Toolbar),
|
||||
new GameUi('load', UI.Load),
|
||||
new GameUi('danmaku', UI.Danmaku),
|
||||
new GameUi('danmakuEditor', UI.DanmakuEditor),
|
||||
new GameUi('tips', UI.Tips)
|
||||
);
|
||||
fixedUi.showAll();
|
||||
|
||||
const { hook } = Mota.require('@user/data-base');
|
||||
hook.once('mounted', () => {
|
||||
@ -84,7 +47,6 @@ hook.once('mounted', () => {
|
||||
|
||||
const COM = createSettingComponents();
|
||||
|
||||
export const mainSetting = new MotaSetting();
|
||||
// 添加不参与全局存储的设置
|
||||
MotaSetting.noStorage.push('action.autoSkill', 'screen.fullscreen');
|
||||
|
||||
|
38
packages/legacy-ui/src/preset/uiIns.ts
Normal file
38
packages/legacy-ui/src/preset/uiIns.ts
Normal file
@ -0,0 +1,38 @@
|
||||
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('toolEditor', UI.ToolEditor),
|
||||
new GameUi('virtualKey', VirtualKey)
|
||||
// todo: 把游戏主 div 加入到 mainUi 里面
|
||||
);
|
||||
mainUi.showAll();
|
||||
|
||||
export const fixedUi = new UiController(true);
|
||||
fixedUi.register(
|
||||
new GameUi('markedEnemy', UI.Marked),
|
||||
new GameUi('fixed', UI.Fixed),
|
||||
new GameUi('chapter', UI.Chapter),
|
||||
new GameUi('completeAchi', UI.CompleteAchi),
|
||||
new GameUi('start', UI.Start),
|
||||
new GameUi('toolbar', UI.Toolbar),
|
||||
new GameUi('load', UI.Load),
|
||||
new GameUi('danmaku', UI.Danmaku),
|
||||
new GameUi('danmakuEditor', UI.DanmakuEditor),
|
||||
new GameUi('tips', UI.Tips)
|
||||
);
|
||||
fixedUi.showAll();
|
@ -1,9 +1,6 @@
|
||||
import { FunctionalComponent, reactive } from 'vue';
|
||||
import { EventEmitter } from '@motajs/legacy-common';
|
||||
import { has } from './utils';
|
||||
import { createSettingComponents } from './preset';
|
||||
|
||||
const COM = createSettingComponents();
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
export interface SettingComponentProps {
|
||||
item: MotaSettingItem;
|
||||
@ -85,7 +82,7 @@ export class MotaSetting extends EventEmitter<SettingEvent> {
|
||||
key: string,
|
||||
name: string,
|
||||
value: MotaSettingType,
|
||||
com: SettingComponent = COM.Default,
|
||||
com: SettingComponent,
|
||||
step: [number, number, number] = [0, 100, 1]
|
||||
) {
|
||||
const setting: MotaSettingItem = {
|
||||
@ -162,12 +159,12 @@ export class MotaSetting extends EventEmitter<SettingEvent> {
|
||||
defaultValue?: T
|
||||
): T | undefined {
|
||||
const setting = this.getSetting(key);
|
||||
if (!has(setting) && !has(defaultValue)) return void 0;
|
||||
if (isNil(setting) && isNil(defaultValue)) return void 0;
|
||||
if (setting instanceof MotaSetting) {
|
||||
if (has(setting)) return defaultValue;
|
||||
if (!isNil(setting)) return defaultValue;
|
||||
return void 0;
|
||||
} else {
|
||||
return has(setting) ? (setting.value as T) : (defaultValue as T);
|
||||
return !isNil(setting) ? (setting.value as T) : (defaultValue as T);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,120 +0,0 @@
|
||||
import list from '../data/achievement.json';
|
||||
import { achiDict, checkCompletionAchievement } from './completion';
|
||||
import { changeLocalStorage, has } from '../utils';
|
||||
import { fixedUi } from '../preset';
|
||||
|
||||
type AchievementList = typeof list;
|
||||
export type AchievementType = keyof AchievementList;
|
||||
|
||||
type AchievementData = Record<AchievementType, boolean[]>;
|
||||
|
||||
export interface Achievement {
|
||||
name: string;
|
||||
text: string[];
|
||||
point: number;
|
||||
hide?: string;
|
||||
progress?: string;
|
||||
percent?: boolean;
|
||||
}
|
||||
|
||||
export default function init() {
|
||||
return { completeAchievement, hasCompletedAchievement, addMountSign };
|
||||
}
|
||||
|
||||
export const totalPoint = Object.values(list)
|
||||
.map((v: Achievement[]) =>
|
||||
v.reduce((prev, curr) => {
|
||||
return curr.point + prev;
|
||||
}, 0)
|
||||
)
|
||||
.reduce((prev, curr) => prev + curr);
|
||||
|
||||
/**
|
||||
* 完成一个成就
|
||||
* @param type 成就类型
|
||||
* @param index 成就索引
|
||||
*/
|
||||
export function completeAchievement(type: AchievementType, index: number) {
|
||||
if (flags.debug || hasCompletedAchievement(type, index)) return;
|
||||
changeLocalStorage<AchievementData>(
|
||||
'achievement',
|
||||
data => {
|
||||
data[type][index] = true;
|
||||
return data;
|
||||
},
|
||||
{
|
||||
normal: [],
|
||||
challenge: [],
|
||||
explore: []
|
||||
}
|
||||
);
|
||||
if (type === 'explore' && !Object.values(achiDict).includes(index)) {
|
||||
checkCompletionAchievement();
|
||||
}
|
||||
fixedUi.open('completeAchi', {
|
||||
complete: `${type},${index}`
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否完成了某个成就
|
||||
* @param type 成就类型
|
||||
* @param index 成就索引
|
||||
*/
|
||||
export function hasCompletedAchievement(type: AchievementType, index: number) {
|
||||
let data = core.getLocalStorage<AchievementData>('achievement');
|
||||
if (!has(data)) {
|
||||
const d = {
|
||||
normal: [],
|
||||
challenge: [],
|
||||
explore: []
|
||||
};
|
||||
data = d;
|
||||
core.setLocalStorage('achievement', d);
|
||||
}
|
||||
return data[type][index] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前成就点数
|
||||
*/
|
||||
export function getNowPoint() {
|
||||
let res = 0;
|
||||
for (const [type, achi] of Object.entries(list)) {
|
||||
achi.forEach((v, i) => {
|
||||
if (hasCompletedAchievement(type as AchievementType, i)) {
|
||||
res += v.point;
|
||||
}
|
||||
});
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// ----- 各个成就相关的函数
|
||||
|
||||
/**
|
||||
* 山路木牌
|
||||
* @param id 木牌id
|
||||
*/
|
||||
export function addMountSign(id: number) {
|
||||
if (flags.debug) return;
|
||||
if (
|
||||
!core.getLocalStorage(`mountSign_${id}`, false) &&
|
||||
!hasCompletedAchievement('explore', 1)
|
||||
) {
|
||||
changeLocalStorage(
|
||||
'mountSign',
|
||||
n => {
|
||||
if (n + 1 >= 5) {
|
||||
completeAchievement('explore', 1);
|
||||
for (const i of [1, 2, 3, 4, 5]) {
|
||||
core.removeLocalStorage(`mountSign_${i}`);
|
||||
}
|
||||
}
|
||||
return n + 1;
|
||||
},
|
||||
0
|
||||
);
|
||||
core.setLocalStorage(`mountSign_${id}`, true);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { has } from '../utils';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { IDamageEnemy } from '@motajs/types';
|
||||
|
||||
export interface CurrentEnemy {
|
||||
@ -73,7 +73,7 @@ export function getDefDamage(
|
||||
|
||||
if (res.length === 0) {
|
||||
origin = dam.damage;
|
||||
if (has(origin)) {
|
||||
if (!isNil(origin)) {
|
||||
res.push([addDef + i * ratio, origin]);
|
||||
last = origin;
|
||||
}
|
||||
@ -113,7 +113,7 @@ export function getCriticalDamage(
|
||||
|
||||
if (res.length === 0) {
|
||||
origin = dam.damage;
|
||||
if (has(origin)) {
|
||||
if (!isNil(origin)) {
|
||||
res.push([addAtk + i * ratio, origin]);
|
||||
last = origin;
|
||||
}
|
||||
|
@ -1,113 +0,0 @@
|
||||
import {
|
||||
AchievementType,
|
||||
completeAchievement,
|
||||
hasCompletedAchievement
|
||||
} from './achievement';
|
||||
import { changeLocalStorage } from '../utils';
|
||||
import list from '../data/achievement.json';
|
||||
|
||||
export const floors: Record<number, FloorIds[]> = {
|
||||
1: ['MT0', 'tower7']
|
||||
};
|
||||
const achis: Record<number, Record<AchievementType, number[]>> = {
|
||||
1: {
|
||||
normal: [0, 1],
|
||||
challenge: [0],
|
||||
explore: [1]
|
||||
}
|
||||
};
|
||||
|
||||
export const achiDict: Record<number, number> = {
|
||||
1: 0
|
||||
};
|
||||
|
||||
export function init() {
|
||||
Object.values(floors).forEach((v, i) => {
|
||||
const from = core.floorIds.indexOf(v[0]);
|
||||
const to = core.floorIds.indexOf(v[1]);
|
||||
const all = core.floorIds.slice(from, to + 1);
|
||||
floors[i + 1] = all;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查所有到达过的楼层,用于成就的计算
|
||||
*/
|
||||
export function checkVisitedFloor() {
|
||||
changeLocalStorage<Partial<Record<FloorIds, boolean>>>(
|
||||
'visitedFloor',
|
||||
data => {
|
||||
let needUpdate = false;
|
||||
core.floorIds.forEach(v => {
|
||||
if (core.hasVisitedFloor(v)) {
|
||||
data[v] = true;
|
||||
needUpdate = true;
|
||||
}
|
||||
});
|
||||
if (needUpdate) {
|
||||
checkCompletionAchievement();
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个章节的完成度
|
||||
* @param num 章节
|
||||
*/
|
||||
export function getChapterCompletion(num: number) {
|
||||
if (!achis[num]) return 0;
|
||||
let res = 0;
|
||||
const all = floors[num];
|
||||
const achiNum = Object.values(achis[num]).reduce(
|
||||
(pre, cur) => pre + cur.length,
|
||||
0
|
||||
);
|
||||
|
||||
// 计算到达过的楼层
|
||||
let visitedFloor = 0;
|
||||
const visited = core.getLocalStorage<Partial<Record<FloorIds, boolean>>>(
|
||||
'visitedFloor',
|
||||
{}
|
||||
);
|
||||
all.forEach(v => {
|
||||
if (visited[v]) visitedFloor++;
|
||||
});
|
||||
const floorRatio = all.length / (all.length + achiNum);
|
||||
const floorPoint = (floorRatio * visitedFloor) / all.length;
|
||||
|
||||
let completedPoint = 0;
|
||||
let totalPoint = 0;
|
||||
|
||||
// 计算成就,占比按成就点走
|
||||
for (const [type, achi] of Object.entries(achis[num]) as [
|
||||
AchievementType,
|
||||
number[]
|
||||
][]) {
|
||||
achi.forEach(v => {
|
||||
totalPoint += list[type][v].point;
|
||||
if (hasCompletedAchievement(type, v)) {
|
||||
completedPoint += list[type][v].point;
|
||||
}
|
||||
});
|
||||
}
|
||||
const achiPoint = (completedPoint / totalPoint) * (1 - floorRatio);
|
||||
|
||||
res = floorPoint + achiPoint;
|
||||
|
||||
return Math.floor(res * 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查完成度成就是否完成
|
||||
*/
|
||||
export function checkCompletionAchievement() {
|
||||
[1].forEach(v => {
|
||||
if (getChapterCompletion(v) >= 100) {
|
||||
completeAchievement('explore', achiDict[v]);
|
||||
}
|
||||
});
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import { getStatusLabel, has } from '../utils';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { getStatusLabel } from '../utils';
|
||||
|
||||
/**
|
||||
* 获取所有装备
|
||||
@ -71,7 +72,7 @@ export function getNowStatus(nowEquip?: Equip, onCol: boolean = false) {
|
||||
else status = getHeroStatusOn(v)?.toString();
|
||||
|
||||
let add = 0;
|
||||
if (has(nowEquip)) {
|
||||
if (!isNil(nowEquip)) {
|
||||
add += Math.floor(
|
||||
(nowEquip.value[v] ?? 0) * core.getBuff(v)
|
||||
);
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { mainSetting } from '../preset/ui';
|
||||
import { downloadCanvasImage, has, tip } from '../utils';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { mainSetting } from '../preset/settingIns';
|
||||
import { tip } from '../use';
|
||||
import { downloadCanvasImage } from '../utils';
|
||||
|
||||
type BFSFromString = `${FloorIds},${number},${number},${Dir}`;
|
||||
type BFSToString = `${FloorIds},${number},${number}`;
|
||||
@ -163,7 +165,7 @@ export function getMapData(
|
||||
noCache: boolean = false
|
||||
): MapBFSResult {
|
||||
if (!floorId) return { maps: [], link: {} };
|
||||
if (has(bfsCache[floorId]) && !noCache) return bfsCache[floorId]!;
|
||||
if (!isNil(bfsCache[floorId]) && !noCache) return bfsCache[floorId]!;
|
||||
|
||||
const queue = [floorId];
|
||||
const used: Partial<Record<FloorIds, boolean>> = {
|
||||
|
@ -1,7 +1,10 @@
|
||||
export * from './achievement';
|
||||
// import { init } from './achievement';
|
||||
|
||||
// init();
|
||||
|
||||
// export * from './achievement';
|
||||
export * from './book';
|
||||
export * from './common';
|
||||
export * from './completion';
|
||||
export * from './equipbox';
|
||||
export * from './fixed';
|
||||
export * from './fly';
|
||||
|
@ -1,340 +0,0 @@
|
||||
<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';
|
||||
import { mainUi } from '../preset';
|
||||
|
||||
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() {
|
||||
mainUi.close(props.num);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
#achievement {
|
||||
width: 90vh;
|
||||
height: 90vh;
|
||||
font-family: 'normal';
|
||||
font-size: 150%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#divider {
|
||||
margin: 1% 0;
|
||||
border-color: #ddd4;
|
||||
}
|
||||
|
||||
#tools {
|
||||
height: 5vh;
|
||||
font-size: 3.2vh;
|
||||
}
|
||||
|
||||
#column {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
margin-top: 3%;
|
||||
font-size: 130%;
|
||||
}
|
||||
|
||||
.list-scroll {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#list {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
#achievement-list {
|
||||
position: relative;
|
||||
width: 300%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
transition: left 0.4s ease;
|
||||
}
|
||||
|
||||
.achievement-one {
|
||||
width: 90vh;
|
||||
}
|
||||
|
||||
.list-div {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list-one {
|
||||
width: 70%;
|
||||
|
||||
.list-content {
|
||||
height: 18vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border: 2px double rgba(132, 132, 132, 0.17);
|
||||
border-radius: 10px;
|
||||
margin: 2% 0 2.5% 0;
|
||||
background-color: rgba(59, 59, 59, 0.281);
|
||||
}
|
||||
|
||||
.list-content[complete='true'] {
|
||||
background-color: rgba(239, 255, 63, 0.205);
|
||||
}
|
||||
|
||||
.list-name {
|
||||
border-bottom: 1px solid #ddd4;
|
||||
}
|
||||
|
||||
.list-text {
|
||||
font-size: 100%;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.list-end {
|
||||
width: 90%;
|
||||
height: 95%;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
font-size: 90%;
|
||||
|
||||
.end-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.complete {
|
||||
color: lightcoral;
|
||||
}
|
||||
|
||||
.complete[complete='true'] {
|
||||
color: lightgreen;
|
||||
}
|
||||
}
|
||||
|
||||
.list-progress {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.progress {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#total-progress {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
#point-progress {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#point-number {
|
||||
font-size: 70%;
|
||||
margin-left: 2%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
#achievement {
|
||||
width: 90vw;
|
||||
height: 90vh;
|
||||
}
|
||||
|
||||
.list-one {
|
||||
width: 90%;
|
||||
|
||||
.list-content {
|
||||
height: 15vh;
|
||||
}
|
||||
|
||||
.list-end {
|
||||
margin-bottom: 0.8vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -41,21 +41,17 @@
|
||||
import { onUnmounted, ref } from 'vue';
|
||||
import EnemyOne from '../components/enemyOne.vue';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import { has } from '../utils';
|
||||
import BookDetail from './bookDetail.vue';
|
||||
import { LeftOutlined } from '@ant-design/icons-vue';
|
||||
import { ToShowEnemy, detailInfo } from '../tools/book';
|
||||
import { getDetailedEnemy } from '../tools/fixed';
|
||||
import { GameUi } from '../controller';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import { mainSetting } from '../preset/ui';
|
||||
import { mainSetting } from '../preset/settingIns';
|
||||
import { isMobile } from '../use';
|
||||
import { IMountedVBind } from '../interface';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
}>();
|
||||
const props = defineProps<IMountedVBind>();
|
||||
|
||||
const floorId =
|
||||
// @ts-ignore
|
||||
@ -122,12 +118,12 @@ async function show() {
|
||||
* 退出怪物手册
|
||||
*/
|
||||
async function exit() {
|
||||
const hold = mainUi.holdOn();
|
||||
mainUi.close(props.num);
|
||||
const hold = props.controller.holdOn();
|
||||
props.controller.close(props.num);
|
||||
if (core.events.recoverEvents(core.status.event.interval)) {
|
||||
hold.end(true);
|
||||
return;
|
||||
} else if (has(core.status.event.ui)) {
|
||||
} else if (!isNil(core.status.event.ui)) {
|
||||
core.status.boxAnimateObjs = [];
|
||||
// @ts-ignore
|
||||
core.ui._drawViewMaps(core.status.event.ui);
|
||||
|
@ -22,7 +22,7 @@
|
||||
:from-book="fromBook"
|
||||
v-else-if="panel === 'critical'"
|
||||
></EnemyCritical>
|
||||
<EnemyTarget v-else-if="panel === 'target'"></EnemyTarget>
|
||||
<!-- <EnemyTarget v-else-if="panel === 'target'"></EnemyTarget> -->
|
||||
</Transition>
|
||||
<div id="detail-more">
|
||||
<Transition name="detail">
|
||||
@ -35,8 +35,8 @@
|
||||
id="enemy-target"
|
||||
class="button-text more"
|
||||
@click="changePanel($event, 'target')"
|
||||
><LeftOutlined /> 怪物更多信息</span
|
||||
>
|
||||
><LeftOutlined />
|
||||
</span>
|
||||
<span
|
||||
id="critical-more"
|
||||
class="button-text more"
|
||||
@ -81,7 +81,7 @@ import { useDrag } from '../use';
|
||||
import EnemySpecial from '../panel/enemySpecial.vue';
|
||||
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
|
||||
import EnemyCritical from '../panel/enemyCritical.vue';
|
||||
import EnemyTarget from '../panel/enemyTarget.vue';
|
||||
// import EnemyTarget from '../panel/enemyTarget.vue';
|
||||
import { detailInfo } from '../tools/book';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
|
||||
|
@ -1,22 +1,17 @@
|
||||
<template>
|
||||
<div id="chapter">
|
||||
<canvas id="chapter-back"></canvas>
|
||||
<span id="chapter-text">{{ chapter }}</span>
|
||||
<span id="chapter-text">{{ props.chapter }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Animation, hyper, sleep } from 'mutate-animate';
|
||||
import { onMounted } from 'vue';
|
||||
import { has } from '../utils';
|
||||
import { GameUi } from '../controller';
|
||||
import { fixedUi } from '../preset/ui';
|
||||
import { IMountedVBind } from '../interface';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
chapter: string;
|
||||
}>();
|
||||
const props = defineProps<IMountedVBind>();
|
||||
|
||||
let can: HTMLCanvasElement;
|
||||
let ctx: CanvasRenderingContext2D;
|
||||
@ -50,14 +45,14 @@ onMounted(async () => {
|
||||
let started = false;
|
||||
|
||||
ani.ticker.add(time => {
|
||||
if (!has(time) || isNaN(time)) return;
|
||||
if (isNil(time) || isNaN(time)) return;
|
||||
if (!started) {
|
||||
started = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (time >= 4050) {
|
||||
fixedUi.close(props.num);
|
||||
props.controller.close(props.num);
|
||||
ani.ticker.destroy();
|
||||
}
|
||||
|
||||
|
@ -1,113 +0,0 @@
|
||||
<template>
|
||||
<Box id="complete-box">
|
||||
<div id="complete">
|
||||
<span>完成成就 {{ achi.name }}</span>
|
||||
<a-progress
|
||||
id="progress"
|
||||
:percent="progress * 100"
|
||||
:strokeColor="{
|
||||
'0%': '#108ee9',
|
||||
'100%': '#87d068'
|
||||
}"
|
||||
:strokeWidth="height / 200"
|
||||
:showInfo="false"
|
||||
></a-progress>
|
||||
<span id="point-number">成就点: {{ now }} / {{ totalPoint }}</span>
|
||||
</div>
|
||||
</Box>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { sleep, Ticker } from 'mutate-animate';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import Box from '../components/box.vue';
|
||||
import list from '../data/achievement.json';
|
||||
import { AchievementType, getNowPoint, totalPoint } from '../tools/achievement';
|
||||
import { GameUi } from '../controller';
|
||||
import { fixedUi } from '../preset/ui';
|
||||
|
||||
const height = window.innerHeight;
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
complete: string;
|
||||
}>();
|
||||
|
||||
const c = props.complete.split(',');
|
||||
const type = c[0] as AchievementType;
|
||||
const index = parseInt(c[1]);
|
||||
|
||||
const achi = list[type][index];
|
||||
const point = achi.point;
|
||||
|
||||
const nowPoint = getNowPoint() - point;
|
||||
const now = ref(nowPoint);
|
||||
const progress = computed(() => now.value / totalPoint);
|
||||
|
||||
onMounted(async () => {
|
||||
await sleep(500);
|
||||
const ticker = new Ticker();
|
||||
const time = Date.now();
|
||||
ticker.add(() => {
|
||||
const nowTime = Date.now();
|
||||
if (nowTime - time > 1000) {
|
||||
now.value = nowPoint + point;
|
||||
ticker.destroy();
|
||||
}
|
||||
const ratio = (nowTime - time) / 1000;
|
||||
now.value = Math.floor(nowPoint + point * ratio);
|
||||
});
|
||||
await sleep(4600);
|
||||
fixedUi.close(props.num);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
#complete-box {
|
||||
width: 30vw;
|
||||
height: 13vh;
|
||||
left: 35vw;
|
||||
position: fixed;
|
||||
background-color: #000d;
|
||||
animation: ani 5s ease 0s 1 forwards running;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
#complete {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: 'normal';
|
||||
font-size: 2.2vh;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#progress {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
@keyframes ani {
|
||||
0% {
|
||||
top: -30vh;
|
||||
}
|
||||
20% {
|
||||
top: 4vh;
|
||||
}
|
||||
80% {
|
||||
top: 4vh;
|
||||
}
|
||||
100% {
|
||||
top: -30vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
#complete-box {
|
||||
width: 90vw;
|
||||
left: 5%;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -30,7 +30,7 @@
|
||||
import { nextTick, onUnmounted, reactive, watch } from 'vue';
|
||||
import { Danmaku } from '../danmaku';
|
||||
import { LikeFilled } from '@ant-design/icons-vue';
|
||||
import { mainSetting } from '../preset/ui';
|
||||
import { mainSetting } from '../preset/settingIns';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
interface ElementMap {
|
||||
@ -205,10 +205,7 @@ onUnmounted(() => {});
|
||||
}
|
||||
|
||||
.danmaku-info {
|
||||
text-shadow:
|
||||
1px 1px 1px black,
|
||||
1px -1px 1px black,
|
||||
-1px 1px 1px black,
|
||||
text-shadow: 1px 1px 1px black, 1px -1px 1px black, -1px 1px 1px black,
|
||||
-1px -1px 1px black;
|
||||
}
|
||||
|
||||
|
@ -159,21 +159,17 @@ import {
|
||||
UpOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { Danmaku } from '../danmaku';
|
||||
import { GameUi } from '../controller';
|
||||
import { sleep } from 'mutate-animate';
|
||||
import { fixedUi } from '../preset/ui';
|
||||
import { calStringSize, tip } from '../utils';
|
||||
import { calStringSize, stringifyCSS, parseCss, getIconHeight } from '../utils';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { stringifyCSS, parseCss, getIconHeight } from '../utils';
|
||||
import { logger, LogLevel } from '@motajs/common';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import BoxAnimate from '../components/boxAnimate.vue';
|
||||
import { IMountedVBind } from '../interface';
|
||||
import { tip } from '../use';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
}>();
|
||||
const props = defineProps<IMountedVBind>();
|
||||
|
||||
const frequentlyIcon: (AllIds | 'hero' | `X${number}`)[] = [
|
||||
'hero',
|
||||
@ -301,7 +297,7 @@ function close() {
|
||||
mainDiv.classList.remove('danmaku-startup');
|
||||
mainDiv.classList.add('danmaku-close');
|
||||
sleep(200).then(() => {
|
||||
fixedUi.close(props.num);
|
||||
props.controller.close(props.num);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -22,21 +22,17 @@ import { computed, onUnmounted, ref } from 'vue';
|
||||
import desc from '../data/desc.json';
|
||||
import { splitText } from '../utils';
|
||||
import Colomn from '../components/colomn.vue';
|
||||
import { GameUi } from '../controller';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import { IMountedVBind } from '../interface';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
}>();
|
||||
const props = defineProps<IMountedVBind>();
|
||||
|
||||
type DescKey = keyof typeof desc;
|
||||
|
||||
const selected = ref(Object.keys(desc)[0] as DescKey);
|
||||
|
||||
function exit() {
|
||||
mainUi.close(props.num);
|
||||
props.controller.close(props.num);
|
||||
}
|
||||
|
||||
const content = computed(() => {
|
||||
|
@ -125,8 +125,8 @@
|
||||
<BoxAnimate
|
||||
:id="
|
||||
isCol
|
||||
? (equiped[selected] ?? 'none')
|
||||
: (toShow[selected]?.[0] ?? 'none')
|
||||
? equiped[selected] ?? 'none'
|
||||
: toShow[selected]?.[0] ?? 'none'
|
||||
"
|
||||
></BoxAnimate>
|
||||
<span>{{ equip.name }}</span>
|
||||
@ -183,18 +183,14 @@ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import { getAddStatus, getEquips, getNowStatus } from '../tools/equipbox';
|
||||
import BoxAnimate from '../components/boxAnimate.vue';
|
||||
import { has, tip, type } from '../utils';
|
||||
import { cancelGlobalDrag, isMobile, useDrag } from '../use';
|
||||
import { type, getStatusLabel } from '../utils';
|
||||
import { cancelGlobalDrag, isMobile, tip, useDrag } from '../use';
|
||||
import { hyper } from 'mutate-animate';
|
||||
import { GameUi } from '../controller';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { getStatusLabel } from '../utils';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import { IMountedVBind } from '../interface';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
}>();
|
||||
const props = defineProps<IMountedVBind>();
|
||||
|
||||
const equips = ref(getEquips());
|
||||
const col = ref('all');
|
||||
@ -242,10 +238,10 @@ const equip = computed(() => {
|
||||
if (isCol.value) {
|
||||
const id = equiped.value[selected.value];
|
||||
const e = core.material.items[id];
|
||||
if (!has(e)) return none;
|
||||
if (isNil(e)) return none;
|
||||
return e;
|
||||
}
|
||||
if (!has(index)) return none;
|
||||
if (isNil(index)) return none;
|
||||
return all[index[0]];
|
||||
});
|
||||
|
||||
@ -276,7 +272,7 @@ const toShow = computed(() => {
|
||||
const e = all[v[0]].equip!;
|
||||
const t = e.type;
|
||||
if (sortNorm !== 'none') {
|
||||
if (!has(e[sortBy][sortNorm])) return false;
|
||||
if (isNil(e[sortBy][sortNorm])) return false;
|
||||
}
|
||||
if (col.value === 'all') return true;
|
||||
if (typeof t === 'string') return t === col.value;
|
||||
@ -309,7 +305,7 @@ function changeSort() {
|
||||
}
|
||||
|
||||
function exit() {
|
||||
mainUi.close(props.num);
|
||||
props.controller.close(props.num);
|
||||
}
|
||||
|
||||
function clickList(i: number) {
|
||||
@ -350,7 +346,7 @@ function canDragin(type: number) {
|
||||
if (type < 0) return false;
|
||||
const et = equip.value.equip?.type;
|
||||
if (!core.canEquip(toShow.value[selected.value]?.[0])) return false;
|
||||
if (!has(et)) return false;
|
||||
if (isNil(et)) return false;
|
||||
if (typeof et === 'number') return type === et;
|
||||
return equipCol[type] === et;
|
||||
}
|
||||
@ -427,10 +423,10 @@ function dragout(e: Event) {
|
||||
}
|
||||
|
||||
function toTool() {
|
||||
mainUi.holdOn();
|
||||
props.controller.holdOn();
|
||||
exit();
|
||||
nextTick(() => {
|
||||
mainUi.open('toolbox');
|
||||
props.controller.open('toolbox');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -31,13 +31,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUpdated, Ref, ref, watch } from 'vue';
|
||||
import Box from '../components/box.vue';
|
||||
import { GameUi } from '../controller';
|
||||
import { nextFrame } from '../utils';
|
||||
import { EnemyInfo, IDamageEnemy } from '@motajs/types';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
enemy: IDamageEnemy;
|
||||
close: Ref<boolean>;
|
||||
loc: [x: number, y: number];
|
||||
|
@ -13,14 +13,9 @@ import { getDetailedEnemy } from '../tools/fixed';
|
||||
import BookDetail from './bookDetail.vue';
|
||||
import { detailInfo } from '../tools/book';
|
||||
import { hovered } from '../preset/fixed';
|
||||
import { GameUi } from '../controller';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import { IMountedVBind } from '../interface';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
panel?: 'special' | 'critical' | 'target';
|
||||
}>();
|
||||
const props = defineProps<IMountedVBind>();
|
||||
|
||||
const panel = props.panel ?? 'special';
|
||||
|
||||
@ -40,7 +35,7 @@ if (hovered) {
|
||||
}
|
||||
|
||||
function close() {
|
||||
mainUi.close(props.num);
|
||||
props.controller.close(props.num);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -98,18 +98,14 @@ import {
|
||||
DoubleRightOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { tip } from '../utils';
|
||||
import { GameUi } from '../controller';
|
||||
import { tip } from '../use';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { createChangable } from '../tools/common';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import { mainSetting } from '../preset/ui';
|
||||
import { mainSetting } from '../preset/settingIns';
|
||||
import { GameStorage } from '@motajs/legacy-system';
|
||||
import { IMountedVBind } from '../interface';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
}>();
|
||||
const props = defineProps<IMountedVBind>();
|
||||
|
||||
type Loc2 = [number, number, number, number];
|
||||
|
||||
@ -162,7 +158,7 @@ let thumb: HTMLCanvasElement;
|
||||
let thumbCtx: CanvasRenderingContext2D;
|
||||
|
||||
function exit() {
|
||||
mainUi.close(props.num);
|
||||
props.controller.close(props.num);
|
||||
}
|
||||
|
||||
const title = computed(() => {
|
||||
@ -584,10 +580,7 @@ onUnmounted(() => {
|
||||
max-width: 50%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-shadow:
|
||||
1px 1px 1px black,
|
||||
1px -1px 1px black,
|
||||
-1px 1px 1px black,
|
||||
text-shadow: 1px 1px 1px black, 1px -1px 1px black, -1px 1px 1px black,
|
||||
-1px -1px 1px black;
|
||||
}
|
||||
|
||||
|
@ -37,14 +37,13 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Hotkey } from '@motajs/system-action';
|
||||
import { GameUi } from '../controller';
|
||||
import Column from '../components/colomn.vue';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import { computed, onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||
import { KeyCode, KeyCodeUtils } from '@motajs/client-base';
|
||||
import { generateBinary, keycode } from '../utils';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { generateBinary, keycode } from '@motajs/legacy-common';
|
||||
import { IMountedVBind } from '../interface';
|
||||
|
||||
interface HotkeyKeys {
|
||||
index: number;
|
||||
@ -64,16 +63,12 @@ interface SelectedKey {
|
||||
index: number;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
hotkey: Hotkey;
|
||||
}>();
|
||||
const props = defineProps<IMountedVBind & { hotkey: Hotkey }>();
|
||||
|
||||
const hotkey = props.hotkey;
|
||||
|
||||
function close() {
|
||||
mainUi.close(props.num);
|
||||
props.controller.close(props.num);
|
||||
}
|
||||
|
||||
const selectedGroup = ref('ui');
|
||||
|
@ -1,4 +1,4 @@
|
||||
export { default as Achievement } from './achievement.vue';
|
||||
// export { default as Achievement } from './achievement.vue';
|
||||
export { default as BgmList } from './bgmList.vue';
|
||||
export { default as Book } from './book.vue';
|
||||
export { default as BookDetail } from './bookDetail.vue';
|
||||
|
@ -34,14 +34,10 @@ import {
|
||||
import { GameUi } from '../controller';
|
||||
import { formatSize } from '../utils';
|
||||
import { logger } from '@motajs/common';
|
||||
import { fixedUi } from '../preset/ui';
|
||||
import { sleep } from 'mutate-animate';
|
||||
import { IMountedVBind } from '../interface';
|
||||
|
||||
const props = defineProps<{
|
||||
ui: GameUi;
|
||||
num: number;
|
||||
callback?: () => void;
|
||||
}>();
|
||||
const props = defineProps<IMountedVBind>();
|
||||
|
||||
const loading = ref(0);
|
||||
const loaded = ref(0);
|
||||
@ -76,8 +72,8 @@ onMounted(async () => {
|
||||
loadDiv.style.opacity = '0';
|
||||
Mota.require('@user/data-base').loading.emit('loaded');
|
||||
await sleep(1000);
|
||||
fixedUi.close(props.num);
|
||||
fixedUi.open('start');
|
||||
props.controller.close(props.num);
|
||||
props.controller.open('start');
|
||||
});
|
||||
loadDiv = document.getElementById('load') as HTMLDivElement;
|
||||
});
|
||||
|
@ -1,157 +0,0 @@
|
||||
<template>
|
||||
<div id="marked-enemy">
|
||||
<Box
|
||||
v-model:left="boxPos.left"
|
||||
v-model:top="boxPos.top"
|
||||
v-model:width="boxPos.width"
|
||||
v-model:height="boxPos.height"
|
||||
:resizable="true"
|
||||
:dragable="true"
|
||||
>
|
||||
<Scroll class="box-scroll" :no-scroll="true">
|
||||
<div class="marked-main">
|
||||
<div class="marked-info">
|
||||
<BoxAnimate
|
||||
:id="enemy.id"
|
||||
:width="24"
|
||||
:height="24"
|
||||
></BoxAnimate>
|
||||
<span class="marked-name marked-item">{{
|
||||
getName()
|
||||
}}</span>
|
||||
</div>
|
||||
<span class="marked-damage marked-item"
|
||||
>伤害:{{ format(info.damage) }}</span
|
||||
>
|
||||
<span class="marked-critical marked-item"
|
||||
>临界:{{ format(info.critical) }}</span
|
||||
>
|
||||
<span class="marked-critical-damage marked-item"
|
||||
>减伤:{{ format(info.criticalDam) }}</span
|
||||
>
|
||||
<span class="marked-def marked-item"
|
||||
>{{ ratio }}防:{{ format(info.defDamage) }}</span
|
||||
>
|
||||
<div class="marked-button">
|
||||
<span
|
||||
class="marked-hide button-text"
|
||||
@click.stop="hidden = true"
|
||||
>隐藏盒子</span
|
||||
>
|
||||
<span
|
||||
class="marked-cancel button-text"
|
||||
@click.stop="unmarkEnemy(enemy.id)"
|
||||
>取消标记</span
|
||||
>
|
||||
</div>
|
||||
</div></Scroll
|
||||
>
|
||||
</Box>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { MarkInfo, unmarkEnemy } from '../mark';
|
||||
import Box from '../components/box.vue';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import BoxAnimate from '../components/boxAnimate.vue';
|
||||
import { GameUi } from '../controller';
|
||||
import { fixedUi } from '../preset/ui';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
enemy: MarkInfo<EnemyIds>;
|
||||
}>();
|
||||
|
||||
interface BoxPos {
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
interface MarkedEnemy {
|
||||
damage: number;
|
||||
critical: number;
|
||||
criticalDam: number;
|
||||
defDamage: number;
|
||||
}
|
||||
|
||||
const enemy = props.enemy;
|
||||
const ratio = core.status.thisMap?.ratio ?? 1;
|
||||
|
||||
const format = core.formatBigNumber;
|
||||
|
||||
const boxPos = reactive<BoxPos>({
|
||||
left: window.innerWidth - 300,
|
||||
top: 100,
|
||||
width: 200,
|
||||
height: 150
|
||||
});
|
||||
const info = reactive<MarkedEnemy>({
|
||||
damage: 0,
|
||||
critical: 0,
|
||||
criticalDam: 0,
|
||||
defDamage: 0
|
||||
});
|
||||
|
||||
const hidden = ref(false);
|
||||
|
||||
watch(hidden, n => {
|
||||
if (n) fixedUi.close(props.num);
|
||||
});
|
||||
watch(enemy.update, update);
|
||||
|
||||
function update() {
|
||||
info.damage = enemy.enemy.calDamage().damage;
|
||||
const critical = enemy.enemy.calCritical()[0];
|
||||
info.critical = critical?.atkDelta ?? 0;
|
||||
info.criticalDam = critical?.delta ?? 0;
|
||||
info.defDamage = enemy.enemy.calDefDamage(ratio).delta;
|
||||
}
|
||||
|
||||
function getName() {
|
||||
return enemy.enemy.enemy.name;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
#marked-enemy {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.box-scroll {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.marked-main {
|
||||
padding: 1vh 0;
|
||||
background-color: #0009;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.marked-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.marked-item {
|
||||
margin-left: 10%;
|
||||
}
|
||||
|
||||
.marked-button {
|
||||
align-self: center;
|
||||
width: 80%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
}
|
||||
</style>
|
@ -73,7 +73,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onUnmounted, ref, shallowRef } from 'vue';
|
||||
import { mainSetting } from '../preset/ui';
|
||||
import { mainSetting } from '../preset/settingIns';
|
||||
import {
|
||||
MotaSetting,
|
||||
MotaSettingItem,
|
||||
@ -85,14 +85,9 @@ import { splitText } from '../utils';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import { isMobile } from '../use';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { GameUi } from '../controller';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import { IMountedVBind } from '../interface';
|
||||
|
||||
const props = defineProps<{
|
||||
info?: MotaSetting;
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
}>();
|
||||
const props = defineProps<IMountedVBind>();
|
||||
|
||||
const setting = props.info ?? mainSetting;
|
||||
const display = shallowRef<SettingDisplayInfo[]>([]);
|
||||
@ -145,7 +140,7 @@ function click(key: string, index: number, item: MotaSettingItem) {
|
||||
}
|
||||
|
||||
function exit() {
|
||||
mainUi.close(props.num);
|
||||
props.controller.close(props.num);
|
||||
}
|
||||
|
||||
gameKey.use(props.ui.symbol);
|
||||
|
@ -169,18 +169,14 @@ import {
|
||||
RightOutlined,
|
||||
DoubleRightOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { splitText, tip } from '../utils';
|
||||
import { splitText } from '../utils';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import BoxAnimate from '../components/boxAnimate.vue';
|
||||
import { GameUi } from '../controller';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import { IMountedVBind } from '../interface';
|
||||
import { tip } from '../use';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
shopId: string;
|
||||
}>();
|
||||
const props = defineProps<IMountedVBind>();
|
||||
|
||||
const id = props.shopId;
|
||||
const shop = core.status.shops[id] as ItemShopEvent;
|
||||
@ -315,7 +311,7 @@ gameKey
|
||||
|
||||
function exit() {
|
||||
if (bought) core.status.route.push('closeShop');
|
||||
mainUi.close(props.num);
|
||||
props.controller.close(props.num);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
|
@ -19,13 +19,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import skills from '../data/skill.json';
|
||||
import { has } from '../utils';
|
||||
import Column from '../components/colomn.vue';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import { IMountedVBind } from '../interface';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
}>();
|
||||
const props = defineProps<IMountedVBind>();
|
||||
|
||||
type Skills = keyof typeof skills;
|
||||
|
||||
@ -47,7 +45,7 @@ const content = computed(() => {
|
||||
.map((v, i, a) => {
|
||||
if (/^\d+\./.test(v)) return `${' '.repeat(12)}${v}`;
|
||||
else if (
|
||||
(has(a[i - 1]) &&
|
||||
(!isNil(a[i - 1]) &&
|
||||
v !== '<br>' &&
|
||||
a[i - 1] === '<br>') ||
|
||||
i === 0
|
||||
@ -65,7 +63,7 @@ const content = computed(() => {
|
||||
});
|
||||
|
||||
function exit() {
|
||||
mainUi.close(props.num);
|
||||
props.controller.close(props.num);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -81,17 +81,14 @@
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import { has, splitText, tip } from '../utils';
|
||||
import { isMobile } from '../use';
|
||||
import { splitText } from '../utils';
|
||||
import { isMobile, tip } from '../use';
|
||||
import { sleep } from 'mutate-animate';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { GameUi } from '../controller';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import { IMountedVBind } from '../interface';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
}>();
|
||||
const props = defineProps<IMountedVBind>();
|
||||
|
||||
const skillTree = Mota.require('@user/legacy-plugin-data');
|
||||
|
||||
@ -179,7 +176,7 @@ const level = computed(() => {
|
||||
});
|
||||
|
||||
function exit() {
|
||||
mainUi.close(props.num);
|
||||
props.controller.close(props.num);
|
||||
}
|
||||
|
||||
function resize() {
|
||||
@ -298,7 +295,7 @@ function selectChapter(delta: number) {
|
||||
const now = chapterList.indexOf(chapter.value);
|
||||
const to = now + delta;
|
||||
|
||||
if (has(chapterList[to]) && flags.chapter > to) {
|
||||
if (!isNil(chapterList[to]) && flags.chapter > to) {
|
||||
selected.value = s[chapterList[to]][0].index;
|
||||
chapter.value = chapterList[to];
|
||||
update.value = !update.value;
|
||||
|
@ -64,19 +64,14 @@ import {
|
||||
FullscreenExitOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { sleep } from 'mutate-animate';
|
||||
import { doByInterval } from '../utils';
|
||||
import { triggerFullscreen } from '../utils';
|
||||
import { doByInterval, triggerFullscreen } from '../utils';
|
||||
import { isMobile } from '../use';
|
||||
import { GameUi } from '../controller';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import { mainSetting } from '../preset/ui';
|
||||
import { mainSetting } from '../preset/settingIns';
|
||||
import { mat4 } from 'gl-matrix';
|
||||
import { IMountedVBind } from '../interface';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
}>();
|
||||
const props = defineProps<IMountedVBind>();
|
||||
|
||||
const bg = core.material.images.images['bg.webp'];
|
||||
|
||||
@ -158,7 +153,7 @@ async function clickStartButton(id: string) {
|
||||
}
|
||||
if (id === 'replay') core.chooseReplayFile();
|
||||
if (id === 'achievement') {
|
||||
mainUi.open('achievement');
|
||||
props.controller.open('achievement');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,8 +85,8 @@
|
||||
<span>{{
|
||||
selected === 'none'
|
||||
? '永久道具'
|
||||
: (getClsName(all[selected].cls as ItemMode) ??
|
||||
'永久道具')
|
||||
: getClsName(all[selected].cls as ItemMode) ??
|
||||
'永久道具'
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -113,17 +113,14 @@ import Scroll from '../components/scroll.vue';
|
||||
import BoxAnimate from '../components/boxAnimate.vue';
|
||||
import { getClsName, getItems } from '../tools/toolbox';
|
||||
import { isMobile } from '../use';
|
||||
import { type, has } from '../utils';
|
||||
import { type } from '../utils';
|
||||
import { hyper } from 'mutate-animate';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { GameUi } from '../controller';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import { IMountedVBind } from '../interface';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
}>();
|
||||
const props = defineProps<IMountedVBind>();
|
||||
|
||||
type ItemMode = 'tools' | 'constants';
|
||||
type ShowItemIds = ItemIdOf<'constants' | 'tools'> | 'none';
|
||||
@ -146,7 +143,7 @@ watch(index, n => {
|
||||
});
|
||||
|
||||
watch(mode, n => {
|
||||
if (!has(items[n][index.value])) {
|
||||
if (isNil(items[n][index.value])) {
|
||||
selected.value = 'none';
|
||||
return;
|
||||
}
|
||||
@ -172,17 +169,17 @@ async function select(id: ShowItemIds, nouse: boolean = false) {
|
||||
}
|
||||
|
||||
function exit() {
|
||||
mainUi.close(props.num);
|
||||
props.controller.close(props.num);
|
||||
}
|
||||
|
||||
function use(id: ShowItemIds) {
|
||||
if (id === 'none') return;
|
||||
if (core.canUseItem(id)) {
|
||||
const hold = mainUi.holdOn();
|
||||
const hold = props.controller.holdOn();
|
||||
exit();
|
||||
nextTick(() => {
|
||||
core.tryUseItem(id, false, () => {
|
||||
if (mainUi.stack.length === 0) {
|
||||
if (props.controller.stack.length === 0) {
|
||||
hold.end(core.status.event.id !== 'toolbox');
|
||||
}
|
||||
});
|
||||
@ -196,10 +193,10 @@ function use(id: ShowItemIds) {
|
||||
}
|
||||
|
||||
async function toEquip() {
|
||||
mainUi.holdOn();
|
||||
props.controller.holdOn();
|
||||
exit();
|
||||
nextTick(() => {
|
||||
mainUi.open('equipbox');
|
||||
props.controller.open('equipbox');
|
||||
});
|
||||
}
|
||||
|
||||
|
47
packages/legacy-ui/src/uiUtils.ts
Normal file
47
packages/legacy-ui/src/uiUtils.ts
Normal file
@ -0,0 +1,47 @@
|
||||
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');
|
||||
}
|
||||
}
|
@ -1,8 +1,23 @@
|
||||
import { sleep } from 'mutate-animate';
|
||||
import { tip } from './utils';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { MessageApi } from 'ant-design-vue/lib/message';
|
||||
|
||||
export default function init() {
|
||||
return { useDrag, useWheel, useUp, isMobile };
|
||||
message.config({
|
||||
maxCount: 3
|
||||
});
|
||||
export function tip(
|
||||
type: Exclude<keyof MessageApi, 'open' | 'config' | 'destroy'>,
|
||||
text: string
|
||||
) {
|
||||
message[type]({
|
||||
content: text,
|
||||
class: 'antdv-message'
|
||||
});
|
||||
}
|
||||
|
||||
let num = 0;
|
||||
export function requireUniqueSymbol() {
|
||||
return num++;
|
||||
}
|
||||
|
||||
type DragFn = (x: number, y: number, e: MouseEvent | TouchEvent) => void;
|
||||
|
@ -1,13 +1,10 @@
|
||||
import { message } from 'ant-design-vue';
|
||||
import { MessageApi } from 'ant-design-vue/lib/message';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { Animation, sleep, TimingFn } from 'mutate-animate';
|
||||
import { Ref, ref } from 'vue';
|
||||
import { EVENT_KEY_CODE_MAP, KeyCode } from '@motajs/client-base';
|
||||
import { KeyCode } from '@motajs/client-base';
|
||||
import axios from 'axios';
|
||||
import { decompressFromBase64 } from 'lz-string';
|
||||
import { Keyboard, KeyboardEmits, isAssist } from '@motajs/system-action';
|
||||
import { fixedUi, mainUi } from './preset/ui';
|
||||
import { logger } from '@motajs/common';
|
||||
|
||||
type CanParseCss = keyof {
|
||||
@ -18,25 +15,6 @@ type CanParseCss = keyof {
|
||||
: never]: CSSStyleDeclaration[P];
|
||||
};
|
||||
|
||||
export default function init() {
|
||||
return {
|
||||
has,
|
||||
getDamageColor,
|
||||
parseCss,
|
||||
tip,
|
||||
changeLocalStorage,
|
||||
swapChapter
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 判定一个值是否不是undefined或null
|
||||
* @param value 要判断的值
|
||||
*/
|
||||
export function has<T>(value: T): value is NonNullable<T> {
|
||||
return !isNil(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据伤害大小获取颜色
|
||||
* @param damage 伤害大小
|
||||
@ -68,14 +46,6 @@ export function setCanvasSize(
|
||||
canvas.style.height = `${h}px`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取事件中的keycode对应的键
|
||||
* @param key 要获取的键
|
||||
*/
|
||||
export function keycode(key: number) {
|
||||
return EVENT_KEY_CODE_MAP[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析css字符串为CSSStyleDeclaration对象
|
||||
* @param css 要解析的css字符串
|
||||
@ -195,7 +165,7 @@ export function type(
|
||||
const all = toShow.length;
|
||||
|
||||
const fn = (time: number) => {
|
||||
if (!has(time)) return;
|
||||
if (isNil(time)) return;
|
||||
const now = ani.x;
|
||||
content.value = toShow.slice(0, Math.floor(now));
|
||||
if (Math.floor(now) === all) {
|
||||
@ -213,19 +183,6 @@ export function type(
|
||||
return content;
|
||||
}
|
||||
|
||||
message.config({
|
||||
maxCount: 3
|
||||
});
|
||||
export function tip(
|
||||
type: Exclude<keyof MessageApi, 'open' | 'config' | 'destroy'>,
|
||||
text: string
|
||||
) {
|
||||
message[type]({
|
||||
content: text,
|
||||
class: 'antdv-message'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文字分段换行等
|
||||
* @param str 文字
|
||||
@ -235,7 +192,7 @@ export function splitText(str: string[]) {
|
||||
.map((v, i, a) => {
|
||||
if (/^\d+\./.test(v)) return `${' '.repeat(12)}${v}`;
|
||||
else if (
|
||||
(has(a[i - 1]) && v !== '<br>' && a[i - 1] === '<br>') ||
|
||||
(!isNil(a[i - 1]) && v !== '<br>' && a[i - 1] === '<br>') ||
|
||||
i === 0
|
||||
) {
|
||||
return `${' '.repeat(8)}${v}`;
|
||||
@ -339,25 +296,6 @@ export function ensureArray<T>(arr: T): T extends any[] ? T : T[] {
|
||||
return arr instanceof Array ? arr : [arr];
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数组内的某个项,返回删除后的数组
|
||||
* @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;
|
||||
}
|
||||
|
||||
export async function triggerFullscreen(full: boolean) {
|
||||
const { maxGameScale } = Mota.require('@user/data-utils');
|
||||
if (!!document.fullscreenElement && !full) {
|
||||
@ -382,20 +320,6 @@ export async function triggerFullscreen(full: boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据布尔值数组转换成一个二进制数
|
||||
* @param arr 要转换的布尔值数组
|
||||
*/
|
||||
export function generateBinary(arr: boolean[]) {
|
||||
let num = 0;
|
||||
arr.forEach((v, i) => {
|
||||
if (v) {
|
||||
num |= 1 << i;
|
||||
}
|
||||
});
|
||||
return num;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得某个状态的中文名
|
||||
* @param name 要获取的属性名
|
||||
@ -423,50 +347,6 @@ export function getStatusLabel(name: string) {
|
||||
);
|
||||
}
|
||||
|
||||
export function flipBinary(num: number, col: number) {
|
||||
const n = 1 << col;
|
||||
if (num & n) return num & ~n;
|
||||
else return num | n;
|
||||
}
|
||||
|
||||
/**
|
||||
* 唤起虚拟键盘,并获取到一次按键操作
|
||||
* @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 formatSize(size: number) {
|
||||
return size < 1 << 10
|
||||
? `${size.toFixed(2)}B`
|
||||
@ -477,17 +357,6 @@ export function formatSize(size: number) {
|
||||
: `${(size / (1 << 30)).toFixed(2)}GB`;
|
||||
}
|
||||
|
||||
let num = 0;
|
||||
export function requireUniqueSymbol() {
|
||||
return num++;
|
||||
}
|
||||
|
||||
export function openDanmakuPoster() {
|
||||
if (!fixedUi.hasName('danmakuEditor')) {
|
||||
fixedUi.open('danmakuEditor');
|
||||
}
|
||||
}
|
||||
|
||||
export function getIconHeight(icon: AllIds | 'hero') {
|
||||
if (icon === 'hero') {
|
||||
if (core.isPlaying()) {
|
||||
|
@ -1,415 +0,0 @@
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import {
|
||||
FloorLayer,
|
||||
ILayerGroupRenderExtends,
|
||||
ILayerRenderExtends,
|
||||
Layer,
|
||||
LayerGroup,
|
||||
LayerMovingRenderable
|
||||
} from './layer';
|
||||
import { texture } from './cache';
|
||||
import { sleep } from 'mutate-animate';
|
||||
import { RenderAdapter } from '@motajs/render-core';
|
||||
|
||||
const { hook } = Mota.require('@user/data-base');
|
||||
|
||||
hook.on('setBlock', (x, y, floor, block) => {
|
||||
const isNow = floor === core.status.floorId;
|
||||
LayerGroupFloorBinder.activedBinder.forEach(v => {
|
||||
if (floor === v.floor || (isNow && v.bindThisFloor)) {
|
||||
v.setBlock('event', block, x, y);
|
||||
}
|
||||
});
|
||||
LayerFloorBinder.listenedBinder.forEach(v => {
|
||||
if (v.layer.layer === 'event') {
|
||||
if (v.floor === floor || (isNow && v.bindThisFloor)) {
|
||||
v.setBlock(block, x, y);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
hook.on('changingFloor', floor => {
|
||||
// 潜在隐患:如果putRenderData改成异步,那么会变成两帧后才能真正刷新并渲染
|
||||
// 考虑到楼层转换一般不会同时执行很多次,因此这里改为立刻更新
|
||||
LayerGroupFloorBinder.activedBinder.forEach(v => {
|
||||
if (v.bindThisFloor) v.updateBindData();
|
||||
v.emit('floorChange', floor);
|
||||
});
|
||||
LayerFloorBinder.listenedBinder.forEach(v => {
|
||||
if (v.bindThisFloor) v.updateBindData();
|
||||
});
|
||||
});
|
||||
hook.on('setBgFgBlock', (name, number, x, y, floor) => {
|
||||
const isNow = floor === core.status.floorId;
|
||||
LayerGroupFloorBinder.activedBinder.forEach(v => {
|
||||
if (floor === v.floor || (isNow && v.bindThisFloor)) {
|
||||
v.setBlock(name, number, x, y);
|
||||
}
|
||||
});
|
||||
LayerFloorBinder.listenedBinder.forEach(v => {
|
||||
if (v.layer.layer === name) {
|
||||
if (v.floor === floor || (isNow && v.bindThisFloor)) {
|
||||
v.setBlock(number, x, y);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
interface LayerGroupBinderEvent {
|
||||
update: [floor: FloorIds];
|
||||
setBlock: [x: number, y: number, floor: FloorIds, block: AllNumbers];
|
||||
floorChange: [floor: FloorIds];
|
||||
}
|
||||
|
||||
/**
|
||||
* 楼层绑定拓展,用于LayerGroup,将楼层数据传输到渲染系统。
|
||||
* 添加后,会自动在LayerGroup包含的子Layer上添加LayerFloorBinder拓展,用于后续处理。
|
||||
* 当移除这个拓展时,其附属的所有子拓展也会一并被移除。
|
||||
*/
|
||||
export class LayerGroupFloorBinder
|
||||
extends EventEmitter<LayerGroupBinderEvent>
|
||||
implements ILayerGroupRenderExtends
|
||||
{
|
||||
id: string = 'floor-binder';
|
||||
|
||||
bindThisFloor: boolean = true;
|
||||
floor?: FloorIds;
|
||||
group!: LayerGroup;
|
||||
|
||||
/** 附属的子LayerFloorBinder拓展 */
|
||||
layerBinders: Set<LayerFloorBinder> = new Set();
|
||||
|
||||
private needUpdate: boolean = false;
|
||||
|
||||
static activedBinder: Set<LayerGroupFloorBinder> = new Set();
|
||||
|
||||
/**
|
||||
* 绑定楼层为当前楼层,并跟随变化
|
||||
*/
|
||||
bindThis() {
|
||||
this.floor = void 0;
|
||||
this.bindThisFloor = true;
|
||||
this.layerBinders.forEach(v => v.bindThis());
|
||||
this.updateBind();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定楼层为指定楼层
|
||||
* @param floorId 楼层id
|
||||
*/
|
||||
bindFloor(floorId: FloorIds) {
|
||||
this.bindThisFloor = false;
|
||||
this.floor = floorId;
|
||||
this.layerBinders.forEach(v => v.bindFloor(floorId));
|
||||
this.updateBind();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在下一帧进行绑定数据更新
|
||||
*/
|
||||
updateBind() {
|
||||
if (this.needUpdate || !this.group) return;
|
||||
this.needUpdate = true;
|
||||
this.group.requestBeforeFrame(() => {
|
||||
this.needUpdate = false;
|
||||
this.updateBindData();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 立刻进行数据绑定更新
|
||||
*/
|
||||
updateBindData() {
|
||||
this.layerBinders.forEach(v => {
|
||||
v.updateBindData();
|
||||
});
|
||||
|
||||
const floor = this.getFloor();
|
||||
this.emit('update', floor);
|
||||
}
|
||||
|
||||
getFloor() {
|
||||
return this.bindThisFloor ? core.status.floorId : this.floor!;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置图块
|
||||
*/
|
||||
setBlock(layer: FloorLayer, block: AllNumbers, x: number, y: number) {
|
||||
const ex = this.group
|
||||
.getLayer(layer)
|
||||
?.getExtends('floor-binder') as LayerFloorBinder;
|
||||
if (!ex) return;
|
||||
ex.setBlock(block, x, y);
|
||||
|
||||
const floor = this.bindThisFloor ? core.status.floorId : this.floor!;
|
||||
this.emit('setBlock', x, y, floor, block);
|
||||
}
|
||||
|
||||
checkLayerExtends(layer: Layer) {
|
||||
const ex = layer.getExtends('floor-binder');
|
||||
|
||||
if (!ex) {
|
||||
const extend = new LayerFloorBinder(this);
|
||||
layer.extends(extend);
|
||||
this.layerBinders.add(extend);
|
||||
} else {
|
||||
if (ex instanceof LayerFloorBinder) {
|
||||
ex.setParent(this);
|
||||
this.layerBinders.add(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
awake(group: LayerGroup) {
|
||||
this.group = group;
|
||||
|
||||
for (const layer of group.layers.values()) {
|
||||
this.checkLayerExtends(layer);
|
||||
}
|
||||
LayerGroupFloorBinder.activedBinder.add(this);
|
||||
}
|
||||
|
||||
onLayerAdd(_group: LayerGroup, layer: Layer): void {
|
||||
this.checkLayerExtends(layer);
|
||||
}
|
||||
|
||||
onDestroy(group: LayerGroup) {
|
||||
LayerGroupFloorBinder.activedBinder.delete(this);
|
||||
group.layers.forEach(v => {
|
||||
v.removeExtends('floor-binder');
|
||||
});
|
||||
this.removeAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 楼层绑定拓展,用于Layer的楼层渲染。
|
||||
* 注意,如果目标Layer是LayerGroup的子元素,那么会自动检测父元素是否包含LayerGroupFloorBinder拓展,
|
||||
* 如果包含,那么会自动将此拓展附加至父元素的拓展。当父元素的拓展被移除时,此拓展也会一并被移除。
|
||||
*/
|
||||
export class LayerFloorBinder implements ILayerRenderExtends {
|
||||
id: string = 'floor-binder';
|
||||
|
||||
parent?: LayerGroupFloorBinder;
|
||||
layer!: Layer;
|
||||
bindThisFloor: boolean = true;
|
||||
floor?: FloorIds;
|
||||
|
||||
static listenedBinder: Set<LayerFloorBinder> = new Set();
|
||||
|
||||
private needUpdate: boolean = false;
|
||||
|
||||
constructor(parent?: LayerGroupFloorBinder) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定楼层为当前楼层,并跟随变化
|
||||
*/
|
||||
bindThis() {
|
||||
this.floor = void 0;
|
||||
this.bindThisFloor = true;
|
||||
this.updateBind();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定楼层为指定楼层
|
||||
* @param floorId 楼层id
|
||||
*/
|
||||
bindFloor(floorId: FloorIds) {
|
||||
this.bindThisFloor = false;
|
||||
this.floor = floorId;
|
||||
this.updateBind();
|
||||
}
|
||||
|
||||
getFloor() {
|
||||
return this.bindThisFloor ? core.status.floorId : this.floor!;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置这个拓展附属至的父拓展(LayerGroupFloorBinder拓展)
|
||||
* @param parent 父拓展
|
||||
*/
|
||||
setParent(parent?: LayerGroupFloorBinder) {
|
||||
this.parent = parent;
|
||||
this.checkListen();
|
||||
}
|
||||
|
||||
private checkListen() {
|
||||
if (this.parent) LayerFloorBinder.listenedBinder.delete(this);
|
||||
else LayerFloorBinder.listenedBinder.add(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在下一帧进行绑定数据更新
|
||||
*/
|
||||
updateBind() {
|
||||
if (this.needUpdate) return;
|
||||
this.needUpdate = true;
|
||||
this.layer.requestBeforeFrame(() => {
|
||||
this.needUpdate = false;
|
||||
this.updateBindData();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置图块
|
||||
*/
|
||||
setBlock(block: AllNumbers, x: number, y: number) {
|
||||
this.layer.putRenderData([block], 1, x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 立刻更新绑定数据,而非下一帧
|
||||
*/
|
||||
updateBindData() {
|
||||
const floor = this.getFloor();
|
||||
if (!floor) return;
|
||||
core.extractBlocks(floor);
|
||||
const map = core.status.maps[floor];
|
||||
this.layer.setMapSize(map.width, map.height);
|
||||
const image = core.status.maps[this.getFloor()].images;
|
||||
if (this.layer.layer === 'event') {
|
||||
const m = map.map;
|
||||
this.layer.putRenderData(m.flat(), map.width, 0, 0);
|
||||
} else {
|
||||
const m = core.maps._getBgFgMapArray(this.layer.layer!, floor);
|
||||
this.layer.putRenderData(m.flat(), map.width, 0, 0);
|
||||
}
|
||||
if (this.layer.layer === 'bg') {
|
||||
// 别忘了背景图块
|
||||
this.layer.setBackground(texture.idNumberMap[map.defaultGround]);
|
||||
}
|
||||
const toDraw = image?.filter(v => v.canvas === this.layer.layer);
|
||||
this.layer.setFloorImage(toDraw ?? []);
|
||||
}
|
||||
|
||||
awake(layer: Layer) {
|
||||
this.layer = layer;
|
||||
if (!this.parent) {
|
||||
const group = layer.parent;
|
||||
if (group instanceof LayerGroup) {
|
||||
const ex = group.getExtends('floor-binder');
|
||||
if (ex instanceof LayerGroupFloorBinder) {
|
||||
ex.checkLayerExtends(layer);
|
||||
this.parent = ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.checkListen();
|
||||
}
|
||||
|
||||
onDestroy(_layer: Layer) {
|
||||
LayerFloorBinder.listenedBinder.delete(this);
|
||||
this.parent?.layerBinders.delete(this);
|
||||
}
|
||||
}
|
||||
|
||||
interface DoorAnimateRenderable {
|
||||
renderable: LayerMovingRenderable;
|
||||
count: number;
|
||||
perTime: number;
|
||||
}
|
||||
|
||||
export class LayerDoorAnimate implements ILayerRenderExtends {
|
||||
id: string = 'door-animate';
|
||||
|
||||
layer!: Layer;
|
||||
|
||||
private moving: Set<LayerMovingRenderable> = new Set();
|
||||
|
||||
private getRenderable(block: Block): DoorAnimateRenderable | null {
|
||||
const { x, y, id } = block;
|
||||
const renderable = texture.getRenderable(id);
|
||||
if (!renderable) return null;
|
||||
const image = renderable.autotile
|
||||
? renderable.image[0]
|
||||
: renderable.image;
|
||||
const time = block.event.doorInfo?.time ?? 160;
|
||||
const frame = renderable.render.length;
|
||||
const perTime = time / frame;
|
||||
|
||||
const data: LayerMovingRenderable = {
|
||||
x,
|
||||
y,
|
||||
zIndex: y,
|
||||
image,
|
||||
autotile: false,
|
||||
animate: 0,
|
||||
frame,
|
||||
bigImage: false,
|
||||
render: renderable.render,
|
||||
alpha: 1
|
||||
};
|
||||
return { renderable: data, count: frame, perTime };
|
||||
}
|
||||
|
||||
/**
|
||||
* 开门
|
||||
* @param block 图块信息
|
||||
*/
|
||||
async openDoor(block: Block) {
|
||||
const renderable = this.getRenderable(block);
|
||||
if (!renderable) return Promise.reject();
|
||||
const { renderable: data, count: frame, perTime } = renderable;
|
||||
data.animate = 0;
|
||||
this.moving.add(data);
|
||||
this.layer.requestUpdateMoving();
|
||||
|
||||
let now = 0;
|
||||
while (now < frame) {
|
||||
await sleep(perTime);
|
||||
data.animate = ++now;
|
||||
this.layer.update(this.layer);
|
||||
}
|
||||
|
||||
this.moving.delete(data);
|
||||
this.layer.requestUpdateMoving();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* 关门
|
||||
* @param block 图块信息
|
||||
*/
|
||||
async closeDoor(block: Block) {
|
||||
const renderable = this.getRenderable(block);
|
||||
if (!renderable) return Promise.reject();
|
||||
const { renderable: data, count: frame, perTime } = renderable;
|
||||
data.animate = frame - 1;
|
||||
this.moving.add(data);
|
||||
this.layer.requestUpdateMoving();
|
||||
|
||||
let now = 0;
|
||||
while (now >= 0) {
|
||||
await sleep(perTime);
|
||||
data.animate = --now;
|
||||
this.layer.update(this.layer);
|
||||
}
|
||||
this.moving.delete(data);
|
||||
this.layer.requestUpdateMoving();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
awake(layer: Layer) {
|
||||
this.layer = layer;
|
||||
doorAdapter.add(this);
|
||||
}
|
||||
|
||||
onMovingUpdate(_layer: Layer, renderable: LayerMovingRenderable[]): void {
|
||||
renderable.push(...this.moving);
|
||||
}
|
||||
|
||||
onDestroy(_layer: Layer): void {
|
||||
doorAdapter.remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
const doorAdapter = new RenderAdapter<LayerDoorAnimate>('door-animate');
|
||||
doorAdapter.receive('openDoor', (item, block: Block) => {
|
||||
return item.openDoor(block);
|
||||
});
|
||||
doorAdapter.receive('closeDoor', (item, block: Block) => {
|
||||
return item.closeDoor(block);
|
||||
});
|
@ -2,7 +2,6 @@ export * from './animate';
|
||||
export * from './block';
|
||||
export * from './cache';
|
||||
export * from './camera';
|
||||
export * from './floor';
|
||||
export * from './frame';
|
||||
export * from './graphics';
|
||||
export * from './hero';
|
||||
|
@ -8,11 +8,11 @@ import {
|
||||
RenderAdapter
|
||||
} from '@motajs/render-core';
|
||||
import { logger } from '@motajs/common';
|
||||
import { TimingFn } from 'mutate-animate';
|
||||
import { sleep, TimingFn } from 'mutate-animate';
|
||||
import { RenderableData, texture } from './cache';
|
||||
import { BlockCacher, CanvasCacheItem, ICanvasCacheItem } from './block';
|
||||
import { LayerFloorBinder, LayerGroupFloorBinder } from './floor';
|
||||
import { IAnimateFrame, renderEmits } from './frame';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
|
||||
export interface ILayerGroupRenderExtends {
|
||||
/** 拓展的唯一标识符 */
|
||||
@ -1550,3 +1550,406 @@ export class Layer extends Container<ELayerEvent> {
|
||||
}
|
||||
|
||||
const layerAdapter = new RenderAdapter<Layer>('layer');
|
||||
|
||||
const { hook } = Mota.require('@user/data-base');
|
||||
|
||||
hook.on('setBlock', (x, y, floor, block) => {
|
||||
const isNow = floor === core.status.floorId;
|
||||
LayerGroupFloorBinder.activedBinder.forEach(v => {
|
||||
if (floor === v.floor || (isNow && v.bindThisFloor)) {
|
||||
v.setBlock('event', block, x, y);
|
||||
}
|
||||
});
|
||||
LayerFloorBinder.listenedBinder.forEach(v => {
|
||||
if (v.layer.layer === 'event') {
|
||||
if (v.floor === floor || (isNow && v.bindThisFloor)) {
|
||||
v.setBlock(block, x, y);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
hook.on('changingFloor', floor => {
|
||||
// 潜在隐患:如果putRenderData改成异步,那么会变成两帧后才能真正刷新并渲染
|
||||
// 考虑到楼层转换一般不会同时执行很多次,因此这里改为立刻更新
|
||||
LayerGroupFloorBinder.activedBinder.forEach(v => {
|
||||
if (v.bindThisFloor) v.updateBindData();
|
||||
v.emit('floorChange', floor);
|
||||
});
|
||||
LayerFloorBinder.listenedBinder.forEach(v => {
|
||||
if (v.bindThisFloor) v.updateBindData();
|
||||
});
|
||||
});
|
||||
hook.on('setBgFgBlock', (name, number, x, y, floor) => {
|
||||
const isNow = floor === core.status.floorId;
|
||||
LayerGroupFloorBinder.activedBinder.forEach(v => {
|
||||
if (floor === v.floor || (isNow && v.bindThisFloor)) {
|
||||
v.setBlock(name, number, x, y);
|
||||
}
|
||||
});
|
||||
LayerFloorBinder.listenedBinder.forEach(v => {
|
||||
if (v.layer.layer === name) {
|
||||
if (v.floor === floor || (isNow && v.bindThisFloor)) {
|
||||
v.setBlock(number, x, y);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
interface LayerGroupBinderEvent {
|
||||
update: [floor: FloorIds];
|
||||
setBlock: [x: number, y: number, floor: FloorIds, block: AllNumbers];
|
||||
floorChange: [floor: FloorIds];
|
||||
}
|
||||
|
||||
/**
|
||||
* 楼层绑定拓展,用于LayerGroup,将楼层数据传输到渲染系统。
|
||||
* 添加后,会自动在LayerGroup包含的子Layer上添加LayerFloorBinder拓展,用于后续处理。
|
||||
* 当移除这个拓展时,其附属的所有子拓展也会一并被移除。
|
||||
*/
|
||||
export class LayerGroupFloorBinder
|
||||
extends EventEmitter<LayerGroupBinderEvent>
|
||||
implements ILayerGroupRenderExtends
|
||||
{
|
||||
id: string = 'floor-binder';
|
||||
|
||||
bindThisFloor: boolean = true;
|
||||
floor?: FloorIds;
|
||||
group!: LayerGroup;
|
||||
|
||||
/** 附属的子LayerFloorBinder拓展 */
|
||||
layerBinders: Set<LayerFloorBinder> = new Set();
|
||||
|
||||
private needUpdate: boolean = false;
|
||||
|
||||
static activedBinder: Set<LayerGroupFloorBinder> = new Set();
|
||||
|
||||
/**
|
||||
* 绑定楼层为当前楼层,并跟随变化
|
||||
*/
|
||||
bindThis() {
|
||||
this.floor = void 0;
|
||||
this.bindThisFloor = true;
|
||||
this.layerBinders.forEach(v => v.bindThis());
|
||||
this.updateBind();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定楼层为指定楼层
|
||||
* @param floorId 楼层id
|
||||
*/
|
||||
bindFloor(floorId: FloorIds) {
|
||||
this.bindThisFloor = false;
|
||||
this.floor = floorId;
|
||||
this.layerBinders.forEach(v => v.bindFloor(floorId));
|
||||
this.updateBind();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在下一帧进行绑定数据更新
|
||||
*/
|
||||
updateBind() {
|
||||
if (this.needUpdate || !this.group) return;
|
||||
this.needUpdate = true;
|
||||
this.group.requestBeforeFrame(() => {
|
||||
this.needUpdate = false;
|
||||
this.updateBindData();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 立刻进行数据绑定更新
|
||||
*/
|
||||
updateBindData() {
|
||||
this.layerBinders.forEach(v => {
|
||||
v.updateBindData();
|
||||
});
|
||||
|
||||
const floor = this.getFloor();
|
||||
this.emit('update', floor);
|
||||
}
|
||||
|
||||
getFloor() {
|
||||
return this.bindThisFloor ? core.status.floorId : this.floor!;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置图块
|
||||
*/
|
||||
setBlock(layer: FloorLayer, block: AllNumbers, x: number, y: number) {
|
||||
const ex = this.group
|
||||
.getLayer(layer)
|
||||
?.getExtends('floor-binder') as LayerFloorBinder;
|
||||
if (!ex) return;
|
||||
ex.setBlock(block, x, y);
|
||||
|
||||
const floor = this.bindThisFloor ? core.status.floorId : this.floor!;
|
||||
this.emit('setBlock', x, y, floor, block);
|
||||
}
|
||||
|
||||
checkLayerExtends(layer: Layer) {
|
||||
const ex = layer.getExtends('floor-binder');
|
||||
|
||||
if (!ex) {
|
||||
const extend = new LayerFloorBinder(this);
|
||||
layer.extends(extend);
|
||||
this.layerBinders.add(extend);
|
||||
} else {
|
||||
if (ex instanceof LayerFloorBinder) {
|
||||
ex.setParent(this);
|
||||
this.layerBinders.add(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
awake(group: LayerGroup) {
|
||||
this.group = group;
|
||||
|
||||
for (const layer of group.layers.values()) {
|
||||
this.checkLayerExtends(layer);
|
||||
}
|
||||
LayerGroupFloorBinder.activedBinder.add(this);
|
||||
}
|
||||
|
||||
onLayerAdd(_group: LayerGroup, layer: Layer): void {
|
||||
this.checkLayerExtends(layer);
|
||||
}
|
||||
|
||||
onDestroy(group: LayerGroup) {
|
||||
LayerGroupFloorBinder.activedBinder.delete(this);
|
||||
group.layers.forEach(v => {
|
||||
v.removeExtends('floor-binder');
|
||||
});
|
||||
this.removeAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 楼层绑定拓展,用于Layer的楼层渲染。
|
||||
* 注意,如果目标Layer是LayerGroup的子元素,那么会自动检测父元素是否包含LayerGroupFloorBinder拓展,
|
||||
* 如果包含,那么会自动将此拓展附加至父元素的拓展。当父元素的拓展被移除时,此拓展也会一并被移除。
|
||||
*/
|
||||
export class LayerFloorBinder implements ILayerRenderExtends {
|
||||
id: string = 'floor-binder';
|
||||
|
||||
parent?: LayerGroupFloorBinder;
|
||||
layer!: Layer;
|
||||
bindThisFloor: boolean = true;
|
||||
floor?: FloorIds;
|
||||
|
||||
static listenedBinder: Set<LayerFloorBinder> = new Set();
|
||||
|
||||
private needUpdate: boolean = false;
|
||||
|
||||
constructor(parent?: LayerGroupFloorBinder) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定楼层为当前楼层,并跟随变化
|
||||
*/
|
||||
bindThis() {
|
||||
this.floor = void 0;
|
||||
this.bindThisFloor = true;
|
||||
this.updateBind();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定楼层为指定楼层
|
||||
* @param floorId 楼层id
|
||||
*/
|
||||
bindFloor(floorId: FloorIds) {
|
||||
this.bindThisFloor = false;
|
||||
this.floor = floorId;
|
||||
this.updateBind();
|
||||
}
|
||||
|
||||
getFloor() {
|
||||
return this.bindThisFloor ? core.status.floorId : this.floor!;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置这个拓展附属至的父拓展(LayerGroupFloorBinder拓展)
|
||||
* @param parent 父拓展
|
||||
*/
|
||||
setParent(parent?: LayerGroupFloorBinder) {
|
||||
this.parent = parent;
|
||||
this.checkListen();
|
||||
}
|
||||
|
||||
private checkListen() {
|
||||
if (this.parent) LayerFloorBinder.listenedBinder.delete(this);
|
||||
else LayerFloorBinder.listenedBinder.add(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在下一帧进行绑定数据更新
|
||||
*/
|
||||
updateBind() {
|
||||
if (this.needUpdate) return;
|
||||
this.needUpdate = true;
|
||||
this.layer.requestBeforeFrame(() => {
|
||||
this.needUpdate = false;
|
||||
this.updateBindData();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置图块
|
||||
*/
|
||||
setBlock(block: AllNumbers, x: number, y: number) {
|
||||
this.layer.putRenderData([block], 1, x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 立刻更新绑定数据,而非下一帧
|
||||
*/
|
||||
updateBindData() {
|
||||
const floor = this.getFloor();
|
||||
if (!floor) return;
|
||||
core.extractBlocks(floor);
|
||||
const map = core.status.maps[floor];
|
||||
this.layer.setMapSize(map.width, map.height);
|
||||
const image = core.status.maps[this.getFloor()].images;
|
||||
if (this.layer.layer === 'event') {
|
||||
const m = map.map;
|
||||
this.layer.putRenderData(m.flat(), map.width, 0, 0);
|
||||
} else {
|
||||
const m = core.maps._getBgFgMapArray(this.layer.layer!, floor);
|
||||
this.layer.putRenderData(m.flat(), map.width, 0, 0);
|
||||
}
|
||||
if (this.layer.layer === 'bg') {
|
||||
// 别忘了背景图块
|
||||
this.layer.setBackground(texture.idNumberMap[map.defaultGround]);
|
||||
}
|
||||
const toDraw = image?.filter(v => v.canvas === this.layer.layer);
|
||||
this.layer.setFloorImage(toDraw ?? []);
|
||||
}
|
||||
|
||||
awake(layer: Layer) {
|
||||
this.layer = layer;
|
||||
if (!this.parent) {
|
||||
const group = layer.parent;
|
||||
if (group instanceof LayerGroup) {
|
||||
const ex = group.getExtends('floor-binder');
|
||||
if (ex instanceof LayerGroupFloorBinder) {
|
||||
ex.checkLayerExtends(layer);
|
||||
this.parent = ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.checkListen();
|
||||
}
|
||||
|
||||
onDestroy(_layer: Layer) {
|
||||
LayerFloorBinder.listenedBinder.delete(this);
|
||||
this.parent?.layerBinders.delete(this);
|
||||
}
|
||||
}
|
||||
|
||||
interface DoorAnimateRenderable {
|
||||
renderable: LayerMovingRenderable;
|
||||
count: number;
|
||||
perTime: number;
|
||||
}
|
||||
|
||||
export class LayerDoorAnimate implements ILayerRenderExtends {
|
||||
id: string = 'door-animate';
|
||||
|
||||
layer!: Layer;
|
||||
|
||||
private moving: Set<LayerMovingRenderable> = new Set();
|
||||
|
||||
private getRenderable(block: Block): DoorAnimateRenderable | null {
|
||||
const { x, y, id } = block;
|
||||
const renderable = texture.getRenderable(id);
|
||||
if (!renderable) return null;
|
||||
const image = renderable.autotile
|
||||
? renderable.image[0]
|
||||
: renderable.image;
|
||||
const time = block.event.doorInfo?.time ?? 160;
|
||||
const frame = renderable.render.length;
|
||||
const perTime = time / frame;
|
||||
|
||||
const data: LayerMovingRenderable = {
|
||||
x,
|
||||
y,
|
||||
zIndex: y,
|
||||
image,
|
||||
autotile: false,
|
||||
animate: 0,
|
||||
frame,
|
||||
bigImage: false,
|
||||
render: renderable.render,
|
||||
alpha: 1
|
||||
};
|
||||
return { renderable: data, count: frame, perTime };
|
||||
}
|
||||
|
||||
/**
|
||||
* 开门
|
||||
* @param block 图块信息
|
||||
*/
|
||||
async openDoor(block: Block) {
|
||||
const renderable = this.getRenderable(block);
|
||||
if (!renderable) return Promise.reject();
|
||||
const { renderable: data, count: frame, perTime } = renderable;
|
||||
data.animate = 0;
|
||||
this.moving.add(data);
|
||||
this.layer.requestUpdateMoving();
|
||||
|
||||
let now = 0;
|
||||
while (now < frame) {
|
||||
await sleep(perTime);
|
||||
data.animate = ++now;
|
||||
this.layer.update(this.layer);
|
||||
}
|
||||
|
||||
this.moving.delete(data);
|
||||
this.layer.requestUpdateMoving();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* 关门
|
||||
* @param block 图块信息
|
||||
*/
|
||||
async closeDoor(block: Block) {
|
||||
const renderable = this.getRenderable(block);
|
||||
if (!renderable) return Promise.reject();
|
||||
const { renderable: data, count: frame, perTime } = renderable;
|
||||
data.animate = frame - 1;
|
||||
this.moving.add(data);
|
||||
this.layer.requestUpdateMoving();
|
||||
|
||||
let now = 0;
|
||||
while (now >= 0) {
|
||||
await sleep(perTime);
|
||||
data.animate = --now;
|
||||
this.layer.update(this.layer);
|
||||
}
|
||||
this.moving.delete(data);
|
||||
this.layer.requestUpdateMoving();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
awake(layer: Layer) {
|
||||
this.layer = layer;
|
||||
doorAdapter.add(this);
|
||||
}
|
||||
|
||||
onMovingUpdate(_layer: Layer, renderable: LayerMovingRenderable[]): void {
|
||||
renderable.push(...this.moving);
|
||||
}
|
||||
|
||||
onDestroy(_layer: Layer): void {
|
||||
doorAdapter.remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
const doorAdapter = new RenderAdapter<LayerDoorAnimate>('door-animate');
|
||||
doorAdapter.receive('openDoor', (item, block: Block) => {
|
||||
return item.openDoor(block);
|
||||
});
|
||||
doorAdapter.receive('closeDoor', (item, block: Block) => {
|
||||
return item.closeDoor(block);
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { RenderAdapter } from '@motajs/render-core';
|
||||
import { HeroRenderer } from './hero';
|
||||
import { ILayerGroupRenderExtends, LayerGroup } from './layer';
|
||||
import { LayerGroupFloorBinder } from './floor';
|
||||
import { LayerGroupFloorBinder } from './layer';
|
||||
import { hyper, TimingFn } from 'mutate-animate';
|
||||
|
||||
export class FloorViewport implements ILayerGroupRenderExtends {
|
||||
|
@ -4,7 +4,7 @@ import {
|
||||
generateBinary,
|
||||
keycode,
|
||||
spliceBy
|
||||
} from '@motajs/legacy-ui';
|
||||
} from '@motajs/legacy-common';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
|
@ -1,8 +1,12 @@
|
||||
import { EventEmitter, Listener } from '@motajs/legacy-common';
|
||||
import {
|
||||
EventEmitter,
|
||||
Listener,
|
||||
deleteWith,
|
||||
flipBinary
|
||||
} from '@motajs/legacy-common';
|
||||
import { KeyCode } from '@motajs/client-base';
|
||||
import { gameKey } from './hotkey';
|
||||
import { unwarpBinary } from './hotkey';
|
||||
import { deleteWith, flipBinary } from '@motajs/legacy-ui';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { shallowReactive } from 'vue';
|
||||
|
||||
|
@ -24,6 +24,14 @@ const aliases = glob.sync('packages/*/src').map((srcPath) => {
|
||||
};
|
||||
});
|
||||
|
||||
const aliasesUser = glob.sync('packages-user/*/src').map((srcPath) => {
|
||||
const packageName = path.basename(path.dirname(srcPath));
|
||||
return {
|
||||
find: `@user/${packageName}`,
|
||||
replacement: path.resolve(__dirname, srcPath),
|
||||
};
|
||||
});
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
@ -51,7 +59,8 @@ export default defineConfig({
|
||||
base: `./`,
|
||||
resolve: {
|
||||
alias: [
|
||||
...aliases
|
||||
...aliases,
|
||||
...aliasesUser
|
||||
]
|
||||
},
|
||||
build: {
|
||||
|
Loading…
Reference in New Issue
Block a user