diff --git a/media/lua/client/TOC/Zombies/ZombiesAmputation.lua b/media/lua/client/TOC/Zombies/ZombiesAmputation.lua index 2b87d75..7ed3dd5 100644 --- a/media/lua/client/TOC/Zombies/ZombiesAmputation.lua +++ b/media/lua/client/TOC/Zombies/ZombiesAmputation.lua @@ -1,151 +1,147 @@ --- todo activate after some more testing +require "lua_timers" + +local ItemsController = require("TOC/Controllers/ItemsController") +local StaticData = require("TOC/StaticData") +local CommandsData = require("TOC/CommandsData") +------------------------------- + +---@param zombie IsoZombie|IsoGameCharacter|IsoMovingObject|IsoObject +---@return integer trueID +local function GetZombieID(zombie) + -- Big love to Chuck and Sir Doggy Jvla for this code + ---@diagnostic disable-next-line: param-type-mismatch + local pID = zombie:getPersistentOutfitID() + local bits = string.split(string.reverse(Long.toUnsignedString(pID, 2)), "") + while #bits < 16 do bits[#bits + 1] = 0 end + + -- trueID + bits[16] = 0 + local trueID = Long.parseUnsignedLong(string.reverse(table.concat(bits, "")), 2) + + return trueID +end + +------------------------------- --- local ItemsController = require("TOC/Controllers/ItemsController") --- local StaticData = require("TOC/StaticData") --- local CommandsData = require("TOC/CommandsData") --- ------------------------------- +---@param item InventoryItem +local function PredicateAmputationItems(item) + return item:getType():contains("Amputation_") +end --- ---@param item InventoryItem --- local function PredicateAmputationItems(item) --- return item:getType():contains("Amputation_") --- end +---@param item InventoryItem +local function PredicateAmputationItemLeft(item) + return item:getType():contains("Amputation_") and item:getType():contains("_L") +end + +---@param item InventoryItem +local function PredicateAmputationItemRight(item) + return item:getType():contains("Amputation_") and item:getType():contains("_R") +end --- local function PredicateAmputationItemLeft(item) --- return item:getType():contains("Amputation_") and item:getType():contains("_L") --- end - --- local function PredicateAmputationItemRight(item) --- return item:getType():contains("Amputation_") and item:getType():contains("_R") --- end - --- ---@param zombie IsoZombie|IsoGameCharacter|IsoMovingObject|IsoObject --- ---@return integer trueID --- local function GetZombieID(zombie) - --- -- Big love to Chuck and Sir Doggy Jvla for this code --- ---@diagnostic disable-next-line: param-type-mismatch --- local pID = zombie:getPersistentOutfitID() --- local bits = string.split(string.reverse(Long.toUnsignedString(pID, 2)), "") --- while #bits < 16 do bits[#bits+1] = 0 end - --- -- trueID --- bits[16] = 0 --- local trueID = Long.parseUnsignedLong(string.reverse(table.concat(bits, "")), 2) - --- return trueID --- end +---@param zombie IsoZombie +local function SpawnAmputation(zombie, side) + local index = ZombRand(1, #StaticData.PARTS_STR) + local limb = StaticData.PARTS_STR[index] .. "_" .. side + local amputationFullType = StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limb --- ---@param zombie IsoZombie --- local function SpawnAmputation(zombie, side) --- local index = ZombRand(1, #StaticData.PARTS_STR) --- local limb = StaticData.PARTS_STR[index] .. "_" .. side --- local amputationFullType = StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limb + ItemsController.Zombie.SpawnAmputationItem(zombie, amputationFullType) --- ItemsController.Zombie.SpawnAmputationItem(zombie, amputationFullType) + -- Add reference and transmit it to server + local pID = GetZombieID(zombie) + local zombieKey = CommandsData.GetZombieKey() + local zombiesMD = ModData.getOrCreate(zombieKey) + if zombiesMD[pID] == nil then zombiesMD[pID] = {} end + zombiesMD[pID][side] = amputationFullType + ModData.add(zombieKey, zombiesMD) + ModData.transmit(zombieKey) +end - --- -- Add reference and transmit it to server --- local pID = GetZombieID(zombie) --- local zombieKey = CommandsData.GetZombieKey() --- local zombiesMD = ModData.getOrCreate(zombieKey) --- if zombiesMD[pID] == nil then zombiesMD[pID] = {} end --- zombiesMD[pID][side] = amputationFullType --- ModData.add(zombieKey, zombiesMD) --- ModData.transmit(zombieKey) --- end - --- ------------------------------- - --- ---@param player IsoGameCharacter --- ---@param zombie IsoZombie --- ---@param handWeapon HandWeapon --- function HandleZombiesAmputations(player, zombie, handWeapon, damage) --- if not instanceof(zombie, "IsoZombie") or not instanceof(player, "IsoPlayer") then return end --- if player ~= getPlayer() then return end - --- -- TODO Check type of weapon. No hands, only knifes or such - - - --- if damage < 3 or ZombRand(0,100) < 25 then return end - --- TOC_DEBUG.print(handWeapon:getName()) - - --- local zombieInv = zombie:getInventory() - - --- -- Check left or right --- local leftItem = zombieInv:containsEval(PredicateAmputationItemLeft) - --- if not leftItem then --- SpawnAmputation(zombie, "L") --- return --- end - - --- local rightItem = zombieInv:containsEval(PredicateAmputationItemRight) --- if not rightItem then --- SpawnAmputation(zombie, "R") --- return --- end --- end - - --- Events.OnWeaponHitCharacter.Add(HandleZombiesAmputations) - --- ----------------------------- - --- local localOnlyZombiesMD - --- local function SetupZombiesModData() --- local zombieKey = CommandsData.GetZombieKey() --- localOnlyZombiesMD = ModData.getOrCreate(zombieKey) - --- end - --- Events.OnInitGlobalModData.Add(SetupZombiesModData) +------------------------------- - --- ---@param zombie IsoZombie --- local function ReapplyAmputation(zombie) --- local pID = GetZombieID(zombie) - --- if localOnlyZombiesMD[pID] ~= nil then --- -- check if zombie has amputation --- local zombiesAmpData = localOnlyZombiesMD[pID] --- local zombieInv = zombie:getInventory() --- local foundItem = zombieInv:containsEvalRecurse(PredicateAmputationItems) - --- if foundItem then --- return --- else --- local leftAmp = zombiesAmpData['L'] --- if leftAmp then --- ItemsController.Zombie.SpawnAmputationItem(zombie, leftAmp) --- end - --- local rightAmp = zombiesAmpData['R'] --- if rightAmp then --- ItemsController.Zombie.SpawnAmputationItem(zombie, rightAmp) --- end - --- -- Removes reference, local only --- localOnlyZombiesMD[pID] = nil --- end --- end --- end - --- Events.OnZombieUpdate.Add(ReapplyAmputation) +local bloodAmount = 10 +---@param player IsoGameCharacter +---@param zombie IsoZombie +---@param handWeapon HandWeapon +local function HandleZombiesAmputations(player, zombie, handWeapon, damage) + if not instanceof(zombie, "IsoZombie") or not instanceof(player, "IsoPlayer") then return end + if player ~= getPlayer() then return end + + -- Check type of weapon. No hands, only knifes or such + local weaponCategories = handWeapon:getScriptItem():getCategories() + if not (weaponCategories:contains("Axe") or weaponCategories:contains("LongBlade")) then return end + + local isCrit = player:isCriticalHit() + local randomChance = ZombRand(0, 100) > (100 - SandboxVars.TOC.ZombieAmputationDamageChance) + if (damage > SandboxVars.TOC.ZombieAmputationDamageThreshold and randomChance) or isCrit then + TOC_DEBUG.print("Amputating zombie limbs - damage: " .. tostring(damage)) + local zombieInv = zombie:getInventory() + -- Check left or right + if not zombieInv:containsEval(PredicateAmputationItemLeft) then + SpawnAmputation(zombie, "L") + elseif not zombieInv:containsEval(PredicateAmputationItemRight) then + SpawnAmputation(zombie, "R") + end + -- add blood splat every couple of seconds for a while + addBloodSplat(getCell():getGridSquare(zombie:getX(), zombie:getY(), zombie:getZ()), bloodAmount) + local timerName = tostring(GetZombieID(zombie)) .. "_timer" + timer:Create(timerName, 1, 10, function() + addBloodSplat(getCell():getGridSquare(zombie:getX(), zombie:getY(), zombie:getZ()), bloodAmount) + end) + end +end +Events.OnWeaponHitCharacter.Add(HandleZombiesAmputations) + +----------------------------- + +local localOnlyZombiesMD + +local function SetupZombiesModData() + local zombieKey = CommandsData.GetZombieKey() + localOnlyZombiesMD = ModData.getOrCreate(zombieKey) +end + +Events.OnInitGlobalModData.Add(SetupZombiesModData) + + +---@param zombie IsoZombie +local function ReapplyAmputation(zombie) + local pID = GetZombieID(zombie) + + if localOnlyZombiesMD[pID] ~= nil then + -- check if zombie has amputation + local zombiesAmpData = localOnlyZombiesMD[pID] + local zombieInv = zombie:getInventory() + local foundItem = zombieInv:containsEvalRecurse(PredicateAmputationItems) + + if foundItem then + return + else + local leftAmp = zombiesAmpData['L'] + if leftAmp then + ItemsController.Zombie.SpawnAmputationItem(zombie, leftAmp) + end + + local rightAmp = zombiesAmpData['R'] + if rightAmp then + ItemsController.Zombie.SpawnAmputationItem(zombie, rightAmp) + end + + -- Removes reference, local only + localOnlyZombiesMD[pID] = nil + end + end +end + +Events.OnZombieUpdate.Add(ReapplyAmputation) diff --git a/media/lua/client/lua_timers.lua b/media/lua/client/lua_timers.lua new file mode 100644 index 0000000..2ee0d69 --- /dev/null +++ b/media/lua/client/lua_timers.lua @@ -0,0 +1,216 @@ +-- Made by Vyshnia +-- Workshop ID: 2875394066 +-- Mod ID: LuaTimers + +local os_time = os.time +local table_insert = table.insert +local table_remove = table.remove +local assert = assert +local type = type +local pairs = pairs + +timer = { + Timers = {}, + SimpleTimers = {} +} + +function timer:Simple(delay, func) + + assert(type(delay) == "number", "Delay of timer should be a number type") + assert(type(func) == "function", "Func of timer should be a function type (lol)") + + table_insert(self.SimpleTimers, { + EndTime = os_time() + delay, + Func = func + }) + +end + +function timer:Create(name, delay, repetitions, func) + + assert(type(name) == "string", "ID of timer should be a string type") + assert(type(delay) == "number", "Delay of timer should be a number type") + assert(type(repetitions) == "number", "Repetitions of timer should be a number type") + assert(type(func) == "function", "Func of timer should be a function type (lol)") + + self.Timers[name] = { + Delay = delay, + StartRepetitions = repetitions, + Repetitions = repetitions, + Infinity = repetitions == 0, + LastFuncTime = os_time(), + Func = func, + Paused = false, + } + +end + +local function timerUpdate() + + local cur_time = os_time() + + for k,v in pairs(timer.Timers) do + + if not v.Paused then + + if cur_time >= v.LastFuncTime + v.Delay then + + v.Func() + + v.LastFuncTime = cur_time + + if not v.Infinity then + + v.Repetitions = v.Repetitions - 1 + + if v.Repetitions <= 0 then + + timer.Timers[k] = nil + + end + + end + + end + + end + + end + + local simple_timers = timer.SimpleTimers + + for i = #simple_timers, 1, -1 do + + local t = simple_timers[i] + + if t.EndTime <= cur_time then + + t.Func() + + table_remove(simple_timers, i) + + end + + end + +end +Events.OnTickEvenPaused.Add(timerUpdate) + +function timer:Remove(name) + + local t = self.Timers[name] + + if not t then return false end + + self.Timers[name] = nil + + return true + +end + +function timer:Exists(name) + + return self.Timers[name] and true or false + +end + +function timer:Start(name) + + local t = self.Timers[name] + + if not t then return false end + + t.Repetitions = t.StartRepetitions + t.LastFuncTime = os_time() + t.Paused = false + t.PausedTime = nil + + return true + +end + +function timer:Pause(name) + + local t = self.Timers[name] + + if not t then return false end + + if t.Paused then return false end + + t.Paused = true + t.PausedTime = os_time() + + return true + +end + +function timer:UnPause(name) + + local t = self.Timers[name] + + if not t then return false end + + if not t.Paused then return false end + + t.Paused = false + + return true + +end +timer.Resume = timer.UnPause + +function timer:Toggle(name) + + local t = self.Timers[name] + + if not t then return false end + + t.Paused = not t.Paused + + return true + +end + +function timer:TimeLeft(name) + + local t = self.Timers[name] + + if not t then return end + + if t.Paused then + + return (t.Repetitions - 1) * t.Delay + (t.LastFuncTime + t.Delay - t.PausedTime) + + else + + return (t.Repetitions - 1) * t.Delay + (t.LastFuncTime + t.Delay - os_time()) + + end + +end + +function timer:NextTimeLeft(name) + + local t = self.Timers[name] + + if not t then return end + + if t.Paused then + + return t.LastFuncTime + t.Delay - t.PausedTime + + else + + return t.LastFuncTime + t.Delay - os_time() + + end + +end + +function timer:RepsLeft(name) + + local t = self.Timers[name] + + return t and t.Repetitions + +end \ No newline at end of file diff --git a/media/lua/shared/Translate/EN/Sandbox_EN.txt b/media/lua/shared/Translate/EN/Sandbox_EN.txt index 20fe0ac..c682b69 100644 --- a/media/lua/shared/Translate/EN/Sandbox_EN.txt +++ b/media/lua/shared/Translate/EN/Sandbox_EN.txt @@ -3,5 +3,7 @@ Sandbox_EN = { Sandbox_TOC_CicatrizationSpeed = "Cicatrization Speed", Sandbox_TOC_WoundDirtynessMultiplier = "Wound Dirtyness Multiplier", Sandbox_TOC_SurgeonAbilityImportance = "Relevance of surgeon doctor ability", + Sandbox_TOC_ZombieAmputationDamageThreshold = "Zombie amputations damage treshold", + Sandbox_TOC_ZombieAmputationDamageChance = "Zombie amputations damage chance", } \ No newline at end of file diff --git a/media/sandbox-options.txt b/media/sandbox-options.txt index 3bb8c03..aa7634d 100644 --- a/media/sandbox-options.txt +++ b/media/sandbox-options.txt @@ -28,3 +28,22 @@ option TOC.SurgeonAbilityImportance translation = TOC_SurgeonAbilityImportance, } +option TOC.ZombieAmputationDamageThreshold +{ + type = integer, + min = 0, + max = 10, + default = 4, + page = TOC, + translation = TOC_ZombieAmputationDamageThreshold, +} + +option TOC.ZombieAmputationDamageChance +{ + type = integer, + min = 0, + max = 100, + default = 25, + page = TOC, + translation = TOC_ZombieAmputationDamageChance, +} \ No newline at end of file