Compare commits

..

7 Commits

Author SHA1 Message Date
ShakeFlower
5b5708c5cb chore:修正写法 2025-06-23 17:24:37 +08:00
32184d23a2 fix: 读档报错 2025-06-23 17:11:21 +08:00
1bfad1d5e7 chore: 添加 == != xxx={true} 的 eslint 限制 2025-06-23 16:57:43 +08:00
ShakeFlower
a2cbe66409 fix:绘制存档界面缩略图 2025-06-23 16:04:58 +08:00
ShakeFlower
f32b9edb72 Merge branch 'dev' of https://github.com/unanmed/HumanBreak into dev 2025-06-23 15:55:43 +08:00
ShakeFlower
462f0d5b7e feat:绘制存档界面缩略图 2025-06-23 15:55:38 +08:00
dd4d94d513 fix: 缩略图 2025-06-23 11:10:45 +08:00
10 changed files with 1088 additions and 27 deletions

View File

@ -3,18 +3,22 @@ import globals from 'globals';
import tseslint from 'typescript-eslint'; import tseslint from 'typescript-eslint';
import eslintPluginVue from 'eslint-plugin-vue'; import eslintPluginVue from 'eslint-plugin-vue';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import eslintPluginReact from 'eslint-plugin-react';
export default tseslint.config( export default tseslint.config(
{ {
ignores: ['node_modules', 'dist', 'public'] ignores: ['node_modules', 'dist', 'public']
}, },
eslint.configs.recommended, eslint.configs.recommended,
...tseslint.configs.recommended, ...tseslint.configs.recommended,
...eslintPluginVue.configs['flat/recommended'], ...eslintPluginVue.configs['flat/recommended'],
eslintPluginPrettierRecommended,
{ {
files: ['**/*.{js,mjs,cjs,vue}'], files: ['**/*.{js,mjs,cjs,vue}'],
rules: { rules: {
'no-console': 'warn' 'no-console': 'warn',
eqeqeq: ['error', 'always']
} }
}, },
{ {
@ -47,6 +51,9 @@ export default tseslint.config(
}, },
{ {
files: ['**/*.{ts,tsx,vue}'], files: ['**/*.{ts,tsx,vue}'],
plugins: {
react: eslintPluginReact
},
rules: { rules: {
'@typescript-eslint/no-empty-object-type': 'off', '@typescript-eslint/no-empty-object-type': 'off',
'@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-explicit-any': 'off',
@ -65,8 +72,9 @@ export default tseslint.config(
'@typescript-eslint/no-namespace': 'off', '@typescript-eslint/no-namespace': 'off',
'@typescript-eslint/no-this-alias': 'off', '@typescript-eslint/no-this-alias': 'off',
'no-console': 'warn', 'no-console': 'warn',
'vue/multi-word-component-names': 'off' 'vue/multi-word-component-names': 'off',
eqeqeq: ['error', 'always'],
'react/jsx-boolean-value': ['error', 'never']
} }
}, }
eslintPluginPrettierRecommended
); );

View File

@ -63,6 +63,7 @@
"concurrently": "^9.1.2", "concurrently": "^9.1.2",
"eslint": "^9.22.0", "eslint": "^9.22.0",
"eslint-plugin-prettier": "^5.5.0", "eslint-plugin-prettier": "^5.5.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-vue": "^9.33.0", "eslint-plugin-vue": "^9.33.0",
"fontmin": "^0.9.9", "fontmin": "^0.9.9",
"form-data": "^4.0.2", "form-data": "^4.0.2",

View File

@ -5,6 +5,7 @@ import {
} from '@motajs/render-core'; } from '@motajs/render-core';
import { SpriteProps } from '@motajs/render-vue'; import { SpriteProps } from '@motajs/render-vue';
import { defineComponent, ref, watch } from 'vue'; import { defineComponent, ref, watch } from 'vue';
import { SetupComponentOptions } from '@motajs/system-ui';
export interface ThumbnailProps extends SpriteProps { export interface ThumbnailProps extends SpriteProps {
loc: ElementLocator; loc: ElementLocator;
@ -15,8 +16,25 @@ export interface ThumbnailProps extends SpriteProps {
// configs // configs
damage?: boolean; damage?: boolean;
all?: boolean; all?: boolean;
noHD?: boolean;
/** 缩略图的比例 */
size?: number;
} }
const thumbnailProps = {
props: [
'loc',
'padStyle',
'floorId',
'map',
'hero',
'damage',
'all',
'noHD',
'size'
]
} satisfies SetupComponentOptions<ThumbnailProps>;
export const Thumbnail = defineComponent<ThumbnailProps>(props => { export const Thumbnail = defineComponent<ThumbnailProps>(props => {
const spriteRef = ref<Sprite>(); const spriteRef = ref<Sprite>();
@ -25,15 +43,16 @@ export const Thumbnail = defineComponent<ThumbnailProps>(props => {
}; };
const drawThumbnail = (canvas: MotaOffscreenCanvas2D) => { const drawThumbnail = (canvas: MotaOffscreenCanvas2D) => {
const ctx = canvas.ctx;
const hero = props.hero; const hero = props.hero;
const options: Partial<DrawThumbnailConfig> = { const options: Partial<DrawThumbnailConfig> = {
damage: props.damage, damage: props.damage,
ctx: canvas.ctx, ctx: ctx,
x: 0, x: 0,
y: 0, y: 0,
size: 1, size: props.size ?? 1,
all: props.all, all: props.all,
noHD: false, noHD: props.noHD,
v2: true, v2: true,
inFlyMap: false inFlyMap: false
}; };
@ -44,7 +63,11 @@ export const Thumbnail = defineComponent<ThumbnailProps>(props => {
options.centerX = hero.loc.x; options.centerX = hero.loc.x;
options.centerY = hero.loc.y; options.centerY = hero.loc.y;
} }
ctx.save();
ctx.fillStyle = props.padStyle;
ctx.fillRect(0, 0, canvas.width, canvas.height);
core.drawThumbnail(props.floorId, props.map, options); core.drawThumbnail(props.floorId, props.map, options);
ctx.restore();
}; };
watch(props, update); watch(props, update);
@ -52,4 +75,4 @@ export const Thumbnail = defineComponent<ThumbnailProps>(props => {
return () => ( return () => (
<sprite ref={spriteRef} loc={props.loc} render={drawThumbnail} /> <sprite ref={spriteRef} loc={props.loc} render={drawThumbnail} />
); );
}); }, thumbnailProps);

View File

@ -407,6 +407,10 @@ export class HeroRenderer
this.animateTick(time); this.animateTick(time);
this.moveTick(time); this.moveTick(time);
}); });
if (core.status.hero) {
const image = core.status.hero.image;
this.setImage(core.material.images.images[image]);
}
} }
onDestroy(layer: Layer): void { onDestroy(layer: Layer): void {

View File

@ -7,10 +7,12 @@ import {
SetupComponentOptions, SetupComponentOptions,
UIComponentProps UIComponentProps
} from '@motajs/system-ui'; } from '@motajs/system-ui';
import { defineComponent, ref, computed } from 'vue'; import { defineComponent, ref, computed, watch, nextTick } from 'vue';
import { Page, PageExpose } from '../components'; import { Page, PageExpose } from '../components';
import { useKey } from '../use'; import { useKey } from '../use';
import { MAP_WIDTH, MAP_HEIGHT } from '../shared'; import { MAP_WIDTH, MAP_HEIGHT } from '../shared';
import { getSave, SaveData } from '../utils';
import { Thumbnail } from '../components/thumbnail';
export interface SaveProps extends UIComponentProps, DefaultProps { export interface SaveProps extends UIComponentProps, DefaultProps {
loc: ElementLocator; loc: ElementLocator;
@ -32,6 +34,11 @@ export type SaveEmits = {
exit: () => void; exit: () => void;
}; };
export type SaveBtnEmits = {
/** 读取数据 */
updateData: (isValid: boolean) => void;
};
const saveProps = { const saveProps = {
props: ['loc', 'controller', 'instance'], props: ['loc', 'controller', 'instance'],
emits: ['delete', 'emit', 'exit'] emits: ['delete', 'emit', 'exit']
@ -41,10 +48,24 @@ const saveBtnProps = {
props: ['loc', 'index', 'isSelected', 'isDelete'] props: ['loc', 'index', 'isSelected', 'isDelete']
} satisfies SetupComponentOptions<SaveBtnProps>; } satisfies SetupComponentOptions<SaveBtnProps>;
export const SaveBtn = defineComponent<SaveBtnProps>(props => { export const SaveBtn = defineComponent<
SaveBtnProps,
SaveBtnEmits,
keyof SaveBtnEmits
>((props, { emit }) => {
const w = props.loc[2] ?? 200; const w = props.loc[2] ?? 200;
const font = new Font('normal', 18); const font = new Font('normal', 18);
const statusFont = new Font('normal', 14); const statusFont = new Font('normal', 14);
const data = ref<SaveData | null>(null);
const mapBlocks = computed(() => {
if (data.value === null) return void 0;
else {
const currData = data.value?.data;
const map = core.maps.loadMap(currData.maps, currData.floorId);
core.extractBlocksForUI(map, currData.hero.flags); // 这一步会向map写入blocks
return map.blocks;
}
});
const text = computed(() => const text = computed(() =>
props.index === -1 ? '自动存档' : `存档${props.index + 1}` props.index === -1 ? '自动存档' : `存档${props.index + 1}`
); );
@ -53,6 +74,18 @@ export const SaveBtn = defineComponent<SaveBtnProps>(props => {
else return 'white'; else return 'white';
}); });
const lineWidth = computed(() => (props.isSelected ? 2 : 1)); const lineWidth = computed(() => (props.isSelected ? 2 : 1));
watch(
() => props.index,
newIndex => {
getSave(newIndex + 1).then(value => {
data.value = value;
emit('updateData', value != null);
});
},
{ immediate: true }
);
return () => ( return () => (
<container loc={props.loc}> <container loc={props.loc}>
<text <text
@ -69,6 +102,17 @@ export const SaveBtn = defineComponent<SaveBtnProps>(props => {
lineWidth={lineWidth.value} lineWidth={lineWidth.value}
lineJoin="miter" lineJoin="miter"
/> />
<Thumbnail
hidden={data.value === null}
loc={[3, 26, w - 6, w - 4]}
padStyle="gray"
floorId={data.value?.data.floorId || 'MT0'}
map={mapBlocks.value}
hero={data.value?.data.hero as HeroStatus}
all
noHD
size={w / MAP_WIDTH}
/>
<text <text
text="placeholder" text="placeholder"
fillStyle="yellow" fillStyle="yellow"
@ -88,14 +132,26 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
const font = new Font('normal', 18); const font = new Font('normal', 18);
const pageFont = new Font('normal', 14); const pageFont = new Font('normal', 14);
/** 各个存档是否有数据 */
const saveValidArr = Array(pageCap).fill(false);
/** 这里参数是存档在当前页的索引posIndex */
const updateValid = (index: number) => (isValid: boolean) => {
saveValidArr[index] = isValid;
};
const isDelete = ref(false); const isDelete = ref(false);
const pageRef = ref<PageExpose>(); const pageRef = ref<PageExpose>();
/** 当前页上被选中的存档的序号 只会是0到5 */ /** 当前页上被选中的存档的序号 只会是0到5 */
const pickIndex = ref(1); const pickIndex = ref(1);
const getPosIndex = (index: number) => {
if (index === -1) return 0;
return index - pageCap * (pageRef.value?.now() || 0) + 1;
};
const emitSave = (index: number) => { const emitSave = (index: number) => {
if (isDelete.value) emit('delete', index); const posIndex = getPosIndex(index);
else emit('emit', index); if (isDelete.value) emit('delete', index, saveValidArr[posIndex]);
else emit('emit', index, saveValidArr[posIndex]);
pickIndex.value = (index % pageCap) + 1; pickIndex.value = (index % pageCap) + 1;
}; };
@ -117,10 +173,11 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
props.controller.close(props.instance); props.controller.close(props.instance);
}; };
// #region 按键实现
const [key] = useKey(); const [key] = useKey();
key.realize('confirm', () => { key.realize('confirm', () => {
const currPage = pageRef.value?.now(); const currPage = pageRef.value?.now();
if (currPage == null) return; if (currPage === void 0) return;
emitSave(pageCap * currPage + pickIndex.value); emitSave(pageCap * currPage + pickIndex.value);
}) })
.realize('exit', exit) .realize('exit', exit)
@ -160,9 +217,9 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
.realize( .realize(
'@save_down', '@save_down',
() => { () => {
if (pickIndex.value <= pageCap - row) if (pickIndex.value <= pageCap - row) {
pickIndex.value += column; pickIndex.value += column;
else { } else {
pickIndex.value += column - pageCap - 1; pickIndex.value += column - pageCap - 1;
pageRef.value?.movePage(1); pageRef.value?.movePage(1);
} }
@ -188,14 +245,16 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
.realize( .realize(
'@save_right', '@save_right',
() => { () => {
if (pickIndex.value < pageCap) pickIndex.value++; if (pickIndex.value < pageCap) {
else { pickIndex.value++;
} else {
pickIndex.value = 0; pickIndex.value = 0;
pageRef.value?.movePage(1); pageRef.value?.movePage(1);
} }
}, },
{ type: 'down-repeat' } { type: 'down-repeat' }
); );
// #endregion
return () => ( return () => (
<container loc={props.loc} zIndex={10}> <container loc={props.loc} zIndex={10}>
@ -214,6 +273,7 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
isSelected={pickIndex.value === 0} isSelected={pickIndex.value === 0}
isDelete={isDelete.value} isDelete={isDelete.value}
onClick={() => emitSave(-1)} onClick={() => emitSave(-1)}
onUpdateData={updateValid(0)}
cursor="pointer" cursor="pointer"
/> />
<SaveBtn <SaveBtn
@ -222,6 +282,7 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
isSelected={pickIndex.value === 1} isSelected={pickIndex.value === 1}
isDelete={isDelete.value} isDelete={isDelete.value}
onClick={() => emitSave(page * pageCap)} onClick={() => emitSave(page * pageCap)}
onUpdateData={updateValid(1)}
cursor="pointer" cursor="pointer"
/> />
<SaveBtn <SaveBtn
@ -230,6 +291,7 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
isSelected={pickIndex.value === 2} isSelected={pickIndex.value === 2}
isDelete={isDelete.value} isDelete={isDelete.value}
onClick={() => emitSave(page * pageCap + 1)} onClick={() => emitSave(page * pageCap + 1)}
onUpdateData={updateValid(2)}
cursor="pointer" cursor="pointer"
/> />
<SaveBtn <SaveBtn
@ -238,6 +300,7 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
isSelected={pickIndex.value === 3} isSelected={pickIndex.value === 3}
isDelete={isDelete.value} isDelete={isDelete.value}
onClick={() => emitSave(page * pageCap + 2)} onClick={() => emitSave(page * pageCap + 2)}
onUpdateData={updateValid(3)}
cursor="pointer" cursor="pointer"
/> />
<SaveBtn <SaveBtn
@ -246,6 +309,7 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
isSelected={pickIndex.value === 4} isSelected={pickIndex.value === 4}
isDelete={isDelete.value} isDelete={isDelete.value}
onClick={() => emitSave(page * pageCap + 3)} onClick={() => emitSave(page * pageCap + 3)}
onUpdateData={updateValid(4)}
cursor="pointer" cursor="pointer"
/> />
<SaveBtn <SaveBtn
@ -254,6 +318,7 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
isSelected={pickIndex.value === 5} isSelected={pickIndex.value === 5}
isDelete={isDelete.value} isDelete={isDelete.value}
onClick={() => emitSave(page * pageCap + 4)} onClick={() => emitSave(page * pageCap + 4)}
onUpdateData={updateValid(5)}
cursor="pointer" cursor="pointer"
/> />
</container> </container>
@ -358,8 +423,9 @@ export async function saveSave(
} }
}; };
const index = await selectSave(controller, loc, validate, props); const index = await selectSave(controller, loc, validate, props);
if (index === -2) return; if (index === -2) return false;
core.doSL(index, 'save'); core.doSL(index + 1, 'save');
return true;
} }
export async function saveLoad( export async function saveLoad(
@ -371,10 +437,11 @@ export async function saveLoad(
return { message: '无效的存档!', valid: exist }; return { message: '无效的存档!', valid: exist };
}; };
const index = await selectSave(controller, loc, validate, props); const index = await selectSave(controller, loc, validate, props);
if (index === -2) return; if (index === -2) return false;
if (index === -1) { if (index === -1) {
core.doSL('autosave', 'load'); core.doSL('autosave', 'load');
} else { } else {
core.doSL(index, 'load'); core.doSL(index + 1, 'load');
} }
return true;
} }

View File

@ -7,6 +7,7 @@ import {
import { defineComponent, nextTick, onMounted, ref } from 'vue'; import { defineComponent, nextTick, onMounted, ref } from 'vue';
import { MAIN_HEIGHT, MAIN_WIDTH } from '../shared'; import { MAIN_HEIGHT, MAIN_WIDTH } from '../shared';
import { import {
ElementLocator,
IActionEvent, IActionEvent,
MotaOffscreenCanvas2D, MotaOffscreenCanvas2D,
Shader, Shader,
@ -169,8 +170,13 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
buttonsAlpha.set(1); buttonsAlpha.set(1);
}; };
const loadGame = () => { const loadGame = async () => {
saveLoad(props.controller, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]); const loc: ElementLocator = [0, 0, MAIN_WIDTH, MAIN_HEIGHT];
const success = await saveLoad(props.controller, loc);
if (success) {
props.controller.close(props.instance);
props.controller.open(MainSceneUI, {});
}
}; };
const replay = () => { const replay = () => {

View File

@ -21,7 +21,7 @@ import { KeyCode } from '@motajs/client-base';
import { Progress } from '../components/misc'; import { Progress } from '../components/misc';
import { generateBinary } from '@motajs/legacy-common'; import { generateBinary } from '@motajs/legacy-common';
import { SetupComponentOptions } from '@motajs/system-ui'; import { SetupComponentOptions } from '@motajs/system-ui';
import { saveSave } from './save'; import { saveSave, saveLoad } from './save';
import { mainUIController } from './controller'; import { mainUIController } from './controller';
import { MAIN_WIDTH, MAIN_HEIGHT } from '../shared'; import { MAIN_WIDTH, MAIN_HEIGHT } from '../shared';
@ -90,7 +90,9 @@ export const PlayingToolbar = defineComponent<
const save = () => { const save = () => {
saveSave(mainUIController, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]); saveSave(mainUIController, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]);
}; };
const load = () => core.load(true); const load = () => {
saveLoad(mainUIController, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]);
};
const equip = () => core.openEquipbox(true); const equip = () => core.openEquipbox(true);
const shop = () => core.openQuickShop(true); const shop = () => core.openQuickShop(true);
const key = () => { const key = () => {

File diff suppressed because it is too large Load Diff

View File

@ -2147,7 +2147,7 @@ control.prototype._doSL_load_afterGet = function (id, data) {
Mota.r(() => { Mota.r(() => {
Mota.require('@user/legacy-plugin-client').end(false); Mota.require('@user/legacy-plugin-client').end(false);
}); });
core.ui.closePanel(); // core.ui.closePanel();
core.loadData(data, function () { core.loadData(data, function () {
core.removeFlag('__fromLoad__'); core.removeFlag('__fromLoad__');
core.drawTip('读档成功'); core.drawTip('读档成功');

View File

@ -280,6 +280,11 @@ interface ResolvedFloor<T extends FloorIds = FloorIds> extends FloorBase<T> {
* *
*/ */
eachArrive?: MotaEvent; eachArrive?: MotaEvent;
/**
*
*/
blocks?: Block[];
} }
interface BlockInfo<T extends keyof NumberToId = keyof NumberToId> { interface BlockInfo<T extends keyof NumberToId = keyof NumberToId> {