diff --git a/sources/BaseAPI/API/Commands/BuiltInCommands/ACommandSideEffects.uc b/sources/BaseAPI/API/Commands/BuiltInCommands/ACommandSideEffects.uc new file mode 100644 index 0000000..e28845f --- /dev/null +++ b/sources/BaseAPI/API/Commands/BuiltInCommands/ACommandSideEffects.uc @@ -0,0 +1,193 @@ +/** + * Author: dkanus + * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore + * License: GPL + * Copyright 2023 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 . + */ +class ACommandSideEffects extends Command; + +// Maps `UserID` to `ArrayList` with side effects listed for that player last time +var private HashTable displayedLists; + +protected function Constructor() { + super.Constructor(); + displayedLists = _.collections.EmptyHashTable(); +} + +protected function Finalizer() { + super.Finalizer(); + _.memory.Free(displayedLists); + displayedLists = none; +} + +protected function BuildData(CommandDataBuilder builder) { + builder.Name(P("sideeffects")); + builder.Group(P("core")); + builder.Summary(P("Displays information about current side effects.")); + builder.Describe(P("This command allows to display current side effects, optionally filtering" + @ "them by specified package names.")); + builder.OptionalParams(); + builder.ParamTextList(P("package_names")); + + builder.SubCommand(P("show")); + builder.Describe(P("This sub-command is only usable after side effects have been shown" + @ "at least once. It takes an index from the last displayed list and displays a verbose" + @ "information about it.")); + builder.ParamInteger(P("side_effect_number")); + + builder.Option(P("verbose")); + builder.Describe(P("Display verbose information about each side effect.")); +} + +protected function Executed(CallData arguments, EPlayer instigator) { + local UserID playerID; + local array relevantSideEffects; + local ArrayList packagesList, storedSideEffectsList; + + playerID = instigator.GetUserID(); + if (arguments.subCommandName.IsEmpty()) { + relevantSideEffects = _.sideEffects.GetAll(); + packagesList = arguments.parameters.GetArrayList(P("package_names")); + FilterSideEffects(/*out*/ relevantSideEffects, packagesList); + _.memory.Free(packagesList); + DisplaySideEffects(relevantSideEffects, arguments.options.HasKey(P("verbose"))); + // Store new side effect list + storedSideEffectsList = _.collections.NewArrayList(relevantSideEffects); + displayedLists.SetItem(playerID, storedSideEffectsList); + _.memory.FreeMany(relevantSideEffects); + _.memory.Free(storedSideEffectsList); + } else { + ShowInfoFor(playerID, arguments.parameters.GetInt(P("side_effect_number"))); + } + _.memory.Free(playerID); +} + +private function FilterSideEffects(out array sideEffects, ArrayList allowedPackages) { + local int i, j; + local int packagesLength; + local bool matchedPackage; + local Text nextSideEffectPackage, nextAllowedPackage; + + if (allowedPackages == none) return; + if (allowedPackages.GetLength() <= 0) return; + + packagesLength = allowedPackages.GetLength(); + while (i < sideEffects.length) { + nextSideEffectPackage = sideEffects[i].GetPackage(); + matchedPackage = false; + for (j = 0; j < packagesLength; j += 1) { + nextAllowedPackage = allowedPackages.GetText(j); + if (nextAllowedPackage.Compare(nextSideEffectPackage, SCASE_INSENSITIVE)) { + matchedPackage = true; + _.memory.Free(nextAllowedPackage); + break; + } + _.memory.Free(nextAllowedPackage); + } + if (!matchedPackage) { + sideEffects.Remove(i, 1); + } else { + i += 1; + } + _.memory.Free(nextSideEffectPackage); + } +} + +private function DisplaySideEffects(array toDisplay, bool verbose) { + local int i; + local MutableText nextPrefix; + + if (toDisplay.length <= 0) { + callerConsole.Write(F("List of side effects is {$TextNeutral empty}.")); + } + for (i = 0; i < toDisplay.length; i += 1) { + nextPrefix = _.text.FromIntM(i + 1); + nextPrefix.Append(P(".")); + DisplaySideEffect(toDisplay[i], nextPrefix, verbose); + _.memory.Free(nextPrefix); + } +} + +private function DisplaySideEffect(SideEffect toDisplay, BaseText prefix, bool verbose) { + local Text effectName, effectDescription, effectPackage, effectSource, effectStatus; + + if (toDisplay == none) { + return; + } + if (prefix != none) { + callerConsole.Write(prefix); + callerConsole.Write(P(" ")); + } + effectName = toDisplay.GetName(); + effectPackage = toDisplay.GetPackage(); + effectSource = toDisplay.GetSource(); + effectStatus = toDisplay.GetStatus(); + callerConsole.UseColor(_.color.TextEmphasis); + callerConsole.Write(P("[")); + callerConsole.Write(effectPackage); + callerConsole.Write(P(" \\ ")); + callerConsole.Write(effectSource); + callerConsole.Write(P("] ")); + callerConsole.ResetColor(); + callerConsole.Write(effectName); + callerConsole.Write(P(" {")); + callerConsole.Write(effectStatus); + callerConsole.WriteLine(P("}")); + if (verbose) { + effectDescription = toDisplay.GetDescription(); + callerConsole.WriteBlock(effectDescription); + } + _.memory.Free5(effectName, effectDescription, effectPackage, effectSource, effectStatus); +} + +private function ShowInfoFor(UserID playerID, int sideEffectIndex) { + local SideEffect toDisplay; + local ArrayList sideEffectList; + + if (playerID == none) { + return; + } + if (sideEffectIndex <= 0) { + callerConsole.WriteLine(F("Specified side effect index {$TextNegative isn't positive}!")); + return; + } + sideEffectList = displayedLists.GetArrayList(playerID); + if (sideEffectList == none) { + callerConsole.WriteLine(F("{$TextNegative Cannot display} side effect by index without" + @ "first listing them. Call {$TextEmphasis sideeffects} command without" + @ "{$TextEmphasis show} subcommand first.")); + return; + } + if (sideEffectIndex > sideEffectList.GetLength()) { + callerConsole.WriteLine(F("Specified side effect index is {$TextNegative out of bounds}.")); + _.memory.Free(sideEffectList); + return; + } + // Above we checked that `sideEffectIndex` lies within `[0; sideEffectList.GetLength()]` segment + // This means that `sideEffectIndex - 1` points at non-`none` value + toDisplay = SideEffect(sideEffectList.GetItem(sideEffectIndex - 1)); + if (!_.sideEffects.IsRegistered(toDisplay)) { + callerConsole.UseColorOnce(_.color.TextWarning); + callerConsole.WriteLine(P("Selected side effect is no longer active!")); + } + DisplaySideEffect(toDisplay, none, true); + _.memory.Free2(toDisplay, sideEffectList); +} + +defaultproperties { +} \ No newline at end of file diff --git a/sources/BaseAPI/API/Commands/Commands_Feature.uc b/sources/BaseAPI/API/Commands/Commands_Feature.uc index 7480bcd..df540a4 100644 --- a/sources/BaseAPI/API/Commands/Commands_Feature.uc +++ b/sources/BaseAPI/API/Commands/Commands_Feature.uc @@ -110,6 +110,7 @@ protected function OnEnabled() { RegisterCommand(class'ACommandHelp'); RegisterCommand(class'ACommandNotify'); RegisterCommand(class'ACommandVote'); + RegisterCommand(class'ACommandSideEffects'); if (_.environment.IsDebugging()) { RegisterCommand(class'ACommandFakers'); } diff --git a/sources/BaseAPI/API/SideEffects/SideEffectAPI.uc b/sources/BaseAPI/API/SideEffects/SideEffectAPI.uc index 7e58b91..3317c59 100644 --- a/sources/BaseAPI/API/SideEffects/SideEffectAPI.uc +++ b/sources/BaseAPI/API/SideEffects/SideEffectAPI.uc @@ -120,6 +120,24 @@ public function SideEffect Add_S( return newSideEffect; } +/// Checks whether specified [`SideEffect`] is currently active. +/// +/// Check is done via contents and not instance equality. +/// Returns `true` if specified [`SideEffect`] is currently active and `false` otherwise. +public function bool IsRegistered(SideEffect sideEffectToCheck) { + local int i; + + if (sideEffectToCheck == none) return false; + if (!sideEffectToCheck.IsInitialized()) return false; + + for (i = 0; i < activeSideEffects.length; i += 1) { + if (activeSideEffects[i].IsEqual(sideEffectToCheck)) { + return true; + } + } + return false; +} + /// Adds a new side effect to the list of active side effects. /// /// This method will fail if its argument is `none`, non-initialized or a side effect with that diff --git a/sources/Users/User.uc b/sources/Users/User.uc index 4eae0ad..ccce20a 100644 --- a/sources/Users/User.uc +++ b/sources/Users/User.uc @@ -176,6 +176,22 @@ public final function bool SetPersistentData( return result; } +public function bool IsEqual(Object other) { + local User otherUser; + + if (id == none) return false; + otherUser = User(other); + if (otherUser == none) return false; + if (otherUser.id == none) return false; + + return id.IsEqual(otherUser.id); +} + +protected function int CalculateHashCode() { + // If `id` is `none`, then caller `User` shouldn't be used at all + return id.GetHashCode(); +} + defaultproperties { } \ No newline at end of file