Project Zomboid

Project Zomboid

Mod Options (Build 41)
 This topic has been pinned, so it's probably important
star  [developer] 18 Jul, 2020 @ 4:19pm
Guide (for modders)
Last edited by star; 8 Nov, 2022 @ 4:09pm
< >
Showing 1-15 of 15 comments
star  [developer] 18 Jul, 2020 @ 4:26pm 
Way 1
The most simplest way is to check options only once at game start. The player should set options in main menu before starting the game. When game is loading, your mod will recive event "GameStart", so you can catch this moment and set the state of your mod according to the mod options.

Create simple table (e.g. "OPTIONS"), where keys are option names (ids).

Then you have to notify Mod Options Mod about your options. To do this you need to call function ModOptions:getInstance().

Definition:
function ModOptions:getInstance(options, mod_id, mod_shortname, mod_fullname)

Minimal Code
-- These are the default options. local OPTIONS = { box1 = true, box2 = false, } -- Connecting the options to the menu, so user can change and save them. if ModOptions and ModOptions.getInstance then ModOptions:getInstance(OPTIONS, "MyModID", "My Mod") end -- Check actual options at game loading. Events.OnGameStart.Add(function() print("checkbox1 = ", OPTIONS.box1) print("checkbox2 = ", OPTIONS.box2) end)

This minimal code should be in the folder media/lua/client, so your options will appear in options in main menu. If you put it in media/lua/server, it won't appear in main menu, and user can't set it before loading the game.

NB: This is for the client side only! It's ok for single player, but in multiplayer every player can change it. Be careful, if your options affect gameplay.
Last edited by star; 21 Jul, 2020 @ 7:34am
star  [developer] 18 Jul, 2020 @ 4:38pm 
Way 2
Another way is to define not only the default options but also their properties.
Let's call this "settings".

-- These are the settings. local SETTINGS = { options = { box1 = true, box2 = false, }, names = { box1 = "First Box", box2 = "Second Box", }, mod_id = "MyModID", mod_shortname = "My Mod", } -- Connecting the settings to the menu, so user can change them. if ModOptions and ModOptions.getInstance then ModOptions:getInstance(SETTINGS) end -- Check actual options at game loading. Events.OnGameStart.Add(function() print("checkbox1 = ", SETTINGS.options.box1) print("checkbox2 = ", SETTINGS.options.box2) end)

Remember that this is the syntactic sugar[en.wikipedia.org] only. You can use either the first way, or the second, or the third, but they are the same.
Last edited by star; 21 Jul, 2020 @ 6:43am
star  [developer] 18 Jul, 2020 @ 4:49pm 
Option Names
To add option names, you need to get link to the settings.

MOD SETTINGS = OPTIONS + OPTION PROPERTIES

More precisely, SETTINGS is a table that contains OPTIONS table and other properties such as names.

Link to the settings is just the result of ModOptions:getInstance(). Also, if you are using Way 2, variable SETTINGS is exacly what you want.

Then you can add names:
if ModOptions and ModOptions.getInstance then local settings = ModOptions:getInstance(OPTIONS, "MyModID", "My Mod") settings.names = { box1 = "Box One", box2 = "Box Two", } end

Note that if you are using Way 2, you can implement properties directly in SETTINGS because it's the same table.
local settings = ModOptions:getInstance(SETTINGS) print(settings == SETTINGS) -- true!

Actually, names should be translated to all languages. And you can specify not names but IGUIs. Ofc there should be proper txt file in shared/Translate/EN.
settings.names = { box1 = "IGUI_MyMod_Box_One", box2 = "IGUI_MyMod_Box_Two", }
Last edited by star; 19 Jul, 2020 @ 3:18pm
star  [developer] 18 Jul, 2020 @ 4:53pm 
Mutual Dependencies
Mutual dependencies between options will help you to avoid impossible options in menu. For example, there are two checkboxes (box1 and box2) and only one may be set.

if ModOptions and ModOptions.getInstance then local settings = ModOptions:getInstance(OPTIONS, "MyModID", "My Mod") local opt1 = settings:getData("box1") local opt2 = settings:getData("box2") function opt1:onUpdate(val) if val then opt2:set(false) -- disable the second option if the first option is set end end function opt2:onUpdate(val) if val then opt1:set(false) -- disable the first option if the second option is set end end end
Last edited by star; 18 Jul, 2020 @ 5:18pm
star  [developer] 19 Jul, 2020 @ 1:21am 
Preloading Options
Normally, the options are loaded at game start or when the user goes to options menu. But sometimes you may need get loaded options immediately.

To do this, call the function ModOptions:loadFile()
if ModOptions and ModOptions.getInstance then local settings = ModOptions:getInstance(OPTIONS, "MyModID", "My Mod") ModOptions:loadFile() print("checkbox1 = ", OPTIONS.box1) -- already accessible! print("checkbox2 = ", OPTIONS.box2) end
Last edited by star; 20 Jul, 2020 @ 5:32am
star  [developer] 19 Jul, 2020 @ 1:21am 
Saved Options Format
Options are saved in the file mods_options.ini in Lua folder.
Check this path:
C:\Users\YourName\Zomboid\Lua\mods_options.ini

This is the file for all mods, so each mod has its own section.
Example:
[MyModID] box1 = true box2 = false
Last edited by star; 19 Jul, 2020 @ 2:45am
star  [developer] 19 Jul, 2020 @ 2:46am 
disclaimer
NB! I recommend to use sandbox-options.txt for server mechanics instead of this mod!

Server Only Mod
(experimental recipe)
What if your mod is server only? Client files won't be loaded on dedicated server. In this case you have to put your default options into media/lua/shared. It gives you the following advantages:
1) Server admin can tune mod options BEFORE loading coop game.
2) Options will be accessible in server lua files.

But you have to provide a link between your files (using a global variable e.g. a table).

In media/lua/shared/yourmod_options.lua:
--Default options. local OPTIONS = { box1 = true, box2 = false, } -- Connecting the options to the menu, so user can change them. if ModOptions and ModOptions.getInstance then ModOptions:getInstance(OPTIONS, "MyModID", "My Mod") end --Make a link MY_COOL_MOD = {} -- global variable (pick another name!) MY_COOL_MOD.OPTIONS = OPTIONS

In media/lua/server/:
local OPTIONS = MY_COOL_MOD.OPTIONS -- Check actual options at game loading. Events.OnGameStart.Add(function() if not isClient() then -- only host may take these options print("checkbox1 = ", OPTIONS.box1) print("checkbox2 = ", OPTIONS.box2) end end)
Last edited by star; 11 Sep, 2022 @ 10:03pm
star  [developer] 19 Jul, 2020 @ 2:46am 
OnApplyInGame()
Sometimes you need to know if the player changed options while playing.
Of course, we are talking about cleint options, not private admin host options.
if ModOptions and ModOptions.getInstance then local settings = ModOptions:getInstance(OPTIONS, "MyModID", "My Mod") local opt1 = settings:getData("box1") function opt1:OnApplyInGame(val) print('Option is updated!', self.id, val) end end
(TODO: only admins may change options during the game)
Last edited by star; 2 Aug, 2020 @ 6:54am
star  [developer] 19 Jul, 2020 @ 2:49am 
Tooltip
if ModOptions and ModOptions.getInstance then local settings = ModOptions:getInstance(OPTIONS, "MyModID", "My Mod") local opt1 = settings:getData("box1") opt1.tooltip = "IGUI_MyMod_Box1" end
Last edited by star; 20 Jul, 2020 @ 11:44am
star  [developer] 19 Jul, 2020 @ 2:49am 
Dropdown list
(experimental feature)
-- These are the default options. local OPTIONS = { box1 = true, box2 = false, dropdown1 = 2, -- default index of choice } -- Connecting the options to the menu if ModOptions and ModOptions.getInstance then local settings = ModOptions:getInstance(OPTIONS, "MyModID", "My Mod") local drop1 = settings:getData("dropdown1") drop1[1] = getText("IGUI_MyMod_Chioce1") --"Chioce One" drop1[2] = getText("IGUI_MyMod_Chioce2") --"Chioce Two" drop1[3] = getText("IGUI_MyMod_Chioce3") --"Chioce Three" drop1.tooltip = "IGUI_MyMod_DropdownList_Tooltip" end -- Check actual options at game loading. Events.OnGameStart.Add(function() print("Number of choice is ", OPTIONS.dropdown1) -- the number from 1 to 3 end)

Don't forget to add strings to the txt file in your mod.
Create the file IG_UI_EN.txt in media/lua/shared/Translate/EN :
IGUI_EN = { IGUI_MyMod_Chioce1 = "Chioce One", IGUI_MyMod_Chioce2 = "Chioce Two", IGUI_MyMod_Chioce3 = "Chioce Three", IGUI_MyMod_DropdownList_Tooltip = "This is a dropdown list.", }
Last edited by star; 21 Jul, 2020 @ 7:29am
star  [developer] 19 Jul, 2020 @ 3:00pm 
Way 3
The third way is to define all the settings in key-value style using subtable "options_data". Remember that it's only syntactic sugar[en.wikipedia.org].

Example from the mod Fair Traits Cost:
local function OnApply(self, val) self:resetLua() end local SETTINGS = { options_data = { tune_positive_trait = { -- choices (12 pieces): "+0", "+1", "+2", "+3", "+10%", "+20%", "+25%", "+30%", getText("IGUI_Opt_TR_30"), "+40%", getText("IGUI_Opt_TR_40"), "+50%", -- other properties of the option: name = "IGUI_Opt_TR_positive", tooltip = "IGUI_Opt_TR_positive_tooltip", default = 1, OnApplyMainMenu = OnApply, }, tune_negative_trait = { "+0", "+1", "+2", "+3", "+10%", "+20%", "+25%", "+30%", getText("IGUI_Opt_TR_30"), "+40%", getText("IGUI_Opt_TR_40"), "+50%", name = "IGUI_Opt_TR_negative", tooltip = "IGUI_Opt_TR_negative_tooltip", default = 1, OnApplyMainMenu = OnApply, }, }, mod_id = 'FairTraitsCost', mod_shortname = 'Fair Cost', mod_fullname = 'Fair Traits Cost', } if ModOptions and ModOptions.getInstance then ModOptions:getInstance(SETTINGS) end
In this case you have all the properties for each option in a single table.
Last edited by star; 2 Aug, 2020 @ 6:58am
star  [developer] 19 Jul, 2020 @ 3:00pm 
Event Listeners
Events are new option values, of course.
For settings in general you can use: OnApply, OnApplyMainMenu, OnApplyInGame:
local settings = ModOptions:getInstance(OPTIONS, "MyModID", "My Mod") function settings:OnApply() print("User applied new options") print("Box1 = ", OPTIONS.box1) end

For separate options you can use the same listeners. But it will be triggered only if the value is changed:
local opt1 = settings:getData("box1") function opt1:OnApply(val) print("User pressed apply button. Changed value = ", val) end

Also you can use onUpdate listener. It will be triggered when the user changed an option before Apply button. This is not the final value, because the user may leave the options without applying it ("back" button).
local opt1 = settings:getData("box1") function opt1:onUpdate(val) print("User clicked the checkbox! Now it is ", val) end
Last edited by star; 2 Aug, 2020 @ 7:16am
star  [developer] 20 Jul, 2020 @ 2:22pm 
OnApplyMainMenu and resetLua
if ModOptions and ModOptions.getInstance then local settings = ModOptions:getInstance(OPTIONS, "MyModID", "My Mod") local opt1 = settings:getData("box1") -- Will be called only in main menu, not during the active game session. -- Will be called only on ACCEPT and only if the value is changed. function opt1:OnApplyMainMenu(val) self:resetLua() -- Reload all mods. end end
Last edited by star; 2 Aug, 2020 @ 6:55am
star  [developer] 2 Aug, 2020 @ 6:45am 
Bind custom keys
If you want to use keys from keyboard, you usually need a blank block like this:
local function KeyUp(keynum) if keynum == Keyboard.KEY_NUMPAD2 then print("Hello! You've pressed 2 on the numpad!") end end Events.OnKeyPressed.Add(KeyUp)
All available key names in Project Zomboid you can see here[pastebin.com].

To make an option using this framework, you have to make a table with default key and its name:
local key_data = { key = Keyboard.KEY_NUMPAD2, --default name = "My Super Key", -- just id (user won't see this name) }

Then you can use this data to tell framework that you want to make an option:
local category = "[Player Control]"; ModOptions:AddKeyBinding(category, key_data);
Choose category name from the list:
[Player Control] [Combat] [Vehicle] [UI] [Hotkeys] [Multiplayer] [Voice] [NPC Interaction] [Debug]
If you choose non-existent category, the framework will create it for you.

That's it!

Last thing that you should remember is to give a name to your option. It's in the txt file that you should create in your mod folder:
/media/lua/shared/Translate/EN/UI_EN.txt
UI_EN = { UI_optionscreen_binding_My Super Key = "Print Hello", }
User will see the name "Print Hello" for this option.

Example mod: Fast Keys
Last edited by star; 2 Aug, 2020 @ 7:23pm
star  [developer] 2 Jan, 2021 @ 12:03am 
disclaimer
NB: I recommend to use sandbox-options.txt for server mechanics instead of this mod!

Move your options to Sandbox Options

NB: There are some unsolved issues. I don't recommend using it.

opt1.sandbox_path = "WorldOptions"
That's it. Nothing else.

Okay, as usual, there are few ways to make your options as sanbox options. Let's take a look at Way 1:
  1. First, make common option in your OPTIONS table (or SETTINGS).
  2. Then get a link to this option in a local variable, e.g. opt1 (of course check if Mod Options is active).
  3. Then add new property sandbox_path: section name in sanbox options.

Example code:
-- These are the default options. local OPTIONS = { --Common options box1 = false, --Sandboxed options box2 = false, int1 = 123, str1 = "some str", dropdown1 = 1, --default index (starting from 1) } -- Checking if Mod Options is installed and active if ModOptions and ModOptions.getInstance then local settings = ModOptions:getInstance(OPTIONS, "MyModID", "My Mod") --Initializing a dropdown list local drop1 = settings:getData("dropdown1") drop1[1] = "One" drop1[2] = "Two" drop1[3] = "Three" --Simple moving of an option to the sandbox window local opt1 = settings:getData("box2") opt1.sandbox_path = "WorldOptions" --Move other options to sandbox window (except box1) settings:MoveOptionsToSandbox("PopulationOptions", {"int1", "str1", "dropdown1"}) end -- Check actual options at game loading. Events.OnGameStart.Add(function() print("checkbox1 = ", OPTIONS.box1) print("checkbox2 = ", OPTIONS.box2) print("int1 = ",OPTIONS.int1) print("str1 = ",OPTIONS.str1) print("dropdown1 = ",OPTIONS.dropdown1) end)

Use section name (sandbox_path) from this list:
  1. PopulationOptions
  2. TimeOptions
  3. WorldOptions
  4. NatureOptions
  5. SadisticAIDirector
  6. Meta
  7. LootRarity
  8. Character
  9. Vehicle
  10. ZombieLore
  11. ZombieAdvanced
Otherwise this mod will create new section for you. For example, name it "MyModSection". Also you should add language string "IGUI_Sandbox_MyModSection".

Sandboxed options won't appear in Options Menu. No events for it. It can't be changed during the game or after saving the game. In MP clients (without cheats) will get it as readonly values.
Last edited by star; 11 Sep, 2022 @ 10:04pm
< >
Showing 1-15 of 15 comments
Per page: 1530 50