moved folders

This commit is contained in:
ZioPao
2025-01-04 20:44:12 +01:00
parent 5d8e60a2e4
commit 6635cc19b2
215 changed files with 197 additions and 189 deletions

View File

@@ -0,0 +1,491 @@
if isServer() then return end
local CommandsData = require("TOC/CommandsData")
local StaticData = require("TOC/StaticData")
----------------
--- An instance will be abbreviated with dcInst
--- Handle all TOC mod data related stuff
---@class DataController
---@field username string
---@field tocData tocModDataType
---@field isDataReady boolean
---@field isResetForced boolean
local DataController = {}
DataController.instances = {}
---Setup a new Mod Data Handler
---@param username string
---@param isResetForced boolean?
---@return DataController
function DataController:new(username, isResetForced)
TOC_DEBUG.print("Creating new DataController instance for " .. username)
---@type DataController
---@diagnostic disable-next-line: missing-fields
local o = {}
setmetatable(o, self)
self.__index = self
o.username = username
o.isResetForced = isResetForced or false
o.isDataReady = false
-- We're gonna set it already from here, to prevent it from looping in SP (in case we need to fetch this instance)
DataController.instances[username] = o
local key = CommandsData.GetKey(username)
if isClient() then
-- In MP, we request the data from the server to trigger DataController.ReceiveData
ModData.request(key)
else
-- In SP, we handle it with another function which will reference the saved instance in DataController.instances
o:initSinglePlayer(key)
end
return o
end
---Setup a new toc mod data data class
---@param key string
function DataController:setup(key)
TOC_DEBUG.print("Running setup")
---@type tocModDataType
self.tocData = {
-- Generic stuff that does not belong anywhere else
isInitializing = true,
isIgnoredPartInfected = false,
isAnyLimbCut = false,
limbs = {},
prostheses = {}
}
---@type partDataType
local defaultParams = {
isCut = false, isInfected = false, isOperated = false, isCicatrized = false, isCauterized = false,
woundDirtyness = -1, cicatrizationTime = -1,
isVisible = false
}
-- Initialize limbs
for i=1, #StaticData.LIMBS_STR do
local limbName = StaticData.LIMBS_STR[i]
self.tocData.limbs[limbName] = {}
self:setLimbParams(StaticData.LIMBS_STR[i], defaultParams, 0)
end
-- Initialize prostheses stuff
for i=1, #StaticData.AMP_GROUPS_STR do
local group = StaticData.AMP_GROUPS_STR[i]
self.tocData.prostheses[group] = {
isProstEquipped = false,
prostFactor = 0,
}
end
-- Add it to client global mod data
ModData.add(key, self.tocData)
-- Sync with the server
self:apply()
-- -- Disable lock
-- self.tocData.isInitializing = false
-- ModData.add(key, self.tocData)
triggerEvent("OnSetupTocData")
end
---In case of desync between the table on ModData and the table here
---@param tocData tocModDataType
function DataController:applyOnlineData(tocData)
local key = CommandsData.GetKey(self.username)
ModData.add(key, tocData)
self.tocData = ModData.get(key)
end
---@param key string
function DataController:tryLoadLocalData(key)
self.tocData = ModData.get(key)
--TOC_DEBUG.printTable(self.tocData)
if self.tocData and self.tocData.limbs then
TOC_DEBUG.print("Found and loaded local data")
else
TOC_DEBUG.print("Local data failed to load! Running setup")
self:setup(key)
end
end
-----------------
--* Setters *--
---@param isDataReady boolean
function DataController:setIsDataReady(isDataReady)
self.isDataReady = isDataReady
end
---@param isResetForced boolean
function DataController:setIsResetForced(isResetForced)
self.isResetForced = isResetForced
end
---Set a generic boolean that toggles varies function of the mod
---@param isAnyLimbCut boolean
function DataController:setIsAnyLimbCut(isAnyLimbCut)
self.tocData.isAnyLimbCut = isAnyLimbCut
end
---Set isIgnoredPartInfected
---@param isIgnoredPartInfected boolean
function DataController:setIsIgnoredPartInfected(isIgnoredPartInfected)
self.tocData.isIgnoredPartInfected = isIgnoredPartInfected
end
---Set isCut
---@param limbName string
---@param isCut boolean
function DataController:setIsCut(limbName, isCut)
self.tocData.limbs[limbName].isCut = isCut
end
---Set isInfected
---@param limbName string
---@param isInfected boolean
function DataController:setIsInfected(limbName, isInfected)
self.tocData.limbs[limbName].isInfected = isInfected
end
---Set isCicatrized
---@param limbName string
---@param isCicatrized boolean
function DataController:setIsCicatrized(limbName, isCicatrized)
self.tocData.limbs[limbName].isCicatrized = isCicatrized
end
---Set isCauterized
---@param limbName string
---@param isCauterized boolean
function DataController:setIsCauterized(limbName, isCauterized)
self.tocData.limbs[limbName].isCauterized = isCauterized
end
---Set woundDirtyness
---@param limbName string
---@param woundDirtyness number
function DataController:setWoundDirtyness(limbName, woundDirtyness)
self.tocData.limbs[limbName].woundDirtyness = woundDirtyness
end
---Set cicatrizationTime
---@param limbName string
---@param cicatrizationTime number
function DataController:setCicatrizationTime(limbName, cicatrizationTime)
self.tocData.limbs[limbName].cicatrizationTime = cicatrizationTime
end
---Set isProstEquipped
---@param group string
---@param isProstEquipped boolean
function DataController:setIsProstEquipped(group, isProstEquipped)
self.tocData.prostheses[group].isProstEquipped = isProstEquipped
end
---Set prostFactor
---@param group string
---@param prostFactor number
function DataController:setProstFactor(group, prostFactor)
self.tocData.prostheses[group].prostFactor = prostFactor
end
-----------------
--* Getters *--
---comment
---@return boolean
function DataController:getIsDataReady()
return self.isDataReady
end
---Set a generic boolean that toggles varies function of the mod
---@return boolean
function DataController:getIsAnyLimbCut()
if not self.isDataReady then return false end
return self.tocData.isAnyLimbCut
end
---Get isIgnoredPartInfected
---@return boolean
function DataController:getIsIgnoredPartInfected()
if not self.isDataReady then return false end
return self.tocData.isIgnoredPartInfected
end
---Get isCut
---@param limbName string
---@return boolean
function DataController:getIsCut(limbName)
if not self.isDataReady then return false end
if self.tocData.limbs[limbName] then
return self.tocData.limbs[limbName].isCut
else
return false
end
end
---Get isVisible
---@param limbName string
---@return boolean
function DataController:getIsVisible(limbName)
if not self.isDataReady then return false end
return self.tocData.limbs[limbName].isVisible
end
---Get isCicatrized
---@param limbName string
---@return boolean
function DataController:getIsCicatrized(limbName)
if not self.isDataReady then return false end
return self.tocData.limbs[limbName].isCicatrized
end
---Get isCauterized
---@param limbName string
---@return boolean
function DataController:getIsCauterized(limbName)
if not self.isDataReady then return false end
return self.tocData.limbs[limbName].isCauterized
end
---Get isInfected
---@param limbName string
---@return boolean
function DataController:getIsInfected(limbName)
return self.tocData.limbs[limbName].isInfected
end
---Get woundDirtyness
---@param limbName string
---@return number
function DataController:getWoundDirtyness(limbName)
if not self.isDataReady then return -1 end
return self.tocData.limbs[limbName].woundDirtyness
end
---Get cicatrizationTime
---@param limbName string
---@return number
function DataController:getCicatrizationTime(limbName)
if not self.isDataReady then return -1 end
return self.tocData.limbs[limbName].cicatrizationTime
end
---Get isProstEquipped
---@param limbName string
---@return boolean
function DataController:getIsProstEquipped(limbName)
local prostGroup = StaticData.LIMBS_TO_AMP_GROUPS_MATCH_IND_STR[limbName]
return self.tocData.prostheses[prostGroup].isProstEquipped
end
---Get prostFactor
---@param group string
---@return number
function DataController:getProstFactor(group)
return self.tocData.prostheses[group].prostFactor
end
--* Limbs data handling *--
---Set a limb and its dependend limbs as cut
---@param limbName string
---@param isOperated boolean
---@param isCicatrized boolean
---@param isCauterized boolean
---@param surgeonFactor number?
function DataController:setCutLimb(limbName, isOperated, isCicatrized, isCauterized, surgeonFactor)
local cicatrizationTime = 0
if isCicatrized == false or isCauterized == false then
cicatrizationTime = StaticData.LIMBS_CICATRIZATION_TIME_IND_NUM[limbName] - surgeonFactor
end
---@type partDataType
local params = {isCut = true, isInfected = false, isOperated = isOperated, isCicatrized = isCicatrized, isCauterized = isCauterized, woundDirtyness = 0, isVisible = true}
self:setLimbParams(limbName, params, cicatrizationTime)
for i=1, #StaticData.LIMBS_DEPENDENCIES_IND_STR[limbName] do
local dependedLimbName = StaticData.LIMBS_DEPENDENCIES_IND_STR[limbName][i]
-- We don't care about isOperated, isCicatrized, isCauterized since this is depending on another limb
-- Same story for cicatrizationTime, which will be 0
-- isCicatrized is to true to prevent it from doing the cicatrization process
self:setLimbParams(dependedLimbName, {isCut = true, isInfected = false, isVisible = false, isCicatrized = true}, 0)
end
-- Set that a limb has been cut, to activate some functions without having to loop through the parts
self:setIsAnyLimbCut(true)
-- TODO In theory we should cache data from here, not AmputationHandler
end
---Set a limb data
---@param limbName string
---@param ampStatus partDataType {isCut, isInfected, isOperated, isCicatrized, isCauterized, isVisible}
---@param cicatrizationTime integer?
function DataController:setLimbParams(limbName, ampStatus, cicatrizationTime)
local limbData = self.tocData.limbs[limbName]
if ampStatus.isCut ~= nil then limbData.isCut = ampStatus.isCut end
if ampStatus.isInfected ~= nil then limbData.isInfected = ampStatus.isInfected end
if ampStatus.isOperated ~= nil then limbData.isOperated = ampStatus.isOperated end
if ampStatus.isCicatrized ~= nil then limbData.isCicatrized = ampStatus.isCicatrized end
if ampStatus.isCauterized ~= nil then limbData.isCauterized = ampStatus.isCauterized end
if ampStatus.woundDirtyness ~= nil then limbData.woundDirtyness = ampStatus.woundDirtyness end
if ampStatus.isVisible ~= nil then limbData.isVisible = ampStatus.isVisible end
if cicatrizationTime ~= nil then limbData.cicatrizationTime = cicatrizationTime end
end
--* Update statuses of a limb *--
---Decreases the cicatrization time
---@param limbName string
function DataController:decreaseCicatrizationTime(limbName)
self.tocData.limbs[limbName].cicatrizationTime = self.tocData.limbs[limbName].cicatrizationTime - 1
end
--* Global Mod Data Handling *--
function DataController:apply()
TOC_DEBUG.print("Applying data for " .. self.username)
ModData.transmit(CommandsData.GetKey(self.username))
-- if getPlayer():getUsername() ~= self.username then
-- sendClientCommand(CommandsData.modules.TOC_RELAY, CommandsData.server.Relay.RelayApplyFromOtherClient, {patientUsername = self.username} )
-- -- force request from the server for that other client...
-- end
end
---Online only, Global Mod Data doesn't trigger this in SP
---@param key string
---@param data tocModDataType
function DataController.ReceiveData(key, data)
-- During startup the game can return Bob as the player username, adding a useless ModData table
if key == "TOC_Bob" then return end
if not luautils.stringStarts(key, StaticData.MOD_NAME .. "_") then return end
TOC_DEBUG.print("ReceiveData for " .. key)
-- if data == nil or data.limbs == nil then
-- TOC_DEBUG.print("Data is nil, new character or something is wrong")
-- end
-- Get DataController instance if there was none for that user and reapply the correct ModData table as a reference
local username = key:sub(5)
local handler = DataController.GetInstance(username)
-- Bit of a workaround, but in a perfect world, I'd use the server to get the data and that would be it.
-- but Zomboid Mod Data handling is too finnicky at best to be that reliable, in case of an unwanted disconnection and what not,
-- so for now, I'm gonna assume that the local data (for the local client) is the
-- most recent (and correct) one instead of trying to fetch it from the server every single time
-- TODO Add update from server scenario
if handler.isResetForced then
TOC_DEBUG.print("Forced reset")
handler:setup(key)
elseif data and data.limbs then
-- Let's validate that the data structure is actually valid to prevent issues
if data.isUpdateFromServer then
TOC_DEBUG.print("Update from the server")
end
handler:applyOnlineData(data)
elseif username == getPlayer():getUsername() then
TOC_DEBUG.print("Trying to load local data or no data is available")
handler:tryLoadLocalData(key)
end
handler:setIsResetForced(false)
handler:setIsDataReady(true)
--TOC_DEBUG.print("Finished ReceiveData, triggering OnReceivedTocData")
triggerEvent("OnReceivedTocData", handler.username)
-- TODO We need an event to track if initialization has been finalized
-- if username == getPlayer():getUsername() and not handler.isResetForced then
-- handler:loadLocalData(key)
-- elseif handler.isResetForced or data == nil then
-- TOC_DEBUG.print("Data is nil or empty!?")
-- TOC_DEBUG.printTable(data)
-- handler:setup(key)
-- elseif data and data.limbs then
-- handler:applyOnlineData(data)
-- end
-- handler:setIsResetForced(false)
-- handler:setIsDataReady(true)
-- -- Event, triggers caching
-- triggerEvent("OnReceivedTocData", handler.username)
-- Transmit it back to the server
--ModData.transmit(key)
--TOC_DEBUG.print("Transmitting data after receiving it for: " .. handler.username)
end
Events.OnReceiveGlobalModData.Add(DataController.ReceiveData)
--- SP Only initialization
---@param key string
function DataController:initSinglePlayer(key)
self:tryLoadLocalData(key)
if self.tocData == nil or self.isResetForced then
self:setup(key)
end
self:setIsDataReady(true)
self:setIsResetForced(false)
-- Event, triggers caching
triggerEvent("OnReceivedTocData", self.username)
end
-------------------
---@param username string?
---@return DataController
function DataController.GetInstance(username)
if username == nil or username == "Bob" then
username = getPlayer():getUsername()
end
if DataController.instances[username] == nil then
TOC_DEBUG.print("Creating NEW instance for " .. username)
return DataController:new(username)
else
return DataController.instances[username]
end
end
function DataController.DestroyInstance(username)
if DataController.instances[username] ~= nil then
DataController.instances[username] = nil
end
end
return DataController

View File

@@ -0,0 +1,179 @@
local StaticData = require("TOC/StaticData")
local CommonMethods = require("TOC/CommonMethods")
---------------------------
--- Submodule to handle spawning the correct items after certain actions (ie: cutting a hand). LOCAL ONLY!
---@class ItemsController
local ItemsController = {}
--* Player Methods *--
---@class ItemsController.Player
ItemsController.Player = {}
---Returns the correct index for the textures of the amputation
---@param playerObj IsoPlayer
---@param isCicatrized boolean
---@return number
---@private
function ItemsController.Player.GetAmputationTexturesIndex(playerObj, isCicatrized)
-- FIX Broken
local textureString = playerObj:getHumanVisual():getSkinTexture()
local isHairy = textureString:sub(-1) == "a"
local matchedIndex = tonumber(textureString:match("%d%d")) -- it must always be at least 1
TOC_DEBUG.print("Texture string: " .. tostring(textureString))
if isHairy then
matchedIndex = matchedIndex + 5
end
if isCicatrized then
matchedIndex = matchedIndex + (isHairy and 5 or 10) -- We add 5 is it's the texture, else 10
end
TOC_DEBUG.print("isCicatrized = " .. tostring(isCicatrized))
TOC_DEBUG.print("Amputation Texture Index: " .. tostring(matchedIndex))
return matchedIndex - 1
end
---Main function to delete a clothing item
---@param playerObj IsoPlayer
---@param clothingItem InventoryItem
---@return boolean
---@private
function ItemsController.Player.RemoveClothingItem(playerObj, clothingItem)
if clothingItem and instanceof(clothingItem, "InventoryItem") then
playerObj:removeWornItem(clothingItem)
---@diagnostic disable-next-line: param-type-mismatch
playerObj:getInventory():Remove(clothingItem) -- Umbrella is wrong, can be an InventoryItem too
TOC_DEBUG.print("found and deleted" .. tostring(clothingItem))
-- Reset model
playerObj:resetModelNextFrame()
return true
end
return false
end
---Search and deletes an old amputation clothing item on the same side
---@param playerObj IsoPlayer
---@param limbName string
function ItemsController.Player.DeleteOldAmputationItem(playerObj, limbName)
local side = CommonMethods.GetSide(limbName)
for partName, _ in pairs(StaticData.PARTS_IND_STR) do
local othLimbName = partName .. "_" .. side
local othClothingItemName = StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. othLimbName
local othClothingItem = playerObj:getInventory():FindAndReturn(othClothingItemName)
-- If we manage to find and remove an item, then we should stop this function.
---@cast othClothingItem InventoryItem
if ItemsController.Player.RemoveClothingItem(playerObj, othClothingItem) then return end
end
end
---Deletes all the old amputation items, used for resets
---@param playerObj IsoPlayer
function ItemsController.Player.DeleteAllOldAmputationItems(playerObj)
-- This part is a workaround for a pretty shitty implementation on the java side. Check ProsthesisHandler for more infos
local group = BodyLocations.getGroup("Human")
group:setMultiItem("TOC_Arm", false)
group:setMultiItem("TOC_ArmProst", false)
for i = 1, #StaticData.LIMBS_STR do
local limbName = StaticData.LIMBS_STR[i]
local clothItemName = StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limbName
local clothItem = playerObj:getInventory():FindAndReturn(clothItemName)
---@cast clothItem InventoryItem
ItemsController.Player.RemoveClothingItem(playerObj, clothItem)
end
-- Reset model just in case
playerObj:resetModel()
group:setMultiItem("TOC_Arm", true)
group:setMultiItem("TOC_ArmProst", true)
end
---Spawns and equips the correct amputation item to the player.
---@param playerObj IsoPlayer
---@param limbName string
function ItemsController.Player.SpawnAmputationItem(playerObj, limbName)
TOC_DEBUG.print("clothing name " .. StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limbName)
local clothingItem = playerObj:getInventory():AddItem(StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limbName)
local texId = ItemsController.Player.GetAmputationTexturesIndex(playerObj, false)
---@cast clothingItem InventoryItem
clothingItem:getVisual():setTextureChoice(texId) -- it counts from 0, so we have to subtract 1
playerObj:setWornItem(clothingItem:getBodyLocation(), clothingItem)
end
---Search through worn items and modifies a specific amputation item
---@param playerObj IsoPlayer
---@param limbName string
---@param isCicatrized boolean
function ItemsController.Player.OverrideAmputationItemVisuals(playerObj, limbName, isCicatrized)
local wornItems = playerObj:getWornItems()
local fullType = StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limbName
for i = 1, wornItems:size() do
local it = wornItems:get(i - 1)
if it then
local wornItem = wornItems:get(i - 1):getItem()
TOC_DEBUG.print(wornItem:getFullType())
if wornItem:getFullType() == fullType then
TOC_DEBUG.print("Found amputation item for " .. limbName)
-- change it here
local texId = ItemsController.Player.GetAmputationTexturesIndex(playerObj, isCicatrized)
wornItem:getVisual():setTextureChoice(texId)
playerObj:resetModelNextFrame() -- necessary to update the model
return
end
end
end
end
--* Zombie Methods *--
---@class ItemsController.Zombie
ItemsController.Zombie = {}
---Set an amputation to a zombie
---@param zombie IsoZombie
---@param amputationFullType string Full Type
function ItemsController.Zombie.SpawnAmputationItem(zombie, amputationFullType)
local texId = ItemsController.Zombie.GetAmputationTexturesIndex(zombie)
local zombieVisuals = zombie:getItemVisuals()
local itemVisual = ItemVisual:new()
itemVisual:setItemType(amputationFullType)
itemVisual:setTextureChoice(texId)
if zombieVisuals then zombieVisuals:add(itemVisual) end
zombie:resetModelNextFrame()
-- Spawn the item too in the inventory to keep track of stuff this way. It's gonna get deleted when we reload the game
local zombieInv = zombie:getInventory()
zombieInv:AddItem(amputationFullType)
-- TODO Remove objects in that part of the body to prevent items floating in mid air
end
function ItemsController.Zombie.GetAmputationTexturesIndex(zombie)
local x = zombie:getHumanVisual():getSkinTexture()
-- Starting ID for zombies = 20
-- 3 levels
local matchedIndex = tonumber(x:match("ZedBody0(%d)")) - 1
matchedIndex = matchedIndex * 3
local level = tonumber(x:match("%d$")) - 1 -- it's from 1 to 3, but we're using it like 0 indexed arrays
local finalId = 20 + matchedIndex + level
--print("Zombie texture index: " .. tostring(finalId))
return finalId
end
return ItemsController

View File

@@ -0,0 +1,369 @@
local LocalPlayerController = require("TOC/Controllers/LocalPlayerController")
local DataController = require("TOC/Controllers/DataController")
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
local CommonMethods = require("TOC/CommonMethods")
local StaticData = require("TOC/StaticData")
-----------------
--* TIMED ACTIONS *--
-- We want to be able to modify how long actions are gonna take,
-- depending on amputation status and kind of action. Also, when the
-- player has not completely cicatrized their own wounds, and try to do any action with
-- a prosthesis on, that can trigger random bleeds.
local function CheckHandFeasibility(limbName)
TOC_DEBUG.print("Checking hand feasibility: " .. limbName)
local dcInst = DataController.GetInstance()
local isFeasible = not dcInst:getIsCut(limbName) or dcInst:getIsProstEquipped(limbName)
TOC_DEBUG.print("isFeasible: " .. tostring(isFeasible))
return isFeasible
end
--* Time to perform actions overrides *--
local og_ISBaseTimedAction_adjustMaxTime = ISBaseTimedAction.adjustMaxTime
--- Adjust time
---@diagnostic disable-next-line: duplicate-set-field
function ISBaseTimedAction:adjustMaxTime(maxTime)
local time = og_ISBaseTimedAction_adjustMaxTime(self, maxTime)
-- Exceptions handling, if we find that parameter then we just use the original time
local actionsQueue = ISTimedActionQueue.getTimedActionQueue(getPlayer())
if actionsQueue and actionsQueue.current and actionsQueue.skipTOC then
--TOC_DEBUG.print("Should skip TOC stuff")
return time
end
-- Action is valid, check if we have any cut limb and then modify maxTime
local dcInst = DataController.GetInstance()
if time ~= -1 and dcInst and dcInst:getIsAnyLimbCut() then
--TOC_DEBUG.print("Overriding adjustMaxTime")
local pl = getPlayer()
local amputatedLimbs = CachedDataHandler.GetAmputatedLimbs(pl:getUsername())
for k, _ in pairs(amputatedLimbs) do
local limbName = k
local perkAmp = Perks["Side_" .. CommonMethods.GetSide(limbName)]
local perkLevel = pl:getPerkLevel(perkAmp)
if dcInst:getIsProstEquipped(limbName) then
-- TODO We should separate this in multiple perks, since this is gonna be a generic familiarity and could make no actual sense
local perkProst = Perks["ProstFamiliarity"]
perkLevel = perkLevel + pl:getPerkLevel(perkProst)
end
local perkLevelScaled
if perkLevel ~= 0 then perkLevelScaled = perkLevel / 10 else perkLevelScaled = 0 end
TOC_DEBUG.print("Perk Level: " .. tostring(perkLevel))
TOC_DEBUG.print("OG time: " .. tostring(time))
-- Modified Time shouldn't EVER be lower compared to the og one.
local modifiedTime = time * (StaticData.LIMBS_TIME_MULTIPLIER_IND_NUM[limbName] - perkLevelScaled)
if modifiedTime >= time then
time = modifiedTime
end
--TOC_DEBUG.print("Modified time: " .. tostring(time))
end
end
--TOC_DEBUG.print("New time with amputations: " .. tostring(time))
return time
end
--* Random bleeding during cicatrization + Perks leveling override *--
local og_ISBaseTimedAction_perform = ISBaseTimedAction.perform
--- After each action, level up perks
---@diagnostic disable-next-line: duplicate-set-field
function ISBaseTimedAction:perform()
og_ISBaseTimedAction_perform(self)
TOC_DEBUG.print("Running ISBaseTimedAction.perform override")
TOC_DEBUG.print("max time: " .. tostring(self.maxTime))
local dcInst = DataController.GetInstance()
if not dcInst:getIsAnyLimbCut() then return end
local amputatedLimbs = CachedDataHandler.GetAmputatedLimbs(LocalPlayerController.username)
local xp = self.maxTime / 100
for k, _ in pairs(amputatedLimbs) do
local limbName = k
-- We're checking for only "visible" amputations to prevent from having bleeds everywhere
if dcInst:getIsCut(limbName) and dcInst:getIsVisible(limbName) then
local side = CommonMethods.GetSide(limbName)
LocalPlayerController.playerObj:getXp():AddXP(Perks["Side_" .. side], xp)
if not dcInst:getIsCicatrized(limbName) and dcInst:getIsProstEquipped(limbName) then
TOC_DEBUG.print("Trying for bleed, player met the criteria")
LocalPlayerController.TryRandomBleed(self.character, limbName)
end
-- Level up prosthesis perk
if dcInst:getIsProstEquipped(limbName) then
LocalPlayerController.playerObj:getXp():AddXP(Perks["ProstFamiliarity"], xp)
end
end
end
end
--* EQUIPPING ITEMS *--
-- Check wheter the player can equip items or not, for example dual wielding when you only have one
-- hand (and no prosthesis) should be disabled. Same thing for some werable items, like watches.
---@class ISEquipWeaponAction
---@field character IsoPlayer
--* Equipping items overrides *--
local og_ISEquipWeaponAction_isValid = ISEquipWeaponAction.isValid
---Add a condition to check the feasibility of having 2 handed weapons or if both arms are cut off
---@return boolean
---@diagnostic disable-next-line: duplicate-set-field
function ISEquipWeaponAction:isValid()
local isValid = og_ISEquipWeaponAction_isValid(self)
if isValid then
local isPrimaryHandValid = CachedDataHandler.GetHandFeasibility(StaticData.SIDES_IND_STR.R)
local isSecondaryHandValid = CachedDataHandler.GetHandFeasibility(StaticData.SIDES_IND_STR.L)
-- Both hands are cut off, so it's impossible to equip in any way
--TOC_DEBUG.print("isPrimaryHandValid : " .. tostring(isPrimaryHandValid))
--TOC_DEBUG.print("isSecondaryHandValid : " .. tostring(isSecondaryHandValid))
if not isPrimaryHandValid and not isSecondaryHandValid then
isValid = false
end
end
return isValid
end
---A recreation of the original method, but with amputations in mind
function ISEquipWeaponAction:performWithAmputation()
TOC_DEBUG.print("running ISEquipWeaponAction performWithAmputation")
local hand = nil
local otherHand = nil
local getMethodFirst = nil
local setMethodFirst = nil
local getMethodSecond = nil
local setMethodSecond = nil
if self.primary then
hand = StaticData.LIMBS_IND_STR.Hand_R
otherHand = StaticData.LIMBS_IND_STR.Hand_L
getMethodFirst = self.character.getSecondaryHandItem
setMethodFirst = self.character.setSecondaryHandItem
getMethodSecond = self.character.getPrimaryHandItem
setMethodSecond = self.character.setPrimaryHandItem
else
hand = StaticData.LIMBS_IND_STR.Hand_L
otherHand = StaticData.LIMBS_IND_STR.Hand_R
getMethodFirst = self.character.getPrimaryHandItem
setMethodFirst = self.character.setPrimaryHandItem
getMethodSecond = self.character.getSecondaryHandItem
setMethodSecond = self.character.setSecondaryHandItem
end
local isFirstValid = CheckHandFeasibility(hand)
local isSecondValid = CheckHandFeasibility(otherHand)
if not self.twoHands then
if getMethodFirst(self.character) and getMethodFirst(self.character):isRequiresEquippedBothHands() then
setMethodFirst(self.character, nil)
-- if this weapon is already equiped in the 2nd hand, we remove it
elseif (getMethodFirst(self.character) == self.item or getMethodFirst(self.character) == getMethodSecond(self.character)) then
setMethodFirst(self.character, nil)
-- if we are equipping a handgun and there is a weapon in the secondary hand we remove it
elseif instanceof(self.item, "HandWeapon") and self.item:getSwingAnim() and self.item:getSwingAnim() == "Handgun" then
if getMethodFirst(self.character) and instanceof(getMethodFirst(self.character), "HandWeapon") then
setMethodFirst(self.character, nil)
end
else
setMethodSecond(self.character, nil)
-- TODO We should use the CachedData indexable instead of dcInst
if isFirstValid then
setMethodSecond(self.character, self.item)
-- Check other HAND!
elseif isSecondValid then
setMethodFirst(self.character, self.item)
end
end
else
setMethodFirst(self.character, nil)
setMethodSecond(self.character, nil)
-- TOC_DEBUG.print("First Hand: " .. tostring(hand))
-- --TOC_DEBUG.print("Prost Group: " .. tostring(prostGroup))
-- TOC_DEBUG.print("Other Hand: " .. tostring(otherHand))
-- --TOC_DEBUG.print("Other Prost Group: " .. tostring(otherProstGroup))
-- TOC_DEBUG.print("isPrimaryHandValid: " .. tostring(isFirstValid))
-- TOC_DEBUG.print("isSecondaryHandValid: " .. tostring(isSecondValid))
if isFirstValid then
setMethodSecond(self.character, self.item)
end
if isSecondValid then
setMethodFirst(self.character, self.item)
end
end
end
local og_ISEquipWeaponAction_perform = ISEquipWeaponAction.perform
---@diagnostic disable-next-line: duplicate-set-field
function ISEquipWeaponAction:perform()
og_ISEquipWeaponAction_perform(self)
--if self.character == getPlayer() then
local dcInst = DataController.GetInstance(self.character:getUsername())
-- Just check it any limb has been cut. If not, we can just return from here
if dcInst:getIsAnyLimbCut() then
self:performWithAmputation()
end
--end
end
function ISInventoryPaneContextMenu.doEquipOption(context, playerObj, isWeapon, items, player)
-- check if hands if not heavy damaged
if (not playerObj:isPrimaryHandItem(isWeapon) or (playerObj:isPrimaryHandItem(isWeapon) and playerObj:isSecondaryHandItem(isWeapon))) and not getSpecificPlayer(player):getBodyDamage():getBodyPart(BodyPartType.Hand_R):isDeepWounded() and (getSpecificPlayer(player):getBodyDamage():getBodyPart(BodyPartType.Hand_R):getFractureTime() == 0 or getSpecificPlayer(player):getBodyDamage():getBodyPart(BodyPartType.Hand_R):getSplintFactor() > 0) then
-- forbid reequipping skinned items to avoid multiple problems for now
local add = true
if playerObj:getSecondaryHandItem() == isWeapon and isWeapon:getScriptItem():getReplaceWhenUnequip() then
add = false
end
if add then
local equipOption = context:addOption(getText("ContextMenu_Equip_Primary"), items,
ISInventoryPaneContextMenu.OnPrimaryWeapon, player)
equipOption.notAvailable = not CheckHandFeasibility(StaticData.LIMBS_IND_STR.Hand_R)
end
end
if (not playerObj:isSecondaryHandItem(isWeapon) or (playerObj:isPrimaryHandItem(isWeapon) and playerObj:isSecondaryHandItem(isWeapon))) and not getSpecificPlayer(player):getBodyDamage():getBodyPart(BodyPartType.Hand_L):isDeepWounded() and (getSpecificPlayer(player):getBodyDamage():getBodyPart(BodyPartType.Hand_L):getFractureTime() == 0 or getSpecificPlayer(player):getBodyDamage():getBodyPart(BodyPartType.Hand_L):getSplintFactor() > 0) then
-- forbid reequipping skinned items to avoid multiple problems for now
local add = true
if playerObj:getPrimaryHandItem() == isWeapon and isWeapon:getScriptItem():getReplaceWhenUnequip() then
add = false
end
if add then
local equipOption = context:addOption(getText("ContextMenu_Equip_Secondary"), items,
ISInventoryPaneContextMenu.OnSecondWeapon, player)
equipOption.notAvailable = not CheckHandFeasibility(StaticData.LIMBS_IND_STR.Hand_L)
end
end
end
local noHandsImpossibleActions = {
getText("ContextMenu_Add_escape_rope_sheet"),
getText("ContextMenu_Add_escape_rope"),
getText("ContextMenu_Remove_escape_rope"),
getText("ContextMenu_Barricade"),
getText("ContextMenu_Unbarricade"),
getText("ContextMenu_MetalBarricade"),
getText("ContextMenu_MetalBarBarricade"),
getText("ContextMenu_Open_window"),
getText("ContextMenu_Close_window"),
getText("ContextMenu_PickupBrokenGlass"),
getText("ContextMenu_Open_door"),
getText("ContextMenu_Close_door"),
}
local og_ISWorldObjectContextMenu_createMenu = ISWorldObjectContextMenu.createMenu
---@param player integer
---@param worldobjects any
---@param x any
---@param y any
---@param test any
function ISWorldObjectContextMenu.createMenu(player, worldobjects, x, y, test)
---@type ISContextMenu
local ogContext = og_ISWorldObjectContextMenu_createMenu(player, worldobjects, x, y, test)
-- goddamn it, zomboid devs. ogContext could be a boolean...
-- TBH, I don't really care about gamepad support, but all this method can break stuff. Let's just disable thisfor gamepad users.
if type(ogContext) == "boolean" or type(ogContext) == "string" then
return ogContext
end
-- The vanilla game doesn't count an item in the off hand as "equipped" for picking up glass. Let's fix that here
local brokenGlassOption = ogContext:getOptionFromName(getText("ContextMenu_RemoveBrokenGlass"))
if brokenGlassOption then
local playerObj = getSpecificPlayer(player)
if (CachedDataHandler.GetHandFeasibility(StaticData.SIDES_IND_STR.R) and playerObj:getPrimaryHandItem()) or
(CachedDataHandler.GetHandFeasibility(StaticData.SIDES_IND_STR.L) and playerObj:getSecondaryHandItem())
then
brokenGlassOption.notAvailable = false
brokenGlassOption.toolTip = nil -- This is active only when you can't do the action.
end
end
-- check if no hands, disable various interactions
if not CachedDataHandler.GetBothHandsFeasibility() then
TOC_DEBUG.print("NO hands :((")
for i = 1, #noHandsImpossibleActions do
local optionName = noHandsImpossibleActions[i]
local option = ogContext:getOptionFromName(optionName)
if option then
option.notAvailable = true
end
end
end
return ogContext
end
--* DISABLE WEARING CERTAIN ITEMS WHEN NO LIMB
local function CheckLimbFeasibility(limbName)
local dcInst = DataController.GetInstance()
local isFeasible = not dcInst:getIsCut(limbName) or dcInst:getIsProstEquipped(limbName)
--TOC_DEBUG.print("isFeasible="..tostring(isFeasible))
return isFeasible
end
---@param obj any
---@param wrappedFunc function
---@param item InventoryItem
---@return boolean
local function WrapClothingAction(obj, wrappedFunc, item)
local isEquippable = wrappedFunc(obj)
if not isEquippable then return isEquippable end
local itemBodyLoc = item:getBodyLocation()
local limbToCheck = StaticData.AFFECTED_BODYLOCS_TO_LIMBS_IND_STR[itemBodyLoc]
if CheckLimbFeasibility(limbToCheck) then return isEquippable else return false end
end
---@diagnostic disable-next-line: duplicate-set-field
local og_ISWearClothing_isValid = ISWearClothing.isValid
function ISWearClothing:isValid()
return WrapClothingAction(self, og_ISWearClothing_isValid, self.item)
end
local og_ISClothingExtraAction_isValid = ISClothingExtraAction.isValid
---@diagnostic disable-next-line: duplicate-set-field
function ISClothingExtraAction:isValid()
return WrapClothingAction(self, og_ISClothingExtraAction_isValid, InventoryItemFactory.CreateItem(self.extra))
end

View File

@@ -0,0 +1,391 @@
local DataController = require("TOC/Controllers/DataController")
local CommonMethods = require("TOC/CommonMethods")
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
local StaticData = require("TOC/StaticData")
require("TOC/Events")
-----------
-- Handle ONLY stuff for the local client
---@class LocalPlayerController
---@field playerObj IsoPlayer
---@field username string
---@field hasBeenDamaged boolean
local LocalPlayerController = {}
--* Initialization
---Setup the Player Handler and modData, only for local client
---@param isForced boolean?
function LocalPlayerController.InitializePlayer(isForced)
local playerObj = getPlayer()
local username = playerObj:getUsername()
TOC_DEBUG.print("Initializing local player: " .. username)
DataController:new(username, isForced)
LocalPlayerController.playerObj = playerObj
LocalPlayerController.username = username
--Setup the CicatrizationUpdate event and triggers it once
Events.OnAmputatedLimb.Add(LocalPlayerController.ToggleUpdateAmputations)
LocalPlayerController.ToggleUpdateAmputations()
-- Since isForced is used to reset an existing player data, we're gonna clean their ISHealthPanel table too
if isForced then
local ItemsController = require("TOC/Controllers/ItemsController")
ItemsController.Player.DeleteAllOldAmputationItems(playerObj)
CachedDataHandler.Setup(username)
end
-- Set a bool to use an overriding GetDamagedParts
SetHealthPanelTOC()
end
---Handles the traits
function LocalPlayerController.ManageTraits()
-- Local player
local playerObj = getPlayer()
local AmputationHandler = require("TOC/Handlers/AmputationHandler")
for k, v in pairs(StaticData.TRAITS_BP) do
if playerObj:HasTrait(k) then
-- Once we find one, we should be done since they're exclusive
TOC_DEBUG.print("Player has amputation trait " .. k .. ", executing it")
local tempHandler = AmputationHandler:new(v, playerObj)
tempHandler:execute(false) -- No damage
tempHandler:close()
-- The wound should be already cicatrized
local dcInst = DataController.GetInstance()
LocalPlayerController.HandleSetCicatrization(DataController.GetInstance(), playerObj, v)
dcInst:apply()
return
end
end
end
-- We need to manage traits when we're done setupping everything
-- It shouldn't be done every single time we initialize the player, fetching data, etc.
Events.OnSetupTocData.Add(LocalPlayerController.ManageTraits)
----------------------------------------------------------
--* Health *--
---Used to heal an area that has been cut previously. There's an exception for bites, those are managed differently
---@param bodyPart BodyPart
function LocalPlayerController.HealArea(bodyPart)
bodyPart:setFractureTime(0)
bodyPart:setScratched(false, true)
bodyPart:setScratchTime(0)
bodyPart:setBleeding(false)
bodyPart:setBleedingTime(0)
bodyPart:SetBitten(false)
bodyPart:setBiteTime(0)
bodyPart:setCut(false)
bodyPart:setCutTime(0)
bodyPart:setDeepWounded(false)
bodyPart:setDeepWoundTime(0)
bodyPart:setHaveBullet(false, 0)
bodyPart:setHaveGlass(false)
bodyPart:setSplint(false, 0)
end
---@param bodyDamage BodyDamage
---@param bodyPart BodyPart
---@param limbName string
---@param dcInst DataController
function LocalPlayerController.HealZombieInfection(bodyDamage, bodyPart, limbName, dcInst)
if bodyDamage:isInfected() == false then return end
bodyDamage:setInfected(false)
bodyDamage:setInfectionMortalityDuration(-1)
bodyDamage:setInfectionTime(-1)
bodyDamage:setInfectionLevel(-1)
bodyPart:SetInfected(false)
dcInst:setIsInfected(limbName, false)
dcInst:apply()
end
---@param character IsoPlayer
---@param limbName string
function LocalPlayerController.TryRandomBleed(character, limbName)
-- Chance should be determined by the cicatrization time
local cicTime = DataController.GetInstance():getCicatrizationTime(limbName)
if cicTime == 0 then return end
-- TODO This is just a placeholder, we need to figure out a better way to calculate this chance
local normCicTime = CommonMethods.Normalize(cicTime, 0, StaticData.LIMBS_CICATRIZATION_TIME_IND_NUM[limbName]) / 2
TOC_DEBUG.print("OG cicTime: " .. tostring(cicTime))
TOC_DEBUG.print("Normalized cic time : " .. tostring(normCicTime))
local chance = ZombRandFloat(0.0, 1.0)
if chance > normCicTime then
TOC_DEBUG.print("Triggered bleeding from non cicatrized wound")
local adjacentBodyPartType = BodyPartType[StaticData.LIMBS_ADJACENT_IND_STR[limbName]]
-- we need to check if the wound is already bleeding before doing anything else to prevent issues with bandages
local bp = character:getBodyDamage():getBodyPart(adjacentBodyPartType)
bp:setBleedingTime(20) -- TODO Should depend on cicatrization instead of a fixed time
-- ADD Could break bandages if bleeding is too much?
--character:getBodyDamage():getBodyPart(adjacentBodyPartType):setBleeding(true)
end
end
-------------------------
--* Damage handling *--
--- Locks OnPlayerGetDamage event, to prevent it from getting spammed constantly
LocalPlayerController.hasBeenDamaged = false
---Check if the player has in infected body part or if they have been hit in a cut area
---@param character IsoPlayer|IsoGameCharacter
function LocalPlayerController.HandleDamage(character)
--TOC_DEBUG.print("Player got hit!")
-- TOC_DEBUG.print(damageType)
if character ~= getPlayer() then
-- Disable lock before doing anything else
LocalPlayerController.hasBeenDamaged = false
return
end
local bd = character:getBodyDamage()
local dcInst = DataController.GetInstance()
local modDataNeedsUpdate = false
for i = 1, #StaticData.LIMBS_STR do
local limbName = StaticData.LIMBS_STR[i]
local bptEnum = StaticData.LIMBS_TO_BODYLOCS_IND_BPT[limbName]
local bodyPart = bd:getBodyPart(bptEnum)
if dcInst:getIsCut(limbName) then
-- Generic injury, let's heal it since they already cut the limb off
if bodyPart:HasInjury() then
TOC_DEBUG.print("Healing area - " .. limbName)
LocalPlayerController.HealArea(bodyPart)
end
-- Special case for bites\zombie infections
if bodyPart:IsInfected() then
TOC_DEBUG.print("Healed from zombie infection - " .. limbName)
LocalPlayerController.HealZombieInfection(bd, bodyPart, limbName, dcInst)
end
else
if (bodyPart:bitten() or bodyPart:IsInfected()) and not dcInst:getIsInfected(limbName) then
dcInst:setIsInfected(limbName, true)
modDataNeedsUpdate = true
end
end
end
-- Check other body parts that are not included in the mod, if there's a bite there then the player is fucked
-- We can skip this loop if the player has been infected. The one before we kinda need it to handle correctly the bites in case the player wanna cut stuff off anyway
if not dcInst:getIsIgnoredPartInfected() then
for i = 1, #StaticData.IGNORED_BODYLOCS_BPT do
local bodyPartType = StaticData.IGNORED_BODYLOCS_BPT[i]
local bodyPart = bd:getBodyPart(bodyPartType)
if bodyPart and (bodyPart:bitten() or bodyPart:IsInfected()) then
dcInst:setIsIgnoredPartInfected(true)
modDataNeedsUpdate = true
end
end
end
if modDataNeedsUpdate then
dcInst:apply()
end
-- Disable the lock
LocalPlayerController.hasBeenDamaged = false
end
---Setup HandleDamage, triggered by OnPlayerGetDamage. To prevent a spam caused by this awful event, we use a bool lock
---@param character IsoPlayer|IsoGameCharacter
---@param damageType string
---@param damageAmount number
function LocalPlayerController.OnGetDamage(character, damageType, damageAmount)
if LocalPlayerController.hasBeenDamaged == false then
-- Start checks
LocalPlayerController.hasBeenDamaged = true
LocalPlayerController.HandleDamage(character)
end
end
Events.OnPlayerGetDamage.Add(LocalPlayerController.OnGetDamage)
--* Amputation Loop handling *--
---Updates the cicatrization process, run when a limb has been cut. Run it every 1 hour
function LocalPlayerController.UpdateAmputations()
local dcInst = DataController.GetInstance()
if not dcInst:getIsDataReady() then
TOC_DEBUG.print("Data not ready for UpdateAmputations, waiting next loop")
return
end
if not dcInst:getIsAnyLimbCut() then
Events.EveryHours.Remove(LocalPlayerController.UpdateAmputations)
end
local pl = LocalPlayerController.playerObj
local visual = pl:getHumanVisual()
local amputatedLimbs = CachedDataHandler.GetAmputatedLimbs(pl:getUsername())
local needsUpdate = false
for k, _ in pairs(amputatedLimbs) do
local limbName = k
local isCicatrized = dcInst:getIsCicatrized(limbName)
if not isCicatrized then
needsUpdate = true
local cicTime = dcInst:getCicatrizationTime(limbName)
TOC_DEBUG.print("Updating cicatrization for " .. tostring(limbName))
--* Dirtyness of the wound
-- We need to get the BloodBodyPartType to find out how dirty the zone is
local bbptEnum = BloodBodyPartType[limbName]
local modifier = 0.01 * SandboxVars.TOC.WoundDirtynessMultiplier
local dirtynessVis = visual:getDirt(bbptEnum) + visual:getBlood(bbptEnum)
local dirtynessWound = dcInst:getWoundDirtyness(limbName) + modifier
local dirtyness = dirtynessVis + dirtynessWound
if dirtyness > 1 then
dirtyness = 1
end
dcInst:setWoundDirtyness(limbName, dirtyness)
TOC_DEBUG.print("Dirtyness for this zone: " .. tostring(dirtyness))
--* Cicatrization
local cicDec = SandboxVars.TOC.CicatrizationSpeed - dirtyness
if cicDec <= 0 then cicDec = 0.1 end
cicTime = cicTime - cicDec
TOC_DEBUG.print("New cicatrization time: " .. tostring(cicTime))
if cicTime <= 0 then
LocalPlayerController.HandleSetCicatrization(dcInst, pl, limbName)
else
dcInst:setCicatrizationTime(limbName, cicTime)
end
end
end
if needsUpdate then
TOC_DEBUG.print("updating modData from cicatrization loop")
dcInst:apply() -- TODO This is gonna be heavy. Not entirely sure
else
TOC_DEBUG.print("Removing UpdateAmputations")
Events.EveryHours.Remove(LocalPlayerController.UpdateAmputations) -- We can remove it safely, no cicatrization happening here boys
end
TOC_DEBUG.print("updating cicatrization and wound dirtyness!")
end
---Starts safely the loop to update cicatrzation
function LocalPlayerController.ToggleUpdateAmputations()
TOC_DEBUG.print("Activating amputation handling loop (if it wasn't active before)")
CommonMethods.SafeStartEvent("EveryHours", LocalPlayerController.UpdateAmputations)
end
--* Cicatrization and cicatrization visuals *--
---Set the boolean and cicTime in DCINST and the visuals for the amputated limb
---@param dcInst DataController
---@param playerObj IsoPlayer
---@param limbName string
function LocalPlayerController.HandleSetCicatrization(dcInst, playerObj, limbName)
TOC_DEBUG.print("Setting cicatrization to " .. tostring(limbName))
dcInst:setIsCicatrized(limbName, true)
dcInst:setCicatrizationTime(limbName, 0)
-- Set visuals for the amputation
local ItemsController = require("TOC/Controllers/ItemsController")
ItemsController.Player.OverrideAmputationItemVisuals(playerObj, limbName, true)
end
--* Object drop handling when amputation occurs
function LocalPlayerController.CanItemBeEquipped(itemObj, limbName)
local bl = itemObj:getBodyLocation()
local side = CommonMethods.GetSide(limbName)
local sideStr = CommonMethods.GetSideFull(side)
-- TODO Check from DataController
if string.contains(limbName, "Hand_") and (bl == sideStr .. "_MiddleFinger" or bl == sideStr .. "_RingFinger") then
return false
end
if string.contains(limbName, "ForeArm_") and (bl == sideStr .. "Wrist") then
return false
end
return true
end
--- Drop all items from the affected limb
---@param limbName string
function LocalPlayerController.DropItemsAfterAmputation(limbName)
TOC_DEBUG.print("Triggered DropItemsAfterAmputation")
local side = CommonMethods.GetSide(limbName)
local sideStr = CommonMethods.GetSideFull(side)
local pl = getPlayer()
local wornItems = pl:getWornItems()
for i = 1, wornItems:size() do
local it = wornItems:get(i - 1)
if it then
local wornItem = wornItems:get(i - 1):getItem()
TOC_DEBUG.print(wornItem:getBodyLocation())
local bl = wornItem:getBodyLocation()
if string.contains(limbName, "Hand_") and (bl == sideStr .. "_MiddleFinger" or bl == sideStr .. "_RingFinger") then
pl:removeWornItem(wornItem)
end
if string.contains(limbName, "ForeArm_") and (bl == sideStr .. "Wrist") then
pl:removeWornItem(wornItem)
end
end
end
-- TODO Consider 2 handed weapons too
-- equipped items too
if side == "R" then
pl:setPrimaryHandItem(nil)
elseif side == "L" then
pl:setSecondaryHandItem(nil)
end
end
Events.OnAmputatedLimb.Add(LocalPlayerController.DropItemsAfterAmputation)
Events.OnProsthesisUnequipped.Add(LocalPlayerController.DropItemsAfterAmputation)
return LocalPlayerController

View File

@@ -0,0 +1,112 @@
local CommonMethods = require("TOC/CommonMethods")
---@class TourniquetController
local TourniquetController = {
bodyLoc = "TOC_ArmAccessory"
}
function TourniquetController.CheckTourniquetOnLimb(player, limbName)
local side = CommonMethods.GetSide(limbName)
local wornItems = player:getWornItems()
for j=1,wornItems:size() do
local wornItem = wornItems:get(j-1)
local fType = wornItem:getItem():getFullType()
if TourniquetController.IsItemTourniquet(fType) then
-- Check side
if luautils.stringEnds(fType, side) then
TOC_DEBUG.print("Found acceptable tourniquet")
return true
end
end
end
return false
end
function TourniquetController.IsItemTourniquet(fType)
-- TODO Add legs stuff
return string.contains(fType, "Surg_Arm_Tourniquet_")
end
---@param player IsoPlayer
---@param limbName string
---@return boolean
function TourniquetController.CheckTourniquet(player, limbName)
local side = CommonMethods.GetSide(limbName)
local wornItems = player:getWornItems()
for j=1,wornItems:size() do
local wornItem = wornItems:get(j-1)
local fType = wornItem:getItem():getFullType()
if string.contains(fType, "Surg_Arm_Tourniquet_") then
-- Check side
if luautils.stringEnds(fType, side) then
TOC_DEBUG.print("Found acceptable tourniquet")
return true
end
end
end
return false
end
---@private
---@param obj any self
---@param wrappedFunc function
function TourniquetController.WrapClothingAction(obj, wrappedFunc)
local isTourniquet = TourniquetController.IsItemTourniquet(obj.item:getFullType())
local group
if isTourniquet then
group = BodyLocations.getGroup("Human")
group:setMultiItem(TourniquetController.bodyLoc, false)
end
local ogValue = wrappedFunc(obj)
if isTourniquet then
group:setMultiItem(TourniquetController.bodyLoc, true)
end
return ogValue -- Needed for isValid
end
--[[
Horrendous workaround
To unequp items, the java side uses WornItems.setItem, which has
a check for multiItem. Basically, if it's active, it won't actually remove the item,
fucking things up. So, to be 100% sure that we're removing the items, we're gonna
disable and re-enable the multi-item bool for the Unequip Action.
Same story as the prosthesis item basically.
]]
local og_ISClothingExtraAction_perform = ISClothingExtraAction.perform
function ISClothingExtraAction:perform()
TourniquetController.WrapClothingAction(self, og_ISClothingExtraAction_perform)
end
local og_ISWearClothing_isValid = ISWearClothing.isValid
function ISWearClothing:isValid()
return TourniquetController.WrapClothingAction(self, og_ISWearClothing_isValid)
end
local og_ISUnequipAction_perform = ISUnequipAction.perform
function ISUnequipAction:perform()
return TourniquetController.WrapClothingAction(self, og_ISUnequipAction_perform)
end
return TourniquetController