v2.1.0 Complete rewrite: proper Mod API, Python toolchain, auto-camera, comprehensive SKILL

This commit is contained in:
JianFeeeee
2026-05-30 11:58:45 +08:00
parent 89c6707aa8
commit 3d70338087
10 changed files with 2479 additions and 3507 deletions

View File

@ -1,281 +1,149 @@
import json
import sys
#!/usr/bin/env python3
"""
ONI Agent — Game State Analyzer
================================
Fetches comprehensive game state and produces actionable analysis across
6 dimensions: oxygen, food, power, temperature, water, research.
"""
import json, sys, io
# Fix GBK encoding
if sys.stdout.encoding and sys.stdout.encoding.upper() in ('GBK', 'GB2312', 'CP936'):
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
from oni_api import api_get
def get_data(endpoint):
r = api_get(endpoint)
if r.get("success"):
return r.get("data", r)
return None
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 main():
game = get_data('/api/state/game')
resources = get_data('/api/state/resources') or []
buildings = get_data('/api/state/buildings') or []
dups = get_data('/api/state/duplicants') or []
alert_data = get_data('/api/state/alert') or []
research = get_data('/api/state/research') or {}
rdict = {}
if isinstance(resources, list):
for r in resources:
rdict[r.get('name', '')] = r.get('amountKg', 0)
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)
# Building type counts
btypes = {}
for b in buildings if isinstance(buildings, list) else []:
bid = b.get('id', '')
btypes[bid] = btypes.get(bid, 0) + 1
print("=" * 56)
print(" ONI Analysis Report")
print(" ONI Agent — Game Analysis")
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 [])}")
if game:
speed_str = f"{game.get('gameSpeed','?')}x speed" if not game.get('isPaused') else "PAUSED"
print(f" Cycle {game.get('cycle','?')} | {game.get('duplicantCount','?')} dupes | "
f"{game.get('gridWidth','?')}x{game.get('gridHeight','?')} grid | {speed_str}")
print()
if critical:
print(f" [CRITICAL] {len(critical)} issues — act immediately!")
for _, msg in critical:
print(f" ! {msg}")
print()
# 1. Oxygen
print("─── Oxygen ───")
o2 = rdict.get('Oxygen', 0)
algae = rdict.get('Algae', 0)
pw = rdict.get('PollutedWater', 0)
water = rdict.get('Water', 0)
has_elec = 'Electrolyzer' in btypes
has_diff = 'OxygenDiffuser' in btypes
if warns:
print(f" [WARN] {len(warns)} issues")
for _, msg in warns:
print(f" * {msg}")
print()
print(f" O2: {o2:.0f} kg Algae: {algae:.0f} kg Water: {water:.0f} kg")
if o2 < 500:
print(f" ⚠ CRITICAL: Oxygen low! {o2:.0f} kg remaining")
if not has_elec and not has_diff:
print(f" ⚠ No oxygen production. Build OxygenDiffuser or plan SPOM")
elif not has_elec and algae < 1000:
print(f" ⚠ Algae running out ({algae:.0f} kg). Build Electrolyzer (SPOM)")
if water > 50000 and not has_elec:
print(f" ℹ Water abundant ({water:.0f} kg). Good time for SPOM")
if pw > 100000:
print(f" ℹ Polluted Water: {pw:.0f} kg — sieve into water or use for thimble reed")
if infos:
print(f" [INFO] {len(infos)} notes")
for _, msg in infos:
print(f" i {msg}")
print()
# 2. Food
print(f"\n─── Food ───")
calories = rdict.get('Calories', 0)
dirt = rdict.get('Dirt', 0)
has_farm = any(bid in ('PlanterBox', 'FarmTile') for bid in btypes)
has_grill = 'ElectricGrill' in btypes
print(f" Calories: {calories:.0f} kcal Dirt: {dirt:.0f} kg")
if calories < 200000:
print(f" ⚠ CRITICAL: Food shortage! {calories:.0f} kcal")
elif calories < 500000:
print(f" ⚠ Food declining ({calories:.0f} kcal). Build farm")
if not has_farm:
print(f" ℹ No farm. Build 5x PlanterBox + plant Mealwood (uses Dirt)")
if not has_grill and has_farm:
print(f" ℹ No grill. Build ElectricGrill for better food quality")
if not all_warnings:
print(" Status: All stable")
print()
# 3. Power
print(f"\n─── Power ───")
coal = rdict.get('Carbon', 0)
hydrogen = rdict.get('Hydrogen', 0)
has_manual = 'ManualGenerator' in btypes
has_coal = 'CoalGenerator' in btypes
has_hydro = 'HydrogenGenerator' in btypes
has_solar = 'SolarPanel' in btypes
has_natgas = 'NaturalGasGenerator' in btypes
print(f" Coal: {coal:.0f} kg Hydrogen: {hydrogen:.0f} kg")
gen_list = []
if has_manual: gen_list.append('Manual')
if has_coal: gen_list.append('Coal')
if has_hydro: gen_list.append('Hydrogen')
if has_natgas: gen_list.append('NaturalGas')
if has_solar: gen_list.append('Solar')
print(f" Generators: {', '.join(gen_list) if gen_list else 'None'}")
if coal < 1000 and has_coal:
print(f" ⚠ Coal low ({coal:.0f} kg). Diversify power production")
if not has_hydro and 'Electrolyzer' in btypes:
print(f" ℹ Have Electrolyzer but no HydrogenGenerator — wasting H2!")
if not any([has_manual, has_coal, has_hydro, has_natgas, has_solar]):
print(f" ⚠ No power generation! Build ManualGenerator or CoalGenerator")
if suggestions:
print(f" Suggestions ({len(suggestions)}):")
for s in suggestions:
print(f" -> {s}")
print()
# 4. Temperature
print(f"\n─── Temperature ───")
ice = rdict.get('Ice', 0) + rdict.get('CrushedIce', 0) + rdict.get('Snow', 0)
granite = rdict.get('Granite', 0)
igneous = rdict.get('IgneousRock', 0)
print(f" Ice/Snow: {ice:.0f} kg")
if ice > 0:
print(f" ℹ Ice available for cooling if melted")
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', '?')}")
# 5. Water
print(f"\n─── Water ───")
salt_water = rdict.get('SaltWater', 0)
brine = rdict.get('Brine', 0)
print(f" Water: {water:.0f} kg Polluted Water: {pw:.0f} kg Salt Water: {salt_water:.0f} kg")
if water < 10000 and pw < 10000:
print(f" ⚠ Low water! Collect from geysers or filter polluted water")
if water < 1000:
print(f" ⚠ CRITICAL: Water nearly empty!")
# 6. Research
print(f"\n─── Research ───")
completed = research.get('completedTechs', [])
print(f" Completed: {len(completed)} techs")
if completed:
print(f" Last: {completed[-1] if completed else 'none'}")
# 7. Alerts
if alert_data:
print(f"\n─── Active Alerts ───")
for a in alert_data:
print(f" [{a.get('severity','?')}] {a.get('title','?')}")
print()
print("=" * 56)
print(" Analysis complete.")
print("=" * 56)
return True
if __name__ == '__main__':
state = get_game_state()
if not print_report(state):
sys.exit(1)
if __name__ == "__main__":
main()