From 8845d69b1d1cee6533f63ae2cb63c45d55207455 Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Sat, 18 Jul 2020 02:31:39 +0700 Subject: [PATCH] Add `ConsoleAPI` --- sources/Core/Console/ConsoleAPI.uc | 280 ++++++++++++++++++ sources/Core/Console/ConsoleBuffer.uc | 393 ++++++++++++++++++++++++++ sources/Core/Console/ConsoleWriter.uc | 373 ++++++++++++++++++++++++ 3 files changed, 1046 insertions(+) create mode 100644 sources/Core/Console/ConsoleAPI.uc create mode 100644 sources/Core/Console/ConsoleBuffer.uc create mode 100644 sources/Core/Console/ConsoleWriter.uc diff --git a/sources/Core/Console/ConsoleAPI.uc b/sources/Core/Console/ConsoleAPI.uc new file mode 100644 index 0000000..5365626 --- /dev/null +++ b/sources/Core/Console/ConsoleAPI.uc @@ -0,0 +1,280 @@ +/** + * API that provides functions for outputting text in + * Killing Floor's console. It takes care of coloring output and breaking up + * long lines (since allowing game to handle line breaking completely + * messes up console output). + * + * Actual output is taken care of by `ConsoleWriter` objects that this + * API generates. + * 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 . + */ +class ConsoleAPI extends Singleton + config(AcediaSystem); + +/** + * Main issue with console output in Killing Floor is + * automatic line breaking of long enough messages: + * it breaks formatting and can lead to an ugly text overlapping. + * To fix this we will try to break up user's output into lines ourselves, + * before game does it for us. + * + * We are not 100% sure how Killing Floor decides when to break the line, + * but it seems to calculate how much text can actually fit in a certain + * area on screen. + * There are two issues: + * 1. We do not know for sure what this limit value is. + * Even if we knew how to compute it, we cannot do that in server mode, + * since it depends on a screen resolution and font, which + * can vary for different players. + * 2. Even invisible characters, such as color change sequences, + * that do not take any space on the screen, contribute towards + * that limit. So for a heavily colored text we will have to + * break line much sooner than for the plain text. + * Both issues are solved by introducing two limits that users themselves + * are allowed to change: visible character limit and total character limit. + * ~ Total character limit will be a hard limit on a character amount in + * a line (including hidden ones used for color change sequences) that + * will be used to prevent Killing Floor's native line breaks. + * ~ Visible character limit will be a lower limit on amount of actually + * visible character. It introduction basically reserves some space that can be + * used only for color change sequences. Without this limit lines with + * colored lines will appear to be shorter that mono-colored ones. + * Visible limit will help to alleviate this problem. + * + * For example, if we set total limit to `120` and visible limit to `80`: + * 1. Line not formatted with color will all break at + * around length of `80`. + * 2. Since color change sequence consists of 4 characters: + * we can fit up to `(120 - 80) / 4 = 10` color swaps into each line, + * while still breaking them at a around the same length of `80`. + * ~ To differentiate our line breaks from line breaks intended by + * the user, we will also add 2 symbols worth of padding in front of all our + * output: + * 1. Before intended new line they will be just two spaces. + * 2. After our line break we will replace first space with "|" to indicate + * that we had to break a long line. + * + * Described measures are not perfect: + * 1. Since Killing Floor's console doe not use monospaced font, + * the same amount of characters on the line does not mean lines of + * visually the same length; + * 2. Heavily enough colored lines are still going to be shorter; + * 3. Depending on a resolution, default limits may appear to either use + * too little space (for high resolutions) or, on the contrary, + * not prevent native line breaks (low resolutions). + * In these cases user might be required to manually set limits; + * 4. There are probably more. + * But if seems to provide good enough results for the average use case. + */ + +/** + * Configures how text will be rendered in target console(s). + */ +struct ConsoleDisplaySettings +{ + // What color to use for text by default + var Color defaultColor; + // How many visible characters in be displayed in a line? + var int maxVisibleLineWidth; + // How many total characters can be output at once? + var int maxTotalLineWidth; +}; +// We will store data for `ConsoleDisplaySettings` separately for the ease of +// configuration. +var private config Color defaultColor; +var private config int maxVisibleLineWidth; +var private config int maxTotalLineWidth; + +/** + * Return current global visible limit that describes how many (at most) + * visible characters can be output in the console line. + * + * Instances of `ConsoleWriter` are initialized with this value, + * but can later change this value independently. + * Changes to global values do not affect already created `ConsoleWriters`. + * + * @return Current global visible limit. + */ +public final function int GetVisibleLineLength() +{ + return maxVisibleLineWidth; +} + +/** + * Sets current global visible limit that describes how many (at most) visible + * characters can be output in the console line. + * + * Instances of `ConsoleWriter` are initialized with this value, + * but can later change this value independently. + * Changes to global values do not affect already created `ConsoleWriters`. + * + * @param newMaxVisibleLineWidth New global visible character limit. + */ +public final function SetVisibleLineLength(int newMaxVisibleLineWidth) +{ + maxVisibleLineWidth = newMaxVisibleLineWidth; +} + +/** + * Return current global total limit that describes how many (at most) + * characters can be output in the console line. + * + * Instances of `ConsoleWriter` are initialized with this value, + * but can later change this value independently. + * Changes to global values do not affect already created `ConsoleWriters`. + * + * @return Current global total limit. + */ +public final function int GetTotalLineLength() +{ + return maxTotalLineWidth; +} + +/** + * Sets current global total limit that describes how many (at most) + * characters can be output in the console line, counting both visible symbols + * and color change sequences. + * + * Instances of `ConsoleWriter` are initialized with this value, + * but can later change this value independently. + * Changes to global values do not affect already created `ConsoleWriters`. + * + * @param newMaxTotalLineWidth New global total character limit. + */ +public final function SetTotalLineLength(int newMaxTotalLineWidth) +{ + maxTotalLineWidth = newMaxTotalLineWidth; +} + +/** + * Return current global total limit that describes how many (at most) + * characters can be output in the console line. + * + * Instances of `ConsoleWriter` are initialized with this value, + * but can later change this value independently. + * Changes to global values do not affect already created `ConsoleWriters`. + * + * @return Current default output color. + */ +public final function Color GetDefaultColor(int newMaxTotalLineWidth) +{ + return defaultColor; +} + +/** + * Sets current global default color for console output., + * + * Instances of `ConsoleWriter` are initialized with this value, + * but can later change this value independently. + * Changes to global values do not affect already created `ConsoleWriters`. + * + * @param newMaxTotalLineWidth New global default output color. + */ +public final function SetDefaultColor(Color newDefaultColor) +{ + defaultColor = newDefaultColor; +} + +/** + * Returns borrowed `ConsoleWriter` instance that will write into + * consoles of all players. + * + * @return ConsoleWriter Borrowed `ConsoleWriter` instance, configured to + * write into consoles of all players. + * Never `none`. + */ +public final function ConsoleWriter ForAll() +{ + local ConsoleDisplaySettings globalSettings; + globalSettings.defaultColor = defaultColor; + globalSettings.maxTotalLineWidth = maxTotalLineWidth; + globalSettings.maxVisibleLineWidth = maxVisibleLineWidth; + return ConsoleWriter(_.memory.Claim(class'ConsoleWriter')) + .Initialize(globalSettings).ForAll(); +} + +/** + * Returns borrowed `ConsoleWriter` instance that will write into + * console of the player with a given controller. + * + * @param targetController Player, to whom console we want to write. + * If `none` - returned `ConsoleWriter` would be configured to + * throw messages away. + * @return Borrowed `ConsoleWriter` instance, configured to + * write into consoles of all players. + * Never `none`. + */ +public final function ConsoleWriter For(PlayerController targetController) +{ + local ConsoleDisplaySettings globalSettings; + globalSettings.defaultColor = defaultColor; + globalSettings.maxTotalLineWidth = maxTotalLineWidth; + globalSettings.maxVisibleLineWidth = maxVisibleLineWidth; + return ConsoleWriter(_.memory.Claim(class'ConsoleWriter')) + .Initialize(globalSettings).ForController(targetController); +} + +/** + * 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. + * Never `none`. + */ +public final function ConsoleWriter MakeForAll() +{ + local ConsoleDisplaySettings globalSettings; + globalSettings.defaultColor = defaultColor; + globalSettings.maxTotalLineWidth = maxTotalLineWidth; + globalSettings.maxVisibleLineWidth = maxVisibleLineWidth; + return ConsoleWriter(_.memory.Allocate(class'ConsoleWriter')) + .Initialize(globalSettings).ForAll(); +} + +/** + * Returns new `ConsoleWriter` instance that will write into + * console of the player with a given controller. + * Should be freed after use. + * + * @param targetController 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. + * Never `none`. + */ +public final function ConsoleWriter MakeFor(PlayerController targetController) +{ + local ConsoleDisplaySettings globalSettings; + globalSettings.defaultColor = defaultColor; + globalSettings.maxTotalLineWidth = maxTotalLineWidth; + globalSettings.maxVisibleLineWidth = maxVisibleLineWidth; + return ConsoleWriter(_.memory.Allocate(class'ConsoleWriter')) + .Initialize(globalSettings).ForController(targetController); +} + +defaultproperties +{ + defaultColor = (R=255,G=255,B=255,A=255) + // These should guarantee decent text output even at + // 640x480 shit resolution + maxVisibleLineWidth = 80 + maxTotalLineWidth = 108 +} \ No newline at end of file diff --git a/sources/Core/Console/ConsoleBuffer.uc b/sources/Core/Console/ConsoleBuffer.uc new file mode 100644 index 0000000..a7f477a --- /dev/null +++ b/sources/Core/Console/ConsoleBuffer.uc @@ -0,0 +1,393 @@ +/** + * Object that provides a buffer functionality for Killing Floor's (in-game) + * console output: it accepts content that user want to output and breaks it + * into lines that will be well-rendered according to the given + * `ConsoleDisplaySettings`. + * 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 . + */ +class ConsoleBuffer extends AcediaObject + dependson(Text) + dependson(ConsoleAPI); + +/** + * `ConsoleBuffer` works by breaking it's input into words, counting how much + * space they take up and only then deciding to which line to append them + * (new or the next, new one). + */ + +var private int CODEPOINT_ESCAPE; +var private int CODEPOINT_NEWLINE; +var private int COLOR_SEQUENCE_LENGTH; + +// Display settings according to which to format our output +var private ConsoleAPI.ConsoleDisplaySettings displaySettings; + +/** + * This structure is used to both share results of our work and for tracking + * information about the line we are currently filling. + */ +struct LineRecord +{ + // Contents of the line, in `STRING_Colored` format + var string contents; + // Is this a wrapped line? + // `true` means that this line was supposed to be part part of another, + // singular line of text, that had to be broken into smaller pieces. + // Such lines will start with "|" in front of them in Acedia's + // `ConsoleWriter`. + var bool wrappedLine; + // Information variables that describe how many visible and total symbols + // (visible + color change sequences) are stored int the `line` + var int visibleSymbolsStored; + var int totalSymbolsStored; + // Does `contents` contain a color change sequence? + // Non-empty line can have no such sequence if they consist of whitespaces. + var private bool colorInserted; + // If `colorInserted == true`, stores the last inserted color. + var private Color endColor; +}; +// Lines that are ready to be output to the console +var private array completedLines; + +// Line we are currently building +var private LineRecord currentLine; +// Word we are currently building, colors of it's characters will be +// automatically converted into `STRCOLOR_Struct`, according to the default +// color setting at the time of their addition. +var private array wordBuffer; +// Amount of color swaps inside `wordBuffer` +var private int colorSwapsInWordBuffer; + +/** + * Returns current setting used by this buffer to break up it's input into + * lines fit to be output in console. + * + * @return Currently used `ConsoleDisplaySettings`. + */ +public final function ConsoleAPI.ConsoleDisplaySettings GetSettings() +{ + return displaySettings; +} + +/** + * Sets new setting to be used by this buffer to break up it's input into + * lines fit to be output in console. + * + * It is recommended (although not required) to call `Flush()` before + * changing settings. Not doing so would not lead to any errors or warnings, + * but can lead to some wonky results and is considered an undefined behavior. + * + * @param newSettings New `ConsoleDisplaySettings` to be used. + * @return Returns caller `ConsoleBuffer` to allow for method chaining. + */ +public final function ConsoleBuffer SetSettings( + ConsoleAPI.ConsoleDisplaySettings newSettings) +{ + displaySettings = newSettings; + return self; +} + +/** + * Does caller `ConsoleBuffer` has any completed lines that can be output? + * + * "Completed line" means that nothing else will be added to it. + * So negative (`false`) response does not mean that the buffer is empty, - + * it can still contain an uncompleted and non-empty line that can still be + * expanded with `InsertString()`. If you want to completely empty the buffer - + * call the `Flush()` method. + * Also see `IsEmpty()`. + * + * @return `true` if caller `ConsoleBuffer` has no completed lines and + * `false` otherwise. + */ +public final function bool HasCompletedLines() +{ + return (completedLines.length > 0); +} + +/** + * Does caller `ConsoleBuffer` has any unprocessed input? + * + * Note that `ConsoleBuffer` can be non-empty, but no completed line if it + * currently builds one. + * See `Flush()` and `HasCompletedLines()` methods. + * + * @return `true` if `ConsoleBuffer` is completely empty + * (either did not receive or already returned all processed input) and + * `false` otherwise. + */ +public final function bool IsEmpty() +{ + if (HasCompletedLines()) return false; + if (currentLine.totalSymbolsStored > 0) return false; + if (wordBuffer.length > 0) return false; + return true; +} + +/** + * Clears the buffer of all data, but leaving current settings intact. + * After this calling method `IsEmpty()` should return `true`. + * + * @return Returns caller `ConsoleBuffer` to allow method chaining. + */ +public final function ConsoleBuffer Clear() +{ + local LineRecord newLineRecord; + currentLine = newLineRecord; + completedLines.length = 0; + return self; +} + +/** + * Inserts a string into the buffer. This method does not automatically break + * the line after the `input`, call `Flush()` or add line feed symbol "\n" + * at the end of the `input` if you want that. + * + * @param input `string` to be added to the current line in caller + * `ConsoleBuffer`. + * @param inputType How to treat given `string` regarding coloring. + * @return Returns caller `ConsoleBuffer` to allow method chaining. + */ +public final function ConsoleBuffer InsertString( + string input, + Text.StringType inputType) +{ + local int inputConsumed; + local array rawInput; + rawInput = _().text.StringToRaw(input, inputType); + while (rawInput.length > 0) + { + // Fill word buffer, remove consumed input from `rawInput` + inputConsumed = 0; + while (inputConsumed < rawInput.length) + { + if (_().text.IsWhitespace(rawInput[inputConsumed])) break; + InsertIntoWordBuffer(rawInput[inputConsumed]); + inputConsumed += 1; + } + rawInput.Remove(0, inputConsumed); + // If we didn't encounter any whitespace symbols - bail + if (rawInput.length <= 0) { + return self; + } + FlushWordBuffer(); + // Dump whitespaces into lines + inputConsumed = 0; + while (inputConsumed < rawInput.length) + { + if (!_().text.IsWhitespace(rawInput[inputConsumed])) break; + AppendWhitespaceToCurrentLine(rawInput[inputConsumed]); + inputConsumed += 1; + } + rawInput.Remove(0, inputConsumed); + } + return self; +} + +/** + * Returns (and makes caller `ConsoleBuffer` forget) next completed line that + * can be output to console in `STRING_Colored` format. + * + * If there are no completed line to return - returns an empty one. + * + * @return Next completed line that can be output, in `STRING_Colored` format. + */ +public final function LineRecord PopNextLine() +{ + local LineRecord result; + if (completedLines.length <= 0) return result; + result = completedLines[0]; + completedLines.Remove(0, 1); + return result; +} + +/** + * Forces all buffered data into "completed line" array, making it retrievable + * by `PopNextLine()`. + * + * @return Next completed line that can be output, in `STRING_Colored` format. + */ +public final function ConsoleBuffer Flush() +{ + FlushWordBuffer(); + BreakLine(false); + return self; +} + +// It is assumed that passed characters are not whitespace, - +// responsibility to check is on the one calling this method. +private final function InsertIntoWordBuffer(Text.Character newCharacter) +{ + local int newCharacterIndex; + local Color oldColor, newColor; + newCharacterIndex = wordBuffer.length; + // Fix text color in the buffer to remember default color, if we use it. + newCharacter.color = + _().text.GetCharacterColor(newCharacter, displaySettings.defaultColor); + newCharacter.colorType = STRCOLOR_Struct; + wordBuffer[newCharacterIndex] = newCharacter; + if (newCharacterIndex <= 0) { + return; + } + oldColor = wordBuffer[newCharacterIndex].color; + newColor = wordBuffer[newCharacterIndex - 1].color; + if (!_().color.AreEqual(oldColor, newColor, true)) { + colorSwapsInWordBuffer += 1; + } +} + +// Pushes whole `wordBuffer` into lines +private final function FlushWordBuffer() +{ + local int i; + local Color newColor; + if (!WordCanFitInCurrentLine() && WordCanFitInNewLine()) { + BreakLine(true); + } + for (i = 0; i < wordBuffer.length; i += 1) + { + if (!CanAppendNonWhitespaceIntoLine(wordBuffer[i])) { + BreakLine(true); + } + newColor = wordBuffer[i].color; + if (MustSwapColorsFor(newColor)) + { + currentLine.contents $= _().color.GetColorTag(newColor); + currentLine.totalSymbolsStored += COLOR_SEQUENCE_LENGTH; + currentLine.colorInserted = true; + currentLine.endColor = newColor; + } + currentLine.contents $= Chr(wordBuffer[i].codePoint); + currentLine.totalSymbolsStored += 1; + currentLine.visibleSymbolsStored += 1; + } + wordBuffer.length = 0; + colorSwapsInWordBuffer = 0; +} + +private final function BreakLine(bool makeWrapped) +{ + local LineRecord newLineRecord; + if (currentLine.visibleSymbolsStored > 0) { + completedLines[completedLines.length] = currentLine; + } + currentLine = newLineRecord; + currentLine.wrappedLine = makeWrapped; +} + +private final function bool MustSwapColorsFor(Color newColor) +{ + if (!currentLine.colorInserted) return true; + return !_().color.AreEqual(currentLine.endColor, newColor, true); +} + +private final function bool CanAppendWhitespaceIntoLine() +{ + // We always allow to append at least something into empty line, + // otherwise we can never insert it anywhere + if (currentLine.totalSymbolsStored <= 0) return true; + if (currentLine.totalSymbolsStored >= displaySettings.maxTotalLineWidth) + { + return false; + } + if (currentLine.visibleSymbolsStored >= displaySettings.maxVisibleLineWidth) + { + return false; + } + return true; +} + +private final function bool CanAppendNonWhitespaceIntoLine( + Text.Character nextCharacter) +{ + // We always allow to insert at least something into empty line, + // otherwise we can never insert it anywhere + if (currentLine.totalSymbolsStored <= 0) { + return true; + } + // Check if we can fit a single character by fitting a whitespace symbol. + if (!CanAppendWhitespaceIntoLine()) { + return false; + } + if (!MustSwapColorsFor(nextCharacter.color)) { + return true; + } + // Can we fit character + color swap sequence? + return ( currentLine.totalSymbolsStored + COLOR_SEQUENCE_LENGTH + 1 + <= displaySettings.maxTotalLineWidth); +} + +// For performance reasons assumes that passed character is a whitespace, +// the burden of checking is on the caller. +private final function AppendWhitespaceToCurrentLine(Text.Character whitespace) +{ + if (_().text.IsCodePoint(whitespace, CODEPOINT_NEWLINE)) { + BreakLine(true); + return; + } + if (!CanAppendWhitespaceIntoLine()) { + BreakLine(true); + } + currentLine.contents $= Chr(whitespace.codePoint); + currentLine.totalSymbolsStored += 1; + currentLine.visibleSymbolsStored += 1; +} + +private final function bool WordCanFitInNewLine() +{ + local int totalCharactersInWord; + if (wordBuffer.length <= 0) return true; + if (wordBuffer.length > displaySettings.maxVisibleLineWidth) { + return false; + } + // `(colorSwapsInWordBuffer + 1)` counts how many times we must + // switch color inside a word + 1 for setting initial color + totalCharactersInWord = wordBuffer.length + + (colorSwapsInWordBuffer + 1) * COLOR_SEQUENCE_LENGTH; + return (totalCharactersInWord <= displaySettings.maxTotalLineWidth); +} + +private final function bool WordCanFitInCurrentLine() +{ + local int totalLimit, visibleLimit; + local int totalCharactersInWord; + if (wordBuffer.length <= 0) return true; + totalLimit = + displaySettings.maxTotalLineWidth - currentLine.totalSymbolsStored; + visibleLimit = + displaySettings.maxVisibleLineWidth - currentLine.visibleSymbolsStored; + // Visible symbols check + if (wordBuffer.length > visibleLimit) { + return false; + } + // Total symbols check + totalCharactersInWord = wordBuffer.length + + colorSwapsInWordBuffer * COLOR_SEQUENCE_LENGTH; + if (MustSwapColorsFor(wordBuffer[0].color)) { + totalCharactersInWord += COLOR_SEQUENCE_LENGTH; + } + return (totalCharactersInWord <= totalLimit); +} + +defaultproperties +{ + CODEPOINT_ESCAPE = 27 + CODEPOINT_NEWLINE = 10 + // CODEPOINT_ESCAPE + + + + COLOR_SEQUENCE_LENGTH = 4 +} \ No newline at end of file diff --git a/sources/Core/Console/ConsoleWriter.uc b/sources/Core/Console/ConsoleWriter.uc new file mode 100644 index 0000000..7408530 --- /dev/null +++ b/sources/Core/Console/ConsoleWriter.uc @@ -0,0 +1,373 @@ +/** + * Object that provides simple access to console output. + * Can either write to a certain player's console or to all consoles at once. + * Supports "fancy" and "raw" output (for more details @see `ConsoleAPI`). + * 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 . + */ +class ConsoleWriter extends AcediaObject + dependson(ConsoleAPI) + dependson(ConnectionService); + +// Prefixes we output before every line to signify whether they were broken +// or not +var private string NEWLINE_PREFIX; +var private string BROKENLINE_PREFIX; + +/** + * Describes current output target of the `ConsoleWriter`. + */ +enum ConsoleWriterTarget +{ + // No one. Can happed if our target disconnects. + CWTARGET_None, + // A certain player. + CWTARGET_Player, + // All players. + CWTARGET_All +}; +var private ConsoleWriterTarget targetType; +// Controller of the player that will receive output passed +// to this `ConsoleWriter`. +// Only used when `targetType == CWTARGET_Player` +var private PlayerController outputTarget; +var private ConsoleBuffer outputBuffer; + +var private ConsoleAPI.ConsoleDisplaySettings displaySettings; + +public final function ConsoleWriter Initialize( + ConsoleAPI.ConsoleDisplaySettings newDisplaySettings) +{ + displaySettings = newDisplaySettings; + if (outputBuffer == none) { + outputBuffer = ConsoleBuffer(_().memory.Allocate(class'ConsoleBuffer')); + } + else { + outputBuffer.Clear(); + } + outputBuffer.SetSettings(displaySettings); + return self; +} + +/** + * Return current default color for caller `ConsoleWriter`. + * + * This method returns default color, i.e. color that will be used if no other + * is specified by text you're outputting. + * If color is specified, this value will be ignored. + * + * This value is not synchronized with the global value from `ConsoleAPI` + * (or such value from any other `ConsoleWriter`) and affects only + * output produced by this `ConsoleWriter`. + * + * @return Current default color. + */ +public final function Color GetColor() +{ + return displaySettings.defaultColor; +} + +/** + * Sets default color for caller 'ConsoleWriter`'s output. + * + * This only changes default color, i.e. color that will be used if no other is + * specified by text you're outputting. + * If color is specified, this value will be ignored. + * + * This value is not synchronized with the global value from `ConsoleAPI` + * (or such value from any other `ConsoleWriter`) and affects only + * output produced by this `ConsoleWriter`. + * + * @param newDefaultColor New color to use when none specified by text itself. + * @return Returns caller `ConsoleWriter` to allow for method chaining. + */ +public final function ConsoleWriter SetColor(Color newDefaultColor) +{ + displaySettings.defaultColor = newDefaultColor; + if (outputBuffer != none) { + outputBuffer.SetSettings(displaySettings); + } + return self; +} + +/** + * Return current visible limit that describes how many (at most) + * visible characters can be output in the console line. + * + * This value is not synchronized with the global value from `ConsoleAPI` + * (or such value from any other `ConsoleWriter`) and affects only + * output produced by this `ConsoleWriter`. + * + * @return Current global visible limit. + */ +public final function int GetVisibleLineLength() +{ + return displaySettings.maxVisibleLineWidth; +} + +/** + * Sets current visible limit that describes how many (at most) visible + * characters can be output in the console line. + * + * This value is not synchronized with the global value from `ConsoleAPI` + * (or such value from any other `ConsoleWriter`) and affects only + * output produced by this `ConsoleWriter`. + * + * @param newVisibleLimit New global visible limit. + * @return Returns caller `ConsoleWriter` to allow for method chaining. + */ +public final function ConsoleWriter SetVisibleLineLength( + int newMaxVisibleLineWidth +) +{ + displaySettings.maxVisibleLineWidth = newMaxVisibleLineWidth; + if (outputBuffer != none) { + outputBuffer.SetSettings(displaySettings); + } + return self; +} + +/** + * Return current total limit that describes how many (at most) + * characters can be output in the console line. + * + * This value is not synchronized with the global value from `ConsoleAPI` + * (or such value from any other `ConsoleWriter`) and affects only + * output produced by this `ConsoleWriter`. + * + * @return Current global total limit. + */ +public final function int GetTotalLineLength() +{ + return displaySettings.maxTotalLineWidth; +} + +/** + * Sets current total limit that describes how many (at most) + * characters can be output in the console line. + * + * This value is not synchronized with the global value from `ConsoleAPI` + * (or such value from any other `ConsoleWriter`) and affects only + * output produced by this `ConsoleWriter`. + * + * @param newTotalLimit New global total limit. + * @return Returns caller `ConsoleWriter` to allow for method chaining. + */ +public final function ConsoleWriter SetTotalLineLength(int newMaxTotalLineWidth) +{ + displaySettings.maxTotalLineWidth = newMaxTotalLineWidth; + if (outputBuffer != none) { + outputBuffer.SetSettings(displaySettings); + } + return self; +} + +/** + * Configures caller `ConsoleWriter` to output to all players. + * `Flush()` will be automatically called between target change. + * + * @return Returns caller `ConsoleWriter` to allow for method chaining. + */ +public final function ConsoleWriter ForAll() +{ + Flush(); + targetType = CWTARGET_All; + return self; +} + +/** + * Configures caller `ConsoleWriter` to output only to a player, + * given by a passed `PlayerController`. + * `Flush()` will be automatically called between target change. + * + * @param targetController Player, to whom console we want to write. + * If `none` - caller `ConsoleWriter` would be configured to + * throw messages away. + * @return ConsoleWriter Returns caller `ConsoleWriter` to allow for + * method chaining. + */ +public final function ConsoleWriter ForController( + PlayerController targetController +) +{ + Flush(); + if (targetController != none) + { + targetType = CWTARGET_Player; + outputTarget = targetController; + } + else { + targetType = CWTARGET_None; + } + return self; +} + +/** + * Returns type of current target for the caller `ConsoleWriter`. + * + * @return `ConsoleWriterTarget` value, describing current target of + * the caller `ConsoleWriter`. + */ +public final function ConsoleWriterTarget CurrentTarget() +{ + if (targetType == CWTARGET_Player && outputTarget == none) { + targetType = CWTARGET_None; + } + return targetType; +} + +/** + * Returns `PlayerController` of the player to whom console caller + * `ConsoleWriter` is outputting messages. + * + * @return `PlayerController` of the player to whom console caller + * `ConsoleWriter` is outputting messages. + * Returns `none` iff it currently outputs to every player or to no one. + */ +public final function PlayerController GetTargetPlayerController() +{ + if (targetType == CWTARGET_All) return none; + return outputTarget; +} + +/** + * Outputs all buffered input and moves further output onto a new line. + * + * @return Returns caller `ConsoleWriter` to allow for method chaining. + */ +public final function ConsoleWriter Flush() +{ + outputBuffer.Flush(); + SendBuffer(); + return self; +} + +/** + * Writes a formatted string into console. + * + * Does not trigger console output, for that use `WriteLine()` or `Flush()`. + * + * To output a different type of string into a console, use `WriteT()`. + * + * @param message Formatted string to output. + * @return Returns caller `ConsoleWriter` to allow for method chaining. + */ +public final function ConsoleWriter Write(string message) +{ + outputBuffer.InsertString(message, STRING_Formatted); + return self; +} + +/** + * Writes a formatted string into console. + * Result will be output immediately, starts a new line. + * + * To output a different type of string into a console, use `WriteLineT()`. + * + * @param message Formatted string to output. + * @return Returns caller `ConsoleWriter` to allow for method chaining. + */ +public final function ConsoleWriter WriteLine(string message) +{ + outputBuffer.InsertString(message, STRING_Formatted); + Flush(); + return self; +} + +/** + * Writes a `string` of specified type into console. + * + * Does not trigger console output, for that use `WriteLineT()` or `Flush()`. + * + * To output a formatted string you might want to simply use `Write()`. + * + * @param message String of a given type to output. + * @param inputType Type of the string method should output. + * @return Returns caller `ConsoleWriter` to allow for method chaining. + */ +public final function ConsoleWriter WriteT( + string message, + Text.StringType inputType) +{ + outputBuffer.InsertString(message, inputType); + return self; +} + +/** + * Writes a `string` of specified type into console. + * Result will be output immediately, starts a new line. + * + * To output a formatted string you might want to simply use `WriteLine()`. + * + * @param message String of a given type to output. + * @param inputType Type of the string method should output. + * @return Returns caller `ConsoleWriter` to allow for method chaining. + */ +public final function ConsoleWriter WriteLineT( + string message, + Text.StringType inputType) +{ + outputBuffer.InsertString(message, inputType); + Flush(); + return self; +} + +// Send all completed lines from an `outputBuffer` +private final function SendBuffer() +{ + local string prefix; + local ConnectionService service; + local ConsoleBuffer.LineRecord nextLineRecord; + while (outputBuffer.HasCompletedLines()) + { + nextLineRecord = outputBuffer.PopNextLine(); + if (nextLineRecord.wrappedLine) { + prefix = NEWLINE_PREFIX; + } + else { + prefix = BROKENLINE_PREFIX; + } + service = ConnectionService(class'ConnectionService'.static.Require()); + SendConsoleMessage(service, prefix $ nextLineRecord.contents); + } +} + +// Assumes `service != none`, caller function must ensure that. +private final function SendConsoleMessage( + ConnectionService service, + string message) +{ + local int i; + local array connections; + if (targetType != CWTARGET_All) + { + if (outputTarget != none) { + outputTarget.ClientMessage(message); + } + return; + } + connections = service.GetActiveConnections(); + for (i = 0; i < connections.length; i += 1) { + connections[i].controllerReference.ClientMessage(message); + } +} + +defaultproperties +{ + NEWLINE_PREFIX = "| " + BROKENLINE_PREFIX = " " +} \ No newline at end of file