diff --git a/backend/engine.py b/backend/engine.py index e098db0..1b07cbb 100644 --- a/backend/engine.py +++ b/backend/engine.py @@ -1,18 +1,19 @@ import math import time +# Drug Catalog mirroring config.js # Drug Catalog mirroring config.js DRUG_CONFIGS = { - 'weed': {'rawCost': 10, 'sellPrice': 16, 'produceRate': 1.0, 'sellRate': 1.0, 'workerCost': 100, 'dealerCost': 150, 'unlockCost': 0}, - 'shrooms': {'rawCost': 35, 'sellPrice': 65, 'produceRate': 0.8, 'sellRate': 0.8, 'workerCost': 450, 'dealerCost': 650, 'unlockCost': 500}, - 'adderall': {'rawCost': 120, 'sellPrice': 240, 'produceRate': 0.6, 'sellRate': 0.6, 'workerCost': 2000, 'dealerCost': 3000, 'unlockCost': 2500}, - 'meth': {'rawCost': 400, 'sellPrice': 950, 'produceRate': 0.5, 'sellRate': 0.5, 'workerCost': 8000, 'dealerCost': 12000, 'unlockCost': 10000}, - 'mdma': {'rawCost': 1200, 'sellPrice': 3200, 'produceRate': 0.4, 'sellRate': 0.4, 'workerCost': 28000, 'dealerCost': 40000, 'unlockCost': 35000}, - 'ketamine': {'rawCost': 3800, 'sellPrice': 11000, 'produceRate': 0.35, 'sellRate': 0.35, 'workerCost': 95000, 'dealerCost': 140000, 'unlockCost': 120000}, - 'cocaine': {'rawCost': 12000, 'sellPrice': 38000, 'produceRate': 0.3, 'sellRate': 0.3, 'workerCost': 320000, 'dealerCost': 480000, 'unlockCost': 400000}, - 'crack': {'rawCost': 35000, 'sellPrice': 115000, 'produceRate': 0.25, 'sellRate': 0.25, 'workerCost': 900000, 'dealerCost': 1400000, 'unlockCost': 1200000}, - 'heroin': {'rawCost': 110000, 'sellPrice': 420000, 'produceRate': 0.2, 'sellRate': 0.2, 'workerCost': 3500000, 'dealerCost': 5500000, 'unlockCost': 4500000}, - 'fentanyl': {'rawCost': 450000, 'sellPrice': 2100000, 'produceRate': 0.15, 'sellRate': 0.15, 'workerCost': 15000000, 'dealerCost': 24000000, 'unlockCost': 20000000} + 'weed': {'rawCost': 10, 'sellPrice': 16, 'produceRate': 1.0, 'sellRate': 1.0, 'supplyRate': 1.0, 'supplierCost': 75, 'workerCost': 100, 'dealerCost': 150, 'unlockCost': 0}, + 'shrooms': {'rawCost': 35, 'sellPrice': 65, 'produceRate': 0.8, 'sellRate': 0.8, 'supplyRate': 0.8, 'supplierCost': 300, 'workerCost': 450, 'dealerCost': 650, 'unlockCost': 500}, + 'adderall': {'rawCost': 120, 'sellPrice': 240, 'produceRate': 0.6, 'sellRate': 0.6, 'supplyRate': 0.6, 'supplierCost': 1500, 'workerCost': 2000, 'dealerCost': 3000, 'unlockCost': 2500}, + 'meth': {'rawCost': 400, 'sellPrice': 950, 'produceRate': 0.5, 'sellRate': 0.5, 'supplyRate': 0.5, 'supplierCost': 6000, 'workerCost': 8000, 'dealerCost': 12000, 'unlockCost': 10000}, + 'mdma': {'rawCost': 1200, 'sellPrice': 3200, 'produceRate': 0.4, 'sellRate': 0.4, 'supplyRate': 0.4, 'supplierCost': 20000, 'workerCost': 28000, 'dealerCost': 40000, 'unlockCost': 35000}, + 'ketamine': {'rawCost': 3800, 'sellPrice': 11000, 'produceRate': 0.35, 'sellRate': 0.35, 'supplyRate': 0.35, 'supplierCost': 75000, 'workerCost': 95000, 'dealerCost': 140000, 'unlockCost': 120000}, + 'cocaine': {'rawCost': 12000, 'sellPrice': 38000, 'produceRate': 0.3, 'sellRate': 0.3, 'supplyRate': 0.3, 'supplierCost': 250000, 'workerCost': 320000, 'dealerCost': 480000, 'unlockCost': 400000}, + 'crack': {'rawCost': 35000, 'sellPrice': 115000, 'produceRate': 0.25, 'sellRate': 0.25, 'supplyRate': 0.25, 'supplierCost': 700000, 'workerCost': 900000, 'dealerCost': 1400000, 'unlockCost': 1200000}, + 'heroin': {'rawCost': 110000, 'sellPrice': 420000, 'produceRate': 0.2, 'sellRate': 0.2, 'supplyRate': 0.2, 'supplierCost': 2800000, 'workerCost': 3500000, 'dealerCost': 5500000, 'unlockCost': 4500000}, + 'fentanyl': {'rawCost': 450000, 'sellPrice': 2100000, 'produceRate': 0.15, 'sellRate': 0.15, 'supplyRate': 0.15, 'supplierCost': 12000000, 'workerCost': 15000000, 'dealerCost': 24000000, 'unlockCost': 20000000} } SYNTHESIS_RECIPES = { @@ -50,6 +51,7 @@ def get_initial_state() -> dict: 'unlocked': (drug_id == 'weed'), 'raw': 0.0, 'product': 0.0, + 'suppliers': 0, 'workers': 0, 'dealers': 0 } @@ -93,7 +95,13 @@ def process_offline_and_ticks(state: dict) -> dict: if not cfg or not data.get('unlocked'): continue - # Workers production + # Suppliers automatic raw cultivation / procurement + suppliers = data.get('suppliers', 0) + if suppliers > 0: + supply_speed = suppliers * cfg.get('supplyRate', 1.0) * prod_m + data['raw'] = data.get('raw', 0.0) + (supply_speed * delta) + + # Workers production (Refining raw -> product) workers = data.get('workers', 0) raw = data.get('raw', 0.0) if workers > 0 and raw > 0: @@ -102,7 +110,7 @@ def process_offline_and_ticks(state: dict) -> dict: data['raw'] = max(0.0, raw - actual_prod) data['product'] = data.get('product', 0.0) + actual_prod - # Dealers selling + # Dealers selling (Product -> Cash) dealers = data.get('dealers', 0) product = data.get('product', 0.0) if dealers > 0 and product > 0: @@ -177,6 +185,14 @@ def apply_actions(state: dict, actions: list) -> dict: state['totalEarned'] += profit state['heat'] = min(100.0, state.get('heat', 0.0) + (0.8 * actual)) + elif action_type == 'hire_supplier' and drug_id in DRUG_CONFIGS: + cfg = DRUG_CONFIGS[drug_id] + count = state['drugs'][drug_id].get('suppliers', 0) + cost = math.floor(cfg.get('supplierCost', 75) * math.pow(1.15, count)) + if state['cash'] >= cost: + state['cash'] -= cost + state['drugs'][drug_id]['suppliers'] = count + 1 + elif action_type == 'hire_worker' and drug_id in DRUG_CONFIGS: cfg = DRUG_CONFIGS[drug_id] count = state['drugs'][drug_id].get('workers', 0) @@ -244,6 +260,7 @@ def apply_actions(state: dict, actions: list) -> dict: d_data['unlocked'] = (d_id == 'weed') d_data['raw'] = 0.0 d_data['product'] = 0.0 + d_data['suppliers'] = 0 d_data['workers'] = 0 d_data['dealers'] = 0 @@ -259,6 +276,7 @@ def sanitize_state(state: dict) -> dict: for d_id, data in state.get('drugs', {}).items(): data['raw'] = clean_round(data.get('raw', 0.0)) data['product'] = clean_round(data.get('product', 0.0)) + data['suppliers'] = int(data.get('suppliers', 0)) data['workers'] = int(data.get('workers', 0)) data['dealers'] = int(data.get('dealers', 0)) diff --git a/frontend/js/config.js b/frontend/js/config.js index 8ab7eaa..b53cb50 100644 --- a/frontend/js/config.js +++ b/frontend/js/config.js @@ -11,6 +11,9 @@ const DRUG_CATALOG = [ rawCost: 10, sellPrice: 16, unlockCost: 0, + supplierName: 'Seed Courier', + supplierCost: 75, + supplyRate: 1.0, workerName: 'Grower', dealerName: 'Corner Pusher', workerCost: 100, @@ -30,6 +33,9 @@ const DRUG_CATALOG = [ rawCost: 35, sellPrice: 65, unlockCost: 500, + supplierName: 'Spore Forager', + supplierCost: 300, + supplyRate: 0.8, workerName: 'Mycologist', dealerName: 'Festival Plug', workerCost: 450, @@ -49,6 +55,9 @@ const DRUG_CATALOG = [ rawCost: 120, sellPrice: 240, unlockCost: 2500, + supplierName: 'Pharmacy Diverter', + supplierCost: 1500, + supplyRate: 0.6, workerName: 'Pharma Tech', dealerName: 'Campus Runner', workerCost: 2000, @@ -68,6 +77,9 @@ const DRUG_CATALOG = [ rawCost: 400, sellPrice: 950, unlockCost: 10000, + supplierName: 'Chemical Smuggler', + supplierCost: 6000, + supplyRate: 0.5, workerName: 'Lab Cook', dealerName: 'Trailer Boss', workerCost: 8000, @@ -87,6 +99,9 @@ const DRUG_CATALOG = [ rawCost: 1200, sellPrice: 3200, unlockCost: 35000, + supplierName: 'Safrole Importer', + supplierCost: 20000, + supplyRate: 0.4, workerName: 'Rave Chemist', dealerName: 'Club Promoter', workerCost: 28000, @@ -106,6 +121,9 @@ const DRUG_CATALOG = [ rawCost: 3800, sellPrice: 11000, unlockCost: 120000, + supplierName: 'Vet Clinic Insider', + supplierCost: 75000, + supplyRate: 0.35, workerName: 'Vet Contact', dealerName: 'Afterparty Plug', workerCost: 95000, @@ -125,6 +143,9 @@ const DRUG_CATALOG = [ rawCost: 12000, sellPrice: 38000, unlockCost: 400000, + supplierName: 'Jungle Smuggler', + supplierCost: 250000, + supplyRate: 0.3, workerName: 'Cartel Chemist', dealerName: 'VIP Broker', workerCost: 320000, @@ -144,6 +165,9 @@ const DRUG_CATALOG = [ rawCost: 35000, sellPrice: 115000, unlockCost: 1200000, + supplierName: 'Base Sourcing Crew', + supplierCost: 700000, + supplyRate: 0.25, workerName: 'Trap Master', dealerName: 'Block Captain', workerCost: 900000, @@ -163,6 +187,9 @@ const DRUG_CATALOG = [ rawCost: 110000, sellPrice: 420000, unlockCost: 4500000, + supplierName: 'Poppy Harvester', + supplierCost: 2800000, + supplyRate: 0.2, workerName: 'Poppy Refiner', dealerName: 'Network Distributor', workerCost: 3500000, @@ -182,6 +209,9 @@ const DRUG_CATALOG = [ rawCost: 450000, sellPrice: 2100000, unlockCost: 20000000, + supplierName: 'Cartel Procurement Agent', + supplierCost: 12000000, + supplyRate: 0.15, workerName: 'Hazmat Synthesizer', dealerName: 'Ghost Syndicate', workerCost: 15000000, diff --git a/frontend/js/game.js b/frontend/js/game.js index 0928d63..8829483 100644 --- a/frontend/js/game.js +++ b/frontend/js/game.js @@ -19,6 +19,7 @@ DRUG_CATALOG.forEach(d => { unlocked: d.unlockedDefault, raw: 0.0, product: 0.0, + suppliers: 0, workers: 0, dealers: 0 }; @@ -106,6 +107,12 @@ function getDealerSpeedMultiplier() { } // Cost scaling formulas +function getSupplierCost(drugId) { + const config = DRUG_CATALOG.find(d => d.id === drugId); + const count = (G.drugs[drugId] && G.drugs[drugId].suppliers) || 0; + return Math.floor((config.supplierCost || 75) * Math.pow(1.15, count)); +} + function getWorkerCost(drugId) { const config = DRUG_CATALOG.find(d => d.id === drugId); const count = G.drugs[drugId].workers; @@ -166,8 +173,9 @@ function renderLabView() { `; DRUG_CATALOG.forEach(d => { - const data = G.drugs[d.id] || { raw: 0, product: 0, workers: 0, dealers: 0, unlocked: false }; + const data = G.drugs[d.id] || { raw: 0, product: 0, suppliers: 0, workers: 0, dealers: 0, unlocked: false }; const rawCost = Math.floor(d.rawCost * getRawCostMultiplier()); + const supplierCost = getSupplierCost(d.id); const workerCost = getWorkerCost(d.id); if (!data.unlocked) { @@ -196,6 +204,11 @@ function renderLabView() { return; } + const suppliers = data.suppliers || 0; + const supplyRate = d.supplyRate || 1.0; + const autoSupplySpeed = suppliers * supplyRate * getProductionMultiplier(); + const autoProdSpeed = data.workers * d.produceRate * getProductionMultiplier(); + html += `
@@ -205,10 +218,15 @@ function renderLabView() {
${d.name}
-
+
+ ${d.supplierName}s: ${suppliers} + + (+${fmtNum(autoSupplySpeed)} r/s) + + • ${d.workerName}s: ${data.workers} - - (${fmtNum(data.workers * d.produceRate * getProductionMultiplier())}g/s) + + (${fmtNum(autoProdSpeed)}g/s)
@@ -246,13 +264,14 @@ function renderLabView() {
+ -
`; @@ -516,6 +535,19 @@ function sellManual(drugId, amount = 1) { } } +function hireSupplier(drugId) { + const cost = getSupplierCost(drugId); + if (G.cash >= cost) { + G.cash -= cost; + if (!G.drugs[drugId].suppliers) G.drugs[drugId].suppliers = 0; + G.drugs[drugId].suppliers += 1; + queueAction({ type: 'hire_supplier', drug_id: drugId }); + checkAchievements(); + renderViewport(); + updateLiveInventory(); + } +} + function hireWorker(drugId) { const cost = getWorkerCost(drugId); if (G.cash >= cost) { @@ -596,6 +628,7 @@ function launderMoney() { unlocked: d.unlockedDefault, raw: 0.0, product: 0.0, + suppliers: 0, workers: 0, dealers: 0 }; @@ -702,6 +735,14 @@ function clientTick(deltaSec) { const data = G.drugs[d.id]; if (!data || !data.unlocked) return; + // Auto Supply + const suppliers = data.suppliers || 0; + if (suppliers > 0) { + const supplyRate = d.supplyRate || 1.0; + const toSupply = suppliers * supplyRate * prodMulti * deltaSec; + data.raw = data.raw + toSupply; + } + // Auto Production if (data.workers > 0 && data.raw > 0) { const prodPerSec = data.workers * d.produceRate * prodMulti; @@ -761,6 +802,7 @@ function updateLiveInventory() { dom.heatBar.style.width = `${Math.min(100, Math.round(G.heat))}%`; const rawCostMulti = getRawCostMultiplier(); + const prodMulti = getProductionMultiplier(); let stashChipsHtml = ''; // Surgically update raw, product, buttons, and stash chips @@ -789,24 +831,38 @@ function updateLiveInventory() { const streetProdEl = document.getElementById(`val-prod-street-${d.id}`); if (streetProdEl) streetProdEl.textContent = `${fmtNum(drugData.product)}g`; + const supplierCountEl = document.getElementById(`val-supplier-count-${d.id}`); + if (supplierCountEl) supplierCountEl.textContent = `${d.supplierName}s: ${drugData.suppliers || 0}`; + + const autoSupplyEl = document.getElementById(`val-auto-supply-${d.id}`); + if (autoSupplyEl) { + const currentSupplySpeed = (drugData.suppliers || 0) * (d.supplyRate || 1.0) * prodMulti; + autoSupplyEl.textContent = `(+${fmtNum(currentSupplySpeed)} r/s)`; + autoSupplyEl.style.color = (drugData.suppliers || 0) > 0 ? '#38bdf8' : '#8b949e'; + } + 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(); + const currentSpeed = drugData.workers * d.produceRate * prodMulti; 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 supplierCost = getSupplierCost(d.id); const workerCost = getWorkerCost(d.id); const dealerCost = getDealerCost(d.id); const btnBuyRaw = document.getElementById(`btn-buy-raw-${d.id}`); if (btnBuyRaw) btnBuyRaw.disabled = (G.cash < rawCost); + const btnHireSupplier = document.getElementById(`btn-hire-supplier-${d.id}`); + if (btnHireSupplier) btnHireSupplier.disabled = (G.cash < supplierCost); + const btnMake = document.getElementById(`btn-make-${d.id}`); if (btnMake) btnMake.disabled = (drugData.raw < 1);