Compare commits

..

2 Commits

  1. 55
      config/FutilityChat.ini
  2. 142
      config/FutilityNicknames.ini
  3. 225
      sources/Commands/ACommandDB.uc
  4. 35
      sources/Commands/ACommandDosh.uc
  5. 562
      sources/Commands/ACommandFeature.uc
  6. 342
      sources/Commands/ACommandFeature_Announcer.uc
  7. 30
      sources/Commands/ACommandGod.uc
  8. 66
      sources/Commands/ACommandInventory.uc
  9. 38
      sources/Commands/ACommandNick.uc
  10. 95
      sources/Commands/ACommandSpawn.uc
  11. 104
      sources/Commands/ACommandTrader.uc
  12. 140
      sources/Commands/ACommandUserData.uc
  13. 80
      sources/Features/FutileChat/FutilityChat.uc
  14. 90
      sources/Features/FutileChat/FutilityChat_Feature.uc
  15. 114
      sources/Features/FutileNickames/FutilityNicknames.uc
  16. 288
      sources/Features/FutileNickames/FutilityNicknames_Feature.uc
  17. 12
      sources/Futility.uc
  18. 6
      sources/Futility_Feature.uc
  19. 126
      sources/Tools/PendingConfigsTool.uc

55
config/FutilityChat.ini

@ -1,34 +1,31 @@
[default FutilityChat] [default FutilityChat]
; This feature allows to configure color of text chat messages.
autoEnable=true autoEnable=true
;= How to color text chat messages? ; How to color text chat messages?
;= ; 1. `CCS_DoNothing` - do not change color in any way;
;= 1. `CCS_DoNothing` - do not change color in any way; ; 2. `CCS_TeamColorForced` - force players' team colors for
;= 2. `CCS_TeamColorForced` - force players' team colors for ; their messages;
;= their messages; ; 3. `CCS_ConfigColorForced` - force `configuredColor` value for
;= 3. `CCS_ConfigColorForced` - force `configuredColor` value for ; players' messages;
;= players' messages; ; 4. `CCS_TeamColorCustom` - use players' team colors for
;= 4. `CCS_TeamColorCustom` - use players' team colors for ; their messages by default, but allow to change color with formatted
;= their messages by default, but allow to change color with formatted tags ; tags (e.g. "Stop right there, {$crimson criminal} scum!");
;= (e.g. "Stop right there, {$crimson criminal} scum!"); ; 5. `CCS_ConfigColorCustom` - use `configuredColor` value for
;= 5. `CCS_ConfigColorCustom` - use `configuredColor` value for ; messages by default, but allow to change color with formatted
;= messages by default, but allow to change color with formatted ; tags (e.g. "Stop right there, {$crimson criminal} scum!");
;= tags (e.g. "Stop right there, {$crimson criminal} scum!"); ; Default is `CCS_DoNothing`, corresponding to vanilla behaviour.
;=
;= Default is `CCS_DoNothing`, corresponding to vanilla behaviour.
colorSetting=CCS_DoNothing colorSetting=CCS_DoNothing
;= Color that will be used if either of `CCS_ConfigColorForced` or ; Color that will be used if either of `CCS_ConfigColorForced` or
;= `CCS_ConfigColorCustom` options were used in `colorSetting`. ; `CCS_ConfigColorCustom` options were used in `colorSetting`.
;= Default value is white: (R=255,G=255,B=255,A=255), has no vanilla ; Default value is white: (R=255,G=255,B=255,A=255),
;= equivalent. ; has no vanilla equivalent.
configuredColor=(R=255,G=255,B=255,A=255) configuredColor=(R=255,G=255,B=255,A=255)
;= Allows to modify team color's value for the chat messages ; Allows to modify team color's value for the chat messages
;= (if either of `CCS_TeamColorForced` or `CCS_TeamColorCustom` options ; (if either of `CCS_TeamColorForced` or `CCS_TeamColorCustom` options
;= were used) to be lighter or darker. ; were used) to be lighter or darker.
;= This value is clamped between -1 and 1: ; This value is clamped between -1 and 1.
;= ; * `0` means using the same color;
;= * `0` means using the same color; ; * range (0; 1) - gives you lighter colors (`1` being white);
;= * range (0; 1) - gives you lighter colors (`1` being white); ; * range (-1; 0) - gives you darker colors (`-1` being black);
;= * range (-1; 0) - gives you darker colors (`-1` being black); ; Default value is `0.6`, has no vanilla equivalent.
;=
;= Default value is `0.6`, has no vanilla equivalent.
teamColorModifier=0.6 teamColorModifier=0.6

142
config/FutilityNicknames.ini

@ -1,67 +1,64 @@
[default FutilityNicknames] [default FutilityNicknames]
;= This feature allows to configure nickname limitations for the server. ; This feature allows to configure nickname limitations for the server.
;= It allows you to customize vanilla limitations for nickname length and ; It allows you to customize vanilla limitations for nickname length and
;= color with those of your own design. Enabling this feature overwrites ; color with those of your own design. Enabling this feature overwrites
;= default behaviour. ; default behaviour.
autoEnable=true autoEnable=true
;= How to treat whitespace characters inside players' nicknames. ; How to treat whitespace characters inside players' nicknames.
;= ; * `NSA_DoNothing` - does nothing, leaving whitespaces as they are;
;= * `NSA_DoNothing` - does nothing, leaving whitespaces as they are;= ; * `NSA_Trim` - removes leading and trailing whitespaces for nicknames;
;= * `NSA_Trim` - removes leading and trailing whitespaces for nicknames;= ; * `NSA_Simplify` - removes leading and trailing whitespaces
;= * `NSA_Simplify` - removes leading and trailing whitespaces ; for nicknames, also reducing a sequence of whitespaces inside
;= for nicknames, also reducing a sequence of whitespaces inside ; nickname to a single space, e.g. "my nick" becomes "my nick".
;= nickname to a single space, e.g. "my nick" becomes "my nick". ; Default is `NSA_DoNothing`, same as on vanilla.
;=
;= Default is `NSA_DoNothing`, same as on vanilla.
spacesAction=NSA_DoNothing spacesAction=NSA_DoNothing
;= How to treat colored nicknames. ; How to treat colored nicknames.
;= * `NCP_ForbidColor` - completely strips down any color from nicknames;= ; * `NCP_ForbidColor` - completely strips down any color from nicknames;
;= * `NCP_ForceTeamColor` - forces all nicknames to have player's current ; * `NCP_ForceTeamColor` - forces all nicknames to have player's current
;= team's color;= ; team's color;
;= * `NCP_ForceSingleColor` - allows nickname to be painted with a single ; * `NCP_ForceSingleColor` - allows nickname to be painted with a single
;= color (sets nickname's color to that of the first character);= ; color (sets nickname's color to that of the first character);
;= * `NCP_AllowAnyColor` - allows nickname to be colored in any way player ; * `NCP_AllowAnyColor` - allows nickname to be colored in any way player
;= wants. ; wants.
;= Default is `NCP_ForbidColor`, same as on vanilla. ; Default is `NCP_ForbidColor`, same as on vanilla.
colorPermissions=NCP_ForbidColor colorPermissions=NCP_ForbidColor
;= Set this to `true` if you wish to replace all whitespace characters with ; Set this to `true` if you wish to replace all whitespace characters with
;= underscores and `false` to leave them as is. ; underscores and `false` to leave them as is.
;= Default is `true`, same as on vanilla. However there is one difference: ; Default is `true`, same as on vanilla. However there is one difference:
;= Futility replaces all whitespace characters (including tabulations, ; Futility replaces all whitespace characters (including tabulations,
;= non-breaking spaces, etc.) instead of only ' '. ; non-breaking spaces, etc.) instead of only ' '.
replaceSpacesWithUnderscores=true replaceSpacesWithUnderscores=true
;= Set this to `true` to remove single 'quotation marks' and `false` to ; Set this to `true` to remove single 'quotation marks' and `false` to
;= leave them. Default is `false`, same as on vanilla. ; leave them. Default is `false`, same as on vanilla.
removeSingleQuotationMarks=false removeSingleQuotationMarks=false
;= Set this to `true` to remove dobule 'quotation marks' and `false` to ; Set this to `true` to remove dobule 'quotation marks' and `false` to
;= leave them. Default is `true`, same as on vanilla. ; leave them. Default is `true`, same as on vanilla.
removeDoubleQuotationMarks=true removeDoubleQuotationMarks=true
;= Max allowed nickname length. Negative values disable any length limits. ; Max allowed nickname length. Negative values disable any length limits.
;= ;
;= NOTE #1: `0` resets all nicknames to be empty and, ; NOTE #1: `0` resets all nicknames to be empty and,
;= if `correctEmptyNicknames` is set to `true`, they will be replaced with ; if `correctEmptyNicknames` is set to `true`, they will be replaced with
;= one of the fallback nicknames ; one of the fallback nicknames
;= (see `correctEmptyNicknames` and `fallbackNickname`). ; (see `correctEmptyNicknames` and `fallbackNickname`).
;= ; NOTE #2: Because of how color swapping in vanilla Killing Floor works,
;= NOTE #2: Because of how color swapping in vanilla Killing Floor works, ; every color swap makes text count as being about 4 characters longer.
;= 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
;= 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
;= 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
;= UI. Unless you are using custom HUD it is recommended to keep this value ; at default `20` and forbid colored nicknames
;= at default `20` and forbid colored nicknames ; (by setting `colorPermissions=NCP_ForbidColor`). Or to allow only one
;= (by setting `colorPermissions=NCP_ForbidColor`). Or to allow only one ; color (by setting `colorPermissions=NCP_ForceSingleColor` or
;= color (by setting `colorPermissions=NCP_ForceSingleColor` or ; `colorPermissions=NCP_ForceTeamColor`) and reducing `maxNicknameLength`
;= `colorPermissions=NCP_ForceTeamColor`) and reducing `maxNicknameLength` ; to `16` (20 characters - 4 for color swap).
;= to `16` (20 characters - 4 for color swap). ; If you want to increase the limit above that, you can also do your
;= If you want to increase the limit above that, you can also do your ; own research by testing nicknames of various length on
;= own research by testing nicknames of various length on ; screen resolutions you care about.
;= screen resolutions you care about.
maxNicknameLength=20 maxNicknameLength=20
;= Should we replace empty player nicknames with a random fallback nickname ; Should we replace empty player nicknames with a random fallback nickname
;= (defined in `fallbackNickname` array)? ; (defined in `fallbackNickname` array)?
correctEmptyNicknames=true correctEmptyNicknames=true
;= Array of fallback nicknames that will be used to replace any empty nicknames ; Array of fallback nicknames that will be used to replace any empty nicknames
;= if `correctEmptyNicknames` is set to `true`. ; if `correctEmptyNicknames` is set to `true`.
fallbackNickname="Fresh Meat" fallbackNickname="Fresh Meat"
fallbackNickname="Rotten Meat" fallbackNickname="Rotten Meat"
fallbackNickname="Troll Meat" fallbackNickname="Troll Meat"
@ -73,21 +70,20 @@ fallbackNickname="Boar Meat"
fallbackNickname="Walrus Meat" fallbackNickname="Walrus Meat"
fallbackNickname="Bug Meat" fallbackNickname="Bug Meat"
fallbackNickname="Horse Meat" fallbackNickname="Horse Meat"
;= Guaranteed order of applying changes (only chosen ones) is as following: ; Guaranteed order of applying changes (only chosen ones) is as following:
;= 1. Trim/simplify spaces;= ; 1. Trim/simplify spaces;
;= 2. Remove single and double quotation marks;= ; 2. Remove single and double quotation marks;
;= 3. Enforce max limit of nickname's length;= ; 3. Enforce max limit of nickname's length;
;= 4. Replace empty nickname with fallback nickname (no further changes ; 4. Replace empty nickname with fallback nickname (no further changes
;= will be applied to fallback nickname in that case);= ; will be applied to fallback nickname in that case);
;= 5. Enforce color limitation;= ; 5. Enforce color limitation;
;= 6. Replace remaining whitespaces with underscores. ; 6. Replace remaining whitespaces with underscores.
;= ;
;= NOTE #1: as follows from the instruction described above, no changes will ; NOTE #1: as follows from the instruction described above, no changes will
;= ever be applied to fallback nicknames (unless player's nickname ; ever be applied to fallback nicknames (unless player's nickname
;= coincides with one by pure accident). ; coincides with one by pure accident).
;= ; NOTE #2: whitespaces inside steam nicknames are converted into underscores
;= NOTE #2: whitespaces inside steam nicknames are converted into underscores ; before they are passed into the game and this is a change Futility
;= before they are passed into the game and this is a change Futility ; cannot currently abort.
;= cannot currently abort. ; Therefore all changes relevant to whitespaces inside nicknames will only
;= Therefore all changes relevant to whitespaces inside nicknames will only ; be applied to in-game changes.
;= be applied to in-game changes.

225
sources/Commands/ACommandDB.uc

@ -1,6 +1,6 @@
/** /**
* Command for working with databases. * Command for working with databases.
* Copyright 2021-2023 Anton Tarasenko * Copyright 2021-2022 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -65,63 +65,87 @@ struct DBPointerPair
}; };
var protected const int TCREATE, TDELETE, TLIST, TREAD, TSIZE, TKEYS, TREMOVE; 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 TWRITE, TWRITE_ARRAY, TWRITE_NULL, TWRITE_BOOLEAN;
var protected const int TJSON_VALUE, TQUERY_INVALID_POINTER, TQUERY_INVALID_DB; var protected const int TWRITE_INTEGER, TWRITE_FLOAT, TWRITE_STRING, TINCREMENT;
var protected const int TDATABASE_NAME, TDATABASE_LINK, TJSON_VALUE;
var protected const int TOBJECT_KEYS_ARE, TOBJECT_SIZE_IS, TQUERY_COMPLETED; var protected const int TOBJECT_KEYS_ARE, TOBJECT_SIZE_IS, TQUERY_COMPLETED;
var protected const int TQUERY_INVALID_POINTER, TQUERY_INVALID_DB;
var protected const int TQUERY_INVALID_DATA, TAVAILABLE_DATABASES, TDA_DELETED; 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_DOESNT_EXIST, TDB_ALREADY_EXISTS, TDB_CREATED;
var protected const int TDB_CANNOT_BE_CREATED, TNO_DEFAULT_COMMAND, TBAD_DBLINK; var protected const int TDB_CANNOT_BE_CREATED, TNO_DEFAULT_COMMAND, TBAD_DBLINK;
protected function BuildData(CommandDataBuilder builder) protected function BuildData(CommandDataBuilder builder)
{ {
builder.Group(P("admin")); builder.Name(P("db")).Group(P("admin"))
builder.Summary(P("Read and edit data in your databases." .Summary(P("Read and edit data in your databases."
@ "Databases' values are addressed with links:" @ "Databases' values are addressed with links:"
@ "\"<db_name>:<json_path>\"")); @ "\"<db_name>:<json_path>\""));
builder.SubCommand(T(TCREATE)); builder.SubCommand(T(TCREATE))
builder.ParamText(T(TDATABASE_NAME)); .ParamText(T(TDATABASE_NAME))
builder.Describe(P("Creates new database with a specified name.")); .Describe(P("Creates new database with a specified name."));
builder.SubCommand(T(TDELETE)); builder.SubCommand(T(TDELETE))
builder.ParamText(T(TDATABASE_NAME)); .ParamText(T(TDATABASE_NAME))
builder.Describe(P("Completely deletes specified database.")); .Describe(P("Completely deletes specified database."));
builder.SubCommand(T(TLIST)); builder.SubCommand(T(TLIST))
builder.Describe(P("Lists available databases.")); .Describe(P("Lists available databases."));
builder.SubCommand(T(TREAD)); builder.SubCommand(T(TREAD))
builder.ParamText(T(TDATABASE_LINK)); .ParamText(T(TDATABASE_LINK))
builder.Describe(P("Reads data from location given by the `databaseLink`.")); .Describe(P("Reads data from location given by the `databaseLink`."));
builder.SubCommand(T(TSIZE)); builder.SubCommand(T(TSIZE))
builder.ParamText(T(TDATABASE_LINK)); .ParamText(T(TDATABASE_LINK))
builder.Describe(P("Gets amount of elements inside JSON array or object at" .Describe(P("Gets amount of elements inside JSON array or object at"
@ "location given by the `databaseLink`.")); @ "location given by the `databaseLink`."));
builder.SubCommand(T(TKEYS)); builder.SubCommand(T(TKEYS))
builder.ParamText(T(TDATABASE_LINK)); .ParamText(T(TDATABASE_LINK))
builder.Describe(P("Lists keys of JSON object at location given by" .Describe(P("Lists keys of JSON object at location given by"
@ "the `databaseLink`.")); @ "the `databaseLink`."));
builder.SubCommand(T(TREMOVE)); builder.SubCommand(T(TREMOVE))
builder.ParamText(T(TDATABASE_LINK)); .ParamText(T(TDATABASE_LINK))
builder.Describe(P("Removes data from location given by the `databaseLink`.")); .Describe(P("Removes data from location given by the `databaseLink`."));
builder.SubCommand(T(TWRITE)); builder.SubCommand(T(TWRITE))
builder.ParamText(T(TDATABASE_LINK)); .ParamText(T(TDATABASE_LINK))
builder.ParamJSON(T(TJSON_VALUE)); .ParamObject(T(TJSON_VALUE))
builder.Describe(P("Writes specified JSON value into location given by" .Describe(P("Writes specified JSON object into location given by"
@ "the `databaseLink`.")); @ "the `databaseLink`."));
builder.Option(T(TINCREMENT)); builder.SubCommand(T(TWRITE_ARRAY))
builder.Describe(F("Specifying this option for any of the" .ParamText(T(TDATABASE_LINK))
.ParamArray(T(TJSON_VALUE))
.Describe(P("Writes specified JSON array into location given by"
@ "the `databaseLink`."));
builder.SubCommand(T(TWRITE_NULL))
.ParamText(T(TDATABASE_LINK))
.Describe(P("Writes specified null value into location given by"
@ "the `databaseLink`."));
builder.SubCommand(T(TWRITE_BOOLEAN))
.ParamText(T(TDATABASE_LINK))
.ParamBoolean(T(TJSON_VALUE))
.Describe(P("Writes specified JSON boolean into location given by"
@ "the `databaseLink`."));
builder.SubCommand(T(TWRITE_INTEGER))
.ParamText(T(TDATABASE_LINK))
.ParamInteger(T(TJSON_VALUE))
.Describe(P("Writes specified integer as JSON number into location"
@ "given by the `databaseLink`."));
builder.SubCommand(T(TWRITE_FLOAT))
.ParamText(T(TDATABASE_LINK))
.ParamNumber(T(TJSON_VALUE))
.Describe(P("Writes specified float as JSON number into location"
@ "given by the `databaseLink`."));
builder.SubCommand(T(TWRITE_STRING))
.ParamText(T(TDATABASE_LINK))
.ParamText(T(TJSON_VALUE))
.Describe(P("Writes specified JSON string into location given by"
@ "the `databaseLink`."));
builder.Option(T(TINCREMENT))
.Describe(F("Specifying this option for any of the"
@ "{$TextEmphasis 'write'} subcommands will cause them to append" @ "{$TextEmphasis 'write'} subcommands will cause them to append"
@ "data to the old one, instead of rewriting it.")); @ "data to the old one, instead of rewriting it."));
} }
protected function PushPlayer(EPlayer nextPlayer, Database callDatabase) protected function PushPlayer(EPlayer nextPlayer, Database callDatabase)
{ {
local EPlayer playerCopy; queueWaitingListPlayers[queueWaitingListPlayers.length] =
EPlayer(nextPlayer.Copy());
if (nextPlayer != none) {
playerCopy = EPlayer(nextPlayer.Copy());
}
if (callDatabase != none) {
callDatabase.NewRef();
}
queueWaitingListPlayers[queueWaitingListPlayers.length] = playerCopy;
queueWaitingListDatabases[queueWaitingListDatabases.length] = callDatabase; queueWaitingListDatabases[queueWaitingListDatabases.length] = callDatabase;
} }
@ -135,10 +159,9 @@ protected function EPlayer PopPlayer(Database relevantDatabase)
while (i < queueWaitingListDatabases.length) while (i < queueWaitingListDatabases.length)
{ {
if (queueWaitingListDatabases[i].IsEqual(relevantDatabase)) if (queueWaitingListDatabases[i] != relevantDatabase)
{ {
result = queueWaitingListPlayers[i]; result = queueWaitingListPlayers[i];
queueWaitingListDatabases[i].FreeSelf();
queueWaitingListPlayers.Remove(i, 1); queueWaitingListPlayers.Remove(i, 1);
queueWaitingListDatabases.Remove(i, 1); queueWaitingListDatabases.Remove(i, 1);
break; break;
@ -152,11 +175,8 @@ protected function EPlayer PopPlayer(Database relevantDatabase)
return none; return none;
} }
protected function Executed( protected function Executed(CallData arguments, EPlayer instigator)
CallData arguments, {
EPlayer instigator,
CommandPermissions permissions
) {
local AcediaObject valueToWrite; local AcediaObject valueToWrite;
local DBPointerPair pair; local DBPointerPair pair;
local Text subCommand; local Text subCommand;
@ -176,7 +196,7 @@ protected function Executed(
} }
// Remember the last player we are making a query to and make that query // Remember the last player we are making a query to and make that query
PushPlayer(instigator, pair.database); PushPlayer(instigator, pair.database);
if (subCommand.Compare(T(TWRITE))) if (subCommand.StartsWith(T(TWRITE)))
{ {
valueToWrite = arguments.parameters.GetItem(T(TJSON_VALUE)); valueToWrite = arguments.parameters.GetItem(T(TJSON_VALUE));
if (arguments.options.HasKey(T(TINCREMENT))) if (arguments.options.HasKey(T(TINCREMENT)))
@ -246,11 +266,11 @@ private function DBPointerPair TryLoadingDB(BaseText databaseLink)
if (databaseLink == none) { if (databaseLink == none) {
return result; return result;
} }
result.database = _server.db.Load(databaseLink); result.database = _.db.Load(databaseLink);
if (result.database == none) { if (result.database == none) {
return result; return result;
} }
result.pointer = _server.db.GetPointer(databaseLink); result.pointer = _.db.GetPointer(databaseLink);
return result; return result;
} }
@ -259,12 +279,12 @@ protected function CreateDatabase(EPlayer instigator, Text databaseName)
if (instigator == none) { if (instigator == none) {
return; return;
} }
if (_server.db.ExistsLocal(databaseName)) if (_.db.ExistsLocal(databaseName))
{ {
callerConsole.WriteLine(T(TDB_ALREADY_EXISTS)); callerConsole.WriteLine(T(TDB_ALREADY_EXISTS));
return; return;
} }
if (_server.db.NewLocal(databaseName) != none) { if (_.db.NewLocal(databaseName) != none) {
callerConsole.WriteLine(T(TDB_CREATED)); callerConsole.WriteLine(T(TDB_CREATED));
} }
else { else {
@ -277,7 +297,7 @@ protected function DeleteDatabase(EPlayer instigator, Text databaseName)
if (instigator == none) { if (instigator == none) {
return; return;
} }
if (_server.db.DeleteLocal(databaseName)) { if (_.db.DeleteLocal(databaseName)) {
callerConsole.WriteLine(T(TDA_DELETED)); callerConsole.WriteLine(T(TDA_DELETED));
} }
else { else {
@ -294,7 +314,7 @@ protected function ListDatabases(EPlayer instigator)
if (instigator == none) { if (instigator == none) {
return; return;
} }
availableDatabases = _server.db.ListLocal(); availableDatabases = _.db.ListLocal();
console = callerConsole; console = callerConsole;
console.Write(T(TAVAILABLE_DATABASES)); console.Write(T(TAVAILABLE_DATABASES));
for (i = 0; i < availableDatabases.length; i += 1) for (i = 0; i < availableDatabases.length; i += 1)
@ -332,8 +352,7 @@ protected function OutputStatus(
protected function DisplayData( protected function DisplayData(
Database.DBQueryResult result, Database.DBQueryResult result,
AcediaObject data, AcediaObject data,
Database source, Database source)
int requestID)
{ {
local Text printedJSON; local Text printedJSON;
local EPlayer instigator; local EPlayer instigator;
@ -354,8 +373,7 @@ protected function DisplayData(
protected function DisplaySize( protected function DisplaySize(
Database.DBQueryResult result, Database.DBQueryResult result,
int size, int size,
Database source, Database source)
int requestID)
{ {
local Text sizeAsText; local Text sizeAsText;
local EPlayer instigator; local EPlayer instigator;
@ -378,8 +396,7 @@ protected function DisplaySize(
protected function DisplayKeys( protected function DisplayKeys(
Database.DBQueryResult result, Database.DBQueryResult result,
ArrayList keys, ArrayList keys,
Database source, Database source)
int requestID)
{ {
local int i; local int i;
local Text nextKey; local Text nextKey;
@ -413,8 +430,7 @@ protected function DisplayKeys(
protected function DisplayResponse( protected function DisplayResponse(
Database.DBQueryResult result, Database.DBQueryResult result,
Database source, Database source)
int requestID)
{ {
local EPlayer instigator; local EPlayer instigator;
@ -425,7 +441,6 @@ protected function DisplayResponse(
defaultproperties defaultproperties
{ {
preferredName = "db"
TCREATE = 0 TCREATE = 0
stringConstants(0) = "create" stringConstants(0) = "create"
TDELETE = 1 TDELETE = 1
@ -442,40 +457,52 @@ defaultproperties
stringConstants(6) = "remove" stringConstants(6) = "remove"
TWRITE = 7 TWRITE = 7
stringConstants(7) = "write" stringConstants(7) = "write"
TINCREMENT = 8 TWRITE_ARRAY = 8
stringConstants(8) = "increment" stringConstants(8) = "write_array"
TDATABASE_NAME = 9 TWRITE_NULL = 9
stringConstants(9) = "databaseName" stringConstants(9) = "write_null"
TDATABASE_LINK = 10 TWRITE_BOOLEAN = 10
stringConstants(10) = "databaseLink" stringConstants(10) = "write_boolean"
TJSON_VALUE = 11 TWRITE_INTEGER = 11
stringConstants(11) = "jsonValue" stringConstants(11) = "write_integer"
TOBJECT_KEYS_ARE = 12 TWRITE_FLOAT = 12
stringConstants(12) = "{$TextEmphasis Object keys are:} " stringConstants(12) = "write_float"
TOBJECT_SIZE_IS = 13 TWRITE_STRING = 13
stringConstants(13) = "{$TextEmphasis Object size is:} " stringConstants(13) = "write_string"
TQUERY_COMPLETED = 14 TINCREMENT = 14
stringConstants(14) = "{$TextPositive Database query was completed!}" stringConstants(14) = "increment"
TQUERY_INVALID_POINTER = 15 TDATABASE_NAME = 15
stringConstants(15) = "{$TextFailure Query was provided with an invalid JSON pointer.}" stringConstants(15) = "databaseName"
TQUERY_INVALID_DB = 16 TDATABASE_LINK = 16
stringConstants(16) = "{$TextFailure Operation could not finish because database is damaged and unusable.}" stringConstants(16) = "databaseLink"
TQUERY_INVALID_DATA = 17 TJSON_VALUE = 17
stringConstants(17) = "{$TextFailure Query data is invalid.}" stringConstants(17) = "jsonValue"
TAVAILABLE_DATABASES = 18 TOBJECT_KEYS_ARE = 18
stringConstants(18) = "{$TextEmphasis Available databases:} " stringConstants(18) = "{$TextEmphasis Object keys are:} "
TDA_DELETED = 19 TOBJECT_SIZE_IS = 19
stringConstants(19) = "{$TextPositive Database was deleted.}" stringConstants(19) = "{$TextEmphasis Object size is:} "
TDB_DOESNT_EXIST = 20 TQUERY_COMPLETED = 20
stringConstants(20) = "{$TextFailure Database with specified name does not exist.}" stringConstants(20) = "{$TextPositive Database query was completed!}"
TDB_ALREADY_EXISTS = 21 TQUERY_INVALID_POINTER = 21
stringConstants(21) = "{$TextFailure Database with specified name already exists.}" stringConstants(21) = "{$TextNegative Query was provided with an invalid JSON pointer.}"
TDB_CREATED = 22 TQUERY_INVALID_DB = 22
stringConstants(22) = "{$TextPositive Database was created.}" stringConstants(22) = "{$TextNegative Operation could not finish because database is damaged and unusable.}"
TDB_CANNOT_BE_CREATED = 23 TQUERY_INVALID_DATA = 23
stringConstants(23) = "{$TextFailure Database cannot be created.}" stringConstants(23) = "{$TextNegative Query data is invalid.}"
TNO_DEFAULT_COMMAND = 24 TAVAILABLE_DATABASES = 24
stringConstants(24) = "{$TextFailure Default command does nothing. Use on of the sub-commands.}" stringConstants(24) = "{$TextEmphasis Available databases:} "
TBAD_DBLINK = 25 TDA_DELETED = 25
stringConstants(25) = "{$TextFailure Database could not be read for the specified link.}" stringConstants(25) = "{$TextPositive Database was deleted.}"
TDB_DOESNT_EXIST = 26
stringConstants(26) = "{$TextNegative Database with specified name does not exist.}"
TDB_ALREADY_EXISTS = 27
stringConstants(27) = "{$TextNegative Database with specified name already exists.}"
TDB_CREATED = 28
stringConstants(28) = "{$TextPositive Database was created.}"
TDB_CANNOT_BE_CREATED = 29
stringConstants(29) = "{$TextNegative Database cannot be created.}"
TNO_DEFAULT_COMMAND = 30
stringConstants(30) = "{$TextNegative Default command does nothing. Use on of the sub-commands.}"
TBAD_DBLINK = 31
stringConstants(31) = "{$TextNegative Database could not be read for the specified link.}"
} }

35
sources/Commands/ACommandDosh.uc

@ -29,23 +29,23 @@ protected function Finalizer()
protected function BuildData(CommandDataBuilder builder) protected function BuildData(CommandDataBuilder builder)
{ {
builder.Group(P("gameplay")); builder.Name(P("dosh")).Group(P("gameplay"))
builder.Summary(P("Changes amount of money.")); .Summary(P("Changes amount of money."));
builder.RequireTarget(); builder.RequireTarget();
builder.ParamInteger(P("amount")); builder.ParamInteger(P("amount"))
builder.Describe(P("Gives (or takes if negative) players a specified <amount>" .Describe(P("Gives (or takes if negative) players a specified <amount>"
@ "of money.")); @ "of money."));
builder.SubCommand(P("set")); builder.SubCommand(P("set"))
builder.ParamInteger(P("amount")); .ParamInteger(P("amount"))
builder.Describe(P("Sets player's money to a specified <amount>.")); .Describe(P("Sets player's money to a specified <amount>."));
builder.Option(P("min")); builder.Option(P("min"))
builder.ParamInteger(P("minValue")); .ParamInteger(P("minValue"))
builder.Describe(F("Players will retain at least this amount of dosh after" .Describe(F("Players will retain at least this amount of dosh after"
@ "the command's execution. In case of conflict, overrides" @ "the command's execution. In case of conflict, overrides"
@ "'{$TextEmphasis --max}' option. `0` is assumed by default.")); @ "'{$TextEmphasis --max}' option. `0` is assumed by default."));
builder.Option(P("max"), P("M")); builder.Option(P("max"), P("M"))
builder.ParamInteger(P("maxValue")); .ParamInteger(P("maxValue"))
builder.Describe(F("Players will have at most this amount of dosh after" .Describe(F("Players will have at most this amount of dosh after"
@ "the command's execution. In case of conflict, it is overridden" @ "the command's execution. In case of conflict, it is overridden"
@ "by '{$TextEmphasis --min}' option.")); @ "by '{$TextEmphasis --min}' option."));
announcer = ACommandDosh_Announcer( announcer = ACommandDosh_Announcer(
@ -55,9 +55,8 @@ protected function BuildData(CommandDataBuilder builder)
protected function ExecutedFor( protected function ExecutedFor(
EPlayer target, EPlayer target,
CallData arguments, CallData arguments,
EPlayer instigator, EPlayer instigator)
CommandPermissions permissions {
) {
local int oldAmount, newAmount; local int oldAmount, newAmount;
local int amount, minValue, maxValue; local int amount, minValue, maxValue;
@ -89,6 +88,6 @@ protected function ExecutedFor(
} }
} }
defaultproperties { defaultproperties
preferredName = "dosh" {
} }

562
sources/Commands/ACommandFeature.uc

@ -21,141 +21,89 @@ class ACommandFeature extends Command
dependson(PendingConfigsTool); dependson(PendingConfigsTool);
var private class<Feature> selectedFeatureClass; var private class<Feature> selectedFeatureClass;
var private Text selectedFeatureName;
var private Text selectedConfigName; var private Text selectedConfigName;
var private PendingConfigsTool pendingConfigs; var private PendingConfigsTool pendingConfigs;
var private ACommandFeature_Announcer announcer; var private ACommandFeature_Announcer announcer;
protected function Constructor() { protected function Constructor()
pendingConfigs = PendingConfigsTool(_.memory.Allocate(class'PendingConfigsTool')); {
pendingConfigs =
PendingConfigsTool(_.memory.Allocate(class'PendingConfigsTool'));
super.Constructor(); super.Constructor();
} }
protected function Finalizer() { protected function Finalizer()
{
_.memory.Free(announcer); _.memory.Free(announcer);
_.memory.Free(pendingConfigs); _.memory.Free(pendingConfigs);
super.Finalizer(); super.Finalizer();
} }
protected function BuildData(CommandDataBuilder builder) { protected function BuildData(CommandDataBuilder builder)
builder.Group(P("admin")); {
builder.Summary(P("Managing features.")); builder.Name(P("feature")).Group(P("admin"))
builder.Describe(P("Command for managing features and their configs.")); .Summary(P("Managing features."))
builder.SubCommand(P("enable")); .Describe(P("Command for displaying and enabling/disabling features."));
builder.ParamText(P("feature"),, P("feature")); builder.SubCommand(P("enable"))
builder.OptionalParams(); .ParamText(P("feature"))
builder.ParamText(P("config")); .OptionalParams()
builder.Describe(P("Enables specified <feature>. If <config> isn't specified -" .ParamText(P("config"))
@ "choses the \"default\" one, making new config with default" .Describe(P("Enables specified <feature>."));
@ "settings if it doesn't exist.")); builder.SubCommand(P("disable"))
builder.SubCommand(P("disable")); .ParamText(P("feature"))
builder.ParamText(P("feature"),, P("feature")); .Describe(P("Disables specified <feature>."));
builder.Describe(P("Disables specified <feature>.")); builder.SubCommand(P("showconf"))
builder.SubCommand(P("showconf")); .ParamText(P("feature"))
builder.ParamText(P("feature"),, P("feature")); .ParamText(P("config"));
builder.OptionalParams(); builder.SubCommand(P("editconf"))
builder.ParamText(P("config")); .ParamText(P("feature"))
builder.Describe(P("Show given <config> for the given <feature>.")); .ParamText(P("config"))
builder.SubCommand(P("editconf")); .ParamText(P("variable path"))
builder.ParamText(P("feature"),, P("feature")); .ParamRemainder(P("value"));
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( announcer = ACommandFeature_Announcer(
_.memory.Allocate(class'ACommandFeature_Announcer')); _.memory.Allocate(class'ACommandFeature_Announcer'));
} }
protected function Executed( protected function Executed(CallData arguments, EPlayer instigator)
CallData arguments, {
EPlayer instigator, local Text userGivenFeatureName, userGivenConfigName;
CommandPermissions permissions
) {
local bool saveFlag, allFlag;
announcer.Setup(none, instigator, othersConsole); announcer.Setup(none, instigator, othersConsole);
saveFlag = arguments.options.HasKey(P("save")); userGivenFeatureName = arguments.parameters.GetText(P("feature"));
allFlag = arguments.options.HasKey(P("all")); selectedFeatureClass = LoadFeatureClass(userGivenFeatureName);
SelectFeatureAndConfig(arguments); _.memory.Free(userGivenFeatureName);
userGivenConfigName = arguments.parameters.GetText(P("config"));
if (userGivenConfigName != none)
{
selectedConfigName = userGivenConfigName.LowerCopy();
userGivenConfigName.FreeSelf();
}
pendingConfigs.SelectConfig(selectedFeatureClass, selectedConfigName);
if (arguments.subCommandName.IsEmpty()) { if (arguments.subCommandName.IsEmpty()) {
ShowAllFeatures(); ShowAllFeatures();
} else if (arguments.subCommandName.Compare(P("enable"))) { }
else if (arguments.subCommandName.Compare(P("enable"))) {
EnableFeature(); EnableFeature();
} else if (arguments.subCommandName.Compare(P("disable"))) { }
else if (arguments.subCommandName.Compare(P("disable"))) {
DisableFeature(); 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); else if (arguments.subCommandName.Compare(P("showconf"))) {
userGivenConfigName = arguments.parameters.GetText(P("config")); ShowFeatureConfig();
if (userGivenConfigName != none) {
selectedConfigName = userGivenConfigName.LowerCopy();
userGivenConfigName.FreeSelf();
} }
pendingConfigs.SelectConfig(selectedFeatureClass, selectedConfigName); else if (arguments.subCommandName.Compare(P("editconf")))
{
EditFeatureConfig(
arguments.parameters.GetText(P("variable path")),
arguments.parameters.GetText(P("value")));
}
_.memory.Free(selectedConfigName);
selectedConfigName = none;
} }
protected function EnableFeature() { protected function EnableFeature()
{
local bool wasEnabled; local bool wasEnabled;
local Text oldConfig, newConfig; local Text oldConfig, newConfig;
local Feature instance; local Feature instance;
@ -164,99 +112,79 @@ protected function EnableFeature() {
oldConfig = selectedFeatureClass.static.GetCurrentConfig(); oldConfig = selectedFeatureClass.static.GetCurrentConfig();
newConfig = PickConfigBasedOnParameter(); newConfig = PickConfigBasedOnParameter();
// Already enabled with the same config! // Already enabled with the same config!
if (oldConfig != none && oldConfig.Compare(newConfig, SCASE_INSENSITIVE)) { if (oldConfig != none && oldConfig.Compare(newConfig, SCASE_INSENSITIVE))
announcer.AnnounceFailedAlreadyEnabled(selectedFeatureName, newConfig); {
announcer.AnnounceFailedAlreadyEnabled(selectedFeatureClass, newConfig);
_.memory.Free(newConfig); _.memory.Free(newConfig);
_.memory.Free(oldConfig); _.memory.Free(oldConfig);
return; return;
} }
// Try enabling and report the result // Try enabling and report the result
instance = selectedFeatureClass.static.EnableMe(newConfig); instance = selectedFeatureClass.static.EnableMe(newConfig);
if (instance == none) { if (instance == none)
{
announcer.AnnounceFailedCannotEnableFeature( announcer.AnnounceFailedCannotEnableFeature(
selectedFeatureName, selectedFeatureClass,
newConfig); newConfig);
} else if (wasEnabled) { }
else if (wasEnabled)
{
announcer.AnnounceSwappedConfig( announcer.AnnounceSwappedConfig(
selectedFeatureName, selectedFeatureClass,
oldConfig, oldConfig,
newConfig); newConfig);
} else { }
announcer.AnnounceEnabledFeature(selectedFeatureName, newConfig); else {
announcer.AnnounceEnabledFeature(selectedFeatureClass, newConfig);
} }
_.memory.Free(newConfig); _.memory.Free(newConfig);
_.memory.Free(oldConfig); _.memory.Free(oldConfig);
} }
protected function DisableFeature() { protected function DisableFeature()
if (!selectedFeatureClass.static.IsEnabled()) { {
announcer.AnnounceFailedAlreadyDisabled(selectedFeatureName); if (!selectedFeatureClass.static.IsEnabled())
{
announcer.AnnounceFailedAlreadyDisabled(selectedFeatureClass);
return; return;
} }
selectedFeatureClass.static.DisableMe(); selectedFeatureClass.static.DisableMe();
// It is possible that this command itself is destroyed after above command // It is possible that this command itself is destroyed after above command
// so do the check just in case // so do the check just in case
if (IsAllocated()) { if (IsAllocated()) {
announcer.AnnounceDisabledFeature(selectedFeatureName); announcer.AnnounceDisabledFeature(selectedFeatureClass);
} }
} }
protected function ShowSelectedConfigs(bool showAllFeatures) { protected function ShowFeatureConfig()
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 MutableText dataAsJSON;
local HashTable currentData, pendingData; local HashTable currentData, pendingData;
if (configName == none) { if (selectedConfigName == none) {
return; return;
} }
currentData = GetCurrentConfigData(configName); currentData = GetCurrentConfigData();
if (currentData == none) { if (currentData == none)
{
announcer.AnnounceFailedNoDataForConfig( announcer.AnnounceFailedNoDataForConfig(
selectedFeatureName, selectedFeatureClass,
configName); selectedConfigName);
return; return;
} }
// Display current data // Display current data
dataAsJSON = _.json.PrettyPrint(currentData); dataAsJSON = _.json.PrettyPrint(currentData);
announcer.AnnounceCurrentConfig(selectedFeatureName, configName); announcer.AnnounceCurrentConfig(selectedFeatureClass, selectedConfigName);
callerConsole.Flush().WriteLine(dataAsJSON); callerConsole.Flush().WriteLine(dataAsJSON);
_.memory.Free(dataAsJSON); _.memory.Free(dataAsJSON);
// Display pending data // Display pending data
pendingConfigs.SelectConfig(selectedFeatureClass, configName);
pendingData = pendingConfigs.GetPendingConfigData(); pendingData = pendingConfigs.GetPendingConfigData();
if (pendingData != none) { if (pendingData != none)
{
dataAsJSON = _.json.PrettyPrint(pendingData); dataAsJSON = _.json.PrettyPrint(pendingData);
announcer.AnnouncePendingConfig( announcer.AnnouncePendingConfig(
selectedFeatureName, selectedFeatureClass,
configName); selectedConfigName);
callerConsole.Flush().WriteLine(dataAsJSON); callerConsole.Flush().WriteLine(dataAsJSON);
_.memory.Free(dataAsJSON); _.memory.Free(dataAsJSON);
} }
@ -264,17 +192,20 @@ protected function ShowFeatureConfig(BaseText configName) {
_.memory.Free(currentData); _.memory.Free(currentData);
} }
protected function Text PickConfigBasedOnParameter() { protected function Text PickConfigBasedOnParameter()
{
local Text resolvedConfig; local Text resolvedConfig;
local class<FeatureConfig> configClass; local class<FeatureConfig> configClass;
configClass = selectedFeatureClass.default.configClass; configClass = selectedFeatureClass.default.configClass;
if (configClass == none) { if (configClass == none)
announcer.AnnounceFailedNoConfigClass(selectedFeatureName); {
announcer.AnnounceFailedNoConfigClass(selectedFeatureClass);
return none; return none;
} }
// If config was specified - simply check that it exists // If config was specified - simply check that it exists
if (selectedConfigName != none) { if (selectedConfigName != none)
{
if (configClass.static.Exists(selectedConfigName)) { if (configClass.static.Exists(selectedConfigName)) {
return selectedConfigName.Copy(); return selectedConfigName.Copy();
} }
@ -284,296 +215,143 @@ protected function Text PickConfigBasedOnParameter() {
// If it wasn't specified - try auto config instead // If it wasn't specified - try auto config instead
resolvedConfig = configClass.static.GetAutoEnabledConfig(); resolvedConfig = configClass.static.GetAutoEnabledConfig();
if (resolvedConfig == none) { if (resolvedConfig == none) {
announcer.AnnounceFailedNoConfigProvided(selectedFeatureName); announcer.AnnounceFailedNoConfigProvided(selectedFeatureClass);
} }
return resolvedConfig; return resolvedConfig;
} }
protected function class<Feature> LoadFeatureClass(BaseText featureClassName) { protected function class<Feature> LoadFeatureClass(BaseText featureName)
{
local Text featureClassName;
local class<Feature> featureClass; local class<Feature> featureClass;
if (featureName == none) {
if (featureClassName == none) {
return none; return none;
} }
if (featureName.StartsWith(P("$"))) {
featureClassName = _.alias.ResolveFeature(featureName, true);
}
else {
featureClassName = featureName.Copy();
}
featureClass = class<Feature>(_.memory.LoadClass(featureClassName)); featureClass = class<Feature>(_.memory.LoadClass(featureClassName));
if (featureClass == none) { if (featureClass == none) {
announcer.AnnounceFailedToLoadFeatureClass(featureClassName); announcer.AnnounceFailedToLoadFeatureClass(featureName);
} }
_.memory.Free(featureClassName);
return featureClass; return featureClass;
} }
protected function ShowAllFeatures() { protected function ShowAllFeatures()
{
local int i; local int i;
local array< class<Feature> > availableFeatures; local array< class<Feature> > availableFeatures;
availableFeatures = _.environment.GetAvailableFeatures(); availableFeatures = _.environment.GetAvailableFeatures();
for (i = 0; i < availableFeatures.length; i ++) { for (i = 0; i < availableFeatures.length; i ++) {
ShowFeature(availableFeatures[i]); ShowFeature(availableFeatures[i]);
} }
} }
protected function ShowFeature(class<Feature> featureClass) protected function ShowFeature(class<Feature> feature)
{ {
local MutableText featureName; local int i;
local MutableText configList; local Text autoConfig;
local MutableText featureName, builder, nextConfig;
local ListBuilder configList;
local array<Text> availableConfigs;
local class<FeatureConfig> configClass;
if (featureClass == none) { if (feature == none) {
return; return;
} }
configClass = feature.default.configClass;
if (configClass != none) {
availableConfigs = configClass.static.AvailableConfigs();
}
featureName = _.text featureName = _.text
.FromClassM(featureClass) .FromClassM(feature)
.ChangeDefaultColor(_.color.TextEmphasis); .ChangeDefaultColor(_.color.TextEmphasis);
configList = PrintConfigList(featureClass); builder = _.text.Empty();
callerConsole.Flush(); if (feature.static.IsEnabled()) {
if (featureClass.static.IsEnabled()) { builder.Append(F("[ {$TextPositive enabled} ] "));
callerConsole.Write(F("[ {$TextPositive enabled} ] "));
} }
else { else {
callerConsole.Write(F("[ {$TextNegative disabled} ] ")); builder.Append(F("[ {$TextNegative disabled} ] "));
} }
callerConsole.Write(featureName) builder.Append(featureName);
.Write(P(" with configs: "))
.WriteLine(configList);
_.memory.Free(featureName); _.memory.Free(featureName);
_.memory.Free(configList); if (availableConfigs.length == 1) {
} builder.Append(P(" with config: "));
}
protected function MutableText PrintConfigList(class<Feature> featureClass) { else if (availableConfigs.length > 1) {
local int i; builder.Append(P(" with configs: "));
local Text autoConfig, enabledConfig; }
local ListBuilder configList; callerConsole.Write(builder);
local MutableText result, nextConfig; _.memory.Free(builder);
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')); configList = ListBuilder(_.memory.Allocate(class'ListBuilder'));
for (i = 0; i < availableConfigs.length; i += 1) { autoConfig = configClass.static.GetAutoEnabledConfig();
for (i = 0; i < availableConfigs.length; i += 1)
{
nextConfig = availableConfigs[i].MutableCopy(); nextConfig = availableConfigs[i].MutableCopy();
if (enabledConfig != none && enabledConfig.Compare(nextConfig, SCASE_INSENSITIVE)) { if (pendingConfigs.HasPendingConfigFor(feature, nextConfig)) {
nextConfig.ChangeDefaultColor(_.color.TextPositive); nextConfig.Append(P("*"));
}
if (pendingConfigs.HasPendingConfigFor(featureClass, nextConfig)) {
nextConfig.Append(F("{$TextEmphasis *}"));
} }
configList.Item(nextConfig); configList.Item(nextConfig);
_.memory.Free(nextConfig); _.memory.Free(nextConfig);
if (autoConfig != none && autoConfig.Compare(availableConfigs[i], SCASE_INSENSITIVE)) { if ( autoConfig != none
if (autoConfig.Compare(enabledConfig, SCASE_INSENSITIVE)) { && autoConfig.Compare(availableConfigs[i], SCASE_INSENSITIVE))
{
configList.Comment(F("{$TextPositive auto enabled}")); configList.Comment(F("{$TextPositive auto enabled}"));
} else {
configList.Comment(F("{$TextNegative auto enabled}"));
}
} }
} }
result = configList.GetMutable(); builder = configList.GetMutable();
_.memory.Free3(configList, autoConfig, enabledConfig); callerConsole.WriteLine(builder);
_.memory.FreeMany(availableConfigs); _.memory.FreeMany(availableConfigs);
return result; _.memory.Free(configList);
} _.memory.Free(autoConfig);
_.memory.Free(builder);
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) { protected function EditFeatureConfig(BaseText pathToValue, BaseText newValue)
local MutableText printedOldValue; {
local MutableText printedNewValue; local PendingConfigsTool.PendingConfigToolError error;
local PendingConfigsTool.PendingConfigToolResult error;
if (selectedFeatureClass == none) { error = pendingConfigs.ChangeConfig(pathToValue, newValue);
return;
}
printedOldValue = PrettyPrintValueAt(pathToValue);
error = pendingConfigs.EditConfig(pathToValue, newValue);
if (error == PCTE_None) {
printedNewValue = PrettyPrintValueAt(pathToValue);
}
if (error == PCTE_ConfigMissing) { if (error == PCTE_ConfigMissing) {
announcer.AnnounceFailedConfigMissing(selectedConfigName); announcer.AnnounceFailedConfigMissing(selectedConfigName);
} }
else if (error == PCTE_ExpectedObject) { else if (error == PCTE_ExpectedObject) {
announcer.AnnounceFailedExpectedObject(); announcer.AnnounceFailedExpectedObject();
} else if (error == PCTE_BadPointer) { }
else if (error == PCTE_BadPointer)
{
announcer.AnnounceFailedBadPointer( announcer.AnnounceFailedBadPointer(
selectedFeatureName, selectedFeatureClass,
selectedConfigName, selectedConfigName,
pathToValue); 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() { private function HashTable GetCurrentConfigData()
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; local class<FeatureConfig> configClass;
if (configName == none) { if (selectedConfigName == none) {
return none; return none;
} }
configClass = selectedFeatureClass.default.configClass; configClass = selectedFeatureClass.default.configClass;
if (configClass == none) { if (configClass == none)
announcer.AnnounceFailedNoConfigClass(selectedFeatureName); {
announcer.AnnounceFailedNoConfigClass(selectedFeatureClass);
return none; return none;
} }
return configClass.static.LoadData(configName); return configClass.static.LoadData(selectedConfigName);
} }
private function HashTable GetCurrentSelectedConfigData() { // 5. Add `saveconf` and `--save` flag
return GetCurrentConfigData(selectedConfigName); // 6. Add `removeconf`
} // 7. Add `newconf`
// 8. Add `autoconf` for setting auto config
defaultproperties { defaultproperties
preferredName = "feature" {
} }

342
sources/Commands/ACommandFeature_Announcer.uc

@ -20,62 +20,38 @@
class ACommandFeature_Announcer extends CommandAnnouncer; class ACommandFeature_Announcer extends CommandAnnouncer;
var private AnnouncementVariations enabledFeature, disabledFeature; var private AnnouncementVariations enabledFeature, disabledFeature;
var private AnnouncementVariations swappedConfig, pendingConfigSaved; var private AnnouncementVariations swappedConfig;
var private AnnouncementVariations showCurrentConfig, showPendingConfig; 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 failedToLoadFeatureClass;
var private AnnouncementVariations failedNoConfigProvided, failedConfigMissing; var private AnnouncementVariations failedNoConfigProvided, failedConfigMissing;
var private AnnouncementVariations failedCannotEnableFeature; var private AnnouncementVariations failedCannotEnableFeature;
var private AnnouncementVariations failedNoConfigClass, failedBadConfigName; var private AnnouncementVariations failedNoConfigClass;
var private AnnouncementVariations failedAlreadyEnabled, failedAlreadyDisabled; var private AnnouncementVariations failedAlreadyEnabled, failedAlreadyDisabled;
var private AnnouncementVariations failedNoDataForConfig, failedExpectedObject; var private AnnouncementVariations failedNoDataForConfig, failedExpectedObject;
var private AnnouncementVariations failedBadPointer, failedPendingConfigMissing; var private AnnouncementVariations failedBadPointer;
protected function Finalizer() protected function Finalizer()
{ {
FreeVariations(enabledFeature); FreeVariations(enabledFeature);
FreeVariations(disabledFeature); FreeVariations(disabledFeature);
FreeVariations(swappedConfig); FreeVariations(swappedConfig);
FreeVariations(pendingConfigSaved);
FreeVariations(showCurrentConfig); FreeVariations(showCurrentConfig);
FreeVariations(showPendingConfig); 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(failedToLoadFeatureClass);
FreeVariations(failedNoConfigProvided); FreeVariations(failedNoConfigProvided);
FreeVariations(failedConfigMissing); FreeVariations(failedConfigMissing);
FreeVariations(failedCannotEnableFeature); FreeVariations(failedCannotEnableFeature);
FreeVariations(failedNoConfigClass); FreeVariations(failedNoConfigClass);
FreeVariations(failedBadConfigName);
FreeVariations(failedAlreadyEnabled); FreeVariations(failedAlreadyEnabled);
FreeVariations(failedAlreadyDisabled); FreeVariations(failedAlreadyDisabled);
FreeVariations(failedNoDataForConfig); FreeVariations(failedNoDataForConfig);
FreeVariations(failedExpectedObject); FreeVariations(failedExpectedObject);
FreeVariations(failedBadPointer); FreeVariations(failedBadPointer);
FreeVariations(failedPendingConfigMissing);
super.Finalizer(); super.Finalizer();
} }
public final function AnnounceEnabledFeature( public final function AnnounceEnabledFeature(
BaseText featureName, class<Feature> featureClass,
BaseText configName) BaseText configName)
{ {
local int i; local int i;
@ -93,12 +69,12 @@ public final function AnnounceEnabledFeature(
} }
templates = MakeArray(enabledFeature); templates = MakeArray(enabledFeature);
for (i = 0; i < templates.length; i += 1) { for (i = 0; i < templates.length; i += 1) {
templates[i].Reset().Arg(featureName).Arg(configName); templates[i].Reset().ArgClass(featureClass).Arg(configName);
} }
MakeAnnouncement(enabledFeature); MakeAnnouncement(enabledFeature);
} }
public final function AnnounceDisabledFeature(BaseText featureName) public final function AnnounceDisabledFeature(class<Feature> featureClass)
{ {
local int i; local int i;
local array<TextTemplate> templates; local array<TextTemplate> templates;
@ -114,13 +90,13 @@ public final function AnnounceDisabledFeature(BaseText featureName)
} }
templates = MakeArray(disabledFeature); templates = MakeArray(disabledFeature);
for (i = 0; i < templates.length; i += 1) { for (i = 0; i < templates.length; i += 1) {
templates[i].Reset().Arg(featureName); templates[i].Reset().ArgClass(featureClass);
} }
MakeAnnouncement(disabledFeature); MakeAnnouncement(disabledFeature);
} }
public final function AnnounceSwappedConfig( public final function AnnounceSwappedConfig(
BaseText featureName, class<Feature> featureClass,
BaseText oldConfig, BaseText oldConfig,
BaseText newConfig) BaseText newConfig)
{ {
@ -142,54 +118,15 @@ public final function AnnounceSwappedConfig(
{ {
templates[i] templates[i]
.Reset() .Reset()
.Arg(featureName) .ArgClass(featureClass)
.Arg(oldConfig) .Arg(oldConfig)
.Arg(newConfig); .Arg(newConfig);
} }
MakeAnnouncement(swappedConfig); 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( public final function AnnounceCurrentConfig(
BaseText featureName, class<Feature> featureClass,
BaseText config) BaseText config)
{ {
if (!showCurrentConfig.initialized) if (!showCurrentConfig.initialized)
@ -200,13 +137,13 @@ public final function AnnounceCurrentConfig(
} }
showCurrentConfig.toSelfReport showCurrentConfig.toSelfReport
.Reset() .Reset()
.Arg(featureName) .ArgClass(featureClass)
.Arg(config); .Arg(config);
MakeAnnouncement(showCurrentConfig); MakeAnnouncement(showCurrentConfig);
} }
public final function AnnouncePendingConfig( public final function AnnouncePendingConfig(
BaseText featureName, class<Feature> featureClass,
BaseText config) BaseText config)
{ {
if (!showPendingConfig.initialized) if (!showPendingConfig.initialized)
@ -217,200 +154,11 @@ public final function AnnouncePendingConfig(
} }
showPendingConfig.toSelfReport showPendingConfig.toSelfReport
.Reset() .Reset()
.Arg(featureName) .ArgClass(featureClass)
.Arg(config); .Arg(config);
MakeAnnouncement(showPendingConfig); 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) public final function AnnounceFailedToLoadFeatureClass(BaseText failedClassName)
{ {
if (!failedToLoadFeatureClass.initialized) if (!failedToLoadFeatureClass.initialized)
@ -424,7 +172,7 @@ public final function AnnounceFailedToLoadFeatureClass(BaseText failedClassName)
} }
public final function AnnounceFailedNoConfigProvided( public final function AnnounceFailedNoConfigProvided(
BaseText featureName) class<Feature> featureClass)
{ {
if (!failedNoConfigProvided.initialized) if (!failedNoConfigProvided.initialized)
{ {
@ -433,7 +181,7 @@ public final function AnnounceFailedNoConfigProvided(
"{$TextFailure No config specified} and {$TextFailure no" "{$TextFailure No config specified} and {$TextFailure no"
@ "auto-enabled config} exists for feature {$TextEmphasis `%1`}"); @ "auto-enabled config} exists for feature {$TextEmphasis `%1`}");
} }
failedNoConfigProvided.toSelfReport.Reset().Arg(featureName); failedNoConfigProvided.toSelfReport.Reset().ArgClass(featureClass);
MakeAnnouncement(failedNoConfigProvided); MakeAnnouncement(failedNoConfigProvided);
} }
@ -449,21 +197,8 @@ public final function AnnounceFailedConfigMissing(BaseText config)
MakeAnnouncement(failedConfigMissing); 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( public final function AnnounceFailedCannotEnableFeature(
BaseText featureName, class<Feature> featureClass,
BaseText config) BaseText config)
{ {
if (!failedCannotEnableFeature.initialized) if (!failedCannotEnableFeature.initialized)
@ -475,72 +210,59 @@ public final function AnnounceFailedCannotEnableFeature(
} }
failedCannotEnableFeature.toSelfReport failedCannotEnableFeature.toSelfReport
.Reset() .Reset()
.Arg(featureName) .ArgClass(featureClass)
.Arg(config); .Arg(config);
MakeAnnouncement(failedCannotEnableFeature); MakeAnnouncement(failedCannotEnableFeature);
} }
public final function AnnounceFailedNoConfigClass( public final function AnnounceFailedNoConfigClass(
BaseText featureName) class<Feature> featureClass)
{ {
if (!failedNoConfigClass.initialized) if (!failedNoConfigClass.initialized)
{ {
failedNoConfigClass.initialized = true; failedNoConfigClass.initialized = true;
failedNoConfigClass.toSelfReport = _.text.MakeTemplate_S( failedNoConfigClass.toSelfReport = _.text.MakeTemplate_S(
"Feature {$TextEmphasis `%1`} {$TextFailure does not have} config" "Feature {$TextEmphasis `%1`} {$TextFailure does not have config"
@ "class! This is most likely caused by its faulty" @ "class}! This is most likely caused by its faulty"
@ "implementation"); @ "implementation");
} }
failedNoConfigClass.toSelfReport.Reset().Arg(featureName); failedNoConfigClass.toSelfReport.Reset().ArgClass(featureClass);
MakeAnnouncement(failedNoConfigClass); 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( public final function AnnounceFailedAlreadyDisabled(
BaseText featureName) class<Feature> featureClass)
{ {
if (!failedAlreadyDisabled.initialized) if (!failedAlreadyDisabled.initialized)
{ {
failedAlreadyDisabled.initialized = true; failedAlreadyDisabled.initialized = true;
failedAlreadyDisabled.toSelfReport = _.text.MakeTemplate_S( failedAlreadyDisabled.toSelfReport = _.text.MakeTemplate_S(
"{$TextFailure Cannot disable} feature {$TextEmphasis `%1`}: it is" "Feature {$TextEmphasis `%1`} is already {$TextNegative disabled}");
@ "already {$TextNegative disabled}");
} }
failedAlreadyDisabled.toSelfReport.Reset().Arg(featureName); failedAlreadyDisabled.toSelfReport.Reset().ArgClass(featureClass);
MakeAnnouncement(failedAlreadyDisabled); MakeAnnouncement(failedAlreadyDisabled);
} }
public final function AnnounceFailedAlreadyEnabled( public final function AnnounceFailedAlreadyEnabled(
BaseText featureName, class<Feature> featureClass,
BaseText config) BaseText config)
{ {
if (!failedAlreadyEnabled.initialized) if (!failedAlreadyEnabled.initialized)
{ {
failedAlreadyEnabled.initialized = true; failedAlreadyEnabled.initialized = true;
failedAlreadyEnabled.toSelfReport = _.text.MakeTemplate_S( failedAlreadyEnabled.toSelfReport = _.text.MakeTemplate_S(
"{$TextFailure Cannot enable} feature {$TextEmphasis `%1`}: it is" "Feature {$TextEmphasis `%1`} is already {$TextNegative enabled}"
@ "already {$TextPositive enabled} with specified config \"%2\""); @ "with specified config \"%2\"");
} }
failedAlreadyEnabled.toSelfReport failedAlreadyEnabled.toSelfReport
.Reset() .Reset()
.Arg(featureName) .ArgClass(featureClass)
.Arg(config); .Arg(config);
MakeAnnouncement(failedAlreadyEnabled); MakeAnnouncement(failedAlreadyEnabled);
} }
public final function AnnounceFailedNoDataForConfig( public final function AnnounceFailedNoDataForConfig(
BaseText featureName, class<Feature> featureClass,
BaseText config) BaseText config)
{ {
if (!failedNoDataForConfig.initialized) if (!failedNoDataForConfig.initialized)
@ -552,7 +274,7 @@ public final function AnnounceFailedNoDataForConfig(
} }
failedNoDataForConfig.toSelfReport failedNoDataForConfig.toSelfReport
.Reset() .Reset()
.Arg(featureName) .ArgClass(featureClass)
.Arg(config); .Arg(config);
MakeAnnouncement(failedNoDataForConfig); MakeAnnouncement(failedNoDataForConfig);
} }
@ -570,7 +292,7 @@ public final function AnnounceFailedExpectedObject()
} }
public final function AnnounceFailedBadPointer( public final function AnnounceFailedBadPointer(
BaseText featureName, class<Feature> featureClass,
BaseText config, BaseText config,
BaseText pointer) BaseText pointer)
{ {
@ -583,7 +305,7 @@ public final function AnnounceFailedBadPointer(
} }
failedBadPointer.toSelfReport failedBadPointer.toSelfReport
.Reset() .Reset()
.Arg(featureName) .ArgClass(featureClass)
.Arg(config) .Arg(config)
.Arg(pointer); .Arg(pointer);
MakeAnnouncement(failedBadPointer); MakeAnnouncement(failedBadPointer);

30
sources/Commands/ACommandGod.uc

@ -46,21 +46,21 @@ protected function Finalizer()
protected function BuildData(CommandDataBuilder builder) protected function BuildData(CommandDataBuilder builder)
{ {
builder.Group(P("gameplay")); builder.Name(P("god")).Group(P("gameplay"))
builder.Summary(P("Command for making player immortal.")); .Summary(P("Command for making player immortal."));
builder.RequireTarget(); builder.RequireTarget()
builder.Describe(P("Gives targeted players god status, making them" .Describe(P("Gives targeted players god status, making them"
@ "invincible.")); @ "invincible."));
builder.SubCommand(P("list")); builder.SubCommand(P("list"))
builder.Describe(P("Reports godhood status of targeted players.")); .Describe(P("Reports godhood status of targeted players."));
builder.SubCommand(P("strip")); builder.SubCommand(P("strip"))
builder.Describe(P("Strips targeted players from the godhood status.")); .Describe(P("Strips targeted players from the godhood status."));
builder.Option(P("demi")); builder.Option(P("demi"))
builder.Describe(P("This flag makes targeted players \"demigods\" instead -" .Describe(P("This flag makes targeted players \"demigods\" instead -"
@ "they still cannot die, but they can take any non-lethal" @ "they still cannot die, but they can take any non-lethal"
@ "damage.")); @ "damage."));
builder.Option(P("unmovable")); builder.Option(P("unmovable"))
builder.Describe(P("This flag also prevents targeted players from being" .Describe(P("This flag also prevents targeted players from being"
@ "affected by the momentum trasnferred from damaging attacks.")); @ "affected by the momentum trasnferred from damaging attacks."));
announcer = ACommandGod_Announcer( announcer = ACommandGod_Announcer(
_.memory.Allocate(class'ACommandGod_Announcer')); _.memory.Allocate(class'ACommandGod_Announcer'));
@ -69,9 +69,8 @@ protected function BuildData(CommandDataBuilder builder)
protected function ExecutedFor( protected function ExecutedFor(
EPlayer target, EPlayer target,
CallData arguments, CallData arguments,
EPlayer instigator, EPlayer instigator)
CommandPermissions permissions {
) {
local GodStatus newGodStatus; local GodStatus newGodStatus;
announcer.Setup(target, instigator, othersConsole); announcer.Setup(target, instigator, othersConsole);
@ -216,7 +215,6 @@ private final function UpdateHealthSignalConnection()
defaultproperties defaultproperties
{ {
preferredName = "god"
TDAMAGE = 0 TDAMAGE = 0
stringConstants(0) = "damage" stringConstants(0) = "damage"
TMOMENTUM = 1 TMOMENTUM = 1

66
sources/Commands/ACommandInventory.uc

@ -34,57 +34,57 @@ var protected const int TLISTS_SKIPPED;
protected function BuildData(CommandDataBuilder builder) protected function BuildData(CommandDataBuilder builder)
{ {
builder.Group(P("gameplay")); builder.Name(T(TINVENTORY)).Group(P("gameplay"))
builder.Summary(P("Manages player's inventory.")); .Summary(P("Manages player's inventory."))
builder.Describe(P("Command for displaying and editing players' inventories." .Describe(P("Command for displaying and editing players' inventories."
@ "If called without specifying subcommand - simply displays" @ "If called without specifying subcommand - simply displays"
@ "targeted player's inventory.")); @ "targeted player's inventory."));
builder.RequireTarget(); builder.RequireTarget();
builder.SubCommand(T(TADD)); builder.SubCommand(T(TADD))
builder.OptionalParams(); .OptionalParams()
builder.ParamTextList(T(TITEMS)); .ParamTextList(T(TITEMS))
builder.Describe(P("This command adds items (based on listed templates) to" .Describe(P("This command adds items (based on listed templates) to"
@ "the targeted player's inventory." @ "the targeted player's inventory."
@ "Instead of templates item aliases can be specified.")); @ "Instead of templates item aliases can be specified."));
builder.SubCommand(T(TREMOVE)); builder.SubCommand(T(TREMOVE))
builder.OptionalParams(); .OptionalParams()
builder.ParamTextList(T(TITEMS)); .ParamTextList(T(TITEMS))
builder.Describe(P("This command removes items (based on listed templates)" .Describe(P("This command removes items (based on listed templates)"
@ "from the targeted player's inventory." @ "from the targeted player's inventory."
@ "Instead of templates item aliases can be specified.")); @ "Instead of templates item aliases can be specified."));
builder.SubCommand(T(TSET)); builder.SubCommand(T(TSET))
builder.OptionalParams(); .OptionalParams()
builder.ParamTextList(T(TITEMS)); .ParamTextList(T(TITEMS))
builder.Describe(P("This command acts like combination of two commands -" .Describe(P("This command acts like combination of two commands -"
@ "first removing all items from the player's current inventory and" @ "first removing all items from the player's current inventory and"
@ "then adding specified items. first clears inventory" @ "then adding specified items. first clears inventory"
@ "(based on specified options) and then ")); @ "(based on specified options) and then "));
builder.Option(T(TEQUIP)); builder.Option(T(TEQUIP))
builder.Describe(F("Affect items currently equipped by the targeted player." .Describe(F("Affect items currently equipped by the targeted player."
@ "Releveant for a {$TextEmphasis remove} subcommand.")); @ "Releveant for a {$TextEmphasis remove} subcommand."));
builder.Option(T(TLIST)); builder.Option(T(TLIST))
builder.Describe(P("Include weapons from specified group into the list.")); .Describe(P("Include weapons from specified group into the list."))
builder.ParamTextList(T(TLISTS_NAMES)); .ParamTextList(T(TLISTS_NAMES));
builder.Option(T(TAMMO)); builder.Option(T(TAMMO))
builder.Describe(P("When adding weapons - signals that their" .Describe(P("When adding weapons - signals that their"
@ "ammo / charge / whatever has to be filled after addition.")); @ "ammo / charge / whatever has to be filled after addition."));
builder.Option(T(TKEEP)); builder.Option(T(TKEEP))
builder.Describe(F("Removing items by default means simply destroying them." .Describe(F("Removing items by default means simply destroying them."
@ "This flag makes command to try and keep them in some form." @ "This flag makes command to try and keep them in some form."
@ "Success for all items is not guaranteed.")); @ "Success for all items is not guaranteed."));
builder.Option(T(THIDDEN)); builder.Option(T(THIDDEN))
builder.Describe(F("Some of the items in the inventory are" .Describe(F("Some of the items in the inventory are"
@ "{$TextEmphasis hidden} and are not supposed to be seem by" @ "{$TextEmphasis hidden} and are not supposed to be seem by"
@ "the player. To avoid weird behavior, {$TextEmphasis inventory}" @ "the player. To avoid weird behavior, {$TextEmphasis inventory}"
@ "command by default ignores them when affecting groups of items" @ "command by default ignores them when affecting groups of items"
@ "(like when removing all items) unless they're directly" @ "(like when removing all items) unless they're directly"
@ "specified. This flag tells it to also affect hidden items.")); @ "specified. This flag tells it to also affect hidden items."));
builder.Option(T(TFORCE)); builder.Option(T(TFORCE))
builder.Describe(P("Sometimes adding and removing items is impossible due to" .Describe(P("Sometimes adding and removing items is impossible due to"
@ "the limitations imposed by the game. This option allows to" @ "the limitations imposed by the game. This option allows to"
@ "ignore some of those limitation.")); @ "ignore some of those limitation."));
builder.Option(T(TALL), P("A")); builder.Option(T(TALL), P("A"))
builder.Describe(F("This flag is used when removing items. If user has" .Describe(F("This flag is used when removing items. If user has"
@ "specified any weapon templates - it means" @ "specified any weapon templates - it means"
@ "\"remove all items with these tempaltes from inventory\"," @ "\"remove all items with these tempaltes from inventory\","
@ "but if user has not specified any templated it simply means" @ "but if user has not specified any templated it simply means"
@ -94,9 +94,8 @@ protected function BuildData(CommandDataBuilder builder)
protected function ExecutedFor( protected function ExecutedFor(
EPlayer target, EPlayer target,
CallData arguments, CallData arguments,
EPlayer instigator, EPlayer instigator)
CommandPermissions permissions {
) {
local InventoryTool tool; local InventoryTool tool;
local ArrayList itemsArray, specifiedLists; local ArrayList itemsArray, specifiedLists;
LoadUserFlags(arguments.options); LoadUserFlags(arguments.options);
@ -287,7 +286,6 @@ protected function array<Text> LoadItemsList(
defaultproperties defaultproperties
{ {
preferredName = "inventory"
TINVENTORY = 0 TINVENTORY = 0
stringConstants(0) = "inventory" stringConstants(0) = "inventory"
TADD = 1 TADD = 1

38
sources/Commands/ACommandNick.uc

@ -34,31 +34,30 @@ protected function Finalizer()
protected function BuildData(CommandDataBuilder builder) protected function BuildData(CommandDataBuilder builder)
{ {
builder.Group(P("gameplay")); builder.Name(P("nick")).Group(P("gameplay"))
builder.Summary(P("Changes nickname.")); .Summary(P("Changes nickname."));
builder.RequireTarget(); builder.RequireTarget();
builder.ParamRemainder(P("nick")); builder.ParamRemainder(P("nick"))
builder.Describe(P("Changes nickname of targeted players to <nick>.")); .Describe(P("Changes nickname of targeted players to <nick>."));
builder.Option(P("plain")); builder.Option(P("plain"))
builder.Describe(P("Take nickname exactly as typed, without attempting to" .Describe(P("Take nickname exactly as typed, without attempting to"
@ "treat it like formatted string.")); @ "treat it like formatted string."));
builder.Option(P("fix"), P("f")); builder.Option(P("fix"), P("f"))
builder.Describe(P("In case of a nickname with erroroneous formatting or" .Describe(P("In case of a nickname with erroroneous formatting or"
@ "invalid default color (specified with `--color`)," @ "invalid default color (specified with `--color`),"
@ "try to fix/ignore it instead of simply rejecting it.")); @ "try to fix/ignore it instead of simply rejecting it."));
builder.Option(P("color")); builder.Option(P("color"))
builder.Describe(P("Color to use for the nickname. In case nickname is already" .Describe(P("Color to use for the nickname. In case nickname is already"
@ "colored, this flag will only affects uncolored parts.")); @ "colored, this flag will only affects uncolored parts."))
builder.ParamText(P("default_color")); .ParamText(P("default_color"));
announcer = ACommandNick_Announcer( announcer = ACommandNick_Announcer(
_.memory.Allocate(class'ACommandNick_Announcer')); _.memory.Allocate(class'ACommandNick_Announcer'));
} }
protected function Executed( protected function Executed(
CallData arguments, CallData arguments,
EPlayer callerPlayer, EPlayer callerPlayer)
CommandPermissions permissions {
) {
local Text givenName; local Text givenName;
local array<FormattingErrorsReport.FormattedStringError> errors; local array<FormattingErrorsReport.FormattedStringError> errors;
@ -90,9 +89,8 @@ protected function Executed(
protected function ExecutedFor( protected function ExecutedFor(
EPlayer target, EPlayer target,
CallData arguments, CallData arguments,
EPlayer instigator, EPlayer instigator)
CommandPermissions permissions {
) {
local Text alteredVersion; local Text alteredVersion;
if (!foundErrors || arguments.options.HasKey(P("fix"))) if (!foundErrors || arguments.options.HasKey(P("fix")))
@ -128,6 +126,6 @@ protected function bool TryChangeDefaultColor(BaseText specifiedColor)
return false; return false;
} }
defaultproperties { defaultproperties
preferredName = "nick" {
} }

95
sources/Commands/ACommandSpawn.uc

@ -30,17 +30,17 @@ protected function Finalizer()
protected function BuildData(CommandDataBuilder builder) protected function BuildData(CommandDataBuilder builder)
{ {
builder.Group(P("debug")); builder.Name(P("spawn")).Group(P("debug"))
builder.Summary(P("Spawns new entity on the map.")); .Summary(P("Spawns new entity on the map."));
builder.ParamText(P("template"),, P("entity")); builder.ParamText(P("template"))
builder.Describe(P("Spawns new entity based on the given template at the point" .Describe(P("Spawns new entity based on the given template at the point"
@ "player is currently looking at.")); @ "player is currently looking at."));
builder.SubCommand(P("at")); builder.SubCommand(P("at"))
builder.ParamText(P("template"),, P("entity")); .ParamText(P("template"))
builder.ParamNumber(P("x")); .ParamNumber(P("x"))
builder.ParamNumber(P("y")); .ParamNumber(P("y"))
builder.ParamNumber(P("z")); .ParamNumber(P("z"))
builder.Describe(P("Spawns new entity based on the given template at" .Describe(P("Spawns new entity based on the given template at"
@ "the point, given by the coordinates")); @ "the point, given by the coordinates"));
announcer = ACommandSpawn_Announcer( announcer = ACommandSpawn_Announcer(
_.memory.Allocate(class'ACommandSpawn_Announcer')); _.memory.Allocate(class'ACommandSpawn_Announcer'));
@ -48,58 +48,61 @@ protected function BuildData(CommandDataBuilder builder)
protected function Executed( protected function Executed(
CallData arguments, CallData arguments,
EPlayer instigator, EPlayer instigator)
CommandPermissions permissions {
) { local Text givenTemplate, template;
local HashTable value;
local Vector spawnLocation; local Vector spawnLocation;
announcer.Setup(none, instigator, othersConsole); announcer.Setup(none, instigator, othersConsole);
value = arguments.parameters.GetHashTable(P("template")); givenTemplate = arguments.parameters.GetText(P("template"));
if (givenTemplate.StartsWithS("$")) {
template = _.alias.ResolveEntity(givenTemplate, true);
}
else {
template = givenTemplate.Copy();
}
_.memory.Free(givenTemplate);
if (arguments.subCommandName.IsEmpty()) { if (arguments.subCommandName.IsEmpty()) {
SpawnInInstigatorSight(instigator, value); SpawnInInstigatorSight(instigator, template);
} else if (arguments.subCommandName.Compare(P("at"), SCASE_INSENSITIVE)) { }
else if (arguments.subCommandName.Compare(P("at"), SCASE_INSENSITIVE))
{
spawnLocation.x = arguments.parameters.GetFloat(P("x")); spawnLocation.x = arguments.parameters.GetFloat(P("x"));
spawnLocation.y = arguments.parameters.GetFloat(P("y")); spawnLocation.y = arguments.parameters.GetFloat(P("y"));
spawnLocation.z = arguments.parameters.GetFloat(P("z")); spawnLocation.z = arguments.parameters.GetFloat(P("z"));
SpawnAt(instigator, value, spawnLocation); SpawnAt(instigator, template, spawnLocation);
} }
_.memory.Free(value); _.memory.Free(template);
} }
private final function SpawnAt( private final function SpawnAt(
EPlayer instigator, EPlayer instigator,
HashTable value, BaseText template,
Vector spawnLocation Vector spawnLocation)
) { {
local Text humanReadable, template;
local EPlaceable result; local EPlaceable result;
humanReadable = value.GetText(P("alias"));
template = value.GetText(P("value"));
result = _server.kf.world.Spawn(template, spawnLocation); result = _server.kf.world.Spawn(template, spawnLocation);
if (result != none) { if (result != none) {
announcer.AnnounceSpawned(humanReadable); announcer.AnnounceSpawned(template);
} else { }
announcer.AnnounceSpawningFailed(humanReadable); else {
announcer.AnnounceSpawningFailed(template);
} }
_.memory.Free2(humanReadable, result); _.memory.Free(result);
} }
private final function SpawnInInstigatorSight( private final function SpawnInInstigatorSight(
EPlayer instigator, EPlayer instigator,
HashTable value BaseText template)
) { {
local EPlaceable result; local EPlaceable result;
local Vector spawnLocation; local Vector spawnLocation;
local TracingIterator iter; local TracingIterator iter;
local Text humanReadable, template;
humanReadable = value.GetText(P("alias"));
template = value.GetText(P("value"));
iter = _server.kf.world.TracePlayerSight(instigator); iter = _server.kf.world.TracePlayerSight(instigator);
iter.LeaveOnlyVisible(); if (iter.LeaveOnlyVisible().HasFinished())
if (iter.HasFinished()) { {
announcer.AnnounceFailedTrace(); announcer.AnnounceFailedTrace();
return; return;
} }
@ -107,18 +110,22 @@ private final function SpawnInInstigatorSight(
result = _server.kf.world.Spawn(template, spawnLocation); result = _server.kf.world.Spawn(template, spawnLocation);
// Shift position back a little and try again; // Shift position back a little and try again;
// this should fix a ton of spawning failures // this should fix a ton of spawning failures
if (result == none) { if (result == none)
spawnLocation = spawnLocation + Normal(iter.GetTracingStart() - spawnLocation) * 100; {
spawnLocation = spawnLocation +
Normal(iter.GetTracingStart() - spawnLocation) * 100;
result = _server.kf.world.Spawn(template, spawnLocation); result = _server.kf.world.Spawn(template, spawnLocation);
} }
if (result != none) { if (result != none) {
announcer.AnnounceSpawned(humanReadable); announcer.AnnounceSpawned(template);
} else {
announcer.AnnounceSpawningFailed(humanReadable);
} }
_.memory.Free4(result, iter, humanReadable, result); else {
announcer.AnnounceSpawningFailed(template);
}
_.memory.Free(result);
_.memory.Free(iter);
} }
defaultproperties { defaultproperties
preferredName = "spawn" {
} }

104
sources/Commands/ACommandTrader.uc

@ -38,60 +38,60 @@ protected function Finalizer()
protected function BuildData(CommandDataBuilder builder) protected function BuildData(CommandDataBuilder builder)
{ {
builder.Group(P("gameplay")); builder.Name(T(TTRADER)).Group(P("gameplay"))
builder.Summary(P("Manages trader time and available traders.")); .Summary(P("Manages trader time and available traders."))
builder.Describe(P("Enables of disables trading.")); .Describe(P("Enables of disables trading."))
builder.ParamBoolean(T(TENABLE)); .ParamBoolean(T(TENABLE));
builder.SubCommand(T(TTIME)); builder.SubCommand(T(TTIME))
builder.Describe(F("Changes current trader time if numeric value is specified." .Describe(F("Changes current trader time if numeric value is specified."
@ "You can also pause trader countdown by specifying" @ "You can also pause trader countdown by specifying"
@ "{$TextEmphasis pause} or turn it back on with" @ "{$TextEmphasis pause} or turn it back on with"
@ "{$TextEmphasis unpause}.")); @ "{$TextEmphasis unpause}."))
builder.ParamText(T(TTRADER_TIME)); .ParamText(T(TTRADER_TIME));
builder.SubCommand(T(TLIST)); builder.SubCommand(T(TLIST))
builder.Describe(P("Lists names of all available traders and" .Describe(P("Lists names of all available traders and"
@ "marks closest one to the caller.")); @ "marks closest one to the caller."));
builder.SubCommand(T(TOPEN)); builder.SubCommand(T(TOPEN))
builder.Describe(P("Opens specified traders.")); .Describe(P("Opens specified traders."))
builder.OptionalParams(); .OptionalParams()
builder.ParamTextList(T(TTRADERS)); .ParamTextList(T(TTRADERS));
builder.SubCommand(T(TCLOSE)); builder.SubCommand(T(TCLOSE))
builder.Describe(P("Closes specified traders.")); .Describe(P("Closes specified traders."))
builder.OptionalParams(); .OptionalParams()
builder.ParamTextList(T(TTRADERS)); .ParamTextList(T(TTRADERS));
builder.SubCommand(T(TAUTO_OPEN)); builder.SubCommand(T(TAUTO_OPEN))
builder.Describe(P("Sets whether specified traders are open automatically.")); .Describe(P("Sets whether specified traders are open automatically."))
builder.ParamBoolean(T(TAUTO_OPEN_QUESTION)); .ParamBoolean(T(TAUTO_OPEN_QUESTION))
builder.OptionalParams(); .OptionalParams()
builder.ParamTextList(T(TTRADERS)); .ParamTextList(T(TTRADERS));
builder.SubCommand(T(TSELECT)); builder.SubCommand(T(TSELECT))
builder.Describe(P("Selects specified trader.")); .Describe(P("Selects specified trader."))
builder.OptionalParams(); .OptionalParams()
builder.ParamText(T(TTRADER)); .ParamText(T(TTRADER));
builder.SubCommand(T(TBOOT)); builder.SubCommand(T(TBOOT))
builder.Describe(P("Boots all players from specified traders. If no traders" .Describe(P("Boots all players from specified traders. If no traders"
@ "were specified - assumes that all of them should be affected.")); @ "were specified - assumes that all of them should be affected."))
builder.OptionalParams(); .OptionalParams()
builder.ParamTextList(T(TTRADERS)); .ParamTextList(T(TTRADERS));
builder.SubCommand(T(TENABLE)); builder.SubCommand(T(TENABLE))
builder.Describe(P("Enables specified traders.")); .Describe(P("Enables specified traders."))
builder.OptionalParams(); .OptionalParams()
builder.ParamTextList(T(TTRADERS)); .ParamTextList(T(TTRADERS));
builder.SubCommand(T(TDISABLE)); builder.SubCommand(T(TDISABLE))
builder.Describe(P("Disables specified traders.")); .Describe(P("Disables specified traders."))
builder.OptionalParams(); .OptionalParams()
builder.ParamTextList(T(TTRADERS)); .ParamTextList(T(TTRADERS));
builder.Option(T(TALL)); builder.Option(T(TALL))
builder.Describe(P("If sub-command targets shops, this flag will make it" .Describe(P("If sub-command targets shops, this flag will make it"
@ "target all the available shops.")); @ "target all the available shops."));
builder.Option(T(TCLOSEST)); builder.Option(T(TCLOSEST))
builder.Describe(P("If sub-command targets shops, this flag will make it also" .Describe(P("If sub-command targets shops, this flag will make it also"
@ "target closest shop to the caller.")); @ "target closest shop to the caller."));
builder.Option(T(TIGNORE_DOORS)); builder.Option(T(TIGNORE_DOORS))
builder.Describe(F("When used with {$TextEmphasis select} sub-command, it will" .Describe(F("When used with {$TextEmphasis select} sub-command, it will"
@ "neither open or close doors.")); @ "neither open or close doors."));
builder.Option(T(TIGNORE_PLAYERS), P("I")); builder.Option(T(TIGNORE_PLAYERS), P("I"))
builder.Describe(P("Normally commands that close doors will automatically boot" .Describe(P("Normally commands that close doors will automatically boot"
@ "players from inside to prevent locking them in. This flag forces" @ "players from inside to prevent locking them in. This flag forces"
@ "this command to leave players inside. However they can still be" @ "this command to leave players inside. However they can still be"
@ "booted out at the end of trading time. Also it is impossible to" @ "booted out at the end of trading time. Also it is impossible to"
@ -100,11 +100,8 @@ protected function BuildData(CommandDataBuilder builder)
_.memory.Allocate(class'ACommandTrader_Announcer')); _.memory.Allocate(class'ACommandTrader_Announcer'));
} }
protected function Executed( protected function Executed(CallData arguments, EPlayer instigator)
CallData arguments, {
EPlayer instigator,
CommandPermissions permissions
) {
local bool newTradingStatus; local bool newTradingStatus;
announcer.Setup(none, instigator, othersConsole); announcer.Setup(none, instigator, othersConsole);
@ -635,7 +632,6 @@ protected function WriteTraderTags(ETrader traderToWrite, bool isClosest)
defaultproperties defaultproperties
{ {
preferredName = "trader"
TLIST = 0 TLIST = 0
stringConstants(0) = "list" stringConstants(0) = "list"
TOPEN = 1 TOPEN = 1

140
sources/Commands/ACommandUserData.uc

@ -1,6 +1,6 @@
/** /**
* Command for changing amount of money players have. * Command for changing amount of money players have.
* Copyright 2022-2023 Anton Tarasenko * Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -21,23 +21,24 @@ class ACommandUserData extends Command;
var private array<EPlayer> playerQueue; var private array<EPlayer> playerQueue;
// TODO: Finish this command after JSON parameters are added
protected function BuildData(CommandDataBuilder builder) protected function BuildData(CommandDataBuilder builder)
{ {
builder.Group(P("admin")); builder.Name(P("userdata")).Group(P("admin"))
builder.Summary(P("Allows to read and write custom user data for players.")); .Summary(P("Allows to read and write custom user data for players."));
builder.RequireTarget(); builder.RequireTarget();
builder.ParamText(P("groupName")); builder.ParamText(P("groupName"))
builder.OptionalParams(); .OptionalParams()
builder.ParamText(P("dataName")); .ParamText(P("dataName"))
builder.Describe(F("Reads user data stored for targeted user under group" .Describe(P("Reads user data stored for targeted user under group"
@ "{$TextEmphasis `groupName`} and name" @ "{$TextEmphasis `groupName`} and name"
@ "{$TextEmphasis `dataName`}. If {$TextEmphasis `dataName`} is" @ "{$TextEmphasis `dataName`}. If {$TextEmphasis `dataName`} is"
@ "omitted, the data inside the whole group will be read.")); @ "omitted, the data inside the whjole group will be read."));
builder.SubCommand(P("write")); builder.SubCommand(P("write"))
builder.ParamText(P("groupName")); .ParamText(P("groupName"))
builder.ParamText(P("dataName")); .ParamText(P("dataName"))
builder.ParamJSON(P("newData")); .ParamObject(P("newData"))
builder.Describe(F("Stores new user data {$TextEmphasis `newData`} for" .Describe(P("Stores new user data {$TextEmphasis `newData`} for"
@ "targeted user under group {$TextEmphasis `groupName`} and name" @ "targeted user under group {$TextEmphasis `groupName`} and name"
@ "{$TextEmphasis `dataName`}.")); @ "{$TextEmphasis `dataName`}."));
} }
@ -45,85 +46,74 @@ protected function BuildData(CommandDataBuilder builder)
protected function ExecutedFor( protected function ExecutedFor(
EPlayer target, EPlayer target,
CallData arguments, CallData arguments,
EPlayer instigator, EPlayer instigator)
CommandPermissions permissions {
) {
local AcediaObject userData; local AcediaObject userData;
local Text groupName, dataName;
groupName = arguments.parameters.GetText(P("groupName")); if (arguments.subCommandName.IsEmpty())
dataName = arguments.parameters.GetText(P("dataName")); {
userData = arguments.parameters.GetItem(P("newData")); target
if (arguments.subCommandName.IsEmpty()) { .GetIdentity()
ReadUserData(target, groupName, dataName); .ReadPersistentData(
arguments.parameters.GetText(P("groupName")),
arguments.parameters.GetText(P("dataName")))
.connect = UserDataRead;
} }
else { else
WriteUserData(target, groupName, dataName, userData); {
userData = arguments.parameters.GetHashTable(P("newData"));
target
.GetIdentity()
.WritePersistentData(
arguments.parameters.GetText(P("groupName")),
arguments.parameters.GetText(P("dataName")),
userData);
} }
_.memory.Free(dataName); playerQueue[playerQueue.length] = target;
_.memory.Free(groupName); target.NewRef();
} }
private final function ReadUserData( private final function UserDataRead(
EPlayer targetPlayer, Database.DBQueryResult result,
BaseText groupName, AcediaObject userData,
BaseText dataName) Database source)
{ {
local User identity;
local AcediaObject rawData;
local MutableText dataAsJSON;
local Text targetPlayerName; local Text targetPlayerName;
local EPlayer targetPlayer;
local MutableText asJSON;
identity = targetPlayer.GetIdentity(); if (playerQueue.length <= 0)
if (identity == none) { {
targetPlayer
.BorrowConsole()
.UseColorOnce(_.color.TextFailure)
.Write(F("There was an internal error with `userdata` command. "))
.Write(P("Please report it!"));
return; return;
} }
targetPlayerName = targetPlayer.GetName(); targetPlayer = playerQueue[0];
rawData = identity.GetPersistentData(groupName, dataName); playerQueue.Remove(0, 1);
dataAsJSON = _.json.PrettyPrint(rawData); if (result != DBR_Success)
targetPlayer.BorrowConsole() {
.Write(F("User data for player ")) targetPlayer
.Write(targetPlayerName) .BorrowConsole()
.Write(P(": ")) .UseColorOnce(_.color.TextFailure)
.WriteLine(dataAsJSON); .Write(F("There was an error reading user data, error code: "))
_.memory.Free(dataAsJSON); .WriteLine(_.text.FromInt(int(result)));
_.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; return;
} }
targetPlayerName = targetPlayer.GetName(); targetPlayerName = targetPlayer.GetName();
if (identity.SetPersistentData(groupName, dataName, rawData)) asJSON = _.json.PrettyPrint(userData);
{
targetPlayer.BorrowConsole() targetPlayer.BorrowConsole()
.Write(P("User data for player ")) .Write(F("{$TextPositive User data for player}"))
.Write(targetPlayerName) .Write(targetPlayerName)
.WriteLine(F(" was {$TextPositive successfully} changed!")); .Write(P(":"))
} .WriteLine(asJSON);
else _.memory.Free(targetPlayer);
{ _.memory.Free(asJSON);
targetPlayer.BorrowConsole()
.Write(P("User data for player "))
.Write(targetPlayerName)
.WriteLine(F(" has {$TextPositive failed} to change!"));
}
_.memory.Free(targetPlayerName); _.memory.Free(targetPlayerName);
_.memory.Free(identity);
} }
defaultproperties { defaultproperties
preferredName = "userdata" {
} }

80
sources/Features/FutileChat/FutilityChat.uc

@ -1,6 +1,6 @@
/** /**
* Config class for storing map lists. * Config object for `FutilityChat_Feature`.
* Copyright 2022-2023 Anton Tarasenko * Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -21,7 +21,8 @@ class FutilityChat extends FeatureConfig
perobjectconfig perobjectconfig
config(FutilityChat); config(FutilityChat);
enum ChatColorSetting { enum ChatColorSetting
{
CCS_DoNothing, CCS_DoNothing,
CCS_TeamColorForced, CCS_TeamColorForced,
CCS_ConfigColorForced, CCS_ConfigColorForced,
@ -29,45 +30,16 @@ enum ChatColorSetting {
CCS_ConfigColorCustom 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; 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; 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; var public config float teamColorModifier;
protected function HashTable ToData() { protected function HashTable ToData()
{
local HashTable data; local HashTable data;
local Text colorAsText; local Text colorAsText;
data = __().collections.EmptyHashTable(); data = __().collections.EmptyHashTable();
data.SetString(P("colorSetting"), StringFromColorSetting(colorSetting)); data.SetString(P("colorSetting"), string(colorSetting));
colorAsText = _.color.ToText(configuredColor); colorAsText = _.color.ToText(configuredColor);
data.SetItem(P("configuredColor"), colorAsText); data.SetItem(P("configuredColor"), colorAsText);
_.memory.Free(colorAsText); _.memory.Free(colorAsText);
@ -75,10 +47,12 @@ protected function HashTable ToData() {
return data; return data;
} }
protected function FromData(HashTable source) { protected function FromData(HashTable source)
{
local Text storedText; local Text storedText;
if (source == none) {
if (source != none) { return;
}
storedText = source.GetText(P("colorSetting")); storedText = source.GetText(P("colorSetting"));
colorSetting = ColorSettingFromText(storedText); colorSetting = ColorSettingFromText(storedText);
_.memory.Free(storedText); _.memory.Free(storedText);
@ -86,10 +60,11 @@ protected function FromData(HashTable source) {
_.color.Parse(storedText, configuredColor); _.color.Parse(storedText, configuredColor);
_.memory.Free(storedText); _.memory.Free(storedText);
teamColorModifier = source.GetFloat(P("teamColorModifier"), 0.5); teamColorModifier = source.GetFloat(P("teamColorModifier"), 0.5);
}
} }
private function ChatColorSetting ColorSettingFromText(BaseText permissions) { private function ChatColorSetting ColorSettingFromText(
BaseText permissions)
{
if (permissions == none) { if (permissions == none) {
return CCS_DoNothing; return CCS_DoNothing;
} }
@ -108,32 +83,15 @@ private function ChatColorSetting ColorSettingFromText(BaseText permissions) {
return CCS_DoNothing; return CCS_DoNothing;
} }
private function string StringFromColorSetting(ChatColorSetting permissions) { protected function DefaultIt()
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; colorSetting = CCS_DoNothing;
configuredColor = _.color.RGB(255, 255, 255); configuredColor = _.color.RGB(255, 255, 255);
teamColorModifier = 0.6; teamColorModifier = 0.6;
} }
defaultproperties { defaultproperties
{
configName = "FutilityChat" configName = "FutilityChat"
colorSetting = CCS_DoNothing colorSetting = CCS_DoNothing
configuredColor = (R=255,G=255,B=255,A=255) configuredColor = (R=255,G=255,B=255,A=255)

90
sources/Features/FutileChat/FutilityChat_Feature.uc

@ -1,6 +1,6 @@
/** /**
* Config class for storing map lists. * This feature allows to configure color of text chat messages.
* Copyright 2022-2023 Anton Tarasenko * Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -20,22 +20,44 @@
class FutilityChat_Feature extends Feature class FutilityChat_Feature extends Feature
dependson(FutilityChat); dependson(FutilityChat);
// 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 private /*config*/ FutilityChat.ChatColorSetting colorSetting; var private /*config*/ FutilityChat.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 private /*config*/ Color configuredColor; var private /*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 private /*config*/ float teamColorModifier; var private /*config*/ float teamColorModifier;
/// Keep track of whether we connected to necessary signals, so that we can // Keep track of whether we connected to necessary signals, so that we can
/// connect to them or disconnect from them once setting get updated // connect to them or disconnect from them once setting get updated
var private bool connectedToSignal; var private bool connectedToSignal;
protected function OnEnabled() { protected function OnDisabled()
if (colorSetting != CCS_DoNothing) { {
_.chat.OnMessage(self).connect = ReformatChatMessage; if (connectedToSignal)
} {
} connectedToSignal = false;
protected function OnDisabled() {
if (colorSetting != CCS_DoNothing) {
_.chat.OnMessage(self).Disconnect(); _.chat.OnMessage(self).Disconnect();
} }
} }
@ -44,7 +66,6 @@ protected function SwapConfig(FeatureConfig config)
{ {
local bool configRequiresSignal; local bool configRequiresSignal;
local FutilityChat newConfig; local FutilityChat newConfig;
newConfig = FutilityChat(config); newConfig = FutilityChat(config);
if (newConfig == none) { if (newConfig == none) {
return; return;
@ -53,49 +74,69 @@ protected function SwapConfig(FeatureConfig config)
configuredColor = newConfig.configuredColor; configuredColor = newConfig.configuredColor;
teamColorModifier = newConfig.teamColorModifier; teamColorModifier = newConfig.teamColorModifier;
configRequiresSignal = (colorSetting != CCS_DoNothing); configRequiresSignal = (colorSetting != CCS_DoNothing);
// Enable or disable censoring if `IsAnyCensoringEnabled()`'s response
// has changed.
if (!connectedToSignal && configRequiresSignal)
{
connectedToSignal = true;
_.chat.OnMessage(self).connect = ReformatChatMessage;
}
if (connectedToSignal && !configRequiresSignal)
{
connectedToSignal = false;
_.chat.OnMessage(self).Disconnect();
}
} }
private function bool ReformatChatMessage( private function bool ReformatChatMessage(
EPlayer sender, EPlayer sender,
MutableText message, MutableText message,
bool teamMessage bool teamMessage)
) { {
local Text messageCopy; local Text messageCopy;
local BaseText.Formatting defaultFormatting; local BaseText.Formatting defaultFormatting;
if (sender == none) return true; if (sender == none) return true;
if (message == none) return true; if (message == none) return true;
if (colorSetting == CCS_DoNothing) return true; if (colorSetting == CCS_DoNothing) return true;
defaultFormatting.isColored = true; defaultFormatting.isColored = true;
if (colorSetting == CCS_TeamColorForced || colorSetting == CCS_TeamColorCustom) { if ( colorSetting == CCS_TeamColorForced
|| colorSetting == CCS_TeamColorCustom)
{
defaultFormatting.color = ModColor(sender.GetTeamColor()); defaultFormatting.color = ModColor(sender.GetTeamColor());
} else { }
else {
defaultFormatting.color = configuredColor; defaultFormatting.color = configuredColor;
} }
if (message.StartsWith(P("|"))) { if (message.StartsWith(P("|"))) {
message.Remove(0, 1); message.Remove(0, 1);
} else if (colorSetting != CCS_TeamColorForced && colorSetting != CCS_ConfigColorForced) { }
else if ( colorSetting != CCS_TeamColorForced
&& colorSetting != CCS_ConfigColorForced)
{
messageCopy = message.Copy(); messageCopy = message.Copy();
class'FormattingStringParser'.static.ParseFormatted(messageCopy, message.Clear()); class'FormattingStringParser'.static
.ParseFormatted(messageCopy, message.Clear());
_.memory.Free(messageCopy); _.memory.Free(messageCopy);
} }
message.ChangeDefaultFormatting(defaultFormatting); message.ChangeDefaultFormatting(defaultFormatting);
return true; return true;
} }
private function Color ModColor(Color inputColor) { private function Color ModColor(Color inputColor)
{
local Color mixColor; local Color mixColor;
local Color outputColor; local Color outputColor;
local float clampedModifier; local float clampedModifier;
if (Abs(teamColorModifier) < 0.001) { if (Abs(teamColorModifier) < 0.001) {
return inputColor; return inputColor;
} }
clampedModifier = FClamp(teamColorModifier, -1.0, 1.0); clampedModifier = FClamp(teamColorModifier, -1.0, 1.0);
if (clampedModifier > 0) { if (clampedModifier > 0) {
mixColor = _.color.White; mixColor = _.color.White;
} else { }
else
{
mixColor = _.color.Black; mixColor = _.color.Black;
clampedModifier *= -1.0; clampedModifier *= -1.0;
} }
@ -106,6 +147,7 @@ private function Color ModColor(Color inputColor) {
return outputColor; return outputColor;
} }
defaultproperties { defaultproperties
{
configClass = class'FutilityChat' configClass = class'FutilityChat'
} }

114
sources/Features/FutileNickames/FutilityNicknames.uc

@ -1,6 +1,6 @@
/** /**
* Config class for storing map lists. * Config object for `FutileNickames_Feature`.
* Copyright 2022-2023 Anton Tarasenko * Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -21,108 +21,62 @@ class FutilityNicknames extends FeatureConfig
perobjectconfig perobjectconfig
config(FutilityNicknames); config(FutilityNicknames);
enum NicknameSpacesAction { enum NicknameSpacesAction
{
NSA_DoNothing, NSA_DoNothing,
NSA_Trim, NSA_Trim,
NSA_Simplify NSA_Simplify
}; };
enum NicknameColorPermissions { enum NicknameColorPermissions
{
NCP_ForbidColor, NCP_ForbidColor,
NCP_ForceTeamColor, NCP_ForceTeamColor,
NCP_ForceSingleColor, NCP_ForceSingleColor,
NCP_AllowAnyColor 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; 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; 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; 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; 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; 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; 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; 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; var public config array<string> fallbackNickname;
protected function HashTable ToData() { protected function HashTable ToData()
{
local int i; local int i;
local ArrayList fallbackNicknamesData; local ArrayList fallbackNicknamesData;
local HashTable data; local HashTable data;
data = __().collections.EmptyHashTable(); data = __().collections.EmptyHashTable();
data.SetString(P("spacesAction"), string(spacesAction)); data.SetString(P("spacesAction"), string(spacesAction));
data.SetString(P("colorPermissions"), string(colorPermissions)); data.SetString(P("colorPermissions"), string(colorPermissions));
data.SetBool(P("replaceSpacesWithUnderscores"), replaceSpacesWithUnderscores); data.SetBool( P("replaceSpacesWithUnderscores"),
data.SetBool(P("removeSingleQuotationMarks"), removeSingleQuotationMarks); replaceSpacesWithUnderscores);
data.SetBool(P("removeDoubleQuotationMarks"), removeDoubleQuotationMarks); data.SetBool( P("removeSingleQuotationMarks"),
removeSingleQuotationMarks);
data.SetBool( P("removeDoubleQuotationMarks"),
removeDoubleQuotationMarks);
data.SetBool(P("correctEmptyNicknames"), correctEmptyNicknames); data.SetBool(P("correctEmptyNicknames"), correctEmptyNicknames);
data.SetInt(P("maxNicknameLength"), maxNicknameLength); data.SetInt(P("maxNicknameLength"), maxNicknameLength);
fallbackNicknamesData = __().collections.EmptyArrayList(); fallbackNicknamesData = __().collections.EmptyArrayList();
for (i = 0; i < fallbackNickname.length; i += 1)
for (i = 0; i < fallbackNickname.length; i += 1) { {
fallbackNicknamesData.AddItem(__().text.FromFormattedString(fallbackNickname[i])); fallbackNicknamesData.AddItem(
__().text.FromFormattedString(fallbackNickname[i]));
} }
data.SetItem(P("fallbackNickname"), fallbackNicknamesData); data.SetItem(P("fallbackNickname"), fallbackNicknamesData);
_.memory.Free(fallbackNicknamesData); _.memory.Free(fallbackNicknamesData);
return data; return data;
} }
protected function FromData(HashTable source) { protected function FromData(HashTable source)
{
local int i; local int i;
local Text nextNickName, storedText; local Text nextNickName, storedText;
local ArrayList fallbackNicknamesData; local ArrayList fallbackNicknamesData;
if (source == none) { if (source == none) {
return; return;
} }
@ -132,20 +86,25 @@ protected function FromData(HashTable source) {
storedText = source.GetText(P("colorPermissions")); storedText = source.GetText(P("colorPermissions"));
colorPermissions = ColorPermissionsFromText(storedText); colorPermissions = ColorPermissionsFromText(storedText);
_.memory.Free(storedText); _.memory.Free(storedText);
replaceSpacesWithUnderscores = source.GetBool(P("replaceSpacesWithUnderscores"), true); replaceSpacesWithUnderscores =
removeSingleQuotationMarks = source.GetBool(P("removeSingleQuotationMarks"), true); source.GetBool(P("replaceSpacesWithUnderscores"), true);
removeDoubleQuotationMarks = source.GetBool(P("removeDoubleQuotationMarks"), true); removeSingleQuotationMarks =
source.GetBool(P("removeSingleQuotationMarks"), true);
removeDoubleQuotationMarks =
source.GetBool(P("removeDoubleQuotationMarks"), true);
correctEmptyNicknames = source.GetBool(P("correctEmptyNicknames"), true); correctEmptyNicknames = source.GetBool(P("correctEmptyNicknames"), true);
maxNicknameLength = source.GetInt(P("correctEmptyNicknames"), 20); maxNicknameLength = source.GetInt(P("correctEmptyNicknames"), 20);
fallbackNicknamesData = source.GetArrayList(P("fallbackNickname")); fallbackNicknamesData = source.GetArrayList(P("fallbackNickname"));
if (fallbackNickname.length > 0) { if (fallbackNickname.length > 0) {
fallbackNickname.length = 0; fallbackNickname.length = 0;
} }
for (i = 0; i < fallbackNicknamesData.GetLength(); i += 1) { for (i = 0; i < fallbackNicknamesData.GetLength(); i += 1)
{
nextNickName = fallbackNicknamesData.GetText(i); nextNickName = fallbackNicknamesData.GetText(i);
if (nextNickName != none) { if (nextNickName != none) {
fallbackNickname[i] = nextNickName.ToFormattedString(); fallbackNickname[i] = nextNickName.ToFormattedString();
} else { }
else {
fallbackNickname[i] = ""; fallbackNickname[i] = "";
} }
_.memory.Free(nextNickName); _.memory.Free(nextNickName);
@ -153,7 +112,8 @@ protected function FromData(HashTable source) {
_.memory.Free(fallbackNicknamesData); _.memory.Free(fallbackNicknamesData);
} }
private function NicknameSpacesAction SpaceActionFromText(BaseText action) { private function NicknameSpacesAction SpaceActionFromText(BaseText action)
{
if (action == none) { if (action == none) {
return NSA_DoNothing; return NSA_DoNothing;
} }
@ -169,7 +129,9 @@ private function NicknameSpacesAction SpaceActionFromText(BaseText action) {
return NSA_DoNothing; return NSA_DoNothing;
} }
private function NicknameColorPermissions ColorPermissionsFromText(BaseText permissions) { private function NicknameColorPermissions ColorPermissionsFromText(
BaseText permissions)
{
if (permissions == none) { if (permissions == none) {
return NCP_ForbidColor; return NCP_ForbidColor;
} }
@ -188,7 +150,8 @@ private function NicknameColorPermissions ColorPermissionsFromText(BaseText perm
return NCP_ForbidColor; return NCP_ForbidColor;
} }
protected function DefaultIt() { protected function DefaultIt()
{
spacesAction = NSA_DoNothing; spacesAction = NSA_DoNothing;
colorPermissions = NCP_ForbidColor; colorPermissions = NCP_ForbidColor;
replaceSpacesWithUnderscores = true; replaceSpacesWithUnderscores = true;
@ -211,7 +174,8 @@ protected function DefaultIt() {
fallbackNickname[9] = "Bug Meat"; fallbackNickname[9] = "Bug Meat";
} }
defaultproperties { defaultproperties
{
configName = "FutilityNicknames" configName = "FutilityNicknames"
spacesAction = NSA_DoNothing spacesAction = NSA_DoNothing
colorPermissions = NCP_ForbidColor colorPermissions = NCP_ForbidColor

288
sources/Features/FutileNickames/FutilityNicknames_Feature.uc

@ -1,6 +1,9 @@
/** /**
* Config class for storing map lists. * This feature allows to configure nickname limitations for the server.
* Copyright 2022-2023 Anton Tarasenko * It allows you to customize vanilla limitations for nickname length and
* color with those of your own design. Enabling this feature overwrites
* default behaviour.
* Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -20,88 +23,155 @@
class FutilityNicknames_Feature extends Feature class FutilityNicknames_Feature extends Feature
dependson(FutilityNicknames); dependson(FutilityNicknames);
//! This feature's functionality is rather simple, but we will still break up /**
//! what its various components are. * 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 * Fallback nicknames are picked at random from
//! `unusedNicknames` and then picking and removing its random elements each * the `fallbackNicknames` array. This is done by copying that array into
//! time we need a fallback. Once `unusedNicknames` is empty - it is copied from * `unusedNicknames` and then picking and removing its random elements each
//! `fallbackNicknames` once again, letting already used nicknames to be reused. * time we need a fallback. Once `unusedNicknames` is empty - it is copied from
//! `unusedNicknames` contains same references as `fallbackNicknames`, * `fallbackNicknames` once again, letting already used nicknames to be reused.
//! so they need to be separately deallocated and should also be forgotten once * `unusedNicknames` contains same references as `fallbackNicknames`,
//! `fallbackNicknames` are deallocated`. * so they need to be separately deallocated and should also be forgotten once
//! This is implemented inside `PickNextFallback()` method. * `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". * Nickname changes are applied inside `CensorNickname()` method that uses
//! Censoring is performed: * several auxiliary methods to perform different stages of "censoring".
//! 1. On any player's name change * Censoring is performed:
//! (using `OnPlayerNameChanging()` signal, connected to * 1. On any player's name change
//! `HandleNicknameChange()`); * (using `OnPlayerNameChanging()` signal, connected to
//! 2. When new player logins (using `OnNewPlayer()` signal, * `HandleNicknameChange()`);
//! conneted to `CensorOriginalNickname()`) to enforce our own * 2. When new player logins (using `OnNewPlayer()` signal,
//! handling of player's original nickname. * conneted to `CensorOriginalNickname()`) to enforce our own
* handling of player's original nickname;
* 3. When censoring is re-activated.
* In case all censoring options of this feature are disabled
* (checked by `IsAnyCensoringEnabled()`) - we do not attempt to
* catch any events or do anything at all.
* If settings change mid-execution, this feature might need to
* enable or disable censoring on-the-fly. To accomplish that we
* remember current status inside `censoringNicknames` boolean
* variable and enable/disable events if required by settings.
* So whenever we re-activate censoring we also need to update
* ("censor") current players' nicknames - a third occasion to
* call `CensorNickname()`, implemented inside
* `CensorCurrentPlayersNicknames()`.
*/
// 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 private /*config*/ FutilityNicknames.NicknameSpacesAction spacesAction; var private /*config*/ FutilityNicknames.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 private /*config*/ FutilityNicknames.NicknameColorPermissions colorPermissions; var private /*config*/ FutilityNicknames.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 private /*config*/ bool replaceSpacesWithUnderscores; var private /*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 private /*config*/ bool removeSingleQuotationMarks; var private /*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 private /*config*/ bool removeDoubleQuotationMarks; var private /*config*/ bool removeDoubleQuotationMarks;
// 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 private /*config*/ int maxNicknameLength; var private /*config*/ int maxNicknameLength;
// Should we replace empty player nicknames with a random fallback nickname
// (defined in `fallbackNickname` array)?
var private /*config*/ bool correctEmptyNicknames; var private /*config*/ bool correctEmptyNicknames;
// Array of fallback nicknames that will be used to replace any empty nicknames
// if `correctEmptyNicknames` is set to `true`.
var private /*config*/ array<Text> fallbackNickname; var private /*config*/ array<Text> fallbackNickname;
/// Guaranteed order of applying changes (only chosen ones) is as following: // Guaranteed order of applying changes (only chosen ones) is as following:
/// // 1. Trim/simplify spaces;
/// 1. Trim/simplify spaces; // 2. Remove single and double quotation marks;
/// 2. Remove single and double quotation marks; // 3. Enforce max limit of nickname's length;
/// 3. Enforce max limit of nickname's length; // 4. Replace empty nickname with fallback nickname (no further changes
/// 4. Replace empty nickname with fallback nickname (no further changes // will be applied to fallback nickname in that case);
/// will be applied to fallback nickname in that case); // 5. Enforce color limitation;
/// 5. Enforce color limitation; // 6. Replace remaining whitespaces with underscores.
/// 6. Replace remaining whitespaces with underscores. //
/// // NOTE #1: as follows from the instruction described above, no changes will
/// NOTE #1: as follows from the instruction described above, no changes will // ever be applied to fallback nicknames (unless player's nickname
/// ever be applied to fallback nicknames (unless player's nickname coincides // coincides with one by pure accident).
/// 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
/// NOTE #2: whitespaces inside steam nicknames are converted into underscores // cannot currently abort.
/// before they are passed into the game and this is a change Futility // Therefore all changes relevant to whitespaces inside nicknames will only
/// cannot currently abort. Therefore all changes relevant to whitespaces inside // be applied to in-game changes.
/// nicknames will only be applied to in-game changes.
/// Nicknames from `fallbackNickname` that can still be picked in the current // Nicknames from `fallbackNickname` that can still be picked in
/// rotation. // the current rotation.
var private array<Text> unusedNicknames; var private array<Text> unusedNicknames;
// Are we currently censoring nicknames?
// Set to `false` if none of the feature's options require
// any action (censoring) and, therefore, we do not listen to any signals.
var private bool censoringNicknames;
var private const int CODEPOINT_UNDERSCORE; var private const int CODEPOINT_UNDERSCORE;
protected function OnEnabled() { protected function OnDisabled()
if (IsAnyCensoringEnabled()) { {
// Do this before adding event handler to avoid censoring nicknames _.memory.FreeMany(fallbackNickname);
// second time (censoring nickname will trigger `OnPlayerNameChanging()` // Free this `Text` data - it will be refilled with `SwapConfig()`
// signal) // if this feature is ever reenabled
CensorCurrentPlayersNicknames(); if (fallbackNickname.length > 0)
_.players.OnPlayerNameChanging(self).connect = HandleNicknameChange; {
_.players.OnNewPlayer(self).connect = CensorOriginalNickname;
}
}
protected function OnDisabled() {
_.memory.FreeMany(fallbackNickname); _.memory.FreeMany(fallbackNickname);
_.memory.FreeMany(unusedNicknames);
fallbackNickname.length = 0; fallbackNickname.length = 0;
unusedNicknames.length = 0; unusedNicknames.length = 0;
if (IsAnyCensoringEnabled()) { }
if (censoringNicknames)
{
censoringNicknames = false;
_.players.OnPlayerNameChanging(self).Disconnect(); _.players.OnPlayerNameChanging(self).Disconnect();
_.players.OnNewPlayer(self).Disconnect(); _.players.OnNewPlayer(self).Disconnect();
} }
} }
protected function SwapConfig(FeatureConfig config) { protected function SwapConfig(FeatureConfig config)
{
local bool configRequiresCensoring;
local FutilityNicknames newConfig; local FutilityNicknames newConfig;
newConfig = FutilityNicknames(config); newConfig = FutilityNicknames(config);
if (newConfig == none) { if (newConfig == none) {
return; return;
@ -113,25 +183,33 @@ protected function SwapConfig(FeatureConfig config) {
spacesAction = newConfig.spacesAction; spacesAction = newConfig.spacesAction;
colorPermissions = newConfig.colorPermissions; colorPermissions = newConfig.colorPermissions;
maxNicknameLength = newConfig.maxNicknameLength; maxNicknameLength = newConfig.maxNicknameLength;
SwapFallbackNicknames(newConfig); configRequiresCensoring = IsAnyCensoringEnabled();
} // Enable or disable censoring if `IsAnyCensoringEnabled()`'s response
// has changed.
private function SwapFallbackNicknames(FutilityNicknames newConfig) { if (!censoringNicknames && configRequiresCensoring)
local int i; {
censoringNicknames = true;
_.memory.FreeMany(fallbackNickname); // Do this before adding event handler to
fallbackNickname.length = 0; // avoid censoring nicknames second time
for (i = 0; i < newConfig.fallbackNickname.length; i += 1) { CensorCurrentPlayersNicknames();
fallbackNickname[i] = _.text.FromFormattedString(newConfig.fallbackNickname[i]); _.players.OnPlayerNameChanging(self).connect = HandleNicknameChange;
_.players.OnNewPlayer(self).connect = CensorOriginalNickname;
} }
unusedNicknames = fallbackNickname; if (censoringNicknames && !configRequiresCensoring)
{
censoringNicknames = false;
_.players.OnPlayerNameChanging(self).Disconnect();
_.players.OnNewPlayer(self).Disconnect();
}
SwapFallbackNicknames(newConfig);
} }
private function Text PickNextFallback() { private function Text PickNextFallback()
{
local int pickedIndex; local int pickedIndex;
local Text result; local Text result;
if (fallbackNickname.length <= 0)
if (fallbackNickname.length <= 0) { {
// Just in case this feature is really misconfigured // Just in case this feature is really misconfigured
return P("Fresh Meat").Copy(); return P("Fresh Meat").Copy();
} }
@ -146,7 +224,23 @@ private function Text PickNextFallback() {
return result; return result;
} }
private function bool IsAnyCensoringEnabled() { protected function SwapFallbackNicknames(FutilityNicknames newConfig)
{
local int i;
_.memory.FreeMany(fallbackNickname);
if (fallbackNickname.length > 0) {
fallbackNickname.length = 0;
}
for (i = 0; i < newConfig.fallbackNickname.length; i += 1)
{
fallbackNickname[i] =
_.text.FromFormattedString(newConfig.fallbackNickname[i]);
}
unusedNicknames = fallbackNickname;
}
private function bool IsAnyCensoringEnabled()
{
return ( replaceSpacesWithUnderscores return ( replaceSpacesWithUnderscores
|| removeSingleQuotationMarks || removeSingleQuotationMarks
|| removeDoubleQuotationMarks || removeDoubleQuotationMarks
@ -160,13 +254,14 @@ private function bool IsAnyCensoringEnabled() {
private function HandleNicknameChange( private function HandleNicknameChange(
EPlayer affectedPlayer, EPlayer affectedPlayer,
BaseText oldName, BaseText oldName,
MutableText newName MutableText newName)
) { {
CensorNickname(newName, affectedPlayer); CensorNickname(newName, affectedPlayer);
} }
// For handling of player's original nicknames. // For handling of player's original nicknames.
private function CensorOriginalNickname(EPlayer affectedPlayer) { private function CensorOriginalNickname(EPlayer affectedPlayer)
{
local Text originalNickname; local Text originalNickname;
if (affectedPlayer == none) { if (affectedPlayer == none) {
return; return;
@ -180,14 +275,15 @@ private function CensorOriginalNickname(EPlayer affectedPlayer) {
// For handling nicknames of players after censoring is re-activated by // For handling nicknames of players after censoring is re-activated by
// config change. // config change.
private function CensorCurrentPlayersNicknames() { private function CensorCurrentPlayersNicknames()
{
local int i; local int i;
local Text nextNickname; local Text nextNickname;
local MutableText alteredNickname; local MutableText alteredNickname;
local array<EPlayer> currentPlayers; local array<EPlayer> currentPlayers;
currentPlayers = _.players.GetAll(); currentPlayers = _.players.GetAll();
for (i = 0; i < currentPlayers.length; i += 1) { for (i = 0; i < currentPlayers.length; i += 1)
{
nextNickname = currentPlayers[i].GetName(); nextNickname = currentPlayers[i].GetName();
alteredNickname = nextNickname.MutableCopy(); alteredNickname = nextNickname.MutableCopy();
CensorNickname(alteredNickname, currentPlayers[i]); CensorNickname(alteredNickname, currentPlayers[i]);
@ -199,10 +295,10 @@ private function CensorCurrentPlayersNicknames() {
} }
} }
private function CensorNickname(MutableText nickname, EPlayer affectedPlayer) { private function CensorNickname(MutableText nickname, EPlayer affectedPlayer)
{
local Text fallback; local Text fallback;
local BaseText.Formatting newFormatting; local BaseText.Formatting newFormatting;
if (nickname == none) return; if (nickname == none) return;
if (affectedPlayer == none) return; if (affectedPlayer == none) return;
@ -218,16 +314,20 @@ private function CensorNickname(MutableText nickname, EPlayer affectedPlayer) {
if (maxNicknameLength >= 0) { if (maxNicknameLength >= 0) {
nickname.Remove(maxNicknameLength); nickname.Remove(maxNicknameLength);
} }
if (correctEmptyNicknames && nickname.IsEmpty()) { if (correctEmptyNicknames && nickname.IsEmpty())
{
fallback = PickNextFallback(); fallback = PickNextFallback();
nickname.Append(fallback); nickname.Append(fallback);
_.memory.Free(fallback); _.memory.Free(fallback);
return; return;
} }
if (colorPermissions != NCP_AllowAnyColor) { if (colorPermissions != NCP_AllowAnyColor)
{
if (colorPermissions == NCP_ForceSingleColor) { if (colorPermissions == NCP_ForceSingleColor) {
newFormatting = nickname.GetCharacter(0).formatting; newFormatting = nickname.GetCharacter(0).formatting;
} else if (colorPermissions == NCP_ForceTeamColor) { }
else if (colorPermissions == NCP_ForceTeamColor)
{
newFormatting.isColored = true; newFormatting.isColored = true;
newFormatting.color = affectedPlayer.GetTeamColor(); newFormatting.color = affectedPlayer.GetTeamColor();
} }
@ -241,17 +341,20 @@ private function CensorNickname(MutableText nickname, EPlayer affectedPlayer) {
} }
// Asusmes `nickname != none`. // Asusmes `nickname != none`.
private function ReplaceSpaces(MutableText nickname) { private function ReplaceSpaces(MutableText nickname)
{
local int i; local int i;
local MutableText nicknameCopy; local MutableText nicknameCopy;
local BaseText.Character nextCharacter, underscoreCharacter; local BaseText.Character nextCharacter, underscoreCharacter;
nicknameCopy = nickname.MutableCopy(); nicknameCopy = nickname.MutableCopy();
nickname.Clear(); nickname.Clear();
underscoreCharacter = _.text.CharacterFromCodePoint(CODEPOINT_UNDERSCORE); underscoreCharacter =
for (i = 0; i < nicknameCopy.GetLength(); i += 1) { _.text.CharacterFromCodePoint(CODEPOINT_UNDERSCORE);
for (i = 0; i < nicknameCopy.GetLength(); i += 1)
{
nextCharacter = nicknameCopy.GetCharacter(i); nextCharacter = nicknameCopy.GetCharacter(i);
if (_.text.IsWhitespace(nextCharacter)) { if (_.text.IsWhitespace(nextCharacter))
{
// Replace character with underscore, leaving the formatting // Replace character with underscore, leaving the formatting
underscoreCharacter.formatting = nextCharacter.formatting; underscoreCharacter.formatting = nextCharacter.formatting;
nextCharacter = underscoreCharacter; nextCharacter = underscoreCharacter;
@ -261,7 +364,8 @@ private function ReplaceSpaces(MutableText nickname) {
_.memory.Free(nicknameCopy); _.memory.Free(nicknameCopy);
} }
defaultproperties { defaultproperties
{
configClass = class'FutilityNicknames' configClass = class'FutilityNicknames'
CODEPOINT_UNDERSCORE = 95 // '_' CODEPOINT_UNDERSCORE = 95 // '_'
} }

12
sources/Futility.uc

@ -21,16 +21,20 @@ class Futility extends FeatureConfig
perobjectconfig perobjectconfig
config(Futility); config(Futility);
protected function HashTable ToData() { protected function HashTable ToData()
{
return _.collections.EmptyHashTable(); return _.collections.EmptyHashTable();
} }
protected function FromData(HashTable source) { protected function FromData(HashTable source)
{
} }
protected function DefaultIt() { protected function DefaultIt()
{
} }
defaultproperties { defaultproperties
{
configName = "Futility" configName = "Futility"
} }

6
sources/Futility_Feature.uc

@ -36,7 +36,7 @@ protected function OnEnabled()
return; return;
} }
for (i = 0; i < allCommandClasses.length; i += 1) { for (i = 0; i < allCommandClasses.length; i += 1) {
//commandsFeature.RegisterCommand(allCommandClasses[i]); commandsFeature.RegisterCommand(allCommandClasses[i]);
} }
_.environment.OnFeatureEnabled(self).connect = RegisterAllCommandClasses; _.environment.OnFeatureEnabled(self).connect = RegisterAllCommandClasses;
} }
@ -52,7 +52,7 @@ protected function OnDisabled()
return; return;
} }
for (i = 0; i < allCommandClasses.length; i += 1) { for (i = 0; i < allCommandClasses.length; i += 1) {
//ommandsFeature.RegisterCommand(allCommandClasses[i]); commandsFeature.RegisterCommand(allCommandClasses[i]);
} }
} }
@ -65,7 +65,7 @@ private final function RegisterAllCommandClasses(Feature enabledFeature)
return; return;
} }
for (i = 0; i < allCommandClasses.length; i += 1) { for (i = 0; i < allCommandClasses.length; i += 1) {
//commandsFeature.RegisterCommand(allCommandClasses[i]); commandsFeature.RegisterCommand(allCommandClasses[i]);
} }
} }

126
sources/Tools/PendingConfigsTool.uc

@ -2,8 +2,8 @@
* Auxiliary object for `ACommandFeature` to help with managing pending * Auxiliary object for `ACommandFeature` to help with managing pending
* configs for `Feature`s. Pending configs are `HashTable`s with config data * 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 * 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 * make several changes to the data before actually making changes to
* the gameplay. * the gameplay code.
* Copyright 2022 Anton Tarasenko * Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
@ -23,30 +23,14 @@
*/ */
class PendingConfigsTool extends AcediaObject; 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 class<Feature> selectedFeatureClass;
var private Text selectedConfigName; var private Text selectedConfigName;
// Possible errors that might occur when working with pending configs enum PendingConfigToolError
enum PendingConfigToolResult
{ {
// No error
PCTE_None, PCTE_None,
// Pending version of specified config does not exist
PCTE_ConfigMissing, PCTE_ConfigMissing,
// JSON object (`HashTable`) was expected as a parameter for the operation,
// but something else was given
PCTE_ExpectedObject, PCTE_ExpectedObject,
// Specified JSON pointer points an non-existent location
PCTE_BadPointer PCTE_BadPointer
}; };
@ -67,14 +51,6 @@ protected function Finalizer()
featurePendingEdits.length = 0; 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( public final function SelectConfig(
class<Feature> featureClass, class<Feature> featureClass,
BaseText configName) BaseText configName)
@ -87,18 +63,6 @@ public final function SelectConfig(
} }
} }
/**
* 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( public function bool HasPendingConfigFor(
class<Feature> featureClass, class<Feature> featureClass,
BaseText configName) BaseText configName)
@ -124,19 +88,6 @@ public function bool HasPendingConfigFor(
return false; 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) public function HashTable GetPendingConfigData(optional bool createIfMissing)
{ {
local int editsIndex; local int editsIndex;
@ -176,21 +127,15 @@ public function HashTable GetPendingConfigData(optional bool createIfMissing)
return result; return result;
} }
/** public function PendingConfigToolError ChangeConfig(
* 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, BaseText pathToValue,
AcediaObject newValue) BaseText newValue)
{ {
local HashTable pendingData; local HashTable pendingData;
local MutableJsonPointer pointer; local JSONPointer pointer;
local PendingConfigToolResult result; local Parser parser;
local AcediaObject newValueAsJSON;
local PendingConfigToolError result;
if (pathToValue == none) { if (pathToValue == none) {
return PCTE_BadPointer; return PCTE_BadPointer;
@ -199,22 +144,31 @@ public function PendingConfigToolResult EditConfig(
if (pendingData == none) { if (pendingData == none) {
return PCTE_ConfigMissing; return PCTE_ConfigMissing;
} }
// Get guaranteed not-`none` JSON value, treating it as JSON string
// if necessary
parser = _.text.Parse(newValue);
newValueAsJSON = _.json.ParseWith(parser);
parser.FreeSelf();
if (newValueAsJSON == none && newValue != none) {
newValueAsJSON = newValue.Copy();
}
// Set new data // Set new data
pointer = _.json.MutablePointer(pathToValue); pointer = _.json.Pointer(pathToValue);
result = SetItemByJSON(pendingData, pointer, newValue); result = SetItemByJSON(pendingData, pointer, newValueAsJSON);
pointer.FreeSelf(); pointer.FreeSelf();
pendingData.FreeSelf(); pendingData.FreeSelf();
_.memory.Free(newValueAsJSON);
return result; return result;
} }
private function PendingConfigToolResult SetItemByJSON( private function PendingConfigToolError SetItemByJSON(
HashTable data, HashTable data,
MutableJsonPointer pointer, JSONPointer pointer,
AcediaObject jsonValue) AcediaObject jsonValue)
{ {
local Text containerIndex; local Text containerIndex;
local AcediaObject container; local AcediaObject container;
local PendingConfigToolResult result; local PendingConfigToolError result;
if (pointer.IsEmpty()) if (pointer.IsEmpty())
{ {
@ -240,11 +194,8 @@ private function PendingConfigToolResult SetItemByJSON(
container.FreeSelf(); container.FreeSelf();
return result; return result;
} }
/*EditFeatureConfig #1: true
SetItemByJSON: true private function PendingConfigToolError SetContainerItemByText(
SetContainerItemByText: true
EditFeatureConfig #2: true */
private function PendingConfigToolResult SetContainerItemByText(
AcediaObject container, AcediaObject container,
BaseText containerIndex, BaseText containerIndex,
AcediaObject jsonValue) AcediaObject jsonValue)
@ -279,31 +230,6 @@ private function PendingConfigToolResult SetContainerItemByText(
return PCTE_None; 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() private function int GetPendingConfigDataIndex()
{ {
local int i; local int i;
@ -317,7 +243,7 @@ private function int GetPendingConfigDataIndex()
return -1; return -1;
} }
private function PendingConfigToolResult ChangePendingConfigData( private function PendingConfigToolError ChangePendingConfigData(
HashTable newData) HashTable newData)
{ {
local int editsIndex; local int editsIndex;

Loading…
Cancel
Save