瀏覽代碼

Merge pull request #25 from djeada/refactor/small-fixes

Refactor/small fixes
Adam Djellouli 2 月之前
父節點
當前提交
3d85c33add

+ 19 - 9
app/game_engine.cpp

@@ -105,9 +105,9 @@ void GameEngine::onAttackClick(qreal sx, qreal sy) {
     return;
   }
 
-  Engine::Core::EntityID targetId = m_pickingService->pickSingle(
-      float(sx), float(sy), *m_world, *m_camera, m_viewport.width,
-      m_viewport.height, 0, true);
+  Engine::Core::EntityID targetId =
+      m_pickingService->pickUnitFirst(float(sx), float(sy), *m_world, *m_camera,
+                                      m_viewport.width, m_viewport.height, 0);
 
   if (targetId == 0) {
 
@@ -297,7 +297,7 @@ void GameEngine::setHoverAtScreen(qreal sx, qreal sy) {
 
       m_window->setCursor(Qt::ArrowCursor);
     }
-    m_hover.buildingId = 0;
+    m_hover.entityId = 0;
     return;
   }
 
@@ -307,7 +307,7 @@ void GameEngine::setHoverAtScreen(qreal sx, qreal sy) {
     m_window->setCursor(Qt::BlankCursor);
   }
 
-  m_hover.buildingId =
+  m_hover.entityId =
       m_pickingService->updateHover(float(sx), float(sy), *m_world, *m_camera,
                                     m_viewport.width, m_viewport.height);
 }
@@ -419,7 +419,8 @@ void GameEngine::update(float dt) {
 }
 
 void GameEngine::render(int pixelWidth, int pixelHeight) {
-  if (!m_renderer || !m_world || !m_runtime.initialized)
+
+  if (!m_renderer || !m_world || !m_runtime.initialized || m_runtime.loading)
     return;
   if (pixelWidth > 0 && pixelHeight > 0) {
     m_viewport.width = pixelWidth;
@@ -437,7 +438,7 @@ void GameEngine::render(int pixelWidth, int pixelHeight) {
       m_ground->submit(*m_renderer, *res);
   }
   if (m_renderer)
-    m_renderer->setHoveredBuildingId(m_hover.buildingId);
+    m_renderer->setHoveredEntityId(m_hover.entityId);
   m_renderer->renderWorld(m_world.get());
   if (m_arrowSystem) {
     if (auto *res = m_renderer->resources())
@@ -742,11 +743,15 @@ void GameEngine::startSkirmish(const QString &mapPath) {
     }
 
     if (m_renderer) {
+
+      m_renderer->pause();
+
+      m_renderer->lockWorldForModification();
       m_renderer->setSelectedEntities({});
-      m_renderer->setHoveredBuildingId(0);
+      m_renderer->setHoveredEntityId(0);
     }
 
-    m_hover.buildingId = 0;
+    m_hover.entityId = 0;
 
     m_world->clear();
 
@@ -772,6 +777,11 @@ void GameEngine::startSkirmish(const QString &mapPath) {
     m_level.camFar = lr.camFar;
     m_level.maxTroopsPerPlayer = lr.maxTroopsPerPlayer;
 
+    if (m_renderer) {
+
+      m_renderer->unlockWorldForModification();
+      m_renderer->resume();
+    }
     m_runtime.loading = false;
   }
 }

+ 1 - 1
app/game_engine.h

@@ -143,7 +143,7 @@ private:
     int maxTroopsPerPlayer = 50;
   };
   struct HoverState {
-    Engine::Core::EntityID buildingId = 0;
+    Engine::Core::EntityID entityId = 0;
   };
   struct PatrolState {
     QVector3D firstWaypoint;

+ 6 - 1
game/systems/ai_system.cpp

@@ -151,7 +151,12 @@ void AISystem::applyCommands(Engine::Core::World *world,
     case AICommandType::MoveUnits:
       if (!command.units.empty() &&
           command.units.size() == command.moveTargets.size()) {
-        CommandService::moveUnits(*world, command.units, command.moveTargets);
+
+        CommandService::MoveOptions opts;
+        opts.allowDirectFallback = false;
+        opts.clearAttackIntent = true;
+        CommandService::moveUnits(*world, command.units, command.moveTargets,
+                                  opts);
       }
       break;
     case AICommandType::AttackTarget:

+ 2 - 1
game/systems/combat_system.cpp

@@ -151,7 +151,8 @@ void CombatSystem::processAttacks(Engine::Core::World *world, float deltaTime) {
                   if (needNewCommand) {
                     CommandService::MoveOptions options;
                     options.clearAttackIntent = false;
-                    options.allowDirectFallback = !targetIsBuilding;
+
+                    options.allowDirectFallback = false;
                     std::vector<Engine::Core::EntityID> unitIds = {
                         attacker->getId()};
                     std::vector<QVector3D> moveTargets = {desiredPos};

+ 20 - 103
game/systems/picking_service.cpp

@@ -56,113 +56,17 @@ PickingService::updateHover(float sx, float sy, Engine::Core::World &world,
     return 0;
   }
   auto prevHover = m_prevHoverId;
-  Engine::Core::EntityID currentHover = 0;
 
-  if (prevHover) {
-    if (auto *e = world.getEntity(prevHover)) {
-      if (e->hasComponent<Engine::Core::BuildingComponent>()) {
+  Engine::Core::EntityID picked =
+      pickSingle(sx, sy, world, camera, viewW, viewH, 0, false);
 
-        auto *u = e->getComponent<Engine::Core::UnitComponent>();
-        if (u && u->ownerId == 1) {
-          if (auto *t = e->getComponent<Engine::Core::TransformComponent>()) {
-            float pxPad = 12.0f;
-            if (auto *prod =
-                    e->getComponent<Engine::Core::ProductionComponent>()) {
-              if (prod->inProgress)
-                pxPad = 24.0f;
-            }
-            const float marginXZ_keep = 1.10f;
-            const float pad_keep = 1.12f;
-            float hxk = std::max(0.4f, t->scale.x * marginXZ_keep * pad_keep);
-            float hzk = std::max(0.4f, t->scale.z * marginXZ_keep * pad_keep);
-            QRectF bounds;
-            if (projectBounds(
-                    camera,
-                    QVector3D(t->position.x, t->position.y, t->position.z), hxk,
-                    hzk, viewW, viewH, bounds)) {
-              bounds.adjust(-pxPad, -pxPad, pxPad, pxPad);
-              if (bounds.contains(QPointF(sx, sy))) {
-                currentHover = prevHover;
-                m_hoverGraceTicks = 6;
-                m_prevHoverId = currentHover;
-                return currentHover;
-              }
-            }
-          }
-        }
-      }
-    }
-  }
-
-  float bestD2 = std::numeric_limits<float>::max();
-  auto ents = world.getEntitiesWith<Engine::Core::TransformComponent>();
-  for (auto *e : ents) {
-    if (!e->hasComponent<Engine::Core::UnitComponent>())
-      continue;
-    if (!e->hasComponent<Engine::Core::BuildingComponent>())
-      continue;
-
-    auto *u = e->getComponent<Engine::Core::UnitComponent>();
-    if (!u || u->ownerId != 1)
-      continue;
-
-    auto *t = e->getComponent<Engine::Core::TransformComponent>();
-    const float marginXZ = 1.10f;
-    const float hoverPad = 1.06f;
-    float hx = std::max(0.4f, t->scale.x * marginXZ * hoverPad);
-    float hz = std::max(0.4f, t->scale.z * marginXZ * hoverPad);
-    QRectF bounds;
-    if (!projectBounds(camera,
-                       QVector3D(t->position.x, t->position.y, t->position.z),
-                       hx, hz, viewW, viewH, bounds))
-      continue;
-    if (!bounds.contains(QPointF(sx, sy)))
-      continue;
-    QPointF centerSp;
-    if (!worldToScreen(camera, viewW, viewH,
-                       QVector3D(t->position.x, t->position.y, t->position.z),
-                       centerSp))
-      centerSp = bounds.center();
-    float dx = float(sx) - float(centerSp.x());
-    float dy = float(sy) - float(centerSp.y());
-    float d2 = dx * dx + dy * dy;
-    if (d2 < bestD2) {
-      bestD2 = d2;
-      currentHover = e->getId();
-    }
-  }
-
-  if (currentHover != 0 && currentHover != prevHover) {
+  if (picked != 0 && picked != prevHover) {
     m_hoverGraceTicks = 6;
   }
 
-  if (currentHover == 0 && prevHover != 0) {
-    if (auto *e = world.getEntity(prevHover)) {
-      auto *t = e->getComponent<Engine::Core::TransformComponent>();
-      auto *u = e->getComponent<Engine::Core::UnitComponent>();
-      if (t && e->getComponent<Engine::Core::BuildingComponent>() && u &&
-          u->ownerId == 1) {
-        const float marginXZ = 1.12f;
-        const float keepPad =
-            (e->getComponent<Engine::Core::ProductionComponent>() &&
-             e->getComponent<Engine::Core::ProductionComponent>()->inProgress)
-                ? 1.16f
-                : 1.12f;
-        float hx = std::max(0.4f, t->scale.x * marginXZ * keepPad);
-        float hz = std::max(0.4f, t->scale.z * marginXZ * keepPad);
-        QRectF bounds;
-        if (projectBounds(
-                camera, QVector3D(t->position.x, t->position.y, t->position.z),
-                hx, hz, viewW, viewH, bounds)) {
-          if (bounds.contains(QPointF(sx, sy))) {
-            currentHover = prevHover;
-          }
-        }
-      }
-    }
-  }
-
+  Engine::Core::EntityID currentHover = picked;
   if (currentHover == 0 && prevHover != 0 && m_hoverGraceTicks > 0) {
+
     currentHover = prevHover;
   }
 
@@ -177,8 +81,9 @@ PickingService::pickSingle(float sx, float sy, Engine::Core::World &world,
                            const Render::GL::Camera &camera, int viewW,
                            int viewH, int ownerFilter,
                            bool preferBuildingsFirst) const {
-  const float baseUnitPickRadius = 18.0f;
-  const float baseBuildingPickRadius = 28.0f;
+
+  const float baseUnitPickRadius = 30.0f;
+  const float baseBuildingPickRadius = 30.0f;
   float bestUnitDist2 = std::numeric_limits<float>::max();
   float bestBuildingDist2 = std::numeric_limits<float>::max();
   Engine::Core::EntityID bestUnitId = 0;
@@ -286,6 +191,18 @@ PickingService::pickSingle(float sx, float sy, Engine::Core::World &world,
   return 0;
 }
 
+Engine::Core::EntityID
+PickingService::pickUnitFirst(float sx, float sy, Engine::Core::World &world,
+                              const Render::GL::Camera &camera, int viewW,
+                              int viewH, int ownerFilter) const {
+
+  auto id = pickSingle(sx, sy, world, camera, viewW, viewH, ownerFilter, false);
+  if (id != 0)
+    return id;
+
+  return pickSingle(sx, sy, world, camera, viewW, viewH, ownerFilter, true);
+}
+
 std::vector<Engine::Core::EntityID>
 PickingService::pickInRect(float x1, float y1, float x2, float y2,
                            Engine::Core::World &world,

+ 6 - 0
game/systems/picking_service.h

@@ -42,6 +42,12 @@ public:
                                     int viewH, int ownerFilter,
                                     bool preferBuildingsFirst) const;
 
+  Engine::Core::EntityID pickUnitFirst(float sx, float sy,
+                                       Engine::Core::World &world,
+                                       const Render::GL::Camera &camera,
+                                       int viewW, int viewH,
+                                       int ownerFilter) const;
+
   std::vector<Engine::Core::EntityID>
   pickInRect(float x1, float y1, float x2, float y2, Engine::Core::World &world,
              const Render::GL::Camera &camera, int viewW, int viewH,

+ 436 - 257
render/entity/barracks_renderer.cpp

@@ -19,25 +19,20 @@ using Render::Geom::cylinderBetween;
 using Render::Geom::sphereAt;
 
 struct BuildingProportions {
-
   static constexpr float baseWidth = 2.4f;
   static constexpr float baseDepth = 2.0f;
   static constexpr float baseHeight = 1.8f;
   static constexpr float foundationHeight = 0.2f;
-
   static constexpr float wallThickness = 0.08f;
   static constexpr float beamThickness = 0.12f;
   static constexpr float cornerPostRadius = 0.08f;
-
   static constexpr float roofPitch = 0.8f;
   static constexpr float roofOverhang = 0.15f;
   static constexpr float thatchLayerHeight = 0.12f;
-
   static constexpr float annexWidth = 1.0f;
   static constexpr float annexDepth = 1.0f;
   static constexpr float annexHeight = 1.2f;
   static constexpr float annexRoofHeight = 0.5f;
-
   static constexpr float doorWidth = 0.5f;
   static constexpr float doorHeight = 0.8f;
   static constexpr float windowWidth = 0.4f;
@@ -45,7 +40,6 @@ struct BuildingProportions {
   static constexpr float chimneyWidth = 0.25f;
   static constexpr float chimneyHeight = 1.0f;
   static constexpr float chimneyCapSize = 0.35f;
-
   static constexpr float bannerPoleHeight = 2.0f;
   static constexpr float bannerPoleRadius = 0.05f;
   static constexpr float bannerWidth = 0.5f;
@@ -78,20 +72,18 @@ static inline BarracksPalette makePalette(const QVector3D &team) {
   return p;
 }
 
+static inline QVector3D lerp(const QVector3D &a, const QVector3D &b, float t) {
+  return a * (1.0f - t) + b * t;
+}
+
 static inline void drawCylinder(ISubmitter &out, const QMatrix4x4 &model,
                                 const QVector3D &a, const QVector3D &b,
                                 float radius, const QVector3D &color,
                                 Texture *white) {
-  out.mesh(getUnitCylinder(), cylinderBetween(a, b, radius) * model, color,
+  out.mesh(getUnitCylinder(), model * cylinderBetween(a, b, radius), color,
            white, 1.0f);
 }
 
-static inline void drawSphere(ISubmitter &out, const QMatrix4x4 &model,
-                              const QVector3D &pos, float radius,
-                              const QVector3D &color, Texture *white) {
-  out.mesh(getUnitSphere(), sphereAt(pos, radius) * model, color, white, 1.0f);
-}
-
 static inline void unitBox(ISubmitter &out, Mesh *unitMesh, Texture *white,
                            const QMatrix4x4 &model, const QVector3D &t,
                            const QVector3D &s, const QVector3D &color) {
@@ -121,270 +113,401 @@ static inline void drawFoundation(const DrawContext &p, ISubmitter &out,
           QVector3D(baseWidth / 2 + 0.1f, foundationHeight / 2,
                     baseDepth / 2 + 0.1f),
           C.stoneDark);
+
+  const float stepH = 0.015f;
+  const float stepW = 0.16f;
+  const float stepD = 0.10f;
+  const float frontZ = baseDepth * 0.5f + 0.12f;
+  for (int i = 0; i < 5; ++i) {
+    float t = i / 4.0f;
+    float x = (i % 2 == 0) ? -0.18f : 0.18f;
+    QVector3D c = lerp(C.path, C.stone, 0.25f * (i % 2));
+    unitBox(out, unit, white, p.model,
+            QVector3D(x, -foundationHeight + stepH, frontZ + t * 0.55f),
+            QVector3D(stepW * (0.95f + 0.1f * (i % 2)), stepH, stepD), c);
+  }
+
+  QVector3D skirtColor = lerp(C.stoneDark, QVector3D(0.0f, 0.0f, 0.0f), 0.25f);
+  unitBox(out, unit, white, p.model, QVector3D(0.0f, 0.02f, 0.0f),
+          QVector3D(baseWidth * 0.50f, 0.01f, baseDepth * 0.50f), skirtColor);
 }
 
-static inline void drawTimberFrame(const DrawContext &p, ISubmitter &out,
-                                   Mesh *unit, Texture *white,
-                                   const BarracksPalette &C) {
-  constexpr float baseWidth = BuildingProportions::baseWidth;
-  constexpr float baseDepth = BuildingProportions::baseDepth;
-  constexpr float baseHeight = BuildingProportions::baseHeight;
-  constexpr float cornerPostRadius = BuildingProportions::cornerPostRadius;
-  constexpr float beamThickness = BuildingProportions::beamThickness;
+static inline void drawWalls(const DrawContext &p, ISubmitter &out, Mesh *,
+                             Texture *white, const BarracksPalette &C) {
+  constexpr float W = BuildingProportions::baseWidth;
+  constexpr float D = BuildingProportions::baseDepth;
+  constexpr float H = BuildingProportions::baseHeight;
 
-  auto cornerPost = [&](float x, float z) {
-    QVector3D bottom(x, 0.0f, z);
-    QVector3D top(x, baseHeight, z);
-    drawCylinder(out, p.model, bottom, top, cornerPostRadius, C.timber, white);
-  };
+  const float r = 0.09f;
+  const float notch = 0.07f;
+
+  const float leftX = -W * 0.5f;
+  const float rightX = W * 0.5f;
+  const float backZ = -D * 0.5f;
+  const float frontZ = D * 0.5f;
 
-  float hw = baseWidth / 2 - cornerPostRadius;
-  float hd = baseDepth / 2 - cornerPostRadius;
-  cornerPost(hw, hd);
-  cornerPost(-hw, hd);
-  cornerPost(hw, -hd);
-  cornerPost(-hw, -hd);
+  const int courses = std::max(4, int(H / (2.0f * r)));
+  const float y0 = r;
 
-  auto beam = [&](const QVector3D &a, const QVector3D &b) {
-    drawCylinder(out, p.model, a, b, beamThickness * 0.6f, C.timber, white);
+  auto logX = [&](float y, float z, float x0, float x1, const QVector3D &col) {
+    drawCylinder(out, p.model, QVector3D(x0 - notch, y, z),
+                 QVector3D(x1 + notch, y, z), r, col, white);
+  };
+  auto logZ = [&](float y, float x, float z0, float z1, const QVector3D &col) {
+    drawCylinder(out, p.model, QVector3D(x, y, z0 - notch),
+                 QVector3D(x, y, z1 + notch), r, col, white);
   };
 
-  float y = baseHeight;
-  beam(QVector3D(-hw, y, hd), QVector3D(hw, y, hd));
-  beam(QVector3D(-hw, y, -hd), QVector3D(hw, y, -hd));
-  beam(QVector3D(-hw, y, -hd), QVector3D(-hw, y, hd));
-  beam(QVector3D(hw, y, -hd), QVector3D(hw, y, hd));
+  const float doorW = BuildingProportions::doorWidth;
+  const float doorH = BuildingProportions::doorHeight;
+  const float gapHalf = doorW * 0.5f;
 
-  y = baseHeight * 0.5f;
-  beam(QVector3D(-hw, y, hd), QVector3D(hw, y, hd));
-  beam(QVector3D(-hw, y, -hd), QVector3D(hw, y, -hd));
-}
+  for (int i = 0; i < courses; ++i) {
+    float y = y0 + i * (2.0f * r);
+    QVector3D logCol = lerp(C.timber, C.timberLight, (i % 2) * 0.25f);
 
-static inline void drawWalls(const DrawContext &p, ISubmitter &out, Mesh *unit,
-                             Texture *white, const BarracksPalette &C) {
-  constexpr float baseWidth = BuildingProportions::baseWidth;
-  constexpr float baseDepth = BuildingProportions::baseDepth;
-  constexpr float baseHeight = BuildingProportions::baseHeight;
-  constexpr float wallThickness = BuildingProportions::wallThickness;
-  constexpr float cornerPostRadius = BuildingProportions::cornerPostRadius;
+    if (y <= (doorH - 0.5f * r)) {
+      logX(y, frontZ, leftX, -gapHalf, logCol);
+      logX(y, frontZ, +gapHalf, rightX, logCol);
+    } else {
+      logX(y, frontZ, leftX, rightX, logCol);
+    }
 
-  float wallY = baseHeight / 2;
-  float wallH = baseHeight * 0.85f;
+    logX(y, backZ, leftX, rightX, logCol);
+    logZ(y, leftX, backZ, frontZ, logCol);
+    logZ(y, rightX, backZ, frontZ, logCol);
+  }
 
-  unitBox(out, unit, white, p.model,
-          QVector3D(-baseWidth / 2 + 0.35f, wallY,
-                    baseDepth / 2 - wallThickness / 2),
-          QVector3D(0.55f, wallH / 2, wallThickness / 2), C.plaster);
-  unitBox(out, unit, white, p.model,
-          QVector3D(baseWidth / 2 - 0.35f, wallY,
-                    baseDepth / 2 - wallThickness / 2),
-          QVector3D(0.55f, wallH / 2, wallThickness / 2), C.plaster);
-
-  unitBox(
-      out, unit, white, p.model,
-      QVector3D(0.0f, wallY, -baseDepth / 2 + wallThickness / 2),
-      QVector3D(baseWidth / 2 - cornerPostRadius, wallH / 2, wallThickness / 2),
-      C.plaster);
-
-  unitBox(
-      out, unit, white, p.model,
-      QVector3D(-baseWidth / 2 + wallThickness / 2, wallY, 0.0f),
-      QVector3D(wallThickness / 2, wallH / 2, baseDepth / 2 - cornerPostRadius),
-      C.plasterShade);
-
-  unitBox(
-      out, unit, white, p.model,
-      QVector3D(baseWidth / 2 - wallThickness / 2, wallY, 0.0f),
-      QVector3D(wallThickness / 2, wallH / 2, baseDepth / 2 - cornerPostRadius),
-      C.plasterShade);
+  QVector3D postCol = C.woodDark;
+  drawCylinder(out, p.model, QVector3D(-gapHalf, y0, frontZ),
+               QVector3D(-gapHalf, y0 + doorH, frontZ), r * 0.95f, postCol,
+               white);
+  drawCylinder(out, p.model, QVector3D(+gapHalf, y0, frontZ),
+               QVector3D(+gapHalf, y0 + doorH, frontZ), r * 0.95f, postCol,
+               white);
+  drawCylinder(out, p.model, QVector3D(-gapHalf, y0 + doorH, frontZ),
+               QVector3D(+gapHalf, y0 + doorH, frontZ), r, C.timberLight,
+               white);
+
+  float braceY0 = H * 0.35f;
+  float braceY1 = H * 0.95f;
+  drawCylinder(out, p.model, QVector3D(leftX + 0.08f, braceY0, backZ + 0.10f),
+               QVector3D(leftX + 0.38f, braceY1, backZ + 0.10f), r * 0.6f,
+               C.woodDark, white);
+  drawCylinder(out, p.model, QVector3D(rightX - 0.08f, braceY0, backZ + 0.10f),
+               QVector3D(rightX - 0.38f, braceY1, backZ + 0.10f), r * 0.6f,
+               C.woodDark, white);
+  drawCylinder(out, p.model, QVector3D(leftX + 0.08f, braceY0, frontZ - 0.10f),
+               QVector3D(leftX + 0.38f, braceY1, frontZ - 0.10f), r * 0.6f,
+               C.woodDark, white);
+  drawCylinder(out, p.model, QVector3D(rightX - 0.08f, braceY0, frontZ - 0.10f),
+               QVector3D(rightX - 0.38f, braceY1, frontZ - 0.10f), r * 0.6f,
+               C.woodDark, white);
 }
 
-static inline void drawRoofs(const DrawContext &p, ISubmitter &out, Mesh *unit,
-                             Texture *white, const BarracksPalette &C) {
-  constexpr float baseWidth = BuildingProportions::baseWidth;
-  constexpr float baseDepth = BuildingProportions::baseDepth;
-  constexpr float baseHeight = BuildingProportions::baseHeight;
-  constexpr float roofPitch = BuildingProportions::roofPitch;
-  constexpr float roofOverhang = BuildingProportions::roofOverhang;
-  constexpr float thatchLayerHeight = BuildingProportions::thatchLayerHeight;
+struct ChimneyInfo {
+  float x;
+  float z;
+  float baseY;
+  float topY;
+  float gapRadius;
+};
 
-  float roofBase = baseHeight;
-  float roofPeak = roofBase + roofPitch;
+static inline ChimneyInfo drawChimney(const DrawContext &p, ISubmitter &out,
+                                      Mesh *unit, Texture *white,
+                                      const BarracksPalette &C) {
+  constexpr float W = BuildingProportions::baseWidth;
+  constexpr float D = BuildingProportions::baseDepth;
+  constexpr float H = BuildingProportions::baseHeight;
+  constexpr float rise = BuildingProportions::roofPitch;
+
+  float x = -W * 0.32f;
+  float z = -D * 0.5f - 0.06f;
+
+  float baseY = 0.18f;
+  float ridgeY = H + rise;
+  float topY = ridgeY + 0.35f;
+
+  QVector3D baseSz(BuildingProportions::chimneyWidth * 0.65f, 0.16f,
+                   BuildingProportions::chimneyWidth * 0.55f);
+  unitBox(out, unit, white, p.model, QVector3D(x, baseY + baseSz.y(), z),
+          baseSz, C.stoneDark);
+
+  int segments = 4;
+  float segH = (topY - (baseY + baseSz.y() * 2.0f)) / float(segments);
+  float w0 = BuildingProportions::chimneyWidth * 0.55f;
+  float w1 = BuildingProportions::chimneyWidth * 0.34f;
+
+  for (int i = 0; i < segments; ++i) {
+    float t = float(i) / float(segments - 1);
+    float wy = w0 * (1.0f - t) + w1 * t;
+    float hz = wy * 0.85f;
+    QVector3D col = (i % 2 == 0) ? C.stone : lerp(C.stone, C.stoneDark, 0.35f);
+    float yMid = baseY + baseSz.y() * 2.0f + segH * (i + 0.5f);
+    unitBox(out, unit, white, p.model, QVector3D(x, yMid, z),
+            QVector3D(wy, segH * 0.5f, hz), col);
+  }
 
-  float layerWidth = baseWidth / 2 + roofOverhang;
-  float layerDepth = baseDepth / 2 + roofOverhang;
+  float corbelY = topY - 0.14f;
+  unitBox(out, unit, white, p.model, QVector3D(x, corbelY, z),
+          QVector3D(w1 * 1.22f, 0.025f, w1 * 1.22f), C.stoneDark);
+  unitBox(out, unit, white, p.model, QVector3D(x, corbelY + 0.05f, z),
+          QVector3D(w1 * 1.05f, 0.02f, w1 * 1.05f),
+          lerp(C.stone, C.stoneDark, 0.2f));
 
-  unitBox(out, unit, white, p.model,
-          QVector3D(0.0f, roofBase + thatchLayerHeight * 0.5f, 0.0f),
-          QVector3D(layerWidth, thatchLayerHeight / 2, layerDepth), C.thatch);
-
-  unitBox(
-      out, unit, white, p.model,
-      QVector3D(0.0f, roofBase + thatchLayerHeight * 1.8f, 0.0f),
-      QVector3D(layerWidth * 0.85f, thatchLayerHeight / 2, layerDepth * 0.85f),
-      C.thatchDark);
-
-  unitBox(
-      out, unit, white, p.model,
-      QVector3D(0.0f, roofBase + thatchLayerHeight * 3.0f, 0.0f),
-      QVector3D(layerWidth * 0.7f, thatchLayerHeight / 2, layerDepth * 0.7f),
-      C.thatch);
+  float potH = 0.10f;
+  unitBox(out, unit, white, p.model, QVector3D(x, topY + potH * 0.5f, z),
+          QVector3D(w1 * 0.45f, potH * 0.5f, w1 * 0.45f),
+          lerp(C.stoneDark, QVector3D(0.08f, 0.08f, 0.08f), 0.35f));
+
+  unitBox(out, unit, white, p.model, QVector3D(x, H + rise * 0.55f, z + 0.06f),
+          QVector3D(w1 * 1.35f, 0.01f, 0.04f),
+          lerp(C.stoneDark, QVector3D(0.05f, 0.05f, 0.05f), 0.3f));
+
+  return ChimneyInfo{x, z, baseY, topY + potH, 0.28f};
 }
 
-static inline void drawDoor(const DrawContext &p, ISubmitter &out, Mesh *unit,
-                            Texture *white, const BarracksPalette &C) {
-  constexpr float baseDepth = BuildingProportions::baseDepth;
-  constexpr float doorWidth = BuildingProportions::doorWidth;
-  constexpr float doorHeight = BuildingProportions::doorHeight;
+static inline void drawRoofs(const DrawContext &p, ISubmitter &out, Mesh *,
+                             Texture *white, const BarracksPalette &C,
+                             const ChimneyInfo &ch) {
+  constexpr float W = BuildingProportions::baseWidth;
+  constexpr float D = BuildingProportions::baseDepth;
+  constexpr float H = BuildingProportions::baseHeight;
+  constexpr float rise = BuildingProportions::roofPitch;
+  constexpr float over = BuildingProportions::roofOverhang;
+
+  const float r = 0.085f;
+
+  const float leftX = -W * 0.5f;
+  const float rightX = W * 0.5f;
+  const float backZ = -D * 0.5f;
+  const float frontZ = D * 0.5f;
+
+  const float plateY = H;
+  const float ridgeY = H + rise;
+
+  drawCylinder(out, p.model, QVector3D(leftX - over, plateY, frontZ + over),
+               QVector3D(rightX + over, plateY, frontZ + over), r, C.woodDark,
+               white);
+  drawCylinder(out, p.model, QVector3D(leftX - over, plateY, backZ - over),
+               QVector3D(rightX + over, plateY, backZ - over), r, C.woodDark,
+               white);
+
+  drawCylinder(out, p.model, QVector3D(leftX - over * 0.5f, ridgeY, 0.0f),
+               QVector3D(rightX + over * 0.5f, ridgeY, 0.0f), r, C.timberLight,
+               white);
+
+  const int pairs = 7;
+  for (int i = 0; i < pairs; ++i) {
+    float t = (pairs == 1) ? 0.0f : (float(i) / float(pairs - 1));
+    float x = (leftX - over * 0.5f) * (1.0f - t) + (rightX + over * 0.5f) * t;
+
+    drawCylinder(out, p.model, QVector3D(x, plateY, backZ - over),
+                 QVector3D(x, ridgeY, 0.0f), r * 0.85f, C.woodDark, white);
+
+    drawCylinder(out, p.model, QVector3D(x, plateY, frontZ + over),
+                 QVector3D(x, ridgeY, 0.0f), r * 0.85f, C.woodDark, white);
+  }
 
-  unitBox(out, unit, white, p.model,
-          QVector3D(0.0f, doorHeight / 2, baseDepth / 2 + 0.01f),
-          QVector3D(doorWidth / 2, doorHeight / 2, 0.05f), C.door);
+  auto purlin = [&](float tz, bool front) {
+    float z = front ? (frontZ + over - tz * (frontZ + over))
+                    : (backZ - over - tz * (backZ - over));
+    float y = plateY + tz * (ridgeY - plateY);
+    drawCylinder(out, p.model, QVector3D(leftX - over * 0.4f, y, z),
+                 QVector3D(rightX + over * 0.4f, y, z), r * 0.6f, C.timber,
+                 white);
+  };
+  purlin(0.35f, true);
+  purlin(0.70f, true);
+  purlin(0.35f, false);
+  purlin(0.70f, false);
+
+  auto splitThatch = [&](float y, float z, float rad, const QVector3D &col) {
+    float gapL = ch.x - ch.gapRadius;
+    float gapR = ch.x + ch.gapRadius;
+    drawCylinder(out, p.model, QVector3D(leftX - over * 0.35f, y, z),
+                 QVector3D(gapL, y, z), rad, col, white);
+    drawCylinder(out, p.model, QVector3D(gapR, y, z),
+                 QVector3D(rightX + over * 0.35f, y, z), rad, col, white);
+  };
 
-  float trimW = 0.04f;
-  unitBox(out, unit, white, p.model,
-          QVector3D(0.0f, doorHeight + trimW, baseDepth / 2 + 0.01f),
-          QVector3D(doorWidth / 2 + trimW, trimW, 0.02f), C.timber);
-}
+  auto thatchRow = [&](float tz, bool front, float radScale, float tint) {
+    float z = front ? (frontZ + over - tz * (frontZ + over))
+                    : (backZ - over - tz * (backZ - over));
+    float y = plateY + tz * (ridgeY - plateY);
+    QVector3D col = lerp(C.thatchDark, C.thatch, clamp01(tint));
+    splitThatch(y, z, r * radScale, col);
+  };
 
-static inline void drawWindows(const DrawContext &p, ISubmitter &out,
-                               Mesh *unit, Texture *white,
-                               const BarracksPalette &C) {
-  constexpr float baseWidth = BuildingProportions::baseWidth;
-  constexpr float baseDepth = BuildingProportions::baseDepth;
-  constexpr float baseHeight = BuildingProportions::baseHeight;
-  constexpr float windowWidth = BuildingProportions::windowWidth;
-  constexpr float windowHeight = BuildingProportions::windowHeight;
+  const int rows = 9;
+  for (int i = 0; i < rows; ++i) {
+    float tz = float(i) / float(rows - 1);
+    float s = 1.30f - 0.6f * tz;
+    float tint = 0.2f + 0.6f * (1.0f - tz);
+    thatchRow(tz, true, s, tint);
+    thatchRow(tz * 0.98f, false, s, tint * 0.95f);
+  }
 
-  unitBox(out, unit, white, p.model,
-          QVector3D(0.0f, baseHeight * 0.6f, -baseDepth / 2 - 0.01f),
-          QVector3D(windowWidth / 2, windowHeight / 2, 0.05f), C.window);
+  float eaveY = plateY + 0.06f;
+  splitThatch(eaveY, frontZ + over * 1.02f, r * 0.55f, C.thatchDark);
+  splitThatch(eaveY, backZ - over * 1.02f, r * 0.55f, C.thatchDark);
 
-  unitBox(out, unit, white, p.model,
-          QVector3D(-baseWidth / 2 - 0.01f, baseHeight * 0.6f, 0.2f),
-          QVector3D(0.05f, windowHeight / 2 * 0.8f, windowWidth / 2 * 0.7f),
-          C.window);
-  unitBox(out, unit, white, p.model,
-          QVector3D(baseWidth / 2 + 0.01f, baseHeight * 0.6f, -0.2f),
-          QVector3D(0.05f, windowHeight / 2 * 0.8f, windowWidth / 2 * 0.7f),
-          C.window);
+  float flashY = plateY + (ridgeY - plateY) * 0.55f;
+  float flashZBack = backZ - over * 0.20f;
+  float ring = ch.gapRadius + 0.04f;
+  unitBox(out, nullptr, white, p.model, QVector3D(ch.x, flashY, flashZBack),
+          QVector3D(ring, 0.008f, 0.02f), C.stoneDark);
 }
 
-static inline void drawAnnex(const DrawContext &p, ISubmitter &out, Mesh *unit,
-                             Texture *white, const BarracksPalette &C) {
-  constexpr float baseWidth = BuildingProportions::baseWidth;
-  constexpr float annexWidth = BuildingProportions::annexWidth;
-  constexpr float annexDepth = BuildingProportions::annexDepth;
-  constexpr float annexHeight = BuildingProportions::annexHeight;
-  constexpr float annexRoofHeight = BuildingProportions::annexRoofHeight;
-  constexpr float cornerPostRadius = BuildingProportions::cornerPostRadius;
-
-  float annexX = baseWidth / 2 + annexWidth / 2 - 0.2f;
-  float annexY = annexHeight / 2;
-
-  unitBox(out, unit, white, p.model, QVector3D(annexX, annexY, -0.3f),
-          QVector3D(annexWidth / 2, annexHeight / 2, annexDepth / 2),
-          C.plasterShade);
-
-  auto annexPost = [&](float ox, float oz) {
-    QVector3D base(annexX + ox, 0.0f, -0.3f + oz);
-    QVector3D top(annexX + ox, annexHeight, -0.3f + oz);
-    drawCylinder(out, p.model, base, top, cornerPostRadius * 0.7f, C.timber,
-                 white);
-  };
+static inline void drawDoor(const DrawContext &p, ISubmitter &out, Mesh *unit,
+                            Texture *white, const BarracksPalette &C) {
+  constexpr float D = BuildingProportions::baseDepth;
+  constexpr float dW = BuildingProportions::doorWidth;
+  constexpr float dH = BuildingProportions::doorHeight;
 
-  float hw = annexWidth / 2 * 0.8f;
-  float hd = annexDepth / 2 * 0.8f;
-  annexPost(hw, hd);
-  annexPost(-hw, hd);
-  annexPost(hw, -hd);
-  annexPost(-hw, -hd);
+  const float y0 = 0.09f;
+  const float zf = D * 0.5f;
 
+  QVector3D frameCol = C.woodDark;
   unitBox(out, unit, white, p.model,
-          QVector3D(annexX, annexHeight + annexRoofHeight / 2, -0.3f),
-          QVector3D(annexWidth / 2 + 0.1f, annexRoofHeight / 2,
-                    annexDepth / 2 + 0.1f),
-          C.thatchDark);
-}
+          QVector3D(0.0f, y0 + dH * 0.5f, zf + 0.015f),
+          QVector3D(dW * 0.5f, dH * 0.5f, 0.02f), C.door);
 
-static inline void drawChimney(const DrawContext &p, ISubmitter &out,
-                               Mesh *unit, Texture *white,
-                               const BarracksPalette &C) {
-  constexpr float baseWidth = BuildingProportions::baseWidth;
-  constexpr float baseDepth = BuildingProportions::baseDepth;
-  constexpr float baseHeight = BuildingProportions::baseHeight;
-  constexpr float roofPitch = BuildingProportions::roofPitch;
-  constexpr float chimneyWidth = BuildingProportions::chimneyWidth;
-  constexpr float chimneyHeight = BuildingProportions::chimneyHeight;
-  constexpr float chimneyCapSize = BuildingProportions::chimneyCapSize;
+  float plankW = dW / 6.0f;
+  for (int i = 0; i < 6; ++i) {
+    float cx = -dW * 0.5f + plankW * (i + 0.5f);
+    QVector3D plankCol = lerp(C.door, C.woodDark, 0.15f * (i % 2));
+    unitBox(out, unit, white, p.model,
+            QVector3D(cx, y0 + dH * 0.5f, zf + 0.022f),
+            QVector3D(plankW * 0.48f, dH * 0.48f, 0.006f), plankCol);
+  }
 
-  float chimneyX = -baseWidth / 2 + 0.4f;
-  float chimneyZ = -baseDepth / 2 + 0.3f;
-  float chimneyBase = baseHeight + roofPitch * 0.3f;
+  drawCylinder(out, p.model,
+               QVector3D(-dW * 0.45f, y0 + dH * 0.35f, zf + 0.03f),
+               QVector3D(+dW * 0.45f, y0 + dH * 0.35f, zf + 0.03f), 0.02f,
+               frameCol, white);
 
-  unitBox(out, unit, white, p.model,
-          QVector3D(chimneyX, chimneyBase + chimneyHeight / 2, chimneyZ),
-          QVector3D(chimneyWidth / 2, chimneyHeight / 2, chimneyWidth / 2),
-          C.stone);
+  drawCylinder(out, p.model, QVector3D(dW * 0.32f, y0 + dH * 0.45f, zf + 0.04f),
+               QVector3D(dW * 0.42f, y0 + dH * 0.45f, zf + 0.04f), 0.012f,
+               C.timberLight, white);
 
   unitBox(out, unit, white, p.model,
-          QVector3D(chimneyX, chimneyBase + chimneyHeight + 0.08f, chimneyZ),
-          QVector3D(chimneyCapSize / 2, 0.08f, chimneyCapSize / 2),
-          C.stoneDark);
+          QVector3D(0.0f, y0 + dH + 0.10f, zf + 0.02f),
+          QVector3D(0.22f, 0.06f, 0.01f), C.woodDark);
+  unitBox(out, unit, white, p.model,
+          QVector3D(0.0f, y0 + dH + 0.10f, zf + 0.025f),
+          QVector3D(0.18f, 0.05f, 0.008f), C.team);
+  unitBox(out, unit, white, p.model,
+          QVector3D(0.0f, y0 + dH + 0.10f, zf + 0.03f),
+          QVector3D(0.08f, 0.02f, 0.007f), C.teamTrim);
 }
 
-static inline void drawPavers(const DrawContext &p, ISubmitter &out, Mesh *unit,
-                              Texture *white, const BarracksPalette &C) {
-  constexpr float baseDepth = BuildingProportions::baseDepth;
-  constexpr float foundationHeight = BuildingProportions::foundationHeight;
-
-  auto paver = [&](float ox, float oz, float sx, float sz) {
-    unitBox(out, unit, white, p.model,
-            QVector3D(0.0f, -foundationHeight + 0.02f, baseDepth / 2 + oz),
-            QVector3D(sx, 0.02f, sz), C.path);
+static inline void drawWindows(const DrawContext &p, ISubmitter &out,
+                               Mesh *unit, Texture *white,
+                               const BarracksPalette &C) {
+  constexpr float W = BuildingProportions::baseWidth;
+  constexpr float D = BuildingProportions::baseDepth;
+  constexpr float H = BuildingProportions::baseHeight;
+
+  const float leftX = -W * 0.5f;
+  const float rightX = W * 0.5f;
+  const float backZ = -D * 0.5f;
+  const float frontZ = D * 0.5f;
+
+  float w = BuildingProportions::windowWidth * 0.55f;
+  float h = BuildingProportions::windowHeight * 0.55f;
+  float frameT = 0.03f;
+
+  auto framedWindow = [&](QVector3D center, bool shutters) {
+    unitBox(out, unit, white, p.model, center + QVector3D(0, 0, 0.012f),
+            QVector3D(w * 0.5f, h * 0.5f, 0.008f), C.window);
+    unitBox(out, unit, white, p.model, center + QVector3D(0, 0, 0.016f),
+            QVector3D(w * 0.5f, frameT, 0.006f), C.timber);
+    unitBox(out, unit, white, p.model, center + QVector3D(0, 0, 0.016f),
+            QVector3D(frameT, h * 0.5f, 0.006f), C.timber);
+    unitBox(out, unit, white, p.model, center + QVector3D(0, 0, 0.016f),
+            QVector3D(w * 0.5f, frameT, 0.006f), C.timber);
+    unitBox(out, unit, white, p.model, center + QVector3D(0, 0, 0.016f),
+            QVector3D(frameT, h * 0.5f, 0.006f), C.timber);
+
+    unitBox(out, unit, white, p.model, center + QVector3D(0, 0, 0.02f),
+            QVector3D(w * 0.02f, h * 0.48f, 0.004f), C.timberLight);
+    unitBox(out, unit, white, p.model, center + QVector3D(0, 0, 0.02f),
+            QVector3D(w * 0.48f, h * 0.02f, 0.004f), C.timberLight);
+
+    if (shutters) {
+      unitBox(out, unit, white, p.model,
+              center + QVector3D(-w * 0.65f, 0, 0.018f),
+              QVector3D(w * 0.30f, h * 0.55f, 0.004f), C.woodDark);
+      unitBox(out, unit, white, p.model,
+              center + QVector3D(+w * 0.65f, 0, 0.018f),
+              QVector3D(w * 0.30f, h * 0.55f, 0.004f), C.woodDark);
+    }
   };
 
-  paver(0.0f, 0.3f, 0.8f, 0.25f);
-  paver(0.0f, 0.6f, 0.6f, 0.2f);
-  paver(0.0f, 0.85f, 0.7f, 0.15f);
+  framedWindow(QVector3D(-0.65f, 0.95f, frontZ + 0.01f), true);
+  framedWindow(QVector3D(+0.65f, 0.95f, frontZ + 0.01f), true);
+  framedWindow(QVector3D(leftX + 0.06f, 0.85f, 0.0f), false);
+  framedWindow(QVector3D(rightX - 0.06f, 0.85f, 0.0f), false);
+  framedWindow(QVector3D(0.0f, 1.00f, backZ - 0.01f), true);
 }
 
-static inline void drawCrates(const DrawContext &p, ISubmitter &out, Mesh *unit,
-                              Texture *white, const BarracksPalette &C) {
-  constexpr float baseWidth = BuildingProportions::baseWidth;
-  constexpr float baseDepth = BuildingProportions::baseDepth;
-
-  float crateX = baseWidth / 2 - 0.4f;
-  float crateZ = baseDepth / 2 - 0.3f;
+static inline void drawAnnex(const DrawContext &p, ISubmitter &out, Mesh *unit,
+                             Texture *white, const BarracksPalette &C) {
+  constexpr float W = BuildingProportions::baseWidth;
+  constexpr float D = BuildingProportions::baseDepth;
+  constexpr float h = BuildingProportions::annexHeight;
+  constexpr float w = BuildingProportions::annexWidth;
+  constexpr float d = BuildingProportions::annexDepth;
+
+  float x = W * 0.5f + w * 0.5f - 0.05f;
+  float z = 0.05f;
+
+  unitBox(out, unit, white, p.model, QVector3D(x, h * 0.5f, z),
+          QVector3D(w * 0.5f, h * 0.5f, d * 0.5f), C.plasterShade);
+
+  unitBox(out, unit, white, p.model, QVector3D(x, h + 0.02f, z),
+          QVector3D(w * 0.55f, 0.02f, d * 0.55f), C.woodDark);
+
+  float plateY = h;
+  float frontZ = z + d * 0.5f;
+  float backZ = z - d * 0.5f;
+  drawCylinder(out, p.model, QVector3D(x - w * 0.52f, plateY, backZ - 0.12f),
+               QVector3D(x + w * 0.52f, plateY, backZ - 0.12f), 0.05f,
+               C.woodDark, white);
+
+  float ridgeY = h + BuildingProportions::annexRoofHeight;
+  drawCylinder(out, p.model, QVector3D(x - w * 0.50f, ridgeY, backZ - 0.02f),
+               QVector3D(x + w * 0.50f, ridgeY, backZ - 0.02f), 0.05f,
+               C.timberLight, white);
+
+  int rows = 6;
+  for (int i = 0; i < rows; ++i) {
+    float t = float(i) / float(rows - 1);
+    float y = plateY + t * (ridgeY - plateY);
+    float zrow = backZ - 0.02f - 0.10f * (1.0f - t);
+    QVector3D col = lerp(C.thatchDark, C.thatch, 0.5f + 0.4f * (1.0f - t));
+    drawCylinder(out, p.model, QVector3D(x - w * 0.55f, y, zrow),
+                 QVector3D(x + w * 0.55f, y, zrow), 0.06f * (1.15f - 0.6f * t),
+                 col, white);
+  }
 
-  unitBox(out, unit, white, p.model, QVector3D(crateX, 0.12f, crateZ),
-          QVector3D(0.12f, 0.12f, 0.12f), C.crate);
   unitBox(out, unit, white, p.model,
-          QVector3D(crateX + 0.28f, 0.10f, crateZ + 0.05f),
-          QVector3D(0.10f, 0.10f, 0.10f), C.crate * 0.9f);
+          QVector3D(x + w * 0.01f, 0.55f, frontZ + 0.01f),
+          QVector3D(0.20f, 0.18f, 0.01f), C.door);
 }
 
-static inline void drawFencePosts(const DrawContext &p, ISubmitter &out,
-                                  Mesh *unit, Texture *white,
-                                  const BarracksPalette &C) {
-  constexpr float baseWidth = BuildingProportions::baseWidth;
-  constexpr float baseDepth = BuildingProportions::baseDepth;
-
-  for (int i = 0; i < 4; ++i) {
-    float x = -baseWidth / 2 - 0.25f;
-    float z = -baseDepth / 2 + 0.3f + i * 0.35f;
-    QVector3D bottom(x, 0.0f, z);
-    QVector3D top(x, 0.6f, z);
-    drawCylinder(out, p.model, bottom, top, 0.04f, C.timber, white);
-  }
-
-  for (int i = 0; i < 3; ++i) {
-    float x = -baseWidth / 2 - 0.25f;
-    float z1 = -baseDepth / 2 + 0.3f + i * 0.35f;
-    float z2 = z1 + 0.35f;
-    drawCylinder(out, p.model, QVector3D(x, 0.35f, z1), QVector3D(x, 0.35f, z2),
-                 0.025f, C.timberLight, white);
-  }
+static inline void drawProps(const DrawContext &p, ISubmitter &out, Mesh *unit,
+                             Texture *white, const BarracksPalette &C) {
+  unitBox(out, unit, white, p.model, QVector3D(0.85f, 0.10f, 0.90f),
+          QVector3D(0.16f, 0.10f, 0.16f), C.crate);
+  unitBox(out, unit, white, p.model, QVector3D(0.85f, 0.22f, 0.90f),
+          QVector3D(0.12f, 0.02f, 0.12f), C.woodDark);
+
+  unitBox(out, unit, white, p.model, QVector3D(-0.9f, 0.12f, -0.80f),
+          QVector3D(0.12f, 0.10f, 0.12f), C.crate);
+  unitBox(out, unit, white, p.model, QVector3D(-0.9f, 0.20f, -0.80f),
+          QVector3D(0.13f, 0.02f, 0.13f), C.woodDark);
 }
 
 static inline void drawBannerAndPole(const DrawContext &p, ISubmitter &out,
@@ -397,28 +520,46 @@ static inline void drawBannerAndPole(const DrawContext &p, ISubmitter &out,
   constexpr float bannerWidth = BuildingProportions::bannerWidth;
   constexpr float bannerHeight = BuildingProportions::bannerHeight;
 
-  float poleX = -baseWidth / 2 - 0.35f;
-  float poleZ = baseDepth / 2 - 0.4f;
+  float poleX = -baseWidth / 2 - 0.65f;
+  float poleZ = baseDepth / 2 - 0.2f;
 
-  drawCylinder(out, p.model, QVector3D(poleX, 0.0f, poleZ),
-               QVector3D(poleX, bannerPoleHeight, poleZ), bannerPoleRadius,
-               C.timber, white);
+  float poleHeight = bannerPoleHeight * 1.9f;
+  float poleRadius = bannerPoleRadius * 1.3f;
+  float bw = bannerWidth * 1.8f;
+  float bh = bannerHeight * 1.8f;
 
-  drawSphere(out, p.model, QVector3D(poleX, bannerPoleHeight + 0.08f, poleZ),
-             0.08f, C.teamTrim, white);
+  QVector3D poleCenter(poleX, poleHeight / 2.0f, poleZ);
+  QVector3D poleSize(poleRadius * 1.6f, poleHeight / 2.0f, poleRadius * 1.6f);
+  unitBox(out, unit, white, p.model, poleCenter, poleSize, C.woodDark);
 
+  float targetWidth = bw * 1.25f;
+  float targetHeight = bh * 0.75f;
+  float panelDepth = 0.02f;
+
+  float beamLength = targetWidth * 0.45f;
+  float beamY = poleHeight - targetHeight * 0.25f;
+  QVector3D beamStart(poleX + 0.02f, beamY, poleZ);
+  QVector3D beamEnd(poleX + beamLength + 0.02f, beamY, poleZ);
+  drawCylinder(out, p.model, beamStart, beamEnd, poleRadius * 0.35f, C.timber,
+               white);
+
+  QVector3D connectorTop(beamEnd.x(), beamEnd.y() - targetHeight * 0.35f,
+                         beamEnd.z());
+  drawCylinder(out, p.model, beamEnd, connectorTop, poleRadius * 0.18f,
+               C.timberLight, white);
+
+  float panelX = beamEnd.x() + (targetWidth * 0.5f - beamLength);
   unitBox(out, unit, white, p.model,
-          QVector3D(poleX + bannerWidth / 2,
-                    bannerPoleHeight - bannerHeight / 2, poleZ),
-          QVector3D(bannerWidth / 2, bannerHeight / 2, 0.02f), C.team);
+          QVector3D(panelX, poleHeight - targetHeight / 2.0f, poleZ + 0.01f),
+          QVector3D(targetWidth / 2.0f, targetHeight / 2.0f, panelDepth),
+          C.team);
 
   unitBox(out, unit, white, p.model,
-          QVector3D(poleX + bannerWidth / 2,
-                    bannerPoleHeight - bannerHeight + 0.04f, poleZ),
-          QVector3D(bannerWidth / 2 + 0.02f, 0.04f, 0.015f), C.teamTrim);
+          QVector3D(panelX, poleHeight - targetHeight + 0.04f, poleZ + 0.01f),
+          QVector3D(targetWidth / 2.0f + 0.02f, 0.04f, 0.015f), C.teamTrim);
   unitBox(out, unit, white, p.model,
-          QVector3D(poleX + bannerWidth / 2, bannerPoleHeight - 0.04f, poleZ),
-          QVector3D(bannerWidth / 2 + 0.02f, 0.04f, 0.015f), C.teamTrim);
+          QVector3D(panelX, poleHeight - 0.04f, poleZ + 0.01f),
+          QVector3D(targetWidth / 2.0f + 0.02f, 0.04f, 0.015f), C.teamTrim);
 }
 
 static inline void drawRallyFlagIfAny(const DrawContext &p, ISubmitter &out,
@@ -438,6 +579,46 @@ static inline void drawRallyFlagIfAny(const DrawContext &p, ISubmitter &out,
   }
 }
 
+static inline void drawHealthBar(const DrawContext &p, ISubmitter &out,
+                                 Mesh *unit, Texture *white) {
+  if (!p.entity)
+    return;
+  auto *u = p.entity->getComponent<Engine::Core::UnitComponent>();
+  if (!u)
+    return;
+
+  int mh = std::max(1, u->maxHealth);
+  float ratio = std::clamp(u->health / float(mh), 0.0f, 1.0f);
+  if (ratio <= 0.0f)
+    return;
+
+  constexpr float baseHeight = BuildingProportions::baseHeight;
+  constexpr float roofPitch = BuildingProportions::roofPitch;
+  float roofPeak = baseHeight + roofPitch;
+  float barY = roofPeak + 0.12f;
+
+  constexpr float barWidth = BuildingProportions::baseWidth * 0.9f;
+  constexpr float barHeight = 0.08f;
+  constexpr float barDepth = 0.12f;
+
+  QVector3D bgColor(0.06f, 0.06f, 0.06f);
+  unitBox(out, unit, white, p.model, QVector3D(0.0f, barY, 0.0f),
+          QVector3D(barWidth / 2.0f, barHeight / 2.0f, barDepth / 2.0f),
+          bgColor);
+
+  float fillWidth = barWidth * ratio;
+  float fillX = -(barWidth - fillWidth) * 0.5f;
+
+  QVector3D red(0.85f, 0.15f, 0.15f);
+  QVector3D green(0.22f, 0.78f, 0.22f);
+  QVector3D fgColor = green * ratio + red * (1.0f - ratio);
+
+  unitBox(out, unit, white, p.model, QVector3D(fillX, barY + 0.005f, 0.0f),
+          QVector3D(fillWidth / 2.0f, (barHeight / 2.0f) * 0.9f,
+                    (barDepth / 2.0f) * 0.95f),
+          fgColor);
+}
+
 static inline void drawSelectionFX(const DrawContext &p, ISubmitter &out) {
   QMatrix4x4 M;
   QVector3D pos = p.model.column(3).toVector3D();
@@ -465,19 +646,17 @@ static void drawBarracks(const DrawContext &p, ISubmitter &out) {
   BarracksPalette C = makePalette(team);
 
   drawFoundation(p, out, unit, white, C);
-  drawTimberFrame(p, out, unit, white, C);
+  drawAnnex(p, out, unit, white, C);
   drawWalls(p, out, unit, white, C);
-  drawRoofs(p, out, unit, white, C);
+  ChimneyInfo ch = drawChimney(p, out, unit, white, C);
+  drawRoofs(p, out, unit, white, C, ch);
   drawDoor(p, out, unit, white, C);
   drawWindows(p, out, unit, white, C);
-  drawAnnex(p, out, unit, white, C);
-  drawChimney(p, out, unit, white, C);
-  drawPavers(p, out, unit, white, C);
-  drawCrates(p, out, unit, white, C);
-  drawFencePosts(p, out, unit, white, C);
   drawBannerAndPole(p, out, unit, white, C);
+  drawProps(p, out, unit, white, C);
 
   drawRallyFlagIfAny(p, out, white, C);
+  drawHealthBar(p, out, unit, white);
   drawSelectionFX(p, out);
 }
 
@@ -487,4 +666,4 @@ void registerBarracksRenderer(Render::GL::EntityRendererRegistry &registry) {
   registry.registerRenderer("barracks", drawBarracks);
 }
 
-} // namespace Render::GL
+} // namespace Render::GL

+ 9 - 1
render/scene_renderer.cpp

@@ -27,12 +27,16 @@ bool Renderer::initialize() {
 void Renderer::shutdown() { m_backend.reset(); }
 
 void Renderer::beginFrame() {
+  if (m_paused.load())
+    return;
   if (m_backend)
     m_backend->beginFrame();
   m_queue.clear();
 }
 
 void Renderer::endFrame() {
+  if (m_paused.load())
+    return;
   if (m_backend && m_camera) {
     m_queue.sortForBatching();
     m_backend->execute(m_queue, *m_camera);
@@ -101,9 +105,13 @@ void Renderer::selectionSmoke(const QMatrix4x4 &model, const QVector3D &color,
 }
 
 void Renderer::renderWorld(Engine::Core::World *world) {
+  if (m_paused.load())
+    return;
   if (!world)
     return;
 
+  std::lock_guard<std::mutex> guard(m_worldMutex);
+
   auto renderableEntities =
       world->getEntitiesWith<Engine::Core::RenderableComponent>();
 
@@ -133,7 +141,7 @@ void Renderer::renderWorld(Engine::Core::World *world) {
 
           ctx.selected =
               (m_selectedIds.find(entity->getId()) != m_selectedIds.end());
-          ctx.hovered = (entity->getId() == m_hoveredBuildingId);
+          ctx.hovered = (entity->getId() == m_hoveredEntityId);
           ctx.animationTime = m_accumulatedTime;
           fn(ctx, *this);
           drawnByRegistry = true;

+ 14 - 2
render/scene_renderer.h

@@ -7,7 +7,9 @@
 #include "gl/resources.h"
 #include "gl/texture.h"
 #include "submitter.h"
+#include <atomic>
 #include <memory>
+#include <mutex>
 #include <optional>
 #include <unordered_set>
 #include <vector>
@@ -52,7 +54,7 @@ public:
   ResourceManager *resources() const {
     return m_backend ? m_backend->resources() : nullptr;
   }
-  void setHoveredBuildingId(unsigned int id) { m_hoveredBuildingId = id; }
+  void setHoveredEntityId(unsigned int id) { m_hoveredEntityId = id; }
 
   void setSelectedEntities(const std::vector<unsigned int> &ids) {
     m_selectedIds.clear();
@@ -96,6 +98,10 @@ public:
   void setGridParams(const GridParams &gp) { m_gridParams = gp; }
   const GridParams &gridParams() const { return m_gridParams; }
 
+  void pause() { m_paused = true; }
+  void resume() { m_paused = false; }
+  bool isPaused() const { return m_paused; }
+
   void mesh(Mesh *mesh, const QMatrix4x4 &model, const QVector3D &color,
             Texture *texture = nullptr, float alpha = 1.0f) override;
   void selectionRing(const QMatrix4x4 &model, float alphaInner,
@@ -109,19 +115,25 @@ public:
 
   void renderWorld(Engine::Core::World *world);
 
+  void lockWorldForModification() { m_worldMutex.lock(); }
+  void unlockWorldForModification() { m_worldMutex.unlock(); }
+
 private:
   Camera *m_camera = nullptr;
   std::shared_ptr<Backend> m_backend;
   DrawQueue m_queue;
 
   std::unique_ptr<EntityRendererRegistry> m_entityRegistry;
-  unsigned int m_hoveredBuildingId = 0;
+  unsigned int m_hoveredEntityId = 0;
   std::unordered_set<unsigned int> m_selectedIds;
 
   int m_viewportWidth = 0;
   int m_viewportHeight = 0;
   GridParams m_gridParams;
   float m_accumulatedTime = 0.0f;
+  std::atomic<bool> m_paused{false};
+
+  std::mutex m_worldMutex;
 };
 
 } // namespace Render::GL

+ 4 - 1
ui/qml/Main.qml

@@ -7,6 +7,8 @@ ApplicationWindow {
     id: mainWindow
     width: 1280
     height: 720
+    
+    visibility: Window.FullScreen
     visible: true
     title: "Standard of Iron - RTS Game"
 
@@ -151,6 +153,7 @@ ApplicationWindow {
         }
 
         onMapChosen: function(mapPath) {
+            console.log("Main: onMapChosen received", mapPath, "game=", typeof game, "startSkirmish=", (typeof game !== 'undefined' && !!game.startSkirmish))
             if (typeof game !== 'undefined' && game.startSkirmish) game.startSkirmish(mapPath)
             mapSelect.visible = false
             mainWindow.menuVisible = false
@@ -160,7 +163,7 @@ ApplicationWindow {
         }
         onCancelled: function() {
             mapSelect.visible = false
-            mainMenu.visible = true
+            mainWindow.menuVisible = true
         }
     }
 

+ 1 - 1
ui/qml/MainMenu.qml

@@ -260,7 +260,7 @@ Item {
     }
 
     
-    Keys.onPressed: {
+    Keys.onPressed: function(event) {
         if (event.key === Qt.Key_Down) {
             container.selectedIndex = Math.min(container.selectedIndex + 1, menuModel.count - 1)
             event.accepted = true

+ 54 - 27
ui/qml/MapSelect.qml

@@ -35,11 +35,52 @@ Item {
     }
     function field(obj, key) { return (obj && obj[key] !== undefined) ? String(obj[key]) : "" }
     function current() { return mget(list.currentIndex) }
+    function acceptSelection() {
+        
+        if (!visible) return
+        if (list.currentIndex < 0 || list.count <= 0) return
+        var it = current()
+        var p = field(it, "path") || field(it, "file")
+        console.log("MapSelect: acceptSelection called, path=", p, "visible=", visible, "currentIndex=", list.currentIndex)
+        if (p && p.length > 0) {
+            console.log("MapSelect: emitting mapChosen for", p)
+            root.mapChosen(p)
+        } else {
+            console.log("MapSelect: no valid path to choose")
+        }
+    }
 
     
     Rectangle { anchors.fill: parent; color: dim }
 
     
+    
+    Keys.onPressed: function(event) {
+        
+        
+        
+        if (!visible) return;
+
+        
+        
+    if (event.key === Qt.Key_Down) {
+            if (list.count > 0)
+                list.currentIndex = Math.min(list.currentIndex + 1, list.count - 1)
+            event.accepted = true
+        } else if (event.key === Qt.Key_Up) {
+            if (list.count > 0)
+                list.currentIndex = Math.max(list.currentIndex - 1, 0)
+            event.accepted = true
+        } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
+            acceptSelection()
+            event.accepted = true
+        } else if (event.key === Qt.Key_Escape) {
+            backBtn.clicked()
+            event.accepted = true
+        }
+    }
+
+    
     Rectangle {
         id: panel
         anchors.centerIn: parent
@@ -98,6 +139,15 @@ Item {
                 keyNavigationWraps: false
                 boundsBehavior: Flickable.StopAtBounds
 
+                
+                
+                Keys.onPressed: function(event) {
+                    if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
+                        root.acceptSelection()
+                        event.accepted = true
+                    }
+                }
+
                 ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
 
                 
@@ -126,7 +176,7 @@ Item {
                         onEntered: { hovered = true; list.currentIndex = index }
                         onExited:  hovered = false
                         onClicked: list.currentIndex = index
-                        onDoubleClicked: playPrimary.clicked()
+                        onDoubleClicked: acceptSelection()
                     }
 
                     Rectangle {
@@ -372,11 +422,7 @@ Item {
                     Behavior on border.color { ColorAnimation { duration: 140 } }
                 }
 
-                onClicked: {
-                    var it = current()
-                    var p = field(it, "path") || field(it, "file")
-                    root.mapChosen(p)
-                }
+                onClicked: acceptSelection()
 
                 ToolTip.visible: playHover.containsMouse
                 ToolTip.text: "Enter"
@@ -384,26 +430,7 @@ Item {
         }
 
         
-        Keys.onPressed: {
-            if (event.key === Qt.Key_Down) {
-                if (list.count > 0)
-                    list.currentIndex = Math.min(list.currentIndex + 1, list.count - 1)
-                event.accepted = true
-            } else if (event.key === Qt.Key_Up) {
-                if (list.count > 0)
-                    list.currentIndex = Math.max(list.currentIndex - 1, 0)
-                event.accepted = true
-            } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
-                if (list.currentIndex >= 0 && list.count > 0) {
-                    var it = current()
-                    var p = field(it, "path") || field(it, "file")
-                    root.mapChosen(p)
-                }
-                event.accepted = true
-            } else if (event.key === Qt.Key_Escape) {
-                root.cancelled()
-                event.accepted = true
-            }
-        }
+        
+        
     }
 }