diff --git a/2001/ze_the_curse_of_blackwater/countdown.js b/2001/ze_the_curse_of_blackwater/countdown.js new file mode 100644 index 0000000..970740c --- /dev/null +++ b/2001/ze_the_curse_of_blackwater/countdown.js @@ -0,0 +1,216 @@ +//纯预设模版的倒计时脚本,游戏内对point_script实体输入RunScriptInput,值为endin_30,则触发30秒内结束战斗的倒计时 +//回合重启或者输入值为stop时倒计时会直接停止 +//局内需存在以下实体 + //①、一个game_zone_player固实体,命名为countdown_game_zone_player_hide,实体内io:OnPlayerOutZone>countdown_hudhint>HideHudHint>>0>-1 + //②、一个game_zone_player固实体,命名为countdown_game_zone_player_show,实体内io:OnPlayerOutZone>countdown_hudhint>ShowHudHint>>0>-1 + //③、一个env_hudhint点实体,命名为countdown_hudhint + //④、一个point_script点实体,命名为countdown_script,并应用上该vjs +//纯数字倒计时和其他倒计时可以在文本模版和预设倒计时模版那里添加即可 +import { Instance } from "cs_script/point_script"; + +// ============= 配置区域 ============= +const CONFIG = { + ENTITY_NAMES: { + HUD_HINT: "countdown_hudhint", + ZONE_SHOW: "countdown_game_zone_player_show", + ZONE_HIDE: "countdown_game_zone_player_hide", + }, + THINK_INTERVAL: 0.1, + ZERO_DISPLAY_TIME: 0.5 // 0 显示多久后消失(秒) +}; + +// 文本模板 +const TEMPLATES = { + gateopen: "Gate will open in {time} sec", + dooropen: "Door will open in {time} sec", + doorclose: "Door will close in {time} sec", + doorbreak: "Door will break in {time} sec", + doormainofficeopen: "Door to the main office will open in {time} sec", + officedoorswillclose: "Office doors will close in {time} sec", + officedoorswillopen: "Office doors will open in {time} sec", + doorintooffice: "Door into office will open in {time} sec", + doortotherightside: "Door to the right side of the lab will open in {time} sec", + doorstoblackwaterundergroundlab: "Doors to Blackwater underground lab will open in {time} sec", + doortomainlabopen: "Door to main lab will open in {time} sec", + doortostorageroomopen: "Door to storage room will open in {time} sec", + doorwiththirdleveropen: "Door with third lever will open in {time} sec", + elevatorwillleave: "Elevator will leave in {time} sec", + emergencyexitopen: "The emergency exit will open in {time} sec", + secondlabopen: "Door to 2-nd lab will open in {time} sec", + sewerdooropen: "Sewer door will open in {time} sec", + secondleveropen: "Door with second lever will open in {time} sec", + firstleveropen: "Door with first lever will open in {time} sec", + maternitywardopen: "Door to maternity ward will open in {time} sec", +}; + +// ===================================== + +// 状态 +const state = { + active: false, + endTime: 0, + lastSecond: -1, + template: "", + zeroEndTime: -1 // 0 显示截止时间 +}; + +// HUD 控制 +const HUD = { + show(message) { + if (message !== undefined) { + Instance.EntFireAtName({ + name: CONFIG.ENTITY_NAMES.HUD_HINT, + input: "SetMessage", + value: message + }); + } + + Instance.EntFireAtName({ + name: CONFIG.ENTITY_NAMES.ZONE_SHOW, + input: "CountPlayersInZone" + }); + }, + + hide() { + Instance.EntFireAtName({ + name: CONFIG.ENTITY_NAMES.ZONE_HIDE, + input: "CountPlayersInZone" + }); + } +}; + +// ================= 核心倒计时 ================= + +class CountdownManager { + + static think() { + if (!state.active) return; + + const now = Instance.GetGameTime(); + const remaining = state.endTime - now; + const seconds = Math.ceil(Math.max(0, remaining)); + + // ===== 已进入 0 显示阶段 ===== + if (state.zeroEndTime > 0) { + if (now >= state.zeroEndTime) { + state.active = false; + state.zeroEndTime = -1; + HUD.hide(); + return; + } + + Instance.SetNextThink(now + CONFIG.THINK_INTERVAL); + return; + } + + // ===== 第一次进入 0 ===== + if (seconds === 0) { + state.lastSecond = 0; + + HUD.show( + state.template.replace(/{time}/g, 0) + ); + + state.zeroEndTime = now + CONFIG.ZERO_DISPLAY_TIME; + Instance.SetNextThink(now + CONFIG.THINK_INTERVAL); + return; + } + + // ===== 正常倒计时 ===== + if (seconds !== state.lastSecond) { + state.lastSecond = seconds; + + HUD.show( + state.template.replace(/{time}/g, seconds) + ); + } + + Instance.SetNextThink(now + CONFIG.THINK_INTERVAL); + } + + static start(templateKey, seconds) { + const template = TEMPLATES[templateKey]; + if (!template || seconds < 0) return false; + + state.active = true; + state.template = template; + state.endTime = Instance.GetGameTime() + seconds; + state.lastSecond = -1; + state.zeroEndTime = -1; + + HUD.show(template.replace(/{time}/g, seconds)); + + Instance.SetThink(CountdownManager.think); + Instance.SetNextThink(Instance.GetGameTime() + CONFIG.THINK_INTERVAL); + + return true; + } + + static stop() { + if (!state.active) return; + state.active = false; + state.zeroEndTime = -1; + HUD.hide(); + } + + static parse(input) { + if (input === "stop") return { type: "stop" }; + + const match = input.match(/^(.+)_([0-9]+)$/); + if (!match) return null; + + return { + type: "countdown", + templateKey: match[1], + seconds: Number(match[2]) + }; + } +} + +// ================= ScriptInput ================= + +function onInput(data) { + const input = data && data.value !== undefined ? data.value : data; + const parsed = CountdownManager.parse(input); + + if (!parsed) return; + + if (parsed.type === "stop") { + CountdownManager.stop(); + } else { + CountdownManager.start(parsed.templateKey, parsed.seconds); + } +} + +Instance.OnScriptInput("countdown", onInput); +Instance.OnScriptInput("stop", CountdownManager.stop); + +// 预设倒计时 +[ + "secondlabopen_25", + "doorbreak_30", + "doorclose_15","doorclose_20","doorclose_25","doorclose_30", + "doorintooffice_15","doorintooffice_20", + "doormainofficeopen_10", + "dooropen_10","dooropen_15","dooropen_20","dooropen_25","dooropen_30","dooropen_40", + "doorstoblackwaterundergroundlab_30", + "doortomainlabopen_20", + "doortostorageroomopen_20","doortostorageroomopen_25","doortostorageroomopen_30", + "doortotherightside_15", + "doorwiththirdleveropen_20", + "elevatorwillleave_20", + "elevatorwillleave_40", + "emergencyexitopen_20","emergencyexitopen_30", + "firstleveropen_30", + "gateopen_60", + "maternitywardopen_40", + "officedoorswillclose_10","officedoorswillclose_20", + "secondlabopen_25", + "secondleveropen_30", + "sewerdooropen_30" +].forEach(preset => { + Instance.OnScriptInput(preset, () => onInput(preset)); +}); + +// 回合开始自动重置 +Instance.OnRoundStart(CountdownManager.stop); diff --git a/2001/ze_the_curse_of_blackwater/creepygirl.js b/2001/ze_the_curse_of_blackwater/creepygirl.js new file mode 100644 index 0000000..d8348b9 --- /dev/null +++ b/2001/ze_the_curse_of_blackwater/creepygirl.js @@ -0,0 +1,393 @@ +import { Instance } from "cs_script/point_script"; + +// ========== 配置参数 ========== +const range = 2048; // 水平检测半径 +const hight = 64; // 垂直检测高度 +const rechecktime = 0.02; // 循环时间 +const retargettime = 8; // 重新寻找目标间隔 +const force = 1500; // 推力大小 +const maxspeed = 235; // 最大移动速度 +const rotaspeed = 240; // 最大旋转角度(度/秒) +const RAISE_HEIGHT = 64; // 视线检测点抬高高度 + +// ========== 状态变量 ========== +let relaySuffix = null; // 批次后缀 +let creepygirlBox = null; // creepygirl_ct实体 +let isChecking = false; // 是否正在检测 +let hasTriggeredFireUser1 = false; // 是否已触发FireUser1 +let hasCalculatedHealth = false; // 是否已计算初始血量 +let isTeleportPaused = false; // Teleport是否暂停 +let isStopped = false; // 脚本是否已停止 + +// ========== 追踪相关变量 ========== +let isTracking = false; // 是否正在追踪 +let targetPlayer = null; // 当前追踪目标 +let visibleCTPlayers = []; // 可见CT玩家列表 +let lastTargetSelectionTime = 0; // 上次选择目标时间 +let isTargetVisible = false; // 目标是否可见 +let lastTargetChangeReason = ""; // 上次目标变化原因 + +// ========== 输入处理 ========== + +// start输入:初始化并启动功能 +Instance.OnScriptInput("start", (inputData) => { + if (isStopped) return; + + if (inputData.caller) { + const relayName = inputData.caller.GetEntityName(); + const suffixMatch = relayName.match(/_(\d+)$/); + + if (suffixMatch && suffixMatch[1]) { + relaySuffix = suffixMatch[1]; + + // 查找对应的creepygirl_ct实体 + const boxName = `creepygirl_ct_${relaySuffix}`; + creepygirlBox = Instance.FindEntityByName(boxName); + + if (creepygirlBox && creepygirlBox.IsValid()) { + // 重置状态 + hasTriggeredFireUser1 = false; + isTracking = false; + targetPlayer = null; + lastTargetSelectionTime = 0; + lastTargetChangeReason = ""; + isTeleportPaused = false; + + // 计算初始血量(仅一次) + if (!hasCalculatedHealth) { + calculateInitialHealth(); + hasCalculatedHealth = true; + } + + // 启动检测循环 + if (!isChecking) { + isChecking = true; + Instance.SetNextThink(Instance.GetGameTime() + rechecktime); + } + } + } + } +}); + +// pause输入:暂停Teleport功能 +Instance.OnScriptInput("pause", (inputData) => { + if (isStopped || isTeleportPaused) return; + isTeleportPaused = true; +}); + +// unpause输入:恢复Teleport功能 +Instance.OnScriptInput("unpause", (inputData) => { + if (isStopped || !isTeleportPaused) return; + isTeleportPaused = false; +}); + +// stop输入:完全停止脚本 +Instance.OnScriptInput("stop", () => { + if (isStopped) return; + + isStopped = true; // 标记为已停止 + isChecking = false; // 停止检测循环 + isTracking = false; // 停止追踪 + + // 清除所有存储信息 + relaySuffix = null; + creepygirlBox = null; + hasTriggeredFireUser1 = false; + hasCalculatedHealth = false; + isTeleportPaused = false; + targetPlayer = null; + visibleCTPlayers = []; + lastTargetSelectionTime = 0; + isTargetVisible = false; + lastTargetChangeReason = ""; +}); + +// ========== 功能函数 ========== + +// 计算初始血量并设置到对应的血量计数器 +function calculateInitialHealth() { + // 1. 计算CT玩家数量 + let ctPlayerCount = 0; + const allPlayers = Instance.FindEntitiesByClass("player"); + + for (const player of allPlayers) { + if (player && player.IsValid() && player.IsAlive() && player.GetTeamNumber() === 3) { + ctPlayerCount++; + } + } + + // 2. 获取per_person实体角度总和 + let perPersonAngleSum = 0; + const perPersonEntity = Instance.FindEntityByName("per_person"); + if (perPersonEntity && perPersonEntity.IsValid()) { + const angles = perPersonEntity.GetAbsAngles(); + perPersonAngleSum = angles.pitch + angles.yaw + angles.roll; + } + + // 3. 获取creepygirl_base_health实体角度总和 + let baseHealthAngleSum = 0; + const baseHealthEntity = Instance.FindEntityByName("creepygirl_base_health"); + if (baseHealthEntity && baseHealthEntity.IsValid()) { + const angles = baseHealthEntity.GetAbsAngles(); + baseHealthAngleSum = angles.pitch + angles.yaw + angles.roll; + } + + // 4. 计算血量:health = a * b + c + const initialHealth = ctPlayerCount * perPersonAngleSum + baseHealthAngleSum * 10; + + // 5. 设置到对应的血量计数器 + const hpCounterName = `creepygirl_hp_counter_${relaySuffix}`; + const hpCounterEntity = Instance.FindEntityByName(hpCounterName); + + if (hpCounterEntity && hpCounterEntity.IsValid()) { + Instance.EntFireAtTarget({ + target: hpCounterEntity, + input: "SetValueNoFire", + value: initialHealth + }); + } +} + +// 检查玩家是否在检测范围内 +function isPlayerInRange(player, boxPos) { + if (!player || !player.IsValid() || !player.IsAlive()) { + return false; + } + + const playerPos = player.GetAbsOrigin(); + const dx = playerPos.x - boxPos.x; + const dy = playerPos.y - boxPos.y; + const horizontalDistance = Math.sqrt(dx * dx + dy * dy); + + return horizontalDistance <= range && Math.abs(playerPos.z - boxPos.z) <= hight; +} + +// 检查玩家是否可见(视线检测点抬高64单位) +function isPlayerVisible(player, boxPos) { + // 抬高creepygirl_ct检测点 + const raisedBoxPos = { + x: boxPos.x, + y: boxPos.y, + z: boxPos.z + RAISE_HEIGHT + }; + + // 抬高玩家视线检测点 + const eyePos = player.GetEyePosition(); + const raisedEyePos = { + x: eyePos.x, + y: eyePos.y, + z: eyePos.z + RAISE_HEIGHT + }; + + const traceResult = Instance.TraceLine({ + start: raisedBoxPos, + end: raisedEyePos, + ignoreEntity: creepygirlBox + }); + + return !traceResult.didHit || traceResult.hitEntity === player; +} + +// 检查玩家是否为CT阵营 +function isPlayerCT(player) { + return player && player.IsValid() && player.GetTeamNumber() === 3; +} + +// 计算目标方向角度 +function calculateYawToTarget(startPos, targetPos) { + const dx = targetPos.x - startPos.x; + const dy = targetPos.y - startPos.y; + return Math.atan2(dy, dx) * (180 / Math.PI); +} + +// 角度标准化到0-360度 +function normalizeAngle(angle) { + while (angle < 0) angle += 360; + while (angle >= 360) angle -= 360; + return angle; +} + +// 计算角度差 +function angleDifference(current, target) { + let diff = normalizeAngle(target) - normalizeAngle(current); + if (diff > 180) diff -= 360; + if (diff < -180) diff += 360; + return diff; +} + +// 从可见玩家中随机选择目标 +function selectRandomTargetFromVisible() { + if (visibleCTPlayers.length === 0) return null; + const randomIndex = Math.floor(Math.random() * visibleCTPlayers.length); + return visibleCTPlayers[randomIndex]; +} + +// 检查目标有效性 +function checkTargetValid(targetPlayer, boxPos) { + if (!targetPlayer || !targetPlayer.IsValid()) return "目标无效"; + if (!targetPlayer.IsAlive()) return "目标死亡"; + if (!isPlayerCT(targetPlayer)) return "目标阵营变更"; + if (!isPlayerInRange(targetPlayer, boxPos)) return "目标超出范围"; + return null; +} + +// ========== 主循环 ========== +Instance.SetThink(() => { + if (isStopped) return; // 脚本已停止,不执行任何操作 + if (!isChecking || !creepygirlBox || !creepygirlBox.IsValid()) return; + + const boxPos = creepygirlBox.GetAbsOrigin(); + const boxAngles = creepygirlBox.GetAbsAngles(); + const currentYaw = boxAngles.yaw; + const currentTime = Instance.GetGameTime(); + + // 重置可见玩家列表 + visibleCTPlayers = []; + isTargetVisible = false; + + // 检测所有玩家 + const allPlayers = Instance.FindEntitiesByClass("player"); + let foundVisiblePlayer = false; + + for (const player of allPlayers) { + if (!player.IsValid() || !player.IsAlive() || !isPlayerCT(player)) continue; + + if (isPlayerInRange(player, boxPos)) { + // 检查可见性(使用抬高后的检测点) + const isVisible = isPlayerVisible(player, boxPos); + + if (isVisible) { + foundVisiblePlayer = true; + visibleCTPlayers.push(player); + + // 检查是否为当前目标 + if (targetPlayer && targetPlayer === player) { + isTargetVisible = true; + } + + // 首次发现可见玩家时触发FireUser1并开始追踪 + if (!hasTriggeredFireUser1) { + Instance.EntFireAtTarget({ + target: creepygirlBox, + input: "FireUser1" + }); + + hasTriggeredFireUser1 = true; + isTracking = true; + lastTargetSelectionTime = currentTime; + lastTargetChangeReason = "首次检测到玩家"; + } + } + } + } + + // 追踪逻辑 + if (isTracking) { + // 检查目标有效性 + const targetInvalidReason = checkTargetValid(targetPlayer, boxPos); + const needsNewTargetByTime = currentTime - lastTargetSelectionTime >= retargettime; + const needsNewTarget = targetInvalidReason !== null || needsNewTargetByTime; + + // 需要重新选择目标 + if (needsNewTarget) { + if (targetInvalidReason) { + lastTargetChangeReason = targetInvalidReason; + } else if (needsNewTargetByTime) { + lastTargetChangeReason = "时间间隔到"; + } + + // 从可见玩家中选择新目标 + if (visibleCTPlayers.length > 0) { + targetPlayer = selectRandomTargetFromVisible(); + lastTargetSelectionTime = currentTime; + isTargetVisible = true; + } else { + // 无可见玩家 + targetPlayer = null; + isTargetVisible = false; + + // 检查是否还有在范围内的CT玩家 + let hasCTPlayersInRange = false; + for (const player of allPlayers) { + if (player.IsValid() && player.IsAlive() && isPlayerCT(player) && + isPlayerInRange(player, boxPos)) { + hasCTPlayersInRange = true; + break; + } + } + + // 无符合条件的CT玩家则停止追踪 + if (!hasCTPlayersInRange) { + isTracking = false; + } + } + } + + // 激活追踪但无目标,尝试选择新目标 + if (!targetPlayer && visibleCTPlayers.length > 0) { + targetPlayer = selectRandomTargetFromVisible(); + lastTargetSelectionTime = currentTime; + isTargetVisible = true; + lastTargetChangeReason = "重新获取目标"; + } + + // 执行追踪(即使目标不可见) + if (targetPlayer && targetPlayer.IsValid() && targetPlayer.IsAlive() && + isPlayerCT(targetPlayer) && isPlayerInRange(targetPlayer, boxPos)) { + + const targetPos = targetPlayer.GetAbsOrigin(); + const targetYaw = calculateYawToTarget(boxPos, targetPos); + const angleDiff = angleDifference(currentYaw, targetYaw); + + // 计算旋转步长 + const maxRotationStep = rotaspeed * rechecktime; + let rotationStep = Math.min(Math.abs(angleDiff), maxRotationStep); + if (angleDiff < 0) rotationStep = -rotationStep; + + // 计算新角度 + const newYaw = normalizeAngle(currentYaw + rotationStep); + const radYaw = newYaw * (Math.PI / 180); + + // 计算推力 + const forwardForce = { + x: Math.cos(radYaw) * force, + y: Math.sin(radYaw) * force, + z: 0 + }; + + // 计算新速度 + const currentVelocity = creepygirlBox.GetAbsVelocity(); + let newVelocity = { + x: currentVelocity.x + forwardForce.x * rechecktime, + y: currentVelocity.y + forwardForce.y * rechecktime, + z: currentVelocity.z + }; + + // 限制最大速度 + const horizontalSpeed = Math.sqrt(newVelocity.x * newVelocity.x + newVelocity.y * newVelocity.y); + if (horizontalSpeed > maxspeed) { + const scale = maxspeed / horizontalSpeed; + newVelocity.x *= scale; + newVelocity.y *= scale; + } + + // 应用Teleport(除非暂停) + if (!isTeleportPaused) { + creepygirlBox.Teleport({ + angles: { pitch: 0, yaw: newYaw, roll: 0 }, + velocity: newVelocity + }); + } + } + } else if (foundVisiblePlayer && hasTriggeredFireUser1) { + // 重新激活追踪 + isTracking = true; + lastTargetSelectionTime = currentTime; + lastTargetChangeReason = "重新激活追踪"; + } + + // 设置下一次检测 + if (!isStopped) { + Instance.SetNextThink(Instance.GetGameTime() + rechecktime); + } +}); \ No newline at end of file diff --git a/2001/ze_the_curse_of_blackwater/creepygirl_tele.js b/2001/ze_the_curse_of_blackwater/creepygirl_tele.js new file mode 100644 index 0000000..182ce09 --- /dev/null +++ b/2001/ze_the_curse_of_blackwater/creepygirl_tele.js @@ -0,0 +1,52 @@ +import { Instance } from "cs_script/point_script"; + +/** + * 记录每个 activator 的返回点 + * key: Entity + * value: { position: Vector, angles: QAngle } + */ +const records = new Map(); + +/** + * 记录当前位置(Catch) + */ +Instance.OnScriptInput("Catch", ({ activator }) => { + if (!activator || !activator.IsValid()) return; + + records.set(activator, { + position: activator.GetAbsOrigin(), + angles: activator.GetAbsAngles() + }); +}); + +/** + * 返回记录点(Back) + */ +Instance.OnScriptInput("Back", ({ activator }) => { + if (!activator || !activator.IsValid()) return; + + const record = records.get(activator); + if (!record) return; + + activator.Teleport({ + position: record.position, + angles: record.angles, + velocity: { x: 0, y: 0, z: 0 } + }); + + records.delete(activator); +}); + +/** + * 新回合清空(防止跨回合错位) + */ +Instance.OnRoundStart(() => { + records.clear(); +}); + +/** + * 脚本重载清空(Tools Mode 安全) + */ +Instance.OnScriptReload({ + after: () => records.clear() +}); diff --git a/2001/ze_the_curse_of_blackwater/decay_push.js b/2001/ze_the_curse_of_blackwater/decay_push.js new file mode 100644 index 0000000..af35cba --- /dev/null +++ b/2001/ze_the_curse_of_blackwater/decay_push.js @@ -0,0 +1,60 @@ +import { Instance } from "cs_script/point_script"; + +// ---------- 配置 ---------- +const PUSH_FORCE = 600; // 水平推力大小 +const PUSH_UPWARD_FORCE = 5; // 基础向上推力 +const UPWARD_BOOST = 0.4; // 额外向上分量 +const MIN_Z = 0.3; // 最小向上分量,保证玩家被弹起 + +// ---------- 工具函数 ---------- + +// 计算从 from 指向 to 的归一化方向向量 +function calculateDirection(from, to) { + const dx = to.x - from.x; + const dy = to.y - from.y; + const dz = to.z - from.z; + + const distance = Math.sqrt(dx*dx + dy*dy + dz*dz); + if (distance === 0) return { x: Math.random()*2-1, y: Math.random()*2-1, z: 0.8 }; + + return { x: dx/distance, y: dy/distance, z: dz/distance }; +} + +// 计算推力向量 +function calculatePushForce(triggerPos, target) { + const targetPos = target.GetAbsOrigin(); + const direction = calculateDirection(triggerPos, targetPos); + + // 保证有足够向上分量 + direction.z = Math.max(direction.z + UPWARD_BOOST, MIN_Z); + + return { + x: direction.x * PUSH_FORCE, + y: direction.y * PUSH_FORCE, + z: direction.z * PUSH_FORCE + PUSH_UPWARD_FORCE + }; +} + +// ---------- 输入处理 ---------- + +function handleKickInput(inputData) { + const { caller, activator } = inputData; + + if (!caller?.IsValid() || !activator?.IsValid()) return; + + const triggerPos = caller.GetAbsOrigin(); + const pushForce = calculatePushForce(triggerPos, activator); + const currentVelocity = activator.GetAbsVelocity(); + + // 应用新的速度 + activator.Teleport({ + velocity: { + x: currentVelocity.x + pushForce.x, + y: currentVelocity.y + pushForce.y, + z: currentVelocity.z + pushForce.z + } + }); +} + +// ---------- 注册事件 ---------- +Instance.OnScriptInput("Kick", handleKickInput); diff --git a/2001/ze_the_curse_of_blackwater/decayed.js b/2001/ze_the_curse_of_blackwater/decayed.js new file mode 100644 index 0000000..6d5fd26 --- /dev/null +++ b/2001/ze_the_curse_of_blackwater/decayed.js @@ -0,0 +1,393 @@ +import { Instance } from "cs_script/point_script"; + +// ========== 配置参数 ========== +const range = 2048; // 水平检测半径 +const hight = 64; // 垂直检测高度 +const rechecktime = 0.02; // 循环时间 +const retargettime = 8; // 重新寻找目标间隔 +const force = 1500; // 推力大小 +const maxspeed = 235; // 最大移动速度 +const rotaspeed = 240; // 最大旋转角度(度/秒) +const RAISE_HEIGHT = 64; // 视线检测点抬高高度 + +// ========== 状态变量 ========== +let relaySuffix = null; // 批次后缀 +let decayedBox = null; // decayed_ct实体 +let isChecking = false; // 是否正在检测 +let hasTriggeredFireUser1 = false; // 是否已触发FireUser1 +let hasCalculatedHealth = false; // 是否已计算初始血量 +let isTeleportPaused = false; // Teleport是否暂停 +let isStopped = false; // 脚本是否已停止 + +// ========== 追踪相关变量 ========== +let isTracking = false; // 是否正在追踪 +let targetPlayer = null; // 当前追踪目标 +let visibleCTPlayers = []; // 可见CT玩家列表 +let lastTargetSelectionTime = 0; // 上次选择目标时间 +let isTargetVisible = false; // 目标是否可见 +let lastTargetChangeReason = ""; // 上次目标变化原因 + +// ========== 输入处理 ========== + +// start输入:初始化并启动功能 +Instance.OnScriptInput("start", (inputData) => { + if (isStopped) return; + + if (inputData.caller) { + const relayName = inputData.caller.GetEntityName(); + const suffixMatch = relayName.match(/_(\d+)$/); + + if (suffixMatch && suffixMatch[1]) { + relaySuffix = suffixMatch[1]; + + // 查找对应的decayed_ct实体 + const boxName = `decayed_ct_${relaySuffix}`; + decayedBox = Instance.FindEntityByName(boxName); + + if (decayedBox && decayedBox.IsValid()) { + // 重置状态 + hasTriggeredFireUser1 = false; + isTracking = false; + targetPlayer = null; + lastTargetSelectionTime = 0; + lastTargetChangeReason = ""; + isTeleportPaused = false; + + // 计算初始血量(仅一次) + if (!hasCalculatedHealth) { + calculateInitialHealth(); + hasCalculatedHealth = true; + } + + // 启动检测循环 + if (!isChecking) { + isChecking = true; + Instance.SetNextThink(Instance.GetGameTime() + rechecktime); + } + } + } + } +}); + +// pause输入:暂停Teleport功能 +Instance.OnScriptInput("pause", (inputData) => { + if (isStopped || isTeleportPaused) return; + isTeleportPaused = true; +}); + +// unpause输入:恢复Teleport功能 +Instance.OnScriptInput("unpause", (inputData) => { + if (isStopped || !isTeleportPaused) return; + isTeleportPaused = false; +}); + +// stop输入:完全停止脚本 +Instance.OnScriptInput("stop", () => { + if (isStopped) return; + + isStopped = true; // 标记为已停止 + isChecking = false; // 停止检测循环 + isTracking = false; // 停止追踪 + + // 清除所有存储信息 + relaySuffix = null; + decayedBox = null; + hasTriggeredFireUser1 = false; + hasCalculatedHealth = false; + isTeleportPaused = false; + targetPlayer = null; + visibleCTPlayers = []; + lastTargetSelectionTime = 0; + isTargetVisible = false; + lastTargetChangeReason = ""; +}); + +// ========== 功能函数 ========== + +// 计算初始血量并设置到对应的血量计数器 +function calculateInitialHealth() { + // 1. 计算CT玩家数量 + let ctPlayerCount = 0; + const allPlayers = Instance.FindEntitiesByClass("player"); + + for (const player of allPlayers) { + if (player && player.IsValid() && player.IsAlive() && player.GetTeamNumber() === 3) { + ctPlayerCount++; + } + } + + // 2. 获取per_person实体角度总和 + let perPersonAngleSum = 0; + const perPersonEntity = Instance.FindEntityByName("per_person"); + if (perPersonEntity && perPersonEntity.IsValid()) { + const angles = perPersonEntity.GetAbsAngles(); + perPersonAngleSum = angles.pitch + angles.yaw + angles.roll; + } + + // 3. 获取decayed_base_health实体角度总和 + let baseHealthAngleSum = 0; + const baseHealthEntity = Instance.FindEntityByName("decayed_base_health"); + if (baseHealthEntity && baseHealthEntity.IsValid()) { + const angles = baseHealthEntity.GetAbsAngles(); + baseHealthAngleSum = angles.pitch + angles.yaw + angles.roll; + } + + // 4. 计算血量:health = a * b + c + const initialHealth = ctPlayerCount * perPersonAngleSum + baseHealthAngleSum * 10; + + // 5. 设置到对应的血量计数器 + const hpCounterName = `decayed_hp_counter_${relaySuffix}`; + const hpCounterEntity = Instance.FindEntityByName(hpCounterName); + + if (hpCounterEntity && hpCounterEntity.IsValid()) { + Instance.EntFireAtTarget({ + target: hpCounterEntity, + input: "SetValueNoFire", + value: initialHealth + }); + } +} + +// 检查玩家是否在检测范围内 +function isPlayerInRange(player, boxPos) { + if (!player || !player.IsValid() || !player.IsAlive()) { + return false; + } + + const playerPos = player.GetAbsOrigin(); + const dx = playerPos.x - boxPos.x; + const dy = playerPos.y - boxPos.y; + const horizontalDistance = Math.sqrt(dx * dx + dy * dy); + + return horizontalDistance <= range && Math.abs(playerPos.z - boxPos.z) <= hight; +} + +// 检查玩家是否可见(视线检测点抬高64单位) +function isPlayerVisible(player, boxPos) { + // 抬高decayed_ct检测点 + const raisedBoxPos = { + x: boxPos.x, + y: boxPos.y, + z: boxPos.z + RAISE_HEIGHT + }; + + // 抬高玩家视线检测点 + const eyePos = player.GetEyePosition(); + const raisedEyePos = { + x: eyePos.x, + y: eyePos.y, + z: eyePos.z + RAISE_HEIGHT + }; + + const traceResult = Instance.TraceLine({ + start: raisedBoxPos, + end: raisedEyePos, + ignoreEntity: decayedBox + }); + + return !traceResult.didHit || traceResult.hitEntity === player; +} + +// 检查玩家是否为CT阵营 +function isPlayerCT(player) { + return player && player.IsValid() && player.GetTeamNumber() === 3; +} + +// 计算目标方向角度 +function calculateYawToTarget(startPos, targetPos) { + const dx = targetPos.x - startPos.x; + const dy = targetPos.y - startPos.y; + return Math.atan2(dy, dx) * (180 / Math.PI); +} + +// 角度标准化到0-360度 +function normalizeAngle(angle) { + while (angle < 0) angle += 360; + while (angle >= 360) angle -= 360; + return angle; +} + +// 计算角度差 +function angleDifference(current, target) { + let diff = normalizeAngle(target) - normalizeAngle(current); + if (diff > 180) diff -= 360; + if (diff < -180) diff += 360; + return diff; +} + +// 从可见玩家中随机选择目标 +function selectRandomTargetFromVisible() { + if (visibleCTPlayers.length === 0) return null; + const randomIndex = Math.floor(Math.random() * visibleCTPlayers.length); + return visibleCTPlayers[randomIndex]; +} + +// 检查目标有效性 +function checkTargetValid(targetPlayer, boxPos) { + if (!targetPlayer || !targetPlayer.IsValid()) return "目标无效"; + if (!targetPlayer.IsAlive()) return "目标死亡"; + if (!isPlayerCT(targetPlayer)) return "目标阵营变更"; + if (!isPlayerInRange(targetPlayer, boxPos)) return "目标超出范围"; + return null; +} + +// ========== 主循环 ========== +Instance.SetThink(() => { + if (isStopped) return; // 脚本已停止,不执行任何操作 + if (!isChecking || !decayedBox || !decayedBox.IsValid()) return; + + const boxPos = decayedBox.GetAbsOrigin(); + const boxAngles = decayedBox.GetAbsAngles(); + const currentYaw = boxAngles.yaw; + const currentTime = Instance.GetGameTime(); + + // 重置可见玩家列表 + visibleCTPlayers = []; + isTargetVisible = false; + + // 检测所有玩家 + const allPlayers = Instance.FindEntitiesByClass("player"); + let foundVisiblePlayer = false; + + for (const player of allPlayers) { + if (!player.IsValid() || !player.IsAlive() || !isPlayerCT(player)) continue; + + if (isPlayerInRange(player, boxPos)) { + // 检查可见性(使用抬高后的检测点) + const isVisible = isPlayerVisible(player, boxPos); + + if (isVisible) { + foundVisiblePlayer = true; + visibleCTPlayers.push(player); + + // 检查是否为当前目标 + if (targetPlayer && targetPlayer === player) { + isTargetVisible = true; + } + + // 首次发现可见玩家时触发FireUser1并开始追踪 + if (!hasTriggeredFireUser1) { + Instance.EntFireAtTarget({ + target: decayedBox, + input: "FireUser1" + }); + + hasTriggeredFireUser1 = true; + isTracking = true; + lastTargetSelectionTime = currentTime; + lastTargetChangeReason = "首次检测到玩家"; + } + } + } + } + + // 追踪逻辑 + if (isTracking) { + // 检查目标有效性 + const targetInvalidReason = checkTargetValid(targetPlayer, boxPos); + const needsNewTargetByTime = currentTime - lastTargetSelectionTime >= retargettime; + const needsNewTarget = targetInvalidReason !== null || needsNewTargetByTime; + + // 需要重新选择目标 + if (needsNewTarget) { + if (targetInvalidReason) { + lastTargetChangeReason = targetInvalidReason; + } else if (needsNewTargetByTime) { + lastTargetChangeReason = "时间间隔到"; + } + + // 从可见玩家中选择新目标 + if (visibleCTPlayers.length > 0) { + targetPlayer = selectRandomTargetFromVisible(); + lastTargetSelectionTime = currentTime; + isTargetVisible = true; + } else { + // 无可见玩家 + targetPlayer = null; + isTargetVisible = false; + + // 检查是否还有在范围内的CT玩家 + let hasCTPlayersInRange = false; + for (const player of allPlayers) { + if (player.IsValid() && player.IsAlive() && isPlayerCT(player) && + isPlayerInRange(player, boxPos)) { + hasCTPlayersInRange = true; + break; + } + } + + // 无符合条件的CT玩家则停止追踪 + if (!hasCTPlayersInRange) { + isTracking = false; + } + } + } + + // 激活追踪但无目标,尝试选择新目标 + if (!targetPlayer && visibleCTPlayers.length > 0) { + targetPlayer = selectRandomTargetFromVisible(); + lastTargetSelectionTime = currentTime; + isTargetVisible = true; + lastTargetChangeReason = "重新获取目标"; + } + + // 执行追踪(即使目标不可见) + if (targetPlayer && targetPlayer.IsValid() && targetPlayer.IsAlive() && + isPlayerCT(targetPlayer) && isPlayerInRange(targetPlayer, boxPos)) { + + const targetPos = targetPlayer.GetAbsOrigin(); + const targetYaw = calculateYawToTarget(boxPos, targetPos); + const angleDiff = angleDifference(currentYaw, targetYaw); + + // 计算旋转步长 + const maxRotationStep = rotaspeed * rechecktime; + let rotationStep = Math.min(Math.abs(angleDiff), maxRotationStep); + if (angleDiff < 0) rotationStep = -rotationStep; + + // 计算新角度 + const newYaw = normalizeAngle(currentYaw + rotationStep); + const radYaw = newYaw * (Math.PI / 180); + + // 计算推力 + const forwardForce = { + x: Math.cos(radYaw) * force, + y: Math.sin(radYaw) * force, + z: 0 + }; + + // 计算新速度 + const currentVelocity = decayedBox.GetAbsVelocity(); + let newVelocity = { + x: currentVelocity.x + forwardForce.x * rechecktime, + y: currentVelocity.y + forwardForce.y * rechecktime, + z: currentVelocity.z + }; + + // 限制最大速度 + const horizontalSpeed = Math.sqrt(newVelocity.x * newVelocity.x + newVelocity.y * newVelocity.y); + if (horizontalSpeed > maxspeed) { + const scale = maxspeed / horizontalSpeed; + newVelocity.x *= scale; + newVelocity.y *= scale; + } + + // 应用Teleport(除非暂停) + if (!isTeleportPaused) { + decayedBox.Teleport({ + angles: { pitch: 0, yaw: newYaw, roll: 0 }, + velocity: newVelocity + }); + } + } + } else if (foundVisiblePlayer && hasTriggeredFireUser1) { + // 重新激活追踪 + isTracking = true; + lastTargetSelectionTime = currentTime; + lastTargetChangeReason = "重新激活追踪"; + } + + // 设置下一次检测 + if (!isStopped) { + Instance.SetNextThink(Instance.GetGameTime() + rechecktime); + } +}); \ No newline at end of file diff --git a/2001/ze_the_curse_of_blackwater/pincher.js b/2001/ze_the_curse_of_blackwater/pincher.js new file mode 100644 index 0000000..efe5a14 --- /dev/null +++ b/2001/ze_the_curse_of_blackwater/pincher.js @@ -0,0 +1,393 @@ +import { Instance } from "cs_script/point_script"; + +// ========== 配置参数 ========== +const range = 2048; // 水平检测半径 +const hight = 64; // 垂直检测高度 +const rechecktime = 0.02; // 循环时间 +const retargettime = 8; // 重新寻找目标间隔 +const force = 1500; // 推力大小 +const maxspeed = 235; // 最大移动速度 +const rotaspeed = 240; // 最大旋转角度(度/秒) +const RAISE_HEIGHT = 64; // 视线检测点抬高高度 + +// ========== 状态变量 ========== +let relaySuffix = null; // 批次后缀 +let pincherBox = null; // pincher_ct实体 +let isChecking = false; // 是否正在检测 +let hasTriggeredFireUser1 = false; // 是否已触发FireUser1 +let hasCalculatedHealth = false; // 是否已计算初始血量 +let isTeleportPaused = false; // Teleport是否暂停 +let isStopped = false; // 脚本是否已停止 + +// ========== 追踪相关变量 ========== +let isTracking = false; // 是否正在追踪 +let targetPlayer = null; // 当前追踪目标 +let visibleCTPlayers = []; // 可见CT玩家列表 +let lastTargetSelectionTime = 0; // 上次选择目标时间 +let isTargetVisible = false; // 目标是否可见 +let lastTargetChangeReason = ""; // 上次目标变化原因 + +// ========== 输入处理 ========== + +// start输入:初始化并启动功能 +Instance.OnScriptInput("start", (inputData) => { + if (isStopped) return; + + if (inputData.caller) { + const relayName = inputData.caller.GetEntityName(); + const suffixMatch = relayName.match(/_(\d+)$/); + + if (suffixMatch && suffixMatch[1]) { + relaySuffix = suffixMatch[1]; + + // 查找对应的pincher_ct实体 + const boxName = `pincher_ct_${relaySuffix}`; + pincherBox = Instance.FindEntityByName(boxName); + + if (pincherBox && pincherBox.IsValid()) { + // 重置状态 + hasTriggeredFireUser1 = false; + isTracking = false; + targetPlayer = null; + lastTargetSelectionTime = 0; + lastTargetChangeReason = ""; + isTeleportPaused = false; + + // 计算初始血量(仅一次) + if (!hasCalculatedHealth) { + calculateInitialHealth(); + hasCalculatedHealth = true; + } + + // 启动检测循环 + if (!isChecking) { + isChecking = true; + Instance.SetNextThink(Instance.GetGameTime() + rechecktime); + } + } + } + } +}); + +// pause输入:暂停Teleport功能 +Instance.OnScriptInput("pause", (inputData) => { + if (isStopped || isTeleportPaused) return; + isTeleportPaused = true; +}); + +// unpause输入:恢复Teleport功能 +Instance.OnScriptInput("unpause", (inputData) => { + if (isStopped || !isTeleportPaused) return; + isTeleportPaused = false; +}); + +// stop输入:完全停止脚本 +Instance.OnScriptInput("stop", () => { + if (isStopped) return; + + isStopped = true; // 标记为已停止 + isChecking = false; // 停止检测循环 + isTracking = false; // 停止追踪 + + // 清除所有存储信息 + relaySuffix = null; + pincherBox = null; + hasTriggeredFireUser1 = false; + hasCalculatedHealth = false; + isTeleportPaused = false; + targetPlayer = null; + visibleCTPlayers = []; + lastTargetSelectionTime = 0; + isTargetVisible = false; + lastTargetChangeReason = ""; +}); + +// ========== 功能函数 ========== + +// 计算初始血量并设置到对应的血量计数器 +function calculateInitialHealth() { + // 1. 计算CT玩家数量 + let ctPlayerCount = 0; + const allPlayers = Instance.FindEntitiesByClass("player"); + + for (const player of allPlayers) { + if (player && player.IsValid() && player.IsAlive() && player.GetTeamNumber() === 3) { + ctPlayerCount++; + } + } + + // 2. 获取per_person实体角度总和 + let perPersonAngleSum = 0; + const perPersonEntity = Instance.FindEntityByName("per_person"); + if (perPersonEntity && perPersonEntity.IsValid()) { + const angles = perPersonEntity.GetAbsAngles(); + perPersonAngleSum = angles.pitch + angles.yaw + angles.roll; + } + + // 3. 获取pincher_base_health实体角度总和 + let baseHealthAngleSum = 0; + const baseHealthEntity = Instance.FindEntityByName("pincher_base_health"); + if (baseHealthEntity && baseHealthEntity.IsValid()) { + const angles = baseHealthEntity.GetAbsAngles(); + baseHealthAngleSum = angles.pitch + angles.yaw + angles.roll; + } + + // 4. 计算血量:health = a * b + c + const initialHealth = ctPlayerCount * perPersonAngleSum + baseHealthAngleSum * 10; + + // 5. 设置到对应的血量计数器 + const hpCounterName = `pincher_hp_counter_${relaySuffix}`; + const hpCounterEntity = Instance.FindEntityByName(hpCounterName); + + if (hpCounterEntity && hpCounterEntity.IsValid()) { + Instance.EntFireAtTarget({ + target: hpCounterEntity, + input: "SetValueNoFire", + value: initialHealth + }); + } +} + +// 检查玩家是否在检测范围内 +function isPlayerInRange(player, boxPos) { + if (!player || !player.IsValid() || !player.IsAlive()) { + return false; + } + + const playerPos = player.GetAbsOrigin(); + const dx = playerPos.x - boxPos.x; + const dy = playerPos.y - boxPos.y; + const horizontalDistance = Math.sqrt(dx * dx + dy * dy); + + return horizontalDistance <= range && Math.abs(playerPos.z - boxPos.z) <= hight; +} + +// 检查玩家是否可见(视线检测点抬高64单位) +function isPlayerVisible(player, boxPos) { + // 抬高pincher_ct检测点 + const raisedBoxPos = { + x: boxPos.x, + y: boxPos.y, + z: boxPos.z + RAISE_HEIGHT + }; + + // 抬高玩家视线检测点 + const eyePos = player.GetEyePosition(); + const raisedEyePos = { + x: eyePos.x, + y: eyePos.y, + z: eyePos.z + RAISE_HEIGHT + }; + + const traceResult = Instance.TraceLine({ + start: raisedBoxPos, + end: raisedEyePos, + ignoreEntity: pincherBox + }); + + return !traceResult.didHit || traceResult.hitEntity === player; +} + +// 检查玩家是否为CT阵营 +function isPlayerCT(player) { + return player && player.IsValid() && player.GetTeamNumber() === 3; +} + +// 计算目标方向角度 +function calculateYawToTarget(startPos, targetPos) { + const dx = targetPos.x - startPos.x; + const dy = targetPos.y - startPos.y; + return Math.atan2(dy, dx) * (180 / Math.PI); +} + +// 角度标准化到0-360度 +function normalizeAngle(angle) { + while (angle < 0) angle += 360; + while (angle >= 360) angle -= 360; + return angle; +} + +// 计算角度差 +function angleDifference(current, target) { + let diff = normalizeAngle(target) - normalizeAngle(current); + if (diff > 180) diff -= 360; + if (diff < -180) diff += 360; + return diff; +} + +// 从可见玩家中随机选择目标 +function selectRandomTargetFromVisible() { + if (visibleCTPlayers.length === 0) return null; + const randomIndex = Math.floor(Math.random() * visibleCTPlayers.length); + return visibleCTPlayers[randomIndex]; +} + +// 检查目标有效性 +function checkTargetValid(targetPlayer, boxPos) { + if (!targetPlayer || !targetPlayer.IsValid()) return "目标无效"; + if (!targetPlayer.IsAlive()) return "目标死亡"; + if (!isPlayerCT(targetPlayer)) return "目标阵营变更"; + if (!isPlayerInRange(targetPlayer, boxPos)) return "目标超出范围"; + return null; +} + +// ========== 主循环 ========== +Instance.SetThink(() => { + if (isStopped) return; // 脚本已停止,不执行任何操作 + if (!isChecking || !pincherBox || !pincherBox.IsValid()) return; + + const boxPos = pincherBox.GetAbsOrigin(); + const boxAngles = pincherBox.GetAbsAngles(); + const currentYaw = boxAngles.yaw; + const currentTime = Instance.GetGameTime(); + + // 重置可见玩家列表 + visibleCTPlayers = []; + isTargetVisible = false; + + // 检测所有玩家 + const allPlayers = Instance.FindEntitiesByClass("player"); + let foundVisiblePlayer = false; + + for (const player of allPlayers) { + if (!player.IsValid() || !player.IsAlive() || !isPlayerCT(player)) continue; + + if (isPlayerInRange(player, boxPos)) { + // 检查可见性(使用抬高后的检测点) + const isVisible = isPlayerVisible(player, boxPos); + + if (isVisible) { + foundVisiblePlayer = true; + visibleCTPlayers.push(player); + + // 检查是否为当前目标 + if (targetPlayer && targetPlayer === player) { + isTargetVisible = true; + } + + // 首次发现可见玩家时触发FireUser1并开始追踪 + if (!hasTriggeredFireUser1) { + Instance.EntFireAtTarget({ + target: pincherBox, + input: "FireUser1" + }); + + hasTriggeredFireUser1 = true; + isTracking = true; + lastTargetSelectionTime = currentTime; + lastTargetChangeReason = "首次检测到玩家"; + } + } + } + } + + // 追踪逻辑 + if (isTracking) { + // 检查目标有效性 + const targetInvalidReason = checkTargetValid(targetPlayer, boxPos); + const needsNewTargetByTime = currentTime - lastTargetSelectionTime >= retargettime; + const needsNewTarget = targetInvalidReason !== null || needsNewTargetByTime; + + // 需要重新选择目标 + if (needsNewTarget) { + if (targetInvalidReason) { + lastTargetChangeReason = targetInvalidReason; + } else if (needsNewTargetByTime) { + lastTargetChangeReason = "时间间隔到"; + } + + // 从可见玩家中选择新目标 + if (visibleCTPlayers.length > 0) { + targetPlayer = selectRandomTargetFromVisible(); + lastTargetSelectionTime = currentTime; + isTargetVisible = true; + } else { + // 无可见玩家 + targetPlayer = null; + isTargetVisible = false; + + // 检查是否还有在范围内的CT玩家 + let hasCTPlayersInRange = false; + for (const player of allPlayers) { + if (player.IsValid() && player.IsAlive() && isPlayerCT(player) && + isPlayerInRange(player, boxPos)) { + hasCTPlayersInRange = true; + break; + } + } + + // 无符合条件的CT玩家则停止追踪 + if (!hasCTPlayersInRange) { + isTracking = false; + } + } + } + + // 激活追踪但无目标,尝试选择新目标 + if (!targetPlayer && visibleCTPlayers.length > 0) { + targetPlayer = selectRandomTargetFromVisible(); + lastTargetSelectionTime = currentTime; + isTargetVisible = true; + lastTargetChangeReason = "重新获取目标"; + } + + // 执行追踪(即使目标不可见) + if (targetPlayer && targetPlayer.IsValid() && targetPlayer.IsAlive() && + isPlayerCT(targetPlayer) && isPlayerInRange(targetPlayer, boxPos)) { + + const targetPos = targetPlayer.GetAbsOrigin(); + const targetYaw = calculateYawToTarget(boxPos, targetPos); + const angleDiff = angleDifference(currentYaw, targetYaw); + + // 计算旋转步长 + const maxRotationStep = rotaspeed * rechecktime; + let rotationStep = Math.min(Math.abs(angleDiff), maxRotationStep); + if (angleDiff < 0) rotationStep = -rotationStep; + + // 计算新角度 + const newYaw = normalizeAngle(currentYaw + rotationStep); + const radYaw = newYaw * (Math.PI / 180); + + // 计算推力 + const forwardForce = { + x: Math.cos(radYaw) * force, + y: Math.sin(radYaw) * force, + z: 0 + }; + + // 计算新速度 + const currentVelocity = pincherBox.GetAbsVelocity(); + let newVelocity = { + x: currentVelocity.x + forwardForce.x * rechecktime, + y: currentVelocity.y + forwardForce.y * rechecktime, + z: currentVelocity.z + }; + + // 限制最大速度 + const horizontalSpeed = Math.sqrt(newVelocity.x * newVelocity.x + newVelocity.y * newVelocity.y); + if (horizontalSpeed > maxspeed) { + const scale = maxspeed / horizontalSpeed; + newVelocity.x *= scale; + newVelocity.y *= scale; + } + + // 应用Teleport(除非暂停) + if (!isTeleportPaused) { + pincherBox.Teleport({ + angles: { pitch: 0, yaw: newYaw, roll: 0 }, + velocity: newVelocity + }); + } + } + } else if (foundVisiblePlayer && hasTriggeredFireUser1) { + // 重新激活追踪 + isTracking = true; + lastTargetSelectionTime = currentTime; + lastTargetChangeReason = "重新激活追踪"; + } + + // 设置下一次检测 + if (!isStopped) { + Instance.SetNextThink(Instance.GetGameTime() + rechecktime); + } +}); \ No newline at end of file