Referencia completa de configuración
Archivos editables, valores de fábrica y esquema SQL del paquete
Consulta los valores de fábrica antes de comparar tu configuración. Modifica tu copia instalada y conserva los archivos originales como respaldo. Los ejemplos de identificadores y webhooks deben sustituirse en el servidor.
config/config.lua
--[[
EN-Arenas · Configuración
© EXOTICANETWORK.STORE — Licencia propietaria. Ver LICENSE.md.
Este archivo se carga SOLO en el servidor: los clientes nunca lo descargan.
Lo que el cliente necesita (posiciones de NPC, ajustes de la partida, etc.)
se lo envía el servidor en tiempo de ejecución.
]]
Config = {}
-- Muestra mensajes de diagnóstico en la consola del servidor.
Config.Debug = false
-- 'auto', 'esx' o 'qbcore'.
Config.Framework = 'auto'
-- Comandos de chat (se registran en el servidor). Pon `open = false` para que
-- el menú solo se pueda abrir desde los NPC de lobby.
Config.Commands = {
open = 'arenas',
leave = 'salirarena',
toggle = 'togglearenas' -- solo administradores (ver Config.AdminAce)
}
-- Permiso ACE para comandos de administración. Ver permissions.cfg.
Config.AdminAce = 'en-arenas.admin'
-- Imagen y título que muestra la interfaz. El logo es una ruta dentro de web/.
Config.Branding = {
title = 'Arenas PvP',
logo = 'assets/logo.png'
}
-- Avatares de Discord en el marcador y el feed de bajas. El token del bot NO
-- va aquí: defínelo en server.cfg con
-- set en_arenas_discord_token "TU_TOKEN"
-- (usa `set`, nunca `setr` ni `sets`, para que no se replique a los clientes).
Config.Discord = {
avatars = true
}
-- Sistema de ambulancia. Ver docs/INTEGRACIONES.md.
-- 'auto' detecta el que esté iniciado (Wasabi → esx_ambulancejob)
-- 'wasabi' Wasabi Ambulance (V1 y V2)
-- 'esx_ambulancejob' ambulancia oficial de ESX
-- 'custom' tu propio adaptador: server/bridge/medical/custom.lua
-- 'none' sin integración médica
Config.Medical = {
system = 'auto',
-- Cambia el nombre si tu recurso de ambulancia está renombrado.
resources = {
wasabi = 'wasabi_ambulance',
esx_ambulancejob = 'esx_ambulancejob'
},
blockDeadPlayers = true, -- impide entrar estando abatido o muerto
clearInjuriesEveryRound = true, -- cura completa al empezar cada ronda
clearInjuriesOnExit = true -- cura completa al salir de la arena
}
-- Apariencia real del top 3 en los NPC de podio. Solo 'illenium-appearance'
-- está soportado; pon false para usar siempre el modelo genérico del NPC.
Config.Appearance = {
resource = 'illenium-appearance'
}
-- NPC de acceso al menú. Puedes agregar tantos como quieras.
Config.Lobbies = {
{
model = 'g_m_y_salvaboss_01',
coords = vector4(-426.16, 1121.43, 325.85, 346.48),
blip = { enabled = true, sprite = 437, colour = 27, scale = 0.8, label = 'Arenas PvP' }
}
}
-- Distancia de interacción con los NPC de lobby (ox_target).
Config.InteractDistance = 2.0
-- Placa de rango sobre la cabeza cerca de los lobbies.
Config.RankTag = {
enabled = true,
showPlayers = false, -- true = también sobre los jugadores, no solo los NPC del podio
radius = 30.0,
heightOffset = 1.05,
scale = 0.62
}
-- NPC de podio con el top 3. kind = 'kills' (bajas) o 'ranked' (puntos).
Config.Leaderboard = {
enabled = true,
refreshSeconds = 300,
drawDistance = 12.0,
peds = {
{ top = 1, kind = 'kills', model = 'a_m_y_business_01', coords = vector4(-406.93, 1126.06, 327.88, 78.64), scenario = 'WORLD_HUMAN_GUARD_STAND' },
{ top = 2, kind = 'kills', model = 'a_m_y_business_01', coords = vector4(-406.52, 1128.16, 326.93, 77.73), scenario = 'WORLD_HUMAN_GUARD_STAND' },
{ top = 3, kind = 'kills', model = 'a_m_y_business_01', coords = vector4(-407.58, 1123.84, 326.91, 75.07), scenario = 'WORLD_HUMAN_GUARD_STAND' }
}
}
Config.Match = {
roundsToWin = 3, -- valor por defecto de las salas
roundsMin = 1, -- rango permitido al crear una sala
roundsMax = 999,
roundSeconds = 90,
countdownSeconds = 3, -- cuenta atrás antes de cada ronda
intermissionSeconds = 3, -- pausa entre rondas
preparationSeconds = 15, -- tiempo máximo para que todos estén listos
podiumMs = 6000, -- duración del podio final antes de volver al lobby
health = 200,
armour = 0,
boundaryPadding = 10.0, -- metros extra sobre maxDistance antes de devolver al spawn
bucketBase = 4000, -- rango de routing buckets reservados para partidas
bucketMax = 9999,
leaveAwardsWin = true, -- rendirse da la victoria al equipo rival
infiniteStamina = true,
requireManualTeams = true, -- el dueño de la sala asigna cada jugador a un equipo
keepWeaponInHand = true, -- re-entrega el arma si otro recurso la quita
enableSpectate = true, -- al morir, cámara sobre un compañero vivo (Q/E cambia)
assistWindowSeconds = 10, -- ventana para contar asistencias
tradeWindowSeconds = 2 -- un jugador recién caído aún puntúa la baja (intercambio)
}
-- Contorno sobre los compañeros, visible a través de paredes.
Config.TeamOutline = {
enabled = true,
duringPreparation = true,
color = { r = 255, g = 255, b = 255, a = 255 },
shader = 1,
technique = 'waterreflection',
maxDistance = 250.0,
updateInterval = 250
}
Config.Ranked = {
enabled = true,
roundsToWin = 3,
placementMatches = 10, -- partidas necesarias antes de mostrar división
-- Puntos por tipo de partida. El rendimiento (bajas/asistencias) se suma
-- hasta maxPerformance para que una partida larga no rompa la economía.
scoring = {
custom = { win = 8, loss = -6, draw = 1, kill = 1, assist = 1, maxPerformance = 4 },
ranked = { win = 22, loss = -16, draw = 2, kill = 2, assist = 1, maxPerformance = 8 },
duel = { win = 6, loss = -5, draw = 0, kill = 1, assist = 0, maxPerformance = 3 }
},
-- Divisiones ordenadas de menor a mayor.
tiers = {
{ id = 'semilla_criolla', label = 'Semilla Criolla', min = 0, color = '#89939f' },
{ id = 'chamo_novato', label = 'Chamo Novato', min = 100, color = '#929daa' },
{ id = 'arepa_de_hierro', label = 'Arepa de Hierro', min = 250, color = '#9ca8b5' },
{ id = 'guarapo_bronce', label = 'Guarapo de Bronce', min = 450, color = '#aa7650' },
{ id = 'motorizado_criollo', label = 'Motorizado Criollo', min = 700, color = '#bd814f' },
{ id = 'guacamaya_roja', label = 'Guacamaya Roja', min = 1000, color = '#d0574d' },
{ id = 'turpial_de_plata', label = 'Turpial de Plata', min = 1400, color = '#c8d4df' },
{ id = 'araguaney_dorado', label = 'Araguaney Dorado', min = 1850, color = '#e7b936' },
{ id = 'guardian_del_barrio', label = 'Guardián del Barrio', min = 2350, color = '#e2a12f' },
{ id = 'guerrero_del_caribe', label = 'Guerrero del Caribe', min = 2900, color = '#27b8d2' },
{ id = 'relampago_catatumbo', label = 'Relámpago del Catatumbo', min = 3500, color = '#2d9cf0' },
{ id = 'vigia_del_avila', label = 'Vigía del Ávila', min = 4200, color = '#37b986' },
{ id = 'llanero_de_acero', label = 'Llanero de Acero', min = 5000, color = '#7ab1c9' },
{ id = 'rastro_del_orinoco', label = 'Rastro del Orinoco', min = 5900, color = '#2c9fb4' },
{ id = 'cazador_de_tepuy', label = 'Cazador de Tepuy', min = 6900, color = '#48c4a2' },
{ id = 'furia_maracucha', label = 'Furia Maracucha', min = 8000, color = '#ff7b36' },
{ id = 'acero_guaro', label = 'Acero Guaro', min = 9200, color = '#cf7746' },
{ id = 'sombra_oriental', label = 'Sombra Oriental', min = 10500, color = '#8b79d7' },
{ id = 'centinela_andino', label = 'Centinela Andino', min = 11900, color = '#759cd8' },
{ id = 'titan_de_la_sabana', label = 'Titán de la Sabana', min = 13400, color = '#45c17d' },
{ id = 'cacique_apure', label = 'Cacique de Apure', min = 15000, color = '#d18b3d' },
{ id = 'cacique_urdaneta', label = 'Cacique Urdaneta', min = 16700, color = '#de7448' },
{ id = 'cacique_tamanaco', label = 'Cacique Tamanaco', min = 18500, color = '#e35d51' },
{ id = 'cacique_guaicaipuro', label = 'Cacique Guaicaipuro', min = 20400, color = '#ec4f55' },
{ id = 'centauro_del_llano', label = 'Centauro del Llano', min = 22400, color = '#d9b640' },
{ id = 'heroe_vinotinto', label = 'Héroe Vinotinto', min = 24500, color = '#b83f55' },
{ id = 'leyenda_del_salto', label = 'Leyenda del Salto Ángel', min = 26700, color = '#42cde6' },
{ id = 'gloria_libertadora', label = 'Gloria Libertadora', min = 29000, color = '#f1c94a' },
{ id = 'soberano_criollo', label = 'Soberano Criollo', min = 31400, color = '#ff6c39' },
{ id = 'inmortal_venezuela', label = 'Inmortal de Venezuela', min = 34000, color = '#ff354f' }
}
}
Config.Bets = {
enabled = true,
account = 'money', -- ESX: money/bank · QBCore: cash/bank
minimum = 0,
maximum = 100000,
itemsEnabled = true, -- permite apostar objetos de ox_inventory (de Config.Weapons)
itemMaxAmount = 5
}
-- Reto 1v1 apuntando a otro jugador con ox_target dentro de una zona de duelo.
Config.Duel = {
enabled = true,
weapon = 'WEAPON_PISTOL_MK2',
roundsToWin = 5,
requestTimeoutSeconds = 15,
targetDistance = 5.0,
-- Claves de Config.Maps permitidas para duelos. Vacío = cualquier mapa.
maps = { 'banco' },
zones = {
{ coords = vector3(-273.0052, -1907.5061, 27.7554), radius = 45.0 }
}
}
-- Capacidades disponibles (jugadores totales por partida).
Config.Modes = {
{ value = 2, label = '1 vs 1' },
{ value = 4, label = '2 vs 2' },
{ value = 6, label = '3 vs 3' },
{ value = 8, label = '4 vs 4' },
{ value = 10, label = '5 vs 5' },
{ value = 12, label = '6 vs 6' }
}
Config.Weapons = {
{ value = 'WEAPON_PISTOL', label = 'Pistola', category = 'Pistolas' },
{ value = 'WEAPON_PISTOL_MK2', label = 'Pistola MK II', category = 'Pistolas' },
{ value = 'WEAPON_COMBATPISTOL', label = 'Pistola de combate', category = 'Pistolas' },
{ value = 'WEAPON_PISTOL50', label = 'Pistola .50', category = 'Pistolas' },
{ value = 'WEAPON_COMBATPDW', label = 'PDW de combate', category = 'Subfusiles' },
{ value = 'WEAPON_MICROSMG', label = 'Micro SMG', category = 'Subfusiles' },
{ value = 'WEAPON_SMG', label = 'SMG', category = 'Subfusiles' },
{ value = 'WEAPON_CARBINERIFLE', label = 'Carabina', category = 'Fusiles' },
{ value = 'WEAPON_COMPACTRIFLE', label = 'Rifle compacto', category = 'Fusiles' }
}
-- Arma de la cola competitiva.
Config.RankedWeapon = 'WEAPON_PISTOL_MK2'
--[[
Mapas. Cada uno necesita:
label nombre visible
img imagen (URL o ruta dentro de web/); opcional
teamA/teamB spawn de cada equipo (vector4)
center centro del área jugable
maxDistance radio permitido desde center (más Config.Match.boundaryPadding)
ranked false para excluirlo de la cola competitiva
]]
-- Ubicaciones del mapa base de GTA V: no necesitan ningún mapeo (ymap/MLO).
-- Spawns tomados del script gratuito fivem-paintball (BesTKiLLeR4050).
Config.Maps = {
['rascacielos'] = {
label = 'Rascacielos', -- obra del Mile High Club, último piso
teamA = vector4(-168.86, -1011.97, 254.13, 341.67),
teamB = vector4(-139.52, -952.93, 254.13, 159.49),
center = vector3(-154.19, -982.45, 254.13),
maxDistance = 35.0
},
['altruistas'] = {
label = 'Campamento Altruista', -- Monte Chiliad
teamA = vector4(-1022.21, 4937.08, 200.93, 143.32),
teamB = vector4(-1093.72, 4942.15, 218.33, 158.38),
center = vector3(-1062.0, 4925.0, 210.0),
maxDistance = 45.0
},
['banco'] = {
label = 'Banco Pacific Standard',
teamA = vector4(244.43, 202.98, 105.21, 73.86),
teamB = vector4(254.34, 225.39, 106.29, 163.04),
center = vector3(249.33, 217.76, 105.75),
maxDistance = 30.0
}
}config/locale.lua
--[[
EN-Arenas · Textos
© EXOTICANETWORK.STORE — Licencia propietaria. Ver LICENSE.md.
Todos los mensajes que ven los jugadores. Se cargan solo en el servidor.
Los valores con %s / %d se completan en tiempo de ejecución.
]]
Locale = {
-- General
notify_title = 'Arenas PvP',
disabled = 'Las arenas están desactivadas temporalmente.',
busy = 'Ya estás en una sala, cola o partida.',
invalid = 'Solicitud inválida.',
too_fast = 'Vas demasiado rápido. Intenta de nuevo.',
must_be_alive = 'Debes estar vivo y sin estado de abatido.',
menu_in_match = 'No puedes abrir el menú durante una partida.',
menu_dead = 'No puedes abrir las arenas estando abatido o muerto.',
menu_too_far = 'Acércate a un punto de arenas para abrir el menú.',
not_in_match = 'No estás dentro de una partida.',
toggled_on = 'Arenas activadas.',
toggled_off = 'Arenas desactivadas.',
-- Interacción
target_open = 'Abrir Arenas PvP',
target_duel = 'Duelo',
suggestion_open = 'Abrir el menú de arenas',
suggestion_leave = 'Abandonar la partida actual',
-- Apuestas
no_money = 'No tienes suficiente dinero para la apuesta.',
no_items = 'No tienes suficientes objetos para la apuesta.',
-- Salas
lobby_not_found = 'La sala no existe.',
lobby_full = 'La sala está llena.',
lobby_closed = 'El dueño cerró la sala.',
lobby_kicked = 'Fuiste expulsado de la sala.',
owner_only_teams = 'Solo el dueño asigna equipos.',
owner_only_start = 'Solo el dueño puede iniciar.',
team_full = 'Ese equipo está completo.',
teams_unassigned = 'Hay jugadores sin equipo asignado.',
teams_need_both = 'Cada equipo necesita al menos un jugador.',
teams_overflow = 'Hay demasiados jugadores en un equipo.',
need_two_players = 'Se necesitan al menos 2 jugadores.',
start_failed = 'No se pudo iniciar la partida; la sala fue cancelada.',
-- Cola competitiva
queue_disabled = 'La cola competitiva está desactivada.',
queue_requeued = 'Un jugador no estaba listo. Sigues en la cola.',
-- Duelos
duel_target_busy = 'Ese jugador ya está ocupado.',
duel_pending = 'Ese jugador ya tiene un reto pendiente.',
duel_zone = 'Tienes que estar en una zona de duelo.',
duel_sent = 'Reto de duelo enviado a %s.',
duel_declined = '%s rechazó tu duelo.',
duel_unavailable = 'El duelo ya no está disponible.',
duel_failed = 'No se pudo iniciar el duelo (revisa tu estado de salud).',
duel_expired = 'Tu reto de duelo expiró.',
-- Partida
not_ready = 'No puedes entrar a una arena estando herido, abatido o muerto.',
setup_interrupted = 'Partida interrumpida durante la preparación. Marcador final %d–%d.',
out_of_bounds = 'Saliste del límite de la arena.',
round_fight = 'Ronda %d: ¡a pelear!',
round_draw = 'Ronda empatada.',
round_won = 'Tu equipo ganó la ronda.',
round_lost = 'Tu equipo perdió la ronda.',
match_draw = 'La partida terminó en empate. %s',
match_won = '¡Victoria! %s',
match_lost = 'Derrota. %s',
match_abandoned = 'La partida terminó por abandono. %s',
points = '%+d pts'
}sql/install.sql
-- ---------------------------------------------------------------------------
-- EN-Arenas · Instalación de base de datos
-- © EXOTICANETWORK.STORE — Licencia propietaria. Ver LICENSE.md.
--
-- Compatible con MySQL 5.7+ y MariaDB 10.3+.
-- El recurso crea esta tabla automáticamente al iniciar; este archivo es para
-- quien prefiera instalarla a mano antes del primer arranque.
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `en_arenas_stats` (
`identifier` VARCHAR(80) NOT NULL COMMENT 'Identificador del framework (ESX identifier / QBCore citizenid / license)',
`player_name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT 'Último nombre conocido del jugador',
`kills` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Bajas totales',
`deaths` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Muertes totales',
`wins` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Partidas ganadas',
`losses` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Partidas perdidas',
`score` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Puntos de clasificación',
`matches_played` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Partidas jugadas (cuentan para la colocación)',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`identifier`),
KEY `idx_en_arenas_ranking` (`score`, `wins`, `kills`),
KEY `idx_en_arenas_kills` (`kills`, `score`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='EN-Arenas: estadísticas y clasificación';server/bridge/medical/custom.lua
--[[
EN-Arenas · Adaptador médico personalizado
© EXOTICANETWORK.STORE — Licencia propietaria. Ver LICENSE.md.
Usa este archivo si tu servidor tiene otro sistema de ambulancia
(qb-ambulancejob, ars_ambulancejob, osp_ambulance, uno propio...).
1. Completa las funciones de abajo con los exports/eventos de tu ambulancia.
2. En config/config.lua pon: Config.Medical.system = 'custom'
(o deja 'auto' y define `resource`: se usará si ese recurso está iniciado
y no se detecta Wasabi ni esx_ambulancejob).
3. En tu ambulancia, ignora la muerte mientras el jugador esté en partida:
if LocalPlayer.state['en-arenas:inMatch'] then return end -- cliente
if Player(source).state['en-arenas:inMatch'] then return end -- servidor
Ejemplos de referencia: server/bridge/medical/wasabi.lua y esx_ambulancejob.lua.
Este archivo no está cifrado: puedes editarlo libremente.
]]
if ENA.Halted then return end
ENA.Medical.register('custom', {
-- Recurso que debe estar iniciado. nil = siempre disponible.
resource = nil,
-- true si el jugador está abatido o muerto según tu ambulancia.
isDead = function(source)
local state = Player(source).state
return state.isDead == true
end,
-- Revive completo: limpia el estado de muerte de tu ambulancia.
revive = function(source)
-- Ejemplo con evento de cliente:
-- TriggerClientEvent('mi_ambulancia:revive', source)
--
-- Ejemplo con export de servidor:
-- exports['mi_ambulancia']:RevivePlayer(source)
end,
-- Cura total / limpieza de lesiones entre rondas (opcional).
heal = function(source)
-- Ejemplo con export de CLIENTE:
-- ENA.Medical.callClientExport(source, 'mi_ambulancia', 'healPlayer', true)
end,
-- State bags que tu ambulancia activa al morir/abatir (opcional).
deathStates = { 'isDead' },
-- Cómo detectar en el cliente que el jugador sigue muerto (opcional).
client = {
deadStates = { 'isDead' }, -- state bags del jugador local
deadExport = nil -- export de cliente que devuelva true si está muerto
}
})