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;
}
if (core.canUseItem(itemId)) {
core.ui.closePanel();
core.useItem(itemId, noRoute, callback);
} else {
core.playSound('操作失败');

View File

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

View File

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

View File

@ -3,9 +3,12 @@
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { onMounted, onUnmounted } from 'vue';
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<{
action?: boolean;
@ -13,20 +16,144 @@ const props = defineProps<{
noBorder?: boolean;
showInfo?: boolean;
autoLocate?: boolean;
width?: number;
height?: number;
}>();
const area = getArea();
const id = requireUniqueSymbol().toFixed(0);
let canvas: HTMLCanvasElement;
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(() => {
const width = props.width ?? 300;
const height = props.height ?? 300;
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.scale = props.scale ?? 3;
drawer.noBorder = props.noBorder ?? 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>

View File

@ -28,6 +28,7 @@ interface CustomToolbarEvent extends EmitableEvent {
interface ToolbarItemBase<T extends ToolbarItemType> {
type: T;
id: string;
noDefaultAction?: boolean;
}
// 快捷键
@ -52,6 +53,8 @@ interface MinimatToolbar extends ToolbarItemBase<'minimap'> {
noBorder: boolean;
showInfo: boolean;
autoLocate: boolean;
width: number;
height: number;
}
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', () => {
CustomToolbar.load();

View File

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

View File

@ -7,7 +7,13 @@ import BoxAnimate from '@/components/boxAnimate.vue';
import { checkAssist } from '../custom/hotkey';
import { getVitualKeyOnce } from '@/plugin/utils';
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 Minimap from '@/components/minimap.vue';
@ -18,6 +24,7 @@ interface Components {
KeyTool: CustomToolbarComponent<'hotkey'>;
ItemTool: CustomToolbarComponent<'item'>;
AssistKeyTool: CustomToolbarComponent<'assistKey'>;
MinimapTool: CustomToolbarComponent<'minimap'>;
}
export function createToolbarComponents() {
@ -25,7 +32,8 @@ export function createToolbarComponents() {
DefaultTool,
KeyTool,
ItemTool,
AssistKeyTool
AssistKeyTool,
MinimapTool
};
return com;
}
@ -35,7 +43,8 @@ export function createToolbarEditorComponents() {
DefaultTool: DefaultToolEditor,
KeyTool: KeyToolEdtior,
ItemTool: ItemToolEditor,
AssistKeyTool: AssistKeyToolEditor
AssistKeyTool: AssistKeyToolEditor,
MinimapTool: MinimapToolEditor
};
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;
return (
<div>
<Minimap></Minimap>
<div style={{ width: `${item.width}px`, height: `${item.height}px` }}>
<Minimap
action={item.action}
scale={item.scale}
noBorder={item.noBorder}
showInfo={item.showInfo}
autoLocate={item.autoLocate}
width={item.width}
height={item.height}
></Minimap>
</div>
);
}
@ -215,3 +232,155 @@ function AssistKeyToolEditor(props: CustomToolbarProps<'assistKey'>) {
</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 */
renderLoaded: () => void;
// /** Emitted in libs/events.js */
// afterGetItem: (
// itemId: AllIdsOf<'items'>,
// x: number,
// y: number,
// isGentleClick: boolean
// ) => void;
// afterOpenDoor: (doorId: AllIdsOf<'animates'>, x: number, y: number) => void;
afterGetItem: (
itemId: AllIdsOf<'items'>,
x: number,
y: number,
isGentleClick: boolean
) => void;
afterOpenDoor: (doorId: AllIdsOf<'animates'>, x: number, y: number) => void;
afterChangeFloor: (floorId: FloorIds) => void;
}
export const hook = new EventEmitter<GameEvent>();

View File

@ -1,3 +1,4 @@
import { mainSetting } from '@/core/main/setting';
import { downloadCanvasImage, has, tip } from '../utils';
type BFSFromString = `${FloorIds},${number},${number},${Dir}`;
@ -225,11 +226,14 @@ export class MinimapDrawer {
private tempCtx: CanvasRenderingContext2D;
private downloadMode: boolean = false;
private innerRatio: number = 1;
constructor(canvas: HTMLCanvasElement) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d')!;
this.tempCtx = this.tempCanvas.getContext('2d')!;
this.innerRatio = mainSetting.getValue('ui.mapScale', 100) / 100;
this.scale *= this.innerRatio;
}
link(canvas: HTMLCanvasElement) {
@ -327,6 +331,8 @@ export class MinimapDrawer {
map.height
);
}
if (!this.noBorder && this.scale * this.innerRatio <= 7)
this.drawEnemy(ctx, id, x, y);
}
/**
@ -375,7 +381,7 @@ export class MinimapDrawer {
if (
this.drawedThumbnail[floorId] ||
(!this.noBorder && scale <= 4) ||
(!this.noBorder && scale * this.innerRatio <= 7) ||
right < 0 ||
bottom < 0 ||
left > map.width ||
@ -414,7 +420,7 @@ export class MinimapDrawer {
w: floor.width,
h: floor.height,
ctx,
damage: this.scale > 7
damage: this.scale * this.innerRatio > 15
});
if (!this.downloadMode) {
if (!core.hasVisitedFloor(floorId)) {
@ -438,21 +444,7 @@ export class MinimapDrawer {
ctx.fillStyle = '#000';
}
}
if (this.scale > 2) {
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();
}
this.drawEnemy(ctx, floorId, x, y);
}
/**
@ -506,4 +498,57 @@ export class MinimapDrawer {
this.ox = (-x + data.width / 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;
}
}
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;
start = document.getElementById('start') as HTMLDivElement;
background = document.getElementById('background') as HTMLImageElement;
CustomToolbar.closeAll();
window.addEventListener('resize', resize);
resize();
@ -325,8 +326,6 @@ onMounted(async () => {
await sleep(1000);
showCursor();
await sleep(1200);
CustomToolbar.closeAll();
});
onUnmounted(() => {

View File

@ -525,7 +525,7 @@ onUnmounted(() => {
display: flex;
margin: v-bind('5 * 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;
border: 1.5px solid #ddd8;
justify-content: center;

View File

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