1 /***************************************************************************** 2 * Copyright (c) 2014-2020 OpenRCT2 developers 3 * 4 * For a complete list of all authors, please refer to contributors.md 5 * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 6 * 7 * OpenRCT2 is licensed under the GNU General Public License version 3. 8 *****************************************************************************/ 9 10 #pragma once 11 12 #ifdef ENABLE_SCRIPTING 13 14 # include "../../../Context.h" 15 # include "../../../config/Config.h" 16 # include "../../../localisation/LocalisationService.h" 17 # include "../../Duktape.hpp" 18 # include "../../ScriptEngine.h" 19 20 namespace OpenRCT2::Scripting 21 { 22 class ScConfiguration 23 { 24 private: 25 bool _isUserConfig{}; 26 DukValue _backingObject; 27 28 public: 29 // context.configuration ScConfiguration()30 ScConfiguration() 31 : _isUserConfig(true) 32 { 33 } 34 35 // context.sharedStorage ScConfiguration(const DukValue & backingObject)36 ScConfiguration(const DukValue& backingObject) 37 : _backingObject(backingObject) 38 { 39 } 40 Register(duk_context * ctx)41 static void Register(duk_context* ctx) 42 { 43 dukglue_register_method(ctx, &ScConfiguration::getAll, "getAll"); 44 dukglue_register_method(ctx, &ScConfiguration::get, "get"); 45 dukglue_register_method(ctx, &ScConfiguration::set, "set"); 46 dukglue_register_method(ctx, &ScConfiguration::has, "has"); 47 } 48 49 private: GetNextNamespace(std::string_view input) const50 std::pair<std::string_view, std::string_view> GetNextNamespace(std::string_view input) const 51 { 52 auto pos = input.find('.'); 53 if (pos == std::string_view::npos) 54 { 55 return std::make_pair(input, std::string_view()); 56 } 57 58 return std::make_pair(input.substr(0, pos), input.substr(pos + 1)); 59 } 60 GetNamespaceAndKey(std::string_view input) const61 std::pair<std::string_view, std::string_view> GetNamespaceAndKey(std::string_view input) const 62 { 63 auto pos = input.find_last_of('.'); 64 return pos == std::string_view::npos ? std::make_pair(std::string_view(), input) 65 : std::make_pair(input.substr(0, pos), input.substr(pos + 1)); 66 } 67 GetNamespaceObject(std::string_view ns) const68 std::optional<DukValue> GetNamespaceObject(std::string_view ns) const 69 { 70 auto store = _backingObject; 71 auto k = ns; 72 bool end; 73 do 74 { 75 auto [next, remainder] = GetNextNamespace(k); 76 store = store[next]; 77 k = remainder; 78 end = store.type() == DukValue::Type::UNDEFINED || remainder.empty(); 79 } while (!end); 80 return store.type() == DukValue::OBJECT ? std::make_optional(store) : std::nullopt; 81 } 82 GetOrCreateNamespaceObject(duk_context * ctx,std::string_view ns) const83 DukValue GetOrCreateNamespaceObject(duk_context* ctx, std::string_view ns) const 84 { 85 auto store = _backingObject; 86 if (!ns.empty()) 87 { 88 std::string_view k = ns; 89 bool end; 90 do 91 { 92 auto [next, remainder] = GetNextNamespace(k); 93 auto subStore = store[next]; 94 k = remainder; 95 if (subStore.type() == DukValue::Type::UNDEFINED) 96 { 97 store.push(); 98 duk_push_object(ctx); 99 store = DukValue::copy_from_stack(ctx); 100 duk_put_prop_lstring(ctx, -2, next.data(), next.size()); 101 duk_pop(ctx); 102 } 103 else 104 { 105 store = subStore; 106 } 107 end = remainder.empty(); 108 } while (!end); 109 } 110 return store; 111 } 112 IsValidNamespace(std::string_view ns) const113 bool IsValidNamespace(std::string_view ns) const 114 { 115 if (ns.empty() || ns[0] == '.' || ns[ns.size() - 1] == '.') 116 { 117 return false; 118 } 119 for (size_t i = 1; i < ns.size() - 1; i++) 120 { 121 if (ns[i - 1] == '.' && ns[i] == '.') 122 { 123 return false; 124 } 125 } 126 return true; 127 } 128 IsValidKey(std::string_view key) const129 bool IsValidKey(std::string_view key) const 130 { 131 return !key.empty() && key.find('.') == std::string_view::npos; 132 } 133 getAll(const std::string & ns) const134 DukValue getAll(const std::string& ns) const 135 { 136 DukValue result; 137 auto ctx = GetContext()->GetScriptEngine().GetContext(); 138 if (IsValidNamespace(ns)) 139 { 140 if (_isUserConfig) 141 { 142 DukObject obj(ctx); 143 if (ns == "general") 144 { 145 obj.Set("general.language", gConfigGeneral.language); 146 obj.Set("general.showFps", gConfigGeneral.show_fps); 147 } 148 result = obj.Take(); 149 } 150 else 151 { 152 auto obj = GetNamespaceObject(ns); 153 result = obj ? *obj : DukObject(ctx).Take(); 154 } 155 } 156 else 157 { 158 duk_error(ctx, DUK_ERR_ERROR, "Namespace was invalid."); 159 } 160 return result; 161 } 162 get(const std::string & key,const DukValue & defaultValue) const163 DukValue get(const std::string& key, const DukValue& defaultValue) const 164 { 165 auto ctx = GetContext()->GetScriptEngine().GetContext(); 166 if (_isUserConfig) 167 { 168 if (key == "general.language") 169 { 170 auto& localisationService = GetContext()->GetLocalisationService(); 171 auto language = localisationService.GetCurrentLanguage(); 172 auto locale = ""; 173 if (language >= 0 && static_cast<size_t>(language) < std::size(LanguagesDescriptors)) 174 { 175 locale = LanguagesDescriptors[language].locale; 176 } 177 duk_push_string(ctx, locale); 178 return DukValue::take_from_stack(ctx); 179 } 180 if (key == "general.showFps") 181 { 182 duk_push_boolean(ctx, gConfigGeneral.show_fps); 183 return DukValue::take_from_stack(ctx); 184 } 185 } 186 else 187 { 188 auto [ns, n] = GetNamespaceAndKey(key); 189 if (!IsValidNamespace(ns)) 190 { 191 duk_error(ctx, DUK_ERR_ERROR, "Namespace was invalid."); 192 } 193 else if (!IsValidKey(n)) 194 { 195 duk_error(ctx, DUK_ERR_ERROR, "Key was invalid."); 196 } 197 else 198 { 199 auto obj = GetNamespaceObject(ns); 200 if (obj) 201 { 202 auto val = (*obj)[n]; 203 if (val.type() != DukValue::Type::UNDEFINED) 204 { 205 return val; 206 } 207 } 208 } 209 } 210 return defaultValue; 211 } 212 set(const std::string & key,const DukValue & value) const213 void set(const std::string& key, const DukValue& value) const 214 { 215 auto& scriptEngine = GetContext()->GetScriptEngine(); 216 auto ctx = scriptEngine.GetContext(); 217 if (_isUserConfig) 218 { 219 try 220 { 221 if (key == "general.showFps") 222 { 223 gConfigGeneral.show_fps = value.as_bool(); 224 } 225 else 226 { 227 duk_error(ctx, DUK_ERR_ERROR, "Property does not exist."); 228 } 229 } 230 catch (const DukException&) 231 { 232 duk_error(ctx, DUK_ERR_ERROR, "Invalid value for this property."); 233 } 234 } 235 else 236 { 237 auto [ns, n] = GetNamespaceAndKey(key); 238 if (!IsValidNamespace(ns)) 239 { 240 duk_error(ctx, DUK_ERR_ERROR, "Namespace was invalid."); 241 } 242 else if (!IsValidKey(n)) 243 { 244 duk_error(ctx, DUK_ERR_ERROR, "Key was invalid."); 245 } 246 else 247 { 248 auto obj = GetOrCreateNamespaceObject(ctx, ns); 249 obj.push(); 250 if (value.type() == DukValue::Type::UNDEFINED) 251 { 252 duk_del_prop_lstring(ctx, -1, n.data(), n.size()); 253 } 254 else 255 { 256 value.push(); 257 duk_put_prop_lstring(ctx, -2, n.data(), n.size()); 258 } 259 duk_pop(ctx); 260 261 scriptEngine.SaveSharedStorage(); 262 } 263 } 264 } 265 has(const std::string & key) const266 bool has(const std::string& key) const 267 { 268 auto val = get(key, DukValue()); 269 return val.type() != DukValue::Type::UNDEFINED; 270 } 271 }; 272 } // namespace OpenRCT2::Scripting 273 274 #endif 275