diff --git a/CMakeLists.txt b/CMakeLists.txt
index eab6b24..1775a1d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,22 +1,33 @@
cmake_minimum_required(VERSION 3.20)
project(YourPartDaemon VERSION 1.0 LANGUAGES CXX)
-set(CMAKE_CXX_STANDARD 20)
+# C++ Standard and Compiler Settings
+set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
-set(CMAKE_C_COMPILER "gcc-13")
-set(CMAKE_CPP_COMPILER "g++-13")
+# Explicitly set compiler versions for Tumbleweed
+set(CMAKE_C_COMPILER gcc-15)
+set(CMAKE_CXX_COMPILER g++-15)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto=auto")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -flto")
+set(CMAKE_BUILD_TYPE Debug)
+# Include /usr/local if needed
+list(APPEND CMAKE_PREFIX_PATH /usr/local)
+
+# Find libwebsockets via pkg-config
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(LWS REQUIRED libwebsockets)
+
+# Find other dependencies
find_package(PostgreSQL REQUIRED)
find_package(Threads REQUIRED)
+find_package(nlohmann_json CONFIG REQUIRED)
+
+# PostgreSQL C++ libpqxx
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBPQXX REQUIRED libpqxx)
-include_directories(${LIBPQXX_INCLUDE_DIRS})
-link_directories(${LIBPQXX_LIBRARY_DIRS})
-add_definitions(${LIBPQXX_CFLAGS_OTHER})
-
+# Project sources and headers
set(SOURCES
src/main.cpp
src/config.cpp
@@ -26,6 +37,12 @@ set(SOURCES
src/produce_worker.cpp
src/message_broker.cpp
src/websocket_server.cpp
+ src/stockagemanager.cpp
+ src/director_worker.cpp
+ src/valuerecalculationworker.cpp
+ src/usercharacterworker.cpp
+ src/houseworker.cpp
+ src/politics_worker.cpp
)
set(HEADERS
@@ -37,13 +54,34 @@ set(HEADERS
src/produce_worker.h
src/message_broker.h
src/websocket_server.h
+ src/stockagemanager.h
+ src/director_worker.h
+ src/valuerecalculationworker.h
+ src/usercharacterworker.h
+ src/houseworker.h
+ src/politics_worker.h
)
+# Define executable target
add_executable(yourpart-daemon ${SOURCES} ${HEADERS})
-find_package(nlohmann_json CONFIG REQUIRED)
-target_include_directories(yourpart-daemon PRIVATE ${PostgreSQL_INCLUDE_DIRS})
-target_link_libraries(yourpart-daemon PRIVATE ${PostgreSQL_LIBRARIES} Threads::Threads z ssl crypto ${CMAKE_SOURCE_DIR}/lib/uSockets.a ${LIBPQXX_LIBRARIES})
+# Include directories
+target_include_directories(yourpart-daemon PRIVATE
+ ${PostgreSQL_INCLUDE_DIRS}
+ ${LIBPQXX_INCLUDE_DIRS}
+ ${LWS_INCLUDE_DIRS}
+)
+# Link libraries
+target_link_libraries(yourpart-daemon PRIVATE
+ ${PostgreSQL_LIBRARIES}
+ Threads::Threads
+ z ssl crypto
+ ${LIBPQXX_LIBRARIES}
+ ${LWS_LIBRARIES}
+ nlohmann_json::nlohmann_json
+)
+
+# Installation rules
install(TARGETS yourpart-daemon DESTINATION /usr/local/bin)
install(FILES daemon.conf DESTINATION /etc/yourpart/)
diff --git a/CMakeLists.txt.user b/CMakeLists.txt.user
new file mode 100644
index 0000000..fb68610
--- /dev/null
+++ b/CMakeLists.txt.user
@@ -0,0 +1,414 @@
+
+
+
+
+
+ EnvironmentId
+ {551ef6b3-a39b-43e2-9ee3-ad56e19ff4f4}
+
+
+ ProjectExplorer.Project.ActiveTarget
+ 0
+
+
+ ProjectExplorer.Project.EditorSettings
+
+ true
+ true
+ true
+
+ Cpp
+
+ CppGlobal
+
+
+
+ QmlJS
+
+ QmlJSGlobal
+
+
+ 2
+ UTF-8
+ false
+ 4
+ false
+ 0
+ 80
+ true
+ true
+ 1
+ 0
+ false
+ true
+ false
+ 2
+ true
+ true
+ 0
+ 8
+ true
+ false
+ 1
+ true
+ true
+ true
+ *.md, *.MD, Makefile
+ false
+ true
+ true
+
+
+
+ ProjectExplorer.Project.PluginSettings
+
+
+ true
+ false
+ true
+ true
+ true
+ true
+
+ false
+
+
+ 0
+ true
+
+ true
+ true
+ Builtin.DefaultTidyAndClazy
+ 8
+ true
+
+
+
+ true
+
+
+
+
+ ProjectExplorer.Project.Target.0
+
+ Desktop
+ true
+ Importiertes Kit
+ Importiertes Kit
+ {78ff90a3-f672-45c2-ad08-343b0923896f}
+ 0
+ 0
+ 0
+
+ Debug
+ 2
+ false
+
+ -DCMAKE_BUILD_TYPE:STRING=Release
+-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
+-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
+-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
+-DCMAKE_GENERATOR:STRING=Unix Makefiles
+-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
+-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
+-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
+ /home/torsten/Programs/yourpart-daemon/build/
+
+
+
+
+ all
+
+ false
+
+ true
+ Erstellen
+ CMakeProjectManager.MakeStep
+
+ 1
+ Erstellen
+ Erstellen
+ ProjectExplorer.BuildSteps.Build
+
+
+
+
+
+ clean
+
+ false
+
+ true
+ Erstellen
+ CMakeProjectManager.MakeStep
+
+ 1
+ Bereinigen
+ Bereinigen
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+ false
+
+ Release
+ CMakeProjectManager.CMakeBuildConfiguration
+ 0
+ 0
+
+
+ 0
+ Deployment
+ Deployment
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ProjectExplorer.DefaultDeployConfiguration
+
+
+
+
+
+
+
+
+ false
+
+ true
+ ApplicationManagerPlugin.Deploy.CMakePackageStep
+
+
+ install-package --acknowledge
+ true
+ Application Manager-Paket installieren
+ ApplicationManagerPlugin.Deploy.InstallPackageStep
+
+
+
+
+
+
+
+ 2
+ Deployment
+ Deployment
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ApplicationManagerPlugin.Deploy.Configuration
+
+ 2
+
+ true
+ true
+ 0
+ true
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+ yourpart-daemon
+ CMakeProjectManager.CMakeRunConfiguration.
+ yourpart-daemon
+ false
+ true
+ true
+ true
+ /home/torsten/Programs/yourpart-daemon/build
+
+ 1
+
+
+ Debug
+ 2
+ false
+
+ -DCMAKE_BUILD_TYPE:STRING=Debug
+-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
+-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
+-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
+-DCMAKE_GENERATOR:STRING=Unix Makefiles
+-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
+-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
+-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
+ /mnt/share/torsten/Programs/yourpart-daemon
+ /home/torsten/Programs/yourpart-daemon/build
+
+
+
+
+ all
+
+ false
+
+ true
+ CMakeProjectManager.MakeStep
+
+ 1
+ Erstellen
+ Erstellen
+ ProjectExplorer.BuildSteps.Build
+
+
+
+
+
+ clean
+
+ false
+
+ true
+ CMakeProjectManager.MakeStep
+
+ 1
+ Bereinigen
+ Bereinigen
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+ false
+
+ Debug (importiert)
+ CMakeProjectManager.CMakeBuildConfiguration
+ 0
+ -1
+
+
+ 0
+ Deployment
+ Deployment
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ProjectExplorer.DefaultDeployConfiguration
+
+
+
+
+
+
+ install
+
+ false
+
+ true
+ ApplicationManagerPlugin.Deploy.CMakePackageStep
+
+
+ install-package --acknowledge
+ true
+ Application Manager-Paket installieren
+ ApplicationManagerPlugin.Deploy.InstallPackageStep
+
+
+
+
+
+
+
+ 2
+ Deployment
+ Deployment
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ApplicationManagerPlugin.Deploy.Configuration
+
+ 2
+ 0
+
+ 2
+
+
+ 0
+ Deployment
+ Deployment
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ProjectExplorer.DefaultDeployConfiguration
+
+
+
+
+
+
+
+
+ false
+
+ true
+ ApplicationManagerPlugin.Deploy.CMakePackageStep
+
+
+ install-package --acknowledge
+ true
+ Application Manager-Paket installieren
+ ApplicationManagerPlugin.Deploy.InstallPackageStep
+
+
+
+
+
+
+
+ 2
+ Deployment
+ Deployment
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ApplicationManagerPlugin.Deploy.Configuration
+
+ 2
+
+ true
+ true
+ 0
+ true
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+ yourpart-daemon
+ CMakeProjectManager.CMakeRunConfiguration.
+ yourpart-daemon
+ false
+ true
+ true
+ true
+ /home/torsten/Programs/yourpart-daemon/build
+
+ 1
+
+
+
+ ProjectExplorer.Project.TargetCount
+ 1
+
+
+ ProjectExplorer.Project.Updater.FileVersion
+ 22
+
+
+ Version
+ 22
+
+
diff --git a/CMakeLists.txt.user.d36652f b/CMakeLists.txt.user.d36652f
new file mode 100644
index 0000000..4b2b1e0
--- /dev/null
+++ b/CMakeLists.txt.user.d36652f
@@ -0,0 +1,205 @@
+
+
+
+
+
+ EnvironmentId
+ {d36652ff-969b-426b-a63f-1edd325096c5}
+
+
+ ProjectExplorer.Project.ActiveTarget
+ 0
+
+
+ ProjectExplorer.Project.EditorSettings
+
+ true
+ false
+ true
+
+ Cpp
+
+ CppGlobal
+
+
+
+ QmlJS
+
+ QmlJSGlobal
+
+
+ 2
+ UTF-8
+ false
+ 4
+ false
+ 80
+ true
+ true
+ 1
+ 0
+ false
+ true
+ false
+ 0
+ true
+ true
+ 0
+ 8
+ true
+ false
+ 1
+ true
+ true
+ true
+ *.md, *.MD, Makefile
+ false
+ true
+ true
+
+
+
+ ProjectExplorer.Project.PluginSettings
+
+
+ true
+ false
+ true
+ true
+ true
+ true
+
+
+ 0
+ true
+
+ true
+ true
+ Builtin.DefaultTidyAndClazy
+ 8
+ true
+
+
+
+ true
+
+
+ true
+
+
+
+
+ ProjectExplorer.Project.Target.0
+
+ Desktop
+ Importiertes Kit
+ Importiertes Kit
+ {3c6cfc13-714d-4db1-bd45-b9794643cc67}
+ 0
+ 0
+ 0
+
+ Debug
+ 2
+ false
+
+ -DCMAKE_GENERATOR:STRING=Unix Makefiles
+-DCMAKE_BUILD_TYPE:STRING=Build
+-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
+-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
+-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
+-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
+-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
+ /home/torsten/Programs/yourpart-daemon
+ /home/torsten/Programs/yourpart-daemon/build
+
+
+
+
+ all
+
+ false
+
+ true
+ Erstellen
+ CMakeProjectManager.MakeStep
+
+ 1
+ Erstellen
+ Erstellen
+ ProjectExplorer.BuildSteps.Build
+
+
+
+
+
+ clean
+
+ false
+
+ true
+ Erstellen
+ CMakeProjectManager.MakeStep
+
+ 1
+ Bereinigen
+ Bereinigen
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+ false
+
+ Erstellen
+ CMakeProjectManager.CMakeBuildConfiguration
+
+ 1
+
+
+ 0
+ Deployment
+ Deployment
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ProjectExplorer.DefaultDeployConfiguration
+
+ 1
+
+ true
+ true
+ 0
+ true
+ /usr/bin/valgrind
+
+ 2
+
+ false
+ yourpart-daemon
+ CMakeProjectManager.CMakeRunConfiguration.yourpart-daemon
+ yourpart-daemon
+ false
+ true
+ true
+ true
+ /home/torsten/Programs/yourpart-daemon/build
+
+ 1
+
+
+
+ ProjectExplorer.Project.TargetCount
+ 1
+
+
+ ProjectExplorer.Project.Updater.FileVersion
+ 22
+
+
+ Version
+ 22
+
+
diff --git a/src/character_creation_worker.cpp b/src/character_creation_worker.cpp
index 2dd3793..116b78d 100644
--- a/src/character_creation_worker.cpp
+++ b/src/character_creation_worker.cpp
@@ -3,15 +3,21 @@
#include
#include
#include
+#include
CharacterCreationWorker::CharacterCreationWorker(ConnectionPool &pool, MessageBroker &broker)
- : Worker(pool, broker, "CharacterCreationWorker")
- , gen(std::random_device{}())
- , dist(2, 3)
-{
+ : Worker(pool, broker, "CharacterCreationWorker"),
+ gen(std::random_device{}()),
+ dist(2, 3),
+ deathCheckRunning(true),
+ deathThread(&CharacterCreationWorker::monitorCharacterDeaths, this) {
}
CharacterCreationWorker::~CharacterCreationWorker() {
+ deathCheckRunning.store(false);
+ if (deathThread.joinable()) {
+ deathThread.join();
+ }
}
void CharacterCreationWorker::run() {
@@ -38,22 +44,17 @@ bool CharacterCreationWorker::isTodayCharacterCreated() {
auto &db = connGuard.get();
setCurrentStep("Execute Query");
auto results = db.query(QUERY_IS_PREVIOUS_DAY_CHARACTER_CREATED);
- if (!results.empty()) {
- std::string created_at_str = results[0].at("created_at");
- return true;
- }
+ return !results.empty();
} catch (const std::exception &e) {
- std::cerr << "[CharacterCreationWorker] Fehler in isTodayCharacterCreated: "
- << e.what() << std::endl;
+ std::cerr << "[CharacterCreationWorker] Fehler in isTodayCharacterCreated: " << e.what() << std::endl;
+ return false;
}
- setCurrentStep("No previous day character found");
- return false;
}
void CharacterCreationWorker::createCharactersForToday() {
loadNames();
if (first_name_cache.empty() || last_name_cache.empty()) {
- std::cerr << "Fehler: Namen konnten nicht geladen werden." << std::endl;
+ std::cerr << "[CharacterCreationWorker] Fehler: Namen konnten nicht geladen werden." << std::endl;
return;
}
@@ -67,7 +68,7 @@ void CharacterCreationWorker::createCharactersForRegion(int region_id) {
std::vector nobility_stands = {1, 2, 3};
std::vector genders = {"male", "female"};
for (auto nobility : nobility_stands) {
- for (auto &gender : genders) {
+ for (const auto &gender : genders) {
int num_chars = dist(gen);
for (int i = 0; i < num_chars; ++i) {
createCharacter(region_id, gender, nobility);
@@ -76,9 +77,7 @@ void CharacterCreationWorker::createCharactersForRegion(int region_id) {
}
}
-void CharacterCreationWorker::createCharacter(int region_id,
- const std::string &gender,
- int title_of_nobility) {
+void CharacterCreationWorker::createCharacter(int region_id, const std::string &gender, int title_of_nobility) {
int first_name_id = getRandomFromSet(first_name_cache[gender]);
if (first_name_id == -1) {
std::cerr << "Fehler: Kein passender Vorname gefunden." << std::endl;
@@ -94,54 +93,152 @@ void CharacterCreationWorker::createCharacter(int region_id,
auto &db = connGuard.get();
db.prepare("insert_character", QUERY_INSERT_CHARACTER);
- db.execute("insert_character", {std::to_string(region_id),
- std::to_string(first_name_id),
- std::to_string(last_name_id),
- gender,
+ db.execute("insert_character", {std::to_string(region_id),
+ std::to_string(first_name_id),
+ std::to_string(last_name_id),
+ gender,
std::to_string(title_of_nobility)});
} catch (const std::exception &e) {
- std::cerr << "[CharacterCreationWorker] Fehler in createCharacter: "
+ std::cerr << "[CharacterCreationWorker] Fehler in createCharacter: " << e.what() << std::endl;
+ }
+}
+
+void CharacterCreationWorker::monitorCharacterDeaths() {
+ while (deathCheckRunning) {
+ try {
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+
+ auto results = db.query(QUERY_GET_ELIGIBLE_NPC_FOR_DEATH);
+ for (const auto &row : results) {
+ int characterId = std::stoi(row.at("id"));
+ int age = std::stoi(row.at("age"));
+ if (calculateDeathProbability(age)) {
+ handleCharacterDeath(characterId);
+ }
+ }
+ } catch (const std::exception &e) {
+ std::cerr << "[CharacterCreationWorker] Fehler beim Überprüfen von Todesfällen: " << e.what() << std::endl;
+ }
+
+ std::this_thread::sleep_for(std::chrono::hours(1));
+ }
+}
+
+bool CharacterCreationWorker::calculateDeathProbability(int age) {
+ if (age < 60) {
+ return false;
+ }
+
+ double baseProbability = 0.01;
+ double increasePerYear = 0.01;
+ double deathProbability = baseProbability + (increasePerYear * (age - 60));
+
+ std::uniform_real_distribution deathDist(0.0, 1.0);
+ return deathDist(gen) < deathProbability;
+}
+
+void CharacterCreationWorker::handleCharacterDeath(int characterId) {
+ try {
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+
+ // 1) Director löschen und User benachrichtigen
+ db.prepare("delete_director", QUERY_DELETE_DIRECTOR);
+ auto dirResult = db.execute("delete_director", { std::to_string(characterId) });
+ if (!dirResult.empty()) {
+ int userId = std::stoi(dirResult[0].at("user_id"));
+ notifyUser(userId, "director_death");
+ }
+
+ // 2) Relationships löschen und betroffene User benachrichtigen
+ db.prepare("delete_relationship", QUERY_DELETE_RELATIONSHIP);
+ auto relResult = db.execute("delete_relationship", { std::to_string(characterId) });
+ for (auto &row : relResult) {
+ int relatedUserId = std::stoi(row.at("related_user_id"));
+ notifyUser(relatedUserId, "relationship_death");
+ }
+
+ // 3) Child-Relations löschen und Eltern benachrichtigen
+ db.prepare("delete_child_relation", QUERY_DELETE_CHILD_RELATION);
+ auto childResult = db.execute("delete_child_relation", { std::to_string(characterId) });
+ for (auto &row : childResult) {
+ int fatherUserId = std::stoi(row.at("father_user_id"));
+ int motherUserId = std::stoi(row.at("mother_user_id"));
+ notifyUser(fatherUserId, "child_death");
+ notifyUser(motherUserId, "child_death");
+ }
+
+ // 4) Charakter als verstorben markieren
+ markCharacterAsDeceased(characterId);
+
+ } catch (const std::exception &e) {
+ std::cerr << "[CharacterCreationWorker] Fehler beim Bearbeiten des Todes: "
<< e.what() << std::endl;
}
}
+void CharacterCreationWorker::notifyUser(int userId, const std::string &eventType) {
+ try {
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+
+ db.prepare("insert_notification", QUERY_INSERT_NOTIFICATION);
+ db.execute("insert_notification", { std::to_string(userId) });
+
+ nlohmann::json message = {
+ {"event", eventType},
+ {"user_id", userId}
+ };
+ broker.publish(message.dump());
+ } catch (const std::exception &e) {
+ std::cerr << "[CharacterCreationWorker] Fehler beim Senden der Benachrichtigung: "
+ << e.what() << std::endl;
+ }
+}
+
+void CharacterCreationWorker::markCharacterAsDeceased(int characterId) {
+ try {
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+
+ db.prepare("mark_character_deceased", QUERY_MARK_CHARACTER_DECEASED);
+ db.execute("mark_character_deceased", {std::to_string(characterId)});
+ } catch (const std::exception &e) {
+ std::cerr << "[CharacterCreationWorker] Fehler beim Markieren des Charakters als verstorben: " << e.what() << std::endl;
+ }
+}
+
std::vector CharacterCreationWorker::getTownRegionIds() {
try {
ConnectionGuard connGuard(pool);
auto &db = connGuard.get();
-
auto rows = db.query(QUERY_GET_TOWN_REGION_IDS);
-
std::vector ids;
- ids.reserve(rows.size());
for (const auto &row : rows) {
ids.push_back(std::stoi(row.at("id")));
}
return ids;
} catch (const std::exception &e) {
- std::cerr << "[CharacterCreationWorker] Fehler in getTownRegionIds: "
- << e.what() << std::endl;
+ std::cerr << "[CharacterCreationWorker] Fehler in getTownRegionIds: " << e.what() << std::endl;
+ return {};
}
- return {};
}
void CharacterCreationWorker::loadNames() {
try {
ConnectionGuard connGuard(pool);
auto &db = connGuard.get();
-
auto firstNameRows = db.query(QUERY_LOAD_FIRST_NAMES);
for (const auto &row : firstNameRows) {
first_name_cache[row.at("gender")].insert(std::stoi(row.at("id")));
}
-
auto lastNameRows = db.query(QUERY_LOAD_LAST_NAMES);
for (const auto &row : lastNameRows) {
last_name_cache.insert(std::stoi(row.at("id")));
}
} catch (const std::exception &e) {
- std::cerr << "[CharacterCreationWorker] Fehler in loadNames: "
- << e.what() << std::endl;
+ std::cerr << "[CharacterCreationWorker] Fehler in loadNames: " << e.what() << std::endl;
}
}
diff --git a/src/character_creation_worker.h b/src/character_creation_worker.h
index 18ead74..a629ca7 100644
--- a/src/character_creation_worker.h
+++ b/src/character_creation_worker.h
@@ -6,11 +6,13 @@
#include
#include
#include
+#include
+#include
class CharacterCreationWorker : public Worker {
public:
CharacterCreationWorker(ConnectionPool &pool, MessageBroker &broker);
- ~CharacterCreationWorker() override;
+ ~CharacterCreationWorker() override;
protected:
void run() override;
@@ -20,6 +22,8 @@ private:
std::uniform_int_distribution dist;
std::unordered_map> first_name_cache;
std::unordered_set last_name_cache;
+ std::atomic deathCheckRunning{true};
+ std::thread deathThread;
bool isTodayCharacterCreated();
void createCharactersForToday();
@@ -28,6 +32,11 @@ private:
std::vector getTownRegionIds();
void loadNames();
int getRandomFromSet(const std::unordered_set &name_set);
+ void monitorCharacterDeaths();
+ void handleCharacterDeath(int characterId);
+ void notifyUser(int userId, const std::string &eventType);
+ void markCharacterAsDeceased(int characterId);
+ bool calculateDeathProbability(int age);
static constexpr const char *QUERY_IS_PREVIOUS_DAY_CHARACTER_CREATED = R"(
SELECT created_at
@@ -57,9 +66,97 @@ private:
static constexpr const char *QUERY_INSERT_CHARACTER = R"(
INSERT INTO falukant_data."character"(
- user_id, region_id, first_name, last_name,
+ user_id, region_id, first_name, last_name,
birthdate, gender, created_at, updated_at, title_of_nobility
)
VALUES (NULL, $1, $2, $3, NOW(), $4, NOW(), NOW(), $5);
)";
+
+ static constexpr const char *QUERY_GET_ELIGIBLE_NPC_FOR_DEATH = R"(
+ WITH aged AS (
+ SELECT
+ c.id,
+ (current_date - c.birthdate::date) AS age,
+ c.user_id
+ FROM
+ falukant_data."character" c
+ WHERE
+ c.user_id IS NULL
+ AND (current_date - c.birthdate::date) > 60
+ ),
+ always_sel AS (
+ -- Immer mitnehmen: alle über 85 Tage
+ SELECT *
+ FROM aged
+ WHERE age > 85
+ ),
+ random_sel AS (
+ -- Zufallsstichprobe: alle zwischen 61 und 85 Tagen, hier beispielhaft auf 10 limitiert
+ SELECT *
+ FROM aged
+ WHERE age <= 85
+ ORDER BY random()
+ LIMIT 10 -- <-- hier die gewünschte Anzahl anpassen
+ )
+ -- Zusammenführen der beiden Mengen
+ SELECT *
+ FROM always_sel
+ UNION ALL
+ SELECT *
+ FROM random_sel;
+ )";
+
+ static constexpr const char *QUERY_DELETE_DIRECTOR = R"(
+ DELETE FROM falukant_data.director
+ WHERE director_character_id = $1
+ RETURNING employer_user_id;
+ )";
+
+ static constexpr const char *QUERY_DELETE_RELATIONSHIP = R"(
+ WITH deleted AS (
+ DELETE FROM falukant_data.relationship
+ WHERE character1_id = $1
+ OR character2_id = $1
+ RETURNING
+ CASE
+ WHEN character1_id = $1 THEN character2_id
+ ELSE character1_id
+ END AS related_character_id,
+ relationship_type_id
+ )
+ SELECT
+ c.user_id AS related_user_id
+ FROM deleted d
+ JOIN falukant_data."character" c
+ ON c.id = d.related_character_id;
+ )";
+
+ static constexpr const char *QUERY_DELETE_CHILD_RELATION = R"(
+ WITH deleted AS (
+ DELETE FROM falukant_data.child_relation
+ WHERE child_character_id = $1
+ RETURNING
+ father_character_id,
+ mother_character_id
+ )
+ SELECT
+ cf.user_id AS father_user_id,
+ cm.user_id AS mother_user_id
+ FROM deleted d
+ JOIN falukant_data."character" cf
+ ON cf.id = d.father_character_id
+ JOIN falukant_data."character" cm
+ ON cm.id = d.mother_character_id;
+ )";
+
+ static constexpr const char *QUERY_INSERT_NOTIFICATION = R"(
+ INSERT INTO falukant_log.notification (user_id, tr, shown, created_at, updated_at)
+ VALUES ($1, 'director_death', false, NOW(), NOW());
+ )";
+
+
+ static constexpr const char *QUERY_MARK_CHARACTER_DECEASED = R"(
+ DELETE FROM falukant_data."character"
+ WHERE id = $1;
+ )";
};
diff --git a/src/database.cpp b/src/database.cpp
index c3dd39b..e3e7f9d 100644
--- a/src/database.cpp
+++ b/src/database.cpp
@@ -1,4 +1,9 @@
#include "database.h"
+#include
+#include
+#include
+#include
+#include
Database::Database(const std::string &conninfo)
{
@@ -9,7 +14,9 @@ Database::Database(const std::string &conninfo)
}
} catch (const std::exception &e) {
std::cerr << "[Database] Fehler beim Verbinden: " << e.what() << std::endl;
- throw;
+
+
+ throw;
}
}
@@ -48,32 +55,16 @@ void Database::prepare(const std::string &stmtName, const std::string &sql)
}
}
-#include
-#include
-#include
-#include
-#include
-#include
-
-std::vector>
-Database::execute(const std::string &stmtName, const std::vector ¶ms)
-{
+Database::FieldList Database::execute(const std::string &stmtName, const std::vector ¶ms) {
std::vector> rows;
-
try {
pqxx::work txn(*connection_);
pqxx::prepare::invocation inv = txn.prepared(stmtName);
-
- // Parameter einzeln hinzufügen
for (auto &p : params) {
inv(p);
}
-
- // Ausführung
pqxx::result r = inv.exec();
txn.commit();
-
- // Ergebnis verarbeiten
for (const auto &row : r) {
std::unordered_map mapRow;
for (pqxx::row::size_type i = 0; i < row.size(); ++i) {
@@ -91,6 +82,12 @@ Database::execute(const std::string &stmtName, const std::vector &p
return rows;
}
+void Database::remove(const std::string &stmtName) {
+ pqxx::work txn(*connection_);
+ txn.conn().unprepare(stmtName);
+ txn.commit();
+}
+
bool Database::isValid() const {
try {
if (!connection_ || !connection_->is_open()) {
diff --git a/src/database.h b/src/database.h
index 30fd07c..27cff2c 100644
--- a/src/database.h
+++ b/src/database.h
@@ -2,24 +2,25 @@
#include
#include
#include
-#include
#include
-#include
#include
#include
#include
-class Database
-{
+class Database {
public:
Database(const std::string &conninfo);
+ typedef std::unordered_map FieldMap;
+ typedef std::vector FieldList;
+
std::vector> query(const std::string &sql);
void prepare(const std::string &stmtName, const std::string &sql);
- std::vector> execute(
+ FieldList execute(
const std::string &stmtName,
const std::vector ¶ms = {}
);
+ void remove(const std::string &stmtName);
bool isOpen() const { return connection_ && connection_->is_open(); }
bool isValid() const;
diff --git a/src/director_worker.cpp b/src/director_worker.cpp
new file mode 100644
index 0000000..e3276aa
--- /dev/null
+++ b/src/director_worker.cpp
@@ -0,0 +1,192 @@
+#include "director_worker.h"
+#include
+
+DirectorWorker::DirectorWorker(ConnectionPool &pool, MessageBroker &broker)
+ : Worker(pool, broker, "DirectorWorker") {
+}
+
+DirectorWorker::~DirectorWorker() {
+}
+
+void DirectorWorker::run() {
+ auto lastExecutionTime = std::chrono::steady_clock::now();
+
+ while (runningWorker) {
+ signalActivity();
+ auto now = std::chrono::steady_clock::now();
+ auto elapsed = std::chrono::duration_cast(now - lastExecutionTime).count();
+ if (elapsed >= 60) {
+ try {
+ performTask();
+ paySalary();
+ calculateSatisfaction();
+ lastExecutionTime = now;
+ } catch (const std::exception &e) {
+ std::cerr << "[DirectorWorker] Fehler beim Ausführen der Aufgabe: " << e.what() << std::endl;
+ }
+ }
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ }
+}
+
+void DirectorWorker::performTask() {
+ try {
+ setCurrentStep("Get Database Connection");
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+ setCurrentStep("Get director actions");
+ db.prepare("QUERY_GET_DIRECTORS", QUERY_GET_DIRECTORS);
+ const auto directors = db.execute("QUERY_GET_DIRECTORS");
+ for (const auto &director: directors) {
+ if (director.at("may_produce") == "t") {
+ startProductions(director);
+ }
+ if (director.at("may_start_transport") == "t") {
+ startTransports(director);
+ }
+ if (director.at("may_sell") == "t") {
+ startSellings(director);
+ }
+ }
+ } catch (const std::exception &e) {
+ std::cerr << "[DirectorWorker] Fehler bei der Datenbankoperation: " << e.what() << std::endl;
+ }
+}
+
+void DirectorWorker::startProductions(std::unordered_map director) {
+ auto parseIntOrZero = [&](const std::string &s){
+ if (s.empty() || s == "null") return 0;
+ try {
+ return std::stoi(s);
+ } catch(...) {
+ return 0;
+ }
+ };
+
+ setCurrentStep("Get Database Connection - Production");
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+
+ setCurrentStep("Get to produce");
+ db.prepare("get_to_produce", QUERY_GET_BEST_PRODUCTION);
+ const auto productions = db.execute("get_to_produce", { director.at("id") });
+ if (productions.empty()) return;
+ const auto &production = productions.at(0);
+
+ int runningProductions = parseIntOrZero(production.at("running_productions"));
+ if (runningProductions >= 2) {
+ return;
+ }
+
+ setCurrentStep("Add production to DB");
+ int availableStock = parseIntOrZero(production.at("stock_size"));
+ int usedStock = parseIntOrZero(production.at("used_in_stock"));
+ int freeCapacity = availableStock - usedStock - runningProductions;
+
+ int certificate = parseIntOrZero(production.at("certificate"));
+ int onePieceCost = certificate * 6;
+ int money = parseIntOrZero(production.at("money"));
+ int maxMoneyProduction = onePieceCost > 0 ? money / onePieceCost : 0;
+
+ int toProduce = std::min(std::min(freeCapacity, maxMoneyProduction), 300);
+ if (toProduce < 1) {
+ return;
+ }
+
+ int falukantUserId = parseIntOrZero(production.at("falukant_user_id"));
+ int productionCost = toProduce * onePieceCost;
+
+ nlohmann::json msg1 = { { "event", "falukantUpdateStatus" } };
+ setCurrentStep("Update money");
+ changeFalukantUserMoney(falukantUserId, -productionCost, "director starts production", msg1);
+
+ setCurrentStep("Insert production");
+ db.prepare("insert_production", QUERY_INSERT_PRODUCTION);
+
+ int remaining = toProduce;
+ while (remaining > 0) {
+ int batch = std::min(100, remaining);
+ db.execute("insert_production", {
+ production.at("branch_id"),
+ production.at("product_id"),
+ std::to_string(batch)
+ });
+ remaining -= batch;
+ }
+
+ nlohmann::json msg2 = {
+ { "event", "production_started" },
+ { "branch_id", production.at("branch_id") }
+ };
+ sendMessageToFalukantUsers(falukantUserId, msg2);
+}
+
+void DirectorWorker::startTransports(std::unordered_map) {
+}
+
+void DirectorWorker::startSellings(std::unordered_map director) {
+ setCurrentStep("Get Database Connection - Production");
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+ setCurrentStep("Get to sell");
+ db.prepare("get_to_sell", QUERY_GET_INVENTORY);
+ const auto inventory = db.execute("get_to_sell", { director.at("id") });
+ for (const auto &item: inventory) {
+ const auto inventoryId = std::stoi(item.at("id"));
+ const auto productId = std::stoi(item.at("product_id"));
+ const auto quantity = std::stoi(item.at("quantity"));
+ const auto quality = std::stoi(item.at("quality"));
+ const auto maxSellPrice = std::stod(item.at("sell_cost"));
+ auto falukantUserId = std::stoi(item.at("user_id"));
+ const auto regionId = std::stoi(item.at("region_id"));
+ if (quantity > 0) {
+ const auto minPrice = maxSellPrice * 0.6;
+ const auto pieceSellPrice = minPrice + (double)(maxSellPrice - minPrice) * (quality / 100.0);
+ const auto sellPrice = pieceSellPrice * quantity;
+ const nlohmann::json changeMessage = {
+ { "productId", productId },
+ { "event", "falukantUpdateStatus" }
+ };
+ changeFalukantUserMoney(falukantUserId, sellPrice, "sell products", changeMessage);
+ db.prepare("QUERY_ADD_SELL_LOG", QUERY_ADD_SELL_LOG);
+ db.execute("QUERY_ADD_SELL_LOG", { std::to_string(regionId), std::to_string(productId), std::to_string(quantity),
+ std::to_string(falukantUserId) });
+ }
+ db.prepare("remove_inventory", QUERY_REMOVE_INVENTORY);
+ db.execute("remove_inventory", { std::to_string(inventoryId) });
+ nlohmann::json message = {
+ { "event", "selled_items" },
+ { "branch_id", item.at("branch_id") },
+ };
+ sendMessageToFalukantUsers(falukantUserId, message);
+ }
+}
+
+void DirectorWorker::paySalary() {
+ setCurrentStep("salary - load to pay");
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+ db.prepare("QUERY_GET_SALARY_TO_PAY", QUERY_GET_SALARY_TO_PAY);
+ const auto &salariesToPay = db.execute("QUERY_GET_SALARY_TO_PAY");
+ nlohmann::json message = {
+ { "event", "falukantUpdateStatus" }
+ };
+ for (auto const &item: salariesToPay) {
+ changeFalukantUserMoney(std::stoi(item.at("employer_user_id")), -std::stoi(item.at("income")), "director payed out", message);
+ db.prepare("QUERY_SET_SALARY_PAYED", QUERY_SET_SALARY_PAYED);
+ db.execute("QUERY_SET_SALARY_PAYED", { std::to_string(std::stoi(item.at("id"))) });
+ }
+}
+
+void DirectorWorker::calculateSatisfaction() {
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+ db.prepare("QUERY_UPDATE_SATISFACTION", QUERY_UPDATE_SATISFACTION);
+ const auto &changedDirectors = db.execute("QUERY_UPDATE_SATISFACTION");
+ nlohmann::json message = {
+ { "event", "directorchanged" }
+ };
+ for (auto const &director: changedDirectors) {
+ sendMessageToFalukantUsers(std::stoi(director.at("employer_user_id")), message);
+ }
+}
diff --git a/src/director_worker.h b/src/director_worker.h
new file mode 100644
index 0000000..6453875
--- /dev/null
+++ b/src/director_worker.h
@@ -0,0 +1,155 @@
+#ifndef DIRECTOR_WORKER_H
+#define DIRECTOR_WORKER_H
+
+#include "worker.h"
+
+class DirectorWorker : public Worker {
+public:
+ explicit DirectorWorker(ConnectionPool &pool, MessageBroker &broker);
+ ~DirectorWorker() override;
+
+protected:
+ void run() override;
+
+private:
+ void performTask();
+ void startProductions(std::unordered_map director);
+ void startTransports(std::unordered_map);
+ void startSellings(std::unordered_map);
+ void paySalary();
+ void calculateSatisfaction();
+
+ static constexpr const char *QUERY_GET_DIRECTORS = R"(
+ select d.may_produce, d.may_sell, d.may_start_transport, b.id branch_id, fu.id falukantUserId, d.id
+ from falukant_data.director d
+ join falukant_data.falukant_user fu
+ on fu.id = d.employer_user_id
+ join falukant_data."character" c
+ on c.id = d.director_character_id
+ join falukant_data.branch b
+ on b.region_id = c.region_id
+ and b.falukant_user_id = fu.id
+ where current_time between '08:00:00' and '17:00:00'
+ )";
+
+ static constexpr const char *QUERY_GET_BEST_PRODUCTION = R"(
+ select fdu."id" falukant_user_id, fdu."money", fdu."certificate", ftp."id" product_id, ftp.label_tr,(select sum("quantity")
+ from "falukant_data"."stock" fds
+ where fds."branch_id" = fdb."id") stock_size, coalesce((select sum(coalesce(fdi."quantity", 0))
+ from "falukant_data"."stock" fds
+ join "falukant_data"."inventory" fdi
+ on fdi."stock_id" = fds."id"
+ where fds."branch_id" = fdb."id"), 0) used_in_stock,
+ (ftp."sell_cost" * (fdtpw."worth_percent" + (fdk_character."knowledge" * 2 + fdk_director."knowledge") / 3) / 100 - 6 * ftp.category) / (300.0 * ftp. production_time) worth,
+ fdb."id" branch_id,
+ (select count("id") from "falukant_data"."production" where "branch_id" = fdb."id") running_productions,
+ coalesce((select sum(coalesce(fdp.quantity, 0)) quantity from
+ falukant_data.production fdp where fdp.branch_id = fdb.id), 0) running_productions
+ from "falukant_data"."director" fdd
+ join "falukant_data".character fdc
+ on fdc.id = fdd.director_character_id
+ join "falukant_data"."falukant_user" fdu
+ on fdd."employer_user_id" = fdu."id"
+ join "falukant_data"."character" user_character
+ on user_character."user_id" = fdu."id"
+ join "falukant_data"."branch" fdb
+ on fdb."falukant_user_id" = fdu."id"
+ and fdb."region_id" = fdc."region_id"
+ join "falukant_data"."town_product_worth" fdtpw
+ on fdtpw."region_id" = fdb."region_id"
+ join "falukant_data"."knowledge" fdk_character
+ on
+ fdk_character."product_id" = fdtpw."product_id"
+ and fdk_character."character_id" = user_character."id"
+ and fdk_character."product_id" = fdtpw."product_id"
+ join "falukant_data"."knowledge" fdk_director
+ on
+ fdk_director."product_id" = fdtpw."product_id"
+ and fdk_director."character_id" = fdd."director_character_id"
+ and fdk_director."product_id" = fdtpw."product_id"
+ join "falukant_type"."product" ftp
+ on
+ ftp."id" = fdtpw."product_id"
+ and ftp.category <= fdu.certificate
+ where fdd."id" = $1
+ order by worth desc
+ limit 1;
+ )";
+
+ static constexpr const char *QUERY_INSERT_PRODUCTION = R"(
+ insert into "falukant_data"."production" ("branch_id", "product_id", "quantity")
+ values ($1, $2, $3)
+ )";
+
+ static constexpr const char *QUERY_GET_INVENTORY = R"(
+ select i.id, i.product_id, i.quantity, i.quality, p.sell_cost, fu.id user_id, b.region_id, b.id branch_id
+ from falukant_data.inventory i
+ join falukant_data.stock s
+ on s.id = i.stock_id
+ join falukant_data.branch b
+ on b.id = s.branch_id
+ join falukant_data.falukant_user fu
+ on fu.id = b.falukant_user_id
+ join falukant_data.director d
+ on d.employer_user_id = fu.id
+ join falukant_type.product p
+ on p.id = i.product_id
+ where d.id = $1
+ )";
+
+ static constexpr const char *QUERY_REMOVE_INVENTORY = R"(
+ delete from falukant_data.inventory
+ where id = $1
+ )";
+
+ static constexpr const char *QUERY_ADD_SELL_LOG = R"(
+ INSERT INTO falukant_log.sell ("region_id", "product_id", "quantity", "seller_id")
+ values ($1, $2, $3, $4)
+ ON CONFLICT ("region_id", "product_id", "seller_id")
+ DO UPDATE
+ SET "quantity" = falukant_log.sell."quantity" + EXCLUDED.quantity
+ )";
+
+ static constexpr const char *QUERY_GET_SALARY_TO_PAY = R"(
+ select d.id, d.employer_user_id, d.income
+ from falukant_data.director d
+ where date(d.last_salary_payout) < date(now())
+ )";
+
+ static constexpr const char *QUERY_SET_SALARY_PAYED = R"(
+ update falukant_data.director
+ set last_salary_payout = NOW()
+ where id = $1
+ )";
+
+ static constexpr const char *QUERY_UPDATE_SATISFACTION = R"(
+ WITH new_sats AS (
+ SELECT
+ d.id,
+ ROUND(
+ d.income::numeric
+ /
+ (
+ c.title_of_nobility
+ * POWER(1.231, AVG(k.knowledge) / 1.5)
+ )
+ * 100
+ ) AS new_satisfaction
+ FROM falukant_data.director d
+ JOIN falukant_data.knowledge k
+ ON d.director_character_id = k.character_id
+ JOIN falukant_data.character c
+ ON c.id = d.director_character_id
+ GROUP BY d.id, c.title_of_nobility, d.income
+ )
+ UPDATE falukant_data.director dir
+ SET satisfaction = ns.new_satisfaction
+ FROM new_sats ns
+ WHERE dir.id = ns.id
+ -- Nur updaten, wenn sich der Wert tatsächlich ändert:
+ AND dir.satisfaction IS DISTINCT FROM ns.new_satisfaction
+ RETURNING dir.employer_user_id;
+ )";
+};
+
+#endif // DIRECTOR_WORKER_H
diff --git a/src/houseworker.cpp b/src/houseworker.cpp
new file mode 100644
index 0000000..029e9bf
--- /dev/null
+++ b/src/houseworker.cpp
@@ -0,0 +1,65 @@
+#include "houseworker.h"
+
+#include
+
+HouseWorker::HouseWorker(ConnectionPool &pool, MessageBroker &broker):
+ Worker(pool, broker, "HouseWorker") {
+
+}
+
+HouseWorker::~HouseWorker() {
+
+}
+
+void HouseWorker::run() {
+ auto lastExecutionTime = std::chrono::steady_clock::now();
+ auto lastHouseStateChange = std::chrono::system_clock::now();
+ while (runningWorker) {
+ signalActivity();
+ auto now = std::chrono::steady_clock::now();
+ auto elapsed = std::chrono::duration_cast(now - lastExecutionTime).count();
+ if (elapsed >= 3600) {
+ performTask();
+ }
+ auto nowSystem = std::chrono::system_clock::now();
+ auto lastDay = floor(lastHouseStateChange);
+ auto today = floor(nowSystem);
+ if (lastDay < today) {
+ performHouseStateChange();
+ lastHouseStateChange = nowSystem;
+ }
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ }
+}
+
+void HouseWorker::performTask() {
+ try {
+ setCurrentStep("Get Database Connection");
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+ setCurrentStep("Get new houses data");
+ db.prepare("QUERY_GET_NEW_HOUSE_DATA", QUERY_GET_NEW_HOUSE_DATA);
+ const auto newHouses = db.execute("QUERY_GET_NEW_HOUSE_DATA");
+ for (const auto &newHouse: newHouses) {
+ db.prepare("QUERY_ADD_NEW_BUYABLE_HOUSE", QUERY_ADD_NEW_BUYABLE_HOUSE);
+ db.execute("QUERY_ADD_NEW_BUYABLE_HOUSE", { newHouse.at("house_id") });
+ }
+ } catch (const std::exception &e) {
+ std::cerr << "[HouseWorker] Fehler bei der Datenbankoperation: " << e.what() << std::endl;
+ }
+}
+
+void HouseWorker::performHouseStateChange() {
+ try {
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+ db.remove("QUERY_UPDATE_BUYABLE_HOUSE_STATE");
+ db.remove("QUERY_UPDATE_USER_HOUSE_STATE");
+ db.prepare("QUERY_UPDATE_BUYABLE_HOUSE_STATE", QUERY_UPDATE_BUYABLE_HOUSE_STATE);
+ db.prepare("QUERY_UPDATE_USER_HOUSE_STATE", QUERY_UPDATE_USER_HOUSE_STATE);
+ db.execute("QUERY_UPDATE_BUYABLE_HOUSE_STATE");
+ db.execute("QUERY_UPDATE_USER_HOUSE_STATE");
+ } catch(const std::exception &e) {
+ std::cerr << "[HouseWorker] Fehler bei der Datenbankoperation: " << e.what() << std::endl;
+ }
+}
diff --git a/src/houseworker.h b/src/houseworker.h
new file mode 100644
index 0000000..5bdf5c2
--- /dev/null
+++ b/src/houseworker.h
@@ -0,0 +1,54 @@
+#ifndef HOUSEWORKER_H
+#define HOUSEWORKER_H
+
+#include "worker.h"
+
+class HouseWorker : public Worker {
+public:
+ HouseWorker(ConnectionPool &pool, MessageBroker &broker);
+ ~HouseWorker() override;
+
+protected:
+ void run() override;
+
+private:
+ void performTask();
+ void performHouseStateChange();
+
+ static constexpr const char *QUERY_GET_NEW_HOUSE_DATA = R"(
+ SELECT
+ h.id AS house_id
+ FROM
+ falukant_type.house AS h
+ WHERE
+ random() < 0.0001
+ and "label_tr" != 'under_bridge';
+ )";
+
+ static constexpr const char *QUERY_ADD_NEW_BUYABLE_HOUSE = R"(
+ insert into falukant_data.buyable_house (house_type_id) values ($1);
+ )";
+
+ static constexpr const char *QUERY_UPDATE_BUYABLE_HOUSE_STATE = R"(
+ update falukant_data.buyable_house
+ set roof_condition = round(roof_condition - random() * (3 + 0 * id)),
+ floor_condition = round(floor_condition - random() * (3 + 0 * id)),
+ wall_condition = round(wall_condition - random() * (3 + 0 * id)),
+ window_condition = round(wall_condition - random() * (3 + 0 * id))
+ )";
+
+ static constexpr const char *QUERY_UPDATE_USER_HOUSE_STATE = R"(
+ update falukant_data.user_house
+ set roof_condition = round(roof_condition - random() * (3 + 0 * id)),
+ floor_condition = round(floor_condition - random() * (3 + 0 * id)),
+ wall_condition = round(wall_condition - random() * (3 + 0 * id)),
+ window_condition = round(window_condition - random() * (3 + 0 * id))
+ where house_type_id not in (
+ select id
+ from falukant_type.house h
+ where h.label_tr = 'under_bridge'
+ )
+ )";
+};
+
+#endif // HOUSEWORKER_H
diff --git a/src/main.cpp b/src/main.cpp
index dfa9c39..85c29a2 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,13 +1,21 @@
#include "character_creation_worker.h"
#include "produce_worker.h"
+#include "stockagemanager.h"
+#include "director_worker.h"
+#include "valuerecalculationworker.h"
#include "connection_pool.h"
#include "websocket_server.h"
#include "message_broker.h"
+#include "usercharacterworker.h"
+#include "houseworker.h"
+#include "politics_worker.h"
#include "config.h"
#include
#include
#include
#include
+#include
+#include
std::atomic keepRunning(true);
@@ -25,39 +33,44 @@ int main() {
try {
Config config("/etc/yourpart/daemon.conf");
ConnectionPool pool(
- config.get("DB_HOST"),
- config.get("DB_PORT"),
+ config.get("DB_HOST"),
+ config.get("DB_PORT"),
config.get("DB_NAME"),
- config.get("DB_USER"),
- config.get("DB_PASSWORD"),
+ config.get("DB_USER"),
+ config.get("DB_PASSWORD"),
10
);
-
int websocketPort = std::stoi(config.get("WEBSOCKET_PORT"));
MessageBroker broker;
WebSocketServer websocketServer(websocketPort, pool, broker);
- CharacterCreationWorker creationWorker(pool, broker);
- ProduceWorker produceWorker(pool, broker);
+ std::vector> workers;
+ workers.push_back(std::make_unique(pool, broker));
+ workers.push_back(std::make_unique(pool, broker));
+ workers.push_back(std::make_unique(pool, broker));
+ workers.push_back(std::make_unique(pool, broker));
+ workers.push_back(std::make_unique(pool, broker));
+ workers.push_back(std::make_unique(pool, broker));
+ workers.push_back(std::make_unique(pool, broker));
+ workers.push_back(std::make_unique(pool, broker));
+ websocketServer.setWorkers(workers);
broker.start();
websocketServer.run();
- creationWorker.startWorkerThread();
- produceWorker.startWorkerThread();
- creationWorker.enableWatchdog();
- produceWorker.enableWatchdog();
-
+ for (auto &worker : workers) {
+ worker->startWorkerThread();
+ worker->enableWatchdog();
+ }
while (keepRunning) {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
-
- creationWorker.stopWorkerThread();
- produceWorker.stopWorkerThread();
+ for (auto &worker : workers) {
+ worker->stopWorkerThread();
+ }
websocketServer.stop();
broker.stop();
} catch (const std::exception &e) {
std::cerr << "Fehler: " << e.what() << std::endl;
return 1;
}
-
return 0;
}
diff --git a/src/politics_worker.cpp b/src/politics_worker.cpp
new file mode 100644
index 0000000..d8188fb
--- /dev/null
+++ b/src/politics_worker.cpp
@@ -0,0 +1,224 @@
+// File: politics_worker.cpp
+
+#include "politics_worker.h"
+#include
+#include
+
+PoliticsWorker::PoliticsWorker(ConnectionPool &pool, MessageBroker &broker)
+ : Worker(pool, broker, "PoliticsWorker")
+{
+}
+
+PoliticsWorker::~PoliticsWorker()
+{
+}
+
+void PoliticsWorker::run() {
+ auto lastExecutionDate = std::chrono::system_clock::time_point{};
+ while (runningWorker) {
+ signalActivity();
+ auto now = std::chrono::system_clock::now();
+ auto todayFloor = std::chrono::floor(now);
+ auto targetTime = todayFloor + std::chrono::hours(3); // 03:00 Uhr
+ if (now >= targetTime && lastExecutionDate < todayFloor) {
+ signalActivity();
+ performDailyPoliticsTask();
+ lastExecutionDate = todayFloor;
+ }
+ for (int i = 0; i < 5 && runningWorker.load(); ++i) {
+ signalActivity();
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ }
+ }
+}
+
+void PoliticsWorker::performDailyPoliticsTask() {
+ try {
+ // … (Schritte für Notifications und evaluatePoliticalPositions) …
+
+ // 3) Elections anlegen und **je 2 × posts_to_fill** Kandidaten hinzufügen
+ {
+ setCurrentStep("Schedule Elections and Insert Candidates");
+
+ // 3a) Neue Elections erzeugen (liefert jetzt auch posts_to_fill)
+ auto elections = scheduleElections();
+
+ if (!elections.empty()) {
+ for (auto const & tpl : elections) {
+ int electionId = std::get<0>(tpl);
+ int regionId = std::get<1>(tpl);
+ int postsToFill = std::get<2>(tpl);
+
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+
+ db.prepare("INSERT_CANDIDATES", QUERY_INSERT_CANDIDATES);
+ // $1 = electionId, $2 = regionId, $3 = postsToFill
+ db.execute("INSERT_CANDIDATES", {
+ std::to_string(electionId),
+ std::to_string(regionId),
+ std::to_string(postsToFill)
+ });
+ }
+ }
+ }
+
+ // … nach scheduleElections() & Kandidaten …
+ {
+ setCurrentStep("Process Elections After 3 Days");
+ auto newOffices = processElections();
+ for (auto const &tup : newOffices) {
+ notifyOfficeFilled({tup});
+ }
+ }
+
+ } catch (std::exception const & e) {
+ std::cerr << "[PoliticsWorker] Fehler bei performDailyPoliticsTask: " << e.what() << "\n";
+ }
+}
+
+void PoliticsWorker::evaluatePoliticalPositions(
+ std::unordered_map& requiredPerRegion,
+ std::unordered_map& occupiedPerRegion
+) {
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+
+ signalActivity();
+ db.prepare("COUNT_OFFICES_PER_REGION", QUERY_COUNT_OFFICES_PER_REGION);
+ signalActivity();
+ const auto result = db.execute("COUNT_OFFICES_PER_REGION");
+ signalActivity();
+
+ for (const auto &row : result) {
+ int regionId = std::stoi(row.at("region_id"));
+ int reqCount = std::stoi(row.at("required_count"));
+ int occCount = std::stoi(row.at("occupied_count"));
+
+ requiredPerRegion[regionId] = reqCount;
+ occupiedPerRegion[regionId] = occCount;
+
+ signalActivity();
+ }
+}
+
+// politics_worker.cpp (Auszug)
+
+std::vector> PoliticsWorker::scheduleElections() {
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+ signalActivity();
+ db.prepare("SELECT_NEEDED_ELECTIONS", QUERY_SELECT_NEEDED_ELECTIONS);
+ signalActivity();
+ auto result = db.execute("SELECT_NEEDED_ELECTIONS");
+ signalActivity();
+ std::vector> created;
+ created.reserve(result.size());
+ for (auto const & row : result) {
+ int electionId = std::stoi(row.at("election_id"));
+ int regionId = std::stoi(row.at("region_id"));
+ int postsToFill = std::stoi(row.at("posts_to_fill"));
+ created.emplace_back(electionId, regionId, postsToFill);
+ signalActivity();
+ }
+ return created;
+}
+
+std::vector> PoliticsWorker::processExpiredOfficesAndFill() {
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+
+ signalActivity();
+ db.prepare("PROCESS_EXPIRED_AND_FILL", QUERY_PROCESS_EXPIRED_AND_FILL);
+ signalActivity();
+ const auto result = db.execute("PROCESS_EXPIRED_AND_FILL");
+ signalActivity();
+
+ std::vector> created;
+ for (const auto &row : result) {
+ int officeId = std::stoi(row.at("office_id"));
+ int officeTypeId = std::stoi(row.at("office_type_id"));
+ int characterId = std::stoi(row.at("character_id"));
+ int regionId = std::stoi(row.at("region_id"));
+ created.emplace_back(officeId, officeTypeId, characterId, regionId);
+ signalActivity();
+ }
+ return created;
+}
+
+std::vector PoliticsWorker::getUserIdsInCitiesOfRegions(const std::vector& regionIds) {
+ if (regionIds.empty()) {
+ return {};
+ }
+
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+
+ std::vector userIds;
+ for (int rid : regionIds) {
+ signalActivity();
+ db.prepare("GET_USERS_IN_CITIES", QUERY_USERS_IN_CITIES_OF_REGIONS);
+ signalActivity();
+ const auto rows = db.execute("GET_USERS_IN_CITIES", { std::to_string(rid) });
+ signalActivity();
+
+ for (const auto &row : rows) {
+ int uid = std::stoi(row.at("user_id"));
+ userIds.push_back(uid);
+ signalActivity();
+ }
+ }
+ return userIds;
+}
+
+void PoliticsWorker::notifyOfficeExpirations() {
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+
+ signalActivity();
+ db.prepare("NOTIFY_OFFICE_EXPIRATION", QUERY_NOTIFY_OFFICE_EXPIRATION);
+ signalActivity();
+ db.execute("NOTIFY_OFFICE_EXPIRATION");
+ signalActivity();
+}
+
+void PoliticsWorker::notifyElectionCreated(const std::vector>& elections) {
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+
+ db.prepare("NOTIFY_ELECTION_CREATED", QUERY_NOTIFY_ELECTION_CREATED);
+ for (const auto &pr : elections) {
+ signalActivity();
+ db.execute("NOTIFY_ELECTION_CREATED", { std::to_string(pr.first) });
+ signalActivity();
+ }
+}
+
+void PoliticsWorker::notifyOfficeFilled(const std::vector>& newOffices) {
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+
+ db.prepare("NOTIFY_OFFICE_FILLED", QUERY_NOTIFY_OFFICE_FILLED);
+ for (const auto &tup : newOffices) {
+ int characterId = std::get<2>(tup);
+ signalActivity();
+ db.execute("NOTIFY_OFFICE_FILLED", { std::to_string(characterId) });
+ signalActivity();
+ }
+}
+
+std::vector> PoliticsWorker::processElections() {
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+ db.prepare("PROCESS_ELECTIONS", QUERY_PROCESS_ELECTIONS);
+ auto result = db.execute("PROCESS_ELECTIONS", {});
+ std::vector> created;
+ for (auto const &row : result) {
+ int officeId = std::stoi(row.at("office_id"));
+ int officeTypeId = std::stoi(row.at("office_type_id"));
+ int characterId = std::stoi(row.at("character_id"));
+ int regionId = std::stoi(row.at("region_id"));
+ created.emplace_back(officeId, officeTypeId, characterId, regionId);
+ }
+ return created;
+}
diff --git a/src/politics_worker.h b/src/politics_worker.h
new file mode 100644
index 0000000..31985e6
--- /dev/null
+++ b/src/politics_worker.h
@@ -0,0 +1,460 @@
+// File: politics_worker.h
+
+#ifndef POLITICS_WORKER_H
+#define POLITICS_WORKER_H
+
+#include "worker.h"
+#include
+#include
+#include
+
+class PoliticsWorker : public Worker {
+public:
+ PoliticsWorker(ConnectionPool &pool, MessageBroker &broker);
+ ~PoliticsWorker() override;
+
+protected:
+ void run() override;
+
+private:
+ void performDailyPoliticsTask();
+
+ void evaluatePoliticalPositions(
+ std::unordered_map& requiredPerRegion,
+ std::unordered_map& occupiedPerRegion
+ );
+
+ std::vector> scheduleElections();
+ std::vector> processExpiredOfficesAndFill();
+ std::vector getUserIdsInCitiesOfRegions(const std::vector& regionIds);
+
+ void notifyOfficeExpirations();
+ void notifyElectionCreated(const std::vector>& elections);
+ void notifyOfficeFilled(const std::vector>& newOffices);
+ std::vector > processElections();
+
+ // ------------------------------------------------------------
+ // QUERY: Zähle pro Region, wie viele Sitze vorgesehen vs. besetzt sind
+ // ------------------------------------------------------------
+ static constexpr const char* QUERY_COUNT_OFFICES_PER_REGION = R"(
+ WITH
+ seats_per_region AS (
+ SELECT
+ pot.id AS office_type_id,
+ rt.id AS region_id,
+ pot.seats_per_region AS seats_total
+ FROM
+ falukant_type.political_office_type AS pot
+ JOIN
+ falukant_type.region AS rt
+ ON pot.region_type = rt.label_tr
+ ),
+ occupied AS (
+ SELECT
+ po.office_type_id,
+ po.region_id,
+ COUNT(*) AS occupied_count
+ FROM
+ falukant_data.political_office AS po
+ GROUP BY
+ po.office_type_id, po.region_id
+ ),
+ combined AS (
+ SELECT
+ spr.region_id,
+ spr.seats_total AS required_count,
+ COALESCE(o.occupied_count, 0) AS occupied_count
+ FROM
+ seats_per_region AS spr
+ LEFT JOIN
+ occupied AS o
+ ON spr.office_type_id = o.office_type_id
+ AND spr.region_id = o.region_id
+ )
+ SELECT
+ region_id,
+ SUM(required_count) AS required_count,
+ SUM(occupied_count) AS occupied_count
+ FROM combined
+ GROUP BY region_id;
+ )";
+
+ // ------------------------------------------------------------
+ // STEP 1: Erzeuge nur diejenigen Wahlen, bei denen noch keine Election
+ // für denselben Termin (NOW()+2 Tage) existiert.
+ // ------------------------------------------------------------
+ static constexpr const char* QUERY_SELECT_NEEDED_ELECTIONS = R"(
+ WITH
+ -- 1) Definiere das heutige Datum einmal als Referenz
+ target_date AS (
+ SELECT NOW()::date AS election_date
+ ),
+
+ -- 2) Lösche nur diejenigen Ämter, deren Ablaufdatum heute erreicht ist,
+ -- und merke deren (office_type_id, region_id)
+ expired_today AS (
+ DELETE FROM falukant_data.political_office AS po
+ USING falukant_type.political_office_type AS pot
+ WHERE po.office_type_id = pot.id
+ AND (po.created_at + (pot.term_length * INTERVAL '1 day'))::date = (SELECT election_date FROM target_date)
+ RETURNING
+ pot.id AS office_type_id,
+ po.region_id AS region_id
+ ),
+
+ -- 3) Gruppiere nach Typ+Region und zähle, wie viele Sitze heute frei geworden sind
+ gaps_per_region AS (
+ SELECT
+ office_type_id,
+ region_id,
+ COUNT(*) AS gaps
+ FROM expired_today
+ GROUP BY office_type_id, region_id
+ ),
+
+ -- 4) Filtere nur diejenigen Typ+Region‐Kombinationen, für die noch **keine** Election
+ -- mit genau demselben Datum angelegt wurde
+ to_schedule AS (
+ SELECT
+ g.office_type_id,
+ g.region_id,
+ g.gaps,
+ td.election_date
+ FROM
+ gaps_per_region AS g
+ CROSS JOIN
+ target_date AS td
+ WHERE NOT EXISTS (
+ SELECT 1
+ FROM falukant_data.election AS e
+ WHERE e.office_type_id = g.office_type_id
+ AND e.region_id = g.region_id
+ AND e."date"::date = td.election_date
+ )
+ ),
+
+ -- 5) Lege für jede so gefilterte Kombination genau eine Election an
+ new_elections AS (
+ INSERT INTO falukant_data.election
+ (office_type_id, "date", posts_to_fill, created_at, updated_at, region_id)
+ SELECT
+ ts.office_type_id,
+ ts.election_date AS "date",
+ ts.gaps AS posts_to_fill,
+ NOW() AS created_at,
+ NOW() AS updated_at,
+ ts.region_id
+ FROM
+ to_schedule AS ts
+ RETURNING
+ id AS election_id,
+ region_id,
+ posts_to_fill
+ )
+
+ -- 6) Gib alle neu angelegten Wahlen zurück
+ SELECT
+ ne.election_id,
+ ne.region_id,
+ ne.posts_to_fill
+ FROM
+ new_elections AS ne
+ ORDER BY
+ ne.region_id,
+ ne.election_id;
+ )";
+
+ // -----------------------------------------------------------------------
+ // 2) Fügt für eine gegebene Election genau LIMIT = ($3 * 2) Kandidaten ein:
+ // $1 = election_id, $2 = region_id, $3 = Anzahl der Sitze (posts_to_fill)
+ // -----------------------------------------------------------------------
+ static constexpr const char* QUERY_INSERT_CANDIDATES = R"(
+ INSERT INTO falukant_data.candidate
+ (election_id, character_id, created_at, updated_at)
+ SELECT
+ $1 AS election_id,
+ sub.id AS character_id,
+ NOW() AS created_at,
+ NOW() AS updated_at
+ FROM (
+ WITH RECURSIVE region_tree AS (
+ SELECT r.id
+ FROM falukant_data.region AS r
+ WHERE r.id = $2
+ UNION ALL
+ SELECT r2.id
+ FROM falukant_data.region AS r2
+ JOIN region_tree AS rt
+ ON r2.parent_id = rt.id
+ )
+ SELECT
+ ch.id
+ FROM
+ falukant_data."character" AS ch
+ JOIN
+ region_tree AS rt2
+ ON ch.region_id = rt2.id
+ WHERE
+ ch.user_id IS NULL
+ AND ch.birthdate <= NOW() - INTERVAL '21 days'
+ AND ch.title_of_nobility IN (
+ SELECT id
+ FROM falukant_type.title
+ WHERE label_tr != 'noncivil'
+ )
+ ORDER BY RANDOM()
+ LIMIT ($3 * 2)
+ ) AS sub(id);
+ )";
+
+ // ------------------------------------------------------------
+ // STEP 2: Füge eine einzelne neue Election ein und liefere die neue election_id
+ // $1 = office_type_id
+ // $2 = gaps (posts_to_fill)
+ // ------------------------------------------------------------
+ static constexpr const char* QUERY_INSERT_ELECTION = R"(
+ INSERT INTO falukant_data.election
+ (office_type_id, "date", posts_to_fill, created_at, updated_at)
+ VALUES
+ (
+ $1,
+ NOW() + INTERVAL '2 days',
+ $2,
+ NOW(),
+ NOW()
+ )
+ RETURNING id;
+ )";
+
+ // ------------------------------------------------------------
+ // QUERY: Process Expired Offices & Refill (Winner + Random)
+ // ------------------------------------------------------------
+ static constexpr const char* QUERY_PROCESS_EXPIRED_AND_FILL = R"(
+ WITH
+ expired_offices AS (
+ DELETE FROM falukant_data.political_office AS po
+ USING falukant_type.political_office_type AS pot
+ WHERE po.office_type_id = pot.id
+ AND (po.created_at + (pot.term_length * INTERVAL '1 day')) <= NOW()
+ RETURNING
+ pot.id AS office_type_id,
+ po.region_id AS region_id
+ ),
+ distinct_types AS (
+ SELECT DISTINCT
+ office_type_id,
+ region_id
+ FROM expired_offices
+ ),
+ votes_per_candidate AS (
+ SELECT
+ dt.office_type_id,
+ dt.region_id,
+ c.character_id,
+ COUNT(v.id) AS vote_count
+ FROM distinct_types AS dt
+ JOIN falukant_data.election AS e
+ ON e.office_type_id = dt.office_type_id
+ JOIN falukant_data.vote AS v
+ ON v.election_id = e.id
+ JOIN falukant_data.candidate AS c
+ ON c.election_id = e.id
+ AND c.id = v.candidate_id
+ WHERE e."date" >= (NOW() - INTERVAL '30 days')
+ GROUP BY
+ dt.office_type_id,
+ dt.region_id,
+ c.character_id
+ ),
+ ranked_winners AS (
+ SELECT
+ vpc.office_type_id,
+ vpc.region_id,
+ vpc.character_id,
+ ROW_NUMBER() OVER (
+ PARTITION BY vpc.office_type_id, vpc.region_id
+ ORDER BY vpc.vote_count DESC
+ ) AS rn
+ FROM votes_per_candidate AS vpc
+ ),
+ selected_winners AS (
+ SELECT
+ rw.office_type_id,
+ rw.region_id,
+ rw.character_id
+ FROM ranked_winners AS rw
+ JOIN falukant_type.political_office_type AS pot
+ ON pot.id = rw.office_type_id
+ WHERE rw.rn <= pot.seats_per_region
+ ),
+ insert_winners AS (
+ INSERT INTO falukant_data.political_office
+ (office_type_id, character_id, created_at, updated_at, region_id)
+ SELECT
+ sw.office_type_id,
+ sw.character_id,
+ NOW() AS created_at,
+ NOW() AS updated_at,
+ sw.region_id
+ FROM selected_winners AS sw
+ RETURNING
+ id AS new_office_id,
+ office_type_id,
+ character_id,
+ region_id
+ ),
+ count_inserted AS (
+ SELECT
+ office_type_id,
+ region_id,
+ COUNT(*) AS inserted_count
+ FROM insert_winners
+ GROUP BY office_type_id, region_id
+ ),
+ needed_to_fill AS (
+ SELECT
+ dt.office_type_id,
+ dt.region_id,
+ (pot.seats_per_region - COALESCE(ci.inserted_count, 0)) AS gaps
+ FROM distinct_types AS dt
+ JOIN falukant_type.political_office_type AS pot
+ ON pot.id = dt.office_type_id
+ LEFT JOIN count_inserted AS ci
+ ON ci.office_type_id = dt.office_type_id
+ AND ci.region_id = dt.region_id
+ WHERE (pot.seats_per_region - COALESCE(ci.inserted_count, 0)) > 0
+ ),
+ random_candidates AS (
+ SELECT
+ rtf.office_type_id,
+ rtf.region_id,
+ ch.id AS character_id,
+ ROW_NUMBER() OVER (
+ PARTITION BY rtf.office_type_id, rtf.region_id
+ ORDER BY RANDOM()
+ ) AS rn
+ FROM needed_to_fill AS rtf
+ JOIN falukant_data."character" AS ch
+ ON ch.region_id = rtf.region_id
+ AND ch.user_id IS NULL
+ AND ch.birthdate <= NOW() - INTERVAL '21 days'
+ AND ch.title_of_nobility IN (
+ SELECT id FROM falukant_type.title WHERE label_tr != 'noncivil'
+ )
+ AND NOT EXISTS (
+ SELECT 1
+ FROM falukant_data.political_office AS po2
+ JOIN falukant_type.political_office_type AS pot2
+ ON pot2.id = po2.office_type_id
+ WHERE po2.character_id = ch.id
+ AND (po2.created_at + (pot2.term_length * INTERVAL '1 day')) > NOW() + INTERVAL '2 days'
+ )
+ ),
+ insert_random AS (
+ INSERT INTO falukant_data.political_office
+ (office_type_id, character_id, created_at, updated_at, region_id)
+ SELECT
+ rc.office_type_id,
+ rc.character_id,
+ NOW() AS created_at,
+ NOW() AS updated_at,
+ rc.region_id
+ FROM random_candidates AS rc
+ JOIN needed_to_fill AS rtf
+ ON rtf.office_type_id = rc.office_type_id
+ AND rtf.region_id = rc.region_id
+ WHERE rc.rn <= rtf.gaps
+ RETURNING
+ id AS new_office_id,
+ office_type_id,
+ character_id,
+ region_id
+ )
+ SELECT
+ new_office_id AS office_id,
+ office_type_id,
+ character_id,
+ region_id
+ FROM insert_winners
+ UNION ALL
+ SELECT
+ new_office_id AS office_id,
+ office_type_id,
+ character_id,
+ region_id
+ FROM insert_random;
+ )";
+
+ // ------------------------------------------------------------
+ // QUERY: Hole User-IDs in allen Cities untergeordneter Regionen:
+ // ------------------------------------------------------------
+ static constexpr const char* QUERY_USERS_IN_CITIES_OF_REGIONS = R"(
+ WITH RECURSIVE region_tree AS (
+ SELECT id
+ FROM falukant_data.region
+ WHERE id = $1
+ UNION ALL
+ SELECT r2.id
+ FROM falukant_data.region AS r2
+ JOIN region_tree AS rt
+ ON r2.parent_id = rt.id
+ )
+ SELECT DISTINCT
+ ch.user_id
+ FROM
+ falukant_data."character" AS ch
+ JOIN
+ region_tree AS rt2
+ ON ch.region_id = rt2.id
+ WHERE
+ ch.user_id IS NOT NULL;
+ )";
+
+ // ------------------------------------------------------------
+ // QUERY: Benachrichtige User, deren Amt in 2 Tagen abläuft
+ // ------------------------------------------------------------
+ static constexpr const char* QUERY_NOTIFY_OFFICE_EXPIRATION = R"(
+ INSERT INTO falukant_log.notification
+ (user_id, tr, created_at, updated_at)
+ SELECT
+ po.character_id,
+ 'notify_office_expiring',
+ NOW(), NOW()
+ FROM
+ falukant_data.political_office AS po
+ JOIN
+ falukant_type.political_office_type AS pot
+ ON po.office_type_id = pot.id
+ WHERE
+ (po.created_at + (pot.term_length * INTERVAL '1 day'))
+ BETWEEN (NOW() + INTERVAL '2 days')
+ AND (NOW() + INTERVAL '2 days' + INTERVAL '1 second');
+ )";
+
+ // ------------------------------------------------------------
+ // QUERY: Benachrichtige User, wenn Election angelegt wurde
+ // ------------------------------------------------------------
+ static constexpr const char* QUERY_NOTIFY_ELECTION_CREATED = R"(
+ INSERT INTO falukant_log.notification
+ (user_id, tr, created_at, updated_at)
+ VALUES
+ ($1, 'notify_election_created', NOW(), NOW());
+ )";
+
+ // ------------------------------------------------------------
+ // QUERY: Benachrichtige User, wenn Amt neu besetzt wurde
+ // ------------------------------------------------------------
+ static constexpr const char* QUERY_NOTIFY_OFFICE_FILLED = R"(
+ INSERT INTO falukant_log.notification
+ (user_id, tr, created_at, updated_at)
+ VALUES
+ ($1, 'notify_office_filled', NOW(), NOW());
+ )";
+
+ static constexpr const char* QUERY_PROCESS_ELECTIONS = R"(
+ SELECT office_id, office_type_id, character_id, region_id
+ FROM falukant_data.process_elections();
+ )";
+};
+
+#endif // POLITICS_WORKER_H
diff --git a/src/produce_worker.cpp b/src/produce_worker.cpp
index df749ea..bca873f 100644
--- a/src/produce_worker.cpp
+++ b/src/produce_worker.cpp
@@ -1,14 +1,12 @@
#include "produce_worker.h"
-#include "connection_guard.h" // Include for ConnectionGuard
+#include "connection_guard.h"
#include
#include
#include
#include
ProduceWorker::ProduceWorker(ConnectionPool &pool, MessageBroker &broker)
- : Worker(pool, broker, "ProduceWorker")
-{
-}
+ : Worker(pool, broker, "ProduceWorker") {}
ProduceWorker::~ProduceWorker() {
}
@@ -43,10 +41,8 @@ void ProduceWorker::processProductions() {
setCurrentStep("Get Database Connection");
ConnectionGuard connGuard(pool);
auto &db = connGuard.get();
-
setCurrentStep("Fetch Finished Productions");
auto finishedProductions = getFinishedProductions(db);
-
setCurrentStep("Process Finished Productions");
for (const auto &production : finishedProductions) {
if (production.find("branch_id") == production.end() ||
@@ -56,16 +52,20 @@ void ProduceWorker::processProductions() {
production.find("user_id") == production.end()) {
continue;
}
-
int branchId = std::stoi(production.at("branch_id"));
int productId = std::stoi(production.at("product_id"));
int quantity = std::stoi(production.at("quantity"));
int quality = std::stoi(production.at("quality"));
int userId = std::stoi(production.at("user_id"));
-
- if (addToInventory(db, branchId, productId, quantity, quality, userId)) {
- }
+ int regionId = std::stoi(production.at("region_id"));
+ addToInventory(db, branchId, productId, quantity, quality, userId);
deleteProduction(db, production.at("production_id"));
+ addProductionToLog(regionId, userId, productId, quantity);
+ const nlohmann::json message = {
+ {"event", "production_ready"},
+ {"branch_id", std::to_string(branchId) }
+ };
+ sendMessageToFalukantUsers(userId, message);
}
} catch (const std::exception &e) {
std::cerr << "[ProduceWorker] Fehler in processProductions: " << e.what() << std::endl;
@@ -83,13 +83,12 @@ std::vector> ProduceWorker::getFini
return {};
}
-bool ProduceWorker::addToInventory(Database &db,
- int branchId,
- int productId,
- int quantity,
- int quality,
- int userId)
-{
+bool ProduceWorker::addToInventory(Database &db,
+ int branchId,
+ int productId,
+ int quantity,
+ int quality,
+ int userId) {
try {
db.prepare("get_stocks", QUERY_GET_AVAILABLE_STOCKS);
auto stocks = db.execute("get_stocks", {std::to_string(branchId)});
@@ -99,22 +98,29 @@ bool ProduceWorker::addToInventory(Database &db,
int totalCapacity = std::stoi(stock.at("total_capacity"));
int filledCapacity = std::stoi(stock.at("filled"));
int freeCapacity = totalCapacity - filledCapacity;
-
if (freeCapacity <= 0) {
continue;
}
-
int toStore = std::min(remainingQuantity, freeCapacity);
if (!storeInStock(db, stockId, productId, toStore, quality)) {
return false;
}
remainingQuantity -= toStore;
- sendProductionReadyEvent(userId, productId, quantity, quality, branchId);
if (remainingQuantity <= 0) {
break;
}
}
- return (remainingQuantity == 0);
+ if (remainingQuantity == 0) {
+ sendProductionReadyEvent(userId, productId, quantity, quality, branchId);
+ return true;
+ }
+ db.prepare("QUERY_ADD_OVERPRODUCTION_NOTIFICATION", QUERY_ADD_OVERPRODUCTION_NOTIFICATION);
+ nlohmann::json notification = {
+ {"tr", "production.overproduction"},
+ {"value", remainingQuantity}
+ };
+ db.execute("QUERY_ADD_OVERPRODUCTION_NOTIFICATION", {std::to_string(userId), notification.dump()});
+ return true;
} catch (const std::exception &e) {
std::cerr << "[ProduceWorker] Fehler in addToInventory: " << e.what() << std::endl;
}
@@ -125,8 +131,7 @@ bool ProduceWorker::storeInStock(Database &db,
int stockId,
int productId,
int quantity,
- int quality)
-{
+ int quality) {
try {
db.prepare("insert_inventory", QUERY_INSERT_INVENTORY);
db.execute("insert_inventory", {std::to_string(stockId),
@@ -170,3 +175,15 @@ void ProduceWorker::sendProductionReadyEvent(int userId,
<< e.what() << std::endl;
}
}
+
+void ProduceWorker::addProductionToLog(int regionId, int userId, int productId, int quantity) {
+ try {
+ ConnectionGuard connGuard(pool);
+ auto &db = connGuard.get();
+ db.prepare("QUERY_INSERT_UPDATE_PRODUCTION_LOG", QUERY_INSERT_UPDATE_PRODUCTION_LOG);
+ db.execute("QUERY_INSERT_UPDATE_PRODUCTION_LOG", { std::to_string(regionId), std::to_string(productId),
+ std::to_string(productId), std::to_string(userId) });
+ } catch (const std::exception &e) {
+
+ }
+}
diff --git a/src/produce_worker.h b/src/produce_worker.h
index d3737bc..1355f92 100644
--- a/src/produce_worker.h
+++ b/src/produce_worker.h
@@ -2,38 +2,35 @@
#include "worker.h"
#include
-#include