1 /* 2 Provide functions to load and save configuration files using json format. 3 4 Replacing previous binary formats with portable text files. 5 See https://github.com/raduprv/Eternal-Lands/issues/71 6 7 Author bluap/pjbroad April 2020 8 Stay at home. Save lives. Protect the NHS. 9 */ 10 #include <iostream> 11 #include <fstream> 12 #include <string> 13 #include <iomanip> 14 #include <nlohmann/json.hpp> 15 16 #include "chat.h" 17 #include "elloggingwrapper.h" 18 #include "counters.h" 19 #include "manufacture.h" 20 #include "text.h" 21 22 // Helper functions 23 // 24 namespace JSON_IO 25 { get_json_indent(void)26 static size_t get_json_indent(void) 27 { return 0; } // 0 is compact, non-zero give pretty output, 4 for example exit_error(const char * function,size_t line,const std::string & message,int error_code)28 static int exit_error(const char *function, size_t line, const std::string& message, int error_code) 29 { LOG_ERROR("%s:%ld %s", function, line, message.c_str()); return error_code; } info_message(const char * function,size_t line,std::string message)30 static void info_message(const char *function, size_t line, std::string message) 31 { LOG_INFO("%s:%ld %s", function, line, message.c_str()); } console_message(const std::string & file_type,const std::string & message)32 static void console_message(const std::string& file_type, const std::string& message) 33 { std::string full_message = "Problem with " + file_type + ": " + message; LOG_TO_CONSOLE(c_red3, full_message.c_str()); } file_format_error(const std::string & file_type)34 static void file_format_error(const std::string& file_type) 35 { console_message(file_type, "File format error. " + file_type + " will not be saved until this is corrected."); } 36 } 37 38 39 namespace JSON_IO_Recipes 40 { 41 using json = nlohmann::json; 42 43 44 // A Class to load and save manufacture recipes in json format. 45 // 46 class Recipes 47 { 48 public: Recipes(void)49 Recipes(void) : opened(false), parse_error(false) {} 50 int open(const char *file_name); 51 int load(recipe_entry *recipes_store, size_t max_recipes); 52 int save(const char *file_name, recipe_entry *recipes_store, size_t num_recipes, int current_recipe); 53 private: 54 bool opened; // we have opened the file and populated the read_json object 55 bool parse_error; // there was an error populating the json object 56 json read_json; // the complete json object read from file 57 const char * class_name_str = "Recipes"; 58 }; 59 60 61 // Read the json from file and return the number of recipes, or -1 for error. 62 // open(const char * file_name)63 int Recipes::open(const char *file_name) 64 { 65 JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + std::string(file_name) + "]"); 66 67 std::ifstream in_file(file_name); 68 if (!in_file) 69 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to open [" + std::string(file_name) + "]", -1); 70 71 try 72 { 73 in_file >> read_json; 74 } 75 catch (json::exception& e) 76 { 77 parse_error = true; 78 JSON_IO::file_format_error(class_name_str); 79 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, e.what(), -1); 80 } 81 82 if (!read_json["recipes"].is_array()) 83 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Missing recipes[]", -1); 84 85 opened = true; 86 return read_json["recipes"].size(); 87 } 88 89 90 // Parse the json read by open() and store into the recipe array. 91 // The recipe array must have been initialised to all zero - calloc(). 92 // String memory is allocated here but must be freed by the caller. 93 // Missing fields should soft fail. 94 // Returns the active recipe, or -1 for an error. 95 // load(recipe_entry * recipes_store,size_t max_recipes)96 int Recipes::load(recipe_entry *recipes_store, size_t max_recipes) 97 { 98 JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, ""); 99 100 if (!recipes_store) 101 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Recipe store is NULL", -1); 102 103 if (!opened) 104 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "JSON object not open()ed", -1); 105 106 size_t number_of_recipes = (read_json["recipes"].is_array()) ?read_json["recipes"].size() :0; 107 int cur_recipe = 0; 108 109 for (size_t i = 0; i < number_of_recipes && i < max_recipes; i++) 110 { 111 json recipe = read_json["recipes"][i]; 112 if (recipe.is_null()) 113 continue; 114 json items = recipe["items"]; 115 if (!items.is_array()) 116 continue; 117 for (size_t j = 0; j < NUM_MIX_SLOTS && j < items.size(); j++) 118 { 119 json the_item = items[j]; 120 if (the_item.is_null()) 121 continue; 122 recipes_store[i].items[j].id = (the_item["id"].is_number_unsigned()) ?the_item["id"].get<Uint16>() :unset_item_uid; 123 recipes_store[i].items[j].image_id = (the_item["image_id"].is_number_integer()) ?the_item["image_id"].get<int>() :0; 124 recipes_store[i].items[j].quantity = (the_item["quantity"].is_number_integer()) ?the_item["quantity"].get<int>() :0; 125 } 126 if (!recipe["name"].is_string()) 127 continue; 128 std::string name = recipe["name"].get<std::string>(); 129 if (name.size() > 0) 130 { 131 recipes_store[i].name = static_cast<char *>(malloc(name.size() + 1)); 132 strcpy(recipes_store[i].name, name.c_str()); 133 } 134 if (recipe["current"].is_boolean()) 135 cur_recipe = i; 136 } 137 138 // we are done with the json object so free the memory 139 read_json.clear(); 140 opened = false; 141 142 return cur_recipe; 143 } 144 145 146 // Save the recipe data in json format. 147 // Empty slots in a recipe are not saved. 148 // Ids that are unset, are not saved. 149 // Null names are saved as empty strings. 150 // Return 0 if successful otherwise -1. 151 // save(const char * file_name,recipe_entry * recipes_store,size_t num_recipes,int current_recipe)152 int Recipes::save(const char *file_name, recipe_entry *recipes_store, size_t num_recipes, int current_recipe) 153 { 154 JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + std::string(file_name) + "]"); 155 156 if (parse_error) 157 { 158 JSON_IO::file_format_error(class_name_str); 159 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Not saving, because we had a load error. Fix the problem first.", -1); 160 } 161 162 json write_json; 163 164 json recipe_list = json::array(); 165 for (size_t i=0; i<num_recipes; i++) 166 { 167 json recipe; 168 json item_list = json::array(); 169 for (size_t j=0; j<NUM_MIX_SLOTS; j++) 170 { 171 if (recipes_store[i].items[j].quantity <= 0) 172 continue; 173 json one_item; 174 if (recipes_store[i].items[j].id != unset_item_uid) 175 one_item["id"] = recipes_store[i].items[j].id; 176 one_item["image_id"] = recipes_store[i].items[j].image_id; 177 one_item["quantity"] = recipes_store[i].items[j].quantity; 178 item_list.push_back(one_item); 179 } 180 recipe["items"] = item_list; 181 recipe["name"] = (recipes_store[i].name) ?recipes_store[i].name :""; 182 if (current_recipe == static_cast<int>(i)) 183 recipe["current"] = true; 184 recipe_list.push_back(recipe); 185 } 186 write_json["recipes"] = recipe_list; 187 188 std::ofstream out_file(file_name); 189 if (out_file) 190 { 191 out_file << std::setw(JSON_IO::get_json_indent()) << write_json << std::endl; 192 return 0; 193 } 194 else 195 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to write json [" + std::string(file_name) + "]", -1); 196 } 197 198 } // end JSON_IO_Recipes namespace 199 200 201 namespace JSON_IO_Quickspells 202 { 203 using json = nlohmann::json; 204 205 206 // A Class to load and save quickspells in json format. 207 // 208 class Quickspells 209 { 210 public: Quickspells(void)211 Quickspells(void) : parse_error(false) {} 212 int load(const char *file_name, int *spell_ids, size_t max_num_spell_id); 213 int save(const char *file_name, Uint16 *spell_ids, size_t num_spell_id); 214 private: 215 bool parse_error; // there was an error populating the json object 216 const char * class_name_str = "Quickspells"; 217 }; 218 219 220 // Load the quickspells ids up to the maximum. 221 // Return the number read or -1 for an error. 222 // load(const char * file_name,int * spell_ids,size_t max_num_spell_id)223 int Quickspells::load(const char *file_name, int *spell_ids, size_t max_num_spell_id) 224 { 225 JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + std::string(file_name) + "]"); 226 227 std::ifstream in_file(file_name); 228 if (!in_file) 229 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to open [" + std::string(file_name) + "]", -1); 230 231 json read_json; 232 233 try 234 { 235 in_file >> read_json; 236 } 237 catch (json::exception& e) 238 { 239 parse_error = true; 240 JSON_IO::file_format_error(class_name_str); 241 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, e.what(), -1); 242 } 243 244 if (!read_json["quickspells"].is_array()) 245 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Missing quickspells[]", -1); 246 247 for (size_t i = 0; i < read_json["quickspells"].size() && i < max_num_spell_id; i++) 248 spell_ids[i] = (read_json["quickspells"][i].is_number_integer()) ?read_json["quickspells"][i].get<int>() :-1; 249 250 return (read_json["quickspells"].size() < max_num_spell_id) ?read_json["quickspells"].size() :max_num_spell_id; 251 } 252 253 254 // Save the quickspells ids. 255 // Return 0, or -1 on an error 256 // save(const char * file_name,Uint16 * spell_ids,size_t num_spell_id)257 int Quickspells::save(const char *file_name, Uint16 *spell_ids, size_t num_spell_id) 258 { 259 JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + std::string(file_name) + "]"); 260 261 if (parse_error) 262 { 263 JSON_IO::file_format_error(class_name_str); 264 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Not saving, because we had a load error. Fix the problem first.", -1); 265 } 266 267 json write_json; 268 269 json quickspells_list = json::array(); 270 for (size_t i = 0; i < num_spell_id; i++) 271 quickspells_list.push_back(spell_ids[i]); 272 write_json["quickspells"] = quickspells_list; 273 274 std::ofstream out_file(file_name); 275 if (out_file) 276 { 277 out_file << std::setw(JSON_IO::get_json_indent()) << write_json << std::endl; 278 return 0; 279 } 280 else 281 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to write json [" + std::string(file_name) + "]", -1); 282 } 283 284 } // end JSON_IO_Quickspells namespace 285 286 287 namespace JSON_IO_Counters 288 { 289 using json = nlohmann::json; 290 291 292 // A Class to load and save the counters in json format. 293 // 294 class Counters 295 { 296 public: Counters(void)297 Counters(void) : parse_error(false) {} 298 int load(const char *file_name, const char **cat_str, int *entries, size_t num_categories, struct Counter **the_counters); 299 int save(const char *file_name, const char **cat_str, const int *entries, size_t num_categories, const struct Counter **the_counters); 300 private: 301 bool parse_error; // there was an error populating the json object 302 const char * class_name_str = "Counters"; 303 }; 304 305 306 // Load the counters. 307 // Memory allocated here, must be freed by the caller. 308 // Return the number of categories actually read, or -1 on error. 309 // load(const char * file_name,const char ** cat_str,int * entries,size_t num_categories,struct Counter ** the_counters)310 int Counters::load(const char *file_name, const char **cat_str, int *entries, size_t num_categories, struct Counter **the_counters) 311 { 312 JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + std::string(file_name) + "]"); 313 314 std::ifstream in_file(file_name); 315 if (!in_file) 316 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to open [" + std::string(file_name) + "]", -1); 317 318 json read_json; 319 320 try 321 { 322 in_file >> read_json; 323 } 324 catch (json::exception& e) 325 { 326 parse_error = true; 327 JSON_IO::file_format_error(class_name_str); 328 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, e.what(), -1); 329 } 330 331 if (!read_json["categories"].is_array()) 332 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Missing categories[]", -1); 333 334 for (size_t i = 0; i < read_json["categories"].size() && i < num_categories; i++) 335 { 336 if (!read_json["categories"][i]["name"].is_string() || (read_json["categories"][i]["name"] != std::string(cat_str[i]))) 337 continue; 338 json entries_list = read_json["categories"][i]["entries"]; 339 if (!entries_list.is_array()) 340 continue; 341 for (size_t j = 0; j < entries_list.size(); j++) 342 { 343 json entry = entries_list[j]; 344 if (!entry["name"].is_string()) 345 continue; 346 std::string name = entry["name"].get<std::string>(); 347 if (name.empty()) 348 continue; 349 entries[i]++; 350 the_counters[i] = (struct Counter *)realloc(the_counters[i], entries[i] * sizeof(struct Counter)); 351 the_counters[i][j].name = static_cast<char *>(malloc(name.size() + 1)); 352 strcpy(the_counters[i][j].name, name.c_str()); 353 the_counters[i][j].n_session = 0; 354 the_counters[i][j].n_total = (entry["n_total"].is_number_unsigned()) ?entry["n_total"].get<Uint32>() :0; 355 the_counters[i][j].extra = (entry["extra"].is_number_unsigned()) ?entry["extra"].get<Uint32>() :0; 356 } 357 } 358 359 return (read_json["categories"].size() < num_categories) ?read_json["categories"].size() :num_categories; 360 } 361 362 363 // Save the counters. 364 // Return 0, or -1 on error. 365 // save(const char * file_name,const char ** cat_str,const int * entries,size_t num_categories,const struct Counter ** the_counters)366 int Counters::save(const char *file_name, const char **cat_str, const int *entries, size_t num_categories, const struct Counter **the_counters) 367 { 368 JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + std::string(file_name) + "]"); 369 370 if (parse_error) 371 { 372 JSON_IO::file_format_error(class_name_str); 373 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Not saving, because we had a load error. Fix the problem first.", -1); 374 } 375 376 json categories_list = json::array(); 377 for (size_t i = 0; i < num_categories; i++) 378 { 379 json category; 380 category["name"] = cat_str[i]; 381 json entries_list = json::array(); 382 for (int j = 0; j < entries[i]; j++) 383 { 384 json entry; 385 entry["name"] = the_counters[i][j].name; 386 entry["n_total"] = the_counters[i][j].n_total; 387 if (the_counters[i][j].extra > 0) 388 entry["extra"] = the_counters[i][j].extra; 389 entries_list.push_back(entry); 390 } 391 category["entries"] = entries_list; 392 categories_list.push_back(category); 393 } 394 395 json write_json; 396 write_json["categories"] = categories_list; 397 398 std::ofstream out_file(file_name); 399 if (out_file) 400 { 401 out_file << std::setw(JSON_IO::get_json_indent()) << write_json << std::endl; 402 return 0; 403 } 404 else 405 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to write json [" + std::string(file_name) + "]", -1); 406 } 407 408 } //end JSON_IO_Counters namespace 409 410 411 namespace JSON_IO_Channel_Colours 412 { 413 using json = nlohmann::json; 414 415 416 // A Class to load and save the the channel colours in json format. 417 // 418 class Channel_Colours 419 { 420 public: Channel_Colours(void)421 Channel_Colours(void) : parse_error(false) {} 422 int load(const char *file_name, channelcolor *the_channel_colours, size_t max_channel_colours); 423 int save(const char *file_name, const channelcolor *the_channel_colours, size_t max_channel_colours); 424 private: 425 bool parse_error; // there was an error populating the json object 426 const char * class_name_str = "Channel Colours"; 427 }; 428 429 430 // Load the channel colours. 431 // Assumed that he channel colour arrar has been initialised. 432 // Return the number of sets actually read, or -1 on error. 433 // load(const char * file_name,channelcolor * the_channel_colours,size_t max_channel_colours)434 int Channel_Colours::load(const char *file_name, channelcolor *the_channel_colours, size_t max_channel_colours) 435 { 436 JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + std::string(file_name) + "]"); 437 438 std::ifstream in_file(file_name); 439 if (!in_file) 440 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to open [" + std::string(file_name) + "]", -1); 441 442 json read_json; 443 444 try 445 { 446 in_file >> read_json; 447 } 448 catch (json::exception& e) 449 { 450 parse_error = true; 451 JSON_IO::file_format_error(class_name_str); 452 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, e.what(), -1); 453 } 454 455 if (!read_json["channel_colours"].is_array()) 456 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Missing channel_colours[]", -1); 457 458 for (size_t i = 0; i < read_json["channel_colours"].size() && i < max_channel_colours; i++) 459 { 460 json channel_colour_set = read_json["channel_colours"][i]; 461 if (!channel_colour_set["channel"].is_number_unsigned() || !channel_colour_set["colour"].is_number_integer() || 462 (channel_colour_set["colour"] < 0)) 463 continue; 464 the_channel_colours[i].nr = channel_colour_set["channel"]; 465 the_channel_colours[i].color = channel_colour_set["colour"]; 466 } 467 468 return (read_json["channel_colours"].size() < max_channel_colours) ?read_json["channel_colours"].size() :max_channel_colours; 469 } 470 471 472 // Save the channel colours. 473 // Return 0, or -1 on error. 474 // save(const char * file_name,const channelcolor * the_channel_colours,size_t max_channel_colours)475 int Channel_Colours::save(const char *file_name, const channelcolor *the_channel_colours, size_t max_channel_colours) 476 { 477 JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + std::string(file_name) + "]"); 478 479 if (parse_error) 480 { 481 JSON_IO::file_format_error(class_name_str); 482 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Not saving, because we had a load error. Fix the problem first.", -1); 483 } 484 485 json write_json; 486 487 json channel_colours_list = json::array(); 488 for (size_t i = 0; i < max_channel_colours; i++) 489 { 490 if (the_channel_colours[i].color < 0) 491 continue; 492 json channel_colour_set; 493 channel_colour_set["channel"] = the_channel_colours[i].nr; 494 channel_colour_set["colour"] = the_channel_colours[i].color; 495 channel_colours_list.push_back(channel_colour_set); 496 } 497 write_json["channel_colours"] = channel_colours_list; 498 499 std::ofstream out_file(file_name); 500 if (out_file) 501 { 502 out_file << std::setw(JSON_IO::get_json_indent()) << write_json << std::endl; 503 return 0; 504 } 505 else 506 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to write json [" + std::string(file_name) + "]", -1); 507 } 508 } 509 510 511 namespace JSON_IO_Character_Options 512 { 513 using json = nlohmann::json; 514 515 516 // A Class to load and save the character options in json format. 517 // Character options if present override values in the ini file. 518 // 519 class Character_Options 520 { 521 public: Character_Options(void)522 Character_Options(void) : parse_error(false), loaded(false), modified(false) {} set_file_name(const char * file_name)523 void set_file_name(const char *file_name) { the_file_name = std::string(file_name); } 524 int load(void); 525 int save(void); 526 template <class TheType> TheType get(const char *var_name, TheType default_var_value) const; 527 template <class TheType> void set(const char *var_name, TheType value); 528 bool exists(const char *var_name) const; 529 void remove(const char *var_name); 530 private: have_options_list(void) const531 bool have_options_list(void) const { return ((json_data.find(ini_options_list_str) != json_data.end()) && json_data[ini_options_list_str].is_array()); } 532 bool parse_error; // there was an error populating the json object 533 bool loaded; // true if the file is already loaded 534 bool modified; // if true, the object has been modified and needs to be saved 535 json json_data; // the json object as read from file, or empty if no file 536 std::string the_file_name; 537 const char * class_name_str = "Character Options"; 538 const char * ini_options_list_str = "ini_options"; 539 const char * name_str = "name"; 540 const char * value_str = "value"; 541 }; 542 543 544 // Load the character options. 545 // Return 0 or -1 on error. 546 // load(void)547 int Character_Options::load(void) 548 { 549 if (loaded) 550 return 0; 551 552 JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + the_file_name + "]"); 553 554 std::ifstream in_file(the_file_name); 555 if (!in_file) 556 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to open [" + the_file_name + "]", -1); 557 558 try 559 { 560 in_file >> json_data; 561 } 562 catch (json::exception& e) 563 { 564 parse_error = true; 565 JSON_IO::file_format_error(class_name_str); 566 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, e.what(), -1); 567 } 568 569 loaded = true; 570 571 return 0; 572 } 573 574 575 // Save the character options. 576 // Return 0, or -1 on error. 577 // save(void)578 int Character_Options::save(void) 579 { 580 if (!modified) 581 return 0; 582 583 JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + the_file_name + "]"); 584 585 if (parse_error) 586 { 587 JSON_IO::file_format_error(class_name_str); 588 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Not saving, because we had a load error. Fix the problem first.", -1); 589 } 590 591 std::ofstream out_file(the_file_name); 592 if (out_file) 593 { 594 out_file << std::setw(JSON_IO::get_json_indent()) << json_data << std::endl; 595 modified = false; 596 return 0; 597 } 598 else 599 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to write json [" + the_file_name + "]", -1); 600 } 601 602 603 // Return true if the specificed option exists for the character name 604 // exists(const char * var_name) const605 bool Character_Options::exists(const char *var_name) const 606 { 607 if (!have_options_list()) 608 return false; 609 610 for (auto var : json_data[ini_options_list_str].items()) 611 if (var.value()[name_str] == var_name) 612 return true; 613 614 return false; 615 } 616 617 618 // Return true if the specificed option exists for the character name 619 // remove(const char * var_name)620 void Character_Options::remove(const char *var_name) 621 { 622 if (!have_options_list()) 623 return; 624 625 // cannot get .erase() to work so do it the hard way.... 626 bool removed_var = false; 627 json new_options_list = json::array(); 628 for (auto var : json_data[ini_options_list_str].items()) 629 if (var.value()[name_str] == var_name) 630 removed_var = true; 631 else 632 new_options_list.push_back(var.value()); 633 if (removed_var) 634 { 635 json_data[ini_options_list_str] = new_options_list; 636 modified = true; 637 } 638 } 639 640 641 // Get the named value, if not found or invalid, the default value is returned. 642 // get(const char * var_name,TheType default_value) const643 template <class TheType> TheType Character_Options::get(const char *var_name, TheType default_value) const 644 { 645 if (!have_options_list()) 646 return default_value; 647 648 for (auto var : json_data[ini_options_list_str].items()) 649 if (var.value()[name_str] == var_name) 650 return var.value()[value_str]; 651 652 return default_value; 653 } 654 655 // Set the named value, creating the array if required 656 // set(const char * var_name,TheType value)657 template <class TheType> void Character_Options::set(const char *var_name, TheType value) 658 { 659 modified = true; 660 661 if (!have_options_list()) 662 json_data[ini_options_list_str] = json::array(); 663 664 for (auto var : json_data[ini_options_list_str].items()) 665 if (var.value()[name_str] == var_name) 666 { 667 var.value()[value_str] = value; 668 return; 669 } 670 json new_value; 671 new_value[name_str] = var_name; 672 new_value[value_str] = value; 673 json_data[ini_options_list_str].push_back(new_value); 674 return; 675 } 676 } 677 678 679 namespace JSON_IO_Client_State 680 { 681 using json = nlohmann::json; 682 683 // A Class to load and save the Client State. 684 // The state is items we need to save for the user 685 // but which are not options for them to set manually. 686 // 687 class Client_State 688 { 689 public: Client_State(void)690 Client_State(void) : parse_error(false) {} 691 int load(const char *file_name); 692 int save(const char *file_name); 693 template <class TheType> TheType get(const char *section_name, const char *var_name, TheType default_var_value) const; 694 template <class TheType> void set(const char *section_name, const char *var_name, TheType value); 695 private: 696 bool parse_error; // there was an error populating the json object 697 const char * class_name_str = "Client State"; 698 json state_read; 699 json state_write; 700 }; 701 702 703 // Load the Client State. 704 // Return 0 on success or -1 on error. 705 // load(const char * file_name)706 int Client_State::load(const char *file_name) 707 { 708 JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + std::string(file_name) + "]"); 709 710 std::ifstream in_file(file_name); 711 if (!in_file) 712 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to open [" + std::string(file_name) + "]", -1); 713 714 try 715 { 716 in_file >> state_read; 717 } 718 catch (json::exception& e) 719 { 720 parse_error = true; 721 JSON_IO::file_format_error(class_name_str); 722 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, e.what(), -1); 723 } 724 725 return 0; 726 } 727 728 729 // Save the Client State. 730 // Return 0, or -1 on error. 731 // save(const char * file_name)732 int Client_State::save(const char *file_name) 733 { 734 JSON_IO::info_message(__PRETTY_FUNCTION__, __LINE__, " [" + std::string(file_name) + "]"); 735 736 if (parse_error) 737 { 738 JSON_IO::file_format_error(class_name_str); 739 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Not saving, because we had a load error. Fix the problem first.", -1); 740 } 741 742 std::ofstream out_file(file_name); 743 if (out_file) 744 { 745 out_file << std::setw(JSON_IO::get_json_indent()) << state_write << std::endl; 746 return 0; 747 } 748 else 749 return JSON_IO::exit_error(__PRETTY_FUNCTION__, __LINE__, "Failed to write json [" + std::string(file_name) + "]", -1); 750 } 751 752 753 // Get the named value, if not found or invalid, the default value is returned. 754 // get(const char * section_name,const char * var_name,TheType default_value) const755 template <class TheType> TheType Client_State::get(const char *section_name, const char *var_name, TheType default_value) const 756 { 757 if (state_read.contains(section_name) && state_read[section_name].contains(var_name)) 758 return state_read[section_name][var_name].get<TheType>(); 759 return default_value; 760 } 761 762 // Set the named value, creating the array if required 763 // set(const char * section_name,const char * var_name,TheType value)764 template <class TheType> void Client_State::set(const char *section_name, const char *var_name, TheType value) 765 { 766 state_write[section_name][var_name] = value; 767 } 768 769 } 770 771 772 // The instance of the manufacture recipe object. 773 static JSON_IO_Recipes::Recipes recipes; 774 775 // The instance of the quickspells object. 776 static JSON_IO_Quickspells::Quickspells quickspells; 777 778 // The instance of the counters object. 779 static JSON_IO_Counters::Counters counters; 780 781 // The instance of the channel colours object. 782 static JSON_IO_Channel_Colours::Channel_Colours channel_colours; 783 784 // The instance of the character options object. 785 static JSON_IO_Character_Options::Character_Options character_options; 786 787 // The instance of the Client State ojbect. 788 static JSON_IO_Client_State::Client_State cstate; 789 790 // The C interface 791 // 792 extern "C" 793 { 794 // manufacture recipe functions json_open_recipes(const char * file_name)795 int json_open_recipes(const char *file_name) 796 { return recipes.open(file_name); } json_load_recipes(recipe_entry * recipes_store,size_t max_recipes)797 int json_load_recipes(recipe_entry *recipes_store, size_t max_recipes) 798 { return recipes.load(recipes_store, max_recipes); } json_save_recipes(const char * file_name,recipe_entry * recipes_store,size_t num_recipes,int current_recipe)799 int json_save_recipes(const char *file_name, recipe_entry *recipes_store, size_t num_recipes, int current_recipe) 800 { return recipes.save(file_name, recipes_store, num_recipes, current_recipe); } 801 802 // quickspells funcitons json_load_quickspells(const char * file_name,int * spell_ids,size_t max_num_spell_id)803 int json_load_quickspells(const char *file_name, int *spell_ids, size_t max_num_spell_id) 804 { return quickspells.load(file_name, spell_ids, max_num_spell_id); } json_save_quickspells(const char * file_name,Uint16 * spell_ids,size_t num_spell_id)805 int json_save_quickspells(const char *file_name, Uint16 *spell_ids, size_t num_spell_id) 806 { return quickspells.save(file_name, spell_ids, num_spell_id); } 807 808 // counters functions json_load_counters(const char * file_name,const char ** cat_str,int * entries,size_t num_categories,struct Counter ** the_counters)809 int json_load_counters(const char *file_name, const char **cat_str, int *entries, size_t num_categories, struct Counter **the_counters) 810 { return counters.load(file_name, cat_str, entries, num_categories, the_counters); } json_save_counters(const char * file_name,const char ** cat_str,const int * entries,size_t num_categories,const struct Counter ** the_counters)811 int json_save_counters(const char *file_name, const char **cat_str, const int *entries, size_t num_categories, const struct Counter **the_counters) 812 { return counters.save(file_name, cat_str, entries, num_categories, the_counters); } 813 814 // channel colours json_load_channel_colours(const char * file_name,channelcolor * the_channel_colours,size_t max_channel_colours)815 int json_load_channel_colours(const char *file_name, channelcolor *the_channel_colours, size_t max_channel_colours) 816 { return channel_colours.load(file_name, the_channel_colours, max_channel_colours); } json_save_channel_colours(const char * file_name,const channelcolor * the_channel_colours,size_t max_channel_colours)817 int json_save_channel_colours(const char *file_name, const channelcolor *the_channel_colours, size_t max_channel_colours) 818 { return channel_colours.save(file_name, the_channel_colours, max_channel_colours); } 819 820 // character options json_character_options_set_file_name(const char * file_name)821 void json_character_options_set_file_name(const char *file_name) 822 { character_options.set_file_name(file_name); } json_character_options_load_file(void)823 int json_character_options_load_file(void) 824 { return character_options.load(); } json_character_options_save_file(void)825 int json_character_options_save_file(void) 826 { return character_options.save(); } json_character_options_exists(const char * var_name)827 int json_character_options_exists(const char *var_name) 828 { return (character_options.exists(var_name)) ?1 :0; } json_character_options_remove(const char * var_name)829 void json_character_options_remove(const char *var_name) 830 { character_options.remove(var_name); } json_character_options_get_int(const char * var_name,int default_value)831 int json_character_options_get_int(const char *var_name, int default_value ) 832 { return character_options.get(var_name, default_value); } json_character_options_set_int(const char * var_name,int value)833 void json_character_options_set_int(const char *var_name, int value) 834 { character_options.set(var_name, value); } json_character_options_get_float(const char * var_name,float default_value)835 float json_character_options_get_float(const char *var_name, float default_value ) 836 { return character_options.get(var_name, default_value); } json_character_options_set_float(const char * var_name,float value)837 void json_character_options_set_float(const char *var_name, float value) 838 { character_options.set(var_name, value); } json_character_options_get_bool(const char * var_name,int default_value)839 int json_character_options_get_bool(const char *var_name, int default_value ) 840 { return (character_options.get(var_name, default_value)) ?1 :0; } json_character_options_set_bool(const char * var_name,int value)841 void json_character_options_set_bool(const char *var_name, int value) 842 { character_options.set(var_name, static_cast<bool>(value)); } 843 844 // Client State json_load_cstate(const char * file_name)845 int json_load_cstate(const char *file_name) 846 { return cstate.load(file_name); } json_save_cstate(const char * file_name)847 int json_save_cstate(const char *file_name) 848 { return cstate.save(file_name); } 849 // get/set int json_cstate_get_int(const char * section_name,const char * var_name,int default_value)850 int json_cstate_get_int(const char *section_name, const char *var_name, int default_value) 851 { return cstate.get(section_name, var_name, default_value); } json_cstate_set_int(const char * section_name,const char * var_name,int value)852 void json_cstate_set_int(const char *section_name, const char *var_name, int value) 853 { cstate.set(section_name, var_name, value); } 854 // get/set unsigned int json_cstate_get_unsigned_int(const char * section_name,const char * var_name,unsigned int default_value)855 unsigned int json_cstate_get_unsigned_int(const char *section_name, const char *var_name, unsigned int default_value) 856 { return cstate.get(section_name, var_name, default_value); } json_cstate_set_unsigned_int(const char * section_name,const char * var_name,unsigned int value)857 void json_cstate_set_unsigned_int(const char *section_name, const char *var_name, unsigned int value) 858 { cstate.set(section_name, var_name, value); } 859 // get/set float json_cstate_get_float(const char * section_name,const char * var_name,float default_value)860 float json_cstate_get_float(const char *section_name, const char *var_name, float default_value) 861 { return cstate.get(section_name, var_name, default_value); } json_cstate_set_float(const char * section_name,const char * var_name,float value)862 void json_cstate_set_float(const char *section_name, const char *var_name, float value) 863 { cstate.set(section_name, var_name, value); } 864 // get_set bool json_cstate_get_bool(const char * section_name,const char * var_name,int default_value)865 int json_cstate_get_bool(const char *section_name, const char *var_name, int default_value) 866 { return ((cstate.get(section_name, var_name, static_cast<bool>(default_value))) ?1 :0); } json_cstate_set_bool(const char * section_name,const char * var_name,int value)867 void json_cstate_set_bool(const char *section_name, const char *var_name, int value) 868 { cstate.set(section_name, var_name, static_cast<bool>(value)); } 869 } 870