// ==UserScript==
// @name FlatMMO Sound Manager
// @namespace com.pizza1337.flatmmo.soundmanager
// @version 1.0
// @description Adds advanced audio controls
// @author Pizza1337
// @match *://flatmmo.com/play.php*
// @grant none
// @require https://update.greasyfork.org/scripts/544062/FlatMMOPlus.js
// @license MIT
// ==/UserScript==
(function() {
'use strict';
class SoundManagerPlugin extends FlatMMOPlusPlugin {
constructor() {
super("sound-manager", {
about: {
name: GM_info.script.name,
version: GM_info.script.version,
author: GM_info.script.author,
description: GM_info.script.description
},
config: [{
id: "separateAlerts",
label: "Separate Alert Sounds",
type: "boolean",
default: true
}]
});
this.userscriptMusicTrack = null;
this.areAudioHooksApplied = false;
this.hasVolumeSlidersBeenAdded = false;
this.alertSounds = [
'sounds/short/gem.ogg',
'sounds/short/fallingtree.mp3',
'sounds/short/birdnest.ogg',
'sounds/short/fullinvent.ogg'
];
this.init();
}
init() {
this.injectStyles();
// Continuously check for game functions and UI elements to be ready
const observer = new MutationObserver(() => {
if (!this.areAudioHooksApplied && typeof window.play_sound === 'function' && typeof window.play_track === 'function') {
this.applyAudioHooks();
}
if (!this.hasVolumeSlidersBeenAdded && document.querySelector("#ui-panel-settings table.settings-ui")) {
this.renderVolumeControls();
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
injectStyles() {
const styles = `
.tm-alert-icon {
width: 64px;
height: 64px;
fill: #000;
}
`;
const styleSheet = document.createElement("style");
styleSheet.type = "text/css";
styleSheet.innerText = styles;
document.head.appendChild(styleSheet);
}
applyAudioHooks() {
this.areAudioHooksApplied = true;
const original_play_sound = window.play_sound;
window.play_sound = (path, vol = 1) => {
const separateAlerts = this.getConfig('separateAlerts');
const isAlert = this.alertSounds.includes(path);
if (isAlert && separateAlerts) {
if (localStorage.getItem('flatmmo_alert_muted') === 'true') return;
const savedVolume = parseFloat(localStorage.getItem('flatmmo_alert_volume') ?? 1.0);
original_play_sound.call(this, path, savedVolume * vol);
} else {
if (localStorage.getItem('sound_off') == 1) return;
const savedVolume = parseFloat(localStorage.getItem('flatmmo_sound_volume') ?? 1.0);
original_play_sound.call(this, path, savedVolume * vol);
}
};
window.pause_track = () => {
if (this.userscriptMusicTrack) {
this.userscriptMusicTrack.pause();
this.userscriptMusicTrack.currentTime = 0;
}
this.userscriptMusicTrack = null;
};
window.play_track = (f) => {
window.pause_track();
if (localStorage.getItem('music_off') == 1) return;
const savedVolume = parseFloat(localStorage.getItem('flatmmo_music_volume') ?? 0.5); // Default is now 50%
this.userscriptMusicTrack = new Audio(f.startsWith('http') ? f : "sounds/tracks/" + f);
this.userscriptMusicTrack.volume = savedVolume;
this.userscriptMusicTrack.play();
window.track = this.userscriptMusicTrack;
};
}
onConfigsChanged() {
this.renderVolumeControls();
}
renderVolumeControls() {
const settingsPanel = document.querySelector("#ui-panel-settings");
const soundIcon = document.getElementById('settings-sound-icon');
const musicIcon = document.getElementById('settings-music-icon');
const settingsTable = settingsPanel?.querySelector("table.settings-ui");
if (!settingsPanel || !soundIcon || !musicIcon || !settingsTable) {
return;
}
this.hasVolumeSlidersBeenAdded = true;
const existingContainer = document.getElementById('volume-controls-wrapper');
if (existingContainer) {
existingContainer.remove();
}
const mainControlsContainer = document.createElement('div');
mainControlsContainer.id = 'volume-controls-wrapper';
mainControlsContainer.style.cssText = 'display: flex; justify-content: space-around; padding: 10px; align-items: start;';
const handleVolumeScroll = (event, slider) => {
event.preventDefault();
const step = parseInt(slider.step, 10);
const currentValue = parseInt(slider.value, 10);
let newValue = event.deltaY < 0 ? Math.min(100, currentValue + step) : Math.max(0, currentValue - step);
if (newValue !== currentValue) {
slider.value = newValue;
slider.dispatchEvent(new Event('input'));
}
};
const createVolumeControl = (type, iconElement, defaultValue, isCustomControl = false) => {
const container = document.createElement('div');
container.style.cssText = 'display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 5px; cursor: pointer;';
const slider = document.createElement('input');
slider.type = 'range';
slider.min = 0;
slider.max = 100;
slider.step = 10;
slider.value = (localStorage.getItem(`flatmmo_${type}_volume`) ?? defaultValue) * 100;
slider.style.cssText = 'width: 60px; margin: 0;';
const tooltip = document.createElement('div');
let tooltipStyle = `position: fixed; padding: 6px 10px; background: #333; color: #fff; border-radius: 6px; font-size: 14px; box-shadow: 0 2px 6px rgba(0,0,0,0.4); pointer-events: none; z-index: 9999; display: none;`;
const isPortrait = localStorage.getItem('flatmmo_ui_position') === 'below';
if (isPortrait) {
const ratio = window.devicePixelRatio || 1;
tooltipStyle += `transform: scale(${1 / ratio}); transform-origin: left top;`;
}
tooltip.style.cssText = tooltipStyle;
document.body.appendChild(tooltip);
const showTooltip = (e) => {
tooltip.textContent = `${slider.value}%`;
tooltip.style.left = `${e.pageX + 15}px`;
tooltip.style.top = `${e.pageY + 10}px`;
tooltip.style.display = 'block';
};
container.addEventListener('wheel', (e) => handleVolumeScroll(e, slider));
container.addEventListener('mouseenter', showTooltip);
container.addEventListener('mousemove', showTooltip);
container.addEventListener('mouseleave', () => { tooltip.style.display = 'none'; });
slider.addEventListener('input', () => {
const newVolume = slider.value / 100;
localStorage.setItem(`flatmmo_${type}_volume`, newVolume);
if (type === 'music' && this.userscriptMusicTrack) {
this.userscriptMusicTrack.volume = newVolume;
}
tooltip.textContent = `${slider.value}%`;
});
if (isCustomControl) {
const iconWrapper = document.createElement('div');
iconWrapper.style.cssText = 'position: relative; width: 64px; height: 64px;';
iconWrapper.appendChild(iconElement);
const muteOverlay = document.createElement('div');
muteOverlay.innerHTML = `<svg viewBox="0 0 32 32" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none;"><line x1="4" y1="4" x2="28" y2="28" stroke="#560606" stroke-width="4"/><line x1="28" y1="4" x2="4" y2="28" stroke="#560606" stroke-width="4"/></svg>`;
const muteKey = `flatmmo_${type}_muted`;
muteOverlay.style.display = localStorage.getItem(muteKey) === 'true' ? 'block' : 'none';
iconWrapper.appendChild(muteOverlay);
iconWrapper.onclick = () => {
const currentlyMuted = localStorage.getItem(muteKey) === 'true';
localStorage.setItem(muteKey, !currentlyMuted);
muteOverlay.style.display = !currentlyMuted ? 'block' : 'none';
};
container.append(iconWrapper, slider);
} else {
iconElement.style.width = '64px';
iconElement.style.height = '64px';
container.append(iconElement, slider);
}
return container;
};
const alertIconContainer = document.createElement('div');
alertIconContainer.innerHTML = `<svg class="tm-alert-icon" viewBox="0 0 24 24"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"></path></svg>`;
const alertIcon = alertIconContainer.firstChild;
soundIcon.remove();
musicIcon.remove();
const soundControl = createVolumeControl('sound', soundIcon, 1.0);
const musicControl = createVolumeControl('music', musicIcon, 0.5); // Default is now 50%
if (this.getConfig('separateAlerts')) {
const alertControl = createVolumeControl('alert', alertIcon, 1.0, true);
mainControlsContainer.append(soundControl, alertControl, musicControl);
} else {
mainControlsContainer.append(soundControl, musicControl);
}
settingsPanel.insertBefore(mainControlsContainer, settingsTable);
}
}
const plugin = new SoundManagerPlugin();
FlatMMOPlus.registerPlugin(plugin);
})();