diff --git a/sources/Commands/ACommandFeature.uc b/sources/Commands/ACommandFeature.uc
new file mode 100644
index 0000000..94dc1a9
--- /dev/null
+++ b/sources/Commands/ACommandFeature.uc
@@ -0,0 +1,294 @@
+/**
+ * Command for managing features.
+ * Copyright 2022 Anton Tarasenko
+ *------------------------------------------------------------------------------
+ * This file is part of Acedia.
+ *
+ * Acedia is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Acedia is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Acedia. If not, see .
+ */
+class ACommandFeature extends Command;
+
+// TODO: autoconf, newconf, deleteconf, setconf
+// TODO: when displaying features - display which one is enabled
+
+protected function BuildData(CommandDataBuilder builder)
+{
+ builder.Name(P("feature")).Summary(P("Managing features."))
+ .Describe(P("Command for displaying and enabling/disabling features."));
+ builder.SubCommand(P("enable"))
+ .ParamText(P("feature"))
+ .OptionalParams()
+ .ParamText(P("config"))
+ .Describe(P("Enables specified ."));
+ builder.SubCommand(P("disable"))
+ .ParamText(P("feature"))
+ .Describe(P("Disables specified ."));
+}
+
+protected function Executed(CallData result, EPlayer callerPlayer)
+{
+ if (result.subCommandName.IsEmpty()) {
+ ShowAllFeatures();
+ }
+ else if (result.subCommandName.Compare(P("enable")))
+ {
+ TryEnableFeature(
+ callerPlayer,
+ result.parameters.GetText(P("feature")),
+ result.parameters.GetText(P("config")));
+ }
+ else if (result.subCommandName.Compare(P("disable"))) {
+ DisableFeature(callerPlayer, result.parameters.GetText(P("feature")));
+ }
+}
+
+protected function TryEnableFeature(
+ EPlayer callerPlayer,
+ Text featureName,
+ Text chosenConfig)
+{
+ local Text oldConfig, newConfig;
+ local class featureClass;
+ local class configClass;
+ featureClass = LoadFeatureClass(featureName);
+ if (featureClass == none) return;
+ configClass = featureClass.default.configClass;
+ if (configClass == none) return;
+
+ if (chosenConfig == none) {
+ newConfig = configClass.static.GetAutoEnabledConfig();
+ }
+ else if (!configClass.static.Exists(chosenConfig))
+ {
+ callerConsole
+ .Write(P("Specified config \""))
+ .Write(chosenConfig)
+ .WriteLine(F("\" {$TextFailure doesn't exist}"));
+ return;
+ }
+ else {
+ newConfig = chosenConfig.Copy();
+ }
+ if (newConfig == none)
+ {
+ callerConsole
+ .Write(F("{$TextFailue No config specified} and"
+ @ "{$TextFailure no auto-enabled config} exists for feature "))
+ .UseColorOnce(_.color.TextEmphasis)
+ .WriteLine(newConfig);
+ _.memory.Free(newConfig);
+ return;
+ }
+ oldConfig = featureClass.static.GetCurrentConfig();
+ if (oldConfig != none && oldConfig.Compare(chosenConfig, SCASE_INSENSITIVE))
+ {
+ callerConsole
+ .Write(P("Config "))
+ .Write(chosenConfig)
+ .WriteLine(P(" is already enabled"));
+ _.memory.Free(oldConfig);
+ _.memory.Free(newConfig);
+ return;
+ }
+ EnableFeature(
+ callerPlayer,
+ featureClass,
+ configClass,
+ newConfig,
+ chosenConfig == none);
+ _.memory.Free(newConfig);
+ _.memory.Free(oldConfig);
+}
+
+protected function EnableFeature(
+ EPlayer callerPlayer,
+ class featureClass,
+ class configClass,
+ Text chosenConfig,
+ bool autoConfig)
+{
+ local bool wasEnabled;
+ local Feature instance;
+ local Text featureName, callerName;
+ if (callerPlayer == none) return;
+ if (featureClass == none) return;
+ if (configClass == none) return;
+
+ callerName = callerPlayer.GetName();
+ featureName = _.text.FromClass(featureClass);
+ wasEnabled = featureClass.static.IsEnabled();
+ instance = featureClass.static.EnableMe(chosenConfig);
+ if (instance == none)
+ {
+ callerConsole.Write(F("Something went {$TextFailure wrong},"
+ @ "{$TextFailure failed} to enabled feature"))
+ .UseColorOnce(_.color.TextEmphasis).WriteLine(featureName);
+ }
+ else if (wasEnabled)
+ {
+ callerConsole
+ .Write(P("Swapping config for the feature "))
+ .UseColorOnce(_.color.TextEmphasis).Write(featureName)
+ .Write(P(" to \"")).Write(chosenConfig).WriteLine(P("\""));
+ othersConsole
+ .Write(callerName)
+ .Write(P(" swapped config for the feature "))
+ .UseColorOnce(_.color.TextEmphasis).Write(featureName)
+ .Write(P(" to \"")).Write(chosenConfig).WriteLine(P("\""));
+ }
+ else
+ {
+ callerConsole
+ .Write(P("Enabling feature "))
+ .UseColorOnce(_.color.TextEmphasis).Write(featureName)
+ .Write(P(" with config \"")).Write(chosenConfig).WriteLine(P("\""));
+ othersConsole
+ .Write(callerName)
+ .Write(P(" enabled feature "))
+ .UseColorOnce(_.color.TextEmphasis).Write(featureName)
+ .Write(P(" with config \"")).Write(chosenConfig).WriteLine(P("\""));
+ }
+ _.memory.Free(callerName);
+ _.memory.Free(featureName);
+}
+
+protected function DisableFeature(EPlayer callerPlayer, Text featureName)
+{
+ local Text playerName;
+ local Text featureRealName;
+ local class featureClass;
+ featureClass = LoadFeatureClass(featureName);
+ if (featureClass == none) return;
+ if (callerPlayer == none) return;
+
+ featureRealName = _.text.FromClass(featureClass);
+ playerName = callerPlayer.GetName();
+ if (!featureClass.static.IsEnabled())
+ {
+ callerConsole
+ .Write(P("Feature "))
+ .UseColorOnce(_.color.TextEmphasis).Write(featureRealName)
+ .WriteLine(F(" is already {$TextNegative disabled}"));
+ _.memory.Free(featureRealName);
+ _.memory.Free(playerName);
+ return;
+ }
+ featureClass.static.DisableMe();
+ callerConsole
+ .Write(P("Feature "))
+ .UseColorOnce(_.color.TextEmphasis).Write(featureRealName)
+ .WriteLine(F(" is {$TextNegative disabled}"));
+ othersConsole
+ .Write(playerName).Write(F(" {$TextNegative disabled} feature "))
+ .UseColorOnce(_.color.TextEmphasis).WriteLine(featureRealName);
+ _.memory.Free(featureRealName);
+ _.memory.Free(playerName);
+}
+
+protected function class LoadFeatureClass(Text featureName)
+{
+ local Text featureClassName;
+ local class featureClass;
+ if (featureName == none) {
+ return none;
+ }
+ if (featureName.StartsWith(P("$"))) {
+ featureClassName = _.alias.ResolveFeature(featureName, true);
+ }
+ else {
+ featureClassName = featureName.Copy();
+ }
+ featureClass = class(_.memory.LoadClass(featureClassName));
+ if (featureClass == none)
+ {
+ callerConsole
+ .Write(F("{$TextFailure Failed} to load feature `"))
+ .Write(featureName)
+ .WriteLine(P("`"));
+ }
+ _.memory.Free(featureClassName);
+ return featureClass;
+}
+
+protected function ShowAllFeatures()
+{
+ local int i;
+ local CoreService service;
+ local array< class > availableFeatures;
+ service = CoreService(class'CoreService'.static.Require());
+ availableFeatures = service.GetAvailableFeatures();
+ for (i = 0; i < availableFeatures.length; i ++) {
+ ShowFeature(availableFeatures[i]);
+ }
+}
+
+protected function ShowFeature(class feature)
+{
+ local int i;
+ local Text autoConfig;
+ local MutableText featureName, builder;
+ local ReportTool reportTool;
+ local array availableConfigs;
+ local class configClass;
+ if (feature == none) {
+ return;
+ }
+ configClass = feature.default.configClass;
+ if (configClass != none) {
+ availableConfigs = configClass.static.AvailableConfigs();
+ }
+ featureName = _.text
+ .FromClassM(feature)
+ .ChangeDefaultFormatting(
+ _.text.FormattingFromColor(_.color.TextEmphasis));
+ builder = _.text.Empty();
+ if (feature.static.IsEnabled()) {
+ builder.Append(F("[ {$TextPositive enabled} ] "));
+ }
+ else {
+ builder.Append(F("[ {$TextNegative disabled} ] "));
+ }
+ builder.Append(featureName);
+ _.memory.Free(featureName);
+ if (availableConfigs.length == 1) {
+ builder.Append(P(" with config:"));
+ }
+ else if (availableConfigs.length > 1) {
+ builder.Append(P(" with configs:"));
+ }
+ reportTool = ReportTool(_.memory.Allocate(class'ReportTool'));
+ reportTool.Initialize(builder);
+ _.memory.Free(builder);
+ autoConfig = configClass.static.GetAutoEnabledConfig();
+ for (i = 0; i < availableConfigs.length; i += 1)
+ {
+ builder = _.text.Empty().Append(availableConfigs[i]);
+ reportTool.Item(builder);
+ if ( autoConfig != none
+ && autoConfig.Compare(availableConfigs[i], SCASE_INSENSITIVE))
+ {
+ reportTool.Detail(F("{$TextPositive auto enabled}"));
+ }
+ _.memory.Free(builder);
+ builder = none;
+ }
+ reportTool.Report(callerConsole);
+ _.memory.FreeMany(availableConfigs);
+ _.memory.Free(reportTool);
+ _.memory.Free(autoConfig);
+}
+
+defaultproperties
+{
+}
\ No newline at end of file
diff --git a/sources/Futility_Feature.uc b/sources/Futility_Feature.uc
index 740cb6e..547539b 100644
--- a/sources/Futility_Feature.uc
+++ b/sources/Futility_Feature.uc
@@ -37,6 +37,7 @@ protected function OnEnabled()
commandsFeature.RegisterCommand(class'ACommandTrader');
commandsFeature.RegisterCommand(class'ACommandDB');
commandsFeature.RegisterCommand(class'ACommandInventory');
+ commandsFeature.RegisterCommand(class'ACommandFeature');
}
protected function OnDisabled()
@@ -51,6 +52,7 @@ protected function OnDisabled()
commandsFeature.RemoveCommand(class'ACommandTrader');
commandsFeature.RemoveCommand(class'ACommandDB');
commandsFeature.RemoveCommand(class'ACommandInventory');
+ commandsFeature.RemoveCommand(class'ACommandFeature');
}
}