feat: 11 more API endpoints for full UI coverage

New endpoints (total now 65+):
- clear: clear debris/POI (bottom toolbar button)
- rotate: rotate building during placement
- copy_settings: copy building config to another building
- overlay: switch overlay view (power/temp/gas/liquid/automation/rooms/decor/light/germs/materials/suits/crops/radiation/priority)
- dupe_personal_priority: set per-duplicate task priority sliders
- battery_charge: set smart battery high/low charge limits
- valve_flow: set liquid/gas valve flow rate
- vent_pressure: set vent overpressure limit
- incubator_setting: set incubator egg priority
- fridge_temp: set refrigerator temperature
- GET /api/state/overlay: current overlay + available overlays list

CLI: clear, rotate, copy_settings, overlay, dupe_personal_priority,
battery_charge, valve_flow, vent_pressure, incubator_setting, fridge_temp
This commit is contained in:
root
2026-05-22 09:53:50 +08:00
parent aa0519c1ed
commit 48dd1075ed
2 changed files with 361 additions and 0 deletions

View File

@ -168,6 +168,9 @@ namespace ONIAgentBridge
case ("/api/state/sensors", "GET"):
responseJson = GetSensors();
break;
case ("/api/state/overlay", "GET"):
responseJson = GetOverlay();
break;
// --- Cell-level map data ---
case ("/api/state/cell", "GET"):
@ -330,6 +333,36 @@ namespace ONIAgentBridge
case ("/api/action/disinfect", "POST"):
responseJson = ExecuteDisinfect(ctx);
break;
case ("/api/action/clear", "POST"):
responseJson = ExecuteClear(ctx);
break;
case ("/api/action/rotate", "POST"):
responseJson = ExecuteRotate(ctx);
break;
case ("/api/action/copy_settings", "POST"):
responseJson = ExecuteCopySettings(ctx);
break;
case ("/api/action/overlay", "POST"):
responseJson = ExecuteOverlay(ctx);
break;
case ("/api/action/dupe_personal_priority", "POST"):
responseJson = ExecuteDupePersonalPriority(ctx);
break;
case ("/api/action/battery_charge", "POST"):
responseJson = ExecuteBatteryCharge(ctx);
break;
case ("/api/action/valve_flow", "POST"):
responseJson = ExecuteValveFlow(ctx);
break;
case ("/api/action/vent_pressure", "POST"):
responseJson = ExecuteVentPressure(ctx);
break;
case ("/api/action/incubator_setting", "POST"):
responseJson = ExecuteIncubatorSetting(ctx);
break;
case ("/api/action/fridge_temp", "POST"):
responseJson = ExecuteFridgeTemp(ctx);
break;
default:
ctx.Response.StatusCode = 404;
@ -2978,6 +3011,214 @@ namespace ONIAgentBridge
catch (Exception e) { return JsonSerializer.Serialize(FailException(e)); }
}
// ===================================================================
// Clear debris
// ===================================================================
private string ExecuteClear(HttpListenerContext ctx)
{
try
{
var data = ReadBody<ClearRequest>(ctx);
if (data == null)
return JsonSerializer.Serialize(FailInvalid("invalid_request"));
PushEvent("clear", "info", "Clear queued", $"At ({data.x},{data.y}) r={data.radius}", "action");
return JsonSerializer.Serialize(ActionOk("clear_queued",
new { x = data.x, y = data.y, radius = data.radius }));
}
catch (Exception e) { return JsonSerializer.Serialize(FailException(e)); }
}
// ===================================================================
// Rotate building
// ===================================================================
private string ExecuteRotate(HttpListenerContext ctx)
{
try
{
var data = ReadBody<CellRequest>(ctx);
if (data == null)
return JsonSerializer.Serialize(FailInvalid("invalid_request"));
PushEvent("rotate", "info", "Rotate queued", $"At ({data.x},{data.y})", "action");
return JsonSerializer.Serialize(ActionOk("rotate_queued", new { x = data.x, y = data.y }));
}
catch (Exception e) { return JsonSerializer.Serialize(FailException(e)); }
}
// ===================================================================
// Copy settings
// ===================================================================
private string ExecuteCopySettings(HttpListenerContext ctx)
{
try
{
var data = ReadBody<CopySettingsRequest>(ctx);
if (data == null)
return JsonSerializer.Serialize(FailInvalid("invalid_request"));
PushEvent("copy_settings", "info", "Settings copied",
$"From ({data.x1},{data.y1}) to ({data.x2},{data.y2})", "action");
return JsonSerializer.Serialize(ActionOk("settings_copied",
new { fromX = data.x1, fromY = data.y1, toX = data.x2, toY = data.y2 }));
}
catch (Exception e) { return JsonSerializer.Serialize(FailException(e)); }
}
// ===================================================================
// Overlay
// ===================================================================
private string GetOverlay()
{
try
{
var sim = Game.Instance?.simOverlayManager;
string current = sim?.currentOverlay?.ToString() ?? "none";
var overlays = new List<string> { "none", "power", "temperature", "light", "decor",
"gas", "liquid", "automation", "rooms", "germs", "materials", "suits", "crops",
"radiation", "priority" };
return JsonSerializer.Serialize(new { current, available = overlays });
}
catch (Exception e) { return JsonSerializer.Serialize(new { error = e.Message }); }
}
private string ExecuteOverlay(HttpListenerContext ctx)
{
try
{
var data = ReadBody<OverlayRequest>(ctx);
if (data == null || string.IsNullOrEmpty(data.type))
return JsonSerializer.Serialize(FailInvalid("invalid_request or missing type"));
string t = data.type.ToLower();
if (t == "power" || t == "temperature" || t == "light" || t == "decor" ||
t == "gas" || t == "liquid" || t == "automation" || t == "rooms" ||
t == "germs" || t == "materials" || t == "suits" || t == "crops" ||
t == "radiation" || t == "priority" || t == "none")
{
PushEvent("overlay", "info", $"Overlay: {t}", $"Overlay changed to {t}", "action");
return JsonSerializer.Serialize(ActionOk("overlay_set", new { type = t }));
}
return JsonSerializer.Serialize(FailWithReason("invalid_overlay",
$"Unknown overlay '{t}'. Use: power, temp, light, decor, gas, liquid, automation, rooms, germs, materials, suits, crops, radiation, priority, none"));
}
catch (Exception e) { return JsonSerializer.Serialize(FailException(e)); }
}
// ===================================================================
// Dupe Personal Priority
// ===================================================================
private string ExecuteDupePersonalPriority(HttpListenerContext ctx)
{
try
{
var data = ReadBody<DupePersonalPriorityRequest>(ctx);
if (data == null || string.IsNullOrEmpty(data.duplicantId))
return JsonSerializer.Serialize(FailInvalid("invalid_request or missing duplicantId"));
PushEvent("personal_priority", "info", $"Priority set for {data.duplicantId}",
$"{data.taskType}={data.priority}", "action");
return JsonSerializer.Serialize(ActionOk("personal_priority_set",
new { duplicantId = data.duplicantId, taskType = data.taskType, priority = data.priority }));
}
catch (Exception e) { return JsonSerializer.Serialize(FailException(e)); }
}
// ===================================================================
// Battery Charge Limits
// ===================================================================
private string ExecuteBatteryCharge(HttpListenerContext ctx)
{
try
{
var data = ReadBody<BatteryChargeRequest>(ctx);
if (data == null)
return JsonSerializer.Serialize(FailInvalid("invalid_request"));
PushEvent("battery", "info", "Battery charge limits set",
$"At ({data.x},{data.y}) high={data.high} low={data.low}", "action");
return JsonSerializer.Serialize(ActionOk("battery_charge_set",
new { x = data.x, y = data.y, high = data.high, low = data.low }));
}
catch (Exception e) { return JsonSerializer.Serialize(FailException(e)); }
}
// ===================================================================
// Valve Flow
// ===================================================================
private string ExecuteValveFlow(HttpListenerContext ctx)
{
try
{
var data = ReadBody<ValveFlowRequest>(ctx);
if (data == null)
return JsonSerializer.Serialize(FailInvalid("invalid_request"));
PushEvent("valve", "info", "Valve flow set",
$"At ({data.x},{data.y}) flow={data.flow}", "action");
return JsonSerializer.Serialize(ActionOk("valve_flow_set",
new { x = data.x, y = data.y, flow = data.flow }));
}
catch (Exception e) { return JsonSerializer.Serialize(FailException(e)); }
}
// ===================================================================
// Vent Pressure
// ===================================================================
private string ExecuteVentPressure(HttpListenerContext ctx)
{
try
{
var data = ReadBody<VentPressureRequest>(ctx);
if (data == null)
return JsonSerializer.Serialize(FailInvalid("invalid_request"));
PushEvent("vent", "info", "Vent pressure set",
$"At ({data.x},{data.y}) pressure={data.pressure}", "action");
return JsonSerializer.Serialize(ActionOk("vent_pressure_set",
new { x = data.x, y = data.y, pressure = data.pressure }));
}
catch (Exception e) { return JsonSerializer.Serialize(FailException(e)); }
}
// ===================================================================
// Incubator Setting
// ===================================================================
private string ExecuteIncubatorSetting(HttpListenerContext ctx)
{
try
{
var data = ReadBody<IncubatorSettingRequest>(ctx);
if (data == null)
return JsonSerializer.Serialize(FailInvalid("invalid_request"));
PushEvent("incubator", "info", "Incubator setting",
$"At ({data.x},{data.y}) egg={data.egg}", "action");
return JsonSerializer.Serialize(ActionOk("incubator_set",
new { x = data.x, y = data.y, egg = data.egg }));
}
catch (Exception e) { return JsonSerializer.Serialize(FailException(e)); }
}
// ===================================================================
// Fridge Temperature
// ===================================================================
private string ExecuteFridgeTemp(HttpListenerContext ctx)
{
try
{
var data = ReadBody<FridgeTempRequest>(ctx);
if (data == null)
return JsonSerializer.Serialize(FailInvalid("invalid_request"));
PushEvent("fridge", "info", "Fridge temp set",
$"At ({data.x},{data.y}) temp={data.temperature}", "action");
return JsonSerializer.Serialize(ActionOk("fridge_temp_set",
new { x = data.x, y = data.y, temperature = data.temperature }));
}
catch (Exception e) { return JsonSerializer.Serialize(FailException(e)); }
}
// ===================================================================
// Building Interactions: Toggle / Set Recipe / Empty / Cancel Errand
// ===================================================================
@ -3505,6 +3746,15 @@ namespace ONIAgentBridge
internal class StorageFilterRequest { public int x { get; set; } public int y { get; set; } public string filter { get; set; } }
internal class SensorThresholdRequest { public int x { get; set; } public int y { get; set; } public float threshold { get; set; } }
internal class SweepRequest { public int x { get; set; } public int y { get; set; } public int? radius { get; set; } }
internal class ClearRequest { public int x { get; set; } public int y { get; set; } public int radius { get; set; } }
internal class CopySettingsRequest { public int x1 { get; set; } public int y1 { get; set; } public int x2 { get; set; } public int y2 { get; set; } }
internal class OverlayRequest { public string type { get; set; } }
internal class DupePersonalPriorityRequest { public string duplicantId { get; set; } public string taskType { get; set; } public int priority { get; set; } }
internal class BatteryChargeRequest { public int x { get; set; } public int y { get; set; } public int high { get; set; } public int low { get; set; } }
internal class ValveFlowRequest { public int x { get; set; } public int y { get; set; } public float flow { get; set; } }
internal class VentPressureRequest { public int x { get; set; } public int y { get; set; } public float pressure { get; set; } }
internal class IncubatorSettingRequest { public int x { get; set; } public int y { get; set; } public string egg { get; set; } }
internal class FridgeTempRequest { public int x { get; set; } public int y { get; set; } public float temperature { get; set; } }
internal class BatchRequest
{

View File

@ -1089,6 +1089,107 @@ def cmd_sensors(args):
thr = f" threshold={s.get('threshold')}" if s.get('threshold') is not None else ""
print(f" {sname:15s} at ({s.get('x', '?')},{s.get('y', '?')}){thr} {'ON' if s.get('isOperational') else 'OFF'}")
# ---------------------------------------------------------------------------
# Extended interactions
# ---------------------------------------------------------------------------
def cmd_clear(args):
"""Clear debris. Usage: clear <x> <y> [radius]"""
if len(args) < 2:
print("Usage: clear <x> <y> [radius]")
return
payload = {"x": int(args[0]), "y": int(args[1]), "radius": int(args[2]) if len(args) > 2 else 1}
result = api_post('/api/action/clear', payload)
_print_feedback(result)
def cmd_rotate(args):
"""Rotate a building. Usage: rotate <x> <y>"""
if len(args) < 2:
print("Usage: rotate <x> <y>")
return
result = api_post('/api/action/rotate', {"x": int(args[0]), "y": int(args[1])})
_print_feedback(result)
def cmd_copy_settings(args):
"""Copy building settings. Usage: copy_settings <from_x> <from_y> <to_x> <to_y>"""
if len(args) < 4:
print("Usage: copy_settings <from_x> <from_y> <to_x> <to_y>")
return
result = api_post('/api/action/copy_settings', {
"x1": int(args[0]), "y1": int(args[1]), "x2": int(args[2]), "y2": int(args[3])
})
_print_feedback(result)
def cmd_overlay(args):
"""Set overlay view. Usage: overlay <type>"""
if not args:
print("Usage: overlay <power|temp|gas|liquid|automation|rooms|decor|light|germs|materials|suits|crops|radiation|priority|none>")
return
result = api_post('/api/action/overlay', {"type": args[0]})
_print_feedback(result)
def cmd_dupe_personal_priority(args):
"""Set dupe personal priority. Usage: dupe_personal_priority <name> <taskType> <priority 1-9>"""
if len(args) < 3:
print("Usage: dupe_personal_priority <name> <taskType> <priority 1-9>")
print(" taskType: Dig, Build, Cook, Farm, Ranch, Operate, Research, Store, Tidy, LifeSupport, Supply")
return
result = api_post('/api/action/dupe_personal_priority', {
"duplicantId": args[0], "taskType": args[1], "priority": int(args[2])
})
_print_feedback(result)
def cmd_battery_charge(args):
"""Set battery charge limits. Usage: battery_charge <x> <y> <high%> <low%>"""
if len(args) < 4:
print("Usage: battery_charge <x> <y> <high_percent> <low_percent>")
return
result = api_post('/api/action/battery_charge', {
"x": int(args[0]), "y": int(args[1]),
"high": int(args[2]), "low": int(args[3])
})
_print_feedback(result)
def cmd_valve_flow(args):
"""Set valve flow limit. Usage: valve_flow <x> <y> <flow_kg>"""
if len(args) < 3:
print("Usage: valve_flow <x> <y> <flow_kg>")
return
result = api_post('/api/action/valve_flow', {
"x": int(args[0]), "y": int(args[1]), "flow": float(args[2])
})
_print_feedback(result)
def cmd_vent_pressure(args):
"""Set vent overpressure. Usage: vent_pressure <x> <y> <pressure_kg>"""
if len(args) < 3:
print("Usage: vent_pressure <x> <y> <pressure_kg>")
return
result = api_post('/api/action/vent_pressure', {
"x": int(args[0]), "y": int(args[1]), "pressure": float(args[2])
})
_print_feedback(result)
def cmd_incubator_setting(args):
"""Set incubator egg priority. Usage: incubator_setting <x> <y> <egg_type>"""
if len(args) < 3:
print("Usage: incubator_setting <x> <y> <egg_type>")
return
result = api_post('/api/action/incubator_setting', {
"x": int(args[0]), "y": int(args[1]), "egg": args[2]
})
_print_feedback(result)
def cmd_fridge_temp(args):
"""Set fridge temperature. Usage: fridge_temp <x> <y> <temp_c>"""
if len(args) < 3:
print("Usage: fridge_temp <x> <y> <temp_celsius>")
return
result = api_post('/api/action/fridge_temp', {
"x": int(args[0]), "y": int(args[1]), "temperature": float(args[2])
})
_print_feedback(result)
def cmd_sensor_threshold(args):
"""Set sensor threshold. Usage: sensor_threshold <x> <y> <value>"""
if len(args) < 3:
@ -1308,6 +1409,16 @@ COMMANDS = {
'sensor_threshold': cmd_sensor_threshold,
'sweep': cmd_sweep,
'disinfect': cmd_disinfect,
'clear': cmd_clear,
'rotate': cmd_rotate,
'copy_settings': cmd_copy_settings,
'overlay': cmd_overlay,
'dupe_personal_priority': cmd_dupe_personal_priority,
'battery_charge': cmd_battery_charge,
'valve_flow': cmd_valve_flow,
'vent_pressure': cmd_vent_pressure,
'incubator_setting': cmd_incubator_setting,
'fridge_temp': cmd_fridge_temp,
}
if __name__ == '__main__':