/** * Auxiliary object for outputting lists of values (with optional comments) * for Futility's commands. Some of the commands need to report that one of * the player did something to affect the other and then list the changes. * This tool is made to simplify forming such reports. * Produced reports have a form of ": * item1 (detail), item2, item3 (detail1, detail2)". * 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 ReportTool extends AcediaObject; /** * How to use: * 1. Specify "list header" via `Initialize()` method right after creating * a new instance of `ReportTool`. It can contain "%%instigator%%" and * "%%target%%" substrings, that will be replaces with approprtiate * parameters of `Report()` method when it is invoked; * 2. Use `Item()` method to add new items (they will be listed after * list header + whitespace, separated by commas and whitespaces ", "); * 3. Use `Detail()` method to specify details for the item (they will be * listed between the paranthesisasd after the corresponding item). * Details will be added to the last item, added via `Item()` call. * If no items were added, specified details will be discarded. * 4. Use `Report()` method to feed the `ConsoleWriter` with report that * has been assebled so far. * 5. Use `Reset()` to forget all the items and details * (but not list header), allowing to start forming a new report. */ // Header template (with possible "%%instigator%%" and "%%target%%" // placeholders) for the lists this `ReportTool` will generate. // Doubles as a way to remember whether `ReportTool` was already // initialized (iff `headerTemplate != none`). var private Text headerTemplate; // Represents one item + all of its details. struct ReportItem { var Text itemTitle; var array details; }; // All items recorded reported thus far var private array itemsToReport; var const int TCAUSE, TTARGET, TCOMMA, TSPACE, TSPACE_OPEN_PARANSIS; var const int TCLOSE_PARANSIS; protected function Finalizer() { Reset(); _.memory.Free(headerTemplate); headerTemplate = none; } /** * Initialized a new `ReportTool` with appropriate template to serve as * a header. * * Template (`template`) is allowed to contain "%%instigator%%" and * "%%target%%" placeholder substrings that will be replaced with corresponding * names of the player that caused a change we are reporting and player * affefcted by that change. * * @param template Template for the header of the reports made by * the caller `ReportTool`. * Method does nothing (initialization fails) iff `template == none`. */ public final function Initialize(BaseText template) { if (template == none) { return; } headerTemplate = template.Copy(); } /** * Adds new `item` to the current report. * * @param item Text to be included into the report as an item. * One should avoid using commas or parantheses inside an `item`, but * this limitation is not checked or prevented by `Item()` method. * Does nothing if `item == none` (`Detail()` will continue adding details * to the previously added item). * @return Reference to the caller `ReportTool` to allow for method chaining. */ public final function ReportTool Item(BaseText item) { local ReportItem newItem; if (headerTemplate == none) return self; if (item == none) return self; newItem.itemTitle = item.Copy(); itemsToReport[itemsToReport.length] = newItem; return self; } /** * Adds new `detail` to the last added `item` in the current report. * * @param detail Text to be included into the report as a detail to * the last added item. One should avoid using commas or parantheses inside * a `detail`, but this limitation is not checked or prevented by * `Detail()` method. * Does nothing if `detail == none` or no items were added thuis far. * @return Reference to the caller `ReportTool` to allow for method chaining. */ public final function ReportTool Detail(BaseText detail) { local array detailToReport; if (headerTemplate == none) return self; if (detail == none) return self; if (itemsToReport.length == 0) return self; detailToReport = itemsToReport[itemsToReport.length - 1].details; detailToReport[detailToReport.length] = detail.Copy(); itemsToReport[itemsToReport.length - 1].details = detailToReport; return self; } /** * Outputs report assembled thus far into the provided `ConsoleWriter`. * Reports will be made only if at least one items was added (see `Item()`). * * @param writer `ConsoleWriter` to output report into. * @param instigator Player that caused the change this report is about. * Their name will replace "%%instigator%%" substring in the header * template (if it is contained there). * @param target Player that was affected by the change this report is * about. Their name will replace "%%target%%" substring in the header * template (if it is contained there). * @return Reference to the caller `ReportTool` to allow for method chaining. */ public final function ReportTool Report( ConsoleWriter writer, optional BaseText instigator, optional BaseText target) { local int i, j; local array detailToReport; if (headerTemplate == none) return self; if (itemsToReport.length == 0) return self; if (writer == none) return self; AppendHeader(writer, instigator, target); for (i = 0; i < itemsToReport.length; i += 1) { if (i > 0) { writer.Write(T(TCOMMA)); } writer.Write(T(TSPACE)).Write(itemsToReport[i].itemTitle); detailToReport = itemsToReport[i].details; if (detailToReport.length > 0) { writer.Write(T(TSPACE_OPEN_PARANSIS)); } for (j = 0; j < detailToReport.length; j += 1) { if (j > 0) { writer.Write(P(", ")); } writer.Write(detailToReport[j]); } if (detailToReport.length > 0) { writer.Write(T(TCLOSE_PARANSIS)); } } writer.Flush(); return self; } private final function AppendHeader( ConsoleWriter writer, optional BaseText instigator, optional BaseText target) { local MutableText intro; local MutableText grayInstigatorName, grayTargetName; if (headerTemplate == none) return; if (writer == none) return; if (instigator != none) { grayInstigatorName = instigator .MutableCopy() .ChangeDefaultColor(_.color.Gray); } if (target != none) { grayTargetName = target.MutableCopy().ChangeDefaultColor(_.color.Gray); } intro = headerTemplate.MutableCopy() .Replace(T(TCAUSE), grayInstigatorName) .Replace(T(TTARGET), grayTargetName); writer.Flush().Write(intro); _.memory.Free(intro); _.memory.Free(grayTargetName); _.memory.Free(grayInstigatorName); } /** * Forgets all items or details specified for the caller `ReportTool` so far, * allowing to start forming a new report. Does not reset template header, * specified in the `Initialize()` method. * * @return Reference to the caller `ReportTool` to allow for method chaining. */ public final function ReportTool Reset() { local int i; for (i = 0; i < itemsToReport.length; i += 1) { _.memory.Free(itemsToReport[i].itemTitle); _.memory.FreeMany(itemsToReport[i].details); } if (itemsToReport.length > 0) { itemsToReport.length = 0; } return self; } defaultproperties { TCAUSE = 0 stringConstants(0) = "%%instigator%%" TTARGET = 1 stringConstants(1) = "%%target%%" TCOMMA = 2 stringConstants(2) = "," TSPACE = 3 stringConstants(3) = " " TSPACE_OPEN_PARANSIS = 4 stringConstants(4) = " (" TCLOSE_PARANSIS = 5 stringConstants(5) = ")" }