GeoGuessr Flashlight Game Mode

Adds a flashlight game mode to Single Player and Multiplayer, and lowered battery recharge delay.

当前为 2025-11-12 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         GeoGuessr Flashlight Game Mode
// @namespace    http://tampermonkey.net/
// @version      4.2
// @description  Adds a flashlight game mode to Single Player and Multiplayer, and lowered battery recharge delay.
// @author       Odinman9847
// @match        https://www.geoguessr.com/*
// @grant        GM_addStyle
// @license MIT
// ==/UserScript==

;(function () {
  'use strict'

  // --- Configuration ---
  const flashlightRadius = 150
  const flashlightFeather = 250
  const darknessColor = 'rgba(0, 0, 0, 1)'
  const batteryDurationSeconds = 5
  const rechargeDelayMilliseconds = 500
  // -------------------

  const overlayId = 'geoguessr-flashlight-overlay'
  const batteryBarId = 'geoguessr-battery-bar'
  const tooltipId = 'geoguessr-flashlight-tooltip'

  // --- Selectors ---
  const singlePlayerGameSelector = '[class*="in-game_root"]'
  const singlePlayerResultSelector = '[class*="result-layout"]'
  // Using '^=' to only match classes that start with 'duels_root'
  const multiplayerGameSelector = '[class^="duels_root"]'
  const multiplayerResultSelector = '[class*="round-score"]'
  // Selector for when you are spectating your opponent
  const spectatorSelector = '[class*="post-guess-player-spectator_root"]'

  // --- Game State Variables ---
  let isFlashlightOn = false
  let batteryLevel = 100
  let batteryUpdateInterval = null
  let rechargeDelayTimestamp = 0
  let lastMouseX = window.innerWidth / 2
  let lastMouseY = window.innerHeight / 2

  // --- Core Functions ---

  function applyFlashlightEffect(event = null) {
    const overlay = document.getElementById(overlayId)
    if (!overlay) return
    if (event) {
      lastMouseX = event.clientX
      lastMouseY = event.clientY
    }
    if (!isFlashlightOn) return
    const radius = flashlightRadius
    const feather = flashlightFeather
    overlay.style.background = `radial-gradient(
            circle at ${lastMouseX}px ${lastMouseY}px,
            transparent ${radius}px,
            rgba(0, 0, 0, 0.08) ${radius + feather * 0.2}px,
            rgba(0, 0, 0, 0.32) ${radius + feather * 0.4}px,
            rgba(0, 0, 0, 0.68) ${radius + feather * 0.6}px,
            rgba(0, 0, 0, 0.92) ${radius + feather * 0.8}px,
            ${darknessColor} ${radius + feather}px
        )`
  }

  function toggleFlashlight(forceState = null) {
    if (forceState === null && !isFlashlightOn && batteryLevel <= 0) {
      return
    }
    const overlay = document.getElementById(overlayId)
    if (!overlay) return
    const oldState = isFlashlightOn
    isFlashlightOn = forceState !== null ? forceState : !isFlashlightOn
    if (isFlashlightOn) {
      applyFlashlightEffect()
    } else {
      overlay.style.background = darknessColor
      if (oldState === true) {
        rechargeDelayTimestamp = Date.now()
      }
    }
  }

  function handleRightClick(event) {
    event.preventDefault()
    toggleFlashlight()
  }

  function updateBattery() {
    const updateAmount = 100 / (batteryDurationSeconds * 20)
    if (isFlashlightOn) {
      batteryLevel -= updateAmount
    } else {
      if (Date.now() - rechargeDelayTimestamp > rechargeDelayMilliseconds) {
        batteryLevel += updateAmount
      }
    }
    batteryLevel = Math.max(0, Math.min(100, batteryLevel))
    const batteryFill = document.querySelector(`#${batteryBarId} > div`)
    if (batteryFill) {
      batteryFill.style.width = `${batteryLevel}%`
    }
    if (batteryLevel <= 0 && isFlashlightOn) {
      toggleFlashlight(false)
    }
  }

  // --- UI and Game State Management ---

  function createGameUI() {
    const overlay = document.createElement('div')
    overlay.id = overlayId
    document.body.appendChild(overlay)
    const batteryContainer = document.createElement('div')
    batteryContainer.id = batteryBarId
    const batteryFill = document.createElement('div')
    batteryContainer.appendChild(batteryFill)
    document.body.appendChild(batteryContainer)
    const tooltip = document.createElement('div')
    tooltip.id = tooltipId
    tooltip.textContent = 'Right-click to toggle flashlight'
    document.body.appendChild(tooltip)
    GM_addStyle(`
            #${overlayId} { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 99998; pointer-events: none; background-color: ${darknessColor}; }
            #${batteryBarId} { position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%); width: 300px; height: 20px; background-color: #222; border: 2px solid #555; border-radius: 5px; z-index: 99999; pointer-events: none; }
            #${batteryBarId} > div { height: 100%; width: ${batteryLevel}%; background-color: #6a994e; transition: width 0.05s linear; }
            #${tooltipId} { position: fixed; bottom: 60px; left: 50%; transform: translateX(-50%); color: #ccc; font-family: sans-serif; font-size: 14px; text-shadow: 1px 1px 2px rgba(0,0,0,0.8); z-index: 99999; pointer-events: none; }
        `)
    window.addEventListener('mousemove', applyFlashlightEffect)
    document.addEventListener('contextmenu', handleRightClick)
    batteryUpdateInterval = setInterval(updateBattery, 50)
  }

  function destroyGameUI() {
    clearInterval(batteryUpdateInterval)
    window.removeEventListener('mousemove', applyFlashlightEffect)
    document.removeEventListener('contextmenu', handleRightClick)
    const overlay = document.getElementById(overlayId)
    const batteryBar = document.getElementById(batteryBarId)
    const tooltip = document.getElementById(tooltipId)
    if (overlay) overlay.remove()
    if (batteryBar) batteryBar.remove()
    if (tooltip) tooltip.remove()
    isFlashlightOn = false
    batteryLevel = 100
    rechargeDelayTimestamp = 0
  }

  function checkGameState() {
    const singlePlayerGameElement = document.querySelector(singlePlayerGameSelector)
    const singlePlayerResultElement = document.querySelector(singlePlayerResultSelector)
    const multiplayerGameElement = document.querySelector(multiplayerGameSelector)
    const multiplayerResultElement = document.querySelector(multiplayerResultSelector)
    const spectatorElement = document.querySelector(spectatorSelector)

    const isSinglePlayerActive = singlePlayerGameElement && !singlePlayerResultElement
    // Multiplayer is now also deactivated by the spectator element
    const isMultiplayerActive =
      multiplayerGameElement && !multiplayerResultElement && !spectatorElement

    const shouldGameBeActive = isSinglePlayerActive || isMultiplayerActive

    const isUiActive = document.getElementById(overlayId)
    if (shouldGameBeActive && !isUiActive) {
      createGameUI()
    } else if (!shouldGameBeActive && isUiActive) {
      destroyGameUI()
    }
  }

  const observer = new MutationObserver(checkGameState)
  observer.observe(document.body, { childList: true, subtree: true })
  checkGameState()
})()