Compare commits

...

8 Commits

Author SHA1 Message Date
b93e286c52 fix: 战斗报错 2025-06-17 18:20:21 +08:00
1b18ce4268 chore: 修改 Save 的部分写法 2025-06-17 16:42:24 +08:00
ShakeFlower
5b24af71b1 fix:修复存档界面写法问题 2025-06-17 16:27:38 +08:00
dff0a925e3 fix: propsOption 没加 emits 2025-06-17 15:39:35 +08:00
bcf3d33a9c feat: 存档验证函数 2025-06-17 15:33:18 +08:00
ShakeFlower
f5a12c4a90 Merge branch 'dev' of https://github.com/unanmed/HumanBreak into dev 2025-06-17 15:14:20 +08:00
ShakeFlower
d359563dad feat:存档界面绘制 2025-06-17 15:01:02 +08:00
127af11e5e chore: 将样板类型声明修改到 src/types 文件夹 2025-06-17 14:42:16 +08:00
38 changed files with 334 additions and 71 deletions

View File

@ -1,30 +1,73 @@
import { ElementLocator, IWheelEvent } from '@motajs/render-core'; import { ElementLocator, IWheelEvent } from '@motajs/render-core';
import { DefaultProps } from '@motajs/render-vue'; import { DefaultProps } from '@motajs/render-vue';
import { Font } from '@motajs/render';
import { import {
GameUI, GameUI,
IUIMountable, IUIMountable,
SetupComponentOptions, SetupComponentOptions,
UIComponentProps UIComponentProps
} from '@motajs/system-ui'; } from '@motajs/system-ui';
import { defineComponent } from 'vue'; import { defineComponent, ref } from 'vue';
import { Page } from '../components'; import { Background, Page, PageExpose } from '../components';
import { useKey } from '../use'; import { useKey } from '../use';
import { MAP_WIDTH, MAP_HEIGHT } from '../shared';
export interface SaveProps extends UIComponentProps, DefaultProps { export interface SaveProps extends UIComponentProps, DefaultProps {
loc: ElementLocator; loc: ElementLocator;
} }
export interface SaveBtnProps extends DefaultProps {
loc: ElementLocator;
index: number;
isDelete: boolean;
}
export type SaveEmits = { export type SaveEmits = {
/** 点击存档时触发 */ /** 点击存档时触发 */
emit: (index: number) => void; emit: (index: number) => void;
/** 删除存档时触发 */
delete: (index: number) => void;
/** 手动点击退出时触发 */ /** 手动点击退出时触发 */
exit: () => void; exit: () => void;
}; };
const saveProps = { const saveProps = {
props: ['loc', 'controller', 'instance'] props: ['loc', 'controller', 'instance'],
emits: ['delete', 'emit', 'exit']
} satisfies SetupComponentOptions<SaveProps, SaveEmits, keyof SaveEmits>; } satisfies SetupComponentOptions<SaveProps, SaveEmits, keyof SaveEmits>;
const saveBtnProps = {
props: ['loc', 'index', 'isDelete']
} satisfies SetupComponentOptions<SaveBtnProps>;
export const SaveBtn = defineComponent<SaveBtnProps>(props => {
const w = props.loc[2] ?? 200;
const text = props.index === -1 ? '自动存档' : `存档${props.index + 1}`;
const font = new Font('normal', 18);
return () => (
<container loc={props.loc}>
<text
text={text}
font={font}
loc={[w / 2, 0, void 0, void 0, 0.5, 0]}
/>
<g-rect
loc={[0, 20, w, w]}
fill
stroke
fillStyle="gray"
strokeStyle={props.isDelete ? 'red' : 'white'}
/>
<text
text="placeholder"
fillStyle="yellow"
font={font}
loc={[w / 2, w + 20, void 0, void 0, 0.5, 0]}
/>
</container>
);
}, saveBtnProps);
export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>( export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
(props, { emit }) => { (props, { emit }) => {
// 这些注释写完之后删了 // 这些注释写完之后删了
@ -33,6 +76,13 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
// onEmit 事件在点击存档或按键确认时触发 // onEmit 事件在点击存档或按键确认时触发
// 存读档执行函数在 ../../utils/saves.ts // 存读档执行函数在 ../../utils/saves.ts
/** 除自动存档外,每一页容纳的存档数量 */
const pageCap = 5;
const font = new Font('normal', 18);
const isDelete = ref(false);
const pageRef = ref<PageExpose>();
// 参考 ../../action/hotkey.ts 中的按键定义 // 参考 ../../action/hotkey.ts 中的按键定义
const [key] = useKey(); const [key] = useKey();
key.realize('confirm', () => {}); key.realize('confirm', () => {});
@ -40,27 +90,123 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
// 其他按键自定义,需要新开一个 save 的 group // 其他按键自定义,需要新开一个 save 的 group
const emitSave = (index: number) => { const emitSave = (index: number) => {
emit('emit', index); if (isDelete.value) emit('delete', index);
else emit('emit', index);
}; };
const wheel = (ev: IWheelEvent) => {}; const wheel = (ev: IWheelEvent) => {
const delta = Math.sign(ev.wheelY);
if (ev.ctrlKey) {
pageRef.value?.movePage(delta * 10);
} else {
pageRef.value?.movePage(delta);
}
};
return () => <Page loc={props.loc} pages={1000} onWheel={wheel}></Page>; const toggleDelete = () => {
isDelete.value = !isDelete.value;
};
const exit = () => {
emit('exit');
props.controller.close(props.instance);
};
return () => (
<container loc={props.loc}>
<Background loc={[0, 0, MAP_WIDTH, MAP_HEIGHT]} color="black" />
<Page
loc={[0, 0, MAP_WIDTH, MAP_HEIGHT - 10]}
pages={1000}
onWheel={wheel}
ref={pageRef}
>
{(page: number) => (
<container loc={[0, 0, MAP_WIDTH, MAP_HEIGHT]}>
<SaveBtn
loc={[30, 50, 120, 170]}
index={-1}
isDelete={isDelete.value}
onClick={() => emitSave(-1)}
cursor="pointer"
/>
<SaveBtn
loc={[180, 50, 120, 170]}
index={page * pageCap}
isDelete={isDelete.value}
onClick={() => emitSave(page * pageCap)}
cursor="pointer"
/>
<SaveBtn
loc={[330, 50, 120, 170]}
index={page * pageCap + 1}
isDelete={isDelete.value}
onClick={() => emitSave(page * pageCap + 1)}
cursor="pointer"
/>
<SaveBtn
loc={[30, 230, 120, 170]}
index={page * pageCap + 2}
isDelete={isDelete.value}
onClick={() => emitSave(page * pageCap + 2)}
cursor="pointer"
/>
<SaveBtn
loc={[180, 230, 120, 170]}
index={page * pageCap + 3}
isDelete={isDelete.value}
onClick={() => emitSave(page * pageCap + 3)}
cursor="pointer"
/>
<SaveBtn
loc={[330, 230, 120, 170]}
index={page * pageCap + 4}
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}
/>
<text
text="返回游戏"
font={font}
loc={[450, 450, void 0, void 0, 1, 0]}
zIndex={10}
onClick={exit}
/>
</container>
);
}, },
saveProps saveProps
); );
export const SaveUI = new GameUI('save', Save); export const SaveUI = new GameUI('save', Save);
export interface SaveValidation {
readonly valid: boolean;
readonly message: string;
}
/** /**
* -1 * -2
* {@link SaveProps} * {@link SaveProps}
* *
* 使 * 使
* ```ts * ```ts
* const index = await selectSave(props.controller, [0, 0, 416, 416]); * const index = await selectSave(props.controller, [0, 0, 416, 416]);
* if (index === -1) { * if (index === -2) {
* // 如果用户未选择存档,而是关闭了存档。 * // 如果用户未选择存档,而是关闭了存档。
* } else if (index === -1) {
* // 用户选择了自动存档。
* } else { * } else {
* // 用户选择了一个存档。 * // 用户选择了一个存档。
* } * }
@ -68,23 +214,34 @@ export const SaveUI = new GameUI('save', Save);
* @param controller * @param controller
* @param loc * @param loc
* @param props * @param props
* @returns * @returns
*/ */
export function selectSave( export function selectSave(
controller: IUIMountable, controller: IUIMountable,
loc: ElementLocator, loc: ElementLocator,
validate?: (index: number) => SaveValidation,
props?: SaveProps props?: SaveProps
) { ) {
return new Promise<number>(res => { return new Promise<number>(res => {
const instance = controller.open(SaveUI, { const instance = controller.open(SaveUI, {
loc, loc,
...props, ...props,
onEmit: index => { onEmit: (index: number) => {
controller.close(instance); if (!validate) {
res(index); controller.close(instance);
res(index);
return;
}
const validation = validate(index);
if (validation.valid) {
controller.close(instance);
res(index);
} else {
core.drawTip(validation.message);
}
}, },
onExit: () => { onExit: () => {
res(-1); res(-2);
} }
}); });
}); });

View File

@ -21,6 +21,9 @@ 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 { selectSave } from './save';
import { mainUIController } from '@user/client-modules';
import { STATUS_BAR_WIDTH, MAP_WIDTH, MAP_HEIGHT } from '../shared';
interface ToolbarProps extends DefaultProps { interface ToolbarProps extends DefaultProps {
loc?: ElementLocator; loc?: ElementLocator;
@ -84,7 +87,21 @@ export const PlayingToolbar = defineComponent<
const book = () => core.openBook(true); const book = () => core.openBook(true);
const tool = () => core.openToolbox(true); const tool = () => core.openToolbox(true);
const fly = () => core.useFly(true); const fly = () => core.useFly(true);
const save = () => core.save(true); const save = async () => {
const index = await selectSave(mainUIController, [
STATUS_BAR_WIDTH,
0,
MAP_WIDTH,
MAP_HEIGHT
]);
if (index === -2) {
// 如果用户未选择存档,而是关闭了存档。
console.log('用户关闭了存档界面。');
} else {
// 用户选择了一个存档。
console.log('用户选择在存档位' + index + '保存。');
}
};
const load = () => core.load(true); const load = () => core.load(true);
const equip = () => core.openEquipbox(true); const equip = () => core.openEquipbox(true);
const shop = () => core.openQuickShop(true); const shop = () => core.openQuickShop(true);

View File

@ -3,6 +3,7 @@
"dependencies": { "dependencies": {
"@motajs/legacy-common": "workspace:*", "@motajs/legacy-common": "workspace:*",
"@user/data-state": "workspace:*", "@user/data-state": "workspace:*",
"@user/data-base": "workspace:*" "@user/data-base": "workspace:*",
"@user/data-utils": "workspace:*"
} }
} }

View File

@ -1,12 +1,11 @@
import { import {
DamageEnemy, DamageEnemy,
ensureFloorDamage, ensureFloorDamage,
getSingleEnemy,
getEnemy, getEnemy,
HeroSkill, HeroSkill,
NightSpecial NightSpecial
} from '@user/data-state'; } from '@user/data-state';
import { hook, loading } from '@user/data-base'; import { hook } from '@user/data-base';
import { Patch, PatchClass } from '@motajs/legacy-common'; import { Patch, PatchClass } from '@motajs/legacy-common';
import { isNil } from 'lodash-es'; import { isNil } from 'lodash-es';
@ -47,7 +46,7 @@ export function patchBattle() {
); );
} }
// 非强制战斗 // 非强制战斗
// @ts-ignore // @ts-expect-error 2.c 重构
if (!core.canBattle(x, y) && !force && !core.status.event.id) { if (!core.canBattle(x, y) && !force && !core.status.event.id) {
core.stopSound(); core.stopSound();
core.playSound('操作失败'); core.playSound('操作失败');
@ -116,7 +115,7 @@ export function patchBattle() {
core.clearContinueAutomaticRoute(); core.clearContinueAutomaticRoute();
// 自动存档 // 自动存档
var inAction = core.status.event.id == 'action'; const inAction = core.status.event.id === 'action';
if (inAction) { if (inAction) {
core.insertAction(beforeBattle, data.x, data.y); core.insertAction(beforeBattle, data.x, data.y);
core.doAction(); core.doAction();
@ -131,7 +130,7 @@ export function patchBattle() {
patch2.add('_action_battle', function (data, x, y, prefix) { patch2.add('_action_battle', function (data, x, y, prefix) {
if (data.id) { if (data.id) {
const enemy = getSingleEnemy(data.id as EnemyIds); // const enemy = getSingleEnemy(data.id as EnemyIds);
// todo: 与不在地图上的怪物战斗 // todo: 与不在地图上的怪物战斗
} else { } else {
if (data.floorId != core.status.floorId) { if (data.floorId != core.status.floorId) {
@ -256,7 +255,6 @@ export function patchBattle() {
} }
); );
} }
loading.once('coreInit', patchBattle);
declare global { declare global {
interface Enemys { interface Enemys {

View File

@ -1,43 +1,49 @@
import { Patch, PatchClass } from '@motajs/legacy-common';
import { EnemyCollection, ensureFloorDamage } from '@user/data-state'; import { EnemyCollection, ensureFloorDamage } from '@user/data-state';
import { formatDamage } from '@user/data-utils'; import { formatDamage } from '@user/data-utils';
export function init() { export function patchDamage() {
core.control.updateDamage = function ( const patch = new Patch(PatchClass.Control);
floorId = core.status.floorId, patch.add(
ctx, 'updateDamage',
thumbnail: boolean = false function (
) { floorId = core.status.floorId,
if (!floorId || core.status.gameOver || main.mode !== 'play') return; ctx,
const onMap = ctx == null; thumbnail: boolean = false
const floor = core.status.maps[floorId]; ) {
if (!floorId || core.status.gameOver || main.mode !== 'play')
return;
const onMap = ctx == null;
const floor = core.status.maps[floorId];
// 没有怪物手册 // 没有怪物手册
// if (!core.hasItem('book')) return; // if (!core.hasItem('book')) return;
core.status.damage.posX = core.bigmap.posX; core.status.damage.posX = core.bigmap.posX;
core.status.damage.posY = core.bigmap.posY; core.status.damage.posY = core.bigmap.posY;
if (!onMap) { if (!onMap) {
const width = core.floors[floorId].width, const width = core.floors[floorId].width,
height = core.floors[floorId].height; height = core.floors[floorId].height;
// 地图过大的缩略图不绘制显伤 // 地图过大的缩略图不绘制显伤
if (width * height > core.bigmap.threshold) return; if (width * height > core.bigmap.threshold) return;
}
// 计算伤害
ensureFloorDamage(floorId);
floor.enemy.extract();
floor.enemy.calRealAttribute();
floor.enemy.calMapDamage();
floor.enemy.emit('calculated');
core.status.damage.data = [];
// floor.enemy.render(true);
// getItemDetail(floorId, onMap); // 宝石血瓶详细信息
if (thumbnail) {
renderThumbnailDamage(floor.enemy);
core.control.drawDamage(ctx, floorId);
}
} }
// 计算伤害 );
ensureFloorDamage(floorId);
floor.enemy.extract();
floor.enemy.calRealAttribute();
floor.enemy.calMapDamage();
floor.enemy.emit('calculated');
core.status.damage.data = [];
// floor.enemy.render(true);
// getItemDetail(floorId, onMap); // 宝石血瓶详细信息
if (thumbnail) {
renderThumbnailDamage(floor.enemy);
this.drawDamage(ctx, floorId);
}
};
} }
function renderThumbnailDamage(col: EnemyCollection) { function renderThumbnailDamage(col: EnemyCollection) {

View File

@ -1,5 +1,7 @@
import { patchBattle } from './battle'; import { patchBattle } from './battle';
import { patchDamage } from './damage';
export function patchAll() { export function patchAll() {
patchBattle(); patchBattle();
patchDamage();
} }

View File

@ -1,5 +0,0 @@
import { init as initItemDetail } from './itemDetail';
initItemDetail();
export * from './itemDetail';

View File

@ -0,0 +1,3 @@
{
"name": "@user/types"
}

View File

@ -0,0 +1,85 @@
export interface IEnemyInfo {}
export interface IDamageInfo {}
export interface IDamageEnemy {
/** 原始怪物信息 */
readonly enemy: Enemy;
/** 该怪物所属的怪物列表 */
readonly collection: IEnemyCollection | null;
/** 怪物横坐标 */
readonly x: number | undefined;
/**
*
*/
getEnemyInfo(): IEnemyInfo;
/**
*
*/
getDamageInfo(): IDamageInfo;
}
export interface IMapDamage {
/** 伤害类型 */
readonly type: string;
/** 伤害值 */
readonly damage: number;
/** 伤害优先级 */
readonly priority: number;
}
export interface IMapDamageSummary {
/** 该点的总伤害 */
readonly totalDamage: number;
/** 该点的伤害信息 */
readonly damages: IMapDamage[];
}
export interface IEnemyCollection {
/** 怪物列表,索引为 x + width * y值表示该点对应的怪物 */
readonly list: Map<number, IDamageEnemy>;
/** 楼层 id */
readonly floorId: FloorIds;
/** 楼层宽度 */
readonly width: number;
/** 楼层高度 */
readonly height: number;
/** 地图伤害 */
readonly mapDamage: Map<number, IMapDamageSummary>;
/** 用于计算本怪物列表中怪物信息的勇士属性 */
readonly hero: HeroStatus;
/**
* null
* @param x
* @param y
*/
getEnemy(x: number, y: number): IDamageEnemy | null;
/**
* null
* @param x
* @param y
*/
getMapDamage(x: number, y: number): IMapDamageSummary | null;
/**
*
*/
refresh(): void;
/**
*
* @param status
*/
with(status: HeroStatus): IEnemyCollection;
}
export interface IDamageSystem {
readonly collections: Map<FloorIds, IEnemyCollection>;
}

View File

@ -0,0 +1 @@
export * from './enemy';

View File

@ -161,7 +161,7 @@ export class Logger {
const n = Math.floor(code / 50) + 1; const n = Math.floor(code / 50) + 1;
const n2 = code % 50; const n2 = code % 50;
const url = `${location.origin}/_docs/logger/warn/warn${n}.html#warn-code-${n2}`; const url = `${location.origin}/_docs/logger/warn/warn${n}.html#warn-code-${n2}`;
console.warn(`[WARNING Code ${code}] ${text}. See ${url}`); console.warn(`[WARNING Code ${code}] ${text} See ${url}`);
} }
} }

View File

@ -118,9 +118,12 @@ export class Patch<T extends PatchClass> {
const set = this.patched[patchClass]; const set = this.patched[patchClass];
const obj = this.getPatchClass(patchClass); const obj = this.getPatchClass(patchClass);
for (const [key, func] of patch.patches) { for (const [key, func] of patch.patches) {
// console.log(key);
if (set.has(key)) { if (set.has(key)) {
logger.warn(49, patchName[patchClass], key); logger.warn(49, patchName[patchClass], key);
} }
set.add(key);
obj[key] = func; obj[key] = func;
} }
this.patchList.delete(patch); this.patchList.delete(patch);

View File

@ -49,13 +49,8 @@ async function buildPackages() {
const dirs = await fs.readdir(packages); const dirs = await fs.readdir(packages);
for (const name of dirs) { for (const name of dirs) {
const dir = path.join(process.cwd(), name); const dir = path.join(process.cwd(), name);
if (name === 'types') { await fs.emptyDir(dir);
await fs.emptyDir(path.join(dir, 'src')); await fs.rmdir(dir);
await fs.rmdir(path.join(dir, 'src'));
} else {
await fs.emptyDir(dir);
await fs.rmdir(dir);
}
} }
} }

View File

@ -32,7 +32,7 @@
"packages-user/**/*.ts", "packages-user/**/*.ts",
"packages-user/**/*.d.ts", "packages-user/**/*.d.ts",
"packages-user/**/*.tsx", "packages-user/**/*.tsx",
"types/**/*.d.ts" "src/types/**/*.d.ts"
], ],
"references": [{ "path": "./tsconfig.node.json" }] "references": [{ "path": "./tsconfig.node.json" }]
} }