381 lines
16 KiB
Lua
381 lines
16 KiB
Lua
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")
|
|
|
|
local OverridenMethodsArchive = require("TOC/OverridenMethodsArchive")
|
|
-----------------
|
|
---@class LimitActionsController
|
|
local LimitActionsController = {}
|
|
|
|
|
|
--* DISABLE WEARING CERTAIN ITEMS WHEN NO LIMB
|
|
|
|
function LimitActionsController.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
|
|
function LimitActionsController.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 LimitActionsController.CheckLimbFeasibility(limbToCheck) then return isEquippable else return false end
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--------------------------------------------
|
|
--* 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
|
|
|
|
---@diagnostic disable-next-line: duplicate-set-field
|
|
local og_ISWearClothing_isValid = ISWearClothing.isValid
|
|
function ISWearClothing:isValid()
|
|
return LimitActionsController.WrapClothingAction(self, og_ISWearClothing_isValid, self.item)
|
|
end
|
|
|
|
|
|
|
|
local og_ISClothingExtraAction_isValid = OverridenMethodsArchive.Save("ISClothingExtraAction_isValid", ISClothingExtraAction.isValid)
|
|
---@diagnostic disable-next-line: duplicate-set-field
|
|
function ISClothingExtraAction:isValid()
|
|
return LimitActionsController.WrapClothingAction(self, og_ISClothingExtraAction_isValid, InventoryItemFactory.CreateItem(self.extra))
|
|
end
|
|
|
|
return LimitActionsController |