mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-07-23 23:01:49 +08:00
381 lines
13 KiB
TypeScript
381 lines
13 KiB
TypeScript
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<SaveProps, SaveEmits, keyof SaveEmits>;
|
||
|
||
const saveBtnProps = {
|
||
props: ['loc', 'index', 'isSelected', 'isDelete']
|
||
} satisfies SetupComponentOptions<SaveBtnProps>;
|
||
|
||
export const SaveBtn = defineComponent<SaveBtnProps>(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 () => (
|
||
<container loc={props.loc}>
|
||
<text
|
||
text={text.value}
|
||
font={font}
|
||
loc={[w / 2, 20, void 0, void 0, 0.5, 1]}
|
||
/>
|
||
<g-rect
|
||
loc={[lineWidth.value, 24, w - 2 * lineWidth.value, w]}
|
||
fill
|
||
stroke
|
||
fillStyle="gray"
|
||
strokeStyle={strokeStyle.value}
|
||
lineWidth={lineWidth.value}
|
||
lineJoin="miter"
|
||
/>
|
||
<text
|
||
text="placeholder"
|
||
fillStyle="yellow"
|
||
font={statusFont}
|
||
loc={[w / 2, w + 28, void 0, void 0, 0.5, 0]}
|
||
/>
|
||
</container>
|
||
);
|
||
}, saveBtnProps);
|
||
|
||
export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
|
||
(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<PageExpose>();
|
||
/** 当前页上被选中的存档的序号 只会是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 () => (
|
||
<container loc={props.loc} zIndex={10}>
|
||
<Page
|
||
loc={[0, 0, MAP_WIDTH, MAP_HEIGHT - 10]}
|
||
pages={1000}
|
||
onWheel={wheel}
|
||
ref={pageRef}
|
||
font={pageFont}
|
||
>
|
||
{(page: number) => (
|
||
<container loc={[0, 0, MAP_WIDTH, MAP_HEIGHT]}>
|
||
<SaveBtn
|
||
loc={[30, 50, 120, 170]}
|
||
index={-1}
|
||
isSelected={pickIndex.value === 0}
|
||
isDelete={isDelete.value}
|
||
onClick={() => emitSave(-1)}
|
||
cursor="pointer"
|
||
/>
|
||
<SaveBtn
|
||
loc={[180, 50, 120, 170]}
|
||
index={page * pageCap}
|
||
isSelected={pickIndex.value === 1}
|
||
isDelete={isDelete.value}
|
||
onClick={() => emitSave(page * pageCap)}
|
||
cursor="pointer"
|
||
/>
|
||
<SaveBtn
|
||
loc={[330, 50, 120, 170]}
|
||
index={page * pageCap + 1}
|
||
isSelected={pickIndex.value === 2}
|
||
isDelete={isDelete.value}
|
||
onClick={() => emitSave(page * pageCap + 1)}
|
||
cursor="pointer"
|
||
/>
|
||
<SaveBtn
|
||
loc={[30, 230, 120, 170]}
|
||
index={page * pageCap + 2}
|
||
isSelected={pickIndex.value === 3}
|
||
isDelete={isDelete.value}
|
||
onClick={() => emitSave(page * pageCap + 2)}
|
||
cursor="pointer"
|
||
/>
|
||
<SaveBtn
|
||
loc={[180, 230, 120, 170]}
|
||
index={page * pageCap + 3}
|
||
isSelected={pickIndex.value === 4}
|
||
isDelete={isDelete.value}
|
||
onClick={() => emitSave(page * pageCap + 3)}
|
||
cursor="pointer"
|
||
/>
|
||
<SaveBtn
|
||
loc={[330, 230, 120, 170]}
|
||
index={page * pageCap + 4}
|
||
isSelected={pickIndex.value === 5}
|
||
isDelete={isDelete.value}
|
||
onClick={() => emitSave(page * pageCap + 4)}
|
||
cursor="pointer"
|
||
/>
|
||
</container>
|
||
)}
|
||
</Page>
|
||
<text
|
||
text="删除模式"
|
||
font={font}
|
||
loc={[30, 450, void 0, void 0, 0, 0]}
|
||
zIndex={10}
|
||
fillStyle={isDelete.value ? 'red' : 'white'}
|
||
onClick={toggleDelete}
|
||
cursor="pointer"
|
||
/>
|
||
<text
|
||
text="返回游戏"
|
||
font={font}
|
||
loc={[450, 450, void 0, void 0, 1, 0]}
|
||
zIndex={10}
|
||
onClick={exit}
|
||
cursor="pointer"
|
||
/>
|
||
</container>
|
||
);
|
||
},
|
||
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<number>(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');
|
||
}
|
||
}
|