mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-18 17:48:52 +08:00
feat: 部分设置菜单
This commit is contained in:
parent
0cc76bd475
commit
8cc0c76846
@ -23,7 +23,7 @@ export const UIContainer = defineComponent<UIContainerProps>(props => {
|
|||||||
controller={data}
|
controller={data}
|
||||||
instance={b}
|
instance={b}
|
||||||
key={b.key}
|
key={b.key}
|
||||||
hidden={b.hidden}
|
hidden={b.hidden && !b.alwaysShow}
|
||||||
></b.ui.component>
|
></b.ui.component>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -34,7 +34,7 @@ export const UIContainer = defineComponent<UIContainerProps>(props => {
|
|||||||
key={v.key}
|
key={v.key}
|
||||||
controller={data}
|
controller={data}
|
||||||
instance={v}
|
instance={v}
|
||||||
hidden={v.hidden}
|
hidden={v.hidden && !v.alwaysShow}
|
||||||
></v.ui.component>
|
></v.ui.component>
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
@ -94,6 +94,7 @@
|
|||||||
"60": "Repeated Tip id: '$1'.",
|
"60": "Repeated Tip id: '$1'.",
|
||||||
"61": "Unexpected recursive call of $1.update in render function. Please ensure you have to do this, if you do, ignore this warn.",
|
"61": "Unexpected recursive call of $1.update in render function. Please ensure you have to do this, if you do, ignore this warn.",
|
||||||
"62": "Recursive fallback fonts in '$1'.",
|
"62": "Recursive fallback fonts in '$1'.",
|
||||||
|
"63": "Uncaught promise error in waiting box component. Error reason: $1",
|
||||||
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.",
|
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.",
|
||||||
"1101": "Cannot add new effect to point effect instance, for there's no more reserve space for it. Please increase the max count of the instance."
|
"1101": "Cannot add new effect to point effect instance, for there's no more reserve space for it. Please increase the max count of the instance."
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ const confirmBoxProps = {
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 确认框组件,与 2.x 的 drawConfirm 类似,可以键盘操作,
|
* 确认框组件,与 2.x 的 drawConfirm 类似,可以键盘操作,单次调用参考 {@link getConfirm}。
|
||||||
* 参数参考 {@link ConfirmBoxProps},事件参考 {@link ConfirmBoxEmits},用例如下:
|
* 参数参考 {@link ConfirmBoxProps},事件参考 {@link ConfirmBoxEmits},用例如下:
|
||||||
* ```tsx
|
* ```tsx
|
||||||
* const onYes = () => console.log('yes');
|
* const onYes = () => console.log('yes');
|
||||||
@ -243,7 +243,7 @@ const choicesProps = {
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 选项框组件,用于在多个选项中选择一个,例如样板的系统设置就由它实现。
|
* 选项框组件,用于在多个选项中选择一个,例如样板的系统设置就由它实现。单次调用参考 {@link getChoice}。
|
||||||
* 参数参考 {@link ChoicesProps},事件参考 {@link ChoicesEmits}。用例如下:
|
* 参数参考 {@link ChoicesProps},事件参考 {@link ChoicesEmits}。用例如下:
|
||||||
* ```tsx
|
* ```tsx
|
||||||
* <Choices
|
* <Choices
|
||||||
@ -530,12 +530,28 @@ export const Choices = defineComponent<
|
|||||||
}, choicesProps);
|
}, choicesProps);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 弹出一个确认框,然后将确认结果返回
|
* 弹出一个确认框,然后将确认结果返回,例如给玩家弹出一个确认框,并获取玩家是否确认:
|
||||||
|
* ```ts
|
||||||
|
* const confirm = await getConfirm(
|
||||||
|
* // 在哪个 UI 控制器上打开,对于一般 UI 组件来说,直接填写 props.controller 即可
|
||||||
|
* props.controller,
|
||||||
|
* // 确认内容
|
||||||
|
* '确认要 xxx 吗?',
|
||||||
|
* // 确认框的位置,宽度由下一个参数指定,高度参数由组件内部计算得出,指定无效
|
||||||
|
* [240, 240, void 0, void 0, 0.5, 0.5],
|
||||||
|
* // 宽度设为 240
|
||||||
|
* 240,
|
||||||
|
* // 可以给选择框传入其他的 props,例如指定字体,此项可选
|
||||||
|
* { font: new Font('Verdana', 20) }
|
||||||
|
* );
|
||||||
|
* // 之后,就可以直接判断 confirm 来执行不同的操作了
|
||||||
|
* if (confirm) { ... }
|
||||||
|
* ```
|
||||||
* @param controller UI 控制器
|
* @param controller UI 控制器
|
||||||
* @param text 确认文本内容
|
* @param text 确认文本内容
|
||||||
* @param loc 确认框的位置
|
* @param loc 确认框的位置
|
||||||
* @param width 确认框的宽度
|
* @param width 确认框的宽度
|
||||||
* @param props 额外的 props
|
* @param props 额外的 props,参考 {@link ConfirmBoxProps}
|
||||||
*/
|
*/
|
||||||
export function getConfirm(
|
export function getConfirm(
|
||||||
controller: IUIMountable,
|
controller: IUIMountable,
|
||||||
@ -567,12 +583,28 @@ export function getConfirm(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 弹出一个选择框,然后将选择结果返回
|
* 弹出一个选择框,然后将选择结果返回,例如给玩家弹出一个选择框,并获取玩家选择了哪个:
|
||||||
|
* ```ts
|
||||||
|
* const choice = await getChoice(
|
||||||
|
* // 在哪个 UI 控制器上打开,对于一般 UI 组件来说,直接填写 props.controller 即可
|
||||||
|
* props.controller,
|
||||||
|
* // 选项内容,参考 Choices 的注释
|
||||||
|
* [[0, '选项1'], [1, '选项2'], [2, '选项3']],
|
||||||
|
* // 选择框的位置,宽度由下一个参数指定,高度参数由组件内部计算得出,指定无效
|
||||||
|
* [240, 240, void 0, void 0, 0.5, 0.5],
|
||||||
|
* // 宽度设为 240
|
||||||
|
* 240,
|
||||||
|
* // 可以给选择框传入其他的 props,例如指定标题,此项可选
|
||||||
|
* { title: '选项标题' }
|
||||||
|
* );
|
||||||
|
* // 之后,就可以直接判断 choice 来执行不同的操作了
|
||||||
|
* if (choice === 0) { ... }
|
||||||
|
* ```
|
||||||
* @param controller UI 控制器
|
* @param controller UI 控制器
|
||||||
* @param choices 选择框的选项
|
* @param choices 选择框的选项
|
||||||
* @param loc 选择框的位置
|
* @param loc 选择框的位置
|
||||||
* @param width 选择框的宽度
|
* @param width 选择框的宽度
|
||||||
* @param props 额外的 props
|
* @param props 额外的 props,参考 {@link ChoicesProps}
|
||||||
*/
|
*/
|
||||||
export function getChoice<T extends ChoiceKey = ChoiceKey>(
|
export function getChoice<T extends ChoiceKey = ChoiceKey>(
|
||||||
controller: IUIMountable,
|
controller: IUIMountable,
|
||||||
|
@ -5,13 +5,15 @@ import {
|
|||||||
PathProps,
|
PathProps,
|
||||||
Sprite
|
Sprite
|
||||||
} from '@/core/render';
|
} from '@/core/render';
|
||||||
import { computed, defineComponent, ref, watch } from 'vue';
|
import { computed, defineComponent, ref, SetupContext, watch } from 'vue';
|
||||||
import { SetupComponentOptions } from './types';
|
import { SetupComponentOptions } from './types';
|
||||||
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
||||||
import { TextboxProps, TextContent } from './textbox';
|
import { TextboxProps, TextContent, TextContentProps } from './textbox';
|
||||||
import { Scroll, ScrollExpose, ScrollProps } from './scroll';
|
import { Scroll, ScrollExpose, ScrollProps } from './scroll';
|
||||||
import { transitioned } from '../use';
|
import { transitioned } from '../use';
|
||||||
import { hyper } from 'mutate-animate';
|
import { hyper } from 'mutate-animate';
|
||||||
|
import { logger } from '@/core/common/logger';
|
||||||
|
import { GameUI, IUIMountable } from '@/core/system';
|
||||||
|
|
||||||
interface ProgressProps extends DefaultProps {
|
interface ProgressProps extends DefaultProps {
|
||||||
/** 进度条的位置 */
|
/** 进度条的位置 */
|
||||||
@ -401,3 +403,181 @@ export const Background = defineComponent<BackgroundProps>(props => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}, backgroundProps);
|
}, backgroundProps);
|
||||||
|
|
||||||
|
export interface WaitBoxProps<T>
|
||||||
|
extends Partial<BackgroundProps>,
|
||||||
|
Partial<TextContentProps> {
|
||||||
|
loc: ElementLocator;
|
||||||
|
width: number;
|
||||||
|
promise?: Promise<T>;
|
||||||
|
text?: string;
|
||||||
|
pad?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WaitBoxEmits<T> = {
|
||||||
|
resolve: (data: T) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface WaitBoxExpose<T> {
|
||||||
|
/**
|
||||||
|
* 手动将此组件兑现,注意调用时如果传入的 Promise 还没有兑现,
|
||||||
|
* 当 Promise 兑现后将不会再次触发 resolve 事件,即 resolve 事件只会被触发一次
|
||||||
|
* @param data 兑现值
|
||||||
|
*/
|
||||||
|
resolve(data: T): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const waitBoxProps = {
|
||||||
|
props: ['promise', 'loc', 'winskin', 'color', 'border'],
|
||||||
|
emits: ['resolve']
|
||||||
|
} satisfies SetupComponentOptions<
|
||||||
|
WaitBoxProps<unknown>,
|
||||||
|
WaitBoxEmits<unknown>,
|
||||||
|
keyof WaitBoxEmits<unknown>
|
||||||
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 等待框,可以等待某个异步操作 (Promise),操作完毕后触发兑现事件,单次调用参考 {@link waitbox}。
|
||||||
|
* 参数参考 {@link WaitBoxProps},事件参考 {@link WaitBoxEmits},函数接口参考 {@link WaitBoxExpose}。用例如下:
|
||||||
|
* ```tsx
|
||||||
|
* // 创建一个等待 1000ms 的 Promise,兑现值是等待完毕时的当前时间刻
|
||||||
|
* const promise = new Promise(res => window.setTimeout(() => res(Date.now()), 1000));
|
||||||
|
*
|
||||||
|
* <WaitBox
|
||||||
|
* // 传入要等待的 Promise
|
||||||
|
* promise={promise}
|
||||||
|
* // 等待框的位置,宽度由 width 参数指定,高度由内部计算得来,不需要手动指定,即使手动指定也无效
|
||||||
|
* loc={[240, 240, void 0, void 0, 0.5, 0.5]}
|
||||||
|
* // 等待框的宽度
|
||||||
|
* width={240}
|
||||||
|
* // 完全继承 Background 的参数,因此可以直接指定背景样式
|
||||||
|
* winskin="winskin2.png"
|
||||||
|
* // 完全继承 TextContent 的参数,因此可以直接指定字体
|
||||||
|
* font={new Font('Verdana', 28)}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const WaitBox = defineComponent<
|
||||||
|
WaitBoxProps<unknown>,
|
||||||
|
WaitBoxEmits<unknown>,
|
||||||
|
keyof WaitBoxEmits<unknown>
|
||||||
|
>(
|
||||||
|
<T,>(
|
||||||
|
props: WaitBoxProps<T>,
|
||||||
|
{ emit, expose, attrs }: SetupContext<WaitBoxEmits<T>>
|
||||||
|
) => {
|
||||||
|
const contentHeight = ref(200);
|
||||||
|
|
||||||
|
const text = computed(() => props.text ?? '请等待 ...');
|
||||||
|
const pad = computed(() => props.pad ?? 24);
|
||||||
|
const containerLoc = computed<ElementLocator>(() => {
|
||||||
|
const [x = 0, y = 0, , , ax = 0, ay = 0] = props.loc;
|
||||||
|
return [x, y, props.width, contentHeight.value, ax, ay];
|
||||||
|
});
|
||||||
|
const backLoc = computed<ElementLocator>(() => {
|
||||||
|
return [1, 1, props.width - 2, contentHeight.value - 2];
|
||||||
|
});
|
||||||
|
const contentLoc = computed<ElementLocator>(() => {
|
||||||
|
return [
|
||||||
|
pad.value,
|
||||||
|
pad.value,
|
||||||
|
props.width - pad.value * 2,
|
||||||
|
contentHeight.value - pad.value * 2
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
let resolved: boolean = false;
|
||||||
|
|
||||||
|
props.promise?.then(
|
||||||
|
value => {
|
||||||
|
resolve(value);
|
||||||
|
},
|
||||||
|
reason => {
|
||||||
|
logger.warn(63, reason);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const resolve = (data: T) => {
|
||||||
|
if (resolved) return;
|
||||||
|
resolved = true;
|
||||||
|
emit('resolve', data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onContentHeight = (height: number) => {
|
||||||
|
contentHeight.value = height + pad.value * 2;
|
||||||
|
};
|
||||||
|
|
||||||
|
expose<WaitBoxExpose<T>>({ resolve });
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<container loc={containerLoc.value}>
|
||||||
|
<Background
|
||||||
|
loc={backLoc.value}
|
||||||
|
zIndex={0}
|
||||||
|
winskin={props.winskin}
|
||||||
|
color={props.color}
|
||||||
|
border={props.border}
|
||||||
|
/>
|
||||||
|
<TextContent
|
||||||
|
{...attrs}
|
||||||
|
autoHeight
|
||||||
|
text={text.value}
|
||||||
|
loc={contentLoc.value}
|
||||||
|
width={props.width - pad.value * 2}
|
||||||
|
zIndex={5}
|
||||||
|
onUpdateHeight={onContentHeight}
|
||||||
|
/>
|
||||||
|
</container>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
waitBoxProps
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开一个等待框,等待传入的 Promise 兑现后,关闭等待框,并将兑现值返回。
|
||||||
|
* 示例,等待 1000ms:
|
||||||
|
* ```ts
|
||||||
|
* // 创建一个等待 1000ms 的 Promise,兑现值是等待完毕时的当前时间刻
|
||||||
|
* const promise = new Promise(res => window.setTimeout(() => res(Date.now()), 1000));
|
||||||
|
* const value = await waitbox(
|
||||||
|
* // 在哪个 UI 控制器上打开,对于一般 UI 组件来说,直接填写 props.controller 即可
|
||||||
|
* props.controller,
|
||||||
|
* // 确认框的位置,宽度由下一个参数指定,高度参数由组件内部计算得出,指定无效
|
||||||
|
* [240, 240, void 0, void 0, 0.5, 0.5],
|
||||||
|
* // 确认框的宽度
|
||||||
|
* 240,
|
||||||
|
* // 要等待的 Promise
|
||||||
|
* promise,
|
||||||
|
* // 额外的 props,例如填写等待文本,此项可选,参考 WaitBoxProps
|
||||||
|
* { text: '请等待 1000ms' }
|
||||||
|
* );
|
||||||
|
* console.log(value); // 输出时间刻
|
||||||
|
* ```
|
||||||
|
* @param controller UI 控制器
|
||||||
|
* @param loc 等待框的位置
|
||||||
|
* @param width 等待框的宽度
|
||||||
|
* @param promise 要等待的 Promise
|
||||||
|
* @param props 额外的 props,参考 {@link WaitBoxProps}
|
||||||
|
*/
|
||||||
|
export function waitbox<T>(
|
||||||
|
controller: IUIMountable,
|
||||||
|
loc: ElementLocator,
|
||||||
|
width: number,
|
||||||
|
promise: Promise<T>,
|
||||||
|
props?: Partial<WaitBoxProps<T>>
|
||||||
|
): Promise<T> {
|
||||||
|
return new Promise<T>(res => {
|
||||||
|
const instance = controller.open(WaitBoxUI, {
|
||||||
|
...(props ?? {}),
|
||||||
|
loc,
|
||||||
|
width,
|
||||||
|
promise,
|
||||||
|
onResolve: data => {
|
||||||
|
controller.close(instance);
|
||||||
|
res(data as T);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WaitBoxUI = new GameUI('wait-box', WaitBox);
|
||||||
|
579
src/module/render/ui/settings.tsx
Normal file
579
src/module/render/ui/settings.tsx
Normal file
@ -0,0 +1,579 @@
|
|||||||
|
import { ElementLocator } from '@/core/render';
|
||||||
|
import { GameUI, UIComponentProps } from '@/core/system';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import {
|
||||||
|
ChoiceItem,
|
||||||
|
ChoiceKey,
|
||||||
|
Choices,
|
||||||
|
ChoicesProps,
|
||||||
|
getConfirm,
|
||||||
|
SetupComponentOptions,
|
||||||
|
waitbox
|
||||||
|
} from '../components';
|
||||||
|
import { mainUi } from '@/core/main/init/ui';
|
||||||
|
import { gameKey } from '@/core/main/custom/hotkey';
|
||||||
|
import { generateKeyboardEvent } from '@/core/main/custom/keyboard';
|
||||||
|
import { getVitualKeyOnce } from '@/plugin/utils';
|
||||||
|
import { getAllSavesData, getSaveData } from '@/module/utils';
|
||||||
|
|
||||||
|
export interface SettingsProps extends Partial<ChoicesProps>, UIComponentProps {
|
||||||
|
loc: ElementLocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
const settingsProps = {
|
||||||
|
props: ['loc', 'controller', 'instance']
|
||||||
|
} satisfies SetupComponentOptions<SettingsProps>;
|
||||||
|
|
||||||
|
const enum MainChoice {
|
||||||
|
SystemSetting,
|
||||||
|
VirtualKey,
|
||||||
|
ViewMap,
|
||||||
|
/** @see {@link ReplaySettings} */
|
||||||
|
Replay,
|
||||||
|
/** @see {@link SyncSave} */
|
||||||
|
SyncSave,
|
||||||
|
/** @see {@link GameInfo} */
|
||||||
|
GameInfo,
|
||||||
|
Restart,
|
||||||
|
Back
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MainSettings = defineComponent<SettingsProps>(props => {
|
||||||
|
const choices: ChoiceItem[] = [
|
||||||
|
[MainChoice.SystemSetting, '系统设置'],
|
||||||
|
[MainChoice.VirtualKey, '虚拟键盘'],
|
||||||
|
[MainChoice.ViewMap, '浏览地图'],
|
||||||
|
[MainChoice.Replay, '录像回放'],
|
||||||
|
[MainChoice.SyncSave, '同步存档'],
|
||||||
|
[MainChoice.GameInfo, '游戏信息'],
|
||||||
|
[MainChoice.Restart, '返回标题'],
|
||||||
|
[MainChoice.Back, '返回游戏']
|
||||||
|
];
|
||||||
|
|
||||||
|
const choose = (key: ChoiceKey) => {
|
||||||
|
switch (key) {
|
||||||
|
case MainChoice.SystemSetting: {
|
||||||
|
mainUi.open('settings');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MainChoice.VirtualKey: {
|
||||||
|
getVitualKeyOnce().then(value => {
|
||||||
|
gameKey.emitKey(
|
||||||
|
value.key,
|
||||||
|
value.assist,
|
||||||
|
'up',
|
||||||
|
generateKeyboardEvent(value.key, value.assist)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MainChoice.ViewMap: {
|
||||||
|
// todo
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MainChoice.Replay: {
|
||||||
|
props.controller.open(ReplaySettingsUI, { loc: props.loc });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MainChoice.SyncSave: {
|
||||||
|
props.controller.open(SyncSaveUI, { loc: props.loc });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MainChoice.GameInfo: {
|
||||||
|
props.controller.open(GameInfoUI, { loc: props.loc });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MainChoice.Restart: {
|
||||||
|
props.controller.closeAll();
|
||||||
|
core.restart();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MainChoice.Back: {
|
||||||
|
props.controller.close(props.instance);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<Choices
|
||||||
|
loc={props.loc}
|
||||||
|
choices={choices}
|
||||||
|
width={240}
|
||||||
|
onChoose={choose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}, settingsProps);
|
||||||
|
|
||||||
|
const enum ReplayChoice {
|
||||||
|
Start,
|
||||||
|
StartFromSave,
|
||||||
|
ResumeReplay,
|
||||||
|
ReplayRest,
|
||||||
|
ChooseReplay,
|
||||||
|
Download,
|
||||||
|
Back
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ReplaySettings = defineComponent<SettingsProps>(props => {
|
||||||
|
const choice: ChoiceItem[] = [
|
||||||
|
[ReplayChoice.Start, '从头回放录像'],
|
||||||
|
[ReplayChoice.StartFromSave, '从存档开始回放'],
|
||||||
|
[ReplayChoice.ResumeReplay, '接续播放剩余录像'],
|
||||||
|
[ReplayChoice.ReplayRest, '播放存档剩余录像'],
|
||||||
|
[ReplayChoice.ChooseReplay, '选择录像文件'],
|
||||||
|
[ReplayChoice.Download, '下载当前录像'],
|
||||||
|
[ReplayChoice.Back, '返回游戏']
|
||||||
|
];
|
||||||
|
|
||||||
|
const choose = (key: ChoiceKey) => {
|
||||||
|
switch (key) {
|
||||||
|
case ReplayChoice.Start: {
|
||||||
|
props.controller.closeAll();
|
||||||
|
core.ui.closePanel();
|
||||||
|
const route = core.status.route.slice();
|
||||||
|
const seed = core.getFlag<number>('__seed__');
|
||||||
|
core.startGame(core.status.hard, seed, route);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ReplayChoice.StartFromSave: {
|
||||||
|
// todo
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ReplayChoice.ResumeReplay: {
|
||||||
|
// todo
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ReplayChoice.ReplayRest: {
|
||||||
|
// todo
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ReplayChoice.ChooseReplay: {
|
||||||
|
props.controller.closeAll();
|
||||||
|
core.chooseReplayFile();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ReplayChoice.Download: {
|
||||||
|
core.download(
|
||||||
|
core.firstData.name + '_' + core.formatDate2() + '.h5route',
|
||||||
|
// @ts-expect-error 暂时无法推导
|
||||||
|
LZString.compressToBase64(
|
||||||
|
JSON.stringify({
|
||||||
|
name: core.firstData.name,
|
||||||
|
hard: core.status.hard,
|
||||||
|
seed: core.getFlag('__seed__'),
|
||||||
|
route: core.encodeRoute(core.status.route)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ReplayChoice.Back: {
|
||||||
|
props.controller.close(props.instance);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<Choices
|
||||||
|
loc={props.loc}
|
||||||
|
choices={choice}
|
||||||
|
width={240}
|
||||||
|
onChoose={choose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}, settingsProps);
|
||||||
|
|
||||||
|
const enum GameInfoChoice {
|
||||||
|
Statistics,
|
||||||
|
Project,
|
||||||
|
Tower,
|
||||||
|
Help,
|
||||||
|
Download,
|
||||||
|
Back
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GameInfo = defineComponent<SettingsProps>(props => {
|
||||||
|
const choices: ChoiceItem[] = [
|
||||||
|
[GameInfoChoice.Statistics, '数据统计'],
|
||||||
|
[GameInfoChoice.Project, '查看工程'],
|
||||||
|
[GameInfoChoice.Tower, '游戏主页'],
|
||||||
|
[GameInfoChoice.Help, '操作帮助'],
|
||||||
|
[GameInfoChoice.Download, '下载离线版本'],
|
||||||
|
[GameInfoChoice.Back, '返回主菜单']
|
||||||
|
];
|
||||||
|
|
||||||
|
const choose = async (key: ChoiceKey) => {
|
||||||
|
switch (key) {
|
||||||
|
case GameInfoChoice.Statistics: {
|
||||||
|
// todo
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GameInfoChoice.Project: {
|
||||||
|
if (core.platform.isPC) window.open('editor.html', '_blank');
|
||||||
|
else {
|
||||||
|
const confirm = await getConfirm(
|
||||||
|
props.controller,
|
||||||
|
'即将离开本游戏,跳转至工程页面,确认跳转?',
|
||||||
|
props.loc,
|
||||||
|
240
|
||||||
|
);
|
||||||
|
if (confirm) {
|
||||||
|
window.location.href = 'editor-mobile.html';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GameInfoChoice.Tower: {
|
||||||
|
const name = core.firstData.name;
|
||||||
|
const href = `/tower/?name=${name}`;
|
||||||
|
if (core.platform.isPC) {
|
||||||
|
window.open(href, '_blank');
|
||||||
|
} else {
|
||||||
|
const confirm = await getConfirm(
|
||||||
|
props.controller,
|
||||||
|
'即将离开本游戏,跳转至评论页面,确认跳转?',
|
||||||
|
props.loc,
|
||||||
|
240
|
||||||
|
);
|
||||||
|
if (confirm) {
|
||||||
|
window.location.href = href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GameInfoChoice.Download: {
|
||||||
|
const name = core.firstData.name;
|
||||||
|
const href = `/games/${name}/${name}.zip`;
|
||||||
|
if (core.platform.isPC) window.open(href);
|
||||||
|
else window.location.href = href;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GameInfoChoice.Help: {
|
||||||
|
// todo
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GameInfoChoice.Back: {
|
||||||
|
props.controller.close(props.instance);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<Choices
|
||||||
|
loc={props.loc}
|
||||||
|
choices={choices}
|
||||||
|
width={240}
|
||||||
|
onChoose={choose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const enum SyncSaveChoice {
|
||||||
|
// ----- 主菜单
|
||||||
|
ToServer,
|
||||||
|
FromServer,
|
||||||
|
ToLocal,
|
||||||
|
FromLocal,
|
||||||
|
ClearLocal,
|
||||||
|
Back,
|
||||||
|
// ----- 子菜单
|
||||||
|
AllSaves,
|
||||||
|
NowSave
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SyncSave = defineComponent<SettingsProps>(props => {
|
||||||
|
const choices: ChoiceItem[] = [
|
||||||
|
[SyncSaveChoice.ToServer, '同步存档至服务器'],
|
||||||
|
[SyncSaveChoice.FromServer, '从服务器加载存档'],
|
||||||
|
[SyncSaveChoice.ToLocal, '存档至本地文件'],
|
||||||
|
[SyncSaveChoice.FromLocal, '存本地文件读档'],
|
||||||
|
[SyncSaveChoice.ClearLocal, '清空本地存档'],
|
||||||
|
[SyncSaveChoice.Back, '返回上一级']
|
||||||
|
];
|
||||||
|
|
||||||
|
const choose = (key: ChoiceKey) => {
|
||||||
|
switch (key) {
|
||||||
|
case SyncSaveChoice.ToServer: {
|
||||||
|
props.controller.open(SyncSaveSelectUI, { loc: props.loc });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyncSaveChoice.FromServer: {
|
||||||
|
// todo
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyncSaveChoice.ToLocal: {
|
||||||
|
props.controller.open(DownloadSaveSelectUI, { loc: props.loc });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyncSaveChoice.FromLocal: {
|
||||||
|
// todo
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyncSaveChoice.ClearLocal: {
|
||||||
|
props.controller.open(ClearSaveSelectUI, { loc: props.loc });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyncSaveChoice.Back: {
|
||||||
|
props.controller.close(props.instance);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<Choices
|
||||||
|
loc={props.loc}
|
||||||
|
width={240}
|
||||||
|
choices={choices}
|
||||||
|
onChoose={choose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SyncSaveSelect = defineComponent<SettingsProps>(props => {
|
||||||
|
const choices: ChoiceItem[] = [
|
||||||
|
[SyncSaveChoice.AllSaves, '同步全部存档'],
|
||||||
|
[SyncSaveChoice.NowSave, '同步当前存档'],
|
||||||
|
[SyncSaveChoice.Back, '返回上一级']
|
||||||
|
];
|
||||||
|
|
||||||
|
const choose = async (key: ChoiceKey) => {
|
||||||
|
switch (key) {
|
||||||
|
case SyncSaveChoice.AllSaves: {
|
||||||
|
core.playSound('confirm.opus');
|
||||||
|
const confirm = await getConfirm(
|
||||||
|
props.controller,
|
||||||
|
'你确定要同步全部存档么?这可能在存档较多的时候比较慢。',
|
||||||
|
props.loc,
|
||||||
|
240
|
||||||
|
);
|
||||||
|
if (confirm) {
|
||||||
|
core.syncSave('all');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyncSaveChoice.NowSave: {
|
||||||
|
core.playSound('confirm.opus');
|
||||||
|
const confirm = await getConfirm(
|
||||||
|
props.controller,
|
||||||
|
'确定要同步当前存档吗?',
|
||||||
|
props.loc,
|
||||||
|
240
|
||||||
|
);
|
||||||
|
if (confirm) {
|
||||||
|
core.syncSave();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyncSaveChoice.Back: {
|
||||||
|
props.controller.close(props.instance);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<Choices
|
||||||
|
loc={props.loc}
|
||||||
|
width={240}
|
||||||
|
choices={choices}
|
||||||
|
onChoose={choose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DownloadSaveSelect = defineComponent<SettingsProps>(props => {
|
||||||
|
const choices: ChoiceItem[] = [
|
||||||
|
[SyncSaveChoice.AllSaves, '下载全部存档'],
|
||||||
|
[SyncSaveChoice.NowSave, '下载当前存档'],
|
||||||
|
[SyncSaveChoice.Back, '返回上一级']
|
||||||
|
];
|
||||||
|
|
||||||
|
const choose = async (key: ChoiceKey) => {
|
||||||
|
switch (key) {
|
||||||
|
case SyncSaveChoice.AllSaves: {
|
||||||
|
const confirm = await getConfirm(
|
||||||
|
props.controller,
|
||||||
|
'确认要下载所有存档吗?',
|
||||||
|
props.loc,
|
||||||
|
240
|
||||||
|
);
|
||||||
|
if (confirm) {
|
||||||
|
const data = await waitbox(
|
||||||
|
props.controller,
|
||||||
|
props.loc,
|
||||||
|
240,
|
||||||
|
getAllSavesData(),
|
||||||
|
{ text: '请等待处理完毕' }
|
||||||
|
);
|
||||||
|
core.download(
|
||||||
|
`${core.firstData.name}_${core.formatDate2(new Date())}.h5save`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyncSaveChoice.NowSave: {
|
||||||
|
const confirm = await getConfirm(
|
||||||
|
props.controller,
|
||||||
|
'确认要下载当前存档吗?',
|
||||||
|
props.loc,
|
||||||
|
240
|
||||||
|
);
|
||||||
|
if (confirm) {
|
||||||
|
const data = await getSaveData(core.saves.saveIndex);
|
||||||
|
core.download(
|
||||||
|
`${core.firstData.name}_${core.formatDate2(new Date())}.h5save`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyncSaveChoice.Back: {
|
||||||
|
props.controller.close(props.instance);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<Choices
|
||||||
|
loc={props.loc}
|
||||||
|
width={240}
|
||||||
|
choices={choices}
|
||||||
|
onChoose={choose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ClearSaveSelect = defineComponent<SettingsProps>(props => {
|
||||||
|
const choices: ChoiceItem[] = [
|
||||||
|
[SyncSaveChoice.AllSaves, '清空全部塔存档'],
|
||||||
|
[SyncSaveChoice.NowSave, '清空当前塔存档'],
|
||||||
|
[SyncSaveChoice.Back, '返回上一级']
|
||||||
|
];
|
||||||
|
|
||||||
|
const choose = async (key: ChoiceKey) => {
|
||||||
|
switch (key) {
|
||||||
|
case SyncSaveChoice.AllSaves: {
|
||||||
|
const confirm = await getConfirm(
|
||||||
|
props.controller,
|
||||||
|
'你确定要清除【全部游戏】的所有本地存档?此行为不可逆!!!',
|
||||||
|
props.loc,
|
||||||
|
240
|
||||||
|
);
|
||||||
|
if (confirm) {
|
||||||
|
await waitbox(
|
||||||
|
props.controller,
|
||||||
|
props.loc,
|
||||||
|
240,
|
||||||
|
new Promise<void>(res => {
|
||||||
|
core.clearLocalForage(() => {
|
||||||
|
core.saves.ids = {};
|
||||||
|
core.saves.autosave.data = null;
|
||||||
|
core.saves.autosave.updated = false;
|
||||||
|
core.saves.autosave.now = 0;
|
||||||
|
// @ts-expect-error 沙比样板
|
||||||
|
core.saves.cache = {};
|
||||||
|
core.saves.saveIndex = 1;
|
||||||
|
core.saves.favorite = [];
|
||||||
|
core.saves.favoriteName = {};
|
||||||
|
// @ts-expect-error 沙比样板
|
||||||
|
core.control._updateFavoriteSaves();
|
||||||
|
core.removeLocalStorage('saveIndex');
|
||||||
|
res();
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
{ text: '正在情况,请稍后...' }
|
||||||
|
);
|
||||||
|
await getConfirm(
|
||||||
|
props.controller,
|
||||||
|
'所有塔的存档已经全部清空',
|
||||||
|
props.loc,
|
||||||
|
240
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyncSaveChoice.NowSave: {
|
||||||
|
const confirm = await getConfirm(
|
||||||
|
props.controller,
|
||||||
|
'你确定要清除【当前游戏】的所有本地存档?此行为不可逆!!!',
|
||||||
|
props.loc,
|
||||||
|
240
|
||||||
|
);
|
||||||
|
if (confirm) {
|
||||||
|
await waitbox(
|
||||||
|
props.controller,
|
||||||
|
props.loc,
|
||||||
|
240,
|
||||||
|
new Promise<void>(res => {
|
||||||
|
Object.keys(core.saves.ids).forEach(function (v) {
|
||||||
|
core.removeLocalForage('save' + v);
|
||||||
|
});
|
||||||
|
core.removeLocalForage('autoSave', () => {
|
||||||
|
core.saves.ids = {};
|
||||||
|
core.saves.autosave.data = null;
|
||||||
|
core.saves.autosave.updated = false;
|
||||||
|
core.saves.autosave.now = 0;
|
||||||
|
core.ui.closePanel();
|
||||||
|
core.saves.saveIndex = 1;
|
||||||
|
core.saves.favorite = [];
|
||||||
|
core.saves.favoriteName = {};
|
||||||
|
// @ts-expect-error 沙比样板
|
||||||
|
core.control._updateFavoriteSaves();
|
||||||
|
core.removeLocalStorage('saveIndex');
|
||||||
|
res();
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
{ text: '正在情况,请稍后...' }
|
||||||
|
);
|
||||||
|
await getConfirm(
|
||||||
|
props.controller,
|
||||||
|
'当前塔的存档已被清空',
|
||||||
|
props.loc,
|
||||||
|
240
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyncSaveChoice.Back: {
|
||||||
|
props.controller.close(props.instance);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<Choices
|
||||||
|
loc={props.loc}
|
||||||
|
width={240}
|
||||||
|
choices={choices}
|
||||||
|
onChoose={choose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
/** @see {@link MainSettings} */
|
||||||
|
export const MainSettingsUI = new GameUI('main-settings', MainSettings);
|
||||||
|
/** @see {@link ReplaySettings} */
|
||||||
|
export const ReplaySettingsUI = new GameUI('replay-settings', ReplaySettings);
|
||||||
|
/** @see {@link GameInfo} */
|
||||||
|
export const GameInfoUI = new GameUI('game-info', GameInfo);
|
||||||
|
/** @see {@link SyncSave} */
|
||||||
|
export const SyncSaveUI = new GameUI('sync-save', SyncSave);
|
||||||
|
/** @see {@link SyncSaveSelect} */
|
||||||
|
export const SyncSaveSelectUI = new GameUI('sync-save-select', SyncSaveSelect);
|
||||||
|
/** @see {@link DownloadSaveSelect} */
|
||||||
|
export const DownloadSaveSelectUI = new GameUI(
|
||||||
|
'download-save-select',
|
||||||
|
DownloadSaveSelect
|
||||||
|
);
|
||||||
|
/** @see {@link ClearSaveSelect} */
|
||||||
|
export const ClearSaveSelectUI = new GameUI(
|
||||||
|
'clear-save-select',
|
||||||
|
ClearSaveSelect
|
||||||
|
);
|
@ -1,4 +1,4 @@
|
|||||||
import { GameUI } from '@/core/system';
|
import { GameUI, UIComponentProps } from '@/core/system';
|
||||||
import { computed, defineComponent, ref, watch } from 'vue';
|
import { computed, defineComponent, ref, watch } from 'vue';
|
||||||
import { SetupComponentOptions, TextContent } from '../components';
|
import { SetupComponentOptions, TextContent } from '../components';
|
||||||
import { DefaultProps, ElementLocator, Sprite, Font } from '@/core/render';
|
import { DefaultProps, ElementLocator, Sprite, Font } from '@/core/render';
|
||||||
@ -34,7 +34,7 @@ export interface ILeftHeroStatus {
|
|||||||
magicDef: number;
|
magicDef: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StatusBarProps<T> extends DefaultProps {
|
interface StatusBarProps<T> extends DefaultProps, UIComponentProps {
|
||||||
loc: ElementLocator;
|
loc: ElementLocator;
|
||||||
status: T;
|
status: T;
|
||||||
hidden: boolean;
|
hidden: boolean;
|
||||||
|
1
src/module/utils/index.ts
Normal file
1
src/module/utils/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './saves';
|
35
src/module/utils/saves.ts
Normal file
35
src/module/utils/saves.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
export function getAllSavesData() {
|
||||||
|
return new Promise<string>(res => {
|
||||||
|
core.getAllSaves(saves => {
|
||||||
|
if (!saves) {
|
||||||
|
res('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const content = {
|
||||||
|
name: core.firstData.name,
|
||||||
|
version: core.firstData.version,
|
||||||
|
data: saves
|
||||||
|
};
|
||||||
|
// @ts-expect-error 暂时无法推导
|
||||||
|
res(LZString.compressToBase64(JSON.stringify(content)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSaveData(index: number) {
|
||||||
|
return new Promise<string>(res => {
|
||||||
|
core.getSave(index, data => {
|
||||||
|
if (!data) {
|
||||||
|
res('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const content = {
|
||||||
|
name: core.firstData.name,
|
||||||
|
version: core.firstData.version,
|
||||||
|
data: data
|
||||||
|
};
|
||||||
|
// @ts-expect-error 暂时无法推导
|
||||||
|
res(LZString.compressToBase64(JSON.stringify(content)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
6
src/types/core.d.ts
vendored
6
src/types/core.d.ts
vendored
@ -657,7 +657,7 @@ interface CoreSave {
|
|||||||
/**
|
/**
|
||||||
* 自动存档信息
|
* 自动存档信息
|
||||||
*/
|
*/
|
||||||
autosave: Readonly<Autosave>;
|
autosave: Autosave;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 收藏的存档
|
* 收藏的存档
|
||||||
@ -679,7 +679,7 @@ interface Autosave {
|
|||||||
/**
|
/**
|
||||||
* 当前存档信息
|
* 当前存档信息
|
||||||
*/
|
*/
|
||||||
data?: Save[];
|
data?: Save[] | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自动存档位的最大值
|
* 自动存档位的最大值
|
||||||
@ -982,7 +982,7 @@ interface Core extends Pick<Main, CoreDataFromMain> {
|
|||||||
/**
|
/**
|
||||||
* 存档信息
|
* 存档信息
|
||||||
*/
|
*/
|
||||||
readonly saves: Readonly<CoreSave>;
|
readonly saves: CoreSave;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局数值信息
|
* 全局数值信息
|
||||||
|
2
src/types/event.d.ts
vendored
2
src/types/event.d.ts
vendored
@ -46,7 +46,7 @@ interface Events extends EventData {
|
|||||||
startGame(
|
startGame(
|
||||||
hard: string,
|
hard: string,
|
||||||
seed?: number,
|
seed?: number,
|
||||||
route?: string,
|
route?: string[],
|
||||||
callback?: () => void
|
callback?: () => void
|
||||||
): void;
|
): void;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user