Greasy Fork is available in English.
Track career BT goals with a red warning before it's too late. THIS IS SEATTLE NINER'S CODE WITH JUST A FEW TWEAKS BY ME, PUBLISHING JUST SO PEOPLE CAN USE TO TEST IF THEY WANT.
// ==UserScript==
// @name SN's GLB Trains Remaining with suggested Slughead42 edits
// @namespace Slughead42
// @description Track career BT goals with a red warning before it's too late. THIS IS SEATTLE NINER'S CODE WITH JUST A FEW TWEAKS BY ME, PUBLISHING JUST SO PEOPLE CAN USE TO TEST IF THEY WANT.
// @version 1.1
// @match https://glb.warriorgeneral.com/game/training.pl?*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const PLATEAU_AGE = 160;
const TP_PER_DAY = 4;
async function fetchData() {
const playerId = new URLSearchParams(window.location.search).get('player_id');
// Fetch Home and Player Profile simultaneously
const [homeRes, playerRes] = await Promise.all([
fetch('https://glb.warriorgeneral.com/game/home.pl'),
fetch(`https://glb.warriorgeneral.com/game/player.pl?player_id=${playerId}`)
]);
const homeHtml = await homeRes.text();
const playerHtml = await playerRes.text();
// 1. Scrape Season/Day from home.pl
const seasonMatch = homeHtml.match(/Season\s*(\d+),\s*Day\s*(\d+)/i);
const sNum = seasonMatch ? parseInt(seasonMatch[1]) : 0;
const dNum = seasonMatch ? parseInt(seasonMatch[2]) : 0;
// 2. Scrape Age from player.pl
const ageMatch = playerHtml.match(/(\d+)d\s*old/i);
const age = ageMatch ? parseInt(ageMatch[1]) : 0;
return { sNum, dNum, age, playerId };
}
async function init() {
const container = document.getElementById('training_settings');
if (!container) return;
// UI Loading State
const bar = document.createElement('div');
bar.style = `background:#444; color:#fff; padding:3px 10px; margin-bottom:8px; border-radius:3px; font-weight:bold; font-size:11px; display:flex; justify-content:space-between; align-items:center; border: 1px solid rgba(0,0,0,0.2);`;
bar.innerHTML = `<span>Syncing Season & Player Data...</span>`;
container.prepend(bar);
try {
const data = await fetchData();
const bankedTP = parseInt(document.querySelector('#training_points_box .points')?.innerText || 0);
const currentBT = parseInt(document.querySelector('a[href*="bonus_tokens.pl"]')?.innerText || 0);
const goal = parseInt(localStorage.getItem(`glb_bt_goal_${data.playerId}`) || 0);
// --- CALCULATIONS ---
const daysLeft = Math.max(0, PLATEAU_AGE - data.age);
let futureTP = daysLeft * TP_PER_DAY;
let dumpsLeft = Math.floor(daysLeft / 40) + 1;
let dotAge = data.age;
if ( data.age < 161 ) {
futureTP = futureTP + ( dumpsLeft * 36 );
if ( data.dNum < 0 ) {
futureTP -= 36;
}
if ( data.age == 0) {
futureTP -= 36;
}
}
const totalTrains = Math.floor((bankedTP + futureTP) / 2);
const minBT = currentBT + (totalTrains * 2);
const maxBT = currentBT + (totalTrains * 6);
// Color Logic
let bg = (maxBT < goal) ? "#000" : (maxBT < (goal + (goal * 0.1)) ? "#dc3545" : (minBT >= goal ? "#28a745" : "#ffc107"));
if (goal === 0) bg = "#007bff";
bar.style.background = bg;
bar.style.color = (bg === "#ffc107") ? "#000" : "#fff";
bar.innerHTML = `
<span>S${data.sNum} D${data.dNum} | Age: ${dotAge}d | ${totalTrains} trains left (${minBT}-${maxBT} BT)</span>
<div style="display:flex; align-items:center; gap:5px;">
Goal: <input type="number" id="set_goal" value="${goal === 0 ? "" : goal}" style="width:45px; height:16px; font-size:10px; border:1px solid #ccc; color:#000;">
<button id="save_bt_pref" type="button" style="height:18px; font-size:9px; cursor:pointer; padding:0 5px; background:#eee; color:#000; border:1px solid #999;">Set Goal</button>
</div>
`;
document.getElementById('save_bt_pref').onclick = (e) => {
e.preventDefault(); e.stopPropagation();
localStorage.setItem(`glb_bt_goal_${data.playerId}`, document.getElementById('set_goal').value);
window.location.reload();
};
} catch (e) {
bar.innerHTML = `<span>Sync Failed. Please check internet connection.</span>`;
console.error(e);
}
}
init();
})();