Bump to mod version and changed folder for specific game version
This commit is contained in:
@@ -1,491 +0,0 @@
|
||||
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
|
||||
@@ -1,179 +0,0 @@
|
||||
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
|
||||
@@ -1,369 +0,0 @@
|
||||
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, instanceItem(self.extra))
|
||||
end
|
||||
@@ -1,391 +0,0 @@
|
||||
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
|
||||
@@ -1,112 +0,0 @@
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user