Files
TankIC/tools/hornet_aim_warning.js
T
TermiNexus 56a1356cce fix(tools): 补全新版符号替换(修复透视/雷达仍不可用)
- 双向校验误判:q100_1 / r11l / jyw / t101_1 在新版同名但含义不同(普通数值 / 别的方法),被当作未变而跳过

- 强制替换:q100_1 到 m10b_1(坦克管理)、r11l 到 j11y(坦克列表)、jyw 到 lz4(取实体)、t101_1 到 p10c_1

- 覆盖 hook_gamemode.js(文件名不匹配 hornet_ 被漏掉)

- 物理组件字段 gzh_1 改为 rzf_1(新版 gzh_1 已是 TerminatorCC 字段,非物理)

- 移除已不存在的 xyu(GameMode),改用 gm.lz4()(等价:取 GameMode 实体)

- 重新生成 hornet_bundle.js(10 模块 / 249229 字节)并同步 unpacked
2026-10-17 15:49:50 +08:00

646 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ===== 3DTank瞄准警告 - 敌方坦克瞄准线预判 =====
// 在浏览器控制台运行(需先运行 hook_gamemode.js)
// 功能:当敌方坦克炮塔朝向玩家时,立刻显示HUD警告并标记方向
// 独立轻量级脚本,不依赖3D透视渲染
(function() {
function _start() {
console.log('===== 3DTank瞄准警告启动 =====');
// 允许在未进入战局 / ght 尚未捕获时启动:先出状态行,威胁判定等进入战局再算
if (window.__hornetAimWarning__) {
window.__hornetAimWarning__.Stop();
console.log('已停止旧瞄准警告');
}
var gm = window.__gameMode__ || null;
// ===== 设置参数(localStorage 持久化)=====
// Sensitivity: 灵敏度系数,>1 更灵敏(阈值更大,更易触发),<1 更迟钝
var Sensitivity = 1.0;
try {
var __savedSens = localStorage.getItem('hornet_aim_sensitivity');
if (__savedSens) {
var __s = parseFloat(__savedSens);
if (!isNaN(__s) && __s >= 0.3 && __s <= 3.0) Sensitivity = __s;
}
} catch(e) {}
// SoundEnabled: 警告声音开关
var SoundEnabled = false;
try {
var __savedSE = localStorage.getItem('hornet_aim_sound');
if (__savedSE === 'true') SoundEnabled = true;
} catch(e) {}
var _audioCtx = null;
var _lastBeepTime = 0;
function PlayBeep() {
if (!SoundEnabled) return;
var now = Date.now();
if (now - _lastBeepTime < 300) return; // 最多每300ms响一次
_lastBeepTime = now;
try {
if (!_audioCtx) _audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var osc = _audioCtx.createOscillator();
var gain = _audioCtx.createGain();
osc.frequency.value = 880;
osc.type = 'square';
gain.gain.value = 0.08;
osc.connect(gain);
gain.connect(_audioCtx.destination);
osc.start();
gain.gain.exponentialRampToValueAtTime(0.001, _audioCtx.currentTime + 0.15);
osc.stop(_audioCtx.currentTime + 0.15);
} catch(e) {}
}
// ===== 引用公共库(hornet_lib.js) =====
var lib = window.__hornetLib__;
if (!lib) {
console.error('[瞄准] 请先运行 hornet_lib.js');
return;
}
var teamFns = lib.LoadTeamFunctions();
var ZCt = teamFns.ZCt, m2Fn = teamFns.m2Fn;
// 公共函数别名(调用处不变)
var FindComponentByField = lib.FindComponentByField;
var GetTankPosition = lib.GetTankPosition;
var GetTankState = lib.GetTankState;
var IsTankAlive = lib.IsTankAlive;
// ===== 获取 TDt 武器皮肤组件(炮塔) =====
function GetTDtComponent(entity) {
var comp = FindComponentByField(entity, 'n11c_1');
if (comp) return comp;
comp = FindComponentByField(entity, 'q112_1');
return comp;
}
// 坦克名称/血量:使用公共库
var GetTankName = lib.GetTankName;
var GetTankHealth = lib.GetTankHealth;
// ===== 获取炮塔瞄准角度 =====
// 从炮塔 Bp 节点世界矩阵(n35_1)提取水平面前向方向
// 返回 { angle: 炮塔朝向与"敌方→玩家"方向的夹角, distance: 距离, turretAngle: 炮塔朝向角度, turretDir: {x,y} }
function GetTurretAimAngle(tank, enemyPos, playerPos) {
var tdt = GetTDtComponent(tank);
if (!tdt || !tdt.n11c_1 || !tdt.n11c_1.m17a_1) return null;
var bpNode = tdt.n11c_1.m17a_1;
var m = bpNode.n35_1;
if (!m) return null;
// 敌方→玩家方向(x,y 水平面)
var dx = playerPos.x - enemyPos.x;
var dy = playerPos.y - enemyPos.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 100) return null; // 太近不判断
var tx = dx / dist, ty = dy / dist;
// 尝试 X 轴(m0,m1) 和 Y 轴(m4,m5),取最小夹角
var minAngle = Math.PI;
var turretAngle = 0;
var turretDir = { x: 1, y: 0 };
var axes = [
{ x: m[0], y: m[1] },
{ x: m[4], y: m[5] }
];
for (var ai = 0; ai < axes.length; ai++) {
var ax = axes[ai].x, ay = axes[ai].y;
var len = Math.sqrt(ax * ax + ay * ay);
if (len < 0.001) continue;
var nx = ax / len, ny = ay / len;
var dot = nx * tx + ny * ty;
var ang = Math.acos(Math.max(-1, Math.min(1, dot)));
if (ang < minAngle) {
minAngle = ang;
turretAngle = Math.atan2(ny, nx);
turretDir = { x: nx, y: ny };
}
}
return { angle: minAngle, distance: dist, turretAngle: turretAngle, turretDir: turretDir };
}
// ===== 全局碰撞检测器(uMt 组件,挂在 GameMode entity 上) =====
var UMtComponent = null;
function GetUMtComponent() {
if (UMtComponent) return UMtComponent;
try {
var gmEntity = gm.lz4();
if (!gmEntity || !gmEntity.qz1_1 || !gmEntity.qz1_1.s2a_1) return null;
gmEntity.qz1_1.s2a_1.forEach(function(valueList, key) {
if (UMtComponent) return;
try {
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
if (comp && typeof comp.a17p === 'function') UMtComponent = comp;
} catch(e) {}
});
if (UMtComponent) console.log('[瞄准] uMt 碰撞检测器已获取');
else UMtComponent = false;
} catch(e) {
console.warn('[瞄准] 获取 uMt 组件失败:', e.message);
}
return UMtComponent || null;
}
// mock O$ 向量:使用公共库
var MkVec = lib.MkVec;
// ===== 射线检测视线遮挡(缓存:每10帧更新) =====
var RaycastCache = new Map();
var RaycastFailCount = 0;
function IsLineOfSightBlocked(enemyPos, playerPos, tankId, frame) {
var cached = RaycastCache.get(tankId);
if (cached && (frame - cached.frame) < 10) return cached.blocked;
var uMt = GetUMtComponent();
if (!uMt) return false;
var blocked = false;
try {
var dx = playerPos.x - enemyPos.x;
var dy = playerPos.y - enemyPos.y;
var dz = playerPos.z - enemyPos.z;
var dist = Math.sqrt(dx*dx + dy*dy + dz*dz);
if (dist < 200) {
RaycastCache.set(tankId, { frame: frame, blocked: false });
return false;
}
var ndx = dx / dist, ndy = dy / dist, ndz = dz / dist;
var startVec = MkVec(enemyPos.x, enemyPos.y, enemyPos.z + 200);
var dirVec = MkVec(ndx, ndy, ndz);
var maxDist = dist - 300;
var result = {
y178_1: MkVec(0,0,0),
z178_1: MkVec(0,0,0),
a179_1: maxDist,
q17o_1: null,
b179_1: null,
r17o: function(t, n, i, r, e) {
this.y178_1.k26(t);
this.z178_1.k26(n);
this.a179_1 = i;
this.q17o_1 = r;
this.b179_1 = e;
},
d3: function() { this.b179_1 = null; }
};
var filter = {
l1da_1: { h1: function() { return false; }, d3: function() {} },
w179: function() { return true; },
d3: function() { this.l1da_1.d3(); },
m1da: function() {}
};
var hit = uMt.a17p(startVec, dirVec, maxDist, result, filter);
blocked = !!hit && result.a179_1 < (maxDist - 50);
RaycastFailCount = 0;
} catch(e) {
RaycastFailCount++;
if (RaycastFailCount <= 3) console.warn('[瞄准] 射线检测异常:', e.message);
blocked = false;
}
RaycastCache.set(tankId, { frame: frame, blocked: blocked });
return blocked;
}
// 玩家坦克/友方/相机/投影:使用公共库
function GetPlayerTank() { return lib.GetPlayerTank(gm); }
function IsFriendly(tank, playerTank) { return lib.IsFriendly(tank, playerTank, gm); }
function GetCameraInfo() { return lib.GetCameraInfo(gm); }
var Project3D = lib.Project3D;
// ===== 创建 Canvas =====
var canvas = document.createElement('canvas');
canvas.id = 'hornetAimWarningCanvas';
canvas.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;z-index:99998;pointer-events:none;';
document.body.appendChild(canvas);
var ctx = canvas.getContext('2d');
function ResizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
ResizeCanvas();
window.addEventListener('resize', ResizeCanvas);
// ===== 警告闪烁状态 =====
var WarningPulse = 0; // 闪烁动画相位
// ===== 死亡检测状态 =====
var PlayerPrevHealth = -1; // 上一帧血量(-1=未初始化)
var DeathInfo = null; // 死亡信息 { killerName, distance, expected, time, threats }
var DeathDisplayTime = 360; // 死亡信息显示帧数(约6秒)
var DeathStats = { total: 0, expected: 0, unexpected: 0 }; // 死亡统计
// ===== 主循环 =====
var running = true;
var frameCount = 0;
function Loop() {
if (!running) return;
frameCount++;
requestAnimationFrame(Loop);
// 换图自刷新:gameMode 实例变化时重绑并清空该地图的缓存
var NewGm = window.__gameMode__;
if (NewGm && NewGm !== gm) {
gm = NewGm;
UMtComponent = null; // 碰撞检测器需从新场景重新获取
RaycastCache.clear(); // 旧地图的视线遮挡缓存失效
DeathInfo = null; // 清掉旧地图的死亡提示
console.log('[瞄准] 检测到新战斗,已重置缓存');
}
// ght 尚未捕获 / 不在战局:清屏并显示等待提示
if (!window.__gameMode__) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'rgba(255, 220, 120, 0.85)';
ctx.font = '11px Arial';
ctx.textAlign = 'left';
ctx.fillText('[瞄准警告] 等待战局…(ght 未捕获)', 5, 18);
return;
}
var screenW = canvas.width;
var screenH = canvas.height;
ctx.clearRect(0, 0, screenW, screenH);
var playerTank = GetPlayerTank();
if (!playerTank) return;
var playerPos = GetTankPosition(playerTank);
if (!playerPos) return;
var camInfo = GetCameraInfo();
if (!camInfo) return;
// 相机位置为0时使用玩家位置
if (Math.abs(camInfo.pos.x) < 0.1 && Math.abs(camInfo.pos.y) < 0.1 && Math.abs(camInfo.pos.z) < 0.1) {
camInfo.pos = { x: playerPos.x, y: playerPos.y, z: playerPos.z + 100 };
}
var tankList = gm.m10b_1.j11y();
var count = tankList.e1();
var threats = [];
// 遍历所有敌方坦克,检测炮塔朝向
for (var i = 0; i < count; i++) {
var tank = tankList.g1(i);
if (!tank || tank === playerTank) continue;
if (!IsTankAlive(tank)) continue;
var pos = GetTankPosition(tank);
if (!pos) continue;
// 只检测敌方
if (IsFriendly(tank, playerTank)) continue;
var aimInfo = GetTurretAimAngle(tank, pos, playerPos);
if (!aimInfo) continue;
// 瞄准阈值随距离变化:近距离~11度(0.20rad),远距离~3度
// 公式:0.20 / (1 + distance/8000),乘以灵敏度系数
var aimThreshold = 0.20 / (1 + aimInfo.distance / 8000) * Sensitivity;
if (aimInfo.angle >= aimThreshold) continue;
// 墙体阻隔判断
var blocked = IsLineOfSightBlocked(pos, playerPos, tank.iz1_1, frameCount);
if (blocked) continue;
// 记录威胁
var proj = Project3D(pos, camInfo.pos, camInfo.yaw, camInfo.pitch, camInfo.fov, screenW, screenH);
var tankName = GetTankName(tank) || ('ID:' + tank.iz1_1);
threats.push({
pos: pos,
distance: aimInfo.distance,
angle: aimInfo.angle,
turretDir: aimInfo.turretDir,
screenProj: proj,
tankId: tank.iz1_1,
tankName: tankName
});
}
// ===== 死亡检测 =====
var playerHealth = GetTankHealth(playerTank);
var playerCurHealth = playerHealth ? playerHealth.current : 0;
// 检测血量从 >0 变为 0 = 死亡
if (PlayerPrevHealth > 0 && playerCurHealth <= 0) {
// 死亡时,从 threats 列表找最近的瞄准者作为可能的击杀者
var killer = null;
var minDist = Infinity;
for (var ki = 0; ki < threats.length; ki++) {
if (threats[ki].distance < minDist) {
minDist = threats[ki].distance;
killer = threats[ki];
}
}
DeathInfo = {
killerName: killer ? killer.tankName : null,
killerDistance: killer ? killer.distance : 0,
expected: threats.length > 0, // 有瞄准者 = 意料之内
threatCount: threats.length,
time: frameCount
};
DeathStats.total++;
if (DeathInfo.expected) DeathStats.expected++;
else DeathStats.unexpected++;
console.log('[瞄准] 玩家死亡! ' + (killer ? '被 ' + killer.tankName + ' 击杀' : '被偷击杀') +
' | 意料之' + (threats.length > 0 ? '内' : '外') +
' | 瞄准者: ' + threats.length +
' | 统计: ' + DeathStats.total + '死 (内' + DeathStats.expected + '/外' + DeathStats.unexpected + ')');
}
// 复活检测:血量恢复 >0
if (PlayerPrevHealth <= 0 && playerCurHealth > 0) {
DeathInfo = null;
console.log('[瞄准] 玩家复活,重置死亡信息');
}
PlayerPrevHealth = playerCurHealth;
// ===== 绘制瞄准线(每个威胁的弹道轨迹) =====
if (threats.length > 0) {
PlayBeep();
ctx.strokeStyle = 'rgba(255, 40, 40, 0.5)';
ctx.lineWidth = 2;
for (var ti = 0; ti < threats.length; ti++) {
var tr = threats[ti];
var startPos = { x: tr.pos.x, y: tr.pos.y, z: tr.pos.z + 200 }; // 炮塔高度
var endPos = { x: playerPos.x, y: playerPos.y, z: playerPos.z };
var segs = 20;
var prevScreen = null;
ctx.beginPath();
for (var si = 0; si <= segs; si++) {
var st = si / segs;
var swp = {
x: startPos.x + (endPos.x - startPos.x) * st,
y: startPos.y + (endPos.y - startPos.y) * st,
z: startPos.z + (endPos.z - startPos.z) * st
};
var sp = Project3D(swp, camInfo.pos, camInfo.yaw, camInfo.pitch, camInfo.fov, screenW, screenH);
if (sp) {
if (prevScreen) ctx.lineTo(sp.x, sp.y);
else ctx.moveTo(sp.x, sp.y);
prevScreen = sp;
} else {
prevScreen = null;
}
}
ctx.stroke();
}
}
// ===== 每个威胁的方向标记 =====
for (var thi = 0; thi < threats.length; thi++) {
var threat = threats[thi];
var proj = threat.screenProj;
if (proj && proj.x >= 0 && proj.x <= screenW && proj.y >= 0 && proj.y <= screenH) {
// ===== 威胁在屏幕内:在威胁位置画红色警告三角形 =====
var mx = proj.x, my = proj.y - 35;
ctx.strokeStyle = 'rgba(255, 0, 0, 1)';
ctx.lineWidth = 2.5;
ctx.fillStyle = 'rgba(255, 0, 0, 0.4)';
ctx.beginPath();
ctx.moveTo(mx, my - 10);
ctx.lineTo(mx - 9, my + 6);
ctx.lineTo(mx + 9, my + 6);
ctx.closePath();
ctx.fill();
ctx.stroke();
// 感叹号
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
ctx.font = 'bold 13px Arial';
ctx.textAlign = 'center';
ctx.fillText('!', mx, my + 4);
// 距离标签
ctx.fillStyle = 'rgba(255, 200, 200, 0.95)';
ctx.font = 'bold 11px Arial';
ctx.fillText(Math.round(threat.distance / 100) + 'm', mx, my + 22);
// 坦克名称
ctx.fillStyle = 'rgba(255, 240, 240, 0.95)';
ctx.font = 'bold 11px Arial';
ctx.fillText(threat.tankName, mx, my + 36);
} else {
// ===== 威胁不在屏幕内:屏幕边缘红色箭头指示方向 =====
var tdx = threat.pos.x - playerPos.x;
var tdy = threat.pos.y - playerPos.y;
var tlen = Math.sqrt(tdx * tdx + tdy * tdy);
if (tlen > 0.001) {
var ndx = tdx / tlen, ndy = tdy / tlen;
// 计算相对相机偏航角的方向
var fwdX = -Math.sin(camInfo.yaw), fwdY = Math.cos(camInfo.yaw);
var rgtX = Math.cos(camInfo.yaw), rgtY = Math.sin(camInfo.yaw);
var screenRight = ndx * rgtX + ndy * rgtY;
var screenFwd = ndx * fwdX + ndy * fwdY;
var sAng = Math.atan2(screenRight, screenFwd);
var edgeR = Math.min(screenW, screenH) * 0.42;
var ex = screenW / 2 + Math.sin(sAng) * edgeR;
var ey = screenH / 2 - Math.cos(sAng) * edgeR;
// 限制在屏幕边缘内
ex = Math.max(30, Math.min(screenW - 30, ex));
ey = Math.max(30, Math.min(screenH - 30, ey));
// 画箭头(指向威胁方向)
ctx.fillStyle = 'rgba(255, 0, 0, 0.95)';
ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)';
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.moveTo(ex + Math.sin(sAng) * 14, ey - Math.cos(sAng) * 14);
ctx.lineTo(ex + Math.sin(sAng + 2.5) * 9, ey - Math.cos(sAng + 2.5) * 9);
ctx.lineTo(ex + Math.sin(sAng - 2.5) * 9, ey - Math.cos(sAng - 2.5) * 9);
ctx.closePath();
ctx.fill();
ctx.stroke();
// 距离标签
ctx.fillStyle = 'rgba(255, 255, 255, 0.95)';
ctx.font = 'bold 11px Arial';
ctx.textAlign = 'center';
ctx.fillText(Math.round(threat.distance / 100) + 'm', ex, ey + 22);
// 坦克名称
ctx.fillStyle = 'rgba(255, 240, 240, 0.95)';
ctx.font = 'bold 10px Arial';
ctx.fillText(threat.tankName, ex, ey + 36);
}
}
}
// ===== 左侧中央常显警告框(有威胁时) =====
if (threats.length > 0) {
WarningPulse += 0.15;
var pulseAlpha = 0.55 + Math.sin(WarningPulse) * 0.2; // 脉动透明度
// 动态高度:标题区 + 威胁列表
var maxListed = 4; // 最多列出4个威胁名称
var listedCount = Math.min(threats.length, maxListed);
var boxW = 240;
var boxH = 70 + listedCount * 18 + (threats.length > maxListed ? 16 : 0);
var boxX = 20;
var boxY = screenH / 2 - boxH / 2;
// 警告框背景(脉动)
ctx.fillStyle = 'rgba(80, 0, 0, ' + pulseAlpha + ')';
ctx.fillRect(boxX, boxY, boxW, boxH);
ctx.strokeStyle = 'rgba(255, 0, 0, 1)';
ctx.lineWidth = 2.5;
ctx.strokeRect(boxX, boxY, boxW, boxH);
// 警告标题
ctx.fillStyle = 'rgba(255, 60, 60, 1)';
ctx.font = 'bold 26px Arial';
ctx.textAlign = 'center';
ctx.fillText('⚠ 被瞄准', boxX + boxW / 2, boxY + 32);
ctx.font = 'bold 13px Arial';
ctx.fillStyle = 'rgba(255, 220, 220, 0.95)';
ctx.fillText(threats.length + ' 个威胁锁定你', boxX + boxW / 2, boxY + 54);
// 威胁坦克名称列表
ctx.textAlign = 'left';
ctx.font = 'bold 12px Arial';
for (var li = 0; li < listedCount; li++) {
var t = threats[li];
var lineY = boxY + 72 + li * 18;
// 名称 + 距离
ctx.fillStyle = 'rgba(255, 240, 240, 0.95)';
ctx.fillText('• ' + t.tankName, boxX + 12, lineY);
ctx.fillStyle = 'rgba(255, 180, 180, 0.85)';
ctx.textAlign = 'right';
ctx.fillText(Math.round(t.distance / 100) + 'm', boxX + boxW - 12, lineY);
ctx.textAlign = 'left';
}
// 超出部分
if (threats.length > maxListed) {
ctx.fillStyle = 'rgba(255, 200, 200, 0.7)';
ctx.font = 'italic 11px Arial';
ctx.textAlign = 'center';
ctx.fillText('等 ' + (threats.length - maxListed) + ' 个...', boxX + boxW / 2, boxY + 72 + listedCount * 18 + 12);
}
}
// ===== 死亡信息显示(屏幕中央,持续6秒或直到复活) =====
if (DeathInfo && (frameCount - DeathInfo.time) < DeathDisplayTime) {
var dBoxW = 400, dBoxH = 120;
var dBoxX = screenW / 2 - dBoxW / 2;
var dBoxY = screenH / 2 - dBoxH / 2 - 60; // 偏上一点
// 半透明背景
ctx.fillStyle = 'rgba(0, 0, 0, 0.75)';
ctx.fillRect(dBoxX, dBoxY, dBoxW, dBoxH);
// 边框(意料之内=红色,意料之外=紫色)
var borderColor = DeathInfo.expected ? 'rgba(255, 60, 60, 1)' : 'rgba(200, 80, 255, 1)';
ctx.strokeStyle = borderColor;
ctx.lineWidth = 3;
ctx.strokeRect(dBoxX, dBoxY, dBoxW, dBoxH);
// 标题
ctx.textAlign = 'center';
ctx.fillStyle = 'rgba(255, 80, 80, 1)';
ctx.font = 'bold 26px Arial';
ctx.fillText('你被击杀', dBoxX + dBoxW / 2, dBoxY + 36);
// 击杀者信息
ctx.font = 'bold 18px Arial';
if (DeathInfo.killerName) {
ctx.fillStyle = 'rgba(255, 220, 220, 1)';
ctx.fillText('击杀者: ' + DeathInfo.killerName, dBoxX + dBoxW / 2, dBoxY + 66);
ctx.font = '12px Arial';
ctx.fillStyle = 'rgba(255, 180, 180, 0.8)';
ctx.fillText('距离 ' + Math.round(DeathInfo.killerDistance / 100) + 'm', dBoxX + dBoxW / 2, dBoxY + 86);
} else {
ctx.fillStyle = 'rgba(220, 180, 255, 1)';
ctx.fillText('未知来源(偷袭)', dBoxX + dBoxW / 2, dBoxY + 66);
}
// 意料之内/外标签
ctx.font = 'bold 16px Arial';
if (DeathInfo.expected) {
ctx.fillStyle = 'rgba(255, 100, 100, 1)';
ctx.fillText('⚠ 意料之内(已在瞄准名单中)', dBoxX + dBoxW / 2, dBoxY + 108);
} else {
ctx.fillStyle = 'rgba(200, 100, 255, 1)';
ctx.fillText('✦ 意料之外(偷袭!)', dBoxX + dBoxW / 2, dBoxY + 108);
}
}
// ===== 顶部状态栏(始终显示) =====
ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
ctx.font = '11px Arial';
ctx.textAlign = 'left';
var statusText = '[瞄准警告] 监控中 | 威胁: ' + threats.length +
' | 死亡: ' + DeathStats.total + '(内' + DeathStats.expected + '/外' + DeathStats.unexpected + ')' +
' | Frame: ' + frameCount;
ctx.fillText(statusText, 5, 18);
}
// ===== 启动 =====
Loop();
console.log('[瞄准] 警告系统已启动,监控敌方坦克炮塔朝向');
// ===== 暴露控制接口 =====
window.__hornetAimWarning__ = {
running: true,
Stop: function() {
this.running = false;
running = false;
window.removeEventListener('resize', ResizeCanvas);
if (canvas && canvas.parentNode) canvas.parentNode.removeChild(canvas);
console.log('[瞄准] 警告系统已停止');
},
GetDeathStats: function() {
return DeathStats;
},
ResetDeathStats: function() {
DeathStats = { total: 0, expected: 0, unexpected: 0 };
console.log('[瞄准] 死亡统计已重置');
},
setSensitivity: function(s) {
var n = parseFloat(s);
if (!isNaN(n) && n >= 0.3 && n <= 3.0) {
Sensitivity = n;
try { localStorage.setItem('hornet_aim_sensitivity', String(n)); } catch(e) {}
console.log('[瞄准] 灵敏度设为: ' + n);
} else {
console.warn('[瞄准] 灵敏度应在 0.3~3.0');
}
},
getSensitivity: function() { return Sensitivity; },
setSound: function(on) {
SoundEnabled = !!on;
try { localStorage.setItem('hornet_aim_sound', SoundEnabled ? 'true' : 'false'); } catch(e) {}
console.log('[瞄准] 警告声音: ' + (SoundEnabled ? '开' : '关'));
},
getSound: function() { return SoundEnabled; }
};
}
var Tool = {
Start: function() {
if (window.__hornetAimWarning__ && window.__hornetAimWarning__.running) return;
_start();
},
Stop: function() {
if (window.__hornetAimWarning__) window.__hornetAimWarning__.Stop();
},
Status: function() {
return (window.__hornetAimWarning__ && window.__hornetAimWarning__.running) ? '运行中' : '已停止';
},
GetRunning: function() {
return !!(window.__hornetAimWarning__ && window.__hornetAimWarning__.running);
},
SetSensitivity: function(s) { if (window.__hornetAimWarning__) window.__hornetAimWarning__.setSensitivity(s); },
GetSensitivity: function() { return window.__hornetAimWarning__ ? window.__hornetAimWarning__.getSensitivity() : 1.0; },
SetSound: function(on) { if (window.__hornetAimWarning__) window.__hornetAimWarning__.setSound(on); },
GetSound: function() { return window.__hornetAimWarning__ ? window.__hornetAimWarning__.getSound() : false; }
};
window.__hornetTools__ = window.__hornetTools__ || {};
window.__hornetTools__.AimWarning = Tool;
console.log('[3DTank] 瞄准警告模块已加载');
})();