/** * Auxiliary object for `ACommandFeature` to help with managing pending * configs for `Feature`s. Pending configs are `HashTable`s with config data * that are yet to be applied to configs and `Feature`s. They allow users to * make several changes to the data before actually applying changes to * the gameplay. * Copyright 2022 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * * Acedia is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License, or * (at your option) any later version. * * Acedia is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Acedia. If not, see . */ 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 selectedFeatureClass; var private Text selectedConfigName; // Possible errors that might occur when working with pending configs enum PendingConfigToolResult { // No error PCTE_None, // Pending version of specified config does not exist PCTE_ConfigMissing, // JSON object (`HashTable`) was expected as a parameter for the operation, // but something else was given PCTE_ExpectedObject, // Specified JSON pointer points an non-existent location PCTE_BadPointer }; struct PendingConfigs { var class featureClass; var HashTable pendingSaves; }; var private array featurePendingEdits; protected function Finalizer() { local int i; for (i = 0; i < featurePendingEdits.length; i ++) { _.memory.Free(featurePendingEdits[i].pendingSaves); } featurePendingEdits.length = 0; } /** * Selects feature and config to perform all future operations on. * * @param featureClass Class of the feature for which to edit pending * configs. * @param configName Name of the pending config that caller tool will * work with. */ public final function SelectConfig( class featureClass, BaseText configName) { _.memory.Free(selectedConfigName); selectedFeatureClass = featureClass; selectedConfigName = none; if (configName != none) { selectedConfigName = configName.LowerCopy(); } } /** * Checks wither caller tool has recorded pending config named `configName` * for `Feature` defined by class `featureClass`. * This method does no checks regarding existence of an actual config for * the specified `Feature` - it only checks whether caller tool has pending * version. * * @param featureClass Class of the `Feature` to check for pending config. * @param configName Name of the config to check for existence of its * pending version. * @return `true` if specified pending config exists and `false` otherwise. */ public function bool HasPendingConfigFor( class featureClass, BaseText configName) { local int i; local bool result; local Text lowerCaseConfigName; if (featureClass == none) return false; if (configName == none) return false; for (i = 0; i < featurePendingEdits.length; i ++) { if (featurePendingEdits[i].featureClass == featureClass) { lowerCaseConfigName = configName.LowerCopy(); result = featurePendingEdits[i].pendingSaves .HasKey(lowerCaseConfigName); lowerCaseConfigName.FreeSelf(); return result; } } return false; } /** * Returns data recorded for the selected pending config inside caller tool. * * @param createIfMissing Method only returns data of the pending version of * the config and if selected config does not yet have a pending version, * it will, by default, return `none`. This parameter allows this method to * create a pending config, based on current config with selected name * (if it exists). * @return Data recorded for the selected pending config. If selected config * does not have a pending version, `createIfMissing` is set to `false` * or not even current config with selected name exists - method returns * `none`. */ public function HashTable GetPendingConfigData(optional bool createIfMissing) { local int editsIndex; local HashTable result; local PendingConfigs newRecord; if (selectedConfigName == none) { return none; } editsIndex = GetPendingConfigDataIndex(); if (editsIndex >= 0) { result = featurePendingEdits[editsIndex] .pendingSaves .GetHashTable(selectedConfigName); if (result != none) { return result; } } if (createIfMissing) { if (editsIndex < 0) { editsIndex = featurePendingEdits.length; newRecord.featureClass = selectedFeatureClass; newRecord.pendingSaves = _.collections.EmptyHashTable(); featurePendingEdits[editsIndex] = newRecord; } result = GetCurrentConfigData(); if (result != none) { featurePendingEdits[editsIndex] .pendingSaves .SetItem(selectedConfigName, result); } } return result; } /** * Makes and edit to the config. * * @param pathToValue JSON path at which to make a change. * @param newValue Value to record at the specified path. * @return Result of the operation that reports any errors that have occured. * Any changes are made iff result is `PCTE_None`. */ public function PendingConfigToolResult EditConfig( BaseText pathToValue, AcediaObject newValue) { local HashTable pendingData; local JSONPointer pointer; local PendingConfigToolResult result; if (pathToValue == none) { return PCTE_BadPointer; } pendingData = GetPendingConfigData(true); if (pendingData == none) { return PCTE_ConfigMissing; } // Set new data pointer = _.json.Pointer(pathToValue); result = SetItemByJSON(pendingData, pointer, newValue); pointer.FreeSelf(); pendingData.FreeSelf(); return result; } private function PendingConfigToolResult SetItemByJSON( HashTable data, JSONPointer pointer, AcediaObject jsonValue) { local Text containerIndex; local AcediaObject container; local PendingConfigToolResult result; if (pointer.IsEmpty()) { if (HashTable(jsonValue) != none) { result = ChangePendingConfigData(HashTable(jsonValue)); _.memory.Free(jsonValue); return result; } _.memory.Free(jsonValue); return PCTE_ExpectedObject; } // Since `!pointer.IsEmpty()`, we are guaranteed to pop a valid value containerIndex = pointer.Pop(); container = data.GetItemByJSON(pointer); if (container == none) { containerIndex.FreeSelf(); return PCTE_BadPointer; } result = SetContainerItemByText(container, containerIndex, jsonValue); containerIndex.FreeSelf(); container.FreeSelf(); return result; } /*EditFeatureConfig #1: true SetItemByJSON: true SetContainerItemByText: true EditFeatureConfig #2: true */ private function PendingConfigToolResult SetContainerItemByText( AcediaObject container, BaseText containerIndex, AcediaObject jsonValue) { local int arrayIndex; local Parser parser; local ArrayList arrayListContainer; local HashTable hashTableContainer; hashTableContainer = HashTable(container); arrayListContainer = ArrayList(container); if (hashTableContainer != none) { hashTableContainer.SetItem(containerIndex, jsonValue); } if (arrayListContainer != none) { parser = containerIndex.Parse(); if (parser.MInteger(arrayIndex, 10).Ok()) { arrayListContainer.SetItem(arrayIndex, jsonValue); parser.FreeSelf(); return PCTE_None; } parser.FreeSelf(); if (containerIndex.Compare(P("-"))) { arrayListContainer.AddItem(jsonValue); } else { return PCTE_BadPointer; } } return PCTE_None; } /** * Removes selected pending config. * * @return Result of the operation that reports any errors that have occured. * Any changes are made iff result is `PCTE_None`. */ public final function PendingConfigToolResult RemoveConfig() { local int editIndex; local HashTable pendingSaves; editIndex = GetPendingConfigDataIndex(); if (editIndex < 0) return PCTE_ConfigMissing; pendingSaves = featurePendingEdits[editIndex].pendingSaves; if (!pendingSaves.HasKey(selectedConfigName)) return PCTE_ConfigMissing; pendingSaves.RemoveItem(selectedConfigName); if (pendingSaves.GetLength() <= 0) { pendingSaves.FreeSelf(); featurePendingEdits.Remove(editIndex, 1); } return PCTE_None; } private function int GetPendingConfigDataIndex() { local int i; for (i = 0; i < featurePendingEdits.length; i ++) { if (featurePendingEdits[i].featureClass == selectedFeatureClass) { return i; } } return -1; } private function PendingConfigToolResult ChangePendingConfigData( HashTable newData) { local int editsIndex; if (selectedConfigName == none) { return PCTE_None; } editsIndex = GetPendingConfigDataIndex(); if (editsIndex < 0) { return PCTE_ConfigMissing; } featurePendingEdits[editsIndex].pendingSaves .SetItem(selectedConfigName, newData); return PCTE_None; } private function HashTable GetCurrentConfigData() { return selectedFeatureClass.default.configClass.static .LoadData(selectedConfigName); } defaultproperties { }