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 { {"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(); 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(); foreach (var minion in Components.MinionIdentities) { var go = minion.gameObject; var stress = go.GetComponent(); var calories = go.GetComponent(); var stamina = go.GetComponent(); var breath = go.GetComponent(); var diseases = go.GetComponent(); var skills = go.GetComponent(); 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(); 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(); foreach (var tech in Research.Instance?.GetResearchTechnologies() ?? new List()) { 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(); 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(); foreach (var notification in AlertManager.Instance?.notifications ?? new List()) { 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(); foreach (var critter in Components.CreatureIdentities) { if (critter == null) continue; var go = critter.gameObject; var pos = go.transform.position; var age = go.GetComponent(); var happiness = go.GetComponent(); 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(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(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(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(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(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(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(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; } } }