Browse Source
I shamefully ended up doing another mega-commit, because a lot of things needed to be redone at once and it was easier that way on me. No one really consistently tracks what I'm doing with these commits anyway. This adds a whole bunch of code to deal with proper clean up for Acedia, so it doesn't crash on map change and also replaces old event/listener system with new signals/slots one.pull/8/head
Anton Tarasenko
3 years ago
81 changed files with 2726 additions and 1320 deletions
@ -1,66 +0,0 @@
|
||||
/** |
||||
* Overloaded broadcast events listener to catch commands input from |
||||
* the in-game chat. |
||||
* Copyright 2020 - 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class BroadcastListener_Commands extends BroadcastListenerBase |
||||
abstract; |
||||
|
||||
// TODO: reimplement with even to provide `APlayer` in the first place |
||||
static function bool HandleText( |
||||
Actor sender, |
||||
out string message, |
||||
optional name messageType) |
||||
{ |
||||
local Text messageAsText; |
||||
local APlayer callerPlayer; |
||||
local Parser parser; |
||||
local Commands_Feature commandFeature; |
||||
local PlayerService service; |
||||
// We only want to catch chat messages |
||||
// and only if `Commands` feature is active |
||||
if (messageType != 'Say') return true; |
||||
commandFeature = |
||||
Commands_Feature(class'Commands_Feature'.static.GetInstance()); |
||||
if (commandFeature == none) return true; |
||||
if (!commandFeature.UsingChatInput()) return true; |
||||
// We are only interested in messages that start with "!" |
||||
parser = __().text.ParseString(message); |
||||
if (!parser.Match(P("!")).Ok()) |
||||
{ |
||||
parser.FreeSelf(); |
||||
// Convert color tags into colors |
||||
messageAsText = __().text.FromFormattedString(message); |
||||
message = messageAsText.ToColoredString(,, __().color.White); |
||||
messageAsText.FreeSelf(); |
||||
return true; |
||||
} |
||||
// Extract `APlayer` from the `sender` |
||||
service = PlayerService(class'PlayerService'.static.Require()); |
||||
if (service != none) { |
||||
callerPlayer = service.GetPlayer(PlayerController(sender)); |
||||
} |
||||
// Pass input to command feature |
||||
commandFeature.HandleInput(parser, callerPlayer); |
||||
parser.FreeSelf(); |
||||
return false; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
@ -0,0 +1,72 @@
|
||||
/** |
||||
* Command for changing nickname of the player. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class ACommandTest extends Command; |
||||
|
||||
protected function BuildData(CommandDataBuilder builder) |
||||
{ |
||||
builder.Name(P("test")).Summary(P("Tests various stuff. Simply call it.")) |
||||
.OptionalParams() |
||||
.ParamText(P("option")); |
||||
} |
||||
|
||||
protected function Executed(CommandCall result) |
||||
{ |
||||
local Parser parser; |
||||
local AssociativeArray root; |
||||
/*local int i; |
||||
local WeaponLocker lol; |
||||
local array<WeaponLocker> aaa; |
||||
local Text message; |
||||
local Timer testTimer; |
||||
message = _.text.FromString("Is lobby?" @ _.kf.IsInLobby() @ |
||||
"Is pre game?" @ _.kf.IsInPreGame() @ |
||||
"Is trader?" @ _.kf.IsTraderActive() @ |
||||
"Is wave?" @ _.kf.IsWaveActive() @ |
||||
"Is finished?" @ _.kf.IsGameFinished() @ |
||||
"Is wipe?" @ _.kf.IsWipe()); |
||||
_.console.ForAll().WriteLine(message); |
||||
testTimer = Timer(_.memory.Allocate(class'Timer')); |
||||
testTimer.SetInterval(result.GetParameters().GetInt(P("add"))); |
||||
testTimer.Start(); |
||||
testTimer.OnElapsed(self).connect = OnTick; |
||||
testTimer.SetAutoReset(true); |
||||
for (i = 0; i < 100; i += 1) { |
||||
class'WeaponLocker'.default.bCollideWorld = false; |
||||
class'WeaponLocker'.default.bBlockActors = false; |
||||
lol = WeaponLocker(_.memory.Allocate(class'WeaponLocker')); |
||||
aaa[i] = lol; |
||||
Log("HUH" @ lol.Destroy()); |
||||
class'WeaponLocker'.default.bCollideWorld = true; |
||||
class'WeaponLocker'.default.bBlockActors = true; |
||||
} |
||||
for (i = 0; i < 100; i += 1) { |
||||
if (aaa[i] != none) |
||||
{ |
||||
Log("UMBRA" @ aaa[i]); |
||||
} |
||||
}*/ |
||||
parser = _.text.ParseString("{\"innerObject\":{\"my_bool\":true,\"array\":[\"Engine.Actor\",false,null,{\"something \\\"here\\\"\":\"yes\",\"maybe\":0.003},56.6],\"one more\":{\"nope\":324532,\"whatever\":false,\"o rly?\":\"ya rly\"},\"my_int\":-9823452},\"some_var\":-7.32,\"another_var\":\"aye!\"}"); |
||||
root = _.json.ParseObjectWith(parser); |
||||
result.GetCallerPlayer().Console().WriteLine(_.json.PrettyPrint(root)); |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
@ -1,134 +0,0 @@
|
||||
/** |
||||
* Event generator for events, related to broadcasting messages |
||||
* through standard Unreal Script means: |
||||
* 1. text messages, typed by a player; |
||||
* 2. localized messages, identified by a LocalMessage class and id. |
||||
* Allows to make decisions whether or not to propagate certain messages. |
||||
* Copyright 2020 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class BroadcastEvents extends Events |
||||
abstract; |
||||
|
||||
struct LocalizedMessage |
||||
{ |
||||
// Every localized message is described by a class and id. |
||||
// For example, consider 'KFMod.WaitingMessage': |
||||
// if passed 'id' is '1', |
||||
// then it's supposed to be a message about new wave, |
||||
// but if passed 'id' is '2', |
||||
// then it's about completing the wave. |
||||
var class<LocalMessage> class; |
||||
var int id; |
||||
// Localized messages in unreal script can be passed along with |
||||
// optional arguments, described by variables below. |
||||
var PlayerReplicationInfo relatedPRI1; |
||||
var PlayerReplicationInfo relatedPRI2; |
||||
var Object relatedObject; |
||||
}; |
||||
|
||||
static function bool CallCanBroadcast(Actor broadcaster, int recentSentTextSize) |
||||
{ |
||||
local int i; |
||||
local bool result; |
||||
local array< class<Listener> > listeners; |
||||
listeners = GetListeners(); |
||||
for (i = 0;i < listeners.length;i += 1) |
||||
{ |
||||
result = class<BroadcastListenerBase>(listeners[i]) |
||||
.static.CanBroadcast(broadcaster, recentSentTextSize); |
||||
if (!result) return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
static function bool CallHandleText( |
||||
Actor sender, |
||||
out string message, |
||||
name messageType) |
||||
{ |
||||
local int i; |
||||
local bool result; |
||||
local array< class<Listener> > listeners; |
||||
listeners = GetListeners(); |
||||
for (i = 0;i < listeners.length;i += 1) |
||||
{ |
||||
result = class<BroadcastListenerBase>(listeners[i]) |
||||
.static.HandleText(sender, message, messageType); |
||||
if (!result) return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
static function bool CallHandleTextFor( |
||||
PlayerController receiver, |
||||
Actor sender, |
||||
out string message, |
||||
name messageType) |
||||
{ |
||||
local int i; |
||||
local bool result; |
||||
local array< class<Listener> > listeners; |
||||
listeners = GetListeners(); |
||||
for (i = 0;i < listeners.length;i += 1) |
||||
{ |
||||
result = class<BroadcastListenerBase>(listeners[i]) |
||||
.static.HandleTextFor(receiver, sender, message, messageType); |
||||
if (!result) return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
static function bool CallHandleLocalized( |
||||
Actor sender, |
||||
LocalizedMessage message) |
||||
{ |
||||
local int i; |
||||
local bool result; |
||||
local array< class<Listener> > listeners; |
||||
listeners = GetListeners(); |
||||
for (i = 0;i < listeners.length;i += 1) |
||||
{ |
||||
result = class<BroadcastListenerBase>(listeners[i]) |
||||
.static.HandleLocalized(sender, message); |
||||
if (!result) return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
static function bool CallHandleLocalizedFor( |
||||
PlayerController receiver, |
||||
Actor sender, |
||||
LocalizedMessage message) |
||||
{ |
||||
local int i; |
||||
local bool result; |
||||
local array< class<Listener> > listeners; |
||||
listeners = GetListeners(); |
||||
for (i = 0;i < listeners.length;i += 1) |
||||
{ |
||||
result = class<BroadcastListenerBase>(listeners[i]) |
||||
.static.HandleLocalizedFor(receiver, sender, message); |
||||
if (!result) return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedListener = class'BroadcastListenerBase' |
||||
} |
@ -1,341 +0,0 @@
|
||||
/** |
||||
* `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. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class BroadcastEventsObserver extends Engine.BroadcastHandler |
||||
dependson(BroadcastEvents) |
||||
config(AcediaSystem); |
||||
|
||||
/** |
||||
* Forcing Acedia's own `BroadcastHandler` is rather invasive and might be |
||||
* undesired, since it can lead to incompatibilities with some mutators. |
||||
* To alleviate this issue Acedia allows server admins to control how it's |
||||
* `BroadcastHandler` is injected. Do note however that anything other than |
||||
* `BHIJ_Root` can lead to issues with Acedia's features. |
||||
*/ |
||||
enum InjectionLevel |
||||
{ |
||||
// `BroadcastEventsObserver` will not be added at all, which will |
||||
// effectively disable `BroadcastEvents`. |
||||
BHIJ_None, |
||||
// `BroadcastEventsObserver` will be places in the broadcast handlers' |
||||
// 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 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 |
||||
// 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` 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<Actor> 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 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<PlayerController> 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 |
||||
// 'storedSenders' and 'blockAllowsBroadcast'. |
||||
|
||||
public function bool HandlerAllowsBroadcast(Actor broadcaster, int sentTextNum) |
||||
{ |
||||
local bool canBroadcast; |
||||
// Check listeners |
||||
canBroadcast = class'BroadcastEvents'.static |
||||
.CallCanBroadcast(broadcaster, sentTextNum); |
||||
// Check other broadcast handlers (if present) |
||||
if (canBroadcast && nextBroadcastHandler != none) |
||||
{ |
||||
canBroadcast = nextBroadcastHandler |
||||
.HandlerAllowsBroadcast(broadcaster, sentTextNum); |
||||
} |
||||
return canBroadcast; |
||||
} |
||||
|
||||
function Broadcast(Actor sender, coerce string message, optional name type) |
||||
{ |
||||
local bool canTryToBroadcast; |
||||
if (!AllowsBroadcast(sender, Len(message))) return; |
||||
canTryToBroadcast = class'BroadcastEvents'.static |
||||
.CallHandleText(sender, message, type); |
||||
if (canTryToBroadcast) |
||||
{ |
||||
storedSenders[storedSenders.length] = sender; |
||||
blockAllowsBroadcast = true; |
||||
super.Broadcast(sender, message, type); |
||||
blockAllowsBroadcast = false; |
||||
storedSenders.length = storedSenders.length - 1; |
||||
} |
||||
} |
||||
|
||||
function BroadcastTeam( |
||||
Controller sender, |
||||
coerce string message, |
||||
optional name type |
||||
) |
||||
{ |
||||
local bool canTryToBroadcast; |
||||
if (!AllowsBroadcast(sender, Len(message))) return; |
||||
canTryToBroadcast = class'BroadcastEvents'.static |
||||
.CallHandleText(sender, message, type); |
||||
if (canTryToBroadcast) |
||||
{ |
||||
storedSenders[storedSenders.length] = sender; |
||||
blockAllowsBroadcast = true; |
||||
super.BroadcastTeam(sender, message, type); |
||||
blockAllowsBroadcast = false; |
||||
storedSenders.length = storedSenders.length - 1; |
||||
} |
||||
} |
||||
|
||||
event AllowBroadcastLocalized( |
||||
Actor sender, |
||||
class<LocalMessage> message, |
||||
optional int switch, |
||||
optional PlayerReplicationInfo relatedPRI1, |
||||
optional PlayerReplicationInfo relatedPRI2, |
||||
optional Object optionalObject |
||||
) |
||||
{ |
||||
local bool canTryToBroadcast; |
||||
local BroadcastEvents.LocalizedMessage packedMessage; |
||||
packedMessage.class = message; |
||||
packedMessage.id = switch; |
||||
packedMessage.relatedPRI1 = relatedPRI1; |
||||
packedMessage.relatedPRI2 = relatedPRI2; |
||||
packedMessage.relatedObject = optionalObject; |
||||
canTryToBroadcast = class'BroadcastEvents'.static |
||||
.CallHandleLocalized(sender, packedMessage); |
||||
if (canTryToBroadcast) |
||||
{ |
||||
super.AllowBroadcastLocalized( sender, message, switch, |
||||
relatedPRI1, relatedPRI2, |
||||
optionalObject); |
||||
} |
||||
} |
||||
|
||||
function bool AllowsBroadcast(Actor broadcaster, int len) |
||||
{ |
||||
if (blockAllowsBroadcast) |
||||
return true; |
||||
return super.AllowsBroadcast(broadcaster, len); |
||||
} |
||||
|
||||
function bool AcceptBroadcastText( |
||||
PlayerController receiver, |
||||
PlayerReplicationInfo senderPRI, |
||||
out string message, |
||||
optional name type |
||||
) |
||||
{ |
||||
local bool canBroadcast; |
||||
local Actor sender; |
||||
if (senderPRI != none) { |
||||
sender = PlayerController(senderPRI.owner); |
||||
} |
||||
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) { |
||||
return false; |
||||
} |
||||
return super.AcceptBroadcastText(receiver, senderPRI, message, type); |
||||
} |
||||
|
||||
|
||||
function bool AcceptBroadcastLocalized( |
||||
PlayerController receiver, |
||||
Actor sender, |
||||
class<LocalMessage> message, |
||||
optional int switch, |
||||
optional PlayerReplicationInfo relatedPRI1, |
||||
optional PlayerReplicationInfo relatedPRI2, |
||||
optional Object obj |
||||
) |
||||
{ |
||||
local bool canBroadcast; |
||||
local BroadcastEvents.LocalizedMessage packedMessage; |
||||
packedMessage.class = message; |
||||
packedMessage.id = switch; |
||||
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) { |
||||
return false; |
||||
} |
||||
return super.AcceptBroadcastLocalized( receiver, sender, message, switch, |
||||
relatedPRI1, relatedPRI2, obj); |
||||
} |
||||
|
||||
event Tick(float delta) |
||||
{ |
||||
trackingBroadcast = false; |
||||
currentBroadcastReceivers.length = 0; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
blockAllowsBroadcast = false |
||||
usedInjectionLevel = BHIJ_Root |
||||
} |
@ -1,175 +0,0 @@
|
||||
/** |
||||
* Listener for events, related to broadcasting messages |
||||
* through standard Unreal Script means: |
||||
* 1. text messages, typed by a player; |
||||
* 2. localized messages, identified by a LocalMessage class and id. |
||||
* Allows to make decisions whether or not to propagate certain messages. |
||||
* Copyright 2020 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class BroadcastListenerBase extends Listener |
||||
abstract; |
||||
|
||||
/** |
||||
* Helper function for extracting `PlayerController` of the `sender` Actor, |
||||
* if it has one / is one. |
||||
*/ |
||||
static public final function PlayerController GetController(Actor sender) |
||||
{ |
||||
local Pawn senderPawn; |
||||
senderPawn = Pawn(sender); |
||||
if (senderPawn != none) { |
||||
return PlayerController(senderPawn.controller); |
||||
} |
||||
return PlayerController(sender); |
||||
} |
||||
|
||||
/** |
||||
* This event is called whenever registered broadcast handlers are asked if |
||||
* they'd allow given actor ('broadcaster') to broadcast a text message. |
||||
* |
||||
* If injection level for Acedia's broadcast handler is `BHIJ_Root`, this event |
||||
* is guaranteed to be generated before any of the other `BroadcastHandler`s |
||||
* receive it. |
||||
* |
||||
* NOTE: this function is ONLY called when someone tries to |
||||
* broadcast TEXT messages. |
||||
* |
||||
* You can also reject a broadcast after looking at the message itself by |
||||
* using `HandleText()` event. |
||||
* |
||||
* @param broadcaster `Actor` that requested broadcast in question. |
||||
* @param recentSentTextSize Amount of recently broadcasted symbols of text |
||||
* by `broadcaster`. This value is periodically reset in 'GameInfo', |
||||
* by default should be each second. |
||||
* @return If one of the listeners returns 'false', - |
||||
* it will be treated just like one of broadcasters returning 'false' |
||||
* in 'AllowsBroadcast' and this method won't be called for remaining |
||||
* active listeners. Return `true` if you do not wish to block |
||||
* `broadcaster` from broadcasting his next message. |
||||
* By default returns `true`. |
||||
*/ |
||||
static function bool CanBroadcast(Actor broadcaster, int recentSentTextSize) |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* This event is called whenever a someone is trying to broadcast |
||||
* a text message (typically the typed by a player). |
||||
* It is called once per message and allows you to change it |
||||
* (by changing 'message' argument) before any of the players receive it. |
||||
* |
||||
* See also `HandleTextFor()`. |
||||
* |
||||
* @param sender `Actor` that requested broadcast in question. |
||||
* @param message Message that `sender` wants to broadcast, possibly |
||||
* altered by other broadcast listeners. |
||||
* @param messageType Name variable that describes a type of the message. |
||||
* Examples are 'Say' and 'CriticalEvent'. |
||||
* @return If one of the listeners returns 'false', - |
||||
* it will be treated just like one of broadcasters returning 'false' |
||||
* in `AcceptBroadcastText()`: this event won't be called for remaining |
||||
* active listeners and message will not be broadcasted. |
||||
*/ |
||||
static function bool HandleText( |
||||
Actor sender, |
||||
out string message, |
||||
optional name messageType) |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* This event is called whenever a someone is trying to broadcast |
||||
* a text message (typically the typed by a player). |
||||
* This event is similar to 'HandleText', but is called for every player |
||||
* the message is sent to. |
||||
* |
||||
* Method allows you to alter the message, but note that changes are |
||||
* accumulated as events go through the players. |
||||
* |
||||
* @param receiver Player, to which message is supposed to be sent next. |
||||
* @param sender `Actor` that requested broadcast in question. |
||||
* @param message Message that `sender` wants to broadcast, possibly |
||||
* altered by other broadcast listeners. |
||||
* But keep in mind that if you do change the message for one client, - |
||||
* clients that come after it will get an already altered version. |
||||
* That is, changes to the message accumulate between different |
||||
* `HandleTextFor()` calls for one broadcast. |
||||
* @param messageType Name variable that describes a type of the message. |
||||
* Examples are 'Say' and 'CriticalEvent'. |
||||
* @return If one of the listeners returns 'false', - |
||||
* message would not be sent to `receiver` at all |
||||
* (but it would not prevent broadcasting it to the rest of the players). |
||||
* Return `true` if you want it to be broadcasted. |
||||
*/ |
||||
static function bool HandleTextFor( |
||||
PlayerController receiver, |
||||
Actor sender, |
||||
out string message, |
||||
optional name messageType) |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* This event is called whenever a someone is trying to broadcast |
||||
* a localized message. It is called once per message, but, |
||||
* unlike `HandleText()`, does not allow you to change it. |
||||
* |
||||
* @param sender `Actor` that requested broadcast in question. |
||||
* @param message Message that `sender` wants to broadcast. |
||||
* @return If one of the listeners returns 'false', - |
||||
* it will be treated just like one of broadcasters returning 'false' |
||||
* in `AcceptBroadcastLocalized()`: this event won't be called for |
||||
* remaining active listeners and message will not be broadcasted. |
||||
*/ |
||||
static function bool HandleLocalized( |
||||
Actor sender, |
||||
BroadcastEvents.LocalizedMessage message) |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* This event is called whenever a someone is trying to broadcast |
||||
* a localized message. This event is similar to 'HandleLocalized', but is |
||||
* called for every player the message is sent to. |
||||
* |
||||
* Unlike `HandleTextFor()` method does not allow you to alter the message. |
||||
* |
||||
* @param receiver Player, to which message is supposed to be sent next. |
||||
* @param sender `Actor` that requested broadcast in question. |
||||
* @param message Message that `sender` wants to broadcast. |
||||
* @return If one of the listeners returns 'false', - |
||||
* message would not be sent to `receiver` at all |
||||
* (but it would not prevent broadcasting it to the rest of the players). |
||||
* Return `true` if you want it to be broadcasted. |
||||
*/ |
||||
static function bool HandleLocalizedFor( |
||||
PlayerController receiver, |
||||
Actor sender, |
||||
BroadcastEvents.LocalizedMessage message) |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedEvents = class'BroadcastEvents' |
||||
} |
@ -1,161 +0,0 @@
|
||||
/** |
||||
* One of the two classes that make up a core of event system in Acedia. |
||||
* |
||||
* `Events` (or it's child) class shouldn't be instantiated. |
||||
* Usually module would provide '...Events' class that defines |
||||
* certain set of static functions that can generate event calls to |
||||
* all it's active listeners. |
||||
* If you're simply using modules someone made, - |
||||
* you don't need to bother yourself with further specifics. |
||||
* If you wish to create your own event generator, |
||||
* then first create a `...ListenerBase` object |
||||
* (more about it in the description of `Listener` class) |
||||
* and set `relatedListener` variable to point to it's class. |
||||
* Then for each event create a caller function in your `Event` class, |
||||
* following this template: |
||||
* ____________________________________________________________________________ |
||||
* | static function CallEVENT_NAME(<ARGUMENTS>) |
||||
* | { |
||||
* | local int i; |
||||
* | local array< class<Listener> > listeners; |
||||
* | listeners = GetListeners(); |
||||
* | for (i = 0; i < listeners.length; i += 1) |
||||
* | { |
||||
* | class<...ListenerBase>(listeners[i]) |
||||
* | .static.EVENT_NAME(<ARGUMENTS>); |
||||
* | } |
||||
* | } |
||||
* |___________________________________________________________________________ |
||||
* If each listener must indicate whether it gives it's permission for |
||||
* something to happen, then use this template: |
||||
* ____________________________________________________________________________ |
||||
* | static function CallEVENT_NAME(<ARGUMENTS>) |
||||
* | { |
||||
* | local int i; |
||||
* | local bool result; |
||||
* | local array< class<Listener> > listeners; |
||||
* | listeners = GetListeners(); |
||||
* | for (i = 0; i < listeners.length; i += 1) |
||||
* | { |
||||
* | result = class<...ListenerBase>(listeners[i]) |
||||
* | .static.EVENT_NAME(<ARGUMENTS>); |
||||
* | if (!result) return false; |
||||
* | } |
||||
* | return true; |
||||
* | } |
||||
* |___________________________________________________________________________ |
||||
* For concrete example look at |
||||
* `MutatorEvents` and `MutatorListenerBase`. |
||||
* Copyright 2020 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class Events extends AcediaObject |
||||
abstract; |
||||
|
||||
var private array< class<Listener> > listeners; |
||||
|
||||
// Reference to the base class of listeners that are allowed to listen to |
||||
// these events |
||||
var public const class<Listener> relatedListener; |
||||
|
||||
// Event class can also auto-spawn a `Service`, |
||||
// in case it's require to generate events |
||||
var public const class<Service> connectedServiceClass; |
||||
// Set this to `true`if you want `connectedServiceClass` service to also |
||||
// auto-shutdown whenever no-one listens to the events. |
||||
var public const bool shutDownServiceWithoutListeners; |
||||
|
||||
static public final function array< class<Listener> > GetListeners() |
||||
{ |
||||
return default.listeners; |
||||
} |
||||
|
||||
// Make given listener active. |
||||
// If listener was already activated also returns 'false'. |
||||
static public final function bool ActivateListener(class<Listener> newListener) |
||||
{ |
||||
local int i; |
||||
if (newListener == none) return false; |
||||
if (!ClassIsChildOf(newListener, default.relatedListener)) return false; |
||||
|
||||
// Spawn service, if absent |
||||
if ( default.listeners.length == 0 |
||||
&& default.connectedServiceClass != none) { |
||||
default.connectedServiceClass.static.Require(); |
||||
} |
||||
// Add listener |
||||
for (i = 0;i < default.listeners.length;i += 1) |
||||
{ |
||||
if (default.listeners[i] == newListener) { |
||||
return false; |
||||
} |
||||
} |
||||
default.listeners[default.listeners.length] = newListener; |
||||
return true; |
||||
} |
||||
|
||||
// Make given listener inactive. |
||||
// If listener wasn't active returns 'false'. |
||||
static public final function bool DeactivateListener(class<Listener> listener) |
||||
{ |
||||
local int i; |
||||
local bool removedListener; |
||||
local Service service; |
||||
if (listener == none) return false; |
||||
|
||||
// Remove listener |
||||
for (i = 0; i < default.listeners.length; i += 1) |
||||
{ |
||||
if (default.listeners[i] == listener) |
||||
{ |
||||
default.listeners.Remove(i, 1); |
||||
removedListener = true; |
||||
break; |
||||
} |
||||
} |
||||
// Remove unneeded service |
||||
if ( default.shutDownServiceWithoutListeners |
||||
&& default.listeners.length == 0 |
||||
&& default.connectedServiceClass != none) |
||||
{ |
||||
service = Service(default.connectedServiceClass.static.GetInstance()); |
||||
if (service != none) { |
||||
service.Destroy(); |
||||
} |
||||
} |
||||
return removedListener; |
||||
} |
||||
|
||||
static public final function bool IsActiveListener(class<Listener> listener) |
||||
{ |
||||
local int i; |
||||
if (listener == none) return false; |
||||
|
||||
for (i = 0; i < default.listeners.length; i += 1) |
||||
{ |
||||
if (default.listeners[i] == listener) |
||||
{ |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedListener = class'Listener' |
||||
} |
@ -1,59 +0,0 @@
|
||||
/** |
||||
* One of the two classes that make up a core of event system in Acedia. |
||||
* |
||||
* 'Listener' (or it's child) class shouldn't be instantiated. |
||||
* Usually module would provide '...ListenerBase' class that defines |
||||
* certain set of static functions, corresponding to events it can listen to. |
||||
* In order to handle those events you must create it's child class and |
||||
* override said functions. But they will only be called if |
||||
* 'SetActive(true)' is called for that child class. |
||||
* To create you own '...ListenerBase' class you need to define |
||||
* a static function for each event you wish it to catch and |
||||
* set 'relatedEvents' variable to point at the 'Events' class |
||||
* that will generate your events. |
||||
* For concrete example look at |
||||
* 'ConnectionEvents' and 'ConnectionListenerBase'. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class Listener extends AcediaObject |
||||
abstract; |
||||
|
||||
var public const class<Events> relatedEvents; |
||||
|
||||
|
||||
static public final function SetActive(bool active) |
||||
{ |
||||
if (active) |
||||
{ |
||||
default.relatedEvents.static.ActivateListener(default.class); |
||||
} |
||||
else |
||||
{ |
||||
default.relatedEvents.static.DeactivateListener(default.class); |
||||
} |
||||
} |
||||
|
||||
static public final function IsActive(bool active) |
||||
{ |
||||
default.relatedEvents.static.IsActiveListener(default.class); |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedEvents = class'Events' |
||||
} |
@ -1,56 +0,0 @@
|
||||
/** |
||||
* Event generator that repeats events of a mutator. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class MutatorEvents extends Events |
||||
abstract; |
||||
|
||||
static function bool CallCheckReplacement(Actor other, out byte isSuperRelevant) |
||||
{ |
||||
local int i; |
||||
local bool result; |
||||
local array< class<Listener> > listeners; |
||||
listeners = GetListeners(); |
||||
for (i = 0; i < listeners.length; i += 1) |
||||
{ |
||||
result = class<MutatorListenerBase>(listeners[i]) |
||||
.static.CheckReplacement(other, isSuperRelevant); |
||||
if (!result) return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
static function bool CallMutate(string command, PlayerController sendingPlayer) |
||||
{ |
||||
local int i; |
||||
local bool result; |
||||
local array< class<Listener> > listeners; |
||||
listeners = GetListeners(); |
||||
for (i = 0; i < listeners.length;i += 1) |
||||
{ |
||||
result = class<MutatorListenerBase>(listeners[i]) |
||||
.static.Mutate(command, sendingPlayer); |
||||
if (!result) return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedListener = class'MutatorListenerBase' |
||||
} |
@ -1,47 +0,0 @@
|
||||
/** |
||||
* Listener for events, normally propagated by mutators. |
||||
* Copyright 2019 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class MutatorListenerBase extends Listener |
||||
abstract; |
||||
|
||||
// This event is called whenever 'CheckReplacement' |
||||
// check is propagated through mutators. |
||||
// If one of the listeners returns 'false', - |
||||
// it will be treated just like a mutator returning 'false' |
||||
// in 'CheckReplacement' and |
||||
// this method won't be called for remaining active listeners. |
||||
static function bool CheckReplacement(Actor other, out byte isSuperRelevant) |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
// This event is called whenever 'Mutate' is propagated through mutators. |
||||
// If one of the listeners returns 'false', - |
||||
// this method won't be called for remaining active listeners or mutators. |
||||
// If all listeners return 'true', - |
||||
// mutate command will be further propagated to the rest of the mutators. |
||||
static function bool Mutate(string command, PlayerController sendingPlayer) |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedEvents = class'MutatorEvents' |
||||
} |
@ -0,0 +1,25 @@
|
||||
/** |
||||
* Does nothing. Exists only so that `Service`s can use its instances as |
||||
* receivers when connecting to `Signal`s. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class ServiceAnchor extends AcediaObject; |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
@ -1,66 +0,0 @@
|
||||
/** |
||||
* Event generator for events related to testing. |
||||
* Copyright 2020 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class TestingEvents extends Events |
||||
abstract; |
||||
|
||||
static function CallTestingBegan(array< class<TestCase> > testQueue) |
||||
{ |
||||
local int i; |
||||
local array< class<Listener> > listeners; |
||||
listeners = GetListeners(); |
||||
for (i = 0; i < listeners.length; i += 1) |
||||
{ |
||||
class<TestingListenerBase>(listeners[i]) |
||||
.static.TestingBegan(testQueue); |
||||
} |
||||
} |
||||
|
||||
static function CallCaseTested( |
||||
class<TestCase> testedCase, |
||||
TestCaseSummary result) |
||||
{ |
||||
local int i; |
||||
local array< class<Listener> > listeners; |
||||
listeners = GetListeners(); |
||||
for (i = 0; i < listeners.length; i += 1) |
||||
{ |
||||
class<TestingListenerBase>(listeners[i]) |
||||
.static.CaseTested(testedCase, result); |
||||
} |
||||
} |
||||
|
||||
static function CallTestingEnded( |
||||
array< class<TestCase> > testQueue, |
||||
array<TestCaseSummary> results) |
||||
{ |
||||
local int i; |
||||
local array< class<Listener> > listeners; |
||||
listeners = GetListeners(); |
||||
for (i = 0; i < listeners.length; i += 1) |
||||
{ |
||||
class<TestingListenerBase>(listeners[i]) |
||||
.static.TestingEnded(testQueue, results); |
||||
} |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedListener = class'TestingListenerBase' |
||||
} |
@ -1,34 +0,0 @@
|
||||
/** |
||||
* Listener for events related to testing. |
||||
* Copyright 2020 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class TestingListenerBase extends Listener |
||||
abstract; |
||||
|
||||
static function TestingBegan(array< class<TestCase> > testQueue) {} |
||||
|
||||
static function CaseTested(class<TestCase> testCase, TestCaseSummary result) {} |
||||
|
||||
static function TestingEnded( |
||||
array< class<TestCase> > testQueue, |
||||
array<TestCaseSummary> results) {} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedEvents = class'TestingEvents' |
||||
} |
@ -0,0 +1,398 @@
|
||||
/** |
||||
* Low-level API that provides set of utility methods for working with |
||||
* `BroadcastHandler`s. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class BroadcastAPI extends AcediaObject; |
||||
|
||||
/** |
||||
* Defines ways to add a new `BroadcastHandler` into the `GameInfo`'s |
||||
* `BroadcastHandler` linked list. |
||||
*/ |
||||
enum InjectionLevel |
||||
{ |
||||
// `BroadcastHandler` will be places in the broadcast handlers' |
||||
// chain as a normal `BroadcastHandler` |
||||
// (through `RegisterBroadcastHandler()` call). |
||||
BHIJ_Registered, |
||||
// `BroadcastHandler` will not be added at all. |
||||
BHIJ_None, |
||||
// `BroadcastEventsObserver` will be injected at the very beginning of |
||||
// the broadcast handlers' chain. |
||||
BHIJ_Root |
||||
}; |
||||
|
||||
/** |
||||
* Describes propagated localized message. |
||||
*/ |
||||
struct LocalizedMessage |
||||
{ |
||||
// Every localized message is described by a class and id. |
||||
// For example, consider 'KFMod.WaitingMessage': |
||||
// if passed 'id' is '1', |
||||
// then it's supposed to be a message about new wave, |
||||
// but if passed 'id' is '2', |
||||
// then it's about completing the wave. |
||||
var class<LocalMessage> class; |
||||
var int id; |
||||
// Localized messages in unreal script can be passed along with |
||||
// optional arguments, described by variables below. |
||||
var PlayerReplicationInfo relatedPRI1; |
||||
var PlayerReplicationInfo relatedPRI2; |
||||
var Object relatedObject; |
||||
}; |
||||
|
||||
/** |
||||
* Called before text message is sent to any player, during the check for |
||||
* whether it is at all allowed to be broadcasted. Corresponds to |
||||
* the `HandlerAllowsBroadcast()` method from `BroadcastHandler`. |
||||
* Return `false` to prevent message from being broadcast. If a `false` is |
||||
* returned, signal propagation will be interrupted. |
||||
* |
||||
* Only guaranteed to be called for a message if `BHIJ_Root` was used to |
||||
* inject `BroadcastEventsObserver`. Otherwise it depends on what other |
||||
* `BroadcastHandler`s are added to `GameInfo`'s linked list. However for |
||||
* `BHIJ_Registered` this signal function should be more reliable than |
||||
* `OnHandleText()`, with the downside of not providing you with |
||||
* an actual message. |
||||
* |
||||
* [Signature] |
||||
* bool <slot>(Actor broadcaster, int newMessageLength) |
||||
* |
||||
* @param broadcaster `Actor` that attempts to broadcast next |
||||
* text message. |
||||
* @param newMessageLength Length of the message (amount of code points). |
||||
* @return `false` if you want to prevent message from being broadcast |
||||
* and `true` otherwise. `false` returned by one of the handlers overrides |
||||
* `true` values returned by others. |
||||
*/ |
||||
/* SIGNAL */ |
||||
public final function Broadcast_OnBroadcastCheck_Slot OnBroadcastCheck( |
||||
AcediaObject receiver) |
||||
{ |
||||
local Signal signal; |
||||
local UnrealService service; |
||||
service = UnrealService(class'UnrealService'.static.Require()); |
||||
signal = service.GetSignal(class'Broadcast_OnBroadcastCheck_Signal'); |
||||
return Broadcast_OnBroadcastCheck_Slot(signal.NewSlot(receiver)); |
||||
} |
||||
|
||||
/** |
||||
* Called before text message is sent to any player, but after the check |
||||
* for whether it is at all allowed to be broadcasted. Corresponds to |
||||
* the `Broadcast()` or `BroadcastTeam()` method from `BroadcastHandler` if |
||||
* `BHIJ_Root` injection method was used and to `BroadcastText()` for |
||||
* `BHIJ_Registered`. |
||||
* Return `false` to prevent message from being broadcast. If `false` is |
||||
* returned, signal propagation to the remaining handlers will also |
||||
* be interrupted. |
||||
* |
||||
* Only guaranteed to be called for a message if `BHIJ_Root` was used to |
||||
* inject `BroadcastEventsObserver`. Otherwise: |
||||
* 1. Whether it gets emitted at all depends on what other |
||||
* `BroadcastHandler`s are added to `GameInfo`'s linked list; |
||||
* 2. This event is actually inaccessible for `BroadcastEventsObserver` |
||||
* and Acedia tries to make a guess on whether it occurred based on |
||||
* parameters of `BroadcastText()` call - in some cases it can be |
||||
* called twice for the same message or not be called at all. |
||||
* Although conditions for that are exotic and unlikely. |
||||
* If you do not care about actual contents of the `message` and simply want to |
||||
* detect (and possibly prevent) message broadcast as early as possible, |
||||
* consider using `OnBroadcastCheck()` signal function instead. |
||||
* |
||||
* [Signature] |
||||
* bool <slot>(Actor sender, out string message, name type, bool teamMessage) |
||||
* |
||||
* @param sender `Actor` that attempts to broadcast next text message. |
||||
* @param message Message that is being broadcasted. Can be changed, but |
||||
* with `BHIJ_Registered` level of injection such change can actually |
||||
* affect detection of new broadcasts and lead to weird behavior. |
||||
* If one of the handler modifies the `message`, then all the handlers |
||||
* after it will get a modified version. |
||||
* @param type Type of the message. |
||||
* @param teamMessage `true` if this message is a message that is being |
||||
* broadcasted within `sender`'s team. Only works if `BHIJ_Root` injection |
||||
* method was used, otherwise, always stays `false`. |
||||
* @return `false` if you want to prevent message from being broadcast |
||||
* and `true` otherwise. `false` returned by one of the handlers overrides |
||||
* `true` values returned by others. |
||||
*/ |
||||
/* SIGNAL */ |
||||
public final function Broadcast_OnHandleText_Slot OnHandleText( |
||||
AcediaObject receiver) |
||||
{ |
||||
local Signal signal; |
||||
local UnrealService service; |
||||
service = UnrealService(class'UnrealService'.static.Require()); |
||||
signal = service.GetSignal(class'Broadcast_OnHandleText_Signal'); |
||||
return Broadcast_OnHandleText_Slot(signal.NewSlot(receiver)); |
||||
} |
||||
|
||||
/** |
||||
* Called before text message is sent to a particular player. Corresponds |
||||
* to the `BroadcastText()` method from `BroadcastHandler`. |
||||
* Return `false` to prevent message from being broadcast to a |
||||
* specified player. If `false` is returned, signal propagation to |
||||
* the remaining handlers will also be interrupted. |
||||
* |
||||
* [Signature] |
||||
* bool <slot>( |
||||
* PlayerController receiver, |
||||
* Actor sender, |
||||
* string message, |
||||
* name type) |
||||
* |
||||
* @param receiver Player that is about to receive message in question. |
||||
* @param sender `Actor` that attempts to broadcast next text message. |
||||
* With `BHIJ_Root` injection level an actual sender `Actor` is passed, |
||||
* instead of extracted `PlayerReplicationInfo` that is given inside |
||||
* `BroadcastText()` for `Pawn`s and `Controller`s. |
||||
* Otherwise returns `PlayerReplicationInfo` provided in |
||||
* the `BroadcastText()`. |
||||
* @param message Message that is being broadcasted. |
||||
* @param type Type of the message. |
||||
* @return `false` if you want to prevent message from being broadcast |
||||
* and `true` otherwise. `false` returned by one of the handlers overrides |
||||
* `true` values returned by others. |
||||
*/ |
||||
/* SIGNAL */ |
||||
public final function Broadcast_OnHandleTextFor_Slot OnHandleTextFor( |
||||
AcediaObject receiver) |
||||
{ |
||||
local Signal signal; |
||||
local UnrealService service; |
||||
service = UnrealService(class'UnrealService'.static.Require()); |
||||
signal = service.GetSignal(class'Broadcast_OnHandleTextFor_Signal'); |
||||
return Broadcast_OnHandleTextFor_Slot(signal.NewSlot(receiver)); |
||||
} |
||||
|
||||
/** |
||||
* Called before localized message is sent to any player. Corresponds to |
||||
* the `AllowBroadcastLocalized()` method from `BroadcastHandler` if |
||||
* `BHIJ_Root` injection method was used and to `BroadcastLocalized()` for |
||||
* `BHIJ_Registered`. |
||||
* Return `false` to prevent message from being broadcast. If `false` is |
||||
* returned, signal propagation for remaining handlers will also |
||||
* be interrupted. |
||||
* |
||||
* Only guaranteed to be called for a message if `BHIJ_Root` was used to |
||||
* inject `BroadcastEventsObserver`. Otherwise: |
||||
* 1. Whether it gets emitted at all depends on what other |
||||
* `BroadcastHandler`s are added to `GameInfo`'s linked list; |
||||
* 2. This event is actually inaccessible for `BroadcastEventsObserver` |
||||
* and Acedia tries to make a guess on whether it occurred based on |
||||
* parameters of `BroadcastLocalized()` call - in some cases it can be |
||||
* called twice for the same message or not be called at all. |
||||
* Although conditions for that are exotic and unlikely. |
||||
* |
||||
* [Signature] |
||||
* bool <slot>( |
||||
* Actor sender, |
||||
* LocalizedMessage packedMessage) |
||||
* |
||||
* @param sender `Actor` that attempts to broadcast next text message. |
||||
* @param packedMessage Message that is being broadcasted, represented as |
||||
* struct that contains all the normal parameters associate with |
||||
* localized messages. |
||||
* @return `false` if you want to prevent message from being broadcast |
||||
* and `true` otherwise. `false` returned by one of the handlers overrides |
||||
* `true` values returned by others. |
||||
*/ |
||||
/* SIGNAL */ |
||||
public final function Broadcast_OnHandleLocalized_Slot OnHandleLocalized( |
||||
AcediaObject receiver) |
||||
{ |
||||
local Signal signal; |
||||
local UnrealService service; |
||||
service = UnrealService(class'UnrealService'.static.Require()); |
||||
signal = service.GetSignal(class'Broadcast_OnHandleLocalized_Signal'); |
||||
return Broadcast_OnHandleLocalized_Slot(signal.NewSlot(receiver)); |
||||
} |
||||
|
||||
/** |
||||
* Called before localized message is sent to a particular player. |
||||
* Corresponds to the `BroadcastLocalized()` method from `BroadcastHandler`. |
||||
* Return `false` to prevent message from being broadcast to a |
||||
* specified player. If `false` is returned, signal propagation to |
||||
* the remaining handlers will also be interrupted. |
||||
* |
||||
* [Signature] |
||||
* bool <slot>( |
||||
* PlayerController receiver, |
||||
* Actor sender, |
||||
* LocalizedMessage packedMessage) |
||||
* |
||||
* @param receiver Player that is about to receive message in question. |
||||
* @param sender `Actor` that attempts to broadcast next localized |
||||
* message. Unlike `OnHandleTextFor()`, this parameter always corresponds |
||||
* to the real sender, regardless of the injection level. |
||||
* @param packedMessage Message that is being broadcasted, represented as |
||||
* struct that contains all the normal parameters associate with |
||||
* localized messages. |
||||
* @return `false` if you want to prevent message from being broadcast |
||||
* and `true` otherwise. `false` returned by one of the handlers overrides |
||||
* `true` values returned by others. |
||||
*/ |
||||
/* SIGNAL */ |
||||
public final function Broadcast_OnHandleLocalizedFor_Slot OnHandleLocalizedFor( |
||||
AcediaObject receiver) |
||||
{ |
||||
local Signal signal; |
||||
local UnrealService service; |
||||
service = UnrealService(class'UnrealService'.static.Require()); |
||||
signal = service.GetSignal(class'Broadcast_OnHandleLocalizedFor_Signal'); |
||||
return Broadcast_OnHandleLocalizedFor_Slot(signal.NewSlot(receiver)); |
||||
} |
||||
|
||||
/** |
||||
* Adds new `BroadcastHandler` class to the current `GameInfo`. |
||||
* Does nothing if given `BroadcastHandler` class was already added before. |
||||
* |
||||
* @param newBHClass Class of `BroadcastHandler` to add. |
||||
* @return `BroadcastHandler` instance if it was added and `none` otherwise. |
||||
*/ |
||||
public final function BroadcastHandler Add( |
||||
class<BroadcastHandler> newBHClass, |
||||
optional InjectionLevel injectionLevel) |
||||
{ |
||||
local LevelInfo level; |
||||
local BroadcastHandler newBroadcastHandler; |
||||
if (injectionLevel == BHIJ_None) return none; |
||||
level = _.unreal.GetLevel(); |
||||
if (level == none || level.game == none) return none; |
||||
if (IsAdded(newBHClass)) return none; |
||||
|
||||
// For some reason `default.nextBroadcastHandlerClass` variable can be |
||||
// auto-set after the level switch. |
||||
// I don't know why, I don't know when exactly, but not resetting it |
||||
// can lead to certain issues, including infinite recursion crashes. |
||||
class'BroadcastHandler'.default.nextBroadcastHandlerClass = none; |
||||
newBroadcastHandler = class'CoreService'.static.Require().Spawn(newBHClass); |
||||
if (injectionLevel == BHIJ_Registered) |
||||
{ |
||||
// There is guaranteed to be SOME broadcast handler |
||||
level.game.broadcastHandler |
||||
.RegisterBroadcastHandler(newBroadcastHandler); |
||||
return newBroadcastHandler; |
||||
} |
||||
// Here `injectionLevel == BHIJ_Root` holds. |
||||
// Swap out level's first handler with ours |
||||
// (needs to be done for both actor reference and it's class) |
||||
newBroadcastHandler.nextBroadcastHandler = level.game.broadcastHandler; |
||||
newBroadcastHandler.nextBroadcastHandlerClass = level.game.broadcastClass; |
||||
level.game.broadcastHandler = newBroadcastHandler; |
||||
level.game.broadcastClass = newBHClass; |
||||
return newBroadcastHandler; |
||||
} |
||||
|
||||
/** |
||||
* Removes given `BroadcastHandler` class from the current `GameInfo`, |
||||
* if it is active. Does nothing otherwise. |
||||
* |
||||
* @param BHClassToRemove Class of `BroadcastHandler` to try and remove. |
||||
* @return `true` if `BHClassToRemove` was removed and `false` otherwise |
||||
* (if they were not active in the first place). |
||||
*/ |
||||
public final function bool Remove(class<BroadcastHandler> BHClassToRemove) |
||||
{ |
||||
local LevelInfo level; |
||||
local BroadcastHandler previousBH, currentBH; |
||||
level = _.unreal.GetLevel(); |
||||
if (level == none || level.game == none) { |
||||
return false; |
||||
} |
||||
currentBH = level.game.broadcastHandler; |
||||
if (currentBH == none) { |
||||
return false; |
||||
} |
||||
// Special case of our `BroadcastHandler` being inserted in the root |
||||
if (currentBH == BHClassToRemove) |
||||
{ |
||||
level.game.broadcastHandler = currentBH.nextBroadcastHandler; |
||||
level.game.broadcastClass = currentBH.nextBroadcastHandlerClass; |
||||
currentBH.Destroy(); |
||||
return true; |
||||
} |
||||
// And after the root |
||||
previousBH = currentBH; |
||||
currentBH = currentBH.nextBroadcastHandler; |
||||
while (currentBH != none) |
||||
{ |
||||
if (currentBH.class != BHClassToRemove) |
||||
{ |
||||
previousBH = currentBH; |
||||
currentBH = currentBH.nextBroadcastHandler; |
||||
} |
||||
else |
||||
{ |
||||
previousBH.nextBroadcastHandler = |
||||
currentBH.nextBroadcastHandler; |
||||
previousBH.default.nextBroadcastHandlerClass = |
||||
currentBH.default.nextBroadcastHandlerClass; |
||||
previousBH.nextBroadcastHandlerClass = |
||||
currentBH.nextBroadcastHandlerClass; |
||||
currentBH.default.nextBroadcastHandlerClass = none; |
||||
currentBH.Destroy(); |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Finds given class of `BroadcastHandler` if it's currently active in |
||||
* `GameInfo`. Returns `none` otherwise. |
||||
* |
||||
* @param BHClassToFind Class of `BroadcastHandler` to find. |
||||
* @return `BroadcastHandler` instance of given class `BHClassToFind`, that is |
||||
* added to `GameInfo`'s linked list and `none` if no such |
||||
* `BroadcastHandler` is currently in the list. |
||||
*/ |
||||
public final function BroadcastHandler FindInstance( |
||||
class<BroadcastHandler> BHClassToFind) |
||||
{ |
||||
local BroadcastHandler BHIter; |
||||
if (BHClassToFind == none) { |
||||
return none; |
||||
} |
||||
BHIter = _.unreal.GetGameType().broadcastHandler; |
||||
while (BHIter != none) |
||||
{ |
||||
if (BHIter.class == BHClassToFind) { |
||||
return BHIter; |
||||
} |
||||
BHIter = BHIter.nextBroadcastHandler; |
||||
} |
||||
return none; |
||||
} |
||||
|
||||
/** |
||||
* Checks if given class of `BroadcastHandler` is currently active in |
||||
* `GameInfo`. |
||||
* |
||||
* @param rulesClassToCheck Class of rules to check for. |
||||
* @return `true` if `GameRules` are active and `false` otherwise. |
||||
*/ |
||||
public final function bool IsAdded(class<BroadcastHandler> BHClassToFind) |
||||
{ |
||||
return (FindInstance(BHClassToFind) != none); |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
@ -0,0 +1,483 @@
|
||||
/** |
||||
* `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 - 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class BroadcastEventsObserver extends Engine.BroadcastHandler |
||||
dependson(BroadcastAPI) |
||||
config(AcediaSystem); |
||||
|
||||
// Forcing Acedia's own `BroadcastHandler` is rather invasive and might be |
||||
// undesired, since it can lead to incompatibilities with some mutators. |
||||
// To alleviate this issue Acedia allows server admins to control how it's |
||||
// `BroadcastHandler` is injected. Do note however that anything other than |
||||
// `BHIJ_Root` can lead to issues with Acedia's features. |
||||
var public config const BroadcastAPI.InjectionLevel usedInjectionLevel; |
||||
|
||||
/** |
||||
* To understand how what our broadcast handler does, let us first explain |
||||
* how `BroadcastHandler` classes work. Here we skip voice and speech |
||||
* broadcasting topics, since they are not something Acedia or this class |
||||
* currently uses. |
||||
* `BroadcastHandler`s are capable of forming a one-way linked list by |
||||
* referring to the next `BroadcastHandler` by `nextBroadcastHandler` and |
||||
* `nextBroadcastHandlerClass` variables. New `BroadcastHandler`s can be added |
||||
* via `RegisterBroadcastHandler()` method. |
||||
* Actual broadcasting of the messages is done by calling one of |
||||
* the three methods: `Broadcast()`, `BroadcastTeam()` and weirdly named |
||||
* `AllowBroadcastLocalized()` on the root `BroadcastHandler` (stored in |
||||
* the current `GameInfo`). These methods are only ever called for the root |
||||
* `BroadcastHandler` and **are not** propagated down the chain. |
||||
* This leads to... |
||||
* ISSUE 1: we cannot reliably detect start of the message propagation with |
||||
* `BroadcastHandler` unless we are at the root of the linked list. This is why |
||||
* it is important for Acedia to use `BHIJ_Root` method for its |
||||
* `BroadcastHandler`. |
||||
* |
||||
* First we will look into `Broadcast()` and `BroadcastTeam()` methods. |
||||
* First thing either of them does is to call `AllowsBroadcast()` method that |
||||
* checks whether message is allowed to be broadcasted at all. It is also only |
||||
* called for the root `BroadcastHandler`, but allows other `BroadcastHandler`s |
||||
* to block the message for their own reasons by propagating |
||||
* `HandlerAllowsBroadcast()` method down the chain. |
||||
* Then they call `BroadcastText()` for every acceptable player controller |
||||
* they find and the only difference between `Broadcast()` and |
||||
* `BroadcastTeam()` is that the latter also checks to that they belong to the |
||||
* same team as the sender. |
||||
* `BroadcastText()` is propagated down the linked list of |
||||
* the `BroadcastHandler`s (allowing them to modify or discard message) and, |
||||
* once list is exhausted, calls `TeamMessage()` method. However it also |
||||
* propagates additional method `AcceptBroadcastText()` down the linked list. |
||||
* Supposedly it's `AcceptBroadcastText()` that you should overload when making |
||||
* your own `BroadcastHandler`, but this setup creates... |
||||
* ISSUE 2: by default `AcceptBroadcastText()` is propagated ANEW |
||||
* inside EVERY `BroadcastText()` call (that is also propagated). This means |
||||
* that if there is several `BroadcastHandler`s in the chain before yours - |
||||
* every single one of them (including your own!) will call |
||||
* `AcceptBroadcastText()` for you. This means that `AcceptBroadcastText()` is |
||||
* going to be called several times for every broadcasted message unless your |
||||
* `BroadcastHandler` is added at the very root of the linked list. |
||||
* |
||||
* All that remains is to consider `AllowBroadcastLocalized()` method. |
||||
* It works in similar way to the previous two, but is simpler: it does not |
||||
* have an analogue to the `AllowsBroadcast()` method and simply calls |
||||
* `BroadcastLocalized()` for every player controller, spectator or not. |
||||
* `BroadcastLocalized()` works exactly the same way as `BroadcastText()`, but |
||||
* with uses `AcceptBroadcastLocalized()` instead of `AcceptBroadcastText()`, |
||||
* completely mirroring issue 2. |
||||
* |
||||
* Summary. |
||||
* Methods only called for root `BroadcastHandler`: |
||||
* 1. `Broadcast()` - starts text message broadcast; |
||||
* 2. `BroadcastTeam()` - starts team text message broadcast; |
||||
* 3. `AllowBroadcastLocalized()` - starts localized message broadcast; |
||||
* 4. `AllowsBroadcast()` - called for text message broadcasts (team or |
||||
* not) to check if they are allowed. |
||||
* Methods that are propagated down the linked list of `BroadcastHandler`s: |
||||
* 1. `HandlerAllowsBroadcast()` - called before broadcasting text message |
||||
* (team or not), before any `BroadcastText()` or |
||||
* `AcceptBroadcastText()` call; |
||||
* 2. `BroadcastText()` - once for every controller that should receive |
||||
* a certain text message (unless blocked at some point); |
||||
* 3. `AcceptBroadcastText()` - called shit ton of times inside |
||||
* `BroadcastText()` to check if message can be propagated; |
||||
* 4. `BroadcastLocalized()` - once for every controller that should |
||||
* receive a certain text message (unless blocked at some point); |
||||
* 5. `AcceptBroadcastLocalized()` - called shit ton of times inside |
||||
* `BroadcastLocalized()` to check if message can be propagated; |
||||
* |
||||
* What are we going to do? |
||||
* We want our `BroadcastHandler` to work at any place inside the |
||||
* linked list, but also to side step issue 2 completely, so we will use |
||||
* `BroadcastText()` and `BroadcastLocalized()` methods for catching messages |
||||
* sent to particular players. We do not want to reimplement `Broadcast()`, |
||||
* `BroadcastTeam()` or `AllowBroadcastLocalized()` (partially because it would |
||||
* mostly involve copy-pasting copyrighted code) and will instead inject some |
||||
* code to reliably catch the moment broadcast has started in case we are |
||||
* actually placed at the root. |
||||
* We also want to track broadcast by message parameters in |
||||
* `BroadcastText()` and `BroadcastLocalized()` methods in case we are not |
||||
* injected at the root to resolve issue 1. When we detect any difference in |
||||
* passed parameters (or players message was broadcasted to get repeated) - |
||||
* we declare a new broadcast. This methods is not perfect, but is likely |
||||
* the best possible guess for the start of broadcast. |
||||
*/ |
||||
|
||||
|
||||
// This is only relevant for `BHIJ_Root` injection level. |
||||
// 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 `PlayerReplicationInfo` 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). |
||||
var private array<Actor> storedSenders; |
||||
|
||||
// This is only relevant for `BHIJ_Root` injection level. |
||||
// We do not want to reimplement functions `Broadcast()`, `BroadcastTeam()` |
||||
// or `AllowBroadcastLocalized()` that root `BroadcastHandler` calls to do |
||||
// checks and send messages to individual players. |
||||
// Instead we would like to inject our own code and call parent version of |
||||
// these methods. |
||||
// We would also like to insert our code in some of the functions between |
||||
// `AllowsBroadcast()` check and actual broadcasting, so we cannot simply use |
||||
// a `super.AllowsBroadcast()` call that calls both of them in order. |
||||
// Instead we move `AllowsBroadcast()` unto our own methods: |
||||
// we first manually do `AllowsBroadcast()` 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 `UpdateTrackingWithTextMessage()` and |
||||
* `UpdateTrackingWithLocalizedMessage()` 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<PlayerController> 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 currentTextMessageContents; |
||||
var private name currentTextMessageType; |
||||
// We allow connected signals to modify message for all players before |
||||
// `BroadcastText()` or `BroadcastLocalized()` calls and can do so in case of |
||||
// `BHIJ_Registered`. |
||||
// But for `BHIJ_Registered` we can only catch those calls and must |
||||
// manually remember modifications we have made. We store those modifications |
||||
// in this variable. It resets when new message is detected. |
||||
var private string currentlyUsedMessage; |
||||
// Remember if currently tracked message was rejected by either |
||||
// `BroadcastText()` or `BroadcastLocalized()`. |
||||
var private bool currentMessageRejected; |
||||
// Variables to stored localized message. Helps to track for point 1. |
||||
var private BroadcastAPI.LocalizedMessage currentLocalizedMessage; |
||||
|
||||
var private Broadcast_OnBroadcastCheck_Signal onBroadcastCheck; |
||||
var private Broadcast_OnHandleLocalized_Signal onHandleLocalized; |
||||
var private Broadcast_OnHandleLocalizedFor_Signal onHandleLocalizedFor; |
||||
var private Broadcast_OnHandleText_Signal onHandleText; |
||||
var private Broadcast_OnHandleTextFor_Signal onHandleTextFor; |
||||
|
||||
public final function Initialize(UnrealService service) |
||||
{ |
||||
if (usedInjectionLevel != BHIJ_Root) { |
||||
Disable('Tick'); |
||||
} |
||||
if (service == none) { |
||||
return; |
||||
} |
||||
onBroadcastCheck = Broadcast_OnBroadcastCheck_Signal( |
||||
service.GetSignal(class'Broadcast_OnBroadcastCheck_Signal')); |
||||
onHandleLocalized = Broadcast_OnHandleLocalized_Signal( |
||||
service.GetSignal(class'Broadcast_OnHandleLocalized_Signal')); |
||||
onHandleLocalizedFor = Broadcast_OnHandleLocalizedFor_Signal( |
||||
service.GetSignal(class'Broadcast_OnHandleLocalizedFor_Signal')); |
||||
onHandleText = Broadcast_OnHandleText_Signal( |
||||
service.GetSignal(class'Broadcast_OnHandleText_Signal')); |
||||
onHandleTextFor = Broadcast_OnHandleTextFor_Signal( |
||||
service.GetSignal(class'Broadcast_OnHandleTextFor_Signal')); |
||||
} |
||||
|
||||
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; |
||||
} |
||||
|
||||
// Return `true` if new broadcast was detected |
||||
private function bool UpdateTrackingWithTextMessage( |
||||
PlayerReplicationInfo senderPRI, |
||||
PlayerController receiver, |
||||
string message, |
||||
name messageType) |
||||
{ |
||||
local bool isCurrentBroadcastContinuation; |
||||
if (usedInjectionLevel != BHIJ_Registered) { |
||||
return false; |
||||
} |
||||
isCurrentBroadcastContinuation = trackingBroadcast |
||||
&& (senderPRI == currentBroadcastInstigator) |
||||
&& (!broadcastingLocalizedMessage) |
||||
&& (message == currentTextMessageContents) |
||||
&& (messageType == currentTextMessageType) |
||||
&& !IsCurrentBroadcastReceiver(receiver); |
||||
if (isCurrentBroadcastContinuation) |
||||
{ |
||||
currentBroadcastReceivers[currentBroadcastReceivers.length] = receiver; |
||||
return false; |
||||
} |
||||
trackingBroadcast = true; |
||||
broadcastingLocalizedMessage = false; |
||||
currentBroadcastInstigator = senderPRI; |
||||
currentTextMessageContents = message; |
||||
currentlyUsedMessage = message; |
||||
currentTextMessageType = messageType; |
||||
currentMessageRejected = false; |
||||
currentBroadcastReceivers.length = 0; |
||||
return true; |
||||
} |
||||
|
||||
// Return `true` if new broadcast was detected |
||||
private function bool UpdateTrackingWithLocalizedMessage( |
||||
Actor sender, |
||||
PlayerController receiver, |
||||
BroadcastAPI.LocalizedMessage localizedMessage) |
||||
{ |
||||
local bool isCurrentBroadcastContinuation; |
||||
if (usedInjectionLevel != BHIJ_Registered) { |
||||
return false; |
||||
} |
||||
isCurrentBroadcastContinuation = trackingBroadcast |
||||
&& (sender == currentBroadcastInstigator) |
||||
&& (broadcastingLocalizedMessage) |
||||
&& (localizedMessage == currentLocalizedMessage) |
||||
&& !IsCurrentBroadcastReceiver(receiver); |
||||
if (isCurrentBroadcastContinuation) |
||||
{ |
||||
currentBroadcastReceivers[currentBroadcastReceivers.length] = receiver; |
||||
return false; |
||||
} |
||||
trackingBroadcast = true; |
||||
broadcastingLocalizedMessage = true; |
||||
currentBroadcastInstigator = sender; |
||||
currentLocalizedMessage = localizedMessage; |
||||
currentBroadcastReceivers.length = 0; |
||||
currentMessageRejected = false; |
||||
return true; |
||||
} |
||||
|
||||
// Makes us stop tracking current broadcast |
||||
private function ResetTracking() |
||||
{ |
||||
trackingBroadcast = false; |
||||
// Only important to forget objects and actors, since keeping |
||||
// references can cause issues. |
||||
// Other fields can remain "dirty", since they will be rewritten before |
||||
// they will ever be used. |
||||
currentBroadcastInstigator = none; |
||||
currentLocalizedMessage.relatedPRI1 = none; |
||||
currentLocalizedMessage.relatedPRI2 = none; |
||||
currentLocalizedMessage.relatedObject = none; |
||||
} |
||||
|
||||
public function bool HandlerAllowsBroadcast(Actor broadcaster, int sentTextNum) |
||||
{ |
||||
local bool canBroadcast; |
||||
// Fire and check signals |
||||
canBroadcast = onBroadcastCheck.Emit(broadcaster, sentTextNum); |
||||
// Check other broadcast handlers (if present) |
||||
if (canBroadcast && nextBroadcastHandler != none) |
||||
{ |
||||
canBroadcast = nextBroadcastHandler |
||||
.HandlerAllowsBroadcast(broadcaster, sentTextNum); |
||||
} |
||||
if (canBroadcast && usedInjectionLevel == BHIJ_Registered) |
||||
{ |
||||
// This method is only really called by the `AllowsBroadcast()` at the |
||||
// beginning of either `Broadcast()` or `BroadcastTeam()` methods. |
||||
// Meaning that new broadcast has started for sure. |
||||
ResetTracking(); |
||||
} |
||||
return canBroadcast; |
||||
} |
||||
|
||||
function Broadcast(Actor sender, coerce string message, optional name type) |
||||
{ |
||||
local bool canTryToBroadcast; |
||||
if (!AllowsBroadcast(sender, Len(message))) { |
||||
return; |
||||
} |
||||
canTryToBroadcast = onHandleText.Emit(sender, message, type, false); |
||||
if (canTryToBroadcast) |
||||
{ |
||||
storedSenders[storedSenders.length] = sender; |
||||
blockAllowsBroadcast = true; |
||||
super.Broadcast(sender, message, type); |
||||
blockAllowsBroadcast = false; |
||||
storedSenders.length = storedSenders.length - 1; |
||||
} |
||||
} |
||||
|
||||
function BroadcastTeam( |
||||
Controller sender, |
||||
coerce string message, |
||||
optional name type) |
||||
{ |
||||
local bool canTryToBroadcast; |
||||
if (!AllowsBroadcast(sender, Len(message))) { |
||||
return; |
||||
} |
||||
canTryToBroadcast = onHandleText.Emit(sender, message, type, true); |
||||
if (canTryToBroadcast) |
||||
{ |
||||
storedSenders[storedSenders.length] = sender; |
||||
blockAllowsBroadcast = true; |
||||
super.BroadcastTeam(sender, message, type); |
||||
blockAllowsBroadcast = false; |
||||
storedSenders.length = storedSenders.length - 1; |
||||
} |
||||
} |
||||
|
||||
event AllowBroadcastLocalized( |
||||
Actor sender, |
||||
class<LocalMessage> message, |
||||
optional int switch, |
||||
optional PlayerReplicationInfo relatedPRI1, |
||||
optional PlayerReplicationInfo relatedPRI2, |
||||
optional Object optionalObject) |
||||
{ |
||||
local bool canTryToBroadcast; |
||||
local BroadcastAPI.LocalizedMessage packedMessage; |
||||
packedMessage.class = message; |
||||
packedMessage.id = switch; |
||||
packedMessage.relatedPRI1 = relatedPRI1; |
||||
packedMessage.relatedPRI2 = relatedPRI2; |
||||
packedMessage.relatedObject = optionalObject; |
||||
canTryToBroadcast = onHandleLocalized.Emit(sender, packedMessage); |
||||
if (canTryToBroadcast) |
||||
{ |
||||
super.AllowBroadcastLocalized( sender, message, switch, |
||||
relatedPRI1, relatedPRI2, |
||||
optionalObject); |
||||
} |
||||
} |
||||
|
||||
function bool AllowsBroadcast(Actor broadcaster, int len) |
||||
{ |
||||
if (blockAllowsBroadcast) { |
||||
return true; // we have already done this check and it passed |
||||
} |
||||
return super.AllowsBroadcast(broadcaster, len); |
||||
} |
||||
|
||||
function BroadcastText( |
||||
PlayerReplicationInfo senderPRI, |
||||
PlayerController receiver, |
||||
string message, |
||||
optional name type) |
||||
{ |
||||
local bool canBroadcast; |
||||
local Actor sender; |
||||
if (senderPRI != none) { |
||||
sender = PlayerController(senderPRI.owner); |
||||
} |
||||
if (sender == none && storedSenders.length > 0) { |
||||
sender = storedSenders[storedSenders.length - 1]; |
||||
} |
||||
if (usedInjectionLevel == BHIJ_Registered) |
||||
{ |
||||
if (UpdateTrackingWithTextMessage(senderPRI, receiver, message, type)) |
||||
{ |
||||
currentMessageRejected = !onHandleText |
||||
.Emit(sender, message, type, false); |
||||
currentlyUsedMessage = message; |
||||
} |
||||
else { |
||||
message = currentlyUsedMessage; |
||||
} |
||||
if (currentMessageRejected) { |
||||
return; |
||||
} |
||||
} |
||||
canBroadcast = onHandleTextFor.Emit(receiver, sender, message, type); |
||||
if (!canBroadcast) { |
||||
return; |
||||
} |
||||
super.BroadcastText(senderPRI, receiver, message, type); |
||||
} |
||||
|
||||
function BroadcastLocalized( |
||||
Actor sender, |
||||
PlayerController receiver, |
||||
class<LocalMessage> message, |
||||
optional int switch, |
||||
optional PlayerReplicationInfo relatedPRI1, |
||||
optional PlayerReplicationInfo relatedPRI2, |
||||
optional Object obj) |
||||
{ |
||||
local bool canBroadcast; |
||||
local BroadcastAPI.LocalizedMessage packedMessage; |
||||
packedMessage.class = message; |
||||
packedMessage.id = switch; |
||||
packedMessage.relatedPRI1 = relatedPRI1; |
||||
packedMessage.relatedPRI2 = relatedPRI2; |
||||
packedMessage.relatedObject = obj; |
||||
if ( usedInjectionLevel == BHIJ_Registered |
||||
&& UpdateTrackingWithLocalizedMessage(sender, receiver, packedMessage)) |
||||
{ |
||||
currentMessageRejected = !onHandleLocalized.Emit(sender, packedMessage); |
||||
} |
||||
if (currentMessageRejected) { |
||||
return; |
||||
} |
||||
canBroadcast = onHandleLocalizedFor.Emit(receiver, sender, packedMessage); |
||||
if (!canBroadcast) { |
||||
return; |
||||
} |
||||
super.BroadcastLocalized( sender, receiver, message, switch, |
||||
relatedPRI1, relatedPRI2, obj); |
||||
} |
||||
|
||||
event Tick(float delta) |
||||
{ |
||||
ResetTracking(); |
||||
} |
||||
|
||||
// senders, out for handletext |
||||
defaultproperties |
||||
{ |
||||
blockAllowsBroadcast = false |
||||
usedInjectionLevel = BHIJ_Root |
||||
} |
@ -0,0 +1,46 @@
|
||||
/** |
||||
* Signal class implementation for `BroadcastAPI`'s `OnBroadcastCheck` signal. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class Broadcast_OnBroadcastCheck_Signal extends Signal; |
||||
|
||||
public final function bool Emit(Actor broadcaster, int newMessageLength) |
||||
{ |
||||
local Slot nextSlot; |
||||
local bool nextReply; |
||||
StartIterating(); |
||||
nextSlot = GetNextSlot(); |
||||
while (nextSlot != none) |
||||
{ |
||||
nextReply = Broadcast_OnBroadcastCheck_Slot(nextSlot) |
||||
.connect(broadcaster, newMessageLength); |
||||
if (!nextReply && !nextSlot.IsEmpty()) |
||||
{ |
||||
CleanEmptySlots(); |
||||
return false; |
||||
} |
||||
nextSlot = GetNextSlot(); |
||||
} |
||||
CleanEmptySlots(); |
||||
return true; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedSlotClass = class'Broadcast_OnBroadcastCheck_Slot' |
||||
} |
@ -0,0 +1,41 @@
|
||||
/** |
||||
* Slot class implementation for `BroadcastAPI`'s `OnBroadcastCheck` signal. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class Broadcast_OnBroadcastCheck_Slot extends Slot; |
||||
|
||||
delegate bool connect(Actor broadcaster, int newMessageLength) |
||||
{ |
||||
DummyCall(); |
||||
return true; |
||||
} |
||||
|
||||
protected function Constructor() |
||||
{ |
||||
connect = none; |
||||
} |
||||
|
||||
protected function Finalizer() |
||||
{ |
||||
super.Finalizer(); |
||||
connect = none; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
@ -0,0 +1,51 @@
|
||||
/** |
||||
* Signal class implementation for `BroadcastAPI`'s `OnHandleLocalizedFor` |
||||
* signal. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class Broadcast_OnHandleLocalizedFor_Signal extends Signal |
||||
dependson(BroadcastAPI); |
||||
|
||||
public final function bool Emit( |
||||
PlayerController receiver, |
||||
Actor sender, |
||||
BroadcastAPI.LocalizedMessage packedMessage) |
||||
{ |
||||
local Slot nextSlot; |
||||
local bool nextReply; |
||||
StartIterating(); |
||||
nextSlot = GetNextSlot(); |
||||
while (nextSlot != none) |
||||
{ |
||||
nextReply = Broadcast_OnHandleLocalizedFor_Slot(nextSlot) |
||||
.connect(receiver, sender, packedMessage); |
||||
if (!nextReply && !nextSlot.IsEmpty()) |
||||
{ |
||||
CleanEmptySlots(); |
||||
return false; |
||||
} |
||||
nextSlot = GetNextSlot(); |
||||
} |
||||
CleanEmptySlots(); |
||||
return true; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedSlotClass = class'Broadcast_OnHandleLocalizedFor_Slot' |
||||
} |
@ -0,0 +1,46 @@
|
||||
/** |
||||
* Slot class implementation for `BroadcastAPI`'s `OnHandleLocalizedFor` |
||||
* signal. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class Broadcast_OnHandleLocalizedFor_Slot extends Slot |
||||
dependson(BroadcastAPI); |
||||
|
||||
delegate bool connect( |
||||
PlayerController receiver, |
||||
Actor sender, |
||||
BroadcastAPI.LocalizedMessage packedMessage) |
||||
{ |
||||
DummyCall(); |
||||
return true; |
||||
} |
||||
|
||||
protected function Constructor() |
||||
{ |
||||
connect = none; |
||||
} |
||||
|
||||
protected function Finalizer() |
||||
{ |
||||
super.Finalizer(); |
||||
connect = none; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
@ -0,0 +1,50 @@
|
||||
/** |
||||
* Signal class implementation for `BroadcastAPI`'s `OnHandleLocalized` |
||||
* signal. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class Broadcast_OnHandleLocalized_Signal extends Signal |
||||
dependson(BroadcastAPI); |
||||
|
||||
public final function bool Emit( |
||||
Actor sender, |
||||
BroadcastAPI.LocalizedMessage packedMessage) |
||||
{ |
||||
local Slot nextSlot; |
||||
local bool nextReply; |
||||
StartIterating(); |
||||
nextSlot = GetNextSlot(); |
||||
while (nextSlot != none) |
||||
{ |
||||
nextReply = Broadcast_OnHandleLocalized_Slot(nextSlot) |
||||
.connect(sender, packedMessage); |
||||
if (!nextReply && !nextSlot.IsEmpty()) |
||||
{ |
||||
CleanEmptySlots(); |
||||
return false; |
||||
} |
||||
nextSlot = GetNextSlot(); |
||||
} |
||||
CleanEmptySlots(); |
||||
return true; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedSlotClass = class'Broadcast_OnHandleLocalized_Slot' |
||||
} |
@ -0,0 +1,44 @@
|
||||
/** |
||||
* Slot class implementation for `BroadcastAPI`'s `OnHandleLocalized` signal. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class Broadcast_OnHandleLocalized_Slot extends Slot |
||||
dependson(BroadcastAPI); |
||||
|
||||
delegate bool connect( |
||||
Actor sender, |
||||
BroadcastAPI.LocalizedMessage packedMessage) |
||||
{ |
||||
DummyCall(); |
||||
return true; |
||||
} |
||||
|
||||
protected function Constructor() |
||||
{ |
||||
connect = none; |
||||
} |
||||
|
||||
protected function Finalizer() |
||||
{ |
||||
super.Finalizer(); |
||||
connect = none; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
@ -0,0 +1,50 @@
|
||||
/** |
||||
* Signal class implementation for `BroadcastAPI`'s `OnHandleTextFor` signal. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class Broadcast_OnHandleTextFor_Signal extends Signal; |
||||
|
||||
public final function bool Emit( |
||||
PlayerController receiver, |
||||
Actor sender, |
||||
string message, |
||||
name type) |
||||
{ |
||||
local Slot nextSlot; |
||||
local bool nextReply; |
||||
StartIterating(); |
||||
nextSlot = GetNextSlot(); |
||||
while (nextSlot != none) |
||||
{ |
||||
nextReply = Broadcast_OnHandleTextFor_Slot(nextSlot) |
||||
.connect(receiver, sender, message, type); |
||||
if (!nextReply && !nextSlot.IsEmpty()) |
||||
{ |
||||
CleanEmptySlots(); |
||||
return false; |
||||
} |
||||
nextSlot = GetNextSlot(); |
||||
} |
||||
CleanEmptySlots(); |
||||
return true; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedSlotClass = class'Broadcast_OnHandleTextFor_Slot' |
||||
} |
@ -0,0 +1,45 @@
|
||||
/** |
||||
* Slot class implementation for `BroadcastAPI`'s `OnHandleTextFor` signal. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class Broadcast_OnHandleTextFor_Slot extends Slot; |
||||
|
||||
delegate bool connect( |
||||
PlayerController receiver, |
||||
Actor sender, |
||||
string message, |
||||
name type) |
||||
{ |
||||
DummyCall(); |
||||
return true; |
||||
} |
||||
|
||||
protected function Constructor() |
||||
{ |
||||
connect = none; |
||||
} |
||||
|
||||
protected function Finalizer() |
||||
{ |
||||
super.Finalizer(); |
||||
connect = none; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
@ -0,0 +1,50 @@
|
||||
/** |
||||
* Signal class implementation for `BroadcastAPI`'s `OnHandleText` signal. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class Broadcast_OnHandleText_Signal extends Signal; |
||||
|
||||
public final function bool Emit( |
||||
Actor sender, |
||||
out string message, |
||||
name type, |
||||
bool teamMessage) |
||||
{ |
||||
local Slot nextSlot; |
||||
local bool nextReply; |
||||
StartIterating(); |
||||
nextSlot = GetNextSlot(); |
||||
while (nextSlot != none) |
||||
{ |
||||
nextReply = Broadcast_OnHandleText_Slot(nextSlot) |
||||
.connect(sender, message, type, teamMessage); |
||||
if (!nextReply && !nextSlot.IsEmpty()) |
||||
{ |
||||
CleanEmptySlots(); |
||||
return false; |
||||
} |
||||
nextSlot = GetNextSlot(); |
||||
} |
||||
CleanEmptySlots(); |
||||
return true; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedSlotClass = class'Broadcast_OnHandleText_Slot' |
||||
} |
@ -0,0 +1,45 @@
|
||||
/** |
||||
* Slot class implementation for `BroadcastAPI`'s `OnHandleText` signal. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class Broadcast_OnHandleText_Slot extends Slot; |
||||
|
||||
delegate bool connect( |
||||
Actor sender, |
||||
out string message, |
||||
name type, |
||||
bool teamMessage) |
||||
{ |
||||
DummyCall(); |
||||
return true; |
||||
} |
||||
|
||||
protected function Constructor() |
||||
{ |
||||
connect = none; |
||||
} |
||||
|
||||
protected function Finalizer() |
||||
{ |
||||
super.Finalizer(); |
||||
connect = none; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
@ -0,0 +1,51 @@
|
||||
/** |
||||
* Signal class implementation for `GameRulesAPI`'s |
||||
* `OnPreventDeathSignal` signal. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class GameRules_OnPreventDeath_Signal extends Signal; |
||||
|
||||
public final function bool Emit( |
||||
Pawn killed, |
||||
Controller killer, |
||||
class<DamageType> damageType, |
||||
Vector hitLocation) |
||||
{ |
||||
local Slot nextSlot; |
||||
local bool shouldPrevent; |
||||
StartIterating(); |
||||
nextSlot = GetNextSlot(); |
||||
while (nextSlot != none) |
||||
{ |
||||
shouldPrevent = GameRules_OnPreventDeath_Slot(nextSlot) |
||||
.connect(killed, killer, damageType, hitLocation); |
||||
if (shouldPrevent && !nextSlot.IsEmpty()) |
||||
{ |
||||
CleanEmptySlots(); |
||||
return shouldPrevent; |
||||
} |
||||
nextSlot = GetNextSlot(); |
||||
} |
||||
CleanEmptySlots(); |
||||
return false; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedSlotClass = class'GameRules_OnPreventDeath_Slot' |
||||
} |
@ -0,0 +1,47 @@
|
||||
/** |
||||
* Slot class implementation for `GameRulesAPI`'s |
||||
* `OnPreventDeathSignal` signal. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class GameRules_OnPreventDeath_Slot extends Slot; |
||||
|
||||
delegate bool connect( |
||||
Pawn killed, |
||||
Controller killer, |
||||
class<DamageType> damageType, |
||||
Vector hitLocation) |
||||
{ |
||||
DummyCall(); |
||||
// Do not override pickup queue by default |
||||
return false; |
||||
} |
||||
|
||||
protected function Constructor() |
||||
{ |
||||
connect = none; |
||||
} |
||||
|
||||
protected function Finalizer() |
||||
{ |
||||
super.Finalizer(); |
||||
connect = none; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
@ -0,0 +1,38 @@
|
||||
/** |
||||
* Signal class implementation for `GameRulesAPI`'s `OnScoreKill` signal. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class GameRules_OnScoreKill_Signal extends Signal; |
||||
|
||||
public final function Emit(Controller killer, Controller killed) |
||||
{ |
||||
local Slot nextSlot; |
||||
StartIterating(); |
||||
nextSlot = GetNextSlot(); |
||||
while (nextSlot != none) |
||||
{ |
||||
GameRules_OnScoreKill_Slot(nextSlot).connect(killer, killed); |
||||
nextSlot = GetNextSlot(); |
||||
} |
||||
CleanEmptySlots(); |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedSlotClass = class'GameRules_OnScoreKill_Slot' |
||||
} |
@ -0,0 +1,40 @@
|
||||
/** |
||||
* Slot class implementation for `GameRulesAPI`'s `OnScoreKill` signal. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class GameRules_OnScoreKill_Slot extends Slot; |
||||
|
||||
delegate connect(Controller killer, Controller killed) |
||||
{ |
||||
DummyCall(); |
||||
} |
||||
|
||||
protected function Constructor() |
||||
{ |
||||
connect = none; |
||||
} |
||||
|
||||
protected function Finalizer() |
||||
{ |
||||
super.Finalizer(); |
||||
connect = none; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
@ -0,0 +1,46 @@
|
||||
/** |
||||
* Signal class implementation for `MutatorAPI`'s `OnCheckReplacement` signal. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class Mutator_OnCheckReplacement_Signal extends Signal; |
||||
|
||||
public final function bool Emit(Actor other, out byte isSuperRelevant) |
||||
{ |
||||
local bool isRelevant; |
||||
local Slot nextSlot; |
||||
StartIterating(); |
||||
nextSlot = GetNextSlot(); |
||||
while (nextSlot != none) |
||||
{ |
||||
isRelevant = Mutator_OnCheckReplacement_Slot(nextSlot) |
||||
.connect(other, isSuperRelevant); |
||||
if (!isRelevant && !nextSlot.IsEmpty()) |
||||
{ |
||||
CleanEmptySlots(); |
||||
return false; |
||||
} |
||||
nextSlot = GetNextSlot(); |
||||
} |
||||
CleanEmptySlots(); |
||||
return true; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedSlotClass = class'Mutator_OnCheckReplacement_Slot' |
||||
} |
@ -0,0 +1,41 @@
|
||||
/** |
||||
* Slot class implementation for `MutatorAPI`'s `OnCheckReplacement` signal. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class Mutator_OnCheckReplacement_Slot extends Slot; |
||||
|
||||
delegate bool connect(Actor other, out byte isSuperRelevant) |
||||
{ |
||||
DummyCall(); |
||||
return true; |
||||
} |
||||
|
||||
protected function Constructor() |
||||
{ |
||||
connect = none; |
||||
} |
||||
|
||||
protected function Finalizer() |
||||
{ |
||||
super.Finalizer(); |
||||
connect = none; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
@ -0,0 +1,38 @@
|
||||
/** |
||||
* Signal class implementation for `MutatorAPI`'s `OnMutate` signal. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class Mutator_OnMutate_Signal extends Signal; |
||||
|
||||
public final function Emit(string command, PlayerController sendingPlayer) |
||||
{ |
||||
local Slot nextSlot; |
||||
StartIterating(); |
||||
nextSlot = GetNextSlot(); |
||||
while (nextSlot != none) |
||||
{ |
||||
Mutator_OnMutate_Slot(nextSlot).connect(command, sendingPlayer); |
||||
nextSlot = GetNextSlot(); |
||||
} |
||||
CleanEmptySlots(); |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedSlotClass = class'Mutator_OnMutate_Slot' |
||||
} |
@ -0,0 +1,40 @@
|
||||
/** |
||||
* Slot class implementation for `MutatorAPI`'s `OnMutate` signal. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class Mutator_OnMutate_Slot extends Slot; |
||||
|
||||
delegate connect(string command, PlayerController sendingPlayer) |
||||
{ |
||||
DummyCall(); |
||||
} |
||||
|
||||
protected function Constructor() |
||||
{ |
||||
connect = none; |
||||
} |
||||
|
||||
protected function Finalizer() |
||||
{ |
||||
super.Finalizer(); |
||||
connect = none; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
@ -0,0 +1,92 @@
|
||||
/** |
||||
* Low-level API that provides set of utility methods for working with |
||||
* `Mutator`s. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class MutatorAPI extends AcediaObject; |
||||
|
||||
/** |
||||
* Called whenever mutators (Acedia's mutator) is asked to check whether |
||||
* an `Actor` should be replaced. This check is done right after that `Actor` |
||||
* has spawned. |
||||
* |
||||
* This check is called in UnrealScript and defined in base `Actor` class |
||||
* inside `PreBeginPlay()` event. It makes each `Actor` call base mutator's |
||||
* (the one linked as the head of the mutator linked list in `GameInfo`) |
||||
* `CheckRelevance()` method for itself as long as it has |
||||
* `bGameRelevant == false` and current `NetMode` is not `NM_Client`. |
||||
* `CheckRelevance()` is only called on the base mutator and always first |
||||
* checks with `AlwaysKeep()` method, that allows any mutator to prevent any |
||||
* further check altogether and then `IsRelevant()` check that then calls |
||||
* sub-check `CheckReplacement()` this signal catches. |
||||
* Any described event that is not `CheckRelevance()` is propagated through |
||||
* the linked mutator list. |
||||
* |
||||
* [Signature] |
||||
* bool <slot>(Actor other, out byte isSuperRelevant) |
||||
* |
||||
* @param other `Actor` that is checked for |
||||
* replacement / modification. |
||||
* @param isSuperRelevant Variable with unclear intention. It is defined in |
||||
* base mutator's `CheckRelevance()` method as a local variable and then |
||||
* passed as an `out` parameter for `IsRelevant()` and `CheckRelevance()` |
||||
* checks and not really used for anything once these checks are complete. |
||||
* Some [sources] |
||||
* (https://wiki.beyondunreal.com/Legacy:Chain_Of_Events_At_Level_Startup) |
||||
* indicate that it used to omit additional `GameInfo`'s relevancy checks, |
||||
* however does not to serve any function in Killing Floor. |
||||
* Mutators might repurpose it for their own uses, but I am not aware of |
||||
* any that do. |
||||
* @return `false` if you want `other` to be destroyed and `true` otherwise. |
||||
*/ |
||||
/* SIGNAL */ |
||||
public final function Mutator_OnCheckReplacement_Slot OnCheckReplacement( |
||||
AcediaObject receiver) |
||||
{ |
||||
local Signal signal; |
||||
local UnrealService service; |
||||
service = UnrealService(class'UnrealService'.static.Require()); |
||||
signal = service.GetSignal(class'Mutator_OnCheckReplacement_Signal'); |
||||
return Mutator_OnCheckReplacement_Slot(signal.NewSlot(receiver)); |
||||
} |
||||
|
||||
/** |
||||
* Called on a server whenever a player uses a "mutate" console command. |
||||
* |
||||
* [Signature] |
||||
* <slot>(string command, PlayerController sendingPlayer) |
||||
* |
||||
* @param command Text, typed by the player after "mutate" command, |
||||
* trimming spaces from the left. |
||||
* @param sendingPlayer Controller of the player who typed command that |
||||
* caused this call. |
||||
*/ |
||||
/* SIGNAL */ |
||||
public final function Mutator_OnMutate_Slot OnMutate( |
||||
AcediaObject receiver) |
||||
{ |
||||
local Signal signal; |
||||
local UnrealService service; |
||||
service = UnrealService(class'UnrealService'.static.Require()); |
||||
signal = service.GetSignal(class'Mutator_OnMutate_Signal'); |
||||
return Mutator_OnMutate_Slot(signal.NewSlot(receiver)); |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
Loading…
Reference in new issue