Greasy Fork is available in English.
Quick stash/pull from loot storage panel.
// ==UserScript==
// @name Dead Frontier Quick Stash
// @author MasterJohnson
// @namespace MasterJohnson
// @version 3.0.0
// @noframes
// @description Quick stash/pull from loot storage panel.
// @match https://fairview.deadfrontier.com/onlinezombiemmo/index.php*
// @exclude https://fairview.deadfrontier.com/onlinezombiemmo/index.php?page=14
// @exclude https://fairview.deadfrontier.com/onlinezombiemmo/index.php?page=18
// @run-at document-start
// @license MIT
// ==/UserScript==
(function() {
'use strict';
if (window.top !== window.self) return;
// ─── CONFIG ───────────────────────────────────────────────────────────────
const PANEL_TOP = '60%';
const PANEL_LEFT = '77%';
const STORAGE_URL = 'https://fairview.deadfrontier.com/onlinezombiemmo/index.php?page=50';
const BACKPACK_URL = 'https://fairview.deadfrontier.com/onlinezombiemmo/index.php?page=25';
const BARRICADE_KEY = 'df_hasBarricade';
const INNER_CITY_SELECTOR = 'button[data-page="21"][data-mod="1"][data-sound="1"]';
const SHOW_IFRAME = true; // set to false to run macros invisibly
// ──────────────────────────────────────────────────────────────────────────
let iframe, stashBtn, pullBtn, barricadeStatus;
let hasBarricade = false;
// ─── UTILITIES ────────────────────────────────────────────────────────────
function waitFor(fn, { maxMs = 8000, intervalMs = 100, label = '' } = {}) {
return new Promise((resolve, reject) => {
const deadline = Date.now() + maxMs;
const tick = () => {
try { const r = fn(); if (r) return resolve(r); } catch (e) { }
if (Date.now() >= deadline) return reject(new Error(`waitFor timeout: ${label}`));
setTimeout(tick, intervalMs);
};
tick();
});
}
function waitForLoad(el) {
return new Promise(r => el.addEventListener('load', r, { once: true }));
}
function getIframeDoc() {
try { return iframe && (iframe.contentDocument || iframe.contentWindow?.document) || null; }
catch (e) { return null; }
}
function waitForBody(fn) {
if (document.body) return fn();
new MutationObserver((_, obs) => {
if (document.body) { obs.disconnect(); fn(); }
}).observe(document.documentElement, { childList: true });
}
// ──────────────────────────────────────────────────────────────────────────
// ─── IFRAME ───────────────────────────────────────────────────────────────
function createIframe() {
console.log('[DF Quick Stash] createIframe');
iframe = document.createElement('iframe');
iframe.style.cssText =
'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);' +
'width:1000px;height:800px;border:20px solid rgba(0,0,0,0.6);' +
'border-radius:6px;box-shadow:0 8px 30px rgba(0,0,0,0.8);' +
'background:#fff;z-index:2147483646;' +
(SHOW_IFRAME ? '' : 'visibility:hidden;pointer-events:none;');
document.body.appendChild(iframe);
}
function destroyIframe() {
console.log('[DF Quick Stash] destroyIframe');
if (iframe) { iframe.remove(); iframe = null; }
}
async function navigateTo(url) {
if (!iframe) createIframe();
console.log('[DF Quick Stash] navigateTo ->', url);
const loaded = waitForLoad(iframe);
iframe.src = url;
await loaded;
await new Promise(r => setTimeout(r, 500));
}
// ──────────────────────────────────────────────────────────────────────────
// ─── WAIT FOR PAGE READY ──────────────────────────────────────────────────
function waitForStorageDoc() {
console.log('[DF Quick Stash] waitForStorageDoc');
return waitFor(() => {
const doc = getIframeDoc();
const s = doc?.getElementById('invtostorage');
const m = doc?.getElementById('storagetoinv');
return (s && m) ? doc : null;
}, { maxMs: 15000, intervalMs: 80, label: 'waitForStorageDoc' });
}
function waitForBackpackDoc() {
console.log('[DF Quick Stash] waitForBackpackDoc');
return waitFor(() => {
const doc = getIframeDoc();
const btn = doc?.querySelector('#backpackWithdraw, button[data-action="backpackWithdraw"]');
return btn ? doc : null;
}, { maxMs: 15000, intervalMs: 120, label: 'waitForBackpackDoc' });
}
// ──────────────────────────────────────────────────────────────────────────
// ─── ACTIONS ──────────────────────────────────────────────────────────────
async function deposit(doc) {
console.log('[DF Quick Stash] deposit');
const btn = doc.getElementById('invtostorage');
if (!btn || btn.disabled) { console.log('[DF Quick Stash] deposit: nothing to deposit'); return; }
btn.click();
await waitFor(() => getIframeDoc()?.getElementById('invtostorage')?.disabled,
{ maxMs: 10000, intervalMs: 80, label: 'deposit complete' });
await new Promise(r => setTimeout(r, 500));
console.log('[DF Quick Stash] deposit: done');
}
async function withdraw(doc) {
console.log('[DF Quick Stash] withdraw');
const btn = doc.querySelector('#backpackWithdraw, button[data-action="backpackWithdraw"]');
if (!btn) {
console.warn('[DF Quick Stash] withdraw: button not found');
throw new Error('withdraw: button not found');
}
if (btn.disabled || btn.hasAttribute('disabled')) {
console.log('[DF Quick Stash] withdraw: waiting for button to enable');
try {
await waitFor(() => {
const b = getIframeDoc()?.querySelector('#backpackWithdraw, button[data-action="backpackWithdraw"]');
return b && !b.disabled && !b.hasAttribute('disabled');
}, { maxMs: 4000, intervalMs: 120, label: 'withdraw enable' });
} catch (e) {
console.warn('[DF Quick Stash] withdraw: button stayed disabled');
throw new Error('withdraw: button stayed disabled');
}
}
btn.click();
await waitFor(() => {
const b = getIframeDoc()?.querySelector('#backpackWithdraw, button[data-action="backpackWithdraw"]');
return !b || b.disabled || b.hasAttribute('disabled');
}, { maxMs: 10000, intervalMs: 80, label: 'withdraw complete' }).catch(() => { });
await new Promise(r => setTimeout(r, 500));
console.log('[DF Quick Stash] withdraw: done');
}
async function pull(doc) {
console.log('[DF Quick Stash] pull');
const btn = doc.getElementById('storagetoinv');
if (!btn || btn.disabled) { console.log('[DF Quick Stash] pull: nothing to pull'); return; }
btn.click();
await waitFor(() => {
const b = getIframeDoc()?.getElementById('storagetoinv');
return !b || b.disabled;
}, { maxMs: 10000, intervalMs: 80, label: 'pull complete' });
await new Promise(r => setTimeout(r, 500));
console.log('[DF Quick Stash] pull: done');
}
// ──────────────────────────────────────────────────────────────────────────
// ─── BARRICADE / INNER CITY ───────────────────────────────────────────────
function updateBarricadeUI() {
barricadeStatus.textContent = hasBarricade ? '# BARRICADE READY' : '# NO BARRICADE';
barricadeStatus.className = hasBarricade ? 'has-barricade' : 'no-barricade';
const btn = document.querySelector(INNER_CITY_SELECTOR);
if (btn) {
btn.style.display = hasBarricade ? '' : 'none';
console.log('[DF Quick Stash] Inner City button ->', hasBarricade ? 'shown' : 'hidden');
}
}
// ──────────────────────────────────────────────────────────────────────────
// ─── MAIN MACRO ───────────────────────────────────────────────────────────
// storage → deposit → backpack → withdraw → storage → deposit → destroy iframe
async function runStashFlow() {
stashBtn.disabled = true;
stashBtn.textContent = '> RUNNING...';
stashBtn.classList.remove('done');
pullBtn.disabled = true;
console.log('[DF Quick Stash] runStashFlow start');
try {
// 1. Storage → deposit
stashBtn.textContent = '> DEPOSIT...';
await navigateTo(STORAGE_URL);
let doc = await waitForStorageDoc();
hasBarricade = !!doc.querySelector('.item[data-type="barricadekit"]');
localStorage.setItem(BARRICADE_KEY, hasBarricade);
console.log('[DF Quick Stash] barricade detected ->', hasBarricade);
updateBarricadeUI();
await deposit(doc);
// 2. Backpack → withdraw
stashBtn.textContent = '> BACKPACK...';
await navigateTo(BACKPACK_URL);
doc = await waitForBackpackDoc();
await withdraw(doc);
// 3. Storage → deposit
stashBtn.textContent = '> DEPOSIT...';
await navigateTo(STORAGE_URL);
doc = await waitForStorageDoc();
await deposit(doc);
await new Promise(r => setTimeout(r, 1500));
// 4. Done — destroy iframe
destroyIframe();
stashBtn.textContent = '> DONE!';
stashBtn.classList.add('done');
console.log('[DF Quick Stash] runStashFlow complete');
} catch (err) {
console.error('[DF Quick Stash] runStashFlow error:', err);
stashBtn.textContent = '> ERROR!';
destroyIframe();
} finally {
if (!stashBtn.classList.contains('done')) stashBtn.disabled = false;
pullBtn.disabled = false;
}
}
// ──────────────────────────────────────────────────────────────────────────
// ─── PULL MACRO ───────────────────────────────────────────────────────────
// storage → pull → destroy iframe
async function runPullFlow() {
pullBtn.disabled = true;
stashBtn.disabled = true;
pullBtn.textContent = '> PULLING...';
pullBtn.classList.remove('done');
console.log('[DF Quick Stash] runPullFlow start');
try {
await navigateTo(STORAGE_URL);
const doc = await waitForStorageDoc();
await pull(doc);
await new Promise(r => setTimeout(r, 1500));
destroyIframe();
console.log('[DF Quick Stash] runPullFlow complete — reloading page');
window.location.reload();
} catch (err) {
console.error('[DF Quick Stash] runPullFlow error:', err);
pullBtn.textContent = '> ERROR!';
destroyIframe();
} finally {
if (!pullBtn.classList.contains('done')) pullBtn.disabled = false;
stashBtn.disabled = false;
}
}
function injectStyles() {
const style = document.createElement('style');
style.textContent = `
#loot-panel-custom {
position: absolute; top: ${PANEL_TOP}; left: ${PANEL_LEFT};
padding: 10px;
background: linear-gradient(135deg, #1a1a1a 0%, #0d0d0d 100%);
border: 2px solid #3e3e3e; border-radius: 2px;
box-shadow: inset 0 0 10px #000, 0 5px 15px rgba(0,0,0,0.8);
z-index: 9999; display: flex; flex-direction: column; gap: 6px;
width: 150px; font-family: "Courier New", Courier, monospace;
}
#loot-panel-custom .header {
font-size: 11px; color: #8b0000; font-weight: bold;
text-transform: uppercase; margin-bottom: 4px;
border-bottom: 1px solid #333; letter-spacing: 1px;
}
.loot-action-btn {
width: 100%; height: 28px; background: #2a2a2a; color: #a9a9a9;
border: 1px solid #444; border-left: 4px solid #8b0000;
cursor: pointer; text-align: left; padding-left: 8px;
font-size: 10px; text-transform: uppercase; transition: all 0.2s;
}
.loot-action-btn:hover { background: #333; color: #ff4500; border-color: #ff4500; }
.loot-action-btn:active { transform: translateX(2px); }
.loot-action-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
.loot-action-btn.done {
opacity: 1 !important; color: #4caf50 !important;
border-color: #4caf50 !important; border-left-color: #4caf50 !important;
box-shadow: 0 0 8px #4caf50, 0 0 18px rgba(76,175,80,0.45);
animation: donePulse 1.5s ease-in-out infinite;
}
@keyframes donePulse {
0%, 100% { box-shadow: 0 0 8px #4caf50, 0 0 18px rgba(76,175,80,0.45); }
50% { box-shadow: 0 0 14px #4caf50, 0 0 30px rgba(76,175,80,0.75); }
}
#barricade-status {
width: 100%; padding: 4px 0 4px 8px; font-size: 10px;
text-transform: uppercase; border-left: 4px solid #555;
box-sizing: border-box; letter-spacing: 0.5px; transition: all 0.3s; color: #555;
}
#barricade-status.waiting { color: #555; border-left-color: #555; }
#barricade-status.has-barricade { color: #4caf50; border-left-color: #4caf50; }
#barricade-status.no-barricade { color: #8b0000; border-left-color: #8b0000; }
`;
document.head.appendChild(style);
}
function injectPanel() {
const panel = document.createElement('div');
panel.id = 'loot-panel-custom';
panel.innerHTML = `
<div class="header">LOOT</div>
<button class="loot-action-btn" id="stash-btn">> STASH ALL</button>
<button class="loot-action-btn" id="pull-btn">> PULL FROM STORAGE</button>
<div id="barricade-status" class="waiting"># PRESS STASH</div>
`;
document.body.appendChild(panel);
stashBtn = document.getElementById('stash-btn');
pullBtn = document.getElementById('pull-btn');
barricadeStatus = document.getElementById('barricade-status');
stashBtn.onclick = () => runStashFlow();
pullBtn.onclick = () => runPullFlow();
console.log("[DF Quick Stash] Panel injected.")
}
// ──────────────────────────────────────────────────────────────────────────
function bootstrap() {
console.log('[DF Quick Stash] bootstrap');
injectStyles();
injectPanel();
// Restore persisted barricade state
try { hasBarricade = localStorage.getItem(BARRICADE_KEY) === 'true'; } catch (e) { }
updateBarricadeUI();
if (hasBarricade) barricadeStatus.textContent = '# BARRICADE READY';
// Watch for Inner City button appearing later in the DOM
if (!document.querySelector(INNER_CITY_SELECTOR)) {
new MutationObserver((_, obs) => {
if (document.querySelector(INNER_CITY_SELECTOR)) { updateBarricadeUI(); obs.disconnect(); }
}).observe(document.body, { childList: true, subtree: true });
}
}
waitForBody(bootstrap);
})();