Skip to content

Instantly share code, notes, and snippets.

@Konijima
Last active January 8, 2024 03:28
Show Gist options
  • Save Konijima/7e6bd1adb6f69444e7b620965a611b74 to your computer and use it in GitHub Desktop.
Save Konijima/7e6bd1adb6f69444e7b620965a611b74 to your computer and use it in GitHub Desktop.
Project Zomboid - How to mod like a boss

How to mod like a boss 😎

This tutorial I will show you

  • how file load order works
  • how to think and structure lua mods
  • how to modify vanilla behaviour
  • how to use require with passion
  • and more...



The vanilla game uses globals?

Project Zomboid uses globals mostly everywhere, this make it easy for modders to use the objects anywhere but it also make it easy to break things by assiging an existing global with something that it shouldn't.

Well other mods uses globals too?

Other mods sometime uses globals too and when players runs 200+ mods at the same time, bet there is possibly conflicts, but also the global environment will be a real mess.

SOOO WHAT AM I SUPOSE TO DO THEN?

There is a solution to prevent using globals and this is exactly what we will be looking at here! But before that let's get to the most basic stuff.

Bear with me. 🐻




How the heck does client, server and shared works?

It may be confusing when we first start modding Project Zomboid so... Let's break this down once for all!

Game Lua files are loaded BEFORE mods Lua files.

SHARED

shared files runs FIRST

  • shared files must be agnostic (Contains classes and/or functions that may be needed into client and/or server files)

CLIENT

client files runs SECOND

  • client files cannot require server files
  • in multiplayer client files runs only on the client machine

SERVER

server files runs LAST

  • server file cannot require client files
  • in singleplayer server files runs only when booting a savegame
  • in multiplayer server files runs on both the client machine and server machine



Think about what you want to create and ask yourself some questions:

  • Is there objects, functions or classes that can help me creating my feature?
  • Is my feature going to be networked in multiplayer, is it transmitted automatically under the hood or I will need to handle transmition myself?
  • Do I need to store data on the player, in an object, in an item or globally in the world itself?
  • Can I take advantage of existing functions or objects such as the 150+ available timed actions?
  • Do I need to overwrite a base feature or can I hook into it and extend it's behaviour?
  • Do I need something that is not exposed by the Java engine into lua?



Example

I want to make a mod that change some vanilla behaviour!

I want to make it so the player cannot do a specific action for a some new reasons.

  • Do I need to think about networking?
    No, timed action are client sided.

  • Do I need to overwrite something?
    Yes but more specifically, I need to Hook into a timed action.

  • Can we just skip the talk and see code example?
    Wait no more!




Let's create a client file into

C:/Zomboid/mods/examplemod/media/lua/client/MyScriptName.lua

Now let's choose a timed action to Hook into by browsing into

C:/Program Files (x86)/Steam/steamapps/common/ProjectZomboid/media/lua/client/TimedActions

I chose ISBuryCorpse.lua and I want to prevent the player from being able to bury a corpse if he is too hungry.
So let's open MyScriptName.lua and also ISBuryCorpse.lua into a code editor

By reading the timed action script we learned that isValid method return a boolean that define if the action can run or not.

ISBuryCorpse.lua

function ISBuryCorpse:isValid()
  local playerInv = self.character:getInventory()
  return playerInv:containsType("CorpseMale") or playerInv:containsType("CorpseFemale")
end

Let's Hook into and add our new condition to the pile.

MyScriptName.lua

--- Save a reference to the original method
local ISBuryCorpse_isValid = ISBuryCorpse.isValid

--- Now overwrite the vanilla method
function ISBuryCorpse:isValid()
  --- We return the original method result
  --- But we also add our own condition
  return ISBuryCorpse_isValid(self) and self.character:getStats():getHunger() < 0.75
end

Crazy! We just modified the behaviour of an action in 10 seconds.

But this didn't require anything?
No the timed action is a global object from the vanilla game so we did not need anything.

To be continued...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment