#include "usercharacterworker.h" #include "connection_guard.h" #include #include #include #include #include #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(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 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(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; }