PageSourceSearch

https://www.spielfieber.net/wp-content/plugins/spielfieber/world/app/manager/PlayerDataManager.js

js spielfieber.net collected 2026-10-01 14:19:51 UTC 12,876 bytes, 379 lines download raw bytes

1import { log } from "../../src/helpers.js";
2
3/**
4 * PlayerDataManager handles persistent player data
5 * - Generates unique player IDs
6 * - Manages localStorage for player data
7 * - Loads/saves player data to WordPress database
8 */
9export class PlayerDataManager {
10    constructor() {
11        this.STORAGE_KEY = 'spielfieber_player_data';
12        this.API_ENDPOINT = null;
13
14        // Initialize API endpoint
15        this._initializeApiEndpoint();
16    }
17
18    /**
19     * Initialize API endpoint URL
20     */
21    _initializeApiEndpoint() {
22        let baseUrl = spielfieber_vars.siteurl;
23
24        // Handle different URL structures
25        if (baseUrl.endsWith('/wp')) {
26            baseUrl = baseUrl.slice(0, -3); // Remove trailing /wp
27        }
28
29        this.API_ENDPOINT = `${baseUrl}/wp-json/spielfieber/v1/player-data`;
30    }
31
32    /**
33     * Generate a unique player ID
34     */
35    _generatePlayerId() {
36        // Create a UUID-like string for the player ID
37        return 'player_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
38    }
39
40    /**
41     * Generate a unique 6-character user code (format: ABC123)
42     */
43    _generateUserCode() {
44        const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
45        const nums = '0123456789';
46        let code = '';
47
48        // 3 letters
49        for (let i = 0; i < 3; i++) {
50            code += chars.charAt(Math.floor(Math.random() * chars.length));
51        }
52
53        // 3 numbers
54        for (let i = 0; i < 3; i++) {
55            code += nums.charAt(Math.floor(Math.random() * nums.length));
56        }
57
58        return code;
59    }
60
61    /**
62     * Get or create player data from localStorage
63     */
64    getPlayerData() {
65        try {
66            const storedData = localStorage.getItem(this.STORAGE_KEY);
67
68            if (storedData) {
69                const playerData = this._migrateToTotalXp(JSON.parse(storedData));
70                console.log("====== DEBUG getPlayerData ======");
71                console.log("Loaded from localStorage:", playerData);
72                console.log("lastCharacterBuilderShown:", playerData.lastCharacterBuilderShown);
73                console.log("=================================");
74                log("PlayerDataManager: Loaded existing player data:", playerData);
75                return playerData;
76            }
77        } catch (error) {
78            log("PlayerDataManager: Error reading from localStorage:", error);
79        }
80
81        // Create new player data if none exists
82        const newPlayerData = this._createNewPlayerData();
83        this.savePlayerDataToStorage(newPlayerData);
84        console.log("====== DEBUG getPlayerData ======");
85        console.log("Created NEW player data:", newPlayerData);
86        console.log("lastCharacterBuilderShown:", newPlayerData.lastCharacterBuilderShown);
87        console.log("=================================");
88        log("PlayerDataManager: Created new player data:", newPlayerData);
89        return newPlayerData;
90    }
91
92    /**
93     * One-time localStorage migration to lifetime-total XP.
94     *
95     * Stored XP used to be the surplus within the current level, so a level 3 player with
96     * 150 held a real total of 450. Without this the stale value would be written straight
97     * back over the migrated server record on the next save, undoing it.
98     * Mirrors spielfieber_migrate_xp_to_totals() in inc/multiplayer-api.php.
99     */
100    _migrateToTotalXp(playerData) {
101        if (!playerData || playerData.xpModel === 'total') return playerData;
102
103        const level = playerData.level || 1;
104        const oldXp = playerData.xp || 0;
105
106        playerData.xp = xpForLevelStart(level) + oldXp;
107        playerData.xpModel = 'total';
108
109        log(`PlayerDataManager: Migrated stored XP to lifetime total (level ${level}, ${oldXp} -> ${playerData.xp})`);
110        this.savePlayerDataToStorage(playerData);
111
112        return playerData;
113    }
114
115    /**
116     * Create new player data with unique ID and user code
117     */
118    _createNewPlayerData() {
119        return {
120            playerId: this._generatePlayerId(),
121            userCode: this._generateUserCode(),
122            name: this._generatePlayerName(),
123            xp: 0,
124            level: 1,
125            xpModel: 'total',
126            character: this._generateRandomCharacter(),
127            createdAt: Date.now(),
128            lastPlayed: Date.now(),
129            lastCharacterBuilderShown: 0  // Never shown yet
130        };
131    }
132
133    /**
134     * Save player data to localStorage
135     */
136    savePlayerDataToStorage(playerData) {
137        try {
138            playerData.lastPlayed = Date.now();
139            localStorage.setItem(this.STORAGE_KEY, JSON.stringify(playerData));
140            log("PlayerDataManager: Saved player data to localStorage");
141        } catch (error) {
142            log("PlayerDataManager: Error saving to localStorage:", error);
143        }
144    }
145
146    /**
147     * Load player data from server by player ID
148     */
149    async loadPlayerDataFromServer(playerId) {
150        try {
151            const response = await fetch(`${this.API_ENDPOINT}/${playerId}`, {
152                method: 'GET',
153                headers: {
154                    'Content-Type': 'application/json',
155                }
156            });
157
158            if (!response.ok) {
159                throw new Error(`HTTP error! status: ${response.status}`);
160            }
161
162            const data = await response.json();
163
164            if (data.success && data.playerData) {
165                log("PlayerDataManager: Loaded player data from server:", data.playerData);
166                return data.playerData;
167            }
168
169            return null;
170        } catch (error) {
171            log("PlayerDataManager: Error loading player data from server:", error);
172            return null;
173        }
174    }
175
176    /**
177     * Load player data from server by user code
178     */
179    async loadPlayerDataByUserCode(userCode) {
180        try {
181            let baseUrl = spielfieber_vars.siteurl;
182            if (baseUrl.endsWith('/wp')) {
183                baseUrl = baseUrl.slice(0, -3);
184            }
185
186            const endpoint = `${baseUrl}/wp-json/spielfieber/v1/player-data-by-code/${userCode}`;
187
188            const response = await fetch(endpoint, {
189                method: 'GET',
190                headers: {
191                    'Content-Type': 'application/json',
192                }
193            });
194
195            if (!response.ok) {
196                throw new Error(`HTTP error! status: ${response.status}`);
197            }
198
199            const data = await response.json();
200
201            if (data.success && data.playerData) {
202                log("PlayerDataManager: Loaded player data by user code:", data.playerData);
203                return data.playerData;
204            }
205
206            return null;
207        } catch (error) {
208            log("PlayerDataManager: Error loading player data by user code:", error);
209            return null;
210        }
211    }
212
213    /**
214     * Save player data to server
215     */
216    async savePlayerDataToServer(playerData) {
217        try {
218            const response = await fetch(this.API_ENDPOINT, {
219                method: 'POST',
220                headers: {
221                    'Content-Type': 'application/json',
222                },
223                body: JSON.stringify(playerData)
224            });
225
226            if (!response.ok) {
227                throw new Error(`HTTP error! status: ${response.status}`);
228            }
229
230            const data = await response.json();
231            log("PlayerDataManager: Saved player data to server:", data);
232            return data.success;
233        } catch (error) {
234            log("PlayerDataManager: ERROR saving player data to server.");
235            return false;
236        }
237    }
238
239    /**
240     * Initialize player data with server sync
241     */
242    async initializePlayerData() {
243        // Get or create local player data
244        let playerData = this.getPlayerData();
245
246        // Try to load updated data from server
247        const serverData = await this.loadPlayerDataFromServer(playerData.playerId);
248
249        if (serverData) {
250            // Merge server data with local data (server takes precedence for name, xp, level)
251            playerData = {
252                ...playerData,
253                name: serverData.name || playerData.name,
254                xp: serverData.xp || playerData.xp,
255                level: serverData.level || playerData.level,
256                character: serverData.character || playerData.character
257            };
258
259            // Save updated data locally
260            this.savePlayerDataToStorage(playerData);
261            log("PlayerDataManager: Merged server data with local data");
262        } else {
263            // Save local data to server for first time
264            await this.savePlayerDataToServer(playerData);
265            log("PlayerDataManager: Saved new player data to server");
266        }
267
268        return playerData;
269    }
270
271    getPlayerPositionX() {
272        const data = this._read() || {};
273        const positionX = this.scene ? this.scene.get("Scene0").multiplayerManager.getPlayerData().x : gameConfig.screenResolution.width / 2;
274        log("PlayerDataManager: Retrieved player position X:" + positionX + " alternative:" + data.lastPositionX);
275        return positionX || data.lastPositionX;
276    }
277
278    updatePlayerPosition(currentPlayer) {
279        const data = this._read() || {};
280        data.playerId = currentPlayer.playerId || data.playerId;
281        data.x = currentPlayer.x;
282        data.y = currentPlayer.y;
283        this._write(data);
284    }
285
286    _read() {
287        try {
288            const raw = localStorage.getItem(this.STORAGE_KEY);
289            return raw ? JSON.parse(raw) : null;
290        } catch (e) {
291            return null;
292        }
293    }
294
295    _write(data) {
296        try {
297            localStorage.setItem(this.STORAGE_KEY, JSON.stringify(data));
298        } catch (e) {
299            console.warn('PlayerDataManager: Failed to write local storage', e);
300        }
301    }
302
303    /**
304     * Update player XP and level
305     */
306    updatePlayerXP(currentPlayerData, xpGain) {
307        currentPlayerData.xp += xpGain;
308
309        // XP is a lifetime total: nothing is deducted here, the level just follows it.
310        // A negative xpGain lowers the total and can therefore lower the level.
311        const newLevel = levelForXp(currentPlayerData.xp);
312        if (newLevel !== currentPlayerData.level) {
313            log(`PlayerDataManager: Level ${newLevel > currentPlayerData.level ? 'up' : 'down'}! Player is now level ${newLevel}`);
314            currentPlayerData.level = newLevel;
315        }
316
317        // Save to both localStorage and server
318        this.savePlayerDataToStorage(currentPlayerData);
319        this.savePlayerDataToServer(currentPlayerData);
320
321        return currentPlayerData;
322    }
323
324    /**
325     * Update player character data
326     */
327    updatePlayerCharacter(currentPlayerData, newCharacterData) {
328        currentPlayerData.character = newCharacterData;
329
330        // Save to both localStorage and server
331        this.savePlayerDataToStorage(currentPlayerData);
332        this.savePlayerDataToServer(currentPlayerData);
333
334        return currentPlayerData;
335    }
336
337    /**
338     * Generate a random player name
339     */
340    _generatePlayerName() {
341        const adjectives = ['Poker', 'Smart', 'Glücks', 'Cool', 'Best', 'Crazy', 'Schneller', 'Erfolgs'];
342        const nouns = ['Spieler', 'Gamer', 'Held', 'Entdecker', 'Besucher', 'Gast', 'User', 'Freund'];
343        const adj = adjectives[Math.floor(Math.random() * adjectives.length)];
344        const noun = nouns[Math.floor(Math.random() * nouns.length)];
345        const number = Math.floor(Math.random() * 999) + 1;
346        return `${adj}${noun}${number}`;
347    }
348
349    /**
350     * Generate random character appearance
351     */
352    _generateRandomCharacter() {
353        // Randomly choose gender
354        const gender = Math.random() < 0.5 ? 'male' : 'female';
355
356        let versions;
357        if (gender === 'female') {
358            versions = {
359                skin: Math.floor(Math.random() * 5) + 1,    // 1-5
360                corset: Math.floor(Math.random() * 5) + 1,  // 1-5
361                hair: Math.floor(Math.random() * 5) + 1,    // 1-5
362                skirt: Math.floor(Math.random() * 5) + 1,   // 1-5
363                socks: Math.floor(Math.random() * 5) + 1,   // 1-5
364                hat: Math.floor(Math.random() * 5) + 1      // 1-5
365            };
366        } else {
367            versions = {
368                skin: Math.floor(Math.random() * 5) + 1,    // 1-5
369                shirt: Math.floor(Math.random() * 5) + 1,   // 1-5
370                hair: Math.floor(Math.random() * 5) + 1,    // 1-5
371                shoes: Math.floor(Math.random() * 5) + 1,   // 1-5
372                pants: Math.floor(Math.random() * 5) + 1,   // 1-5
373                hat: Math.floor(Math.random() * 5) + 1      // 1-5
374            };
375        }
376
377        return { gender, versions };
378    }
379}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.