refactor: 修复所有的循环引用

This commit is contained in:
unanmed 2025-03-09 19:15:22 +08:00
parent 4fda246a9a
commit 23a8c0bf3a
82 changed files with 1015 additions and 2087 deletions

View File

@ -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[] = [

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,3 @@
import { HeroSkill } from './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;
}
}

View File

@ -1,7 +1,7 @@
import { logger } from '@motajs/common';
import { EventEmitter } from 'eventemitter3';
import { cloneDeep } from 'lodash-es';
import { HeroSkill, NightSpecial } from '../mechanism/misc';
import { HeroSkill, NightSpecial } from '../mechanism';
/**
*

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import { Animation, hyper, linear, power, sleep } from 'mutate-animate';
import { Chase, ChaseData, IChaseController } from './chase';
import { completeAchievement } from '@motajs/legacy-ui';
// import { completeAchievement } from '@motajs/legacy-ui';
import {
Camera,
CameraAnimation,
@ -206,7 +206,7 @@ export function initChase(): IChaseController {
core.removeFlag('chaseId');
if (success) {
completeAchievement('challenge', 0);
// completeAchievement('challenge', 0);
}
});

View File

@ -1,5 +1 @@
export * from './gameCanvas';
export * from './halo';
export * from './pointShader';
export * from './pop';
export * from './portal';

View File

@ -1,6 +1,3 @@
export * from './boss';
export * from './chase';
export * from './fx';
export * from './fallback';
export * from '../../client-modules/src/render/loopMap';

View File

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

View File

@ -1,6 +1,6 @@
// @ts-nocheck
import { HeroSkill } from '@/game/mechanism/misc';
import { HeroSkill } from '@user/data-state';
// 所有的主动技能效果
var ignoreInJump = {

View File

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

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

View File

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

View File

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

View File

@ -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 () => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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;
/**
* uiui都会同时关闭掉
* @param num ui的唯一标识符
*/
close(num: number): void;
/**
* id关闭所有同id的uiui后的所有ui都关闭掉
* @param id ui的id
*/
closeByName(id: string): void;
/**
* ui
* @param id ui的id
* @param vOn
* @param vBind
* @returns ui的唯一标识符
*/
open(id: string, vBind?: UiVBind, vOn?: UiVOn): void;
/**
* ui
* @param id ui的id
* @param ui GameUi实例
*/
register(...ui: IGameUi[]): void;
/**
* ui
* @param id ui的id
*/
unregister(...id: string[]): void;
/**
* ui的唯一标识符进行聚焦
* @param num ui的唯一标识符
*/
focusByNum(num: number): void;
/**
* ui
* @param num ui的唯一标识符
*/
getByNum(num: number): void;
/**
* ui的唯一标识符来判断当前是否存在某个ui
* @param id ui的唯一标识符
*/
hasName(id: string): void;
}

View File

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

View File

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

View File

@ -1,85 +0,0 @@
<template>
<div id="enemy-target">
<div id="enemy-desc">
<span>怪物描述</span>
<Scroll id="enemy-desc-scroll">
<span
>&nbsp;&nbsp;&nbsp;&nbsp;{{
enemy.enemy.enemy.description
}}</span
>
</Scroll>
</div>
<a-divider dashed style="border-color: #ddd4"></a-divider>
<div>
<div id="mark-target">
<span
id="mark-info"
:style="{ color: marked ? 'lightgreen' : 'lightcoral' }"
>{{ marked ? '已标记该怪物' : '未标记该怪物' }}</span
>
<span class="button-text" @click.stop="mark">{{
marked ? '取消标记该怪物' : '标记该怪物为目标'
}}</span>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import Scroll from '../components/scroll.vue';
import { detailInfo } from '../tools/book';
import { hasMarkedEnemy, markEnemy } from '../mark';
const enemy = detailInfo.enemy!;
const marked = ref(hasMarkedEnemy(enemy.enemy.id));
function mark() {
markEnemy(enemy.enemy.id);
marked.value = hasMarkedEnemy(enemy.enemy.id);
}
</script>
<style lang="less" scoped>
#enemy-target {
width: 100%;
font-size: 160%;
}
#enemy-desc {
width: 100%;
height: 30vh;
display: flex;
flex-direction: column;
align-items: center;
}
#enemy-desc-scroll {
height: 100%;
width: 100%;
}
#mark-target {
margin-top: 10%;
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-around;
font-size: 3.3vh;
}
#mark-info {
transition: color 0.2s linear;
}
@media screen and (max-width: 600px) {
#enemy-desc {
font-size: 70%;
}
#mark-target {
font-size: 100%;
}
}
</style>

View File

@ -1,6 +1,6 @@
import { Danmaku } from '../danmaku';
import { Component, h } from 'vue';
import { mainSetting } from './ui';
import { mainSetting } from './settingIns';
import { getIconHeight } from '../utils';
import { BoxAnimate } from '../components';

View File

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

View File

@ -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', () => {

View File

@ -4,3 +4,5 @@ export * from './danmaku';
export * from './fixed';
export * from './hotkey';
export * from './keyboard';
export * from './uiIns';
export * from './settingIns';

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,113 +0,0 @@
<template>
<Box id="complete-box">
<div id="complete">
<span>完成成就&nbsp;&nbsp;&nbsp;&nbsp;{{ achi.name }}</span>
<a-progress
id="progress"
:percent="progress * 100"
:strokeColor="{
'0%': '#108ee9',
'100%': '#87d068'
}"
:strokeWidth="height / 200"
:showInfo="false"
></a-progress>
<span id="point-number">成就点: {{ now }} / {{ totalPoint }}</span>
</div>
</Box>
</template>
<script lang="ts" setup>
import { sleep, Ticker } from 'mutate-animate';
import { computed, onMounted, ref } from 'vue';
import Box from '../components/box.vue';
import list from '../data/achievement.json';
import { AchievementType, getNowPoint, totalPoint } from '../tools/achievement';
import { GameUi } from '../controller';
import { fixedUi } from '../preset/ui';
const height = window.innerHeight;
const props = defineProps<{
num: number;
ui: GameUi;
complete: string;
}>();
const c = props.complete.split(',');
const type = c[0] as AchievementType;
const index = parseInt(c[1]);
const achi = list[type][index];
const point = achi.point;
const nowPoint = getNowPoint() - point;
const now = ref(nowPoint);
const progress = computed(() => now.value / totalPoint);
onMounted(async () => {
await sleep(500);
const ticker = new Ticker();
const time = Date.now();
ticker.add(() => {
const nowTime = Date.now();
if (nowTime - time > 1000) {
now.value = nowPoint + point;
ticker.destroy();
}
const ratio = (nowTime - time) / 1000;
now.value = Math.floor(nowPoint + point * ratio);
});
await sleep(4600);
fixedUi.close(props.num);
});
</script>
<style lang="less" scoped>
#complete-box {
width: 30vw;
height: 13vh;
left: 35vw;
position: fixed;
background-color: #000d;
animation: ani 5s ease 0s 1 forwards running;
z-index: 10000;
}
#complete {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
font-family: 'normal';
font-size: 2.2vh;
align-items: center;
justify-content: center;
}
#progress {
width: 90%;
}
@keyframes ani {
0% {
top: -30vh;
}
20% {
top: 4vh;
}
80% {
top: 4vh;
}
100% {
top: -30vh;
}
}
@media screen and (max-width: 600px) {
#complete-box {
width: 90vw;
left: 5%;
}
}
</style>

View File

@ -30,7 +30,7 @@
import { nextTick, onUnmounted, reactive, watch } from 'vue';
import { 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;
}

View File

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

View File

@ -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(() => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 () => {

View File

@ -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 `${'&nbsp;'.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>

View File

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

View File

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

View File

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

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

View File

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

View File

@ -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 `${'&nbsp;'.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 `${'&nbsp;'.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()) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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