Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<CheckZoneRequestDto>();
var result = _builderService.CheckZone(body);
await context.SendJsonResponse(result);
}
}
}
30 changes: 30 additions & 0 deletions Source/RIMAPI/RimworldRestApi/Models/BuilderDtos.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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<CheckZoneIssueDto> Terrain { get; set; } = new List<CheckZoneIssueDto>();
public List<CheckZoneIssueDto> Ores { get; set; } = new List<CheckZoneIssueDto>();
public List<CheckZoneIssueDto> Buildings { get; set; } = new List<CheckZoneIssueDto>();
public List<CheckZoneIssueDto> Zones { get; set; } = new List<CheckZoneIssueDto>();
}

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; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -233,5 +233,121 @@ public ApiResult PlaceBlueprints(PasteAreaRequestDto request)
return ApiResult.Fail(ex.Message);
}
}

public ApiResult<CheckZoneResultDto> CheckZone(CheckZoneRequestDto request)
{
try
{
var map = MapHelper.GetMapByID(request.MapId);
if (map == null) return ApiResult<CheckZoneResultDto>.Fail($"Map {request.MapId} not found.");

if (request.PointA == null || request.PointB == null)
return ApiResult<CheckZoneResultDto>.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<Thing> processedThings = new HashSet<Thing>();

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<Thing> 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<CheckZoneResultDto>.Ok(new CheckZoneResultDto
{
CanBuild = canBuild,
Issues = issues
});
}
catch (Exception ex)
{
LogApi.Error($"CheckZone Error: {ex}");
return ApiResult<CheckZoneResultDto>.Fail(ex.Message);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ public interface IBuilderService
ApiResult<BlueprintDto> CopyArea(CopyAreaRequestDto request);
ApiResult PasteArea(PasteAreaRequestDto request);
ApiResult PlaceBlueprints(PasteAreaRequestDto request);
ApiResult<CheckZoneResultDto> CheckZone(CheckZoneRequestDto request);
}
}
23 changes: 23 additions & 0 deletions tests/bruno_api_collection/Builder/CheckZone.yml
Original file line number Diff line number Diff line change
@@ -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