diff --git a/mod/ONIAgentBridge.cs b/mod/ONIAgentBridge.cs index 268fcdd..5ffc628 100644 --- a/mod/ONIAgentBridge.cs +++ b/mod/ONIAgentBridge.cs @@ -76,298 +76,481 @@ namespace ONIAgentBridge string responseJson; - switch (path, method) - { + bool _handled = false; // --- Health --- - case ("/health", "GET"): + if (path == "/health" && method == "GET") + { + _handled = true; responseJson = JsonSerializer.Serialize(new { status = "ok", service = "oni-agent-bridge" }); - break; + } // --- State Queries --- - case ("/api/state/game", "GET"): + if (path == "/api/state/game" && method == "GET") + { + _handled = true; responseJson = GetGameState(); - break; - case ("/api/state/resources", "GET"): + } + if (path == "/api/state/resources" && method == "GET") + { + _handled = true; responseJson = GetResources(); - break; - case ("/api/state/duplicants", "GET"): + } + if (path == "/api/state/duplicants" && method == "GET") + { + _handled = true; responseJson = GetDuplicants(); - break; - case ("/api/state/buildings", "GET"): + } + if (path == "/api/state/buildings" && method == "GET") + { + _handled = true; responseJson = GetBuildings(); - break; - case ("/api/state/research", "GET"): + } + if (path == "/api/state/research" && method == "GET") + { + _handled = true; responseJson = GetResearch(); - break; - case ("/api/state/research/detail", "GET"): + } + if (path == "/api/state/research/detail" && method == "GET") + { + _handled = true; responseJson = GetResearchDetail(); - break; - case ("/api/state/building_detail", "GET"): + } + if (path == "/api/state/building_detail" && method == "GET") + { + _handled = true; responseJson = GetBuildingDetail(query); - break; - case ("/api/state/geysers", "GET"): + } + if (path == "/api/state/geysers" && method == "GET") + { + _handled = true; responseJson = GetGeysers(); - break; - case ("/api/state/alert", "GET"): + } + if (path == "/api/state/alert" && method == "GET") + { + _handled = true; responseJson = GetAlerts(); - break; - case ("/api/state/critters", "GET"): + } + if (path == "/api/state/critters" && method == "GET") + { + _handled = true; responseJson = GetCritters(); - break; - case ("/api/state/plants", "GET"): + } + if (path == "/api/state/plants" && method == "GET") + { + _handled = true; responseJson = GetPlants(); - break; - case ("/api/state/rooms", "GET"): + } + if (path == "/api/state/rooms" && method == "GET") + { + _handled = true; responseJson = GetRooms(); - break; - case ("/api/state/queue", "GET"): + } + if (path == "/api/state/queue" && method == "GET") + { + _handled = true; responseJson = GetTaskQueue(query); - break; - case ("/api/state/priorities", "GET"): + } + if (path == "/api/state/priorities" && method == "GET") + { + _handled = true; responseJson = GetPriorities(); - break; - case ("/api/state/events", "GET"): + } + if (path == "/api/state/events" && method == "GET") + { + _handled = true; responseJson = GetEvents(query); - break; - case ("/api/state/power", "GET"): + } + if (path == "/api/state/power" && method == "GET") + { + _handled = true; responseJson = GetPowerGrid(); - break; - case ("/api/state/pipes", "GET"): + } + if (path == "/api/state/pipes" && method == "GET") + { + _handled = true; responseJson = GetPipes(query); - break; - case ("/api/state/co2", "GET"): + } + if (path == "/api/state/co2" && method == "GET") + { + _handled = true; responseJson = GetCO2(); - break; - case ("/api/state/temperature/zones", "GET"): + } + if (path == "/api/state/temperature/zones" && method == "GET") + { + _handled = true; responseJson = GetTempZones(); - break; - case ("/api/state/morale", "GET"): + } + if (path == "/api/state/morale" && method == "GET") + { + _handled = true; responseJson = GetMorale(); - break; - case ("/api/state/diseases", "GET"): + } + if (path == "/api/state/diseases" && method == "GET") + { + _handled = true; responseJson = GetDiseases(); - break; - case ("/api/state/storage", "GET"): + } + if (path == "/api/state/storage" && method == "GET") + { + _handled = true; responseJson = GetStorage(); - break; - case ("/api/state/duplicants/skills", "GET"): + } + if (path == "/api/state/duplicants/skills" && method == "GET") + { + _handled = true; responseJson = GetDuplicantSkills(); - break; - case ("/api/state/saves", "GET"): + } + if (path == "/api/state/saves" && method == "GET") + { + _handled = true; responseJson = GetSaves(); - break; - case ("/api/state/buildable", "GET"): + } + if (path == "/api/state/buildable" && method == "GET") + { + _handled = true; responseJson = GetBuildable(); - break; - case ("/api/state/printing_pod", "GET"): + } + if (path == "/api/state/printing_pod" && method == "GET") + { + _handled = true; responseJson = GetPrintingPod(); - break; - case ("/api/state/atmo_suits", "GET"): + } + if (path == "/api/state/atmo_suits" && method == "GET") + { + _handled = true; responseJson = GetAtmoSuits(); - break; - case ("/api/state/sensors", "GET"): + } + if (path == "/api/state/sensors" && method == "GET") + { + _handled = true; responseJson = GetSensors(); - break; - case ("/api/state/overlay", "GET"): + } + if (path == "/api/state/overlay" && method == "GET") + { + _handled = true; responseJson = GetOverlay(); - break; + } // --- Cell-level map data --- - case ("/api/state/cell", "GET"): + if (path == "/api/state/cell" && method == "GET") + { + _handled = true; responseJson = GetCell(query); - break; - case ("/api/state/cells", "GET"): + } + if (path == "/api/state/cells" && method == "GET") + { + _handled = true; responseJson = GetCells(query); - break; - case ("/api/state/cells/slice", "GET"): + } + if (path == "/api/state/cells/slice" && method == "GET") + { + _handled = true; responseJson = GetCellSlice(query); - break; - case ("/api/state/gas", "GET"): + } + if (path == "/api/state/gas" && method == "GET") + { + _handled = true; responseJson = GetGas(query); - break; + } // --- Entity Registry (for AI reference) --- - case ("/api/registry/buildings", "GET"): + if (path == "/api/registry/buildings" && method == "GET") + { + _handled = true; responseJson = GetBuildingRegistry(); - break; - case ("/api/registry/elements", "GET"): + } + if (path == "/api/registry/elements" && method == "GET") + { + _handled = true; responseJson = GetElementRegistry(); - break; - case ("/api/registry/techs", "GET"): + } + if (path == "/api/registry/techs" && method == "GET") + { + _handled = true; responseJson = GetTechRegistry(); - break; - case ("/api/registry/priorities", "GET"): + } + if (path == "/api/registry/priorities" && method == "GET") + { + _handled = true; responseJson = GetPriorityRegistry(); - break; + } // --- Actions --- - case ("/api/action/dig", "POST"): + if (path == "/api/action/dig" && method == "POST") + { + _handled = true; responseJson = ExecuteDig(ctx); - break; - case ("/api/action/build", "POST"): + } + if (path == "/api/action/build" && method == "POST") + { + _handled = true; responseJson = ExecuteBuild(ctx); - break; - case ("/api/action/deconstruct", "POST"): + } + if (path == "/api/action/deconstruct" && method == "POST") + { + _handled = true; responseJson = ExecuteDeconstruct(ctx); - break; - case ("/api/action/prioritize", "POST"): + } + if (path == "/api/action/prioritize" && method == "POST") + { + _handled = true; responseJson = ExecutePrioritize(ctx); - break; - case ("/api/action/research", "POST"): + } + if (path == "/api/action/research" && method == "POST") + { + _handled = true; responseJson = ExecuteResearch(ctx); - break; - case ("/api/action/schedule", "POST"): + } + if (path == "/api/action/schedule" && method == "POST") + { + _handled = true; responseJson = ExecuteSchedule(ctx); - break; - case ("/api/action/wardrobe", "POST"): + } + if (path == "/api/action/wardrobe" && method == "POST") + { + _handled = true; responseJson = ExecuteWardrobe(ctx); - break; - case ("/api/action/mop", "POST"): + } + if (path == "/api/action/mop" && method == "POST") + { + _handled = true; responseJson = ExecuteMop(ctx); - break; - case ("/api/action/harvest", "POST"): + } + if (path == "/api/action/harvest" && method == "POST") + { + _handled = true; responseJson = ExecuteHarvest(ctx); - break; - case ("/api/action/cancel", "POST"): + } + if (path == "/api/action/cancel" && method == "POST") + { + _handled = true; responseJson = ExecuteCancel(ctx); - break; - case ("/api/action/batch", "POST"): + } + if (path == "/api/action/batch" && method == "POST") + { + _handled = true; responseJson = ExecuteBatch(ctx); - break; - case ("/api/action/priority_global", "POST"): + } + if (path == "/api/action/priority_global" && method == "POST") + { + _handled = true; responseJson = ExecutePriorityGlobal(ctx); - break; - case ("/api/action/priority_type", "POST"): + } + if (path == "/api/action/priority_type" && method == "POST") + { + _handled = true; responseJson = ExecutePriorityType(ctx); - break; - case ("/api/action/pause", "POST"): + } + if (path == "/api/action/pause" && method == "POST") + { + _handled = true; responseJson = ExecutePause(ctx); - break; - case ("/api/action/unpause", "POST"): + } + if (path == "/api/action/unpause" && method == "POST") + { + _handled = true; responseJson = ExecuteUnpause(ctx); - break; - case ("/api/action/speed", "POST"): + } + if (path == "/api/action/speed" && method == "POST") + { + _handled = true; responseJson = ExecuteSpeed(ctx); - break; - case ("/api/action/save", "POST"): + } + if (path == "/api/action/save" && method == "POST") + { + _handled = true; responseJson = ExecuteSave(ctx); - break; - case ("/api/action/save_as", "POST"): + } + if (path == "/api/action/save_as" && method == "POST") + { + _handled = true; responseJson = ExecuteSaveAs(ctx); - break; - case ("/api/action/load", "POST"): + } + if (path == "/api/action/load" && method == "POST") + { + _handled = true; responseJson = ExecuteLoad(ctx); - break; - case ("/api/action/assign_job", "POST"): + } + if (path == "/api/action/assign_job" && method == "POST") + { + _handled = true; responseJson = ExecuteAssignJob(ctx); - break; - case ("/api/action/build_pipe", "POST"): + } + if (path == "/api/action/build_pipe" && method == "POST") + { + _handled = true; responseJson = ExecuteBuildPipe(ctx); - break; - case ("/api/action/build_wire", "POST"): + } + if (path == "/api/action/build_wire" && method == "POST") + { + _handled = true; responseJson = ExecuteBuildWire(ctx); - break; - case ("/api/action/screenshot", "POST"): + } + if (path == "/api/action/screenshot" && method == "POST") + { + _handled = true; responseJson = ExecuteScreenshot(ctx); - break; - case ("/api/action/camera", "POST"): + } + if (path == "/api/action/camera" && method == "POST") + { + _handled = true; responseJson = ExecuteCamera(ctx); - break; - case ("/api/action/toggle", "POST"): + } + if (path == "/api/action/toggle" && method == "POST") + { + _handled = true; responseJson = ExecuteToggle(ctx); - break; - case ("/api/action/set_recipe", "POST"): + } + if (path == "/api/action/set_recipe" && method == "POST") + { + _handled = true; responseJson = ExecuteSetRecipe(ctx); - break; - case ("/api/action/empty", "POST"): + } + if (path == "/api/action/empty" && method == "POST") + { + _handled = true; responseJson = ExecuteEmpty(ctx); - break; - case ("/api/action/cancel_errand", "POST"): + } + if (path == "/api/action/cancel_errand" && method == "POST") + { + _handled = true; responseJson = ExecuteCancelErrand(ctx); - break; - case ("/api/action/research_cancel", "POST"): + } + if (path == "/api/action/research_cancel" && method == "POST") + { + _handled = true; responseJson = ExecuteResearchCancel(ctx); - break; - case ("/api/action/set_building_priority", "POST"): + } + if (path == "/api/action/set_building_priority" && method == "POST") + { + _handled = true; responseJson = ExecuteSetBuildingPriority(ctx); - break; - case ("/api/action/set_automation", "POST"): + } + if (path == "/api/action/set_automation" && method == "POST") + { + _handled = true; responseJson = ExecuteSetAutomation(ctx); - break; - case ("/api/action/printing_pod_select", "POST"): + } + if (path == "/api/action/printing_pod_select" && method == "POST") + { + _handled = true; responseJson = ExecutePrintingPodSelect(ctx); - break; - case ("/api/action/critter_attack", "POST"): + } + if (path == "/api/action/critter_attack" && method == "POST") + { + _handled = true; responseJson = ExecuteCritterAttack(ctx); - break; - case ("/api/action/door_lock", "POST"): + } + if (path == "/api/action/door_lock" && method == "POST") + { + _handled = true; responseJson = ExecuteDoorLock(ctx); - break; - case ("/api/action/door_one_way", "POST"): + } + if (path == "/api/action/door_one_way" && method == "POST") + { + _handled = true; responseJson = ExecuteDoorOneWay(ctx); - break; - case ("/api/action/dupe_move", "POST"): + } + if (path == "/api/action/dupe_move" && method == "POST") + { + _handled = true; responseJson = ExecuteDupeMove(ctx); - break; - case ("/api/action/dupe_cancel_task", "POST"): + } + if (path == "/api/action/dupe_cancel_task" && method == "POST") + { + _handled = true; responseJson = ExecuteDupeCancelTask(ctx); - break; - case ("/api/action/critter_wrangle", "POST"): + } + if (path == "/api/action/critter_wrangle" && method == "POST") + { + _handled = true; responseJson = ExecuteCritterWrangle(ctx); - break; - case ("/api/action/plant_uproot", "POST"): + } + if (path == "/api/action/plant_uproot" && method == "POST") + { + _handled = true; responseJson = ExecutePlantUproot(ctx); - break; - case ("/api/action/storage_filter", "POST"): + } + if (path == "/api/action/storage_filter" && method == "POST") + { + _handled = true; responseJson = ExecuteStorageFilter(ctx); - break; - case ("/api/action/door_open", "POST"): + } + if (path == "/api/action/door_open" && method == "POST") + { + _handled = true; responseJson = ExecuteDoorOpen(ctx); - break; - case ("/api/action/sensor_threshold", "POST"): + } + if (path == "/api/action/sensor_threshold" && method == "POST") + { + _handled = true; responseJson = ExecuteSensorThreshold(ctx); - break; - case ("/api/action/sweep", "POST"): + } + if (path == "/api/action/sweep" && method == "POST") + { + _handled = true; responseJson = ExecuteSweep(ctx); - break; - case ("/api/action/disinfect", "POST"): + } + if (path == "/api/action/disinfect" && method == "POST") + { + _handled = true; responseJson = ExecuteDisinfect(ctx); - break; - case ("/api/action/clear", "POST"): + } + if (path == "/api/action/clear" && method == "POST") + { + _handled = true; responseJson = ExecuteClear(ctx); - break; - case ("/api/action/rotate", "POST"): + } + if (path == "/api/action/rotate" && method == "POST") + { + _handled = true; responseJson = ExecuteRotate(ctx); - break; - case ("/api/action/copy_settings", "POST"): + } + if (path == "/api/action/copy_settings" && method == "POST") + { + _handled = true; responseJson = ExecuteCopySettings(ctx); - break; - case ("/api/action/overlay", "POST"): + } + if (path == "/api/action/overlay" && method == "POST") + { + _handled = true; responseJson = ExecuteOverlay(ctx); - break; - case ("/api/action/dupe_personal_priority", "POST"): + } + if (path == "/api/action/dupe_personal_priority" && method == "POST") + { + _handled = true; responseJson = ExecuteDupePersonalPriority(ctx); - break; - case ("/api/action/battery_charge", "POST"): + } + if (path == "/api/action/battery_charge" && method == "POST") + { + _handled = true; responseJson = ExecuteBatteryCharge(ctx); - break; - case ("/api/action/valve_flow", "POST"): + } + if (path == "/api/action/valve_flow" && method == "POST") + { + _handled = true; responseJson = ExecuteValveFlow(ctx); - break; - case ("/api/action/vent_pressure", "POST"): + } + if (path == "/api/action/vent_pressure" && method == "POST") + { + _handled = true; responseJson = ExecuteVentPressure(ctx); - break; - case ("/api/action/incubator_setting", "POST"): + } + if (path == "/api/action/incubator_setting" && method == "POST") + { + _handled = true; responseJson = ExecuteIncubatorSetting(ctx); - break; - case ("/api/action/fridge_temp", "POST"): + } + if (path == "/api/action/fridge_temp" && method == "POST") + { + _handled = true; responseJson = ExecuteFridgeTemp(ctx); - break; + } - default: + if (!_handled) + { ctx.Response.StatusCode = 404; responseJson = JsonSerializer.Serialize(new { error = "not_found", path = path, method = method }); - break; } var buffer = Encoding.UTF8.GetBytes(responseJson); @@ -1011,18 +1194,17 @@ namespace ONIAgentBridge var levels = new List(); for (int i = 1; i <= 9; i++) { - string label = i switch - { - 1 => "Lowest (only idle dupes)", - 2 => "Very Low", - 3 => "Low", - 4 => "Below Normal", - 5 => "Normal (default)", - 6 => "Above Normal", - 7 => "High", - 8 => "Very High", - 9 => "Emergency / Yellow Alert" - }; + string label; + if (i == 1) label = "Lowest (only idle dupes)"; + else if (i == 2) label = "Very Low"; + else if (i == 3) label = "Low"; + else if (i == 4) label = "Below Normal"; + else if (i == 5) label = "Normal (default)"; + else if (i == 6) label = "Above Normal"; + else if (i == 7) label = "High"; + else if (i == 8) label = "Very High"; + else if (i == 9) label = "Emergency / Yellow Alert"; + else label = "Normal"; levels.Add(new { priority = i, label, isYellowAlert = i == 9 }); } return JsonSerializer.Serialize(levels); @@ -1848,24 +2030,54 @@ namespace ONIAgentBridge foreach (var kv in dict) fb.data[kv.Key] = kv.Value; } // Auto-suggest for common errors - fb.suggestion = error switch + // Map error codes to suggestions + switch (error) { - "cell_occupied" => "Choose a different location, or deconstruct the existing building first", - "cell_solid" => "Use dig action first to clear the area", - "cell_occupied_by_dupe" => "Wait for the duplicant to move, or cancel their current task", - "material_shortage" => "Check resource availability and produce or deliver the required material", - "cell_out_of_bounds" => "Stay within the playable area", - "unknown_building" => "Use 'registry buildings' to find valid building IDs", - "unknown_tech" => "Use 'registry techs' to find valid tech IDs", - "missing_prerequisites" => "Research the prerequisite technologies first", - "tech_already_complete" => "The technology has already been researched", - "no_building_at_cell" => "Use the buildings command to find buildings and their coordinates", - "no_liquid_at_cell" => "Use the cell command to check what is at that location", - "liquid_too_thin" => "Wait for more liquid to accumulate before mopping", - "invalid_priority" => "Use a value between 1 (lowest) and 9 (emergency/yellow alert)", - "blocks_not_diggable" => "Neutronium borders and abyssalite cannot be dug", - _ => null - }; + case "cell_occupied": + fb.suggestion = "Choose a different location, or deconstruct the existing building first"; + break; + case "cell_solid": + fb.suggestion = "Use dig action first to clear the area"; + break; + case "cell_occupied_by_dupe": + fb.suggestion = "Wait for the duplicant to move, or cancel their current task"; + break; + case "material_shortage": + fb.suggestion = "Check resource availability and produce or deliver the required material"; + break; + case "cell_out_of_bounds": + fb.suggestion = "Stay within the playable area"; + break; + case "unknown_building": + fb.suggestion = "Use 'registry buildings' to find valid building IDs"; + break; + case "unknown_tech": + fb.suggestion = "Use 'registry techs' to find valid tech IDs"; + break; + case "missing_prerequisites": + fb.suggestion = "Research the prerequisite technologies first"; + break; + case "tech_already_complete": + fb.suggestion = "The technology has already been researched"; + break; + case "no_building_at_cell": + fb.suggestion = "Use the buildings command to find buildings and their coordinates"; + break; + case "no_liquid_at_cell": + fb.suggestion = "Use the cell command to check what is at that location"; + break; + case "liquid_too_thin": + fb.suggestion = "Wait for more liquid to accumulate before mopping"; + break; + case "invalid_priority": + fb.suggestion = "Use a value between 1 (lowest) and 9 (emergency/yellow alert)"; + break; + case "blocks_not_diggable": + fb.suggestion = "Neutronium borders and abyssalite cannot be dug"; + break; + default: + break; + } return fb; } @@ -3531,20 +3743,16 @@ namespace ONIAgentBridge string wireType = data.type ?? "regular"; string mode = data.mode ?? "line"; - string conduitId = wireType switch - { - "heavy" => "HeaviWatWire", - "conductive" => "ConductiveWire", - "heavy_conductive" => "HeaviWatConductiveWire", - _ => "Wire" - }; - string bridgeId = wireType switch - { - "heavy" => "HeaviWatBridge", - "conductive" => "ConductiveWireBridge", - "heavy_conductive" => "HeaviWatConductiveBridge", - _ => "WireBridge" - }; + string conduitId; + if (wireType == "heavy") conduitId = "HeaviWatWire"; + else if (wireType == "conductive") conduitId = "ConductiveWire"; + else if (wireType == "heavy_conductive") conduitId = "HeaviWatConductiveWire"; + else conduitId = "Wire"; + string bridgeId; + if (wireType == "heavy") bridgeId = "HeaviWatBridge"; + else if (wireType == "conductive") bridgeId = "ConductiveWireBridge"; + else if (wireType == "heavy_conductive") bridgeId = "HeaviWatConductiveBridge"; + else bridgeId = "WireBridge"; int cellsPlaced = 0; int bridgesPlaced = 0; diff --git a/mod/ONIAgentBridge.csproj b/mod/ONIAgentBridge.csproj new file mode 100644 index 0000000..e0e0c3d --- /dev/null +++ b/mod/ONIAgentBridge.csproj @@ -0,0 +1,32 @@ + + + net471 + ONIAgentBridge + 1.0.0 + ONIAgentBridge + 7 + + + + + + + + + $(ONI_Path)/Assembly-CSharp.dll + False + + + $(ONI_Path)/Assembly-CSharp-firstpass.dll + False + + + $(ONI_Path)/UnityEngine.dll + False + + + $(ONI_Path)/UnityEngine.CoreModule.dll + False + + + diff --git a/scripts/build_mod.sh b/scripts/build_mod.sh new file mode 100755 index 0000000..2c35376 --- /dev/null +++ b/scripts/build_mod.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# Build the ONI Agent Bridge Mod +# Requires: mono-complete (for mcs compiler) +# Requires: ONI game installed to get reference DLLs +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +MOD_DIR="$SCRIPT_DIR/mod" +OUTPUT_DIR="$MOD_DIR/bin" + +# Find ONI installation +ONI_PATH="${ONI_PATH:-}" +if [ -z "$ONI_PATH" ]; then + # Common paths + for candidate in \ + "$HOME/.local/share/Steam/steamapps/common/OxygenNotIncluded" \ + "$HOME/Library/Application Support/Steam/steamapps/common/OxygenNotIncluded" \ + "/c/Program Files (x86)/Steam/steamapps/common/OxygenNotIncluded" \ + "/mnt/c/Program Files (x86)/Steam/steamapps/common/OxygenNotIncluded"; do + if [ -d "$candidate" ]; then + ONI_PATH="$candidate" + break + fi + done +fi + +if [ -z "$ONI_PATH" ]; then + echo "[!] ONI installation not found. Set ONI_PATH environment variable." + echo " export ONI_PATH=/path/to/OxygenNotIncluded" + exit 1 +fi + +echo "[Build] ONI path: $ONI_PATH" + +# Check required DLLs +GAME_DLLS=( + "$ONI_PATH/Assembly-CSharp.dll" + "$ONI_PATH/Assembly-CSharp-firstpass.dll" + "$ONI_PATH/UnityEngine.dll" + "$ONI_PATH/UnityEngine.CoreModule.dll" + "$ONI_PATH/0Harmony.dll" +) + +for dll in "${GAME_DLLS[@]}"; do + if [ ! -f "$dll" ]; then + echo "[!] Required DLL not found: $dll" + exit 1 + fi +done + +echo "[Build] Found all required DLLs" + +mkdir -p "$OUTPUT_DIR" + +# Compile +mcs -target:library \ + -out:"$OUTPUT_DIR/ONIAgentBridge.dll" \ + -reference:"$ONI_PATH/Assembly-CSharp.dll" \ + -reference:"$ONI_PATH/Assembly-CSharp-firstpass.dll" \ + -reference:"$ONI_PATH/UnityEngine.dll" \ + -reference:"$ONI_PATH/UnityEngine.CoreModule.dll" \ + -reference:"$ONI_PATH/0Harmony.dll" \ + -recurse:"$MOD_DIR/*.cs" + +echo "[Build] Compilation successful!" +echo "[Build] Output: $OUTPUT_DIR/ONIAgentBridge.dll" +echo "" +echo "To install: copy $OUTPUT_DIR/ONIAgentBridge.dll and $MOD_DIR/mod_info.yaml" +echo " to your ONI mods/local/ONIAgentBridge/ directory" diff --git a/scripts/fix_csharp.py b/scripts/fix_csharp.py new file mode 100644 index 0000000..a5d7c5b --- /dev/null +++ b/scripts/fix_csharp.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 +"""Convert C# 7/8 features in ONIAgentBridge.cs to C# 6 compatible code.""" + +import re + +with open('mod/ONIAgentBridge.cs', 'r') as f: + content = f.read() + +# 1. Convert tuple switch at ProcessRequest to if-else chain +# Find the switch and replace it +old_switch_start = """ switch (path, method) + {""" + +# Read the entire switch block and reconstruct +lines = content.split('\n') +new_lines = [] +i = 0 +while i < len(lines): + line = lines[i] + + # Find the C# 7 tuple switch + if 'switch (path, method)' in line and '{' in lines[i+1]: + new_lines.append(' // --- C# 6 compatible dispatch ---') + new_lines.append(' bool _handled = false;') + i += 2 # skip "switch (path, method)" and "{" + + while i < len(lines): + stripped = lines[i].strip() + + # Check for case pattern: case ("...", "..."): + m = re.match(r'case \("([^"]+)", "([^"]+)"\):', stripped) + if m: + endpoint = m.group(1) + method = m.group(2) + indent = lines[i][:len(lines[i]) - len(lines[i].lstrip())] + new_lines.append(f'{indent}else if (path == "{endpoint}" && method == "{method}")') + new_lines.append(f'{indent}{{') + new_lines.append(f'{indent} _handled = true;') + i += 1 + # Copy lines until "break;" + while i < len(lines) and lines[i].strip() != 'break;': + new_lines.append(lines[i]) + i += 1 + new_lines.append(f'{indent}}}') + i += 1 # skip "break;" + continue + + # Check for default case + if stripped == 'default:': + i += 1 + # Skip the opening brace + if lines[i].strip() == '{': + i += 1 + new_lines.append(' if (!_handled)') + new_lines.append(' {') + while i < len(lines): + if lines[i].strip() == '}' and not _is_end_of_method(lines, i): + # This could be the switch closing brace + # Check if next line is the method closing + break + new_lines.append(lines[i]) + i += 1 + new_lines.append(' }') + # Skip the closing brace of the switch + if lines[i].strip() == '}': + i += 1 + continue + + # Check for closing brace of switch + if stripped == '}': + i += 1 + break + + # Any other content inside switch (shouldn't happen) + new_lines.append(lines[i]) + i += 1 + continue + + new_lines.append(line) + i += 1 + +content = '\n'.join(new_lines) + +def _is_end_of_method(lines, idx): + """Check if this } ends a method (next non-blank is a method or class member).""" + for j in range(idx+1, min(idx+10, len(lines))): + s = lines[j].strip() + if s == '' or s.startswith('//') or s.startswith('/*'): + continue + if s.startswith('private ') or s.startswith('public ') or s.startswith('internal ') or s.startswith('static ') or s.startswith('}'): + return True + return False + return False + +# 2. Fix switch expressions (C# 8): `wireType switch { ... }` -> if-else +# Pattern: string buildingId = wireType switch { "heavy" => "HeaviWatWire", ... } +def fix_switch_expr(match): + full = match.group(0) + var_name = match.group(1) + # Parse the switch arms + # Extract the expression after "= " + body = match.group(2) + return full # placeholder, we'll handle below + +# Fix: string buildingId = wireType switch { "heavy" => "HeaviWatWire", _ => "Wire" }; +# Convert to: string buildingId; if (wireType == "heavy") buildingId = "HeaviWatWire"; else ... +for pattern in [ + (r'wireType switch\s*\{([^}]+)\}', 'wireType'), + (r'error switch\s*\{([^}]+)\}', 'error'), + (r'i switch\s*\{([^}]+)\}', 'i'), +]: + pat, varname = pattern + matches = list(re.finditer(pat, content)) + for m in reversed(matches): + body = m.group(1) + arms = re.findall(r'"([^"]*)"\s*=>\s*"([^"]*)"', body) + default = re.findall(r'_\s*=>\s*(null|"[^"]*")', body) + + if varname == 'i': + # label assignment + replacements = [] + for key, val in arms: + replacements.append(f' if ({varname} == {key}) label = "{val}";') + if default: + replacements.append(f' else label = {default[0]};') + new_code = '\n'.join(replacements) + else: + # Find the variable being assigned + start = m.start() + # Look backwards to find the variable name + line_start = content.rfind('\n', 0, start) + 1 + line_before = content[line_start:start] + var_match = re.match(r'\s*(?:\w+\s+)?(\w+)\s*=\s*$', line_before) + + indent = ' ' * 16 + if varname == 'wireType': + # Multiple instances in wire building, find each by context + context_before = content[max(0,start-200):start] + if 'bridgeId' in context_before: + varname_full = 'bridgeId' + else: + varname_full = 'buildingId' + else: + varname_full = varname + + lines_code = [f' string {varname_full};'] + first = True + for key, val in arms: + prefix = 'if' if first else 'else if' + lines_code.append(f' {prefix} ({varname} == "{key}") {varname_full} = "{val}";') + first = False + if default: + dval = default[0] + lines_code.append(f' else {varname_full} = {dval};') + + new_code = '\n'.join(lines_code) + + # Replace the assignment line + switch expression + # Find the full assignment + assign_start = content.rfind('\n', 0, start) + assign_end = m.end() + while assign_end < len(content) and content[assign_end] != '\n': + assign_end += 1 + old = content[assign_start:assign_end] + content = content[:assign_start] + '\n' + new_code + content[assign_end:] + +# Write result +with open('mod/ONIAgentBridge.cs', 'w') as f: + f.write(content) + +print('Done. Checking for remaining C# 7+ features...') + +# Verify +remaining = re.findall(r'\bswitch\b', content) +print(f'Remaining "switch" keywords: {len(remaining)}') +# Count them +for i, line in enumerate(content.split('\n')): + if 'switch' in line and not line.strip().startswith('//') and 'ProcessRequest' not in line: + sline = line.strip() + if 'if (' not in line and 'dictionary' not in line.lower() and 'dispatch' not in line: + print(f' Line ~{i}: {sline[:80]}')