#include "underground_worker.h" #include #include #include #include #include using json = nlohmann::json; UndergroundWorker::~UndergroundWorker() = default; static std::mt19937& rng() { static thread_local std::mt19937 g{std::random_device{}()}; return g; } int UndergroundWorker::randomInt(int lo,int hi){ std::uniform_int_distribution d(lo,hi); return d(rng()); } long long UndergroundWorker::randomLL(long long lo,long long hi){ std::uniform_int_distribution d(lo,hi); return d(rng()); } std::vector UndergroundWorker::randomIndices(size_t n,size_t k){ std::vector idx(n); std::iota(idx.begin(),idx.end(),0); std::shuffle(idx.begin(),idx.end(),rng()); if(k UndergroundWorker::fetchPending(){ ConnectionGuard g(pool); auto& db=g.get(); db.prepare("UG_SELECT_PENDING",Q_SELECT_PENDING); return db.execute("UG_SELECT_PENDING"); } nlohmann::json UndergroundWorker::executeRow(const Row& r){ int performerId=std::stoi(r.at("performer_id")); int victimId=std::stoi(r.at("victim_id")); std::string type=r.at("underground_type"); std::string params=r.at("parameters"); return handleTask(type,performerId,victimId,params); } nlohmann::json UndergroundWorker::handleTask(const std::string& type,int performerId,int victimId,const std::string& paramsJson){ json p; try{ p=json::parse(paramsJson);} catch(...){ p=json::object(); } if(type=="spyin") return spyIn(performerId,victimId,p); if(type=="assassin") return assassin(performerId,victimId,p); if(type=="sabotage") return sabotage(performerId,victimId,p); if(type=="corrupt_politician") return corruptPolitician(performerId,victimId,p); if(type=="rob") return rob(performerId,victimId,p); return {{"status","unknown_type"},{"type",type}}; } nlohmann::json UndergroundWorker::spyIn(int performerId,int victimId,const json& p){ ConnectionGuard g(pool); auto& db=g.get(); db.prepare("UG_SELECT_BY_PERFORMER",Q_SELECT_BY_PERFORMER); const auto rows = db.execute("UG_SELECT_BY_PERFORMER",{ std::to_string(victimId) }); json activities = json::array(); for(const auto& r : rows){ json params = json::object(); try{ params = json::parse(r.at("parameters")); }catch(...){} json result = nullptr; auto it = r.find("result_text"); if(it != r.end()){ try{ result = json::parse(it->second); }catch(...){} } std::string status = "pending"; if(result.is_object()){ if(auto s = result.find("status"); s!=result.end() && s->is_string()) { status = s->get(); } else { status = "done"; } } activities.push_back({ {"id", std::stoi(r.at("id"))}, {"type", r.at("underground_type")}, {"performed_by", std::stoi(r.at("performer_id"))}, {"victim_id", std::stoi(r.at("victim_id"))}, {"created_at", r.at("created_at")}, {"parameters", params}, {"result", result}, {"status", status} }); } return { {"status","success"}, {"action","spyin"}, {"performer_id", performerId}, {"victim_id", victimId}, {"details", p}, {"victim_illegal_activity_count", activities.size()}, {"victim_illegal_activities", activities} }; } nlohmann::json UndergroundWorker::assassin(int performerId,int victimId,const json& p){ ConnectionGuard g(pool); auto& db=g.get(); db.prepare("UG_SELECT_CHAR_HEALTH",Q_SELECT_CHAR_HEALTH); db.prepare("UG_UPDATE_CHAR_HEALTH",Q_UPDATE_CHAR_HEALTH); const auto rows=db.execute("UG_SELECT_CHAR_HEALTH",{std::to_string(victimId)}); if(rows.empty()) return {{"status","error"},{"action","assassin"},{"performer_id",performerId},{"victim_id",victimId},{"message","victim_not_found"},{"details",p}}; int current=std::stoi(rows.front().at("health")); std::uniform_int_distribution dist(0,current); int new_health=dist(rng()); db.execute("UG_UPDATE_CHAR_HEALTH",{std::to_string(victimId),std::to_string(new_health)}); return {{"status","success"},{"action","assassin"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"previous_health",current},{"new_health",new_health},{"reduced_by",current-new_health}}; } nlohmann::json UndergroundWorker::sabotage(int performerId,int victimId,const json& p){ const auto target=p.value("target",std::string{}); if(target=="house") return sabotageHouse(performerId,victimId,p); if(target=="storage") return sabotageStorage(performerId,victimId,p); return {{"status","error"},{"action","sabotage"},{"message","unknown_target"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}}; } int UndergroundWorker::getUserIdForCharacter(int characterId){ ConnectionGuard g(pool); auto& db=g.get(); db.prepare("UG_SELECT_CHAR_USER",Q_SELECT_CHAR_USER); const auto r=db.execute("UG_SELECT_CHAR_USER",{std::to_string(characterId)}); if(r.empty()) return -1; return std::stoi(r.front().at("user_id")); } std::optional UndergroundWorker::getHouseByUser(int userId){ ConnectionGuard g(pool); auto& db=g.get(); db.prepare("UG_SELECT_HOUSE_BY_USER",Q_SELECT_HOUSE_BY_USER); const auto r=db.execute("UG_SELECT_HOUSE_BY_USER",{std::to_string(userId)}); if(r.empty()) return std::nullopt; HouseConditions h{ std::stoi(r.front().at("id")), std::stoi(r.front().at("roof_condition")), std::stoi(r.front().at("floor_condition")), std::stoi(r.front().at("wall_condition")), std::stoi(r.front().at("window_condition")) }; return h; } void UndergroundWorker::updateHouse(const HouseConditions& h){ ConnectionGuard g(pool); auto& db=g.get(); db.prepare("UG_UPDATE_HOUSE",Q_UPDATE_HOUSE); db.execute("UG_UPDATE_HOUSE",{ std::to_string(h.id), std::to_string(std::clamp(h.roof,0,100)), std::to_string(std::clamp(h.floor,0,100)), std::to_string(std::clamp(h.wall,0,100)), std::to_string(std::clamp(h.windowc,0,100)) }); } nlohmann::json UndergroundWorker::sabotageHouse(int performerId,int victimId,const json& p){ int userId=getUserIdForCharacter(victimId); if(userId<0) return {{"status","error"},{"action","sabotage"},{"target","house"},{"message","victim_not_found"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}}; auto hopt=getHouseByUser(userId); if(!hopt) return {{"status","error"},{"action","sabotage"},{"target","house"},{"message","house_not_found"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}}; auto h=*hopt; std::vector allow; if(p.contains("conditions") && p["conditions"].is_array()) for(const auto& s:p["conditions"]) if(s.is_string()) allow.push_back(s.get()); std::vector> fields={ {"roof_condition",&h.roof}, {"floor_condition",&h.floor}, {"wall_condition",&h.wall}, {"window_condition",&h.windowc} }; std::vector> pool; for(auto& f:fields) if(allow.empty() || std::find(allow.begin(),allow.end(),f.first)!=allow.end()) pool.push_back(f); if(pool.empty()) return {{"status","error"},{"action","sabotage"},{"target","house"},{"message","no_conditions_selected"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}}; size_t k=static_cast(randomInt(1,(int)pool.size())); std::vector picks=randomIndices(pool.size(),k); json changed=json::array(); for(size_t i: picks){ int& cur=*pool[i].second; if(cur>0){ int red=randomInt(1,cur); cur=std::clamp(cur-red,0,100); } changed.push_back(pool[i].first); } updateHouse(h); return { {"status","success"}, {"action","sabotage"}, {"target","house"}, {"performer_id",performerId}, {"victim_id",victimId}, {"details",p}, {"changed_conditions",changed}, {"new_conditions",{ {"roof_condition",h.roof}, {"floor_condition",h.floor}, {"wall_condition",h.wall}, {"window_condition",h.windowc} }} }; } std::vector UndergroundWorker::selectStockByBranch(int branchId){ ConnectionGuard g(pool); auto& db=g.get(); db.prepare("UG_SELECT_STOCK_BY_BRANCH",Q_SELECT_STOCK_BY_BRANCH); return db.execute("UG_SELECT_STOCK_BY_BRANCH",{std::to_string(branchId)}); } std::vector UndergroundWorker::filterByStockTypes(const std::vector& rows,const std::vector& allowed){ if(allowed.empty()) return rows; std::vector out; out.reserve(rows.size()); for(const auto& r:rows){ int t=std::stoi(r.at("stock_type_id")); if(std::find(allowed.begin(),allowed.end(),t)!=allowed.end()) out.push_back(r); } return out; } void UndergroundWorker::updateStockQty(int id,long long qty){ ConnectionGuard g(pool); auto& db=g.get(); db.prepare("UG_UPDATE_STOCK_QTY",Q_UPDATE_STOCK_QTY); db.execute("UG_UPDATE_STOCK_QTY",{std::to_string(id),std::to_string(qty)}); } nlohmann::json UndergroundWorker::sabotageStorage(int performerId,int victimId,const json& p){ if(!p.contains("branch_id") || !p["branch_id"].is_number_integer()) return {{"status","error"},{"action","sabotage"},{"target","storage"},{"message","branch_id_required"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}}; int branchId=p["branch_id"].get(); std::vector allowed; if(p.contains("stock_type_ids") && p["stock_type_ids"].is_array()) for(const auto& v:p["stock_type_ids"]) if(v.is_number_integer()) allowed.push_back(v.get()); auto rows=filterByStockTypes(selectStockByBranch(branchId),allowed); if(rows.empty()) return {{"status","success"},{"action","sabotage"},{"target","storage"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"removed_total",0},{"affected_rows",json::array()}}; long long total=0; for(const auto& r:rows) total+=std::stoll(r.at("quantity")); if(total<=0) return {{"status","success"},{"action","sabotage"},{"target","storage"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"removed_total",0},{"affected_rows",json::array()}}; long long cap=total/4; if(cap<=0) return {{"status","success"},{"action","sabotage"},{"target","storage"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"removed_total",0},{"affected_rows",json::array()}}; long long to_remove=randomLL(1,cap); std::shuffle(rows.begin(),rows.end(),rng()); json affected=json::array(); for(const auto& r:rows){ if(to_remove==0) break; int id=std::stoi(r.at("id")); long long q=std::stoll(r.at("quantity")); if(q<=0) continue; long long take=randomLL(1,std::min(q,to_remove)); long long newq=q-take; updateStockQty(id,newq); to_remove-=take; affected.push_back({{"id",id},{"stock_type_id",std::stoi(r.at("stock_type_id"))},{"previous_quantity",q},{"new_quantity",newq},{"removed",take}}); } long long removed=0; for(const auto& a:affected) removed+=a.at("removed").get(); return { {"status","success"}, {"action","sabotage"}, {"target","storage"}, {"performer_id",performerId}, {"victim_id",victimId}, {"details",p}, {"removed_total",removed}, {"affected_rows",affected} }; } nlohmann::json UndergroundWorker::corruptPolitician(int performerId,int victimId,const json& p){ return {{"status","success"},{"action","corrupt_politician"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}}; } nlohmann::json UndergroundWorker::rob(int performerId,int victimId,const json& p){ int userId=getUserIdForCharacter(victimId); if(userId<0) return {{"status","error"},{"action","rob"},{"message","victim_not_found"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}}; ConnectionGuard g(pool); auto& db=g.get(); db.prepare("UG_SELECT_FALUKANT_USER",Q_SELECT_FALUKANT_USER); const auto fu=db.execute("UG_SELECT_FALUKANT_USER",{std::to_string(userId)}); if(fu.empty()) return {{"status","error"},{"action","rob"},{"message","falukant_user_not_found"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}}; int falukantUserId=std::stoi(fu.front().at("id")); double money=std::stod(fu.front().at("money")); int defaultBranch=std::stoi(fu.front().at("main_branch_region_id")); bool stealGoods = (randomInt(0,1)==1); if(stealGoods){ int branchId = p.contains("branch_id") && p["branch_id"].is_number_integer() ? p["branch_id"].get() : defaultBranch; if(branchId<=0){ return {{"status","success"},{"action","rob"},{"mode","goods"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"removed_total",0},{"affected_rows",json::array()}}; } auto rows = selectStockByBranch(branchId); if(rows.empty()){ return {{"status","success"},{"action","rob"},{"mode","goods"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"removed_total",0},{"affected_rows",json::array()}}; } long long total=0; for(const auto& r:rows) total+=std::stoll(r.at("quantity")); if(total<=0){ return {{"status","success"},{"action","rob"},{"mode","goods"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"removed_total",0},{"affected_rows",json::array()}}; } long long cap = std::max(1, total/2); long long to_remove = randomLL(1, cap); std::shuffle(rows.begin(),rows.end(),rng()); json affected = json::array(); for(const auto& r:rows){ if(to_remove==0) break; int id=std::stoi(r.at("id")); long long q=std::stoll(r.at("quantity")); if(q<=0) continue; long long take=randomLL(1,std::min(q,to_remove)); long long newq=q-take; updateStockQty(id,newq); to_remove-=take; affected.push_back({ {"id",id}, {"stock_type_id",std::stoi(r.at("stock_type_id"))}, {"previous_quantity",q}, {"new_quantity",newq}, {"removed",take} }); } long long removed=0; for(const auto& a:affected) removed+=a.at("removed").get(); return { {"status","success"}, {"action","rob"}, {"mode","goods"}, {"performer_id",performerId}, {"victim_id",victimId}, {"details",p}, {"removed_total",removed}, {"affected_rows",affected} }; } else { if(money<=0.0){ return {{"status","success"},{"action","rob"},{"mode","money"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"stolen",0.0},{"balance_before",0.0},{"balance_after",0.0}}; } double rate = randomDouble(0.0,0.18); double amount = std::floor(money * rate * 100.0 + 0.5) / 100.0; if(amount < 0.01) amount = 0.01; if(amount > money) amount = money; json msg = { {"event","money_changed"}, {"reason","robbery"}, {"delta",-amount}, {"performer_id",performerId}, {"victim_id",victimId} }; changeFalukantUserMoney(falukantUserId, -amount, "robbery", msg); double after = std::floor((money - amount) * 100.0 + 0.5)/100.0; return { {"status","success"}, {"action","rob"}, {"mode","money"}, {"performer_id",performerId}, {"victim_id",victimId}, {"details",p}, {"stolen",amount}, {"rate",rate}, {"balance_before",money}, {"balance_after",after} }; } } void UndergroundWorker::updateResult(int id,const nlohmann::json& result){ ConnectionGuard g(pool); auto& db=g.get(); db.prepare("UG_UPDATE_RESULT",Q_UPDATE_RESULT); db.execute("UG_UPDATE_RESULT",{std::to_string(id),result.dump()}); } double UndergroundWorker::randomDouble(double lo,double hi){ std::uniform_real_distribution d(lo,hi); return d(rng()); }