From ea615abaa3784a35b998b71125c69ea50d2e4c96 Mon Sep 17 00:00:00 2001 From: Sarah Djukanovic Date: Sat, 18 Apr 2026 13:11:11 +0200 Subject: [PATCH] Initial PR to highlight the changes done, plus the new add-on code. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../core/EdgerunningSystem.reds | 201 +++++++++++ .../helpers/PsychosisEffectsChecker.reds | 8 + .../WannabeEdgerunnerAddon/WEA_Config.reds | 171 ++++++++++ .../WannabeEdgerunnerAddon/WEA_Neuro.reds | 67 ++++ .../WannabeEdgerunnerAddon/WEA_Psychosis.reds | 24 ++ .../WannabeEdgerunnerAddon/WEA_Sleep.reds | 28 ++ .../WannabeEdgerunnerAddon/WEA_Smoke.reds | 80 +++++ .../WannabeEdgerunnerAddon/WEA_Utils.reds | 9 + .../WEA_StatusEffects.yaml | 323 ++++++++++++++++++ 9 files changed, 911 insertions(+) create mode 100644 src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Config.reds create mode 100644 src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Neuro.reds create mode 100644 src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Psychosis.reds create mode 100644 src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Sleep.reds create mode 100644 src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Smoke.reds create mode 100644 src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Utils.reds create mode 100644 src/Wannabe Edgerunner/r6/tweaks/WannabeEdgerunnerAddon/WEA_StatusEffects.yaml diff --git a/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunner/core/EdgerunningSystem.reds b/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunner/core/EdgerunningSystem.reds index b6b488cc..2b0759a5 100644 --- a/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunner/core/EdgerunningSystem.reds +++ b/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunner/core/EdgerunningSystem.reds @@ -90,6 +90,16 @@ public class EdgerunningSystem extends ScriptableSystem { private persistent let humanityRestoringActionTakenSocial: Bool = false; private persistent let humanityRestoringActionTakenShower: Bool = false; + // WEA Add-on fields (˘ω˘) + public let m_weaSleepPending: Bool; + public let m_weaSleepCheckTime: Float; + public let m_weaSleepBypass: Bool; + public let m_weaCurrentTwitchTier: Int32; + public let m_weaHostilityPulseId: DelayID; + public let m_weaNeuroReduction: Float; + private persistent let m_weaSmokeCount: Int32; + private persistent let m_weaSmokeLastDay: Int32; + public static func GetInstance(gameInstance: GameInstance) -> ref { let system: ref = GameInstance.GetScriptableSystemsContainer(gameInstance).Get(n"Edgerunning.System.EdgerunningSystem") as EdgerunningSystem; return system; @@ -180,6 +190,30 @@ public class EdgerunningSystem extends ScriptableSystem { }; this.ScheduleHumanityRestoreEffect(psychoDuration + 3.0); + + // WEA: hostility pulse - broadcast combat stims so nearby NPCs react to psychosis (╯°□°)╯︵ ┻━┻ + let weaConfig = WEAConfig.Get(); + if weaConfig.psychosisHostilityEnabled { + let radius = Cast(weaConfig.psychosisHostilityRadius); + + // instant burst + repeating 1s callback + let broadcastPlayer = GetPlayer(GetGameInstance()); + if IsDefined(broadcastPlayer) { + let broadcaster = broadcastPlayer.GetStimBroadcasterComponent(); + if IsDefined(broadcaster) { + broadcaster.TriggerSingleBroadcast(broadcastPlayer, gamedataStimType.Combat, radius); + broadcaster.TriggerSingleBroadcast(broadcastPlayer, gamedataStimType.Gunshot, radius); + broadcaster.TriggerSingleBroadcast(broadcastPlayer, gamedataStimType.Explosion, radius); + broadcaster.TriggerSingleBroadcast(broadcastPlayer, gamedataStimType.CombatCall, radius); + }; + }; + + let callback = new WEA_HostilityPulseCallback(); + callback.m_systemRef = this; + callback.m_radius = radius; + this.m_weaHostilityPulseId = GameInstance.GetDelaySystem(GetGameInstance()).DelayCallback(callback, 1.0, true); + E(s"! WEA Psychosis: hostility pulse started, radius \(radius)m"); + }; } public func RunPostPsychosisFlow() -> Void { @@ -211,6 +245,10 @@ public class EdgerunningSystem extends ScriptableSystem { } public func StopPsychosisFlow() -> Void { + // WEA: stop hostility pulse (´• ω •`) + GameInstance.GetDelaySystem(GetGameInstance()).CancelCallback(this.m_weaHostilityPulseId); + E("! WEA Psychosis: hostility pulse stopped"); + E(s"? Stop psychosis"); this.effectsHelper.StopNewPsychosisEffect(); this.effectsHelper.CancelCycledFx(); @@ -245,6 +283,7 @@ public class EdgerunningSystem extends ScriptableSystem { } public func OnEnemyKilled(affiliation: gamedataAffiliation) -> Void { + this.WEA_SetNeuroBypass(); if this.effectsChecker.IsPossessed() { this.StopEverythingNew(); E(s"! Playing as Johnny - all effects removed"); @@ -276,8 +315,34 @@ public class EdgerunningSystem extends ScriptableSystem { } public func OnRestoreAction(action: HumanityRestoringAction) -> Void { + // WEA: sleep verification - must actually sleep, not just tap the bed (¬_¬) + let weaConfig = WEAConfig.Get(); + let isVerifiedSleep = false; // true only on the bypass path - tells the scaling code it's safe to read checkTime + if Equals(action, HumanityRestoringAction.Sleep) && weaConfig.sleepVerificationEnabled { + if this.m_weaSleepBypass { + this.m_weaSleepBypass = false; + isVerifiedSleep = true; + } else { + if !this.m_weaSleepPending { + this.m_weaSleepPending = true; + this.m_weaSleepCheckTime = GameInstance.GetTimeSystem(GetGameInstance()).GetGameTimeStamp(); + let verifyCb = new WEA_SleepVerifyCallback(); + verifyCb.m_systemRef = this; + GameInstance.GetDelaySystem(GetGameInstance()).DelayCallback(verifyCb, 10.0, false); + }; + return; + }; + }; + switch (action) { case HumanityRestoringAction.Sleep: + // WEA: snapshot for sleep scaling ♪(´▽`) + let weaPreRestoreHumanity: Int32 = 0; + let weaApplyScaling: Bool = isVerifiedSleep && weaConfig.sleepScalingEnabled; + if weaApplyScaling { + weaPreRestoreHumanity = this.GetHumanityCurrent(); + }; + this.StopEverythingNew(); this.SetWentFullPsycho(false); if !this.config.fullHumanityRestoreOnSleep { @@ -306,6 +371,36 @@ public class EdgerunningSystem extends ScriptableSystem { } StatusEffectHelper.RemoveStatusEffect(this.player, t"BaseStatusEffect.LifeAffirmedBuff"); E("! Rested, humanity value restored."); + + // WEA: scale restore by hours slept - short naps = less humanity (´-ω-`) + if weaApplyScaling { + let sleepNow = GameInstance.GetTimeSystem(GetGameInstance()).GetGameTimeStamp(); + let hoursSlept = (sleepNow - this.m_weaSleepCheckTime) / 3600.0; + let fullHours = Cast(weaConfig.sleepScalingFullHours); + let scaleFactor: Float = 1.0; + if fullHours > 0.0 && hoursSlept < fullHours { + scaleFactor = hoursSlept / fullHours; + }; + if scaleFactor < 1.0 { + let restoredAmount = this.GetHumanityCurrent() - weaPreRestoreHumanity; + if restoredAmount > 0 { + let excess = Cast(restoredAmount) * (1.0 - scaleFactor); + E(s"! WEA Sleep: slept \(hoursSlept)h, scale \(scaleFactor * 100.0)%, clawing back \(excess)"); + this.AddHumanityDamage(excess); + }; + }; + }; + + // WEA: sleep recovery cap (。•́︿•̀。) + if weaConfig.sleepRecoveryCap < 100 { + let totalHumanity = this.GetHumanityTotal(); + let maxAllowed = (totalHumanity * weaConfig.sleepRecoveryCap) / 100; + let currentHumanity = this.GetHumanityCurrent(); + if currentHumanity > maxAllowed { + let excess = Cast(currentHumanity - maxAllowed); + this.AddHumanityDamage(excess); + }; + }; break; case HumanityRestoringAction.Lover: if !this.humanityRestoringActionTakenLover { StatusEffectHelper.ApplyStatusEffect(this.player, t"BaseStatusEffect.LifeAffirmedBuff", this.player.GetEntityID()); } @@ -355,6 +450,7 @@ public class EdgerunningSystem extends ScriptableSystem { } public func OnBerserkActivation(item: ItemID) -> Void { + this.WEA_SetNeuroBypass(); E("BERSERK ACTIVATED"); let itemRecord: ref = RPGManager.GetItemRecord(item); let quality: gamedataQuality = itemRecord.Quality().Type(); @@ -408,6 +504,7 @@ public class EdgerunningSystem extends ScriptableSystem { } public func OnOverClockActivation(itemRecord: ref) -> Void { + this.WEA_SetNeuroBypass(); E("OVERCLOCK ACTIVATED"); let quality: gamedataQuality = itemRecord.Quality().Type(); let qualityMult: Float; @@ -460,6 +557,7 @@ public class EdgerunningSystem extends ScriptableSystem { } public func OnSandevistanActivation(item: ItemID) -> Void { + this.WEA_SetNeuroBypass(); E("SANDEVISTAN ACTIVATED"); let itemRecord: ref = RPGManager.GetItemRecord(item); let quality: gamedataQuality = itemRecord.Quality().Type(); @@ -513,6 +611,7 @@ public class EdgerunningSystem extends ScriptableSystem { } public func OnKerenzikovActivation() -> Void { + this.WEA_SetNeuroBypass(); E("KERENZIKOV ACTIVATED"); let itemRecord: ref = this.cyberwareHelper.GetCurrentKerenzikov(); if !IsDefined(itemRecord) { @@ -571,6 +670,7 @@ public class EdgerunningSystem extends ScriptableSystem { } public func OnOpticalCamoActivation() -> Void { + this.WEA_SetNeuroBypass(); E("OPTICAL CAMO ACTIVATED"); let itemRecord: ref = this.cyberwareHelper.GetCurrentOpticalCamo(); if !IsDefined(itemRecord) { @@ -630,6 +730,7 @@ public class EdgerunningSystem extends ScriptableSystem { public func OnBloodPumpActivation() -> Void { + this.WEA_SetNeuroBypass(); E("BLOOD PUMP ACTIVATED"); let itemRecord: ref = this.cyberwareHelper.GetCurrentBloodPump(); if !IsDefined(itemRecord) { @@ -686,6 +787,7 @@ public class EdgerunningSystem extends ScriptableSystem { } public func OnArmsCyberwareActivation(type: gamedataItemType) -> Void { + this.WEA_SetNeuroBypass(); let itemId: ItemID = EquipmentSystem.GetInstance(this.player).GetActiveItem(this.player, gamedataEquipmentArea.ArmsCW); let itemRecord: ref = RPGManager.GetItemRecord(itemId); let itemType: gamedataItemType = RPGManager.GetItemType(itemId); @@ -785,6 +887,15 @@ public class EdgerunningSystem extends ScriptableSystem { } public func AddHumanityDamage(cost: Float) -> Void { + // WEA: neuro tier reduction - soften damage instead of blocking, floor at 1.0 (╹◡╹) + let weaConfig = WEAConfig.Get(); + if weaConfig.neuroEnabled && this.m_weaNeuroReduction > 0.0 { + let reduced = cost * (1.0 - this.m_weaNeuroReduction); + if reduced < 1.0 { reduced = 1.0; }; + E(s"! WEA Neuro: damage \(cost) reduced to \(reduced) (block \(this.m_weaNeuroReduction * 100.0)%)"); + cost = reduced; + }; + let total: Int32 = this.GetHumanityTotal(); let damage: Int32 = CeilF(cost); @@ -952,6 +1063,9 @@ public class EdgerunningSystem extends ScriptableSystem { evt.color = this.GetHumanityColor(); GameInstance.GetUISystem(this.player.GetGame()).QueueEvent(evt); + // WEA: update twitch tier on humanity change (ᗒᗣᗕ)՞ + this.WEA_UpdateTwitchTier(); + if this.effectsChecker.IsRipperdocBuffActive() && !this.IsWentFullPsycho() { return; }; if this.effectsChecker.IsNewPsychosisActive() { return; }; if this.effectsChecker.IsNewPostPsychosisActive() { return; }; @@ -1097,6 +1211,9 @@ public class EdgerunningSystem extends ScriptableSystem { } private func ShowHumanityRestoredMessage(opt amount: Int32) -> Void { + // WEA: optional HUD message suppression (・ω<) + if WEAConfig.Get().hideHumanityMessages { return; }; + let onScreenMessage: SimpleScreenMessage; let blackboardDef = GetAllBlackboardDefs().UI_Notifications; let blackboard = GameInstance.GetBlackboardSystem(this.player.GetGame()).Get(blackboardDef); @@ -1132,6 +1249,90 @@ public class EdgerunningSystem extends ScriptableSystem { // system.effectsHelper.RunNewPsychosisEffect(); // system.effectsHelper.RunNewPostPsychosisEffect(); } + + // WEA Add-on helpers ٩(。•́‿•̀。)۶ + public func WEA_GetSmokeCount() -> Int32 { return this.m_weaSmokeCount; } + public func WEA_SetSmokeCount(value: Int32) -> Void { this.m_weaSmokeCount = value; } + public func WEA_GetSmokeLastDay() -> Int32 { return this.m_weaSmokeLastDay; } + public func WEA_SetSmokeLastDay(value: Int32) -> Void { this.m_weaSmokeLastDay = value; } + + public func WEA_GetHumanityPercent() -> Int32 { + let total = this.GetHumanityTotal(); + if total <= 0 { return 0; }; + let current = this.GetHumanityCurrent(); + return (current * 100) / total; + } + + // bypass IsRipperdocBuffActive once so AddHumanityDamage can apply tier reduction + private func WEA_SetNeuroBypass() -> Void { + let config = WEAConfig.Get(); + if config.neuroEnabled && this.m_weaNeuroReduction > 0.0 { + this.effectsChecker.m_weaBypassBuff = true; + }; + } + + // twitch tier picker - fires from InvalidateCurrentState (ᗒᗣᗕ)՞ + private func WEA_UpdateTwitchTier() -> Void { + let config = WEAConfig.Get(); + if !config.twitchEnabled { + this.WEA_ClearAllTwitch(); + return; + }; + + let twitchPlayer = GetPlayer(GetGameInstance()); + if !IsDefined(twitchPlayer) { + this.WEA_ClearAllTwitch(); + return; + }; + + let humanityPercent = this.WEA_GetHumanityPercent(); + let total = this.GetHumanityTotal(); + let threshold = this.GetPsychosisThreshold(); + + // convert the absolute threshold to a percent so we can compare apples to apples + let severePercent = 0; + if total > 0 { + severePercent = (threshold * 100) / total; + }; + + let targetTier = 0; + if humanityPercent < config.twitchThresholdMild { targetTier = 1; }; + if humanityPercent < config.twitchThresholdModerate { targetTier = 2; }; + if severePercent > 0 && humanityPercent <= severePercent { targetTier = 3; }; + + if targetTier != this.m_weaCurrentTwitchTier { + this.WEA_ApplyTwitchTier(targetTier, twitchPlayer); + }; + } + + private func WEA_ApplyTwitchTier(tier: Int32, player: ref) -> Void { + this.WEA_ClearAllTwitch(); + this.m_weaCurrentTwitchTier = tier; + + if tier >= 1 { + StatusEffectHelper.ApplyStatusEffect(player, t"WEA_StatusEffect.Twitch_01"); + }; + if tier >= 2 { + StatusEffectHelper.RemoveStatusEffect(player, t"WEA_StatusEffect.Twitch_01"); + StatusEffectHelper.ApplyStatusEffect(player, t"WEA_StatusEffect.Twitch_02"); + }; + if tier >= 3 { + StatusEffectHelper.RemoveStatusEffect(player, t"WEA_StatusEffect.Twitch_02"); + StatusEffectHelper.ApplyStatusEffect(player, t"WEA_StatusEffect.Twitch_03"); + }; + } + + private func WEA_ClearAllTwitch() -> Void { + let clearPlayer = GetPlayer(GetGameInstance()); + if !IsDefined(clearPlayer) { return; }; + + if this.m_weaCurrentTwitchTier > 0 { + StatusEffectHelper.RemoveStatusEffect(clearPlayer, t"WEA_StatusEffect.Twitch_01"); + StatusEffectHelper.RemoveStatusEffect(clearPlayer, t"WEA_StatusEffect.Twitch_02"); + StatusEffectHelper.RemoveStatusEffect(clearPlayer, t"WEA_StatusEffect.Twitch_03"); + this.m_weaCurrentTwitchTier = 0; + }; + } } public class LaunchCycledPsychosisCheckCallback extends DelayCallback { diff --git a/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunner/helpers/PsychosisEffectsChecker.reds b/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunner/helpers/PsychosisEffectsChecker.reds index 34c47c96..527ff755 100644 --- a/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunner/helpers/PsychosisEffectsChecker.reds +++ b/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunner/helpers/PsychosisEffectsChecker.reds @@ -15,6 +15,9 @@ public class PsychosisEffectsChecker { private let questsSystem: ref; private let possessed: Bool; + // WEA: one-shot bypass flag, consumed on read ಠ‿ಠ + public let m_weaBypassBuff: Bool; + public func Init(player: ref) -> Void { this.player = player; this.playerSystem = GameInstance.GetPlayerSystem(this.player.GetGame()); @@ -38,6 +41,11 @@ public class PsychosisEffectsChecker { } public func IsRipperdocBuffActive() -> Bool { + // WEA: bypass flag - return false once so neuro reduction applies + if this.m_weaBypassBuff { + this.m_weaBypassBuff = false; + return false; + }; let checked: Bool = StatusEffectSystem.ObjectHasStatusEffectWithTag(this.player, n"Neuroblockers"); E(s"? Check for neuroblockers tag: \(checked)"); return checked; diff --git a/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Config.reds b/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Config.reds new file mode 100644 index 00000000..c1feba75 --- /dev/null +++ b/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Config.reds @@ -0,0 +1,171 @@ +// Wannabe Edgerunner Add-on - config (ノ◕ヮ◕)ノ*:・゚✧ + +@if(ModuleExists("ModSettingsModule")) +public class WEAConfig { + + @runtimeProperty("ModSettings.mod", "WE Add-on") + @runtimeProperty("ModSettings.category", "Sleep Recovery") + @runtimeProperty("ModSettings.displayName", "Require Actual Sleep") + @runtimeProperty("ModSettings.description", "If enabled, clicking the bed and canceling won't restore humanity - you must actually sleep") + public let sleepVerificationEnabled: Bool = true; + + @runtimeProperty("ModSettings.mod", "WE Add-on") + @runtimeProperty("ModSettings.category", "Sleep Recovery") + @runtimeProperty("ModSettings.displayName", "Max Recovery %") + @runtimeProperty("ModSettings.description", "Maximum humanity percentage recoverable from sleep (100 = full restore)") + @runtimeProperty("ModSettings.step", "5") + @runtimeProperty("ModSettings.min", "10") + @runtimeProperty("ModSettings.max", "100") + public let sleepRecoveryCap: Int32 = 100; + + @runtimeProperty("ModSettings.mod", "WE Add-on") + @runtimeProperty("ModSettings.category", "Sleep Recovery") + @runtimeProperty("ModSettings.displayName", "Scale By Hours Slept") + @runtimeProperty("ModSettings.description", "Short naps restore less than a full night's sleep - the longer you actually sleep, the more humanity comes back. Needs Sleep Verification.") + public let sleepScalingEnabled: Bool = false; + + @runtimeProperty("ModSettings.mod", "WE Add-on") + @runtimeProperty("ModSettings.category", "Sleep Recovery") + @runtimeProperty("ModSettings.displayName", "Full Restore Hours") + @runtimeProperty("ModSettings.description", "Sleep this many hours to wake up fully rested. Less than that, and you only get a proportional slice of humanity back.") + @runtimeProperty("ModSettings.step", "1") + @runtimeProperty("ModSettings.min", "6") + @runtimeProperty("ModSettings.max", "12") + public let sleepScalingFullHours: Int32 = 8; + + @runtimeProperty("ModSettings.mod", "WE Add-on") + @runtimeProperty("ModSettings.category", "HUD") + @runtimeProperty("ModSettings.displayName", "Hide Humanity Restored Messages") + @runtimeProperty("ModSettings.description", "Tired of '+X Humanity' popups cluttering your screen every time you pet a cat? Flip this on for a quieter HUD (・ω<)") + public let hideHumanityMessages: Bool = false; + + @runtimeProperty("ModSettings.mod", "WE Add-on") + @runtimeProperty("ModSettings.category", "Weapon Twitch") + @runtimeProperty("ModSettings.displayName", "Enable Weapon Twitch") + @runtimeProperty("ModSettings.description", "Hands tremble when aiming at low humanity - small, quick twitches that worsen as you lose yourself") + public let twitchEnabled: Bool = true; + + @runtimeProperty("ModSettings.mod", "WE Add-on") + @runtimeProperty("ModSettings.category", "Weapon Twitch") + @runtimeProperty("ModSettings.displayName", "Mild Twitch Threshold %") + @runtimeProperty("ModSettings.description", "Humanity % below which mild weapon twitch begins") + @runtimeProperty("ModSettings.step", "5") + @runtimeProperty("ModSettings.min", "10") + @runtimeProperty("ModSettings.max", "80") + public let twitchThresholdMild: Int32 = 40; + + @runtimeProperty("ModSettings.mod", "WE Add-on") + @runtimeProperty("ModSettings.category", "Weapon Twitch") + @runtimeProperty("ModSettings.displayName", "Moderate Twitch Threshold %") + @runtimeProperty("ModSettings.description", "Humanity % below which moderate weapon twitch kicks in") + @runtimeProperty("ModSettings.step", "5") + @runtimeProperty("ModSettings.min", "5") + @runtimeProperty("ModSettings.max", "60") + public let twitchThresholdModerate: Int32 = 25; + + // severe twitch reuses the base mod's psychosis threshold + @runtimeProperty("ModSettings.mod", "WE Add-on") + @runtimeProperty("ModSettings.category", "Neuroblockers") + @runtimeProperty("ModSettings.displayName", "Tier-Based Effectiveness") + @runtimeProperty("ModSettings.description", "Neuroblockers reduce humanity damage instead of blocking it - effectiveness depends on quality tier") + public let neuroEnabled: Bool = true; + + @runtimeProperty("ModSettings.mod", "WE Add-on") + @runtimeProperty("ModSettings.category", "Neuroblockers") + @runtimeProperty("ModSettings.displayName", "Common Reduction %") + @runtimeProperty("ModSettings.description", "Damage reduction from Common neuroblockers") + @runtimeProperty("ModSettings.step", "5") + @runtimeProperty("ModSettings.min", "0") + @runtimeProperty("ModSettings.max", "100") + public let neuroReductionCommon: Int32 = 40; + + @runtimeProperty("ModSettings.mod", "WE Add-on") + @runtimeProperty("ModSettings.category", "Neuroblockers") + @runtimeProperty("ModSettings.displayName", "Uncommon Reduction %") + @runtimeProperty("ModSettings.description", "Damage reduction from Uncommon neuroblockers") + @runtimeProperty("ModSettings.step", "5") + @runtimeProperty("ModSettings.min", "0") + @runtimeProperty("ModSettings.max", "100") + public let neuroReductionUncommon: Int32 = 60; + + @runtimeProperty("ModSettings.mod", "WE Add-on") + @runtimeProperty("ModSettings.category", "Neuroblockers") + @runtimeProperty("ModSettings.displayName", "Rare Reduction %") + @runtimeProperty("ModSettings.description", "Damage reduction from Rare neuroblockers - the best you'll find in Night City") + @runtimeProperty("ModSettings.step", "5") + @runtimeProperty("ModSettings.min", "0") + @runtimeProperty("ModSettings.max", "100") + public let neuroReductionRare: Int32 = 80; + + @runtimeProperty("ModSettings.mod", "WE Add-on") + @runtimeProperty("ModSettings.category", "Psychosis Hostility") + @runtimeProperty("ModSettings.displayName", "NPCs Fear Cyberpsycho") + @runtimeProperty("ModSettings.description", "During psychosis, nearby NPCs are hit with combat stimuli - everyone panics and fights back. Hardcore vibes.") + public let psychosisHostilityEnabled: Bool = false; + + @runtimeProperty("ModSettings.mod", "WE Add-on") + @runtimeProperty("ModSettings.category", "Psychosis Hostility") + @runtimeProperty("ModSettings.displayName", "Pulse Radius") + @runtimeProperty("ModSettings.description", "How far the hostility broadcast reaches (meters)") + @runtimeProperty("ModSettings.step", "5") + @runtimeProperty("ModSettings.min", "10") + @runtimeProperty("ModSettings.max", "100") + public let psychosisHostilityRadius: Int32 = 60; + + @runtimeProperty("ModSettings.mod", "WE Add-on") + @runtimeProperty("ModSettings.category", "Cigarette Humanity") + @runtimeProperty("ModSettings.displayName", "Enable Cigarette Recovery") + @runtimeProperty("ModSettings.description", "Smoking a cigarette restores a small amount of humanity - take a drag, feel human again") + public let smokeEnabled: Bool = true; + + @runtimeProperty("ModSettings.mod", "WE Add-on") + @runtimeProperty("ModSettings.category", "Cigarette Humanity") + @runtimeProperty("ModSettings.displayName", "Humanity Per Cigarette") + @runtimeProperty("ModSettings.step", "1") + @runtimeProperty("ModSettings.min", "1") + @runtimeProperty("ModSettings.max", "15") + public let smokeRestoreAmount: Int32 = 5; + + @runtimeProperty("ModSettings.mod", "WE Add-on") + @runtimeProperty("ModSettings.category", "Cigarette Humanity") + @runtimeProperty("ModSettings.displayName", "Daily Smoke Limit") + @runtimeProperty("ModSettings.description", "Max cigarettes per day that restore humanity (diminishing returns and all that)") + @runtimeProperty("ModSettings.step", "1") + @runtimeProperty("ModSettings.min", "1") + @runtimeProperty("ModSettings.max", "10") + public let smokeDailyLimit: Int32 = 4; + + public static func Get() -> ref { + return new WEAConfig(); + } +} + +@if(!ModuleExists("ModSettingsModule")) +public class WEAConfig { + + public let sleepVerificationEnabled: Bool = true; + public let sleepRecoveryCap: Int32 = 100; + public let sleepScalingEnabled: Bool = false; + public let sleepScalingFullHours: Int32 = 8; + public let hideHumanityMessages: Bool = false; + + public let twitchEnabled: Bool = true; + public let twitchThresholdMild: Int32 = 40; + public let twitchThresholdModerate: Int32 = 25; + + public let neuroEnabled: Bool = true; + public let neuroReductionCommon: Int32 = 40; + public let neuroReductionUncommon: Int32 = 60; + public let neuroReductionRare: Int32 = 80; + + public let psychosisHostilityEnabled: Bool = false; + public let psychosisHostilityRadius: Int32 = 60; + + public let smokeEnabled: Bool = true; + public let smokeRestoreAmount: Int32 = 5; + public let smokeDailyLimit: Int32 = 4; + + public static func Get() -> ref { + return new WEAConfig(); + } +} diff --git a/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Neuro.reds b/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Neuro.reds new file mode 100644 index 00000000..d93d5735 --- /dev/null +++ b/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Neuro.reds @@ -0,0 +1,67 @@ +// Wannabe Edgerunner Add-on - neuroblocker quality tracker (;¬_¬) +// watches for neuroblocker status effects applying/removing so we know which tier is active + +module Edgerunning.System +import Edgerunning.Common.* + +@wrapMethod(PlayerPuppet) +protected cb func OnStatusEffectApplied(evt: ref) -> Bool { + let result = wrappedMethod(evt); + let effectId: TweakDBID = evt.staticData.GetID(); + WEA_OnNeuroBlockerApplied(this, effectId); + return result; +} + +@wrapMethod(PlayerPuppet) +protected cb func OnStatusEffectRemoved(evt: ref) -> Bool { + let result = wrappedMethod(evt); + let effectId: TweakDBID = evt.staticData.GetID(); + WEA_OnNeuroBlockerRemoved(this, effectId); + return result; +} + +// stash reduction % on the system - AddHumanityDamage picks it up (ง •̀_•́)ง +public static func WEA_OnNeuroBlockerApplied(player: ref, effectId: TweakDBID) -> Void { + if !WEA_IsNeuroBlockerEffect(effectId) { return; }; + + let config = WEAConfig.Get(); + if !config.neuroEnabled { return; }; + + let sys = EdgerunningSystem.GetInstance(player.GetGame()); + if !IsDefined(sys) { return; }; + + // map the specific tweakDB id → config percent. base game has Common/Uncommon/the unnamed "Rare" one + let reduction: Float; + if Equals(effectId, t"BaseStatusEffect.RipperDocMedBuffCommon") { + reduction = Cast(config.neuroReductionCommon) / 100.0; + E(s"! WEA Neuro: Common neuroblocker applied, \(config.neuroReductionCommon)% reduction"); + } else { + if Equals(effectId, t"BaseStatusEffect.RipperDocMedBuffUncommon") { + reduction = Cast(config.neuroReductionUncommon) / 100.0; + E(s"! WEA Neuro: Uncommon neuroblocker applied, \(config.neuroReductionUncommon)% reduction"); + } else { + reduction = Cast(config.neuroReductionRare) / 100.0; + E(s"! WEA Neuro: Rare neuroblocker applied, \(config.neuroReductionRare)% reduction"); + }; + }; + + sys.m_weaNeuroReduction = reduction; +} + +// neuroblocker wore off (。•́︿•̀。) +public static func WEA_OnNeuroBlockerRemoved(player: ref, effectId: TweakDBID) -> Void { + if !WEA_IsNeuroBlockerEffect(effectId) { return; }; + + let sys = EdgerunningSystem.GetInstance(player.GetGame()); + if !IsDefined(sys) { return; }; + + sys.m_weaNeuroReduction = 0.0; + E("! WEA Neuro: neuroblocker wore off, full damage restored"); +} + +// the three known neuroblocker buffs. the unnamed "Rare" one is just `RipperDocMedBuff` (no suffix) +public static func WEA_IsNeuroBlockerEffect(effectId: TweakDBID) -> Bool { + return Equals(effectId, t"BaseStatusEffect.RipperDocMedBuff") + || Equals(effectId, t"BaseStatusEffect.RipperDocMedBuffUncommon") + || Equals(effectId, t"BaseStatusEffect.RipperDocMedBuffCommon"); +} diff --git a/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Psychosis.reds b/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Psychosis.reds new file mode 100644 index 00000000..2433c3d2 --- /dev/null +++ b/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Psychosis.reds @@ -0,0 +1,24 @@ +// Wannabe Edgerunner Add-on - hostility pulse callback (╯°□°)╯︵ ┻━┻ + +module Edgerunning.System + +// 1s repeating callback - broadcasts combat stims during psychosis +public class WEA_HostilityPulseCallback extends DelayCallback { + let m_systemRef: wref; + let m_radius: Float; + + public func Call() -> Void { + if !IsDefined(this.m_systemRef) { return; }; + + let player = GetPlayer(GetGameInstance()); + if !IsDefined(player) { return; }; + + let broadcaster = player.GetStimBroadcasterComponent(); + if !IsDefined(broadcaster) { return; }; + + broadcaster.TriggerSingleBroadcast(player, gamedataStimType.Combat, this.m_radius); + broadcaster.TriggerSingleBroadcast(player, gamedataStimType.Gunshot, this.m_radius); + broadcaster.TriggerSingleBroadcast(player, gamedataStimType.Explosion, this.m_radius); + broadcaster.TriggerSingleBroadcast(player, gamedataStimType.CombatCall, this.m_radius); + } +} diff --git a/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Sleep.reds b/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Sleep.reds new file mode 100644 index 00000000..afdecfc8 --- /dev/null +++ b/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Sleep.reds @@ -0,0 +1,28 @@ +// Wannabe Edgerunner Add-on - sleep verification callback (¬_¬) +// "did you actually sleep or just tap the bed?" - we find out 10 real seconds later + +module Edgerunning.System +import Edgerunning.Common.* + +// 10s delay - checks if 6+ game-hours passed (actual sleep vs bed cancel) (¬ᗜ¬) +public class WEA_SleepVerifyCallback extends DelayCallback { + let m_systemRef: wref; + + public func Call() -> Void { + if !IsDefined(this.m_systemRef) { return; }; + if !this.m_systemRef.m_weaSleepPending { return; }; // pending flag got cleared elsewhere, we're stale + + this.m_systemRef.m_weaSleepPending = false; + + let gi = GetGameInstance(); + let currentTime = GameInstance.GetTimeSystem(gi).GetGameTimeStamp(); + let elapsed = currentTime - this.m_systemRef.m_weaSleepCheckTime; + + // 6 in-game hours = 21600 game-seconds. anything less = they bailed on the sleep ui + if elapsed >= 21600.0 { + // flip the bypass flag and re-fire OnRestoreAction - this time the gate lets it through + this.m_systemRef.m_weaSleepBypass = true; + this.m_systemRef.OnRestoreAction(HumanityRestoringAction.Sleep); + }; + } +} diff --git a/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Smoke.reds b/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Smoke.reds new file mode 100644 index 00000000..69f6cce0 --- /dev/null +++ b/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Smoke.reds @@ -0,0 +1,80 @@ +// Wannabe Edgerunner Add-on - cigarette humanity (ー_ー)旦~~ +// compatible with vanilla, FAC, Consumable Animations, Dark Future smoking items + +module Edgerunning.System +import Edgerunning.Common.* + +// 5-param overload +@wrapMethod(ItemActionsHelper) +public final static func ProcessItemAction(gi: GameInstance, executor: wref, itemData: wref, actionID: TweakDBID, fromInventory: Bool) -> Bool { + let result = wrappedMethod(gi, executor, itemData, actionID, fromInventory); + if result && IsDefined(itemData) { + WEA_TrySmokeCigarette(gi, executor, itemData, actionID); + }; + return result; +} + +// 6-param overload with quantity +@wrapMethod(ItemActionsHelper) +public final static func ProcessItemAction(gi: GameInstance, executor: wref, itemData: wref, actionID: TweakDBID, fromInventory: Bool, quantity: Int32) -> Bool { + let result = wrappedMethod(gi, executor, itemData, actionID, fromInventory, quantity); + if result && IsDefined(itemData) { + WEA_TrySmokeCigarette(gi, executor, itemData, actionID); + }; + return result; +} + +public static func WEA_TrySmokeCigarette(gi: GameInstance, executor: wref, itemData: wref, actionID: TweakDBID) -> Void { + let player = executor as PlayerPuppet; + if !IsDefined(player) { return; }; + + let config = WEAConfig.Get(); + if !config.smokeEnabled { return; }; + + // only Consume/Eat/Drink actions + let actionRecord = TweakDBInterface.GetObjectActionRecord(actionID); + if !IsDefined(actionRecord) { return; }; + let actionType = actionRecord.ActionName(); + if !Equals(actionType, n"Consume") && !Equals(actionType, n"Eat") && !Equals(actionType, n"Drink") { + return; + }; + + let itemRecord = TweakDBInterface.GetItemRecord(itemData.GetID().GetTDBID()); + if !IsDefined(itemRecord) { return; }; + if !WEA_IsCigarette(itemRecord) { return; }; + + let sys = EdgerunningSystem.GetInstance(gi); + if !IsDefined(sys) { return; }; + + let today = WEA_GetGameDay(gi); + let lastDay = sys.WEA_GetSmokeLastDay(); + + // new day resets counter + if today != lastDay { + sys.WEA_SetSmokeCount(0); + sys.WEA_SetSmokeLastDay(today); + }; + + let smokeCount = sys.WEA_GetSmokeCount(); + if smokeCount >= config.smokeDailyLimit { + return; + }; + + sys.WEA_SetSmokeCount(smokeCount + 1); + sys.RemoveHumanityDamage(config.smokeRestoreAmount); + E("! Smoked a cigarette, humanity restored (" + ToString(smokeCount + 1) + "/" + ToString(config.smokeDailyLimit) + ")"); +} + +// checks multiple signals for mod compatibility +public static func WEA_IsCigarette(record: ref) -> Bool { + if record.TagsContains(n"DarkFutureAnimSmoking") { return true; }; + if record.TagsContains(n"Smoking") { return true; }; + if record.TagsContains(n"Cigarette") { return true; }; + + let recordName = TDBID.ToStringDEBUG(record.GetID()); + if StrContains(recordName, "Cigarette") { return true; }; + if StrContains(recordName, "cigarette") { return true; }; + if StrContains(recordName, "Cigar") { return true; }; + + return false; +} diff --git a/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Utils.reds b/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Utils.reds new file mode 100644 index 00000000..41fa44d0 --- /dev/null +++ b/src/Wannabe Edgerunner/r6/scripts/WannabeEdgerunnerAddon/WEA_Utils.reds @@ -0,0 +1,9 @@ +// Wannabe Edgerunner Add-on - module-level helper ╰(*°▽°*)╯ + +module Edgerunning.System + +// what game-day is it - used by the cigarette counter to know when to reset +public static func WEA_GetGameDay(gi: GameInstance) -> Int32 { + let gameTime = GameInstance.GetTimeSystem(gi).GetGameTime(); + return GameTime.Days(gameTime); +} diff --git a/src/Wannabe Edgerunner/r6/tweaks/WannabeEdgerunnerAddon/WEA_StatusEffects.yaml b/src/Wannabe Edgerunner/r6/tweaks/WannabeEdgerunnerAddon/WEA_StatusEffects.yaml new file mode 100644 index 00000000..3f7c8e54 --- /dev/null +++ b/src/Wannabe Edgerunner/r6/tweaks/WannabeEdgerunnerAddon/WEA_StatusEffects.yaml @@ -0,0 +1,323 @@ +# Wannabe Edgerunner Add-on - weapon sway status effects +# Three tiers of hand tremor: fast micro-twitches that get worse as humanity drops. +# The key: very low SwayTraversalTime (fast) + small SwaySideMax (tiny amplitude) = trembling hands + +# ═════════════════════════════════════════════════════════════════ +# Shared records +# ═════════════════════════════════════════════════════════════════ + +WEA_StatusEffect.SingleStack: + $type: gamedataStatModifierGroup_Record + drawBasedOnStatType: False + optimiseCombinedModifiers: False + saveBasedOnStatType: False + statModsLimit: -1 + relatedModifierGroups: [] + statModifiers: + - $type: gamedataConstantStatModifier_Record + statType: BaseStats.MaxStacks + modifierType: Additive + value: 1 + +# ═════════════════════════════════════════════════════════════════ +# TIER 1 - Mild tremor (below 40% humanity) +# Barely noticeable, just enough to make you uneasy +# ═════════════════════════════════════════════════════════════════ + +WEA_StatusEffect.Twitch_01_Mod_TraversalTime: + $type: gamedataConstantStatModifier_Record + value: 0.012 + modifierType: Multiplier + statType: BaseStats.SwayTraversalTime + +WEA_StatusEffect.Twitch_01_Mod_StartDelay: + $type: gamedataConstantStatModifier_Record + value: 0 + modifierType: Multiplier + statType: BaseStats.SwayStartDelay + +WEA_StatusEffect.Twitch_01_Mod_BlendTime: + $type: gamedataConstantStatModifier_Record + value: 0 + modifierType: Multiplier + statType: BaseStats.SwayStartBlendTime + +WEA_StatusEffect.Twitch_01_Mod_AllowSway: + $type: gamedataConstantStatModifier_Record + value: 1 + modifierType: Additive + statType: BaseStats.RecoilAllowSway + +WEA_StatusEffect.Twitch_01_Mod_SideMax: + $type: gamedataConstantStatModifier_Record + value: 0.012 + modifierType: Multiplier + statType: BaseStats.SwaySideMaximumAngleDistance + +WEA_StatusEffect.Twitch_01_Mod_SideMin: + $type: gamedataConstantStatModifier_Record + value: 0.012 + modifierType: Multiplier + statType: BaseStats.SwaySideMinimumAngleDistance + +WEA_StatusEffect.Twitch_01_StatGroup: + $type: gamedataStatModifierGroup_Record + drawBasedOnStatType: False + optimiseCombinedModifiers: False + saveBasedOnStatType: False + statModsLimit: -1 + relatedModifierGroups: [] + statModifiers: + - WEA_StatusEffect.Twitch_01_Mod_TraversalTime + - WEA_StatusEffect.Twitch_01_Mod_StartDelay + - WEA_StatusEffect.Twitch_01_Mod_BlendTime + - WEA_StatusEffect.Twitch_01_Mod_AllowSway + - WEA_StatusEffect.Twitch_01_Mod_SideMax + - WEA_StatusEffect.Twitch_01_Mod_SideMin + +WEA_StatusEffect.Twitch_01_Effector: + $type: gamedataApplyStatGroupEffector_Record + applicationTarget: Weapon + removeWithEffector: True + statGroup: WEA_StatusEffect.Twitch_01_StatGroup + effectorClassName: ApplyStatGroupEffector + prereqRecord: Prereqs.AnyWeaponHeldPrereq + removeAfterActionCall: False + removeAfterPrereqCheck: False + statModifierGroups: [] + reapplyOnWeaponChange: False + +WEA_StatusEffect.Twitch_01_Package: + $type: gamedataGameplayLogicPackage_Record + stackable: False + animationWrapperOverrides: [] + effectors: + - WEA_StatusEffect.Twitch_01_Effector + items: [] + statPools: [] + stats: [] + +WEA_StatusEffect.Twitch_01: + $type: gamedataStatusEffect_Record + duration: BaseStats.InfiniteDuration + dynamicDuration: False + isAffectedByTimeDilationNPC: True + isAffectedByTimeDilationPlayer: True + maxStacks: WEA_StatusEffect.SingleStack + reapplyPackagesOnMaxStacks: False + removeAllStacksWhenDurationEnds: False + removeOnStoryTier: False + replicated: True + savable: True + stopActiveSfxOnDeactivate: True + debugTags: [] + gameplayTags: + - Debuff + - WEA_HumanityTwitch + immunityStats: [] + packages: + - WEA_StatusEffect.Twitch_01_Package + SFX: [] + VFX: [] + +# ═════════════════════════════════════════════════════════════════ +# TIER 2 - Moderate tremor (below 25% humanity) +# Hands are definitely shaking now. Getting harder to aim. +# ═════════════════════════════════════════════════════════════════ + +WEA_StatusEffect.Twitch_02_Mod_TraversalTime: + $type: gamedataConstantStatModifier_Record + value: 0.008 + modifierType: Multiplier + statType: BaseStats.SwayTraversalTime + +WEA_StatusEffect.Twitch_02_Mod_StartDelay: + $type: gamedataConstantStatModifier_Record + value: 0 + modifierType: Multiplier + statType: BaseStats.SwayStartDelay + +WEA_StatusEffect.Twitch_02_Mod_BlendTime: + $type: gamedataConstantStatModifier_Record + value: 0 + modifierType: Multiplier + statType: BaseStats.SwayStartBlendTime + +WEA_StatusEffect.Twitch_02_Mod_AllowSway: + $type: gamedataConstantStatModifier_Record + value: 1 + modifierType: Additive + statType: BaseStats.RecoilAllowSway + +WEA_StatusEffect.Twitch_02_Mod_SideMax: + $type: gamedataConstantStatModifier_Record + value: 0.022 + modifierType: Multiplier + statType: BaseStats.SwaySideMaximumAngleDistance + +WEA_StatusEffect.Twitch_02_Mod_SideMin: + $type: gamedataConstantStatModifier_Record + value: 0.022 + modifierType: Multiplier + statType: BaseStats.SwaySideMinimumAngleDistance + +WEA_StatusEffect.Twitch_02_StatGroup: + $type: gamedataStatModifierGroup_Record + drawBasedOnStatType: False + optimiseCombinedModifiers: False + saveBasedOnStatType: False + statModsLimit: -1 + relatedModifierGroups: [] + statModifiers: + - WEA_StatusEffect.Twitch_02_Mod_TraversalTime + - WEA_StatusEffect.Twitch_02_Mod_StartDelay + - WEA_StatusEffect.Twitch_02_Mod_BlendTime + - WEA_StatusEffect.Twitch_02_Mod_AllowSway + - WEA_StatusEffect.Twitch_02_Mod_SideMax + - WEA_StatusEffect.Twitch_02_Mod_SideMin + +WEA_StatusEffect.Twitch_02_Effector: + $type: gamedataApplyStatGroupEffector_Record + applicationTarget: Weapon + removeWithEffector: True + statGroup: WEA_StatusEffect.Twitch_02_StatGroup + effectorClassName: ApplyStatGroupEffector + prereqRecord: Prereqs.AnyWeaponHeldPrereq + removeAfterActionCall: False + removeAfterPrereqCheck: False + statModifierGroups: [] + reapplyOnWeaponChange: False + +WEA_StatusEffect.Twitch_02_Package: + $type: gamedataGameplayLogicPackage_Record + stackable: False + animationWrapperOverrides: [] + effectors: + - WEA_StatusEffect.Twitch_02_Effector + items: [] + statPools: [] + stats: [] + +WEA_StatusEffect.Twitch_02: + $type: gamedataStatusEffect_Record + duration: BaseStats.InfiniteDuration + dynamicDuration: False + isAffectedByTimeDilationNPC: True + isAffectedByTimeDilationPlayer: True + maxStacks: WEA_StatusEffect.SingleStack + reapplyPackagesOnMaxStacks: False + removeAllStacksWhenDurationEnds: False + removeOnStoryTier: False + replicated: True + savable: True + stopActiveSfxOnDeactivate: True + debugTags: [] + gameplayTags: + - Debuff + - WEA_HumanityTwitch + immunityStats: [] + packages: + - WEA_StatusEffect.Twitch_02_Package + SFX: [] + VFX: [] + +# ═════════════════════════════════════════════════════════════════ +# TIER 3 - Severe tremor (at/below psychosis threshold) +# You're barely holding it together. The crosshair dances. +# ═════════════════════════════════════════════════════════════════ + +WEA_StatusEffect.Twitch_03_Mod_TraversalTime: + $type: gamedataConstantStatModifier_Record + value: 0.005 + modifierType: Multiplier + statType: BaseStats.SwayTraversalTime + +WEA_StatusEffect.Twitch_03_Mod_StartDelay: + $type: gamedataConstantStatModifier_Record + value: 0 + modifierType: Multiplier + statType: BaseStats.SwayStartDelay + +WEA_StatusEffect.Twitch_03_Mod_BlendTime: + $type: gamedataConstantStatModifier_Record + value: 0 + modifierType: Multiplier + statType: BaseStats.SwayStartBlendTime + +WEA_StatusEffect.Twitch_03_Mod_AllowSway: + $type: gamedataConstantStatModifier_Record + value: 1 + modifierType: Additive + statType: BaseStats.RecoilAllowSway + +WEA_StatusEffect.Twitch_03_Mod_SideMax: + $type: gamedataConstantStatModifier_Record + value: 0.038 + modifierType: Multiplier + statType: BaseStats.SwaySideMaximumAngleDistance + +WEA_StatusEffect.Twitch_03_Mod_SideMin: + $type: gamedataConstantStatModifier_Record + value: 0.038 + modifierType: Multiplier + statType: BaseStats.SwaySideMinimumAngleDistance + +WEA_StatusEffect.Twitch_03_StatGroup: + $type: gamedataStatModifierGroup_Record + drawBasedOnStatType: False + optimiseCombinedModifiers: False + saveBasedOnStatType: False + statModsLimit: -1 + relatedModifierGroups: [] + statModifiers: + - WEA_StatusEffect.Twitch_03_Mod_TraversalTime + - WEA_StatusEffect.Twitch_03_Mod_StartDelay + - WEA_StatusEffect.Twitch_03_Mod_BlendTime + - WEA_StatusEffect.Twitch_03_Mod_AllowSway + - WEA_StatusEffect.Twitch_03_Mod_SideMax + - WEA_StatusEffect.Twitch_03_Mod_SideMin + +WEA_StatusEffect.Twitch_03_Effector: + $type: gamedataApplyStatGroupEffector_Record + applicationTarget: Weapon + removeWithEffector: True + statGroup: WEA_StatusEffect.Twitch_03_StatGroup + effectorClassName: ApplyStatGroupEffector + prereqRecord: Prereqs.AnyWeaponHeldPrereq + removeAfterActionCall: False + removeAfterPrereqCheck: False + statModifierGroups: [] + reapplyOnWeaponChange: False + +WEA_StatusEffect.Twitch_03_Package: + $type: gamedataGameplayLogicPackage_Record + stackable: False + animationWrapperOverrides: [] + effectors: + - WEA_StatusEffect.Twitch_03_Effector + items: [] + statPools: [] + stats: [] + +WEA_StatusEffect.Twitch_03: + $type: gamedataStatusEffect_Record + duration: BaseStats.InfiniteDuration + dynamicDuration: False + isAffectedByTimeDilationNPC: True + isAffectedByTimeDilationPlayer: True + maxStacks: WEA_StatusEffect.SingleStack + reapplyPackagesOnMaxStacks: False + removeAllStacksWhenDurationEnds: False + removeOnStoryTier: False + replicated: True + savable: True + stopActiveSfxOnDeactivate: True + debugTags: [] + gameplayTags: + - Debuff + - WEA_HumanityTwitch + immunityStats: [] + packages: + - WEA_StatusEffect.Twitch_03_Package + SFX: [] + VFX: []