// ==UserScript==
// @name The West - Experience Record
// @namespace http://tampermonkey.net/
// @version 3.1
// @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 = [];
// Funkcja do aktualizacji statusu kolekcji
function updateCollectionStatus(processed, total, foundEntries) {
const statusElement = document.querySelector('#collection-status');
if (statusElement) {
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 ? `<br>Szacowany pozostały czas: ${timeEstimate}` : '';
statusElement.innerHTML = `
<div style="background: rgba(0,0,0,0.7); padding: 10px; border-radius: 5px;">
<strong>Status pobierania:</strong><br>
Postęp: ${processed}/${total} stron (${percent}%)<br>
Znalezione wpisy z XP: ${foundEntries}<br>
- Prace: ${workEntries} (${tempExpLog.filter(e => e.type === 'work').reduce((sum, e) => sum + e.amount, 0)} XP)<br>
- Pojedynki: ${duelEntries} (${tempExpLog.filter(e => e.type === 'duel').reduce((sum, e) => sum + e.amount, 0)} XP)<br>
- Bitwy fortowe: ${battleEntries} (${tempExpLog.filter(e => e.type === 'battle').reduce((sum, e) => sum + e.amount, 0)} XP)${timeInfo}
<div class="progress-bar" style="
background: #444;
height: 20px;
border-radius: 10px;
margin-top: 10px;
overflow: hidden;
">
<div style="
width: ${percent}%;
background: #4CAF50;
height: 100%;
transition: width 0.3s ease;
"></div>
</div>
</div>
`;
}
}
// 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() {
console.log('Debugowanie systemu raportów...');
// 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) {
console.log('Test query ' + (index + 1) + ':', params);
console.log('Response:', response);
if (response) {
console.log('Total reports:', response.count);
console.log('Reports per page:', response.reports ? response.reports.length : 0);
console.log('Response structure:', Object.keys(response));
if (response.reports && response.reports.length > 0) {
console.log('Sample report structure:', Object.keys(response.reports[0]));
}
}
});
}, index * 1000);
}
testQueries.forEach(makeTestQuery);
}
function formatTimeRemaining(seconds) {
if (!seconds || isNaN(seconds)) return "obliczanie...";
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;
processedPagesTime.push(timeElapsed / processedPages);
// Oblicz średni czas na stronę z ostatnich 5 stron
const recentTimes = processedPagesTime.slice(-5);
if (recentTimes.length === 0) return null;
const avgTimePerPage = recentTimes.reduce((a, b) => a + b, 0) / recentTimes.length;
if (isNaN(avgTimePerPage)) return null;
// Oblicz pozostały czas
const remainingPages = totalPages - processedPages;
const estimatedTimeRemaining = remainingPages * avgTimePerPage / 1000;
return formatTimeRemaining(estimatedTimeRemaining);
}
// Funkcja do zbierania historii doświadczenia
function collectExperienceHistory(maxPages = null) {
if (isCollectingHistory) {
alert('Pobieranie historii jest już w trakcie. Poczekaj na zakończenie.');
return;
}
isCollectingHistory = true;
isPaused = false;
shouldCancel = false;
collectionStartTime = Date.now();
processedPagesTime = [];
tempExpLog = [];
let processedPages = 0;
let failedAttempts = 0;
const MAX_RETRIES = 3;
// 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 = 1000;
const MAX_DELAY = 1200;
const RETRY_DELAY = 2000;
function getRandomDelay() {
return Math.floor(Math.random() * (MAX_DELAY - MIN_DELAY + 1)) + MIN_DELAY;
}
function finishCollection(wasSuccessful = true) {
isCollectingHistory = false;
isPaused = false;
shouldCancel = false;
// 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) {
showError('Anulowano zbieranie historii.');
return;
}
if (tempExpLog.length === 0) {
showError('Nie znaleziono żadnych wpisów z doświadczeniem.');
return;
}
experienceLog = tempExpLog;
totalExperience = tempExpLog.reduce((sum, entry) => sum + entry.amount, 0);
localStorage.setItem('experienceLog', JSON.stringify(experienceLog));
localStorage.setItem('totalExperience', JSON.stringify(totalExperience));
updateDisplay();
updateXPStats();
showError('Zakończono pobieranie! Znaleziono ' + tempExpLog.length + ' wpisów.');
}
// Pobierz wszystkie raporty
Ajax.remoteCall('reports', 'get_reports', {
page: 1,
folder: 'all'
}, function(initialData) {
console.log('Pobieranie wszystkich raportów...');
processedPages = 0;
if (!initialData || initialData.error) {
console.error('Błąd pobierania danych:', initialData);
isCollectingHistory = false;
alert('Nie udało się pobrać informacji o raportach. Spróbuj ponownie.');
return;
}
const totalPages = initialData.count;
const pagesToProcess = maxPages ? Math.min(maxPages, totalPages) : totalPages;
console.log(`Znaleziono ${totalPages} stron raportów`);
function processPage(page) {
if (shouldCancel) {
finishCollection(false);
return;
}
if (!isCollectingHistory || page > pagesToProcess) {
console.log('Zakończono przetwarzanie raportów');
finishCollection(true);
return;
}
if (isPaused) {
setTimeout(() => processPage(page), 500);
return;
}
console.log(`Pobieram stronę ${page}...`);
Ajax.remoteCall('reports', 'get_reports', {
page: page,
folder: 'all'
}, function(data) {
if (!isCollectingHistory || shouldCancel) return;
if (data && !data.error && data.reports && data.reports.length > 0) {
failedAttempts = 0;
console.log(`Znaleziono ${data.reports.length} raportów na stronie ${page}`);
data.reports.forEach(report => {
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 reportType = report.title.includes('Raport dot. pracy') ? 'work' :
report.title.includes('Pojedynek') ? 'duel' :
(report.title.includes('Bitwa') || report.title.includes('Fort')) ? 'battle' : 'other';
console.log(`Znaleziono XP w raporcie:`, {
title: report.title,
exp: exp,
type: reportType
});
tempExpLog.push({
amount: exp,
source: report.title,
timestamp: report.date_received,
page: page,
type: reportType
});
}
});
processedPages++;
updateCollectionStatus(processedPages, pagesToProcess, tempExpLog.length);
setTimeout(() => processPage(page + 1), getRandomDelay());
} else {
console.error(`Błąd pobierania strony ${page}:`, data);
failedAttempts++;
if (failedAttempts < MAX_RETRIES) {
setTimeout(() => processPage(page), RETRY_DELAY);
} else {
console.error(`Nie udało się przetworzyć strony ${page} po ${MAX_RETRIES} próbach`);
finishCollection(true);
}
}
});
}
processPage(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) return;
const trackerDiv = document.createElement('div');
trackerDiv.id = 'experience-tracker';
trackerDiv.style.position = 'fixed';
// Wczytaj zapisaną pozycję
const savedPosition = JSON.parse(localStorage.getItem('experienceTrackerPosition')) || {
top: '50px',
left: 'auto',
right: '310px'
};
// Zastosuj zapisaną pozycję
trackerDiv.style.top = savedPosition.top;
trackerDiv.style.left = savedPosition.left;
trackerDiv.style.right = savedPosition.right;
trackerDiv.style.width = '280px';
trackerDiv.style.padding = '15px';
trackerDiv.style.background = 'rgba(20, 20, 20, 0.95)';
trackerDiv.style.color = '#fff';
trackerDiv.style.border = '1px solid #444';
trackerDiv.style.borderRadius = '8px';
trackerDiv.style.zIndex = '1000';
trackerDiv.style.cursor = 'move';
trackerDiv.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.3)';
trackerDiv.style.fontFamily = 'Arial, sans-serif';
trackerDiv.innerHTML = `
<div style="margin-bottom: 15px; border-bottom: 1px solid #444; padding-bottom: 10px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h3 style="margin: 0; font-size: 16px; color: #FFD700;">Doświadczenie</h3>
<span id="total-experience" style="font-size: 20px; font-weight: bold; color: #4CAF50;">${totalExperience} XP</span>
</div>
<div style="display: flex; flex-direction: column; gap: 5px; margin-top: 8px; font-size: 13px;">
<div style="display: flex; justify-content: space-between; padding: 3px 0;">
<span style="color: #BBB;">Z prac:</span>
<span id="work-xp" style="color: #3498db;">0 XP</span>
</div>
<div style="display: flex; justify-content: space-between; padding: 3px 0;">
<span style="color: #BBB;">Z pojedynków:</span>
<span id="duel-xp" style="color: #e74c3c;">0 XP</span>
</div>
<div style="display: flex; justify-content: space-between; padding: 3px 0;">
<span style="color: #BBB;">Z bitew fortowych:</span>
<span id="battle-xp" style="color: #9b59b6;">0 XP</span>
</div>
</div>
</div>
<div id="collection-status" style="margin: 10px 0; font-size: 13px; color: #BBB;"></div>
<div id="collection-controls" style="display: none; grid-template-columns: 1fr 1fr; gap: 8px; margin: 10px 0;">
<button id="pause-collection" style="
padding: 8px;
background: #F39C12;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: background 0.2s;
">Wstrzymaj</button>
<button id="cancel-collection" style="
padding: 8px;
background: #E74C3C;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: background 0.2s;
">Anuluj</button>
</div>
<div id="standard-buttons" style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 15px;">
<button id="show-experience-log" style="
padding: 8px;
background: #2C3E50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: background 0.2s;
">Szczegóły</button>
<button id="collect-history" style="
padding: 8px;
background: #27AE60;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: background 0.2s;
">Zbierz historię</button>
<button id="update-history" style="
padding: 8px;
background: #2196F3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: background 0.2s;
">Aktualizuj dane</button>
<button id="reset-experience" style="
padding: 8px;
background: #C0392B;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: background 0.2s;
">Reset</button>
</div>
`;
// Dodanie hover efektów dla przycisków
const buttons = trackerDiv.getElementsByTagName('button');
for (let button of buttons) {
button.addEventListener('mouseover', function() {
const currentBg = this.style.background;
this.style.background = currentBg.replace(')', ' brightness(120%)');
});
button.addEventListener('mouseout', function() {
const currentBg = this.style.background;
this.style.background = currentBg.replace(' brightness(120%)', '');
});
}
document.body.appendChild(trackerDiv);
// Dodanie obsługi przycisków
document.querySelector('#collect-history').addEventListener('click', () => {
if (!isCollectingHistory) {
collectExperienceHistory();
}
});
document.querySelector('#pause-collection').addEventListener('click', function() {
isPaused = !isPaused;
this.textContent = isPaused ? 'Wznów' : 'Wstrzymaj';
this.style.background = isPaused ? '#27AE60' : '#F39C12';
});
document.querySelector('#cancel-collection').addEventListener('click', function() {
if (confirm('Czy na pewno chcesz anulować zbieranie historii?')) {
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) {
updateExperienceHistory();
}
});
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 historii
const originalCollectExperienceHistory = collectExperienceHistory;
collectExperienceHistory = function(maxPages) {
originalCollectExperienceHistory.call(this, maxPages);
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'; // Zmiana kursora na "move" dla całego obszaru nagłówka
}
element.onmousedown = (event) => {
// Sprawdzamy, czy kliknięcie było w obszarze nagłówka lub jego potomków
let targetElement = event.target;
let isInHeader = false;
// Sprawdzamy, czy kliknięty element jest w obszarze nagłówka
while (targetElement && targetElement !== element) {
if (targetElement === headerArea) {
isInHeader = true;
break;
}
targetElement = targetElement.parentElement;
}
// Jeśli kliknięcie było w obszarze nagłówka i nie w przycisk
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'; // Deaktywuje "right" gdy "left" jest ustawione
}
function onMouseUp() {
if (isDragging) {
// Zapisz pozycję w localStorage
const position = {
top: element.style.top,
left: element.style.left,
right: 'auto'
};
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) {
totalExperience += amount;
experienceLog.push({ amount, source, timestamp: new Date().toLocaleString() });
// 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('', 'Experience Log', 'width=800,height=800');
logWindow.document.write(`
<!DOCTYPE html>
<html>
<head>
<title>Historia doświadczenia</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Roboto', sans-serif;
}
body {
background: #f5f6fa;
color: #2d3436;
line-height: 1.6;
padding: 30px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
margin-bottom: 30px;
color: #2d3436;
}
.header h1 {
font-size: 28px;
font-weight: 500;
}
.controls {
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
margin-bottom: 30px;
display: flex;
gap: 15px;
flex-wrap: wrap;
align-items: center;
}
.controls select, .controls input {
padding: 10px 15px;
border: 1px solid #dfe6e9;
border-radius: 8px;
font-size: 14px;
color: #2d3436;
background: white;
min-width: 160px;
outline: none;
transition: all 0.3s ease;
}
.controls select:hover, .controls input:hover {
border-color: #74b9ff;
}
.controls select:focus, .controls input:focus {
border-color: #0984e3;
box-shadow: 0 0 0 3px rgba(9,132,227,0.1);
}
.controls button {
padding: 10px 20px;
background: #0984e3;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 500;
transition: all 0.3s ease;
}
.controls button:hover {
background: #0652DD;
transform: translateY(-1px);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.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;
}
.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;
}
.entries-list {
background: white;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
overflow: hidden;
}
.entry {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 20px;
padding: 15px 20px;
align-items: center;
border-bottom: 1px solid #f1f2f6;
transition: all 0.2s ease;
}
.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-work {
background: #55efc4;
color: #00b894;
}
.badge-duel {
background: #ff7675;
color: #d63031;
}
.badge-other {
background: #81ecec;
color: #00cec9;
}
.badge-battle {
background: #9b59b6;
color: #8e44ad;
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
margin-top: 30px;
}
.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;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: #636e72;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.entry {
animation: fadeIn 0.3s ease-out forwards;
}
#error-message {
position: fixed;
top: 20px;
right: 20px;
background: #d63031;
color: white;
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
display: none;
animation: fadeIn 0.3s ease-out;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Historia doświadczenia</h1>
</div>
<div class="controls">
<select id="categoryFilter">
<option value="all">Wszystkie źródła</option>
<option value="Prace">Tylko prace</option>
<option value="Pojedynki">Tylko pojedynki</option>
<option value="Bitwy">Tylko bitwy fortowe</option>
<option value="Inne">Inne</option>
</select>
<select id="sortBy">
<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 id="error-message"></div>
<div id="summary" class="stats-grid"></div>
<div id="experience-list" class="entries-list"></div>
<div id="pagination" class="pagination"></div>
</div>
<script>
let currentPage = 1;
const itemsPerPage = 25;
let filteredData = [];
const originalData = ${JSON.stringify(experienceLog)};
function showError(message) {
const errorDiv = document.getElementById('error-message');
errorDiv.textContent = message;
errorDiv.style.display = 'block';
setTimeout(() => {
errorDiv.style.display = 'none';
}, 5000);
}
function updateSummary() {
try {
const summary = {
total: 0,
categories: {
'Prace': { exp: 0, count: 0 },
'Pojedynki': { exp: 0, count: 0 },
'Bitwy fortowe': { exp: 0, count: 0 },
'Inne': { exp: 0, count: 0 }
}
};
originalData.forEach(entry => {
if (!entry || !entry.amount) return;
summary.total += entry.amount;
if (entry.type === 'work') {
summary.categories['Prace'].exp += entry.amount;
summary.categories['Prace'].count++;
} else if (entry.type === 'duel') {
summary.categories['Pojedynki'].exp += entry.amount;
summary.categories['Pojedynki'].count++;
} else if (entry.type === 'battle') {
summary.categories['Bitwy fortowe'].exp += entry.amount;
summary.categories['Bitwy fortowe'].count++;
} else {
summary.categories['Inne'].exp += entry.amount;
summary.categories['Inne'].count++;
}
});
const summaryDiv = document.getElementById('summary');
let summaryHTML = '<div class="stat-card">' +
'<h3>Łącznie</h3>' +
'<div class="value">' + summary.total.toLocaleString() + ' XP</div>' +
'<div class="sub-value">' + originalData.length + ' wpisów</div>' +
'</div>';
Object.entries(summary.categories).forEach(([category, data]) => {
if (data.count > 0) { // Pokazuj tylko kategorie, które mają wpisy
summaryHTML += '<div class="stat-card">' +
'<h3>' + category + '</h3>' +
'<div class="value">' + data.exp.toLocaleString() + ' XP</div>' +
'<div class="sub-value">' + data.count + ' wpisów</div>' +
'</div>';
}
});
summaryDiv.innerHTML = summaryHTML;
} catch (error) {
showError('Błąd podczas generowania podsumowania: ' + error.message);
}
}
function applyFilters() {
try {
const category = document.getElementById('categoryFilter').value;
const sortBy = document.getElementById('sortBy').value;
const searchText = document.getElementById('searchInput').value.toLowerCase();
filteredData = originalData.filter(entry => {
if (!entry || !entry.source) return false;
const matchesCategory =
category === 'all' ? true :
category === 'Prace' ? entry.type === 'work' :
category === 'Pojedynki' ? entry.type === 'duel' :
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 'exp-desc':
return b.amount - a.amount;
case 'exp-asc':
return a.amount - b.amount;
default:
return 0;
}
});
currentPage = 1;
renderPage();
} catch (error) {
showError('Błąd podczas filtrowania: ' + error.message);
}
}
function renderPage() {
try {
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 class="empty-state">Brak wpisów spełniających kryteria</div>';
} else {
pageItems.forEach(entry => {
const typeBadge = entry.type === 'work' ? '<span class="badge badge-work">Praca</span>' :
entry.type === 'duel' ? '<span class="badge badge-duel">Pojedynek</span>' :
entry.type === 'battle' ? '<span class="badge badge-battle">Bitwa</span>' :
'<span class="badge badge-other">Inne</span>';
const entryDiv = document.createElement('div');
entryDiv.className = 'entry';
entryDiv.innerHTML =
'<span class="timestamp">' + entry.timestamp + '</span>' +
'<span class="source">' + typeBadge + entry.source + '</span>' +
'<span class="amount">+' + entry.amount.toLocaleString() + ' XP</span>';
listDiv.appendChild(entryDiv);
});
}
updatePagination();
} catch (error) {
showError('Błąd podczas renderowania strony: ' + error.message);
}
}
function updatePagination() {
const paginationDiv = document.getElementById('pagination');
const totalPages = Math.max(1, Math.ceil(filteredData.length / itemsPerPage));
let paginationHTML = '';
if (totalPages > 1) {
if (currentPage > 1) {
paginationHTML += '<button onclick="changePage(1)">«</button>';
paginationHTML += '<button onclick="changePage(' + (currentPage - 1) + ')">‹</button>';
}
for (let i = Math.max(1, currentPage - 2);
i <= Math.min(totalPages, currentPage + 2); i++) {
paginationHTML += '<button onclick="changePage(' + i + ')" ' +
(i === currentPage ? 'class="active"' : '') + '>' + i + '</button>';
}
if (currentPage < totalPages) {
paginationHTML += '<button onclick="changePage(' + (currentPage + 1) + ')">›</button>';
paginationHTML += '<button onclick="changePage(' + totalPages + ')">»</button>';
}
}
paginationHTML += '<span class="info">Strona ' + currentPage + ' z ' + totalPages +
' (' + filteredData.length + ' wpisów)</span>';
paginationDiv.innerHTML = paginationHTML;
}
function changePage(newPage) {
currentPage = newPage;
renderPage();
window.scrollTo(0, 0);
}
// Inicjalizacja
try {
filteredData = [...originalData];
updateSummary();
applyFilters();
} catch (error) {
showError('Błąd podczas inicjalizacji: ' + error.message);
}
</script>
</body>
</html>
`);
logWindow.document.close();
}
// Funkcja resetująca doświadczenie do 0
function resetExperience() {
if (confirm('Jesteś pewien, że chcesz zresetować całkowite doświadczenie?')) {
totalExperience = 0;
experienceLog = [];
// 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');
if (workXPElement) workXPElement.textContent = '0 XP';
if (duelXPElement) duelXPElement.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);
};
// Nowa funkcja: aktualizuje tylko nowe raporty, przerywa na pierwszym duplikacie
function updateExperienceHistory() {
if (isCollectingHistory) {
alert('Pobieranie historii jest już w trakcie. Poczekaj na zakończenie.');
return;
}
isCollectingHistory = true;
tempExpLog = [];
let processedPages = 0;
let failedAttempts = 0;
const MAX_RETRIES = 3;
const MIN_DELAY = 1000;
const MAX_DELAY = 1200;
const RETRY_DELAY = 2000;
// 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: 'all'
}, function(initialData) {
if (!initialData || initialData.error) {
console.error('Błąd pobierania danych:', initialData);
isCollectingHistory = false;
alert('Nie udało się pobrać informacji o raportach. Spróbuj ponownie.');
return;
}
const totalPages = initialData.count;
function processPage(page) {
if (!isCollectingHistory || page > totalPages || foundDuplicate) {
finishUpdate();
return;
}
Ajax.remoteCall('reports', 'get_reports', {
page: page,
folder: 'all'
}, function(data) {
if (!isCollectingHistory || foundDuplicate) 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';
tempExpLog.push({
amount: exp,
source: report.title,
timestamp: report.date_received,
page: page,
type: reportType
});
}
}
processedPages++;
updateCollectionStatus(processedPages, totalPages, tempExpLog.length);
if (!foundDuplicate) {
setTimeout(() => processPage(page + 1), getRandomDelay());
} else {
finishUpdate();
}
} else {
failedAttempts++;
if (failedAttempts < MAX_RETRIES) {
setTimeout(() => processPage(page), RETRY_DELAY);
} else {
finishUpdate();
}
}
});
}
processPage(1);
});
function finishUpdate() {
if (tempExpLog.length === 0) {
isCollectingHistory = false;
const statusElement = document.querySelector('#collection-status');
if (statusElement) {
statusElement.innerHTML = 'Brak nowych wpisów z doświadczeniem.';
setTimeout(() => { statusElement.innerHTML = ''; }, 5000);
}
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();
isCollectingHistory = false;
const statusElement = document.querySelector('#collection-status');
if (statusElement) {
statusElement.innerHTML = `<div style="background: rgba(0,0,0,0.7); padding: 10px; border-radius: 5px;">
<strong>Zaktualizowano dane!</strong><br>
Dodano ${tempExpLog.length} nowych wpisów z XP<br>
Łączne doświadczenie: ${totalExperience.toLocaleString()} XP<br>
</div>`;
setTimeout(() => { statusElement.innerHTML = ''; }, 10000);
}
}
}
// Dodanie okna śledzenia doświadczenia
setInterval(() => {
addExperienceWindow();
}, 1000);
})();