|
|
@@ -14,14 +14,17 @@
|
|
|
#include "game/systems/combat_system.h"
|
|
|
#include "game/systems/selection_system.h"
|
|
|
#include "game/systems/arrow_system.h"
|
|
|
+#include "game/systems/production_system.h"
|
|
|
#include "game/map/map_loader.h"
|
|
|
#include "game/map/map_transformer.h"
|
|
|
#include "game/visuals/visual_catalog.h"
|
|
|
#include "game/units/factory.h"
|
|
|
+#include "game/units/unit.h"
|
|
|
#include "game/map/environment.h"
|
|
|
|
|
|
#include "selected_units_model.h"
|
|
|
#include <cmath>
|
|
|
+#include <limits>
|
|
|
GameEngine::GameEngine() {
|
|
|
m_world = std::make_unique<Engine::Core::World>();
|
|
|
m_renderer = std::make_unique<Render::GL::Renderer>();
|
|
|
@@ -34,6 +37,7 @@ GameEngine::GameEngine() {
|
|
|
m_world->addSystem(std::make_unique<Game::Systems::MovementSystem>());
|
|
|
m_world->addSystem(std::make_unique<Game::Systems::CombatSystem>());
|
|
|
m_world->addSystem(std::make_unique<Game::Systems::AISystem>());
|
|
|
+ m_world->addSystem(std::make_unique<Game::Systems::ProductionSystem>());
|
|
|
|
|
|
m_selectionSystem = std::make_unique<Game::Systems::SelectionSystem>();
|
|
|
m_world->addSystem(std::make_unique<Game::Systems::SelectionSystem>());
|
|
|
@@ -92,13 +96,125 @@ void GameEngine::onRightClick(qreal sx, qreal sy) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+void GameEngine::setHoverAtScreen(qreal sx, qreal sy) {
|
|
|
+ if (!m_window) return;
|
|
|
+ ensureInitialized();
|
|
|
+ // Negative coords are used by QML to signal hover exit
|
|
|
+ if (sx < 0 || sy < 0) {
|
|
|
+ if (m_hoveredBuildingId != 0) { m_hoveredBuildingId = 0; }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ auto prevHover = m_hoveredBuildingId;
|
|
|
+ m_hoveredBuildingId = 0;
|
|
|
+
|
|
|
+ // Helper: project a base rectangle (XZ) to screen and return bounds
|
|
|
+ auto projectBounds = [&](const QVector3D& center, float hx, float hz, QRectF& out) -> bool {
|
|
|
+ QPointF p1, p2, p3, p4;
|
|
|
+ bool ok1 = worldToScreen(QVector3D(center.x() - hx, center.y() + 0.0f, center.z() - hz), p1);
|
|
|
+ bool ok2 = worldToScreen(QVector3D(center.x() + hx, center.y() + 0.0f, center.z() - hz), p2);
|
|
|
+ bool ok3 = worldToScreen(QVector3D(center.x() + hx, center.y() + 0.0f, center.z() + hz), p3);
|
|
|
+ bool ok4 = worldToScreen(QVector3D(center.x() - hx, center.y() + 0.0f, center.z() + hz), p4);
|
|
|
+ if (!(ok1 && ok2 && ok3 && ok4)) return false;
|
|
|
+ float minX = std::min(std::min(float(p1.x()), float(p2.x())), std::min(float(p3.x()), float(p4.x())));
|
|
|
+ float maxX = std::max(std::max(float(p1.x()), float(p2.x())), std::max(float(p3.x()), float(p4.x())));
|
|
|
+ float minY = std::min(std::min(float(p1.y()), float(p2.y())), std::min(float(p3.y()), float(p4.y())));
|
|
|
+ float maxY = std::max(std::max(float(p1.y()), float(p2.y())), std::max(float(p3.y()), float(p4.y())));
|
|
|
+ out = QRectF(QPointF(minX, minY), QPointF(maxX, maxY));
|
|
|
+ return true;
|
|
|
+ };
|
|
|
+
|
|
|
+ // Hysteresis: keep current hover if mouse stays within an expanded screen-space bounds
|
|
|
+ if (prevHover) {
|
|
|
+ if (auto* e = m_world->getEntity(prevHover)) {
|
|
|
+ if (e->hasComponent<Engine::Core::BuildingComponent>()) {
|
|
|
+ if (auto* t = e->getComponent<Engine::Core::TransformComponent>()) {
|
|
|
+ // If production UI is active (inProgress), be extra forgiving
|
|
|
+ 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(QVector3D(t->position.x, t->position.y, t->position.z), hxk, hzk, bounds)) {
|
|
|
+ bounds.adjust(-pxPad, -pxPad, pxPad, pxPad);
|
|
|
+ if (bounds.contains(QPointF(sx, sy))) {
|
|
|
+ m_hoveredBuildingId = prevHover;
|
|
|
+ m_hoverGraceTicks = 6;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ float bestD2 = std::numeric_limits<float>::max();
|
|
|
+ auto ents = m_world->getEntitiesWith<Engine::Core::TransformComponent>();
|
|
|
+ for (auto* e : ents) {
|
|
|
+ if (!e->hasComponent<Engine::Core::UnitComponent>()) continue;
|
|
|
+ if (!e->hasComponent<Engine::Core::BuildingComponent>()) continue;
|
|
|
+ auto* t = e->getComponent<Engine::Core::TransformComponent>();
|
|
|
+ // Screen-space bounds of the building base with modest padding
|
|
|
+ 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(QVector3D(t->position.x, t->position.y, t->position.z), hx, hz, bounds)) continue;
|
|
|
+ if (!bounds.contains(QPointF(sx, sy))) continue;
|
|
|
+ // Break ties by closeness to projected center
|
|
|
+ QPointF centerSp;
|
|
|
+ if (!worldToScreen(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; m_hoveredBuildingId = e->getId(); }
|
|
|
+ }
|
|
|
+
|
|
|
+ // If we acquired (or re-acquired) a hover, extend grace a bit to ride through transient changes
|
|
|
+ if (m_hoveredBuildingId != 0 && m_hoveredBuildingId != prevHover) {
|
|
|
+ m_hoverGraceTicks = 6;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Hysteresis: if we had a previous hover, allow it to persist with a slightly larger screen-space pad
|
|
|
+ if (m_hoveredBuildingId == 0 && prevHover != 0) {
|
|
|
+ if (auto* e = m_world->getEntity(prevHover)) {
|
|
|
+ auto* t = e->getComponent<Engine::Core::TransformComponent>();
|
|
|
+ if (t && e->getComponent<Engine::Core::BuildingComponent>()) {
|
|
|
+ // Grow keep pad slightly more if production is in progress
|
|
|
+ const float marginXZ = 1.12f; // tiny extra pad
|
|
|
+ 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(QVector3D(t->position.x, t->position.y, t->position.z), hx, hz, bounds)) {
|
|
|
+ if (bounds.contains(QPointF(sx, sy))) {
|
|
|
+ m_hoveredBuildingId = prevHover;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Grace period: avoid rapid clear/re-acquire near edges
|
|
|
+ if (m_hoveredBuildingId == 0 && prevHover != 0 && m_hoverGraceTicks > 0) {
|
|
|
+ m_hoveredBuildingId = prevHover;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
void GameEngine::onClickSelect(qreal sx, qreal sy, bool additive) {
|
|
|
if (!m_window || !m_selectionSystem) return;
|
|
|
ensureInitialized();
|
|
|
// Pick closest unit to the cursor in screen space within a radius
|
|
|
- const float pickRadius = 18.0f; // pixels
|
|
|
- float bestDist2 = pickRadius * pickRadius;
|
|
|
- Engine::Core::EntityID bestId = 0;
|
|
|
+ const float baseUnitPickRadius = 18.0f; // pixels
|
|
|
+ const float baseBuildingPickRadius = 28.0f; // base px, used as fallback when projection fails
|
|
|
+ float bestUnitDist2 = std::numeric_limits<float>::max();
|
|
|
+ float bestBuildingDist2 = std::numeric_limits<float>::max();
|
|
|
+ Engine::Core::EntityID bestUnitId = 0;
|
|
|
+ Engine::Core::EntityID bestBuildingId = 0;
|
|
|
auto ents = m_world->getEntitiesWith<Engine::Core::TransformComponent>();
|
|
|
for (auto* e : ents) {
|
|
|
if (!e->hasComponent<Engine::Core::UnitComponent>()) continue;
|
|
|
@@ -110,13 +226,65 @@ void GameEngine::onClickSelect(qreal sx, qreal sy, bool additive) {
|
|
|
float dx = float(sx) - float(sp.x());
|
|
|
float dy = float(sy) - float(sp.y());
|
|
|
float d2 = dx*dx + dy*dy;
|
|
|
- if (d2 < bestDist2) { bestDist2 = d2; bestId = e->getId(); }
|
|
|
+ if (e->hasComponent<Engine::Core::BuildingComponent>()) {
|
|
|
+ // Prefer accurate hit test using projected 3D bounding box (covers roof/body)
|
|
|
+ bool hit = false;
|
|
|
+ float pickDist2 = d2; // default to center distance for tie-break
|
|
|
+ // Generous half-extents to cover composed mesh (foundation/roof overhangs)
|
|
|
+ const float marginXZ = 1.6f;
|
|
|
+ const float marginY = 1.2f;
|
|
|
+ float hx = std::max(0.6f, t->scale.x * marginXZ);
|
|
|
+ float hz = std::max(0.6f, t->scale.z * marginXZ);
|
|
|
+ float hy = std::max(0.5f, t->scale.y * marginY);
|
|
|
+ QVector<QPointF> pts;
|
|
|
+ pts.reserve(8);
|
|
|
+ auto project = [&](const QVector3D& w){ QPointF sp; if (worldToScreen(w, sp)) { pts.push_back(sp); return true; } return false; };
|
|
|
+ bool ok =
|
|
|
+ project(QVector3D(t->position.x - hx, t->position.y + 0.0f, t->position.z - hz)) &&
|
|
|
+ project(QVector3D(t->position.x + hx, t->position.y + 0.0f, t->position.z - hz)) &&
|
|
|
+ project(QVector3D(t->position.x + hx, t->position.y + 0.0f, t->position.z + hz)) &&
|
|
|
+ project(QVector3D(t->position.x - hx, t->position.y + 0.0f, t->position.z + hz)) &&
|
|
|
+ project(QVector3D(t->position.x - hx, t->position.y + hy, t->position.z - hz)) &&
|
|
|
+ project(QVector3D(t->position.x + hx, t->position.y + hy, t->position.z - hz)) &&
|
|
|
+ project(QVector3D(t->position.x + hx, t->position.y + hy, t->position.z + hz)) &&
|
|
|
+ project(QVector3D(t->position.x - hx, t->position.y + hy, t->position.z + hz));
|
|
|
+ if (ok && pts.size() == 8) {
|
|
|
+ float minX = pts[0].x(), maxX = pts[0].x();
|
|
|
+ float minY = pts[0].y(), maxY = pts[0].y();
|
|
|
+ for (const auto& p2 : pts) { minX = std::min(minX, float(p2.x())); maxX = std::max(maxX, float(p2.x())); minY = std::min(minY, float(p2.y())); maxY = std::max(maxY, float(p2.y())); }
|
|
|
+ if (float(sx) >= minX && float(sx) <= maxX && float(sy) >= minY && float(sy) <= maxY) {
|
|
|
+ hit = true;
|
|
|
+ // Use distance to center for tie-break when overlapping multiple buildings
|
|
|
+ pickDist2 = d2;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!hit) {
|
|
|
+ // Fallback to a scaled circular radius if projection failed
|
|
|
+ float scaleXZ = std::max(std::max(t->scale.x, t->scale.z), 1.0f);
|
|
|
+ float rp = baseBuildingPickRadius * scaleXZ;
|
|
|
+ float r2 = rp * rp;
|
|
|
+ if (d2 <= r2) hit = true;
|
|
|
+ }
|
|
|
+ if (hit && pickDist2 < bestBuildingDist2) { bestBuildingDist2 = pickDist2; bestBuildingId = e->getId(); }
|
|
|
+ } else {
|
|
|
+ float r2 = baseUnitPickRadius * baseUnitPickRadius;
|
|
|
+ if (d2 <= r2 && d2 < bestUnitDist2) { bestUnitDist2 = d2; bestUnitId = e->getId(); }
|
|
|
+ }
|
|
|
}
|
|
|
- if (bestId) {
|
|
|
+ // Decide selection target by closest entity under cursor within radius
|
|
|
+ if (bestBuildingId && (!bestUnitId || bestBuildingDist2 <= bestUnitDist2)) {
|
|
|
+ if (!additive) m_selectionSystem->clearSelection();
|
|
|
+ m_selectionSystem->selectUnit(bestBuildingId);
|
|
|
+ syncSelectionFlags();
|
|
|
+ emit selectedUnitsChanged();
|
|
|
+ if (m_selectedUnitsModel) QMetaObject::invokeMethod(m_selectedUnitsModel, "refresh");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (bestUnitId) {
|
|
|
// If we clicked near a unit, this is a selection click. Optionally clear previous selection.
|
|
|
if (!additive) m_selectionSystem->clearSelection();
|
|
|
// Clicked near a unit: (re)select it
|
|
|
- m_selectionSystem->selectUnit(bestId);
|
|
|
+ m_selectionSystem->selectUnit(bestUnitId);
|
|
|
syncSelectionFlags();
|
|
|
emit selectedUnitsChanged();
|
|
|
if (m_selectedUnitsModel) QMetaObject::invokeMethod(m_selectedUnitsModel, "refresh");
|
|
|
@@ -173,6 +341,8 @@ void GameEngine::onAreaSelected(qreal x1, qreal y1, qreal x2, qreal y2, bool add
|
|
|
auto ents = m_world->getEntitiesWith<Engine::Core::TransformComponent>();
|
|
|
for (auto* e : ents) {
|
|
|
if (!e->hasComponent<Engine::Core::UnitComponent>()) continue;
|
|
|
+ // Exclude buildings from rectangle selection
|
|
|
+ if (e->hasComponent<Engine::Core::BuildingComponent>()) continue;
|
|
|
auto* u = e->getComponent<Engine::Core::UnitComponent>();
|
|
|
if (!u || u->ownerId != m_localOwnerId) continue; // only area-select friendlies
|
|
|
auto* t = e->getComponent<Engine::Core::TransformComponent>();
|
|
|
@@ -225,6 +395,23 @@ void GameEngine::initialize() {
|
|
|
} else {
|
|
|
setupFallbackTestUnit();
|
|
|
}
|
|
|
+ // Spawn a starting barracks for the local player near origin if none present
|
|
|
+ bool hasBarracks = false;
|
|
|
+ for (auto* e : m_world->getEntitiesWith<Engine::Core::UnitComponent>()) {
|
|
|
+ if (auto* u = e->getComponent<Engine::Core::UnitComponent>()) {
|
|
|
+ if (u->unitType == "barracks" && u->ownerId == m_localOwnerId) { hasBarracks = true; break; }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!hasBarracks) {
|
|
|
+ auto reg2 = Game::Map::MapTransformer::getFactoryRegistry();
|
|
|
+ if (reg2) {
|
|
|
+ Game::Units::SpawnParams sp;
|
|
|
+ sp.position = QVector3D(-4.0f, 0.0f, -3.0f);
|
|
|
+ sp.playerId = m_localOwnerId;
|
|
|
+ sp.unitType = "barracks";
|
|
|
+ reg2->create("barracks", *m_world, sp);
|
|
|
+ }
|
|
|
+ }
|
|
|
} else {
|
|
|
qWarning() << "Map load failed:" << err << "- using fallback unit";
|
|
|
Game::Map::Environment::applyDefault(*m_renderer, *m_camera);
|
|
|
@@ -244,6 +431,8 @@ void GameEngine::update(float dt) {
|
|
|
dt *= m_timeScale;
|
|
|
}
|
|
|
if (m_world) m_world->update(dt);
|
|
|
+ // Decay hover grace window
|
|
|
+ if (m_hoverGraceTicks > 0) --m_hoverGraceTicks;
|
|
|
// Prune selection of dead units and keep flags in sync
|
|
|
syncSelectionFlags();
|
|
|
// Update camera follow behavior after world update so positions are fresh
|
|
|
@@ -283,6 +472,8 @@ void GameEngine::render(int pixelWidth, int pixelHeight) {
|
|
|
m_camera->setPerspective(m_camera->getFOV(), aspect, m_camera->getNear(), m_camera->getFar());
|
|
|
}
|
|
|
m_renderer->beginFrame();
|
|
|
+ // Provide hovered id for subtle outline
|
|
|
+ if (m_renderer) m_renderer->setHoveredBuildingId(m_hoveredBuildingId);
|
|
|
m_renderer->renderWorld(m_world.get());
|
|
|
// Render arrows
|
|
|
if (m_arrowSystem) {
|
|
|
@@ -335,8 +526,10 @@ void GameEngine::setupFallbackTestUnit() {
|
|
|
|
|
|
bool GameEngine::screenToGround(const QPointF& screenPt, QVector3D& outWorld) {
|
|
|
if (!m_window || !m_camera) return false;
|
|
|
- return m_camera->screenToGround(float(screenPt.x()), float(screenPt.y()),
|
|
|
- float(m_window->width()), float(m_window->height()), outWorld);
|
|
|
+ // Prefer the active GL viewport size; fall back to window size if not set yet
|
|
|
+ float w = (m_viewW > 0 ? float(m_viewW) : float(m_window->width()));
|
|
|
+ float h = (m_viewH > 0 ? float(m_viewH) : float(m_window->height()));
|
|
|
+ return m_camera->screenToGround(float(screenPt.x()), float(screenPt.y()), w, h, outWorld);
|
|
|
}
|
|
|
|
|
|
bool GameEngine::worldToScreen(const QVector3D& world, QPointF& outScreen) const {
|
|
|
@@ -453,3 +646,97 @@ void GameEngine::cameraSetFollowLerp(float alpha) {
|
|
|
}
|
|
|
|
|
|
QObject* GameEngine::selectedUnitsModel() { return m_selectedUnitsModel; }
|
|
|
+
|
|
|
+bool GameEngine::hasSelectedType(const QString& type) const {
|
|
|
+ if (!m_selectionSystem || !m_world) return false;
|
|
|
+ const auto& sel = m_selectionSystem->getSelectedUnits();
|
|
|
+ for (auto id : sel) {
|
|
|
+ if (auto* e = m_world->getEntity(id)) {
|
|
|
+ if (auto* u = e->getComponent<Engine::Core::UnitComponent>()) {
|
|
|
+ if (QString::fromStdString(u->unitType) == type) return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+void GameEngine::recruitNearSelected(const QString& unitType) {
|
|
|
+ ensureInitialized();
|
|
|
+ if (!m_world) return;
|
|
|
+ if (!m_selectionSystem) return;
|
|
|
+ const auto& sel = m_selectionSystem->getSelectedUnits();
|
|
|
+ if (sel.empty()) return;
|
|
|
+ // Find first selected barracks of local player
|
|
|
+ for (auto id : sel) {
|
|
|
+ if (auto* e = m_world->getEntity(id)) {
|
|
|
+ auto* u = e->getComponent<Engine::Core::UnitComponent>();
|
|
|
+ auto* t = e->getComponent<Engine::Core::TransformComponent>();
|
|
|
+ auto* p = e->getComponent<Engine::Core::ProductionComponent>();
|
|
|
+ if (!u || !t) continue;
|
|
|
+ if (u->unitType == "barracks" && u->ownerId == m_localOwnerId) {
|
|
|
+ if (!p) { p = e->addComponent<Engine::Core::ProductionComponent>(); }
|
|
|
+ if (!p) return;
|
|
|
+ if (p->producedCount >= p->maxUnits) return; // cap reached
|
|
|
+ if (p->inProgress) return; // already building
|
|
|
+ // Start a new production order
|
|
|
+ p->productType = unitType.toStdString();
|
|
|
+ // Build time could vary by unit type in future; keep current
|
|
|
+ p->timeRemaining = p->buildTime;
|
|
|
+ p->inProgress = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+QVariantMap GameEngine::getSelectedProductionState() const {
|
|
|
+ QVariantMap m;
|
|
|
+ m["hasBarracks"] = false;
|
|
|
+ m["inProgress"] = false;
|
|
|
+ m["timeRemaining"] = 0.0;
|
|
|
+ m["buildTime"] = 0.0;
|
|
|
+ m["producedCount"] = 0;
|
|
|
+ m["maxUnits"] = 0;
|
|
|
+ if (!m_selectionSystem || !m_world) return m;
|
|
|
+ const auto& sel = m_selectionSystem->getSelectedUnits();
|
|
|
+ for (auto id : sel) {
|
|
|
+ if (auto* e = m_world->getEntity(id)) {
|
|
|
+ if (auto* u = e->getComponent<Engine::Core::UnitComponent>()) {
|
|
|
+ if (u->unitType == "barracks") {
|
|
|
+ m["hasBarracks"] = true;
|
|
|
+ if (auto* p = e->getComponent<Engine::Core::ProductionComponent>()) {
|
|
|
+ m["inProgress"] = p->inProgress;
|
|
|
+ m["timeRemaining"] = p->timeRemaining;
|
|
|
+ m["buildTime"] = p->buildTime;
|
|
|
+ m["producedCount"] = p->producedCount;
|
|
|
+ m["maxUnits"] = p->maxUnits;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return m;
|
|
|
+}
|
|
|
+
|
|
|
+void GameEngine::setRallyAtScreen(qreal sx, qreal sy) {
|
|
|
+ ensureInitialized();
|
|
|
+ if (!m_world || !m_selectionSystem) return;
|
|
|
+ QVector3D hit;
|
|
|
+ if (!screenToGround(QPointF(sx, sy), hit)) return;
|
|
|
+ const auto& sel = m_selectionSystem->getSelectedUnits();
|
|
|
+ for (auto id : sel) {
|
|
|
+ if (auto* e = m_world->getEntity(id)) {
|
|
|
+ if (auto* u = e->getComponent<Engine::Core::UnitComponent>()) {
|
|
|
+ if (u->unitType == "barracks") {
|
|
|
+ if (auto* p = e->getComponent<Engine::Core::ProductionComponent>()) {
|
|
|
+ p->rallyX = hit.x();
|
|
|
+ p->rallyZ = hit.z();
|
|
|
+ p->rallySet = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|