Download Oficial
Baixe o L2 API Bridge
Escolha a versao adequada para seu servidor e comece a integrar sua API em minutos.
Todas as Versoes
v1.0.0
07/01/2026Release Inicial
3 downloads
Configuracao
config.properties
# ============================================
# L2 API Bridge - Configurações
# Desenvolvido por: TurtleLess
# https://qesta.com.br
# ============================================
# Porta do servidor HTTP
# Recomendado: 8080 para desenvolvimento, outra para produção
SERVER_PORT=8080
# JWT Secret para autenticação HMAC-SHA256
# IMPORTANTE: Gere um secret forte e único para produção!
# Mínimo 32 caracteres. Use geradores online ou comandos:
# Linux/Mac: openssl rand -base64 32
# Windows: Use geradores online
JWT_SECRET=CHANGE_THIS_TO_A_SECURE_RANDOM_STRING_MIN_32_CHARS
# IPs permitidos (whitelist)
# Separe múltiplos IPs por vírgula
# Exemplos:
# - IP único: 192.168.1.100
# - Múltiplos IPs: 192.168.1.100,192.168.1.101
# - CIDR: 192.168.1.0/24 (permite toda a rede)
# - Permitir todos: * ou 0.0.0.0/0 (NÃO recomendado em produção!)
# Padrão em desenvolvimento: localhost
ALLOWED_IPS=127.0.0.1,::1
# Rate Limiting (requisições por minuto por IP)
# 0 = desabilitado
# Recomendado: 60-120 para produção
RATE_LIMIT_PER_MINUTE=60
# Habilitar logging detalhado
# true = habilitar logs completos
# false = apenas logs críticos
ENABLE_LOGGING=true
# Nível de log (SEVERE, WARNING, INFO, FINE, FINER, FINEST)
# Produção: WARNING
# Desenvolvimento: INFO ou FINE
LOG_LEVEL=INFO
# Habilitar métricas de performance
# true = coletar métricas (em desenvolvimento futuro)
# false = desabilitado
ENABLE_METRICS=false
# ============================================
# INSTRUÇÕES DE USO
# ============================================
#
# 1. Copie este arquivo para: l2apibridge.properties
# (remova o .example)
#
# 2. Configure o JWT_SECRET com um valor forte:
# Comando Linux/Mac: openssl rand -base64 32
# Ou use: https://www.grc.com/passwords.htm
#
# 3. Ajuste ALLOWED_IPS conforme sua rede:
# - Desenvolvimento local: 127.0.0.1,::1
# - Produção: IPs específicos do seu servidor
#
# 4. Em produção, sempre use:
# - JWT_SECRET único e forte
# - ALLOWED_IPS específicos (nunca *)
# - RATE_LIMIT_PER_MINUTE >= 60
# - LOG_LEVEL=WARNING
# - HTTPS com proxy reverso (nginx/apache)
Adapter Java
L2ApiBridgeAdapter.java
package custom;
// =====================================================================
// ███████╗ ÁREA DE CONFIGURAÇÃO DO ADMIN - IMPORTS ███████╗
// =====================================================================
//
// ATENÇÃO: Altere os imports abaixo conforme o CORE do seu servidor!
//
// ┌─────────────────────────────────────────────────────────────────┐
// │ SERVIDOR ESTILO aCis / L2J Official / L2JOrg │
// ├─────────────────────────────────────────────────────────────────┤
// │ Pacote base: net.sf.l2j.* │
// │ Classes principais: │
// │ - L2World │
// │ - L2PcInstance (jogador) │
// │ - Announcements │
// │ - ItemTable │
// │ - ThreadPoolManager │
// └─────────────────────────────────────────────────────────────────┘
//
// SE O SEU SERVIDOR FOR MOBIUS, COMENTE AS LINHAS ABAIXO E
// DESCOMENTE A SEÇÃO "MOBIUS" MAIS ABAIXO!
// ★ aCis / L2J Official / L2JOrg - IMPORTS PADRÃO (ATIVE ESTES) ★
import net.sf.l2j.gameserver.model.L2World;
import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
import net.sf.l2j.gameserver.Announcements;
import net.sf.l2j.gameserver.data.ItemTable;
import net.sf.l2j.gameserver.ThreadPoolManager;
import net.sf.l2j.gameserver.model.L2Skill;
import net.sf.l2j.gameserver.data.SkillTable;
import net.sf.l2j.gameserver.model.item.instance.ItemInstance;
import net.sf.l2j.gameserver.network.serverpackets.CharInfo;
import net.sf.l2j.gameserver.network.serverpackets.UserInfo;
import net.sf.l2j.gameserver.network.serverpackets.CreatureSay;
import net.sf.l2j.gameserver.network.SystemMessageId;
import net.sf.l2j.gameserver.model.actor.Player;
// ★ MOBIUS - IMPORTS ALTERNATIVOS (COMENTE OS ACIMA E DESCOMENTE ESTES) ★
// import org.l2jmobius.gameserver.model.World;
// import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance;
// import org.l2jmobius.gameserver.util.Broadcast;
// import org.l2jmobius.gameserver.data.ItemTable;
// import org.l2jmobius.commons.threads.ThreadPool;
// import org.l2jmobius.gameserver.model.Skill;
// import org.l2jmobius.gameserver.data.xml.SkillData;
// import org.l2jmobius.gameserver.model.items.instance.Item;
// import org.l2jmobius.gameserver.network.serverpackets.CharInfo;
// import org.l2jmobius.gameserver.network.serverpackets.UserInfo;
// import org.l2jmobius.gameserver.network.serverpackets.CreatureSay;
// import org.l2jmobius.gameserver.network.SystemMessageId;
// =====================================================================
// ███████╗ FIM DA ÁREA DE CONFIGURAÇÃO ███████╗
// =====================================================================
import br.com.l2apibridge.api.IGameServerBridge;
import br.com.l2apibridge.core.BridgeManager;
import java.util.Collection;
import java.util.logging.Logger;
/**
* L2ApiAdapter - Adaptador entre o L2ApiCore e o Game Server Lineage 2
*
* Este script deve ser colocado em: data/scripts/custom/L2ApiAdapter.java
*
* O servidor L2 compilará este script automaticamente ao iniciar.
* Ele registra todas as funções do IGameServerBridge com o Core da API.
*
* @author TurtleLess (https://qesta.com.br)
* @version 2.0 - Fase 2: Adapter Implementation
*/
public class L2ApiAdapter implements IGameServerBridge {
private static final Logger LOGGER = Logger.getLogger(L2ApiAdapter.class.getName());
/**
* Construtor - Registra automaticamente este adapter no BridgeManager
*/
public L2ApiAdapter() {
try {
// Conecta este adapter ao Core da API
BridgeManager.getInstance().registerAdapter(this);
LOGGER.info("═══════════════════════════════════════════════════");
LOGGER.info(" L2ApiAdapter v2.0 - Successfully Registered!");
LOGGER.info(" Core: L2ApiBridge by TurtleLess");
LOGGER.info(" Website: https://qesta.com.br");
LOGGER.info("═══════════════════════════════════════════════════");
} catch (Exception e) {
LOGGER.severe("ERRO CRÍTICO: Não foi possível registrar o adapter!");
LOGGER.severe("Verifique se o JAR do L2ApiBridge está no classpath!");
e.printStackTrace();
}
}
/**
* Método main para compatibilidade com engines antigas de script
*/
public static void main(String[] args) {
new L2ApiAdapter();
}
// ═════════════════════════════════════════════════════════════════
// MÉTODOS AUXILIARES INTERNOS
// ═════════════════════════════════════════════════════════════════
/**
* Obtém um jogador online pelo nome.
* IMPORTANTE: Este método é thread-safe para leitura.
*/
private L2PcInstance getPlayer(String charName) {
if (charName == null || charName.trim().isEmpty()) {
return null;
}
// aCis/L2J: L2World.getInstance().getPlayer(charName)
// Mobius: World.getInstance().getPlayer(charName)
return L2World.getInstance().getPlayer(charName);
}
/**
* Executa uma tarefa na Thread Principal do Game Server.
* CRÍTICO: Qualquer modificação de world/inventory DEVE usar este método!
*/
private void executeInGameThread(Runnable task) {
// aCis/L2J: ThreadPoolManager.getInstance().execute(task)
// Mobius: ThreadPool.execute(task)
ThreadPoolManager.getInstance().execute(task);
}
// ═════════════════════════════════════════════════════════════════
// IMPLEMENTAÇÃO DA INTERFACE IGameServerBridge
// ═════════════════════════════════════════════════════════════════
@Override
public boolean giveItem(String charName, int itemId, long count) {
L2PcInstance player = getPlayer(charName);
if (player == null) {
LOGGER.warning("giveItem: Player '" + charName + "' não encontrado!");
return false;
}
// THREAD SAFETY: Executar na thread principal do game
executeInGameThread(() -> {
try {
player.addItem("L2ApiBridge", itemId, count, player, true);
LOGGER.info("Item concedido: " + itemId + " x" + count + " para " + charName);
} catch (Exception e) {
LOGGER.severe("Erro ao conceder item: " + e.getMessage());
e.printStackTrace();
}
});
return true; // Comando foi enfileirado com sucesso
}
@Override
public void announce(String message) {
if (message == null || message.trim().isEmpty()) {
return;
}
executeInGameThread(() -> {
try {
// aCis/L2J: Announcements.getInstance().announceToAll(message)
// Mobius: Broadcast.toAllOnlinePlayers(message)
Announcements.getInstance().announceToAll(message);
LOGGER.info("Anúncio enviado: " + message);
} catch (Exception e) {
LOGGER.severe("Erro ao enviar anúncio: " + e.getMessage());
}
});
}
@Override
public boolean isPlayerOnline(String charName) {
return getPlayer(charName) != null;
}
@Override
public String getPlayerInfo(String charName) {
L2PcInstance player = getPlayer(charName);
if (player == null) {
return null;
}
try {
// Construir JSON manualmente (evita dependência do Gson no script)
StringBuilder json = new StringBuilder();
json.append("{");
json.append("\"name\":\"").append(player.getName()).append("\",");
json.append("\"level\":").append(player.getLevel()).append(",");
json.append("\"class\":\"").append(player.getTemplate().getClassName()).append("\",");
json.append("\"hp\":").append((int) player.getCurrentHp()).append(",");
json.append("\"maxHp\":").append(player.getMaxHp()).append(",");
json.append("\"mp\":").append((int) player.getCurrentMp()).append(",");
json.append("\"maxMp\":").append(player.getMaxMp()).append(",");
json.append("\"cp\":").append((int) player.getCurrentCp()).append(",");
json.append("\"maxCp\":").append(player.getMaxCp()).append(",");
json.append("\"exp\":").append(player.getExp()).append(",");
json.append("\"sp\":").append(player.getSp()).append(",");
json.append("\"pk\":").append(player.getPkKills()).append(",");
json.append("\"pvp\":").append(player.getPvpKills()).append(",");
json.append("\"online\":true");
json.append("}");
return json.toString();
} catch (Exception e) {
LOGGER.severe("Erro ao obter info do player: " + e.getMessage());
return null;
}
}
@Override
public boolean teleportPlayer(String charName, int x, int y, int z) {
L2PcInstance player = getPlayer(charName);
if (player == null) {
return false;
}
executeInGameThread(() -> {
try {
player.teleToLocation(x, y, z, 0);
LOGGER.info("Player " + charName + " teleportado para " + x + "," + y + "," + z);
} catch (Exception e) {
LOGGER.severe("Erro ao teleportar: " + e.getMessage());
}
});
return true;
}
@Override
public String getServerStatus() {
try {
Collection<L2PcInstance> players = L2World.getInstance().getAllPlayers().values();
int onlineCount = players.size();
Runtime runtime = Runtime.getRuntime();
long maxMemory = runtime.maxMemory() / 1024 / 1024;
long totalMemory = runtime.totalMemory() / 1024 / 1024;
long freeMemory = runtime.freeMemory() / 1024 / 1024;
long usedMemory = totalMemory - freeMemory;
StringBuilder json = new StringBuilder();
json.append("{");
json.append("\"status\":\"online\",");
json.append("\"playersOnline\":").append(onlineCount).append(",");
json.append("\"memoryUsed\":").append(usedMemory).append(",");
json.append("\"memoryMax\":").append(maxMemory).append(",");
json.append("\"uptime\":\"N/A\"");
json.append("}");
return json.toString();
} catch (Exception e) {
LOGGER.severe("Erro ao obter status: " + e.getMessage());
return "{\"status\":\"error\"}";
}
}
@Override
public boolean removeItem(String charName, int itemId, long count) {
L2PcInstance player = getPlayer(charName);
if (player == null) {
return false;
}
executeInGameThread(() -> {
try {
player.destroyItemByItemId("L2ApiBridge", itemId, count, player, true);
LOGGER.info("Item removido: " + itemId + " x" + count + " de " + charName);
} catch (Exception e) {
LOGGER.severe("Erro ao remover item: " + e.getMessage());
}
});
return true;
}
@Override
public String getPlayerInventory(String charName) {
L2PcInstance player = getPlayer(charName);
if (player == null) {
return null;
}
try {
StringBuilder json = new StringBuilder();
json.append("{\"items\":[");
boolean first = true;
for (ItemInstance item : player.getInventory().getItems()) {
if (!first) json.append(",");
json.append("{");
json.append("\"itemId\":").append(item.getItemId()).append(",");
json.append("\"count\":").append(item.getCount()).append(",");
json.append("\"name\":\"").append(item.getName()).append("\",");
json.append("\"enchant\":").append(item.getEnchantLevel());
json.append("}");
first = false;
}
json.append("]}");
return json.toString();
} catch (Exception e) {
LOGGER.severe("Erro ao obter inventário: " + e.getMessage());
return null;
}
}
@Override
public boolean kickPlayer(String charName, String reason) {
L2PcInstance player = getPlayer(charName);
if (player == null) {
return false;
}
executeInGameThread(() -> {
try {
if (reason != null && !reason.isEmpty()) {
player.sendMessage("Você foi desconectado: " + reason);
}
player.logout(false);
LOGGER.info("Player " + charName + " foi kickado. Motivo: " + reason);
} catch (Exception e) {
LOGGER.severe("Erro ao kickar player: " + e.getMessage());
}
});
return true;
}
@Override
public boolean banPlayer(String charName, int duration, String reason) {
L2PcInstance player = getPlayer(charName);
if (player == null) {
return false;
}
executeInGameThread(() -> {
try {
// Implementação básica - pode ser expandida conforme sistema de ban do servidor
LOGGER.warning("banPlayer: Método não implementado completamente!");
LOGGER.warning("Player: " + charName + ", Duração: " + duration + "min, Motivo: " + reason);
// TODO: Implementar sistema de ban do seu servidor aqui
// Exemplo: LoginServerThread.getInstance().sendAccessLevel(charName, -100);
} catch (Exception e) {
LOGGER.severe("Erro ao banir player: " + e.getMessage());
}
});
return true;
}
@Override
public boolean unbanPlayer(String charName) {
executeInGameThread(() -> {
try {
LOGGER.warning("unbanPlayer: Método não implementado completamente!");
LOGGER.warning("Player: " + charName);
// TODO: Implementar sistema de unban do seu servidor aqui
} catch (Exception e) {
LOGGER.severe("Erro ao desbanir player: " + e.getMessage());
}
});
return true;
}
@Override
public boolean sendPrivateMessage(String charName, String message) {
L2PcInstance player = getPlayer(charName);
if (player == null) {
return false;
}
executeInGameThread(() -> {
try {
player.sendMessage(message);
LOGGER.info("Mensagem privada enviada para " + charName + ": " + message);
} catch (Exception e) {
LOGGER.severe("Erro ao enviar mensagem: " + e.getMessage());
}
});
return true;
}
@Override
public boolean setPlayerLevel(String charName, int level) {
L2PcInstance player = getPlayer(charName);
if (player == null) {
return false;
}
executeInGameThread(() -> {
try {
long pXp = player.getExp();
long tXp = player.getStat().getExpForLevel(level);
if (pXp > tXp) {
player.removeExpAndSp(pXp - tXp, 0);
} else if (pXp < tXp) {
player.addExpAndSp(tXp - pXp, 0);
}
LOGGER.info("Level de " + charName + " alterado para " + level);
} catch (Exception e) {
LOGGER.severe("Erro ao alterar level: " + e.getMessage());
}
});
return true;
}
@Override
public boolean addPlayerExp(String charName, long exp) {
L2PcInstance player = getPlayer(charName);
if (player == null) {
return false;
}
executeInGameThread(() -> {
try {
player.addExpAndSp(exp, 0);
LOGGER.info("Exp adicionada para " + charName + ": " + exp);
} catch (Exception e) {
LOGGER.severe("Erro ao adicionar exp: " + e.getMessage());
}
});
return true;
}
@Override
public boolean setPlayerClass(String charName, int classId) {
L2PcInstance player = getPlayer(charName);
if (player == null) {
return false;
}
executeInGameThread(() -> {
try {
player.setClassId(classId);
player.broadcastUserInfo();
LOGGER.info("Classe de " + charName + " alterada para " + classId);
} catch (Exception e) {
LOGGER.severe("Erro ao alterar classe: " + e.getMessage());
}
});
return true;
}
@Override
public boolean addPlayerSkill(String charName, int skillId, int skillLevel) {
L2PcInstance player = getPlayer(charName);
if (player == null) {
return false;
}
executeInGameThread(() -> {
try {
L2Skill skill = SkillTable.getInstance().getInfo(skillId, skillLevel);
if (skill != null) {
player.addSkill(skill, true);
LOGGER.info("Skill " + skillId + " nível " + skillLevel + " adicionada para " + charName);
} else {
LOGGER.warning("Skill não encontrada: " + skillId + "/" + skillLevel);
}
} catch (Exception e) {
LOGGER.severe("Erro ao adicionar skill: " + e.getMessage());
}
});
return true;
}
@Override
public boolean removePlayerSkill(String charName, int skillId) {
L2PcInstance player = getPlayer(charName);
if (player == null) {
return false;
}
executeInGameThread(() -> {
try {
player.removeSkill(skillId);
LOGGER.info("Skill " + skillId + " removida de " + charName);
} catch (Exception e) {
LOGGER.severe("Erro ao remover skill: " + e.getMessage());
}
});
return true;
}
@Override
public String getOnlinePlayers() {
try {
Collection<L2PcInstance> players = L2World.getInstance().getAllPlayers().values();
StringBuilder json = new StringBuilder();
json.append("{\"players\":[");
boolean first = true;
for (L2PcInstance player : players) {
if (!first) json.append(",");
json.append("{");
json.append("\"name\":\"").append(player.getName()).append("\",");
json.append("\"level\":").append(player.getLevel());
json.append("}");
first = false;
}
json.append("],\"count\":").append(players.size()).append("}");
return json.toString();
} catch (Exception e) {
LOGGER.severe("Erro ao obter players online: " + e.getMessage());
return "{\"players\":[],\"count\":0}";
}
}
@Override
public boolean modifyPlayerAdena(String charName, long amount) {
L2PcInstance player = getPlayer(charName);
if (player == null) {
return false;
}
executeInGameThread(() -> {
try {
if (amount > 0) {
player.addAdena("L2ApiBridge", amount, player, true);
} else {
player.reduceAdena("L2ApiBridge", Math.abs(amount), player, true);
}
LOGGER.info("Adena modificada para " + charName + ": " + amount);
} catch (Exception e) {
LOGGER.severe("Erro ao modificar adena: " + e.getMessage());
}
});
return true;
}
@Override
public boolean healPlayer(String charName) {
L2PcInstance player = getPlayer(charName);
if (player == null) {
return false;
}
executeInGameThread(() -> {
try {
player.setCurrentHp(player.getMaxHp());
player.setCurrentMp(player.getMaxMp());
player.setCurrentCp(player.getMaxCp());
player.broadcastStatusUpdate();
LOGGER.info("Player " + charName + " curado completamente");
} catch (Exception e) {
LOGGER.severe("Erro ao curar player: " + e.getMessage());
}
});
return true;
}
@Override
public boolean revivePlayer(String charName) {
L2PcInstance player = getPlayer(charName);
if (player == null) {
return false;
}
executeInGameThread(() -> {
try {
if (player.isDead()) {
player.doRevive();
LOGGER.info("Player " + charName + " revivido");
} else {
LOGGER.warning("Player " + charName + " não está morto");
}
} catch (Exception e) {
LOGGER.severe("Erro ao reviver player: " + e.getMessage());
}
});
return true;
}
@Override
public String getPlayerClan(String charName) {
L2PcInstance player = getPlayer(charName);
if (player == null || player.getClan() == null) {
return null;
}
try {
StringBuilder json = new StringBuilder();
json.append("{");
json.append("\"clanName\":\"").append(player.getClan().getName()).append("\",");
json.append("\"clanLevel\":").append(player.getClan().getLevel()).append(",");
json.append("\"allyName\":\"").append(player.getClan().getAllyName() != null ? player.getClan().getAllyName() : "").append("\"");
json.append("}");
return json.toString();
} catch (Exception e) {
LOGGER.severe("Erro ao obter clan: " + e.getMessage());
return null;
}
}
@Override
public String executeAdminCommand(String command) {
LOGGER.warning("executeAdminCommand: Método sensível - usar com cuidado!");
LOGGER.info("Comando admin solicitado: " + command);
// TODO: Implementar execução de comandos admin se necessário
return "{\"result\":\"not_implemented\",\"command\":\"" + command + "\"}";
}
@Override
public boolean reloadServerConfig(String configType) {
LOGGER.info("reloadServerConfig solicitado: " + configType);
// TODO: Implementar reload de configs conforme seu servidor
return false;
}
@Override
public String getServerStats() {
return getServerStatus(); // Reutiliza o método getServerStatus
}
@Override
public boolean setPlayerTitle(String charName, String title) {
L2PcInstance player = getPlayer(charName);
if (player == null) {
return false;
}
executeInGameThread(() -> {
try {
player.setTitle(title);
player.broadcastTitleInfo();
LOGGER.info("Título de " + charName + " alterado para: " + title);
} catch (Exception e) {
LOGGER.severe("Erro ao alterar título: " + e.getMessage());
}
});
return true;
}
@Override
public String getPlayerQuests(String charName) {
L2PcInstance player = getPlayer(charName);
if (player == null) {
return null;
}
try {
// Implementação básica - expandir conforme sistema de quests do servidor
return "{\"quests\":[],\"note\":\"Quest system not implemented\"}";
} catch (Exception e) {
LOGGER.severe("Erro ao obter quests: " + e.getMessage());
return null;
}
}
@Override
public boolean spawnItem(int itemId, long count, int x, int y, int z) {
executeInGameThread(() -> {
try {
ItemTable.getInstance().createDummyItem(itemId);
// TODO: Criar item no mundo nas coordenadas especificadas
LOGGER.info("Item " + itemId + " x" + count + " spawned em " + x + "," + y + "," + z);
} catch (Exception e) {
LOGGER.severe("Erro ao spawnar item: " + e.getMessage());
}
});
return true;
}
}