
The best plugin from tags and optimized
& y HEX &#RRGGBB%tagskeeper_tag% para mostrar el tag en chat, tablist, o donde se use PlaceholderAPIdata.yml por UUID (persiste tras cambios de nombre)tagskeeper.<id>) y compatibilidad legacy (alonsotags.tag.<id>)%preview%, %status%, %selected%, %price%| Dependencia | Tipo | Necesaria para |
|---|---|---|
| Paper 1.20.x | Servidor | Ejecucion |
| PlaceholderAPI | Plugin (soft-depend) | Placeholder %tagskeeper_tag% |
| Vault + Economy Provider | Plugin (soft-depend) | Compra de tags con precio |
Si Vault no esta instalado, EconomyHook.setup() falla silenciosamente y EconomyHook.isEnabled() retorna false. Los tags con price simplemente no muestran precio ni permiten compra.
plugins/plugins/TagsKeepers/tags.yml/reloadtags para aplicar cambios sin reiniciarEn primer arranque el plugin genera los archivos por defecto:
config.yml (solo indica storage.type: YAML)messages.ymlmenu.ymltags.yml (viene con 9 tags de ejemplo)data.yml (vacio, se llena automaticamente)| Comando | Aliases | Descripcion | Uso en consola |
|---|---|---|---|
/tags |
/tag |
Abre el menu de tags (pagina 1) | No, solo retorna true sin accion |
/reloadtags |
-- | Recarga configuracion, mensajes, menu y tags | Si |
/tags no verifica permisos. Cualquier jugador puede abrir el menu. Los tags individuales se controlan por permiso interno.
| Nodo | Default | Descripcion |
|---|---|---|
tagskeeper.perms.reload |
op |
Acceso a /reloadtags |
tagskeeper.bypass.price |
op |
Obtiene tags de pago sin descontar dinero |
tagskeeper.<id> |
-- | Acceso al tag con ese ID (ej: tagskeeper.vip) |
alonsotags.tag.<id> |
-- | Permiso legacy, se verifica como alternativa al anterior |
tags.yml contiene la definicion de los tags bajo la clave tags:.
tags:
<id>:
tag: "<nombre que muestra en el chat/scoreboard/tablist>"
menu-display: "<nombre en el menu>"
preview: "<vista previa con {player}>"
permission: "tagskeepers.example <permiso requerido>"
material: "<Material>"
page: <numero de pagina>
slot: <slot en el inventario>
price: <precio opcional, sin comillas: 10000>
lore:
- "<linea de lore>"
| Campo | Requerido | Default | Tipo |
|---|---|---|---|
tag |
Si | -- | String (colores) |
menu-display |
Si | -- | String (colores) |
preview |
Si | -- | String (colores, debe contener {player}) |
permission |
Si | -- | String (nodo de permiso) |
material |
Si | -- | String (Bukkit Material, ej: NAME_TAG, DIAMOND) |
slot |
Si | -- | Integer (0-53 segun el tamanio del menu) |
page |
No | 1 |
Integer |
lore |
Si | -- | Lista de strings |
price |
No | -1 (sin precio) |
Double |
| Placeholder | Se reemplaza por |
|---|---|
%preview% |
El campo preview con {player} remplazado por el nombre del jugador |
%status% |
El mensaje configurado en messages.yml segun tag-status.unlocked o tag-status.locked |
%selected% |
El mensaje configurado en messages.yml segun tag-selection.selected o tag-selection.not-selected |
%price% |
El precio formateado por Vault si el tag tiene price y Vault activo; string vacio en caso contrario |
tags:
vip:
tag: "&8&#FFD700&lVIP&8"
menu-display: "&fTag &8&#FFD700&lVIP&8"
preview: "&8&#FFD700&lVIP &f{player}"
permission: "tagskeeper.vip"
material: NAME_TAG
page: 1
slot: 10
price: 10000
lore:
- ""
- "&7Vista Previa:"
- "&f%preview%"
- ""
- "&7Estado: %status%"
- "&7Precio: &e%price%"
- ""
- "%selected%"
| ID | Pagina | Slot | Precio |
|---|---|---|---|
| campeon | 1 | 10 | -- |
| mvp | 1 | 11 | -- |
| elite | 1 | 12 | -- |
| random | 1 | 13 | -- |
| veterano | 1 | 14 | -- |
| og | 1 | 15 | -- |
| koth | 1 | 16 | -- |
| emperor | 2 | 13 | -- |
| king | 3 | 13 | -- |
Ningun tag por defecto incluye price.
Configuracion en menu.yml.
Cada pagina se define como menus.page-<numero>. El plugin trae 3 paginas preconfiguradas.
menus:
page-1:
title: "<titulo del inventario>"
size: 36
next-page-slot: 32
previous-page-slot: <opcional>
remove-tag-slot: 31
| Opcion | Descripcion |
|---|---|
title |
Titulo del inventario (soporta colores HEX) |
size |
Tamanio del inventario, multiplo de 9 (tipico: 27, 36, 45, 54) |
next-page-slot |
Slot donde aparece la flecha de siguiente pagina |
previous-page-slot |
Slot donde aparece la flecha de pagina anterior |
remove-tag-slot |
Slot del item para remover el tag actual |
Si una pagina no tiene next-page-slot o previous-page-slot, esa flecha no se muestra.
menus:
fill-empty-slots: true
filler:
material: BLACK_STAINED_GLASS_PANE
name: " "
Si fill-empty-slots es true, se llenan todos los slots (0 a size-1) con el material configurado antes de colocar los items de tags/navegacion. Esto evita que los jugadores muevan items al inventario. Los items de tags sobrescriben el filler en sus slots especificos.
menus:
sounds:
open: BLOCK_CHEST_OPEN
select: ENTITY_PLAYER_LEVELUP
denied: ENTITY_VILLAGER_NO
already-selected: BLOCK_NOTE_BLOCK_BASS
Los sonidos se reproducen con volumen 1.0 y pitch 1.0. El nombre debe corresponder a un valor de org.bukkit.Sound. Si el nombre es invalido, se registra una advertencia en la consola y se omite el sonido.
menus:
remove-tag:
enabled: true
active:
material: PLAYER_HEAD
basehead: "<base64>"
name: "&fMenu &8» &8&#FFD700<ags&8 &f"
lore:
- " &fTag Actual:"
- " %current_tag%"
- " &8- &fClick para remover"
inactive:
material: GRAY_DYE
name: "&fMenu &8» &8&#FFD700<ags&8 &f"
lore:
- " &7Actualmente no tienes"
- " &7ningun tag activo."
El item active se muestra cuando el jugador tiene un tag equipado. El item inactive cuando no. El placeholder %current_tag% en el lore se reemplaza por el tag actual formateado, o "&7None" si el tag ID no se encuentra en el registro.
Las flechas de navegacion se crean con material ARROW:
"&aSiguiente Pagina""&cPagina Anterior"(Textos en espanol, hardcodeados en MenuManager.java.)
EconomyHook usa reflection para acceder a net.milkbowl.vault.economy.Economy, por lo que no requiere VaultAPI en compilacion. En runtime:
EconomyHook.setup() se llama desde Main.onEnable()Vault exista en el servidorBukkit.getServicesManager().getRegistration()has(), withdrawPlayer(), format() via reflectionisEnabled() retorna falseCuando un jugador hace click en un tag para el que no tiene permiso:
price (> 0) o la economia no esta activa: mensaje de no-permissionprice y la economia esta activa:tagskeeper.bypass.price: se marca como comprado sin cobrar, se selecciona el tagtag-insufficient-fundswithdraw(), se marca como comprado en data.yml, se selecciona el tagLa compra se guarda en data.yml como:
purchased:
<uuid>:
<tag-id>: true
El tag comprado queda vinculado al UUID permanentemente. No expira.
| Ruta | Placeholders |
|---|---|
tag-purchased |
%prefix%, %price% |
tag-insufficient-funds |
%prefix%, %price% |
tag-bypass-purchase |
%prefix%, %price% |
Si PlaceholderAPI esta presente en el servidor, el plugin registra la expansion tagskeeper con el placeholder:
| Placeholder | Retorno |
|---|---|
%tagskeeper_tag% |
El valor del campo tag del tag seleccionado (con colores), o string vacio si no tiene tag o el ID no existe |
La expansion se registra en Main.onEnable() si PlaceholderAPI se detecta como plugin cargado.
En plugins compatibles con PlaceholderAPI (TAB, EssentialsXChat, CMI, DeluxeChat, etc.):
Formato en config: %tagskeeper_tag%
Ejemplo en chat: &8&#FFD700&lVIP &7DevAdvvy: Hola
El plugin usa ColorUtil.java que convierte el formato &#RRGGBB al equivalente de BungeeCord ChatColor.
&#RRGGBB
Donde RR, GG, BB son valores hexadecimales (00-FF).
&#FF0000 Rojo
�FF00 Verde
�FF Azul
&#FFD700 Dorado
&#E59500 Naranja
&#FFD700&l Dorado + negrita
&#FF6A00&l&o Naranja + negrita + italica
&8&#FFD700 Gris oscuro + Dorado (sin formato)
Los codigos & (0-9, a-f, k-o, r) de Minecraft estandar funcionan junto con los HEX.
tag en tags.ymlmenu-display en tags.ymlpreview en tags.ymllore en tags.ymltitle en menu.ymlname del filler y remove-tag en menu.ymlmessages.yml contiene todos los textos que el plugin envia a los jugadores.
| Placeholder | Descripcion |
|---|---|
%prefix% |
Se reemplaza por el valor de prefix en messages.yml |
%tag% |
(Solo en tag-selected) El tag seleccionado, con colores |
%price% |
(Solo en mensajes de compra) Precio formateado por Vault |
prefix: "<prefijo del plugin>"
no-permission: "%prefix% <sin permiso>"
tag-selected: "%prefix% <tag seleccionado: %tag%>"
tag-already-selected: "%prefix% <ya seleccionado>"
tag-removed: "%prefix% <tag removido>"
no-tag-equipped: "%prefix% <sin tag equipado>"
reload-success: "%prefix% <recargado>"
tag-purchased: "%prefix% <comprado por %price%>"
tag-insufficient-funds: "%prefix% <dinero insuficiente: %price%>"
tag-bypass-purchase: "%prefix% <obtenido gratuitamente>"
Main.onEnable() guarda los archivos YAML por defecto si no existenConfigManager carga messages.yml y menu.yml a memoriaYAMLStorage carga data.yml (o lo crea si no existe)TagManager carga tags.yml a memoria en un HashMap<String, Tag>PlayerDataManager inicializa un cache vacio Map<UUID, PlayerTagData>EconomyHook.setup() intenta conectar con VaultJoinListener.onJoin():
data.yml el tag seleccionado del jugador (config.getString(uuid))PlayerDataManagerInventoryListener.onClick():
PlayerTagData.selectedTag en memoriadata.yml via YAMLStorage.saveTag(uuid, tagId)/reloadtagsReloadCommand.onCommand():
reloadConfig() (recarga config.yml)ConfigManager.reload() (recarga messages.yml y menu.yml)TagManager.loadTags() (limpia y recarga tags.yml)data.yml ni al cache de PlayerDataManagergit clone https://github.com/DevAdvvy/TagsKeepers.git
cd TagsKeepers
mvn clean package
El JAR se genera en target/TagsKeepers-1.3.jar.
Las dependencias (Paper API, PlaceholderAPI, authlib) se resuelven desde los repositorios Maven configurados en pom.xml. No se incluyen en el JAR final (scope provided).
data.yml se escribe en disco en cada operacion de guardado (save()). No hay buffer ni escritura diferida.HashMap<String, Tag> donde la clave es el ID en minusculas.EconomyHook usa player.getLocation() como parametro de Player para las llamadas a Vault. Vault acepta OfflinePlayer, y Player extiende OfflinePlayer.InventoryListener.onClick() usa stream().anyMatch() para verificar que el slot clickeado corresponde a un tag en la pagina actual antes de iterar la busqueda.[a-zA-Z0-9_\-]+ sobre el texture ID extraido para prevenir SSRF hacia dominios que no sean textures.minecraft.net.