import { ElementLocator, IWheelEvent } from '@motajs/render-core'; import { DefaultProps } from '@motajs/render-vue'; import { Font } from '@motajs/render'; import { GameUI, IUIMountable, SetupComponentOptions, UIComponentProps } from '@motajs/system-ui'; import { defineComponent, ref, computed } from 'vue'; import { Page, PageExpose } from '../components'; import { useKey } from '../use'; import { MAP_WIDTH, MAP_HEIGHT } from '../shared'; export interface SaveProps extends UIComponentProps, DefaultProps { loc: ElementLocator; } export interface SaveBtnProps extends DefaultProps { loc: ElementLocator; index: number; isSelected: boolean; isDelete: boolean; } export type SaveEmits = { /** 点击存档时触发 */ emit: (index: number, exist: boolean) => void; /** 删除存档时触发 */ delete: (index: number, exist: boolean) => void; /** 手动点击退出时触发 */ exit: () => void; }; const saveProps = { props: ['loc', 'controller', 'instance'], emits: ['delete', 'emit', 'exit'] } satisfies SetupComponentOptions; const saveBtnProps = { props: ['loc', 'index', 'isSelected', 'isDelete'] } satisfies SetupComponentOptions; export const SaveBtn = defineComponent(props => { const w = props.loc[2] ?? 200; const font = new Font('normal', 18); const statusFont = new Font('normal', 14); const text = computed(() => props.index === -1 ? '自动存档' : `存档${props.index + 1}` ); const strokeStyle = computed(() => { if (props.isSelected) return props.isDelete ? 'red' : 'gold'; else return 'white'; }); const lineWidth = computed(() => (props.isSelected ? 2 : 1)); return () => ( ); }, saveBtnProps); export const Save = defineComponent( (props, { emit }) => { const row = 2; const column = 3; /** 除自动存档外,每一页容纳的存档数量 */ const pageCap = row * column - 1; const font = new Font('normal', 18); const pageFont = new Font('normal', 14); const isDelete = ref(false); const pageRef = ref(); /** 当前页上被选中的存档的序号 只会是0到5 */ const pickIndex = ref(1); const emitSave = (index: number) => { if (isDelete.value) emit('delete', index); else emit('emit', index); pickIndex.value = (index % pageCap) + 1; }; const wheel = (ev: IWheelEvent) => { const delta = Math.sign(ev.wheelY); if (ev.ctrlKey) { pageRef.value?.movePage(delta * 10); } else { pageRef.value?.movePage(delta); } }; const toggleDelete = () => { isDelete.value = !isDelete.value; }; const exit = () => { emit('exit'); props.controller.close(props.instance); }; const [key] = useKey(); key.realize('confirm', () => { const currPage = pageRef.value?.now(); if (currPage == null) return; emitSave(pageCap * currPage + pickIndex.value); }) .realize('exit', exit) .realize('@save_exit', exit) .realize( '@save_pageUp', () => { pageRef.value?.movePage(1); }, { type: 'down-repeat' } ) .realize( '@save_pageDown', () => { pageRef.value?.movePage(-1); }, { type: 'down-repeat' } ) .realize( '@save_up', () => { if (!pageRef.value) return; const now = pageRef.value.now(); if (pickIndex.value >= row) { pickIndex.value -= column; } else { if (now === 0) { pickIndex.value = 0; } else { pickIndex.value += pageCap + 1 - column; pageRef.value?.movePage(-1); } } }, { type: 'down-repeat' } ) .realize( '@save_down', () => { if (pickIndex.value <= pageCap - row) pickIndex.value += column; else { pickIndex.value += column - pageCap - 1; pageRef.value?.movePage(1); } }, { type: 'down-repeat' } ) .realize( '@save_left', () => { if (!pageRef.value) return; const now = pageRef.value.now(); if (pickIndex.value > 0) { pickIndex.value--; } else { if (now > 0) { pickIndex.value = pageCap; pageRef.value?.movePage(-1); } } }, { type: 'down-repeat' } ) .realize( '@save_right', () => { if (pickIndex.value < pageCap) pickIndex.value++; else { pickIndex.value = 0; pageRef.value?.movePage(1); } }, { type: 'down-repeat' } ); return () => ( {(page: number) => ( emitSave(-1)} cursor="pointer" /> emitSave(page * pageCap)} cursor="pointer" /> emitSave(page * pageCap + 1)} cursor="pointer" /> emitSave(page * pageCap + 2)} cursor="pointer" /> emitSave(page * pageCap + 3)} cursor="pointer" /> emitSave(page * pageCap + 4)} cursor="pointer" /> )} ); }, saveProps ); export const SaveUI = new GameUI('save', Save); export interface SaveValidation { readonly valid: boolean; readonly message: string; } export type SaveValidationFunction = ( index: number, exist: boolean ) => SaveValidation; /** * 打开存读档界面并让用户选择一个存档。如果用户手动关闭了存档界面,返回 -2,否则返回用户选择的存档索引。 * 参数参考 {@link SaveProps},事件不可自定义。 * * 使用示例: * ```ts * const index = await selectSave(props.controller, [0, 0, 416, 416]); * if (index === -2) { * // 如果用户未选择存档,而是关闭了存档。 * } else if (index === -1) { * // 用户选择了自动存档。 * } else { * // 用户选择了一个存档。 * } * ``` * @param controller 在哪个控制器上打开 * @param loc 存读档界面的坐标 * @param props 传递给存读档界面的参数 * @returns 选择的存档索引 */ export function selectSave( controller: IUIMountable, loc: ElementLocator, validate?: SaveValidationFunction, props?: SaveProps ) { return new Promise(res => { const instance = controller.open(SaveUI, { loc, ...props, onEmit: (index: number, exist: boolean) => { if (!validate) { controller.close(instance); res(index); return; } const validation = validate(index, exist); if (validation.valid) { controller.close(instance); res(index); } else { core.drawTip(validation.message); } }, onExit: () => { res(-2); } }); }); } export async function saveSave( controller: IUIMountable, loc: ElementLocator, props?: SaveProps ) { const validate = (index: number): SaveValidation => { if (index === -1) { return { message: '不能存档至自动存档!', valid: false }; } else { return { message: '', valid: true }; } }; const index = await selectSave(controller, loc, validate, props); if (index === -2) return; core.doSL(index, 'save'); } export async function saveLoad( controller: IUIMountable, loc: ElementLocator, props?: SaveProps ) { const validate = (_: number, exist: boolean): SaveValidation => { return { message: '无效的存档!', valid: exist }; }; const index = await selectSave(controller, loc, validate, props); if (index === -2) return; if (index === -1) { core.doSL('autosave', 'load'); } else { core.doSL(index, 'load'); } }