// ==UserScript==
// @name NOT FUNCTIONAL CURRENTLY NEED TO FIX
// @namespace https://www.iqrpg.com/
// @version 1.3.8
// @description Advanced Audio Alerts with EASY instructions and Functional Overlay
// @Author Oatmilk (Formerly Grogu2484)
// @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
// @license MIT
// ==/UserScript==
/**************************************************************************************
* *
* YOU MAY EDIT ANYTHING BELOW THIS LINE UNTIL NEXT BOX LIKE THIS !!! *
* *
**************************************************************************************/
// QUICK ACCESS CONTROLS
// Quickly enable or disable specific game notification types
var NOTIFICATIONS_ENABLED = {
auto: true, // Auto timer notifications
dungeon: true, // Dungeon completion notifications
boss: true, // Boss fight notifications
events: true, // Skill events notifications
whispers: true, // Whisper notifications
land: true, // Land timer notifications
mastery: true, // Mastery milestone notifications
effects: true, // Effect expiration notifications
clan: true, // Clan-related notifications
bonusExp: true // Bonus EXP notifications
};
// SOUND PROFILE SELECTION
// Choose from: "default", "subtle", "game", "natural", "custom"
var SOUND_PROFILE = "default";
/*
SOUND PROFILE DETAILS:
----------------------
- default: Original game sounds (recommended)
- subtle: Quieter, less intrusive notifications
- game: Video game-style voice alerts
- natural: Nature-themed sound effects
- custom: Use your own sound URLs (configure below)
*/
// CUSTOM SOUND PROFILE CONFIGURATION
// ONLY USE IF ------> SOUND_PROFILE is set to "custom"
var CUSTOM_SOUND_URLS = {
auto: 'https://www.example.com/auto-sound.wav',
boss: 'https://www.example.com/boss-sound.wav',
bossDefeated: 'https://www.example.com/boss-defeated-sound.wav',
event: 'https://www.example.com/event-sound.wav',
whisper: 'https://www.example.com/whisper-sound.wav',
land: 'https://www.example.com/land-sound.wav',
mastery: 'https://www.example.com/mastery-sound.wav',
effect: 'https://www.example.com/effect-sound.wav',
watchtower: 'https://www.example.com/watchtower-sound.wav',
bonusExp: 'https://www.example.com/bonus-exp-sound.wav'
};
/*
MAIN CONTROLS - QUICK TOGGLE
Set to true --->ENABLES sounds, false<--- DISABLES sounds
-----------------------------------------------
*/
// MASTER TOGGLE - Set to false to disable ALL AUDIO(SOUND) notifications
var MASTER_AUDIO_ENABLED = true;
// MASTER TOGGLE - Set to false to disable ALL DESKTOP(Image) notifications
var MASTER_DESKTOP_ENABLED = true;
// VISUAL OVERLAY - Set to false to disable startup overlay
var SHOW_STARTUP_OVERLAY = true;
// VISUAL OVERLAY DURATION - How long to show the startup overlay (in seconds)
var OVERLAY_DURATION = 5;
/*
VOLUME CONTROL
--------------
Set the master volume for all sounds (Valid range: 0.0 to 1.0 (0 = silent, 1 = maximum volume))
*/
var MASTER_VOLUME = 0.5;
/*
INDIVIDUAL NOTIFICATION SETTINGS
-------------------------------
EDIT ALL THE SETTINGS BELOW! EACH SECTION CONTROLS A DIFFERENT TYPE OF NOTIFICATION
SET AUDIO ALERTS TO LOWERCASE true TO ENABLE SOUNDS, SET TO LOWERCASE false TO DISABLE AUDIO ALERTS
SET DESKTOP ALERTS TO LOWERCASE true TO ENABLE DESTOP NOTIFICATIONS, SET TO LOWERCASE false to DISABLE DESKTOP NOTIFICATIONS
DEBUG IS THE LAST EDITABLE SECTION. THERE IS A BIG PAGE BREAK SO YOU DO NOT EDIT BELOW THAT LINE OR IT WILL NOT WORK..
*/
// AUTO SECTION
var autoAudioAlert = true; // Audio alert when autos are low
var autoAlertSoundURL = 'https://www.myinstants.com/media/sounds/notification.mp3';
var autoAlertRepeatInSeconds = 10; // How often to repeat (1-60 seconds)
var autoAlertNumber = 100; // Alert at this number of autos (1-100)
var autoMaxNumberOfAudioAlerts = 0; // Maximum alerts (0 = unlimited)
var autoDesktopAlert = false; // Desktop notification when autos are low
// DUNGEON SECTION
var dungeonAudioAlert = true; // Audio alert when dungeon completes
var dungeonDesktopAlert = false; // Desktop notification for dungeon
// BOSS SECTION
var bossAudioAlert = true; // Audio alert for boss events
var bossAlertSoundURL = 'https://www.myinstants.com/media/sounds/alert.mp3';
var bossDefeatedSoundURL = 'https://www.myinstants.com/media/sounds/ff-victory.mp3';
var bossDesktopAlert = false; // Desktop notification for boss events
// EVENT SECTION
var eventDesktopAlert = true; // Desktop notification for events
var eventAlertSoundURL = 'https://www.myinstants.com/media/sounds/alert.mp3';
var eventAlert_Woodcutting = true; // Alert for woodcutting events
var eventAlert_Quarrying = true; // Alert for quarrying events
var eventAlert_Mining = true; // Alert for mining events
var eventAudioAlert = true; // Audio alert when events start
var eventAudioAlertFinished = false; // Audio alert when events finish
// WHISPER SECTION
var whisperAudioAlert = true; // Audio alert for whispers
var whisperAlertSoundURL = 'https://www.myinstants.com/media/sounds/ping.mp3';
var whisperAlertOnlyWhenTabIsInactive = false; // Only alert when tab not active
var whisperDesktopAlert = false; // Desktop notification for whispers
// LAND SECTION
var landAudioAlert = true; // Audio alert when land timer completes
var landAlertSoundURL = 'https://www.myinstants.com/media/sounds/coins.mp3';
// MASTERY SECTION
var masteryAudioAlert = true; // Audio alert for mastery milestones
var masteryEveryXLevels = 10; // Alert every X levels (1-100)
var masteryAlertSoundURL = 'https://www.myinstants.com/media/sounds/ff-victory.mp3';
// EFFECT SECTION
var effectAudioAlert = true; // Audio alert when effects expire
var effectAutoLeft = 5; // Alert at this many minutes (1-60)
var effectAlertSoundURL = 'https://www.myinstants.com/media/sounds/hammer.mp3';
// CLAN SECTION
var watchtowerAudioAlert = true; // Audio alert for watchtower events
var watchtowerAlertSoundURL = 'https://www.myinstants.com/media/sounds/alert.mp3';
var watchtowerDesktopAlert = false; // Desktop notification for watchtower
// BONUS EXP SECTION
var bonusExpAudioAlert = true; // Audio alert for bonus exp
var bonusExpAlertSoundURL = 'https://www.myinstants.com/media/sounds/magic.mp3';
// DEBUG SECTION
var showDebugInfo = true; // Show debug info in console
/**************************************************************************************
* *
* !! DO NOT EDIT ANYTHING BELOW THIS LINE !! *
* *
**************************************************************************************/
(function() {
'use strict';
// Additional configuration (managed by code)
var NOTIFICATION_DURATION = 7; // Notification display time in seconds
var LOW_RESOURCE_MODE = false; // Reduce CPU usage mode
var autoProgressiveAlerts = true; // Progressive auto alerts
var dungeonTimerAlert = true; // Dungeon timer alerts
var dungeonTimerMinutes = 5; // Alert minutes for dungeon timer
var bossDifferentSounds = true; // Different sounds for boss types
var eventReminderAlert = false; // Reminder alert for events
var eventUniqueSound = true; // Unique sounds per event type
var whisperUrgentMode = false; // Priority for whisper notifications
var whisperRepeatAlert = false; // Repeat alerts for whispers
var landTimerAlert = false; // Land timer alerts
var landTimerMinutes = 5; // Alert minutes for land timer
var masteryCelebrationMode = false; // Special celebration for mastery
var effectPriorityMode = false; // Prioritize important effects
var effectCustomPriority = []; // Priority effect names
var clanChatHighlight = false; // Highlight clan chat messages
var clanChatKeywords = []; // Keywords to highlight
var bonusExpBlinkingAlert = false; // Blinking alert for bonus exp
var enableVisualIndicators = true; // Visual indicators toggle - CHANGED TO TRUE
var visualAlertColor = "#ff5555"; // Visual alert color
var visualAlertPulse = true; // Pulse animation for alerts
var visualAlertCorner = "top-right"; // Position of visual alerts
var enableErrorLogging = true; // Log errors to console - CHANGED TO TRUE
var statsTracking = false; // Track notification stats
// State variables
let alerting = false;
let alertInterval = null;
let currentAutoAlerts = 0;
let canSendDesktopAlert = true;
let desktopNotificationCooldown = false;
let bonusExpActive = false;
let soundProfile = {};
let lastWhisperTime = 0;
let visualAlertElement = null;
let adaptedSelectors = {};
let notificationStats = {
total: 0,
auto: 0,
dungeon: 0,
boss: 0,
event: 0,
whisper: 0,
land: 0,
mastery: 0,
effect: 0,
clan: 0,
bonusExp: 0
};
// Debug function for testing sound playback
function testSoundPlayback() {
console.log("Testing sound playback...");
try {
const testSounds = [
{ name: "Auto", url: soundProfile.auto },
{ name: "Boss", url: soundProfile.boss },
{ name: "Event", url: soundProfile.event },
{ name: "Whisper", url: soundProfile.whisper }
];
let delay = 0;
testSounds.forEach(sound => {
setTimeout(() => {
console.log(`Testing ${sound.name} sound: ${sound.url}`);
const audio = new Audio(sound.url);
audio.volume = 0.5; // Lower volume for testing
// Add event listeners to diagnose issues
audio.addEventListener('play', () => {
console.log(`${sound.name} sound started playing`);
});
audio.addEventListener('ended', () => {
console.log(`${sound.name} sound completed`);
});
audio.addEventListener('error', (e) => {
console.error(`${sound.name} sound error:`, e);
showVisualAlert(`Sound Error: ${sound.name}`, 3000);
});
// Play the sound
audio.play().catch(error => {
console.error(`${sound.name} sound playback failed:`, error);
showVisualAlert(`Failed to play ${sound.name} sound`, 3000);
});
}, delay);
delay += 2000; // Add 2 second delay between sounds
});
} catch (error) {
console.error("Sound test failed:", error);
}
}
// Pass the real volume setting to the internal variable
var masterAudioLevel = MASTER_VOLUME;
// Create and show startup overlay
function showStartupOverlay() {
if (!SHOW_STARTUP_OVERLAY) return;
// Create overlay container
const overlay = document.createElement('div');
overlay.id = 'iqrpg-enhancer-overlay';
// Style the overlay
Object.assign(overlay.style, {
position: 'fixed',
top: '0',
left: '0',
width: '100%',
height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.8)',
zIndex: '10000',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
transition: 'opacity 1s ease-in-out',
opacity: '0'
});
// Create header
const header = document.createElement('div');
header.textContent = 'IQRPG AUDIO ENHANCER';
Object.assign(header.style, {
color: '#fff',
fontSize: '28px',
fontWeight: 'bold',
textAlign: 'center',
marginBottom: '20px',
fontFamily: 'Arial, sans-serif',
textShadow: '0 0 10px #00ccff, 0 0 20px #00ccff'
});
// Create status message
const status = document.createElement('div');
status.textContent = 'SUCCESSFULLY LOADED';
Object.assign(status.style, {
color: '#00ff00',
fontSize: '22px',
fontWeight: 'bold',
textAlign: 'center',
marginBottom: '30px',
fontFamily: 'Arial, sans-serif'
});
// Create version info
const version = document.createElement('div');
version.textContent = 'Version 1.2.2 (Fixed)';
Object.assign(version.style, {
color: '#aaaaaa',
fontSize: '16px',
textAlign: 'center',
marginBottom: '30px',
fontFamily: 'Arial, sans-serif'
});
// Create active modules section
const modules = document.createElement('div');
Object.assign(modules.style, {
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
maxWidth: '600px',
margin: '0 auto'
});
// Add module indicators
const moduleList = [
{ name: 'Auto Alerts', enabled: NOTIFICATIONS_ENABLED.auto && autoAudioAlert },
{ name: 'Dungeon Alerts', enabled: NOTIFICATIONS_ENABLED.dungeon && dungeonAudioAlert },
{ name: 'Boss Alerts', enabled: NOTIFICATIONS_ENABLED.boss && bossAudioAlert },
{ name: 'Event Alerts', enabled: NOTIFICATIONS_ENABLED.events && eventAudioAlert },
{ name: 'Whisper Alerts', enabled: NOTIFICATIONS_ENABLED.whispers && whisperAudioAlert },
{ name: 'Land Timer', enabled: NOTIFICATIONS_ENABLED.land && landAudioAlert },
{ name: 'Mastery Alerts', enabled: NOTIFICATIONS_ENABLED.mastery && masteryAudioAlert },
{ name: 'Effect Alerts', enabled: NOTIFICATIONS_ENABLED.effects && effectAudioAlert },
{ name: 'Clan Alerts', enabled: NOTIFICATIONS_ENABLED.clan && watchtowerAudioAlert },
{ name: 'Bonus EXP', enabled: NOTIFICATIONS_ENABLED.bonusExp && bonusExpAudioAlert }
];
moduleList.forEach(moduleInfo => {
const module = document.createElement('div');
module.textContent = moduleInfo.name;
Object.assign(module.style, {
padding: '8px 15px',
margin: '5px',
backgroundColor: moduleInfo.enabled ? 'rgba(0, 255, 0, 0.2)' : 'rgba(255, 0, 0, 0.2)',
border: `1px solid ${moduleInfo.enabled ? '#00ff00' : '#ff0000'}`,
borderRadius: '4px',
color: moduleInfo.enabled ? '#00ff00' : '#ff0000',
fontFamily: 'Arial, sans-serif',
fontSize: '14px'
});
modules.appendChild(module);
});
// Create sound profile info
const profileInfo = document.createElement('div');
profileInfo.textContent = `Active Sound Profile: ${SOUND_PROFILE.toUpperCase()}`;
Object.assign(profileInfo.style, {
color: '#ffffff',
fontSize: '16px',
textAlign: 'center',
marginTop: '30px',
fontFamily: 'Arial, sans-serif'
});
// Create volume info
const volumeInfo = document.createElement('div');
volumeInfo.textContent = `Volume: ${Math.round(MASTER_VOLUME * 100)}%`;
Object.assign(volumeInfo.style, {
color: '#ffffff',
fontSize: '16px',
textAlign: 'center',
marginTop: '10px',
fontFamily: 'Arial, sans-serif'
});
// Add test sound button
const testSoundButton = document.createElement('button');
testSoundButton.textContent = 'Test Sounds';
Object.assign(testSoundButton.style, {
marginTop: '20px',
padding: '8px 20px',
backgroundColor: '#007bff',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '14px',
fontFamily: 'Arial, sans-serif',
marginRight: '10px'
});
testSoundButton.addEventListener('mouseover', () => {
testSoundButton.style.backgroundColor = '#0069d9';
});
testSoundButton.addEventListener('mouseout', () => {
testSoundButton.style.backgroundColor = '#007bff';
});
testSoundButton.addEventListener('click', () => {
testSoundPlayback();
});
// Create closing note
const closingNote = document.createElement('div');
closingNote.textContent = `This overlay will close in ${OVERLAY_DURATION} seconds...`;
Object.assign(closingNote.style, {
color: '#aaaaaa',
fontSize: '14px',
textAlign: 'center',
marginTop: '40px',
fontFamily: 'Arial, sans-serif'
});
// Add close button
const closeButton = document.createElement('button');
closeButton.textContent = 'Close';
Object.assign(closeButton.style, {
marginTop: '20px',
padding: '8px 20px',
backgroundColor: '#444',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '14px',
fontFamily: 'Arial, sans-serif'
});
closeButton.addEventListener('mouseover', () => {
closeButton.style.backgroundColor = '#666';
});
closeButton.addEventListener('mouseout', () => {
closeButton.style.backgroundColor = '#444';
});
closeButton.addEventListener('click', () => {
hideStartupOverlay();
});
// Create button container
const buttonContainer = document.createElement('div');
Object.assign(buttonContainer.style, {
display: 'flex',
justifyContent: 'center',
marginTop: '20px'
});
buttonContainer.appendChild(testSoundButton);
buttonContainer.appendChild(closeButton);
// Assemble overlay
overlay.appendChild(header);
overlay.appendChild(status);
overlay.appendChild(version);
overlay.appendChild(modules);
overlay.appendChild(profileInfo);
overlay.appendChild(volumeInfo);
overlay.appendChild(buttonContainer);
overlay.appendChild(closingNote);
// Add overlay to document
document.body.appendChild(overlay);
// Fade in the overlay
setTimeout(() => {
overlay.style.opacity = '1';
}, 100);
// Set timeout to remove overlay
setTimeout(() => {
hideStartupOverlay();
}, OVERLAY_DURATION * 1000);
}
// Hide and remove the startup overlay
function hideStartupOverlay() {
const overlay = document.getElementById('iqrpg-enhancer-overlay');
if (!overlay) return;
// Fade out
overlay.style.opacity = '0';
// Remove after fade
setTimeout(() => {
if (overlay.parentNode) {
overlay.parentNode.removeChild(overlay);
}
}, 1000);
}
// Initialize sound profiles based on selected profile
function initSoundProfiles() {
// Default profile (updated more reliable sounds)
const defaultProfile = {
auto: 'https://www.myinstants.com/media/sounds/notification.mp3',
boss: 'https://www.myinstants.com/media/sounds/alert.mp3',
bossDefeated: 'https://www.myinstants.com/media/sounds/ff-victory.mp3',
event: 'https://www.myinstants.com/media/sounds/alert.mp3',
whisper: 'https://www.myinstants.com/media/sounds/ping.mp3',
land: 'https://www.myinstants.com/media/sounds/coins.mp3',
mastery: 'https://www.myinstants.com/media/sounds/ff-victory.mp3',
effect: 'https://www.myinstants.com/media/sounds/hammer.mp3',
watchtower: 'https://www.myinstants.com/media/sounds/alert.mp3',
bonusExp: 'https://www.myinstants.com/media/sounds/magic.mp3'
};
// Subtle profile (quieter, less intrusive sounds)
const subtleProfile = {
auto: 'https://www.myinstants.com/media/sounds/pristine-609.mp3',
boss: 'https://www.myinstants.com/media/sounds/interface-124464.mp3',
bossDefeated: 'https://www.myinstants.com/media/sounds/success-1-6297.mp3',
event: 'https://www.myinstants.com/media/sounds/click-21156.mp3',
whisper: 'https://www.myinstants.com/media/sounds/notification-sound-7062.mp3',
land: 'https://www.myinstants.com/media/sounds/coins-497.mp3',
mastery: 'https://www.myinstants.com/media/sounds/success-fanfare-trumpets-6185.mp3',
effect: 'https://www.myinstants.com/media/sounds/beep-6-96243.mp3',
watchtower: 'https://www.myinstants.com/media/sounds/click-for-my-website-345.mp3',
bonusExp: 'https://www.myinstants.com/media/sounds/short-success-sound-glockenspiel-treasure-video-game-6346.mp3'
};
// Game profile (video game sounds)
const gameProfile = {
auto: 'https://www.myinstants.com/media/sounds/mario-coin-sound.mp3',
boss: 'https://www.myinstants.com/media/sounds/zelda-secret.mp3',
bossDefeated: 'https://www.myinstants.com/media/sounds/ff-victory.mp3',
event: 'https://www.myinstants.com/media/sounds/zelda-secret.mp3',
whisper: 'https://www.myinstants.com/media/sounds/notification_2.mp3',
land: 'https://www.myinstants.com/media/sounds/chest-opening-sfx.mp3',
mastery: 'https://www.myinstants.com/media/sounds/level-up-sound-effect.mp3',
effect: 'https://www.myinstants.com/media/sounds/beep-7-88619.mp3',
watchtower: 'https://www.myinstants.com/media/sounds/Metal-Gear-Solid-Alert.mp3',
bonusExp: 'https://www.myinstants.com/media/sounds/enchant.mp3'
};
// Natural profile (nature sounds)
const naturalProfile = {
auto: 'https://www.myinstants.com/media/sounds/birds-singing-sound-effect.mp3',
boss: 'https://www.myinstants.com/media/sounds/thunder-38516.mp3',
bossDefeated: 'https://www.myinstants.com/media/sounds/birds-singing-sound-effect.mp3',
event: 'https://www.myinstants.com/media/sounds/rain-and-thunder-nature-sounds-7803.mp3',
whisper: 'https://www.myinstants.com/media/sounds/owl-hooting-sound-effect.mp3',
land: 'https://www.myinstants.com/media/sounds/river-stream-nature-sounds-7782.mp3',
mastery: 'https://www.myinstants.com/media/sounds/wind-sound-effect.mp3',
effect: 'https://www.myinstants.com/media/sounds/forest-wind-bird-sounds.mp3',
watchtower: 'https://www.myinstants.com/media/sounds/wolf-howl-sound-effect.mp3',
bonusExp: 'https://www.myinstants.com/media/sounds/waterfall-nature-sounds-7825.mp3'
};
// Custom profile (user defined sounds)
const customProfile = {
auto: CUSTOM_SOUND_URLS.auto || defaultProfile.auto,
boss: CUSTOM_SOUND_URLS.boss || defaultProfile.boss,
bossDefeated: CUSTOM_SOUND_URLS.bossDefeated || defaultProfile.bossDefeated,
event: CUSTOM_SOUND_URLS.event || defaultProfile.event,
whisper: CUSTOM_SOUND_URLS.whisper || defaultProfile.whisper,
land: CUSTOM_SOUND_URLS.land || defaultProfile.land,
mastery: CUSTOM_SOUND_URLS.mastery || defaultProfile.mastery,
effect: CUSTOM_SOUND_URLS.effect || defaultProfile.effect,
watchtower: CUSTOM_SOUND_URLS.watchtower || defaultProfile.watchtower,
bonusExp: CUSTOM_SOUND_URLS.bonusExp || defaultProfile.bonusExp
};
// Set active profile based on user selection
switch (SOUND_PROFILE.toLowerCase()) {
case "subtle":
soundProfile = subtleProfile;
break;
case "game":
soundProfile = gameProfile;
break;
case "natural":
soundProfile = naturalProfile;
break;
case "custom":
soundProfile = customProfile;
break;
default:
soundProfile = defaultProfile;
}
// Log sound profile for debugging
if (showDebugInfo) {
console.log("Active sound profile:", SOUND_PROFILE);
console.log("Sound URLs:", soundProfile);
}
}
// Play audio alert with the specified sound URL
function playAudioAlert(soundURL, volume = masterAudioLevel) {
if (!MASTER_AUDIO_ENABLED) return;
try {
console.log("Attempting to play sound:", soundURL);
// Preload the audio first
const audio = new Audio();
// Set up error handling
audio.onerror = function(e) {
console.error("Audio error:", e);
showVisualAlert("Sound playback error", 2000);
};
// Set up success handling
audio.oncanplaythrough = function() {
console.log("Sound loaded and ready to play");
// Set volume and play
audio.volume = volume;
// Try to play the sound
const playPromise = audio.play();
// Handle autoplay policy issues
if (playPromise !== undefined) {
playPromise.then(() => {
console.log("Sound playing successfully");
}).catch(error => {
console.error("Sound play failed:", error);
showVisualAlert("Browser blocked autoplay", 2000);
// Try an alternative approach for browsers with strict autoplay policies
document.addEventListener('click', function playOnClick() {
audio.play();
document.removeEventListener('click', playOnClick);
}, { once: true });
});
}
};
// Set the source after attaching event handlers
audio.src = soundURL;
if (statsTracking) {
notificationStats.total++;
}
} catch (error) {
console.error('Error creating audio object:', error);
showVisualAlert("Failed to create audio", 2000);
}
}
// Send desktop notification
function sendDesktopNotification(title, message, icon = null, duration = NOTIFICATION_DURATION * 1000) {
if (!MASTER_DESKTOP_ENABLED || !canSendDesktopAlert || desktopNotificationCooldown) return;
// Check if notifications are supported and permitted
if (!("Notification" in window)) {
if (showDebugInfo) {
console.log('Desktop notifications not supported in this browser');
}
return;
}
if (Notification.permission === "denied") {
if (showDebugInfo) {
console.log('Desktop notifications permission denied');
}
return;
}
// Request permission if not granted
if (Notification.permission !== "granted") {
Notification.requestPermission().then(permission => {
if (permission === "granted") {
// Try again after permission granted
sendDesktopNotification(title, message, icon, duration);
}
});
return;
}
// Set notification cooldown
desktopNotificationCooldown = true;
// Create and show notification
const options = {
body: message,
icon: icon || 'https://www.iqrpg.com/favicon.ico',
silent: true // We handle audio separately
};
const notification = new Notification(title, options);
// Close notification after duration
setTimeout(() => {
notification.close();
desktopNotificationCooldown = false;
}, duration);
}
// Display visual alert on screen
function showVisualAlert(message, duration = 3000) {
if (!enableVisualIndicators) return;
console.log("Showing visual alert:", message);
// Create alert element if it doesn't exist
if (!visualAlertElement) {
visualAlertElement = document.createElement('div');
visualAlertElement.id = 'iqrpg-audio-enhancer-alert';
// Apply styling based on user preferences
const style = {
position: 'fixed',
padding: '10px 15px',
backgroundColor: visualAlertColor,
color: '#fff',
fontWeight: 'bold',
zIndex: '9999',
borderRadius: '5px',
boxShadow: '0 2px 10px rgba(0,0,0,0.2)',
opacity: '0',
transition: 'opacity 0.3s ease-in-out'
};
// Position based on user preference
switch (visualAlertCorner) {
case 'top-left':
style.top = '10px';
style.left = '10px';
break;
case 'top-right':
style.top = '10px';
style.right = '10px';
break;
case 'bottom-left':
style.bottom = '10px';
style.left = '10px';
break;
case 'bottom-right':
style.bottom = '10px';
style.right = '10px';
break;
default:
style.top = '10px';
style.right = '10px';
}
// Apply styles
Object.assign(visualAlertElement.style, style);
// Add pulse animation if enabled
if (visualAlertPulse) {
visualAlertElement.style.animation = 'iqrpg-pulse 1s infinite';
// Add keyframes for pulse animation
const styleSheet = document.createElement('style');
styleSheet.textContent = `
@keyframes iqrpg-pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
`;
document.head.appendChild(styleSheet);
}
document.body.appendChild(visualAlertElement);
}
// Update message and show alert
visualAlertElement.textContent = message;
visualAlertElement.style.opacity = '1';
// Hide after duration
setTimeout(() => {
visualAlertElement.style.opacity = '0';
}, duration);
}
// Handle title change (for whispers and other notifications)
function handleTitleChange() {
const title = document.title;
if (showDebugInfo) {
console.log("Title changed:", title);
}
// Check for whisper notifications
if (NOTIFICATIONS_ENABLED.whispers && title.includes('Whisper')) {
handleWhisperNotification();
}
// Check for event notifications
if (NOTIFICATIONS_ENABLED.events && title.includes('Event')) {
handleEventNotification();
}
// Check for bonus exp notifications
if (NOTIFICATIONS_ENABLED.bonusExp && title.includes('Bonus EXP')) {
handleBonusExpNotification();
}
}
// Handle whisper notifications
function handleWhisperNotification() {
if (!whisperAudioAlert) return;
console.log("Whisper notification triggered");
// Don't alert if tab is active and setting requires inactive tab
if (whisperAlertOnlyWhenTabIsInactive && document.visibilityState === 'visible') {
console.log("Tab is active, skipping whisper alert");
return;
}
// Check time since last whisper alert to prevent spam
const now = Date.now();
if (now - lastWhisperTime < 3000) { // 3 second cooldown
console.log("Whisper alert on cooldown");
return;
}
lastWhisperTime = now;
// Play whisper sound
playAudioAlert(soundProfile.whisper);
// Update stats if enabled
if (statsTracking) {
notificationStats.whisper++;
}
// Send desktop notification if enabled
if (whisperDesktopAlert) {
sendDesktopNotification('IQRPG Whisper', 'You received a new whisper!');
}
// Show visual indicator if enabled
if (enableVisualIndicators) {
showVisualAlert('New Whisper');
}
}
// Handle event notifications
function handleEventNotification() {
if (!eventAudioAlert) return;
console.log("Event notification triggered");
// Extract event type from title if possible
const title = document.title;
let eventType = 'unknown';
if (title.includes('Woodcutting') && eventAlert_Woodcutting) {
eventType = 'woodcutting';
} else if (title.includes('Quarrying') && eventAlert_Quarrying) {
eventType = 'quarrying';
} else if (title.includes('Mining') && eventAlert_Mining) {
eventType = 'mining';
} else {
// Unknown event type or disabled
console.log("Unknown or disabled event type");
return;
}
console.log(`Event type: ${eventType}`);
// Play event sound
playAudioAlert(soundProfile.event);
// Update stats if enabled
if (statsTracking) {
notificationStats.event++;
}
// Send desktop notification if enabled
if (eventDesktopAlert) {
sendDesktopNotification('IQRPG Event', `A ${eventType} event has started!`);
}
// Show visual indicator if enabled
if (enableVisualIndicators) {
showVisualAlert(`${eventType.charAt(0).toUpperCase() + eventType.slice(1)} Event Started`);
}
}
// Handle bonus exp notifications
function handleBonusExpNotification() {
if (!bonusExpAudioAlert || bonusExpActive) return;
console.log("Bonus EXP notification triggered");
bonusExpActive = true;
// Play bonus exp sound
playAudioAlert(soundProfile.bonusExp);
// Update stats if enabled
if (statsTracking) {
notificationStats.bonusExp++;
}
// Add blinking indicator if enabled
if (bonusExpBlinkingAlert) {
const favicon = document.querySelector('link[rel="icon"]');
if (favicon) {
// Remember original favicon
const originalFavicon = favicon.href;
// Create alternate favicon (simple colored square)
const canvas = document.createElement('canvas');
canvas.width = 16;
canvas.height = 16;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#ffcc00';
ctx.fillRect(0, 0, 16, 16);
const alternateFavicon = canvas.toDataURL();
// Set up blinking interval
let blinkState = false;
const blinkInterval = setInterval(() => {
favicon.href = blinkState ? originalFavicon : alternateFavicon;
blinkState = !blinkState;
}, 500);
// Stop blinking after 10 seconds
setTimeout(() => {
clearInterval(blinkInterval);
favicon.href = originalFavicon;
bonusExpActive = false;
}, 10000);
}
} else {
// Reset active state after delay
setTimeout(() => {
bonusExpActive = false;
}, 10000);
}
// Show visual indicator if enabled
if (enableVisualIndicators) {
showVisualAlert('Bonus EXP Active!');
}
}
// Handle dungeon completion notification
function handleDungeonCompletion() {
if (!NOTIFICATIONS_ENABLED.dungeon || !dungeonAudioAlert) return;
console.log("Dungeon completion triggered");
// Play dungeon completion sound
playAudioAlert(soundProfile.boss);
// Update stats if enabled
if (statsTracking) {
notificationStats.dungeon++;
}
// Send desktop notification if enabled
if (dungeonDesktopAlert) {
sendDesktopNotification('IQRPG Dungeon', 'Dungeon run has completed!');
}
// Show visual indicator if enabled
if (enableVisualIndicators) {
showVisualAlert('Dungeon Completed');
}
}
// Handle boss notification
function handleBossNotification(type = 'normal') {
if (!NOTIFICATIONS_ENABLED.boss || !bossAudioAlert) return;
console.log(`Boss notification triggered: ${type}`);
// Determine which sound to play based on boss type
let soundUrl = soundProfile.boss;
if (type === 'defeated' && bossDifferentSounds) {
soundUrl = soundProfile.bossDefeated;
}
// Play boss sound
playAudioAlert(soundUrl);
// Update stats if enabled
if (statsTracking) {
notificationStats.boss++;
}
// Send desktop notification if enabled
if (bossDesktopAlert) {
const message = type === 'defeated' ? 'Boss has been defeated!' : 'Boss fight in progress!';
sendDesktopNotification('IQRPG Boss', message);
}
// Show visual indicator if enabled
if (enableVisualIndicators) {
const message = type === 'defeated' ? 'Boss Defeated!' : 'Boss Fight!';
showVisualAlert(message);
}
}
// Handle autos remaining change
function handleAutosRemainingChange(count) {
if (!NOTIFICATIONS_ENABLED.auto || !autoAudioAlert) return;
// Convert to number if it's a string
const autosRemaining = typeof count === 'string' ? parseInt(count, 10) : count;
if (showDebugInfo) {
console.log(`Autos remaining: ${autosRemaining}`);
}
// Check if we should alert
if (isNaN(autosRemaining) || autosRemaining > autoAlertNumber) {
// Reset alert count if autos are above threshold
currentAutoAlerts = 0;
// Clear alert interval if it exists
if (alertInterval) {
clearInterval(alertInterval);
alertInterval = null;
alerting = false;
}
return;
}
// Don't start a new alert if we're already alerting
if (alerting) return;
// Check if we've reached the maximum number of alerts
if (autoMaxNumberOfAudioAlerts > 0 && currentAutoAlerts >= autoMaxNumberOfAudioAlerts) {
return;
}
// Start alerting
alerting = true;
console.log(`Auto alert triggered: ${autosRemaining} autos remaining`);
// Play auto alert sound
playAudioAlert(soundProfile.auto);
currentAutoAlerts++;
// Update stats if enabled
if (statsTracking) {
notificationStats.auto++;
}
// Send desktop notification if enabled
if (autoDesktopAlert) {
sendDesktopNotification('IQRPG Auto Alert', `Only ${autosRemaining} autos remaining!`);
}
// Show visual indicator if enabled
if (enableVisualIndicators) {
showVisualAlert(`${autosRemaining} Autos Left`);
}
// Set up repeat alert if configured
if (autoAlertRepeatInSeconds > 0) {
alertInterval = setInterval(() => {
// Check if we've reached the maximum number of alerts
if (autoMaxNumberOfAudioAlerts > 0 && currentAutoAlerts >= autoMaxNumberOfAudioAlerts) {
clearInterval(alertInterval);
alertInterval = null;
alerting = false;
return;
}
// Play auto alert sound again
playAudioAlert(soundProfile.auto);
currentAutoAlerts++;
// Update stats if enabled
if (statsTracking) {
notificationStats.auto++;
}
}, autoAlertRepeatInSeconds * 1000);
} else {
alerting = false;
}
}
// Handle mastery level change
function handleMasteryLevelChange(level) {
if (!NOTIFICATIONS_ENABLED.mastery || !masteryAudioAlert) return;
// Convert to number if it's a string
const masteryLevel = typeof level === 'string' ? parseInt(level, 10) : level;
if (showDebugInfo) {
console.log(`Mastery level: ${masteryLevel}`);
}
// Check if this level is a milestone
if (isNaN(masteryLevel) || masteryLevel % masteryEveryXLevels !== 0) {
return;
}
console.log(`Mastery milestone reached: ${masteryLevel}`);
// Play mastery sound
playAudioAlert(soundProfile.mastery);
// Update stats if enabled
if (statsTracking) {
notificationStats.mastery++;
}
// Show visual indicator if enabled
if (enableVisualIndicators) {
showVisualAlert(`Mastery Level ${masteryLevel}!`);
}
// Special celebration if enabled
if (masteryCelebrationMode) {
// Create a temporary celebration overlay
const overlay = document.createElement('div');
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.backgroundColor = 'rgba(0,0,0,0.7)';
overlay.style.zIndex = '10000';
overlay.style.display = 'flex';
overlay.style.justifyContent = 'center';
overlay.style.alignItems = 'center';
overlay.style.flexDirection = 'column';
const message = document.createElement('div');
message.style.color = '#ffcc00';
message.style.fontSize = '32px';
message.style.fontWeight = 'bold';
message.style.textShadow = '0 0 10px rgba(255,204,0,0.7)';
message.textContent = `MASTERY LEVEL ${masteryLevel}!`;
overlay.appendChild(message);
document.body.appendChild(overlay);
// Remove overlay after a short delay
setTimeout(() => {
overlay.style.transition = 'opacity 1s ease-out';
overlay.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(overlay);
}, 1000);
}, 2000);
}
}
// Handle effect expiration
function handleEffectExpiration(effectName, minutesLeft) {
if (!NOTIFICATIONS_ENABLED.effects || !effectAudioAlert) return;
if (showDebugInfo) {
console.log(`Effect: ${effectName}, minutes left: ${minutesLeft}`);
}
// Check priority mode
if (effectPriorityMode && effectCustomPriority.length > 0) {
if (!effectCustomPriority.some(name => effectName.includes(name))) {
return;
}
}
// Convert to number if it's a string
const minsLeft = typeof minutesLeft === 'string' ? parseInt(minutesLeft, 10) : minutesLeft;
// Check if the effect is about to expire
if (isNaN(minsLeft) || minsLeft > effectAutoLeft) {
return;
}
console.log(`Effect expiring soon: ${effectName}, ${minsLeft} min remaining`);
// Play effect expiration sound
playAudioAlert(soundProfile.effect);
// Update stats if enabled
if (statsTracking) {
notificationStats.effect++;
}
// Show visual indicator if enabled
if (enableVisualIndicators) {
showVisualAlert(`${effectName} expires in ${minsLeft} min`);
}
}
// Handle land timer completion
function handleLandTimerCompletion() {
if (!NOTIFICATIONS_ENABLED.land || !landAudioAlert) return;
console.log("Land timer completed");
// Play land timer sound
playAudioAlert(soundProfile.land);
// Update stats if enabled
if (statsTracking) {
notificationStats.land++;
}
// Show visual indicator if enabled
if (enableVisualIndicators) {
showVisualAlert('Land Timer Completed');
}
}
// Handle clan watchtower notification
function handleWatchtowerNotification() {
if (!NOTIFICATIONS_ENABLED.clan || !watchtowerAudioAlert) return;
console.log("Watchtower notification triggered");
// Play watchtower sound
playAudioAlert(soundProfile.watchtower);
// Update stats if enabled
if (statsTracking) {
notificationStats.clan++;
}
// Send desktop notification if enabled
if (watchtowerDesktopAlert) {
sendDesktopNotification('IQRPG Clan', 'Watchtower Alert!');
}
// Show visual indicator if enabled
if (enableVisualIndicators) {
showVisualAlert('Clan Watchtower Alert');
}
}
// Find and process game elements
function setupGameSelectors() {
// Force enable debug logging during setup
const originalDebugSetting = showDebugInfo;
showDebugInfo = true;
console.log("Setting up game selectors...");
// Determine which version of the game UI is being used
const isNewUI = document.querySelector('.game-panel-x') !== null;
console.log("Detected UI version:", isNewUI ? "New" : "Classic");
// Set up selectors based on UI version
if (isNewUI) {
// NEW UI SELECTORS
adaptedSelectors = {
autosRemaining: '.auto-label, .auto-counter, [class*="auto"]',
dungeonComplete: '.dungeon-complete-indicator, .dungeon-result, [class*="dungeon"]',
bossStatus: '.boss-status-indicator, .boss-result, [class*="boss"]',
masteryLevel: '.mastery-level-display, .mastery-level, [class*="mastery"]',
effectsContainer: '.active-effects-panel, .effects-list, [class*="effect"]',
landTimer: '.land-timer-display, .land-timer, [class*="land"]',
chatContainer: '.chat-container-x, .chat-area, [class*="chat"]'
};
} else {
// CLASSIC UI SELECTORS
adaptedSelectors = {
autosRemaining: '#autosRemaining, .autoDisplay, [class*="auto"]',
dungeonComplete: '.dungeon-complete, #dungeonComplete, [class*="dungeon"]',
bossStatus: '.boss-status, #bossStatus, [class*="boss"]',
masteryLevel: '#masteryLevel, .masteryLevel, [class*="mastery"]',
effectsContainer: '.effects-container, #effectsContainer, [class*="effect"]',
landTimer: '#landTimer, .landTimer, [class*="land"]',
chatContainer: '.chat-container, #chatContainer, [class*="chat"]'
};
}
// Log selectors for debugging
console.log("Game selectors set up:", adaptedSelectors);
// Add fallback selectors for common elements
console.log("Adding fallback selectors");
const fallbackSelectors = {
autosRemaining: document.querySelectorAll('[data-id="autos"], [data-attribute="autos"], [id*="auto"], [class*="auto"]'),
dungeonComplete: document.querySelectorAll('[data-id="dungeon"], [data-attribute="dungeon"], [id*="dungeon"], [class*="dungeon"]'),
bossStatus: document.querySelectorAll('[data-id="boss"], [data-attribute="boss"], [id*="boss"], [class*="boss"]'),
masteryLevel: document.querySelectorAll('[data-id="mastery"], [data-attribute="mastery"], [id*="mastery"], [class*="mastery"]'),
effectsContainer: document.querySelectorAll('[data-id="effects"], [data-attribute="effects"], [id*="effect"], [class*="effect"]'),
landTimer: document.querySelectorAll('[data-id="land"], [data-attribute="land"], [id*="land"], [class*="land"]'),
chatContainer: document.querySelectorAll('[data-id="chat"], [data-attribute="chat"], [id*="chat"], [class*="chat"]')
};
// Log the number of elements found for each fallback selector
for (const key in fallbackSelectors) {
console.log(`Fallback ${key} elements found: ${fallbackSelectors[key].length}`);
}
// Restore original debug setting
showDebugInfo = originalDebugSetting;
return isNewUI;
}
// Observe changes to game elements
function setupObservers() {
// Initialize sound profiles
initSoundProfiles();
// Set up game selectors
const isNewUI = setupGameSelectors();
console.log("Setting up observers...");
// Create a function to handle title changes
const titleChangeHandler = () => {
handleTitleChange();
};
// Set up title observer for title-based notifications
const titleObserver = new MutationObserver(titleChangeHandler);
// Observe title changes
titleObserver.observe(document.querySelector('title') || document.head, {
subtree: true,
characterData: true,
childList: true,
attributes: true
});
console.log("Title observer set up");
// Also listen for the visibilitychange event to handle tab focus changes
document.addEventListener('visibilitychange', titleChangeHandler);
// Helper function to safely find elements
function safeQuerySelector(selector) {
try {
// First try with the exact selector
let element = document.querySelector(selector);
if (!element && selector.includes(',')) {
// If the selector contains multiple options, try them one by one
const selectors = selector.split(',').map(s => s.trim());
for (const s of selectors) {
element = document.querySelector(s);
if (element) {
console.log(`Found element using selector: ${s}`);
break;
}
}
}
if (!element) {
console.warn(`Element not found with selector ${selector}`);
}
return element;
} catch (error) {
console.error(`Error finding element with selector ${selector}:`, error);
return null;
}
}
// Helper function to safely create observers
function safeObserve(element, callback, options = {}) {
if (!element) {
console.warn("Cannot observe null element");
return null;
}
try {
const observer = new MutationObserver(callback);
observer.observe(element, {
subtree: true,
characterData: true,
childList: true,
attributes: true,
...options
});
console.log("Observer set up for", element);
return observer;
} catch (error) {
console.error('Error setting up observer:', error);
return null;
}
}
// Set up a global observer to watch for dynamically added elements
const bodyObserver = new MutationObserver((mutations) => {
// Check if we need to find game elements that weren't initially available
for (const [key, selector] of Object.entries(adaptedSelectors)) {
if (!document.querySelector(selector)) {
// Try to find the element again
const element = safeQuerySelector(selector);
if (element) {
console.log(`Found previously missing element for ${key}`);
// Set up the appropriate observer based on the element type
switch (key) {
case 'autosRemaining':
setupAutoObserver(element);
break;
case 'dungeonComplete':
setupDungeonObserver(element);
break;
case 'bossStatus':
setupBossObserver(element);
break;
case 'masteryLevel':
setupMasteryObserver(element);
break;
case 'effectsContainer':
setupEffectsObserver(element);
break;
case 'landTimer':
setupLandObserver(element);
break;
case 'chatContainer':
setupChatObserver(element);
break;
}
}
}
}
});
// Start observing the body for changes
bodyObserver.observe(document.body, {
subtree: true,
childList: true
});
console.log("Body observer set up");
// Setup function for auto observer
function setupAutoObserver(element) {
safeObserve(element, mutations => {
for (const mutation of mutations) {
if (mutation.type === 'characterData' || mutation.type === 'childList') {
// Extract number from text content
const text = element.textContent;
const match = text.match(/(\d+)/);
if (match) {
handleAutosRemainingChange(parseInt(match[1], 10));
}
}
}
});
}
// Setup function for dungeon observer
function setupDungeonObserver(element) {
safeObserve(element, mutations => {
for (const mutation of mutations) {
if (mutation.type === 'attributes' || mutation.type === 'childList') {
// Check if dungeon complete indicator is visible
if ((element.style.display !== 'none' && element.classList.contains('active')) ||
element.textContent.toLowerCase().includes('complete') ||
element.textContent.toLowerCase().includes('finished')) {
handleDungeonCompletion();
}
}
}
});
}
// Setup function for boss observer
function setupBossObserver(element) {
safeObserve(element, mutations => {
for (const mutation of mutations) {
if (mutation.type === 'attributes' || mutation.type === 'childList') {
// Check boss status text
const text = element.textContent.toLowerCase();
if (text.includes('defeated') || text.includes('victory')) {
handleBossNotification('defeated');
} else if (text.includes('fight') || text.includes('in progress') || text.includes('battle')) {
handleBossNotification('normal');
}
}
}
});
}
// Setup function for mastery observer
function setupMasteryObserver(element) {
safeObserve(element, mutations => {
for (const mutation of mutations) {
if (mutation.type === 'characterData' || mutation.type === 'childList') {
// Extract level from text content
const text = element.textContent;
const match = text.match(/(\d+)/);
if (match) {
handleMasteryLevelChange(parseInt(match[1], 10));
}
}
}
});
}
// Setup function for effects observer
function setupEffectsObserver(element) {
safeObserve(element, mutations => {
for (const mutation of mutations) {
if (mutation.type === 'childList') {
// Process each effect element
const effectElements = element.querySelectorAll('.effect, [class*="effect"]');
effectElements.forEach(effect => {
try {
const nameElement = effect.querySelector('.effect-name, [class*="name"]');
const timeElement = effect.querySelector('.effect-time, [class*="time"]');
if (nameElement && timeElement) {
const effectName = nameElement.textContent.trim();
const timeText = timeElement.textContent.trim();
// Extract minutes remaining
const minutesMatch = timeText.match(/(\d+)m/);
if (minutesMatch) {
const minutes = parseInt(minutesMatch[1], 10);
handleEffectExpiration(effectName, minutes);
}
}
} catch (error) {
console.error('Error processing effect element:', error);
}
});
}
}
});
}
// Setup function for land observer
function setupLandObserver(element) {
safeObserve(element, mutations => {
for (const mutation of mutations) {
if (mutation.type === 'characterData' || mutation.type === 'childList') {
// Check if timer is completed (displays "Ready")
const text = element.textContent.trim().toLowerCase();
if (text.includes('ready') || text.includes('0:00') || text.includes('completed')) {
handleLandTimerCompletion();
}
}
}
});
}
// Setup function for chat observer
function setupChatObserver(element) {
safeObserve(element, mutations => {
for (const mutation of mutations) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
// Process new chat messages
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE && node.classList) {
// Check for clan messages
if (NOTIFICATIONS_ENABLED.clan &&
(node.classList.contains('clan-message') ||
node.querySelector('.clan-tag, [class*="clan"]'))) {
// Check for watchtower alerts
const text = node.textContent.toLowerCase();
if (text.includes('watchtower') || text.includes('attack')) {
handleWatchtowerNotification();
}
// Highlight clan chat if enabled
if (clanChatHighlight && clanChatKeywords.length > 0) {
for (const keyword of clanChatKeywords) {
if (text.includes(keyword.toLowerCase())) {
node.style.backgroundColor = 'rgba(255, 255, 0, 0.2)';
node.style.borderLeft = '3px solid #ffcc00';
break;
}
}
}
}
// Check for whispers
if (NOTIFICATIONS_ENABLED.whispers &&
(node.classList.contains('whisper') ||
node.classList.contains('whisper-message') ||
node.querySelector('.whisper-tag, [class*="whisper"]'))) {
handleWhisperNotification();
}
}
});
}
}
});
}
// Try to set up observers for each element
console.log("Setting up element-specific observers");
// Observe autos remaining
const autosElement = safeQuerySelector(adaptedSelectors.autosRemaining);
if (autosElement) {
setupAutoObserver(autosElement);
}
// Observe dungeon completion
const dungeonElement = safeQuerySelector(adaptedSelectors.dungeonComplete);
if (dungeonElement) {
setupDungeonObserver(dungeonElement);
}
// Observe boss status
const bossElement = safeQuerySelector(adaptedSelectors.bossStatus);
if (bossElement) {
setupBossObserver(bossElement);
}
// Observe mastery level
const masteryElement = safeQuerySelector(adaptedSelectors.masteryLevel);
if (masteryElement) {
setupMasteryObserver(masteryElement);
}
// Observe active effects
const effectsElement = safeQuerySelector(adaptedSelectors.effectsContainer);
if (effectsElement) {
setupEffectsObserver(effectsElement);
}
// Observe land timer
const landElement = safeQuerySelector(adaptedSelectors.landTimer);
if (landElement) {
setupLandObserver(landElement);
}
// Observe chat for clan notifications
const chatElement = safeQuerySelector(adaptedSelectors.chatContainer);
if (chatElement) {
setupChatObserver(chatElement);
}
console.log("Observers setup complete");
// Try triggering some artificial alerts to test if everything works
setTimeout(() => {
console.log("Testing alerts...");
showVisualAlert("Script loaded successfully!", 3000);
}, 3000);
}
// Add settings button to game UI
function addSettingsButton() {
try {
console.log("Adding settings button...");
// Create settings button
const settingsButton = document.createElement('div');
settingsButton.id = 'iqrpg-audio-enhancer-settings';
settingsButton.title = 'Audio Enhancer Settings';
// Style the button
Object.assign(settingsButton.style, {
position: 'fixed',
top: '10px',
right: '10px',
width: '24px',
height: '24px',
backgroundColor: '#333',
borderRadius: '50%',
cursor: 'pointer',
zIndex: '9999',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
boxShadow: '0 2px 5px rgba(0,0,0,0.2)',
border: '2px solid #555'
});
// Add speaker icon
settingsButton.innerHTML = `
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2">
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
<path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path>
</svg>
`;
// Toggle mute on click
settingsButton.addEventListener('click', function() {
MASTER_AUDIO_ENABLED = !MASTER_AUDIO_ENABLED;
// Update button appearance
if (!MASTER_AUDIO_ENABLED) {
this.innerHTML = `
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#ff5555" stroke-width="2">
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
<line x1="23" y1="9" x2="17" y2="15"></line>
<line x1="17" y1="9" x2="23" y2="15"></line>
</svg>
`;
this.style.borderColor = '#ff5555';
// Show notification
showVisualAlert('Audio Alerts Disabled', 2000);
} else {
this.innerHTML = `
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2">
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
<path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path>
</svg>
`;
this.style.borderColor = '#555';
// Play test sound and show notification
playAudioAlert(soundProfile.auto, 0.5);
showVisualAlert('Audio Alerts Enabled', 2000);
}
// Log state change
console.log('Audio alerts:', MASTER_AUDIO_ENABLED ? 'enabled' : 'disabled');
});
// Add button to document
document.body.appendChild(settingsButton);
console.log('Settings button added');
} catch (error) {
console.error('Error adding settings button:', error);
}
}
// Check game version and compatibility
function checkGameVersion() {
try {
// Look for version indicator
const versionElement = document.querySelector('.version, [class*="version"]');
if (versionElement) {
const gameVersion = versionElement.textContent.trim();
console.log('Detected IQRPG version:', gameVersion);
// Check if we need to adapt to a new version
if (gameVersion.includes('beta') || parseFloat(gameVersion) >= 0.5) {
// Re-setup observers with updated selectors
setupGameSelectors();
setupObservers();
console.log('Adapted to new game version');
}
} else {
console.log('Game version element not found, using default selectors');
}
} catch (error) {
console.error('Error checking game version:', error);
}
}
// Initialization on document ready
$(document).ready(function() {
try {
console.log("IQRPG Audio Enhancer starting...");
// Use appropriate delay based on resource mode
const initDelay = LOW_RESOURCE_MODE ? 1000 : 500;
setTimeout(() => {
// Self-healing mechanism - detect script startup failures
try {
setupObservers();
addSettingsButton();
// Show startup overlay to confirm script is loaded
showStartupOverlay();
// Show initialization message in console
console.log('IQRPG Audio Enhancer initialized!');
// Log active sound profile
console.log('Active sound profile:', SOUND_PROFILE);
console.log('Master volume:', MASTER_VOLUME);
console.log('Adapted selectors:', adaptedSelectors);
// Initial visual alert if enabled
if (enableVisualIndicators) {
showVisualAlert('IQRPG Audio Enhancer activated!', 3000);
}
// Add version check mechanism
checkGameVersion();
// Add browser interaction event to help with autoplay restrictions
document.addEventListener('click', function triggerUserInteraction() {
console.log("User interaction detected - audio should now work");
// Play a silent sound to unlock audio
const silentSound = new Audio("data:audio/mp3;base64,SUQzBAAAAAABEVRYWFgAAAAtAAADY29tbWVudABCaWdTb3VuZEJhbmsuY29tIC8gTGFTb25vdGhlcXVlLm9yZwBURU5DAAAAHQAAA1N3aXRjaCBQbHVzIMKpIE5DSCBTb2Z0d2FyZQBUSVQyAAAABgAAAzIyMzUAVFNTRQAAAA8AAANMYXZmNTcuODMuMTAwAAAAAAAAAAAAAAD/80DEAAAAA0gAAAAATEFNRTMuMTAwVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/zQsRbAAADSAAAAABVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/zQMSkAAADSAAAAABVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV");
silentSound.play().catch(e => console.log("Silent sound playback failed:", e));
// Remove the event listener after first click
document.removeEventListener('click', triggerUserInteraction);
});
} catch (error) {
console.error('IQRPG Audio Enhancer initialization error:', error);
// Recovery attempt
setTimeout(() => {
console.log('Attempting recovery after initialization error...');
setupObservers();
}, 10000); // Try again after 10 seconds
}
}, initDelay);
} catch (outerError) {
console.error('Critical startup error:', outerError);
}
// Enhanced whisper message listener with fixed audio handling
function setupEnhancedWhisperListener() {
let lastMessageCount = 0;
// Less aggressive interval to reduce system load
const CHECK_INTERVAL = 300;
// Tracking last notification time to prevent spam
let lastNotificationTime = 0;
function scanForWhispers() {
const possibleContainers = [
'#messages',
'.messages',
'[id*="message"]',
'[class*="message"]',
'#chat',
'.chat',
'[id*="chat"]',
'[class*="chat"]'
];
for (const selector of possibleContainers) {
const container = document.querySelector(selector);
if (!container) continue;
// Only check for new messages to reduce processing
const messages = container.querySelectorAll('div, p, li, span');
if (messages.length <= lastMessageCount) continue;
// Only scan new messages
const newMessages = Array.from(messages).slice(lastMessageCount);
for (const message of newMessages) {
const messageText = message.textContent.toLowerCase();
// Whisper detection via class
const hasWhisperClass = message.classList && (
message.classList.contains('whisper') ||
message.classList.contains('private') ||
message.classList.contains('pm')
);
// Simpler content matching to reduce false positives
const hasWhisperContent =
messageText.includes('whisper') ||
messageText.includes('whispers') ||
messageText.includes('pm:') ||
messageText.includes('from:') ||
messageText.match(/\[from .*\]/i);
if (hasWhisperClass || hasWhisperContent) {
const now = Date.now();
// Prevent notification spam with 3-second cooldown
if (now - lastNotificationTime > 3000) {
console.log("Whisper detected:", messageText);
lastNotificationTime = now;
// Use the original notification system instead of duplicating it
handleWhisperNotification();
showVisualAlert("Whisper Message Received", 5000);
}
}
}
// Update message count
lastMessageCount = messages.length;
break; // Only check one container
}
}
// Run checks at a reasonable interval
setInterval(scanForWhispers, CHECK_INTERVAL);
// Simple title check
const originalTitle = document.title;
setInterval(() => {
if (document.title !== originalTitle &&
document.title.toLowerCase().includes('whisper')) {
// Use existing handler
handleWhisperNotification();
}
}, CHECK_INTERVAL);
console.log("Enhanced whisper detection activated - fixed version");
}
// Call setup but don't start multiple interval timers
setupEnhancedWhisperListener();
}); // Close document.ready
})(); // Close and invoke IIFE