Home / Developers / Lineage 2
🏰

Lineage 2

Java L2J / L2jMobius

Overview

Reward your Lineage 2 private server players for voting on topgservers. This guide covers a custom Java handler for L2J/L2jMobius emulators that polls the vote-check API and grants adena, items, or noblesse status. A Python sidecar handles real-time webhooks.

Prerequisites

  • A Lineage 2 server running L2J, L2jMobius, or compatible Java emulator
  • Java 11 or newer (17 recommended)
  • A topgservers API key — generate one in My Servers → API
  • Your webhook secret (optional, for real-time rewards)
  • Port 443 outbound access from your server for HTTPS calls

Installation

1 Create the handler class

Add a new Java class to your game server source. The handler registers a voice command (.topgvote) and a background polling task.

Directory structure
gameserver/java/org/l2j/gameserver/custom/
├── TopGVoteHandler.java
├── TopGVoteTask.java
└── TopGWebhookReceiver.java (optional)

2 Implement the vote handler

The handler exposes a voiced command for players to manually check their vote status and claim rewards.

TopGVoteHandler.java
package org.l2j.gameserver.custom;

import org.l2j.gameserver.handler.IVoicedCommandHandler;
import org.l2j.gameserver.model.actor.instance.PlayerInstance;

import java.net.URI;
import java.net.http.*;

public class TopGVoteHandler implements IVoicedCommandHandler {
    private static final String API_KEY = "tgs_your_api_key_here";
    private static final String[] COMMANDS = { "topgvote" };
    private final HttpClient http = HttpClient.newHttpClient();

    @Override
    public boolean useVoicedCommand(String command, PlayerInstance player,
                                     String params) {
        if (!"topgvote".equals(command)) return false;

        try {
            HttpRequest req = HttpRequest.newBuilder()
                .uri(URI.create(
                    "https://topgservers.net/api/v1/vote-check?username="
                    + player.getName()))
                .header("Authorization", "Bearer " + API_KEY)
                .GET().build();

            http.sendAsync(req, HttpResponse.BodyHandlers.ofString())
                .thenAccept(res -> {
                    if (res.statusCode() == 200
                        && res.body().contains("\"voted\":true")) {
                        grantReward(player);
                        player.sendMessage(
                            "Thanks for voting on topgservers! Rewards granted.");
                    } else {
                        player.sendMessage(
                            "No vote found. Visit topgservers.net to vote!");
                    }
                });
        } catch (Exception e) {
            player.sendMessage("Vote check failed. Try again later.");
        }

        return true;
    }

    private void grantReward(PlayerInstance player) {
        // 500k adena
        player.addAdena("TopGVote", 500000, null, true);
        // 5x Scroll of Enchant Weapon (D)
        player.addItem("TopGVote", 955, 5, null, true);
    }

    @Override
    public String[] getVoicedCommandList() {
        return COMMANDS;
    }
}

3 Add background polling task

Schedule a task that automatically checks votes for all online players every 60 seconds.

TopGVoteTask.java
package org.l2j.gameserver.custom;

import org.l2j.gameserver.model.World;
import org.l2j.gameserver.model.actor.instance.PlayerInstance;

import java.net.URI;
import java.net.http.*;
import java.util.*;
import java.util.concurrent.*;

public class TopGVoteTask implements Runnable {
    private static final String API_KEY = "tgs_your_api_key_here";
    private final HttpClient http = HttpClient.newHttpClient();
    private final Set<String> rewarded =
        ConcurrentHashMap.newKeySet();

    public void schedule() {
        ScheduledExecutorService exec =
            Executors.newSingleThreadScheduledExecutor();
        exec.scheduleAtFixedRate(this, 30, 60, TimeUnit.SECONDS);
    }

    @Override
    public void run() {
        for (PlayerInstance player : World.getInstance().getPlayers()) {
            String name = player.getName();
            if (rewarded.contains(name)) continue;

            try {
                HttpRequest req = HttpRequest.newBuilder()
                    .uri(URI.create(
                        "https://topgservers.net/api/v1/vote-check?username="
                        + name))
                    .header("Authorization", "Bearer " + API_KEY)
                    .GET().build();

                HttpResponse<String> res =
                    http.send(req, HttpResponse.BodyHandlers.ofString());

                if (res.statusCode() == 200
                    && res.body().contains("\"voted\":true")) {
                    rewarded.add(name);
                    player.addAdena("TopGVote", 500000, null, true);
                    player.addItem("TopGVote", 955, 5, null, true);
                    player.sendMessage(
                        "Thanks for voting on topgservers! Rewards granted.");
                }
            } catch (Exception e) {
                // Silently skip — will retry next cycle
            }
        }
    }

    /** Call at midnight to allow re-voting */
    public void resetDaily() {
        rewarded.clear();
    }
}

Configuration

TopGVoteHandler.java (config constants)
// Configuration
private static final String API_KEY         = "tgs_your_api_key_here";
private static final String WEBHOOK_SECRET  = "whsec_your_secret_here";

// Rewards
private static final int REWARD_ADENA       = 500000;     // adena
private static final int REWARD_ITEM_ID     = 955;        // EWD scroll
private static final int REWARD_ITEM_COUNT  = 5;

// Polling
private static final int CHECK_INTERVAL     = 60;         // seconds

Vote Check

Call the /api/v1/vote-check endpoint to determine if a player has voted today.

Vote check (Java HttpClient)
HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create(
        "https://topgservers.net/api/v1/vote-check?username=" + playerName))
    .header("Authorization", "Bearer " + API_KEY)
    .GET().build();

HttpResponse<String> res =
    http.send(req, HttpResponse.BodyHandlers.ofString());

if (res.statusCode() == 200 && res.body().contains("\"voted\":true")) {
    // Player voted today — grant reward
}

Webhook Receiver

Verify the X-TopG-Signature header using HMAC-SHA256 to ensure webhook payloads are authentic.

webhook_receiver.py (sidecar)
"""
Webhook receiver for Lineage 2 servers.
Run alongside your game server. Inserts into topg_votes table
so the Java handler can pick up rewards on next check.
"""
import hmac, hashlib, json, mysql.connector
from http.server import HTTPServer, BaseHTTPRequestHandler

WEBHOOK_SECRET = "whsec_your_secret_here"
DB_CONFIG = {
    "host": "127.0.0.1",
    "user": "l2j",
    "password": "password",
    "database": "l2jgs"
}

class WebhookHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        length = int(self.headers.get('Content-Length', 0))
        body = self.rfile.read(length).decode()
        sig = self.headers.get('X-TopG-Signature', '')

        if not verify_signature(body, sig):
            self.send_response(401)
            self.end_headers()
            return

        data = json.loads(body)
        username = data.get('username', '')

        if username:
            db = mysql.connector.connect(**DB_CONFIG)
            cursor = db.cursor()
            cursor.execute(
                "INSERT IGNORE INTO topg_votes (name, vote_date) "
                "VALUES (%s, CURDATE())",
                (username,)
            )
            db.commit()
            db.close()

        self.send_response(200)
        self.end_headers()

def verify_signature(body, sig_header):
    parts = dict(p.split('=', 1) for p in sig_header.split(',') if '=' in p)
    timestamp = parts.get('t', '')
    received = parts.get('v1', '')

    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        f"{timestamp}.{body}".encode(),
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(expected, received)

if __name__ == '__main__':
    server = HTTPServer(('0.0.0.0', 8090), WebhookHandler)
    print("Webhook receiver listening on :8090")
    server.serve_forever()

Reward Examples

Reward examples (L2J)
// Adena
player.addAdena("TopGVote", 500000, null, true);

// Items — addItem(process, itemId, count, reference, sendMessage)
player.addItem("TopGVote", 955, 5, null, true);    // EWD scroll x5
player.addItem("TopGVote", 6673, 10, null, true);  // Festival Adena x10

// Noblesse status
player.setNoble(true);
player.sendMessage("You have been granted Noblesse status!");

// Class-specific buff
player.getEffectList().stopAllEffects();
// or use SkillTable for custom buffs:
// SkillTable.getInstance().getSkill(1204, 2).applyEffects(player, player);

// Announce server-wide
Announcements.getInstance().announceToAll(
    player.getName() + " voted on topgservers and received rewards!");

Notes & Tips

L2J and L2jMobius class names may differ slightly between versions. Adjust package paths and class names to match your source. For the webhook approach, create the votes table with: `CREATE TABLE topg_votes (name VARCHAR(50), vote_date DATE, rewarded TINYINT DEFAULT 0, PRIMARY KEY(name, vote_date));` Register the handler in your GameServer init or custom loader.