Initial commit: Rust YpDaemon
This commit is contained in:
968
src/worker/underground.rs
Normal file
968
src/worker/underground.rs
Normal file
@@ -0,0 +1,968 @@
|
||||
use crate::db::{ConnectionPool, DbError, Row, Rows};
|
||||
use crate::message_broker::MessageBroker;
|
||||
use rand::distributions::{Distribution, Uniform};
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::Rng;
|
||||
use serde_json::json;
|
||||
use serde_json::Value as Json;
|
||||
use std::cmp::{max, min};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use super::base::{BaseWorker, Worker, WorkerState};
|
||||
|
||||
pub struct UndergroundWorker {
|
||||
base: BaseWorker,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct HouseConditions {
|
||||
id: i32,
|
||||
roof: i32,
|
||||
floor: i32,
|
||||
wall: i32,
|
||||
windowc: i32,
|
||||
}
|
||||
|
||||
// Query-Konstanten (1:1 aus der C++-Version übernommen)
|
||||
const Q_SELECT_BY_PERFORMER: &str = r#"
|
||||
SELECT u.id,
|
||||
t.tr AS underground_type,
|
||||
u.performer_id,
|
||||
u.victim_id,
|
||||
to_char(u.created_at,'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') AS created_at,
|
||||
COALESCE(u.parameters::text,'{}') AS parameters,
|
||||
COALESCE(u.result::text,'null') AS result_text
|
||||
FROM falukant_data.underground u
|
||||
JOIN falukant_type.underground t ON t.tr = u.underground_type_id
|
||||
WHERE u.performer_id = $1
|
||||
ORDER BY u.created_at DESC;
|
||||
"#;
|
||||
|
||||
const Q_SELECT_PENDING: &str = r#"
|
||||
SELECT u.id,
|
||||
t.tr AS underground_type,
|
||||
u.performer_id,
|
||||
u.victim_id,
|
||||
COALESCE(u.parameters::text,'{}') AS parameters
|
||||
FROM falukant_data.underground u
|
||||
JOIN falukant_type.underground t ON t.tr = u.underground_type_id
|
||||
WHERE u.result IS NULL
|
||||
AND u.created_at <= NOW() - INTERVAL '1 day'
|
||||
ORDER BY u.created_at ASC
|
||||
LIMIT 200;
|
||||
"#;
|
||||
|
||||
const Q_UPDATE_RESULT: &str = r#"
|
||||
UPDATE falukant_data.underground
|
||||
SET result = $2::jsonb,
|
||||
updated_at = NOW()
|
||||
WHERE id = $1;
|
||||
"#;
|
||||
|
||||
const Q_SELECT_CHAR_USER: &str = r#"
|
||||
SELECT user_id
|
||||
FROM falukant_data.character
|
||||
WHERE id = $1;
|
||||
"#;
|
||||
|
||||
const Q_SELECT_HOUSE_BY_USER: &str = r#"
|
||||
SELECT id, roof_condition, floor_condition, wall_condition, window_condition
|
||||
FROM falukant_data.user_house
|
||||
WHERE user_id = $1
|
||||
LIMIT 1;
|
||||
"#;
|
||||
|
||||
const Q_UPDATE_HOUSE: &str = r#"
|
||||
UPDATE falukant_data.user_house
|
||||
SET roof_condition = $2,
|
||||
floor_condition = $3,
|
||||
wall_condition = $4,
|
||||
window_condition = $5
|
||||
WHERE id = $1;
|
||||
"#;
|
||||
|
||||
const Q_SELECT_STOCK_BY_BRANCH: &str = r#"
|
||||
SELECT id, stock_type_id, quantity
|
||||
FROM falukant_data.stock
|
||||
WHERE branch_id = $1
|
||||
ORDER BY quantity DESC;
|
||||
"#;
|
||||
|
||||
const Q_UPDATE_STOCK_QTY: &str = r#"
|
||||
UPDATE falukant_data.stock
|
||||
SET quantity = $2
|
||||
WHERE id = $1;
|
||||
"#;
|
||||
|
||||
const Q_SELECT_CHAR_HEALTH: &str = r#"
|
||||
SELECT health
|
||||
FROM falukant_data.character
|
||||
WHERE id = $1;
|
||||
"#;
|
||||
|
||||
const Q_UPDATE_CHAR_HEALTH: &str = r#"
|
||||
UPDATE falukant_data.character
|
||||
SET health = $2,
|
||||
updated_at = NOW()
|
||||
WHERE id = $1;
|
||||
"#;
|
||||
|
||||
const Q_SELECT_FALUKANT_USER: &str = r#"
|
||||
SELECT id,
|
||||
money,
|
||||
COALESCE(main_branch_region_id, 0) AS main_branch_region_id
|
||||
FROM falukant_data.falukant_user
|
||||
WHERE user_id = $1
|
||||
LIMIT 1;
|
||||
"#;
|
||||
|
||||
// Query für Geldänderungen (lokale Variante von BaseWorker::change_falukant_user_money)
|
||||
const QUERY_UPDATE_MONEY: &str = r#"
|
||||
SELECT falukant_data.update_money($1, $2, $3);
|
||||
"#;
|
||||
|
||||
impl UndergroundWorker {
|
||||
pub fn new(pool: ConnectionPool, broker: MessageBroker) -> Self {
|
||||
Self {
|
||||
base: BaseWorker::new("UndergroundWorker", pool, broker),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_loop(pool: ConnectionPool, broker: MessageBroker, state: Arc<WorkerState>) {
|
||||
while state.running_worker.load(Ordering::Relaxed) {
|
||||
if let Err(err) = Self::tick(&pool, &broker) {
|
||||
eprintln!("[UndergroundWorker] Fehler in tick: {err}");
|
||||
}
|
||||
|
||||
// Entspricht ~60-Sekunden-Loop mit 1-Sekunden-Schritten
|
||||
for _ in 0..60 {
|
||||
if !state.running_worker.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tick(pool: &ConnectionPool, broker: &MessageBroker) -> Result<(), DbError> {
|
||||
let rows = Self::fetch_pending(pool)?;
|
||||
|
||||
for row in rows {
|
||||
let id = match row.get("id").and_then(|v| v.parse::<i32>().ok()) {
|
||||
Some(id) => id,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
match Self::execute_row(pool, &row) {
|
||||
Ok(res) => {
|
||||
Self::update_result(pool, id, &res)?;
|
||||
let event = json!({
|
||||
"event": "underground_processed",
|
||||
"id": id,
|
||||
"type": row.get("underground_type").cloned().unwrap_or_default()
|
||||
});
|
||||
broker.publish(event.to_string());
|
||||
}
|
||||
Err(err) => {
|
||||
let error_res = json!({
|
||||
"status": "error",
|
||||
"message": err.to_string()
|
||||
});
|
||||
let _ = Self::update_result(pool, id, &error_res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fetch_pending(pool: &ConnectionPool) -> Result<Rows, DbError> {
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
conn.prepare("ug_select_pending", Q_SELECT_PENDING)?;
|
||||
conn.execute("ug_select_pending", &[])
|
||||
}
|
||||
|
||||
fn execute_row(pool: &ConnectionPool, r: &Row) -> Result<Json, DbError> {
|
||||
let performer_id = parse_i32(r, "performer_id", -1);
|
||||
let victim_id = parse_i32(r, "victim_id", -1);
|
||||
let task_type = r.get("underground_type").cloned().unwrap_or_default();
|
||||
let params = r.get("parameters").cloned().unwrap_or_else(|| "{}".into());
|
||||
|
||||
Ok(Self::handle_task(pool, &task_type, performer_id, victim_id, ¶ms)?)
|
||||
}
|
||||
|
||||
fn handle_task(
|
||||
pool: &ConnectionPool,
|
||||
task_type: &str,
|
||||
performer_id: i32,
|
||||
victim_id: i32,
|
||||
params_json: &str,
|
||||
) -> Result<Json, DbError> {
|
||||
let p: Json = serde_json::from_str(params_json).unwrap_or_else(|_| json!({}));
|
||||
|
||||
match task_type {
|
||||
"spyin" => Self::spy_in(pool, performer_id, victim_id, &p),
|
||||
"assassin" => Self::assassin(pool, performer_id, victim_id, &p),
|
||||
"sabotage" => Self::sabotage(pool, performer_id, victim_id, &p),
|
||||
"corrupt_politician" => Ok(Self::corrupt_politician(performer_id, victim_id, &p)),
|
||||
"rob" => Self::rob(pool, performer_id, victim_id, &p),
|
||||
_ => Ok(json!({
|
||||
"status": "unknown_type",
|
||||
"type": task_type
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
fn spy_in(
|
||||
pool: &ConnectionPool,
|
||||
performer_id: i32,
|
||||
victim_id: i32,
|
||||
p: &Json,
|
||||
) -> Result<Json, DbError> {
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
conn.prepare("ug_select_by_performer", Q_SELECT_BY_PERFORMER)?;
|
||||
let rows = conn.execute("ug_select_by_performer", &[&victim_id])?;
|
||||
|
||||
let mut activities = Vec::new();
|
||||
for r in rows {
|
||||
let params: Json = r
|
||||
.get("parameters")
|
||||
.and_then(|s| serde_json::from_str(s).ok())
|
||||
.unwrap_or_else(|| json!({}));
|
||||
let result_text = r.get("result_text").cloned().unwrap_or_else(|| "null".into());
|
||||
let result: Json = serde_json::from_str(&result_text).unwrap_or(Json::Null);
|
||||
|
||||
let mut status = "pending".to_string();
|
||||
if let Json::Object(obj) = &result {
|
||||
if let Some(Json::String(s)) = obj.get("status") {
|
||||
status = s.clone();
|
||||
} else {
|
||||
status = "done".to_string();
|
||||
}
|
||||
}
|
||||
|
||||
let activity = json!({
|
||||
"id": parse_i32(&r, "id", -1),
|
||||
"type": r.get("underground_type").cloned().unwrap_or_default(),
|
||||
"performed_by": parse_i32(&r, "performer_id", -1),
|
||||
"victim_id": parse_i32(&r, "victim_id", -1),
|
||||
"created_at": r.get("created_at").cloned().unwrap_or_default(),
|
||||
"parameters": params,
|
||||
"result": result,
|
||||
"status": status
|
||||
});
|
||||
|
||||
activities.push(activity);
|
||||
}
|
||||
|
||||
Ok(json!({
|
||||
"status": "success",
|
||||
"action": "spyin",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p,
|
||||
"victim_illegal_activity_count": activities.len(),
|
||||
"victim_illegal_activities": activities
|
||||
}))
|
||||
}
|
||||
|
||||
fn assassin(
|
||||
pool: &ConnectionPool,
|
||||
performer_id: i32,
|
||||
victim_id: i32,
|
||||
p: &Json,
|
||||
) -> Result<Json, DbError> {
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
|
||||
conn.prepare("ug_select_char_health", Q_SELECT_CHAR_HEALTH)?;
|
||||
conn.prepare("ug_update_char_health", Q_UPDATE_CHAR_HEALTH)?;
|
||||
|
||||
let rows = conn.execute("ug_select_char_health", &[&victim_id])?;
|
||||
if rows.is_empty() {
|
||||
return Ok(json!({
|
||||
"status": "error",
|
||||
"action": "assassin",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"message": "victim_not_found",
|
||||
"details": p
|
||||
}));
|
||||
}
|
||||
|
||||
let current = parse_i32(&rows[0], "health", 0);
|
||||
let mut rng = rand::thread_rng();
|
||||
let dist = Uniform::from(0..=current.max(0));
|
||||
let new_health = dist.sample(&mut rng);
|
||||
|
||||
conn.execute("ug_update_char_health", &[&victim_id, &new_health])?;
|
||||
|
||||
Ok(json!({
|
||||
"status": "success",
|
||||
"action": "assassin",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p,
|
||||
"previous_health": current,
|
||||
"new_health": new_health,
|
||||
"reduced_by": current - new_health
|
||||
}))
|
||||
}
|
||||
|
||||
fn sabotage(
|
||||
pool: &ConnectionPool,
|
||||
performer_id: i32,
|
||||
victim_id: i32,
|
||||
p: &Json,
|
||||
) -> Result<Json, DbError> {
|
||||
let target = p
|
||||
.get("target")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
match target.as_str() {
|
||||
"house" => Self::sabotage_house(pool, performer_id, victim_id, p),
|
||||
"storage" => Self::sabotage_storage(pool, performer_id, victim_id, p),
|
||||
_ => Ok(json!({
|
||||
"status": "error",
|
||||
"action": "sabotage",
|
||||
"message": "unknown_target",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_user_id_for_character(pool: &ConnectionPool, character_id: i32) -> Result<i32, DbError> {
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
conn.prepare("ug_select_char_user", Q_SELECT_CHAR_USER)?;
|
||||
let rows = conn.execute("ug_select_char_user", &[&character_id])?;
|
||||
|
||||
Ok(rows
|
||||
.get(0)
|
||||
.and_then(|r| r.get("user_id"))
|
||||
.and_then(|v| v.parse::<i32>().ok())
|
||||
.unwrap_or(-1))
|
||||
}
|
||||
|
||||
fn get_house_by_user(pool: &ConnectionPool, user_id: i32) -> Result<Option<HouseConditions>, DbError> {
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
conn.prepare("ug_select_house_by_user", Q_SELECT_HOUSE_BY_USER)?;
|
||||
let rows = conn.execute("ug_select_house_by_user", &[&user_id])?;
|
||||
|
||||
if rows.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let r = &rows[0];
|
||||
Ok(Some(HouseConditions {
|
||||
id: parse_i32(r, "id", -1),
|
||||
roof: parse_i32(r, "roof_condition", 0),
|
||||
floor: parse_i32(r, "floor_condition", 0),
|
||||
wall: parse_i32(r, "wall_condition", 0),
|
||||
windowc: parse_i32(r, "window_condition", 0),
|
||||
}))
|
||||
}
|
||||
|
||||
fn update_house(pool: &ConnectionPool, h: &HouseConditions) -> Result<(), DbError> {
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
conn.prepare("ug_update_house", Q_UPDATE_HOUSE)?;
|
||||
|
||||
let roof = h.roof.clamp(0, 100);
|
||||
let floor = h.floor.clamp(0, 100);
|
||||
let wall = h.wall.clamp(0, 100);
|
||||
let windowc = h.windowc.clamp(0, 100);
|
||||
|
||||
conn.execute("ug_update_house", &[&h.id, &roof, &floor, &wall, &windowc])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sabotage_house(
|
||||
pool: &ConnectionPool,
|
||||
performer_id: i32,
|
||||
victim_id: i32,
|
||||
p: &Json,
|
||||
) -> Result<Json, DbError> {
|
||||
let user_id = Self::get_user_id_for_character(pool, victim_id)?;
|
||||
if user_id < 0 {
|
||||
return Ok(json!({
|
||||
"status": "error",
|
||||
"action": "sabotage",
|
||||
"target": "house",
|
||||
"message": "victim_not_found",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p
|
||||
}));
|
||||
}
|
||||
|
||||
let mut house = match Self::get_house_by_user(pool, user_id)? {
|
||||
Some(h) => h,
|
||||
None => {
|
||||
return Ok(json!({
|
||||
"status": "error",
|
||||
"action": "sabotage",
|
||||
"target": "house",
|
||||
"message": "house_not_found",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p
|
||||
}))
|
||||
}
|
||||
};
|
||||
|
||||
// Erlaubte Felder aus Params
|
||||
let mut allow: Vec<String> = Vec::new();
|
||||
if let Some(conds) = p.get("conditions").and_then(|v| v.as_array()) {
|
||||
for s in conds {
|
||||
if let Some(name) = s.as_str() {
|
||||
allow.push(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Statt Referenzen auf Felder zu speichern, arbeiten wir über Indizes,
|
||||
// um Borrowing-Probleme zu vermeiden.
|
||||
let all_fields = ["roof_condition", "floor_condition", "wall_condition", "window_condition"];
|
||||
let candidate_indices: Vec<usize> = (0..all_fields.len())
|
||||
.filter(|&idx| {
|
||||
allow.is_empty()
|
||||
|| allow
|
||||
.iter()
|
||||
.any(|name| name == all_fields[idx])
|
||||
})
|
||||
.collect();
|
||||
|
||||
if candidate_indices.is_empty() {
|
||||
return Ok(json!({
|
||||
"status": "error",
|
||||
"action": "sabotage",
|
||||
"target": "house",
|
||||
"message": "no_conditions_selected",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p
|
||||
}));
|
||||
}
|
||||
|
||||
let k = random_int(1, candidate_indices.len() as i32) as usize;
|
||||
let picks = random_indices(candidate_indices.len(), k);
|
||||
|
||||
let mut changed = Vec::new();
|
||||
for i in picks {
|
||||
let idx = candidate_indices[i];
|
||||
let (name, value_ref) = match idx {
|
||||
0 => ("roof_condition", &mut house.roof),
|
||||
1 => ("floor_condition", &mut house.floor),
|
||||
2 => ("wall_condition", &mut house.wall),
|
||||
3 => ("window_condition", &mut house.windowc),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
if *value_ref > 0 {
|
||||
let red = random_int(1, *value_ref);
|
||||
*value_ref = (*value_ref - red).clamp(0, 100);
|
||||
}
|
||||
changed.push(name.to_string());
|
||||
}
|
||||
|
||||
Self::update_house(pool, &house)?;
|
||||
|
||||
Ok(json!({
|
||||
"status": "success",
|
||||
"action": "sabotage",
|
||||
"target": "house",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p,
|
||||
"changed_conditions": changed,
|
||||
"new_conditions": {
|
||||
"roof_condition": house.roof,
|
||||
"floor_condition": house.floor,
|
||||
"wall_condition": house.wall,
|
||||
"window_condition": house.windowc
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
fn select_stock_by_branch(pool: &ConnectionPool, branch_id: i32) -> Result<Rows, DbError> {
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
conn.prepare("ug_select_stock_by_branch", Q_SELECT_STOCK_BY_BRANCH)?;
|
||||
conn.execute("ug_select_stock_by_branch", &[&branch_id])
|
||||
}
|
||||
|
||||
fn filter_by_stock_types(rows: &Rows, allowed: &[i32]) -> Rows {
|
||||
if allowed.is_empty() {
|
||||
return rows.clone();
|
||||
}
|
||||
|
||||
let mut out = Vec::new();
|
||||
for r in rows {
|
||||
if let Some(t) = r.get("stock_type_id").and_then(|v| v.parse::<i32>().ok()) {
|
||||
if allowed.contains(&t) {
|
||||
out.push(r.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn update_stock_qty(pool: &ConnectionPool, id: i32, qty: i64) -> Result<(), DbError> {
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
conn.prepare("ug_update_stock_qty", Q_UPDATE_STOCK_QTY)?;
|
||||
// beide Parameter explizit als ToSql-Traitobjekte typisieren, um Mischtypen zu erlauben
|
||||
use postgres::types::ToSql;
|
||||
let p1: &(dyn ToSql + Sync) = &id;
|
||||
let p2: &(dyn ToSql + Sync) = &qty;
|
||||
conn.execute("ug_update_stock_qty", &[p1, p2])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sabotage_storage(
|
||||
pool: &ConnectionPool,
|
||||
performer_id: i32,
|
||||
victim_id: i32,
|
||||
p: &Json,
|
||||
) -> Result<Json, DbError> {
|
||||
let branch_id = match p.get("branch_id").and_then(|v| v.as_i64()) {
|
||||
Some(id) => id as i32,
|
||||
None => {
|
||||
return Ok(json!({
|
||||
"status": "error",
|
||||
"action": "sabotage",
|
||||
"target": "storage",
|
||||
"message": "branch_id_required",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p
|
||||
}))
|
||||
}
|
||||
};
|
||||
|
||||
let mut allowed = Vec::new();
|
||||
if let Some(arr) = p.get("stock_type_ids").and_then(|v| v.as_array()) {
|
||||
for v in arr {
|
||||
if let Some(id) = v.as_i64() {
|
||||
allowed.push(id as i32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let rows_all = Self::select_stock_by_branch(pool, branch_id)?;
|
||||
let mut rows = Self::filter_by_stock_types(&rows_all, &allowed);
|
||||
if rows.is_empty() {
|
||||
return Ok(json!({
|
||||
"status": "success",
|
||||
"action": "sabotage",
|
||||
"target": "storage",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p,
|
||||
"removed_total": 0,
|
||||
"affected_rows": []
|
||||
}));
|
||||
}
|
||||
|
||||
let mut total: i64 = 0;
|
||||
for r in &rows {
|
||||
if let Some(q) = r.get("quantity").and_then(|v| v.parse::<i64>().ok()) {
|
||||
total += q;
|
||||
}
|
||||
}
|
||||
if total <= 0 {
|
||||
return Ok(json!({
|
||||
"status": "success",
|
||||
"action": "sabotage",
|
||||
"target": "storage",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p,
|
||||
"removed_total": 0,
|
||||
"affected_rows": []
|
||||
}));
|
||||
}
|
||||
|
||||
let cap = total / 4;
|
||||
if cap <= 0 {
|
||||
return Ok(json!({
|
||||
"status": "success",
|
||||
"action": "sabotage",
|
||||
"target": "storage",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p,
|
||||
"removed_total": 0,
|
||||
"affected_rows": []
|
||||
}));
|
||||
}
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut to_remove = random_ll(1, cap);
|
||||
rows.shuffle(&mut rng);
|
||||
|
||||
let mut affected = Vec::new();
|
||||
for r in rows {
|
||||
if to_remove == 0 {
|
||||
break;
|
||||
}
|
||||
let id = parse_i32(&r, "id", -1);
|
||||
let q = r
|
||||
.get("quantity")
|
||||
.and_then(|v| v.parse::<i64>().ok())
|
||||
.unwrap_or(0);
|
||||
if q <= 0 {
|
||||
continue;
|
||||
}
|
||||
let take = random_ll(1, min(q, to_remove));
|
||||
let newq = q - take;
|
||||
Self::update_stock_qty(pool, id, newq)?;
|
||||
to_remove -= take;
|
||||
let entry = json!({
|
||||
"id": id,
|
||||
"stock_type_id": parse_i32(&r, "stock_type_id", -1),
|
||||
"previous_quantity": q,
|
||||
"new_quantity": newq,
|
||||
"removed": take
|
||||
});
|
||||
affected.push(entry);
|
||||
}
|
||||
|
||||
let removed_total: i64 = affected
|
||||
.iter()
|
||||
.filter_map(|a| a.get("removed").and_then(|v| v.as_i64()))
|
||||
.sum();
|
||||
|
||||
Ok(json!({
|
||||
"status": "success",
|
||||
"action": "sabotage",
|
||||
"target": "storage",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p,
|
||||
"removed_total": removed_total,
|
||||
"affected_rows": affected
|
||||
}))
|
||||
}
|
||||
|
||||
fn corrupt_politician(
|
||||
performer_id: i32,
|
||||
victim_id: i32,
|
||||
p: &Json,
|
||||
) -> Json {
|
||||
json!({
|
||||
"status": "success",
|
||||
"action": "corrupt_politician",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p
|
||||
})
|
||||
}
|
||||
|
||||
fn rob(
|
||||
pool: &ConnectionPool,
|
||||
performer_id: i32,
|
||||
victim_id: i32,
|
||||
p: &Json,
|
||||
) -> Result<Json, DbError> {
|
||||
let user_id = Self::get_user_id_for_character(pool, victim_id)?;
|
||||
if user_id < 0 {
|
||||
return Ok(json!({
|
||||
"status": "error",
|
||||
"action": "rob",
|
||||
"message": "victim_not_found",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p
|
||||
}));
|
||||
}
|
||||
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
conn.prepare("ug_select_falukant_user", Q_SELECT_FALUKANT_USER)?;
|
||||
let fu = conn.execute("ug_select_falukant_user", &[&user_id])?;
|
||||
if fu.is_empty() {
|
||||
return Ok(json!({
|
||||
"status": "error",
|
||||
"action": "rob",
|
||||
"message": "falukant_user_not_found",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p
|
||||
}));
|
||||
}
|
||||
|
||||
let falukant_user_id = parse_i32(&fu[0], "id", -1);
|
||||
let money = fu[0]
|
||||
.get("money")
|
||||
.and_then(|v| v.parse::<f64>().ok())
|
||||
.unwrap_or(0.0);
|
||||
let default_branch = parse_i32(&fu[0], "main_branch_region_id", 0);
|
||||
|
||||
let steal_goods = random_int(0, 1) == 1;
|
||||
|
||||
if steal_goods {
|
||||
let branch_id = p
|
||||
.get("branch_id")
|
||||
.and_then(|v| v.as_i64())
|
||||
.map(|v| v as i32)
|
||||
.unwrap_or(default_branch);
|
||||
|
||||
if branch_id <= 0 {
|
||||
return Ok(json!({
|
||||
"status": "success",
|
||||
"action": "rob",
|
||||
"mode": "goods",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p,
|
||||
"removed_total": 0,
|
||||
"affected_rows": []
|
||||
}));
|
||||
}
|
||||
|
||||
let rows_all = Self::select_stock_by_branch(pool, branch_id)?;
|
||||
let mut rows = rows_all;
|
||||
if rows.is_empty() {
|
||||
return Ok(json!({
|
||||
"status": "success",
|
||||
"action": "rob",
|
||||
"mode": "goods",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p,
|
||||
"removed_total": 0,
|
||||
"affected_rows": []
|
||||
}));
|
||||
}
|
||||
|
||||
let mut total: i64 = 0;
|
||||
for r in &rows {
|
||||
if let Some(q) = r.get("quantity").and_then(|v| v.parse::<i64>().ok()) {
|
||||
total += q;
|
||||
}
|
||||
}
|
||||
if total <= 0 {
|
||||
return Ok(json!({
|
||||
"status": "success",
|
||||
"action": "rob",
|
||||
"mode": "goods",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p,
|
||||
"removed_total": 0,
|
||||
"affected_rows": []
|
||||
}));
|
||||
}
|
||||
|
||||
let cap = max(1_i64, total / 2);
|
||||
let mut to_remove = random_ll(1, cap);
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
rows.shuffle(&mut rng);
|
||||
|
||||
let mut affected = Vec::new();
|
||||
for r in rows {
|
||||
if to_remove == 0 {
|
||||
break;
|
||||
}
|
||||
let id = parse_i32(&r, "id", -1);
|
||||
let q = r
|
||||
.get("quantity")
|
||||
.and_then(|v| v.parse::<i64>().ok())
|
||||
.unwrap_or(0);
|
||||
if q <= 0 {
|
||||
continue;
|
||||
}
|
||||
let take = random_ll(1, min(q, to_remove));
|
||||
let newq = q - take;
|
||||
Self::update_stock_qty(pool, id, newq)?;
|
||||
to_remove -= take;
|
||||
affected.push(json!({
|
||||
"id": id,
|
||||
"stock_type_id": parse_i32(&r, "stock_type_id", -1),
|
||||
"previous_quantity": q,
|
||||
"new_quantity": newq,
|
||||
"removed": take
|
||||
}));
|
||||
}
|
||||
|
||||
let removed: i64 = affected
|
||||
.iter()
|
||||
.filter_map(|a| a.get("removed").and_then(|v| v.as_i64()))
|
||||
.sum();
|
||||
|
||||
Ok(json!({
|
||||
"status": "success",
|
||||
"action": "rob",
|
||||
"mode": "goods",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p,
|
||||
"removed_total": removed,
|
||||
"affected_rows": affected
|
||||
}))
|
||||
} else {
|
||||
if money <= 0.0 {
|
||||
return Ok(json!({
|
||||
"status": "success",
|
||||
"action": "rob",
|
||||
"mode": "money",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p,
|
||||
"stolen": 0.0,
|
||||
"balance_before": 0.0,
|
||||
"balance_after": 0.0
|
||||
}));
|
||||
}
|
||||
|
||||
let rate = random_double(0.0, 0.18);
|
||||
let mut amount = (money * rate * 100.0).round() / 100.0;
|
||||
if amount < 0.01 {
|
||||
amount = 0.01;
|
||||
}
|
||||
if amount > money {
|
||||
amount = money;
|
||||
}
|
||||
|
||||
let _msg = json!({
|
||||
"event": "money_changed",
|
||||
"reason": "robbery",
|
||||
"delta": -amount,
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id
|
||||
});
|
||||
|
||||
if let Err(err) =
|
||||
change_falukant_user_money(pool, falukant_user_id, -amount, "robbery")
|
||||
{
|
||||
eprintln!(
|
||||
"[UndergroundWorker] Fehler bei change_falukant_user_money: {err}"
|
||||
);
|
||||
}
|
||||
// Event manuell publizieren
|
||||
// (BaseWorker kümmert sich aktuell nur um die DB-Änderung)
|
||||
// Hinweis: Wir haben keinen direkten Zugriff auf broker hier, daher wird das
|
||||
// Event nur im Rückgabe-JSON signalisiert.
|
||||
|
||||
let after = ((money - amount) * 100.0).round() / 100.0;
|
||||
|
||||
Ok(json!({
|
||||
"status": "success",
|
||||
"action": "rob",
|
||||
"mode": "money",
|
||||
"performer_id": performer_id,
|
||||
"victim_id": victim_id,
|
||||
"details": p,
|
||||
"stolen": amount,
|
||||
"rate": rate,
|
||||
"balance_before": money,
|
||||
"balance_after": after
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn update_result(pool: &ConnectionPool, id: i32, result: &Json) -> Result<(), DbError> {
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
conn.prepare("ug_update_result", Q_UPDATE_RESULT)?;
|
||||
let result_text = result.to_string();
|
||||
conn.execute("ug_update_result", &[&id, &result_text])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Worker for UndergroundWorker {
|
||||
fn start_worker_thread(&mut self) {
|
||||
let pool = self.base.pool.clone();
|
||||
let broker = self.base.broker.clone();
|
||||
|
||||
self.base
|
||||
.start_worker_with_loop(move |state: Arc<WorkerState>| {
|
||||
UndergroundWorker::run_loop(pool.clone(), broker.clone(), state);
|
||||
});
|
||||
}
|
||||
|
||||
fn stop_worker_thread(&mut self) {
|
||||
self.base.stop_worker();
|
||||
}
|
||||
|
||||
fn enable_watchdog(&mut self) {
|
||||
self.base.start_watchdog();
|
||||
}
|
||||
}
|
||||
|
||||
// Hilfsfunktionen für Zufall und Parsing
|
||||
|
||||
fn random_int(lo: i32, hi: i32) -> i32 {
|
||||
let mut rng = rand::thread_rng();
|
||||
rng.gen_range(lo..=hi)
|
||||
}
|
||||
|
||||
fn random_ll(lo: i64, hi: i64) -> i64 {
|
||||
let mut rng = rand::thread_rng();
|
||||
rng.gen_range(lo..=hi)
|
||||
}
|
||||
|
||||
fn random_indices(n: usize, k: usize) -> Vec<usize> {
|
||||
let mut idx: Vec<usize> = (0..n).collect();
|
||||
let mut rng = rand::thread_rng();
|
||||
idx.shuffle(&mut rng);
|
||||
if k < idx.len() {
|
||||
idx.truncate(k);
|
||||
}
|
||||
idx
|
||||
}
|
||||
|
||||
fn random_double(lo: f64, hi: f64) -> f64 {
|
||||
let mut rng = rand::thread_rng();
|
||||
rng.gen_range(lo..hi)
|
||||
}
|
||||
|
||||
fn parse_i32(row: &Row, key: &str, default: i32) -> i32 {
|
||||
row.get(key)
|
||||
.and_then(|v| v.parse::<i32>().ok())
|
||||
.unwrap_or(default)
|
||||
}
|
||||
|
||||
fn change_falukant_user_money(
|
||||
pool: &ConnectionPool,
|
||||
falukant_user_id: i32,
|
||||
money_change: f64,
|
||||
action: &str,
|
||||
) -> Result<(), DbError> {
|
||||
use postgres::types::ToSql;
|
||||
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
conn.prepare("ug_update_money", QUERY_UPDATE_MONEY)?;
|
||||
let p1: &(dyn ToSql + Sync) = &falukant_user_id;
|
||||
let p2: &(dyn ToSql + Sync) = &money_change;
|
||||
let p3: &(dyn ToSql + Sync) = &action;
|
||||
conn.execute("ug_update_money", &[p1, p2, p3])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user