- Implement all Mod API endpoints (buildings, research, geysers, alerts, critters, deconstruct, prioritize, research, schedule, wardrobe) - Enhance Python tools with comprehensive CLI, analysis (O2/food/power/temp/water), and 7 blueprints (SPOM, toilet, ranch, farm, cooling, bedroom) - Add utility scripts: auto_repair, auto_analyze, watch mode, setup - Write Agent-Mod integration constraints and development guide - Create skills directory with ONI agent skill definition
205 lines
7.2 KiB
Python
205 lines
7.2 KiB
Python
import json
|
|
import urllib.request
|
|
import urllib.error
|
|
import sys
|
|
import os
|
|
|
|
CONFIG_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config.json')
|
|
|
|
def load_config():
|
|
with open(CONFIG_PATH) as f:
|
|
return json.load(f)
|
|
|
|
def api_url(endpoint):
|
|
cfg = load_config()
|
|
return f"http://{cfg['modHost']}:{cfg['modPort']}{endpoint}"
|
|
|
|
def api_get(endpoint):
|
|
url = api_url(endpoint)
|
|
cfg = load_config()
|
|
try:
|
|
req = urllib.request.Request(url, method='GET')
|
|
with urllib.request.urlopen(req, timeout=cfg['timeout']) as resp:
|
|
return json.loads(resp.read().decode())
|
|
except urllib.error.URLError as e:
|
|
return {"error": str(e)}
|
|
|
|
def api_post(endpoint, data):
|
|
url = api_url(endpoint)
|
|
cfg = load_config()
|
|
try:
|
|
req = urllib.request.Request(
|
|
url, data=json.dumps(data).encode(),
|
|
headers={'Content-Type': 'application/json'},
|
|
method='POST'
|
|
)
|
|
with urllib.request.urlopen(req, timeout=cfg['timeout']) as resp:
|
|
return json.loads(resp.read().decode())
|
|
except urllib.error.URLError as e:
|
|
return {"error": str(e)}
|
|
|
|
def cmd_health():
|
|
print(api_get('/health'))
|
|
|
|
def cmd_status():
|
|
game = api_get('/api/state/game')
|
|
resources = api_get('/api/state/resources')
|
|
dups = api_get('/api/state/duplicants')
|
|
alerts = api_get('/api/state/alert')
|
|
|
|
if 'error' in game:
|
|
print(f"Error: {game['error']}")
|
|
return
|
|
|
|
print("=== Game ===")
|
|
print(f" Cycle: {game.get('cycle', '?')}")
|
|
print(f" Duplicants: {game.get('duplicantCount', '?')}")
|
|
print(f" World: {game.get('worldName', '?')}")
|
|
|
|
print("\n=== Resources (top 15) ===")
|
|
if isinstance(resources, list):
|
|
for r in sorted(resources, key=lambda x: x.get('amount', 0), reverse=True)[:15]:
|
|
print(f" {r.get('name', '?'):20s} {r.get('amount', 0):>10.1f} {r.get('unit', '')}")
|
|
|
|
print("\n=== Duplicants ===")
|
|
if isinstance(dups, list):
|
|
for d in dups:
|
|
print(f" {d.get('name'):12s} stress={d.get('stress', '?'):>5} food={d.get('calories', 0)/1000:>6.0f} kcal"
|
|
f" stamina={d.get('stamina', '?'):>5} o2={d.get('oxygen', '?'):>5}")
|
|
|
|
print("\n=== Alerts ===")
|
|
if isinstance(alerts, list):
|
|
for a in alerts:
|
|
print(f" [{a.get('severity', '?')}] {a.get('title', '?')}: {a.get('message', '')}" if alerts else " (none)")
|
|
|
|
def cmd_resources():
|
|
data = api_get('/api/state/resources')
|
|
if isinstance(data, list):
|
|
for r in sorted(data, key=lambda x: x.get('amount', 0), reverse=True):
|
|
print(f"{r.get('name', '?'):30s} {r.get('amount', 0):>12.1f} {r.get('unit', '')}")
|
|
else:
|
|
print(json.dumps(data, indent=2, ensure_ascii=False))
|
|
|
|
def cmd_duplicants():
|
|
print(json.dumps(api_get('/api/state/duplicants'), indent=2, ensure_ascii=False))
|
|
|
|
def cmd_buildings():
|
|
data = api_get('/api/state/buildings')
|
|
if isinstance(data, list):
|
|
for b in data:
|
|
print(f" {b.get('name', '?'):25s} at ({b.get('x', '?')}, {b.get('y', '?')}) "
|
|
f"{'ON' if b.get('isOperational') else 'OFF'}")
|
|
else:
|
|
print(json.dumps(data, indent=2, ensure_ascii=False))
|
|
|
|
def cmd_research():
|
|
data = api_get('/api/state/research')
|
|
if isinstance(data, list):
|
|
for t in data:
|
|
status = "DONE" if t.get('isComplete') else f"{t.get('progress', 0)*100:.0f}%"
|
|
print(f" {t.get('name', '?'):25s} [{status}] ({t.get('category', '?')})")
|
|
else:
|
|
print(json.dumps(data, indent=2, ensure_ascii=False))
|
|
|
|
def cmd_geysers():
|
|
data = api_get('/api/state/geysers')
|
|
if isinstance(data, list):
|
|
for g in data:
|
|
print(f" {g.get('name', '?'):25s} at ({g.get('x', '?')}, {g.get('y', '?')}) "
|
|
f"state={g.get('state', '?')} rate={g.get('emitRate', '?')}g/s")
|
|
else:
|
|
print(json.dumps(data, indent=2, ensure_ascii=False))
|
|
|
|
def cmd_critters():
|
|
data = api_get('/api/state/critters')
|
|
if isinstance(data, list):
|
|
for c in data:
|
|
print(f" {c.get('name', '?'):20s} ({c.get('species', '?')}) at ({c.get('x', '?')}, {c.get('y', '?')}) "
|
|
f"age={c.get('age', '?'):.1f} happy={c.get('happiness', '?')}")
|
|
else:
|
|
print(json.dumps(data, indent=2, ensure_ascii=False))
|
|
|
|
def cmd_dig(args):
|
|
if len(args) < 4:
|
|
print("Usage: dig <x> <y> <width> <height>")
|
|
return
|
|
result = api_post('/api/action/dig', {
|
|
"x": int(args[0]), "y": int(args[1]),
|
|
"width": int(args[2]), "height": int(args[3])
|
|
})
|
|
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
|
|
def cmd_build(args):
|
|
if len(args) < 3:
|
|
print("Usage: build <buildingId> <x> <y> [rotation]")
|
|
return
|
|
data = {"buildingId": args[0], "x": int(args[1]), "y": int(args[2])}
|
|
if len(args) >= 4:
|
|
data["rotation"] = args[3]
|
|
result = api_post('/api/action/build', data)
|
|
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
|
|
def cmd_deconstruct(args):
|
|
if len(args) < 3:
|
|
print("Usage: deconstruct <buildingId> <x> <y>")
|
|
return
|
|
result = api_post('/api/action/deconstruct', {
|
|
"buildingId": args[0], "x": int(args[1]), "y": int(args[2])
|
|
})
|
|
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
|
|
def cmd_prioritize(args):
|
|
if len(args) < 3:
|
|
print("Usage: prioritize <x> <y> <priority>")
|
|
return
|
|
result = api_post('/api/action/prioritize', {
|
|
"x": int(args[0]), "y": int(args[1]), "priority": int(args[2])
|
|
})
|
|
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
|
|
def cmd_research_select(args):
|
|
if len(args) < 1:
|
|
print("Usage: research_select <techId>")
|
|
return
|
|
result = api_post('/api/action/research', {"techId": args[0]})
|
|
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
|
|
COMMANDS = {
|
|
'health': cmd_health,
|
|
'status': cmd_status,
|
|
'resources': cmd_resources,
|
|
'duplicants': cmd_duplicants,
|
|
'buildings': cmd_buildings,
|
|
'research': cmd_research,
|
|
'geysers': cmd_geysers,
|
|
'critters': cmd_critters,
|
|
'dig': cmd_dig,
|
|
'build': cmd_build,
|
|
'deconstruct': cmd_deconstruct,
|
|
'prioritize': cmd_prioritize,
|
|
'research_select': cmd_research_select,
|
|
}
|
|
|
|
if __name__ == '__main__':
|
|
cmd = sys.argv[1] if len(sys.argv) > 1 else 'help'
|
|
|
|
if cmd == 'help' or cmd not in COMMANDS:
|
|
print("ONI Agent API Client")
|
|
print("")
|
|
print("Commands:")
|
|
print(" health Check Mod connection")
|
|
print(" status Game overview (cycle, resources, dups, alerts)")
|
|
print(" resources List all resources with amounts")
|
|
print(" duplicants Show duplicant details")
|
|
print(" buildings List all buildings")
|
|
print(" research Show research tree progress")
|
|
print(" geysers Show geyser states")
|
|
print(" critters Show critter list")
|
|
print(" dig <x> <y> <w> <h> Dig area")
|
|
print(" build <id> <x> <y> Place building")
|
|
print(" deconstruct <id> <x> <y> Remove building")
|
|
print(" prioritize <x> <y> <p> Set priority")
|
|
print(" research_select <id> Select tech to research")
|
|
else:
|
|
COMMANDS[cmd](sys.argv[2:])
|