您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Tracks and displays gained experience in The West with activity details, including reset functionality and draggable window
// ==UserScript== // @name The West - Experience // @namespace http://tampermonkey.net/ // @version 6.8 // @description Tracks and displays gained experience in The West with activity details, including reset functionality and draggable window // @author DK, Shikokuchuo // @include https://*.the-west.*/game.php* // @grant none // ==/UserScript== (function() { 'use strict'; // Inicjalizacja zmiennych let totalExperience = JSON.parse(localStorage.getItem('totalExperience')) || 0; let experienceLog = JSON.parse(localStorage.getItem('experienceLog')) || []; let savedPosition = JSON.parse(localStorage.getItem('experienceTrackerPosition')) || { top: '50px', left: 'auto', right: '310px' }; let isCollectingHistory = false; let isPaused = false; let shouldCancel = false; let collectionStartTime = null; let processedPagesTime = []; let tempExpLog = []; let messageTimeout = null; let isTrackerVisible = JSON.parse(localStorage.getItem('experienceTrackerVisible')) !== false; // domyślnie widoczny // Dodajemy zmienną na nazwę gracza let playerName = Character.name; // Dodajemy nowe zmienne do przechowywania stanu kolekcji let collectionState = JSON.parse(localStorage.getItem('collectionState')) || { inProgress: false, currentPage: 1, folder: null, tempLog: [], totalPages: 0 }; // Funkcja do wyświetlania komunikatów function showError(message, duration = 5000) { if (messageTimeout) { clearTimeout(messageTimeout); messageTimeout = null; } const statusElement = document.querySelector('#collection-status'); if (statusElement) { statusElement.innerHTML = ` <div style=" background: rgba(0,0,0,0.7); padding: 10px; border-radius: 5px; margin-top: 10px; color: white; text-align: center; animation: fadeIn 0.3s ease-in-out; "> ${message} </div> `; if (duration > 0) { messageTimeout = setTimeout(() => { if (statusElement) { statusElement.innerHTML = ''; } messageTimeout = null; }, duration); } } } // Funkcja do pokazywania komunikatu o postępie function showProgress(message) { const statusElement = document.querySelector('#collection-status'); if (statusElement) { statusElement.innerHTML = ` <div style=" background: rgba(0,0,0,0.7); padding: 10px; border-radius: 5px; margin-top: 10px; color: white; text-align: center; "> ${message} </div> `; } } // Dodaj style CSS dla animacji const style = document.createElement('style'); style.textContent = ` @keyframes fadeIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } } `; document.head.appendChild(style); // Funkcja do aktualizacji statusu kolekcji function updateCollectionStatus(processed, total, foundEntries) { let statusElement = document.querySelector('#collection-status'); const experienceTracker = document.querySelector('#experience-tracker'); const controlButtons = document.querySelector('#collection-controls'); // Jeśli element nie istnieje lub nie ma właściwej klasy, tworzymy go na nowo if (!statusElement || !statusElement.classList.contains('collection-status-fixed')) { // Usuń stary element jeśli istnieje if (statusElement) { statusElement.remove(); } // Stwórz nowy element statusElement = document.createElement('div'); statusElement.id = 'collection-status'; statusElement.classList.add('collection-status-fixed'); statusElement.style.cssText = ` margin: 15px 0; opacity: 0; transition: opacity 0.3s ease; `; // Dodaj status przed przyciskami if (experienceTracker && controlButtons) { experienceTracker.insertBefore(statusElement, controlButtons); } else { experienceTracker.appendChild(statusElement); } // Pokaż element z animacją setTimeout(() => { statusElement.style.opacity = '1'; }, 100); } const percent = Math.round((processed / total) * 100); // Używamy tempExpLog zamiast experienceLog podczas zbierania danych const workEntries = tempExpLog.filter(e => e.type === 'work').length; const duelEntries = tempExpLog.filter(e => e.type === 'duel').length; const battleEntries = tempExpLog.filter(e => e.type === 'battle').length; // Dodajmy też szacowany pozostały czas const timeEstimate = calculateTimeEstimate(processed, total); const timeInfo = timeEstimate ? `<div class="time-estimate">⏱️ ${timeEstimate}</div>` : ''; statusElement.innerHTML = ` <div class="status-container" style=" background: rgba(20, 20, 20, 0.95); padding: 15px; border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.1); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); "> <div class="status-header" style=" display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); "> <div style=" color: #F1C40F; font-size: 14px; font-weight: 600; letter-spacing: 0.5px; ">Status pobierania</div> <div style=" background: rgba(241, 196, 15, 0.1); color: #F1C40F; padding: 4px 8px; border-radius: 12px; font-size: 12px; font-weight: 600; ">${percent}%</div> </div> <div class="status-grid" style=" display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 15px; "> <div class="stat-box" style=" background: rgba(46, 204, 113, 0.1); padding: 10px; border-radius: 8px; border: 1px solid rgba(46, 204, 113, 0.2); "> <div style="color: #2ecc71; font-size: 12px; margin-bottom: 5px;">Prace</div> <div style="font-size: 14px; font-weight: 600; color: #fff;"> ${workEntries}<br> <span style="font-size: 12px; color: #2ecc71;">${tempExpLog.filter(e => e.type === 'work').reduce((sum, e) => sum + e.amount, 0)} XP</span> </div> </div> <div class="stat-box" style=" background: rgba(231, 76, 60, 0.1); padding: 10px; border-radius: 8px; border: 1px solid rgba(231, 76, 60, 0.2); "> <div style="color: #e74c3c; font-size: 12px; margin-bottom: 5px;">Pojedynki</div> <div style="font-size: 14px; font-weight: 600; color: #fff;"> ${duelEntries}<br> <span style="font-size: 12px; color: #e74c3c;">${tempExpLog.filter(e => e.type === 'duel').reduce((sum, e) => sum + e.amount, 0)} XP</span> </div> </div> <div class="stat-box" style=" background: rgba(155, 89, 182, 0.1); padding: 10px; border-radius: 8px; border: 1px solid rgba(155, 89, 182, 0.2); "> <div style="color: #9b59b6; font-size: 12px; margin-bottom: 5px;">Bitwy</div> <div style="font-size: 14px; font-weight: 600; color: #fff;"> ${battleEntries}<br> <span style="font-size: 12px; color: #9b59b6;">${tempExpLog.filter(e => e.type === 'battle').reduce((sum, e) => sum + e.amount, 0)} XP</span> </div> </div> </div> <div class="progress-info" style=" display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; font-size: 12px; color: #95a5a6; "> <div>Postęp: ${processed}/${total} stron</div> <div>Znalezione wpisy: ${foundEntries}</div> </div> <div class="progress-container" style=" background: rgba(255, 255, 255, 0.1); height: 6px; border-radius: 3px; overflow: hidden; margin-bottom: 10px; "> <div class="progress-bar" style=" width: ${percent}%; background: linear-gradient(90deg, #2ecc71, #27ae60); height: 100%; transition: width 0.3s ease; border-radius: 3px; "></div> </div> ${timeInfo ? ` <div style=" text-align: center; color: #95a5a6; font-size: 12px; margin-top: 10px; padding: 5px; background: rgba(255, 255, 255, 0.05); border-radius: 4px; ">${timeInfo}</div> ` : ''} </div> `; } // Dodaj nową funkcję do obsługi przeciągania statusu function makeStatusDraggable(element) { let isDragging = false; let currentX; let currentY; let initialX; let initialY; let xOffset = 0; let yOffset = 0; element.addEventListener('mousedown', dragStart); document.addEventListener('mousemove', drag); document.addEventListener('mouseup', dragEnd); function dragStart(e) { if (e.target.closest('.status-container')) { initialX = e.clientX - xOffset; initialY = e.clientY - yOffset; isDragging = true; } } function drag(e) { if (isDragging) { e.preventDefault(); currentX = e.clientX - initialX; currentY = e.clientY - initialY; xOffset = currentX; yOffset = currentY; setTranslate(currentX, currentY, element); } } function dragEnd(e) { initialX = currentX; initialY = currentY; isDragging = false; } function setTranslate(xPos, yPos, el) { // Zabezpieczenie przed wyjściem poza ekran const rect = el.getBoundingClientRect(); const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; if (xPos < -rect.width + 50) xPos = -rect.width + 50; if (xPos > windowWidth - 50) xPos = windowWidth - 50; if (yPos < 0) yPos = 0; if (yPos > windowHeight - 50) yPos = windowHeight - 50; el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`; } } // Funkcja do aktualizacji statystyk XP function updateXPStats() { const workXP = experienceLog.reduce((sum, entry) => { if (entry.type === 'work') { return sum + entry.amount; } return sum; }, 0); const duelXP = experienceLog.reduce((sum, entry) => { if (entry.type === 'duel') { return sum + entry.amount; } return sum; }, 0); const battleXP = experienceLog.reduce((sum, entry) => { if (entry.type === 'battle') { return sum + entry.amount; } return sum; }, 0); const workXPElement = document.querySelector('#work-xp'); const duelXPElement = document.querySelector('#duel-xp'); const battleXPElement = document.querySelector('#battle-xp'); if (workXPElement) workXPElement.textContent = workXP + ' XP'; if (duelXPElement) duelXPElement.textContent = duelXP + ' XP'; if (battleXPElement) battleXPElement.textContent = battleXP + ' XP'; } // Funkcja do debugowania systemu raportów function debugReportSystem() { // Sprawdź różne parametry zapytań const testQueries = [ { page: 1, folder: 'all' }, { page: 1, folder: 'all', offset: 0 }, { page: 1, folder: 'all', offset: 0, limit: 50 } ]; function makeTestQuery(params, index) { setTimeout(() => { Ajax.remoteCall('reports', 'get_reports', params, function(response) { }); }, index * 1000); } testQueries.forEach(makeTestQuery); } function formatTimeRemaining(milliseconds) { if (!milliseconds || isNaN(milliseconds)) return "obliczanie..."; const seconds = milliseconds / 1000; if (seconds < 60) return Math.round(seconds) + ' sekund'; const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.round(seconds % 60); return minutes + ' min ' + remainingSeconds + ' sek'; } function calculateTimeEstimate(processedPages, totalPages) { if (processedPages < 2) return null; const currentTime = Date.now(); const timeElapsed = currentTime - collectionStartTime; // Oblicz średni czas na stronę w milisekundach const avgTimePerPage = timeElapsed / processedPages; if (isNaN(avgTimePerPage)) return null; // Oblicz pozostały czas w milisekundach const remainingPages = totalPages - processedPages; const estimatedTimeRemaining = remainingPages * avgTimePerPage; return formatTimeRemaining(estimatedTimeRemaining); } // Funkcja do tłumaczenia nazw kategorii function getCategoryName(folder) { switch(folder) { case 'all': return 'WSZYSTKIE RAPORTY'; case 'job': return 'PRACE'; case 'duel': return 'POJEDYNKI'; case 'fortbattle': return 'BITWY FORTOWE'; default: return folder; } } // Funkcja do zapisywania stanu kolekcji function saveCollectionState(state) { localStorage.setItem('collectionState', JSON.stringify(state)); } // Funkcja do czyszczenia stanu kolekcji function clearCollectionState() { collectionState = { inProgress: false, currentPage: 1, folder: null, tempLog: [], totalPages: 0 }; saveCollectionState(collectionState); } function toggleMainElements(show) { const experienceHeader = document.querySelector('#experience-tracker > div:first-child'); const statsGrid = document.querySelector('#experience-tracker > div:nth-child(2)'); const statusElement = document.querySelector('#collection-status'); if (experienceHeader && statsGrid) { if (show) { // Pokazujemy główne elementy z animacją experienceHeader.style.display = 'flex'; statsGrid.style.display = 'grid'; setTimeout(() => { experienceHeader.style.opacity = '1'; statsGrid.style.opacity = '1'; if (statusElement) { statusElement.style.opacity = '0'; setTimeout(() => { statusElement.remove(); }, 300); } }, 50); } else { // Ukrywamy główne elementy z animacją experienceHeader.style.opacity = '0'; statsGrid.style.opacity = '0'; setTimeout(() => { experienceHeader.style.display = 'none'; statsGrid.style.display = 'none'; }, 300); } } } // Funkcja do zbierania historii doświadczenia function collectExperienceHistory(maxPages = null, folder = 'all', callback = null) { if (isCollectingHistory) { showError('Pobieranie danych jest już w trakcie. Poczekaj na zakończenie.'); if (callback) callback(); return; } // Ukryj główne elementy przed rozpoczęciem pobierania toggleMainElements(false); isCollectingHistory = true; isPaused = false; shouldCancel = false; collectionStartTime = Date.now(); processedPagesTime = []; // Sprawdzamy, czy mamy zapisany stan kolekcji do wznowienia const shouldResume = collectionState.inProgress && collectionState.folder === folder && collectionState.tempLog.length > 0; if (shouldResume) { tempExpLog = collectionState.tempLog; showError(`Wznawianie zbierania danych dla ${getCategoryName(folder)} od strony ${collectionState.currentPage}`); } else { tempExpLog = []; collectionState.currentPage = 1; collectionState.folder = folder; collectionState.tempLog = []; } // Zachowujemy istniejące wpisy z innych kategorii let existingEntries = []; if (folder !== 'all') { existingEntries = experienceLog.filter(entry => { const entryCategory = entry.type === 'work' ? 'job' : entry.type === 'duel' ? 'duel' : entry.type === 'battle' ? 'fortbattle' : 'other'; return entryCategory !== folder; }); } // Pokaż przyciski kontrolne i ukryj standardowe const controlButtons = document.querySelector('#collection-controls'); const standardButtons = document.querySelector('#standard-buttons'); if (controlButtons) { controlButtons.style.display = 'grid'; } if (standardButtons) { standardButtons.style.display = 'none'; } // Dostosowane opóźnienia const MIN_DELAY = 2400; const MAX_DELAY = 3000; const RETRY_DELAY = 3000; let processedPages = shouldResume ? collectionState.currentPage - 1 : 0; let failedAttempts = 0; const MAX_RETRIES = 3; function getRandomDelay() { return Math.floor(Math.random() * (MAX_DELAY - MIN_DELAY + 1)) + MIN_DELAY; } function finishCollection(wasSuccessful = true) { isCollectingHistory = false; isPaused = false; // Pokaż z powrotem główne elementy toggleMainElements(true); // Przywróć standardowe przyciski i ukryj kontrolne const controlButtons = document.querySelector('#collection-controls'); const standardButtons = document.querySelector('#standard-buttons'); if (controlButtons) { controlButtons.style.display = 'none'; } if (standardButtons) { standardButtons.style.display = 'grid'; } // Ukryj status pobierania const statusElement = document.querySelector('#collection-status'); if (statusElement) { statusElement.innerHTML = ''; } if (!wasSuccessful) { // Zapisz stan kolekcji do późniejszego wznowienia collectionState.inProgress = true; collectionState.tempLog = tempExpLog; saveCollectionState(collectionState); showError('Przerwano zbieranie danych. Możesz wznowić je później.'); if (callback) callback(); return; } // Wyczyść stan kolekcji po udanym zakończeniu clearCollectionState(); if (tempExpLog.length === 0 && existingEntries.length === 0) { showError('Nie znaleziono żadnych wpisów z doświadczeniem.'); if (callback) callback(); return; } // Łączymy nowe wpisy z zachowanymi wpisami z innych kategorii if (folder === 'all') { experienceLog = tempExpLog; } else { experienceLog = [...tempExpLog, ...existingEntries]; } totalExperience = experienceLog.reduce((sum, entry) => sum + entry.amount, 0); localStorage.setItem('experienceLog', JSON.stringify(experienceLog)); localStorage.setItem('totalExperience', JSON.stringify(totalExperience)); updateDisplay(); updateXPStats(); const newEntriesCount = tempExpLog.length; showError(`Zakończono pobieranie ${getCategoryName(folder)}! ${folder === 'all' ? 'Znaleziono' : 'Dodano'} ${newEntriesCount} wpisów.`); if (callback) { callback(); } } // Pobierz wszystkie raporty Ajax.remoteCall('reports', 'get_reports', { page: 1, folder: folder }, function(initialData) { if (!initialData || initialData.error) { isCollectingHistory = false; showError('Nie udało się pobrać informacji o raportach. Spróbuj ponownie.'); return; } const totalPages = initialData.count; collectionState.totalPages = totalPages; const pagesToProcess = maxPages ? Math.min(maxPages, totalPages) : totalPages; function processPage(page) { if (shouldCancel) { finishCollection(false); return; } if (!isCollectingHistory || page > pagesToProcess) { finishCollection(true); return; } if (isPaused) { // Zapisz aktualny stan przed zatrzymaniem collectionState.currentPage = page; collectionState.tempLog = tempExpLog; collectionState.inProgress = true; saveCollectionState(collectionState); setTimeout(() => processPage(page), 500); return; } collectionState.currentPage = page; saveCollectionState(collectionState); Ajax.remoteCall('reports', 'get_reports', { page: page, folder: folder }, function(data) { if (!isCollectingHistory || shouldCancel) return; if (data && !data.error && data.reports && data.reports.length > 0) { failedAttempts = 0; data.reports.forEach(report => { // Najpierw sprawdzamy typ raportu const reportType = report.title.includes('Raport dot. pracy') ? 'work' : report.title.includes('Pojedynek') ? 'duel' : (report.title.includes('Bitwa') || report.title.includes('Fort')) ? 'battle' : 'other'; // Domyślnie zakładamy, że nie powinniśmy zliczyć XP let shouldCount = false; // Dla pojedynków sprawdzamy szczegóły if (reportType === 'duel') { // Jeśli to pojedynek z bandytą, zawsze zliczamy if (report.title.includes('bandytą') || report.title.includes('bandit')) { shouldCount = true; } else { // Dla pojedynków z graczami sprawdzamy zwycięzcę const winnerMatch = report.popupData.match(/Zwycięzca:\s*([^<]+)/) || report.popupData.match(/(\w+)\s+wygrywa pojedynek!/); if (winnerMatch) { const winner = winnerMatch[1].trim(); shouldCount = winner === playerName; } else { shouldCount = false; } } } else { // Dla nie-pojedynków zawsze zliczamy shouldCount = true; } // Tylko jeśli powinniśmy zliczyć XP, sprawdzamy czy jest XP do dodania if (shouldCount) { const expMatch = report.popupData.match(/experience.png[^>]*>(?:[^<]*<\/[^>]+>)*[^<]*<td>(\d+)<\/td>/) || report.popupData.match(/Doświadczenie<\/span>\s*<span[^>]*>(\d+)\s*punktów/); const exp = expMatch ? parseInt(expMatch[1]) : 0; if (exp > 0) { tempExpLog.push({ amount: exp, source: report.title, timestamp: report.date_received, page: page, type: reportType }); // Aktualizuj zapisany stan collectionState.tempLog = tempExpLog; saveCollectionState(collectionState); } } }); processedPages++; updateCollectionStatus(processedPages, pagesToProcess, tempExpLog.length); setTimeout(() => processPage(page + 1), getRandomDelay()); } else { failedAttempts++; if (failedAttempts < MAX_RETRIES) { setTimeout(() => processPage(page), RETRY_DELAY); } else { showError(`Nie udało się przetworzyć strony ${page} po ${MAX_RETRIES} próbach`); finishCollection(true); } } }); } processPage(shouldResume ? collectionState.currentPage : 1); }); } // Funkcja do aktualizacji wyświetlania function updateDisplay() { const totalExperienceElement = document.querySelector('#total-experience'); if (totalExperienceElement) { totalExperienceElement.textContent = totalExperience; } } // Funkcja do dodawania okna z doświadczeniem function addExperienceWindow() { const existingWindow = document.querySelector('#experience-tracker'); if (existingWindow) { existingWindow.style.display = isTrackerVisible ? 'block' : 'none'; return; } // Pobierz zapisaną pozycję const savedPosition = JSON.parse(localStorage.getItem('experienceTrackerPosition')) || { top: '50px', left: 'auto', right: '310px' }; const trackerDiv = document.createElement('div'); trackerDiv.id = 'experience-tracker'; trackerDiv.style.cssText = ` position: fixed; top: ${savedPosition.top}; left: ${savedPosition.left}; right: ${savedPosition.right}; width: 280px; padding: 15px; background: rgba(20, 20, 20, 0.95); color: #fff; border: 1px solid #444; border-radius: 8px; z-index: 1000; cursor: move; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); font-family: Arial, sans-serif; display: ${isTrackerVisible ? 'block' : 'none'}; `; // Dodaj style dla animacji const style = document.createElement('style'); style.textContent = ` #experience-tracker > div { transition: opacity 0.3s ease; } #collection-status { transition: opacity 0.3s ease; } `; document.head.appendChild(style); trackerDiv.innerHTML = ` <div style="background: linear-gradient(135deg, rgba(241, 196, 15, 0.1), rgba(241, 196, 15, 0.05)); border-radius: 10px; padding: 15px; margin-bottom: 15px; border: 1px solid rgba(241, 196, 15, 0.2); display: flex; justify-content: space-between; align-items: center;"> <div style="display: flex; flex-direction: column;"> <span style="font-size: 12px; color: #F1C40F; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 4px;">Całkowite doświadczenie</span> <span id="total-experience" style="font-size: 24px; font-weight: bold; color: #F1C40F; transition: all 0.3s ease;">${totalExperience} XP</span> </div> </div> <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 10px; font-size: 13px;"> <div style="background: rgba(39, 174, 96, 0.2); padding: 8px; border-radius: 8px; text-align: center; border: 1px solid rgba(39, 174, 96, 0.3);"> <div style="color: #2ecc71; font-weight: bold;">Prace</div> <span id="work-xp" style="color: #fff;">0 XP</span> </div> <div style="background: rgba(231, 76, 60, 0.2); padding: 8px; border-radius: 8px; text-align: center; border: 1px solid rgba(231, 76, 60, 0.3);"> <div style="color: #e74c3c; font-weight: bold;">PvP</div> <span id="duel-xp" style="color: #fff;">0 XP</span> </div> <div style="background: rgba(155, 89, 182, 0.2); padding: 8px; border-radius: 8px; text-align: center; border: 1px solid rgba(155, 89, 182, 0.3);"> <div style="color: #9b59b6; font-weight: bold;">Bitwy</div> <span id="battle-xp" style="color: #fff;">0 XP</span> </div> </div> <div id="collection-status"></div> <div id="collection-controls" style="display: none; grid-template-columns: 1fr 1fr; gap: 10px; margin: 10px 0;"> <button id="pause-collection" style=" padding: 10px 15px; background: rgba(241, 196, 15, 0.1); color: #F1C40F; border: 1px solid rgba(241, 196, 15, 0.2); border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 600; letter-spacing: 0.3px; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; gap: 8px; "> <span style="font-size: 16px;">⏸️</span> Wstrzymaj </button> <button id="cancel-collection" style=" padding: 10px 15px; background: rgba(231, 76, 60, 0.1); color: #E74C3C; border: 1px solid rgba(231, 76, 60, 0.2); border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 600; letter-spacing: 0.3px; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; gap: 8px; "> <span style="font-size: 16px;">✖️</span> Anuluj </button> </div> <div id="standard-buttons" style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 15px;"> <button id="update-history" style=" padding: 10px 0px; background: rgba(33, 150, 243, 0.1); color: #2196F3; border: 1px solid rgba(33, 150, 243, 0.2); border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 600; letter-spacing: 0.3px; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; gap: 8px; "> <span style="font-size: 16px;">🔄</span> Aktualizuj dane </button> <button id="collect-history" style=" padding: 10px 0px; background: rgba(130, 224, 170, 0.1); color: #82E0AA; border: 1px solid rgba(130, 224, 170, 0.2); border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 600; letter-spacing: 0.3px; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; gap: 8px; "> <span style="font-size: 16px;">📊</span> Zbierz dane </button> <button id="show-experience-log" style=" padding: 10px 15px; background: rgba(230, 126, 34, 0.1); color: #E67E22; border: 1px solid rgba(230, 126, 34, 0.2); border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 600; letter-spacing: 0.3px; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; gap: 8px; "> <span style="font-size: 16px;">📋</span> Szczegóły </button> <button id="reset-experience" style=" padding: 10px 15px; background: rgba(255, 148, 148, 0.1); color: #FF9494; border: 1px solid rgba(255, 148, 148, 0.2); border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 600; letter-spacing: 0.3px; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; gap: 8px; "> <span style="font-size: 16px;">🗑️</span> Kasuj </button> </div> `; // Modyfikacja efektu hover dla przycisków const buttons = trackerDiv.getElementsByTagName('button'); for (let button of buttons) { button.addEventListener('mouseover', function() { this.style.transform = 'translateY(-1px)'; this.style.boxShadow = '0 4px 12px ' + ( this.id === 'pause-collection' ? 'rgba(241, 196, 15, 0.2)' : this.id === 'cancel-collection' ? 'rgba(231, 76, 60, 0.2)' : 'rgba(255, 255, 255, 0.1)' ); }); button.addEventListener('mouseout', function() { this.style.transform = 'translateY(0)'; this.style.boxShadow = 'none'; }); } document.body.appendChild(trackerDiv); // Dodanie obsługi przycisków document.querySelector('#collect-history').addEventListener('click', () => { if (!isCollectingHistory) { showCategorySelectionPanel('collect'); } }); document.querySelector('#pause-collection').addEventListener('click', function() { isPaused = !isPaused; if (isPaused) { this.innerHTML = ` <span style="font-size: 16px;">▶️</span> Wznów `; this.style.background = 'rgba(46, 204, 113, 0.1)'; this.style.color = '#2ECC71'; this.style.border = '1px solid rgba(46, 204, 113, 0.2)'; } else { this.innerHTML = ` <span style="font-size: 16px;">⏸️</span> Wstrzymaj `; this.style.background = 'rgba(241, 196, 15, 0.1)'; this.style.color = '#F1C40F'; this.style.border = '1px solid rgba(241, 196, 15, 0.2)'; } }); document.querySelector('#cancel-collection').addEventListener('click', function() { shouldCancel = true; }); document.querySelector('#show-experience-log').addEventListener('click', showExperienceDetails); document.querySelector('#reset-experience').addEventListener('click', resetExperience); // Dodaj obsługę przycisku aktualizacji document.querySelector('#update-history').addEventListener('click', () => { if (!isCollectingHistory) { showCategorySelectionPanel('update'); } }); makeDraggable(trackerDiv); // Aktualizuj statystyki przy tworzeniu okna updateXPStats(); // Modyfikacja funkcji addExperience aby aktualizowała statystyki const originalAddExperience = addExperience; addExperience = function(amount, source) { originalAddExperience.call(this, amount, source); updateXPStats(); }; // Dodanie aktualizacji statystyk po zebraniu danych const originalCollectExperienceHistory = collectExperienceHistory; collectExperienceHistory = function(maxPages, folder, callback = null) { originalCollectExperienceHistory.call(this, maxPages, folder, callback); setTimeout(updateXPStats, 1000); // Aktualizuj po zakończeniu zbierania }; } // Funkcja przeciągania elementu function makeDraggable(element) { let isDragging = false; let startX, startY, initialX, initialY; // Znajdujemy element nagłówka (cały górny obszar z doświadczeniem) const headerArea = element.querySelector('div:first-child'); if (headerArea) { headerArea.style.cursor = 'move'; } element.onmousedown = (event) => { let targetElement = event.target; let isInHeader = false; while (targetElement && targetElement !== element) { if (targetElement === headerArea) { isInHeader = true; break; } targetElement = targetElement.parentElement; } if (isInHeader && event.target.tagName !== 'BUTTON') { isDragging = true; startX = event.clientX; startY = event.clientY; initialX = parseInt(window.getComputedStyle(element).left, 10) || 0; initialY = parseInt(window.getComputedStyle(element).top, 10) || 0; document.onmousemove = onMouseMove; document.onmouseup = onMouseUp; } }; function onMouseMove(event) { if (!isDragging) return; const deltaX = event.clientX - startX; const deltaY = event.clientY - startY; element.style.left = `${initialX + deltaX}px`; element.style.top = `${initialY + deltaY}px`; element.style.right = 'auto'; } function onMouseUp() { if (isDragging) { const position = { top: element.style.top, left: element.style.left, right: element.style.right }; localStorage.setItem('experienceTrackerPosition', JSON.stringify(position)); } isDragging = false; document.onmousemove = null; document.onmouseup = null; } } // Funkcja zapisywania pozycji okna function savePosition(element) { const top = element.style.top; const left = element.style.left; const right = element.style.right; localStorage.setItem('experienceTrackerPosition', JSON.stringify({ top, left, right })); alert('Pozycja okna została zapisana!'); } // Funkcja do zapisywania zdobytego doświadczenia function addExperience(amount, source) { const now = new Date(); const day = String(now.getDate()).padStart(2, '0'); const month = String(now.getMonth() + 1).padStart(2, '0'); const year = now.getFullYear(); const formattedDate = `${day}.${month}.${year}`; totalExperience += amount; experienceLog.push({ amount, source, timestamp: formattedDate }); // Aktualizuj lokalne przechowywanie localStorage.setItem('totalExperience', JSON.stringify(totalExperience)); localStorage.setItem('experienceLog', JSON.stringify(experienceLog)); // Zaktualizuj wyświetlaną wartość const totalExperienceElement = document.querySelector('#total-experience'); if (totalExperienceElement) { totalExperienceElement.textContent = totalExperience; } } // Funkcja do pokazywania szczegółów doświadczenia function showExperienceDetails() { const logWindow = window.open('', '_blank'); // Ensure we have a valid window reference if (logWindow === null) { alert('Proszę zezwolić na otwieranie nowych kart w przeglądarce.'); return; } logWindow.document.write(` <!DOCTYPE html> <html> <head> <title>Historia doświadczenia</title> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Roboto', sans-serif; } html { min-width: 1300px; } body { background: #f5f6fa; color: #2d3436; line-height: 1.6; padding: 20px; min-width: 1300px; width: 100%; } .container { min-width: 1300px; width: 100%; margin: 0 auto; padding: 20px; } .controls { background: white; padding: 15px; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; margin-bottom: 15px; } .controls select, .controls input, .controls button { padding: 8px 12px; border: 1px solid #dfe6e9; border-radius: 6px; font-size: 13px; width: 100%; } .controls button { background: #2196F3; color: white; border: none; cursor: pointer; transition: background 0.2s; } .controls button:hover { background: #1976D2; } .chart-container { background: white; padding: 20px; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); height: 400px; margin: 20px 0; } .content-wrapper { display: grid; grid-template-columns: 20% 35% 45%; gap: 20px; margin-top: 20px; width: 100%; height: calc(100vh - 220px); /* Dodajemy stałą wysokość dla wrappera */ } .left-column, .middle-column, .right-column { width: 100%; min-width: 0; height: 100%; /* Ustawiamy pełną wysokość dla kolumn */ } .right-column { display: flex; flex-direction: column; gap: 15px; height: 560px; /* Całkowita wysokość kolumny */ } .right-content { background: white; padding: 20px; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); height: 500px !important; overflow: hidden; min-height: 500px !important; max-height: 500px !important; display: flex; flex-direction: column; } .right-content h2 { margin: 0 0 15px 0; color: #2d3436; font-size: 18px; flex: 0 0 auto; } .entries-list { flex: 1; overflow-y: auto; } .entries-header { display: grid; grid-template-columns: 100px 1fr 100px; gap: 15px; padding: 10px 15px; background: white; font-weight: bold; border-bottom: 2px solid #ddd; position: sticky; top: 0; z-index: 1; flex: 0 0 auto; /* Dodajemy flex aby header nie rozpychał kontenera */ } .entry { display: grid; grid-template-columns: 100px 1fr 100px; gap: 15px; padding: 15px; border-bottom: 1px solid #f1f2f6; transition: all 0.2s ease; align-items: center; } .entry:hover { background: #f8f9fa; } .entry:last-child { border-bottom: none; } .entry .timestamp { color: #636e72; font-size: 14px; white-space: nowrap; } .entry .source { color: #2d3436; font-weight: 500; } .entry .amount { color: #00b894; font-weight: 700; font-size: 16px; } .badge { display: inline-block; padding: 4px 8px; border-radius: 6px; font-size: 12px; font-weight: 500; margin-right: 8px; } .badge-church { background: #81ecec; color: #00cec9; } .badge-work { background: #55efc4; color: #00b894; } .badge-duel-player { background: #ff7675; color: #d63031; } .badge-duel-bandit { background: #fab1a0; color: #e17055; } .badge-battle { background: #9b59b6; color: #8e44ad; } .badge-other { background: #81ecec; color: #00cec9; } .pagination { display: flex; justify-content: center; align-items: center; gap: 10px; padding: 10px; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); height: 45px; flex: 0 0 auto; /* Dodajemy flex aby paginacja nie rozpychała kontenera */ } .pagination button { padding: 8px 12px; border: 1px solid #dfe6e9; background: white; color: #2d3436; border-radius: 8px; cursor: pointer; transition: all 0.3s ease; } .pagination button:hover { border-color: #0984e3; color: #0984e3; } .pagination button.active { background: #0984e3; color: white; border-color: #0984e3; } .pagination .info { color: #636e72; font-size: 14px; } .daily-xp-container { background: white; padding: 20px; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); height: 500px !important; overflow: hidden; min-height: 500px !important; max-height: 500px !important; display: flex; flex-direction: column; } .daily-xp-container h2 { margin: 0 0 15px 0; color: #2d3436; font-size: 18px; flex: 0 0 auto; } .daily-xp-header { display: grid; grid-template-columns: 100px minmax(150px, 1fr) 100px; gap: 15px; padding: 10px 15px; background: white; font-weight: bold; border-bottom: 2px solid #ddd; flex: 0 0 auto; } .daily-xp-header > div:nth-child(3) { text-align: right; padding-right: 30px; } .daily-xp-entries { flex: 1; overflow-y: auto; padding-right: 5px; } .daily-xp-table { width: 100%; border-collapse: collapse; table-layout: fixed; } .daily-xp-table td { padding: 10px 15px; border-bottom: 1px solid #f1f2f6; } .daily-xp-table td:first-child { width: 100px; } .daily-xp-table td:nth-child(2) { width: auto; min-width: 150px; } .daily-xp-table td:last-child { width: 100px; } .daily-xp-entry { transition: all 0.2s ease; } .daily-xp-entry:hover { background: #f8f9fa; } .daily-xp-entry:last-child td { border-bottom: none; } .daily-xp-entry .date { color: #636e72; font-size: 14px; } .daily-xp-entry .xp { color: #2d3436; font-weight: 500; text-align: center; } .daily-xp-entry .difference { text-align: right; font-weight: 700; font-size: 14px; padding-right: 30px; } .daily-xp-entry .difference.positive { color: #27ae60; } .daily-xp-entry .difference.negative { color: #e74c3c; } .daily-xp-entry .difference.neutral { color: #666; } .stat-card { background: white; padding: 20px; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); transition: all 0.3s ease; margin-bottom: 15px; } .stat-card:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(0,0,0,0.1); } .stat-card h3 { color: #636e72; font-size: 14px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 10px; } .stat-card .value { font-size: 24px; font-weight: 700; color: #2d3436; margin-bottom: 5px; } .stat-card .sub-value { font-size: 14px; color: #636e72; } </style> </head> <body> <div class="container"> <div class="controls"> <select id="categoryFilter"> <option value="all">Wszystkie źródła</option> <option value="Prace">Zwykłe prace</option> <option value="Kosciol">Budowa kościoła</option> <option value="PojedynkiGracze">Pojedynki z graczami</option> <option value="PojedynkiBandyci">Pojedynki z bandytami</option> <option value="Bitwy">Bitwy fortowe</option> <option value="Inne">Inne</option> </select> <select id="sortBy"> <option value="date-desc">Od najnowszych</option> <option value="exp-desc">Najwięcej XP</option> <option value="exp-asc">Najmniej XP</option> </select> <input type="text" id="searchInput" placeholder="Szukaj..."> <button onclick="applyFilters()">Filtruj</button> </div> <div class="chart-container"> <canvas id="dailyExpChart"></canvas> </div> <div class="content-wrapper"> <div class="left-column"> <div id="summary" class="stats-grid"></div> </div> <div class="middle-column"> <div class="daily-xp-container"> <h2>Dzienne XP</h2> <div class="daily-xp-header"> <div>Data</div> <div style="text-align: center;">XP</div> <div style="text-align: right;">Różnica</div> </div> <div class="daily-xp-entries"> <table class="daily-xp-table"> <tbody id="daily-xp-tbody"></tbody> </table> </div> </div> </div> <div class="right-column"> <div class="right-content"> <h2>Raporty</h2> <div class="entries-header"> <div>Data</div> <div>Raport</div> <div>XP</div> </div> <div class="entries-list" id="experience-list"></div> </div> <div id="pagination" class="pagination"></div> </div> </div> </div> <script> let currentPage = 1; const itemsPerPage = 25; let filteredData = []; const originalData = ${JSON.stringify(experienceLog)}; function updateSummary() { const summary = { total: 0, categories: { 'Zwykłe prace': { exp: 0, count: 0, max: 0 }, 'Budowa kościoła': { exp: 0, count: 0, max: 0 }, 'Pojedynki z graczami': { exp: 0, count: 0, max: 0 }, 'Pojedynki z bandytami': { exp: 0, count: 0, max: 0 }, 'Bitwy fortowe': { exp: 0, count: 0, max: 0 }, 'Inne': { exp: 0, count: 0, max: 0 } } }; // Funkcja pomocnicza do formatowania daty function formatDate(timestamp) { if (!timestamp) return ''; // Jeśli timestamp zaczyna się od "Godzina:" if (timestamp.startsWith('Godzina:')) { const today = new Date(); const day = String(today.getDate()).padStart(2, '0'); const month = String(today.getMonth() + 1).padStart(2, '0'); const year = today.getFullYear(); return day + '.' + month + '.' + year; } // Jeśli data jest w formacie DD.MM.YYYY, HH:mm:ss if (timestamp.includes('.') && timestamp.includes(':')) { const [datePart] = timestamp.split(','); return datePart.trim(); } // Jeśli data jest w formacie "DD MMM YYYY" const monthMap = { 'sty': '01', 'lut': '02', 'mar': '03', 'kwi': '04', 'maj': '05', 'cze': '06', 'lip': '07', 'sie': '08', 'wrz': '09', 'paź': '10', 'lis': '11', 'gru': '12' }; const parts = timestamp.split(' '); if (parts.length === 3) { const day = String(parseInt(parts[0])).padStart(2, '0'); const monthAbbr = parts[1].toLowerCase(); const year = parts[2]; if (monthMap[monthAbbr]) { return day + '.' + monthMap[monthAbbr] + '.' + year; } } // Dla pozostałych przypadków zwróć oryginalny timestamp return timestamp; } // Analiza dziennego XP const dailyExp = {}; const dailyMaxNpcExp = {}; originalData.forEach(entry => { const formattedDate = formatDate(entry.timestamp); if (!dailyExp[formattedDate]) { dailyExp[formattedDate] = 0; dailyMaxNpcExp[formattedDate] = 0; } dailyExp[formattedDate] += entry.amount; if (entry.source.includes('Pojedynek z bandytą')) { dailyMaxNpcExp[formattedDate] = Math.max(dailyMaxNpcExp[formattedDate], entry.amount); } // Aktualizacja statystyk ogólnych let category = 'Inne'; if (entry.type === 'work') { if (entry.source.includes('rozbudowa Kościół')) { category = 'Budowa kościoła'; } else { category = 'Zwykłe prace'; } } else if (entry.type === 'duel') { if (entry.source.includes('bandytą') || entry.source.includes('bandit')) { category = 'Pojedynki z bandytami'; } else { category = 'Pojedynki z graczami'; } } else if (entry.type === 'battle') { category = 'Bitwy fortowe'; } summary.categories[category].exp += entry.amount; summary.categories[category].count++; summary.categories[category].max = Math.max(summary.categories[category].max, entry.amount); summary.total += entry.amount; }); // Sortowanie dat const sortedDates = Object.keys(dailyExp).sort((a, b) => { const [dayA, monthA, yearA] = a.split(' '); const [dayB, monthB, yearB] = b.split(' '); const monthMap = { 'sty': 1, 'lut': 2, 'mar': 3, 'kwi': 4, 'maj': 5, 'cze': 6, 'lip': 7, 'sie': 8, 'wrz': 9, 'paź': 10, 'lis': 11, 'gru': 12 }; if (yearA !== yearB) return yearB - yearA; if (monthMap[monthA] !== monthMap[monthB]) return monthMap[monthB] - monthMap[monthA]; return dayB - dayA; }); // Aktualizacja HTML const summaryDiv = document.getElementById('summary'); const summaryHtml = [ '<div class="stats-grid">', '<div class="stat-card">', '<h3>Całkowite XP</h3>', '<div class="value">' + summary.total.toLocaleString() + ' XP</div>', '<div class="sub-value">' + originalData.length + ' wpisów</div>', '</div>', Object.entries(summary.categories).map(([category, data]) => data.count > 0 ? [ '<div class="stat-card">', '<h3>' + category + '</h3>', '<div class="value">' + data.exp.toLocaleString() + ' XP</div>', '<div class="sub-value">', 'Ilość: ' + data.count + '<br>', 'Średnio: ' + Math.round(data.exp / data.count).toLocaleString() + ' XP<br>', 'Max: ' + data.max.toLocaleString() + ' XP', '</div>', '</div>' ].join('') : '' ).join(''), '</div>' ].join(''); summaryDiv.innerHTML = summaryHtml; // Aktualizacja tabeli dziennego XP const dailyXpTbody = document.getElementById('daily-xp-tbody'); if (dailyXpTbody) { let htmlContent = ''; htmlContent += sortedDates.map((date, index) => { const prevDayXP = index < sortedDates.length - 1 ? dailyExp[sortedDates[index + 1]] : 0; const difference = dailyExp[date] - prevDayXP; const differenceText = index < sortedDates.length - 1 ? (difference > 0 ? '+' + difference.toLocaleString() : difference.toLocaleString()) : '-'; const differenceClass = difference > 0 ? 'positive' : difference < 0 ? 'negative' : 'neutral'; return '<tr class="daily-xp-entry">' + '<td class="date">' + date + '</td>' + '<td class="xp">' + dailyExp[date].toLocaleString() + '</td>' + '<td class="difference ' + differenceClass + '">' + differenceText + ' XP</td>' + '</tr>'; }).join(''); dailyXpTbody.innerHTML = htmlContent; } // Tworzenie wykresu const ctx = document.getElementById('dailyExpChart'); new Chart(ctx, { type: 'line', data: { labels: sortedDates.slice(0, 30), // Ostatnie 30 dni datasets: [{ label: 'XP dziennie', data: sortedDates.slice(0, 30).map(date => dailyExp[date]), borderColor: '#4CAF50', backgroundColor: 'rgba(76, 175, 80, 0.1)', tension: 0.1, fill: true }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: true, position: 'top' }, tooltip: { callbacks: { label: function(context) { return context.raw.toLocaleString() + ' XP'; } } } }, scales: { y: { beginAtZero: true, ticks: { callback: function(value) { return value.toLocaleString(); } } } } } }); } function applyFilters() { const category = document.getElementById('categoryFilter').value; const sortBy = document.getElementById('sortBy').value; const searchText = document.getElementById('searchInput').value.toLowerCase(); filteredData = originalData.filter(entry => { const matchesCategory = category === 'all' ? true : category === 'Kosciol' ? (entry.type === 'work' && entry.source.includes('rozbudowa Kościół')) : category === 'Prace' ? (entry.type === 'work' && !entry.source.includes('rozbudowa Kościół')) : category === 'PojedynkiGracze' ? (entry.type === 'duel' && !entry.source.includes('bandytą') && !entry.source.includes('bandit')) : category === 'PojedynkiBandyci' ? (entry.type === 'duel' && (entry.source.includes('bandytą') || entry.source.includes('bandit'))) : category === 'Bitwy' ? entry.type === 'battle' : category === 'Inne' ? (entry.type !== 'work' && entry.type !== 'duel' && entry.type !== 'battle') : false; const matchesSearch = searchText === '' || entry.source.toLowerCase().includes(searchText); return matchesCategory && matchesSearch; }); filteredData.sort((a, b) => { switch(sortBy) { case 'date-desc': return new Date(b.timestamp) - new Date(a.timestamp); case 'exp-desc': return b.amount - a.amount; case 'exp-asc': return a.amount - b.amount; default: return 0; } }); currentPage = 1; renderPage(); } function renderPage() { const start = (currentPage - 1) * itemsPerPage; const end = start + itemsPerPage; const pageItems = filteredData.slice(start, end); const listDiv = document.getElementById('experience-list'); listDiv.innerHTML = ''; if (pageItems.length === 0) { listDiv.innerHTML = '<div style="text-align: center; padding: 20px; color: #666;">Brak wpisów spełniających kryteria</div>'; return; } // Funkcja pomocnicza do formatowania daty function formatDate(timestamp) { if (!timestamp) return ''; // Jeśli timestamp zaczyna się od "Godzina:" if (timestamp.startsWith('Godzina:')) { const today = new Date(); const day = String(today.getDate()).padStart(2, '0'); const month = String(today.getMonth() + 1).padStart(2, '0'); const year = today.getFullYear(); return day + '.' + month + '.' + year; } // Jeśli data jest w formacie DD.MM.YYYY, HH:mm:ss if (timestamp.includes('.') && timestamp.includes(':')) { const [datePart] = timestamp.split(','); return datePart.trim(); } // Jeśli data jest w formacie "DD MMM YYYY" const monthMap = { 'sty': '01', 'lut': '02', 'mar': '03', 'kwi': '04', 'maj': '05', 'cze': '06', 'lip': '07', 'sie': '08', 'wrz': '09', 'paź': '10', 'lis': '11', 'gru': '12' }; const parts = timestamp.split(' '); if (parts.length === 3) { const day = String(parseInt(parts[0])).padStart(2, '0'); const monthAbbr = parts[1].toLowerCase(); const year = parts[2]; if (monthMap[monthAbbr]) { return day + '.' + monthMap[monthAbbr] + '.' + year; } } // Dla pozostałych przypadków zwróć oryginalny timestamp return timestamp; } pageItems.forEach(entry => { const entryDiv = document.createElement('div'); entryDiv.className = 'entry'; let badgeClass = ''; let badgeText = ''; if (entry.type === 'work') { if (entry.source.includes('rozbudowa Kościół')) { badgeClass = 'badge-church'; badgeText = 'Kościół'; } else { badgeClass = 'badge-work'; badgeText = 'Praca'; } } else if (entry.type === 'duel') { if (entry.source.includes('bandytą')) { badgeClass = 'badge-duel-bandit'; badgeText = 'Bandyta'; } else { badgeClass = 'badge-duel-player'; badgeText = 'PvP'; } } else if (entry.type === 'battle') { badgeClass = 'badge-battle'; badgeText = 'Bitwa'; } else { badgeClass = 'badge-other'; badgeText = 'Inne'; } entryDiv.innerHTML = [ '<span class="timestamp">' + formatDate(entry.timestamp) + '</span>', '<span class="source">', '<span class="badge ' + badgeClass + '">' + badgeText + '</span>', entry.source, '</span>', '<span class="amount">+' + entry.amount.toLocaleString() + ' XP</span>' ].join(''); listDiv.appendChild(entryDiv); }); updatePagination(); } function updatePagination() { const totalPages = Math.ceil(filteredData.length / itemsPerPage); const paginationDiv = document.getElementById('pagination'); let html = [ '<div class="pagination">', '<span class="info">Strona ' + currentPage + ' z ' + totalPages + ' (' + filteredData.length + ' wpisów)</span>' ]; if (totalPages > 1) { if (currentPage > 1) { html.push('<button onclick="changePage(1)">«</button>'); html.push('<button onclick="changePage(' + (currentPage - 1) + ')">‹</button>'); } for (let i = Math.max(1, currentPage - 2); i <= Math.min(totalPages, currentPage + 2); i++) { html.push('<button onclick="changePage(' + i + ')"' + (i === currentPage ? ' class="active"' : '') + '>' + i + '</button>'); } if (currentPage < totalPages) { html.push('<button onclick="changePage(' + (currentPage + 1) + ')">›</button>'); html.push('<button onclick="changePage(' + totalPages + ')">»</button>'); } } html.push('</div>'); paginationDiv.innerHTML = html.join(''); } function changePage(newPage) { currentPage = newPage; renderPage(); window.scrollTo(0, 0); } // Inicjalizacja filteredData = [...originalData]; updateSummary(); applyFilters(); </script> <script> // Add resize handling window.addEventListener('resize', function() { if (window.outerWidth < 1300) { window.resizeTo(1300, window.outerHeight); } }); </script> </body> </html> `); logWindow.document.close(); } // Funkcja resetująca doświadczenie do 0 function resetExperience() { totalExperience = 0; experienceLog = []; // Czyścimy również stan przerwanej kolekcji clearCollectionState(); // Zapisz zresetowane wartości w localStorage localStorage.setItem('totalExperience', JSON.stringify(totalExperience)); localStorage.setItem('experienceLog', JSON.stringify(experienceLog)); // Zaktualizuj wyświetlaną wartość doświadczenia const totalExperienceElement = document.querySelector('#total-experience'); if (totalExperienceElement) { totalExperienceElement.textContent = totalExperience + ' XP'; } // Zaktualizuj statystyki z prac i pojedynków const workXPElement = document.querySelector('#work-xp'); const duelXPElement = document.querySelector('#duel-xp'); const battleXPElement = document.querySelector('#battle-xp'); if (workXPElement) workXPElement.textContent = '0 XP'; if (duelXPElement) duelXPElement.textContent = '0 XP'; if (battleXPElement) battleXPElement.textContent = '0 XP'; } // Nasłuchiwanie na zmiany w doświadczeniu (np. praca, bitwa) const originalSetExperience = window.Character.setExperience; window.Character.setExperience = function(newExperience) { const currentExperience = this.experience; const gainedExperience = newExperience - currentExperience; if (gainedExperience > 0) { addExperience(gainedExperience, 'Aktywność w grze'); // Domyślna aktywność } originalSetExperience.call(this, newExperience); }; // Dodajemy nową funkcję do tworzenia panelu wyboru function showCategorySelectionPanel(action) { const existingPanel = document.querySelector('#category-selection-panel'); if (existingPanel) existingPanel.remove(); const experienceTracker = document.querySelector('#experience-tracker'); if (!experienceTracker) return; const panel = document.createElement('div'); panel.id = 'category-selection-panel'; panel.style.cssText = ` color: white; width: 100%; box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2); margin-top: 15px; `; const title = action === 'collect' ? 'Zbieranie danych' : 'Aktualizacja danych'; // Dodajemy informację o przerwanej kolekcji const hasInterruptedCollection = collectionState.inProgress && collectionState.tempLog.length > 0; panel.innerHTML = ` <div style="margin-bottom: 15px; text-align: center;"> <span style="font-size: 18px; color: #F1C40F; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 4px; display: block;"> ${title} </span> </div> <div style="display: grid; gap: 12px;"> ${hasInterruptedCollection ? ` <div style="margin-bottom: 15px; padding: 15px; background: rgba(241, 196, 15, 0.1); border: 1px solid rgba(241, 196, 15, 0.2); border-radius: 8px;"> <div style="margin-bottom: 8px; color: #F1C40F;">Wykryto przerwaną kolekcję:</div> <div style="color: #DDD; font-size: 13px; line-height: 1.5;"> Kategoria: ${getCategoryName(collectionState.folder)}<br> Postęp: ${collectionState.currentPage}/${collectionState.totalPages} stron<br> Zebrane wpisy: ${collectionState.tempLog.length} </div> </div> <button id="resume-collection" style=" padding: 10px 15px; background: rgba(130, 224, 170, 0.1); color: #82E0AA; border: 1px solid rgba(130, 224, 170, 0.2); border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 600; letter-spacing: 0.3px; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; gap: 8px; "> <span style="font-size: 16px;">▶️</span> Wznów przerwaną kolekcję </button> <button id="clear-interrupted" style=" padding: 10px 15px; background: rgba(255, 148, 148, 0.1); color: #FF9494; border: 1px solid rgba(255, 148, 148, 0.2); border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 600; letter-spacing: 0.3px; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; gap: 8px; "> <span style="font-size: 16px;">🗑️</span> Wyczyść przerwaną kolekcję </button> ` : ` <label style=" display: flex; align-items: center; padding: 12px 15px; background: rgba(241, 196, 15, 0.1); border: 1px solid rgba(241, 196, 15, 0.2); border-radius: 8px; cursor: pointer; transition: all 0.3s ease; gap: 10px; "> <input type="checkbox" data-folder="all" class="category-checkbox" style=" width: 16px; height: 16px; cursor: pointer; "> <span style="color: #F1C40F; font-weight: 600;">WSZYSTKIE RAPORTY</span> </label> <label style=" display: flex; align-items: center; padding: 12px 15px; background: rgba(46, 204, 113, 0.1); border: 1px solid rgba(46, 204, 113, 0.2); border-radius: 8px; cursor: pointer; transition: all 0.3s ease; gap: 10px; "> <input type="checkbox" data-folder="job" class="category-checkbox" style=" width: 16px; height: 16px; cursor: pointer; "> <span style="color: #2ecc71; font-weight: 600;">PRACE</span> </label> <label style=" display: flex; align-items: center; padding: 12px 15px; background: rgba(231, 76, 60, 0.1); border: 1px solid rgba(231, 76, 60, 0.2); border-radius: 8px; cursor: pointer; transition: all 0.3s ease; gap: 10px; "> <input type="checkbox" data-folder="duel" class="category-checkbox" style=" width: 16px; height: 16px; cursor: pointer; "> <span style="color: #e74c3c; font-weight: 600;">POJEDYNKI</span> </label> <label style=" display: flex; align-items: center; padding: 12px 15px; background: rgba(155, 89, 182, 0.1); border: 1px solid rgba(155, 89, 182, 0.2); border-radius: 8px; cursor: pointer; transition: all 0.3s ease; gap: 10px; "> <input type="checkbox" data-folder="fortbattle" class="category-checkbox" style=" width: 16px; height: 16px; cursor: pointer; "> <span style="color: #9b59b6; font-weight: 600;">BITWY FORTOWE</span> </label> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 5px;"> <button id="start-collection" style=" padding: 10px 15px; background: rgba(130, 224, 170, 0.1); color: #82E0AA; border: 1px solid rgba(130, 224, 170, 0.2); border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 600; letter-spacing: 0.3px; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; gap: 8px; "> <span style="font-size: 16px;">▶️</span> Start </button> <button id="cancel-selection" style=" padding: 10px 15px; background: rgba(255, 148, 148, 0.1); color: #FF9494; border: 1px solid rgba(255, 148, 148, 0.2); border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 600; letter-spacing: 0.3px; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; gap: 8px; "> <span style="font-size: 16px;">✖️</span> Anuluj </button> </div> `} </div> `; // Dodaj panel do głównego interfejsu experienceTracker.appendChild(panel); // Dodaj obsługę zdarzeń const buttons = panel.getElementsByTagName('button'); for (let button of buttons) { button.addEventListener('click', function() { if (this.id === 'resume-collection') { panel.remove(); collectExperienceHistory(null, collectionState.folder); } else if (this.id === 'clear-interrupted') { clearCollectionState(); panel.remove(); } else if (this.id === 'start-collection') { const selectedFolders = Array.from(panel.querySelectorAll('.category-checkbox:checked')) .map(cb => cb.dataset.folder); if (selectedFolders.length === 0) { showError('Wybierz przynajmniej jedną kategorię!'); return; } panel.remove(); if (selectedFolders.includes('all')) { if (action === 'collect') { collectExperienceHistory(null, 'all'); } else { updateExperienceHistory('all'); } } else { processCategories(selectedFolders, action); } } else if (this.id === 'cancel-selection') { panel.remove(); } }); } // Obsługa checkboxów const allCheckbox = panel.querySelector('input[data-folder="all"]'); if (allCheckbox) { allCheckbox.addEventListener('change', function() { const checkboxes = panel.querySelectorAll('.category-checkbox:not([data-folder="all"])'); checkboxes.forEach(checkbox => { checkbox.checked = false; checkbox.disabled = this.checked; }); }); } const otherCheckboxes = panel.querySelectorAll('.category-checkbox:not([data-folder="all"])'); otherCheckboxes.forEach(checkbox => { checkbox.addEventListener('change', function() { if (this.checked && allCheckbox) { allCheckbox.checked = false; } }); }); } // Dodaj funkcję do przetwarzania kategorii async function processCategories(folders, action) { for (let i = 0; i < folders.length; i++) { const folder = folders[i]; showError(`Rozpoczynam zbieranie danych dla ${getCategoryName(folder)}`); await new Promise((resolve) => { if (action === 'collect') { collectExperienceHistory(null, folder, () => { showError(`Zakończono przetwarzanie ${getCategoryName(folder)}`); setTimeout(resolve, 2000); }); } else { updateExperienceHistory(folder, () => { showError(`Zakończono przetwarzanie ${getCategoryName(folder)}`); setTimeout(resolve, 2000); }); } }); if (shouldCancel) { showError('Anulowano zbieranie pozostałych danych.'); break; } } if (!shouldCancel) { showError('Zakończono przetwarzanie wszystkich wybranych kategorii!'); } } // Modyfikacja funkcji updateExperienceHistory function updateExperienceHistory(folder = 'all', callback = null) { if (isCollectingHistory) { showError('Aktualizacja danych jest już w trakcie. Poczekaj na zakończenie.'); if (callback) callback(); return; } // Ukryj główne elementy przed rozpoczęciem aktualizacji toggleMainElements(false); showError(`Rozpoczynam aktualizację danych dla ${getCategoryName(folder)}`); isCollectingHistory = true; isPaused = false; shouldCancel = false; tempExpLog = []; let processedPages = 0; let failedAttempts = 0; const MAX_RETRIES = 3; const MIN_DELAY = 2000; const MAX_DELAY = 2200; const RETRY_DELAY = 3000; // Pokaż przyciski kontrolne i ukryj standardowe const controlButtons = document.querySelector('#collection-controls'); const standardButtons = document.querySelector('#standard-buttons'); if (controlButtons) { controlButtons.style.display = 'grid'; } if (standardButtons) { standardButtons.style.display = 'none'; } // Tworzymy zbiór istniejących raportów (unikalny po source+timestamp) const existingKeys = new Set(experienceLog.map(e => `${e.source}|${e.timestamp}`)); let foundDuplicate = false; function getRandomDelay() { return Math.floor(Math.random() * (MAX_DELAY - MIN_DELAY + 1)) + MIN_DELAY; } Ajax.remoteCall('reports', 'get_reports', { page: 1, folder: folder }, function(initialData) { if (!initialData || initialData.error) { isCollectingHistory = false; showError('Nie udało się pobrać informacji o raportach. Spróbuj ponownie.'); return; } const totalPages = initialData.count; collectionState.totalPages = totalPages; function processPage(page) { if (!isCollectingHistory || shouldCancel || page > totalPages || foundDuplicate) { finishUpdate(shouldCancel); return; } if (isPaused) { setTimeout(() => processPage(page), 500); return; } Ajax.remoteCall('reports', 'get_reports', { page: page, folder: folder }, function(data) { if (!isCollectingHistory || shouldCancel || foundDuplicate) { finishUpdate(shouldCancel); return; } if (data && !data.error && data.reports && data.reports.length > 0) { failedAttempts = 0; for (const report of data.reports) { const expMatch = report.popupData.match(/experience.png[^>]*>(?:[^<]*<\/[^>]+>)*[^<]*<td>(\d+)<\/td>/) || report.popupData.match(/Doświadczenie<\/span>\s*<span[^>]*>(\d+)\s*punktów/); const exp = expMatch ? parseInt(expMatch[1]) : 0; if (exp > 0) { const key = `${report.title}|${report.date_received}`; if (existingKeys.has(key)) { foundDuplicate = true; break; } const reportType = report.title.includes('Raport dot. pracy') ? 'work' : report.title.includes('Pojedynek') ? 'duel' : (report.title.includes('Bitwa') || report.title.includes('Fort')) ? 'battle' : 'other'; // Dodajemy sprawdzanie zwycięzcy dla pojedynków let shouldCount = true; if (reportType === 'duel') { if (report.title.includes('bandytą') || report.title.includes('bandit')) { shouldCount = true; } else { // Dla pojedynków z graczami sprawdzamy zwycięzcę const winnerMatch = report.popupData.match(/Zwycięzca:\s*([^<]+)/) || report.popupData.match(/(\w+)\s+wygrywa pojedynek!/); if (winnerMatch) { const winner = winnerMatch[1].trim(); shouldCount = winner === playerName; } else { shouldCount = false; } } } if (shouldCount) { tempExpLog.push({ amount: exp, source: report.title, timestamp: report.date_received, page: page, type: reportType }); // Aktualizuj zapisany stan collectionState.tempLog = tempExpLog; saveCollectionState(collectionState); } } } processedPages++; updateCollectionStatus(processedPages, totalPages, tempExpLog.length); if (!foundDuplicate && !shouldCancel) { setTimeout(() => processPage(page + 1), getRandomDelay()); } else { finishUpdate(shouldCancel); } } else { failedAttempts++; if (failedAttempts < MAX_RETRIES && !shouldCancel) { setTimeout(() => processPage(page), RETRY_DELAY); } else { showError(`Nie udało się przetworzyć strony ${page} po ${MAX_RETRIES} próbach`); finishUpdate(shouldCancel); } } }); } processPage(1); }); function finishUpdate(wasCancelled = false) { isCollectingHistory = false; isPaused = false; // Pokaż z powrotem główne elementy toggleMainElements(true); // Przywróć standardowe przyciski i ukryj kontrolne const controlButtons = document.querySelector('#collection-controls'); const standardButtons = document.querySelector('#standard-buttons'); if (controlButtons) { controlButtons.style.display = 'none'; } if (standardButtons) { standardButtons.style.display = 'grid'; } if (wasCancelled) { showError('Anulowano aktualizację danych.'); if (callback) callback(); return; } if (tempExpLog.length === 0) { showError(`Brak nowych wpisów w kategorii ${getCategoryName(folder)}.`); if (callback) callback(); return; } // Dodaj nowe wpisy na początek loga (bo są najnowsze) experienceLog = tempExpLog.concat(experienceLog); totalExperience = experienceLog.reduce((sum, entry) => sum + entry.amount, 0); localStorage.setItem('experienceLog', JSON.stringify(experienceLog)); localStorage.setItem('totalExperience', JSON.stringify(totalExperience)); updateDisplay(); updateXPStats(); showError(`Zaktualizowano ${getCategoryName(folder)}! Dodano ${tempExpLog.length} nowych wpisów.`); if (callback) { callback(); } } } // Funkcja do sprawdzania ilości stron dla różnych typów raportów function checkReportPages() { const types = [ { name: 'Wszystkie raporty', params: { folder: 'all' } }, { name: 'Raporty z pracy', params: { folder: 'job' } }, { name: 'Raporty z pojedynków', params: { folder: 'duel' } }, { name: 'Raporty z bitew fortowych', params: { folder: 'fortbattle' } } ]; let results = {}; let completed = 0; showError('Rozpoczynam sprawdzanie raportów...'); types.forEach(type => { Ajax.remoteCall('reports', 'get_reports', { page: 1, ...type.params }, function(response) { completed++; if (response && !response.error) { results[type.name] = { totalPages: response.pages || response.count || 0, reportsPerPage: response.reports ? response.reports.length : 0, totalReports: response.total || (response.reports ? response.reports.length * (response.pages || response.count || 0) : 0) }; } else { results[type.name] = { error: response ? response.error : 'Brak odpowiedzi' }; } if (completed === types.length) { let resultMessage = 'Statystyki raportów:\n\n'; Object.entries(results).forEach(([name, data]) => { resultMessage += `${name}:\n`; if (data.error) { resultMessage += ` Błąd: ${data.error}\n`; } else { resultMessage += ` Liczba stron: ${data.totalPages}\n`; resultMessage += ` Raportów na stronę: ${data.reportsPerPage}\n`; resultMessage += ` Łączna liczba raportów: ${data.totalReports}\n`; if (data.reportsPerPage > 0) { resultMessage += ` Szacowana łączna liczba raportów: ${data.totalPages * data.reportsPerPage}\n`; } resultMessage += '------------------------\n'; } }); showError(resultMessage, 10000); // Pokazuj przez 10 sekund } }); }); return "Sprawdzanie ilości stron... Wyniki pojawią się w oknie statusu."; } // Dodaj do window aby było dostępne w konsoli window.checkReportPages = checkReportPages; // Dodanie okna śledzenia doświadczenia setInterval(() => { addExperienceWindow(); createVisibilityToggle(); }, 1000); // Funkcja do przełączania widoczności function toggleTrackerVisibility() { const tracker = document.querySelector('#experience-tracker'); const toggleCheckbox = document.querySelector('#toggle-tracker-visibility'); if (tracker && toggleCheckbox) { isTrackerVisible = toggleCheckbox.checked; tracker.style.display = isTrackerVisible ? 'block' : 'none'; localStorage.setItem('experienceTrackerVisible', JSON.stringify(isTrackerVisible)); } } // Funkcja do tworzenia checkboxa widoczności function createVisibilityToggle() { const existingToggle = document.querySelector('#tracker-visibility-toggle'); if (existingToggle) return; const toggleContainer = document.createElement('div'); toggleContainer.id = 'tracker-visibility-toggle'; // Pozycjonowanie względem ui_topbar const uiTopbar = document.querySelector('#ui_topbar'); const topbarRect = uiTopbar ? uiTopbar.getBoundingClientRect() : null; toggleContainer.style.cssText = ` position: fixed; top: ${topbarRect ? topbarRect.top + 'px' : '10px'}; right: ${topbarRect ? (window.innerWidth - topbarRect.left) + 'px' : '310px'}; background: rgba(20, 20, 20, 0.95); padding: 5px 10px; border-radius: 4px; border: 1px solid #444; z-index: 1000; color: white; font-family: Arial, sans-serif; font-size: 12px; display: flex; align-items: center; gap: 5px; cursor: pointer; `; toggleContainer.innerHTML = ` <input type="checkbox" id="toggle-tracker-visibility" ${isTrackerVisible ? 'checked' : ''}> `; document.body.appendChild(toggleContainer); // Aktualizacja pozycji przy zmianie rozmiaru okna window.addEventListener('resize', () => { const uiTopbar = document.querySelector('#ui_topbar'); const topbarRect = uiTopbar ? uiTopbar.getBoundingClientRect() : null; if (topbarRect && toggleContainer) { toggleContainer.style.top = topbarRect.top + 'px'; toggleContainer.style.right = (window.innerWidth - topbarRect.left) + 'px'; } }); const checkbox = toggleContainer.querySelector('#toggle-tracker-visibility'); checkbox.addEventListener('change', toggleTrackerVisibility); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址