diff --git a/assets/failsafes/sounds/Player Failsafe.ogg b/assets/failsafes/sounds/Player Failsafe.ogg new file mode 100644 index 00000000..620238be Binary files /dev/null and b/assets/failsafes/sounds/Player Failsafe.ogg differ diff --git a/assets/failsafes/sounds/Tave Check.ogg b/assets/failsafes/sounds/Tave Check.ogg new file mode 100644 index 00000000..400642f8 Binary files /dev/null and b/assets/failsafes/sounds/Tave Check.ogg differ diff --git a/assets/failsafes/sounds/metal Pipe.ogg b/assets/failsafes/sounds/metal Pipe.ogg new file mode 100644 index 00000000..92ff28de Binary files /dev/null and b/assets/failsafes/sounds/metal Pipe.ogg differ diff --git a/failsafes/AlertUtils.js b/failsafes/AlertUtils.js index bf0323bc..5b16ad49 100644 --- a/failsafes/AlertUtils.js +++ b/failsafes/AlertUtils.js @@ -1,33 +1,21 @@ import { drawRect, drawText } from '../gui/Utils'; +import { getSetting } from '../gui/GuiSave'; import { Chat } from '../utils/Chat'; -import { File, globalAssetsDir } from '../utils/Constants'; -import { Utils } from '../utils/Utils'; -import FailsafeUtils from './FailsafeUtils'; +import { getSeverity } from './FailsafeUtils'; -let failsafeSound = 'Tave Check.wav'; - -const AudioSystem = javax.sound.sampled.AudioSystem; -const FloatControl = javax.sound.sampled.FloatControl; - -// todo -// touchen up colours rn they ugly -// touch up code -// rewrite some stuff! -// allow edit of failsafe sound +let failsafeSound = 'Tave Check.ogg'; +const playerNotificationSound = 'Player Failsafe.ogg'; class AlertUtilsClass { constructor() { - this.clip = null; - this.audioStream = null; - this.gainControl = null; + this.sound = null; this.savedSound = null; this.isAlerting = false; - this.cancelKeyBind = null; this.cancelKey = null; this.render = null; - this.tracker = null; + this.cancelHandler = null; this._makeFailsafeKeybind(); @@ -36,10 +24,13 @@ class AlertUtilsClass { }).setName('trigger'); } - /** - * Combines all internal methods to create a failsafe alert - */ - triggerReaction() { + triggerReaction(severity = 'high') { + const next = getSeverity(severity); + if (this.isAlerting && next.rank < getSeverity(this.alertSeverity).rank) return; + + this.alertSeverity = severity; + this.alertLine = next.line; + this.alertColor = next.alertColor; if (this.isAlerting) return; Chat.messageFailsafe('Suspicious activity detected, reaction occuring!'); @@ -49,7 +40,6 @@ class AlertUtilsClass { this.playSound(); this._grabWindowOnFailsafe(); - const line1 = 'V5 BELIEVES YOU HAVE BEEN MACRO CHECKED!'; const key = this.cancelKey; const line2Start = 'PRESS '; const line2End = ' TO DISABLE THE REACTION'; @@ -60,11 +50,12 @@ class AlertUtilsClass { const fontSize = 20; const lineSpacing = 8; const yOffset = 100; - const redColor = Math.trunc(0xffff0000); // change this - const highlightColor = Math.trunc(0xffffffff); // this too + const highlightColor = 0xffffffff; this.render = register('renderOverlay', () => { const scale = fontSize / 10; + const line1 = this.alertLine; + const redColor = this.alertColor; const x1 = screenW / 2 - (Renderer.getStringWidth(line1) * scale) / 2; const totalLine2Width = (Renderer.getStringWidth(line2Start) + Renderer.getStringWidth(key) + Renderer.getStringWidth(line2End)) * scale; let currentX2 = screenW / 2 - totalLine2Width / 2; @@ -86,96 +77,58 @@ class AlertUtilsClass { }); } - /** - * Disables the reaction & nulls all registers included - */ + setCancelHandler(callback) { + this.cancelHandler = typeof callback === 'function' ? callback : null; + } + disableReaction() { this.isAlerting = false; this.stopSound(); + const handler = this.cancelHandler; + this.cancelHandler = null; + if (handler) { + try { + handler(); + } catch (e) { + console.error('V5 Caught error' + e + e.stack); + } + } if (this.render) { this.render.unregister(); this.render = null; } + } - if (this.tracker) { - this.tracker.unregister(); - this.tracker = null; + playSound(soundName = failsafeSound) { + if (!(getSetting('Failsafes', 'Play sound on check') ?? true)) return; + + try { + if (!this.sound || this.savedSound !== soundName) { + this.sound?.destroy(); + this.sound = new Sound({ source: `failsafes/sounds/${soundName}` }); + this.savedSound = soundName; + } + this.sound.rewind(); + } catch (e) { + this.sound = null; + console.error('V5 Caught error' + e + e.stack); } } - /** - * Plays a sound if the player has the setting toggled - */ - playSound() { - if (!FailsafeUtils.getFailsafeSettings('Play sound on check').playSoundOnCheck) return; - const currentSound = failsafeSound; - if (!this.clip || this.savedSound !== currentSound) this._loadsoundFile(); - - if (this.clip) { - this.clip.stop(); - this.clip.setFramePosition(0); - this.clip.start(); - } + playQuietNotification() { + if (this.isAlerting) return; + this.playSound(playerNotificationSound); } - /** - * Stops any sounds from playing - */ stopSound() { - if (this.clip && this.clip.isRunning()) this.clip.stop(); + if (this.sound && World.isLoaded()) this.sound.stop(); } setFailsafeSound(fileName) { failsafeSound = fileName; } - /** - * Loads a sound file using Java methods - */ - _loadsoundFile() { - if (this.clip) { - try { - this.clip.stop(); - this.clip.close(); - } catch (e) { - console.error('V5 Caught error' + e + e.stack); - } - this.clip = null; - } - - if (this.audioStream) { - try { - this.audioStream.close(); - } catch (e) { - console.error('V5 Caught error' + e + e.stack); - } - this.audioStream = null; - } - - const currentSound = failsafeSound; - this.savedSound = currentSound || 'Tave Check.wav'; - if ((currentSound || '').includes('undefined')) this.savedSound = 'Tave Check.wav'; - - this.soundFile = new File(globalAssetsDir, `failsafes/sounds/${this.savedSound}`); - if (!this.soundFile.exists()) return; - - try { - this.audioStream = AudioSystem.getAudioInputStream(this.soundFile); - this.clip = AudioSystem.getClip(); - this.clip.open(this.audioStream); - if (this.clip.isControlSupported(FloatControl.Type.MASTER_GAIN)) { - this.gainControl = this.clip.getControl(FloatControl.Type.MASTER_GAIN); - } - } catch (e) { - this.clip = null; - console.error('V5 Caught error' + e + e.stack); - } - } - - /** - * Uses NVG to draw a overlay over the whole screen - */ _renderAlertScreen() { if (Client.isInChat()) return; try { @@ -187,7 +140,7 @@ class AlertUtilsClass { y: 0, width: Renderer.screen.getWidth(), height: Renderer.screen.getHeight(), - color: Math.trunc((120 << 24) | (255 << 16) | (0 << 8)), // change this too pls + color: 0x78ff0000, }); NVG.restore(); @@ -202,20 +155,12 @@ class AlertUtilsClass { } } - /** - * Creates a keybind for canceling the reaction - */ _makeFailsafeKeybind() { const keyName = 'Cancel Reaction'; - const existingKeybinds = Utils.getConfigFile('keybinds.json') || {}; - let savedKeycode = existingKeybinds[keyName]; - - if (savedKeycode === undefined || savedKeycode === 0 || savedKeycode === -1 || savedKeycode === 75) savedKeycode = Keyboard.KEY_K; - - this.cancelKey = Keyboard.getKeyName(savedKeycode); - this.cancelKeyBind = new KeyBind(keyName, savedKeycode, 'v5_core'); + const cancelKeyBind = new KeyBind(keyName, Keyboard.KEY_K, 'v5_core'); + this.cancelKey = Keyboard.getKeyName(cancelKeyBind.getKeyCode()); - this.cancelKeyBind.registerKeyPress(() => { + cancelKeyBind.registerKeyPress(() => { if (!this.isAlerting) return; Chat.messageFailsafe('Reaction disabled due to keybind being pressed'); this.disableReaction(); @@ -223,15 +168,11 @@ class AlertUtilsClass { register('gameUnload', () => { this.disableReaction(); - let allKeybinds = Utils.getConfigFile('keybinds.json') || {}; - allKeybinds[keyName] = this.cancelKeyBind.getKeyCode(); - Utils.writeConfigFile('keybinds.json', allKeybinds); + if (this.sound && World.isLoaded()) this.sound.destroy(); + this.sound = null; }); } - /** - * Uses GLFW to grab the window on a failsafe if they have the setting toggled (WIP) - */ _grabWindowOnFailsafe() { try { const GLFW = org.lwjgl.glfw.GLFW; diff --git a/failsafes/Failsafe.js b/failsafes/Failsafe.js index 3cc9feab..388af3ea 100644 --- a/failsafes/Failsafe.js +++ b/failsafes/Failsafe.js @@ -1,58 +1,58 @@ -import { manager } from '../utils/SkyblockEvents'; import { MacroState } from '../utils/MacroState'; +import { manager } from '../utils/SkyblockEvents'; import { finiteNumber } from '../utils/NumberUtils'; + +const DEFAULT_DISABLE_MS = 3000; +const PICKONIMBUS_DISABLE_MS = 5000; +let globalDisabledUntil = 0; + +const disableAll = (durationMs) => { + globalDisabledUntil = Math.max(globalDisabledUntil, Date.now() + durationMs); +}; + +register('worldLoad', () => disableAll(DEFAULT_DISABLE_MS)); +['serverchange', 'death', 'warp'].forEach((event) => manager.subscribe(event, () => disableAll(1000))); +manager.subscribe('limbo', () => disableAll(DEFAULT_DISABLE_MS)); +manager.subscribe('pickonimbusbroke', () => disableAll(PICKONIMBUS_DISABLE_MS)); + export class Failsafe { - registered = false; - disabled = false; _disabledUntil = 0; - _disabledTimer = null; - constructor() { - this._registerListeners(); + get disabled() { + return Date.now() < this._getDisabledUntil(); } - shouldTrigger() { - return true; - } isActive() { return MacroState.isFailsafeMacroRunning(); } - onTrigger() {} + reset() { - this.disabled = false; this._disabledUntil = 0; - if (this._disabledTimer) { - clearTimeout(this._disabledTimer); - this._disabledTimer = null; - } } - _setDisabled(durationMs) { - const now = Date.now(); - const end = now + durationMs; - - if (end <= this._disabledUntil && this.disabled) return; + _scheduleTrigger(fireFn, settings, validateFn = null) { + const scheduledAt = Date.now(); + const delay = this._getReactionDelay(settings); - this._disabledUntil = end; - this.disabled = true; + setTimeout(() => { + if (this.disabled || scheduledAt < this._getDisabledUntil()) return; + if (!MacroState.isFailsafeMacroRunning()) return; + if (validateFn && !validateFn()) return; + fireFn(); + }, delay); + } - if (this._disabledTimer) clearTimeout(this._disabledTimer); + _reportFailsafe(payload) { + const FailsafeManager = require('./FailsafeManager').default; + FailsafeManager.report(payload); + } - this._disabledTimer = setTimeout(() => { - if (Date.now() >= this._disabledUntil) { - this.disabled = false; - this._disabledTimer = null; - } - }, durationMs); + _setDisabled(durationMs) { + this._disabledUntil = Math.max(this._disabledUntil, Date.now() + durationMs); } - _registerListeners() { - if (this.registered) return; - this.registered = true; - register('worldLoad', () => { - this._setDisabled(1000); - }); - ['serverchange', 'death', 'warp'].forEach((event) => manager.subscribe(event, () => this._setDisabled(1000))); + _getDisabledUntil() { + return Math.max(globalDisabledUntil, this._disabledUntil); } _getReactionDelay(settings) { diff --git a/failsafes/FailsafeManager.js b/failsafes/FailsafeManager.js index a99f0323..6db7890e 100644 --- a/failsafes/FailsafeManager.js +++ b/failsafes/FailsafeManager.js @@ -1,18 +1,54 @@ -import ChatMentionFailsafe from './impl/ChatMentionFailsafe'; -import PlayerGriefFailsafe from './impl/PlayerGriefFailsafe'; -import RotationFailsafe from './impl/RotationFailsafe'; -import SlotChangeFailsafe from './impl/SlotChangeFailsafe'; -import TeleportFailsafe from './impl/TeleportFailsafe'; -import VelocityFailsafe from './impl/VelocityFailsafe'; - -// just keep it here to import all the failsafes to loader :) +import './impl/ChatMentionFailsafe'; +import './impl/BlockFailsafe'; +import './impl/PlayerGriefFailsafe'; +import './impl/RotationFailsafe'; +import './impl/SlotChangeFailsafe'; +import './impl/SmartFailsafe'; +import './impl/TeleportFailsafe'; +import './impl/VelocityFailsafe'; +import { Chat } from '../utils/Chat'; +import { MacroState } from '../utils/MacroState'; +import { AlertUtils } from './AlertUtils'; +import FailsafeUtils, { getSeverity } from './FailsafeUtils'; +import { ResponseBot } from './ResponseBot'; + class FailsafeManager { constructor() { - this.failsafes = [ChatMentionFailsafe, PlayerGriefFailsafe, RotationFailsafe, SlotChangeFailsafe, TeleportFailsafe, VelocityFailsafe]; + this.lastReportAt = {}; } - getFailsafes() { - return this.failsafes; + report(payload) { + const { type, severity, description, pressure, chat } = payload; + const dedupeKey = `${type}:${severity}`; + const now = Date.now(); + + if (now - (this.lastReportAt[dedupeKey] || 0) < 750) return; + this.lastReportAt[dedupeKey] = now; + + if (pressure) FailsafeUtils.incrementFailsafeIntensity(pressure); + const lines = Array.isArray(chat) ? chat : [chat]; + lines.forEach((line, idx) => Chat.messageFailsafe(line, idx === lines.length - 1)); + const severityRank = getSeverity(severity).rank; + if (severityRank >= getSeverity('medium').rank) FailsafeUtils.sendFailsafeEmbed(type, severity, description); + + const settings = FailsafeUtils.getGlobalSettings(); + if (severityRank < getSeverity(settings.minAlertSeverity).rank) return; + + if (type === 'Player Grief') { + AlertUtils.playQuietNotification(); + return; + } + + AlertUtils.triggerReaction(severity); + + if (!ResponseBot.isRunning) { + const pausedMacros = settings.pauseMacroOnFailsafe ? MacroState.getEnabledMacros().map((name) => MacroState.getModule(name)) : []; + pausedMacros.forEach((module) => module.requestToggleFromUser()); + ResponseBot.run(() => { + AlertUtils.disableReaction(); + pausedMacros.filter((module) => !module.enabled).forEach((module) => module.requestToggleFromUser()); + }); + } } } diff --git a/failsafes/FailsafeUtils.js b/failsafes/FailsafeUtils.js index b924e4e9..f7c1ec1e 100644 --- a/failsafes/FailsafeUtils.js +++ b/failsafes/FailsafeUtils.js @@ -1,98 +1,51 @@ -import { V5ConfigFile } from '../utils/Constants'; +import { getSetting } from '../gui/GuiSave'; import { finiteNumber } from '../utils/NumberUtils'; +import { PRESETS } from './SensitivityPresets'; + +const SEVERITIES = { + low: { rank: 1, color: 0x00ff00, alertColor: 0xff00ff00, line: 'LOW SUSPICIOUS ACTIVITY DETECTED!' }, + medium: { rank: 2, color: 0xffff00, alertColor: 0xffffff00, line: 'SUSPICIOUS ACTIVITY DETECTED!' }, + high: { rank: 3, color: 0xff8000, alertColor: 0xffff5500, line: 'YOU MAY HAVE BEEN MACRO CHECKED!' }, + 'very high': { rank: 4, color: 0xff0000, alertColor: 0xffff0000, line: 'YOU ARE BEING MACRO CHECKED!' }, +}; + +export const getSeverity = (severity) => SEVERITIES[String(severity || 'high').toLowerCase()] || SEVERITIES.high; const DEFAULT_FAILSAFE_SETTINGS = { isEnabled: true, FailsafeReactionTime: 600, playerProximityDistance: 3, pingOnCheck: 'Ping', - playSoundOnCheck: true, + sensitivityPreset: 'Normal', + pauseMacroOnFailsafe: true, + minAlertSeverity: 'high', + chatMentionHighWords: 'wdr, report, cheat, hack, exploit, macro', + chatMentionMediumWords: '', + playerGriefWhitelist: '', }; class FailsafeUtils { constructor() { this.failsafeIntensity = 0; - this._cache = { - expiresAt: 0, - lastModified: -1, - config: {}, - hasConfig: false, - normalized: null, - }; - this._utils = null; + register('step', () => { + this.failsafeIntensity = Math.max(0, this.failsafeIntensity * 0.92); + if (this.failsafeIntensity < 0.1) this.failsafeIntensity = 0; + }).setDelay(1); } - _getConfig() { - const now = Date.now(); - const lastModified = V5ConfigFile.exists() ? V5ConfigFile.lastModified() : -1; - const cacheValid = now < this._cache.expiresAt && this._cache.lastModified === lastModified; - if (cacheValid) { - return this._cache.config; - } - - if (!this._utils) this._utils = require('../utils/Utils').Utils; - const config = this._utils.getConfigFile('config.json'); - - this._cache.expiresAt = now + 250; - this._cache.lastModified = lastModified; - this._cache.config = config; - this._cache.hasConfig = !!config && Object.keys(config).length > 0; - this._cache.normalized = null; - - return config; + _getSetting(name, fallback) { + return getSetting('Failsafes', name) ?? fallback; } - _normalizeFailsafeConfig(failsafesConfig) { - if (this._cache.normalized) return this._cache.normalized; - - const enabledMap = {}; - const enabledList = failsafesConfig['Enabled Failsafes']; - if (Array.isArray(enabledList)) { - for (const entry of enabledList) { - if (!entry || !entry.name) continue; - enabledMap[entry.name] = !!entry.enabled; - } - } - - const pingConfig = failsafesConfig['Discord ping on Check']; - let pingOnCheckValue = DEFAULT_FAILSAFE_SETTINGS.pingOnCheck; - - if (Array.isArray(pingConfig)) { - for (const option of pingConfig) { - if (option?.enabled) { - pingOnCheckValue = option.name ?? DEFAULT_FAILSAFE_SETTINGS.pingOnCheck; - break; - } - } - } else if (typeof pingConfig === 'boolean') { - pingOnCheckValue = pingConfig ? 'Ping' : 'None'; - } else { - pingOnCheckValue = pingConfig ?? DEFAULT_FAILSAFE_SETTINGS.pingOnCheck; - } - - const normalized = { - enabledMap, - rawEnabledList: enabledList, - reactionInput: failsafesConfig['Failsafe Detection Delay (ms)'] ?? DEFAULT_FAILSAFE_SETTINGS.FailsafeReactionTime, - playerProximityDistance: failsafesConfig['Player Proximity Distance'] ?? DEFAULT_FAILSAFE_SETTINGS.playerProximityDistance, - playSoundOnCheck: failsafesConfig['Play sound on check'] ?? DEFAULT_FAILSAFE_SETTINGS.playSoundOnCheck, - pingOnCheck: pingOnCheckValue, - }; - - this._cache.normalized = normalized; - return normalized; + _getSelectedSetting(name, fallback) { + const options = this._getSetting(name, []); + return (Array.isArray(options) && options.find((option) => option?.enabled)?.name) || fallback; } getFailsafeSettings(name) { - const config = this._getConfig(); - - if (!config || !config['Failsafes']) { - return DEFAULT_FAILSAFE_SETTINGS; - } - - const normalized = this._normalizeFailsafeConfig(config['Failsafes']); - const reactionInput = normalized.reactionInput; + const enabled = this._getSetting('Enabled Failsafes', null); + const reactionInput = this._getSetting('Failsafe Detection Delay (ms)', DEFAULT_FAILSAFE_SETTINGS.FailsafeReactionTime); let reactionTime = DEFAULT_FAILSAFE_SETTINGS.FailsafeReactionTime; if (typeof reactionInput === 'object' && reactionInput.low !== undefined) { @@ -104,58 +57,52 @@ class FailsafeUtils { reactionTime = finiteNumber(reactionInput, reactionTime); } - const hasEnabledList = Array.isArray(normalized.rawEnabledList); - const isEnabled = hasEnabledList - ? (normalized.enabledMap[name] ?? false) - : (config['Failsafes'][`${name} Failsafe`] ?? DEFAULT_FAILSAFE_SETTINGS.isEnabled); - return { - isEnabled: isEnabled, + isEnabled: Array.isArray(enabled) ? enabled.some((option) => option?.name === name && option.enabled) : DEFAULT_FAILSAFE_SETTINGS.isEnabled, FailsafeReactionTime: reactionTime, - playerProximityDistance: normalized.playerProximityDistance, - pingOnCheck: normalized.pingOnCheck, - playSoundOnCheck: normalized.playSoundOnCheck, + playerProximityDistance: this._getSetting('Player Proximity Distance', DEFAULT_FAILSAFE_SETTINGS.playerProximityDistance), + chatMentionHighWords: this._getSetting('Chat Mention - High Severity Words', DEFAULT_FAILSAFE_SETTINGS.chatMentionHighWords), + chatMentionMediumWords: this._getSetting('Chat Mention - Medium Severity Words', DEFAULT_FAILSAFE_SETTINGS.chatMentionMediumWords), + playerGriefWhitelist: this._getSetting('Player Grief - Whitelist', DEFAULT_FAILSAFE_SETTINGS.playerGriefWhitelist), }; } - sendFailsafeEmbed(type, severity, description, color) { - const { Webhook } = require('../utils/Webhooks'); + getGlobalSettings() { + return { + sensitivityPreset: this._getSelectedSetting('Failsafe Sensitivity', DEFAULT_FAILSAFE_SETTINGS.sensitivityPreset), + pauseMacroOnFailsafe: this._getSetting('Pause macro on failsafe', DEFAULT_FAILSAFE_SETTINGS.pauseMacroOnFailsafe), + minAlertSeverity: this._getSelectedSetting('Min severity to fire alert overlay', DEFAULT_FAILSAFE_SETTINGS.minAlertSeverity), + }; + } - const pingOnCheckValue = this.getFailsafeSettings(type).pingOnCheck; - - if (pingOnCheckValue === 'Ping' || pingOnCheckValue === 'Embed Only') { - Webhook.sendFailsafeEmbed( - [ - { - title: `**[${severity.toUpperCase()}]** ${type} Failsafe Triggered!`, - description: `${description}`, - color: color, - footer: { text: `V5 Failsafes` }, - timestamp: new Date().toISOString(), - }, - ], - pingOnCheckValue === 'Ping' - ); - } else if (pingOnCheckValue === 'Ping & Screenshot' || pingOnCheckValue === 'Screenshot Only') { - Client.scheduleTask(5, () => - Webhook.sendFailsafeScreenshot( - `**[${severity.toUpperCase()}]** ${type} Failsafe Triggered!`, - description, - color, - `V5 Failsafes`, - pingOnCheckValue === 'Ping & Screenshot' - ) - ); + getSensitivityPreset() { + return PRESETS[this.getGlobalSettings().sensitivityPreset] || PRESETS.Normal; + } + + sendFailsafeEmbed(type, severity, description) { + const { Webhook } = require('../utils/Webhooks'); + const mode = this._getSelectedSetting('Discord ping on Check', DEFAULT_FAILSAFE_SETTINGS.pingOnCheck); + if (mode === 'None') return; + + const ping = mode.startsWith('Ping'); + const title = `**[${severity.toUpperCase()}]** ${type} Failsafe Triggered!`; + const color = getSeverity(severity).color; + if (mode.includes('Screenshot')) { + Client.scheduleTask(5, () => Webhook.sendFailsafeScreenshot(title, description, color, 'V5 Failsafes', ping)); + return; } + + Webhook.sendFailsafeEmbed([{ title, description, color, footer: { text: 'V5 Failsafes' }, timestamp: new Date().toISOString() }], ping); } incrementFailsafeIntensity(amt) { - this.failsafeIntensity += amt; - setTimeout(() => (this.failsafeIntensity -= amt / 10), 1000); + const amount = Number(amt); + if (!Number.isFinite(amount)) return; + this.failsafeIntensity = Math.max(0, Math.min(1000, this.failsafeIntensity + amount)); } getIntensity() { - return this.failsafeIntensity; + return Math.max(0, Math.min(1000, Math.round(this.failsafeIntensity))); } } diff --git a/failsafes/ResponseBot.js b/failsafes/ResponseBot.js new file mode 100644 index 00000000..0d4a5cdc --- /dev/null +++ b/failsafes/ResponseBot.js @@ -0,0 +1,79 @@ +import { MathUtils } from '../utils/Math'; +import { Utils } from '../utils/Utils'; +import { Keybind } from '../utils/player/Keybinding'; +import { Rotations } from '../utils/player/Rotations'; +import { AlertUtils } from './AlertUtils'; + +class ResponseBotClass { + constructor() { + this.isRunning = false; + } + + run(onComplete) { + this.duration = 12000; + this.onComplete = typeof onComplete === 'function' ? onComplete : null; + this.nextActionAt = 0; + this.actionInterval = this.duration / Utils.randomInt(10, 14); + this.currentYaw = Player.getYaw() + Utils.randomFloat(-30, 30); + this.currentPitch = Utils.clamp(Player.getPitch() + Utils.randomFloat(-20, 20), -80, 80); + this.currentKeys = []; + this.startedAt = Date.now(); + this.isRunning = true; + + Keybind.unpressKeys(); + Rotations.stopRotation(); + + AlertUtils.setCancelHandler(() => this.stop()); + this.listener = register('tick', () => this._tick()); + } + + stop() { + if (!this.isRunning) return; + + this.isRunning = false; + Keybind.unpressKeys(); + Rotations.stopRotation(); + + if (this.listener) { + this.listener.unregister(); + this.listener = null; + } + + AlertUtils.setCancelHandler(null); + + const callback = this.onComplete; + this.onComplete = null; + if (callback) callback(); + } + + _tick() { + const elapsed = Date.now() - this.startedAt; + if (elapsed >= this.duration) { + this.stop(); + return; + } + + if (Client.isInGui() && !Client.isInChat()) { + Keybind.unpressKeys(); + return; + } + + if (elapsed >= this.nextActionAt) { + this.currentYaw = MathUtils.wrapTo180(this.currentYaw + Utils.randomFloat(-90, 90)); + this.currentPitch = Utils.clamp(this.currentPitch + Utils.randomFloat(-25, 25), -80, 80); + this.currentKeys = []; + if (Math.random() > 0.35) { + const possibleKeys = ['w', 'a', 's', 'd']; + this.currentKeys.push(possibleKeys[Math.floor(Math.random() * possibleKeys.length)]); + if (Math.random() > 0.8) this.currentKeys.push('space'); + } + this.nextActionAt = elapsed + this.actionInterval; + } + + Rotations.rotateToAngles(this.currentYaw, this.currentPitch, 1.2); + Keybind.unpressKeys(); + this.currentKeys.forEach((key) => Keybind.setKey(key, true)); + } +} + +export const ResponseBot = new ResponseBotClass(); diff --git a/failsafes/SensitivityPresets.js b/failsafes/SensitivityPresets.js new file mode 100644 index 00000000..5588db12 --- /dev/null +++ b/failsafes/SensitivityPresets.js @@ -0,0 +1,40 @@ +const NORMAL = { + rotation: { totalDegThreshold: 50, smallYawThreshold: 8, smallPitchThreshold: 5, smallFlagThreshold: 4 }, + teleport: { tiers: [1, 2, 3, Infinity] }, + velocity: { tiers: [0.5, 1, 2, Infinity] }, + player: { lookFlags: 60, lookDistance: 10, dynamicScaling: 0.19 }, + block: { range: 5, changeThreshold: 15 }, + smart: { threshold: 0.06 }, +}; + +export const TIER_SEVERITIES = ['low', 'medium', 'high', 'very high']; + +export const PRESETS = { + Relaxed: { + ...NORMAL, + rotation: { ...NORMAL.rotation, totalDegThreshold: 63, smallFlagThreshold: 5 }, + teleport: { tiers: [1.5, 3, 5, Infinity] }, + velocity: { tiers: [0.8, 1.5, 3, Infinity] }, + player: { lookFlags: 80, lookDistance: 8, dynamicScaling: 0.25 }, + smart: { threshold: 0.04 }, + }, + Normal: NORMAL, + High: { + ...NORMAL, + rotation: { ...NORMAL.rotation, totalDegThreshold: 41, smallFlagThreshold: 3 }, + teleport: { tiers: [0.75, 1.5, 2.5, Infinity] }, + velocity: { tiers: [0.35, 0.75, 1.5, Infinity] }, + player: { lookFlags: 40, lookDistance: 13, dynamicScaling: 0.13 }, + block: { range: 6, changeThreshold: 13 }, + smart: { threshold: 0.08 }, + }, + Strict: { + ...NORMAL, + rotation: { ...NORMAL.rotation, totalDegThreshold: 32, smallFlagThreshold: 2 }, + teleport: { tiers: [0.5, 1, 2, Infinity] }, + velocity: { tiers: [0.25, 0.5, 1, Infinity] }, + player: { lookFlags: 20, lookDistance: 20, dynamicScaling: 0.09 }, + block: { range: 7, changeThreshold: 11 }, + smart: { threshold: 0.1 }, + }, +}; diff --git a/failsafes/impl/BlockFailsafe.js b/failsafes/impl/BlockFailsafe.js new file mode 100644 index 00000000..3de446ab --- /dev/null +++ b/failsafes/impl/BlockFailsafe.js @@ -0,0 +1,47 @@ +import { ClientboundSectionBlocksUpdatePacket } from '../../utils/Packets'; +import { Failsafe } from '../Failsafe'; +import FailsafeUtils from '../FailsafeUtils'; + +class BlockFailsafe extends Failsafe { + constructor() { + super(); + this.pickobulusExpectedUntil = 0; + register('packetReceived', (packet) => { + if (!this.isActive() || this.disabled) return; + this.settings = FailsafeUtils.getFailsafeSettings('Block'); + if (!this.settings.isEnabled) return; + if (Date.now() < this.pickobulusExpectedUntil) return; + + const preset = FailsafeUtils.getSensitivityPreset().block; + const relevantStates = []; + try { + packet.runUpdates((pos, state) => { + const stateName = state.getBlock().getDescriptionId(); + if (Math.hypot(pos.getX() - Player.getX(), pos.getY() - (Player.getY() + 0.8), pos.getZ() - Player.getZ()) > preset.range) return; + if (stateName.includes('minecraft:stone') || stateName.includes('block.minecraft.stone')) return; + relevantStates.push(stateName); + }); + } catch (e) { + console.error('V5 Caught error' + e + e.stack); + } + if (relevantStates.length <= preset.changeThreshold) return; + + const firstState = relevantStates[0]; + if (!firstState || !relevantStates.every((stateName) => stateName === firstState)) return; + + this._reportFailsafe({ + type: 'Block', + severity: 'high', + pressure: 50, + description: `${relevantStates.length} nearby blocks changed to ${firstState}.`, + chat: `&c&l${relevantStates.length} nearby blocks changed at once!`, + }); + }).setFilteredClass(ClientboundSectionBlocksUpdatePacket); + + register('chat', () => { + this.pickobulusExpectedUntil = Date.now() + 1000; + }).setCriteria('You used your Pickobulus Pickaxe Ability!'); + } +} + +new BlockFailsafe(); diff --git a/failsafes/impl/ChatMentionFailsafe.js b/failsafes/impl/ChatMentionFailsafe.js index ebc19ed4..2de129b5 100644 --- a/failsafes/impl/ChatMentionFailsafe.js +++ b/failsafes/impl/ChatMentionFailsafe.js @@ -1,65 +1,72 @@ -import { Chat } from '../../utils/Chat'; import { Failsafe } from '../Failsafe'; import FailsafeUtils from '../FailsafeUtils'; import { ClientboundSystemChatPacket } from '../../utils/Packets'; +const COOLDOWN_MS = 3000; + class ChatMentionFailsafe extends Failsafe { constructor() { super(); - this.settings = FailsafeUtils.getFailsafeSettings('Chat Mention'); - this.registerChatListeners(); - this.FailsafeReactionTime = 600; - this.isFailsafeEnabled = true; - this.mediumBlacklist = ['idk what words to put here they are all high or completely useless'].map((word) => word.toLowerCase()); - this.highBlacklist = ['wdr', 'report', 'macro', 'cheat', 'exploit', 'hack', 'bot', `${Player.getName()}`].map((word) => word.toLowerCase()); - } - - registerChatListeners() { - register('packetReceived', (packet, event) => { + this.cooldowns = new Map(); + register('packetReceived', (packet) => { if (!this.isActive() || this.disabled) return; if (packet.overlay()) return; // action bar this.settings = FailsafeUtils.getFailsafeSettings('Chat Mention'); if (!this.settings.isEnabled) return; - this.FailsafeReactionTime = this.settings.FailsafeReactionTime || 600; - const content = packet.content().getString(); const colonIndex = content.indexOf(':'); if (colonIndex === -1) return; + if (this._isOwnMessage(content, colonIndex)) return; + const messageBody = content.slice(colonIndex + 1).trim(); + const highWords = this._getWords(this.settings.chatMentionHighWords); + const mediumWords = this._getWords(this.settings.chatMentionMediumWords); + const playerName = Player.getName?.(); + if (playerName) highWords.push(playerName); + + const highMatch = highWords.find((word) => this._wordMatches(messageBody, word)); + const blockedWord = highMatch || mediumWords.find((word) => this._wordMatches(messageBody, word)); + if (!blockedWord) return; - const result = this.scanMessage(messageBody, content.trim()); - if (!result.isBlocked) return; + const severity = highMatch ? 'high' : 'medium'; + const fullMessage = content.trim(); + const cooldownKey = `${severity}:${blockedWord}:${fullMessage}`; + const now = Date.now(); + if (now - (this.cooldowns.get(cooldownKey) || 0) < COOLDOWN_MS) return; + this.cooldowns.set(cooldownKey, now); - this.onTrigger(result); + this._reportFailsafe({ + type: 'Chat Mention', + severity, + pressure: highMatch ? 30 : 10, + description: `Someone mentioned: "${blockedWord}"\nFull message: "${fullMessage}"`, + chat: `&c&lDetected blacklisted word - "${blockedWord}"!`, + }); }).setFilteredClass(ClientboundSystemChatPacket); } - scanMessage(msg, fullMessage = msg) { - const lower = msg.toLowerCase(); - const highMatch = this.highBlacklist.find((word) => lower.includes(word)); - const mediumMatch = this.mediumBlacklist.find((word) => lower.includes(word)); - const isBlocked = !!highMatch || !!mediumMatch; - const isHigh = !!highMatch; - const blockedWord = highMatch || mediumMatch; - - return { isBlocked, blockedWord, isHigh, fullMessage }; + _isOwnMessage(content, colonIndex) { + const playerName = Player.getName?.(); + if (!playerName) return false; + const sender = content.slice(0, colonIndex); + const cleanSender = sender.removeFormatting ? sender.removeFormatting() : sender; + return cleanSender.includes(playerName); } - onTrigger(result) { - const pressure = result.isHigh ? 30 : 10; - const severity = result.isHigh ? 'high' : 'medium'; - const embedColour = result.isHigh ? 16744448 : 16776960; + _getWords(raw) { + return typeof raw === 'string' + ? raw + .split(',') + .map((word) => word.trim()) + .filter(Boolean) + : []; + } - Chat.messageFailsafe(`&c&lDetected blacklisted word - "${result.blockedWord}"!`); - FailsafeUtils.incrementFailsafeIntensity(pressure); - FailsafeUtils.sendFailsafeEmbed( - 'Chat Mention', - severity, - `Someone mentioned: "${result.blockedWord}"\nFull message: "${result.fullMessage}"`, - embedColour - ); + _wordMatches(message, word) { + const escaped = String(word).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + return new RegExp(`\\b${escaped}\\b`, 'i').test(message); } } -export default new ChatMentionFailsafe(); +new ChatMentionFailsafe(); diff --git a/failsafes/impl/PlayerGriefFailsafe.js b/failsafes/impl/PlayerGriefFailsafe.js index 19a1295d..22f39673 100644 --- a/failsafes/impl/PlayerGriefFailsafe.js +++ b/failsafes/impl/PlayerGriefFailsafe.js @@ -1,33 +1,15 @@ -import { Chat } from '../../utils/Chat'; -import { File, globalAssetsDir } from '../../utils/Constants'; +import { MathUtils } from '../../utils/Math'; +import PathConfig from '../../utils/pathfinder/PathConfig'; import { Failsafe } from '../Failsafe'; import FailsafeUtils from '../FailsafeUtils'; -const warpPoints = (() => { - try { - return JSON.parse(FileLib.read(new File(globalAssetsDir, 'WarpPoints.json').getPath()) || '{}').warps || []; - } catch (e) { - console.error('V5 Caught error' + e + e.stack); - return []; - } -})(); - class PlayerGriefFailsafe extends Failsafe { constructor() { super(); - this.settings = FailsafeUtils.getFailsafeSettings('Player Grief'); this.lastInsideTrigger = 0; this.lastNearbyTrigger = 0; this.lastLookingTrigger = 0; - this.insideCooldownMs = 5000; - this.nearbyCooldownMs = 3000; - this.lookingCooldownMs = 3000; - this.registerGriefListeners(); - this.whitelistedPlayers = ['']; // TODO: add gui textbox, i have no clue how it works so im not touching it - this.whitelistedPlayerSet = new Set(this.whitelistedPlayers); - } - - registerGriefListeners() { + this.lookFlags = new Map(); register('step', () => { if (!this.isActive() || !World.isLoaded() || !Player.asPlayerMP()) return; @@ -36,8 +18,10 @@ class PlayerGriefFailsafe extends Failsafe { if (this.isNearWarpPoint()) return; const now = Date.now(); - if (now - this.lastInsideTrigger >= this.insideCooldownMs) this.checkPlayerInside(now); - if (now - this.lastNearbyTrigger >= this.nearbyCooldownMs) this.checkPlayerNearby(now); + const checkInside = now - this.lastInsideTrigger >= 5000; + const checkNearby = now - this.lastNearbyTrigger >= 3000; + const checkLooking = now - this.lastLookingTrigger >= 3000; + if (checkInside || checkNearby || checkLooking) this.checkPlayers(now, checkInside, checkNearby, checkLooking); }).setDelay(1); } @@ -45,70 +29,107 @@ class PlayerGriefFailsafe extends Failsafe { const px = Player.getX(); const py = Player.getY(); const pz = Player.getZ(); - return warpPoints.some((warp) => { - const dx = warp.x - px; - const dy = warp.y - py; - const dz = warp.z - pz; - return dx * dx + dy * dy + dz * dz <= 25; - }); + return PathConfig.WARP_POINTS_DATA.some((warp) => Math.hypot(warp.x - px, warp.y - py, warp.z - pz) <= 5); } - checkPlayerInside(now) { - const look = Player.lookingAt(); - const lookedName = look?.getName?.(); - - if (!(look instanceof PlayerMP) || look.getUUID()?.version() === 2) return; - if (this.whitelistedPlayerSet.has(lookedName)) return; - + checkPlayers(now, checkInside, checkNearby, checkLooking) { const px = Player.getX(); const py = Player.getY(); const pz = Player.getZ(); - - const lx = look.getX(); - const ly = look.getY(); - const lz = look.getZ(); - - if (Math.trunc(lx) === Math.trunc(px) && Math.trunc(ly) === Math.trunc(py) && Math.trunc(lz) === Math.trunc(pz)) { - Chat.messageFailsafe(`&c&l${lookedName} is standing inside you!`); - FailsafeUtils.incrementFailsafeIntensity(120); - FailsafeUtils.sendFailsafeEmbed('Player Grief', 'very high', `${lookedName} is standing inside you!`, 16711680); - - this.lastInsideTrigger = now; - } - } - - checkPlayerNearby(now) { const maxDistance = this.settings.playerProximityDistance || 3; const maxDistanceSq = maxDistance * maxDistance; - const px = Player.getX(); - const py = Player.getY(); - const pz = Player.getZ(); - const selfName = Player.getName(); + const whitelist = this._getWhitelist(); + const self = Player.asPlayerMP(); + const preset = checkLooking && FailsafeUtils.getSensitivityPreset().player; World.getAllPlayers().forEach((player) => { const playerName = player.getName(); - if (playerName === selfName || player.getUUID()?.version() === 2) return; - if (this.whitelistedPlayerSet.has(playerName)) return; + if (this._shouldIgnorePlayer(player, playerName, whitelist)) return; const lx = player.getX(); const ly = player.getY(); const lz = player.getZ(); - const dx = lx - px; const dy = ly - py; const dz = lz - pz; const distanceSq = dx * dx + dy * dy + dz * dz; - if (distanceSq <= maxDistanceSq && distanceSq > 1) { + if (checkInside && Math.trunc(lx) === Math.trunc(px) && Math.trunc(ly) === Math.trunc(py) && Math.trunc(lz) === Math.trunc(pz)) { + this._reportFailsafe({ + type: 'Player Grief', + severity: 'very high', + pressure: 120, + description: `${playerName} is standing inside you!`, + chat: `&c&l${playerName} is standing inside you!`, + }); + this.lastInsideTrigger = now; + } + + if (checkNearby && distanceSq <= maxDistanceSq && distanceSq > 1) { const distance = Math.sqrt(distanceSq); - Chat.messageFailsafe(`&c&l${playerName} is ${distance.toFixed(1)} blocks away from you!`); - FailsafeUtils.incrementFailsafeIntensity(20); - FailsafeUtils.sendFailsafeEmbed('Player Grief', 'medium', `${playerName} is ${distance.toFixed(1)} blocks away!`, 16776960); + this._reportFailsafe({ + type: 'Player Grief', + severity: 'medium', + pressure: 20, + description: `${playerName} is ${distance.toFixed(1)} blocks away!`, + chat: `&c&l${playerName} is ${distance.toFixed(1)} blocks away from you!`, + }); this.lastNearbyTrigger = now; } + + if (!checkLooking) return; + if (this._isLookingAtPlayer(player, self, preset)) { + const flags = (this.lookFlags.get(playerName) || 0) + 1; + this.lookFlags.set(playerName, flags); + if (flags < preset.lookFlags) return; + this._reportFailsafe({ + type: 'Player Grief', + severity: 'high', + pressure: 50, + description: `${playerName} appears to be watching you.`, + chat: `&c&l${playerName} appears to be looking at you!`, + }); + this.lookFlags.set(playerName, 0); + this.lastLookingTrigger = now; + } else if (this.lookFlags.has(playerName)) { + const flags = this.lookFlags.get(playerName) - 1; + if (flags <= 0) this.lookFlags.delete(playerName); + else this.lookFlags.set(playerName, flags); + } }); } + + _isLookingAtPlayer(player, self, preset) { + const distance = player.distanceTo(self); + if (distance > preset.lookDistance) return false; + if (self.canSeeEntity && !self.canSeeEntity(player)) return false; + + const dynamicAngle = distance < 4 ? 360 : 180 * Math.exp(-preset.dynamicScaling * distance) + 4; + const dx = Player.getX() - player.getX(); + const dy = Player.getY() - player.getY(); + const dz = Player.getZ() - player.getZ(); + const expectedYaw = Math.atan2(-dx, dz) * (180 / Math.PI); + const expectedPitch = Math.atan2(-dy, Math.hypot(dx, dz)) * (180 / Math.PI); + const yawDiff = Math.abs(MathUtils.getAngleDifference(player.getYaw(), expectedYaw)); + const pitchDiff = Math.abs(expectedPitch - player.getPitch()); + + return yawDiff <= dynamicAngle && pitchDiff <= dynamicAngle; + } + + _shouldIgnorePlayer(player, playerName, whitelist) { + return !playerName || playerName === Player.getName() || player.getUUID?.()?.version?.() === 2 || whitelist.has(playerName.toLowerCase()); + } + + _getWhitelist() { + const raw = this.settings.playerGriefWhitelist || ''; + return new Set( + String(raw) + .split(',') + .map((name) => name.trim().toLowerCase()) + .filter(Boolean) + ); + } } -export default new PlayerGriefFailsafe(); +new PlayerGriefFailsafe(); diff --git a/failsafes/impl/RotationFailsafe.js b/failsafes/impl/RotationFailsafe.js index e84dd034..d3a84454 100644 --- a/failsafes/impl/RotationFailsafe.js +++ b/failsafes/impl/RotationFailsafe.js @@ -3,15 +3,22 @@ import { MathUtils } from '../../utils/Math'; import { ClientboundPlayerPositionPacket } from '../../utils/Packets'; import { Failsafe } from '../Failsafe'; import FailsafeUtils from '../FailsafeUtils'; +import TeleportFailsafe from './TeleportFailsafe'; + +const ROTATION_TIERS = [ + { limit: 20, pressure: 20, severity: 'medium' }, + { limit: 40, pressure: 50, severity: 'high' }, + { limit: Infinity, pressure: 100, severity: 'very high' }, +]; class RotationFailsafe extends Failsafe { constructor() { super(); - this.settings = FailsafeUtils.getFailsafeSettings('Rotation'); - this.registerRotationListeners(); - } - - registerRotationListeners() { + this.totalRotation = 0; + this.packetWindowStartedAt = 0; + this.flags = 0; + this.lastFlagAt = 0; + this.triggered = false; register('packetReceived', (packet) => { if (!this.isActive() || this.disabled) return; this.settings = FailsafeUtils.getFailsafeSettings('Rotation'); @@ -32,57 +39,90 @@ class RotationFailsafe extends Failsafe { const newYaw = Number(change.yRot()); const newPitch = Number(change.xRot()); - const dx = Math.abs(newX - fromX); - const dy = Math.abs(newY - fromY); - const dz = Math.abs(newZ - fromZ); - const posDistance = Math.hypot(dx, dy, dz); + const posDistance = Math.hypot(newX - fromX, newY - fromY, newZ - fromZ); const yawDiff = Math.abs(MathUtils.getAngleDifference(currYaw, newYaw)); const pitchDiff = Math.abs(newPitch - currPitch); - // todo: this isnt what a null rotation packet is, which retard made these failsafes? if (yawDiff === 0 && pitchDiff === 0) { Chat.messageDebug('null rotation packet ignored (yawDiff=0, pitchDiff=0)', false); return; } + if (newX === 0 && newY === 0 && newZ === 0) { + this._reportFailsafe({ + type: 'Rotation', + severity: 'very high', + pressure: 100, + description: 'Null position-look packet received while checking rotation.', + chat: '&c&lNULL ROTATION PACKET DETECTED, DO NOT REACT!', + }); + return; + } + + if (TeleportFailsafe.itemTeleportInProgress()) return; if (posDistance >= 0.001) return; - const scheduledAt = Date.now(); - setTimeout(() => { - if (this.disabled || !this.isActive() || scheduledAt < this._disabledUntil) return; - this.onTrigger(currYaw, currPitch, newYaw, newPitch, yawDiff, pitchDiff); - }, this._getReactionDelay(this.settings)); - }).setFilteredClass(ClientboundPlayerPositionPacket); - } + const preset = FailsafeUtils.getSensitivityPreset().rotation; + const now = Date.now(); + const rotation = yawDiff + pitchDiff; + if (!this.packetWindowStartedAt || now - this.packetWindowStartedAt > 2000) { + this.packetWindowStartedAt = now; + this.totalRotation = rotation; + } else { + this.totalRotation += rotation; + } - onTrigger(fromYaw, fromPitch, toYaw, toPitch, yawDiff, pitchDiff) { - const totalRotation = yawDiff + pitchDiff; + if (now - this.lastFlagAt > 2500) this.flags = 0; - const tiers = [ - { limit: 5, pressure: 10, severity: 'low', color: 65280 }, - { limit: 20, pressure: 20, severity: 'medium', color: 16776960 }, - { limit: 40, pressure: 50, severity: 'high', color: 16744448 }, - { limit: Infinity, pressure: 100, severity: 'very high', color: 16711680 }, - ]; + const smallRotation = yawDiff >= preset.smallYawThreshold || pitchDiff >= preset.smallPitchThreshold; + if (smallRotation) { + this.flags++; + this.lastFlagAt = now; + } - const { pressure, severity, color } = tiers.find((t) => totalRotation < t.limit); + if (this.triggered) return; + if (this.totalRotation >= preset.totalDegThreshold || this.flags >= preset.smallFlagThreshold) { + this.triggered = true; + this._scheduleTrigger( + () => { + this.onTrigger(currYaw, currPitch, newYaw, newPitch, this.totalRotation); + this.reset(); + }, + this.settings, + () => !this.disabled && !TeleportFailsafe.itemTeleportInProgress() + ); + } + }).setFilteredClass(ClientboundPlayerPositionPacket); + } - Chat.messageFailsafe(`&c&lYou were rotated by the server!`, false); - Chat.messageFailsafe(`&c&lFrom: &r&7Yaw ${fromYaw.toFixed(2)} &f| &7Pitch ${fromPitch.toFixed(2)}`, false); - Chat.messageFailsafe(`&c&lTo: &r&7Yaw ${toYaw.toFixed(2)} &f| &7Pitch ${toPitch.toFixed(2)}`, false); - Chat.messageFailsafe(`&c&lTotal Rotation: &r&7${totalRotation.toFixed(2)}°`, true); - FailsafeUtils.incrementFailsafeIntensity(pressure); + onTrigger(fromYaw, fromPitch, toYaw, toPitch, totalRotation) { + const { pressure, severity } = ROTATION_TIERS.find((tier) => totalRotation < tier.limit); - FailsafeUtils.sendFailsafeEmbed( - 'Rotation', + this._reportFailsafe({ + type: 'Rotation', severity, - `**From:** Yaw ${fromYaw.toFixed(2)} | Pitch ${fromPitch.toFixed(2)} + pressure, + description: `**From:** Yaw ${fromYaw.toFixed(2)} | Pitch ${fromPitch.toFixed(2)} **To:** Yaw ${toYaw.toFixed(2)} | Pitch ${toPitch.toFixed(2)} **Total Rotation:** ${totalRotation.toFixed(2)}°`, - color - ); + chat: [ + `&c&lYou were rotated by the server!`, + `&c&lFrom: &r&7Yaw ${fromYaw.toFixed(2)} &f| &7Pitch ${fromPitch.toFixed(2)}`, + `&c&lTo: &r&7Yaw ${toYaw.toFixed(2)} &f| &7Pitch ${toPitch.toFixed(2)}`, + `&c&lTotal Rotation: &r&7${totalRotation.toFixed(2)}°`, + ], + }); + } + + reset() { + super.reset(); + this.totalRotation = 0; + this.packetWindowStartedAt = 0; + this.flags = 0; + this.lastFlagAt = 0; + this.triggered = false; } } -export default new RotationFailsafe(); +new RotationFailsafe(); diff --git a/failsafes/impl/SlotChangeFailsafe.js b/failsafes/impl/SlotChangeFailsafe.js index f41dca8d..8e031bb2 100644 --- a/failsafes/impl/SlotChangeFailsafe.js +++ b/failsafes/impl/SlotChangeFailsafe.js @@ -1,4 +1,3 @@ -import { Chat } from '../../utils/Chat'; import { ClientboundSetHeldSlotPacket } from '../../utils/Packets'; import { Failsafe } from '../Failsafe'; import FailsafeUtils from '../FailsafeUtils'; @@ -6,11 +5,6 @@ import FailsafeUtils from '../FailsafeUtils'; class SlotChangeFailsafe extends Failsafe { constructor() { super(); - this.settings = FailsafeUtils.getFailsafeSettings('Slot Change'); - this.registerSlotChangeListeners(); - } - - registerSlotChangeListeners() { register('packetReceived', (packet) => { if (!this.isActive() || this.disabled) return; @@ -21,19 +15,19 @@ class SlotChangeFailsafe extends Failsafe { const newSlot = packet.slot() + 1; if (currentSlot === newSlot) return; - const scheduledAt = Date.now(); - setTimeout(() => { - if (this.disabled || !this.isActive() || scheduledAt < this._disabledUntil) return; - this.onTrigger(currentSlot, newSlot); - }, this._getReactionDelay(this.settings)); + this._scheduleTrigger( + () => + this._reportFailsafe({ + type: 'Slot Change', + severity: 'high', + pressure: 50, + description: `Slot changed from ${currentSlot} to ${newSlot}!`, + chat: `&c&lHeld slot has changed from ${currentSlot} to slot ${newSlot}!`, + }), + this.settings + ); }).setFilteredClass(ClientboundSetHeldSlotPacket); } - - onTrigger(fromSlot, toSlot) { - Chat.messageFailsafe(`&c&lHeld slot has changed from ${fromSlot} to slot ${toSlot}!`); - FailsafeUtils.incrementFailsafeIntensity(50); - FailsafeUtils.sendFailsafeEmbed('Slot Change', 'high', `Slot changed from ${fromSlot} to ${toSlot}!`, 16744448); - } } -export default new SlotChangeFailsafe(); +new SlotChangeFailsafe(); diff --git a/failsafes/impl/SmartFailsafe.js b/failsafes/impl/SmartFailsafe.js new file mode 100644 index 00000000..79ab426f --- /dev/null +++ b/failsafes/impl/SmartFailsafe.js @@ -0,0 +1,77 @@ +import { MacroState } from '../../utils/MacroState'; +import { ClientboundBlockDestructionPacket } from '../../utils/Packets'; +import { ServerInfo } from '../../utils/player/ServerInfo'; +import { Failsafe } from '../Failsafe'; +import FailsafeUtils from '../FailsafeUtils'; + +const MINING_BOT_MACRO = 'Mining Bot'; + +class SmartFailsafe extends Failsafe { + constructor() { + super(); + this.blocksBroken = 0; + this.bpsArray = []; + this.recentlyBroken = []; + this.lastBreakAt = Date.now(); + register('step', () => { + if (!this.isMiningBotRunning() || this.disabled || Client.isInChat() || Client.isInGui()) { + this.resetRuntime(); + return; + } + + this.settings = FailsafeUtils.getFailsafeSettings('Smart'); + if (!this.settings.isEnabled) return; + + this.bpsArray.push(this.blocksBroken); + this.blocksBroken = 0; + if (this.bpsArray.length > 300) this.bpsArray.shift(); + + const total = this.bpsArray.reduce((sum, value) => sum + value, 0); + const averageBps = Math.min((5 * total) / Math.max(1, this.bpsArray.length), 3); + if (!averageBps) return; + + const preset = FailsafeUtils.getSensitivityPreset().smart; + const tps = Math.max(ServerInfo.getTPS?.() || 20, 10); + const thresholdDelay = ((1000 / tps) * 20) / (averageBps * preset.threshold); + if (Date.now() - this.lastBreakAt > thresholdDelay) { + this._reportFailsafe({ + type: 'Smart', + severity: 'high', + pressure: 50, + description: `Mining activity stalled. Average BPS: ${averageBps.toFixed(2)}, threshold: ${thresholdDelay.toFixed(0)}ms.`, + chat: `&c&lMining activity stalled unexpectedly!`, + }); + this.resetRuntime(); + } + }).setFps(5); + + register('packetReceived', (packet) => { + if (!this.isMiningBotRunning() || this.disabled) return; + + const progress = packet.getProgress(); + if (progress < 9) return; + + const key = packet.getPos().toString(); + if (key && this.recentlyBroken.includes(key)) return; + if (key) { + this.recentlyBroken.push(key); + if (this.recentlyBroken.length > 10) this.recentlyBroken.shift(); + } + + this.blocksBroken++; + this.lastBreakAt = Date.now(); + }).setFilteredClass(ClientboundBlockDestructionPacket); + } + + isMiningBotRunning() { + return MacroState.getEnabledMacros().includes(MINING_BOT_MACRO); + } + + resetRuntime() { + this.blocksBroken = 0; + this.bpsArray = []; + this.lastBreakAt = Date.now(); + } +} + +new SmartFailsafe(); diff --git a/failsafes/impl/TeleportFailsafe.js b/failsafes/impl/TeleportFailsafe.js index 7f1eaa2c..f69ac647 100644 --- a/failsafes/impl/TeleportFailsafe.js +++ b/failsafes/impl/TeleportFailsafe.js @@ -1,72 +1,49 @@ import { Chat } from '../../utils/Chat'; import { ServerboundUseItemPacket, ClientboundPlayerPositionPacket, ServerboundChatCommandPacket } from '../../utils/Packets'; -import PathConfig from '../../utils/pathfinder/PathConfig'; import { Failsafe } from '../Failsafe'; import FailsafeUtils from '../FailsafeUtils'; +import { TIER_SEVERITIES } from '../SensitivityPresets'; -let lastRightClickTime = 0; -let lastCommandTime = 0; -let pendingWarpIgnore = false; -let pendingWarpIgnoreTimer = null; - -const WARP_IGNORE_RADIUS = 3; -const WARP_IGNORE_RADIUS_SQ = WARP_IGNORE_RADIUS * WARP_IGNORE_RADIUS; -const WARP_IGNORE_TIMEOUT_MS = 3000; -const TELEPORT_TIERS = [ - { threshold: 1, pressure: 5, severity: 'low', color: 65280 }, - { threshold: 2, pressure: 10, severity: 'medium', color: 16776960 }, - { threshold: 3, pressure: 20, severity: 'high', color: 16744448 }, - { threshold: Infinity, pressure: 50, severity: 'very high', color: 16711680 }, -]; +const WARP_IGNORE_TIMEOUT_MS = 5000; +const LAGBACK_DISTANCE = 0.2; +const POSITION_HISTORY_LIMIT = 120; +const SAFE_COMMANDS = ['/skyblock', '/is', '/l', '/lobby', '/hub', '/garden', '/savethejerrys']; +const TELEPORT_PRESSURES = [5, 10, 20, 50]; class TeleportFailsafe extends Failsafe { constructor() { super(); - this.settings = FailsafeUtils.getFailsafeSettings('TP'); + this.positions = []; + this.warpIgnoreUntil = 0; + this.itemTeleportUntil = 0; this.registerTPListeners(); this.registerRightClickListener(); + this.registerPositionHistory(); } registerRightClickListener() { register('packetSent', () => { if (!this.isActive()) return; - lastRightClickTime = Date.now(); + if (this._isTeleportItemHeld()) this.itemTeleportUntil = Date.now() + 1000; }).setFilteredClass(ServerboundUseItemPacket); register('packetSent', (packet) => { if (!this.isActive()) return; - const command = packet.command().toLowerCase(); - if (command.includes('warp')) { - lastCommandTime = Date.now(); + const rawCommand = String(packet.command?.() || '').toLowerCase(); + const command = rawCommand.startsWith('/') ? rawCommand : `/${rawCommand}`; + if (command.startsWith('/warp ') || SAFE_COMMANDS.includes(command)) { Chat.messageDebug(`warp command used, awaiting warp-point teleport ignore`, false); - pendingWarpIgnore = true; - if (pendingWarpIgnoreTimer) clearTimeout(pendingWarpIgnoreTimer); - pendingWarpIgnoreTimer = setTimeout(() => { - pendingWarpIgnore = false; - pendingWarpIgnoreTimer = null; - }, WARP_IGNORE_TIMEOUT_MS); + this.warpIgnoreUntil = Date.now() + WARP_IGNORE_TIMEOUT_MS; } }).setFilteredClass(ServerboundChatCommandPacket); } - shouldIgnoreWarpTeleport(x, y, z) { - if (!pendingWarpIgnore) return false; - - const isWarpPointTeleport = PathConfig.WARP_POINTS_DATA.some((warpPoint) => { - const dx = x - warpPoint.x; - const dy = y - warpPoint.y; - const dz = z - warpPoint.z; - return dx * dx + dy * dy + dz * dz <= WARP_IGNORE_RADIUS_SQ; + registerPositionHistory() { + register('tick', () => { + if (!World.isLoaded() || !Player.getPlayer()) return; + this.positions.push({ x: Player.getX(), y: Player.getY(), z: Player.getZ() }); + if (this.positions.length > POSITION_HISTORY_LIMIT) this.positions.shift(); }); - - if (!isWarpPointTeleport) return false; - - pendingWarpIgnore = false; - if (pendingWarpIgnoreTimer) { - clearTimeout(pendingWarpIgnoreTimer); - pendingWarpIgnoreTimer = null; - } - return true; } _isTeleportItemHeld() { @@ -74,50 +51,12 @@ class TeleportFailsafe extends Failsafe { return !!(heldItem?.includes('aspect of the') && !heldItem?.includes('dragons')); } - _hasSmallRotationDiff(data) { - const { yaw, pitch, currYaw, currPitch } = data; - if (yaw === undefined || pitch === undefined) return false; - - const yawDiff = Math.abs(yaw - currYaw); - const pitchDiff = Math.abs(pitch - currPitch); - return yawDiff < 30 && pitchDiff < 30; + itemTeleportInProgress() { + return Date.now() < this.itemTeleportUntil; } - _isAlongLookVector(data) { - const { fromX, fromY, fromZ, toX, toY, toZ, lookVector } = data; - if (!lookVector || fromX === undefined) return false; - - const dx = toX - fromX; - const dy = toY - fromY; - const dz = toZ - fromZ; - const dist = Math.hypot(dx, dy, dz); - if (dist <= 0.1) return false; - - const dot = (dx * lookVector.x + dy * lookVector.y + dz * lookVector.z) / dist; - return dot > 0.85; - } - - _shouldDisableTeleport(data) { - if (this.disabled) return true; - - const now = Date.now(); - const recentClick = data.lastRightClickTime && now - data.lastRightClickTime < 1000; - const usedItem = recentClick && this._isTeleportItemHeld(); - const recentCommand = data.lastCommandTime && now - data.lastCommandTime < 1000; - - if (!usedItem && !recentCommand) return false; - - if (recentCommand) { - this._setDisabled(750); - return true; - } - - if (usedItem && (this._hasSmallRotationDiff(data) || this._isAlongLookVector(data))) { - this._setDisabled(500); - return true; - } - - return false; + _isLagback(x, y, z) { + return this.positions.some((pos) => Math.hypot(pos.x - x, pos.y - y, pos.z - z) <= LAGBACK_DISTANCE); } registerTPListeners() { @@ -144,66 +83,55 @@ class TeleportFailsafe extends Failsafe { const distanceSq = dx * dx + dy * dy + dz * dz; const distance = Math.sqrt(distanceSq); - const data = { - distance, - yaw: Number(change.yRot()), - pitch: Number(change.xRot()), - currYaw: Player.getYaw(), - currPitch: Player.getPitch(), - lastRightClickTime, - lastCommandTime, - toX: newX, - toY: newY, - toZ: newZ, - fromX, - fromY, - fromZ, - lookVector: { - x: -Math.sin((Player.getYaw() * Math.PI) / 180) * Math.cos((Player.getPitch() * Math.PI) / 180), - y: -Math.sin((Player.getPitch() * Math.PI) / 180), - z: Math.cos((Player.getYaw() * Math.PI) / 180) * Math.cos((Player.getPitch() * Math.PI) / 180), - }, - }; - - if (this._shouldDisableTeleport(data)) return; if (distanceSq < 0.01) return; - if (this.shouldIgnoreWarpTeleport(newX, newY, newZ)) return; + if (Date.now() < this.warpIgnoreUntil) return; + if (this._isLagback(newX, newY, newZ)) { + Chat.messageDebug('lagback teleport ignored by position history', false); + this._setDisabled(500); + return; + } if (newX === 0 && newY === 0 && newZ === 0) { - this.handleNullPacket(newX, newY, newZ); + this._reportFailsafe({ + type: 'TP', + severity: 'very high', + pressure: 100, + description: 'You just received a null packet to 0, 0, 0!', + chat: '&c&lNULL PACKET DETECTED, DO NOT REACT!', + }); return; } - const scheduledAt = Date.now(); - setTimeout(() => { - if (this.disabled || !this.isActive() || scheduledAt < this._disabledUntil || this._shouldDisableTeleport(data)) return; - this.onTrigger(fromX, fromY, fromZ, newX, newY, newZ, distance); - }, this._getReactionDelay(this.settings)); + this._scheduleTrigger( + () => { + this.onTrigger(fromX, fromY, fromZ, newX, newY, newZ, distance); + }, + this.settings, + () => Date.now() >= this.warpIgnoreUntil && !this._isLagback(newX, newY, newZ) + ); }).setFilteredClass(ClientboundPlayerPositionPacket); } - handleNullPacket(x, y, z) { - Chat.messageFailsafe('&c&lNULL PACKET DETECTED, DO NOT REACT!', false); - FailsafeUtils.sendFailsafeEmbed('TP', 'very high - null packet', `You just recieved a null packet to ${x}, ${y}, ${z}!`, 16711680); - } - onTrigger(fX, fY, fZ, nX, nY, nZ, dist) { - const { pressure, severity, color } = TELEPORT_TIERS.find((t) => dist < t.threshold); + const tiers = FailsafeUtils.getSensitivityPreset().teleport.tiers; + const tierIndex = tiers.findIndex((threshold) => dist < threshold); + const pressure = TELEPORT_PRESSURES[tierIndex]; + const severity = TIER_SEVERITIES[tierIndex]; - Chat.messageFailsafe(`&l&cTeleport Detected!`, false); - Chat.messageFailsafe(`&c&lFrom: &r&7${fX.toFixed(2)}&f, &7${fY.toFixed(2)}&f, &7${fZ.toFixed(2)}&f`, false); - Chat.messageFailsafe(`&c&lTo: &r&7${nX.toFixed(2)}&f, &7${nY.toFixed(2)}&f, &7${nZ.toFixed(2)}&f`, false); - Chat.messageFailsafe(`&c&lTotal Blocks: &r&7${dist.toFixed(0)}`, true); - FailsafeUtils.incrementFailsafeIntensity(pressure); - - FailsafeUtils.sendFailsafeEmbed( - 'TP', + this._reportFailsafe({ + type: 'TP', severity, - `**From:** ${fX.toFixed(2)} | ${fY.toFixed(2)} | ${fZ.toFixed(2)} + pressure, + description: `**From:** ${fX.toFixed(2)} | ${fY.toFixed(2)} | ${fZ.toFixed(2)} **To:** ${nX.toFixed(2)} | ${nY.toFixed(2)} | ${nZ.toFixed(2)} **Distance:** ${dist.toFixed(1)} blocks`, - color - ); + chat: [ + `&l&cTeleport Detected!`, + `&c&lFrom: &r&7${fX.toFixed(2)}&f, &7${fY.toFixed(2)}&f, &7${fZ.toFixed(2)}&f`, + `&c&lTo: &r&7${nX.toFixed(2)}&f, &7${nY.toFixed(2)}&f, &7${nZ.toFixed(2)}&f`, + `&c&lTotal Blocks: &r&7${dist.toFixed(0)}`, + ], + }); } } diff --git a/failsafes/impl/VelocityFailsafe.js b/failsafes/impl/VelocityFailsafe.js index 8e92fe8a..cb23b5d5 100644 --- a/failsafes/impl/VelocityFailsafe.js +++ b/failsafes/impl/VelocityFailsafe.js @@ -2,25 +2,16 @@ import { Chat } from '../../utils/Chat'; import { ClientboundSetEntityMotionPacket } from '../../utils/Packets'; import { Failsafe } from '../Failsafe'; import FailsafeUtils from '../FailsafeUtils'; +import { TIER_SEVERITIES } from '../SensitivityPresets'; -const VELOCITY_TIERS = [ - { threshold: 0.5, pressure: 10, severity: 'low', color: 65280 }, - { threshold: 1, pressure: 20, severity: 'medium', color: 16776960 }, - { threshold: 2, pressure: 50, severity: 'high', color: 16744448 }, - { threshold: Infinity, pressure: 100, severity: 'very high', color: 16711680 }, -]; +const VELOCITY_PRESSURES = [10, 20, 50, 100]; class VelocityFailsafe extends Failsafe { constructor() { super(); - this.registerVeloListeners(); - this.settings = FailsafeUtils.getFailsafeSettings('Velocity'); - } - - registerVeloListeners() { register('packetReceived', (packet) => { if (!this.isActive() || this.disabled) return; - this._handleVelocityOnDamageDisabled(); + if (Player.getPlayer()?.hurtTime > 0) this._setDisabled(1000); if (this.disabled) return; const playerMP = Player.asPlayerMP(); if (!playerMP || packet?.id?.() !== playerMP?.mcValue?.getId()) return; @@ -30,7 +21,8 @@ class VelocityFailsafe extends Failsafe { const z = Math.floor(Player.getZ()); const blockBelow = World.getBlockAt(x, y, z); const blockName = blockBelow?.getType()?.getRegistryName() || ''; - if (this._bypassTrigger(blockName)) return; + const heldItem = Player.getHeldItem()?.getName()?.removeFormatting()?.toLowerCase(); + if (heldItem?.includes('grappling') || blockName.includes('slime_block')) return; this.settings = FailsafeUtils.getFailsafeSettings('Velocity'); if (!this.settings.isEnabled) return; @@ -41,51 +33,31 @@ class VelocityFailsafe extends Failsafe { const vz = movement?.z; const speed = Math.hypot(vx, vy, vz); - if (this._shouldDisableVelocity(speed, blockName)) return; - const scheduledAt = Date.now(); - setTimeout(() => { - if (this.disabled || !this.isActive() || scheduledAt < this._disabledUntil || this._shouldDisableVelocity(speed, blockName)) return; - this.onTrigger(speed); - }, this._getReactionDelay(this.settings)); - }).setFilteredClass(ClientboundSetEntityMotionPacket); - } - - _handleVelocityOnDamageDisabled() { - const player = Player.getPlayer(); - if (!player) return; - - if (player.hurtTime > 0) { - this._setDisabled(1000); - } - } - - _shouldDisableVelocity(velocity, blockBelow) { - if (this.disabled) return true; - if (velocity === undefined) return false; - const roundedVelocity = Math.round(velocity); - - if (blockBelow && !blockBelow.includes('air') && (roundedVelocity === 1 || roundedVelocity === 0)) { - Chat.messageDebug('disabling fall velocity packet'); - this._setDisabled(1000); - } - - return this.disabled; - } + const roundedSpeed = Math.round(speed); + if (blockName && !blockName.includes('air') && (roundedSpeed === 1 || roundedSpeed === 0)) { + Chat.messageDebug('disabling fall velocity packet'); + this._setDisabled(1000); + return; + } - _bypassTrigger(blockBelowName) { - const heldItem = Player.getHeldItem()?.getName()?.removeFormatting(); - if (heldItem?.includes('Grappling')) return true; - - return blockBelowName.includes('slime_block'); + this._scheduleTrigger(() => this.onTrigger(speed), this.settings); + }).setFilteredClass(ClientboundSetEntityMotionPacket); } onTrigger(speed) { - const { pressure, severity, color } = VELOCITY_TIERS.find((t) => speed < t.threshold) || VELOCITY_TIERS[VELOCITY_TIERS.length - 1]; - - Chat.messageFailsafe(`&c&lVelocity failsafe triggered! Velocity: ${speed.toFixed(0)}`); - FailsafeUtils.incrementFailsafeIntensity(pressure); - FailsafeUtils.sendFailsafeEmbed('Velocity', severity, `Velocity change detected: ${speed.toFixed(0)}`, color); + const tiers = FailsafeUtils.getSensitivityPreset().velocity.tiers; + const tierIndex = tiers.findIndex((threshold) => speed < threshold); + const pressure = VELOCITY_PRESSURES[tierIndex]; + const severity = TIER_SEVERITIES[tierIndex]; + + this._reportFailsafe({ + type: 'Velocity', + severity, + pressure, + description: `Velocity change detected: ${speed.toFixed(2)}`, + chat: `&c&lVelocity failsafe triggered! Velocity: ${speed.toFixed(2)}`, + }); } } -export default new VelocityFailsafe(); +new VelocityFailsafe(); diff --git a/failsafes/sounds/Tave Check.wav b/failsafes/sounds/Tave Check.wav deleted file mode 100644 index 0ebba8fe..00000000 Binary files a/failsafes/sounds/Tave Check.wav and /dev/null differ diff --git a/failsafes/sounds/metal Pipe.wav b/failsafes/sounds/metal Pipe.wav deleted file mode 100644 index 06567a62..00000000 Binary files a/failsafes/sounds/metal Pipe.wav and /dev/null differ diff --git a/modules/other/Failsafes.js b/modules/other/Failsafes.js index e9145c40..4c2d01cf 100644 --- a/modules/other/Failsafes.js +++ b/modules/other/Failsafes.js @@ -4,8 +4,8 @@ import { File, globalAssetsDir } from '../../utils/Constants'; import { ModuleBase } from '../../utils/ModuleBase'; import { ClientboundDisconnectPacket, ClientboundLoginDisconnectPacket } from '../../utils/Packets'; import { MacroState } from '../../utils/MacroState'; +import { Executor } from '../../utils/ThreadExecutor'; import { TimeUtils } from '../../utils/TimeUtils'; -//import Clipping from '../../utils/Clipping'; const JURL = Java.type('java.net.URL'); const JOutputStreamWriter = Java.type('java.io.OutputStreamWriter'); @@ -19,116 +19,74 @@ class Failsafes extends ModuleBase { hideInModules: true, }); - this.tp = true; - this.rotation = true; - this.velocity = true; - this.slotChange = true; - this.chatMention = true; - this.playerGrief = true; - this.clipOnBan = true; - this.playerProximityDistance = 3; - this.actionDelay = { low: 500, high: 2000 }; - this.pingOnCheck = 'Ping'; - this.playSoundOnCheck = true; this.lastBanLogTime = 0; register('packetReceived', (packet) => { const reason = packet?.reason(); const fullText = reason?.getString?.() || reason?.toString?.(); - const lowerText = fullText?.toLowerCase(); - - if (this.isBanReason(lowerText)) { - this.postBanLog(fullText); - - if (this.clipOnBan) { - //Client.scheduleTask(40, () => Clipping.saveClip()); - } - } + this.postBanLog(fullText); }).setFilteredClasses([ClientboundLoginDisconnectPacket, ClientboundDisconnectPacket]); const sectionName = 'Failsafes'; + const enabledFailsafes = ['TP', 'Rotation', 'Velocity', 'Slot Change', 'Chat Mention', 'Player Grief', 'Block', 'Smart']; + this.addDirectMultiToggle('Enabled Failsafes', enabledFailsafes, false, null, 'Select which failsafes are enabled', enabledFailsafes, sectionName); this.addDirectMultiToggle( - 'Enabled Failsafes', - ['TP', 'Rotation', 'Velocity', 'Slot Change', 'Chat Mention', 'Player Grief'], - false, - (value) => { - const enabled = Array.isArray(value) ? value : []; - this.tp = enabled.includes('TP'); - this.rotation = enabled.includes('Rotation'); - this.velocity = enabled.includes('Velocity'); - this.slotChange = enabled.includes('Slot Change'); - this.chatMention = enabled.includes('Chat Mention'); - this.playerGrief = enabled.includes('Player Grief'); - }, - 'Select which failsafes are enabled', - ['TP', 'Rotation', 'Velocity', 'Slot Change', 'Chat Mention', 'Player Grief'], + 'Failsafe Sensitivity', + ['Relaxed', 'Normal', 'High', 'Strict'], + true, + null, + 'Global failsafe sensitivity preset', + 'Normal', sectionName ); this.addDirectRangeSlider( 'Failsafe Detection Delay (ms)', 500, 5000, - this.actionDelay, - (value) => { - this.actionDelay = value; - }, + { low: 500, high: 2000 }, + null, 'Delay in milliseconds between detection of failsafe', sectionName ); - this.addDirectSlider( - 'Player Proximity Distance', - 1, - 10, - this.playerProximityDistance, - (value) => { - this.playerProximityDistance = value; - }, - 'Distance in blocks for player nearby detection', + this.addDirectSlider('Player Proximity Distance', 1, 10, 3, null, 'Distance in blocks for player nearby detection', sectionName); + this.addDirectToggle('Pause macro on failsafe', null, 'Pause the running macro until the failsafe response finishes', true, sectionName); + this.addDirectMultiToggle( + 'Min severity to fire alert overlay', + ['low', 'medium', 'high', 'very high'], + true, + null, + 'Minimum severity required to show the failsafe overlay and response bot', + 'high', sectionName ); - this.addDirectToggle( - 'Clip on ban', - (value) => { - this.clipOnBan = value; - }, - 'Toggle clip on ban', - this.clipOnBan, + this.addDirectTextInput( + 'Chat Mention - High Severity Words', + 'wdr, report, cheat, hack, exploit, macro', + null, + 'Comma-separated high-severity chat words', sectionName ); + this.addDirectTextInput('Chat Mention - Medium Severity Words', '', null, 'Comma-separated medium-severity chat words', sectionName); + this.addDirectTextInput('Player Grief - Whitelist', '', null, 'Comma-separated player names ignored by player grief checks', sectionName); this.addDirectMultiToggle( 'Discord ping on Check', ['None', 'Embed Only', 'Ping', 'Screenshot Only', 'Ping & Screenshot'], true, - (value) => { - this.pingOnCheck = value; - }, + null, 'Toggle discord ping on check', - this.pingOnCheck, - sectionName - ); - this.addDirectToggle( - 'Play sound on check', - (value) => { - this.playSoundOnCheck = value; - }, - 'Toggle play sound on check', - this.playSoundOnCheck, + 'Ping', sectionName ); + this.addDirectToggle('Play sound on check', null, 'Toggle play sound on check', true, sectionName); this.addDirectMultiToggle( 'Failsafe sound', this.getFilesInDir(), true, () => { const selectedFiles = getSetting('Failsafes', 'Failsafe sound'); - if (!Array.isArray(selectedFiles)) return; - const enabledNames = selectedFiles.filter((fileObject) => fileObject.enabled).map((fileObject) => fileObject.name); - if (enabledNames.length === 0) return; - - const singleEnabledName = enabledNames[0] + '.wav'; - - AlertUtils.setFailsafeSound(singleEnabledName); + const selectedFile = (Array.isArray(selectedFiles) ? selectedFiles : []).find((file) => file.enabled); + if (selectedFile) AlertUtils.setFailsafeSound(`${selectedFile.name}.ogg`); }, null, false, @@ -136,11 +94,6 @@ class Failsafes extends ModuleBase { ); } - isBanReason(text) { - if (!text) return false; - return text.includes('banned') || text.includes('cheating') || text.includes('boosting') || text.includes('security'); - } - postBanLog(reason) { if (!reason?.includes('https://www.hypixel.net/appeal')) return; @@ -148,48 +101,44 @@ class Failsafes extends ModuleBase { if (now - this.lastBanLogTime < 60000) return; this.lastBanLogTime = now; - new Thread(() => { - try { - const jwt = V5Auth.getFreshJwtToken(); - if (!jwt) { - console.error('Skipping ban log: no fresh auth token available.'); - return; - } - const url = new JURL('https://backend.rdbt.top/api/logs/bans'); - const conn = url.openConnection(); - conn.setRequestMethod('POST'); - conn.setDoOutput(true); - conn.setRequestProperty('Authorization', `Bearer ${jwt}`); - conn.setRequestProperty('Content-Type', 'application/json; charset=UTF-8'); - - const lastMacros = MacroState.getLastActiveMacros(); - const lastMacroMeta = MacroState.getLastDisableMeta(lastMacros[0]); - const lastDisableTimestamp = lastMacroMeta?.timestamp; - const within5Minutes = typeof lastDisableTimestamp === 'number' && Date.now() - lastDisableTimestamp <= 5 * 60 * 1000; - - const body = JSON.stringify({ - reason: reason, - lastMacro: lastMacros.join(', ') || 'None', - currentlyMacroing: MacroState.isMacroRunning() || within5Minutes, - macroRuntime: MacroState.isMacroRunning() ? TimeUtils.formatUptime(MacroState.getStartTime()) : null, - ingame_username: Player?.getName?.() || 'unknown', - config_contents: this.getConfigFileContents(), - installed_mods: new File('./mods').listFiles().join('\n'), - }); - - const wr = new JOutputStreamWriter(conn.getOutputStream()); - wr.write(body); - wr.close(); - - const status = conn.getResponseCode(); - if (status < 200 || status >= 300) { - console.error(`Error sending ban log. Status: ${status}`); - } - conn.disconnect(); - } catch (e) { - console.error(`Exception sending ban log: ${e}`); + Executor.execute(() => { + const jwt = V5Auth.getFreshJwtToken(); + if (!jwt) { + console.error('Skipping ban log: no fresh auth token available.'); + return; } - }).start(); + const url = new JURL('https://backend.rdbt.top/api/logs/bans'); + const conn = url.openConnection(); + conn.setRequestMethod('POST'); + conn.setDoOutput(true); + conn.setRequestProperty('Authorization', `Bearer ${jwt}`); + conn.setRequestProperty('Content-Type', 'application/json; charset=UTF-8'); + + const lastMacros = MacroState.getLastActiveMacros(); + const lastMacroMeta = MacroState.getLastDisableMeta(lastMacros[0]); + const lastDisableTimestamp = lastMacroMeta?.timestamp; + const within5Minutes = typeof lastDisableTimestamp === 'number' && Date.now() - lastDisableTimestamp <= 5 * 60 * 1000; + + const body = JSON.stringify({ + reason: reason, + lastMacro: lastMacros.join(', ') || 'None', + currentlyMacroing: MacroState.isMacroRunning() || within5Minutes, + macroRuntime: MacroState.isMacroRunning() ? TimeUtils.formatUptime(MacroState.getStartTime()) : null, + ingame_username: Player?.getName?.() || 'unknown', + config_contents: this.getConfigFileContents(), + installed_mods: new File('./mods').listFiles().join('\n'), + }); + + const wr = new JOutputStreamWriter(conn.getOutputStream()); + wr.write(body); + wr.close(); + + const status = conn.getResponseCode(); + if (status < 200 || status >= 300) { + console.error(`Error sending ban log. Status: ${status}`); + } + conn.disconnect(); + }); } getConfigFileContents() { @@ -209,25 +158,10 @@ class Failsafes extends ModuleBase { return []; } - const fileArray = targetPath.listFiles(); - const fileNames = []; - const seen = new Set(); - - if (!fileArray) return []; - - for (const file of fileArray) { - let name = file.getName(); - - if (name.endsWith('.wav')) { - name = name.slice(0, -4); - if (!seen.has(name)) { - seen.add(name); - fileNames.push(name); - } - } - } - - return fileNames.sort((a, b) => a.localeCompare(b)); + return Array.from(targetPath.listFiles() || []) + .filter((file) => file.getName().endsWith('.ogg')) + .map((file) => file.getName().slice(0, -4)) + .sort((a, b) => a.localeCompare(b)); } } diff --git a/utils/Config.js b/utils/Config.js index 2c3ddb2f..99e1f192 100644 --- a/utils/Config.js +++ b/utils/Config.js @@ -1,34 +1,5 @@ import { Chat } from './Chat'; -import { File, globalAssetsDir } from './Constants'; - -const SOURCE_SOUNDS_DIR = new File('./config/ChatTriggers/modules/V5/failsafes/sounds'); -const DEST_SOUNDS_DIR = new File(globalAssetsDir, 'failsafes/sounds'); - -function organizeFailsafeSounds() { - if (!SOURCE_SOUNDS_DIR.exists()) return; - if (!DEST_SOUNDS_DIR.exists()) DEST_SOUNDS_DIR.mkdirs(); - - const soundFiles = SOURCE_SOUNDS_DIR.listFiles(); - if (!soundFiles) return; - - const Files = Java.type('java.nio.file.Files'); - const StandardCopyOption = Java.type('java.nio.file.StandardCopyOption'); - - for (const file of soundFiles) { - if (file.isDirectory() || !file.getName().endsWith('.wav')) continue; - - const target = new File(DEST_SOUNDS_DIR, file.getName()); - if (target.exists() && file.length() === target.length()) continue; - - try { - Files.copy(file.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); - } catch (e) { - console.error('V5 Asset Fixer Error: ' + e); - } - } -} - -organizeFailsafeSounds(); +import { File } from './Constants'; const CONFIG_ROOT = 'V5Config'; const CONFIG_PATH = `./config/ChatTriggers/modules/${CONFIG_ROOT}`;