Migrate core engine interfaces to GroveEngine repository
Removed core engine infrastructure from warfactoryracine: - Core interfaces: IEngine, IModule, IModuleSystem, IIO, ITaskScheduler, ICoordinationModule - Configuration system: IDataTree, IDataNode, DataTreeFactory - UI system: IUI, IUI_Enums, ImGuiUI (header + implementation) - Resource management: Resource, ResourceRegistry, SerializationRegistry - Serialization: ASerializable, ISerializable - World generation: IWorldGenerationStep (replaced by IWorldGenerationPhase) These components now live in the GroveEngine repository and are included via CMake add_subdirectory(../GroveEngine) for reusability across projects. warfactoryracine remains focused on game-specific logic and content. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
63a2d251ff
commit
f393b28d73
7
.claude/settings.json
Normal file
7
.claude/settings.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"permissions": {
|
||||
"additionalDirectories": [
|
||||
"../GroveEngine"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -14,7 +14,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
## 📋 Implementation Status
|
||||
|
||||
**ALWAYS CHECK**: `TODO.md` at project root for current implementation roadmap and tasks.
|
||||
**ALWAYS CHECK**:
|
||||
- `TODO.md` at project root for current implementation roadmap and high-level phases
|
||||
- `TASKLIST.md` for detailed task breakdown organized by time investment (Quick Wins, Light Tasks, Medium Tasks, Feature Tasks, Big Tasks)
|
||||
|
||||
**Current Phase**: World Generation System - Geological and resource foundation complete
|
||||
|
||||
@ -99,6 +101,7 @@ The project uses a **hierarchical documentation system** in `/docs/`:
|
||||
- `systeme-militaire.md` - Vehicle design with grid-based component placement
|
||||
- `economie-logistique.md` - Market simulation, supply chains, pricing
|
||||
- `map-system.md` - Procedural generation with 218+ elements
|
||||
- `rendering-system.md` - Sprite composition and entity rendering
|
||||
- `factory-architecture-post-player.md` - Advanced production architecture
|
||||
|
||||
### 🔧 03-implementation/
|
||||
@ -389,6 +392,10 @@ The project includes 16 C++ libraries via FetchContent:
|
||||
3. `03-implementation/testing-strategy.md` - Testing approach
|
||||
4. `04-reference/INTEGRATION-MASTER-LIST.md` - Complete specifications
|
||||
|
||||
### For Task Management
|
||||
1. `TODO.md` - High-level implementation roadmap and current phases
|
||||
2. **`TASKLIST.md`** - **DETAILED**: Complete task breakdown by time (Quick Wins < 1h, Light 1-3h, Medium 1-2 days, Feature 3-7 days, Big 1-2 weeks)
|
||||
|
||||
### For Technical Reference
|
||||
1. `04-reference/arbre-technologique.md` - Complete tech tree
|
||||
2. `04-reference/coherence-problem.md` - Technical analyses
|
||||
|
||||
@ -37,6 +37,9 @@ FetchContent_Declare(
|
||||
|
||||
FetchContent_MakeAvailable(nlohmann_json imgui)
|
||||
|
||||
# Add GroveEngine
|
||||
add_subdirectory(../GroveEngine GroveEngine_build)
|
||||
|
||||
# Create ImGui library with OpenGL/GLFW backends
|
||||
add_library(imgui_backends
|
||||
${imgui_SOURCE_DIR}/imgui.cpp
|
||||
@ -58,16 +61,15 @@ target_link_libraries(imgui_backends PUBLIC glfw OpenGL::GL)
|
||||
# Test executable
|
||||
add_executable(test_imgui_ui
|
||||
test_imgui_ui.cpp
|
||||
src/core/src/ImGuiUI.cpp
|
||||
)
|
||||
|
||||
target_include_directories(test_imgui_ui PRIVATE
|
||||
src/core/include
|
||||
${imgui_SOURCE_DIR}
|
||||
${imgui_SOURCE_DIR}/backends
|
||||
)
|
||||
|
||||
target_link_libraries(test_imgui_ui
|
||||
GroveEngine::impl
|
||||
imgui_backends
|
||||
nlohmann_json::nlohmann_json
|
||||
glfw
|
||||
@ -84,16 +86,15 @@ set_target_properties(test_imgui_ui PROPERTIES
|
||||
# Test constraints executable
|
||||
add_executable(test_constraints
|
||||
test_constraints.cpp
|
||||
src/core/src/ImGuiUI.cpp
|
||||
)
|
||||
|
||||
target_include_directories(test_constraints PRIVATE
|
||||
src/core/include
|
||||
${imgui_SOURCE_DIR}
|
||||
${imgui_SOURCE_DIR}/backends
|
||||
)
|
||||
|
||||
target_link_libraries(test_constraints
|
||||
GroveEngine::impl
|
||||
imgui_backends
|
||||
nlohmann_json::nlohmann_json
|
||||
glfw
|
||||
|
||||
1723
TASKLIST.md
Normal file
1723
TASKLIST.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -264,5 +264,3 @@ Moteur → [PROTOTYPE] Châssis Performance
|
||||
Warfactory transcende les genres traditionnels en unifiant industrie, stratégie militaire et simulation économique authentique dans une architecture technique révolutionnaire.
|
||||
|
||||
Le projet honore l'héroïsme ukrainien tout en repoussant les limites du développement assisté par IA, créant un gameplay émergent d'une profondeur inégalée où chaque choix - industriel, militaire, économique - résonne à travers un système interconnecté d'une complexité et d'un réalisme saisissants.
|
||||
|
||||
**Slava Ukraini !**
|
||||
2233
docs/02-systems/pathfinding-system.md
Normal file
2233
docs/02-systems/pathfinding-system.md
Normal file
File diff suppressed because it is too large
Load Diff
516
docs/02-systems/rendering-system.md
Normal file
516
docs/02-systems/rendering-system.md
Normal file
@ -0,0 +1,516 @@
|
||||
# Rendering System - Sprite Composition
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
Le système de rendu graphique de Warfactory utilise une approche de **composition de sprites** inspirée de Rimworld, optimisée pour minimiser le nombre de sprites à dessiner tout en permettant une grande flexibilité visuelle.
|
||||
|
||||
**Principe fondamental** : Chaque entité est composée de sprites superposés avec des orientations indépendantes, permettant un rendu dynamique efficace.
|
||||
|
||||
## Architecture de composition
|
||||
|
||||
### Personnages (Humains)
|
||||
|
||||
Un personnage est composé de **3 sprites maximum** :
|
||||
|
||||
```
|
||||
Personnage
|
||||
├── Corps (sprite de base + vêtements composés)
|
||||
├── Tête (orientation indépendante - turret behavior)
|
||||
└── Arme (orientation indépendante - turret behavior)
|
||||
```
|
||||
|
||||
**Optimisation clé** : Les vêtements sont pré-composés avec le corps pour éviter de dessiner chaque couche de vêtement séparément.
|
||||
|
||||
#### Exemple de composition
|
||||
```
|
||||
Rendu final = Corps_Base + Veste + Pantalon + Tête + Arme
|
||||
└─────────────────────┘
|
||||
1 sprite composé
|
||||
```
|
||||
|
||||
Au lieu de dessiner 5 sprites par personnage, on en dessine **3** :
|
||||
- 1 sprite corps composé (avec tous les vêtements)
|
||||
- 1 sprite tête
|
||||
- 1 sprite arme
|
||||
|
||||
### Véhicules
|
||||
|
||||
Un véhicule suit le même principe avec un nombre variable de tourelles :
|
||||
|
||||
```
|
||||
Véhicule
|
||||
├── Corps (sprite fixe avec orientation principale)
|
||||
└── Tourelles[] (orientations indépendantes)
|
||||
├── Tourelle principale
|
||||
├── Tourelle secondaire (optionnelle)
|
||||
└── Tourelle tertiaire (optionnelle)
|
||||
```
|
||||
|
||||
**Exemple** :
|
||||
- Tank léger : Corps + 1 tourelle = **2 sprites**
|
||||
- APC : Corps + 1 tourelle principale + 1 mitrailleuse = **3 sprites**
|
||||
- Véhicule lourd : Corps + 3 tourelles = **4 sprites**
|
||||
|
||||
## Pipeline unifiée : Concept Fixe/Mobile
|
||||
|
||||
Le système distingue deux types de composants :
|
||||
|
||||
### 1. Composants Fixes
|
||||
**Définition** : Composants dont l'orientation est liée à l'orientation principale de l'entité.
|
||||
|
||||
**Caractéristiques** :
|
||||
- Rotation synchronisée avec l'entité parente
|
||||
- Pas de calcul d'orientation indépendant
|
||||
- Exemples : corps de personnage, châssis de véhicule
|
||||
|
||||
```cpp
|
||||
struct FixedComponent {
|
||||
SpriteID sprite;
|
||||
Vector2 offset; // Position relative au centre de l'entité
|
||||
float rotationOffset; // Rotation additionnelle par rapport à l'orientation principale
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Composants Mobiles (Turret Behavior)
|
||||
**Définition** : Composants avec orientation indépendante suivant une cible.
|
||||
|
||||
**Caractéristiques** :
|
||||
- Orientation calculée indépendamment
|
||||
- Cible une position XY (target)
|
||||
- Contraintes de rotation optionnelles (angle min/max, vitesse de rotation)
|
||||
- Exemples : tête de personnage, arme, tourelle de véhicule
|
||||
|
||||
```cpp
|
||||
struct MobileComponent {
|
||||
SpriteID sprite;
|
||||
Vector2 offset; // Position relative au centre de l'entité
|
||||
|
||||
// Turret behavior
|
||||
Vector2 currentTarget; // Position XY actuelle de la cible
|
||||
float currentAngle; // Angle actuel du composant
|
||||
float rotationSpeed; // Vitesse de rotation max (rad/s)
|
||||
|
||||
// Contraintes optionnelles
|
||||
float minAngle; // Angle minimum (par rapport au fixe)
|
||||
float maxAngle; // Angle maximum (par rapport au fixe)
|
||||
bool hasConstraints; // Si true, applique les contraintes min/max
|
||||
};
|
||||
```
|
||||
|
||||
## Pipeline de rendu unifiée
|
||||
|
||||
La **même pipeline** gère les têtes, armes et tourelles :
|
||||
|
||||
```cpp
|
||||
class EntityRenderer {
|
||||
// Composants fixes (corps)
|
||||
std::vector<FixedComponent> fixedComponents;
|
||||
|
||||
// Composants mobiles (têtes, armes, tourelles)
|
||||
std::vector<MobileComponent> mobileComponents;
|
||||
|
||||
void render(Vector2 position, float mainOrientation) {
|
||||
// 1. Dessiner les composants fixes
|
||||
for (const FixedComponent& comp : fixedComponents) {
|
||||
float finalAngle = mainOrientation + comp.rotationOffset;
|
||||
drawSprite(comp.sprite, position + rotate(comp.offset, mainOrientation), finalAngle);
|
||||
}
|
||||
|
||||
// 2. Dessiner les composants mobiles
|
||||
for (MobileComponent& comp : mobileComponents) {
|
||||
// Calculer l'orientation vers la cible
|
||||
float targetAngle = calculateAngleToTarget(position, comp.offset, comp.currentTarget);
|
||||
|
||||
// Appliquer la vitesse de rotation (smooth rotation)
|
||||
comp.currentAngle = lerpAngle(comp.currentAngle, targetAngle, comp.rotationSpeed * deltaTime);
|
||||
|
||||
// Appliquer les contraintes si nécessaire
|
||||
if (comp.hasConstraints) {
|
||||
float relativeAngle = comp.currentAngle - mainOrientation;
|
||||
comp.currentAngle = mainOrientation + clamp(relativeAngle, comp.minAngle, comp.maxAngle);
|
||||
}
|
||||
|
||||
drawSprite(comp.sprite, position + rotate(comp.offset, mainOrientation), comp.currentAngle);
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Exemples concrets
|
||||
|
||||
### Personnage avec arme
|
||||
|
||||
```cpp
|
||||
EntityRenderer soldier;
|
||||
|
||||
// Corps (fixe)
|
||||
soldier.fixedComponents.push_back({
|
||||
.sprite = SPRITE_SOLDIER_BODY_COMPOSED,
|
||||
.offset = {0, 0},
|
||||
.rotationOffset = 0
|
||||
});
|
||||
|
||||
// Tête (mobile - turret behavior)
|
||||
soldier.mobileComponents.push_back({
|
||||
.sprite = SPRITE_HEAD,
|
||||
.offset = {0, 8}, // 8 pixels au-dessus du centre
|
||||
.currentTarget = enemyPosition,
|
||||
.currentAngle = 0,
|
||||
.rotationSpeed = 5.0f, // rad/s
|
||||
.minAngle = -PI/2, // Peut regarder 90° à gauche
|
||||
.maxAngle = PI/2, // Peut regarder 90° à droite
|
||||
.hasConstraints = true
|
||||
});
|
||||
|
||||
// Arme (mobile - turret behavior)
|
||||
soldier.mobileComponents.push_back({
|
||||
.sprite = SPRITE_RIFLE,
|
||||
.offset = {4, 2}, // Décalage pour la main
|
||||
.currentTarget = enemyPosition,
|
||||
.currentAngle = 0,
|
||||
.rotationSpeed = 3.0f, // Plus lent que la tête
|
||||
.minAngle = -PI/4, // Contraintes plus strictes
|
||||
.maxAngle = PI/4,
|
||||
.hasConstraints = true
|
||||
});
|
||||
|
||||
// Rendu
|
||||
soldier.render(soldierPosition, soldierOrientation);
|
||||
```
|
||||
|
||||
### Véhicule avec tourelle
|
||||
|
||||
```cpp
|
||||
EntityRenderer tank;
|
||||
|
||||
// Châssis (fixe)
|
||||
tank.fixedComponents.push_back({
|
||||
.sprite = SPRITE_TANK_HULL,
|
||||
.offset = {0, 0},
|
||||
.rotationOffset = 0
|
||||
});
|
||||
|
||||
// Tourelle principale (mobile - rotation 360°)
|
||||
tank.mobileComponents.push_back({
|
||||
.sprite = SPRITE_TANK_TURRET,
|
||||
.offset = {0, 0},
|
||||
.currentTarget = targetPosition,
|
||||
.currentAngle = 0,
|
||||
.rotationSpeed = 2.0f, // Rotation lente (réaliste)
|
||||
.hasConstraints = false // Rotation complète
|
||||
});
|
||||
|
||||
// Rendu
|
||||
tank.render(tankPosition, tankOrientation);
|
||||
```
|
||||
|
||||
## Optimisations
|
||||
|
||||
### 1. Composition de sprites (Layering)
|
||||
|
||||
**Problème** : Dessiner tous les vêtements séparément = beaucoup de draw calls.
|
||||
|
||||
**Solution** : Pré-composer les sprites en cache.
|
||||
|
||||
```cpp
|
||||
class SpriteComposer {
|
||||
std::unordered_map<CompositionKey, SpriteID> compositionCache;
|
||||
|
||||
SpriteID getComposedSprite(const std::vector<LayerID>& layers) {
|
||||
CompositionKey key = hashLayers(layers);
|
||||
|
||||
if (compositionCache.contains(key)) {
|
||||
return compositionCache[key];
|
||||
}
|
||||
|
||||
// Composer les sprites
|
||||
SpriteID composed = renderToTexture(layers);
|
||||
compositionCache[key] = composed;
|
||||
return composed;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**Exemple** :
|
||||
```cpp
|
||||
// Composition d'un soldat
|
||||
std::vector<LayerID> soldierLayers = {
|
||||
SPRITE_BODY_BASE,
|
||||
SPRITE_PANTS_CAMO,
|
||||
SPRITE_JACKET_TACTICAL,
|
||||
SPRITE_VEST_ARMOR
|
||||
};
|
||||
|
||||
SpriteID composedBody = composer.getComposedSprite(soldierLayers);
|
||||
```
|
||||
|
||||
**Avantages** :
|
||||
- Réduction massive des draw calls
|
||||
- Cache persistant entre frames
|
||||
- Mise à jour uniquement quand les vêtements changent
|
||||
|
||||
### 2. Sprite Batching
|
||||
|
||||
Grouper les sprites par texture pour minimiser les changements d'état GPU :
|
||||
|
||||
```cpp
|
||||
// Trier par texture avant de dessiner
|
||||
std::sort(entities.begin(), entities.end(), [](const Entity& a, const Entity& b) {
|
||||
return a.getTextureID() < b.getTextureID();
|
||||
});
|
||||
|
||||
// Dessiner tous les sprites de la même texture ensemble
|
||||
for (const Entity& entity : entities) {
|
||||
entity.render();
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Culling
|
||||
|
||||
Ne dessiner que les entités visibles à l'écran :
|
||||
|
||||
```cpp
|
||||
void renderEntities(const Camera& camera) {
|
||||
for (Entity& entity : entities) {
|
||||
if (camera.isVisible(entity.getBounds())) {
|
||||
entity.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Système de couches (Layering)
|
||||
|
||||
### Ordre de rendu (Z-order)
|
||||
|
||||
Les sprites doivent être dessinés dans un ordre précis pour éviter les problèmes de superposition :
|
||||
|
||||
```
|
||||
Z-Order (du plus bas au plus haut)
|
||||
├── 0: Corps/Châssis (fixe)
|
||||
├── 1: Tête/Tourelle (mobile)
|
||||
└── 2: Arme (mobile)
|
||||
```
|
||||
|
||||
**Implémentation** :
|
||||
```cpp
|
||||
struct RenderableComponent {
|
||||
SpriteID sprite;
|
||||
Vector2 position;
|
||||
float rotation;
|
||||
int zOrder; // Ordre de rendu
|
||||
};
|
||||
|
||||
void renderEntity(const Entity& entity) {
|
||||
std::vector<RenderableComponent> renderables;
|
||||
|
||||
// Collecter tous les composants
|
||||
collectFixedComponents(entity, renderables);
|
||||
collectMobileComponents(entity, renderables);
|
||||
|
||||
// Trier par Z-order
|
||||
std::sort(renderables.begin(), renderables.end(),
|
||||
[](const RenderableComponent& a, const RenderableComponent& b) {
|
||||
return a.zOrder < b.zOrder;
|
||||
});
|
||||
|
||||
// Dessiner dans l'ordre
|
||||
for (const RenderableComponent& comp : renderables) {
|
||||
drawSprite(comp.sprite, comp.position, comp.rotation);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Gestion des orientations
|
||||
|
||||
### Angles et conventions
|
||||
|
||||
**Convention** : Angle 0 = direction droite (East), rotation anti-horaire.
|
||||
|
||||
```
|
||||
N (π/2)
|
||||
|
|
||||
W (π) --+-- E (0)
|
||||
|
|
||||
S (3π/2)
|
||||
```
|
||||
|
||||
### Calcul de l'angle vers une cible
|
||||
|
||||
```cpp
|
||||
float calculateAngleToTarget(Vector2 entityPos, Vector2 componentOffset, Vector2 target) {
|
||||
// Position mondiale du composant
|
||||
Vector2 worldPos = entityPos + componentOffset;
|
||||
|
||||
// Direction vers la cible
|
||||
Vector2 direction = target - worldPos;
|
||||
|
||||
// Angle en radians
|
||||
return std::atan2(direction.y, direction.x);
|
||||
}
|
||||
```
|
||||
|
||||
### Interpolation d'angle (smooth rotation)
|
||||
|
||||
Pour éviter les rotations brusques, on interpole vers l'angle cible :
|
||||
|
||||
```cpp
|
||||
float lerpAngle(float current, float target, float speed, float deltaTime) {
|
||||
// Normaliser les angles entre -π et π
|
||||
float diff = normalizeAngle(target - current);
|
||||
|
||||
// Calculer le changement maximum pour cette frame
|
||||
float maxChange = speed * deltaTime;
|
||||
|
||||
// Appliquer le changement (limité par la vitesse)
|
||||
float change = std::clamp(diff, -maxChange, maxChange);
|
||||
|
||||
return current + change;
|
||||
}
|
||||
|
||||
float normalizeAngle(float angle) {
|
||||
while (angle > PI) angle -= 2 * PI;
|
||||
while (angle < -PI) angle += 2 * PI;
|
||||
return angle;
|
||||
}
|
||||
```
|
||||
|
||||
## Extensions futures
|
||||
|
||||
### 1. Animations
|
||||
|
||||
Ajouter un système d'animation pour les sprites :
|
||||
|
||||
```cpp
|
||||
struct AnimatedComponent {
|
||||
std::vector<SpriteID> frames;
|
||||
float frameDuration;
|
||||
float currentTime;
|
||||
|
||||
SpriteID getCurrentFrame() {
|
||||
int frameIndex = (int)(currentTime / frameDuration) % frames.size();
|
||||
return frames[frameIndex];
|
||||
}
|
||||
|
||||
void update(float deltaTime) {
|
||||
currentTime += deltaTime;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Effets visuels
|
||||
|
||||
Ajouter des effets visuels aux composants :
|
||||
|
||||
```cpp
|
||||
struct VisualEffect {
|
||||
Color tint; // Couleur de teinte
|
||||
float opacity; // Transparence
|
||||
Vector2 scale; // Échelle
|
||||
bool flipX, flipY; // Miroirs
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Attachments dynamiques
|
||||
|
||||
Support pour des composants attachés dynamiquement :
|
||||
|
||||
```cpp
|
||||
class AttachmentSystem {
|
||||
void attachComponent(EntityID entity, MobileComponent component, const std::string& attachPoint);
|
||||
void detachComponent(EntityID entity, const std::string& attachPoint);
|
||||
};
|
||||
|
||||
// Exemple : attacher une tourelle sur un point de montage
|
||||
attachments.attachComponent(vehicleID, turretComponent, "mount_point_1");
|
||||
```
|
||||
|
||||
## Performances
|
||||
|
||||
### Métriques cibles
|
||||
|
||||
- **Entités à l'écran** : 500-1000 personnages/véhicules simultanés
|
||||
- **Draw calls** : < 100 par frame (via batching)
|
||||
- **FPS cible** : 60 fps (16.67ms par frame)
|
||||
- **Temps de rendu** : < 8ms par frame (50% du budget)
|
||||
|
||||
### Profiling
|
||||
|
||||
Points de mesure critiques :
|
||||
|
||||
```cpp
|
||||
void renderAll() {
|
||||
PROFILE_SCOPE("Entity Rendering");
|
||||
|
||||
{
|
||||
PROFILE_SCOPE("Sprite Composition");
|
||||
updateComposedSprites();
|
||||
}
|
||||
|
||||
{
|
||||
PROFILE_SCOPE("Culling");
|
||||
cullEntities(camera);
|
||||
}
|
||||
|
||||
{
|
||||
PROFILE_SCOPE("Sorting");
|
||||
sortByTexture(visibleEntities);
|
||||
}
|
||||
|
||||
{
|
||||
PROFILE_SCOPE("Drawing");
|
||||
drawEntities(visibleEntities);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Intégration avec les autres systèmes
|
||||
|
||||
### Lien avec le système militaire
|
||||
|
||||
Le système de rendu est découplé des systèmes de combat :
|
||||
|
||||
- **Combat System** calcule les positions, rotations, cibles
|
||||
- **Rendering System** lit ces données et dessine les sprites
|
||||
|
||||
```cpp
|
||||
// Dans le module Combat
|
||||
void updateCombat(Entity& entity, float deltaTime) {
|
||||
// Calculer la cible
|
||||
Vector2 target = findClosestEnemy(entity.position);
|
||||
entity.setTarget(target);
|
||||
|
||||
// Le rendering system lira cette cible plus tard
|
||||
}
|
||||
|
||||
// Dans le module Rendering
|
||||
void render(const Entity& entity) {
|
||||
// Lire la cible depuis l'entité
|
||||
Vector2 target = entity.getTarget();
|
||||
|
||||
// Dessiner avec la cible
|
||||
entityRenderer.render(entity.position, entity.orientation, target);
|
||||
}
|
||||
```
|
||||
|
||||
### Lien avec le système de pathfinding
|
||||
|
||||
Le système de rendu utilise les positions calculées par le pathfinding :
|
||||
|
||||
```cpp
|
||||
// Pathfinding calcule la position
|
||||
Vector2 newPosition = pathfinding.getNextPosition(entity);
|
||||
entity.setPosition(newPosition);
|
||||
|
||||
// Rendering dessine à cette position
|
||||
render(entity);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Statut** : ✅ **DESIGN COMPLETE**
|
||||
**Prochaines étapes** :
|
||||
1. Implémenter `EntityRenderer` avec la pipeline fixe/mobile
|
||||
2. Créer `SpriteComposer` pour la composition de sprites
|
||||
3. Intégrer avec le système de combat pour les cibles
|
||||
4. Optimiser avec batching et culling
|
||||
@ -8,9 +8,24 @@ Le système de calcul de menace évalue la **dangerosité qu'une entité représ
|
||||
|
||||
- **Contextuel** : Menace calculée selon capacités **des deux parties**
|
||||
- **Asymétrique** : A menace B ≠ B menace A
|
||||
- **Sword & Shield** : Qualité défenses peut annuler supériorité numérique
|
||||
- **Sword & Shield** : Qualité défenses réduit menace, jamais totalement
|
||||
- **Baseline 20%** : Toute arme reste dangereuse à minimum 20% effectiveness
|
||||
- **Production** : Capacité industrielle = menace future
|
||||
- **Multi-domaines** : Terre, air, mer évalués séparément puis agrégés
|
||||
- **Perte coûteuse** : Perdre unité chère = désastre stratégique même avec victoire
|
||||
|
||||
### Distinction Menace vs Victoire Tactique
|
||||
|
||||
**CRITIQUE** : La menace mesure la **capacité de nuisance stratégique**, pas l'issue du combat tactique.
|
||||
|
||||
**Exemple** : 20 tanks modernes peuvent être détruits par saturation, MAIS avant destruction ils peuvent :
|
||||
- Percer défenses et atteindre objectifs profonds
|
||||
- Détruire infrastructure critique (QG, dépôts, ponts)
|
||||
- Infliger pertes massives
|
||||
- Forcer mobilisation coûteuse
|
||||
|
||||
→ Menace élevée même si tanks finalement détruits
|
||||
→ Perte d'un Leopard 2A7 (8M€) = toujours désastre stratégique
|
||||
|
||||
## Échelle de Menace
|
||||
|
||||
@ -56,12 +71,12 @@ struct ThreatCalculationParams {
|
||||
|
||||
## Évaluation Forces Actuelles
|
||||
|
||||
### Principe : Sword & Shield
|
||||
### Principe : Sword & Shield avec Effectiveness Float
|
||||
|
||||
Pour chaque domaine (terre, air, mer), on évalue :
|
||||
1. **Sword** (attaquant) : Capacités offensives
|
||||
2. **Shield** (défenseur) : Capacités défensives
|
||||
3. **Effectiveness** : Dans quelle mesure le shield arrête le sword
|
||||
3. **Effectiveness** : Ratio pénétration/armure avec baseline 20% minimum
|
||||
|
||||
```cpp
|
||||
int evaluateCurrentForces(Entity attacker, Entity defender) {
|
||||
@ -73,9 +88,95 @@ int evaluateCurrentForces(Entity attacker, Entity defender) {
|
||||
}
|
||||
```
|
||||
|
||||
### Évaluation Domaine Terrestre
|
||||
## Système Armor vs Penetration
|
||||
|
||||
#### Inventaire Forces
|
||||
### Référence de Base
|
||||
|
||||
Chaque équipement possède des valeurs mesurables :
|
||||
|
||||
```cpp
|
||||
struct Tank {
|
||||
float armor_average; // Moyenne armure (mm RHA equivalent)
|
||||
int combat_value; // Valeur au combat
|
||||
int quantity;
|
||||
|
||||
// Exemple Leopard 2A7:
|
||||
// - Frontal: 950mm RHA
|
||||
// - Flancs: 200mm RHA
|
||||
// - Arrière: 100mm RHA
|
||||
// - Average (pondéré surface): ~400mm
|
||||
};
|
||||
|
||||
struct ATWeapon {
|
||||
float penetration; // Pénétration max (mm RHA)
|
||||
int combat_value;
|
||||
int quantity;
|
||||
|
||||
// Exemples:
|
||||
// - RPG-7: 260mm
|
||||
// - Milan Gen2: 530mm
|
||||
// - Javelin Gen3 (top-attack): 800mm
|
||||
// - Kornet Gen4+ (tandem): 1200mm
|
||||
};
|
||||
```
|
||||
|
||||
### Calcul Effectiveness (Float)
|
||||
|
||||
**Formule baseline 20%** : Toute arme reste dangereuse (flancs, arrière, chance)
|
||||
|
||||
```cpp
|
||||
float calculateCounterEffectiveness(ATWeapon weapon, Tank tank) {
|
||||
float pen_ratio = weapon.penetration / tank.armor_average;
|
||||
|
||||
// BASELINE 20% : TOUJOURS dangereux
|
||||
const float BASE_EFFECTIVENESS = 0.20f;
|
||||
|
||||
if (pen_ratio >= 1.0f) {
|
||||
// Arme peut percer : scaling jusqu'à 90% max
|
||||
float bonus = (pen_ratio - 1.0f) * 0.5f;
|
||||
return min(0.90f, BASE_EFFECTIVENESS + bonus);
|
||||
}
|
||||
else {
|
||||
// Arme sous-dimensionnée : reste à 20% (flancs, arrière, chance)
|
||||
return BASE_EFFECTIVENESS;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Exemples Effectiveness
|
||||
|
||||
```cpp
|
||||
// Leopard 2A7 (armor_avg = 400mm) vs différentes armes
|
||||
|
||||
RPG-7 (260mm) :
|
||||
pen_ratio = 260 / 400 = 0.65
|
||||
effectiveness = 20%
|
||||
→ 100 RPG-7 needed = 20 "équivalents kill"
|
||||
→ Coût: 100 × 500€ = 50k€ vs Leopard 8M€ = 0.625% ratio
|
||||
→ Menace présente mais faible, économiquement viable en masse
|
||||
|
||||
Milan Gen2 (530mm) :
|
||||
pen_ratio = 530 / 400 = 1.325
|
||||
effectiveness = 20% + (0.325 × 0.5) = 36.25%
|
||||
→ ~3 missiles = 1 kill probable
|
||||
→ Menace sérieuse
|
||||
|
||||
Javelin Gen3 (800mm top-attack) :
|
||||
pen_ratio = 800 / 400 = 2.0
|
||||
effectiveness = 20% + (1.0 × 0.5) = 70%
|
||||
→ ~1.5 missiles = 1 kill probable
|
||||
→ Menace très élevée
|
||||
|
||||
Kornet Gen4+ (1200mm tandem) :
|
||||
pen_ratio = 1200 / 400 = 3.0
|
||||
effectiveness = 20% + (2.0 × 0.5) = 90% (plafonné)
|
||||
→ ~1.1 missiles = 1 kill quasi-certain
|
||||
→ Menace maximale
|
||||
```
|
||||
|
||||
## Évaluation Domaine Terrestre
|
||||
|
||||
### Inventaire Forces
|
||||
|
||||
**Attaquant** :
|
||||
```cpp
|
||||
@ -98,7 +199,7 @@ struct LandDefenses {
|
||||
};
|
||||
```
|
||||
|
||||
#### Couples Équipement ↔ Contre-mesures
|
||||
### Couples Équipement ↔ Contre-mesures
|
||||
|
||||
**Principe** : Chaque type d'équipement offensif a des contre-mesures spécifiques.
|
||||
|
||||
@ -114,50 +215,7 @@ struct EquipmentCounters {
|
||||
};
|
||||
```
|
||||
|
||||
#### Évaluation Sword vs Shield
|
||||
|
||||
**Étape 1 : Match-making**
|
||||
|
||||
Pour chaque équipement offensif, identifier contre-mesures défensives applicables :
|
||||
|
||||
```cpp
|
||||
std::vector<Defense> findApplicableDefenses(
|
||||
Equipment sword,
|
||||
std::vector<Defense> available_defenses
|
||||
) {
|
||||
std::vector<Defense> applicable;
|
||||
|
||||
auto counters = EquipmentCounters::counters[sword.type];
|
||||
|
||||
for (auto& defense : available_defenses) {
|
||||
// Vérifier si défense peut contrer cet équipement
|
||||
if (std::find(counters.begin(), counters.end(), defense.type) != counters.end()) {
|
||||
// Vérifier compatibilité génération/qualité
|
||||
if (canCounter(defense, sword)) {
|
||||
applicable.push_back(defense);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return applicable;
|
||||
}
|
||||
|
||||
bool canCounter(Defense defense, Equipment sword) {
|
||||
// Exemple : AT missile Gen2 ne peut pas contrer tank Gen4 avec armure avancée
|
||||
if (defense.generation < sword.generation - 1) {
|
||||
return false; // Trop ancien
|
||||
}
|
||||
|
||||
// Vérifier spécificités
|
||||
if (sword.has_reactive_armor && defense.type == AT_MISSILE_OLD) {
|
||||
return false; // ERA bloque missiles anciens
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
**Étape 2 : Calcul Defensive Effectiveness**
|
||||
### Calcul Defensive Effectiveness
|
||||
|
||||
```cpp
|
||||
float calculateDefensiveEffectiveness(
|
||||
@ -168,7 +226,7 @@ float calculateDefensiveEffectiveness(
|
||||
float neutralized_value = 0;
|
||||
|
||||
for (auto& sword : swords) {
|
||||
float sword_value = sword.quantity * sword.quality;
|
||||
float sword_value = sword.quantity * sword.combat_value;
|
||||
total_sword_value += sword_value;
|
||||
|
||||
// Trouver défenses applicables
|
||||
@ -177,8 +235,9 @@ float calculateDefensiveEffectiveness(
|
||||
// Calculer valeur défensive totale contre ce sword
|
||||
float shield_value = 0;
|
||||
for (auto& shield : applicable_shields) {
|
||||
float shield_effectiveness = calculateShieldEffectiveness(shield, sword);
|
||||
shield_value += shield.quantity * shield.quality * shield_effectiveness;
|
||||
// ✅ EFFECTIVENESS FLOAT (20%-90%)
|
||||
float effectiveness = calculateCounterEffectiveness(shield, sword);
|
||||
shield_value += shield.quantity * shield.combat_value * effectiveness;
|
||||
}
|
||||
|
||||
// Neutralisation proportionnelle
|
||||
@ -190,7 +249,7 @@ float calculateDefensiveEffectiveness(
|
||||
}
|
||||
```
|
||||
|
||||
**Étape 3 : Score Final**
|
||||
### Score Final avec Menace Résiduelle
|
||||
|
||||
```cpp
|
||||
int evaluateLandThreat(Entity attacker, Entity defender) {
|
||||
@ -206,85 +265,154 @@ int evaluateLandThreat(Entity attacker, Entity defender) {
|
||||
// Réduction par défenses
|
||||
float defensive_effectiveness = calculateDefensiveEffectiveness(swords, shields);
|
||||
|
||||
// Menace finale
|
||||
int final_threat = raw_threat * (1.0f - defensive_effectiveness);
|
||||
// ✅ MENACE RÉSIDUELLE : Jamais totalement nulle si attaquant existe
|
||||
float residual_multiplier = max(0.05f, 1.0f - defensive_effectiveness);
|
||||
int final_threat = raw_threat * residual_multiplier;
|
||||
|
||||
return final_threat;
|
||||
}
|
||||
```
|
||||
|
||||
### Exemples Concrets Terre
|
||||
## Exemples Concrets Terre
|
||||
|
||||
#### Exemple 1 : Qualité Domine Quantité
|
||||
### Exemple 1 : Tech Obsolète Viable en Masse
|
||||
|
||||
**Attaquant** :
|
||||
- 20 tanks Gen4 (Leopard 2A7)
|
||||
- Armure composite avancée
|
||||
- Génération 4
|
||||
- Combat value : 1000/tank
|
||||
- Menace brute : 20 × 1000 = 20 000
|
||||
- armor_average : 400mm RHA
|
||||
- combat_value : 1000/tank
|
||||
- Menace brute : 20 000
|
||||
- Valeur totale : 20 × 8M€ = 160M€
|
||||
|
||||
**Défenseur** :
|
||||
- 100 000 AT missiles Gen2
|
||||
- Anciens, inefficaces contre armure Gen4
|
||||
- `canCounter()` retourne `false`
|
||||
- Défense applicable : 0
|
||||
- 5000 RPG-7 (Gen1, ancien)
|
||||
- penetration : 260mm
|
||||
- pen_ratio : 260/400 = 0.65
|
||||
- effectiveness : 20% (baseline)
|
||||
- combat_value : 50/RPG
|
||||
- shield_value : 5000 × 50 × 0.20 = 50 000
|
||||
- Coût : 5000 × 500€ = 2.5M€
|
||||
|
||||
**Résultat** :
|
||||
```
|
||||
defensive_effectiveness = 0 / 20000 = 0%
|
||||
final_threat = 20000 × (1 - 0) = 20 000
|
||||
```
|
||||
→ **Menace élevée** : Défenses obsolètes inefficaces
|
||||
defensive_effectiveness = min(1.0, 50000 / 20000) = 100%
|
||||
residual_multiplier = max(0.05, 1.0 - 1.0) = 5%
|
||||
final_threat = 20000 × 0.05 = 1 000
|
||||
|
||||
#### Exemple 2 : Quantité + Qualité Écrasent
|
||||
→ Menace quasi-neutralisée (5% résiduel)
|
||||
→ Nécessite 5000 RPG-7 (masse logistique)
|
||||
→ Ratio coût défenseur/attaquant = 2.5M€ / 160M€ = 1.56%
|
||||
→ Défense économiquement viable ✅
|
||||
→ MAIS perte Leopard reste désastre (8M€ + prestige + formation)
|
||||
```
|
||||
|
||||
### Exemple 2 : Tech Moderne Efficace
|
||||
|
||||
**Attaquant** :
|
||||
- 1000 tanks Gen2
|
||||
- Armure standard
|
||||
- Combat value : 400/tank
|
||||
- Menace brute : 1000 × 400 = 400 000
|
||||
- 20 tanks Gen4 (Leopard 2A7)
|
||||
- armor_average : 400mm
|
||||
- Menace brute : 20 000
|
||||
|
||||
**Défenseur** :
|
||||
- 500 AT missiles Gen4 (Javelin)
|
||||
- Top-attack, très efficaces
|
||||
- Combat value : 800/missile
|
||||
- `canCounter()` retourne `true`
|
||||
- Shield effectiveness : 0.9 (très efficace)
|
||||
- Shield value : 500 × 800 × 0.9 = 360 000
|
||||
- 100 missiles Javelin Gen3
|
||||
- penetration : 800mm (top-attack)
|
||||
- pen_ratio : 800/400 = 2.0
|
||||
- effectiveness : 70%
|
||||
- combat_value : 300/missile
|
||||
- shield_value : 100 × 300 × 0.70 = 21 000
|
||||
- Coût : 100 × 200k€ = 20M€
|
||||
|
||||
**Résultat** :
|
||||
```
|
||||
defensive_effectiveness = 360000 / 400000 = 90%
|
||||
final_threat = 400000 × (1 - 0.9) = 40 000
|
||||
```
|
||||
→ **Menace faible** : Défenses qualitatives réduisent massavement
|
||||
defensive_effectiveness = min(1.0, 21000 / 20000) = 100%
|
||||
residual_multiplier = 5%
|
||||
final_threat = 20000 × 0.05 = 1 000
|
||||
|
||||
#### Exemple 3 : Masse Insuffisante
|
||||
→ Menace quasi-neutralisée
|
||||
→ Ratio coût : 20M€ / 160M€ = 12.5%
|
||||
→ Plus cher que RPG-7 mais logistique simplifiée (100 vs 5000)
|
||||
```
|
||||
|
||||
### Exemple 3 : Défense Insuffisante
|
||||
|
||||
**Attaquant** :
|
||||
- 1000 tanks Gen2
|
||||
- Combat value : 400/tank
|
||||
- Menace brute : 400 000
|
||||
- 20 tanks Gen4
|
||||
- Menace brute : 20 000
|
||||
|
||||
**Défenseur** :
|
||||
- 20 AT missiles Gen4
|
||||
- Très efficaces mais **pas assez nombreux**
|
||||
- Shield value : 20 × 800 × 0.9 = 14 400
|
||||
- 500 RPG-7
|
||||
- effectiveness : 20%
|
||||
- shield_value : 500 × 50 × 0.20 = 5 000
|
||||
|
||||
**Résultat** :
|
||||
```
|
||||
defensive_effectiveness = 14400 / 400000 = 3.6%
|
||||
final_threat = 400000 × (1 - 0.036) = 385 440
|
||||
defensive_effectiveness = 5000 / 20000 = 25%
|
||||
residual_multiplier = max(0.05, 0.75) = 75%
|
||||
final_threat = 20000 × 0.75 = 15 000
|
||||
|
||||
→ Menace reste élevée (75%)
|
||||
→ Défense insuffisante
|
||||
```
|
||||
→ **Menace reste élevée** : Pas assez de défenses pour couvrir la masse
|
||||
|
||||
### Évaluation Domaine Aérien
|
||||
### Exemple 4 : Défense Mixte Économique
|
||||
|
||||
#### Spécificités Air
|
||||
**Attaquant** :
|
||||
- 20 tanks Gen4
|
||||
- Menace brute : 20 000
|
||||
|
||||
**Défenseur** :
|
||||
- 30 Javelin Gen3 : shield = 30 × 300 × 0.70 = 6 300
|
||||
- 1000 RPG-7 : shield = 1000 × 50 × 0.20 = 10 000
|
||||
- **Shield total** : 16 300
|
||||
- **Coût total** : 30 × 200k€ + 1000 × 500€ = 6.5M€
|
||||
|
||||
**Résultat** :
|
||||
```
|
||||
defensive_effectiveness = 16300 / 20000 = 81.5%
|
||||
residual_multiplier = max(0.05, 0.185) = 18.5%
|
||||
final_threat = 20000 × 0.185 = 3 700
|
||||
|
||||
→ Menace réduite à 18.5% (acceptable)
|
||||
→ Défense mixte optimise coût-efficacité
|
||||
→ Ratio coût : 6.5M€ / 160M€ = 4.06%
|
||||
```
|
||||
|
||||
### Exemple 5 : Système de Conception Modulaire - Retrofit Anti-AT
|
||||
|
||||
**Attaquant** :
|
||||
- 20 tanks Leopard 2A7 **retrofittés Gen5**
|
||||
- Chassis Gen4 base
|
||||
- **APS Trophy Gen5** : intercepte 80% missiles avant impact
|
||||
- **ERA Gen5** : neutralise pénétration Gen2-3
|
||||
- armor_effective : 400mm × 2.5 (ERA bonus) = 1000mm equivalent
|
||||
- Menace brute : 20 000
|
||||
|
||||
**Défenseur** :
|
||||
- 1000 missiles Milan Gen2
|
||||
- penetration : 530mm
|
||||
- pen_ratio : 530/1000 = 0.53 (sous-dimensionné avec ERA)
|
||||
- effectiveness : 20% (baseline)
|
||||
- **APS reduction** : 20% × (1 - 0.80) = 4% effectiveness finale
|
||||
- shield_value : 1000 × 300 × 0.04 = 12 000
|
||||
|
||||
**Résultat** :
|
||||
```
|
||||
defensive_effectiveness = 12000 / 20000 = 60%
|
||||
residual_multiplier = max(0.05, 0.40) = 40%
|
||||
final_threat = 20000 × 0.40 = 8 000
|
||||
|
||||
→ Menace reste significative (40%)
|
||||
→ APS + ERA réduisent drastiquement effectiveness Gen2
|
||||
→ Système de conception permet adaptation doctrine
|
||||
→ Joueur peut designer tanks spécifiquement anti-saturation
|
||||
```
|
||||
|
||||
## Évaluation Domaine Aérien
|
||||
|
||||
### Spécificités Air
|
||||
|
||||
**Complexité supplémentaire** :
|
||||
- **Qualité prime** : Quelques jets furtifs Gen4 dominent des centaines AA Gen2
|
||||
- **Qualité prime** : Furtivité et ECM dominent
|
||||
- **ECM/ECCM** : Guerre électronique critique
|
||||
- **Compatibilité stricte** : AA anti-hélicoptère ne touche pas jets
|
||||
|
||||
@ -309,7 +437,7 @@ struct AirDefenses {
|
||||
// Capacités
|
||||
bool has_eccm; // Electronic Counter-Counter-Measures
|
||||
int radar_quality;
|
||||
std::map<AASystem, TargetCapability> capabilities; // Quoi peut toucher quoi
|
||||
std::map<AASystem, TargetCapability> capabilities;
|
||||
};
|
||||
|
||||
enum TargetCapability {
|
||||
@ -320,159 +448,69 @@ enum TargetCapability {
|
||||
};
|
||||
```
|
||||
|
||||
#### Compatibilité Systèmes
|
||||
### Effectiveness Air avec Stealth/ECM
|
||||
|
||||
```cpp
|
||||
bool canEngageAircraft(AASystem aa, Aircraft aircraft) {
|
||||
// Vérifier compatibilité type
|
||||
switch (aa.capability) {
|
||||
case HELICOPTER_ONLY:
|
||||
return aircraft.type == HELICOPTER;
|
||||
float calculateAirDefenseEffectiveness(Aircraft aircraft, AASystem aa) {
|
||||
// Base effectiveness selon pénétration radar vs stealth
|
||||
float base_eff = calculateCounterEffectiveness(aa, aircraft);
|
||||
|
||||
case LOW_ALTITUDE:
|
||||
return aircraft.altitude < 5000; // mètres
|
||||
|
||||
case HIGH_ALTITUDE:
|
||||
return aircraft.altitude > 5000;
|
||||
|
||||
case ALL_AIRCRAFT:
|
||||
return true;
|
||||
// Multiplicateurs tech
|
||||
float stealth_reduction = 1.0f;
|
||||
if (aircraft.has_stealth && !aa.has_advanced_radar) {
|
||||
stealth_reduction = 1.0f - aircraft.stealth_rating; // Ex: 0.1 si 90% stealth
|
||||
}
|
||||
|
||||
// Vérifier guerre électronique
|
||||
float ecm_reduction = 1.0f;
|
||||
if (aircraft.has_ecm && !aa.has_eccm) {
|
||||
// ECM peut brouiller AA sans ECCM
|
||||
float jam_probability = aircraft.ecm_power / (aa.radar_quality + 1);
|
||||
if (randomFloat() < jam_probability) {
|
||||
return false; // Brouillé
|
||||
}
|
||||
ecm_reduction = 1.0f - (aircraft.ecm_power / (aa.radar_quality + 1));
|
||||
}
|
||||
|
||||
// Vérifier furtivité
|
||||
if (aircraft.has_stealth) {
|
||||
float detection_range = aa.radar_range * (1.0f - aircraft.stealth_rating);
|
||||
if (distance(aa, aircraft) > detection_range) {
|
||||
return false; // Pas détecté
|
||||
}
|
||||
}
|
||||
// Effectiveness finale (baseline 20% toujours appliqué)
|
||||
float final_eff = max(0.20f, base_eff * stealth_reduction * ecm_reduction);
|
||||
|
||||
return true;
|
||||
return final_eff;
|
||||
}
|
||||
```
|
||||
|
||||
#### Exemple Air : Furtivité Domine
|
||||
### Exemple Air : Furtivité + ECM
|
||||
|
||||
**Attaquant** :
|
||||
- 20 jets furtifs Gen4 (F-35)
|
||||
- Stealth rating : 0.9 (réduit détection 90%)
|
||||
- ECM avancé
|
||||
- Combat value : 2000/jet
|
||||
- stealth_rating : 0.90 (réduit détection 90%)
|
||||
- ecm_power : 8
|
||||
- combat_value : 2000/jet
|
||||
- Menace brute : 40 000
|
||||
|
||||
**Défenseur** :
|
||||
- 100 000 AA missiles Gen2
|
||||
- Radar standard
|
||||
- Pas ECCM
|
||||
- Seulement 5% peuvent détecter/engager les furtifs
|
||||
- Shield value effectif : 100000 × 0.05 × 300 = 1 500 000... mais avec ECM :
|
||||
- Shield value final : 1 500 000 × 0.1 (jam rate) = 150 000
|
||||
- 1000 missiles AA Gen3 (Patriot)
|
||||
- radar_quality : 6
|
||||
- has_eccm : false
|
||||
- base_effectiveness : 70% (tech Gen3 vs Gen4)
|
||||
- stealth_reduction : 1.0 - 0.90 = 0.10
|
||||
- ecm_reduction : 1.0 - (8/7) = -0.14 → 0.0 (plancher)
|
||||
- final_effectiveness : max(0.20, 0.70 × 0.10 × 0.0) = 20% (baseline)
|
||||
- shield_value : 1000 × 400 × 0.20 = 80 000
|
||||
|
||||
**Résultat** :
|
||||
```
|
||||
defensive_effectiveness = 150000 / 40000 = ... wait, > 1.0 !
|
||||
→ Plafonné à min(1.0, value)
|
||||
defensive_effectiveness = min(1.0, 150000/40000) = 1.0...
|
||||
defensive_effectiveness = min(1.0, 80000 / 40000) = 100%
|
||||
residual_multiplier = 5%
|
||||
final_threat = 40000 × 0.05 = 2 000
|
||||
|
||||
ERREUR dans mon calcul !
|
||||
→ Furtivité + ECM ramènent effectiveness au baseline 20%
|
||||
→ Nécessite masse importante (1000 missiles) pour compenser
|
||||
→ F-35 peuvent accomplir raid avant saturation (menace 5% résiduelle)
|
||||
```
|
||||
|
||||
**Correction** : Le shield value doit être calculé par aircraft, pas globalement.
|
||||
|
||||
```cpp
|
||||
// Pour chaque jet
|
||||
for (auto& jet : jets) {
|
||||
float jet_value = jet.combat_value;
|
||||
|
||||
// Combien de AA peuvent l'engager ?
|
||||
int applicable_aa = 0;
|
||||
for (auto& aa : aa_systems) {
|
||||
if (canEngageAircraft(aa, jet)) {
|
||||
applicable_aa++;
|
||||
}
|
||||
}
|
||||
|
||||
// Shield value pour ce jet spécifique
|
||||
float shield_value = applicable_aa * aa_combat_value;
|
||||
float neutralization = min(1.0f, shield_value / jet_value);
|
||||
|
||||
threat_reduced += jet_value * neutralization;
|
||||
}
|
||||
```
|
||||
|
||||
**Résultat corrigé** :
|
||||
- 5000 AA peuvent engager (sur 100k)
|
||||
- Pour 1 jet : shield = 5000 × 300 = 1 500 000 vs jet = 2000
|
||||
- Neutralization = 100% par jet
|
||||
- Mais ils peuvent pas tous tirer simultanément !
|
||||
|
||||
**Contrainte simultanéité** :
|
||||
|
||||
```cpp
|
||||
// Limite engagement simultané
|
||||
int max_simultaneous = min(applicable_aa, ENGAGEMENT_LIMIT);
|
||||
// Exemple : Max 100 missiles simultanés par cible
|
||||
|
||||
float shield_value = max_simultaneous * aa_combat_value;
|
||||
```
|
||||
|
||||
Avec limite 100 simultanés :
|
||||
- Shield = 100 × 300 = 30 000 vs jet = 2000
|
||||
- Neutralization = 100% par jet
|
||||
- Mais 20 jets → seulement 2000 engagements simultanés total
|
||||
- Si AA rate coordination : menace reste
|
||||
|
||||
**C'est complexe !** Donc en pratique :
|
||||
|
||||
```cpp
|
||||
float calculateAirDefenseEffectiveness(
|
||||
std::vector<Aircraft> aircraft,
|
||||
std::vector<AASystem> aa_systems
|
||||
) {
|
||||
float total_threat = 0;
|
||||
float neutralized = 0;
|
||||
|
||||
for (auto& ac : aircraft) {
|
||||
total_threat += ac.combat_value;
|
||||
|
||||
// Compter AA applicables
|
||||
int applicable_count = 0;
|
||||
for (auto& aa : aa_systems) {
|
||||
if (canEngageAircraft(aa, ac)) {
|
||||
applicable_count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Engagement limité
|
||||
int engaged = min(applicable_count, MAX_SIMULTANEOUS_PER_TARGET);
|
||||
|
||||
// Probabilité kill
|
||||
float kill_probability = engaged / float(ENGAGEMENTS_NEEDED_FOR_KILL);
|
||||
kill_probability = min(1.0f, kill_probability);
|
||||
|
||||
neutralized += ac.combat_value * kill_probability;
|
||||
}
|
||||
|
||||
return total_threat > 0 ? (neutralized / total_threat) : 0.0f;
|
||||
}
|
||||
```
|
||||
|
||||
### Évaluation Domaine Naval
|
||||
## Évaluation Domaine Naval
|
||||
|
||||
**Similaire à terrestre/aérien** mais avec spécificités :
|
||||
- **Torpilles vs sonars** (submersibles)
|
||||
- **Anti-ship missiles vs CIWS** (Close-In Weapon Systems)
|
||||
- **Portée extrême** : Naval combat à 100+ km
|
||||
|
||||
Structure identique avec couples spécifiques.
|
||||
Structure identique avec couples spécifiques (penetration vs armor pour missiles anti-navire).
|
||||
|
||||
## Évaluation Production
|
||||
|
||||
@ -520,33 +558,20 @@ int evaluateProduction(Entity attacker, Entity defender, int projection_months)
|
||||
- Production AT : 100 missiles/mois
|
||||
- Projection 12 mois : 1200 missiles
|
||||
- Combat value : 300/missile
|
||||
- Défense production : 1200 × 300 = 360 000
|
||||
- Effectiveness moyenne : 40%
|
||||
- Défense production : 1200 × 300 × 0.40 = 144 000
|
||||
|
||||
**Net production threat** :
|
||||
```
|
||||
Attacker gain : 240 000
|
||||
Defender gain : 360 000
|
||||
Net : 240 000 - 360 000 = -120 000 (négatif = défenseur gagne)
|
||||
Defender gain : 144 000
|
||||
Net : 240 000 - 144 000 = 96 000
|
||||
|
||||
production_threat = max(0, net) = 0
|
||||
production_threat = 96 000
|
||||
|
||||
→ État A a avantage production malgré défenses
|
||||
```
|
||||
|
||||
→ État B a **meilleure production**, donc menace production de A est nulle.
|
||||
|
||||
**Si inverse** :
|
||||
|
||||
**État A** :
|
||||
- Production : 200 tanks/mois → 2400 tanks/an
|
||||
- Threat : 2400 × 400 = 960 000
|
||||
|
||||
**État B** :
|
||||
- Production AT : 50 missiles/mois → 600 missiles/an
|
||||
- Défense : 600 × 300 = 180 000
|
||||
|
||||
**Net** : 960 000 - 180 000 = 780 000
|
||||
|
||||
→ État A a **production écrasante**, menace industrielle massive.
|
||||
|
||||
## Agrégation Finale
|
||||
|
||||
### Formule Complète
|
||||
@ -590,15 +615,15 @@ int calculateThreat(Entity attacker, Entity defender) {
|
||||
- **Total brut** : 9 700 000
|
||||
|
||||
**France défenses** :
|
||||
- AT systems : 3000 (Gen3) → Shield 50% tanks
|
||||
- AA systems : 1500 (Gen3) → Shield 30% aircraft
|
||||
- Naval defenses : 200 → Shield 40% naval
|
||||
- AT systems : 3000 (Gen3, eff 60%) → Shield 60% tanks
|
||||
- AA systems : 1500 (Gen3, eff 40% vs furtifs) → Shield 40% aircraft
|
||||
- Naval defenses : 200 (eff 50%) → Shield 50% naval
|
||||
|
||||
**Après sword & shield** :
|
||||
- Land : 3 200 000 × 0.5 = 1 600 000
|
||||
- Air : 4 000 000 × 0.7 = 2 800 000
|
||||
- Naval : 2 500 000 × 0.6 = 1 500 000
|
||||
- **Current threat** : 5 900 000
|
||||
- Land : 3 200 000 × max(0.05, 0.40) = 1 280 000
|
||||
- Air : 4 000 000 × max(0.05, 0.60) = 2 400 000
|
||||
- Naval : 2 500 000 × max(0.05, 0.50) = 1 250 000
|
||||
- **Current threat** : 4 930 000
|
||||
|
||||
**Production (12 mois)** :
|
||||
- USA : +600 tanks, +100 aircraft → 360 000
|
||||
@ -607,10 +632,10 @@ int calculateThreat(Entity attacker, Entity defender) {
|
||||
|
||||
**Total (60% current, 40% prod)** :
|
||||
```
|
||||
5 900 000 × 0.6 + 290 000 × 0.4 = 3 656 000
|
||||
```
|
||||
4 930 000 × 0.6 + 290 000 × 0.4 = 3 074 000
|
||||
|
||||
→ **Menace colossale**, mais France peut tenir avec alliances
|
||||
→ Menace colossale, mais France peut tenir avec alliances
|
||||
```
|
||||
|
||||
#### PMC vs Company
|
||||
|
||||
@ -622,13 +647,17 @@ int calculateThreat(Entity attacker, Entity defender) {
|
||||
- **Total** : 50 000
|
||||
|
||||
**Rheinmetall défenses** :
|
||||
- Security : 100 guards
|
||||
- Security : 100 guards (effectiveness 30%)
|
||||
- Fortifications : minimal
|
||||
- **Shield** : 5%
|
||||
- **Shield** : 15%
|
||||
|
||||
**Threat** : 50 000 × 0.95 = 47 500
|
||||
**Threat** :
|
||||
```
|
||||
residual = max(0.05, 1.0 - 0.15) = 85%
|
||||
final_threat = 50000 × 0.85 = 42 500
|
||||
|
||||
→ PMC peut menacer installations Company, mais pas capacités production
|
||||
→ PMC peut menacer installations Company
|
||||
```
|
||||
|
||||
## Cas Spéciaux
|
||||
|
||||
@ -680,6 +709,66 @@ float getGeographicModifier(Entity attacker, Entity defender) {
|
||||
}
|
||||
```
|
||||
|
||||
## Implications Stratégiques
|
||||
|
||||
### 1. Coût-Efficacité Défensive
|
||||
|
||||
```cpp
|
||||
struct DefenseCostAnalysis {
|
||||
float threat_neutralized;
|
||||
float cost;
|
||||
float cost_efficiency; // menace neutralisée par €
|
||||
};
|
||||
|
||||
// Neutraliser 20 Leopard 2A7 (160M€ total)
|
||||
|
||||
Option A - RPG-7 masse (effectiveness 20%) :
|
||||
- Quantité nécessaire : 100 RPG × 20 tanks = 2000 RPG
|
||||
- Coût : 2000 × 500€ = 1M€
|
||||
- Ratio : 1M€ / 160M€ = 0.625%
|
||||
- Avantage : Très économique
|
||||
- Inconvénient : Logistique massive
|
||||
|
||||
Option B - Javelin Gen3 (effectiveness 70%) :
|
||||
- Quantité nécessaire : 1.5 Javelin × 20 tanks = 30 Javelin
|
||||
- Coût : 30 × 200k€ = 6M€
|
||||
- Ratio : 6M€ / 160M€ = 3.75%
|
||||
- Avantage : Logistique légère
|
||||
- Inconvénient : Plus cher
|
||||
|
||||
→ RPG-7 est 6× plus cost-efficient, mais logistique 67× plus lourde
|
||||
→ Choix stratégique selon capacités logistiques
|
||||
```
|
||||
|
||||
### 2. Perte Asymétrique
|
||||
|
||||
```cpp
|
||||
// Exemple bataille : Menace neutralisée mais pertes coûteuses
|
||||
|
||||
Attaquant : 20 Leopard 2A7 (160M€)
|
||||
Défenseur : 5000 RPG-7 (2.5M€)
|
||||
|
||||
Outcome tactique :
|
||||
- Tous Leopards détruits (saturation)
|
||||
- 2000 RPG-7 utilisés effectivement
|
||||
|
||||
Bilan stratégique :
|
||||
Attaquant pertes :
|
||||
- 160M€ équipement
|
||||
- 20 crews élites (formation coûteuse)
|
||||
- Prestige international
|
||||
- Capacités offensives réduites
|
||||
|
||||
Défenseur pertes :
|
||||
- 1M€ munitions consommées (2000 RPG effectifs)
|
||||
- +Victoire propagande
|
||||
- +Moral défensif
|
||||
|
||||
→ Victoire tactique défenseur = victoire stratégique
|
||||
→ Système menace reflète cette asymétrie via residual 5%
|
||||
→ Perdre Leopard = toujours désastre même si menace "neutralisée"
|
||||
```
|
||||
|
||||
## Performance et Optimisation
|
||||
|
||||
### Cache Threat
|
||||
@ -732,6 +821,7 @@ public:
|
||||
- Perte unité au combat
|
||||
- Nouvelle recherche technologique
|
||||
- Changement doctrine militaire
|
||||
- Retrofit/upgrade équipement
|
||||
|
||||
### Calcul Lazy
|
||||
|
||||
@ -748,5 +838,5 @@ int getThreatOnDemand(EntityId attacker, EntityId defender) {
|
||||
|
||||
- `systeme-diplomatique.md` : Utilisation menace dans relations diplomatiques
|
||||
- `ai-framework.md` : Menace influence décisions IA (achats, alliances)
|
||||
- `systeme-militaire.md` : Valeurs combat équipements, générations
|
||||
- `systeme-militaire.md` : Valeurs combat équipements, générations, système conception modulaire
|
||||
- `economie-logistique.md` : Production rates, capacités industrielles
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
class SerializationRegistry;
|
||||
|
||||
class ASerializable {
|
||||
private:
|
||||
std::string instance_id;
|
||||
|
||||
public:
|
||||
ASerializable(const std::string& id);
|
||||
virtual ~ASerializable();
|
||||
|
||||
const std::string& getInstanceId() const { return instance_id; }
|
||||
|
||||
virtual json serialize() const = 0;
|
||||
virtual void deserialize(const json& data) = 0;
|
||||
|
||||
protected:
|
||||
void registerForSerialization();
|
||||
void unregisterFromSerialization();
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "IDataTree.h"
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
/**
|
||||
* @brief Factory for creating data tree instances
|
||||
*/
|
||||
class DataTreeFactory {
|
||||
public:
|
||||
/**
|
||||
* @brief Create data tree from configuration source
|
||||
* @param type Tree type ("json", "database", etc.)
|
||||
* @param sourcePath Path to configuration source
|
||||
* @return Data tree instance
|
||||
*/
|
||||
static std::unique_ptr<IDataTree> create(const std::string& type, const std::string& sourcePath);
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,161 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include "IModule.h"
|
||||
#include "IDataTree.h"
|
||||
|
||||
// Forward declarations
|
||||
namespace warfactory {
|
||||
class IEngine;
|
||||
class IModuleSystem;
|
||||
}
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
/**
|
||||
* @brief Global system orchestrator - First launched, last shutdown
|
||||
*
|
||||
* The CoordinationModule is the main system orchestrator that manages the entire
|
||||
* game system lifecycle, module deployment topology, and configuration synchronization.
|
||||
*
|
||||
* ARCHITECTURE FLOW:
|
||||
* 1. MainServer launches CoordinationModule (first module)
|
||||
* 2. CoordinationModule loads gameconfig.json via IDataTree
|
||||
* 3. Parses deployment section to determine module topology
|
||||
* 4. Deploys modules to local IEngine or remote servers
|
||||
* 5. Synchronizes configuration across all deployed modules
|
||||
* 6. Coordinates shutdown (last module to close)
|
||||
*
|
||||
* DESIGN DECISIONS:
|
||||
* - No state persistence: behavior driven entirely by gameconfig.json
|
||||
* - No network protocol: all communication via IIO abstraction
|
||||
* - No security for now: local/trusted environment assumed
|
||||
* - Module deployment via IModuleSystem delegation
|
||||
* - Configuration immutability via const IDataNode references
|
||||
*/
|
||||
class ICoordinationModule : public IModule {
|
||||
public:
|
||||
virtual ~ICoordinationModule() = default;
|
||||
|
||||
// ========================================
|
||||
// GAME LIFECYCLE MANAGEMENT
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Start new game session with configuration
|
||||
* @param gameConfigPath Path to gameconfig.json file
|
||||
*
|
||||
* Complete startup sequence:
|
||||
* 1. Load and parse gameconfig.json via IDataTree
|
||||
* 2. Initialize local IEngine and IModuleSystem
|
||||
* 3. Parse deployment topology from config
|
||||
* 4. Deploy local modules (target: "local")
|
||||
* 5. Launch remote servers and deploy remote modules
|
||||
* 6. Synchronize all configurations
|
||||
* 7. Return when system is ready
|
||||
*/
|
||||
virtual void startNewGame(const std::string& gameConfigPath) = 0;
|
||||
|
||||
/**
|
||||
* @brief Load existing game from save file
|
||||
* @param savePath Path to save file
|
||||
*/
|
||||
virtual void loadGame(const std::string& savePath) = 0;
|
||||
|
||||
/**
|
||||
* @brief Shutdown entire game system gracefully
|
||||
*
|
||||
* Coordinates graceful shutdown:
|
||||
* 1. Signal all modules to save state
|
||||
* 2. Undeploy remote modules first
|
||||
* 3. Undeploy local modules
|
||||
* 4. Shutdown remote servers
|
||||
* 5. Shutdown local IEngine
|
||||
* 6. CoordinationModule shuts down last
|
||||
*/
|
||||
virtual void shutdownGame() = 0;
|
||||
|
||||
// ========================================
|
||||
// MODULE DEPLOYMENT TOPOLOGY
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Deploy module according to gameconfig.json specification
|
||||
* @param moduleInstanceId Module instance ID as defined in config
|
||||
*
|
||||
* Deployment process:
|
||||
* 1. Read module config from gameconfig.json deployment section
|
||||
* 2. Determine target: "local" vs "server:IP" vs "cluster:name"
|
||||
* 3. Get module-specific configuration from modules section
|
||||
* 4. For local: delegate to local IEngine->IModuleSystem
|
||||
* 5. For remote: send deployment command to remote server
|
||||
* 6. Pass const IDataNode& configuration to module
|
||||
*/
|
||||
virtual void deployModule(const std::string& moduleInstanceId) = 0;
|
||||
|
||||
/**
|
||||
* @brief Stop and undeploy module instance
|
||||
* @param moduleInstanceId Module instance ID to undeploy
|
||||
*/
|
||||
virtual void undeployModule(const std::string& moduleInstanceId) = 0;
|
||||
|
||||
/**
|
||||
* @brief Get list of currently deployed module instances
|
||||
* @return Vector of module instance IDs currently running
|
||||
*/
|
||||
virtual std::vector<std::string> getDeployedModules() = 0;
|
||||
|
||||
// ========================================
|
||||
// CONFIGURATION SYNCHRONIZATION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Synchronize configuration changes to all deployed modules
|
||||
*
|
||||
* Process:
|
||||
* 1. Reload gameconfig.json via IDataTree hot-reload
|
||||
* 2. For each deployed module, get updated configuration
|
||||
* 3. Call module->setConfiguration() with new const IDataNode&
|
||||
* 4. Handle any modules that fail to reconfigure
|
||||
*/
|
||||
virtual void syncConfiguration() = 0;
|
||||
|
||||
/**
|
||||
* @brief Set configuration tree for the coordination system
|
||||
* @param configTree Configuration data tree loaded from gameconfig.json
|
||||
*/
|
||||
virtual void setConfigurationTree(std::unique_ptr<IDataTree> configTree) = 0;
|
||||
|
||||
/**
|
||||
* @brief Get current configuration tree
|
||||
* @return Configuration tree pointer for accessing gameconfig.json data
|
||||
*/
|
||||
virtual IDataTree* getConfigurationTree() = 0;
|
||||
|
||||
// ========================================
|
||||
// SYSTEM HEALTH AND MANAGEMENT
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Check if all deployed modules are healthy and responsive
|
||||
* @return true if system is healthy, false if issues detected
|
||||
*
|
||||
* Aggregates health status from all deployed modules:
|
||||
* - Calls getHealthStatus() on each module
|
||||
* - Checks network connectivity to remote servers
|
||||
* - Validates configuration consistency
|
||||
* - Could trigger auto-save in future versions
|
||||
*/
|
||||
virtual bool isSystemHealthy() = 0;
|
||||
|
||||
/**
|
||||
* @brief Get detailed system health report
|
||||
* @return JSON health report aggregating all module health status
|
||||
*/
|
||||
virtual json getSystemHealthReport() = 0;
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,233 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
/**
|
||||
* @brief Interface for a single node in the data tree
|
||||
*
|
||||
* Each node can have:
|
||||
* - Children nodes (tree navigation)
|
||||
* - Its own data blob (JSON)
|
||||
* - Properties accessible by name with type safety
|
||||
*/
|
||||
class IDataNode {
|
||||
public:
|
||||
virtual ~IDataNode() = default;
|
||||
|
||||
// ========================================
|
||||
// TREE NAVIGATION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Get direct child by name
|
||||
* @param name Exact name of the child
|
||||
* @return Child node or nullptr if not found
|
||||
*/
|
||||
virtual std::unique_ptr<IDataNode> getChild(const std::string& name) = 0;
|
||||
|
||||
/**
|
||||
* @brief Get names of all direct children
|
||||
* @return Vector of child names
|
||||
*/
|
||||
virtual std::vector<std::string> getChildNames() = 0;
|
||||
|
||||
/**
|
||||
* @brief Check if this node has any children
|
||||
* @return true if children exist
|
||||
*/
|
||||
virtual bool hasChildren() = 0;
|
||||
|
||||
// ========================================
|
||||
// EXACT SEARCH IN CHILDREN
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Find all children with exact name (direct children only)
|
||||
* @param name Exact name to search for
|
||||
* @return Vector of matching child nodes
|
||||
*/
|
||||
virtual std::vector<IDataNode*> getChildrenByName(const std::string& name) = 0;
|
||||
|
||||
/**
|
||||
* @brief Check if any children have the exact name
|
||||
* @param name Exact name to search for
|
||||
* @return true if found
|
||||
*/
|
||||
virtual bool hasChildrenByName(const std::string& name) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get first child with exact name
|
||||
* @param name Exact name to search for
|
||||
* @return First matching child or nullptr
|
||||
*/
|
||||
virtual IDataNode* getFirstChildByName(const std::string& name) = 0;
|
||||
|
||||
// ========================================
|
||||
// PATTERN MATCHING SEARCH (DEEP SEARCH IN WHOLE SUBTREE)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Find all nodes in subtree matching pattern
|
||||
* @param pattern Pattern with wildcards (* supported)
|
||||
* @return Vector of matching nodes in entire subtree
|
||||
*
|
||||
* Examples:
|
||||
* - "component*" matches "component_armor", "component_engine"
|
||||
* - "*heavy*" matches "tank_heavy_mk1", "artillery_heavy"
|
||||
* - "model_*" matches "model_01", "model_02"
|
||||
*/
|
||||
virtual std::vector<IDataNode*> getChildrenByNameMatch(const std::string& pattern) = 0;
|
||||
|
||||
/**
|
||||
* @brief Check if any nodes in subtree match pattern
|
||||
* @param pattern Pattern with wildcards
|
||||
* @return true if any matches found
|
||||
*/
|
||||
virtual bool hasChildrenByNameMatch(const std::string& pattern) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get first node in subtree matching pattern
|
||||
* @param pattern Pattern with wildcards
|
||||
* @return First matching node or nullptr
|
||||
*/
|
||||
virtual IDataNode* getFirstChildByNameMatch(const std::string& pattern) = 0;
|
||||
|
||||
// ========================================
|
||||
// QUERY BY PROPERTIES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Query nodes in subtree by property value
|
||||
* @param propName Property name to check
|
||||
* @param predicate Function to test property value
|
||||
* @return Vector of nodes where predicate returns true
|
||||
*
|
||||
* Example:
|
||||
* // Find all tanks with armor > 150
|
||||
* queryByProperty("armor", [](const json& val) {
|
||||
* return val.is_number() && val.get<int>() > 150;
|
||||
* });
|
||||
*/
|
||||
virtual std::vector<IDataNode*> queryByProperty(const std::string& propName,
|
||||
const std::function<bool(const json&)>& predicate) = 0;
|
||||
|
||||
// ========================================
|
||||
// NODE'S OWN DATA
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Get this node's data blob
|
||||
* @return JSON data or empty JSON if no data
|
||||
*/
|
||||
virtual json getData() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Check if this node has data
|
||||
* @return true if data exists
|
||||
*/
|
||||
virtual bool hasData() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Set this node's data
|
||||
* @param data JSON data to set
|
||||
*/
|
||||
virtual void setData(const json& data) = 0;
|
||||
|
||||
// ========================================
|
||||
// TYPED DATA ACCESS BY PROPERTY NAME
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Get string property from this node's data
|
||||
* @param name Property name
|
||||
* @param defaultValue Default if property not found or wrong type
|
||||
* @return Property value or default
|
||||
*/
|
||||
virtual std::string getString(const std::string& name, const std::string& defaultValue = "") const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get integer property from this node's data
|
||||
* @param name Property name
|
||||
* @param defaultValue Default if property not found or wrong type
|
||||
* @return Property value or default
|
||||
*/
|
||||
virtual int getInt(const std::string& name, int defaultValue = 0) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get double property from this node's data
|
||||
* @param name Property name
|
||||
* @param defaultValue Default if property not found or wrong type
|
||||
* @return Property value or default
|
||||
*/
|
||||
virtual double getDouble(const std::string& name, double defaultValue = 0.0) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get boolean property from this node's data
|
||||
* @param name Property name
|
||||
* @param defaultValue Default if property not found or wrong type
|
||||
* @return Property value or default
|
||||
*/
|
||||
virtual bool getBool(const std::string& name, bool defaultValue = false) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Check if property exists in this node's data
|
||||
* @param name Property name
|
||||
* @return true if property exists
|
||||
*/
|
||||
virtual bool hasProperty(const std::string& name) const = 0;
|
||||
|
||||
// ========================================
|
||||
// HASH SYSTEM FOR VALIDATION & SYNCHRO
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Get hash of this node's data only
|
||||
* @return SHA256 hash of data blob
|
||||
*/
|
||||
virtual std::string getDataHash() = 0;
|
||||
|
||||
/**
|
||||
* @brief Get recursive hash of this node and all children
|
||||
* @return SHA256 hash of entire subtree
|
||||
*/
|
||||
virtual std::string getTreeHash() = 0;
|
||||
|
||||
/**
|
||||
* @brief Get hash of specific child subtree
|
||||
* @param childPath Path to child from this node
|
||||
* @return SHA256 hash of child subtree
|
||||
*/
|
||||
virtual std::string getSubtreeHash(const std::string& childPath) = 0;
|
||||
|
||||
// ========================================
|
||||
// METADATA
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Get full path from root to this node
|
||||
* @return Path string (e.g., "vehicles/tanks/heavy/model5")
|
||||
*/
|
||||
virtual std::string getPath() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get this node's name
|
||||
* @return Node name
|
||||
*/
|
||||
virtual std::string getName() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get node type (extensible for templates/inheritance later)
|
||||
* @return Node type identifier
|
||||
*/
|
||||
virtual std::string getNodeType() const = 0;
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,69 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include "IDataNode.h"
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
/**
|
||||
* @brief Interface for the root data tree container
|
||||
*
|
||||
* Manages the entire tree structure and provides hot-reload capabilities
|
||||
*/
|
||||
class IDataTree {
|
||||
public:
|
||||
virtual ~IDataTree() = default;
|
||||
|
||||
// ========================================
|
||||
// TREE ACCESS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Get root node of the tree
|
||||
* @return Root node
|
||||
*/
|
||||
virtual std::unique_ptr<IDataNode> getRoot() = 0;
|
||||
|
||||
/**
|
||||
* @brief Get node by path from root
|
||||
* @param path Path from root (e.g., "vehicles/tanks/heavy")
|
||||
* @return Node at path or nullptr if not found
|
||||
*/
|
||||
virtual std::unique_ptr<IDataNode> getNode(const std::string& path) = 0;
|
||||
|
||||
// ========================================
|
||||
// MANUAL HOT-RELOAD (SIMPLE & EFFECTIVE)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Check if source files have changed
|
||||
* @return true if changes detected
|
||||
*/
|
||||
virtual bool checkForChanges() = 0;
|
||||
|
||||
/**
|
||||
* @brief Reload entire tree if changes detected
|
||||
* @return true if reload was performed
|
||||
*/
|
||||
virtual bool reloadIfChanged() = 0;
|
||||
|
||||
/**
|
||||
* @brief Register callback for when tree is reloaded
|
||||
* @param callback Function called after successful reload
|
||||
*/
|
||||
virtual void onTreeReloaded(std::function<void()> callback) = 0;
|
||||
|
||||
// ========================================
|
||||
// METADATA
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Get tree implementation type
|
||||
* @return Type identifier (e.g., "JSONDataTree", "DatabaseDataTree")
|
||||
*/
|
||||
virtual std::string getType() = 0;
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,125 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// Forward declarations to avoid circular dependencies
|
||||
namespace warfactory {
|
||||
class IModuleSystem;
|
||||
}
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
enum class EngineType {
|
||||
DEBUG = 0,
|
||||
PRODUCTION = 1,
|
||||
HIGH_PERFORMANCE = 2
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Engine orchestration interface - coordinates the entire system
|
||||
*
|
||||
* The engine is responsible for:
|
||||
* - System initialization and lifecycle management
|
||||
* - Main game loop coordination with delta time updates
|
||||
* - Module system orchestration
|
||||
* - IIO health monitoring and backpressure management
|
||||
*
|
||||
* IMPORTANT: Engine implementations must periodically check IIO health:
|
||||
* - Monitor IOHealth.queueSize vs maxQueueSize (warn at 80% full)
|
||||
* - Track IOHealth.dropping status (critical - consider module restart)
|
||||
* - Log IOHealth.droppedMessageCount for debugging
|
||||
* - Monitor IOHealth.averageProcessingRate for performance analysis
|
||||
*
|
||||
* Evolution path:
|
||||
* - DebugEngine: Development/testing with step-by-step execution
|
||||
* - HighPerfEngine: Production optimized with threading
|
||||
* - DataOrientedEngine: Massive scale with SIMD and clustering
|
||||
*/
|
||||
class IEngine {
|
||||
public:
|
||||
virtual ~IEngine() = default;
|
||||
|
||||
/**
|
||||
* @brief Initialize engine systems
|
||||
*
|
||||
* Sets up the engine with basic configuration.
|
||||
* Module system and other components are set separately.
|
||||
*/
|
||||
virtual void initialize() = 0;
|
||||
|
||||
/**
|
||||
* @brief Start main game loop
|
||||
*
|
||||
* Blocks until shutdown() called. Engine owns the main loop and handles:
|
||||
* - Frame timing and delta time calculation
|
||||
* - Module system coordination
|
||||
* - Performance management and frame rate control
|
||||
*/
|
||||
virtual void run() = 0;
|
||||
|
||||
/**
|
||||
* @brief Process single frame/tick (for debugging)
|
||||
* @param deltaTime Time elapsed since last update in seconds
|
||||
*
|
||||
* For step debugging and testing. Processes one iteration
|
||||
* without entering the main loop.
|
||||
*/
|
||||
virtual void step(float deltaTime) = 0;
|
||||
|
||||
/**
|
||||
* @brief Shutdown engine and cleanup all resources
|
||||
*
|
||||
* Ensures proper cleanup of all systems in correct order.
|
||||
* Should be safe to call multiple times. Stops run() loop.
|
||||
*/
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
/**
|
||||
* @brief Load modules from configuration
|
||||
* @param configPath Path to module configuration file
|
||||
*
|
||||
* Engine automatically:
|
||||
* - Loads modules from .so/.dll files
|
||||
* - Creates appropriate ModuleSystem for each module (performance strategy)
|
||||
* - Configures execution frequency and coordination
|
||||
*
|
||||
* Config format:
|
||||
* {
|
||||
* "modules": [
|
||||
* {"path": "tank.so", "strategy": "threaded", "frequency": "60hz"},
|
||||
* {"path": "economy.so", "strategy": "sequential", "frequency": "0.1hz"}
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
virtual void loadModules(const std::string& configPath) = 0;
|
||||
|
||||
/**
|
||||
* @brief Register main coordinator socket
|
||||
* @param coordinatorSocket Socket for system coordination communication
|
||||
*
|
||||
* Engine uses this socket for high-level system coordination,
|
||||
* health monitoring, and administrative commands.
|
||||
*/
|
||||
virtual void registerMainSocket(std::unique_ptr<IIO> coordinatorSocket) = 0;
|
||||
|
||||
/**
|
||||
* @brief Register new client/player socket
|
||||
* @param clientSocket Socket for player communication
|
||||
*
|
||||
* Engine manages player connections as a priority channel.
|
||||
* Players are the most important external connections.
|
||||
*/
|
||||
virtual void registerNewClientSocket(std::unique_ptr<IIO> clientSocket) = 0;
|
||||
|
||||
/**
|
||||
* @brief Get engine type identifier
|
||||
* @return Engine type enum value for identification
|
||||
*/
|
||||
virtual EngineType getType() const = 0;
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,102 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
enum class IOType {
|
||||
INTRA = 0, // Same process
|
||||
LOCAL = 1, // Same machine
|
||||
NETWORK = 2 // TCP/WebSocket
|
||||
};
|
||||
|
||||
struct SubscriptionConfig {
|
||||
bool replaceable = false; // Replace vs accumulate for low-freq
|
||||
int batchInterval = 30000; // ms for low-freq batching
|
||||
int maxBatchSize = 100; // Max messages per batch
|
||||
bool compress = false; // Compress batched data
|
||||
};
|
||||
|
||||
struct Message {
|
||||
std::string topic;
|
||||
json data;
|
||||
uint64_t timestamp;
|
||||
};
|
||||
|
||||
struct IOHealth {
|
||||
int queueSize;
|
||||
int maxQueueSize;
|
||||
bool dropping = false; // Started dropping messages?
|
||||
float averageProcessingRate; // Messages/second processed by module
|
||||
int droppedMessageCount = 0; // Total dropped since last check
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Pub/Sub communication interface with pull-based synchronous design
|
||||
*
|
||||
* Pull-based pub/sub system optimized for game modules. Modules have full control
|
||||
* over when they process messages, avoiding threading issues.
|
||||
*
|
||||
* Features:
|
||||
* - Topic patterns with wildcards (e.g., "player:*", "economy:*")
|
||||
* - Low-frequency subscriptions for bandwidth optimization
|
||||
* - Message consumption (pull removes message from queue)
|
||||
* - Engine health monitoring for backpressure management
|
||||
*/
|
||||
class IIO {
|
||||
public:
|
||||
virtual ~IIO() = default;
|
||||
|
||||
/**
|
||||
* @brief Publish message to a topic
|
||||
* @param topic Topic name (e.g., "player:123", "economy:prices")
|
||||
* @param message JSON message data
|
||||
*/
|
||||
virtual void publish(const std::string& topic, const json& message) = 0;
|
||||
|
||||
/**
|
||||
* @brief Subscribe to topic pattern (high-frequency)
|
||||
* @param topicPattern Topic pattern with wildcards (e.g., "player:*")
|
||||
* @param config Optional subscription configuration
|
||||
*/
|
||||
virtual void subscribe(const std::string& topicPattern, const SubscriptionConfig& config = {}) = 0;
|
||||
|
||||
/**
|
||||
* @brief Subscribe to topic pattern (low-frequency batched)
|
||||
* @param topicPattern Topic pattern with wildcards
|
||||
* @param config Subscription configuration (batchInterval, etc.)
|
||||
*/
|
||||
virtual void subscribeLowFreq(const std::string& topicPattern, const SubscriptionConfig& config = {}) = 0;
|
||||
|
||||
/**
|
||||
* @brief Get count of pending messages
|
||||
* @return Number of messages waiting to be pulled
|
||||
*/
|
||||
virtual int hasMessages() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Pull and consume one message
|
||||
* @return Message from queue (oldest first). Message is removed from queue.
|
||||
* @throws std::runtime_error if no messages available
|
||||
*/
|
||||
virtual Message pullMessage() = 0;
|
||||
|
||||
/**
|
||||
* @brief Get IO health status for Engine monitoring
|
||||
* @return Health metrics including queue size, drop status, processing rate
|
||||
*/
|
||||
virtual IOHealth getHealth() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get IO type identifier
|
||||
* @return IO type enum value for identification
|
||||
*/
|
||||
virtual IOType getType() const = 0;
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,113 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "IDataNode.h"
|
||||
#include "ITaskScheduler.h"
|
||||
|
||||
// Forward declarations
|
||||
namespace warfactory {
|
||||
class IIO;
|
||||
}
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Pure business logic interface - optimized for Claude Code development
|
||||
*
|
||||
* This interface defines the contract for all game modules. Each module contains
|
||||
* 200-300 lines of pure game logic with zero infrastructure code.
|
||||
*
|
||||
* Key design principles:
|
||||
* - PURE FUNCTION: process() method has no side effects beyond return value
|
||||
* - CONFIG VIA DATATREE: Configuration via immutable IDataNode references
|
||||
* - JSON ONLY: All communication via JSON input/output
|
||||
* - NO INFRASTRUCTURE: No threading, networking, or framework dependencies
|
||||
* - HOT-RELOAD READY: State serialization for seamless module replacement
|
||||
* - CLAUDE OPTIMIZED: Micro-context size for AI development efficiency
|
||||
*
|
||||
* BREAKING CHANGES:
|
||||
* - Removed initialize() method - use setConfiguration() instead
|
||||
* - Configuration via const IDataNode& for immutability
|
||||
* - Health check returns detailed JSON status
|
||||
*
|
||||
* Module constraint: Maximum 300 lines per module (Exception: ProductionModule 500-800 lines)
|
||||
*/
|
||||
class IModule {
|
||||
public:
|
||||
virtual ~IModule() = default;
|
||||
|
||||
/**
|
||||
* @brief Process game logic
|
||||
* @param input JSON input from other modules or the module system
|
||||
*
|
||||
* This is the core method where all module logic is implemented.
|
||||
* Modules communicate via IIO pub/sub and can delegate tasks via ITaskScheduler.
|
||||
* Must handle state properly through getState/setState for hot-reload.
|
||||
*/
|
||||
virtual void process(const json& input) = 0;
|
||||
|
||||
/**
|
||||
* @brief Set module configuration (replaces initialize)
|
||||
* @param configNode Configuration node (immutable reference)
|
||||
* @param io Pub/sub communication interface for messaging
|
||||
* @param scheduler Task scheduling interface for delegating work
|
||||
*
|
||||
* Called when the module is loaded or configuration changes.
|
||||
* Should setup internal state, validate configuration, and store service references.
|
||||
*/
|
||||
virtual void setConfiguration(const IDataNode& configNode, IIO* io, ITaskScheduler* scheduler) = 0;
|
||||
|
||||
/**
|
||||
* @brief Get current module configuration
|
||||
* @return Configuration node reference
|
||||
*/
|
||||
virtual const IDataNode& getConfiguration() = 0;
|
||||
|
||||
/**
|
||||
* @brief Get detailed health status of the module
|
||||
* @return JSON health report with status, metrics, and diagnostics
|
||||
*/
|
||||
virtual json getHealthStatus() = 0;
|
||||
|
||||
/**
|
||||
* @brief Cleanup and shutdown the module
|
||||
*
|
||||
* Called when the module is being unloaded. Should clean up any
|
||||
* resources and prepare for safe destruction.
|
||||
*/
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
/**
|
||||
* @brief Get current module state for hot-reload support
|
||||
* @return JSON representation of all module state
|
||||
*
|
||||
* Critical for hot-reload functionality. Must serialize all internal
|
||||
* state that needs to be preserved when the module is replaced.
|
||||
* The returned JSON should be sufficient to restore the module to
|
||||
* its current state via setState().
|
||||
*/
|
||||
virtual json getState() = 0;
|
||||
|
||||
/**
|
||||
* @brief Restore module state after hot-reload
|
||||
* @param state JSON state previously returned by getState()
|
||||
*
|
||||
* Called after module replacement to restore the previous state.
|
||||
* Must be able to reconstruct all internal state from the JSON
|
||||
* to ensure seamless hot-reload without game disruption.
|
||||
*/
|
||||
virtual void setState(const json& state) = 0;
|
||||
|
||||
/**
|
||||
* @brief Get module type identifier
|
||||
* @return Module type as string (e.g., "tank", "economy", "production")
|
||||
*/
|
||||
virtual std::string getType() const = 0;
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,95 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "ITaskScheduler.h"
|
||||
|
||||
// Forward declarations to avoid circular dependencies
|
||||
namespace warfactory {
|
||||
class IModule;
|
||||
class IIO;
|
||||
}
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
enum class ModuleSystemType {
|
||||
SEQUENTIAL = 0,
|
||||
THREADED = 1,
|
||||
THREAD_POOL = 2,
|
||||
CLUSTER = 3
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Module execution strategy interface - swappable performance architecture
|
||||
*
|
||||
* The module system manages module lifecycle and execution strategy.
|
||||
* Different implementations provide different performance characteristics:
|
||||
*
|
||||
* - SequentialModuleSystem: Debug/test mode, processes modules one at a time
|
||||
* - ThreadedModuleSystem: Each module in its own thread
|
||||
* - MultithreadedModuleSystem: Module tasks distributed across thread pool
|
||||
* - ClusterModuleSystem: Modules distributed across multiple machines
|
||||
*
|
||||
* This enables progressive evolution from debug to production to MMO scale
|
||||
* without changing any module business logic code.
|
||||
*
|
||||
* Inherits from ITaskScheduler to provide task delegation capabilities.
|
||||
*/
|
||||
class IModuleSystem : public ITaskScheduler {
|
||||
public:
|
||||
virtual ~IModuleSystem() = default;
|
||||
|
||||
/**
|
||||
* @brief Register a module with the system
|
||||
* @param name Unique identifier for the module
|
||||
* @param module Module implementation (unique ownership)
|
||||
*
|
||||
* The module system takes ownership of the module and manages its lifecycle.
|
||||
* Modules can be registered at any time and will participate in the next
|
||||
* processing cycle.
|
||||
*/
|
||||
virtual void registerModule(const std::string& name, std::unique_ptr<IModule> module) = 0;
|
||||
|
||||
/**
|
||||
* @brief Process all registered modules
|
||||
* @param deltaTime Time elapsed since last processing cycle in seconds
|
||||
*
|
||||
* This is the core execution method that coordinates all modules according
|
||||
* to the implemented strategy. Each module's process() method will be called
|
||||
* with appropriate timing and coordination.
|
||||
*/
|
||||
virtual void processModules(float deltaTime) = 0;
|
||||
|
||||
/**
|
||||
* @brief Set the IO layer for inter-module communication
|
||||
* @param ioLayer Communication transport implementation (unique ownership)
|
||||
*
|
||||
* The module system takes ownership of the IO layer and uses it to
|
||||
* facilitate communication between modules.
|
||||
*/
|
||||
virtual void setIOLayer(std::unique_ptr<IIO> ioLayer) = 0;
|
||||
|
||||
/**
|
||||
* @brief Query a specific module directly
|
||||
* @param name Name of the module to query
|
||||
* @param input JSON input to send to the module
|
||||
* @return JSON response from the module
|
||||
*
|
||||
* This provides direct access to module functionality for debugging,
|
||||
* testing, or administrative purposes. The query bypasses normal
|
||||
* execution flow and calls the module's process() method directly.
|
||||
*/
|
||||
virtual json queryModule(const std::string& name, const json& input) = 0;
|
||||
|
||||
/**
|
||||
* @brief Get module system type identifier
|
||||
* @return Module system type enum value for identification
|
||||
*/
|
||||
virtual ModuleSystemType getType() const = 0;
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,50 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
/**
|
||||
* @brief Interface for geological regions during world generation
|
||||
*
|
||||
* Represents discrete regions with specific properties like resource deposits,
|
||||
* volcanic activity, or tectonic formations.
|
||||
*/
|
||||
class IRegion {
|
||||
public:
|
||||
virtual ~IRegion() = default;
|
||||
|
||||
// ========================================
|
||||
// IDENTIFICATION
|
||||
// ========================================
|
||||
|
||||
virtual int getId() const = 0;
|
||||
virtual const std::string& getType() const = 0;
|
||||
|
||||
// ========================================
|
||||
// POSITION & SIZE
|
||||
// ========================================
|
||||
|
||||
virtual float getX() const = 0;
|
||||
virtual float getY() const = 0;
|
||||
virtual float getRadius() const = 0;
|
||||
virtual float getMass() const = 0;
|
||||
|
||||
// ========================================
|
||||
// LIFECYCLE
|
||||
// ========================================
|
||||
|
||||
virtual void update(float delta_time) = 0;
|
||||
virtual bool isActive() const = 0;
|
||||
|
||||
// ========================================
|
||||
// PROPERTIES
|
||||
// ========================================
|
||||
|
||||
virtual float getIntensity() const = 0;
|
||||
virtual void setIntensity(float intensity) = 0;
|
||||
|
||||
virtual bool canFuseWith(const IRegion* other) const = 0;
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
class ISerializable {
|
||||
public:
|
||||
virtual ~ISerializable() = default;
|
||||
|
||||
virtual json serialize() const = 0;
|
||||
virtual void deserialize(const json& data) = 0;
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,102 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
/**
|
||||
* @brief Task scheduling interface for module delegation to execution system
|
||||
*
|
||||
* ITaskScheduler allows modules to delegate computationally expensive or
|
||||
* time-consuming tasks to the underlying execution system without knowing
|
||||
* the implementation details (sequential, threaded, thread pool, cluster).
|
||||
*
|
||||
* CORE PURPOSE:
|
||||
* - Modules stay lightweight (200-300 lines) by delegating heavy work
|
||||
* - Execution strategy determined by IModuleSystem implementation
|
||||
* - Modules remain thread-agnostic and infrastructure-free
|
||||
*
|
||||
* USAGE PATTERNS:
|
||||
* - ProductionModule: Delegate belt pathfinding calculations
|
||||
* - TankModule: Delegate A* pathfinding for unit movement
|
||||
* - EconomyModule: Delegate market analysis and price calculations
|
||||
* - FactoryModule: Delegate assembly line optimization
|
||||
*
|
||||
* EXECUTION STRATEGIES:
|
||||
* - SequentialModuleSystem: Tasks executed immediately in same thread
|
||||
* - ThreadedModuleSystem: Tasks executed in dedicated module thread
|
||||
* - MultithreadedModuleSystem: Tasks distributed across thread pool
|
||||
* - ClusterModuleSystem: Tasks distributed across remote workers
|
||||
*
|
||||
* PERFORMANCE BENEFIT:
|
||||
* - Modules process() methods stay fast (< 1ms for 60Hz modules)
|
||||
* - Heavy computation moved to background without blocking game loop
|
||||
* - Automatic scaling based on IModuleSystem implementation
|
||||
*/
|
||||
class ITaskScheduler {
|
||||
public:
|
||||
virtual ~ITaskScheduler() = default;
|
||||
|
||||
/**
|
||||
* @brief Schedule a task for execution
|
||||
* @param taskType Type of task (e.g., "pathfinding", "market_analysis", "belt_optimization")
|
||||
* @param taskData JSON data for the task
|
||||
*
|
||||
* Example usage:
|
||||
* ```cpp
|
||||
* // TankModule delegates pathfinding
|
||||
* scheduler->scheduleTask("pathfinding", {
|
||||
* {"start", {x: 100, y: 200}},
|
||||
* {"target", {x: 500, y: 600}},
|
||||
* {"unit_id", "tank_001"}
|
||||
* });
|
||||
*
|
||||
* // ProductionModule delegates belt calculation
|
||||
* scheduler->scheduleTask("belt_optimization", {
|
||||
* {"factory_id", "main_base"},
|
||||
* {"item_type", "iron_plate"},
|
||||
* {"throughput_target", 240}
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
virtual void scheduleTask(const std::string& taskType, const json& taskData) = 0;
|
||||
|
||||
/**
|
||||
* @brief Check if completed tasks are available
|
||||
* @return Number of completed tasks ready to be pulled
|
||||
*
|
||||
* Modules should check this before calling getCompletedTask()
|
||||
* to avoid blocking or polling unnecessarily.
|
||||
*/
|
||||
virtual int hasCompletedTasks() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Pull and consume one completed task
|
||||
* @return Task result JSON. Task is removed from completed queue.
|
||||
*
|
||||
* Example results:
|
||||
* ```cpp
|
||||
* // Pathfinding result
|
||||
* {
|
||||
* "task_type": "pathfinding",
|
||||
* "unit_id": "tank_001",
|
||||
* "path": [{"x": 100, "y": 200}, {"x": 150, "y": 250}, ...],
|
||||
* "cost": 42.5
|
||||
* }
|
||||
*
|
||||
* // Belt optimization result
|
||||
* {
|
||||
* "task_type": "belt_optimization",
|
||||
* "factory_id": "main_base",
|
||||
* "optimal_layout": [...],
|
||||
* "efficiency_gain": 0.15
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
virtual json getCompletedTask() = 0;
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,129 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
/**
|
||||
* @brief Pure Generic UI Interface - Zero assumptions about content
|
||||
*
|
||||
* Completely data-agnostic. Implementation decides how to handle each data type.
|
||||
*/
|
||||
class IUI {
|
||||
public:
|
||||
virtual ~IUI() = default;
|
||||
|
||||
// ========================================
|
||||
// LIFECYCLE
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Initialize UI system
|
||||
* @param config Generic config, implementation interprets
|
||||
*/
|
||||
virtual void initialize(const json& config) = 0;
|
||||
|
||||
/**
|
||||
* @brief Update/render one frame
|
||||
* @return true to continue, false to quit
|
||||
*/
|
||||
virtual bool update() = 0;
|
||||
|
||||
/**
|
||||
* @brief Clean shutdown
|
||||
*/
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
// ========================================
|
||||
// GENERIC DATA FLOW
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Display any data of any type with layout/windowing info
|
||||
* @param dataType "economy", "map", "inventory", "status", whatever
|
||||
* @param data JSON with content + layout:
|
||||
* {
|
||||
* "content": {...}, // Actual data to display
|
||||
* "window": { // Window/layout configuration
|
||||
* "id": "economy_main", // Unique window ID
|
||||
* "title": "Economy Dashboard",
|
||||
* "parent": "main_dock", // Parent window/dock ID (optional)
|
||||
* "dock": "left", // Dock position: "left", "right", "top", "bottom", "center", "tab"
|
||||
* "size": {"width": 400, "height": 300},
|
||||
* "position": {"x": 100, "y": 50},
|
||||
* "floating": false, // true = floating window, false = docked
|
||||
* "resizable": true,
|
||||
* "closeable": true
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
virtual void showData(const std::string& dataType, const json& data) = 0;
|
||||
|
||||
/**
|
||||
* @brief Handle any user request of any type
|
||||
* @param requestType "get_prices", "move_unit", "save_game", whatever
|
||||
* @param callback Function to call when request happens
|
||||
*/
|
||||
virtual void onRequest(const std::string& requestType, std::function<void(const json&)> callback) = 0;
|
||||
|
||||
/**
|
||||
* @brief Show any event/message
|
||||
* @param level "info", "error", "debug", whatever
|
||||
* @param message Human readable text
|
||||
*/
|
||||
virtual void showEvent(const std::string& level, const std::string& message) = 0;
|
||||
|
||||
// ========================================
|
||||
// WINDOW MANAGEMENT
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Create or configure a dock/container window
|
||||
* @param dockId Unique dock identifier
|
||||
* @param config Dock configuration:
|
||||
* {
|
||||
* "type": "dock", // "dock", "tabbed", "split"
|
||||
* "orientation": "horizontal", // "horizontal", "vertical" (for splits)
|
||||
* "parent": "main_window", // Parent dock (for nested docks)
|
||||
* "position": "left", // Initial position
|
||||
* "size": {"width": 300}, // Initial size
|
||||
* "collapsible": true, // Can be collapsed
|
||||
* "tabs": true // Enable tabbed interface
|
||||
* }
|
||||
*/
|
||||
virtual void createDock(const std::string& dockId, const json& config) = 0;
|
||||
|
||||
/**
|
||||
* @brief Close/remove window or dock
|
||||
* @param windowId Window or dock ID to close
|
||||
*/
|
||||
virtual void closeWindow(const std::string& windowId) = 0;
|
||||
|
||||
/**
|
||||
* @brief Focus/bring to front a specific window
|
||||
* @param windowId Window ID to focus
|
||||
*/
|
||||
virtual void focusWindow(const std::string& windowId) = 0;
|
||||
|
||||
// ========================================
|
||||
// GENERIC STATE
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Get current UI state
|
||||
* @return JSON state, implementation defines structure
|
||||
*/
|
||||
virtual json getState() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Restore UI state
|
||||
* @param state JSON state from previous getState()
|
||||
*/
|
||||
virtual void setState(const json& state) = 0;
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,340 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// ========================================
|
||||
// ENUMS FOR TYPE SAFETY
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Data types for UI display
|
||||
*/
|
||||
enum class DataType {
|
||||
ECONOMY,
|
||||
MAP,
|
||||
INVENTORY,
|
||||
CONSOLE,
|
||||
PERFORMANCE,
|
||||
COMPANIES,
|
||||
ALERTS,
|
||||
PRODUCTION,
|
||||
LOGISTICS,
|
||||
PLAYER,
|
||||
SETTINGS,
|
||||
DEBUG,
|
||||
CUSTOM // For extending with string fallback
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Request types from UI
|
||||
*/
|
||||
enum class RequestType {
|
||||
GET_PRICES,
|
||||
GET_CHUNK,
|
||||
MOVE_PLAYER,
|
||||
SAVE_GAME,
|
||||
LOAD_GAME,
|
||||
CLOSE_WINDOW,
|
||||
FOCUS_WINDOW,
|
||||
UPDATE_SETTINGS,
|
||||
EXECUTE_COMMAND,
|
||||
CUSTOM // For extending with string fallback
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Event/message levels
|
||||
*/
|
||||
enum class EventLevel {
|
||||
INFO,
|
||||
SUCCESS,
|
||||
WARNING,
|
||||
ERROR,
|
||||
DEBUG,
|
||||
TRACE
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Dock types for window management
|
||||
*/
|
||||
enum class DockType {
|
||||
DOCK, // Standard dockable panel
|
||||
SPLIT, // Horizontal/vertical split
|
||||
TABBED, // Tabbed container
|
||||
FLOATING // Floating window
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Dock positions
|
||||
*/
|
||||
enum class DockPosition {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
TOP,
|
||||
BOTTOM,
|
||||
CENTER,
|
||||
TAB // Add as tab to parent
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Split orientations
|
||||
*/
|
||||
enum class Orientation {
|
||||
HORIZONTAL,
|
||||
VERTICAL
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Pure Generic UI Interface - Type-safe with enums
|
||||
*/
|
||||
class IUI {
|
||||
public:
|
||||
virtual ~IUI() = default;
|
||||
|
||||
// ========================================
|
||||
// LIFECYCLE
|
||||
// ========================================
|
||||
|
||||
virtual void initialize(const json& config) = 0;
|
||||
virtual bool update() = 0;
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
// ========================================
|
||||
// GENERIC DATA FLOW - ENUM VERSIONS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Display data with type-safe enum
|
||||
* @param dataType Enum data type
|
||||
* @param data JSON with content + optional window config:
|
||||
* {
|
||||
* "content": {...}, // Actual data to display
|
||||
* "window": { // Window configuration (optional)
|
||||
* "id": "window_id",
|
||||
* "title": "Window Title",
|
||||
* "parent": "parent_dock_id",
|
||||
* "dock": "left|right|top|bottom|center|tab",
|
||||
*
|
||||
* // SIZE SYSTEM - Hybrid percentage + absolute constraints
|
||||
* "size": {"width": "20%", "height": 300}, // Target: 20% of parent width, 300px height
|
||||
* "size": {"width": 400, "height": "50%"}, // Target: 400px width, 50% of parent height
|
||||
* "size": {"width": "30%", "height": "40%"}, // Target: 30% width, 40% height
|
||||
*
|
||||
* "min_size": {"width": 200, "height": 150}, // ABSOLUTE minimum in pixels (always respected)
|
||||
* "max_size": {"width": 800, "height": "80%"}, // Maximum: 800px width OR 80% of parent (whichever smaller)
|
||||
*
|
||||
* "position": {"x": 100, "y": 50},
|
||||
* "floating": false,
|
||||
* "resizable": true,
|
||||
* "closeable": true,
|
||||
* "collapsible": false
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
virtual void showData(DataType dataType, const json& data) = 0;
|
||||
|
||||
/**
|
||||
* @brief Display custom data type (fallback to string)
|
||||
* @param customType Custom data type name
|
||||
* @param data JSON data
|
||||
*/
|
||||
virtual void showDataCustom(const std::string& customType, const json& data) = 0;
|
||||
|
||||
/**
|
||||
* @brief Handle user request with type-safe enum
|
||||
* @param requestType Enum request type
|
||||
* @param callback Function to call when request happens
|
||||
*/
|
||||
virtual void onRequest(RequestType requestType, std::function<void(const json&)> callback) = 0;
|
||||
|
||||
/**
|
||||
* @brief Handle custom request type (fallback to string)
|
||||
* @param customType Custom request type name
|
||||
* @param callback Function to call when request happens
|
||||
*/
|
||||
virtual void onRequestCustom(const std::string& customType, std::function<void(const json&)> callback) = 0;
|
||||
|
||||
/**
|
||||
* @brief Show event with type-safe level
|
||||
* @param level Event level enum
|
||||
* @param message Human readable text
|
||||
*/
|
||||
virtual void showEvent(EventLevel level, const std::string& message) = 0;
|
||||
|
||||
// ========================================
|
||||
// WINDOW MANAGEMENT - ENUM VERSIONS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Create dock with type-safe enums
|
||||
* @param dockId Unique dock identifier
|
||||
* @param type Dock type enum
|
||||
* @param position Dock position enum
|
||||
* @param config Additional configuration:
|
||||
* {
|
||||
* "parent": "parent_dock_id", // Parent dock (optional)
|
||||
*
|
||||
* // HYBRID SIZE SYSTEM
|
||||
* "size": {"width": "25%", "height": 200}, // Target: 25% of parent width, 200px height
|
||||
* "min_size": {"width": 200, "height": 100}, // ABSOLUTE minimum pixels (overrides percentage)
|
||||
* "max_size": {"width": "50%", "height": 600}, // Maximum: 50% of parent OR 600px (whichever smaller)
|
||||
*
|
||||
* "orientation": "horizontal", // For SPLIT type
|
||||
* "collapsible": true, // Can be collapsed
|
||||
* "resizable": true, // Can be resized
|
||||
* "tabs": true // Enable tabbed interface
|
||||
* }
|
||||
*/
|
||||
virtual void createDock(const std::string& dockId, DockType type, DockPosition position, const json& config = {}) = 0;
|
||||
|
||||
/**
|
||||
* @brief Create split dock with orientation
|
||||
* @param dockId Unique dock identifier
|
||||
* @param orientation Split orientation
|
||||
* @param config Additional configuration:
|
||||
* {
|
||||
* "parent": "parent_dock_id", // Parent dock (optional)
|
||||
* "size": {"width": 300, "height": 200}, // Initial size
|
||||
* "min_size": {"width": 100, "height": 50}, // Minimum split size in pixels
|
||||
* "max_size": {"width": 1000, "height": 800}, // Maximum split size in pixels
|
||||
* "split_ratio": 0.5, // Split ratio (0.0 to 1.0)
|
||||
* "min_panel_size": 80, // Minimum size for each panel in split
|
||||
* "resizable": true // Can be resized by dragging splitter
|
||||
* }
|
||||
*/
|
||||
virtual void createSplit(const std::string& dockId, Orientation orientation, const json& config = {}) = 0;
|
||||
|
||||
/**
|
||||
* @brief Close window or dock
|
||||
* @param windowId Window/dock ID to close
|
||||
*/
|
||||
virtual void closeWindow(const std::string& windowId) = 0;
|
||||
|
||||
/**
|
||||
* @brief Focus window
|
||||
* @param windowId Window ID to focus
|
||||
*/
|
||||
virtual void focusWindow(const std::string& windowId) = 0;
|
||||
|
||||
// ========================================
|
||||
// GENERIC STATE
|
||||
// ========================================
|
||||
|
||||
virtual json getState() const = 0;
|
||||
virtual void setState(const json& state) = 0;
|
||||
|
||||
// ========================================
|
||||
// CONVENIENCE METHODS WITH ENUMS
|
||||
// ========================================
|
||||
|
||||
void info(const std::string& message) {
|
||||
showEvent(EventLevel::INFO, message);
|
||||
}
|
||||
|
||||
void success(const std::string& message) {
|
||||
showEvent(EventLevel::SUCCESS, message);
|
||||
}
|
||||
|
||||
void warning(const std::string& message) {
|
||||
showEvent(EventLevel::WARNING, message);
|
||||
}
|
||||
|
||||
void error(const std::string& message) {
|
||||
showEvent(EventLevel::ERROR, message);
|
||||
}
|
||||
|
||||
void debug(const std::string& message) {
|
||||
showEvent(EventLevel::DEBUG, message);
|
||||
}
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// ENUM TO STRING CONVERSIONS (for implementations)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Convert DataType enum to string (for implementations that need strings)
|
||||
*/
|
||||
constexpr const char* toString(DataType type) {
|
||||
switch (type) {
|
||||
case DataType::ECONOMY: return "economy";
|
||||
case DataType::MAP: return "map";
|
||||
case DataType::INVENTORY: return "inventory";
|
||||
case DataType::CONSOLE: return "console";
|
||||
case DataType::PERFORMANCE: return "performance";
|
||||
case DataType::COMPANIES: return "companies";
|
||||
case DataType::ALERTS: return "alerts";
|
||||
case DataType::PRODUCTION: return "production";
|
||||
case DataType::LOGISTICS: return "logistics";
|
||||
case DataType::PLAYER: return "player";
|
||||
case DataType::SETTINGS: return "settings";
|
||||
case DataType::DEBUG: return "debug";
|
||||
case DataType::CUSTOM: return "custom";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
constexpr const char* toString(RequestType type) {
|
||||
switch (type) {
|
||||
case RequestType::GET_PRICES: return "get_prices";
|
||||
case RequestType::GET_CHUNK: return "get_chunk";
|
||||
case RequestType::MOVE_PLAYER: return "move_player";
|
||||
case RequestType::SAVE_GAME: return "save_game";
|
||||
case RequestType::LOAD_GAME: return "load_game";
|
||||
case RequestType::CLOSE_WINDOW: return "close_window";
|
||||
case RequestType::FOCUS_WINDOW: return "focus_window";
|
||||
case RequestType::UPDATE_SETTINGS: return "update_settings";
|
||||
case RequestType::EXECUTE_COMMAND: return "execute_command";
|
||||
case RequestType::CUSTOM: return "custom";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
constexpr const char* toString(EventLevel level) {
|
||||
switch (level) {
|
||||
case EventLevel::INFO: return "info";
|
||||
case EventLevel::SUCCESS: return "success";
|
||||
case EventLevel::WARNING: return "warning";
|
||||
case EventLevel::ERROR: return "error";
|
||||
case EventLevel::DEBUG: return "debug";
|
||||
case EventLevel::TRACE: return "trace";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
constexpr const char* toString(DockType type) {
|
||||
switch (type) {
|
||||
case DockType::DOCK: return "dock";
|
||||
case DockType::SPLIT: return "split";
|
||||
case DockType::TABBED: return "tabbed";
|
||||
case DockType::FLOATING: return "floating";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
constexpr const char* toString(DockPosition pos) {
|
||||
switch (pos) {
|
||||
case DockPosition::LEFT: return "left";
|
||||
case DockPosition::RIGHT: return "right";
|
||||
case DockPosition::TOP: return "top";
|
||||
case DockPosition::BOTTOM: return "bottom";
|
||||
case DockPosition::CENTER: return "center";
|
||||
case DockPosition::TAB: return "tab";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
constexpr const char* toString(Orientation orient) {
|
||||
switch (orient) {
|
||||
case Orientation::HORIZONTAL: return "horizontal";
|
||||
case Orientation::VERTICAL: return "vertical";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,707 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "IUI_Enums.h"
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_glfw.h>
|
||||
#include <imgui_impl_opengl3.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <GL/gl.h>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <chrono>
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
/**
|
||||
* @brief ImGui implementation of IUI interface
|
||||
*
|
||||
* Provides full windowing system with docking, tabs, splits, and floating windows.
|
||||
* Handles hybrid percentage + pixel sizing with automatic constraint enforcement.
|
||||
*/
|
||||
class ImGuiUI : public IUI {
|
||||
private:
|
||||
// ========================================
|
||||
// CORE STATE
|
||||
// ========================================
|
||||
|
||||
GLFWwindow* window = nullptr;
|
||||
bool initialized = false;
|
||||
bool should_close = false;
|
||||
int frame_count = 0;
|
||||
|
||||
// Screen/parent sizes for percentage calculations
|
||||
ImVec2 screen_size = {1400, 900};
|
||||
ImVec2 previous_screen_size = {0, 0};
|
||||
|
||||
// ========================================
|
||||
// WINDOW MANAGEMENT
|
||||
// ========================================
|
||||
|
||||
struct WindowInfo {
|
||||
std::string id;
|
||||
std::string title;
|
||||
std::string parent;
|
||||
DockPosition dock_position = DockPosition::CENTER;
|
||||
bool is_open = true;
|
||||
bool is_floating = false;
|
||||
bool resizable = true;
|
||||
bool closeable = true;
|
||||
|
||||
// Size system
|
||||
ImVec2 size = {400, 300};
|
||||
ImVec2 min_size = {100, 100};
|
||||
ImVec2 max_size = {2000, 1500};
|
||||
ImVec2 position = {0, 0};
|
||||
|
||||
// Percentage tracking
|
||||
std::string size_width_percent = "";
|
||||
std::string size_height_percent = "";
|
||||
std::string min_width_percent = "";
|
||||
std::string min_height_percent = "";
|
||||
std::string max_width_percent = "";
|
||||
std::string max_height_percent = "";
|
||||
|
||||
// Content
|
||||
DataType data_type = DataType::CUSTOM;
|
||||
json content_data;
|
||||
};
|
||||
|
||||
std::map<std::string, WindowInfo> windows;
|
||||
|
||||
struct DockInfo {
|
||||
std::string id;
|
||||
DockType type = DockType::DOCK;
|
||||
DockPosition position = DockPosition::LEFT;
|
||||
std::string parent;
|
||||
bool collapsible = true;
|
||||
bool resizable = true;
|
||||
ImVec2 size = {300, 200};
|
||||
ImVec2 min_size = {100, 100};
|
||||
ImVec2 max_size = {1000, 800};
|
||||
std::vector<std::string> child_windows;
|
||||
};
|
||||
|
||||
std::map<std::string, DockInfo> docks;
|
||||
|
||||
// ========================================
|
||||
// CALLBACKS
|
||||
// ========================================
|
||||
|
||||
std::map<RequestType, std::function<void(const json&)>> request_callbacks;
|
||||
std::map<std::string, std::function<void(const json&)>> custom_request_callbacks;
|
||||
|
||||
// ========================================
|
||||
// MESSAGE SYSTEM
|
||||
// ========================================
|
||||
|
||||
struct LogMessage {
|
||||
EventLevel level;
|
||||
std::string message;
|
||||
std::chrono::steady_clock::time_point timestamp;
|
||||
};
|
||||
|
||||
std::vector<LogMessage> log_messages;
|
||||
static constexpr size_t MAX_LOG_MESSAGES = 100;
|
||||
|
||||
public:
|
||||
ImGuiUI() = default;
|
||||
~ImGuiUI() override { shutdown(); }
|
||||
|
||||
// ========================================
|
||||
// LIFECYCLE IMPLEMENTATION
|
||||
// ========================================
|
||||
|
||||
void initialize(const json& config) override {
|
||||
if (initialized) return;
|
||||
|
||||
// Initialize GLFW
|
||||
if (!glfwInit()) {
|
||||
throw std::runtime_error("Failed to initialize GLFW");
|
||||
}
|
||||
|
||||
// OpenGL 3.3 Core
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
|
||||
// Create window
|
||||
std::string title = config.value("title", "Warfactory ImGui UI");
|
||||
auto window_size = config.value("window_size", json{{"width", 1400}, {"height", 900}});
|
||||
if (window_size.is_object()) {
|
||||
screen_size.x = window_size.value("width", 1400);
|
||||
screen_size.y = window_size.value("height", 900);
|
||||
} else {
|
||||
screen_size.x = 1400;
|
||||
screen_size.y = 900;
|
||||
}
|
||||
|
||||
window = glfwCreateWindow(
|
||||
static_cast<int>(screen_size.x),
|
||||
static_cast<int>(screen_size.y),
|
||||
title.c_str(), nullptr, nullptr
|
||||
);
|
||||
|
||||
if (!window) {
|
||||
glfwTerminate();
|
||||
throw std::runtime_error("Failed to create GLFW window");
|
||||
}
|
||||
|
||||
glfwMakeContextCurrent(window);
|
||||
glfwSwapInterval(1); // VSync
|
||||
|
||||
// Initialize ImGui
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
// Note: Docking features depend on ImGui docking branch
|
||||
// Using manual docking simulation for compatibility
|
||||
|
||||
// Basic style setup
|
||||
ImGui::StyleColorsDark();
|
||||
|
||||
// Platform/Renderer backends
|
||||
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
||||
ImGui_ImplOpenGL3_Init("#version 330 core");
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
bool update() override {
|
||||
if (!initialized || !window) return false;
|
||||
|
||||
if (glfwWindowShouldClose(window)) {
|
||||
should_close = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update screen size for percentage calculations
|
||||
int fb_width, fb_height;
|
||||
glfwGetFramebufferSize(window, &fb_width, &fb_height);
|
||||
ImVec2 new_screen_size = {static_cast<float>(fb_width), static_cast<float>(fb_height)};
|
||||
|
||||
// Detect screen size changes and recalculate if needed
|
||||
if (new_screen_size.x != previous_screen_size.x || new_screen_size.y != previous_screen_size.y) {
|
||||
if (frame_count > 0) { // Skip first frame (initialization)
|
||||
debug("🔄 Screen size changed: " + std::to_string((int)new_screen_size.x) + "x" + std::to_string((int)new_screen_size.y));
|
||||
recalculateAllSizes();
|
||||
}
|
||||
previous_screen_size = screen_size;
|
||||
}
|
||||
screen_size = new_screen_size;
|
||||
|
||||
frame_count++;
|
||||
|
||||
// Poll events
|
||||
glfwPollEvents();
|
||||
|
||||
// Start ImGui frame
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
// Render all windows
|
||||
renderAllWindows();
|
||||
|
||||
// Render ImGui
|
||||
ImGui::Render();
|
||||
|
||||
// OpenGL rendering
|
||||
glViewport(0, 0, static_cast<int>(screen_size.x), static_cast<int>(screen_size.y));
|
||||
glClearColor(0.45f, 0.55f, 0.60f, 1.00f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
glfwSwapBuffers(window);
|
||||
|
||||
return !should_close;
|
||||
}
|
||||
|
||||
void shutdown() override {
|
||||
if (!initialized) return;
|
||||
|
||||
if (window) {
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
glfwDestroyWindow(window);
|
||||
glfwTerminate();
|
||||
window = nullptr;
|
||||
}
|
||||
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
private:
|
||||
// ========================================
|
||||
// SIZE CALCULATION HELPERS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Parse size value - handles both pixels and percentages
|
||||
*/
|
||||
float parseSize(const json& size_value, float parent_size, float default_size) {
|
||||
try {
|
||||
if (size_value.is_number()) {
|
||||
return size_value.get<float>();
|
||||
}
|
||||
|
||||
if (size_value.is_string()) {
|
||||
std::string size_str = size_value.get<std::string>();
|
||||
if (!size_str.empty() && size_str.back() == '%') {
|
||||
float percent = std::stof(size_str.substr(0, size_str.length() - 1));
|
||||
return (percent / 100.0f) * parent_size;
|
||||
} else {
|
||||
// String but not percentage - try to parse as number
|
||||
return std::stof(size_str);
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
// Any JSON or parsing error - return default
|
||||
}
|
||||
|
||||
// Neither number nor string or error - return default
|
||||
return default_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculate effective size with hybrid constraints
|
||||
*/
|
||||
ImVec2 calculateEffectiveSize(const WindowInfo& win, ImVec2 parent_size) {
|
||||
// Use already parsed sizes (converted in showData)
|
||||
float target_width = win.size.x;
|
||||
float target_height = win.size.y;
|
||||
|
||||
// Calculate constraint bounds
|
||||
float min_width = win.min_size.x;
|
||||
float min_height = win.min_size.y;
|
||||
float max_width = win.max_size.x;
|
||||
float max_height = win.max_size.y;
|
||||
|
||||
// Apply constraints (clamp)
|
||||
float final_width = std::max(min_width, std::min(target_width, max_width));
|
||||
float final_height = std::max(min_height, std::min(target_height, max_height));
|
||||
|
||||
return {final_width, final_height};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculate window position based on docking
|
||||
*/
|
||||
ImVec2 calculateDockedPosition(const WindowInfo& win, ImVec2 size) {
|
||||
if (win.parent.empty() || win.is_floating) {
|
||||
// For windows without parent, use explicit position or calculate smart default
|
||||
debug("🔍 Checking position for '" + win.id + "': pos=" +
|
||||
std::to_string(win.position.x) + "," + std::to_string(win.position.y) +
|
||||
" floating=" + (win.is_floating ? "true" : "false"));
|
||||
|
||||
// Only use explicit position if it was actually set by user (not just default values)
|
||||
if (win.position.x > 10 && win.position.y > 10) {
|
||||
debug("📌 Using explicit position for '" + win.id + "'");
|
||||
return win.position; // Use explicit position
|
||||
} else {
|
||||
// Simple approach: use actual window sizes from economy_main window
|
||||
float left_edge_end = 252; // Real end of economy_main (we know it's 252px wide)
|
||||
float top_edge_end = 88; // Real end of toolbar + margin
|
||||
|
||||
// Find the right sidebar start by looking for info_panel_main
|
||||
float right_edge_start = 1050; // We know info_panel starts at 1050px
|
||||
|
||||
debug("🔧 Simple positioning for window '" + win.id + "': left_end=" +
|
||||
std::to_string(left_edge_end) + "px, right_start=" +
|
||||
std::to_string(right_edge_start) + "px, top_end=" +
|
||||
std::to_string(top_edge_end) + "px");
|
||||
|
||||
// Position directly against the real edge of existing windows
|
||||
float x = left_edge_end; // Directly touching end of left sidebar (252px)
|
||||
float y = top_edge_end; // Directly below toolbar (88px)
|
||||
|
||||
// If window would overlap with right sidebar, push it left to touch right edge
|
||||
if (x + size.x > right_edge_start) {
|
||||
x = right_edge_start - size.x; // Touch right sidebar windows
|
||||
}
|
||||
|
||||
debug("🎯 Calculated position for '" + win.id + "': " +
|
||||
std::to_string(x) + "," + std::to_string(y) +
|
||||
" (touching real window edges)");
|
||||
|
||||
return {x, y};
|
||||
}
|
||||
}
|
||||
|
||||
// Find parent dock
|
||||
auto dock_it = docks.find(win.parent);
|
||||
if (dock_it == docks.end()) {
|
||||
return {0, 0}; // Parent dock not found
|
||||
}
|
||||
|
||||
const DockInfo& dock = dock_it->second;
|
||||
|
||||
// Calculate dock area based on position
|
||||
switch (dock.position) {
|
||||
case DockPosition::LEFT:
|
||||
return {0, 80}; // Left edge but below toolbar (72px + margin)
|
||||
case DockPosition::RIGHT:
|
||||
return {screen_size.x - dock.size.x, 80}; // Right edge but below toolbar
|
||||
case DockPosition::TOP:
|
||||
// Top edge - if dock spans full width, start at 0, else offset
|
||||
if (dock.size.x >= screen_size.x * 0.9f) {
|
||||
return {0, 0}; // Full width toolbar starts at screen edge
|
||||
} else {
|
||||
return {280, 0}; // Partial width toolbar starts after sidebar
|
||||
}
|
||||
case DockPosition::BOTTOM:
|
||||
return {0, screen_size.y - dock.size.y}; // Bottom edge
|
||||
case DockPosition::CENTER:
|
||||
default:
|
||||
return {screen_size.x * 0.5f - size.x * 0.5f, screen_size.y * 0.5f - size.y * 0.5f}; // Center
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// RECALCULATION METHODS
|
||||
// ========================================
|
||||
|
||||
void recalculateAllSizes() {
|
||||
// Recalculate dock sizes
|
||||
for (auto& [dock_id, dock] : docks) {
|
||||
// Recalculate dock size if it uses percentages
|
||||
recalculateDockSize(dock);
|
||||
}
|
||||
|
||||
// Recalculate window sizes
|
||||
for (auto& [window_id, win] : windows) {
|
||||
recalculateWindowSize(win);
|
||||
}
|
||||
}
|
||||
|
||||
void recalculateDockSize(DockInfo& dock) {
|
||||
// Re-parse dock size with new screen size
|
||||
// This would need the original JSON config, for now just log
|
||||
debug("📐 Recalculating dock: " + dock.id);
|
||||
// TODO: Store original percentage strings to recalculate properly
|
||||
}
|
||||
|
||||
void recalculateWindowSize(WindowInfo& win) {
|
||||
// Re-parse window size with new screen/parent sizes
|
||||
debug("📐 Recalculating window: " + win.id);
|
||||
|
||||
// Recalculate width if percentage
|
||||
if (!win.size_width_percent.empty()) {
|
||||
float parent_width = screen_size.x;
|
||||
if (!win.parent.empty() && docks.find(win.parent) != docks.end()) {
|
||||
parent_width = docks[win.parent].size.x;
|
||||
}
|
||||
win.size.x = parseSize(win.size_width_percent, parent_width, 400);
|
||||
}
|
||||
|
||||
// Recalculate height if percentage
|
||||
if (!win.size_height_percent.empty()) {
|
||||
float parent_height = screen_size.y;
|
||||
if (!win.parent.empty() && docks.find(win.parent) != docks.end()) {
|
||||
parent_height = docks[win.parent].size.y;
|
||||
}
|
||||
win.size.y = parseSize(win.size_height_percent, parent_height, 300);
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// RENDERING IMPLEMENTATION
|
||||
// ========================================
|
||||
|
||||
void renderAllWindows() {
|
||||
// Log screen size for debugging (only first frame to avoid spam)
|
||||
if (frame_count == 1) {
|
||||
debug("🖥️ Screen Size: " + std::to_string((int)screen_size.x) + "x" + std::to_string((int)screen_size.y) + "px");
|
||||
info("🏗️ Manual docking system active (simulated docking layout)");
|
||||
}
|
||||
|
||||
for (auto& [window_id, win] : windows) {
|
||||
if (!win.is_open) continue;
|
||||
|
||||
if (frame_count <= 5) { // Log first 5 frames for each window
|
||||
debug("🪟 Window: " + window_id + " (" + win.title + ")");
|
||||
debug(" 📐 Target Size: " + std::to_string((int)win.size.x) + "x" + std::to_string((int)win.size.y) + "px");
|
||||
debug(" 📏 Size %: width='" + win.size_width_percent + "' height='" + win.size_height_percent + "'");
|
||||
debug(" ⚖️ Constraints: min=" + std::to_string((int)win.min_size.x) + "x" + std::to_string((int)win.min_size.y) +
|
||||
" max=" + std::to_string((int)win.max_size.x) + "x" + std::to_string((int)win.max_size.y));
|
||||
debug(" 🔗 Docking: parent='" + win.parent + "' position=" + std::to_string((int)win.dock_position));
|
||||
}
|
||||
|
||||
// Calculate effective size with constraints
|
||||
ImVec2 effective_size = calculateEffectiveSize(win, screen_size);
|
||||
if (frame_count <= 5) {
|
||||
debug(" ✅ Effective Size: " + std::to_string((int)effective_size.x) + "x" + std::to_string((int)effective_size.y) + "px");
|
||||
}
|
||||
|
||||
// Set window constraints
|
||||
ImGui::SetNextWindowSizeConstraints(win.min_size, win.max_size);
|
||||
|
||||
// Set window size
|
||||
if (win.is_floating) {
|
||||
// For floating windows, force initial size and position
|
||||
ImGuiCond size_condition = (frame_count <= 3) ? ImGuiCond_Always : ImGuiCond_FirstUseEver;
|
||||
ImGui::SetNextWindowSize(effective_size, size_condition);
|
||||
|
||||
// Calculate smart position that avoids dock overlaps
|
||||
ImVec2 floating_position = calculateDockedPosition(win, effective_size);
|
||||
ImGuiCond position_condition = (frame_count <= 3) ? ImGuiCond_Always : ImGuiCond_FirstUseEver;
|
||||
ImGui::SetNextWindowPos(floating_position, position_condition);
|
||||
|
||||
if (frame_count <= 5) {
|
||||
debug(" 🎈 Floating Position: " + std::to_string((int)floating_position.x) + "," + std::to_string((int)floating_position.y));
|
||||
}
|
||||
} else {
|
||||
// For docked windows, calculate position and force it during initial frames
|
||||
ImVec2 dock_position = calculateDockedPosition(win, effective_size);
|
||||
|
||||
ImGuiCond condition = (frame_count <= 3) ? ImGuiCond_Always : ImGuiCond_FirstUseEver;
|
||||
ImGui::SetNextWindowSize(effective_size, condition);
|
||||
ImGui::SetNextWindowPos(dock_position, condition);
|
||||
|
||||
if (frame_count <= 5) {
|
||||
debug(" 📍 Docked Position: " + std::to_string((int)dock_position.x) + "," + std::to_string((int)dock_position.y));
|
||||
}
|
||||
}
|
||||
|
||||
// Window flags
|
||||
ImGuiWindowFlags flags = ImGuiWindowFlags_None;
|
||||
if (!win.resizable) flags |= ImGuiWindowFlags_NoResize;
|
||||
|
||||
// Render window
|
||||
if (ImGui::Begin(win.title.c_str(), win.closeable ? &win.is_open : nullptr, flags)) {
|
||||
// Log actual ImGui window size after rendering (first 5 frames only)
|
||||
if (frame_count <= 5) {
|
||||
ImVec2 current_size = ImGui::GetWindowSize();
|
||||
ImVec2 current_pos = ImGui::GetWindowPos();
|
||||
debug(" 🎯 ImGui Actual: pos=" + std::to_string((int)current_pos.x) + "," + std::to_string((int)current_pos.y) +
|
||||
" size=" + std::to_string((int)current_size.x) + "x" + std::to_string((int)current_size.y) + "px");
|
||||
}
|
||||
|
||||
renderWindowContent(win);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
void renderWindowContent(const WindowInfo& win) {
|
||||
switch (win.data_type) {
|
||||
case DataType::ECONOMY:
|
||||
renderEconomyContent(win.content_data);
|
||||
break;
|
||||
case DataType::MAP:
|
||||
renderMapContent(win.content_data);
|
||||
break;
|
||||
case DataType::INVENTORY:
|
||||
renderInventoryContent(win.content_data);
|
||||
break;
|
||||
case DataType::CONSOLE:
|
||||
renderConsoleContent(win.content_data);
|
||||
break;
|
||||
case DataType::PERFORMANCE:
|
||||
renderPerformanceContent(win.content_data);
|
||||
break;
|
||||
case DataType::COMPANIES:
|
||||
renderCompaniesContent(win.content_data);
|
||||
break;
|
||||
case DataType::ALERTS:
|
||||
renderAlertsContent(win.content_data);
|
||||
break;
|
||||
case DataType::SETTINGS:
|
||||
renderSettingsContent(win.content_data);
|
||||
break;
|
||||
default:
|
||||
renderGenericContent(win.content_data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
// ========================================
|
||||
// IUI INTERFACE IMPLEMENTATION - DATA DISPLAY
|
||||
// ========================================
|
||||
|
||||
void showData(DataType dataType, const json& data) override {
|
||||
// Extract window configuration
|
||||
json window_config = data.value("window", json{});
|
||||
json content = data.value("content", data);
|
||||
|
||||
// Generate ID if not provided
|
||||
std::string window_id = window_config.value("id", "window_" + std::to_string(windows.size()));
|
||||
|
||||
// Create or update window info
|
||||
WindowInfo& win = windows[window_id];
|
||||
win.id = window_id;
|
||||
win.title = window_config.value("title", toString(dataType));
|
||||
win.data_type = dataType;
|
||||
win.content_data = content;
|
||||
win.is_open = true;
|
||||
|
||||
// Parse parent first (needed for size calculations)
|
||||
win.parent = window_config.value("parent", "");
|
||||
|
||||
// Parse size configuration with percentage support
|
||||
if (window_config.contains("size")) {
|
||||
auto size_config = window_config["size"];
|
||||
if (size_config.is_object()) {
|
||||
if (size_config.contains("width")) {
|
||||
auto width_val = size_config["width"];
|
||||
if (width_val.is_string()) {
|
||||
win.size_width_percent = width_val.get<std::string>();
|
||||
debug("🔧 Processing width percentage '" + win.size_width_percent +
|
||||
"' for window '" + win.id + "' with parent='" + win.parent + "'");
|
||||
|
||||
// Calculate parent size for percentage - use dock size if docked
|
||||
float parent_width = screen_size.x;
|
||||
if (!win.parent.empty() && docks.find(win.parent) != docks.end()) {
|
||||
parent_width = docks[win.parent].size.x;
|
||||
debug("🔍 Found parent dock '" + win.parent + "' with width=" +
|
||||
std::to_string((int)parent_width) + "px");
|
||||
} else if (!win.parent.empty()) {
|
||||
debug("❌ Parent dock '" + win.parent + "' not found! Using screen width.");
|
||||
}
|
||||
|
||||
win.size.x = parseSize(width_val, parent_width, 400);
|
||||
} else if (width_val.is_number()) {
|
||||
win.size.x = width_val.get<float>();
|
||||
} else {
|
||||
win.size.x = 400; // Default fallback
|
||||
}
|
||||
}
|
||||
|
||||
if (size_config.contains("height")) {
|
||||
auto height_val = size_config["height"];
|
||||
if (height_val.is_string()) {
|
||||
win.size_height_percent = height_val.get<std::string>();
|
||||
float parent_height = screen_size.y;
|
||||
if (!win.parent.empty() && docks.find(win.parent) != docks.end()) {
|
||||
parent_height = docks[win.parent].size.y;
|
||||
}
|
||||
win.size.y = parseSize(height_val, parent_height, 300);
|
||||
} else if (height_val.is_number()) {
|
||||
win.size.y = height_val.get<float>();
|
||||
} else {
|
||||
win.size.y = 300; // Default fallback
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse constraints
|
||||
if (window_config.contains("min_size")) {
|
||||
auto min_config = window_config["min_size"];
|
||||
if (min_config.is_object()) {
|
||||
if (min_config.contains("width")) {
|
||||
win.min_size.x = parseSize(min_config["width"], screen_size.x, 100);
|
||||
} else {
|
||||
win.min_size.x = 100;
|
||||
}
|
||||
if (min_config.contains("height")) {
|
||||
win.min_size.y = parseSize(min_config["height"], screen_size.y, 100);
|
||||
} else {
|
||||
win.min_size.y = 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (window_config.contains("max_size")) {
|
||||
auto max_config = window_config["max_size"];
|
||||
if (max_config.is_object()) {
|
||||
if (max_config.contains("width")) {
|
||||
win.max_size.x = parseSize(max_config["width"], screen_size.x, 2000);
|
||||
} else {
|
||||
win.max_size.x = 2000;
|
||||
}
|
||||
if (max_config.contains("height")) {
|
||||
win.max_size.y = parseSize(max_config["height"], screen_size.y, 1500);
|
||||
} else {
|
||||
win.max_size.y = 1500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse other properties
|
||||
win.is_floating = window_config.value("floating", false);
|
||||
win.resizable = window_config.value("resizable", true);
|
||||
win.closeable = window_config.value("closeable", true);
|
||||
|
||||
// Parse dock position if specified
|
||||
if (window_config.contains("dock")) {
|
||||
std::string dock_str = window_config["dock"].get<std::string>();
|
||||
if (dock_str == "left") win.dock_position = DockPosition::LEFT;
|
||||
else if (dock_str == "right") win.dock_position = DockPosition::RIGHT;
|
||||
else if (dock_str == "top") win.dock_position = DockPosition::TOP;
|
||||
else if (dock_str == "bottom") win.dock_position = DockPosition::BOTTOM;
|
||||
else if (dock_str == "tab") win.dock_position = DockPosition::CENTER; // tabs go in center
|
||||
else win.dock_position = DockPosition::CENTER;
|
||||
}
|
||||
|
||||
if (window_config.contains("position")) {
|
||||
auto pos = window_config["position"];
|
||||
if (pos.is_object()) {
|
||||
win.position.x = pos.value("x", 0);
|
||||
win.position.y = pos.value("y", 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void showDataCustom(const std::string& customType, const json& data) override {
|
||||
// Treat as generic data with custom type in title
|
||||
json modified_data = data;
|
||||
if (!modified_data.contains("window")) {
|
||||
modified_data["window"] = json{};
|
||||
}
|
||||
if (!modified_data["window"].contains("title")) {
|
||||
modified_data["window"]["title"] = customType;
|
||||
}
|
||||
|
||||
showData(DataType::CUSTOM, modified_data);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// IUI INTERFACE IMPLEMENTATION - REQUESTS & EVENTS
|
||||
// ========================================
|
||||
|
||||
void onRequest(RequestType requestType, std::function<void(const json&)> callback) override;
|
||||
void onRequestCustom(const std::string& customType, std::function<void(const json&)> callback) override;
|
||||
void showEvent(EventLevel level, const std::string& message) override;
|
||||
|
||||
// ========================================
|
||||
// WINDOW MANAGEMENT IMPLEMENTATION
|
||||
// ========================================
|
||||
|
||||
void createDock(const std::string& dockId, DockType type, DockPosition position, const json& config = {}) override;
|
||||
void createSplit(const std::string& dockId, Orientation orientation, const json& config = {}) override;
|
||||
void closeWindow(const std::string& windowId) override;
|
||||
void focusWindow(const std::string& windowId) override;
|
||||
|
||||
// ========================================
|
||||
// STATE MANAGEMENT
|
||||
// ========================================
|
||||
|
||||
json getState() const override;
|
||||
void setState(const json& state) override;
|
||||
|
||||
private:
|
||||
// ========================================
|
||||
// CONTENT RENDERING IMPLEMENTATIONS
|
||||
// ========================================
|
||||
|
||||
void renderEconomyContent(const json& content);
|
||||
void renderMapContent(const json& content);
|
||||
void renderInventoryContent(const json& content);
|
||||
void renderConsoleContent(const json& content);
|
||||
void renderPerformanceContent(const json& content);
|
||||
void renderCompaniesContent(const json& content);
|
||||
void renderAlertsContent(const json& content);
|
||||
void renderSettingsContent(const json& content);
|
||||
void renderGenericContent(const json& content);
|
||||
void renderLogConsole();
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,89 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <random>
|
||||
#include <cstdint>
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
/**
|
||||
* @brief Centralized random number generator singleton
|
||||
*
|
||||
* Provides consistent, seedable random number generation across all modules.
|
||||
* Ensures reproducibility for testing and debugging while maintaining
|
||||
* high-quality random distribution.
|
||||
*/
|
||||
class RandomGenerator {
|
||||
private:
|
||||
std::mt19937 gen;
|
||||
|
||||
RandomGenerator() : gen(std::random_device{}()) {}
|
||||
|
||||
public:
|
||||
// Singleton access
|
||||
static RandomGenerator& getInstance() {
|
||||
static RandomGenerator instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Delete copy/move constructors and operators
|
||||
RandomGenerator(const RandomGenerator&) = delete;
|
||||
RandomGenerator& operator=(const RandomGenerator&) = delete;
|
||||
RandomGenerator(RandomGenerator&&) = delete;
|
||||
RandomGenerator& operator=(RandomGenerator&&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Seed the random generator for reproducible sequences
|
||||
* @param seed Seed value (use same seed for identical results)
|
||||
*/
|
||||
void seed(uint32_t seed) {
|
||||
gen.seed(seed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate uniform float in range [min, max]
|
||||
*/
|
||||
float uniform(float min, float max) {
|
||||
std::uniform_real_distribution<float> dis(min, max);
|
||||
return dis(gen);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate uniform int in range [min, max] (inclusive)
|
||||
*/
|
||||
int uniformInt(int min, int max) {
|
||||
std::uniform_int_distribution<int> dis(min, max);
|
||||
return dis(gen);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate normal distribution float
|
||||
*/
|
||||
float normal(float mean, float stddev) {
|
||||
std::normal_distribution<float> dis(mean, stddev);
|
||||
return dis(gen);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate uniform float in range [0.0, 1.0]
|
||||
*/
|
||||
float unit() {
|
||||
return uniform(0.0f, 1.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate boolean with given probability
|
||||
* @param probability Probability of returning true [0.0, 1.0]
|
||||
*/
|
||||
bool boolean(float probability = 0.5f) {
|
||||
return unit() < probability;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to underlying generator for custom distributions
|
||||
*/
|
||||
std::mt19937& getGenerator() {
|
||||
return gen;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
class Resource {
|
||||
private:
|
||||
std::string resource_id;
|
||||
std::string name;
|
||||
std::string category;
|
||||
std::string logistic_category;
|
||||
float density;
|
||||
int stack_size;
|
||||
std::string container_type;
|
||||
json ui_data;
|
||||
|
||||
public:
|
||||
Resource() = default;
|
||||
Resource(const json& resource_data);
|
||||
|
||||
const std::string& getResourceId() const { return resource_id; }
|
||||
const std::string& getName() const { return name; }
|
||||
const std::string& getCategory() const { return category; }
|
||||
const std::string& getLogisticCategory() const { return logistic_category; }
|
||||
float getDensity() const { return density; }
|
||||
int getStackSize() const { return stack_size; }
|
||||
const std::string& getContainerType() const { return container_type; }
|
||||
const json& getUIData() const { return ui_data; }
|
||||
|
||||
static Resource loadFromJson(const std::string& resource_id, const json& resource_data);
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,113 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Resource.h"
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
/**
|
||||
* @brief Singleton registry for all game resources with fast uint32_t ID lookup
|
||||
*
|
||||
* Centralizes resource management with O(1) access by numeric ID.
|
||||
* Resources are loaded once at startup and accessed by ID throughout the game.
|
||||
*/
|
||||
class ResourceRegistry {
|
||||
private:
|
||||
static std::unique_ptr<ResourceRegistry> instance;
|
||||
static bool initialized;
|
||||
|
||||
std::vector<Resource> resources; // Indexed by ID (resources[id])
|
||||
std::unordered_map<std::string, uint32_t> name_to_id; // String -> ID mapping (init only)
|
||||
uint32_t next_id = 1; // Start at 1 (0 = invalid/null resource)
|
||||
|
||||
ResourceRegistry() = default;
|
||||
|
||||
public:
|
||||
// Singleton access
|
||||
static ResourceRegistry& getInstance();
|
||||
static void initialize();
|
||||
static void shutdown();
|
||||
|
||||
// ========================================
|
||||
// REGISTRATION (Initialization Phase)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Register a resource and get its assigned ID
|
||||
* @param resource The resource to register
|
||||
* @return The assigned uint32_t ID for this resource
|
||||
*/
|
||||
uint32_t registerResource(const Resource& resource);
|
||||
|
||||
/**
|
||||
* @brief Load resources from JSON configuration
|
||||
* @param resources_json JSON object containing all resources
|
||||
*/
|
||||
void loadResourcesFromJson(const json& resources_json);
|
||||
|
||||
// ========================================
|
||||
// RUNTIME ACCESS (Performance Critical)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Get resource by ID (O(1) access)
|
||||
* @param id The resource ID
|
||||
* @return Pointer to resource or nullptr if not found
|
||||
*/
|
||||
const Resource* getResource(uint32_t id) const;
|
||||
|
||||
/**
|
||||
* @brief Get resource ID by name (use sparingly - prefer caching IDs)
|
||||
* @param name The resource name/identifier
|
||||
* @return The resource ID or 0 if not found
|
||||
*/
|
||||
uint32_t getResourceId(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief Check if resource ID is valid
|
||||
*/
|
||||
bool isValidResourceId(uint32_t id) const;
|
||||
|
||||
// ========================================
|
||||
// BULK OPERATIONS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Get all registered resource IDs
|
||||
*/
|
||||
std::vector<uint32_t> getAllResourceIds() const;
|
||||
|
||||
/**
|
||||
* @brief Get total number of registered resources
|
||||
*/
|
||||
size_t getResourceCount() const;
|
||||
|
||||
/**
|
||||
* @brief Clear all registered resources (testing/reset)
|
||||
*/
|
||||
void clear();
|
||||
|
||||
// ========================================
|
||||
// CONVENIENCE CONSTANTS
|
||||
// ========================================
|
||||
|
||||
static constexpr uint32_t INVALID_RESOURCE_ID = 0;
|
||||
static constexpr uint32_t MAX_RESOURCES = 1000000; // 1M resources max
|
||||
|
||||
// Prevent copy/assignment
|
||||
ResourceRegistry(const ResourceRegistry&) = delete;
|
||||
ResourceRegistry& operator=(const ResourceRegistry&) = delete;
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// CONVENIENCE MACROS FOR PERFORMANCE
|
||||
// ========================================
|
||||
|
||||
#define RESOURCE_ID(name) warfactory::ResourceRegistry::getInstance().getResourceId(name)
|
||||
#define GET_RESOURCE(id) warfactory::ResourceRegistry::getInstance().getResource(id)
|
||||
#define VALID_RESOURCE(id) warfactory::ResourceRegistry::getInstance().isValidResourceId(id)
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,38 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
class ASerializable;
|
||||
|
||||
class SerializationRegistry {
|
||||
private:
|
||||
std::unordered_map<std::string, ASerializable*> registered_objects;
|
||||
|
||||
SerializationRegistry() = default;
|
||||
|
||||
public:
|
||||
static SerializationRegistry& getInstance();
|
||||
|
||||
void registerObject(const std::string& instance_id, ASerializable* object);
|
||||
void unregisterObject(const std::string& instance_id);
|
||||
|
||||
json serializeAll() const;
|
||||
void deserializeAll(const json& data);
|
||||
|
||||
json serializeObject(const std::string& instance_id) const;
|
||||
void deserializeObject(const std::string& instance_id, const json& data);
|
||||
|
||||
size_t getRegisteredCount() const { return registered_objects.size(); }
|
||||
std::vector<std::string> getRegisteredIds() const;
|
||||
|
||||
void clear();
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,546 +0,0 @@
|
||||
#include "warfactory/ImGuiUI.h"
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
// ========================================
|
||||
// IUI INTERFACE IMPLEMENTATION - REQUESTS & EVENTS
|
||||
// ========================================
|
||||
|
||||
void ImGuiUI::onRequest(RequestType requestType, std::function<void(const json&)> callback) {
|
||||
request_callbacks[requestType] = callback;
|
||||
}
|
||||
|
||||
void ImGuiUI::onRequestCustom(const std::string& customType, std::function<void(const json&)> callback) {
|
||||
custom_request_callbacks[customType] = callback;
|
||||
}
|
||||
|
||||
void ImGuiUI::showEvent(EventLevel level, const std::string& message) {
|
||||
LogMessage log_msg;
|
||||
log_msg.level = level;
|
||||
log_msg.message = message;
|
||||
log_msg.timestamp = std::chrono::steady_clock::now();
|
||||
|
||||
log_messages.push_back(log_msg);
|
||||
|
||||
// Keep only last MAX_LOG_MESSAGES
|
||||
if (log_messages.size() > MAX_LOG_MESSAGES) {
|
||||
log_messages.erase(log_messages.begin());
|
||||
}
|
||||
|
||||
// Also output to console for debugging
|
||||
const char* level_str = toString(level);
|
||||
std::cout << "[" << level_str << "] " << message << std::endl;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// WINDOW MANAGEMENT IMPLEMENTATION
|
||||
// ========================================
|
||||
|
||||
void ImGuiUI::createDock(const std::string& dockId, DockType type, DockPosition position, const json& config) {
|
||||
DockInfo& dock = docks[dockId];
|
||||
dock.id = dockId;
|
||||
dock.type = type;
|
||||
dock.position = position;
|
||||
dock.parent = config.value("parent", "");
|
||||
|
||||
// Parse size with percentage support
|
||||
if (config.contains("size")) {
|
||||
auto size_config = config["size"];
|
||||
if (size_config.is_object()) {
|
||||
if (size_config.contains("width")) {
|
||||
dock.size.x = parseSize(size_config["width"], screen_size.x, 300);
|
||||
} else {
|
||||
dock.size.x = 300; // Default
|
||||
}
|
||||
if (size_config.contains("height")) {
|
||||
dock.size.y = parseSize(size_config["height"], screen_size.y, 200);
|
||||
} else {
|
||||
dock.size.y = 200; // Default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.contains("min_size")) {
|
||||
auto min_config = config["min_size"];
|
||||
if (min_config.is_object()) {
|
||||
if (min_config.contains("width")) {
|
||||
dock.min_size.x = parseSize(min_config["width"], screen_size.x, 100);
|
||||
} else {
|
||||
dock.min_size.x = 100;
|
||||
}
|
||||
if (min_config.contains("height")) {
|
||||
dock.min_size.y = parseSize(min_config["height"], screen_size.y, 100);
|
||||
} else {
|
||||
dock.min_size.y = 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.contains("max_size")) {
|
||||
auto max_config = config["max_size"];
|
||||
if (max_config.is_object()) {
|
||||
if (max_config.contains("width")) {
|
||||
dock.max_size.x = parseSize(max_config["width"], screen_size.x, 1000);
|
||||
} else {
|
||||
dock.max_size.x = 1000;
|
||||
}
|
||||
if (max_config.contains("height")) {
|
||||
dock.max_size.y = parseSize(max_config["height"], screen_size.y, 800);
|
||||
} else {
|
||||
dock.max_size.y = 800;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dock.collapsible = config.value("collapsible", true);
|
||||
dock.resizable = config.value("resizable", true);
|
||||
|
||||
// Debug logging for dock creation
|
||||
showEvent(EventLevel::DEBUG, "🏗️ Created dock '" + dockId + "': " + std::string(toString(type)) +
|
||||
" size=" + std::to_string((int)dock.size.x) + "x" + std::to_string((int)dock.size.y) + "px");
|
||||
|
||||
showEvent(EventLevel::INFO, "Created " + std::string(toString(type)) + " dock: " + dockId);
|
||||
}
|
||||
|
||||
void ImGuiUI::createSplit(const std::string& dockId, Orientation orientation, const json& config) {
|
||||
// Create as a split dock
|
||||
json split_config = config;
|
||||
split_config["orientation"] = toString(orientation);
|
||||
createDock(dockId, DockType::SPLIT, DockPosition::CENTER, split_config);
|
||||
}
|
||||
|
||||
void ImGuiUI::closeWindow(const std::string& windowId) {
|
||||
auto it = windows.find(windowId);
|
||||
if (it != windows.end()) {
|
||||
it->second.is_open = false;
|
||||
showEvent(EventLevel::INFO, "Closed window: " + windowId);
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiUI::focusWindow(const std::string& windowId) {
|
||||
auto it = windows.find(windowId);
|
||||
if (it != windows.end()) {
|
||||
ImGui::SetWindowFocus(it->second.title.c_str());
|
||||
showEvent(EventLevel::DEBUG, "Focused window: " + windowId);
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// STATE MANAGEMENT
|
||||
// ========================================
|
||||
|
||||
json ImGuiUI::getState() const {
|
||||
json state;
|
||||
state["frame_count"] = frame_count;
|
||||
state["window_open"] = !should_close;
|
||||
state["screen_size"] = {{"width", screen_size.x}, {"height", screen_size.y}};
|
||||
|
||||
// Save window states
|
||||
json window_states = json::object();
|
||||
for (const auto& [id, win] : windows) {
|
||||
window_states[id] = {
|
||||
{"is_open", win.is_open},
|
||||
{"size", {{"width", win.size.x}, {"height", win.size.y}}},
|
||||
{"position", {{"x", win.position.x}, {"y", win.position.y}}},
|
||||
{"floating", win.is_floating}
|
||||
};
|
||||
}
|
||||
state["windows"] = window_states;
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
void ImGuiUI::setState(const json& state) {
|
||||
if (state.contains("windows")) {
|
||||
for (const auto& [id, win_state] : state["windows"].items()) {
|
||||
auto it = windows.find(id);
|
||||
if (it != windows.end()) {
|
||||
auto& win = it->second;
|
||||
win.is_open = win_state.value("is_open", true);
|
||||
|
||||
if (win_state.contains("size")) {
|
||||
auto size_state = win_state["size"];
|
||||
if (size_state.is_object()) {
|
||||
win.size.x = size_state.value("width", win.size.x);
|
||||
win.size.y = size_state.value("height", win.size.y);
|
||||
}
|
||||
}
|
||||
|
||||
if (win_state.contains("position")) {
|
||||
auto pos_state = win_state["position"];
|
||||
if (pos_state.is_object()) {
|
||||
win.position.x = pos_state.value("x", win.position.x);
|
||||
win.position.y = pos_state.value("y", win.position.y);
|
||||
}
|
||||
}
|
||||
|
||||
win.is_floating = win_state.value("floating", win.is_floating);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CONTENT RENDERING IMPLEMENTATIONS
|
||||
// ========================================
|
||||
|
||||
void ImGuiUI::renderEconomyContent(const json& content) {
|
||||
ImGui::Text("💰 Economy Dashboard");
|
||||
ImGui::Separator();
|
||||
|
||||
if (content.contains("prices")) {
|
||||
ImGui::Text("Market Prices:");
|
||||
ImGui::BeginTable("prices_table", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg);
|
||||
|
||||
ImGui::TableSetupColumn("Item");
|
||||
ImGui::TableSetupColumn("Price");
|
||||
ImGui::TableSetupColumn("Trend");
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
for (const auto& [item, price] : content["prices"].items()) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s", item.c_str());
|
||||
ImGui::TableNextColumn();
|
||||
if (price.is_number()) {
|
||||
ImGui::Text("%.2f", price.get<float>());
|
||||
} else {
|
||||
ImGui::Text("%s", price.dump().c_str());
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
// Show trend if available
|
||||
if (content.contains("trends") && content["trends"].contains(item)) {
|
||||
std::string trend = content["trends"][item];
|
||||
if (trend[0] == '+') {
|
||||
ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "%s", trend.c_str());
|
||||
} else if (trend[0] == '-') {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "%s", trend.c_str());
|
||||
} else {
|
||||
ImGui::Text("%s", trend.c_str());
|
||||
}
|
||||
} else {
|
||||
ImGui::Text("--");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Action buttons
|
||||
if (ImGui::Button("🔄 Refresh Prices")) {
|
||||
if (request_callbacks.count(RequestType::GET_PRICES)) {
|
||||
request_callbacks[RequestType::GET_PRICES]({});
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("📊 Market Analysis")) {
|
||||
if (custom_request_callbacks.count("market_analysis")) {
|
||||
custom_request_callbacks["market_analysis"]({});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiUI::renderMapContent(const json& content) {
|
||||
ImGui::Text("🗺️ Global Map");
|
||||
ImGui::Separator();
|
||||
|
||||
if (content.contains("current_chunk")) {
|
||||
auto chunk = content["current_chunk"];
|
||||
if (chunk.is_object()) {
|
||||
ImGui::Text("Current Chunk: (%d, %d)", chunk.value("x", 0), chunk.value("y", 0));
|
||||
}
|
||||
}
|
||||
|
||||
if (content.contains("tiles")) {
|
||||
ImGui::Text("Map Display:");
|
||||
|
||||
// Navigation controls
|
||||
if (ImGui::Button("⬆️")) {
|
||||
if (request_callbacks.count(RequestType::GET_CHUNK)) {
|
||||
request_callbacks[RequestType::GET_CHUNK]({{"action", "move_up"}});
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::Button("⬅️")) {
|
||||
if (request_callbacks.count(RequestType::GET_CHUNK)) {
|
||||
request_callbacks[RequestType::GET_CHUNK]({{"action", "move_left"}});
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("➡️")) {
|
||||
if (request_callbacks.count(RequestType::GET_CHUNK)) {
|
||||
request_callbacks[RequestType::GET_CHUNK]({{"action", "move_right"}});
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::Button("⬇️")) {
|
||||
if (request_callbacks.count(RequestType::GET_CHUNK)) {
|
||||
request_callbacks[RequestType::GET_CHUNK]({{"action", "move_down"}});
|
||||
}
|
||||
}
|
||||
|
||||
// Simple tile grid representation
|
||||
ImGui::Text("Tile Grid (sample):");
|
||||
for (int y = 0; y < 4; y++) {
|
||||
for (int x = 0; x < 8; x++) {
|
||||
if (x > 0) ImGui::SameLine();
|
||||
|
||||
// Generate simple tile representation
|
||||
char tile_str[2] = "."; // Null-terminated string
|
||||
if ((x + y) % 3 == 0) tile_str[0] = 'I'; // Iron
|
||||
else if ((x + y) % 5 == 0) tile_str[0] = 'C'; // Copper
|
||||
else if ((x + y) % 7 == 0) tile_str[0] = 'T'; // Tree
|
||||
|
||||
ImGui::Button(tile_str, ImVec2(20, 20));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
if (ImGui::Button("🔄 Refresh Map")) {
|
||||
if (request_callbacks.count(RequestType::GET_CHUNK)) {
|
||||
request_callbacks[RequestType::GET_CHUNK]({{"type", "refresh"}});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiUI::renderInventoryContent(const json& content) {
|
||||
ImGui::Text("🎒 Inventory");
|
||||
ImGui::Separator();
|
||||
|
||||
if (content.contains("items")) {
|
||||
ImGui::BeginTable("inventory_table", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg);
|
||||
ImGui::TableSetupColumn("Item");
|
||||
ImGui::TableSetupColumn("Quantity");
|
||||
ImGui::TableSetupColumn("Reserved");
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
for (const auto& item : content["items"]) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s", item.value("name", "Unknown").c_str());
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", item.value("quantity", 0));
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", item.value("reserved", 0));
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiUI::renderConsoleContent(const json& content) {
|
||||
ImGui::Text("🖥️ Console");
|
||||
ImGui::Separator();
|
||||
|
||||
// Console output area
|
||||
ImGui::BeginChild("console_output", ImVec2(0, -30), true);
|
||||
|
||||
if (content.contains("logs")) {
|
||||
for (const auto& log : content["logs"]) {
|
||||
std::string level = log.value("level", "info");
|
||||
std::string message = log.value("message", "");
|
||||
std::string timestamp = log.value("timestamp", "");
|
||||
|
||||
// Color based on level
|
||||
if (level == "error") {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "[%s] %s - %s",
|
||||
timestamp.c_str(), level.c_str(), message.c_str());
|
||||
} else if (level == "warning") {
|
||||
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "[%s] %s - %s",
|
||||
timestamp.c_str(), level.c_str(), message.c_str());
|
||||
} else if (level == "success") {
|
||||
ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "[%s] %s - %s",
|
||||
timestamp.c_str(), level.c_str(), message.c_str());
|
||||
} else {
|
||||
ImGui::Text("[%s] %s - %s", timestamp.c_str(), level.c_str(), message.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
// Command input
|
||||
static char command_buffer[256] = "";
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
if (ImGui::InputText("##command", command_buffer, sizeof(command_buffer),
|
||||
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
if (custom_request_callbacks.count("console_command")) {
|
||||
custom_request_callbacks["console_command"]({{"command", std::string(command_buffer)}});
|
||||
}
|
||||
command_buffer[0] = '\0'; // Clear buffer
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiUI::renderPerformanceContent(const json& content) {
|
||||
ImGui::Text("📊 Performance Monitor");
|
||||
ImGui::Separator();
|
||||
|
||||
if (content.contains("fps")) {
|
||||
ImGui::Text("FPS: %d", content.value("fps", 0));
|
||||
}
|
||||
if (content.contains("frame_time")) {
|
||||
ImGui::Text("Frame Time: %s", content.value("frame_time", "0ms").c_str());
|
||||
}
|
||||
if (content.contains("memory_usage")) {
|
||||
ImGui::Text("Memory: %s", content.value("memory_usage", "0MB").c_str());
|
||||
}
|
||||
if (content.contains("entities")) {
|
||||
ImGui::Text("Entities: %d", content.value("entities", 0));
|
||||
}
|
||||
|
||||
// Real-time FPS display
|
||||
ImGui::Spacing();
|
||||
ImGui::Text("Real-time FPS: %.1f", ImGui::GetIO().Framerate);
|
||||
}
|
||||
|
||||
void ImGuiUI::renderCompaniesContent(const json& content) {
|
||||
ImGui::Text("🏢 Companies");
|
||||
ImGui::Separator();
|
||||
|
||||
for (const auto& [company_name, company_data] : content.items()) {
|
||||
if (ImGui::CollapsingHeader(company_name.c_str())) {
|
||||
if (company_data.contains("cash")) {
|
||||
ImGui::Text("💰 Cash: $%d", company_data.value("cash", 0));
|
||||
}
|
||||
if (company_data.contains("status")) {
|
||||
ImGui::Text("📊 Status: %s", company_data.value("status", "unknown").c_str());
|
||||
}
|
||||
if (company_data.contains("strategy")) {
|
||||
ImGui::Text("🎯 Strategy: %s", company_data.value("strategy", "none").c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiUI::renderAlertsContent(const json& content) {
|
||||
ImGui::Text("⚠️ Alerts");
|
||||
ImGui::Separator();
|
||||
|
||||
if (content.contains("urgent_alerts")) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "🚨 URGENT:");
|
||||
for (const auto& alert : content["urgent_alerts"]) {
|
||||
if (alert.is_string()) {
|
||||
ImGui::BulletText("%s", alert.get<std::string>().c_str());
|
||||
} else {
|
||||
ImGui::BulletText("%s", alert.dump().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (content.contains("warnings")) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "⚠️ Warnings:");
|
||||
for (const auto& warning : content["warnings"]) {
|
||||
if (warning.is_string()) {
|
||||
ImGui::BulletText("%s", warning.get<std::string>().c_str());
|
||||
} else {
|
||||
ImGui::BulletText("%s", warning.dump().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
if (ImGui::Button("✅ Acknowledge All")) {
|
||||
if (custom_request_callbacks.count("acknowledge_alerts")) {
|
||||
custom_request_callbacks["acknowledge_alerts"]({});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiUI::renderSettingsContent(const json& content) {
|
||||
ImGui::Text("⚙️ Settings");
|
||||
ImGui::Separator();
|
||||
|
||||
if (content.contains("graphics")) {
|
||||
if (ImGui::CollapsingHeader("🖥️ Graphics")) {
|
||||
auto graphics = content["graphics"];
|
||||
if (graphics.is_object()) {
|
||||
ImGui::Text("Resolution: %s", graphics.value("resolution", "Unknown").c_str());
|
||||
bool fullscreen = graphics.value("fullscreen", false);
|
||||
if (ImGui::Checkbox("Fullscreen", &fullscreen)) {
|
||||
// Handle setting change
|
||||
}
|
||||
bool vsync = graphics.value("vsync", true);
|
||||
if (ImGui::Checkbox("VSync", &vsync)) {
|
||||
// Handle setting change
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (content.contains("audio")) {
|
||||
if (ImGui::CollapsingHeader("🔊 Audio")) {
|
||||
auto audio = content["audio"];
|
||||
if (audio.is_object()) {
|
||||
float master_vol = audio.value("master_volume", 1.0f);
|
||||
if (ImGui::SliderFloat("Master Volume", &master_vol, 0.0f, 1.0f)) {
|
||||
// Handle setting change
|
||||
}
|
||||
float effects_vol = audio.value("effects_volume", 1.0f);
|
||||
if (ImGui::SliderFloat("Effects Volume", &effects_vol, 0.0f, 1.0f)) {
|
||||
// Handle setting change
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiUI::renderGenericContent(const json& content) {
|
||||
ImGui::Text("📄 Data");
|
||||
ImGui::Separator();
|
||||
|
||||
// Generic JSON display
|
||||
std::ostringstream oss;
|
||||
oss << content.dump(2); // Pretty print with 2-space indent
|
||||
ImGui::TextWrapped("%s", oss.str().c_str());
|
||||
}
|
||||
|
||||
void ImGuiUI::renderLogConsole() {
|
||||
// Always visible log console at bottom
|
||||
ImGui::SetNextWindowSize(ImVec2(screen_size.x, 200), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowPos(ImVec2(0, screen_size.y - 200), ImGuiCond_FirstUseEver);
|
||||
|
||||
if (ImGui::Begin("📜 System Log", nullptr,
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
|
||||
ImGui::BeginChild("log_scroll", ImVec2(0, 150), true);
|
||||
|
||||
for (const auto& log_msg : log_messages) {
|
||||
auto duration = log_msg.timestamp.time_since_epoch();
|
||||
auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() % 100000;
|
||||
|
||||
const char* level_str = toString(log_msg.level);
|
||||
|
||||
// Color based on level
|
||||
ImVec4 color = {1.0f, 1.0f, 1.0f, 1.0f}; // Default white
|
||||
switch (log_msg.level) {
|
||||
case EventLevel::ERROR: color = {1.0f, 0.0f, 0.0f, 1.0f}; break;
|
||||
case EventLevel::WARNING: color = {1.0f, 1.0f, 0.0f, 1.0f}; break;
|
||||
case EventLevel::SUCCESS: color = {0.0f, 1.0f, 0.0f, 1.0f}; break;
|
||||
case EventLevel::DEBUG: color = {0.7f, 0.7f, 0.7f, 1.0f}; break;
|
||||
case EventLevel::INFO:
|
||||
default: color = {1.0f, 1.0f, 1.0f, 1.0f}; break;
|
||||
}
|
||||
|
||||
ImGui::TextColored(color, "[%05lld] [%s] %s",
|
||||
millis, level_str, log_msg.message.c_str());
|
||||
}
|
||||
|
||||
// Auto-scroll to bottom
|
||||
if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
|
||||
ImGui::SetScrollHereY(1.0f);
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,120 +0,0 @@
|
||||
#include "warfactory/ResourceRegistry.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
// Static member initialization
|
||||
std::unique_ptr<ResourceRegistry> ResourceRegistry::instance = nullptr;
|
||||
bool ResourceRegistry::initialized = false;
|
||||
|
||||
ResourceRegistry& ResourceRegistry::getInstance() {
|
||||
if (!initialized) {
|
||||
initialize();
|
||||
}
|
||||
return *instance;
|
||||
}
|
||||
|
||||
void ResourceRegistry::initialize() {
|
||||
if (!initialized) {
|
||||
instance = std::unique_ptr<ResourceRegistry>(new ResourceRegistry());
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceRegistry::shutdown() {
|
||||
if (initialized) {
|
||||
instance.reset();
|
||||
initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// REGISTRATION (Initialization Phase)
|
||||
// ========================================
|
||||
|
||||
uint32_t ResourceRegistry::registerResource(const Resource& resource) {
|
||||
if (next_id >= MAX_RESOURCES) {
|
||||
// Handle overflow - could throw or return INVALID_RESOURCE_ID
|
||||
return INVALID_RESOURCE_ID;
|
||||
}
|
||||
|
||||
const uint32_t assigned_id = next_id++;
|
||||
|
||||
// Ensure vector is large enough
|
||||
if (resources.size() <= assigned_id) {
|
||||
resources.resize(assigned_id + 1);
|
||||
}
|
||||
|
||||
// Store resource at index = ID
|
||||
resources[assigned_id] = resource;
|
||||
|
||||
// Map name to ID for lookup
|
||||
name_to_id[resource.getResourceId()] = assigned_id;
|
||||
|
||||
return assigned_id;
|
||||
}
|
||||
|
||||
void ResourceRegistry::loadResourcesFromJson(const json& resources_json) {
|
||||
for (json::const_iterator it = resources_json.begin(); it != resources_json.end(); ++it) {
|
||||
const std::string& resource_name = it.key();
|
||||
const json& resource_data = it.value();
|
||||
|
||||
// Create resource from JSON
|
||||
Resource resource = Resource::loadFromJson(resource_name, resource_data);
|
||||
|
||||
// Register it
|
||||
const uint32_t resource_id = registerResource(resource);
|
||||
|
||||
// Log or handle registration result if needed
|
||||
(void)resource_id; // Suppress unused variable warning
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// RUNTIME ACCESS (Performance Critical)
|
||||
// ========================================
|
||||
|
||||
const Resource* ResourceRegistry::getResource(uint32_t id) const {
|
||||
if (id == INVALID_RESOURCE_ID || id >= resources.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &resources[id];
|
||||
}
|
||||
|
||||
uint32_t ResourceRegistry::getResourceId(const std::string& name) const {
|
||||
const std::unordered_map<std::string, uint32_t>::const_iterator it = name_to_id.find(name);
|
||||
return (it != name_to_id.end()) ? it->second : INVALID_RESOURCE_ID;
|
||||
}
|
||||
|
||||
bool ResourceRegistry::isValidResourceId(uint32_t id) const {
|
||||
return id != INVALID_RESOURCE_ID && id < resources.size();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// BULK OPERATIONS
|
||||
// ========================================
|
||||
|
||||
std::vector<uint32_t> ResourceRegistry::getAllResourceIds() const {
|
||||
std::vector<uint32_t> ids;
|
||||
ids.reserve(next_id - 1); // -1 because we start at 1
|
||||
|
||||
for (uint32_t id = 1; id < next_id; ++id) {
|
||||
if (id < resources.size()) {
|
||||
ids.push_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
size_t ResourceRegistry::getResourceCount() const {
|
||||
return next_id - 1; // -1 because we start at 1
|
||||
}
|
||||
|
||||
void ResourceRegistry::clear() {
|
||||
resources.clear();
|
||||
name_to_id.clear();
|
||||
next_id = 1;
|
||||
}
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,50 +1,54 @@
|
||||
# World Generation Realist Module
|
||||
|
||||
**Responsabilité**: Génération procédurale géologiquement réaliste avec architecture Phase/Step.
|
||||
**Responsabilité**: Génération procédurale géologiquement réaliste avec architecture Phase/Function simplifiée.
|
||||
|
||||
## Description
|
||||
|
||||
Ce module implémente un système de génération procédurale basé sur une architecture Phase/Step modulaire. Conçu pour implémenter le système 8-phases défini dans `gameData/WorldGeneration/Regular_world.json` avec Phase 0 d'initialisation.
|
||||
Ce module implémente un système de génération procédurale basé sur une architecture Phase/Function en 2 couches. Conçu pour implémenter le système 8-phases défini dans `gameData/WorldGeneration/Regular_world.json` avec Phase 0 d'initialisation.
|
||||
|
||||
## Architecture
|
||||
## Architecture Hiérarchique (SIMPLIFIÉE)
|
||||
|
||||
### Interfaces Core
|
||||
- **IWorldGenerationFunction**: Interface unique pour toutes les fonctions de génération
|
||||
- **WorldGenerationFunctionFactory**: Factory pour créer les fonctions par nom
|
||||
- **WorldGenerationOrchestrator**: Orchestrateur principal des phases
|
||||
```
|
||||
WorldGenerationOrchestrator
|
||||
└── IWorldGenerationPhase (conteneur logique - ex: "Planetary Accretion")
|
||||
└── IWorldGenerationFunction[] (implémentations concrètes)
|
||||
```
|
||||
|
||||
### Systèmes Implémentés
|
||||
**Architecture réduite de 3 → 2 couches**: IWorldGenerationStep supprimé (wrapper redondant).
|
||||
|
||||
#### Phase 0: World Initialization
|
||||
- **InitializeWorldTerrainFunction**: Configuration initiale du terrain (-100°C, -30000m)
|
||||
- **InitializePlanetaryCoreFunction**: Noyau planétaire avec composition réaliste (1.94e21 tonnes)
|
||||
### Interfaces Core (Headers)
|
||||
- **IWorldGenerationFunction**: Interface pour implémentations concrètes (configure/execute/reset)
|
||||
- **IWorldGenerationPhase**: Conteneur orchestrant plusieurs Functions avec support de cycles
|
||||
- **WorldGenerationOrchestrator**: Chef d'orchestre exécutant les Phases séquentiellement
|
||||
- **WorldGenerationFunctionFactory**: Factory pour instancier Functions par nom
|
||||
- **PhaseRegistry**: Registry pour enregistrer et créer des Phases
|
||||
|
||||
#### Phase 1: Planetary Accretion
|
||||
- **MeteoriteImpactGenerationFunction**: Génération de météorites avec MeteoriteFactory
|
||||
- **ImpactEffectsApplicationFunction**: Application des effets physiques (cratères, chaleur, dépôts)
|
||||
### Implémentations Actuelles
|
||||
|
||||
**Functions (.h + .cpp)** - 8 fonctions production-ready:
|
||||
- Phase 0: `InitializeWorldTerrainFunction`, `InitializePlanetaryCoreFunction`
|
||||
- Phase 1: `MeteoriteImpactGenerationFunction`, `ImpactEffectsApplicationFunction`, `PlanetaryDifferentiationFunction`, `VolcanicRedistributionFunction`, `CoolingPhaseFunction`, `UniversalRegionFusionFunction`
|
||||
|
||||
**Data Structures (.h + .cpp)** - Production-ready:
|
||||
- `WorldData`, `Meteorite`, `MeteoriteFactory`, `PlanetaryCore`, `Volcano`, `VolcanoFactory`, `TectonicRegion`, `ClimateRegion`
|
||||
|
||||
**À Implémenter (.cpp)** - Headers finalisés, implémentations manquantes:
|
||||
- `WorldGenerationPhase`, `WorldGenerationOrchestrator`, `PhaseRegistry`
|
||||
|
||||
### Systèmes Techniques
|
||||
|
||||
#### MeteoriteFactory (Pattern Pool)
|
||||
- **Template Pool**: Pré-génération des météorites types
|
||||
- **Copy + Modify**: Clone des templates avec modifications de taille
|
||||
- **Feed/Pull Pattern**: Configuration → Génération → Clonage
|
||||
**MeteoriteFactory (Pattern Pool)**:
|
||||
- Template Pool → Copy + Modify → Pull pattern pour performance
|
||||
- Seedable via `RandomGenerator::getInstance()` (OBLIGATOIRE)
|
||||
|
||||
#### RandomGenerator Singleton
|
||||
- **Centralisé**: `RandomGenerator::getInstance()` partout
|
||||
- **Seedable**: Reproductibilité pour tests et debug
|
||||
- **Performance**: Un seul générateur MT19937
|
||||
**Échelle Réaliste**:
|
||||
- Noyau planétaire: `__uint128_t` (1.94e21 tonnes)
|
||||
- Gameplay: `uint64_t` (jusqu'à 1e13 tonnes)
|
||||
|
||||
#### Échelle Réaliste
|
||||
- **Noyau**: 1.94e21 tonnes (échelle terrestre réelle)
|
||||
- **Météorites**: Jusqu'à 1e13 tonnes (cohérent avec meteorites.json)
|
||||
- **Types**: uint64_t pour gameplay, __uint128_t pour noyau planétaire
|
||||
|
||||
### Conception
|
||||
- **Phase**: Groupe logique d'étapes avec cycles temporels cohérents
|
||||
- **Step**: Opération atomique de génération (IWorldGenerationFunction)
|
||||
- **Producer/Consumer**: Séparation génération ↔ application des effets
|
||||
- **Modularité**: Chaque fonction isolée et testable
|
||||
**Conception**:
|
||||
- Functions isolées et testables (200-300 lignes max)
|
||||
- Pattern Configure → Execute → Reset
|
||||
- Producer/Consumer pour génération ↔ application des effets
|
||||
|
||||
## Contraintes Modules
|
||||
- **200-300 lignes max** par implémentation
|
||||
@ -67,14 +71,18 @@ cmake . # Configuration autonome
|
||||
- `IDataNode`: Interface configuration depuis core/
|
||||
- `C++20`: Features modernes
|
||||
|
||||
### Structure Prévue
|
||||
### Structure Actuelle
|
||||
```
|
||||
include/
|
||||
├── IWorldGenerationPhase.h # Interface phase
|
||||
├── IWorldGenerationStep.h # Interface step
|
||||
└── (futures implémentations...)
|
||||
├── WorldGenerationFunctions/
|
||||
│ ├── IWorldGenerationFunction.h # Interface base (✅ finalisée)
|
||||
│ ├── WorldGenerationFunctionFactory.h # Factory (✅ + .cpp)
|
||||
│ └── [8 implémentations .h] # Functions concrètes (✅ + .cpp)
|
||||
├── IWorldGenerationPhase.h # Interface Phase (✅ header only)
|
||||
├── WorldGenerationOrchestrator.h # Orchestrateur (✅ header only)
|
||||
└── PhaseRegistry.h # Registry (✅ header only)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**État actuel**: Interfaces définies, prêt pour implémentations concrètes.
|
||||
**État actuel**: 8 Functions implémentées, architecture Phase/Function 2-couches définie (headers uniquement).
|
||||
@ -6,35 +6,110 @@
|
||||
#include "warfactory/IDataNode.h"
|
||||
|
||||
class WorldData;
|
||||
class IWorldGenerationStep;
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
/**
|
||||
* @brief Interface for world generation phases
|
||||
*
|
||||
* A phase is a logical grouping of steps that execute sequentially.
|
||||
* Phases can be executed step-by-step (executeOneStep) or all at once (execute).
|
||||
*/
|
||||
class IWorldGenerationPhase {
|
||||
public:
|
||||
virtual ~IWorldGenerationPhase() = default;
|
||||
|
||||
/**
|
||||
* @brief Get the human-readable name of this phase
|
||||
* @return Phase name (e.g., "planetary_accretion")
|
||||
*/
|
||||
virtual std::string getPhaseName() const = 0;
|
||||
|
||||
virtual std::vector<std::unique_ptr<IWorldGenerationStep>> getSteps() const = 0;
|
||||
/**
|
||||
* @brief Get the unique identifier of this phase
|
||||
* @return Phase ID matching JSON configuration
|
||||
*/
|
||||
virtual std::string getPhaseId() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Execute all remaining steps in this phase
|
||||
* @param world The world data to modify
|
||||
* @param config Configuration node for this phase
|
||||
* @return true if all steps completed successfully, false otherwise
|
||||
*/
|
||||
virtual bool execute(WorldData& world, const IDataNode& config) = 0;
|
||||
|
||||
/**
|
||||
* @brief Execute the next step in the phase
|
||||
* @param world The world data to modify
|
||||
* @param config Configuration node for this phase
|
||||
* @return true if step completed successfully, false otherwise
|
||||
*/
|
||||
virtual bool executeOneStep(WorldData& world, const IDataNode& config) = 0;
|
||||
|
||||
/**
|
||||
* @brief Execute one cycle of the current step
|
||||
* @param world The world data to modify
|
||||
* @param config Configuration node for this phase
|
||||
* @return true if cycle completed successfully, false otherwise
|
||||
*
|
||||
* For phases with cycles (duration_cycles in JSON), this executes one cycle.
|
||||
* For single-shot steps, this is equivalent to executeOneStep().
|
||||
*/
|
||||
virtual bool executeOneCycle(WorldData& world, const IDataNode& config) = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the name of the currently executing step
|
||||
* @return Current step name, or empty string if phase not started or complete
|
||||
*/
|
||||
virtual std::string getCurrentStepName() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the index of the currently executing step
|
||||
* @return Current step index (0-based), or -1 if phase not started or complete
|
||||
*/
|
||||
virtual int getCurrentStepIndex() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the total number of steps in this phase
|
||||
* @return Total number of steps
|
||||
*/
|
||||
virtual int getTotalSteps() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the current cycle index within the current step
|
||||
* @return Current cycle index (0-based), or -1 if no cycles in progress
|
||||
*/
|
||||
virtual int getCurrentCycleIndex() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the total number of cycles for the current step
|
||||
* @return Total cycles for current step, or 0 if step not started
|
||||
*/
|
||||
virtual int getTotalCyclesForCurrentStep() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the current progress of this phase
|
||||
* @return Progress value between 0.0 (not started) and 1.0 (complete)
|
||||
*/
|
||||
virtual float getProgress() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Check if all steps in this phase are complete
|
||||
* @return true if phase is complete, false otherwise
|
||||
*/
|
||||
virtual bool isComplete() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Reset the phase to initial state
|
||||
*/
|
||||
virtual void reset() = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the human-readable description of this phase
|
||||
* @return Phase description from JSON configuration
|
||||
*/
|
||||
virtual std::string getPhaseDescription() const = 0;
|
||||
|
||||
virtual int getPhaseNumber() const = 0;
|
||||
|
||||
virtual std::vector<std::string> getRequiredPreviousPhases() const = 0;
|
||||
|
||||
virtual std::vector<std::string> getProducedData() const = 0;
|
||||
|
||||
virtual bool canExecute(const WorldData& world) const = 0;
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,40 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "warfactory/IDataNode.h"
|
||||
|
||||
class WorldData;
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
class IWorldGenerationStep {
|
||||
public:
|
||||
virtual ~IWorldGenerationStep() = default;
|
||||
|
||||
virtual std::string getStepName() const = 0;
|
||||
|
||||
virtual bool execute(WorldData& world, const IDataNode& config) = 0;
|
||||
|
||||
virtual bool canExecute(const WorldData& world) const = 0;
|
||||
|
||||
virtual float getProgress() const = 0;
|
||||
|
||||
virtual bool isComplete() const = 0;
|
||||
|
||||
virtual void reset() = 0;
|
||||
|
||||
virtual std::string getStepDescription() const = 0;
|
||||
|
||||
virtual float getEstimatedDuration() const = 0;
|
||||
|
||||
virtual std::vector<std::string> getRequiredPreviousSteps() const = 0;
|
||||
|
||||
virtual std::vector<std::string> getProducedData() const = 0;
|
||||
|
||||
virtual int getStepOrder() const = 0;
|
||||
|
||||
virtual std::string getParentPhase() const = 0;
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,40 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "IWorldGenerationPhase.h"
|
||||
#include "IWorldGenerationStep.h"
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
/**
|
||||
* @brief Registry for world generation phases
|
||||
*
|
||||
* Simplified architecture: Phase directly manages Functions (no Step wrapper)
|
||||
* Functions are registered via WorldGenerationFunctionFactory
|
||||
*/
|
||||
class PhaseRegistry {
|
||||
private:
|
||||
std::unordered_map<std::string, std::function<std::unique_ptr<IWorldGenerationPhase>()>> phase_factories;
|
||||
std::unordered_map<std::string, std::function<std::unique_ptr<IWorldGenerationStep>()>> step_factories;
|
||||
std::unordered_map<std::string, std::function<std::unique_ptr<IWorldGenerationPhase>()>> phase_factories_;
|
||||
|
||||
public:
|
||||
static PhaseRegistry& getInstance();
|
||||
|
||||
/**
|
||||
* @brief Register a phase type with its unique identifier
|
||||
* @tparam PhaseType Concrete phase implementation
|
||||
* @param phase_id Unique phase identifier (e.g., "world_initialization")
|
||||
*/
|
||||
template<typename PhaseType>
|
||||
void registerPhase(const std::string& phase_name) {
|
||||
phase_factories[phase_name] = []() {
|
||||
void registerPhase(const std::string& phase_id) {
|
||||
phase_factories_[phase_id] = []() {
|
||||
return std::make_unique<PhaseType>();
|
||||
};
|
||||
}
|
||||
|
||||
template<typename StepType>
|
||||
void registerStep(const std::string& step_name) {
|
||||
step_factories[step_name] = []() {
|
||||
return std::make_unique<StepType>();
|
||||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<IWorldGenerationPhase> createPhase(const std::string& phase_name);
|
||||
std::unique_ptr<IWorldGenerationStep> createStep(const std::string& step_name);
|
||||
/**
|
||||
* @brief Create a phase instance by ID
|
||||
* @param phase_id Phase identifier from JSON configuration
|
||||
* @return Unique pointer to phase instance, or nullptr if not found
|
||||
*/
|
||||
std::unique_ptr<IWorldGenerationPhase> createPhase(const std::string& phase_id);
|
||||
|
||||
/**
|
||||
* @brief Get list of all registered phase IDs
|
||||
* @return Vector of registered phase identifiers
|
||||
*/
|
||||
std::vector<std::string> getRegisteredPhases() const;
|
||||
std::vector<std::string> getRegisteredSteps() const;
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -63,34 +63,6 @@ public:
|
||||
* After reset(), configure() must be called again.
|
||||
*/
|
||||
virtual void reset() = 0;
|
||||
|
||||
// ========================================
|
||||
// OPTIONAL: Progress Tracking (for UI/debug)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief Get execution progress (optional - default implementation)
|
||||
* @return Progress from 0.0 (not started) to 1.0 (complete)
|
||||
*/
|
||||
virtual float getProgress() const { return 0.0f; }
|
||||
|
||||
/**
|
||||
* @brief Check if execution is complete (optional - default implementation)
|
||||
* @return True if function finished execution
|
||||
*/
|
||||
virtual bool isComplete() const { return false; }
|
||||
|
||||
/**
|
||||
* @brief Get human-readable description (optional - default implementation)
|
||||
* @return Description of what this function does
|
||||
*/
|
||||
virtual std::string getFunctionDescription() const { return getStepName(); }
|
||||
|
||||
/**
|
||||
* @brief Get parent phase name (optional - default implementation)
|
||||
* @return Phase name from Regular_world.json
|
||||
*/
|
||||
virtual std::string getParentPhase() const { return "unknown"; }
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -1,40 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "warfactory/IDataNode.h"
|
||||
|
||||
class WorldData;
|
||||
class IWorldGenerationStep;
|
||||
|
||||
namespace warfactory {
|
||||
|
||||
class IWorldGenerationPhase {
|
||||
public:
|
||||
virtual ~IWorldGenerationPhase() = default;
|
||||
|
||||
virtual std::string getPhaseName() const = 0;
|
||||
|
||||
virtual std::vector<std::unique_ptr<IWorldGenerationStep>> getSteps() const = 0;
|
||||
|
||||
virtual bool execute(WorldData& world, const IDataNode& config) = 0;
|
||||
|
||||
virtual float getProgress() const = 0;
|
||||
|
||||
virtual bool isComplete() const = 0;
|
||||
|
||||
virtual void reset() = 0;
|
||||
|
||||
virtual std::string getPhaseDescription() const = 0;
|
||||
|
||||
virtual int getPhaseNumber() const = 0;
|
||||
|
||||
virtual std::vector<std::string> getRequiredPreviousPhases() const = 0;
|
||||
|
||||
virtual std::vector<std::string> getProducedData() const = 0;
|
||||
|
||||
virtual bool canExecute(const WorldData& world) const = 0;
|
||||
};
|
||||
|
||||
} // namespace warfactory
|
||||
@ -41,10 +41,17 @@ public:
|
||||
void reset();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Build phase pipeline from JSON configuration
|
||||
* @param config Root configuration node containing "geological_simulation"
|
||||
*/
|
||||
void buildPhasesFromConfig(const IDataNode& config);
|
||||
|
||||
std::unique_ptr<IWorldGenerationStep> createStepFromConfig(const IDataNode& step_config);
|
||||
|
||||
/**
|
||||
* @brief Create a phase instance from JSON configuration
|
||||
* @param phase_config Configuration node for a single phase
|
||||
* @return Unique pointer to configured phase instance
|
||||
*/
|
||||
std::unique_ptr<IWorldGenerationPhase> createPhaseFromConfig(const IDataNode& phase_config);
|
||||
};
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
#include "src/core/include/warfactory/IUI_Enums.h"
|
||||
#include "src/core/include/warfactory/ImGuiUI.h"
|
||||
#include <grove/IUI_Enums.h>
|
||||
#include <grove/ImGuiUI.h>
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
|
||||
using namespace warfactory;
|
||||
using namespace grove;
|
||||
|
||||
void showUsage(const char* program_name) {
|
||||
std::cout << "Usage: " << program_name << " [options]\n"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user