Ein metin2 gift code-System ist ein einfacher, aber äußerst nützlicher Mechanismus, der einem Spieler einen einmalig nutzbaren Code übergibt und im Gegenzug eine Ingame-Belohnung gewährt (Yang, Items, EXP-Tränke oder ein exklusives Kostüm). Ob bei Discord-Events, in YouTube-Videos oder um neue Spieler zu gewinnen, verteilt: Diese Codes machen Spieler glücklich und bleiben bei korrekter Umsetzung resistent gegen Missbrauch. In diesem Artikel gehe ich den gesamten Ablauf durch: vom Datenbankentwurf und der Codegenerierung bis zum Einlösen im Webpanel und der Auslieferung der Belohnung an den Spieler.
Wie das System funktioniert
In Metin2 liegen Account- (account) und Spielerdaten (player) in MySQL. Der Ablauf eines Gift Codes verläuft typischerweise so:
- Ein Administrator definiert im Panel ein Belohnungsset (zum Beispiel 1.000.000 Yang + 50 EXP-Tränke).
- Das System erzeugt einen oder mehrere eindeutige Codes, die an diese Belohnung gebunden sind.
- Der Spieler gibt den Code im Feld „Code einlösen" auf der Website ein.
- Der Server validiert den Code, prüft, ob er bereits verwendet wurde, und überträgt die Belohnung auf das Konto des Spielers.
Beim Ausliefern der Belohnung ist es am sichersten, die mail / Geschenkbox-Logik von Metin2 zu nutzen, statt Items direkt ins Inventar zu schreiben; so geht die Belohnung selbst dann nicht verloren, wenn der Spieler offline ist.
Die Datenbanktabellen entwerfen
Das Herz des gesamten Systems sind zwei Tabellen: gift_rewards, die die Belohnungen enthält, und gift_codes, die die Codes enthält. Das folgende Schema fügt außerdem eine Relationstabelle hinzu, damit ein einzelner Code mehrere Item-Belohnungen tragen kann.
CREATE TABLE gift_codes (
id INT AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(32) NOT NULL UNIQUE,
reward_id INT NOT NULL,
max_uses INT NOT NULL DEFAULT 1,
used_count INT NOT NULL DEFAULT 0,
expires_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX (reward_id)
);
CREATE TABLE gift_rewards (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(64) NOT NULL,
yang BIGINT NOT NULL DEFAULT 0
);
CREATE TABLE gift_reward_items (
reward_id INT NOT NULL,
item_vnum INT NOT NULL,
count SMALLINT NOT NULL DEFAULT 1
);
CREATE TABLE gift_redemptions (
id INT AUTO_INCREMENT PRIMARY KEY,
code_id INT NOT NULL,
account_id INT NOT NULL,
redeemed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uq_code_account (code_id, account_id)
);
Der UNIQUE KEY uq_code_account ist hier entscheidend: Er verhindert auf Datenbankebene, dass dasselbe Konto denselben Code zweimal einlöst. max_uses legt fest, wie viele verschiedene Personen einen einzelnen Code verwenden dürfen (etwa „100 Personen" für Event-Codes).
Eindeutige Codes generieren
Codes sollten unvorhersehbar und gut lesbar sein. Eine gute Praxis ist, mehrdeutige Zeichen wie 0/O und 1/I/L wegzulassen und ein Großbuchstaben-Alphabet zu verwenden. Sichere Generierung mit PHP:
function generateGiftCode(int $length = 12): string {
$alphabet = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789';
$code = '';
$max = strlen($alphabet) - 1;
for ($i = 0; $i < $length; $i++) {
$code .= $alphabet[random_int(0, $max)];
}
// zur Lesbarkeit gruppieren: ABCD-EFGH-JKMN
return implode('-', str_split($code, 4));
}
Da random_int() kryptografisch sicher ist, verwende immer diese Funktion statt rand(). Beim Speichern des generierten Codes fängt die UNIQUE-Beschränkung in der Datenbank Kollisionen ab; tritt eine auf, generierst du einfach neu und versuchst es erneut.
Der Einlöse-Ablauf (Webpanel)
Nach dem Einloggen sendet der Spieler den Code ab. Die serverseitige Logik muss innerhalb einer einzigen Transaktion laufen, damit die Belohnung nicht zweimal gewährt wird, wenn zwei Anfragen gleichzeitig eintreffen:
$pdo->beginTransaction();
$stmt = $pdo->prepare(
'SELECT * FROM gift_codes WHERE code = ? FOR UPDATE'
);
$stmt->execute([$code]);
$gc = $stmt->fetch();
if (!$gc) {
throw new Exception('Ungültiger Code.');
}
if ($gc['expires_at'] !== null && strtotime($gc['expires_at']) < time()) {
throw new Exception('Dieser Code ist abgelaufen.');
}
if ($gc['used_count'] >= $gc['max_uses']) {
throw new Exception('Dieser Code ist aufgebraucht.');
}
// hat dieses Konto ihn bereits eingelöst?
$ins = $pdo->prepare(
'INSERT INTO gift_redemptions (code_id, account_id) VALUES (?, ?)'
);
try {
$ins->execute([$gc['id'], $accountId]);
} catch (PDOException $e) {
throw new Exception('Du hast diesen Code bereits verwendet.');
}
$pdo->prepare('UPDATE gift_codes SET used_count = used_count + 1 WHERE id = ?')
->execute([$gc['id']]);
grantReward($pdo, $gc['reward_id'], $accountId);
$pdo->commit();
Die Zeile SELECT ... FOR UPDATE sperrt die betreffende Code-Zeile für die Dauer der Transaktion und verhindert so Race Conditions (gleichzeitiges zweifaches Einlösen). Die „bereits eingelöst"-Prüfung einem mit try/catch abgefangenen UNIQUE-Fehler zu überlassen, ist sicherer als ein separates SELECT, das dazwischen ein offenes Zeitfenster lässt.
Die Belohnung an den Spieler ausliefern
Yang zu gewähren ist einfach; du erhöhst die gold-Spalte des Spielers. Ist der Spieler in diesem Moment jedoch im Spiel, kann die über die Webseite vorgenommene Änderung von der In-Memory-Kopie auf dem game-Server überschrieben werden und verloren gehen. Deshalb ist es die korrekte Methode, Item- und Yang-Belohnungen in das Geschenk-/Mail-System von Metin2 zu schreiben; öffnet der Spieler die Box aus dem Spiel heraus, landet die Belohnung sicher im Inventar.
function grantReward(PDO $pdo, int $rewardId, int $accountId): void {
$reward = $pdo->query(
"SELECT * FROM gift_rewards WHERE id = $rewardId"
)->fetch();
// die Yang-Belohnung zur Geschenkbox hinzufügen
if ($reward['yang'] > 0) {
addMailMoney($accountId, (int)$reward['yang']);
}
// die Items zur Geschenkbox hinzufügen
$items = $pdo->prepare(
'SELECT * FROM gift_reward_items WHERE reward_id = ?'
);
$items->execute([$rewardId]);
foreach ($items as $it) {
addMailItem($accountId, (int)$it['item_vnum'], (int)$it['count']);
}
}
Die Funktionen addMailMoney und addMailItem fügen hier Zeilen in die Geschenk-/Mail-Tabellen deiner Server-Distribution ein. Welche Tabellen und Spalten du verwendest, hängt von deiner Server-Quelle (Revision) ab; am robustesten ist es daher, die vorhandene „Item-Shop-Auslieferungs"-Logik in deiner eigenen Quelle als Referenz zu nehmen.
Sicherheit und Missbrauchsprävention
- Rate Limiting: Begrenze Versuche auf wenige pro Minute je IP und Konto; sonst können Bots den Code-Pool per Brute Force durchsuchen.
- Lange, zufällige Codes: Mit
random_intgenerierte 12-Zeichen-Codes sind praktisch unmöglich zu erraten. - Protokollierung: Erfasse jede Einlösung mit IP, Konto und Zeitstempel; so erkennst du verdächtige Massenversuche.
- Ablauf- und Mengenlimits: Gib Event-Codes immer ein
expires_atund ein sinnvollesmax_uses.
Häufige Fragen
Kann ich die Belohnung direkt ins Inventar des Spielers schreiben?
Technisch ja, aber wenn der Spieler in diesem Moment online ist, überschreiben die In-Memory-Daten des game-Servers die Web-Änderung und die Belohnung geht verloren. Die Nutzung der Geschenk-/Mailbox beseitigt dieses Problem vollständig.
Wie viele Personen können einen einzelnen Code verwenden?
Das entscheidet die Spalte max_uses. Für persönliche Codes setzt du 1, für allgemeine Event-Codes etwa 100. Dank der eindeutigen Beschränkung in der Tabelle gift_redemptions kann dasselbe Konto denselben Code dennoch nur einmal verwenden.
Kann ich Codes in großen Mengen generieren?
Ja. Wenn du die Generierungsfunktion in einer Schleife aufrufst und jeden Code in die Tabelle gift_codes einfügst, fängt die UNIQUE-Beschränkung mögliche Kollisionen ab. Du generierst den kollidierenden Code einfach neu und fügst ihn ein.
Möchtest du ein sicheres Gift-Code-System auf deinem Server einrichten? Vom Datenbankentwurf über das Webpanel bis zur Belohnungsauslieferungs-Logik kann ich eine Lösung entwickeln, die zu deiner Metin2-Infrastruktur passt. Kontaktiere mich, um über dein Projekt zu sprechen.