Dead Frontier Quick Stash

Quick stash/pull from loot storage panel.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

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