feat(workers): clarify worker roles with live rate badges, fix live button unlock states, and enforce server achievement sync

This commit is contained in:
Christian 2026-10-15 16:42:07 +02:00
parent 2fdc030e9d
commit 9ea0174d86
2 changed files with 43 additions and 4 deletions

View file

@ -116,8 +116,25 @@ def process_offline_and_ticks(state: dict) -> dict:
# Heat decay # Heat decay
state['heat'] = max(0.0, state.get('heat', 0.0) - (0.5 * delta)) state['heat'] = max(0.0, state.get('heat', 0.0) - (0.5 * delta))
check_server_achievements(state)
return sanitize_state(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: def apply_actions(state: dict, actions: list) -> dict:
"""Applies and authorizes player actions sequentially against server authority.""" """Applies and authorizes player actions sequentially against server authority."""
prod_m, sell_m, raw_m, dealer_m = get_multipliers(state) prod_m, sell_m, raw_m, dealer_m = get_multipliers(state)

View file

@ -186,7 +186,7 @@ function renderLabView() {
</div> </div>
</div> </div>
<div class="unlock-overlay-box"> <div class="unlock-overlay-box">
<button class="btn-unlock" onclick="unlockDrug('${d.id}')" ${G.cash < unlockCost ? 'disabled' : ''}> <button id="btn-unlock-${d.id}" class="btn-unlock" onclick="unlockDrug('${d.id}')" ${G.cash < unlockCost ? 'disabled' : ''}>
<iconify-icon icon="lucide:lock-open"></iconify-icon> <iconify-icon icon="lucide:lock-open"></iconify-icon>
Acquire Formula ($${unlockCost.toLocaleString()}) Acquire Formula ($${unlockCost.toLocaleString()})
</button> </button>
@ -205,18 +205,27 @@ function renderLabView() {
</div> </div>
<div> <div>
<div class="card-title">${d.name}</div> <div class="card-title">${d.name}</div>
<div style="font-size: 0.68rem; color: var(--text-secondary); font-family: var(--font-mono);">${d.workerName}s: ${data.workers}</div> <div style="font-size: 0.68rem; color: var(--text-secondary); font-family: var(--font-mono);">
<span id="val-worker-count-${d.id}">${d.workerName}s: ${data.workers}</span>
<span id="val-auto-prod-${d.id}" style="color: ${data.workers > 0 ? '#4ade80' : '#8b949e'}; margin-left: 6px;">
(${fmtNum(data.workers * d.produceRate * getProductionMultiplier())}g/s)
</span>
</div> </div>
</div> </div>
</div> </div>
<div style="font-family: var(--font-mono); font-size: 0.65rem; color: #8b949e; text-align: right; background: #0c1117; padding: 4px 8px; border-radius: 6px; border: 1px solid #1f2937;">
<span>Auto-Refines</span><br>
<span style="color: #4ade80;">1 Raw ➔ 1g</span>
</div>
</div>
<div class="card-stats-row"> <div class="card-stats-row">
<div class="stat-item"> <div class="stat-item">
<span class="stat-label">${d.rawName}</span> <span class="stat-label">RAW SUPPLY (${d.rawName.toUpperCase()})</span>
<span class="stat-value" id="val-raw-${d.id}">${fmtNum(data.raw)}</span> <span class="stat-value" id="val-raw-${d.id}">${fmtNum(data.raw)}</span>
</div> </div>
<div class="stat-item"> <div class="stat-item">
<span class="stat-label">${d.productName}</span> <span class="stat-label">READY PRODUCT (${d.productName.toUpperCase()})</span>
<span class="stat-value" id="val-prod-${d.id}">${fmtNum(data.product)}g</span> <span class="stat-value" id="val-prod-${d.id}">${fmtNum(data.product)}g</span>
</div> </div>
</div> </div>
@ -780,6 +789,16 @@ function updateLiveInventory() {
const streetProdEl = document.getElementById(`val-prod-street-${d.id}`); const streetProdEl = document.getElementById(`val-prod-street-${d.id}`);
if (streetProdEl) streetProdEl.textContent = `${fmtNum(drugData.product)}g`; 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) // Live button state updates (enable/disable as resources change)
const rawCost = Math.floor(d.rawCost * rawCostMulti); const rawCost = Math.floor(d.rawCost * rawCostMulti);
const workerCost = getWorkerCost(d.id); const workerCost = getWorkerCost(d.id);
@ -802,6 +821,9 @@ function updateLiveInventory() {
const btnSell10x = document.getElementById(`btn-sell-10x-${d.id}`); const btnSell10x = document.getElementById(`btn-sell-10x-${d.id}`);
if (btnSell10x) btnSell10x.disabled = (drugData.product < 10); 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) { if (dom.quickStash) {