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 &it;
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;