local IsoObject = IsoObject
local ZombRandFloat = ZombRandFloat
Kinkocalypse = Kinkocalypse or {}
Kinkocalypse.MoveUpdateCounter = 0

Kinkocalypse.OrigTimedActionCreate = ISBaseTimedAction.create
Kinkocalypse.OrigTimedActionBegin = ISBaseTimedAction.begin
Kinkocalypse.OrigTimedActionIsValid = ISBaseTimedAction.isValid
Kinkocalypse.OrigTimedActionPerform = ISBaseTimedAction.perform
Kinkocalypse.OrigTimedActionUpdate = ISBaseTimedAction.update

local cumSprites = {
	"cum_floor_large_01",
	"cum_floor_large_02",
	"cum_floor_large_03"
}

Kinkocalypse.HandsAffectedTimedActions = 
{
	"ISBuildAction", "ISCureFliesAction", "ISAddItemInRecipe", "ISBarricadeAction", "ISCraftAction", "ISCutHair", "ISBuryCorpse",
	"ISDismantleAction", "ISDyeHair", "ISFixAction", "ISFixGenerator", "ISChopTreeAction", "ISConsolidateDrainable", 
	"ISSmashWindow", "ISPlaceTrap", "ISPlumbItem", "ISRackFirearm", "ISEquipHeavyItem", "ISFillGrave", "ISGrabCorpseAction",
	"ISDestroyStuffAction", "ISClimbWall", "ISOpenCloseWindow", "ISRemoveBush", "ISTrimBeard", "ISUnbarricadeAction", "ISFitnessAction",
	"ISClimbThroughWindow", "ISClimbOverFence", "ISClimbSheetRopeAction", "ISAddCompost", "ISAddSheetAction", "ISAddSheetRope",
	"ISApplyBandage", "ISAttachItemHotbar", "ISBBQAddFuel", "ISBBQExtinguish", "ISBBQInsertPropaneTank", "ISBBQRemovePropaneTank",
	"ISCleanBandage", "ISCleanBlood", "ISCleanBurn", "ISClearAshes", "ISConnectCarBatteryToChargerAction", "ISConsolidateDrainableAll", 
	"ISDisinfect", "ISDrinkFromBottle", "ISDryMyself", "ISEatFoodAction", "ISPlaceCarBatteryChargerAction", "ISEmptyRainBarrelAction",
	"ISEquipWeaponAction", "ISFireplaceExtinguish", "ISGetCompost", "ISReadABook", "ISRemoveBrokenGlass", "ISRemoveGrass", "ISRemovePatch",
	"ISRepairClothing", "ISStitch", "ISSplint", "ISTakeGenerator", "ISTakeTrap", "ISUpgradeWeapon", "ISWashClothing", "ISWashYourself", "ISInventoryTransferAction"
}


Kinkocalypse.CanPerformAction = function(self)
	local canPerform = true
	local equipedTags = Kinkocalypse.GetPlayerEquipedRestrictionsTags()
	if equipedTags["ChainedHandsBack"] ~= nil then
		for i = 1, #Kinkocalypse.HandsAffectedTimedActions do
			if self.Type == Kinkocalypse.HandsAffectedTimedActions[i] then
				canPerform = false
				if not canPerform then Kinkocalypse.Say(getText("UI_Kincalypse_HandsCuffedCantDo")) end
			end
		end
	end
	return canPerform
end

ISBaseTimedAction.isValid = function(self)
	if Kinkocalypse.CanPerformAction(self) == false then
		ISBaseTimedAction.stop(self)
		return false
	end
	return Kinkocalypse.OrigTimedActionIsValid(self)
end

ISBaseTimedAction.begin = function(self)
	if Kinkocalypse.CanPerformAction(self) == false then
		return ISBaseTimedAction.stop(self)
	end
	Kinkocalypse.OrigTimedActionBegin(self)	
end

ISBaseTimedAction.perform = function(self)
	if Kinkocalypse.CanPerformAction(self) == false then
		return ISBaseTimedAction.stop(self)
	end
	Kinkocalypse.OrigTimedActionPerform(self)	
end

 ISBaseTimedAction.update = function(self)
	if Kinkocalypse.CanPerformAction(self) == false then
		return ISBaseTimedAction.stop(self)
	end
	Kinkocalypse.OrigTimedActionUpdate(self)
end


ISBaseTimedAction.create = function(self)
-- this section for slowing down timed actions was causing a strange error with ZomboWin: 
-- animation would not play and character would say "I  needed that!" when hands were chained
-- unsure how to fix - commented out for now

--	local equipedTags = Kinkocalypse.GetPlayerEquipedRestrictionsTags()
--	local timeMult = 1

--	if equipedTags["ChainedHandsBack"] ~= nil then timeMult = timeMult + ZombRand(3.0, 5.0) end
--	if equipedTags["SteelWristCuffs"] ~= nil then timeMult = timeMult + ZombRand(0.1, 0.2)	end
--	if equipedTags["ChainedHands"] ~= nil then timeMult = timeMult + ZombRand(0.5, 1.0) end	
--	if equipedTags["SteelChastityBra"] ~= nil then timeMult = timeMult + ZombRand(0.1, 0.3) end
--	if equipedTags["SteelBlindFold"] ~= nil then timeMult = timeMult + ZombRand(2.0, 3.0) end
--	if equipedTags["SteelRestrictionGloves"] ~= nil then timeMult = timeMult + ZombRand(0.1, 0.3) end	

--	local newTime = self.maxTime * timeMult
--	self.maxTime = newTime	
	return Kinkocalypse.OrigTimedActionCreate(self);
end


Kinkocalypse.PlayerHasVagina = function(player)
	-- for genital swap option so you can play as trans character
	if player:isFemale() and SandboxVars.Kinkocalypse.SwapGenitals == false then return true;
	elseif not player:isFemale() and SandboxVars.Kinkocalypse.SwapGenitals == true then return true;
	else return false end
end


Kinkocalypse.AddCumToSprite = function(player)
	-- add cum to the ground around the player
	if SandboxVars.Kinkocalypse.AllowWorldCum == true then
		local offsetx = ZombRandFloat(3, 4)
		local offsety = ZombRandFloat(2, 3)
		local square = getCell():getGridSquare(player:getX() + offsetx,player:getY() + offsety, player:getZ())
		local cum = IsoObject.new(square, cumSprites[ZombRand(1, #cumSprites + 1)])
		cum:setCustomColor(1, 1, 1, 1)
		square:AddTileObject(cum)
	else
		return end
end


function Kinkocalypse.FormatMinutes(minutes)
    local days = math.floor(minutes / 1440)  -- 1 день = 1440 минут
    local hours = math.floor((minutes % 1440) / 60)  -- Остаток от дней в часах
    local mins = minutes % 60  -- Остаток в минутах

    local result = ""
    if days > 0 then result = result .. days .. "d " end
    if hours > 0 then result = result .. hours .. "h " end
    if mins > 0 or (days == 0 and hours == 0) then result = result .. mins .. "m"  end

    return result:match("^%s*(.-)%s*$")  -- Убирает лишние пробелы в начале и конце
end


Kinkocalypse.MasturbateAnus = function()
	local player = getPlayer()
	local equipedTags = Kinkocalypse.GetPlayerEquipedRestrictionsTags()
	local actionData =
	{
		id = "Masturbate_Anus",
		animation = "FingerAss",
		duration = ZombRand(300, 600),
		canStop = true,
		canMove = true,
		arousalReduceMin = 10,
		arousalReduceMax = 20,
		requiredArousal = 20,
		arousalOnFail = ZombRand(5, 10)
	}
	Kinkocalypse.PlayAction(player, equipedTags, actionData)
end


Kinkocalypse.MasturbateGenetails = function()
	local player = getPlayer()
	local equipedTags = Kinkocalypse.GetPlayerEquipedRestrictionsTags()
	local actionData = 
	{
		id = "Masturbate_Genetails",
		animation = "StrokeCock",
		duration = ZombRand(240, 420),
		canStop = true,
		canMove = true,
		arousalReduceMin = 35,
		arousalReduceMax = 60,
		requiredArousal = 20,
		arousalOnFail = ZombRand(10, 15)
	}
	if Kinkocalypse.PlayerHasVagina(player) == true or equipedTags("SteelChastityBelt") ~= nil then
		if ZombRand(100) > 50 then actionData.animation = "FingerPussy"
		else actionData.animation = "FingerBoth" end
	end
	Kinkocalypse.PlayAction(player, equipedTags, actionData)
end


Kinkocalypse.MasturbateTits = function()
	local player = getPlayer()
	local equipedTags = Kinkocalypse.GetPlayerEquipedRestrictionsTags()
	local actionData = 
	{
		id = "Masturbate_Tits",
		animation = "WearClothing",
		duration = ZombRand(300, 600),
		canStop = true,
		canMove = true,
		arousalReduceMin = 15,
		arousalReduceMax = 30,
		requiredArousal = 35,
		arousalOnFail = ZombRand(5, 10)
	}
	if ISAnimationAction then actionData.animation = "Yonchi_titstease" end
	Kinkocalypse.PlayAction(player, equipedTags, actionData)
end


Kinkocalypse.MasturbateForce = function()
	local player = getPlayer()
	local equipedTags = Kinkocalypse.GetPlayerEquipedRestrictionsTags()
	local actionData = 
	{
		id = "",
		animation = "",
		duration = ZombRand(240, 600),
		canStop = false,
		canMove = false,
		arousalReduceMin = 10,
		arousalReduceMax = 25,
		requiredArousal = 0,
		arousalOnFail = nil
	}
	local diceRoll = ZombRand(100)
	
	if diceRoll > 70 then
		actionData.id = "Masturbate_Anus"
		actionData.animation = "FingerAss"
	elseif diceRoll > 50 and player:isFemale() then
		actionData.id = "Masturbate_Tits"
		actionData.animation = "WearClothing"
		if ISAnimationAction then actionData.animation = "Yonchi_titstease" end
	else
		actionData.id = "Masturbate_Genetails"
			if ZombRand(100) > 50 then actionData.animation = "FingerPussy"
			else actionData.animation = "FingerBoth" end
	end
	Kinkocalypse.PlayAction(player, equipedTags, actionData)
end


Kinkocalypse.DoOrgasm = function(player, equipedTags, arousalDecrease)
	local ArousalLevel = player:getModData().Arousal
	local orgasmSound = Kinkocalypse.GetOrgasmSound()
	
	if equipedTags["SteelGag"] ~= nil then
		local dialogueMuffled = true
		Kinkocalypse.Say(orgasmSound, dialogueMuffled)
	else player:SayShout(orgasmSound) end
	if equipedTags["ChainedHandsBack"] == nil then player:dropHandItems() end

	player:setZ(player:getZ() + 0.1) -- what is this for?

	-- orgasm affects player stats
	ArousalLevel = ArousalLevel - (arousalDecrease + ZombRand(10))
	if ArousalLevel < 0 then ArousalLevel = 0 end
	player:getModData().Arousal = ArousalLevel
	Kinkocalypse.WetPants(75 + ZombRand(25))
	Kinkocalypse.AddCumToSprite(player)
	player:getBodyDamage():setUnhappynessLevel(0)
	player:getBodyDamage():setBoredomLevel(0)
	player:getStats():setStress(0)
	player:getStats():setFatigue(player:getStats():getFatigue() + ZombRand(0.1, 0.2))
end


Kinkocalypse.EveryTenMinutes = function()
	-- AddRestrictionPain functionality
	if SandboxVars.Kinkocalypse.RestrictionPain == true then
		local player = getPlayer()
		local equipedTags = Kinkocalypse.GetPlayerEquipedRestrictionsTags()
		Kinkocalypse.MaintainCharacterRestrictionsTime(player, equipedTags)
	end

	-- TODO this is not currently implemented... requires True Crawl. unfinished.
	if Kinkocalypse.ControllCenterIsOn() == true and Kinkocalypse.IsPunishmentActive("ForceCrawl") == true then
		if player:getVariableBoolean("isCrawling") == false and player:isAsleep() == false then
			Kinkocalypse.AddPunishmentScore(10, getText("UI_Kincalypse_ViolateForceCrawlRule"))
			Kinkocalypse.AddPunishment("ForceCrawl", 60 + ZombRand(120))
			local diceRoll = ZombRand(90)
			if diceRoll > 60 then
				Kinkocalypse.AddPunishment("ChainedLegs", 60 + ZombRand(120))
			elseif diceRoll > 30 then
				Kinkocalypse.AddPunishment("ChainedHandsBack", 60 + ZombRand(120))
			end
			player:setSneaking(true)
		end
	end

	if Kinkocalypse.ControllCenterIsOn() == true and Kinkocalypse.IsPunishmentActive("NoOutside") == true and getPlayer():isOutside() then
		Kinkocalypse.AddPunishmentScore(25, getText("UI_Kincalypse_ViolateNoOutside"))
		Kinkocalypse.AddPunishment("NoOutside", 30 + ZombRand(60))
	end
end
Events.EveryTenMinutes.Add(Kinkocalypse.EveryTenMinutes)


Kinkocalypse.TagToText = function(tag)
	if tag == nil then return "???"
	else return getText("UI_Kincalypse_Device_"..tag) end
	return "???"
end


Kinkocalypse.AddRestrictionPain = function(player, tag, wearTime)
	if Kinkocalypse.ControllCenterIsOn() ~= true then return end

	if wearTime > ZombRand(1440, 5760) then
		local affectedBodyParts = nil
		local maxStiffness = 10

		if tag == "SteelChastityBelt" then affectedBodyParts = { BodyPartType.Groin }
		elseif tag == "SteelAnalPlug" or tag == "SteelVaginalPlug" then affectedBodyParts = { BodyPartType.Groin }
			maxStiffness = maxStiffness + 50
		elseif tag == "SteelGag" then affectedBodyParts = { BodyPartType.Head }
			if Kinkocalypse.IsPunishmentActive("Gaged") then maxStiffness = maxStiffness + 50 end
		elseif tag == "SteelChastityBra" then affectedBodyParts = { BodyPartType.Torso_Upper }
		elseif tag == "SteelCorset" then affectedBodyParts = { BodyPartType.Torso_Lower }
		elseif tag == "SteelWristCuffs" then affectedBodyParts = { BodyPartType.ForeArm_L, BodyPartType.ForeArm_R }
			if Kinkocalypse.IsPunishmentActive("ChainedHandsBack") == true then maxStiffness = maxStiffness + 25 end
		elseif tag == "SteelAnkleCuffs" or tag == "SteelAnklets" then affectedBodyParts = { BodyPartType.LowerLeg_L, BodyPartType.LowerLeg_R }
			if Kinkocalypse.IsPunishmentActive("ChainedLegs") == true then maxStiffness = maxStiffness + 25 end
		elseif tag == "SteelCollar" then affectedBodyParts = { BodyPartType.Neck } end

		if affectedBodyParts ~= nil and ZombRand(100) > 60 - maxStiffness then
			for i, bp in ipairs(affectedBodyParts) do
				local part = player:getBodyDamage():getBodyPart(bp)
				local stiffnessNow = part:getStiffness()
				if stiffnessNow <= maxStiffness then part:setStiffness(stiffnessNow + ZombRand(5, 20)) end
			end

			local deviceAffected = Kinkocalypse.TagToText(tag)
			-- change text from vaginal plug to cock plug if player has penis
			if Kinkocalypse.PlayerHasVagina(player) == false and tag == "SteelVaginalPlug" then deviceAffected = getText("UI_Kincalypse_Device_SteelPenisPlug") end
			if ZombRand(1000) > 990 - maxStiffness
				then Kinkocalypse.Say(getText(deviceAffected)) end --changed from "device sits uncomfortable" to allow for more specific complaints
		end
	end
end


Kinkocalypse.MaintainCharacterRestrictionsTime = function(player, equipedTags)
	local modData = player:getModData()
	local analPlugTime = modData.AnalPlugTime or 0
	local vaginalPlugTime = modData.VaginalPlugTime or 0
	local gagTime = modData.GagTime or 0
	local chastityBraTime = modData.ChastityBraTime or 0
	local chastityBeltTime = modData.ChastityBeltTime or 0
	local corsetTime = modData.CorsetTime or 0
	local handCuffsTime = modData.HandCuffsTime or 0
	local legCuffsTime = modData.LegCuffsTime or 0
	local collarTime = modData.CollarTime or 0
	local ankletsTime = modData.AnkletsTime or 0

	local equipTagList = {"SteelAnalPlug", "SteelVaginalPlug", "SteelGag", "SteelChastityBra", "SteelChastityBelt", "SteelCorset", "SteelWristCuffs", "SteelAnkleCuffs", "SteelCollar", "SteelAnklets"}
	local equipTimeList = {analPlugTime, vaginalPlugTime, gagTime, chastityBraTime, chastityBeltTime, corsetTime, handCuffsTime, legCuffsTime, collarTime, ankletsTime}

	-- check whether each item in equipTagList is equipped or not
	-- add restriction time to each item equipped
	for i, equipedTag in ipairs(equipTagList) do
		local equipTagTime = equipTimeList[i]
		if equipedTags[equipedTag] ~= nil then
			Kinkocalypse.AddRestrictionPain(player, equipedTag, equipTagTime)
			equipTimeList[i] = equipTagTime + 1
		else
			-- TODO figure out how to maintain restriction time when switching party members using the Party System mod?
			-- right now it resets to 0 when switching characters since the alternate character does not have the items equipped
			-- meaning that restriction pain never actually starts if the player is switching characters frequently
			if SandboxVars.PartySystemMod == false then equipTimeList[i] = 0 end
		end
	end

	modData.AnalPlugTime = analPlugTime
	modData.VaginalPlugTime = vaginalPlugTime
	modData.GagTime = gagTime
	modData.ChastityBraTime = chastityBraTime
	modData.ChastityBeltTime = chastityBeltTime
	modData.CorsetTime = corsetTime
	modData.HandCuffsTime = handCuffsTime
	modData.LegCuffsTime = legCuffsTime
	modData.CollarTime = collarTime
	modData.AnkletsTime = ankletsTime
end


Kinkocalypse.ApplyPunishment = function (player, punishment)
	-- took this out of MaintainCharacterStats for readability
	-- contains punishments that need to be checked for every 1 minute

	if punishment == "GenitalClamp" then
		local stats = player:getStats()
		local stress = stats:getStress()
		local pain = ZombRand(40, 60)
		local fractureMax = 20
		local painMax = 80

		if stress <= 20 then stats:setStress(stress + pain / 2) end

		local part = player:getBodyDamage():getBodyPart(BodyPartType.Groin)
		local painNow = part:getPain()
		-- TODO figure out how to just play the limping animation without the fracture? idk
		-- if player:getVariableFloat("WalkInjury") < walkInjury then player:setVariable("WalkInjury", walkInjury) end
		if part:getFractureTime() < fractureMax then part:setFractureTime(fractureMax) end

		if painNow <= painMax then part:setAdditionalPain(pain) end
		if painNow >= 50 and ZombRand(1000) > 995 then
			stats:setPanic(painMax)
			if Kinkocalypse.PlayerHasVagina(player) then Kinkocalypse.Say(getText("UI_Kincalypse_ClitHurts"))
			else Kinkocalypse.Say(getText("UI_Kincalypse_TestesHurt")) end
		end
	end

	if punishment == "NippleClamps" then
		local stats = player:getStats()
		local stress = stats:getStress()
		local pain = ZombRand(20, 35)
		local painMax = 80

		if stress <= 20 then stats:setStress(stress + pain) end

		local part = player:getBodyDamage():getBodyPart(BodyPartType.Torso_Upper)
		painNow = part:getPain()

		if painNow <= painMax then part:setAdditionalPain(pain) end
		if painNow >= 20 and ZombRand(1000) > 990 then
			Kinkocalypse.Say(getText("UI_Kincalypse_NipplesHurt")) end
	end

	if punishment == "ForceCrouch" then
		if player:isSneaking() == false then
			Kinkocalypse.AddPunishmentScore(10, getText("UI_Kincalypse_ViolateForceCrouchRule"))
			Kinkocalypse.AddPunishment("ForceCrouch", 60 + ZombRand(120))
			if ZombRand(100) > 50 then
				Kinkocalypse.AddPunishment("ChainedLegs", 60 + ZombRand(120))
			end
			player:setSneaking(true)
		end
	end

	if punishment == "NoDriving" and player:isDriving() then
		Kinkocalypse.AddPunishmentScore(10, getText("UI_Kincalypse_ViolateNoDrivingRule"))
		Kinkocalypse.AddPunishment("NoDriving", 20 + ZombRand(60))
	end
	
	if punishment == "EmptyHands" then
		local first = player:getPrimaryHandItem()
		local second = player:getSecondaryHandItem()
		local doPunish = true

		if first ~= nil and Kinkocalypse.IsHasTag(first, "RestrictedSystemCuffs") then doPunish = false end
		if second ~= nil and Kinkocalypse.IsHasTag(second, "RestrictedSystemCuffs") then doPunish = false end

		if doPunish == true and (first ~= nil or second ~= nil) then
			Kinkocalypse.AddPunishmentScore(20, getText("UI_Kincalypse_ViolateEmptyHandsRule"))
			player:dropHandItems()
			if ZombRand(100) > 50 then Kinkocalypse.AddPunishment("HandsBack", 60 + ZombRand(120)) end
		end
	end
end


Kinkocalypse.SetArousal = function(player, modData, equipedTags)
	-- set arousal level based on various modifiers
	if modData.Arousal == nil then modData.Arousal = 0.0 end
	if modData.RestrictionItemsCount == nil then modData.RestrictionItemsCount = 0 end
	local sexDesireValue = (modData.RestrictionItemsCount + 1) * 0.0005

	-- check for zombowin traits
	if player:HasTrait("Sexaddict") then sexDesireValue = (sexDesireValue * 2) + 0.001 end
	if player:HasTrait("submissive") and modData.RestrictionItemsCount > 0 then sexDesireValue = sexDesireValue * (modData.RestrictionItemsCount * 0.5) end

	if equipedTags["SteelVaginalPlug"] ~= nil then sexDesireValue = sexDesireValue * 3 end
	if equipedTags["SteelAnalPlug"] ~= nil then sexDesireValue = sexDesireValue * 2 end
	if Kinkocalypse.IsPunishmentActive("Aphrodisiac") == true then sexDesireValue = sexDesireValue * 10 end
	if Kinkocalypse.IsPunishmentActive("Vibtation") == true then sexDesireValue = sexDesireValue * ZombRand(3, 5)
		-- integrate vibrators with zombowin
		if modData.Temptation and modData.Temptation.tCount then modData.Temptation.tCount = modData.Temptation.tCount + ZombRand(0.15, 0.3) end
	end

	modData.Arousal = modData.Arousal + sexDesireValue
	if modData.Arousal > 200 then modData.Arousal = 200.0 end
	if modData.Arousal < 0 then modData.Arousal = 0.0 end
	return sexDesireValue
end


Kinkocalypse.MaintainArousal = function(player, modData, equipedTags)
	-- moved from MaintainCharacterStats for readability
	local bodyDamage = player:getBodyDamage()
	local unhappiness = bodyDamage:getUnhappynessLevel()
	local stats = player:getStats()
	local stress = stats:getStress()
	local drunkenness = stats:getDrunkenness()

	-- set arousal level based on various modifiers
	local sexDesireValue = Kinkocalypse.SetArousal(player, modData, equipedTags)

	if Kinkocalypse.IsPunishmentActive("Vibtation") == true then
		if ZombRand(1000) > 950 then
			player:Say("<Bzz-Zzz>")
			Kinkocalypse.WetPants(ZombRand(1,5))
		end
	end

	ArousalLevel = modData.Arousal

	-- tick down time since last forced masturbation
	if modData.ForcedMasturbationTime == nil then modData.ForcedMasturbationTime = 0 end
	if modData.ForcedMasturbationTime > 0 then
		modData.ForcedMasturbationTime = modData.ForcedMasturbationTime - 1
	else modData.ForcedMasturbationTime = 0 end

	local randomNotifyChance = ZombRand(1000)
	local forcedMasturbationModifier = 0
	if ArousalLevel >= 190 then forcedMasturbationModifier = forcedMasturbationModifier + 40
	elseif ArousalLevel >= 150 then forcedMasturbationModifier = forcedMasturbationModifier + 20 end

	local dialogueMuffled = false
	local hornyUIString = "UI_Kincalypse_Horny"
	if equipedTags["SteelGag"] ~= nil then
		hornyUIString = hornyUIString.."Gagged"
		dialogueMuffled = true
	end
	local wetPantsAmount = 0
	local drunkennessMax = 0
	local sexDesireModifier = 1

	if ArousalLevel >= 150 then hornyUIString = hornyUIString.."5"; wetPantsAmount = 60; drunkennessMax = 65; sexDesireModifier = 8
	elseif ArousalLevel >= 99 then hornyUIString = hornyUIString.."4"; wetPantsAmount = 50; drunkennessMax = 35; sexDesireModifier = 5
	elseif ArousalLevel >= 90 then hornyUIString = hornyUIString.."3"; wetPantsAmount = 35; drunkennessMax = 20; sexDesireModifier = 4
	elseif ArousalLevel >= 75 then hornyUIString = hornyUIString.."2"; wetPantsAmount = 25; drunkennessMax = 10; sexDesireModifier = 3
	elseif ArousalLevel >= 50 then hornyUIString = hornyUIString.."1"; wetPantsAmount = 15; drunkennessMax = 0; sexDesireModifier = 2 end

	sexDesireValue = sexDesireValue * sexDesireModifier
	if SandboxVars.Kinkocalypse.AllowUnhappiness and unhappiness <= 50 then bodyDamage:setUnhappynessLevel(unhappiness + (sexDesireValue * 5)) end
	if SandboxVars.Kinkocalypse.AllowStress and stress <= 0.5 then stats:setStress(stress + sexDesireValue) end
	if SandboxVars.Kinkocalypse.AllowDrunkenness and drunkenness < drunkennessMax and ZombRand(100) > 90 then stats:setDrunkenness(drunkennessMax) end

	if equipedTags["SteelGag"] ~= nil then
		if ZombRand(1000) > 990 then
			if SandboxVars.Kinkocalypse.AllowRandomMoaning == true or ArousalLevel >=150 then
				player:Say(Kinkocalypse.GetMoan())
			end
		end
	end

	if randomNotifyChance > 990 and ArousalLevel >= 50 then
		Kinkocalypse.Say(getText(hornyUIString).." "..string.format("%.2f", ArousalLevel).."]", dialogueMuffled)
		Kinkocalypse.WetPants(wetPantsAmount)
		if ArousalLevel > 99 then
			Kinkocalypse.AddCumToSprite(player)
			--default ForcedMasturbationChance is 25
			if ZombRand(100) <= SandboxVars.Kinkocalypse.ForcedMasturbationChance and modData.ForcedMasturbationTime <= 0 then
				modData.ForcedMasturbationTime = ZombRand(60, 240) - forcedMasturbationModifier
				Kinkocalypse.MasturbateForce()
			end
		end
	end

	if ArousalLevel >= SandboxVars.Kinkocalypse.ArousalForOrgasm and ZombRand(1000) < 5 and SandboxVars.Kinkocalypse.AllowOrgasm then
		local orgasmPower = ZombRand(10, 20)
		if equipedTags["SteelAnalPlug"] ~= nil then orgasmPower = orgasmPower + ZombRand(15, 20) end
		if equipedTags["SteelVaginalPlug"] ~= nil then orgasmPower = orgasmPower + ZombRand(5, 10) end
		if Kinkocalypse.IsPunishmentActive("Vibtation") == true or SandboxVars.Kinkocalypse.CumWithoutVibrators then
			orgasmPower = orgasmPower + ZombRand(5, 30)
			Kinkocalypse.DoOrgasm(player, equipedTags, orgasmPower)
		end
	end
end


Kinkocalypse.MaintainCharacterStats = function()
	local player = getPlayer()
	if GameTime:isGamePaused() == true then return end

	-- keep this here - no sleep punishment needs to be checked for before anything else
	if player:isAsleep() then
		if Kinkocalypse.IsPunishmentActive("NoSleep") == true then
			player:forceAwake()
			Kinkocalypse.AddPunishmentScore(100, getText("UI_Kincalypse_ViolateNoSleepRule"))
			return
		end
	return end

	local modData = player:getModData()
	local equipedTags = Kinkocalypse.GetPlayerEquipedRestrictionsTags()

	Kinkocalypse.CheckHandBackChains(player, equipedTags)
	Kinkocalypse.CheckBlindFold(player)
	Kinkocalypse.CheckRestrictionItems(player, equipedTags)

	-- player movement
	modData.RestrainsSpeed = 1.0
	if equipedTags["ChainedLegs"] ~= nil then modData.RestrainsSpeed = modData.RestrainsSpeed - 0.35 end
	if equipedTags["ChainedThighs"] ~= nil then modData.RestrainsSpeed = modData.RestrainsSpeed - 0.05 end
	if equipedTags["SteelAnklets"] ~= nil then modData.RestrainsSpeed = modData.RestrainsSpeed - 0.15 end
	if equipedTags["SteelChastityBelt"] ~= nil then modData.RestrainsSpeed = modData.RestrainsSpeed - 0.05 end
	if equipedTags["SteelAnalPlug"] ~= nil then modData.RestrainsSpeed = modData.RestrainsSpeed - 0.05 end
	if equipedTags["SteelVaginalPlug"] ~= nil then modData.RestrainsSpeed = modData.RestrainsSpeed - 0.05 end
	if modData.RestrainsSpeed < 0.40 then modData.RestrainsSpeed = 0.40 end

	-- use Speed Framework to slow down player based on equipped restraints
	-- switching to this fixed so many bugs yay no more falling through the stairs or getting teleported randomly when exiting a vehicle!
	SpeedFramework.SetPlayerSpeed(player, modData.RestrainsSpeed)

	-- punishment checking
	for i, punishment in ipairs(Kinkocalypse.PunishmentList) do
		if Kinkocalypse.IsPunishmentActive(punishment) == true then Kinkocalypse.ApplyPunishment(player, punishment) end
	end

	-- gag/speech checking
	player:setCanShout(true)
	if equipedTags["SteelGag"] ~= nil then player:setCanShout(false) end

	Kinkocalypse.MaintainArousal(player, modData, equipedTags)

end
Events.EveryOneMinute.Add(Kinkocalypse.MaintainCharacterStats)


Kinkocalypse.TripCharacter = function(fallChance)
	-- thanks to TripnFall mod from BOBcat (https://steamcommunity.com/id/the_bobcat/myworkshopfiles/?appid=108600)
	local player = getPlayer()
	local equipedTags = Kinkocalypse.GetPlayerEquipedRestrictionsTags()
	player:setBlockMovement(true)
	player:playSound('TripOverObstacle')

	local bumpType
	if ZombRand(100) > 50 then bumpType = 'left' else bumpType = 'right' end
	if player:isSneaking() then fallChance = fallChance - 30 end
	local isFalling = ZombRand(100) < fallChance

	if ZombRand(100) > 50 and isFalling then player:fallenOnKnees() end
	player:setBumpFallType('FallForward')
	player:setBumpType(bumpType)
	player:setBumpDone(false)
	player:setBumpFall(isFalling)
	player:reportEvent('wasBumped')

	player:setBlockMovement(false)

	--if equipedTags["SteelGag"] ~= nil then Kinkocalypse.Say(getText("UI_Kincalypse_Ouch"))
	--else player:SayShout(getText("UI_Kincalypse_Ouch")) end
end

-- moving hook
Kinkocalypse.OnPlayerMove = function()
	local player = getPlayer()
	if GameTime:isGamePaused() == true or player:isAsleep() then return end

	if player:getModData().RestrictionItemsCount == nil then player:getModData().RestrictionItemsCount = 0 end
	Kinkocalypse.MoveUpdateCounter = Kinkocalypse.MoveUpdateCounter + 1
	if Kinkocalypse.MoveUpdateCounter > 50 + ZombRand(25, 50) then
		Kinkocalypse.MoveUpdateCounter = 0

		local equipedTags = Kinkocalypse.GetPlayerEquipedRestrictionsTags()
		local tripChanceFilter = ZombRand(1000)
		local chanceToTrip = 0;
		local enduranceDrain = player:getModData().RestrictionItemsCount * 0.00002

		-- trip modifiers
		if equipedTags["ChainedLegs"] ~= nil then
			chanceToTrip = chanceToTrip + 10; tripChanceFilter = tripChanceFilter + 50; enduranceDrain = enduranceDrain + 0.0003; end
		if equipedTags["ChainedHands"] ~= nil then
			chanceToTrip = chanceToTrip + 3; tripChanceFilter = tripChanceFilter + 10; enduranceDrain = enduranceDrain + 0.00005; end
		if equipedTags["ChainedHandsBack"] ~= nil then
			chanceToTrip = chanceToTrip + 10; tripChanceFilter = tripChanceFilter + 25; enduranceDrain = enduranceDrain + 0.00003 end
		if equipedTags["ChainedThighs"] ~= nil then
			chanceToTrip = chanceToTrip + 3; enduranceDrain = enduranceDrain + 0.00001 end
		if equipedTags["SteelBlindFold"] ~= nil then 
			chanceToTrip = chanceToTrip + 15; tripChanceFilter = tripChanceFilter + 25	end
		if equipedTags["SteelAnklets"] ~= nil then 
			chanceToTrip = chanceToTrip + 10; tripChanceFilter = tripChanceFilter + 35; enduranceDrain = enduranceDrain + 0.0005 end
		if equipedTags["SteelCorset"] ~= nil then 
			chanceToTrip = chanceToTrip + 3; enduranceDrain = enduranceDrain + 0.001 end
		if player:getModData().Arousal >= 150 then tripChanceFilter = tripChanceFilter + 50; enduranceDrain = enduranceDrain + 0.0003 end
		if Kinkocalypse.IsPunishmentActive("GenitalClamp") == true then
			enduranceDrain = enduranceDrain * 1.30; chanceToTrip = chanceToTrip * 2 end
		if Kinkocalypse.IsPunishmentActive("Vibtation") == true then
			enduranceDrain = enduranceDrain * 1.25; chanceToTrip = chanceToTrip * 2 end
		if player:isRunning() == true then
			enduranceDrain = enduranceDrain * 1.25; chanceToTrip = chanceToTrip * 2 end
		if player:isSprinting() == true then
			enduranceDrain = enduranceDrain * 2; chanceToTrip = chanceToTrip * 3 end

		player:getStats():setEndurance(player:getStats():getEndurance() - enduranceDrain);

		--trip character
		if tripChanceFilter > 995 then
			if chanceToTrip > ZombRand(200) then
				Kinkocalypse.TripCharacter(0)
			end
		elseif equipedTags["ChainedLegs"] ~= nil or equipedTags["SteelAnklets"] ~= nil then
			if player:isRunning() and chanceToTrip > ZombRand(200) then
				Kinkocalypse.TripCharacter(40)
			end
			if player:isSprinting() and chanceToTrip > ZombRand(100) then
				Kinkocalypse.TripCharacter(70)
			end
		end

		if Kinkocalypse.IsPunishmentActive("NoRunning") == true and (player:isRunning() or player:isSprinting()) then
			Kinkocalypse.AddPunishmentScore(10, getText("UI_Kincalypse_ViolateRunningRule"))
			Kinkocalypse.AddPunishment("NoRunning", 60 + ZombRand(120))
			if ZombRand(100) > 50 then 
				Kinkocalypse.AddPunishment("ChainedLegs", 60 + ZombRand(120))
			end
		end
	end
end
Events.OnPlayerMove.Add(Kinkocalypse.OnPlayerMove)