怪物标记功能

This commit is contained in:
unanmed 2022-12-29 00:26:12 +08:00
parent 22350c4dda
commit 8418bccdf6
31 changed files with 662 additions and 316 deletions

View File

@ -388,9 +388,6 @@ actions.prototype._sys_keyDown_lockControl = function (keyCode) {
case 'viewMaps':
this._keyDownViewMaps(keyCode);
break;
case 'toolbox':
this._keyDownToolbox(keyCode);
break;
case 'save':
case 'load':
case 'replayLoad':

View File

@ -1,6 +1,6 @@
var enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80 =
{
"greenSlime": {"name":"绿头怪","hp":100,"atk":11,"def":3,"money":0,"exp":1,"point":0,"special":[]},
"greenSlime": {"name":"绿头怪","hp":100,"atk":11,"def":3,"money":0,"exp":1,"point":0,"special":[],"description":"一种极其低级的怪物,低级到普通人用手都可以打死。"},
"redSlime": {"name":"红头怪","hp":120,"atk":16,"def":6,"money":0,"exp":2,"point":0,"special":[],"value":10},
"blackSlime": {"name":"青头怪","hp":170,"atk":20,"def":8,"money":0,"exp":3,"point":0,"special":[]},
"slimelord": {"name":"粘液王","hp":200,"atk":58,"def":24,"money":0,"exp":8,"point":0,"special":[]},

View File

@ -21,10 +21,10 @@ main.floors.MT0=
[141,141,141,141, 0, 0, 0,141, 0, 0, 0,141, 33, 33,20040],
[141, 34, 34,141, 0,141, 0, 0, 0,141, 0,494,482,482,20040],
[141, 33, 33,492, 0,141, 0, 0, 0,141, 0,141, 33, 33,20040],
[141, 34, 34,141, 0,141, 0, 0, 0,141, 0,141,141,141,20040],
[141,141,141,141, 0,129,558, 46,322,129, 0,141, 33, 33,20040],
[141, 34, 34,141, 0,141, 0,559, 0,141, 0,141,141,141,20040],
[141,141,141,141, 0,129,558, 46,560, 0, 0,141, 33, 33,20040],
[141, 33, 33,141, 0,141,367, 0,129,141, 0,494,482,482,20040],
[141, 33, 33,492, 0,141,129, 0,129,141, 0,141, 33, 33,20040],
[141, 33, 33,492, 0,141, 0, 0,129,141, 0,141, 33, 33,20040],
[141,141,141,141,141,141,141,141,141,141,141,141,141,141,20040]
],
"firstArrive": [
@ -46,22 +46,11 @@ main.floors.MT0=
"\t[原始人]\b[up,hero]出去找些柴火"
],
"8,13": [
"对状态栏的说明",
"PC端\n从上到下依次是楼层名、境界名、生命右方为每回合生命回复、攻击右方为额外攻击、防御、智慧、金币、距离升级剩余经验、三色钥匙",
"手机端:\n最左边一列为生命、生命右方为每回合生命回复、攻击右方为额外攻击\n第二列为智慧、金币、距离升级剩余经验\n第三列为三色钥匙、楼层名、等级名"
],
"6,13": [
"你可以在初始赠送的系统设置里面修改一些设置\n带地图的楼传开启时如果是手机玩家请尽量使用2D绘图模式而不是3D因为3D绘图比较耗能该功能可以有效解决找不到路的问题\n你也可以用定点查看代替怪物手册对性能消耗较低更加流畅",
"打开小地图时小地图会在右上角显示可随意开启或关闭打开时点击小地图可以进入大地图模式大地图模式下可以按W或点击相应位置开启区域地图模式比较耗性能因为是绘制整个区域的地图而不是6格以内的地图\n楼传界面中可以按PgUp和PgDn来上楼或下楼上下左右移动地图\n在地图界面中用紫色标记的地图为目前可以到达即去到了临近的地图却没有到达的地图可以防止找不到路",
"注意,小地图和平面楼传只能浏览和传送当前区域的地图,非当前区域可能无法浏览和传送",
"你面前的三个道具比较重要,请充分利用"
"本塔有很多新的功能,所有的说明都详细地写在了前方的百科全书里面,里面包含所有的功能说明,不阅读可能会影响正常的游戏体验,请仔细阅读。"
],
"8,12": [
"该塔计分方式:生命+5000*黄钥匙+15000*蓝钥匙"
],
"9,11": [
"对怪物手册的说明:\n1.该塔可以显示未破防时的临界显伤,如果减伤项为黄色且有->标识,说明改临界是在当前未破防的情况下算得的,比如 临界 10 减伤 \r[#ffd700] ->1000\r[] 说明再加10点攻击破防破防后伤害为1000"
],
"5,11": [
"原声音乐可以在网易云音乐搜索:魔塔 人类:开天辟地 bgm部分音乐因为版权问题可能无法播放或者不在歌单内"
]

View File

@ -515,7 +515,9 @@ var icons_4665ee12_3a1f_44a4_bea3_0fccba634dc1 =
"I489": 92,
"I490": 93,
"I491": 94,
"I558": 95
"I558": 95,
"I559": 96,
"I560": 97
},
"autotile": {
"autotile": 0,

View File

@ -1242,5 +1242,18 @@ var items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a =
"canUseItemEffect": "true",
"text": "可以查看游戏内你已经听过的bgm歌曲名格式歌手——歌曲名",
"useItemEffect": "core.openBgms();"
},
"I559": {
"cls": "constants",
"name": "系统设置",
"canUseItemEffect": "true",
"text": "内含所有系统设置项",
"useItemEffect": "if (!main.replayChecking) {\n\tcore.plugin.settingsOpened.value = true;\n}"
},
"I560": {
"cls": "constants",
"name": "百科全书",
"canUseItemEffect": "true",
"text": "一个包含游戏中所有功能详细说明的百科全书,可以查看游戏中所有的功能"
}
}

View File

@ -483,6 +483,8 @@ var maps_90f36752_8815_4be8_b32b_d7fad1d0542e =
"556": {"cls":"enemys","id":"E556"},
"557": {"cls":"enemys","id":"E557"},
"558": {"cls":"items","id":"I558"},
"559": {"cls":"items","id":"I559"},
"560": {"cls":"items","id":"I560"},
"20037": {"cls":"tileset","id":"X20037","cannotOut":["up","left"],"cannotIn":["up","left"]},
"20038": {"cls":"tileset","id":"X20038","cannotOut":["up"],"cannotIn":["up"]},
"20039": {"cls":"tileset","id":"X20039","cannotOut":["up","right"],"cannotIn":["up","right"]},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -3370,13 +3370,6 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = {
* 如有bug在大群或造塔群@古祠
*/
// 谁tm在即捡即用效果里面调用带有含刷新状态栏的函数
var origin = core.control.updateStatusBar;
core.updateStatusBar = core.control.updateStatusBar = function () {
if (core.getFlag('__statistics__')) return;
else return origin.apply(core.control, arguments);
};
core.bigmap.threshold = 256;
core.control.updateDamage = function (floorId, ctx) {
@ -8008,268 +8001,7 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = {
}
};
},
weatherSuperimpose: function () {
// 天气叠加功能
////// 更改天气效果 //////
control.prototype.setWeather = function (type, level) {
// 非雨雪
if (type == null) {
Object.keys(core.control.weathers).forEach(one => {
core.deleteCanvas('weather' + one);
});
core.animateFrame.weather.type = [];
core.animateFrame.weather.nodes = {};
core.animateFrame.weather.level = {};
core.animateFrame.weather.time = {};
return;
}
if (!core.animateFrame.weather.level || level == null)
core.animateFrame.weather.level = {};
if (!core.animateFrame.weather.type)
core.animateFrame.weather.type = [];
level = core.clamp(parseInt(level) || 5, 1, 10);
// 当前天气:则忽略
if (
core.animateFrame.weather.type.includes(type) &&
level == core.animateFrame.weather.level[type]
)
return;
if (core.animateFrame.weather.nodes[type]) return;
// 计算当前的宽高
core.createCanvas(
'weather' + type,
0,
0,
core.__PIXELS__,
core.__PIXELS__,
80
);
core.animateFrame.weather.type.push(type);
core.animateFrame.weather.level[type] = level;
this._setWeather_createNodes(type, level);
};
control.prototype._setWeather_createNodes = function (type, level) {
var number =
level *
parseInt(
(20 * core.bigmap.width * core.bigmap.height) /
(core.__SIZE__ * core.__SIZE__)
);
if (!core.animateFrame.weather.nodes[type])
core.animateFrame.weather.nodes[type] = [];
switch (type) {
case 'rain':
for (var a = 0; a < number; a++) {
core.animateFrame.weather.nodes.rain.push({
x: Math.random() * core.bigmap.width * 32,
y: Math.random() * core.bigmap.height * 32,
l: Math.random() * 2.5,
xs: -4 + Math.random() * 4 + 2,
ys: Math.random() * 10 + 10
});
}
break;
case 'snow':
for (var a = 0; a < number; a++) {
core.animateFrame.weather.nodes.snow.push({
x: Math.random() * core.bigmap.width * 32,
y: Math.random() * core.bigmap.height * 32,
r: Math.random() * 5 + 1,
d: Math.random() * Math.min(level, 200)
});
}
break;
case 'fog':
if (core.animateFrame.weather.fog) {
core.animateFrame.weather.nodes[type] = [
{
level: number,
x: 0,
y: -core.__PIXELS__ / 2,
dx: -Math.random() * 1.5,
dy: Math.random(),
delta: 0.001
}
];
}
break;
case 'cloud':
if (core.animateFrame.weather.cloud) {
core.animateFrame.weather.nodes[type] = [
{
level: number,
x: 0,
y: -core.__PIXELS__ / 2,
dx: -Math.random() * 1.5,
dy: Math.random(),
delta: 0.001
}
];
}
break;
case 'sun':
if (core.animateFrame.weather.sun) {
// 直接绘制
core.clearMap('weather' + type);
core.setAlpha('weather' + type, level / 10);
core.drawImage(
'weather' + type,
core.animateFrame.weather.sun,
0,
0,
core.animateFrame.weather.sun.width,
core.animateFrame.weather.sun.height,
0,
0,
core.__PIXELS__,
core.__PIXELS__
);
core.setAlpha('weather' + type, 1);
}
break;
}
};
core.registerAnimationFrame('weather', true, timestamp => {
var weather = core.animateFrame.weather;
if (!weather.type) return;
weather.type.forEach(one => {
if (
timestamp - weather.time[one] <= 30 ||
!core.dymCanvas['weather' + one]
)
return;
core.control['_animationFrame_weather_' + one]();
weather.time[one] = timestamp;
});
});
// 雨
control.prototype._animationFrame_weather_rain = function () {
var ctx = core.dymCanvas.weatherrain,
ox = core.bigmap.offsetX,
oy = core.bigmap.offsetY;
core.clearMap('weatherrain');
ctx.strokeStyle = 'rgba(174,194,224,0.8)';
ctx.lineWidth = 1;
ctx.lineCap = 'round';
core.animateFrame.weather.nodes.rain.forEach(p => {
ctx.beginPath();
ctx.moveTo(p.x - ox, p.y - oy);
ctx.lineTo(p.x + p.l * p.xs - ox, p.y + p.l * p.ys - oy);
ctx.stroke();
p.x += p.xs;
p.y += p.ys;
if (
p.x > core.bigmap.width * 32 ||
p.y > core.bigmap.height * 32
) {
p.x = Math.random() * core.bigmap.width * 32;
p.y = -10;
}
});
ctx.fill();
};
// 雪
control.prototype._animationFrame_weather_snow = function () {
var ctx = core.dymCanvas.weathersnow,
ox = core.bigmap.offsetX,
oy = core.bigmap.offsetY;
core.clearMap('weathersnow');
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
ctx.beginPath();
if (!core.animateFrame.weather.data)
core.animateFrame.weather.data = {};
core.animateFrame.weather.data.snow =
core.animateFrame.weather.data.snow || 0;
core.animateFrame.weather.data.snow += 0.01;
var angle = core.animateFrame.weather.data.snow;
core.animateFrame.weather.nodes.snow.forEach(p => {
ctx.moveTo(p.x - ox, p.y - oy);
ctx.arc(p.x - ox, p.y - oy, p.r, 0, Math.PI * 2, true);
// update
p.x += Math.sin(angle) * core.animateFrame.weather.level.snow;
p.y += Math.cos(angle + p.d) + 1 + p.r / 2;
if (
p.x > core.bigmap.width * 32 + 5 ||
p.x < -5 ||
p.y > core.bigmap.height * 32
) {
if (Math.random() > 1 / 3) {
p.x = Math.random() * core.bigmap.width * 32;
p.y = -10;
} else {
if (Math.sin(angle) > 0) p.x = -5;
else p.x = core.bigmap.width * 32 + 5;
p.y = Math.random() * core.bigmap.height * 32;
}
}
});
ctx.fill();
};
// 图片天气
control.prototype.__animateFrame_weather_image = function (
image,
type
) {
if (!image) return;
var node = core.animateFrame.weather.nodes[type][0];
core.setAlpha('weather' + type, node.level / 500);
var wind = 1.5;
var width = image.width,
height = image.height;
node.x += node.dx * wind;
node.y += (2 * node.dy - 1) * wind;
if (node.x + 3 * width <= core.__PIXELS__) {
node.x += 4 * width;
while (node.x > 0) node.x -= width;
}
node.dy += node.delta;
if (node.dy >= 1) {
node.delta = -0.001;
} else if (node.dy <= 0) {
node.delta = 0.001;
}
if (node.y + 3 * height <= core.__PIXELS__) {
node.y += 4 * height;
while (node.y > 0) node.y -= height;
} else if (node.y >= 0) {
node.y -= height;
}
for (var i = 0; i < 3; ++i) {
for (var j = 0; j < 3; ++j) {
if (
node.x + (i + 1) * width <= 0 ||
node.x + i * width >= core.__PIXELS__ ||
node.y + (j + 1) * height <= 0 ||
node.y + j * height >= core.__PIXELS__
)
continue;
core.drawImage(
'weather' + type,
image,
node.x + i * width,
node.y + j * height
);
}
}
core.setAlpha('weather' + type, 1);
};
// 雾
control.prototype._animationFrame_weather_fog = function () {
core.clearMap('weatherfog');
this.__animateFrame_weather_image(
core.animateFrame.weather.fog,
'fog'
);
};
// 云
control.prototype._animationFrame_weather_cloud = function () {
core.clearMap('weathercloud');
this.__animateFrame_weather_image(
core.animateFrame.weather.cloud,
'cloud'
);
};
},
weatherSuperimpose: function () {},
popupDamage: function () {
// 伤害弹出
// 复写阻激夹域检测
@ -9266,6 +8998,13 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = {
uiChange: function () {
if (main.replayChecking) return;
function updateVueStatusBar() {
if (main.replayChecking) return;
core.plugin.statusBarStatus.value =
!core.plugin.statusBarStatus.value;
core.checkMarkedEnemy();
}
ui.prototype.drawBook = function () {
return (core.plugin.bookOpened.value = true);
};
@ -9286,8 +9025,7 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = {
core.clearRouteFolding();
core.control.noAutoEvents = true;
// 更新vue状态栏
core.plugin.statusBarStatus.value =
!core.plugin.statusBarStatus.value;
updateVueStatusBar();
};
control.prototype.showStatusBar = function () {

View File

@ -1,6 +1,7 @@
<template>
<div id="non-ui">
<StatusBar v-if="showStatusBar"></StatusBar>
<MarkedEnemy></MarkedEnemy>
</div>
</template>
@ -8,6 +9,7 @@
import { ref } from 'vue';
import StatusBar from './ui/statusBar.vue';
import { showStatusBar } from './plugin/uiController';
import MarkedEnemy from './ui/markedEnemy.vue';
</script>
<style lang="less" scoped>

View File

@ -38,6 +38,7 @@ import { onMounted, onUnmounted, onUpdated, ref, watch } from 'vue';
import { DragOutlined } from '@ant-design/icons-vue';
import { isMobile, useDrag, cancelGlobalDrag } from '../plugin/use';
import { has } from '../plugin/utils';
import { sleep } from 'mutate-animate';
const props = defineProps<{
resizable?: boolean;
@ -143,15 +144,16 @@ function resize() {
if (has(props.width)) width.value = props.width;
if (has(props.height)) height.value = props.height;
main.style.left = `${left}px`;
main.style.top = `${top}px`;
main.style.width = `${width}px`;
main.style.height = `${height}px`;
main.style.left = `${left.value}px`;
main.style.top = `${top.value}px`;
main.style.width = `${width.value}px`;
main.style.height = `${height.value}px`;
}
onUpdated(resize);
onMounted(() => {
onMounted(async () => {
await sleep(50);
resize();
useDrag(

View File

@ -14,7 +14,7 @@ import { has } from '../plugin/utils';
const id = (Math.random() * 1e8).toFixed(0);
const props = defineProps<{
id: AllIds | 'hero';
id: AllIds | 'hero' | 'none';
noborder?: boolean;
width?: number;
height?: number;
@ -47,6 +47,8 @@ function draw() {
c.height = scale * h;
ctx.scale(scale, scale);
if (props.id === 'none') return;
if (props.id === 'hero') {
const img = core.material.images.hero;
ctx.drawImage(img, 0, 0, img.width / 4, img.height / 4, 0, 0, w, h);
@ -56,7 +58,7 @@ function draw() {
drawFn = () => {
core.clearMap(ctx);
const frame = core.status.globalAnimateStatus % frames;
core.drawIcon(ctx, props.id, 0, 0, w, h, frame);
core.drawIcon(ctx, props.id as AllIds, 0, 0, w, h, frame);
};
drawFn();

40
src/data/desc.json Normal file
View File

@ -0,0 +1,40 @@
{
"statusBar": {
"text": "状态栏",
"desc": [
"在本塔中,状态栏与游戏画面是分开的。你可以自由拖动状态栏,也可以修改其大小。",
"具体方法如下:点击一下状态栏之后,左上角的拖拽图标会放大,此时你可以按住它拖动状态栏。",
"你可以直接将鼠标放到状态栏的边框上,然后直接拖动以改变状态栏的大小。手机端可以先点击一下状态栏使边框",
"变宽,然后拖动。电脑端点击状态栏也可以使边框变宽。",
"<br>",
"<br>",
"状态栏可以纵向滚动,如果你发现状态栏显示不全,可以尝试拉大状态栏,或者纵向拖动状态栏,就像网页上下拖动一样。",
"电脑端还可以使用滚轮上下滚动。",
"<br>",
"<br>",
"如果你觉得状态栏有些碍事,你完全可以将其缩小,或者把它放到不碍事的地方。",
"<br>",
"<br>",
"状态栏上面可能会有按钮(在开启技能树后会出现),你可以直接点击。"
]
},
"markEnemy": {
"text": "标记怪物",
"desc": [
"标记怪物可以使你能够更加方便地了解一个怪物的情况。你可以在怪物手册的怪物更多信息栏进行标记。",
"当一个怪物被标记后,怪物会有以下行为:",
"<br>",
"1. 当勇士恰好能打败怪物时,会进行提示",
"<br>",
"2. 当怪物的伤害恰好低于勇士生命值的2/3或1/3时会进行提示",
"<br>",
"3. 当勇士恰好踩到怪物的临界时,会进行提示",
"<br>",
"4. 被标记的怪物会出现类似于状态栏的盒子,可以随意拖动和改变大小。你也可以选择关闭这个盒子,",
"被关闭后可以通过重新标记来打开。这个盒子会显示标记的怪物的临界与伤害信息等,依然可以纵向滚动。",
"<br>",
"<br>",
"这个功能可以用于标记boss或者较强的挡路怪当这些怪能够攻击时你可以直接收到信息不需要再时刻费心注意怪物的伤害。"
]
}
}

6
src/data/settings.json Normal file
View File

@ -0,0 +1,6 @@
{
"transition": {
"text": "界面动画",
"desc": "是否展示当一个ui界面如怪物手册等的打开与关闭时的动画。当此项开启时所有界面被打开或关闭时都会展示动画否则会直接展示出来"
}
}

View File

@ -5,10 +5,19 @@ import use from './plugin/use';
import animate from './plugin/animateController';
import utils from './plugin/utils';
import status from './plugin/ui/statusBar';
import mark from './plugin/mark';
window.addEventListener('load', () => {
// 每个引入的插件都要在这里执行,否则不会被转发
const toForward: any[] = [pop(), ui(), use(), animate(), utils(), status()];
const toForward: any[] = [
pop(),
ui(),
use(),
animate(),
utils(),
status(),
mark()
];
// 初始化所有插件并转发到core上
(async function () {

77
src/panel/enemyTarget.vue Normal file
View File

@ -0,0 +1,77 @@
<template>
<div id="enemy-target">
<div id="enemy-desc">
<span>怪物描述</span>
<Scroll id="enemy-desc-scroll">
<span>{{ enemy.description }}</span>
</Scroll>
</div>
<a-divider dashed style="border-color: #ddd4"></a-divider>
<div>
<div id="mark-target">
<span
id="mark-info"
:style="{ color: marked ? 'lightgreen' : 'lightcoral' }"
>{{ marked ? '已标记该怪物' : '未标记该怪物' }}</span
>
<span class="button-text" @click="mark">{{
marked ? '取消标记该怪物' : '标记该怪物为目标'
}}</span>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import Scroll from '../components/scroll.vue';
import { hasMarkedEnemy, markEnemy, unmarkEnemy } from '../plugin/mark';
const enemy = core.plugin.bookDetailEnemy;
const marked = ref(hasMarkedEnemy(enemy.id));
function mark() {
if (marked.value) unmarkEnemy(enemy.id);
if (!marked.value) markEnemy(enemy.id);
marked.value = !marked.value;
}
</script>
<style lang="less" scoped>
#enemy-target {
width: 100%;
font-size: 2.5vh;
}
#enemy-desc {
width: 100%;
height: 30vh;
display: flex;
flex-direction: column;
align-items: center;
}
#enemy-desc-scroll {
height: 100%;
width: 100%;
}
#mark-target {
margin-top: 10%;
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-around;
font-size: 3.3vh;
}
#mark-info {
transition: color 0.2s linear;
}
@media screen and (max-width: 600px) {
#enemy-target {
font-size: 3vw;
}
}
</style>

127
src/plugin/mark.ts Normal file
View File

@ -0,0 +1,127 @@
import { reactive, ref } from 'vue';
import { message } from 'ant-design-vue';
const markedEnemy = reactive<EnemyIds[]>([]);
interface MarkInfo {
nextCritical: number;
}
const markInfo: Partial<Record<EnemyIds, MarkInfo>> = {};
const criticalReached: Partial<Record<EnemyIds, Record<number, boolean>>> = {};
const enemyDamageInfo: Partial<Record<EnemyIds, Record<number, boolean>>> = {};
/**
* 2/31/3
* @param id id
*/
export function markEnemy(id: EnemyIds) {
if (hasMarkedEnemy(id)) return;
markedEnemy.push(id);
markInfo[id] = {
nextCritical:
core.nextCriticals(id, 1)[0]?.[0] ?? 0 + core.status.hero.atk
};
criticalReached[id] = { 0: true };
enemyDamageInfo[id] = { 1: false, 2: false, 3: false };
getMarkInfo(id);
checkMarkedEnemy();
}
/**
*
*/
export function hasMarkedEnemy(id: EnemyIds) {
return markedEnemy.includes(id);
}
/**
*
*/
export function unmarkEnemy(id: EnemyIds) {
const index = markedEnemy.indexOf(id);
if (index === -1) return;
markedEnemy.splice(index, 1);
checkMarkedEnemy();
}
/**
*
*/
export function getMarkedEnemy() {
return markedEnemy;
}
/**
*
* @param id id
*/
export function getMarkInfo(id: EnemyIds) {
const reached = criticalReached[id]!;
const info = markInfo[id]!;
if (core.status.hero.atk >= info.nextCritical) {
if (!reached[info.nextCritical]) {
message.success({
content: `踩到了${core.material.enemys[id].name}的临界!`,
class: 'antdv-message'
});
}
reached[info.nextCritical] = true;
const n = core.nextCriticals(id, 1)[0]?.[0];
const next = (n ?? 0) + core.status.hero.atk;
info.nextCritical = next;
}
}
/**
*
*/
export function checkMarkedEnemy() {
checkMarkedStatus.value = !checkMarkedStatus.value;
const toDelete: EnemyIds[] = [];
const hp = core.status.hero.hp;
getMarkedEnemy().forEach(v => {
getMarkInfo(v);
const damage = core.getDamageInfo(v)?.damage ?? -1;
if (damage === -1) return;
const info = enemyDamageInfo[v]!;
const name = core.material.enemys[v].name;
if (damage < hp / 3) {
if (!info[3]) {
message.success({
content: `${name}的伤害已降至勇士生命值的1/3`,
class: 'antdv-message'
});
}
info[1] = true;
info[2] = true;
info[3] = true;
} else if (damage < (hp / 3) * 2) {
if (!info[2]) {
message.success({
content: `${name}的伤害已降至勇士生命值的2/3`,
class: 'antdv-message'
});
}
info[1] = true;
info[2] = true;
info[3] = false;
} else if (damage < hp) {
if (!info[1]) {
message.success({
content: `${name}的伤害已降至勇士生命值的2/3`,
class: 'antdv-message'
});
}
info[1] = true;
info[2] = false;
info[3] = false;
}
});
}
export const checkMarkedStatus = ref(false);
export default function init() {
return { checkMarkedEnemy, checkStatus: checkMarkedStatus };
}

15
src/plugin/settings.ts Normal file
View File

@ -0,0 +1,15 @@
import { ref, watch } from 'vue';
/**
* ui时是否展示动画
*/
export const transition = ref(true);
watch(transition, n => {
core.plugin.transition.value = n;
core.setLocalStorage('transition', n);
});
window.addEventListener('load', () => {
transition.value = core.getLocalStorage('transition');
});

View File

@ -3,12 +3,15 @@ import { Component, markRaw, ref, Ref, watch } from 'vue';
import Book from '../ui/book.vue';
import Toolbox from '../ui/toolbox.vue';
import Equipbox from '../ui/equipbox.vue';
import StatusBar from '../ui/statusBar.vue';
import Settings from '../ui/settings.vue';
import Desc from '../ui/desc.vue';
export const bookOpened = ref(false);
export const toolOpened = ref(false);
export const equipOpened = ref(false);
export const showStatusBar = ref(false);
export const settingsOpened = ref(false);
export const descOpened = ref(false);
export const transition = ref(true);
export const noClosePanel = ref(false);
@ -19,7 +22,9 @@ let app: HTMLDivElement;
const UI_LIST: [Ref<boolean>, Component][] = [
[bookOpened, Book],
[toolOpened, Toolbox],
[equipOpened, Equipbox]
[equipOpened, Equipbox],
[settingsOpened, Settings],
[descOpened, Desc]
];
/** ui栈 */
@ -48,7 +53,8 @@ export default function init() {
bookOpened,
toolOpened,
equipOpened,
showStatusBar
showStatusBar,
settingsOpened
};
}
@ -57,7 +63,7 @@ async function showApp() {
if (transition.value) {
app.style.transition = 'all 0.6s linear';
} else {
app.style.transition = '';
app.style.transition = 'none';
}
app.style.display = 'flex';
await sleep(50);

View File

@ -100,10 +100,7 @@ export function useDrag(
export function cancelGlobalDrag(fn: DragFn): void {
const fns = dragFnMap.get(fn);
dragFnMap.delete(fn);
if (!fns)
throw new ReferenceError(
'The drag function to be canceled does not exist.'
);
if (!fns) return;
document.removeEventListener('mousemove', fns[0]);
document.removeEventListener('touchmove', fns[1]);
document.removeEventListener('mouseup', fns[0]);

View File

@ -99,6 +99,7 @@ export function type(
if (typeof toShow !== 'string') {
throw new TypeError('Error str type in typing!');
}
if (toShow.startsWith('!!html')) return ref(toShow);
if (avr) time *= toShow.length;
const ani = new Animation();
const content = ref('');

2
src/source/cls.d.ts vendored
View File

@ -482,6 +482,8 @@ interface IdToCls {
E556: 'enemys';
E557: 'enemys';
I558: 'items';
I559: 'items';
I560: 'items';
X20037: 'tileset';
X20038: 'tileset';
X20039: 'tileset';

View File

@ -179,4 +179,6 @@ interface ItemDeclaration {
I490: 'items';
I491: 'items';
I558: 'constants';
I559: 'constants';
I560: 'constants';
}

View File

@ -482,6 +482,8 @@ interface IdToNumber {
E556: 556;
E557: 557;
I558: 558;
I559: 559;
I560: 560;
X20037: 20037;
X20038: 20038;
X20039: 20039;
@ -998,6 +1000,8 @@ interface NumberToId {
556: 'E556';
557: 'E557';
558: 'I558';
559: 'I559';
560: 'I560';
20037: 'X20037';
20038: 'X20038';
20039: 'X20039';

View File

@ -46,6 +46,11 @@ type Enemy<I extends EnemyIds = EnemyIds> = {
*/
name: string;
/**
*
*/
description: string;
/**
* IDnullID来替换该怪物原本的ID
*

19
src/types/plugin.d.ts vendored
View File

@ -30,7 +30,7 @@ interface PluginDeclaration extends PluginUtils {
bookDetailPos: number;
/** 怪物手册详细信息展示的怪物 */
bookDetailEnemy: Enemy & DetailedEnemy;
bookDetailEnemy: DetailedEnemy;
/** ui是否使用渐变 */
readonly transition: Ref<boolean>;
@ -44,18 +44,24 @@ interface PluginDeclaration extends PluginUtils {
/** 装备栏是否打开 */
readonly equipOpened: Ref<boolean>;
/** 状态栏信息,取反后刷新状态栏 */
readonly statusBarStatus: Ref<boolean>;
/** 是否显示状态栏 */
readonly showStatusBar: Ref<boolean>;
/** 设置界面是否打开 */
readonly settingsOpened: Ref<boolean>;
/** ui栈 */
readonly uiStack: Ref<Component[]>;
/** 是否是移动设备 */
readonly isMobile: boolean;
/** 状态栏信息,取反后刷新状态栏 */
readonly statusBarStatus: Ref<boolean>;
/** 检查标记的怪物,取反后更新显示信息 */
readonly checkMarkedStatus: Ref<boolean>;
/**
*
* @param ele
@ -105,6 +111,11 @@ interface PluginDeclaration extends PluginUtils {
* @param fn
*/
removeAnimate(fn: (time: number) => void);
/**
*
*/
checkMarkedEnemy();
}
interface PluginUtils {

View File

@ -127,6 +127,8 @@ async function show() {
async function exit() {
noClosePanel.value = true;
core.plugin.bookOpened.value = false;
if (core.plugin.transition.value) await sleep(650);
else await sleep(100);
if (core.events.recoverEvents(core.status.event.interval)) {
return;
} else if (has(core.status.event.ui)) {
@ -134,7 +136,6 @@ async function exit() {
// @ts-ignore
core.ui._drawViewMaps(core.status.event.ui);
} else core.ui.closePanel();
await sleep(650);
}
function checkScroll() {
@ -202,7 +203,8 @@ function keydown(e: KeyboardEvent) {
onMounted(async () => {
const div = document.getElementById('book') as HTMLDivElement;
div.style.opacity = '1';
await sleep(600);
if (core.plugin.transition.value) await sleep(600);
else await sleep(100);
document.addEventListener('keyup', keyup);
document.addEventListener('keydown', keydown);
});

View File

@ -11,6 +11,7 @@
<Transition name="detail">
<EnemySpecial v-if="panel === 'special'"></EnemySpecial>
<EnemyCritical v-else-if="panel === 'critical'"></EnemyCritical>
<EnemyTarget v-else-if="panel === 'target'"></EnemyTarget>
</Transition>
<div id="detail-more">
<Transition name="detail">
@ -19,7 +20,12 @@
class="detial-more"
v-if="panel === 'special'"
>
<span></span>
<span
id="enemy-target"
class="button-text more"
@click="changePanel($event, 'target')"
><LeftOutlined /> 怪物更多信息</span
>
<span
id="critical-more"
class="button-text more"
@ -39,6 +45,19 @@
><left-outlined /> 怪物特殊属性</span
>
</div>
<div
id="special-more"
class="detial-more"
v-else-if="panel === 'target'"
>
<span></span>
<span
id="enemy-pos"
class="button-text more"
@click="changePanel($event, 'special')"
>怪物特殊属性 <RightOutlined
/></span>
</div>
</Transition>
</div>
</div>
@ -54,6 +73,7 @@ import EnemyCritical from '../panel/enemyCritical.vue';
import { KeyCode } from '../plugin/keyCodes';
import { keycode } from '../plugin/utils';
import { sleep } from 'mutate-animate';
import EnemyTarget from '../panel/enemyTarget.vue';
const enemy = core.plugin.bookDetailEnemy;
const top = ref(core.plugin.bookDetailPos);
@ -104,7 +124,7 @@ onMounted(async () => {
if (y > (parseFloat(style.height) * 4) / 5) moved = true;
},
() => {
if (moved === false && panel.value !== 'critical') {
if (moved === false && panel.value === 'special') {
close();
}
moved = false;

14
src/ui/desc.vue Normal file
View File

@ -0,0 +1,14 @@
<template>
<div id="desc"></div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
</script>
<style lang="less" scoped>
#desc {
width: 100%;
height: 100%;
}
</style>

156
src/ui/markedEnemy.vue Normal file
View File

@ -0,0 +1,156 @@
<template>
<div id="marked-enemy">
<div v-for="v of all">
<Box
v-if="!getBoxPos(v).hidden"
v-model:left="getBoxPos(v).left"
v-model:top="getBoxPos(v).top"
v-model:width="getBoxPos(v).width"
v-model:height="getBoxPos(v).height"
:resizable="true"
>
<Scroll class="box-scroll" :no-scroll="true">
<div class="marked-main">
<div class="marked-info">
<BoxAnimate
:id="v"
:width="24"
:height="24"
></BoxAnimate>
<span class="marked-name marked-item">{{
getName(v)
}}</span>
</div>
<span class="marked-damage marked-item"
>伤害{{ getDamage(v) }}</span
>
<span class="marked-critical marked-item"
>临界: {{ getCritical(v)[0] }}</span
>
<span class="marked-critical-damage marked-item"
>减伤 {{ getCritical(v)[1] }}</span
>
<span class="marked-def marked-item"
>{{ ratio }}: {{ getDefDamage(v) }}</span
>
<div class="marked-button">
<span
class="marked-hide button-text"
@click="getBoxPos(v).hidden = true"
>隐藏盒子</span
>
<span
class="marked-cancel button-text"
@click="unmarkEnemy(v)"
>取消标记</span
>
</div>
</div></Scroll
>
</Box>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue';
import { checkMarkedStatus, getMarkedEnemy, unmarkEnemy } from '../plugin/mark';
import { has } from '../plugin/utils';
import Box from '../components/box.vue';
import Scroll from '../components/scroll.vue';
import BoxAnimate from '../components/boxAnimate.vue';
interface BoxPos {
left: number;
top: number;
width: number;
height: number;
hidden: boolean;
}
const ratio = core.status.thisMap?.ratio ?? 1;
let all = getMarkedEnemy();
watch(checkMarkedStatus, update);
const boxPos = reactive<Partial<Record<EnemyIds, BoxPos>>>({});
function update() {
all.push(...all.splice(0, all.length));
for (const id in boxPos) {
if (!all.includes(id as EnemyIds)) delete boxPos[id as EnemyIds];
}
}
function getBoxPos(id: EnemyIds) {
if (has(boxPos[id])) return boxPos[id]!;
boxPos[id] = {
left: window.innerWidth - 300,
top: 100,
width: 200,
height: 150,
hidden: false
};
return boxPos[id]!;
}
function getName(id: EnemyIds) {
return core.material.enemys[id].name;
}
function getDamage(id: EnemyIds) {
return core.formatBigNumber(core.getDamageInfo(id)?.damage) ?? '???';
}
function getCritical(id: EnemyIds) {
return (
core.nextCriticals(id, 1)[0]?.map(v => core.formatBigNumber(v)) ?? [
0, 0
]
);
}
function getDefDamage(id: EnemyIds) {
return core.formatBigNumber(core.getDefDamage(id, ratio));
}
</script>
<style lang="less" scoped>
#marked-enemy {
width: 100%;
height: 100%;
}
.box-scroll {
height: 100%;
width: 100%;
}
.marked-main {
padding: 1vh 0 1vh 0;
background-color: #0009;
display: flex;
flex-direction: column;
overflow: hidden;
}
.marked-info {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.marked-item {
margin-left: 10%;
}
.marked-button {
align-self: center;
width: 80%;
display: flex;
flex-direction: row;
justify-content: space-around;
}
</style>

106
src/ui/settings.vue Normal file
View File

@ -0,0 +1,106 @@
<template>
<div id="settings">
<div id="tools">
<span class="button-text tools" @click="exit"
><left-outlined /> 返回游戏</span
>
</div>
<div id="settings-main">
<Scroll id="setting-left">
<div id="setting-list">
<span
class="selectable setting-item"
:selected="selected === 'transition'"
@click="transition = !transition"
>界面动画:&nbsp;&nbsp;&nbsp;{{
transition ? 'ON' : 'OFF'
}}</span
>
</div>
</Scroll>
<a-divider id="divider" type="vertical"></a-divider>
<div id="setting-right">
<Scroll style="height: 100%">
<span
v-if="descText.value.startsWith('!!html')"
v-html="descText.value"
></span>
<span v-else>{{ descText.value }}</span>
</Scroll>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import { transition } from '../plugin/settings';
import settingInfo from '../data/settings.json';
import { type } from '../plugin/utils';
import Scroll from '../components/scroll.vue';
import { LeftOutlined } from '@ant-design/icons-vue';
type Settings = typeof settingInfo;
const selected = ref<keyof Settings>('transition');
const descText = computed(() => {
return type(settingInfo[selected.value].desc);
});
function exit() {
core.plugin.settingsOpened.value = false;
}
</script>
<style lang="less" scoped>
#settings {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-family: 'normal';
font-size: 24px;
user-select: none;
}
#settings-main {
width: 60%;
height: 60%;
display: flex;
flex-direction: row;
}
#setting-list {
display: flex;
flex-direction: column;
}
.setting-item {
width: 100%;
padding: 1% 3% 1% 3%;
}
#setting-left {
flex-basis: 40%;
}
#setting-right {
flex-basis: 60%;
}
#divider {
height: 100%;
}
#tools {
width: 100%;
font-family: 'normal';
font-size: 1.7em;
height: 5vh;
position: fixed;
left: 10vw;
top: 10vh;
}
</style>

View File

@ -149,7 +149,6 @@ watch(mode, n => {
const descText = computed(() => {
const s = selected.value;
if (s === 'none') return ref('没有选择道具');
if (all[s].text!.startsWith('!!html')) return ref(all[s].text);
return type(all[s].text!, 25, hyper('sin', 'out'), true);
});