feat: 自定义工具栏显示小地图

This commit is contained in:
unanmed 2024-04-21 18:15:28 +08:00
parent 59c3b46cfc
commit fef9950542
13 changed files with 421 additions and 41 deletions

View File

@ -4441,7 +4441,6 @@ events.prototype.tryUseItem = function (itemId, noRoute, callback) {
return; return;
} }
if (core.canUseItem(itemId)) { if (core.canUseItem(itemId)) {
core.ui.closePanel();
core.useItem(itemId, noRoute, callback); core.useItem(itemId, noRoute, callback);
} else { } else {
core.playSound('操作失败'); core.playSound('操作失败');

View File

@ -201,6 +201,7 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = {
} }
if (!flags.debug && !main.replayChecking) if (!flags.debug && !main.replayChecking)
Mota.Plugin.require('completion_r').checkVisitedFloor(); Mota.Plugin.require('completion_r').checkVisitedFloor();
Mota.require('var', 'hook').emit('afterChangeFloor', floorId);
}, },
flyTo: function (toId, callback) { flyTo: function (toId, callback) {
// 楼层传送器的使用从当前楼层飞往toId // 楼层传送器的使用从当前楼层飞往toId

View File

@ -13,7 +13,7 @@
</div> </div>
</div> </div>
<div id="ui-fixed"> <div id="ui-fixed">
<template v-for="ui of fixedUi.stack"> <template v-for="ui of fixedUi.stack" :key="ui.num">
<component <component
:is="ui.ui.component" :is="ui.ui.component"
v-on="ui.vOn ?? {}" v-on="ui.vOn ?? {}"

View File

@ -3,9 +3,12 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted } from 'vue'; import { onMounted, onUnmounted } from 'vue';
import { requireUniqueSymbol } from '../plugin/utils'; import { requireUniqueSymbol } from '../plugin/utils';
import { MinimapDrawer } from '../plugin/ui/fly'; import { MinimapDrawer, getArea } from '../plugin/ui/fly';
import { hook } from '@/game/game';
import { useDrag, useWheel } from '@/plugin/use';
import { debounce } from 'lodash-es';
const props = defineProps<{ const props = defineProps<{
action?: boolean; action?: boolean;
@ -13,20 +16,144 @@ const props = defineProps<{
noBorder?: boolean; noBorder?: boolean;
showInfo?: boolean; showInfo?: boolean;
autoLocate?: boolean; autoLocate?: boolean;
width?: number;
height?: number;
}>(); }>();
const area = getArea();
const id = requireUniqueSymbol().toFixed(0); const id = requireUniqueSymbol().toFixed(0);
let canvas: HTMLCanvasElement; let canvas: HTMLCanvasElement;
let drawer: MinimapDrawer; let drawer: MinimapDrawer;
function onChange(floor: FloorIds) {
drawer.nowFloor = floor;
drawer.nowArea =
Object.keys(area).find(v => area[v].includes(core.status.floorId)) ??
'';
drawer.drawedThumbnail = {};
if (props.autoLocate) {
drawer.locateMap(drawer.nowFloor);
}
drawer.drawMap();
}
let scale = props.scale ?? 3;
let lastX = 0;
let lastY = 0;
let moved = false;
let startX = 0;
let startY = 0;
let touchScale = false;
let lastDis = 0;
let lastScale = scale;
const changeScale = debounce((s: number) => {
canvas.style.transform = '';
drawer.drawedThumbnail = {};
drawer.scale = s;
drawer.drawMap();
lastScale = s;
}, 200);
function resize(delta: number) {
drawer.ox *= delta;
drawer.oy *= delta;
scale = delta * scale;
changeScale(scale);
canvas.style.transform = `scale(${scale / lastScale})`;
drawer.thumbnailLoc = {};
}
function drag(x: number, y: number) {
if (touchScale) return;
const dx = x - lastX;
const dy = y - lastY;
drawer.ox += dx;
drawer.oy += dy;
lastX = x;
lastY = y;
drawer.checkMoveThumbnail();
drawer.drawToTarget();
if (Math.abs(x - startX) > 10 || Math.abs(y - startY) > 10) moved = true;
}
function touchdown(e: TouchEvent) {
if (e.touches.length >= 2) {
touchScale = true;
lastDis = Math.sqrt(
(e.touches[0].clientX - e.touches[1].clientX) ** 2 +
(e.touches[0].clientY - e.touches[1].clientY) ** 2
);
}
}
function touchup(e: TouchEvent) {
if (e.touches.length < 2) touchScale = false;
}
function touchmove(e: TouchEvent) {
if (!touchScale) return;
const dis = Math.sqrt(
(e.touches[0].clientX - e.touches[1].clientX) ** 2 +
(e.touches[0].clientY - e.touches[1].clientY) ** 2
);
resize(dis / lastDis);
lastDis = dis;
}
onMounted(() => { onMounted(() => {
const width = props.width ?? 300;
const height = props.height ?? 300;
canvas = document.getElementById(`minimap-${id}`) as HTMLCanvasElement; canvas = document.getElementById(`minimap-${id}`) as HTMLCanvasElement;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
canvas.width = width * devicePixelRatio;
canvas.height = height * devicePixelRatio;
drawer = new MinimapDrawer(canvas); drawer = new MinimapDrawer(canvas);
drawer.scale = props.scale ?? 3; drawer.scale = props.scale ?? 3;
drawer.noBorder = props.noBorder ?? false; drawer.noBorder = props.noBorder ?? false;
drawer.showInfo = props.showInfo ?? false; drawer.showInfo = props.showInfo ?? false;
drawer.drawMap();
hook.on('afterChangeFloor', onChange);
if (props.action) {
useDrag(
canvas,
drag,
(x, y) => {
lastX = x;
lastY = y;
startX = x;
startY = y;
},
() => {
setTimeout(() => {
moved = false;
}, 50);
},
true
);
useWheel(canvas, (x, y) => {
const delta = -Math.sign(y) * 0.1 + 1;
resize(delta);
});
canvas.addEventListener('touchstart', touchdown);
canvas.addEventListener('touchend', touchup);
canvas.addEventListener('touchmove', touchmove);
}
});
onUnmounted(() => {
hook.off('afterChangeFloor', onChange);
}); });
</script> </script>

View File

@ -28,6 +28,7 @@ interface CustomToolbarEvent extends EmitableEvent {
interface ToolbarItemBase<T extends ToolbarItemType> { interface ToolbarItemBase<T extends ToolbarItemType> {
type: T; type: T;
id: string; id: string;
noDefaultAction?: boolean;
} }
// 快捷键 // 快捷键
@ -52,6 +53,8 @@ interface MinimatToolbar extends ToolbarItemBase<'minimap'> {
noBorder: boolean; noBorder: boolean;
showInfo: boolean; showInfo: boolean;
autoLocate: boolean; autoLocate: boolean;
width: number;
height: number;
} }
interface ToolbarItemMap { interface ToolbarItemMap {
@ -407,6 +410,28 @@ CustomToolbar.register(
}; };
} }
); );
CustomToolbar.register(
'minimap',
'小地图',
function (id, item) {
return true;
},
COM.MinimapTool,
EDITOR.MinimapTool,
item => {
return {
action: false,
scale: 5,
width: 300,
height: 300,
noBorder: false,
showInfo: false,
autoLocate: true,
...item,
noDefaultAction: true
};
}
);
Mota.require('var', 'loading').once('coreInit', () => { Mota.require('var', 'loading').once('coreInit', () => {
CustomToolbar.load(); CustomToolbar.load();

View File

@ -147,6 +147,7 @@ function showSpecialSetting(id: string, vBind?: any) {
mainUi.showAll(); mainUi.showAll();
}); });
mainUi.open(id, vBind); mainUi.open(id, vBind);
console.log(core.status.lockControl);
} }
function HotkeySetting(props: SettingComponentProps) { function HotkeySetting(props: SettingComponentProps) {

View File

@ -7,7 +7,13 @@ import BoxAnimate from '@/components/boxAnimate.vue';
import { checkAssist } from '../custom/hotkey'; import { checkAssist } from '../custom/hotkey';
import { getVitualKeyOnce } from '@/plugin/utils'; import { getVitualKeyOnce } from '@/plugin/utils';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { Select, SelectOption } from 'ant-design-vue'; import {
Button,
InputNumber,
Select,
SelectOption,
Switch
} from 'ant-design-vue';
import { mainSetting } from '../setting'; import { mainSetting } from '../setting';
import Minimap from '@/components/minimap.vue'; import Minimap from '@/components/minimap.vue';
@ -18,6 +24,7 @@ interface Components {
KeyTool: CustomToolbarComponent<'hotkey'>; KeyTool: CustomToolbarComponent<'hotkey'>;
ItemTool: CustomToolbarComponent<'item'>; ItemTool: CustomToolbarComponent<'item'>;
AssistKeyTool: CustomToolbarComponent<'assistKey'>; AssistKeyTool: CustomToolbarComponent<'assistKey'>;
MinimapTool: CustomToolbarComponent<'minimap'>;
} }
export function createToolbarComponents() { export function createToolbarComponents() {
@ -25,7 +32,8 @@ export function createToolbarComponents() {
DefaultTool, DefaultTool,
KeyTool, KeyTool,
ItemTool, ItemTool,
AssistKeyTool AssistKeyTool,
MinimapTool
}; };
return com; return com;
} }
@ -35,7 +43,8 @@ export function createToolbarEditorComponents() {
DefaultTool: DefaultToolEditor, DefaultTool: DefaultToolEditor,
KeyTool: KeyToolEdtior, KeyTool: KeyToolEdtior,
ItemTool: ItemToolEditor, ItemTool: ItemToolEditor,
AssistKeyTool: AssistKeyToolEditor AssistKeyTool: AssistKeyToolEditor,
MinimapTool: MinimapToolEditor
}; };
return com; return com;
} }
@ -89,12 +98,20 @@ function AssistKeyTool(props: CustomToolbarProps<'assistKey'>) {
); );
} }
function MinimapToolbar(props: CustomToolbarProps<'minimap'>) { function MinimapTool(props: CustomToolbarProps<'minimap'>) {
const { item, toolbar } = props; const { item, toolbar } = props;
return ( return (
<div> <div style={{ width: `${item.width}px`, height: `${item.height}px` }}>
<Minimap></Minimap> <Minimap
action={item.action}
scale={item.scale}
noBorder={item.noBorder}
showInfo={item.showInfo}
autoLocate={item.autoLocate}
width={item.width}
height={item.height}
></Minimap>
</div> </div>
); );
} }
@ -215,3 +232,155 @@ function AssistKeyToolEditor(props: CustomToolbarProps<'assistKey'>) {
</div> </div>
); );
} }
function MinimapToolEditor(props: CustomToolbarProps<'minimap'>) {
const { item, toolbar } = props;
type K = keyof typeof item;
const setConfig: <T extends K>(key: T, value: (typeof item)[T]) => void = (
key,
value
) => {
let v = value;
if (key === 'height' || key === 'height') {
if ((v as number) > 1000) (v as number) = 1000;
if ((v as number) < 50) (v as number) = 50;
} else if (key === 'scale') {
if ((v as number) > 20) (v as number) = 20;
if ((v as number) < 1) (v as number) = 1;
}
toolbar.set(item.id, { [key]: v });
toolbar.refresh();
};
return (
<div
style="
display: flex; flex-direction: column;
align-items: center; padding: 0 5%; margin: 1%
"
>
<div class="toolbar-editor-item">
<span></span>
<div style="width: 70%; display: flex; justify-content: end">
<InputNumber
class="number-input"
size="large"
// keyboard={true}
min={50}
max={1000}
step={50}
value={item.width}
onChange={value => setConfig('width', value as number)}
></InputNumber>
<Button
style="margin-left: 5%"
class="number-button"
type="primary"
onClick={() => setConfig('width', item.width - 50)}
>
</Button>
<Button
style="margin-left: 5%"
class="number-button"
type="primary"
onClick={() => setConfig('width', item.width + 50)}
>
</Button>
</div>
</div>
<div class="toolbar-editor-item">
<span></span>
<div style="width: 70%; display: flex; justify-content: end">
<InputNumber
class="number-input"
size="large"
// keyboard={true}
min={50}
max={1000}
step={50}
value={item.height}
onChange={value => setConfig('height', value as number)}
></InputNumber>
<Button
style="margin-left: 5%"
class="number-button"
type="primary"
onClick={() => setConfig('height', item.height - 50)}
>
</Button>
<Button
style="margin-left: 5%"
class="number-button"
type="primary"
onClick={() => setConfig('height', item.height + 50)}
>
</Button>
</div>
</div>
<div class="toolbar-editor-item">
<span></span>
<div style="width: 70%; display: flex; justify-content: end">
<InputNumber
class="number-input"
size="large"
// keyboard={true}
min={1}
max={20}
step={1}
value={item.scale}
onChange={value => setConfig('scale', value as number)}
></InputNumber>
<Button
style="margin-left: 5%"
class="number-button"
type="primary"
onClick={() => setConfig('scale', item.scale - 1)}
>
</Button>
<Button
style="margin-left: 5%"
class="number-button"
type="primary"
onClick={() => setConfig('scale', item.scale + 1)}
>
</Button>
</div>
</div>
<div class="toolbar-editor-item">
<span></span>
<Switch
checked={item.noBorder}
onClick={() => setConfig('noBorder', !item.noBorder)}
></Switch>
</div>
<div class="toolbar-editor-item">
<span></span>
<Switch
checked={item.showInfo}
onClick={() => setConfig('showInfo', !item.showInfo)}
></Switch>
</div>
<div class="toolbar-editor-item">
<span></span>
<Switch
checked={item.autoLocate}
onClick={() => setConfig('autoLocate', !item.autoLocate)}
></Switch>
</div>
<div class="toolbar-editor-item">
<span></span>
<Switch
checked={item.action}
onClick={() => setConfig('action', !item.action)}
></Switch>
</div>
</div>
);
}

View File

@ -83,13 +83,14 @@ export interface GameEvent extends EmitableEvent {
/** Emitted in core/index.ts */ /** Emitted in core/index.ts */
renderLoaded: () => void; renderLoaded: () => void;
// /** Emitted in libs/events.js */ // /** Emitted in libs/events.js */
// afterGetItem: ( afterGetItem: (
// itemId: AllIdsOf<'items'>, itemId: AllIdsOf<'items'>,
// x: number, x: number,
// y: number, y: number,
// isGentleClick: boolean isGentleClick: boolean
// ) => void; ) => void;
// afterOpenDoor: (doorId: AllIdsOf<'animates'>, x: number, y: number) => void; afterOpenDoor: (doorId: AllIdsOf<'animates'>, x: number, y: number) => void;
afterChangeFloor: (floorId: FloorIds) => void;
} }
export const hook = new EventEmitter<GameEvent>(); export const hook = new EventEmitter<GameEvent>();

View File

@ -1,3 +1,4 @@
import { mainSetting } from '@/core/main/setting';
import { downloadCanvasImage, has, tip } from '../utils'; import { downloadCanvasImage, has, tip } from '../utils';
type BFSFromString = `${FloorIds},${number},${number},${Dir}`; type BFSFromString = `${FloorIds},${number},${number},${Dir}`;
@ -225,11 +226,14 @@ export class MinimapDrawer {
private tempCtx: CanvasRenderingContext2D; private tempCtx: CanvasRenderingContext2D;
private downloadMode: boolean = false; private downloadMode: boolean = false;
private innerRatio: number = 1;
constructor(canvas: HTMLCanvasElement) { constructor(canvas: HTMLCanvasElement) {
this.canvas = canvas; this.canvas = canvas;
this.ctx = canvas.getContext('2d')!; this.ctx = canvas.getContext('2d')!;
this.tempCtx = this.tempCanvas.getContext('2d')!; this.tempCtx = this.tempCanvas.getContext('2d')!;
this.innerRatio = mainSetting.getValue('ui.mapScale', 100) / 100;
this.scale *= this.innerRatio;
} }
link(canvas: HTMLCanvasElement) { link(canvas: HTMLCanvasElement) {
@ -327,6 +331,8 @@ export class MinimapDrawer {
map.height map.height
); );
} }
if (!this.noBorder && this.scale * this.innerRatio <= 7)
this.drawEnemy(ctx, id, x, y);
} }
/** /**
@ -375,7 +381,7 @@ export class MinimapDrawer {
if ( if (
this.drawedThumbnail[floorId] || this.drawedThumbnail[floorId] ||
(!this.noBorder && scale <= 4) || (!this.noBorder && scale * this.innerRatio <= 7) ||
right < 0 || right < 0 ||
bottom < 0 || bottom < 0 ||
left > map.width || left > map.width ||
@ -414,7 +420,7 @@ export class MinimapDrawer {
w: floor.width, w: floor.width,
h: floor.height, h: floor.height,
ctx, ctx,
damage: this.scale > 7 damage: this.scale * this.innerRatio > 15
}); });
if (!this.downloadMode) { if (!this.downloadMode) {
if (!core.hasVisitedFloor(floorId)) { if (!core.hasVisitedFloor(floorId)) {
@ -438,21 +444,7 @@ export class MinimapDrawer {
ctx.fillStyle = '#000'; ctx.fillStyle = '#000';
} }
} }
if (this.scale > 2) { this.drawEnemy(ctx, floorId, x, y);
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = `3px "normal"`;
ctx.strokeStyle = 'black';
Mota.require('fn', 'ensureFloorDamage')(floorId);
const enemyNum = core.status.maps[floorId].enemy.list.length;
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
ctx.fillRect(x - 6, y - 2, 12, 4);
ctx.fillStyle = 'white';
ctx.strokeText(`${enemyNum}怪物`, x, y);
ctx.fillText(`${enemyNum}怪物`, x, y);
ctx.restore();
}
} }
/** /**
@ -506,4 +498,57 @@ export class MinimapDrawer {
this.ox = (-x + data.width / 2 - 5) * this.scale; this.ox = (-x + data.width / 2 - 5) * this.scale;
this.oy = (-y + data.height / 2 - 5) * this.scale; this.oy = (-y + data.height / 2 - 5) * this.scale;
} }
private drawEnemy(
ctx: CanvasRenderingContext2D,
floorId: FloorIds,
x: number,
y: number
) {
if (
this.scale * this.innerRatio > 10 &&
this.scale * this.innerRatio < 40 &&
!this.downloadMode &&
this.showInfo
) {
ctx.save();
ctx.lineWidth = 0.5;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = `3px "normal"`;
ctx.strokeStyle = 'black';
Mota.require('fn', 'ensureFloorDamage')(floorId);
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
ctx.fillRect(x - 6, y - 2, 12, 4);
ctx.fillStyle = 'white';
const enemy = core.status.maps[floorId].enemy.list;
if (enemy.length === 0) {
ctx.strokeStyle = 'lightgreen';
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.beginPath();
ctx.moveTo(x - 1.5, y);
ctx.lineTo(x - 0.5, y + 1);
ctx.lineTo(x + 1.5, y - 1);
ctx.stroke();
} else {
const ids = [...new Set(enemy.map(v => v.id))];
if (ids.length === 1) {
core.drawIcon(ctx, ids[0], x - 2, y - 2, 4, 4);
} else if (ids.length === 2) {
core.drawIcon(ctx, ids[0], x - 4, y - 2, 4, 4);
core.drawIcon(ctx, ids[1], x, y - 2, 4, 4);
} else {
core.drawIcon(ctx, ids[0], x - 5, y - 2, 4, 4);
core.drawIcon(ctx, ids[1], x - 1, y - 2, 4, 4);
ctx.fillStyle = 'white';
ctx.strokeStyle = 'black';
ctx.strokeText('…', x + 4, y);
ctx.fillText('…', x + 4, y);
}
}
ctx.restore();
}
}
} }

View File

@ -111,3 +111,11 @@
background-color: #0000; background-color: #0000;
} }
} }
div.toolbar-editor-item {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
}

View File

@ -309,6 +309,7 @@ onMounted(async () => {
main = document.getElementById('start-main') as HTMLDivElement; main = document.getElementById('start-main') as HTMLDivElement;
start = document.getElementById('start') as HTMLDivElement; start = document.getElementById('start') as HTMLDivElement;
background = document.getElementById('background') as HTMLImageElement; background = document.getElementById('background') as HTMLImageElement;
CustomToolbar.closeAll();
window.addEventListener('resize', resize); window.addEventListener('resize', resize);
resize(); resize();
@ -325,8 +326,6 @@ onMounted(async () => {
await sleep(1000); await sleep(1000);
showCursor(); showCursor();
await sleep(1200); await sleep(1200);
CustomToolbar.closeAll();
}); });
onUnmounted(() => { onUnmounted(() => {

View File

@ -525,7 +525,7 @@ onUnmounted(() => {
display: flex; display: flex;
margin: v-bind('5 * scale + "px"'); margin: v-bind('5 * scale + "px"');
min-width: v-bind('50 * scale + "px"'); min-width: v-bind('50 * scale + "px"');
height: v-bind('50 * scale + "px"'); min-height: v-bind('50 * scale + "px"');
background-color: #222; background-color: #222;
border: 1.5px solid #ddd8; border: 1.5px solid #ddd8;
justify-content: center; justify-content: center;

View File

@ -7,12 +7,14 @@
v-model:top="box.y" v-model:top="box.y"
v-model:height="box.height" v-model:height="box.height"
v-model:width="box.width" v-model:width="box.width"
:key="num"
> >
<div class="toolbar"> <div class="toolbar">
<div <div
class="toolbar-item" class="toolbar-item"
v-for="item of bar.items" v-for="item of bar.items"
@click.stop="click" @click.stop="click"
:noaction="!!item.noDefaultAction"
> >
<component <component
:is="(CustomToolbar.info[item.type].show as any)" :is="(CustomToolbar.info[item.type].show as any)"
@ -97,8 +99,7 @@ onUnmounted(() => {
display: flex; display: flex;
margin: v-bind('scale * 5 + "px"'); margin: v-bind('scale * 5 + "px"');
min-width: v-bind('scale * 50 + "px"'); min-width: v-bind('scale * 50 + "px"');
height: v-bind('scale * 50 + "px"'); min-height: v-bind('scale * 50 + "px"');
cursor: pointer;
background-color: #222; background-color: #222;
border: 1.5px solid #ddd8; border: 1.5px solid #ddd8;
justify-content: center; justify-content: center;
@ -106,6 +107,10 @@ onUnmounted(() => {
transition: all 0.1s linear; transition: all 0.1s linear;
} }
.toolbar-item[noaction='false'] {
cursor: pointer;
}
.toolbar-item::v-deep(> *) { .toolbar-item::v-deep(> *) {
height: 100%; height: 100%;
min-width: v-bind('scale * 50 + "px"'); min-width: v-bind('scale * 50 + "px"');
@ -123,11 +128,11 @@ onUnmounted(() => {
background-color: #555; background-color: #555;
} }
.toolbar-item:hover { .toolbar-item:hover[noaction='false'] {
background-color: #444; background-color: #444;
} }
.toolbar-item:active { .toolbar-item:active[noaction='false'] {
background-color: #333; background-color: #333;
} }
</style> </style>