From 926108e6e13e1988eed1b087e91d41d1577977b4 Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Thu, 22 Apr 2021 15:45:08 +0700 Subject: [PATCH] Add `trader` built-in command --- .../BuiltInCommands/ACommandTrader.uc | 570 ++++++++++++++++++ sources/Manifest.uc | 2 +- 2 files changed, 571 insertions(+), 1 deletion(-) create mode 100644 sources/Commands/BuiltInCommands/ACommandTrader.uc diff --git a/sources/Commands/BuiltInCommands/ACommandTrader.uc b/sources/Commands/BuiltInCommands/ACommandTrader.uc new file mode 100644 index 0000000..2604dfb --- /dev/null +++ b/sources/Commands/BuiltInCommands/ACommandTrader.uc @@ -0,0 +1,570 @@ +/** + * Command for managing trader time and traders. + * Copyright 2021 Anton Tarasenko + *------------------------------------------------------------------------------ + * This file is part of Acedia. + * + * Acedia is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, or + * (at your option) any later version. + * + * Acedia is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Acedia. If not, see . + */ +class ACommandTrader extends Command; + +var protected const int TLIST, TOPEN, TCLOSE, TENABLE, TDISABLE, TAUTO_OPEN; +var protected const int TTRADER, TTRADERS, TALL, TAUTO_OPEN_QUESTION, TQUOTE; +var protected const int TAUTO_OPEN_FLAG, TDISABLED_FLAG, TUNKNOWN_TRADERS; +var protected const int TLIST_TRADERS, TCOMMA_SPACE, TSELECTED_FLAG; +var protected const int TPARENTHESIS_OPEN, TPARENTHESIS_CLOSE; +var protected const int TSELECT, TIGNORE_DOORS, TBOOT, TTRADER_TIME, TTIME; +var protected const int TIGNORE_PLAYERS, TPAUSE, TUNPAUSE, TCANNOT_PARSE_PARAM; +var protected const int TCLOSEST, TSPACE; + +protected function BuildData(CommandDataBuilder builder) +{ + builder.Name(T(TTRADER)) + .Summary(P("Manages trader time and available traders.")) + .Describe(P("Enables of disables trading.")) + .ParamBoolean(T(TENABLE)); + builder.SubCommand(T(TTIME)) + .Describe(F("Changes current trader time if numeric value is specified." + @ "You can also pause trader countdown by specifying" + @ "{$TextEmphasis pause} or turn it back on with" + @ "{$TextEmphasis unpause}.")) + .ParamText(T(TTRADER_TIME)); + builder.SubCommand(T(TLIST)) + .Describe(P("Lists names of all available traders and" + @ "marks closest one to the caller.")); + builder.SubCommand(T(TOPEN)) + .Describe(P("Opens specified traders.")) + .OptionalParams() + .ParamTextList(T(TTRADERS)); + builder.SubCommand(T(TCLOSE)) + .Describe(P("Closes specified traders.")) + .OptionalParams() + .ParamTextList(T(TTRADERS)); + builder.SubCommand(T(TAUTO_OPEN)) + .Describe(P("Sets whether specified traders are open automatically.")) + .ParamBoolean(T(TAUTO_OPEN_QUESTION)) + .OptionalParams() + .ParamTextList(T(TTRADERS)); + builder.SubCommand(T(TSELECT)) + .Describe(P("Selects specified trader.")) + .OptionalParams() + .ParamText(T(TTRADER)); + builder.SubCommand(T(TBOOT)) + .Describe(P("Boots all players from specified traders. If no traders" + @ "were specified - assumes that all of them should be affected.")) + .OptionalParams() + .ParamTextList(T(TTRADERS)); + builder.SubCommand(T(TENABLE)) + .Describe(P("Enables specified traders.")) + .OptionalParams() + .ParamTextList(T(TTRADERS)); + builder.SubCommand(T(TDISABLE)) + .Describe(P("Disables specified traders.")) + .OptionalParams() + .ParamTextList(T(TTRADERS)); + builder.Option(T(TALL)) + .Describe(P("If sub-command targets shops, this flag will make it" + @ "target all the available shops.")); + builder.Option(T(TCLOSEST)) + .Describe(P("If sub-command targets shops, this flag will make it also" + @ "target closest shop to the caller.")); + builder.Option(T(TIGNORE_DOORS)) + .Describe(F("When used with {$TextEmphasis select} sub-command, it will" + @ "neither open or close doors.")); + builder.Option(T(TIGNORE_PLAYERS), P("I")) + .Describe(P("Normally commands that close doors will automatically boot" + @ "players from inside to prevent locking them in. This flag forces" + @ "this command to leave players inside. However they can still be" + @ "booted out at the end of trading time. Also it is impossible to" + @ "disable the trader and not boot players inside it.")); +} + +protected function Executed(CommandCall result) +{ + local Text subCommand; + local AssociativeArray commandParameters, commandOptions; + subCommand = result.GetSubCommand(); + commandParameters = result.GetParameters(); + commandOptions = result.GetOptions(); + if (subCommand.IsEmpty()) { + _.kf.trading.SetTradingStatus(commandParameters.GetBool(T(TENABLE))); + } + else if (subCommand.Compare(T(TLIST))) { + ListTradersFor(result.GetCallerPlayer()); + } + else if (subCommand.Compare(T(TTIME), SCASE_INSENSITIVE)) { + HandleTraderTime(result); + } + else if (subCommand.Compare(T(TOPEN), SCASE_INSENSITIVE)) { + SetTradersOpen(true, result); + } + else if (subCommand.Compare(T(TCLOSE), SCASE_INSENSITIVE)) { + SetTradersOpen(false, result); + } + else if (subCommand.Compare(T(TSELECT), SCASE_INSENSITIVE)) { + SelectTrader(result); + } + else if (subCommand.Compare(T(TBOOT), SCASE_INSENSITIVE)) { + BootFromTraders(result); + } + else if (subCommand.Compare(T(TENABLE), SCASE_INSENSITIVE)) { + SetTradersEnabled(true, result); + } + else if (subCommand.Compare(T(TDISABLE), SCASE_INSENSITIVE)) { + SetTradersEnabled(false, result); + } + else if (subCommand.Compare(T(TAUTO_OPEN), SCASE_INSENSITIVE)) { + SetTradersAutoOpen(result); + } + subCommand.FreeSelf(); +} + +protected function ListTradersFor(APlayer target) +{ + local int i; + local ATrader closestTrader; + local ConsoleWriter console; + local array availableTraders; + if (target == none) { + return; + } + availableTraders = _.kf.trading.GetTraders(); + console = target.Console(); + console.Flush() + .UseColor(_.color.TextEmphasis) + .Write(T(TLIST_TRADERS)) + .ResetColor(); + closestTrader = FindClosestTrader(target); + for (i = 0; i < availableTraders.length; i += 1) + { + WriteTrader(availableTraders[i], availableTraders[i] == closestTrader, + console); + if (i != availableTraders.length - 1) { + console.Write(T(TCOMMA_SPACE)); + } + } + console.Flush(); +} + +protected function HandleTraderTime(CommandCall result) +{ + local int countDownValue; + local Text parameter; + local Parser parser; + local APlayer callerPlayer; + parameter = result.GetParameters().GetText(T(TTRADER_TIME)); + if (parameter.Compare(T(TPAUSE), SCASE_INSENSITIVE)) + { + _.kf.trading.SetCountDownPause(true); + return; + } + else if (parameter.Compare(T(TUNPAUSE), SCASE_INSENSITIVE)) + { + _.kf.trading.SetCountDownPause(false); + return; + } + parser = _.text.Parse(parameter); + if (parser.MInteger(countDownValue).Ok()) { + _.kf.trading.SetCountDown(countDownValue); + } + else + { + callerPlayer = result.GetCallerPlayer(); + if (callerPlayer != none) + { + callerPlayer.Console() + .UseColor(_.color.TextFailure) + .Write(T(TCANNOT_PARSE_PARAM)) + .WriteLine(parameter) + .ResetColor(); + } + } + parser.FreeSelf(); + +} + +protected function SetTradersOpen(bool doOpen, CommandCall result) +{ + local int i; + local bool needToBootPlayers; + local array selectedTraders; + selectedTraders = GetTradersArray(result); + needToBootPlayers = !doOpen + && !result.GetOptions().HasKey(T(TIGNORE_PLAYERS)); + for (i = 0; i < selectedTraders.length; i += 1) + { + selectedTraders[i].SetOpen(doOpen); + if (needToBootPlayers) { + selectedTraders[i].BootPlayers(); + } + } +} + +protected function SelectTrader(CommandCall result) +{ + local int i; + local APlayer callerPlayer; + local ConsoleWriter console; + local Text selectedTraderName, nextTraderName; + local ATrader previouslySelectedTrader; + local array availableTraders; + selectedTraderName = result.GetParameters().GetText(T(TTRADER)); + previouslySelectedTrader = _.kf.trading.GetSelectedTrader(); + // Corner case: no new trader + if (selectedTraderName == none) + { + _.kf.trading.SelectTrader(none); + HandleTraderSwap(result, none, availableTraders[i]); + return; + } + // Find new trader among available ones + availableTraders = _.kf.trading.GetTraders(); + for (i = 0; i < availableTraders.length; i += 1) + { + nextTraderName = availableTraders[i].GetName(); + if (selectedTraderName.Compare(nextTraderName)) + { + availableTraders[i].Select(); + HandleTraderSwap( result, previouslySelectedTrader, + availableTraders[i]); + nextTraderName.FreeSelf(); + return; + } + nextTraderName.FreeSelf(); + } + // If we have reached here: given trader name was invalid. + callerPlayer = result.GetCallerPlayer(); + if (callerPlayer != none) { + console = callerPlayer.Console(); + } + if (console != none) + { + console.Flush() + .UseColor(_.color.TextNegative).Write(T(TUNKNOWN_TRADERS)) + .ResetColor().WriteLine(selectedTraderName); + } +} + +// Boot players from the old trader iff +// 1. It's different from the new one (otherwise swapping means nothing); +// 2. Option "ignore-players" was not specified. +protected function HandleTraderSwap( + CommandCall result, + ATrader oldTrader, + ATrader newTrader) +{ + if (oldTrader == none) return; + if (oldTrader == newTrader) return; + if (result.GetOptions().HasKey(T(TIGNORE_DOORS))) return; + if (result.GetOptions().HasKey(T(TIGNORE_PLAYERS))) return; + + oldTrader.Close().BootPlayers(); + if (newTrader != none) { + newTrader.Open(); + } +} + +protected function BootFromTraders(CommandCall result) +{ + local int i; + local array selectedTraders; + selectedTraders = GetTradersArray(result); + if (selectedTraders.length <= 0) { + selectedTraders = _.kf.trading.GetTraders(); + } + for (i = 0; i < selectedTraders.length; i += 1) { + selectedTraders[i].BootPlayers(); + } +} + +protected function SetTradersEnabled(bool doEnable, CommandCall result) +{ + local int i; + local array selectedTraders; + selectedTraders = GetTradersArray(result); + for (i = 0; i < selectedTraders.length; i += 1) { + selectedTraders[i].SetEnabled(doEnable); + } +} + +protected function SetTradersAutoOpen(CommandCall result) +{ + local int i; + local bool doAutoOpen; + local array selectedTraders; + doAutoOpen = result.GetParameters().GetBool(T(TAUTO_OPEN_QUESTION)); + selectedTraders = GetTradersArray(result); + for (i = 0; i < selectedTraders.length; i += 1) { + selectedTraders[i].SetAutoOpen(doAutoOpen); + } +} + +// Reads traders specified for the command (if any). +// Assumes `result != none`. +protected function array GetTradersArray(CommandCall result) +{ + local int i, j; + local APLayer callerPlayer; + local Text nextTraderName; + local DynamicArray specifiedTrades; + local array resultTraders; + local array availableTraders; + // Boundary cases: all traders and no traders at all + availableTraders = _.kf.trading.GetTraders(); + if (result.GetOptions().HasKey(T(TALL))) { + return availableTraders; + } + // Add closest one, if flag tells us to + callerPlayer = result.GetCallerPlayer(); + if (result.GetOptions().HasKey(T(TCLOSEST))) + { + resultTraders = + InsertTrader(resultTraders, FindClosestTrader(callerPlayer)); + } + specifiedTrades = result.GetParameters().GetDynamicArray(T(TTRADERS)); + if (specifiedTrades == none) { + return resultTraders; + } + // We iterate over `availableTraders` in the outer loop because: + // 1. Each `ATrader` from `availableTraders` will be matched only once, + // ensuring that result will not contain duplicate instances; + // 2. `availableTraders.GetName()` creates a new `Text` copy and + // `specifiedTrades.GetText()` does not. + for (i = 0; i < availableTraders.length; i += 1) + { + nextTraderName = availableTraders[i].GetName(); + for (j = 0; j < specifiedTrades.GetLength(); j += 1) + { + if (nextTraderName.Compare(specifiedTrades.GetText(j))) + { + resultTraders = + InsertTrader(resultTraders, availableTraders[i]); + specifiedTrades.Remove(j, 1); + break; + } + } + nextTraderName.FreeSelf(); + if (specifiedTrades.GetLength() <= 0) { + break; + } + } + // Some of the remaining trader names inside `specifiedTrades` do not + // match any actual traders. Report it. + if (callerPlayer != none && specifiedTrades.GetLength() > 0) { + ReportUnknowTraders(specifiedTrades, callerPlayer.Console()); + } + return resultTraders; +} + +// Auxiliary method that adds `newTrader` into existing array of traders +// if it is still missing. +protected function array InsertTrader( + array traders, + ATrader newTrader) +{ + local int i; + if (newTrader == none) { + return traders; + } + for (i = 0; i < traders.length; i += 1) + { + if (traders[i] == newTrader) { + return traders; + } + } + traders[traders.length] = newTrader; + return traders; +} + +protected function ReportUnknowTraders( + DynamicArray specifiedTrades, + ConsoleWriter console) +{ + local int i; + if (console == none) return; + if (specifiedTrades == none) return; + + console.Flush() + .UseColor(_.color.TextNegative) + .Write(T(TUNKNOWN_TRADERS)) + .ResetColor(); + for (i = 0; i < specifiedTrades.GetLength(); i += 1) + { + console.Write(specifiedTrades.GetText(i)); + if (i != specifiedTrades.GetLength() - 1) { + console.Write(T(TCOMMA_SPACE)); + } + } + console.Flush(); +} + +// Find closest trader to the `target` player +protected function ATrader FindClosestTrader(APlayer target) +{ + local int i; + local float newDistance, bestDistance; + local ATrader bestTrader; + local array availableTraders; + local Vector targetLocation; + if (target == none) { + return none; + } + targetLocation = target.GetLocation(); + availableTraders = _.kf.trading.GetTraders(); + for (i = 0; i < availableTraders.length; i += 1) + { + newDistance = + VSizeSquared(availableTraders[i].GetLocation() - targetLocation); + if (bestTrader == none || newDistance < bestDistance) + { + bestTrader = availableTraders[i]; + bestDistance = newDistance; + } + } + return bestTrader; +} + +// Writes a trader name along with information on whether it's +// disabled / auto-open +protected function WriteTrader( + ATrader traderToWrite, + bool isClosestTrader, + ConsoleWriter console) +{ + local Text traderName; + if (traderToWrite == none) return; + if (console == none) return; + + console.Write(T(TQUOTE)); + if (traderToWrite.IsOpen()) { + console.UseColor(_.color.TextPositive); + } + else { + console.UseColor(_.color.TextNegative); + } + traderName = traderToWrite.GetName(); + console.Write(traderName) + .ResetColor() + .Write(T(TQUOTE)); + traderName.FreeSelf(); + WriteTraderTags(traderToWrite, isClosestTrader, console); +} + +protected function WriteTraderTags( + ATrader traderToWrite, + bool isClosest, + ConsoleWriter console) +{ + local bool hasTagsInFront; + local bool isAutoOpen, isSelected; + if (traderToWrite == none) { + return; + } + if (!traderToWrite.IsEnabled()) + { + console.Write(T(TDISABLED_FLAG)); + return; + } + isAutoOpen = traderToWrite.IsAutoOpen(); + isSelected = traderToWrite.IsSelected(); + if (!isAutoOpen && !isSelected && !isClosest) { + return; + } + console.Write(T(TSPACE)).Write(T(TPARENTHESIS_OPEN)); + if (isClosest) + { + console.Write(T(TCLOSEST)); + hasTagsInFront = true; + } + if (isAutoOpen) + { + if (hasTagsInFront) { + console.Write(T(TCOMMA_SPACE)); + } + console.Write(T(TAUTO_OPEN_FLAG)); + hasTagsInFront = true; + } + if (isSelected) + { + if (hasTagsInFront) { + console.Write(T(TCOMMA_SPACE)); + } + console.Write(T(TSELECTED_FLAG)); + } + console.Write(T(TPARENTHESIS_CLOSE)); +} + +defaultproperties +{ + TLIST = 0 + stringConstants(0) = "list" + TOPEN = 1 + stringConstants(1) = "open" + TCLOSE = 2 + stringConstants(2) = "close" + TENABLE = 3 + stringConstants(3) = "enable" + TDISABLE = 4 + stringConstants(4) = "disable" + TAUTO_OPEN = 5 + stringConstants(5) = "autoopen" + TTRADER = 6 + stringConstants(6) = "trader" + TTRADERS = 7 + stringConstants(7) = "traders" + TALL = 8 + stringConstants(8) = "all" + TAUTO_OPEN_QUESTION = 9 + stringConstants(9) = "autoOpen?" + TQUOTE = 10 + stringConstants(10) = "\"" + TAUTO_OPEN_FLAG = 11 + stringConstants(11) = "auto-open" + TDISABLED_FLAG = 12 + stringConstants(12) = " (disabled)" + TUNKNOWN_TRADERS = 13 + stringConstants(13) = "Could not find some of the traders: " + TLIST_TRADERS = 14 + stringConstants(14) = "List of available traders: " + TCOMMA_SPACE = 15 + stringConstants(15) = ", " + TPARENTHESIS_OPEN = 16 + stringConstants(16) = "(" + TPARENTHESIS_CLOSE = 17 + stringConstants(17) = ")" + TSELECTED_FLAG = 18 + stringConstants(18) = "selected" + TSELECT = 19 + stringConstants(19) = "select" + TIGNORE_DOORS = 20 + stringConstants(20) = "ignore-doors" + TBOOT = 21 + stringConstants(21) = "boot" + TTIME = 22 + stringConstants(22) = "time" + TTRADER_TIME = 23 + stringConstants(23) = "traderTime" + TIGNORE_PLAYERS = 24 + stringConstants(24) = "ignore-players" + TPAUSE = 25 + stringConstants(25) = "pause" + TUNPAUSE = 26 + stringConstants(26) = "unpause" + TCANNOT_PARSE_PARAM = 27 + stringConstants(27) = "Cannot parse parameter: " + TCLOSEST = 28 + stringConstants(28) = "closest" + TSPACE = 29 + stringConstants(29) = " " +} \ No newline at end of file diff --git a/sources/Manifest.uc b/sources/Manifest.uc index 789e0f4..bb3b074 100644 --- a/sources/Manifest.uc +++ b/sources/Manifest.uc @@ -26,7 +26,7 @@ defaultproperties commands(0) = class'ACommandHelp' commands(1) = class'ACommandDosh' commands(2) = class'ACommandNick' - commands(3) = class'ACommandTest' + commands(3) = class'ACommandTrader' services(0) = class'ConnectionService' services(1) = class'PlayerService' aliasSources(0) = class'AliasSource'