您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Play sound when turn options appear after a long stretch of straight road.
// ==UserScript== // @name Internet Roadtrip Turn Alert // @namespace jdranczewski.github.io // @match https://neal.fun/internet-roadtrip/* // @version 0.2.1 // @author jdranczewski // @description Play sound when turn options appear after a long stretch of straight road. // @license MIT // @icon https://files.catbox.moe/fdkl61.png // @grant GM.setValues // @grant GM.getValues // @grant GM.addStyle // @grant GM.notification // @grant unsafeWindow // @run-at document-end // @require https://cdn.jsdelivr.net/npm/[email protected] // ==/UserScript== // This works together with irf.d.ts to give us type hints /** * Internet Roadtrip Framework * @typedef {typeof import('internet-roadtrip-framework')} IRF */ (async function() { // Styles GM.addStyle(` #ta-alert-box { position: fixed; pointer-events: none; width: 100%; height: 100%; background-color: #00000036; z-index: 1000000; transition: opacity 3s; opacity: 0; background-image: url("https://files.catbox.moe/l0mcvt.png"); background-repeat: no-repeat; background-position: center; display: flex; justify-content: center; align-items: center; } #ta-alert-box span { color: white; font-weight: bold; text-shadow: 1px 1px 2px #000000; font-size: 23px; transform: translate(0px, 50px); } #ta-alert-box.ta-alert-show { opacity: 1 !important; transition: opacity .5s; } `) // References const v_container = await IRF.vdom.container; const v_map = await IRF.vdom.map; // Settings const settings = { "turn_alert_sound": true, "turn_alert_visual": true, "turn_alert_notif": false, "minutes": 5, "sound": 'https://files.catbox.moe/04idsc.mp3', "volume": 0.3, "marker_alert_sound": true, "marker_alert_visual": true, "marker_alert_notif": false, "distance": 250, "marker_sound": 'https://files.catbox.moe/83p4v5.mp3', "marker_volume": 0.3, } const storedSettings = await GM.getValues(Object.keys(settings)) Object.assign( settings, storedSettings ); if (settings.sound == 'https://files.catbox.moe/6beir6.mp3') { // Replace the default sound with a better version settings.sound = "https://files.catbox.moe/04idsc.mp3"; } await GM.setValues(settings); // Visual alert setup const alert_box = document.createElement("div"); alert_box.id = "ta-alert-box"; document.body.appendChild(alert_box); function warn_visual(text="") { alert_text.innerText = text; alert_box.classList.toggle("ta-alert-show", true); setTimeout(() => { alert_box.classList.toggle("ta-alert-show", false); }, 2500); } const alert_text = document.createElement("span"); alert_box.appendChild(alert_text) // Settings panel GUI let gm_info = GM.info gm_info.script.name = "Turn alert" const irf_settings = IRF.ui.panel.createTabFor( gm_info, { tabName: "Turn alert", style: ` .ta-straight-n {font-weigth: bold} .ta-bad {color: #ff3434} .ta-good {color: #0f0 !important} ` } ); // Set up and status let straight_streak = 0; const status = {}; { const status_el = document.createElement("div"); status_el.innerText = "Status:" const status_ul = document.createElement("ul"); status_el.appendChild(status_ul) // Stop numbers let li = document.createElement("li"); status.straight_n = document.createElement("span"); status.straight_n.style.fontWeight = "bold"; li.appendChild(status.straight_n); li.append("/"); status.straight_lim = document.createElement("span"); status.straight_lim.style.color = "#aaa"; li.appendChild(status.straight_lim); li.append(" stops going straight.") status_ul.appendChild(li); // Next stop status li = document.createElement("li"); status.alert_next = document.createElement("span"); status.alert_next.classList.add("ta-bad"); li.appendChild(status.alert_next); li.append(" next turn - "); const force_button = document.createElement("button"); force_button.innerText = "Force alert next turn"; force_button.onclick = (e) => { straight_streak = 10000; } li.appendChild(force_button) status_ul.appendChild(li); // Connection to Tricks li = document.createElement("li"); status.mmt = document.createElement("span"); status.mmt.classList.add("ta-bad"); li.appendChild(status.mmt); li.append(" to Minimap Tricks for markers."); status_ul.appendChild(li); irf_settings.container.appendChild(status_el); } // GUI objects function add_checkbox( name, identifier, callback=undefined, settings_container=irf_settings.container ) { let label = document.createElement("label"); let checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.checked = settings[identifier]; checkbox.className = IRF.ui.panel.styles.toggle; label.appendChild(checkbox); let text = document.createElement("span"); text.innerText = " " + name; label.appendChild(text); checkbox.onchange = () => { settings[identifier] = checkbox.checked; GM.setValues(settings); if (callback) callback(checkbox.checked); } settings_container.appendChild(label); settings_container.appendChild(document.createElement("br")); settings_container.appendChild(document.createElement("br")); } function add_slider( name, identifier, callback=undefined, slider_bits=[1, 17, .5], settings_container=irf_settings.container ) { let label = document.createElement("label"); let text = document.createElement("span"); text.innerText = " " + name + ": "; label.appendChild(text); let value_label = document.createElement("span"); value_label.innerText = settings[identifier]; label.appendChild(value_label); let slider = document.createElement("input"); slider.type = "range"; slider.min = slider_bits[0]; slider.max = slider_bits[1]; slider.step = slider_bits[2]; slider.value = settings[identifier]; slider.className = IRF.ui.panel.styles.slider; label.appendChild(slider); slider.oninput = () => { settings[identifier] = slider.value; value_label.innerText = slider.value; GM.setValues(settings); if (callback) callback(slider.value); } slider.onmousedown = (e) => {e.stopPropagation()} settings_container.appendChild(label); settings_container.appendChild(document.createElement("br")); settings_container.appendChild(document.createElement("br")); } // Set up warn objects const howler = await IRF.modules.howler; class Warn { constructor(kind, settings_text, warn_text) { this._kind = kind; this._settings_text = settings_text; this._warn_text = warn_text; this.howl = new howler.Howl({ src: [ settings[kind == "turn" ? "sound" : `${kind}_sound`] ], volume: settings[kind == "turn" ? "volume" : `${kind}_volume`] }) this.settings = document.createElement("div"); this.settings.appendChild(document.createElement("hr")); const heading = document.createElement("h3"); heading.innerText = `${settings_text} warning` this.settings.appendChild(heading); add_checkbox( `${settings_text} visual warning`, `${kind}_alert_visual`, undefined, this.settings ) add_checkbox( `${settings_text} desktop notification`, `${kind}_alert_notif`, undefined, this.settings ) add_checkbox( `${settings_text} sound warning`, `${kind}_alert_sound`, undefined, this.settings ) add_slider("Volume", (kind == "turn" ? "volume" : `${kind}_volume`), (value) => { this.howl.volume(value); }, [0, 1, 0.05], this.settings); // Set sound text box let label = document.createElement("label"); let text = document.createElement("span"); text.innerHTML = " Sound file URL (host on <a href='https://catbox.moe' target='_blank'>catbox.moe</a>):"; label.appendChild(text); label.appendChild(document.createElement("br")); let box = document.createElement("input"); box.value = settings[kind == "turn" ? "sound" : `${kind}_sound`]; box.style.width = "100%"; label.appendChild(box); this.settings.appendChild(label); this.settings.appendChild(document.createElement("br")); this.settings.appendChild(document.createElement("br")); // Test and save button let button = document.createElement("button"); button.innerText = `Test ${kind} alert and save sound (if you hear it, it worked!)`; button.onclick = async () => { this.howl = new howler.Howl({ src: [ box.value ], volume: settings[kind == "turn" ? "volume" : `${kind}_volume`] }) this.howl.once("end", () => { settings[kind == "turn" ? "sound" : `${kind}_sound`] = box.value; GM.setValues(settings); }) this.warn(); } this.settings.appendChild(button); this.settings.appendChild(document.createElement("br")); this.settings.appendChild(document.createElement("br")); } warn() { if (settings[`${this._kind}_alert_visual`]) { warn_visual(this._warn_text) } if (settings[`${this._kind}_alert_sound`]) { this.howl.play(); } if (settings[`${this._kind}_alert_notif`]) { GM.notification( `Warning! - ${this._warn_text}`, "Internet Roadtrip", "https://files.catbox.moe/fdkl61.png", (e) => {console.log("notif click", e)} ); } } } const warn_turn = new Warn("turn", "Turn", "Turn now!"); add_slider( "Time going straight before alerting (minutes, approx.)", "minutes", undefined, [1, 60, 1], warn_turn.settings ); const warn_marker = new Warn("marker", "Marker", "Marker ahead!"); add_slider( "Distance from marker (meters, we move ~3m per second)", "distance", undefined, [50, 1500, 50], warn_marker.settings ); irf_settings.container.appendChild(warn_turn.settings); irf_settings.container.appendChild(warn_marker.settings); // Override the setter for the number of available options // To warn if we encounter a turn suddenly // Get the original setter const { set: currentOptionsSetter } = Object.getOwnPropertyDescriptor(v_container.state, 'currentOptions'); // Override the setter Object.defineProperty(v_container.state, 'currentOptions', { set(currentOptions) { // Set the units on the scale bar let straight_lim = settings.minutes*12; if (currentOptions.length == 1) { straight_streak += 1; } else { // console.log("Not straight!"); if (straight_streak > straight_lim) { warn_turn.warn(); } straight_streak = 0; } status.straight_n.innerText = straight_streak; status.straight_lim.innerText = straight_lim; if (straight_streak > straight_lim) { status.alert_next.innerText = "Will alert"; status.alert_next.classList.toggle("ta-good", true); } else { status.alert_next.innerText = "No alert"; status.alert_next.classList.toggle("ta-good", false); } return currentOptionsSetter.call(this, currentOptions); }, configurable: true, enumerable: true, }); // Override changeStop to alert when we come close to a Marker const changeStop = v_container.methods.changeStop; const alerted = []; v_container.state.changeStop = new Proxy(changeStop, { apply: (target, thisArg, args) => { const returnValue = Reflect.apply(target, thisArg, args); const coords = args[5][0]; if (unsafeWindow._MMT_getMarkers) { status.mmt.innerText = "Connected"; status.mmt.classList.toggle("ta-good", true); const markers = unsafeWindow._MMT_getMarkers(); if (markers) { for (const [marker_id, marker] of Object.entries(markers)) { let distance = marker.getLngLat().distanceTo(v_map.data.marker.getLngLat()); if (alerted.includes(marker_id)) { if (distance > settings.distance) { // Remove marker from alerted list if it's now out of range const index = alerted.indexOf(marker_id); if (index > -1) { alerted.splice(index, 1); } } } else if (distance < settings.distance) { alerted.push(marker_id); warn_marker.warn(); } } } } else { status.mmt.innerText = "Not connected"; status.mmt.classList.toggle("ta-good", false); } return returnValue; }, }); })();