#include "actionneededmanager.h" #include "yamlparser.h" #include "unit.h" #include "city.h" #include #include #include ActionNeededManager::ActionNeededManager() : m_elapsedGameTime(0.0) { } bool ActionNeededManager::loadConfigFile(const QString &filePath) { YamlNode root = YamlParser::parseFile(filePath); if (root.children.isEmpty()) { qWarning() << "Failed to parse action needed YAML, using defaults:" << filePath; return false; } const YamlNode &node = root.hasChild("action_needed") ? root["action_needed"] : root; if (node.hasChild("enabled")) { m_config.enabled = node["enabled"].asBool(true); } if (node.hasChild("track_idle_units")) { m_config.trackIdleUnits = node["track_idle_units"].asBool(true); } if (node.hasChild("track_city_no_production")) { m_config.trackCityNoProduction = node["track_city_no_production"].asBool(true); } if (node.hasChild("track_city_free_population")) { m_config.trackCityFreePopulation = node["track_city_free_population"].asBool(true); } if (node.hasChild("track_no_research")) { m_config.trackNoResearch = node["track_no_research"].asBool(true); } if (node.hasChild("max_visible_items")) { m_config.maxVisibleItems = node["max_visible_items"].asInt(8); } if (node.hasChild("item_width")) { m_config.itemWidth = node["item_width"].asInt(155); } if (node.hasChild("item_height")) { m_config.itemHeight = node["item_height"].asInt(42); } if (node.hasChild("bottom_offset")) { m_config.bottomOffset = node["bottom_offset"].asInt(16); } if (node.hasChild("item_spacing")) { m_config.itemSpacing = node["item_spacing"].asInt(8); } if (node.hasChild("border_radius")) { m_config.borderRadius = node["border_radius"].asInt(6); } if (node.hasChild("pulse_animation")) { m_config.pulseAnimation = node["pulse_animation"].asBool(true); } if (node.hasChild("pulse_speed")) { m_config.pulseSpeed = node["pulse_speed"].asFloat(3.0f); } if (node.hasChild("unit_idle_color")) { QString hex = node["unit_idle_color"].asString("#3CA0FF"); m_config.unitIdleColor = QColor(hex); } if (node.hasChild("city_no_prod_color")) { QString hex = node["city_no_prod_color"].asString("#FFA028"); m_config.cityNoProdColor = QColor(hex); } if (node.hasChild("city_free_pop_color")) { QString hex = node["city_free_pop_color"].asString("#46DC82"); m_config.cityFreePopColor = QColor(hex); } if (node.hasChild("no_research_color")) { QString hex = node["no_research_color"].asString("#38C8FF"); m_config.noResearchColor = QColor(hex); } if (node.hasChild("card_bg_color")) { QString hex = node["card_bg_color"].asString("#121822"); m_config.cardBgColor = QColor(hex); } if (node.hasChild("card_bg_alpha")) { m_config.cardBgAlpha = node["card_bg_alpha"].asInt(235); } m_config.cardBgColor.setAlpha(m_config.cardBgAlpha); qDebug() << "ActionNeededManager config loaded from" << filePath; return true; } void ActionNeededManager::update(float deltaTime, int activePlayerId, const std::vector> &units, const std::vector &cities, bool needsResearch) { if (!m_config.enabled) return; m_elapsedGameTime += static_cast(deltaTime); std::set currentConditionKeys; // 0. Player has no active research if (m_config.trackNoResearch && needsResearch) { QString key = QString("%1_%2").arg(static_cast(ActionNeededType::NoResearch)).arg(activePlayerId); currentConditionKeys.insert(key); auto it = std::find_if(m_items.begin(), m_items.end(), [&](const ActionItem &item) { return item.type == ActionNeededType::NoResearch && item.entityId == activePlayerId; }); if (it == m_items.end()) { ActionItem newItem; newItem.type = ActionNeededType::NoResearch; newItem.entityId = activePlayerId; newItem.firstDetectedTime = m_elapsedGameTime; newItem.dismissed = false; m_items.push_back(newItem); } } // 1. Idle Units waiting for action if (m_config.trackIdleUnits) { for (const auto &u : units) { if (!u || u->isDead() || u->playerId != activePlayerId) continue; bool isIdle = (u->state == UnitState::Idle) && (u->path.empty() || u->pathIndex >= u->path.size()) && !u->hasQueuedAction() && !u->hasRoadRoute() && !u->isBuilding(); if (isIdle) { QString key = QString("%1_%2").arg(static_cast(ActionNeededType::UnitIdle)).arg(u->id); currentConditionKeys.insert(key); auto it = std::find_if(m_items.begin(), m_items.end(), [&](const ActionItem &item) { return item.type == ActionNeededType::UnitIdle && item.entityId == u->id; }); if (it == m_items.end()) { ActionItem newItem; newItem.type = ActionNeededType::UnitIdle; newItem.entityId = u->id; newItem.firstDetectedTime = m_elapsedGameTime; newItem.dismissed = false; m_items.push_back(newItem); } } } } // 2. Cities waiting for action for (const auto &c : cities) { if (c.playerId != activePlayerId) continue; // a) City is not building anything if (m_config.trackCityNoProduction) { bool noProd = !c.hasActiveProject(); if (noProd) { QString key = QString("%1_%2").arg(static_cast(ActionNeededType::CityNoProduction)).arg(c.id); currentConditionKeys.insert(key); auto it = std::find_if(m_items.begin(), m_items.end(), [&](const ActionItem &item) { return item.type == ActionNeededType::CityNoProduction && item.entityId == c.id; }); if (it == m_items.end()) { ActionItem newItem; newItem.type = ActionNeededType::CityNoProduction; newItem.entityId = c.id; newItem.firstDetectedTime = m_elapsedGameTime; newItem.dismissed = false; m_items.push_back(newItem); } } } // b) City has free / unassigned population if (m_config.trackCityFreePopulation) { int freePop = c.getAvailablePop(); if (freePop > 0) { QString key = QString("%1_%2").arg(static_cast(ActionNeededType::CityFreePopulation)).arg(c.id); currentConditionKeys.insert(key); auto it = std::find_if(m_items.begin(), m_items.end(), [&](const ActionItem &item) { return item.type == ActionNeededType::CityFreePopulation && item.entityId == c.id; }); if (it == m_items.end()) { ActionItem newItem; newItem.type = ActionNeededType::CityFreePopulation; newItem.entityId = c.id; newItem.firstDetectedTime = m_elapsedGameTime; newItem.auxValue = freePop; newItem.dismissed = false; m_items.push_back(newItem); } else { // If free population increased since it was dismissed, reactivate it! if (freePop > it->dismissedAuxValue) { it->dismissed = false; } it->auxValue = freePop; } } } } // 3. Problem resolved -> remove from queue! m_items.erase(std::remove_if(m_items.begin(), m_items.end(), [&](const ActionItem &item) { return currentConditionKeys.find(item.getKey()) == currentConditionKeys.end(); }), m_items.end()); } std::vector ActionNeededManager::getVisibleItems() const { std::vector visible; visible.reserve(m_items.size()); for (const auto &item : m_items) { if (!item.dismissed) { visible.push_back(item); } } // Sort in order of oldest first (firstDetectedTime ASC) std::sort(visible.begin(), visible.end(), [](const ActionItem &a, const ActionItem &b) { if (a.firstDetectedTime != b.firstDetectedTime) return a.firstDetectedTime < b.firstDetectedTime; return a.entityId < b.entityId; }); return visible; } int ActionNeededManager::getVisibleCount() const { int count = 0; for (const auto &item : m_items) { if (!item.dismissed) { count++; } } return count; } void ActionNeededManager::dismissItem(ActionNeededType type, int entityId) { for (auto &item : m_items) { if (item.type == type && item.entityId == entityId) { item.dismissed = true; item.dismissedAuxValue = item.auxValue; break; } } } void ActionNeededManager::resetDismissals() { for (auto &item : m_items) { item.dismissed = false; item.dismissedAuxValue = 0; } } void ActionNeededManager::reset() { m_items.clear(); m_elapsedGameTime = 0.0; }