From 5fe67246631e3ff66ab13e951f5f26a2fb8484fe Mon Sep 17 00:00:00 2001 From: Lars Behrends Date: Mon, 29 Dec 2025 09:27:06 +0100 Subject: [PATCH] feat: add minecraftStats to player model and update related API endpoints --- backend/database.js | 3 +++ backend/server.js | 28 +++++++++++++++------------ pages/PlayerProfile.tsx | 43 ++++++++++++++++++++++++++++++++++------- types.ts | 25 ++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 19 deletions(-) diff --git a/backend/database.js b/backend/database.js index e591782..9cbb85f 100644 --- a/backend/database.js +++ b/backend/database.js @@ -23,6 +23,7 @@ const SEED_PLAYERS = [ isAdmin: 0, tags: JSON.stringify(['#Bürger', '#Händler']), stats: JSON.stringify({ playtimeHours: 482, level: 45, role: 'Bürger', organizationId: 'org-3' }), + minecraftStats: null, inventory: JSON.stringify([]), storyMarkdown: '# Der Bauplan von V\n\n> "Stein erinnert sich..."' }, @@ -34,6 +35,7 @@ const SEED_PLAYERS = [ isAdmin: 1, // DrKButz is admin for testing tags: JSON.stringify(['#Bauunternehmer']), stats: JSON.stringify({ playtimeHours: 120, level: 12, role: 'Unternehmer', organizationId: 'org-4' }), + minecraftStats: null, inventory: JSON.stringify([]), storyMarkdown: '# Forschungslogbuch:\n\nSpezialisiert auf...' } @@ -117,6 +119,7 @@ function setupTables() { isAdmin TINYINT DEFAULT 0, tags JSON, stats JSON, + minecraftStats JSON, inventory JSON, storyMarkdown TEXT, discordId VARCHAR(255) diff --git a/backend/server.js b/backend/server.js index d229798..3155b80 100644 --- a/backend/server.js +++ b/backend/server.js @@ -152,9 +152,11 @@ app.get('/api/players', (req, res) => { // Parse JSON fields const parsed = rows.map(r => ({ ...r, - tags: JSON.parse(r.tags), - stats: JSON.parse(r.stats), - inventory: JSON.parse(r.inventory), + tags: JSON.parse(r.tags || '[]'), + stats: JSON.parse(r.minecraftStats || '{}'), + //stats: JSON.parse(r.stats || '{}'), + //minecraftStats: r.minecraftStats ? JSON.parse(r.minecraftStats) : undefined, + inventory: JSON.parse(r.inventory || '[]'), isOnline: !!r.isOnline })); res.json(parsed); @@ -474,15 +476,16 @@ app.post('/api/admin/npc-citizen', (req, res) => { role: role || 'Bürger', organizationId: organizationId || null }), + minecraftStats: null, inventory: JSON.stringify([]), storyMarkdown: storyMarkdown || `# ${username}\n\nEin NPC-Bürger im Obsidian-Tal.`, discordId: null }; - db.run(`INSERT INTO players (uuid, username, isOnline, isNpc, tags, stats, inventory, storyMarkdown, discordId) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + db.run(`INSERT INTO players (uuid, username, isOnline, isNpc, tags, stats, minecraftStats, inventory, storyMarkdown, discordId) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [npcData.uuid, npcData.username, npcData.isOnline, npcData.isNpc, npcData.tags, - npcData.stats, npcData.inventory, npcData.storyMarkdown, npcData.discordId], + npcData.stats, npcData.minecraftStats, npcData.inventory, npcData.storyMarkdown, npcData.discordId], function(err) { if (err) { console.error('Error creating NPC citizen:', err); @@ -1600,7 +1603,8 @@ app.post('/api/data', (req, res) => { const { eventType, player, uuid, timestamp isNpc: 0, isAdmin: 0, tags: JSON.stringify([]), - stats: JSON.stringify({ + stats: JSON.stringify({}), + minecraftStats: JSON.stringify({ char, statistics, advancements, @@ -1620,8 +1624,8 @@ app.post('/api/data', (req, res) => { const { eventType, player, uuid, timestamp if (row) { // Update existing player - db.run(`UPDATE players SET username = ?, isOnline = ?, stats = ?, storyMarkdown = ? WHERE uuid = ?`, - [playerData.username, playerData.isOnline, playerData.stats, playerData.storyMarkdown, uuid], + db.run(`UPDATE players SET username = ?, isOnline = ?, minecraftStats = ? WHERE uuid = ?`, + [playerData.username, playerData.isOnline, playerData.minecraftStats, uuid], function(err) { if (err) { console.error('Error updating player:', err); @@ -1637,10 +1641,10 @@ app.post('/api/data', (req, res) => { const { eventType, player, uuid, timestamp ); } else { // Insert new player - db.run(`INSERT INTO players (uuid, username, isOnline, isNpc, isAdmin, tags, stats, inventory, storyMarkdown, discordId) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + db.run(`INSERT INTO players (uuid, username, isOnline, isNpc, isAdmin, tags, stats, minecraftStats, inventory, storyMarkdown, discordId) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [playerData.uuid, playerData.username, playerData.isOnline, playerData.isNpc, playerData.isAdmin, - playerData.tags, playerData.stats, playerData.inventory, playerData.storyMarkdown, playerData.discordId], + playerData.tags, playerData.stats, playerData.minecraftStats, playerData.inventory, playerData.storyMarkdown, playerData.discordId], function(err) { if (err) { console.error('Error inserting player:', err); diff --git a/pages/PlayerProfile.tsx b/pages/PlayerProfile.tsx index bba4a76..f474518 100644 --- a/pages/PlayerProfile.tsx +++ b/pages/PlayerProfile.tsx @@ -22,7 +22,7 @@ const PlayerProfile: React.FC = () => { // Is this the logged-in user's profile? const isOwner = currentUser?.linkedPlayerUuid === player?.uuid; - const playerOrg = player ? dbService.getOrg(player.stats.organizationId || '') : null; + const playerOrg = player ? dbService.getOrg(player.stats?.organizationId || '') : null; // Check if player is already linked to anyone in our mock/real DB logic // Since the Player object doesn't expose 'discordId' publicly in types yet (it's hidden in DB), @@ -182,7 +182,7 @@ const PlayerProfile: React.FC = () => {
- {player.tags.map(tag => ( + {(player.tags || []).map(tag => ( {tag} @@ -200,12 +200,41 @@ const PlayerProfile: React.FC = () => {
- {player.stats.playtimeHours}h + + {player.minecraftStats?.statistics?.general?.["minecraft:play_time"] + ? `${Math.round((player.minecraftStats.statistics.general["minecraft:play_time"] || 0) / 20 / 3600)}h` + : `${player.stats?.playtimeHours || 0}h` + } +
- Lvl {player.stats.level} + + Lvl {player.minecraftStats?.char?.xpLevel || player.stats?.level || 1} +
+ {player.minecraftStats && player.minecraftStats.char && player.minecraftStats.statistics && ( + <> +
+ + + {player.minecraftStats.char.health}/{player.minecraftStats.char.maxHealth} HP + +
+
+ + + {player.minecraftStats.char.foodLevel}/20 Food + +
+
+ + + {player.minecraftStats.statistics.general?.["minecraft:mob_kills"] || 0} Kills + +
+ + )}
@@ -214,7 +243,7 @@ const PlayerProfile: React.FC = () => {
{/* Left Col: Inventory & Org */}
- +

Zugehörigkeit

@@ -229,7 +258,7 @@ const PlayerProfile: React.FC = () => { {playerOrg ? playerOrg.name.charAt(0) : }
-
{player.stats.role}
+
{player.stats?.role || 'Unbekannt'}
{playerOrg ? ( {playerOrg.name} @@ -263,7 +292,7 @@ const PlayerProfile: React.FC = () => {
- {renderMarkdown(player.storyMarkdown)} + {renderMarkdown(player.storyMarkdown || '')}
diff --git a/types.ts b/types.ts index e05fac6..079fcbf 100644 --- a/types.ts +++ b/types.ts @@ -7,6 +7,30 @@ export interface Item { nbtSummary?: string; } +export interface MinecraftStats { + char: { + health: number; + maxHealth: number; + foodLevel: number; + xpLevel: number; + position: { + x: number; + y: number; + z: number; + }; + }; + statistics: { + general: { [key: string]: number }; + kills: { [key: string]: number }; + killed_by: { [key: string]: number }; + }; + advancements: Array<{ + id: string; + title: string; + }>; + lastSync: number; +} + export interface PlayerStats { playtimeHours: number; level: number; @@ -20,6 +44,7 @@ export interface Player { skinUrl?: string; // Placeholder in a real app inventory: (Item | null)[]; stats: PlayerStats; + minecraftStats?: MinecraftStats; storyMarkdown: string; tags: string[]; isOnline: boolean;