summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.emmyrc.json23
-rw-r--r--.gitmodules3
m---------Umbrella0
-rw-r--r--build.lua161
-rw-r--r--mods/zombification-chance/42/media/lua/server/zombChance.lua119
-rw-r--r--mods/zombification-chance/42/media/lua/shared/zombChance_SandboxOverride.lua7
-rwxr-xr-xmods/zombification-chance/42/media/sandbox-options.txt8
-rw-r--r--mods/zombification-chance/42/mod.info3
-rw-r--r--mods/zombification-chance/common/.gitkeep0
-rw-r--r--mods/zombification-chance/mod.info3
10 files changed, 327 insertions, 0 deletions
diff --git a/.emmyrc.json b/.emmyrc.json
new file mode 100644
index 0000000..c2ac08a
--- /dev/null
+++ b/.emmyrc.json
@@ -0,0 +1,23 @@
+{
+ "$schema": "https://raw.githubusercontent.com/EmmyLuaLs/emmylua-analyzer-rust/refs/heads/main/crates/emmylua_code_analysis/resources/schema.json",
+ "workspace": {
+ "library": ["Umbrella/library"],
+ "workspaceRoots": [
+ "mods/zombification-chance/42/media/lua",
+ "examples/JSSevereInfection42/42/media/lua",
+ "examples/3268487204/42.13/media/lua",
+ "examples/3581134738/42/media/lua"
+ ]
+ },
+ "runtime": {
+ "requirePattern": [
+ "shared/?.lua",
+ "client/?.lua",
+ "server/?.lua"
+ ],
+ "version": "Lua5.1"
+ },
+ "diagnostics": {
+ "disable": ["unnecessary-if"]
+ }
+} \ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..88b822c
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "Umbrella"]
+ path = Umbrella
+ url = git@github.com:PZ-Umbrella/Umbrella.git
diff --git a/Umbrella b/Umbrella
new file mode 160000
+Subproject 17381967473cc2b40a7878f3c8aebe68210b63e
diff --git a/build.lua b/build.lua
new file mode 100644
index 0000000..9014b44
--- /dev/null
+++ b/build.lua
@@ -0,0 +1,161 @@
+local lfs = require("lfs")
+
+local enableDebug = true
+local enableImGui = false
+local flatpakSteam = true
+
+-- Name of your local workshop container
+local workshopName = "MyModWorkshop"
+
+-- Mods live here inside the git repo
+local repoModsDir = "mods"
+
+local isWindows = package.config:sub(1, 1) == "\\"
+local sep = isWindows and "\\" or "/"
+
+local homeDir = isWindows and os.getenv("userprofile") or os.getenv("HOME")
+local flatpakSteamVar = not isWindows and os.getenv("HOME") .. "/.var/app/com.valvesoftware.Steam"
+
+-- Launch via Steam
+local pzCacheRoot
+local pzExecutable
+if isWindows then
+ pzCacheRoot = (homeDir .. "\\Zomboid")
+ pzExecutable = "steam.exe -applaunch 108600"
+else
+ pzExecutable = flatpakSteam and "flatpak run com.valvesoftware.Steam -applaunch 108600" or "steam -applaunch 108600"
+ pzCacheRoot = flatpakSteam and (flatpakSteamVar .. "/Zomboid") or (homeDir .. "/Zomboid")
+end
+
+-- Final destination: Workshop/<Name>/Contents/mods
+local pzModsDir = pzCacheRoot .. sep .. "Workshop" .. sep .. workshopName .. sep .. "Contents" .. sep .. "mods"
+
+-- =========================
+-- File helpers
+-- =========================
+
+local function copyFile(src, dst)
+ local inFile = assert(io.open(src, "rb"))
+ local data = inFile:read("*all")
+ inFile:close()
+
+ local outFile = assert(io.open(dst, "wb"))
+ outFile:write(data)
+ outFile:close()
+end
+
+local function mkdir(path)
+ if isWindows then
+ os.execute(string.format('mkdir "%s" 2>nul', path))
+ else
+ os.execute(string.format('mkdir -p "%s"', path))
+ end
+end
+
+local function copyDir(src, dst)
+ mkdir(dst)
+ for name in lfs.dir(src) do
+ if name ~= "." and name ~= ".." then
+ local srcPath = src .. sep .. name
+ local dstPath = dst .. sep .. name
+ local attr = lfs.attributes(srcPath)
+ if attr.mode == "directory" then
+ copyDir(srcPath, dstPath)
+ else
+ copyFile(srcPath, dstPath)
+ end
+ end
+ end
+end
+
+-- =========================
+-- Safety helpers
+-- =========================
+
+local function normalizePath(path)
+ path = path:gsub("\\", "/")
+ return path:gsub("/+$", "")
+end
+
+local function isRootPath(path)
+ if path == "/" then
+ return true
+ end
+ if path:match("^[A-Za-z]:$") then
+ return true
+ end
+ return false
+end
+local function isSubPath(child, parent)
+ child = normalizePath(child)
+ parent = normalizePath(parent)
+ return child:sub(1, #parent + 1) == parent .. "/"
+end
+
+local function validatePath(target, root, home)
+ local t = normalizePath(target)
+ local r = normalizePath(root)
+ local h = normalizePath(home)
+
+ assert(t ~= "", "Refusing to delete empty path")
+ assert(not isRootPath(t), "Refusing to delete root path")
+ assert(t ~= h, "Refusing to delete home directory")
+ assert(isSubPath(t, r), "Refusing to delete outside workshop mods")
+ assert(#t > #r + 1, "Refusing to delete mods root")
+end
+
+-- =========================
+-- Discover mods dynamically
+-- =========================
+
+local mods = {}
+
+for name in lfs.dir(repoModsDir) do
+ if name ~= "." and name ~= ".." then
+ local path = repoModsDir .. sep .. name
+ local attr = lfs.attributes(path)
+ if attr and attr.mode == "directory" then
+ table.insert(mods, name)
+ end
+ end
+end
+
+assert(#mods > 0, "No mods found in ./mods directory")
+
+-- =========================
+-- Copy mods
+-- =========================
+
+print("Installing mods into workshop:")
+mkdir(pzModsDir)
+
+for _, modName in ipairs(mods) do
+ local src = repoModsDir .. sep .. modName
+ local dst = pzModsDir .. sep .. modName
+ print(" - " .. modName)
+ print(" - copied" .. src .. " to " .. dst)
+ validatePath(dst, pzModsDir, homeDir)
+ copyDir(src, dst)
+end
+
+print("All mods copied successfully!")
+
+-- =========================
+-- Launch Project Zomboid
+-- =========================
+
+local params = {}
+table.insert(params, "-modfolders workshop,mods")
+
+if enableDebug then
+ table.insert(params, "-debug")
+end
+if enableImGui then
+ table.insert(params, "-imgui")
+end
+
+local cmd = string.format("%s %s", pzExecutable, table.concat(params, " "))
+
+print("Launching:")
+print(cmd)
+os.execute(cmd)
diff --git a/mods/zombification-chance/42/media/lua/server/zombChance.lua b/mods/zombification-chance/42/media/lua/server/zombChance.lua
new file mode 100644
index 0000000..b32618d
--- /dev/null
+++ b/mods/zombification-chance/42/media/lua/server/zombChance.lua
@@ -0,0 +1,119 @@
+local debug = false
+
+--- Appends an integer to a separated string
+---@param str string current string
+---@param separator string separator
+---@param value integer integer to append
+---@return string new string
+local function appendIntToSeparatedString(str, separator, value)
+ if str == "" then
+ return tostring(value)
+ end
+ return str .. separator .. tostring(value)
+end
+
+---@diagnostic disable-next-line
+local zombificationChance = SandboxVars.ZombificationChance.chance
+
+local zombificationBodyPartsKey = "zombificationChance-infected"
+
+--- Splits a string at a given separator
+---@param inputstr string
+---@param separator string
+---@return table<integer, string>
+local function splitString(inputstr, separator)
+ assert(separator ~= "", "Separator must be provided")
+ local result = {}
+ for element in string.gmatch(inputstr, "([^" .. separator .. "]+)") do
+ table.insert(result, element)
+ end
+ return result
+end
+
+--- Returns true if zombification should occur based on chance
+---@return boolean
+local function rollZombification()
+ return ZombRand(100) <= zombificationChance
+end
+
+--- Event handler: called when player receives damage
+---@param character IsoGameCharacter
+---@param damage number
+local function onPlayerReceiveDamage(character, damageType, damage)
+ local bodyDamage = character:getBodyDamage()
+ local usedBodyPartIndexes = splitString(character:GetVariable(zombificationBodyPartsKey), " ")
+
+ for i = 0, bodyDamage:getBodyParts():size() - 1 do
+ local bodyPart = bodyDamage:getBodyParts():get(i)
+
+ if bodyPart:IsFakeInfected() then
+ local alreadyProcessed = false
+ for _, usedIndex in ipairs(usedBodyPartIndexes) do
+ if tonumber(usedIndex) == i then
+ alreadyProcessed = true
+ break
+ end
+ end
+
+ if not alreadyProcessed then
+ -- Mark this body part as processed
+ character:SetVariable(
+ zombificationBodyPartsKey,
+ appendIntToSeparatedString(character:GetVariable(zombificationBodyPartsKey), " ", i)
+ )
+
+ if rollZombification() then
+ if debug then character:Say("[PZC] Index: " ..
+ local i = 0
+ return function()
+ i = i + 1
+ if i <= #bodyPartIndexes then
+ return (bodyPartIndexes[i])
+ end
+ end
+end
+
+--- Removes an element from a separated string by value
+---@param str string original string
+---@param value string|number element to remove
+---@param separator string separator (must be provided)
+---@return string
+function removeElementFromSeparatedString(str, value, separator)
+ assert(separator ~= "", "Separator must be provided")
+ value = tostring(value)
+
+
+ local result = {}
+ for element in string.gmatch(str, "([^" .. separator .. "]+)") do
+ if element ~= value then
+ table.insert(result, element)
+ end
+ end
+
+ return table.concat(result, separator)
+end
+
+--- Event handler: called every update for a player
+---@param player IsoPlayer
+local function forgetOldFakeInfections(player)
+ local bodyDamage = player:getBodyDamage()
+
+ for bodyPartIndex in ProcessedBodyPartIndeciesIterator(player) do
+ local bodyPart = bodyDamage:getBodyParts():get(bodyPartIndex)
+ if not bodyPart:IsFakeInfected() then
+ if debug then player:Say("[PZC] Index: " .. bodyPart:getIndex() .. ", isNotFakeInfected, now forgotton") end
+ player:SetVariable(
+ zombificationBodyPartsKey,
+ removeElementFromSeparatedString(
+ player:GetVariable(zombificationBodyPartsKey),
+ bodyPartIndex,
+ " "
+ )
+ )
+ end
+ end
+end
+
+-- Register events
+Events.OnPlayerUpdate.Add(forgetOldFakeInfections)
+Events.OnPlayerGetDamage.Add(onPlayerReceiveDamage) \ No newline at end of file
diff --git a/mods/zombification-chance/42/media/lua/shared/zombChance_SandboxOverride.lua b/mods/zombification-chance/42/media/lua/shared/zombChance_SandboxOverride.lua
new file mode 100644
index 0000000..7dcff9b
--- /dev/null
+++ b/mods/zombification-chance/42/media/lua/shared/zombChance_SandboxOverride.lua
@@ -0,0 +1,7 @@
+function zombChanceSandboxOverride()
+ getSandboxOptions():set("ZombieLore.Mortality", 7);
+end
+
+Events.OnGameStart.Add(zombChanceSandboxOverride)
+Events.OnGameBoot.Add(zombChanceSandboxOverride)
+Events.OnLoad.Add(zombChanceSandboxOverride) \ No newline at end of file
diff --git a/mods/zombification-chance/42/media/sandbox-options.txt b/mods/zombification-chance/42/media/sandbox-options.txt
new file mode 100755
index 0000000..b28e4ce
--- /dev/null
+++ b/mods/zombification-chance/42/media/sandbox-options.txt
@@ -0,0 +1,8 @@
+VERSION = 1,
+
+option ZombificationChance.chance
+{
+ type = double, min = 0, max = 100, default = 20,
+ page = ZombificationChance,
+ translation = ZombificationChance_chance,
+} \ No newline at end of file
diff --git a/mods/zombification-chance/42/mod.info b/mods/zombification-chance/42/mod.info
new file mode 100644
index 0000000..4a00b36
--- /dev/null
+++ b/mods/zombification-chance/42/mod.info
@@ -0,0 +1,3 @@
+name=Zombification-chance
+id=ZombificationChance
+description=Makes Zombification occur randomly when bitten \ No newline at end of file
diff --git a/mods/zombification-chance/common/.gitkeep b/mods/zombification-chance/common/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/mods/zombification-chance/common/.gitkeep
diff --git a/mods/zombification-chance/mod.info b/mods/zombification-chance/mod.info
new file mode 100644
index 0000000..4a00b36
--- /dev/null
+++ b/mods/zombification-chance/mod.info
@@ -0,0 +1,3 @@
+name=Zombification-chance
+id=ZombificationChance
+description=Makes Zombification occur randomly when bitten \ No newline at end of file