ADD: Upgrade buildings

This commit is contained in:
sebastian 2026-02-21 07:09:44 +01:00
parent a582ddc519
commit 398414a43d
22 changed files with 302223 additions and 49 deletions

View File

@ -248,6 +248,12 @@ add_executable(Dicewars_Siedler src/main.cpp
src/game/ui/components/buildingMenu/UiBuildingMenuCostContainer.h
src/game/hexWorld/events/BuildingTypeSelectEvent.cpp
src/game/hexWorld/events/BuildingTypeSelectEvent.h
src/game/hexWorld/ecs/components/UpgradeComponent.cpp
src/game/hexWorld/ecs/components/UpgradeComponent.h
src/game/hexWorld/ecs/systems/UpgradeSystem.cpp
src/game/hexWorld/ecs/systems/UpgradeSystem.h
src/game/hexWorld/ecs/systems/SelectionSystem.cpp
src/game/hexWorld/ecs/systems/SelectionSystem.h
)
target_compile_options(Dicewars_Siedler PRIVATE

View File

@ -6,23 +6,16 @@
{
"name": "cabin-base",
"filename": "cabin.obj",
"conditionKey": "fillRatio",
"minValue": 0.0,
"maxValue": 0.1
"conditionKey": "level",
"minValue": 1,
"maxValue": 1
},
{
"name": "cabin-one",
"filename": "cabin_1.obj",
"conditionKey": "fillRatio",
"minValue": 0.1,
"maxValue": 0.2
},
{
"name": "cabin-two",
"filename": "cabin_2.obj",
"conditionKey": "fillRatio",
"minValue": 0.2,
"maxValue": 1.0
"name": "cabin-level2",
"filename": "cabin2.obj",
"conditionKey": "level",
"minValue": 2,
"maxValue": 2
}
]
}

View File

@ -0,0 +1,12 @@
# Blender 5.0.1 MTL File: 'None'
# www.blender.org
newmtl Material.001
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 1.000000
illum 2
map_Kd cabin.jpg

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

View File

@ -0,0 +1,14 @@
{
"name": "cabin2",
"allowedTileResources" : ["stone"],
"cost" : {"wood": 15},
"modelStages": [
{
"name": "cabin2-base",
"filename": "cabin2.obj",
"conditionKey": "fillRatio",
"minValue": 0.0,
"maxValue": 0.1
}
]
}

View File

@ -0,0 +1,12 @@
# Blender 5.0.1 MTL File: 'None'
# www.blender.org
newmtl Material.001
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 1.000000
illum 2
map_Kd cabin.jpg

File diff suppressed because it is too large Load Diff

View File

@ -52,6 +52,14 @@ public:
return nullptr;
}
template<typename T>
void removeComponent(EntityID entity) {
auto it = components.find(typeid(T));
if (it != components.end()) {
it->second.erase(entity);
}
}
const std::vector<EntityID>& getAllEntities() const { return entities; }
};

View File

@ -7,6 +7,7 @@
#include <cassert>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <utility>
#include "json.hpp"

View File

@ -25,6 +25,8 @@
#include "hexWorld/ecs/components/ProducingComponent.h"
#include "hexWorld/ecs/systems/CollectResourceSystem.h"
#include "hexWorld/ecs/systems/ProducingSystem.h"
#include "hexWorld/ecs/systems/SelectionSystem.h"
#include "hexWorld/ecs/systems/UpgradeSystem.h"
#include "hexWorld/events/BuildingTypeSelectEvent.h"
#include "hexWorld/events/TurnChangedEvent.h"
@ -60,6 +62,7 @@ void GameLayer::onAttach()
AssetManager::loadAsset("assets/buildings/stone_mason/stone_mason.json", loader);
AssetManager::loadAsset("assets/buildings/forest_hut/cabin.json", loader);
AssetManager::loadAsset("assets/buildings/forest_hut/level2/cabin.json", loader);
auto modelTexture = AssetManager::loadTexture("warning", "assets/worldIcons/warning.png", loader);
AssetManager::loadTexture("error", "assets/worldIcons/error.png", loader);
@ -111,6 +114,8 @@ void GameLayer::onUpdate()
camera->move(moveDir, 0.5f);
if (gameInputUser->isMouseEnabled()) {
tileHighlightSystem->update(*entityManager, *mousePicker, *camera, *gameMode);
SelectionSystem::update(*entityManager, *gameInputUser, *mousePicker);
UpgradeSystem::tryUpdate(*entityManager, *gameMode, gameMode->getCurrentPlayer(), *turnState);
} else {
tileHighlightSystem->reset(*entityManager);
}

View File

@ -6,6 +6,14 @@
#define DICEWARS_SIEDLER_BUILDINGDEFINITION_H
#include "../ecs/components/BuildingComponent.h"
struct BuildingLevelData {
float productionMultiplier;
int workforce;
int maxStorageBonus;
std::unordered_map<RessourceType, int> upgradeCosts;
};
struct BuildingDefinition {
BuildingType type;
@ -18,6 +26,8 @@ struct BuildingDefinition {
int baseAmountPerTurn;
RessourceType produces;
std::vector<BuildingLevelData> levels;
};
#endif //DICEWARS_SIEDLER_BUILDINGDEFINITION_H

View File

@ -11,12 +11,18 @@ namespace TemporaryBuildingDefinitionFactory {
auto allowedTileResources = std::vector<RessourceType>{RessourceType::WOOD};
auto resourceCosts = std::unordered_map<RessourceType, int>{};
resourceCosts[RessourceType::WOOD] = 10;
std::vector<BuildingLevelData> levels = {};
auto upgradeCosts = std::unordered_map<RessourceType, int>{};
upgradeCosts[RessourceType::WOOD] = 15;
levels.push_back({
1.2, 3, 5, upgradeCosts
});
return {
BuildingType::FOREST_HUT,
allowedTileResources,
resourceCosts,
"cabin",
20, 1, RessourceType::WOOD
20, 1, RessourceType::WOOD, levels
};
}
@ -25,12 +31,14 @@ namespace TemporaryBuildingDefinitionFactory {
auto resourceCosts = std::unordered_map<RessourceType, int>{};
resourceCosts[RessourceType::WOOD] = 15;
resourceCosts[RessourceType::STONE] = 10;
return {
BuildingType::FOREST_HUT,
allowedTileResources,
resourceCosts,
"stoneMason",
20, 1, RessourceType::STONE
20, 1, RessourceType::STONE,
};
}
}

View File

@ -0,0 +1,7 @@
//
// Created by sebastian on 20.02.26.
//
#include "UpgradeComponent.h"
void UpgradeComponent::setMaxLevel(int maxLevel) {this->maxLevel = maxLevel;}

View File

@ -0,0 +1,29 @@
//
// Created by sebastian on 20.02.26.
//
#ifndef DICEWARS_SIEDLER_UPGRADECOMPONENT_H
#define DICEWARS_SIEDLER_UPGRADECOMPONENT_H
#include "../../../../engine/core/ECS/Component.h"
class UpgradeComponent : public Component {
public:
UpgradeComponent(int currentLevel = 1, int maxLevel = 1): currentLevel(currentLevel), maxLevel(maxLevel) {}
[[nodiscard]] int getCurrentLevel() const {return currentLevel;}
[[nodiscard]] int getMaxLevel() const {return maxLevel;}
[[nodiscard]] bool canUpgrade() const {return currentLevel < maxLevel;}
void upgrade() {currentLevel++;}
void setMaxLevel(int maxLevel);
public:
private:
int currentLevel = 1;
int maxLevel = 1;
};
#endif //DICEWARS_SIEDLER_UPGRADECOMPONENT_H

View File

@ -17,6 +17,7 @@
#include "../../gameplay/TurnState.h"
#include "../components/BuildingComponent.h"
#include "../components/TileGameplayComponent.h"
#include "../components/UpgradeComponent.h"
#include "GLFW/glfw3.h"
void BuildingPlacementSystem::update(EntityManager& entityManager, GameMode& gameMode, PlayerID player, const TurnState& turnState) {
@ -58,6 +59,12 @@ void BuildingPlacementSystem::update(EntityManager& entityManager, GameMode& gam
const EntityID buildingEntity = BuildingFactory::create(entityManager, def, *transform, entityID, player);
gameplay->buildingEntityID = buildingEntity;
if (!def.levels.empty()) {
std::cerr << "Adding upgrade component" << std::endl;
UpgradeComponent upgrade_component = UpgradeComponent(1, static_cast<int>(def.levels.size() + 1));
entityManager.addComponent(buildingEntity, std::make_shared<UpgradeComponent>(upgrade_component));
}
break;
}
};

View File

@ -21,39 +21,11 @@ std::vector<StorageStatus> ProducingSystem::storageIcons = {
void ProducingSystem::onTurnEnded(EntityManager &em) {
for (const EntityID e : em.getAllEntities()) {
const auto prod = em.getComponent<ProducingComponent>(e);
const auto modelState = em.getComponent<ModelStateComponent>(e);
if (!isValidProducer(em, e)) continue;
if (!prod || !modelState) {
continue;
}
if (prod->isBlocked()) {
std::cout << "Producing is blocked for entity " << e << std::endl;
continue;
}
const int produced = prod->getAmountPerTurn();
prod->setStorage(std::min(prod->getStorage() + produced, prod->getMaxStorage()));
float fillRatio = static_cast<float>(prod->getStorage()) / static_cast<float>(prod->getMaxStorage());
modelState->params["fillRatio"] = fillRatio;
// -- Icon / Animation Logik
for (const auto& status : storageIcons) {
if (fillRatio >= status.minRatio && fillRatio <= status.maxRatio) {
auto spriteComp = em.getComponent<WorldSpriteComponent>(e);
if (spriteComp) {
if (spriteComp->iconName != status.iconName) {
spriteComp->iconName = status.iconName;
spriteComp->texture = AssetManager::getTexture(status.iconName);
}
} else {
createWorldSprite(em, e, status.iconName);
}
break;
}
}
handleProduction(em, e);
updateFillRatio(em, e);
updateStorageIcon(em, e);
}
}
@ -86,3 +58,48 @@ void ProducingSystem::createWorldSprite(EntityManager &em, EntityID e, const std
em.addComponent(e, anim);
}
}
bool ProducingSystem::isValidProducer(EntityManager &em, EntityID e) {
const auto prod = em.getComponent<ProducingComponent>(e);
const auto modelState = em.getComponent<ModelStateComponent>(e);
return prod && modelState && !prod->isBlocked();
}
void ProducingSystem::handleProduction(EntityManager &em, EntityID e) {
auto prod = em.getComponent<ProducingComponent>(e);
const int produced = prod->getAmountPerTurn();
prod->setStorage(std::min(prod->getStorage() + produced, prod->getMaxStorage()));
}
void ProducingSystem::updateFillRatio(EntityManager &em, EntityID e) {
const auto prod = em.getComponent<ProducingComponent>(e);
const auto modelState = em.getComponent<ModelStateComponent>(e);
float fillRatio = static_cast<float>(prod->getStorage()) / static_cast<float>(prod->getMaxStorage());
modelState->params["fillRatio"] = fillRatio;
}
void ProducingSystem::updateStorageIcon(EntityManager &em, EntityID e) {
const auto prod = em.getComponent<ProducingComponent>(e);
float fillRatio = static_cast<float>(prod->getStorage()) / static_cast<float>(prod->getMaxStorage());
for (const auto& status : storageIcons) {
if (fillRatio >= status.minRatio && fillRatio <= status.maxRatio) {
applyIcon(em, e, status.iconName);
break;
}
}
}
void ProducingSystem::applyIcon(EntityManager &em, EntityID e, const std::string &iconName) {
if (const auto spriteComp = em.getComponent<WorldSpriteComponent>(e)) {
if (spriteComp->iconName != iconName) {
spriteComp->iconName = iconName;
spriteComp->texture = AssetManager::getTexture(iconName);
} else {
createWorldSprite(em, e, iconName);
}
}
}

View File

@ -6,6 +6,7 @@
#define DICEWARS_SIEDLER_PRODUCINGSYSTEM_H
#include <vector>
#include "../../../../engine/core/ECS/EntityManager.h"
#include "../../../../engine/core/ECS/WorldSpriteComponent.h"
#include "../../../player/Player.h"
@ -25,6 +26,13 @@ private:
static std::vector<StorageStatus> storageIcons;
static void createWorldSprite(EntityManager &em, uint32_t e, const std::string &iconName);
static bool isValidProducer(EntityManager &em, EntityID e);
static void handleProduction(EntityManager &em, EntityID e);
static void updateFillRatio(EntityManager &em, EntityID e);
static void updateStorageIcon(EntityManager &em, EntityID e);
static void applyIcon(EntityManager &em, EntityID e, const std::string &iconName);
};

View File

@ -0,0 +1,38 @@
//
// Created by sebastian on 20.02.26.
//
#include "SelectionSystem.h"
#include <iostream>
#include <ostream>
#include "../../../../engine/core/ECS/TileRenderComponent.h"
#include "../components/TileGameplayComponent.h"
EntityID SelectionSystem::selectedEntity = 0;
void SelectionSystem::update(EntityManager &em, GameInputUser& input, MousePicker& picker) {
//Todo: Move Logic to select tile to highlight here?
if (!input.isMouseEnabled()) return;
if (!Application::getInstance().mouse->isButtonDown(MouseButton::LEFT)) {
return;
}
for (EntityID e : em.getAllEntities()) {
const auto tileRenderComponent = em.getComponent<TileRenderComponent>(e);
const auto gameplayComponent = em.getComponent<TileGameplayComponent>(e);
if (tileRenderComponent && gameplayComponent && tileRenderComponent->isHighlighted) {
//Todo: Store focused Entity in TileHighlightSystem and fetch it?
selectedEntity = e;
break;
}
}
if (Application::getInstance().mouse->isReleaseEvent(MouseButton::LEFT)) {
selectedEntity = 0;
}
}

View File

@ -0,0 +1,20 @@
//
// Created by sebastian on 20.02.26.
//
#ifndef DICEWARS_SIEDLER_SELECTIONSYSTEM_H
#define DICEWARS_SIEDLER_SELECTIONSYSTEM_H
#include "../../../../engine/core/inputsOutputs/stateControl/inputUser/GameInputUser.h"
class EntityManager;
class SelectionSystem {
public:
static EntityID selectedEntity;
public:
static void update(EntityManager& em, GameInputUser& input, MousePicker& picker);
};
#endif //DICEWARS_SIEDLER_SELECTIONSYSTEM_H

View File

@ -0,0 +1,80 @@
//
// Created by sebastian on 20.02.26.
//
#include "UpgradeSystem.h"
#include <iostream>
#include <ostream>
#include "SelectionSystem.h"
#include "../../building/BuildingConfig.h"
#include "../components/UpgradeComponent.h"
#include "../components/BuildingComponent.h"
#include "../components/ProducingComponent.h"
#include "../../../../engine/core/ECS/TileRenderComponent.h"
#include "../../../../engine/core/ECS/ModelComponent.h"
#include "../../../../engine/core/ECS/ModelStateComponent.h"
#include "../components/TileGameplayComponent.h"
void UpgradeSystem::tryUpdate(EntityManager &em, GameMode &gm, PlayerID player, const TurnState &turnState) {
EntityID tileEntityID = SelectionSystem::selectedEntity;
auto tileComponent = em.getComponent<TileGameplayComponent>(tileEntityID);
if (!tileComponent || !tileComponent->hasBuilding()) return;
EntityID entityID = tileComponent->buildingEntityID;
auto buildingComponent = em.getComponent<BuildingComponent>(entityID);
auto tileHighlightComponent = em.getComponent<TileRenderComponent>(tileEntityID);
auto modelComponent = em.getComponent<ModelStateComponent>(entityID);
if (buildingComponent && tileHighlightComponent && tileHighlightComponent->isHighlighted && modelComponent) {
UpgradeResult result = canUpgrade(em, player, gm, entityID, turnState);
if (result != UpgradeResult::Ok) return;
auto tier = em.getComponent<UpgradeComponent>(entityID);
auto building = em.getComponent<BuildingComponent>(entityID);
auto prod = em.getComponent<ProducingComponent>(entityID);
const auto& def = BuildingConfig::get(building->type);
const BuildingLevelData& nextTierData = def.levels.at(tier->getCurrentLevel() -1);
gm.pay(player, nextTierData.upgradeCosts);
tier->upgrade();
prod->setMaxStorage(def.maxStorage + nextTierData.maxStorageBonus);
//Todo: Workforce
//Todo: Change Model Component
modelComponent->params["level"] = 2.f;
}
}
UpgradeResult UpgradeSystem::canUpgrade(EntityManager &em, PlayerID player, GameMode& gameMode, EntityID buildingEntity, const TurnState &turnState) {
if (!gameMode.hasTurn(player, turnState.currentTurn)) return UpgradeResult::NotPlayersTurn;
auto tier = em.getComponent<UpgradeComponent>(buildingEntity);
auto type = em.getComponent<BuildingComponent>(buildingEntity);
if (!tier) return UpgradeResult::NoTierComponent;
if (!type) return UpgradeResult::InvalidTarget;
const auto& def = BuildingConfig::get(BuildingType::FOREST_HUT);
int nextTier = tier->getCurrentLevel() -1;
if (nextTier > static_cast<int>(def.levels.size()+1)) return UpgradeResult::MaxTierReached;
if (!tier->canUpgrade()) return UpgradeResult::ResearchLocked; // maybe move check to special research system
const BuildingLevelData& nextTierData = def.levels.at(tier->getCurrentLevel() -1);
for (const auto& [resource, cost] : def.levels.at(nextTier).upgradeCosts) {
if (!gameMode.canAfford(player, resource, cost)) return UpgradeResult::NotEnoughResources;
}
return UpgradeResult::Ok;
}

View File

@ -0,0 +1,29 @@
//
// Created by sebastian on 20.02.26.
//
#ifndef DICEWARS_SIEDLER_UPGRADESYSTEM_H
#define DICEWARS_SIEDLER_UPGRADESYSTEM_H
#include "../../../GameMode.h"
#include "../../gameplay/TurnState.h"
enum class UpgradeResult {
Ok,
NotPlayersTurn,
NoTierComponent,
MaxTierReached,
ResearchLocked,
NotEnoughResources,
InvalidTarget
};
class EntityManager;
class UpgradeSystem {
public:
static void tryUpdate(EntityManager &em, GameMode &gm, PlayerID player, const TurnState &turnState);
static UpgradeResult canUpgrade(EntityManager &em, PlayerID player, GameMode &gameMode, EntityID buildingEntity, const TurnState &turnState);
};
#endif //DICEWARS_SIEDLER_UPGRADESYSTEM_H