diff --git a/sources/CommandAnnouncer.uc b/sources/CommandAnnouncer.uc new file mode 100644 index 0000000..8fceb1b --- /dev/null +++ b/sources/CommandAnnouncer.uc @@ -0,0 +1,265 @@ +/** + * Simple class to simplify announcements of changes made by Futility's + * commands. Allows to announnce different messages to self, target and others; + * changing them up when self coincides with target. + * 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 . + */ +class CommandAnnouncer extends AcediaObject; + +/** + * # `CommandAnnouncer` + * + * Simple class to simplify announcements of changes made by Futility's + * commands. Allows to announnce different messages to self, target and others; + * changing them up when self coincides with target. + * Technically only for reporting successes, since failures are only + * reported to the instigator and are, therefore, simple. But for the sake of + * consistency, every report can be placed here. + * + * ## Usage + * + * There is supposed to be a separate announcer class for every command class, + * so there's two steps to setting one up: creating new class and putting it + * to proper use. + * + * ### Creating new `CommandAnnouncer` class + * + * Step by step the process is: + * 1. Declare a new class, extending `CommandAnnouncer`; + * 2. Declare iinside `AnnouncementVariations` field variables for every + * announcement; + * 3. Declare a `Finalizer()` and place a `FreeVariations(...);` line for + * each announcement variable inside. Don't forget to later also make + * a `super.Finalizer();` call; + * 4. For every announcement make a separate method that accepts values to + * be included in that announcement as arguments. + * 5. Inside that method check whether corresponding announcement variable + * was already initialized (`initialized` field in side + * `AnnouncementVariations` struct) and otherwise initialize all + * contained `TextTemplate`s. + * 6. Fill every template with passed arguments ("instigator" and "target" + * arguments are auto-filled later). For that you can use auxiliary + * method `MakeArray()`, e.g. + * ```unrealscript + * local int i; + * local array templates; + * // ... + * templates = MakeArray(gainedDosh); + * for (i = 0; i < templates.length; i += 1) { + * templates[i].Reset().ArgInt(doshAmount); + * } + * ``` + * 7. Make a `MakeAnnouncement()`, passing it `AnnouncementVariations` that + * you've just (initialized and) filled with arguments. + * + * ### Using created `CommandAnnouncer` class + * + * Simply allocate variable of that class, make a `Setup()` call inside + * `Executed()` and `ExecutedFor()` (only necessary to do in the one you are + * using). + * Since it is way more efficient to only allocate such variable once + * (avoiding creating templates every time), it is recommended that you create + * it inside `BuildData()` call and remember that instance in a field variable. + * You then simply need to declare command's finalizer to deallocate it. + * Just don't forget to call `super.Finalizer()` as well. + */ + +var private EPlayer instigator, target; +var private int instigatorLifeVersion, targetLifeVersion; +var private ConsoleWriter publicConsole; +var private int publicConsoleLifeVersion; + +var private MutableText instigatorName, targetName; + +struct AnnouncementVariations +{ + var public bool initialized; + // `toSelf...` == command's instigator is targeting himself/herself; + // `toOther...` == command's instigator is targeting somebody else; + // `...report` == message is for a report to command's instigator; + // `...private` == message is for a report to command's target; + // `...public` == message is for a report to eveyone who isn't + // an instigator or a target. + var public TextTemplate toSelfReport; + var public TextTemplate toSelfPublic; + var public TextTemplate toOtherReport; + var public TextTemplate toOtherPrivate; + var public TextTemplate toOtherPublic; +}; + +protected function Finalizer() +{ + instigator = none; + target = none; + publicConsole = none; + _.memory.Free(instigatorName); + _.memory.Free(targetName); + instigatorName = none; + targetName = none; +} + +/** + * Prepares caller `CommandAnnouncer` to make announcements about + * `newInstigator` player affecting `newTarget` player. + * + * @param newTarget Player that is targeted by the command. + * @param newInstigator Player that is calling the command, can be + * the same as `newTarget`. + * @param newPublicConsole Console instance to announce command's action to + * other (directly unaffected) players. + */ +public final function Setup( + EPlayer newTarget, + EPlayer newInstigator, + ConsoleWriter newPublicConsole) +{ + target = none; + if (newTarget != none && newTarget.IsAllocated()) + { + target = newTarget; + targetLifeVersion = newTarget.GetLifeVersion(); + _.memory.Free(targetName); + targetName = target + .GetName() + .IntoMutableText() + .ChangeDefaultColor(_.color.Gray); + } + instigator = none; + if (newInstigator != none && newInstigator.IsAllocated()) + { + instigator = newInstigator; + instigatorLifeVersion = newInstigator.GetLifeVersion(); + _.memory.Free(instigatorName); + instigatorName = instigator + .GetName() + .IntoMutableText() + .ChangeDefaultColor(_.color.Gray); + } + publicConsole = none; + if (newPublicConsole != none && newPublicConsole.IsAllocated()) + { + publicConsole = newPublicConsole; + publicConsoleLifeVersion = newPublicConsole.GetLifeVersion(); + } +} + +/** + * Makes appropriate announcements from `variations` to appropriate targets. + * + * @param variations Struct with announcement templates to make. + */ +protected final function MakeAnnouncement(AnnouncementVariations variations) +{ + local ConsoleWriter instigatorConsole, targetConsole; + + if (!variations.initialized) return; + if (!AreClassesValid()) return; + + instigatorConsole = _.console.For(instigator); + targetConsole = _.console.For(target); + if (instigator.SameAs(target)) + { + // If instigator is targeting himself, then there is no need for + // a separate announcement to target + AnnounceTemplate(instigatorConsole, variations.toSelfReport); + AnnounceTemplate(publicConsole, variations.toSelfPublic); + } + else + { + // Otherwise report to three different targets + AnnounceTemplate(instigatorConsole, variations.toOtherReport); + AnnounceTemplate(targetConsole, variations.toOtherPrivate); + AnnounceTemplate(publicConsole, variations.toOtherPublic); + } +} + +/** + * Auxiliary method to free all objects inside given `AnnouncementVariations` + * struct. + * + * @param variations Struct, whos contained objects methodf should free. + */ +protected final function FreeVariations(out AnnouncementVariations variations) +{ + _.memory.Free(variations.toSelfReport); + _.memory.Free(variations.toSelfPublic); + _.memory.Free(variations.toOtherReport); + _.memory.Free(variations.toOtherPrivate); + _.memory.Free(variations.toOtherPublic); + variations.toSelfReport = none; + variations.toSelfPublic = none; + variations.toOtherReport = none; + variations.toOtherPrivate = none; + variations.toOtherPublic = none; + variations.initialized = false; +} + +/** + * Auxiliary method to put all `TextTemplate`s inside `variations` into + * an array that can then be easily iterated over. + */ +protected final function array MakeArray( + AnnouncementVariations variations) +{ + local array result; + + result[result.length] = variations.toSelfReport; + result[result.length] = variations.toSelfPublic; + result[result.length] = variations.toOtherReport; + result[result.length] = variations.toOtherPrivate; + result[result.length] = variations.toOtherPublic; + return result; +} + +private final function bool AreClassesValid() +{ + if (instigator == none) return false; + if (target == none) return false; + if (publicConsole == none) return false; + if (instigator.GetLifeVersion() != instigatorLifeVersion) return false; + if (target.GetLifeVersion() != targetLifeVersion) return false; + if (!instigator.IsExistent()) return false; + if (!target.IsExistent()) return false; + + if (publicConsole.GetLifeVersion() != publicConsoleLifeVersion) { + return false; + } + return true; +} + +private final function AnnounceTemplate( + ConsoleWriter writer, + TextTemplate template) +{ + local MutableText result; + + if (writer == none) return; + if (template == none) return; + if (!template.IsInitialized()) return; + + template + .TextArg(P("intigator"), instigatorName) + .TextArg(P("target"), targetName); + result = template.CollectFormattedM(); + writer.Say(result); + _.memory.Free(result); +} + +defaultproperties +{ +} \ No newline at end of file