diff --git a/sources/Events/Broadcast/BroadcastEventsObserver.uc b/sources/Events/Broadcast/BroadcastEventsObserver.uc index ddd2c8f..989c478 100644 --- a/sources/Events/Broadcast/BroadcastEventsObserver.uc +++ b/sources/Events/Broadcast/BroadcastEventsObserver.uc @@ -1,7 +1,9 @@ /** - * 'BroadcastHandler' class that used by Acedia to catch + * `BroadcastHandler` class that used by Acedia to catch * broadcasting events. For Acedia to work properly it needs to be added to * the very beginning of the broadcast handlers' chain. + * However, for compatibility reasons Acedia also supports less invasive + * methods to add it at the cost of some functionality degradation. * Copyright 2020 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. @@ -36,32 +38,138 @@ enum InjectionLevel // effectively disable `BroadcastEvents`. BHIJ_None, // `BroadcastEventsObserver` will be places in the broadcast handlers' - // chain as a normal `BroadcastHandler`, which can lead to incorrect + // chain as a normal `BroadcastHandler` + // (through `RegisterBroadcastHandler()` call), which can lead to incorrect // handling of `HandleText` and `HandleLocalized` events. BHIJ_Registered, // `BroadcastEventsObserver` will be injected at the very beginning of // the broadcast handlers' chain. - // This provides full Acedia's functionality. + // This option provides full Acedia's functionality. BHIJ_Root }; var public config const InjectionLevel usedInjectionLevel; -// The way vanilla 'BroadcastHandler' works - it can check if broadcast is +// The way vanilla `BroadcastHandler` works - it can check if broadcast is // possible for any actor, but for actually sending the text messages it will -// try to extract player's data from it -// and will simply pass 'none' if it can't. -// We remember senders in this array in order to pass real ones to our events. -// Array instead of variable is to account for folded calls -// (when handling of broadcast events leads to another message generation). +// try to extract player's data from it and will simply pass `none` for +// a sender if it can't. +// We remember senders in this array in order to pass real ones to +// our events. +// We use an array instead of a single variable is to account for possible +// folded calls (when handling of broadcast events leads to another +// message generation). +// This is only relevant for `BHIJ_Root` injection level. var private array storedSenders; // We want to insert our code in some of the functions between -// 'AllowsBroadcast' check and actual broadcasting, -// so we can't just use a 'super.AllowsBroadcast()' call. -// Instead we first manually do this check, then perform our logic and then -// make a super call, but with 'blockAllowsBroadcast' flag set to 'true', -// which causes overloaded 'AllowsBroadcast()' to omit actual checks. +// `AllowsBroadcast` check and actual broadcasting, +// so we can't just use a `super.AllowsBroadcast()` call. +// Instead we first manually do this check, then perform our logic and then +// make a super call, but with `blockAllowsBroadcast` flag set to `true`, +// which causes overloaded `AllowsBroadcast()` to omit checks that we've +// already performed. var private bool blockAllowsBroadcast; +/* + * In case of `BHIJ_Registered` injection level, we do not get notified + * when a message starts getting broadcasted through `Broadcast()`, + * `BroadcastTeam()` and `AcceptBroadcastLocalized()`. + * Instead we are only notified when a message is broadcasted to + * a particular player, so with 2 players instead of sequence `Broadcast()`, + * `AcceptBroadcastText()`, `AcceptBroadcastText()` + * we get `AcceptBroadcastText()`, `AcceptBroadcastText()`. + * This means that we can only guess when new broadcast was initiated. + * We do this by: + * 1. Recording broadcast instigator (sender) and his message. If any of + * these variables change - we assume it's a new broadcast. + * 2. Recording players that already received that message, - if message is + * resend to one of them - it's a new broadcast + * (of possibly duplicate message). + * 3. All broadcasted messages are sent to all players within 1 tick, so + * any first message within each tick is a start of a new broadcast. + * + * Check logic is implemented in `IsFromNewTextBroadcast()` and + * `IsFromNewLocalizedBroadcast()` methods. + */ +// Are we already already tracking any broadcast? Helps to track for point 3. +var private bool trackingBroadcast; +// Sender of the current broadcast. Helps to track for point 1. +var private Actor currentBroadcastInstigator; +// Players that already received current broadcast. Helps to track for point 2. +var private array currentBroadcastReceivers; +// Is current broadcast sending a +// text message (`Broadcast()` and `BroadcastTeam()`) +// or localized message (`AcceptBroadcastLocalized()`)? +// Helps to track message for point 1. +var private bool broadcastingLocalizedMessage; +// Variables to stored text message. Helps to track for point 1. +var private string currentTextMessageContent; +var private name currentTextMessageType; +// Variables to stored localized message. Helps to track for point 1. +var private BroadcastEvents.LocalizedMessage currentLocalizedMessage; + +private function bool IsCurrentBroadcastReceiver(PlayerController receiver) +{ + local int i; + for (i = 0; i < currentBroadcastReceivers.length; i += 1) + { + if (currentBroadcastReceivers[i] == receiver) { + return true; + } + } + return false; +} + +private function bool IsFromNewTextBroadcast( + PlayerReplicationInfo senderPRI, + PlayerController receiver, + string message, + name messageType) +{ + local bool isCurrentBroadcastContinuation; + if (usedInjectionLevel != BHIJ_Registered) return false; + + isCurrentBroadcastContinuation = trackingBroadcast + && (senderPRI == currentBroadcastInstigator) + && (!broadcastingLocalizedMessage) + && (message == currentTextMessageContent) + && (currentTextMessageType == currentTextMessageType) + && !IsCurrentBroadcastReceiver(receiver); + if (isCurrentBroadcastContinuation) { + return false; + } + trackingBroadcast = true; + broadcastingLocalizedMessage = false; + currentBroadcastInstigator = senderPRI; + currentTextMessageContent = message; + currentTextMessageType = messageType; + currentBroadcastReceivers.length = 0; + return true; +} + +private function bool IsFromNewLocalizedBroadcast( + Actor sender, + PlayerController receiver, + BroadcastEvents.LocalizedMessage localizedMessage) +{ + local bool isCurrentBroadcastContinuation; + if (usedInjectionLevel != BHIJ_Registered) return false; + + isCurrentBroadcastContinuation = trackingBroadcast + && (sender == currentBroadcastInstigator) + && (broadcastingLocalizedMessage) + && (localizedMessage == currentLocalizedMessage) + && !IsCurrentBroadcastReceiver(receiver); + if (isCurrentBroadcastContinuation) { + return false; + } + trackingBroadcast = true; + broadcastingLocalizedMessage = true; + currentBroadcastInstigator = sender; + currentLocalizedMessage = localizedMessage; + currentBroadcastReceivers.length = 0; + return true; +} + // Functions below simply reroute vanilla's broadcast events to // Acedia's 'BroadcastEvents', while keeping original senders // and blocking 'AllowsBroadcast()' as described in comments for @@ -85,8 +193,7 @@ public function bool HandlerAllowsBroadcast(Actor broadcaster, int sentTextNum) function Broadcast(Actor sender, coerce string message, optional name type) { local bool canTryToBroadcast; - if (!AllowsBroadcast(sender, Len(message))) - return; + if (!AllowsBroadcast(sender, Len(message))) return; canTryToBroadcast = class'BroadcastEvents'.static .CallHandleText(sender, message, type); if (canTryToBroadcast) @@ -99,16 +206,14 @@ function Broadcast(Actor sender, coerce string message, optional name type) } } -function BroadcastTeam -( - Controller sender, - coerce string message, - optional name type +function BroadcastTeam( + Controller sender, + coerce string message, + optional name type ) { local bool canTryToBroadcast; - if (!AllowsBroadcast(sender, Len(message))) - return; + if (!AllowsBroadcast(sender, Len(message))) return; canTryToBroadcast = class'BroadcastEvents'.static .CallHandleText(sender, message, type); if (canTryToBroadcast) @@ -121,8 +226,7 @@ function BroadcastTeam } } -event AllowBroadcastLocalized -( +event AllowBroadcastLocalized( Actor sender, class message, optional int switch, @@ -133,8 +237,6 @@ event AllowBroadcastLocalized { local bool canTryToBroadcast; local BroadcastEvents.LocalizedMessage packedMessage; - if (!AllowsBroadcast(sender, Len(message))) - return; packedMessage.class = message; packedMessage.id = switch; packedMessage.relatedPRI1 = relatedPRI1; @@ -150,15 +252,14 @@ event AllowBroadcastLocalized } } -function bool AllowsBroadcast(actor broadcaster, int len) +function bool AllowsBroadcast(Actor broadcaster, int len) { if (blockAllowsBroadcast) return true; return super.AllowsBroadcast(broadcaster, len); } -function bool AcceptBroadcastText -( +function bool AcceptBroadcastText( PlayerController receiver, PlayerReplicationInfo senderPRI, out string message, @@ -167,26 +268,31 @@ function bool AcceptBroadcastText { local bool canBroadcast; local Actor sender; - if (senderPRI != none) - { + if (senderPRI != none) { sender = PlayerController(senderPRI.owner); } - if (sender == none && storedSenders.length > 0) - { + if (sender == none && storedSenders.length > 0) { sender = storedSenders[storedSenders.length - 1]; } + if (usedInjectionLevel == BHIJ_Registered) + { + if (IsFromNewTextBroadcast(senderPRI, receiver, message, type)) + { + class'BroadcastEvents'.static.CallHandleText(sender, message, type); + currentBroadcastReceivers.length = 0; + } + currentBroadcastReceivers[currentBroadcastReceivers.length] = receiver; + } canBroadcast = class'BroadcastEvents'.static .CallHandleTextFor(receiver, sender, message, type); - if (!canBroadcast) - { + if (!canBroadcast) { return false; } return super.AcceptBroadcastText(receiver, senderPRI, message, type); } -function bool AcceptBroadcastLocalized -( +function bool AcceptBroadcastLocalized( PlayerController receiver, Actor sender, class message, @@ -203,16 +309,31 @@ function bool AcceptBroadcastLocalized packedMessage.relatedPRI1 = relatedPRI1; packedMessage.relatedPRI2 = relatedPRI2; packedMessage.relatedObject = obj; + if (usedInjectionLevel == BHIJ_Registered) + { + if (IsFromNewLocalizedBroadcast(sender, receiver, packedMessage)) + { + class'BroadcastEvents'.static + .CallHandleLocalized(sender, packedMessage); + currentBroadcastReceivers.length = 0; + } + currentBroadcastReceivers[currentBroadcastReceivers.length] = receiver; + } canBroadcast = class'BroadcastEvents'.static .CallHandleLocalizedFor(receiver, sender, packedMessage); - if (!canBroadcast) - { + if (!canBroadcast) { return false; } return super.AcceptBroadcastLocalized( receiver, sender, message, switch, relatedPRI1, relatedPRI2, obj); } +event Tick(float delta) +{ + trackingBroadcast = false; + currentBroadcastReceivers.length = 0; +} + defaultproperties { blockAllowsBroadcast = false