Bump to mod version and changed folder for specific game version
This commit is contained in:
50
42.6/media/lua/client/TOC/API.lua
Normal file
50
42.6/media/lua/client/TOC/API.lua
Normal file
@@ -0,0 +1,50 @@
|
||||
------------------------------------------
|
||||
-- Compatibility Handler by Dhert
|
||||
------------------------------------------
|
||||
|
||||
local DataController = require("TOC/Controllers/DataController")
|
||||
local StaticData = require("TOC/StaticData")
|
||||
|
||||
|
||||
local TOC_Compat = {}
|
||||
|
||||
-- Raw access, must pass valid part
|
||||
--- @param player IsoPlayer
|
||||
--- @param part string
|
||||
--- @return boolean
|
||||
function TOC_Compat.hasPart(player, part)
|
||||
if not player or not part then return false end
|
||||
local dc = DataController.GetInstance(player:getUsername())
|
||||
if not dc then return false end
|
||||
return (dc:getIsCut(part) and dc:getIsProstEquipped(part)) or not dc:getIsCut(part)
|
||||
end
|
||||
|
||||
--- Check if hand is available
|
||||
---@param player IsoPlayer
|
||||
---@param left boolean Optional
|
||||
---@return boolean
|
||||
function TOC_Compat.hasHand(player, left)
|
||||
return TOC_Compat.hasPart(player, ((left and StaticData.LIMBS_IND_STR.Hand_L) or StaticData.LIMBS_IND_STR.Hand_R))
|
||||
end
|
||||
|
||||
--- Check if both hands are available
|
||||
---@param player IsoPlayer
|
||||
---@return boolean
|
||||
function TOC_Compat.hasBothHands(player)
|
||||
return TOC_Compat.hasHand(player, false) and TOC_Compat.hasHand(player, true)
|
||||
end
|
||||
|
||||
|
||||
-- This returns a number for the hands that you have
|
||||
----- 11 == both hands
|
||||
----- 10 == left hand
|
||||
----- 01 (1) == right hand
|
||||
----- 00 (0) == no hands
|
||||
---@param player any
|
||||
---@return integer
|
||||
function TOC_Compat.getHands(player)
|
||||
return ((TOC_Compat.hasHand(player, false) and 1) or 0) + ((TOC_Compat.hasHand(player, true) and 10) or 0)
|
||||
end
|
||||
|
||||
|
||||
return TOC_Compat
|
||||
115
42.6/media/lua/client/TOC/Admin.lua
Normal file
115
42.6/media/lua/client/TOC/Admin.lua
Normal file
@@ -0,0 +1,115 @@
|
||||
local CommandsData = require("TOC/CommandsData")
|
||||
local ClientRelayCommands = require("TOC/ClientRelayCommands")
|
||||
local StaticData = require("TOC/StaticData")
|
||||
local DataController = require("TOC/Controllers/DataController")
|
||||
-------------------
|
||||
|
||||
---@param playerNum number
|
||||
---@param context ISContextMenu
|
||||
---@param worldobjects table
|
||||
local function AddAdminTocOptions(playerNum, context, worldobjects)
|
||||
if not(isClient() and isAdmin() or isDebugEnabled()) then return end
|
||||
|
||||
local players = {}
|
||||
for _, v in ipairs(worldobjects) do
|
||||
for x = v:getSquare():getX() - 1, v:getSquare():getX() + 1 do
|
||||
for y = v:getSquare():getY() - 1, v:getSquare():getY() + 1 do
|
||||
local sq = getCell():getGridSquare(x, y, v:getSquare():getZ());
|
||||
if sq then
|
||||
for z = 0, sq:getMovingObjects():size() - 1 do
|
||||
local o = sq:getMovingObjects():get(z)
|
||||
if instanceof(o, "IsoPlayer") then
|
||||
---@cast o IsoPlayer
|
||||
|
||||
local oId = o:getOnlineID()
|
||||
players[oId] = o
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
for _, pl in pairs(players) do
|
||||
---@cast pl IsoPlayer
|
||||
|
||||
local clickedPlayerNum = pl:getOnlineID()
|
||||
|
||||
local option = context:addOption(getText("ContextMenu_Admin_TOC") .. " - " .. pl:getUsername(), nil, nil)
|
||||
local subMenu = ISContextMenu:getNew(context)
|
||||
context:addSubMenu(option, subMenu)
|
||||
|
||||
subMenu:addOption(getText("ContextMenu_Admin_ResetTOC"), nil, function()
|
||||
if isClient() then
|
||||
sendClientCommand(CommandsData.modules.TOC_RELAY, CommandsData.server.Relay.RelayExecuteInitialization,
|
||||
{ patientNum = clickedPlayerNum })
|
||||
else
|
||||
-- TODO ugly
|
||||
ClientRelayCommands.ReceiveExecuteInitialization()
|
||||
end
|
||||
end)
|
||||
|
||||
-- Force amputation
|
||||
local forceAmpOption = subMenu:addOption(getText("ContextMenu_Admin_ForceAmputation"), nil, nil)
|
||||
local forceAmpSubMenu = ISContextMenu:getNew(subMenu)
|
||||
context:addSubMenu(forceAmpOption, forceAmpSubMenu)
|
||||
|
||||
for i = 1, #StaticData.LIMBS_STR do
|
||||
local limbName = StaticData.LIMBS_STR[i]
|
||||
local limbTranslatedName = getText("ContextMenu_Limb_" .. limbName)
|
||||
|
||||
forceAmpSubMenu:addOption(limbTranslatedName, nil, function()
|
||||
if isClient() then
|
||||
sendClientCommand(CommandsData.modules.TOC_RELAY, CommandsData.server.Relay.RelayForcedAmputation,
|
||||
{ patientNum = clickedPlayerNum, limbName = limbName })
|
||||
else
|
||||
ClientRelayCommands.ReceiveExecuteAmputationAction({surgeonNum=clickedPlayerNum, limbName=limbName, damagePlayer=false})
|
||||
-- todo ugly
|
||||
end
|
||||
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
Events.OnFillWorldObjectContextMenu.Add(AddAdminTocOptions)
|
||||
|
||||
|
||||
--* Override to cheats to fix stuff
|
||||
|
||||
local og_ISHealthPanel_onCheatCurrentPlayer = ISHealthPanel.onCheatCurrentPlayer
|
||||
|
||||
---Override to onCheatCurrentPlayer to fix behaviour with TOC
|
||||
---@param bodyPart BodyPart
|
||||
---@param action any
|
||||
---@param player IsoPlayer
|
||||
function ISHealthPanel.onCheatCurrentPlayer(bodyPart, action, player)
|
||||
og_ISHealthPanel_onCheatCurrentPlayer(bodyPart, action, player)
|
||||
local bptString = BodyPartType.ToString(bodyPart:getType())
|
||||
|
||||
if action == "healthFullBody" then
|
||||
-- loop all limbs and reset them if infected
|
||||
local dcInst = DataController.GetInstance()
|
||||
|
||||
for i = 1, #StaticData.LIMBS_STR do
|
||||
local limbName = StaticData.LIMBS_STR[i]
|
||||
|
||||
dcInst:setIsInfected(limbName, false)
|
||||
end
|
||||
|
||||
dcInst:setIsIgnoredPartInfected(false)
|
||||
|
||||
dcInst:apply()
|
||||
end
|
||||
|
||||
if action == "healthFull" then
|
||||
-- Get the limbName for that BodyPart and fix the values in TOC Data
|
||||
local limbName = StaticData.BODYLOCS_TO_LIMBS_IND_STR[bptString]
|
||||
local dcInst = DataController.GetInstance()
|
||||
|
||||
dcInst:setIsInfected(limbName, false)
|
||||
dcInst:apply()
|
||||
end
|
||||
end
|
||||
77
42.6/media/lua/client/TOC/ClientRelayCommands.lua
Normal file
77
42.6/media/lua/client/TOC/ClientRelayCommands.lua
Normal file
@@ -0,0 +1,77 @@
|
||||
local CommandsData = require("TOC/CommandsData")
|
||||
local AmputationHandler = require("TOC/Handlers/AmputationHandler")
|
||||
local DataController = require("TOC/Controllers/DataController")
|
||||
--------------------------------------------
|
||||
|
||||
local ClientRelayCommands = {}
|
||||
|
||||
---Initialize Amputation Handler
|
||||
---@param limbName any
|
||||
---@param surgeonNum any
|
||||
---@return AmputationHandler
|
||||
local function InitAmputationHandler(limbName, surgeonNum)
|
||||
|
||||
-- TODO Pretty unclean
|
||||
local surgeonPl = getSpecificPlayer(surgeonNum)
|
||||
local handler = AmputationHandler:new(limbName, surgeonPl)
|
||||
return handler
|
||||
end
|
||||
|
||||
---Receive the damage from another player during the amputation
|
||||
---@param args receiveDamageDuringAmputationParams
|
||||
function ClientRelayCommands.ReceiveDamageDuringAmputation(args)
|
||||
AmputationHandler.ApplyDamageDuringAmputation(getPlayer(), args.limbName)
|
||||
end
|
||||
|
||||
---Creates a new handler and execute the amputation function on this client
|
||||
---@param args receiveExecuteAmputationActionParams
|
||||
function ClientRelayCommands.ReceiveExecuteAmputationAction(args)
|
||||
|
||||
-- Check if player already doesn't have that limb or it's a dependant limb.
|
||||
-- Mostly a check for admin forced amputations more than anything else, since this case is handled in the GUI already.
|
||||
local dcInst = DataController.GetInstance()
|
||||
if dcInst:getIsCut(args.limbName) then return end
|
||||
|
||||
local handler = InitAmputationHandler(args.limbName, args.surgeonNum)
|
||||
handler:execute(args.damagePlayer)
|
||||
end
|
||||
|
||||
|
||||
--* APPLY RELAY *--
|
||||
|
||||
function ClientRelayCommands.ReceiveApplyFromServer()
|
||||
TOC_DEBUG.print("Received forced re-apply from server")
|
||||
local key = CommandsData.GetKey(getPlayer():getUsername())
|
||||
ModData.request(key)
|
||||
end
|
||||
|
||||
|
||||
--* TRIGGERED BY ADMINS *--
|
||||
|
||||
function ClientRelayCommands.ReceiveExecuteInitialization()
|
||||
local LocalPlayerController = require("TOC/Controllers/LocalPlayerController")
|
||||
LocalPlayerController.InitializePlayer(true)
|
||||
end
|
||||
|
||||
---Creates a new handler and execute the amputation function on this client
|
||||
---@param args receiveForcedCicatrizationParams
|
||||
function ClientRelayCommands.ReceiveForcedCicatrization(args)
|
||||
local dcInst = DataController.GetInstance()
|
||||
--dcInst:setCicatrizationTime(args.limbName, 1)
|
||||
dcInst:setIsCicatrized(args.limbName, true)
|
||||
dcInst:apply()
|
||||
end
|
||||
|
||||
-------------------------
|
||||
|
||||
local function OnServerRelayCommand(module, command, args)
|
||||
if module == CommandsData.modules.TOC_RELAY and ClientRelayCommands[command] then
|
||||
TOC_DEBUG.print("Received Server Relay command - " .. tostring(command))
|
||||
ClientRelayCommands[command](args)
|
||||
end
|
||||
end
|
||||
|
||||
Events.OnServerCommand.Add(OnServerRelayCommand)
|
||||
|
||||
-- TODO temporary
|
||||
return ClientRelayCommands
|
||||
48
42.6/media/lua/client/TOC/CommonMethods.lua
Normal file
48
42.6/media/lua/client/TOC/CommonMethods.lua
Normal file
@@ -0,0 +1,48 @@
|
||||
local StaticData = require("TOC/StaticData")
|
||||
-----------------------------------
|
||||
|
||||
local CommonMethods = {}
|
||||
|
||||
---@param val number
|
||||
---@param min number
|
||||
---@param max number
|
||||
function CommonMethods.Normalize(val, min, max)
|
||||
if (max - min) == 0 then return 1 end
|
||||
return (val - min)/(max-min)
|
||||
|
||||
end
|
||||
|
||||
function CommonMethods.GetLimbNameFromBodyPart(bodyPart)
|
||||
local bodyPartTypeStr = BodyPartType.ToString(bodyPart:getType())
|
||||
return StaticData.LIMBS_IND_STR[bodyPartTypeStr]
|
||||
end
|
||||
|
||||
---Returns the side for a certain limb or prosthesis
|
||||
---@param name string
|
||||
---@return string "L" or "R"
|
||||
function CommonMethods.GetSide(name)
|
||||
if string.find(name, "_L") then return "L" else return "R" end
|
||||
end
|
||||
|
||||
---Returns full name for the side, to be used with BodyLocations
|
||||
---@param side string
|
||||
---@return string?
|
||||
function CommonMethods.GetSideFull(side)
|
||||
if side == 'R' then
|
||||
return "Right"
|
||||
elseif side == 'L' then
|
||||
return 'Left'
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
---Stops and start an event, making sure that we don't stack them up
|
||||
---@param event string
|
||||
---@param method function
|
||||
function CommonMethods.SafeStartEvent(event, method)
|
||||
Events[event].Remove(method)
|
||||
Events[event].Add(method)
|
||||
end
|
||||
|
||||
return CommonMethods
|
||||
75
42.6/media/lua/client/TOC/Compat.lua
Normal file
75
42.6/media/lua/client/TOC/Compat.lua
Normal file
@@ -0,0 +1,75 @@
|
||||
|
||||
---@class Compat
|
||||
---@field handlers table<string, {fun : function, isActive : boolean}>
|
||||
local Compat = {
|
||||
handlers = {}
|
||||
}
|
||||
|
||||
--- Brutal hands has a TOC_COMPAT but its check is wrong and uses an old API.
|
||||
function Compat.BrutalHandwork()
|
||||
BrutalHands = BrutalHands or {}
|
||||
BrutalHands.TOC = require("TOC/API")
|
||||
|
||||
end
|
||||
|
||||
--- Was handled inside old TOC
|
||||
function Compat.FancyHandwork()
|
||||
require("TimedActions/FHSwapHandsAction")
|
||||
local og_FHSwapHandsAction_isValid = FHSwapHandsAction.isValid
|
||||
function FHSwapHandsAction:isValid()
|
||||
local tocApi = require("TOC/API")
|
||||
if tocApi.hasBothHands(self.character) then
|
||||
return og_FHSwapHandsAction_isValid(self)
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Compat.iMeds()
|
||||
require("Component/Interface/Service/ContextMenu/Menu/HealthPanel/HealthPanelMenuInitializer")
|
||||
-- placeholder, in case we need to do something more drastic with it.
|
||||
end
|
||||
|
||||
|
||||
------------------------------
|
||||
|
||||
Compat.handlers = {
|
||||
["BrutalHandwork"] = {
|
||||
fun = Compat.BrutalHandwork,
|
||||
isActive = false},
|
||||
["FancyHandwork"] = {
|
||||
fun = Compat.FancyHandwork,
|
||||
isActive = false},
|
||||
|
||||
-- either or
|
||||
['iMeds'] = {
|
||||
fun = Compat.iMeds,
|
||||
isActive = false},
|
||||
['iMedsFixed'] = {
|
||||
fun = Compat.iMeds,
|
||||
isActive = false}
|
||||
}
|
||||
|
||||
|
||||
function Compat.RunModCompatibility()
|
||||
local activatedMods = getActivatedMods()
|
||||
TOC_DEBUG.print("Checking for mods compatibility")
|
||||
|
||||
for k, modCompatHandler in pairs(Compat.handlers) do
|
||||
if activatedMods:contains(k) then
|
||||
TOC_DEBUG.print("Found " .. k .. ", running compatibility handler")
|
||||
modCompatHandler.fun()
|
||||
modCompatHandler.isActive = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
Events.OnGameStart.Add(Compat.RunModCompatibility)
|
||||
|
||||
return Compat
|
||||
491
42.6/media/lua/client/TOC/Controllers/DataController.lua
Normal file
491
42.6/media/lua/client/TOC/Controllers/DataController.lua
Normal 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
|
||||
179
42.6/media/lua/client/TOC/Controllers/ItemsController.lua
Normal file
179
42.6/media/lua/client/TOC/Controllers/ItemsController.lua
Normal 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
|
||||
369
42.6/media/lua/client/TOC/Controllers/LimitActionsController.lua
Normal file
369
42.6/media/lua/client/TOC/Controllers/LimitActionsController.lua
Normal 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, instanceItem(self.extra))
|
||||
end
|
||||
391
42.6/media/lua/client/TOC/Controllers/LocalPlayerController.lua
Normal file
391
42.6/media/lua/client/TOC/Controllers/LocalPlayerController.lua
Normal 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
|
||||
112
42.6/media/lua/client/TOC/Controllers/TourniquetController.lua
Normal file
112
42.6/media/lua/client/TOC/Controllers/TourniquetController.lua
Normal 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
|
||||
5
42.6/media/lua/client/TOC/Events.lua
Normal file
5
42.6/media/lua/client/TOC/Events.lua
Normal file
@@ -0,0 +1,5 @@
|
||||
--* Setup Events *--
|
||||
LuaEventManager.AddEvent("OnAmputatedLimb") --Triggered when a limb has been amputated
|
||||
LuaEventManager.AddEvent("OnProsthesisUnequipped")
|
||||
LuaEventManager.AddEvent("OnReceivedTocData") -- Triggered when TOC data is ready
|
||||
LuaEventManager.AddEvent("OnSetupTocData") -- Triggered when TOC has been setupped
|
||||
211
42.6/media/lua/client/TOC/Handlers/AmputationHandler.lua
Normal file
211
42.6/media/lua/client/TOC/Handlers/AmputationHandler.lua
Normal file
@@ -0,0 +1,211 @@
|
||||
local DataController = require("TOC/Controllers/DataController")
|
||||
local ItemsController = require("TOC/Controllers/ItemsController")
|
||||
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
|
||||
local LocalPlayerController = require("TOC/Controllers/LocalPlayerController")
|
||||
local StaticData = require("TOC/StaticData")
|
||||
local TourniquetController = require("TOC/Controllers/TourniquetController")
|
||||
---------------------------
|
||||
|
||||
--- Manages an amputation. Will be run on the patient client
|
||||
---@class AmputationHandler
|
||||
---@field patientPl IsoPlayer
|
||||
---@field limbName string
|
||||
---@field bodyPartType BodyPartType
|
||||
---@field surgeonPl IsoPlayer?
|
||||
local AmputationHandler = {}
|
||||
|
||||
---@param limbName string
|
||||
---@param surgeonPl IsoPlayer?
|
||||
---@return AmputationHandler
|
||||
function AmputationHandler:new(limbName, surgeonPl)
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
|
||||
o.patientPl = getPlayer()
|
||||
o.limbName = limbName
|
||||
o.bodyPartType = BodyPartType[limbName]
|
||||
|
||||
-- TOC_DEBUG.print("limbName = " .. o.limbName)
|
||||
-- TOC_DEBUG.print("bodyPartType = " .. tostring(o.bodyPartType))
|
||||
|
||||
if surgeonPl then
|
||||
o.surgeonPl = surgeonPl
|
||||
else
|
||||
o.surgeonPl = o.patientPl
|
||||
end
|
||||
|
||||
AmputationHandler.instance = o
|
||||
return o
|
||||
end
|
||||
|
||||
|
||||
--* Static methods *--
|
||||
|
||||
---@param player IsoPlayer
|
||||
---@param limbName string
|
||||
function AmputationHandler.ApplyDamageDuringAmputation(player, limbName)
|
||||
|
||||
|
||||
local ampGroup = StaticData.LIMBS_TO_AMP_GROUPS_MATCH_IND_STR[limbName]
|
||||
local isTourniquetEquipped = false
|
||||
|
||||
-- Check if tourniquet is applied on the zone
|
||||
for bl, tournAmpGroup in pairs(StaticData.TOURNIQUET_BODYLOCS_TO_GROUPS_IND_STR) do
|
||||
local item = player:getWornItem(bl)
|
||||
|
||||
-- LimbName -> Group -> BodyLoc
|
||||
if item and tournAmpGroup == ampGroup then
|
||||
TOC_DEBUG.print("tourniquet is equipped")
|
||||
isTourniquetEquipped = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local bodyDamage = player:getBodyDamage()
|
||||
local bodyPartType = BodyPartType[limbName]
|
||||
local bodyDamagePart = bodyDamage:getBodyPart(bodyPartType)
|
||||
TOC_DEBUG.print("damage patient - " .. tostring(bodyPartType))
|
||||
|
||||
bodyDamagePart:setBleeding(true)
|
||||
bodyDamagePart:setCut(true)
|
||||
|
||||
local bleedingTime
|
||||
if isTourniquetEquipped then
|
||||
bleedingTime = ZombRand(1,5)
|
||||
else
|
||||
bleedingTime = ZombRand(10, 20)
|
||||
end
|
||||
|
||||
bodyDamagePart:setBleedingTime(bleedingTime)
|
||||
end
|
||||
|
||||
|
||||
---@param prevAction ISBaseTimedAction
|
||||
---@param limbName string
|
||||
---@param surgeonPl IsoPlayer
|
||||
---@param patientPl IsoPlayer
|
||||
---@param stitchesItem InventoryItem
|
||||
---@return ISStitch
|
||||
function AmputationHandler.PrepareStitchesAction(prevAction, limbName, surgeonPl, patientPl, stitchesItem)
|
||||
local bd = patientPl:getBodyDamage()
|
||||
|
||||
-- we need the adjacent one, not the actual one
|
||||
local adjacentLimb = StaticData.LIMBS_ADJACENT_IND_STR[limbName]
|
||||
local bodyPart = bd:getBodyPart(BodyPartType[adjacentLimb])
|
||||
|
||||
local stitchesAction = ISStitch:new(surgeonPl, patientPl, stitchesItem, bodyPart, true)
|
||||
ISTimedActionQueue.addAfter(prevAction, stitchesAction)
|
||||
|
||||
return stitchesAction
|
||||
end
|
||||
|
||||
---Setup the ISApplyBandage action that will trigger after the amputation
|
||||
---@param prevAction ISBaseTimedAction
|
||||
---@param limbName string
|
||||
---@param surgeonPl IsoPlayer
|
||||
---@param patientPl IsoPlayer
|
||||
---@param bandageItem InventoryItem
|
||||
---@return ISApplyBandage
|
||||
function AmputationHandler.PrepareBandagesAction(prevAction, limbName, surgeonPl, patientPl, bandageItem)
|
||||
local bd = patientPl:getBodyDamage()
|
||||
-- we need the adjacent one, not the actual one
|
||||
local adjacentLimb = StaticData.LIMBS_ADJACENT_IND_STR[limbName]
|
||||
local bodyPart = bd:getBodyPart(BodyPartType[adjacentLimb])
|
||||
|
||||
local bandageAction = ISApplyBandage:new(surgeonPl, patientPl, bandageItem, bodyPart, true)
|
||||
ISTimedActionQueue.addAfter(prevAction, bandageAction)
|
||||
|
||||
return bandageAction
|
||||
end
|
||||
|
||||
|
||||
--* Main methods *--
|
||||
|
||||
---Set the damage to the adjacent part of the cut area
|
||||
---@param surgeonFactor number
|
||||
function AmputationHandler:damageAfterAmputation(surgeonFactor)
|
||||
|
||||
|
||||
TOC_DEBUG.print("Applying damage after amputation")
|
||||
local patientStats = self.patientPl:getStats()
|
||||
local bd = self.patientPl:getBodyDamage()
|
||||
|
||||
local adjacentLimb = StaticData.LIMBS_ADJACENT_IND_STR[self.limbName]
|
||||
local bodyPart = bd:getBodyPart(BodyPartType[adjacentLimb])
|
||||
local baseDamage = StaticData.LIMBS_BASE_DAMAGE_IND_NUM[self.limbName]
|
||||
|
||||
|
||||
-- Check if player has tourniquet equipped on the limb
|
||||
-- TODO Suboptimal checks, but they should work for now.
|
||||
local hasTourniquet = TourniquetController.CheckTourniquetOnLimb(self.patientPl, self.limbName)
|
||||
if hasTourniquet then
|
||||
TOC_DEBUG.print("Do something different for the damage calculation because tourniquet is applied")
|
||||
baseDamage = baseDamage * 0.5 -- 50% less damage due to tourniquet
|
||||
end
|
||||
|
||||
|
||||
bodyPart:AddDamage(baseDamage - surgeonFactor)
|
||||
bodyPart:setAdditionalPain(baseDamage - surgeonFactor)
|
||||
bodyPart:setBleeding(true)
|
||||
bodyPart:setBleedingTime(baseDamage - surgeonFactor)
|
||||
bodyPart:setDeepWounded(true)
|
||||
bodyPart:setDeepWoundTime(baseDamage - surgeonFactor)
|
||||
patientStats:setEndurance(surgeonFactor)
|
||||
patientStats:setStress(baseDamage - surgeonFactor)
|
||||
end
|
||||
|
||||
---Execute the amputation. This method doesn't check if the upper limb has been amputated or not, so if
|
||||
--- somehow the method gets triggered and we're trying to cut off a part that doesn't really exist anymore,
|
||||
--- it will still be executed. This is by design, additional checks must be made BEFORE running the AmputationHandler
|
||||
---@param damagePlayer boolean
|
||||
function AmputationHandler:execute(damagePlayer)
|
||||
local surgeonFactor = self.surgeonPl:getPerkLevel(Perks.Doctor) * SandboxVars.TOC.SurgeonAbilityImportance
|
||||
|
||||
-- Set the data in modData
|
||||
local dcInst = DataController.GetInstance()
|
||||
dcInst:setCutLimb(self.limbName, false, false, false, surgeonFactor)
|
||||
dcInst:apply() -- This will force rechecking the cached amputated limbs on the other client
|
||||
|
||||
-- Heal the area, we're gonna re-set the damage after (if it's enabled)
|
||||
local bd = self.patientPl:getBodyDamage()
|
||||
local bodyPart = bd:getBodyPart(self.bodyPartType)
|
||||
LocalPlayerController.HealArea(bodyPart)
|
||||
|
||||
-- Give the player the correct amputation item
|
||||
ItemsController.Player.DeleteOldAmputationItem(self.patientPl, self.limbName)
|
||||
ItemsController.Player.SpawnAmputationItem(self.patientPl, self.limbName)
|
||||
|
||||
-- Add it to the list of cut limbs on this local client
|
||||
local username = self.patientPl:getUsername()
|
||||
CachedDataHandler.AddAmputatedLimb(username, self.limbName)
|
||||
|
||||
-- TODO Not optimal, we're already cycling through this when using setCutLimb
|
||||
for i=1, #StaticData.LIMBS_DEPENDENCIES_IND_STR[self.limbName] do
|
||||
local dependedLimbName = StaticData.LIMBS_DEPENDENCIES_IND_STR[self.limbName][i]
|
||||
CachedDataHandler.AddAmputatedLimb(username, dependedLimbName)
|
||||
end
|
||||
|
||||
-- Cache highest amputation and hand feasibility
|
||||
CachedDataHandler.CalculateCacheableValues(username)
|
||||
|
||||
-- If the part was actually infected, heal the player, if they were in time (infectionLevel < 20)
|
||||
if bd:getInfectionLevel() < 20 and bodyPart:IsInfected() and not dcInst:getIsIgnoredPartInfected() then
|
||||
LocalPlayerController.HealZombieInfection(bd, bodyPart, self.limbName, dcInst)
|
||||
end
|
||||
|
||||
-- The last part is to handle the damage that the player will receive after the amputation
|
||||
if not damagePlayer then return end
|
||||
self:damageAfterAmputation(surgeonFactor)
|
||||
|
||||
-- Trigger this event
|
||||
triggerEvent("OnAmputatedLimb", self.limbName)
|
||||
end
|
||||
|
||||
---Deletes the instance
|
||||
function AmputationHandler:close()
|
||||
AmputationHandler.instance = nil
|
||||
end
|
||||
|
||||
return AmputationHandler
|
||||
167
42.6/media/lua/client/TOC/Handlers/CachedDataHandler.lua
Normal file
167
42.6/media/lua/client/TOC/Handlers/CachedDataHandler.lua
Normal file
@@ -0,0 +1,167 @@
|
||||
local DataController = require("TOC/Controllers/DataController")
|
||||
|
||||
local StaticData = require("TOC/StaticData")
|
||||
local CommonMethods = require("TOC/CommonMethods")
|
||||
---------------------------
|
||||
|
||||
---@class CachedDataHandler
|
||||
local CachedDataHandler = {}
|
||||
|
||||
---Reset everything cache related for that specific user
|
||||
---@param username string
|
||||
function CachedDataHandler.Setup(username)
|
||||
CachedDataHandler.amputatedLimbs[username] = {}
|
||||
-- username -> side
|
||||
CachedDataHandler.highestAmputatedLimbs[username] = {}
|
||||
|
||||
|
||||
-- Local only, doesn't matter for Health Panel
|
||||
CachedDataHandler.handFeasibility = {}
|
||||
end
|
||||
|
||||
---Will calculate all the values that we need
|
||||
function CachedDataHandler.CalculateCacheableValues(username)
|
||||
CachedDataHandler.CalculateHighestAmputatedLimbs(username)
|
||||
if getPlayer():getUsername() == username then
|
||||
CachedDataHandler.CalculateBothHandsFeasibility()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
--* Amputated Limbs caching *--
|
||||
CachedDataHandler.amputatedLimbs = {}
|
||||
|
||||
---Calculate the currently amputated limbs for a certain player
|
||||
---@param username string
|
||||
function CachedDataHandler.CalculateAmputatedLimbs(username)
|
||||
TOC_DEBUG.print("Calculating amputated limbs for " .. username)
|
||||
CachedDataHandler.amputatedLimbs[username] = {}
|
||||
local dcInst = DataController.GetInstance(username)
|
||||
|
||||
for i=1, #StaticData.LIMBS_STR do
|
||||
local limbName = StaticData.LIMBS_STR[i]
|
||||
if dcInst:getIsCut(limbName) then
|
||||
CachedDataHandler.AddAmputatedLimb(username, limbName)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
---Add an amputated limb to the cached list for that user
|
||||
---@param username string
|
||||
---@param limbName string
|
||||
function CachedDataHandler.AddAmputatedLimb(username, limbName)
|
||||
TOC_DEBUG.print("Added " .. limbName .. " to known amputated limbs for " .. username)
|
||||
|
||||
-- Add it to the generic list
|
||||
if CachedDataHandler.amputatedLimbs[username] == nil then
|
||||
CachedDataHandler.amputatedLimbs[username] = {}
|
||||
end
|
||||
CachedDataHandler.amputatedLimbs[username][limbName] = limbName
|
||||
end
|
||||
|
||||
---Returns a table containing the cached amputated limbs
|
||||
---@param username string
|
||||
---@return table
|
||||
function CachedDataHandler.GetAmputatedLimbs(username)
|
||||
return CachedDataHandler.amputatedLimbs[username]
|
||||
end
|
||||
|
||||
--* Highest amputated limb per side caching *--
|
||||
CachedDataHandler.highestAmputatedLimbs = {}
|
||||
|
||||
---Calcualate the highest point of amputations achieved by the player
|
||||
---@param username string
|
||||
function CachedDataHandler.CalculateHighestAmputatedLimbs(username)
|
||||
TOC_DEBUG.print("Triggered CalculateHighestAmputatedLimbs")
|
||||
local dcInst = DataController.GetInstance(username)
|
||||
if dcInst == nil then
|
||||
TOC_DEBUG.print("DataController not found for " .. username)
|
||||
return
|
||||
end
|
||||
|
||||
CachedDataHandler.CalculateAmputatedLimbs(username)
|
||||
|
||||
local amputatedLimbs = CachedDataHandler.amputatedLimbs[username]
|
||||
CachedDataHandler.highestAmputatedLimbs[username] = {}
|
||||
--TOC_DEBUG.print("Searching highest amputations for " .. username)
|
||||
|
||||
for k, _ in pairs(amputatedLimbs) do
|
||||
local limbName = k
|
||||
local side = CommonMethods.GetSide(limbName)
|
||||
if dcInst:getIsCut(limbName) and dcInst:getIsVisible(limbName) then
|
||||
TOC_DEBUG.print("Added Highest Amputation: " .. limbName)
|
||||
CachedDataHandler.highestAmputatedLimbs[username][side] = limbName
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---Get the cached highest point of amputation for each side
|
||||
---@param username string
|
||||
---@return table<string, string>
|
||||
function CachedDataHandler.GetHighestAmputatedLimbs(username)
|
||||
return CachedDataHandler.highestAmputatedLimbs[username]
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
--* Hand feasibility caching *--
|
||||
CachedDataHandler.handFeasibility = {}
|
||||
|
||||
---@param limbName string
|
||||
function CachedDataHandler.CalculateHandFeasibility(limbName)
|
||||
local dcInst = DataController.GetInstance()
|
||||
local side = CommonMethods.GetSide(limbName)
|
||||
|
||||
-- TODO if we re run this too early, it might break everything after a forced re-init
|
||||
|
||||
CachedDataHandler.handFeasibility[side] = not dcInst:getIsCut(limbName) or dcInst:getIsProstEquipped(limbName)
|
||||
TOC_DEBUG.print("Calculated hand feasibility: " .. tostring(side))
|
||||
end
|
||||
|
||||
---@param side string Either "L" or "R"
|
||||
---@return boolean
|
||||
function CachedDataHandler.GetHandFeasibility(side)
|
||||
|
||||
-- FIX horrendous workaround, but with a forced init we run the caching too early and it breaks this, setting it to nil.
|
||||
if CachedDataHandler.handFeasibility[side] == nil then
|
||||
CachedDataHandler.CalculateBothHandsFeasibility()
|
||||
end
|
||||
|
||||
return CachedDataHandler.handFeasibility[side]
|
||||
end
|
||||
|
||||
|
||||
function CachedDataHandler.CalculateBothHandsFeasibility()
|
||||
CachedDataHandler.CalculateHandFeasibility("Hand_L")
|
||||
CachedDataHandler.CalculateHandFeasibility("Hand_R")
|
||||
local interactStr = "Interact"
|
||||
|
||||
if CachedDataHandler.interactKey == nil or CachedDataHandler.interactKey == 0 then
|
||||
CachedDataHandler.interactKey = getCore():getKey(interactStr)
|
||||
end
|
||||
|
||||
|
||||
|
||||
if not CachedDataHandler.GetBothHandsFeasibility() then
|
||||
TOC_DEBUG.print("Disabling interact key")
|
||||
TOC_DEBUG.print("Cached current key for interact: " .. tostring(CachedDataHandler.interactKey))
|
||||
getCore():addKeyBinding(interactStr, Keyboard.KEY_NONE)
|
||||
else
|
||||
TOC_DEBUG.print("Re-enabling interact key")
|
||||
TOC_DEBUG.print("Cached current key for interact: " .. tostring(CachedDataHandler.interactKey))
|
||||
|
||||
getCore():addKeyBinding(interactStr, CachedDataHandler.interactKey)
|
||||
end
|
||||
end
|
||||
|
||||
function CachedDataHandler.GetBothHandsFeasibility()
|
||||
return CachedDataHandler.handFeasibility["L"] or CachedDataHandler.handFeasibility["R"]
|
||||
end
|
||||
|
||||
return CachedDataHandler
|
||||
180
42.6/media/lua/client/TOC/Handlers/ProsthesisHandler.lua
Normal file
180
42.6/media/lua/client/TOC/Handlers/ProsthesisHandler.lua
Normal file
@@ -0,0 +1,180 @@
|
||||
local CommonMethods = require("TOC/CommonMethods")
|
||||
local StaticData = require("TOC/StaticData")
|
||||
local DataController = require("TOC/Controllers/DataController")
|
||||
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
|
||||
-------------------------
|
||||
|
||||
---@class ProsthesisHandler
|
||||
local ProsthesisHandler = {}
|
||||
|
||||
local bodyLocArmProst = StaticData.MOD_BODYLOCS_BASE_IND_STR.TOC_ArmProst
|
||||
local bodyLocLegProst = StaticData.MOD_BODYLOCS_BASE_IND_STR.TOC_LegProst
|
||||
|
||||
---Check if the following item is a prosthesis or not
|
||||
---@param item InventoryItem?
|
||||
---@return boolean
|
||||
function ProsthesisHandler.CheckIfProst(item)
|
||||
-- TODO Won't be correct when prost for legs are gonna be in
|
||||
--TOC_DEBUG.print("Checking if item is prost")
|
||||
if item == nil then
|
||||
--TOC_DEBUG.print("Not prost")
|
||||
|
||||
return false
|
||||
end
|
||||
return item:getBodyLocation():contains(bodyLocArmProst)
|
||||
end
|
||||
|
||||
---Get the grouping for the prosthesis
|
||||
---@param item InventoryItem
|
||||
---@return string
|
||||
function ProsthesisHandler.GetGroup(item)
|
||||
local fullType = item:getFullType()
|
||||
local side = CommonMethods.GetSide(fullType)
|
||||
|
||||
|
||||
local bodyLocation = item:getBodyLocation()
|
||||
local position
|
||||
if bodyLocation == bodyLocArmProst then
|
||||
position = "Top_"
|
||||
elseif bodyLocation == bodyLocLegProst then
|
||||
position = "Bottom_"
|
||||
else
|
||||
TOC_DEBUG.print("Something is wrong, no position in this item")
|
||||
position = nil
|
||||
end
|
||||
|
||||
local index = position .. side
|
||||
local group = StaticData.AMP_GROUPS_IND_STR[index]
|
||||
return group
|
||||
end
|
||||
|
||||
---Check if a prosthesis is equippable. It depends whether the player has a cut limb or not on that specific side. There's an exception for Upper arm, obviously
|
||||
---@param fullType string
|
||||
---@return boolean
|
||||
function ProsthesisHandler.CheckIfEquippable(fullType)
|
||||
--TOC_DEBUG.print("Current item is a prosthesis")
|
||||
local side = CommonMethods.GetSide(fullType)
|
||||
--TOC_DEBUG.print("Checking side: " .. tostring(side))
|
||||
|
||||
local highestAmputatedLimbs = CachedDataHandler.GetHighestAmputatedLimbs(getPlayer():getUsername())
|
||||
|
||||
if highestAmputatedLimbs then
|
||||
local hal = highestAmputatedLimbs[side]
|
||||
if hal and not string.contains(hal, "UpperArm") then
|
||||
--TOC_DEBUG.print("Found acceptable limb to use prosthesis => " .. tostring(hal))
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- No acceptable cut limbs
|
||||
return false
|
||||
end
|
||||
|
||||
---Handle equipping or unequipping prosthetics
|
||||
---@param item InventoryItem
|
||||
---@param isEquipping boolean
|
||||
---@return boolean
|
||||
function ProsthesisHandler.SearchAndSetupProsthesis(item, isEquipping)
|
||||
if not ProsthesisHandler.CheckIfProst(item) then return false end
|
||||
|
||||
local group = ProsthesisHandler.GetGroup(item)
|
||||
TOC_DEBUG.print("Setup Prosthesis => " .. group .. " - is equipping? " .. tostring(isEquipping))
|
||||
local dcInst = DataController.GetInstance()
|
||||
dcInst:setIsProstEquipped(group, isEquipping)
|
||||
dcInst:apply()
|
||||
|
||||
-- Calculates hands feasibility once again
|
||||
CachedDataHandler.CalculateBothHandsFeasibility()
|
||||
return true
|
||||
end
|
||||
|
||||
-------------------------
|
||||
--* Overrides *--
|
||||
|
||||
|
||||
---@param item InventoryItem
|
||||
---@param isEquippable boolean
|
||||
---@return unknown
|
||||
local function HandleProsthesisValidation(item, isEquippable)
|
||||
local isProst = ProsthesisHandler.CheckIfProst(item)
|
||||
if not isProst then return isEquippable end
|
||||
|
||||
local fullType = item:getFullType() -- use fulltype for side
|
||||
if isEquippable then
|
||||
isEquippable = ProsthesisHandler.CheckIfEquippable(fullType)
|
||||
else
|
||||
getPlayer():Say(getText("UI_Say_CantEquip"))
|
||||
end
|
||||
|
||||
return isEquippable
|
||||
end
|
||||
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
local og_ISWearClothing_isValid = ISWearClothing.isValid
|
||||
function ISWearClothing:isValid()
|
||||
local isEquippable = og_ISWearClothing_isValid(self)
|
||||
return HandleProsthesisValidation(self.item, isEquippable)
|
||||
end
|
||||
|
||||
local og_ISWearClothing_perform = ISWearClothing.perform
|
||||
function ISWearClothing:perform()
|
||||
ProsthesisHandler.SearchAndSetupProsthesis(self.item, true)
|
||||
og_ISWearClothing_perform(self)
|
||||
end
|
||||
|
||||
local og_ISClothingExtraAction_isValid = ISClothingExtraAction.isValid
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function ISClothingExtraAction:isValid()
|
||||
local isEquippable = og_ISClothingExtraAction_isValid(self)
|
||||
-- self.extra is a string, not the item
|
||||
local testItem = instanceItem(self.extra)
|
||||
return HandleProsthesisValidation(testItem, isEquippable)
|
||||
end
|
||||
|
||||
|
||||
local og_ISClothingExtraAction_perform = ISClothingExtraAction.perform
|
||||
function ISClothingExtraAction:perform()
|
||||
local extraItem = instanceItem(self.extra)
|
||||
ProsthesisHandler.SearchAndSetupProsthesis(extraItem, true)
|
||||
og_ISClothingExtraAction_perform(self)
|
||||
end
|
||||
|
||||
local og_ISUnequipAction_perform = ISUnequipAction.perform
|
||||
function ISUnequipAction:perform()
|
||||
|
||||
--[[
|
||||
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.
|
||||
]]
|
||||
|
||||
local isProst = ProsthesisHandler.SearchAndSetupProsthesis(self.item, false)
|
||||
local group
|
||||
if isProst then
|
||||
group = BodyLocations.getGroup("Human")
|
||||
group:setMultiItem("TOC_ArmProst", false)
|
||||
end
|
||||
og_ISUnequipAction_perform(self)
|
||||
|
||||
if isProst then
|
||||
group:setMultiItem("TOC_ArmProst", true)
|
||||
|
||||
-- we need to fetch the limbname associated to the prosthesis
|
||||
local side = CommonMethods.GetSide(self.item:getFullType())
|
||||
local highestAmputatedLimbs = CachedDataHandler.GetHighestAmputatedLimbs(getPlayer():getUsername())
|
||||
if highestAmputatedLimbs then
|
||||
local hal = highestAmputatedLimbs[side]
|
||||
if hal then
|
||||
-- This could break if amputated limbs aren't cached for some reason
|
||||
triggerEvent("OnProsthesisUnequipped", hal)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return ProsthesisHandler
|
||||
71
42.6/media/lua/client/TOC/Main.lua
Normal file
71
42.6/media/lua/client/TOC/Main.lua
Normal file
@@ -0,0 +1,71 @@
|
||||
local LocalPlayerController = require("TOC/Controllers/LocalPlayerController")
|
||||
local CommonMethods = require("TOC/CommonMethods")
|
||||
local CommandsData = require("TOC/CommandsData")
|
||||
require("TOC/Events")
|
||||
------------------
|
||||
|
||||
---@class Main
|
||||
local Main = {
|
||||
_version = "2.2"
|
||||
}
|
||||
|
||||
function Main.Start()
|
||||
TOC_DEBUG.print("Starting The Only Cure version " .. tostring(Main._version))
|
||||
Main.SetupEvents()
|
||||
end
|
||||
|
||||
function Main.SetupEvents()
|
||||
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
|
||||
Events.OnReceivedTocData.Add(CachedDataHandler.CalculateCacheableValues)
|
||||
end
|
||||
|
||||
|
||||
function Main.InitializePlayer()
|
||||
---Looop until we've successfully initialized the mod
|
||||
local function TryToInitialize()
|
||||
local pl = getPlayer()
|
||||
TOC_DEBUG.print("Current username in TryToInitialize: " .. pl:getUsername())
|
||||
if pl:getUsername() == "Bob" then
|
||||
TOC_DEBUG.print("Username is still Bob, waiting")
|
||||
return
|
||||
end
|
||||
|
||||
LocalPlayerController.InitializePlayer(false)
|
||||
Events.OnTick.Remove(TryToInitialize)
|
||||
end
|
||||
CommonMethods.SafeStartEvent("OnTick", TryToInitialize)
|
||||
end
|
||||
|
||||
---Clean the TOC table for that SP player, to prevent it from clogging ModData up
|
||||
---@param player IsoPlayer
|
||||
function Main.WipeData(player)
|
||||
local username = player:getUsername()
|
||||
TOC_DEBUG.print("Wiping data after death: " .. username)
|
||||
local key = CommandsData.GetKey(username)
|
||||
|
||||
--ModData.remove(key)
|
||||
|
||||
if not isClient() then
|
||||
-- For SP, it's enough just removing the data this way
|
||||
ModData.remove(key)
|
||||
else
|
||||
-- Different story for MP, we're gonna 'force' it to reload it
|
||||
-- at the next character by passing an empty mod data
|
||||
ModData.add(key, {})
|
||||
ModData.transmit(key)
|
||||
|
||||
end
|
||||
|
||||
|
||||
-- Let's wipe the instance too just to be sure
|
||||
-- TODO This can break things I guess
|
||||
--local DataController = require("TOC/Controllers/DataController")
|
||||
--DataController.DestroyInstance(username)
|
||||
|
||||
end
|
||||
|
||||
--* Events *--
|
||||
|
||||
Events.OnGameStart.Add(Main.Start)
|
||||
Events.OnCreatePlayer.Add(Main.InitializePlayer)
|
||||
Events.OnPlayerDeath.Add(Main.WipeData)
|
||||
207
42.6/media/lua/client/TOC/Tests.lua
Normal file
207
42.6/media/lua/client/TOC/Tests.lua
Normal file
@@ -0,0 +1,207 @@
|
||||
if not getActivatedMods():contains("TEST_FRAMEWORK") or not isDebugEnabled() then return end
|
||||
local TestFramework = require("TestFramework/TestFramework")
|
||||
local TestUtils = require("TestFramework/TestUtils")
|
||||
|
||||
local LocalPlayerController = require("TOC/Controllers/LocalPlayerController")
|
||||
local AmputationHandler = require("TOC/Handlers/AmputationHandler")
|
||||
local DataController = require("TOC/Controllers/DataController")
|
||||
local StaticData = require("TOC/StaticData")
|
||||
|
||||
|
||||
TestFramework.registerTestModule("LocalPlayerController", "Setup", function()
|
||||
local Tests = {}
|
||||
function Tests.InitializePlayer()
|
||||
LocalPlayerController.InitializePlayer(true)
|
||||
end
|
||||
return Tests
|
||||
end)
|
||||
|
||||
TestFramework.registerTestModule("LocalPlayerController", "Perks", function()
|
||||
local Tests = {}
|
||||
|
||||
function Tests.SetMaxPerks()
|
||||
local pl = getPlayer()
|
||||
for _=0, 10 do
|
||||
pl:LevelPerk(Perks["Side_L"])
|
||||
pl:LevelPerk(Perks["Side_R"])
|
||||
pl:getXp():setXPToLevel(Perks["Side_L"], pl:getPerkLevel(Perks["Side_L"]))
|
||||
pl:getXp():setXPToLevel(Perks["Side_R"], pl:getPerkLevel(Perks["Side_R"]))
|
||||
end
|
||||
|
||||
SyncXp(pl)
|
||||
end
|
||||
|
||||
function Tests.ResetPerks()
|
||||
local pl = getPlayer()
|
||||
for _=0, 10 do
|
||||
pl:LoseLevel(Perks["Side_L"])
|
||||
pl:LoseLevel(Perks["Side_R"])
|
||||
pl:getXp():setXPToLevel(Perks["Side_L"], pl:getPerkLevel(Perks["Side_L"]))
|
||||
pl:getXp():setXPToLevel(Perks["Side_R"], pl:getPerkLevel(Perks["Side_R"]))
|
||||
end
|
||||
SyncXp(pl)
|
||||
end
|
||||
|
||||
return Tests
|
||||
end)
|
||||
|
||||
TestFramework.registerTestModule("LocalPlayerController", "Cicatrization", function()
|
||||
local Tests = {}
|
||||
|
||||
function Tests.SetCicatrizationTimeToOne()
|
||||
for i=1, #StaticData.LIMBS_STR do
|
||||
local limbName = StaticData.LIMBS_STR[i]
|
||||
DataController.GetInstance():setCicatrizationTime(limbName, 1)
|
||||
TestUtils.assert(DataController.GetInstance():getCicatrizationTime(limbName) == 1)
|
||||
end
|
||||
DataController.GetInstance():apply()
|
||||
TestUtils.assert(DataController.GetInstance():getIsCut("Hand_L"))
|
||||
end
|
||||
|
||||
return Tests
|
||||
end)
|
||||
|
||||
|
||||
TestFramework.registerTestModule("AmputationHandler", "Top Left", function()
|
||||
local Tests = {}
|
||||
|
||||
function Tests.CutLeftHand()
|
||||
local handler = AmputationHandler:new("Hand_L")
|
||||
handler:execute(true)
|
||||
TestUtils.assert(DataController.GetInstance():getIsCut("Hand_L"))
|
||||
end
|
||||
|
||||
function Tests.CutLeftForearm()
|
||||
local handler = AmputationHandler:new("ForeArm_L")
|
||||
handler:execute(true)
|
||||
TestUtils.assert(DataController.GetInstance():getIsCut("ForeArm_L") and DataController.GetInstance():getIsCut("Hand_L"))
|
||||
end
|
||||
|
||||
function Tests.CutLeftUpperarm()
|
||||
local handler = AmputationHandler:new("UpperArm_L")
|
||||
handler:execute(true)
|
||||
TestUtils.assert(DataController.GetInstance():getIsCut("UpperArm_L") and DataController.GetInstance():getIsCut("ForeArm_L") and DataController.GetInstance():getIsCut("Hand_L"))
|
||||
end
|
||||
|
||||
return Tests
|
||||
end)
|
||||
|
||||
TestFramework.registerTestModule("AmputationHandler", "Top Right", function()
|
||||
local Tests = {}
|
||||
|
||||
function Tests.CutRightHand()
|
||||
local handler = AmputationHandler:new("Hand_R")
|
||||
handler:execute(true)
|
||||
TestUtils.assert(DataController.GetInstance():getIsCut("Hand_R"))
|
||||
end
|
||||
|
||||
function Tests.CutRightForearm()
|
||||
local handler = AmputationHandler:new("ForeArm_R")
|
||||
handler:execute(true)
|
||||
TestUtils.assert(DataController.GetInstance():getIsCut("ForeArm_R") and DataController.GetInstance():getIsCut("Hand_R"))
|
||||
end
|
||||
|
||||
function Tests.CutRightUpperarm()
|
||||
local handler = AmputationHandler:new("UpperArm_R")
|
||||
handler:execute(true)
|
||||
TestUtils.assert(DataController.GetInstance():getIsCut("UpperArm_R") and DataController.GetInstance():getIsCut("ForeArm_R") and DataController.GetInstance():getIsCut("Hand_R"))
|
||||
end
|
||||
|
||||
return Tests
|
||||
end)
|
||||
|
||||
TestFramework.registerTestModule("TimedActions", "CauterizeAction", function()
|
||||
local Tests = {}
|
||||
local CauterizeAction = require("TOC/TimedActions/CauterizeAction")
|
||||
|
||||
function Tests.CauterizeLeftHand()
|
||||
ISTimedActionQueue.add(CauterizeAction:new(getPlayer(), "Hand_L", getPlayer()))
|
||||
end
|
||||
|
||||
function Tests.CauterizeLefForeArm()
|
||||
ISTimedActionQueue.add(CauterizeAction:new(getPlayer(), "ForeArm_L", getPlayer()))
|
||||
end
|
||||
|
||||
function Tests.CauterizeLeftUpperArm()
|
||||
ISTimedActionQueue.add(CauterizeAction:new(getPlayer(), "UpperArm_L", getPlayer()))
|
||||
end
|
||||
|
||||
function Tests.CauterizeRightHand()
|
||||
ISTimedActionQueue.add(CauterizeAction:new(getPlayer(), "Hand_R", getPlayer()))
|
||||
end
|
||||
|
||||
function Tests.CauterizeRightForeArm()
|
||||
ISTimedActionQueue.add(CauterizeAction:new(getPlayer(), "ForeArm_R", getPlayer()))
|
||||
end
|
||||
|
||||
function Tests.CauterizeRightUpperArm()
|
||||
ISTimedActionQueue.add(CauterizeAction:new(getPlayer(), "UpperArm_R", getPlayer()))
|
||||
end
|
||||
|
||||
return Tests
|
||||
|
||||
end)
|
||||
|
||||
TestFramework.registerTestModule("Various", "Player", function()
|
||||
local Tests = {}
|
||||
|
||||
|
||||
function Tests.BleedTest()
|
||||
local pl = getPlayer()
|
||||
|
||||
--pl:getBodyDamage():getBodyPart(BodyPartType.ForeArm_R):setBleeding(true)
|
||||
pl:getBodyDamage():getBodyPart(BodyPartType.ForeArm_R):setBleedingTime(20)
|
||||
end
|
||||
|
||||
function Tests.Kill()
|
||||
getPlayer():Kill(getPlayer())
|
||||
end
|
||||
|
||||
return Tests
|
||||
end)
|
||||
|
||||
|
||||
TestFramework.registerTestModule("Various", "Visuals", function()
|
||||
local Tests = {}
|
||||
|
||||
function Tests.AddBloodLeftForearm()
|
||||
local playerObj = getPlayer()
|
||||
local limbName = "ForeArm_L"
|
||||
local fullType = StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limbName
|
||||
|
||||
|
||||
local item = playerObj:getInventory():FindAndReturn(fullType)
|
||||
if instanceof(item, "Clothing") then
|
||||
|
||||
---@cast item Clothing
|
||||
|
||||
print("Found limb to add blood onto")
|
||||
item:setBloodLevel(100)
|
||||
local coveredParts = BloodClothingType.getCoveredParts(item:getBloodClothingType())
|
||||
if coveredParts then
|
||||
for j=0,coveredParts:size()-1 do
|
||||
item:setBlood(coveredParts:get(j), 100)
|
||||
item:setDirt(coveredParts:get(j), 100)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
playerObj:resetModelNextFrame()
|
||||
end
|
||||
|
||||
return Tests
|
||||
end)
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------------
|
||||
if not getActivatedMods():contains("PerfTestFramework") or not isDebugEnabled() then return end
|
||||
local PerfTest = require("PerfTest/main") -- SHould be global anyway
|
||||
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
|
||||
|
||||
PerfTest.RegisterMethod("LocalPlayerController", LocalPlayerController, "InitializePlayer")
|
||||
PerfTest.RegisterMethod("LocalPlayerController", LocalPlayerController, "UpdateAmputations")
|
||||
PerfTest.RegisterMethod("CachedDataHandler", CachedDataHandler, "CalculateHighestAmputatedLimbs")
|
||||
PerfTest.RegisterMethod("ISHealthPanel", ISHealthPanel, "render")
|
||||
|
||||
PerfTest.Init()
|
||||
89
42.6/media/lua/client/TOC/TimedActions/CauterizeAction.lua
Normal file
89
42.6/media/lua/client/TOC/TimedActions/CauterizeAction.lua
Normal file
@@ -0,0 +1,89 @@
|
||||
require "TimedActions/ISBaseTimedAction"
|
||||
local DataController = require("TOC/Controllers/DataController")
|
||||
local LocalPlayerController = require("TOC/Controllers/LocalPlayerController")
|
||||
---------------
|
||||
|
||||
---@class CauterizeAction : ISBaseTimedAction
|
||||
---@field character IsoPlayer
|
||||
---@field ovenObj IsoObject
|
||||
---@field limbName string
|
||||
local CauterizeAction = ISBaseTimedAction:derive("CauterizeAction")
|
||||
|
||||
---@param character IsoPlayer
|
||||
---@param stoveObj IsoObject
|
||||
---@param limbName string
|
||||
---@return CauterizeAction
|
||||
function CauterizeAction:new(character, limbName, stoveObj)
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
|
||||
-- We need to follow ISBaseTimedAction. self.character is gonna be the surgeon
|
||||
o.character = character
|
||||
o.stoveObj = stoveObj
|
||||
o.limbName = limbName
|
||||
|
||||
o.stopOnWalk = true
|
||||
o.stopOnRun = true
|
||||
|
||||
-- Max time depends on the strength
|
||||
o.maxTime = 20
|
||||
if o.character:isTimedActionInstant() then o.maxTime = 1 end
|
||||
|
||||
return o
|
||||
end
|
||||
|
||||
function CauterizeAction:isValid()
|
||||
return not ISHealthPanel.DidPatientMove(self.character, self.character, self.character:getX(), self.character:getY())
|
||||
end
|
||||
|
||||
function CauterizeAction:waitToStart()
|
||||
self.character:faceThisObject(self.ovenObj)
|
||||
return self.character:shouldBeTurning()
|
||||
end
|
||||
|
||||
function CauterizeAction:start()
|
||||
self:setActionAnim("Loot") -- TODO Better anim pls
|
||||
|
||||
-- Setup audio
|
||||
self.sound = self.character:getEmitter():playSound("Cauterization")
|
||||
local radius = 5
|
||||
addSound(self.character, self.character:getX(), self.character:getY(), self.character:getZ(), radius, radius)
|
||||
end
|
||||
|
||||
function CauterizeAction:update()
|
||||
self.character:setMetabolicTarget(Metabolics.HeavyWork)
|
||||
end
|
||||
|
||||
function CauterizeAction:stopSound()
|
||||
if self.sound then
|
||||
self.character:getEmitter():stopSound(self.sound)
|
||||
self.sound = nil
|
||||
end
|
||||
end
|
||||
|
||||
function CauterizeAction:stop()
|
||||
self:stopSound()
|
||||
ISBaseTimedAction.stop(self)
|
||||
end
|
||||
|
||||
function CauterizeAction:perform()
|
||||
-- Stop the sound
|
||||
self:stopSound()
|
||||
|
||||
local dcInst = DataController.GetInstance()
|
||||
dcInst:setCicatrizationTime(self.limbName, 0)
|
||||
dcInst:setIsCauterized(self.limbName, true)
|
||||
|
||||
-- Set isCicatrized and the visuals in one go, since this action is gonna be run only on a single client
|
||||
LocalPlayerController.HandleSetCicatrization(dcInst, self.character, self.limbName)
|
||||
|
||||
-- TODO Add specific visuals for cauterization
|
||||
|
||||
-- we don't care about the depended limbs, since they're alread "cicatrized"
|
||||
dcInst:apply()
|
||||
|
||||
ISBaseTimedAction.perform(self)
|
||||
end
|
||||
|
||||
return CauterizeAction
|
||||
123
42.6/media/lua/client/TOC/TimedActions/CleanWoundAction.lua
Normal file
123
42.6/media/lua/client/TOC/TimedActions/CleanWoundAction.lua
Normal file
@@ -0,0 +1,123 @@
|
||||
local DataController = require("TOC/Controllers/DataController")
|
||||
local CommonMethods = require("TOC/CommonMethods")
|
||||
--------------------
|
||||
|
||||
---@class CleanWoundAction : ISBaseTimedAction
|
||||
---@field doctor IsoPlayer
|
||||
---@field otherPlayer IsoPlayer
|
||||
---@field bandage InventoryItem
|
||||
---@field bodyPart any
|
||||
local CleanWoundAction = ISBaseTimedAction:derive("CleanWoundAction")
|
||||
|
||||
---@param doctor IsoPlayer
|
||||
---@param otherPlayer IsoPlayer
|
||||
---@param bandage InventoryItem
|
||||
---@param bodyPart any
|
||||
---@return CleanWoundAction
|
||||
function CleanWoundAction:new(doctor, otherPlayer, bandage, bodyPart)
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
o.character = doctor
|
||||
o.otherPlayer = otherPlayer
|
||||
o.doctorLevel = doctor:getPerkLevel(Perks.Doctor)
|
||||
o.bodyPart = bodyPart
|
||||
o.bandage = bandage
|
||||
o.stopOnWalk = true
|
||||
o.stopOnRun = true
|
||||
|
||||
o.bandagedPlayerX = otherPlayer:getX()
|
||||
o.bandagedPlayerY = otherPlayer:getY()
|
||||
|
||||
o.maxTime = 250 - (o.doctorLevel * 6)
|
||||
if doctor:isTimedActionInstant() then
|
||||
o.maxTime = 1
|
||||
end
|
||||
if doctor:getAccessLevel() ~= "None" then
|
||||
o.doctorLevel = 10
|
||||
end
|
||||
return o
|
||||
end
|
||||
function CleanWoundAction:isValid()
|
||||
if ISHealthPanel.DidPatientMove(self.character, self.otherPlayer, self.bandagedPlayerX, self.bandagedPlayerY) then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function CleanWoundAction:waitToStart()
|
||||
if self.character == self.otherPlayer then
|
||||
return false
|
||||
end
|
||||
self.character:faceThisObject(self.otherPlayer)
|
||||
return self.character:shouldBeTurning()
|
||||
end
|
||||
|
||||
function CleanWoundAction:update()
|
||||
if self.character ~= self.otherPlayer then
|
||||
self.character:faceThisObject(self.otherPlayer)
|
||||
end
|
||||
local jobType = getText("ContextMenu_CleanWound")
|
||||
ISHealthPanel.setBodyPartActionForPlayer(self.otherPlayer, self.bodyPart, self, jobType, { cleanBurn = true })
|
||||
self.character:setMetabolicTarget(Metabolics.LightDomestic)
|
||||
end
|
||||
|
||||
function CleanWoundAction:start()
|
||||
if self.character == self.otherPlayer then
|
||||
self:setActionAnim(CharacterActionAnims.Bandage)
|
||||
self:setAnimVariable("BandageType", ISHealthPanel.getBandageType(self.bodyPart))
|
||||
self.character:reportEvent("EventBandage")
|
||||
else
|
||||
self:setActionAnim("Loot")
|
||||
self.character:SetVariable("LootPosition", "Mid")
|
||||
self.character:reportEvent("EventLootItem")
|
||||
end
|
||||
self:setOverrideHandModels(nil, nil)
|
||||
end
|
||||
|
||||
function CleanWoundAction:stop()
|
||||
ISHealthPanel.setBodyPartActionForPlayer(self.otherPlayer, self.bodyPart, nil, nil, nil)
|
||||
ISBaseTimedAction.stop(self)
|
||||
end
|
||||
|
||||
function CleanWoundAction:perform()
|
||||
|
||||
TOC_DEBUG.print("CleanWound for " .. self.otherPlayer:getUsername())
|
||||
|
||||
if self.character:HasTrait("Hemophobic") then
|
||||
self.character:getStats():setPanic(self.character:getStats():getPanic() + 15)
|
||||
end
|
||||
|
||||
self.character:getXp():AddXP(Perks.Doctor, 10)
|
||||
local addPain = (60 - (self.doctorLevel * 1))
|
||||
self.bodyPart:setAdditionalPain(self.bodyPart:getAdditionalPain() + addPain)
|
||||
self.bandage:Use()
|
||||
|
||||
-- TOC Data handling
|
||||
|
||||
local limbName = CommonMethods.GetLimbNameFromBodyPart(self.bodyPart)
|
||||
local dcInst = DataController.GetInstance(self.otherPlayer:getUsername())
|
||||
|
||||
local currentWoundDirtyness = dcInst:getWoundDirtyness(limbName)
|
||||
local newWoundDirtyness = currentWoundDirtyness - (self.bandage:getBandagePower() * 10)
|
||||
if newWoundDirtyness < 0 then newWoundDirtyness = 0 end
|
||||
|
||||
dcInst:setWoundDirtyness(limbName, newWoundDirtyness)
|
||||
|
||||
dcInst:apply()
|
||||
|
||||
-- Clean visual
|
||||
local bbptEnum = BloodBodyPartType[limbName]
|
||||
|
||||
---@type HumanVisual
|
||||
local visual = self.otherPlayer:getHumanVisual()
|
||||
visual:setDirt(bbptEnum, 0)
|
||||
visual:setBlood(bbptEnum, 0)
|
||||
|
||||
ISHealthPanel.setBodyPartActionForPlayer(self.otherPlayer, self.bodyPart, nil, nil, nil)
|
||||
|
||||
-- needed to remove from queue / start next.
|
||||
ISBaseTimedAction.perform(self)
|
||||
end
|
||||
|
||||
return CleanWoundAction
|
||||
138
42.6/media/lua/client/TOC/TimedActions/CutLimbAction.lua
Normal file
138
42.6/media/lua/client/TOC/TimedActions/CutLimbAction.lua
Normal file
@@ -0,0 +1,138 @@
|
||||
require "TimedActions/ISBaseTimedAction"
|
||||
local AmputationHandler = require("TOC/Handlers/AmputationHandler")
|
||||
local CommandsData = require("TOC/CommandsData")
|
||||
|
||||
-----------------------------
|
||||
|
||||
---@class CutLimbAction : ISBaseTimedAction
|
||||
---@field patient IsoPlayer
|
||||
---@field character IsoPlayer
|
||||
---@field patientX number
|
||||
---@field patientY number
|
||||
---@field limbName string
|
||||
---@field item InventoryItem
|
||||
---@field stitchesItem InventoryItem?
|
||||
---@field bandageItem InventoryItem?
|
||||
local CutLimbAction = ISBaseTimedAction:derive("CutLimbAction")
|
||||
|
||||
---Starts CutLimbAction
|
||||
---@param surgeon IsoPlayer This is gonna be self.character to have working animations
|
||||
---@param patient IsoPlayer
|
||||
---@param limbName string
|
||||
---@param item InventoryItem This is gonna be the saw, following ISBaseTimedAction
|
||||
---@param stitchesItem InventoryItem?
|
||||
---@param bandageItem InventoryItem?
|
||||
---@return CutLimbAction
|
||||
function CutLimbAction:new(surgeon, patient, limbName, item, stitchesItem, bandageItem)
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
|
||||
-- We need to follow ISBaseTimedAction. self.character is gonna be the surgeon
|
||||
o.character = surgeon
|
||||
o.patient = patient
|
||||
o.limbName = limbName
|
||||
o.item = item
|
||||
|
||||
o.patientX = patient:getX()
|
||||
o.patientY = patient:getY()
|
||||
|
||||
o.stitchesItem = stitchesItem or nil
|
||||
o.bandageItem = bandageItem or nil
|
||||
|
||||
o.stopOnWalk = true
|
||||
o.stopOnRun = true
|
||||
|
||||
o.maxTime = 1000 - (surgeon:getPerkLevel(Perks.Doctor) * 50)
|
||||
if o.character:isTimedActionInstant() then o.maxTime = 1 end
|
||||
|
||||
return o
|
||||
end
|
||||
|
||||
function CutLimbAction:isValid()
|
||||
return not ISHealthPanel.DidPatientMove(self.character,self.patient, self.patientX, self.patientY)
|
||||
end
|
||||
|
||||
function CutLimbAction:start()
|
||||
if self.patient == self.character then
|
||||
-- Self
|
||||
AmputationHandler.ApplyDamageDuringAmputation(self.patient, self.limbName)
|
||||
else
|
||||
-- Another player
|
||||
---@type relayDamageDuringAmputationParams
|
||||
local params = {patientNum = self.patient:getOnlineID(), limbName = self.limbName}
|
||||
sendClientCommand(CommandsData.modules.TOC_RELAY, CommandsData.server.Relay.RelayDamageDuringAmputation, params )
|
||||
end
|
||||
|
||||
---@type ISBaseTimedAction
|
||||
local prevAction = self
|
||||
|
||||
-- Handle stitching
|
||||
if self.stitchesItem then
|
||||
TOC_DEBUG.print("Stitches...")
|
||||
prevAction = AmputationHandler.PrepareStitchesAction(prevAction, self.limbName, self.character, self.patient, self.stitchesItem)
|
||||
end
|
||||
|
||||
-- Handle bandages
|
||||
if self.bandageItem then
|
||||
prevAction = AmputationHandler.PrepareBandagesAction(prevAction, self.limbName, self.character, self.patient, self.bandageItem)
|
||||
end
|
||||
|
||||
-- Setup cosmetic stuff
|
||||
self:setActionAnim("SawLog")
|
||||
self:setOverrideHandModels(self.item:getStaticModel())
|
||||
|
||||
-- Setup audio
|
||||
self.sound = self.character:getEmitter():playSound("Amputation")
|
||||
local radius = 5
|
||||
addSound(self.character, self.character:getX(), self.character:getY(), self.character:getZ(), radius, radius)
|
||||
|
||||
end
|
||||
|
||||
function CutLimbAction:waitToStart()
|
||||
if self.character == self.patient then
|
||||
return false
|
||||
end
|
||||
self.character:faceThisObject(self.patient)
|
||||
return self.character:shouldBeTurning()
|
||||
end
|
||||
|
||||
function CutLimbAction:update()
|
||||
self.character:setMetabolicTarget(Metabolics.HeavyWork)
|
||||
if self.character ~= self.patient then
|
||||
self.patient:setMetabolicTarget(Metabolics.HeavyWork)
|
||||
end
|
||||
end
|
||||
|
||||
function CutLimbAction:stopSound()
|
||||
if self.sound then
|
||||
self.character:getEmitter():stopSound(self.sound)
|
||||
self.sound = nil
|
||||
end
|
||||
end
|
||||
|
||||
function CutLimbAction:stop()
|
||||
self:stopSound()
|
||||
ISBaseTimedAction.stop(self)
|
||||
end
|
||||
|
||||
function CutLimbAction:perform()
|
||||
-- Stop the sound
|
||||
self:stopSound()
|
||||
|
||||
if self.patient == self.character then
|
||||
TOC_DEBUG.print("patient and surgeon are the same, executing on the client")
|
||||
local handler = AmputationHandler:new(self.limbName)
|
||||
handler:execute(true)
|
||||
else
|
||||
TOC_DEBUG.print("patient and surgeon not the same, sending relay to server")
|
||||
-- Other player
|
||||
---@type relayExecuteAmputationActionParams
|
||||
local params = {patientNum = self.patient:getOnlineID(), limbName = self.limbName}
|
||||
sendClientCommand(CommandsData.modules.TOC_RELAY, CommandsData.server.Relay.RelayExecuteAmputationAction, params )
|
||||
end
|
||||
|
||||
ISBaseTimedAction.perform(self)
|
||||
end
|
||||
|
||||
return CutLimbAction
|
||||
140
42.6/media/lua/client/TOC/TimedActions/IgnoredActions.lua
Normal file
140
42.6/media/lua/client/TOC/TimedActions/IgnoredActions.lua
Normal file
@@ -0,0 +1,140 @@
|
||||
-- TODO This section must be overhauled
|
||||
|
||||
-- local DataController = require("TOC/Controllers/DataController")
|
||||
-- local StaticData = require("TOC/StaticData")
|
||||
|
||||
---@diagnostic disable: duplicate-set-field
|
||||
-- Bunch of actions shouldn't be modified by the adjusted time
|
||||
|
||||
-----------------------------------------------
|
||||
---* Some actions have specific maxTime calculations and we must account for that
|
||||
---ISAttachItemHotbar
|
||||
---ISDetachItemHotbar
|
||||
---ISEquipWeaponAction
|
||||
---ISUnequipAction
|
||||
|
||||
-- --- We're forced to re-run this crap to fix it
|
||||
-- ---@param action ISBaseTimedAction
|
||||
-- local function HandleSpeedSpecificAction(action, time)
|
||||
-- action.skipTOC = true
|
||||
-- action.maxTime = time
|
||||
-- action.animSpeed = 1.0
|
||||
-- end
|
||||
|
||||
-- local og_ISAttachItemHotbar_new = ISAttachItemHotbar.new
|
||||
-- function ISAttachItemHotbar:new(character, item, slot, slotIndex, slotDef)
|
||||
-- local action = og_ISAttachItemHotbar_new(self, character, item, slot, slotIndex, slotDef)
|
||||
-- HandleSpeedSpecificAction(action, -1)
|
||||
-- return action
|
||||
-- end
|
||||
|
||||
-- local og_ISDetachItemHotbar_new = ISDetachItemHotbar.new
|
||||
-- function ISDetachItemHotbar:new(character, item)
|
||||
-- local action = og_ISDetachItemHotbar_new(self, character, item)
|
||||
-- HandleSpeedSpecificAction(action, -1)
|
||||
-- return action
|
||||
-- end
|
||||
|
||||
|
||||
-- local og_ISEquipWeaponAction_new = ISEquipWeaponAction.new
|
||||
-- function ISEquipWeaponAction:new(character, item, time, primary, twoHands)
|
||||
|
||||
-- local action = og_ISEquipWeaponAction_new(self, character, item, time, primary, twoHands)
|
||||
-- TOC_DEBUG.print("Override ISEquipWeaponAction New")
|
||||
|
||||
|
||||
-- -- check if right arm is cut off or not. if it is, penality shall apply
|
||||
-- -- if we got here, the action is valid, so we know that we have a prosthesis.
|
||||
|
||||
|
||||
-- local dcInst = DataController.GetInstance()
|
||||
|
||||
-- if not dcInst:getIsCut(StaticData.LIMBS_IND_STR.Hand_R) then
|
||||
-- action.skipTOC = true
|
||||
-- action.maxTime = time
|
||||
-- action.animSpeed = 1.0
|
||||
-- TOC_DEBUG.print("Skipping TOC for ISEquipWeaponAction new")
|
||||
-- end
|
||||
|
||||
|
||||
-- -- if not twoHands then
|
||||
-- -- TOC_DEBUG.print("Not a two handed action, re-adding skip TOC")
|
||||
-- -- HandleSpeedSpecificAction(action)
|
||||
-- -- end
|
||||
-- return action
|
||||
-- end
|
||||
|
||||
-- local og_ISUnequipAction_new = ISUnequipAction.new
|
||||
-- function ISUnequipAction:new(character, item, time)
|
||||
-- local action = og_ISUnequipAction_new(self, character, item, time)
|
||||
-- ---@cast item InventoryItem
|
||||
|
||||
-- -- For some reason (I have no clue why), if we re-run the method it breaks basically every unequip clothing action. Not for weapons though.
|
||||
-- if instanceof(item, 'HandWeapon') then
|
||||
-- --print("Running handlespeedspecificaction")
|
||||
-- HandleSpeedSpecificAction(action)
|
||||
-- end
|
||||
|
||||
-- return action
|
||||
-- end
|
||||
|
||||
------------------------------------------------------
|
||||
--- Normal cases
|
||||
|
||||
|
||||
local og_ISEatFoodAction_new = ISEatFoodAction.new
|
||||
function ISEatFoodAction:new(character, item, percentage)
|
||||
local action = og_ISEatFoodAction_new(self, character, item, percentage)
|
||||
--TOC_DEBUG.print("Override ISEatFoodAction")
|
||||
action.skipTOC = true
|
||||
return action
|
||||
end
|
||||
|
||||
local og_ISReadABook_new = ISReadABook.new
|
||||
function ISReadABook:new(character, item, time)
|
||||
local action = og_ISReadABook_new(self, character, item, time)
|
||||
--TOC_DEBUG.print("Override ISReadABook")
|
||||
action.skipTOC = true
|
||||
return action
|
||||
end
|
||||
|
||||
local og_ISTakePillAction_new = ISTakePillAction.new
|
||||
function ISTakePillAction:new(character, item, time)
|
||||
local action = og_ISTakePillAction_new(self, character, item, time)
|
||||
--TOC_DEBUG.print("Override ISTakePillAction")
|
||||
action.skipTOC = true
|
||||
return action
|
||||
end
|
||||
|
||||
local og_ISTakeWaterAction_new = ISTakeWaterAction.new
|
||||
function ISTakeWaterAction:new(character, item, waterUnit, waterObject, time, oldItem)
|
||||
local action = og_ISTakeWaterAction_new(self, character, item, waterUnit, waterObject, time, oldItem)
|
||||
--TOC_DEBUG.print("Override ISTakeWaterAction")
|
||||
action.skipTOC = true
|
||||
return action
|
||||
end
|
||||
|
||||
local og_ISDrinkFromBottle_new = ISDrinkFromBottle.new
|
||||
function ISDrinkFromBottle:new(character, item, uses)
|
||||
local action = og_ISDrinkFromBottle_new(self, character, item, uses)
|
||||
--TOC_DEBUG.print("Override ISDrinkFromBottle")
|
||||
action.skipTOC = true
|
||||
return action
|
||||
end
|
||||
|
||||
-- FIX This doesn't exist anymore in B42
|
||||
-- local og_ISFinalizeDealAction_new = ISFinalizeDealAction.new
|
||||
-- function ISFinalizeDealAction:new(player, otherPlayer, itemsToGive, itemsToReceive, time)
|
||||
-- local action = og_ISFinalizeDealAction_new(self, player, otherPlayer, itemsToGive, itemsToReceive, time)
|
||||
-- --TOC_DEBUG.print("Override ISFinalizeDealAction")
|
||||
-- action.skipTOC = true
|
||||
-- return action
|
||||
-- end
|
||||
|
||||
local og_ISCampingInfoAction_new = ISCampingInfoAction.new
|
||||
function ISCampingInfoAction:new(character, campfireObject, campfire)
|
||||
local action = og_ISCampingInfoAction_new(self, character, campfireObject, campfire)
|
||||
--TOC_DEBUG.print("Override ISCampingInfoAction")
|
||||
action.skipTOC = true
|
||||
return action
|
||||
end
|
||||
@@ -0,0 +1,80 @@
|
||||
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
|
||||
local StaticData = require("TOC/StaticData")
|
||||
|
||||
-- Since amputations are actually clothing items, we need to override ISWashYourself to account for that
|
||||
|
||||
-- TODO Clean this up
|
||||
|
||||
local og_ISWashYourself_perform = ISWashYourself.perform
|
||||
function ISWashYourself:perform()
|
||||
|
||||
TOC_DEBUG.print("ISWashYourself override")
|
||||
|
||||
---@type IsoPlayer
|
||||
local pl = self.character
|
||||
local plInv = pl:getInventory()
|
||||
-- Search for amputations and clean them here
|
||||
local amputatedLimbs = CachedDataHandler.GetAmputatedLimbs(pl:getUsername())
|
||||
for limbName, _ in pairs(amputatedLimbs) do
|
||||
|
||||
TOC_DEBUG.print("Checking if " .. limbName .. " is in inventory and washing it")
|
||||
|
||||
-- get clothing item
|
||||
local foundItem = plInv:FindAndReturn(StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limbName)
|
||||
if foundItem and instanceof(foundItem, "Clothing") then
|
||||
|
||||
TOC_DEBUG.print("Washing " .. limbName)
|
||||
|
||||
---@cast foundItem Clothing
|
||||
foundItem:setWetness(100)
|
||||
foundItem:setBloodLevel(0)
|
||||
foundItem:setDirtyness(0) -- TODO Integrate with other dirtyness
|
||||
|
||||
local coveredParts = BloodClothingType.getCoveredParts(foundItem:getBloodClothingType())
|
||||
for j=0, coveredParts:size() - 1 do
|
||||
foundItem:setBlood(coveredParts:get(j), 0)
|
||||
foundItem:setDirt(coveredParts:get(j), 0)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
og_ISWashYourself_perform(self)
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
local og_ISWashYourself_GetRequiredWater = ISWashYourself.GetRequiredWater
|
||||
|
||||
|
||||
---@param character IsoPlayer
|
||||
---@return integer
|
||||
function ISWashYourself.GetRequiredWater(character)
|
||||
|
||||
local units = og_ISWashYourself_GetRequiredWater(character)
|
||||
local amputatedLimbs = CachedDataHandler.GetAmputatedLimbs(character:getUsername())
|
||||
local plInv = character:getInventory()
|
||||
for limbName, _ in pairs(amputatedLimbs) do
|
||||
|
||||
TOC_DEBUG.print("Checking if " .. limbName .. " is in inventory and washing it")
|
||||
|
||||
-- get clothing item
|
||||
local item = plInv:FindAndReturn(StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limbName)
|
||||
if item and instanceof(item, "Clothing") then
|
||||
local coveredParts = BloodClothingType.getCoveredParts(item:getBloodClothingType())
|
||||
if coveredParts then
|
||||
for i=1,coveredParts:size() do
|
||||
local part = coveredParts:get(i-1)
|
||||
if item:getBlood(part) > 0 then
|
||||
units = units + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return units
|
||||
end
|
||||
116
42.6/media/lua/client/TOC/UI/ConfirmationPanel.lua
Normal file
116
42.6/media/lua/client/TOC/UI/ConfirmationPanel.lua
Normal file
@@ -0,0 +1,116 @@
|
||||
---@class ConfirmationPanel : ISPanel
|
||||
local ConfirmationPanel = ISPanel:derive("ConfirmationPanel")
|
||||
|
||||
---Starts a new confirmation panel
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param width number
|
||||
---@param height number
|
||||
---@param alertText string
|
||||
---@param onConfirmFunc function
|
||||
---@return ConfirmationPanel
|
||||
function ConfirmationPanel:new(x, y, width, height, alertText, parentPanel, onConfirmFunc)
|
||||
local o = ISPanel:new(x, y, width, height)
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
|
||||
o:initialise()
|
||||
o.alertText = alertText
|
||||
o.onConfirmFunc = onConfirmFunc
|
||||
o.parentPanel = parentPanel
|
||||
ConfirmationPanel.instance = o
|
||||
|
||||
---@cast o ConfirmationPanel
|
||||
return o
|
||||
end
|
||||
|
||||
function ConfirmationPanel:createChildren()
|
||||
ISPanel.createChildren(self)
|
||||
self.borderColor = { r = 1, g = 0, b = 0, a = 1 }
|
||||
|
||||
self.textPanel = ISRichTextPanel:new(0, 0, self.width, self.height)
|
||||
self.textPanel:initialise()
|
||||
self:addChild(self.textPanel)
|
||||
self.textPanel.defaultFont = UIFont.Medium
|
||||
self.textPanel.anchorTop = true
|
||||
self.textPanel.anchorLeft = false
|
||||
self.textPanel.anchorBottom = true
|
||||
self.textPanel.anchorRight = false
|
||||
self.textPanel.marginLeft = 0
|
||||
self.textPanel.marginTop = 10
|
||||
self.textPanel.marginRight = 0
|
||||
self.textPanel.marginBottom = 0
|
||||
self.textPanel.autosetheight = false
|
||||
self.textPanel.background = false
|
||||
self.textPanel:setText(self.alertText)
|
||||
self.textPanel:paginate()
|
||||
|
||||
local yPadding = 10
|
||||
local xPadding = self:getWidth() / 4
|
||||
local btnWidth = 100
|
||||
local btnHeight = 25
|
||||
|
||||
|
||||
local yButton = self:getHeight() - yPadding - btnHeight
|
||||
|
||||
self.btnYes = ISButton:new(xPadding, yButton, btnWidth, btnHeight, getText("IGUI_Yes"), self, self.onClick)
|
||||
self.btnYes.internal = "YES"
|
||||
self.btnYes:initialise()
|
||||
self.btnYes.borderColor = { r = 1, g = 0, b = 0, a = 1 }
|
||||
self.btnYes:setEnable(true)
|
||||
self:addChild(self.btnYes)
|
||||
|
||||
self.btnNo = ISButton:new(self:getWidth() - xPadding - btnWidth, yButton, btnWidth, btnHeight, getText("IGUI_No"), self,
|
||||
self.onClick)
|
||||
self.btnNo.internal = "NO"
|
||||
self.btnNo:initialise()
|
||||
self.btnNo:setEnable(true)
|
||||
self:addChild(self.btnNo)
|
||||
end
|
||||
|
||||
function ConfirmationPanel:onClick(btn)
|
||||
if btn.internal == 'YES' then
|
||||
self.onConfirmFunc(self.parentPanel)
|
||||
self:close()
|
||||
elseif btn.internal == 'NO' then
|
||||
self:close()
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------
|
||||
|
||||
---@param alertText string
|
||||
---@param x any
|
||||
---@param y any
|
||||
---@param parentPanel any
|
||||
---@param onConfirmFunc any
|
||||
---@return ConfirmationPanel
|
||||
function ConfirmationPanel.Open(alertText, x, y, parentPanel, onConfirmFunc)
|
||||
local width = 500
|
||||
local height = 120
|
||||
|
||||
|
||||
local screenWidth = getCore():getScreenWidth()
|
||||
local screenHeight = getCore():getScreenHeight()
|
||||
|
||||
-- Check for oversize
|
||||
if x+width > screenWidth then
|
||||
x = screenWidth - width
|
||||
end
|
||||
|
||||
if y+height > screenHeight then
|
||||
y = screenHeight - height
|
||||
end
|
||||
|
||||
local panel = ConfirmationPanel:new(x, y, width, height, alertText, parentPanel, onConfirmFunc)
|
||||
panel:initialise()
|
||||
panel:addToUIManager()
|
||||
panel:bringToTop()
|
||||
return panel
|
||||
end
|
||||
|
||||
function ConfirmationPanel.Close()
|
||||
ConfirmationPanel.instance:close()
|
||||
end
|
||||
|
||||
return ConfirmationPanel
|
||||
282
42.6/media/lua/client/TOC/UI/HealthPanel.lua
Normal file
282
42.6/media/lua/client/TOC/UI/HealthPanel.lua
Normal file
@@ -0,0 +1,282 @@
|
||||
local StaticData = require("TOC/StaticData")
|
||||
local DataController = require("TOC/Controllers/DataController")
|
||||
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
|
||||
local Compat = require("TOC/Compat")
|
||||
|
||||
local CutLimbInteractionHandler = require("TOC/UI/Interactions/CutLimbInteractionHandler")
|
||||
local WoundCleaningInteractionHandler = require("TOC/UI/Interactions/WoundCleaningInteractionHandler")
|
||||
------------------------
|
||||
|
||||
|
||||
local isReady = false
|
||||
|
||||
function SetHealthPanelTOC()
|
||||
|
||||
-- depending on compatibility
|
||||
|
||||
isReady = true
|
||||
end
|
||||
|
||||
|
||||
-- We're overriding ISHealthPanel to add custom textures to the body panel.
|
||||
-- By doing so we can show the player which limbs have been cut without having to use another menu
|
||||
-- We can show prosthesis too this way
|
||||
-- We also manage the drag'n drop of items on the body to let the players use the saw this way too
|
||||
---@diagnostic disable: duplicate-set-field
|
||||
|
||||
--ISHealthBodyPartPanel = ISBodyPartPanel:derive("ISHealthBodyPartPanel")
|
||||
|
||||
--* Handling drag n drop of the saw *--
|
||||
|
||||
local og_ISHealthPanel_dropItemsOnBodyPart = ISHealthPanel.dropItemsOnBodyPart
|
||||
function ISHealthPanel:dropItemsOnBodyPart(bodyPart, items)
|
||||
og_ISHealthPanel_dropItemsOnBodyPart(self, bodyPart, items)
|
||||
|
||||
TOC_DEBUG.print("override to dropItemsOnBodyPart running")
|
||||
local cutLimbInteraction = CutLimbInteractionHandler:new(self, bodyPart)
|
||||
local woundCleaningInteraction = WoundCleaningInteractionHandler:new(self, bodyPart, self.character:getUsername())
|
||||
|
||||
for _,item in ipairs(items) do
|
||||
cutLimbInteraction:checkItem(item)
|
||||
woundCleaningInteraction:checkItem(item)
|
||||
end
|
||||
if cutLimbInteraction:dropItems(items) or woundCleaningInteraction:dropItems(items) then
|
||||
return
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local og_ISHealthPanel_doBodyPartContextMenu = ISHealthPanel.doBodyPartContextMenu
|
||||
function ISHealthPanel:doBodyPartContextMenu(bodyPart, x, y)
|
||||
og_ISHealthPanel_doBodyPartContextMenu(self, bodyPart, x, y)
|
||||
local playerNum = self.otherPlayer and self.otherPlayer:getPlayerNum() or self.character:getPlayerNum()
|
||||
|
||||
-- To not recreate it but reuse the one that has been created in the original method
|
||||
|
||||
-- TODO This will work ONLY when an addOption has been already done in the og method.
|
||||
local context = getPlayerContextMenu(playerNum)
|
||||
context:bringToTop()
|
||||
context:setVisible(true)
|
||||
|
||||
|
||||
local cutLimbInteraction = CutLimbInteractionHandler:new(self, bodyPart)
|
||||
self:checkItems({cutLimbInteraction})
|
||||
cutLimbInteraction:addToMenu(context)
|
||||
|
||||
local woundCleaningInteraction = WoundCleaningInteractionHandler:new(self, bodyPart, self.character:getUsername())
|
||||
self:checkItems({woundCleaningInteraction})
|
||||
woundCleaningInteraction:addToMenu(context)
|
||||
end
|
||||
|
||||
|
||||
--* Modifications and additional methods to handle visible amputation on the health menu *--
|
||||
|
||||
---Get a value between 1 and 0.1 for the cicatrization time
|
||||
---@param cicTime integer
|
||||
---@return integer
|
||||
local function GetColorFromCicatrizationTime(cicTime, limbName)
|
||||
local defaultTime = StaticData.LIMBS_CICATRIZATION_TIME_IND_NUM[limbName]
|
||||
local delta = cicTime/defaultTime
|
||||
return math.max(0.15, math.min(delta, 1))
|
||||
end
|
||||
|
||||
---Try to draw the highest amputation in the health panel, based on the cicatrization time
|
||||
---@param side string L or R
|
||||
---@param username string
|
||||
function ISHealthPanel:tryDrawAmputation(highestAmputations, side, username)
|
||||
local redColor
|
||||
local texture
|
||||
|
||||
if TOC_DEBUG.enableHealthPanelDebug then
|
||||
redColor = 1
|
||||
texture = getTexture("media/ui/test_pattern.png")
|
||||
else
|
||||
if highestAmputations[side] == nil then return end
|
||||
local limbName = highestAmputations[side]
|
||||
--TOC_DEBUG.print("Drawing " .. tostring(limbName) .. " for " .. username)
|
||||
|
||||
local cicTime = DataController.GetInstance(username):getCicatrizationTime(limbName)
|
||||
redColor = GetColorFromCicatrizationTime(cicTime, limbName)
|
||||
|
||||
local sexPl = self.character:isFemale() and "Female" or "Male"
|
||||
texture = StaticData.HEALTH_PANEL_TEXTURES[sexPl][limbName]
|
||||
end
|
||||
|
||||
-- B42, for some reason the positioning of the texture changed. Realigned it manually with those fixed values
|
||||
self:drawTexture(texture, self.healthPanel.x - 5, self.healthPanel.y - 9, 1, redColor, 0, 0)
|
||||
end
|
||||
function ISHealthPanel:tryDrawProsthesis(highestAmputations, side, username)
|
||||
local dc = DataController.GetInstance(username) -- TODO CACHE PROSTHESIS!!! Don't use DC here
|
||||
local limbName = highestAmputations[side]
|
||||
if limbName and dc:getIsProstEquipped(limbName) then
|
||||
self:drawTexture(StaticData.HEALTH_PANEL_TEXTURES.ProstArm[side], self.healthPanel.x, self.healthPanel.y, 1, 1, 1, 1)
|
||||
end
|
||||
end
|
||||
|
||||
local og_ISHealthPanel_render = ISHealthPanel.render
|
||||
function ISHealthPanel:render()
|
||||
og_ISHealthPanel_render(self)
|
||||
local username = self.character:getUsername()
|
||||
local highestAmputations = CachedDataHandler.GetHighestAmputatedLimbs(username)
|
||||
|
||||
if highestAmputations ~= nil then
|
||||
|
||||
-- Left Texture
|
||||
self:tryDrawAmputation(highestAmputations, "L", username)
|
||||
self:tryDrawProsthesis(highestAmputations, "L", username)
|
||||
|
||||
-- Right Texture
|
||||
self:tryDrawAmputation(highestAmputations, "R", username)
|
||||
self:tryDrawProsthesis(highestAmputations, "R", username)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- local og_ISHealthPanel_update = ISHealthPanel.update
|
||||
-- function ISHealthPanel:update()
|
||||
-- og_ISHealthPanel_update(self)
|
||||
-- -- TODO Listen for changes on other player side instead of looping this
|
||||
|
||||
|
||||
-- -- FIX Re-enable it, just for test
|
||||
-- if self.character then
|
||||
-- local locPlUsername = getPlayer():getUsername()
|
||||
-- local remPlUsername = self.character:getUsername()
|
||||
-- if locPlUsername ~= remPlUsername and self:isReallyVisible() then
|
||||
-- -- Request update for TOC DATA
|
||||
-- local key = CommandsData.GetKey(remPlUsername)
|
||||
-- --ModData.request(key)
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
|
||||
|
||||
|
||||
-- We need to override this to force the alpha to 1
|
||||
local og_ISCharacterInfoWindow_render = ISCharacterInfoWindow.prerender
|
||||
function ISCharacterInfoWindow:prerender()
|
||||
og_ISCharacterInfoWindow_render(self)
|
||||
self.backgroundColor.a = 1
|
||||
end
|
||||
|
||||
-- We need to override this to force the alpha to 1 for the Medical Check in particular
|
||||
local og_ISHealthPanel_prerender = ISHealthPanel.prerender
|
||||
function ISHealthPanel:prerender()
|
||||
og_ISHealthPanel_prerender(self)
|
||||
self.backgroundColor.a = 1
|
||||
end
|
||||
|
||||
--- The medical check wrap the health panel into this. We need to override its color
|
||||
local overrideBackgroundColor = true
|
||||
local og_ISUIElement_wrapInCollapsableWindow = ISUIElement.wrapInCollapsableWindow
|
||||
---@param title string
|
||||
---@param resizable any
|
||||
---@param subClass any
|
||||
---@return any
|
||||
function ISUIElement:wrapInCollapsableWindow(title, resizable, subClass)
|
||||
local panel = og_ISUIElement_wrapInCollapsableWindow(self, title, resizable, subClass)
|
||||
|
||||
if overrideBackgroundColor then
|
||||
TOC_DEBUG.print("Overriding color for panel - " .. title)
|
||||
self.backgroundColor.a = 1
|
||||
panel.backgroundColor.a = 1
|
||||
end
|
||||
|
||||
return panel
|
||||
end
|
||||
|
||||
-- This is run when a player is trying the Medical Check action on another player
|
||||
local og_ISMedicalCheckAction_perform = ISMedicalCheckAction.perform
|
||||
function ISMedicalCheckAction:perform()
|
||||
local username = self.otherPlayer:getUsername()
|
||||
TOC_DEBUG.print("Medical Action on " .. username )
|
||||
|
||||
-- We need to recalculate them here before we can create the highest amputations point
|
||||
CachedDataHandler.CalculateAmputatedLimbs(username)
|
||||
og_ISMedicalCheckAction_perform(self)
|
||||
end
|
||||
|
||||
local og_ISHealthBodyPartListBox_doDrawItem = ISHealthBodyPartListBox.doDrawItem
|
||||
function ISHealthBodyPartListBox:doDrawItem(y, item, alt)
|
||||
y = og_ISHealthBodyPartListBox_doDrawItem(self, y, item, alt)
|
||||
y = y - 5
|
||||
local x = 15
|
||||
local fontHgt = getTextManager():getFontHeight(UIFont.Small)
|
||||
|
||||
local username = self.parent.character:getUsername()
|
||||
--local amputatedLimbs = CachedDataHandler.GetIndexedAmputatedLimbs(username)
|
||||
|
||||
---@type BodyPart
|
||||
local bodyPart = item.item.bodyPart
|
||||
|
||||
local bodyPartTypeStr = BodyPartType.ToString(bodyPart:getType())
|
||||
local limbName = StaticData.LIMBS_IND_STR[bodyPartTypeStr]
|
||||
|
||||
|
||||
-- We should cache a lot of other stuff to have this working with CacheDataHandler :(
|
||||
if limbName then
|
||||
local dcInst = DataController.GetInstance(username)
|
||||
if dcInst:getIsCut(limbName) and dcInst:getIsVisible(limbName) then
|
||||
if dcInst:getIsCicatrized(limbName) then
|
||||
if dcInst:getIsCauterized(limbName) then
|
||||
self:drawText("- " .. getText("IGUI_HealthPanel_Cauterized"), x, y, 0.58, 0.75, 0.28, 1, UIFont.Small)
|
||||
else
|
||||
self:drawText("- " .. getText("IGUI_HealthPanel_Cicatrized"), x, y, 0.28, 0.89, 0.28, 1, UIFont.Small)
|
||||
end
|
||||
|
||||
y = y + fontHgt
|
||||
else
|
||||
local cicaTime = dcInst:getCicatrizationTime(limbName)
|
||||
|
||||
-- Show it in percentage
|
||||
local maxCicaTime = StaticData.LIMBS_CICATRIZATION_TIME_IND_NUM[limbName]
|
||||
local percentage = (1 - cicaTime/maxCicaTime) * 100
|
||||
self:drawText("- " .. getText("IGUI_HealthPanel_Cicatrization") .. string.format(" %.2f", percentage) .. "%", x, y, 0.89, 0.28, 0.28, 1, UIFont.Small)
|
||||
y = y + fontHgt
|
||||
|
||||
local scaledDirtyness = math.floor(dcInst:getWoundDirtyness(limbName) * 100)
|
||||
self:drawText("- " .. getText("IGUI_HealthPanel_WoundDirtyness") .. string.format(" %d", scaledDirtyness) .. "%", x, y, 0.89, 0.28, 0.28, 1, UIFont.Small)
|
||||
y = y + fontHgt
|
||||
|
||||
end
|
||||
|
||||
if dcInst:getIsProstEquipped(limbName) then
|
||||
self:drawText("- " .. getText("IGUI_HealthPanel_ProstEquipped"), x, y, 0.28, 0.89, 0.28, 1, UIFont.Small)
|
||||
y = y + fontHgt
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
y = y + 5
|
||||
return y
|
||||
end
|
||||
|
||||
local og_ISHealthPanel_getDamagedParts = ISHealthPanel.getDamagedParts
|
||||
function ISHealthPanel:getDamagedParts()
|
||||
-- check for imeds or if TOC is ready to display its stuff on the health panel
|
||||
if isReady == false or Compat.handlers['iMeds'].isActive or Compat.handlers['iMedsFixed'].isActive then
|
||||
return og_ISHealthPanel_getDamagedParts(self)
|
||||
elseif isReady then
|
||||
local result = {}
|
||||
local bodyParts = self:getPatient():getBodyDamage():getBodyParts()
|
||||
if isClient() and not self:getPatient():isLocalPlayer() then
|
||||
bodyParts = self:getPatient():getBodyDamageRemote():getBodyParts()
|
||||
end
|
||||
|
||||
local patientUsername = self:getPatient():getUsername()
|
||||
local mdh = DataController.GetInstance(patientUsername)
|
||||
for i=1,bodyParts:size() do
|
||||
local bodyPart = bodyParts:get(i-1)
|
||||
local bodyPartTypeStr = BodyPartType.ToString(bodyPart:getType())
|
||||
local limbName = StaticData.LIMBS_IND_STR[bodyPartTypeStr]
|
||||
|
||||
if ISHealthPanel.cheat or bodyPart:HasInjury() or bodyPart:bandaged() or bodyPart:stitched() or bodyPart:getSplintFactor() > 0 or bodyPart:getAdditionalPain() > 10 or bodyPart:getStiffness() > 5 or (mdh:getIsCut(limbName) and mdh:getIsVisible(limbName)) then
|
||||
table.insert(result, bodyPart)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,287 @@
|
||||
local BaseHandler = require("TOC/UI/Interactions/HealthPanelBaseHandler")
|
||||
local StaticData = require("TOC/StaticData")
|
||||
local DataController = require("TOC/Controllers/DataController")
|
||||
local ConfirmationPanel = require("TOC/UI/ConfirmationPanel")
|
||||
|
||||
local CutLimbAction = require("TOC/TimedActions/CutLimbAction")
|
||||
---------------------
|
||||
|
||||
|
||||
|
||||
|
||||
--* Various functions to help during these pesky checks
|
||||
|
||||
---Check if the item type corresponds to a compatible saw
|
||||
---@param itemType string
|
||||
local function CheckIfSaw(itemType)
|
||||
return itemType == StaticData.SAWS_TYPES_IND_STR.saw
|
||||
or itemType == StaticData.SAWS_TYPES_IND_STR.gardenSaw
|
||||
end
|
||||
|
||||
---Return a compatible bandage
|
||||
---@param player IsoPlayer
|
||||
---@return InventoryItem?
|
||||
local function GetBandageItem(player)
|
||||
local plInv = player:getInventory()
|
||||
local bandageItem = plInv:FindAndReturn("Base.Bandage") or plInv:FindAndReturn("Base.RippedSheets")
|
||||
|
||||
---@cast bandageItem InventoryItem
|
||||
|
||||
return bandageItem
|
||||
end
|
||||
|
||||
---Return a suture needle or thread (only if the player has a needle too)
|
||||
---@param player IsoPlayer
|
||||
---@return InventoryItem?
|
||||
local function GetStitchesConsumableItem(player)
|
||||
local plInv = player:getInventory()
|
||||
|
||||
-- Suture needle has priority
|
||||
|
||||
local sutureNeedle = plInv:FindAndReturn("Base.SutureNeedle")
|
||||
|
||||
if sutureNeedle then
|
||||
return sutureNeedle
|
||||
else
|
||||
-- Didn't find the suture one, so let's search for the normal one + thread
|
||||
|
||||
local needleItem = plInv:FindAndReturn("Base.Needle")
|
||||
|
||||
if needleItem == nil then return nil end
|
||||
|
||||
-- Found the normal one, searching for thread
|
||||
|
||||
local threadItem = plInv:FindAndReturn("Base.Thread")
|
||||
---@cast threadItem DrainableComboItem
|
||||
|
||||
if threadItem and threadItem:getUsedDelta() > 0 then
|
||||
return threadItem
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
local textConfirmAmp = getText("IGUI_Confirmation_Amputate")
|
||||
local textAmp = getText("ContextMenu_Amputate")
|
||||
local textAmpBandage = getText("ContextMenu_Amputate_Bandage")
|
||||
local textAmpStitch = getText("ContextMenu_Amputate_Stitch")
|
||||
local textAmpStitchBandage = getText("ContextMenu_Amputate_Stitch_Bandage")
|
||||
|
||||
---Add the action to the queue
|
||||
---@param limbName string
|
||||
---@param surgeon IsoPlayer
|
||||
---@param patient IsoPlayer
|
||||
---@param sawItem InventoryItem
|
||||
---@param stitchesItem InventoryItem?
|
||||
---@param bandageItem InventoryItem?
|
||||
local function PerformAction(surgeon, patient, limbName, sawItem, stitchesItem, bandageItem)
|
||||
|
||||
|
||||
local x = (getCore():getScreenWidth() - 500) / 2
|
||||
local y = getCore():getScreenHeight() / 2
|
||||
|
||||
|
||||
ConfirmationPanel.Open(textConfirmAmp, x, y, nil, function()
|
||||
|
||||
-- get saw in hand
|
||||
-- todo primary or secondary depending on amputation status of surgeon
|
||||
ISTimedActionQueue.add(ISEquipWeaponAction:new(surgeon, sawItem, 50, true, false))
|
||||
|
||||
local lHandItem = surgeon:getSecondaryHandItem()
|
||||
if lHandItem then
|
||||
ISTimedActionQueue.add(ISUnequipAction:new(surgeon, lHandItem, 50))
|
||||
end
|
||||
|
||||
|
||||
ISTimedActionQueue.add(CutLimbAction:new(surgeon, patient, limbName, sawItem, stitchesItem, bandageItem))
|
||||
|
||||
end)
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
---Adds the actions to the inventory context menu
|
||||
---@param player IsoPlayer
|
||||
---@param context ISContextMenu
|
||||
---@param sawItem InventoryItem
|
||||
---@param stitchesItem InventoryItem?
|
||||
---@param bandageItem InventoryItem?
|
||||
local function AddInvAmputationOptions(player, context, sawItem, stitchesItem, bandageItem)
|
||||
local text
|
||||
|
||||
-- Set the correct text option
|
||||
if stitchesItem and bandageItem then
|
||||
--TOC_DEBUG.print("stitches and bandage")
|
||||
text = textAmpStitchBandage
|
||||
elseif not bandageItem and stitchesItem then
|
||||
--TOC_DEBUG.print("only stitches")
|
||||
text = textAmpStitch
|
||||
elseif not stitchesItem and bandageItem then
|
||||
--TOC_DEBUG.print("only bandages")
|
||||
text = textAmpBandage
|
||||
else
|
||||
text = textAmp
|
||||
end
|
||||
|
||||
TOC_DEBUG.print("Current text " .. tostring(text))
|
||||
local option = context:addOption(text, nil)
|
||||
local subMenu = context:getNew(context)
|
||||
context:addSubMenu(option, subMenu)
|
||||
|
||||
|
||||
|
||||
-- TODO Separate into groups
|
||||
|
||||
|
||||
-- Amputate -> Top/Bottom - > Left/Right - > Limb
|
||||
-- for i=1, #StaticData.PROSTHESES_GROUPS_STR do
|
||||
-- local group = StaticData.PROSTHESES_GROUPS_STR[i]
|
||||
|
||||
-- for j=1, #StaticData.SIDES_IND_STR do
|
||||
|
||||
-- end
|
||||
|
||||
-- end
|
||||
|
||||
-- for k,v in pairs(StaticData.LIMBS_TO_PROST_GROUP_MATCH_IND_STR) do
|
||||
-- TOC_DEBUG.print(k)
|
||||
|
||||
-- end
|
||||
|
||||
|
||||
local dc = DataController.GetInstance()
|
||||
for i = 1, #StaticData.LIMBS_STR do
|
||||
local limbName = StaticData.LIMBS_STR[i]
|
||||
if not dc:getIsCut(limbName) then
|
||||
local limbTranslatedName = getText("ContextMenu_Limb_" .. limbName)
|
||||
subMenu:addOption(limbTranslatedName, player, PerformAction, player, limbName, sawItem, stitchesItem, bandageItem)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---Handler for OnFillInventoryObjectContextMenu
|
||||
---@param playerNum number
|
||||
---@param context ISContextMenu
|
||||
---@param items table
|
||||
local function AddInventoryAmputationMenu(playerNum, context, items)
|
||||
local item
|
||||
|
||||
-- We can't access the item if we don't create the loop and start ipairs.
|
||||
for _, v in ipairs(items) do
|
||||
item = v
|
||||
if not instanceof(v, "InventoryItem") then
|
||||
item = v.items[1]
|
||||
end
|
||||
break
|
||||
end
|
||||
|
||||
local itemType = item:getType()
|
||||
if CheckIfSaw(itemType) then
|
||||
local player = getSpecificPlayer(playerNum)
|
||||
local sawItem = item
|
||||
local stitchesItem = GetStitchesConsumableItem(player)
|
||||
local bandageItem = GetBandageItem(player)
|
||||
|
||||
TOC_DEBUG.print("Stitches item: " .. tostring(stitchesItem))
|
||||
TOC_DEBUG.print("Bandage item: " .. tostring(bandageItem))
|
||||
|
||||
AddInvAmputationOptions(player, context, sawItem, stitchesItem, bandageItem)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Events.OnFillInventoryObjectContextMenu.Add(AddInventoryAmputationMenu)
|
||||
|
||||
-------------------------------------
|
||||
|
||||
---@class CutLimbInteractionHandler : BaseHandler
|
||||
---@field items table
|
||||
---@field limbName string
|
||||
---@field itemType string temporary
|
||||
local CutLimbInteractionHandler = BaseHandler:derive("CutLimbInteractionHandler")
|
||||
|
||||
|
||||
---Creates new CutLimbInteractionHandler
|
||||
---@param panel ISUIElement
|
||||
---@param bodyPart BodyPart
|
||||
---@return CutLimbInteractionHandler
|
||||
function CutLimbInteractionHandler:new(panel, bodyPart)
|
||||
local o = BaseHandler.new(self, panel, bodyPart)
|
||||
o.items.ITEMS = {}
|
||||
o.limbName = BodyPartType.ToString(bodyPart:getType())
|
||||
o.itemType = "Saw"
|
||||
--TOC_DEBUG.print("init CutLimbInteractionHandler")
|
||||
return o
|
||||
end
|
||||
|
||||
---@param item InventoryItem
|
||||
function CutLimbInteractionHandler:checkItem(item)
|
||||
--TOC_DEBUG.print("CutLimbInteractionHandler checkItem")
|
||||
local itemType = item:getType()
|
||||
|
||||
if CheckIfSaw(itemType) then
|
||||
TOC_DEBUG.print("added to list -> " .. itemType)
|
||||
self:addItem(self.items.ITEMS, item)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param type any
|
||||
function CutLimbInteractionHandler:openConfirmation(x, y, type)
|
||||
ConfirmationPanel.Open(textConfirmAmp, x, y, nil, function()
|
||||
self.onMenuOptionSelected(self, type)
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
|
||||
---@param context ISContextMenu
|
||||
function CutLimbInteractionHandler:addToMenu(context)
|
||||
--TOC_DEBUG.print("CutLimbInteractionHandler addToMenu")
|
||||
local types = self:getAllItemTypes(self.items.ITEMS)
|
||||
local patientUsername = self:getPatient():getUsername()
|
||||
if #types > 0 and StaticData.LIMBS_TO_BODYLOCS_IND_BPT[self.limbName] and not DataController.GetInstance(patientUsername):getIsCut(self.limbName) then
|
||||
TOC_DEBUG.print("addToMenu, types > 0")
|
||||
|
||||
local x = (getCore():getScreenWidth() - 500) / 2
|
||||
local y = getCore():getScreenHeight() / 2
|
||||
|
||||
for i=1, #types do
|
||||
context:addOption(getText("ContextMenu_Amputate"), self, self.openConfirmation, x, y, types[i])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function CutLimbInteractionHandler:dropItems(items)
|
||||
local types = self:getAllItemTypes(items)
|
||||
if #self.items.ITEMS > 0 and #types == 1 and StaticData.LIMBS_TO_BODYLOCS_IND_BPT[self.limbName] then
|
||||
self:onMenuOptionSelected(types[1])
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---Check if CutLimbInteractionHandler is valid, the limb must not be cut to be valid
|
||||
---@return boolean
|
||||
function CutLimbInteractionHandler:isValid()
|
||||
--TOC_DEBUG.print("CutLimbInteractionHandler isValid")
|
||||
self:checkItems()
|
||||
local patientUsername = self:getPatient():getUsername()
|
||||
return not DataController.GetInstance(patientUsername):getIsCut(self.limbName)
|
||||
end
|
||||
|
||||
function CutLimbInteractionHandler:perform(previousAction, itemType)
|
||||
local item = self:getItemOfType(self.items.ITEMS, itemType)
|
||||
previousAction = self:toPlayerInventory(item, previousAction)
|
||||
TOC_DEBUG.print("Perform CutLimbInteractionHandler on " .. self.limbName)
|
||||
local action = CutLimbAction:new(self:getDoctor(),self:getPatient(), self.limbName, item)
|
||||
ISTimedActionQueue.addAfter(previousAction, action)
|
||||
end
|
||||
|
||||
return CutLimbInteractionHandler
|
||||
@@ -0,0 +1,139 @@
|
||||
-- Had to copy and paste this stuff from the base game since this is a local only class. Kinda shit, but eh
|
||||
|
||||
---@class BaseHandler : ISBaseObject
|
||||
---@field panel ISUIElement
|
||||
---@field bodyPart BodyPart
|
||||
---@field items table
|
||||
local BaseHandler = ISBaseObject:derive("BaseHandler")
|
||||
|
||||
---@param panel ISUIElement
|
||||
---@param bodyPart BodyPart
|
||||
---@return table
|
||||
function BaseHandler:new(panel, bodyPart)
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
o.panel = panel
|
||||
o.bodyPart = bodyPart
|
||||
o.items = {}
|
||||
return o
|
||||
end
|
||||
|
||||
function BaseHandler:isInjured()
|
||||
TOC_DEBUG.print("Running isInjured")
|
||||
local bodyPart = self.bodyPart
|
||||
return (bodyPart:HasInjury() or bodyPart:stitched() or bodyPart:getSplintFactor() > 0) and not bodyPart:bandaged()
|
||||
end
|
||||
|
||||
function BaseHandler:checkItems()
|
||||
for k,v in pairs(self.items) do
|
||||
table.wipe(v)
|
||||
end
|
||||
|
||||
local containers = ISInventoryPaneContextMenu.getContainers(self:getDoctor())
|
||||
local done = {}
|
||||
local childContainers = {}
|
||||
for i=1,containers:size() do
|
||||
local container = containers:get(i-1)
|
||||
done[container] = true
|
||||
table.wipe(childContainers)
|
||||
self:checkContainerItems(container, childContainers)
|
||||
for _,container2 in ipairs(childContainers) do
|
||||
if not done[container2] then
|
||||
done[container2] = true
|
||||
self:checkContainerItems(container2, nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function BaseHandler:checkContainerItems(container, childContainers)
|
||||
local containerItems = container:getItems()
|
||||
for i=1,containerItems:size() do
|
||||
local item = containerItems:get(i-1)
|
||||
if item:IsInventoryContainer() then
|
||||
if childContainers then
|
||||
table.insert(childContainers, item:getInventory())
|
||||
end
|
||||
else
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
self:checkItem(item) -- This is in inherited classes, we never use this class by itself
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function BaseHandler:dropItems(items)
|
||||
return false
|
||||
end
|
||||
|
||||
function BaseHandler:addItem(items, item)
|
||||
table.insert(items, item)
|
||||
end
|
||||
|
||||
function BaseHandler:getAllItemTypes(items)
|
||||
local done = {}
|
||||
local types = {}
|
||||
for _,item in ipairs(items) do
|
||||
if not done[item:getFullType()] then
|
||||
table.insert(types, item:getFullType())
|
||||
done[item:getFullType()] = true
|
||||
end
|
||||
end
|
||||
return types
|
||||
end
|
||||
|
||||
function BaseHandler:getItemOfType(items, type)
|
||||
for _,item in ipairs(items) do
|
||||
if item:getFullType() == type then
|
||||
return item
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function BaseHandler:getItemOfTag(items, type)
|
||||
for _,item in ipairs(items) do
|
||||
if item:hasTag(type) then
|
||||
return item
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function BaseHandler:getAllItemsOfType(items, type)
|
||||
local items = {}
|
||||
for _,item in ipairs(items) do
|
||||
if item:getFullType() == type then
|
||||
table.insert(items, item)
|
||||
end
|
||||
end
|
||||
return items
|
||||
end
|
||||
|
||||
function BaseHandler:onMenuOptionSelected(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
|
||||
ISTimedActionQueue.add(HealthPanelAction:new(self:getDoctor(), self, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8))
|
||||
end
|
||||
|
||||
function BaseHandler:toPlayerInventory(item, previousAction)
|
||||
if item:getContainer() ~= self:getDoctor():getInventory() then
|
||||
local action = ISInventoryTransferAction:new(self:getDoctor(), item, item:getContainer(), self:getDoctor():getInventory())
|
||||
ISTimedActionQueue.addAfter(previousAction, action)
|
||||
-- FIXME: ISHealthPanel.actions never gets cleared
|
||||
self.panel.actions = self.panel.actions or {}
|
||||
self.panel.actions[action] = self.bodyPart
|
||||
return action
|
||||
end
|
||||
return previousAction
|
||||
end
|
||||
|
||||
---@return IsoPlayer
|
||||
function BaseHandler:getDoctor()
|
||||
return self.panel.otherPlayer or self.panel.character
|
||||
end
|
||||
|
||||
---@return IsoPlayer
|
||||
function BaseHandler:getPatient()
|
||||
return self.panel.character
|
||||
end
|
||||
|
||||
return BaseHandler
|
||||
@@ -0,0 +1,84 @@
|
||||
|
||||
local BaseHandler = require("TOC/UI/Interactions/HealthPanelBaseHandler")
|
||||
local CommonMethods = require("TOC/CommonMethods")
|
||||
local DataController = require("TOC/Controllers/DataController")
|
||||
|
||||
local CleanWoundAction = require("TOC/TimedActions/CleanWoundAction")
|
||||
-------------------------
|
||||
---@class WoundCleaningInteractionHandler : BaseHandler
|
||||
---@field username string
|
||||
---@field limbName string
|
||||
local WoundCleaningInteractionHandler = BaseHandler:derive("WoundCleaningInteractionHandler")
|
||||
|
||||
---@param panel any
|
||||
---@param bodyPart any
|
||||
---@param username string
|
||||
---@return table
|
||||
function WoundCleaningInteractionHandler:new(panel, bodyPart, username)
|
||||
local o = BaseHandler.new(self, panel, bodyPart)
|
||||
o.items.ITEMS = {}
|
||||
o.username = username
|
||||
|
||||
o.limbName = CommonMethods.GetLimbNameFromBodyPart(bodyPart)
|
||||
|
||||
return o
|
||||
end
|
||||
|
||||
function WoundCleaningInteractionHandler:checkItem(item)
|
||||
-- Disinfected rag or bandage
|
||||
--TOC_DEBUG.print("WoundCleaningInteractionHandler checkItem")
|
||||
if item:getBandagePower() >=2 and item:isAlcoholic() then
|
||||
--TOC_DEBUG.print("Adding " .. item:getName())
|
||||
self:addItem(self.items.ITEMS, item)
|
||||
end
|
||||
end
|
||||
|
||||
function WoundCleaningInteractionHandler:addToMenu(context)
|
||||
--TOC_DEBUG.print("WoundCleaningInteraction addToMenu")
|
||||
|
||||
local types = self:getAllItemTypes(self.items.ITEMS)
|
||||
if #types > 0 and self:isValid() then
|
||||
TOC_DEBUG.print("WoundCleaningInteraction inside addToMenu")
|
||||
local option = context:addOption(getText("ContextMenu_CleanWound"), nil)
|
||||
local subMenu = context:getNew(context)
|
||||
context:addSubMenu(option, subMenu)
|
||||
for i=1, #types do
|
||||
local item = self:getItemOfType(self.items.ITEMS, types[i])
|
||||
subMenu:addOption(item:getName(), self, self.onMenuOptionSelected, item:getFullType())
|
||||
TOC_DEBUG.print(item:getName())
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function WoundCleaningInteractionHandler:dropItems(items)
|
||||
local types = self:getAllItemTypes(items)
|
||||
if #self.items.ITEMS > 0 and #types == 1 and self:isActionValid() then
|
||||
self:onMenuOptionSelected(types[1])
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function WoundCleaningInteractionHandler:isValid()
|
||||
self:checkItems()
|
||||
return self:isActionValid()
|
||||
end
|
||||
|
||||
function WoundCleaningInteractionHandler:isActionValid()
|
||||
if self.limbName == nil then return false end
|
||||
local dcInst = DataController.GetInstance(self.username)
|
||||
local check = dcInst:getIsCut(self.limbName) and not dcInst:getIsCicatrized(self.limbName) and dcInst:getWoundDirtyness(self.limbName) > 0
|
||||
--TOC_DEBUG.print("WoundCleaningInteraction isValid: " .. tostring(check))
|
||||
return check
|
||||
end
|
||||
|
||||
function WoundCleaningInteractionHandler:perform(previousAction, itemType)
|
||||
local item = self:getItemOfType(self.items.ITEMS, itemType)
|
||||
previousAction = self:toPlayerInventory(item, previousAction)
|
||||
local action = CleanWoundAction:new(self:getDoctor(), self:getPatient(), item, self.bodyPart)
|
||||
ISTimedActionQueue.addAfter(previousAction, action)
|
||||
end
|
||||
|
||||
|
||||
return WoundCleaningInteractionHandler
|
||||
98
42.6/media/lua/client/TOC/UI/SurgeryInteractions.lua
Normal file
98
42.6/media/lua/client/TOC/UI/SurgeryInteractions.lua
Normal file
@@ -0,0 +1,98 @@
|
||||
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
|
||||
local DataController = require("TOC/Controllers/DataController")
|
||||
local CauterizeAction = require("TOC/TimedActions/CauterizeAction")
|
||||
---------------
|
||||
|
||||
|
||||
---@param tooltip ISToolTip
|
||||
---@param desc string
|
||||
local function AppendToDescription(tooltip, desc)
|
||||
if tooltip.description == "" then
|
||||
desc = string.upper(string.sub(desc, 1, 1)) .. string.sub(desc, 2)
|
||||
tooltip.description = desc
|
||||
else
|
||||
desc = string.lower(string.sub(desc, 1, 1)) .. string.sub(desc, 2)
|
||||
tooltip.description = tooltip.description .. getText("Tooltip_Surgery_And") .. desc
|
||||
end
|
||||
end
|
||||
|
||||
---@param playerNum number
|
||||
---@param context ISContextMenu
|
||||
---@param worldObjects any
|
||||
---@param test any
|
||||
local function AddStoveContextMenu(playerNum, context, worldObjects, test)
|
||||
if test then return true end
|
||||
|
||||
local pl = getSpecificPlayer(playerNum)
|
||||
|
||||
local dcInst = DataController.GetInstance()
|
||||
if not dcInst:getIsAnyLimbCut() then return end
|
||||
local amputatedLimbs = CachedDataHandler.GetAmputatedLimbs(pl:getUsername())
|
||||
|
||||
---@type IsoStove?
|
||||
local stoveObj = nil
|
||||
for _, obj in pairs(worldObjects) do
|
||||
if instanceof(obj, "IsoStove") then
|
||||
stoveObj = obj
|
||||
break
|
||||
end
|
||||
end
|
||||
if stoveObj == nil then return end
|
||||
local tempTooltip = ISToolTip:new()
|
||||
tempTooltip:initialise()
|
||||
tempTooltip.description = ""
|
||||
tempTooltip:setVisible(false)
|
||||
|
||||
local addMainOption = false
|
||||
local subMenu
|
||||
|
||||
for k, _ in pairs(amputatedLimbs) do
|
||||
|
||||
-- We need to let the player cauterize ONLY the visible one!
|
||||
---@type string
|
||||
local limbName = k
|
||||
if dcInst:getIsVisible(limbName) and not dcInst:getIsCicatrized(limbName) then
|
||||
if addMainOption == false then
|
||||
-- Adds the cauterize option ONLY when it's needed
|
||||
local optionMain = context:addOption(getText("ContextMenu_Cauterize"), nil)
|
||||
subMenu = context:getNew(context)
|
||||
context:addSubMenu(optionMain, subMenu)
|
||||
addMainOption = true
|
||||
end
|
||||
|
||||
local option = subMenu:addOption(getText("ContextMenu_Limb_" .. limbName), nil, function()
|
||||
local adjacent = AdjacentFreeTileFinder.Find(stoveObj:getSquare(), pl)
|
||||
ISTimedActionQueue.add(ISWalkToTimedAction:new(pl, adjacent))
|
||||
ISTimedActionQueue.add(CauterizeAction:new(pl, limbName, stoveObj))
|
||||
end)
|
||||
|
||||
|
||||
-- Notifications, in case the player can't do the action
|
||||
local isPlayerCourageous = pl:HasTrait("Brave") or pl:getPerkLevel(Perks.Strength) > 5
|
||||
local isTempHighEnough = stoveObj:getCurrentTemperature() >= 250
|
||||
local isLimbFree = not dcInst:getIsProstEquipped(limbName)
|
||||
|
||||
option.notAvailable = not(isPlayerCourageous and isTempHighEnough and isLimbFree)
|
||||
if not isTempHighEnough then
|
||||
AppendToDescription(tempTooltip, getText("Tooltip_Surgery_TempTooLow"))
|
||||
end
|
||||
|
||||
if not isPlayerCourageous then
|
||||
AppendToDescription(tempTooltip, getText("Tooltip_Surgery_Coward"))
|
||||
end
|
||||
|
||||
if not isLimbFree then
|
||||
AppendToDescription(tempTooltip, getText("Tooltip_Surgery_LimbNotFree"))
|
||||
end
|
||||
|
||||
if option.notAvailable then
|
||||
tempTooltip:setName(getText("Tooltip_Surgery_CantCauterize"))
|
||||
option.toolTip = tempTooltip
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
Events.OnFillWorldObjectContextMenu.Add(AddStoveContextMenu)
|
||||
157
42.6/media/lua/client/TOC/Zombies/ZombiesAmputation.lua
Normal file
157
42.6/media/lua/client/TOC/Zombies/ZombiesAmputation.lua
Normal file
@@ -0,0 +1,157 @@
|
||||
require "lua_timers"
|
||||
|
||||
local ItemsController = require("TOC/Controllers/ItemsController")
|
||||
local StaticData = require("TOC/StaticData")
|
||||
local CommandsData = require("TOC/CommandsData")
|
||||
-------------------------------
|
||||
|
||||
---@param zombie IsoZombie|IsoGameCharacter|IsoMovingObject|IsoObject
|
||||
---@return integer trueID
|
||||
local function GetZombieID(zombie)
|
||||
-- Big love to Chuck and Sir Doggy Jvla for this code
|
||||
---@diagnostic disable-next-line: param-type-mismatch
|
||||
local pID = zombie:getPersistentOutfitID()
|
||||
local bits = string.split(string.reverse(Long.toUnsignedString(pID, 2)), "")
|
||||
while #bits < 16 do bits[#bits + 1] = 0 end
|
||||
|
||||
-- trueID
|
||||
bits[16] = 0
|
||||
local trueID = Long.parseUnsignedLong(string.reverse(table.concat(bits, "")), 2)
|
||||
|
||||
return trueID
|
||||
end
|
||||
|
||||
-------------------------------
|
||||
|
||||
|
||||
---@param item InventoryItem
|
||||
local function PredicateAmputationItems(item)
|
||||
return item:getType():contains("Amputation_")
|
||||
end
|
||||
|
||||
---@param item InventoryItem
|
||||
local function PredicateAmputationItemLeft(item)
|
||||
return item:getType():contains("Amputation_") and item:getType():contains("_L")
|
||||
end
|
||||
|
||||
---@param item InventoryItem
|
||||
local function PredicateAmputationItemRight(item)
|
||||
return item:getType():contains("Amputation_") and item:getType():contains("_R")
|
||||
end
|
||||
|
||||
---@param zombie IsoZombie
|
||||
local function SpawnAmputation(zombie, side)
|
||||
local index = ZombRand(1, #StaticData.PARTS_STR)
|
||||
local limb = StaticData.PARTS_STR[index] .. "_" .. side
|
||||
local amputationFullType = StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limb
|
||||
|
||||
|
||||
ItemsController.Zombie.SpawnAmputationItem(zombie, amputationFullType)
|
||||
|
||||
|
||||
-- Add reference and transmit it to server
|
||||
local pID = GetZombieID(zombie)
|
||||
local zombieKey = CommandsData.GetZombieKey()
|
||||
local zombiesMD = ModData.getOrCreate(zombieKey)
|
||||
if zombiesMD[pID] == nil then zombiesMD[pID] = {} end
|
||||
zombiesMD[pID][side] = amputationFullType
|
||||
ModData.add(zombieKey, zombiesMD)
|
||||
ModData.transmit(zombieKey)
|
||||
end
|
||||
|
||||
-------------------------------
|
||||
|
||||
local bloodAmount = 10
|
||||
|
||||
|
||||
---@param player IsoGameCharacter
|
||||
---@param zombie IsoZombie
|
||||
---@param handWeapon HandWeapon
|
||||
local function HandleZombiesAmputations(player, zombie, handWeapon, damage)
|
||||
if not SandboxVars.TOC.EnableZombieAmputations then return end
|
||||
|
||||
if not instanceof(zombie, "IsoZombie") or not instanceof(player, "IsoPlayer") then return end
|
||||
if player ~= getPlayer() then return end
|
||||
|
||||
-- Check type of weapon. No hands, only knifes or such
|
||||
local weaponCategories = handWeapon:getScriptItem():getCategories()
|
||||
if not (weaponCategories:contains("Axe") or weaponCategories:contains("LongBlade")) then return end
|
||||
|
||||
local isCrit = player:isCriticalHit()
|
||||
local randomChance = ZombRand(0, 100) > (100 - SandboxVars.TOC.ZombieAmputationDamageChance)
|
||||
if (damage > SandboxVars.TOC.ZombieAmputationDamageThreshold and randomChance) or isCrit then
|
||||
TOC_DEBUG.print("Amputating zombie limbs - damage: " .. tostring(damage))
|
||||
local zombieInv = zombie:getInventory()
|
||||
|
||||
-- Check left or right
|
||||
local randSide = ZombRand(2) -- Random side
|
||||
local preferredSide = randSide == 0 and "L" or "R"
|
||||
local alternateSide = preferredSide == "L" and "R" or "L"
|
||||
|
||||
local predicatePreferred = preferredSide == "L" and PredicateAmputationItemLeft or PredicateAmputationItemRight
|
||||
local predicateAlternate = alternateSide == "L" and PredicateAmputationItemLeft or PredicateAmputationItemRight
|
||||
|
||||
if not zombieInv:containsEval(predicatePreferred) then
|
||||
SpawnAmputation(zombie, preferredSide)
|
||||
elseif not zombieInv:containsEval(predicateAlternate) then
|
||||
SpawnAmputation(zombie, alternateSide)
|
||||
end
|
||||
|
||||
TOC_DEBUG.print("Amputating zombie limbs - damage: " .. tostring(damage) .. ", preferred limb side: " .. preferredSide)
|
||||
|
||||
-- add blood splat every couple of seconds for a while
|
||||
addBloodSplat(getCell():getGridSquare(zombie:getX(), zombie:getY(), zombie:getZ()), bloodAmount)
|
||||
local timerName = tostring(GetZombieID(zombie)) .. "_timer"
|
||||
timer:Create(timerName, 1, 10, function()
|
||||
addBloodSplat(getCell():getGridSquare(zombie:getX(), zombie:getY(), zombie:getZ()), bloodAmount)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
Events.OnWeaponHitCharacter.Add(HandleZombiesAmputations)
|
||||
|
||||
-----------------------------
|
||||
|
||||
local localOnlyZombiesMD
|
||||
|
||||
local function SetupZombiesModData()
|
||||
if not SandboxVars.TOC.EnableZombieAmputations then return end
|
||||
local zombieKey = CommandsData.GetZombieKey()
|
||||
localOnlyZombiesMD = ModData.getOrCreate(zombieKey)
|
||||
end
|
||||
|
||||
Events.OnInitGlobalModData.Add(SetupZombiesModData)
|
||||
|
||||
|
||||
---@param zombie IsoZombie
|
||||
local function ReapplyAmputation(zombie)
|
||||
if not SandboxVars.TOC.EnableZombieAmputations then return end
|
||||
|
||||
local pID = GetZombieID(zombie)
|
||||
|
||||
if localOnlyZombiesMD[pID] ~= nil then
|
||||
-- check if zombie has amputation
|
||||
local zombiesAmpData = localOnlyZombiesMD[pID]
|
||||
local zombieInv = zombie:getInventory()
|
||||
local foundItem = zombieInv:containsEvalRecurse(PredicateAmputationItems)
|
||||
|
||||
if foundItem then
|
||||
return
|
||||
else
|
||||
local leftAmp = zombiesAmpData['L']
|
||||
if leftAmp then
|
||||
ItemsController.Zombie.SpawnAmputationItem(zombie, leftAmp)
|
||||
end
|
||||
|
||||
local rightAmp = zombiesAmpData['R']
|
||||
if rightAmp then
|
||||
ItemsController.Zombie.SpawnAmputationItem(zombie, rightAmp)
|
||||
end
|
||||
|
||||
-- Removes reference, local only
|
||||
localOnlyZombiesMD[pID] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Events.OnZombieUpdate.Add(ReapplyAmputation)
|
||||
216
42.6/media/lua/client/lua_timers.lua
Normal file
216
42.6/media/lua/client/lua_timers.lua
Normal file
@@ -0,0 +1,216 @@
|
||||
-- Made by Vyshnia
|
||||
-- Workshop ID: 2875394066
|
||||
-- Mod ID: LuaTimers
|
||||
|
||||
local os_time = os.time
|
||||
local table_insert = table.insert
|
||||
local table_remove = table.remove
|
||||
local assert = assert
|
||||
local type = type
|
||||
local pairs = pairs
|
||||
|
||||
timer = {
|
||||
Timers = {},
|
||||
SimpleTimers = {}
|
||||
}
|
||||
|
||||
function timer:Simple(delay, func)
|
||||
|
||||
assert(type(delay) == "number", "Delay of timer should be a number type")
|
||||
assert(type(func) == "function", "Func of timer should be a function type (lol)")
|
||||
|
||||
table_insert(self.SimpleTimers, {
|
||||
EndTime = os_time() + delay,
|
||||
Func = func
|
||||
})
|
||||
|
||||
end
|
||||
|
||||
function timer:Create(name, delay, repetitions, func)
|
||||
|
||||
assert(type(name) == "string", "ID of timer should be a string type")
|
||||
assert(type(delay) == "number", "Delay of timer should be a number type")
|
||||
assert(type(repetitions) == "number", "Repetitions of timer should be a number type")
|
||||
assert(type(func) == "function", "Func of timer should be a function type (lol)")
|
||||
|
||||
self.Timers[name] = {
|
||||
Delay = delay,
|
||||
StartRepetitions = repetitions,
|
||||
Repetitions = repetitions,
|
||||
Infinity = repetitions == 0,
|
||||
LastFuncTime = os_time(),
|
||||
Func = func,
|
||||
Paused = false,
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
local function timerUpdate()
|
||||
|
||||
local cur_time = os_time()
|
||||
|
||||
for k,v in pairs(timer.Timers) do
|
||||
|
||||
if not v.Paused then
|
||||
|
||||
if cur_time >= v.LastFuncTime + v.Delay then
|
||||
|
||||
v.Func()
|
||||
|
||||
v.LastFuncTime = cur_time
|
||||
|
||||
if not v.Infinity then
|
||||
|
||||
v.Repetitions = v.Repetitions - 1
|
||||
|
||||
if v.Repetitions <= 0 then
|
||||
|
||||
timer.Timers[k] = nil
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local simple_timers = timer.SimpleTimers
|
||||
|
||||
for i = #simple_timers, 1, -1 do
|
||||
|
||||
local t = simple_timers[i]
|
||||
|
||||
if t.EndTime <= cur_time then
|
||||
|
||||
t.Func()
|
||||
|
||||
table_remove(simple_timers, i)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
Events.OnTickEvenPaused.Add(timerUpdate)
|
||||
|
||||
function timer:Remove(name)
|
||||
|
||||
local t = self.Timers[name]
|
||||
|
||||
if not t then return false end
|
||||
|
||||
self.Timers[name] = nil
|
||||
|
||||
return true
|
||||
|
||||
end
|
||||
|
||||
function timer:Exists(name)
|
||||
|
||||
return self.Timers[name] and true or false
|
||||
|
||||
end
|
||||
|
||||
function timer:Start(name)
|
||||
|
||||
local t = self.Timers[name]
|
||||
|
||||
if not t then return false end
|
||||
|
||||
t.Repetitions = t.StartRepetitions
|
||||
t.LastFuncTime = os_time()
|
||||
t.Paused = false
|
||||
t.PausedTime = nil
|
||||
|
||||
return true
|
||||
|
||||
end
|
||||
|
||||
function timer:Pause(name)
|
||||
|
||||
local t = self.Timers[name]
|
||||
|
||||
if not t then return false end
|
||||
|
||||
if t.Paused then return false end
|
||||
|
||||
t.Paused = true
|
||||
t.PausedTime = os_time()
|
||||
|
||||
return true
|
||||
|
||||
end
|
||||
|
||||
function timer:UnPause(name)
|
||||
|
||||
local t = self.Timers[name]
|
||||
|
||||
if not t then return false end
|
||||
|
||||
if not t.Paused then return false end
|
||||
|
||||
t.Paused = false
|
||||
|
||||
return true
|
||||
|
||||
end
|
||||
timer.Resume = timer.UnPause
|
||||
|
||||
function timer:Toggle(name)
|
||||
|
||||
local t = self.Timers[name]
|
||||
|
||||
if not t then return false end
|
||||
|
||||
t.Paused = not t.Paused
|
||||
|
||||
return true
|
||||
|
||||
end
|
||||
|
||||
function timer:TimeLeft(name)
|
||||
|
||||
local t = self.Timers[name]
|
||||
|
||||
if not t then return end
|
||||
|
||||
if t.Paused then
|
||||
|
||||
return (t.Repetitions - 1) * t.Delay + (t.LastFuncTime + t.Delay - t.PausedTime)
|
||||
|
||||
else
|
||||
|
||||
return (t.Repetitions - 1) * t.Delay + (t.LastFuncTime + t.Delay - os_time())
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function timer:NextTimeLeft(name)
|
||||
|
||||
local t = self.Timers[name]
|
||||
|
||||
if not t then return end
|
||||
|
||||
if t.Paused then
|
||||
|
||||
return t.LastFuncTime + t.Delay - t.PausedTime
|
||||
|
||||
else
|
||||
|
||||
return t.LastFuncTime + t.Delay - os_time()
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function timer:RepsLeft(name)
|
||||
|
||||
local t = self.Timers[name]
|
||||
|
||||
return t and t.Repetitions
|
||||
|
||||
end
|
||||
32
42.6/media/lua/server/TOC/DebugCommands.lua
Normal file
32
42.6/media/lua/server/TOC/DebugCommands.lua
Normal file
@@ -0,0 +1,32 @@
|
||||
local CommandsData = require("TOC/CommandsData")
|
||||
local ServerDataHandler = require("TOC/ServerDataHandler")
|
||||
----------------------------
|
||||
|
||||
local DebugCommands = {}
|
||||
|
||||
---comment
|
||||
---@param playerObj IsoPlayer
|
||||
---@param args table
|
||||
function DebugCommands.PrintAllTocData(playerObj, args)
|
||||
TOC_DEBUG.printTable(ServerDataHandler.modData)
|
||||
end
|
||||
|
||||
---Print ALL TOC data
|
||||
---@param playerObj IsoPlayer
|
||||
---@param args printTocDataParams
|
||||
function DebugCommands.PrintTocData(playerObj, args)
|
||||
local key = CommandsData.GetKey(args.username)
|
||||
local tocData = ServerDataHandler.GetTable(key)
|
||||
TOC_DEBUG.printTable(tocData)
|
||||
end
|
||||
|
||||
--------------------
|
||||
|
||||
local function OnClientDebugCommand(module, command, playerObj, args)
|
||||
if module == CommandsData.modules.TOC_DEBUG and DebugCommands[command] then
|
||||
DebugCommands[command](playerObj, args)
|
||||
end
|
||||
end
|
||||
|
||||
Events.OnClientCommand.Add(OnClientDebugCommand)
|
||||
|
||||
31
42.6/media/lua/server/TOC/Distributions.lua
Normal file
31
42.6/media/lua/server/TOC/Distributions.lua
Normal file
@@ -0,0 +1,31 @@
|
||||
require('Items/Distributions')
|
||||
require('Items/SuburbsDistributions')
|
||||
|
||||
-- Insert Prosts and various items in the Medical Clinic loot table
|
||||
|
||||
local prosthesisLoot = {
|
||||
[1] = {
|
||||
name = "TOC.Prost_HookArm_L",
|
||||
chance = 3
|
||||
},
|
||||
|
||||
[2] = {
|
||||
name = "TOC.Prost_NormalArm_L",
|
||||
chance = 2
|
||||
},
|
||||
|
||||
[3] = {
|
||||
name = "TOC.Surg_Arm_Tourniquet_L",
|
||||
chance = 20
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for i=1, #prosthesisLoot do
|
||||
local tab = prosthesisLoot[i]
|
||||
table.insert(ProceduralDistributions.list.MedicalClinicTools.items, tab.name)
|
||||
table.insert(ProceduralDistributions.list.MedicalClinicTools.items, tab.chance)
|
||||
end
|
||||
|
||||
|
||||
|
||||
15
42.6/media/lua/server/TOC/LimitActionsController.lua
Normal file
15
42.6/media/lua/server/TOC/LimitActionsController.lua
Normal file
@@ -0,0 +1,15 @@
|
||||
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
|
||||
------------------------------
|
||||
|
||||
local og_ISObjectClickHandler_doClickSpecificObject = ISObjectClickHandler.doClickSpecificObject
|
||||
|
||||
---@param object IsoObject
|
||||
---@param playerNum any
|
||||
---@param playerObj IsoPlayer
|
||||
function ISObjectClickHandler.doClickSpecificObject(object, playerNum, playerObj)
|
||||
if CachedDataHandler.GetBothHandsFeasibility() then
|
||||
og_ISObjectClickHandler_doClickSpecificObject(object, playerNum, playerObj)
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
62
42.6/media/lua/server/TOC/ServerDataHandler.lua
Normal file
62
42.6/media/lua/server/TOC/ServerDataHandler.lua
Normal file
@@ -0,0 +1,62 @@
|
||||
if isClient() then return end -- The event makes this necessary to prevent clients from running this file
|
||||
local StaticData = require("TOC/StaticData")
|
||||
local CommandsData = require("TOC/CommandsData")
|
||||
------------------------
|
||||
|
||||
local ServerDataHandler = {}
|
||||
ServerDataHandler.modData = {}
|
||||
|
||||
---Get the server mod data table containing that player TOC data
|
||||
---@param key string
|
||||
---@return tocModDataType
|
||||
function ServerDataHandler.GetTable(key)
|
||||
return ServerDataHandler.modData[key]
|
||||
end
|
||||
|
||||
---Add table to the ModData and a local table
|
||||
---@param key string
|
||||
---@param table tocModDataType
|
||||
function ServerDataHandler.AddTable(key, table)
|
||||
-- Check if key is valid
|
||||
if not luautils.stringStarts(key, StaticData.MOD_NAME .. "_") then return end
|
||||
|
||||
TOC_DEBUG.print("Received TOC ModData: " .. tostring(key))
|
||||
--TOC_DEBUG.printTable(table)
|
||||
|
||||
-- Set that the data has been modified and it's updated on the server
|
||||
table.isUpdateFromServer = true -- FIX this is useless as of now
|
||||
|
||||
ModData.add(key, table) -- Add it to the server mod data
|
||||
ServerDataHandler.modData[key] = table
|
||||
|
||||
|
||||
-- Check integrity of table. if it doesn't contains toc data, it means that we received a reset
|
||||
if table.limbs == nil then return end
|
||||
|
||||
-- Since this could be triggered by a different client than the one referenced in the key, we're gonna
|
||||
-- apply the changes back to the key client again to be sure that everything is in sync
|
||||
local username = CommandsData.GetUsername(key)
|
||||
TOC_DEBUG.print("Reapplying to " .. username)
|
||||
|
||||
-- Since getPlayerFromUsername doesn't work in mp, we're gonna do this workaround. ew
|
||||
local onlinePlayers = getOnlinePlayers()
|
||||
local selectedPlayer
|
||||
for i=0, onlinePlayers:size() - 1 do
|
||||
|
||||
---@type IsoPlayer
|
||||
local player = onlinePlayers:get(i)
|
||||
if player:getUsername() == username then
|
||||
selectedPlayer = player
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
TOC_DEBUG.print("Player username from IsoPlayer: " .. selectedPlayer:getUsername())
|
||||
sendServerCommand(selectedPlayer, CommandsData.modules.TOC_RELAY, CommandsData.client.Relay.ReceiveApplyFromServer, {})
|
||||
|
||||
end
|
||||
|
||||
Events.OnReceiveGlobalModData.Add(ServerDataHandler.AddTable)
|
||||
|
||||
|
||||
return ServerDataHandler
|
||||
67
42.6/media/lua/server/TOC/ServerRelayCommands.lua
Normal file
67
42.6/media/lua/server/TOC/ServerRelayCommands.lua
Normal file
@@ -0,0 +1,67 @@
|
||||
require ("TOC/Debug")
|
||||
local CommandsData = require("TOC/CommandsData")
|
||||
--------------------------------------------
|
||||
|
||||
local ServerRelayCommands = {}
|
||||
|
||||
-- TODO We can easily make this a lot more simple without having functions
|
||||
|
||||
---Relay DamageDuringAmputation to another client
|
||||
---@param args relayDamageDuringAmputationParams
|
||||
function ServerRelayCommands.RelayDamageDuringAmputation(_, args)
|
||||
local patientPl = getPlayerByOnlineID(args.patientNum)
|
||||
|
||||
---@type receiveDamageDuringAmputationParams
|
||||
local params = {limbName = args.limbName}
|
||||
sendServerCommand(patientPl, CommandsData.modules.TOC_RELAY, CommandsData.client.Relay.ReceiveDamageDuringAmputation, params)
|
||||
end
|
||||
|
||||
---Relay ExecuteAmputationAction to another client
|
||||
---@param surgeonPl IsoPlayer
|
||||
---@param args relayExecuteAmputationActionParams
|
||||
function ServerRelayCommands.RelayExecuteAmputationAction(surgeonPl, args)
|
||||
local patientPl = getPlayerByOnlineID(args.patientNum)
|
||||
local surgeonNum = surgeonPl:getOnlineID()
|
||||
|
||||
---@type receiveDamageDuringAmputationParams
|
||||
local params = {surgeonNum = surgeonNum, limbName = args.limbName, damagePlayer = true}
|
||||
sendServerCommand(patientPl, CommandsData.modules.TOC_RELAY, CommandsData.client.Relay.ReceiveExecuteAmputationAction, params)
|
||||
end
|
||||
|
||||
--* ADMIN ONLY *--
|
||||
---Relay a local init from another client
|
||||
---@param adminObj IsoPlayer
|
||||
---@param args relayExecuteInitializationParams
|
||||
function ServerRelayCommands.RelayExecuteInitialization(adminObj, args)
|
||||
local patientPl = getPlayerByOnlineID(args.patientNum)
|
||||
sendServerCommand(patientPl, CommandsData.modules.TOC_RELAY, CommandsData.client.Relay.ReceiveExecuteInitialization, {})
|
||||
|
||||
end
|
||||
|
||||
---Relay a forced amputation to another client.
|
||||
---@param adminObj IsoPlayer
|
||||
---@param args relayForcedAmputationParams
|
||||
function ServerRelayCommands.RelayForcedAmputation(adminObj, args)
|
||||
local patientPl = getPlayerByOnlineID(args.patientNum)
|
||||
local adminNum = adminObj:getOnlineID()
|
||||
|
||||
---@type receiveDamageDuringAmputationParams
|
||||
local ampParams = {surgeonNum = adminNum, limbName = args.limbName, damagePlayer = false} -- the only difference between relayExecuteAmputationAction and this is the damage
|
||||
sendServerCommand(patientPl, CommandsData.modules.TOC_RELAY, CommandsData.client.Relay.ReceiveExecuteAmputationAction, ampParams)
|
||||
|
||||
-- Automatic cicatrization
|
||||
sendServerCommand(patientPl, CommandsData.modules.TOC_RELAY, CommandsData.client.Relay.ReceiveForcedCicatrization, {limbName = args.limbName})
|
||||
end
|
||||
|
||||
|
||||
|
||||
-------------------------
|
||||
|
||||
local function OnClientRelayCommand(module, command, playerObj, args)
|
||||
if module == CommandsData.modules.TOC_RELAY and ServerRelayCommands[command] then
|
||||
TOC_DEBUG.print("Received Client Relay command - " .. tostring(command))
|
||||
ServerRelayCommands[command](playerObj, args)
|
||||
end
|
||||
end
|
||||
|
||||
Events.OnClientCommand.Add(OnClientRelayCommand)
|
||||
58
42.6/media/lua/shared/TOC/BodyLocations.lua
Normal file
58
42.6/media/lua/shared/TOC/BodyLocations.lua
Normal file
@@ -0,0 +1,58 @@
|
||||
require("TOC/Debug")
|
||||
require("NPCs/BodyLocations")
|
||||
|
||||
local BodyLocationsAPI = {}
|
||||
local function customGetVal(obj, int) return getClassFieldVal(obj, getClassField(obj, int)) end
|
||||
local group = BodyLocations.getGroup("Human")
|
||||
|
||||
---@type ArrayList
|
||||
local list = customGetVal(group, 1)
|
||||
|
||||
---@param toRelocateOrCreate string
|
||||
---@param locationElement string
|
||||
---@param afterBoolean boolean
|
||||
---@return BodyLocation
|
||||
function BodyLocationsAPI.MoveOrCreateBeforeOrAfter(toRelocateOrCreate, locationElement, afterBoolean)
|
||||
-- Check type of arg 2 == string - if not error out.
|
||||
if type(locationElement) ~= "string" then error("Argument 2 is not of type string. Please re-check!", 2) end
|
||||
local itemToMoveTo = group:getLocation(locationElement) -- get location to move to
|
||||
if itemToMoveTo ~= nil then
|
||||
-- Check type of arg 1 == string - if not, error out.
|
||||
if type(toRelocateOrCreate) ~= "string" then error("Argument 1 is not of type string. Please re-check!", 2) end
|
||||
local curItem = group:getOrCreateLocation(toRelocateOrCreate) -- get current item - or create
|
||||
list:remove(curItem) -- remove from the list
|
||||
local index = group:indexOf(locationElement) -- get current index after removal of the location to move to
|
||||
if afterBoolean then index = index + 1 end -- if we want it after it, we increase the index to move to by one
|
||||
list:add(index, curItem) -- we add the item again
|
||||
|
||||
|
||||
return curItem
|
||||
else -- we did not find the location to move to, so we throw an error.
|
||||
error("Could not find the BodyLocation [".. tostring(locationElement) .."] - please check the passed arguments!", 2)
|
||||
end
|
||||
end
|
||||
|
||||
function TestBodyLocations()
|
||||
local group = BodyLocations.getGroup("Human")
|
||||
local x = group:getAllLocations()
|
||||
|
||||
for i=0, x:size() -1 do
|
||||
|
||||
---@type BodyLocation
|
||||
local bl = x:get(i)
|
||||
|
||||
print(bl:getId())
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- MultiItem causes a ton of issues... fucking hell
|
||||
|
||||
BodyLocationsAPI.MoveOrCreateBeforeOrAfter("TOC_Arm", "FullTop", true)
|
||||
group:setMultiItem("TOC_Arm", true)
|
||||
|
||||
BodyLocationsAPI.MoveOrCreateBeforeOrAfter("TOC_ArmProst", "TOC_Arm", true)
|
||||
group:setMultiItem("TOC_ArmProst", true)
|
||||
|
||||
BodyLocationsAPI.MoveOrCreateBeforeOrAfter("TOC_ArmAccessory", "TOC_ArmProst", true)
|
||||
group:setMultiItem("TOC_ArmAccessory", true)
|
||||
57
42.6/media/lua/shared/TOC/CommandsData.lua
Normal file
57
42.6/media/lua/shared/TOC/CommandsData.lua
Normal file
@@ -0,0 +1,57 @@
|
||||
local StaticData = require("TOC/StaticData")
|
||||
------------------------
|
||||
|
||||
local CommandsData = {}
|
||||
|
||||
CommandsData.modules = {
|
||||
TOC_DEBUG = "TOC_DEBUG",
|
||||
TOC_RELAY = "TOC_RELAY"
|
||||
}
|
||||
|
||||
CommandsData.client = {
|
||||
Relay = {
|
||||
ReceiveDamageDuringAmputation = "ReceiveDamageDuringAmputation", ---@alias receiveDamageDuringAmputationParams { limbName : string}
|
||||
ReceiveExecuteAmputationAction = "ReceiveExecuteAmputationAction", ---@alias receiveExecuteAmputationActionParams {surgeonNum : number, limbName : string, damagePlayer : boolean}
|
||||
|
||||
--* APPLY *--
|
||||
ReceiveApplyFromServer = "ReceiveApplyFromServer",
|
||||
|
||||
--* ADMIN ONLY --*
|
||||
ReceiveExecuteInitialization = "ReceiveExecuteInitialization",
|
||||
ReceiveForcedCicatrization = "ReceiveForcedCicatrization" ---@alias receiveForcedCicatrizationParams {limbName : string}
|
||||
}
|
||||
}
|
||||
|
||||
CommandsData.server = {
|
||||
Debug = {
|
||||
PrintTocData = "PrintTocData", ---@alias printTocDataParams {username : string}
|
||||
PrintAllTocData = "PrintAllTocData"
|
||||
},
|
||||
|
||||
Relay = {
|
||||
RelayDamageDuringAmputation = "RelayDamageDuringAmputation", ---@alias relayDamageDuringAmputationParams {patientNum : number, limbName : string}
|
||||
RelayExecuteAmputationAction = "RelayExecuteAmputationAction", ---@alias relayExecuteAmputationActionParams {patientNum : number, limbName : string}
|
||||
|
||||
--* ADMIN ONLY *--
|
||||
RelayExecuteInitialization = "RelayExecuteInitialization", ---@alias relayExecuteInitializationParams {patientNum : number}
|
||||
RelayForcedAmputation = "RelayForcedAmputation" ---@alias relayForcedAmputationParams {patientNum : number, limbName : string}
|
||||
}
|
||||
}
|
||||
|
||||
---Get the correct key for that particular player to be used in the global mod data table
|
||||
---@param username string
|
||||
---@return string
|
||||
function CommandsData.GetKey(username)
|
||||
return StaticData.MOD_NAME .. "_" .. username
|
||||
end
|
||||
|
||||
function CommandsData.GetUsername(key)
|
||||
return string.sub(key, #StaticData.MOD_NAME + 2, #key) -- Not sure why +2... Something with kahlua, it should be +1
|
||||
end
|
||||
|
||||
function CommandsData.GetZombieKey()
|
||||
return StaticData.MOD_NAME .. "_ZOMBIES"
|
||||
end
|
||||
|
||||
|
||||
return CommandsData
|
||||
110
42.6/media/lua/shared/TOC/Debug.lua
Normal file
110
42.6/media/lua/shared/TOC/Debug.lua
Normal file
@@ -0,0 +1,110 @@
|
||||
TOC_DEBUG = {}
|
||||
TOC_DEBUG.disablePaneMod = false
|
||||
TOC_DEBUG.enableHealthPanelDebug = false
|
||||
|
||||
function TOC_DEBUG.TogglePaneMod()
|
||||
TOC_DEBUG.disablePaneMod = not TOC_DEBUG.disablePaneMod
|
||||
end
|
||||
|
||||
function TOC_DEBUG.ToggleHealthPanelDebug()
|
||||
TOC_DEBUG.enableHealthPanelDebug = not TOC_DEBUG.enableHealthPanelDebug
|
||||
end
|
||||
|
||||
---Print debug
|
||||
---@param string string
|
||||
function TOC_DEBUG.print(string)
|
||||
--if isDebugEnabled() then
|
||||
local runningFile = TOC_DEBUG.getRunningFile()
|
||||
print("[TOC]" .. "[" .. runningFile .. "] " .. tostring(string))
|
||||
--end
|
||||
end
|
||||
|
||||
---Horrendous but I don't really care about performance for this
|
||||
---@return string
|
||||
function TOC_DEBUG.getRunningFile()
|
||||
local coroutine = getCurrentCoroutine()
|
||||
|
||||
local o = getCoroutineObjStack(coroutine, 0)
|
||||
if o then
|
||||
local s = KahluaUtil.rawTostring2(o)
|
||||
local match = string.match(s, "file: (%w+)%.lua")
|
||||
if match then return match end
|
||||
|
||||
end
|
||||
|
||||
return ""
|
||||
|
||||
end
|
||||
|
||||
function TOC_DEBUG.printTable(table, indent)
|
||||
if not table then return end
|
||||
indent = indent or ""
|
||||
|
||||
for key, value in pairs(table) do
|
||||
if type(value) == "table" then
|
||||
print(indent .. key .. " (table):")
|
||||
TOC_DEBUG.printTable(value, indent .. " ")
|
||||
else
|
||||
print(indent .. key .. ":", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---------------------------------
|
||||
--* Random debug commands *--
|
||||
|
||||
function TOC_DEBUG.TestBodyDamage(id)
|
||||
local StaticData = require("TOC/StaticData")
|
||||
|
||||
local pl = getPlayerByOnlineID(id)
|
||||
local bd = pl:getBodyDamage()
|
||||
|
||||
TOC_DEBUG.print(tostring(bd))
|
||||
|
||||
if bd then
|
||||
TOC_DEBUG.print("bd for " .. pl:getUsername() .. " exists")
|
||||
local bptEnum = StaticData.LIMBS_TO_BODYLOCS_IND_BPT["Hand_L"]
|
||||
local bodyPart = bd:getBodyPart(bptEnum)
|
||||
|
||||
bodyPart:setBleeding(true)
|
||||
bodyPart:setCut(true)
|
||||
TOC_DEBUG.print(tostring(bodyPart))
|
||||
end
|
||||
end
|
||||
|
||||
function TOC_DEBUG.TestBloodDrop()
|
||||
local pl = getPlayer()
|
||||
|
||||
--IsoZombieGiblets.GibletType.A
|
||||
--local giblets = IsoZombieGiblets.new(getCell())
|
||||
|
||||
local sq = pl:getSquare()
|
||||
local t = IsoZombieGiblets.class.GibletType
|
||||
|
||||
print(t)
|
||||
--IsoBall.new(getCell(), pl:getX(), pl:)
|
||||
addBloodSplat(sq, 100)
|
||||
|
||||
|
||||
--pl:getChunk():addBloodSplat(pl:getX(), pl:getY(), pl:getZ(), 100)
|
||||
--IsoZombieGiblets.new(x, getCell(), pl:getX(), pl:getY(), pl:getZ(), 100, 1)
|
||||
|
||||
end
|
||||
---------------------------------
|
||||
--* Debug server commands *--
|
||||
|
||||
local CommandsData = require("TOC/CommandsData")
|
||||
|
||||
function TOC_DEBUG.printPlayerServerModData(username)
|
||||
sendClientCommand(CommandsData.modules.TOC_DEBUG, CommandsData.server.Debug.PrintTocData, {username = username})
|
||||
end
|
||||
|
||||
function TOC_DEBUG.printAllServerModData()
|
||||
sendClientCommand(CommandsData.modules.TOC_DEBUG, CommandsData.server.Debug.PrintAllTocData, {})
|
||||
end
|
||||
|
||||
function TOC_DEBUG.testRelayDamage()
|
||||
---@type relayDamageDuringAmputationParams
|
||||
local params = {limbName = "Hand_R", patientNum = getPlayer():getOnlineID()}
|
||||
sendClientCommand(CommandsData.modules.TOC_RELAY, CommandsData.server.Relay.RelayDamageDuringAmputation, params)
|
||||
end
|
||||
293
42.6/media/lua/shared/TOC/StaticData.lua
Normal file
293
42.6/media/lua/shared/TOC/StaticData.lua
Normal file
@@ -0,0 +1,293 @@
|
||||
---@alias partDataType { isCut : boolean?, isInfected : boolean?, isOperated : boolean?, isCicatrized : boolean?, isCauterized : boolean?, isVisible : boolean?, woundDirtyness : number, cicatrizationTime : number }
|
||||
---@alias limbsTable {Hand_L : partDataType, ForeArm_L : partDataType, UpperArm_L : partDataType, Hand_R : partDataType, ForeArm_R : partDataType, UpperArm_R : partDataType }
|
||||
---@alias prosthesisData {isProstEquipped : boolean, prostFactor : number }
|
||||
---@alias prosthesesTable {Top_L : prosthesisData, Top_R : prosthesisData } -- TODO add Bottom_L and Bottom_R
|
||||
---@alias tocModDataType { limbs : limbsTable, prostheses : prosthesesTable, isIgnoredPartInfected : boolean, isAnyLimbCut : boolean, isUpdateFromServer : boolean, isInitializing : boolean}
|
||||
---------------------------
|
||||
|
||||
|
||||
-- _STR = Only strings, no index
|
||||
-- _IND_STR = indexed Strings
|
||||
-- _IND_BPT = Indexed BodyPartType
|
||||
|
||||
-- PART = Single part, could be hand, forearm, etc
|
||||
-- LIMB = Part + side
|
||||
-- BODYLOCS = Body Locations
|
||||
|
||||
|
||||
|
||||
local StaticData = {}
|
||||
|
||||
---Mod name, used to setup Global Mod Data and various stuff
|
||||
StaticData.MOD_NAME = "TOC"
|
||||
|
||||
-------------------------
|
||||
--* Base
|
||||
|
||||
|
||||
-- TODO Add references inside tables instead of making multiple tables
|
||||
|
||||
StaticData.SIDES_IND_STR = {
|
||||
R = "R",
|
||||
L = "L"
|
||||
}
|
||||
StaticData.SIDES_STR = {
|
||||
"R", "L"
|
||||
}
|
||||
StaticData.PARTS_IND_STR = {
|
||||
Hand = "Hand",
|
||||
ForeArm = "ForeArm",
|
||||
UpperArm = "UpperArm"
|
||||
}
|
||||
StaticData.PARTS_STR = {
|
||||
"Hand",
|
||||
"ForeArm",
|
||||
"UpperArm"
|
||||
}
|
||||
|
||||
|
||||
StaticData.MOD_BODYLOCS_BASE_IND_STR = {
|
||||
TOC_ArmProst = "TOC_ArmProst",
|
||||
TOC_LegProst = "TOC_LegProst",
|
||||
TOC_Arm = "TOC_Arm",
|
||||
}
|
||||
|
||||
-- No "MAX" here.
|
||||
StaticData.IGNORED_BODYLOCS_BPT = {
|
||||
BodyPartType.Foot_L, BodyPartType.Foot_R, BodyPartType.Groin, BodyPartType.Head,
|
||||
BodyPartType.LowerLeg_L, BodyPartType.LowerLeg_R, BodyPartType.Neck, BodyPartType.Torso_Lower,
|
||||
BodyPartType.Torso_Upper, BodyPartType.UpperLeg_L, BodyPartType.UpperLeg_R
|
||||
}
|
||||
|
||||
|
||||
-- Assembled BodyParts string
|
||||
StaticData.LIMBS_STR = {}
|
||||
StaticData.LIMBS_IND_STR = {}
|
||||
StaticData.LIMBS_DEPENDENCIES_IND_STR = {}
|
||||
StaticData.LIMBS_ADJACENT_IND_STR = {}
|
||||
StaticData.LIMBS_CICATRIZATION_TIME_IND_NUM = {}
|
||||
StaticData.LIMBS_BASE_DAMAGE_IND_NUM = {}
|
||||
StaticData.LIMBS_TIME_MULTIPLIER_IND_NUM = {}
|
||||
|
||||
|
||||
|
||||
StaticData.LIMBS_TO_BODYLOCS_IND_BPT = {} -- {limbName = bodyLoc}
|
||||
StaticData.BODYLOCS_TO_LIMBS_IND_STR = {} -- {bodyLoc = limbName}
|
||||
|
||||
-- FIXME You weren't considering surgeonFactor, which decreases that base time. Fuck mod 60
|
||||
-- CicatrizationBaseTime should be mod 60 since we're using EveryHours to update the cicatrizationTime
|
||||
|
||||
---@param assembledName string
|
||||
---@param side string
|
||||
local function AssembleHandData(assembledName, side)
|
||||
StaticData.LIMBS_BASE_DAMAGE_IND_NUM[assembledName] = 60
|
||||
StaticData.LIMBS_CICATRIZATION_TIME_IND_NUM[assembledName] = 120
|
||||
StaticData.LIMBS_TIME_MULTIPLIER_IND_NUM[assembledName] = 2
|
||||
StaticData.LIMBS_DEPENDENCIES_IND_STR[assembledName] = {}
|
||||
StaticData.LIMBS_ADJACENT_IND_STR[assembledName] = StaticData.PARTS_IND_STR.ForeArm .. "_" .. side
|
||||
end
|
||||
|
||||
---@param assembledName string
|
||||
---@param side string
|
||||
local function AssembleForearmData(assembledName, side)
|
||||
StaticData.LIMBS_BASE_DAMAGE_IND_NUM[assembledName] = 80
|
||||
StaticData.LIMBS_CICATRIZATION_TIME_IND_NUM[assembledName] = 144
|
||||
StaticData.LIMBS_TIME_MULTIPLIER_IND_NUM[assembledName] = 3
|
||||
StaticData.LIMBS_DEPENDENCIES_IND_STR[assembledName] = { StaticData.PARTS_IND_STR.Hand .. "_" .. side }
|
||||
StaticData.LIMBS_ADJACENT_IND_STR[assembledName] = StaticData.PARTS_IND_STR.UpperArm .. "_" .. side
|
||||
end
|
||||
|
||||
---@param assembledName string
|
||||
---@param side string
|
||||
local function AssembleUpperarmData(assembledName, side)
|
||||
StaticData.LIMBS_BASE_DAMAGE_IND_NUM[assembledName] = 100
|
||||
StaticData.LIMBS_CICATRIZATION_TIME_IND_NUM[assembledName] = 192
|
||||
StaticData.LIMBS_TIME_MULTIPLIER_IND_NUM[assembledName] = 4
|
||||
StaticData.LIMBS_DEPENDENCIES_IND_STR[assembledName] = { StaticData.PARTS_IND_STR.Hand .. "_" .. side,
|
||||
StaticData.PARTS_IND_STR.ForeArm .. "_" .. side }
|
||||
StaticData.LIMBS_ADJACENT_IND_STR[assembledName] = "Torso_Upper"
|
||||
end
|
||||
|
||||
for side, _ in pairs(StaticData.SIDES_IND_STR) do
|
||||
for part, _ in pairs(StaticData.PARTS_IND_STR) do
|
||||
local assembledName = part .. "_" .. side
|
||||
|
||||
-- Assembled strings
|
||||
table.insert(StaticData.LIMBS_STR, assembledName) -- We need a table like this to cycle through it easily
|
||||
StaticData.LIMBS_IND_STR[assembledName] = assembledName
|
||||
|
||||
|
||||
-- BodyParts stuff
|
||||
---@type BodyPartType
|
||||
local bptType = BodyPartType[assembledName]
|
||||
local bptString = BodyPartType.ToString(bptType)
|
||||
|
||||
|
||||
StaticData.LIMBS_TO_BODYLOCS_IND_BPT[assembledName] = bptType
|
||||
StaticData.BODYLOCS_TO_LIMBS_IND_STR[bptString] = assembledName
|
||||
|
||||
-- Dependencies and cicatrization time
|
||||
if part == StaticData.PARTS_IND_STR.Hand then
|
||||
AssembleHandData(assembledName, side)
|
||||
elseif part == StaticData.PARTS_IND_STR.ForeArm then
|
||||
AssembleForearmData(assembledName, side)
|
||||
elseif part == StaticData.PARTS_IND_STR.UpperArm then
|
||||
AssembleUpperarmData(assembledName, side)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-----------------
|
||||
--* Amputation Groups
|
||||
|
||||
StaticData.AMP_GROUPS_BASE_IND_STR = {
|
||||
Top = "Top",
|
||||
Bottom = "Bottom"
|
||||
}
|
||||
|
||||
StaticData.AMP_GROUPS_IND_STR = {}
|
||||
StaticData.AMP_GROUPS_STR = {}
|
||||
|
||||
for side, _ in pairs(StaticData.SIDES_IND_STR) do
|
||||
for group, _ in pairs(StaticData.AMP_GROUPS_BASE_IND_STR) do
|
||||
local sidedGroup = group .. "_" .. side
|
||||
StaticData.AMP_GROUPS_IND_STR[sidedGroup] = sidedGroup
|
||||
table.insert(StaticData.AMP_GROUPS_STR, sidedGroup)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- TODO We can do this in one pass if we do it before
|
||||
|
||||
StaticData.AMP_GROUP_TO_LIMBS_MATCH_IND_STR = {} -- This is probably unnecessary
|
||||
StaticData.LIMBS_TO_AMP_GROUPS_MATCH_IND_STR = {}
|
||||
|
||||
for side, _ in pairs(StaticData.SIDES_IND_STR) do
|
||||
for part, _ in pairs(StaticData.PARTS_IND_STR) do
|
||||
local limbName = part .. "_" .. side
|
||||
local group
|
||||
if part == StaticData.PARTS_IND_STR.Hand or part == StaticData.PARTS_IND_STR.ForeArm or part == StaticData.PARTS_IND_STR.UpperArm then
|
||||
group = StaticData.AMP_GROUPS_BASE_IND_STR.Top
|
||||
else
|
||||
group = StaticData.AMP_GROUPS_BASE_IND_STR.Bottom
|
||||
end
|
||||
|
||||
local sidedGroup = group .. "_" .. side
|
||||
if StaticData.AMP_GROUP_TO_LIMBS_MATCH_IND_STR[sidedGroup] == nil then
|
||||
StaticData.AMP_GROUP_TO_LIMBS_MATCH_IND_STR[sidedGroup] = {}
|
||||
end
|
||||
table.insert(StaticData.AMP_GROUP_TO_LIMBS_MATCH_IND_STR[sidedGroup], limbName)
|
||||
|
||||
StaticData.LIMBS_TO_AMP_GROUPS_MATCH_IND_STR[limbName] = sidedGroup
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
StaticData.TOURNIQUET_BODYLOCS_TO_GROUPS_IND_STR = {
|
||||
["HandsLeft"] = StaticData.AMP_GROUPS_IND_STR.Top_L,
|
||||
["HandsRight"] = StaticData.AMP_GROUPS_IND_STR.Top_R
|
||||
}
|
||||
|
||||
|
||||
StaticData.AFFECTED_BODYLOCS_TO_LIMBS_IND_STR = {}
|
||||
|
||||
local handsBodyLocs = {"Hands%s", "%s_MiddleFinger", "%s_RingFinger"}
|
||||
local foreArmBodyLocs = {"%sWrist"}
|
||||
|
||||
|
||||
|
||||
for side, _ in pairs(StaticData.SIDES_IND_STR) do
|
||||
for part, _ in pairs(StaticData.PARTS_IND_STR) do
|
||||
local limbName = part .. "_" .. side
|
||||
|
||||
local sideFull
|
||||
if side == 'R' then sideFull = "Right" else sideFull = "Left" end
|
||||
|
||||
if part == "Hand" then
|
||||
for i=1, #handsBodyLocs do
|
||||
local bl = string.format(handsBodyLocs[i], sideFull)
|
||||
StaticData.AFFECTED_BODYLOCS_TO_LIMBS_IND_STR[bl] = limbName
|
||||
end
|
||||
elseif part == "ForeArm" then
|
||||
-- -- UGLY very ugly
|
||||
-- for i=1, #handsBodyLocs do
|
||||
-- local bl = string.format(handsBodyLocs[i], sideFull)
|
||||
-- StaticData.AFFECTED_BODYLOCS_TO_LIMBS_IND_STR[bl] = limbName
|
||||
-- end
|
||||
for i=1, #foreArmBodyLocs do
|
||||
local bl = string.format(foreArmBodyLocs[i], sideFull)
|
||||
StaticData.AFFECTED_BODYLOCS_TO_LIMBS_IND_STR[bl] = limbName
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-----------------
|
||||
--* Traits
|
||||
|
||||
-- Link a trait to a specific body part
|
||||
StaticData.TRAITS_BP = {
|
||||
Amputee_Hand = "Hand_L",
|
||||
Amputee_ForeArm = "ForeArm_L",
|
||||
Amputee_UpperArm = "UpperArm_L"
|
||||
}
|
||||
|
||||
-----------------
|
||||
--* Visuals and clothing
|
||||
|
||||
--- Textures
|
||||
StaticData.HEALTH_PANEL_TEXTURES = {
|
||||
|
||||
Female = {
|
||||
Hand_L = getTexture("media/ui/Female/Hand_L.png"),
|
||||
ForeArm_L = getTexture("media/ui/Female/ForeArm_L.png"),
|
||||
UpperArm_L = getTexture("media/ui/Female/UpperArm_L.png"),
|
||||
|
||||
Hand_R = getTexture("media/ui/Female/Hand_R.png"),
|
||||
ForeArm_R = getTexture("media/ui/Female/ForeArm_R.png"),
|
||||
UpperArm_R = getTexture("media/ui/Female/UpperArm_R.png")
|
||||
},
|
||||
|
||||
Male = {
|
||||
Hand_L = getTexture("media/ui/Male/Hand_L.png"),
|
||||
ForeArm_L = getTexture("media/ui/Male/ForeArm_L.png"),
|
||||
UpperArm_L = getTexture("media/ui/Male/UpperArm_L.png"),
|
||||
|
||||
Hand_R = getTexture("media/ui/Male/Hand_R.png"),
|
||||
ForeArm_R = getTexture("media/ui/Male/ForeArm_R.png"),
|
||||
UpperArm_R = getTexture("media/ui/Male/UpperArm_R.png")
|
||||
},
|
||||
|
||||
ProstArm = {
|
||||
L = getTexture("media/ui/ProstArm_L.png"),
|
||||
R = getTexture("media/ui/ProstArm_R.png")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StaticData.AMPUTATION_CLOTHING_ITEM_BASE = "TOC.Amputation_"
|
||||
|
||||
|
||||
------------------
|
||||
--* Items check
|
||||
|
||||
local sawObj = instanceItem("Base.Saw")
|
||||
local gardenSawObj = instanceItem("Base.GardenSaw")
|
||||
|
||||
StaticData.SAWS_NAMES_IND_STR = {
|
||||
saw = sawObj:getName(),
|
||||
gardenSaw = gardenSawObj:getName()
|
||||
}
|
||||
|
||||
StaticData.SAWS_TYPES_IND_STR = {
|
||||
saw = sawObj:getType(),
|
||||
gardenSaw = gardenSawObj:getType()
|
||||
}
|
||||
|
||||
|
||||
return StaticData
|
||||
49
42.6/media/lua/shared/TOC/Traits.lua
Normal file
49
42.6/media/lua/shared/TOC/Traits.lua
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
---Setups the custom TOC traits
|
||||
|
||||
local TRAITS = {
|
||||
Amputee_Hand = "Amputee_Hand",
|
||||
Amputee_ForeArm = "Amputee_ForeArm",
|
||||
Amputee_UpperArm = "Amputee_UpperArm",
|
||||
--Insensitive = "Insensitive" -- TODO Disabled for now, until we reintroduce it
|
||||
}
|
||||
|
||||
|
||||
local function GetTraitText(trait)
|
||||
return getText("UI_trait_" .. trait)
|
||||
end
|
||||
|
||||
local function GetTraitDesc(trait)
|
||||
return getText("UI_trait_" .. trait .. "_desc")
|
||||
end
|
||||
|
||||
|
||||
local function SetupTraits()
|
||||
-- Perks.Left_Hand is defined in perks.txt
|
||||
|
||||
local traitsTable = {
|
||||
[1] = TraitFactory.addTrait(TRAITS.Amputee_Hand, GetTraitText(TRAITS.Amputee_Hand), -8, GetTraitDesc(TRAITS.Amputee_Hand), false, false),
|
||||
[2] = TraitFactory.addTrait(TRAITS.Amputee_ForeArm, GetTraitText(TRAITS.Amputee_ForeArm), -10, GetTraitDesc(TRAITS.Amputee_ForeArm), false, false),
|
||||
[3] = TraitFactory.addTrait(TRAITS.Amputee_UpperArm, GetTraitText(TRAITS.Amputee_UpperArm), -20, GetTraitDesc(TRAITS.Amputee_UpperArm), false, false)
|
||||
}
|
||||
|
||||
for i=1, #traitsTable do
|
||||
|
||||
---@type Trait
|
||||
local t = traitsTable[i]
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
t:addXPBoost(Perks.Left_Hand, 4)
|
||||
t:addXPBoost(Perks.Fitness, -1)
|
||||
t:addXPBoost(Perks.Strength, -1)
|
||||
end
|
||||
|
||||
--TraitFactory.addTrait(TRAITS.Insensitive, GetTraitText(TRAITS.Insensitive), 6, GetTraitDesc(TRAITS.Insensitive), false, false)
|
||||
|
||||
TraitFactory.setMutualExclusive(TRAITS.Amputee_Hand, TRAITS.Amputee_ForeArm)
|
||||
TraitFactory.setMutualExclusive(TRAITS.Amputee_Hand, TRAITS.Amputee_UpperArm)
|
||||
TraitFactory.setMutualExclusive(TRAITS.Amputee_ForeArm, TRAITS.Amputee_UpperArm)
|
||||
end
|
||||
|
||||
|
||||
|
||||
Events.OnGameBoot.Add(SetupTraits)
|
||||
Reference in New Issue
Block a user