WENOPAL

How to make your first Project Zomboid mod (B42)

We build a complete mod from scratch: a sopaipilla you can eat that shows up in fridges. Structure, mod.info, item, icon, translation and loot.

Level beginner Β· Updated

We're going to make a small but complete mod: a sopaipilla (a Chilean fried pastry) that you can eat, with its own icon, its name in English and Spanish, and that shows up in fridges around the map. It's the same recipe we use in real mods like MelΓ³n con Vino, so what you learn here actually carries over.

1. The folder structure

Inside your Zomboid/Workshop/ folder, create this (names matter, including upper and lower case):

text
Zomboid/Workshop/
└── Sopaipillas/                      <- the project folder
    β”œβ”€β”€ workshop.txt                  <- data for Steam (step 8)
    β”œβ”€β”€ preview.png                   <- Workshop image
    └── Contents/
        └── mods/
            └── Sopaipillas/          <- the mod ID
                └── 42/               <- files for Build 42
                    β”œβ”€β”€ mod.info
                    └── media/
                        β”œβ”€β”€ scripts/items/Sopaipillas.txt
                        β”œβ”€β”€ textures/Item_Sopaipilla.png
                        └── lua/
                            β”œβ”€β”€ server/Items/SopaipillasLoot.lua
                            └── shared/Translate/
                                β”œβ”€β”€ ES/ItemName.json
                                └── EN/ItemName.json

The 42/ folder tells the game those files are for Build 42. Some mods also have a common/ folder for files that work in any version, but it isn't required: our mod Terremoto works without one.

2. mod.info: the mod's ID card

ini
name=Sopaipillas
id=Sopaipillas
description=Sopaipillas para comer en el apocalipsis. Build 42.
author=TuNombre
modversion=1.0.0
versionMin=42.0.0
  • id has to be unique and identical to the name of the folder inside mods/.
  • versionMin is the minimum game version your mod needs.
  • You can add poster=poster.png and icon=icon.png (inside 42/) so it looks nice in the mods menu.

3. The item

media/scripts/items/Sopaipillas.txt:

text
module Sopaipillas
{
    imports
    {
        Base,
    }

    item Sopaipilla
    {
        DisplayName = Sopaipilla,
        DisplayCategory = Food,
        ItemType = base:food,
        Icon = Sopaipilla,
        Weight = 0.1,
        HungerChange = -12,
        UnhappyChange = -5,
        DaysFresh = 3,
        DaysTotallyRotten = 6,
        Calories = 250,
        Carbohydrates = 30,
        Lipids = 12,
        Proteins = 4,
    }
}

What each part means:

  • module Sopaipillas is your items' family name. This one will be called Sopaipillas.Sopaipilla everywhere in the game. Use a short name of your own so you don't clash with other mods.
  • ItemType = base:food makes it food. For a generic object, use base:normal.
  • Icon = Sopaipilla looks for the image textures/Item_Sopaipilla.png. The game adds the Item_ prefix on its own.
  • A negative HungerChange reduces hunger. A negative UnhappyChange improves mood.
  • DaysFresh and DaysTotallyRotten control when it goes stale and when it rots.

4. The icon

Draw a 32Γ—32 pixel PNG with a transparent background and save it as media/textures/Item_Sopaipilla.png. If you don't give the item a 3D model, the game also uses this icon to show it lying on the ground.

5. The name in each language

Since Build 42, translations are JSON files. media/lua/shared/Translate/ES/ItemName.json (Spanish):

json
{
    "Sopaipillas.Sopaipilla": "Sopaipilla"
}

And EN/ItemName.json (English):

json
{
    "Sopaipillas.Sopaipilla": "Sopaipilla (fried dough)"
}

The key is always Module.Item. More details in Translations in B42.

6. Making it show up in the world (loot)

media/lua/server/Items/SopaipillasLoot.lua:

lua
require "Items/ProceduralDistributions"

local function agregarLoot()
    local ok, err = pcall(function()
        local function meter(lista, peso)
            local dist = ProceduralDistributions.list[lista]
            if not dist or not dist.items then return end   -- if the list doesn't exist, we don't break anything
            table.insert(dist.items, "Sopaipillas.Sopaipilla")
            table.insert(dist.items, peso)
        end
        meter("FridgeGeneric", 2)
        meter("FridgeTrailerPark", 3)
        meter("FridgeBreakRoom", 1)
    end)
    if not ok then print("[Sopaipillas] error en el loot: " .. tostring(err)) end
end

Events.OnPostDistributionMerge.Add(agregarLoot)

Every container on the map has a loot list made of item and weight pairs. Here we add the sopaipilla to three fridge lists. Everything goes inside pcall so that, if something fails, the mod doesn't take down the game's loading. Full explanation in Loot.

7. Testing it in game

  1. Launch the game with -debug (see the previous guide).
  2. Under Mods, enable "Sopaipillas".
  3. Start a new game.
  4. In the debug menu, open Items List, search for "Sopaipilla" and add it to your inventory.
  5. Check that it has an icon and a name, then eat it.
  6. If something doesn't show up, open Zomboid/console.txt and search for "Sopaipillas" or "ERROR".

8. workshop.txt and publishing

ini
version=1
title=Sopaipillas [B42]
description=Sopaipillas para comer en el apocalipsis.
tags=Build 42;Food
visibility=public

Also put a preview.png in the project folder (square, under 1 MB). Then you upload it from inside the game: Publishing to the Workshop.

What's next?