您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
The script that makes pinpointing matter. Read more at the GeoClassics discord server or check out Pinpointing Tournaments on twitch.tv/GeoClassics.
// ==UserScript== // @name Pinpointing Duels // @namespace http://tampermonkey.net/ // @version 2.0.6 // @description The script that makes pinpointing matter. Read more at the GeoClassics discord server or check out Pinpointing Tournaments on twitch.tv/GeoClassics. // @match https://www.geoguessr.com/* // @icon https://i.imgur.com/eKp3nIa.png // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @license MIT // @require https://update.gf.qytechs.cn/scripts/460322/1408713/Geoguessr%20Styles%20Scan.js // ==/UserScript== (function() { GM_registerMenuCommand("Settings", showSettingsPanel); function showSettingsPanel() { if (document.getElementById('settings-panel')) return; const firstToValue = GM_getValue('firstTo', 10); const tieRangeValue = GM_getValue('tieRange', 0); const horizontalValue = GM_getValue('scoreBoxOffset', 30); const verticalValue = GM_getValue('scoreBoxTop', 86); const panel = document.createElement('div'); panel.id = 'settings-panel'; panel.style.cssText = ` position: fixed; top: 100px; right: 100px; background: #171717; color: white; padding: 20px; z-index: 10000; border-radius: 10px; font-family: sans-serif; width: 300px; `; // BEST OF const firstToLabel = document.createElement('label'); firstToLabel.innerText = "First to:"; const firstToSelect = document.createElement('select'); for (let x = 3; x <= 30; x += 1) { const option = document.createElement('option'); option.value = x; option.innerText = x; if (x == firstToValue) option.selected = true; firstToSelect.appendChild(option); } // TIE RANGE const tieLabel = document.createElement('label'); tieLabel.innerText = "Tie Range Mode:"; const tieSelect = document.createElement('select'); [ { value: 0, label: "Disabled" }, { value: 1, label: "Full Tie Range" }, { value: 2, label: "Half Tie Range" } ].forEach(({ value, label }) => { const option = document.createElement('option'); option.value = value; option.innerText = label; if (value == tieRangeValue) option.selected = true; tieSelect.appendChild(option); }); // HORIZONTAL POSITION const hLabel = document.createElement('label'); hLabel.innerText = `Score Board Horizontal Position: ${horizontalValue}%`; hLabel.style.display = "block"; const hSlider = document.createElement('input'); hSlider.type = "range"; hSlider.min = "0"; hSlider.max = "100"; hSlider.value = horizontalValue; hSlider.style.width = "100%"; hSlider.addEventListener('input', () => { hLabel.innerText = `Score Board Horizontal Position: ${hSlider.value}%`; updateScoreBoxPosition(parseInt(hSlider.value), parseInt(vSlider.value)); }); // VERTICAL POSITION const vLabel = document.createElement('label'); vLabel.innerText = `Score Board Vertical Position: ${verticalValue}px`; vLabel.style.display = "block"; const vSlider = document.createElement('input'); vSlider.type = "range"; vSlider.min = "0"; vSlider.max = "300"; vSlider.value = verticalValue; vSlider.style.width = "100%"; vSlider.addEventListener('input', () => { vLabel.innerText = `Score Board Vertical Position: ${vSlider.value}px`; updateScoreBoxPosition(parseInt(hSlider.value), parseInt(vSlider.value)); }); // Buttons const saveBtn = document.createElement('button'); saveBtn.innerText = "Save"; saveBtn.style.margin = "10px"; saveBtn.style.cursor = "pointer"; saveBtn.style.color = "white"; saveBtn.onclick = () => { firstTo = parseInt(firstToSelect.value); tieRange = parseInt(tieSelect.value); winThreshold = firstTo; GM_setValue('firstTo', firstTo); GM_setValue('tieRange', tieRange); GM_setValue('scoreBoxOffset', parseInt(hSlider.value)); GM_setValue('scoreBoxTop', parseInt(vSlider.value)); alert("Settings saved!"); panel.remove(); }; const cancelBtn = document.createElement('button'); cancelBtn.innerText = "Cancel"; cancelBtn.style.cursor = "pointer"; cancelBtn.style.color = "white"; cancelBtn.onclick = () => panel.remove(); // Add to DOM panel.append( firstToLabel, firstToSelect, document.createElement("br"), document.createElement("br"), tieLabel, tieSelect, document.createElement("br"), document.createElement("br"), hLabel, hSlider, document.createElement("br"), document.createElement("br"), vLabel, vSlider, document.createElement("br"), document.createElement("br"), saveBtn, cancelBtn ); document.body.appendChild(panel); } function updateScoreBoxPosition(horizontalPercent, verticalPx) { const leftEl = document.getElementById("leftScore"); const rightEl = document.getElementById("rightScore"); if (leftEl) { leftEl.style.left = `${horizontalPercent}%`; leftEl.style.top = `${verticalPx}px`; } if (rightEl) { rightEl.style.right = `${horizontalPercent}%`; rightEl.style.top = `${verticalPx}px`; } } function showResultDisplay(message) { const container = document.querySelector("." + cn("round-score-header_roundNumber__")); if (!container) return; // Avoid duplicates if (document.getElementById("roundDisplayMessage")) return; const wrapper = document.createElement("div"); wrapper.id = "roundDisplayMessage"; wrapper.innerHTML = ` <div style="padding: 20px 0; margin: 0 auto; font-size: 32px; line-height: 48px; text-align: center; color: #f2f2f2;">${message}</div> `; container.insertBefore(wrapper, container.firstChild); } function showPenaltyDisplay(message) { const container = document.querySelector("." + cn("round-score-header_roundNumber__")); if (!container) return; // Avoid duplicates if (document.getElementById("roundDisplayPenalty")) return; const wrapper = document.createElement("div"); wrapper.id = "roundDisplayPenalty"; wrapper.innerHTML = ` <div style="padding: 20px 0; margin: 24px auto; font-size: 24px; line-height: 24px; text-align: center; color: #f2f2f2;">SCORE 0 FOR GUESSING EARLY AND MISSING 5K: ${message}</div> `; container.insertBefore(wrapper, container.firstChild); } 'use strict'; // Inject custom CSS for the overlay backdrop and active round wrapper to use your default image. const customStyles = ` .overlay_backdrop__ueiEF, .views_activeRoundWrapper__1_J5M { background-image: url('https://i.imgur.com/vzIi2Wl.jpeg') !important; background-position: center !important; background-size: cover !important; background-repeat: no-repeat !important; } `; const styleElem = document.createElement('style'); styleElem.textContent = customStyles; document.head.appendChild(styleElem); let firstTo = GM_getValue('firstTo', 10); // Fallback 10 let winThreshold = firstTo; //Win Value let tieRange = GM_getValue('tieRange', 0); let leftScore = 0, rightScore = 0; let gameOver = false; // Flag to ensure the end screen is only shown once let currentDuel = false; // Flag to ensure the end screen is only shown once // Extract the logged-in player's ID from __NEXT_DATA__ const getLoggedInUserId = () => { const element = document.getElementById("__NEXT_DATA__"); if (!element) return null; let exto = JSON.parse(element.innerText).props.accountProps.account.user.userId return exto; }; // Determine which team (0 or 1) the logged-in user belongs to. const getLoggedInUserTeamIndex = (teams, loggedInUserId) => { for (let i = 0; i < teams.length; i++) { if (teams[i].players && teams[i].players.some(player => player.playerId === loggedInUserId)) { return i; } } return null; }; // Remove unwanted UI elements and ensure our score display exists. const modifyHealthBars = () => { const healthContainer = document.querySelector("." + cn("hud_root__")); if (!healthContainer) return; document.querySelectorAll('[class*="health-bar_barInner__"]').forEach(bar => bar.style.display = "none"); document.querySelectorAll('[class*="health-bar_slant__"]').forEach(slant => slant.style.display = "none"); document.querySelectorAll("." + cn("health-bar_playerContainer__")).forEach(container => container.style.top = "0.5rem"); document.querySelectorAll("." + cn("health-bar_container__")).forEach(container => container.style.setProperty("--bar-container-width", "15rem")); document.querySelectorAll("." + cn("health-bar_barInnerContainer__")).forEach(container => container.style.background = "none"); if (!document.getElementById("leftScore") || !document.getElementById("rightScore")) { createScoreDisplays(); } }; // Create score display divs. const createScoreDisplays = () => { const hudRoot = document.querySelector("." + cn("duels_hud__")); if (!hudRoot) return; const createScoreDiv = (id, position) => { const div = document.createElement("div"); div.id = id; div.innerText = (id === "leftScore") ? leftScore : rightScore; div.style.cssText = ` padding: 10px 20px; font-size: 36px; font-weight: bold; color: white; background: linear-gradient(180deg,rgba(131,125,187,.6),rgba(131,125,187,0) 75%),#3c2075; border-radius: 5px; text-align: center; margin: 5px; position: absolute; top: 20px; ${position}: 320px; z-index: 1000; `; return div; }; hudRoot.appendChild(createScoreDiv("leftScore", "left")); hudRoot.appendChild(createScoreDiv("rightScore", "right")); }; // Create and display the end screen overlay. const showEndScreen = () => { const overlay = document.createElement("div"); overlay.id = "endScreenOverlay"; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: linear-gradient(180deg,rgba(6,43,20,1),rgba(11,65,43,1) 95%),#062b14; color: #5adb95; display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 9999; text-align: center; `; const winnerText = (leftScore >= winThreshold) ? "YOU WIN THE GAME!" : "OPPONENT WINS THE GAME!"; const scoreText = `${leftScore}-${rightScore}`; const winnerElem = document.createElement("div"); winnerElem.innerText = winnerText; winnerElem.style.cssText = ` font-size: 72pt; margin-bottom: 20px; `; const scoreElem = document.createElement("div"); scoreElem.innerText = scoreText; scoreElem.style.cssText = ` font-size: 90pt; margin-bottom: 20px; color: white; `; const messageElem = document.createElement("div"); messageElem.innerText = "Please guess Antarctica for the remaining rounds so the game is saved and you get a summary link."; messageElem.style.cssText = ` font-size: 24pt; margin-bottom: 20px; color: white; `; const button = document.createElement("button"); button.innerText = "Okay"; button.className = cn("button_button__") + " " + cn("button_variantPrimary__"); // Add the classes button.addEventListener("click", () => { overlay.remove(); }); overlay.appendChild(winnerElem); overlay.appendChild(scoreElem); overlay.appendChild(messageElem); overlay.appendChild(button); document.body.appendChild(overlay); }; // Flash the score element by toggling backgrounds every 0.5s for 5s. const flashScore = (scoreElement) => { const originalBackground = scoreElement.style.background; const flashBackground = "linear-gradient(180deg,rgba(90,219,149,1),rgba(60,200,110,1) 95%),#5adb95"; let flashCount = 0; const interval = setInterval(() => { // Toggle the background on odd/even counts. scoreElement.style.background = (flashCount % 2 === 0) ? flashBackground : originalBackground; flashCount++; if (flashCount >= 10) { // 10 toggles * 0.5s = 5 seconds total. clearInterval(interval); scoreElement.style.background = originalBackground; } }, 500); }; const updateScores = (response) => { let team0Score = 0, team1Score = 0; let newTeam0Score = 0, newTeam1Score = 0; let tieDistance = 0; const timeAfterGuess = response.options.roundTime; const maxRoundTime = response.options.maxRoundTime; if (response.teams && response.teams.length >= 2) { const loggedInUserId = getLoggedInUserId(); const teamIndex = getLoggedInUserTeamIndex(response.teams, loggedInUserId); for (let i = 0; i < response.currentRoundNumber && newTeam0Score < winThreshold && newTeam1Score < winThreshold; i++) { if (!response.rounds[i].hasProcessedRoundTimeout) continue; const oldDisplayMessage = document.getElementById("roundDisplayMessage"); if (oldDisplayMessage) oldDisplayMessage.remove(); const oldPenaltyMessage = document.getElementById("roundDisplayPenalty"); if (oldPenaltyMessage) oldPenaltyMessage.remove(); let message = '', tieMessage = '', winMessage = ''; let roundStartTime = new Date(response.rounds[i]?.startTime)/1000 || Infinity; let roundEndTime = new Date(response.rounds[i]?.endTime)/1000 || Infinity; let team0RoundScore = 0,team1RoundScore = 0; let team0Time = Infinity, team1Time = Infinity; let team0BestScore = 0, team1BestScore = 0; for (let j = 0; j < response.teams[0]?.players.length; j++){ let currentGuess; response.teams[0].players[j].guesses.forEach((guess) => {if (guess.roundNumber == i+1) currentGuess = guess;}); if (currentGuess === undefined) continue; let guessTime = new Date(currentGuess.created)/1000 || Infinity; let score = Math.round(5000*Math.exp(-10*currentGuess.distance/response.options?.map?.maxErrorDistance)) || 0; score = currentGuess.distance < 25 ? 5000 : score; if (guessTime < team0Time && guessTime < roundEndTime) { team0Time = guessTime; team0RoundScore = score; } else if (score > team0BestScore) { team0BestScore = score; } } if (team0Time > roundEndTime) team0RoundScore = team0BestScore; for (let j = 0; j < response.teams[1]?.players.length; j++){ let currentGuess; response.teams[1].players[j].guesses.forEach((guess) => {if (guess.roundNumber == i+1) currentGuess = guess;}); if (currentGuess === undefined) continue; let guessTime = new Date(currentGuess.created)/1000 || Infinity; let score = Math.round(5000*Math.exp(-10*currentGuess.distance/response.options?.map?.maxErrorDistance)) || 0; score = currentGuess.distance < 25 ? 5000 : score; if (guessTime < team1Time && guessTime < roundEndTime) { team1Time = guessTime; team1RoundScore = score; } else if (score > team1BestScore) { team1BestScore = score; } } if (team1Time > roundEndTime) team1RoundScore = team1BestScore; if (team0RoundScore < 5000 && team0Time < team1Time && team0Time < (roundStartTime + maxRoundTime - timeAfterGuess)) { showPenaltyDisplay(teamIndex === 0 ? "YOU" : "YOUR OPPONENT"); team0RoundScore = 0; } else if (team1RoundScore < 5000 && team1Time < team0Time && team1Time < (roundStartTime + maxRoundTime - timeAfterGuess)) { showPenaltyDisplay(teamIndex === 1 ? "YOU" : "YOUR OPPONENT"); team1RoundScore = 0; } // Score counted as 0 if penalty for early sending team0Score = newTeam0Score; team1Score = newTeam1Score; const highestScore = team0RoundScore > team1RoundScore ? team0RoundScore : team1RoundScore; if (tieRange > 0) tieDistance = Math.floor((5000 - highestScore) / tieRange); if (team0RoundScore === 5000 && team1RoundScore === 5000) { if (team0Time < team1Time) newTeam0Score++; else if (team1Time < team0Time) newTeam1Score++; message = `1 POINT FOR FASTEST 5k`; } else if (team0RoundScore == 5000 || team1RoundScore == 5000) { if (team0RoundScore == 5000) newTeam0Score += 2; else newTeam1Score += 2; message = `2 POINTS FOR SOLO 5K`; } else if (team0RoundScore > team1RoundScore + tieDistance || team1RoundScore > team0RoundScore + tieDistance) { if (team0RoundScore > team1RoundScore + tieDistance) newTeam0Score++; else newTeam1Score ++; message = `1 POINT FOR CLOSEST GUESS`; if (tieRange>0) tieMessage = `<br><span style="font-size: 14px;">TIE RANGE: ${tieDistance} POINTS</span>`; } else { message = `TIE!`; if (tieRange>0) tieMessage = `<br><span style="font-size: 14px;">TIE RANGE: ${tieDistance} POINTS</span>`; } if (message !== `TIE!`) winMessage = (teamIndex === 0) === (newTeam0Score > team0Score) ? "YOU WIN THE ROUND! <br>" : "YOUR OPPONENT WINS THE ROUND! <br>"; const isMatchPoint = newTeam0Score >= winThreshold - 2 || newTeam1Score >= winThreshold - 2; showResultDisplay(`${winMessage} ${message} ${isMatchPoint ? "<br>MATCH POINT!" : "" } ${tieMessage}`); } const leftScoreChanged = (teamIndex === 0 ? newTeam0Score !== leftScore : newTeam1Score !== leftScore); const rightScoreChanged = (teamIndex === 0 ? newTeam1Score !== rightScore : newTeam0Score !== rightScore); if (teamIndex === 0) { leftScore = newTeam0Score; rightScore = newTeam1Score; } else if (teamIndex === 1) { leftScore = newTeam1Score; rightScore = newTeam0Score; } else { leftScore = newTeam0Score; rightScore = newTeam1Score; } const isMatchPoint = leftScore >= winThreshold - 2 || rightScore >= winThreshold - 2; const leftScoreEl = document.getElementById("leftScore"); const rightScoreEl = document.getElementById("rightScore"); if (leftScoreEl) { leftScoreEl.innerText = leftScore; if (leftScoreChanged) flashScore(leftScoreEl); } if (rightScoreEl) { rightScoreEl.innerText = rightScore; if (rightScoreChanged) flashScore(rightScoreEl); } if (!gameOver && (leftScore >= winThreshold || rightScore >= winThreshold)) { gameOver = true; showEndScreen(); } } }; const fetchDuelData = () => { const duelId = location.pathname.split("/")[2]; if (!duelId) return; if (gameOver) { if(duelId != currentDuel) { gameOver = false } else { return } } currentDuel = duelId fetch(`https://game-server.geoguessr.com/api/duels/${duelId}`, { method: "GET", credentials: "include" }) .then(res => res.json()) .then(updateScores) .catch(err => {}); }; const observer = new MutationObserver(() => { requestAnimationFrame(modifyHealthBars); }); observer.observe(document.body, { childList: true, subtree: true }); if (location.href.includes("/duels/")) { scanStyles().then(_ => { fetchDuelData(); }); } // Listen for URL changes to auto-activate the script. (function() { const _wr = type => { const orig = history[type]; return function() { const rv = orig.apply(this, arguments); window.dispatchEvent(new Event('locationchange')); return rv; }; }; history.pushState = _wr("pushState"); history.replaceState = _wr("replaceState"); window.addEventListener('popstate', () => { window.dispatchEvent(new Event('locationchange')); }); })(); window.addEventListener('locationchange', function(){ if (location.href.includes("/duels/")) { scanStyles().then(_ => { fetchDuelData(); modifyHealthBars(); }); } }); setInterval(() => { if (location.href.includes("/duels/")) { scanStyles().then(_ => { fetchDuelData(); }); } }, 5000); if (location.href.includes("/team-duels/")) { scanStyles().then(_ => { fetchDuelData(); }); } // Listen for URL changes to auto-activate the script. (function() { const _wr = type => { const orig = history[type]; return function() { const rv = orig.apply(this, arguments); window.dispatchEvent(new Event('locationchange')); return rv; }; }; history.pushState = _wr("pushState"); history.replaceState = _wr("replaceState"); window.addEventListener('popstate', () => { window.dispatchEvent(new Event('locationchange')); }); })(); window.addEventListener('locationchange', function(){ if (location.href.includes("/team-duels/")) { scanStyles().then(_ => { fetchDuelData(); modifyHealthBars(); }); } }); setInterval(() => { if (location.href.includes("/team-duels/")) { scanStyles().then(_ => { fetchDuelData(); }); } }, 2500); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址