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 diff --git a/tests/bruno_api_collection/Builder/CheckZone.yml b/tests/bruno_api_collection/Builder/CheckZone.yml new file mode 100644 index 0000000..edfe1db --- /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: |- + { + "map_id": "0", + "point_a": { "x": 120, "z": 120 }, + "point_b": { "x": 130, "z": 130 } + } + auth: inherit + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5