/* tws_bank.gsc — persistent per-player bank, MariaDB-backed. See NOTES.md for the DB-first / spawn-dispatch / command surface invariants. */ #include maps\_utility; #include common_scripts\utility; init() { if (GetDvar(#"zombiemode") != "1") return; scripts\sp\tws_common::ensure_config(); bank_init(); scripts\sp\tws_common::register_spawn_handler(::bank_load); } bank_init() { if (!isDefined(level.db_probe_done) || !level.db_probe_done) level thread bank_deferred_start(); else bank_start(); } bank_deferred_start() { level waittill("db_probe_done"); bank_start(); } bank_start() { if (!level.db_online) return; bank_register_commands(); } bank_register_commands() { command::add_sv("bank", ::bank_cmd_bank); command::add_sv("deposit", ::bank_cmd_deposit); command::add_sv("withdraw", ::bank_cmd_withdraw); } // DIAGNOSTIC STUB — bare minimum to isolate the connect-handshake crash. // Do not restore the getGuid / mysql body until this passes join. bank_load() { self endon("disconnect"); self iPrintLnBold("^2bank_load: entered"); self.bank = 0; self.bank_unavailable = false; self iPrintLnBold("^2bank_load: done"); } bank_available(player) { if (!level.db_online) { player iPrintLnBold("^1Bank offline"); return 0; } if (is_true(player.bank_unavailable)) { player iPrintLnBold("^1Bank unavailable for your account"); return 0; } if (!isDefined(player.bank)) { player iPrintLnBold("^3Bank still loading, try again"); return 0; } return 1; } bank_cmd_bank(args) { if (!bank_available(self)) return; self iPrintLnBold("Bank: " + self.bank + " Score: " + self.score); } bank_cmd_deposit(args) { if (!bank_available(self)) return; if (args.size < 2) { self iPrintLnBold("Usage: deposit "); return; } amount = int(args[1]); if (amount <= 0) { self iPrintLnBold("^1Amount must be positive"); return; } if (amount > self.score) { self iPrintLnBold("^1Not enough score"); return; } self thread bank_deposit_write(amount); } bank_cmd_withdraw(args) { if (!bank_available(self)) return; if (args.size < 2) { self iPrintLnBold("Usage: withdraw "); return; } amount = int(args[1]); if (amount <= 0) { self iPrintLnBold("^1Amount must be positive"); return; } if (amount > self.bank) { self iPrintLnBold("^1Insufficient balance"); return; } self thread bank_withdraw_write(amount); } // upsert: first deposit creates the row, later ones increment bank_deposit_write(amount) { self endon("disconnect"); name = bank_display_name(self); server_id = bank_server_id(); q = mysql::prepared_statement( "INSERT INTO bank (guid, balance, last_name, last_server) VALUES (?, ?, ?, ?) " + "ON DUPLICATE KEY UPDATE balance = balance + VALUES(balance), " + "last_name = VALUES(last_name), last_server = VALUES(last_server)", self.bank_guid, amount, name, server_id); q waittill("done", result, affected_rows, error); if (isDefined(error) && error != "") { self iPrintLnBold("^1Deposit failed"); return; } // score can change between dispatch and write completing — refund if so if (amount > self.score) { mysql::prepared_statement( "UPDATE bank SET balance = balance - ? WHERE guid = ?", amount, self.bank_guid); self iPrintLnBold("^1Deposit rolled back (score changed)"); return; } self.score -= amount; self.bank += amount; self iPrintLnBold("Deposited " + amount + " Bank: " + self.bank); } // balance guard lives in the WHERE so concurrent commands can't overdraw bank_withdraw_write(amount) { self endon("disconnect"); q = mysql::prepared_statement( "UPDATE bank SET balance = balance - ? WHERE guid = ? AND balance >= ?", amount, self.bank_guid, amount); q waittill("done", result, affected_rows, error); if (isDefined(error) && error != "") { self iPrintLnBold("^1Withdraw failed"); return; } if (!isDefined(affected_rows) || affected_rows == 0) { self iPrintLnBold("^1Insufficient balance"); self thread bank_load(); // re-sync in case memory drifted from DB return; } self.score += amount; self.bank -= amount; self iPrintLnBold("Withdrew " + amount + " Bank: " + self.bank); } bank_display_name(player) { if (isDefined(player.name)) return player.name; if (isDefined(player.playername)) return player.playername; return "unknown"; } bank_server_id() { id = GetDvar("tws_server_id"); if (id == "") return "unknown"; return id; }