Objetivo: poner tus ítems en el mundo con una probabilidad que entiendes (no "a ojo"), dejar que el dueño del servidor la ajuste con opciones de sandbox, y que todo el texto del mod salga en el idioma del jugador.
1. Cómo llega el loot de Lua a Java
Las distribuciones son tablas de Lua (server/Items/Distributions.lua, ProceduralDistributions.lua: 53.418 líneas). Pero el que tira los dados es Java (ItemPickerJava, módulo 08). El paso de una cosa a la otra ocurre una sola vez, al cargar el mundo. En el bytecode de zombie.iso.IsoWorld el orden es:
triggerEvent OnPreDistributionMerge
triggerEvent OnDistributionMerge
triggerEvent OnPostDistributionMerge
SandboxOptions.load()
ItemPickerJava.Parse() ← Java copia las tablas de Lua a sus propias estructuras
ItemPickerJava.InitSandboxLootSettings()Consecuencias:
- Modifica las tablas en uno de los tres eventos (o al cargar tu archivo). En nuestros mods usamos
OnPreDistributionMerge(Tao Pai Pai's Pillar, Sable de Luz) uOnPostDistributionMerge(Tocomples Food, Adornos18): los dos sirven porque van antes deParse(). - Cambiar las tablas después no hace nada: Java ya tiene su copia. Si en mitad de la partida haces
table.insertenProceduralDistributions, el loot no cambia.
Agregar un ítem a un contenedor
Las distribuciones procedurales son listas planas nombre, peso, nombre, peso.... Tocomples Food lo hace en CompletoLoot.lua con el patrón defensivo que conviene copiar:
require "Items/Distributions"
require "Items/ProceduralDistributions"
local applied = false
local function addLoot()
if applied then return end -- una sola vez
local ok, err = pcall(function()
local function insertProcedural(name, item, weight)
local d = ProceduralDistributions.list[name]
if not d or not d.items then return end -- el contenedor puede no existir en otra versión
table.insert(d.items, item)
table.insert(d.items, weight)
end
insertProcedural("FridgeGeneric", "CompleteItaliano.Vienesa", 50)
applied = true
end)
if not ok then print("[CompletoItaliano] Distribution injection error: " .. tostring(err)) end
end
Events.OnPostDistributionMerge.Add(addLoot)Qué significa el peso
Con la fórmula que sacaste del .jar en el módulo 08:
probabilidad por tirada = (peso × 100 × modificadorDeSandbox + densidadDeZombis) × modificadorPorTiempo / 10.000Con loot "normal", el peso es un porcentaje por tirada: la vienesa de Tocomples Food con peso 50 tiene un 50 % de salir en cada tirada de cada refrigerador (FridgeGeneric tira varias veces por contenedor). Es mucho. Antes de elegir un peso, mira qué pesos tienen ítems parecidos del juego en ese mismo contenedor.
Dos detalles del código de Java que conviene recordar:
- El modificador de sandbox depende del tipo de loot del ítem (
getLootType: comida, enlatados, armas, herramientas, libros...), que se deduce de su script. Una comida tuya sigue la opción "Comida" del sandbox. - Si el contenedor se llena,
tryAddItemToContainerdevuelvenily se cortan todas las tiradas que faltaban. Ítems pesados con peso alto pueden dejar contenedores "vacíos" de lo demás.
Después de llenar: OnFillContainer
Cuando Java termina de llenar un contenedor dispara OnFillContainer(roomName, containerType, container) (está en ItemPickerJava.fillContainerInternal). Ahí puedes agregar, quitar o modificar lo que salió, con lógica que las tablas no permiten (por ejemplo: "solo si es de noche", "solo en Muldraugh"). Corre en el servidor (o en un jugador), y lo que agregues hay que sincronizarlo (módulo 12).
2. Opciones de sandbox
Son las opciones que el dueño de la partida elige al crearla. Se declaran en media/sandbox-options.txt (este es el de Batalla de Aura, uno de nuestros mods):
VERSION = 1,
option BatallaAura.Exertion
{
type = double, ← boolean, integer, double o enum (los que usan nuestros mods)
min = 0.1,
max = 5.0,
default = 1.0,
page = BatallaAura, ← pestaña en el menú de sandbox
translation = BatallaAura_Exertion,
}Y se leen en Lua desde la tabla global SandboxVars:
local mult = SandboxVars.BatallaAura.Exertion -- módulo.opciónReglas:
- Lee siempre con valor por defecto:
(SandboxVars.CursoLab and SandboxVars.CursoLab.Peso) or 10. Una partida creada antes de instalar tu mod no tiene tus opciones. - Léelas cuando las uses, no al cargar el archivo. Tus archivos de Lua cargan antes de que exista la partida.
- En multijugador las opciones vienen del servidor: el cliente las recibe, pero quien decide es el servidor.
3. Traducciones (Build 42: JSON)
Desde B42 las traducciones son archivos JSON en media/lua/shared/Translate/<IDIOMA>/. El juego trae 29 idiomas y 44 archivos por idioma. Nuestros mods usan:
| Archivo | Claves | Ejemplo |
|---|---|---|
ItemName.json | Modulo.Item | "CompleteItaliano.Vienesa": "Vienesa" |
IG_UI.json | IGUI_... | "IGUI_PumpkinZ_Cuts": "Cortes: %1" |
Sandbox.json | Sandbox_<pagina>, Sandbox_<translation>, Sandbox_<translation>_tooltip | "Sandbox_BatallaAura_Exertion": "Desgaste al bailar" |
Tooltip.json | Tooltip_... | el valor de Tooltip = del ítem |
Recipes.json | nombre del craftRecipe |
En Lua: getText("IGUI_PumpkinZ_Cuts", self.cuts) reemplaza %1 por el primer argumento (%2 el segundo...).
Cómo busca, según Translator.getTextInternal en el .jar:
- Elige la tabla por el prefijo de la clave:
IGUI_→ textos de interfaz,Sandbox_→ sandbox,Tooltip_,ContextMenu_,Moodles_,Farming_,UI_,Fluid_... Una clave sin un prefijo conocido (CursoLab_Hola) nunca se encuentra, esté en el archivo que esté. Usa siempre un prefijo del juego:IGUI_CursoLab_Hola. - Si no la encuentra, devuelve la clave misma, y en modo debug escribe
ERROR: Missing translation "..."en la consola. UnIGUI_CursoLab_Holaen pantalla significa "falta la traducción", no "error de código". <br>en el texto se convierte en salto de línea.
Los idiomas: EN es la base y el respaldo. El juego distingue ES y variantes (ES_CL, ES_MX), PT y PTBR. Nuestros mods traen EN, ES, PT y PTBR.
Ejercicios
Ejercicio 13.1 · Leer las distribuciones
Con _juego/lua/server/Items/, responde:
- ¿En qué contenedores puede aparecer la lupa (
Base.MagnifyingGlass) y con qué peso? (El sitio wenopal.work lo muestra en su ficha: compara.) - ¿Cuántas tiradas (
rolls) tieneFridgeGeneric? - Con la fórmula, ¿cuál es la probabilidad de que un refrigerador genérico tenga al menos una vienesa de Tocomples Food?
Pista para la 3
Si la probabilidad por tirada es p y hay r tiradas, la de que salga al menos una es 1 - (1 - p)^r. Con p = 0,5 y unas pocas tiradas, es casi segura. ¿Es eso lo que queríamos para Tocomples Food?
Ejercicio 13.2 · Loot configurable
Agrega la sopaipilla a FridgeGeneric y a BakeryMisc con un peso que sale de una opción de sandbox CursoLab.SopaipillaLoot (entero de 0 a 20, por defecto 4, 0 = no aparece).
Listo cuando: con 0 no aparece nunca, con 20 aparece casi siempre, y la opción sale traducida en español e inglés.
Ejercicio 13.3 · ¿Cuándo existe SandboxVars?
En el ejercicio anterior leíste la opción de sandbox en OnPostDistributionMerge, pero SandboxOptions.load() corre después de ese evento (sección 1). Comprueba qué valor ves ahí:
- en una partida nueva con la opción en 20,
- al cargar esa misma partida.
Imprime el valor en el evento y en OnFillContainer. Si no es el que esperas, cambia el diseño (pista: OnFillContainer corre durante la partida).
Listo cuando: puedes explicar con tus logs cuál de los dos lugares es confiable para leer la opción.
Ejercicio 13.4 · Loot con reglas
Con OnFillContainer: en las cocinas (roomName == "kitchen") de noche, un 10 % de probabilidad de agregar una sopaipilla recién hecha (cocida y caliente). Sincroniza el ítem agregado para que funcione en el servidor.
Ejercicio 13.5 · Cómo carga el traductor
Decompila zombie.core.Translator (módulo 08) y responde:
getTextbusca por prefijo (sección 3). Pero al cargar, ¿el nombre del archivo JSON decide en qué tabla queda cada clave? ¿Funciona una claveIGUI_...dentro deSandbox.json?- Si una clave existe en
ENpero no enES, ¿qué ve un jugador en español? - ¿Qué pasa si dos mods definen la misma clave?
Comprueba cada respuesta en el juego.
Desafío 13
Escribe pztrad.py: recorre un mod y avisa de (a) cada getText("...") del Lua cuya clave no está en EN, (b) cada clave de EN que falta en ES, PT o PTBR, y (c) cada clave de traducción que nadie usa. Pásalo por tus propios mods. (Es parecido a lo que hace nuestro sitio para detectar traducciones al portugués que faltan.)
Anterior: 12 · Multijugador · Siguiente: 14 · Datos persistentes y rendimiento