mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-01-31 15:09:26 +08:00
楼传
This commit is contained in:
parent
71a4551004
commit
b3602d3f21
1
components.d.ts
vendored
1
components.d.ts
vendored
@ -11,6 +11,7 @@ declare module '@vue/runtime-core' {
|
|||||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||||
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
||||||
ASlider: typeof import('ant-design-vue/es')['Slider']
|
ASlider: typeof import('ant-design-vue/es')['Slider']
|
||||||
|
ASwitch: typeof import('ant-design-vue/es')['Switch']
|
||||||
Box: typeof import('./src/components/box.vue')['default']
|
Box: typeof import('./src/components/box.vue')['default']
|
||||||
BoxAnimate: typeof import('./src/components/boxAnimate.vue')['default']
|
BoxAnimate: typeof import('./src/components/boxAnimate.vue')['default']
|
||||||
Colomn: typeof import('./src/components/colomn.vue')['default']
|
Colomn: typeof import('./src/components/colomn.vue')['default']
|
||||||
|
@ -2685,8 +2685,8 @@ maps.prototype._drawThumbnail_drawToTarget = function (floorId, options) {
|
|||||||
y = options.y || 0,
|
y = options.y || 0,
|
||||||
size = options.size || 1;
|
size = options.size || 1;
|
||||||
// size的含义改为(0,1]范围的系数以适配长方形,默认为1,楼传为3/4,SL界面为0.3
|
// size的含义改为(0,1]范围的系数以适配长方形,默认为1,楼传为3/4,SL界面为0.3
|
||||||
var w = Math.ceil(size * core._PX_),
|
var w = size * core._PX_,
|
||||||
h = Math.ceil(size * core._PY_);
|
h = size * core._PY_;
|
||||||
// 特判是否为编辑器,编辑器中长宽均采用core.js的遗留正方形像素边长,以保证下面的绘制正常
|
// 特判是否为编辑器,编辑器中长宽均采用core.js的遗留正方形像素边长,以保证下面的绘制正常
|
||||||
if (main.mode == 'editor') w = h = size * core.__PIXELS__;
|
if (main.mode == 'editor') w = h = size * core.__PIXELS__;
|
||||||
var width = core.floors[floorId].width,
|
var width = core.floors[floorId].width,
|
||||||
@ -2697,6 +2697,21 @@ maps.prototype._drawThumbnail_drawToTarget = function (floorId, options) {
|
|||||||
if (centerY == null) centerY = Math.floor(height / 2);
|
if (centerY == null) centerY = Math.floor(height / 2);
|
||||||
var tempCanvas = core.bigmap.tempCanvas;
|
var tempCanvas = core.bigmap.tempCanvas;
|
||||||
|
|
||||||
|
if (options.inFlyMap) {
|
||||||
|
ctx.drawImage(
|
||||||
|
tempCanvas.canvas,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
tempCanvas.canvas.width,
|
||||||
|
tempCanvas.canvas.height,
|
||||||
|
options.x,
|
||||||
|
options.y,
|
||||||
|
options.w,
|
||||||
|
options.h
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const scale = core.domStyle.scale * devicePixelRatio;
|
const scale = core.domStyle.scale * devicePixelRatio;
|
||||||
if (options.all) {
|
if (options.all) {
|
||||||
var tempWidth = tempCanvas.canvas.width,
|
var tempWidth = tempCanvas.canvas.width,
|
||||||
|
@ -52,6 +52,7 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = {
|
|||||||
// 隐藏右下角的音乐按钮
|
// 隐藏右下角的音乐按钮
|
||||||
core.dom.musicBtn.style.display = 'none';
|
core.dom.musicBtn.style.display = 'none';
|
||||||
core.dom.enlargeBtn.style.display = 'none';
|
core.dom.enlargeBtn.style.display = 'none';
|
||||||
|
core.splitArea();
|
||||||
},
|
},
|
||||||
win: function (reason, norank, noexit) {
|
win: function (reason, norank, noexit) {
|
||||||
// 游戏获胜事件
|
// 游戏获胜事件
|
||||||
|
@ -325,7 +325,7 @@ var items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a =
|
|||||||
"text": "可以自由往来去过的楼层",
|
"text": "可以自由往来去过的楼层",
|
||||||
"hideInReplay": true,
|
"hideInReplay": true,
|
||||||
"hideInToolbox": true,
|
"hideInToolbox": true,
|
||||||
"useItemEffect": "core.ui.drawFly(core.floorIds.indexOf(core.status.floorId));",
|
"useItemEffect": "core.ui.drawFly();",
|
||||||
"canUseItemEffect": "(function () {\n\treturn core.status.maps[core.status.floorId].canFlyFrom;\n})();"
|
"canUseItemEffect": "(function () {\n\treturn core.status.maps[core.status.floorId].canFlyFrom;\n})();"
|
||||||
},
|
},
|
||||||
"coin": {
|
"coin": {
|
||||||
|
@ -5445,6 +5445,11 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = {
|
|||||||
return (core.plugin.equipOpened.value = true);
|
return (core.plugin.equipOpened.value = true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ui.prototype.drawFly = function () {
|
||||||
|
if (!core.isReplaying())
|
||||||
|
return (core.plugin.flyOpened.value = true);
|
||||||
|
};
|
||||||
|
|
||||||
control.prototype.updateStatusBar_update = function () {
|
control.prototype.updateStatusBar_update = function () {
|
||||||
if (!core.isPlaying() || core.hasFlag('__statistics__')) return;
|
if (!core.isPlaying() || core.hasFlag('__statistics__')) return;
|
||||||
core.control.controldata.updateStatusBar();
|
core.control.controldata.updateStatusBar();
|
||||||
|
@ -44,12 +44,12 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#startPanel {
|
#startPanel {
|
||||||
width: 100%;
|
width: 150%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: -25%;
|
||||||
background-color: #fff;
|
background-color: #000;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
z-index: 300;
|
z-index: 300;
|
||||||
}
|
}
|
||||||
@ -106,6 +106,7 @@ body {
|
|||||||
width: auto;
|
width: auto;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
z-index: 260;
|
z-index: 260;
|
||||||
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
#startLogo {
|
#startLogo {
|
||||||
@ -143,7 +144,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#startButtonGroup {
|
#startButtonGroup {
|
||||||
width: 33%;
|
width: 25%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 1.4em;
|
font-size: 1.4em;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { has } from '../utils';
|
import { has } from '../utils';
|
||||||
|
|
||||||
export default function init() {
|
export default function init() {
|
||||||
return { splitArea, getMapData };
|
return { splitArea, getMapDrawData };
|
||||||
}
|
}
|
||||||
|
|
||||||
type BFSFromString = `${FloorIds},${number},${number},${Dir}`;
|
type BFSFromString = `${FloorIds},${number},${number},${Dir}`;
|
||||||
@ -12,7 +12,20 @@ interface MapBFSResult {
|
|||||||
link: Record<BFSFromString, BFSToString>;
|
link: Record<BFSFromString, BFSToString>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MapDrawData {
|
||||||
|
locs: Partial<Record<FloorIds, LocArr>>;
|
||||||
|
line: [number, number, number, number][];
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
let area: Record<string, FloorIds[]> = {};
|
||||||
|
|
||||||
const bfsCache: Partial<Record<FloorIds, MapBFSResult>> = {};
|
const bfsCache: Partial<Record<FloorIds, MapBFSResult>> = {};
|
||||||
|
/**
|
||||||
|
* 键的格式:FloorIds,interval,border
|
||||||
|
*/
|
||||||
|
const drawCache: Record<string, MapDrawData> = {};
|
||||||
|
|
||||||
const arrow: Partial<Record<AllIds, Dir>> = {
|
const arrow: Partial<Record<AllIds, Dir>> = {
|
||||||
leftPortal: 'left',
|
leftPortal: 'left',
|
||||||
@ -21,9 +34,125 @@ const arrow: Partial<Record<AllIds, Dir>> = {
|
|||||||
downPortal: 'down'
|
downPortal: 'down'
|
||||||
};
|
};
|
||||||
|
|
||||||
export function splitArea() {}
|
/**
|
||||||
|
* 切分地图区域
|
||||||
|
*/
|
||||||
|
export function splitArea() {
|
||||||
|
const used: FloorIds[] = [];
|
||||||
|
for (const id of core.floorIds) {
|
||||||
|
if (used.includes(id) || core.status.maps[id].deleted) continue;
|
||||||
|
const data = getMapData(id, true);
|
||||||
|
used.push(...data.maps);
|
||||||
|
if (data.maps.length > 0) {
|
||||||
|
const title = core.status.maps[id].title;
|
||||||
|
area[title] = data.maps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function getMapDrawData(floorId: FloorIds) {}
|
export function getArea() {
|
||||||
|
return area;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取地图绘制信息
|
||||||
|
* @param floorId 中心楼层
|
||||||
|
* @param interval 地图间距
|
||||||
|
* @param border 边框宽度
|
||||||
|
* @param noCache 是否不使用缓存
|
||||||
|
*/
|
||||||
|
export function getMapDrawData(
|
||||||
|
floorId: FloorIds,
|
||||||
|
interval: number = 5,
|
||||||
|
border: number = 1,
|
||||||
|
noCache: boolean = false
|
||||||
|
): MapDrawData {
|
||||||
|
const id = `${floorId},${interval},${border}`;
|
||||||
|
if (drawCache[id] && !noCache) return drawCache[id];
|
||||||
|
const { link, maps } = getMapData(floorId, noCache);
|
||||||
|
const locs: Partial<Record<FloorIds, LocArr>> = {};
|
||||||
|
const line: [number, number, number, number][] = [];
|
||||||
|
const center = core.status.maps[floorId];
|
||||||
|
let left = -center.width / 2,
|
||||||
|
right = center.width / 2,
|
||||||
|
top = -center.height / 2,
|
||||||
|
bottom = center.height / 2;
|
||||||
|
for (const [from, to] of Object.entries(link)) {
|
||||||
|
const [fromId, fxs, fys, dir] = from.split(',') as [
|
||||||
|
FloorIds,
|
||||||
|
string,
|
||||||
|
string,
|
||||||
|
Dir
|
||||||
|
];
|
||||||
|
const [toId, txs, tys] = to.split(',') as [FloorIds, string, string];
|
||||||
|
const fromMap = core.status.maps[fromId];
|
||||||
|
const toMap = core.status.maps[toId];
|
||||||
|
const fx = parseInt(fxs),
|
||||||
|
fy = parseInt(fys),
|
||||||
|
tx = parseInt(txs),
|
||||||
|
ty = parseInt(tys);
|
||||||
|
const fw = fromMap.width,
|
||||||
|
fh = fromMap.height;
|
||||||
|
const tw = toMap.width,
|
||||||
|
th = toMap.height;
|
||||||
|
locs[fromId] ??= [0, 0];
|
||||||
|
const [fromX, fromY] = locs[fromId]!;
|
||||||
|
if (!locs[toId]) {
|
||||||
|
const dx = core.utils.scan[dir].x,
|
||||||
|
dy = core.utils.scan[dir].y;
|
||||||
|
const toX =
|
||||||
|
fromX +
|
||||||
|
(fx - fw / 2) -
|
||||||
|
(tx - tw / 2) +
|
||||||
|
(border * 2 + interval) * dx,
|
||||||
|
toY =
|
||||||
|
fromY +
|
||||||
|
(fy - fh / 2) -
|
||||||
|
(ty - th / 2) +
|
||||||
|
(border * 2 + interval) * dy;
|
||||||
|
// 地图位置和连线位置
|
||||||
|
locs[toId] = [toX, toY];
|
||||||
|
}
|
||||||
|
const [toX, toY] = locs[toId]!;
|
||||||
|
line.push([
|
||||||
|
fromX + (fx - fw / 2 + 0.5),
|
||||||
|
fromY + (fy - fh / 2 + 0.5),
|
||||||
|
toX + (tx - tw / 2 + 0.5),
|
||||||
|
toY + (ty - th / 2 + 0.5)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 计算地图总长宽
|
||||||
|
const l = toX - tw / 2,
|
||||||
|
r = toX + tw / 2,
|
||||||
|
t = toY - th / 2,
|
||||||
|
b = toY + th / 2;
|
||||||
|
if (l < left) left = l;
|
||||||
|
if (r > right) right = r;
|
||||||
|
if (t < top) top = t;
|
||||||
|
if (b > bottom) bottom = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移动位置,居中
|
||||||
|
Object.values(locs).forEach(v => {
|
||||||
|
v[0] -= left;
|
||||||
|
v[1] -= top;
|
||||||
|
});
|
||||||
|
line.forEach(v => {
|
||||||
|
v[0] -= left;
|
||||||
|
v[2] -= left;
|
||||||
|
v[1] -= top;
|
||||||
|
v[3] -= top;
|
||||||
|
});
|
||||||
|
|
||||||
|
left -= 5;
|
||||||
|
right += 5;
|
||||||
|
top -= 5;
|
||||||
|
bottom += 5;
|
||||||
|
|
||||||
|
const res = { locs, line, width: right - left, height: bottom - top };
|
||||||
|
|
||||||
|
return (drawCache[id] = res);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 广度优先搜索地图信息
|
* 广度优先搜索地图信息
|
||||||
@ -53,12 +182,12 @@ export function getMapData(
|
|||||||
const block = blocks[loc as LocString];
|
const block = blocks[loc as LocString];
|
||||||
const id = block.event.id;
|
const id = block.event.id;
|
||||||
if (id in arrow) {
|
if (id in arrow) {
|
||||||
const from = `${now},${loc},${arrow[id]}` as BFSFromString;
|
|
||||||
const to = `${target},${ev.loc![0]},${
|
|
||||||
ev.loc![1]
|
|
||||||
}` as BFSToString;
|
|
||||||
link[from] = to;
|
|
||||||
if (!used[target]) {
|
if (!used[target]) {
|
||||||
|
const from = `${now},${loc},${arrow[id]}` as BFSFromString;
|
||||||
|
const to = `${target},${ev.loc![0]},${
|
||||||
|
ev.loc![1]
|
||||||
|
}` as BFSToString;
|
||||||
|
link[from] = to;
|
||||||
queue.push(target);
|
queue.push(target);
|
||||||
floors.push(target);
|
floors.push(target);
|
||||||
}
|
}
|
||||||
@ -67,8 +196,74 @@ export function getMapData(
|
|||||||
used[now] = true;
|
used[now] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const res = {
|
||||||
maps: floors,
|
maps: floors,
|
||||||
link
|
link
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return (bfsCache[floorId] = res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制小地图
|
||||||
|
* @param ctx 画布
|
||||||
|
* @param floorId 中心楼层
|
||||||
|
* @param interval 楼层间距
|
||||||
|
* @param border 边框粗细
|
||||||
|
* @param noCache 是否不使用缓存
|
||||||
|
*/
|
||||||
|
export function drawFlyMap(
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
floorId: FloorIds,
|
||||||
|
offset: [number, number],
|
||||||
|
size: [number, number],
|
||||||
|
scale: number = 3,
|
||||||
|
interval: number = 5,
|
||||||
|
border: number = 1,
|
||||||
|
noCache: boolean = false
|
||||||
|
) {
|
||||||
|
const data = getMapDrawData(floorId, interval, border, noCache);
|
||||||
|
const [ox, oy] = offset;
|
||||||
|
const [width, height] = size;
|
||||||
|
const canvas = ctx.canvas;
|
||||||
|
canvas.width = data.width * devicePixelRatio * scale;
|
||||||
|
canvas.height = data.height * devicePixelRatio * scale;
|
||||||
|
ctx.lineWidth = border * devicePixelRatio * scale;
|
||||||
|
ctx.strokeStyle = '#fff';
|
||||||
|
ctx.scale(scale, scale);
|
||||||
|
// 绘制连线
|
||||||
|
data.line.forEach(([x1, y1, x2, y2]) => {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x1, y1);
|
||||||
|
ctx.lineTo(x2, y2);
|
||||||
|
ctx.stroke();
|
||||||
|
});
|
||||||
|
for (const [id, [x, y]] of Object.entries(data.locs) as [
|
||||||
|
FloorIds,
|
||||||
|
LocArr
|
||||||
|
][]) {
|
||||||
|
drawThumbnail(ctx, id, scale, [x, y], offset, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawThumbnail(
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
floorId: FloorIds,
|
||||||
|
scale: number,
|
||||||
|
pos: [number, number],
|
||||||
|
offset: [number, number],
|
||||||
|
size: [number, number]
|
||||||
|
) {
|
||||||
|
const [x, y] = pos;
|
||||||
|
const [ox, oy] = offset;
|
||||||
|
const [width, height] = size;
|
||||||
|
const map = core.status.maps[floorId];
|
||||||
|
if (
|
||||||
|
ox + x * scale + (map.width * scale) / 2 < 0 ||
|
||||||
|
ox + x * scale - (map.width * scale) / 2 > width ||
|
||||||
|
oy + y * scale + (map.height * scale) / 2 < 0 ||
|
||||||
|
oy + y * scale - (map.height * scale) / 2 > height
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import Settings from '../ui/settings.vue';
|
|||||||
import Desc from '../ui/desc.vue';
|
import Desc from '../ui/desc.vue';
|
||||||
import Skill from '../ui/skill.vue';
|
import Skill from '../ui/skill.vue';
|
||||||
import SkillTree from '../ui/skillTree.vue';
|
import SkillTree from '../ui/skillTree.vue';
|
||||||
|
import Fly from '../ui/fly.vue';
|
||||||
|
|
||||||
export const bookOpened = ref(false);
|
export const bookOpened = ref(false);
|
||||||
export const toolOpened = ref(false);
|
export const toolOpened = ref(false);
|
||||||
@ -16,6 +17,7 @@ export const settingsOpened = ref(false);
|
|||||||
export const descOpened = ref(false);
|
export const descOpened = ref(false);
|
||||||
export const skillOpened = ref(false);
|
export const skillOpened = ref(false);
|
||||||
export const skillTreeOpened = ref(false);
|
export const skillTreeOpened = ref(false);
|
||||||
|
export const flyOpened = ref(false);
|
||||||
|
|
||||||
export const transition = ref(true);
|
export const transition = ref(true);
|
||||||
export const noClosePanel = ref(false);
|
export const noClosePanel = ref(false);
|
||||||
@ -30,7 +32,8 @@ const UI_LIST: [Ref<boolean>, Component][] = [
|
|||||||
[settingsOpened, Settings],
|
[settingsOpened, Settings],
|
||||||
[descOpened, Desc],
|
[descOpened, Desc],
|
||||||
[skillOpened, Skill],
|
[skillOpened, Skill],
|
||||||
[skillTreeOpened, SkillTree]
|
[skillTreeOpened, SkillTree],
|
||||||
|
[flyOpened, Fly]
|
||||||
];
|
];
|
||||||
|
|
||||||
/** ui栈 */
|
/** ui栈 */
|
||||||
@ -63,7 +66,8 @@ export default function init() {
|
|||||||
settingsOpened,
|
settingsOpened,
|
||||||
descOpened,
|
descOpened,
|
||||||
skillOpened,
|
skillOpened,
|
||||||
skillTreeOpened
|
skillTreeOpened,
|
||||||
|
flyOpened
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,8 @@
|
|||||||
|
|
||||||
.selectable {
|
.selectable {
|
||||||
border: #0000 0.5px solid;
|
border: #0000 0.5px solid;
|
||||||
|
padding: 1% 3% 1% 3%;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectable[selected='true'] {
|
.selectable[selected='true'] {
|
||||||
|
2
src/types/action.d.ts
vendored
2
src/types/action.d.ts
vendored
@ -132,6 +132,8 @@ interface Actions extends VoidedActionFuncs {
|
|||||||
* @param x 要判断的横坐标
|
* @param x 要判断的横坐标
|
||||||
*/
|
*/
|
||||||
_out(x: number): boolean;
|
_out(x: number): boolean;
|
||||||
|
|
||||||
|
_getNextFlyFloor(delta: number, index: number): number;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare const actions: new () => Actions;
|
declare const actions: new () => Actions;
|
||||||
|
2
src/types/function.d.ts
vendored
2
src/types/function.d.ts
vendored
@ -196,7 +196,7 @@ interface EventData {
|
|||||||
* @param toId 目标楼层
|
* @param toId 目标楼层
|
||||||
* @param callback 飞到后的回调函数
|
* @param callback 飞到后的回调函数
|
||||||
*/
|
*/
|
||||||
flyTo(toId: FloorIds, callback: () => void): void;
|
flyTo(toId: FloorIds, callback?: () => void): boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 与怪物战斗前
|
* 与怪物战斗前
|
||||||
|
30
src/types/map.d.ts
vendored
30
src/types/map.d.ts
vendored
@ -187,6 +187,11 @@ interface Floor<T extends FloorIds = FloorIds> extends FloorBase<T> {
|
|||||||
* 图块信息
|
* 图块信息
|
||||||
*/
|
*/
|
||||||
blocks: Block[];
|
blocks: Block[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否被砍层
|
||||||
|
*/
|
||||||
|
deleted?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ResolvedFloor<T extends FloorIds = FloorIds> extends FloorBase<T> {
|
interface ResolvedFloor<T extends FloorIds = FloorIds> extends FloorBase<T> {
|
||||||
@ -400,6 +405,31 @@ interface DrawThumbnailConfig {
|
|||||||
* 是否使用v2优化
|
* 是否使用v2优化
|
||||||
*/
|
*/
|
||||||
v2: boolean;
|
v2: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否在小地图中出现
|
||||||
|
*/
|
||||||
|
inFlyMap: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小地图模式下的横坐标
|
||||||
|
*/
|
||||||
|
x: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小地图模式下的纵坐标
|
||||||
|
*/
|
||||||
|
y: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小地图模式下的宽度
|
||||||
|
*/
|
||||||
|
w: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小地图模式下的高度
|
||||||
|
*/
|
||||||
|
h: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BlockFilter {
|
interface BlockFilter {
|
||||||
|
13
src/types/plugin.d.ts
vendored
13
src/types/plugin.d.ts
vendored
@ -18,7 +18,8 @@ interface PluginDeclaration
|
|||||||
extends PluginUtils,
|
extends PluginUtils,
|
||||||
PluginUis,
|
PluginUis,
|
||||||
PluginUse,
|
PluginUse,
|
||||||
SkillTree {
|
SkillTree,
|
||||||
|
MiniMap {
|
||||||
/**
|
/**
|
||||||
* 添加函数 例:添加弹出文字,像这个就可以使用core.addPop或core.plugin.addPop调用
|
* 添加函数 例:添加弹出文字,像这个就可以使用core.addPop或core.plugin.addPop调用
|
||||||
* @param px 弹出的横坐标
|
* @param px 弹出的横坐标
|
||||||
@ -149,6 +150,9 @@ interface PluginUis {
|
|||||||
/** 技能树界面是否打开 */
|
/** 技能树界面是否打开 */
|
||||||
readonly skillTreeOpened: Ref<boolean>;
|
readonly skillTreeOpened: Ref<boolean>;
|
||||||
|
|
||||||
|
/** 楼传界面是否打开 */
|
||||||
|
readonly flyOpened: Ref<boolean>;
|
||||||
|
|
||||||
/** ui栈 */
|
/** ui栈 */
|
||||||
readonly uiStack: Ref<Component[]>;
|
readonly uiStack: Ref<Component[]>;
|
||||||
|
|
||||||
@ -246,6 +250,13 @@ interface SkillTree {
|
|||||||
loadSkillTree(data: number[]): void;
|
loadSkillTree(data: number[]): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MiniMap {
|
||||||
|
/**
|
||||||
|
* 切分区域
|
||||||
|
*/
|
||||||
|
splitArea(): void;
|
||||||
|
}
|
||||||
|
|
||||||
type Chapter = 'chapter1';
|
type Chapter = 'chapter1';
|
||||||
|
|
||||||
interface Skill {
|
interface Skill {
|
||||||
|
@ -201,7 +201,6 @@ function keydown(e: KeyboardEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const div = document.getElementById('book') as HTMLDivElement;
|
|
||||||
if (core.plugin.transition.value) await sleep(600);
|
if (core.plugin.transition.value) await sleep(600);
|
||||||
else await sleep(50);
|
else await sleep(50);
|
||||||
document.addEventListener('keyup', keyup);
|
document.addEventListener('keyup', keyup);
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
><div id="desc-list">
|
><div id="desc-list">
|
||||||
<div
|
<div
|
||||||
v-for="(data, k) in desc"
|
v-for="(data, k) in desc"
|
||||||
class="selectable desc-item"
|
class="selectable"
|
||||||
:selected="selected === k"
|
:selected="selected === k"
|
||||||
@click="click(k)"
|
@click="click(k)"
|
||||||
>
|
>
|
||||||
@ -53,9 +53,4 @@ function show(condition: string) {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.desc-item {
|
|
||||||
padding: 1% 3% 1% 3%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
748
src/ui/fly.vue
Normal file
748
src/ui/fly.vue
Normal file
@ -0,0 +1,748 @@
|
|||||||
|
<template>
|
||||||
|
<div id="fly">
|
||||||
|
<div id="tools">
|
||||||
|
<span class="button-text" @click="exit"
|
||||||
|
><left-outlined /> 返回游戏</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div id="fly-settings">
|
||||||
|
<div id="fly-border">
|
||||||
|
<span>无边框模式</span>
|
||||||
|
<a-switch
|
||||||
|
class="fly-settings"
|
||||||
|
v-model:checked="noBorder"
|
||||||
|
checked-children="ON"
|
||||||
|
un-checked-children="OFF"
|
||||||
|
></a-switch>
|
||||||
|
</div>
|
||||||
|
<div v-if="!isMobile" id="fly-tradition">
|
||||||
|
<span>传统按键模式</span>
|
||||||
|
<a-switch
|
||||||
|
class="fly-settings"
|
||||||
|
v-model:checked="tradition"
|
||||||
|
checked-children="ON"
|
||||||
|
un-checked-children="OFF"
|
||||||
|
></a-switch>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="fly-main">
|
||||||
|
<div id="fly-left">
|
||||||
|
<Scroll id="fly-area"
|
||||||
|
><div id="area-list">
|
||||||
|
<span
|
||||||
|
v-for="(v, k) in area"
|
||||||
|
:selected="nowArea === k"
|
||||||
|
class="selectable"
|
||||||
|
@click="nowArea = k"
|
||||||
|
>{{ k }}</span
|
||||||
|
>
|
||||||
|
</div></Scroll
|
||||||
|
>
|
||||||
|
<a-divider type="vertical" dashed id="divider-left"></a-divider>
|
||||||
|
<div id="fly-map-div">
|
||||||
|
<canvas id="fly-map" @click="click"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a-divider
|
||||||
|
id="divider-right"
|
||||||
|
dashed
|
||||||
|
:type="isMobile ? 'horizontal' : 'vertical'"
|
||||||
|
></a-divider>
|
||||||
|
<div id="fly-right">
|
||||||
|
<canvas id="fly-thumbnail" @click="fly"></canvas>
|
||||||
|
<div id="fly-tools">
|
||||||
|
<double-left-outlined
|
||||||
|
@click="changeFloorByDelta(-10)"
|
||||||
|
class="button-text"
|
||||||
|
/>
|
||||||
|
<left-outlined
|
||||||
|
@click="changeFloorByDelta(-1)"
|
||||||
|
class="button-text"
|
||||||
|
/>
|
||||||
|
<span id="fly-now">{{ title }}</span>
|
||||||
|
<right-outlined
|
||||||
|
@click="changeFloorByDelta(1)"
|
||||||
|
class="button-text"
|
||||||
|
/>
|
||||||
|
<double-right-outlined
|
||||||
|
@click="changeFloorByDelta(10)"
|
||||||
|
class="button-text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||||
|
import Scroll from '../components/scroll.vue';
|
||||||
|
import { getArea, getMapDrawData, getMapData } from '../plugin/ui/fly';
|
||||||
|
import { cancelGlobalDrag, isMobile, useDrag, useWheel } from '../plugin/use';
|
||||||
|
import {
|
||||||
|
LeftOutlined,
|
||||||
|
DoubleLeftOutlined,
|
||||||
|
RightOutlined,
|
||||||
|
DoubleRightOutlined
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import { keycode, tip } from '../plugin/utils';
|
||||||
|
import { sleep } from 'mutate-animate';
|
||||||
|
import { KeyCode } from '../plugin/keyCodes';
|
||||||
|
|
||||||
|
type Loc2 = [number, number, number, number];
|
||||||
|
|
||||||
|
const area = getArea();
|
||||||
|
const nowArea = ref(
|
||||||
|
Object.keys(area).find(v => area[v].includes(core.status.floorId))!
|
||||||
|
);
|
||||||
|
const nowFloor = ref(core.status.floorId);
|
||||||
|
const noBorder = ref(false);
|
||||||
|
const tradition = ref(false);
|
||||||
|
let scale = isMobile ? 1.5 : 3;
|
||||||
|
let ox = 0;
|
||||||
|
let oy = 0;
|
||||||
|
let drawedThumbnail: Partial<Record<FloorIds, boolean>> = {};
|
||||||
|
let thumbnailLoc: Partial<Record<FloorIds, Loc2>> = {};
|
||||||
|
|
||||||
|
const floor = computed(() => {
|
||||||
|
return core.status.maps[nowFloor.value];
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(nowFloor, draw);
|
||||||
|
watch(nowArea, n => {
|
||||||
|
ox = 0;
|
||||||
|
oy = 0;
|
||||||
|
scale = 3;
|
||||||
|
lastScale = 3;
|
||||||
|
if (!area[n].includes(nowFloor.value))
|
||||||
|
nowFloor.value =
|
||||||
|
area[n].find(v => v === core.status.floorId) ?? area[n][0];
|
||||||
|
});
|
||||||
|
watch(noBorder, n => {
|
||||||
|
drawedThumbnail = {};
|
||||||
|
drawMap();
|
||||||
|
});
|
||||||
|
|
||||||
|
const temp = document.createElement('canvas');
|
||||||
|
const tempCtx = temp.getContext('2d')!;
|
||||||
|
let map: HTMLCanvasElement;
|
||||||
|
let mapCtx: CanvasRenderingContext2D;
|
||||||
|
let thumb: HTMLCanvasElement;
|
||||||
|
let thumbCtx: CanvasRenderingContext2D;
|
||||||
|
|
||||||
|
function exit() {
|
||||||
|
core.plugin.flyOpened.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = computed(() => {
|
||||||
|
return core.status.maps[nowFloor.value].title;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制小地图
|
||||||
|
* @param noCache 是否不使用缓存
|
||||||
|
*/
|
||||||
|
function drawMap(noCache: boolean = false) {
|
||||||
|
const border = noBorder.value ? 0.5 : 1;
|
||||||
|
const data = getMapDrawData(
|
||||||
|
nowFloor.value,
|
||||||
|
noBorder.value ? 0 : 5,
|
||||||
|
border,
|
||||||
|
noCache
|
||||||
|
);
|
||||||
|
const ctx = tempCtx;
|
||||||
|
const s = scale * devicePixelRatio;
|
||||||
|
temp.width = data.width * s;
|
||||||
|
temp.height = data.height * s;
|
||||||
|
ctx.lineWidth = (border * devicePixelRatio) / 2;
|
||||||
|
ctx.strokeStyle = '#fff';
|
||||||
|
ctx.scale(s, s);
|
||||||
|
ctx.translate(5, 5);
|
||||||
|
|
||||||
|
if (!noBorder.value) {
|
||||||
|
// 绘制连线
|
||||||
|
data.line.forEach(([x1, y1, x2, y2]) => {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x1, y1);
|
||||||
|
ctx.lineTo(x2, y2);
|
||||||
|
ctx.stroke();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制地图及缩略图
|
||||||
|
for (const [id, [x, y]] of Object.entries(data.locs) as [
|
||||||
|
FloorIds,
|
||||||
|
LocArr
|
||||||
|
][]) {
|
||||||
|
if (!noBorder.value) drawBorder(id, x, y);
|
||||||
|
drawThumbnail(id, x, y);
|
||||||
|
}
|
||||||
|
drawToTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawBorder(id: FloorIds, x: number, y: number) {
|
||||||
|
const border = noBorder.value ? 0.5 : 1;
|
||||||
|
const ctx = tempCtx;
|
||||||
|
ctx.lineWidth = border * devicePixelRatio;
|
||||||
|
const map = core.status.maps[id];
|
||||||
|
|
||||||
|
if (!core.hasVisitedFloor(id)) {
|
||||||
|
ctx.fillStyle = '#d0d';
|
||||||
|
} else {
|
||||||
|
ctx.fillStyle = '#000';
|
||||||
|
}
|
||||||
|
if (id === nowFloor.value) {
|
||||||
|
ctx.strokeStyle = 'gold';
|
||||||
|
} else {
|
||||||
|
ctx.strokeStyle = '#fff';
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.strokeRect(
|
||||||
|
x - map.width / 2,
|
||||||
|
y - map.height / 2,
|
||||||
|
map.width,
|
||||||
|
map.height
|
||||||
|
);
|
||||||
|
|
||||||
|
ctx.fillRect(x - map.width / 2, y - map.height / 2, map.width, map.height);
|
||||||
|
if (id === nowFloor.value) {
|
||||||
|
ctx.fillStyle = '#ff04';
|
||||||
|
ctx.fillRect(
|
||||||
|
x - map.width / 2,
|
||||||
|
y - map.height / 2,
|
||||||
|
map.width,
|
||||||
|
map.height
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制小地图至目标画布
|
||||||
|
*/
|
||||||
|
function drawToTarget(s: number = 1) {
|
||||||
|
mapCtx.clearRect(0, 0, map.width, map.height);
|
||||||
|
mapCtx.drawImage(
|
||||||
|
temp,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
temp.width,
|
||||||
|
temp.height,
|
||||||
|
ox * devicePixelRatio + (map.width - temp.width) / 2,
|
||||||
|
oy * devicePixelRatio + (map.height - temp.height) / 2,
|
||||||
|
temp.width,
|
||||||
|
temp.height
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否应该绘制缩略图
|
||||||
|
*/
|
||||||
|
function checkThumbnail(floorId: FloorIds, x: number, y: number) {
|
||||||
|
const floor = core.status.maps[floorId];
|
||||||
|
const s = scale * devicePixelRatio;
|
||||||
|
const px = ox * devicePixelRatio + (map.width - temp.width) / 2 + 5 * s;
|
||||||
|
const py = oy * devicePixelRatio + (map.height - temp.height) / 2 + 5 * s;
|
||||||
|
const left = px + (x - floor.width / 2) * s;
|
||||||
|
const top = py + (y - floor.height / 2) * s;
|
||||||
|
const right = left + floor.width * s;
|
||||||
|
const bottom = top + floor.height * s;
|
||||||
|
|
||||||
|
thumbnailLoc[floorId] = [left, top, right, bottom];
|
||||||
|
|
||||||
|
if (
|
||||||
|
drawedThumbnail[floorId] ||
|
||||||
|
(!noBorder.value && scale <= 4) ||
|
||||||
|
right < 0 ||
|
||||||
|
bottom < 0 ||
|
||||||
|
left > map.width ||
|
||||||
|
top > map.height
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制缩略图
|
||||||
|
*/
|
||||||
|
function drawThumbnail(
|
||||||
|
floorId: FloorIds,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
noCheck: boolean = false
|
||||||
|
) {
|
||||||
|
if (!noCheck && !checkThumbnail(floorId, x, y)) return;
|
||||||
|
const floor = core.status.maps[floorId];
|
||||||
|
drawedThumbnail[floorId] = true;
|
||||||
|
|
||||||
|
// 绘制缩略图
|
||||||
|
const ctx = tempCtx;
|
||||||
|
core.drawThumbnail(floorId, void 0, {
|
||||||
|
all: true,
|
||||||
|
inFlyMap: true,
|
||||||
|
x: x - floor.width / 2,
|
||||||
|
y: y - floor.height / 2,
|
||||||
|
w: floor.width,
|
||||||
|
h: floor.height,
|
||||||
|
ctx,
|
||||||
|
damage: true
|
||||||
|
});
|
||||||
|
if (!core.hasVisitedFloor(floorId)) {
|
||||||
|
ctx.fillStyle = '#d0d6';
|
||||||
|
ctx.fillRect(
|
||||||
|
x - floor.width / 2,
|
||||||
|
y - floor.height / 2,
|
||||||
|
floor.width,
|
||||||
|
floor.height
|
||||||
|
);
|
||||||
|
ctx.fillStyle = '#000';
|
||||||
|
}
|
||||||
|
if (nowFloor.value === floorId) {
|
||||||
|
ctx.fillStyle = '#ff04';
|
||||||
|
ctx.fillRect(
|
||||||
|
x - floor.width / 2,
|
||||||
|
y - floor.height / 2,
|
||||||
|
floor.width,
|
||||||
|
floor.height
|
||||||
|
);
|
||||||
|
ctx.fillStyle = '#000';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当移动时检查是否应该绘制缩略图
|
||||||
|
*/
|
||||||
|
function checkMoveThumbnail() {
|
||||||
|
const border = noBorder.value ? 0.5 : 1;
|
||||||
|
const data = getMapDrawData(nowFloor.value, noBorder.value ? 0 : 5, border);
|
||||||
|
for (const [id, [x, y]] of Object.entries(data.locs) as [
|
||||||
|
FloorIds,
|
||||||
|
LocArr
|
||||||
|
][]) {
|
||||||
|
if (checkThumbnail(id, x, y)) drawThumbnail(id, x, y, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawRight() {
|
||||||
|
let w = thumb.width;
|
||||||
|
let h = thumb.height;
|
||||||
|
let x = 0;
|
||||||
|
let y = 0;
|
||||||
|
const ratio = floor.value.width / floor.value.height;
|
||||||
|
if (ratio > 1) {
|
||||||
|
h = w / ratio;
|
||||||
|
y = thumb.height / 2 - h / 2;
|
||||||
|
}
|
||||||
|
if (ratio < 1) {
|
||||||
|
w = h / ratio;
|
||||||
|
x = thumb.width / 2 - w / 2;
|
||||||
|
}
|
||||||
|
thumbCtx.fillStyle = '#000';
|
||||||
|
thumbCtx.fillRect(0, 0, thumb.width, thumb.height);
|
||||||
|
core.drawThumbnail(nowFloor.value, void 0, {
|
||||||
|
ctx: thumbCtx,
|
||||||
|
all: true,
|
||||||
|
damage: true,
|
||||||
|
inFlyMap: true,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
w,
|
||||||
|
h
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制所有内容
|
||||||
|
*/
|
||||||
|
function draw() {
|
||||||
|
drawedThumbnail = {};
|
||||||
|
drawMap();
|
||||||
|
drawRight();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fly() {
|
||||||
|
if (core.flyTo(nowFloor.value)) exit();
|
||||||
|
else tip('error', `无法飞往${floor.value.title}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastScale = scale;
|
||||||
|
const changeScale = debounce((s: number) => {
|
||||||
|
map.style.transform = '';
|
||||||
|
drawedThumbnail = {};
|
||||||
|
drawMap();
|
||||||
|
lastScale = s;
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
function resize(delta: number) {
|
||||||
|
ox *= delta;
|
||||||
|
oy *= delta;
|
||||||
|
scale = delta * scale;
|
||||||
|
changeScale(scale);
|
||||||
|
map.style.transform = `scale(${scale / lastScale})`;
|
||||||
|
thumbnailLoc = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastX = 0;
|
||||||
|
let lastY = 0;
|
||||||
|
|
||||||
|
let moved = false;
|
||||||
|
let startX = 0;
|
||||||
|
let startY = 0;
|
||||||
|
|
||||||
|
// -------------------- 点击事件
|
||||||
|
|
||||||
|
function drag(x: number, y: number) {
|
||||||
|
if (touchScale) return;
|
||||||
|
const dx = x - lastX;
|
||||||
|
const dy = y - lastY;
|
||||||
|
ox += dx;
|
||||||
|
oy += dy;
|
||||||
|
lastX = x;
|
||||||
|
lastY = y;
|
||||||
|
checkMoveThumbnail();
|
||||||
|
drawToTarget();
|
||||||
|
if (Math.abs(x - startX) > 10 || Math.abs(y - startY) > 10) moved = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function click(e: MouseEvent) {
|
||||||
|
if (moved) return;
|
||||||
|
|
||||||
|
const x = e.offsetX * devicePixelRatio;
|
||||||
|
const y = e.offsetY * devicePixelRatio;
|
||||||
|
for (const [id, [left, top, right, bottom]] of Object.entries(
|
||||||
|
thumbnailLoc
|
||||||
|
) as [FloorIds, Loc2][]) {
|
||||||
|
if (x >= left && x <= right && y >= top && y <= bottom) {
|
||||||
|
if (id === nowFloor.value) {
|
||||||
|
fly();
|
||||||
|
} else {
|
||||||
|
nowFloor.value = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeAreaByFloor(id: FloorIds) {
|
||||||
|
nowArea.value = Object.keys(area).find(v => area[v].includes(id))!;
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeFloorByDelta(delta: number) {
|
||||||
|
const now = core.floorIds.indexOf(nowFloor.value);
|
||||||
|
let to = now + delta;
|
||||||
|
if (to < 0) to = 0;
|
||||||
|
if (to >= core.floorIds.length) to = core.floorIds.length - 1;
|
||||||
|
const floor = core.status.maps[core.floorIds[to]];
|
||||||
|
if (floor.deleted) {
|
||||||
|
while (to !== now) {
|
||||||
|
to -= Math.sign(delta);
|
||||||
|
const floor = core.status.maps[core.floorIds[to]];
|
||||||
|
if (!floor.deleted) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nowFloor.value = core.floorIds[to];
|
||||||
|
changeAreaByFloor(nowFloor.value);
|
||||||
|
locateMap(nowFloor.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeFloorByDir(dir: Dir) {
|
||||||
|
const data = getMapData(nowFloor.value);
|
||||||
|
|
||||||
|
for (const [from, to] of Object.entries(data.link)) {
|
||||||
|
if (!from.startsWith(nowFloor.value)) continue;
|
||||||
|
const d = from.split(',')[3] as Dir;
|
||||||
|
|
||||||
|
if (d === dir) {
|
||||||
|
const target = to.split(',')[0] as FloorIds;
|
||||||
|
locateMap(target);
|
||||||
|
nowFloor.value = target;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 居中地图
|
||||||
|
* @param id 楼层id
|
||||||
|
*/
|
||||||
|
function locateMap(id: FloorIds) {
|
||||||
|
const data = getMapDrawData(
|
||||||
|
id,
|
||||||
|
noBorder.value ? 5 : 0,
|
||||||
|
noBorder.value ? 0.5 : 1
|
||||||
|
);
|
||||||
|
const [x, y] = data.locs[id]!;
|
||||||
|
ox = (-x + data.width / 2) * scale;
|
||||||
|
oy = (-y + data.height / 2) * scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- 键盘事件
|
||||||
|
|
||||||
|
function keyup(e: KeyboardEvent) {
|
||||||
|
const c = keycode(e.keyCode);
|
||||||
|
if (c === KeyCode.Enter || c === KeyCode.Space || c === KeyCode.KeyC) fly();
|
||||||
|
if (c === KeyCode.Escape || c === KeyCode.KeyX || c === KeyCode.KeyG) {
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
if (!tradition.value) {
|
||||||
|
if (c === KeyCode.LeftArrow) changeFloorByDir('left');
|
||||||
|
if (c === KeyCode.RightArrow) changeFloorByDir('right');
|
||||||
|
if (c === KeyCode.UpArrow) changeFloorByDir('up');
|
||||||
|
if (c === KeyCode.DownArrow) changeFloorByDir('down');
|
||||||
|
if (c === KeyCode.PageUp) changeFloorByDelta(1);
|
||||||
|
if (c === KeyCode.PageDown) changeFloorByDelta(-1);
|
||||||
|
} else {
|
||||||
|
if (c === KeyCode.UpArrow) changeFloorByDelta(1);
|
||||||
|
if (c === KeyCode.DownArrow) changeFloorByDelta(-1);
|
||||||
|
if (c === KeyCode.LeftArrow) changeFloorByDelta(-10);
|
||||||
|
if (c === KeyCode.RightArrow) changeFloorByDelta(10);
|
||||||
|
if (c === KeyCode.PageUp) changeFloorByDelta(10);
|
||||||
|
if (c === KeyCode.PageDown) changeFloorByDelta(-10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- 触摸事件
|
||||||
|
|
||||||
|
let touchScale = false;
|
||||||
|
let lastDis = 0;
|
||||||
|
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(async () => {
|
||||||
|
map = document.getElementById('fly-map') as HTMLCanvasElement;
|
||||||
|
mapCtx = map.getContext('2d')!;
|
||||||
|
thumb = document.getElementById('fly-thumbnail') as HTMLCanvasElement;
|
||||||
|
thumbCtx = thumb.getContext('2d')!;
|
||||||
|
|
||||||
|
const mapStyle = getComputedStyle(map);
|
||||||
|
const thumbStyle = getComputedStyle(thumb);
|
||||||
|
map.width = parseFloat(mapStyle.width) * devicePixelRatio;
|
||||||
|
map.height = parseFloat(mapStyle.height) * devicePixelRatio;
|
||||||
|
thumb.width = parseFloat(thumbStyle.width) * devicePixelRatio;
|
||||||
|
thumb.height = parseFloat(thumbStyle.width) * devicePixelRatio;
|
||||||
|
|
||||||
|
Array.from(document.getElementsByClassName('fly-settings')).forEach(v => {
|
||||||
|
v.addEventListener('click', e => (v as HTMLElement).blur());
|
||||||
|
});
|
||||||
|
|
||||||
|
draw();
|
||||||
|
|
||||||
|
useDrag(
|
||||||
|
map,
|
||||||
|
drag,
|
||||||
|
(x, y) => {
|
||||||
|
lastX = x;
|
||||||
|
lastY = y;
|
||||||
|
startX = x;
|
||||||
|
startY = y;
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
moved = false;
|
||||||
|
}, 50);
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
useWheel(map, (x, y) => {
|
||||||
|
const delta = -Math.sign(y) * 0.1 + 1;
|
||||||
|
resize(delta);
|
||||||
|
});
|
||||||
|
|
||||||
|
await sleep(50);
|
||||||
|
if (core.plugin.transition.value) await sleep(600);
|
||||||
|
|
||||||
|
document.addEventListener('keyup', keyup);
|
||||||
|
map.addEventListener('touchstart', touchdown);
|
||||||
|
map.addEventListener('touchend', touchup);
|
||||||
|
map.addEventListener('touchend', touchmove);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
cancelGlobalDrag(drag);
|
||||||
|
document.removeEventListener('keyup', keyup);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
#fly {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
font-size: 2.7vh;
|
||||||
|
font-family: 'normal';
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tools {
|
||||||
|
width: 100%;
|
||||||
|
font-family: 'normal';
|
||||||
|
font-size: 3.2vh;
|
||||||
|
height: 5vh;
|
||||||
|
position: fixed;
|
||||||
|
left: 5vw;
|
||||||
|
top: 5vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fly-main {
|
||||||
|
display: flex;
|
||||||
|
height: 80%;
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fly-left {
|
||||||
|
width: 50vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fly-area {
|
||||||
|
height: 100%;
|
||||||
|
width: 15vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#area-list {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divider-left {
|
||||||
|
margin: 0;
|
||||||
|
height: 100%;
|
||||||
|
border-color: #ddd4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fly-map-div,
|
||||||
|
#fly-map {
|
||||||
|
width: 35vw;
|
||||||
|
height: 72vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divider-right {
|
||||||
|
height: 100%;
|
||||||
|
border-color: #ddd4;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fly-right {
|
||||||
|
width: 40vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fly-tools {
|
||||||
|
margin: 0;
|
||||||
|
width: 80%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fly-thumbnail {
|
||||||
|
width: 35vw;
|
||||||
|
height: 35vw;
|
||||||
|
border: 0.1vw solid #ddd4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fly-settings {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 5vh;
|
||||||
|
left: 10vw;
|
||||||
|
width: 80vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-right: 5vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fly-settings[aria-checked='false'] {
|
||||||
|
background-color: #ddd4;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
#fly {
|
||||||
|
padding: 5%;
|
||||||
|
font-size: 3.8vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fly-main {
|
||||||
|
flex-direction: column;
|
||||||
|
height: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fly-map-div,
|
||||||
|
#fly-map {
|
||||||
|
width: 60vw;
|
||||||
|
height: 30vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fly-area {
|
||||||
|
width: 30vw;
|
||||||
|
height: 30vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fly-left {
|
||||||
|
width: 90vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divider-right {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fly-right {
|
||||||
|
width: 90vw;
|
||||||
|
height: 60vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fly-thumbnail {
|
||||||
|
width: 80vw;
|
||||||
|
height: 80vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tools {
|
||||||
|
top: 2vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fly-settings {
|
||||||
|
bottom: 2%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -3,7 +3,7 @@
|
|||||||
><template #left
|
><template #left
|
||||||
><div id="setting-list">
|
><div id="setting-list">
|
||||||
<span
|
<span
|
||||||
class="selectable setting-item"
|
class="selectable"
|
||||||
:selected="selected === 'transition'"
|
:selected="selected === 'transition'"
|
||||||
@click="click('transition')"
|
@click="click('transition')"
|
||||||
>界面动画: {{
|
>界面动画: {{
|
||||||
@ -11,7 +11,7 @@
|
|||||||
}}</span
|
}}</span
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="selectable setting-item"
|
class="selectable"
|
||||||
:selected="selected === 'itemDetail'"
|
:selected="selected === 'itemDetail'"
|
||||||
@click="click('itemDetail')"
|
@click="click('itemDetail')"
|
||||||
>宝石血瓶显伤: {{
|
>宝石血瓶显伤: {{
|
||||||
@ -19,7 +19,7 @@
|
|||||||
}}</span
|
}}</span
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="selectable setting-item"
|
class="selectable"
|
||||||
:selected="selected === 'autoSkill'"
|
:selected="selected === 'autoSkill'"
|
||||||
@click="click('autoSkill')"
|
@click="click('autoSkill')"
|
||||||
>自动切换技能: {{
|
>自动切换技能: {{
|
||||||
@ -27,7 +27,7 @@
|
|||||||
}}</span
|
}}</span
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="selectable setting-item"
|
class="selectable"
|
||||||
:selected="selected === 'autoScale'"
|
:selected="selected === 'autoScale'"
|
||||||
@click="click('autoScale')"
|
@click="click('autoScale')"
|
||||||
>自动放缩: {{
|
>自动放缩: {{
|
||||||
|
@ -67,11 +67,6 @@ function exit() {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.skill-item {
|
|
||||||
width: 100%;
|
|
||||||
padding: 1% 3% 1% 3%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skill-item[selectable='false'] {
|
.skill-item[selectable='false'] {
|
||||||
color: gray;
|
color: gray;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user