v2.1.0 Complete rewrite: proper Mod API, Python toolchain, auto-camera, comprehensive SKILL
This commit is contained in:
@ -1,163 +1,185 @@
|
||||
import json
|
||||
import sys
|
||||
from oni_api import api_post
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
ONI Agent — Blueprint Builder
|
||||
==============================
|
||||
Pre-built building modules for one-click deployment.
|
||||
|
||||
Usage:
|
||||
python oni_builder.py list List all blueprints
|
||||
python oni_builder.py show <name> Show blueprint details
|
||||
python oni_builder.py build <name> <x> <y> Build blueprint at anchor point
|
||||
"""
|
||||
|
||||
import json, sys
|
||||
from oni_api import api_post, api_get
|
||||
|
||||
BLUEPRINTS = {
|
||||
'spom': {
|
||||
'name': 'SPOM (Self-Powered Oxygen Module)',
|
||||
'description': 'Standard Rodriguez SPOM: electrolyzer + hydrogen generators',
|
||||
'size': {'width': 8, 'height': 6},
|
||||
'dig': {'x': -1, 'y': -1, 'width': 10, 'height': 8},
|
||||
'buildings': [
|
||||
{'id': 'Electrolyzer', 'x': 3, 'y': 2},
|
||||
{'id': 'GasPump', 'x': 1, 'y': 2},
|
||||
{'id': 'GasPump', 'x': 5, 'y': 2},
|
||||
{'id': 'HydrogenGenerator', 'x': 1, 'y': 0},
|
||||
{'id': 'HydrogenGenerator', 'x': 4, 'y': 0},
|
||||
{'id': 'GasFilter', 'x': 3, 'y': 0},
|
||||
'desc': 'Standard Rodriguez SPOM: electrolyzer + H2 generators + gas pumps',
|
||||
'size': {'w': 8, 'h': 6},
|
||||
'dig': {'dx': -1, 'dy': -1, 'w': 10, 'h': 8},
|
||||
'steps': [
|
||||
{'type': 'build', 'id': 'Electrolyzer', 'x': 3, 'y': 2},
|
||||
{'type': 'build', 'id': 'GasPump', 'x': 1, 'y': 2},
|
||||
{'type': 'build', 'id': 'GasPump', 'x': 5, 'y': 2},
|
||||
{'type': 'build', 'id': 'HydrogenGenerator', 'x': 1, 'y': 0},
|
||||
{'type': 'build', 'id': 'HydrogenGenerator', 'x': 4, 'y': 0},
|
||||
],
|
||||
},
|
||||
'spom_mini': {
|
||||
'name': 'Mini SPOM',
|
||||
'description': 'Compact electrolyzer + 1 hydrogen generator for early game',
|
||||
'size': {'width': 5, 'height': 4},
|
||||
'dig': {'x': -1, 'y': -1, 'width': 7, 'height': 6},
|
||||
'buildings': [
|
||||
{'id': 'Electrolyzer', 'x': 2, 'y': 1},
|
||||
{'id': 'GasPump', 'x': 1, 'y': 1},
|
||||
{'id': 'HydrogenGenerator', 'x': 2, 'y': 0},
|
||||
'desc': 'Compact electrolyzer + 1 hydrogen generator for early game',
|
||||
'size': {'w': 5, 'h': 4},
|
||||
'dig': {'dx': -1, 'dy': -1, 'w': 7, 'h': 6},
|
||||
'steps': [
|
||||
{'type': 'build', 'id': 'Electrolyzer', 'x': 2, 'y': 1},
|
||||
{'type': 'build', 'id': 'GasPump', 'x': 1, 'y': 1},
|
||||
{'type': 'build', 'id': 'HydrogenGenerator', 'x': 2, 'y': 0},
|
||||
],
|
||||
},
|
||||
'toilet_loop': {
|
||||
'name': 'Bathroom Water Loop',
|
||||
'description': 'Lavatory -> Water Purifier -> Lavatory closed loop',
|
||||
'size': {'width': 8, 'height': 4},
|
||||
'dig': {'x': -1, 'y': -1, 'width': 10, 'height': 6},
|
||||
'buildings': [
|
||||
{'id': 'Lavatory', 'x': 1, 'y': 1},
|
||||
{'id': 'Lavatory', 'x': 3, 'y': 1},
|
||||
{'id': 'WaterPurifier', 'x': 6, 'y': 1},
|
||||
{'id': 'LiquidPump', 'x': 6, 'y': 2},
|
||||
],
|
||||
},
|
||||
'ranch_hatch': {
|
||||
'name': 'Hatch Ranch',
|
||||
'description': 'Standard hatch ranching module with feeder and incubator',
|
||||
'size': {'width': 10, 'height': 6},
|
||||
'dig': {'x': -1, 'y': -1, 'width': 12, 'height': 8},
|
||||
'buildings': [
|
||||
{'id': 'RanchStation', 'x': 1, 'y': 1},
|
||||
{'id': 'Incubator', 'x': 4, 'y': 1},
|
||||
{'id': 'StorageLocker', 'x': 8, 'y': 1},
|
||||
'desc': '2 Lavatories -> Water Purifier closed loop',
|
||||
'size': {'w': 8, 'h': 4},
|
||||
'dig': {'dx': -1, 'dy': -1, 'w': 10, 'h': 6},
|
||||
'steps': [
|
||||
{'type': 'build', 'id': 'Lavatory', 'x': 1, 'y': 1},
|
||||
{'type': 'build', 'id': 'Lavatory', 'x': 3, 'y': 1},
|
||||
{'type': 'build', 'id': 'WaterPurifier', 'x': 6, 'y': 1},
|
||||
],
|
||||
},
|
||||
'farm_mealwood': {
|
||||
'name': 'Mealwood Farm',
|
||||
'description': 'Basic mealwood farm: 5 planter boxes + storage',
|
||||
'size': {'width': 6, 'height': 4},
|
||||
'dig': {'x': -1, 'y': -1, 'width': 8, 'height': 6},
|
||||
'buildings': [
|
||||
{'id': 'PlanterBox', 'x': 1, 'y': 1},
|
||||
{'id': 'PlanterBox', 'x': 3, 'y': 1},
|
||||
{'id': 'PlanterBox', 'x': 5, 'y': 1},
|
||||
{'id': 'PlanterBox', 'x': 1, 'y': 3},
|
||||
{'id': 'PlanterBox', 'x': 3, 'y': 3},
|
||||
{'id': 'StorageLocker', 'x': 5, 'y': 3},
|
||||
'desc': '5 PlanterBox + 1 StorageLocker',
|
||||
'size': {'w': 6, 'h': 4},
|
||||
'dig': {'dx': -1, 'dy': -1, 'w': 8, 'h': 6},
|
||||
'steps': [
|
||||
{'type': 'build', 'id': 'PlanterBox', 'x': 1, 'y': 1},
|
||||
{'type': 'build', 'id': 'PlanterBox', 'x': 3, 'y': 1},
|
||||
{'type': 'build', 'id': 'PlanterBox', 'x': 5, 'y': 1},
|
||||
{'type': 'build', 'id': 'PlanterBox', 'x': 1, 'y': 3},
|
||||
{'type': 'build', 'id': 'PlanterBox', 'x': 3, 'y': 3},
|
||||
{'type': 'build', 'id': 'StorageLocker', 'x': 5, 'y': 3},
|
||||
],
|
||||
},
|
||||
'bedroom': {
|
||||
'name': 'Standard Bedroom',
|
||||
'desc': '4 Cots + decor for bedroom room bonus',
|
||||
'size': {'w': 8, 'h': 4},
|
||||
'dig': {'dx': -1, 'dy': -1, 'w': 10, 'h': 6},
|
||||
'steps': [
|
||||
{'type': 'build', 'id': 'Cot', 'x': 1, 'y': 1},
|
||||
{'type': 'build', 'id': 'Cot', 'x': 3, 'y': 1},
|
||||
{'type': 'build', 'id': 'Cot', 'x': 5, 'y': 1},
|
||||
{'type': 'build', 'id': 'Cot', 'x': 7, 'y': 1},
|
||||
],
|
||||
},
|
||||
'cooling': {
|
||||
'name': 'Steam Turbine Cooler',
|
||||
'description': 'Liquid cooling loop with steam turbine + aquatuner',
|
||||
'size': {'width': 8, 'height': 6},
|
||||
'dig': {'x': -1, 'y': -1, 'width': 10, 'height': 8},
|
||||
'buildings': [
|
||||
{'id': 'SteamTurbine', 'x': 1, 'y': 4},
|
||||
{'id': 'SteamTurbine', 'x': 5, 'y': 4},
|
||||
{'id': 'Aquatuner', 'x': 2, 'y': 1},
|
||||
{'id': 'LiquidPump', 'x': 5, 'y': 1},
|
||||
'desc': 'Aquatuner + Steam Turbine cooling loop',
|
||||
'size': {'w': 8, 'h': 6},
|
||||
'dig': {'dx': -1, 'dy': -1, 'w': 10, 'h': 8},
|
||||
'steps': [
|
||||
{'type': 'build', 'id': 'SteamTurbine', 'x': 1, 'y': 4},
|
||||
{'type': 'build', 'id': 'SteamTurbine', 'x': 5, 'y': 4},
|
||||
{'type': 'build', 'id': 'Aquatuner', 'x': 2, 'y': 1},
|
||||
],
|
||||
},
|
||||
'bedroom': {
|
||||
'name': 'Barracks / Bedroom',
|
||||
'description': 'Basic bedroom module with cots and decorations',
|
||||
'size': {'width': 8, 'height': 4},
|
||||
'dig': {'x': -1, 'y': -1, 'width': 10, 'height': 6},
|
||||
'buildings': [
|
||||
{'id': 'Bed', 'x': 1, 'y': 1},
|
||||
{'id': 'Bed', 'x': 3, 'y': 1},
|
||||
{'id': 'Bed', 'x': 5, 'y': 1},
|
||||
{'id': 'LadderBed', 'x': 1, 'y': 3},
|
||||
{'id': 'LadderBed', 'x': 3, 'y': 3},
|
||||
{'id': 'FlowerVase', 'x': 6, 'y': 1},
|
||||
'ranch_hatch': {
|
||||
'name': 'Hatch Ranch',
|
||||
'desc': 'RanchStation + Incubator + StorageLocker',
|
||||
'size': {'w': 10, 'h': 6},
|
||||
'dig': {'dx': -1, 'dy': -1, 'w': 12, 'h': 8},
|
||||
'steps': [
|
||||
{'type': 'build', 'id': 'RanchStation', 'x': 1, 'y': 1},
|
||||
{'type': 'build', 'id': 'Incubator', 'x': 4, 'y': 1},
|
||||
{'type': 'build', 'id': 'StorageLocker', 'x': 8, 'y': 1},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def list_blueprints():
|
||||
print(f"Available Blueprints ({len(BLUEPRINTS)}):")
|
||||
print("=" * 60)
|
||||
for key, bp in BLUEPRINTS.items():
|
||||
print(f" {key:16s} {bp['name']:28s} {bp['size']['width']}x{bp['size']['height']}")
|
||||
print(f" {'':16s} {bp['description']}")
|
||||
def cmd_list(args):
|
||||
print(f"Available blueprints ({len(BLUEPRINTS)}):")
|
||||
for name, bp in sorted(BLUEPRINTS.items()):
|
||||
print(f" {name:15s} {bp['name']}")
|
||||
print(f" {'':15s} {bp['desc']}")
|
||||
print(f" {'':15s} Size: {bp['size']['w']}x{bp['size']['h']} | {len(bp['steps'])} buildings")
|
||||
print()
|
||||
|
||||
def cmd_show(args):
|
||||
if not args:
|
||||
print("Usage: show <blueprint_name>", file=sys.stderr)
|
||||
return
|
||||
name = args[0]
|
||||
if name not in BLUEPRINTS:
|
||||
print(f"Unknown blueprint: {name}", file=sys.stderr)
|
||||
return
|
||||
bp = BLUEPRINTS[name]
|
||||
print(f"Blueprint: {bp['name']}")
|
||||
print(f" {bp['desc']}")
|
||||
print(f" Size: {bp['size']['w']}x{bp['size']['h']}")
|
||||
if bp.get('dig'):
|
||||
d = bp['dig']
|
||||
print(f" Dig area: offset ({d['dx']},{d['dy']}) {d['w']}x{d['h']}")
|
||||
print(f" Buildings ({len(bp['steps'])}):")
|
||||
for s in bp['steps']:
|
||||
print(f" {s['id']:25s} at anchor+({s['x']},{s['y']})")
|
||||
|
||||
def apply_blueprint(name, origin_x, origin_y):
|
||||
bp = BLUEPRINTS.get(name)
|
||||
if not bp:
|
||||
print(f"[!] Blueprint '{name}' not found")
|
||||
print(f" Use 'list' to see available blueprints")
|
||||
return False
|
||||
def cmd_build(args):
|
||||
if len(args) < 3:
|
||||
print("Usage: build <blueprint_name> <anchor_x> <anchor_y>", file=sys.stderr)
|
||||
return
|
||||
name, ax, ay = args[0], int(args[1]), int(args[2])
|
||||
if name not in BLUEPRINTS:
|
||||
print(f"Unknown blueprint: {name}", file=sys.stderr)
|
||||
return
|
||||
bp = BLUEPRINTS[name]
|
||||
|
||||
print(f"Applying blueprint: {bp['name']}")
|
||||
print(f" Origin: ({origin_x}, {origin_y})")
|
||||
print(f" Size: {bp['size']['width']} x {bp['size']['height']}")
|
||||
print()
|
||||
# 1. Dig the area first
|
||||
if bp.get('dig'):
|
||||
d = bp['dig']
|
||||
dx, dy = ax + d['dx'], ay + d['dy']
|
||||
print(f"Digging area: ({dx},{dy}) {d['w']}x{d['h']}...")
|
||||
r = api_post('/api/action/dig', {"x": dx, "y": dy, "width": d['w'], "height": d['h']})
|
||||
if r.get("success"):
|
||||
info = r.get("data", {})
|
||||
print(f" Queued {info.get('count', '?')} dig orders")
|
||||
else:
|
||||
print(f" Dig warning: {r.get('errorMessage', r.get('error', 'unknown'))}")
|
||||
|
||||
# 2. Build each structure
|
||||
results = []
|
||||
for step in bp['steps']:
|
||||
bx, by = ax + step['x'], ay + step['y']
|
||||
print(f"Building {step['id']} at ({bx},{by})...")
|
||||
r = api_post('/api/action/build', {"buildingId": step['id'], "x": bx, "y": by})
|
||||
if r.get("success"):
|
||||
info = r.get("data", {})
|
||||
has_mat = info.get('hasAllMaterials', False)
|
||||
results.append(f" ✓ {step['id']} at ({bx},{by}) {'(may wait for materials)' if not has_mat else ''}")
|
||||
else:
|
||||
results.append(f" ✗ {step['id']} at ({bx},{by}): {r.get('errorMessage', r.get('error', 'unknown'))}")
|
||||
|
||||
dig = bp.get('dig', {'x': -1, 'y': -1, 'width': bp['size']['width'] + 2, 'height': bp['size']['height'] + 2})
|
||||
dig_result = api_post('/api/action/dig', {
|
||||
'x': origin_x + dig['x'],
|
||||
'y': origin_y + dig['y'],
|
||||
'width': dig['width'],
|
||||
'height': dig['height'],
|
||||
})
|
||||
status = 'OK' if 'error' not in dig_result else dig_result.get('error', 'fail')
|
||||
print(f" [Dig] area ({dig['width']}x{dig['height']}): {status}")
|
||||
print(f"\n--- {bp['name']} build results ---")
|
||||
for r in results:
|
||||
print(r)
|
||||
|
||||
for b in bp['buildings']:
|
||||
x = origin_x + b['x']
|
||||
y = origin_y + b['y']
|
||||
result = api_post('/api/action/build', {
|
||||
'buildingId': b['id'],
|
||||
'x': x,
|
||||
'y': y,
|
||||
})
|
||||
ok = 'error' not in result
|
||||
results.append({'building': b['id'], 'x': x, 'y': y, 'ok': ok})
|
||||
status = 'OK' if ok else result.get('error', 'fail')
|
||||
print(f" [Build] {b['id']:20s} at ({x:3d}, {y:3d}): {status}")
|
||||
|
||||
ok_count = sum(1 for r in results if r['ok'])
|
||||
print()
|
||||
print(f" Result: {ok_count}/{len(results)} buildings placed")
|
||||
return ok_count == len(results)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
cmd = sys.argv[1] if len(sys.argv) > 1 else 'list'
|
||||
|
||||
if cmd == 'list':
|
||||
list_blueprints()
|
||||
elif cmd == 'build':
|
||||
if len(sys.argv) < 4:
|
||||
print("Usage: python oni_builder.py build <blueprint_name> <origin_x> <origin_y>")
|
||||
print()
|
||||
list_blueprints()
|
||||
sys.exit(1)
|
||||
success = apply_blueprint(sys.argv[2], int(sys.argv[3]), int(sys.argv[4]))
|
||||
sys.exit(0 if success else 1)
|
||||
def main():
|
||||
if len(sys.argv) < 2 or sys.argv[1] in ("-h", "--help", "help"):
|
||||
print(__doc__)
|
||||
return
|
||||
cmd = sys.argv[1]
|
||||
args = sys.argv[2:]
|
||||
if cmd == "list":
|
||||
cmd_list(args)
|
||||
elif cmd == "show":
|
||||
cmd_show(args)
|
||||
elif cmd == "build":
|
||||
cmd_build(args)
|
||||
else:
|
||||
print("Usage: python oni_builder.py <list|build>")
|
||||
print(f"Unknown: {cmd}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user