Compare commits
13
Commits
9f61bbd739
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9bb91eef4 | ||
|
|
8c24b112d4 | ||
|
|
2ef8e857e0 | ||
|
|
772d4494e8 | ||
|
|
ea0eedc0fb | ||
|
|
eff6598555 | ||
|
|
8eda9a2532 | ||
|
|
882c83ba13 | ||
|
|
eaa847ac27 | ||
|
|
4685451afe | ||
|
|
a8841f59d9 | ||
|
|
d266715418 | ||
|
|
d4bd9f4711 |
@@ -5,3 +5,4 @@ Makefile*
|
|||||||
tiletest
|
tiletest
|
||||||
terrain_test2
|
terrain_test2
|
||||||
.qmake.stash
|
.qmake.stash
|
||||||
|
tiletest
|
||||||
|
|||||||
@@ -24,15 +24,6 @@ This document serves as the primary technical specification, architecture refere
|
|||||||
> - **Key-Based Lookup:** Code must reference screen texts exclusively by localization keys (e.g. `localization.get("ui.button.found_city")` or equivalent lookup) resolving to the external file.
|
> - **Key-Based Lookup:** Code must reference screen texts exclusively by localization keys (e.g. `localization.get("ui.button.found_city")` or equivalent lookup) resolving to the external file.
|
||||||
> - Whenever new on-screen text is introduced, add its English translation entry to the external string file first before referencing its key in code.
|
> - Whenever new on-screen text is introduced, add its English translation entry to the external string file first before referencing its key in code.
|
||||||
|
|
||||||
### 0.3 Session Git Branching & Build Restrictions
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> **Strict Agent Directive:** Every session must adhere to an isolated Git workflow and never build without permission.
|
|
||||||
> - **Dedicated Session Branch:** Every session MUST work on its own git branch.
|
|
||||||
> - **Automatic Branch Creation:** If a session does not have its own branch yet upon starting, the agent MUST generate an appropriate branch name (e.g. `session/<topic-or-task>`) and check it out immediately (`git checkout -b <branch-name>`).
|
|
||||||
> - **Do NOT Build Without Explicit Permission:** The agent MUST NOT compile or build the application (never run `make`, `qmake`, `cmake`, `ninja`, compiler invocations, or build scripts) unless explicit permission has been granted by the user.
|
|
||||||
> - **Immediate Commits:** After making any change/edit to the project, the agent MUST immediately commit the changes to its current session branch with a clear and concise commit message (`git add <files>` and `git commit -m "..."`).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Project Vision & Core Concept
|
## 1. Project Vision & Core Concept
|
||||||
@@ -113,6 +104,30 @@ To implement the RTS Civilization game, develop modular subsystems decoupled fro
|
|||||||
+-----------------+
|
+-----------------+
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 3.1 Simulation Time & Tick System
|
||||||
|
|
||||||
|
The game combines continuous real-time rendering and interpolation (`deltaTime`) with a configurable **Simulation Tick** (`movement_tick_duration` in `config/game.yaml`, defaulting to `30.0` seconds at $1\times$ simulation speed):
|
||||||
|
|
||||||
|
1. **Tick Timer & Speed Multiplier:**
|
||||||
|
- `GameManager::update` advances an internal tick timer by `deltaTime * simSpeedMultiplier`.
|
||||||
|
- When the timer reaches `movement_tick_duration`, a simulation tick fires (`processTickYields`).
|
||||||
|
2. **Discrete Per-Tick Economy & Progression (`processTickYields`):**
|
||||||
|
- **Food & Population:** City food yield is converted into inhabitants (`food * foodToInhabitantsRatio`).
|
||||||
|
- **Labor (Production):** City labor yield is added to the active construction project (`currentLabor`), completing units or buildings when `laborCost` is reached, or accumulating locally if idle.
|
||||||
|
- **National Stockpiles:** Culture, Faith, and Gold yields from all owned cities are added to the player's national treasury.
|
||||||
|
- **Science & Research:** Total science generated per tick across a player's cities is applied directly to the active technology in the player's `researchQueue`.
|
||||||
|
3. **Continuous Real-Time Unit Movement (`movement_speed`, Scaled by Tick Duration):**
|
||||||
|
- Units move smoothly every frame, and their movement rate is defined in **movement points (base hexes) per tick** (`movement_speed` in `config/units/*.yaml`, stored in `UnitConfig::movementSpeed` — higher values mean faster movement, e.g., `2.0` for infantry/civilian, `3.0` for galley, `4.0` for cavalry).
|
||||||
|
- Effective speed in hexes per second is calculated dynamically per hex segment as:
|
||||||
|
$$\text{hexesPerSec} = \frac{\text{movementSpeed}}{\text{tickDuration} \times \text{stepCost}} \times \text{simSpeedMultiplier}$$
|
||||||
|
- On flat terrain (`stepCost = 1.0`), a unit with `movementSpeed = 2.0` traverses $2.0$ hexes during one full simulation tick ($0.5$ ticks per hex).
|
||||||
|
4. **Unit Construction & Action Durations (`action_durations`):**
|
||||||
|
- Unlike `movement_speed` (which is a rate per tick), unit build actions (such as `boerderij_bouwen` and `build_road` under `action_durations` in `config/units/builder.yaml`) are configured as **durations in simulation ticks** (ticks per action, accessed via `UnitConfig::getActionDuration`).
|
||||||
|
- Total real-time build duration in seconds is calculated as:
|
||||||
|
$$\text{duration} = \text{ticks} \times \text{movement\_tick\_duration}$$
|
||||||
|
- While building, `buildProgress` interpolates continuously from $0.0$ to $1.0$ over `duration` seconds (scaled by `simSpeedMultiplier`).
|
||||||
|
- Similarly, city production (`labor_cost` in `config/units/*.yaml` and `config/buildings.yaml`) and technology research (`science_cost` in `config/technologies.yaml`) progress discretely each tick based on the city/empire's per-tick yield.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Agents & Unit Specifications
|
## 4. Agents & Unit Specifications
|
||||||
@@ -133,7 +148,7 @@ Units are autonomous or player-directed entities traversing the hex map in real
|
|||||||
- **Siege:** Catapult $\rightarrow$ Trebuchet $\rightarrow$ Bombard (high bonus damage against city walls).
|
- **Siege:** Catapult $\rightarrow$ Trebuchet $\rightarrow$ Bombard (high bonus damage against city walls).
|
||||||
- **Naval:** Galley $\rightarrow$ Caravel $\rightarrow$ Frigate $\rightarrow$ Battleship (operates only on water hexes).
|
- **Naval:** Galley $\rightarrow$ Caravel $\rightarrow$ Frigate $\rightarrow$ Battleship (operates only on water hexes).
|
||||||
|
|
||||||
### 4.2 Unit Data Model (`Unit.h`)
|
### 4.2 Unit Data Model (`unit.h` / `unitconfig.h`)
|
||||||
```cpp
|
```cpp
|
||||||
enum class UnitType { Settler, Worker, Warrior, Archer, Horseman, Galley };
|
enum class UnitType { Settler, Worker, Warrior, Archer, Horseman, Galley };
|
||||||
enum class UnitState { Idle, Moving, Attacking, Harvesting, Building, Fortified };
|
enum class UnitState { Idle, Moving, Attacking, Harvesting, Building, Fortified };
|
||||||
@@ -155,14 +170,14 @@ public:
|
|||||||
float health, maxHealth;
|
float health, maxHealth;
|
||||||
float attackDamage;
|
float attackDamage;
|
||||||
float defense;
|
float defense;
|
||||||
float movementSpeed; // hexes per second
|
float movementSpeed; // movement points (base hexes) per simulation tick
|
||||||
float visionRange; // in hex radius
|
float visionRange; // in hex radius
|
||||||
|
|
||||||
// Path & Target
|
// Path & Target
|
||||||
std::vector<std::pair<int, int>> currentPath;
|
std::vector<std::pair<int, int>> currentPath;
|
||||||
int targetEntityId; // target unit or city
|
int targetEntityId; // target unit or city
|
||||||
|
|
||||||
void update(float deltaTime);
|
void update(float deltaTime, float tickDuration, float simSpeedMultiplier);
|
||||||
void commandMoveTo(int destX, int destY);
|
void commandMoveTo(int destX, int destY);
|
||||||
void commandAttack(int targetId);
|
void commandAttack(int targetId);
|
||||||
void commandBuildImprovement(int improvementType);
|
void commandBuildImprovement(int improvementType);
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ MOVE = mv -f
|
|||||||
TAR = tar -cf
|
TAR = tar -cf
|
||||||
COMPRESS = gzip -9f
|
COMPRESS = gzip -9f
|
||||||
DISTNAME = tiletest1.0.0
|
DISTNAME = tiletest1.0.0
|
||||||
DISTDIR = /home/bart/project/tiletest/.tmp/tiletest1.0.0
|
DISTDIR = /home/bart/git/Ascent-of-Man/.tmp/tiletest1.0.0
|
||||||
LINK = g++
|
LINK = g++
|
||||||
LFLAGS = -Wl,-O1
|
LFLAGS = -Wl,-O1
|
||||||
LIBS = $(SUBLIBS) -L/usr/lib64 -lGLEW -lX11 -lGLU /usr/lib/x86_64-linux-gnu/libQt5Widgets.so /usr/lib/x86_64-linux-gnu/libQt5Gui.so /usr/lib/x86_64-linux-gnu/libQt5Core.so -lGL -lpthread
|
LIBS = $(SUBLIBS) -L/usr/lib64 -lGLEW -lX11 -lGLU /usr/lib/x86_64-linux-gnu/libQt5Widgets.so /usr/lib/x86_64-linux-gnu/libQt5Gui.so /usr/lib/x86_64-linux-gnu/libQt5Core.so -lGL -lpthread
|
||||||
@@ -62,6 +62,7 @@ SOURCES = main.cpp \
|
|||||||
player.cpp \
|
player.cpp \
|
||||||
yamlparser.cpp \
|
yamlparser.cpp \
|
||||||
unitconfig.cpp \
|
unitconfig.cpp \
|
||||||
|
buildingconfig.cpp \
|
||||||
pathfinder.cpp \
|
pathfinder.cpp \
|
||||||
unit.cpp \
|
unit.cpp \
|
||||||
city.cpp \
|
city.cpp \
|
||||||
@@ -69,7 +70,10 @@ SOURCES = main.cpp \
|
|||||||
gamemanager.cpp \
|
gamemanager.cpp \
|
||||||
unitrenderer.cpp \
|
unitrenderer.cpp \
|
||||||
localization.cpp \
|
localization.cpp \
|
||||||
terrainmovementmanager.cpp qrc_shaders.cpp \
|
terrainmovementmanager.cpp \
|
||||||
|
roadconfig.cpp \
|
||||||
|
actionneededmanager.cpp \
|
||||||
|
techconfig.cpp qrc_shaders.cpp \
|
||||||
moc_mainwindow.cpp
|
moc_mainwindow.cpp
|
||||||
OBJECTS = main.o \
|
OBJECTS = main.o \
|
||||||
mainwindow.o \
|
mainwindow.o \
|
||||||
@@ -81,6 +85,7 @@ OBJECTS = main.o \
|
|||||||
player.o \
|
player.o \
|
||||||
yamlparser.o \
|
yamlparser.o \
|
||||||
unitconfig.o \
|
unitconfig.o \
|
||||||
|
buildingconfig.o \
|
||||||
pathfinder.o \
|
pathfinder.o \
|
||||||
unit.o \
|
unit.o \
|
||||||
city.o \
|
city.o \
|
||||||
@@ -89,6 +94,9 @@ OBJECTS = main.o \
|
|||||||
unitrenderer.o \
|
unitrenderer.o \
|
||||||
localization.o \
|
localization.o \
|
||||||
terrainmovementmanager.o \
|
terrainmovementmanager.o \
|
||||||
|
roadconfig.o \
|
||||||
|
actionneededmanager.o \
|
||||||
|
techconfig.o \
|
||||||
qrc_shaders.o \
|
qrc_shaders.o \
|
||||||
moc_mainwindow.o
|
moc_mainwindow.o
|
||||||
DIST = shaders/simple.vert \
|
DIST = shaders/simple.vert \
|
||||||
@@ -104,13 +112,6 @@ DIST = shaders/simple.vert \
|
|||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/common/g++-base.conf \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/common/g++-base.conf \
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/common/g++-unix.conf \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/common/g++-unix.conf \
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/qconfig.pri \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/qconfig.pri \
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KConfigCore.pri \
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KConfigGui.pri \
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KCoreAddons.pri \
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KDBusAddons.pri \
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KI18n.pri \
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KService.pri \
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KWindowSystem.pri \
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_accessibility_support_private.pri \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_accessibility_support_private.pri \
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_bootstrap_private.pri \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_bootstrap_private.pri \
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_concurrent.pri \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_concurrent.pri \
|
||||||
@@ -142,8 +143,6 @@ DIST = shaders/simple.vert \
|
|||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_platformcompositor_support_private.pri \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_platformcompositor_support_private.pri \
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_printsupport.pri \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_printsupport.pri \
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_printsupport_private.pri \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_printsupport_private.pri \
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_script.pri \
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_scripttools.pri \
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_service_support_private.pri \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_service_support_private.pri \
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_sql.pri \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_sql.pri \
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_sql_private.pri \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_sql_private.pri \
|
||||||
@@ -193,6 +192,7 @@ DIST = shaders/simple.vert \
|
|||||||
player.h \
|
player.h \
|
||||||
yamlparser.h \
|
yamlparser.h \
|
||||||
unitconfig.h \
|
unitconfig.h \
|
||||||
|
buildingconfig.h \
|
||||||
pathfinder.h \
|
pathfinder.h \
|
||||||
unit.h \
|
unit.h \
|
||||||
city.h \
|
city.h \
|
||||||
@@ -200,7 +200,10 @@ DIST = shaders/simple.vert \
|
|||||||
gamemanager.h \
|
gamemanager.h \
|
||||||
unitrenderer.h \
|
unitrenderer.h \
|
||||||
localization.h \
|
localization.h \
|
||||||
terrainmovementmanager.h main.cpp \
|
terrainmovementmanager.h \
|
||||||
|
roadconfig.h \
|
||||||
|
actionneededmanager.h \
|
||||||
|
techconfig.h main.cpp \
|
||||||
mainwindow.cpp \
|
mainwindow.cpp \
|
||||||
qscenerenderer.cpp \
|
qscenerenderer.cpp \
|
||||||
vertex.cpp \
|
vertex.cpp \
|
||||||
@@ -210,6 +213,7 @@ DIST = shaders/simple.vert \
|
|||||||
player.cpp \
|
player.cpp \
|
||||||
yamlparser.cpp \
|
yamlparser.cpp \
|
||||||
unitconfig.cpp \
|
unitconfig.cpp \
|
||||||
|
buildingconfig.cpp \
|
||||||
pathfinder.cpp \
|
pathfinder.cpp \
|
||||||
unit.cpp \
|
unit.cpp \
|
||||||
city.cpp \
|
city.cpp \
|
||||||
@@ -217,7 +221,10 @@ DIST = shaders/simple.vert \
|
|||||||
gamemanager.cpp \
|
gamemanager.cpp \
|
||||||
unitrenderer.cpp \
|
unitrenderer.cpp \
|
||||||
localization.cpp \
|
localization.cpp \
|
||||||
terrainmovementmanager.cpp
|
terrainmovementmanager.cpp \
|
||||||
|
roadconfig.cpp \
|
||||||
|
actionneededmanager.cpp \
|
||||||
|
techconfig.cpp
|
||||||
QMAKE_TARGET = tiletest
|
QMAKE_TARGET = tiletest
|
||||||
DESTDIR =
|
DESTDIR =
|
||||||
TARGET = tiletest
|
TARGET = tiletest
|
||||||
@@ -238,13 +245,6 @@ Makefile: tiletest.pro /usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-g++/qmake.con
|
|||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/common/g++-base.conf \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/common/g++-base.conf \
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/common/g++-unix.conf \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/common/g++-unix.conf \
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/qconfig.pri \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/qconfig.pri \
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KConfigCore.pri \
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KConfigGui.pri \
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KCoreAddons.pri \
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KDBusAddons.pri \
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KI18n.pri \
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KService.pri \
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KWindowSystem.pri \
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_accessibility_support_private.pri \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_accessibility_support_private.pri \
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_bootstrap_private.pri \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_bootstrap_private.pri \
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_concurrent.pri \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_concurrent.pri \
|
||||||
@@ -276,8 +276,6 @@ Makefile: tiletest.pro /usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-g++/qmake.con
|
|||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_platformcompositor_support_private.pri \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_platformcompositor_support_private.pri \
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_printsupport.pri \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_printsupport.pri \
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_printsupport_private.pri \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_printsupport_private.pri \
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_script.pri \
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_scripttools.pri \
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_service_support_private.pri \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_service_support_private.pri \
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_sql.pri \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_sql.pri \
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_sql_private.pri \
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_sql_private.pri \
|
||||||
@@ -328,13 +326,6 @@ Makefile: tiletest.pro /usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-g++/qmake.con
|
|||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/common/g++-base.conf:
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/common/g++-base.conf:
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/common/g++-unix.conf:
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/common/g++-unix.conf:
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/qconfig.pri:
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/qconfig.pri:
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KConfigCore.pri:
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KConfigGui.pri:
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KCoreAddons.pri:
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KDBusAddons.pri:
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KI18n.pri:
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KService.pri:
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_KWindowSystem.pri:
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_accessibility_support_private.pri:
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_accessibility_support_private.pri:
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_bootstrap_private.pri:
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_bootstrap_private.pri:
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_concurrent.pri:
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_concurrent.pri:
|
||||||
@@ -366,8 +357,6 @@ Makefile: tiletest.pro /usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-g++/qmake.con
|
|||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_platformcompositor_support_private.pri:
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_platformcompositor_support_private.pri:
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_printsupport.pri:
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_printsupport.pri:
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_printsupport_private.pri:
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_printsupport_private.pri:
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_script.pri:
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_scripttools.pri:
|
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_service_support_private.pri:
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_service_support_private.pri:
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_sql.pri:
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_sql.pri:
|
||||||
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_sql_private.pri:
|
/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/qt_lib_sql_private.pri:
|
||||||
@@ -424,8 +413,8 @@ distdir: FORCE
|
|||||||
$(COPY_FILE) --parents $(DIST) $(DISTDIR)/
|
$(COPY_FILE) --parents $(DIST) $(DISTDIR)/
|
||||||
$(COPY_FILE) --parents shaders.qrc $(DISTDIR)/
|
$(COPY_FILE) --parents shaders.qrc $(DISTDIR)/
|
||||||
$(COPY_FILE) --parents /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/data/dummy.cpp $(DISTDIR)/
|
$(COPY_FILE) --parents /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/data/dummy.cpp $(DISTDIR)/
|
||||||
$(COPY_FILE) --parents mainwindow.h qscenerenderer.h vertex.h tile.h terrain.h tilevertexinfo.h matrix2d.h face.h player.h yamlparser.h unitconfig.h pathfinder.h unit.h city.h terrainyieldmanager.h gamemanager.h unitrenderer.h localization.h terrainmovementmanager.h $(DISTDIR)/
|
$(COPY_FILE) --parents mainwindow.h qscenerenderer.h vertex.h tile.h terrain.h tilevertexinfo.h matrix2d.h face.h player.h yamlparser.h unitconfig.h buildingconfig.h pathfinder.h unit.h city.h terrainyieldmanager.h gamemanager.h unitrenderer.h localization.h terrainmovementmanager.h roadconfig.h actionneededmanager.h techconfig.h $(DISTDIR)/
|
||||||
$(COPY_FILE) --parents main.cpp mainwindow.cpp qscenerenderer.cpp vertex.cpp tile.cpp tilevertexinfo.cpp face.cpp player.cpp yamlparser.cpp unitconfig.cpp pathfinder.cpp unit.cpp city.cpp terrainyieldmanager.cpp gamemanager.cpp unitrenderer.cpp localization.cpp terrainmovementmanager.cpp $(DISTDIR)/
|
$(COPY_FILE) --parents main.cpp mainwindow.cpp qscenerenderer.cpp vertex.cpp tile.cpp tilevertexinfo.cpp face.cpp player.cpp yamlparser.cpp unitconfig.cpp buildingconfig.cpp pathfinder.cpp unit.cpp city.cpp terrainyieldmanager.cpp gamemanager.cpp unitrenderer.cpp localization.cpp terrainmovementmanager.cpp roadconfig.cpp actionneededmanager.cpp techconfig.cpp $(DISTDIR)/
|
||||||
$(COPY_FILE) --parents mainwindow.ui $(DISTDIR)/
|
$(COPY_FILE) --parents mainwindow.ui $(DISTDIR)/
|
||||||
|
|
||||||
|
|
||||||
@@ -471,7 +460,7 @@ compiler_moc_header_clean:
|
|||||||
moc_mainwindow.cpp: mainwindow.h \
|
moc_mainwindow.cpp: mainwindow.h \
|
||||||
moc_predefs.h \
|
moc_predefs.h \
|
||||||
/usr/lib/qt5/bin/moc
|
/usr/lib/qt5/bin/moc
|
||||||
/usr/lib/qt5/bin/moc $(DEFINES) --include /home/bart/project/tiletest/moc_predefs.h -I/usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-g++ -I/home/bart/project/tiletest -I/usr/include/x86_64-linux-gnu/qt5 -I/usr/include/x86_64-linux-gnu/qt5/QtWidgets -I/usr/include/x86_64-linux-gnu/qt5/QtGui -I/usr/include/x86_64-linux-gnu/qt5/QtCore -I/usr/include/c++/13 -I/usr/include/x86_64-linux-gnu/c++/13 -I/usr/include/c++/13/backward -I/usr/lib/gcc/x86_64-linux-gnu/13/include -I/usr/local/include -I/usr/include/x86_64-linux-gnu -I/usr/include mainwindow.h -o moc_mainwindow.cpp
|
/usr/lib/qt5/bin/moc $(DEFINES) --include /home/bart/git/Ascent-of-Man/moc_predefs.h -I/usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-g++ -I/home/bart/git/Ascent-of-Man -I/usr/include/x86_64-linux-gnu/qt5 -I/usr/include/x86_64-linux-gnu/qt5/QtWidgets -I/usr/include/x86_64-linux-gnu/qt5/QtGui -I/usr/include/x86_64-linux-gnu/qt5/QtCore -I/usr/include/c++/13 -I/usr/include/x86_64-linux-gnu/c++/13 -I/usr/include/c++/13/backward -I/usr/lib/gcc/x86_64-linux-gnu/13/include -I/usr/local/include -I/usr/include/x86_64-linux-gnu -I/usr/include mainwindow.h -o moc_mainwindow.cpp
|
||||||
|
|
||||||
compiler_moc_objc_header_make_all:
|
compiler_moc_objc_header_make_all:
|
||||||
compiler_moc_objc_header_clean:
|
compiler_moc_objc_header_clean:
|
||||||
@@ -495,8 +484,12 @@ ui_mainwindow.h: mainwindow.ui \
|
|||||||
terrainmovementmanager.h \
|
terrainmovementmanager.h \
|
||||||
player.h \
|
player.h \
|
||||||
city.h \
|
city.h \
|
||||||
|
roadconfig.h \
|
||||||
gamemanager.h \
|
gamemanager.h \
|
||||||
terrainyieldmanager.h
|
buildingconfig.h \
|
||||||
|
terrainyieldmanager.h \
|
||||||
|
actionneededmanager.h \
|
||||||
|
techconfig.h
|
||||||
/usr/lib/qt5/bin/uic mainwindow.ui -o ui_mainwindow.h
|
/usr/lib/qt5/bin/uic mainwindow.ui -o ui_mainwindow.h
|
||||||
|
|
||||||
compiler_yacc_decl_make_all:
|
compiler_yacc_decl_make_all:
|
||||||
@@ -513,7 +506,26 @@ main.o: main.cpp mainwindow.h
|
|||||||
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o main.o main.cpp
|
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o main.o main.cpp
|
||||||
|
|
||||||
mainwindow.o: mainwindow.cpp mainwindow.h \
|
mainwindow.o: mainwindow.cpp mainwindow.h \
|
||||||
ui_mainwindow.h
|
ui_mainwindow.h \
|
||||||
|
qscenerenderer.h \
|
||||||
|
terrain.h \
|
||||||
|
tile.h \
|
||||||
|
vertex.h \
|
||||||
|
face.h \
|
||||||
|
tilevertexinfo.h \
|
||||||
|
matrix2d.h \
|
||||||
|
unitrenderer.h \
|
||||||
|
unit.h \
|
||||||
|
unitconfig.h \
|
||||||
|
terrainmovementmanager.h \
|
||||||
|
player.h \
|
||||||
|
city.h \
|
||||||
|
roadconfig.h \
|
||||||
|
gamemanager.h \
|
||||||
|
buildingconfig.h \
|
||||||
|
terrainyieldmanager.h \
|
||||||
|
actionneededmanager.h \
|
||||||
|
techconfig.h
|
||||||
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o mainwindow.o mainwindow.cpp
|
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o mainwindow.o mainwindow.cpp
|
||||||
|
|
||||||
qscenerenderer.o: qscenerenderer.cpp qscenerenderer.h \
|
qscenerenderer.o: qscenerenderer.cpp qscenerenderer.h \
|
||||||
@@ -529,10 +541,15 @@ qscenerenderer.o: qscenerenderer.cpp qscenerenderer.h \
|
|||||||
terrainmovementmanager.h \
|
terrainmovementmanager.h \
|
||||||
player.h \
|
player.h \
|
||||||
city.h \
|
city.h \
|
||||||
|
roadconfig.h \
|
||||||
gamemanager.h \
|
gamemanager.h \
|
||||||
|
buildingconfig.h \
|
||||||
terrainyieldmanager.h \
|
terrainyieldmanager.h \
|
||||||
|
actionneededmanager.h \
|
||||||
|
techconfig.h \
|
||||||
pathfinder.h \
|
pathfinder.h \
|
||||||
yamlparser.h
|
yamlparser.h \
|
||||||
|
localization.h
|
||||||
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o qscenerenderer.o qscenerenderer.cpp
|
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o qscenerenderer.o qscenerenderer.cpp
|
||||||
|
|
||||||
vertex.o: vertex.cpp vertex.h \
|
vertex.o: vertex.cpp vertex.h \
|
||||||
@@ -553,8 +570,12 @@ tile.o: tile.cpp tile.h \
|
|||||||
terrainmovementmanager.h \
|
terrainmovementmanager.h \
|
||||||
player.h \
|
player.h \
|
||||||
city.h \
|
city.h \
|
||||||
|
roadconfig.h \
|
||||||
gamemanager.h \
|
gamemanager.h \
|
||||||
terrainyieldmanager.h
|
buildingconfig.h \
|
||||||
|
terrainyieldmanager.h \
|
||||||
|
actionneededmanager.h \
|
||||||
|
techconfig.h
|
||||||
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o tile.o tile.cpp
|
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o tile.o tile.cpp
|
||||||
|
|
||||||
tilevertexinfo.o: tilevertexinfo.cpp tilevertexinfo.h \
|
tilevertexinfo.o: tilevertexinfo.cpp tilevertexinfo.h \
|
||||||
@@ -580,6 +601,10 @@ unitconfig.o: unitconfig.cpp unitconfig.h \
|
|||||||
yamlparser.h
|
yamlparser.h
|
||||||
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o unitconfig.o unitconfig.cpp
|
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o unitconfig.o unitconfig.cpp
|
||||||
|
|
||||||
|
buildingconfig.o: buildingconfig.cpp buildingconfig.h \
|
||||||
|
yamlparser.h
|
||||||
|
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o buildingconfig.o buildingconfig.cpp
|
||||||
|
|
||||||
pathfinder.o: pathfinder.cpp pathfinder.h \
|
pathfinder.o: pathfinder.cpp pathfinder.h \
|
||||||
terrain.h \
|
terrain.h \
|
||||||
matrix2d.h \
|
matrix2d.h \
|
||||||
@@ -592,7 +617,8 @@ unit.o: unit.cpp unit.h \
|
|||||||
terrainmovementmanager.h \
|
terrainmovementmanager.h \
|
||||||
terrain.h \
|
terrain.h \
|
||||||
matrix2d.h \
|
matrix2d.h \
|
||||||
pathfinder.h
|
pathfinder.h \
|
||||||
|
localization.h
|
||||||
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o unit.o unit.cpp
|
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o unit.o unit.cpp
|
||||||
|
|
||||||
city.o: city.cpp city.h \
|
city.o: city.cpp city.h \
|
||||||
@@ -613,9 +639,13 @@ gamemanager.o: gamemanager.cpp gamemanager.h \
|
|||||||
terrainmovementmanager.h \
|
terrainmovementmanager.h \
|
||||||
terrain.h \
|
terrain.h \
|
||||||
matrix2d.h \
|
matrix2d.h \
|
||||||
|
buildingconfig.h \
|
||||||
|
roadconfig.h \
|
||||||
unit.h \
|
unit.h \
|
||||||
city.h \
|
city.h \
|
||||||
terrainyieldmanager.h \
|
terrainyieldmanager.h \
|
||||||
|
actionneededmanager.h \
|
||||||
|
techconfig.h \
|
||||||
pathfinder.h \
|
pathfinder.h \
|
||||||
yamlparser.h \
|
yamlparser.h \
|
||||||
localization.h
|
localization.h
|
||||||
@@ -629,6 +659,7 @@ unitrenderer.o: unitrenderer.cpp unitrenderer.h \
|
|||||||
matrix2d.h \
|
matrix2d.h \
|
||||||
player.h \
|
player.h \
|
||||||
city.h \
|
city.h \
|
||||||
|
roadconfig.h \
|
||||||
pathfinder.h
|
pathfinder.h
|
||||||
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o unitrenderer.o unitrenderer.cpp
|
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o unitrenderer.o unitrenderer.cpp
|
||||||
|
|
||||||
@@ -641,9 +672,36 @@ terrainmovementmanager.o: terrainmovementmanager.cpp terrainmovementmanager.h \
|
|||||||
matrix2d.h \
|
matrix2d.h \
|
||||||
unitconfig.h \
|
unitconfig.h \
|
||||||
pathfinder.h \
|
pathfinder.h \
|
||||||
yamlparser.h
|
yamlparser.h \
|
||||||
|
gamemanager.h \
|
||||||
|
player.h \
|
||||||
|
buildingconfig.h \
|
||||||
|
roadconfig.h \
|
||||||
|
unit.h \
|
||||||
|
city.h \
|
||||||
|
terrainyieldmanager.h \
|
||||||
|
actionneededmanager.h \
|
||||||
|
techconfig.h
|
||||||
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o terrainmovementmanager.o terrainmovementmanager.cpp
|
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o terrainmovementmanager.o terrainmovementmanager.cpp
|
||||||
|
|
||||||
|
roadconfig.o: roadconfig.cpp roadconfig.h \
|
||||||
|
yamlparser.h
|
||||||
|
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o roadconfig.o roadconfig.cpp
|
||||||
|
|
||||||
|
actionneededmanager.o: actionneededmanager.cpp actionneededmanager.h \
|
||||||
|
yamlparser.h \
|
||||||
|
unit.h \
|
||||||
|
unitconfig.h \
|
||||||
|
terrainmovementmanager.h \
|
||||||
|
terrain.h \
|
||||||
|
matrix2d.h \
|
||||||
|
city.h
|
||||||
|
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o actionneededmanager.o actionneededmanager.cpp
|
||||||
|
|
||||||
|
techconfig.o: techconfig.cpp techconfig.h \
|
||||||
|
yamlparser.h
|
||||||
|
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o techconfig.o techconfig.cpp
|
||||||
|
|
||||||
qrc_shaders.o: qrc_shaders.cpp
|
qrc_shaders.o: qrc_shaders.cpp
|
||||||
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o qrc_shaders.o qrc_shaders.cpp
|
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o qrc_shaders.o qrc_shaders.cpp
|
||||||
|
|
||||||
|
|||||||
+29
-1
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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("") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
+77
-23
@@ -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... (%1 ticks)"
|
||||||
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"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
# Classification thresholds
|
# Classification thresholds
|
||||||
water_height_threshold: 6 # Raw heightmap elevation value (< 6 is water)
|
water_height_threshold: 6 # Raw heightmap elevation value (< 6 is water)
|
||||||
|
coast_beach_width_km: 0.022 # Width of shoreline sandy beach transition above water level (in km)
|
||||||
mountain_rock_height_km: 2.3 # Height in km >= 2.3 is rocky mountain
|
mountain_rock_height_km: 2.3 # Height in km >= 2.3 is rocky mountain
|
||||||
snow_height_km: 3.1 # Altitude in km >= 3.1 is high peak snow
|
snow_height_km: 3.1 # Altitude in km >= 3.1 is high peak snow
|
||||||
polar_snow_line_enabled: true # Whether latitude reduces snowline altitude
|
polar_snow_line_enabled: true # Whether latitude reduces snowline altitude
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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
|
||||||
|
recharge_ticks: 2
|
||||||
|
attack_flash_ticks: 1
|
||||||
|
actions:
|
||||||
|
- verplaatsen
|
||||||
|
- aanvallen
|
||||||
@@ -6,13 +6,17 @@ 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:
|
||||||
- verplaatsen
|
- verplaatsen
|
||||||
- boerderij_bouwen
|
- boerderij_bouwen
|
||||||
- build_road
|
- build_road
|
||||||
action_speeds:
|
recharge_ticks: 0
|
||||||
boerderij_bouwen: 10.0 # Duur in seconden die deze builder unit nodig heeft voor de actie boerderij_bouwen
|
attack_flash_ticks: 0
|
||||||
build_road: 2 # Duur in ticks
|
action_durations:
|
||||||
|
boerderij_bouwen: 2.0
|
||||||
|
build_road: 2.0
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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
|
||||||
|
recharge_ticks: 2
|
||||||
|
attack_flash_ticks: 1
|
||||||
|
actions:
|
||||||
|
- verplaatsen
|
||||||
|
- aanvallen
|
||||||
@@ -8,7 +8,11 @@ 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
|
||||||
|
recharge_ticks: 2
|
||||||
|
attack_flash_ticks: 1
|
||||||
actions:
|
actions:
|
||||||
- verplaatsen
|
- verplaatsen
|
||||||
- aanvallen
|
- aanvallen
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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
|
||||||
|
recharge_ticks: 2
|
||||||
|
attack_flash_ticks: 1
|
||||||
|
actions:
|
||||||
|
- verplaatsen
|
||||||
|
- aanvallen
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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
|
||||||
|
recharge_ticks: 2
|
||||||
|
attack_flash_ticks: 1
|
||||||
|
actions:
|
||||||
|
- verplaatsen
|
||||||
|
- aanvallen
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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
|
||||||
|
recharge_ticks: 2
|
||||||
|
attack_flash_ticks: 1
|
||||||
|
actions:
|
||||||
|
- verplaatsen
|
||||||
|
- aanvallen
|
||||||
@@ -6,7 +6,11 @@ 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
|
||||||
|
recharge_ticks: 0
|
||||||
|
attack_flash_ticks: 0
|
||||||
actions:
|
actions:
|
||||||
- verplaatsen
|
- verplaatsen
|
||||||
- steden_bouwen
|
- steden_bouwen
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ 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
|
||||||
|
recharge_ticks: 2
|
||||||
|
attack_flash_ticks: 1
|
||||||
actions:
|
actions:
|
||||||
- verplaatsen
|
- verplaatsen
|
||||||
- aanvallen
|
- aanvallen
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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
|
||||||
|
recharge_ticks: 2
|
||||||
|
attack_flash_ticks: 1
|
||||||
|
actions:
|
||||||
|
- verplaatsen
|
||||||
|
- aanvallen
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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
|
||||||
|
recharge_ticks: 2
|
||||||
|
attack_flash_ticks: 1
|
||||||
|
actions:
|
||||||
|
- verplaatsen
|
||||||
|
- aanvallen
|
||||||
@@ -8,7 +8,11 @@ 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
|
||||||
|
recharge_ticks: 2
|
||||||
|
attack_flash_ticks: 1
|
||||||
actions:
|
actions:
|
||||||
- verplaatsen
|
- verplaatsen
|
||||||
- aanvallen
|
- aanvallen
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
# Military Unit Recharge System Implementation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This implementation adds a recharge/cooldown system to military units in the RTS game engine, providing visual feedback when units cannot attack due to recent attacks or damage taken. The recharge bar is displayed below the unit's health bar.
|
||||||
|
|
||||||
|
## Features Implemented
|
||||||
|
|
||||||
|
### 1. **Recharge Timer System**
|
||||||
|
- Military units now have a configurable recharge time between attacks
|
||||||
|
- Recharge timer decrements over time, preventing immediate re-attack after each hit
|
||||||
|
- Units are automatically reset to idle state when recharge completes
|
||||||
|
|
||||||
|
### 2. **Visual Feedback - Recharge Bar**
|
||||||
|
- A green bar is displayed below military unit cylinders during recharge cooldown
|
||||||
|
- Bar fill percentage indicates remaining recharge time (0% = ready to attack, 100% = just attacked)
|
||||||
|
- Only military units that can attack (`config.canAttack()`) are shown with recharge bars
|
||||||
|
- Non-military units (settlement, builders) have the fields but won't display bars
|
||||||
|
|
||||||
|
### 3. **Attack Flash Enhancement**
|
||||||
|
- Attack flash timer now uses configurable duration from config
|
||||||
|
- Flash effect provides immediate visual confirmation of successful attacks
|
||||||
|
- Color transitions smoothly from red/white back to unit base color
|
||||||
|
|
||||||
|
## Technical Implementation
|
||||||
|
|
||||||
|
### Modified Files
|
||||||
|
|
||||||
|
#### `unit.h` / `unit.cpp`
|
||||||
|
Added new member variables:
|
||||||
|
```cpp
|
||||||
|
float attackFlashTimer; // Flash effect timer (seconds)
|
||||||
|
float rechargeTimer; // Recharge/cooldown timer (seconds)
|
||||||
|
bool isRecharging(); // Checks if unit is currently on cooldown
|
||||||
|
```
|
||||||
|
|
||||||
|
Modified methods:
|
||||||
|
- `canAttack()`: Now checks recharge timer before allowing attack
|
||||||
|
- `performAttack()`: Sets rechargeTimer when attack is performed
|
||||||
|
- `takeDamage()`: Resets rechargeTimer when unit takes damage
|
||||||
|
- `update()`: Decrements both flash and recharge timers each frame
|
||||||
|
|
||||||
|
#### `unitconfig.h` / `unit.cpp`
|
||||||
|
Added configuration fields:
|
||||||
|
```cpp
|
||||||
|
int rechargeTime; // Time in milliseconds between attacks
|
||||||
|
float rechargeDuration; // Visual cooldown bar duration
|
||||||
|
int attackFlashDuration; // How long the flash effect lasts (ms)
|
||||||
|
```
|
||||||
|
|
||||||
|
Default values:
|
||||||
|
- `recharge_time`: 500ms (0.5 seconds)
|
||||||
|
- `recharge_duration`: 300ms
|
||||||
|
- `attack_flash_duration`: 400ms
|
||||||
|
|
||||||
|
#### `unitrenderer.h` / `unitrenderer.cpp`
|
||||||
|
Added recharge bar rendering:
|
||||||
|
- New mesh buffers for flat quad-based recharge bars
|
||||||
|
- Trapezoidal bar geometry showing remaining recharge percentage
|
||||||
|
- Positioned below unit cylinder with small gap for visual separation
|
||||||
|
- Only rendered for military units currently recharging
|
||||||
|
|
||||||
|
#### All Unit YAML Configs (`config/units/*.yaml`)
|
||||||
|
Added recharge configuration to all 12 unit types:
|
||||||
|
```yaml
|
||||||
|
recharge_time: 500
|
||||||
|
recharge_duration: 300
|
||||||
|
attack_flash_duration: 400
|
||||||
|
```
|
||||||
|
|
||||||
|
## Game Mechanics
|
||||||
|
|
||||||
|
### Attack Cooldown Flow
|
||||||
|
1. Unit performs attack → `attackFlashTimer` = 0.4s, `rechargeTimer` = 0.5s
|
||||||
|
2. Visual flash effect plays (red/white transition)
|
||||||
|
3. Recharge bar appears below unit showing remaining cooldown
|
||||||
|
4. Both timers decrement each frame
|
||||||
|
5. When `rechargeTimer` reaches 0, unit can attack again
|
||||||
|
|
||||||
|
### Damage Taken Flow
|
||||||
|
1. Unit receives damage → Health decreased
|
||||||
|
2. `attackFlashTimer` set to 0.4s (damage flash effect)
|
||||||
|
3. `rechargeTimer` reset to full recharge time
|
||||||
|
4. Unit must wait through recharge before attacking
|
||||||
|
|
||||||
|
### Visual Hierarchy (from bottom to top)
|
||||||
|
1. Ground level
|
||||||
|
2. **Recharge bar** (below unit, if applicable)
|
||||||
|
3. Unit cylinder mesh
|
||||||
|
4. Health bar (if implemented later)
|
||||||
|
5. Selection ring (when selected)
|
||||||
|
|
||||||
|
## Configuration Examples
|
||||||
|
|
||||||
|
### Default Recharge Settings (All Units)
|
||||||
|
```yaml
|
||||||
|
recharge_time: 500 # 0.5 seconds between attacks
|
||||||
|
recharge_duration: 300 # Visual feedback duration
|
||||||
|
attack_flash_duration: 400 # Flash effect time
|
||||||
|
```
|
||||||
|
|
||||||
|
### Faster Recharge (Rapid Attack Units)
|
||||||
|
```yaml
|
||||||
|
recharge_time: 300 # Quicker cooldown for high-damage units
|
||||||
|
attack_flash_duration: 350
|
||||||
|
```
|
||||||
|
|
||||||
|
### Slower Recharge (Heavy Infantry/Armor)
|
||||||
|
```yaml
|
||||||
|
recharge_time: 700 # Longer cooldown for powerful units
|
||||||
|
attack_flash_duration: 500
|
||||||
|
```
|
||||||
|
|
||||||
|
## Design Rationale
|
||||||
|
|
||||||
|
1. **Modular Configuration**: All recharge parameters are YAML-configurable per the AGENTS.md guidelines
|
||||||
|
2. **Visual Feedback**: Recharge bar provides clear indication of when units can attack again
|
||||||
|
3. **Performance**: Single mesh buffer reused for all recharge bars (efficient batch rendering)
|
||||||
|
4. **Backwards Compatible**: Non-military units have fields but no functional impact
|
||||||
|
5. **Consistent API**: `isRecharging()` method used by both renderer and game logic
|
||||||
|
|
||||||
|
## Future Enhancement Possibilities
|
||||||
|
|
||||||
|
- Add configurable recharge times per tech tree node
|
||||||
|
- Implement different recharge rates for melee vs ranged attacks
|
||||||
|
- Add area-of-effect cooldown reduction mechanics
|
||||||
|
- Implement weapon-specific reload animations
|
||||||
|
- Display numerical cooldown timer on hover tooltip
|
||||||
|
|
||||||
|
---
|
||||||
|
*Implementation Date: 2026-11-10*
|
||||||
|
*See AGENTS.md Section 4.2 for Unit Architecture Reference*
|
||||||
+974
-436
File diff suppressed because it is too large
Load Diff
+35
-3
@@ -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
|
||||||
|
|||||||
Binary file not shown.
+18
-7
@@ -56,7 +56,7 @@ def trace_river(start_tile, edge_steps):
|
|||||||
path.append((curr, in_edge, None))
|
path.append((curr, in_edge, None))
|
||||||
return path
|
return path
|
||||||
|
|
||||||
def build_rivermap(output_path="/home/bart/project/tiletest/images/rivermap.png", preview_path="/home/bart/project/tiletest/images/rivermap_preview.png"):
|
def build_rivermap(output_path="/home/bart/git/Ascent-of-Man/images/rivermap.png", preview_path="/home/bart/git/Ascent-of-Man/images/rivermap_preview.png"):
|
||||||
river_img = Image.new('RGB', (WIDTH, HEIGHT), (0, 0, 0))
|
river_img = Image.new('RGB', (WIDTH, HEIGHT), (0, 0, 0))
|
||||||
draw = ImageDraw.Draw(river_img)
|
draw = ImageDraw.Draw(river_img)
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ def build_rivermap(output_path="/home/bart/project/tiletest/images/rivermap.png"
|
|||||||
|
|
||||||
# Build an overlay preview on top of typemap.png
|
# Build an overlay preview on top of typemap.png
|
||||||
try:
|
try:
|
||||||
type_img = Image.open("/home/bart/project/tiletest/images/typemap.png").convert("RGBA")
|
type_img = Image.open("/home/bart/git/Ascent-of-Man/images/typemap.png").convert("RGBA")
|
||||||
r_arr = np.array(river_img)
|
r_arr = np.array(river_img)
|
||||||
t_arr = np.array(type_img)
|
t_arr = np.array(type_img)
|
||||||
mask = (r_arr[:, :, 2] > 120) & (r_arr[:, :, 0] < 100)
|
mask = (r_arr[:, :, 2] > 120) & (r_arr[:, :, 0] < 100)
|
||||||
@@ -103,7 +103,18 @@ def build_rivermap(output_path="/home/bart/project/tiletest/images/rivermap.png"
|
|||||||
print(f"Saved preview to {preview_path}")
|
print(f"Saved preview to {preview_path}")
|
||||||
|
|
||||||
# Build orthographic hex-tile minimap.png
|
# Build orthographic hex-tile minimap.png
|
||||||
h_arr = np.array(Image.open("/home/bart/project/tiletest/images/heightmap.jpg"))
|
water_threshold = 6
|
||||||
|
try:
|
||||||
|
with open("/home/bart/git/Ascent-of-Man/config/terrain_movement.yaml", "r") as yf:
|
||||||
|
for line in yf:
|
||||||
|
if line.strip().startswith("water_height_threshold:"):
|
||||||
|
val_str = line.split(":", 1)[1].split("#", 1)[0].strip()
|
||||||
|
water_threshold = int(val_str)
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
h_arr = np.array(Image.open("/home/bart/git/Ascent-of-Man/images/heightmap.jpg"))
|
||||||
R = 8.0
|
R = 8.0
|
||||||
W = 3608
|
W = 3608
|
||||||
H = 1740
|
H = 1740
|
||||||
@@ -122,11 +133,11 @@ def build_rivermap(output_path="/home/bart/project/tiletest/images/rivermap.png"
|
|||||||
|
|
||||||
px = min(3599, max(0, int(round(wx * SCALE_X))))
|
px = min(3599, max(0, int(round(wx * SCALE_X))))
|
||||||
py = min(1499, max(0, int(round(wy * SCALE_Y))))
|
py = min(1499, max(0, int(round(wy * SCALE_Y))))
|
||||||
h = h_arr[1500 - 1 - py, px]
|
h = int(h_arr[1500 - 1 - py, px, 0]) if h_arr.ndim == 3 else int(h_arr[1500 - 1 - py, px])
|
||||||
tp = t_arr[1500 - 1 - py, px]
|
tp = t_arr[1500 - 1 - py, px]
|
||||||
dist_pole = abs((wy / 108.22) - 0.5) * 2.0
|
dist_pole = abs((wy / 108.22) - 0.5) * 2.0
|
||||||
|
|
||||||
if h <= 2:
|
if h < water_threshold:
|
||||||
color = (220, 235, 250) if dist_pole > 0.85 else (28, 95, 175) # Water (blue) / polar ice
|
color = (220, 235, 250) if dist_pole > 0.85 else (28, 95, 175) # Water (blue) / polar ice
|
||||||
elif h > 182 or (dist_pole > 0.80 and h > 20):
|
elif h > 182 or (dist_pole > 0.80 and h > 20):
|
||||||
color = (245, 250, 255) # Snow (white) above 2500m
|
color = (245, 250, 255) # Snow (white) above 2500m
|
||||||
@@ -169,8 +180,8 @@ def build_rivermap(output_path="/home/bart/project/tiletest/images/rivermap.png"
|
|||||||
hdraw.line([(mpx, mpy), (cx, cy)], fill=(0, 220, 255), width=3)
|
hdraw.line([(mpx, mpy), (cx, cy)], fill=(0, 220, 255), width=3)
|
||||||
hdraw.ellipse([mpx - 2, mpy - 2, mpx + 2, mpy + 2], fill=(0, 240, 255))
|
hdraw.ellipse([mpx - 2, mpy - 2, mpx + 2, mpy + 2], fill=(0, 240, 255))
|
||||||
|
|
||||||
hex_img.save("/home/bart/project/tiletest/images/minimap.png")
|
hex_img.save("/home/bart/git/Ascent-of-Man/images/minimap.png")
|
||||||
print("Saved orthographic hex minimap to /home/bart/project/tiletest/images/minimap.png")
|
print("Saved orthographic hex minimap to /home/bart/git/Ascent-of-Man/images/minimap.png")
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print(f"Minimap/preview generation skipped: {ex}")
|
print(f"Minimap/preview generation skipped: {ex}")
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 117 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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 {
|
||||||
|
|||||||
+1092
-733
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+1059
-260
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
|
|||||||
Binary file not shown.
+16
-4
@@ -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
@@ -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); }
|
||||||
|
|||||||
+155
-16
@@ -17,8 +17,21 @@ 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;
|
||||||
|
uniform float waterLevelKm = 0.08235294;
|
||||||
|
uniform float coastBeachWidthKm = 0.022;
|
||||||
|
|
||||||
smooth in vec3 vNormal;
|
smooth in vec3 vNormal;
|
||||||
smooth in vec3 vWorldPos;
|
smooth in vec3 vWorldPos;
|
||||||
@@ -79,6 +92,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 +160,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 +171,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);
|
||||||
|
|
||||||
@@ -145,8 +234,12 @@ void main() {
|
|||||||
// Snowline in kilometers: 2.5 (2500m) in temperate zone, dropping to 0.0 (sea level) at poles!
|
// Snowline in kilometers: 2.5 (2500m) in temperate zone, dropping to 0.0 (sea level) at poles!
|
||||||
float snowLine = mix(2.5, 0.0, poleFactor);
|
float snowLine = mix(2.5, 0.0, poleFactor);
|
||||||
|
|
||||||
// 3. Water vs Land detection (z_pos = height in kilometers, 0.0 = sea level)
|
// 3. Water vs Land detection (z_pos = height in kilometers; water is height < water_height_threshold)
|
||||||
float oceanWaterFac = 1.0 - smoothstep(0.005, 0.025, z_pos);
|
// One heightmap step is 3.5 / 255.0 = 0.0137255 km, so height = threshold - 1 is at waterLevelKm - 0.0137255.
|
||||||
|
// All water hexes (height <= threshold - 1) remain 100% water and transition smoothly across the shoreline.
|
||||||
|
float waterEdgeLow = max(0.0, waterLevelKm - 0.0135);
|
||||||
|
float waterEdgeHigh = max(waterEdgeLow + 0.002, waterLevelKm - 0.0015);
|
||||||
|
float oceanWaterFac = 1.0 - smoothstep(waterEdgeLow, waterEdgeHigh, z_pos);
|
||||||
|
|
||||||
// River water detection
|
// River water detection
|
||||||
float riverDist = getRiverDistance(fragVert.xy, riverMask);
|
float riverDist = getRiverDistance(fragVert.xy, riverMask);
|
||||||
@@ -234,10 +327,13 @@ void main() {
|
|||||||
biomeGround = farmColor;
|
biomeGround = farmColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shoreline beach: where gentle slopes meet water (sea level up to ~110m, or along rivers)
|
// Shoreline beach: where gentle slopes meet water right along the waterline (or along rivers)
|
||||||
// Steep slopes (norm.z < 0.55) remain rocky cliffs; gentle slopes (norm.z > 0.80) get a sandy beach
|
// Steep slopes (norm.z < 0.55) remain rocky cliffs; gentle slopes (norm.z > 0.80) get a sandy beach
|
||||||
float gentleSlope = smoothstep(0.55, 0.85, norm.z);
|
float gentleSlope = smoothstep(0.55, 0.85, norm.z);
|
||||||
float coastBeach = (1.0 - smoothstep(0.015, 0.110, z_pos)) * smoothstep(0.003, 0.015, z_pos) * gentleSlope;
|
float beachStart = max(0.0, waterLevelKm - 0.015);
|
||||||
|
float beachPeak = waterEdgeHigh;
|
||||||
|
float beachEnd = waterLevelKm + max(0.005, coastBeachWidthKm);
|
||||||
|
float coastBeach = (1.0 - smoothstep(beachPeak, beachEnd, z_pos)) * smoothstep(beachStart, beachPeak, z_pos) * gentleSlope;
|
||||||
float riverBeach = riverBankFac;
|
float riverBeach = riverBankFac;
|
||||||
float beachFac = max(coastBeach, riverBeach);
|
float beachFac = max(coastBeach, riverBeach);
|
||||||
|
|
||||||
@@ -276,6 +372,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
@@ -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
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
TerrainMovementConfig::TerrainMovementConfig()
|
TerrainMovementConfig::TerrainMovementConfig()
|
||||||
: waterHeightThreshold(6),
|
: waterHeightThreshold(6),
|
||||||
|
coastBeachWidthKm(0.022f),
|
||||||
mountainRockHeightKm(2.3f),
|
mountainRockHeightKm(2.3f),
|
||||||
snowHeightKm(3.1f),
|
snowHeightKm(3.1f),
|
||||||
polarSnowLineEnabled(true),
|
polarSnowLineEnabled(true),
|
||||||
@@ -61,6 +62,9 @@ bool TerrainMovementManager::loadConfig(const QString &filePath)
|
|||||||
if (root.hasChild("water_height_threshold")) {
|
if (root.hasChild("water_height_threshold")) {
|
||||||
m_config.waterHeightThreshold = root["water_height_threshold"].asInt(6);
|
m_config.waterHeightThreshold = root["water_height_threshold"].asInt(6);
|
||||||
}
|
}
|
||||||
|
if (root.hasChild("coast_beach_width_km")) {
|
||||||
|
m_config.coastBeachWidthKm = root["coast_beach_width_km"].asFloat(0.022f);
|
||||||
|
}
|
||||||
if (root.hasChild("mountain_rock_height_km")) {
|
if (root.hasChild("mountain_rock_height_km")) {
|
||||||
m_config.mountainRockHeightKm = root["mountain_rock_height_km"].asFloat(2.3f);
|
m_config.mountainRockHeightKm = root["mountain_rock_height_km"].asFloat(2.3f);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ struct DomainMovementRule {
|
|||||||
|
|
||||||
struct TerrainMovementConfig {
|
struct TerrainMovementConfig {
|
||||||
int waterHeightThreshold;
|
int waterHeightThreshold;
|
||||||
|
float coastBeachWidthKm;
|
||||||
float mountainRockHeightKm;
|
float mountainRockHeightKm;
|
||||||
float snowHeightKm;
|
float snowHeightKm;
|
||||||
bool polarSnowLineEnabled;
|
bool polarSnowLineEnabled;
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -3,6 +3,7 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include "qscenerenderer.h"
|
#include "qscenerenderer.h"
|
||||||
#include "gamemanager.h"
|
#include "gamemanager.h"
|
||||||
|
#include "terrainmovementmanager.h"
|
||||||
#include <QVector3D>
|
#include <QVector3D>
|
||||||
QImage * Tile::m_image_grass = NULL;
|
QImage * Tile::m_image_grass = NULL;
|
||||||
QOpenGLTexture * Tile::m_texture_grass = NULL;
|
QOpenGLTexture * Tile::m_texture_grass = NULL;
|
||||||
@@ -127,31 +128,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 +248,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 +362,41 @@ 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
const TerrainMovementConfig &tmCfg = TerrainMovementManager::instance().getConfig();
|
||||||
|
float waterLevelKm = (tmCfg.waterHeightThreshold / 255.0f) * 3.5f;
|
||||||
|
m_program->setUniformValue("waterLevelKm", waterLevelKm);
|
||||||
|
m_program->setUniformValue("coastBeachWidthKm", tmCfg.coastBeachWidthKm);
|
||||||
|
|
||||||
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();
|
||||||
|
|||||||
+4
-2
@@ -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
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -1,21 +1,70 @@
|
|||||||
#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),
|
||||||
health((float)cfg.health), maxHealth((float)cfg.health),
|
health((float)cfg.health), maxHealth((float)cfg.health),
|
||||||
buildCharges(cfg.buildCharges),
|
buildCharges(cfg.buildCharges),
|
||||||
state(UnitState::Idle), pathIndex(0), moveProgress(0.0f),
|
state(UnitState::Idle), pathIndex(0), moveProgress(0.0f),
|
||||||
targetTileX(startX), targetTileY(startY), attackFlashTimer(0.0f),
|
targetTileX(startX), targetTileY(startY), attackFlashTimer(0.0f), rechargeTimer(0.0f),
|
||||||
buildProgress(0.0f), buildDuration(0.0f), buildingImprovementId("")
|
buildProgress(0.0f), buildDuration(0.0f), buildingImprovementId(""),
|
||||||
|
movement_tick_duration(30.0f) // Default: 30 seconds per tick at 1x speed
|
||||||
{
|
{
|
||||||
QVector2D w = Pathfinder::hexToWorld(startX, startY);
|
QVector2D w = Pathfinder::hexToWorld(startX, startY);
|
||||||
worldPos = QVector3D(w.x(), w.y(), groundZ);
|
worldPos = QVector3D(w.x(), w.y(), groundZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Unit::isRecharging() const {
|
||||||
|
// Only military units have recharge/cooldown
|
||||||
|
if (!config.canAttack()) return false;
|
||||||
|
return rechargeTimer > 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Unit::canAttack(const Unit &other) const
|
||||||
|
{
|
||||||
|
if (other.playerId == playerId) return false;
|
||||||
|
if (other.isDead() || isDead()) return false;
|
||||||
|
if (!config.canAttack()) return false;
|
||||||
|
|
||||||
|
// Check if unit is currently on cooldown/recharge
|
||||||
|
if (isRecharging()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dist = Pathfinder::hexDistance(tileX, tileY, other.tileX, other.tileY);
|
||||||
|
return dist <= config.attackRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Unit::performAttack(Unit &target)
|
||||||
|
{
|
||||||
|
if (!canAttack(target)) return;
|
||||||
|
|
||||||
|
attackFlashTimer = config.attackFlashTicks * movement_tick_duration; // Convert ticks to seconds
|
||||||
|
rechargeTimer = config.rechargeTicks * movement_tick_duration; // Convert ticks to seconds
|
||||||
|
|
||||||
|
target.takeDamage((float)config.attackDamage);
|
||||||
|
|
||||||
|
// Retaliation if target is adjacent melee military unit
|
||||||
|
if (!target.isDead() && target.config.isMilitary() && !config.isRanged()) {
|
||||||
|
int dist = Pathfinder::hexDistance(tileX, tileY, target.tileX, target.tileY);
|
||||||
|
if (dist <= target.config.attackRange) {
|
||||||
|
takeDamage(target.config.attackDamage * 0.5f); // 50% counterattack damage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Unit::takeDamage(float damage)
|
||||||
|
{
|
||||||
|
health = std::max(0.0f, health - damage);
|
||||||
|
attackFlashTimer = movement_tick_duration; // Flash lasts 1 tick
|
||||||
|
rechargeTimer = config.rechargeTicks * movement_tick_duration; // Reset recharge to full duration
|
||||||
|
}
|
||||||
|
|
||||||
float Unit::getTerrainHeight(float worldX, float worldY, Matrix2D<TerrainPoint> *mapPoints, int numPoints05)
|
float Unit::getTerrainHeight(float worldX, float worldY, Matrix2D<TerrainPoint> *mapPoints, int numPoints05)
|
||||||
{
|
{
|
||||||
if (!mapPoints || numPoints05 <= 0) return 1.0f;
|
if (!mapPoints || numPoints05 <= 0) return 1.0f;
|
||||||
@@ -57,6 +106,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 +161,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 +213,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();
|
||||||
}
|
}
|
||||||
@@ -150,10 +242,18 @@ void Unit::cancelBuilding()
|
|||||||
void Unit::update(float deltaTime, float tickDuration, float simSpeedMultiplier,
|
void Unit::update(float deltaTime, float tickDuration, float simSpeedMultiplier,
|
||||||
Matrix2D<TerrainPoint> *mapPoints, int numPoints05)
|
Matrix2D<TerrainPoint> *mapPoints, int numPoints05)
|
||||||
{
|
{
|
||||||
|
// Update attack flash timer (visual feedback)
|
||||||
if (attackFlashTimer > 0.0f) {
|
if (attackFlashTimer > 0.0f) {
|
||||||
attackFlashTimer = std::max(0.0f, attackFlashTimer - deltaTime);
|
attackFlashTimer = std::max(0.0f, attackFlashTimer - deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update recharge timer (cooldown between attacks)
|
||||||
|
// Recharge decays based on simulation ticks, not real-time multiplier
|
||||||
|
if (rechargeTimer > 0.0f) {
|
||||||
|
float tickDurationSec = deltaTime / movement_tick_duration;
|
||||||
|
rechargeTimer = std::max(0.0f, rechargeTimer - tickDurationSec);
|
||||||
|
}
|
||||||
|
|
||||||
if (state == UnitState::Building) {
|
if (state == UnitState::Building) {
|
||||||
if (buildDuration > 0.001f) {
|
if (buildDuration > 0.001f) {
|
||||||
buildProgress += (deltaTime * simSpeedMultiplier) / buildDuration;
|
buildProgress += (deltaTime * simSpeedMultiplier) / buildDuration;
|
||||||
@@ -215,35 +315,3 @@ void Unit::update(float deltaTime, float tickDuration, float simSpeedMultiplier,
|
|||||||
|
|
||||||
worldPos = QVector3D(curX, curY, curZ);
|
worldPos = QVector3D(curX, curY, curZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Unit::canAttack(const Unit &other) const
|
|
||||||
{
|
|
||||||
if (other.playerId == playerId) return false;
|
|
||||||
if (other.isDead() || isDead()) return false;
|
|
||||||
if (!config.canAttack()) return false;
|
|
||||||
|
|
||||||
int dist = Pathfinder::hexDistance(tileX, tileY, other.tileX, other.tileY);
|
|
||||||
return dist <= config.attackRange;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Unit::performAttack(Unit &target)
|
|
||||||
{
|
|
||||||
if (!canAttack(target)) return;
|
|
||||||
|
|
||||||
attackFlashTimer = 0.5f; // Visual flash duration
|
|
||||||
target.takeDamage((float)config.attackDamage);
|
|
||||||
|
|
||||||
// Retaliation if target is adjacent melee military unit
|
|
||||||
if (!target.isDead() && target.config.isMilitary() && !config.isRanged()) {
|
|
||||||
int dist = Pathfinder::hexDistance(tileX, tileY, target.tileX, target.tileY);
|
|
||||||
if (dist <= target.config.attackRange) {
|
|
||||||
takeDamage(target.config.attackDamage * 0.5f); // 50% counterattack damage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Unit::takeDamage(float damage)
|
|
||||||
{
|
|
||||||
health = std::max(0.0f, health - damage);
|
|
||||||
attackFlashTimer = 0.4f;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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) {}
|
||||||
|
|
||||||
@@ -56,6 +56,9 @@ public:
|
|||||||
float health;
|
float health;
|
||||||
float maxHealth;
|
float maxHealth;
|
||||||
int buildCharges;
|
int buildCharges;
|
||||||
|
|
||||||
|
// Simulation parameters (set during unit creation or via GameManager)
|
||||||
|
float movement_tick_duration; // Seconds per simulation tick
|
||||||
UnitState state;
|
UnitState state;
|
||||||
|
|
||||||
// Movement Path
|
// Movement Path
|
||||||
@@ -65,9 +68,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,17 +92,26 @@ 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
|
||||||
float attackFlashTimer;
|
float attackFlashTimer; // Flash effect timer (visual feedback after attack or taking damage)
|
||||||
|
float rechargeTimer; // Recharge/cooldown timer (time remaining until unit can attack again)
|
||||||
|
bool isRecharging() const; // Returns true if unit is currently on cooldown
|
||||||
|
|
||||||
// Building State
|
// Building State
|
||||||
float buildProgress; // 0.0 to 1.0
|
float buildProgress; // 0.0 to 1.0
|
||||||
|
|||||||
+4
-2
@@ -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")) {
|
||||||
@@ -80,11 +82,11 @@ bool UnitConfigManager::loadConfigFile(const QString &filePath)
|
|||||||
if (root.hasChild(key)) {
|
if (root.hasChild(key)) {
|
||||||
const YamlNode &sNode = root[key];
|
const YamlNode &sNode = root[key];
|
||||||
for (auto it = sNode.children.begin(); it != sNode.children.end(); ++it) {
|
for (auto it = sNode.children.begin(); it != sNode.children.end(); ++it) {
|
||||||
cfg.actionSpeeds[it.key()] = it.value().asFloat(10.0f);
|
cfg.actionSpeeds[it.key()] = it.value().asFloat(2.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
parseActionMap("action_speeds");
|
parseActionMap("action_durations");
|
||||||
parseActionMap("action_durations");
|
parseActionMap("action_durations");
|
||||||
|
|
||||||
m_configs[cfg.id] = cfg;
|
m_configs[cfg.id] = cfg;
|
||||||
|
|||||||
+13
-4
@@ -44,17 +44,26 @@ 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 simulation ticks per action (e.g. "boerderij_bouwen": 2.0f)
|
||||||
QStringList impassableTerrains;
|
QStringList impassableTerrains;
|
||||||
QStringList passableTerrains;
|
QStringList passableTerrains;
|
||||||
|
|
||||||
|
// Recharge / Cooldown configuration for military units (in simulation ticks)
|
||||||
|
int rechargeTicks; // Ticks between attacks for military units
|
||||||
|
int attackFlashTicks; // Ticks for visual attack flash effect
|
||||||
|
|
||||||
UnitConfig()
|
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(""),
|
||||||
|
rechargeTicks(2), // Default recharge: 2 simulation ticks
|
||||||
|
attackFlashTicks(1) // Attack flash effect: 1 tick
|
||||||
|
{}
|
||||||
|
|
||||||
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; }
|
||||||
@@ -66,11 +75,11 @@ struct UnitConfig {
|
|||||||
bool canBuildRoad() const { return actions.contains("build_road") || actions.contains("weg_bouwen"); }
|
bool canBuildRoad() const { return actions.contains("build_road") || actions.contains("weg_bouwen"); }
|
||||||
bool isBuilder() const { return canBuildFarm() || canBuildRoad(); }
|
bool isBuilder() const { return canBuildFarm() || canBuildRoad(); }
|
||||||
|
|
||||||
float getActionDuration(const QString &actionName, float fallback = 10.0f) const {
|
float getActionDuration(const QString &actionName, float fallbackTicks = 2.0f) const {
|
||||||
if (actionSpeeds.contains(actionName)) {
|
if (actionSpeeds.contains(actionName)) {
|
||||||
return actionSpeeds.value(actionName);
|
return actionSpeeds.value(actionName);
|
||||||
}
|
}
|
||||||
return fallback;
|
return fallbackTicks;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
+8
-9
@@ -9,17 +9,15 @@ struct VertexPN {
|
|||||||
};
|
};
|
||||||
|
|
||||||
UnitRenderer::UnitRenderer()
|
UnitRenderer::UnitRenderer()
|
||||||
: m_program(nullptr), m_vertexCount(0), m_initialized(false), m_roadBuffersInitialized(false)
|
: m_program(nullptr), m_vertexCount(0), m_initialized(false),
|
||||||
|
m_roadVao(), m_roadVbo(), m_roadBuffersInitialized(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
UnitRenderer::~UnitRenderer()
|
UnitRenderer::~UnitRenderer()
|
||||||
{
|
{
|
||||||
if (m_program) delete m_program;
|
if (m_program) delete m_program;
|
||||||
if (m_roadBuffersInitialized) {
|
m_vao.destroy();
|
||||||
m_roadVao.destroy();
|
|
||||||
m_roadVbo.destroy();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnitRenderer::initialize()
|
void UnitRenderer::initialize()
|
||||||
@@ -28,8 +26,8 @@ void UnitRenderer::initialize()
|
|||||||
initializeOpenGLFunctions();
|
initializeOpenGLFunctions();
|
||||||
|
|
||||||
m_program = new QOpenGLShaderProgram();
|
m_program = new QOpenGLShaderProgram();
|
||||||
m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, "/home/bart/project/tiletest/shaders/unit.vert");
|
m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, "/home/bart/git/Ascent-of-Man/shaders/unit.vert");
|
||||||
m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, "/home/bart/project/tiletest/shaders/unit.frag");
|
m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, "/home/bart/git/Ascent-of-Man/shaders/unit.frag");
|
||||||
m_program->link();
|
m_program->link();
|
||||||
|
|
||||||
buildCylinderMesh();
|
buildCylinderMesh();
|
||||||
@@ -56,7 +54,6 @@ void UnitRenderer::buildCylinderMesh()
|
|||||||
float c2 = std::cos(a2), s2 = std::sin(a2);
|
float c2 = std::cos(a2), s2 = std::sin(a2);
|
||||||
|
|
||||||
// --- Side Quad (2 Triangles) ---
|
// --- Side Quad (2 Triangles) ---
|
||||||
// Tri 1
|
|
||||||
vertices.push_back({c1, s1, 0.0f, c1, s1, 0.0f});
|
vertices.push_back({c1, s1, 0.0f, c1, s1, 0.0f});
|
||||||
vertices.push_back({c2, s2, 0.0f, c2, s2, 0.0f});
|
vertices.push_back({c2, s2, 0.0f, c2, s2, 0.0f});
|
||||||
vertices.push_back({c2, s2, 1.0f, c2, s2, 0.0f});
|
vertices.push_back({c2, s2, 1.0f, c2, s2, 0.0f});
|
||||||
@@ -96,6 +93,8 @@ void UnitRenderer::buildCylinderMesh()
|
|||||||
m_vbo.release();
|
m_vbo.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void UnitRenderer::renderRoads(
|
void UnitRenderer::renderRoads(
|
||||||
const std::map<std::pair<int, int>, TileRoad> &roads,
|
const std::map<std::pair<int, int>, TileRoad> &roads,
|
||||||
const RoadConfigManager &roadConfigs,
|
const RoadConfigManager &roadConfigs,
|
||||||
@@ -368,7 +367,7 @@ void UnitRenderer::renderUnits(
|
|||||||
const Player *player = players.getPlayer(unit->playerId);
|
const Player *player = players.getPlayer(unit->playerId);
|
||||||
QColor col = player ? player->primaryColor : QColor(200, 200, 200);
|
QColor col = player ? player->primaryColor : QColor(200, 200, 200);
|
||||||
|
|
||||||
// Flash white/red if attacked
|
// Flash white/red if attacked or damaged
|
||||||
if (unit->attackFlashTimer > 0.0f) {
|
if (unit->attackFlashTimer > 0.0f) {
|
||||||
float f = unit->attackFlashTimer / 0.5f;
|
float f = unit->attackFlashTimer / 0.5f;
|
||||||
col = QColor(
|
col = QColor(
|
||||||
|
|||||||
@@ -60,15 +60,21 @@ public:
|
|||||||
private:
|
private:
|
||||||
void buildCylinderMesh();
|
void buildCylinderMesh();
|
||||||
|
|
||||||
|
// Main unit cylinder mesh
|
||||||
QOpenGLShaderProgram *m_program;
|
QOpenGLShaderProgram *m_program;
|
||||||
QOpenGLBuffer m_vbo;
|
QOpenGLBuffer m_vbo;
|
||||||
QOpenGLVertexArrayObject m_vao;
|
QOpenGLVertexArrayObject m_vao;
|
||||||
int m_vertexCount;
|
int m_vertexCount;
|
||||||
bool m_initialized;
|
bool m_initialized;
|
||||||
|
|
||||||
|
// Road rendering buffers (existing functionality)
|
||||||
QOpenGLBuffer m_roadVbo;
|
QOpenGLBuffer m_roadVbo;
|
||||||
QOpenGLVertexArrayObject m_roadVao;
|
QOpenGLVertexArrayObject m_roadVao;
|
||||||
bool m_roadBuffersInitialized;
|
bool m_roadBuffersInitialized;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // UNITRENDERER_H
|
#endif // UNITRENDERER_H
|
||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user