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(¤tlevel, 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 }