A classic DOS-style Dopewars mini-game overlay that auto-runs while flying in Torn. Features Torn NPC events, draggable game window, and destination-based trading.
// ==UserScript==
// @name Torn Dopewars – Travel Edition
// @namespace https://greasyfork.org/users/000000 // (replace with your GF user number)
// @version 1.0
// @description A classic DOS-style Dopewars mini-game overlay that auto-runs while flying in Torn. Features Torn NPC events, draggable game window, and destination-based trading.
// @author loneblackbear
// @license MIT
// @match *://www.torn.com/*
// @grant GM_addStyle
// @run-at document-end
// ==/UserScript==
/*
MIT License
Copyright (c) 2025 loneblackbear
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
(function() {
'use strict';
console.log("[Torn Dopewars] Script loaded.");
// --- CONFIG --------------------------------------------------------------
const LOCATIONS = [
"Mexico",
"Cayman Islands",
"Canada",
"Hawaii",
"United Kingdom",
"Argentina",
"Switzerland",
"Japan",
"China",
"UAE",
"South Africa"
];
const COMMODITIES = [
{ id: 1, name: "Weed", baseMin: 100, baseMax: 400 },
{ id: 2, name: "LSD", baseMin: 500, baseMax: 1200 },
{ id: 3, name: "Cocaine", baseMin: 5000, baseMax: 15000 },
{ id: 4, name: "Heroin", baseMin: 3000, baseMax: 8000 },
{ id: 5, name: "Ecstasy", baseMin: 700, baseMax: 2000 },
{ id: 6, name: "Speed", baseMin: 200, baseMax: 900 },
{ id: 7, name: "Shrooms", baseMin: 150, baseMax: 700 }
];
const RANDOM_EVENTS = [
{
label: "Leslie bargain crate",
apply: (state) => {
const item = randomElement(state.items);
const extra = randInt(5, 20);
item.owned += extra;
state.message = `Leslie hooks you up with +${extra} ${item.name}!`;
}
},
{
label: "Duke's collectors",
apply: (state) => {
const loss = Math.min(state.cash, randInt(1000, 5000));
state.cash -= loss;
state.message = `Duke's boys shake you down for $${loss}.`;
}
},
{
label: "Christmas Town gift",
apply: (state) => {
const gain = randInt(2000, 10000);
state.cash += gain;
state.message = `Christmas Town blessing! You gain $${gain}.`;
}
},
{
label: "Dump treasure",
apply: (state) => {
const gain = randInt(500, 4000);
state.cash += gain;
state.message = `You flip dump junk for $${gain}.`;
}
},
{
label: "Casino loss",
apply: (state) => {
const loss = Math.min(state.cash, randInt(500, 6000));
state.cash -= loss;
state.message = `Casino tilt! You blow $${loss}.`;
}
},
{
label: "Black Friday $1 deal",
apply: (state) => {
const item = randomElement(state.items);
item.price = randInt(1, 3);
state.message = `Black Friday deal! ${item.name} costs only $${item.price} today!`;
}
}
];
const DAYS_TOTAL = 30;
const START_CASH = 2000;
const START_DEBT = 5500;
const MAX_LOAN = 10000;
// --- STATE ---------------------------------------------------------------
let gameState = null;
let gameActive = false;
let inFlight = false;
let ui = {
container: null,
pre: null,
help: null,
toggleBtn: null
};
const dragState = {
active: false,
offsetX: 0,
offsetY: 0
};
// --- UTILS ---------------------------------------------------------------
function randInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function randomElement(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
function padRight(str, width) {
str = String(str);
if (str.length >= width) return str.slice(0, width);
return str + " ".repeat(width - str.length);
}
function truncate(str, maxLen) {
str = String(str);
if (str.length <= maxLen) return str;
return str.slice(0, maxLen - 3) + "...";
}
// --- UI (overlay + draggable) -------------------------------------------
function injectStyles() {
const css = `
#tdt-game-window {
position: fixed;
bottom: 8px;
left: 8px;
width: 520px;
height: 260px;
background: #000;
color: #0f0;
font-family: "Lucida Console", "Consolas", monospace;
font-size: 12px;
border: 2px solid #0f0;
padding: 4px 6px;
box-sizing: border-box;
z-index: 999999;
display: none;
opacity: 0.94;
cursor: move; /* visually hint it's draggable */
}
#tdt-game-window:focus {
outline: 1px solid #0f0;
}
#tdt-game-main {
white-space: pre;
line-height: 1.1;
height: 210px;
overflow: hidden;
user-select: none;
}
#tdt-game-help {
border-top: 1px solid #0f0;
padding-top: 2px;
margin-top: 2px;
font-size: 11px;
}
#tdt-game-toggle {
position: fixed;
bottom: 60px;
right: 8px;
z-index: 999999;
font-size: 11px;
background: rgba(0,0,0,0.8);
color: #0f0;
border: 1px solid #0f0;
padding: 4px 6px;
cursor: pointer;
}
`;
if (typeof GM_addStyle !== "undefined") {
GM_addStyle(css);
} else {
const style = document.createElement("style");
style.textContent = css;
document.head.appendChild(style);
}
}
function createUI() {
if (ui.container) return;
const body = document.body || document.documentElement;
// Toggle button – label "dope"
const toggle = document.createElement("button");
toggle.id = "tdt-game-toggle";
toggle.textContent = "dope";
toggle.addEventListener("click", () => {
if (!gameActive) startNewGame();
toggleGameVisible();
});
body.appendChild(toggle);
const container = document.createElement("div");
container.id = "tdt-game-window";
container.tabIndex = 0;
const main = document.createElement("div");
main.id = "tdt-game-main";
const help = document.createElement("div");
help.id = "tdt-game-help";
help.textContent = "[Arrows] Move [B]uy [S]ell [L]oan [R]epay [K]Bank [N]ext Day [Q]uit";
container.appendChild(main);
container.appendChild(help);
body.appendChild(container);
container.addEventListener("keydown", onKeyDown);
container.addEventListener("click", () => container.focus());
// DRAG HANDLERS (drag by clicking anywhere on the window)
container.addEventListener("mousedown", onDragStart);
ui.container = container;
ui.pre = main;
ui.help = help;
ui.toggleBtn = toggle;
console.log("[Torn Dopewars] UI created.");
}
function toggleGameVisible() {
if (!ui.container) return;
if (ui.container.style.display === "none" || ui.container.style.display === "") {
ui.container.style.display = "block";
ui.container.focus();
} else {
ui.container.style.display = "none";
}
}
// --- DRAG LOGIC ----------------------------------------------------------
function onDragStart(e) {
// left button only
if (e.button !== 0) return;
if (!ui.container) return;
dragState.active = true;
const rect = ui.container.getBoundingClientRect();
dragState.offsetX = e.clientX - rect.left;
dragState.offsetY = e.clientY - rect.top;
// switch to explicit top/left so dragging works cleanly
ui.container.style.top = rect.top + "px";
ui.container.style.left = rect.left + "px";
ui.container.style.bottom = "auto";
document.addEventListener("mousemove", onDragMove);
document.addEventListener("mouseup", onDragEnd);
}
function onDragMove(e) {
if (!dragState.active || !ui.container) return;
const newLeft = e.clientX - dragState.offsetX;
const newTop = e.clientY - dragState.offsetY;
ui.container.style.left = newLeft + "px";
ui.container.style.top = newTop + "px";
}
function onDragEnd() {
dragState.active = false;
document.removeEventListener("mousemove", onDragMove);
document.removeEventListener("mouseup", onDragEnd);
}
// --- GAME LOGIC ----------------------------------------------------------
function startNewGame() {
const items = COMMODITIES.map(c => ({
id: c.id,
name: c.name,
baseMin: c.baseMin,
baseMax: c.baseMax,
price: randInt(c.baseMin, c.baseMax),
owned: 0
}));
gameState = {
day: 1,
locationIndex: 0,
cash: START_CASH,
bank: 0,
debt: START_DEBT,
maxSpace: 100,
cursor: 0,
items,
message: "Welcome to Torn Dopewars - fly safe."
};
rollPrices();
maybeRandomEvent();
gameActive = true;
render();
console.log("[Torn Dopewars] New game started.");
}
function endGame(reason) {
if (!gameActive || !gameState) return;
const netWorth = gameState.cash + gameState.bank + totalInventoryValue() - gameState.debt;
gameState.message = `Game over (${reason}). Net worth: $${netWorth}. Press Q to close.`;
render();
gameActive = false;
console.log("[Torn Dopewars] Game ended:", reason);
}
function totalInventory() {
return gameState.items.reduce((sum, it) => sum + it.owned, 0);
}
function totalInventoryValue() {
return gameState.items.reduce((sum, it) => sum + it.owned * it.price, 0);
}
function rollPrices() {
gameState.items.forEach(it => {
it.price = randInt(it.baseMin, it.baseMax);
});
}
function maybeRandomEvent() {
if (Math.random() < 0.35) {
const ev = randomElement(RANDOM_EVENTS);
ev.apply(gameState);
} else {
gameState.message = `Arrived in ${LOCATIONS[gameState.locationIndex]}.`;
}
}
function changeLocation() {
gameState.locationIndex = (gameState.locationIndex + 1) % LOCATIONS.length;
gameState.day++;
if (gameState.day > DAYS_TOTAL) {
endGame("time up");
return;
}
rollPrices();
maybeRandomEvent();
}
function buySelected() {
const item = gameState.items[gameState.cursor];
if (!item) return;
if (gameState.cash < item.price) {
gameState.message = "Not enough cash.";
return;
}
if (totalInventory() >= gameState.maxSpace) {
gameState.message = "Your bags are full.";
return;
}
item.owned++;
gameState.cash -= item.price;
gameState.message = `Bought 1 ${item.name}.`;
}
function sellSelected() {
const item = gameState.items[gameState.cursor];
if (!item) return;
if (item.owned <= 0) {
gameState.message = "You don't own any.";
return;
}
item.owned--;
gameState.cash += item.price;
gameState.message = `Sold 1 ${item.name}.`;
}
function takeLoan() {
const room = MAX_LOAN - gameState.debt;
if (room <= 0) {
gameState.message = "Loan shark: you hit your limit.";
return;
}
const amount = Math.min(2000, room);
gameState.debt += amount;
gameState.cash += amount;
gameState.message = `You borrow $${amount} from Duke's boys.`;
}
function payBank() {
if (gameState.cash <= 0) {
gameState.message = "No spare cash to bank.";
return;
}
const amount = Math.min(gameState.cash, 2000);
gameState.cash -= amount;
gameState.bank += amount;
gameState.message = `Deposited $${amount} in Torn bank.`;
}
function repayDebt() {
if (gameState.cash <= 0 || gameState.debt <= 0) {
gameState.message = "Nothing to repay.";
return;
}
const amount = Math.min(gameState.cash, gameState.debt, 2000);
gameState.cash -= amount;
gameState.debt -= amount;
gameState.message = `Repaid $${amount} of debt.`;
}
// --- INPUT & RENDER ------------------------------------------------------
function onKeyDown(e) {
if (!gameActive && e.key.toLowerCase() !== "q") return;
const key = e.key;
if (["ArrowUp", "ArrowDown"].includes(key)) {
e.preventDefault();
if (key === "ArrowUp") {
gameState.cursor = (gameState.cursor - 1 + gameState.items.length) % gameState.items.length;
} else if (key === "ArrowDown") {
gameState.cursor = (gameState.cursor + 1) % gameState.items.length;
}
render();
return;
}
const k = key.toLowerCase();
if (k === "b") {
e.preventDefault();
buySelected();
render();
} else if (k === "s") {
e.preventDefault();
sellSelected();
render();
} else if (k === "n") {
e.preventDefault();
changeLocation();
render();
} else if (k === "l") {
e.preventDefault();
takeLoan();
render();
} else if (k === "k") {
e.preventDefault();
payBank();
render();
} else if (k === "r") {
e.preventDefault();
repayDebt();
render();
} else if (k === "q") {
e.preventDefault();
if (gameActive) {
endGame("quit");
}
if (ui.container) {
ui.container.style.display = "none";
}
}
}
function render() {
if (!ui.pre || !gameState) return;
const loc = LOCATIONS[gameState.locationIndex];
const header1 = padRight(`Location: ${loc}`, 26)
+ padRight(`Day: ${gameState.day}/${DAYS_TOTAL}`, 16)
+ padRight(`Space: ${totalInventory()}/${gameState.maxSpace}`, 16);
const header2 = padRight(`Cash: $${gameState.cash}`, 20)
+ padRight(`Bank: $${gameState.bank}`, 18)
+ padRight(`Debt: $${gameState.debt}`, 18);
let lines = [];
lines.push(header1);
lines.push(header2);
lines.push("".padEnd(50, "-"));
lines.push(padRight("Item", 16) + padRight("Price", 12) + padRight("Owned", 8));
lines.push("".padEnd(50, "-"));
gameState.items.forEach((it, idx) => {
const cursorMark = (idx === gameState.cursor) ? ">" : " ";
const line = cursorMark + " " +
padRight(it.name, 14) +
padRight(`$${it.price}`, 12) +
padRight(String(it.owned), 8);
lines.push(line);
});
lines.push("".padEnd(50, "-"));
lines.push(truncate(gameState.message || "", 60));
ui.pre.textContent = lines.join("\n");
}
// --- FLIGHT DETECTION (simple heuristic) --------------------------------
function detectInFlight() {
const txt = (document.body.innerText || "").toLowerCase();
if (txt.includes("time left until landing") ||
txt.includes("time left till landing") ||
txt.includes("you are currently flying")) {
return true;
}
return false;
}
function pollFlightStatus() {
const now = detectInFlight();
if (now && !inFlight) {
inFlight = true;
console.log("[Torn Dopewars] Detected takeoff.");
if (!gameActive) startNewGame();
if (ui.container) {
ui.container.style.display = "block";
ui.container.focus();
}
} else if (!now && inFlight) {
inFlight = false;
console.log("[Torn Dopewars] Detected landing.");
endGame("landing");
if (ui.container) ui.container.style.display = "none";
}
}
// --- BOOTSTRAP -----------------------------------------------------------
injectStyles();
const boot = () => {
if (!document.body) {
setTimeout(boot, 200);
return;
}
createUI();
setInterval(pollFlightStatus, 5000);
};
boot();
})();