Files
yourpart3/src/usercharacterworker.cpp
Torsten Schulz (local) 6a1260687b Implement comprehensive character deletion process in UserCharacterWorker
- Add queries and logic to delete associated data when a character dies, including directors, relationships, child relations, knowledge, debtors prism, political offices, and election candidates.
- Enhance error handling to log issues during the deletion process.
2026-01-14 14:38:42 +01:00

428 lines
16 KiB
C++

#include "usercharacterworker.h"
#include "connection_guard.h"
#include <iostream>
#include <chrono>
#include <thread>
#include <vector>
#include <cmath>
#include "utils.h"
UserCharacterWorker::UserCharacterWorker(ConnectionPool &pool, MessageBroker &broker)
: Worker(pool, broker, "UserCharacterWorker"),
gen(rd()), dist(0.0, 1.0) {}
UserCharacterWorker::~UserCharacterWorker() {}
void UserCharacterWorker::run() {
using namespace std::chrono;
auto lastExecutionTime = steady_clock::now();
int lastPregnancyDay = -1;
while (runningWorker) {
signalActivity();
// 1h-Block
auto nowSteady = steady_clock::now();
auto elapsed = duration_cast<seconds>(nowSteady - lastExecutionTime).count();
if (elapsed >= 3600) {
try {
processCharacterEvents();
updateCharactersMood();
handleCredits();
} catch (const std::exception &e) {
std::cerr << "[UserCharacterWorker] Fehler in processCharacterEvents: " << e.what() << std::endl;
}
lastExecutionTime = nowSteady;
}
// Schwangerschaftsverarbeitung: initial oder täglich um 06:00 einmal pro Tag
auto nowSys = system_clock::now();
std::time_t t = system_clock::to_time_t(nowSys);
std::tm local_tm;
localtime_r(&t, &local_tm);
if (lastPregnancyDay == -1 || (local_tm.tm_hour == 6 && local_tm.tm_yday != lastPregnancyDay)) {
try {
processPregnancies();
} catch (const std::exception &e) {
std::cerr << "[UserCharacterWorker] Fehler in processPregnancies: " << e.what() << std::endl;
}
lastPregnancyDay = local_tm.tm_yday;
}
std::this_thread::sleep_for(seconds(1));
recalculateKnowledge();
}
}
void UserCharacterWorker::processCharacterEvents() {
setCurrentStep("Get character data");
ConnectionGuard connGuard(pool);
auto &db = connGuard.get();
db.prepare(QUERY_GET_USERS_TO_UPDATE, QUERY_GET_USERS_TO_UPDATE);
auto rows = db.execute(QUERY_GET_USERS_TO_UPDATE);
std::vector<Character> characters;
for (const auto &row : rows) {
characters.push_back({ std::stoi(row.at("id")), std::stoi(row.at("age")), std::stoi(row.at("health")) });
}
for (auto &character : characters) {
updateCharacterHealth(character);
}
}
void UserCharacterWorker::updateCharacterHealth(Character& character) {
int healthChange = calculateHealthChange(character.age);
if (healthChange != 0) {
character.health = std::max(0, character.health + healthChange);
if (character.health == 0) {
handleCharacterDeath(character.id);
return;
}
ConnectionGuard connGuard(pool);
auto &db = connGuard.get();
db.prepare("QUERY_UPDATE_CHARACTERS_HEALTH", QUERY_UPDATE_CHARACTERS_HEALTH);
db.execute("QUERY_UPDATE_CHARACTERS_HEALTH",
{ std::to_string(character.health), std::to_string(character.id) });
}
}
void UserCharacterWorker::updateCharactersMood() {
ConnectionGuard connGuard(pool);
auto &db = connGuard.get();
db.prepare("QUERY_UPDATE_MOOD", QUERY_UPDATE_MOOD);
db.execute("QUERY_UPDATE_MOOD");
}
int UserCharacterWorker::calculateHealthChange(int age) {
if (age < 30) {
return 0;
}
if (age >= 45) {
double probability = std::min(1.0, 0.1 + (age - 45) * 0.02);
if (dist(gen) < probability) {
return -std::uniform_int_distribution<int>(1, 10)(gen);
}
return 0;
}
double probability = (age - 30) / 30.0;
return (dist(gen) < probability) ? -1 : 0;
}
void UserCharacterWorker::handleCharacterDeath(int characterId) {
setHeir(characterId);
nlohmann::json deathEvent = {
{"event", "CharacterDeath"},
{"character_id", characterId}
};
broker.publish(deathEvent.dump());
ConnectionGuard connGuard(pool);
auto &db = connGuard.get();
try {
// 1) Director löschen (falls Character ein Director ist)
db.prepare("delete_director", QUERY_DELETE_DIRECTOR);
db.execute("delete_director", { std::to_string(characterId) });
// 2) Relationships löschen (Ehepartner, etc.)
db.prepare("delete_relationship", QUERY_DELETE_RELATIONSHIP);
db.execute("delete_relationship", { std::to_string(characterId) });
// 3) Child-Relations löschen (als Kind, Vater oder Mutter)
db.prepare("delete_child_relation", QUERY_DELETE_CHILD_RELATION);
db.execute("delete_child_relation", { std::to_string(characterId) });
// 4) Knowledge löschen
db.prepare("delete_knowledge", QUERY_DELETE_KNOWLEDGE);
db.execute("delete_knowledge", { std::to_string(characterId) });
// 5) Debtors_prism löschen
db.prepare("delete_debtors_prism", QUERY_DELETE_DEBTORS_PRISM);
db.execute("delete_debtors_prism", { std::to_string(characterId) });
// 6) Political Office löschen
db.prepare("delete_political_office", QUERY_DELETE_POLITICAL_OFFICE);
db.execute("delete_political_office", { std::to_string(characterId) });
// 7) Election Candidate löschen
db.prepare("delete_election_candidate", QUERY_DELETE_ELECTION_CANDIDATE);
db.execute("delete_election_candidate", { std::to_string(characterId) });
// 8) Character löschen
db.prepare("delete_character", "DELETE FROM falukant_data.character WHERE id = $1");
db.execute("delete_character", { std::to_string(characterId) });
} catch (const std::exception &e) {
std::cerr << "[UserCharacterWorker] Fehler beim Löschen der Character-Verknüpfungen: "
<< e.what() << std::endl;
}
}
void UserCharacterWorker::setHeir(int characterId) {
auto falukantUserId = getFalukantUserId(characterId);
auto heirId = getHeirFromChildren(characterId);
auto newMoney = calculateNewMoney(falukantUserId, true);
if (heirId < 1) {
getRandomHeir(characterId);
newMoney = calculateNewMoney(falukantUserId, false);
}
setNewCharacter(falukantUserId, heirId);
setNewMoney(falukantUserId, newMoney);
}
int UserCharacterWorker::getFalukantUserId(int characterId) {
ConnectionGuard guard(pool);
auto &db = guard.get();
db.prepare("QUERY_GET_FALUKANT_USER_ID", QUERY_GET_FALUKANT_USER_ID);
const auto rows = db.execute("QUERY_GET_FALUKANT_USER_ID", { std::to_string(characterId) });
if (!rows.empty() && !rows.front().at("user_id").empty()) {
return std::stoi(rows.front().at("user_id"));
}
return -1;
}
int UserCharacterWorker::getHeirFromChildren(int deceasedCharacterId) {
ConnectionGuard guard(pool);
auto &db = guard.get();
db.prepare("QUERY_GET_HEIR", QUERY_GET_HEIR);
const auto rows = db.execute("QUERY_GET_HEIR", { std::to_string(deceasedCharacterId) });
if (!rows.empty()) {
return std::stoi(rows.front().at("child_character_id"));
}
return -1;
}
int UserCharacterWorker::getRandomHeir(int deceasedCharacterId) {
ConnectionGuard guard(pool);
auto &db = guard.get();
db.prepare("QUERY_RANDOM_HEIR", QUERY_RANDOM_HEIR);
const auto rows = db.execute("QUERY_RANDOM_HEIR", { std::to_string(deceasedCharacterId) });
if (!rows.empty()) {
return std::stoi(rows.front().at("child_character_id"));
}
return -1;
}
void UserCharacterWorker::setNewCharacter(int falukantUserId, int heirCharacterId) {
if (heirCharacterId < 1) return;
ConnectionGuard guard(pool);
auto &db = guard.get();
db.prepare("QUERY_SET_CHARACTER_USER", QUERY_SET_CHARACTER_USER);
db.execute("QUERY_SET_CHARACTER_USER", {
std::to_string(falukantUserId),
std::to_string(heirCharacterId)
});
}
void UserCharacterWorker::setNewMoney(int falukantUserId, double newAmount) {
ConnectionGuard guard(pool);
auto &db = guard.get();
db.prepare("QUERY_UPDATE_USER_MONEY", QUERY_UPDATE_USER_MONEY);
db.execute("QUERY_UPDATE_USER_MONEY", {
std::to_string(newAmount),
std::to_string(falukantUserId)
});
}
void UserCharacterWorker::recalculateKnowledge() {
setCurrentStep("Get character data");
ConnectionGuard connGuard(pool);
auto &db = connGuard.get();
db.prepare("QUERY_UPDATE_GET_ITEMS_TO_UPDATE", QUERY_UPDATE_GET_ITEMS_TO_UPDATE);
auto rows = db.execute("QUERY_UPDATE_GET_ITEMS_TO_UPDATE");
for (const auto &updateItem: rows) {
if (std::stoi(updateItem.at("quantity")) >= 10) {
db.prepare("QUERY_UPDATE_GET_CHARACTER_IDS", QUERY_UPDATE_GET_CHARACTER_IDS);
auto charactersData = db.execute("QUERY_UPDATE_GET_CHARACTER_IDS", { updateItem.at("producer_id") });
for (const auto &characterRow: charactersData) {
db.prepare("QUERY_UPDATE_KNOWLEDGE", QUERY_UPDATE_KNOWLEDGE);
if (characterRow.at("director_id") == "") {
db.execute("QUERY_UPDATE_KNOWLEDGE", { characterRow.at("character_id"), updateItem.at("product_id"), "2" });
} else {
db.execute("QUERY_UPDATE_KNOWLEDGE", { characterRow.at("character_id"), updateItem.at("product_id"), "1" });
db.execute("QUERY_UPDATE_KNOWLEDGE", { characterRow.at("director_id"), updateItem.at("product_id"), "1" });
}
}
}
db.prepare("QUERY_DELETE_LOG_ENTRY", QUERY_DELETE_LOG_ENTRY);
db.execute("QUERY_DELETE_LOG_ENTRY", { updateItem.at("id") });
const nlohmann::json message = {
{"event", "knowledge_update"},
};
sendMessageToFalukantUsers(std::stoi(updateItem.at("producer_id")), message);
}
}
void UserCharacterWorker::processPregnancies() {
ConnectionGuard connGuard(pool);
auto &db = connGuard.get();
db.prepare("QUERY_AUTOBATISM", QUERY_AUTOBATISM);
db.execute("QUERY_AUTOBATISM");
db.prepare("get_candidates", QUERY_GET_PREGNANCY_CANDIDATES);
auto rows = db.execute("get_candidates");
const nlohmann::json message = {
{"event", "children_update"},
};
for (const auto &row : rows) {
int fatherCid = Utils::optionalStoiOrDefault(row, "father_cid", -1);
int motherCid = Utils::optionalStoiOrDefault(row, "mother_cid", -1);
if (fatherCid < 0 || motherCid < 0) {
continue; // ungültige Daten überspringen
}
int titleOfNobility = Utils::optionalStoiOrDefault(row, "title_of_nobility", 0);
int lastName = Utils::optionalStoiOrDefault(row, "last_name", 0);
int regionId = Utils::optionalStoiOrDefault(row, "region_id", 0);
auto fatherUidOpt = Utils::optionalUid(row.at("father_uid"));
auto motherUidOpt = Utils::optionalUid(row.at("mother_uid"));
// Geschlecht zufällig
std::string gender = (dist(gen) < 0.5) ? "male" : "female";
db.prepare("insert_child", QUERY_INSERT_CHILD);
auto resChild = db.execute("insert_child", {
std::to_string(regionId), // $1
gender, // $2
std::to_string(lastName), // $3
std::to_string(titleOfNobility) // $4
});
if (resChild.empty()) continue;
int childCid = Utils::optionalStoiOrDefault(resChild.front(), "child_cid", -1);
if (childCid < 0) continue;
db.prepare("insert_relation", QUERY_INSERT_CHILD_RELATION);
db.execute("insert_relation", {
std::to_string(fatherCid),
std::to_string(motherCid),
std::to_string(childCid)
});
if (fatherUidOpt) {
sendMessageToFalukantUsers(*fatherUidOpt, message);
// Sende falukantUpdateStatus nach dem Erstellen des Kindes
nlohmann::json updateMessage = { { "event", "falukantUpdateStatus" } };
sendMessageToFalukantUsers(*fatherUidOpt, updateMessage);
}
if (motherUidOpt) {
sendMessageToFalukantUsers(*motherUidOpt, message);
// Sende falukantUpdateStatus nach dem Erstellen des Kindes
nlohmann::json updateMessage = { { "event", "falukantUpdateStatus" } };
sendMessageToFalukantUsers(*motherUidOpt, updateMessage);
}
}
}
void UserCharacterWorker::handleCredits() {
ConnectionGuard connGuard(pool);
auto &db = connGuard.get();
db.prepare("QUERY_GET_OPEN_CREDITS", QUERY_GET_OPEN_CREDITS);
const auto &credits = db.execute("QUERY_GET_OPEN_CREDITS");
const nlohmann::json message = {
{ "event", "falukantUpdateStatus" }
};
db.prepare("QUERY_UPDATE_CREDIT", QUERY_UPDATE_CREDIT);
db.prepare("QUERY_ADD_CHARACTER_TO_DEBTORS_PRISM", QUERY_ADD_CHARACTER_TO_DEBTORS_PRISM);
for (const auto &credit: credits) {
const auto userMoney = std::stod(credit.at("money"));
auto remainingAmount = std::stod(credit.at("remaining_amount"));
const auto amount = std::stod(credit.at("amount"));
const auto fee = std::stoi(credit.at("interest_rate"));
const auto falukantUserId = std::stoi(credit.at("user_id"));
const auto payRate = amount / 10 + amount * fee / 100;
remainingAmount -= payRate;
if (payRate <= userMoney - (payRate * 3)) {
changeFalukantUserMoney(falukantUserId, -payRate, "credit pay rate", message);
} else {
if (credit.at("prism_started_previously") == "t") {
changeFalukantUserMoney(falukantUserId, payRate, "debitor_prism", message);
} else {
db.execute("QUERY_ADD_CHARACTER_TO_DEBTORS_PRISM", { credit.at("character_id") });
}
}
db.execute("QUERY_UPDATE_CREDIT", { std::to_string(remainingAmount), std::to_string(falukantUserId) });
}
db.prepare("QUERY_CLEANUP_CREDITS", QUERY_CLEANUP_CREDITS);
db.execute("QUERY_CLEANUP_CREDITS");
}
double UserCharacterWorker::getCurrentMoney(int falukantUserId) {
ConnectionGuard g(pool); auto &db = g.get();
db.prepare("GET_CURRENT_MONEY", QUERY_GET_CURRENT_MONEY);
auto rows = db.execute("GET_CURRENT_MONEY", {std::to_string(falukantUserId)});
return rows.empty()? 0.0 : std::stod(rows.front().at("sum"));
}
double UserCharacterWorker::getHouseValue(int falukantUserId) {
ConnectionGuard g(pool); auto &db = g.get();
db.prepare("HOUSE_VALUE", QUERY_HOUSE_VALUE);
auto rows = db.execute("HOUSE_VALUE", {std::to_string(falukantUserId)});
return rows.empty()? 0.0 : std::stod(rows.front().at("sum"));
}
double UserCharacterWorker::getSettlementValue(int falukantUserId) {
ConnectionGuard g(pool); auto &db = g.get();
db.prepare("SETTLEMENT_VALUE", QUERY_SETTLEMENT_VALUE);
auto rows = db.execute("SETTLEMENT_VALUE", {std::to_string(falukantUserId)});
return rows.empty()? 0.0 : std::stod(rows.front().at("sum"));
}
double UserCharacterWorker::getInventoryValue(int falukantUserId) {
ConnectionGuard g(pool); auto &db = g.get();
db.prepare("INVENTORY_VALUE", QUERY_INVENTORY_VALUE);
auto rows = db.execute("INVENTORY_VALUE", {std::to_string(falukantUserId)});
return rows.empty()? 0.0 : std::stod(rows.front().at("sum"));
}
double UserCharacterWorker::getCreditDebt(int falukantUserId) {
ConnectionGuard guard(pool);
auto &db = guard.get();
db.prepare("CREDIT_DEBT", QUERY_CREDIT_DEBT);
auto rows = db.execute("CREDIT_DEBT", { std::to_string(falukantUserId) });
return rows.empty()
? 0.0
: std::stod(rows.front().at("sum"));
}
int UserCharacterWorker::getChildCount(int deceasedUserId) {
ConnectionGuard g(pool); auto &db = g.get();
db.prepare("COUNT_CHILDREN", QUERY_COUNT_CHILDREN);
auto rows = db.execute("COUNT_CHILDREN", {std::to_string(deceasedUserId)});
return rows.empty()? 0 : std::stoi(rows.front().at("cnt"));
}
double UserCharacterWorker::calculateNewMoney(int falukantUserId, bool hasHeir) {
if (!hasHeir) {
return 800.0;
}
double cash = getCurrentMoney(falukantUserId);
double houses = getHouseValue(falukantUserId);
double sets = getSettlementValue(falukantUserId);
double inv = getInventoryValue(falukantUserId);
double debt = getCreditDebt(falukantUserId);
double totalAssets = cash + houses + sets + inv - debt;
int childCount = getChildCount(falukantUserId);
bool single = (childCount <= 1);
double heirShare = single ? totalAssets : totalAssets * 0.8;
double net = heirShare - (houses + sets + inv + debt);
if (net <= 1000.0) {
return 1000.0;
}
return net;
}