aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Metin2

Système Metin2 Gift Code : créer des codes cadeaux

Un système de metin2 gift code est un mécanisme simple mais très utile qui remet au joueur un code à usage unique et accorde en échange une récompense en jeu (yang, objets, potions d'EXP ou un costume exclusif). Distribués lors d'événements Discord, dans des vidéos YouTube ou pour attirer de nouveaux joueurs, ces codes font plaisir aux joueurs tout en restant résistants aux abus lorsqu'ils sont bien conçus. Dans cet article, je détaille tout le flux : de la conception de la base de données et la génération du code jusqu'à l'utilisation sur le panneau web et la remise de la récompense au joueur.

Comment fonctionne le système

Dans Metin2, les données de compte (account) et de joueur (player) résident dans MySQL. Le flux d'un gift code se déroule généralement ainsi :

  • Un administrateur définit un ensemble de récompenses dans le panneau (par exemple 1 000 000 de yang + 50 potions d'EXP).
  • Le système génère un ou plusieurs codes uniques liés à cette récompense.
  • Le joueur saisit le code dans le champ « Utiliser un code » du site web.
  • Le serveur valide le code, vérifie s'il a déjà été utilisé et transfère la récompense sur le compte du joueur.

Pour remettre la récompense, l'approche la plus sûre consiste à utiliser la logique de mail / boîte cadeau de Metin2 plutôt que d'écrire les objets directement dans l'inventaire ; ainsi la récompense n'est jamais perdue, même lorsque le joueur est hors ligne.

Concevoir les tables de la base de données

Le cœur de tout le système, ce sont deux tables : gift_rewards qui contient les récompenses et gift_codes qui contient les codes. Le schéma ci-dessous ajoute aussi une table de relation pour qu'un seul code puisse porter plusieurs objets en récompense.

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)
);

La UNIQUE KEY uq_code_account est ici cruciale : elle empêche, au niveau de la base de données, qu'un même compte utilise deux fois le même code. max_uses définit combien de personnes différentes peuvent utiliser un même code (du type « 100 personnes » pour les codes d'événement).

Générer des codes uniques

Les codes doivent être imprévisibles et faciles à lire. Une bonne pratique consiste à supprimer les caractères ambigus comme 0/O et 1/I/L et à utiliser un alphabet en majuscules. Génération sécurisée en 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)];
    }
    // regrouper pour la lisibilité : ABCD-EFGH-JKMN
    return implode('-', str_split($code, 4));
}

Comme random_int() est cryptographiquement sûr, utilisez-le toujours à la place de rand(). Lorsque vous enregistrez le code généré, la contrainte UNIQUE de la base de données détecte les collisions ; en cas de collision, il suffit de régénérer et de réessayer.

Le flux d'utilisation (panneau web)

Après s'être connecté, le joueur soumet le code. La logique côté serveur doit s'exécuter dans une seule transaction afin que la récompense ne soit pas accordée deux fois lorsque deux requêtes arrivent en même temps :

$pdo->beginTransaction();

$stmt = $pdo->prepare(
    'SELECT * FROM gift_codes WHERE code = ? FOR UPDATE'
);
$stmt->execute([$code]);
$gc = $stmt->fetch();

if (!$gc) {
    throw new Exception('Code invalide.');
}
if ($gc['expires_at'] !== null && strtotime($gc['expires_at']) < time()) {
    throw new Exception('Ce code a expiré.');
}
if ($gc['used_count'] >= $gc['max_uses']) {
    throw new Exception('Ce code est épuisé.');
}

// ce compte l'a-t-il déjà utilisé ?
$ins = $pdo->prepare(
    'INSERT INTO gift_redemptions (code_id, account_id) VALUES (?, ?)'
);
try {
    $ins->execute([$gc['id'], $accountId]);
} catch (PDOException $e) {
    throw new Exception('Vous avez déjà utilisé ce code.');
}

$pdo->prepare('UPDATE gift_codes SET used_count = used_count + 1 WHERE id = ?')
    ->execute([$gc['id']]);

grantReward($pdo, $gc['reward_id'], $accountId);
$pdo->commit();

La ligne SELECT ... FOR UPDATE verrouille la ligne du code concernée pendant la durée de la transaction, ce qui évite les conditions de concurrence (utilisation simultanée deux fois). Laisser la vérification « déjà utilisé » à une erreur UNIQUE interceptée par try/catch est plus sûr que de faire un SELECT séparé, qui laisse une fenêtre temporelle ouverte entre les deux.

Remettre la récompense au joueur

Accorder du yang est simple ; vous augmentez la colonne gold du joueur. Mais si le joueur est en jeu à ce moment-là, la modification effectuée côté web peut être écrasée par la copie en mémoire du serveur game et perdue. C'est pourquoi écrire les récompenses en objets et en yang dans le système cadeau/mail de Metin2 est la bonne méthode ; lorsque le joueur ouvre la boîte depuis le jeu, la récompense arrive en toute sécurité dans l'inventaire.

function grantReward(PDO $pdo, int $rewardId, int $accountId): void {
    $reward = $pdo->query(
        "SELECT * FROM gift_rewards WHERE id = $rewardId"
    )->fetch();

    // ajouter la récompense en yang à la boîte cadeau
    if ($reward['yang'] > 0) {
        addMailMoney($accountId, (int)$reward['yang']);
    }

    // ajouter les objets à la boîte cadeau
    $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']);
    }
}

Les fonctions addMailMoney et addMailItem insèrent ici des lignes dans les tables cadeau/mail de votre distribution serveur. Les tables et colonnes que vous utilisez dépendent de votre source serveur (révision) ; l'approche la plus robuste est donc de vous référer à la logique de « livraison de l'item shop » déjà présente dans votre propre source.

Sécurité et prévention des abus

  • Limitation de débit : limitez les tentatives à quelques-unes par minute par IP et par compte ; sinon des bots peuvent scanner par force brute le pool de codes.
  • Codes longs et aléatoires : des codes de 12 caractères générés avec random_int sont pratiquement impossibles à deviner.
  • Journalisation : enregistrez chaque utilisation avec l'IP, le compte et l'horodatage ; c'est ainsi que vous repérez les tentatives massives suspectes.
  • Limites de durée et de quantité : donnez toujours aux codes d'événement un expires_at et un max_uses raisonnable.

Questions fréquentes

Puis-je écrire la récompense directement dans l'inventaire du joueur ?

Techniquement oui, mais si le joueur est en ligne à ce moment-là, les données en mémoire du serveur game écrasent la modification web et la récompense est perdue. Utiliser la boîte cadeau/mail élimine totalement ce problème.

Combien de personnes peuvent utiliser un même code ?

C'est la colonne max_uses qui le décide. Vous mettez 1 pour les codes personnels et, par exemple, 100 pour les codes d'événement généraux. Grâce à la contrainte unique de la table gift_redemptions, un même compte ne peut malgré tout utiliser le même code qu'une seule fois.

Puis-je générer des codes en masse ?

Oui. Si vous appelez la fonction de génération dans une boucle et insérez chacun dans la table gift_codes, la contrainte UNIQUE détecte toute collision. Il suffit de régénérer et d'insérer le code en conflit.

Vous voulez mettre en place un système de gift code sécurisé sur votre serveur ? De la conception de la base de données au panneau web et à la logique de remise des récompenses, je peux développer une solution adaptée à votre infrastructure Metin2. Contactez-moi pour parler de votre projet.

Bu kategorideki tüm yazılar →

Devamı için