Files
The-Only-Cure/media/lua/client/TOC/Controllers/LimitActionsController.lua
2024-10-12 18:48:52 +02:00

364 lines
15 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")
-----------------
--* 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)
-- The vanilla game doesn't count an item in the off hand as "equipped" for picking up glass. Let's fix that here
local brokenGlassOption = ogContext:getOptionFromName(getText("ContextMenu_RemoveBrokenGlass"))
if brokenGlassOption then
local playerObj = getSpecificPlayer(player)
if (CachedDataHandler.GetHandFeasibility(StaticData.SIDES_IND_STR.R) and playerObj:getPrimaryHandItem()) or
(CachedDataHandler.GetHandFeasibility(StaticData.SIDES_IND_STR.L) and playerObj:getSecondaryHandItem())
then
brokenGlassOption.notAvailable = false
brokenGlassOption.toolTip = nil -- This is active only when you can't do the action.
end
end
-- check if no hands, disable various interactions
if not CachedDataHandler.GetBothHandsFeasibility() then
TOC_DEBUG.print("NO hands :((")
for i = 1, #noHandsImpossibleActions do
local optionName = noHandsImpossibleActions[i]
local option = ogContext:getOptionFromName(optionName)
if option then
option.notAvailable = true
end
end
end
return ogContext
end
--* DISABLE WEARING CERTAIN ITEMS WHEN NO LIMB
local function CheckLimbFeasibility(limbName)
local dcInst = DataController.GetInstance()
local isFeasible = not dcInst:getIsCut(limbName) or dcInst:getIsProstEquipped(limbName)
--TOC_DEBUG.print("isFeasible="..tostring(isFeasible))
return isFeasible
end
---@param obj any
---@param wrappedFunc function
---@param item InventoryItem
---@return boolean
local function WrapClothingAction(obj, wrappedFunc, item)
local isEquippable = wrappedFunc(obj)
if not isEquippable then return isEquippable end
local itemBodyLoc = item:getBodyLocation()
local limbToCheck = StaticData.AFFECTED_BODYLOCS_TO_LIMBS_IND_STR[itemBodyLoc]
if CheckLimbFeasibility(limbToCheck) then return isEquippable else return false end
end
---@diagnostic disable-next-line: duplicate-set-field
local og_ISWearClothing_isValid = ISWearClothing.isValid
function ISWearClothing:isValid()
return WrapClothingAction(self, og_ISWearClothing_isValid, self.item)
end
local og_ISClothingExtraAction_isValid = ISClothingExtraAction.isValid
---@diagnostic disable-next-line: duplicate-set-field
function ISClothingExtraAction:isValid()
return WrapClothingAction(self, og_ISClothingExtraAction_isValid, InventoryItemFactory.CreateItem(self.extra))
end