Compare commits

...
7 Commits
58 changed files with 3196 additions and 798 deletions
+29 -1
View File
@@ -33,6 +33,9 @@ bool ActionNeededManager::loadConfigFile(const QString &filePath)
if (node.hasChild("track_city_free_population")) { if (node.hasChild("track_city_free_population")) {
m_config.trackCityFreePopulation = node["track_city_free_population"].asBool(true); 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")) { if (node.hasChild("max_visible_items")) {
m_config.maxVisibleItems = node["max_visible_items"].asInt(8); m_config.maxVisibleItems = node["max_visible_items"].asInt(8);
} }
@@ -70,6 +73,10 @@ bool ActionNeededManager::loadConfigFile(const QString &filePath)
QString hex = node["city_free_pop_color"].asString("#46DC82"); QString hex = node["city_free_pop_color"].asString("#46DC82");
m_config.cityFreePopColor = QColor(hex); 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")) { if (node.hasChild("card_bg_color")) {
QString hex = node["card_bg_color"].asString("#121822"); QString hex = node["card_bg_color"].asString("#121822");
m_config.cardBgColor = QColor(hex); m_config.cardBgColor = QColor(hex);
@@ -85,7 +92,8 @@ bool ActionNeededManager::loadConfigFile(const QString &filePath)
void ActionNeededManager::update(float deltaTime, int activePlayerId, void ActionNeededManager::update(float deltaTime, int activePlayerId,
const std::vector<std::shared_ptr<Unit>> &units, const std::vector<std::shared_ptr<Unit>> &units,
const std::vector<City> &cities) const std::vector<City> &cities,
bool needsResearch)
{ {
if (!m_config.enabled) return; if (!m_config.enabled) return;
@@ -93,6 +101,25 @@ void ActionNeededManager::update(float deltaTime, int activePlayerId,
std::set<QString> currentConditionKeys; std::set<QString> currentConditionKeys;
// 0. Player has no active research
if (m_config.trackNoResearch && needsResearch) {
QString key = QString("%1_%2").arg(static_cast<int>(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 // 1. Idle Units waiting for action
if (m_config.trackIdleUnits) { if (m_config.trackIdleUnits) {
for (const auto &u : units) { for (const auto &u : units) {
@@ -100,6 +127,7 @@ void ActionNeededManager::update(float deltaTime, int activePlayerId,
bool isIdle = (u->state == UnitState::Idle) bool isIdle = (u->state == UnitState::Idle)
&& (u->path.empty() || u->pathIndex >= u->path.size()) && (u->path.empty() || u->pathIndex >= u->path.size())
&& !u->currentAction.isValid()
&& !u->hasQueuedAction() && !u->hasQueuedAction()
&& !u->hasRoadRoute() && !u->hasRoadRoute()
&& !u->isBuilding(); && !u->isBuilding();
+6 -2
View File
@@ -12,7 +12,8 @@ class City;
enum class ActionNeededType { enum class ActionNeededType {
UnitIdle = 0, UnitIdle = 0,
CityNoProduction = 1, CityNoProduction = 1,
CityFreePopulation = 2 CityFreePopulation = 2,
NoResearch = 3
}; };
struct ActionItem { struct ActionItem {
@@ -33,6 +34,7 @@ struct ActionNeededConfig {
bool trackIdleUnits = true; bool trackIdleUnits = true;
bool trackCityNoProduction = true; bool trackCityNoProduction = true;
bool trackCityFreePopulation = true; bool trackCityFreePopulation = true;
bool trackNoResearch = true;
int maxVisibleItems = 8; int maxVisibleItems = 8;
int itemWidth = 155; int itemWidth = 155;
@@ -46,6 +48,7 @@ struct ActionNeededConfig {
QColor unitIdleColor = QColor(60, 160, 255); QColor unitIdleColor = QColor(60, 160, 255);
QColor cityNoProdColor = QColor(255, 160, 40); QColor cityNoProdColor = QColor(255, 160, 40);
QColor cityFreePopColor = QColor(70, 220, 130); QColor cityFreePopColor = QColor(70, 220, 130);
QColor noResearchColor = QColor(56, 200, 255);
QColor cardBgColor = QColor(18, 24, 34); QColor cardBgColor = QColor(18, 24, 34);
int cardBgAlpha = 235; int cardBgAlpha = 235;
}; };
@@ -59,7 +62,8 @@ public:
void update(float deltaTime, int activePlayerId, void update(float deltaTime, int activePlayerId,
const std::vector<std::shared_ptr<Unit>> &units, const std::vector<std::shared_ptr<Unit>> &units,
const std::vector<City> &cities); const std::vector<City> &cities,
bool needsResearch = false);
std::vector<ActionItem> getVisibleItems() const; std::vector<ActionItem> getVisibleItems() const;
int getVisibleCount() const; int getVisibleCount() const;
+2
View File
@@ -53,6 +53,8 @@ bool BuildingConfigManager::loadConfigFile(const QString &filePath)
bCfg.scienceBonus = it.value().hasChild("science_bonus") ? it.value()["science_bonus"].asInt(0) : 0; bCfg.scienceBonus = it.value().hasChild("science_bonus") ? it.value()["science_bonus"].asInt(0) : 0;
bCfg.faithBonus = it.value().hasChild("faith_bonus") ? it.value()["faith_bonus"].asInt(0) : 0; bCfg.faithBonus = it.value().hasChild("faith_bonus") ? it.value()["faith_bonus"].asInt(0) : 0;
bCfg.goldBonus = it.value().hasChild("gold_bonus") ? it.value()["gold_bonus"].asInt(0) : 0; bCfg.goldBonus = it.value().hasChild("gold_bonus") ? it.value()["gold_bonus"].asInt(0) : 0;
bCfg.requiredTech = it.value().hasChild("required_tech") ? it.value()["required_tech"].asString("") : "";
bCfg.obsoleteTech = it.value().hasChild("obsolete_tech") ? it.value()["obsolete_tech"].asString("") : "";
bCfg.iconPath = it.value().hasChild("icon") ? it.value()["icon"].asString() : ""; bCfg.iconPath = it.value().hasChild("icon") ? it.value()["icon"].asString() : "";
bCfg.description = it.value().hasChild("description") ? it.value()["description"].asString() : ""; bCfg.description = it.value().hasChild("description") ? it.value()["description"].asString() : "";
m_buildings[bCfg.id] = bCfg; m_buildings[bCfg.id] = bCfg;
+3
View File
@@ -17,6 +17,8 @@ struct BuildingConfig {
int scienceBonus; int scienceBonus;
int faithBonus; int faithBonus;
int goldBonus; int goldBonus;
QString requiredTech;
QString obsoleteTech;
QString iconPath; QString iconPath;
QString description; QString description;
@@ -24,6 +26,7 @@ struct BuildingConfig {
: id(""), name(""), district("center"), laborCost(1000), : id(""), name(""), district("center"), laborCost(1000),
foodModifier(0.0f), foodBonus(0), laborBonus(0), cultureBonus(0), foodModifier(0.0f), foodBonus(0), laborBonus(0), cultureBonus(0),
scienceBonus(0), faithBonus(0), goldBonus(0), scienceBonus(0), faithBonus(0), goldBonus(0),
requiredTech(""), obsoleteTech(""),
iconPath(""), description("") {} iconPath(""), description("") {}
}; };
BIN
View File
Binary file not shown.
+2
View File
@@ -4,6 +4,7 @@ action_needed:
track_idle_units: true track_idle_units: true
track_city_no_production: true track_city_no_production: true
track_city_free_population: true track_city_free_population: true
track_no_research: true
max_visible_items: 8 max_visible_items: 8
# Visual & Layout parameters # Visual & Layout parameters
@@ -19,5 +20,6 @@ action_needed:
unit_idle_color: "#3CA0FF" unit_idle_color: "#3CA0FF"
city_no_prod_color: "#FFA028" city_no_prod_color: "#FFA028"
city_free_pop_color: "#46DC82" city_free_pop_color: "#46DC82"
no_research_color: "#38C8FF"
card_bg_color: "#121822" card_bg_color: "#121822"
card_bg_alpha: 235 card_bg_alpha: 235
+131 -8
View File
@@ -1,19 +1,55 @@
# City Buildings & Extensions configuration # City Buildings & Extensions configuration
# All gameplay values are configurable via YAML # All gameplay values, required technologies, and obsolescence technologies are configurable via YAML
districts: districts:
center: center:
id: "center" id: "center"
name: "City Center" name: "City Center"
max_buildings: 10 max_buildings: 12
description: "The urban core and administrative center of the city." description: "The urban core and administrative center of the city."
buildings: buildings:
monument:
id: "monument"
name: "Monument"
district: "center"
labor_cost: 800
required_tech: ""
obsolete_tech: ""
food_modifier: 0.0
food_bonus: 0
labor_bonus: 0
culture_bonus: 250
science_bonus: 0
faith_bonus: 0
gold_bonus: 0
icon: "images/improvements/monument.png"
description: "+250 Culture per tick"
palisade:
id: "palisade"
name: "Wooden Palisade"
district: "center"
labor_cost: 700
required_tech: ""
obsolete_tech: "masonry"
food_modifier: 0.0
food_bonus: 0
labor_bonus: 100
culture_bonus: 100
science_bonus: 0
faith_bonus: 0
gold_bonus: 0
icon: "images/improvements/monument.png"
description: "+100 Production, +100 Culture/tick (Obsolete with Masonry)"
granary: granary:
id: "granary" id: "granary"
name: "Granary" name: "Granary"
district: "center" district: "center"
labor_cost: 1000 labor_cost: 1000
required_tech: "pottery"
obsolete_tech: ""
food_modifier: 0.50 food_modifier: 0.50
food_bonus: 0 food_bonus: 0
labor_bonus: 0 labor_bonus: 0
@@ -24,17 +60,104 @@ buildings:
icon: "images/improvements/granary.png" icon: "images/improvements/granary.png"
description: "+50% Food production in city" description: "+50% Food production in city"
monument: library:
id: "monument" id: "library"
name: "Monument" name: "Library"
district: "center" district: "center"
labor_cost: 800 labor_cost: 1200
required_tech: "writing"
obsolete_tech: ""
food_modifier: 0.0 food_modifier: 0.0
food_bonus: 0 food_bonus: 0
labor_bonus: 0 labor_bonus: 0
culture_bonus: 250 culture_bonus: 0
science_bonus: 350
faith_bonus: 0
gold_bonus: 0
icon: "images/resources/kennis.png"
description: "+350 Science per tick"
walls:
id: "walls"
name: "Stone Walls"
district: "center"
labor_cost: 1500
required_tech: "masonry"
obsolete_tech: "gunpowder"
food_modifier: 0.0
food_bonus: 0
labor_bonus: 200
culture_bonus: 200
science_bonus: 0 science_bonus: 0
faith_bonus: 0 faith_bonus: 0
gold_bonus: 0 gold_bonus: 0
icon: "images/improvements/monument.png" icon: "images/improvements/monument.png"
description: "+250 Culture per tick" description: "+200 Production, +200 Culture/tick (Obsolete with Gunpowder)"
water_mill:
id: "water_mill"
name: "Water Mill"
district: "center"
labor_cost: 1600
required_tech: "wheel"
obsolete_tech: ""
food_modifier: 0.0
food_bonus: 200
labor_bonus: 250
culture_bonus: 0
science_bonus: 0
faith_bonus: 0
gold_bonus: 0
icon: "images/improvements/granary.png"
description: "+200 Food, +250 Production per tick"
market:
id: "market"
name: "Market"
district: "center"
labor_cost: 1800
required_tech: "currency"
obsolete_tech: ""
food_modifier: 0.0
food_bonus: 0
labor_bonus: 0
culture_bonus: 0
science_bonus: 0
faith_bonus: 0
gold_bonus: 400
icon: "images/resources/goud.png"
description: "+400 Gold per tick"
university:
id: "university"
name: "University"
district: "center"
labor_cost: 2800
required_tech: "education"
obsolete_tech: ""
food_modifier: 0.0
food_bonus: 0
labor_bonus: 0
culture_bonus: 100
science_bonus: 700
faith_bonus: 0
gold_bonus: 0
icon: "images/resources/kennis.png"
description: "+700 Science, +100 Culture per tick"
workshop:
id: "workshop"
name: "Workshop"
district: "center"
labor_cost: 2600
required_tech: "machinery"
obsolete_tech: ""
food_modifier: 0.0
food_bonus: 0
labor_bonus: 500
culture_bonus: 0
science_bonus: 0
faith_bonus: 0
gold_bonus: 0
icon: "images/resources/arbeid.png"
description: "+500 Production per tick"
+1 -1
View File
@@ -2,7 +2,7 @@ movement_tick_duration: 30.0
# Action queue configuration # Action queue configuration
action_queue: action_queue:
max_size: 5 max_size: 15
auto_queue_move_when_busy: true auto_queue_move_when_busy: true
allow_action_queueing: true allow_action_queueing: true
+20 -11
View File
@@ -1,34 +1,43 @@
# Road System Configuration # Road System Configuration
default_road_type: "dirt" default_road_type: "dirt"
build_duration_ticks: 2 build_duration_ticks: 2
meander_amplitude: 0.045 meander_amplitude: 0.025
meander_subdivisions: 5 meander_subdivisions: 6
types: types:
dirt: dirt:
id: "dirt" id: "dirt"
name: "Dirt Road" name: "Dirt Road"
speed_multiplier: 1.30 # +30% movement speed speed_multiplier: 1.30 # +30% movement speed
color: "#c49a62" color: "#d8b480"
border_color: "#6b4b23" border_color: "#52381c"
width: 3.5 width: 3.5
world_width: 0.11 world_width: 0.056
border_width: 0.012
texture_scale: 4.5
opacity: 0.96
cost: 5 cost: 5
stone: stone:
id: "stone" id: "stone"
name: "Stone Road" name: "Stone Road"
speed_multiplier: 1.60 # +60% movement speed speed_multiplier: 1.60 # +60% movement speed
color: "#9aa1a8" color: "#c2beb6"
border_color: "#464b50" border_color: "#46484c"
width: 4.0 width: 4.0
world_width: 0.13 world_width: 0.064
border_width: 0.012
texture_scale: 5.5
opacity: 0.98
cost: 15 cost: 15
rail: rail:
id: "rail" id: "rail"
name: "Railroad" name: "Railroad"
speed_multiplier: 2.50 # +150% movement speed speed_multiplier: 2.50 # +150% movement speed
color: "#3a3c40" color: "#4a4e54"
border_color: "#181a1c" border_color: "#22252a"
width: 4.5 width: 4.5
world_width: 0.15 world_width: 0.070
border_width: 0.014
texture_scale: 6.0
opacity: 0.98
cost: 30 cost: 30
+76 -22
View File
@@ -49,25 +49,30 @@ ui:
queue_cleared: "Queued actions cancelled" queue_cleared: "Queued actions cancelled"
queue_full: "Action queue full!" queue_full: "Action queue full!"
action_queued: "Action queued: %1" action_queued: "Action queued: %1"
action_queued_count: "%1 added to queue (#%2)"
action_scheduled_after_current: "%1 scheduled after current action"
action_cancelled: "%1 cancelled" action_cancelled: "%1 cancelled"
farm_queued: "Build Farm queued on arrival" farm_queued: "Build Farm queued"
farm_started: "Constructing Farm..." farm_started: "Constructing Farm..."
farm_completed: "Farm completed!" farm_completed: "Farm completed!"
farm_completed_consumed: "Farm completed! (Builder consumed)" farm_completed_consumed: "Farm completed! (Builder consumed)"
farm_completed_charges: "Farm completed! (%1 %2 remaining)" farm_completed_charges: "Farm completed! (%1 %2 remaining)"
road_queued: "Build Road queued on arrival" moving_to_build_farm: "Moving to build Farm at (%1, %2)..."
moving_to_build_city: "Moving to found City at (%1, %2)..."
moving_to_attack: "Moving into attack range..."
road_queued: "Build Road queued"
road_started: "Constructing %1... (%2 ticks)" road_started: "Constructing %1... (%2 ticks)"
road_completed: "%1 completed!" road_completed: "%1 completed!"
road_already_here: "Tile already has a road!" road_already_here: "Tile already has a road!"
cannot_build_road_water: "Roads cannot be built on water!" cannot_build_road_water: "Roads cannot be built on water!"
city_queued: "Found City queued on arrival" city_queued: "Found City queued"
city_founded: "%1 founded!" city_founded: "%1 founded!"
already_building: "Already busy building!" already_building: "Already busy building!"
busy: "Unit is busy!" busy: "Unit is busy!"
cannot_act_busy: "Unit cannot perform action while busy!" cannot_act_busy: "Unit cannot perform action while busy!"
tile_occupied: "Tile already has an improvement!" tile_occupied: "Tile already has an improvement!"
cannot_build_here: "Cannot build here! Allowed on: %1" cannot_build_here: "Cannot build here! Allowed on: %1"
cannot_build_destination: "Cannot build on destination terrain! Allowed on: %1" cannot_build_destination: "Cannot build on target terrain! Allowed on: %1"
no_build_charges: "No build charges remaining!" no_build_charges: "No build charges remaining!"
charge_singular: "charge" charge_singular: "charge"
charge_plural: "charges" charge_plural: "charges"
@@ -76,6 +81,7 @@ ui:
no_path: "No reachable path!" no_path: "No reachable path!"
tile_purchased: "Tile purchased! (-%1 Culture)" tile_purchased: "Tile purchased! (-%1 Culture)"
out_of_range: "Out of range!" out_of_range: "Out of range!"
no_enemy_on_tile: "Select a tile with an enemy unit!"
defeated: "Defeated!" defeated: "Defeated!"
city_already_here: "A city already exists here!" city_already_here: "A city already exists here!"
unsuitable_terrain: "Unsuitable terrain!" unsuitable_terrain: "Unsuitable terrain!"
@@ -86,35 +92,45 @@ ui:
combat_format: "Attack: %1 | Range: %2 hex" combat_format: "Attack: %1 | Range: %2 hex"
building_pct: "Building... %1%" building_pct: "Building... %1%"
charges_format: "Charges: %1" charges_format: "Charges: %1"
status_queued: "Queued: %1" status_queued: "Queue (%1): %2"
btn_move: "Move [M]" btn_move: "Move [M]"
btn_attack: "Attack [A]" btn_attack: "Attack [A]"
btn_build_city: "Settle [B]" btn_build_city: "Settle [B]"
btn_build_farm: "Farm [F]" btn_build_farm: "Farm [F]"
btn_farm_queued: "Farm (Queued)" btn_farm_queued: "Farm [F]"
btn_farm_building: "Building %1%" btn_farm_building: "Building %1%"
btn_build_road: "Road [R]" btn_build_road: "Road [R]"
btn_road_queued: "Road (Queued)" btn_road_queued: "Road [R]"
btn_road_building: "Building %1%" btn_road_building: "Building %1%"
tip_move: "Move (M)" tip_move: "Move (M) - Click target tile (Shift+Click to queue)"
tip_attack: "Attack (A) [Range: %1]" tip_attack: "Attack (A) [Range: %1] - Click enemy (Shift+Click to queue)"
tip_build_city: "Found City (B)" tip_build_city: "Found City (B) - Click target tile (Shift+Click to queue)"
tip_build_farm: "Build Farm (F)" tip_build_farm: "Build Farm (F) - Click target tile (Shift+Click to queue)"
tip_farm_building: "Busy building... %1%" tip_farm_building: "Busy building... %1% (Click to plan next target)"
tip_farm_queued: "Build Farm (Queued on arrival - click to cancel)" tip_farm_queued: "Build Farm (Queued - Click to plan target)"
tip_build_road: "Build Road (R) - Click target tile to build route" tip_build_road: "Build Road (R) - Click target tile (Shift+Click to queue)"
tip_road_building: "Busy building road... %1%" tip_road_building: "Busy building road... %1% (Click to plan next target)"
tip_road_queued: "Build Road (Queued on arrival - click to cancel)" tip_road_queued: "Build Road (Queued - Click to plan target)"
road_started: "Constructing %1... (%2 ticks)"
road_completed: "%1 completed!"
road_queued: "Build Road queued on arrival"
road_already_here: "Tile already has a road!"
cannot_build_road_water: "Roads cannot be built on water!"
road_route_started: "Building road route to (%1, %2)..." road_route_started: "Building road route to (%1, %2)..."
road_route_completed: "Road route completed!" road_route_completed: "Road route completed!"
road_route_cancelled: "Road route cancelled" road_route_cancelled: "Road route cancelled"
status_road_route: "Road Route: %1/%2" status_road_route: "Road Route: %1/%2"
btn_road_route: "Route (%1/%2)" btn_road_route: "Route (%1/%2)"
preview_move: "Move (%1 hexes)"
preview_farm: "Build Farm"
preview_farm_move: "Build Farm (%1 hexes)"
preview_city: "Found City"
preview_city_move: "Found City (%1 hexes)"
preview_road: "Road Route (%1 hexes)"
preview_road_here: "Build Road"
preview_attack: "Attack"
preview_attack_move: "Attack (%1 hexes)"
preview_queue_prefix: "+Queue: %1"
queue_badge_move: "#%1 Move"
queue_badge_farm: "#%1 Farm"
queue_badge_city: "#%1 Settle"
queue_badge_road: "#%1 Road"
queue_badge_attack: "#%1 Attack"
improvement: improvement:
farm: "Farm" farm: "Farm"
@@ -128,7 +144,7 @@ ui:
management_title: "%1 - CITY MANAGEMENT" management_title: "%1 - CITY MANAGEMENT"
close_btn: "[X] Close" close_btn: "[X] Close"
local_info: "Local: Population: %L1 (%2 Pop: %3 working, %4 free) | Labor: %L5" local_info: "Local: Population: %L1 (%2 Pop: %3 working, %4 free) | Labor: %L5"
national_info: "National: Culture: %L1 | Science: %L2 | Gold: %L3 | Faith: %L4" national_info: "National: Culture: %L1 | Science: +%L2/tick | Gold: %L3 | Faith: %L4"
help_text: "Click a slot to assign/free Pop | Hold a slot to choose resource | Buy tile with Culture." help_text: "Click a slot to assign/free Pop | Hold a slot to choose resource | Buy tile with Culture."
current_production: "CURRENT PRODUCTION" current_production: "CURRENT PRODUCTION"
production_panel_title: "CITY PRODUCTION" production_panel_title: "CITY PRODUCTION"
@@ -160,6 +176,8 @@ ui:
queued_item_removed: "%1: Removed %2 from queue" queued_item_removed: "%1: Removed %2 from queue"
already_built: "%1 already built in this city!" already_built: "%1 already built in this city!"
already_queued: "%1 is already in the production queue!" already_queued: "%1 is already in the production queue!"
requires_tech: "%1 requires technology: %2!"
obsolete_item: "%1 is obsolete and can no longer be built!"
growth_float: "+%1 Inhabitants | +%2 Culture" growth_float: "+%1 Inhabitants | +%2 Culture"
not_enough_culture: "Not enough Culture! (%1 needed)" not_enough_culture: "Not enough Culture! (%1 needed)"
worker_freed: "Pop worker freed" worker_freed: "Pop worker freed"
@@ -187,6 +205,42 @@ ui:
unit_idle_desc: "Needs orders" unit_idle_desc: "Needs orders"
city_no_production_desc: "No production" city_no_production_desc: "No production"
city_free_pop_desc: "+%1 Free Pop" city_free_pop_desc: "+%1 Free Pop"
no_research_title: "RESEARCH"
no_research_desc: "Choose technology"
dismiss_tip: "Right-click to dismiss" dismiss_tip: "Right-click to dismiss"
more_items: "+%1" more_items: "+%1"
tech_tree:
title: "TECHNOLOGY TREE"
close_btn: "[X] Close"
btn_open: "Tech [T]"
no_research: "No Research"
choose_research: "Choose Tech [T]"
science_rate: "+%L1/tick"
active_research: "ACTIVE RESEARCH: %1"
no_active_research: "No active research - Click any technology node below to start researching"
queue_label: "QUEUE (%1):"
clear_queue_btn: "Clear Queue"
status_researched: "RESEARCHED"
status_researching: "ACTIVE #1"
status_queued: "QUEUED #%1"
status_available: "AVAILABLE"
status_locked: "LOCKED"
cost_format: "%L1 Science (~%2 %3)"
progress_format: "%L1 / %L2 (%3%)"
remaining_format: "%L1 left (~%2 %3)"
tick_singular: "tick"
tick_plural: "ticks"
prereqs_label: "Req: %1"
prereqs_none: "Base Technology"
unlocks_label: "Unlocks: %1"
obsoletes_label: "Obsoletes: %1"
help_text: "Left-click a tech to research (prerequisites auto-queued by least remaining work) | Shift+Click to append | Right-click to remove | Drag/Scroll to pan"
tech_completed_float: "Technology Researched: %1!"
tech_started_float: "Researching: %1"
tech_queued_float: "Research Queue Updated (%1 techs, target: %2)"
tech_already_researched: "%1 has already been researched!"
tech_cancelled_float: "Research queue cleared"
tech_removed_float: "Removed %1 from research queue"
+202
View File
@@ -0,0 +1,202 @@
# Technology Tree Configuration
# All gameplay values, science costs, layout coordinates, and prerequisite chains are configurable via YAML.
technologies:
pottery:
id: "pottery"
name: "Pottery"
science_cost: 1000
column: 0
row: 0
icon: "images/improvements/granary.png"
description: "Ceramic vessels for storing grain and preserving food supplies."
animal_husbandry:
id: "animal_husbandry"
name: "Animal Husbandry"
science_cost: 1000
column: 0
row: 1
icon: "images/resources/voedsel.png"
description: "Domestication and breeding of livestock for food and labor."
mining:
id: "mining"
name: "Mining"
science_cost: 1200
column: 0
row: 2
icon: "images/resources/arbeid.png"
description: "Extraction of stone and metal ores from the earth."
sailing:
id: "sailing"
name: "Sailing"
science_cost: 1500
column: 1
row: 0
icon: "images/units/galley.png"
description: "Harnessing the wind to navigate coastal waters."
prerequisites:
- "pottery"
writing:
id: "writing"
name: "Writing"
science_cost: 1800
column: 1
row: 1
icon: "images/resources/kennis.png"
description: "Recording laws, history, and scientific knowledge in script."
prerequisites:
- "pottery"
archery:
id: "archery"
name: "Archery"
science_cost: 1500
column: 1
row: 2
icon: "images/units/slinger.png"
description: "Use of bows and arrows for superior ranged warfare."
prerequisites:
- "animal_husbandry"
bronze_working:
id: "bronze_working"
name: "Bronze Working"
science_cost: 1800
column: 1
row: 3
icon: "images/units/warrior.png"
description: "Smelting copper and tin into durable bronze weapons and armor."
prerequisites:
- "mining"
masonry:
id: "masonry"
name: "Masonry"
science_cost: 1600
column: 1
row: 4
icon: "images/improvements/monument.png"
description: "Cutting and laying stone for permanent fortifications and monuments."
prerequisites:
- "mining"
celestial_navigation:
id: "celestial_navigation"
name: "Celestial Navigation"
science_cost: 2500
column: 2
row: 0
icon: "images/units/galley.png"
description: "Using the stars to guide maritime trade and exploration."
prerequisites:
- "sailing"
- "writing"
currency:
id: "currency"
name: "Currency"
science_cost: 2400
column: 2
row: 1
icon: "images/resources/goud.png"
description: "Standardized coinage enabling thriving markets and trade."
prerequisites:
- "writing"
wheel:
id: "wheel"
name: "The Wheel"
science_cost: 2200
column: 2
row: 2
icon: "images/actions/road.png"
description: "Rotational mechanics for transport and water-powered mills."
prerequisites:
- "archery"
- "mining"
horseback_riding:
id: "horseback_riding"
name: "Horseback Riding"
science_cost: 2500
column: 2
row: 3
icon: "images/actions/move.png"
description: "Mounted cavalry tactics for rapid battlefield maneuvers."
prerequisites:
- "archery"
iron_working:
id: "iron_working"
name: "Iron Working"
science_cost: 2800
column: 2
row: 4
icon: "images/actions/attack.png"
description: "Forging strong iron blades and heavy infantry armor."
prerequisites:
- "bronze_working"
education:
id: "education"
name: "Education"
science_cost: 3600
column: 3
row: 0
icon: "images/resources/kennis.png"
description: "Higher learning institutions and systematic scientific inquiry."
prerequisites:
- "currency"
- "celestial_navigation"
engineering:
id: "engineering"
name: "Engineering"
science_cost: 3400
column: 3
row: 2
icon: "images/resources/arbeid.png"
description: "Advanced structural design, aqueducts, and mechanical contraptions."
prerequisites:
- "wheel"
- "masonry"
stirrups:
id: "stirrups"
name: "Stirrups"
science_cost: 4000
column: 3
row: 3
icon: "images/actions/move.png"
description: "Saddle stirrups enabling heavily armored shock cavalry."
prerequisites:
- "horseback_riding"
- "iron_working"
machinery:
id: "machinery"
name: "Machinery"
science_cost: 4200
column: 4
row: 1
icon: "images/resources/arbeid.png"
description: "Mechanical gears, crossbows, and specialized workshops."
prerequisites:
- "engineering"
- "iron_working"
gunpowder:
id: "gunpowder"
name: "Gunpowder"
science_cost: 5200
column: 4
row: 3
icon: "images/actions/attack.png"
description: "Explosive black powder revolutionizing firearms and siege warfare."
prerequisites:
- "machinery"
- "stirrups"
+16
View File
@@ -0,0 +1,16 @@
id: archer
name: Archer
category: militair
domain: land
health: 85
movement_speed: 2.0
cost: 60
labor_cost: 1200
attack_damage: 25
attack_range: 2
required_tech: "archery"
obsolete_tech: "machinery"
icon: images/units/slinger.png
actions:
- verplaatsen
- aanvallen
+5 -3
View File
@@ -6,7 +6,9 @@ health: 100
movement_speed: 2.0 movement_speed: 2.0
cost: 50 cost: 50
labor_cost: 1000 labor_cost: 1000
build_charges: -1 # -1 voor oneindig doorwerken required_tech: ""
obsolete_tech: ""
build_charges: -1 # -1 for infinite build charges
infinite_charges: true infinite_charges: true
icon: images/units/builder.png icon: images/units/builder.png
actions: actions:
@@ -14,5 +16,5 @@ actions:
- boerderij_bouwen - boerderij_bouwen
- build_road - build_road
action_speeds: action_speeds:
boerderij_bouwen: 10.0 # Duur in seconden die deze builder unit nodig heeft voor de actie boerderij_bouwen boerderij_bouwen: 10.0
build_road: 2 # Duur in ticks build_road: 2
+16
View File
@@ -0,0 +1,16 @@
id: crossbowman
name: Crossbowman
category: militair
domain: land
health: 110
movement_speed: 2.0
cost: 120
labor_cost: 2400
attack_damage: 42
attack_range: 2
required_tech: "machinery"
obsolete_tech: ""
icon: images/units/slinger.png
actions:
- verplaatsen
- aanvallen
+2
View File
@@ -8,6 +8,8 @@ cost: 60
labor_cost: 1500 labor_cost: 1500
attack_damage: 25 attack_damage: 25
attack_range: 1 attack_range: 1
required_tech: "sailing"
obsolete_tech: ""
icon: images/units/galley.png icon: images/units/galley.png
actions: actions:
- verplaatsen - verplaatsen
+16
View File
@@ -0,0 +1,16 @@
id: horseman
name: Horseman
category: militair
domain: land
health: 125
movement_speed: 4.0
cost: 85
labor_cost: 1700
attack_damage: 34
attack_range: 1
required_tech: "horseback_riding"
obsolete_tech: "stirrups"
icon: images/units/warrior.png
actions:
- verplaatsen
- aanvallen
+16
View File
@@ -0,0 +1,16 @@
id: knight
name: Knight
category: militair
domain: land
health: 180
movement_speed: 4.0
cost: 140
labor_cost: 2800
attack_damage: 52
attack_range: 1
required_tech: "stirrups"
obsolete_tech: ""
icon: images/units/warrior.png
actions:
- verplaatsen
- aanvallen
+16
View File
@@ -0,0 +1,16 @@
id: musketman
name: Musketman
category: militair
domain: land
health: 200
movement_speed: 2.0
cost: 160
labor_cost: 3200
attack_damage: 60
attack_range: 1
required_tech: "gunpowder"
obsolete_tech: ""
icon: images/units/warrior.png
actions:
- verplaatsen
- aanvallen
+2
View File
@@ -6,6 +6,8 @@ health: 100
movement_speed: 2.0 movement_speed: 2.0
cost: 80 cost: 80
labor_cost: 2000 labor_cost: 2000
required_tech: ""
obsolete_tech: ""
icon: images/units/settler.png icon: images/units/settler.png
actions: actions:
- verplaatsen - verplaatsen
+2
View File
@@ -8,6 +8,8 @@ cost: 35
labor_cost: 800 labor_cost: 800
attack_damage: 15 attack_damage: 15
attack_range: 2 attack_range: 2
required_tech: ""
obsolete_tech: "archery"
icon: images/units/slinger.png icon: images/units/slinger.png
actions: actions:
- verplaatsen - verplaatsen
+16
View File
@@ -0,0 +1,16 @@
id: spearman
name: Spearman
category: militair
domain: land
health: 120
movement_speed: 2.0
cost: 65
labor_cost: 1300
attack_damage: 28
attack_range: 1
required_tech: "bronze_working"
obsolete_tech: "gunpowder"
icon: images/units/warrior.png
actions:
- verplaatsen
- aanvallen
+16
View File
@@ -0,0 +1,16 @@
id: swordsman
name: Swordsman
category: militair
domain: land
health: 140
movement_speed: 2.0
cost: 90
labor_cost: 1800
attack_damage: 38
attack_range: 1
required_tech: "iron_working"
obsolete_tech: "gunpowder"
icon: images/units/warrior.png
actions:
- verplaatsen
- aanvallen
+2
View File
@@ -8,6 +8,8 @@ cost: 40
labor_cost: 1000 labor_cost: 1000
attack_damage: 20 attack_damage: 20
attack_range: 1 attack_range: 1
required_tech: ""
obsolete_tech: "iron_working"
icon: images/units/warrior.png icon: images/units/warrior.png
actions: actions:
- verplaatsen - verplaatsen
BIN
View File
Binary file not shown.
+963 -426
View File
File diff suppressed because it is too large Load Diff
+35 -3
View File
@@ -14,6 +14,7 @@
#include "terrain.h" #include "terrain.h"
#include "terrainyieldmanager.h" #include "terrainyieldmanager.h"
#include "actionneededmanager.h" #include "actionneededmanager.h"
#include "techconfig.h"
enum class ActionMode { enum class ActionMode {
None, None,
@@ -48,13 +49,32 @@ public:
void update(float deltaTime, Matrix2D<TerrainPoint> *mapPoints, int numPoints05); void update(float deltaTime, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
PlayerManager& players() { return m_playerManager; } PlayerManager& players() { return m_playerManager; }
const PlayerManager& players() const { return m_playerManager; }
UnitConfigManager& unitConfigs() { return m_unitConfigManager; } UnitConfigManager& unitConfigs() { return m_unitConfigManager; }
const UnitConfigManager& unitConfigs() const { return m_unitConfigManager; }
BuildingConfigManager& buildingConfigs() { return m_buildingConfigManager; } BuildingConfigManager& buildingConfigs() { return m_buildingConfigManager; }
const BuildingConfigManager& buildingConfigs() const { return m_buildingConfigManager; } const BuildingConfigManager& buildingConfigs() const { return m_buildingConfigManager; }
RoadConfigManager& roadConfigs() { return m_roadConfigManager; } RoadConfigManager& roadConfigs() { return m_roadConfigManager; }
const RoadConfigManager& roadConfigs() const { return m_roadConfigManager; } const RoadConfigManager& roadConfigs() const { return m_roadConfigManager; }
ActionNeededManager& actionNeededManager() { return m_actionNeededManager; } ActionNeededManager& actionNeededManager() { return m_actionNeededManager; }
const ActionNeededManager& actionNeededManager() const { return m_actionNeededManager; } const ActionNeededManager& actionNeededManager() const { return m_actionNeededManager; }
TechTreeManager& techTree() { return m_techTreeManager; }
const TechTreeManager& techTree() const { return m_techTreeManager; }
// Tech Tree View & Research System
bool isTechTreeOpen() const { return m_isTechTreeOpen; }
void openTechTree();
void closeTechTree();
void toggleTechTree();
int calculatePlayerSciencePerTick(int playerId, Matrix2D<TerrainPoint> *mapPoints, int numPoints05) const;
int getTechRemainingScience(const Player &player, const QString &techId) const;
bool selectTechForPlayer(int playerId, const QString &targetTechId, bool appendToQueue = false);
void removeTechFromPlayerQueue(int playerId, const QString &techId);
void clearPlayerResearchQueue(int playerId);
void advancePlayerResearch(Player &player, int scienceGenerated);
QStringList getTechUnlocks(const QString &techId) const;
QStringList getTechObsoletes(const QString &techId) const;
const std::vector<std::shared_ptr<Unit>>& getUnits() const { return m_units; } const std::vector<std::shared_ptr<Unit>>& getUnits() const { return m_units; }
const std::vector<City>& getCities() const { return m_cities; } const std::vector<City>& getCities() const { return m_cities; }
@@ -99,12 +119,22 @@ public:
void removeQueuedCityProject(int cityId, int queueIndex); void removeQueuedCityProject(int cityId, int queueIndex);
void completeCityProject(City &city, Matrix2D<TerrainPoint> *mapPoints, int numPoints05); void completeCityProject(City &city, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
void commandMoveSelected(int targetX, int targetY, Matrix2D<TerrainPoint> *mapPoints, int numPoints05); void getPlannedOriginTile(const Unit &unit, bool appendWithShift, int &outX, int &outY) const;
void commandAttack(Unit *target); bool scheduleOrStartUnitAction(Unit &unit, const UnitAction &action, bool appendWithShift,
Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
bool startUnitAction(Unit &unit, const UnitAction &action,
Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
void continueCurrentUnitAction(Unit &unit, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
bool commandMoveSelected(int targetX, int targetY, Matrix2D<TerrainPoint> *mapPoints, int numPoints05, bool appendWithShift = false);
bool commandAttack(Unit *target, Matrix2D<TerrainPoint> *mapPoints = nullptr, int numPoints05 = 0, bool appendWithShift = false);
bool commandBuildCityAtTileSelected(int targetX, int targetY, Matrix2D<TerrainPoint> *mapPoints, int numPoints05, bool appendWithShift = false);
bool commandBuildFarmAtTileSelected(int targetX, int targetY, Matrix2D<TerrainPoint> *mapPoints, int numPoints05, bool appendWithShift = false);
bool commandBuildRoadToTileSelected(int targetX, int targetY, Matrix2D<TerrainPoint> *mapPoints, int numPoints05, bool appendWithShift = false);
void commandBuildCitySelected(Matrix2D<TerrainPoint> *mapPoints, int numPoints05); void commandBuildCitySelected(Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
void commandBuildFarmSelected(Matrix2D<TerrainPoint> *mapPoints, int numPoints05); void commandBuildFarmSelected(Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
void commandBuildRoadSelected(Matrix2D<TerrainPoint> *mapPoints, int numPoints05); void commandBuildRoadSelected(Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
void commandBuildRoadToTileSelected(int targetX, int targetY, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
void advanceRoadRoute(Unit &unit, Matrix2D<TerrainPoint> *mapPoints, int numPoints05); void advanceRoadRoute(Unit &unit, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
void completeUnitBuilding(Unit &unit, Matrix2D<TerrainPoint> *mapPoints, int numPoints05); void completeUnitBuilding(Unit &unit, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
void processUnitActionQueue(Unit &unit, Matrix2D<TerrainPoint> *mapPoints, int numPoints05); void processUnitActionQueue(Unit &unit, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
@@ -149,6 +179,7 @@ private:
BuildingConfigManager m_buildingConfigManager; BuildingConfigManager m_buildingConfigManager;
RoadConfigManager m_roadConfigManager; RoadConfigManager m_roadConfigManager;
ActionNeededManager m_actionNeededManager; ActionNeededManager m_actionNeededManager;
TechTreeManager m_techTreeManager;
std::vector<std::shared_ptr<Unit>> m_units; std::vector<std::shared_ptr<Unit>> m_units;
std::vector<City> m_cities; std::vector<City> m_cities;
std::vector<FloatingText> m_floatingTexts; std::vector<FloatingText> m_floatingTexts;
@@ -157,6 +188,7 @@ private:
int m_selectedUnitId; int m_selectedUnitId;
int m_activeCityViewId; int m_activeCityViewId;
bool m_isTechTreeOpen;
ActionMode m_actionMode; ActionMode m_actionMode;
float m_movementTickDuration; // Default 30.0s float m_movementTickDuration; // Default 30.0s
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+59
View File
@@ -2,8 +2,12 @@
#define PLAYER_H #define PLAYER_H
#include <QString> #include <QString>
#include <QStringList>
#include <QColor> #include <QColor>
#include <QMap>
#include <vector> #include <vector>
#include <set>
#include <algorithm>
struct Player { struct Player {
int id; int id;
@@ -12,11 +16,17 @@ struct Player {
QColor secondaryColor; QColor secondaryColor;
// National resources (shared across all cities of this nation) // National resources (shared across all cities of this nation)
// Note: science represents per-tick science generation rate (no longer stockpiled)
int culture; int culture;
int science; int science;
int gold; int gold;
int faith; int faith;
// Technology Tree & Research Progression
std::set<QString> researchedTechs;
QMap<QString, int> techProgress; // Tech ID -> accumulated science towards that tech
std::vector<QString> researchQueue; // Ordered queue of Tech IDs (front is active research)
Player() Player()
: id(0), name("Unknown"), primaryColor(Qt::blue), secondaryColor(Qt::white), : id(0), name("Unknown"), primaryColor(Qt::blue), secondaryColor(Qt::white),
culture(10000), science(0), gold(0), faith(0) {} culture(10000), science(0), gold(0), faith(0) {}
@@ -24,6 +34,55 @@ struct Player {
int pCulture = 10000, int pScience = 0, int pGold = 0, int pFaith = 0) int pCulture = 10000, int pScience = 0, int pGold = 0, int pFaith = 0)
: id(pId), name(pName), primaryColor(primary), secondaryColor(secondary), : id(pId), name(pName), primaryColor(primary), secondaryColor(secondary),
culture(pCulture), science(pScience), gold(pGold), faith(pFaith) {} culture(pCulture), science(pScience), gold(pGold), faith(pFaith) {}
bool hasResearched(const QString &techId) const {
if (techId.isEmpty()) return true;
return researchedTechs.find(techId) != researchedTechs.end();
}
int getTechProgress(const QString &techId) const {
return techProgress.value(techId, 0);
}
bool hasActiveResearch() const {
return !researchQueue.empty();
}
QString getActiveResearchTech() const {
return researchQueue.empty() ? QString() : researchQueue.front();
}
bool isTechQueued(const QString &techId) const {
return std::find(researchQueue.begin(), researchQueue.end(), techId) != researchQueue.end();
}
int getTechQueuePosition(const QString &techId) const {
for (size_t i = 0; i < researchQueue.size(); ++i) {
if (researchQueue[i] == techId) {
return static_cast<int>(i) + 1;
}
}
return 0;
}
bool arePrerequisitesMet(const QStringList &prerequisites) const {
for (const QString &pre : prerequisites) {
if (!hasResearched(pre)) return false;
}
return true;
}
bool isItemUnlocked(const QString &requiredTech) const {
return requiredTech.isEmpty() || hasResearched(requiredTech);
}
bool isItemObsolete(const QString &obsoleteTech) const {
return !obsoleteTech.isEmpty() && hasResearched(obsoleteTech);
}
bool isItemBuildable(const QString &requiredTech, const QString &obsoleteTech) const {
return isItemUnlocked(requiredTech) && !isItemObsolete(obsoleteTech);
}
}; };
class PlayerManager { class PlayerManager {
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+1004 -218
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -191,6 +191,31 @@ protected:
void drawActionNeededQueue(QPainter &painter); void drawActionNeededQueue(QPainter &painter);
void handleActionItemClick(ActionNeededType type, int entityId); void handleActionItemClick(ActionNeededType type, int entityId);
// Technology Tree UI Overlay
void drawTechTreeOverlay(QPainter &painter);
QRect m_techTreeBtnRect;
QRect m_scienceBarHitRect;
QRect m_closeTechTreeBtnRect;
QRect m_clearTechQueueBtnRect;
QRect m_techTreeCanvasRect;
int m_techTreeScrollX = 0;
int m_techTreeScrollY = 0;
bool m_draggingTechTree = false;
bool m_techTreeDragMoved = false;
QPoint m_techTreeDragStartPos;
struct TechNodeHit {
QRect rect;
QString techId;
};
std::vector<TechNodeHit> m_techNodeHits;
struct TechQueueChipHit {
QRect rect;
QString techId;
};
std::vector<TechQueueChipHit> m_techQueueChipHits;
}; };
#endif // QSCENERENDERER_H #endif // QSCENERENDERER_H
BIN
View File
Binary file not shown.
+16 -4
View File
@@ -17,7 +17,10 @@ RoadConfigManager::RoadConfigManager()
dirt.colorStr = "#c49a62"; dirt.colorStr = "#c49a62";
dirt.borderColorStr = "#6b4b23"; dirt.borderColorStr = "#6b4b23";
dirt.width = 3.5f; dirt.width = 3.5f;
dirt.worldWidth = 0.11f; dirt.worldWidth = 0.068f;
dirt.borderWidth = 0.022f;
dirt.textureScale = 2.4f;
dirt.opacity = 0.90f;
dirt.cost = 5; dirt.cost = 5;
m_types["dirt"] = dirt; m_types["dirt"] = dirt;
@@ -28,7 +31,10 @@ RoadConfigManager::RoadConfigManager()
stone.colorStr = "#9aa1a8"; stone.colorStr = "#9aa1a8";
stone.borderColorStr = "#464b50"; stone.borderColorStr = "#464b50";
stone.width = 4.0f; stone.width = 4.0f;
stone.worldWidth = 0.13f; stone.worldWidth = 0.078f;
stone.borderWidth = 0.020f;
stone.textureScale = 2.8f;
stone.opacity = 0.94f;
stone.cost = 15; stone.cost = 15;
m_types["stone"] = stone; m_types["stone"] = stone;
@@ -39,7 +45,10 @@ RoadConfigManager::RoadConfigManager()
rail.colorStr = "#3a3c40"; rail.colorStr = "#3a3c40";
rail.borderColorStr = "#181a1c"; rail.borderColorStr = "#181a1c";
rail.width = 4.5f; rail.width = 4.5f;
rail.worldWidth = 0.15f; rail.worldWidth = 0.086f;
rail.borderWidth = 0.024f;
rail.textureScale = 3.0f;
rail.opacity = 0.96f;
rail.cost = 30; rail.cost = 30;
m_types["rail"] = rail; m_types["rail"] = rail;
} }
@@ -75,7 +84,10 @@ bool RoadConfigManager::loadConfigFile(const QString &filePath)
rCfg.colorStr = it.value().hasChild("color") ? it.value()["color"].asString("#c49a62") : "#c49a62"; rCfg.colorStr = it.value().hasChild("color") ? it.value()["color"].asString("#c49a62") : "#c49a62";
rCfg.borderColorStr = it.value().hasChild("border_color") ? it.value()["border_color"].asString("#6b4b23") : "#6b4b23"; rCfg.borderColorStr = it.value().hasChild("border_color") ? it.value()["border_color"].asString("#6b4b23") : "#6b4b23";
rCfg.width = it.value().hasChild("width") ? it.value()["width"].asFloat(3.5f) : 3.5f; rCfg.width = it.value().hasChild("width") ? it.value()["width"].asFloat(3.5f) : 3.5f;
rCfg.worldWidth = it.value().hasChild("world_width") ? it.value()["world_width"].asFloat(0.11f) : 0.11f; rCfg.worldWidth = it.value().hasChild("world_width") ? it.value()["world_width"].asFloat(0.068f) : 0.068f;
rCfg.borderWidth = it.value().hasChild("border_width") ? it.value()["border_width"].asFloat(0.022f) : 0.022f;
rCfg.textureScale = it.value().hasChild("texture_scale") ? it.value()["texture_scale"].asFloat(2.4f) : 2.4f;
rCfg.opacity = it.value().hasChild("opacity") ? it.value()["opacity"].asFloat(0.90f) : 0.90f;
rCfg.cost = it.value().hasChild("cost") ? it.value()["cost"].asInt(5) : 5; rCfg.cost = it.value().hasChild("cost") ? it.value()["cost"].asInt(5) : 5;
m_types[rCfg.id] = rCfg; m_types[rCfg.id] = rCfg;
} }
+6 -3
View File
@@ -27,10 +27,13 @@ struct RoadTypeConfig {
QString id = "dirt"; QString id = "dirt";
QString name = "Dirt Road"; QString name = "Dirt Road";
float speedMultiplier = 1.30f; float speedMultiplier = 1.30f;
QString colorStr = "#c49a62"; QString colorStr = "#d8b480";
QString borderColorStr = "#6b4b23"; QString borderColorStr = "#52381c";
float width = 3.5f; float width = 3.5f;
float worldWidth = 0.11f; float worldWidth = 0.056f;
float borderWidth = 0.012f;
float textureScale = 4.5f;
float opacity = 0.96f;
int cost = 5; int cost = 5;
QColor getColor() const { return QColor(colorStr); } QColor getColor() const { return QColor(colorStr); }
+142 -12
View File
@@ -17,6 +17,17 @@ uniform int isFarm;
uniform float farmTextureScale; uniform float farmTextureScale;
uniform int farmMappingMode; uniform int farmMappingMode;
uniform int hasRoad;
uniform int roadMask;
uniform int roadType;
uniform float roadRadius;
uniform float roadBorderWidth;
uniform float roadTextureScale;
uniform float roadOpacity;
uniform float roadMeander;
uniform vec3 roadColor;
uniform vec3 roadBorderColor;
uniform float time; uniform float time;
uniform int riverMask; uniform int riverMask;
@@ -79,6 +90,61 @@ const vec2 EDGE_MIDPOINTS[6] = vec2[6](
vec2(-0.375, -0.21650635) vec2(-0.375, -0.21650635)
); );
float bezierCurveDist(vec2 p, vec2 p0, vec2 p2) {
vec2 s0 = p0;
vec2 s1 = 0.5625 * p0 + 0.0625 * p2;
vec2 s2 = 0.2500 * p0 + 0.2500 * p2;
vec2 s3 = 0.0625 * p0 + 0.5625 * p2;
vec2 s4 = p2;
float d0 = distToSegment(p, s0, s1);
float d1 = distToSegment(p, s1, s2);
float d2 = distToSegment(p, s2, s3);
float d3 = distToSegment(p, s3, s4);
return min(min(d0, d1), min(d2, d3));
}
float meanderingBezierDist(vec2 p, vec2 p0, vec2 p2, float amp, float s1) {
vec2 chord = p2 - p0;
float cLen = length(chord);
vec2 perp = (cLen > 1e-4) ? vec2(-chord.y / cLen, chord.x / cLen) : vec2(0.0, 1.0);
// sin^2(pi*t) has BOTH zero value AND zero derivative at t=0 and t=1,
// guaranteeing C1-continuous, kink-free transitions across hex borders!
const float PI_VAL = 3.14159265;
vec2 prevPt = p0;
float minDist = 999.0;
for (int i = 1; i <= 8; ++i) {
float t = float(i) / 8.0;
float omt = 1.0 - t;
vec2 basePt = (omt * omt) * p0 + (t * t) * p2;
float st = sin(PI_VAL * t);
float wave = amp * s1 * (st * st);
vec2 curPt = basePt + perp * wave;
minDist = min(minDist, distToSegment(p, prevPt, curPt));
prevPt = curPt;
}
return minDist;
}
float meanderingBranchDist(vec2 p, vec2 pe, float amp, float s1) {
float dLen = length(pe);
vec2 perp = (dLen > 1e-4) ? vec2(pe.y / dLen, -pe.x / dLen) : vec2(0.0, 1.0);
const float PI_VAL = 3.14159265;
vec2 prevPt = pe;
float minDist = 999.0;
for (int i = 1; i <= 6; ++i) {
float t = float(i) / 6.0;
vec2 basePt = pe * (1.0 - t);
float st = sin(PI_VAL * t);
float wave = amp * 0.65 * s1 * (st * st);
vec2 curPt = basePt + perp * wave;
minDist = min(minDist, distToSegment(p, prevPt, curPt));
prevPt = curPt;
}
return minDist;
}
float getRiverDistance(vec2 p, int mask) { float getRiverDistance(vec2 p, int mask) {
if (mask == 0) return 999.0; if (mask == 0) return 999.0;
@@ -92,18 +158,7 @@ float getRiverDistance(vec2 p, int mask) {
} }
if (count == 2) { if (count == 2) {
vec2 p0 = EDGE_MIDPOINTS[edges[0]]; return bezierCurveDist(p, EDGE_MIDPOINTS[edges[0]], EDGE_MIDPOINTS[edges[1]]);
vec2 p2 = EDGE_MIDPOINTS[edges[1]];
vec2 s0 = p0;
vec2 s1 = 0.5625 * p0 + 0.0625 * p2;
vec2 s2 = 0.2500 * p0 + 0.2500 * p2;
vec2 s3 = 0.0625 * p0 + 0.5625 * p2;
vec2 s4 = p2;
float d0 = distToSegment(p, s0, s1);
float d1 = distToSegment(p, s1, s2);
float d2 = distToSegment(p, s2, s3);
float d3 = distToSegment(p, s3, s4);
return min(min(d0, d1), min(d2, d3));
} else { } else {
float minDist = 999.0; float minDist = 999.0;
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
@@ -114,6 +169,38 @@ float getRiverDistance(vec2 p, int mask) {
} }
} }
float getRoadDistance(vec2 p, int mask, int ix, int iy, float amp) {
// Exact integer parity per hex tile: completely immune to floating-point interpolation jitter across scanlines!
int parity = (ix * 3 + iy * 5) & 1;
float s1 = (parity == 0) ? 1.0 : -1.0;
int edges[6];
int count = 0;
for (int e = 0; e < 6; ++e) {
if ((mask & (1 << e)) != 0) {
edges[count] = e;
count++;
}
}
if (count == 0) {
return meanderingBezierDist(p, vec2(-0.18, 0.0), vec2(0.18, 0.0), amp * 0.7, s1);
} else if (count == 1) {
return meanderingBranchDist(p, EDGE_MIDPOINTS[edges[0]], amp, s1);
} else if (count == 2) {
return meanderingBezierDist(p, EDGE_MIDPOINTS[edges[0]], EDGE_MIDPOINTS[edges[1]], amp, s1);
} else {
float minDist = 999.0;
for (int i = 0; i < count; ++i) {
minDist = min(minDist, meanderingBranchDist(p, EDGE_MIDPOINTS[edges[i]], amp * 0.5, s1));
for (int j = i + 1; j < count; ++j) {
minDist = min(minDist, bezierCurveDist(p, EDGE_MIDPOINTS[edges[i]], EDGE_MIDPOINTS[edges[j]]));
}
}
return minDist;
}
}
void main() { void main() {
vec3 norm = normalize(vNormal); vec3 norm = normalize(vNormal);
@@ -276,6 +363,49 @@ void main() {
vec4 nonSnowLand = mix(biomeGround, cliffRockColor, rockWeight); vec4 nonSnowLand = mix(biomeGround, cliffRockColor, rockWeight);
vec4 landColor = mix(nonSnowLand, snowColor, snowWeight); vec4 landColor = mix(nonSnowLand, snowColor, snowWeight);
// 6b. Road Texture Integration (blended directly into the tile surface texture)
if (hasRoad == 1) {
// Recover exact integer grid indices of the tile center (spacing is 0.75 in X and 0.43287992 in Y)
vec2 tileCenterWorld = vWorldPos.xy - fragVert.xy;
int ix = int(floor(tileCenterWorld.x / 0.75 + 0.5));
int iy = int(floor(tileCenterWorld.y / 0.43287992 + 0.5));
float amp = max(0.0, roadMeander);
float rScale = max(1.0, roadTextureScale);
vec2 uvRoad = vWorldPos.xy * rScale;
// Sample textures in uniform control flow (before any fragment-dependent branch) to avoid mipmap derivative glitches
vec3 soilTex = texture(texSand, uvRoad).rgb;
vec3 saharaTex = texture(texSahara, uvRoad * 0.9).rgb;
float fineGrain = (mix(soilTex.r, saharaTex.r, 0.5) - 0.5) * 0.18;
float rawRoadDist = getRoadDistance(fragVert.xy, roadMask, ix, iy, amp);
float rCore = max(0.012, roadRadius);
float rOuter = rCore + max(0.005, roadBorderWidth);
float roadCoreFac = 1.0 - smoothstep(rCore * 0.65, rCore, rawRoadDist);
float roadTotalFac = (1.0 - smoothstep(rCore, rOuter, rawRoadDist)) * (1.0 - oceanWaterFac);
if (roadTotalFac > 0.001) {
vec3 centerTone = clamp(roadColor * (1.0 + fineGrain), 0.0, 1.0);
vec3 borderTone = clamp(roadBorderColor * (1.0 + fineGrain * 0.7), 0.0, 1.0);
vec3 roadSurf = mix(borderTone, centerTone, roadCoreFac);
// If crossing a river, render a bridge deck and suppress river water under the bridge
if (riverWaterFac > 0.01) {
vec3 bridgeCol = (roadType >= 1)
? vec3(0.72, 0.70, 0.66) * (1.0 + fineGrain)
: vec3(0.68, 0.48, 0.28) * (1.0 + fineGrain);
roadSurf = mix(roadSurf, bridgeCol, riverWaterFac);
waterFac *= (1.0 - roadTotalFac * 0.98);
}
float blendAlpha = roadTotalFac * clamp(roadOpacity, 0.0, 1.0);
landColor.rgb = mix(landColor.rgb, roadSurf, blendAlpha);
}
}
// 7. Combine Land and Sea // 7. Combine Land and Sea
vec4 surfaceColor = mix(landColor, waterColor, waterFac); vec4 surfaceColor = mix(landColor, waterColor, waterFac);
+131
View File
@@ -0,0 +1,131 @@
#include "techconfig.h"
#include "yamlparser.h"
#include <QDebug>
#include <algorithm>
TechTreeManager::TechTreeManager()
: m_maxColumn(0), m_maxRow(0)
{
}
int TechTreeManager::computeTechDepth(const QString &techId, QMap<QString, int> &visited) const
{
if (visited.contains(techId)) {
return visited.value(techId);
}
visited[techId] = 0; // Guard against cycles
auto it = m_techs.find(techId);
if (it == m_techs.end()) {
return 0;
}
int maxPrereqDepth = -1;
for (const QString &pre : it.value().prerequisites) {
if (m_techs.contains(pre)) {
int d = computeTechDepth(pre, visited);
if (d > maxPrereqDepth) {
maxPrereqDepth = d;
}
}
}
int depth = maxPrereqDepth + 1;
visited[techId] = depth;
return depth;
}
bool TechTreeManager::loadConfigFile(const QString &filePath)
{
YamlNode root = YamlParser::parseFile(filePath);
if (root.children.isEmpty()) {
qWarning() << "Failed to parse technologies config YAML:" << filePath;
return false;
}
m_techs.clear();
m_orderedTechIds.clear();
m_maxColumn = 0;
m_maxRow = 0;
const YamlNode &techsNode = root.hasChild("technologies") ? root["technologies"] : root;
QMap<QString, bool> hasExplicitColumn;
QMap<QString, bool> hasExplicitRow;
for (auto it = techsNode.children.begin(); it != techsNode.children.end(); ++it) {
const YamlNode &node = it.value();
TechConfig cfg;
cfg.id = node.hasChild("id") ? node["id"].asString() : it.key();
cfg.name = node.hasChild("name") ? node["name"].asString() : cfg.id;
cfg.scienceCost = node.hasChild("science_cost") ? node["science_cost"].asInt(1000) : 1000;
if (node.hasChild("cost") && !node.hasChild("science_cost")) {
cfg.scienceCost = node["cost"].asInt(1000);
}
cfg.scienceCost = std::max(1, cfg.scienceCost);
if (node.hasChild("prerequisites")) {
cfg.prerequisites = node["prerequisites"].listValues;
}
if (node.hasChild("icon")) {
cfg.iconPath = node["icon"].asString("images/resources/kennis.png");
}
if (node.hasChild("description")) {
cfg.description = node["description"].asString("");
}
if (node.hasChild("column")) {
cfg.column = node["column"].asInt(0);
hasExplicitColumn[cfg.id] = true;
}
if (node.hasChild("row")) {
cfg.row = node["row"].asInt(0);
hasExplicitRow[cfg.id] = true;
}
m_techs[cfg.id] = cfg;
}
// Auto-calculate column and row for any tech without explicit coordinates
QMap<QString, int> depthCache;
QMap<int, int> nextRowInCol;
for (auto it = m_techs.begin(); it != m_techs.end(); ++it) {
if (!hasExplicitColumn.value(it.key(), false)) {
it.value().column = computeTechDepth(it.key(), depthCache);
}
if (hasExplicitRow.value(it.key(), false)) {
nextRowInCol[it.value().column] = std::max(nextRowInCol.value(it.value().column, 0), it.value().row + 1);
}
}
for (auto it = m_techs.begin(); it != m_techs.end(); ++it) {
if (!hasExplicitRow.value(it.key(), false)) {
int col = it.value().column;
it.value().row = nextRowInCol.value(col, 0);
nextRowInCol[col] = it.value().row + 1;
}
m_maxColumn = std::max(m_maxColumn, it.value().column);
m_maxRow = std::max(m_maxRow, it.value().row);
m_orderedTechIds.push_back(it.key());
}
std::sort(m_orderedTechIds.begin(), m_orderedTechIds.end(), [&](const QString &a, const QString &b) {
const TechConfig &ta = m_techs[a];
const TechConfig &tb = m_techs[b];
if (ta.column != tb.column) return ta.column < tb.column;
if (ta.row != tb.row) return ta.row < tb.row;
return ta.id < tb.id;
});
qDebug() << "TechTreeManager loaded" << m_techs.size() << "technologies from" << filePath;
return !m_techs.isEmpty();
}
const TechConfig* TechTreeManager::getTech(const QString &id) const
{
auto it = m_techs.find(id);
if (it != m_techs.end()) {
return &it.value();
}
return nullptr;
}
+45
View File
@@ -0,0 +1,45 @@
#ifndef TECHCONFIG_H
#define TECHCONFIG_H
#include <QString>
#include <QStringList>
#include <QMap>
#include <vector>
struct TechConfig {
QString id;
QString name;
int scienceCost;
QStringList prerequisites;
int column;
int row;
QString iconPath;
QString description;
TechConfig()
: id(""), name(""), scienceCost(1000), column(0), row(0),
iconPath("images/resources/kennis.png"), description("") {}
};
class TechTreeManager {
public:
TechTreeManager();
bool loadConfigFile(const QString &filePath);
const TechConfig* getTech(const QString &id) const;
const QMap<QString, TechConfig>& getAllTechs() const { return m_techs; }
const std::vector<QString>& getOrderedTechIds() const { return m_orderedTechIds; }
int getMaxColumn() const { return m_maxColumn; }
int getMaxRow() const { return m_maxRow; }
private:
int computeTechDepth(const QString &techId, QMap<QString, int> &visited) const;
QMap<QString, TechConfig> m_techs;
std::vector<QString> m_orderedTechIds;
int m_maxColumn;
int m_maxRow;
};
#endif // TECHCONFIG_H
Binary file not shown.
Binary file not shown.
+45 -15
View File
@@ -127,31 +127,31 @@ void Tile::init(QSceneRenderer* s, int x, int y)
buildHeights(); buildHeights();
if (Tile::m_image_grass == NULL) if (Tile::m_image_grass == NULL)
Tile::m_image_grass = new QImage("/home/bart/project/tiletest/images/grass.jpg"); Tile::m_image_grass = new QImage("/home/bart/git/Ascent-of-Man/images/grass.jpg");
if (Tile::m_image_border == NULL) if (Tile::m_image_border == NULL)
Tile::m_image_border = new QImage("/home/bart/project/tiletest/images/border.png"); Tile::m_image_border = new QImage("/home/bart/git/Ascent-of-Man/images/border.png");
if (Tile::m_image_rock == NULL) if (Tile::m_image_rock == NULL)
Tile::m_image_rock = new QImage("/home/bart/project/tiletest/images/Texturelabs_Stone_136XL_seamless.jpg"); Tile::m_image_rock = new QImage("/home/bart/git/Ascent-of-Man/images/Texturelabs_Stone_136XL_seamless.jpg");
if (Tile::m_image_sand == NULL) if (Tile::m_image_sand == NULL)
Tile::m_image_sand = new QImage("/home/bart/project/tiletest/images/Texturelabs_Soil_126XL_seamless.jpg"); Tile::m_image_sand = new QImage("/home/bart/git/Ascent-of-Man/images/Texturelabs_Soil_126XL_seamless.jpg");
if (Tile::m_image_water == NULL) if (Tile::m_image_water == NULL)
Tile::m_image_water = new QImage("/home/bart/project/tiletest/images/Texturelabs_Water_152XL_seamless.jpg"); Tile::m_image_water = new QImage("/home/bart/git/Ascent-of-Man/images/Texturelabs_Water_152XL_seamless.jpg");
if (Tile::m_image_snow == NULL) if (Tile::m_image_snow == NULL)
Tile::m_image_snow = new QImage("/home/bart/project/tiletest/images/Texturelabs_Water_155XL_seamless.jpg"); Tile::m_image_snow = new QImage("/home/bart/git/Ascent-of-Man/images/Texturelabs_Water_155XL_seamless.jpg");
if (Tile::m_image_typemap == NULL) if (Tile::m_image_typemap == NULL)
Tile::m_image_typemap = new QImage("/home/bart/project/tiletest/images/typemap.png"); Tile::m_image_typemap = new QImage("/home/bart/git/Ascent-of-Man/images/typemap.png");
if (Tile::m_image_sahara == NULL) if (Tile::m_image_sahara == NULL)
Tile::m_image_sahara = new QImage("/home/bart/project/tiletest/images/Texturelabs_Soil_133L_seamless.jpg"); Tile::m_image_sahara = new QImage("/home/bart/git/Ascent-of-Man/images/Texturelabs_Soil_133L_seamless.jpg");
if (Tile::m_image_tundra == NULL) if (Tile::m_image_tundra == NULL)
Tile::m_image_tundra = new QImage("/home/bart/project/tiletest/images/Texturelabs_Soil_135XL_seamless.jpg"); Tile::m_image_tundra = new QImage("/home/bart/git/Ascent-of-Man/images/Texturelabs_Soil_135XL_seamless.jpg");
if (Tile::m_image_redsand == NULL) if (Tile::m_image_redsand == NULL)
Tile::m_image_redsand = new QImage("/home/bart/project/tiletest/images/Texturelabs_RedSand_seamless.jpg"); Tile::m_image_redsand = new QImage("/home/bart/git/Ascent-of-Man/images/Texturelabs_RedSand_seamless.jpg");
if (Tile::m_image_redrock == NULL) if (Tile::m_image_redrock == NULL)
Tile::m_image_redrock = new QImage("/home/bart/project/tiletest/images/Texturelabs_RedRock_seamless.jpg"); Tile::m_image_redrock = new QImage("/home/bart/git/Ascent-of-Man/images/Texturelabs_RedRock_seamless.jpg");
if (Tile::m_image_forest == NULL) if (Tile::m_image_forest == NULL)
Tile::m_image_forest = new QImage("/home/bart/project/tiletest/images/Texturelabs_Forest_seamless.jpg"); Tile::m_image_forest = new QImage("/home/bart/git/Ascent-of-Man/images/Texturelabs_Forest_seamless.jpg");
if (Tile::m_image_farm == NULL) if (Tile::m_image_farm == NULL)
Tile::m_image_farm = new QImage("/home/bart/project/tiletest/images/farm.jpeg"); Tile::m_image_farm = new QImage("/home/bart/git/Ascent-of-Man/images/farm.jpeg");
if (Tile::m_texture_grass == NULL) { if (Tile::m_texture_grass == NULL) {
m_texture_grass = new QOpenGLTexture(*m_image_grass); m_texture_grass = new QOpenGLTexture(*m_image_grass);
@@ -247,8 +247,8 @@ void Tile::init(QSceneRenderer* s, int x, int y)
if (first){ if (first){
m_program = new QOpenGLShaderProgram(); m_program = new QOpenGLShaderProgram();
m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, "/home/bart/project/tiletest/shaders/simple.vert"); m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, "/home/bart/git/Ascent-of-Man/shaders/simple.vert");
m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, "/home/bart/project/tiletest/shaders/simple.frag"); m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, "/home/bart/git/Ascent-of-Man/shaders/simple.frag");
m_program->link(); m_program->link();
} }
m_program->bind(); m_program->bind();
@@ -361,6 +361,36 @@ void Tile::paint(QMatrix4x4 p, QVector3D& cameraPosition,QVector3D& cameraUpDire
m_program->setUniformValue("farmTextureScale", fScale); m_program->setUniformValue("farmTextureScale", fScale);
m_program->setUniformValue("farmMappingMode", mappingMode); m_program->setUniformValue("farmMappingMode", mappingMode);
const TileRoad *road = GameManager::instance().getRoadAt(m_x, m_y);
if (road != nullptr) {
const RoadTypeConfig *rCfg = GameManager::instance().roadConfigs().getRoadType(road->roadTypeId);
int rType = 0;
if (road->roadTypeId == "stone") rType = 1;
else if (road->roadTypeId == "rail") rType = 2;
float rRad = rCfg ? (rCfg->worldWidth * 0.5f) : 0.034f;
float rBorder = rCfg ? rCfg->borderWidth : 0.022f;
float rScale = rCfg ? rCfg->textureScale : 2.4f;
float rOpacity = rCfg ? rCfg->opacity : 0.90f;
float rMeander = GameManager::instance().roadConfigs().meanderAmplitude();
QColor col = rCfg ? rCfg->getColor() : QColor("#c49a62");
QColor bCol = rCfg ? rCfg->getBorderColor() : QColor("#6b4b23");
m_program->setUniformValue("hasRoad", 1);
m_program->setUniformValue("roadMask", (int)road->connectionMask);
m_program->setUniformValue("roadType", rType);
m_program->setUniformValue("roadRadius", rRad);
m_program->setUniformValue("roadBorderWidth", rBorder);
m_program->setUniformValue("roadTextureScale", rScale);
m_program->setUniformValue("roadOpacity", rOpacity);
m_program->setUniformValue("roadMeander", rMeander);
m_program->setUniformValue("roadColor", QVector3D(col.redF(), col.greenF(), col.blueF()));
m_program->setUniformValue("roadBorderColor", QVector3D(bCol.redF(), bCol.greenF(), bCol.blueF()));
} else {
m_program->setUniformValue("hasRoad", 0);
m_program->setUniformValue("roadMask", 0);
}
m_program->setUniformValue("time", time); m_program->setUniformValue("time", time);
m_program->setUniformValue("riverMask", (int)m_riverMask); m_program->setUniformValue("riverMask", (int)m_riverMask);
m_object.bind(); m_object.bind();
BIN
View File
Binary file not shown.
+4 -2
View File
@@ -33,7 +33,8 @@ SOURCES += main.cpp \
localization.cpp \ localization.cpp \
terrainmovementmanager.cpp \ terrainmovementmanager.cpp \
roadconfig.cpp \ roadconfig.cpp \
actionneededmanager.cpp actionneededmanager.cpp \
techconfig.cpp
HEADERS += mainwindow.h \ HEADERS += mainwindow.h \
qscenerenderer.h \ qscenerenderer.h \
@@ -56,7 +57,8 @@ HEADERS += mainwindow.h \
localization.h \ localization.h \
terrainmovementmanager.h \ terrainmovementmanager.h \
roadconfig.h \ roadconfig.h \
actionneededmanager.h actionneededmanager.h \
techconfig.h
FORMS += mainwindow.ui FORMS += mainwindow.ui
BIN
View File
Binary file not shown.
+58 -13
View File
@@ -1,8 +1,10 @@
#include "unit.h" #include "unit.h"
#include "pathfinder.h" #include "pathfinder.h"
#include "terrainmovementmanager.h" #include "terrainmovementmanager.h"
#include "localization.h"
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <QStringList>
Unit::Unit(int uId, int pId, const UnitConfig &cfg, int startX, int startY, float groundZ) Unit::Unit(int uId, int pId, const UnitConfig &cfg, int startX, int startY, float groundZ)
: id(uId), playerId(pId), config(cfg), tileX(startX), tileY(startY), : id(uId), playerId(pId), config(cfg), tileX(startX), tileY(startY),
@@ -57,6 +59,36 @@ bool Unit::hasQueuedActionType(UnitActionType type) const
return false; return false;
} }
bool Unit::hasQueuedImprovementAt(int tx, int ty) const
{
if (currentAction.type == UnitActionType::BuildImprovement &&
currentAction.targetX == tx && currentAction.targetY == ty) {
return true;
}
for (const auto &act : actionQueue) {
if (act.type == UnitActionType::BuildImprovement &&
act.targetX == tx && act.targetY == ty) {
return true;
}
}
return false;
}
bool Unit::hasQueuedCityAt(int tx, int ty) const
{
if (currentAction.type == UnitActionType::BuildCity &&
currentAction.targetX == tx && currentAction.targetY == ty) {
return true;
}
for (const auto &act : actionQueue) {
if (act.type == UnitActionType::BuildCity &&
act.targetX == tx && act.targetY == ty) {
return true;
}
}
return false;
}
void Unit::removeQueuedActionsOfType(UnitActionType type) void Unit::removeQueuedActionsOfType(UnitActionType type)
{ {
for (auto it = actionQueue.begin(); it != actionQueue.end(); ) { for (auto it = actionQueue.begin(); it != actionQueue.end(); ) {
@@ -82,23 +114,35 @@ const UnitAction* Unit::peekNextQueuedAction() const
return &actionQueue.front(); return &actionQueue.front();
} }
QString Unit::getActionSummary(const UnitAction &act) const
{
switch (act.type) {
case UnitActionType::Move:
return Localization::instance().get("ui.actions.move", "Move");
case UnitActionType::BuildImprovement:
return Localization::instance().get("ui.actions.build_farm", "Build Farm");
case UnitActionType::BuildCity:
return Localization::instance().get("ui.actions.found_city", "Found City");
case UnitActionType::BuildRoad:
return Localization::instance().get("ui.actions.build_road", "Build Road");
case UnitActionType::Attack:
return Localization::instance().get("ui.actions.attack", "Attack");
default:
return "";
}
}
QString Unit::getQueuedActionSummary() const QString Unit::getQueuedActionSummary() const
{ {
if (actionQueue.empty()) return ""; if (actionQueue.empty()) return "";
switch (actionQueue.front().type) { QStringList parts;
case UnitActionType::Move: for (size_t i = 0; i < actionQueue.size() && i < 3; ++i) {
return "Move"; parts.append(getActionSummary(actionQueue[i]));
case UnitActionType::BuildImprovement:
return "Build Farm";
case UnitActionType::BuildCity:
return "Found City";
case UnitActionType::BuildRoad:
return "Build Road";
case UnitActionType::Attack:
return "Attack";
default:
return "Action";
} }
if (actionQueue.size() > 3) {
parts.append(QString("+%1").arg(actionQueue.size() - 3));
}
return parts.join(" -> ");
} }
void Unit::setPath(const std::vector<std::pair<int, int>> &newPath) void Unit::setPath(const std::vector<std::pair<int, int>> &newPath)
@@ -122,6 +166,7 @@ void Unit::stopMovement()
pathIndex = 0; pathIndex = 0;
moveProgress = 0.0f; moveProgress = 0.0f;
state = UnitState::Idle; state = UnitState::Idle;
currentAction = UnitAction();
clearActionQueue(); clearActionQueue();
clearRoadRoute(); clearRoadRoute();
} }
+12 -4
View File
@@ -35,8 +35,8 @@ struct UnitAction {
UnitAction() = default; UnitAction() = default;
UnitAction(UnitActionType t) : type(t) {} UnitAction(UnitActionType t) : type(t) {}
UnitAction(UnitActionType t, int x, int y, const QString &data = "") UnitAction(UnitActionType t, int x, int y, const QString &data = "", int uId = -1)
: type(t), targetX(x), targetY(y), extraData(data) {} : type(t), targetX(x), targetY(y), targetUnitId(uId), extraData(data) {}
UnitAction(UnitActionType t, int targetId) UnitAction(UnitActionType t, int targetId)
: type(t), targetUnitId(targetId) {} : type(t), targetUnitId(targetId) {}
@@ -65,9 +65,10 @@ public:
int targetTileX; int targetTileX;
int targetTileY; int targetTileY;
// Action Queue // Currently executing action & subsequent Action Queue
UnitAction currentAction;
std::deque<UnitAction> actionQueue; std::deque<UnitAction> actionQueue;
int maxQueueSize = 5; int maxQueueSize = 15;
// Road Construction Route (tile-by-tile road paving) // Road Construction Route (tile-by-tile road paving)
std::vector<std::pair<int, int>> roadRoute; std::vector<std::pair<int, int>> roadRoute;
@@ -88,13 +89,20 @@ public:
} }
} }
bool hasActiveAction() const {
return currentAction.isValid() || state != UnitState::Idle || hasRoadRoute();
}
bool queueAction(const UnitAction &action); bool queueAction(const UnitAction &action);
void clearActionQueue(); void clearActionQueue();
bool hasQueuedAction() const { return !actionQueue.empty(); } bool hasQueuedAction() const { return !actionQueue.empty(); }
bool hasQueuedActionType(UnitActionType type) const; bool hasQueuedActionType(UnitActionType type) const;
bool hasQueuedImprovementAt(int tx, int ty) const;
bool hasQueuedCityAt(int tx, int ty) const;
void removeQueuedActionsOfType(UnitActionType type); void removeQueuedActionsOfType(UnitActionType type);
UnitAction popNextQueuedAction(); UnitAction popNextQueuedAction();
const UnitAction* peekNextQueuedAction() const; const UnitAction* peekNextQueuedAction() const;
QString getActionSummary(const UnitAction &act) const;
QString getQueuedActionSummary() const; QString getQueuedActionSummary() const;
// Attack cooldown / flash // Attack cooldown / flash
BIN
View File
Binary file not shown.
+2
View File
@@ -53,6 +53,8 @@ bool UnitConfigManager::loadConfigFile(const QString &filePath)
cfg.attackRange = root.hasChild("attack_range") ? root["attack_range"].asInt(1) : 1; cfg.attackRange = root.hasChild("attack_range") ? root["attack_range"].asInt(1) : 1;
cfg.buildCharges = root.hasChild("build_charges") ? root["build_charges"].asInt(-1) : -1; cfg.buildCharges = root.hasChild("build_charges") ? root["build_charges"].asInt(-1) : -1;
cfg.infiniteCharges = root.hasChild("infinite_charges") ? root["infinite_charges"].asBool(cfg.buildCharges < 0) : (cfg.buildCharges < 0); cfg.infiniteCharges = root.hasChild("infinite_charges") ? root["infinite_charges"].asBool(cfg.buildCharges < 0) : (cfg.buildCharges < 0);
cfg.requiredTech = root.hasChild("required_tech") ? root["required_tech"].asString("") : "";
cfg.obsoleteTech = root.hasChild("obsolete_tech") ? root["obsolete_tech"].asString("") : "";
cfg.iconPath = root.hasChild("icon") ? root["icon"].asString() : ""; cfg.iconPath = root.hasChild("icon") ? root["icon"].asString() : "";
if (root.hasChild("domain")) { if (root.hasChild("domain")) {
+3 -1
View File
@@ -44,6 +44,8 @@ struct UnitConfig {
int attackRange; // 1 for melee, >1 for ranged int attackRange; // 1 for melee, >1 for ranged
int buildCharges; int buildCharges;
bool infiniteCharges; bool infiniteCharges;
QString requiredTech;
QString obsoleteTech;
QString iconPath; QString iconPath;
QStringList actions; QStringList actions;
QMap<QString, float> actionSpeeds; // Duration in seconds per action (e.g. "boerderij_bouwen": 10.0f) QMap<QString, float> actionSpeeds; // Duration in seconds per action (e.g. "boerderij_bouwen": 10.0f)
@@ -54,7 +56,7 @@ struct UnitConfig {
: id(""), name(""), category(UnitCategory::Builder), : id(""), name(""), category(UnitCategory::Builder),
movementDomain(MovementDomain::Land), health(100), movementDomain(MovementDomain::Land), health(100),
movementSpeed(2.0f), cost(10), laborCost(1000), attackDamage(0), attackRange(1), movementSpeed(2.0f), cost(10), laborCost(1000), attackDamage(0), attackRange(1),
buildCharges(-1), infiniteCharges(true), iconPath("") {} buildCharges(-1), infiniteCharges(true), requiredTech(""), obsoleteTech(""), iconPath("") {}
bool isRanged() const { return attackRange > 1; } bool isRanged() const { return attackRange > 1; }
bool isMilitary() const { return category == UnitCategory::Military; } bool isMilitary() const { return category == UnitCategory::Military; }
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.