1 /*-------------------------------------------------------------------------------
2 
3 	BARONY
4 	File: scores.cpp
5 	Desc: contains code for handling scores, statistics, and save games
6 
7 	Copyright 2013-2016 (c) Turning Wheel LLC, all rights reserved.
8 	See LICENSE for details.
9 
10 -------------------------------------------------------------------------------*/
11 
12 #include "main.hpp"
13 #include "files.hpp"
14 #include "game.hpp"
15 #include "stat.hpp"
16 #include "menu.hpp"
17 #include "monster.hpp"
18 #include "scores.hpp"
19 #include "items.hpp"
20 #include "interface/interface.hpp"
21 #include "magic/magic.hpp"
22 #include "net.hpp"
23 #include "player.hpp"
24 #include "sys/stat.h"
25 #include "paths.hpp"
26 #include "collision.hpp"
27 #include "mod_tools.hpp"
28 #include "lobbies.hpp"
29 
30 // definitions
31 list_t topscores;
32 list_t topscoresMultiplayer;
33 int victory = false;
34 Uint32 completionTime = 0;
35 bool conductPenniless = true;
36 bool conductFoodless = true;
37 bool conductVegetarian = true;
38 bool conductIlliterate = true;
39 Sint32 conductGameChallenges[NUM_CONDUCT_CHALLENGES] = { 0 }; // additional 'conducts' to be stored in here.
40 Sint32 gameStatistics[NUM_GAMEPLAY_STATISTICS] = { 0 }; // general saved game statistics to be stored in here.
41 std::vector<std::pair<Uint32, Uint32>> achievementRhythmOfTheKnightVec[MAXPLAYERS] = {};
42 bool achievementStatusRhythmOfTheKnight[MAXPLAYERS] = { false };
43 std::pair<Uint32, Uint32> achievementThankTheTankPair[MAXPLAYERS] = { std::make_pair(0, 0) };
44 bool achievementStatusBaitAndSwitch[MAXPLAYERS] = { false };
45 Uint32 achievementBaitAndSwitchTimer[MAXPLAYERS] = { 0 };
46 std::unordered_set<int> clientLearnedAlchemyIngredients;
47 bool achievementStatusThankTheTank[MAXPLAYERS] = { false };
48 std::vector<Uint32> achievementStrobeVec[MAXPLAYERS] = {};
49 bool achievementStatusStrobe[MAXPLAYERS] = { false };
50 bool playerFailedRangedOnlyConduct[MAXPLAYERS] = { false };
51 list_t booksRead;
52 bool usedClass[NUMCLASSES] = {0};
53 bool usedRace[NUMRACES] = { 0 };
54 Uint32 loadingsavegame = 0;
55 bool achievementBrawlerMode = false;
56 bool achievementRangedMode[MAXPLAYERS] = { 0 };
57 int savegameCurrentFileIndex = 0;
58 score_t steamLeaderboardScore;
59 AchievementObserver achievementObserver;
60 
61 /*-------------------------------------------------------------------------------
62 
63 	scoreConstructor
64 
65 	creates a score_t structure
66 
67 -------------------------------------------------------------------------------*/
68 
scoreConstructor()69 score_t* scoreConstructor()
70 {
71 	node_t* node;
72 
73 	score_t* score = (score_t*) malloc(sizeof(score_t));
74 	if ( !score )
75 	{
76 		printlog( "failed to allocate memory for new score!\n" );
77 		exit(1);
78 	}
79 	// Stat set to 0 as monster type not needed, values will be overwritten by the player data
80 	score->stats = new Stat(0);
81 	if ( !score->stats )
82 	{
83 		printlog( "failed to allocate memory for new stat!\n" );
84 		exit(1);
85 	}
86 
87 	// set all data elements
88 	int c;
89 	for ( c = 0; c < NUMMONSTERS; c++ )
90 	{
91 		score->kills[c] = kills[c];
92 	}
93 	score->stats->type = stats[clientnum]->type;
94 	score->stats->sex = stats[clientnum]->sex;
95 	score->stats->appearance = stats[clientnum]->appearance;
96 	score->stats->playerRace = stats[clientnum]->playerRace;
97 	//score->stats->appearance |= stats[clientnum]->playerRace << 8;
98 	strcpy(score->stats->name, stats[clientnum]->name);
99 	strcpy(score->stats->obituary, stats[clientnum]->obituary);
100 	score->victory = victory;
101 	score->dungeonlevel = currentlevel;
102 	score->classnum = client_classes[clientnum];
103 	score->stats->HP = stats[clientnum]->HP;
104 	score->stats->MAXHP = stats[clientnum]->MAXHP;
105 	score->stats->MP = stats[clientnum]->MP;
106 	score->stats->MAXMP = stats[clientnum]->MAXMP;
107 	score->stats->STR = stats[clientnum]->STR;
108 	score->stats->DEX = stats[clientnum]->DEX;
109 	score->stats->CON = stats[clientnum]->CON;
110 	score->stats->INT = stats[clientnum]->INT;
111 	score->stats->PER = stats[clientnum]->PER;
112 	score->stats->CHR = stats[clientnum]->CHR;
113 	score->stats->EXP = stats[clientnum]->EXP;
114 	score->stats->LVL = stats[clientnum]->LVL;
115 	score->stats->GOLD = stats[clientnum]->GOLD;
116 	score->stats->HUNGER = stats[clientnum]->HUNGER;
117 	for ( c = 0; c < NUMPROFICIENCIES; c++ )
118 	{
119 		score->stats->PROFICIENCIES[c] = stats[clientnum]->PROFICIENCIES[c];
120 	}
121 	for ( c = 0; c < NUMEFFECTS; c++ )
122 	{
123 		score->stats->EFFECTS[c] = stats[clientnum]->EFFECTS[c];
124 		score->stats->EFFECTS_TIMERS[c] = stats[clientnum]->EFFECTS_TIMERS[c];
125 	}
126 	score->stats->leader_uid = 0;
127 	score->stats->FOLLOWERS.first = NULL;
128 	score->stats->FOLLOWERS.last = NULL;
129 	score->stats->stache_x1 = 0;
130 	score->stats->stache_x2 = 0;
131 	score->stats->stache_y1 = 0;
132 	score->stats->stache_y2 = 0;
133 	score->stats->inventory.first = NULL;
134 	score->stats->inventory.last = NULL;
135 	score->stats->helmet = NULL;
136 	score->stats->breastplate = NULL;
137 	score->stats->gloves = NULL;
138 	score->stats->shoes = NULL;
139 	score->stats->shield = NULL;
140 	score->stats->weapon = NULL;
141 	score->stats->cloak = NULL;
142 	score->stats->amulet = NULL;
143 	score->stats->ring = NULL;
144 	score->stats->mask = NULL;
145 	list_Copy(&score->stats->inventory, &stats[clientnum]->inventory);
146 	for ( node = score->stats->inventory.first; node != NULL; node = node->next )
147 	{
148 		Item* item = (Item*)node->element;
149 		item->node = node;
150 	}
151 	for ( c = 0, node = stats[clientnum]->inventory.first; node != NULL; node = node->next, c++ )
152 	{
153 		Item* item = (Item*)node->element;
154 		if ( stats[clientnum]->helmet == item )
155 		{
156 			node_t* node2 = list_Node(&score->stats->inventory, c);
157 			Item* item2 = (Item*)node2->element;
158 			score->stats->helmet = item2;
159 		}
160 		else if ( stats[clientnum]->breastplate == item )
161 		{
162 			node_t* node2 = list_Node(&score->stats->inventory, c);
163 			Item* item2 = (Item*)node2->element;
164 			score->stats->breastplate = item2;
165 		}
166 		else if ( stats[clientnum]->gloves == item )
167 		{
168 			node_t* node2 = list_Node(&score->stats->inventory, c);
169 			Item* item2 = (Item*)node2->element;
170 			score->stats->gloves = item2;
171 		}
172 		else if ( stats[clientnum]->shoes == item )
173 		{
174 			node_t* node2 = list_Node(&score->stats->inventory, c);
175 			Item* item2 = (Item*)node2->element;
176 			score->stats->shoes = item2;
177 		}
178 		else if ( stats[clientnum]->shield == item )
179 		{
180 			node_t* node2 = list_Node(&score->stats->inventory, c);
181 			Item* item2 = (Item*)node2->element;
182 			score->stats->shield = item2;
183 		}
184 		else if ( stats[clientnum]->weapon == item )
185 		{
186 			node_t* node2 = list_Node(&score->stats->inventory, c);
187 			Item* item2 = (Item*)node2->element;
188 			score->stats->weapon = item2;
189 		}
190 		else if ( stats[clientnum]->cloak == item )
191 		{
192 			node_t* node2 = list_Node(&score->stats->inventory, c);
193 			Item* item2 = (Item*)node2->element;
194 			score->stats->cloak = item2;
195 		}
196 		else if ( stats[clientnum]->amulet == item )
197 		{
198 			node_t* node2 = list_Node(&score->stats->inventory, c);
199 			Item* item2 = (Item*)node2->element;
200 			score->stats->amulet = item2;
201 		}
202 		else if ( stats[clientnum]->ring == item )
203 		{
204 			node_t* node2 = list_Node(&score->stats->inventory, c);
205 			Item* item2 = (Item*)node2->element;
206 			score->stats->ring = item2;
207 		}
208 		else if ( stats[clientnum]->mask == item )
209 		{
210 			node_t* node2 = list_Node(&score->stats->inventory, c);
211 			Item* item2 = (Item*)node2->element;
212 			score->stats->mask = item2;
213 		}
214 	}
215 	score->stats->monster_sound = NULL;
216 	score->stats->monster_idlevar = 0;
217 
218 	score->completionTime = completionTime;
219 	score->conductPenniless = conductPenniless;
220 	score->conductFoodless = conductFoodless;
221 	score->conductVegetarian = conductVegetarian;
222 	score->conductIlliterate = conductIlliterate;
223 	for ( c = 0; c < NUM_CONDUCT_CHALLENGES; ++c )
224 	{
225 		score->conductGameChallenges[c] = conductGameChallenges[c];
226 	}
227 	for ( c = 0; c < NUM_GAMEPLAY_STATISTICS; ++c )
228 	{
229 		score->gameStatistics[c] = gameStatistics[c];
230 	}
231 	return score;
232 }
233 
234 /*-------------------------------------------------------------------------------
235 
236 	scoreDeconstructor
237 
238 	destroys a score_t structure
239 
240 -------------------------------------------------------------------------------*/
241 
scoreDeconstructor(void * data)242 void scoreDeconstructor(void* data)
243 {
244 	if ( data )
245 	{
246 		score_t* score = (score_t*)data;
247 		if ( score->stats )
248 		{
249 			delete score->stats;
250 		}
251 		//score->stats->~Stat();
252 		free(data);
253 	}
254 }
255 
256 /*-------------------------------------------------------------------------------
257 
258 	saveScore
259 
260 	saves the current game score to the highscore list. Returns -1 if the
261 	score is not saved
262 
263 -------------------------------------------------------------------------------*/
264 
saveScore()265 int saveScore()
266 {
267 	node_t* node;
268 	int c;
269 
270 	score_t* currentscore = scoreConstructor();
271 	list_t* scoresPtr = &topscores;
272 	if ( conductGameChallenges[CONDUCT_MULTIPLAYER] )
273 	{
274 		scoresPtr = &topscoresMultiplayer;
275 	}
276 
277 #ifdef STEAMWORKS
278 	if ( g_SteamLeaderboards )
279 	{
280 		if ( steamLeaderboardSetScore(currentscore) )
281 		{
282 			g_SteamLeaderboards->LeaderboardUpload.score = totalScore(currentscore);
283 			g_SteamLeaderboards->LeaderboardUpload.time = currentscore->completionTime / TICKS_PER_SECOND;
284 			g_SteamLeaderboards->LeaderboardUpload.status = LEADERBOARD_STATE_FIND_LEADERBOARD_TIME;
285 			printlog("[STEAM]: Initialising leaderboard score upload...");
286 		}
287 		else
288 		{
289 			printlog("[STEAM]: Did not qualify for leaderboard score upload.");
290 			if ( currentscore->gameStatistics[STATISTICS_DISABLE_UPLOAD] == 1 )
291 			{
292 				printlog("[STEAM]: Loaded data did not match hash as expected.");
293 			}
294 		}
295 	}
296 #endif // STEAMWORKS
297 
298 	for ( c = 0, node = scoresPtr->first; node != NULL; node = node->next, c++ )
299 	{
300 		score_t* score = (score_t*)node->element;
301 		if ( totalScore(score) <= totalScore(currentscore) )
302 		{
303 			node_t* newNode = list_AddNode(scoresPtr, c);
304 			newNode->element = currentscore;
305 			newNode->deconstructor = &scoreDeconstructor;
306 			newNode->size = sizeof(score_t);
307 			while ( list_Size(scoresPtr) > MAXTOPSCORES )
308 			{
309 				list_RemoveNode(scoresPtr->last);
310 			}
311 			return c;
312 		}
313 	}
314 	if ( c == MAXTOPSCORES )
315 	{
316 		scoreDeconstructor((void*)currentscore);
317 		return -1; // do not save the score
318 	}
319 	node = list_AddNodeLast(scoresPtr);
320 	node->element = currentscore;
321 	node->deconstructor = &scoreDeconstructor;
322 	node->size = sizeof(score_t);
323 
324 	return c;
325 }
326 
327 /*-------------------------------------------------------------------------------
328 
329 	totalScore
330 
331 	calculates the total score value of a score_t object
332 
333 -------------------------------------------------------------------------------*/
334 
totalScore(score_t * score)335 int totalScore(score_t* score)
336 {
337 	int amount = 0;
338 
339 	node_t* node;
340 	for ( node = score->stats->inventory.first; node != NULL; node = node->next )
341 	{
342 		Item* item = (Item*)node->element;
343 		amount += items[item->type].value;
344 	}
345 	amount += score->stats->GOLD;
346 	amount += score->stats->EXP;
347 	amount += score->stats->LVL * 500;
348 
349 	int c;
350 	for ( c = 0; c < NUMPROFICIENCIES; c++ )
351 	{
352 		amount += score->stats->PROFICIENCIES[c];
353 	}
354 	for ( c = 0; c < NUMMONSTERS; c++ )
355 	{
356 		if ( c != HUMAN )
357 		{
358 			amount += score->kills[c] * 100;
359 		}
360 		else
361 		{
362 			amount -= score->kills[c] * 100;
363 		}
364 	}
365 
366 	amount += score->dungeonlevel * 500;
367 	if ( score->victory == 3 )
368 	{
369 		amount += score->victory * 20000;
370 	}
371 	else
372 	{
373 		amount += score->victory * 10000;
374 	}
375 	amount -= score->completionTime / TICKS_PER_SECOND;
376 	if ( score->victory )
377 	{
378 		amount += score->conductPenniless * 5000;
379 		amount += score->conductFoodless * 5000;
380 		amount += score->conductVegetarian * 5000;
381 		amount += score->conductIlliterate * 5000;
382 		amount += conductGameChallenges[CONDUCT_BOOTS_SPEED] * 20000;
383 		amount += conductGameChallenges[CONDUCT_BRAWLER] * 20000;
384 		amount += conductGameChallenges[CONDUCT_RANGED_ONLY] * 20000;
385 		amount += conductGameChallenges[CONDUCT_ACCURSED] * 50000;
386 		amount += conductGameChallenges[CONDUCT_BLESSED_BOOTS_SPEED] * 100000;
387 		if ( score->conductGameChallenges[CONDUCT_HARDCORE] == 1
388 			&& score->conductGameChallenges[CONDUCT_CHEATS_ENABLED] == 0 )
389 		{
390 			amount *= 2;
391 		}
392 		if ( score->conductGameChallenges[CONDUCT_KEEPINVENTORY] )
393 		{
394 			amount /= 2;
395 		}
396 		if ( score->conductGameChallenges[CONDUCT_LIFESAVING] )
397 		{
398 			amount /= 4;
399 		}
400 	}
401 	if ( amount < 0 )
402 	{
403 		amount = 0;
404 	}
405 
406 	return amount;
407 }
408 
409 /*-------------------------------------------------------------------------------
410 
411 	loadScore
412 
413 	loads the given highscore into stats[0] so that it may be displayed
414 	in a character window at the main menu
415 
416 -------------------------------------------------------------------------------*/
417 
loadScore(int scorenum)418 void loadScore(int scorenum)
419 {
420 	node_t* node = nullptr;
421 	if ( scoreDisplayMultiplayer )
422 	{
423 		node = list_Node(&topscoresMultiplayer, scorenum);
424 	}
425 	else
426 	{
427 		node = list_Node(&topscores, scorenum);
428 	}
429 	if ( !node )
430 	{
431 		return;
432 	}
433 	score_t* score = (score_t*)node->element;
434 	stats[0]->clearStats();
435 
436 	int c;
437 	for ( c = 0; c < NUMMONSTERS; c++ )
438 	{
439 		kills[c] = score->kills[c];
440 	}
441 	stats[0]->type = score->stats->type;
442 	stats[0]->sex = score->stats->sex;
443 	stats[0]->appearance = score->stats->appearance;
444 	stats[0]->playerRace = score->stats->playerRace;
445 	//((stats[0]->appearance & 0xFF00) >> 8);
446 	//stats[0]->appearance = (stats[0]->appearance & 0xFF);
447 	strcpy(stats[0]->name, score->stats->name);
448 	client_classes[0] = score->classnum;
449 	victory = score->victory;
450 	currentlevel = score->dungeonlevel;
451 
452 	completionTime = score->completionTime;
453 	conductPenniless = score->conductPenniless;
454 	conductFoodless = score->conductFoodless;
455 	conductVegetarian = score->conductVegetarian;
456 	conductIlliterate = score->conductIlliterate;
457 
458 	stats[0]->HP = score->stats->HP;
459 	stats[0]->MAXHP = score->stats->MAXHP;
460 	stats[0]->MP = score->stats->MP;
461 	stats[0]->MAXMP = score->stats->MAXMP;
462 	stats[0]->STR = score->stats->STR;
463 	stats[0]->DEX = score->stats->DEX;
464 	stats[0]->CON = score->stats->CON;
465 	stats[0]->INT = score->stats->INT;
466 	stats[0]->PER = score->stats->PER;
467 	stats[0]->CHR = score->stats->CHR;
468 	stats[0]->EXP = score->stats->EXP;
469 	stats[0]->LVL = score->stats->LVL;
470 	stats[0]->GOLD = score->stats->GOLD;
471 	stats[0]->HUNGER = score->stats->HUNGER;
472 	for ( c = 0; c < NUMPROFICIENCIES; c++ )
473 	{
474 		stats[0]->PROFICIENCIES[c] = score->stats->PROFICIENCIES[c];
475 	}
476 	for ( c = 0; c < NUMEFFECTS; c++ )
477 	{
478 		stats[0]->EFFECTS[c] = score->stats->EFFECTS[c];
479 		stats[0]->EFFECTS_TIMERS[c] = score->stats->EFFECTS_TIMERS[c];
480 	}
481 	list_FreeAll(&stats[0]->inventory);
482 	list_Copy(&stats[0]->inventory, &score->stats->inventory);
483 	for ( node = stats[0]->inventory.first; node != NULL; node = node->next )
484 	{
485 		Item* item = (Item*)node->element;
486 		item->node = node;
487 	}
488 	for ( c = 0, node = score->stats->inventory.first; node != NULL; node = node->next, c++ )
489 	{
490 		Item* item = (Item*)node->element;
491 		if ( score->stats->helmet == item )
492 		{
493 			node_t* node2 = list_Node(&stats[0]->inventory, c);
494 			Item* item2 = (Item*)node2->element;
495 			stats[0]->helmet = item2;
496 		}
497 		else if ( score->stats->breastplate == item )
498 		{
499 			node_t* node2 = list_Node(&stats[0]->inventory, c);
500 			Item* item2 = (Item*)node2->element;
501 			stats[0]->breastplate = item2;
502 		}
503 		else if ( score->stats->gloves == item )
504 		{
505 			node_t* node2 = list_Node(&stats[0]->inventory, c);
506 			Item* item2 = (Item*)node2->element;
507 			stats[0]->gloves = item2;
508 		}
509 		else if ( score->stats->shoes == item )
510 		{
511 			node_t* node2 = list_Node(&stats[0]->inventory, c);
512 			Item* item2 = (Item*)node2->element;
513 			stats[0]->shoes = item2;
514 		}
515 		else if ( score->stats->shield == item )
516 		{
517 			node_t* node2 = list_Node(&stats[0]->inventory, c);
518 			Item* item2 = (Item*)node2->element;
519 			stats[0]->shield = item2;
520 		}
521 		else if ( score->stats->weapon == item )
522 		{
523 			node_t* node2 = list_Node(&stats[0]->inventory, c);
524 			Item* item2 = (Item*)node2->element;
525 			stats[0]->weapon = item2;
526 		}
527 		else if ( score->stats->cloak == item )
528 		{
529 			node_t* node2 = list_Node(&stats[0]->inventory, c);
530 			Item* item2 = (Item*)node2->element;
531 			stats[0]->cloak = item2;
532 		}
533 		else if ( score->stats->amulet == item )
534 		{
535 			node_t* node2 = list_Node(&stats[0]->inventory, c);
536 			Item* item2 = (Item*)node2->element;
537 			stats[0]->amulet = item2;
538 		}
539 		else if ( score->stats->ring == item )
540 		{
541 			node_t* node2 = list_Node(&stats[0]->inventory, c);
542 			Item* item2 = (Item*)node2->element;
543 			stats[0]->ring = item2;
544 		}
545 		else if ( score->stats->mask == item )
546 		{
547 			node_t* node2 = list_Node(&stats[0]->inventory, c);
548 			Item* item2 = (Item*)node2->element;
549 			stats[0]->mask = item2;
550 		}
551 	}
552 	for ( c = 0; c < NUM_CONDUCT_CHALLENGES; ++c )
553 	{
554 		conductGameChallenges[c] = score->conductGameChallenges[c];
555 	}
556 	for ( c = 0; c < NUM_GAMEPLAY_STATISTICS; ++c )
557 	{
558 		gameStatistics[c] = score->gameStatistics[c];
559 	}
560 }
561 
562 /*-------------------------------------------------------------------------------
563 
564 	saveAllScores
565 
566 	saves all highscores to the scores data file
567 
568 -------------------------------------------------------------------------------*/
569 
saveAllScores(const std::string & scoresfilename)570 void saveAllScores(const std::string& scoresfilename)
571 {
572 	node_t* node;
573 	FILE* fp;
574 	int c;
575 
576 	char path[PATH_MAX] = "";
577 	completePath(path, scoresfilename.c_str(), outputdir);
578 
579 	// open file
580 	if ( (fp = fopen(path, "wb")) == NULL )
581 	{
582 		printlog("error: failed to save '%s!'\n", scoresfilename.c_str());
583 		return;
584 	}
585 
586 	// magic number
587 	fprintf(fp, "BARONYSCORES");
588 	fprintf(fp, VERSION);
589 
590 	// header info
591 	c = list_Size(&booksRead);
592 	fwrite(&c, sizeof(Uint32), 1, fp);
593 	for ( node = booksRead.first; node != NULL; node = node->next )
594 	{
595 		char* book = (char*)node->element;
596 		c = strlen(book);
597 		fwrite(&c, sizeof(Uint32), 1, fp);
598 		fputs(book, fp);
599 	}
600 	for ( c = 0; c < NUMCLASSES; c++ )
601 	{
602 		fwrite(&usedClass[c], sizeof(bool), 1, fp);
603 	}
604 	for ( c = 0; c < NUMRACES; c++ )
605 	{
606 		fwrite(&usedRace[c], sizeof(bool), 1, fp);
607 	}
608 
609 	// score list
610 	if ( scoresfilename.compare(SCORESFILE) == 0 )
611 	{
612 		c = list_Size(&topscores);
613 		node = topscores.first;
614 	}
615 	else
616 	{
617 		c = list_Size(&topscoresMultiplayer);
618 		node = topscoresMultiplayer.first;
619 	}
620 	fwrite(&c, sizeof(Uint32), 1, fp);
621 	for (; node != NULL; node = node->next )
622 	{
623 		score_t* score = (score_t*)node->element;
624 		for ( c = 0; c < NUMMONSTERS; c++ )
625 		{
626 			fwrite(&score->kills[c], sizeof(Sint32), 1, fp);
627 		}
628 		fwrite(&score->completionTime, sizeof(Uint32), 1, fp);
629 		fwrite(&score->conductPenniless, sizeof(bool), 1, fp);
630 		fwrite(&score->conductFoodless, sizeof(bool), 1, fp);
631 		fwrite(&score->conductVegetarian, sizeof(bool), 1, fp);
632 		fwrite(&score->conductIlliterate, sizeof(bool), 1, fp);
633 		fwrite(&score->stats->type, sizeof(Monster), 1, fp);
634 		fwrite(&score->stats->sex, sizeof(sex_t), 1, fp);
635 		Uint32 raceAndAppearance = 0;
636 		raceAndAppearance |= (score->stats->playerRace << 8);
637 		raceAndAppearance |= (score->stats->appearance);
638 		fwrite(&raceAndAppearance, sizeof(Uint32), 1, fp);
639 		fwrite(score->stats->name, sizeof(char), 32, fp);
640 		fwrite(&score->classnum, sizeof(Sint32), 1, fp);
641 		fwrite(&score->dungeonlevel, sizeof(Sint32), 1, fp);
642 		fwrite(&score->victory, sizeof(int), 1, fp);
643 		fwrite(&score->stats->HP, sizeof(Sint32), 1, fp);
644 		fwrite(&score->stats->MAXHP, sizeof(Sint32), 1, fp);
645 		fwrite(&score->stats->MP, sizeof(Sint32), 1, fp);
646 		fwrite(&score->stats->MAXMP, sizeof(Sint32), 1, fp);
647 		fwrite(&score->stats->STR, sizeof(Sint32), 1, fp);
648 		fwrite(&score->stats->DEX, sizeof(Sint32), 1, fp);
649 		fwrite(&score->stats->CON, sizeof(Sint32), 1, fp);
650 		fwrite(&score->stats->INT, sizeof(Sint32), 1, fp);
651 		fwrite(&score->stats->PER, sizeof(Sint32), 1, fp);
652 		fwrite(&score->stats->CHR, sizeof(Sint32), 1, fp);
653 		fwrite(&score->stats->EXP, sizeof(Sint32), 1, fp);
654 		fwrite(&score->stats->LVL, sizeof(Sint32), 1, fp);
655 		fwrite(&score->stats->GOLD, sizeof(Sint32), 1, fp);
656 		fwrite(&score->stats->HUNGER, sizeof(Sint32), 1, fp);
657 		for ( c = 0; c < NUMPROFICIENCIES; c++ )
658 		{
659 			fwrite(&score->stats->PROFICIENCIES[c], sizeof(Sint32), 1, fp);
660 		}
661 		for ( c = 0; c < NUMEFFECTS; c++ )
662 		{
663 			fwrite(&score->stats->EFFECTS[c], sizeof(bool), 1, fp);
664 			fwrite(&score->stats->EFFECTS_TIMERS[c], sizeof(Sint32), 1, fp);
665 		}
666 		for ( c = 0; c < NUM_CONDUCT_CHALLENGES; ++c )
667 		{
668 			fwrite(&score->conductGameChallenges[c], sizeof(Sint32), 1, fp);
669 		}
670 		for ( c = 0; c < NUM_GAMEPLAY_STATISTICS; ++c )
671 		{
672 			fwrite(&score->gameStatistics[c], sizeof(Sint32), 1, fp);
673 		}
674 
675 		// inventory
676 		node_t* node2;
677 		c = list_Size(&score->stats->inventory);
678 		fwrite(&c, sizeof(ItemType), 1, fp);
679 		for ( node2 = score->stats->inventory.first; node2 != NULL; node2 = node2->next )
680 		{
681 			Item* item = (Item*)node2->element;
682 			fwrite(&item->type, sizeof(ItemType), 1, fp);
683 			fwrite(&item->status, sizeof(Status), 1, fp);
684 			fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
685 			fwrite(&item->count, sizeof(Sint16), 1, fp);
686 			fwrite(&item->appearance, sizeof(Uint32), 1, fp);
687 			fwrite(&item->identified, sizeof(bool), 1, fp);
688 		}
689 		if ( score->stats->helmet )
690 		{
691 			c = list_Index(score->stats->helmet->node);
692 			fwrite(&c, sizeof(ItemType), 1, fp);
693 		}
694 		else
695 		{
696 			c = list_Size(&score->stats->inventory);
697 			fwrite(&c, sizeof(ItemType), 1, fp);
698 		}
699 		if ( score->stats->breastplate )
700 		{
701 			c = list_Index(score->stats->breastplate->node);
702 			fwrite(&c, sizeof(ItemType), 1, fp);
703 		}
704 		else
705 		{
706 			c = list_Size(&score->stats->inventory);
707 			fwrite(&c, sizeof(ItemType), 1, fp);
708 		}
709 		if ( score->stats->gloves )
710 		{
711 			c = list_Index(score->stats->gloves->node);
712 			fwrite(&c, sizeof(ItemType), 1, fp);
713 		}
714 		else
715 		{
716 			c = list_Size(&score->stats->inventory);
717 			fwrite(&c, sizeof(ItemType), 1, fp);
718 		}
719 		if ( score->stats->shoes )
720 		{
721 			c = list_Index(score->stats->shoes->node);
722 			fwrite(&c, sizeof(ItemType), 1, fp);
723 		}
724 		else
725 		{
726 			c = list_Size(&score->stats->inventory);
727 			fwrite(&c, sizeof(ItemType), 1, fp);
728 		}
729 		if ( score->stats->shield )
730 		{
731 			c = list_Index(score->stats->shield->node);
732 			fwrite(&c, sizeof(ItemType), 1, fp);
733 		}
734 		else
735 		{
736 			c = list_Size(&score->stats->inventory);
737 			fwrite(&c, sizeof(ItemType), 1, fp);
738 		}
739 		if ( score->stats->weapon )
740 		{
741 			c = list_Index(score->stats->weapon->node);
742 			fwrite(&c, sizeof(ItemType), 1, fp);
743 		}
744 		else
745 		{
746 			c = list_Size(&score->stats->inventory);
747 			fwrite(&c, sizeof(ItemType), 1, fp);
748 		}
749 		if ( score->stats->cloak )
750 		{
751 			c = list_Index(score->stats->cloak->node);
752 			fwrite(&c, sizeof(ItemType), 1, fp);
753 		}
754 		else
755 		{
756 			c = list_Size(&score->stats->inventory);
757 			fwrite(&c, sizeof(ItemType), 1, fp);
758 		}
759 		if ( score->stats->amulet )
760 		{
761 			c = list_Index(score->stats->amulet->node);
762 			fwrite(&c, sizeof(ItemType), 1, fp);
763 		}
764 		else
765 		{
766 			c = list_Size(&score->stats->inventory);
767 			fwrite(&c, sizeof(ItemType), 1, fp);
768 		}
769 		if ( score->stats->ring )
770 		{
771 			c = list_Index(score->stats->ring->node);
772 			fwrite(&c, sizeof(ItemType), 1, fp);
773 		}
774 		else
775 		{
776 			c = list_Size(&score->stats->inventory);
777 			fwrite(&c, sizeof(ItemType), 1, fp);
778 		}
779 		if ( score->stats->mask )
780 		{
781 			c = list_Index(score->stats->mask->node);
782 			fwrite(&c, sizeof(ItemType), 1, fp);
783 		}
784 		else
785 		{
786 			c = list_Size(&score->stats->inventory);
787 			fwrite(&c, sizeof(ItemType), 1, fp);
788 		}
789 	}
790 
791 	fclose(fp);
792 }
793 
794 /*-------------------------------------------------------------------------------
795 
796 	loadAllScores
797 
798 	loads all highscores from the scores data file
799 
800 -------------------------------------------------------------------------------*/
801 
loadAllScores(const std::string & scoresfilename)802 void loadAllScores(const std::string& scoresfilename)
803 {
804 	FILE* fp;
805 	Uint32 c, i;
806 	char path[PATH_MAX] = "";
807 	completePath(path, scoresfilename.c_str(), outputdir);
808 
809 	// clear top scores
810 	if ( scoresfilename.compare(SCORESFILE) == 0 )
811 	{
812 		list_FreeAll(&topscores);
813 	}
814 	else
815 	{
816 		list_FreeAll(&topscoresMultiplayer);
817 	}
818 
819 	// open file
820 	if ( (fp = fopen(path, "rb")) == NULL )
821 	{
822 		return;
823 	}
824 
825 	// magic number
826 	char checkstr[64];
827 	fread(checkstr, sizeof(char), strlen("BARONYSCORES"), fp);
828 	if ( strncmp(checkstr, "BARONYSCORES", strlen("BARONYSCORES")) )
829 	{
830 		printlog("error: '%s' is corrupt!\n", scoresfilename.c_str());
831 		fclose(fp);
832 		return;
833 	}
834 
835 	fread(checkstr, sizeof(char), strlen(VERSION), fp);
836 
837 	int versionNumber = 300;
838 	char versionStr[4] = "000";
839 	i = 0;
840 	for ( int j = 0; j < strlen(VERSION); ++j )
841 	{
842 		if ( checkstr[j] >= '0' && checkstr[j] <= '9' )
843 		{
844 			versionStr[i] = checkstr[j]; // copy all integers into versionStr.
845 			++i;
846 			if ( i == 3 )
847 			{
848 				versionStr[i] = '\0';
849 				break; // written 3 characters, add termination and break loop.
850 			}
851 		}
852 	}
853 	versionNumber = atoi(versionStr); // convert from string to int.
854 	printlog("notice: '%s' version number %d", scoresfilename.c_str(), versionNumber);
855 	if ( versionNumber < 200 || versionNumber > 999 )
856 	{
857 		// if version number less than v2.0.0, or more than 3 digits, abort and rebuild scores file.
858 		printlog("error: '%s' is corrupt!\n", scoresfilename.c_str());
859 		fclose(fp);
860 		return;
861 	}
862 
863 	// header info
864 	list_FreeAll(&booksRead);
865 	fread(&c, sizeof(Uint32), 1, fp);
866 	for ( i = 0; i < c; i++ )
867 	{
868 		Uint32 booknamelen = 0;
869 		fread(&booknamelen, sizeof(Uint32), 1, fp);
870 		fgets(tempstr, booknamelen + 1, fp);
871 
872 		char* book = (char*) malloc(sizeof(char) * (strlen(tempstr) + 1));
873 		strcpy(book, tempstr);
874 
875 		node_t* node = list_AddNodeLast(&booksRead);
876 		node->element = book;
877 		node->size = sizeof(char) * (strlen(tempstr) + 1);
878 		node->deconstructor = &defaultDeconstructor;
879 	}
880 	for ( c = 0; c < NUMCLASSES; c++ )
881 	{
882 		if ( versionNumber < 300 )
883 		{
884 			if ( c < 10 )
885 			{
886 				fread(&usedClass[c], sizeof(bool), 1, fp);
887 			}
888 			else
889 			{
890 				usedClass[c] = false;
891 			}
892 		}
893 		else if ( versionNumber < 323 )
894 		{
895 			if ( c < 13 )
896 			{
897 				fread(&usedClass[c], sizeof(bool), 1, fp);
898 			}
899 			else
900 			{
901 				usedClass[c] = false;
902 			}
903 		}
904 		else
905 		{
906 			fread(&usedClass[c], sizeof(bool), 1, fp);
907 		}
908 	}
909 
910 	for ( c = 0; c < NUMRACES; c++ )
911 	{
912 		if ( versionNumber <= 325 )
913 		{
914 			// don't read race info.
915 			usedRace[c] = false;
916 		}
917 		else
918 		{
919 			fread(&usedRace[c], sizeof(bool), 1, fp);
920 		}
921 	}
922 
923 	// read scores
924 	Uint32 numscores = 0;
925 	fread(&numscores, sizeof(Uint32), 1, fp);
926 	for ( i = 0; i < numscores; i++ )
927 	{
928 		node_t* node = nullptr;
929 		if ( scoresfilename.compare(SCORESFILE) == 0 )
930 		{
931 			node = list_AddNodeLast(&topscores);
932 		}
933 		else
934 		{
935 			node = list_AddNodeLast(&topscoresMultiplayer);
936 		}
937 		score_t* score = (score_t*) malloc(sizeof(score_t));
938 		if ( !score )
939 		{
940 			printlog( "failed to allocate memory for new score!\n" );
941 			exit(1);
942 		}
943 		// Stat set to 0 as monster type not needed, values will be overwritten by the savegame data
944 		score->stats = new Stat(0);
945 		if ( !score->stats )
946 		{
947 			printlog( "failed to allocate memory for new stat!\n" );
948 			exit(1);
949 		}
950 		node->element = score;
951 		node->deconstructor = &scoreDeconstructor;
952 		node->size = sizeof(score_t);
953 
954 		if ( versionNumber < 300 )
955 		{
956 			// legacy nummonsters
957 			for ( c = 0; c < NUMMONSTERS; c++ )
958 			{
959 				if ( c < 21 )
960 				{
961 					fread(&score->kills[c], sizeof(Sint32), 1, fp);
962 				}
963 				else
964 				{
965 					score->kills[c] = 0;
966 				}
967 			}
968 		}
969 		else if ( versionNumber < 325 )
970 		{
971 			// legacy nummonsters
972 			for ( c = 0; c < NUMMONSTERS; c++ )
973 			{
974 				if ( c < 33 )
975 				{
976 					fread(&score->kills[c], sizeof(Sint32), 1, fp);
977 				}
978 				else
979 				{
980 					score->kills[c] = 0;
981 				}
982 			}
983 		}
984 		else
985 		{
986 			for ( c = 0; c < NUMMONSTERS; c++ )
987 			{
988 				fread(&score->kills[c], sizeof(Sint32), 1, fp);
989 			}
990 		}
991 		fread(&score->completionTime, sizeof(Uint32), 1, fp);
992 		fread(&score->conductPenniless, sizeof(bool), 1, fp);
993 		fread(&score->conductFoodless, sizeof(bool), 1, fp);
994 		fread(&score->conductVegetarian, sizeof(bool), 1, fp);
995 		fread(&score->conductIlliterate, sizeof(bool), 1, fp);
996 		fread(&score->stats->type, sizeof(Monster), 1, fp);
997 		fread(&score->stats->sex, sizeof(sex_t), 1, fp);
998 		fread(&score->stats->appearance, sizeof(Uint32), 1, fp);
999 		if ( versionNumber >= 323 )
1000 		{
1001 			score->stats->playerRace = ((score->stats->appearance & 0xFF00) >> 8);
1002 			score->stats->appearance = (score->stats->appearance & 0xFF);
1003 		}
1004 		fread(&score->stats->name, sizeof(char), 32, fp);
1005 		fread(&score->classnum, sizeof(Sint32), 1, fp);
1006 		fread(&score->dungeonlevel, sizeof(Sint32), 1, fp);
1007 		fread(&score->victory, sizeof(int), 1, fp);
1008 		fread(&score->stats->HP, sizeof(Sint32), 1, fp);
1009 		fread(&score->stats->MAXHP, sizeof(Sint32), 1, fp);
1010 		fread(&score->stats->MP, sizeof(Sint32), 1, fp);
1011 		fread(&score->stats->MAXMP, sizeof(Sint32), 1, fp);
1012 		fread(&score->stats->STR, sizeof(Sint32), 1, fp);
1013 		fread(&score->stats->DEX, sizeof(Sint32), 1, fp);
1014 		fread(&score->stats->CON, sizeof(Sint32), 1, fp);
1015 		fread(&score->stats->INT, sizeof(Sint32), 1, fp);
1016 		fread(&score->stats->PER, sizeof(Sint32), 1, fp);
1017 		fread(&score->stats->CHR, sizeof(Sint32), 1, fp);
1018 		fread(&score->stats->EXP, sizeof(Sint32), 1, fp);
1019 		fread(&score->stats->LVL, sizeof(Sint32), 1, fp);
1020 		fread(&score->stats->GOLD, sizeof(Sint32), 1, fp);
1021 		fread(&score->stats->HUNGER, sizeof(Sint32), 1, fp);
1022 		for ( c = 0; c < NUMPROFICIENCIES; c++ )
1023 		{
1024 			if ( versionNumber < 323 && c >= PRO_UNARMED )
1025 			{
1026 				score->stats->PROFICIENCIES[c] = 0;
1027 			}
1028 			else
1029 			{
1030 				fread(&score->stats->PROFICIENCIES[c], sizeof(Sint32), 1, fp);
1031 			}
1032 		}
1033 		if ( versionNumber < 300 )
1034 		{
1035 			// legacy effects
1036 			for ( c = 0; c < NUMEFFECTS; c++ )
1037 			{
1038 				if ( c < 16 )
1039 				{
1040 					fread(&score->stats->EFFECTS[c], sizeof(bool), 1, fp);
1041 					fread(&score->stats->EFFECTS_TIMERS[c], sizeof(Sint32), 1, fp);
1042 				}
1043 				else
1044 				{
1045 					score->stats->EFFECTS[c] = false;
1046 					score->stats->EFFECTS_TIMERS[c] = 0;
1047 				}
1048 			}
1049 		}
1050 		else if ( versionNumber < 302 )
1051 		{
1052 			for ( c = 0; c < NUMEFFECTS; c++ )
1053 			{
1054 				if ( c < 19 )
1055 				{
1056 					fread(&score->stats->EFFECTS[c], sizeof(bool), 1, fp);
1057 					fread(&score->stats->EFFECTS_TIMERS[c], sizeof(Sint32), 1, fp);
1058 				}
1059 				else
1060 				{
1061 					score->stats->EFFECTS[c] = false;
1062 					score->stats->EFFECTS_TIMERS[c] = 0;
1063 				}
1064 			}
1065 		}
1066 		else if ( versionNumber <= 323 )
1067 		{
1068 			for ( c = 0; c < NUMEFFECTS; c++ )
1069 			{
1070 				if ( c < 32 )
1071 				{
1072 					fread(&score->stats->EFFECTS[c], sizeof(bool), 1, fp);
1073 					fread(&score->stats->EFFECTS_TIMERS[c], sizeof(Sint32), 1, fp);
1074 				}
1075 				else
1076 				{
1077 					score->stats->EFFECTS[c] = false;
1078 					score->stats->EFFECTS_TIMERS[c] = 0;
1079 				}
1080 			}
1081 		}
1082 		else
1083 		{
1084 			for ( c = 0; c < NUMEFFECTS; c++ )
1085 			{
1086 				fread(&score->stats->EFFECTS[c], sizeof(bool), 1, fp);
1087 				fread(&score->stats->EFFECTS_TIMERS[c], sizeof(Sint32), 1, fp);
1088 			}
1089 		}
1090 
1091 		if ( versionNumber >= 310 )
1092 		{
1093 			for ( c = 0; c < NUM_CONDUCT_CHALLENGES; ++c )
1094 			{
1095 				fread(&score->conductGameChallenges[c], sizeof(Sint32), 1, fp);
1096 			}
1097 			for ( c = 0; c < NUM_GAMEPLAY_STATISTICS; ++c )
1098 			{
1099 				fread(&score->gameStatistics[c], sizeof(Sint32), 1, fp);
1100 			}
1101 		}
1102 		else
1103 		{
1104 			for ( c = 0; c < NUM_CONDUCT_CHALLENGES; ++c )
1105 			{
1106 				score->conductGameChallenges[c] = 0;
1107 			}
1108 		}
1109 		score->stats->leader_uid = 0;
1110 		score->stats->FOLLOWERS.first = NULL;
1111 		score->stats->FOLLOWERS.last = NULL;
1112 		score->stats->stache_x1 = 0;
1113 		score->stats->stache_x2 = 0;
1114 		score->stats->stache_y1 = 0;
1115 		score->stats->stache_y2 = 0;
1116 
1117 		// inventory
1118 		int numitems = 0;
1119 		fread(&numitems, sizeof(Uint32), 1, fp);
1120 		score->stats->inventory.first = NULL;
1121 		score->stats->inventory.last = NULL;
1122 		for ( c = 0; c < numitems; c++ )
1123 		{
1124 			ItemType type;
1125 			Status status;
1126 			Sint16 beatitude;
1127 			Sint16 count;
1128 			Uint32 appearance;
1129 			bool identified;
1130 			fread(&type, sizeof(ItemType), 1, fp);
1131 			fread(&status, sizeof(Status), 1, fp);
1132 			fread(&beatitude, sizeof(Sint16), 1, fp);
1133 			fread(&count, sizeof(Sint16), 1, fp);
1134 			fread(&appearance, sizeof(Uint32), 1, fp);
1135 			fread(&identified, sizeof(bool), 1, fp);
1136 			newItem(type, status, beatitude, count, appearance, identified, &score->stats->inventory);
1137 		}
1138 		fread(&c, sizeof(Uint32), 1, fp);
1139 		node = list_Node(&score->stats->inventory, c);
1140 		if ( node )
1141 		{
1142 			score->stats->helmet = (Item*)node->element;
1143 		}
1144 		else
1145 		{
1146 			score->stats->helmet = NULL;
1147 		}
1148 		fread(&c, sizeof(Uint32), 1, fp);
1149 		node = list_Node(&score->stats->inventory, c);
1150 		if ( node )
1151 		{
1152 			score->stats->breastplate = (Item*)node->element;
1153 		}
1154 		else
1155 		{
1156 			score->stats->breastplate = NULL;
1157 		}
1158 		fread(&c, sizeof(Uint32), 1, fp);
1159 		node = list_Node(&score->stats->inventory, c);
1160 		if ( node )
1161 		{
1162 			score->stats->gloves = (Item*)node->element;
1163 		}
1164 		else
1165 		{
1166 			score->stats->gloves = NULL;
1167 		}
1168 		fread(&c, sizeof(Uint32), 1, fp);
1169 		node = list_Node(&score->stats->inventory, c);
1170 		if ( node )
1171 		{
1172 			score->stats->shoes = (Item*)node->element;
1173 		}
1174 		else
1175 		{
1176 			score->stats->shoes = NULL;
1177 		}
1178 		fread(&c, sizeof(Uint32), 1, fp);
1179 		node = list_Node(&score->stats->inventory, c);
1180 		if ( node )
1181 		{
1182 			score->stats->shield = (Item*)node->element;
1183 		}
1184 		else
1185 		{
1186 			score->stats->shield = NULL;
1187 		}
1188 		fread(&c, sizeof(Uint32), 1, fp);
1189 		node = list_Node(&score->stats->inventory, c);
1190 		if ( node )
1191 		{
1192 			score->stats->weapon = (Item*)node->element;
1193 		}
1194 		else
1195 		{
1196 			score->stats->weapon = NULL;
1197 		}
1198 		fread(&c, sizeof(Uint32), 1, fp);
1199 		node = list_Node(&score->stats->inventory, c);
1200 		if ( node )
1201 		{
1202 			score->stats->cloak = (Item*)node->element;
1203 		}
1204 		else
1205 		{
1206 			score->stats->cloak = NULL;
1207 		}
1208 		fread(&c, sizeof(Uint32), 1, fp);
1209 		node = list_Node(&score->stats->inventory, c);
1210 		if ( node )
1211 		{
1212 			score->stats->amulet = (Item*)node->element;
1213 		}
1214 		else
1215 		{
1216 			score->stats->amulet = NULL;
1217 		}
1218 		fread(&c, sizeof(Uint32), 1, fp);
1219 		node = list_Node(&score->stats->inventory, c);
1220 		if ( node )
1221 		{
1222 			score->stats->ring = (Item*)node->element;
1223 		}
1224 		else
1225 		{
1226 			score->stats->ring = NULL;
1227 		}
1228 		fread(&c, sizeof(Uint32), 1, fp);
1229 		node = list_Node(&score->stats->inventory, c);
1230 		if ( node )
1231 		{
1232 			score->stats->mask = (Item*)node->element;
1233 		}
1234 		else
1235 		{
1236 			score->stats->mask = NULL;
1237 		}
1238 
1239 		score->stats->monster_sound = NULL;
1240 		score->stats->monster_idlevar = 0;
1241 	}
1242 
1243 	fclose(fp);
1244 }
1245 
1246 /*-------------------------------------------------------------------------------
1247 
1248 	saveGame
1249 
1250 	Saves the player character as they were at the start of the
1251 	last level
1252 
1253 -------------------------------------------------------------------------------*/
1254 
saveGame(int saveIndex)1255 int saveGame(int saveIndex)
1256 {
1257 	if ( gameModeManager.getMode() != GameModeManager_t::GAME_MODE_DEFAULT )
1258 	{
1259 		return 1;
1260 	}
1261 
1262 	int player;
1263 	node_t* node;
1264 	FILE* fp;
1265 	Sint32 c;
1266 	char savefile[PATH_MAX] = "";
1267 	char path[PATH_MAX] = "";
1268 
1269 	// open file
1270 	if ( !intro )
1271 	{
1272 		messagePlayer(clientnum, language[1121]);
1273 	}
1274 
1275 	if ( multiplayer == SINGLE )
1276 	{
1277 		strncpy(savefile, setSaveGameFileName(true, false, saveIndex).c_str(), PATH_MAX - 1);
1278 	}
1279 	else
1280 	{
1281 		strncpy(savefile, setSaveGameFileName(false, false, saveIndex).c_str(), PATH_MAX - 1);
1282 	}
1283 	completePath(path, savefile, outputdir);
1284 
1285 	if ( (fp = fopen(path, "wb")) == NULL )
1286 	{
1287 		printlog("warning: failed to save '%s'!\n", path);
1288 		return 1;
1289 	}
1290 
1291 	// write header info
1292 	fprintf(fp, "BARONYSAVEGAME");
1293 	fprintf(fp, VERSION);
1294 	fwrite(&uniqueGameKey, sizeof(Uint32), 1, fp);
1295 	if ( multiplayer > SINGLE && directConnect )
1296 	{
1297 		multiplayer += 2;
1298 		fwrite(&multiplayer, sizeof(Uint32), 1, fp);
1299 		multiplayer -= 2;
1300 	}
1301 	else
1302 	{
1303 		if ( multiplayer == SERVER && LobbyHandler.hostingType == LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY )
1304 		{
1305 			multiplayer = SERVERCROSSPLAY;
1306 			fwrite(&multiplayer, sizeof(Uint32), 1, fp);
1307 			multiplayer = SERVER;
1308 		}
1309 		else
1310 		{
1311 			fwrite(&multiplayer, sizeof(Uint32), 1, fp);
1312 		}
1313 	}
1314 	Uint32 hash = 0;
1315 #ifdef WINDOWS
1316 	struct _stat result;
1317 	if ( _stat(path, &result) == 0 )
1318 	{
1319 		struct tm *tm = localtime(&result.st_mtime);
1320 		if ( tm )
1321 		{
1322 			hash = tm->tm_hour + tm->tm_mday * tm->tm_year + tm->tm_wday + tm->tm_yday;
1323 		}
1324 	}
1325 #else
1326 	struct stat result;
1327 	if ( stat(path, &result) == 0 )
1328 	{
1329 		struct tm *tm = localtime(&result.st_mtime);
1330 		if ( tm )
1331 		{
1332 			hash = tm->tm_hour + tm->tm_mday * tm->tm_year + tm->tm_wday + tm->tm_yday;
1333 		}
1334 	}
1335 #endif // WINDOWS
1336 	hash += (stats[clientnum]->STR + stats[clientnum]->LVL + stats[clientnum]->DEX * stats[clientnum]->INT);
1337 	hash += (stats[clientnum]->CON * stats[clientnum]->PER + std::min(stats[clientnum]->GOLD, 5000) - stats[clientnum]->CON);
1338 	hash += (stats[clientnum]->HP - stats[clientnum]->MP);
1339 	hash += (currentlevel);
1340 	Uint32 writeCurrentLevel = (hash << 8);
1341 	writeCurrentLevel |= (currentlevel & 0xFF);
1342 
1343 	fwrite(&clientnum, sizeof(Uint32), 1, fp);
1344 	fwrite(&mapseed, sizeof(Uint32), 1, fp);
1345 	fwrite(&writeCurrentLevel, sizeof(Uint32), 1, fp);
1346 	fwrite(&secretlevel, sizeof(bool), 1, fp);
1347 	fwrite(&completionTime, sizeof(Uint32), 1, fp);
1348 	fwrite(&conductPenniless, sizeof(bool), 1, fp);
1349 	fwrite(&conductFoodless, sizeof(bool), 1, fp);
1350 	fwrite(&conductVegetarian, sizeof(bool), 1, fp);
1351 	fwrite(&conductIlliterate, sizeof(bool), 1, fp);
1352 	for ( c = 0; c < NUM_CONDUCT_CHALLENGES; ++c )
1353 	{
1354 		fwrite(&conductGameChallenges[c], sizeof(Sint32), 1, fp);
1355 	}
1356 	for ( c = 0; c < NUM_GAMEPLAY_STATISTICS; ++c )
1357 	{
1358 		fwrite(&gameStatistics[c], sizeof(Sint32), 1, fp);
1359 	}
1360 	fwrite(&svFlags, sizeof(Uint32), 1, fp);
1361 
1362 	// write hotbar items
1363 	for ( c = 0; c < NUM_HOTBAR_SLOTS; c++ )
1364 	{
1365 		int index = list_Size(&stats[clientnum]->inventory);
1366 		Item* item = uidToItem(hotbar[c].item);
1367 		if ( item )
1368 		{
1369 			index = list_Index(item->node);
1370 		}
1371 		fwrite(&index, sizeof(Uint32), 1, fp);
1372 	}
1373 
1374 	// write spells
1375 	Uint32 numspells = list_Size(&spellList);
1376 	fwrite(&numspells, sizeof(Uint32), 1, fp);
1377 	for ( node = spellList.first; node != NULL; node = node->next )
1378 	{
1379 		spell_t* spell = (spell_t*)node->element;
1380 		fwrite(&spell->ID, sizeof(Uint32), 1, fp);
1381 	}
1382 
1383 
1384 	// player data
1385 	for ( player = 0; player < MAXPLAYERS; player++ )
1386 	{
1387 		fwrite(&client_classes[player], sizeof(Uint32), 1, fp);
1388 		for ( c = 0; c < NUMMONSTERS; c++ )
1389 		{
1390 			fwrite(&kills[c], sizeof(Sint32), 1, fp);
1391 		}
1392 		fwrite(&stats[player]->type, sizeof(Monster), 1, fp);
1393 		fwrite(&stats[player]->sex, sizeof(sex_t), 1, fp);
1394 		Uint32 raceAndAppearance = 0;
1395 		raceAndAppearance |= (stats[player]->playerRace << 8);
1396 		raceAndAppearance |= (stats[player]->appearance);
1397 		fwrite(&raceAndAppearance, sizeof(Uint32), 1, fp);
1398 		fwrite(stats[player]->name, sizeof(char), 32, fp);
1399 		fwrite(&stats[player]->HP, sizeof(Sint32), 1, fp);
1400 		fwrite(&stats[player]->MAXHP, sizeof(Sint32), 1, fp);
1401 		fwrite(&stats[player]->MP, sizeof(Sint32), 1, fp);
1402 		fwrite(&stats[player]->MAXMP, sizeof(Sint32), 1, fp);
1403 		fwrite(&stats[player]->STR, sizeof(Sint32), 1, fp);
1404 		fwrite(&stats[player]->DEX, sizeof(Sint32), 1, fp);
1405 		fwrite(&stats[player]->CON, sizeof(Sint32), 1, fp);
1406 		fwrite(&stats[player]->INT, sizeof(Sint32), 1, fp);
1407 		fwrite(&stats[player]->PER, sizeof(Sint32), 1, fp);
1408 		fwrite(&stats[player]->CHR, sizeof(Sint32), 1, fp);
1409 		fwrite(&stats[player]->EXP, sizeof(Sint32), 1, fp);
1410 		fwrite(&stats[player]->LVL, sizeof(Sint32), 1, fp);
1411 		fwrite(&stats[player]->GOLD, sizeof(Sint32), 1, fp);
1412 		fwrite(&stats[player]->HUNGER, sizeof(Sint32), 1, fp);
1413 		for ( c = 0; c < NUMPROFICIENCIES; c++ )
1414 		{
1415 			fwrite(&stats[player]->PROFICIENCIES[c], sizeof(Sint32), 1, fp);
1416 		}
1417 		for ( c = 0; c < NUMEFFECTS; c++ )
1418 		{
1419 			fwrite(&stats[player]->EFFECTS[c], sizeof(bool), 1, fp);
1420 			fwrite(&stats[player]->EFFECTS_TIMERS[c], sizeof(Sint32), 1, fp);
1421 		}
1422 		for ( c = 0; c < 32; c++ )
1423 		{
1424 			fwrite(&stats[player]->MISC_FLAGS[c], sizeof(Sint32), 1, fp);
1425 		}
1426 
1427 		// inventory
1428 		if ( player == clientnum )
1429 		{
1430 			c = list_Size(&stats[player]->inventory);
1431 			fwrite(&c, sizeof(Uint32), 1, fp);
1432 			for ( node = stats[player]->inventory.first; node != NULL; node = node->next )
1433 			{
1434 				Item* item = (Item*)node->element;
1435 				fwrite(&item->type, sizeof(ItemType), 1, fp);
1436 				fwrite(&item->status, sizeof(Status), 1, fp);
1437 				fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1438 				fwrite(&item->count, sizeof(Sint16), 1, fp);
1439 				fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1440 				fwrite(&item->identified, sizeof(bool), 1, fp);
1441 				fwrite(&item->x, sizeof(Sint32), 1, fp);
1442 				fwrite(&item->y, sizeof(Sint32), 1, fp);
1443 			}
1444 			if ( stats[player]->helmet )
1445 			{
1446 				c = list_Index(stats[player]->helmet->node);
1447 				fwrite(&c, sizeof(Uint32), 1, fp);
1448 			}
1449 			else
1450 			{
1451 				c = list_Size(&stats[player]->inventory);
1452 				fwrite(&c, sizeof(Uint32), 1, fp);
1453 			}
1454 			if ( stats[player]->breastplate )
1455 			{
1456 				c = list_Index(stats[player]->breastplate->node);
1457 				fwrite(&c, sizeof(Uint32), 1, fp);
1458 			}
1459 			else
1460 			{
1461 				c = list_Size(&stats[player]->inventory);
1462 				fwrite(&c, sizeof(Uint32), 1, fp);
1463 			}
1464 			if ( stats[player]->gloves )
1465 			{
1466 				c = list_Index(stats[player]->gloves->node);
1467 				fwrite(&c, sizeof(Uint32), 1, fp);
1468 			}
1469 			else
1470 			{
1471 				c = list_Size(&stats[player]->inventory);
1472 				fwrite(&c, sizeof(Uint32), 1, fp);
1473 			}
1474 			if ( stats[player]->shoes )
1475 			{
1476 				c = list_Index(stats[player]->shoes->node);
1477 				fwrite(&c, sizeof(Uint32), 1, fp);
1478 			}
1479 			else
1480 			{
1481 				c = list_Size(&stats[player]->inventory);
1482 				fwrite(&c, sizeof(Uint32), 1, fp);
1483 			}
1484 			if ( stats[player]->shield )
1485 			{
1486 				c = list_Index(stats[player]->shield->node);
1487 				fwrite(&c, sizeof(Uint32), 1, fp);
1488 			}
1489 			else
1490 			{
1491 				c = list_Size(&stats[player]->inventory);
1492 				fwrite(&c, sizeof(Uint32), 1, fp);
1493 			}
1494 			if ( stats[player]->weapon )
1495 			{
1496 				c = list_Index(stats[player]->weapon->node);
1497 				fwrite(&c, sizeof(Uint32), 1, fp);
1498 			}
1499 			else
1500 			{
1501 				c = list_Size(&stats[player]->inventory);
1502 				fwrite(&c, sizeof(Uint32), 1, fp);
1503 			}
1504 			if ( stats[player]->cloak )
1505 			{
1506 				c = list_Index(stats[player]->cloak->node);
1507 				fwrite(&c, sizeof(Uint32), 1, fp);
1508 			}
1509 			else
1510 			{
1511 				c = list_Size(&stats[player]->inventory);
1512 				fwrite(&c, sizeof(Uint32), 1, fp);
1513 			}
1514 			if ( stats[player]->amulet )
1515 			{
1516 				c = list_Index(stats[player]->amulet->node);
1517 				fwrite(&c, sizeof(Uint32), 1, fp);
1518 			}
1519 			else
1520 			{
1521 				c = list_Size(&stats[player]->inventory);
1522 				fwrite(&c, sizeof(Uint32), 1, fp);
1523 			}
1524 			if ( stats[player]->ring )
1525 			{
1526 				c = list_Index(stats[player]->ring->node);
1527 				fwrite(&c, sizeof(Uint32), 1, fp);
1528 			}
1529 			else
1530 			{
1531 				c = list_Size(&stats[player]->inventory);
1532 				fwrite(&c, sizeof(Uint32), 1, fp);
1533 			}
1534 			if ( stats[player]->mask )
1535 			{
1536 				c = list_Index(stats[player]->mask->node);
1537 				fwrite(&c, sizeof(Uint32), 1, fp);
1538 			}
1539 			else
1540 			{
1541 				c = list_Size(&stats[player]->inventory);
1542 				fwrite(&c, sizeof(Uint32), 1, fp);
1543 			}
1544 		}
1545 		else
1546 		{
1547 			if ( multiplayer == SERVER )
1548 			{
1549 				if ( stats[player]->helmet )
1550 				{
1551 					Item* item = stats[player]->helmet;
1552 					fwrite(&item->type, sizeof(ItemType), 1, fp);
1553 					fwrite(&item->status, sizeof(Status), 1, fp);
1554 					fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1555 					fwrite(&item->count, sizeof(Sint16), 1, fp);
1556 					fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1557 					fwrite(&item->identified, sizeof(bool), 1, fp);
1558 				}
1559 				else
1560 				{
1561 					c = NUMITEMS;
1562 					fwrite(&c, sizeof(ItemType), 1, fp);
1563 				}
1564 				if ( stats[player]->breastplate )
1565 				{
1566 					Item* item = stats[player]->breastplate;
1567 					fwrite(&item->type, sizeof(ItemType), 1, fp);
1568 					fwrite(&item->status, sizeof(Status), 1, fp);
1569 					fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1570 					fwrite(&item->count, sizeof(Sint16), 1, fp);
1571 					fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1572 					fwrite(&item->identified, sizeof(bool), 1, fp);
1573 				}
1574 				else
1575 				{
1576 					c = NUMITEMS;
1577 					fwrite(&c, sizeof(ItemType), 1, fp);
1578 				}
1579 				if ( stats[player]->gloves )
1580 				{
1581 					Item* item = stats[player]->gloves;
1582 					fwrite(&item->type, sizeof(ItemType), 1, fp);
1583 					fwrite(&item->status, sizeof(Status), 1, fp);
1584 					fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1585 					fwrite(&item->count, sizeof(Sint16), 1, fp);
1586 					fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1587 					fwrite(&item->identified, sizeof(bool), 1, fp);
1588 				}
1589 				else
1590 				{
1591 					c = NUMITEMS;
1592 					fwrite(&c, sizeof(ItemType), 1, fp);
1593 				}
1594 				if ( stats[player]->shoes )
1595 				{
1596 					Item* item = stats[player]->shoes;
1597 					fwrite(&item->type, sizeof(ItemType), 1, fp);
1598 					fwrite(&item->status, sizeof(Status), 1, fp);
1599 					fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1600 					fwrite(&item->count, sizeof(Sint16), 1, fp);
1601 					fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1602 					fwrite(&item->identified, sizeof(bool), 1, fp);
1603 				}
1604 				else
1605 				{
1606 					c = NUMITEMS;
1607 					fwrite(&c, sizeof(ItemType), 1, fp);
1608 				}
1609 				if ( stats[player]->shield )
1610 				{
1611 					Item* item = stats[player]->shield;
1612 					fwrite(&item->type, sizeof(ItemType), 1, fp);
1613 					fwrite(&item->status, sizeof(Status), 1, fp);
1614 					fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1615 					fwrite(&item->count, sizeof(Sint16), 1, fp);
1616 					fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1617 					fwrite(&item->identified, sizeof(bool), 1, fp);
1618 				}
1619 				else
1620 				{
1621 					c = NUMITEMS;
1622 					fwrite(&c, sizeof(ItemType), 1, fp);
1623 				}
1624 				if ( stats[player]->weapon )
1625 				{
1626 					Item* item = stats[player]->weapon;
1627 					fwrite(&item->type, sizeof(ItemType), 1, fp);
1628 					fwrite(&item->status, sizeof(Status), 1, fp);
1629 					fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1630 					fwrite(&item->count, sizeof(Sint16), 1, fp);
1631 					fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1632 					fwrite(&item->identified, sizeof(bool), 1, fp);
1633 				}
1634 				else
1635 				{
1636 					c = NUMITEMS;
1637 					fwrite(&c, sizeof(ItemType), 1, fp);
1638 				}
1639 				if ( stats[player]->cloak )
1640 				{
1641 					Item* item = stats[player]->cloak;
1642 					fwrite(&item->type, sizeof(ItemType), 1, fp);
1643 					fwrite(&item->status, sizeof(Status), 1, fp);
1644 					fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1645 					fwrite(&item->count, sizeof(Sint16), 1, fp);
1646 					fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1647 					fwrite(&item->identified, sizeof(bool), 1, fp);
1648 				}
1649 				else
1650 				{
1651 					c = NUMITEMS;
1652 					fwrite(&c, sizeof(ItemType), 1, fp);
1653 				}
1654 				if ( stats[player]->amulet )
1655 				{
1656 					Item* item = stats[player]->amulet;
1657 					fwrite(&item->type, sizeof(ItemType), 1, fp);
1658 					fwrite(&item->status, sizeof(Status), 1, fp);
1659 					fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1660 					fwrite(&item->count, sizeof(Sint16), 1, fp);
1661 					fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1662 					fwrite(&item->identified, sizeof(bool), 1, fp);
1663 				}
1664 				else
1665 				{
1666 					c = NUMITEMS;
1667 					fwrite(&c, sizeof(ItemType), 1, fp);
1668 				}
1669 				if ( stats[player]->ring )
1670 				{
1671 					Item* item = stats[player]->ring;
1672 					fwrite(&item->type, sizeof(ItemType), 1, fp);
1673 					fwrite(&item->status, sizeof(Status), 1, fp);
1674 					fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1675 					fwrite(&item->count, sizeof(Sint16), 1, fp);
1676 					fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1677 					fwrite(&item->identified, sizeof(bool), 1, fp);
1678 				}
1679 				else
1680 				{
1681 					c = NUMITEMS;
1682 					fwrite(&c, sizeof(ItemType), 1, fp);
1683 				}
1684 				if ( stats[player]->mask )
1685 				{
1686 					Item* item = stats[player]->mask;
1687 					fwrite(&item->type, sizeof(ItemType), 1, fp);
1688 					fwrite(&item->status, sizeof(Status), 1, fp);
1689 					fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1690 					fwrite(&item->count, sizeof(Sint16), 1, fp);
1691 					fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1692 					fwrite(&item->identified, sizeof(bool), 1, fp);
1693 				}
1694 				else
1695 				{
1696 					c = NUMITEMS;
1697 					fwrite(&c, sizeof(ItemType), 1, fp);
1698 				}
1699 			}
1700 			else
1701 			{
1702 				c = NUMITEMS;
1703 				fwrite(&c, sizeof(ItemType), 1, fp);
1704 			}
1705 		}
1706 	}
1707 	fclose(fp);
1708 
1709 	// clients don't save follower info
1710 	if ( multiplayer == CLIENT )
1711 	{
1712 		return 0;
1713 	}
1714 
1715 	if ( multiplayer == SINGLE )
1716 	{
1717 		strncpy(savefile, setSaveGameFileName(true, true, saveIndex).c_str(), PATH_MAX - 1);
1718 	}
1719 	else
1720 	{
1721 		strncpy(savefile, setSaveGameFileName(false, true, saveIndex).c_str(), PATH_MAX - 1);
1722 	}
1723 	completePath(path, savefile, outputdir);
1724 
1725 	// now we save the follower information
1726 	if ( (fp = fopen(path, "wb")) == NULL )
1727 	{
1728 		printlog("warning: failed to save '%s'!\n", path);
1729 		return 1;
1730 	}
1731 	fprintf(fp, "BARONYSAVEGAMEFOLLOWERS");
1732 	fprintf(fp, VERSION);
1733 
1734 	// write follower information
1735 	for ( c = 0; c < MAXPLAYERS; c++ )
1736 	{
1737 		// record number of followers for this player
1738 		Uint32 size = list_Size(&stats[c]->FOLLOWERS);
1739 		fwrite(&size, sizeof(Uint32), 1, fp);
1740 
1741 		// get followerStats
1742 		int i;
1743 		for ( i = 0; i < size; i++ )
1744 		{
1745 			node_t* node = list_Node(&stats[c]->FOLLOWERS, i);
1746 			if ( node )
1747 			{
1748 				Entity* follower = uidToEntity(*((Uint32*)node->element));
1749 				Stat* followerStats = (follower) ? follower->getStats() : NULL;
1750 				if ( followerStats )
1751 				{
1752 					// record follower stats
1753 					fwrite(&followerStats->type, sizeof(Monster), 1, fp);
1754 					fwrite(&followerStats->sex, sizeof(sex_t), 1, fp);
1755 					fwrite(&followerStats->appearance, sizeof(Uint32), 1, fp);
1756 					fwrite(followerStats->name, sizeof(char), 32, fp);
1757 					fwrite(&followerStats->HP, sizeof(Sint32), 1, fp);
1758 					fwrite(&followerStats->MAXHP, sizeof(Sint32), 1, fp);
1759 					fwrite(&followerStats->MP, sizeof(Sint32), 1, fp);
1760 					fwrite(&followerStats->MAXMP, sizeof(Sint32), 1, fp);
1761 					fwrite(&followerStats->STR, sizeof(Sint32), 1, fp);
1762 					fwrite(&followerStats->DEX, sizeof(Sint32), 1, fp);
1763 					fwrite(&followerStats->CON, sizeof(Sint32), 1, fp);
1764 					fwrite(&followerStats->INT, sizeof(Sint32), 1, fp);
1765 					fwrite(&followerStats->PER, sizeof(Sint32), 1, fp);
1766 					fwrite(&followerStats->CHR, sizeof(Sint32), 1, fp);
1767 					fwrite(&followerStats->EXP, sizeof(Sint32), 1, fp);
1768 					fwrite(&followerStats->LVL, sizeof(Sint32), 1, fp);
1769 					fwrite(&followerStats->GOLD, sizeof(Sint32), 1, fp);
1770 					fwrite(&followerStats->HUNGER, sizeof(Sint32), 1, fp);
1771 
1772 					int j;
1773 					for ( j = 0; j < NUMPROFICIENCIES; j++ )
1774 					{
1775 						fwrite(&followerStats->PROFICIENCIES[j], sizeof(Sint32), 1, fp);
1776 					}
1777 					for ( j = 0; j < NUMEFFECTS; j++ )
1778 					{
1779 						fwrite(&followerStats->EFFECTS[j], sizeof(bool), 1, fp);
1780 						fwrite(&followerStats->EFFECTS_TIMERS[j], sizeof(Sint32), 1, fp);
1781 					}
1782 					for ( j = 0; j < 32; ++j )
1783 					{
1784 						fwrite(&followerStats->MISC_FLAGS[j], sizeof(Sint32), 1, fp);
1785 					}
1786 
1787 					// record follower inventory
1788 					Uint32 invSize = list_Size(&followerStats->inventory);
1789 					fwrite(&invSize, sizeof(Uint32), 1, fp);
1790 					for ( node = followerStats->inventory.first; node != NULL; node = node->next )
1791 					{
1792 						Item* item = (Item*)node->element;
1793 						fwrite(&item->type, sizeof(ItemType), 1, fp);
1794 						fwrite(&item->status, sizeof(Status), 1, fp);
1795 						fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1796 						fwrite(&item->count, sizeof(Sint16), 1, fp);
1797 						fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1798 						fwrite(&item->identified, sizeof(bool), 1, fp);
1799 						fwrite(&item->x, sizeof(Sint32), 1, fp);
1800 						fwrite(&item->y, sizeof(Sint32), 1, fp);
1801 					}
1802 
1803 					// record follower equipment (since NPCs never store equipment as inventory)
1804 					if ( followerStats->helmet )
1805 					{
1806 						Item* item = followerStats->helmet;
1807 						fwrite(&item->type, sizeof(ItemType), 1, fp);
1808 						fwrite(&item->status, sizeof(Status), 1, fp);
1809 						fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1810 						fwrite(&item->count, sizeof(Sint16), 1, fp);
1811 						fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1812 						fwrite(&item->identified, sizeof(bool), 1, fp);
1813 					}
1814 					else
1815 					{
1816 						ItemType tempItem = static_cast<ItemType>(NUMITEMS);
1817 						fwrite(&tempItem, sizeof(ItemType), 1, fp);
1818 					}
1819 					if ( followerStats->breastplate )
1820 					{
1821 						Item* item = followerStats->breastplate;
1822 						fwrite(&item->type, sizeof(ItemType), 1, fp);
1823 						fwrite(&item->status, sizeof(Status), 1, fp);
1824 						fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1825 						fwrite(&item->count, sizeof(Sint16), 1, fp);
1826 						fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1827 						fwrite(&item->identified, sizeof(bool), 1, fp);
1828 					}
1829 					else
1830 					{
1831 						ItemType tempItem = static_cast<ItemType>(NUMITEMS);
1832 						fwrite(&tempItem, sizeof(ItemType), 1, fp);
1833 					}
1834 					if ( followerStats->gloves )
1835 					{
1836 						Item* item = followerStats->gloves;
1837 						fwrite(&item->type, sizeof(ItemType), 1, fp);
1838 						fwrite(&item->status, sizeof(Status), 1, fp);
1839 						fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1840 						fwrite(&item->count, sizeof(Sint16), 1, fp);
1841 						fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1842 						fwrite(&item->identified, sizeof(bool), 1, fp);
1843 					}
1844 					else
1845 					{
1846 						ItemType tempItem = static_cast<ItemType>(NUMITEMS);
1847 						fwrite(&tempItem, sizeof(ItemType), 1, fp);
1848 					}
1849 					if ( followerStats->shoes )
1850 					{
1851 						Item* item = followerStats->shoes;
1852 						fwrite(&item->type, sizeof(ItemType), 1, fp);
1853 						fwrite(&item->status, sizeof(Status), 1, fp);
1854 						fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1855 						fwrite(&item->count, sizeof(Sint16), 1, fp);
1856 						fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1857 						fwrite(&item->identified, sizeof(bool), 1, fp);
1858 					}
1859 					else
1860 					{
1861 						ItemType tempItem = static_cast<ItemType>(NUMITEMS);
1862 						fwrite(&tempItem, sizeof(ItemType), 1, fp);
1863 					}
1864 					if ( followerStats->shield )
1865 					{
1866 						Item* item = followerStats->shield;
1867 						fwrite(&item->type, sizeof(ItemType), 1, fp);
1868 						fwrite(&item->status, sizeof(Status), 1, fp);
1869 						fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1870 						fwrite(&item->count, sizeof(Sint16), 1, fp);
1871 						fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1872 						fwrite(&item->identified, sizeof(bool), 1, fp);
1873 					}
1874 					else
1875 					{
1876 						ItemType tempItem = static_cast<ItemType>(NUMITEMS);
1877 						fwrite(&tempItem, sizeof(ItemType), 1, fp);
1878 					}
1879 					if ( followerStats->weapon )
1880 					{
1881 						Item* item = followerStats->weapon;
1882 						fwrite(&item->type, sizeof(ItemType), 1, fp);
1883 						fwrite(&item->status, sizeof(Status), 1, fp);
1884 						fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1885 						fwrite(&item->count, sizeof(Sint16), 1, fp);
1886 						fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1887 						fwrite(&item->identified, sizeof(bool), 1, fp);
1888 					}
1889 					else
1890 					{
1891 						ItemType tempItem = static_cast<ItemType>(NUMITEMS);
1892 						fwrite(&tempItem, sizeof(ItemType), 1, fp);
1893 					}
1894 					if ( followerStats->cloak )
1895 					{
1896 						Item* item = followerStats->cloak;
1897 						fwrite(&item->type, sizeof(ItemType), 1, fp);
1898 						fwrite(&item->status, sizeof(Status), 1, fp);
1899 						fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1900 						fwrite(&item->count, sizeof(Sint16), 1, fp);
1901 						fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1902 						fwrite(&item->identified, sizeof(bool), 1, fp);
1903 					}
1904 					else
1905 					{
1906 						ItemType tempItem = static_cast<ItemType>(NUMITEMS);
1907 						fwrite(&tempItem, sizeof(ItemType), 1, fp);
1908 					}
1909 					if ( followerStats->amulet )
1910 					{
1911 						Item* item = followerStats->amulet;
1912 						fwrite(&item->type, sizeof(ItemType), 1, fp);
1913 						fwrite(&item->status, sizeof(Status), 1, fp);
1914 						fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1915 						fwrite(&item->count, sizeof(Sint16), 1, fp);
1916 						fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1917 						fwrite(&item->identified, sizeof(bool), 1, fp);
1918 					}
1919 					else
1920 					{
1921 						ItemType tempItem = static_cast<ItemType>(NUMITEMS);
1922 						fwrite(&tempItem, sizeof(ItemType), 1, fp);
1923 					}
1924 					if ( followerStats->ring )
1925 					{
1926 						Item* item = followerStats->ring;
1927 						fwrite(&item->type, sizeof(ItemType), 1, fp);
1928 						fwrite(&item->status, sizeof(Status), 1, fp);
1929 						fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1930 						fwrite(&item->count, sizeof(Sint16), 1, fp);
1931 						fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1932 						fwrite(&item->identified, sizeof(bool), 1, fp);
1933 					}
1934 					else
1935 					{
1936 						ItemType tempItem = static_cast<ItemType>(NUMITEMS);
1937 						fwrite(&tempItem, sizeof(ItemType), 1, fp);
1938 					}
1939 					if ( followerStats->mask )
1940 					{
1941 						Item* item = followerStats->mask;
1942 						fwrite(&item->type, sizeof(ItemType), 1, fp);
1943 						fwrite(&item->status, sizeof(Status), 1, fp);
1944 						fwrite(&item->beatitude, sizeof(Sint16), 1, fp);
1945 						fwrite(&item->count, sizeof(Sint16), 1, fp);
1946 						fwrite(&item->appearance, sizeof(Uint32), 1, fp);
1947 						fwrite(&item->identified, sizeof(bool), 1, fp);
1948 					}
1949 					else
1950 					{
1951 						ItemType tempItem = static_cast<ItemType>(NUMITEMS);
1952 						fwrite(&tempItem, sizeof(ItemType), 1, fp);
1953 					}
1954 				}
1955 			}
1956 		}
1957 	}
1958 
1959 
1960 	fclose(fp);
1961 	return 0;
1962 }
1963 
1964 /*-------------------------------------------------------------------------------
1965 
1966 	loadGame
1967 
1968 	Loads a character savegame stored in SAVEGAMEFILE
1969 
1970 -------------------------------------------------------------------------------*/
1971 
loadGame(int player,int saveIndex)1972 int loadGame(int player, int saveIndex)
1973 {
1974 	Sint32 mul;
1975 	node_t* node;
1976 	FILE* fp;
1977 	int c;
1978 
1979 	char savefile[PATH_MAX] = "";
1980 	char path[PATH_MAX] = "";
1981 	if ( multiplayer == SINGLE )
1982 	{
1983 		strncpy(savefile, setSaveGameFileName(true, false, saveIndex).c_str(), PATH_MAX - 1);
1984 	}
1985 	else
1986 	{
1987 		strncpy(savefile, setSaveGameFileName(false, false, saveIndex).c_str(), PATH_MAX - 1);
1988 	}
1989 	completePath(path, savefile, outputdir);
1990 
1991 	// open file
1992 	if ( (fp = fopen(path, "rb")) == NULL )
1993 	{
1994 		printlog("error: failed to load '%s'!\n", path);
1995 		return 1;
1996 	}
1997 
1998 	// read from file
1999 	char checkstr[64];
2000 	fread(checkstr, sizeof(char), strlen("BARONYSAVEGAME"), fp);
2001 	if ( strncmp(checkstr, "BARONYSAVEGAME", strlen("BARONYSAVEGAME")) )
2002 	{
2003 		printlog("error: '%s' is corrupt!\n", path);
2004 		fclose(fp);
2005 		return 1;
2006 	}
2007 	fread(checkstr, sizeof(char), strlen(VERSION), fp);
2008 	int versionNumber = getSavegameVersion(checkstr);
2009 	printlog("loadGame: '%s' version number %d", savefile, versionNumber);
2010 	if ( versionNumber == -1 )
2011 	{
2012 		// if getSavegameVersion returned -1, abort.
2013 		printlog("error: '%s' is corrupt!\n", path);
2014 		fclose(fp);
2015 		return 1;
2016 	}
2017 
2018 	// assemble string
2019 	Uint32 hash = 0;
2020 	Uint32 loadedHash = 0;
2021 #ifdef WINDOWS
2022 	struct _stat result;
2023 	if ( _stat(path, &result) == 0 )
2024 	{
2025 		struct tm *tm = localtime(&result.st_mtime);
2026 		if ( tm )
2027 		{
2028 			hash = tm->tm_hour + tm->tm_mday * tm->tm_year + tm->tm_wday + tm->tm_yday;
2029 		}
2030 	}
2031 #else
2032 	struct stat result;
2033 	if ( stat(path, &result) == 0 )
2034 	{
2035 		struct tm *tm = localtime(&result.st_mtime);
2036 		if ( tm )
2037 		{
2038 			hash = tm->tm_hour + tm->tm_mday * tm->tm_year + tm->tm_wday + tm->tm_yday;
2039 		}
2040 	}
2041 #endif // WINDOWS
2042 
2043 	// read basic header info
2044 	fread(&uniqueGameKey, sizeof(Uint32), 1, fp);
2045 	fread(&mul, sizeof(Uint32), 1, fp);
2046 	fread(&clientnum, sizeof(Uint32), 1, fp);
2047 	fread(&mapseed, sizeof(Uint32), 1, fp);
2048 	fread(&currentlevel, sizeof(Uint32), 1, fp);
2049 	if ( versionNumber >= 323 )
2050 	{
2051 		loadedHash = (currentlevel & 0xFFFFFF00) >> 8;
2052 		currentlevel = currentlevel & 0xFF;
2053 	}
2054 	fread(&secretlevel, sizeof(bool), 1, fp);
2055 	fread(&completionTime, sizeof(Uint32), 1, fp);
2056 	fread(&conductPenniless, sizeof(bool), 1, fp);
2057 	fread(&conductFoodless, sizeof(bool), 1, fp);
2058 	fread(&conductVegetarian, sizeof(bool), 1, fp);
2059 	fread(&conductIlliterate, sizeof(bool), 1, fp);
2060 	if ( versionNumber >= 310 )
2061 	{
2062 		for ( c = 0; c < NUM_CONDUCT_CHALLENGES; ++c )
2063 		{
2064 			fread(&conductGameChallenges[c], sizeof(Sint32), 1, fp);
2065 		}
2066 		for ( c = 0; c < NUM_GAMEPLAY_STATISTICS; ++c )
2067 		{
2068 			fread(&gameStatistics[c], sizeof(Sint32), 1, fp);
2069 		}
2070 	}
2071 	if ( versionNumber >= 335 )
2072 	{
2073 		gameModeManager.currentSession.saveServerFlags();
2074 		if ( multiplayer == CLIENT )
2075 		{
2076 			fread(&lobbyWindowSvFlags, sizeof(Uint32), 1, fp);
2077 		}
2078 		else
2079 		{
2080 			fread(&svFlags, sizeof(Uint32), 1, fp);
2081 		}
2082 		printlog("[SESSION]: Using savegame server flags");
2083 	}
2084 
2085 	// read hotbar item offsets
2086 	Uint32 temp_hotbar[NUM_HOTBAR_SLOTS];
2087 	for ( c = 0; c < NUM_HOTBAR_SLOTS; c++ )
2088 	{
2089 		fread(&temp_hotbar[c], sizeof(Uint32), 1, fp);
2090 	}
2091 
2092 	// read spells
2093 	list_FreeAll(&spellList);
2094 	Uint32 numspells = 0;
2095 	fread(&numspells, sizeof(Uint32), 1, fp);
2096 	for ( c = 0; c < numspells; c++ )
2097 	{
2098 		int spellnum = 0;
2099 		fread(&spellnum, sizeof(Uint32), 1, fp);
2100 		spell_t* spell = copySpell(getSpellFromID(spellnum));
2101 
2102 		node = list_AddNodeLast(&spellList);
2103 		node->element = spell;
2104 		node->deconstructor = &spellDeconstructor;
2105 		node->size = sizeof(spell);
2106 	}
2107 
2108 	int monsters = NUMMONSTERS;
2109 	if ( versionNumber < 325 )
2110 	{
2111 		monsters = 33;
2112 	}
2113 
2114 	// skip through other player data until you get to the correct player
2115 	for ( c = 0; c < player; c++ )
2116 	{
2117 		fseek(fp, sizeof(Uint32), SEEK_CUR);
2118 		fseek(fp, monsters * sizeof(Sint32), SEEK_CUR);
2119 		fseek(fp, sizeof(Monster), SEEK_CUR);
2120 		fseek(fp, sizeof(sex_t), SEEK_CUR);
2121 		fseek(fp, sizeof(Uint32), SEEK_CUR);
2122 		fseek(fp, sizeof(char) * 32, SEEK_CUR);
2123 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2124 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2125 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2126 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2127 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2128 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2129 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2130 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2131 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2132 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2133 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2134 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2135 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2136 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2137 		if ( versionNumber >= 323 )
2138 		{
2139 			fseek(fp, sizeof(Sint32)*NUMPROFICIENCIES, SEEK_CUR);
2140 		}
2141 		else
2142 		{
2143 			fseek(fp, sizeof(Sint32)*14, SEEK_CUR);
2144 		}
2145 		if ( versionNumber <= 323 ) // legacy
2146 		{
2147 			fseek(fp, sizeof(bool)*32, SEEK_CUR);
2148 			fseek(fp, sizeof(Sint32)*32, SEEK_CUR);
2149 		}
2150 		else
2151 		{
2152 			fseek(fp, sizeof(bool)*NUMEFFECTS, SEEK_CUR);
2153 			fseek(fp, sizeof(Sint32)*NUMEFFECTS, SEEK_CUR);
2154 		}
2155 		if ( versionNumber >= 323 )
2156 		{
2157 			fseek(fp, sizeof(Sint32)*32, SEEK_CUR); // stat flags
2158 		}
2159 
2160 		if ( clientnum == 0 && c != 0 )
2161 		{
2162 			// server needs to skip past other players' equipment
2163 			int i;
2164 			for ( i = 0; i < 10; i++ )
2165 			{
2166 				int itemtype = NUMITEMS;
2167 				fread(&itemtype, sizeof(ItemType), 1, fp);
2168 				if ( itemtype < NUMITEMS )
2169 				{
2170 					fseek(fp, sizeof(Status), SEEK_CUR);
2171 					fseek(fp, sizeof(Sint16), SEEK_CUR);
2172 					fseek(fp, sizeof(Sint16), SEEK_CUR);
2173 					fseek(fp, sizeof(Uint32), SEEK_CUR);
2174 					fseek(fp, sizeof(bool), SEEK_CUR);
2175 				}
2176 			}
2177 		}
2178 		else
2179 		{
2180 			if ( clientnum != 0 )
2181 			{
2182 				// client needs to skip the dummy byte
2183 				fseek(fp, sizeof(Status), SEEK_CUR);
2184 			}
2185 			else
2186 			{
2187 				// server needs to skip past its inventory
2188 				int numitems = 0;
2189 				fread(&numitems, sizeof(Uint32), 1, fp);
2190 
2191 				int i;
2192 				for ( i = 0; i < numitems; i++ )
2193 				{
2194 					fseek(fp, sizeof(ItemType), SEEK_CUR);
2195 					fseek(fp, sizeof(Status), SEEK_CUR);
2196 					fseek(fp, sizeof(Sint16), SEEK_CUR);
2197 					fseek(fp, sizeof(Sint16), SEEK_CUR);
2198 					fseek(fp, sizeof(Uint32), SEEK_CUR);
2199 					fseek(fp, sizeof(bool), SEEK_CUR);
2200 					fseek(fp, sizeof(Sint32), SEEK_CUR);
2201 					fseek(fp, sizeof(Sint32), SEEK_CUR);
2202 				}
2203 				fseek(fp, sizeof(Uint32) * 10, SEEK_CUR); // equipment slots
2204 			}
2205 		}
2206 	}
2207 
2208 	// read in player data
2209 	stats[player]->clearStats();
2210 	fread(&client_classes[player], sizeof(Uint32), 1, fp);
2211 	for ( c = 0; c < monsters; c++ )
2212 	{
2213 		fread(&kills[c], sizeof(Sint32), 1, fp);
2214 	}
2215 	fread(&stats[player]->type, sizeof(Monster), 1, fp);
2216 	fread(&stats[player]->sex, sizeof(sex_t), 1, fp);
2217 	fread(&stats[player]->appearance, sizeof(Uint32), 1, fp);
2218 	if ( versionNumber >= 323 )
2219 	{
2220 		stats[player]->playerRace = ((stats[player]->appearance & 0xFF00) >> 8);
2221 		stats[player]->appearance = (stats[player]->appearance & 0xFF);
2222 	}
2223 	fread(&stats[player]->name, sizeof(char), 32, fp);
2224 	fread(&stats[player]->HP, sizeof(Sint32), 1, fp);
2225 	fread(&stats[player]->MAXHP, sizeof(Sint32), 1, fp);
2226 	fread(&stats[player]->MP, sizeof(Sint32), 1, fp);
2227 	fread(&stats[player]->MAXMP, sizeof(Sint32), 1, fp);
2228 	fread(&stats[player]->STR, sizeof(Sint32), 1, fp);
2229 	fread(&stats[player]->DEX, sizeof(Sint32), 1, fp);
2230 	fread(&stats[player]->CON, sizeof(Sint32), 1, fp);
2231 	fread(&stats[player]->INT, sizeof(Sint32), 1, fp);
2232 	fread(&stats[player]->PER, sizeof(Sint32), 1, fp);
2233 	fread(&stats[player]->CHR, sizeof(Sint32), 1, fp);
2234 	fread(&stats[player]->EXP, sizeof(Sint32), 1, fp);
2235 	fread(&stats[player]->LVL, sizeof(Sint32), 1, fp);
2236 	fread(&stats[player]->GOLD, sizeof(Sint32), 1, fp);
2237 	fread(&stats[player]->HUNGER, sizeof(Sint32), 1, fp);
2238 	for ( c = 0; c < NUMPROFICIENCIES; c++ )
2239 	{
2240 		if ( versionNumber < 323 && c >= PRO_UNARMED )
2241 		{
2242 			stats[player]->PROFICIENCIES[c] = 0;
2243 		}
2244 		else
2245 		{
2246 			fread(&stats[player]->PROFICIENCIES[c], sizeof(Sint32), 1, fp);
2247 		}
2248 	}
2249 	for ( c = 0; c < NUMEFFECTS; c++ )
2250 	{
2251 		if ( versionNumber <= 323 ) // legacy
2252 		{
2253 			if ( c < 32 )
2254 			{
2255 				fread(&stats[player]->EFFECTS[c], sizeof(bool), 1, fp);
2256 				fread(&stats[player]->EFFECTS_TIMERS[c], sizeof(Sint32), 1, fp);
2257 			}
2258 			else
2259 			{
2260 				stats[player]->EFFECTS[c] = false;
2261 				stats[player]->EFFECTS_TIMERS[c] = 0;
2262 			}
2263 		}
2264 		else
2265 		{
2266 			fread(&stats[player]->EFFECTS[c], sizeof(bool), 1, fp);
2267 			fread(&stats[player]->EFFECTS_TIMERS[c], sizeof(Sint32), 1, fp);
2268 		}
2269 	}
2270 	if ( versionNumber >= 323 )
2271 	{
2272 		for ( c = 0; c < 32; c++ )
2273 		{
2274 			fread(&stats[player]->MISC_FLAGS[c], sizeof(Sint32), 1, fp);
2275 			if ( c < STAT_FLAG_PLAYER_RACE )
2276 			{
2277 				stats[player]->MISC_FLAGS[c] = 0; // we don't really need these on load.
2278 			}
2279 		}
2280 	}
2281 
2282 	if ( player == clientnum )
2283 	{
2284 		// inventory
2285 		int numitems = 0;
2286 		fread(&numitems, sizeof(Uint32), 1, fp);
2287 		stats[player]->inventory.first = NULL;
2288 		stats[player]->inventory.last = NULL;
2289 		for ( c = 0; c < numitems; c++ )
2290 		{
2291 			ItemType type;
2292 			Status status;
2293 			Sint16 beatitude;
2294 			Sint16 count;
2295 			Uint32 appearance;
2296 			bool identified;
2297 			fread(&type, sizeof(ItemType), 1, fp);
2298 			fread(&status, sizeof(Status), 1, fp);
2299 			fread(&beatitude, sizeof(Sint16), 1, fp);
2300 			fread(&count, sizeof(Sint16), 1, fp);
2301 			fread(&appearance, sizeof(Uint32), 1, fp);
2302 			fread(&identified, sizeof(bool), 1, fp);
2303 			Item* item = newItem(type, status, beatitude, count, appearance, identified, &stats[player]->inventory);
2304 			fread(&item->x, sizeof(Sint32), 1, fp);
2305 			fread(&item->y, sizeof(Sint32), 1, fp);
2306 		}
2307 
2308 		// equipment
2309 		fread(&c, sizeof(Uint32), 1, fp);
2310 		node = list_Node(&stats[player]->inventory, c);
2311 		if ( node )
2312 		{
2313 			stats[player]->helmet = (Item*)node->element;
2314 		}
2315 		else
2316 		{
2317 			stats[player]->helmet = NULL;
2318 		}
2319 		fread(&c, sizeof(Uint32), 1, fp);
2320 		node = list_Node(&stats[player]->inventory, c);
2321 		if ( node )
2322 		{
2323 			stats[player]->breastplate = (Item*)node->element;
2324 		}
2325 		else
2326 		{
2327 			stats[player]->breastplate = NULL;
2328 		}
2329 		fread(&c, sizeof(Uint32), 1, fp);
2330 		node = list_Node(&stats[player]->inventory, c);
2331 		if ( node )
2332 		{
2333 			stats[player]->gloves = (Item*)node->element;
2334 		}
2335 		else
2336 		{
2337 			stats[player]->gloves = NULL;
2338 		}
2339 		fread(&c, sizeof(Uint32), 1, fp);
2340 		node = list_Node(&stats[player]->inventory, c);
2341 		if ( node )
2342 		{
2343 			stats[player]->shoes = (Item*)node->element;
2344 		}
2345 		else
2346 		{
2347 			stats[player]->shoes = NULL;
2348 		}
2349 		fread(&c, sizeof(Uint32), 1, fp);
2350 		node = list_Node(&stats[player]->inventory, c);
2351 		if ( node )
2352 		{
2353 			stats[player]->shield = (Item*)node->element;
2354 		}
2355 		else
2356 		{
2357 			stats[player]->shield = NULL;
2358 		}
2359 		fread(&c, sizeof(Uint32), 1, fp);
2360 		node = list_Node(&stats[player]->inventory, c);
2361 		if ( node )
2362 		{
2363 			stats[player]->weapon = (Item*)node->element;
2364 		}
2365 		else
2366 		{
2367 			stats[player]->weapon = NULL;
2368 		}
2369 		fread(&c, sizeof(Uint32), 1, fp);
2370 		node = list_Node(&stats[player]->inventory, c);
2371 		if ( node )
2372 		{
2373 			stats[player]->cloak = (Item*)node->element;
2374 		}
2375 		else
2376 		{
2377 			stats[player]->cloak = NULL;
2378 		}
2379 		fread(&c, sizeof(Uint32), 1, fp);
2380 		node = list_Node(&stats[player]->inventory, c);
2381 		if ( node )
2382 		{
2383 			stats[player]->amulet = (Item*)node->element;
2384 		}
2385 		else
2386 		{
2387 			stats[player]->amulet = NULL;
2388 		}
2389 		fread(&c, sizeof(Uint32), 1, fp);
2390 		node = list_Node(&stats[player]->inventory, c);
2391 		if ( node )
2392 		{
2393 			stats[player]->ring = (Item*)node->element;
2394 		}
2395 		else
2396 		{
2397 			stats[player]->ring = NULL;
2398 		}
2399 		fread(&c, sizeof(Uint32), 1, fp);
2400 		node = list_Node(&stats[player]->inventory, c);
2401 		if ( node )
2402 		{
2403 			stats[player]->mask = (Item*)node->element;
2404 		}
2405 		else
2406 		{
2407 			stats[player]->mask = NULL;
2408 		}
2409 	}
2410 	else
2411 	{
2412 		stats[player]->inventory.first = NULL;
2413 		stats[player]->inventory.last = NULL;
2414 		stats[player]->helmet = NULL;
2415 		stats[player]->breastplate = NULL;
2416 		stats[player]->gloves = NULL;
2417 		stats[player]->shoes = NULL;
2418 		stats[player]->shield = NULL;
2419 		stats[player]->weapon = NULL;
2420 		stats[player]->cloak = NULL;
2421 		stats[player]->amulet = NULL;
2422 		stats[player]->ring = NULL;
2423 		stats[player]->mask = NULL;
2424 
2425 		if ( multiplayer == SERVER )
2426 		{
2427 			for ( c = 0; c < 10; c++ )
2428 			{
2429 				ItemType type;
2430 				Status status;
2431 				Sint16 beatitude;
2432 				Sint16 count;
2433 				Uint32 appearance;
2434 				bool identified;
2435 
2436 				fread(&type, sizeof(ItemType), 1, fp);
2437 				if ( (int)type < NUMITEMS )
2438 				{
2439 					fread(&status, sizeof(Status), 1, fp);
2440 					fread(&beatitude, sizeof(Sint16), 1, fp);
2441 					fread(&count, sizeof(Sint16), 1, fp);
2442 					fread(&appearance, sizeof(Uint32), 1, fp);
2443 					fread(&identified, sizeof(bool), 1, fp);
2444 
2445 					Item* item = newItem(type, status, beatitude, count, appearance, identified, NULL);
2446 
2447 					switch ( c )
2448 					{
2449 						case 0:
2450 							stats[player]->helmet = item;
2451 							break;
2452 						case 1:
2453 							stats[player]->breastplate = item;
2454 							break;
2455 						case 2:
2456 							stats[player]->gloves = item;
2457 							break;
2458 						case 3:
2459 							stats[player]->shoes = item;
2460 							break;
2461 						case 4:
2462 							stats[player]->shield = item;
2463 							break;
2464 						case 5:
2465 							stats[player]->weapon = item;
2466 							break;
2467 						case 6:
2468 							stats[player]->cloak = item;
2469 							break;
2470 						case 7:
2471 							stats[player]->amulet = item;
2472 							break;
2473 						case 8:
2474 							stats[player]->ring = item;
2475 							break;
2476 						case 9:
2477 							stats[player]->mask = item;
2478 							break;
2479 					}
2480 				}
2481 			}
2482 		}
2483 	}
2484 
2485 	// assign hotbar items
2486 	for ( c = 0; c < NUM_HOTBAR_SLOTS; c++ )
2487 	{
2488 		node = list_Node(&stats[player]->inventory, temp_hotbar[c]);
2489 		if ( node )
2490 		{
2491 			Item* item = (Item*)node->element;
2492 			hotbar[c].item = item->uid;
2493 		}
2494 		else
2495 		{
2496 			hotbar[c].item = 0;
2497 			for ( int d = 0; d < NUM_HOTBAR_ALTERNATES; ++d )
2498 			{
2499 				hotbar_alternate[d][c].item = 0;
2500 			}
2501 		}
2502 	}
2503 
2504 	// reset some unused variables
2505 	stats[player]->monster_sound = NULL;
2506 	stats[player]->monster_idlevar = 0;
2507 	stats[player]->leader_uid = 0;
2508 	stats[player]->FOLLOWERS.first = NULL;
2509 	stats[player]->FOLLOWERS.last = NULL;
2510 	stats[player]->stache_x1 = 0;
2511 	stats[player]->stache_x2 = 0;
2512 	stats[player]->stache_y1 = 0;
2513 	stats[player]->stache_y2 = 0;
2514 
2515 
2516 	hash += (stats[clientnum]->STR + stats[clientnum]->LVL + stats[clientnum]->DEX * stats[clientnum]->INT);
2517 	hash += (stats[clientnum]->CON * stats[clientnum]->PER + std::min(stats[clientnum]->GOLD, 5000) - stats[clientnum]->CON);
2518 	hash += (stats[clientnum]->HP - stats[clientnum]->MP);
2519 	hash += (currentlevel);
2520 
2521 	if ( hash != loadedHash )
2522 	{
2523 		gameStatistics[STATISTICS_DISABLE_UPLOAD] = 1;
2524 	}
2525 	//printlog("%d, %d", hash, loadedHash);
2526 
2527 	enchantedFeatherScrollSeed.seed(uniqueGameKey);
2528 	enchantedFeatherScrollsShuffled.clear();
2529 	enchantedFeatherScrollsShuffled = enchantedFeatherScrollsFixedList;
2530 	std::shuffle(enchantedFeatherScrollsShuffled.begin(), enchantedFeatherScrollsShuffled.end(), enchantedFeatherScrollSeed);
2531 	for ( auto it = enchantedFeatherScrollsShuffled.begin(); it != enchantedFeatherScrollsShuffled.end(); ++it )
2532 	{
2533 		//printlog("Sequence: %d", *it);
2534 	}
2535 
2536 	fclose(fp);
2537 	return 0;
2538 }
2539 
2540 /*-------------------------------------------------------------------------------
2541 
2542 	loadGameFollowers
2543 
2544 	Loads follower data from a save game file
2545 
2546 -------------------------------------------------------------------------------*/
2547 
loadGameFollowers(int saveIndex)2548 list_t* loadGameFollowers(int saveIndex)
2549 {
2550 	FILE* fp;
2551 	int c;
2552 
2553 	char savefile[PATH_MAX] = "";
2554 	char path[PATH_MAX] = "";
2555 	if ( multiplayer == SINGLE )
2556 	{
2557 		strncpy(savefile, setSaveGameFileName(true, true, saveIndex).c_str(), PATH_MAX - 1);
2558 	}
2559 	else
2560 	{
2561 		strncpy(savefile, setSaveGameFileName(false, true, saveIndex).c_str(), PATH_MAX - 1);
2562 	}
2563 	completePath(path, savefile, outputdir);
2564 
2565 	// open file
2566 	if ( (fp = fopen(path, "rb")) == NULL )
2567 	{
2568 		printlog("error: failed to load '%s'!\n", path);
2569 		return NULL;
2570 	}
2571 
2572 	// read from file
2573 	char checkstr[64];
2574 	fread(checkstr, sizeof(char), strlen("BARONYSAVEGAMEFOLLOWERS"), fp);
2575 	if ( strncmp(checkstr, "BARONYSAVEGAMEFOLLOWERS", strlen("BARONYSAVEGAMEFOLLOWERS")) )
2576 	{
2577 		printlog("error: '%s' is corrupt!\n", path);
2578 		fclose(fp);
2579 		return NULL;
2580 	}
2581 	fread(checkstr, sizeof(char), strlen(VERSION), fp);
2582 	int versionNumber = getSavegameVersion(checkstr);
2583 	printlog("loadGameFollowers: '%s' version number %d", savefile, versionNumber);
2584 	if ( versionNumber == -1 )
2585 	{
2586 		// if version number returned is invalid, abort
2587 		printlog("error: '%s' is corrupt!\n", path);
2588 		fclose(fp);
2589 		return nullptr;
2590 	}
2591 
2592 	// create followers list
2593 	list_t* followers = (list_t*) malloc(sizeof(list_t));
2594 	followers->first = NULL;
2595 	followers->last = NULL;
2596 
2597 	// read the follower data
2598 	for ( c = 0; c < MAXPLAYERS; c++ )
2599 	{
2600 		list_t* followerList = (list_t*) malloc(sizeof(list_t));
2601 		followerList->first = NULL;
2602 		followerList->last = NULL;
2603 		node_t* node = list_AddNodeLast(followers);
2604 		node->element = followerList;
2605 		node->deconstructor = &listDeconstructor;
2606 		node->size = sizeof(list_t);
2607 
2608 		// number of followers for this player
2609 		Uint32 numFollowers = 0;
2610 		fread(&numFollowers, sizeof(Uint32), 1, fp);
2611 
2612 		int i;
2613 		for ( i = 0; i < numFollowers; i++ )
2614 		{
2615 			// Stat set to 0 as monster type not needed, values will be overwritten by the saved follower data
2616 			Stat* followerStats = new Stat(0);
2617 
2618 			node_t* node = list_AddNodeLast(followerList);
2619 			node->element = followerStats;
2620 			//node->deconstructor = &followerStats->~Stat;
2621 			node->size = sizeof(followerStats);
2622 
2623 			// read follower attributes
2624 			fread(&followerStats->type, sizeof(Monster), 1, fp);
2625 			fread(&followerStats->sex, sizeof(sex_t), 1, fp);
2626 			fread(&followerStats->appearance, sizeof(Uint32), 1, fp);
2627 			fread(&followerStats->name, sizeof(char), 32, fp);
2628 			fread(&followerStats->HP, sizeof(Sint32), 1, fp);
2629 			fread(&followerStats->MAXHP, sizeof(Sint32), 1, fp);
2630 			fread(&followerStats->MP, sizeof(Sint32), 1, fp);
2631 			fread(&followerStats->MAXMP, sizeof(Sint32), 1, fp);
2632 			fread(&followerStats->STR, sizeof(Sint32), 1, fp);
2633 			fread(&followerStats->DEX, sizeof(Sint32), 1, fp);
2634 			fread(&followerStats->CON, sizeof(Sint32), 1, fp);
2635 			fread(&followerStats->INT, sizeof(Sint32), 1, fp);
2636 			fread(&followerStats->PER, sizeof(Sint32), 1, fp);
2637 			fread(&followerStats->CHR, sizeof(Sint32), 1, fp);
2638 			fread(&followerStats->EXP, sizeof(Sint32), 1, fp);
2639 			fread(&followerStats->LVL, sizeof(Sint32), 1, fp);
2640 			fread(&followerStats->GOLD, sizeof(Sint32), 1, fp);
2641 			fread(&followerStats->HUNGER, sizeof(Sint32), 1, fp);
2642 
2643 			int j;
2644 			for ( j = 0; j < NUMPROFICIENCIES; j++ )
2645 			{
2646 				if ( versionNumber < 323 && j >= PRO_UNARMED )
2647 				{
2648 					followerStats->PROFICIENCIES[j] = 0;
2649 				}
2650 				else
2651 				{
2652 					fread(&followerStats->PROFICIENCIES[j], sizeof(Sint32), 1, fp);
2653 				}
2654 			}
2655 			for ( j = 0; j < NUMEFFECTS; j++ )
2656 			{
2657 				if ( versionNumber <= 323 ) // legacy
2658 				{
2659 					if ( c < 32 )
2660 					{
2661 						fread(&followerStats->EFFECTS[j], sizeof(bool), 1, fp);
2662 						fread(&followerStats->EFFECTS_TIMERS[j], sizeof(Sint32), 1, fp);
2663 					}
2664 					else
2665 					{
2666 						followerStats->EFFECTS[j] = false;
2667 						followerStats->EFFECTS_TIMERS[j] = 0;
2668 					}
2669 				}
2670 				else
2671 				{
2672 					fread(&followerStats->EFFECTS[j], sizeof(bool), 1, fp);
2673 					fread(&followerStats->EFFECTS_TIMERS[j], sizeof(Sint32), 1, fp);
2674 				}
2675 			}
2676 			if ( versionNumber >= 323 )
2677 			{
2678 				for ( j = 0; j < 32; ++j )
2679 				{
2680 					fread(&followerStats->MISC_FLAGS[j], sizeof(Sint32), 1, fp);
2681 				}
2682 			}
2683 
2684 			/*printlog("\n\n ** FOLLOWER #%d **\n", i + 1);
2685 			printlog("Follower stats: \n");
2686 			followerStats->printStats();
2687 			printlog("\n\n");*/
2688 
2689 			// item variables
2690 			ItemType type;
2691 			Status status;
2692 			Sint16 beatitude;
2693 			Sint16 count;
2694 			Uint32 appearance;
2695 			bool identified;
2696 
2697 			// read follower inventory
2698 			Uint32 invSize = 0;
2699 			fread(&invSize, sizeof(Uint32), 1, fp);
2700 			for ( j = 0; j < invSize; j++ )
2701 			{
2702 				fread(&type, sizeof(ItemType), 1, fp);
2703 				fread(&status, sizeof(Status), 1, fp);
2704 				fread(&beatitude, sizeof(Sint16), 1, fp);
2705 				fread(&count, sizeof(Sint16), 1, fp);
2706 				fread(&appearance, sizeof(Uint32), 1, fp);
2707 				fread(&identified, sizeof(bool), 1, fp);
2708 
2709 				Item* item = newItem(type, status, beatitude, count, appearance, identified, &followerStats->inventory);
2710 				fread(&item->x, sizeof(Sint32), 1, fp);
2711 				fread(&item->y, sizeof(Sint32), 1, fp);
2712 			}
2713 
2714 			// read follower equipment
2715 			int b;
2716 			for ( b = 0; b < 10; b++ )
2717 			{
2718 				fread(&type, sizeof(ItemType), 1, fp);
2719 				if ( (int)type < NUMITEMS )
2720 				{
2721 					fread(&status, sizeof(Status), 1, fp);
2722 					fread(&beatitude, sizeof(Sint16), 1, fp);
2723 					fread(&count, sizeof(Sint16), 1, fp);
2724 					fread(&appearance, sizeof(Uint32), 1, fp);
2725 					fread(&identified, sizeof(bool), 1, fp);
2726 
2727 					Item* item = newItem(type, status, beatitude, count, appearance, identified, NULL);
2728 
2729 					switch ( b )
2730 					{
2731 						case 0:
2732 							followerStats->helmet = item;
2733 							break;
2734 						case 1:
2735 							followerStats->breastplate = item;
2736 							break;
2737 						case 2:
2738 							followerStats->gloves = item;
2739 							break;
2740 						case 3:
2741 							followerStats->shoes = item;
2742 							break;
2743 						case 4:
2744 							followerStats->shield = item;
2745 							break;
2746 						case 5:
2747 							followerStats->weapon = item;
2748 							break;
2749 						case 6:
2750 							followerStats->cloak = item;
2751 							break;
2752 						case 7:
2753 							followerStats->amulet = item;
2754 							break;
2755 						case 8:
2756 							followerStats->ring = item;
2757 							break;
2758 						case 9:
2759 							followerStats->mask = item;
2760 							break;
2761 					}
2762 				}
2763 			}
2764 		}
2765 	}
2766 
2767 	fclose(fp);
2768 	return followers;
2769 }
2770 
2771 /*-------------------------------------------------------------------------------
2772 
2773 	deleteSaveGame
2774 
2775 	Deletes the saved game
2776 
2777 -------------------------------------------------------------------------------*/
2778 
deleteSaveGame(int gametype,int saveIndex)2779 int deleteSaveGame(int gametype, int saveIndex)
2780 {
2781 	char savefile[PATH_MAX] = "";
2782 	char path[PATH_MAX] = "";
2783 	if ( gametype == SINGLE )
2784 	{
2785 		strncpy(savefile, setSaveGameFileName(true, false, saveIndex).c_str(), PATH_MAX - 1);
2786 	}
2787 	else
2788 	{
2789 		strncpy(savefile, setSaveGameFileName(false, false, saveIndex).c_str(), PATH_MAX - 1);
2790 	}
2791 	completePath(path, savefile, outputdir);
2792 
2793 	if (access(path, F_OK) != -1)
2794 	{
2795 		printlog("deleting savegame in '%s'...\n", path);
2796 		int result = remove(path);
2797 		if (result)
2798 		{
2799 			printlog("warning: failed to delete savegame in '%s'!\n", path);
2800 #ifdef _MSC_VER
2801 			printlog(strerror(errno));
2802 #endif
2803 		}
2804 	}
2805 
2806 	if ( gametype == SINGLE )
2807 	{
2808 		strncpy(savefile, setSaveGameFileName(true, true, saveIndex).c_str(), 63);
2809 	}
2810 	else
2811 	{
2812 		strncpy(savefile, setSaveGameFileName(false, true, saveIndex).c_str(), 63);
2813 	}
2814 	completePath(path, savefile, outputdir);
2815 
2816 	if (access(path, F_OK) != -1)
2817 	{
2818 		printlog("deleting savegame in '%s'...\n", path);
2819 		int result = remove(path);
2820 		if (result)
2821 		{
2822 			printlog("warning: failed to delete savegame in '%s'!\n", path);
2823 #ifdef _MSC_VER
2824 			printlog(strerror(errno));
2825 #endif
2826 		}
2827 		return result;
2828 	}
2829 	else
2830 	{
2831 		return 0;
2832 	}
2833 }
2834 
2835 /*-------------------------------------------------------------------------------
2836 
2837 	saveGameExists
2838 
2839 	checks to see if a valid save game exists.
2840 
2841 -------------------------------------------------------------------------------*/
2842 
saveGameExists(bool singleplayer,int saveIndex)2843 bool saveGameExists(bool singleplayer, int saveIndex)
2844 {
2845 	char savefile[PATH_MAX] = "";
2846 	char path[PATH_MAX] = "";
2847 	strncpy(savefile, setSaveGameFileName(singleplayer, false, saveIndex).c_str(), PATH_MAX - 1);
2848 	completePath(path, savefile, outputdir);
2849 
2850 	if ( access(path, F_OK ) == -1 )
2851 	{
2852 		return false;
2853 	}
2854 	else
2855 	{
2856 		FILE* fp;
2857 		if ( (fp = fopen(path, "rb")) == NULL )
2858 		{
2859 			return false;
2860 		}
2861 		char checkstr[64];
2862 		fread(checkstr, sizeof(char), strlen("BARONYSAVEGAME"), fp);
2863 		if ( strncmp(checkstr, "BARONYSAVEGAME", strlen("BARONYSAVEGAME")) )
2864 		{
2865 			fclose(fp);
2866 			return false;
2867 		}
2868 		fread(checkstr, sizeof(char), strlen(VERSION), fp);
2869 		int versionNumber = getSavegameVersion(checkstr);
2870 		if ( versionNumber == -1 )
2871 		{
2872 			// if getSavegameVersion returned -1, abort.
2873 			fclose(fp);
2874 			return false;
2875 		}
2876 		fclose(fp);
2877 		return true;
2878 	}
2879 }
2880 
2881 /*-------------------------------------------------------------------------------
2882 
2883 	getSaveGameName
2884 
2885 	Gets the name of the character in the saved game
2886 
2887 -------------------------------------------------------------------------------*/
2888 
getSaveGameName(bool singleplayer,int saveIndex)2889 char* getSaveGameName(bool singleplayer, int saveIndex)
2890 {
2891 	char name[128];
2892 	FILE* fp;
2893 	int c;
2894 
2895 	int level, class_;
2896 	int mul, plnum, dungeonlevel;
2897 	int playerRace, playerAppearance;
2898 
2899 	char* tempstr = (char*) calloc(1024, sizeof(char));
2900 	char savefile[PATH_MAX] = "";
2901 	char path[PATH_MAX] = "";
2902 	strncpy(savefile, setSaveGameFileName(singleplayer, false, saveIndex).c_str(), PATH_MAX - 1);
2903 	completePath(path, savefile, outputdir);
2904 
2905 	// open file
2906 	if ( (fp = fopen(path, "rb")) == NULL )
2907 	{
2908 		printlog("error: failed to check name in '%s'!\n", path);
2909 		free(tempstr);
2910 		return NULL;
2911 	}
2912 
2913 	// read from file
2914 	char checkstr[64];
2915 	fread(checkstr, sizeof(char), strlen("BARONYSAVEGAME"), fp);
2916 	if ( strncmp(checkstr, "BARONYSAVEGAME", strlen("BARONYSAVEGAME")) )
2917 	{
2918 		printlog("error: '%s' is corrupt!\n", path);
2919 		fclose(fp);
2920 		free(tempstr);
2921 		return NULL;
2922 	}
2923 	fread(checkstr, sizeof(char), strlen(VERSION), fp);
2924 	int versionNumber = getSavegameVersion(checkstr);
2925 	printlog("getSaveGameName: '%s' version number %d", savefile, versionNumber);
2926 	if ( versionNumber == -1 )
2927 	{
2928 		// if getSavegameVersion returned -1, abort.
2929 		printlog("error: '%s' is corrupt!\n", path);
2930 		fclose(fp);
2931 		free(tempstr);
2932 		return nullptr;
2933 	}
2934 
2935 	fseek(fp, sizeof(Uint32), SEEK_CUR);
2936 	fread(&mul, sizeof(Uint32), 1, fp);
2937 	fread(&plnum, sizeof(Uint32), 1, fp);
2938 	fseek(fp, sizeof(Uint32), SEEK_CUR);
2939 	fread(&dungeonlevel, sizeof(Uint32), 1, fp);
2940 	dungeonlevel = dungeonlevel & 0xFF;
2941 	fseek(fp,  sizeof(bool), SEEK_CUR);
2942 	if ( versionNumber >= 310 )
2943 	{
2944 		fseek(fp, sizeof(Sint32) * NUM_CONDUCT_CHALLENGES, SEEK_CUR);
2945 		fseek(fp, sizeof(Sint32) * NUM_GAMEPLAY_STATISTICS, SEEK_CUR);
2946 	}
2947 	if ( versionNumber >= 335 )
2948 	{
2949 		fseek(fp, sizeof(Uint32), SEEK_CUR); // svFlags
2950 	}
2951 	fseek(fp, sizeof(Uint32)*NUM_HOTBAR_SLOTS, SEEK_CUR);
2952 	fseek(fp, sizeof(Uint32) + sizeof(bool) + sizeof(bool) + sizeof(bool) + sizeof(bool), SEEK_CUR);
2953 
2954 	int numspells = 0;
2955 	fread(&numspells, sizeof(Uint32), 1, fp);
2956 	for ( c = 0; c < numspells; c++ )
2957 	{
2958 		fseek(fp, sizeof(Uint32), SEEK_CUR);
2959 	}
2960 
2961 	int monsters = NUMMONSTERS;
2962 	if ( versionNumber < 325 )
2963 	{
2964 		monsters = 33;
2965 	}
2966 
2967 	// skip through other player data until you get to the correct player
2968 	for ( c = 0; c < plnum; c++ )
2969 	{
2970 		fseek(fp, sizeof(Uint32), SEEK_CUR);
2971 		fseek(fp, monsters * sizeof(Sint32), SEEK_CUR);
2972 		fseek(fp, sizeof(Monster), SEEK_CUR);
2973 		fseek(fp, sizeof(sex_t), SEEK_CUR);
2974 		fseek(fp, sizeof(Uint32), SEEK_CUR);
2975 		fseek(fp, sizeof(char) * 32, SEEK_CUR);
2976 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2977 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2978 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2979 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2980 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2981 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2982 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2983 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2984 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2985 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2986 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2987 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2988 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2989 		fseek(fp, sizeof(Sint32), SEEK_CUR);
2990 		if ( versionNumber >= 323 )
2991 		{
2992 			fseek(fp, sizeof(Sint32)*NUMPROFICIENCIES, SEEK_CUR);
2993 		}
2994 		else
2995 		{
2996 			fseek(fp, sizeof(Sint32)*14, SEEK_CUR);
2997 		}
2998 		if ( versionNumber <= 323 )
2999 		{
3000 			fseek(fp, sizeof(bool)*32, SEEK_CUR);
3001 			fseek(fp, sizeof(Sint32)*32, SEEK_CUR);
3002 		}
3003 		else
3004 		{
3005 			fseek(fp, sizeof(bool)*NUMEFFECTS, SEEK_CUR);
3006 			fseek(fp, sizeof(Sint32)*NUMEFFECTS, SEEK_CUR);
3007 		}
3008 		if ( versionNumber >= 323 )
3009 		{
3010 			fseek(fp, sizeof(Sint32) * 32, SEEK_CUR); // stat flags
3011 		}
3012 
3013 		if ( plnum == 0 )
3014 		{
3015 			// server needs to skip past its inventory
3016 			int numitems = 0;
3017 			fread(&numitems, sizeof(Uint32), 1, fp);
3018 
3019 			int i;
3020 			for ( i = 0; i < numitems; i++ )
3021 			{
3022 				fseek(fp, sizeof(ItemType), SEEK_CUR);
3023 				fseek(fp, sizeof(Status), SEEK_CUR);
3024 				fseek(fp, sizeof(Sint16), SEEK_CUR);
3025 				fseek(fp, sizeof(Sint16), SEEK_CUR);
3026 				fseek(fp, sizeof(Uint32), SEEK_CUR);
3027 				fseek(fp, sizeof(bool), SEEK_CUR);
3028 				fseek(fp, sizeof(Sint32), SEEK_CUR);
3029 				fseek(fp, sizeof(Sint32), SEEK_CUR);
3030 			}
3031 			fseek(fp, sizeof(Uint32) * 10, SEEK_CUR); // equipment slots
3032 		}
3033 		else
3034 		{
3035 			// client needs to skip the dummy byte
3036 			fseek(fp, sizeof(Status), SEEK_CUR);
3037 		}
3038 	}
3039 
3040 	fread(&class_, sizeof(Uint32), 1, fp);
3041 	for ( c = 0; c < monsters; c++ )
3042 	{
3043 		fseek(fp, sizeof(Sint32), SEEK_CUR);
3044 	}
3045 	fseek(fp, sizeof(Monster) + sizeof(sex_t), SEEK_CUR);
3046 	Uint32 raceAndAppearance = 0;
3047 	fread(&raceAndAppearance, sizeof(Uint32), 1, fp);
3048 	playerAppearance = raceAndAppearance & 0xFF;
3049 	playerRace = (raceAndAppearance & 0xFF00) >> 8;
3050 	fread(&name, sizeof(char), 32, fp);
3051 	name[32] = 0;
3052 	fseek(fp, sizeof(Sint32) * 11, SEEK_CUR);
3053 	fread(&level, sizeof(Sint32), 1, fp);
3054 
3055 	// assemble string
3056 	char timestamp[128] = "";
3057 #ifdef WINDOWS
3058 	struct _stat result;
3059 	if ( _stat(path, &result) == 0 )
3060 	{
3061 		struct tm *tm = localtime(&result.st_mtime);
3062 		if ( tm )
3063 		{
3064 			errno_t err = strftime(timestamp, 127, "%d %b %Y, %H:%M", tm); //day, month, year, time
3065 		}
3066 	}
3067 #else
3068 	struct stat result;
3069 	if ( stat(path, &result) == 0 )
3070 	{
3071 		struct tm *tm = localtime(&result.st_mtime);
3072 		if ( tm )
3073 		{
3074 			strftime(timestamp, 127, "%d %b %Y, %H:%M", tm); //day, month, year, time
3075 		}
3076 	}
3077 #endif // WINDOWS
3078 
3079 	int plnumTemp = plnum;
3080 	if ( plnumTemp >= MAXPLAYERS )
3081 	{
3082 		plnumTemp = MAXPLAYERS - 1; // fix for loading 16-player savefile in normal Barony. plnum might be out of index for stats[]
3083 	}
3084 	int oldRace = stats[plnumTemp]->playerRace;
3085 	stats[plnumTemp]->playerRace = playerRace;
3086 
3087 	if ( mul == DIRECTCLIENT || mul == CLIENT )
3088 	{
3089 		// include the player number in the printf.
3090 		snprintf(tempstr, 1024, language[1540 + mul], name, level, playerClassLangEntry(class_, plnumTemp), dungeonlevel, plnum, timestamp);
3091 	}
3092 	else
3093 	{
3094 		if ( mul == SERVERCROSSPLAY )
3095 		{
3096 			mul = SERVER;
3097 		}
3098 		snprintf(tempstr, 1024, language[1540 + mul], name, level, playerClassLangEntry(class_, plnumTemp), dungeonlevel, timestamp);
3099 	}
3100 	// close file
3101 	stats[plnumTemp]->playerRace = oldRace;
3102 	fclose(fp);
3103 
3104 	return tempstr;
3105 }
3106 
3107 /*-------------------------------------------------------------------------------
3108 
3109 	getSaveGameUniqueGameKey
3110 
3111 	Returns the uniqueGameKey variable stored in the save game
3112 
3113 -------------------------------------------------------------------------------*/
3114 
getSaveGameUniqueGameKey(bool singleplayer,int saveIndex)3115 Uint32 getSaveGameUniqueGameKey(bool singleplayer, int saveIndex)
3116 {
3117 	FILE* fp;
3118 	Uint32 gameKey;
3119 	char savefile[PATH_MAX] = "";
3120 	char path[PATH_MAX] = "";
3121 	strncpy(savefile, setSaveGameFileName(singleplayer, false, saveIndex).c_str(), PATH_MAX - 1);
3122 	completePath(path, savefile, outputdir);
3123 
3124 	// open file
3125 	if ( (fp = fopen(path, "rb")) == NULL )
3126 	{
3127 		printlog("error: failed to get map seed out of '%s'!\n", path);
3128 		return 0;
3129 	}
3130 
3131 	// read from file
3132 	char checkstr[64];
3133 	fread(checkstr, sizeof(char), strlen("BARONYSAVEGAME"), fp);
3134 	if ( strncmp(checkstr, "BARONYSAVEGAME", strlen("BARONYSAVEGAME")) )
3135 	{
3136 		printlog("error: '%s' is corrupt!\n", path);
3137 		fclose(fp);
3138 		return 0;
3139 	}
3140 	fread(checkstr, sizeof(char), strlen(VERSION), fp);
3141 	int versionNumber = getSavegameVersion(checkstr);
3142 	if ( versionNumber == -1 )
3143 	{
3144 		// if getSavegameVersion returned -1, abort.
3145 		printlog("error: '%s' is corrupt!\n", path);
3146 		fclose(fp);
3147 		return 0;
3148 	}
3149 
3150 	fread(&gameKey, sizeof(Uint32), 1, fp);
3151 
3152 	// close file
3153 	fclose(fp);
3154 	return gameKey;
3155 }
3156 
3157 /*-------------------------------------------------------------------------------
3158 
3159 getSaveGameVersionNum
3160 
3161 Returns the savefile version
3162 
3163 -------------------------------------------------------------------------------*/
3164 
getSaveGameVersionNum(bool singleplayer,int saveIndex)3165 int getSaveGameVersionNum(bool singleplayer, int saveIndex)
3166 {
3167 	FILE* fp;
3168 	Uint32 gameKey;
3169 	char savefile[PATH_MAX] = "";
3170 	char path[PATH_MAX] = "";
3171 	strncpy(savefile, setSaveGameFileName(singleplayer, false, saveIndex).c_str(), PATH_MAX - 1);
3172 	completePath(path, savefile, outputdir);
3173 
3174 	// open file
3175 	if ( (fp = fopen(path, "rb")) == NULL )
3176 	{
3177 		printlog("error: failed to get map seed out of '%s'!\n", path);
3178 		return 0;
3179 	}
3180 
3181 	// read from file
3182 	char checkstr[64];
3183 	fread(checkstr, sizeof(char), strlen("BARONYSAVEGAME"), fp);
3184 	if ( strncmp(checkstr, "BARONYSAVEGAME", strlen("BARONYSAVEGAME")) )
3185 	{
3186 		printlog("error: '%s' is corrupt!\n", path);
3187 		fclose(fp);
3188 		return 0;
3189 	}
3190 	fread(checkstr, sizeof(char), strlen(VERSION), fp);
3191 	int versionNumber = getSavegameVersion(checkstr);
3192 	if ( versionNumber == -1 )
3193 	{
3194 		printlog("error: '%s' is corrupt!\n", path);
3195 	}
3196 
3197 	// close file
3198 	fclose(fp);
3199 	return versionNumber;
3200 }
3201 
3202 /*-------------------------------------------------------------------------------
3203 
3204 	getSaveGameType
3205 
3206 	Returns the multiplayer variable stored in the save game
3207 
3208 -------------------------------------------------------------------------------*/
3209 
getSaveGameType(bool singleplayer,int saveIndex)3210 int getSaveGameType(bool singleplayer, int saveIndex)
3211 {
3212 	FILE* fp;
3213 	int mul;
3214 	char savefile[PATH_MAX] = "";
3215 	char path[PATH_MAX] = "";
3216 	strncpy(savefile, setSaveGameFileName(singleplayer, false, saveIndex).c_str(), PATH_MAX - 1);
3217 	completePath(path, savefile, outputdir);
3218 
3219 	// open file
3220 	if ( (fp = fopen(path, "rb")) == NULL )
3221 	{
3222 		printlog("error: failed to get game type out of '%s'!\n", path);
3223 		return 0;
3224 	}
3225 
3226 	// read from file
3227 	char checkstr[64];
3228 	fread(checkstr, sizeof(char), strlen("BARONYSAVEGAME"), fp);
3229 	if ( strncmp(checkstr, "BARONYSAVEGAME", strlen("BARONYSAVEGAME")) )
3230 	{
3231 		printlog("error: '%s' is corrupt!\n", path);
3232 		fclose(fp);
3233 		return 0;
3234 	}
3235 	fread(checkstr, sizeof(char), strlen(VERSION), fp);
3236 	int versionNumber = getSavegameVersion(checkstr);
3237 	if ( versionNumber == -1 )
3238 	{
3239 		// if getSavegameVersion returned -1, abort.
3240 		printlog("error: '%s' is corrupt!\n", path);
3241 		fclose(fp);
3242 		return 0;
3243 	}
3244 
3245 	fseek(fp, sizeof(Uint32), SEEK_CUR);
3246 	fread(&mul, sizeof(Uint32), 1, fp);
3247 
3248 	// close file
3249 	fclose(fp);
3250 	return mul;
3251 }
3252 
3253 /*-------------------------------------------------------------------------------
3254 
3255 	getSaveGameClientnum
3256 
3257 	Returns the clientnum variable stored in the save game
3258 
3259 -------------------------------------------------------------------------------*/
3260 
getSaveGameClientnum(bool singleplayer,int saveIndex)3261 int getSaveGameClientnum(bool singleplayer, int saveIndex)
3262 {
3263 	FILE* fp;
3264 	int clientnum;
3265 	char savefile[PATH_MAX] = "";
3266 	char path[PATH_MAX] = "";
3267 	strncpy(savefile, setSaveGameFileName(singleplayer, false, saveIndex).c_str(), PATH_MAX - 1);
3268 	completePath(path, savefile, outputdir);
3269 
3270 	// open file
3271 	if ( (fp = fopen(path, "rb")) == NULL )
3272 	{
3273 		printlog("error: failed to get clientnum out of '%s'!\n", path);
3274 		return 0;
3275 	}
3276 
3277 	// read from file
3278 	char checkstr[64];
3279 	fread(checkstr, sizeof(char), strlen("BARONYSAVEGAME"), fp);
3280 	if ( strncmp(checkstr, "BARONYSAVEGAME", strlen("BARONYSAVEGAME")) )
3281 	{
3282 		printlog("error: '%s' is corrupt!\n", path);
3283 		fclose(fp);
3284 		return 0;
3285 	}
3286 	fread(checkstr, sizeof(char), strlen(VERSION), fp);
3287 	int versionNumber = getSavegameVersion(checkstr);
3288 	if ( versionNumber == -1 )
3289 	{
3290 		// if getSavegameVersion returned -1, abort.
3291 		printlog("error: '%s' is corrupt!\n", path);
3292 		fclose(fp);
3293 		return 0;
3294 	}
3295 
3296 	fseek(fp, sizeof(Uint32), SEEK_CUR);
3297 	fseek(fp, sizeof(Uint32), SEEK_CUR);
3298 	fread(&clientnum, sizeof(Uint32), 1, fp);
3299 
3300 	// close file
3301 	fclose(fp);
3302 	return clientnum;
3303 }
3304 
3305 /*-------------------------------------------------------------------------------
3306 
3307 	getSaveGameMapSeed
3308 
3309 	Returns the mapseed variable stored in the save game
3310 
3311 -------------------------------------------------------------------------------*/
3312 
getSaveGameMapSeed(bool singleplayer,int saveIndex)3313 Uint32 getSaveGameMapSeed(bool singleplayer, int saveIndex)
3314 {
3315 	FILE* fp;
3316 	Uint32 seed;
3317 	char savefile[PATH_MAX] = "";
3318 	char path[PATH_MAX] = "";
3319 	strncpy(savefile, setSaveGameFileName(singleplayer, false, saveIndex).c_str(), PATH_MAX - 1);
3320 	completePath(path, savefile, outputdir);
3321 
3322 	// open file
3323 	if ( (fp = fopen(path, "rb")) == NULL )
3324 	{
3325 		printlog("error: failed to get map seed out of '%s'!\n", path);
3326 		return 0;
3327 	}
3328 
3329 	// read from file
3330 	char checkstr[64];
3331 	fread(checkstr, sizeof(char), strlen("BARONYSAVEGAME"), fp);
3332 	if ( strncmp(checkstr, "BARONYSAVEGAME", strlen("BARONYSAVEGAME")) )
3333 	{
3334 		printlog("error: '%s' is corrupt!\n", path);
3335 		fclose(fp);
3336 		return 0;
3337 	}
3338 	fread(checkstr, sizeof(char), strlen(VERSION), fp);
3339 	int versionNumber = getSavegameVersion(checkstr);
3340 	if ( versionNumber == -1 )
3341 	{
3342 		// if getSavegameVersion returned -1, abort.
3343 		printlog("error: '%s' is corrupt!\n", path);
3344 		fclose(fp);
3345 		return 0;
3346 	}
3347 
3348 	fseek(fp, sizeof(Uint32), SEEK_CUR);
3349 	fseek(fp, sizeof(Uint32), SEEK_CUR);
3350 	fseek(fp, sizeof(Uint32), SEEK_CUR);
3351 	fread(&seed, sizeof(Uint32), 1, fp);
3352 
3353 	// close file
3354 	fclose(fp);
3355 	return seed;
3356 }
3357 
getSavegameVersion(char checkstr[64])3358 int getSavegameVersion(char checkstr[64])
3359 {
3360 	int versionNumber = 300;
3361 	char versionStr[4] = "000";
3362 	int i = 0;
3363 	for ( int j = 0; j < strlen(VERSION); ++j )
3364 	{
3365 		if ( checkstr[j] >= '0' && checkstr[j] <= '9' )
3366 		{
3367 			versionStr[i] = checkstr[j]; // copy all integers into versionStr.
3368 			++i;
3369 			if ( i == 3 )
3370 			{
3371 				versionStr[i] = '\0';
3372 				break; // written 3 characters, add termination and break loop.
3373 			}
3374 		}
3375 	}
3376 	versionNumber = atoi(versionStr); // convert from string to int.
3377 	if ( versionNumber < 200 || versionNumber > 999 )
3378 	{
3379 		// if version number less than v2.0.0, or more than 3 digits, abort.
3380 		return -1;
3381 	}
3382 	return versionNumber;
3383 }
3384 
setDefaultPlayerConducts()3385 void setDefaultPlayerConducts()
3386 {
3387 	conductPenniless = true;
3388 	conductFoodless = true;
3389 	conductVegetarian = true;
3390 	conductIlliterate = true;
3391 
3392 	for ( int c = 0; c < NUM_CONDUCT_CHALLENGES; ++c )
3393 	{
3394 		conductGameChallenges[c] = 0;
3395 	}
3396 	conductGameChallenges[CONDUCT_HARDCORE] = 1;
3397 	conductGameChallenges[CONDUCT_CHEATS_ENABLED] = 0;
3398 	conductGameChallenges[CONDUCT_CLASSIC_MODE] = 0;
3399 	conductGameChallenges[CONDUCT_BRAWLER] = 1;
3400 	conductGameChallenges[CONDUCT_RANGED_ONLY] = 1;
3401 	conductGameChallenges[CONDUCT_MODDED] = 0;
3402 	conductGameChallenges[CONDUCT_LIFESAVING] = 0;
3403 	conductGameChallenges[CONDUCT_KEEPINVENTORY] = 0;
3404 
3405 	for ( int c = 0; c < NUM_GAMEPLAY_STATISTICS; ++c )
3406 	{
3407 		gameStatistics[c] = 0;
3408 	}
3409 	for ( int c = 0; c < MAXPLAYERS; ++c )
3410 	{
3411 		achievementStatusRhythmOfTheKnight[c] = false;
3412 		achievementStatusStrobe[c] = false;
3413 		achievementStatusThankTheTank[c] = false;
3414 		achievementRhythmOfTheKnightVec[c].clear();
3415 		achievementThankTheTankPair[c].first = 0;
3416 		achievementThankTheTankPair[c].second = 0;
3417 		achievementStrobeVec[c].clear();
3418 		achievementStatusBaitAndSwitch[c] = false;
3419 		achievementBaitAndSwitchTimer[c] = 0;
3420 		playerFailedRangedOnlyConduct[c] = false;
3421 	}
3422 	clientLearnedAlchemyIngredients.clear();
3423 	achievementObserver.clearPlayerAchievementData();
3424 }
3425 
updatePlayerConductsInMainLoop()3426 void updatePlayerConductsInMainLoop()
3427 {
3428 	if ( conductPenniless )
3429 	{
3430 		if ( stats[clientnum]->GOLD > 0 )
3431 		{
3432 			conductPenniless = false;
3433 		}
3434 	}
3435 	if ( !conductGameChallenges[CONDUCT_KEEPINVENTORY] )
3436 	{
3437 		if ( (svFlags & SV_FLAG_KEEPINVENTORY) )
3438 		{
3439 			if ( multiplayer != SINGLE )
3440 			{
3441 				conductGameChallenges[CONDUCT_KEEPINVENTORY] = 1;
3442 			}
3443 		}
3444 	}
3445 	if ( !conductGameChallenges[CONDUCT_LIFESAVING] )
3446 	{
3447 		if ( (svFlags & SV_FLAG_LIFESAVING) )
3448 		{
3449 			conductGameChallenges[CONDUCT_LIFESAVING] = 1;
3450 		}
3451 	}
3452 	if ( conductGameChallenges[CONDUCT_HARDCORE] )
3453 	{
3454 		if ( !(svFlags & SV_FLAG_HARDCORE) )
3455 		{
3456 			conductGameChallenges[CONDUCT_HARDCORE] = 0;
3457 		}
3458 	}
3459 	if ( !conductGameChallenges[CONDUCT_CHEATS_ENABLED] )
3460 	{
3461 		if ( (svFlags & SV_FLAG_CHEATS) )
3462 		{
3463 			conductGameChallenges[CONDUCT_CHEATS_ENABLED] = 1;
3464 		}
3465 	}
3466 	if ( !conductGameChallenges[CONDUCT_MULTIPLAYER] )
3467 	{
3468 		if ( multiplayer != SINGLE )
3469 		{
3470 			conductGameChallenges[CONDUCT_MULTIPLAYER] = 1;
3471 		}
3472 	}
3473 	if ( !conductGameChallenges[CONDUCT_CLASSIC_MODE] )
3474 	{
3475 		if ( (svFlags & SV_FLAG_CLASSIC) )
3476 		{
3477 			conductGameChallenges[CONDUCT_CLASSIC_MODE] = 1;
3478 		}
3479 	}
3480 	if ( !conductGameChallenges[CONDUCT_MODDED] )
3481 	{
3482 		if ( gamemods_numCurrentModsLoaded > 0 )
3483 		{
3484 			conductGameChallenges[CONDUCT_MODDED] = 1;
3485 		}
3486 	}
3487 
3488 	achievementObserver.achievementTimersTickDown();
3489 }
3490 
updateGameplayStatisticsInMainLoop()3491 void updateGameplayStatisticsInMainLoop()
3492 {
3493 	// local player only here.
3494 	if ( gameStatistics[STATISTICS_BOMB_SQUAD] >= 5 )
3495 	{
3496 		steamAchievement("BARONY_ACH_BOMB_SQUAD");
3497 	}
3498 	if ( gameStatistics[STATISTICS_SITTING_DUCK] >= 10 )
3499 	{
3500 		steamAchievement("BARONY_ACH_SITTING_DUCK");
3501 	}
3502 	if ( gameStatistics[STATISTICS_YES_WE_CAN] >= 10 )
3503 	{
3504 		steamAchievement("BARONY_ACH_YES_WE_CAN");
3505 	}
3506 	if ( gameStatistics[STATISTICS_FIRE_MAYBE_DIFFERENT] >= 2 )
3507 	{
3508 		steamAchievement("BARONY_ACH_FIRE_MAYBE_DIFFERENT");
3509 	}
3510 	if ( gameStatistics[STATISTICS_HEAL_BOT] >= 1000 )
3511 	{
3512 		steamAchievement("BARONY_ACH_HEAL_BOT");
3513 	}
3514 	if ( gameStatistics[STATISTICS_HOT_TUB_TIME_MACHINE] >= 50 )
3515 	{
3516 		steamAchievement("BARONY_ACH_HOT_TUB");
3517 	}
3518 	if ( gameStatistics[STATISTICS_FUNCTIONAL] >= 10 )
3519 	{
3520 		steamAchievement("BARONY_ACH_FUNCTIONAL");
3521 	}
3522 	if ( gameStatistics[STATISTICS_OHAI_MARK] >= 20 )
3523 	{
3524 		steamAchievement("BARONY_ACH_OHAI_MARK");
3525 	}
3526 	if ( gameStatistics[STATISTICS_PIMPING_AINT_EASY] >= 6 )
3527 	{
3528 		steamAchievement("BARONY_ACH_PIMPIN");
3529 	}
3530 	if ( gameStatistics[STATISTICS_TRIBE_SUBSCRIBE] >= 4 )
3531 	{
3532 		steamAchievement("BARONY_ACH_TRIBE_SUBSCRIBE");
3533 	}
3534 	if ( gameStatistics[STATISTICS_FORUM_TROLL] > 0 )
3535 	{
3536 		int walls = gameStatistics[STATISTICS_FORUM_TROLL] & 0xFF;
3537 		int trolls = ((gameStatistics[STATISTICS_FORUM_TROLL] >> 8) & 0xFF);
3538 		int fears = ((gameStatistics[STATISTICS_FORUM_TROLL] >> 16) & 0xFF);
3539 		if ( walls == 3 && trolls == 3 && fears == 3 )
3540 		{
3541 			steamAchievement("BARONY_ACH_FORUM_TROLL");
3542 		}
3543 	}
3544 
3545 	if ( gameStatistics[STATISTICS_TEMPT_FATE] == -1 )
3546 	{
3547 		steamAchievement("BARONY_ACH_TEMPT_FATE");
3548 	}
3549 	else if ( gameStatistics[STATISTICS_TEMPT_FATE] > 0 )
3550 	{
3551 		// tick down 5 sec counter for achievement, this function called once per second.
3552 		--gameStatistics[STATISTICS_TEMPT_FATE];
3553 		if ( gameStatistics[STATISTICS_TEMPT_FATE] < 0 )
3554 		{
3555 			gameStatistics[STATISTICS_TEMPT_FATE] = 0;
3556 		}
3557 	}
3558 
3559 	if ( gameStatistics[STATISTICS_ALCHEMY_RECIPES] != 0 && clientLearnedAlchemyIngredients.empty() )
3560 	{
3561 		int numpotions = static_cast<int>(potionStandardAppearanceMap.size());
3562 		for ( int i = 0; i < numpotions; ++i )
3563 		{
3564 			bool learned = gameStatistics[STATISTICS_ALCHEMY_RECIPES] & (1 << i);
3565 			if ( learned )
3566 			{
3567 				auto typeAppearance = potionStandardAppearanceMap.at(i);
3568 				int type = typeAppearance.first;
3569 				clientLearnedAlchemyIngredients.insert(type);
3570 			}
3571 		}
3572 	}
3573 
3574 	if ( (ticks % (TICKS_PER_SECOND * 8) == 0) && gameStatistics[STATISTICS_ALCHEMY_RECIPES] != 0 )
3575 	{
3576 		int numpotions = static_cast<int>(potionStandardAppearanceMap.size());
3577 		bool failAchievement = false;
3578 		for ( int i = 0; i < numpotions; ++i )
3579 		{
3580 			bool learned = gameStatistics[STATISTICS_ALCHEMY_RECIPES] & (1 << i);
3581 			auto typeAppearance = potionStandardAppearanceMap.at(i);
3582 			int type = typeAppearance.first;
3583 			if ( !learned && (GenericGUI.isItemBaseIngredient(type) || GenericGUI.isItemSecondaryIngredient(type)) )
3584 			{
3585 				failAchievement = true;
3586 				break;
3587 			}
3588 		}
3589 		if ( !failAchievement )
3590 		{
3591 			steamAchievement("BARONY_ACH_MIXOLOGIST");
3592 		}
3593 	}
3594 
3595 	if ( (ticks % (TICKS_PER_SECOND * 8) == 0) && (gameStatistics[STATISTICS_POP_QUIZ_1] != 0 || gameStatistics[STATISTICS_POP_QUIZ_2] != 0) )
3596 	{
3597 		int numSpellsCast = 0;
3598 		int stat1 = gameStatistics[STATISTICS_POP_QUIZ_1];
3599 		int stat2 = gameStatistics[STATISTICS_POP_QUIZ_1];
3600 		for ( int i = 0; i < 30; ++i )
3601 		{
3602 			// count the bits set.
3603 			numSpellsCast += (stat1 & 1);
3604 			numSpellsCast += (stat2 & 1);
3605 			stat1 = stat1 >> 1;
3606 			stat2 = stat2 >> 1;
3607 		}
3608 		if ( numSpellsCast >= 20 )
3609 		{
3610 			steamAchievement("BARONY_ACH_POP_QUIZ");
3611 		}
3612 	}
3613 
3614 	if ( ticks % (TICKS_PER_SECOND * 10) == 0 )
3615 	{
3616 		for ( int i = 0; i < MAXPLAYERS; ++i )
3617 		{
3618 			if ( (i == clientnum) || (multiplayer == SERVER && i != clientnum) )
3619 			{
3620 				// clients update their own total, server can update clients.
3621 				if ( achievementObserver.playerAchievements[i].torchererScrap > 0 )
3622 				{
3623 					if ( i == clientnum )
3624 					{
3625 						steamStatisticUpdate(STEAM_STAT_TORCHERER, STEAM_STAT_INT, achievementObserver.playerAchievements[i].torchererScrap);
3626 					}
3627 					else
3628 					{
3629 						steamStatisticUpdateClient(i, STEAM_STAT_TORCHERER, STEAM_STAT_INT,
3630 							achievementObserver.playerAchievements[i].torchererScrap);
3631 					}
3632 					achievementObserver.playerAchievements[i].torchererScrap = 0;
3633 				}
3634 				if ( achievementObserver.playerAchievements[i].superShredder > 0 )
3635 				{
3636 					if ( i == clientnum )
3637 					{
3638 						steamStatisticUpdate(STEAM_STAT_SUPER_SHREDDER, STEAM_STAT_INT, achievementObserver.playerAchievements[i].superShredder);
3639 					}
3640 					else
3641 					{
3642 						steamStatisticUpdateClient(i, STEAM_STAT_SUPER_SHREDDER, STEAM_STAT_INT, achievementObserver.playerAchievements[i].superShredder);
3643 					}
3644 					achievementObserver.playerAchievements[i].superShredder = 0;
3645 				}
3646 				if ( achievementObserver.playerAchievements[i].fixerUpper > 0 )
3647 				{
3648 					if ( i == clientnum )
3649 					{
3650 						steamStatisticUpdate(STEAM_STAT_FIXER_UPPER, STEAM_STAT_INT, achievementObserver.playerAchievements[i].fixerUpper);
3651 					}
3652 					else
3653 					{
3654 						steamStatisticUpdateClient(i, STEAM_STAT_FIXER_UPPER, STEAM_STAT_INT, achievementObserver.playerAchievements[i].fixerUpper);
3655 					}
3656 					achievementObserver.playerAchievements[i].fixerUpper = 0;
3657 				}
3658 			}
3659 		}
3660 	}
3661 
3662 	if ( ticks % (TICKS_PER_SECOND * 5) == 0 )
3663 	{
3664 		std::unordered_set<int> potionList;
3665 		std::unordered_set<int> ammoList;
3666 		std::unordered_set<int> bowList;
3667 		std::unordered_set<int> utilityBeltList;
3668 		int badAndBeautiful = -1;
3669 		if ( stats[clientnum]->appearance == 0 && (stats[clientnum]->type == INCUBUS || stats[clientnum]->type == SUCCUBUS) )
3670 		{
3671 			if ( stats[clientnum]->playerRace == RACE_INCUBUS || stats[clientnum]->playerRace == RACE_SUCCUBUS )
3672 			{
3673 				badAndBeautiful = 0;
3674 			}
3675 		}
3676 		int dummy1 = 0;
3677 		int dummy2 = 0;
3678 		for ( node_t* node = stats[clientnum]->inventory.first; node != nullptr; node = node->next )
3679 		{
3680 			Item* item = (Item*)node->element;
3681 			if ( item )
3682 			{
3683 				if ( itemCategory(item) == POTION )
3684 				{
3685 					switch ( item->type )
3686 					{
3687 						case POTION_EMPTY:
3688 						case POTION_THUNDERSTORM:
3689 						case POTION_ICESTORM:
3690 						case POTION_STRENGTH:
3691 						case POTION_FIRESTORM:
3692 							// do nothing, these are brewed only potions
3693 							break;
3694 						default:
3695 							potionList.insert(item->type);
3696 							break;
3697 					}
3698 				}
3699 				else if ( client_classes[clientnum] == CLASS_HUNTER && itemTypeIsQuiver(item->type) )
3700 				{
3701 					ammoList.insert(item->type);
3702 				}
3703 				else if ( client_classes[clientnum] == CLASS_HUNTER && isRangedWeapon(*item) )
3704 				{
3705 					if ( item->type == CROSSBOW || item->type == HEAVY_CROSSBOW )
3706 					{
3707 						bowList.insert(CROSSBOW);
3708 					}
3709 					else if ( item->type == SHORTBOW || item->type == LONGBOW || item->type == COMPOUND_BOW )
3710 					{
3711 						bowList.insert(SHORTBOW);
3712 					}
3713 				}
3714 				if ( GenericGUI.tinkeringGetCraftingCost(item, &dummy1, &dummy2) )
3715 				{
3716 					utilityBeltList.insert(item->type);
3717 				}
3718 				if ( badAndBeautiful >= 0 && item->identified && item->beatitude < 0 && itemIsEquipped(item, clientnum) )
3719 				{
3720 					++badAndBeautiful;
3721 				}
3722 			}
3723 		}
3724 		if ( potionList.size() >= 16 )
3725 		{
3726 			steamAchievement("BARONY_ACH_POTION_PREPARATION");
3727 		}
3728 		if ( ammoList.size() >= 7 && bowList.size() >= 2 )
3729 		{
3730 			steamAchievement("BARONY_ACH_ARSENAL");
3731 		}
3732 		if ( utilityBeltList.size() >= 16 )
3733 		{
3734 			steamAchievement("BARONY_ACH_UTILITY_BELT");
3735 		}
3736 		if ( badAndBeautiful >= 4 )
3737 		{
3738 			steamAchievement("BARONY_ACH_BAD_BEAUTIFUL");
3739 		}
3740 
3741 		if ( multiplayer != CLIENT )
3742 		{
3743 			for ( int i = 0; i < MAXPLAYERS; ++i )
3744 			{
3745 				// server only will have these numbers here.
3746 				if ( achievementObserver.playerAchievements[i].ifYouLoveSomething > 0 )
3747 				{
3748 					steamStatisticUpdateClient(i, STEAM_STAT_IF_YOU_LOVE_SOMETHING, STEAM_STAT_INT, achievementObserver.playerAchievements[i].ifYouLoveSomething);
3749 					achievementObserver.playerAchievements[i].ifYouLoveSomething = 0;
3750 				}
3751 				if ( achievementObserver.playerAchievements[i].socialButterfly > 0 )
3752 				{
3753 					steamStatisticUpdateClient(i, STEAM_STAT_SOCIAL_BUTTERFLY, STEAM_STAT_INT, achievementObserver.playerAchievements[i].socialButterfly);
3754 					achievementObserver.playerAchievements[i].socialButterfly = 0;
3755 				}
3756 				if ( achievementObserver.playerAchievements[i].trashCompactor > 0 )
3757 				{
3758 					steamStatisticUpdateClient(i, STEAM_STAT_TRASH_COMPACTOR, STEAM_STAT_INT, achievementObserver.playerAchievements[i].trashCompactor);
3759 					achievementObserver.playerAchievements[i].trashCompactor = 0;
3760 				}
3761 			}
3762 		}
3763 	}
3764 }
3765 
setSaveGameFileName(bool singleplayer,bool followersFile,int saveIndex)3766 std::string setSaveGameFileName(bool singleplayer, bool followersFile, int saveIndex)
3767 {
3768 	std::string filename = "savegames/savegame" + std::to_string(saveIndex);
3769 
3770 	//OLD FORMAT
3771 	//#define SAVEGAMEFILE "savegame.dat"
3772 	//#define SAVEGAMEFILE2 "savegame2.dat" // saves follower data
3773 	//#define SAVEGAMEFILE_MULTIPLAYER "savegame_multiplayer.dat"
3774 	//#define SAVEGAMEFILE2_MULTIPLAYER "savegame2_multiplayer.dat" // saves follower data
3775 	//#define SAVEGAMEFILE_MODDED "savegame_modded.dat"
3776 	//#define SAVEGAMEFILE2_MODDED "savegame2_modded.dat"
3777 	//#define SAVEGAMEFILE_MODDED_MULTIPLAYER "savegame_modded_multiplayer.dat"
3778 	//#define SAVEGAMEFILE2_MODDED_MULTIPLAYER "savegame2_modded_multiplayer.dat"
3779 
3780 	if ( !followersFile )
3781 	{
3782 		if ( singleplayer )
3783 		{
3784 			if ( gamemods_numCurrentModsLoaded == -1 )
3785 			{
3786 				filename.append(".dat");
3787 			}
3788 			else
3789 			{
3790 				filename.append("_modded.dat");
3791 			}
3792 		}
3793 		else
3794 		{
3795 			if ( gamemods_numCurrentModsLoaded == -1 )
3796 			{
3797 				filename.append("_mp.dat");
3798 			}
3799 			else
3800 			{
3801 				filename.append("_mp_modded.dat");
3802 			}
3803 		}
3804 	}
3805 	else
3806 	{
3807 		if ( singleplayer )
3808 		{
3809 			if ( gamemods_numCurrentModsLoaded == -1 )
3810 			{
3811 				filename.append("_npcs.dat");
3812 			}
3813 			else
3814 			{
3815 				filename.append("_npcs_modded.dat");
3816 			}
3817 		}
3818 		else
3819 		{
3820 			if ( gamemods_numCurrentModsLoaded == -1 )
3821 			{
3822 				filename.append("_mp_npcs.dat");
3823 			}
3824 			else
3825 			{
3826 				filename.append("_mp_npcs_modded.dat");
3827 			}
3828 		}
3829 	}
3830 	return filename;
3831 }
3832 
anySaveFileExists()3833 bool anySaveFileExists()
3834 {
3835 	for ( int fileNumber = 0; fileNumber < SAVE_GAMES_MAX; ++fileNumber )
3836 	{
3837 		if ( saveGameExists(true, fileNumber) )
3838 		{
3839 			return true;
3840 		}
3841 	}
3842 	for ( int fileNumber = 0; fileNumber < SAVE_GAMES_MAX; ++fileNumber )
3843 	{
3844 		if ( saveGameExists(false, fileNumber) )
3845 		{
3846 			return true;
3847 		}
3848 	}
3849 	return false;
3850 }
3851 
updateAchievementRhythmOfTheKnight(int player,Entity * target,bool playerIsHit)3852 void updateAchievementRhythmOfTheKnight(int player, Entity* target, bool playerIsHit)
3853 {
3854 	if ( achievementStatusRhythmOfTheKnight[player] || multiplayer == CLIENT
3855 		|| player < 0 || player >= MAXPLAYERS )
3856 	{
3857 		return;
3858 	}
3859 
3860 	Uint32 targetUid = target->getUID();
3861 
3862 	if ( !playerIsHit )
3863 	{
3864 		// player attacking a monster, needs to be after a block (vec size 1, 3 or 5)
3865 		if ( !achievementRhythmOfTheKnightVec[player].empty() )
3866 		{
3867 			if ( achievementRhythmOfTheKnightVec[player].at(0).second != targetUid )
3868 			{
3869 				// check first uid entry, if not matching the monster, we swapped targets and should reset.
3870 				achievementRhythmOfTheKnightVec[player].clear();
3871 				//messagePlayer(0, "cleared, not attacking same target");
3872 				return;
3873 			}
3874 			else
3875 			{
3876 				int size = achievementRhythmOfTheKnightVec[player].size();
3877 				if ( size % 2 == 1 ) // 1, 3, 5
3878 				{
3879 					// we're on correct sequence and same monster, add entry to vector.
3880 					achievementRhythmOfTheKnightVec[player].push_back(std::make_pair(target->ticks, targetUid));
3881 					if ( size == 5 )
3882 					{
3883 						// we pushed back to a total of 6 entries, get achievement.
3884 						real_t timeTaken = (achievementRhythmOfTheKnightVec[player].at(5).first - achievementRhythmOfTheKnightVec[player].at(0).first) / 50.f;
3885 						if ( timeTaken <= 3 )
3886 						{
3887 							//messagePlayer(0, "achievement get!, time taken %f", timeTaken);
3888 							achievementStatusRhythmOfTheKnight[player] = true;
3889 							steamAchievementClient(player, "BARONY_ACH_RHYTHM_OF_THE_KNIGHT");
3890 						}
3891 						achievementRhythmOfTheKnightVec[player].clear();
3892 					}
3893 				}
3894 				else
3895 				{
3896 					// we attacked twice and we're out of sequence.
3897 					achievementRhythmOfTheKnightVec[player].clear();
3898 					//messagePlayer(0, "cleared, out of sequence");
3899 					return;
3900 				}
3901 			}
3902 		}
3903 	}
3904 	else
3905 	{
3906 		// rhythm is initiated on first successful block
3907 		if ( achievementRhythmOfTheKnightVec[player].empty() )
3908 		{
3909 			achievementRhythmOfTheKnightVec[player].push_back(std::make_pair(target->ticks, targetUid));
3910 		}
3911 		else
3912 		{
3913 			if ( achievementRhythmOfTheKnightVec[player].at(0).second != targetUid )
3914 			{
3915 				// check first uid entry, if not matching the monster, we swapped targets and should reset.
3916 				achievementRhythmOfTheKnightVec[player].clear();
3917 				//messagePlayer(0, "cleared, not blocking same target");
3918 			}
3919 			int size = achievementRhythmOfTheKnightVec[player].size();
3920 			if ( size == 1 || size == 3 || size == 5 )
3921 			{
3922 				achievementRhythmOfTheKnightVec[player].clear();
3923 				//messagePlayer(0, "cleared, out of sequence");
3924 			}
3925 			achievementRhythmOfTheKnightVec[player].push_back(std::make_pair(target->ticks, targetUid));
3926 		}
3927 	}
3928 }
3929 
updateAchievementBaitAndSwitch(int player,bool isTeleporting)3930 void updateAchievementBaitAndSwitch(int player, bool isTeleporting)
3931 {
3932 	if ( player < 0 || player >= MAXPLAYERS )
3933 	{
3934 		return;
3935 	}
3936 	if ( !stats[player] || stats[player]->playerRace != RACE_SUCCUBUS || achievementStatusBaitAndSwitch[player] || multiplayer == CLIENT )
3937 	{
3938 		return;
3939 	}
3940 
3941 	if ( stats[player]->playerRace == RACE_SUCCUBUS && stats[player]->appearance != 0 )
3942 	{
3943 		return;
3944 	}
3945 
3946 	if ( !isTeleporting )
3947 	{
3948 		achievementBaitAndSwitchTimer[player] = ticks;
3949 	}
3950 	else
3951 	{
3952 		if ( achievementBaitAndSwitchTimer[player] > 0 && (ticks - achievementBaitAndSwitchTimer[player]) <= TICKS_PER_SECOND )
3953 		{
3954 			achievementStatusBaitAndSwitch[player] = true;
3955 			steamAchievementClient(player, "BARONY_ACH_BAIT_AND_SWITCH");
3956 		}
3957 	}
3958 }
3959 
updateAchievementThankTheTank(int player,Entity * target,bool targetKilled)3960 void updateAchievementThankTheTank(int player, Entity* target, bool targetKilled)
3961 {
3962 	if ( player < 0 || player >= MAXPLAYERS )
3963 	{
3964 		return;
3965 	}
3966 	if ( achievementStatusThankTheTank[player] || multiplayer == CLIENT )
3967 	{
3968 		return;
3969 	}
3970 
3971 	if ( !targetKilled )
3972 	{
3973 		achievementThankTheTankPair[player] = std::make_pair(ticks, target->getUID()); // track the monster UID defending against
3974 		//messagePlayer(0, "pair: %d, %d", achievementThankTheTankPair[player].first, achievementThankTheTankPair[player].second);
3975 	}
3976 	else if ( achievementThankTheTankPair[player].first != 0
3977 		&& achievementThankTheTankPair[player].second != 0 ) // check there is a ticks/UID entry.
3978 	{
3979 		if ( players[player] && players[player]->entity )
3980 		{
3981 			if ( players[player]->entity->checkEnemy(target) )
3982 			{
3983 				if ( target->getUID() == achievementThankTheTankPair[player].second )
3984 				{
3985 					// same target dying, check timestamp within 3 seconds.
3986 					if ( (ticks - achievementThankTheTankPair[player].first) / 50.f < 3.f )
3987 					{
3988 						achievementStatusThankTheTank[player] = true;
3989 					}
3990 				}
3991 			}
3992 		}
3993 	}
3994 }
3995 
3996 #ifdef STEAMWORKS
3997 
steamLeaderboardSetScore(score_t * score)3998 bool steamLeaderboardSetScore(score_t* score)
3999 {
4000 	if ( !g_SteamLeaderboards )
4001 	{
4002 		return false;
4003 	}
4004 
4005 	if ( !score )
4006 	{
4007 		return false;
4008 	}
4009 
4010 	if ( score->victory == 0 )
4011 	{
4012 		return false;
4013 	}
4014 
4015 	if ( score->conductGameChallenges[CONDUCT_CHEATS_ENABLED]
4016 		|| gamemods_disableSteamAchievements
4017 		|| score->conductGameChallenges[CONDUCT_LIFESAVING] )
4018 	{
4019 		return false;
4020 	}
4021 	if ( score->gameStatistics[STATISTICS_DISABLE_UPLOAD] == 1 )
4022 	{
4023 		return false;
4024 	}
4025 
4026 	bool monster = false;
4027 	if ( score->stats && score->stats->playerRace > 0 && score->stats->appearance == 0 )
4028 	{
4029 		monster = true;
4030 	}
4031 
4032 	if ( !score->conductGameChallenges[CONDUCT_MULTIPLAYER] )
4033 	{
4034 		// single player
4035 		if ( !score->conductGameChallenges[CONDUCT_HARDCORE] )
4036 		{
4037 			if ( score->victory == 2 )
4038 			{
4039 				if ( monster )
4040 				{
4041 					g_SteamLeaderboards->LeaderboardUpload.boardIndex = LEADERBOARD_DLC_HELL_TIME;
4042 				}
4043 				else
4044 				{
4045 					g_SteamLeaderboards->LeaderboardUpload.boardIndex = LEADERBOARD_HELL_TIME;
4046 				}
4047 			}
4048 			else if ( score->victory == 3 )
4049 			{
4050 				if ( monster )
4051 				{
4052 					g_SteamLeaderboards->LeaderboardUpload.boardIndex = LEADERBOARD_DLC_NORMAL_TIME;
4053 				}
4054 				else
4055 				{
4056 					g_SteamLeaderboards->LeaderboardUpload.boardIndex = LEADERBOARD_NORMAL_TIME;
4057 				}
4058 			}
4059 			else if ( score->victory == 1 )
4060 			{
4061 				if ( monster )
4062 				{
4063 					g_SteamLeaderboards->LeaderboardUpload.boardIndex = LEADERBOARD_DLC_CLASSIC_TIME;
4064 				}
4065 				else
4066 				{
4067 					g_SteamLeaderboards->LeaderboardUpload.boardIndex = LEADERBOARD_CLASSIC_TIME;
4068 				}
4069 			}
4070 		}
4071 		else if ( score->conductGameChallenges[CONDUCT_HARDCORE] )
4072 		{
4073 			if ( score->victory == 3 )
4074 			{
4075 				if ( monster )
4076 				{
4077 					g_SteamLeaderboards->LeaderboardUpload.boardIndex = LEADERBOARD_DLC_HARDCORE_TIME;
4078 				}
4079 				else
4080 				{
4081 					g_SteamLeaderboards->LeaderboardUpload.boardIndex = LEADERBOARD_HARDCORE_TIME;
4082 				}
4083 			}
4084 			else
4085 			{
4086 				if ( monster )
4087 				{
4088 					g_SteamLeaderboards->LeaderboardUpload.boardIndex = LEADERBOARD_DLC_CLASSIC_HARDCORE_TIME;
4089 				}
4090 				else
4091 				{
4092 					g_SteamLeaderboards->LeaderboardUpload.boardIndex = LEADERBOARD_CLASSIC_HARDCORE_TIME;
4093 				}
4094 			}
4095 		}
4096 	}
4097 	else if ( score->conductGameChallenges[CONDUCT_MULTIPLAYER] )
4098 	{
4099 		// multiplayer
4100 		if ( score->victory == 2 )
4101 		{
4102 			if ( monster )
4103 			{
4104 				g_SteamLeaderboards->LeaderboardUpload.boardIndex = LEADERBOARD_DLC_MULTIPLAYER_HELL_TIME;
4105 			}
4106 			else
4107 			{
4108 				g_SteamLeaderboards->LeaderboardUpload.boardIndex = LEADERBOARD_MULTIPLAYER_HELL_TIME;
4109 			}
4110 		}
4111 		else if ( score->victory == 3 )
4112 		{
4113 			if ( monster )
4114 			{
4115 				g_SteamLeaderboards->LeaderboardUpload.boardIndex = LEADERBOARD_DLC_MULTIPLAYER_TIME;
4116 			}
4117 			else
4118 			{
4119 				g_SteamLeaderboards->LeaderboardUpload.boardIndex = LEADERBOARD_MULTIPLAYER_TIME;
4120 			}
4121 		}
4122 		else if ( score->victory == 1 )
4123 		{
4124 			if ( monster )
4125 			{
4126 				g_SteamLeaderboards->LeaderboardUpload.boardIndex = LEADERBOARD_DLC_MULTIPLAYER_CLASSIC_TIME;
4127 			}
4128 			else
4129 			{
4130 				g_SteamLeaderboards->LeaderboardUpload.boardIndex = LEADERBOARD_MULTIPLAYER_CLASSIC_TIME;
4131 			}
4132 		}
4133 	}
4134 	else
4135 	{
4136 		g_SteamLeaderboards->LeaderboardUpload.boardIndex = LEADERBOARD_NONE;
4137 	}
4138 
4139 	if ( g_SteamLeaderboards->LeaderboardUpload.boardIndex == LEADERBOARD_NONE )
4140 	{
4141 		return false;
4142 	}
4143 
4144 	// assemble the score tags.
4145 	//int completionTime = score->completionTime;
4146 	int c = 0;
4147 	int tag = TAG_MONSTER_KILLS_1;
4148 	int i = 0;
4149 	int tagWidth = 8;
4150 	for ( c = 0; c < NUMMONSTERS; ++c )
4151 	{
4152 		g_SteamLeaderboards->LeaderboardUpload.tags[tag] |= (static_cast<Uint8>(score->kills[c]) << (i * tagWidth));
4153 		++i;
4154 		if ( i >((32 / tagWidth) - 1) )
4155 		{
4156 			i = 0;
4157 			++tag;
4158 		}
4159 	}
4160 
4161 	i = 0;
4162 	tagWidth = 8;
4163 	tag = TAG_NAME1;
4164 	for ( c = 0; c < std::min(32, (int)(strlen(score->stats->name))); ++c )
4165 	{
4166 		g_SteamLeaderboards->LeaderboardUpload.tags[tag] |= (Uint8)(score->stats->name[c]) << (i * tagWidth);
4167 		++i;
4168 		if ( i > ((32 / tagWidth) - 1) )
4169 		{
4170 			i = 0;
4171 			++tag;
4172 		}
4173 	}
4174 
4175 	tagWidth = 8;
4176 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_RACESEXAPPEARANCECLASS] |= score->stats->type;
4177 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_RACESEXAPPEARANCECLASS] |= (score->stats->sex) << (tagWidth * 1);
4178 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_RACESEXAPPEARANCECLASS] |= (score->stats->appearance) << (tagWidth * 2);
4179 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_RACESEXAPPEARANCECLASS] |= (score->classnum) << (tagWidth * 3);
4180 
4181 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_VICTORYDUNGEONLEVELCONDUCTORIGINAL] |= (score->victory);
4182 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_VICTORYDUNGEONLEVELCONDUCTORIGINAL] |= (score->dungeonlevel) << (tagWidth);
4183 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_VICTORYDUNGEONLEVELCONDUCTORIGINAL] |= (score->conductPenniless) << (tagWidth * 2);
4184 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_VICTORYDUNGEONLEVELCONDUCTORIGINAL] |= (score->conductFoodless) << (tagWidth * 2 + 1);
4185 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_VICTORYDUNGEONLEVELCONDUCTORIGINAL] |= (score->conductVegetarian) << (tagWidth * 2 + 2);
4186 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_VICTORYDUNGEONLEVELCONDUCTORIGINAL] |= (score->conductIlliterate) << (tagWidth * 2 + 3);
4187 
4188 	tag = TAG_CONDUCT_2W_1;
4189 	tagWidth = 16;
4190 	i = 0;
4191 	for ( c = 0; c < 32; ++c )
4192 	{
4193 		if ( c < 16 )
4194 		{
4195 			g_SteamLeaderboards->LeaderboardUpload.tags[tag] |= score->conductGameChallenges[c] << (c * 2);
4196 		}
4197 		else
4198 		{
4199 			g_SteamLeaderboards->LeaderboardUpload.tags[tag] |= score->conductGameChallenges[c] << ((16 - c) * 2);
4200 		}
4201 		if ( c == 15 )
4202 		{
4203 			++tag;
4204 		}
4205 	}
4206 
4207 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_CONDUCT_4W_1] |= (Uint8)(score->stats->playerRace); // store in right-most 8 bits.
4208 	// conducts TAG_CONDUCT_4W_2 to TAG_CONDUCT_4W_4 unused.
4209 
4210 	// store new gameplay stats as required. not many to start with.
4211 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_GAMEPLAY_STATS_2W_1] |= std::min(3, score->gameStatistics[STATISTICS_FIRE_MAYBE_DIFFERENT]);
4212 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_GAMEPLAY_STATS_2W_1] |= std::min(3, score->gameStatistics[STATISTICS_SITTING_DUCK]) << 2;
4213 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_GAMEPLAY_STATS_2W_1] |= std::min(3, score->gameStatistics[STATISTICS_TEMPT_FATE]) << 4;
4214 
4215 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_GAMEPLAY_STATS_8W_1] |= (Uint8)score->gameStatistics[STATISTICS_BOMB_SQUAD];
4216 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_GAMEPLAY_STATS_8W_1] |= (Uint8)score->gameStatistics[STATISTICS_HOT_TUB_TIME_MACHINE] << 8;
4217 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_GAMEPLAY_STATS_8W_1] |= (Uint8)score->gameStatistics[STATISTICS_YES_WE_CAN] << 16;
4218 
4219 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_GAMEPLAY_STATS_16W_1] |= (Uint16)score->gameStatistics[STATISTICS_HEAL_BOT];
4220 
4221 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_HEALTH] |= (Uint16)score->stats->MAXHP;
4222 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_HEALTH] |= (Uint16)score->stats->HP << 16;
4223 
4224 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_MANA] |= (Uint16)score->stats->MAXMP;
4225 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_MANA] |= (Uint16)score->stats->MP << 16;
4226 
4227 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_STRDEXCONINT] |= (Uint8)score->stats->STR;
4228 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_STRDEXCONINT] |= (Uint8)score->stats->DEX << 8;
4229 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_STRDEXCONINT] |= (Uint8)score->stats->CON << 16;
4230 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_STRDEXCONINT] |= (Uint8)score->stats->INT << 24;
4231 
4232 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_PERCHREXPLVL] |= (Uint8)score->stats->PER;
4233 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_PERCHREXPLVL] |= (Uint8)score->stats->CHR << 8;
4234 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_PERCHREXPLVL] |= (Uint8)score->stats->EXP << 16;
4235 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_PERCHREXPLVL] |= (Uint8)score->stats->LVL << 24;
4236 
4237 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_GOLD] |= score->stats->GOLD;
4238 
4239 	tagWidth = 8;
4240 	tag = TAG_PROFICIENCY1;
4241 	i = 0;
4242 	for ( c = 0; c < NUMPROFICIENCIES; ++c )
4243 	{
4244 		g_SteamLeaderboards->LeaderboardUpload.tags[tag] |= score->stats->PROFICIENCIES[c] << (i * tagWidth);
4245 		++i;
4246 		if ( i > ((32 / tagWidth) - 1) )
4247 		{
4248 			i = 0;
4249 			++tag;
4250 		}
4251 	}
4252 
4253 	if ( score->stats->helmet )
4254 	{
4255 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT1] |= (Uint8)(score->stats->helmet->type);
4256 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT_BEATITUDE1] |= (Sint16)(score->stats->helmet->beatitude);
4257 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT_APPEARANCE] |=
4258 			(score->stats->helmet->appearance % items[score->stats->helmet->type].variations);
4259 	}
4260 	if ( score->stats->breastplate )
4261 	{
4262 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT1] |= (Uint8)(score->stats->breastplate->type) << 8;
4263 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT_BEATITUDE1] |= (Sint16)(score->stats->breastplate->beatitude) << 8;
4264 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT_APPEARANCE] |=
4265 			(score->stats->breastplate->appearance % items[score->stats->breastplate->type].variations) << 8;
4266 	}
4267 	if ( score->stats->gloves )
4268 	{
4269 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT1] |= (Uint8)(score->stats->gloves->type) << 16;
4270 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT_BEATITUDE1] |= (Sint16)(score->stats->gloves->beatitude) << 16;
4271 	}
4272 	if ( score->stats->shoes )
4273 	{
4274 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT1] |= (Uint8)(score->stats->shoes->type) << 24;
4275 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT_BEATITUDE1] |= (Sint16)(score->stats->shoes->beatitude) << 24;
4276 	}
4277 
4278 	if ( score->stats->shield )
4279 	{
4280 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT2] |= (Uint8)(score->stats->shield->type);
4281 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT_BEATITUDE2] |= (Sint16)(score->stats->shield->beatitude);
4282 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT_APPEARANCE] |=
4283 			(score->stats->shield->appearance % items[score->stats->shield->type].variations) << 12;
4284 	}
4285 	if ( score->stats->weapon )
4286 	{
4287 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT2] |= (Uint8)(score->stats->weapon->type) << 8;
4288 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT_BEATITUDE2] |= (Sint16)(score->stats->weapon->beatitude) << 8;
4289 	}
4290 	if ( score->stats->cloak )
4291 	{
4292 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT2] |= (Uint8)(score->stats->cloak->type) << 16;
4293 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT_BEATITUDE2] |= (Sint16)(score->stats->cloak->beatitude) << 16;
4294 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT_APPEARANCE] |=
4295 			(score->stats->cloak->appearance % items[score->stats->cloak->type].variations) << 4;
4296 	}
4297 	if ( score->stats->amulet )
4298 	{
4299 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT2] |= (Uint8)(score->stats->amulet->type) << 24;
4300 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT_BEATITUDE2] |= (Sint16)(score->stats->amulet->beatitude) << 16;
4301 	}
4302 
4303 	if ( score->stats->ring )
4304 	{
4305 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT3] |= (Uint8)(score->stats->ring->type);
4306 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT_BEATITUDE3] |= (Sint16)(score->stats->ring->beatitude);
4307 	}
4308 	if ( score->stats->mask )
4309 	{
4310 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT3] |= (Uint8)(score->stats->mask->type) << 8;
4311 		g_SteamLeaderboards->LeaderboardUpload.tags[TAG_EQUIPMENT_BEATITUDE3] |= (Sint16)(score->stats->mask->beatitude);
4312 	}
4313 
4314 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_TOTAL_SCORE] = totalScore(score);
4315 	g_SteamLeaderboards->LeaderboardUpload.tags[TAG_COMPLETION_TIME] = score->completionTime / TICKS_PER_SECOND;
4316 	return true;
4317 }
4318 
steamLeaderboardReadScore(int tags[CSteamLeaderboards::k_numLeaderboardTags])4319 bool steamLeaderboardReadScore(int tags[CSteamLeaderboards::k_numLeaderboardTags])
4320 {
4321 	stats[0]->clearStats();
4322 
4323 	int c = 0;
4324 	int tag = TAG_MONSTER_KILLS_1;
4325 	int tagWidth = 8;
4326 	int i = 0;
4327 	for ( c = 0; c < NUMMONSTERS; c++ )
4328 	{
4329 		kills[c] = ((tags[tag]) >> (i * tagWidth)) & 0xFF;
4330 		++i;
4331 		if ( i > ((32 / tagWidth) - 1) )
4332 		{
4333 			i = 0;
4334 			++tag;
4335 		}
4336 	}
4337 
4338 	i = 0;
4339 	tagWidth = 8;
4340 	tag = TAG_NAME1;
4341 	char name[33] = "";
4342 	for ( c = 0; c < 32; ++c )
4343 	{
4344 		name[c] = ((tags[tag]) >> (i * tagWidth)) & 0xFF;
4345 		if ( name[c] == 0 )
4346 		{
4347 			break;
4348 		}
4349 		++i;
4350 		if ( i > ((32 / tagWidth) - 1) )
4351 		{
4352 			i = 0;
4353 			++tag;
4354 		}
4355 	}
4356 	name[c] = '\0';
4357 	strcpy(stats[0]->name, name);
4358 
4359 
4360 	tagWidth = 8;
4361 	stats[0]->type = (Monster)(tags[TAG_RACESEXAPPEARANCECLASS] & 0xFF);
4362 	stats[0]->sex = (sex_t)((tags[TAG_RACESEXAPPEARANCECLASS] >> tagWidth) & 0xFF);
4363 	stats[0]->appearance = (tags[TAG_RACESEXAPPEARANCECLASS] >> tagWidth * 2) & 0xFF;
4364 	client_classes[0] = (tags[TAG_RACESEXAPPEARANCECLASS] >> tagWidth * 3) & 0xFF;
4365 
4366 	victory = (tags[TAG_VICTORYDUNGEONLEVELCONDUCTORIGINAL] >> tagWidth * 0) & 0xFF;
4367 	currentlevel = (tags[TAG_VICTORYDUNGEONLEVELCONDUCTORIGINAL] >> tagWidth * 1) & 0xFF;
4368 	conductPenniless = (tags[TAG_VICTORYDUNGEONLEVELCONDUCTORIGINAL] >> tagWidth * 2) & 1;
4369 	conductFoodless = (tags[TAG_VICTORYDUNGEONLEVELCONDUCTORIGINAL] >> (tagWidth * 2 + 1)) & 1;
4370 	conductVegetarian = (tags[TAG_VICTORYDUNGEONLEVELCONDUCTORIGINAL] >> (tagWidth * 2 + 2)) & 1;
4371 	conductIlliterate = (tags[TAG_VICTORYDUNGEONLEVELCONDUCTORIGINAL] >> (tagWidth * 2 + 3)) & 1;
4372 
4373 	tag = TAG_CONDUCT_2W_1;
4374 	tagWidth = 2;
4375 	i = 0;
4376 	for ( c = 0; c < 32; ++c )
4377 	{
4378 		if ( c < 16 )
4379 		{
4380 			conductGameChallenges[c] = (tags[tag] >> c * tagWidth) & 0b11;
4381 		}
4382 		else
4383 		{
4384 			conductGameChallenges[c] = (tags[tag] >> (16 - c) * tagWidth) & 0b11;
4385 		}
4386 		if ( c == 15 )
4387 		{
4388 			++tag;
4389 		}
4390 	}
4391 
4392 	stats[0]->playerRace = (tags[TAG_CONDUCT_4W_1] & 0xFF);
4393 
4394 	// conducts TAG_CONDUCT_4W_2 to TAG_CONDUCT_4W_4 unused.
4395 
4396 	// store new gameplay stats as required. not many to start with.
4397 	gameStatistics[STATISTICS_FIRE_MAYBE_DIFFERENT] = tags[TAG_GAMEPLAY_STATS_2W_1] & 0b11;
4398 	gameStatistics[STATISTICS_SITTING_DUCK] = (tags[TAG_GAMEPLAY_STATS_2W_1] >> 2) & 0b11;
4399 	gameStatistics[STATISTICS_TEMPT_FATE] = (tags[TAG_GAMEPLAY_STATS_2W_1] >> 4) & 0b11;
4400 
4401 	gameStatistics[STATISTICS_BOMB_SQUAD] = tags[TAG_GAMEPLAY_STATS_8W_1] & 0xFF;
4402 	gameStatistics[STATISTICS_HOT_TUB_TIME_MACHINE] = (tags[TAG_GAMEPLAY_STATS_8W_1] >> 8) & 0xFF;
4403 	gameStatistics[STATISTICS_YES_WE_CAN] = (tags[TAG_GAMEPLAY_STATS_8W_1] >> 16) & 0xFF;
4404 
4405 	gameStatistics[STATISTICS_HEAL_BOT] = tags[TAG_GAMEPLAY_STATS_16W_1] & 0xFFFF;
4406 
4407 	stats[0]->MAXHP = tags[TAG_HEALTH] & 0xFFFF;
4408 	stats[0]->HP = (tags[TAG_HEALTH] >> 16) & 0xFFFF;
4409 	stats[0]->MAXMP = tags[TAG_MANA] & 0xFFFF;
4410 	stats[0]->MP = (tags[TAG_MANA] >> 16) & 0xFFFF;
4411 	stats[0]->STR = (tags[TAG_STRDEXCONINT] >> 0) & 0xFF;
4412 	if ( stats[0]->STR > 240 )
4413 	{
4414 		stats[0]->STR = (Sint8)stats[0]->STR;
4415 	}
4416 	stats[0]->DEX = (tags[TAG_STRDEXCONINT] >> 8) & 0xFF;
4417 	if ( stats[0]->DEX > 240 )
4418 	{
4419 		stats[0]->DEX = (Sint8)stats[0]->DEX;
4420 	}
4421 	stats[0]->CON = (tags[TAG_STRDEXCONINT] >> 16) & 0xFF;
4422 	if ( stats[0]->CON > 240 )
4423 	{
4424 		stats[0]->CON = (Sint8)stats[0]->CON;
4425 	}
4426 	stats[0]->INT = (tags[TAG_STRDEXCONINT] >> 24) & 0xFF;
4427 	if ( stats[0]->INT > 240 )
4428 	{
4429 		stats[0]->INT = (Sint8)stats[0]->INT;
4430 	}
4431 	stats[0]->PER = (tags[TAG_PERCHREXPLVL] >> 0) & 0xFF;
4432 	if ( stats[0]->PER > 240 )
4433 	{
4434 		stats[0]->PER = (Sint8)stats[0]->PER;
4435 	}
4436 	stats[0]->CHR = (tags[TAG_PERCHREXPLVL] >> 8) & 0xFF;
4437 	if ( stats[0]->CHR > 240 )
4438 	{
4439 		stats[0]->CHR = (Sint8)stats[0]->CHR;
4440 	}
4441 	stats[0]->EXP = (tags[TAG_PERCHREXPLVL] >> 16) & 0xFF;
4442 	stats[0]->LVL = (tags[TAG_PERCHREXPLVL] >> 24) & 0xFF;
4443 	stats[0]->GOLD = tags[TAG_GOLD];
4444 
4445 	tagWidth = 8;
4446 	tag = TAG_PROFICIENCY1;
4447 	i = 0;
4448 	for ( c = 0; c < NUMPROFICIENCIES; ++c )
4449 	{
4450 		stats[0]->PROFICIENCIES[c] = (tags[tag] >> (i * tagWidth)) & 0xFF;
4451 		++i;
4452 		if ( i > ((32 / tagWidth) - 1) )
4453 		{
4454 			i = 0;
4455 			++tag;
4456 		}
4457 	}
4458 
4459 	list_FreeAll(&stats[0]->inventory);
4460 	if ( ((tags[TAG_EQUIPMENT1] >> 0) & 0xFF) > 0 )
4461 	{
4462 		stats[0]->helmet = newItem(ItemType((tags[TAG_EQUIPMENT1] >> 0) & 0xFF), EXCELLENT,
4463 			Sint16((tags[TAG_EQUIPMENT_BEATITUDE1] >> 0) & 0xFF), 1, tags[TAG_EQUIPMENT_APPEARANCE] & 0xF, true, &stats[0]->inventory);
4464 	}
4465 	if ( ((tags[TAG_EQUIPMENT1] >> 8) & 0xFF) > 0 )
4466 	{
4467 		stats[0]->breastplate = newItem(ItemType((tags[TAG_EQUIPMENT1] >> 8) & 0xFF), EXCELLENT,
4468 			Sint16((tags[TAG_EQUIPMENT_BEATITUDE1] >> 8) & 0xFF), 1, (tags[TAG_EQUIPMENT_APPEARANCE] >> 8) & 0xF, true, &stats[0]->inventory);
4469 	}
4470 	if ( ((tags[TAG_EQUIPMENT1] >> 16) & 0xFF) > 0 )
4471 	{
4472 		stats[0]->gloves = newItem(ItemType((tags[TAG_EQUIPMENT1] >> 16) & 0xFF), EXCELLENT, Sint16((tags[TAG_EQUIPMENT_BEATITUDE1] >> 16) & 0xFF), 1, rand(), true, &stats[0]->inventory);
4473 	}
4474 	if ( ((tags[TAG_EQUIPMENT1] >> 24) & 0xFF) > 0 )
4475 	{
4476 		stats[0]->shoes = newItem(ItemType((tags[TAG_EQUIPMENT1] >> 24) & 0xFF), EXCELLENT, Sint16((tags[TAG_EQUIPMENT_BEATITUDE1] >> 24) & 0xFF), 1, rand(), true, &stats[0]->inventory);
4477 	}
4478 
4479 	if ( ((tags[TAG_EQUIPMENT2] >> 0) & 0xFF) > 0 )
4480 	{
4481 		stats[0]->shield = newItem(ItemType((tags[TAG_EQUIPMENT2] >> 0) & 0xFF), EXCELLENT,
4482 			Sint16((tags[TAG_EQUIPMENT_BEATITUDE2] >> 0) & 0xFF), 1, (tags[TAG_EQUIPMENT_APPEARANCE] >> 12) & 0xF, true, &stats[0]->inventory);
4483 	}
4484 	if ( ((tags[TAG_EQUIPMENT2] >> 8) & 0xFF) > 0 )
4485 	{
4486 		stats[0]->weapon = newItem(ItemType((tags[TAG_EQUIPMENT2] >> 8) & 0xFF), EXCELLENT, Sint16((tags[TAG_EQUIPMENT_BEATITUDE2] >> 8) & 0xFF), 1, rand(), true, &stats[0]->inventory);
4487 	}
4488 	if ( ((tags[TAG_EQUIPMENT2] >> 16) & 0xFF) > 0 )
4489 	{
4490 		stats[0]->cloak = newItem(ItemType((tags[TAG_EQUIPMENT2] >> 16) & 0xFF), EXCELLENT,
4491 			Sint16((tags[TAG_EQUIPMENT_BEATITUDE2] >> 16) & 0xFF), 1, (tags[TAG_EQUIPMENT_APPEARANCE] >> 4) & 0xF, true, &stats[0]->inventory);
4492 	}
4493 	if ( ((tags[TAG_EQUIPMENT2] >> 24) & 0xFF) > 0 )
4494 	{
4495 		stats[0]->amulet = newItem(ItemType((tags[TAG_EQUIPMENT2] >> 24) & 0xFF), EXCELLENT, Sint16((tags[TAG_EQUIPMENT_BEATITUDE2] >> 24) & 0xFF), 1, rand(), true, &stats[0]->inventory);
4496 	}
4497 
4498 	if ( ((tags[TAG_EQUIPMENT3] >> 16) & 0xFF) > 0 )
4499 	{
4500 		stats[0]->ring = newItem(ItemType((tags[TAG_EQUIPMENT3] >> 16) & 0xFF), EXCELLENT, Sint16((tags[TAG_EQUIPMENT_BEATITUDE3] >> 0) & 0xFF), 1, rand(), true, &stats[0]->inventory);
4501 	}
4502 	if ( ((tags[TAG_EQUIPMENT3] >> 24) & 0xFF) > 0 )
4503 	{
4504 		stats[0]->mask = newItem(ItemType((tags[TAG_EQUIPMENT3] >> 24) & 0xFF), EXCELLENT, Sint16((tags[TAG_EQUIPMENT_BEATITUDE3] >> 8) & 0xFF), 1, rand(), true, &stats[0]->inventory);
4505 	}
4506 
4507 	completionTime = tags[TAG_COMPLETION_TIME] * TICKS_PER_SECOND;
4508 	//g_SteamLeaderboards->LeaderboardUpload.tags[TAG_TOTAL_SCORE] = totalScore(score);
4509 	return true;
4510 }
4511 
4512 #endif // STEAMWORKS
4513 
getCurrentPlayerUids()4514 void AchievementObserver::getCurrentPlayerUids()
4515 {
4516 	for ( int i = 0; i < MAXPLAYERS; ++i )
4517 	{
4518 		if ( players[i] && players[i]->entity )
4519 		{
4520 			playerUids[i] = players[i]->entity->getUID();
4521 		}
4522 	}
4523 }
4524 
updateOnLevelChange()4525 bool AchievementObserver::updateOnLevelChange()
4526 {
4527 	if ( levelObserved != currentlevel )
4528 	{
4529 		for ( int i = 0; i < MAXPLAYERS; ++i )
4530 		{
4531 			playerAchievements[i].flutterShyCoordinates = std::make_pair(0.0, 0.0);
4532 			playerAchievements[i].caughtInAMoshTargets.clear();
4533 			playerAchievements[i].strungOutTicks.clear();
4534 			playerAchievements[i].ironicPunishmentTargets.clear();
4535 			playerAchievements[i].gastricBypassSpell = std::make_pair(0, 0);
4536 			playerAchievements[i].rat5000secondRule.clear();
4537 		}
4538 		levelObserved = currentlevel;
4539 		return true;
4540 	}
4541 	return false;
4542 }
4543 
checkUidIsFromPlayer(Uint32 uid)4544 int AchievementObserver::checkUidIsFromPlayer(Uint32 uid)
4545 {
4546 	for ( int i = 0; i < MAXPLAYERS; ++i )
4547 	{
4548 		if ( achievementObserver.playerUids[i] == uid )
4549 		{
4550 			return i;
4551 		}
4552 	}
4553 	return -1;
4554 }
4555 
updateData()4556 void AchievementObserver::updateData()
4557 {
4558 	getCurrentPlayerUids();
4559 	if ( updateOnLevelChange() )
4560 	{
4561 		entityAchievementsToProcess.clear();
4562 #ifdef DEBUG_ACHIEVEMENTS
4563 		messagePlayer(0, "[DEBUG]: Achievement data reset for floor.");
4564 #endif
4565 	}
4566 }
4567 
addEntityAchievementTimer(Entity * entity,int achievement,int ticks,bool resetTimerIfActive,int optionalIncrement)4568 bool AchievementObserver::addEntityAchievementTimer(Entity* entity, int achievement, int ticks, bool resetTimerIfActive, int optionalIncrement)
4569 {
4570 	if ( !entity )
4571 	{
4572 		return false;
4573 	}
4574 	Uint32 uid = entity->getUID();
4575 	if ( uid == 0 )
4576 	{
4577 		return false;
4578 	}
4579 
4580 	auto it = entityAchievementsToProcess.find(uid);
4581 	if ( it != entityAchievementsToProcess.end() )
4582 	{
4583 		auto inner_it = (*it).second.find(achievement);
4584 		if ( inner_it != (*it).second.end() )
4585 		{
4586 			//achievement exists, need to update the ticks value.
4587 			if ( resetTimerIfActive )
4588 			{
4589 				entityAchievementsToProcess[uid][achievement].first = ticks;
4590 			}
4591 			entityAchievementsToProcess[uid][achievement].second += optionalIncrement;
4592 			return false;
4593 		}
4594 		else
4595 		{
4596 			//uid exists, but achievement is not in map. make entry.
4597 			entityAchievementsToProcess[uid].insert(std::make_pair(achievement, std::make_pair(ticks, optionalIncrement))); // set the inner map properties.
4598 			return true;
4599 		}
4600 	}
4601 	else
4602 	{
4603 		// uid does not exist in map, make new entry.
4604 		entityAchievementsToProcess.insert(std::make_pair(uid, std::unordered_map<int, std::pair<int, int>>())); // add a map object at the first key.
4605 		entityAchievementsToProcess[uid].insert(std::make_pair(achievement, std::make_pair(ticks, optionalIncrement))); // set the inner map properties.
4606 		return true;
4607 	}
4608 }
4609 
printActiveAchievementTimers()4610 void AchievementObserver::printActiveAchievementTimers()
4611 {
4612 	for ( auto it = entityAchievementsToProcess.begin(); it != entityAchievementsToProcess.end(); ++it )
4613 	{
4614 		for ( auto inner_it = (*it).second.begin(); inner_it != (*it).second.end(); ++inner_it )
4615 		{
4616 			messagePlayer(0, "Uid: %d, achievement: %d, ticks: %d, counter: %d", (*it).first, (*inner_it).first, (*inner_it).second.first, (*inner_it).second.second);
4617 		}
4618 	}
4619 }
4620 
achievementTimersTickDown()4621 void AchievementObserver::achievementTimersTickDown()
4622 {
4623 	for ( auto it = entityAchievementsToProcess.begin(); it != entityAchievementsToProcess.end(); /* don't iterate here*/ )
4624 	{
4625 		for ( auto inner_it = (*it).second.begin(); inner_it != (*it).second.end(); /* don't iterate here*/ )
4626 		{
4627 			bool removeEntry = false;
4628 			if ( (*inner_it).second.first > 0 )
4629 			{
4630 				--((*inner_it).second.first);
4631 				if ( (*inner_it).second.first == 0 )
4632 				{
4633 					// remove me.
4634 					removeEntry = true;
4635 				}
4636 			}
4637 			else if ( (*inner_it).second.first == 0 )
4638 			{
4639 				// remove me.
4640 				removeEntry = true;
4641 			}
4642 
4643 			if ( removeEntry )
4644 			{
4645 				inner_it = (*it).second.erase(inner_it);
4646 			}
4647 			else
4648 			{
4649 				++inner_it;
4650 			}
4651 		}
4652 
4653 		if ( (*it).second.empty() )
4654 		{
4655 			it = entityAchievementsToProcess.erase(it);
4656 		}
4657 		else
4658 		{
4659 			++it;
4660 		}
4661 	}
4662 
4663 	//printActiveAchievementTimers();
4664 }
4665 
awardAchievementIfActive(int player,Entity * entity,int achievement)4666 void AchievementObserver::awardAchievementIfActive(int player, Entity* entity, int achievement)
4667 {
4668 	if ( !entity )
4669 	{
4670 		return;
4671 	}
4672 	Uint32 uid = entity->getUID();
4673 	auto it = entityAchievementsToProcess.find(uid);
4674 	if ( it != entityAchievementsToProcess.end() )
4675 	{
4676 		auto inner_it = (*it).second.find(achievement);
4677 		if ( inner_it != (*it).second.end() && (*it).second[achievement].first != 0 )
4678 		{
4679 			if ( achievement == BARONY_ACH_BOMBTRACK )
4680 			{
4681 				if ( (*it).second[achievement].second >= 5 )
4682 				{
4683 					awardAchievement(player, achievement);
4684 				}
4685 			}
4686 			else if ( achievement == BARONY_ACH_OHAI_MARK )
4687 			{
4688 				serverUpdatePlayerGameplayStats(player, STATISTICS_OHAI_MARK, 1);
4689 			}
4690 			else if ( achievement == BARONY_ACH_IF_YOU_LOVE_SOMETHING )
4691 			{
4692 				playerAchievements[player].ifYouLoveSomething++;
4693 			}
4694 			else if ( achievement == BARONY_ACH_COWBOY_FROM_HELL )
4695 			{
4696 				steamStatisticUpdateClient(player, STEAM_STAT_COWBOY_FROM_HELL, STEAM_STAT_INT, 1);
4697 			}
4698 			else
4699 			{
4700 				awardAchievement(player, achievement);
4701 			}
4702 		}
4703 	}
4704 }
4705 
checkMapScriptsOnVariableSet()4706 void AchievementObserver::checkMapScriptsOnVariableSet()
4707 {
4708 	for ( auto it = textSourceScript.scriptVariables.begin(); it != textSourceScript.scriptVariables.end(); ++it )
4709 	{
4710 		size_t found = (*it).first.find("$ACH_TUTORIAL_SECRET");
4711 		if ( found != std::string::npos )
4712 		{
4713 			std::string mapname = map.name;
4714 			if ( mapname.find("Tutorial Hub") != std::string::npos )
4715 			{
4716 				updatePlayerAchievement(clientnum, BARONY_ACH_MASTER, EXTRA_CREDIT_SECRET);
4717 			}
4718 			else
4719 			{
4720 				updatePlayerAchievement(clientnum, BARONY_ACH_EXTRA_CREDIT, EXTRA_CREDIT_SECRET);
4721 			}
4722 		}
4723 	}
4724 }
4725 
updatePlayerAchievement(int player,Achievement achievement,AchievementEvent achEvent)4726 void AchievementObserver::updatePlayerAchievement(int player, Achievement achievement, AchievementEvent achEvent)
4727 {
4728 	switch ( achievement )
4729 	{
4730 		case BARONY_ACH_BACK_TO_BASICS:
4731 			if ( gameModeManager.getMode() == GameModeManager_t::GAME_MODE_TUTORIAL )
4732 			{
4733 				bool alternateUnlock = false;
4734 #ifdef STEAMWORKS
4735 				if ( SteamUser()->BLoggedOn() )
4736 				{
4737 					SteamUserStats()->GetAchievement("BARONY_ACH_LICH_HUNTER", &alternateUnlock);
4738 				}
4739 #endif
4740 				if ( g_SteamStats[STEAM_STAT_BACK_TO_BASICS].m_iValue == 1 || alternateUnlock )
4741 				{
4742 					awardAchievement(player, achievement);
4743 				}
4744 			}
4745 			break;
4746 		case BARONY_ACH_FAST_LEARNER:
4747 			if ( gameModeManager.getMode() == GameModeManager_t::GAME_MODE_TUTORIAL )
4748 			{
4749 				if ( g_SteamStats[STEAM_STAT_DIPLOMA].m_iValue == 10
4750 					&& gameModeManager.Tutorial.levels.size() == (gameModeManager.Tutorial.getNumTutorialLevels() + 1) ) // include the + 1 for hub
4751 				{
4752 					Uint32 totalTime = 0;
4753 					bool allLevelsCompleted = true;
4754 					for ( auto it = gameModeManager.Tutorial.levels.begin(); it != gameModeManager.Tutorial.levels.end(); ++it )
4755 					{
4756 						if ( it->completionTime > 0 )
4757 						{
4758 							totalTime += it->completionTime;
4759 						}
4760 						else
4761 						{
4762 							if ( it->filename.compare("tutorial_hub") != 0 )
4763 							{
4764 								allLevelsCompleted = false;
4765 								break;
4766 							}
4767 						}
4768 					}
4769 					if ( allLevelsCompleted && totalTime < TICKS_PER_SECOND * 20 * 60 )
4770 					{
4771 						awardAchievement(player, achievement);
4772 					}
4773 				}
4774 			}
4775 			break;
4776 		case BARONY_ACH_MASTER:
4777 			if ( gameModeManager.getMode() == GameModeManager_t::GAME_MODE_TUTORIAL )
4778 			{
4779 				std::string mapname = map.name;
4780 				if ( mapname.find("Tutorial Hub") != std::string::npos )
4781 				{
4782 					awardAchievement(player, achievement);
4783 				}
4784 			}
4785 			break;
4786 		case BARONY_ACH_DIPLOMA:
4787 		case BARONY_ACH_EXTRA_CREDIT:
4788 			if ( gameModeManager.getMode() == GameModeManager_t::GAME_MODE_TUTORIAL )
4789 			{
4790 				std::string mapname = map.name;
4791 				if ( mapname.find("Tutorial Hub") == std::string::npos
4792 					&& mapname.find("Tutorial ") != std::string::npos )
4793 				{
4794 					int levelnum = stoi(mapname.substr(mapname.find("Tutorial ") + strlen("Tutorial "), 2));
4795 					SteamStatIndexes statBitCounter = STEAM_STAT_DIPLOMA_LVLS;
4796 					SteamStatIndexes statLevelTotal = STEAM_STAT_DIPLOMA;
4797 					if ( achievement == BARONY_ACH_EXTRA_CREDIT )
4798 					{
4799 						statBitCounter = STEAM_STAT_EXTRA_CREDIT_LVLS;
4800 						statLevelTotal = STEAM_STAT_EXTRA_CREDIT;
4801 					}
4802 
4803 					if ( levelnum >= 1 && levelnum <= gameModeManager.Tutorial.getNumTutorialLevels() )
4804 					{
4805 						if ( !(g_SteamStats[statBitCounter].m_iValue & (1 << levelnum - 1)) ) // bit not set
4806 						{
4807 							// update with the difference in values.
4808 							steamStatisticUpdate(statBitCounter, STEAM_STAT_INT, (1 << levelnum - 1));
4809 						}
4810 
4811 						int levelsCompleted = 0;
4812 						for ( int i = 0; i < gameModeManager.Tutorial.getNumTutorialLevels(); ++i )
4813 						{
4814 							if ( g_SteamStats[statBitCounter].m_iValue & (1 << i) ) // count the bits
4815 							{
4816 								++levelsCompleted;
4817 							}
4818 						}
4819 						if ( levelsCompleted >= g_SteamStats[statLevelTotal].m_iValue )
4820 						{
4821 							steamStatisticUpdate(statLevelTotal, STEAM_STAT_INT, levelsCompleted - g_SteamStats[statLevelTotal].m_iValue);
4822 						}
4823 					}
4824 				}
4825 			}
4826 			break;
4827 		case BARONY_ACH_REAL_BOY:
4828 			if ( achEvent == REAL_BOY_HUMAN_RECRUIT )
4829 			{
4830 				playerAchievements[player].realBoy.first = 1;
4831 			}
4832 			else if ( achEvent == REAL_BOY_SHOP )
4833 			{
4834 				playerAchievements[player].realBoy.second = 1;
4835 			}
4836 			if ( playerAchievements[player].realBoy.first == 1 && playerAchievements[player].realBoy.second == 1 )
4837 			{
4838 				awardAchievement(player, achievement);
4839 			}
4840 			break;
4841 		case BARONY_ACH_STRUNG_OUT:
4842 			if ( !playerAchievements[player].strungOut )
4843 			{
4844 				playerAchievements[player].strungOutTicks.push_back(ticks);
4845 				if ( playerAchievements[player].strungOutTicks.size() >= 10 )
4846 				{
4847 					Uint32 timePassed = playerAchievements[player].strungOutTicks.at(playerAchievements[player].strungOutTicks.size() - 1);
4848 					timePassed -= playerAchievements[player].strungOutTicks.at(0);
4849 					if ( timePassed < 9 * TICKS_PER_SECOND )
4850 					{
4851 						playerAchievements[player].strungOut = true;
4852 						awardAchievement(player, achievement);
4853 					}
4854 
4855 					while ( playerAchievements[player].strungOutTicks.size() >= 10 )
4856 					{
4857 						auto it = playerAchievements[player].strungOutTicks.begin();
4858 						playerAchievements[player].strungOutTicks.erase(it);
4859 					}
4860 				}
4861 			}
4862 			break;
4863 		case BARONY_ACH_COOP_ESCAPE_MINES:
4864 		{
4865 			std::unordered_set<int> races;
4866 			std::unordered_set<int> classes;
4867 			for ( int i = 0; i < MAXPLAYERS; ++i )
4868 			{
4869 				if ( !client_disconnected[i] )
4870 				{
4871 					if ( stats[i] && stats[i]->playerRace != RACE_HUMAN && stats[i]->appearance == 0 )
4872 					{
4873 						races.insert(stats[i]->playerRace);
4874 					}
4875 					if ( client_classes[i] > CLASS_MONK )
4876 					{
4877 						classes.insert(client_classes[i]);
4878 					}
4879 				}
4880 			}
4881 			std::vector<int> awardAchievementsToAllPlayers;
4882 			if ( !races.empty() )
4883 			{
4884 				if ( races.find(RACE_INCUBUS) != races.end() && races.find(RACE_SUCCUBUS) != races.end() )
4885 				{
4886 					awardAchievementsToAllPlayers.push_back(BARONY_ACH_SWINGERS);
4887 				}
4888 				if ( races.find(RACE_VAMPIRE) != races.end() && races.find(RACE_INSECTOID) != races.end() )
4889 				{
4890 					awardAchievementsToAllPlayers.push_back(BARONY_ACH_COLD_BLOODED);
4891 				}
4892 				if ( races.find(RACE_AUTOMATON) != races.end() && races.find(RACE_SKELETON) != races.end() )
4893 				{
4894 					awardAchievementsToAllPlayers.push_back(BARONY_ACH_SOULLESS);
4895 				}
4896 				if ( races.find(RACE_GOATMAN) != races.end() && races.find(RACE_GOBLIN) != races.end() )
4897 				{
4898 					awardAchievementsToAllPlayers.push_back(BARONY_ACH_TRIBAL);
4899 				}
4900 			}
4901 
4902 			if ( !classes.empty() )
4903 			{
4904 				if ( classes.find(CLASS_MACHINIST) != classes.end() && classes.find(CLASS_MESMER) != classes.end() )
4905 				{
4906 					awardAchievementsToAllPlayers.push_back(BARONY_ACH_MANAGEMENT_TEAM);
4907 				}
4908 				if ( classes.find(CLASS_ACCURSED) != classes.end() && classes.find(CLASS_PUNISHER) != classes.end() )
4909 				{
4910 					awardAchievementsToAllPlayers.push_back(BARONY_ACH_SOCIOPATHS);
4911 				}
4912 				if ( classes.find(CLASS_SHAMAN) != classes.end() && classes.find(CLASS_CONJURER) != classes.end() )
4913 				{
4914 					awardAchievementsToAllPlayers.push_back(BARONY_ACH_FACES_OF_DEATH);
4915 				}
4916 				if ( classes.find(CLASS_HUNTER) != classes.end() && classes.find(CLASS_BREWER) != classes.end() )
4917 				{
4918 					awardAchievementsToAllPlayers.push_back(BARONY_ACH_SURVIVALISTS);
4919 				}
4920 			}
4921 			if ( !awardAchievementsToAllPlayers.empty() )
4922 			{
4923 				for ( auto it = awardAchievementsToAllPlayers.begin(); it != awardAchievementsToAllPlayers.end(); ++it )
4924 				{
4925 					for ( int i = 0; i < MAXPLAYERS; ++i )
4926 					{
4927 						if ( !client_disconnected[i] )
4928 						{
4929 							awardAchievement(i, *it);
4930 						}
4931 					}
4932 				}
4933 			}
4934 		}
4935 			break;
4936 		default:
4937 			break;
4938 	}
4939 #ifdef DEBUG_ACHIEVEMENTS
4940 	messagePlayer(player, "[DEBUG]: Processed achievement %d, event: %d", achievement, achEvent);
4941 #endif
4942 }
4943 
clearPlayerAchievementData()4944 void AchievementObserver::clearPlayerAchievementData()
4945 {
4946 	for ( int i = 0; i < MAXPLAYERS; ++i )
4947 	{
4948 		playerAchievements[i].caughtInAMosh = false;
4949 		playerAchievements[i].bombTrack = false;
4950 		playerAchievements[i].calmLikeABomb = false;
4951 		playerAchievements[i].strungOut = false;
4952 		playerAchievements[i].wonderfulToys = false;
4953 		playerAchievements[i].levitantLackey = false;
4954 		playerAchievements[i].flutterShy = false;
4955 		playerAchievements[i].gastricBypass = false;
4956 		playerAchievements[i].ticksSpentOverclocked = 0;
4957 		playerAchievements[i].tradition = false;
4958 		playerAchievements[i].traditionKills = 0;
4959 		playerAchievements[i].torchererScrap = 0;
4960 		playerAchievements[i].superShredder = 0;
4961 		playerAchievements[i].fixerUpper = 0;
4962 		playerAchievements[i].ifYouLoveSomething = 0;
4963 		playerAchievements[i].socialButterfly = 0;
4964 		playerAchievements[i].rollTheBones = 0;
4965 		playerAchievements[i].trashCompactor = 0;
4966 
4967 		playerAchievements[i].realBoy = std::make_pair(0, 0);
4968 		playerAchievements[i].caughtInAMoshTargets.clear();
4969 		playerAchievements[i].strungOutTicks.clear();
4970 		playerAchievements[i].ironicPunishmentTargets.clear();
4971 		playerAchievements[i].flutterShyCoordinates = std::make_pair(0.0, 0.0);
4972 		playerAchievements[i].gastricBypassSpell = std::make_pair(0, 0);
4973 		playerAchievements[i].rat5000secondRule.clear();
4974 	}
4975 }
4976 
awardAchievement(int player,int achievement)4977 void AchievementObserver::awardAchievement(int player, int achievement)
4978 {
4979 	switch ( achievement )
4980 	{
4981 		case BARONY_ACH_MASTER:
4982 			steamAchievementClient(player, "BARONY_ACH_MASTER");
4983 			break;
4984 		case BARONY_ACH_FAST_LEARNER:
4985 			steamAchievementClient(player, "BARONY_ACH_FAST_LEARNER");
4986 			break;
4987 		case BARONY_ACH_BACK_TO_BASICS:
4988 			steamAchievementClient(player, "BARONY_ACH_BACK_TO_BASICS");
4989 			break;
4990 		case BARONY_ACH_TELEFRAG:
4991 			steamAchievementClient(player, "BARONY_ACH_TELEFRAG");
4992 			break;
4993 		case BARONY_ACH_REAL_BOY:
4994 			steamAchievementClient(player, "BARONY_ACH_REAL_BOY");
4995 			break;
4996 		case BARONY_ACH_SWINGERS:
4997 			steamAchievementClient(player, "BARONY_ACH_SWINGERS");
4998 			break;
4999 		case BARONY_ACH_COLD_BLOODED:
5000 			steamAchievementClient(player, "BARONY_ACH_COLD_BLOODED");
5001 			break;
5002 		case BARONY_ACH_SOULLESS:
5003 			steamAchievementClient(player, "BARONY_ACH_SOULLESS");
5004 			break;
5005 		case BARONY_ACH_TRIBAL:
5006 			steamAchievementClient(player, "BARONY_ACH_TRIBAL");
5007 			break;
5008 		case BARONY_ACH_MANAGEMENT_TEAM:
5009 			steamAchievementClient(player, "BARONY_ACH_MANAGEMENT_TEAM");
5010 			break;
5011 		case BARONY_ACH_SOCIOPATHS:
5012 			steamAchievementClient(player, "BARONY_ACH_SOCIOPATHS");
5013 			break;
5014 		case BARONY_ACH_FACES_OF_DEATH:
5015 			steamAchievementClient(player, "BARONY_ACH_FACES_OF_DEATH");
5016 			break;
5017 		case BARONY_ACH_SURVIVALISTS:
5018 			steamAchievementClient(player, "BARONY_ACH_SURVIVALISTS");
5019 			break;
5020 		case BARONY_ACH_BOMBTRACK:
5021 			steamAchievementClient(player, "BARONY_ACH_BOMBTRACK");
5022 			break;
5023 		case BARONY_ACH_CALM_LIKE_A_BOMB:
5024 			achievementObserver.playerAchievements[player].calmLikeABomb = true;
5025 			steamAchievementClient(player, "BARONY_ACH_CALM_LIKE_A_BOMB");
5026 			break;
5027 		case BARONY_ACH_CAUGHT_IN_A_MOSH:
5028 			steamAchievementClient(player, "BARONY_ACH_CAUGHT_IN_A_MOSH");
5029 			break;
5030 		case BARONY_ACH_STRUNG_OUT:
5031 			steamAchievementClient(player, "BARONY_ACH_STRUNG_OUT");
5032 			break;
5033 		case BARONY_ACH_PLEASE_HOLD:
5034 			steamAchievementClient(player, "BARONY_ACH_PLEASE_HOLD");
5035 			break;
5036 		case BARONY_ACH_FELL_BEAST:
5037 			steamAchievementClient(player, "BARONY_ACH_FELL_BEAST");
5038 			break;
5039 		case BARONY_ACH_IRONIC_PUNISHMENT:
5040 			steamAchievementClient(player, "BARONY_ACH_IRONIC_PUNISHMENT");
5041 			break;
5042 		default:
5043 			messagePlayer(player, "[WARNING]: Unhandled achievement: %d", achievement);
5044 			break;
5045 	}
5046 }
5047 
checkPathBetweenObjects(Entity * player,Entity * target,int achievement)5048 bool AchievementObserver::PlayerAchievements::checkPathBetweenObjects(Entity* player, Entity* target, int achievement)
5049 {
5050 	if ( !player )
5051 	{
5052 		return false;
5053 	}
5054 
5055 	real_t oldx = 0.0;
5056 	real_t oldy = 0.0;
5057 	if ( achievement == BARONY_ACH_LEVITANT_LACKEY )
5058 	{
5059 		if ( this->levitantLackey )
5060 		{
5061 			return false;
5062 		}
5063 	}
5064 	else if ( achievement == BARONY_ACH_WONDERFUL_TOYS )
5065 	{
5066 		if ( this->wonderfulToys )
5067 		{
5068 			return false;
5069 		}
5070 	}
5071 	else if ( achievement == BARONY_ACH_FLUTTERSHY )
5072 	{
5073 		if ( this->flutterShy )
5074 		{
5075 			return false;
5076 		}
5077 
5078 		// if the player exists, we need another entity to path to (target is nullptr)
5079 		// use a bodypart entity
5080 		if ( !target )
5081 		{
5082 			target = player->bodyparts.at(0);
5083 			oldx = target->x;
5084 			oldy = target->y;
5085 		}
5086 		target->x = this->flutterShyCoordinates.first;
5087 		target->y = this->flutterShyCoordinates.second;
5088 	}
5089 
5090 	if ( !target )
5091 	{
5092 		return false;
5093 	}
5094 
5095 	list_t* playerPath = generatePath((int)floor(player->x / 16), (int)floor(player->y / 16),
5096 		(int)floor(target->x / 16), (int)floor(target->y / 16), player, target, true);
5097 	if ( playerPath == nullptr )
5098 	{
5099 		// no path.
5100 		if ( achievement == BARONY_ACH_LEVITANT_LACKEY )
5101 		{
5102 			steamAchievementClient(player->skill[2], "BARONY_ACH_LEVITANT_LACKEY");
5103 			this->levitantLackey = true;
5104 		}
5105 		else if ( achievement == BARONY_ACH_WONDERFUL_TOYS )
5106 		{
5107 			steamAchievementClient(player->skill[2], "BARONY_ACH_WONDERFUL_TOYS");
5108 			this->wonderfulToys = true;
5109 		}
5110 		else if ( achievement == BARONY_ACH_FLUTTERSHY )
5111 		{
5112 			steamAchievementClient(player->skill[2], "BARONY_ACH_FLUTTERSHY");
5113 			this->flutterShy = true;
5114 			target->x = oldx;
5115 			target->y = oldy;
5116 		}
5117 		return true;
5118 	}
5119 	else
5120 	{
5121 		list_FreeAll(playerPath);
5122 		free(playerPath);
5123 		if ( achievement == BARONY_ACH_FLUTTERSHY )
5124 		{
5125 			target->x = oldx;
5126 			target->y = oldy;
5127 		}
5128 	}
5129 	return false;
5130 }
5131 
checkTraditionKill(Entity * player,Entity * target)5132 bool AchievementObserver::PlayerAchievements::checkTraditionKill(Entity* player, Entity* target)
5133 {
5134 	if ( tradition )
5135 	{
5136 		return false;
5137 	}
5138 
5139 	std::vector<list_t*> entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(target, 3);
5140 	bool foundFountain = false;
5141 	for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
5142 	{
5143 		list_t* currentList = *it;
5144 		node_t* node;
5145 		for ( node = currentList->first; node != nullptr; node = node->next )
5146 		{
5147 			Entity* entity = (Entity*)node->element;
5148 			if ( entity && entity->behavior == &actFountain )
5149 			{
5150 				if ( entityDist(target, entity) < 16 * 3 )
5151 				{
5152 					foundFountain = true;
5153 					break;
5154 				}
5155 			}
5156 		}
5157 		if ( foundFountain )
5158 		{
5159 			break;
5160 		}
5161 	}
5162 
5163 	if ( foundFountain )
5164 	{
5165 		steamStatisticUpdateClient(player->skill[2], STEAM_STAT_TRADITION, STEAM_STAT_INT, 1);
5166 		++traditionKills;
5167 		if ( traditionKills >= 25 ) // max is 20, but allow some error in transmission
5168 		{
5169 			tradition = true;
5170 		}
5171 	}
5172 	return true;
5173 }
5174 
updateGlobalStat(int index,int value)5175 void AchievementObserver::updateGlobalStat(int index, int value)
5176 {
5177 	if ( multiplayer == CLIENT )
5178 	{
5179 		return;
5180 	}
5181 	if ( conductGameChallenges[CONDUCT_CHEATS_ENABLED]
5182 		|| gamemods_disableSteamAchievements )
5183 	{
5184 		return;
5185 	}
5186 #if defined USE_EOS
5187 	EOS.queueGlobalStatUpdate(index, value);
5188 #endif
5189 }
5190 
getIndexForDeathType(int type)5191 SteamGlobalStatIndexes getIndexForDeathType(int type)
5192 {
5193 	switch ( static_cast<Monster>(type) )
5194 	{
5195 		case HUMAN:
5196 			return STEAM_GSTAT_DEATHS_HUMAN;
5197 		case RAT:
5198 			return STEAM_GSTAT_DEATHS_RAT;
5199 		case GOBLIN:
5200 			return STEAM_GSTAT_DEATHS_GOBLIN;
5201 		case SLIME:
5202 			return STEAM_GSTAT_DEATHS_SLIME;
5203 		case TROLL:
5204 			return STEAM_GSTAT_DEATHS_TROLL;
5205 		case SPIDER:
5206 			return STEAM_GSTAT_DEATHS_SPIDER;
5207 		case GHOUL:
5208 			return STEAM_GSTAT_DEATHS_GHOUL;
5209 		case SKELETON:
5210 			return STEAM_GSTAT_DEATHS_SKELETON;
5211 		case SCORPION:
5212 			return STEAM_GSTAT_DEATHS_SCORPION;
5213 		case CREATURE_IMP:
5214 			return STEAM_GSTAT_DEATHS_IMP;
5215 		case GNOME:
5216 			return STEAM_GSTAT_DEATHS_GNOME;
5217 		case DEMON:
5218 			return STEAM_GSTAT_DEATHS_DEMON;
5219 		case SUCCUBUS:
5220 			return STEAM_GSTAT_DEATHS_SUCCUBUS;
5221 		case LICH:
5222 			return STEAM_GSTAT_DEATHS_LICH;
5223 		case MINOTAUR:
5224 			return STEAM_GSTAT_DEATHS_MINOTAUR;
5225 		case DEVIL:
5226 			return STEAM_GSTAT_DEATHS_DEVIL;
5227 		case SHOPKEEPER:
5228 			return STEAM_GSTAT_DEATHS_SHOPKEEPER;
5229 		case KOBOLD:
5230 			return STEAM_GSTAT_DEATHS_KOBOLD;
5231 		case SCARAB:
5232 			return STEAM_GSTAT_DEATHS_SCARAB;
5233 		case CRYSTALGOLEM:
5234 			return STEAM_GSTAT_DEATHS_CRYSTALGOLEM;
5235 		case INCUBUS:
5236 			return STEAM_GSTAT_DEATHS_INCUBUS;
5237 		case VAMPIRE:
5238 			return STEAM_GSTAT_DEATHS_VAMPIRE;
5239 		case SHADOW:
5240 			return STEAM_GSTAT_DEATHS_SHADOW;
5241 		case COCKATRICE:
5242 			return STEAM_GSTAT_DEATHS_COCKATRICE;
5243 		case INSECTOID:
5244 			return STEAM_GSTAT_DEATHS_INSECTOID;
5245 		case GOATMAN:
5246 			return STEAM_GSTAT_DEATHS_GOATMAN;
5247 		case AUTOMATON:
5248 			return STEAM_GSTAT_DEATHS_AUTOMATON;
5249 		case LICH_ICE:
5250 			return STEAM_GSTAT_DEATHS_LICHICE;
5251 		case LICH_FIRE:
5252 			return STEAM_GSTAT_DEATHS_LICHICE;
5253 		case SENTRYBOT:
5254 			return STEAM_GSTAT_DEATHS_SENTRYBOT;
5255 		case SPELLBOT:
5256 			return STEAM_GSTAT_DEATHS_SPELLBOT;
5257 		case GYROBOT:
5258 			return STEAM_GSTAT_DEATHS_GYROBOT;
5259 		case DUMMYBOT:
5260 			return STEAM_GSTAT_DEATHS_DUMMYBOT;
5261 		default:
5262 			return STEAM_GSTAT_INVALID;
5263 	}
5264 	return STEAM_GSTAT_INVALID;
5265 }