burnrate 0.1.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.
- package/LICENSE +21 -0
- package/README.md +507 -0
- package/dist/cli/format.d.ts +13 -0
- package/dist/cli/format.js +319 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.js +1121 -0
- package/dist/cli/setup.d.ts +8 -0
- package/dist/cli/setup.js +143 -0
- package/dist/core/async-engine.d.ts +411 -0
- package/dist/core/async-engine.js +2274 -0
- package/dist/core/async-worldgen.d.ts +19 -0
- package/dist/core/async-worldgen.js +221 -0
- package/dist/core/engine.d.ts +154 -0
- package/dist/core/engine.js +1104 -0
- package/dist/core/pathfinding.d.ts +38 -0
- package/dist/core/pathfinding.js +146 -0
- package/dist/core/types.d.ts +489 -0
- package/dist/core/types.js +359 -0
- package/dist/core/worldgen.d.ts +22 -0
- package/dist/core/worldgen.js +292 -0
- package/dist/db/database.d.ts +83 -0
- package/dist/db/database.js +829 -0
- package/dist/db/turso-database.d.ts +177 -0
- package/dist/db/turso-database.js +1586 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.js +1877 -0
- package/dist/server/api.d.ts +8 -0
- package/dist/server/api.js +1234 -0
- package/dist/server/async-tick-server.d.ts +5 -0
- package/dist/server/async-tick-server.js +63 -0
- package/dist/server/errors.d.ts +78 -0
- package/dist/server/errors.js +156 -0
- package/dist/server/rate-limit.d.ts +22 -0
- package/dist/server/rate-limit.js +134 -0
- package/dist/server/tick-server.d.ts +9 -0
- package/dist/server/tick-server.js +114 -0
- package/dist/server/validation.d.ts +194 -0
- package/dist/server/validation.js +114 -0
- package/package.json +65 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BURNRATE CLI Formatting
|
|
3
|
+
* Terminal-native visual output
|
|
4
|
+
*/
|
|
5
|
+
import { getSupplyState } from '../core/types.js';
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// MAIN VIEW
|
|
8
|
+
// ============================================================================
|
|
9
|
+
export function formatView(db, player) {
|
|
10
|
+
const tick = db.getCurrentTick();
|
|
11
|
+
const season = db.getSeasonInfo();
|
|
12
|
+
const zone = db.getZone(player.locationId);
|
|
13
|
+
const routes = db.getRoutesFromZone(player.locationId);
|
|
14
|
+
const shipments = db.getPlayerShipments(player.id).filter(s => s.status === 'in_transit');
|
|
15
|
+
// Get recent events for alerts
|
|
16
|
+
const events = db.getEvents({ limit: 10, actorId: player.id });
|
|
17
|
+
const alerts = events.slice(0, 3).map(e => formatEventAlert(e, db));
|
|
18
|
+
let output = '';
|
|
19
|
+
output += `╔════════════════════════════════════════════════════════════════╗\n`;
|
|
20
|
+
output += `║ BURNRATE │ Tick ${tick.toString().padStart(6)} │ Season ${season.seasonNumber} Week ${season.seasonWeek} │ ${player.tier.charAt(0).toUpperCase() + player.tier.slice(1).padEnd(9)} ║\n`;
|
|
21
|
+
output += `╠════════════════════════════════════════════════════════════════╣\n`;
|
|
22
|
+
output += `║ BALANCE: ${player.inventory.credits.toString().padEnd(8)} cr │ SHIPMENTS: ${shipments.length.toString().padEnd(2)} active │ REP: ${player.reputation.toString().padEnd(5)} ║\n`;
|
|
23
|
+
output += `╠════════════════════════════════════════════════════════════════╣\n`;
|
|
24
|
+
// Alerts
|
|
25
|
+
output += `║ ALERTS ║\n`;
|
|
26
|
+
if (alerts.length === 0) {
|
|
27
|
+
output += `║ (no recent alerts) ║\n`;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
for (const alert of alerts) {
|
|
31
|
+
output += `║ ${alert.padEnd(62)} ║\n`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
output += `╠════════════════════════════════════════════════════════════════╣\n`;
|
|
35
|
+
// Current location
|
|
36
|
+
if (zone) {
|
|
37
|
+
const supplyState = getSupplyState(zone.supplyLevel);
|
|
38
|
+
const supplyBar = makeSupplyBar(zone.supplyLevel, 20);
|
|
39
|
+
const stateLabel = supplyState.toUpperCase().padEnd(9);
|
|
40
|
+
output += `║ LOCATION: ${zone.name.padEnd(52)} ║\n`;
|
|
41
|
+
output += `║ Supply: ${supplyBar} ${Math.round(zone.supplyLevel).toString().padStart(3)}% ${stateLabel} ║\n`;
|
|
42
|
+
// Market summary
|
|
43
|
+
const orders = db.getOrdersForZone(zone.id);
|
|
44
|
+
const resources = ['fuel', 'rations', 'parts', 'metal'];
|
|
45
|
+
const prices = resources.map(r => {
|
|
46
|
+
const resourceOrders = orders.filter(o => o.resource === r);
|
|
47
|
+
const avgPrice = resourceOrders.length > 0
|
|
48
|
+
? Math.round(resourceOrders.reduce((s, o) => s + o.price, 0) / resourceOrders.length)
|
|
49
|
+
: '—';
|
|
50
|
+
return `${r}: ${avgPrice}`;
|
|
51
|
+
}).join(' │ ');
|
|
52
|
+
output += `║ Market: ${prices.padEnd(54)} ║\n`;
|
|
53
|
+
// Routes
|
|
54
|
+
const routeStr = routes.slice(0, 3).map(r => {
|
|
55
|
+
const toZone = db.getZone(r.toZoneId);
|
|
56
|
+
const riskLabel = r.baseRisk < 0.1 ? 'safe' : r.baseRisk < 0.25 ? 'moderate' : 'dangerous';
|
|
57
|
+
return `→${toZone?.name.split('.')[1] || '?'} (${riskLabel})`;
|
|
58
|
+
}).join(' ');
|
|
59
|
+
output += `║ Routes: ${routeStr.padEnd(54)} ║\n`;
|
|
60
|
+
}
|
|
61
|
+
output += `╚════════════════════════════════════════════════════════════════╝\n`;
|
|
62
|
+
return output;
|
|
63
|
+
}
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// ZONE VIEW
|
|
66
|
+
// ============================================================================
|
|
67
|
+
export function formatZone(db, zone) {
|
|
68
|
+
const supplyState = getSupplyState(zone.supplyLevel);
|
|
69
|
+
const supplyBar = makeSupplyBar(zone.supplyLevel, 30);
|
|
70
|
+
const routes = db.getRoutesFromZone(zone.id);
|
|
71
|
+
let output = '';
|
|
72
|
+
output += `\n┌─ ZONE: ${zone.name} ${'─'.repeat(50 - zone.name.length)}┐\n`;
|
|
73
|
+
output += `│ Type: ${zone.type.toUpperCase().padEnd(56)} │\n`;
|
|
74
|
+
output += `│ Owner: ${(zone.ownerId || 'Neutral').padEnd(55)} │\n`;
|
|
75
|
+
output += `│ ${'─'.repeat(62)} │\n`;
|
|
76
|
+
output += `│ Supply: ${supplyBar} ${Math.round(zone.supplyLevel).toString().padStart(3)}% │\n`;
|
|
77
|
+
output += `│ State: ${supplyState.toUpperCase().padEnd(55)} │\n`;
|
|
78
|
+
output += `│ Stockpile: ${zone.suStockpile.toString().padEnd(8)} SU │\n`;
|
|
79
|
+
output += `│ Burn Rate: ${zone.burnRate.toString().padEnd(8)} SU/tick │\n`;
|
|
80
|
+
if (zone.burnRate > 0) {
|
|
81
|
+
const hoursLeft = zone.suStockpile > 0 ? (zone.suStockpile / zone.burnRate * 10 / 60).toFixed(1) : '0';
|
|
82
|
+
output += `│ Time Until Empty: ${hoursLeft.padEnd(6)} hours │\n`;
|
|
83
|
+
}
|
|
84
|
+
output += `│ Compliance Streak: ${zone.complianceStreak.toString().padEnd(6)} ticks │\n`;
|
|
85
|
+
output += `│ Garrison: ${zone.garrisonLevel.toString().padEnd(52)} │\n`;
|
|
86
|
+
output += `│ ${'─'.repeat(62)} │\n`;
|
|
87
|
+
output += `│ ROUTES FROM HERE │\n`;
|
|
88
|
+
for (const route of routes.slice(0, 6)) {
|
|
89
|
+
const toZone = db.getZone(route.toZoneId);
|
|
90
|
+
const riskBar = makeRiskBar(route.baseRisk * route.chokepointRating, 10);
|
|
91
|
+
const riskPct = Math.round(route.baseRisk * route.chokepointRating * 100);
|
|
92
|
+
output += `│ → ${(toZone?.name || 'Unknown').padEnd(25)} ${riskBar} ${riskPct.toString().padStart(2)}% ${route.distance}t │\n`;
|
|
93
|
+
}
|
|
94
|
+
if (routes.length > 6) {
|
|
95
|
+
output += `│ ... and ${routes.length - 6} more routes │\n`;
|
|
96
|
+
}
|
|
97
|
+
output += `└${'─'.repeat(64)}┘\n`;
|
|
98
|
+
return output;
|
|
99
|
+
}
|
|
100
|
+
// ============================================================================
|
|
101
|
+
// ROUTES VIEW
|
|
102
|
+
// ============================================================================
|
|
103
|
+
export function formatRoutes(db, fromZoneId) {
|
|
104
|
+
const fromZone = db.getZone(fromZoneId);
|
|
105
|
+
const routes = db.getRoutesFromZone(fromZoneId);
|
|
106
|
+
let output = '';
|
|
107
|
+
output += `\n┌─ ROUTES FROM ${fromZone?.name || 'Unknown'} ${'─'.repeat(40 - (fromZone?.name.length || 7))}┐\n`;
|
|
108
|
+
output += `│ Signal: — (no recent intel) │\n`;
|
|
109
|
+
output += `├──────────────────────────────────────────────────────────────────┤\n`;
|
|
110
|
+
for (const route of routes) {
|
|
111
|
+
const toZone = db.getZone(route.toZoneId);
|
|
112
|
+
const riskBar = makeRiskBar(route.baseRisk * route.chokepointRating, 10);
|
|
113
|
+
const riskPct = Math.round(route.baseRisk * route.chokepointRating * 100);
|
|
114
|
+
const riskLabel = riskPct < 10 ? 'SAFE' : riskPct < 25 ? 'MODERATE' : riskPct < 50 ? 'DANGEROUS' : 'AVOID';
|
|
115
|
+
output += `│ → ${(toZone?.name || 'Unknown').padEnd(30)} │\n`;
|
|
116
|
+
output += `│ Distance: ${route.distance} ticks │ Capacity: ${route.capacity.toString().padEnd(4)} │ Risk: ${riskBar} ${riskPct.toString().padStart(2)}% │\n`;
|
|
117
|
+
if (route.chokepointRating > 1) {
|
|
118
|
+
output += `│ ⚠ Chokepoint: +${Math.round((route.chokepointRating - 1) * 100)}% intercept modifier │\n`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
output += `├──────────────────────────────────────────────────────────────────┤\n`;
|
|
122
|
+
output += `│ Legend: ░ <10% ▒ 10-30% ▓ 30-50% █ >50% │\n`;
|
|
123
|
+
output += `└──────────────────────────────────────────────────────────────────┘\n`;
|
|
124
|
+
return output;
|
|
125
|
+
}
|
|
126
|
+
// ============================================================================
|
|
127
|
+
// MARKET VIEW
|
|
128
|
+
// ============================================================================
|
|
129
|
+
export function formatMarket(db, zoneId, resource) {
|
|
130
|
+
const zone = db.getZone(zoneId);
|
|
131
|
+
const orders = db.getOrdersForZone(zoneId, resource);
|
|
132
|
+
const buys = orders.filter(o => o.side === 'buy').sort((a, b) => b.price - a.price);
|
|
133
|
+
const sells = orders.filter(o => o.side === 'sell').sort((a, b) => a.price - b.price);
|
|
134
|
+
let output = '';
|
|
135
|
+
output += `\n┌─ MARKET: ${zone?.name || 'Unknown'} ${resource ? `(${resource})` : ''} ${'─'.repeat(35)}┐\n`;
|
|
136
|
+
output += `│ Market Depth: ${zone?.marketDepth.toFixed(1)}x │\n`;
|
|
137
|
+
output += `├──────────────────────────────────────────────────────────────────┤\n`;
|
|
138
|
+
if (resource) {
|
|
139
|
+
// Show order book for specific resource
|
|
140
|
+
output += `│ BUY ORDERS │ SELL ORDERS │\n`;
|
|
141
|
+
output += `│ Price Qty │ Price Qty │\n`;
|
|
142
|
+
output += `├─────────────────────────────────────┼─────────────────────────────┤\n`;
|
|
143
|
+
const maxRows = Math.max(buys.length, sells.length, 1);
|
|
144
|
+
for (let i = 0; i < Math.min(maxRows, 8); i++) {
|
|
145
|
+
const buy = buys[i];
|
|
146
|
+
const sell = sells[i];
|
|
147
|
+
const buyStr = buy ? `${buy.price.toString().padStart(6)} cr ${buy.quantity.toString().padStart(6)}` : ''.padEnd(16);
|
|
148
|
+
const sellStr = sell ? `${sell.price.toString().padStart(6)} cr ${sell.quantity.toString().padStart(6)}` : ''.padEnd(16);
|
|
149
|
+
output += `│ ${buyStr.padEnd(35)} │ ${sellStr.padEnd(27)} │\n`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
// Show summary of all resources
|
|
154
|
+
output += `│ Resource Best Bid Best Ask Spread │\n`;
|
|
155
|
+
output += `├──────────────────────────────────────────────────────────────────┤\n`;
|
|
156
|
+
const resources = ['ore', 'fuel', 'grain', 'fiber', 'metal', 'chemicals', 'rations', 'textiles', 'ammo', 'medkits', 'parts', 'comms'];
|
|
157
|
+
for (const res of resources) {
|
|
158
|
+
const resOrders = orders.filter(o => o.resource === res);
|
|
159
|
+
const resBuys = resOrders.filter(o => o.side === 'buy');
|
|
160
|
+
const resSells = resOrders.filter(o => o.side === 'sell');
|
|
161
|
+
const bestBid = resBuys.length > 0 ? Math.max(...resBuys.map(o => o.price)) : null;
|
|
162
|
+
const bestAsk = resSells.length > 0 ? Math.min(...resSells.map(o => o.price)) : null;
|
|
163
|
+
const spread = bestBid && bestAsk ? bestAsk - bestBid : null;
|
|
164
|
+
const bidStr = bestBid ? `${bestBid} cr` : '—';
|
|
165
|
+
const askStr = bestAsk ? `${bestAsk} cr` : '—';
|
|
166
|
+
const spreadStr = spread !== null ? `${spread} cr` : '—';
|
|
167
|
+
output += `│ ${res.padEnd(14)} ${bidStr.padStart(10)} ${askStr.padStart(10)} ${spreadStr.padStart(10)} │\n`;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
output += `└──────────────────────────────────────────────────────────────────┘\n`;
|
|
171
|
+
return output;
|
|
172
|
+
}
|
|
173
|
+
// ============================================================================
|
|
174
|
+
// SHIPMENTS VIEW
|
|
175
|
+
// ============================================================================
|
|
176
|
+
export function formatShipments(shipments) {
|
|
177
|
+
let output = '';
|
|
178
|
+
output += `\n┌─ YOUR SHIPMENTS ${'─'.repeat(48)}┐\n`;
|
|
179
|
+
if (shipments.length === 0) {
|
|
180
|
+
output += `│ No active shipments │\n`;
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
output += `│ ID Type Status ETA Cargo │\n`;
|
|
184
|
+
output += `├──────────────────────────────────────────────────────────────────┤\n`;
|
|
185
|
+
for (const s of shipments) {
|
|
186
|
+
const id = s.id.slice(0, 8);
|
|
187
|
+
const cargoStr = Object.entries(s.cargo)
|
|
188
|
+
.filter(([_, v]) => v && v > 0)
|
|
189
|
+
.map(([k, v]) => `${k}:${v}`)
|
|
190
|
+
.join(', ')
|
|
191
|
+
.slice(0, 20);
|
|
192
|
+
output += `│ ${id} ${s.type.padEnd(9)} ${s.status.padEnd(12)} ${s.ticksToNextZone.toString().padEnd(4)}t ${cargoStr.padEnd(20)} │\n`;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
output += `└──────────────────────────────────────────────────────────────────┘\n`;
|
|
196
|
+
return output;
|
|
197
|
+
}
|
|
198
|
+
// ============================================================================
|
|
199
|
+
// EVENTS VIEW
|
|
200
|
+
// ============================================================================
|
|
201
|
+
export function formatEvents(events) {
|
|
202
|
+
let output = '';
|
|
203
|
+
output += `\n┌─ RECENT EVENTS ${'─'.repeat(49)}┐\n`;
|
|
204
|
+
if (events.length === 0) {
|
|
205
|
+
output += `│ No recent events │\n`;
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
for (const e of events) {
|
|
209
|
+
const line = formatEventLine(e);
|
|
210
|
+
output += `│ ${line.padEnd(63)} │\n`;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
output += `└──────────────────────────────────────────────────────────────────┘\n`;
|
|
214
|
+
return output;
|
|
215
|
+
}
|
|
216
|
+
// ============================================================================
|
|
217
|
+
// HELP
|
|
218
|
+
// ============================================================================
|
|
219
|
+
export function formatHelp() {
|
|
220
|
+
return `
|
|
221
|
+
╔══════════════════════════════════════════════════════════════════╗
|
|
222
|
+
║ BURNRATE ║
|
|
223
|
+
║ A logistics war MMO for Claude Code ║
|
|
224
|
+
║ The front doesn't feed itself. ║
|
|
225
|
+
╠══════════════════════════════════════════════════════════════════╣
|
|
226
|
+
║ GETTING STARTED ║
|
|
227
|
+
║ burnrate join <name> Create a new character ║
|
|
228
|
+
║ burnrate view See the world and your status ║
|
|
229
|
+
║ burnrate status See your inventory and stats ║
|
|
230
|
+
╠══════════════════════════════════════════════════════════════════╣
|
|
231
|
+
║ VIEWING ║
|
|
232
|
+
║ burnrate view zone <name> Details on a specific zone ║
|
|
233
|
+
║ burnrate view routes Routes from your location ║
|
|
234
|
+
║ burnrate view market [res] Market orders (optional: filter) ║
|
|
235
|
+
║ burnrate view shipments Your active shipments ║
|
|
236
|
+
║ burnrate view events [n] Recent events (default: 20) ║
|
|
237
|
+
╠══════════════════════════════════════════════════════════════════╣
|
|
238
|
+
║ TRADING ║
|
|
239
|
+
║ burnrate buy <res> <qty> [--limit <price>] ║
|
|
240
|
+
║ burnrate sell <res> <qty> [--limit <price>] ║
|
|
241
|
+
╠══════════════════════════════════════════════════════════════════╣
|
|
242
|
+
║ SHIPPING ║
|
|
243
|
+
║ burnrate ship --from <zone> --to <zone> --cargo "res:qty,..." ║
|
|
244
|
+
║ Options: --type courier|freight|convoy (default: courier) ║
|
|
245
|
+
╠══════════════════════════════════════════════════════════════════╣
|
|
246
|
+
║ INTEL ║
|
|
247
|
+
║ burnrate scan <zone> Gather intel on a zone ║
|
|
248
|
+
╠══════════════════════════════════════════════════════════════════╣
|
|
249
|
+
║ COMBAT ║
|
|
250
|
+
║ burnrate hire <type> [--count n] Hire escort or raider units ║
|
|
251
|
+
╠══════════════════════════════════════════════════════════════════╣
|
|
252
|
+
║ SUPPLY ║
|
|
253
|
+
║ burnrate supply <amount> Deposit SU to current zone ║
|
|
254
|
+
╠══════════════════════════════════════════════════════════════════╣
|
|
255
|
+
║ Supply Units (SU) require: 2 rations + 1 fuel + 1 parts + 1 ammo ║
|
|
256
|
+
║ Controlled zones burn SU each tick. No supply = zone collapses. ║
|
|
257
|
+
╚══════════════════════════════════════════════════════════════════╝
|
|
258
|
+
`;
|
|
259
|
+
}
|
|
260
|
+
// ============================================================================
|
|
261
|
+
// HELPERS
|
|
262
|
+
// ============================================================================
|
|
263
|
+
function makeSupplyBar(level, width) {
|
|
264
|
+
const filled = Math.min(width, Math.round((level / 100) * width));
|
|
265
|
+
const empty = width - filled;
|
|
266
|
+
return '█'.repeat(filled) + '░'.repeat(empty);
|
|
267
|
+
}
|
|
268
|
+
function makeRiskBar(risk, width) {
|
|
269
|
+
const normalizedRisk = Math.min(1, risk);
|
|
270
|
+
const filled = Math.round(normalizedRisk * width);
|
|
271
|
+
const empty = width - filled;
|
|
272
|
+
let bar = '';
|
|
273
|
+
for (let i = 0; i < filled; i++) {
|
|
274
|
+
const pos = i / width;
|
|
275
|
+
if (pos < 0.3)
|
|
276
|
+
bar += '▒';
|
|
277
|
+
else if (pos < 0.5)
|
|
278
|
+
bar += '▓';
|
|
279
|
+
else
|
|
280
|
+
bar += '█';
|
|
281
|
+
}
|
|
282
|
+
bar += '░'.repeat(empty);
|
|
283
|
+
return bar;
|
|
284
|
+
}
|
|
285
|
+
function formatEventAlert(event, db) {
|
|
286
|
+
switch (event.type) {
|
|
287
|
+
case 'shipment_arrived':
|
|
288
|
+
return `✓ Shipment arrived at ${event.data.destination}`;
|
|
289
|
+
case 'shipment_intercepted':
|
|
290
|
+
return `⚠ Shipment intercepted! Lost cargo.`;
|
|
291
|
+
case 'zone_state_changed':
|
|
292
|
+
return `⚠ ${event.data.zoneName} now ${event.data.newState.toUpperCase()}`;
|
|
293
|
+
case 'trade_executed':
|
|
294
|
+
return `✓ Trade: ${event.data.quantity} ${event.data.resource} at ${event.data.price} cr`;
|
|
295
|
+
default:
|
|
296
|
+
return `• ${event.type.replace(/_/g, ' ')}`;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function formatEventLine(event) {
|
|
300
|
+
const tick = `[${event.tick}]`.padEnd(8);
|
|
301
|
+
switch (event.type) {
|
|
302
|
+
case 'tick':
|
|
303
|
+
return `${tick} Tick processed`;
|
|
304
|
+
case 'shipment_created':
|
|
305
|
+
return `${tick} Shipment created: ${event.data.from} → ${event.data.to}`;
|
|
306
|
+
case 'shipment_arrived':
|
|
307
|
+
return `${tick} Shipment arrived at ${event.data.destination}`;
|
|
308
|
+
case 'shipment_intercepted':
|
|
309
|
+
return `${tick} ⚠ Shipment intercepted!`;
|
|
310
|
+
case 'trade_executed':
|
|
311
|
+
return `${tick} Trade: ${event.data.quantity} ${event.data.resource} @ ${event.data.price}`;
|
|
312
|
+
case 'zone_supplied':
|
|
313
|
+
return `${tick} Supplied ${event.data.zoneName || event.data.zoneId}`;
|
|
314
|
+
case 'zone_state_changed':
|
|
315
|
+
return `${tick} ${event.data.zoneName}: ${event.data.previousState} → ${event.data.newState}`;
|
|
316
|
+
default:
|
|
317
|
+
return `${tick} ${event.type.replace(/_/g, ' ')}`;
|
|
318
|
+
}
|
|
319
|
+
}
|