Prepare fixtures
This commit is contained in:
parent
797e5ea192
commit
9c94356263
6021 changed files with 722805 additions and 22 deletions
482
kf_sources/Futility/Classes/ACommandDB.uc
Normal file
482
kf_sources/Futility/Classes/ACommandDB.uc
Normal file
|
|
@ -0,0 +1,482 @@
|
|||
/**
|
||||
* Command for working with databases.
|
||||
* Copyright 2021-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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class ACommandDB extends Command
|
||||
dependson(Database);
|
||||
|
||||
/**
|
||||
* This command provides a text user interface to databases.
|
||||
* It can perform two types of tasks:
|
||||
* 1. Tasks that have to do with managing set of databases as a whole:
|
||||
* listing them, their creation and deletion. For currently implemented
|
||||
* local databases they work synchronously and are just simple bindings
|
||||
* to the Acedia's API.
|
||||
* 2. Tasks that edit a particular database: these require us to send
|
||||
* database a query and then wait for the response. With these we
|
||||
* cannot give an immediate reply, so we have to remember player that
|
||||
* requested these queries to then relay him database's reply.
|
||||
* Main problem with remembering a player for 2nd-type tasks is that,
|
||||
* while highly unlikely, several different players may make their own requests
|
||||
* while we are still waiting for the reply to the previous query.
|
||||
* We could simply remember a queue of several players and then return them in
|
||||
* a FIFO order (since databases guarantee orderly replies to their queries),
|
||||
* but requests can also be towards different databases and, therefore,
|
||||
* completed in a random order.
|
||||
* To solve this we fill-in the waiting queue of pairs player-database that
|
||||
* link player that made a request and database he made this request to.
|
||||
* Once reply from any database arrives - we simply search and return
|
||||
* the first player in our queue that made a request to this database.
|
||||
* Thanks to the fact that databases reply to their queries in order of
|
||||
* arrival - this will let us fetch precisely the players that were responsible
|
||||
* for these requests. This logic is implemented in `PushPlayer()` and
|
||||
* `PopPlayer()` methods.
|
||||
* The rest of the methods are mostly straightforward callbacks that
|
||||
* transform `Database`'s reply into text message for the player.
|
||||
*/
|
||||
|
||||
// Array of pairs is represented by two arrays of single values.
|
||||
// Arrays should be kept same length, elements with the same index
|
||||
// correspond to the same pair.
|
||||
var protected array<Database> queueWaitingListDatabases;
|
||||
var protected array<EPlayer> queueWaitingListPlayers;
|
||||
|
||||
// Auxiliary structure that corresponds to database + JSON path from resolved
|
||||
// database link.
|
||||
struct DBPointerPair
|
||||
{
|
||||
var public Database database;
|
||||
var public JSONPointer pointer;
|
||||
};
|
||||
|
||||
var protected const int TCREATE, TDELETE, TLIST, TREAD, TSIZE, TKEYS, TREMOVE;
|
||||
var protected const int TWRITE, TINCREMENT, TDATABASE_NAME, TDATABASE_LINK;
|
||||
var protected const int TJSON_VALUE, TQUERY_INVALID_POINTER, TQUERY_INVALID_DB;
|
||||
var protected const int TOBJECT_KEYS_ARE, TOBJECT_SIZE_IS, TQUERY_COMPLETED;
|
||||
var protected const int TQUERY_INVALID_DATA, TAVAILABLE_DATABASES, TDA_DELETED;
|
||||
var protected const int TDB_DOESNT_EXIST, TDB_ALREADY_EXISTS, TDB_CREATED;
|
||||
var protected const int TDB_CANNOT_BE_CREATED, TNO_DEFAULT_COMMAND, TBAD_DBLINK;
|
||||
|
||||
protected function BuildData(CommandDataBuilder builder)
|
||||
{
|
||||
builder.Group(P("admin"));
|
||||
builder.Summary(P("Read and edit data in your databases."
|
||||
@ "Databases' values are addressed with links:"
|
||||
@ "\"<db_name>:<json_path>\""));
|
||||
builder.SubCommand(T(TCREATE));
|
||||
builder.ParamText(T(TDATABASE_NAME));
|
||||
builder.Describe(P("Creates new database with a specified name."));
|
||||
builder.SubCommand(T(TDELETE));
|
||||
builder.ParamText(T(TDATABASE_NAME));
|
||||
builder.Describe(P("Completely deletes specified database."));
|
||||
builder.SubCommand(T(TLIST));
|
||||
builder.Describe(P("Lists available databases."));
|
||||
builder.SubCommand(T(TREAD));
|
||||
builder.ParamText(T(TDATABASE_LINK));
|
||||
builder.Describe(P("Reads data from location given by the `databaseLink`."));
|
||||
builder.SubCommand(T(TSIZE));
|
||||
builder.ParamText(T(TDATABASE_LINK));
|
||||
builder.Describe(P("Gets amount of elements inside JSON array or object at"
|
||||
@ "location given by the `databaseLink`."));
|
||||
builder.SubCommand(T(TKEYS));
|
||||
builder.ParamText(T(TDATABASE_LINK));
|
||||
builder.Describe(P("Lists keys of JSON object at location given by"
|
||||
@ "the `databaseLink`."));
|
||||
builder.SubCommand(T(TREMOVE));
|
||||
builder.ParamText(T(TDATABASE_LINK));
|
||||
builder.Describe(P("Removes data from location given by the `databaseLink`."));
|
||||
builder.SubCommand(T(TWRITE));
|
||||
builder.ParamText(T(TDATABASE_LINK));
|
||||
builder.ParamJSON(T(TJSON_VALUE));
|
||||
builder.Describe(P("Writes specified JSON value into location given by"
|
||||
@ "the `databaseLink`."));
|
||||
builder.Option(T(TINCREMENT));
|
||||
builder.Describe(F("Specifying this option for any of the"
|
||||
@ "{$TextEmphasis 'write'} subcommands will cause them to append"
|
||||
@ "data to the old one, instead of rewriting it."));
|
||||
}
|
||||
|
||||
protected function PushPlayer(EPlayer nextPlayer, Database callDatabase)
|
||||
{
|
||||
local EPlayer playerCopy;
|
||||
|
||||
if (nextPlayer != none) {
|
||||
playerCopy = nextPlayer;
|
||||
nextPlayer.NewRef();
|
||||
}
|
||||
if (callDatabase != none) {
|
||||
callDatabase.NewRef();
|
||||
}
|
||||
queueWaitingListPlayers[queueWaitingListPlayers.length] = playerCopy;
|
||||
queueWaitingListDatabases[queueWaitingListDatabases.length] = callDatabase;
|
||||
}
|
||||
|
||||
protected function EPlayer PopPlayer(Database relevantDatabase)
|
||||
{
|
||||
local int i;
|
||||
local EPlayer result;
|
||||
|
||||
if (queueWaitingListPlayers.length <= 0) return none;
|
||||
if (queueWaitingListDatabases.length <= 0) return none;
|
||||
|
||||
while (i < queueWaitingListDatabases.length)
|
||||
{
|
||||
if (queueWaitingListDatabases[i].IsEqual(relevantDatabase))
|
||||
{
|
||||
result = queueWaitingListPlayers[i];
|
||||
queueWaitingListDatabases[i].FreeSelf();
|
||||
queueWaitingListPlayers.Remove(i, 1);
|
||||
queueWaitingListDatabases.Remove(i, 1);
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
if (result != none && result.IsExistent()) {
|
||||
return result;
|
||||
}
|
||||
_.memory.Free(result);
|
||||
return none;
|
||||
}
|
||||
|
||||
protected function Executed(
|
||||
CallData arguments,
|
||||
EPlayer instigator,
|
||||
CommandPermissions permissions
|
||||
) {
|
||||
local AcediaObject valueToWrite;
|
||||
local DBPointerPair pair;
|
||||
local Text subCommand;
|
||||
|
||||
subCommand = arguments.subCommandName;
|
||||
// Try executing on of the operation that manage multiple databases
|
||||
if (TryAPICallCommands(subCommand, instigator, arguments.parameters)) {
|
||||
return;
|
||||
}
|
||||
// If we have failed - it has got to be one of the operations on
|
||||
// a single database
|
||||
pair = TryLoadingDB(arguments.parameters.GetText(T(TDATABASE_LINK)));
|
||||
if (pair.database == none)
|
||||
{
|
||||
callerConsole.WriteLine(T(TBAD_DBLINK));
|
||||
return;
|
||||
}
|
||||
// Remember the last player we are making a query to and make that query
|
||||
PushPlayer(instigator, pair.database);
|
||||
if (subCommand.Compare(T(TWRITE)))
|
||||
{
|
||||
valueToWrite = arguments.parameters.GetItem(T(TJSON_VALUE));
|
||||
if (arguments.options.HasKey(T(TINCREMENT)))
|
||||
{
|
||||
pair.database.IncrementData(pair.pointer, valueToWrite)
|
||||
.connect = DisplayResponse;
|
||||
}
|
||||
else
|
||||
{
|
||||
pair.database.WriteData(pair.pointer, valueToWrite)
|
||||
.connect = DisplayResponse;
|
||||
}
|
||||
}
|
||||
else if (subCommand.Compare(T(TREAD))) {
|
||||
pair.database.ReadData(pair.pointer).connect = DisplayData;
|
||||
}
|
||||
else if (subCommand.Compare(T(TSIZE))) {
|
||||
pair.database.GetDataSize(pair.pointer).connect = DisplaySize;
|
||||
}
|
||||
else if (subCommand.Compare(T(TKEYS))) {
|
||||
pair.database.GetDataKeys(pair.pointer).connect = DisplayKeys;
|
||||
}
|
||||
else if (subCommand.Compare(T(TREMOVE))) {
|
||||
pair.database.RemoveData(pair.pointer).connect = DisplayResponse;
|
||||
}
|
||||
_.memory.Free(pair.pointer);
|
||||
}
|
||||
|
||||
// Simple API calls
|
||||
private function bool TryAPICallCommands(
|
||||
BaseText subCommand,
|
||||
EPlayer instigator,
|
||||
HashTable commandParameters)
|
||||
{
|
||||
local Text databaseName;
|
||||
if (subCommand.IsEmpty())
|
||||
{
|
||||
callerConsole.WriteLine(T(TNO_DEFAULT_COMMAND));
|
||||
return true;
|
||||
}
|
||||
else if (subCommand.Compare(T(TLIST)))
|
||||
{
|
||||
ListDatabases(instigator);
|
||||
return true;
|
||||
}
|
||||
else if (subCommand.Compare(T(TCREATE)))
|
||||
{
|
||||
databaseName = commandParameters.GetText(T(TDATABASE_NAME));
|
||||
CreateDatabase(instigator, databaseName);
|
||||
_.memory.Free(databaseName);
|
||||
return true;
|
||||
}
|
||||
else if (subCommand.Compare(T(TDELETE)))
|
||||
{
|
||||
databaseName = commandParameters.GetText(T(TDATABASE_NAME));
|
||||
DeleteDatabase(instigator, databaseName);
|
||||
_.memory.Free(databaseName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// json pointer as `Text` -> `DBPointerPair` representation converter method
|
||||
private function DBPointerPair TryLoadingDB(BaseText databaseLink)
|
||||
{
|
||||
local DBPointerPair result;
|
||||
if (databaseLink == none) {
|
||||
return result;
|
||||
}
|
||||
result.database = _server.db.Load(databaseLink);
|
||||
if (result.database == none) {
|
||||
return result;
|
||||
}
|
||||
result.pointer = _server.db.GetPointer(databaseLink);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected function CreateDatabase(EPlayer instigator, Text databaseName)
|
||||
{
|
||||
if (instigator == none) {
|
||||
return;
|
||||
}
|
||||
if (_server.db.ExistsLocal(databaseName))
|
||||
{
|
||||
callerConsole.WriteLine(T(TDB_ALREADY_EXISTS));
|
||||
return;
|
||||
}
|
||||
if (_server.db.NewLocal(databaseName) != none) {
|
||||
callerConsole.WriteLine(T(TDB_CREATED));
|
||||
}
|
||||
else {
|
||||
callerConsole.WriteLine(T(TDB_CANNOT_BE_CREATED));
|
||||
}
|
||||
}
|
||||
|
||||
protected function DeleteDatabase(EPlayer instigator, Text databaseName)
|
||||
{
|
||||
if (instigator == none) {
|
||||
return;
|
||||
}
|
||||
if (_server.db.DeleteLocal(databaseName)) {
|
||||
callerConsole.WriteLine(T(TDA_DELETED));
|
||||
}
|
||||
else {
|
||||
callerConsole.WriteLine(T(TDB_DOESNT_EXIST));
|
||||
}
|
||||
}
|
||||
|
||||
protected function ListDatabases(EPlayer instigator)
|
||||
{
|
||||
local int i;
|
||||
local array<Text> availableDatabases;
|
||||
local ConsoleWriter console;
|
||||
|
||||
if (instigator == none) {
|
||||
return;
|
||||
}
|
||||
availableDatabases = _server.db.ListLocal();
|
||||
console = callerConsole;
|
||||
console.Write(T(TAVAILABLE_DATABASES));
|
||||
for (i = 0; i < availableDatabases.length; i += 1)
|
||||
{
|
||||
if (i > 0) {
|
||||
console.ResetColor().Write(P(", "));
|
||||
}
|
||||
console.UseColor(_.color.TextSubtle).Write(availableDatabases[i]);
|
||||
}
|
||||
console.ResetColor().Flush();
|
||||
_.memory.FreeMany(availableDatabases);
|
||||
}
|
||||
|
||||
protected function OutputStatus(
|
||||
EPlayer instigator,
|
||||
Database.DBQueryResult error)
|
||||
{
|
||||
if (instigator == none) {
|
||||
return;
|
||||
}
|
||||
if (error == DBR_Success) {
|
||||
instigator.BorrowConsole().WriteLine(T(TQUERY_COMPLETED));
|
||||
}
|
||||
if (error == DBR_InvalidPointer) {
|
||||
instigator.BorrowConsole().WriteLine(T(TQUERY_INVALID_POINTER));
|
||||
}
|
||||
if (error == DBR_InvalidDatabase) {
|
||||
instigator.BorrowConsole().WriteLine(T(TQUERY_INVALID_DB));
|
||||
}
|
||||
if (error == DBR_InvalidData) {
|
||||
instigator.BorrowConsole().WriteLine(T(TQUERY_INVALID_DATA));
|
||||
}
|
||||
}
|
||||
|
||||
protected function DisplayData(
|
||||
Database.DBQueryResult result,
|
||||
AcediaObject data,
|
||||
Database source,
|
||||
int requestID)
|
||||
{
|
||||
local Text printedJSON;
|
||||
local EPlayer instigator;
|
||||
|
||||
instigator = PopPlayer(source);
|
||||
OutputStatus(instigator, result);
|
||||
if (instigator != none && result == DBR_Success)
|
||||
{
|
||||
printedJSON = _.json.PrettyPrint(data).IntoText();
|
||||
instigator.BorrowConsole().Write(printedJSON).Flush();
|
||||
_.memory.Free(printedJSON);
|
||||
_.memory.Free(instigator);
|
||||
instigator = none;
|
||||
}
|
||||
_.memory.Free(data);
|
||||
}
|
||||
|
||||
protected function DisplaySize(
|
||||
Database.DBQueryResult result,
|
||||
int size,
|
||||
Database source,
|
||||
int requestID)
|
||||
{
|
||||
local Text sizeAsText;
|
||||
local EPlayer instigator;
|
||||
|
||||
instigator = PopPlayer(source);
|
||||
OutputStatus(instigator, result);
|
||||
if (instigator != none && result == DBR_Success)
|
||||
{
|
||||
sizeAsText = _.text.FromInt(size);
|
||||
instigator.BorrowConsole()
|
||||
.Write(T(TOBJECT_SIZE_IS))
|
||||
.Write(sizeAsText)
|
||||
.Flush();
|
||||
_.memory.Free(sizeAsText);
|
||||
_.memory.Free(instigator);
|
||||
instigator = none;
|
||||
}
|
||||
}
|
||||
|
||||
protected function DisplayKeys(
|
||||
Database.DBQueryResult result,
|
||||
ArrayList keys,
|
||||
Database source,
|
||||
int requestID)
|
||||
{
|
||||
local int i;
|
||||
local Text nextKey;
|
||||
local EPlayer instigator;
|
||||
local ConsoleWriter console;
|
||||
|
||||
instigator = PopPlayer(source);
|
||||
OutputStatus(instigator, result);
|
||||
if (keys == none) {
|
||||
return;
|
||||
}
|
||||
if (instigator != none && result == DBR_Success)
|
||||
{
|
||||
console = instigator.BorrowConsole();
|
||||
console.Write(T(TOBJECT_KEYS_ARE));
|
||||
for (i = 0; i < keys.GetLength(); i += 1)
|
||||
{
|
||||
if (i > 0) {
|
||||
console.ResetColor().Write(P(", "));
|
||||
}
|
||||
nextKey = keys.GetText(i);
|
||||
console.UseColor(_.color.jPropertyName).Write(nextKey);
|
||||
_.memory.Free(nextKey);
|
||||
}
|
||||
console.Flush();
|
||||
_.memory.Free(instigator);
|
||||
instigator = none;
|
||||
}
|
||||
_.memory.Free(keys);
|
||||
}
|
||||
|
||||
protected function DisplayResponse(
|
||||
Database.DBQueryResult result,
|
||||
Database source,
|
||||
int requestID)
|
||||
{
|
||||
local EPlayer instigator;
|
||||
|
||||
instigator = PopPlayer(source);
|
||||
OutputStatus(instigator, result);
|
||||
_.memory.Free(instigator);
|
||||
}
|
||||
|
||||
defaultproperties
|
||||
{
|
||||
preferredName = "db"
|
||||
TCREATE = 0
|
||||
stringConstants(0) = "create"
|
||||
TDELETE = 1
|
||||
stringConstants(1) = "delete"
|
||||
TLIST = 2
|
||||
stringConstants(2) = "list"
|
||||
TREAD = 3
|
||||
stringConstants(3) = "read"
|
||||
TSIZE = 4
|
||||
stringConstants(4) = "size"
|
||||
TKEYS = 5
|
||||
stringConstants(5) = "keys"
|
||||
TREMOVE = 6
|
||||
stringConstants(6) = "remove"
|
||||
TWRITE = 7
|
||||
stringConstants(7) = "write"
|
||||
TINCREMENT = 8
|
||||
stringConstants(8) = "increment"
|
||||
TDATABASE_NAME = 9
|
||||
stringConstants(9) = "databaseName"
|
||||
TDATABASE_LINK = 10
|
||||
stringConstants(10) = "databaseLink"
|
||||
TJSON_VALUE = 11
|
||||
stringConstants(11) = "jsonValue"
|
||||
TOBJECT_KEYS_ARE = 12
|
||||
stringConstants(12) = "{$TextEmphasis Object keys are:} "
|
||||
TOBJECT_SIZE_IS = 13
|
||||
stringConstants(13) = "{$TextEmphasis Object size is:} "
|
||||
TQUERY_COMPLETED = 14
|
||||
stringConstants(14) = "{$TextPositive Database query was completed!}"
|
||||
TQUERY_INVALID_POINTER = 15
|
||||
stringConstants(15) = "{$TextFailure Query was provided with an invalid JSON pointer.}"
|
||||
TQUERY_INVALID_DB = 16
|
||||
stringConstants(16) = "{$TextFailure Operation could not finish because database is damaged and unusable.}"
|
||||
TQUERY_INVALID_DATA = 17
|
||||
stringConstants(17) = "{$TextFailure Query data is invalid.}"
|
||||
TAVAILABLE_DATABASES = 18
|
||||
stringConstants(18) = "{$TextEmphasis Available databases:} "
|
||||
TDA_DELETED = 19
|
||||
stringConstants(19) = "{$TextPositive Database was deleted.}"
|
||||
TDB_DOESNT_EXIST = 20
|
||||
stringConstants(20) = "{$TextFailure Database with specified name does not exist.}"
|
||||
TDB_ALREADY_EXISTS = 21
|
||||
stringConstants(21) = "{$TextFailure Database with specified name already exists.}"
|
||||
TDB_CREATED = 22
|
||||
stringConstants(22) = "{$TextPositive Database was created.}"
|
||||
TDB_CANNOT_BE_CREATED = 23
|
||||
stringConstants(23) = "{$TextFailure Database cannot be created.}"
|
||||
TNO_DEFAULT_COMMAND = 24
|
||||
stringConstants(24) = "{$TextFailure Default command does nothing. Use on of the sub-commands.}"
|
||||
TBAD_DBLINK = 25
|
||||
stringConstants(25) = "{$TextFailure Database could not be read for the specified link.}"
|
||||
}
|
||||
94
kf_sources/Futility/Classes/ACommandDosh.uc
Normal file
94
kf_sources/Futility/Classes/ACommandDosh.uc
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* Command for changing amount of money players have.
|
||||
* Copyright 2021 - 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class ACommandDosh extends Command;
|
||||
|
||||
var private ACommandDosh_Announcer announcer;
|
||||
|
||||
protected function Finalizer()
|
||||
{
|
||||
_.memory.Free(announcer);
|
||||
super.Finalizer();
|
||||
}
|
||||
|
||||
protected function BuildData(CommandDataBuilder builder)
|
||||
{
|
||||
builder.Group(P("gameplay"));
|
||||
builder.Summary(P("Changes amount of money."));
|
||||
builder.RequireTarget();
|
||||
builder.ParamInteger(P("amount"));
|
||||
builder.Describe(P("Gives (or takes if negative) players a specified <amount>"
|
||||
@ "of money."));
|
||||
builder.SubCommand(P("set"));
|
||||
builder.ParamInteger(P("amount"));
|
||||
builder.Describe(P("Sets player's money to a specified <amount>."));
|
||||
builder.Option(P("min"));
|
||||
builder.ParamInteger(P("minValue"));
|
||||
builder.Describe(F("Players will retain at least this amount of dosh after"
|
||||
@ "the command's execution. In case of conflict, overrides"
|
||||
@ "'{$TextEmphasis --max}' option. `0` is assumed by default."));
|
||||
builder.Option(P("max"), P("M"));
|
||||
builder.ParamInteger(P("maxValue"));
|
||||
builder.Describe(F("Players will have at most this amount of dosh after"
|
||||
@ "the command's execution. In case of conflict, it is overridden"
|
||||
@ "by '{$TextEmphasis --min}' option."));
|
||||
announcer = ACommandDosh_Announcer(
|
||||
_.memory.Allocate(class'ACommandDosh_Announcer'));
|
||||
}
|
||||
|
||||
protected function ExecutedFor(
|
||||
EPlayer target,
|
||||
CallData arguments,
|
||||
EPlayer instigator,
|
||||
CommandPermissions permissions
|
||||
) {
|
||||
local int oldAmount, newAmount;
|
||||
local int amount, minValue, maxValue;
|
||||
|
||||
// Find min and max value boundaries
|
||||
minValue = arguments.options.GetIntBy(P("/min/minValue"), 0);
|
||||
maxValue = arguments.options.GetIntBy(P("/max/maxValue"), MaxInt);
|
||||
if (minValue > maxValue) {
|
||||
maxValue = minValue;
|
||||
}
|
||||
// Change dosh
|
||||
oldAmount = target.GetDosh();
|
||||
amount = arguments.parameters.GetInt(P("amount"));
|
||||
if (arguments.subCommandName.IsEmpty()) {
|
||||
newAmount = oldAmount + amount;
|
||||
}
|
||||
else {
|
||||
// This has to be "dosh set"
|
||||
newAmount = amount;
|
||||
}
|
||||
newAmount = Clamp(newAmount, minValue, maxValue);
|
||||
target.SetDosh(newAmount);
|
||||
// Announce dosh change, if necessary
|
||||
announcer.Setup(target, instigator, othersConsole);
|
||||
if (newAmount > oldAmount) {
|
||||
announcer.AnnounceGainedDosh(newAmount - oldAmount);
|
||||
}
|
||||
if (newAmount < oldAmount) {
|
||||
announcer.AnnounceLostDosh(oldAmount - newAmount);
|
||||
}
|
||||
}
|
||||
|
||||
defaultproperties {
|
||||
preferredName = "dosh"
|
||||
}
|
||||
92
kf_sources/Futility/Classes/ACommandDosh_Announcer.uc
Normal file
92
kf_sources/Futility/Classes/ACommandDosh_Announcer.uc
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
* Announcer for `ACommandDosh`.
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class ACommandDosh_Announcer extends CommandAnnouncer;
|
||||
|
||||
var private AnnouncementVariations gainedDosh, lostDosh;
|
||||
|
||||
protected function Finalizer()
|
||||
{
|
||||
FreeVariations(gainedDosh);
|
||||
FreeVariations(lostDosh);
|
||||
super.Finalizer();
|
||||
}
|
||||
|
||||
public final function AnnounceGainedDosh(int doshAmount)
|
||||
{
|
||||
local int i;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!gainedDosh.initialized)
|
||||
{
|
||||
gainedDosh.initialized = true;
|
||||
gainedDosh.toSelfReport = _.text.MakeTemplate_S(
|
||||
"You {$TextPositive gave} yourself {$TypeNumber %1} do$h!");
|
||||
gainedDosh.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextPositive gave} themselves {$TypeNumber %1}"
|
||||
@ "do$h!");
|
||||
gainedDosh.toOtherReport = _.text.MakeTemplate_S(
|
||||
"You {$TextPositive gave} %%target%% {$TypeNumber %1} do$h!");
|
||||
gainedDosh.toOtherPrivate = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextPositive gave} you {$TypeNumber %1} do$h!");
|
||||
gainedDosh.toOtherPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextPositive gave} %%target%% {$TypeNumber %1}"
|
||||
@ "do$h!");
|
||||
}
|
||||
templates = MakeArray(gainedDosh);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().ArgInt(doshAmount);
|
||||
}
|
||||
MakeAnnouncement(gainedDosh);
|
||||
}
|
||||
|
||||
public final function AnnounceLostDosh(int doshAmount)
|
||||
{
|
||||
local int i;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!lostDosh.initialized)
|
||||
{
|
||||
lostDosh.initialized = true;
|
||||
lostDosh.toSelfReport = _.text.MakeTemplate_S(
|
||||
"You {$TextNegative took} {$TypeNumber %1} do$h from"
|
||||
@ "yourself!");
|
||||
lostDosh.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextNegative took} {$TypeNumber %1} do$h from"
|
||||
@ "themselves!");
|
||||
lostDosh.toOtherReport = _.text.MakeTemplate_S(
|
||||
"You {$TextNegative took} {$TypeNumber %1} do$h from"
|
||||
@ "%%target%%!");
|
||||
lostDosh.toOtherPrivate = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextNegative took} {$TypeNumber %1} do$h from"
|
||||
@ "you!");
|
||||
lostDosh.toOtherPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextNegative took} {$TypeNumber %1} do$h from"
|
||||
@ "%%target%%!");
|
||||
}
|
||||
templates = MakeArray(lostDosh);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().ArgInt(doshAmount);
|
||||
}
|
||||
MakeAnnouncement(lostDosh);
|
||||
}
|
||||
|
||||
defaultproperties
|
||||
{
|
||||
}
|
||||
579
kf_sources/Futility/Classes/ACommandFeature.uc
Normal file
579
kf_sources/Futility/Classes/ACommandFeature.uc
Normal file
|
|
@ -0,0 +1,579 @@
|
|||
/**
|
||||
* Command for managing features.
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class ACommandFeature extends Command
|
||||
dependson(PendingConfigsTool);
|
||||
|
||||
var private class<Feature> selectedFeatureClass;
|
||||
var private Text selectedFeatureName;
|
||||
var private Text selectedConfigName;
|
||||
|
||||
var private PendingConfigsTool pendingConfigs;
|
||||
var private ACommandFeature_Announcer announcer;
|
||||
|
||||
protected function Constructor() {
|
||||
pendingConfigs = PendingConfigsTool(_.memory.Allocate(class'PendingConfigsTool'));
|
||||
super.Constructor();
|
||||
}
|
||||
|
||||
protected function Finalizer() {
|
||||
_.memory.Free(announcer);
|
||||
_.memory.Free(pendingConfigs);
|
||||
super.Finalizer();
|
||||
}
|
||||
|
||||
protected function BuildData(CommandDataBuilder builder) {
|
||||
builder.Group(P("admin"));
|
||||
builder.Summary(P("Managing features."));
|
||||
builder.Describe(P("Command for managing features and their configs."));
|
||||
builder.SubCommand(P("enable"));
|
||||
builder.ParamText(P("feature"),, P("feature"));
|
||||
builder.OptionalParams();
|
||||
builder.ParamText(P("config"));
|
||||
builder.Describe(P("Enables specified <feature>. If <config> isn't specified -"
|
||||
@ "choses the \"default\" one, making new config with default"
|
||||
@ "settings if it doesn't exist."));
|
||||
builder.SubCommand(P("disable"));
|
||||
builder.ParamText(P("feature"),, P("feature"));
|
||||
builder.Describe(P("Disables specified <feature>."));
|
||||
builder.SubCommand(P("showconf"));
|
||||
builder.ParamText(P("feature"),, P("feature"));
|
||||
builder.OptionalParams();
|
||||
builder.ParamText(P("config"));
|
||||
builder.Describe(P("Show given <config> for the given <feature>."));
|
||||
builder.SubCommand(P("editconf"));
|
||||
builder.ParamText(P("feature"),, P("feature"));
|
||||
builder.ParamText(P("config"));
|
||||
builder.ParamText(P("variable_path"));
|
||||
builder.ParamJSON(P("value"));
|
||||
builder.Describe(P("Changes a value inside given <config> of the given"
|
||||
@ "<feature> by setting value at JSON path <variable_path> to"
|
||||
@ "the JSON value <value>. Changes won't be immediately applied to"
|
||||
@ "the game and kept as pending."));
|
||||
builder.SubCommand(P("saveconf"));
|
||||
builder.ParamText(P("feature"),, P("feature"));
|
||||
builder.ParamText(P("config"));
|
||||
builder.Describe(P("Saves pending changes for the given <config> of the given"
|
||||
@ "<feature>."));
|
||||
builder.SubCommand(P("newconf"));
|
||||
builder.ParamText(P("feature"),, P("feature"));
|
||||
builder.ParamText(P("config"));
|
||||
builder.Describe(P("Creates new config for the given <feature>."));
|
||||
builder.SubCommand(P("removeconf"));
|
||||
builder.ParamText(P("feature"),, P("feature"));
|
||||
builder.ParamText(P("config"));
|
||||
builder.Describe(P("Removes specified <config> of the specified <feature>."));
|
||||
builder.SubCommand(P("autoconf"));
|
||||
builder.ParamText(P("feature"),, P("feature"));
|
||||
builder.OptionalParams();
|
||||
builder.ParamText(P("config"));
|
||||
builder.Describe(P("Changes current auto config config of the specified"
|
||||
@ "<feature>. Auto config is a config that is supposed to be"
|
||||
@ "automatically enabled at the start of the Acedia, unless"
|
||||
@ "otherwise specified for the loader."));
|
||||
builder.Option(P("all"));
|
||||
builder.Describe(F("Affects subcommand {$TextEmphasis showconf} by making it"
|
||||
@ "show all available configs."));
|
||||
builder.Option(P("save"));
|
||||
builder.Describe(F("Affects subcommand {$TextEmphasis editconf} by making it"
|
||||
@ "also save all pending changes."));
|
||||
announcer = ACommandFeature_Announcer(
|
||||
_.memory.Allocate(class'ACommandFeature_Announcer'));
|
||||
}
|
||||
|
||||
protected function Executed(
|
||||
CallData arguments,
|
||||
EPlayer instigator,
|
||||
CommandPermissions permissions
|
||||
) {
|
||||
local bool saveFlag, allFlag;
|
||||
|
||||
announcer.Setup(none, instigator, othersConsole);
|
||||
saveFlag = arguments.options.HasKey(P("save"));
|
||||
allFlag = arguments.options.HasKey(P("all"));
|
||||
SelectFeatureAndConfig(arguments);
|
||||
if (arguments.subCommandName.IsEmpty()) {
|
||||
ShowAllFeatures();
|
||||
} else if (arguments.subCommandName.Compare(P("enable"))) {
|
||||
EnableFeature();
|
||||
} else if (arguments.subCommandName.Compare(P("disable"))) {
|
||||
DisableFeature();
|
||||
} else if (arguments.subCommandName.Compare(P("showconf"))) {
|
||||
ShowSelectedConfigs(allFlag);
|
||||
} else if (arguments.subCommandName.Compare(P("editconf"))) {
|
||||
EditFeatureConfig(
|
||||
arguments.parameters.GetText(P("variable_path")),
|
||||
arguments.parameters.GetItem(P("value")),
|
||||
saveFlag);
|
||||
} else if (arguments.subCommandName.Compare(P("saveconf"))) {
|
||||
SaveFeatureConfig();
|
||||
} else if (arguments.subCommandName.Compare(P("newconf"))) {
|
||||
NewFeatureConfig();
|
||||
} else if (arguments.subCommandName.Compare(P("removeconf"))) {
|
||||
RemoveFeatureConfig();
|
||||
} else if (arguments.subCommandName.Compare(P("autoconf"))) {
|
||||
SetAutoFeatureConfig();
|
||||
}
|
||||
_.memory.Free2(selectedConfigName, selectedFeatureName);
|
||||
selectedConfigName = none;
|
||||
selectedFeatureName = none;
|
||||
}
|
||||
|
||||
protected function SelectFeatureAndConfig(CallData arguments) {
|
||||
local Text featureClassName, userGivenConfigName;
|
||||
|
||||
selectedFeatureName = arguments.parameters.GetTextBy(P("/feature/alias"));
|
||||
featureClassName = arguments.parameters.GetTextBy(P("/feature/value"));
|
||||
selectedFeatureClass = LoadFeatureClass(featureClassName);
|
||||
if (selectedFeatureClass == none && !arguments.subCommandName.IsEmpty()) {
|
||||
_.memory.Free(selectedFeatureName);
|
||||
selectedFeatureName = none;
|
||||
return;
|
||||
}
|
||||
_.memory.Free(featureClassName);
|
||||
userGivenConfigName = arguments.parameters.GetText(P("config"));
|
||||
if (userGivenConfigName != none) {
|
||||
selectedConfigName = userGivenConfigName.LowerCopy();
|
||||
userGivenConfigName.FreeSelf();
|
||||
}
|
||||
pendingConfigs.SelectConfig(selectedFeatureClass, selectedConfigName);
|
||||
}
|
||||
|
||||
protected function EnableFeature() {
|
||||
local bool wasEnabled;
|
||||
local Text oldConfig, newConfig;
|
||||
local Feature instance;
|
||||
|
||||
wasEnabled = selectedFeatureClass.static.IsEnabled();
|
||||
oldConfig = selectedFeatureClass.static.GetCurrentConfig();
|
||||
newConfig = PickConfigBasedOnParameter();
|
||||
// Already enabled with the same config!
|
||||
if (oldConfig != none && oldConfig.Compare(newConfig, SCASE_INSENSITIVE)) {
|
||||
announcer.AnnounceFailedAlreadyEnabled(selectedFeatureName, newConfig);
|
||||
_.memory.Free(newConfig);
|
||||
_.memory.Free(oldConfig);
|
||||
return;
|
||||
}
|
||||
// Try enabling and report the result
|
||||
instance = selectedFeatureClass.static.EnableMe(newConfig);
|
||||
if (instance == none) {
|
||||
announcer.AnnounceFailedCannotEnableFeature(
|
||||
selectedFeatureName,
|
||||
newConfig);
|
||||
} else if (wasEnabled) {
|
||||
announcer.AnnounceSwappedConfig(
|
||||
selectedFeatureName,
|
||||
oldConfig,
|
||||
newConfig);
|
||||
} else {
|
||||
announcer.AnnounceEnabledFeature(selectedFeatureName, newConfig);
|
||||
}
|
||||
_.memory.Free(newConfig);
|
||||
_.memory.Free(oldConfig);
|
||||
}
|
||||
|
||||
protected function DisableFeature() {
|
||||
if (!selectedFeatureClass.static.IsEnabled()) {
|
||||
announcer.AnnounceFailedAlreadyDisabled(selectedFeatureName);
|
||||
return;
|
||||
}
|
||||
selectedFeatureClass.static.DisableMe();
|
||||
// It is possible that this command itself is destroyed after above command
|
||||
// so do the check just in case
|
||||
if (IsAllocated()) {
|
||||
announcer.AnnounceDisabledFeature(selectedFeatureName);
|
||||
}
|
||||
}
|
||||
|
||||
protected function ShowSelectedConfigs(bool showAllFeatures) {
|
||||
local int i;
|
||||
local array<Text> availableConfigs;
|
||||
local MutableText configList;
|
||||
local class<FeatureConfig> configClass;
|
||||
|
||||
if (showAllFeatures) {
|
||||
configClass = selectedFeatureClass.default.configClass;
|
||||
if (configClass != none) {
|
||||
availableConfigs = configClass.static.AvailableConfigs();
|
||||
}
|
||||
for (i = 0; i < availableConfigs.length; i += 1) {
|
||||
ShowFeatureConfig(availableConfigs[i]);
|
||||
}
|
||||
_.memory.FreeMany(availableConfigs);
|
||||
return;
|
||||
}
|
||||
if (selectedConfigName != none) {
|
||||
ShowFeatureConfig(selectedConfigName);
|
||||
return;
|
||||
}
|
||||
configList = PrintConfigList(selectedFeatureClass);
|
||||
callerConsole
|
||||
.Flush()
|
||||
.Write(P("Available configs: "))
|
||||
.WriteLine(configList);
|
||||
_.memory.Free(configList);
|
||||
}
|
||||
|
||||
protected function ShowFeatureConfig(BaseText configName) {
|
||||
local MutableText dataAsJSON;
|
||||
local HashTable currentData, pendingData;
|
||||
|
||||
if (configName == none) {
|
||||
return;
|
||||
}
|
||||
currentData = GetCurrentConfigData(configName);
|
||||
if (currentData == none) {
|
||||
announcer.AnnounceFailedNoDataForConfig(
|
||||
selectedFeatureName,
|
||||
configName);
|
||||
return;
|
||||
}
|
||||
// Display current data
|
||||
dataAsJSON = _.json.PrettyPrint(currentData);
|
||||
announcer.AnnounceCurrentConfig(selectedFeatureName, configName);
|
||||
callerConsole.Flush().WriteLine(dataAsJSON);
|
||||
_.memory.Free(dataAsJSON);
|
||||
// Display pending data
|
||||
pendingConfigs.SelectConfig(selectedFeatureClass, configName);
|
||||
pendingData = pendingConfigs.GetPendingConfigData();
|
||||
if (pendingData != none) {
|
||||
dataAsJSON = _.json.PrettyPrint(pendingData);
|
||||
announcer.AnnouncePendingConfig(
|
||||
selectedFeatureName,
|
||||
configName);
|
||||
callerConsole.Flush().WriteLine(dataAsJSON);
|
||||
_.memory.Free(dataAsJSON);
|
||||
}
|
||||
_.memory.Free(pendingData);
|
||||
_.memory.Free(currentData);
|
||||
}
|
||||
|
||||
protected function Text PickConfigBasedOnParameter() {
|
||||
local Text resolvedConfig;
|
||||
local class<FeatureConfig> configClass;
|
||||
|
||||
configClass = selectedFeatureClass.default.configClass;
|
||||
if (configClass == none) {
|
||||
announcer.AnnounceFailedNoConfigClass(selectedFeatureName);
|
||||
return none;
|
||||
}
|
||||
// If config was specified - simply check that it exists
|
||||
if (selectedConfigName != none) {
|
||||
if (configClass.static.Exists(selectedConfigName)) {
|
||||
return selectedConfigName.Copy();
|
||||
}
|
||||
announcer.AnnounceFailedConfigMissing(selectedConfigName);
|
||||
return none;
|
||||
}
|
||||
// If it wasn't specified - try auto config instead
|
||||
resolvedConfig = configClass.static.GetAutoEnabledConfig();
|
||||
if (resolvedConfig == none) {
|
||||
announcer.AnnounceFailedNoConfigProvided(selectedFeatureName);
|
||||
}
|
||||
return resolvedConfig;
|
||||
}
|
||||
|
||||
protected function class<Feature> LoadFeatureClass(BaseText featureClassName) {
|
||||
local class<Feature> featureClass;
|
||||
|
||||
if (featureClassName == none) {
|
||||
return none;
|
||||
}
|
||||
featureClass = class<Feature>(_.memory.LoadClass(featureClassName));
|
||||
if (featureClass == none) {
|
||||
announcer.AnnounceFailedToLoadFeatureClass(featureClassName);
|
||||
}
|
||||
return featureClass;
|
||||
}
|
||||
|
||||
protected function ShowAllFeatures() {
|
||||
local int i;
|
||||
local array< class<Feature> > availableFeatures;
|
||||
|
||||
availableFeatures = _.environment.GetAvailableFeatures();
|
||||
for (i = 0; i < availableFeatures.length; i ++) {
|
||||
ShowFeature(availableFeatures[i]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function ShowFeature(class<Feature> featureClass)
|
||||
{
|
||||
local MutableText featureName;
|
||||
local MutableText configList;
|
||||
|
||||
if (featureClass == none) {
|
||||
return;
|
||||
}
|
||||
featureName = _.text
|
||||
.FromClassMutable(featureClass)
|
||||
.ChangeDefaultColor(_.color.TextEmphasis);
|
||||
configList = PrintConfigList(featureClass);
|
||||
callerConsole.Flush();
|
||||
if (featureClass.static.IsEnabled()) {
|
||||
callerConsole.Write(F("[ {$TextPositive enabled} ] "));
|
||||
}
|
||||
else {
|
||||
callerConsole.Write(F("[ {$TextNegative disabled} ] "));
|
||||
}
|
||||
callerConsole.Write(featureName)
|
||||
.Write(P(" with configs: "))
|
||||
.WriteLine(configList);
|
||||
_.memory.Free(featureName);
|
||||
_.memory.Free(configList);
|
||||
}
|
||||
|
||||
protected function MutableText PrintConfigList(class<Feature> featureClass) {
|
||||
local int i;
|
||||
local Text autoConfig, enabledConfig;
|
||||
local ListBuilder configList;
|
||||
local MutableText result, nextConfig;
|
||||
local array<Text> availableConfigs;
|
||||
local class<FeatureConfig> configClass;
|
||||
|
||||
if (featureClass == none) return none;
|
||||
configClass = featureClass.default.configClass;
|
||||
if (configClass == none) return none;
|
||||
|
||||
availableConfigs = configClass.static.AvailableConfigs();
|
||||
enabledConfig = featureClass.static.GetCurrentConfig();
|
||||
autoConfig = configClass.static.GetAutoEnabledConfig();
|
||||
configList = ListBuilder(_.memory.Allocate(class'ListBuilder'));
|
||||
for (i = 0; i < availableConfigs.length; i += 1) {
|
||||
nextConfig = availableConfigs[i].MutableCopy();
|
||||
if (enabledConfig != none && enabledConfig.Compare(nextConfig, SCASE_INSENSITIVE)) {
|
||||
nextConfig.ChangeDefaultColor(_.color.TextPositive);
|
||||
}
|
||||
if (pendingConfigs.HasPendingConfigFor(featureClass, nextConfig)) {
|
||||
nextConfig.Append(F("{$TextEmphasis *}"));
|
||||
}
|
||||
configList.Item(nextConfig);
|
||||
_.memory.Free(nextConfig);
|
||||
if (autoConfig != none && autoConfig.Compare(availableConfigs[i], SCASE_INSENSITIVE)) {
|
||||
if (autoConfig.Compare(enabledConfig, SCASE_INSENSITIVE)) {
|
||||
configList.Comment(F("{$TextPositive auto enabled}"));
|
||||
} else {
|
||||
configList.Comment(F("{$TextNegative auto enabled}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
result = configList.GetMutable();
|
||||
_.memory.Free3(configList, autoConfig, enabledConfig);
|
||||
_.memory.FreeMany(availableConfigs);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected function MutableText PrettyPrintValueAt(BaseText pathToValue) {
|
||||
local MutableText printedValue;
|
||||
local AcediaObject value;
|
||||
local HashTable relevantData;
|
||||
|
||||
relevantData = pendingConfigs.GetPendingConfigData();
|
||||
if (relevantData == none) {
|
||||
relevantData = GetCurrentSelectedConfigData();
|
||||
}
|
||||
if (relevantData != none) {
|
||||
value = relevantData.GetItemBy(pathToValue);
|
||||
}
|
||||
if (value != none) {
|
||||
printedValue = _.json.PrettyPrint(value);
|
||||
_.memory.Free(value);
|
||||
}
|
||||
_.memory.Free(relevantData);
|
||||
return printedValue;
|
||||
}
|
||||
|
||||
protected function EditFeatureConfig(BaseText pathToValue, AcediaObject newValue, bool saveConfig) {
|
||||
local MutableText printedOldValue;
|
||||
local MutableText printedNewValue;
|
||||
local PendingConfigsTool.PendingConfigToolResult error;
|
||||
|
||||
if (selectedFeatureClass == none) {
|
||||
return;
|
||||
}
|
||||
printedOldValue = PrettyPrintValueAt(pathToValue);
|
||||
error = pendingConfigs.EditConfig(pathToValue, newValue);
|
||||
if (error == PCTE_None) {
|
||||
printedNewValue = PrettyPrintValueAt(pathToValue);
|
||||
}
|
||||
if (error == PCTE_ConfigMissing) {
|
||||
announcer.AnnounceFailedConfigMissing(selectedConfigName);
|
||||
}
|
||||
else if (error == PCTE_ExpectedObject) {
|
||||
announcer.AnnounceFailedExpectedObject();
|
||||
} else if (error == PCTE_BadPointer) {
|
||||
announcer.AnnounceFailedBadPointer(
|
||||
selectedFeatureName,
|
||||
selectedConfigName,
|
||||
pathToValue);
|
||||
} else if (printedOldValue == none) {
|
||||
announcer.AnnounceConfigNewValue(
|
||||
selectedFeatureName,
|
||||
selectedConfigName,
|
||||
pathToValue,
|
||||
printedNewValue);
|
||||
} else {
|
||||
announcer.AnnounceConfigEdited(
|
||||
selectedFeatureName,
|
||||
selectedConfigName,
|
||||
pathToValue,
|
||||
printedOldValue,
|
||||
printedNewValue);
|
||||
}
|
||||
if (saveConfig && error == PCTE_None) {
|
||||
SaveFeatureConfig();
|
||||
}
|
||||
_.memory.Free(printedOldValue);
|
||||
_.memory.Free(printedNewValue);
|
||||
_.memory.Free(pathToValue);
|
||||
_.memory.Free(newValue);
|
||||
}
|
||||
|
||||
protected function SaveFeatureConfig() {
|
||||
local BaseText enabledConfigName;
|
||||
local HashTable pendingData;
|
||||
local class<FeatureConfig> configClass;
|
||||
|
||||
configClass = selectedFeatureClass.default.configClass;
|
||||
if (configClass == none) {
|
||||
announcer.AnnounceFailedNoConfigClass(selectedFeatureName);
|
||||
return;
|
||||
}
|
||||
pendingData = pendingConfigs.GetPendingConfigData();
|
||||
if (pendingData == none) {
|
||||
announcer.AnnounceFailedPendingConfigMissing(selectedConfigName);
|
||||
return;
|
||||
}
|
||||
// Make sure config exists
|
||||
configClass.static.NewConfig(selectedConfigName);
|
||||
configClass.static.SaveData(selectedConfigName, pendingData);
|
||||
// Re-apply config if it is active?
|
||||
enabledConfigName = selectedFeatureClass.static.GetCurrentConfig();
|
||||
if (selectedConfigName.Compare(enabledConfigName, SCASE_INSENSITIVE)) {
|
||||
selectedFeatureClass.static.EnableMe(selectedConfigName);
|
||||
announcer.AnnouncePublicPendingConfigSaved(selectedFeatureName);
|
||||
} else {
|
||||
announcer.AnnouncePrivatePendingConfigSaved(selectedFeatureName, selectedConfigName);
|
||||
}
|
||||
_.memory.Free(enabledConfigName);
|
||||
pendingData.FreeSelf();
|
||||
pendingConfigs.RemoveConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
protected function NewFeatureConfig() {
|
||||
local BaseText enabledConfigName;
|
||||
local class<FeatureConfig> configClass;
|
||||
|
||||
configClass = selectedFeatureClass.default.configClass;
|
||||
if (configClass == none) {
|
||||
announcer.AnnounceFailedNoConfigClass(selectedFeatureName);
|
||||
return;
|
||||
}
|
||||
if (configClass.static.Exists(selectedConfigName)) {
|
||||
announcer.AnnounceFailedConfigAlreadyExists(selectedFeatureName, selectedConfigName);
|
||||
return;
|
||||
}
|
||||
if (!configClass.static.NewConfig(selectedConfigName)) {
|
||||
announcer.AnnounceFailedBadConfigName(selectedConfigName);
|
||||
return;
|
||||
}
|
||||
enabledConfigName = selectedFeatureClass.static.GetCurrentConfig();
|
||||
if (selectedConfigName.Compare(enabledConfigName, SCASE_INSENSITIVE)) {
|
||||
selectedFeatureClass.static.EnableMe(selectedConfigName);
|
||||
announcer.AnnouncePublicPendingConfigSaved(selectedFeatureName);
|
||||
}
|
||||
_.memory.Free(enabledConfigName);
|
||||
announcer.AnnounceConfigCreated(selectedFeatureName, selectedConfigName);
|
||||
}
|
||||
|
||||
protected function RemoveFeatureConfig() {
|
||||
local class<FeatureConfig> configClass;
|
||||
|
||||
configClass = selectedFeatureClass.default.configClass;
|
||||
if (configClass == none) {
|
||||
announcer.AnnounceFailedNoConfigClass(selectedFeatureName);
|
||||
return;
|
||||
}
|
||||
if (!configClass.static.Exists(selectedConfigName)) {
|
||||
announcer.AnnounceFailedConfigDoesNotExist(
|
||||
selectedFeatureName,
|
||||
selectedConfigName);
|
||||
return;
|
||||
}
|
||||
pendingConfigs.RemoveConfig();
|
||||
configClass.static.DeleteConfig(selectedConfigName);
|
||||
announcer.AnnounceConfigRemoved(selectedFeatureName, selectedConfigName);
|
||||
}
|
||||
|
||||
protected function SetAutoFeatureConfig() {
|
||||
local Text currentAutoEnabledConfig;
|
||||
local class<FeatureConfig> configClass;
|
||||
|
||||
configClass = selectedFeatureClass.default.configClass;
|
||||
if (configClass == none) {
|
||||
announcer.AnnounceFailedNoConfigClass(selectedFeatureName);
|
||||
return;
|
||||
}
|
||||
if (selectedConfigName != none && !configClass.static.Exists(selectedConfigName)) {
|
||||
announcer.AnnounceFailedConfigDoesNotExist(selectedFeatureName, selectedConfigName);
|
||||
return;
|
||||
}
|
||||
currentAutoEnabledConfig = configClass.static.GetAutoEnabledConfig();
|
||||
if (selectedConfigName == none && currentAutoEnabledConfig == none) {
|
||||
announcer.AnnounceFailedAlreadyNoAutoEnabled(selectedFeatureName);
|
||||
}
|
||||
else if (selectedConfigName != none &&
|
||||
selectedConfigName.Compare(currentAutoEnabledConfig, SCASE_INSENSITIVE)) {
|
||||
announcer.AnnounceFailedAlreadySameAutoEnabled(
|
||||
selectedFeatureName,
|
||||
selectedConfigName);
|
||||
} else {
|
||||
configClass.static.SetAutoEnabledConfig(selectedConfigName);
|
||||
if (selectedConfigName != none) {
|
||||
announcer.AnnounceAutoEnabledConfig(
|
||||
selectedFeatureName,
|
||||
selectedConfigName);
|
||||
} else {
|
||||
announcer.AnnounceRemovedAutoEnabledConfig(selectedFeatureName);
|
||||
}
|
||||
}
|
||||
_.memory.Free(currentAutoEnabledConfig);
|
||||
}
|
||||
|
||||
private function HashTable GetCurrentConfigData(BaseText configName) {
|
||||
local class<FeatureConfig> configClass;
|
||||
|
||||
if (configName == none) {
|
||||
return none;
|
||||
}
|
||||
configClass = selectedFeatureClass.default.configClass;
|
||||
if (configClass == none) {
|
||||
announcer.AnnounceFailedNoConfigClass(selectedFeatureName);
|
||||
return none;
|
||||
}
|
||||
return configClass.static.LoadData(configName);
|
||||
}
|
||||
|
||||
private function HashTable GetCurrentSelectedConfigData() {
|
||||
return GetCurrentConfigData(selectedConfigName);
|
||||
}
|
||||
|
||||
defaultproperties {
|
||||
preferredName = "feature"
|
||||
}
|
||||
594
kf_sources/Futility/Classes/ACommandFeature_Announcer.uc
Normal file
594
kf_sources/Futility/Classes/ACommandFeature_Announcer.uc
Normal file
|
|
@ -0,0 +1,594 @@
|
|||
/**
|
||||
* Announcer for `ACommandFeature`.
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class ACommandFeature_Announcer extends CommandAnnouncer;
|
||||
|
||||
var private AnnouncementVariations enabledFeature, disabledFeature;
|
||||
var private AnnouncementVariations swappedConfig, pendingConfigSaved;
|
||||
var private AnnouncementVariations showCurrentConfig, showPendingConfig;
|
||||
var private AnnouncementVariations configCreated, configRemoved, configEdited;
|
||||
var private AnnouncementVariations configEditedNew;
|
||||
var private AnnouncementVariations pendingConfigSavedPublic;
|
||||
var private AnnouncementVariations pendingConfigSavedPrivate;
|
||||
var private AnnouncementVariations autoEnabled, removedAutoEnabled;
|
||||
var private AnnouncementVariations failedAlreadyNoAutoEnabled;
|
||||
var private AnnouncementVariations failedAlreadySameAutoEnabled;
|
||||
var private AnnouncementVariations failedConfigAlreadyExists;
|
||||
var private AnnouncementVariations failedConfigDoesNotExists;
|
||||
var private AnnouncementVariations failedToLoadFeatureClass;
|
||||
var private AnnouncementVariations failedNoConfigProvided, failedConfigMissing;
|
||||
var private AnnouncementVariations failedCannotEnableFeature;
|
||||
var private AnnouncementVariations failedNoConfigClass, failedBadConfigName;
|
||||
var private AnnouncementVariations failedAlreadyEnabled, failedAlreadyDisabled;
|
||||
var private AnnouncementVariations failedNoDataForConfig, failedExpectedObject;
|
||||
var private AnnouncementVariations failedBadPointer, failedPendingConfigMissing;
|
||||
|
||||
protected function Finalizer()
|
||||
{
|
||||
FreeVariations(enabledFeature);
|
||||
FreeVariations(disabledFeature);
|
||||
FreeVariations(swappedConfig);
|
||||
FreeVariations(pendingConfigSaved);
|
||||
FreeVariations(showCurrentConfig);
|
||||
FreeVariations(showPendingConfig);
|
||||
FreeVariations(configCreated);
|
||||
FreeVariations(configRemoved);
|
||||
FreeVariations(configEdited);
|
||||
FreeVariations(configEditedNew);
|
||||
FreeVariations(pendingConfigSavedPublic);
|
||||
FreeVariations(pendingConfigSavedPrivate);
|
||||
FreeVariations(autoEnabled);
|
||||
FreeVariations(removedAutoEnabled);
|
||||
FreeVariations(failedAlreadyNoAutoEnabled);
|
||||
FreeVariations(failedAlreadySameAutoEnabled);
|
||||
FreeVariations(failedConfigAlreadyExists);
|
||||
FreeVariations(failedConfigDoesNotExists);
|
||||
FreeVariations(failedToLoadFeatureClass);
|
||||
FreeVariations(failedNoConfigProvided);
|
||||
FreeVariations(failedConfigMissing);
|
||||
FreeVariations(failedCannotEnableFeature);
|
||||
FreeVariations(failedNoConfigClass);
|
||||
FreeVariations(failedBadConfigName);
|
||||
FreeVariations(failedAlreadyEnabled);
|
||||
FreeVariations(failedAlreadyDisabled);
|
||||
FreeVariations(failedNoDataForConfig);
|
||||
FreeVariations(failedExpectedObject);
|
||||
FreeVariations(failedBadPointer);
|
||||
FreeVariations(failedPendingConfigMissing);
|
||||
super.Finalizer();
|
||||
}
|
||||
|
||||
public final function AnnounceEnabledFeature(
|
||||
BaseText featureName,
|
||||
BaseText configName)
|
||||
{
|
||||
local int i;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!enabledFeature.initialized)
|
||||
{
|
||||
enabledFeature.initialized = true;
|
||||
enabledFeature.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Feature {$TextEmphasis `%1`} {$TextPositive enabled} with config"
|
||||
@ "\"%2\"");
|
||||
enabledFeature.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextPositive enabled} feature"
|
||||
@ "{$TextEmphasis `%1`} with config \"%2\"");
|
||||
}
|
||||
templates = MakeArray(enabledFeature);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().Arg(featureName).Arg(configName);
|
||||
}
|
||||
MakeAnnouncement(enabledFeature);
|
||||
}
|
||||
|
||||
public final function AnnounceDisabledFeature(BaseText featureName)
|
||||
{
|
||||
local int i;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!disabledFeature.initialized)
|
||||
{
|
||||
disabledFeature.initialized = true;
|
||||
disabledFeature.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Feature {$TextEmphasis `%1`} {$TextNegative disabled}");
|
||||
disabledFeature.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextNegative disabled} feature"
|
||||
@ "{$TextEmphasis `%1`}");
|
||||
}
|
||||
templates = MakeArray(disabledFeature);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().Arg(featureName);
|
||||
}
|
||||
MakeAnnouncement(disabledFeature);
|
||||
}
|
||||
|
||||
public final function AnnounceSwappedConfig(
|
||||
BaseText featureName,
|
||||
BaseText oldConfig,
|
||||
BaseText newConfig)
|
||||
{
|
||||
local int i;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!swappedConfig.initialized)
|
||||
{
|
||||
swappedConfig.initialized = true;
|
||||
swappedConfig.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Config for feature {$TextEmphasis `%1`} {$TextNeutral swapped}"
|
||||
@ "from \"%2\" to \"%3\"");
|
||||
swappedConfig.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextNeutral swapped} config for feature"
|
||||
@ "{$TextEmphasis `%1`} from \"%2\" to \"%3\"");
|
||||
}
|
||||
templates = MakeArray(swappedConfig);
|
||||
for (i = 0; i < templates.length; i += 1)
|
||||
{
|
||||
templates[i]
|
||||
.Reset()
|
||||
.Arg(featureName)
|
||||
.Arg(oldConfig)
|
||||
.Arg(newConfig);
|
||||
}
|
||||
MakeAnnouncement(swappedConfig);
|
||||
}
|
||||
|
||||
public final function AnnouncePublicPendingConfigSaved(
|
||||
BaseText featureName)
|
||||
{
|
||||
local int i;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!pendingConfigSavedPublic.initialized)
|
||||
{
|
||||
pendingConfigSavedPublic.initialized = true;
|
||||
pendingConfigSavedPublic.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Active config for feature {$TextEmphasis `%1`} was"
|
||||
@ "{$TextNeutral modified}");
|
||||
pendingConfigSavedPublic.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextNeutral modified} active config for feature"
|
||||
@ "{$TextEmphasis `%1`}");
|
||||
}
|
||||
templates = MakeArray(pendingConfigSavedPublic);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().Arg(featureName);
|
||||
}
|
||||
MakeAnnouncement(pendingConfigSavedPublic);
|
||||
}
|
||||
|
||||
public final function AnnouncePrivatePendingConfigSaved(BaseText featureName, BaseText configName)
|
||||
{
|
||||
if (!pendingConfigSavedPrivate.initialized)
|
||||
{
|
||||
pendingConfigSavedPrivate.initialized = true;
|
||||
pendingConfigSavedPrivate.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Config \"%2\" for feature {$TextEmphasis `%1`} was"
|
||||
@ "{$TextNeutral modified}");
|
||||
}
|
||||
pendingConfigSavedPrivate.toSelfReport
|
||||
.Reset()
|
||||
.Arg(featureName)
|
||||
.Arg(configName);
|
||||
MakeAnnouncement(pendingConfigSavedPrivate);
|
||||
}
|
||||
|
||||
public final function AnnounceCurrentConfig(
|
||||
BaseText featureName,
|
||||
BaseText config)
|
||||
{
|
||||
if (!showCurrentConfig.initialized)
|
||||
{
|
||||
showCurrentConfig.initialized = true;
|
||||
showCurrentConfig.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Current config \"%2\" for feature {$TextEmphasis `%1`}:");
|
||||
}
|
||||
showCurrentConfig.toSelfReport
|
||||
.Reset()
|
||||
.Arg(featureName)
|
||||
.Arg(config);
|
||||
MakeAnnouncement(showCurrentConfig);
|
||||
}
|
||||
|
||||
public final function AnnouncePendingConfig(
|
||||
BaseText featureName,
|
||||
BaseText config)
|
||||
{
|
||||
if (!showPendingConfig.initialized)
|
||||
{
|
||||
showPendingConfig.initialized = true;
|
||||
showPendingConfig.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Pending config \"%2\" for feature {$TextEmphasis `%1`}:");
|
||||
}
|
||||
showPendingConfig.toSelfReport
|
||||
.Reset()
|
||||
.Arg(featureName)
|
||||
.Arg(config);
|
||||
MakeAnnouncement(showPendingConfig);
|
||||
}
|
||||
|
||||
public final function AnnounceConfigCreated(
|
||||
BaseText featureName,
|
||||
BaseText config)
|
||||
{
|
||||
if (!configCreated.initialized)
|
||||
{
|
||||
configCreated.initialized = true;
|
||||
configCreated.toSelfReport = _.text.MakeTemplate_S(
|
||||
"{$TextPositive Created config} \"%2\" for feature"
|
||||
@ "{$TextEmphasis `%1`}");
|
||||
}
|
||||
configCreated.toSelfReport
|
||||
.Reset()
|
||||
.Arg(featureName)
|
||||
.Arg(config);
|
||||
MakeAnnouncement(configCreated);
|
||||
}
|
||||
|
||||
public final function AnnounceConfigRemoved(
|
||||
BaseText featureName,
|
||||
BaseText config)
|
||||
{
|
||||
if (!configRemoved.initialized)
|
||||
{
|
||||
configRemoved.initialized = true;
|
||||
configRemoved.toSelfReport = _.text.MakeTemplate_S(
|
||||
"{$TextNegative Removed config} \"%2\" for feature"
|
||||
@ "{$TextEmphasis `%1`}");
|
||||
}
|
||||
configRemoved.toSelfReport
|
||||
.Reset()
|
||||
.Arg(featureName)
|
||||
.Arg(config);
|
||||
MakeAnnouncement(configRemoved);
|
||||
}
|
||||
|
||||
public final function AnnounceConfigEdited(
|
||||
BaseText featureName,
|
||||
BaseText config,
|
||||
BaseText pathToValue,
|
||||
BaseText oldValue,
|
||||
BaseText newValue)
|
||||
{
|
||||
if (!configEdited.initialized)
|
||||
{
|
||||
configEdited.initialized = true;
|
||||
configEdited.toSelfReport = _.text.MakeTemplate_S(
|
||||
"{$TextNeutral Edited config} \"%2\" for feature"
|
||||
@ "{$TextEmphasis `%1`} by replacing old value %4 at \"%3\""
|
||||
@ "with new value %5");
|
||||
}
|
||||
configEdited.toSelfReport
|
||||
.Reset()
|
||||
.Arg(featureName)
|
||||
.Arg(config)
|
||||
.Arg(pathToValue)
|
||||
.Arg(oldValue)
|
||||
.Arg(newValue);
|
||||
MakeAnnouncement(configEdited);
|
||||
}
|
||||
|
||||
public final function AnnounceConfigNewValue(
|
||||
BaseText featureName,
|
||||
BaseText config,
|
||||
BaseText pathToValue,
|
||||
BaseText newValue)
|
||||
{
|
||||
if (!configEditedNew.initialized)
|
||||
{
|
||||
configEditedNew.initialized = true;
|
||||
configEditedNew.toSelfReport = _.text.MakeTemplate_S(
|
||||
"{$TextNeutral Edited config} \"%2\" for feature"
|
||||
@ "{$TextEmphasis `%1`} by adding value %4 at \"%3\"");
|
||||
}
|
||||
configEditedNew.toSelfReport
|
||||
.Reset()
|
||||
.Arg(featureName)
|
||||
.Arg(config)
|
||||
.Arg(pathToValue)
|
||||
.Arg(newValue);
|
||||
MakeAnnouncement(configEditedNew);
|
||||
}
|
||||
|
||||
public final function AnnounceAutoEnabledConfig(
|
||||
BaseText featureName,
|
||||
BaseText config)
|
||||
{
|
||||
if (!autoEnabled.initialized)
|
||||
{
|
||||
autoEnabled.initialized = true;
|
||||
autoEnabled.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Config \"%2\" for feature {$TextEmphasis `%1`} will now be"
|
||||
@ "{$TextPositive auto-enabled}!");
|
||||
}
|
||||
autoEnabled.toSelfReport
|
||||
.Reset()
|
||||
.Arg(featureName)
|
||||
.Arg(config);
|
||||
MakeAnnouncement(autoEnabled);
|
||||
}
|
||||
|
||||
public final function AnnounceRemovedAutoEnabledConfig(
|
||||
BaseText featureName)
|
||||
{
|
||||
if (!removedAutoEnabled.initialized)
|
||||
{
|
||||
removedAutoEnabled.initialized = true;
|
||||
removedAutoEnabled.toSelfReport = _.text.MakeTemplate_S(
|
||||
"No config for feature {$TextEmphasis `%1`} will now be"
|
||||
@ "{$TextPositive auto-enabled}!");
|
||||
}
|
||||
removedAutoEnabled.toSelfReport
|
||||
.Reset()
|
||||
.Arg(featureName);
|
||||
MakeAnnouncement(removedAutoEnabled);
|
||||
}
|
||||
|
||||
public final function AnnounceFailedAlreadyNoAutoEnabled(
|
||||
BaseText featureName)
|
||||
{
|
||||
if (!failedAlreadyNoAutoEnabled.initialized)
|
||||
{
|
||||
failedAlreadyNoAutoEnabled.initialized = true;
|
||||
failedAlreadyNoAutoEnabled.toSelfReport = _.text.MakeTemplate_S(
|
||||
"{$TextFailure Cannot remove} auto-enabled config status for"
|
||||
@ "feature {$TextEmphasis `%1`}: it already has"
|
||||
@ "{$TextNeutral no auto-enabled config}!");
|
||||
}
|
||||
failedAlreadyNoAutoEnabled.toSelfReport
|
||||
.Reset()
|
||||
.Arg(featureName);
|
||||
MakeAnnouncement(failedAlreadyNoAutoEnabled);
|
||||
}
|
||||
|
||||
public final function AnnounceFailedAlreadySameAutoEnabled(
|
||||
BaseText featureName,
|
||||
BaseText config)
|
||||
{
|
||||
if (!failedAlreadySameAutoEnabled.initialized)
|
||||
{
|
||||
failedAlreadySameAutoEnabled.initialized = true;
|
||||
failedAlreadySameAutoEnabled.toSelfReport = _.text.MakeTemplate_S(
|
||||
"{$TextFailure Cannot make} config \"%2\" auto-enabled for feature"
|
||||
@ "{$TextEmphasis `%1`}: it already"
|
||||
@ "{$TextNeutral is auto-enabled}!");
|
||||
}
|
||||
failedAlreadySameAutoEnabled.toSelfReport
|
||||
.Reset()
|
||||
.Arg(featureName)
|
||||
.Arg(config);
|
||||
MakeAnnouncement(failedAlreadySameAutoEnabled);
|
||||
}
|
||||
|
||||
public final function AnnounceFailedConfigAlreadyExists(
|
||||
BaseText featureName,
|
||||
BaseText config)
|
||||
{
|
||||
if (!failedConfigAlreadyExists.initialized)
|
||||
{
|
||||
failedConfigAlreadyExists.initialized = true;
|
||||
failedConfigAlreadyExists.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Config \"%2\" for feature {$TextEmphasis `%1`}"
|
||||
@ "{$TextFailure already exists}");
|
||||
}
|
||||
failedConfigAlreadyExists.toSelfReport
|
||||
.Reset()
|
||||
.Arg(featureName)
|
||||
.Arg(config);
|
||||
MakeAnnouncement(failedConfigAlreadyExists);
|
||||
}
|
||||
|
||||
public final function AnnounceFailedConfigDoesNotExist(
|
||||
BaseText featureName,
|
||||
BaseText config)
|
||||
{
|
||||
if (!failedConfigDoesNotExists.initialized)
|
||||
{
|
||||
failedConfigDoesNotExists.initialized = true;
|
||||
failedConfigDoesNotExists.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Config \"%2\" for feature {$TextEmphasis `%1`}"
|
||||
@ "{$TextFailure doesn't exist}");
|
||||
}
|
||||
failedConfigDoesNotExists.toSelfReport
|
||||
.Reset()
|
||||
.Arg(featureName)
|
||||
.Arg(config);
|
||||
MakeAnnouncement(failedConfigDoesNotExists);
|
||||
}
|
||||
|
||||
public final function AnnounceFailedToLoadFeatureClass(BaseText failedClassName)
|
||||
{
|
||||
if (!failedToLoadFeatureClass.initialized)
|
||||
{
|
||||
failedToLoadFeatureClass.initialized = true;
|
||||
failedToLoadFeatureClass.toSelfReport = _.text.MakeTemplate_S(
|
||||
"{$TextFailure Failed} to load feature class {$TextEmphasis `%1`}");
|
||||
}
|
||||
failedToLoadFeatureClass.toSelfReport.Reset().Arg(failedClassName);
|
||||
MakeAnnouncement(failedToLoadFeatureClass);
|
||||
}
|
||||
|
||||
public final function AnnounceFailedNoConfigProvided(
|
||||
BaseText featureName)
|
||||
{
|
||||
if (!failedNoConfigProvided.initialized)
|
||||
{
|
||||
failedNoConfigProvided.initialized = true;
|
||||
failedNoConfigProvided.toSelfReport = _.text.MakeTemplate_S(
|
||||
"{$TextFailure No config specified} and {$TextFailure no"
|
||||
@ "auto-enabled config} exists for feature {$TextEmphasis `%1`}");
|
||||
}
|
||||
failedNoConfigProvided.toSelfReport.Reset().Arg(featureName);
|
||||
MakeAnnouncement(failedNoConfigProvided);
|
||||
}
|
||||
|
||||
public final function AnnounceFailedConfigMissing(BaseText config)
|
||||
{
|
||||
if (!failedConfigMissing.initialized)
|
||||
{
|
||||
failedConfigMissing.initialized = true;
|
||||
failedConfigMissing.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Specified config \"%1\" {$TextFailure doesn't exist}");
|
||||
}
|
||||
failedConfigMissing.toSelfReport.Reset().Arg(config);
|
||||
MakeAnnouncement(failedConfigMissing);
|
||||
}
|
||||
|
||||
public final function AnnounceFailedPendingConfigMissing(BaseText config)
|
||||
{
|
||||
if (!failedPendingConfigMissing.initialized)
|
||||
{
|
||||
failedPendingConfigMissing.initialized = true;
|
||||
failedPendingConfigMissing.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Specified config \"%1\" {$TextFailure doesn't have} any pending"
|
||||
@ "changes");
|
||||
}
|
||||
failedPendingConfigMissing.toSelfReport.Reset().Arg(config);
|
||||
MakeAnnouncement(failedPendingConfigMissing);
|
||||
}
|
||||
|
||||
public final function AnnounceFailedCannotEnableFeature(
|
||||
BaseText featureName,
|
||||
BaseText config)
|
||||
{
|
||||
if (!failedCannotEnableFeature.initialized)
|
||||
{
|
||||
failedCannotEnableFeature.initialized = true;
|
||||
failedCannotEnableFeature.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Something went {$TextFailure wrong}, {$TextFailure failed} to"
|
||||
@ "enable feature {$TextEmphasis `%1`} with config \"%2\"");
|
||||
}
|
||||
failedCannotEnableFeature.toSelfReport
|
||||
.Reset()
|
||||
.Arg(featureName)
|
||||
.Arg(config);
|
||||
MakeAnnouncement(failedCannotEnableFeature);
|
||||
}
|
||||
|
||||
public final function AnnounceFailedNoConfigClass(
|
||||
BaseText featureName)
|
||||
{
|
||||
if (!failedNoConfigClass.initialized)
|
||||
{
|
||||
failedNoConfigClass.initialized = true;
|
||||
failedNoConfigClass.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Feature {$TextEmphasis `%1`} {$TextFailure does not have} config"
|
||||
@ "class! This is most likely caused by its faulty"
|
||||
@ "implementation");
|
||||
}
|
||||
failedNoConfigClass.toSelfReport.Reset().Arg(featureName);
|
||||
MakeAnnouncement(failedNoConfigClass);
|
||||
}
|
||||
|
||||
public final function AnnounceFailedBadConfigName(BaseText configName)
|
||||
{
|
||||
if (!failedBadConfigName.initialized)
|
||||
{
|
||||
failedBadConfigName.initialized = true;
|
||||
failedBadConfigName.toSelfReport = _.text.MakeTemplate_S(
|
||||
"{$TextFailure Cannot create} a config with invalid name \"%1\"");
|
||||
}
|
||||
failedBadConfigName.toSelfReport.Reset().Arg(configName);
|
||||
MakeAnnouncement(failedBadConfigName);
|
||||
}
|
||||
|
||||
public final function AnnounceFailedAlreadyDisabled(
|
||||
BaseText featureName)
|
||||
{
|
||||
if (!failedAlreadyDisabled.initialized)
|
||||
{
|
||||
failedAlreadyDisabled.initialized = true;
|
||||
failedAlreadyDisabled.toSelfReport = _.text.MakeTemplate_S(
|
||||
"{$TextFailure Cannot disable} feature {$TextEmphasis `%1`}: it is"
|
||||
@ "already {$TextNegative disabled}");
|
||||
}
|
||||
failedAlreadyDisabled.toSelfReport.Reset().Arg(featureName);
|
||||
MakeAnnouncement(failedAlreadyDisabled);
|
||||
}
|
||||
|
||||
public final function AnnounceFailedAlreadyEnabled(
|
||||
BaseText featureName,
|
||||
BaseText config)
|
||||
{
|
||||
if (!failedAlreadyEnabled.initialized)
|
||||
{
|
||||
failedAlreadyEnabled.initialized = true;
|
||||
failedAlreadyEnabled.toSelfReport = _.text.MakeTemplate_S(
|
||||
"{$TextFailure Cannot enable} feature {$TextEmphasis `%1`}: it is"
|
||||
@ "already {$TextPositive enabled} with specified config \"%2\"");
|
||||
}
|
||||
failedAlreadyEnabled.toSelfReport
|
||||
.Reset()
|
||||
.Arg(featureName)
|
||||
.Arg(config);
|
||||
MakeAnnouncement(failedAlreadyEnabled);
|
||||
}
|
||||
|
||||
public final function AnnounceFailedNoDataForConfig(
|
||||
BaseText featureName,
|
||||
BaseText config)
|
||||
{
|
||||
if (!failedNoDataForConfig.initialized)
|
||||
{
|
||||
failedNoDataForConfig.initialized = true;
|
||||
failedNoDataForConfig.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Feature {$TextEmphasis `%1`} is {$TextFailure missing data} for"
|
||||
@ "config \"%2\"");
|
||||
}
|
||||
failedNoDataForConfig.toSelfReport
|
||||
.Reset()
|
||||
.Arg(featureName)
|
||||
.Arg(config);
|
||||
MakeAnnouncement(failedNoDataForConfig);
|
||||
}
|
||||
|
||||
public final function AnnounceFailedExpectedObject()
|
||||
{
|
||||
if (!failedExpectedObject.initialized)
|
||||
{
|
||||
failedExpectedObject.initialized = true;
|
||||
failedExpectedObject.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Value change {$TextFailure failed}, because when changing"
|
||||
@ "the value of the whole config, a JSON object must be provided");
|
||||
}
|
||||
MakeAnnouncement(failedExpectedObject);
|
||||
}
|
||||
|
||||
public final function AnnounceFailedBadPointer(
|
||||
BaseText featureName,
|
||||
BaseText config,
|
||||
BaseText pointer)
|
||||
{
|
||||
if (!failedBadPointer.initialized)
|
||||
{
|
||||
failedBadPointer.initialized = true;
|
||||
failedBadPointer.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Provided JSON pointer \"%3\" is {$TextFailure invalid} for config"
|
||||
@ "\"%2\" of feature {$TextEmphasis `%1`}");
|
||||
}
|
||||
failedBadPointer.toSelfReport
|
||||
.Reset()
|
||||
.Arg(featureName)
|
||||
.Arg(config)
|
||||
.Arg(pointer);
|
||||
MakeAnnouncement(failedBadPointer);
|
||||
}
|
||||
|
||||
defaultproperties
|
||||
{
|
||||
}
|
||||
224
kf_sources/Futility/Classes/ACommandGod.uc
Normal file
224
kf_sources/Futility/Classes/ACommandGod.uc
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
/**
|
||||
* Command for making player immortal.
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class ACommandGod extends Command;
|
||||
|
||||
struct GodStatus
|
||||
{
|
||||
// Player to whom we grant godhood
|
||||
var EPlayer target;
|
||||
// Is `target` only a demigod (can get damaged, but not die)?
|
||||
var bool demigod;
|
||||
// Should `target` be unaffected by attacks momentum?
|
||||
var bool unmovable;
|
||||
};
|
||||
|
||||
var private bool connectedToSignal;
|
||||
var private array<GodStatus> godhoodList;
|
||||
|
||||
var private ACommandGod_Announcer announcer;
|
||||
|
||||
var private const int TDAMAGE, TMOMENTUM;
|
||||
|
||||
protected function Finalizer()
|
||||
{
|
||||
connectedToSignal = false;
|
||||
_server.kf.health.OnDamage(self).Disconnect();
|
||||
_.memory.Free(announcer);
|
||||
super.Finalizer();
|
||||
}
|
||||
|
||||
protected function BuildData(CommandDataBuilder builder)
|
||||
{
|
||||
builder.Group(P("gameplay"));
|
||||
builder.Summary(P("Command for making player immortal."));
|
||||
builder.RequireTarget();
|
||||
builder.Describe(P("Gives targeted players god status, making them"
|
||||
@ "invincible."));
|
||||
builder.SubCommand(P("list"));
|
||||
builder.Describe(P("Reports godhood status of targeted players."));
|
||||
builder.SubCommand(P("strip"));
|
||||
builder.Describe(P("Strips targeted players from the godhood status."));
|
||||
builder.Option(P("demi"));
|
||||
builder.Describe(P("This flag makes targeted players \"demigods\" instead -"
|
||||
@ "they still cannot die, but they can take any non-lethal"
|
||||
@ "damage."));
|
||||
builder.Option(P("unmovable"));
|
||||
builder.Describe(P("This flag also prevents targeted players from being"
|
||||
@ "affected by the momentum trasnferred from damaging attacks."));
|
||||
announcer = ACommandGod_Announcer(
|
||||
_.memory.Allocate(class'ACommandGod_Announcer'));
|
||||
}
|
||||
|
||||
protected function ExecutedFor(
|
||||
EPlayer target,
|
||||
CallData arguments,
|
||||
EPlayer instigator,
|
||||
CommandPermissions permissions
|
||||
) {
|
||||
local GodStatus newGodStatus;
|
||||
|
||||
announcer.Setup(target, instigator, othersConsole);
|
||||
if (arguments.subCommandName.IsEmpty())
|
||||
{
|
||||
newGodStatus.target = target;
|
||||
newGodStatus.demigod = arguments.options.HasKey(P("demi"));
|
||||
newGodStatus.unmovable = arguments.options.HasKey(P("unmovable"));
|
||||
MakeGod(target, newGodStatus);
|
||||
}
|
||||
else if (arguments.subCommandName.Compare(P("list"))) {
|
||||
announcer.AnnounceGodStatus(BorrowGodStatus(target));
|
||||
}
|
||||
else if (arguments.subCommandName.Compare(P("strip"))) {
|
||||
RemoveGod(target);
|
||||
}
|
||||
}
|
||||
|
||||
private function ProtectDivines(
|
||||
EPawn target,
|
||||
EPawn instigator,
|
||||
HashTable damageData)
|
||||
{
|
||||
local int damage;
|
||||
local EPlayer targetedPlayer;
|
||||
local GodStatus targetDivinity;
|
||||
|
||||
targetedPlayer = target.GetPlayer();
|
||||
targetDivinity = BorrowGodStatus(targetedPlayer);
|
||||
_.memory.Free(targetedPlayer);
|
||||
if (targetDivinity.target == none) {
|
||||
return;
|
||||
}
|
||||
if (targetDivinity.unmovable) {
|
||||
damageData.SetVector(T(TMOMENTUM), Vect(0.0f, 0.0f, 0.0f));
|
||||
}
|
||||
if (targetDivinity.demiGod)
|
||||
{
|
||||
damage = damageData.GetInt(T(TDAMAGE));
|
||||
damage = Min(damage, target.GetHealth() - 1);
|
||||
damageData.SetInt(T(TDAMAGE), damage);
|
||||
}
|
||||
else {
|
||||
damageData.SetInt(T(TDAMAGE), 0);
|
||||
}
|
||||
}
|
||||
|
||||
private final function MakeGod(
|
||||
EPlayer target,
|
||||
GodStatus newGodStatus)
|
||||
{
|
||||
local int godIndex;
|
||||
local bool wasGod;
|
||||
local GodStatus oldGodStatus;
|
||||
|
||||
if (target == none) {
|
||||
return;
|
||||
}
|
||||
for (godIndex = 0; godIndex < godhoodList.length; godIndex += 1)
|
||||
{
|
||||
if (target.SameAs(godhoodList[godIndex].target))
|
||||
{
|
||||
wasGod = true;
|
||||
oldGodStatus = godhoodList[godIndex];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (wasGod)
|
||||
{
|
||||
if ( newGodStatus.demiGod == oldGodStatus.demiGod
|
||||
&& newGodStatus.unmovable == oldGodStatus.unmovable)
|
||||
{
|
||||
announcer.AnnounceSameGod(newGodStatus);
|
||||
}
|
||||
else
|
||||
{
|
||||
announcer.AnnounceChangedGod(oldGodStatus, newGodStatus);
|
||||
godhoodList[godIndex].target.FreeSelf();
|
||||
newGodStatus.target.NewRef();
|
||||
godhoodList[godIndex] = newGodStatus;
|
||||
}
|
||||
}
|
||||
else {
|
||||
announcer.AnnounceNewGod(newGodStatus);
|
||||
newGodStatus.target.NewRef();
|
||||
godhoodList[godhoodList.length] = newGodStatus;
|
||||
}
|
||||
UpdateHealthSignalConnection();
|
||||
}
|
||||
|
||||
private final function RemoveGod(EPlayer target)
|
||||
{
|
||||
local int i;
|
||||
|
||||
if (target == none) {
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < godhoodList.length; i += 1)
|
||||
{
|
||||
if (target.SameAs(godhoodList[i].target))
|
||||
{
|
||||
announcer.AnnounceRemoveGod(godhoodList[i]);
|
||||
godhoodList[i].target.FreeSelf();
|
||||
godhoodList.Remove(i, 1);
|
||||
UpdateHealthSignalConnection();
|
||||
return;
|
||||
}
|
||||
}
|
||||
announcer.AnnounceWasNotGod();
|
||||
}
|
||||
|
||||
private final function GodStatus BorrowGodStatus(EPlayer target)
|
||||
{
|
||||
local int i;
|
||||
local GodStatus emptyStatus;
|
||||
|
||||
if (target == none) {
|
||||
return emptyStatus;
|
||||
}
|
||||
for (i = 0; i < godhoodList.length; i += 1)
|
||||
{
|
||||
if (target.SameAs(godhoodList[i].target)) {
|
||||
return godhoodList[i];
|
||||
}
|
||||
}
|
||||
return emptyStatus;
|
||||
}
|
||||
|
||||
private final function UpdateHealthSignalConnection()
|
||||
{
|
||||
if (connectedToSignal && godhoodList.length <= 0)
|
||||
{
|
||||
_server.kf.health.OnDamage(self).Disconnect();
|
||||
connectedToSignal = false;
|
||||
}
|
||||
if (!connectedToSignal && godhoodList.length > 0)
|
||||
{
|
||||
connectedToSignal = true;
|
||||
_server.kf.health.OnDamage(self).connect = ProtectDivines;
|
||||
}
|
||||
}
|
||||
|
||||
defaultproperties
|
||||
{
|
||||
preferredName = "god"
|
||||
TDAMAGE = 0
|
||||
stringConstants(0) = "damage"
|
||||
TMOMENTUM = 1
|
||||
stringConstants(1) = "momentum"
|
||||
}
|
||||
225
kf_sources/Futility/Classes/ACommandGod_Announcer.uc
Normal file
225
kf_sources/Futility/Classes/ACommandGod_Announcer.uc
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
/**
|
||||
* Announcer for `ACommandGod`.
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class ACommandGod_Announcer extends CommandAnnouncer
|
||||
dependson(ACommandGod);
|
||||
|
||||
var private AnnouncementVariations godStatus, newGod, removeGod, sameGod;
|
||||
var private AnnouncementVariations changedGod, wasNotGod;
|
||||
|
||||
protected function Finalizer()
|
||||
{
|
||||
FreeVariations(godStatus);
|
||||
FreeVariations(newGod);
|
||||
FreeVariations(removeGod);
|
||||
FreeVariations(sameGod);
|
||||
FreeVariations(changedGod);
|
||||
FreeVariations(wasNotGod);
|
||||
super.Finalizer();
|
||||
}
|
||||
|
||||
public final function AnnounceGodStatus(ACommandGod.GodStatus status)
|
||||
{
|
||||
local int i;
|
||||
local MutableText statusAsText;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!godStatus.initialized)
|
||||
{
|
||||
godStatus.initialized = true;
|
||||
godStatus.toSelfReport = _.text.MakeTemplate_S(
|
||||
"You're %1");
|
||||
godStatus.toOtherReport = _.text.MakeTemplate_S(
|
||||
"%%target%% is %1");
|
||||
}
|
||||
statusAsText = DisplayStatus(status);
|
||||
templates = MakeArray(godStatus);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().Arg(statusAsText);
|
||||
}
|
||||
_.memory.Free(statusAsText);
|
||||
MakeAnnouncement(godStatus);
|
||||
}
|
||||
|
||||
public final function AnnounceNewGod(ACommandGod.GodStatus status)
|
||||
{
|
||||
local int i;
|
||||
local MutableText statusAsText;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!newGod.initialized)
|
||||
{
|
||||
newGod.initialized = true;
|
||||
newGod.toSelfReport = _.text.MakeTemplate_S(
|
||||
"You {$TextPositive made} yourself %1");
|
||||
newGod.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextPositive made} themselves %1");
|
||||
newGod.toOtherReport = _.text.MakeTemplate_S(
|
||||
"You {$TextPositive made} %%target%% %1");
|
||||
newGod.toOtherPrivate = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextPositive made} you %1");
|
||||
newGod.toOtherPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextPositive made} %%target%% %1");
|
||||
}
|
||||
statusAsText = DisplayStatus(status);
|
||||
templates = MakeArray(newGod);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().Arg(statusAsText);
|
||||
}
|
||||
_.memory.Free(statusAsText);
|
||||
MakeAnnouncement(newGod);
|
||||
}
|
||||
|
||||
public final function AnnounceRemoveGod(ACommandGod.GodStatus status)
|
||||
{
|
||||
local int i;
|
||||
local MutableText statusAsText;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!removeGod.initialized)
|
||||
{
|
||||
removeGod.initialized = true;
|
||||
removeGod.toSelfReport = _.text.MakeTemplate_S(
|
||||
"You, %1, {$TextNegative became} a mere {$TextNegative mortal}");
|
||||
removeGod.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%1 %%instigator%% {$TextNegative made} themselves a mere"
|
||||
@ "{$TextNegative mortal}");
|
||||
removeGod.toOtherReport = _.text.MakeTemplate_S(
|
||||
"%1 %%target%% was {$TextNegative made} a mere"
|
||||
@ "{$TextNegative mortal} by you");
|
||||
removeGod.toOtherPrivate = _.text.MakeTemplate_S(
|
||||
"You, %1, was {$TextNegative made} a mere {$TextNegative mortal}"
|
||||
@ "by %%instigator%%");
|
||||
removeGod.toOtherPublic = _.text.MakeTemplate_S(
|
||||
"%1 %%target%% was {$TextNegative made} a mere"
|
||||
@ "{$TextNegative mortal} by %%instigator%%");
|
||||
}
|
||||
statusAsText = DisplayStatus(status);
|
||||
templates = MakeArray(removeGod);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().Arg(statusAsText);
|
||||
}
|
||||
_.memory.Free(statusAsText);
|
||||
MakeAnnouncement(removeGod);
|
||||
}
|
||||
|
||||
public final function AnnounceSameGod(ACommandGod.GodStatus status)
|
||||
{
|
||||
local int i;
|
||||
local MutableText statusAsText;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!sameGod.initialized)
|
||||
{
|
||||
sameGod.initialized = true;
|
||||
sameGod.toSelfReport = _.text.MakeTemplate_S(
|
||||
"You are already %1");
|
||||
sameGod.toOtherReport = _.text.MakeTemplate_S(
|
||||
"%%target%% is already %1");
|
||||
}
|
||||
statusAsText = DisplayStatus(status);
|
||||
templates = MakeArray(sameGod);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().Arg(statusAsText);
|
||||
}
|
||||
_.memory.Free(statusAsText);
|
||||
MakeAnnouncement(sameGod);
|
||||
}
|
||||
|
||||
public final function AnnounceChangedGod(
|
||||
ACommandGod.GodStatus oldStatus,
|
||||
ACommandGod.GodStatus newStatus)
|
||||
{
|
||||
local int i;
|
||||
local MutableText oldStatusAsText, newStatusAsText;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!changedGod.initialized)
|
||||
{
|
||||
changedGod.initialized = true;
|
||||
changedGod.toSelfReport = _.text.MakeTemplate_S(
|
||||
"You, %1, {$TextPositive made} yourself %2");
|
||||
changedGod.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%1 %%instigator%% {$TextPositive made} themselves %2");
|
||||
changedGod.toOtherReport = _.text.MakeTemplate_S(
|
||||
"You {$TextPositive made} %1 %%target%% into %1");
|
||||
changedGod.toOtherPrivate = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextPositive made} you, %1, into %2");
|
||||
changedGod.toOtherPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextPositive made} %1 %%target%% into %2");
|
||||
}
|
||||
oldStatusAsText = DisplayStatus(oldStatus);
|
||||
newStatusAsText = DisplayStatus(newStatus);
|
||||
templates = MakeArray(changedGod);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().Arg(oldStatusAsText).Arg(newStatusAsText);
|
||||
}
|
||||
_.memory.Free(oldStatusAsText);
|
||||
_.memory.Free(newStatusAsText);
|
||||
MakeAnnouncement(changedGod);
|
||||
}
|
||||
|
||||
public final function AnnounceWasNotGod()
|
||||
{
|
||||
local int i;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!sameGod.initialized)
|
||||
{
|
||||
sameGod.initialized = true;
|
||||
sameGod.toSelfReport = _.text.MakeTemplate_S(
|
||||
"You are already a mere {$TextNegative mortal}");
|
||||
sameGod.toOtherReport = _.text.MakeTemplate_S(
|
||||
"%%target%% is already a mere {$TextNegative mortal}");
|
||||
}
|
||||
templates = MakeArray(sameGod);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset();
|
||||
}
|
||||
MakeAnnouncement(sameGod);
|
||||
}
|
||||
|
||||
private final function MutableText DisplayStatus(ACommandGod.GodStatus status)
|
||||
{
|
||||
local MutableText builder;
|
||||
|
||||
builder = _.text.Empty();
|
||||
if (status.target == none)
|
||||
{
|
||||
builder.Append(F("a mere {$TextNegative mortal}"));
|
||||
return builder;
|
||||
}
|
||||
if (status.unmovable) {
|
||||
builder.Append(F("an {$TextPositive unmovable}, "));
|
||||
}
|
||||
else {
|
||||
builder.Append(F("a {$TextNeutral simple}, "));
|
||||
}
|
||||
if (status.demigod) {
|
||||
builder.Append(F("immortal {$TextNeutral demigod}"));
|
||||
}
|
||||
else {
|
||||
builder.Append(F("invincible {$TextPositive god}"));
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
defaultproperties
|
||||
{
|
||||
}
|
||||
319
kf_sources/Futility/Classes/ACommandInventory.uc
Normal file
319
kf_sources/Futility/Classes/ACommandInventory.uc
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
/**
|
||||
* Command for managing (displaying + adding and removing to/items from it)
|
||||
* player's inventory.
|
||||
* Copyright 2021 - 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class ACommandInventory extends Command;
|
||||
|
||||
// Load user-specified options into the boolean flags during'
|
||||
// `ExecutedFor()` and use them in auxiliary methods.
|
||||
// This is might be a questionable way of doing things, but it allows to
|
||||
// avoid passing flags in copious amounts to auxiliary methods and
|
||||
// does not overcomplicate logic
|
||||
var private bool flagAll, flagForce, flagAmmo, flagKeep;
|
||||
var private bool flagEquip, flagHidden, flagGroups;
|
||||
|
||||
var protected const int TINVENTORY, TADD, TREMOVE, TITEMS, TEQUIP, TALL, TKEEP;
|
||||
var protected const int THIDDEN, TFORCE, TAMMO, TLIST, TLISTS_NAMES, TSET;
|
||||
var protected const int TLISTS_SKIPPED;
|
||||
|
||||
protected function BuildData(CommandDataBuilder builder)
|
||||
{
|
||||
builder.Group(P("gameplay"));
|
||||
builder.Summary(P("Manages player's inventory."));
|
||||
builder.Describe(P("Command for displaying and editing players' inventories."
|
||||
@ "If called without specifying subcommand - simply displays"
|
||||
@ "targeted player's inventory."));
|
||||
builder.RequireTarget();
|
||||
builder.SubCommand(T(TADD));
|
||||
builder.OptionalParams();
|
||||
builder.ParamTextList(T(TITEMS));
|
||||
builder.Describe(P("This command adds items (based on listed templates) to"
|
||||
@ "the targeted player's inventory."
|
||||
@ "Instead of templates item aliases can be specified."));
|
||||
builder.SubCommand(T(TREMOVE));
|
||||
builder.OptionalParams();
|
||||
builder.ParamTextList(T(TITEMS));
|
||||
builder.Describe(P("This command removes items (based on listed templates)"
|
||||
@ "from the targeted player's inventory."
|
||||
@ "Instead of templates item aliases can be specified."));
|
||||
builder.SubCommand(T(TSET));
|
||||
builder.OptionalParams();
|
||||
builder.ParamTextList(T(TITEMS));
|
||||
builder.Describe(P("This command acts like combination of two commands -"
|
||||
@ "first removing all items from the player's current inventory and"
|
||||
@ "then adding specified items. first clears inventory"
|
||||
@ "(based on specified options) and then "));
|
||||
builder.Option(T(TEQUIP));
|
||||
builder.Describe(F("Affect items currently equipped by the targeted player."
|
||||
@ "Releveant for a {$TextEmphasis remove} subcommand."));
|
||||
builder.Option(T(TLIST));
|
||||
builder.Describe(P("Include weapons from specified group into the list."));
|
||||
builder.ParamTextList(T(TLISTS_NAMES));
|
||||
builder.Option(T(TAMMO));
|
||||
builder.Describe(P("When adding weapons - signals that their"
|
||||
@ "ammo / charge / whatever has to be filled after addition."));
|
||||
builder.Option(T(TKEEP));
|
||||
builder.Describe(F("Removing items by default means simply destroying them."
|
||||
@ "This flag makes command to try and keep them in some form."
|
||||
@ "Success for all items is not guaranteed."));
|
||||
builder.Option(T(THIDDEN));
|
||||
builder.Describe(F("Some of the items in the inventory are"
|
||||
@ "{$TextEmphasis hidden} and are not supposed to be seem by"
|
||||
@ "the player. To avoid weird behavior, {$TextEmphasis inventory}"
|
||||
@ "command by default ignores them when affecting groups of items"
|
||||
@ "(like when removing all items) unless they're directly"
|
||||
@ "specified. This flag tells it to also affect hidden items."));
|
||||
builder.Option(T(TFORCE));
|
||||
builder.Describe(P("Sometimes adding and removing items is impossible due to"
|
||||
@ "the limitations imposed by the game. This option allows to"
|
||||
@ "ignore some of those limitation."));
|
||||
builder.Option(T(TALL), P("A"));
|
||||
builder.Describe(F("This flag is used when removing items. If user has"
|
||||
@ "specified any weapon templates - it means"
|
||||
@ "\"remove all items with these tempaltes from inventory\","
|
||||
@ "but if user has not specified any templated it simply means"
|
||||
@ "\"remove all items from the inventory\"."));
|
||||
}
|
||||
|
||||
protected function ExecutedFor(
|
||||
EPlayer target,
|
||||
CallData arguments,
|
||||
EPlayer instigator,
|
||||
CommandPermissions permissions
|
||||
) {
|
||||
local InventoryTool tool;
|
||||
local ArrayList itemsArray, specifiedLists;
|
||||
LoadUserFlags(arguments.options);
|
||||
tool = class'InventoryTool'.static.CreateFor(target);
|
||||
if (tool == none) {
|
||||
return;
|
||||
}
|
||||
itemsArray = arguments.parameters.GetArrayList(T(TITEMS));
|
||||
specifiedLists = arguments.options.GetArrayListBy(P("/list/lists names"));
|
||||
if (arguments.subCommandName.IsEmpty()) {
|
||||
tool.ReportInventory(callerConsole, flagHidden);
|
||||
}
|
||||
else if (arguments.subCommandName.Compare(T(TADD))) {
|
||||
SubCommandAdd(tool, itemsArray, specifiedLists);
|
||||
}
|
||||
else if (arguments.subCommandName.Compare(T(TREMOVE))) {
|
||||
SubCommandRemove(tool, itemsArray, specifiedLists);
|
||||
}
|
||||
else if (arguments.subCommandName.Compare(T(TSET)))
|
||||
{
|
||||
tool.RemoveAllItems(flagKeep, flagForce, flagHidden);
|
||||
SubCommandAdd(tool, itemsArray, specifiedLists);
|
||||
}
|
||||
tool.SetupReportInstigator(instigator);
|
||||
if (!instigator.SameAs(target)) {
|
||||
tool.ReportChanges(instigator, targetConsole, IRT_Target);
|
||||
}
|
||||
tool.ReportChanges(instigator, callerConsole, IRT_Instigator);
|
||||
tool.ReportChanges(instigator, othersConsole, IRT_Others);
|
||||
_.memory.Free(tool);
|
||||
_.memory.Free(itemsArray);
|
||||
_.memory.Free(specifiedLists);
|
||||
}
|
||||
|
||||
protected function SubCommandAdd(
|
||||
InventoryTool tool,
|
||||
ArrayList itemsArray,
|
||||
ArrayList specifiedLists)
|
||||
{
|
||||
local int i;
|
||||
local int itemsAmount;
|
||||
local Text nextItem;
|
||||
local array<Text> itemsFromLists;
|
||||
if (tool == none) {
|
||||
return;
|
||||
}
|
||||
if (itemsArray != none) {
|
||||
itemsAmount = itemsArray.GetLength();
|
||||
}
|
||||
// Add items user listed manually
|
||||
// Use `itemsAmount` because `itemsArray` can be `none`
|
||||
for (i = 0; i < itemsAmount; i += 1)
|
||||
{
|
||||
nextItem = itemsArray.GetText(i);
|
||||
tool.AddItem(nextItem, flagForce, flagAmmo);
|
||||
_.memory.Free(nextItem);
|
||||
}
|
||||
// Add items from specified lists
|
||||
itemsFromLists = LoadAllItemsLists(specifiedLists);
|
||||
for (i = 0; i < itemsFromLists.length; i += 1) {
|
||||
tool.AddItem(itemsFromLists[i], flagForce, flagAmmo);
|
||||
}
|
||||
_.memory.FreeMany(itemsFromLists);
|
||||
}
|
||||
|
||||
protected function SubCommandRemove(
|
||||
InventoryTool tool,
|
||||
ArrayList itemsArray,
|
||||
ArrayList specifiedLists)
|
||||
{
|
||||
local int i;
|
||||
local int itemsAmount;
|
||||
local Text nextItem;
|
||||
local array<Text> itemsFromLists;
|
||||
if (tool == none) {
|
||||
return;
|
||||
}
|
||||
if (itemsArray != none) {
|
||||
itemsAmount = itemsArray.GetLength();
|
||||
}
|
||||
// Remove due to "--all" option
|
||||
if (flagAll && itemsAmount <= 0)
|
||||
{
|
||||
tool.RemoveAllItems(flagKeep, flagForce, flagHidden);
|
||||
return;
|
||||
}
|
||||
// Remove due to "--equip" option
|
||||
if (flagEquip) {
|
||||
tool.RemoveEquippedItems(flagKeep, flagForce, flagHidden);
|
||||
}
|
||||
// Remove items user listed manually
|
||||
// Use `itemsAmount` because `itemsArray` can be `none`
|
||||
for (i = 0; i < itemsAmount; i += 1)
|
||||
{
|
||||
nextItem = itemsArray.GetText(i);
|
||||
tool.RemoveItem(nextItem, flagKeep, flagForce, flagAll);
|
||||
_.memory.Free(nextItem);
|
||||
}
|
||||
// Remove items from specified lists
|
||||
itemsFromLists = LoadAllItemsLists(specifiedLists);
|
||||
for (i = 0; i < itemsFromLists.length; i += 1) {
|
||||
tool.RemoveItem(itemsFromLists[i], flagKeep, flagForce, flagAll);
|
||||
}
|
||||
_.memory.FreeMany(itemsFromLists);
|
||||
}
|
||||
|
||||
protected function LoadUserFlags(HashTable options)
|
||||
{
|
||||
if (options == none)
|
||||
{
|
||||
flagAll = false;
|
||||
flagForce = false;
|
||||
flagAmmo = false;
|
||||
flagKeep = false;
|
||||
flagEquip = false;
|
||||
flagHidden = false;
|
||||
flagGroups = false;
|
||||
return;
|
||||
}
|
||||
flagAll = options.HasKey(T(TALL));
|
||||
flagForce = options.HasKey(T(TFORCE));
|
||||
flagAmmo = options.HasKey(T(TAMMO));
|
||||
flagKeep = options.HasKey(T(TKEEP));
|
||||
flagEquip = options.HasKey(T(TEQUIP));
|
||||
flagHidden = options.HasKey(T(THIDDEN));
|
||||
flagGroups = options.HasKey(T(TLIST));
|
||||
}
|
||||
|
||||
protected function array<Text> LoadAllItemsLists(ArrayList specifiedLists)
|
||||
{
|
||||
local int i, j;
|
||||
local Text nextList;
|
||||
local array<Text> result;
|
||||
local array<Text> nextItemBatch;
|
||||
local array<Text> availableLists;
|
||||
local ListBuilder badLists;
|
||||
local MutableText badListsAsText;
|
||||
|
||||
if (specifiedLists == none) {
|
||||
return result;
|
||||
}
|
||||
badLists = ListBuilder(_.memory.Allocate(class'ListBuilder'));
|
||||
callerConsole.Write(T(TLISTS_SKIPPED));
|
||||
availableLists = _server.kf.templates.GetAvailableLists();
|
||||
for (i = 0; i < specifiedLists.GetLength(); i += 1)
|
||||
{
|
||||
nextList = specifiedLists.GetText(i);
|
||||
nextItemBatch = LoadItemsList(nextList, availableLists, badLists);
|
||||
_.memory.Free(nextList);
|
||||
for (j = 0; j < nextItemBatch.length; j += 1) {
|
||||
result[result.length] = nextItemBatch[j];
|
||||
}
|
||||
}
|
||||
badListsAsText = badLists.IntoMutableText();
|
||||
callerConsole.WriteLine(badListsAsText);
|
||||
_.memory.Free(badListsAsText);
|
||||
_.memory.FreeMany(availableLists);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected function array<Text> LoadItemsList(
|
||||
BaseText listName,
|
||||
array<BaseText> availableLists,
|
||||
ListBuilder badLists)
|
||||
{
|
||||
local int i;
|
||||
local array<Text> emptyArray;
|
||||
if (listName == none) {
|
||||
return emptyArray;
|
||||
}
|
||||
// Try exact matching first
|
||||
for (i = 0; i < availableLists.length; i += 1)
|
||||
{
|
||||
if (availableLists[i].Compare(listName, SCASE_INSENSITIVE)) {
|
||||
return _server.kf.templates.GetItemList(availableLists[i]);
|
||||
}
|
||||
}
|
||||
// Prefix matching otherwise
|
||||
for (i = 0; i < availableLists.length; i += 1)
|
||||
{
|
||||
if (availableLists[i].StartsWith(listName, SCASE_INSENSITIVE)) {
|
||||
return _server.kf.templates.GetItemList(availableLists[i]);
|
||||
}
|
||||
}
|
||||
badLists.Item(listName);
|
||||
return emptyArray;
|
||||
}
|
||||
|
||||
defaultproperties
|
||||
{
|
||||
preferredName = "inventory"
|
||||
TINVENTORY = 0
|
||||
stringConstants(0) = "inventory"
|
||||
TADD = 1
|
||||
stringConstants(1) = "add"
|
||||
TREMOVE = 2
|
||||
stringConstants(2) = "remove"
|
||||
TITEMS = 3
|
||||
stringConstants(3) = "items"
|
||||
TEQUIP = 4
|
||||
stringConstants(4) = "equip"
|
||||
TALL = 5
|
||||
stringConstants(5) = "all"
|
||||
TKEEP = 6
|
||||
stringConstants(6) = "keep"
|
||||
THIDDEN = 7
|
||||
stringConstants(7) = "hidden"
|
||||
TFORCE = 8
|
||||
stringConstants(8) = "force"
|
||||
TAMMO = 9
|
||||
stringConstants(9) = "ammo"
|
||||
TLIST = 10
|
||||
stringConstants(10) = "list"
|
||||
TLISTS_NAMES = 11
|
||||
stringConstants(11) = "lists names"
|
||||
TSET = 12
|
||||
stringConstants(12) = "set"
|
||||
TLISTS_SKIPPED = 13
|
||||
stringConstants(13) = "Following lists could not have been found and will be {$TextFailure skipped}:"
|
||||
}
|
||||
133
kf_sources/Futility/Classes/ACommandNick.uc
Normal file
133
kf_sources/Futility/Classes/ACommandNick.uc
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
/**
|
||||
* Command for changing nickname of the player.
|
||||
* Copyright 2021-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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class ACommandNick extends Command;
|
||||
|
||||
var private bool foundErrors;
|
||||
var private MutableText newName;
|
||||
|
||||
var private ACommandNick_Announcer announcer;
|
||||
|
||||
protected function Finalizer()
|
||||
{
|
||||
_.memory.Free(announcer);
|
||||
_.memory.Free(newName);
|
||||
newName = none;
|
||||
super.Finalizer();
|
||||
}
|
||||
|
||||
protected function BuildData(CommandDataBuilder builder)
|
||||
{
|
||||
builder.Group(P("gameplay"));
|
||||
builder.Summary(P("Changes nickname."));
|
||||
builder.RequireTarget();
|
||||
builder.ParamRemainder(P("nick"));
|
||||
builder.Describe(P("Changes nickname of targeted players to <nick>."));
|
||||
builder.Option(P("plain"));
|
||||
builder.Describe(P("Take nickname exactly as typed, without attempting to"
|
||||
@ "treat it like formatted string."));
|
||||
builder.Option(P("fix"), P("f"));
|
||||
builder.Describe(P("In case of a nickname with erroroneous formatting or"
|
||||
@ "invalid default color (specified with `--color`),"
|
||||
@ "try to fix/ignore it instead of simply rejecting it."));
|
||||
builder.Option(P("color"));
|
||||
builder.Describe(P("Color to use for the nickname. In case nickname is already"
|
||||
@ "colored, this flag will only affects uncolored parts."));
|
||||
builder.ParamText(P("default_color"));
|
||||
announcer = ACommandNick_Announcer(
|
||||
_.memory.Allocate(class'ACommandNick_Announcer'));
|
||||
}
|
||||
|
||||
protected function Executed(
|
||||
CallData arguments,
|
||||
EPlayer callerPlayer,
|
||||
CommandPermissions permissions
|
||||
) {
|
||||
local Text givenName;
|
||||
local array<FormattingErrorsReport.FormattedStringError> errors;
|
||||
|
||||
givenName = arguments.parameters.GetText(P("nick"));
|
||||
// `newName`'s reference persists between different command calls and
|
||||
// only deallocated when we need this variable for the next execution.
|
||||
// "Leaking" a single `Text` like that is insignificant.
|
||||
_.memory.Free(newName);
|
||||
newName = _.text.Empty();
|
||||
if (arguments.options.HasKey(P("plain"))) {
|
||||
newName = givenName.MutableCopy();
|
||||
}
|
||||
else
|
||||
{
|
||||
errors = class'FormattingStringParser'.static
|
||||
.ParseFormatted(givenName, newName, true);
|
||||
}
|
||||
foundErrors = false;
|
||||
if (arguments.options.HasKey(P("color")))
|
||||
{
|
||||
foundErrors = !TryChangeDefaultColor(
|
||||
arguments.options.GetTextBy(P("/color/default_color")));
|
||||
}
|
||||
foundErrors = foundErrors || (errors.length > 0);
|
||||
class'FormattingReportTool'.static.Report(callerConsole, errors);
|
||||
class'FormattingReportTool'.static.FreeErrors(errors);
|
||||
}
|
||||
|
||||
protected function ExecutedFor(
|
||||
EPlayer target,
|
||||
CallData arguments,
|
||||
EPlayer instigator,
|
||||
CommandPermissions permissions
|
||||
) {
|
||||
local Text alteredVersion;
|
||||
|
||||
if (!foundErrors || arguments.options.HasKey(P("fix")))
|
||||
{
|
||||
announcer.Setup(target, instigator, othersConsole);
|
||||
target.SetName(newName);
|
||||
alteredVersion = target.GetName();
|
||||
if (newName.Compare(alteredVersion, SCASE_SENSITIVE, SFORM_SENSITIVE)) {
|
||||
announcer.AnnounceChangedNickname(newName);
|
||||
}
|
||||
else {
|
||||
announcer.AnnounceChangedAlteredNickname(newName, alteredVersion);
|
||||
}
|
||||
_.memory.Free(alteredVersion);
|
||||
}
|
||||
}
|
||||
|
||||
protected function bool TryChangeDefaultColor(BaseText specifiedColor)
|
||||
{
|
||||
local Color defaultColor;
|
||||
|
||||
if (newName == none) return false;
|
||||
if (specifiedColor == none) return false;
|
||||
|
||||
if (_.color.Parse(specifiedColor, defaultColor))
|
||||
{
|
||||
newName.ChangeDefaultColor(defaultColor);
|
||||
return true;
|
||||
}
|
||||
callerConsole
|
||||
.Write(F("Specified {$TextFailure invalid} color: "))
|
||||
.WriteLine(specifiedColor);
|
||||
return false;
|
||||
}
|
||||
|
||||
defaultproperties {
|
||||
preferredName = "nick"
|
||||
}
|
||||
96
kf_sources/Futility/Classes/ACommandNick_Announcer.uc
Normal file
96
kf_sources/Futility/Classes/ACommandNick_Announcer.uc
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* Announcer for `ACommandNick`.
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class ACommandNick_Announcer extends CommandAnnouncer;
|
||||
|
||||
var private AnnouncementVariations changedNickname, changedAlteredNickname;
|
||||
|
||||
protected function Finalizer()
|
||||
{
|
||||
FreeVariations(changedNickname);
|
||||
FreeVariations(changedAlteredNickname);
|
||||
super.Finalizer();
|
||||
}
|
||||
|
||||
public final function AnnounceChangedNickname(BaseText newNickname)
|
||||
{
|
||||
local int i;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!changedNickname.initialized)
|
||||
{
|
||||
changedNickname.initialized = true;
|
||||
changedNickname.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Your nickname {$TextNeutral changed} to \"%1\"");
|
||||
changedNickname.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextNeutral changed} their own nickname to"
|
||||
@ "\"%1\"");
|
||||
changedNickname.toOtherReport = _.text.MakeTemplate_S(
|
||||
"Nickname for %%target%% {$TextNeutral changed} to"
|
||||
@ "\"%1\"");
|
||||
changedNickname.toOtherPrivate = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextNeutral changed} your nickname to \"%1\"");
|
||||
changedNickname.toOtherPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextNeutral changed} nickname for player"
|
||||
@ "%%target%% to \"%1\"");
|
||||
}
|
||||
templates = MakeArray(changedNickname);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().Arg(newNickname);
|
||||
}
|
||||
MakeAnnouncement(changedNickname);
|
||||
}
|
||||
|
||||
public final function AnnounceChangedAlteredNickname(
|
||||
BaseText newNickname,
|
||||
BaseText alteredVersion)
|
||||
{
|
||||
local int i;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!changedAlteredNickname.initialized)
|
||||
{
|
||||
changedAlteredNickname.initialized = true;
|
||||
changedAlteredNickname.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Your nickname was {$TextNeutral changed} to \"%1\", but then"
|
||||
@ "got altered by the game into \"%2\"");
|
||||
changedAlteredNickname.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% has {$TextNeutral changed} their own nickname to"
|
||||
@ "\"%1\", but it was altered by the game into \"%2\"");
|
||||
changedAlteredNickname.toOtherReport = _.text.MakeTemplate_S(
|
||||
"Nickname for %%target%% was {$TextNeutral changed} to"
|
||||
@ "\"%1\", but then got altered by the game into \"%2\"");
|
||||
changedAlteredNickname.toOtherPrivate = _.text.MakeTemplate_S(
|
||||
"%%instigator%% has {$TextNeutral changed} your nickname to \"%1\","
|
||||
@ "but then it got altered by the game into \"%2\"");
|
||||
changedAlteredNickname.toOtherPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% has {$TextNeutral changed} nickname for player"
|
||||
@ "%%target%% to \"%1\", but then it got altered by the game"
|
||||
@ "into \"%2\"");
|
||||
}
|
||||
templates = MakeArray(changedAlteredNickname);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().Arg(newNickname).Arg(alteredVersion);
|
||||
}
|
||||
MakeAnnouncement(changedAlteredNickname);
|
||||
}
|
||||
|
||||
defaultproperties
|
||||
{
|
||||
}
|
||||
124
kf_sources/Futility/Classes/ACommandSpawn.uc
Normal file
124
kf_sources/Futility/Classes/ACommandSpawn.uc
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
/**
|
||||
* Command for spawning new entities into the world.
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class ACommandSpawn extends Command;
|
||||
|
||||
// TODO: use spawned name for errors output?
|
||||
var private ACommandSpawn_Announcer announcer;
|
||||
|
||||
protected function Finalizer()
|
||||
{
|
||||
_.memory.Free(announcer);
|
||||
super.Finalizer();
|
||||
}
|
||||
|
||||
protected function BuildData(CommandDataBuilder builder)
|
||||
{
|
||||
builder.Group(P("debug"));
|
||||
builder.Summary(P("Spawns new entity on the map."));
|
||||
builder.ParamText(P("template"),, P("entity"));
|
||||
builder.Describe(P("Spawns new entity based on the given template at the point"
|
||||
@ "player is currently looking at."));
|
||||
builder.SubCommand(P("at"));
|
||||
builder.ParamText(P("template"),, P("entity"));
|
||||
builder.ParamNumber(P("x"));
|
||||
builder.ParamNumber(P("y"));
|
||||
builder.ParamNumber(P("z"));
|
||||
builder.Describe(P("Spawns new entity based on the given template at"
|
||||
@ "the point, given by the coordinates"));
|
||||
announcer = ACommandSpawn_Announcer(
|
||||
_.memory.Allocate(class'ACommandSpawn_Announcer'));
|
||||
}
|
||||
|
||||
protected function Executed(
|
||||
CallData arguments,
|
||||
EPlayer instigator,
|
||||
CommandPermissions permissions
|
||||
) {
|
||||
local HashTable value;
|
||||
local Vector spawnLocation;
|
||||
|
||||
announcer.Setup(none, instigator, othersConsole);
|
||||
value = arguments.parameters.GetHashTable(P("template"));
|
||||
if (arguments.subCommandName.IsEmpty()) {
|
||||
SpawnInInstigatorSight(instigator, value);
|
||||
} else if (arguments.subCommandName.Compare(P("at"), SCASE_INSENSITIVE)) {
|
||||
spawnLocation.x = arguments.parameters.GetFloat(P("x"));
|
||||
spawnLocation.y = arguments.parameters.GetFloat(P("y"));
|
||||
spawnLocation.z = arguments.parameters.GetFloat(P("z"));
|
||||
SpawnAt(instigator, value, spawnLocation);
|
||||
}
|
||||
_.memory.Free(value);
|
||||
}
|
||||
|
||||
private final function SpawnAt(
|
||||
EPlayer instigator,
|
||||
HashTable value,
|
||||
Vector spawnLocation
|
||||
) {
|
||||
local Text humanReadable, template;
|
||||
local EPlaceable result;
|
||||
|
||||
humanReadable = value.GetText(P("alias"));
|
||||
template = value.GetText(P("value"));
|
||||
result = _server.kf.world.Spawn(template, spawnLocation);
|
||||
if (result != none) {
|
||||
announcer.AnnounceSpawned(humanReadable);
|
||||
} else {
|
||||
announcer.AnnounceSpawningFailed(humanReadable);
|
||||
}
|
||||
_.memory.Free2(humanReadable, result);
|
||||
}
|
||||
|
||||
private final function SpawnInInstigatorSight(
|
||||
EPlayer instigator,
|
||||
HashTable value
|
||||
) {
|
||||
local EPlaceable result;
|
||||
local Vector spawnLocation;
|
||||
local TracingIterator iter;
|
||||
local Text humanReadable, template;
|
||||
|
||||
humanReadable = value.GetText(P("alias"));
|
||||
template = value.GetText(P("value"));
|
||||
iter = _server.kf.world.TracePlayerSight(instigator);
|
||||
iter.LeaveOnlyVisible();
|
||||
if (iter.HasFinished()) {
|
||||
announcer.AnnounceFailedTrace();
|
||||
return;
|
||||
}
|
||||
spawnLocation = iter.GetHitLocation();
|
||||
result = _server.kf.world.Spawn(template, spawnLocation);
|
||||
// Shift position back a little and try again;
|
||||
// this should fix a ton of spawning failures
|
||||
if (result == none) {
|
||||
spawnLocation = spawnLocation + Normal(iter.GetTracingStart() - spawnLocation) * 100;
|
||||
result = _server.kf.world.Spawn(template, spawnLocation);
|
||||
}
|
||||
if (result != none) {
|
||||
announcer.AnnounceSpawned(humanReadable);
|
||||
} else {
|
||||
announcer.AnnounceSpawningFailed(humanReadable);
|
||||
}
|
||||
_.memory.Free4(result, iter, humanReadable, result);
|
||||
}
|
||||
|
||||
defaultproperties {
|
||||
preferredName = "spawn"
|
||||
}
|
||||
90
kf_sources/Futility/Classes/ACommandSpawn_Announcer.uc
Normal file
90
kf_sources/Futility/Classes/ACommandSpawn_Announcer.uc
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* Announcer for `ACommandSpawn`.
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class ACommandSpawn_Announcer extends CommandAnnouncer;
|
||||
|
||||
var private AnnouncementVariations spawned, spawningFailed, failedTrace;
|
||||
|
||||
protected function Finalizer()
|
||||
{
|
||||
FreeVariations(spawned);
|
||||
FreeVariations(spawningFailed);
|
||||
FreeVariations(failedTrace);
|
||||
super.Finalizer();
|
||||
}
|
||||
|
||||
public final function AnnounceSpawned(BaseText template)
|
||||
{
|
||||
local int i;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!spawned.initialized)
|
||||
{
|
||||
spawned.initialized = true;
|
||||
spawned.toSelfReport = _.text.MakeTemplate_S(
|
||||
"You {$TextPositive spawned} {$TextEmphasis %1}!");
|
||||
spawned.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextNeutral spawned} {$TextEmphasis %1}!");
|
||||
}
|
||||
templates = MakeArray(spawned);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().Arg(template);
|
||||
}
|
||||
MakeAnnouncement(spawned);
|
||||
}
|
||||
|
||||
public final function AnnounceSpawningFailed(BaseText template)
|
||||
{
|
||||
local int i;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!spawningFailed.initialized)
|
||||
{
|
||||
spawningFailed.initialized = true;
|
||||
spawningFailed.toSelfReport = _.text.MakeTemplate_S(
|
||||
"{$TextFailure Couldn't spawn} {$TextEmphasis %1}!");
|
||||
}
|
||||
templates = MakeArray(spawningFailed);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().Arg(template);
|
||||
}
|
||||
MakeAnnouncement(spawningFailed);
|
||||
}
|
||||
|
||||
public final function AnnounceFailedTrace()
|
||||
{
|
||||
local int i;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!failedTrace.initialized)
|
||||
{
|
||||
failedTrace.initialized = true;
|
||||
failedTrace.toSelfReport = _.text.MakeTemplate_S(
|
||||
"{$TextFailure Failed} to trace spawn point");
|
||||
}
|
||||
templates = MakeArray(failedTrace);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset();
|
||||
}
|
||||
MakeAnnouncement(failedTrace);
|
||||
}
|
||||
|
||||
defaultproperties
|
||||
{
|
||||
}
|
||||
699
kf_sources/Futility/Classes/ACommandTrader.uc
Normal file
699
kf_sources/Futility/Classes/ACommandTrader.uc
Normal file
|
|
@ -0,0 +1,699 @@
|
|||
/**
|
||||
* Command for managing trader time and traders.
|
||||
* Copyright 2021-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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class ACommandTrader extends Command;
|
||||
|
||||
var private ACommandTrader_Announcer announcer;
|
||||
|
||||
var protected const int TLIST, TOPEN, TCLOSE, TENABLE, TDISABLE, TAUTO_OPEN;
|
||||
var protected const int TTRADER, TTRADERS, TALL, TAUTO_OPEN_QUESTION, TQUOTE;
|
||||
var protected const int TAUTO_OPEN_FLAG, TDISABLED_FLAG, TUNKNOWN_TRADERS;
|
||||
var protected const int TLIST_TRADERS, TCOMMA_SPACE, TSELECTED_FLAG;
|
||||
var protected const int TPARENTHESIS_OPEN, TPARENTHESIS_CLOSE;
|
||||
var protected const int TSELECT, TIGNORE_DOORS, TBOOT, TTRADER_TIME, TTIME;
|
||||
var protected const int TIGNORE_PLAYERS, TPAUSE, TUNPAUSE, TCANNOT_PARSE_PARAM;
|
||||
var protected const int TCLOSEST, TSPACE;
|
||||
|
||||
protected function Finalizer()
|
||||
{
|
||||
_.memory.Free(announcer);
|
||||
super.Finalizer();
|
||||
}
|
||||
|
||||
protected function BuildData(CommandDataBuilder builder)
|
||||
{
|
||||
builder.Group(P("gameplay"));
|
||||
builder.Summary(P("Manages trader time and available traders."));
|
||||
builder.Describe(P("Enables of disables trading."));
|
||||
builder.ParamBoolean(T(TENABLE));
|
||||
builder.SubCommand(T(TTIME));
|
||||
builder.Describe(F("Changes current trader time if numeric value is specified."
|
||||
@ "You can also pause trader countdown by specifying"
|
||||
@ "{$TextEmphasis pause} or turn it back on with"
|
||||
@ "{$TextEmphasis unpause}."));
|
||||
builder.ParamText(T(TTRADER_TIME));
|
||||
builder.SubCommand(T(TLIST));
|
||||
builder.Describe(P("Lists names of all available traders and"
|
||||
@ "marks closest one to the caller."));
|
||||
builder.SubCommand(T(TOPEN));
|
||||
builder.Describe(P("Opens specified traders."));
|
||||
builder.OptionalParams();
|
||||
builder.ParamTextList(T(TTRADERS));
|
||||
builder.SubCommand(T(TCLOSE));
|
||||
builder.Describe(P("Closes specified traders."));
|
||||
builder.OptionalParams();
|
||||
builder.ParamTextList(T(TTRADERS));
|
||||
builder.SubCommand(T(TAUTO_OPEN));
|
||||
builder.Describe(P("Sets whether specified traders are open automatically."));
|
||||
builder.ParamBoolean(T(TAUTO_OPEN_QUESTION));
|
||||
builder.OptionalParams();
|
||||
builder.ParamTextList(T(TTRADERS));
|
||||
builder.SubCommand(T(TSELECT));
|
||||
builder.Describe(P("Selects specified trader."));
|
||||
builder.OptionalParams();
|
||||
builder.ParamText(T(TTRADER));
|
||||
builder.SubCommand(T(TBOOT));
|
||||
builder.Describe(P("Boots all players from specified traders. If no traders"
|
||||
@ "were specified - assumes that all of them should be affected."));
|
||||
builder.OptionalParams();
|
||||
builder.ParamTextList(T(TTRADERS));
|
||||
builder.SubCommand(T(TENABLE));
|
||||
builder.Describe(P("Enables specified traders."));
|
||||
builder.OptionalParams();
|
||||
builder.ParamTextList(T(TTRADERS));
|
||||
builder.SubCommand(T(TDISABLE));
|
||||
builder.Describe(P("Disables specified traders."));
|
||||
builder.OptionalParams();
|
||||
builder.ParamTextList(T(TTRADERS));
|
||||
builder.Option(T(TALL));
|
||||
builder.Describe(P("If sub-command targets shops, this flag will make it"
|
||||
@ "target all the available shops."));
|
||||
builder.Option(T(TCLOSEST));
|
||||
builder.Describe(P("If sub-command targets shops, this flag will make it also"
|
||||
@ "target closest shop to the caller."));
|
||||
builder.Option(T(TIGNORE_DOORS));
|
||||
builder.Describe(F("When used with {$TextEmphasis select} sub-command, it will"
|
||||
@ "neither open or close doors."));
|
||||
builder.Option(T(TIGNORE_PLAYERS), P("I"));
|
||||
builder.Describe(P("Normally commands that close doors will automatically boot"
|
||||
@ "players from inside to prevent locking them in. This flag forces"
|
||||
@ "this command to leave players inside. However they can still be"
|
||||
@ "booted out at the end of trading time. Also it is impossible to"
|
||||
@ "disable the trader and not boot players inside it."));
|
||||
announcer = ACommandTrader_Announcer(
|
||||
_.memory.Allocate(class'ACommandTrader_Announcer'));
|
||||
}
|
||||
|
||||
protected function Executed(
|
||||
CallData arguments,
|
||||
EPlayer instigator,
|
||||
CommandPermissions permissions
|
||||
) {
|
||||
local bool newTradingStatus;
|
||||
|
||||
announcer.Setup(none, instigator, othersConsole);
|
||||
if (arguments.subCommandName.IsEmpty())
|
||||
{
|
||||
newTradingStatus = arguments.parameters.GetBool(T(TENABLE));
|
||||
if ( arguments.parameters.GetBool(T(TENABLE))
|
||||
== _server.kf.trading.IsTradingActive())
|
||||
{
|
||||
announcer.AnnounceTradingNoChange();
|
||||
}
|
||||
_server.kf.trading.SetTradingStatus(newTradingStatus);
|
||||
if (newTradingStatus) {
|
||||
announcer.AnnounceActivatedTrading();
|
||||
}
|
||||
else {
|
||||
announcer.AnnounceDeactivatedTrading();
|
||||
}
|
||||
}
|
||||
else if (arguments.subCommandName.Compare(T(TLIST))) {
|
||||
ListTradersFor(instigator);
|
||||
}
|
||||
else if (arguments.subCommandName.Compare(T(TTIME), SCASE_INSENSITIVE)) {
|
||||
HandleTraderTime(arguments);
|
||||
}
|
||||
else if (arguments.subCommandName.Compare(T(TOPEN), SCASE_INSENSITIVE)) {
|
||||
SetTradersOpen(true, arguments, instigator);
|
||||
}
|
||||
else if (arguments.subCommandName.Compare(T(TCLOSE), SCASE_INSENSITIVE)) {
|
||||
SetTradersOpen(false, arguments, instigator);
|
||||
}
|
||||
else if (arguments.subCommandName.Compare(T(TSELECT), SCASE_INSENSITIVE)) {
|
||||
SelectTrader(arguments, instigator);
|
||||
}
|
||||
else if (arguments.subCommandName.Compare(T(TBOOT), SCASE_INSENSITIVE)) {
|
||||
BootFromTraders(arguments, instigator);
|
||||
}
|
||||
else if (arguments.subCommandName.Compare(T(TENABLE), SCASE_INSENSITIVE)) {
|
||||
SetTradersEnabled(true, arguments, instigator);
|
||||
}
|
||||
else if (arguments.subCommandName.Compare(T(TDISABLE), SCASE_INSENSITIVE)) {
|
||||
SetTradersEnabled(false, arguments, instigator);
|
||||
}
|
||||
else if (arguments.subCommandName.Compare(T(TAUTO_OPEN), SCASE_INSENSITIVE))
|
||||
{
|
||||
SetTradersAutoOpen(arguments, instigator);
|
||||
}
|
||||
}
|
||||
|
||||
protected function ListTradersFor(EPlayer target)
|
||||
{
|
||||
local int i;
|
||||
local ETrader closestTrader;
|
||||
local array<ETrader> availableTraders;
|
||||
if (target == none) {
|
||||
return;
|
||||
}
|
||||
availableTraders = _server.kf.trading.GetTraders();
|
||||
callerConsole.Flush()
|
||||
.UseColorOnce(_.color.TextEmphasis).Write(T(TLIST_TRADERS));
|
||||
closestTrader = FindClosestTrader(target);
|
||||
for (i = 0; i < availableTraders.length; i += 1)
|
||||
{
|
||||
WriteTrader(availableTraders[i],
|
||||
availableTraders[i].SameAs(closestTrader));
|
||||
if (i != availableTraders.length - 1) {
|
||||
callerConsole.Write(T(TCOMMA_SPACE));
|
||||
}
|
||||
}
|
||||
_.memory.Free(closestTrader);
|
||||
_.memory.FreeMany(availableTraders);
|
||||
callerConsole.Flush();
|
||||
}
|
||||
|
||||
protected function HandleTraderTime(CallData result)
|
||||
{
|
||||
local bool oldIsPaused, newIsPaused;
|
||||
local int countDownValue;
|
||||
local Text parameter;
|
||||
local Parser parser;
|
||||
parameter = result.parameters.GetText(T(TTRADER_TIME));
|
||||
if (parameter.Compare(T(TPAUSE), SCASE_INSENSITIVE))
|
||||
{
|
||||
oldIsPaused = _server.kf.trading.IsCountDownPaused();
|
||||
if (!oldIsPaused) {
|
||||
_server.kf.trading.SetCountdownPause(true);
|
||||
}
|
||||
newIsPaused = _server.kf.trading.IsCountDownPaused();
|
||||
if (oldIsPaused != newIsPaused) {
|
||||
announcer.AnnouncePausedTime();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (parameter.Compare(T(TUNPAUSE), SCASE_INSENSITIVE))
|
||||
{
|
||||
oldIsPaused = _server.kf.trading.IsCountDownPaused();
|
||||
if (oldIsPaused) {
|
||||
_server.kf.trading.SetCountdownPause(false);
|
||||
}
|
||||
newIsPaused = _server.kf.trading.IsCountDownPaused();
|
||||
if (oldIsPaused != newIsPaused) {
|
||||
announcer.AnnounceUnpausedTime();
|
||||
}
|
||||
return;
|
||||
}
|
||||
parser = _.text.Parse(parameter);
|
||||
if (parser.MInteger(countDownValue).Ok())
|
||||
{
|
||||
_server.kf.trading.SetCountdown(countDownValue);
|
||||
announcer.AnnounceChangedCountdown(_server.kf.trading.GetCountdown());
|
||||
}
|
||||
else
|
||||
{
|
||||
callerConsole
|
||||
.UseColor(_.color.TextFailure)
|
||||
.Write(T(TCANNOT_PARSE_PARAM))
|
||||
.WriteLine(parameter)
|
||||
.ResetColor();
|
||||
}
|
||||
parser.FreeSelf();
|
||||
|
||||
}
|
||||
|
||||
protected function SetTradersOpen(
|
||||
bool doOpen,
|
||||
CallData result,
|
||||
EPlayer callerPlayer)
|
||||
{
|
||||
local int i;
|
||||
local bool needToBootPlayers;
|
||||
local array<ETrader> selectedTraders;
|
||||
local Text nextTraderName;
|
||||
local ListBuilder affectedTraders;
|
||||
|
||||
affectedTraders = ListBuilder(_.memory.Allocate(class'ListBuilder'));
|
||||
selectedTraders = GetTradersArray(result, callerPlayer);
|
||||
needToBootPlayers = !doOpen
|
||||
&& !result.options.HasKey(T(TIGNORE_PLAYERS));
|
||||
for (i = 0; i < selectedTraders.length; i += 1)
|
||||
{
|
||||
if (selectedTraders[i].IsOpen() != doOpen)
|
||||
{
|
||||
nextTraderName = selectedTraders[i].GetName();
|
||||
affectedTraders.Item(nextTraderName);
|
||||
_.memory.Free(nextTraderName);
|
||||
}
|
||||
selectedTraders[i].SetOpen(doOpen);
|
||||
if (needToBootPlayers) {
|
||||
selectedTraders[i].BootPlayers();
|
||||
}
|
||||
}
|
||||
if (doOpen) {
|
||||
announcer.AnnounceTradersOpened(affectedTraders);
|
||||
}
|
||||
else {
|
||||
announcer.AnnounceTradersClosed(affectedTraders);
|
||||
}
|
||||
_.memory.FreeMany(selectedTraders);
|
||||
_.memory.Free(affectedTraders);
|
||||
}
|
||||
|
||||
protected function bool AreTradersSame(ETrader trader1, ETrader trader2)
|
||||
{
|
||||
if (trader1 == none && trader2 == none) return true;
|
||||
if (trader1 == none && trader2 != none) return false;
|
||||
if (trader1 != none && trader2 == none) return false;
|
||||
|
||||
return trader1.SameAs(trader2);
|
||||
}
|
||||
|
||||
protected function SelectTrader(CallData result, EPlayer callerPlayer)
|
||||
{
|
||||
local Text specifiedTraderName;
|
||||
local ETrader previouslySelectedTrader, newlySelectedTrader;
|
||||
|
||||
previouslySelectedTrader = _server.kf.trading.GetSelectedTrader();
|
||||
specifiedTraderName = result.parameters.GetText(T(TTRADER));
|
||||
// Try to get trader user want to select:
|
||||
// first try closes (if option is specified), next trader's name
|
||||
if (callerPlayer != none && result.options.HasKey(T(TCLOSEST))) {
|
||||
newlySelectedTrader = FindClosestTrader(callerPlayer);
|
||||
}
|
||||
if (newlySelectedTrader == none) {
|
||||
newlySelectedTrader = _server.kf.trading.GetTrader(specifiedTraderName);
|
||||
}
|
||||
// If nothing is found, but name was specified - there is an error
|
||||
if (newlySelectedTrader == none && specifiedTraderName != none)
|
||||
{
|
||||
callerConsole.Flush()
|
||||
.UseColorOnce(_.color.TextFailure).Write(T(TUNKNOWN_TRADERS))
|
||||
.WriteLine(specifiedTraderName);
|
||||
_.memory.Free(previouslySelectedTrader);
|
||||
return;
|
||||
}
|
||||
// Select proper trader
|
||||
HandleTraderSwap(result, previouslySelectedTrader, newlySelectedTrader);
|
||||
_server.kf.trading.SelectTrader(newlySelectedTrader);
|
||||
// Report change
|
||||
if (AreTradersSame(previouslySelectedTrader, newlySelectedTrader)) {
|
||||
announcer.AnnounceSelectedSameTrader();
|
||||
}
|
||||
else if (newlySelectedTrader == none) {
|
||||
announcer.AnnounceSelectedNoTrader();
|
||||
}
|
||||
else {
|
||||
announcer.AnnounceSelectedTrader(newlySelectedTrader);
|
||||
}
|
||||
_.memory.Free(previouslySelectedTrader);
|
||||
_.memory.Free(newlySelectedTrader);
|
||||
}
|
||||
|
||||
// Boot players from the old trader iff
|
||||
// 1. It is different from the new one (otherwise swapping means nothing);
|
||||
// 2. Option "ignore-players" was not specified.
|
||||
// 3. New trader was actually closed.
|
||||
protected function HandleTraderSwap(
|
||||
CallData result,
|
||||
ETrader oldTrader,
|
||||
ETrader newTrader)
|
||||
{
|
||||
local bool closeOldTrader, openNewTrader;
|
||||
if (oldTrader == none) return;
|
||||
if (oldTrader.SameAs(newTrader)) return;
|
||||
|
||||
closeOldTrader = newTrader == none || !newTrader.IsOpen();
|
||||
openNewTrader = oldTrader.IsOpen();
|
||||
if (closeOldTrader)
|
||||
{
|
||||
if (!result.options.HasKey(T(TIGNORE_DOORS))) {
|
||||
oldTrader.Close();
|
||||
}
|
||||
if (!result.options.HasKey(T(TIGNORE_PLAYERS))) {
|
||||
oldTrader.BootPlayers();
|
||||
}
|
||||
}
|
||||
if (openNewTrader && newTrader != none) {
|
||||
newTrader.Open();
|
||||
}
|
||||
}
|
||||
|
||||
protected function BootFromTraders(CallData result, EPlayer callerPlayer)
|
||||
{
|
||||
local int i;
|
||||
local array<ETrader> selectedTraders;
|
||||
local Text nextTraderName;
|
||||
local ListBuilder affectedTraderList;
|
||||
|
||||
affectedTraderList = ListBuilder(_.memory.Allocate(class'ListBuilder'));
|
||||
selectedTraders = GetTradersArray(result, callerPlayer);
|
||||
if (selectedTraders.length <= 0) {
|
||||
selectedTraders = _server.kf.trading.GetTraders();
|
||||
}
|
||||
for (i = 0; i < selectedTraders.length; i += 1)
|
||||
{
|
||||
nextTraderName = selectedTraders[i].GetName();
|
||||
affectedTraderList.Item(nextTraderName);
|
||||
selectedTraders[i].BootPlayers();
|
||||
_.memory.Free(nextTraderName);
|
||||
}
|
||||
announcer.AnnounceBootedPlayers(affectedTraderList);
|
||||
_.memory.FreeMany(selectedTraders);
|
||||
_.memory.Free(affectedTraderList);
|
||||
}
|
||||
|
||||
protected function SetTradersEnabled(
|
||||
bool doEnable,
|
||||
CallData result,
|
||||
EPlayer callerPlayer)
|
||||
{
|
||||
local int i;
|
||||
local array<ETrader> selectedTraders;
|
||||
local Text nextTraderName;
|
||||
local ListBuilder affectedTraderList;
|
||||
|
||||
affectedTraderList = ListBuilder(_.memory.Allocate(class'ListBuilder'));
|
||||
selectedTraders = GetTradersArray(result, callerPlayer);
|
||||
for (i = 0; i < selectedTraders.length; i += 1)
|
||||
{
|
||||
if (doEnable != selectedTraders[i].IsEnabled())
|
||||
{
|
||||
nextTraderName = selectedTraders[i].GetName();
|
||||
affectedTraderList.Item(nextTraderName);
|
||||
_.memory.Free(nextTraderName);
|
||||
}
|
||||
selectedTraders[i].SetEnabled(doEnable);
|
||||
}
|
||||
if (doEnable) {
|
||||
announcer.AnnounceEnabledTraders(affectedTraderList);
|
||||
}
|
||||
else {
|
||||
announcer.AnnounceDisabledTraders(affectedTraderList);
|
||||
}
|
||||
_.memory.FreeMany(selectedTraders);
|
||||
_.memory.Free(affectedTraderList);
|
||||
}
|
||||
|
||||
protected function SetTradersAutoOpen(CallData result, EPlayer callerPlayer)
|
||||
{
|
||||
local int i;
|
||||
local bool doAutoOpen;
|
||||
local array<ETrader> selectedTraders;
|
||||
local Text nextTraderName;
|
||||
local ListBuilder affectedTraderList;
|
||||
|
||||
affectedTraderList = ListBuilder(_.memory.Allocate(class'ListBuilder'));
|
||||
doAutoOpen = result.parameters.GetBool(T(TAUTO_OPEN_QUESTION));
|
||||
selectedTraders = GetTradersArray(result, callerPlayer);
|
||||
for (i = 0; i < selectedTraders.length; i += 1)
|
||||
{
|
||||
if (doAutoOpen != selectedTraders[i].IsAutoOpen())
|
||||
{
|
||||
nextTraderName = selectedTraders[i].GetName();
|
||||
affectedTraderList.Item(nextTraderName);
|
||||
_.memory.Free(nextTraderName);
|
||||
}
|
||||
selectedTraders[i].SetAutoOpen(doAutoOpen);
|
||||
}
|
||||
if (doAutoOpen) {
|
||||
announcer.AnnounceAutoOpenTraders(affectedTraderList);
|
||||
}
|
||||
else {
|
||||
announcer.AnnounceDoNotAutoOpenTraders(affectedTraderList);
|
||||
}
|
||||
_.memory.FreeMany(selectedTraders);
|
||||
_.memory.Free(affectedTraderList);
|
||||
}
|
||||
|
||||
// Reads traders specified for the command (if any).
|
||||
// Assumes `result != none`.
|
||||
protected function array<ETrader> GetTradersArray(
|
||||
CallData result,
|
||||
EPlayer callerPlayer)
|
||||
{
|
||||
local int i, j;
|
||||
local Text nextTraderName, nextSpecifiedTrader;
|
||||
local ArrayList specifiedTrades;
|
||||
local array<ETrader> resultTraders;
|
||||
local array<ETrader> availableTraders;
|
||||
// Boundary cases: all traders and no traders at all
|
||||
availableTraders = _server.kf.trading.GetTraders();
|
||||
if (result.options.HasKey(T(TALL))) {
|
||||
return availableTraders;
|
||||
}
|
||||
// Add closest one, if flag tells us to
|
||||
if (result.options.HasKey(T(TCLOSEST)))
|
||||
{
|
||||
resultTraders =
|
||||
InsertTrader(resultTraders, FindClosestTrader(callerPlayer));
|
||||
}
|
||||
specifiedTrades = result.parameters.GetArrayList(T(TTRADERS));
|
||||
if (specifiedTrades == none) {
|
||||
return resultTraders;
|
||||
}
|
||||
// We iterate over `availableTraders` in the outer loop because:
|
||||
// 1. Each `ETrader` from `availableTraders` will be matched only once,
|
||||
// ensuring that result will not contain duplicate instances;
|
||||
// 2. `availableTraders.GetName()` creates a new `Text` copy and
|
||||
// `specifiedTrades.GetText()` does not.
|
||||
for (i = 0; i < availableTraders.length; i += 1)
|
||||
{
|
||||
nextTraderName = availableTraders[i].GetName();
|
||||
for (j = 0; j < specifiedTrades.GetLength(); j += 1)
|
||||
{
|
||||
nextSpecifiedTrader = specifiedTrades.GetText(j);
|
||||
if (nextTraderName.Compare(nextSpecifiedTrader))
|
||||
{
|
||||
resultTraders =
|
||||
InsertTrader(resultTraders, availableTraders[i]);
|
||||
availableTraders[i] = none;
|
||||
specifiedTrades.Remove(j, 1);
|
||||
_.memory.Free(nextSpecifiedTrader);
|
||||
break;
|
||||
}
|
||||
_.memory.Free(nextSpecifiedTrader);
|
||||
}
|
||||
nextTraderName.FreeSelf();
|
||||
if (specifiedTrades.GetLength() <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Some of the remaining trader names inside `specifiedTrades` do not
|
||||
// match any actual traders. Report it.
|
||||
if (callerPlayer != none && specifiedTrades.GetLength() > 0) {
|
||||
ReportUnknowTraders(specifiedTrades);
|
||||
}
|
||||
_.memory.Free(specifiedTrades);
|
||||
_.memory.FreeMany(availableTraders);
|
||||
return resultTraders;
|
||||
}
|
||||
|
||||
// Auxiliary method that adds `newTrader` into existing array of traders
|
||||
// if it is still missing.
|
||||
protected function array<ETrader> InsertTrader(
|
||||
/* take */ array<ETrader> traders,
|
||||
/* take */ ETrader newTrader)
|
||||
{
|
||||
local int i;
|
||||
if (newTrader == none) {
|
||||
return traders;
|
||||
}
|
||||
for (i = 0; i < traders.length; i += 1)
|
||||
{
|
||||
if (traders[i].SameAs(newTrader))
|
||||
{
|
||||
_.memory.Free(newTrader);
|
||||
return traders;
|
||||
}
|
||||
}
|
||||
traders[traders.length] = newTrader;
|
||||
return traders;
|
||||
}
|
||||
|
||||
protected function ReportUnknowTraders(ArrayList specifiedTrades)
|
||||
{
|
||||
local int i;
|
||||
local Text nextTraderName;
|
||||
if (specifiedTrades == none) {
|
||||
return;
|
||||
}
|
||||
callerConsole.Flush()
|
||||
.UseColorOnce(_.color.TextNegative).Write(T(TUNKNOWN_TRADERS));
|
||||
for (i = 0; i < specifiedTrades.GetLength(); i += 1)
|
||||
{
|
||||
nextTraderName = specifiedTrades.GetText(i);
|
||||
callerConsole.Write(nextTraderName);
|
||||
_.memory.Free(nextTraderName);
|
||||
if (i != specifiedTrades.GetLength() - 1) {
|
||||
callerConsole.Write(T(TCOMMA_SPACE));
|
||||
}
|
||||
}
|
||||
callerConsole.Flush();
|
||||
}
|
||||
|
||||
// Find closest trader to the `target` player
|
||||
protected function ETrader FindClosestTrader(EPlayer target)
|
||||
{
|
||||
local int i;
|
||||
local float newDistance, bestDistance;
|
||||
local ETrader bestTrader;
|
||||
local array<ETrader> availableTraders;
|
||||
local Vector targetLocation;
|
||||
if (target == none) {
|
||||
return none;
|
||||
}
|
||||
targetLocation = target.GetLocation();
|
||||
availableTraders = _server.kf.trading.GetTraders();
|
||||
for (i = 0; i < availableTraders.length; i += 1)
|
||||
{
|
||||
newDistance =
|
||||
VSizeSquared(availableTraders[i].GetLocation() - targetLocation);
|
||||
if (bestTrader == none || newDistance < bestDistance)
|
||||
{
|
||||
bestDistance = newDistance;
|
||||
_.memory.Free(bestTrader);
|
||||
bestTrader = availableTraders[i];
|
||||
availableTraders[i] = none;
|
||||
}
|
||||
}
|
||||
_.memory.FreeMany(availableTraders);
|
||||
return bestTrader;
|
||||
}
|
||||
|
||||
// Writes a trader name along with information on whether it's
|
||||
// disabled / auto-open
|
||||
protected function WriteTrader(
|
||||
ETrader traderToWrite,
|
||||
bool isClosestTrader)
|
||||
{
|
||||
local Text traderName;
|
||||
if (traderToWrite == none) {
|
||||
return;
|
||||
}
|
||||
callerConsole.Write(T(TQUOTE));
|
||||
if (traderToWrite.IsOpen()) {
|
||||
callerConsole.UseColor(_.color.TextPositive);
|
||||
}
|
||||
else {
|
||||
callerConsole.UseColor(_.color.TextNegative);
|
||||
}
|
||||
traderName = traderToWrite.GetName();
|
||||
callerConsole.Write(traderName)
|
||||
.ResetColor()
|
||||
.Write(T(TQUOTE));
|
||||
traderName.FreeSelf();
|
||||
WriteTraderTags(traderToWrite, isClosestTrader);
|
||||
}
|
||||
|
||||
protected function WriteTraderTags(ETrader traderToWrite, bool isClosest)
|
||||
{
|
||||
local bool hasTagsInFront;
|
||||
local bool isAutoOpen, isSelected;
|
||||
if (traderToWrite == none) {
|
||||
return;
|
||||
}
|
||||
if (!traderToWrite.IsEnabled())
|
||||
{
|
||||
callerConsole.Write(T(TDISABLED_FLAG));
|
||||
return;
|
||||
}
|
||||
isAutoOpen = traderToWrite.IsAutoOpen();
|
||||
isSelected = traderToWrite.IsSelected();
|
||||
if (!isAutoOpen && !isSelected && !isClosest) {
|
||||
return;
|
||||
}
|
||||
callerConsole.Write(T(TSPACE)).Write(T(TPARENTHESIS_OPEN));
|
||||
if (isClosest)
|
||||
{
|
||||
callerConsole.Write(T(TCLOSEST));
|
||||
hasTagsInFront = true;
|
||||
}
|
||||
if (isAutoOpen)
|
||||
{
|
||||
if (hasTagsInFront) {
|
||||
callerConsole.Write(T(TCOMMA_SPACE));
|
||||
}
|
||||
callerConsole.Write(T(TAUTO_OPEN_FLAG));
|
||||
hasTagsInFront = true;
|
||||
}
|
||||
if (isSelected)
|
||||
{
|
||||
if (hasTagsInFront) {
|
||||
callerConsole.Write(T(TCOMMA_SPACE));
|
||||
}
|
||||
callerConsole.Write(T(TSELECTED_FLAG));
|
||||
}
|
||||
callerConsole.Write(T(TPARENTHESIS_CLOSE));
|
||||
}
|
||||
|
||||
defaultproperties
|
||||
{
|
||||
preferredName = "trader"
|
||||
TLIST = 0
|
||||
stringConstants(0) = "list"
|
||||
TOPEN = 1
|
||||
stringConstants(1) = "open"
|
||||
TCLOSE = 2
|
||||
stringConstants(2) = "close"
|
||||
TENABLE = 3
|
||||
stringConstants(3) = "enable"
|
||||
TDISABLE = 4
|
||||
stringConstants(4) = "disable"
|
||||
TAUTO_OPEN = 5
|
||||
stringConstants(5) = "autoopen"
|
||||
TTRADER = 6
|
||||
stringConstants(6) = "trader"
|
||||
TTRADERS = 7
|
||||
stringConstants(7) = "traders"
|
||||
TALL = 8
|
||||
stringConstants(8) = "all"
|
||||
TAUTO_OPEN_QUESTION = 9
|
||||
stringConstants(9) = "autoOpen?"
|
||||
TQUOTE = 10
|
||||
stringConstants(10) = "\""
|
||||
TAUTO_OPEN_FLAG = 11
|
||||
stringConstants(11) = "auto-open"
|
||||
TDISABLED_FLAG = 12
|
||||
stringConstants(12) = " (disabled)"
|
||||
TUNKNOWN_TRADERS = 13
|
||||
stringConstants(13) = "Could not find some of the traders: "
|
||||
TLIST_TRADERS = 14
|
||||
stringConstants(14) = "List of available traders: "
|
||||
TCOMMA_SPACE = 15
|
||||
stringConstants(15) = ", "
|
||||
TPARENTHESIS_OPEN = 16
|
||||
stringConstants(16) = "("
|
||||
TPARENTHESIS_CLOSE = 17
|
||||
stringConstants(17) = ")"
|
||||
TSELECTED_FLAG = 18
|
||||
stringConstants(18) = "selected"
|
||||
TSELECT = 19
|
||||
stringConstants(19) = "select"
|
||||
TIGNORE_DOORS = 20
|
||||
stringConstants(20) = "ignore-doors"
|
||||
TBOOT = 21
|
||||
stringConstants(21) = "boot"
|
||||
TTIME = 22
|
||||
stringConstants(22) = "time"
|
||||
TTRADER_TIME = 23
|
||||
stringConstants(23) = "traderTime"
|
||||
TIGNORE_PLAYERS = 24
|
||||
stringConstants(24) = "ignore-players"
|
||||
TPAUSE = 25
|
||||
stringConstants(25) = "pause"
|
||||
TUNPAUSE = 26
|
||||
stringConstants(26) = "unpause"
|
||||
TCANNOT_PARSE_PARAM = 27
|
||||
stringConstants(27) = "Cannot parse parameter: "
|
||||
TCLOSEST = 28
|
||||
stringConstants(28) = "closest"
|
||||
TSPACE = 29
|
||||
stringConstants(29) = " "
|
||||
}
|
||||
420
kf_sources/Futility/Classes/ACommandTrader_Announcer.uc
Normal file
420
kf_sources/Futility/Classes/ACommandTrader_Announcer.uc
Normal file
|
|
@ -0,0 +1,420 @@
|
|||
/**
|
||||
* Announcer for `ACommandTrader`.
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class ACommandTrader_Announcer extends CommandAnnouncer;
|
||||
|
||||
var private AnnouncementVariations tradingNoChange;
|
||||
var private AnnouncementVariations activatedTrading, deactivatedTrading;
|
||||
var private AnnouncementVariations pausedTime, unpausedTime, changedCountdown;
|
||||
var private AnnouncementVariations tradersOpened, tradersClosed;
|
||||
var private AnnouncementVariations selectedNoTrader, selectedSameTrader;
|
||||
var private AnnouncementVariations selectedTrader, bootedPlayers;
|
||||
var private AnnouncementVariations enabledTraders, disabledTraders;
|
||||
var private AnnouncementVariations autoOpenTraders, doNotAutoOpenTraders;
|
||||
|
||||
protected function Finalizer()
|
||||
{
|
||||
FreeVariations(tradingNoChange);
|
||||
FreeVariations(activatedTrading);
|
||||
FreeVariations(deactivatedTrading);
|
||||
FreeVariations(pausedTime);
|
||||
FreeVariations(unpausedTime);
|
||||
FreeVariations(changedCountdown);
|
||||
FreeVariations(tradersOpened);
|
||||
FreeVariations(tradersClosed);
|
||||
FreeVariations(selectedNoTrader);
|
||||
FreeVariations(selectedSameTrader);
|
||||
FreeVariations(selectedTrader);
|
||||
FreeVariations(bootedPlayers);
|
||||
FreeVariations(enabledTraders);
|
||||
FreeVariations(disabledTraders);
|
||||
FreeVariations(autoOpenTraders);
|
||||
FreeVariations(doNotAutoOpenTraders);
|
||||
super.Finalizer();
|
||||
}
|
||||
|
||||
public final function AnnounceTradingNoChange()
|
||||
{
|
||||
local int i;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!tradingNoChange.initialized)
|
||||
{
|
||||
tradingNoChange.initialized = true;
|
||||
tradingNoChange.toSelfReport = _.text.MakeTemplate_S(
|
||||
"There was {$TextNegative no change} in trading time status");
|
||||
}
|
||||
templates = MakeArray(tradingNoChange);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset();
|
||||
}
|
||||
MakeAnnouncement(tradingNoChange);
|
||||
}
|
||||
|
||||
public final function AnnounceActivatedTrading()
|
||||
{
|
||||
local int i;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!activatedTrading.initialized)
|
||||
{
|
||||
activatedTrading.initialized = true;
|
||||
activatedTrading.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Trader time {$TextPositive started}");
|
||||
activatedTrading.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextPositive started} trader time");
|
||||
}
|
||||
templates = MakeArray(activatedTrading);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset();
|
||||
}
|
||||
MakeAnnouncement(activatedTrading);
|
||||
}
|
||||
|
||||
public final function AnnounceDeactivatedTrading()
|
||||
{
|
||||
local int i;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!deactivatedTrading.initialized)
|
||||
{
|
||||
deactivatedTrading.initialized = true;
|
||||
deactivatedTrading.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Trader time {$TextNegative ended}");
|
||||
deactivatedTrading.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextNegative ended} trader time");
|
||||
}
|
||||
templates = MakeArray(pausedTime);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset();
|
||||
}
|
||||
MakeAnnouncement(deactivatedTrading);
|
||||
}
|
||||
|
||||
public final function AnnouncePausedTime()
|
||||
{
|
||||
local int i;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!pausedTime.initialized)
|
||||
{
|
||||
pausedTime.initialized = true;
|
||||
pausedTime.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Trader time {$TextNeutral paused}");
|
||||
pausedTime.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextNeutral paused} trader time");
|
||||
}
|
||||
templates = MakeArray(pausedTime);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset();
|
||||
}
|
||||
MakeAnnouncement(pausedTime);
|
||||
}
|
||||
|
||||
public final function AnnounceUnpausedTime()
|
||||
{
|
||||
local int i;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!unpausedTime.initialized)
|
||||
{
|
||||
unpausedTime.initialized = true;
|
||||
unpausedTime.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Trader time {$TextNeutral unpaused}");
|
||||
unpausedTime.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextNeutral unpaused} trader time");
|
||||
}
|
||||
templates = MakeArray(unpausedTime);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset();
|
||||
}
|
||||
MakeAnnouncement(unpausedTime);
|
||||
}
|
||||
|
||||
public final function AnnounceChangedCountdown(int userTimerValue)
|
||||
{
|
||||
local int i;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!changedCountdown.initialized)
|
||||
{
|
||||
changedCountdown.initialized = true;
|
||||
changedCountdown.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Trader time {$TextNeutral changed} to %1");
|
||||
changedCountdown.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextNeutral changed} trader time to %1");
|
||||
}
|
||||
templates = MakeArray(changedCountdown);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().ArgInt(userTimerValue);
|
||||
}
|
||||
MakeAnnouncement(changedCountdown);
|
||||
}
|
||||
|
||||
public final function AnnounceTradersOpened(ListBuilder traderList)
|
||||
{
|
||||
local int i;
|
||||
local MutableText traderListAsText;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!tradersOpened.initialized)
|
||||
{
|
||||
tradersOpened.initialized = true;
|
||||
tradersOpened.toSelfReport = _.text.MakeTemplate_S(
|
||||
"{$TextPositive Opened} following traders: %1");
|
||||
tradersOpened.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextPositive opened} following traders: %1");
|
||||
}
|
||||
if (traderList.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
traderListAsText = traderList.GetMutable();
|
||||
templates = MakeArray(tradersOpened);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().Arg(traderListAsText);
|
||||
}
|
||||
MakeAnnouncement(tradersOpened);
|
||||
_.memory.Free(traderListAsText);
|
||||
}
|
||||
|
||||
public final function AnnounceTradersClosed(ListBuilder traderList)
|
||||
{
|
||||
local int i;
|
||||
local MutableText traderListAsText;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!tradersClosed.initialized)
|
||||
{
|
||||
tradersClosed.initialized = true;
|
||||
tradersClosed.toSelfReport = _.text.MakeTemplate_S(
|
||||
"{$TextNegative Closed} following traders: %1");
|
||||
tradersClosed.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextNegative closed} following traders: %1");
|
||||
}
|
||||
if (traderList.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
traderListAsText = traderList.GetMutable();
|
||||
templates = MakeArray(tradersClosed);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().Arg(traderListAsText);
|
||||
}
|
||||
MakeAnnouncement(tradersClosed);
|
||||
_.memory.Free(traderListAsText);
|
||||
}
|
||||
|
||||
public final function AnnounceSelectedNoTrader()
|
||||
{
|
||||
local int i;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!selectedNoTrader.initialized)
|
||||
{
|
||||
selectedNoTrader.initialized = true;
|
||||
selectedNoTrader.toSelfReport = _.text.MakeTemplate_S(
|
||||
"All traders were {$TextNegative deselected}");
|
||||
selectedNoTrader.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextNegative deselected} all traders");
|
||||
}
|
||||
templates = MakeArray(selectedNoTrader);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset();
|
||||
}
|
||||
MakeAnnouncement(selectedNoTrader);
|
||||
}
|
||||
|
||||
public final function AnnounceSelectedSameTrader()
|
||||
{
|
||||
local int i;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!selectedSameTrader.initialized)
|
||||
{
|
||||
selectedSameTrader.initialized = true;
|
||||
selectedSameTrader.toSelfReport = _.text.MakeTemplate_S(
|
||||
"{$TestNeutral No changes} made as a result of"
|
||||
@ "{$TextEmphasis select} command");
|
||||
}
|
||||
templates = MakeArray(selectedSameTrader);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset();
|
||||
}
|
||||
MakeAnnouncement(selectedSameTrader);
|
||||
}
|
||||
|
||||
public final function AnnounceSelectedTrader(ETrader trader)
|
||||
{
|
||||
local int i;
|
||||
local Text traderName;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!selectedTrader.initialized)
|
||||
{
|
||||
selectedTrader.initialized = true;
|
||||
selectedTrader.toSelfReport = _.text.MakeTemplate_S(
|
||||
"{$TextNeutral Selected} trader \"%1\"");
|
||||
selectedTrader.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextNeutral selected} trader \"%1\"");
|
||||
}
|
||||
traderName = trader.GetName();
|
||||
templates = MakeArray(selectedTrader);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().Arg(traderName);
|
||||
}
|
||||
MakeAnnouncement(selectedTrader);
|
||||
_.memory.Free(traderName);
|
||||
}
|
||||
|
||||
public final function AnnounceBootedPlayers(ListBuilder traderList)
|
||||
{
|
||||
local int i;
|
||||
local MutableText traderListAsText;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!bootedPlayers.initialized)
|
||||
{
|
||||
bootedPlayers.initialized = true;
|
||||
bootedPlayers.toSelfReport = _.text.MakeTemplate_S(
|
||||
"{$TextNegative Booted} players from following traders: %1");
|
||||
bootedPlayers.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextNegative booted} players from following"
|
||||
@ "traders: %1");
|
||||
}
|
||||
if (traderList.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
traderListAsText = traderList.GetMutable();
|
||||
templates = MakeArray(bootedPlayers);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().Arg(traderListAsText);
|
||||
}
|
||||
MakeAnnouncement(bootedPlayers);
|
||||
_.memory.Free(traderListAsText);
|
||||
}
|
||||
|
||||
public final function AnnounceEnabledTraders(ListBuilder traderList)
|
||||
{
|
||||
local int i;
|
||||
local MutableText traderListAsText;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!enabledTraders.initialized)
|
||||
{
|
||||
enabledTraders.initialized = true;
|
||||
enabledTraders.toSelfReport = _.text.MakeTemplate_S(
|
||||
"{$TextPositive Enabled} following traders: %1");
|
||||
enabledTraders.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextPositive enabled} following traders: %1");
|
||||
}
|
||||
if (traderList.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
traderListAsText = traderList.GetMutable();
|
||||
templates = MakeArray(enabledTraders);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().Arg(traderListAsText);
|
||||
}
|
||||
MakeAnnouncement(enabledTraders);
|
||||
_.memory.Free(traderListAsText);
|
||||
}
|
||||
|
||||
public final function AnnounceDisabledTraders(ListBuilder traderList)
|
||||
{
|
||||
local int i;
|
||||
local MutableText traderListAsText;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!disabledTraders.initialized)
|
||||
{
|
||||
disabledTraders.initialized = true;
|
||||
disabledTraders.toSelfReport = _.text.MakeTemplate_S(
|
||||
"{$TextNegative Disabled} following traders: %1");
|
||||
disabledTraders.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextNegative disabled} following traders: %1");
|
||||
}
|
||||
if (traderList.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
traderListAsText = traderList.GetMutable();
|
||||
templates = MakeArray(disabledTraders);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().Arg(traderListAsText);
|
||||
}
|
||||
MakeAnnouncement(disabledTraders);
|
||||
_.memory.Free(traderListAsText);
|
||||
}
|
||||
|
||||
public final function AnnounceAutoOpenTraders(ListBuilder traderList)
|
||||
{
|
||||
local int i;
|
||||
local MutableText traderListAsText;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!autoOpenTraders.initialized)
|
||||
{
|
||||
autoOpenTraders.initialized = true;
|
||||
autoOpenTraders.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Following traders will be {$TextPositive auto-opened}: %1");
|
||||
autoOpenTraders.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% made following traders {$TextPositive automatically"
|
||||
@ "openable}: %1");
|
||||
}
|
||||
if (traderList.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
traderListAsText = traderList.GetMutable();
|
||||
templates = MakeArray(autoOpenTraders);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().Arg(traderListAsText);
|
||||
}
|
||||
MakeAnnouncement(autoOpenTraders);
|
||||
_.memory.Free(traderListAsText);
|
||||
}
|
||||
|
||||
public final function AnnounceDoNotAutoOpenTraders(ListBuilder traderList)
|
||||
{
|
||||
local int i;
|
||||
local MutableText traderListAsText;
|
||||
local array<TextTemplate> templates;
|
||||
|
||||
if (!doNotAutoOpenTraders.initialized)
|
||||
{
|
||||
doNotAutoOpenTraders.initialized = true;
|
||||
doNotAutoOpenTraders.toSelfReport = _.text.MakeTemplate_S(
|
||||
"Following traders will {$TextNegative no longer} be auto-opened:"
|
||||
@ "%1");
|
||||
doNotAutoOpenTraders.toSelfPublic = _.text.MakeTemplate_S(
|
||||
"%%instigator%% made following traders {$TextNegative no longer}"
|
||||
@ "automatically openable: %1");
|
||||
}
|
||||
if (traderList.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
traderListAsText = traderList.GetMutable();
|
||||
templates = MakeArray(doNotAutoOpenTraders);
|
||||
for (i = 0; i < templates.length; i += 1) {
|
||||
templates[i].Reset().Arg(traderListAsText);
|
||||
}
|
||||
MakeAnnouncement(doNotAutoOpenTraders);
|
||||
_.memory.Free(traderListAsText);
|
||||
}
|
||||
|
||||
defaultproperties
|
||||
{
|
||||
}
|
||||
129
kf_sources/Futility/Classes/ACommandUserData.uc
Normal file
129
kf_sources/Futility/Classes/ACommandUserData.uc
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
/**
|
||||
* Command for changing amount of money players have.
|
||||
* Copyright 2022-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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class ACommandUserData extends Command;
|
||||
|
||||
var private array<EPlayer> playerQueue;
|
||||
|
||||
protected function BuildData(CommandDataBuilder builder)
|
||||
{
|
||||
builder.Group(P("admin"));
|
||||
builder.Summary(P("Allows to read and write custom user data for players."));
|
||||
builder.RequireTarget();
|
||||
builder.ParamText(P("groupName"));
|
||||
builder.OptionalParams();
|
||||
builder.ParamText(P("dataName"));
|
||||
builder.Describe(F("Reads user data stored for targeted user under group"
|
||||
@ "{$TextEmphasis `groupName`} and name"
|
||||
@ "{$TextEmphasis `dataName`}. If {$TextEmphasis `dataName`} is"
|
||||
@ "omitted, the data inside the whole group will be read."));
|
||||
builder.SubCommand(P("write"));
|
||||
builder.ParamText(P("groupName"));
|
||||
builder.ParamText(P("dataName"));
|
||||
builder.ParamJSON(P("newData"));
|
||||
builder.Describe(F("Stores new user data {$TextEmphasis `newData`} for"
|
||||
@ "targeted user under group {$TextEmphasis `groupName`} and name"
|
||||
@ "{$TextEmphasis `dataName`}."));
|
||||
}
|
||||
|
||||
protected function ExecutedFor(
|
||||
EPlayer target,
|
||||
CallData arguments,
|
||||
EPlayer instigator,
|
||||
CommandPermissions permissions
|
||||
) {
|
||||
local AcediaObject userData;
|
||||
local Text groupName, dataName;
|
||||
|
||||
groupName = arguments.parameters.GetText(P("groupName"));
|
||||
dataName = arguments.parameters.GetText(P("dataName"));
|
||||
userData = arguments.parameters.GetItem(P("newData"));
|
||||
if (arguments.subCommandName.IsEmpty()) {
|
||||
ReadUserData(target, groupName, dataName);
|
||||
}
|
||||
else {
|
||||
WriteUserData(target, groupName, dataName, userData);
|
||||
}
|
||||
_.memory.Free(dataName);
|
||||
_.memory.Free(groupName);
|
||||
}
|
||||
|
||||
private final function ReadUserData(
|
||||
EPlayer targetPlayer,
|
||||
BaseText groupName,
|
||||
BaseText dataName)
|
||||
{
|
||||
local User identity;
|
||||
local AcediaObject rawData;
|
||||
local MutableText dataAsJSON;
|
||||
local Text targetPlayerName;
|
||||
|
||||
identity = targetPlayer.GetIdentity();
|
||||
if (identity == none) {
|
||||
return;
|
||||
}
|
||||
targetPlayerName = targetPlayer.GetName();
|
||||
rawData = identity.GetPersistentData(groupName, dataName);
|
||||
dataAsJSON = _.json.PrettyPrint(rawData);
|
||||
targetPlayer.BorrowConsole()
|
||||
.Write(F("User data for player "))
|
||||
.Write(targetPlayerName)
|
||||
.Write(P(": "))
|
||||
.WriteLine(dataAsJSON);
|
||||
_.memory.Free(dataAsJSON);
|
||||
_.memory.Free(rawData);
|
||||
_.memory.Free(targetPlayerName);
|
||||
_.memory.Free(identity);
|
||||
}
|
||||
|
||||
private final function WriteUserData(
|
||||
EPlayer targetPlayer,
|
||||
BaseText groupName,
|
||||
BaseText dataName,
|
||||
AcediaObject rawData)
|
||||
{
|
||||
local User identity;
|
||||
local Text targetPlayerName;
|
||||
|
||||
identity = targetPlayer.GetIdentity();
|
||||
if (identity == none) {
|
||||
return;
|
||||
}
|
||||
targetPlayerName = targetPlayer.GetName();
|
||||
if (identity.SetPersistentData(groupName, dataName, rawData))
|
||||
{
|
||||
targetPlayer.BorrowConsole()
|
||||
.Write(P("User data for player "))
|
||||
.Write(targetPlayerName)
|
||||
.WriteLine(F(" was {$TextPositive successfully} changed!"));
|
||||
}
|
||||
else
|
||||
{
|
||||
targetPlayer.BorrowConsole()
|
||||
.Write(P("User data for player "))
|
||||
.Write(targetPlayerName)
|
||||
.WriteLine(F(" has {$TextPositive failed} to change!"));
|
||||
}
|
||||
_.memory.Free(targetPlayerName);
|
||||
_.memory.Free(identity);
|
||||
}
|
||||
|
||||
defaultproperties {
|
||||
preferredName = "userdata"
|
||||
}
|
||||
282
kf_sources/Futility/Classes/CommandAnnouncer.uc
Normal file
282
kf_sources/Futility/Classes/CommandAnnouncer.uc
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
/**
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<TextTemplate> 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;
|
||||
_.memory.Free(targetName);
|
||||
targetName = none;
|
||||
if (newTarget != none && newTarget.IsAllocated())
|
||||
{
|
||||
target = newTarget;
|
||||
targetLifeVersion = newTarget.GetLifeVersion();
|
||||
targetName = target
|
||||
.GetName()
|
||||
.IntoMutableText()
|
||||
.ChangeDefaultColor(_.color.LightGray);
|
||||
}
|
||||
instigator = none;
|
||||
_.memory.Free(instigatorName);
|
||||
instigatorName = none;
|
||||
if (newInstigator != none && newInstigator.IsAllocated())
|
||||
{
|
||||
instigator = newInstigator;
|
||||
instigatorLifeVersion = newInstigator.GetLifeVersion();
|
||||
instigatorName = instigator
|
||||
.GetName()
|
||||
.IntoMutableText()
|
||||
.ChangeDefaultColor(_.color.LightGray);
|
||||
}
|
||||
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 (!ValidateClasses()) return;
|
||||
|
||||
instigatorConsole = _.console.For(instigator);
|
||||
targetConsole = _.console.For(target);
|
||||
if (target == none || 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<TextTemplate> MakeArray(
|
||||
AnnouncementVariations variations)
|
||||
{
|
||||
local array<TextTemplate> result;
|
||||
|
||||
if (variations.toSelfReport != none) {
|
||||
result[result.length] = variations.toSelfReport;
|
||||
}
|
||||
if (variations.toSelfPublic != none) {
|
||||
result[result.length] = variations.toSelfPublic;
|
||||
}
|
||||
if (variations.toOtherReport != none) {
|
||||
result[result.length] = variations.toOtherReport;
|
||||
}
|
||||
if (variations.toOtherPrivate != none) {
|
||||
result[result.length] = variations.toOtherPrivate;
|
||||
}
|
||||
if (variations.toOtherPublic != none) {
|
||||
result[result.length] = variations.toOtherPublic;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private final function bool ValidateClasses()
|
||||
{
|
||||
if (instigator == none) return false;
|
||||
if (publicConsole == none) return false;
|
||||
if (instigator.GetLifeVersion() != instigatorLifeVersion) return false;
|
||||
if (!instigator.IsExistent()) return false;
|
||||
|
||||
if (target != none)
|
||||
{
|
||||
if ( target.GetLifeVersion() != targetLifeVersion
|
||||
|| !target.IsExistent())
|
||||
{
|
||||
target = none;
|
||||
}
|
||||
}
|
||||
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("instigator"), instigatorName)
|
||||
.TextArg(P("target"), targetName);
|
||||
result = template.CollectFormattedMutable();
|
||||
writer.Write(result).Flush();
|
||||
_.memory.Free(result);
|
||||
}
|
||||
|
||||
defaultproperties
|
||||
{
|
||||
}
|
||||
158
kf_sources/Futility/Classes/FormattingReportTool.uc
Normal file
158
kf_sources/Futility/Classes/FormattingReportTool.uc
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
/**
|
||||
* Interface class for providing static methods for working with errors,
|
||||
* that can arise from parsing formatted strings.
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class FormattingReportTool extends AcediaObject
|
||||
abstract;
|
||||
|
||||
var private const int TCASES, TREPORT_HEADER, TUNMATCHED_SINGLE;
|
||||
var private const int TUNMATCHED_MULTIPLE, TEMPTY_TAG_SINGLE;
|
||||
var private const int TEMPTY_TAG_MULTIPLE, TBAD_COLOR, TBAD_GRADIENT_POINT;
|
||||
var private const int TBADSHORT_TAG;
|
||||
|
||||
/**
|
||||
* Outputs report about formatting errors given by `errors` array.
|
||||
* Reports will be made only if at least one error exists.
|
||||
*
|
||||
* @param writer `ConsoleWriter` to output report into.
|
||||
* @param errors Formatting errors to report.
|
||||
*/
|
||||
public final static function Report(
|
||||
ConsoleWriter writer,
|
||||
array<FormattingErrorsReport.FormattedStringError> errors)
|
||||
{
|
||||
local int i;
|
||||
local ListBuilder builder;
|
||||
local MutableText itemList;
|
||||
|
||||
builder = ListBuilder(__().memory.Allocate(class'ListBuilder'));
|
||||
writer.Write(T(default.TREPORT_HEADER));
|
||||
for (i = 0; i < errors.length; i += 1)
|
||||
{
|
||||
if (errors[i].type == FSE_UnmatchedClosingBrackets)
|
||||
{
|
||||
ReportCount(
|
||||
errors[i],
|
||||
builder,
|
||||
default.TUNMATCHED_SINGLE,
|
||||
default.TUNMATCHED_MULTIPLE);
|
||||
}
|
||||
else if (errors[i].type == FSE_EmptyColorTag)
|
||||
{
|
||||
ReportCount(
|
||||
errors[i],
|
||||
builder,
|
||||
default.TEMPTY_TAG_SINGLE,
|
||||
default.TEMPTY_TAG_MULTIPLE);
|
||||
}
|
||||
else if (errors[i].type == FSE_BadColor) {
|
||||
builder.Item(T(default.TBAD_COLOR)).Comment(errors[i].cause);
|
||||
}
|
||||
else if (errors[i].type == FSE_BadShortColorTag) {
|
||||
builder.Item(T(default.TBADSHORT_TAG)).Comment(errors[i].cause);
|
||||
}
|
||||
else if (errors[i].type == FSE_BadGradientPoint)
|
||||
{
|
||||
builder
|
||||
.Item(T(default.TBAD_GRADIENT_POINT))
|
||||
.Comment(errors[i].cause);
|
||||
}
|
||||
}
|
||||
itemList = builder.IntoMutableText();
|
||||
writer.WriteLine(itemList);
|
||||
__().memory.Free(itemList);
|
||||
}
|
||||
|
||||
/**
|
||||
* `FormattedStringError` is a struct that can contain a `Text` object that
|
||||
* needs to be deallocated. This is convenience method that does that.
|
||||
*
|
||||
* @param errors Errors, whos `cause` filds must deallocated.
|
||||
*/
|
||||
public final static function FreeErrors(
|
||||
array<FormattingErrorsReport.FormattedStringError> errors)
|
||||
{
|
||||
local int i;
|
||||
|
||||
for (i = 0; i < errors.length; i += 1) {
|
||||
__().memory.Free(errors[i].cause);
|
||||
}
|
||||
}
|
||||
|
||||
private final static function ReportCause(
|
||||
FormattingErrorsReport.FormattedStringError error,
|
||||
ListBuilder builder,
|
||||
int sentence)
|
||||
{
|
||||
local MutableText causeBuilder;
|
||||
|
||||
if (error.cause == none) {
|
||||
return;
|
||||
}
|
||||
builder.Item(T(sentence));
|
||||
causeBuilder = __().text.FromIntMutable(error.count).Append(T(default.TCASES));
|
||||
builder.Comment(causeBuilder);
|
||||
__().memory.Free(causeBuilder);
|
||||
}
|
||||
|
||||
// In the methods below, do not double check the error type in the following
|
||||
// errors or whether `builder != none`
|
||||
private final static function ReportCount(
|
||||
FormattingErrorsReport.FormattedStringError error,
|
||||
ListBuilder builder,
|
||||
int singleSentence,
|
||||
int multipleSentence)
|
||||
{
|
||||
local MutableText commentBuilder;
|
||||
|
||||
if (error.count < 1) {
|
||||
return;
|
||||
}
|
||||
if (error.count == 1)
|
||||
{
|
||||
builder.Item(T(singleSentence));
|
||||
return;
|
||||
}
|
||||
builder.Item(T(multipleSentence));
|
||||
commentBuilder = __().text.FromIntMutable(error.count).Append(T(default.TCASES));
|
||||
builder.Comment(commentBuilder);
|
||||
__().memory.Free(commentBuilder);
|
||||
}
|
||||
|
||||
defaultproperties
|
||||
{
|
||||
TCASES = 0
|
||||
stringConstants(0) = " cases"
|
||||
TREPORT_HEADER = 1
|
||||
stringConstants(1) = "{$TextFailure Following formatting errors were found}:"
|
||||
TUNMATCHED_SINGLE = 2
|
||||
stringConstants(2) = "unmatched closing curly bracket '&}'"
|
||||
TUNMATCHED_MULTIPLE = 3
|
||||
stringConstants(3) = "several unmatched closing curly brackets '&}'"
|
||||
TEMPTY_TAG_SINGLE = 4
|
||||
stringConstants(4) = "empty formatting tag"
|
||||
TEMPTY_TAG_MULTIPLE = 5
|
||||
stringConstants(5) = "several empty formatting tag"
|
||||
TBAD_COLOR = 6
|
||||
stringConstants(6) = "bad color"
|
||||
TBAD_GRADIENT_POINT = 7
|
||||
stringConstants(7) = "bad gradient point"
|
||||
TBADSHORT_TAG = 8
|
||||
stringConstants(8) = "bad short tag"
|
||||
}
|
||||
36
kf_sources/Futility/Classes/Futility.uc
Normal file
36
kf_sources/Futility/Classes/Futility.uc
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Config object for `Futility_Feature`.
|
||||
* Copyright 2021-2022 Anton Tarasenko
|
||||
*------------------------------------------------------------------------------
|
||||
* This file is part of Futility.
|
||||
*
|
||||
* Futility 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.
|
||||
*
|
||||
* Futility 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 Futility. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class Futility extends FeatureConfig
|
||||
perobjectconfig
|
||||
config(Futility);
|
||||
|
||||
protected function HashTable ToData() {
|
||||
return _.collections.EmptyHashTable();
|
||||
}
|
||||
|
||||
protected function FromData(HashTable source) {
|
||||
}
|
||||
|
||||
protected function DefaultIt() {
|
||||
}
|
||||
|
||||
defaultproperties {
|
||||
configName = "Futility"
|
||||
}
|
||||
141
kf_sources/Futility/Classes/FutilityChat.uc
Normal file
141
kf_sources/Futility/Classes/FutilityChat.uc
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
/**
|
||||
* Config class for storing map lists.
|
||||
* Copyright 2022-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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class FutilityChat extends FeatureConfig
|
||||
perobjectconfig
|
||||
config(FutilityChat);
|
||||
|
||||
enum ChatColorSetting {
|
||||
CCS_DoNothing,
|
||||
CCS_TeamColorForced,
|
||||
CCS_ConfigColorForced,
|
||||
CCS_TeamColorCustom,
|
||||
CCS_ConfigColorCustom
|
||||
};
|
||||
|
||||
/// How to color text chat messages?
|
||||
///
|
||||
/// 1. `CCS_DoNothing` - do not change color in any way;
|
||||
/// 2. `CCS_TeamColorForced` - force players' team colors for
|
||||
/// their messages;
|
||||
/// 3. `CCS_ConfigColorForced` - force `configuredColor` value for
|
||||
/// players' messages;
|
||||
/// 4. `CCS_TeamColorCustom` - use players' team colors for
|
||||
/// their messages by default, but allow to change color with formatted tags
|
||||
/// (e.g. "Stop right there, {$crimson criminal} scum!");
|
||||
/// 5. `CCS_ConfigColorCustom` - use `configuredColor` value for
|
||||
/// messages by default, but allow to change color with formatted
|
||||
/// tags (e.g. "Stop right there, {$crimson criminal} scum!");
|
||||
///
|
||||
/// Default is `CCS_DoNothing`, corresponding to vanilla behaviour.
|
||||
var public config ChatColorSetting colorSetting;
|
||||
/// Color that will be used if either of `CCS_ConfigColorForced` or
|
||||
/// `CCS_ConfigColorCustom` options were used in `colorSetting`.
|
||||
/// Default value is white: (R=255,G=255,B=255,A=255), has no vanilla
|
||||
/// equivalent.
|
||||
var public config Color configuredColor;
|
||||
/// Allows to modify team color's value for the chat messages
|
||||
/// (if either of `CCS_TeamColorForced` or `CCS_TeamColorCustom` options
|
||||
/// were used) to be lighter or darker.
|
||||
/// This value is clamped between -1 and 1:
|
||||
///
|
||||
/// * `0` means using the same color;
|
||||
/// * range (0; 1) - gives you lighter colors (`1` being white);
|
||||
/// * range (-1; 0) - gives you darker colors (`-1` being black);
|
||||
///
|
||||
/// Default value is `0.6`, has no vanilla equivalent.
|
||||
var public config float teamColorModifier;
|
||||
|
||||
protected function HashTable ToData() {
|
||||
local HashTable data;
|
||||
local Text colorAsText;
|
||||
|
||||
data = __().collections.EmptyHashTable();
|
||||
data.SetString(P("colorSetting"), StringFromColorSetting(colorSetting));
|
||||
colorAsText = _.color.ToText(configuredColor);
|
||||
data.SetItem(P("configuredColor"), colorAsText);
|
||||
_.memory.Free(colorAsText);
|
||||
data.SetFloat(P("teamColorModifier"), teamColorModifier);
|
||||
return data;
|
||||
}
|
||||
|
||||
protected function FromData(HashTable source) {
|
||||
local Text storedText;
|
||||
|
||||
if (source != none) {
|
||||
storedText = source.GetText(P("colorSetting"));
|
||||
colorSetting = ColorSettingFromText(storedText);
|
||||
_.memory.Free(storedText);
|
||||
storedText = source.GetText(P("configuredColor"));
|
||||
_.color.Parse(storedText, configuredColor);
|
||||
_.memory.Free(storedText);
|
||||
teamColorModifier = source.GetFloat(P("teamColorModifier"), 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
private function ChatColorSetting ColorSettingFromText(BaseText permissions) {
|
||||
if (permissions == none) {
|
||||
return CCS_DoNothing;
|
||||
}
|
||||
if (permissions.EndsWith(P("TeamColorForced"), SCASE_INSENSITIVE)) {
|
||||
return CCS_TeamColorForced;
|
||||
}
|
||||
if (permissions.EndsWith(P("ConfigColorForced"), SCASE_INSENSITIVE)) {
|
||||
return CCS_ConfigColorForced;
|
||||
}
|
||||
if (permissions.EndsWith(P("TeamColorCustom"), SCASE_INSENSITIVE)) {
|
||||
return CCS_TeamColorCustom;
|
||||
}
|
||||
if (permissions.EndsWith(P("ConfigColorCustom"), SCASE_INSENSITIVE)) {
|
||||
return CCS_ConfigColorCustom;
|
||||
}
|
||||
return CCS_DoNothing;
|
||||
}
|
||||
|
||||
private function string StringFromColorSetting(ChatColorSetting permissions) {
|
||||
if (permissions == CCS_DoNothing) {
|
||||
return "DoNothing";
|
||||
}
|
||||
if (permissions == CCS_TeamColorForced) {
|
||||
return "TeamColorForced";
|
||||
}
|
||||
if (permissions == CCS_ConfigColorForced) {
|
||||
return "ConfigColorForced";
|
||||
}
|
||||
if (permissions == CCS_TeamColorCustom) {
|
||||
return "TeamColorCustom";
|
||||
}
|
||||
if (permissions == CCS_ConfigColorCustom) {
|
||||
return "ConfigColorCustom";
|
||||
}
|
||||
return "DoNothing";
|
||||
}
|
||||
|
||||
protected function DefaultIt() {
|
||||
colorSetting = CCS_DoNothing;
|
||||
configuredColor = _.color.RGB(255, 255, 255);
|
||||
teamColorModifier = 0.6;
|
||||
}
|
||||
|
||||
defaultproperties {
|
||||
configName = "FutilityChat"
|
||||
colorSetting = CCS_DoNothing
|
||||
configuredColor = (R=255,G=255,B=255,A=255)
|
||||
teamColorModifier = 0.6
|
||||
}
|
||||
111
kf_sources/Futility/Classes/FutilityChat_Feature.uc
Normal file
111
kf_sources/Futility/Classes/FutilityChat_Feature.uc
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
/**
|
||||
* Config class for storing map lists.
|
||||
* Copyright 2022-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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class FutilityChat_Feature extends Feature
|
||||
dependson(FutilityChat);
|
||||
|
||||
var private /*config*/ FutilityChat.ChatColorSetting colorSetting;
|
||||
var private /*config*/ Color configuredColor;
|
||||
var private /*config*/ float teamColorModifier;
|
||||
|
||||
/// Keep track of whether we connected to necessary signals, so that we can
|
||||
/// connect to them or disconnect from them once setting get updated
|
||||
var private bool connectedToSignal;
|
||||
|
||||
protected function OnEnabled() {
|
||||
if (colorSetting != CCS_DoNothing) {
|
||||
_.chat.OnMessage(self).connect = ReformatChatMessage;
|
||||
}
|
||||
}
|
||||
|
||||
protected function OnDisabled() {
|
||||
if (colorSetting != CCS_DoNothing) {
|
||||
_.chat.OnMessage(self).Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
protected function SwapConfig(FeatureConfig config)
|
||||
{
|
||||
local bool configRequiresSignal;
|
||||
local FutilityChat newConfig;
|
||||
|
||||
newConfig = FutilityChat(config);
|
||||
if (newConfig == none) {
|
||||
return;
|
||||
}
|
||||
colorSetting = newConfig.colorSetting;
|
||||
configuredColor = newConfig.configuredColor;
|
||||
teamColorModifier = newConfig.teamColorModifier;
|
||||
configRequiresSignal = (colorSetting != CCS_DoNothing);
|
||||
}
|
||||
|
||||
private function bool ReformatChatMessage(
|
||||
EPlayer sender,
|
||||
MutableText message,
|
||||
bool teamMessage
|
||||
) {
|
||||
local Text messageCopy;
|
||||
local BaseText.Formatting defaultFormatting;
|
||||
|
||||
if (sender == none) return true;
|
||||
if (message == none) return true;
|
||||
if (colorSetting == CCS_DoNothing) return true;
|
||||
|
||||
defaultFormatting.isColored = true;
|
||||
if (colorSetting == CCS_TeamColorForced || colorSetting == CCS_TeamColorCustom) {
|
||||
defaultFormatting.color = ModColor(sender.GetTeamColor());
|
||||
} else {
|
||||
defaultFormatting.color = configuredColor;
|
||||
}
|
||||
if (message.StartsWith(P("|"))) {
|
||||
message.Remove(0, 1);
|
||||
} else if (colorSetting != CCS_TeamColorForced && colorSetting != CCS_ConfigColorForced) {
|
||||
messageCopy = message.Copy();
|
||||
class'FormattingStringParser'.static.ParseFormatted(messageCopy, message.Clear());
|
||||
_.memory.Free(messageCopy);
|
||||
}
|
||||
message.ChangeDefaultFormatting(defaultFormatting);
|
||||
return true;
|
||||
}
|
||||
|
||||
private function Color ModColor(Color inputColor) {
|
||||
local Color mixColor;
|
||||
local Color outputColor;
|
||||
local float clampedModifier;
|
||||
|
||||
if (Abs(teamColorModifier) < 0.001) {
|
||||
return inputColor;
|
||||
}
|
||||
clampedModifier = FClamp(teamColorModifier, -1.0, 1.0);
|
||||
if (clampedModifier > 0) {
|
||||
mixColor = _.color.White;
|
||||
} else {
|
||||
mixColor = _.color.Black;
|
||||
clampedModifier *= -1.0;
|
||||
}
|
||||
outputColor.R = Lerp(clampedModifier, inputColor.R, mixColor.R);
|
||||
outputColor.G = Lerp(clampedModifier, inputColor.G, mixColor.G);
|
||||
outputColor.B = Lerp(clampedModifier, inputColor.B, mixColor.B);
|
||||
outputColor.A = inputColor.A;
|
||||
return outputColor;
|
||||
}
|
||||
|
||||
defaultproperties {
|
||||
configClass = class'FutilityChat'
|
||||
}
|
||||
233
kf_sources/Futility/Classes/FutilityNicknames.uc
Normal file
233
kf_sources/Futility/Classes/FutilityNicknames.uc
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
/**
|
||||
* Config class for storing map lists.
|
||||
* Copyright 2022-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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class FutilityNicknames extends FeatureConfig
|
||||
perobjectconfig
|
||||
config(FutilityNicknames);
|
||||
|
||||
enum NicknameSpacesAction {
|
||||
NSA_DoNothing,
|
||||
NSA_Trim,
|
||||
NSA_Simplify
|
||||
};
|
||||
|
||||
enum NicknameColorPermissions {
|
||||
NCP_ForbidColor,
|
||||
NCP_ForceTeamColor,
|
||||
NCP_ForceSingleColor,
|
||||
NCP_AllowAnyColor
|
||||
};
|
||||
|
||||
/// How to treat whitespace characters inside players' nicknames:
|
||||
///
|
||||
/// * `NSA_DoNothing` - does nothing, leaving whitespaces as they are;
|
||||
/// * `NSA_Trim` - removes leading and trailing whitespaces for nicknames;
|
||||
/// * `NSA_Simplify` - removes leading and trailing whitespaces
|
||||
/// for nicknames, also reducing a sequence of whitespaces inside
|
||||
/// nickname to a single space, e.g. "my nick" becomes "my nick".
|
||||
///
|
||||
/// Default is `NSA_DoNothing`, same as on vanilla.
|
||||
var public config NicknameSpacesAction spacesAction;
|
||||
/// How to treat colored nicknames:
|
||||
///
|
||||
/// * `NCP_ForbidColor` - completely strips down any color from nicknames;
|
||||
/// * `NCP_ForceTeamColor` - forces all nicknames to have player's current
|
||||
/// team's color;
|
||||
/// * `NCP_ForceSingleColor` - allows nickname to be painted with a single
|
||||
/// color (sets nickname's color to that of the first character);
|
||||
/// * `NCP_AllowAnyColor` - allows nickname to be colored in any way player
|
||||
/// wants.
|
||||
/// Default is `NCP_ForbidColor`, same as on vanilla.
|
||||
var public config NicknameColorPermissions colorPermissions;
|
||||
/// Set this to `true` if you wish to replace all whitespace characters with
|
||||
/// underscores and `false` to leave them as is.
|
||||
/// Default is `true`, same as on vanilla. However there is one difference:
|
||||
/// Futility replaces all whitespace characters (including tabulations,
|
||||
/// non-breaking spaces, etc.) instead of only ' '.
|
||||
var public config bool replaceSpacesWithUnderscores;
|
||||
/// Set this to `true` to remove single 'quotation marks' and `false` to
|
||||
/// leave them. Default is `false`, same as on vanilla.
|
||||
var public config bool removeSingleQuotationMarks;
|
||||
/// Set this to `true` to remove dobule 'quotation marks' and `false` to
|
||||
/// leave them. Default is `true`, same as on vanilla.
|
||||
var public config bool removeDoubleQuotationMarks;
|
||||
/// Should we replace empty player nicknames with a random fallback nickname
|
||||
/// (defined in `fallbackNickname` array)?
|
||||
var public config bool correctEmptyNicknames;
|
||||
/// Max allowed nickname length. Negative values disable any length limits.
|
||||
///
|
||||
/// NOTE #1: `0` resets all nicknames to be empty and,
|
||||
/// if `correctEmptyNicknames` is set to `true`, they will be replaced with
|
||||
/// one of the fallback nicknames (see `correctEmptyNicknames` and
|
||||
/// `fallbackNickname`).
|
||||
///
|
||||
/// NOTE #2: Because of how color swapping in vanilla Killing Floor works,
|
||||
/// every color swap makes text count as being about 4 characters longer.
|
||||
/// So if one uses too many colors in the nickname, for drawing functions
|
||||
/// it will appear to be longer than it actually is and it *will* mess up
|
||||
/// UI. Unless you are using custom HUD it is recommended to keep this value
|
||||
/// at default `20` and forbid colored nicknames (by setting
|
||||
/// `colorPermissions=NCP_ForbidColor`). Or to allow only one color (by setting
|
||||
/// `colorPermissions=NCP_ForceSingleColor` or
|
||||
/// `colorPermissions=NCP_ForceTeamColor`) and reducing `maxNicknameLength` to
|
||||
/// `16` (20 characters - 4 for color swap).
|
||||
/// If you want to increase the limit above that, you can also do your own
|
||||
/// research by testing nicknames of various length on screen resolutions you
|
||||
/// care about.
|
||||
var public config int maxNicknameLength;
|
||||
/// Array of fallback nicknames that will be used to replace any empty nicknames
|
||||
/// if `correctEmptyNicknames` is set to `true`.
|
||||
var public config array<string> fallbackNickname;
|
||||
|
||||
protected function HashTable ToData() {
|
||||
local int i;
|
||||
local ArrayList fallbackNicknamesData;
|
||||
local HashTable data;
|
||||
|
||||
data = __().collections.EmptyHashTable();
|
||||
data.SetString(P("spacesAction"), string(spacesAction));
|
||||
data.SetString(P("colorPermissions"), string(colorPermissions));
|
||||
data.SetBool(P("replaceSpacesWithUnderscores"), replaceSpacesWithUnderscores);
|
||||
data.SetBool(P("removeSingleQuotationMarks"), removeSingleQuotationMarks);
|
||||
data.SetBool(P("removeDoubleQuotationMarks"), removeDoubleQuotationMarks);
|
||||
data.SetBool(P("correctEmptyNicknames"), correctEmptyNicknames);
|
||||
data.SetInt(P("maxNicknameLength"), maxNicknameLength);
|
||||
fallbackNicknamesData = __().collections.EmptyArrayList();
|
||||
|
||||
for (i = 0; i < fallbackNickname.length; i += 1) {
|
||||
fallbackNicknamesData.AddItem(__().text.FromFormattedString(fallbackNickname[i]));
|
||||
}
|
||||
data.SetItem(P("fallbackNickname"), fallbackNicknamesData);
|
||||
_.memory.Free(fallbackNicknamesData);
|
||||
return data;
|
||||
}
|
||||
|
||||
protected function FromData(HashTable source) {
|
||||
local int i;
|
||||
local Text nextNickName, storedText;
|
||||
local ArrayList fallbackNicknamesData;
|
||||
|
||||
if (source == none) {
|
||||
return;
|
||||
}
|
||||
storedText = source.GetText(P("spacesAction"));
|
||||
spacesAction = SpaceActionFromText(storedText);
|
||||
_.memory.Free(storedText);
|
||||
storedText = source.GetText(P("colorPermissions"));
|
||||
colorPermissions = ColorPermissionsFromText(storedText);
|
||||
_.memory.Free(storedText);
|
||||
replaceSpacesWithUnderscores = source.GetBool(P("replaceSpacesWithUnderscores"), true);
|
||||
removeSingleQuotationMarks = source.GetBool(P("removeSingleQuotationMarks"), true);
|
||||
removeDoubleQuotationMarks = source.GetBool(P("removeDoubleQuotationMarks"), true);
|
||||
correctEmptyNicknames = source.GetBool(P("correctEmptyNicknames"), true);
|
||||
maxNicknameLength = source.GetInt(P("correctEmptyNicknames"), 20);
|
||||
fallbackNicknamesData = source.GetArrayList(P("fallbackNickname"));
|
||||
if (fallbackNickname.length > 0) {
|
||||
fallbackNickname.length = 0;
|
||||
}
|
||||
for (i = 0; i < fallbackNicknamesData.GetLength(); i += 1) {
|
||||
nextNickName = fallbackNicknamesData.GetText(i);
|
||||
if (nextNickName != none) {
|
||||
fallbackNickname[i] = nextNickName.ToFormattedString();
|
||||
} else {
|
||||
fallbackNickname[i] = "";
|
||||
}
|
||||
_.memory.Free(nextNickName);
|
||||
}
|
||||
_.memory.Free(fallbackNicknamesData);
|
||||
}
|
||||
|
||||
private function NicknameSpacesAction SpaceActionFromText(BaseText action) {
|
||||
if (action == none) {
|
||||
return NSA_DoNothing;
|
||||
}
|
||||
if (action.EndsWith(P("DoNothing"), SCASE_INSENSITIVE)) {
|
||||
return NSA_DoNothing;
|
||||
}
|
||||
if (action.EndsWith(P("Trim"), SCASE_INSENSITIVE)) {
|
||||
return NSA_Trim;
|
||||
}
|
||||
if (action.EndsWith(P("Simplify"), SCASE_INSENSITIVE)) {
|
||||
return NSA_Simplify;
|
||||
}
|
||||
return NSA_DoNothing;
|
||||
}
|
||||
|
||||
private function NicknameColorPermissions ColorPermissionsFromText(BaseText permissions) {
|
||||
if (permissions == none) {
|
||||
return NCP_ForbidColor;
|
||||
}
|
||||
if (permissions.EndsWith(P("ForbidColor"), SCASE_INSENSITIVE)) {
|
||||
return NCP_ForbidColor;
|
||||
}
|
||||
if (permissions.EndsWith(P("TeamColor"), SCASE_INSENSITIVE)) {
|
||||
return NCP_ForceTeamColor;
|
||||
}
|
||||
if (permissions.EndsWith(P("SingleColor"), SCASE_INSENSITIVE)) {
|
||||
return NCP_ForceSingleColor;
|
||||
}
|
||||
if (permissions.EndsWith(P("AllowAnyColor"), SCASE_INSENSITIVE)) {
|
||||
return NCP_AllowAnyColor;
|
||||
}
|
||||
return NCP_ForbidColor;
|
||||
}
|
||||
|
||||
protected function DefaultIt() {
|
||||
spacesAction = NSA_DoNothing;
|
||||
colorPermissions = NCP_ForbidColor;
|
||||
replaceSpacesWithUnderscores = true;
|
||||
removeSingleQuotationMarks = false;
|
||||
removeDoubleQuotationMarks = true;
|
||||
correctEmptyNicknames = true;
|
||||
maxNicknameLength = 20;
|
||||
if (fallbackNickname.length > 0) {
|
||||
fallbackNickname.length = 0;
|
||||
}
|
||||
fallbackNickname[0] = "Fresh Meat";
|
||||
fallbackNickname[1] = "Rotten Meat";
|
||||
fallbackNickname[2] = "Troll Meat";
|
||||
fallbackNickname[3] = "Rat Meat";
|
||||
fallbackNickname[4] = "Dog Meat";
|
||||
fallbackNickname[5] = "Elk Meat";
|
||||
fallbackNickname[6] = "Crab Meat";
|
||||
fallbackNickname[7] = "Boar Meat";
|
||||
fallbackNickname[8] = "Horker Meat";
|
||||
fallbackNickname[9] = "Bug Meat";
|
||||
}
|
||||
|
||||
defaultproperties {
|
||||
configName = "FutilityNicknames"
|
||||
spacesAction = NSA_DoNothing
|
||||
colorPermissions = NCP_ForbidColor
|
||||
replaceSpacesWithUnderscores = true
|
||||
removeSingleQuotationMarks = false
|
||||
removeDoubleQuotationMarks = true
|
||||
correctEmptyNicknames = true
|
||||
maxNicknameLength = 20
|
||||
fallbackNickname(0) = "Fresh Meat"
|
||||
fallbackNickname(1) = "Rotten Meat"
|
||||
fallbackNickname(2) = "Troll Meat"
|
||||
fallbackNickname(3) = "Rat Meat"
|
||||
fallbackNickname(4) = "Dog Meat"
|
||||
fallbackNickname(5) = "Elk Meat"
|
||||
fallbackNickname(6) = "Crab Meat"
|
||||
fallbackNickname(7) = "Boar Meat"
|
||||
fallbackNickname(8) = "Horker Meat"
|
||||
fallbackNickname(9) = "Bug Meat"
|
||||
}
|
||||
267
kf_sources/Futility/Classes/FutilityNicknames_Feature.uc
Normal file
267
kf_sources/Futility/Classes/FutilityNicknames_Feature.uc
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
/**
|
||||
* Config class for storing map lists.
|
||||
* Copyright 2022-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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class FutilityNicknames_Feature extends Feature
|
||||
dependson(FutilityNicknames);
|
||||
|
||||
//! This feature's functionality is rather simple, but we will still break up
|
||||
//! what its various components are.
|
||||
//!
|
||||
//! Fallback nicknames are picked at random from
|
||||
//! the `fallbackNicknames` array. This is done by copying that array into
|
||||
//! `unusedNicknames` and then picking and removing its random elements each
|
||||
//! time we need a fallback. Once `unusedNicknames` is empty - it is copied from
|
||||
//! `fallbackNicknames` once again, letting already used nicknames to be reused.
|
||||
//! `unusedNicknames` contains same references as `fallbackNicknames`,
|
||||
//! so they need to be separately deallocated and should also be forgotten once
|
||||
//! `fallbackNicknames` are deallocated`.
|
||||
//! This is implemented inside `PickNextFallback()` method.
|
||||
//!
|
||||
//! Nickname changes are applied inside `CensorNickname()` method that uses
|
||||
//! several auxiliary methods to perform different stages of "censoring".
|
||||
//! Censoring is performed:
|
||||
//! 1. On any player's name change
|
||||
//! (using `OnPlayerNameChanging()` signal, connected to
|
||||
//! `HandleNicknameChange()`);
|
||||
//! 2. When new player logins (using `OnNewPlayer()` signal,
|
||||
//! conneted to `CensorOriginalNickname()`) to enforce our own
|
||||
//! handling of player's original nickname.
|
||||
|
||||
var private /*config*/ FutilityNicknames.NicknameSpacesAction spacesAction;
|
||||
var private /*config*/ FutilityNicknames.NicknameColorPermissions colorPermissions;
|
||||
var private /*config*/ bool replaceSpacesWithUnderscores;
|
||||
var private /*config*/ bool removeSingleQuotationMarks;
|
||||
var private /*config*/ bool removeDoubleQuotationMarks;
|
||||
var private /*config*/ int maxNicknameLength;
|
||||
var private /*config*/ bool correctEmptyNicknames;
|
||||
var private /*config*/ array<Text> fallbackNickname;
|
||||
|
||||
/// Guaranteed order of applying changes (only chosen ones) is as following:
|
||||
///
|
||||
/// 1. Trim/simplify spaces;
|
||||
/// 2. Remove single and double quotation marks;
|
||||
/// 3. Enforce max limit of nickname's length;
|
||||
/// 4. Replace empty nickname with fallback nickname (no further changes
|
||||
/// will be applied to fallback nickname in that case);
|
||||
/// 5. Enforce color limitation;
|
||||
/// 6. Replace remaining whitespaces with underscores.
|
||||
///
|
||||
/// NOTE #1: as follows from the instruction described above, no changes will
|
||||
/// ever be applied to fallback nicknames (unless player's nickname coincides
|
||||
/// with one by pure accident).
|
||||
///
|
||||
/// NOTE #2: whitespaces inside steam nicknames are converted into underscores
|
||||
/// before they are passed into the game and this is a change Futility
|
||||
/// cannot currently abort. Therefore all changes relevant to whitespaces inside
|
||||
/// nicknames will only be applied to in-game changes.
|
||||
|
||||
/// Nicknames from `fallbackNickname` that can still be picked in the current
|
||||
/// rotation.
|
||||
var private array<Text> unusedNicknames;
|
||||
|
||||
var private const int CODEPOINT_UNDERSCORE;
|
||||
|
||||
protected function OnEnabled() {
|
||||
if (IsAnyCensoringEnabled()) {
|
||||
// Do this before adding event handler to avoid censoring nicknames
|
||||
// second time (censoring nickname will trigger `OnPlayerNameChanging()`
|
||||
// signal)
|
||||
CensorCurrentPlayersNicknames();
|
||||
_.players.OnPlayerNameChanging(self).connect = HandleNicknameChange;
|
||||
_.players.OnNewPlayer(self).connect = CensorOriginalNickname;
|
||||
}
|
||||
}
|
||||
|
||||
protected function OnDisabled() {
|
||||
_.memory.FreeMany(fallbackNickname);
|
||||
_.memory.FreeMany(unusedNicknames);
|
||||
fallbackNickname.length = 0;
|
||||
unusedNicknames.length = 0;
|
||||
if (IsAnyCensoringEnabled()) {
|
||||
_.players.OnPlayerNameChanging(self).Disconnect();
|
||||
_.players.OnNewPlayer(self).Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
protected function SwapConfig(FeatureConfig config) {
|
||||
local FutilityNicknames newConfig;
|
||||
|
||||
newConfig = FutilityNicknames(config);
|
||||
if (newConfig == none) {
|
||||
return;
|
||||
}
|
||||
replaceSpacesWithUnderscores = newConfig.replaceSpacesWithUnderscores;
|
||||
removeSingleQuotationMarks = newConfig.removeSingleQuotationMarks;
|
||||
removeDoubleQuotationMarks = newConfig.removeDoubleQuotationMarks;
|
||||
correctEmptyNicknames = newConfig.correctEmptyNicknames;
|
||||
spacesAction = newConfig.spacesAction;
|
||||
colorPermissions = newConfig.colorPermissions;
|
||||
maxNicknameLength = newConfig.maxNicknameLength;
|
||||
SwapFallbackNicknames(newConfig);
|
||||
}
|
||||
|
||||
private function SwapFallbackNicknames(FutilityNicknames newConfig) {
|
||||
local int i;
|
||||
|
||||
_.memory.FreeMany(fallbackNickname);
|
||||
fallbackNickname.length = 0;
|
||||
for (i = 0; i < newConfig.fallbackNickname.length; i += 1) {
|
||||
fallbackNickname[i] = _.text.FromFormattedString(newConfig.fallbackNickname[i]);
|
||||
}
|
||||
unusedNicknames = fallbackNickname;
|
||||
}
|
||||
|
||||
private function Text PickNextFallback() {
|
||||
local int pickedIndex;
|
||||
local Text result;
|
||||
|
||||
if (fallbackNickname.length <= 0) {
|
||||
// Just in case this feature is really misconfigured
|
||||
return P("Fresh Meat").Copy();
|
||||
}
|
||||
if (unusedNicknames.length <= 0) {
|
||||
unusedNicknames = fallbackNickname;
|
||||
}
|
||||
// Pick one nickname at random.
|
||||
// `pickedIndex` will belong to [0; unusedNicknames.length - 1] segment.
|
||||
pickedIndex = Rand(unusedNicknames.length);
|
||||
result = unusedNicknames[pickedIndex].Copy();
|
||||
unusedNicknames.Remove(pickedIndex, 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
private function bool IsAnyCensoringEnabled() {
|
||||
return ( replaceSpacesWithUnderscores
|
||||
|| removeSingleQuotationMarks
|
||||
|| removeDoubleQuotationMarks
|
||||
|| correctEmptyNicknames
|
||||
|| maxNicknameLength >= 0
|
||||
|| colorPermissions != NCP_AllowAnyColor
|
||||
|| spacesAction != NSA_DoNothing);
|
||||
}
|
||||
|
||||
// For nickname changes mid-game.
|
||||
private function HandleNicknameChange(
|
||||
EPlayer affectedPlayer,
|
||||
BaseText oldName,
|
||||
MutableText newName
|
||||
) {
|
||||
CensorNickname(newName, affectedPlayer);
|
||||
}
|
||||
|
||||
// For handling of player's original nicknames.
|
||||
private function CensorOriginalNickname(EPlayer affectedPlayer) {
|
||||
local Text originalNickname;
|
||||
if (affectedPlayer == none) {
|
||||
return;
|
||||
}
|
||||
originalNickname = affectedPlayer.GetOriginalName();
|
||||
// This will automatically trigger `OnPlayerNameChanging()` signal and
|
||||
// our `HandleNicknameChange()` handler.
|
||||
affectedPlayer.SetName(originalNickname);
|
||||
_.memory.Free(originalNickname);
|
||||
}
|
||||
|
||||
// For handling nicknames of players after censoring is re-activated by
|
||||
// config change.
|
||||
private function CensorCurrentPlayersNicknames() {
|
||||
local int i;
|
||||
local Text nextNickname;
|
||||
local MutableText alteredNickname;
|
||||
local array<EPlayer> currentPlayers;
|
||||
|
||||
currentPlayers = _.players.GetAll();
|
||||
for (i = 0; i < currentPlayers.length; i += 1) {
|
||||
nextNickname = currentPlayers[i].GetName();
|
||||
alteredNickname = nextNickname.MutableCopy();
|
||||
CensorNickname(alteredNickname, currentPlayers[i]);
|
||||
if (!alteredNickname.Compare(nextNickname)) {
|
||||
currentPlayers[i].SetName(alteredNickname);
|
||||
}
|
||||
_.memory.Free(alteredNickname);
|
||||
_.memory.Free(nextNickname);
|
||||
}
|
||||
}
|
||||
|
||||
private function CensorNickname(MutableText nickname, EPlayer affectedPlayer) {
|
||||
local Text fallback;
|
||||
local BaseText.Formatting newFormatting;
|
||||
|
||||
if (nickname == none) return;
|
||||
if (affectedPlayer == none) return;
|
||||
|
||||
if (spacesAction != NSA_DoNothing) {
|
||||
nickname.Simplify(spacesAction == NSA_Simplify);
|
||||
}
|
||||
if (removeSingleQuotationMarks) {
|
||||
nickname.Replace(P("'"), P(""));
|
||||
}
|
||||
if (removeDoubleQuotationMarks) {
|
||||
nickname.Replace(P("\""), P(""));
|
||||
}
|
||||
if (maxNicknameLength >= 0) {
|
||||
nickname.Remove(maxNicknameLength);
|
||||
}
|
||||
if (correctEmptyNicknames && nickname.IsEmpty()) {
|
||||
fallback = PickNextFallback();
|
||||
nickname.Append(fallback);
|
||||
_.memory.Free(fallback);
|
||||
return;
|
||||
}
|
||||
if (colorPermissions != NCP_AllowAnyColor) {
|
||||
if (colorPermissions == NCP_ForceSingleColor) {
|
||||
newFormatting = nickname.GetCharacter(0).formatting;
|
||||
} else if (colorPermissions == NCP_ForceTeamColor) {
|
||||
newFormatting.isColored = true;
|
||||
newFormatting.color = affectedPlayer.GetTeamColor();
|
||||
}
|
||||
// `colorPermissions == NCP_ForbidColor`
|
||||
// `newFormatting` is colorless by default
|
||||
nickname.ChangeFormatting(newFormatting);
|
||||
}
|
||||
if (replaceSpacesWithUnderscores) {
|
||||
ReplaceSpaces(nickname);
|
||||
}
|
||||
}
|
||||
|
||||
// Asusmes `nickname != none`.
|
||||
private function ReplaceSpaces(MutableText nickname) {
|
||||
local int i;
|
||||
local MutableText nicknameCopy;
|
||||
local BaseText.Character nextCharacter, underscoreCharacter;
|
||||
|
||||
nicknameCopy = nickname.MutableCopy();
|
||||
nickname.Clear();
|
||||
underscoreCharacter = _.text.CharacterFromCodePoint(CODEPOINT_UNDERSCORE);
|
||||
for (i = 0; i < nicknameCopy.GetLength(); i += 1) {
|
||||
nextCharacter = nicknameCopy.GetCharacter(i);
|
||||
if (_.text.IsWhitespace(nextCharacter)) {
|
||||
// Replace character with underscore, leaving the formatting
|
||||
underscoreCharacter.formatting = nextCharacter.formatting;
|
||||
nextCharacter = underscoreCharacter;
|
||||
}
|
||||
nickname.AppendCharacter(nextCharacter);
|
||||
}
|
||||
_.memory.Free(nicknameCopy);
|
||||
}
|
||||
|
||||
defaultproperties {
|
||||
configClass = class'FutilityNicknames'
|
||||
CODEPOINT_UNDERSCORE = 95 // '_'
|
||||
}
|
||||
85
kf_sources/Futility/Classes/Futility_Feature.uc
Normal file
85
kf_sources/Futility/Classes/Futility_Feature.uc
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* This is the Futility feature, whose main purpose is to register commands
|
||||
* from its package.
|
||||
* Copyright 2021-2022 Anton Tarasenko
|
||||
*------------------------------------------------------------------------------
|
||||
* This file is part of Futility.
|
||||
*
|
||||
* Futility 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.
|
||||
*
|
||||
* Futility 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 Futility. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class Futility_Feature extends Feature;
|
||||
|
||||
var private array< class<Command> > allCommandClasses;
|
||||
|
||||
var private LoggerAPI.Definition errNoCommandsFeature;
|
||||
|
||||
protected function OnEnabled()
|
||||
{
|
||||
local int i;
|
||||
local Commands_Feature commandsFeature;
|
||||
commandsFeature =
|
||||
Commands_Feature(class'Commands_Feature'.static.GetEnabledInstance());
|
||||
if (commandsFeature == none)
|
||||
{
|
||||
_.logger.Auto(errNoCommandsFeature);
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < allCommandClasses.length; i += 1) {
|
||||
//commandsFeature.RegisterCommand(allCommandClasses[i]);
|
||||
}
|
||||
_.environment.OnFeatureEnabled(self).connect = RegisterAllCommandClasses;
|
||||
}
|
||||
|
||||
protected function OnDisabled()
|
||||
{
|
||||
local int i;
|
||||
local Commands_Feature commandsFeature;
|
||||
_.environment.OnFeatureEnabled(self).Disconnect();
|
||||
commandsFeature =
|
||||
Commands_Feature(class'Commands_Feature'.static.GetEnabledInstance());
|
||||
if (commandsFeature == none) {
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < allCommandClasses.length; i += 1) {
|
||||
//ommandsFeature.RegisterCommand(allCommandClasses[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private final function RegisterAllCommandClasses(Feature enabledFeature)
|
||||
{
|
||||
local int i;
|
||||
local Commands_Feature commandsFeature;
|
||||
commandsFeature = Commands_Feature(enabledFeature);
|
||||
if (commandsFeature == none) {
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < allCommandClasses.length; i += 1) {
|
||||
//commandsFeature.RegisterCommand(allCommandClasses[i]);
|
||||
}
|
||||
}
|
||||
|
||||
defaultproperties
|
||||
{
|
||||
configClass = class'Futility'
|
||||
allCommandClasses(0) = class'ACommandDosh'
|
||||
allCommandClasses(1) = class'ACommandNick'
|
||||
allCommandClasses(2) = class'ACommandTrader'
|
||||
allCommandClasses(3) = class'ACommandDB'
|
||||
allCommandClasses(4) = class'ACommandInventory'
|
||||
allCommandClasses(5) = class'ACommandFeature'
|
||||
allCommandClasses(6) = class'ACommandGod'
|
||||
allCommandClasses(7) = class'ACommandSpawn'
|
||||
allCommandClasses(8) = class'ACommandUserData'
|
||||
errNoCommandsFeature = (l=LOG_Error,m="`Commands_Feature` is not detected, \"Futility\" will not be able to provide its functionality.")
|
||||
}
|
||||
799
kf_sources/Futility/Classes/InventoryTool.uc
Normal file
799
kf_sources/Futility/Classes/InventoryTool.uc
Normal file
|
|
@ -0,0 +1,799 @@
|
|||
/**
|
||||
* Auxiliary object for working with player's inventory and making reports
|
||||
* about it. Simplifies code for inventory commands themselves by
|
||||
* taking care of actual item addition/removal and reporting about successes,
|
||||
* failures and inventory status.
|
||||
* This tool is supposed to be created for one player and provides wrapper
|
||||
* methods for his usual inventory methods that take care of information
|
||||
* collection about outcome of operations and then reporting on them.
|
||||
* Copyright 2022-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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class InventoryTool extends AcediaObject;
|
||||
|
||||
enum InventoryReportTarget
|
||||
{
|
||||
IRT_Instigator,
|
||||
IRT_Target,
|
||||
IRT_Others
|
||||
};
|
||||
|
||||
/**
|
||||
* Every instance of this class is created for a particular player and that
|
||||
* player cannot be changed. It allows:
|
||||
* 1. This object allows for editing player's inventory in a way that allows
|
||||
* it to produce a human-readable report about the changes.
|
||||
* Call `AddItem()`, `RemoveItem()` or `RemoveAllItems()` and then call
|
||||
* `ReportChanges()` to write changes made into the `ConsoleWriter`.
|
||||
* 2. `ReportInventory()` summarizes player's inventory.
|
||||
*/
|
||||
|
||||
// References to player (for whom this tool was created)...
|
||||
var private EPlayer targetPlayer;
|
||||
// ...and his inventory (for easy access)
|
||||
var private EInventory targetInventory;
|
||||
|
||||
/**
|
||||
* `ListBuilder`s for 6 different cases:
|
||||
* ~ two of "...Verbose" and "...Failed" ones make reports about
|
||||
* successes and failures of adding and removals to the instigator of
|
||||
* these changes;
|
||||
* ~ two other ones (`itemsAdded` and `itemsRemoved`) make reports about
|
||||
* successful changes to everybody else present on the server.
|
||||
* Supposed to be created via `CreateFor()` method.
|
||||
*/
|
||||
var public ListBuilder itemsAdded;
|
||||
var public ListBuilder itemsRemoved;
|
||||
var public ListBuilder itemsAddedPrivate;
|
||||
var public ListBuilder itemsRemovedPrivate;
|
||||
var public ListBuilder itemsAdditionFailed;
|
||||
var public ListBuilder itemsRemovalFailed;
|
||||
|
||||
var private TextTemplate templateItemsAdded, templateItemsRemoved;
|
||||
var private TextTemplate templateItemsAddedVerbose, templateItemsRemovedVerbose;
|
||||
var private TextTemplate templateAdditionFailed, templateRemovalFailed;
|
||||
|
||||
var const int TINSTIGATOR, TTARGET, TRESOLVED_INTO, TTILDE_QUOTE;
|
||||
var const int TITEM_MISSING, TITEM_NOT_REMOVABLE, TUNKNOWN, TVISIBLE;
|
||||
var const int TDISPLAYING_INVENTORY, THEADER_COLON, TDOT_SPACE, TCOLON_SPACE;
|
||||
var const int TCOMMA_SPACE, TSPACE, TOUT_OF, THIDDEN_ITEMS, TDOLLAR, TYOU;
|
||||
var const int TTHEMSELVES, TFAULTY_INVENTORY_IMPLEMENTATION;
|
||||
|
||||
public static function StaticConstructor()
|
||||
{
|
||||
if (StaticConstructorGuard()) {
|
||||
return;
|
||||
}
|
||||
default.templateItemsAdded = __().text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextPositive added} following weapons to"
|
||||
@ "%%target%%: ");
|
||||
default.templateItemsRemoved = __().text.MakeTemplate_S(
|
||||
"%%instigator%% {$TextNegative removed} following weapons from"
|
||||
@ "%%target%%: ");
|
||||
default.templateItemsAddedVerbose = __().text.MakeTemplate_S(
|
||||
"Weapons {$TextPositive added} to %%target%%: ");
|
||||
default.templateItemsRemovedVerbose = __().text.MakeTemplate_S(
|
||||
"Weapons {$TextNegative removed} from %%target%%: ");
|
||||
default.templateAdditionFailed = __().text.MakeTemplate_S(
|
||||
"Weapons we've {$TextFailure failed} to add to %%target%%: ");
|
||||
default.templateRemovalFailed = __().text.MakeTemplate_S(
|
||||
"Weapons we've {$TextFailure failed} to remove from %%target%%: " );
|
||||
}
|
||||
|
||||
protected function Constructor()
|
||||
{
|
||||
itemsAdded = ListBuilder(_.memory.Allocate(class'ListBuilder'));
|
||||
itemsRemoved = ListBuilder(_.memory.Allocate(class'ListBuilder'));
|
||||
itemsAddedPrivate = ListBuilder(_.memory.Allocate(class'ListBuilder'));
|
||||
itemsRemovedPrivate = ListBuilder(_.memory.Allocate(class'ListBuilder'));
|
||||
itemsAdditionFailed = ListBuilder(_.memory.Allocate(class'ListBuilder'));
|
||||
itemsRemovalFailed = ListBuilder(_.memory.Allocate(class'ListBuilder'));
|
||||
}
|
||||
|
||||
protected function Finalizer()
|
||||
{
|
||||
// Deallocate report tools
|
||||
_.memory.Free(itemsAdded);
|
||||
_.memory.Free(itemsRemoved);
|
||||
_.memory.Free(itemsAddedPrivate);
|
||||
_.memory.Free(itemsRemovedPrivate);
|
||||
_.memory.Free(itemsAdditionFailed);
|
||||
_.memory.Free(itemsRemovalFailed);
|
||||
itemsAdded = none;
|
||||
itemsRemoved = none;
|
||||
itemsAddedPrivate = none;
|
||||
itemsRemovedPrivate = none;
|
||||
itemsAdditionFailed = none;
|
||||
itemsRemovalFailed = none;
|
||||
// Deallocate player references
|
||||
_.memory.Free(targetPlayer);
|
||||
_.memory.Free(targetInventory);
|
||||
targetPlayer = none;
|
||||
targetInventory = none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new `InventoryTool` instance for a given player `target`.
|
||||
*
|
||||
* @param target Player for which to create new `InventoryTool`.
|
||||
* @return `InventoryTool` created for the given player - not a copy of any
|
||||
* preexisting instance. `none` iff `target == none` or refers to
|
||||
* a non-existent player.
|
||||
*/
|
||||
public static final function InventoryTool CreateFor(EPlayer target)
|
||||
{
|
||||
local InventoryTool newInventoryTool;
|
||||
|
||||
if (target == none) return none;
|
||||
if (!target.IsExistent()) return none;
|
||||
|
||||
newInventoryTool =
|
||||
InventoryTool(__().memory.Allocate(class'InventoryTool'));
|
||||
if (target != none) {
|
||||
newInventoryTool.targetPlayer = target;
|
||||
target.NewRef();
|
||||
}
|
||||
newInventoryTool.targetInventory = target.GetInventory();
|
||||
return newInventoryTool;
|
||||
}
|
||||
|
||||
// Checks whether reference to the `EPlayer` that caller `InventoryTool` was
|
||||
// created for is still valid.
|
||||
private final function bool TargetPlayerIsInvalid()
|
||||
{
|
||||
if (targetPlayer == none) return true;
|
||||
if (!targetPlayer.IsExistent()) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets `InventoryTool`, forgetting about changes made with it so far.
|
||||
*/
|
||||
public final function Reset()
|
||||
{
|
||||
itemsAddedPrivate.Reset();
|
||||
itemsRemovedPrivate.Reset();
|
||||
itemsAdded.Reset();
|
||||
itemsRemoved.Reset();
|
||||
itemsAdditionFailed.Reset();
|
||||
itemsRemovalFailed.Reset();
|
||||
}
|
||||
|
||||
// Makes "`resolvedWhat` resolved into `intoWhat`" line
|
||||
// In case `resolvedWhat == intoWhat` just returns copy of
|
||||
// original `resolvedWhat`
|
||||
private function MutableText MakeResolvedIntoLine(
|
||||
BaseText resolvedWhat,
|
||||
BaseText intoWhat)
|
||||
{
|
||||
if (resolvedWhat == none) {
|
||||
return none;
|
||||
}
|
||||
if (_.text.IsEmpty(intoWhat) || resolvedWhat.Compare(intoWhat)) {
|
||||
return resolvedWhat.MutableCopy();
|
||||
}
|
||||
return _.text.Empty()
|
||||
.Append(T(TTILDE_QUOTE))
|
||||
.Append(resolvedWhat)
|
||||
.Append(T(TRESOLVED_INTO))
|
||||
.Append(intoWhat)
|
||||
.Append(T(TTILDE_QUOTE));
|
||||
}
|
||||
|
||||
// Tries to fill ammo for the `item` in case it is a weapon
|
||||
private function TryFillAmmo(EItem item)
|
||||
{
|
||||
local EWeapon itemAsWeapon;
|
||||
|
||||
if (item == none) {
|
||||
return;
|
||||
}
|
||||
itemAsWeapon = EWeapon(item.As(class'EWeapon'));
|
||||
if (itemAsWeapon != none)
|
||||
{
|
||||
itemAsWeapon.FillAmmo();
|
||||
_.memory.Free(itemAsWeapon);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new item, based on user provided name `userProvidedName`.
|
||||
*
|
||||
* @param userProvidedName Name of the inventory, provided by the user.
|
||||
* If it is started with "$", then tool tried to treat it as
|
||||
* an alias first. If it either does not start with "$" or does not
|
||||
* correspond to a valid alias - it is treated as a template.
|
||||
* @param doForce Set to `true` if we must try to add an item
|
||||
* even if it normally cannot be added.
|
||||
* @param doFillAmmo Set to `true` if we must also fill ammo reserves
|
||||
* of weapons we have added to the full.
|
||||
*/
|
||||
public function AddItem(
|
||||
BaseText userProvidedName,
|
||||
bool doForce,
|
||||
bool doFillAmmo)
|
||||
{
|
||||
local EItem addedItem;
|
||||
local MutableText resolvedLine;
|
||||
local Text realItemName, itemTemplate, failureReason;
|
||||
|
||||
if (TargetPlayerIsInvalid()) return;
|
||||
if (userProvidedName == none) return;
|
||||
|
||||
// Get template in case alias was specified
|
||||
// (`itemTemplate` cannot be `none`, since `userProvidedName != none`)
|
||||
if (userProvidedName.StartsWith(T(TDOLLAR))) {
|
||||
itemTemplate = _.alias.ResolveWeapon(userProvidedName, true);
|
||||
}
|
||||
else {
|
||||
itemTemplate = userProvidedName.Copy();
|
||||
}
|
||||
// The only way we can fail in a valid way is when API says we will
|
||||
// via `CanAddTemplateExplain()`
|
||||
failureReason = targetInventory
|
||||
.CanAddTemplateExplain(itemTemplate, doForce);
|
||||
if (failureReason != none)
|
||||
{
|
||||
itemsAdditionFailed.Item(userProvidedName).Comment(failureReason);
|
||||
_.memory.Free(failureReason);
|
||||
_.memory.Free(itemTemplate);
|
||||
return;
|
||||
}
|
||||
// Actually try to add specified item
|
||||
addedItem = targetInventory.AddTemplate(itemTemplate, doForce);
|
||||
if (addedItem != none)
|
||||
{
|
||||
if (doFillAmmo) {
|
||||
TryFillAmmo(addedItem);
|
||||
}
|
||||
realItemName = addedItem.GetName();
|
||||
resolvedLine = MakeResolvedIntoLine(userProvidedName, itemTemplate);
|
||||
itemsAdded.Item(realItemName);
|
||||
itemsAddedPrivate.Item(realItemName).Comment(resolvedLine);
|
||||
_.memory.Free(realItemName);
|
||||
_.memory.Free(resolvedLine);
|
||||
_.memory.Free(addedItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
// `CanAddTemplateExplain()` told us that we should not have failed,
|
||||
// so complain about bad API
|
||||
itemsAdditionFailed.Item(userProvidedName)
|
||||
.Comment(T(TFAULTY_INVENTORY_IMPLEMENTATION));
|
||||
}
|
||||
_.memory.Free(itemTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a specified item, based on user provided name `userProvidedName`.
|
||||
*
|
||||
* @param userProvidedName Name of inventory, provided by the user.
|
||||
* If it is started with "$", then tool tried to treat it as
|
||||
* an alias first. If it either does not start with "$" or does not
|
||||
* correspond to a valid alias - it is treated as a template.
|
||||
* @param doKeep Set to `true` if item should be preserved
|
||||
* (or, at least, attempted to be preserved) and not simply destroyed.
|
||||
* @param doForce Set to `true` if we must try to remove an item
|
||||
* even if it normally cannot be removed.
|
||||
* @param doRemoveAll Set to `true` to remove all instances of given
|
||||
* template and `false` to only remove one.
|
||||
*/
|
||||
public function RemoveItem(
|
||||
BaseText userProvidedName,
|
||||
bool doKeep,
|
||||
bool doForce,
|
||||
bool doRemoveAll)
|
||||
{
|
||||
local bool itemWasMissing;
|
||||
local Text realItemName, itemTemplate;
|
||||
local MutableText resolvedLine;
|
||||
local EItem storedItem;
|
||||
|
||||
if (TargetPlayerIsInvalid()) return;
|
||||
if (userProvidedName == none) return;
|
||||
|
||||
// Get template in case alias was specified
|
||||
// (`itemTemplate` cannot be `none`, since `userProvidedName != none`)
|
||||
if (userProvidedName.StartsWith(T(TDOLLAR))) {
|
||||
itemTemplate = _.alias.ResolveWeapon(userProvidedName, true);
|
||||
}
|
||||
else {
|
||||
itemTemplate = userProvidedName.Copy();
|
||||
}
|
||||
// Check if item is even in the inventory
|
||||
storedItem = targetInventory.GetTemplateItem(itemTemplate);
|
||||
if (storedItem == none)
|
||||
{
|
||||
// If not, we still need to attempt to remove it, as it can be
|
||||
// "merged" into another item
|
||||
itemWasMissing = true;
|
||||
realItemName = P("").Copy();
|
||||
}
|
||||
else {
|
||||
// Need to remember the name before removing the item
|
||||
realItemName = storedItem.GetName();
|
||||
}
|
||||
if (targetInventory
|
||||
.RemoveTemplate(itemTemplate, doKeep, doForce, doRemoveAll))
|
||||
{
|
||||
resolvedLine = MakeResolvedIntoLine(userProvidedName, itemTemplate);
|
||||
itemsRemoved.Item(realItemName);
|
||||
itemsRemovedPrivate.Item(realItemName).Comment(resolvedLine);
|
||||
_.memory.Free(resolvedLine);
|
||||
}
|
||||
// Try to guess why operation failed
|
||||
// (no special explanation method is present in the API)
|
||||
else if (itemWasMissing) { // likely because it was missing
|
||||
itemsRemovalFailed.Item(userProvidedName).Comment(T(TITEM_MISSING));
|
||||
}
|
||||
else if (!doForce && !storedItem.IsRemovable()) // simply was not removable
|
||||
{
|
||||
itemsRemovalFailed.Item(userProvidedName)
|
||||
.Comment(T(TITEM_NOT_REMOVABLE));
|
||||
}
|
||||
else { // no idea about the reason
|
||||
itemsRemovalFailed.Item(userProvidedName).Comment(T(TUNKNOWN));
|
||||
}
|
||||
_.memory.Free(storedItem);
|
||||
_.memory.Free(realItemName);
|
||||
_.memory.Free(itemTemplate);
|
||||
}
|
||||
|
||||
// Auxiliary method for detecting and reporting about removed items by
|
||||
/// comparing lists of `EItem` interfeaces created beofer and after removal
|
||||
private function DetectAndReportRemovedItems(
|
||||
out array<EItem> itemsAfterRemoval,
|
||||
array<EItem> itemsBeforeRemoval,
|
||||
array<BaseText> itemNames,
|
||||
bool doForce)
|
||||
{
|
||||
local int i, j;
|
||||
local bool itemWasRemoved;
|
||||
|
||||
for (i = 0; i < itemsBeforeRemoval.length; i += 1)
|
||||
{
|
||||
itemWasRemoved = true;
|
||||
// If item was not destroyed - double check whether it got removed
|
||||
if (itemsBeforeRemoval[i].IsExistent())
|
||||
{
|
||||
for (j = 0; j < itemsAfterRemoval.length; j += 1)
|
||||
{
|
||||
if (itemsBeforeRemoval[i].SameAs(itemsAfterRemoval[j]))
|
||||
{
|
||||
_.memory.Free(itemsAfterRemoval[j]);
|
||||
itemsAfterRemoval.Remove(j, 1);
|
||||
itemWasRemoved = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (itemWasRemoved)
|
||||
{
|
||||
itemsRemoved.Item(itemNames[i]);
|
||||
itemsRemovedPrivate.Item(itemNames[i]);
|
||||
}
|
||||
else if (doForce || itemsBeforeRemoval[i].IsRemovable()) {
|
||||
itemsRemovalFailed.Item(itemNames[i]).Comment(T(TUNKNOWN));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all items from the player's inventory.
|
||||
*
|
||||
* @param doKeep Set to `true` if items should be preserved
|
||||
* (or, at least, attempted to be preserved) and not simply destroyed.
|
||||
* @param doForce Set to `true` if we must try to remove an item
|
||||
* even if it normally cannot be removed.
|
||||
* @param includeHidden Set to `true` if "hidden" items should also be
|
||||
* targeted by this method. These are items player cannot directly see in
|
||||
* their inventory, usually serving some sort of technical role.
|
||||
*/
|
||||
public function RemoveAllItems(bool doKeep, bool doForce, bool includeHidden)
|
||||
{
|
||||
local int i;
|
||||
local array<Text> itemNames;
|
||||
local array<EItem> itemsBeforeRemoval, itemsAfterRemoval;
|
||||
|
||||
if (TargetPlayerIsInvalid()) {
|
||||
return;
|
||||
}
|
||||
// Remove all items!
|
||||
// Remember what items we have had before to output them and
|
||||
// what items we have after removal to detect what we have actually
|
||||
// removed.
|
||||
// This is necessary, since (to an extent depending on flags)
|
||||
// some items might not be removable.
|
||||
if (includeHidden) {
|
||||
itemsBeforeRemoval = targetInventory.GetAllItems();
|
||||
}
|
||||
else {
|
||||
itemsBeforeRemoval = targetInventory.GetTagItems(T(TVISIBLE));
|
||||
}
|
||||
for (i = 0; i < itemsBeforeRemoval.length; i += 1) {
|
||||
itemNames[i] = itemsBeforeRemoval[i].GetName();
|
||||
}
|
||||
targetInventory.RemoveAll(doKeep, doForce, includeHidden);
|
||||
itemsAfterRemoval = targetInventory.GetAllItems();
|
||||
// Figure out what items are actually gone and report about them
|
||||
DetectAndReportRemovedItems( itemsAfterRemoval,
|
||||
itemsBeforeRemoval, itemNames,
|
||||
doForce);
|
||||
_.memory.FreeMany(itemNames);
|
||||
_.memory.FreeMany(itemsBeforeRemoval);
|
||||
_.memory.FreeMany(itemsAfterRemoval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all equipped items from the player's inventory.
|
||||
*
|
||||
* @param doKeep Set to `true` if items should be preserved
|
||||
* (or, at least, attempted to be preserved) and not simply destroyed.
|
||||
* @param doForce Set to `true` if we must try to remove an item
|
||||
* even if it normally cannot be removed.
|
||||
* @param includeHidden Set to `true` if "hidden" items should also be
|
||||
* targeted by this method. These are items player cannot directly see in
|
||||
* their inventory, usually serving some sort of technical role.
|
||||
*/
|
||||
public function RemoveEquippedItems(
|
||||
bool doKeep,
|
||||
bool doForce,
|
||||
bool includeHidden)
|
||||
{
|
||||
local int i;
|
||||
local EItem nextItem;
|
||||
local Text nextItemName;
|
||||
local array<EItem> equippedItems;
|
||||
|
||||
if (TargetPlayerIsInvalid()) {
|
||||
return;
|
||||
}
|
||||
equippedItems = targetInventory.GetEquippedItems();
|
||||
for (i = 0; i < equippedItems.length; i += 1)
|
||||
{
|
||||
nextItem = equippedItems[i];
|
||||
if (!nextItem.IsExistent()) continue;
|
||||
if (!includeHidden && !nextItem.HasTag(T(TVISIBLE))) continue;
|
||||
|
||||
nextItemName = nextItem.GetName();
|
||||
// Try to guess the reason we cannot remove the item
|
||||
if (!doForce && !nextItem.IsRemovable())
|
||||
{
|
||||
itemsRemovalFailed
|
||||
.Item(nextItemName)
|
||||
.Comment(T(TITEM_NOT_REMOVABLE));
|
||||
}
|
||||
else if (!targetInventory.Remove(nextItem, doKeep, doForce))
|
||||
{
|
||||
itemsRemovalFailed
|
||||
.Item(nextItemName)
|
||||
.Comment(T(TUNKNOWN));
|
||||
}
|
||||
_.memory.Free(nextItemName);
|
||||
nextItemName = none;
|
||||
}
|
||||
_.memory.FreeMany(equippedItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells `InventoryTool` which player is responsible for the changes it is
|
||||
* reporting. This information is used to choose the phrasing of the reported
|
||||
* messages.
|
||||
*
|
||||
* @param instigator Player that supposedly requested all the changes done by
|
||||
* the calller `InventoryTool`.
|
||||
*/
|
||||
public final function SetupReportInstigator(EPlayer instigator)
|
||||
{
|
||||
local MutableText instigatorName, targetName;
|
||||
|
||||
if (TargetPlayerIsInvalid()) return;
|
||||
if (instigator == none) return;
|
||||
|
||||
instigatorName = ColorNickname(instigator.GetName());
|
||||
if (!targetPlayer.SameAs(instigator)) {
|
||||
targetName = ColorNickname(targetPlayer.GetName());
|
||||
}
|
||||
else {
|
||||
targetName = T(TYOU).MutableCopy();
|
||||
}
|
||||
// For instigator
|
||||
default.templateItemsAdded.Reset().TextArg(T(TINSTIGATOR), instigatorName);
|
||||
default.templateItemsRemoved
|
||||
.Reset()
|
||||
.TextArg(T(TINSTIGATOR), instigatorName);
|
||||
// For everybody else
|
||||
default.templateAdditionFailed.Reset().TextArg(T(TTARGET), targetName);
|
||||
default.templateRemovalFailed.Reset().TextArg(T(TTARGET), targetName);
|
||||
default.templateItemsAddedVerbose.Reset().TextArg(T(TTARGET), targetName);
|
||||
default.templateItemsRemovedVerbose.Reset().TextArg(T(TTARGET), targetName);
|
||||
_.memory.Free(instigatorName);
|
||||
_.memory.Free(targetName);
|
||||
}
|
||||
|
||||
private final function MutableText ColorNickname(/* take */ BaseText nickname)
|
||||
{
|
||||
if (nickname == none) {
|
||||
return none;
|
||||
}
|
||||
return nickname
|
||||
.IntoMutableText()
|
||||
.ChangeDefaultColor(_.color.LightGray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports changes made to the player's inventory so far.
|
||||
*
|
||||
* Ability to provide this reports is pretty much the main reason for
|
||||
* using `InventoryTool`
|
||||
* @param blamedPlayer Player that should be listed as the one who caused
|
||||
* the changes.
|
||||
* @param writer `ConsoleWriter` that will be used to output report.
|
||||
* Method does nothing if given `writer` is `none`.
|
||||
* @param reportTarget For who is this report meant to? For general public
|
||||
* and target only actually occured changes are reported (with different
|
||||
* phrasing), but for instigator changes that tool failed to do will also
|
||||
* be reported.
|
||||
*/
|
||||
public final function ReportChanges(
|
||||
EPlayer instigator,
|
||||
ConsoleWriter writer,
|
||||
InventoryReportTarget reportTarget)
|
||||
{
|
||||
if (TargetPlayerIsInvalid()) {
|
||||
return;
|
||||
}
|
||||
if (reportTarget != IRT_Instigator)
|
||||
{
|
||||
SwapTargetNameInTemplates(instigator, reportTarget);
|
||||
ReportWeaponList(writer, default.templateItemsRemoved, itemsRemoved);
|
||||
ReportWeaponList(writer, default.templateItemsAdded, itemsAdded);
|
||||
return;
|
||||
}
|
||||
ReportWeaponList(
|
||||
writer,
|
||||
default.templateItemsRemovedVerbose,
|
||||
itemsRemovedPrivate);
|
||||
ReportWeaponList(
|
||||
writer,
|
||||
default.templateRemovalFailed,
|
||||
itemsRemovalFailed);
|
||||
ReportWeaponList(
|
||||
writer,
|
||||
default.templateItemsAddedVerbose,
|
||||
itemsAddedPrivate);
|
||||
ReportWeaponList(
|
||||
writer,
|
||||
default.templateAdditionFailed,
|
||||
itemsAdditionFailed);
|
||||
}
|
||||
|
||||
private final function SwapTargetNameInTemplates(
|
||||
EPlayer instigator,
|
||||
InventoryReportTarget reportTarget)
|
||||
{
|
||||
local MutableText targetName;
|
||||
|
||||
if (TargetPlayerIsInvalid()) {
|
||||
return;
|
||||
}
|
||||
if (!targetPlayer.SameAs(instigator)) {
|
||||
targetName = ColorNickname(targetPlayer.GetName());
|
||||
}
|
||||
else if (reportTarget == IRT_Target) {
|
||||
targetName = T(TYOU).MutableCopy();
|
||||
}
|
||||
else {
|
||||
targetName = T(TTHEMSELVES).MutableCopy();
|
||||
}
|
||||
default.templateItemsAdded.TextArg(T(TTARGET), targetName);
|
||||
default.templateItemsRemoved.TextArg(T(TTARGET), targetName);
|
||||
_.memory.Free(targetName);
|
||||
}
|
||||
|
||||
private final function ReportWeaponList(
|
||||
ConsoleWriter writer,
|
||||
TextTemplate header,
|
||||
ListBuilder builder)
|
||||
{
|
||||
local MutableText output;
|
||||
|
||||
if (writer == none) return;
|
||||
if (builder == none) return;
|
||||
if (builder.IsEmpty()) return;
|
||||
|
||||
if (header != none)
|
||||
{
|
||||
output = header.CollectFormattedMutable();
|
||||
writer.Write(output);
|
||||
_.memory.Free(output);
|
||||
output = none;
|
||||
}
|
||||
output = builder.GetMutable();
|
||||
writer.WriteLine(output);
|
||||
_.memory.Free(output);
|
||||
}
|
||||
|
||||
// TODO: Use `ListBuilder` for the below method?
|
||||
/**
|
||||
* Command that outputs summary of the player's inventory.
|
||||
*
|
||||
* @param writer `ConsoleWriter` into which to output information.
|
||||
* Method does nothing if given `writer` is `none`.
|
||||
* @param includeHidden Set to `true` if "hidden" items should also be
|
||||
* targeted by this method. These are items player cannot directly see in
|
||||
* their inventory, usually serving some sort of technical role.
|
||||
*/
|
||||
public final function ReportInventory(ConsoleWriter writer, bool includeHidden)
|
||||
{
|
||||
local int i;
|
||||
local int lineCounter;
|
||||
local array<EItem> availableItems;
|
||||
local Text playerName;
|
||||
|
||||
if (writer == none) return;
|
||||
if (TargetPlayerIsInvalid()) return;
|
||||
|
||||
playerName = targetPlayer.GetName();
|
||||
writer.Flush()
|
||||
.Write(T(TDISPLAYING_INVENTORY))
|
||||
.UseColorOnce(_.color.White).Write(playerName)
|
||||
.Write(T(THEADER_COLON)).Flush();
|
||||
lineCounter = 1;
|
||||
availableItems = targetInventory.GetAllItems();
|
||||
// First show visible items
|
||||
for (i = 0; i < availableItems.length; i += 1)
|
||||
{
|
||||
if (availableItems[i].HasTag(T(TVISIBLE)))
|
||||
{
|
||||
AppendItemInfo(writer, availableItems[i], lineCounter);
|
||||
lineCounter += 1;
|
||||
}
|
||||
}
|
||||
// Once more pass for non-visible items, to display them at the end
|
||||
if (includeHidden)
|
||||
{
|
||||
writer.Write(T(THIDDEN_ITEMS)).Flush();
|
||||
for (i = 0; i < availableItems.length; i += 1)
|
||||
{
|
||||
if (!availableItems[i].HasTag(T(TVISIBLE)))
|
||||
{
|
||||
AppendItemInfo(writer, availableItems[i], lineCounter);
|
||||
lineCounter += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
_.memory.Free(playerName);
|
||||
_.memory.FreeMany(availableItems);
|
||||
}
|
||||
|
||||
private final function AppendItemInfo(
|
||||
ConsoleWriter writer,
|
||||
EItem item,
|
||||
int lineNumber)
|
||||
{
|
||||
local Text itemName;
|
||||
local Text lineNumberAsText;
|
||||
local EWeapon itemAsWeapon;
|
||||
local Mutabletext allAmmoInfo;
|
||||
|
||||
if (writer == none) return;
|
||||
if (item == none) return;
|
||||
|
||||
itemName = item.GetName();
|
||||
lineNumberAsText = _.text.FromInt(lineNumber);
|
||||
writer.Write(lineNumberAsText)
|
||||
.Write(T(TDOT_SPACE))
|
||||
.UseColorOnce(_.color.TextEmphasis).Write(itemName);
|
||||
// Try to display additional ammo info if this is a weapon
|
||||
itemAsWeapon = EWeapon(item.As(class'EWeapon'));
|
||||
if (itemAsWeapon != none)
|
||||
{
|
||||
allAmmoInfo = DisplayAllAmmoInfo(itemAsWeapon);
|
||||
if (allAmmoInfo != none) {
|
||||
writer.Write(T(TCOLON_SPACE)).Write(allAmmoInfo);
|
||||
}
|
||||
_.memory.Free(itemAsWeapon);
|
||||
_.memory.Free(allAmmoInfo);
|
||||
}
|
||||
writer.Flush();
|
||||
_.memory.Free(itemName);
|
||||
_.memory.Free(lineNumberAsText);
|
||||
}
|
||||
|
||||
private final function MutableText DisplayAllAmmoInfo(EWeapon weapon)
|
||||
{
|
||||
local int i;
|
||||
local array<EAmmo> allAmmo;
|
||||
local MutableText builder;
|
||||
|
||||
allAmmo = weapon.GetAvailableAmmo();
|
||||
if (allAmmo.length == 0) {
|
||||
return none;
|
||||
}
|
||||
builder = _.text.Empty();
|
||||
for (i = 0; i < allAmmo.length; i += 1)
|
||||
{
|
||||
if (i > 0) {
|
||||
builder.Append(T(TCOMMA_SPACE));
|
||||
}
|
||||
AppendAmmoInstanceInfo(builder, allAmmo[i]);
|
||||
}
|
||||
_.memory.FreeMany(allAmmo);
|
||||
return builder;
|
||||
}
|
||||
|
||||
private final function AppendAmmoInstanceInfo(MutableText builder, EAmmo ammo)
|
||||
{
|
||||
local Text ammoName;
|
||||
|
||||
if (ammo == none) {
|
||||
return;
|
||||
}
|
||||
ammoName = ammo.GetName();
|
||||
builder.AppendString( string(ammo.GetTotalAmount()),
|
||||
_.text.FormattingFromColor(_.color.TypeNumber))
|
||||
.Append(T(TSPACE)).Append(ammoName).Append(T(TOUT_OF))
|
||||
.AppendString( string(ammo.GetMaxTotalAmount()),
|
||||
_.text.FormattingFromColor(_.color.TypeNumber));
|
||||
_.memory.Free(ammoName);
|
||||
}
|
||||
|
||||
defaultproperties
|
||||
{
|
||||
TINSTIGATOR = 0
|
||||
stringConstants(0) = "instigator"
|
||||
TTARGET = 1
|
||||
stringConstants(1) = "target"
|
||||
TRESOLVED_INTO = 2
|
||||
stringConstants(2) = "` resolved into `"
|
||||
TTILDE_QUOTE = 3
|
||||
stringConstants(3) = "`"
|
||||
TFAULTY_INVENTORY_IMPLEMENTATION = 4
|
||||
stringConstants(4) = "faulty inventory implementation"
|
||||
TITEM_MISSING = 5
|
||||
stringConstants(5) = "item missing"
|
||||
TITEM_NOT_REMOVABLE = 6
|
||||
stringConstants(6) = "item not removable"
|
||||
TUNKNOWN = 7
|
||||
stringConstants(7) = "unknown"
|
||||
TVISIBLE = 8
|
||||
stringConstants(8) = "visible"
|
||||
TDISPLAYING_INVENTORY = 9
|
||||
stringConstants(9) = "{$TextHeader Displaying inventory for player }"
|
||||
THEADER_COLON = 10
|
||||
stringConstants(10) = "{$TextHeader :}"
|
||||
TDOT_SPACE = 11
|
||||
stringConstants(11) = ". "
|
||||
TCOLON_SPACE = 12
|
||||
stringConstants(12) = ": "
|
||||
TCOMMA_SPACE = 13
|
||||
stringConstants(13) = ", "
|
||||
TSPACE = 14
|
||||
stringConstants(14) = " "
|
||||
TOUT_OF = 15
|
||||
stringConstants(15) = " out of "
|
||||
THIDDEN_ITEMS = 16
|
||||
stringConstants(16) = "{$TextSubHeader Hidden items:}"
|
||||
TDOLLAR = 17
|
||||
stringConstants(17) = "$"
|
||||
TYOU = 18
|
||||
stringConstants(18) = "you"
|
||||
TTHEMSELVES = 19
|
||||
stringConstants(19) = "themselves"
|
||||
}
|
||||
251
kf_sources/Futility/Classes/ListBuilder.uc
Normal file
251
kf_sources/Futility/Classes/ListBuilder.uc
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
/**
|
||||
* Many different Futility's commands need to output lists of items
|
||||
* separated by commas, each item possibly containing comments in
|
||||
* the parentheses (also separated by commas). This class provides necessary
|
||||
* functionality.
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class ListBuilder extends AcediaObject;
|
||||
|
||||
/**
|
||||
* # `ListBuilder`
|
||||
*
|
||||
* Many different Futility's commands need to output lists of items
|
||||
* separated by commas, each item possibly containing comments in
|
||||
* the parentheses (also separated by commas). This class provides necessary
|
||||
* functionality.
|
||||
* Example of such list:
|
||||
* "item1, item2 (comment1, comment2), item3 (just_comment), item4, item5".
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* 1. Use `Item()` method to add new items (they will be listed after
|
||||
* list header + whitespace, separated by commas and whitespaces ", ");
|
||||
* 2. Use `Comment()` method to specify comments for the item (they will
|
||||
* be listed between the paranthesisasd after the corresponding item).
|
||||
* Comments will be added to the last item, added via `Item()` call.
|
||||
* If no items were added, specified comment will be discarded.
|
||||
* 3. Use `Get()` / `GetMutable()` methods to return list built so far.
|
||||
* 4. Use `Reset()` to forget all the items and comments
|
||||
* (but not list header), allowing to start forming a new report.
|
||||
*/
|
||||
|
||||
// Represents one item + all of its comments
|
||||
struct FutilityListItem
|
||||
{
|
||||
var Text itemTitle;
|
||||
var array<Text> comments;
|
||||
};
|
||||
// All items recorded reported thus far
|
||||
var private array<FutilityListItem> collectedItems;
|
||||
|
||||
var const int TCAUSE, TTARGET, TCOMMA, TSPACE, TCOMMA_SPACE;
|
||||
var const int TSPACE_OPEN_PARANSIS, TCLOSE_PARANSIS;
|
||||
|
||||
protected function Finalizer()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if caller `ListBuilder` already has any items added.
|
||||
*
|
||||
* @return `true` if caller `ListBuilder` doesn't have any items added and
|
||||
* `false` if it has at least one.
|
||||
*/
|
||||
public final function bool IsEmpty()
|
||||
{
|
||||
return (collectedItems.length <= 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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` (`Comment()` will continue adding
|
||||
* comments to the previously added item).
|
||||
* @return Reference to the caller `ListBuilder` to allow for method chaining.
|
||||
*/
|
||||
public final function ListBuilder Item(BaseText item)
|
||||
{
|
||||
local FutilityListItem newItem;
|
||||
|
||||
if (item == none) {
|
||||
return self;
|
||||
}
|
||||
newItem.itemTitle = item.Copy();
|
||||
collectedItems[collectedItems.length] = newItem;
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new `comment` to the last added `item` in the current report.
|
||||
*
|
||||
* @param comment Text to be included into the report as a comment to
|
||||
* the last added item. One should avoid using commas or parantheses inside
|
||||
* a `comment`, but this limitation is not checked or prevented by
|
||||
* `Comment()` method.
|
||||
* Does nothing if `comment == none` or no items were added thuis far.
|
||||
* @return Reference to the caller `ListBuilder` to allow for method chaining.
|
||||
*/
|
||||
public final function ListBuilder Comment(BaseText comment)
|
||||
{
|
||||
local array<Text> itemComments;
|
||||
|
||||
if (comment == none) return self;
|
||||
if (collectedItems.length == 0) return self;
|
||||
|
||||
itemComments = collectedItems[collectedItems.length - 1].comments;
|
||||
itemComments[itemComments.length] = comment.Copy();
|
||||
collectedItems[collectedItems.length - 1].comments = itemComments;
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list, assembled from items and their comment specified so far as
|
||||
* `Text`.
|
||||
*
|
||||
* @see `GetMutable()`, `IntoText()`, `IntoMutableText()`
|
||||
*
|
||||
* @return Assembled list of specified items with specified comments.
|
||||
*/
|
||||
public final function Text Get()
|
||||
{
|
||||
local MutableText mutableResult;
|
||||
|
||||
mutableResult = GetMutable();
|
||||
if (mutableResult != none) {
|
||||
return mutableResult.IntoText();
|
||||
}
|
||||
return none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list, assembled from items and their comment specified so far as
|
||||
* `MutableText`.
|
||||
*
|
||||
* @see `Get()`, `IntoText()`, `IntoMutableText()`
|
||||
*
|
||||
* @return Assembled list of specified items with specified comments.
|
||||
*/
|
||||
public final function MutableText GetMutable()
|
||||
{
|
||||
local int i, j;
|
||||
local MutableText result;
|
||||
local array<Text> itemComments;
|
||||
|
||||
if (collectedItems.length == 0) {
|
||||
return _.text.Empty();
|
||||
}
|
||||
result = _.text.Empty();
|
||||
for (i = 0; i < collectedItems.length; i += 1)
|
||||
{
|
||||
if (i > 0) {
|
||||
result.Append(T(TCOMMA_SPACE));
|
||||
}
|
||||
result.Append(collectedItems[i].itemTitle);
|
||||
itemComments = collectedItems[i].comments;
|
||||
if (itemComments.length > 0) {
|
||||
result.Append(T(TSPACE_OPEN_PARANSIS));
|
||||
}
|
||||
for (j = 0; j < itemComments.length; j += 1)
|
||||
{
|
||||
if (j > 0) {
|
||||
result.Append(T(TCOMMA_SPACE));
|
||||
}
|
||||
result.Append(itemComments[j]);
|
||||
}
|
||||
if (itemComments.length > 0) {
|
||||
result.Append(T(TCLOSE_PARANSIS));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts caller `Listbuilder` into list, assembled from items and their
|
||||
* comment specified so far as `Text`.
|
||||
* Caller `ListBuilder()` is atuomatically deallocated.
|
||||
*
|
||||
* @see `Get()`, `GetMutable()`, `IntoMutableText()`
|
||||
*
|
||||
* @return Assembled list of specified items with specified comments.
|
||||
*/
|
||||
public final function Text IntoText()
|
||||
{
|
||||
local Text result;
|
||||
|
||||
result = Get();
|
||||
FreeSelf();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts caller `Listbuilder` into list, assembled from items and their
|
||||
* comment specified so far as `MutableText`.
|
||||
* Caller `ListBuilder()` is atuomatically deallocated.
|
||||
*
|
||||
* @see `Get()`, `GetMutable()`, `IntoText()`
|
||||
*
|
||||
* @return Assembled list of specified items with specified comments.
|
||||
*/
|
||||
public final function MutableText IntoMutableText()
|
||||
{
|
||||
local MutableText result;
|
||||
|
||||
result = GetMutable();
|
||||
FreeSelf();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forgets all items or comments specified for the caller `ListBuilder` so far,
|
||||
* allowing to start forming a new report.
|
||||
*
|
||||
* @return Reference to the caller `ListBuilder` to allow for method chaining.
|
||||
*/
|
||||
public final function ListBuilder Reset()
|
||||
{
|
||||
local int i;
|
||||
|
||||
for (i = 0; i < collectedItems.length; i += 1)
|
||||
{
|
||||
_.memory.Free(collectedItems[i].itemTitle);
|
||||
_.memory.FreeMany(collectedItems[i].comments);
|
||||
}
|
||||
collectedItems.length = 0;
|
||||
return self;
|
||||
}
|
||||
|
||||
defaultproperties
|
||||
{
|
||||
TCAUSE = 0
|
||||
stringConstants(0) = "%%instigator%%"
|
||||
TTARGET = 1
|
||||
stringConstants(1) = "%%target%%"
|
||||
TCOMMA = 2
|
||||
stringConstants(2) = ","
|
||||
TCOMMA_SPACE = 3
|
||||
stringConstants(3) = ", "
|
||||
TSPACE_OPEN_PARANSIS = 4
|
||||
stringConstants(4) = " ("
|
||||
TCLOSE_PARANSIS = 5
|
||||
stringConstants(5) = ")"
|
||||
}
|
||||
28
kf_sources/Futility/Classes/Manifest.uc
Normal file
28
kf_sources/Futility/Classes/Manifest.uc
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* Manifest is meant to describe contents of the Acedia's package.
|
||||
* Copyright 2021 - 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class Manifest extends _manifest
|
||||
abstract;
|
||||
|
||||
defaultproperties
|
||||
{
|
||||
features(0) = class'Futility_Feature'
|
||||
features(1) = class'FutilityNicknames_Feature'
|
||||
features(2) = class'FutilityChat_Feature'
|
||||
}
|
||||
345
kf_sources/Futility/Classes/PendingConfigsTool.uc
Normal file
345
kf_sources/Futility/Classes/PendingConfigsTool.uc
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
/**
|
||||
* Auxiliary object for `ACommandFeature` to help with managing pending
|
||||
* configs for `Feature`s. Pending configs are `HashTable`s with config data
|
||||
* that are yet to be applied to configs and `Feature`s. They allow users to
|
||||
* make several changes to the data before actually applying changes to
|
||||
* the gameplay.
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class PendingConfigsTool extends AcediaObject;
|
||||
|
||||
/**
|
||||
* This tool works by selecting feature (by class) and config (by `Text`
|
||||
* name) on which it will operate with `SelectConfig()` method and then
|
||||
* invoking the rest of its methods on these selections.
|
||||
* There are some expections (`HasPendingConfigFor()` method) that
|
||||
* explicitly take these values as parameters.
|
||||
* This tool is supposed to be created once for the "feature" command and
|
||||
* have its `SelectConfig()` called each execution with user-specified
|
||||
* parameters.
|
||||
*/
|
||||
var private class<Feature> selectedFeatureClass;
|
||||
var private Text selectedConfigName;
|
||||
|
||||
// Possible errors that might occur when working with pending configs
|
||||
enum PendingConfigToolResult
|
||||
{
|
||||
// No error
|
||||
PCTE_None,
|
||||
// Pending version of specified config does not exist
|
||||
PCTE_ConfigMissing,
|
||||
// JSON object (`HashTable`) was expected as a parameter for the operation,
|
||||
// but something else was given
|
||||
PCTE_ExpectedObject,
|
||||
// Specified JSON pointer points an non-existent location
|
||||
PCTE_BadPointer
|
||||
};
|
||||
|
||||
struct PendingConfigs
|
||||
{
|
||||
var class<Feature> featureClass;
|
||||
var HashTable pendingSaves;
|
||||
};
|
||||
var private array<PendingConfigs> featurePendingEdits;
|
||||
|
||||
protected function Finalizer()
|
||||
{
|
||||
local int i;
|
||||
|
||||
for (i = 0; i < featurePendingEdits.length; i ++) {
|
||||
_.memory.Free(featurePendingEdits[i].pendingSaves);
|
||||
}
|
||||
featurePendingEdits.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects feature and config to perform all future operations on.
|
||||
*
|
||||
* @param featureClass Class of the feature for which to edit pending
|
||||
* configs.
|
||||
* @param configName Name of the pending config that caller tool will
|
||||
* work with.
|
||||
*/
|
||||
public final function SelectConfig(
|
||||
class<Feature> featureClass,
|
||||
BaseText configName)
|
||||
{
|
||||
_.memory.Free(selectedConfigName);
|
||||
selectedFeatureClass = featureClass;
|
||||
selectedConfigName = none;
|
||||
if (configName != none) {
|
||||
selectedConfigName = configName.LowerCopy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks wither caller tool has recorded pending config named `configName`
|
||||
* for `Feature` defined by class `featureClass`.
|
||||
* This method does no checks regarding existence of an actual config for
|
||||
* the specified `Feature` - it only checks whether caller tool has pending
|
||||
* version.
|
||||
*
|
||||
* @param featureClass Class of the `Feature` to check for pending config.
|
||||
* @param configName Name of the config to check for existence of its
|
||||
* pending version.
|
||||
* @return `true` if specified pending config exists and `false` otherwise.
|
||||
*/
|
||||
public function bool HasPendingConfigFor(
|
||||
class<Feature> featureClass,
|
||||
BaseText configName)
|
||||
{
|
||||
local int i;
|
||||
local bool result;
|
||||
local Text lowerCaseConfigName;
|
||||
|
||||
if (featureClass == none) return false;
|
||||
if (configName == none) return false;
|
||||
|
||||
for (i = 0; i < featurePendingEdits.length; i ++)
|
||||
{
|
||||
if (featurePendingEdits[i].featureClass == featureClass)
|
||||
{
|
||||
lowerCaseConfigName = configName.LowerCopy();
|
||||
result = featurePendingEdits[i].pendingSaves
|
||||
.HasKey(lowerCaseConfigName);
|
||||
lowerCaseConfigName.FreeSelf();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns data recorded for the selected pending config inside caller tool.
|
||||
*
|
||||
* @param createIfMissing Method only returns data of the pending version of
|
||||
* the config and if selected config does not yet have a pending version,
|
||||
* it will, by default, return `none`. This parameter allows this method to
|
||||
* create a pending config, based on current config with selected name
|
||||
* (if it exists).
|
||||
* @return Data recorded for the selected pending config. If selected config
|
||||
* does not have a pending version, `createIfMissing` is set to `false`
|
||||
* or not even current config with selected name exists - method returns
|
||||
* `none`.
|
||||
*/
|
||||
public function HashTable GetPendingConfigData(optional bool createIfMissing)
|
||||
{
|
||||
local int editsIndex;
|
||||
local HashTable result;
|
||||
local PendingConfigs newRecord;
|
||||
|
||||
if (selectedConfigName == none) {
|
||||
return none;
|
||||
}
|
||||
editsIndex = GetPendingConfigDataIndex();
|
||||
if (editsIndex >= 0)
|
||||
{
|
||||
result = featurePendingEdits[editsIndex]
|
||||
.pendingSaves
|
||||
.GetHashTable(selectedConfigName);
|
||||
if (result != none) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (createIfMissing)
|
||||
{
|
||||
if (editsIndex < 0)
|
||||
{
|
||||
editsIndex = featurePendingEdits.length;
|
||||
newRecord.featureClass = selectedFeatureClass;
|
||||
newRecord.pendingSaves = _.collections.EmptyHashTable();
|
||||
featurePendingEdits[editsIndex] = newRecord;
|
||||
}
|
||||
result = GetCurrentConfigData();
|
||||
if (result != none)
|
||||
{
|
||||
featurePendingEdits[editsIndex]
|
||||
.pendingSaves
|
||||
.SetItem(selectedConfigName, result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes and edit to the config.
|
||||
*
|
||||
* @param pathToValue JSON path at which to make a change.
|
||||
* @param newValue Value to record at the specified path.
|
||||
* @return Result of the operation that reports any errors that have occured.
|
||||
* Any changes are made iff result is `PCTE_None`.
|
||||
*/
|
||||
public function PendingConfigToolResult EditConfig(
|
||||
BaseText pathToValue,
|
||||
AcediaObject newValue)
|
||||
{
|
||||
local HashTable pendingData;
|
||||
local MutableJsonPointer pointer;
|
||||
local PendingConfigToolResult result;
|
||||
|
||||
if (pathToValue == none) {
|
||||
return PCTE_BadPointer;
|
||||
}
|
||||
pendingData = GetPendingConfigData(true);
|
||||
if (pendingData == none) {
|
||||
return PCTE_ConfigMissing;
|
||||
}
|
||||
// Set new data
|
||||
pointer = _.json.MutablePointer(pathToValue);
|
||||
result = SetItemByJSON(pendingData, pointer, newValue);
|
||||
pointer.FreeSelf();
|
||||
pendingData.FreeSelf();
|
||||
return result;
|
||||
}
|
||||
|
||||
private function PendingConfigToolResult SetItemByJSON(
|
||||
HashTable data,
|
||||
MutableJsonPointer pointer,
|
||||
AcediaObject jsonValue)
|
||||
{
|
||||
local Text containerIndex;
|
||||
local AcediaObject container;
|
||||
local PendingConfigToolResult result;
|
||||
|
||||
if (pointer.IsEmpty())
|
||||
{
|
||||
if (HashTable(jsonValue) != none)
|
||||
{
|
||||
result = ChangePendingConfigData(HashTable(jsonValue));
|
||||
_.memory.Free(jsonValue);
|
||||
return result;
|
||||
}
|
||||
_.memory.Free(jsonValue);
|
||||
return PCTE_ExpectedObject;
|
||||
}
|
||||
// Since `!pointer.IsEmpty()`, we are guaranteed to pop a valid value
|
||||
containerIndex = pointer.Pop();
|
||||
container = data.GetItemByJSON(pointer);
|
||||
if (container == none)
|
||||
{
|
||||
containerIndex.FreeSelf();
|
||||
return PCTE_BadPointer;
|
||||
}
|
||||
result = SetContainerItemByText(container, containerIndex, jsonValue);
|
||||
containerIndex.FreeSelf();
|
||||
container.FreeSelf();
|
||||
return result;
|
||||
}
|
||||
/*EditFeatureConfig #1: true
|
||||
SetItemByJSON: true
|
||||
SetContainerItemByText: true
|
||||
EditFeatureConfig #2: true */
|
||||
private function PendingConfigToolResult SetContainerItemByText(
|
||||
AcediaObject container,
|
||||
BaseText containerIndex,
|
||||
AcediaObject jsonValue)
|
||||
{
|
||||
local int arrayIndex;
|
||||
local Parser parser;
|
||||
local ArrayList arrayListContainer;
|
||||
local HashTable hashTableContainer;
|
||||
|
||||
hashTableContainer = HashTable(container);
|
||||
arrayListContainer = ArrayList(container);
|
||||
if (hashTableContainer != none) {
|
||||
hashTableContainer.SetItem(containerIndex, jsonValue);
|
||||
}
|
||||
if (arrayListContainer != none)
|
||||
{
|
||||
parser = containerIndex.Parse();
|
||||
if (parser.MInteger(arrayIndex, 10).Ok())
|
||||
{
|
||||
arrayListContainer.SetItem(arrayIndex, jsonValue);
|
||||
parser.FreeSelf();
|
||||
return PCTE_None;
|
||||
}
|
||||
parser.FreeSelf();
|
||||
if (containerIndex.Compare(P("-"))) {
|
||||
arrayListContainer.AddItem(jsonValue);
|
||||
}
|
||||
else {
|
||||
return PCTE_BadPointer;
|
||||
}
|
||||
}
|
||||
return PCTE_None;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes selected pending config.
|
||||
*
|
||||
* @return Result of the operation that reports any errors that have occured.
|
||||
* Any changes are made iff result is `PCTE_None`.
|
||||
*/
|
||||
public final function PendingConfigToolResult RemoveConfig()
|
||||
{
|
||||
local int editIndex;
|
||||
local HashTable pendingSaves;
|
||||
|
||||
editIndex = GetPendingConfigDataIndex();
|
||||
if (editIndex < 0) return PCTE_ConfigMissing;
|
||||
pendingSaves = featurePendingEdits[editIndex].pendingSaves;
|
||||
if (!pendingSaves.HasKey(selectedConfigName)) return PCTE_ConfigMissing;
|
||||
|
||||
pendingSaves.RemoveItem(selectedConfigName);
|
||||
if (pendingSaves.GetLength() <= 0)
|
||||
{
|
||||
pendingSaves.FreeSelf();
|
||||
featurePendingEdits.Remove(editIndex, 1);
|
||||
}
|
||||
return PCTE_None;
|
||||
}
|
||||
|
||||
private function int GetPendingConfigDataIndex()
|
||||
{
|
||||
local int i;
|
||||
|
||||
for (i = 0; i < featurePendingEdits.length; i ++)
|
||||
{
|
||||
if (featurePendingEdits[i].featureClass == selectedFeatureClass) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private function PendingConfigToolResult ChangePendingConfigData(
|
||||
HashTable newData)
|
||||
{
|
||||
local int editsIndex;
|
||||
|
||||
if (selectedConfigName == none) {
|
||||
return PCTE_None;
|
||||
}
|
||||
editsIndex = GetPendingConfigDataIndex();
|
||||
if (editsIndex < 0) {
|
||||
return PCTE_ConfigMissing;
|
||||
}
|
||||
featurePendingEdits[editsIndex].pendingSaves
|
||||
.SetItem(selectedConfigName, newData);
|
||||
return PCTE_None;
|
||||
}
|
||||
|
||||
private function HashTable GetCurrentConfigData()
|
||||
{
|
||||
return selectedFeatureClass.default.configClass.static
|
||||
.LoadData(selectedConfigName);
|
||||
}
|
||||
|
||||
defaultproperties
|
||||
{
|
||||
}
|
||||
33
kf_sources/Futility/Classes/index.html
Normal file
33
kf_sources/Futility/Classes/index.html
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<html>
|
||||
<head><title>Index of /kf_sources/Futility/Classes/</title></head>
|
||||
<body>
|
||||
<h1>Index of /kf_sources/Futility/Classes/</h1><hr><pre><a href="../">../</a>
|
||||
<a href="ACommandDB.uc">ACommandDB.uc</a> 09-Sep-2023 21:02 17533
|
||||
<a href="ACommandDosh.uc">ACommandDosh.uc</a> 21-Aug-2023 20:11 3467
|
||||
<a href="ACommandDosh_Announcer.uc">ACommandDosh_Announcer.uc</a> 29-Jun-2022 14:01 3465
|
||||
<a href="ACommandFeature.uc">ACommandFeature.uc</a> 07-May-2025 13:39 21862
|
||||
<a href="ACommandFeature_Announcer.uc">ACommandFeature_Announcer.uc</a> 21-Aug-2023 20:11 20633
|
||||
<a href="ACommandGod.uc">ACommandGod.uc</a> 21-Aug-2023 20:11 6803
|
||||
<a href="ACommandGod_Announcer.uc">ACommandGod_Announcer.uc</a> 05-Jul-2022 21:22 7924
|
||||
<a href="ACommandInventory.uc">ACommandInventory.uc</a> 21-Aug-2023 20:11 12226
|
||||
<a href="ACommandNick.uc">ACommandNick.uc</a> 21-Aug-2023 20:11 4716
|
||||
<a href="ACommandNick_Announcer.uc">ACommandNick_Announcer.uc</a> 12-Aug-2022 20:51 4006
|
||||
<a href="ACommandSpawn.uc">ACommandSpawn.uc</a> 19-Nov-2023 18:54 4367
|
||||
<a href="ACommandSpawn_Announcer.uc">ACommandSpawn_Announcer.uc</a> 09-Jul-2022 19:27 2888
|
||||
<a href="ACommandTrader.uc">ACommandTrader.uc</a> 21-Aug-2023 20:11 25036
|
||||
<a href="ACommandTrader_Announcer.uc">ACommandTrader_Announcer.uc</a> 02-Jul-2022 17:48 14635
|
||||
<a href="ACommandUserData.uc">ACommandUserData.uc</a> 21-Aug-2023 20:11 4440
|
||||
<a href="CommandAnnouncer.uc">CommandAnnouncer.uc</a> 07-May-2025 13:40 10686
|
||||
<a href="FormattingReportTool.uc">FormattingReportTool.uc</a> 07-May-2025 12:14 5618
|
||||
<a href="Futility.uc">Futility.uc</a> 21-Aug-2023 20:11 1181
|
||||
<a href="FutilityChat.uc">FutilityChat.uc</a> 21-Aug-2023 20:11 5168
|
||||
<a href="FutilityChat_Feature.uc">FutilityChat_Feature.uc</a> 21-Aug-2023 20:11 3832
|
||||
<a href="FutilityNicknames.uc">FutilityNicknames.uc</a> 21-Aug-2023 20:11 9733
|
||||
<a href="FutilityNicknames_Feature.uc">FutilityNicknames_Feature.uc</a> 21-Aug-2023 20:11 10477
|
||||
<a href="Futility_Feature.uc">Futility_Feature.uc</a> 21-Aug-2023 20:11 3135
|
||||
<a href="InventoryTool.uc">InventoryTool.uc</a> 07-May-2025 13:40 29064
|
||||
<a href="ListBuilder.uc">ListBuilder.uc</a> 30-Jun-2022 10:02 8094
|
||||
<a href="Manifest.uc">Manifest.uc</a> 13-Jan-2022 20:31 1100
|
||||
<a href="PendingConfigsTool.uc">PendingConfigsTool.uc</a> 21-Aug-2023 20:11 11274
|
||||
</pre><hr></body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue