Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eff6598555 | ||
|
|
8eda9a2532 | ||
|
|
882c83ba13 | ||
|
|
eaa847ac27 | ||
|
|
4685451afe | ||
|
|
a8841f59d9 | ||
|
|
d266715418 |
+29
-1
@@ -33,6 +33,9 @@ bool ActionNeededManager::loadConfigFile(const QString &filePath)
|
||||
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);
|
||||
}
|
||||
@@ -70,6 +73,10 @@ bool ActionNeededManager::loadConfigFile(const QString &filePath)
|
||||
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);
|
||||
@@ -85,7 +92,8 @@ bool ActionNeededManager::loadConfigFile(const QString &filePath)
|
||||
|
||||
void ActionNeededManager::update(float deltaTime, int activePlayerId,
|
||||
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;
|
||||
|
||||
@@ -93,6 +101,25 @@ void ActionNeededManager::update(float deltaTime, int activePlayerId,
|
||||
|
||||
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
|
||||
if (m_config.trackIdleUnits) {
|
||||
for (const auto &u : units) {
|
||||
@@ -100,6 +127,7 @@ void ActionNeededManager::update(float deltaTime, int activePlayerId,
|
||||
|
||||
bool isIdle = (u->state == UnitState::Idle)
|
||||
&& (u->path.empty() || u->pathIndex >= u->path.size())
|
||||
&& !u->currentAction.isValid()
|
||||
&& !u->hasQueuedAction()
|
||||
&& !u->hasRoadRoute()
|
||||
&& !u->isBuilding();
|
||||
|
||||
@@ -12,7 +12,8 @@ class City;
|
||||
enum class ActionNeededType {
|
||||
UnitIdle = 0,
|
||||
CityNoProduction = 1,
|
||||
CityFreePopulation = 2
|
||||
CityFreePopulation = 2,
|
||||
NoResearch = 3
|
||||
};
|
||||
|
||||
struct ActionItem {
|
||||
@@ -33,6 +34,7 @@ struct ActionNeededConfig {
|
||||
bool trackIdleUnits = true;
|
||||
bool trackCityNoProduction = true;
|
||||
bool trackCityFreePopulation = true;
|
||||
bool trackNoResearch = true;
|
||||
int maxVisibleItems = 8;
|
||||
|
||||
int itemWidth = 155;
|
||||
@@ -46,6 +48,7 @@ struct ActionNeededConfig {
|
||||
QColor unitIdleColor = QColor(60, 160, 255);
|
||||
QColor cityNoProdColor = QColor(255, 160, 40);
|
||||
QColor cityFreePopColor = QColor(70, 220, 130);
|
||||
QColor noResearchColor = QColor(56, 200, 255);
|
||||
QColor cardBgColor = QColor(18, 24, 34);
|
||||
int cardBgAlpha = 235;
|
||||
};
|
||||
@@ -59,7 +62,8 @@ public:
|
||||
|
||||
void update(float deltaTime, int activePlayerId,
|
||||
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;
|
||||
int getVisibleCount() const;
|
||||
|
||||
@@ -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.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.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.description = it.value().hasChild("description") ? it.value()["description"].asString() : "";
|
||||
m_buildings[bCfg.id] = bCfg;
|
||||
|
||||
@@ -17,6 +17,8 @@ struct BuildingConfig {
|
||||
int scienceBonus;
|
||||
int faithBonus;
|
||||
int goldBonus;
|
||||
QString requiredTech;
|
||||
QString obsoleteTech;
|
||||
QString iconPath;
|
||||
QString description;
|
||||
|
||||
@@ -24,6 +26,7 @@ struct BuildingConfig {
|
||||
: id(""), name(""), district("center"), laborCost(1000),
|
||||
foodModifier(0.0f), foodBonus(0), laborBonus(0), cultureBonus(0),
|
||||
scienceBonus(0), faithBonus(0), goldBonus(0),
|
||||
requiredTech(""), obsoleteTech(""),
|
||||
iconPath(""), description("") {}
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ action_needed:
|
||||
track_idle_units: true
|
||||
track_city_no_production: true
|
||||
track_city_free_population: true
|
||||
track_no_research: true
|
||||
max_visible_items: 8
|
||||
|
||||
# Visual & Layout parameters
|
||||
@@ -19,5 +20,6 @@ action_needed:
|
||||
unit_idle_color: "#3CA0FF"
|
||||
city_no_prod_color: "#FFA028"
|
||||
city_free_pop_color: "#46DC82"
|
||||
no_research_color: "#38C8FF"
|
||||
card_bg_color: "#121822"
|
||||
card_bg_alpha: 235
|
||||
|
||||
+131
-8
@@ -1,19 +1,55 @@
|
||||
# City Buildings & Extensions configuration
|
||||
# All gameplay values are configurable via YAML
|
||||
# All gameplay values, required technologies, and obsolescence technologies are configurable via YAML
|
||||
|
||||
districts:
|
||||
center:
|
||||
id: "center"
|
||||
name: "City Center"
|
||||
max_buildings: 10
|
||||
max_buildings: 12
|
||||
description: "The urban core and administrative center of the city."
|
||||
|
||||
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:
|
||||
id: "granary"
|
||||
name: "Granary"
|
||||
district: "center"
|
||||
labor_cost: 1000
|
||||
required_tech: "pottery"
|
||||
obsolete_tech: ""
|
||||
food_modifier: 0.50
|
||||
food_bonus: 0
|
||||
labor_bonus: 0
|
||||
@@ -24,17 +60,104 @@ buildings:
|
||||
icon: "images/improvements/granary.png"
|
||||
description: "+50% Food production in city"
|
||||
|
||||
monument:
|
||||
id: "monument"
|
||||
name: "Monument"
|
||||
library:
|
||||
id: "library"
|
||||
name: "Library"
|
||||
district: "center"
|
||||
labor_cost: 800
|
||||
labor_cost: 1200
|
||||
required_tech: "writing"
|
||||
obsolete_tech: ""
|
||||
food_modifier: 0.0
|
||||
food_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
|
||||
faith_bonus: 0
|
||||
gold_bonus: 0
|
||||
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
@@ -2,7 +2,7 @@ movement_tick_duration: 30.0
|
||||
|
||||
# Action queue configuration
|
||||
action_queue:
|
||||
max_size: 5
|
||||
max_size: 15
|
||||
auto_queue_move_when_busy: true
|
||||
allow_action_queueing: true
|
||||
|
||||
|
||||
+20
-11
@@ -1,34 +1,43 @@
|
||||
# Road System Configuration
|
||||
default_road_type: "dirt"
|
||||
build_duration_ticks: 2
|
||||
meander_amplitude: 0.045
|
||||
meander_subdivisions: 5
|
||||
meander_amplitude: 0.025
|
||||
meander_subdivisions: 6
|
||||
|
||||
types:
|
||||
dirt:
|
||||
id: "dirt"
|
||||
name: "Dirt Road"
|
||||
speed_multiplier: 1.30 # +30% movement speed
|
||||
color: "#c49a62"
|
||||
border_color: "#6b4b23"
|
||||
color: "#d8b480"
|
||||
border_color: "#52381c"
|
||||
width: 3.5
|
||||
world_width: 0.11
|
||||
world_width: 0.056
|
||||
border_width: 0.012
|
||||
texture_scale: 4.5
|
||||
opacity: 0.96
|
||||
cost: 5
|
||||
stone:
|
||||
id: "stone"
|
||||
name: "Stone Road"
|
||||
speed_multiplier: 1.60 # +60% movement speed
|
||||
color: "#9aa1a8"
|
||||
border_color: "#464b50"
|
||||
color: "#c2beb6"
|
||||
border_color: "#46484c"
|
||||
width: 4.0
|
||||
world_width: 0.13
|
||||
world_width: 0.064
|
||||
border_width: 0.012
|
||||
texture_scale: 5.5
|
||||
opacity: 0.98
|
||||
cost: 15
|
||||
rail:
|
||||
id: "rail"
|
||||
name: "Railroad"
|
||||
speed_multiplier: 2.50 # +150% movement speed
|
||||
color: "#3a3c40"
|
||||
border_color: "#181a1c"
|
||||
color: "#4a4e54"
|
||||
border_color: "#22252a"
|
||||
width: 4.5
|
||||
world_width: 0.15
|
||||
world_width: 0.070
|
||||
border_width: 0.014
|
||||
texture_scale: 6.0
|
||||
opacity: 0.98
|
||||
cost: 30
|
||||
|
||||
+76
-22
@@ -49,25 +49,30 @@ ui:
|
||||
queue_cleared: "Queued actions cancelled"
|
||||
queue_full: "Action queue full!"
|
||||
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"
|
||||
farm_queued: "Build Farm queued on arrival"
|
||||
farm_queued: "Build Farm queued"
|
||||
farm_started: "Constructing Farm..."
|
||||
farm_completed: "Farm completed!"
|
||||
farm_completed_consumed: "Farm completed! (Builder consumed)"
|
||||
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_completed: "%1 completed!"
|
||||
road_already_here: "Tile already has a road!"
|
||||
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!"
|
||||
already_building: "Already busy building!"
|
||||
busy: "Unit is busy!"
|
||||
cannot_act_busy: "Unit cannot perform action while busy!"
|
||||
tile_occupied: "Tile already has an improvement!"
|
||||
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!"
|
||||
charge_singular: "charge"
|
||||
charge_plural: "charges"
|
||||
@@ -76,6 +81,7 @@ ui:
|
||||
no_path: "No reachable path!"
|
||||
tile_purchased: "Tile purchased! (-%1 Culture)"
|
||||
out_of_range: "Out of range!"
|
||||
no_enemy_on_tile: "Select a tile with an enemy unit!"
|
||||
defeated: "Defeated!"
|
||||
city_already_here: "A city already exists here!"
|
||||
unsuitable_terrain: "Unsuitable terrain!"
|
||||
@@ -86,35 +92,45 @@ ui:
|
||||
combat_format: "Attack: %1 | Range: %2 hex"
|
||||
building_pct: "Building... %1%"
|
||||
charges_format: "Charges: %1"
|
||||
status_queued: "Queued: %1"
|
||||
status_queued: "Queue (%1): %2"
|
||||
btn_move: "Move [M]"
|
||||
btn_attack: "Attack [A]"
|
||||
btn_build_city: "Settle [B]"
|
||||
btn_build_farm: "Farm [F]"
|
||||
btn_farm_queued: "Farm (Queued)"
|
||||
btn_farm_queued: "Farm [F]"
|
||||
btn_farm_building: "Building %1%"
|
||||
btn_build_road: "Road [R]"
|
||||
btn_road_queued: "Road (Queued)"
|
||||
btn_road_queued: "Road [R]"
|
||||
btn_road_building: "Building %1%"
|
||||
tip_move: "Move (M)"
|
||||
tip_attack: "Attack (A) [Range: %1]"
|
||||
tip_build_city: "Found City (B)"
|
||||
tip_build_farm: "Build Farm (F)"
|
||||
tip_farm_building: "Busy building... %1%"
|
||||
tip_farm_queued: "Build Farm (Queued on arrival - click to cancel)"
|
||||
tip_build_road: "Build Road (R) - Click target tile to build route"
|
||||
tip_road_building: "Busy building road... %1%"
|
||||
tip_road_queued: "Build Road (Queued on arrival - click to cancel)"
|
||||
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!"
|
||||
tip_move: "Move (M) - Click target tile (Shift+Click to queue)"
|
||||
tip_attack: "Attack (A) [Range: %1] - Click enemy (Shift+Click to queue)"
|
||||
tip_build_city: "Found City (B) - Click target tile (Shift+Click to queue)"
|
||||
tip_build_farm: "Build Farm (F) - Click target tile (Shift+Click to queue)"
|
||||
tip_farm_building: "Busy building... %1% (Click to plan next target)"
|
||||
tip_farm_queued: "Build Farm (Queued - Click to plan target)"
|
||||
tip_build_road: "Build Road (R) - Click target tile (Shift+Click to queue)"
|
||||
tip_road_building: "Busy building road... %1% (Click to plan next target)"
|
||||
tip_road_queued: "Build Road (Queued - Click to plan target)"
|
||||
road_route_started: "Building road route to (%1, %2)..."
|
||||
road_route_completed: "Road route completed!"
|
||||
road_route_cancelled: "Road route cancelled"
|
||||
status_road_route: "Road 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:
|
||||
farm: "Farm"
|
||||
@@ -128,7 +144,7 @@ ui:
|
||||
management_title: "%1 - CITY MANAGEMENT"
|
||||
close_btn: "[X] Close"
|
||||
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."
|
||||
current_production: "CURRENT PRODUCTION"
|
||||
production_panel_title: "CITY PRODUCTION"
|
||||
@@ -160,6 +176,8 @@ ui:
|
||||
queued_item_removed: "%1: Removed %2 from queue"
|
||||
already_built: "%1 already built in this city!"
|
||||
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"
|
||||
not_enough_culture: "Not enough Culture! (%1 needed)"
|
||||
worker_freed: "Pop worker freed"
|
||||
@@ -187,6 +205,42 @@ ui:
|
||||
unit_idle_desc: "Needs orders"
|
||||
city_no_production_desc: "No production"
|
||||
city_free_pop_desc: "+%1 Free Pop"
|
||||
no_research_title: "RESEARCH"
|
||||
no_research_desc: "Choose technology"
|
||||
dismiss_tip: "Right-click to dismiss"
|
||||
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"
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -6,7 +6,9 @@ health: 100
|
||||
movement_speed: 2.0
|
||||
cost: 50
|
||||
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
|
||||
icon: images/units/builder.png
|
||||
actions:
|
||||
@@ -14,5 +16,5 @@ actions:
|
||||
- boerderij_bouwen
|
||||
- build_road
|
||||
action_speeds:
|
||||
boerderij_bouwen: 10.0 # Duur in seconden die deze builder unit nodig heeft voor de actie boerderij_bouwen
|
||||
build_road: 2 # Duur in ticks
|
||||
boerderij_bouwen: 10.0
|
||||
build_road: 2
|
||||
|
||||
@@ -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
|
||||
@@ -8,6 +8,8 @@ cost: 60
|
||||
labor_cost: 1500
|
||||
attack_damage: 25
|
||||
attack_range: 1
|
||||
required_tech: "sailing"
|
||||
obsolete_tech: ""
|
||||
icon: images/units/galley.png
|
||||
actions:
|
||||
- verplaatsen
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -6,6 +6,8 @@ health: 100
|
||||
movement_speed: 2.0
|
||||
cost: 80
|
||||
labor_cost: 2000
|
||||
required_tech: ""
|
||||
obsolete_tech: ""
|
||||
icon: images/units/settler.png
|
||||
actions:
|
||||
- verplaatsen
|
||||
|
||||
@@ -8,6 +8,8 @@ cost: 35
|
||||
labor_cost: 800
|
||||
attack_damage: 15
|
||||
attack_range: 2
|
||||
required_tech: ""
|
||||
obsolete_tech: "archery"
|
||||
icon: images/units/slinger.png
|
||||
actions:
|
||||
- verplaatsen
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -8,6 +8,8 @@ cost: 40
|
||||
labor_cost: 1000
|
||||
attack_damage: 20
|
||||
attack_range: 1
|
||||
required_tech: ""
|
||||
obsolete_tech: "iron_working"
|
||||
icon: images/units/warrior.png
|
||||
actions:
|
||||
- verplaatsen
|
||||
|
||||
+961
-424
File diff suppressed because it is too large
Load Diff
+35
-3
@@ -14,6 +14,7 @@
|
||||
#include "terrain.h"
|
||||
#include "terrainyieldmanager.h"
|
||||
#include "actionneededmanager.h"
|
||||
#include "techconfig.h"
|
||||
|
||||
enum class ActionMode {
|
||||
None,
|
||||
@@ -48,13 +49,32 @@ public:
|
||||
void update(float deltaTime, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
||||
|
||||
PlayerManager& players() { return m_playerManager; }
|
||||
const PlayerManager& players() const { return m_playerManager; }
|
||||
UnitConfigManager& unitConfigs() { return m_unitConfigManager; }
|
||||
const UnitConfigManager& unitConfigs() const { return m_unitConfigManager; }
|
||||
BuildingConfigManager& buildingConfigs() { return m_buildingConfigManager; }
|
||||
const BuildingConfigManager& buildingConfigs() const { return m_buildingConfigManager; }
|
||||
RoadConfigManager& roadConfigs() { return m_roadConfigManager; }
|
||||
const RoadConfigManager& roadConfigs() const { return m_roadConfigManager; }
|
||||
ActionNeededManager& actionNeededManager() { 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<City>& getCities() const { return m_cities; }
|
||||
@@ -99,12 +119,22 @@ public:
|
||||
void removeQueuedCityProject(int cityId, int queueIndex);
|
||||
void completeCityProject(City &city, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
||||
|
||||
void commandMoveSelected(int targetX, int targetY, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
||||
void commandAttack(Unit *target);
|
||||
void getPlannedOriginTile(const Unit &unit, bool appendWithShift, int &outX, int &outY) const;
|
||||
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 commandBuildFarmSelected(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 completeUnitBuilding(Unit &unit, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
||||
void processUnitActionQueue(Unit &unit, Matrix2D<TerrainPoint> *mapPoints, int numPoints05);
|
||||
@@ -149,6 +179,7 @@ private:
|
||||
BuildingConfigManager m_buildingConfigManager;
|
||||
RoadConfigManager m_roadConfigManager;
|
||||
ActionNeededManager m_actionNeededManager;
|
||||
TechTreeManager m_techTreeManager;
|
||||
std::vector<std::shared_ptr<Unit>> m_units;
|
||||
std::vector<City> m_cities;
|
||||
std::vector<FloatingText> m_floatingTexts;
|
||||
@@ -157,6 +188,7 @@ private:
|
||||
|
||||
int m_selectedUnitId;
|
||||
int m_activeCityViewId;
|
||||
bool m_isTechTreeOpen;
|
||||
ActionMode m_actionMode;
|
||||
|
||||
float m_movementTickDuration; // Default 30.0s
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -2,8 +2,12 @@
|
||||
#define PLAYER_H
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QColor>
|
||||
#include <QMap>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
|
||||
struct Player {
|
||||
int id;
|
||||
@@ -12,11 +16,17 @@ struct Player {
|
||||
QColor secondaryColor;
|
||||
|
||||
// National resources (shared across all cities of this nation)
|
||||
// Note: science represents per-tick science generation rate (no longer stockpiled)
|
||||
int culture;
|
||||
int science;
|
||||
int gold;
|
||||
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()
|
||||
: id(0), name("Unknown"), primaryColor(Qt::blue), secondaryColor(Qt::white),
|
||||
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)
|
||||
: id(pId), name(pName), primaryColor(primary), secondaryColor(secondary),
|
||||
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 {
|
||||
|
||||
Binary file not shown.
+1004
-218
File diff suppressed because it is too large
Load Diff
@@ -191,6 +191,31 @@ protected:
|
||||
|
||||
void drawActionNeededQueue(QPainter &painter);
|
||||
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
|
||||
|
||||
Binary file not shown.
+16
-4
@@ -17,7 +17,10 @@ RoadConfigManager::RoadConfigManager()
|
||||
dirt.colorStr = "#c49a62";
|
||||
dirt.borderColorStr = "#6b4b23";
|
||||
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;
|
||||
m_types["dirt"] = dirt;
|
||||
|
||||
@@ -28,7 +31,10 @@ RoadConfigManager::RoadConfigManager()
|
||||
stone.colorStr = "#9aa1a8";
|
||||
stone.borderColorStr = "#464b50";
|
||||
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;
|
||||
m_types["stone"] = stone;
|
||||
|
||||
@@ -39,7 +45,10 @@ RoadConfigManager::RoadConfigManager()
|
||||
rail.colorStr = "#3a3c40";
|
||||
rail.borderColorStr = "#181a1c";
|
||||
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;
|
||||
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.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.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;
|
||||
m_types[rCfg.id] = rCfg;
|
||||
}
|
||||
|
||||
+6
-3
@@ -27,10 +27,13 @@ struct RoadTypeConfig {
|
||||
QString id = "dirt";
|
||||
QString name = "Dirt Road";
|
||||
float speedMultiplier = 1.30f;
|
||||
QString colorStr = "#c49a62";
|
||||
QString borderColorStr = "#6b4b23";
|
||||
QString colorStr = "#d8b480";
|
||||
QString borderColorStr = "#52381c";
|
||||
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;
|
||||
|
||||
QColor getColor() const { return QColor(colorStr); }
|
||||
|
||||
+142
-12
@@ -17,6 +17,17 @@ uniform int isFarm;
|
||||
uniform float farmTextureScale;
|
||||
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 int riverMask;
|
||||
|
||||
@@ -79,6 +90,61 @@ const vec2 EDGE_MIDPOINTS[6] = vec2[6](
|
||||
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) {
|
||||
if (mask == 0) return 999.0;
|
||||
|
||||
@@ -92,18 +158,7 @@ float getRiverDistance(vec2 p, int mask) {
|
||||
}
|
||||
|
||||
if (count == 2) {
|
||||
vec2 p0 = EDGE_MIDPOINTS[edges[0]];
|
||||
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));
|
||||
return bezierCurveDist(p, EDGE_MIDPOINTS[edges[0]], EDGE_MIDPOINTS[edges[1]]);
|
||||
} else {
|
||||
float minDist = 999.0;
|
||||
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() {
|
||||
vec3 norm = normalize(vNormal);
|
||||
|
||||
@@ -276,6 +363,49 @@ void main() {
|
||||
vec4 nonSnowLand = mix(biomeGround, cliffRockColor, rockWeight);
|
||||
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
|
||||
vec4 surfaceColor = mix(landColor, waterColor, waterFac);
|
||||
|
||||
|
||||
+131
@@ -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;
|
||||
}
|
||||
@@ -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.
@@ -127,31 +127,31 @@ void Tile::init(QSceneRenderer* s, int x, int y)
|
||||
buildHeights();
|
||||
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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) {
|
||||
m_texture_grass = new QOpenGLTexture(*m_image_grass);
|
||||
@@ -247,8 +247,8 @@ void Tile::init(QSceneRenderer* s, int x, int y)
|
||||
|
||||
if (first){
|
||||
m_program = new QOpenGLShaderProgram();
|
||||
m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, "/home/bart/project/tiletest/shaders/simple.vert");
|
||||
m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, "/home/bart/project/tiletest/shaders/simple.frag");
|
||||
m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, "/home/bart/git/Ascent-of-Man/shaders/simple.vert");
|
||||
m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, "/home/bart/git/Ascent-of-Man/shaders/simple.frag");
|
||||
m_program->link();
|
||||
}
|
||||
m_program->bind();
|
||||
@@ -361,6 +361,36 @@ void Tile::paint(QMatrix4x4 p, QVector3D& cameraPosition,QVector3D& cameraUpDire
|
||||
m_program->setUniformValue("farmTextureScale", fScale);
|
||||
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("riverMask", (int)m_riverMask);
|
||||
m_object.bind();
|
||||
|
||||
+4
-2
@@ -33,7 +33,8 @@ SOURCES += main.cpp \
|
||||
localization.cpp \
|
||||
terrainmovementmanager.cpp \
|
||||
roadconfig.cpp \
|
||||
actionneededmanager.cpp
|
||||
actionneededmanager.cpp \
|
||||
techconfig.cpp
|
||||
|
||||
HEADERS += mainwindow.h \
|
||||
qscenerenderer.h \
|
||||
@@ -56,7 +57,8 @@ HEADERS += mainwindow.h \
|
||||
localization.h \
|
||||
terrainmovementmanager.h \
|
||||
roadconfig.h \
|
||||
actionneededmanager.h
|
||||
actionneededmanager.h \
|
||||
techconfig.h
|
||||
|
||||
FORMS += mainwindow.ui
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,8 +1,10 @@
|
||||
#include "unit.h"
|
||||
#include "pathfinder.h"
|
||||
#include "terrainmovementmanager.h"
|
||||
#include "localization.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <QStringList>
|
||||
|
||||
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),
|
||||
@@ -57,6 +59,36 @@ bool Unit::hasQueuedActionType(UnitActionType type) const
|
||||
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)
|
||||
{
|
||||
for (auto it = actionQueue.begin(); it != actionQueue.end(); ) {
|
||||
@@ -82,23 +114,35 @@ const UnitAction* Unit::peekNextQueuedAction() const
|
||||
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
|
||||
{
|
||||
if (actionQueue.empty()) return "";
|
||||
switch (actionQueue.front().type) {
|
||||
case UnitActionType::Move:
|
||||
return "Move";
|
||||
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";
|
||||
QStringList parts;
|
||||
for (size_t i = 0; i < actionQueue.size() && i < 3; ++i) {
|
||||
parts.append(getActionSummary(actionQueue[i]));
|
||||
}
|
||||
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)
|
||||
@@ -122,6 +166,7 @@ void Unit::stopMovement()
|
||||
pathIndex = 0;
|
||||
moveProgress = 0.0f;
|
||||
state = UnitState::Idle;
|
||||
currentAction = UnitAction();
|
||||
clearActionQueue();
|
||||
clearRoadRoute();
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ struct UnitAction {
|
||||
|
||||
UnitAction() = default;
|
||||
UnitAction(UnitActionType t) : type(t) {}
|
||||
UnitAction(UnitActionType t, int x, int y, const QString &data = "")
|
||||
: type(t), targetX(x), targetY(y), extraData(data) {}
|
||||
UnitAction(UnitActionType t, int x, int y, const QString &data = "", int uId = -1)
|
||||
: type(t), targetX(x), targetY(y), targetUnitId(uId), extraData(data) {}
|
||||
UnitAction(UnitActionType t, int targetId)
|
||||
: type(t), targetUnitId(targetId) {}
|
||||
|
||||
@@ -65,9 +65,10 @@ public:
|
||||
int targetTileX;
|
||||
int targetTileY;
|
||||
|
||||
// Action Queue
|
||||
// Currently executing action & subsequent Action Queue
|
||||
UnitAction currentAction;
|
||||
std::deque<UnitAction> actionQueue;
|
||||
int maxQueueSize = 5;
|
||||
int maxQueueSize = 15;
|
||||
|
||||
// Road Construction Route (tile-by-tile road paving)
|
||||
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);
|
||||
void clearActionQueue();
|
||||
bool hasQueuedAction() const { return !actionQueue.empty(); }
|
||||
bool hasQueuedActionType(UnitActionType type) const;
|
||||
bool hasQueuedImprovementAt(int tx, int ty) const;
|
||||
bool hasQueuedCityAt(int tx, int ty) const;
|
||||
void removeQueuedActionsOfType(UnitActionType type);
|
||||
UnitAction popNextQueuedAction();
|
||||
const UnitAction* peekNextQueuedAction() const;
|
||||
QString getActionSummary(const UnitAction &act) const;
|
||||
QString getQueuedActionSummary() const;
|
||||
|
||||
// Attack cooldown / flash
|
||||
|
||||
@@ -53,6 +53,8 @@ bool UnitConfigManager::loadConfigFile(const QString &filePath)
|
||||
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.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() : "";
|
||||
|
||||
if (root.hasChild("domain")) {
|
||||
|
||||
+3
-1
@@ -44,6 +44,8 @@ struct UnitConfig {
|
||||
int attackRange; // 1 for melee, >1 for ranged
|
||||
int buildCharges;
|
||||
bool infiniteCharges;
|
||||
QString requiredTech;
|
||||
QString obsoleteTech;
|
||||
QString iconPath;
|
||||
QStringList actions;
|
||||
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),
|
||||
movementDomain(MovementDomain::Land), health(100),
|
||||
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 isMilitary() const { return category == UnitCategory::Military; }
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user