ONI Agent Bridge - initial mod implementation
This commit is contained in:
281
tools/oni_analyzer.py
Normal file
281
tools/oni_analyzer.py
Normal file
@ -0,0 +1,281 @@
|
||||
import json
|
||||
import sys
|
||||
from oni_api import api_get
|
||||
|
||||
|
||||
def get_game_state():
|
||||
return {
|
||||
'game': api_get('/api/state/game'),
|
||||
'resources': api_get('/api/state/resources'),
|
||||
'duplicants': api_get('/api/state/duplicants'),
|
||||
'buildings': api_get('/api/state/buildings'),
|
||||
'research': api_get('/api/state/research'),
|
||||
'geysers': api_get('/api/state/geysers'),
|
||||
'alerts': api_get('/api/state/alert'),
|
||||
'critters': api_get('/api/state/critters'),
|
||||
'plants': api_get('/api/state/plants'),
|
||||
'rooms': api_get('/api/state/rooms'),
|
||||
}
|
||||
|
||||
|
||||
def as_dict(resources):
|
||||
if not isinstance(resources, list):
|
||||
return {}
|
||||
return {r.get('name'): r for r in resources}
|
||||
|
||||
|
||||
def buildings_by_cat(buildings):
|
||||
cats = {}
|
||||
for b in (buildings or []):
|
||||
cat = b.get('category', 'Other')
|
||||
cats.setdefault(cat, []).append(b)
|
||||
return cats
|
||||
|
||||
|
||||
def analyze_o2(resources, buildings):
|
||||
r = as_dict(resources)
|
||||
o2 = r.get('Oxygen', {}).get('amount', 0)
|
||||
algae = r.get('Algae', {}).get('amount', 0)
|
||||
pw = r.get('PollutedWater', {}).get('amount', 0)
|
||||
|
||||
has_electrolyzer = any(b.get('id') == 'Electrolyzer' for b in (buildings or []))
|
||||
has_diffuser = any(b.get('id') == 'OxygenDiffuser' for b in (buildings or []))
|
||||
|
||||
warnings = []
|
||||
if o2 < 100:
|
||||
warnings.append(("CRITICAL", f"Oxygen critically low ({o2:.0f} kg)!"))
|
||||
elif o2 < 500:
|
||||
warnings.append(("WARN", f"Oxygen declining ({o2:.0f} kg)"))
|
||||
|
||||
if o2 < 1000 and not has_electrolyzer and not has_diffuser:
|
||||
warnings.append(("CRITICAL", "No oxygen production buildings found! Build OxygenDiffuser or Electrolyzer"))
|
||||
|
||||
if algae < 1000:
|
||||
warnings.append(("WARN", f"Algae running out ({algae:.0f} kg) — build electrolyzer"))
|
||||
elif algae < 5000 and not has_electrolyzer:
|
||||
warnings.append(("INFO", f"Algae moderate ({algae:.0f} kg) — plan SPOM transition"))
|
||||
|
||||
if pw > 50000:
|
||||
warnings.append(("INFO", f"Polluted water abundant ({pw:.0f} kg) — use for reed fiber / pincha pepper"))
|
||||
|
||||
return warnings
|
||||
|
||||
|
||||
def analyze_food(resources, buildings):
|
||||
r = as_dict(resources)
|
||||
cal = r.get('Calories', {}).get('amount', 0)
|
||||
|
||||
has_farm = any(b.get('id') in ('PlanterBox', 'FarmTile') for b in (buildings or []))
|
||||
has_grill = any(b.get('id') == 'ElectricGrill' for b in (buildings or []))
|
||||
|
||||
warnings = []
|
||||
if cal < 100000:
|
||||
warnings.append(("CRITICAL", f"Food shortage ({cal:.0f} kcal)!"))
|
||||
elif cal < 500000:
|
||||
warnings.append(("WARN", f"Food declining ({cal:.0f} kcal)"))
|
||||
elif cal > 2000000:
|
||||
warnings.append(("INFO", f"Food surplus ({cal:.0f} kcal)"))
|
||||
|
||||
if cal < 500000 and not has_farm:
|
||||
warnings.append(("WARN", "No farm plots found. Build PlanterBox and plant Mealwood"))
|
||||
if cal < 500000 and not has_grill:
|
||||
warnings.append(("INFO", "Build ElectricGrill to improve food quality"))
|
||||
return warnings
|
||||
|
||||
|
||||
def analyze_power(resources, buildings):
|
||||
r = as_dict(resources)
|
||||
coal = r.get('Coal', {}).get('amount', 0)
|
||||
hydrogen = r.get('Hydrogen', {}).get('amount', 0)
|
||||
natgas = r.get('NaturalGas', {}).get('amount', 0)
|
||||
|
||||
generators = [b for b in (buildings or []) if b.get('id') in (
|
||||
'CoalGenerator', 'HydrogenGenerator', 'NaturalGasGenerator',
|
||||
'ManualGenerator', 'PetroleumGenerator', 'WoodBurner'
|
||||
)]
|
||||
batteries = [b for b in (buildings or []) if b.get('id') in ('Battery', 'JumboBattery', 'SmartBattery')]
|
||||
|
||||
warnings = []
|
||||
if not generators:
|
||||
warnings.append(("CRITICAL", "No power generators found! Build ManualGenerator or CoalGenerator"))
|
||||
else:
|
||||
powered_on = sum(1 for g in generators if g.get('isOperational'))
|
||||
warnings.append(("INFO", f"Power: {len(generators)} generators ({powered_on} operational), {len(batteries)} batteries"))
|
||||
|
||||
if coal < 5000 and any(g.get('id') == 'CoalGenerator' for g in generators):
|
||||
warnings.append(("WARN", f"Coal low ({coal:.0f} kg) — diversify power"))
|
||||
if hydrogen > 20000:
|
||||
warnings.append(("INFO", f"Hydrogen stockpiled ({hydrogen:.0f} kg) — add generators"))
|
||||
if natgas > 10000:
|
||||
warnings.append(("INFO", f"Natural gas abundant ({natgas:.0f} kg)"))
|
||||
|
||||
return warnings
|
||||
|
||||
|
||||
def analyze_temp():
|
||||
from oni_api import api_get
|
||||
data = api_get('/api/state/temperature/zones', auto_pause=False)
|
||||
warnings = []
|
||||
if 'error' in data:
|
||||
return warnings
|
||||
avg = data.get('averageC', 0)
|
||||
if avg > 50:
|
||||
warnings.append(("CRITICAL", f"Overheating ({avg:.0f}°C)"))
|
||||
elif avg > 35:
|
||||
warnings.append(("WARN", f"High temperature ({avg:.0f}°C)"))
|
||||
elif avg < -5:
|
||||
warnings.append(("WARN", f"Too cold ({avg:.0f}°C)"))
|
||||
if data.get('hotSpots'):
|
||||
warnings.append(("WARN", f"{len(data['hotSpots'])} hot spots (>50°C)"))
|
||||
if data.get('coldSpots'):
|
||||
warnings.append(("WARN", f"{len(data['coldSpots'])} cold spots (<5°C)"))
|
||||
return warnings
|
||||
|
||||
|
||||
def analyze_water(resources):
|
||||
r = as_dict(resources)
|
||||
water = r.get('Water', {}).get('amount', 0)
|
||||
pw = r.get('PollutedWater', {}).get('amount', 0)
|
||||
sw = r.get('SaltWater', {}).get('amount', 0)
|
||||
|
||||
total_water = water + pw + sw
|
||||
warnings = []
|
||||
if total_water < 10000:
|
||||
warnings.append(("WARN", f"Total water low ({total_water:.0f} kg across all sources)"))
|
||||
elif total_water < 50000:
|
||||
warnings.append(("INFO", f"Water reserves moderate ({total_water:.0f} kg)"))
|
||||
if water < 10000 and pw > 10000:
|
||||
warnings.append(("INFO", f"Filter polluted water ({pw:.0f} kg available)"))
|
||||
|
||||
return warnings
|
||||
|
||||
|
||||
def analyze_research(research):
|
||||
if not isinstance(research, list):
|
||||
return []
|
||||
warnings = []
|
||||
done = sum(1 for t in research if t.get('isComplete'))
|
||||
total = len(research)
|
||||
if done == 0 and total > 0:
|
||||
warnings.append(("WARN", "No research completed! Start with Research Station"))
|
||||
elif done < total * 0.3:
|
||||
warnings.append(("INFO", f"Research progress: {done}/{total} ({done*100//total}%)"))
|
||||
return warnings
|
||||
|
||||
|
||||
def suggest_actions(warnings, alerts, buildings):
|
||||
suggestions = []
|
||||
|
||||
has_electrolyzer = any(b.get('id') == 'Electrolyzer' for b in (buildings or []))
|
||||
has_lavatory = any(b.get('id') == 'Lavatory' for b in (buildings or []))
|
||||
has_sieve = any(b.get('id') == 'WaterSiever' for b in (buildings or []))
|
||||
|
||||
for sev, msg in warnings:
|
||||
ml = msg.lower()
|
||||
if 'oxygen' in ml:
|
||||
suggestions.append("Build SPOM: Electrolyzer + Hydrogen Generator at a water source")
|
||||
elif 'food' in ml and 'shortage' in ml:
|
||||
suggestions.append("Build PlanterBoxes x5, plant Mealwood (no irrigation needed)")
|
||||
elif 'food' in ml and 'declining' in ml:
|
||||
suggestions.append("Expand farm or start hatch ranching (Hatch eats Sedimentary Rock)")
|
||||
elif 'power' in ml and 'generator' in ml:
|
||||
suggestions.append("Build ManualGenerator (early) or CoalGenerator (durable)")
|
||||
elif 'coal' in ml:
|
||||
suggestions.append("Diversify power: build HydrogenGenerator + SmartBattery")
|
||||
elif 'hydrogen' in ml:
|
||||
suggestions.append("Connect HydrogenGenerator to your hydrogen vent/SPOM")
|
||||
elif 'water' in ml and 'low' in ml:
|
||||
suggestions.append("Dig to find water geyser or filter polluted water")
|
||||
elif 'heat' in ml or 'overheat' in ml:
|
||||
suggestions.append("Build insulated tiles around heat sources; add cooling loop")
|
||||
|
||||
if not has_lavatory:
|
||||
suggestions.append("Build Lavatory + Water Sieve for renewable water loop")
|
||||
if not has_electrolyzer:
|
||||
suggestions.append("Plan SPOM once Algae < 5t or you have renewable water")
|
||||
if not has_sieve and has_lavatory:
|
||||
suggestions.append("Build Water Sieve to close the bathroom loop")
|
||||
|
||||
return list(dict.fromkeys(suggestions))
|
||||
|
||||
|
||||
def print_report(state):
|
||||
g = state.get('game', {})
|
||||
if 'error' in g:
|
||||
print(f"[!] Cannot connect to game: {g['error']}")
|
||||
return False
|
||||
|
||||
resources = state.get('resources', [])
|
||||
buildings = state.get('buildings', [])
|
||||
alerts = state.get('alerts', [])
|
||||
|
||||
all_warnings = []
|
||||
all_warnings += analyze_o2(resources, buildings)
|
||||
all_warnings += analyze_food(resources, buildings)
|
||||
all_warnings += analyze_power(resources, buildings)
|
||||
all_warnings += analyze_temp()
|
||||
all_warnings += analyze_water(resources)
|
||||
all_warnings += analyze_research(state.get('research', []))
|
||||
|
||||
critical = [w for w in all_warnings if w[0] == 'CRITICAL']
|
||||
warns = [w for w in all_warnings if w[0] == 'WARN']
|
||||
infos = [w for w in all_warnings if w[0] == 'INFO']
|
||||
|
||||
suggestions = suggest_actions(all_warnings, alerts, buildings)
|
||||
|
||||
print("=" * 56)
|
||||
print(" ONI Analysis Report")
|
||||
print("=" * 56)
|
||||
print(f" Cycle: {g.get('cycle', '?')}")
|
||||
print(f" Duplicants: {g.get('duplicantCount', '?')}")
|
||||
print(f" World: {g.get('worldName', '?')}")
|
||||
print(f" Grid: {g.get('gridWidth', '?')} x {g.get('gridHeight', '?')}")
|
||||
print(f" Buildings: {len(buildings or [])}")
|
||||
print(f" Critters: {len(state.get('critters', []) or [])}")
|
||||
print(f" Geysers: {len(state.get('geysers', []) or [])}")
|
||||
print(f" Plants: {len(state.get('plants', []) or [])}")
|
||||
print(f" Rooms: {len(state.get('rooms', []) or [])}")
|
||||
print(f" Research done: {sum(1 for t in (state.get('research') or []) if t.get('isComplete'))}/{len(state.get('research', []) or [])}")
|
||||
print()
|
||||
|
||||
if critical:
|
||||
print(f" [CRITICAL] {len(critical)} issues — act immediately!")
|
||||
for _, msg in critical:
|
||||
print(f" ! {msg}")
|
||||
print()
|
||||
|
||||
if warns:
|
||||
print(f" [WARN] {len(warns)} issues")
|
||||
for _, msg in warns:
|
||||
print(f" * {msg}")
|
||||
print()
|
||||
|
||||
if infos:
|
||||
print(f" [INFO] {len(infos)} notes")
|
||||
for _, msg in infos:
|
||||
print(f" i {msg}")
|
||||
print()
|
||||
|
||||
if not all_warnings:
|
||||
print(" Status: All stable")
|
||||
print()
|
||||
|
||||
if suggestions:
|
||||
print(f" Suggestions ({len(suggestions)}):")
|
||||
for s in suggestions:
|
||||
print(f" -> {s}")
|
||||
print()
|
||||
|
||||
print(f" In-game alerts: {len(alerts) if isinstance(alerts, list) else 0}")
|
||||
if isinstance(alerts, list):
|
||||
for a in alerts:
|
||||
print(f" [{a.get('severity', '?')}] {a.get('title', '?')}")
|
||||
print("=" * 56)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
state = get_game_state()
|
||||
if not print_report(state):
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user