Tracks how many times each employee has been trained. Reset all counts with one button.
// ==UserScript==
// @name Torn - Training Tracker
// @namespace https://www.torn.com/
// @version 1.1
// @description Tracks how many times each employee has been trained. Reset all counts with one button.
// @author Systoned
// @match https://www.torn.com/companies.php*
// @grant none
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
const STORAGE_KEY = 'resilience_train_counts';
function getCounts() {
try {
return JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};
} catch (e) {
return {};
}
}
function saveCounts(counts) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(counts));
}
function injectCountBadge(li, id) {
const counts = getCounts();
const count = counts[id] || 0;
const existing = li.querySelector('.tt-count');
if (existing) {
existing.textContent = `Trained: ${count}`;
return;
}
const trainDiv = li.querySelector('.train');
if (!trainDiv) return;
const badge = document.createElement('div');
badge.className = 'tt-count';
badge.style.cssText = `
clear: both;
padding: 4px 10px;
margin: 4px 0 0 0;
background: #2a2a2a;
color: #aaa;
font-size: 12px;
border-radius: 4px;
display: inline-block;
`;
badge.textContent = `Trained: ${count}`;
// Inject as a sibling after .train, not inside it
trainDiv.insertAdjacentElement('afterend', badge);
}
function injectResetButton() {
const existing = document.getElementById('tt-reset-btn');
if (existing) existing.remove();
const target = document.querySelector('.title.p10');
if (!target) return;
const btn = document.createElement('button');
btn.id = 'tt-reset-btn';
btn.textContent = 'Reset Train Counts';
btn.style.cssText = `
margin-left: 12px;
padding: 4px 10px;
background: #8b0000;
color: #fff;
border: none;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
vertical-align: middle;
`;
btn.addEventListener('click', () => {
if (confirm('Reset all training counts to zero?')) {
saveCounts({});
document.querySelectorAll('.tt-count').forEach(el => {
el.textContent = 'Trained: 0';
});
}
});
target.appendChild(btn);
}
function processEmployees() {
const rows = document.querySelectorAll('li[data-user]');
rows.forEach(li => {
const id = li.getAttribute('data-user');
if (!id) return;
injectCountBadge(li, id);
});
injectResetButton();
}
// Watch for AJAX train completions
$('body').on('ajaxComplete', function (e, xhr, settings) {
const target = (settings.url || '') + (settings.data || '');
if (!target.includes('trainemp2')) return;
let data;
try {
data = JSON.parse(xhr.responseText);
} catch (err) {
return;
}
if (!data.success) return;
const idMatch = target.match(/ID=(\d+)/);
if (!idMatch) return;
const id = idMatch[1];
const counts = getCounts();
counts[id] = (counts[id] || 0) + 1;
saveCounts(counts);
const li = document.querySelector(`li[data-user="${id}"]`);
if (li) injectCountBadge(li, id);
});
function init() {
if (!window.location.href.includes('companies.php')) return;
const employeesPanel = document.getElementById('employees');
if (!employeesPanel) return;
let debounce;
const observer = new MutationObserver(() => {
clearTimeout(debounce);
debounce = setTimeout(processEmployees, 300);
});
observer.observe(employeesPanel, { childList: true, subtree: true });
// Run once in case the tab is already visible on load
processEmployees();
}
if (document.readyState === 'complete') {
init();
} else {
window.addEventListener('load', init);
}
})();