diff --git a/tests/test_web_inventory_api.py b/tests/test_web_inventory_api.py index 70a2be7..6d420b3 100644 --- a/tests/test_web_inventory_api.py +++ b/tests/test_web_inventory_api.py @@ -32,7 +32,7 @@ class TestWebInventoryAPI(unittest.TestCase): self.g = mosfet_web.g self.original = { key: self.g.get(key) for key in - ('connected', 'game', 'inv', 'chest_viewers') + ('connected', 'game', 'inv', 'chest_viewers', 'time', 'time_base') } self.game = FakeGame() self.g.connected = True @@ -54,6 +54,16 @@ class TestWebInventoryAPI(unittest.TestCase): item = response.get_json()['inv']['9'] self.assertEqual(item['components']['custom_name']['text'], 'Test Paper') + def test_global_api_exposes_world_time_ticks(self): + self.g.time = 12345.5 + self.g.time_base = 12345.5 + + response = self.client.get('/api/global') + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.get_json()['time'], 12345.5) + self.assertEqual(response.get_json()['time_base'], 12345.5) + def test_global_api_stringifies_tuple_keys(self): self.g.chest_viewers = {(12, 64, -7): 1} diff --git a/web_interface/build/index.html b/web_interface/build/index.html index 6fb265d..c3e9ef4 100644 --- a/web_interface/build/index.html +++ b/web_interface/build/index.html @@ -86,6 +86,7 @@
Game mode
—
+
Time
—
Active job
IDLE
Path nodes
0
Entities
0 mobs · 0 drops
diff --git a/web_interface/public/index.html b/web_interface/public/index.html index 6fb265d..c3e9ef4 100644 --- a/web_interface/public/index.html +++ b/web_interface/public/index.html @@ -86,6 +86,7 @@
Game mode
—
+
Time
—
Active job
IDLE
Path nodes
0
Entities
0 mobs · 0 drops
diff --git a/web_interface/src/dashboard.js b/web_interface/src/dashboard.js index ebf8d69..fe6d362 100644 --- a/web_interface/src/dashboard.js +++ b/web_interface/src/dashboard.js @@ -57,6 +57,19 @@ return modes[mode] || (mode == null ? '—' : String(mode)); } + function formatWorldClock(ticks) { + if (ticks == null) return '—'; + const time = Number(ticks); + if (!Number.isFinite(time)) return '—'; + const dayTicks = ((Math.floor(time) % 24000) + 24000) % 24000; + const minutes = Math.floor((((dayTicks + 6000) % 24000) / 1000) * 60); + const hour24 = Math.floor(minutes / 60); + const hour12 = hour24 % 12 || 12; + const minute = minutes % 60; + const period = hour24 < 12 ? 'AM' : 'PM'; + return `${String(hour12).padStart(2, '0')}:${String(minute).padStart(2, '0')} ${period}`; + } + function textureCandidates(name) { const id = String(name || '').replace(/^minecraft:/, '').toLowerCase(); if (!/^[a-z0-9_]+$/.test(id) || id === 'air') return []; @@ -451,6 +464,11 @@ const center = state.chunk_state?.center; $('chunk-detail').textContent = center ? `Center ${center[0]}, ${center[1]} · radius ${state.chunk_state.radius ?? '—'}` : 'Waiting for world data'; $('game-mode').textContent = formatMode(state.game_mode); + const worldTime = online && state.time_base != null && state.time != null + ? Number(state.time) : NaN; + $('world-time').textContent = Number.isFinite(worldTime) + ? `${Math.floor(worldTime).toLocaleString()} t · ${formatWorldClock(worldTime)}` + : '—'; $('job-state').textContent = prettyName(state.job_state || 'idle').toUpperCase(); $('path-length').textContent = String(state.path?.length || 0); $('entity-count').textContent = `${Object.keys(state.mobs || {}).length} mobs · ${Object.keys(state.objects || {}).length} drops`; diff --git a/web_interface/src/index.html b/web_interface/src/index.html index 6fb265d..c3e9ef4 100644 --- a/web_interface/src/index.html +++ b/web_interface/src/index.html @@ -86,6 +86,7 @@
Game mode
—
+
Time
—
Active job
IDLE
Path nodes
0
Entities
0 mobs · 0 drops