Перенос 2D → 3D без потерь (расчистка места, очередь «ждут места»); DEV «Камера» -- ползунки наклона, перспективы и отдаления

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Alexander
2026-11-05 17:56:38 +01:00
co-authored by Claude Opus 5.5
parent 8a5f7253db
commit f78eb7ea7e
7 changed files with 260 additions and 20 deletions
@@ -152,6 +152,29 @@ namespace Bogatyri.Settlement.Placement
// Проверить место: прямоугольник sizeW x sizeH с поворотом rotation, левый нижний угол (x, y).
// ignoreId -- здание, которое переносят: его прежний след не мешает. foundationMm -- высота фундамента
public PlaceError Validate(int x, int y, int sizeW, int sizeH, int rotation, int townHallLevel, string ignoreId, out int foundationMm)
=> ValidateCore(x, y, sizeW, sizeH, rotation, townHallLevel, ignoreId, false, out foundationMm);
// То же, но деревья и валуны не мешают: можно ли поставить здесь, если расчистить (перенос 2D-города, 05.11)
public PlaceError ValidateIfCleared(int x, int y, int sizeW, int sizeH, int rotation, int townHallLevel, string ignoreId, out int foundationMm)
=> ValidateCore(x, y, sizeW, sizeH, rotation, townHallLevel, ignoreId, true, out foundationMm);
// Деревья и валуны (id) под прямоугольником -- что придётся расчистить. Каждый -- один раз
public List<string> ObstaclesUnder(int x, int y, int sizeW, int sizeH, int rotation)
{
rotation = NormalizeRotation(rotation);
int w = rotation == 90 || rotation == 270 ? sizeH : sizeW;
int h = rotation == 90 || rotation == 270 ? sizeW : sizeH;
var ids = new List<string>();
for (int cy = y; cy < y + h; cy++)
for (int cx = x; cx < x + w; cx++)
{
string id = ObstacleAt(cx, cy);
if (id != null && !ids.Contains(id)) ids.Add(id);
}
return ids;
}
PlaceError ValidateCore(int x, int y, int sizeW, int sizeH, int rotation, int townHallLevel, string ignoreId, bool ignoreObstacles, out int foundationMm)
{
foundationMm = 0;
rotation = NormalizeRotation(rotation);
@@ -167,7 +190,7 @@ namespace Bogatyri.Settlement.Placement
if (kind[i] == CellKind.Water) return PlaceError.Water;
if (kind[i] == CellKind.Mountain) return PlaceError.Mountain;
if (!IsOpen(cx, cy, townHallLevel)) return PlaceError.ZoneLocked;
if (obstacleAt.ContainsKey(i)) return PlaceError.Obstacle;
if (!ignoreObstacles && obstacleAt.ContainsKey(i)) return PlaceError.Obstacle;
min = Math.Min(min, heightMm[i]);
max = Math.Max(max, heightMm[i]);
}
@@ -35,6 +35,24 @@ public class PlacementTests
Assert.AreEqual(PlaceError.None, map.Validate(15, 2, 2, 2, 0, 2, null, out _), "11: Ратуша 2 открыла зону");
}
[Test]
public void Placement_IfCleared_IgnoresOnlyTreesAndListsThem()
{
var map = Map();
Assert.AreEqual(PlaceError.Obstacle, map.Validate(4, 4, 2, 2, 0, 1, null, out _), "дерево мешает");
Assert.AreEqual(PlaceError.None, map.ValidateIfCleared(4, 4, 2, 2, 0, 1, null, out _), "если расчистить -- можно");
var under = map.ObstaclesUnder(4, 4, 2, 2, 0);
Assert.AreEqual(1, under.Count, "под зданием -- одно дерево");
Assert.AreEqual("tree_1", under[0]);
Assert.AreEqual(0, map.ObstaclesUnder(1, 1, 2, 2, 0).Count, "рядом деревьев нет");
Assert.AreEqual(PlaceError.Water, map.ValidateIfCleared(9, 2, 2, 2, 0, 1, null, out _), "река остаётся рекой");
Assert.AreEqual(PlaceError.ZoneLocked, map.ValidateIfCleared(15, 2, 2, 2, 0, 1, null, out _), "закрытая зона -- закрыта");
map.Place("a", "Дом", 1, 1, 2, 2, 0, 1);
Assert.AreEqual(PlaceError.Occupied, map.ValidateIfCleared(1, 1, 2, 2, 0, 1, null, out _), "чужое здание мешает");
Assert.IsTrue(map.ClearObstacle("tree_1"));
Assert.AreEqual(PlaceError.None, map.Place("b", "Дом", 4, 4, 2, 2, 0, 1), "после расчистки ставится");
}
[Test]
public void Placement_GapBetweenBuildings()
{