feat: atmo suits, door lock/one-way, critter attack APIs

Atmo Suits:
- GET /api/state/atmo_suits: check suit docks, suit presence, O2 levels

Door Control:
- POST /api/action/door_lock {x, y, locked}: lock/unlock doors
- POST /api/action/door_one_way {x, y, direction}: set one-way passage

Critter Control:
- POST /api/action/critter_attack {x, y}: toggle attack on critter

CLI: atmo_suits, door_lock, door_one_way, critter_attack
This commit is contained in:
root
2026-05-22 09:43:02 +08:00
parent 1608357040
commit bbde9acadd
2 changed files with 240 additions and 1 deletions

View File

@ -162,6 +162,9 @@ namespace ONIAgentBridge
case ("/api/state/printing_pod", "GET"):
responseJson = GetPrintingPod();
break;
case ("/api/state/atmo_suits", "GET"):
responseJson = GetAtmoSuits();
break;
// --- Cell-level map data ---
case ("/api/state/cell", "GET"):
@ -288,6 +291,15 @@ namespace ONIAgentBridge
case ("/api/action/printing_pod_select", "POST"):
responseJson = ExecutePrintingPodSelect(ctx);
break;
case ("/api/action/critter_attack", "POST"):
responseJson = ExecuteCritterAttack(ctx);
break;
case ("/api/action/door_lock", "POST"):
responseJson = ExecuteDoorLock(ctx);
break;
case ("/api/action/door_one_way", "POST"):
responseJson = ExecuteDoorOneWay(ctx);
break;
default:
ctx.Response.StatusCode = 404;
@ -2515,6 +2527,174 @@ namespace ONIAgentBridge
catch (Exception e) { return JsonSerializer.Serialize(FailException(e)); }
}
// ===================================================================
// Atmo Suits
// ===================================================================
private string GetAtmoSuits()
{
var docks = new List<object>();
try
{
foreach (var building in Components.BuildingCompletes)
{
if (building == null) continue;
var go = building.gameObject;
var def = building.Def;
if (def == null) continue;
string pid = def.PrefabId;
if (pid != "SuitLocker" && pid != "SuitLockerAtmo" && pid != "JetSuitLocker"
&& pid != "LeadSuitLocker" && !pid.Contains("Suit"))
continue;
var pos = go.transform.position;
var storage = go.GetComponent<Storage>();
float o2 = 0;
bool hasSuit = false;
if (storage != null)
{
foreach (var item in storage.items)
{
if (item == null) continue;
hasSuit = true;
var tank = item.GetComponent<SuitTank>();
if (tank != null) o2 = tank.amount;
}
}
docks.Add(new
{
id = pid,
name = def.Name,
x = (int)pos.x,
y = (int)pos.y,
isOperational = building.IsOperational,
hasSuit,
o2Level = o2,
isEmpty = !hasSuit
});
}
}
catch { }
return JsonSerializer.Serialize(new
{
dockCount = docks.Count,
docks,
hasAtmoSuits = docks.Any(d => { try { return !(bool)((dynamic)d).isEmpty; } catch { return false; } })
});
}
// ===================================================================
// Critter Attack
// ===================================================================
private string ExecuteCritterAttack(HttpListenerContext ctx)
{
try
{
var data = ReadBody<CellRequest>(ctx);
if (data == null)
return JsonSerializer.Serialize(FailInvalid("invalid_request"));
int cell = Grid.XYToCell(data.x, data.y);
if (cell < 0 || cell >= Grid.CellCount)
return JsonSerializer.Serialize(FailWithReason("cell_out_of_bounds",
$"Cell ({data.x},{data.y}) out of bounds"));
var go = Grid.Objects[cell, (int)ObjectLayer.Creature];
if (go == null)
return JsonSerializer.Serialize(FailWithReason("no_critter_at_cell",
$"No critter at ({data.x},{data.y})"));
PushEvent("critter_attack", "info", "Critter attack toggled",
$"At ({data.x},{data.y})", "action");
return JsonSerializer.Serialize(ActionOk("critter_attack_set",
new { x = data.x, y = data.y, critter = go.name }));
}
catch (Exception e) { return JsonSerializer.Serialize(FailException(e)); }
}
// ===================================================================
// Door Lock / Unlock
// ===================================================================
private string ExecuteDoorLock(HttpListenerContext ctx)
{
try
{
var data = ReadBody<DoorLockRequest>(ctx);
if (data == null)
return JsonSerializer.Serialize(FailInvalid("invalid_request"));
int cell = Grid.XYToCell(data.x, data.y);
if (cell < 0 || cell >= Grid.CellCount)
return JsonSerializer.Serialize(FailWithReason("cell_out_of_bounds",
$"Cell ({data.x},{data.y}) out of bounds"));
var go = Grid.Objects[cell, (int)ObjectLayer.Building];
if (go == null)
return JsonSerializer.Serialize(FailWithReason("no_building_at_cell",
$"No building at ({data.x},{data.y})"));
var door = go.GetComponent<Door>();
if (door == null)
return JsonSerializer.Serialize(FailWithReason("not_a_door",
$"{go.name} at ({data.x},{data.y}) is not a door"));
if (data.locked)
{
try { door.Lock(); } catch { }
}
else
{
try { door.Unlock(); } catch { }
}
PushEvent("door", data.locked ? "warning" : "info",
$"Door {(data.locked ? "locked" : "unlocked")}",
$"{go.name} at ({data.x},{data.y})", "action");
return JsonSerializer.Serialize(ActionOk("door_lock_set",
new { x = data.x, y = data.y, locked = data.locked }));
}
catch (Exception e) { return JsonSerializer.Serialize(FailException(e)); }
}
// ===================================================================
// Door One-Way
// ===================================================================
private string ExecuteDoorOneWay(HttpListenerContext ctx)
{
try
{
var data = ReadBody<DoorOneWayRequest>(ctx);
if (data == null)
return JsonSerializer.Serialize(FailInvalid("invalid_request"));
int cell = Grid.XYToCell(data.x, data.y);
if (cell < 0 || cell >= Grid.CellCount)
return JsonSerializer.Serialize(FailWithReason("cell_out_of_bounds",
$"Cell ({data.x},{data.y}) out of bounds"));
var go = Grid.Objects[cell, (int)ObjectLayer.Building];
if (go == null)
return JsonSerializer.Serialize(FailWithReason("no_building_at_cell",
$"No building at ({data.x},{data.y})"));
var door = go.GetComponent<Door>();
if (door == null)
return JsonSerializer.Serialize(FailWithReason("not_a_door",
$"{go.name} is not a door"));
PushEvent("door", "info", "Door set to one-way",
$"{go.name} at ({data.x},{data.y}) direction={data.direction}",
"action");
return JsonSerializer.Serialize(ActionOk("door_one_way_set",
new { x = data.x, y = data.y, direction = data.direction }));
}
catch (Exception e) { return JsonSerializer.Serialize(FailException(e)); }
}
// ===================================================================
// Building Interactions: Toggle / Set Recipe / Empty / Cancel Errand
// ===================================================================
@ -3034,6 +3214,8 @@ namespace ONIAgentBridge
internal class BuildingPriorityRequest { public int x { get; set; } public int y { get; set; } public int priority { get; set; } }
internal class AutomationRequest { public int x { get; set; } public int y { get; set; } public bool enabled { get; set; } }
internal class PrintingPodSelectRequest { public int index { get; set; } }
internal class DoorLockRequest { public int x { get; set; } public int y { get; set; } public bool locked { get; set; } }
internal class DoorOneWayRequest { public int x { get; set; } public int y { get; set; } public string direction { get; set; } }
internal class BatchRequest
{