From 0fc6c0593e4693e0ab92a00720f444a4c6303a37 Mon Sep 17 00:00:00 2001 From: Yuri Date: Sun, 12 Apr 2026 17:41:52 +0300 Subject: [PATCH 1/3] AddCheckZone api --- .../Controllers/Colony/BuilderController.cs | 8 ++ .../RimworldRestApi/Models/BuilderDtos.cs | 30 +++++ .../World/BuilderService/BuilderService.cs | 116 ++++++++++++++++++ .../World/BuilderService/IBuilderService.cs | 1 + 4 files changed, 155 insertions(+) diff --git a/Source/RIMAPI/RimworldRestApi/Controllers/Colony/BuilderController.cs b/Source/RIMAPI/RimworldRestApi/Controllers/Colony/BuilderController.cs index 4d421b2..e7db57f 100644 --- a/Source/RIMAPI/RimworldRestApi/Controllers/Colony/BuilderController.cs +++ b/Source/RIMAPI/RimworldRestApi/Controllers/Colony/BuilderController.cs @@ -38,5 +38,13 @@ public async Task PlaceBlueprints(HttpListenerContext context) var result = _builderService.PlaceBlueprints(body); await context.SendJsonResponse(result); } + + [Post("/api/v1/builder/check-zone")] + public async Task CheckZone(HttpListenerContext context) + { + var body = await context.Request.ReadBodyAsync(); + var result = _builderService.CheckZone(body); + await context.SendJsonResponse(result); + } } } \ No newline at end of file diff --git a/Source/RIMAPI/RimworldRestApi/Models/BuilderDtos.cs b/Source/RIMAPI/RimworldRestApi/Models/BuilderDtos.cs index d198219..3d1f589 100644 --- a/Source/RIMAPI/RimworldRestApi/Models/BuilderDtos.cs +++ b/Source/RIMAPI/RimworldRestApi/Models/BuilderDtos.cs @@ -18,6 +18,13 @@ public class PasteAreaRequestDto public bool ClearObstacles { get; set; } = true; // Destroy existing things before pasting } + public class CheckZoneRequestDto + { + public int MapId { get; set; } + public PositionDto PointA { get; set; } + public PositionDto PointB { get; set; } + } + // --- The Blueprint Data Structure --- public class BlueprintDto { @@ -42,4 +49,27 @@ public class SavedBuildingDto public int RelZ { get; set; } public int Rotation { get; set; } // 0=North, 1=East, 2=South, 3=West } + + public class CheckZoneResultDto + { + public bool CanBuild { get; set; } + public CheckZoneIssuesDto Issues { get; set; } + } + + public class CheckZoneIssuesDto + { + public List Terrain { get; set; } = new List(); + public List Ores { get; set; } = new List(); + public List Buildings { get; set; } = new List(); + public List Zones { get; set; } = new List(); + } + + public class CheckZoneIssueDto + { + public int X { get; set; } + public int Z { get; set; } + public string DefName { get; set; } + public string Label { get; set; } + public string ZoneType { get; set; } + } } \ No newline at end of file diff --git a/Source/RIMAPI/RimworldRestApi/Services/World/BuilderService/BuilderService.cs b/Source/RIMAPI/RimworldRestApi/Services/World/BuilderService/BuilderService.cs index caf8603..4c0066b 100644 --- a/Source/RIMAPI/RimworldRestApi/Services/World/BuilderService/BuilderService.cs +++ b/Source/RIMAPI/RimworldRestApi/Services/World/BuilderService/BuilderService.cs @@ -233,5 +233,121 @@ public ApiResult PlaceBlueprints(PasteAreaRequestDto request) return ApiResult.Fail(ex.Message); } } + + public ApiResult CheckZone(CheckZoneRequestDto request) + { + try + { + var map = MapHelper.GetMapByID(request.MapId); + if (map == null) return ApiResult.Fail($"Map {request.MapId} not found."); + + if (request.PointA == null || request.PointB == null) + return ApiResult.Fail("PointA and PointB are required."); + + int minX = Mathf.Min(request.PointA.X, request.PointB.X); + int minZ = Mathf.Min(request.PointA.Z, request.PointB.Z); + int maxX = Mathf.Max(request.PointA.X, request.PointB.X); + int maxZ = Mathf.Max(request.PointA.Z, request.PointB.Z); + + var issues = new CheckZoneIssuesDto(); + HashSet processedThings = new HashSet(); + + CellRect rect = new CellRect(minX, minZ, (maxX - minX) + 1, (maxZ - minZ) + 1); + + foreach (IntVec3 cell in rect) + { + if (!cell.InBounds(map)) continue; + + // 1. Check Terrain (affordances) + TerrainDef terrain = map.terrainGrid.TerrainAt(cell); + if (terrain != null) + { + bool hasHeavyAffordance = terrain.affordances != null && + terrain.affordances.Contains(TerrainAffordanceDefOf.Heavy); + if (!hasHeavyAffordance) + { + issues.Terrain.Add(new CheckZoneIssueDto + { + X = cell.x, + Z = cell.z, + DefName = terrain.defName, + Label = terrain.label + }); + } + } + + // 2. Check Things (Ores and Buildings) + List things = cell.GetThingList(map); + foreach (var thing in things) + { + if (processedThings.Contains(thing)) continue; + + // Check for mineable ores + if (thing.def.mineable) + { + processedThings.Add(thing); + issues.Ores.Add(new CheckZoneIssueDto + { + X = thing.Position.x, + Z = thing.Position.z, + DefName = thing.def.defName, + Label = thing.def.label + }); + } + // Check for buildings + else if (thing.def.category == ThingCategory.Building) + { + processedThings.Add(thing); + issues.Buildings.Add(new CheckZoneIssueDto + { + X = thing.Position.x, + Z = thing.Position.z, + DefName = thing.def.defName, + Label = thing.def.label + }); + } + } + } + + // 3. Check Zones (Growing and Stockpile) + foreach (Zone zone in map.zoneManager.AllZones) + { + if (zone is Zone_Growing || zone is Zone_Stockpile) + { + foreach (IntVec3 cell in zone.cells) + { + if (cell.x >= minX && cell.x <= maxX && cell.z >= minZ && cell.z <= maxZ) + { + issues.Zones.Add(new CheckZoneIssueDto + { + X = cell.x, + Z = cell.z, + DefName = zone.GetType().Name, + Label = zone.label, + ZoneType = zone is Zone_Growing ? "Growing" : "Stockpile" + }); + break; + } + } + } + } + + bool canBuild = issues.Terrain.Count == 0 && + issues.Ores.Count == 0 && + issues.Buildings.Count == 0 && + issues.Zones.Count == 0; + + return ApiResult.Ok(new CheckZoneResultDto + { + CanBuild = canBuild, + Issues = issues + }); + } + catch (Exception ex) + { + LogApi.Error($"CheckZone Error: {ex}"); + return ApiResult.Fail(ex.Message); + } + } } } \ No newline at end of file diff --git a/Source/RIMAPI/RimworldRestApi/Services/World/BuilderService/IBuilderService.cs b/Source/RIMAPI/RimworldRestApi/Services/World/BuilderService/IBuilderService.cs index eb40993..4d58bb6 100644 --- a/Source/RIMAPI/RimworldRestApi/Services/World/BuilderService/IBuilderService.cs +++ b/Source/RIMAPI/RimworldRestApi/Services/World/BuilderService/IBuilderService.cs @@ -8,5 +8,6 @@ public interface IBuilderService ApiResult CopyArea(CopyAreaRequestDto request); ApiResult PasteArea(PasteAreaRequestDto request); ApiResult PlaceBlueprints(PasteAreaRequestDto request); + ApiResult CheckZone(CheckZoneRequestDto request); } } \ No newline at end of file From 9b908c5d4367f3de4edaf4280e913baf02751901 Mon Sep 17 00:00:00 2001 From: Yuri Date: Sun, 12 Apr 2026 17:42:13 +0300 Subject: [PATCH 2/3] AddCheckZone bruno yml --- .../Builder/CheckZone.yml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tests/bruno_api_collection/Builder/CheckZone.yml diff --git a/tests/bruno_api_collection/Builder/CheckZone.yml b/tests/bruno_api_collection/Builder/CheckZone.yml new file mode 100644 index 0000000..682cd89 --- /dev/null +++ b/tests/bruno_api_collection/Builder/CheckZone.yml @@ -0,0 +1,23 @@ +info: + name: CheckZone + type: http + seq: 4 + +http: + method: POST + url: "{{baseURL}}/api/v1/builder/check-zone" + body: + type: json + data: |- + { + "mapId": "uuid", + "pointA": { "x": 10, "z": 20 }, + "pointB": { "x": 50, "z": 60 } + } + auth: inherit + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 From f227d17cc5fed359c0f6589bea78c8404736d88b Mon Sep 17 00:00:00 2001 From: Yuri Date: Mon, 13 Apr 2026 10:17:02 +0300 Subject: [PATCH 3/3] AddCheckZone bruno yml fix --- tests/bruno_api_collection/Builder/CheckZone.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/bruno_api_collection/Builder/CheckZone.yml b/tests/bruno_api_collection/Builder/CheckZone.yml index 682cd89..edfe1db 100644 --- a/tests/bruno_api_collection/Builder/CheckZone.yml +++ b/tests/bruno_api_collection/Builder/CheckZone.yml @@ -10,9 +10,9 @@ http: type: json data: |- { - "mapId": "uuid", - "pointA": { "x": 10, "z": 20 }, - "pointB": { "x": 50, "z": 60 } + "map_id": "0", + "point_a": { "x": 120, "z": 120 }, + "point_b": { "x": 130, "z": 130 } } auth: inherit