Dead Frontier Quick Stash

Quick stash/pull from loot storage panel.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// ==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);
})();