1 /*------------------------------------------------------------------------------- 2 3 BARONY 4 File: mod_tools.hpp 5 Desc: misc modding tools 6 7 Copyright 2013-2016 (c) Turning Wheel LLC, all rights reserved. 8 See LICENSE for details. 9 10 -------------------------------------------------------------------------------*/ 11 12 #pragma once 13 #include "main.hpp" 14 #include "stat.hpp" 15 #include "json.hpp" 16 #include "files.hpp" 17 #include "prng.hpp" 18 #include "rapidjson/document.h" 19 #include "rapidjson/filereadstream.h" 20 #include "rapidjson/filewritestream.h" 21 #include "rapidjson/prettywriter.h" 22 #include "net.hpp" 23 #include "scores.hpp" 24 25 class CustomHelpers 26 { 27 public: addMemberToSubkey(rapidjson::Document & d,std::string subkey,std::string name,const rapidjson::Value & value)28 static void addMemberToSubkey(rapidjson::Document& d, std::string subkey, std::string name, const rapidjson::Value& value) 29 { 30 rapidjson::Value key(name.c_str(), d.GetAllocator()); // copy string name 31 rapidjson::Value val(value, d.GetAllocator()); 32 d[subkey.c_str()].AddMember(key, val, d.GetAllocator()); 33 } addMemberToRoot(rapidjson::Document & d,std::string name,const rapidjson::Value & value)34 static void addMemberToRoot(rapidjson::Document& d, std::string name, const rapidjson::Value& value) 35 { 36 rapidjson::Value key(name.c_str(), d.GetAllocator()); // copy string name 37 rapidjson::Value val(value, d.GetAllocator()); 38 d.AddMember(key, val, d.GetAllocator()); 39 } addArrayMemberToSubkey(rapidjson::Document & d,std::string subkey,const rapidjson::Value & value)40 static void addArrayMemberToSubkey(rapidjson::Document& d, std::string subkey, const rapidjson::Value& value) 41 { 42 rapidjson::Value val(value, d.GetAllocator()); // some value 43 d[subkey.c_str()].PushBack(val, d.GetAllocator()); 44 } isLevelPartOfSet(int level,bool secret,std::pair<std::unordered_set<int>,std::unordered_set<int>> & pairOfSets)45 static bool isLevelPartOfSet(int level, bool secret, std::pair<std::unordered_set<int>, std::unordered_set<int>>& pairOfSets) 46 { 47 if ( !secret ) 48 { 49 if ( pairOfSets.first.find(level) == pairOfSets.first.end() ) 50 { 51 return false; 52 } 53 } 54 else 55 { 56 if ( pairOfSets.second.find(level) == pairOfSets.second.end() ) 57 { 58 return false; 59 } 60 } 61 return true; 62 } 63 }; 64 65 class MonsterStatCustomManager 66 { 67 public: 68 std::mt19937 monsterStatSeed; 69 static const std::vector<std::string> itemStatusStrings; 70 static const std::vector<std::string> shopkeeperTypeStrings; MonsterStatCustomManager()71 MonsterStatCustomManager() : 72 monsterStatSeed(rand()) 73 { 74 }; 75 getSlotFromKeyName(std::string keyName)76 int getSlotFromKeyName(std::string keyName) 77 { 78 if ( keyName.compare("weapon") == 0 ) 79 { 80 return ITEM_SLOT_WEAPON; 81 } 82 else if ( keyName.compare("shield") == 0 ) 83 { 84 return ITEM_SLOT_SHIELD; 85 } 86 else if ( keyName.compare("helmet") == 0 ) 87 { 88 return ITEM_SLOT_HELM; 89 } 90 else if ( keyName.compare("breastplate") == 0 ) 91 { 92 return ITEM_SLOT_ARMOR; 93 } 94 else if ( keyName.compare("gloves") == 0 ) 95 { 96 return ITEM_SLOT_GLOVES; 97 } 98 else if ( keyName.compare("shoes") == 0 ) 99 { 100 return ITEM_SLOT_BOOTS; 101 } 102 else if ( keyName.compare("cloak") == 0 ) 103 { 104 return ITEM_SLOT_CLOAK; 105 } 106 else if ( keyName.compare("ring") == 0 ) 107 { 108 return ITEM_SLOT_RING; 109 } 110 else if ( keyName.compare("amulet") == 0 ) 111 { 112 return ITEM_SLOT_AMULET; 113 } 114 else if ( keyName.compare("mask") == 0 ) 115 { 116 return ITEM_SLOT_MASK; 117 } 118 return 0; 119 } 120 121 class ItemEntry 122 { 123 public: 124 ItemType type = WOODEN_SHIELD; 125 Status status = DECREPIT; 126 Sint16 beatitude = 0; 127 Sint16 count = 1; 128 Uint32 appearance = 0; 129 bool identified = 0; 130 int percentChance = 100; 131 int weightedChance = 1; 132 int dropChance = 100; 133 bool emptyItemEntry = false; 134 bool dropItemOnDeath = true; ItemEntry()135 ItemEntry() {}; ItemEntry(const Item & itemToRead)136 ItemEntry(const Item& itemToRead) 137 { 138 readFromItem(itemToRead); 139 } readFromItem(const Item & itemToRead)140 void readFromItem(const Item& itemToRead) 141 { 142 type = itemToRead.type; 143 status = itemToRead.status; 144 beatitude = itemToRead.beatitude; 145 count = itemToRead.count; 146 appearance = itemToRead.appearance; 147 identified = itemToRead.identified; 148 if ( itemToRead.appearance == MONSTER_ITEM_UNDROPPABLE_APPEARANCE ) 149 { 150 dropItemOnDeath = false; 151 } 152 } setValueFromAttributes(rapidjson::Document & d,rapidjson::Value & outObject)153 void setValueFromAttributes(rapidjson::Document& d, rapidjson::Value& outObject) 154 { 155 rapidjson::Value key1("type", d.GetAllocator()); 156 rapidjson::Value val1(itemNameStrings[type + 2], d.GetAllocator()); 157 outObject.AddMember(key1, val1, d.GetAllocator()); 158 159 rapidjson::Value key2("status", d.GetAllocator()); 160 rapidjson::Value val2(itemStatusStrings.at(status).c_str(), d.GetAllocator()); 161 outObject.AddMember(key2, val2, d.GetAllocator()); 162 163 outObject.AddMember("beatitude", rapidjson::Value(beatitude), d.GetAllocator()); 164 outObject.AddMember("count", rapidjson::Value(count), d.GetAllocator()); 165 outObject.AddMember("appearance", rapidjson::Value(appearance), d.GetAllocator()); 166 outObject.AddMember("identified", rapidjson::Value(identified), d.GetAllocator()); 167 outObject.AddMember("spawn_percent_chance", rapidjson::Value(100), d.GetAllocator()); 168 outObject.AddMember("drop_percent_chance", rapidjson::Value(dropItemOnDeath ? 100 : 0), d.GetAllocator()); 169 outObject.AddMember("slot_weighted_chance", rapidjson::Value(1), d.GetAllocator()); 170 } 171 getRandomArrayStr(const rapidjson::GenericArray<true,rapidjson::GenericValue<rapidjson::UTF8<>>> & arr,const char * invalidEntry)172 const char* getRandomArrayStr(const rapidjson::GenericArray<true, rapidjson::GenericValue<rapidjson::UTF8<>>>& arr, const char* invalidEntry) 173 { 174 if ( arr.Size() == 0 ) 175 { 176 return invalidEntry; 177 } 178 return (arr[rapidjson::SizeType(rand() % arr.Size())].GetString()); 179 } getRandomArrayInt(const rapidjson::GenericArray<true,rapidjson::GenericValue<rapidjson::UTF8<>>> & arr,int invalidEntry)180 int getRandomArrayInt(const rapidjson::GenericArray<true, rapidjson::GenericValue<rapidjson::UTF8<>>>& arr, int invalidEntry) 181 { 182 if ( arr.Size() == 0 ) 183 { 184 return invalidEntry; 185 } 186 return (arr[rapidjson::SizeType(rand() % arr.Size())].GetInt()); 187 } 188 readKeyToItemEntry(rapidjson::Value::ConstMemberIterator & itr)189 bool readKeyToItemEntry(rapidjson::Value::ConstMemberIterator& itr) 190 { 191 std::string name = itr->name.GetString(); 192 if ( name.compare("type") == 0 ) 193 { 194 std::string itemName = "empty"; 195 if ( itr->value.IsArray() ) 196 { 197 itemName = getRandomArrayStr(itr->value.GetArray(), "empty"); 198 } 199 else if ( itr->value.IsString() ) 200 { 201 itemName = itr->value.GetString(); 202 } 203 204 if ( itemName.compare("empty") == 0 ) 205 { 206 emptyItemEntry = true; 207 return true; 208 } 209 for ( int i = 0; i < NUMITEMS; ++i ) 210 { 211 if ( itemName.compare(itemNameStrings[i + 2]) == 0 ) 212 { 213 this->type = static_cast<ItemType>(i); 214 return true; 215 } 216 } 217 } 218 else if ( name.compare("status") == 0 ) 219 { 220 std::string status = "broken"; 221 if ( itr->value.IsArray() ) 222 { 223 status = getRandomArrayStr(itr->value.GetArray(), "broken"); 224 } 225 else if ( itr->value.IsString() ) 226 { 227 status = itr->value.GetString(); 228 } 229 for ( Uint32 i = 0; i < itemStatusStrings.size(); ++i ) 230 { 231 if ( status.compare(itemStatusStrings.at(i)) == 0 ) 232 { 233 this->status = static_cast<Status>(i); 234 return true; 235 } 236 } 237 } 238 else if ( name.compare("beatitude") == 0 ) 239 { 240 if ( itr->value.IsArray() ) 241 { 242 this->beatitude = static_cast<Sint16>(getRandomArrayInt(itr->value.GetArray(), 0)); 243 } 244 else if ( itr->value.IsInt() ) 245 { 246 this->beatitude = static_cast<Sint16>(itr->value.GetInt()); 247 } 248 return true; 249 } 250 else if ( name.compare("count") == 0 ) 251 { 252 if ( itr->value.IsArray() ) 253 { 254 this->count = static_cast<Sint16>(getRandomArrayInt(itr->value.GetArray(), 1)); 255 } 256 else if ( itr->value.IsInt() ) 257 { 258 this->count = static_cast<Sint16>(itr->value.GetInt()); 259 } 260 return true; 261 } 262 else if ( name.compare("appearance") == 0 ) 263 { 264 if ( itr->value.IsArray() ) 265 { 266 this->appearance = static_cast<Uint32>(getRandomArrayInt(itr->value.GetArray(), rand())); 267 } 268 else if ( itr->value.IsInt() ) 269 { 270 this->appearance = static_cast<Uint32>(itr->value.GetInt()); 271 } 272 else if ( itr->value.IsString() ) 273 { 274 std::string str = itr->value.GetString(); 275 if ( str.compare("random") == 0 ) 276 { 277 this->appearance = rand(); 278 } 279 } 280 return true; 281 } 282 else if ( name.compare("identified") == 0 ) 283 { 284 this->identified = itr->value.GetBool(); 285 return true; 286 } 287 else if ( name.compare("spawn_percent_chance") == 0 ) 288 { 289 this->percentChance = itr->value.GetInt(); 290 return true; 291 } 292 else if ( name.compare("drop_percent_chance") == 0 ) 293 { 294 this->dropChance = itr->value.GetInt(); 295 if ( rand() % 100 >= this->dropChance ) 296 { 297 this->dropItemOnDeath = false; 298 } 299 else 300 { 301 this->dropItemOnDeath = true; 302 } 303 } 304 else if ( name.compare("slot_weighted_chance") == 0 ) 305 { 306 this->weightedChance = std::max(1, itr->value.GetInt()); 307 return true; 308 } 309 return false; 310 } 311 }; 312 313 class StatEntry 314 { 315 std::mt19937 StatEntrySeed; 316 public: 317 char name[128] = ""; 318 int type = NOTHING; 319 sex_t sex = sex_t::MALE; 320 Uint32 appearance = 0; 321 Sint32 HP = 10; 322 Sint32 MAXHP = 10; 323 Sint32 OLDHP = 10; 324 Sint32 MP = 10; 325 Sint32 MAXMP = 10; 326 Sint32 STR = 0; 327 Sint32 DEX = 0; 328 Sint32 CON = 0; 329 Sint32 INT = 0; 330 Sint32 PER = 0; 331 Sint32 CHR = 0; 332 Sint32 EXP = 0; 333 Sint32 LVL = 0; 334 Sint32 GOLD = 0; 335 Sint32 HUNGER = 0; 336 Sint32 RANDOM_STR = 0; 337 Sint32 RANDOM_DEX = 0; 338 Sint32 RANDOM_CON = 0; 339 Sint32 RANDOM_INT = 0; 340 Sint32 RANDOM_PER = 0; 341 Sint32 RANDOM_CHR = 0; 342 Sint32 RANDOM_MAXHP = 0; 343 Sint32 RANDOM_HP = 0; 344 Sint32 RANDOM_MAXMP = 0; 345 Sint32 RANDOM_MP = 0; 346 Sint32 RANDOM_LVL = 0; 347 Sint32 RANDOM_GOLD = 0; 348 349 Sint32 PROFICIENCIES[NUMPROFICIENCIES]; 350 351 std::vector<std::pair<ItemEntry, int>> equipped_items; 352 std::vector<ItemEntry> inventory_items; 353 std::vector<std::pair<std::string, int>> followerVariants; 354 std::vector<std::pair<std::string, int>> shopkeeperStoreTypes; 355 int chosenShopkeeperStore = -1; 356 int shopkeeperMinItems = -1; 357 int shopkeeperMaxItems = -1; 358 int shopkeeperMaxGeneratedBlessing = -1; 359 bool shopkeeperGenDefaultItems = true; 360 enum ShopkeeperCustomFlags : int 361 { 362 ENABLE_GEN_ITEMS = 1, 363 DISABLE_GEN_ITEMS 364 }; 365 366 int numFollowers = 0; 367 bool isMonsterNameGeneric = false; 368 bool useDefaultEquipment = true; 369 bool useDefaultInventoryItems = true; 370 bool disableMiniboss = true; 371 bool forceFriendlyToPlayer = false; 372 bool forceEnemyToPlayer = false; 373 bool forceRecruitableToPlayer = false; 374 bool disableItemDrops = false; 375 int xpAwardPercent = 100; 376 bool castSpellbooksFromInventory = false; 377 int spellbookCastCooldown = 250; 378 StatEntry(const Stat * myStats)379 StatEntry(const Stat* myStats) : 380 StatEntrySeed(rand()) 381 { 382 readFromStats(myStats); 383 } StatEntry()384 StatEntry() : 385 StatEntrySeed(rand()) 386 { 387 for ( int i = 0; i < NUMPROFICIENCIES; ++i ) 388 { 389 PROFICIENCIES[i] = 0; 390 } 391 }; 392 getFollowerVariant()393 std::string getFollowerVariant() 394 { 395 if ( followerVariants.size() > 0 ) 396 { 397 std::vector<int> variantChances(followerVariants.size(), 0); 398 int index = 0; 399 for ( auto& pair : followerVariants ) 400 { 401 variantChances.at(index) = pair.second; 402 ++index; 403 } 404 405 std::discrete_distribution<> variantWeightedDistribution(variantChances.begin(), variantChances.end()); 406 int result = variantWeightedDistribution(StatEntrySeed); 407 return followerVariants.at(result).first; 408 } 409 return "none"; 410 } 411 readFromStats(const Stat * myStats)412 void readFromStats(const Stat* myStats) 413 { 414 strcpy(name, myStats->name); 415 type = myStats->type; 416 sex = myStats->sex; 417 appearance = myStats->appearance; 418 HP = myStats->HP; 419 MAXHP = myStats->MAXHP; 420 OLDHP = HP; 421 MP = myStats->MP; 422 MAXMP = myStats->MAXMP; 423 STR = myStats->STR; 424 DEX = myStats->DEX; 425 CON = myStats->CON; 426 INT = myStats->INT; 427 PER = myStats->PER; 428 CHR = myStats->CHR; 429 EXP = myStats->EXP; 430 LVL = myStats->LVL; 431 GOLD = myStats->GOLD; 432 433 RANDOM_STR = myStats->RANDOM_STR; 434 RANDOM_DEX = myStats->RANDOM_DEX; 435 RANDOM_CON = myStats->RANDOM_CON; 436 RANDOM_INT = myStats->RANDOM_INT; 437 RANDOM_PER = myStats->RANDOM_PER; 438 RANDOM_CHR = myStats->RANDOM_CHR; 439 RANDOM_MAXHP = myStats->RANDOM_MAXHP; 440 RANDOM_HP = myStats->RANDOM_HP; 441 RANDOM_MAXMP = myStats->RANDOM_MAXMP; 442 RANDOM_MP = myStats->RANDOM_MP; 443 RANDOM_LVL = myStats->RANDOM_LVL; 444 RANDOM_GOLD = myStats->RANDOM_GOLD; 445 446 for ( int i = 0; i < NUMPROFICIENCIES; ++i ) 447 { 448 PROFICIENCIES[i] = 0; 449 } 450 for ( int i = 0; i < NUMPROFICIENCIES; ++i ) 451 { 452 PROFICIENCIES[i] = myStats->PROFICIENCIES[i]; 453 } 454 } 455 setStats(Stat * myStats)456 void setStats(Stat* myStats) 457 { 458 strcpy(myStats->name, name); 459 myStats->type = static_cast<Monster>(type); 460 myStats->sex = static_cast<sex_t>(sex); 461 myStats->appearance = appearance; 462 myStats->HP = HP; 463 myStats->MAXHP = MAXHP; 464 myStats->OLDHP = myStats->HP; 465 myStats->MP = MP; 466 myStats->MAXMP = MAXMP; 467 myStats->STR = STR; 468 myStats->DEX = DEX; 469 myStats->CON = CON; 470 myStats->INT = INT; 471 myStats->PER = PER; 472 myStats->CHR = CHR; 473 myStats->EXP = EXP; 474 myStats->LVL = LVL; 475 myStats->GOLD = GOLD; 476 477 myStats->RANDOM_STR = RANDOM_STR; 478 myStats->RANDOM_DEX = RANDOM_DEX; 479 myStats->RANDOM_CON = RANDOM_CON; 480 myStats->RANDOM_INT = RANDOM_INT; 481 myStats->RANDOM_PER = RANDOM_PER; 482 myStats->RANDOM_CHR = RANDOM_CHR; 483 myStats->RANDOM_MAXHP = RANDOM_MAXHP; 484 myStats->RANDOM_HP = RANDOM_HP; 485 myStats->RANDOM_MAXMP = RANDOM_MAXMP; 486 myStats->RANDOM_MP = RANDOM_MP; 487 myStats->RANDOM_LVL = RANDOM_LVL; 488 myStats->RANDOM_GOLD = RANDOM_GOLD; 489 490 for ( int i = 0; i < NUMPROFICIENCIES; ++i ) 491 { 492 myStats->PROFICIENCIES[i] = PROFICIENCIES[i]; 493 } 494 } 495 setItems(Stat * myStats)496 void setItems(Stat* myStats) 497 { 498 std::unordered_set<int> equippedSlots; 499 for ( auto& it : equipped_items ) 500 { 501 equippedSlots.insert(it.second); 502 if ( it.first.percentChance < 100 ) 503 { 504 if ( rand() % 100 >= it.first.percentChance ) 505 { 506 continue; 507 } 508 } 509 if ( it.first.emptyItemEntry ) 510 { 511 continue; 512 } 513 switch ( it.second ) 514 { 515 case ITEM_SLOT_WEAPON: 516 myStats->weapon = newItem(it.first.type, it.first.status, it.first.beatitude, it.first.count, it.first.appearance, it.first.identified, nullptr); 517 if ( myStats->weapon ) 518 { 519 myStats->weapon->isDroppable = it.first.dropItemOnDeath; 520 } 521 break; 522 case ITEM_SLOT_SHIELD: 523 myStats->shield = newItem(it.first.type, it.first.status, it.first.beatitude, it.first.count, it.first.appearance, it.first.identified, nullptr); 524 if ( myStats->shield ) 525 { 526 myStats->shield->isDroppable = it.first.dropItemOnDeath; 527 } 528 break; 529 case ITEM_SLOT_HELM: 530 myStats->helmet = newItem(it.first.type, it.first.status, it.first.beatitude, it.first.count, it.first.appearance, it.first.identified, nullptr); 531 if ( myStats->helmet ) 532 { 533 myStats->helmet->isDroppable = it.first.dropItemOnDeath; 534 } 535 break; 536 case ITEM_SLOT_ARMOR: 537 myStats->breastplate = newItem(it.first.type, it.first.status, it.first.beatitude, it.first.count, it.first.appearance, it.first.identified, nullptr); 538 if ( myStats->breastplate ) 539 { 540 myStats->breastplate->isDroppable = it.first.dropItemOnDeath; 541 } 542 break; 543 case ITEM_SLOT_GLOVES: 544 myStats->gloves = newItem(it.first.type, it.first.status, it.first.beatitude, it.first.count, it.first.appearance, it.first.identified, nullptr); 545 if ( myStats->gloves ) 546 { 547 myStats->gloves->isDroppable = it.first.dropItemOnDeath; 548 } 549 break; 550 case ITEM_SLOT_BOOTS: 551 myStats->shoes = newItem(it.first.type, it.first.status, it.first.beatitude, it.first.count, it.first.appearance, it.first.identified, nullptr); 552 if ( myStats->shoes ) 553 { 554 myStats->shoes->isDroppable = it.first.dropItemOnDeath; 555 } 556 break; 557 case ITEM_SLOT_CLOAK: 558 myStats->cloak = newItem(it.first.type, it.first.status, it.first.beatitude, it.first.count, it.first.appearance, it.first.identified, nullptr); 559 if ( myStats->cloak ) 560 { 561 myStats->cloak->isDroppable = it.first.dropItemOnDeath; 562 } 563 break; 564 case ITEM_SLOT_RING: 565 myStats->ring = newItem(it.first.type, it.first.status, it.first.beatitude, it.first.count, it.first.appearance, it.first.identified, nullptr); 566 if ( myStats->ring ) 567 { 568 myStats->ring->isDroppable = it.first.dropItemOnDeath; 569 } 570 break; 571 case ITEM_SLOT_AMULET: 572 myStats->amulet = newItem(it.first.type, it.first.status, it.first.beatitude, it.first.count, it.first.appearance, it.first.identified, nullptr); 573 if ( myStats->amulet ) 574 { 575 myStats->amulet->isDroppable = it.first.dropItemOnDeath; 576 } 577 break; 578 case ITEM_SLOT_MASK: 579 myStats->mask = newItem(it.first.type, it.first.status, it.first.beatitude, it.first.count, it.first.appearance, it.first.identified, nullptr); 580 if ( myStats->mask ) 581 { 582 myStats->mask->isDroppable = it.first.dropItemOnDeath; 583 } 584 break; 585 default: 586 break; 587 } 588 } 589 for ( int equipSlots = 0; equipSlots < 10; ++equipSlots ) 590 { 591 if ( !useDefaultEquipment ) 592 { 593 // disable any default item slot spawning. 594 myStats->EDITOR_ITEMS[equipSlots * ITEM_SLOT_NUMPROPERTIES] = 0; 595 } 596 else 597 { 598 if ( equippedSlots.find(equipSlots * ITEM_SLOT_NUMPROPERTIES) != equippedSlots.end() ) 599 { 600 // disable item slots we (attempted) to fill in. 601 myStats->EDITOR_ITEMS[equipSlots * ITEM_SLOT_NUMPROPERTIES] = 0; 602 } 603 } 604 } 605 for ( auto& it : inventory_items ) 606 { 607 if ( it.emptyItemEntry ) 608 { 609 continue; 610 } 611 if ( it.percentChance < 100 ) 612 { 613 if ( rand() % 100 >= it.percentChance ) 614 { 615 continue; 616 } 617 } 618 Item* item = newItem(it.type, it.status, it.beatitude, it.count, it.appearance, it.identified, &myStats->inventory); 619 if ( item ) 620 { 621 item->isDroppable = it.dropItemOnDeath; 622 } 623 } 624 if ( !useDefaultInventoryItems ) 625 { 626 for ( int invSlots = ITEM_SLOT_INV_1; invSlots <= ITEM_SLOT_INV_6; invSlots = invSlots + ITEM_SLOT_NUMPROPERTIES ) 627 { 628 myStats->EDITOR_ITEMS[invSlots] = 0; 629 } 630 } 631 } 632 setStatsAndEquipmentToMonster(Stat * myStats)633 void setStatsAndEquipmentToMonster(Stat* myStats) 634 { 635 //myStats->clearStats(); 636 setStats(myStats); 637 setItems(myStats); 638 639 if ( isMonsterNameGeneric ) 640 { 641 myStats->MISC_FLAGS[STAT_FLAG_MONSTER_NAME_GENERIC] = 1; 642 } 643 if ( disableMiniboss ) 644 { 645 myStats->MISC_FLAGS[STAT_FLAG_DISABLE_MINIBOSS] = 1; 646 } 647 if ( forceFriendlyToPlayer ) 648 { 649 myStats->MISC_FLAGS[STAT_FLAG_FORCE_ALLEGIANCE_TO_PLAYER] = 650 Stat::MonsterForceAllegiance::MONSTER_FORCE_PLAYER_ALLY; 651 } 652 if ( forceEnemyToPlayer ) 653 { 654 myStats->MISC_FLAGS[STAT_FLAG_FORCE_ALLEGIANCE_TO_PLAYER] = 655 Stat::MonsterForceAllegiance::MONSTER_FORCE_PLAYER_ENEMY; 656 } 657 if ( forceRecruitableToPlayer ) 658 { 659 myStats->MISC_FLAGS[STAT_FLAG_FORCE_ALLEGIANCE_TO_PLAYER] = 660 Stat::MonsterForceAllegiance::MONSTER_FORCE_PLAYER_RECRUITABLE; 661 } 662 if ( disableItemDrops ) 663 { 664 myStats->MISC_FLAGS[STAT_FLAG_NO_DROP_ITEMS] = 1; 665 } 666 if ( xpAwardPercent != 100 ) 667 { 668 myStats->MISC_FLAGS[STAT_FLAG_XP_PERCENT_AWARD] = 1 + std::min(std::max(0, xpAwardPercent), 100); 669 } 670 if ( castSpellbooksFromInventory ) 671 { 672 myStats->MISC_FLAGS[STAT_FLAG_MONSTER_CAST_INVENTORY_SPELLBOOKS] = 1; 673 myStats->MISC_FLAGS[STAT_FLAG_MONSTER_CAST_INVENTORY_SPELLBOOKS] |= (spellbookCastCooldown << 4); 674 } 675 if ( myStats->type == SHOPKEEPER ) 676 { 677 if ( chosenShopkeeperStore >= 0 ) 678 { 679 myStats->MISC_FLAGS[STAT_FLAG_NPC] = chosenShopkeeperStore + 1; 680 } 681 Uint8 numItems = 0; 682 myStats->MISC_FLAGS[STAT_FLAG_SHOPKEEPER_CUSTOM_PROPERTIES] = 0; 683 if ( shopkeeperGenDefaultItems ) 684 { 685 if ( shopkeeperMinItems >= 0 && shopkeeperMaxItems >= 0 ) 686 { 687 numItems = shopkeeperMinItems + rand() % std::max(1, (shopkeeperMaxItems - shopkeeperMinItems + 1)); 688 myStats->MISC_FLAGS[STAT_FLAG_SHOPKEEPER_CUSTOM_PROPERTIES] |= numItems + 1; 689 } 690 if ( shopkeeperMaxGeneratedBlessing >= 0 ) 691 { 692 myStats->MISC_FLAGS[STAT_FLAG_SHOPKEEPER_CUSTOM_PROPERTIES] |= (static_cast<Uint8>(shopkeeperMaxGeneratedBlessing + 1) << 8); 693 } 694 myStats->MISC_FLAGS[STAT_FLAG_SHOPKEEPER_CUSTOM_PROPERTIES] |= (ShopkeeperCustomFlags::ENABLE_GEN_ITEMS << 12); // indicate to use this property. 695 } 696 else 697 { 698 myStats->MISC_FLAGS[STAT_FLAG_SHOPKEEPER_CUSTOM_PROPERTIES] |= (ShopkeeperCustomFlags::DISABLE_GEN_ITEMS << 12); // indicate to disable gen items. 699 } 700 } 701 } 702 setStatsAndEquipmentToPlayer(Stat * myStats,int player)703 void setStatsAndEquipmentToPlayer(Stat* myStats, int player) 704 { 705 //if ( player == 0 ) 706 //{ 707 // TextSourceScript tmpScript; 708 // tmpScript.playerClearInventory(true); 709 //} 710 //else 711 //{ 712 // // other players 713 // myStats->freePlayerEquipment(); 714 // myStats->clearStats(); 715 // TextSourceScript tmpScript; 716 // tmpScript.updateClientInformation(player, true, true, TextSourceScript::CLIENT_UPDATE_ALL); 717 //} 718 } 719 }; 720 writeAllFromStats(Stat * myStats)721 void writeAllFromStats(Stat* myStats) 722 { 723 rapidjson::Document d; 724 d.SetObject(); 725 rapidjson::Value version; 726 version.SetInt(1); 727 CustomHelpers::addMemberToRoot(d, "version", version); 728 readAttributesFromStats(myStats, d); 729 readItemsFromStats(myStats, d); 730 731 // misc properties 732 rapidjson::Value propsObject; 733 propsObject.SetObject(); 734 CustomHelpers::addMemberToRoot(d, "properties", propsObject); 735 CustomHelpers::addMemberToSubkey(d, "properties", "monster_name_always_display_as_generic_species", rapidjson::Value(false)); 736 CustomHelpers::addMemberToSubkey(d, "properties", "populate_empty_equipped_items_with_default", rapidjson::Value(true)); 737 CustomHelpers::addMemberToSubkey(d, "properties", "populate_default_inventory", rapidjson::Value(true)); 738 CustomHelpers::addMemberToSubkey(d, "properties", "disable_miniboss_chance", rapidjson::Value(false)); 739 CustomHelpers::addMemberToSubkey(d, "properties", "force_player_recruitable", rapidjson::Value(false)); 740 CustomHelpers::addMemberToSubkey(d, "properties", "force_player_friendly", rapidjson::Value(false)); 741 CustomHelpers::addMemberToSubkey(d, "properties", "force_player_enemy", rapidjson::Value(false)); 742 CustomHelpers::addMemberToSubkey(d, "properties", "disable_item_drops", rapidjson::Value(false)); 743 CustomHelpers::addMemberToSubkey(d, "properties", "xp_award_percent", rapidjson::Value(100)); 744 CustomHelpers::addMemberToSubkey(d, "properties", "enable_casting_inventory_spellbooks", rapidjson::Value(false)); 745 CustomHelpers::addMemberToSubkey(d, "properties", "spellbook_cast_cooldown", rapidjson::Value(250)); 746 747 if ( myStats->type == SHOPKEEPER ) 748 { 749 // shop properties 750 CustomHelpers::addMemberToRoot(d, "shopkeeper_properties", propsObject); 751 752 rapidjson::Value shopObject(rapidjson::kObjectType); 753 shopObject.SetObject(); 754 755 rapidjson::Value storeTypesObject(rapidjson::kObjectType); 756 storeTypesObject.AddMember("equipment", rapidjson::Value(1), d.GetAllocator()); 757 storeTypesObject.AddMember("hats", rapidjson::Value(1), d.GetAllocator()); 758 storeTypesObject.AddMember("jewelry", rapidjson::Value(1), d.GetAllocator()); 759 storeTypesObject.AddMember("books", rapidjson::Value(1), d.GetAllocator()); 760 storeTypesObject.AddMember("apothecary", rapidjson::Value(1), d.GetAllocator()); 761 storeTypesObject.AddMember("staffs", rapidjson::Value(1), d.GetAllocator()); 762 storeTypesObject.AddMember("food", rapidjson::Value(1), d.GetAllocator()); 763 storeTypesObject.AddMember("hardware", rapidjson::Value(1), d.GetAllocator()); 764 storeTypesObject.AddMember("hunting", rapidjson::Value(1), d.GetAllocator()); 765 storeTypesObject.AddMember("general", rapidjson::Value(1), d.GetAllocator()); 766 767 CustomHelpers::addMemberToSubkey(d, "shopkeeper_properties", "store_type_chances", storeTypesObject); 768 CustomHelpers::addMemberToSubkey(d, "shopkeeper_properties", "generate_default_shop_items", rapidjson::Value(true)); 769 CustomHelpers::addMemberToSubkey(d, "shopkeeper_properties", "num_generated_items_min", rapidjson::Value(10)); 770 CustomHelpers::addMemberToSubkey(d, "shopkeeper_properties", "num_generated_items_max", rapidjson::Value(15)); 771 CustomHelpers::addMemberToSubkey(d, "shopkeeper_properties", "generated_item_blessing_max", rapidjson::Value(0)); 772 } 773 774 // follower details 775 rapidjson::Value followersObject; 776 followersObject.SetObject(); 777 CustomHelpers::addMemberToRoot(d, "followers", followersObject); 778 CustomHelpers::addMemberToSubkey(d, "followers", "num_followers", rapidjson::Value(0)); 779 rapidjson::Value followerVariantsObject; 780 followerVariantsObject.SetObject(); 781 CustomHelpers::addMemberToSubkey(d, "followers", "follower_variants", followerVariantsObject); 782 783 writeToFile(d, monstertypename[myStats->type]); 784 } 785 readItemsFromStats(Stat * myStats,rapidjson::Document & d)786 void readItemsFromStats(Stat* myStats, rapidjson::Document& d) 787 { 788 rapidjson::Value equippedItemsObject; 789 equippedItemsObject.SetObject(); 790 CustomHelpers::addMemberToRoot(d, "equipped_items", equippedItemsObject); 791 addMemberFromItem(d, "equipped_items", "weapon", myStats->weapon); 792 addMemberFromItem(d, "equipped_items", "shield", myStats->shield); 793 addMemberFromItem(d, "equipped_items", "helmet", myStats->helmet); 794 addMemberFromItem(d, "equipped_items", "breastplate", myStats->breastplate); 795 addMemberFromItem(d, "equipped_items", "gloves", myStats->gloves); 796 addMemberFromItem(d, "equipped_items", "shoes", myStats->shoes); 797 addMemberFromItem(d, "equipped_items", "cloak", myStats->cloak); 798 addMemberFromItem(d, "equipped_items", "ring", myStats->ring); 799 addMemberFromItem(d, "equipped_items", "amulet", myStats->amulet); 800 addMemberFromItem(d, "equipped_items", "mask", myStats->mask); 801 802 rapidjson::Value invItemsArray; 803 invItemsArray.SetArray(); 804 CustomHelpers::addMemberToRoot(d, "inventory_items", invItemsArray); 805 for ( node_t* node = myStats->inventory.first; node; node = node->next ) 806 { 807 Item* item = (Item*)node->element; 808 if ( item ) 809 { 810 addArrayMemberFromItem(d, "inventory_items", item); 811 } 812 } 813 } 814 readAttributesFromStats(Stat * myStats,rapidjson::Document & d)815 void readAttributesFromStats(Stat* myStats, rapidjson::Document& d) 816 { 817 rapidjson::Value statsObject; 818 statsObject.SetObject(); 819 CustomHelpers::addMemberToRoot(d, "stats", statsObject); 820 821 StatEntry statEntry(myStats); 822 CustomHelpers::addMemberToSubkey(d, "stats", "name", rapidjson::Value(statEntry.name, d.GetAllocator())); 823 CustomHelpers::addMemberToSubkey(d, "stats", "type", rapidjson::Value(monstertypename[statEntry.type], d.GetAllocator())); 824 CustomHelpers::addMemberToSubkey(d, "stats", "sex", rapidjson::Value(statEntry.sex)); 825 CustomHelpers::addMemberToSubkey(d, "stats", "appearance", rapidjson::Value(statEntry.appearance)); 826 CustomHelpers::addMemberToSubkey(d, "stats", "HP", rapidjson::Value(statEntry.HP)); 827 CustomHelpers::addMemberToSubkey(d, "stats", "MAXHP", rapidjson::Value(statEntry.MAXHP)); 828 CustomHelpers::addMemberToSubkey(d, "stats", "MP", rapidjson::Value(statEntry.MP)); 829 CustomHelpers::addMemberToSubkey(d, "stats", "MAXMP", rapidjson::Value(statEntry.MAXMP)); 830 CustomHelpers::addMemberToSubkey(d, "stats", "STR", rapidjson::Value(statEntry.STR)); 831 CustomHelpers::addMemberToSubkey(d, "stats", "DEX", rapidjson::Value(statEntry.DEX)); 832 CustomHelpers::addMemberToSubkey(d, "stats", "CON", rapidjson::Value(statEntry.CON)); 833 CustomHelpers::addMemberToSubkey(d, "stats", "INT", rapidjson::Value(statEntry.INT)); 834 CustomHelpers::addMemberToSubkey(d, "stats", "PER", rapidjson::Value(statEntry.PER)); 835 CustomHelpers::addMemberToSubkey(d, "stats", "CHR", rapidjson::Value(statEntry.CHR)); 836 CustomHelpers::addMemberToSubkey(d, "stats", "EXP", rapidjson::Value(statEntry.EXP)); 837 CustomHelpers::addMemberToSubkey(d, "stats", "LVL", rapidjson::Value(statEntry.LVL)); 838 CustomHelpers::addMemberToSubkey(d, "stats", "GOLD", rapidjson::Value(statEntry.GOLD)); 839 840 rapidjson::Value miscStatsObject; 841 miscStatsObject.SetObject(); 842 CustomHelpers::addMemberToRoot(d, "misc_stats", miscStatsObject); 843 844 CustomHelpers::addMemberToSubkey(d, "misc_stats", "RANDOM_STR", rapidjson::Value(statEntry.RANDOM_STR)); 845 CustomHelpers::addMemberToSubkey(d, "misc_stats", "RANDOM_DEX", rapidjson::Value(statEntry.RANDOM_DEX)); 846 CustomHelpers::addMemberToSubkey(d, "misc_stats", "RANDOM_CON", rapidjson::Value(statEntry.RANDOM_CON)); 847 CustomHelpers::addMemberToSubkey(d, "misc_stats", "RANDOM_INT", rapidjson::Value(statEntry.RANDOM_INT)); 848 CustomHelpers::addMemberToSubkey(d, "misc_stats", "RANDOM_PER", rapidjson::Value(statEntry.RANDOM_PER)); 849 CustomHelpers::addMemberToSubkey(d, "misc_stats", "RANDOM_CHR", rapidjson::Value(statEntry.RANDOM_CHR)); 850 CustomHelpers::addMemberToSubkey(d, "misc_stats", "RANDOM_MAXHP", rapidjson::Value(statEntry.RANDOM_MAXHP)); 851 CustomHelpers::addMemberToSubkey(d, "misc_stats", "RANDOM_HP", rapidjson::Value(statEntry.RANDOM_HP)); 852 CustomHelpers::addMemberToSubkey(d, "misc_stats", "RANDOM_MAXMP", rapidjson::Value(statEntry.RANDOM_MAXMP)); 853 CustomHelpers::addMemberToSubkey(d, "misc_stats", "RANDOM_MP", rapidjson::Value(statEntry.RANDOM_MP)); 854 CustomHelpers::addMemberToSubkey(d, "misc_stats", "RANDOM_LVL", rapidjson::Value(statEntry.RANDOM_LVL)); 855 CustomHelpers::addMemberToSubkey(d, "misc_stats", "RANDOM_GOLD", rapidjson::Value(statEntry.RANDOM_GOLD)); 856 857 rapidjson::Value profObject; 858 profObject.SetObject(); 859 CustomHelpers::addMemberToRoot(d, "proficiencies", profObject); 860 861 for ( int i = 0; i < NUMPROFICIENCIES; ++i ) 862 { 863 CustomHelpers::addMemberToSubkey(d, "proficiencies", getSkillLangEntry(i), rapidjson::Value(statEntry.PROFICIENCIES[i])); 864 } 865 } 866 readKeyToStatEntry(StatEntry & statEntry,rapidjson::Value::ConstMemberIterator & itr)867 bool readKeyToStatEntry(StatEntry& statEntry, rapidjson::Value::ConstMemberIterator& itr) 868 { 869 std::string name = itr->name.GetString(); 870 if ( name.compare("name") == 0 ) 871 { 872 strcpy(statEntry.name, itr->value.GetString()); 873 return true; 874 } 875 else if ( name.compare("type") == 0 ) 876 { 877 std::string val = itr->value.GetString(); 878 for ( int i = 0; i < NUMMONSTERS; ++i ) 879 { 880 if ( val.compare(monstertypename[i]) == 0 ) 881 { 882 statEntry.type = i; 883 break; 884 } 885 } 886 return true; 887 } 888 else if ( name.compare("sex") == 0 ) 889 { 890 statEntry.sex = static_cast<sex_t>(itr->value.GetInt()); 891 return true; 892 } 893 else if ( name.compare("appearance") == 0 ) 894 { 895 statEntry.appearance = static_cast<Uint32>(itr->value.GetInt()); 896 return true; 897 } 898 else if ( name.compare("HP") == 0 ) 899 { 900 statEntry.HP = static_cast<Sint32>(itr->value.GetInt()); 901 return true; 902 } 903 else if ( name.compare("MAXHP") == 0 ) 904 { 905 statEntry.MAXHP = static_cast<Sint32>(itr->value.GetInt()); 906 return true; 907 } 908 else if ( name.compare("MP") == 0 ) 909 { 910 statEntry.MP = static_cast<Sint32>(itr->value.GetInt()); 911 return true; 912 } 913 else if ( name.compare("MAXMP") == 0 ) 914 { 915 statEntry.MAXMP = static_cast<Sint32>(itr->value.GetInt()); 916 return true; 917 } 918 else if ( name.compare("STR") == 0 ) 919 { 920 statEntry.STR = static_cast<Sint32>(itr->value.GetInt()); 921 return true; 922 } 923 else if ( name.compare("DEX") == 0 ) 924 { 925 statEntry.DEX = static_cast<Sint32>(itr->value.GetInt()); 926 return true; 927 } 928 else if ( name.compare("CON") == 0 ) 929 { 930 statEntry.CON = static_cast<Sint32>(itr->value.GetInt()); 931 return true; 932 } 933 else if ( name.compare("INT") == 0 ) 934 { 935 statEntry.INT = static_cast<Sint32>(itr->value.GetInt()); 936 return true; 937 } 938 else if ( name.compare("PER") == 0 ) 939 { 940 statEntry.PER = static_cast<Sint32>(itr->value.GetInt()); 941 return true; 942 } 943 else if ( name.compare("CHR") == 0 ) 944 { 945 statEntry.CHR = static_cast<Sint32>(itr->value.GetInt()); 946 return true; 947 } 948 else if ( name.compare("EXP") == 0 ) 949 { 950 statEntry.EXP = static_cast<Sint32>(itr->value.GetInt()); 951 return true; 952 } 953 else if ( name.compare("LVL") == 0 ) 954 { 955 statEntry.LVL = static_cast<Sint32>(itr->value.GetInt()); 956 return true; 957 } 958 else if ( name.compare("GOLD") == 0 ) 959 { 960 statEntry.GOLD = static_cast<Sint32>(itr->value.GetInt()); 961 return true; 962 } 963 else if ( name.compare("RANDOM_STR") == 0 ) 964 { 965 statEntry.RANDOM_STR = static_cast<Sint32>(itr->value.GetInt()); 966 return true; 967 } 968 else if ( name.compare("RANDOM_DEX") == 0 ) 969 { 970 statEntry.RANDOM_DEX = static_cast<Sint32>(itr->value.GetInt()); 971 return true; 972 } 973 else if ( name.compare("RANDOM_CON") == 0 ) 974 { 975 statEntry.RANDOM_CON = static_cast<Sint32>(itr->value.GetInt()); 976 return true; 977 } 978 else if ( name.compare("RANDOM_INT") == 0 ) 979 { 980 statEntry.RANDOM_INT = static_cast<Sint32>(itr->value.GetInt()); 981 return true; 982 } 983 else if ( name.compare("RANDOM_PER") == 0 ) 984 { 985 statEntry.RANDOM_PER = static_cast<Sint32>(itr->value.GetInt()); 986 return true; 987 } 988 else if ( name.compare("RANDOM_CHR") == 0 ) 989 { 990 statEntry.RANDOM_CHR = static_cast<Sint32>(itr->value.GetInt()); 991 return true; 992 } 993 else if ( name.compare("RANDOM_MAXHP") == 0 ) 994 { 995 statEntry.RANDOM_MAXHP = static_cast<Sint32>(itr->value.GetInt()); 996 return true; 997 } 998 else if ( name.compare("RANDOM_HP") == 0 ) 999 { 1000 statEntry.RANDOM_HP = static_cast<Sint32>(itr->value.GetInt()); 1001 return true; 1002 } 1003 else if ( name.compare("RANDOM_MAXMP") == 0 ) 1004 { 1005 statEntry.RANDOM_MAXMP = static_cast<Sint32>(itr->value.GetInt()); 1006 return true; 1007 } 1008 else if ( name.compare("RANDOM_MP") == 0 ) 1009 { 1010 statEntry.RANDOM_MP = static_cast<Sint32>(itr->value.GetInt()); 1011 return true; 1012 } 1013 else if ( name.compare("RANDOM_LVL") == 0 ) 1014 { 1015 statEntry.RANDOM_LVL = static_cast<Sint32>(itr->value.GetInt()); 1016 return true; 1017 } 1018 else if ( name.compare("RANDOM_GOLD") == 0 ) 1019 { 1020 statEntry.RANDOM_GOLD = static_cast<Sint32>(itr->value.GetInt()); 1021 return true; 1022 } 1023 else 1024 { 1025 for ( int i = 0; i < NUMPROFICIENCIES; ++i ) 1026 { 1027 if ( name.compare(getSkillLangEntry(i)) == 0 ) 1028 { 1029 statEntry.PROFICIENCIES[i] = static_cast<Sint32>(itr->value.GetInt()); 1030 return true; 1031 } 1032 } 1033 } 1034 return false; 1035 } 1036 addArrayMemberFromItem(rapidjson::Document & d,std::string rootKey,Item * item)1037 void addArrayMemberFromItem(rapidjson::Document& d, std::string rootKey, Item* item) 1038 { 1039 if ( item ) 1040 { 1041 rapidjson::Value itemObject(rapidjson::kObjectType); 1042 ItemEntry itemEntry(*item); 1043 itemEntry.setValueFromAttributes(d, itemObject); 1044 CustomHelpers::addArrayMemberToSubkey(d, rootKey, itemObject); 1045 } 1046 } addMemberFromItem(rapidjson::Document & d,std::string rootKey,std::string key,Item * item)1047 void addMemberFromItem(rapidjson::Document& d, std::string rootKey, std::string key, Item* item) 1048 { 1049 if ( item ) 1050 { 1051 rapidjson::Value itemObject(rapidjson::kObjectType); 1052 ItemEntry itemEntry(*item); 1053 itemEntry.setValueFromAttributes(d, itemObject); 1054 CustomHelpers::addMemberToSubkey(d, rootKey, key.c_str(), itemObject); 1055 } 1056 } 1057 writeToFile(rapidjson::Document & d,std::string monsterFileName)1058 void writeToFile(rapidjson::Document& d, std::string monsterFileName) 1059 { 1060 int filenum = 0; 1061 std::string testPath = "/data/custom-monsters/monster_" + monsterFileName + "_export" + std::to_string(filenum) + ".json"; 1062 while ( PHYSFS_getRealDir(testPath.c_str()) != nullptr && filenum < 1000 ) 1063 { 1064 ++filenum; 1065 testPath = "/data/custom-monsters/monster_" + monsterFileName + "_export" + std::to_string(filenum) + ".json"; 1066 } 1067 std::string outputPath = PHYSFS_getRealDir("/data/custom-monsters/"); 1068 outputPath.append(PHYSFS_getDirSeparator()); 1069 std::string fileName = "data/custom-monsters/monster_" + monsterFileName + "_export" + std::to_string(filenum) + ".json"; 1070 outputPath.append(fileName.c_str()); 1071 1072 1073 FILE* fp = fopen(outputPath.c_str(), "wb"); 1074 if ( !fp ) 1075 { 1076 return; 1077 } 1078 char buf[65536]; 1079 rapidjson::FileWriteStream os(fp, buf, sizeof(buf)); 1080 rapidjson::PrettyWriter<rapidjson::FileWriteStream> writer(os); 1081 d.Accept(writer); 1082 1083 fclose(fp); 1084 } 1085 readFromFile(std::string monsterFileName)1086 StatEntry* readFromFile(std::string monsterFileName) 1087 { 1088 std::string filePath = "/data/custom-monsters/"; 1089 filePath.append(monsterFileName); 1090 if ( filePath.find(".json") == std::string::npos ) 1091 { 1092 filePath.append(".json"); 1093 } 1094 if ( PHYSFS_getRealDir(filePath.c_str()) ) 1095 { 1096 std::string inputPath = PHYSFS_getRealDir(filePath.c_str()); 1097 inputPath.append(filePath); 1098 1099 FILE* fp = fopen(inputPath.c_str(), "rb"); 1100 if ( !fp ) 1101 { 1102 printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); 1103 return nullptr; 1104 } 1105 char buf[65536]; 1106 rapidjson::FileReadStream is(fp, buf, sizeof(buf)); 1107 fclose(fp); 1108 1109 rapidjson::Document d; 1110 d.ParseStream(is); 1111 1112 1113 if ( !d.HasMember("version") ) 1114 { 1115 printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); 1116 return nullptr; 1117 } 1118 StatEntry* statEntry = new StatEntry(); 1119 int version = d["version"].GetInt(); 1120 const rapidjson::Value& stats = d["stats"]; 1121 for ( rapidjson::Value::ConstMemberIterator stat_itr = stats.MemberBegin(); stat_itr != stats.MemberEnd(); ++stat_itr ) 1122 { 1123 readKeyToStatEntry(*statEntry, stat_itr); 1124 } 1125 const rapidjson::Value& miscStats = d["misc_stats"]; 1126 for ( rapidjson::Value::ConstMemberIterator stat_itr = miscStats.MemberBegin(); stat_itr != miscStats.MemberEnd(); ++stat_itr ) 1127 { 1128 readKeyToStatEntry(*statEntry, stat_itr); 1129 } 1130 const rapidjson::Value& proficiencies = d["proficiencies"]; 1131 for ( rapidjson::Value::ConstMemberIterator stat_itr = proficiencies.MemberBegin(); stat_itr != proficiencies.MemberEnd(); ++stat_itr ) 1132 { 1133 readKeyToStatEntry(*statEntry, stat_itr); 1134 } 1135 const rapidjson::Value& equipped_items = d["equipped_items"]; 1136 for ( rapidjson::Value::ConstMemberIterator itemSlot_itr = equipped_items.MemberBegin(); itemSlot_itr != equipped_items.MemberEnd(); ++itemSlot_itr ) 1137 { 1138 std::string slotName = itemSlot_itr->name.GetString(); 1139 if ( itemSlot_itr->value.MemberCount() > 0 ) 1140 { 1141 if ( itemSlot_itr->value.IsArray() ) 1142 { 1143 std::vector<std::pair<ItemEntry, int>> itemsToChoose; 1144 // a selection of items in the slot. need to choose 1. 1145 for ( rapidjson::Value::ConstValueIterator itemArray_itr = itemSlot_itr->value.Begin(); itemArray_itr != itemSlot_itr->value.End(); ++itemArray_itr ) 1146 { 1147 ItemEntry item; 1148 for ( rapidjson::Value::ConstMemberIterator item_itr = itemArray_itr->MemberBegin(); item_itr != itemArray_itr->MemberEnd(); ++item_itr ) 1149 { 1150 item.readKeyToItemEntry(item_itr); 1151 } 1152 itemsToChoose.push_back(std::make_pair(item, getSlotFromKeyName(slotName))); 1153 } 1154 if ( itemsToChoose.size() > 0 ) 1155 { 1156 std::vector<int> itemChances(itemsToChoose.size(), 0); 1157 int index = 0; 1158 for ( auto& pair : itemsToChoose ) 1159 { 1160 itemChances.at(index) = pair.first.weightedChance; 1161 ++index; 1162 } 1163 1164 std::discrete_distribution<> itemWeightedDistribution(itemChances.begin(), itemChances.end()); 1165 int result = itemWeightedDistribution(monsterStatSeed); 1166 statEntry->equipped_items.push_back(std::make_pair(itemsToChoose.at(result).first, itemsToChoose.at(result).second)); 1167 } 1168 } 1169 else 1170 { 1171 ItemEntry item; 1172 for ( rapidjson::Value::ConstMemberIterator item_itr = itemSlot_itr->value.MemberBegin(); item_itr != itemSlot_itr->value.MemberEnd(); ++item_itr ) 1173 { 1174 item.readKeyToItemEntry(item_itr); 1175 } 1176 statEntry->equipped_items.push_back(std::make_pair(item, getSlotFromKeyName(slotName))); 1177 } 1178 } 1179 } 1180 const rapidjson::Value& inventory_items = d["inventory_items"]; 1181 for ( rapidjson::Value::ConstValueIterator itemSlot_itr = inventory_items.Begin(); itemSlot_itr != inventory_items.End(); ++itemSlot_itr ) 1182 { 1183 if ( itemSlot_itr->IsArray() ) 1184 { 1185 std::vector<ItemEntry> itemsToChoose; 1186 // a selection of items in the slot. need to choose 1. 1187 for ( rapidjson::Value::ConstValueIterator itemArray_itr = itemSlot_itr->Begin(); itemArray_itr != itemSlot_itr->End(); ++itemArray_itr ) 1188 { 1189 ItemEntry item; 1190 for ( rapidjson::Value::ConstMemberIterator item_itr = itemArray_itr->MemberBegin(); item_itr != itemArray_itr->MemberEnd(); ++item_itr ) 1191 { 1192 item.readKeyToItemEntry(item_itr); 1193 } 1194 itemsToChoose.push_back(item); 1195 } 1196 if ( itemsToChoose.size() > 0 ) 1197 { 1198 std::vector<int> itemChances(itemsToChoose.size(), 0); 1199 int index = 0; 1200 for ( auto& i : itemsToChoose ) 1201 { 1202 itemChances.at(index) = i.weightedChance; 1203 ++index; 1204 } 1205 1206 std::discrete_distribution<> itemWeightedDistribution(itemChances.begin(), itemChances.end()); 1207 int result = itemWeightedDistribution(monsterStatSeed); 1208 statEntry->inventory_items.push_back(itemsToChoose.at(result)); 1209 } 1210 } 1211 else 1212 { 1213 ItemEntry item; 1214 for ( rapidjson::Value::ConstMemberIterator item_itr = itemSlot_itr->MemberBegin(); item_itr != itemSlot_itr->MemberEnd(); ++item_itr ) 1215 { 1216 item.readKeyToItemEntry(item_itr); 1217 } 1218 statEntry->inventory_items.push_back(item); 1219 } 1220 } 1221 if ( d.HasMember("followers") ) 1222 { 1223 const rapidjson::Value& numFollowersVal = d["followers"]["num_followers"]; 1224 statEntry->numFollowers = numFollowersVal.GetInt(); 1225 const rapidjson::Value& followers = d["followers"]["follower_variants"]; 1226 1227 statEntry->followerVariants.clear(); 1228 for ( rapidjson::Value::ConstMemberIterator follower_itr = followers.MemberBegin(); follower_itr != followers.MemberEnd(); ++follower_itr ) 1229 { 1230 statEntry->followerVariants.push_back(std::make_pair(follower_itr->name.GetString(), follower_itr->value.GetInt())); 1231 } 1232 } 1233 if ( d.HasMember("properties") ) 1234 { 1235 if ( d["properties"].HasMember("monster_name_always_display_as_generic_species") ) 1236 { 1237 statEntry->isMonsterNameGeneric = d["properties"]["monster_name_always_display_as_generic_species"].GetBool(); 1238 } 1239 if ( d["properties"].HasMember("populate_empty_equipped_items_with_default") ) 1240 { 1241 statEntry->useDefaultEquipment = d["properties"]["populate_empty_equipped_items_with_default"].GetBool(); 1242 } 1243 if ( d["properties"].HasMember("populate_default_inventory") ) 1244 { 1245 statEntry->useDefaultInventoryItems = d["properties"]["populate_default_inventory"].GetBool(); 1246 } 1247 if ( d["properties"].HasMember("disable_miniboss_chance") ) 1248 { 1249 statEntry->disableMiniboss = d["properties"]["disable_miniboss_chance"].GetBool(); 1250 } 1251 if ( d["properties"].HasMember("force_player_recruitable") ) 1252 { 1253 statEntry->forceRecruitableToPlayer = d["properties"]["force_player_recruitable"].GetBool(); 1254 } 1255 if ( d["properties"].HasMember("force_player_friendly") ) 1256 { 1257 statEntry->forceFriendlyToPlayer = d["properties"]["force_player_friendly"].GetBool(); 1258 } 1259 if ( d["properties"].HasMember("force_player_enemy") ) 1260 { 1261 statEntry->forceEnemyToPlayer = d["properties"]["force_player_enemy"].GetBool(); 1262 } 1263 if ( d["properties"].HasMember("disable_item_drops") ) 1264 { 1265 statEntry->disableItemDrops = d["properties"]["disable_item_drops"].GetBool(); 1266 } 1267 if ( d["properties"].HasMember("xp_award_percent") ) 1268 { 1269 statEntry->xpAwardPercent = d["properties"]["xp_award_percent"].GetInt(); 1270 } 1271 if ( d["properties"].HasMember("enable_casting_inventory_spellbooks") ) 1272 { 1273 statEntry->castSpellbooksFromInventory = d["properties"]["enable_casting_inventory_spellbooks"].GetBool(); 1274 } 1275 if ( d["properties"].HasMember("spellbook_cast_cooldown") ) 1276 { 1277 statEntry->spellbookCastCooldown = d["properties"]["spellbook_cast_cooldown"].GetInt(); 1278 } 1279 } 1280 if ( d.HasMember("shopkeeper_properties") ) 1281 { 1282 if ( d["shopkeeper_properties"].HasMember("store_type_chances") ) 1283 { 1284 for ( rapidjson::Value::ConstMemberIterator types_itr = d["shopkeeper_properties"]["store_type_chances"].MemberBegin(); 1285 types_itr != d["shopkeeper_properties"]["store_type_chances"].MemberEnd(); ++types_itr ) 1286 { 1287 statEntry->shopkeeperStoreTypes.push_back(std::make_pair(types_itr->name.GetString(), types_itr->value.GetInt())); 1288 } 1289 if ( !statEntry->shopkeeperStoreTypes.empty() ) 1290 { 1291 std::vector<int> storeChances(statEntry->shopkeeperStoreTypes.size(), 0); 1292 int index = 0; 1293 for ( auto& chance : storeChances ) 1294 { 1295 chance = statEntry->shopkeeperStoreTypes.at(index).second; 1296 ++index; 1297 } 1298 1299 std::discrete_distribution<> storeTypeWeightedDistribution(storeChances.begin(), storeChances.end()); 1300 std::string result = statEntry->shopkeeperStoreTypes.at(storeTypeWeightedDistribution(monsterStatSeed)).first; 1301 index = 0; 1302 for ( auto& lookup : shopkeeperTypeStrings ) 1303 { 1304 if ( lookup.compare(result) == 0 ) 1305 { 1306 statEntry->chosenShopkeeperStore = index; 1307 break; 1308 } 1309 ++index; 1310 } 1311 } 1312 if ( d["shopkeeper_properties"].HasMember("generate_default_shop_items") ) 1313 { 1314 statEntry->shopkeeperGenDefaultItems = d["shopkeeper_properties"]["generate_default_shop_items"].GetBool(); 1315 } 1316 if ( d["shopkeeper_properties"].HasMember("num_generated_items_min") ) 1317 { 1318 statEntry->shopkeeperMinItems = d["shopkeeper_properties"]["num_generated_items_min"].GetInt(); 1319 } 1320 if ( d["shopkeeper_properties"].HasMember("num_generated_items_max") ) 1321 { 1322 statEntry->shopkeeperMaxItems = d["shopkeeper_properties"]["num_generated_items_max"].GetInt(); 1323 } 1324 if ( d["shopkeeper_properties"].HasMember("generated_item_blessing_max") ) 1325 { 1326 statEntry->shopkeeperMaxGeneratedBlessing = d["shopkeeper_properties"]["generated_item_blessing_max"].GetInt(); 1327 } 1328 } 1329 } 1330 printlog("[JSON]: Successfully read json file %s", inputPath.c_str()); 1331 return statEntry; 1332 } 1333 else 1334 { 1335 printlog("[JSON]: Error: Could not locate json file %s", filePath.c_str()); 1336 } 1337 return nullptr; 1338 } 1339 }; 1340 extern MonsterStatCustomManager monsterStatCustomManager; 1341 1342 class MonsterCurveCustomManager 1343 { 1344 bool usingCustomManager = false; 1345 public: 1346 std::mt19937 curveSeed; MonsterCurveCustomManager()1347 MonsterCurveCustomManager() : 1348 curveSeed(rand()) 1349 {}; 1350 1351 class MonsterCurveEntry 1352 { 1353 public: 1354 int monsterType = NOTHING; 1355 int levelmin = 0; 1356 int levelmax = 99; 1357 int chance = 1; 1358 int fallbackMonsterType = NOTHING; 1359 std::vector<std::pair<std::string, int>> variants; MonsterCurveEntry(std::string monsterStr,int levelNumMin,int levelNumMax,int chanceNum,std::string fallbackMonsterStr)1360 MonsterCurveEntry(std::string monsterStr, int levelNumMin, int levelNumMax, int chanceNum, std::string fallbackMonsterStr) 1361 { 1362 monsterType = getMonsterTypeFromString(monsterStr); 1363 fallbackMonsterType = getMonsterTypeFromString(fallbackMonsterStr); 1364 levelmin = levelNumMin; 1365 levelmax = levelNumMax; 1366 chance = chanceNum; 1367 }; addVariant(std::string variantName,int chance)1368 void addVariant(std::string variantName, int chance) 1369 { 1370 variants.push_back(std::make_pair(variantName, chance)); 1371 } 1372 }; 1373 1374 class LevelCurve 1375 { 1376 public: 1377 std::string mapName = ""; 1378 std::vector<MonsterCurveEntry> monsterCurve; 1379 std::vector<MonsterCurveEntry> fixedSpawns; 1380 }; 1381 1382 std::vector<LevelCurve> allLevelCurves; inUse()1383 inline bool inUse() { return usingCustomManager; }; 1384 readFromFile()1385 void readFromFile() 1386 { 1387 allLevelCurves.clear(); 1388 usingCustomManager = false; 1389 if ( PHYSFS_getRealDir("/data/monstercurve.json") ) 1390 { 1391 std::string inputPath = PHYSFS_getRealDir("/data/monstercurve.json"); 1392 inputPath.append("/data/monstercurve.json"); 1393 1394 FILE* fp = fopen(inputPath.c_str(), "rb"); 1395 if ( !fp ) 1396 { 1397 printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); 1398 return; 1399 } 1400 char buf[65536]; 1401 rapidjson::FileReadStream is(fp, buf, sizeof(buf)); 1402 fclose(fp); 1403 1404 rapidjson::Document d; 1405 d.ParseStream(is); 1406 if ( !d.HasMember("version") ) 1407 { 1408 printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); 1409 return; 1410 } 1411 int version = d["version"].GetInt(); 1412 1413 if ( d.HasMember("levels") ) 1414 { 1415 usingCustomManager = true; 1416 const rapidjson::Value& levels = d["levels"]; 1417 for ( rapidjson::Value::ConstMemberIterator map_itr = levels.MemberBegin(); map_itr != levels.MemberEnd(); ++map_itr ) 1418 { 1419 LevelCurve newCurve; 1420 newCurve.mapName = map_itr->name.GetString(); 1421 const rapidjson::Value& randomGeneration = map_itr->value["random_generation_monsters"]; 1422 for ( rapidjson::Value::ConstValueIterator monsters_itr = randomGeneration.Begin(); monsters_itr != randomGeneration.End(); ++monsters_itr ) 1423 { 1424 const rapidjson::Value& monster = *monsters_itr; 1425 MonsterCurveEntry newMonster(monster["name"].GetString(), 1426 monster["dungeon_depth_minimum"].GetInt(), 1427 monster["dungeon_depth_maximum"].GetInt(), 1428 monster["weighted_chance"].GetInt(), 1429 ""); 1430 1431 if ( monster.HasMember("variants") ) 1432 { 1433 for ( rapidjson::Value::ConstMemberIterator var_itr = monster["variants"].MemberBegin(); 1434 var_itr != monster["variants"].MemberEnd(); ++var_itr ) 1435 { 1436 newMonster.addVariant(var_itr->name.GetString(), var_itr->value.GetInt()); 1437 } 1438 } 1439 newCurve.monsterCurve.push_back(newMonster); 1440 } 1441 1442 if ( map_itr->value.HasMember("fixed_monsters") ) 1443 { 1444 const rapidjson::Value& fixedGeneration = map_itr->value["fixed_monsters"]; 1445 for ( rapidjson::Value::ConstValueIterator monsters_itr = fixedGeneration.Begin(); monsters_itr != fixedGeneration.End(); ++monsters_itr ) 1446 { 1447 const rapidjson::Value& monster = *monsters_itr; 1448 MonsterCurveEntry newMonster(monster["name"].GetString(), 0, 255, 1, ""); 1449 1450 if ( monster.HasMember("variants") ) 1451 { 1452 for ( rapidjson::Value::ConstMemberIterator var_itr = monster["variants"].MemberBegin(); 1453 var_itr != monster["variants"].MemberEnd(); ++var_itr ) 1454 { 1455 newMonster.addVariant(var_itr->name.GetString(), var_itr->value.GetInt()); 1456 } 1457 } 1458 newCurve.fixedSpawns.push_back(newMonster); 1459 } 1460 } 1461 allLevelCurves.push_back(newCurve); 1462 } 1463 } 1464 printCurve(allLevelCurves); 1465 printlog("[JSON]: Successfully read json file %s", inputPath.c_str()); 1466 } 1467 } 1468 getMonsterTypeFromString(std::string monsterStr)1469 static int getMonsterTypeFromString(std::string monsterStr) 1470 { 1471 if ( monsterStr.compare("") == 0 ) 1472 { 1473 return NOTHING; 1474 } 1475 for ( int i = NOTHING; i < NUMMONSTERS; ++i ) 1476 { 1477 if ( monsterStr.compare(monstertypename[i]) == 0 ) 1478 { 1479 return i; 1480 } 1481 } 1482 return NOTHING; 1483 } printCurve(std::vector<LevelCurve> toPrint)1484 void printCurve(std::vector<LevelCurve> toPrint) 1485 { 1486 return; 1487 for ( LevelCurve curve : toPrint ) 1488 { 1489 printlog("Map Name: %s", curve.mapName.c_str()); 1490 for ( MonsterCurveEntry monsters : curve.monsterCurve ) 1491 { 1492 printlog("[MonsterCurveCustomManager]: Monster: %s | lvl: %d-%d | chance: %d | fallback type: %s", monstertypename[monsters.monsterType], 1493 monsters.levelmin, monsters.levelmax, monsters.chance, monstertypename[monsters.fallbackMonsterType]); 1494 } 1495 } 1496 } curveExistsForCurrentMapName(std::string currentMap)1497 bool curveExistsForCurrentMapName(std::string currentMap) 1498 { 1499 if ( !inUse() ) 1500 { 1501 return false; 1502 } 1503 if ( currentMap.compare("") == 0 ) 1504 { 1505 return false; 1506 } 1507 for ( LevelCurve curve : allLevelCurves ) 1508 { 1509 if ( curve.mapName.compare(currentMap) == 0 ) 1510 { 1511 //printlog("[MonsterCurveCustomManager]: curveExistsForCurrentMapName: true"); 1512 return true; 1513 } 1514 } 1515 return false; 1516 } rollMonsterFromCurve(std::string currentMap)1517 int rollMonsterFromCurve(std::string currentMap) 1518 { 1519 std::vector<int> monsterCurveChances(NUMMONSTERS, 0); 1520 1521 for ( LevelCurve curve : allLevelCurves ) 1522 { 1523 if ( curve.mapName.compare(currentMap) == 0 ) 1524 { 1525 for ( MonsterCurveEntry& monster : curve.monsterCurve ) 1526 { 1527 if ( currentlevel >= monster.levelmin && currentlevel <= monster.levelmax ) 1528 { 1529 if ( monster.monsterType != NOTHING ) 1530 { 1531 monsterCurveChances[monster.monsterType] += monster.chance; 1532 } 1533 } 1534 else 1535 { 1536 if ( monster.fallbackMonsterType != NOTHING ) 1537 { 1538 monsterCurveChances[monster.fallbackMonsterType] += monster.chance; 1539 } 1540 } 1541 } 1542 std::discrete_distribution<> monsterWeightedDistribution(monsterCurveChances.begin(), monsterCurveChances.end()); 1543 int result = monsterWeightedDistribution(curveSeed); 1544 //printlog("[MonsterCurveCustomManager]: Rolled: %d", result); 1545 return result; 1546 } 1547 } 1548 printlog("[MonsterCurveCustomManager]: Error: default to nothing."); 1549 return NOTHING; 1550 } rollMonsterVariant(std::string currentMap,int monsterType)1551 std::string rollMonsterVariant(std::string currentMap, int monsterType) 1552 { 1553 for ( LevelCurve& curve : allLevelCurves ) 1554 { 1555 if ( curve.mapName.compare(currentMap) == 0 ) 1556 { 1557 std::vector<std::string> variantResults; 1558 std::vector<int> variantChances; 1559 for ( MonsterCurveEntry& monster : curve.monsterCurve ) 1560 { 1561 if ( currentlevel >= monster.levelmin && currentlevel <= monster.levelmax ) 1562 { 1563 if ( monster.monsterType == monsterType && monster.variants.size() > 0 ) 1564 { 1565 for ( auto& pair : monster.variants ) 1566 { 1567 auto find = std::find(variantResults.begin(), variantResults.end(), pair.first); 1568 if ( find == variantResults.end() ) 1569 { 1570 variantResults.push_back(pair.first); 1571 variantChances.push_back(pair.second); 1572 } 1573 else 1574 { 1575 size_t dist = static_cast<size_t>(std::distance(variantResults.begin(), find)); 1576 variantChances.at(dist) += pair.second; 1577 } 1578 } 1579 1580 } 1581 } 1582 } 1583 if ( !variantResults.empty() ) 1584 { 1585 std::discrete_distribution<> variantWeightedDistribution(variantChances.begin(), variantChances.end()); 1586 int result = variantWeightedDistribution(curveSeed); 1587 return variantResults[result]; 1588 } 1589 } 1590 } 1591 return "default"; 1592 } rollFixedMonsterVariant(std::string currentMap,int monsterType)1593 std::string rollFixedMonsterVariant(std::string currentMap, int monsterType) 1594 { 1595 for ( LevelCurve& curve : allLevelCurves ) 1596 { 1597 if ( curve.mapName.compare(currentMap) == 0 ) 1598 { 1599 for ( MonsterCurveEntry& monster : curve.fixedSpawns ) 1600 { 1601 if ( monster.monsterType == monsterType && monster.variants.size() > 0 ) 1602 { 1603 std::vector<int> variantChances(monster.variants.size(), 0); 1604 int index = 0; 1605 for ( auto& pair : monster.variants ) 1606 { 1607 variantChances.at(index) = pair.second; 1608 ++index; 1609 } 1610 1611 std::discrete_distribution<> variantWeightedDistribution(variantChances.begin(), variantChances.end()); 1612 int result = variantWeightedDistribution(curveSeed); 1613 return monster.variants.at(result).first; 1614 } 1615 } 1616 } 1617 } 1618 return "default"; 1619 } 1620 createMonsterFromFile(Entity * entity,Stat * myStats,const std::string & filename,Monster & outMonsterType)1621 void createMonsterFromFile(Entity* entity, Stat* myStats, const std::string& filename, Monster& outMonsterType) 1622 { 1623 MonsterStatCustomManager::StatEntry* statEntry = monsterStatCustomManager.readFromFile(filename.c_str()); 1624 if ( statEntry ) 1625 { 1626 statEntry->setStatsAndEquipmentToMonster(myStats); 1627 outMonsterType = myStats->type; 1628 while ( statEntry->numFollowers > 0 ) 1629 { 1630 std::string followerName = statEntry->getFollowerVariant(); 1631 if ( followerName.compare("") && followerName.compare("none") ) 1632 { 1633 MonsterStatCustomManager::StatEntry* followerEntry = monsterStatCustomManager.readFromFile(followerName.c_str()); 1634 if ( followerEntry ) 1635 { 1636 Entity* summonedFollower = summonMonster(static_cast<Monster>(followerEntry->type), entity->x, entity->y); 1637 if ( summonedFollower ) 1638 { 1639 if ( summonedFollower->getStats() ) 1640 { 1641 followerEntry->setStatsAndEquipmentToMonster(summonedFollower->getStats()); 1642 summonedFollower->getStats()->leader_uid = entity->getUID(); 1643 } 1644 } 1645 delete followerEntry; 1646 } 1647 else 1648 { 1649 Entity* summonedFollower = summonMonster(myStats->type, entity->x, entity->y); 1650 if ( summonedFollower ) 1651 { 1652 if ( summonedFollower->getStats() ) 1653 { 1654 summonedFollower->getStats()->leader_uid = entity->getUID(); 1655 } 1656 } 1657 } 1658 } 1659 --statEntry->numFollowers; 1660 } 1661 delete statEntry; 1662 } 1663 } 1664 writeSampleToDocument()1665 void writeSampleToDocument() 1666 { 1667 rapidjson::Document d; 1668 d.SetObject(); 1669 1670 CustomHelpers::addMemberToRoot(d, "version", rapidjson::Value(1)); 1671 rapidjson::Value levelObj(rapidjson::kObjectType); 1672 levelObj.AddMember("The Mines", rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1673 levelObj["The Mines"].AddMember("fixed_monsters", rapidjson::Value(rapidjson::kArrayType), d.GetAllocator()); 1674 1675 auto& fm = levelObj["The Mines"]["fixed_monsters"]; 1676 fm.PushBack(rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1677 fm[rapidjson::SizeType(0)].AddMember("name", "rat", d.GetAllocator()); 1678 fm[rapidjson::SizeType(0)].AddMember("variants", rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1679 fm[rapidjson::SizeType(0)]["variants"].AddMember("default", rapidjson::Value(1), d.GetAllocator()); 1680 1681 fm.PushBack(rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1682 fm[rapidjson::SizeType(1)].AddMember("name", "skeleton", d.GetAllocator()); 1683 fm[rapidjson::SizeType(1)].AddMember("variants", rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1684 fm[rapidjson::SizeType(1)]["variants"].AddMember("default", rapidjson::Value(1), d.GetAllocator()); 1685 1686 fm.PushBack(rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1687 fm[rapidjson::SizeType(2)].AddMember("name", "spider", d.GetAllocator()); 1688 fm[rapidjson::SizeType(2)].AddMember("variants", rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1689 fm[rapidjson::SizeType(2)]["variants"].AddMember("default", rapidjson::Value(1), d.GetAllocator()); 1690 1691 fm.PushBack(rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1692 fm[rapidjson::SizeType(3)].AddMember("name", "troll", d.GetAllocator()); 1693 fm[rapidjson::SizeType(3)].AddMember("variants", rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1694 fm[rapidjson::SizeType(3)]["variants"].AddMember("default", rapidjson::Value(1), d.GetAllocator()); 1695 1696 levelObj["The Mines"].AddMember("random_generation_monsters", rapidjson::Value(rapidjson::kArrayType), d.GetAllocator()); 1697 1698 auto& mines = levelObj["The Mines"]["random_generation_monsters"]; 1699 mines.PushBack(rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1700 mines[rapidjson::SizeType(0)].AddMember("name", "rat", d.GetAllocator()); 1701 mines[rapidjson::SizeType(0)].AddMember("weighted_chance", rapidjson::Value(4), d.GetAllocator()); 1702 mines[rapidjson::SizeType(0)].AddMember("dungeon_depth_minimum", rapidjson::Value(0), d.GetAllocator()); 1703 mines[rapidjson::SizeType(0)].AddMember("dungeon_depth_maximum", rapidjson::Value(99), d.GetAllocator()); 1704 mines[rapidjson::SizeType(0)].AddMember("variants", rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1705 mines[rapidjson::SizeType(0)]["variants"].AddMember("default", rapidjson::Value(1), d.GetAllocator()); 1706 1707 mines.PushBack(rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1708 mines[rapidjson::SizeType(1)].AddMember("name", "skeleton", d.GetAllocator()); 1709 mines[rapidjson::SizeType(1)].AddMember("weighted_chance", rapidjson::Value(4), d.GetAllocator()); 1710 mines[rapidjson::SizeType(1)].AddMember("dungeon_depth_minimum", rapidjson::Value(0), d.GetAllocator()); 1711 mines[rapidjson::SizeType(1)].AddMember("dungeon_depth_maximum", rapidjson::Value(99), d.GetAllocator()); 1712 mines[rapidjson::SizeType(1)].AddMember("variants", rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1713 mines[rapidjson::SizeType(1)]["variants"].AddMember("default", rapidjson::Value(1), d.GetAllocator()); 1714 1715 mines.PushBack(rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1716 mines[rapidjson::SizeType(2)].AddMember("name", "spider", d.GetAllocator()); 1717 mines[rapidjson::SizeType(2)].AddMember("weighted_chance", rapidjson::Value(1), d.GetAllocator()); 1718 mines[rapidjson::SizeType(2)].AddMember("dungeon_depth_minimum", rapidjson::Value(2), d.GetAllocator()); 1719 mines[rapidjson::SizeType(2)].AddMember("dungeon_depth_maximum", rapidjson::Value(99), d.GetAllocator()); 1720 mines[rapidjson::SizeType(2)].AddMember("variants", rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1721 mines[rapidjson::SizeType(2)]["variants"].AddMember("default", rapidjson::Value(1), d.GetAllocator()); 1722 1723 mines.PushBack(rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1724 mines[rapidjson::SizeType(3)].AddMember("name", "troll", d.GetAllocator()); 1725 mines[rapidjson::SizeType(3)].AddMember("weighted_chance", rapidjson::Value(1), d.GetAllocator()); 1726 mines[rapidjson::SizeType(3)].AddMember("dungeon_depth_minimum", rapidjson::Value(2), d.GetAllocator()); 1727 mines[rapidjson::SizeType(3)].AddMember("dungeon_depth_maximum", rapidjson::Value(99), d.GetAllocator()); 1728 mines[rapidjson::SizeType(3)].AddMember("variants", rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1729 mines[rapidjson::SizeType(3)]["variants"].AddMember("default", rapidjson::Value(1), d.GetAllocator()); 1730 1731 levelObj.AddMember("The Swamp", rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1732 levelObj["The Swamp"].AddMember("random_generation_monsters", rapidjson::Value(rapidjson::kArrayType), d.GetAllocator()); 1733 levelObj["The Swamp"]["random_generation_monsters"].PushBack(rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1734 1735 auto& swamp = levelObj["The Swamp"]["random_generation_monsters"]; 1736 swamp[rapidjson::SizeType(0)].AddMember("name", "spider", d.GetAllocator()); 1737 swamp[rapidjson::SizeType(0)].AddMember("weighted_chance", rapidjson::Value(2), d.GetAllocator()); 1738 swamp[rapidjson::SizeType(0)].AddMember("dungeon_depth_minimum", rapidjson::Value(0), d.GetAllocator()); 1739 swamp[rapidjson::SizeType(0)].AddMember("dungeon_depth_maximum", rapidjson::Value(99), d.GetAllocator()); 1740 swamp[rapidjson::SizeType(0)].AddMember("variants", rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1741 swamp[rapidjson::SizeType(0)]["variants"].AddMember("default", rapidjson::Value(1), d.GetAllocator()); 1742 1743 swamp.PushBack(rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1744 swamp[rapidjson::SizeType(1)].AddMember("name", "goblin", d.GetAllocator()); 1745 swamp[rapidjson::SizeType(1)].AddMember("weighted_chance", rapidjson::Value(3), d.GetAllocator()); 1746 swamp[rapidjson::SizeType(1)].AddMember("dungeon_depth_minimum", rapidjson::Value(0), d.GetAllocator()); 1747 swamp[rapidjson::SizeType(1)].AddMember("dungeon_depth_maximum", rapidjson::Value(99), d.GetAllocator()); 1748 swamp[rapidjson::SizeType(1)].AddMember("variants", rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1749 swamp[rapidjson::SizeType(1)]["variants"].AddMember("default", rapidjson::Value(1), d.GetAllocator()); 1750 1751 swamp.PushBack(rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1752 swamp[rapidjson::SizeType(2)].AddMember("name", "slime", d.GetAllocator()); 1753 swamp[rapidjson::SizeType(2)].AddMember("weighted_chance", rapidjson::Value(3), d.GetAllocator()); 1754 swamp[rapidjson::SizeType(2)].AddMember("dungeon_depth_minimum", rapidjson::Value(0), d.GetAllocator()); 1755 swamp[rapidjson::SizeType(2)].AddMember("dungeon_depth_maximum", rapidjson::Value(99), d.GetAllocator()); 1756 swamp[rapidjson::SizeType(2)].AddMember("variants", rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1757 swamp[rapidjson::SizeType(2)]["variants"].AddMember("default", rapidjson::Value(1), d.GetAllocator()); 1758 1759 swamp.PushBack(rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1760 swamp[rapidjson::SizeType(3)].AddMember("name", "ghoul", d.GetAllocator()); 1761 swamp[rapidjson::SizeType(3)].AddMember("weighted_chance", rapidjson::Value(2), d.GetAllocator()); 1762 swamp[rapidjson::SizeType(3)].AddMember("dungeon_depth_minimum", rapidjson::Value(0), d.GetAllocator()); 1763 swamp[rapidjson::SizeType(3)].AddMember("dungeon_depth_maximum", rapidjson::Value(99), d.GetAllocator()); 1764 swamp[rapidjson::SizeType(3)].AddMember("variants", rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1765 swamp[rapidjson::SizeType(3)]["variants"].AddMember("default", rapidjson::Value(1), d.GetAllocator()); 1766 1767 levelObj.AddMember("My level", rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1768 1769 levelObj["My level"].AddMember("random_generation_monsters", rapidjson::Value(rapidjson::kArrayType), d.GetAllocator()); 1770 levelObj["My level"]["random_generation_monsters"].PushBack(rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1771 auto& customLevel = levelObj["My level"]["random_generation_monsters"]; 1772 customLevel[rapidjson::SizeType(0)].AddMember("name", "demon", d.GetAllocator()); 1773 customLevel[rapidjson::SizeType(0)].AddMember("weighted_chance", rapidjson::Value(1), d.GetAllocator()); 1774 customLevel[rapidjson::SizeType(0)].AddMember("dungeon_depth_minimum", rapidjson::Value(0), d.GetAllocator()); 1775 customLevel[rapidjson::SizeType(0)].AddMember("dungeon_depth_maximum", rapidjson::Value(99), d.GetAllocator()); 1776 customLevel[rapidjson::SizeType(0)].AddMember("variants", rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); 1777 customLevel[rapidjson::SizeType(0)]["variants"].AddMember("default", rapidjson::Value(1), d.GetAllocator()); 1778 1779 CustomHelpers::addMemberToRoot(d, "levels", levelObj); 1780 1781 writeToFile(d); 1782 } 1783 writeToFile(rapidjson::Document & d)1784 void writeToFile(rapidjson::Document& d) 1785 { 1786 int filenum = 0; 1787 std::string testPath = "/data/monstercurve_export" + std::to_string(filenum) + ".json"; 1788 while ( PHYSFS_getRealDir(testPath.c_str()) != nullptr && filenum < 1000 ) 1789 { 1790 ++filenum; 1791 testPath = "/data/monstercurve_export" + std::to_string(filenum) + ".json"; 1792 } 1793 std::string outputPath = PHYSFS_getRealDir("/data/"); 1794 outputPath.append(PHYSFS_getDirSeparator()); 1795 std::string fileName = "data/monstercurve_export" + std::to_string(filenum) + ".json"; 1796 outputPath.append(fileName.c_str()); 1797 1798 FILE* fp = fopen(outputPath.c_str(), "wb"); 1799 if ( !fp ) 1800 { 1801 return; 1802 } 1803 char buf[65536]; 1804 rapidjson::FileWriteStream os(fp, buf, sizeof(buf)); 1805 rapidjson::PrettyWriter<rapidjson::FileWriteStream> writer(os); 1806 d.Accept(writer); 1807 1808 fclose(fp); 1809 } 1810 }; 1811 extern MonsterCurveCustomManager monsterCurveCustomManager; 1812 1813 class GameplayCustomManager 1814 { 1815 public: 1816 bool usingCustomManager = false; 1817 int xpShareRange = XPSHARERANGE; 1818 std::pair<std::unordered_set<int>, std::unordered_set<int>> minotaurForceEnableFloors; 1819 std::pair<std::unordered_set<int>, std::unordered_set<int>> minotaurForceDisableFloors; 1820 std::pair<std::unordered_set<int>, std::unordered_set<int>> hungerDisableFloors; 1821 std::pair<std::unordered_set<int>, std::unordered_set<int>> herxChatterDisableFloors; 1822 std::pair<std::unordered_set<int>, std::unordered_set<int>> minimapDisableFloors; 1823 int globalXPPercent = 100; 1824 int globalGoldPercent = 100; 1825 bool minimapShareProgress = false; 1826 int playerWeightPercent = 100; 1827 double playerSpeedMax = 18.0; inUse()1828 inline bool inUse() { return usingCustomManager; }; resetValues()1829 void resetValues() 1830 { 1831 usingCustomManager = false; 1832 xpShareRange = XPSHARERANGE; 1833 globalXPPercent = 100; 1834 globalGoldPercent = 100; 1835 minimapShareProgress = false; 1836 playerWeightPercent = 100; 1837 playerSpeedMax = 18.0; 1838 1839 minotaurForceEnableFloors.first.clear(); 1840 minotaurForceEnableFloors.second.clear(); 1841 minotaurForceDisableFloors.first.clear(); 1842 minotaurForceDisableFloors.second.clear(); 1843 hungerDisableFloors.first.clear(); 1844 hungerDisableFloors.second.clear(); 1845 herxChatterDisableFloors.first.clear(); 1846 herxChatterDisableFloors.second.clear(); 1847 minimapDisableFloors.first.clear(); 1848 minimapDisableFloors.second.clear(); 1849 allMapGenerations.clear(); 1850 } 1851 1852 class MapGeneration 1853 { 1854 public: MapGeneration(std::string name)1855 MapGeneration(std::string name) { mapName = name; }; 1856 std::string mapName = ""; 1857 std::vector<std::string> trapTypes; 1858 std::unordered_set<int> minoFloors; 1859 std::unordered_set<int> darkFloors; 1860 std::unordered_set<int> shopFloors; 1861 std::unordered_set<int> npcSpawnFloors; 1862 bool usingTrapTypes = false; 1863 int minoPercent = -1; 1864 int shopPercent = -1; 1865 int darkPercent = -1; 1866 int npcSpawnPercent = -1; 1867 }; 1868 1869 std::vector<MapGeneration> allMapGenerations; mapGenerationExistsForMapName(std::string name)1870 bool mapGenerationExistsForMapName(std::string name) 1871 { 1872 for ( auto& it : allMapGenerations ) 1873 { 1874 if ( it.mapName.compare(name) == 0 ) 1875 { 1876 return true; 1877 } 1878 } 1879 return false; 1880 } getMapGenerationForMapName(std::string name)1881 MapGeneration* getMapGenerationForMapName(std::string name) 1882 { 1883 for ( auto& it : allMapGenerations ) 1884 { 1885 if ( it.mapName.compare(name) == 0 ) 1886 { 1887 return ⁢ 1888 } 1889 } 1890 return nullptr; 1891 } 1892 writeAllToDocument()1893 void writeAllToDocument() 1894 { 1895 rapidjson::Document d; 1896 d.SetObject(); 1897 1898 CustomHelpers::addMemberToRoot(d, "version", rapidjson::Value(1)); 1899 CustomHelpers::addMemberToRoot(d, "xp_share_range", rapidjson::Value(xpShareRange)); 1900 CustomHelpers::addMemberToRoot(d, "global_xp_award_percent", rapidjson::Value(globalXPPercent)); 1901 CustomHelpers::addMemberToRoot(d, "global_gold_drop_scale_percent", rapidjson::Value(globalGoldPercent)); 1902 CustomHelpers::addMemberToRoot(d, "player_share_minimap_progress", rapidjson::Value(minimapShareProgress)); 1903 CustomHelpers::addMemberToRoot(d, "player_speed_weight_impact_percent", rapidjson::Value(playerWeightPercent)); 1904 CustomHelpers::addMemberToRoot(d, "player_speed_max", rapidjson::Value(playerSpeedMax)); 1905 1906 rapidjson::Value obj(rapidjson::kObjectType); 1907 rapidjson::Value arr(rapidjson::kArrayType); 1908 CustomHelpers::addMemberToRoot(d, "minotaur_force_disable_on_floors", obj); 1909 CustomHelpers::addMemberToSubkey(d, "minotaur_force_disable_on_floors", "normal_floors", arr); 1910 CustomHelpers::addMemberToSubkey(d, "minotaur_force_disable_on_floors", "secret_floors", arr); 1911 CustomHelpers::addMemberToRoot(d, "minotaur_force_enable_on_floors", obj); 1912 CustomHelpers::addMemberToSubkey(d, "minotaur_force_enable_on_floors", "normal_floors", arr); 1913 CustomHelpers::addMemberToSubkey(d, "minotaur_force_enable_on_floors", "secret_floors", arr); 1914 CustomHelpers::addMemberToRoot(d, "disable_herx_messages_on_floors", obj); 1915 CustomHelpers::addMemberToSubkey(d, "disable_herx_messages_on_floors", "normal_floors", arr); 1916 CustomHelpers::addMemberToSubkey(d, "disable_herx_messages_on_floors", "secret_floors", arr); 1917 CustomHelpers::addMemberToRoot(d, "disable_minimap_on_floors", obj); 1918 CustomHelpers::addMemberToSubkey(d, "disable_minimap_on_floors", "normal_floors", arr); 1919 CustomHelpers::addMemberToSubkey(d, "disable_minimap_on_floors", "secret_floors", arr); 1920 1921 rapidjson::Value mapGenObj; 1922 mapGenObj.SetObject(); 1923 CustomHelpers::addMemberToRoot(d, "map_generation", mapGenObj); 1924 rapidjson::Value key1("The Mines", d.GetAllocator()); 1925 rapidjson::Value minesObj(rapidjson::kObjectType); 1926 1927 rapidjson::Value trapArray1(rapidjson::kArrayType); 1928 trapArray1.PushBack("boulders", d.GetAllocator()); 1929 minesObj.AddMember("trap_generation_types", trapArray1, d.GetAllocator()); 1930 minesObj.AddMember("minotaur_floors", rapidjson::Value(rapidjson::kArrayType), d.GetAllocator()); 1931 minesObj["minotaur_floors"].PushBack(2, d.GetAllocator()); 1932 minesObj["minotaur_floors"].PushBack(3, d.GetAllocator()); 1933 minesObj.AddMember("minotaur_floor_percent", rapidjson::Value(50), d.GetAllocator()); 1934 1935 minesObj.AddMember("dark_floors", rapidjson::Value(rapidjson::kArrayType), d.GetAllocator()); 1936 minesObj["dark_floors"].PushBack(1, d.GetAllocator()); 1937 minesObj["dark_floors"].PushBack(2, d.GetAllocator()); 1938 minesObj["dark_floors"].PushBack(3, d.GetAllocator()); 1939 minesObj["dark_floors"].PushBack(4, d.GetAllocator()); 1940 minesObj.AddMember("dark_floor_percent", rapidjson::Value(25), d.GetAllocator()); 1941 1942 minesObj.AddMember("shop_floors", rapidjson::Value(rapidjson::kArrayType), d.GetAllocator()); 1943 minesObj["shop_floors"].PushBack(2, d.GetAllocator()); 1944 minesObj["shop_floors"].PushBack(3, d.GetAllocator()); 1945 minesObj["shop_floors"].PushBack(4, d.GetAllocator()); 1946 minesObj.AddMember("shop_floor_percent", rapidjson::Value(50), d.GetAllocator()); 1947 1948 minesObj.AddMember("npc_floors", rapidjson::Value(rapidjson::kArrayType), d.GetAllocator()); 1949 minesObj["npc_floors"].PushBack(2, d.GetAllocator()); 1950 minesObj["npc_floors"].PushBack(3, d.GetAllocator()); 1951 minesObj["npc_floors"].PushBack(4, d.GetAllocator()); 1952 minesObj.AddMember("npc_spawn_chance", rapidjson::Value(10), d.GetAllocator()); 1953 1954 d["map_generation"].AddMember(key1, minesObj, d.GetAllocator()); 1955 1956 rapidjson::Value key2("The Swamp", d.GetAllocator()); 1957 rapidjson::Value swampObj(rapidjson::kObjectType); 1958 1959 rapidjson::Value trapArray2(rapidjson::kArrayType); 1960 trapArray2.PushBack("boulders", d.GetAllocator()); 1961 trapArray2.PushBack("arrows", d.GetAllocator()); 1962 swampObj.AddMember("trap_generation_types", trapArray2, d.GetAllocator()); 1963 swampObj.AddMember("minotaur_floors", rapidjson::Value(rapidjson::kArrayType), d.GetAllocator()); 1964 swampObj["minotaur_floors"].PushBack(7, d.GetAllocator()); 1965 swampObj["minotaur_floors"].PushBack(8, d.GetAllocator()); 1966 swampObj.AddMember("minotaur_floor_percent", rapidjson::Value(50), d.GetAllocator()); 1967 1968 swampObj.AddMember("dark_floors", rapidjson::Value(rapidjson::kArrayType), d.GetAllocator()); 1969 swampObj["dark_floors"].PushBack(6, d.GetAllocator()); 1970 swampObj["dark_floors"].PushBack(7, d.GetAllocator()); 1971 swampObj["dark_floors"].PushBack(8, d.GetAllocator()); 1972 swampObj["dark_floors"].PushBack(9, d.GetAllocator()); 1973 swampObj.AddMember("dark_floor_percent", rapidjson::Value(25), d.GetAllocator()); 1974 1975 swampObj.AddMember("shop_floors", rapidjson::Value(rapidjson::kArrayType), d.GetAllocator()); 1976 swampObj["shop_floors"].PushBack(6, d.GetAllocator()); 1977 swampObj["shop_floors"].PushBack(7, d.GetAllocator()); 1978 swampObj["shop_floors"].PushBack(8, d.GetAllocator()); 1979 swampObj["shop_floors"].PushBack(9, d.GetAllocator()); 1980 swampObj.AddMember("shop_floor_percent", rapidjson::Value(50), d.GetAllocator()); 1981 1982 swampObj.AddMember("npc_floors", rapidjson::Value(rapidjson::kArrayType), d.GetAllocator()); 1983 swampObj["npc_floors"].PushBack(6, d.GetAllocator()); 1984 swampObj["npc_floors"].PushBack(7, d.GetAllocator()); 1985 swampObj["npc_floors"].PushBack(8, d.GetAllocator()); 1986 swampObj["npc_floors"].PushBack(9, d.GetAllocator()); 1987 swampObj.AddMember("npc_spawn_chance", rapidjson::Value(10), d.GetAllocator()); 1988 1989 d["map_generation"].AddMember(key2, swampObj, d.GetAllocator()); 1990 1991 writeToFile(d); 1992 } 1993 writeToFile(rapidjson::Document & d)1994 void writeToFile(rapidjson::Document& d) 1995 { 1996 int filenum = 0; 1997 std::string testPath = "/data/gameplaymodifiers_export" + std::to_string(filenum) + ".json"; 1998 while ( PHYSFS_getRealDir(testPath.c_str()) != nullptr && filenum < 1000 ) 1999 { 2000 ++filenum; 2001 testPath = "/data/gameplaymodifiers_export" + std::to_string(filenum) + ".json"; 2002 } 2003 std::string outputPath = PHYSFS_getRealDir("/data/"); 2004 outputPath.append(PHYSFS_getDirSeparator()); 2005 std::string fileName = "data/gameplaymodifiers_export" + std::to_string(filenum) + ".json"; 2006 outputPath.append(fileName.c_str()); 2007 2008 FILE* fp = fopen(outputPath.c_str(), "wb"); 2009 if ( !fp ) 2010 { 2011 return; 2012 } 2013 char buf[65536]; 2014 rapidjson::FileWriteStream os(fp, buf, sizeof(buf)); 2015 rapidjson::PrettyWriter<rapidjson::FileWriteStream> writer(os); 2016 d.Accept(writer); 2017 2018 fclose(fp); 2019 } 2020 readFromFile()2021 void readFromFile() 2022 { 2023 resetValues(); 2024 if ( PHYSFS_getRealDir("/data/gameplaymodifiers.json") ) 2025 { 2026 std::string inputPath = PHYSFS_getRealDir("/data/gameplaymodifiers.json"); 2027 inputPath.append("/data/gameplaymodifiers.json"); 2028 2029 FILE* fp = fopen(inputPath.c_str(), "rb"); 2030 if ( !fp ) 2031 { 2032 printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); 2033 return; 2034 } 2035 char buf[65536]; 2036 rapidjson::FileReadStream is(fp, buf, sizeof(buf)); 2037 fclose(fp); 2038 2039 rapidjson::Document d; 2040 d.ParseStream(is); 2041 if ( !d.HasMember("version") ) 2042 { 2043 printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); 2044 return; 2045 } 2046 int version = d["version"].GetInt(); 2047 2048 for ( rapidjson::Value::ConstMemberIterator prop_itr = d.MemberBegin(); prop_itr != d.MemberEnd(); ++prop_itr ) 2049 { 2050 if ( readKeyToGameplayProperty(prop_itr) ) 2051 { 2052 usingCustomManager = true; 2053 } 2054 } 2055 2056 printlog("[JSON]: Successfully read json file %s", inputPath.c_str()); 2057 } 2058 } 2059 readKeyToGameplayProperty(rapidjson::Value::ConstMemberIterator & itr)2060 bool readKeyToGameplayProperty(rapidjson::Value::ConstMemberIterator& itr) 2061 { 2062 std::string name = itr->name.GetString(); 2063 if ( name.compare("version") == 0 ) 2064 { 2065 return true; 2066 } 2067 else if ( name.compare("xp_share_range") == 0 ) 2068 { 2069 xpShareRange = itr->value.GetInt(); 2070 return true; 2071 } 2072 else if ( name.compare("global_xp_award_percent") == 0 ) 2073 { 2074 globalXPPercent = itr->value.GetInt(); 2075 return true; 2076 } 2077 else if ( name.compare("global_gold_drop_scale_percent") == 0 ) 2078 { 2079 globalGoldPercent = itr->value.GetInt(); 2080 return true; 2081 } 2082 else if ( name.compare("player_share_minimap_progress") == 0 ) 2083 { 2084 minimapShareProgress = itr->value.GetBool(); 2085 return true; 2086 } 2087 else if ( name.compare("player_speed_weight_impact_percent") == 0 ) 2088 { 2089 playerWeightPercent = itr->value.GetInt(); 2090 return true; 2091 } 2092 else if ( name.compare("player_speed_max") == 0 ) 2093 { 2094 playerSpeedMax = itr->value.GetDouble(); 2095 return true; 2096 } 2097 else if ( name.compare("minotaur_force_disable_on_floors") == 0 ) 2098 { 2099 for ( rapidjson::Value::ConstValueIterator arr_itr = itr->value["normal_floors"].Begin(); arr_itr != itr->value["normal_floors"].End(); ++arr_itr ) 2100 { 2101 minotaurForceDisableFloors.first.insert(arr_itr->GetInt()); 2102 } 2103 for ( rapidjson::Value::ConstValueIterator arr_itr = itr->value["secret_floors"].Begin(); arr_itr != itr->value["secret_floors"].End(); ++arr_itr ) 2104 { 2105 minotaurForceDisableFloors.second.insert(arr_itr->GetInt()); 2106 } 2107 return true; 2108 } 2109 else if ( name.compare("minotaur_force_enable_on_floors") == 0 ) 2110 { 2111 for ( rapidjson::Value::ConstValueIterator arr_itr = itr->value["normal_floors"].Begin(); arr_itr != itr->value["normal_floors"].End(); ++arr_itr ) 2112 { 2113 minotaurForceEnableFloors.first.insert(arr_itr->GetInt()); 2114 } 2115 for ( rapidjson::Value::ConstValueIterator arr_itr = itr->value["secret_floors"].Begin(); arr_itr != itr->value["secret_floors"].End(); ++arr_itr ) 2116 { 2117 minotaurForceEnableFloors.second.insert(arr_itr->GetInt()); 2118 } 2119 return true; 2120 } 2121 else if ( name.compare("disable_hunger_on_floors") == 0 ) 2122 { 2123 for ( rapidjson::Value::ConstValueIterator arr_itr = itr->value["normal_floors"].Begin(); arr_itr != itr->value["normal_floors"].End(); ++arr_itr ) 2124 { 2125 hungerDisableFloors.first.insert(arr_itr->GetInt()); 2126 } 2127 for ( rapidjson::Value::ConstValueIterator arr_itr = itr->value["secret_floors"].Begin(); arr_itr != itr->value["secret_floors"].End(); ++arr_itr ) 2128 { 2129 hungerDisableFloors.second.insert(arr_itr->GetInt()); 2130 } 2131 return true; 2132 } 2133 else if ( name.compare("disable_herx_messages_on_floors") == 0 ) 2134 { 2135 for ( rapidjson::Value::ConstValueIterator arr_itr = itr->value["normal_floors"].Begin(); arr_itr != itr->value["normal_floors"].End(); ++arr_itr ) 2136 { 2137 herxChatterDisableFloors.first.insert(arr_itr->GetInt()); 2138 } 2139 for ( rapidjson::Value::ConstValueIterator arr_itr = itr->value["secret_floors"].Begin(); arr_itr != itr->value["secret_floors"].End(); ++arr_itr ) 2140 { 2141 herxChatterDisableFloors.second.insert(arr_itr->GetInt()); 2142 } 2143 return true; 2144 } 2145 else if ( name.compare("disable_minimap_on_floors") == 0 ) 2146 { 2147 for ( rapidjson::Value::ConstValueIterator arr_itr = itr->value["normal_floors"].Begin(); arr_itr != itr->value["normal_floors"].End(); ++arr_itr ) 2148 { 2149 minimapDisableFloors.first.insert(arr_itr->GetInt()); 2150 } 2151 for ( rapidjson::Value::ConstValueIterator arr_itr = itr->value["secret_floors"].Begin(); arr_itr != itr->value["secret_floors"].End(); ++arr_itr ) 2152 { 2153 minimapDisableFloors.second.insert(arr_itr->GetInt()); 2154 } 2155 return true; 2156 } 2157 else if ( name.compare("map_generation") == 0 ) 2158 { 2159 for ( rapidjson::Value::ConstMemberIterator map_itr = itr->value.MemberBegin(); map_itr != itr->value.MemberEnd(); ++map_itr ) 2160 { 2161 std::string mapName = map_itr->name.GetString(); 2162 MapGeneration m(mapName); 2163 for ( rapidjson::Value::ConstMemberIterator obj_itr = map_itr->value.MemberBegin(); obj_itr != map_itr->value.MemberEnd(); ++obj_itr ) 2164 { 2165 readKeyToMapGenerationProperty(m, obj_itr); 2166 } 2167 allMapGenerations.push_back(m); 2168 } 2169 return true; 2170 } 2171 printlog("[JSON]: Unknown property '%s'", name.c_str()); 2172 return false; 2173 } 2174 readKeyToMapGenerationProperty(MapGeneration & m,rapidjson::Value::ConstMemberIterator & itr)2175 bool readKeyToMapGenerationProperty(MapGeneration& m, rapidjson::Value::ConstMemberIterator& itr) 2176 { 2177 std::string name = itr->name.GetString(); 2178 if ( name.compare("trap_generation_types") == 0 ) 2179 { 2180 m.usingTrapTypes = true; 2181 for ( rapidjson::Value::ConstValueIterator arr_itr = itr->value.Begin(); arr_itr != itr->value.End(); ++arr_itr ) 2182 { 2183 m.trapTypes.push_back(arr_itr->GetString()); 2184 } 2185 return true; 2186 } 2187 else if ( name.compare("minotaur_floors") == 0 ) 2188 { 2189 for ( rapidjson::Value::ConstValueIterator arr_itr = itr->value.Begin(); arr_itr != itr->value.End(); ++arr_itr ) 2190 { 2191 m.minoFloors.insert(arr_itr->GetInt()); 2192 } 2193 return true; 2194 } 2195 else if ( name.compare("dark_floors") == 0 ) 2196 { 2197 for ( rapidjson::Value::ConstValueIterator arr_itr = itr->value.Begin(); arr_itr != itr->value.End(); ++arr_itr ) 2198 { 2199 m.darkFloors.insert(arr_itr->GetInt()); 2200 } 2201 return true; 2202 } 2203 else if ( name.compare("shop_floors") == 0 ) 2204 { 2205 for ( rapidjson::Value::ConstValueIterator arr_itr = itr->value.Begin(); arr_itr != itr->value.End(); ++arr_itr ) 2206 { 2207 m.shopFloors.insert(arr_itr->GetInt()); 2208 } 2209 return true; 2210 } 2211 else if ( name.compare("npc_floors") == 0 ) 2212 { 2213 for ( rapidjson::Value::ConstValueIterator arr_itr = itr->value.Begin(); arr_itr != itr->value.End(); ++arr_itr ) 2214 { 2215 m.npcSpawnFloors.insert(arr_itr->GetInt()); 2216 } 2217 return true; 2218 } 2219 else if ( name.compare("dark_floor_percent") == 0 ) 2220 { 2221 m.darkPercent = itr->value.GetInt(); 2222 return true; 2223 } 2224 else if ( name.compare("minotaur_floor_percent") == 0 ) 2225 { 2226 m.minoPercent = itr->value.GetInt(); 2227 return true; 2228 } 2229 else if ( name.compare("shop_floor_percent") == 0 ) 2230 { 2231 m.shopPercent = itr->value.GetInt(); 2232 return true; 2233 } 2234 else if ( name.compare("npc_spawn_chance") == 0 ) 2235 { 2236 m.npcSpawnPercent = itr->value.GetInt(); 2237 return true; 2238 } 2239 printlog("[JSON]: Unknown property '%s'", name.c_str()); 2240 return false; 2241 } 2242 processedMinotaurSpawn(int level,bool secret,std::string mapName)2243 bool processedMinotaurSpawn(int level, bool secret, std::string mapName) 2244 { 2245 if ( !inUse() ) 2246 { 2247 return false; 2248 } 2249 2250 if ( CustomHelpers::isLevelPartOfSet(level, secret, minotaurForceEnableFloors) ) 2251 { 2252 minotaurlevel = 1; 2253 return true; 2254 } 2255 if ( CustomHelpers::isLevelPartOfSet(level, secret, minotaurForceDisableFloors) ) 2256 { 2257 minotaurlevel = 0; 2258 return true; 2259 } 2260 2261 auto m = getMapGenerationForMapName(mapName); 2262 if ( m ) 2263 { 2264 if ( m->minoPercent == -1 ) 2265 { 2266 // no key value read in. 2267 return false; 2268 } 2269 2270 if ( m->minoFloors.find(level) == m->minoFloors.end() ) 2271 { 2272 // not found 2273 minotaurlevel = 0; 2274 return true; 2275 } 2276 // found, roll prng 2277 if ( prng_get_uint() % 100 < m->minoPercent ) 2278 { 2279 minotaurlevel = 1; 2280 } 2281 else 2282 { 2283 minotaurlevel = 0; 2284 } 2285 return true; 2286 } 2287 return false; 2288 } 2289 processedDarkFloor(int level,bool secret,std::string mapName)2290 bool processedDarkFloor(int level, bool secret, std::string mapName) 2291 { 2292 if ( !inUse() ) 2293 { 2294 return false; 2295 } 2296 2297 auto m = getMapGenerationForMapName(mapName); 2298 if ( m ) 2299 { 2300 if ( m->darkPercent == -1 ) 2301 { 2302 // no key value read in. 2303 return false; 2304 } 2305 2306 if ( m->darkFloors.find(level) == m->darkFloors.end() ) 2307 { 2308 // not found 2309 darkmap = false; 2310 return true; 2311 } 2312 // found, roll prng 2313 if ( prng_get_uint() % 100 < m->darkPercent ) 2314 { 2315 darkmap = true; 2316 } 2317 else 2318 { 2319 darkmap = false; 2320 } 2321 return true; 2322 } 2323 return false; 2324 } 2325 processedShopFloor(int level,bool secret,std::string mapName,bool & shoplevel)2326 bool processedShopFloor(int level, bool secret, std::string mapName, bool& shoplevel) 2327 { 2328 if ( !inUse() ) 2329 { 2330 return false; 2331 } 2332 2333 auto m = getMapGenerationForMapName(mapName); 2334 if ( m ) 2335 { 2336 if ( m->shopPercent == -1 ) 2337 { 2338 // no key value read in. 2339 return false; 2340 } 2341 2342 if ( m->shopFloors.find(level) == m->shopFloors.end() ) 2343 { 2344 // not found 2345 shoplevel = false; 2346 return true; 2347 } 2348 // found, roll prng 2349 if ( prng_get_uint() % 100 < m->shopPercent ) 2350 { 2351 shoplevel = true; 2352 } 2353 else 2354 { 2355 shoplevel = false; 2356 } 2357 return true; 2358 } 2359 return false; 2360 } 2361 2362 enum PropertyTypes : int 2363 { 2364 PROPERTY_NPC 2365 }; 2366 processedPropertyForFloor(int level,bool secret,std::string mapName,PropertyTypes propertyType,bool & bOut)2367 bool processedPropertyForFloor(int level, bool secret, std::string mapName, PropertyTypes propertyType, bool& bOut) 2368 { 2369 if ( !inUse() ) 2370 { 2371 return false; 2372 } 2373 2374 auto m = getMapGenerationForMapName(mapName); 2375 if ( m ) 2376 { 2377 int percentValue = -1; 2378 switch ( propertyType ) 2379 { 2380 case PROPERTY_NPC: 2381 if ( m->npcSpawnFloors.find(level) == m->npcSpawnFloors.end() ) 2382 { 2383 // not found 2384 bOut = false; 2385 return true; 2386 } 2387 percentValue = m->npcSpawnPercent; 2388 break; 2389 default: 2390 break; 2391 } 2392 2393 if ( percentValue == -1 ) 2394 { 2395 // no key value read in. 2396 return false; 2397 } 2398 2399 // found, roll prng 2400 if ( prng_get_uint() % 100 < percentValue ) 2401 { 2402 bOut = true; 2403 } 2404 else 2405 { 2406 bOut = false; 2407 } 2408 return true; 2409 } 2410 return false; 2411 } 2412 }; 2413 extern GameplayCustomManager gameplayCustomManager; 2414 2415 class GameModeManager_t 2416 { 2417 public: 2418 enum GameModes : int 2419 { 2420 GAME_MODE_DEFAULT, 2421 GAME_MODE_TUTORIAL_INIT, 2422 GAME_MODE_TUTORIAL 2423 }; 2424 GameModes currentMode = GAME_MODE_DEFAULT; getMode() const2425 GameModes getMode() const { return currentMode; }; setMode(const GameModes mode)2426 void setMode(const GameModes mode) { currentMode = mode; }; 2427 class CurrentSession_t 2428 { 2429 public: 2430 Uint32 serverFlags = 0; 2431 bool bHasSavedServerFlags = false; restoreSavedServerFlags()2432 void restoreSavedServerFlags() 2433 { 2434 if ( bHasSavedServerFlags ) 2435 { 2436 bHasSavedServerFlags = false; 2437 svFlags = serverFlags; 2438 printlog("[SESSION]: Restoring server flags at stage: %d", introstage); 2439 } 2440 } saveServerFlags()2441 void saveServerFlags() 2442 { 2443 serverFlags = svFlags; 2444 bHasSavedServerFlags = true; 2445 printlog("[SESSION]: Saving server flags at stage: %d", introstage); 2446 } 2447 } currentSession; isServerflagDisabledForCurrentMode(int i)2448 bool isServerflagDisabledForCurrentMode(int i) 2449 { 2450 if ( getMode() == GAME_MODE_DEFAULT ) 2451 { 2452 return false; 2453 } 2454 else if ( getMode() == GAME_MODE_TUTORIAL ) 2455 { 2456 int flag = power(2, i); 2457 switch ( flag ) 2458 { 2459 case SV_FLAG_HARDCORE: 2460 case SV_FLAG_HUNGER: 2461 case SV_FLAG_FRIENDLYFIRE: 2462 case SV_FLAG_LIFESAVING: 2463 case SV_FLAG_TRAPS: 2464 case SV_FLAG_CLASSIC: 2465 case SV_FLAG_MINOTAURS: 2466 case SV_FLAG_KEEPINVENTORY: 2467 return true; 2468 break; 2469 default: 2470 break; 2471 } 2472 return false; 2473 } 2474 return false; 2475 } 2476 class Tutorial_t 2477 { 2478 std::string currentMap = ""; 2479 const Uint32 kNumTutorialLevels = 10; 2480 public: init()2481 void init() 2482 { 2483 readFromFile(); 2484 } 2485 int dungeonLevel = -1; setTutorialMap(std::string & mapname)2486 void setTutorialMap(std::string& mapname) 2487 { 2488 loadCustomNextMap = mapname; 2489 currentMap = loadCustomNextMap; 2490 } launchHub()2491 void launchHub() 2492 { 2493 loadCustomNextMap = "tutorial_hub.lmp"; 2494 currentMap = loadCustomNextMap; 2495 } 2496 void startTutorial(std::string mapToSet); 2497 static void buttonReturnToTutorialHub(button_t* my); 2498 static void buttonRestartTrial(button_t* my); getNumTutorialLevels()2499 const Uint32 getNumTutorialLevels() { return kNumTutorialLevels; } 2500 void openGameoverWindow(); onMapRestart(int levelNum)2501 void onMapRestart(int levelNum) 2502 { 2503 achievementObserver.updateGlobalStat( 2504 std::min(STEAM_GSTAT_TUTORIAL1_ATTEMPTS - 1 + levelNum, static_cast<int>(STEAM_GSTAT_TUTORIAL10_ATTEMPTS))); 2505 } 2506 2507 class Menu_t 2508 { 2509 bool bWindowOpen = false; 2510 public: isOpen()2511 bool isOpen() { return bWindowOpen; } 2512 void open(); close()2513 void close() { bWindowOpen = false; } 2514 void onClickEntry(); 2515 int windowScroll = 0; 2516 int selectedMenuItem = -1; 2517 std::string windowTitle = ""; 2518 std::string defaultHoverText = ""; 2519 } Menu; 2520 2521 class FirstTimePrompt_t 2522 { 2523 bool bWindowOpen = false; 2524 public: 2525 void createPrompt(); 2526 void drawDialogue(); isOpen()2527 bool isOpen() { return bWindowOpen; } close()2528 void close() { bWindowOpen = false; } 2529 bool doButtonSkipPrompt = false; 2530 bool showFirstTimePrompt = false; 2531 static void buttonSkipPrompt(button_t* my); 2532 static void buttonPromptEnterTutorialHub(button_t* my); 2533 } FirstTimePrompt; 2534 2535 class Level_t 2536 { 2537 public: Level_t()2538 Level_t() 2539 { 2540 filename = ""; 2541 title = ""; 2542 description = ""; 2543 completionTime = 0; 2544 }; 2545 std::string filename; 2546 std::string title; 2547 std::string description; 2548 Uint32 completionTime; 2549 }; 2550 std::vector<Level_t> levels; 2551 2552 void readFromFile(); 2553 void writeToDocument(); writeToFile(rapidjson::Document & d)2554 void writeToFile(rapidjson::Document& d) 2555 { 2556 std::string outputPath = outputdir; 2557 outputPath.append(PHYSFS_getDirSeparator()); 2558 std::string fileName = "data/tutorial_scores.json"; 2559 outputPath.append(fileName.c_str()); 2560 2561 FILE* fp = fopen(outputPath.c_str(), "wb"); 2562 if ( !fp ) 2563 { 2564 return; 2565 } 2566 char buf[65536]; 2567 rapidjson::FileWriteStream os(fp, buf, sizeof(buf)); 2568 rapidjson::PrettyWriter<rapidjson::FileWriteStream> writer(os); 2569 d.Accept(writer); 2570 2571 fclose(fp); 2572 } 2573 } Tutorial; 2574 }; 2575 extern GameModeManager_t gameModeManager;