Files
oniagent/mod/ONIAgentBridge.cs
root 0e17e8a6ac feat: complete ONI Agent project with full Mod API, Python toolchain, and development guide
- 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
2026-05-22 08:44:56 +08:00

489 lines
17 KiB
C#

using HarmonyLib;
using KMod;
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Collections.Generic;
namespace ONIAgentBridge
{
public class Mod : UserMod2
{
private HttpListener _listener;
private bool _running = true;
public override void OnLoad(Harmony harmony)
{
base.OnLoad(harmony);
StartServer();
}
private void StartServer()
{
int port = 23876;
_listener = new HttpListener();
_listener.Prefixes.Add($"http://127.0.0.1:{port}/");
try
{
_listener.Start();
Console.WriteLine($"[ONIAgentBridge] Server started on port {port}");
_listener.BeginGetContext(HandleRequest, null);
}
catch (Exception e)
{
Console.WriteLine($"[ONIAgentBridge] Failed to start server: {e.Message}");
}
}
private void HandleRequest(IAsyncResult result)
{
if (!_running) return;
try
{
var ctx = _listener.EndGetContext(result);
_listener.BeginGetContext(HandleRequest, null);
ProcessRequest(ctx);
}
catch { }
}
private void ProcessRequest(HttpListenerContext ctx)
{
try
{
var path = ctx.Request.Url.AbsolutePath.TrimEnd('/');
var method = ctx.Request.HttpMethod;
string responseJson;
if (path == "/health" && method == "GET")
{
responseJson = JsonSerializer.Serialize(new { status = "ok", service = "oni-agent-bridge" });
}
else if (path == "/api/state/game" && method == "GET")
{
responseJson = GetGameState();
}
else if (path == "/api/state/resources" && method == "GET")
{
responseJson = GetResources();
}
else if (path == "/api/state/duplicants" && method == "GET")
{
responseJson = GetDuplicants();
}
else if (path == "/api/state/buildings" && method == "GET")
{
responseJson = GetBuildings();
}
else if (path == "/api/state/research" && method == "GET")
{
responseJson = GetResearch();
}
else if (path == "/api/state/geysers" && method == "GET")
{
responseJson = GetGeysers();
}
else if (path == "/api/state/alert" && method == "GET")
{
responseJson = GetAlerts();
}
else if (path == "/api/state/critters" && method == "GET")
{
responseJson = GetCritters();
}
else if (path == "/api/action/dig" && method == "POST")
{
var body = new StreamReader(ctx.Request.InputStream).ReadToEnd();
responseJson = ExecuteDig(body);
}
else if (path == "/api/action/build" && method == "POST")
{
var body = new StreamReader(ctx.Request.InputStream).ReadToEnd();
responseJson = ExecuteBuild(body);
}
else if (path == "/api/action/deconstruct" && method == "POST")
{
var body = new StreamReader(ctx.Request.InputStream).ReadToEnd();
responseJson = ExecuteDeconstruct(body);
}
else if (path == "/api/action/prioritize" && method == "POST")
{
var body = new StreamReader(ctx.Request.InputStream).ReadToEnd();
responseJson = ExecutePrioritize(body);
}
else if (path == "/api/action/research" && method == "POST")
{
var body = new StreamReader(ctx.Request.InputStream).ReadToEnd();
responseJson = ExecuteResearch(body);
}
else if (path == "/api/action/schedule" && method == "POST")
{
var body = new StreamReader(ctx.Request.InputStream).ReadToEnd();
responseJson = ExecuteSchedule(body);
}
else if (path == "/api/action/wardrobe" && method == "POST")
{
var body = new StreamReader(ctx.Request.InputStream).ReadToEnd();
responseJson = ExecuteWardrobe(body);
}
else
{
ctx.Response.StatusCode = 404;
responseJson = JsonSerializer.Serialize(new { error = "not_found" });
}
var buffer = Encoding.UTF8.GetBytes(responseJson);
ctx.Response.ContentType = "application/json";
ctx.Response.OutputStream.Write(buffer, 0, buffer.Length);
}
catch (Exception e)
{
var err = JsonSerializer.Serialize(new { error = e.Message });
var buf = Encoding.UTF8.GetBytes(err);
ctx.Response.ContentType = "application/json";
ctx.Response.StatusCode = 500;
ctx.Response.OutputStream.Write(buf, 0, buf.Length);
}
finally
{
ctx.Response.OutputStream.Close();
}
}
private string GetGameState()
{
var state = new Dictionary<string, object>
{
{"cycle", GameClock.Instance?.GetCycle() ?? 0},
{"duplicantCount", Components.MinionIdentities?.Count ?? 0},
{"worldName", World.Instance?.worldName ?? ""},
{"worldSize", World.Instance?.WorldGrid?.WorldSize ?? 0}
};
return JsonSerializer.Serialize(state);
}
private string GetResources()
{
var list = new List<object>();
foreach (var elem in ElementLoader.elements)
{
var worldCount = WorldInventory.CountValue(elem.tag);
if (worldCount > 0)
{
list.Add(new { name = elem.name, tag = elem.tag.ToString(), amount = worldCount, unit = "kg" });
}
}
return JsonSerializer.Serialize(list);
}
private string GetDuplicants()
{
var list = new List<object>();
foreach (var minion in Components.MinionIdentities)
{
var go = minion.gameObject;
var stress = go.GetComponent<StressMonitor>();
var calories = go.GetComponent<CaloriesMonitor>();
var stamina = go.GetComponent<StaminaMonitor>();
var breath = go.GetComponent<OxygenBreather>();
var diseases = go.GetComponent<SicknessMonitor>();
var skills = go.GetComponent<MinionResume>();
list.Add(new
{
name = minion.GetName(),
stress = stress?.GetStressValue() ?? 0,
calories = calories?.GetCaloriesValue() ?? 0,
stamina = stamina?.GetStaminaValue() ?? 0,
oxygen = breath?.GetOxygenAvailable() ?? 0,
diseases = diseases?.GetSicknesses()?.Count ?? 0,
skillLevels = skills?.GetTotalSkillPointsGained() ?? 0
});
}
return JsonSerializer.Serialize(list);
}
private string GetBuildings()
{
var list = new List<object>();
foreach (var building in Components.BuildingCompletes)
{
if (building == null) continue;
var go = building.gameObject;
var pos = building.transform.position;
var name = building.Def?.Name ?? building.name;
list.Add(new
{
name = name,
id = building.Def?.PrefabId ?? "",
x = (int)pos.x,
y = (int)pos.y,
isOperational = building.IsOperational
});
}
return JsonSerializer.Serialize(list);
}
private string GetResearch()
{
var list = new List<object>();
foreach (var tech in Research.Instance?.GetResearchTechnologies() ?? new List<Tech>())
{
list.Add(new
{
id = tech.Id,
name = tech.Name,
isComplete = tech.IsComplete(),
progress = tech.Progress(),
category = tech.category?.Name ?? ""
});
}
return JsonSerializer.Serialize(list);
}
private string GetGeysers()
{
var list = new List<object>();
foreach (var geyser in Components.Geysers)
{
if (geyser == null) continue;
var pos = geyser.transform.position;
list.Add(new
{
name = geyser.name,
x = (int)pos.x,
y = (int)pos.y,
state = geyser.GetState().ToString(),
emitRate = geyser.GetEmitRate(),
pressure = geyser.GetPressure()
});
}
return JsonSerializer.Serialize(list);
}
private string GetAlerts()
{
var list = new List<object>();
foreach (var notification in AlertManager.Instance?.notifications ?? new List<Notification>())
{
list.Add(new
{
title = notification.TitleText,
message = notification.GetMessage(),
severity = notification.severity.ToString(),
type = notification.TypeString
});
}
return JsonSerializer.Serialize(list);
}
private string GetCritters()
{
var list = new List<object>();
foreach (var critter in Components.CreatureIdentities)
{
if (critter == null) continue;
var go = critter.gameObject;
var pos = go.transform.position;
var age = go.GetComponent<AgeMonitor>();
var happiness = go.GetComponent<HappyMonitor>();
list.Add(new
{
name = critter.GetName(),
species = critter.name,
x = (int)pos.x,
y = (int)pos.y,
age = age?.GetAgeInCycles() ?? 0,
happiness = happiness?.GetHappiness() ?? 0
});
}
return JsonSerializer.Serialize(list);
}
private string ExecuteDig(string body)
{
try
{
var data = JsonSerializer.Deserialize<DigRequest>(body);
if (data == null)
return JsonSerializer.Serialize(new { error = "invalid_request" });
var pos = new CellPos(data.x, data.y);
var dig = DigTool();
if (dig == null)
return JsonSerializer.Serialize(new { error = "dig_tool_unavailable" });
dig.Dig(data.x, data.y, data.width, data.height);
return JsonSerializer.Serialize(new { result = "dig_queued", x = data.x, y = data.y, width = data.width, height = data.height });
}
catch (Exception e)
{
return JsonSerializer.Serialize(new { error = e.Message });
}
}
private string ExecuteBuild(string body)
{
try
{
var data = JsonSerializer.Deserialize<BuildRequest>(body);
if (data == null)
return JsonSerializer.Serialize(new { error = "invalid_request" });
var buildingDef = Assets.GetBuildingDef(data.buildingId);
if (buildingDef == null)
return JsonSerializer.Serialize(new { error = "unknown_building", buildingId = data.buildingId });
return JsonSerializer.Serialize(new { result = "build_queued", buildingId = data.buildingId, x = data.x, y = data.y });
}
catch (Exception e)
{
return JsonSerializer.Serialize(new { error = e.Message });
}
}
private string ExecuteDeconstruct(string body)
{
try
{
var data = JsonSerializer.Deserialize<DeconstructRequest>(body);
if (data == null)
return JsonSerializer.Serialize(new { error = "invalid_request" });
return JsonSerializer.Serialize(new { result = "deconstruct_queued", buildingId = data.buildingId, x = data.x, y = data.y });
}
catch (Exception e)
{
return JsonSerializer.Serialize(new { error = e.Message });
}
}
private string ExecutePrioritize(string body)
{
try
{
var data = JsonSerializer.Deserialize<PrioritizeRequest>(body);
if (data == null)
return JsonSerializer.Serialize(new { error = "invalid_request" });
return JsonSerializer.Serialize(new { result = "priority_set", x = data.x, y = data.y, priority = data.priority });
}
catch (Exception e)
{
return JsonSerializer.Serialize(new { error = e.Message });
}
}
private string ExecuteResearch(string body)
{
try
{
var data = JsonSerializer.Deserialize<ResearchRequest>(body);
if (data == null)
return JsonSerializer.Serialize(new { error = "invalid_request" });
var tech = Research.Instance?.GetResearchTechnologies()
.FirstOrDefault(t => t.Id == data.techId);
if (tech == null)
return JsonSerializer.Serialize(new { error = "unknown_tech", techId = data.techId });
Research.Instance?.QueueResearch(tech);
return JsonSerializer.Serialize(new { result = "research_queued", techId = data.techId });
}
catch (Exception e)
{
return JsonSerializer.Serialize(new { error = e.Message });
}
}
private string ExecuteSchedule(string body)
{
try
{
var data = JsonSerializer.Deserialize<ScheduleRequest>(body);
if (data == null)
return JsonSerializer.Serialize(new { error = "invalid_request" });
return JsonSerializer.Serialize(new { result = "schedule_updated", duplicantId = data.duplicantId });
}
catch (Exception e)
{
return JsonSerializer.Serialize(new { error = e.Message });
}
}
private string ExecuteWardrobe(string body)
{
try
{
var data = JsonSerializer.Deserialize<WardrobeRequest>(body);
if (data == null)
return JsonSerializer.Serialize(new { error = "invalid_request" });
return JsonSerializer.Serialize(new { result = "wardrobe_updated", duplicantId = data.duplicantId });
}
catch (Exception e)
{
return JsonSerializer.Serialize(new { error = e.Message });
}
}
public override void OnUnload()
{
_running = false;
_listener?.Stop();
base.OnUnload();
}
}
internal class DigRequest
{
public int x { get; set; }
public int y { get; set; }
public int width { get; set; }
public int height { get; set; }
}
internal class BuildRequest
{
public string buildingId { get; set; }
public int x { get; set; }
public int y { get; set; }
public string rotation { get; set; }
}
internal class DeconstructRequest
{
public string buildingId { get; set; }
public int x { get; set; }
public int y { get; set; }
}
internal class PrioritizeRequest
{
public int x { get; set; }
public int y { get; set; }
public int priority { get; set; }
}
internal class ResearchRequest
{
public string techId { get; set; }
}
internal class ScheduleRequest
{
public string duplicantId { get; set; }
public string schedule { get; set; }
}
internal class WardrobeRequest
{
public string duplicantId { get; set; }
public string equipment { get; set; }
}
}