@ing.jorgeu/wargaming-overlay 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE +13 -0
  2. package/PUBLISHING.md +35 -0
  3. package/README.md +471 -0
  4. package/THIRD_PARTY_ASSETS.md +7 -0
  5. package/battle_reader.py +110 -0
  6. package/cli.js +78 -0
  7. package/diagnostics.js +38 -0
  8. package/game-store.js +17 -0
  9. package/package.json +46 -0
  10. package/platform.js +46 -0
  11. package/public/assets/deathwatch-texture.png +0 -0
  12. package/public/assets/faction-emblem-placeholder.png +0 -0
  13. package/public/assets/factions/adepta-sororitas.png +0 -0
  14. package/public/assets/factions/adeptus-custodes.png +0 -0
  15. package/public/assets/factions/adeptus-mechanicus.png +0 -0
  16. package/public/assets/factions/aeldari.png +0 -0
  17. package/public/assets/factions/agents-of-the-imperium.png +0 -0
  18. package/public/assets/factions/astra-militarum.png +0 -0
  19. package/public/assets/factions/black-templars.png +0 -0
  20. package/public/assets/factions/blood-angels.png +0 -0
  21. package/public/assets/factions/chaos-daemons.png +0 -0
  22. package/public/assets/factions/chaos-knights.png +0 -0
  23. package/public/assets/factions/chaos-space-marines.png +0 -0
  24. package/public/assets/factions/custom.png +0 -0
  25. package/public/assets/factions/dark-angels.png +0 -0
  26. package/public/assets/factions/death-guard.png +0 -0
  27. package/public/assets/factions/deathwatch.png +0 -0
  28. package/public/assets/factions/drukhari.png +0 -0
  29. package/public/assets/factions/emperors-children.png +0 -0
  30. package/public/assets/factions/genestealer-cults.png +0 -0
  31. package/public/assets/factions/grey-knights.png +0 -0
  32. package/public/assets/factions/imperial-knights.png +0 -0
  33. package/public/assets/factions/leagues-of-votann.png +0 -0
  34. package/public/assets/factions/necrons.png +0 -0
  35. package/public/assets/factions/orks.png +0 -0
  36. package/public/assets/factions/space-marines.png +0 -0
  37. package/public/assets/factions/space-wolves.png +0 -0
  38. package/public/assets/factions/tau-empire.png +0 -0
  39. package/public/assets/factions/thousand-sons.png +0 -0
  40. package/public/assets/factions/tyranids.png +0 -0
  41. package/public/assets/factions/world-eaters.png +0 -0
  42. package/public/assets/formations/disruption.png +0 -0
  43. package/public/assets/formations/priority-assets.png +0 -0
  44. package/public/assets/formations/purge-the-foe.png +0 -0
  45. package/public/assets/formations/reconnaissance.png +0 -0
  46. package/public/assets/formations/take-and-hold.png +0 -0
  47. package/public/assets/generic-texture.png +0 -0
  48. package/public/assets/thousand-sons-texture.png +0 -0
  49. package/public/battle-title.css +2 -0
  50. package/public/control.html +3 -0
  51. package/public/control.js +35 -0
  52. package/public/detachments.css +5 -0
  53. package/public/faction-medallion.css +6 -0
  54. package/public/formations.css +5 -0
  55. package/public/generic-texture.css +1 -0
  56. package/public/live-score.css +7 -0
  57. package/public/live-score.js +49 -0
  58. package/public/overlay-art.html +1 -0
  59. package/public/overlay-v4-pre-art.css +10 -0
  60. package/public/overlay-v4.css +10 -0
  61. package/public/overlay-wide.html +1 -0
  62. package/public/overlay.html +1 -0
  63. package/public/overlay.js +13 -0
  64. package/public/resolution.css +2 -0
  65. package/public/resolution.js +9 -0
  66. package/public/style.css +1 -0
  67. package/reader.py +36 -0
  68. package/server.js +67 -0
package/server.js ADDED
@@ -0,0 +1,67 @@
1
+ const http = require('http');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ const dataDir = process.env.OVERLAY_DATA_DIR;
6
+ if (!dataDir) throw new Error('Inicia con wargaming-overlay start o npm start.');
7
+ const { spawn } = require('child_process');
8
+ const readline = require('readline');
9
+ const {mergeGame, emptyGame} = require('./game-store');
10
+ let game = emptyGame();
11
+ const gamePath = path.join(dataDir, 'game-state.json');
12
+ try { game = {...JSON.parse(fs.readFileSync(gamePath, 'utf8')), connected:false}; } catch {}
13
+ function persistGame() { fs.writeFileSync(gamePath + '.tmp', JSON.stringify(game, null, 2)); fs.renameSync(gamePath + '.tmp', gamePath); }
14
+ const readerOptions = { env: { ...process.env, PYTHONIOENCODING: "utf-8" }, stdio: ['ignore', 'pipe', 'inherit'] };
15
+ if (process.env.SUDO_UID) { readerOptions.uid = Number(process.env.SUDO_UID); readerOptions.gid = Number(process.env.SUDO_GID); }
16
+ let reader;
17
+ function startReader(){
18
+ let lastError = '';
19
+ reader = spawn(process.env.OVERLAY_PYTHON || 'python3', [path.join(__dirname, 'reader.py')], readerOptions);
20
+ readline.createInterface({ input: reader.stdout }).on('line', line => {
21
+ try { const incoming = JSON.parse(line); game = mergeGame(game, incoming); persistGame(); if (incoming.error !== lastError) { if (incoming.error) console.error(incoming.error); else if(lastError) console.log('Teléfono conectado. Lectura recuperada.'); lastError = incoming.error; } } catch (e) { console.error('Lectura:', e.message); }
22
+ });
23
+ reader.on('error', e => console.error('Lector:', e.message));
24
+ reader.on('exit', () => { game = mergeGame(game, {connected:false, error:'Lector detenido. Reinicia el servidor.'}); persistGame(); });
25
+ }
26
+ for (const signal of ['SIGINT', 'SIGTERM']) process.on(signal, () => { reader?.kill(); process.exit(); });
27
+ process.on('exit', () => reader?.kill());
28
+ const root = path.join(__dirname, 'public');
29
+ const statePath = path.join(dataDir, 'overlay-state.json');
30
+ const defaultState = {
31
+ title: '',
32
+ left: { name: 'PLAYER ONE', faction: 'Space Marines', color: '#245eb8' },
33
+ right: { name: 'PLAYER TWO', faction: 'Chaos Space Marines', color: '#7d1520' }
34
+ };
35
+ let state = defaultState;
36
+ try { state = { ...defaultState, ...JSON.parse(fs.readFileSync(statePath, 'utf8')) }; }
37
+ catch { /* First run: use the defaults. */ }
38
+ const listeners = new Set();
39
+ function broadcast() { const msg = `data: ${JSON.stringify(state)}\n\n`; listeners.forEach(r => r.write(msg)); }
40
+ function send(res, code, type, body) { res.writeHead(code, { 'Content-Type': type }); res.end(body); }
41
+ function saveState() { const temporaryPath = `${statePath}.tmp`; fs.writeFileSync(temporaryPath, JSON.stringify(state, null, 2)); fs.renameSync(temporaryPath, statePath); }
42
+
43
+ const server = http.createServer((req, res) => {
44
+ res.setHeader('Cache-Control','no-store');
45
+ const url = new URL(req.url, 'http://localhost');
46
+ if (url.pathname === '/game/reset' && req.method === 'POST') { game = emptyGame(); persistGame(); return send(res, 200, 'application/json', JSON.stringify({ok:true})); }
47
+ if (url.pathname === '/game-state.json') { res.setHeader('Cache-Control', 'no-store'); return send(res, 200, 'application/json', JSON.stringify(game)); }
48
+ if (url.pathname === '/events') {
49
+ res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive' });
50
+ listeners.add(res); res.write(`data: ${JSON.stringify(state)}\n\n`);
51
+ req.on('close', () => listeners.delete(res)); return;
52
+ }
53
+ if (url.pathname === '/state' && req.method === 'GET') return send(res, 200, 'application/json', JSON.stringify(state));
54
+ if (url.pathname === '/state' && req.method === 'POST') {
55
+ let body = ''; req.on('data', c => body += c); req.on('end', () => {
56
+ try { state = JSON.parse(body); saveState(); broadcast(); send(res, 200, 'application/json', JSON.stringify({ ok: true })); }
57
+ catch { send(res, 400, 'application/json', JSON.stringify({ error: 'Invalid data' })); }
58
+ }); return;
59
+ }
60
+ const file = url.pathname === '/' ? 'control.html' : url.pathname.slice(1);
61
+ const filePath = path.join(root, file);
62
+ if (!filePath.startsWith(root + path.sep) || !fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) return send(res, 404, 'text/plain', 'Not found');
63
+ const types = { '.html': 'text/html; charset=utf-8', '.css': 'text/css; charset=utf-8', '.js': 'text/javascript; charset=utf-8' };
64
+ send(res, 200, types[path.extname(filePath)] || 'application/octet-stream', fs.readFileSync(filePath));
65
+ });
66
+ server.on('error', e => { console.error(e.code==='EADDRINUSE' ? 'El puerto está ocupado. Detén el otro servidor o usa --port 8766.' : 'No se pudo iniciar: '+e.message); reader?.kill(); process.exitCode=1; });
67
+ server.listen(Number(process.env.PORT || 8765), '127.0.0.1', () => { startReader(); console.log(`Panel: http://127.0.0.1:${process.env.PORT || 8765}\nOBS: http://127.0.0.1:${process.env.PORT || 8765}/overlay-art.html\nDatos: ${dataDir}\nCtrl+C para detener.`); });