Compare commits

..

No commits in common. "82d58756b708515ced6ebcbce8675d3dab8fbc75" and "4c6343227f1d62005f93c039be737cbec010bf3a" have entirely different histories.

18 changed files with 206 additions and 415 deletions

View File

@ -1,5 +1,5 @@
import { gameKey } from '@motajs/system-action';
import { MAIN_WIDTH, MAIN_HEIGHT, POP_BOX_WIDTH, CENTER_LOC } from './shared';
import { MAIN_WIDTH, MAIN_HEIGHT } from './shared';
import {
saveSave,
mainUIController,
@ -9,7 +9,6 @@ import {
ReplaySettingsUI,
openViewMap
} from './ui';
import { ElementLocator } from '@motajs/render-core';
export function createAction() {
gameKey
@ -23,13 +22,11 @@ export function createAction() {
saveLoad(mainUIController, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]);
})
.realize('menu', () => {
const loc = CENTER_LOC.slice() as ElementLocator;
loc[2] = POP_BOX_WIDTH;
openSettings(mainUIController, loc);
openSettings(mainUIController, [420, 240, 240, 400, 0.5, 0.5]);
})
.realize('replay', () => {
mainUIController.open(ReplaySettingsUI, {
loc: CENTER_LOC
loc: [420, 240, void 0, void 0, 0.5, 0.5]
});
})
.realize('viewMap', () => {

View File

@ -33,8 +33,6 @@ export interface ConfirmBoxProps extends DefaultProps, TextContentProps {
color?: CanvasStyle;
/** 对话框边框颜色,当未设置 winskin 时生效 */
border?: CanvasStyle;
/** 按键作用域,如果需要同作用域按键,那么需要传入 */
scope?: symbol;
}
export type ConfirmBoxEmits = {
@ -55,8 +53,7 @@ const confirmBoxProps = {
'winskin',
'defaultYes',
'color',
'border',
'scope'
'border'
],
emits: ['no', 'yes']
} satisfies SetupComponentOptions<
@ -146,7 +143,7 @@ export const ConfirmBox = defineComponent<
noSize.value = [width, height];
};
const [key] = useKey(false, props.scope);
const [key] = useKey();
key.realize('confirm', () => {
if (selected.value) emit('yes');
else emit('no');
@ -245,8 +242,6 @@ export interface ChoicesProps extends DefaultProps, TextContentProps {
interval?: number;
/** 默认选中的选项索引 */
selected?: number;
/** 按键作用域,如果需要同作用域按键,那么需要传入,例如系统设置 UI */
scope?: symbol;
}
export type ChoicesEmits = {
@ -270,8 +265,7 @@ const choicesProps = {
'titleFill',
'pad',
'interval',
'selected',
'scope'
'selected'
],
emits: ['choose']
} satisfies SetupComponentOptions<
@ -470,7 +464,7 @@ export const Choices = defineComponent<
selected.value = 0;
};
const [key] = useKey(false, props.scope);
const [key] = useKey();
key.realize('moveUp', () => {
if (selected.value === 0) {
if (pageCom.value?.now() !== 0) {
@ -511,25 +505,27 @@ export const Choices = defineComponent<
color={props.color ?? '#333'}
border={props.border}
/>
<text
hidden={!hasTitle.value}
loc={titleLoc.value}
text={props.title}
font={props.titleFont ?? new Font(void 0, 18)}
fillStyle={props.titleFill ?? 'gold'}
zIndex={5}
onSetText={updateTitleHeight}
/>
<TextContent
{...attrs}
hidden={!hasText.value}
text={props.text}
loc={contentLoc.value}
width={contentWidth.value}
zIndex={5}
autoHeight
onUpdateHeight={updateContentHeight}
/>
{hasTitle.value && (
<text
loc={titleLoc.value}
text={props.title}
font={props.titleFont ?? new Font(void 0, 18)}
fillStyle={props.titleFill ?? 'gold'}
zIndex={5}
onSetText={updateTitleHeight}
/>
)}
{hasText.value && (
<TextContent
{...attrs}
text={props.text}
loc={contentLoc.value}
width={contentWidth.value}
zIndex={5}
autoHeight
onUpdateHeight={updateContentHeight}
/>
)}
<Page
ref={pageCom}
loc={choiceLoc.value}

View File

@ -5,14 +5,6 @@ import { computed, defineComponent, onMounted, ref, watch } from 'vue';
import { Scroll, ScrollExpose } from './scroll';
import { Font } from '@motajs/render-style';
import { MotaOffscreenCanvas2D } from '@motajs/render-core';
import {
HALF_STATUS_WIDTH,
STATUS_BAR_HEIGHT,
STATUS_BAR_WIDTH
} from '../shared';
const SCROLL_HEIGHT = STATUS_BAR_HEIGHT - 280;
const HALF_HEIGHT = SCROLL_HEIGHT / 2;
export interface FloorSelectorProps extends DefaultProps {
floors: FloorIds[];
@ -75,7 +67,7 @@ export const FloorSelector = defineComponent<
const getGradient = (ctx: CanvasRenderingContext2D) => {
if (gradient) return gradient;
gradient = ctx.createLinearGradient(0, 0, 0, SCROLL_HEIGHT);
gradient = ctx.createLinearGradient(0, 0, 0, 200);
gradient.addColorStop(0, 'rgba(255,255,255,0)');
gradient.addColorStop(0.2, 'rgba(255,255,255,1)');
gradient.addColorStop(0.8, 'rgba(255,255,255,1)');
@ -120,24 +112,12 @@ export const FloorSelector = defineComponent<
return () => (
<container>
<text
text={floorName.value}
loc={[HALF_STATUS_WIDTH, 24]}
anc={[0.5, 0.5]}
/>
<g-line line={[48, 40, STATUS_BAR_WIDTH - 48, 40]} lineWidth={1} />
<g-line
line={[
48,
STATUS_BAR_HEIGHT - 40,
STATUS_BAR_WIDTH - 48,
STATUS_BAR_HEIGHT - 40
]}
lineWidth={1}
/>
<text text={floorName.value} loc={[90, 24]} anc={[0.5, 0.5]} />
<g-line line={[48, 40, 132, 40]} lineWidth={1} />
<g-line line={[48, 440, 132, 440]} lineWidth={1} />
<text
text="退出"
loc={[90, STATUS_BAR_HEIGHT - 24]}
loc={[90, 456]}
anc={[0.5, 0.5]}
cursor="pointer"
onClick={close}
@ -158,25 +138,25 @@ export const FloorSelector = defineComponent<
/>
<text
text="「 下移一层 」"
loc={[90, STATUS_BAR_HEIGHT - 110]}
loc={[90, 370]}
anc={[0.5, 0.5]}
cursor="pointer"
onClick={() => changeFloor(-1)}
/>
<text
text="「 下移十层 」"
loc={[90, STATUS_BAR_HEIGHT - 70]}
loc={[90, 410]}
anc={[0.5, 0.5]}
cursor="pointer"
onClick={() => changeFloor(-10)}
/>
<container loc={[0, 140, 144, SCROLL_HEIGHT]}>
<container loc={[0, 140, 144, 200]}>
<Scroll
ref={scrollRef}
loc={[0, 0, 144, SCROLL_HEIGHT]}
loc={[0, 0, 144, 200]}
noscroll
zIndex={10}
padEnd={HALF_HEIGHT - 12}
padEnd={88}
>
{floors.value.map((v, i, a) => {
const floor = core.floors[v];
@ -188,7 +168,7 @@ export const FloorSelector = defineComponent<
return (
<container
nocache
loc={[0, i * 24 + HALF_HEIGHT - 12, 144, 24]}
loc={[0, i * 24 + 88, 144, 24]}
key={v}
>
<text
@ -215,14 +195,14 @@ export const FloorSelector = defineComponent<
})}
</Scroll>
<g-line
line={[130, 0, 130, SCROLL_HEIGHT]}
line={[130, 0, 130, 200]}
zIndex={5}
lineWidth={1}
strokeStyle="#aaa"
/>
<sprite
zIndex={20}
loc={[0, 0, 144, SCROLL_HEIGHT]}
loc={[0, 0, 144, 200]}
nocache
noevent
render={renderMask}

View File

@ -118,16 +118,6 @@ export const Page = defineComponent<
const height = computed(() => props.loc[3] ?? 200);
const round = computed(() => font.value.size / 4);
const nowPageFont = computed(() => Font.clone(font.value, { weight: 700 }));
/** 页码的横向间距 */
const interval = computed(() => {
const size = font.value.size * 1.5;
const max = size * 9;
if (width.value > max) {
return size;
} else {
return (width.value - size * 5) / 4;
}
});
// 左右箭头的颜色
const leftColor = computed(() => (isFirst.value ? '#666' : '#ddd'));
@ -145,12 +135,11 @@ export const Page = defineComponent<
pageLoc.value = [0, height.value - pageH, width.value, pageH];
const center = width.value / 2;
const size = font.value.size * 1.5;
const int = size + interval.value;
nowPageLoc.value = [center, 0, size, size, 0.5, 0];
leftPageLoc.value = [center - int, 0, size, size, 0.5, 0];
leftLoc.value = [center - int * 2, 0, size, size, 0.5, 0];
rightPageLoc.value = [center + int, 0, size, size, 0.5, 0];
rightLoc.value = [center + int * 2, 0, size, size, 0.5, 0];
leftPageLoc.value = [center - size * 1.5, 0, size, size, 0.5, 0];
leftLoc.value = [center - size * 3, 0, size, size, 0.5, 0];
rightPageLoc.value = [center + size * 1.5, 0, size, size, 0.5, 0];
rightLoc.value = [center + size * 3, 0, size, size, 0.5, 0];
};
const updateArrowPath = () => {

View File

@ -361,8 +361,6 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
//#region 事件监听
// todo: 滑动操作时的滚动惯性
const customPropagate = <T extends ActionType>(
type: T,
progress: EventProgress,

View File

@ -130,7 +130,7 @@ export const TextContent = defineComponent<
let needUpdate = false;
const retype = () => {
if (needUpdate || props.hidden) return;
if (needUpdate) return;
needUpdate = true;
if (!spriteElement.value) {
needUpdate = false;

View File

@ -13,12 +13,6 @@ import { RenderableData, texture } from './cache';
import { BlockCacher, CanvasCacheItem, ICanvasCacheItem } from './block';
import { IAnimateFrame, renderEmits } from './frame';
import { EventEmitter } from 'eventemitter3';
import {
MAP_BLOCK_HEIGHT,
MAP_BLOCK_WIDTH,
MAP_HEIGHT,
MAP_WIDTH
} from '../shared';
export interface ILayerGroupRenderExtends {
/** 拓展的唯一标识符 */
@ -110,7 +104,7 @@ export class LayerGroup
// static list: Set<LayerGroup> = new Set();
cellSize: number = 32;
blockSize: number = MAP_BLOCK_WIDTH;
blockSize: number = core._WIDTH_;
/** 当前楼层 */
floorId?: FloorIds;
@ -132,7 +126,7 @@ export class LayerGroup
this.setHD(true);
this.setAntiAliasing(false);
this.size(MAP_WIDTH, MAP_HEIGHT);
this.size(core._PX_, core._PY_);
this.on('afterRender', () => {
this.releaseNeedRender();
@ -384,8 +378,8 @@ export function calNeedRenderOf(
cell: number,
block: BlockCacher<any>
): Set<number> {
const w = MAP_BLOCK_WIDTH * cell;
const h = MAP_BLOCK_HEIGHT * cell;
const w = core._WIDTH_ * cell;
const h = core._HEIGHT_ * cell;
const size = block.blockSize;
const width = block.blockData.width;
@ -610,7 +604,7 @@ export class Layer extends Container<ELayerEvent> {
block: BlockCacher<ICanvasCacheItem> = new BlockCacher(
0,
0,
MAP_BLOCK_WIDTH,
core._WIDTH_,
4
);
@ -631,20 +625,20 @@ export class Layer extends Container<ELayerEvent> {
// this.setHD(false);
this.setAntiAliasing(false);
this.size(MAP_WIDTH, MAP_HEIGHT);
this.size(core._PX_, core._PY_);
this.staticMap.setHD(false);
this.staticMap.setAntiAliasing(false);
this.staticMap.size(MAP_WIDTH, MAP_HEIGHT);
this.staticMap.size(core._PX_, core._PY_);
this.movingMap.setHD(false);
this.movingMap.setAntiAliasing(false);
this.movingMap.size(MAP_WIDTH, MAP_HEIGHT);
this.movingMap.size(core._PX_, core._PY_);
this.backMap.setHD(false);
this.backMap.setAntiAliasing(false);
this.backMap.size(MAP_WIDTH, MAP_HEIGHT);
this.backMap.size(core._PX_, core._PY_);
this.main.setAntiAliasing(false);
this.main.setHD(false);
this.main.size(MAP_WIDTH, MAP_HEIGHT);
this.main.size(core._PX_, core._PY_);
this.appendChild(this.main);
this.main.setRenderFn((canvas, transform) => {
@ -795,7 +789,7 @@ export class Layer extends Container<ELayerEvent> {
const [sx, sy, w, h] = data.render[i];
canvas.setHD(false);
canvas.setAntiAliasing(false);
canvas.size(MAP_WIDTH, MAP_HEIGHT);
canvas.size(core._PX_, core._PY_);
temp.size(w, h);
const img = data.autotile ? data.image[0b11111111] : data.image;
@ -1210,7 +1204,7 @@ export class Layer extends Container<ELayerEvent> {
const temp = this.requireCanvas(true, false);
temp.setAntiAliasing(false);
temp.setHD(false);
temp.size(MAP_WIDTH, MAP_HEIGHT);
temp.size(core._PX_, core._PY_);
// 先画到临时画布,用于缓存
for (let nx = sx; nx < ex; nx++) {
@ -1267,7 +1261,7 @@ export class Layer extends Container<ELayerEvent> {
const [a, b, , c, d, , e, f] = mat;
ctx.setTransform(a, b, c, d, e, f);
const max1 = 1 / Math.min(a, b, c, d) ** 2;
const max2 = Math.max(MAP_WIDTH, MAP_HEIGHT) * 2;
const max2 = Math.max(core._PX_, core._PY_) * 2;
const r = (max1 * max2) ** 2;
this.movingRenderable.forEach(v => {

View File

@ -1,99 +1,21 @@
import { ElementLocator } from '@motajs/render-core';
// 地图格子宽高,此处仅影响画面,不影响游戏内逻辑,游戏内逻辑地图大小请在 core.js 中修改
export const MAP_BLOCK_WIDTH = 15;
export const MAP_BLOCK_HEIGHT = 15;
// 本文件为 UI 配置文件,你可以修改下面的每个常量来控制 UI 的显示参数,每个常量都有注释说明
// 地图像素宽高
export const MAP_WIDTH = 32 * MAP_BLOCK_WIDTH;
export const MAP_HEIGHT = 32 * MAP_BLOCK_HEIGHT;
//#region 地图
/** 每个格子的宽高 */
export const CELL_SIZE = 32;
/** 地图格子宽度,此处仅影响画面,不影响游戏内逻辑,游戏内逻辑地图大小请在 core.js 中修改 */
export const MAP_BLOCK_WIDTH = 13;
/** 地图格子高度,此处仅影响画面,不影响游戏内逻辑,游戏内逻辑地图大小请在 core.js 中修改 */
export const MAP_BLOCK_HEIGHT = 13;
/** 地图像素宽度 */
export const MAP_WIDTH = CELL_SIZE * MAP_BLOCK_WIDTH;
/** 地图像素高度 */
export const MAP_HEIGHT = CELL_SIZE * MAP_BLOCK_HEIGHT;
//#region 状态栏
/** 状态栏像素宽度 */
// 状态栏像素宽高
export const STATUS_BAR_WIDTH = 180;
/** 状态栏像素高度 */
export const STATUS_BAR_HEIGHT = 32 * MAP_BLOCK_HEIGHT;
/** 右侧状态栏的横坐标 */
// 右侧状态栏的横坐标
export const RIGHT_STATUS_POS = STATUS_BAR_WIDTH + MAP_WIDTH;
/** 是否启用右侧状态栏 */
// 是否启用右侧状态栏
export const ENABLE_RIGHT_STATUS_BAR = true;
/** 状态栏数量,启用右侧状态栏为两个,不启用为一个 */
export const STATUS_BAR_COUNT = ENABLE_RIGHT_STATUS_BAR ? 2 : 1;
/** 状态栏宽度的一半 */
export const HALF_STATUS_WIDTH = STATUS_BAR_WIDTH / 2;
//#region 游戏画面
/** 游戏画面像素宽度,宽=地图宽度+状态栏宽度*状态栏数量 */
// 游戏画面像素宽高,宽=地图宽度+状态栏宽度*状态栏数量
export const MAIN_WIDTH = MAP_WIDTH + STATUS_BAR_WIDTH * STATUS_BAR_COUNT;
/** 游戏画面像素高度 */
export const MAIN_HEIGHT = MAP_HEIGHT;
/** 游戏画面宽度的一半 */
export const HALF_WIDTH = MAIN_WIDTH / 2;
/** 游戏画面高度的一半 */
export const HALF_HEIGHT = MAIN_HEIGHT / 2;
/** 全屏显示的 loc */
export const FULL_LOC: ElementLocator = [0, 0, MAIN_WIDTH, MAIN_HEIGHT];
/** 居中显示的 loc */
export const CENTER_LOC: ElementLocator = [
HALF_WIDTH,
HALF_HEIGHT,
void 0,
void 0,
0.5,
0.5
];
//#region 通用配置
/** 弹框的宽度,使用在内置 UI 与组件中,包括确认框、选择框、等待框等 */
export const POP_BOX_WIDTH = MAP_WIDTH / 2;
//#region 存档界面
/** 存档缩略图尺寸 */
export const SAVE_ITEM_SIZE = 150;
/** 单个存档上方显示第几号存档的高度 */
export const SAVE_ITEM_TOP = 24;
/** 单个存档下方显示这个存档信息的高度 */
export const SAVE_ITEM_DOWN = 16;
/** 单个存档高度,包括存档下方的信息 */
export const SAVE_ITEM_HEIGHT = SAVE_ITEM_SIZE + SAVE_ITEM_TOP + SAVE_ITEM_DOWN;
/** 存档间距 */
export const SAVE_INTERVAL = 30;
/** 存档下巴高度,即下方显示页码和返回按钮的高度 */
export const SAVE_DOWN_PAD = 30;
/** 存档页码数,调高并不会影响性能,但是如果玩家存档太多的话会导致存档体积很大 */
export const SAVE_PAGES = 1000;
//#region 标题界面
/** 标题文字宽度 */
export const TITLE_WIDTH = 640;
/** 标题文字高度 */
export const TITLE_HEIGHT = 100;
/** 标题文字宽度的一半 */
export const HALF_TITLE_WIDTH = TITLE_WIDTH / 2;
/** 标题文字高度的一半 */
export const HALF_TITLE_HEIGHT = TITLE_HEIGHT / 2;
/** 标题文字中心横坐标 */
export const TITLE_X = HALF_WIDTH;
/** 标题文字中心纵坐标 */
export const TITLE_Y = 120;
/** 标题界面按钮宽度,如果文字被裁剪可以考虑扩大此值 */
export const BUTTONS_WIDTH = 200;
/** 标题界面按钮高度,如果文字被裁剪可以考虑扩大此值 */
export const BUTTONS_HEIGHT = 160;
/** 标题界面按钮左上角横坐标 */
export const BUTTONS_X = 50;
/** 标题界面按钮左上角纵坐标 */
export const BUTTONS_Y = MAIN_HEIGHT - BUTTONS_HEIGHT;

View File

@ -16,19 +16,7 @@ import {
} from 'vue';
import { getConfirm, Page, PageExpose, Thumbnail } from '../components';
import { useKey } from '../use';
import {
HALF_HEIGHT,
HALF_WIDTH,
MAP_WIDTH,
POP_BOX_WIDTH,
SAVE_DOWN_PAD,
SAVE_INTERVAL,
SAVE_ITEM_DOWN,
SAVE_ITEM_HEIGHT,
SAVE_ITEM_SIZE,
SAVE_ITEM_TOP,
SAVE_PAGES
} from '../shared';
import { MAP_WIDTH } from '../shared';
import { getSave, SaveData, adjustGrid, IGridLayoutData } from '../utils';
export const enum SaveMode {
@ -73,11 +61,10 @@ export const SaveItem = defineComponent<SaveItemProps>(props => {
const statusFont = new Font('normal', 14);
const w = computed(() => props.loc[2] ?? 200);
const h = computed(() => props.loc[3] ?? 200);
const lineWidth = computed(() => (props.selected ? 4 : 2));
const imgLoc = computed<ElementLocator>(() => {
const size = w.value - 4;
return [2, SAVE_ITEM_TOP, size, size];
return [2, 24, size, size];
});
const name = computed(() => {
@ -115,7 +102,7 @@ export const SaveItem = defineComponent<SaveItemProps>(props => {
<text
text={name.value}
font={font}
loc={[w.value / 2, SAVE_ITEM_TOP - 4]}
loc={[w.value / 2, 20]}
anc={[0.5, 1]}
/>
<g-rect
@ -143,7 +130,7 @@ export const SaveItem = defineComponent<SaveItemProps>(props => {
text={statusText.value}
fillStyle="yellow"
font={statusFont}
loc={[w.value / 2, h.value - SAVE_ITEM_DOWN + 2]}
loc={[w.value / 2, w.value + 28]}
anc={[0.5, 0]}
/>
</container>
@ -152,8 +139,12 @@ export const SaveItem = defineComponent<SaveItemProps>(props => {
export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
(props, { emit }) => {
const font = Font.defaults({ size: 18 });
const pageFont = Font.defaults({ size: 14 });
const itemSize = 150;
const itemHeight = itemSize + 40;
const interval = 30;
const font = new Font('normal', 18);
const pageFont = new Font('normal', 14);
/** 当前页上被选中的存档的posIndex */
const selected = ref(0);
@ -169,16 +160,16 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
const grid = computed<IGridLayoutData>(() =>
adjustGrid(
width.value,
height.value - SAVE_DOWN_PAD,
SAVE_ITEM_SIZE,
SAVE_ITEM_HEIGHT,
SAVE_INTERVAL
height.value - 30,
itemSize,
itemHeight,
interval
)
);
const contentLoc = computed<ElementLocator>(() => {
const cx = width.value / 2;
const cy = (height.value - SAVE_DOWN_PAD) / 2;
const cy = (height.value - 30) / 2;
return [cx, cy, grid.value.width, grid.value.height, 0.5, 0.5];
});
@ -253,8 +244,8 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
const confirm = await getConfirm(
props.controller,
`确认要删除存档 ${index + 1}`,
[HALF_WIDTH, HALF_HEIGHT, void 0, void 0, 0.5, 0.5],
POP_BOX_WIDTH,
[420, 240, void 0, void 0, 0.5, 0.5],
240,
{ winskin: 'winskin2.png' }
);
if (confirm) {
@ -379,9 +370,6 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
);
//#region 事件监听
// todo: 按住快速切换页码
const wheel = (ev: IWheelEvent) => {
const delta = Math.sign(ev.wheelY);
if (ev.ctrlKey) {
@ -396,7 +384,7 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
<Page
ref={pageRef}
loc={[0, 0, width.value, height.value - 10]}
pages={SAVE_PAGES}
pages={1000}
font={pageFont}
v-model:page={now.value}
onWheel={wheel}

View File

@ -24,8 +24,6 @@ import { openStatistics } from './statistics';
import { saveWithExist } from './save';
import { compressToBase64 } from 'lz-string';
import { ViewMapUI } from './viewmap';
import { CENTER_LOC, FULL_LOC, MAIN_HEIGHT, POP_BOX_WIDTH } from '../shared';
import { useKey } from '../use';
export interface MainSettingsProps
extends Partial<ChoicesProps>,
@ -63,9 +61,6 @@ export const MainSettings = defineComponent<MainSettingsProps>(props => {
[MainChoice.Back, '返回游戏']
];
const [key, scope] = useKey();
key.realize('exit', () => props.controller.close(props.instance));
const choose = async (key: ChoiceKey) => {
switch (key) {
case MainChoice.SystemSetting: {
@ -84,9 +79,7 @@ export const MainSettings = defineComponent<MainSettingsProps>(props => {
break;
}
case MainChoice.ViewMap: {
props.controller.open(ViewMapUI, {
loc: FULL_LOC
});
props.controller.open(ViewMapUI, { loc: [0, 0, 840, 840] });
break;
}
case MainChoice.Replay: {
@ -105,8 +98,8 @@ export const MainSettings = defineComponent<MainSettingsProps>(props => {
const confirm = await getConfirm(
props.controller,
'确认要返回标题吗?',
CENTER_LOC,
POP_BOX_WIDTH
[420, 240, void 0, void 0, 0.5, 0.5],
240
);
if (confirm) {
props.controller.closeAll();
@ -125,11 +118,10 @@ export const MainSettings = defineComponent<MainSettingsProps>(props => {
<Choices
loc={props.loc}
choices={choices}
width={POP_BOX_WIDTH}
width={240}
onChoose={choose}
maxHeight={MAIN_HEIGHT - 64}
maxHeight={400}
interval={8}
scope={scope}
/>
);
}, mainSettingsProps);
@ -155,9 +147,6 @@ export const ReplaySettings = defineComponent<MainSettingsProps>(props => {
[ReplayChoice.Back, '返回游戏']
];
const [key, scope] = useKey();
key.realize('exit', () => props.controller.close(props.instance));
const choose = async (key: ChoiceKey) => {
switch (key) {
case ReplayChoice.Start: {
@ -169,7 +158,10 @@ export const ReplaySettings = defineComponent<MainSettingsProps>(props => {
break;
}
case ReplayChoice.StartFromSave: {
const index = await saveWithExist(props.controller, FULL_LOC);
const index = await saveWithExist(
props.controller,
[0, 0, 840, 480]
);
if (index === -2) break;
if (index === -1) {
core.doSL('autoSave', 'replayLoad');
@ -180,7 +172,10 @@ export const ReplaySettings = defineComponent<MainSettingsProps>(props => {
break;
}
case ReplayChoice.ResumeReplay: {
const index = await saveWithExist(props.controller, FULL_LOC);
const index = await saveWithExist(
props.controller,
[0, 0, 840, 480]
);
if (index === -2) break;
const name = index === -1 ? 'autoSave' : index + 1;
const success = core.doSL(name, 'replayRemain');
@ -190,11 +185,14 @@ export const ReplaySettings = defineComponent<MainSettingsProps>(props => {
}
await getConfirm(
props.controller,
'[步骤2]请选择第二个存档。\n\\r[yellow]该存档必须是前一个存档的后续。\\r\n将尝试播放到此存档。',
CENTER_LOC,
POP_BOX_WIDTH
'[步骤2]请选择第二个存档。\n\r[yellow]该存档必须是前一个存档的后续。\r\n将尝试播放到此存档。',
[420, 240, void 0, void 0, 0.5, 0.5],
240
);
const index2 = await saveWithExist(
props.controller,
[0, 0, 840, 480]
);
const index2 = await saveWithExist(props.controller, FULL_LOC);
if (index2 === -2) break;
const name2 = index2 === -1 ? 'autoSave' : index2 + 1;
core.doSL(name2, 'replayRemain');
@ -202,7 +200,10 @@ export const ReplaySettings = defineComponent<MainSettingsProps>(props => {
break;
}
case ReplayChoice.ReplayRest: {
const index = await saveWithExist(props.controller, FULL_LOC);
const index = await saveWithExist(
props.controller,
[0, 0, 840, 480]
);
if (index === -2) break;
if (index === -1) {
core.doSL('autoSave', 'replaySince');
@ -242,10 +243,9 @@ export const ReplaySettings = defineComponent<MainSettingsProps>(props => {
<Choices
loc={props.loc}
choices={choice}
width={POP_BOX_WIDTH}
width={240}
onChoose={choose}
interval={8}
scope={scope}
/>
);
}, mainSettingsProps);
@ -269,9 +269,6 @@ export const GameInfo = defineComponent<MainSettingsProps>(props => {
[GameInfoChoice.Back, '返回主菜单']
];
const [key, scope] = useKey();
key.realize('exit', () => props.controller.close(props.instance));
const choose = async (key: ChoiceKey) => {
switch (key) {
case GameInfoChoice.Statistics: {
@ -284,8 +281,8 @@ export const GameInfo = defineComponent<MainSettingsProps>(props => {
const confirm = await getConfirm(
props.controller,
'即将离开本游戏,跳转至工程页面,确认跳转?',
CENTER_LOC,
POP_BOX_WIDTH
props.loc,
240
);
if (confirm) {
window.location.href = 'editor-mobile.html';
@ -302,8 +299,8 @@ export const GameInfo = defineComponent<MainSettingsProps>(props => {
const confirm = await getConfirm(
props.controller,
'即将离开本游戏,跳转至评论页面,确认跳转?',
CENTER_LOC,
POP_BOX_WIDTH
props.loc,
240
);
if (confirm) {
window.location.href = href;
@ -333,10 +330,9 @@ export const GameInfo = defineComponent<MainSettingsProps>(props => {
<Choices
loc={props.loc}
choices={choices}
width={POP_BOX_WIDTH}
width={240}
onChoose={choose}
interval={8}
scope={scope}
/>
);
}, mainSettingsProps);
@ -364,9 +360,6 @@ export const SyncSave = defineComponent<MainSettingsProps>(props => {
[SyncSaveChoice.Back, '返回上一级']
];
const [key, scope] = useKey();
key.realize('exit', () => props.controller.close(props.instance));
const choose = async (key: ChoiceKey) => {
switch (key) {
case SyncSaveChoice.ToServer: {
@ -377,8 +370,8 @@ export const SyncSave = defineComponent<MainSettingsProps>(props => {
const replay = await getInput(
props.controller,
'请输入存档编号+密码',
CENTER_LOC,
POP_BOX_WIDTH
[240, 240, void 0, void 0, 0.5, 0.5],
240
);
await syncFromServer(props.controller, replay);
break;
@ -405,11 +398,10 @@ export const SyncSave = defineComponent<MainSettingsProps>(props => {
return () => (
<Choices
loc={props.loc}
width={POP_BOX_WIDTH}
width={240}
choices={choices}
onChoose={choose}
interval={8}
scope={scope}
/>
);
}, mainSettingsProps);
@ -421,9 +413,6 @@ export const SyncSaveSelect = defineComponent<MainSettingsProps>(props => {
[SyncSaveChoice.Back, '返回上一级']
];
const [key, scope] = useKey();
key.realize('exit', () => props.controller.close(props.instance));
const choose = async (key: ChoiceKey) => {
switch (key) {
case SyncSaveChoice.AllSaves: {
@ -431,8 +420,8 @@ export const SyncSaveSelect = defineComponent<MainSettingsProps>(props => {
const confirm = await getConfirm(
props.controller,
'你确定要同步全部存档么?这可能在存档较多的时候比较慢。',
CENTER_LOC,
POP_BOX_WIDTH
props.loc,
240
);
if (confirm) {
core.syncSave('all');
@ -444,8 +433,8 @@ export const SyncSaveSelect = defineComponent<MainSettingsProps>(props => {
const confirm = await getConfirm(
props.controller,
'确定要同步当前存档吗?',
CENTER_LOC,
POP_BOX_WIDTH
props.loc,
240
);
if (confirm) {
core.syncSave();
@ -462,11 +451,10 @@ export const SyncSaveSelect = defineComponent<MainSettingsProps>(props => {
return () => (
<Choices
loc={props.loc}
width={POP_BOX_WIDTH}
width={240}
choices={choices}
onChoose={choose}
interval={8}
scope={scope}
/>
);
}, mainSettingsProps);
@ -478,23 +466,20 @@ export const DownloadSaveSelect = defineComponent<MainSettingsProps>(props => {
[SyncSaveChoice.Back, '返回上一级']
];
const [key, scope] = useKey();
key.realize('exit', () => props.controller.close(props.instance));
const choose = async (key: ChoiceKey) => {
switch (key) {
case SyncSaveChoice.AllSaves: {
const confirm = await getConfirm(
props.controller,
'确认要下载所有存档吗?',
CENTER_LOC,
POP_BOX_WIDTH
props.loc,
240
);
if (confirm) {
const data = await waitbox(
props.controller,
CENTER_LOC,
POP_BOX_WIDTH,
props.loc,
240,
getAllSavesData(),
{ text: '请等待处理完毕' }
);
@ -511,8 +496,8 @@ export const DownloadSaveSelect = defineComponent<MainSettingsProps>(props => {
const confirm = await getConfirm(
props.controller,
'确认要下载当前存档吗?',
CENTER_LOC,
POP_BOX_WIDTH
props.loc,
240
);
if (confirm) {
const data = await getSaveData(core.saves.saveIndex);
@ -535,11 +520,10 @@ export const DownloadSaveSelect = defineComponent<MainSettingsProps>(props => {
return () => (
<Choices
loc={props.loc}
width={POP_BOX_WIDTH}
width={240}
choices={choices}
onChoose={choose}
interval={8}
scope={scope}
/>
);
}, mainSettingsProps);
@ -551,23 +535,20 @@ export const ClearSaveSelect = defineComponent<MainSettingsProps>(props => {
[SyncSaveChoice.Back, '返回上一级']
];
const [key, scope] = useKey();
key.realize('exit', () => props.controller.close(props.instance));
const choose = async (key: ChoiceKey) => {
switch (key) {
case SyncSaveChoice.AllSaves: {
const confirm = await getConfirm(
props.controller,
'你确定要清除【全部游戏】的所有本地存档?此行为不可逆!!!',
CENTER_LOC,
POP_BOX_WIDTH
props.loc,
240
);
if (confirm) {
await waitbox(
props.controller,
CENTER_LOC,
POP_BOX_WIDTH,
props.loc,
240,
new Promise<void>(res => {
core.clearLocalForage(() => {
core.saves.ids = {};
@ -590,8 +571,8 @@ export const ClearSaveSelect = defineComponent<MainSettingsProps>(props => {
await getConfirm(
props.controller,
'所有塔的存档已经全部清空',
CENTER_LOC,
POP_BOX_WIDTH
props.loc,
240
);
}
break;
@ -600,14 +581,14 @@ export const ClearSaveSelect = defineComponent<MainSettingsProps>(props => {
const confirm = await getConfirm(
props.controller,
'你确定要清除【当前游戏】的所有本地存档?此行为不可逆!!!',
CENTER_LOC,
POP_BOX_WIDTH
props.loc,
240
);
if (confirm) {
await waitbox(
props.controller,
CENTER_LOC,
POP_BOX_WIDTH,
props.loc,
240,
new Promise<void>(res => {
Object.keys(core.saves.ids).forEach(function (v) {
core.removeLocalForage('save' + v);
@ -632,8 +613,8 @@ export const ClearSaveSelect = defineComponent<MainSettingsProps>(props => {
await getConfirm(
props.controller,
'当前塔的存档已被清空',
CENTER_LOC,
POP_BOX_WIDTH
props.loc,
240
);
}
break;
@ -652,7 +633,6 @@ export const ClearSaveSelect = defineComponent<MainSettingsProps>(props => {
choices={choices}
onChoose={choose}
interval={8}
scope={scope}
/>
);
}, mainSettingsProps);

View File

@ -91,9 +91,9 @@ export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>(
return num.toString().padStart(2, '0');
};
const font1 = Font.defaults({ size: 18 });
const font2 = Font.defaults({ size: 18, weight: 700 });
const font3 = Font.defaults({ size: 14, weight: 700 });
const font1 = new Font('normal', 18);
const font2 = new Font('normal', 18, 'px', 700);
const font3 = new Font('normal', 14, 'px', 700);
const iconLoc = (n: number): ElementLocator => {
return [16, 76 + 44 * n, 32, 32];

View File

@ -5,22 +5,7 @@ import {
UIComponentProps
} from '@motajs/system-ui';
import { defineComponent, nextTick, onMounted, ref } from 'vue';
import {
BUTTONS_HEIGHT,
BUTTONS_WIDTH,
BUTTONS_X,
BUTTONS_Y,
HALF_HEIGHT,
HALF_TITLE_HEIGHT,
HALF_TITLE_WIDTH,
HALF_WIDTH,
MAIN_HEIGHT,
MAIN_WIDTH,
TITLE_HEIGHT,
TITLE_WIDTH,
TITLE_X,
TITLE_Y
} from '../shared';
import { MAIN_HEIGHT, MAIN_WIDTH } from '../shared';
import {
ElementLocator,
IActionEvent,
@ -162,8 +147,8 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
let cursorScale = 1;
const titleFont = Font.defaults({ size: 72 });
const buttonFont = Font.defaults({ size: 24, weight: 600 });
const titleFont = new Font('normal', 72).string();
const buttonFont = new Font('normal', 24, 'px', 600);
//#region 按钮功能
@ -403,7 +388,7 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
}
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = titleFont.string();
ctx.font = titleFont;
ctx.fillStyle = titleGradient!;
ctx.filter = `
drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.5))
@ -411,7 +396,7 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
drop-shadow(12px 12px 4px rgba(0, 0, 0, 0.4))
blur(1px)
`;
ctx.fillText(core.firstData.title, HALF_TITLE_WIDTH, HALF_TITLE_HEIGHT);
ctx.fillText(core.firstData.title, 320, 50);
};
const renderCursor = (canvas: MotaOffscreenCanvas2D) => {
@ -435,15 +420,15 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
>
<image
image={bg}
loc={[HALF_WIDTH, HALF_HEIGHT, width, height]}
loc={[MAIN_WIDTH / 2, MAIN_HEIGHT / 2, width, height]}
anc={[0.5, 0.5]}
filter="brightness(120%)contrast(110%)"
zIndex={0}
/>
<shader
ref={imageShader}
zIndex={5}
loc={[0, 0, MAIN_WIDTH, MAIN_HEIGHT]}
filter="brightness(120%)contrast(110%)"
/>
<sprite
ref={maskSprite}
@ -456,16 +441,17 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
<sprite
ref={titleSprite}
render={renderTitle}
loc={[TITLE_X, TITLE_Y, TITLE_WIDTH, TITLE_HEIGHT, 0.5, 0.5]}
loc={[MAIN_WIDTH / 2, 120, 640, 100, 0.5, 0.5]}
zIndex={10}
/>
<container
zIndex={15}
loc={[BUTTONS_X, BUTTONS_Y, BUTTONS_WIDTH, BUTTONS_HEIGHT]}
loc={[50, MAIN_HEIGHT, 200, 160]}
anc={[0, 1]}
>
<container
hidden={selectHard.value}
loc={[0, 0, BUTTONS_WIDTH, BUTTONS_HEIGHT]}
loc={[0, 0, 200, 160]}
alpha={buttonsAlpha.ref.value}
>
{buttons.map((v, i) => {
@ -489,7 +475,7 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
</container>
<container
hidden={!selectHard.value}
loc={[0, 0, BUTTONS_WIDTH, BUTTONS_HEIGHT]}
loc={[0, 0, 200, 160]}
alpha={buttonsAlpha.ref.value}
>
{hard.map((v, i) => {
@ -542,15 +528,16 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
cursor="pointer"
/>
)}
<g-line
line={[5, 35, 35, 5]}
strokeStyle="gray"
lineWidth={3}
lineCap="round"
zIndex={5}
noevent
hidden={soundOpened.value}
/>
{!soundOpened.value && (
<g-line
line={[5, 35, 35, 5]}
strokeStyle="gray"
lineWidth={3}
lineCap="round"
noevent
zIndex={5}
/>
)}
</container>
</container>
);

View File

@ -23,7 +23,7 @@ import { generateBinary } from '@motajs/legacy-common';
import { SetupComponentOptions } from '@motajs/system-ui';
import { saveSave, saveLoad } from './save';
import { mainUIController } from './controller';
import { MAIN_HEIGHT, FULL_LOC, POP_BOX_WIDTH, CENTER_LOC } from '../shared';
import { MAIN_WIDTH, MAIN_HEIGHT } from '../shared';
import { openSettings } from './settings';
import { openViewMap } from './viewmap';
@ -90,10 +90,10 @@ export const PlayingToolbar = defineComponent<
const tool = () => core.openToolbox(true);
const fly = () => core.useFly(true);
const save = () => {
saveSave(mainUIController, FULL_LOC);
saveSave(mainUIController, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]);
};
const load = () => {
saveLoad(mainUIController, FULL_LOC);
saveLoad(mainUIController, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]);
};
const equip = () => core.openEquipbox(true);
const shop = () => core.openQuickShop(true);
@ -111,15 +111,12 @@ export const PlayingToolbar = defineComponent<
const redo = () => core.doSL('autoSave', 'reload');
const numpad = () => emit('numpad');
const view = () => {
openViewMap(mainUIController, FULL_LOC);
openViewMap(mainUIController, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]);
};
const danmaku = () => requestAnimationFrame(openDanmakuPoster);
const replay = () => core.ui._drawReplay();
const settings = () => {
const loc = CENTER_LOC.slice() as ElementLocator;
loc[2] = POP_BOX_WIDTH;
loc[3] = MAIN_HEIGHT - 72;
openSettings(mainUIController, loc);
openSettings(mainUIController, [420, 240, 240, 400, 0.5, 0.5]);
};
return () => (
@ -169,7 +166,7 @@ export const ReplayingToolbar = defineComponent<ReplayingProps>(props => {
const bookIcon = core.statusBar.icons.book;
const saveIcon = core.statusBar.icons.save;
const font1 = Font.defaults({ size: 16 });
const font1 = new Font('normal', 16);
const font2 = new Font('Verdana', 12);
const speedText = computed(() => `${status.speed}`);
@ -186,7 +183,7 @@ export const ReplayingToolbar = defineComponent<ReplayingProps>(props => {
const speedUp = () => core.speedUpReplay();
const book = () => core.openBook(true);
const save = () => {
saveSave(mainUIController, FULL_LOC);
saveSave(mainUIController, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]);
};
const view = () => {
if (core.isPlaying() && !core.isMoving() && !core.status.lockControl) {

View File

@ -38,17 +38,6 @@ import { clamp, mean } from 'lodash-es';
import { calculateStatisticsOne, StatisticsDataOneFloor } from './statistics';
import { Tip, TipExpose } from '../components';
import { useKey } from '../use';
import {
ENABLE_RIGHT_STATUS_BAR,
FULL_LOC,
HALF_WIDTH,
MAIN_HEIGHT,
MAP_HEIGHT,
MAP_WIDTH,
RIGHT_STATUS_POS,
STATUS_BAR_HEIGHT,
STATUS_BAR_WIDTH
} from '../shared';
export interface ViewMapProps extends UIComponentProps, BaseProps {
loc: ElementLocator;
@ -346,18 +335,12 @@ export const ViewMap = defineComponent<ViewMapProps>(props => {
return () => (
<container loc={props.loc} nocache>
<g-rect fillStyle="black" fill loc={FULL_LOC} />
<g-rect stroke zIndex={100} loc={FULL_LOC} noevent />
<g-line
line={[STATUS_BAR_WIDTH, 0, STATUS_BAR_WIDTH, MAIN_HEIGHT]}
lineWidth={1}
/>
<g-line
line={[RIGHT_STATUS_POS, 0, RIGHT_STATUS_POS, MAIN_HEIGHT]}
lineWidth={1}
/>
<g-rect fillStyle="black" fill loc={[0, 0, 840, 480]} />
<g-rect stroke zIndex={100} loc={[0, 0, 840, 480]} noevent />
<g-line line={[180, 0, 180, 480]} lineWidth={1} />
<g-line line={[180 + 480, 0, 180 + 480, 480]} lineWidth={1} />
<FloorSelector
loc={[0, 0, STATUS_BAR_WIDTH, MAIN_HEIGHT]}
loc={[0, 0, 180, 480]}
floors={viewableFloor}
v-model:now={now.value}
onClose={close}
@ -365,7 +348,7 @@ export const ViewMap = defineComponent<ViewMapProps>(props => {
<layer-group
ref={group}
ex={layerGroupExtends}
loc={[STATUS_BAR_WIDTH, 0, MAP_WIDTH, MAP_HEIGHT]}
loc={[180, 0, 480, 480]}
onDown={downMap}
onMove={moveMap}
onUp={upMap}
@ -382,12 +365,12 @@ export const ViewMap = defineComponent<ViewMapProps>(props => {
<Tip
ref={tip}
zIndex={40}
loc={[STATUS_BAR_WIDTH + 8, 8, 200, 32]}
loc={[188, 8, 200, 32]}
pad={[12, 6]}
corner={16}
/>
<sprite
loc={[STATUS_BAR_WIDTH, 0, MAP_WIDTH, 64]}
loc={[180, 0, 480, 64]}
render={renderTop}
alpha={topAlpha.value}
zIndex={10}
@ -397,7 +380,7 @@ export const ViewMap = defineComponent<ViewMapProps>(props => {
onClick={() => changeFloor(1)}
/>
<sprite
loc={[STATUS_BAR_WIDTH, MAP_HEIGHT - 64, MAP_WIDTH, 64]}
loc={[180, 416, 480, 64]}
render={renderBottom}
alpha={bottomAlpha.value}
zIndex={10}
@ -408,22 +391,19 @@ export const ViewMap = defineComponent<ViewMapProps>(props => {
/>
<text
text="上移地图"
loc={[HALF_WIDTH, 24]}
loc={[420, 24]}
anc={[0.5, 0.5]}
zIndex={20}
noevent
/>
<text
text="下移地图"
loc={[HALF_WIDTH, MAP_HEIGHT - 24]}
loc={[420, 456]}
anc={[0.5, 0.5]}
zIndex={20}
noevent
/>
<container
loc={[RIGHT_STATUS_POS, 0, STATUS_BAR_WIDTH, STATUS_BAR_HEIGHT]}
hidden={!ENABLE_RIGHT_STATUS_BAR}
>
<container loc={[660, 0, 180, 480]}>
<text
text="鼠标 / 单指拖动地图"
font={rightFont}

View File

@ -73,22 +73,17 @@ type KeyUsing = [Hotkey, symbol];
/**
*
* @param noScope
* @param scope `noScope` `true`
*/
export function useKey(noScope: boolean = false, scope?: symbol): KeyUsing {
export function useKey(noScope: boolean = false): KeyUsing {
if (noScope) {
return [gameKey, gameKey.scope];
} else {
const sym = scope ?? Symbol();
if (sym === gameKey.scope) {
return [gameKey, gameKey.scope];
} else {
gameKey.use(sym);
onUnmounted(() => {
gameKey.dispose();
});
return [gameKey, sym];
}
const sym = Symbol();
gameKey.use(sym);
onUnmounted(() => {
gameKey.dispose();
});
return [gameKey, sym];
}
}

View File

@ -34,8 +34,9 @@ export function deleteWith<T>(arr: T[], ele: T): T[] {
export function spliceBy<T>(arr: T[], from: T): T[] {
const index = arr.indexOf(from);
if (index === -1) return [];
return arr.splice(index);
if (index === -1) return arr;
arr.splice(index);
return arr;
}
/**

View File

@ -160,18 +160,8 @@ export class Font implements IFontConfig {
/**
*
*/
static defaults(config?: Partial<IFontConfig>) {
if (!config) {
return new Font();
} else {
return new Font(
config.family,
config.size,
config.sizeUnit,
config.weight,
config.italic
);
}
static defaults() {
return new Font();
}
/**

View File

@ -179,8 +179,7 @@ export class Hotkey extends EventEmitter<HotkeyEvent> {
* @param symbol symbol
*/
use(symbol: symbol): void {
if (symbol === this.scope) return;
this.dispose(symbol);
spliceBy(this.scopeStack, symbol);
this.scopeStack.push(symbol);
this.scope = symbol;
this.conditionMap.set(symbol, () => true);
@ -191,12 +190,10 @@ export class Hotkey extends EventEmitter<HotkeyEvent> {
* @param symbol symbol
*/
dispose(symbol: symbol = this.scopeStack.at(-1) ?? Symbol()): void {
const disposed = spliceBy(this.scopeStack, symbol);
disposed.forEach(v => {
for (const key of Object.values(this.data)) {
key.emits.delete(v);
}
});
for (const key of Object.values(this.data)) {
key.emits.delete(symbol);
}
spliceBy(this.scopeStack, symbol);
this.scope = this.scopeStack.at(-1) ?? Symbol();
}