490 lines
22 KiB
Python
490 lines
22 KiB
Python
import math
|
|
import time
|
|
|
|
# Drug Catalog mirroring config.js
|
|
DRUG_CONFIGS = {
|
|
'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 = {
|
|
'calvin_klein': {'ingredients': {'cocaine': 5, 'ketamine': 5}, 'payout': 95000},
|
|
'speedball': {'ingredients': {'heroin': 3, 'cocaine': 3}, 'payout': 850000},
|
|
'candy_flip': {'ingredients': {'shrooms': 15, 'mdma': 10}, 'payout': 75000}
|
|
}
|
|
|
|
SKILL_COSTS = {
|
|
'chem_1': 500, 'chem_2': 15000, 'chem_3': 250000,
|
|
'king_1': 1000, 'king_2': 35000, 'king_3': 600000,
|
|
'smug_1': 2000, 'smug_2': 50000, 'smug_3': 1000000
|
|
}
|
|
|
|
UNIT_MILESTONES = [1, 5, 10, 25, 50, 100, 200, 300, 400, 500, 750, 1000, 1500, 2000]
|
|
|
|
OFFSHORE_PERK_COSTS = {
|
|
'swiss_vault': [1, 3, 8, 20, 50],
|
|
'auto_contracts': [2, 5, 12, 30],
|
|
'corrupt_judiciary': [2, 6, 15, 40],
|
|
'bulk_discount': [5, 15, 35, 80],
|
|
'compound_interest': [4, 10, 25, 60, 150]
|
|
}
|
|
|
|
ACHIEVEMENT_TIER_REQS = {
|
|
'dirty_empire': [10000, 250000, 5000000, 100000000, 1000000000],
|
|
'master_synthesizer': [100, 1000, 15000, 200000, 2500000],
|
|
'street_monopoly': [100, 1500, 25000, 300000, 3000000],
|
|
'supply_baron': [250, 3000, 50000, 500000],
|
|
'vault_hoarder': [50000, 500000, 10000000, 250000000],
|
|
'syndicate_army': [25, 100, 500, 2500],
|
|
'purity_legend': [5, 25, 100, 500],
|
|
'shadow_cartel': [3, 10, 30],
|
|
'swiss_kingpin': [1, 3, 10, 25],
|
|
'formula_expansion': [3, 6, 8, 10]
|
|
}
|
|
|
|
def clean_round(val: float) -> float:
|
|
"""Rounds float gracefully to 2 decimal places without floating point anomalies."""
|
|
if math.isnan(val) or math.isinf(val) or val < 0:
|
|
return 0.0
|
|
return round(float(val), 2)
|
|
|
|
def get_unit_milestone_multiplier(count: int) -> float:
|
|
"""Calculates 2x multiplier for each milestone threshold achieved."""
|
|
reached = sum(1 for m in UNIT_MILESTONES if count >= m)
|
|
return float(math.pow(2.0, reached))
|
|
|
|
def get_all_tier_milestone_multiplier(state: dict, unit_key: str) -> float:
|
|
"""Calculates 2x global set multiplier when all unlocked drugs reach a milestone."""
|
|
unlocked = [d for d in state.get('drugs', {}).values() if d.get('unlocked')]
|
|
if not unlocked:
|
|
return 1.0
|
|
min_count = min(d.get(unit_key, 0) for d in unlocked)
|
|
reached = sum(1 for m in UNIT_MILESTONES if min_count >= m)
|
|
return float(math.pow(2.0, reached))
|
|
|
|
def get_initial_state() -> dict:
|
|
state = {
|
|
'cash': 25.0,
|
|
'cleanBonds': 0,
|
|
'totalCleanBonds': 0,
|
|
'heat': 0.0,
|
|
'totalEarned': 0.0,
|
|
'stats': {
|
|
'totalProduced': 0.0,
|
|
'totalSold': 0.0,
|
|
'totalSupplied': 0.0,
|
|
'purityProcs': 0,
|
|
'raidsSurvived': 0,
|
|
'prestigeCount': 0
|
|
},
|
|
'achievements': {}, # { ach_id: claimed_tier_int }
|
|
'offshorePerks': {}, # { perk_id: level_int }
|
|
'skills': {s: False for s in SKILL_COSTS},
|
|
'synthesis': {r: 0 for r in SYNTHESIS_RECIPES},
|
|
'drugs': {},
|
|
'lastTick': time.time()
|
|
}
|
|
for drug_id, cfg in DRUG_CONFIGS.items():
|
|
state['drugs'][drug_id] = {
|
|
'unlocked': (drug_id == 'weed'),
|
|
'raw': 0.0,
|
|
'product': 0.0,
|
|
'suppliers': 0,
|
|
'workers': 0,
|
|
'dealers': 0
|
|
}
|
|
return state
|
|
|
|
def get_multipliers(state: dict):
|
|
# Total lifetime bonds provide permanent base boost
|
|
total_bonds = max(state.get('totalCleanBonds', 0), state.get('cleanBonds', 0))
|
|
skills = state.get('skills', {})
|
|
ach = state.get('achievements', {})
|
|
perks = state.get('offshorePerks', {})
|
|
|
|
# Compound interest perk raises bond sale value
|
|
compound_lvl = perks.get('compound_interest', 0)
|
|
bond_sell_rate = 0.04 + (compound_lvl * 0.02)
|
|
bond_prod_rate = 0.03
|
|
|
|
prod_m = 1.0 + (total_bonds * bond_prod_rate)
|
|
if skills.get('chem_1'): prod_m += 0.15
|
|
|
|
# Achievement bonuses for production
|
|
synth_tier = ach.get('master_synthesizer', 0)
|
|
prod_m += [0.0, 0.05, 0.10, 0.15, 0.25, 0.50][min(5, synth_tier)]
|
|
formula_tier = ach.get('formula_expansion', 0)
|
|
prod_m += [0.0, 0.05, 0.10, 0.15, 0.30][min(4, formula_tier)]
|
|
|
|
# Sales multiplier
|
|
sell_m = 1.0 + (total_bonds * bond_sell_rate)
|
|
if skills.get('king_2'): sell_m += 0.25
|
|
empire_tier = ach.get('dirty_empire', 0)
|
|
sell_m += [0.0, 0.05, 0.10, 0.15, 0.25, 0.50][min(5, empire_tier)]
|
|
|
|
# Raw material cost multiplier
|
|
raw_m = 0.85 if skills.get('chem_2') else 1.0
|
|
vault_tier = ach.get('vault_hoarder', 0)
|
|
raw_discount = [0.0, 0.05, 0.10, 0.15, 0.25][min(4, vault_tier)]
|
|
raw_m = max(0.1, raw_m - raw_discount)
|
|
|
|
# Dealer speed multiplier
|
|
dealer_m = 1.0
|
|
if skills.get('king_1'): dealer_m += 0.20
|
|
if skills.get('king_3'): dealer_m += 0.50
|
|
street_tier = ach.get('street_monopoly', 0)
|
|
dealer_m += [0.0, 0.05, 0.10, 0.15, 0.25, 0.50][min(5, street_tier)]
|
|
|
|
# Supplier speed multiplier
|
|
supply_tier = ach.get('supply_baron', 0)
|
|
supplier_m = 1.0 + [0.0, 0.05, 0.10, 0.15, 0.25][min(4, supply_tier)]
|
|
|
|
# Syndicate Army all speed multiplier
|
|
army_tier = ach.get('syndicate_army', 0)
|
|
all_unit_m = 1.0 + [0.0, 0.05, 0.10, 0.15, 0.25][min(4, army_tier)]
|
|
|
|
dealer_m *= all_unit_m
|
|
supplier_m *= all_unit_m
|
|
worker_m = all_unit_m
|
|
|
|
return prod_m, sell_m, raw_m, dealer_m, supplier_m, worker_m
|
|
|
|
def get_current_stat_value(state: dict, stat_key: str) -> float:
|
|
"""Helper to query current progress for an achievement category."""
|
|
if stat_key == 'totalEarned':
|
|
return float(state.get('totalEarned', 0.0))
|
|
elif stat_key == 'cash':
|
|
return float(state.get('cash', 0.0))
|
|
elif stat_key == 'totalPersonnel':
|
|
total = 0
|
|
for d in state.get('drugs', {}).values():
|
|
total += d.get('suppliers', 0) + d.get('workers', 0) + d.get('dealers', 0)
|
|
return float(total)
|
|
elif stat_key == 'unlockedCount':
|
|
return float(sum(1 for d in state.get('drugs', {}).values() if d.get('unlocked')))
|
|
else:
|
|
return float(state.get('stats', {}).get(stat_key, 0.0))
|
|
|
|
def validate_achievement_claim(state: dict, ach_id: str, tier: int) -> bool:
|
|
reqs = ACHIEVEMENT_TIER_REQS.get(ach_id)
|
|
if not reqs or tier < 1 or tier > len(reqs):
|
|
return False
|
|
|
|
current_claimed = state.get('achievements', {}).get(ach_id, 0)
|
|
if tier != current_claimed + 1:
|
|
return False
|
|
|
|
req_val = reqs[tier - 1]
|
|
stat_key_map = {
|
|
'dirty_empire': 'totalEarned',
|
|
'master_synthesizer': 'totalProduced',
|
|
'street_monopoly': 'totalSold',
|
|
'supply_baron': 'totalSupplied',
|
|
'vault_hoarder': 'cash',
|
|
'syndicate_army': 'totalPersonnel',
|
|
'purity_legend': 'purityProcs',
|
|
'shadow_cartel': 'raidsSurvived',
|
|
'swiss_kingpin': 'prestigeCount',
|
|
'formula_expansion': 'unlockedCount'
|
|
}
|
|
stat_key = stat_key_map.get(ach_id, 'totalEarned')
|
|
current_val = get_current_stat_value(state, stat_key)
|
|
return current_val >= req_val
|
|
|
|
def process_offline_and_ticks(state: dict) -> dict:
|
|
now = time.time()
|
|
last = state.get('lastTick', now)
|
|
delta = min(max(0.0, now - last), 86400.0) # Cap at 24h offline
|
|
state['lastTick'] = now
|
|
|
|
if delta <= 0:
|
|
return sanitize_state(state)
|
|
|
|
prod_m, sell_m, raw_m, dealer_m, supplier_m, worker_m = get_multipliers(state)
|
|
|
|
# All-tier milestone multipliers
|
|
all_tier_sup = get_all_tier_milestone_multiplier(state, 'suppliers')
|
|
all_tier_work = get_all_tier_milestone_multiplier(state, 'workers')
|
|
all_tier_deal = get_all_tier_milestone_multiplier(state, 'dealers')
|
|
|
|
stats = state.setdefault('stats', {})
|
|
|
|
for drug_id, data in state.get('drugs', {}).items():
|
|
cfg = DRUG_CONFIGS.get(drug_id)
|
|
if not cfg or not data.get('unlocked'):
|
|
continue
|
|
|
|
# Suppliers automatic raw cultivation / procurement
|
|
suppliers = data.get('suppliers', 0)
|
|
if suppliers > 0:
|
|
unit_milestone_m = get_unit_milestone_multiplier(suppliers)
|
|
supply_speed = suppliers * cfg.get('supplyRate', 1.0) * prod_m * supplier_m * unit_milestone_m * all_tier_sup
|
|
added_raw = supply_speed * delta
|
|
data['raw'] = data.get('raw', 0.0) + added_raw
|
|
stats['totalSupplied'] = stats.get('totalSupplied', 0.0) + added_raw
|
|
|
|
# Workers production (Refining raw -> product)
|
|
workers = data.get('workers', 0)
|
|
raw = data.get('raw', 0.0)
|
|
if workers > 0 and raw > 0:
|
|
unit_milestone_m = get_unit_milestone_multiplier(workers)
|
|
potential_prod = workers * cfg['produceRate'] * prod_m * worker_m * unit_milestone_m * all_tier_work * delta
|
|
actual_prod = min(raw, potential_prod)
|
|
data['raw'] = max(0.0, raw - actual_prod)
|
|
data['product'] = data.get('product', 0.0) + actual_prod
|
|
stats['totalProduced'] = stats.get('totalProduced', 0.0) + actual_prod
|
|
|
|
# Dealers selling (Product -> Cash)
|
|
dealers = data.get('dealers', 0)
|
|
product = data.get('product', 0.0)
|
|
if dealers > 0 and product > 0:
|
|
unit_milestone_m = get_unit_milestone_multiplier(dealers)
|
|
potential_sell = dealers * cfg['sellRate'] * dealer_m * unit_milestone_m * all_tier_deal * delta
|
|
actual_sell = min(product, potential_sell)
|
|
data['product'] = max(0.0, product - actual_sell)
|
|
rev = actual_sell * cfg['sellPrice'] * sell_m
|
|
state['cash'] = state.get('cash', 0.0) + rev
|
|
state['totalEarned'] = state.get('totalEarned', 0.0) + rev
|
|
stats['totalSold'] = stats.get('totalSold', 0.0) + actual_sell
|
|
|
|
# Heat decay
|
|
state['heat'] = max(0.0, state.get('heat', 0.0) - (0.5 * delta))
|
|
|
|
return sanitize_state(state)
|
|
|
|
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, supplier_m, worker_m = get_multipliers(state)
|
|
|
|
# Unit price scaling factor (1.15 down to 1.11 via bulk_discount perk)
|
|
discount_lvl = state.get('offshorePerks', {}).get('bulk_discount', 0)
|
|
scaling_r = max(1.10, 1.15 - (discount_lvl * 0.01))
|
|
|
|
for act in actions:
|
|
action_type = act.get('type')
|
|
drug_id = act.get('drug_id')
|
|
|
|
if action_type == 'buy_raw' and drug_id in DRUG_CONFIGS:
|
|
cfg = DRUG_CONFIGS[drug_id]
|
|
cost = cfg['rawCost'] * raw_m
|
|
amount = max(1, int(act.get('amount', 1)))
|
|
total_cost = cost * amount
|
|
if state['cash'] >= total_cost:
|
|
state['cash'] -= total_cost
|
|
state['drugs'][drug_id]['raw'] = state['drugs'][drug_id].get('raw', 0.0) + amount
|
|
state.setdefault('stats', {})['totalSupplied'] = state['stats'].get('totalSupplied', 0.0) + amount
|
|
|
|
elif action_type == 'manufacture' and drug_id in DRUG_CONFIGS:
|
|
data = state['drugs'][drug_id]
|
|
amount = max(1, int(act.get('amount', 1)))
|
|
actual = min(amount, int(data.get('raw', 0)))
|
|
if actual > 0:
|
|
data['raw'] -= actual
|
|
yield_count = actual
|
|
if state.get('skills', {}).get('chem_3'):
|
|
# 10% chance on client for 3x yield
|
|
yield_count *= 1.3
|
|
state.setdefault('stats', {})['purityProcs'] = state['stats'].get('purityProcs', 0) + 1
|
|
data['product'] = data.get('product', 0.0) + yield_count
|
|
state.setdefault('stats', {})['totalProduced'] = state['stats'].get('totalProduced', 0.0) + yield_count
|
|
|
|
# Swiss vault secrecy perk reduces heat generation
|
|
vault_lvl = state.get('offshorePerks', {}).get('swiss_vault', 0)
|
|
heat_rise = max(0.2, (0.5 * actual) * (1.0 - vault_lvl * 0.12))
|
|
state['heat'] = min(100.0, state.get('heat', 0.0) + heat_rise)
|
|
|
|
elif action_type == 'sell_manual' and drug_id in DRUG_CONFIGS:
|
|
data = state['drugs'][drug_id]
|
|
cfg = DRUG_CONFIGS[drug_id]
|
|
amount = max(1, int(act.get('amount', 1)))
|
|
actual = min(amount, int(data.get('product', 0)))
|
|
if actual > 0:
|
|
data['product'] -= actual
|
|
profit = actual * cfg['sellPrice'] * sell_m
|
|
state['cash'] += profit
|
|
state['totalEarned'] += profit
|
|
state.setdefault('stats', {})['totalSold'] = state['stats'].get('totalSold', 0.0) + actual
|
|
|
|
vault_lvl = state.get('offshorePerks', {}).get('swiss_vault', 0)
|
|
heat_rise = max(0.3, (0.8 * actual) * (1.0 - vault_lvl * 0.12))
|
|
state['heat'] = min(100.0, state.get('heat', 0.0) + heat_rise)
|
|
|
|
elif action_type == 'hire_supplier' and drug_id in DRUG_CONFIGS:
|
|
cfg = DRUG_CONFIGS[drug_id]
|
|
hire_qty = max(1, int(act.get('count', 1)))
|
|
base_cost = cfg.get('supplierCost', 75)
|
|
for _ in range(hire_qty):
|
|
count = state['drugs'][drug_id].get('suppliers', 0)
|
|
cost = math.floor(base_cost * math.pow(scaling_r, count))
|
|
if state['cash'] >= cost:
|
|
state['cash'] -= cost
|
|
state['drugs'][drug_id]['suppliers'] = count + 1
|
|
else:
|
|
break
|
|
|
|
elif action_type == 'hire_worker' and drug_id in DRUG_CONFIGS:
|
|
cfg = DRUG_CONFIGS[drug_id]
|
|
hire_qty = max(1, int(act.get('count', 1)))
|
|
base_cost = cfg['workerCost']
|
|
for _ in range(hire_qty):
|
|
count = state['drugs'][drug_id].get('workers', 0)
|
|
cost = math.floor(base_cost * math.pow(scaling_r, count))
|
|
if state['cash'] >= cost:
|
|
state['cash'] -= cost
|
|
state['drugs'][drug_id]['workers'] = count + 1
|
|
else:
|
|
break
|
|
|
|
elif action_type == 'hire_dealer' and drug_id in DRUG_CONFIGS:
|
|
cfg = DRUG_CONFIGS[drug_id]
|
|
hire_qty = max(1, int(act.get('count', 1)))
|
|
base_cost = cfg['dealerCost']
|
|
for _ in range(hire_qty):
|
|
count = state['drugs'][drug_id].get('dealers', 0)
|
|
cost = math.floor(base_cost * math.pow(scaling_r, count))
|
|
if state['cash'] >= cost:
|
|
state['cash'] -= cost
|
|
state['drugs'][drug_id]['dealers'] = count + 1
|
|
else:
|
|
break
|
|
|
|
elif action_type == 'unlock_drug' and drug_id in DRUG_CONFIGS:
|
|
cfg = DRUG_CONFIGS[drug_id]
|
|
cost = cfg['unlockCost']
|
|
if state['cash'] >= cost and not state['drugs'][drug_id].get('unlocked'):
|
|
state['cash'] -= cost
|
|
state['drugs'][drug_id]['unlocked'] = True
|
|
|
|
# Procurement pipeline perk gives free starter units!
|
|
starter_units = state.get('offshorePerks', {}).get('auto_contracts', 0)
|
|
if starter_units > 0:
|
|
state['drugs'][drug_id]['suppliers'] = state['drugs'][drug_id].get('suppliers', 0) + starter_units
|
|
state['drugs'][drug_id]['workers'] = state['drugs'][drug_id].get('workers', 0) + starter_units
|
|
|
|
elif action_type == 'blend_recipe':
|
|
recipe_id = act.get('recipe_id')
|
|
if recipe_id in SYNTHESIS_RECIPES:
|
|
r = SYNTHESIS_RECIPES[recipe_id]
|
|
can_blend = True
|
|
for ingr_id, needed in r['ingredients'].items():
|
|
if state['drugs'].get(ingr_id, {}).get('product', 0) < needed:
|
|
can_blend = False
|
|
break
|
|
if can_blend:
|
|
for ingr_id, needed in r['ingredients'].items():
|
|
state['drugs'][ingr_id]['product'] -= needed
|
|
state['cash'] += r['payout']
|
|
state['totalEarned'] += r['payout']
|
|
state['heat'] = min(100.0, state.get('heat', 0.0) + 5.0)
|
|
|
|
elif action_type == 'buy_skill':
|
|
skill_id = act.get('skill_id')
|
|
if skill_id in SKILL_COSTS and not state.get('skills', {}).get(skill_id):
|
|
cost = SKILL_COSTS[skill_id]
|
|
if state['cash'] >= cost:
|
|
state['cash'] -= cost
|
|
state.setdefault('skills', {})[skill_id] = True
|
|
|
|
elif action_type == 'claim_achievement':
|
|
ach_id = act.get('ach_id')
|
|
tier = int(act.get('tier', 1))
|
|
if validate_achievement_claim(state, ach_id, tier):
|
|
state.setdefault('achievements', {})[ach_id] = tier
|
|
|
|
elif action_type == 'buy_offshore_perk':
|
|
perk_id = act.get('perk_id')
|
|
if perk_id in OFFSHORE_PERK_COSTS:
|
|
costs = OFFSHORE_PERK_COSTS[perk_id]
|
|
current_lvl = state.setdefault('offshorePerks', {}).get(perk_id, 0)
|
|
if current_lvl < len(costs):
|
|
cost = costs[current_lvl]
|
|
if state.get('cleanBonds', 0) >= cost:
|
|
state['cleanBonds'] -= cost
|
|
state['offshorePerks'][perk_id] = current_lvl + 1
|
|
|
|
elif action_type == 'bribe_police':
|
|
base_bribe = max(100.0, state['cash'] * 0.15)
|
|
judiciary_lvl = state.get('offshorePerks', {}).get('corrupt_judiciary', 0)
|
|
bribe_cost = max(25.0, base_bribe * (1.0 - judiciary_lvl * 0.20))
|
|
if state['cash'] >= bribe_cost:
|
|
state['cash'] -= bribe_cost
|
|
state['heat'] = 0.0
|
|
state.setdefault('stats', {})['raidsSurvived'] = state['stats'].get('raidsSurvived', 0) + 1
|
|
|
|
elif action_type == 'flush_stash':
|
|
state['heat'] = 20.0
|
|
state.setdefault('stats', {})['raidsSurvived'] = state['stats'].get('raidsSurvived', 0) + 1
|
|
|
|
elif action_type == 'breakout_raid':
|
|
success = bool(act.get('success', False))
|
|
if success:
|
|
state['heat'] = 0.0
|
|
state.setdefault('stats', {})['raidsSurvived'] = state['stats'].get('raidsSurvived', 0) + 1
|
|
|
|
elif action_type == 'launder_prestige':
|
|
bonds_to_earn = math.floor(math.sqrt(max(0.0, state.get('totalEarned', 0.0)) / 25000.0))
|
|
gain = max(0, bonds_to_earn - state.get('totalCleanBonds', 0))
|
|
if gain >= 1:
|
|
state['totalCleanBonds'] = state.get('totalCleanBonds', 0) + gain
|
|
state['cleanBonds'] = state.get('cleanBonds', 0) + gain
|
|
|
|
stats = state.setdefault('stats', {})
|
|
stats['prestigeCount'] = stats.get('prestigeCount', 0) + 1
|
|
|
|
# Calculate start cash with bond and swiss kingpin bonuses
|
|
start_cash = 50.0 + (state['totalCleanBonds'] * 25.0)
|
|
swiss_tier = state.get('achievements', {}).get('swiss_kingpin', 0)
|
|
if swiss_tier >= 1: start_cash += 250.0
|
|
if swiss_tier >= 2: start_cash += 2500.0
|
|
if swiss_tier >= 3: start_cash += 25000.0
|
|
if swiss_tier >= 4: start_cash += 250000.0
|
|
|
|
state['cash'] = start_cash
|
|
state['heat'] = 0.0
|
|
|
|
# Starter units from auto_contracts perk
|
|
free_starter = state.get('offshorePerks', {}).get('auto_contracts', 0)
|
|
|
|
for d_id, d_data in state['drugs'].items():
|
|
d_data['unlocked'] = (d_id == 'weed')
|
|
d_data['raw'] = 0.0
|
|
d_data['product'] = 0.0
|
|
d_data['suppliers'] = free_starter if d_data['unlocked'] else 0
|
|
d_data['workers'] = free_starter if d_data['unlocked'] else 0
|
|
d_data['dealers'] = 0
|
|
|
|
return sanitize_state(state)
|
|
|
|
def sanitize_state(state: dict) -> dict:
|
|
"""Enforces max 1 decimal and prevents negative or runaway numbers."""
|
|
state['cash'] = clean_round(state.get('cash', 0.0))
|
|
state['heat'] = clean_round(min(100.0, max(0.0, state.get('heat', 0.0))))
|
|
state['totalEarned'] = clean_round(state.get('totalEarned', 0.0))
|
|
state['cleanBonds'] = max(0, int(state.get('cleanBonds', 0)))
|
|
state['totalCleanBonds'] = max(state['cleanBonds'], int(state.get('totalCleanBonds', 0)))
|
|
|
|
stats = state.setdefault('stats', {})
|
|
for k in ['totalProduced', 'totalSold', 'totalSupplied']:
|
|
stats[k] = clean_round(stats.get(k, 0.0))
|
|
for k in ['purityProcs', 'raidsSurvived', 'prestigeCount']:
|
|
stats[k] = int(stats.get(k, 0))
|
|
|
|
state.setdefault('achievements', {})
|
|
state.setdefault('offshorePerks', {})
|
|
|
|
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))
|
|
|
|
return state
|