From 9ea0174d86c04d1df9ffeea36f3e22290b0d537c Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 15 Oct 2026 16:42:07 +0200 Subject: [PATCH] feat(workers): clarify worker roles with live rate badges, fix live button unlock states, and enforce server achievement sync --- backend/engine.py | 17 +++++++++++++++++ frontend/js/game.js | 30 ++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/backend/engine.py b/backend/engine.py index 819fb45..e098db0 100644 --- a/backend/engine.py +++ b/backend/engine.py @@ -116,8 +116,25 @@ def process_offline_and_ticks(state: dict) -> dict: # Heat decay state['heat'] = max(0.0, state.get('heat', 0.0) - (0.5 * delta)) + check_server_achievements(state) + return sanitize_state(state) +def check_server_achievements(state: dict): + ach = state.setdefault('achievements', {}) + if state.get('totalEarned', 0) > 0: + ach['first_hit'] = True + if state.get('drugs', {}).get('weed', {}).get('workers', 0) >= 10: + ach['grower_op'] = True + if state.get('drugs', {}).get('meth', {}).get('unlocked'): + ach['blue_sky'] = True + if state.get('drugs', {}).get('cocaine', {}).get('unlocked'): + ach['cartel_tier'] = True + if state.get('cash', 0) >= 1000000: + ach['millionaire'] = True + if state.get('cleanBonds', 0) >= 1: + ach['laundered'] = True + def apply_actions(state: dict, actions: list) -> dict: """Applies and authorizes player actions sequentially against server authority.""" prod_m, sell_m, raw_m, dealer_m = get_multipliers(state) diff --git a/frontend/js/game.js b/frontend/js/game.js index 392a74c..0928d63 100644 --- a/frontend/js/game.js +++ b/frontend/js/game.js @@ -186,7 +186,7 @@ function renderLabView() {
- @@ -205,18 +205,27 @@ function renderLabView() {
${d.name}
-
${d.workerName}s: ${data.workers}
+
+ ${d.workerName}s: ${data.workers} + + (${fmtNum(data.workers * d.produceRate * getProductionMultiplier())}g/s) + +
+
+ Auto-Refines
+ 1 Raw ➔ 1g +
- ${d.rawName} + RAW SUPPLY (${d.rawName.toUpperCase()}) ${fmtNum(data.raw)}
- ${d.productName} + READY PRODUCT (${d.productName.toUpperCase()}) ${fmtNum(data.product)}g
@@ -780,6 +789,16 @@ function updateLiveInventory() { const streetProdEl = document.getElementById(`val-prod-street-${d.id}`); if (streetProdEl) streetProdEl.textContent = `${fmtNum(drugData.product)}g`; + const workerCountEl = document.getElementById(`val-worker-count-${d.id}`); + if (workerCountEl) workerCountEl.textContent = `${d.workerName}s: ${drugData.workers}`; + + const autoProdEl = document.getElementById(`val-auto-prod-${d.id}`); + if (autoProdEl) { + const currentSpeed = drugData.workers * d.produceRate * getProductionMultiplier(); + autoProdEl.textContent = `(${fmtNum(currentSpeed)}g/s)`; + autoProdEl.style.color = (drugData.workers > 0 && drugData.raw > 0) ? '#4ade80' : '#8b949e'; + } + // Live button state updates (enable/disable as resources change) const rawCost = Math.floor(d.rawCost * rawCostMulti); const workerCost = getWorkerCost(d.id); @@ -802,6 +821,9 @@ function updateLiveInventory() { const btnSell10x = document.getElementById(`btn-sell-10x-${d.id}`); if (btnSell10x) btnSell10x.disabled = (drugData.product < 10); + + const btnUnlock = document.getElementById(`btn-unlock-${d.id}`); + if (btnUnlock) btnUnlock.disabled = (G.cash < getUnlockCost(d.id)); }); if (dom.quickStash) {