Browse Source

Add `ConsoleAPI`

new
Anton Tarasenko 4 years ago
parent
commit
8845d69b1d
  1. 280
      sources/Core/Console/ConsoleAPI.uc
  2. 393
      sources/Core/Console/ConsoleBuffer.uc
  3. 373
      sources/Core/Console/ConsoleWriter.uc

280
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 <https://www.gnu.org/licenses/>.
*/
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
}

393
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 <https://www.gnu.org/licenses/>.
*/
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<LineRecord> 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<Text.Character> 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<Text.Character> 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 + <redByte> + <greenByte> + <blueByte>
COLOR_SEQUENCE_LENGTH = 4
}

373
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 <https://www.gnu.org/licenses/>.
*/
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<ConnectionService.Connection> 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 = " "
}