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

Metin2 Gift Code Sistemi: Hediye Kodu Yapımı

Bir metin2 gift code sistemi, oyuncuya tek kullanımlık bir kod verip karşılığında oyun içi ödül (yang, eşya, EXP iksiri ya da özel kostüm) tanımlayan basit ama çok işe yarayan bir mekanizmadır. Discord etkinliklerinde, YouTube videolarında ya da yeni oyuncuları çekmek için dağıtılan bu kodlar, doğru kurulduğunda hem oyuncuyu mutlu eder hem de istismara kapalı kalır. Bu yazıda kodun veritabanı tasarımından üretimine, web panelinden kullanılmasına ve ödülün oyuncuya teslim edilmesine kadar tüm akışı anlatıyorum.

Sistem nasıl çalışır?

Metin2'de hesap (account) ve oyuncu (player) verileri MySQL üzerinde tutulur. Gift code akışı tipik olarak şöyle ilerler:

  • Yönetici panelden bir ödül seti tanımlar (örneğin 1.000.000 yang + 50 EXP iksiri).
  • Sistem bu ödüle bağlı bir veya birçok benzersiz kod üretir.
  • Oyuncu web sitesindeki "Kod Kullan" alanına kodu girer.
  • Sunucu kodu doğrular, daha önce kullanılıp kullanılmadığını kontrol eder ve ödülü oyuncunun hesabına aktarır.

Ödülü oyuncuya teslim ederken doğrudan envanterine eşya basmak yerine Metin2'nin mail / hediye kutusu mantığını kullanmak en güvenlisidir; böylece oyuncu çevrimdışıyken bile ödül kaybolmaz.

Veritabanı tablolarını tasarlama

Tüm sistemin kalbi iki tablodur: ödülleri tutan gift_rewards ve kodları tutan gift_codes. Aşağıdaki şema, bir koda birden çok ödül bağlayabilmek için araya bir ilişki tablosu da koyar.

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

Buradaki UNIQUE KEY uq_code_account kritik: aynı hesabın aynı kodu iki kez kullanmasını veritabanı seviyesinde engeller. max_uses ise tek bir kodu kaç farklı kişinin kullanabileceğini belirler (etkinlik kodları için "100 kişi" gibi).

Benzersiz kod üretme

Kodlar tahmin edilemez ve okunması kolay olmalı. Karışıklığa yol açan 0/O ve 1/I/L gibi karakterleri çıkarıp büyük harf bir alfabe kullanmak iyi bir pratiktir. PHP ile güvenli üretim:

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)];
    }
    // okunabilirlik için gruplama: ABCD-EFGH-JKMN
    return implode('-', str_split($code, 4));
}

random_int() kriptografik olarak güvenli olduğu için rand() yerine mutlaka onu kullanın. Üretilen kodu kaydederken veritabanındaki UNIQUE kısıtı çakışmaları yakalar; çakışma olursa basitçe yeniden üretip tekrar denersiniz.

Kodu kullanma akışı (web paneli)

Oyuncu giriş yaptıktan sonra kodu gönderir. Sunucu tarafındaki mantık tek bir transaction içinde çalışmalı ki iki istek aynı anda geldiğinde ödül iki kez verilmesin:

$pdo->beginTransaction();

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

if (!$gc) {
    throw new Exception('Geçersiz kod.');
}
if ($gc['expires_at'] !== null && strtotime($gc['expires_at']) < time()) {
    throw new Exception('Bu kodun süresi dolmuş.');
}
if ($gc['used_count'] >= $gc['max_uses']) {
    throw new Exception('Bu kod tükenmiş.');
}

// hesap daha önce kullanmış mı?
$ins = $pdo->prepare(
    'INSERT INTO gift_redemptions (code_id, account_id) VALUES (?, ?)'
);
try {
    $ins->execute([$gc['id'], $accountId]);
} catch (PDOException $e) {
    throw new Exception('Bu kodu zaten kullandın.');
}

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

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

SELECT ... FOR UPDATE satırı, transaction süresince ilgili kod satırını kilitleyerek yarış koşullarını (aynı anda iki kez kullanma) engeller. Hesabın daha önce kullanıp kullanmadığı kontrolünü try/catch ile UNIQUE hatasına bırakmak, ayrı bir SELECT yapıp arada açık kalan zaman penceresinden daha güvenlidir.

Ödülü oyuncuya teslim etme

Yang vermek basittir; ilgili oyuncunun gold sütununu artırırsınız. Ancak oyuncu o an oyundaysa, web tarafından yapılan değişiklik game sunucusundaki bellek kopyasının üzerine yazılarak kaybolabilir. Bu yüzden eşya ve yang ödüllerini Metin2'nin hediye/mail sistemine yazmak en doğru yöntemdir; oyuncu kutuyu oyun içinden açtığında ödül güvenle envantere geçer.

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

    // Yang ödülünü hediye kutusuna ekle
    if ($reward['yang'] > 0) {
        addMailMoney($accountId, (int)$reward['yang']);
    }

    // Eşyaları hediye kutusuna ekle
    $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']);
    }
}

Buradaki addMailMoney ve addMailItem fonksiyonları, kullandığınız sunucu dağıtımının hediye/mail tablolarına satır ekler. Hangi tablo ve sütunları kullandığınız, sunucu kaynağınıza (revizyon) göre değişir; bu yüzden kendi kaynağınızdaki mevcut "item shop teslim" mantığını referans almanız en sağlamı.

Güvenlik ve kötüye kullanım önleme

  • Hız sınırı (rate limit): IP ve hesap başına dakikada birkaç deneme ile sınırlayın; aksi halde botlar kod havuzunu brute-force ile tarayabilir.
  • Uzun ve rastgele kodlar: 12 karakter ve random_int ile üretilen kodların tahmin edilmesi pratikte imkânsızdır.
  • Loglama: Her kullanımı IP, hesap ve zaman damgasıyla kaydedin; şüpheli toplu denemeleri böyle yakalarsınız.
  • Süre ve adet limiti: Etkinlik kodlarına mutlaka expires_at ve makul bir max_uses verin.

Sık Sorulan Sorular

Ödülü doğrudan oyuncunun envanterine basabilir miyim?

Teknik olarak mümkün, ancak oyuncu o sırada çevrimiçiyse game sunucusunun bellekteki verisi web değişikliğinin üzerine yazar ve ödül kaybolur. Hediye/mail kutusu kullanmak bu sorunu tamamen ortadan kaldırır.

Tek bir kodu kaç kişi kullanabilir?

Bunu max_uses sütunu belirler. Kişiye özel kodlar için 1, genel etkinlik kodları için örneğin 100 verirsiniz. gift_redemptions tablosundaki benzersiz kısıt sayesinde aynı hesap aynı kodu yine de yalnızca bir kez kullanabilir.

Kodları toplu üretebilir miyim?

Evet. Üretici fonksiyonu bir döngüde çağırıp her birini gift_codes tablosuna eklerseniz, UNIQUE kısıtı olası çakışmaları yakalar. Çakışan kodu yeniden üretip eklemeniz yeterli.

Sunucunuza güvenli bir gift code sistemi mi kurmak istiyorsunuz? Veritabanı tasarımından web paneli ve ödül teslim mantığına kadar Metin2 altyapınıza uygun bir çözüm geliştirebilirim. Projenizi konuşmak için benimle iletişime geçin.

Bu kategorideki tüm yazılar →

Devamı için