// ==UserScript==
// @name MouseHunt Auto Disarm/Swap Bait
// @author Kane Kiew
// @namespace https://greasyfork.org/en/users/979741
// @version 3.3
// @description Automatically Disarms/Swaps bait when the remaining quantity matches or goes below user input
// @match http://mousehuntgame.com/*
// @match https://mousehuntgame.com/*
// @match http://www.mousehuntgame.com/*
// @match https://www.mousehuntgame.com/*
// @match http://www.mousehuntgame.com/camp.php*
// @match https://www.mousehuntgame.com/camp.php*
// @match http://apps.facebook.com/mousehunt/*
// @match https://apps.facebook.com/mousehunt/*
// @icon https://www.google.com/s2/favicons?domain=mousehuntgame.com
// @grant none
// @license GPL-3.0+; http://www.gnu.org/copyleft/gpl.html
// ==/UserScript==
(function () {
'use strict';
// Declare the dropdownDiv variable outside the function scope
let dropdownDiv;
let lastActionTime = 0; // Track when last action was performed to prevent spamming
let baitList = []; // Store the bait list globally
let dropdownInitialized = false; // Track if dropdown has been initialized
let lastBaitQuantity = 0; // Track last known bait quantity
let checkInterval; // Interval for periodic checking
// Function to update the selected cheese UI element
function updateSelectedCheeseUI(selectedBait) {
selectedCheeseElement.textContent = `Selected Cheese: ${selectedBait || 'Not Set'}`;
}
// Function to get currently equipped cheese ID
function getEquippedCheeseID() {
const baitImg = document.querySelector('.campPage-trap-baitDetails .mousehuntHud-bait .mousehuntHud-image');
if (!baitImg) return null;
const imgSrc = baitImg.getAttribute('src');
const idMatch = imgSrc.match(/items\/(\d+)\./);
return idMatch ? parseInt(idMatch[1]) : null;
}
// Function to force update bait quantity
function forceUpdateBaitQuantity() {
const newQuantity = getBaitQuantity();
if (newQuantity !== lastBaitQuantity) {
lastBaitQuantity = newQuantity;
updateUI();
checkQuantity();
}
}
// Watch for changes in bait quantity using MutationObserver
const baitQuantityObserver = new MutationObserver(() => {
console.log('MutationObserver detected a change in bait quantity');
forceUpdateBaitQuantity();
});
// Select the parent element of bait quantity using querySelector
const baitQuantityParent = document.querySelector('.campPage-trap-baitDetails');
if (baitQuantityParent) {
baitQuantityObserver.observe(baitQuantityParent, { childList: true, subtree: true, characterData: true });
}
// Set up periodic checking
function setupPeriodicChecking() {
// Clear any existing interval
if (checkInterval) {
clearInterval(checkInterval);
}
// Check every 5 seconds (adjust as needed)
checkInterval = setInterval(forceUpdateBaitQuantity, 900000);
}
// Initialize the dropdown with the pre-loaded bait list
function initializeDropdown() {
if (dropdownInitialized) return;
// Remove existing dropdown if it exists
const existingDropdown = document.getElementById('bait-dropdown');
if (existingDropdown) {
existingDropdown.remove();
}
dropdownDiv = document.createElement('div');
dropdownDiv.id = 'bait-dropdown';
dropdownDiv.style.position = 'fixed';
dropdownDiv.style.top = '126px';
dropdownDiv.style.left = '10px';
dropdownDiv.style.zIndex = '9999';
dropdownDiv.style.display = isDisarmSelected ? 'none' : 'block';
const dropdown = $('<select></select>');
if (baitList.length === 0) {
const option = document.createElement('option');
option.value = '';
option.text = 'No Baits found';
dropdown.append(option);
} else {
const defaultOption = document.createElement('option');
defaultOption.value = '';
defaultOption.text = 'Select a bait...';
dropdown.append(defaultOption);
baitList.forEach((bait) => {
const option = document.createElement('option');
option.value = bait.itemID;
option.text = `${bait.name} (ID: ${bait.itemID})`;
dropdown.append(option);
});
}
$(dropdownDiv).append(dropdown);
document.body.appendChild(dropdownDiv);
// Initialize Select2
dropdown.select2();
// Add event listener to Select2 dropdown
dropdown.on('change', function (e) {
const selectedBaitID = parseInt(e.target.value);
localStorage.setItem('selectedBaitID', selectedBaitID);
const selectedBait = e.target.selectedOptions[0].text;
localStorage.setItem('selectedBait', selectedBait);
console.log('Selected bait ID:', selectedBaitID);
console.log('Selected bait:', selectedBait);
checkQuantity(selectedBaitID);
updateSelectedCheeseUI(selectedBait);
});
// Set the selected value if it exists in localStorage
const storedBaitID = localStorage.getItem('selectedBaitID');
if (storedBaitID) {
dropdown.val(storedBaitID).trigger('change');
}
dropdownInitialized = true;
}
function processTrapComponents(components) {
baitList = components
.filter((component) => component.classification === 'bait')
.map((component) => ({
itemID: component.item_id,
name: component.name,
}))
.sort((a, b) => a.name.localeCompare(b.name)); // Sort by name
// Initialize the dropdown immediately with the loaded bait list
initializeDropdown();
updateUI();
}
function retrieveTrapComponents() {
const xhr = new XMLHttpRequest();
xhr.addEventListener('load', function () {
if (xhr.status === 200) {
let data;
try {
data = JSON.parse(xhr.responseText).components;
if (data && data.length > 0) {
processTrapComponents(data);
} else {
console.log('Invalid components array data from gettrapcomponents.php');
setTimeout(retrieveTrapComponents, 5000); // Retry after 5 seconds
}
} catch (error) {
console.log('Failed to process server response for gettrapcomponents.php');
console.error(error.stack);
setTimeout(retrieveTrapComponents, 5000); // Retry after 5 seconds
}
} else {
console.log('Error retrieving trap components from gettrapcomponents.php');
setTimeout(retrieveTrapComponents, 5000); // Retry after 5 seconds
}
});
xhr.open('GET', 'https://www.mousehuntgame.com/managers/ajax/users/gettrapcomponents.php');
xhr.send();
}
// Get bait quantity from the page
function getBaitQuantity() {
const hudBaitQuantity = document.querySelector('.campPage-trap-baitDetails .campPage-trap-baitQuantity');
if (hudBaitQuantity !== null) {
const quantityText = hudBaitQuantity.innerText.replace(/,/g, ''); // Remove commas from quantity text
return parseInt(quantityText) || 0;
}
return 0;
}
// Create UI elements
const createUIElements = () => {
const createUIElement = (left, top, text) => {
const element = document.createElement('div');
element.style.position = 'fixed';
element.style.left = left;
element.style.top = top;
element.style.backgroundColor = '#fff';
element.style.padding = '5px';
element.style.border = '1px solid #ccc';
element.style.borderRadius = '3px';
element.style.zIndex = '9999';
element.style.cursor = 'pointer';
element.textContent = text;
document.body.appendChild(element);
return element;
};
return {
// Create selected cheese UI element
selectedCheeseElement: createUIElement('10px', '101px', 'Selected Cheese: Not Set'),
targetQuantityElement: createUIElement('10px', '10px', 'Target Quantity: N/A (Click to set)'),
baitQuantityElement: createUIElement('10px', '35px', 'Bait Quantity: 0'),
};
};
const { targetQuantityElement, baitQuantityElement, selectedCheeseElement } = createUIElements();
// Create toggle switch UI element
const toggleSwitchElement = document.createElement('div');
toggleSwitchElement.style.position = 'fixed';
toggleSwitchElement.style.width = '110px';
toggleSwitchElement.style.height = '30px';
toggleSwitchElement.style.left = '10px';
toggleSwitchElement.style.top = '60px';
toggleSwitchElement.style.backgroundColor = '#fff';
toggleSwitchElement.style.padding = '5px';
toggleSwitchElement.style.border = '1px solid #ccc';
toggleSwitchElement.style.borderRadius = '3px';
toggleSwitchElement.style.zIndex = '9999';
toggleSwitchElement.style.display = 'flex';
toggleSwitchElement.style.alignItems = 'center';
toggleSwitchElement.style.justifyContent = 'space-between';
toggleSwitchElement.style.cursor = 'pointer';
document.body.appendChild(toggleSwitchElement);
const sliderLabelElement = document.createElement('div');
sliderLabelElement.style.fontWeight = 'bold';
sliderLabelElement.style.marginLeft = '5px';
toggleSwitchElement.appendChild(sliderLabelElement);
const sliderElement = document.createElement('div');
sliderElement.style.position = 'relative';
sliderElement.style.width = '40px';
sliderElement.style.height = '20px';
sliderElement.style.backgroundColor = '#ddd';
sliderElement.style.borderRadius = '10px';
sliderElement.style.cursor = 'pointer';
sliderElement.style.transition = 'background-color 0.3s ease';
toggleSwitchElement.appendChild(sliderElement);
const sliderKnob = document.createElement('div');
sliderKnob.style.position = 'absolute';
sliderKnob.style.width = '16px';
sliderKnob.style.height = '16px';
sliderKnob.style.backgroundColor = '#fff';
sliderKnob.style.borderRadius = '50%';
sliderKnob.style.boxShadow = '0 0 2px rgba(0,0,0,0.3)';
sliderKnob.style.transition = 'transform 0.3s ease';
sliderKnob.style.top = '2px';
sliderElement.appendChild(sliderKnob);
// Update target quantity UI
const updateTargetQuantityUI = () => {
const storedTarget = localStorage.getItem('targetQuantity');
if (storedTarget === null || storedTarget === 'N/A') {
targetQuantity = null;
targetQuantityElement.textContent = 'Target Quantity: N/A (Click to set)';
} else {
targetQuantity = parseInt(storedTarget);
if (!isNaN(targetQuantity)) {
targetQuantityElement.textContent = `Target Quantity: ${targetQuantity}`;
} else {
targetQuantity = null;
targetQuantityElement.textContent = 'Target Quantity: N/A (Click to set)';
localStorage.setItem('targetQuantity', 'N/A');
}
}
};
// Update bait quantity UI
const updateBaitQuantityUI = () => {
const baitQuantity = getBaitQuantity();
lastBaitQuantity = baitQuantity;
baitQuantityElement.textContent = `Bait Quantity: ${baitQuantity}`;
};
// Update toggle switch UI
const updateToggleSwitchUI = () => {
localStorage.setItem('isDisarmSelected', isDisarmSelected.toString());
if (isDisarmSelected) {
sliderLabelElement.textContent = 'Disarm';
sliderElement.style.backgroundColor = '#6b8cff';
sliderKnob.style.transform = 'translateX(20px)';
// Hide dropdown and selected cheese when in Disarm mode
if (dropdownDiv) dropdownDiv.style.display = 'none';
selectedCheeseElement.style.display = 'none';
} else {
sliderLabelElement.textContent = 'Swap Bait';
sliderElement.style.backgroundColor = '#ffb46b';
sliderKnob.style.transform = 'translateX(2px)';
// Show dropdown and selected cheese when in Swap Bait mode
if (dropdownDiv) dropdownDiv.style.display = 'block';
selectedCheeseElement.style.display = 'block';
}
};
// Update UI elements
const updateUI = () => {
updateSelectedCheeseUI(localStorage.getItem('selectedBait'));
updateTargetQuantityUI();
updateBaitQuantityUI();
updateToggleSwitchUI();
};
// Reset target quantity and selected cheese after action
const resetAfterAction = () => {
// Reset target quantity
targetQuantity = null;
localStorage.setItem('targetQuantity', 'N/A');
// Reset selected cheese
localStorage.removeItem('selectedBaitID');
localStorage.removeItem('selectedBait');
// Update dropdown to show no selection
if (dropdownDiv) {
const select = dropdownDiv.querySelector('select');
if (select) {
$(select).val('').trigger('change');
}
}
updateUI();
};
// Check if the action should be performed based on target quantity
const checkQuantity = () => {
// Prevent rapid consecutive actions (minimum 5 seconds between actions)
const now = Date.now();
if (now - lastActionTime < 5000) {
return;
}
// If target quantity is not set (N/A), do nothing
if (targetQuantity === null) {
return;
}
const selectedBaitID = parseInt(localStorage.getItem('selectedBaitID'));
const equippedCheeseID = getEquippedCheeseID();
const baitQuantity = getBaitQuantity();
// Check if we're trying to swap to the same cheese
const isSameCheese = selectedBaitID && equippedCheeseID && (selectedBaitID === equippedCheeseID);
// Check if bait quantity is <= target (including 0) or if we're swapping to same cheese
if (baitQuantity <= targetQuantity || isSameCheese) {
if (isDisarmSelected) {
console.log(`Disarming bait (quantity: ${baitQuantity} <= target: ${targetQuantity})`);
hg.utils.TrapControl.disarmBait().go();
lastActionTime = now;
resetAfterAction();
} else if (selectedBaitID && !isNaN(selectedBaitID)) {
if (isSameCheese) {
console.log(`Selected cheese (ID: ${selectedBaitID}) is same as equipped cheese - resetting selection`);
} else {
console.log(`Swapping to bait ID ${selectedBaitID} (quantity: ${baitQuantity} <= target: ${targetQuantity})`);
}
hg.utils.TrapControl.armItem(selectedBaitID, 'bait').go();
lastActionTime = now;
resetAfterAction();
}
}
// Check if target is greater than current bait quantity
else if (targetQuantity > baitQuantity) {
console.log(`Target quantity (${targetQuantity}) is greater than current bait (${baitQuantity}) - performing action now`);
if (isDisarmSelected) {
hg.utils.TrapControl.disarmBait().go();
lastActionTime = now;
resetAfterAction();
} else if (selectedBaitID && !isNaN(selectedBaitID)) {
hg.utils.TrapControl.armItem(selectedBaitID, 'bait').go();
lastActionTime = now;
resetAfterAction();
}
}
};
// Prompt user to input the remaining bait quantity
const updatePrompt = () => {
const currentTarget = localStorage.getItem('targetQuantity');
const input = prompt('Enter the remaining bait quantity (0 is valid) or "N/A" to disable:',
currentTarget !== null && currentTarget !== 'N/A' ? currentTarget : '');
if (input === null) return; // User cancelled
if (input.trim().toUpperCase() === 'N/A') {
targetQuantity = null;
localStorage.setItem('targetQuantity', 'N/A');
} else {
const newTarget = parseInt(input);
if (!isNaN(newTarget)) {
targetQuantity = newTarget;
localStorage.setItem('targetQuantity', newTarget.toString());
} else {
// Invalid input - set to N/A
targetQuantity = null;
localStorage.setItem('targetQuantity', 'N/A');
}
}
updateUI();
checkQuantity(); // Immediately check after changing target
};
// Add event listener to target quantity element
targetQuantityElement.addEventListener('click', updatePrompt);
// Update UI when the toggle switch is clicked
toggleSwitchElement.addEventListener('click', () => {
isDisarmSelected = !isDisarmSelected;
updateUI();
});
// Initialize target quantity and isDisarmSelected
let targetQuantity = null;
let isDisarmSelected = localStorage.getItem('isDisarmSelected') === 'true';
// Set up periodic checking
setupPeriodicChecking();
// Retrieve trap components immediately (but don't show dropdown until needed)
retrieveTrapComponents();
// Set initial UI state
updateUI();
// Also check when the window becomes visible again
document.addEventListener('visibilitychange', function() {
if (!document.hidden) {
forceUpdateBaitQuantity();
}
});
// Intercept AJAX calls for horn sounds and trap changes
const originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function () {
this.addEventListener('load', function () {
if (
this.responseURL === 'https://www.mousehuntgame.com/managers/ajax/users/changetrap.php' ||
this.responseURL === 'https://www.mousehuntgame.com/managers/ajax/turns/activeturn.php' ||
this.responseURL === 'https://www.mousehuntgame.com/managers/ajax/purchases/itempurchase.php'
) {
console.log('Intercepted relevant AJAX call:', this.responseURL);
forceUpdateBaitQuantity();
}
});
originalOpen.apply(this, arguments);
};
})();