您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Audio signals for various aspects of IQRPG
当前为
// ==UserScript== // @name IQRPG+ Combined // @namespace https://www.iqrpg.com/ // @version 0.1.8 // @description Audio signals for various aspects of IQRPG // @Author Sanjin // @match http://iqrpg.com/game.html // @match https://iqrpg.com/game.html // @match http://www.iqrpg.com/game.html // @match https://www.iqrpg.com/game.html // @require http://code.jquery.com/jquery-latest.js // @grant none // ==/UserScript== /* Original script by Bunjo and Vifs Shout out to Xortrox & euphone & Karubo for their contributions to the script. */ // Use a namespace to avoid global scope pollution const IQRPG_Plus = (function() { // Private variables let userSettings = {}; let isAlerting = false; let alertTimer = null; let currAutoAudioPlays = 0; let canSendDesktopAlert = true; let audioRepeatLock = false; let desktopNotificationOnCooldown = false; let bonusExp = false; let isDragging = false; let dragOffsetX = 0; let dragOffsetY = 0; let processedEvents = new Map(); // Store processed events with their unique signature let lastEventTime = 0; let lastEventType = ''; // Store references to UI elements const elements = { buttonPanel: null, buttonContainer: null, settingsPanel: null, settingsButton: null, graphButton: null, swordsButton: null, testVolumeButton: null }; // Store references to observers const observers = { main: null, land: null, mastery: null }; // Cache DOM query results const domCache = {}; // Default settings const defaultSettings = { masterAudioLevel: 1, autoAudioAlert: true, autoAlertSoundURL: 'https://www.pacdv.com/sounds/mechanical_sound_effects/gun-reload-1.wav', autoAlertRepeatInSeconds: 2, autoAlertNumber: 10, autoMaxNumberOfAudioAlerts: 2, autoDesktopAlert: true, dungeonAudioAlert: true, dungeonDesktopAlert: true, bossAudioAlert: true, bossAlertSoundURL: 'https://www.pacdv.com/sounds/interface_sound_effects/sound8.mp3', bossDefeatedSoundURL: 'https://ia801306.us.archive.org/32/items/FF7ACVictoryFanfareRingtoneperfectedMp3/FF7%20AC%20Victory%20Fanfare%20Ringtone%20%28perfected%20mp3%29.mp3', bossDesktopAlert: true, eventDesktopAlert: true, eventAlertSoundURL: 'https://www.pacdv.com/sounds/interface_sound_effects/sound8.mp3', eventAlert_Woodcutting: true, eventAlert_Quarrying: true, eventAlert_Mining: true, eventAudioAlert: true, eventAudioAlertFinished: true, whisperAudioAlert: true, whisperAlertSoundURL: 'https://www.pacdv.com/sounds/mechanical_sound_effects/spring_1.wav', whisperAlertOnlyWhenTabIsInactive: false, whisperDesktopAlert: true, landAudioAlert: true, landAlertSoundURL: 'https://www.pacdv.com/sounds/mechanical_sound_effects/coins_4.wav', masteryAudioAlert: true, masteryEveryXLevels: 50, masteryAlertSoundURL: 'https://ia801306.us.archive.org/32/items/FF7ACVictoryFanfareRingtoneperfectedMp3/FF7%20AC%20Victory%20Fanfare%20Ringtone%20%28perfected%20mp3%29.mp3', effectAudioAlert: true, effectAutoLeft: 5, effectAlertSoundURL: 'https://www.pacdv.com/sounds/mechanical_sound_effects/hammer-1.mp3', watchtowerAudioAlert: true, watchtowerAlertSoundURL: 'https://www.pacdv.com/sounds/interface_sound_effects/sound8.mp3', watchtowerDesktopAlert: true, bonusExpAudioAlert: true, bonusExpAlertSoundURL: 'https://www.pacdv.com/sounds/miscellaneous_sounds/magic-wand-1.wav', showDebugInfo: true }; // Load settings from localStorage function loadSettings() { try { const savedSettings = JSON.parse(localStorage.getItem('iqrpgSettings')); // Ensure all default settings exist in the loaded settings const result = savedSettings ? Object.assign({}, defaultSettings, savedSettings) : defaultSettings; console.log('IQRPG+ Settings loaded:', result); return result; } catch (e) { console.error('Error loading settings:', e); return defaultSettings; } } // Save settings to localStorage function saveSettings() { try { localStorage.setItem('iqrpgSettings', JSON.stringify(userSettings)); console.log('IQRPG+ Settings saved:', userSettings); } catch (e) { console.error('Error saving settings:', e); } } // Apply settings to checkboxes function applySettingsToUI() { // Apply checkbox states for (const setting in userSettings) { const checkbox = document.getElementById(setting); if (checkbox && checkbox.type === 'checkbox') { checkbox.checked = userSettings[setting]; } } // Apply volume slider const volumeSlider = document.getElementById('volumeSlider'); if (volumeSlider) { volumeSlider.value = userSettings.masterAudioLevel; } } // Initialize the userscript function init() { userSettings = loadSettings(); // Ensure we have up-to-date settings saveSettings(); createUI(); setupWebSocketInterception(); setupObservers(); setupEventListeners(); // Check Notification permissions if (Notification.permission !== "denied") { Notification.requestPermission(); } } // Create and setup UI elements function createUI() { try { // Create main panel elements.buttonPanel = document.createElement('div'); elements.buttonPanel.id = 'buttonPanel'; // Create settings panel elements.settingsPanel = document.createElement('div'); elements.settingsPanel.id = 'settingsPanel'; elements.settingsPanel.className = 'hidden'; elements.settingsPanel.innerHTML = ` <h2>Settings</h2> <label>Master Volume: <input type="range" id="volumeSlider" min="0" max="1" step="0.1" value="${userSettings.masterAudioLevel}"></label> <button id="testVolumeButton">Test Volume</button> <button id="resetSettingsButton">Reset Settings</button> <div style="margin-top: 10px;"> <label style="display: block;"><input type="checkbox" id="autoAudioAlert" ${userSettings.autoAudioAlert ? 'checked' : ''}> Auto Audio Alert</label> <label style="display: block;"><input type="checkbox" id="autoDesktopAlert" ${userSettings.autoDesktopAlert ? 'checked' : ''}> Auto Desktop Alert</label> <label style="display: block;"><input type="checkbox" id="dungeonAudioAlert" ${userSettings.dungeonAudioAlert ? 'checked' : ''}> Dungeon Audio Alert</label> <label style="display: block;"><input type="checkbox" id="dungeonDesktopAlert" ${userSettings.dungeonDesktopAlert ? 'checked' : ''}> Dungeon Desktop Alert</label> <label style="display: block;"><input type="checkbox" id="bossAudioAlert" ${userSettings.bossAudioAlert ? 'checked' : ''}> Boss Audio Alert</label> <label style="display: block;"><input type="checkbox" id="bossDesktopAlert" ${userSettings.bossDesktopAlert ? 'checked' : ''}> Boss Desktop Alert</label> <label style="display: block;"><input type="checkbox" id="eventAudioAlert" ${userSettings.eventAudioAlert ? 'checked' : ''}> Event Audio Alert</label> <label style="display: block;"><input type="checkbox" id="eventAudioAlertFinished" ${userSettings.eventAudioAlertFinished ? 'checked' : ''}> Event Finished Audio Alert</label> <label style="display: block;"><input type="checkbox" id="eventDesktopAlert" ${userSettings.eventDesktopAlert ? 'checked' : ''}> Event Desktop Alert</label> <label style="display: block;"><input type="checkbox" id="whisperAudioAlert" ${userSettings.whisperAudioAlert ? 'checked' : ''}> Whisper Audio Alert</label> <label style="display: block;"><input type="checkbox" id="whisperDesktopAlert" ${userSettings.whisperDesktopAlert ? 'checked' : ''}> Whisper Desktop Alert</label> <label style="display: block;"><input type="checkbox" id="landAudioAlert" ${userSettings.landAudioAlert ? 'checked' : ''}> Land Audio Alert</label> <label style="display: block;"><input type="checkbox" id="masteryAudioAlert" ${userSettings.masteryAudioAlert ? 'checked' : ''}> Mastery Audio Alert</label> <label style="display: block;"><input type="checkbox" id="effectAudioAlert" ${userSettings.effectAudioAlert ? 'checked' : ''}> Effect Audio Alert</label> <label style="display: block;"><input type="checkbox" id="watchtowerAudioAlert" ${userSettings.watchtowerAudioAlert ? 'checked' : ''}> Watchtower Audio Alert</label> <label style="display: block;"><input type="checkbox" id="watchtowerDesktopAlert" ${userSettings.watchtowerDesktopAlert ? 'checked' : ''}> Watchtower Desktop Alert</label> <label style="display: block;"><input type="checkbox" id="bonusExpAudioAlert" ${userSettings.bonusExpAudioAlert ? 'checked' : ''}> Bonus Exp Audio Alert</label> </div> `; // Create buttons with helper function elements.settingsButton = createControlButton('🔔'); elements.graphButton = createControlButton('📊'); elements.swordsButton = createControlButton('⚔️'); // Create container for buttons elements.buttonContainer = document.createElement('div'); elements.buttonContainer.id = 'buttonContainer'; // Assemble the UI elements.buttonContainer.appendChild(elements.settingsButton); elements.buttonContainer.appendChild(elements.graphButton); elements.buttonContainer.appendChild(elements.swordsButton); elements.buttonPanel.appendChild(elements.buttonContainer); elements.buttonPanel.appendChild(elements.settingsPanel); document.body.appendChild(elements.buttonPanel); // Add stylesheets addStyles(); // Apply saved position loadSavedPosition(); // Initialize settings based on saved values applySettingsToUI(); } catch (error) { console.error('Error creating UI:', error); } } // Helper to create control buttons function createControlButton(icon) { const button = document.createElement('button'); button.textContent = icon; button.className = 'control-button'; return button; } // Add CSS styles function addStyles() { const style = document.createElement('style'); style.textContent = ` #buttonPanel { position: fixed; top: 50px; right: 10px; z-index: 1001; background-color: rgba(0, 0, 0, 0.8); border: 1px solid #333; padding: 0; display: flex; flex-direction: column; cursor: move; overflow: visible; width: 120px; transition: box-shadow 0.2s ease; } #buttonPanel.dragging { box-shadow: 0 0 8px rgba(34, 116, 34, 0.7); opacity: 0.9; } #buttonContainer { display: flex; flex-direction: row; padding: 6px; width: 100%; box-sizing: border-box; justify-content: center; cursor: move; } .control-button { background-color: rgba(0, 0, 0, 0.8); color: white; border: 1px solid #333; padding: 8px; width: 25px; height: 25px; line-height: 1; text-align: center; cursor: pointer; font-size: 14px; margin: 0 2px; display: flex; align-items: center; justify-content: center; } .control-button:hover { border-color: #227422; background-color: rgba(10, 10, 10, 0.9); } #settingsPanel { position: absolute; top: 100%; left: -1px; width: calc(100% + 2px); min-width: 250px; background-color: rgba(0, 0, 0, 0.8); color: white; border: 1px solid #333; border-top: none; padding: 15px; display: none; z-index: 1002; font-family: 'Arial', sans-serif; box-sizing: border-box; max-height: 80vh; overflow-y: auto; } #testVolumeButton, #resetSettingsButton { background-color: rgba(0, 0, 0, 0.8); color: white; border: 1px solid #333; padding: 5px 10px; cursor: pointer; font-size: 12px; margin: 5px 0; } #testVolumeButton:hover, #resetSettingsButton:hover { border-color: #227422; background-color: rgba(10, 10, 10, 0.9); } #resetSettingsButton { background-color: rgba(50, 10, 10, 0.8); margin-left: 10px; } #volumeSlider { margin: 10px 0; width: 90%; accent-color: #227422; } #settingsPanel h2 { margin-top: 0; padding-top: 0; font-size: 16px; margin-bottom: 10px; color: #227422; } #settingsPanel label { font-size: 14px; } #settingsPanel input[type="checkbox"] { accent-color: #227422; } `; document.head.appendChild(style); } // Load saved position from localStorage function loadSavedPosition() { try { const savedPosition = localStorage.getItem('iqrpgPanelPosition'); if (savedPosition) { const position = JSON.parse(savedPosition); elements.buttonPanel.style.left = position.left; elements.buttonPanel.style.top = position.top; elements.buttonPanel.style.right = 'auto'; // Clear right positioning when using left } else { // Initial position if no saved position exists elements.buttonPanel.style.top = '50px'; elements.buttonPanel.style.right = '10px'; } } catch (e) { console.error('Error loading saved position:', e); // Use default position on error elements.buttonPanel.style.top = '50px'; elements.buttonPanel.style.right = '10px'; } } // Setup event listeners for UI elements function setupEventListeners() { // Settings button elements.settingsButton.addEventListener('click', (e) => { e.stopPropagation(); // Prevent triggering drag when clicking button toggleSettingsPanel(); }); // Graph button - to be implemented elements.graphButton.addEventListener('click', (e) => { e.stopPropagation(); showGraphPanel(); }); // Swords button - to be implemented elements.swordsButton.addEventListener('click', (e) => { e.stopPropagation(); showCombatPanel(); }); // Test volume button const testVolumeButton = document.getElementById('testVolumeButton'); if (testVolumeButton) { testVolumeButton.addEventListener('click', () => { console.log('Test volume button clicked. Current master volume:', userSettings.masterAudioLevel); playSound(userSettings.autoAlertSoundURL, userSettings.masterAudioLevel); }); } // Reset settings button const resetSettingsButton = document.getElementById('resetSettingsButton'); if (resetSettingsButton) { resetSettingsButton.addEventListener('click', () => { if (confirm('Are you sure you want to reset all settings to default?')) { localStorage.removeItem('iqrpgSettings'); userSettings = Object.assign({}, defaultSettings); applySettingsToUI(); saveSettings(); alert('Settings have been reset to default.'); } }); } // Volume slider const volumeSlider = document.getElementById('volumeSlider'); if (volumeSlider) { volumeSlider.addEventListener('input', (event) => { userSettings.masterAudioLevel = event.target.value; saveSettings(); }); } // Checkbox settings setupSettingsToggleListeners(); // Make panel draggable setupPanelDragging(); // Start/stop dungeon button $('body').on('click', "button:contains('Start Dungeon')", function() { stopAlert(); }); // Keyboard shortcuts setupKeyboardShortcuts(); } // Show/hide settings panel function toggleSettingsPanel() { if (elements.settingsPanel.style.display === 'none' || elements.settingsPanel.style.display === '') { updateSettingsPanelPosition(); elements.settingsPanel.style.display = 'block'; } else { elements.settingsPanel.style.display = 'none'; } } // Graph panel implementation placeholder function showGraphPanel() { console.log('Graph panel functionality not yet implemented'); // To be implemented } // Combat panel implementation placeholder function showCombatPanel() { console.log('Combat panel functionality not yet implemented'); // To be implemented } // Setup toggle listeners for settings checkboxes function setupSettingsToggleListeners() { const toggleSettings = [ 'autoAudioAlert', 'autoDesktopAlert', 'dungeonAudioAlert', 'dungeonDesktopAlert', 'bossAudioAlert', 'bossDesktopAlert', 'eventAudioAlert', 'eventDesktopAlert', 'whisperAudioAlert', 'whisperDesktopAlert', 'landAudioAlert', 'masteryAudioAlert', 'effectAudioAlert', 'watchtowerAudioAlert', 'watchtowerDesktopAlert', 'bonusExpAudioAlert', 'eventAudioAlertFinished' ]; toggleSettings.forEach(setting => { const checkbox = document.getElementById(setting); if (checkbox) { checkbox.addEventListener('change', (event) => { userSettings[setting] = event.target.checked; saveSettings(); console.log(`${setting} set to ${event.target.checked}`); }); } else { console.log(`Checkbox element not found for setting: ${setting}`); } }); } // Setup panel dragging functionality function setupPanelDragging() { elements.buttonPanel.addEventListener('mousedown', (e) => { // Only start dragging if clicking on the panel background or button container if (e.target === elements.buttonPanel || e.target === elements.buttonContainer) { isDragging = true; dragOffsetX = e.clientX - elements.buttonPanel.getBoundingClientRect().left; dragOffsetY = e.clientY - elements.buttonPanel.getBoundingClientRect().top; elements.buttonPanel.classList.add('dragging'); } }); document.addEventListener('mousemove', (e) => { if (isDragging) { const newX = e.clientX - dragOffsetX; const newY = e.clientY - dragOffsetY; // Calculate limits to keep panel within screen bounds const maxX = window.innerWidth - elements.buttonPanel.offsetWidth; const maxY = window.innerHeight - elements.buttonPanel.offsetHeight; // Apply position with limits elements.buttonPanel.style.left = Math.max(0, Math.min(maxX, newX)) + 'px'; elements.buttonPanel.style.top = Math.max(0, Math.min(maxY, newY)) + 'px'; elements.buttonPanel.style.right = 'auto'; // Clear right positioning // If panel is near the right edge, reposition the settings panel updateSettingsPanelPosition(); } }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; elements.buttonPanel.classList.remove('dragging'); // Save panel position to localStorage for persistence const position = { left: elements.buttonPanel.style.left, top: elements.buttonPanel.style.top }; localStorage.setItem('iqrpgPanelPosition', JSON.stringify(position)); } }); // Stop dragging if mouse leaves the window document.addEventListener('mouseleave', () => { if (isDragging) { isDragging = false; elements.buttonPanel.classList.remove('dragging'); } }); } // Update settings panel position based on button panel position function updateSettingsPanelPosition() { const panelRect = elements.buttonPanel.getBoundingClientRect(); const screenWidth = window.innerWidth; // If panel is close to the right edge of the screen, position settings to the left if (panelRect.right + 250 > screenWidth) { // 250px is min-width of settings panel elements.settingsPanel.style.left = 'auto'; elements.settingsPanel.style.right = '-1px'; } else { elements.settingsPanel.style.left = '-1px'; elements.settingsPanel.style.right = 'auto'; } // Match the width to the button panel elements.settingsPanel.style.width = (elements.buttonPanel.offsetWidth + 2) + 'px'; } // Setup keyboard shortcuts function setupKeyboardShortcuts() { document.addEventListener('keyup', function(e) { if (e.altKey === true) { let index = -1; const channels = $('.chat-channels').children(); if (isNaN(e.key)) { let direction = null; if (e.which === 38) { // Up arrow direction = true; } else if (e.which === 40) { // Down arrow direction = false; } if (direction !== null) { let chatChannels = channels; if (e.shiftKey === true) { chatChannels = $('.chat-channels').children('.new-message, .active-channel'); } index = chatChannels.index($('.active-channel')); if (direction) { index--; } else { index++; } } } else { if (e.ctrlKey === true) { index = -1 + parseInt(e.key); } } if (index >= 0 && index < channels.length) { channels[index].click(); $('#chatInput').focus(); } } }); } // WebSocket interception function setupWebSocketInterception() { const OldSocket = window.WebSocket; window.WebSocket = function WebSocket(url, protocols) { console.log('IQRPG+ Socket Monitor Initilized...'); const socket = new OldSocket(...arguments); socket.addEventListener('message', handleWebSocketMessage); return socket; }; } // Handle WebSocket messages function handleWebSocketMessage(event) { try { const message = JSON.parse(event.data); switch(message.type){ case 'playersOnline': case 'loadMessages': case 'addItemsToUser': case 'notification': case 'bonus': break; case 'event': debugInfo('Event Data:'); debugInfo(message); handleGameEvent(message); break; case 'msg': handleChatMessage(message); break; case 'boss': break; default: debugInfo(message); } } catch (error) { console.error('Error handling WebSocket message:', error); } } // Handle game events (woodcutting, mining, etc) function handleGameEvent(message) { if (!message.data || !message.data.type) return; const eventType = message.data.type; const timeRemaining = message.data.timeRemaining || 0; const currentTime = Date.now(); // Create a unique signature for this event // Combine type, time remaining, and any other distinguishing factors const eventSignature = `${eventType}-${timeRemaining}`; // Check if we've seen this exact event recently (within 1 second) if (processedEvents.has(eventSignature)) { const lastProcessedTime = processedEvents.get(eventSignature); // Only ignore if the same exact event was processed very recently (500ms) // This prevents double notifications while still allowing rapid consecutive events if (currentTime - lastProcessedTime < 500) { debugInfo('Ignoring duplicate event: ' + eventSignature); return; } } // Update the processed events map with this event processedEvents.set(eventSignature, currentTime); // Clean up old events from the map (older than 10 seconds) processedEvents.forEach((timestamp, signature) => { if (currentTime - timestamp > 10000) { processedEvents.delete(signature); } }); if (eventType === "woodcutting" && userSettings.eventAlert_Woodcutting) { handleResourceEvent('Woodcutting', timeRemaining); } else if (eventType === "mining" && userSettings.eventAlert_Mining) { handleResourceEvent('Mining', timeRemaining); } else if (eventType === "quarrying" && userSettings.eventAlert_Quarrying) { handleResourceEvent('Quarrying', timeRemaining); } else { debugInfo('Unsupported Event - ' + eventType); } } // Handle resource events (common code for woodcutting, mining, quarrying) function handleResourceEvent(eventName, timeRemaining) { if (userSettings.eventAudioAlert) { playSound(userSettings.eventAlertSoundURL); } if (userSettings.eventDesktopAlert) { showNotification(`IQRPG Event!`, `${eventName} event has started!`); } // Set timeout for end of event setTimeout(function() { if (userSettings.eventAudioAlertFinished) { playSound(userSettings.eventAlertSoundURL); } if (userSettings.eventDesktopAlert) { showNotification(`IQRPG Event Finished!`, `${eventName} event has ended!`); } }, timeRemaining * 10); } // Handle chat messages function handleChatMessage(message) { if (!message.data || !message.data.type) return; switch(message.data.type) { case 'clanGlobal': if (message.data.msg && message.data.msg.startsWith('The watchtower')) { if (userSettings.watchtowerAudioAlert) { playSound(userSettings.watchtowerAlertSoundURL); } if (userSettings.watchtowerDesktopAlert) { showNotification('IQRPG Watchtower!', message.data.msg); } } break; case 'pm-from': handleWhisper(message); break; case 'eventGlobal': if (message.data.msg && message.data.msg.startsWith('A rift to the dark realm has opened')) { if (userSettings.eventAudioAlert) { playSound(userSettings.bossAlertSoundURL, 0.1); } if (userSettings.eventDesktopAlert) { showNotification('IQRPG Boss!', 'A rift to the dark realm has opened!'); } } break; case 'pm-to': case 'msg': case 'global': case 'me': break; default: debugInfo('Unsupported msg type:' + message.data.type); debugInfo(message); break; } } // Handle whispers/private messages function handleWhisper(message) { if (userSettings.whisperAlertOnlyWhenTabIsInactive) { if (document.hidden) { triggerWhisperAlert(message); } } else { triggerWhisperAlert(message); } } // Trigger whisper alerts (sound and notification) function triggerWhisperAlert(message) { if (userSettings.whisperAudioAlert) { playSound(userSettings.whisperAlertSoundURL); } if (userSettings.whisperDesktopAlert) { if (canSendDesktopAlert) { showNotification('IQRPG Whisper!', message.data.username + ': ' + message.data.msg); canSendDesktopAlert = false; setTimeout(() => { canSendDesktopAlert = true; }, 10000); } } } // Setup observers for DOM changes function setupObservers() { const MutationObserver = window.MutationObserver || window.WebKitMutationObserver; const obsConfig = { childList: true, characterData: true, attributes: true, subtree: true }; // Main observer for title changes and auto remaining observers.main = new MutationObserver(handleMutations); // Land observer for land timer observers.land = new MutationObserver(handleLandMutations); // Mastery observer for mastery levels observers.mastery = new MutationObserver(handleMasteryMutations); // Initialize observers with delay to ensure elements are available setTimeout(function() { try { const actionTimer = $("div.action-timer__text")[0]; const title = $("head title")[0]; if (actionTimer) { observers.main.observe(actionTimer, obsConfig); } if (title) { observers.main.observe(title, obsConfig); } if (userSettings.landAudioAlert) { const landSection = $(".main-section")[2]; if (landSection) { observers.land.observe(landSection, obsConfig); } } if (userSettings.masteryAudioAlert) { const masteries = $(".clickable > .flex.space-between > .green-text"); if (masteries.length > 0) { masteries.each(function(index) { observers.mastery.observe(masteries[index], obsConfig); }); } } } catch (error) { console.error("Error setting up observers:", error); } }, 500); } // Handle main mutations (title changes, auto remaining) function handleMutations(mutationRecords) { try { // Check for effects if (userSettings.effectAudioAlert) { checkEffects(); } // Check for bonus exp if (userSettings.bonusExpAudioAlert) { checkBonusExp(); } mutationRecords.forEach(function(mutation) { // Handle title changes if (mutation.type == "childList") { if (mutation.target.nodeName == "TITLE") { handleTitleChange(mutation.target.innerHTML); } } // Handle auto remaining changes if (mutation.type == "characterData") { handleAutoRemaining(mutation.target.data); } }); } catch (error) { console.error("Error handling mutations:", error); } } // Handle title changes function handleTitleChange(title) { console.log('Title changed to:', title); console.log('Current settings status - dungeonAudioAlert:', userSettings.dungeonAudioAlert, 'bossAudioAlert:', userSettings.bossAudioAlert, 'watchtowerAudioAlert:', userSettings.watchtowerAudioAlert); switch (title) { case 'Dungeon Complete Idle Quest RPG': if (userSettings.dungeonDesktopAlert) { showNotification('IQRPG Dungeon Alert!', 'You have completed your dungeon!'); } if (!isAlerting && userSettings.dungeonAudioAlert && canPlayMoreAlerts()) { console.log('Starting dungeon audio alert. dungeonAudioAlert setting:', userSettings.dungeonAudioAlert); startAlert(); } break; case 'Clan Boss Defeated Idle Quest RPG': if (userSettings.watchtowerDesktopAlert) { showNotification('IQRPG Watchtower Alert!', 'Your clan has defeated the boss!'); } if (userSettings.watchtowerAudioAlert && canPlayMoreAlerts()) { playSound(userSettings.bossDefeatedSoundURL); } break; case 'All Mobs Defeated Idle Quest RPG': if (userSettings.watchtowerDesktopAlert) { showNotification('IQRPG Watchtower Alert!', 'All mobs have been defeated!'); } if (!isAlerting && userSettings.watchtowerAudioAlert && canPlayMoreAlerts()) { startAlert(); } break; case 'Boss Defeated Idle Quest RPG': if (userSettings.bossDesktopAlert) { showNotification('IQRPG Boss Alert!', 'The boss has been defeated!'); } if (userSettings.bossAudioAlert) { playSound(userSettings.bossDefeatedSoundURL); } break; case 'ALERT': break; default: stopAlert(); currAutoAudioPlays = 0; } } // Handle auto remaining changes function handleAutoRemaining(data) { if (!data) return; try { const autosRemaining = parseInt(data.replace('Autos Remaining: ', '')); if (isNaN(autosRemaining)) return; if (autosRemaining <= userSettings.autoAlertNumber && userSettings.autoAlertNumber) { if (autosRemaining == userSettings.autoAlertNumber && userSettings.autoDesktopAlert) { showNotification('IQRPG Auto Alert!', 'You have ' + userSettings.autoAlertNumber + ' remaining!'); } // Only start sound alerts if auto audio alerts are enabled if (!isAlerting && userSettings.autoAudioAlert && canPlayMoreAlerts()) { console.log('Starting auto audio alert. autoAudioAlert setting:', userSettings.autoAudioAlert); startAlert(); } } else { stopAlert(); currAutoAudioPlays = 0; } } catch (error) { console.error("Error handling auto remaining:", error); } } // Handle land mutations function handleLandMutations(mutationRecords) { mutationRecords.forEach(function(mutation) { if (mutation.type == 'characterData') { if (mutation.target.data == '00:00') { if (userSettings.landAudioAlert) { playSound(userSettings.landAlertSoundURL); } } } }); } // Handle mastery mutations function handleMasteryMutations(mutationRecords) { mutationRecords.forEach(function(mutation) { if (mutation.type == 'characterData') { const level = parseInt(mutation.target.data); if (!isNaN(level) && level % userSettings.masteryEveryXLevels == 0) { if (userSettings.masteryAudioAlert) { playSound(userSettings.masteryAlertSoundURL); } } } }); } // Check for effects function checkEffects() { try { const effects = $(".main-section__body > div > .flex.space-between > .green-text"); if (!effects || !effects.length) return; effects.each(function(index) { const effectLeft = $(effects[index])[0].innerHTML; if (effectLeft == userSettings.effectAutoLeft) { if (userSettings.effectAudioAlert) { playSound(userSettings.effectAlertSoundURL); } } }); } catch (error) { console.error("Error checking effects:", error); } } // Check for bonus exp function checkBonusExp() { try { const bonusExpSpan = $('.main-section__body> div > div > div > span.exp-text'); const hasBonus = bonusExpSpan != null && bonusExpSpan.length != 0; if (hasBonus && !bonusExp) { bonusExp = true; if (userSettings.bonusExpAudioAlert) { playSound(userSettings.bonusExpAlertSoundURL); } } else if (!hasBonus && bonusExp) { bonusExp = false; } } catch (error) { console.error("Error checking bonus exp:", error); } } // Check if more alerts can be played function canPlayMoreAlerts() { return currAutoAudioPlays <= userSettings.autoMaxNumberOfAudioAlerts || userSettings.autoMaxNumberOfAudioAlerts == 0; } // Start alert sounds function startAlert() { if (isAlerting || !canPlayMoreAlerts()) return; isAlerting = true; currAutoAudioPlays++; // Check if auto audio alert is enabled before playing sound if (userSettings.autoAudioAlert) { playSound(userSettings.autoAlertSoundURL); } const repeatInterval = userSettings.autoAlertRepeatInSeconds * 1000; alertTimer = setInterval(() => { if (canPlayMoreAlerts()) { currAutoAudioPlays++; // Check if auto audio alert is enabled before playing sound if (userSettings.autoAudioAlert) { playSound(userSettings.autoAlertSoundURL); } } else { stopAlert(); } }, repeatInterval); } // Stop alert sounds function stopAlert() { isAlerting = false; if (alertTimer) { clearInterval(alertTimer); alertTimer = null; } } // Show desktop notification function showNotification(title, text) { if (desktopNotificationOnCooldown) return; try { desktopNotificationOnCooldown = true; setTimeout(() => { desktopNotificationOnCooldown = false; }, 7000); if (!("Notification" in window)) { console.error("This browser does not support desktop notifications"); return; } if (Notification.permission === "granted") { const notification = new Notification(title, { body: text }); setupNotificationBehavior(notification); } else if (Notification.permission !== "denied") { Notification.requestPermission().then(function(permission) { if (permission === "granted") { const notification = new Notification(title, { body: text }); setupNotificationBehavior(notification); } }); } } catch (error) { console.error("Error showing notification:", error); } } // Setup notification behavior function setupNotificationBehavior(notification) { if (!notification) return; notification.onclick = function() { window.focus(); this.close(); }; setTimeout(() => { if (notification) notification.close(); }, 7000); } // Play sound with volume control function playSound(sound, volume = null) { try { if (!sound) return; // Added debug log for sound playback console.log('Playing sound:', sound, 'with volume:', (volume !== null ? volume : userSettings.masterAudioLevel), 'Master audio level:', userSettings.masterAudioLevel); const audio = new Audio(sound); audio.volume = volume !== null ? volume : userSettings.masterAudioLevel; const playPromise = audio.play(); if (playPromise) { playPromise.catch(error => { console.error("Error playing sound:", error); }); } } catch (error) { console.error("Error playing sound:", error); } } // Debug logging function debugInfo(msg) { if (userSettings.showDebugInfo) { console.log(msg); } } return { init }; })(); // Initialize the script when the document is ready $(document).ready(function() { IQRPG_Plus.init(); });