Browse Source

Add `InfoQueryHandler`

pull/8/head
Anton Tarasenko 2 years ago
parent
commit
88e217d747
  1. 95
      sources/Commands/Commands_Feature.uc
  2. 27
      sources/Console/ConsoleAPI.uc
  3. 13
      sources/CoreService.uc
  4. 31
      sources/Features/Feature.uc
  5. 47
      sources/InfoQueryHandler/Events/InfoQueryHandler_OnQuery_Signal.uc
  6. 65
      sources/InfoQueryHandler/Events/InfoQueryHandler_OnQuery_Slot.uc
  7. 312
      sources/InfoQueryHandler/InfoQueryHandler.uc

95
sources/Commands/Commands_Feature.uc

@ -28,6 +28,10 @@ var private array<Text> commandDelimiters;
// Keys should be deallocated when their entry is removed.
var private AssociativeArray registeredCommands;
// When this flag is set to true, mutate input becomes available
// despite `useMutateInput` flag to allow to unlock server in case of an error
var private bool emergencyEnabledMutate;
// Setting this to `true` enables players to input commands right in the chat
// by prepending them with `chatCommandPrefix`.
// Default is `true`.
@ -54,6 +58,12 @@ protected function OnEnabled()
commandDelimiters[2] = P("[");
// Negation of the selector
commandDelimiters[3] = P("!");
// `SwapConfig()` will no longer touch `_.unreal.mutator.OnMutate(self)`
// with `emergencyEnabledMutate` set to `true`, so we need to give
// access here
if (emergencyEnabledMutate) {
_.unreal.mutator.OnMutate(self).connect = HandleMutate;
}
}
protected function OnDisabled()
@ -96,7 +106,9 @@ protected function SwapConfig(FeatureConfig config)
_.chat.OnMessage(self).Disconnect();
}
}
if (useMutateInput != newConfig.useMutateInput)
// Do not make any modifications here in case "mutate" was
// emergency-enabled
if (useMutateInput != newConfig.useMutateInput && !emergencyEnabledMutate)
{
useMutateInput = newConfig.useMutateInput;
if (newConfig.useMutateInput) {
@ -108,6 +120,87 @@ protected function SwapConfig(FeatureConfig config)
}
}
/**
* `Command_Feature` is a critical command to have running on your server and,
* if disabled by accident, there will be no way of starting it again without
* restarting the level or even editing configs.
*
* This method allows to enable it along with "mutate" input in case something
* goes wrong.
*/
public final static function EmergencyEnable()
{
local Text autoConfig;
local Commands_Feature feature;
if (!IsEnabled())
{
autoConfig = GetAutoEnabledConfig();
EnableMe(autoConfig);
__().memory.Free(autoConfig);
}
feature = Commands_Feature(GetInstance());
if ( !feature.emergencyEnabledMutate
&& !feature.IsUsingMutateInput() && !feature.IsUsingChatInput())
{
default.emergencyEnabledMutate = true;
feature.emergencyEnabledMutate = true;
__().unreal.mutator.OnMutate(feature).connect = HandleMutate;
}
}
/**
* Checks if `Commands_Feature` currently uses chat as input.
* If `Commands_Feature` is not enabled, then it does not use anything
* as input.
*
* @return `true` if `Commands_Feature` is currently enabled and is using chat
* as input and `false` otherwise.
*/
public final static function bool IsUsingChatInput()
{
local Commands_Feature instance;
instance = Commands_Feature(GetInstance());
if (instance != none) {
return instance.useChatInput;
}
return false;
}
/**
* Checks if `Commands_Feature` currently uses mutate command as input.
* If `Commands_Feature` is not enabled, then it does not use anything
* as input.
*
* @return `true` if `Commands_Feature` is currently enabled and is using
* mutate command as input and `false` otherwise.
*/
public final static function bool IsUsingMutateInput()
{
local Commands_Feature instance;
instance = Commands_Feature(GetInstance());
if (instance != none) {
return instance.useMutateInput;
}
return false;
}
/**
* Returns prefix that will indicate that chat message is intended to be
* a command. By default "!".
*
* @return Prefix that indicates that chat message is intended to be a command.
* If `Commands_Feature` is disabled, always returns `false`.
*/
public final static function Text GetChatPrefix()
{
local Commands_Feature instance;
instance = Commands_Feature(GetInstance());
if (instance != none && instance.chatCommandPrefix != none) {
return instance.chatCommandPrefix.Copy();
}
return none;
}
/**
* Registers given command class, making it available for usage.
*

27
sources/Console/ConsoleAPI.uc

@ -177,7 +177,7 @@ public final function Color GetDefaultColor(int newMaxTotalLineWidth)
}
/**
* Sets current global default color for console output.,
* Sets current global default color for console output.
*
* Instances of `ConsoleWriter` are initialized with this value,
* but can later change this value independently.
@ -193,7 +193,6 @@ public final function SetDefaultColor(Color newDefaultColor)
/**
* Returns new `ConsoleWriter` instance that will write into
* consoles of all players.
* Should be freed after use.
*
* @return ConsoleWriter New `ConsoleWriter` instance, configured to
* write into consoles of all players.
@ -212,13 +211,12 @@ public final function ConsoleWriter ForAll()
/**
* Returns new `ConsoleWriter` instance that will write into
* console of the given player.
* Should be freed after use.
*
* @param targetPlayer Player, to whom console we want to write.
* If `none` - returned `ConsoleWriter` would be configured to
* throw messages away.
* @return New `ConsoleWriter` instance, configured to
* write into consoles of all players.
* write into console of `targetPlayer`.
* Guaranteed to not be `none`.
*/
public final function ConsoleWriter For(EPlayer targetPlayer)
@ -231,6 +229,27 @@ public final function ConsoleWriter For(EPlayer targetPlayer)
.Initialize(globalSettings).ForPlayer(targetPlayer);
}
/**
* Returns new `ConsoleWriter` instance that will write into
* console of the given player.
*
* @param targetPlayer Player, to whom console we want to write.
* If `none` - returned `ConsoleWriter` would be configured to
* throw messages away.
* @return New `ConsoleWriter` instance, configured to
* write into console of `targetPlayer`.
* Guaranteed to not be `none`.
*/
public final function ConsoleWriter ForController(PlayerController targetPlayer)
{
local EPlayer wrapper;
local ConsoleWriter result;
wrapper = _.players.FromController(targetPlayer);
result = For(wrapper);
_.memory.Free(wrapper);
return result;
}
defaultproperties
{
defaultColor = (R=255,G=255,B=255,A=255)

13
sources/CoreService.uc

@ -52,7 +52,7 @@ var private LoggerAPI.Definition errorNoManifest, errorCannotRunTests;
// We do not implement `OnShutdown()`, because total Acedia's clean up
// is supposed to happen before that event.
protected function OnCreated()
protected function OnLaunch()
{
BootUp();
default.packagesToLoad.length = 0;
@ -161,6 +161,8 @@ private final function BootUp()
if (class'TestingService'.default.runTestsOnStartUp) {
RunStartUpTests();
}
class'InfoQueryHandler'.static.StaticConstructor();
_.unreal.mutator.OnMutate(_self).connect = EnableCommandsFeature;
}
private final function LoadManifest(class<_manifest> manifestClass)
@ -243,6 +245,15 @@ private final function RunStartUpTests()
}
}
private final function EnableCommandsFeature(
string command,
PlayerController sendingPlayer)
{
if (command ~= "acediacommands") {
class'Commands_Feature'.static.EmergencyEnable();
}
}
/**
* Registers class derived from `AcediaObject` for clean up when
* Acedia shuts down.

31
sources/Features/Feature.uc

@ -56,6 +56,7 @@ var public const class<FeatureConfig> configClass;
// Only a default value is ever used.
var protected bool blockSpawning;
// TODO: remove this?
// Setting that tells Acedia whether or not to enable this feature
// during initialization.
// Only it's default value is ever used.
@ -124,9 +125,10 @@ protected function Finalizer()
default.currentConfigName = none;
currentConfigName = none;
currentConfig = none;
default.activeInstance = none;
default.activeInstance = none;
}
// TODO: free `newConfigName`?
/**
* Changes config for the caller `Feature` class.
*
@ -142,9 +144,6 @@ private final function ApplyConfig(BaseText newConfigName)
{
local Text configNameCopy;
local FeatureConfig newConfig;
if (newConfigName == none) {
return;
}
newConfig =
FeatureConfig(configClass.static.GetConfigInstance(newConfigName));
if (newConfig == none)
@ -174,15 +173,6 @@ private final function ApplyConfig(BaseText newConfigName)
*/
public final static function Feature GetInstance()
{
if (default.activeInstance == none) {
return none;
}
if ( default.activeInstance.GetLifeVersion()
!= default.activeInstanceLifeVersion)
{
default.activeInstance = none;
return none;
}
return default.activeInstance;
}
@ -246,14 +236,11 @@ public static final function bool IsEnabled()
/**
* Enables the feature and returns it's active instance.
*
* Does nothing if passed `configName` is `none`.
*
* Cannot fail as long as `configName != none`. Any checks on whether it's
* appropriate to enable `Feature` must be done separately, before calling
* this method.
*
* If `Feature` is already enabled - changes its config to `configName`
* (unless it's `none`).
* If `Feature` is already enabled - changes its config to `configName`.
*
* @param configName Name of the config to use for this `Feature`.
* Passing `none` will make caller `Feature` use "default" config.
@ -262,16 +249,18 @@ public static final function bool IsEnabled()
public static final function Feature EnableMe(BaseText configName)
{
local Feature myInstance;
if (configName == none) {
return none;
}
myInstance = GetInstance();
if (myInstance != none)
{
myInstance.ApplyConfig(configName);
return myInstance;
}
default.currentConfigName = configName.Copy();
if (configName != none) {
default.currentConfigName = configName.Copy();
}
else {
default.currentConfigName = none;
}
default.blockSpawning = false;
myInstance = Feature(__().memory.Allocate(default.class));
default.activeInstance = myInstance;

47
sources/InfoQueryHandler/Events/InfoQueryHandler_OnQuery_Signal.uc

@ -0,0 +1,47 @@
/**
* Signal class implementation for `InfoQueryHandler`'s signals.
* Copyright 2022 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 InfoQueryHandler_OnQuery_Signal extends Signal;
public final function Emit(ConsoleWriter writer)
{
local Text nextHeader;
local Slot nextSlot;
local InfoQueryHandler_OnQuery_Slot nextQuerySlot;
StartIterating();
nextSlot = GetNextSlot();
while (nextSlot != none)
{
nextQuerySlot = InfoQueryHandler_OnQuery_Slot(nextSlot);
// Output slot info
nextHeader = nextQuerySlot.GetHeader();
class'InfoQueryHandler'.static.AddHeader(nextHeader);
_.memory.Free(nextHeader);
nextQuerySlot.connect(writer);
class'InfoQueryHandler'.static.AddSeparator();
// gg go next
nextSlot = GetNextSlot();
}
CleanEmptySlots();
}
defaultproperties
{
relatedSlotClass = class'InfoQueryHandler_OnQuery_Slot'
}

65
sources/InfoQueryHandler/Events/InfoQueryHandler_OnQuery_Slot.uc

@ -0,0 +1,65 @@
/**
* Slot class implementation for `InfoQueryHandler`'s signals.
* Copyright 2022 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 InfoQueryHandler_OnQuery_Slot extends Slot;
var private Text linkedHeader;
delegate connect(ConsoleWriter writer)
{
DummyCall();
}
protected function Constructor()
{
connect = none;
}
protected function Finalizer()
{
super.Finalizer();
connect = none;
_.memory.Free(linkedHeader);
linkedHeader = none;
}
public final function InitializeHeader(BaseText header)
{
if (linkedHeader != none) {
return;
}
if (header != none) {
linkedHeader = header.Copy();
}
else {
linkedHeader = P("").Copy();
}
}
public final function Text GetHeader()
{
if (linkedHeader != none) {
return linkedHeader.Copy();
}
return none;
}
defaultproperties
{
}

312
sources/InfoQueryHandler/InfoQueryHandler.uc

@ -0,0 +1,312 @@
/**
* Utility that help AcediaCore and its `Feature`s to add information to
* console queries like "help", "status", etc. in a more unified way.
* In Killing Floor this corresponds to "mutate" command.
* Copyright 2022 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 InfoQueryHandler extends AcediaObject
abstract;
var private ServiceAnchor anchor;
var private ConsoleWriter currentOutput;
var private InfoQueryHandler_OnQuery_Signal onHelpSignal;
var private InfoQueryHandler_OnQuery_Signal onStatusSignal;
var private InfoQueryHandler_OnQuery_Signal onVersionSignal;
var private InfoQueryHandler_OnQuery_Signal onCreditsSignal;
var private const int TACEDIA_HEADER, TACEDIA_SUBHEADER, TACEDIA_HELP;
var private const int TACEDIA_HELP_COMMANDS_CHAT, TACEDIA_HELP_COMMANDS_CONSOLE;
var private const int TACEDIA_HELP_COMMANDS_CHAT_AND_CONSOLE;
var private const int TACEDIA_HELP_COMMANDS_NO, TACEDIA_HELP_COMMANDS_USELESS;
var private const int TACEDIA_RUNNING, TACEDIA_VERSION, TACEDIA_CREDITS;
var private const int TACEDIA_ACKNOWLEDGMENT, TPREFIX, TSEPARATOR;
public static function StaticConstructor()
{
if (StaticConstructorGuard()) {
return;
}
default.anchor = ServiceAnchor(__().memory.Allocate(class'ServiceAnchor'));
default.onHelpSignal = InfoQueryHandler_OnQuery_Signal(
__().memory.Allocate(class'InfoQueryHandler_OnQuery_Signal'));
default.onStatusSignal = InfoQueryHandler_OnQuery_Signal(
__().memory.Allocate(class'InfoQueryHandler_OnQuery_Signal'));
default.onVersionSignal = InfoQueryHandler_OnQuery_Signal(
__().memory.Allocate(class'InfoQueryHandler_OnQuery_Signal'));
default.onCreditsSignal = InfoQueryHandler_OnQuery_Signal(
__().memory.Allocate(class'InfoQueryHandler_OnQuery_Signal'));
// We cannot make an instance of an abstract `InfoQueryHandler` class,
// use created `ConsoleWriter` to connect
__().unreal.mutator.OnMutate(default.anchor).connect = HandleMutate;
}
/**
* Called when user uses appropriate tools to request "help" via console query.
*
* [Signature]
* <slot>()
*/
/* SIGNAL */
public final static function InfoQueryHandler_OnQuery_Slot OnHelp(
AcediaObject receiver,
Text header)
{
local InfoQueryHandler_OnQuery_Slot newSlot;
StaticConstructor();
newSlot = InfoQueryHandler_OnQuery_Slot(
default.onHelpSignal.NewSlot(receiver));
newSlot.InitializeHeader(header);
return newSlot;
}
/**
* Called when user uses appropriate tools to request "status" via console
* query.
*
* [Signature]
* <slot>()
*/
/* SIGNAL */
public final static function InfoQueryHandler_OnQuery_Slot OnStatus(
AcediaObject receiver,
Text header)
{
local InfoQueryHandler_OnQuery_Slot newSlot;
StaticConstructor();
newSlot = InfoQueryHandler_OnQuery_Slot(
default.onStatusSignal.NewSlot(receiver));
newSlot.InitializeHeader(header);
return newSlot;
}
/**
* Called when user uses appropriate tools to request "version" via console
* query.
*
* [Signature]
* <slot>()
*/
/* SIGNAL */
public final static function InfoQueryHandler_OnQuery_Slot OnVersion(
AcediaObject receiver,
Text header)
{
local InfoQueryHandler_OnQuery_Slot newSlot;
StaticConstructor();
newSlot = InfoQueryHandler_OnQuery_Slot(
default.onVersionSignal.NewSlot(receiver));
newSlot.InitializeHeader(header);
return newSlot;
}
/**
* Called when user uses appropriate tools to request "credits" via console
* query.
*
* [Signature]
* <slot>()
*/
/* SIGNAL */
public final static function InfoQueryHandler_OnQuery_Slot OnCredits(
AcediaObject receiver,
Text header)
{
local InfoQueryHandler_OnQuery_Slot newSlot;
StaticConstructor();
newSlot = InfoQueryHandler_OnQuery_Slot(
default.onCreditsSignal.NewSlot(receiver));
newSlot.InitializeHeader(header);
return newSlot;
}
/**
* Adds header for a component of Acedia named `headerText` to the current
* output. Implemented to only work during `InfoQueryHandler`'s signals'
* propagation.
*
* @param headerText Name of the Acedia's component to print header for.
*/
public final static function AddHeader(Text headerText)
{
if (default.currentOutput == none) {
return;
}
AddSeparator();
default.currentOutput
.Write(T(default.TACEDIA_SUBHEADER))
.UseColorOnce(__().color.yellow)
.WriteLine(headerText);
AddSeparator();
}
/**
* Adds standard line separator to the current output. Implemented to only work
* during `InfoQueryHandler`'s signals' propagation.
*/
public final static function AddSeparator()
{
if (default.currentOutput == none) {
return;
}
default.currentOutput
.Flush()
.UseColorOnce(__().color.white)
.WriteLine(T(default.TSEPARATOR));
}
private final static function HandleMutate(
string command,
PlayerController sendingPlayer)
{
if (!( command ~= "help"
|| command ~= "status"
|| command ~= "version"
|| command ~= "credits"))
{
return;
}
StartOutput(sendingPlayer);
AddSeparator();
default.currentOutput.WriteLine(T(default.TACEDIA_HEADER));
AddSeparator();
if (command ~= "help")
{
OutAcediaHelp();
default.onHelpSignal.Emit(default.currentOutput);
}
else if (command ~= "status")
{
OutAcediaStatus();
default.onStatusSignal.Emit(default.currentOutput);
}
else if (command ~= "version")
{
OutAcediaVersion();
default.onVersionSignal.Emit(default.currentOutput);
}
else if (command ~= "credits")
{
OutAcediaCredits();
default.onCreditsSignal.Emit(default.currentOutput);
}
AddSeparator();
StopOutput();
}
private final static function StartOutput(PlayerController targetPlayer)
{
default.currentOutput = __().console.ForController(targetPlayer);
}
private final static function StopOutput()
{
__().memory.Free(default.currentOutput);
default.currentOutput = none;
}
private final static function OutAcediaHelp()
{
local Text prefix;
local MutableText builder;
default.currentOutput
.Flush()
.WriteLine(T(default.TACEDIA_HELP));
prefix = class'Commands_Feature'.static.GetChatPrefix();
if (!class'Commands_Feature'.static.IsEnabled()) {
default.currentOutput.WriteLine(T(default.TACEDIA_HELP_COMMANDS_NO));
}
else if ( class'Commands_Feature'.static.IsUsingChatInput()
&& class'Commands_Feature'.static.IsUsingMutateInput())
{
builder =
T(default.TACEDIA_HELP_COMMANDS_CHAT_AND_CONSOLE).MutableCopy();
builder.Replace(T(default.TPREFIX), prefix);
default.currentOutput.WriteLine(builder);
__().memory.Free(builder);
}
else if (class'Commands_Feature'.static.IsUsingChatInput())
{
builder =
T(default.TACEDIA_HELP_COMMANDS_CHAT).MutableCopy();
builder.Replace(T(default.TPREFIX), prefix);
default.currentOutput.WriteLine(builder);
__().memory.Free(builder);
}
else if (class'Commands_Feature'.static.IsUsingMutateInput())
{
default.currentOutput
.WriteLine(T(default.TACEDIA_HELP_COMMANDS_CONSOLE));
}
else
{
default.currentOutput
.WriteLine(T(default.TACEDIA_HELP_COMMANDS_USELESS));
}
__().memory.Free(prefix);
}
private final static function OutAcediaStatus()
{
default.currentOutput.WriteLine(T(default.TACEDIA_RUNNING));
}
private final static function OutAcediaVersion()
{
default.currentOutput.WriteLine(T(default.TACEDIA_VERSION));
}
private final static function OutAcediaCredits()
{
default.currentOutput.WriteLine(T(default.TACEDIA_CREDITS));
default.currentOutput.WriteLine(T(default.TACEDIA_ACKNOWLEDGMENT));
}
defaultproperties
{
TACEDIA_HEADER = 0
stringConstants(0) = "{$red Acedia Framework}"
TACEDIA_SUBHEADER = 1
stringConstants(1) = "{$red Acedia Framework}{$white / }"
TACEDIA_HELP = 2
stringConstants(2) = "Acedia always supports four commands: {$TextEmphasis help}, {$TextEmphasis status}, {$TextEmphasis version} and {$TextEmphasis credits}"
TACEDIA_HELP_COMMANDS_CHAT = 3
stringConstants(3) = "To get detailed information about available to you commands, please type {$TextEmphasis %PREFIX%help -l} in chat"
TACEDIA_HELP_COMMANDS_CONSOLE = 4
stringConstants(4) = "To get detailed information about available to you commands, please type {$TextEmphasis mutate help -l} in console"
TACEDIA_HELP_COMMANDS_CHAT_AND_CONSOLE = 5
stringConstants(5) = "To get detailed information about available to you commands, please type {$TextEmphasis %PREFIX%help -l} in chat or {$TextEmphasis mutate help -l} in console"
TACEDIA_HELP_COMMANDS_NO = 6
stringConstants(6) = "Unfortunately other commands aren't available right now. To enable them please type {$TextEmphasis mutate acediacommands} in console if you have enough rights to reenable them."
TACEDIA_HELP_COMMANDS_USELESS = 7
stringConstants(7) = "Unfortunately every known way to access other command is disabled on this server. To enable them please type {$TextEmphasis mutate acediacommands} in console if you have enough rights to reenable them."
TACEDIA_RUNNING = 8
stringConstants(8) = "AcediaCore is running"
TACEDIA_VERSION = 9
stringConstants(9) = "AcediaCore version 0.1.dev6 - this is a development version, bugs and issues are expected"
TACEDIA_CREDITS = 10
stringConstants(10) = "AcediaCore was developed by dkanus, 2019 - 2022"
TACEDIA_ACKNOWLEDGMENT = 11
stringConstants(11) = "Special thanks for NikC- and Chaos for suggestions, testing and discussion"
TPREFIX = 12
stringConstants(12) = "{$TextEmphasis %PREFIX%}"
TSEPARATOR = 13
stringConstants(13) = "============================="
}
Loading…
Cancel
Save