1 /*-------------------------------------------------------------------------------
2
3 BARONY
4 File: game.cpp
5 Desc: contains main game code
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 "draw.hpp"
14 #include "game.hpp"
15 #include "stat.hpp"
16 #include "messages.hpp"
17 #include "entity.hpp"
18 #include "files.hpp"
19 #include "menu.hpp"
20 #include "classdescriptions.hpp"
21 #include "interface/interface.hpp"
22 #include "magic/magic.hpp"
23 #include "sound.hpp"
24 #include "items.hpp"
25 #include "init.hpp"
26 #include "shops.hpp"
27 #include "monster.hpp"
28 #include "scores.hpp"
29 #include "menu.hpp"
30 #include "net.hpp"
31 #ifdef STEAMWORKS
32 #include <steam/steam_api.h>
33 #include "steam.hpp"
34 #endif
35 #include "prng.hpp"
36 #include "collision.hpp"
37 #include "paths.hpp"
38 #include "player.hpp"
39 #include "mod_tools.hpp"
40 #include "lobbies.hpp"
41 #include "interface/ui.hpp"
42 #include <limits>
43
44 #include "UnicodeDecoder.h"
45
46 #ifdef LINUX
47 //Sigsegv catching stuff.
48 #include <signal.h>
49 #include <string.h>
50 #include <execinfo.h>
51 #include <sys/stat.h>
52
53 const unsigned STACK_SIZE = 10;
54
segfault_sigaction(int signal,siginfo_t * si,void * arg)55 void segfault_sigaction(int signal, siginfo_t* si, void* arg)
56 {
57 printf("Caught segfault at address %p\n", si->si_addr);
58
59 printlog("Caught segfault at address %p\n", si->si_addr);
60
61 //Dump the stack.
62 void* array[STACK_SIZE];
63 size_t size;
64
65 size = backtrace(array, STACK_SIZE);
66
67 printlog("Signal %d (dumping stack):\n", signal);
68 backtrace_symbols_fd(array, size, STDERR_FILENO);
69
70 SDL_SetRelativeMouseMode(SDL_FALSE); //Uncapture mouse.
71
72 exit(0);
73 }
74
75 #endif
76
77 #ifdef APPLE
78
79 #include <sys/stat.h>
80
81 #endif
82
83 #ifdef BSD
84
85 #include <sys/types.h>
86 #include <sys/sysctl.h>
87
88 #endif
89
90 #ifdef HAIKU
91
92 #include <FindDirectory.h>
93
94 #endif
95
96 #ifdef WINDOWS
make_minidump(EXCEPTION_POINTERS * e)97 void make_minidump(EXCEPTION_POINTERS* e)
98 {
99 auto hDbgHelp = LoadLibraryA("dbghelp");
100 if ( hDbgHelp == nullptr )
101 {
102 return;
103 }
104 auto pMiniDumpWriteDump = (decltype(&MiniDumpWriteDump))GetProcAddress(hDbgHelp, "MiniDumpWriteDump");
105 if ( pMiniDumpWriteDump == nullptr )
106 {
107 return;
108 }
109
110 char name[PATH_MAX];
111 {
112 strcpy(name, "barony_crash");
113 auto nameEnd = name + strlen("barony_crash");
114 SYSTEMTIME t;
115 GetLocalTime(&t);
116 wsprintfA(nameEnd,
117 "_%4d%02d%02d_%02d%02d%02d.dmp",
118 t.wYear, t.wMonth, t.wDay, t.wHour, t.wMinute, t.wSecond);
119 }
120 std::string crashlogDir = outputdir;
121 crashlogDir.append(PHYSFS_getDirSeparator()).append("crashlogs");
122 if ( access(crashlogDir.c_str(), F_OK) == -1 )
123 {
124 // crashlog folder does not exist to write to.
125 printlog("Error accessing crashlogs folder, cannot create crash dump file.");
126 return;
127 }
128
129 // make a new crash folder.
130 char newCrashlogFolder[PATH_MAX] = "";
131 strncpy(newCrashlogFolder, name, strlen(name) - strlen(".dmp")); // folder name is the dmp file without .dmp
132 if ( PHYSFS_setWriteDir(crashlogDir.c_str()) ) // write to the crashlogs/ directory
133 {
134 if ( PHYSFS_mkdir(newCrashlogFolder) ) // make the folder to hold the .dmp file
135 {
136 // the full path of the .dmp file to create.
137 crashlogDir.append(PHYSFS_getDirSeparator()).append(newCrashlogFolder).append(PHYSFS_getDirSeparator());
138 }
139 else
140 {
141 printlog("[PhysFS]: unsuccessfully created %s folder. Error code: %d", newCrashlogFolder, PHYSFS_getLastErrorCode());
142 return;
143 }
144 }
145 else
146 {
147 printlog("[PhysFS]: unsuccessfully mounted base %s folder. Error code: %d", outputdir, PHYSFS_getLastErrorCode());
148 return;
149 }
150
151 std::string crashDumpFile = crashlogDir + name;
152
153 auto hFile = CreateFileA(crashDumpFile.c_str(), GENERIC_ALL, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
154 if ( hFile == INVALID_HANDLE_VALUE )
155 {
156 printlog("Error in file handle for %s, cannot create crash dump file.", crashDumpFile.c_str());
157 return;
158 }
159
160 MINIDUMP_EXCEPTION_INFORMATION exceptionInfo;
161 exceptionInfo.ThreadId = GetCurrentThreadId();
162 exceptionInfo.ExceptionPointers = e;
163 exceptionInfo.ClientPointers = FALSE;
164
165 auto dumped = pMiniDumpWriteDump(
166 GetCurrentProcess(),
167 GetCurrentProcessId(),
168 hFile,
169 MINIDUMP_TYPE(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory),
170 e ? &exceptionInfo : nullptr,
171 nullptr,
172 nullptr);
173
174 CloseHandle(hFile);
175
176 printlog("CRITICAL ERROR: Barony has encountered a crash. Submit the crashlog folder in a .zip to the developers: %s", crashlogDir.c_str());
177 if ( logfile )
178 {
179 fclose(logfile);
180 }
181
182 // now copy the logfile into the crash folder.
183 char logfilePath[PATH_MAX];
184 completePath(logfilePath, "log.txt", outputdir);
185 std::string crashLogFile = crashlogDir + "log.txt";
186 CopyFileA(logfilePath, crashLogFile.c_str(), false);
187 return;
188 }
189
unhandled_handler(EXCEPTION_POINTERS * e)190 LONG CALLBACK unhandled_handler(EXCEPTION_POINTERS* e)
191 {
192 make_minidump(e);
193 return EXCEPTION_CONTINUE_SEARCH;
194 }
195 #endif
196
197 std::vector<std::string> randomPlayerNamesMale;
198 std::vector<std::string> randomPlayerNamesFemale;
199 std::vector<std::string> physFSFilesInDirectory;
200 TileEntityListHandler TileEntityList;
201 // recommended for valgrind debugging:
202 // res of 480x270
203 // /nohud
204 // undefine SOUND, MUSIC (see sound.h)
205
206 int game = 1;
207 Uint32 uniqueGameKey = 0;
208 DebugStatsClass DebugStats;
209 Uint32 networkTickrate = 0;
210 bool gameloopFreezeEntities = false;
211 Uint32 serverSchedulePlayerHealthUpdate = 0;
212 Uint32 serverLastPlayerHealthUpdate = 0;
213
214 /*-------------------------------------------------------------------------------
215
216 gameLogic
217
218 Updates the gamestate; moves actors, primarily
219
220 -------------------------------------------------------------------------------*/
221
gameLogic(void)222 void gameLogic(void)
223 {
224 Uint32 x;
225 node_t* node, *nextnode, *node2;
226 Entity* entity;
227 int c = 0;
228 Uint32 i = 0, j;
229 FILE* fp;
230 deleteent_t* deleteent;
231 bool entitydeletedself;
232 int auto_appraise_lowest_time = std::numeric_limits<int>::max();
233 Item* auto_appraise_target = NULL;
234
235 if ( creditstage > 0 )
236 {
237 credittime++;
238 }
239 if ( intromoviestage > 0 )
240 {
241 intromovietime++;
242 }
243 if ( firstendmoviestage > 0 )
244 {
245 firstendmovietime++;
246 }
247 if ( secondendmoviestage > 0 )
248 {
249 secondendmovietime++;
250 }
251 if ( thirdendmoviestage > 0 )
252 {
253 thirdendmovietime++;
254 }
255 if ( fourthendmoviestage > 0 )
256 {
257 fourthendmovietime++;
258 }
259 for ( int i = 0; i < 8; ++i )
260 {
261 if ( DLCendmovieStageAndTime[i][MOVIE_STAGE] > 0 )
262 {
263 DLCendmovieStageAndTime[i][MOVIE_TIME]++;
264 }
265 }
266
267 DebugStats.eventsT1 = std::chrono::high_resolution_clock::now();
268
269 #ifdef SOUND
270 // sound_update(); //Update FMOD and whatnot.
271 #endif
272
273 // camera shaking
274 for (int c = 0; c < MAXPLAYERS; ++c)
275 {
276 if ( !splitscreen && c != clientnum )
277 {
278 continue;
279 }
280
281 auto& camera_shakex = cameravars[c].shakex;
282 auto& camera_shakey = cameravars[c].shakey;
283 auto& camera_shakex2 = cameravars[c].shakex2;
284 auto& camera_shakey2 = cameravars[c].shakey2;
285 if ( shaking )
286 {
287 camera_shakex2 = (camera_shakex2 + camera_shakex) * .8;
288 camera_shakey2 = (camera_shakey2 + camera_shakey) * .9;
289 if ( camera_shakex2 > 0 )
290 {
291 if ( camera_shakex2 < .02 && camera_shakex >= -.01 )
292 {
293 camera_shakex2 = 0;
294 camera_shakex = 0;
295 }
296 else
297 {
298 camera_shakex -= .01;
299 }
300 }
301 else if ( camera_shakex2 < 0 )
302 {
303 if ( camera_shakex2 > -.02 && camera_shakex <= .01 )
304 {
305 camera_shakex2 = 0;
306 camera_shakex = 0;
307 }
308 else
309 {
310 camera_shakex += .01;
311 }
312 }
313 if ( camera_shakey2 > 0 )
314 {
315 camera_shakey -= 1;
316 }
317 else if ( camera_shakey2 < 0 )
318 {
319 camera_shakey += 1;
320 }
321 }
322 else
323 {
324 camera_shakex = 0;
325 camera_shakey = 0;
326 camera_shakex2 = 0;
327 camera_shakey2 = 0;
328 }
329 }
330
331 // drunkenness
332 if ( !intro )
333 {
334 if ( stats[clientnum]->EFFECTS[EFF_DRUNK] )
335 {
336 // goat/drunkards no spin!
337 if ( stats[clientnum]->type == GOATMAN )
338 {
339 // return to normal.
340 if ( drunkextend > 0 )
341 {
342 drunkextend -= .005;
343 if ( drunkextend < 0 )
344 {
345 drunkextend = 0;
346 }
347 }
348 }
349 else
350 {
351 if ( drunkextend < 0.5 )
352 {
353 drunkextend += .005;
354 if ( drunkextend > 0.5 )
355 {
356 drunkextend = 0.5;
357 }
358 }
359 }
360 }
361 else
362 {
363 if ( stats[clientnum]->EFFECTS[EFF_WITHDRAWAL] || stats[clientnum]->EFFECTS[EFF_DISORIENTED] )
364 {
365 // special widthdrawal shakes
366 if ( drunkextend < 0.2 )
367 {
368 drunkextend += .005;
369 if ( drunkextend > 0.2 )
370 {
371 drunkextend = 0.2;
372 }
373 }
374 }
375 else
376 {
377 // return to normal.
378 if ( drunkextend > 0 )
379 {
380 drunkextend -= .005;
381 if ( drunkextend < 0 )
382 {
383 drunkextend = 0;
384 }
385 }
386 }
387 }
388 }
389
390 // fading in/out
391 if ( fadeout == true )
392 {
393 fadealpha = std::min(fadealpha + 5, 255);
394 if ( fadealpha == 255 )
395 {
396 fadefinished = true;
397 }
398 if ( multiplayer == SERVER && introstage == 3 )
399 {
400 // machinegun this message to clients to make sure they get it!
401 for ( c = 1; c < MAXPLAYERS; c++ )
402 {
403 if ( client_disconnected[c] )
404 {
405 continue;
406 }
407 strcpy((char*)net_packet->data, "BARONY_GAME_START");
408 SDLNet_Write32(svFlags, &net_packet->data[17]);
409 SDLNet_Write32(uniqueGameKey, &net_packet->data[21]);
410 if ( loadingsavegame == 0 )
411 {
412 net_packet->data[25] = 0;
413 }
414 else
415 {
416 net_packet->data[25] = 1;
417 }
418 net_packet->address.host = net_clients[c - 1].host;
419 net_packet->address.port = net_clients[c - 1].port;
420 net_packet->len = 26;
421 sendPacket(net_sock, -1, net_packet, c - 1);
422 }
423 }
424 }
425 else
426 {
427 fadealpha = std::max(0, fadealpha - 5);
428 }
429
430 // handle safe packets
431 if ( !(ticks % 4) )
432 {
433 j = 0;
434 for ( node = safePacketsSent.first; node != nullptr; node = nextnode )
435 {
436 nextnode = node->next;
437
438 packetsend_t* packet = (packetsend_t*)node->element;
439 //printlog("Packet resend: %d", packet->hostnum);
440 sendPacket(packet->sock, packet->channel, packet->packet, packet->hostnum, true);
441 packet->tries++;
442 if ( packet->tries >= MAXTRIES )
443 {
444 list_RemoveNode(node);
445 }
446 j++;
447 if ( j >= MAXDELETES )
448 {
449 break;
450 }
451 }
452 }
453
454 // spawn flame particles on burning objects
455 if ( !gamePaused || (multiplayer && !client_disconnected[0]) )
456 {
457 for ( node = map.entities->first; node != nullptr; node = node->next )
458 {
459 entity = (Entity*)node->element;
460 if ( entity->flags[BURNING] )
461 {
462 if ( !entity->flags[BURNABLE] )
463 {
464 entity->flags[BURNING] = false;
465 continue;
466 }
467 j = 1 + rand() % 4;
468 for ( c = 0; c < j; ++c )
469 {
470 Entity* flame = spawnFlame(entity, SPRITE_FLAME);
471 flame->x += rand() % (entity->sizex * 2 + 1) - entity->sizex;
472 flame->y += rand() % (entity->sizey * 2 + 1) - entity->sizey;
473 flame->z += rand() % 5 - 2;
474 }
475 }
476 }
477 }
478
479 // damage indicator timers
480 handleDamageIndicatorTicks();
481
482 if ( intro == true )
483 {
484 // rotate gear
485 gearrot += 1;
486 if ( gearrot >= 360 )
487 {
488 gearrot -= 360;
489 }
490 gearsize -= std::max<double>(2, gearsize / 35.0);
491 if ( gearsize < 70 )
492 {
493 gearsize = 70;
494 logoalpha += 2;
495 }
496
497 // animate tiles
498 if ( ticks % 10 == 0 && !gamePaused )
499 {
500 int x, y, z;
501 for ( x = 0; x < map.width; x++ )
502 {
503 for ( y = 0; y < map.height; y++ )
504 {
505 for ( z = 0; z < MAPLAYERS; z++ )
506 {
507 if ( animatedtiles[map.tiles[z + y * MAPLAYERS + x * MAPLAYERS * map.height]] )
508 {
509 map.tiles[z + y * MAPLAYERS + x * MAPLAYERS * map.height]--;
510 if ( !animatedtiles[map.tiles[z + y * MAPLAYERS + x * MAPLAYERS * map.height]] )
511 {
512 int tile = map.tiles[z + y * MAPLAYERS + x * MAPLAYERS * map.height];
513 do
514 {
515 tile++;
516 }
517 while ( animatedtiles[tile] );
518 map.tiles[z + y * MAPLAYERS + x * MAPLAYERS * map.height] = tile - 1;
519 }
520 }
521 }
522 }
523 }
524 }
525
526 // execute entity behaviors
527 c = multiplayer;
528 x = clientnum;
529 multiplayer = SINGLE;
530 clientnum = 0;
531 for ( node = map.entities->first; node != nullptr; node = nextnode )
532 {
533 nextnode = node->next;
534 entity = (Entity*)node->element;
535 if ( entity && !entity->ranbehavior )
536 {
537 entity->ticks++;
538 if ( entity->behavior != nullptr )
539 {
540 (*entity->behavior)(entity);
541 if ( entitiesdeleted.first != nullptr )
542 {
543 entitydeletedself = false;
544 for ( node2 = entitiesdeleted.first; node2 != nullptr; node2 = node2->next )
545 {
546 if ( entity == (Entity*)node2->element )
547 {
548 entitydeletedself = true;
549 break;
550 }
551 }
552 if ( entitydeletedself == false )
553 {
554 entity->ranbehavior = true;
555 }
556 nextnode = map.entities->first;
557 list_FreeAll(&entitiesdeleted);
558 }
559 else
560 {
561 entity->ranbehavior = true;
562 nextnode = node->next;
563 }
564 }
565 }
566 }
567 for ( node = map.entities->first; node != nullptr; node = node->next )
568 {
569 entity = (Entity*)node->element;
570 entity->ranbehavior = false;
571 }
572 multiplayer = c;
573 clientnum = x;
574 }
575 else
576 {
577 if ( multiplayer == SERVER )
578 {
579 if ( ticks % 4 == 0 )
580 {
581 // continue informing clients of entities they need to delete
582 for ( i = 1; i < MAXPLAYERS; i++ )
583 {
584 j = 0;
585 for ( node = entitiesToDelete[i].first; node != NULL; node = nextnode )
586 {
587 nextnode = node->next;
588
589 // send the delete entity command to the client
590 strcpy((char*)net_packet->data, "ENTD");
591 deleteent = (deleteent_t*)node->element;
592 SDLNet_Write32(deleteent->uid, &net_packet->data[4]);
593 net_packet->address.host = net_clients[i - 1].host;
594 net_packet->address.port = net_clients[i - 1].port;
595 net_packet->len = 8;
596 sendPacket(net_sock, -1, net_packet, i - 1);
597
598 // quit reminding clients after a certain number of attempts
599 deleteent->tries++;
600 if ( deleteent->tries >= MAXTRIES )
601 {
602 list_RemoveNode(node);
603 }
604 j++;
605 if ( j >= MAXDELETES )
606 {
607 break;
608 }
609 }
610 }
611 }
612 }
613 DebugStats.eventsT2 = std::chrono::high_resolution_clock::now();
614 if ( multiplayer != CLIENT ) // server/singleplayer code
615 {
616 for ( c = 0; c < MAXPLAYERS; c++ )
617 {
618 if ( assailantTimer[c] > 0 )
619 {
620 --assailantTimer[c];
621 //messagePlayer(0, "music cd: %d", assailantTimer[c]);
622 }
623 if ( assailant[c] == true && assailantTimer[c] <= 0 )
624 {
625 assailant[c] = false;
626 assailantTimer[c] = 0;
627 }
628 }
629
630 if ( !directConnect && LobbyHandler.getHostingType() == LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY )
631 {
632 #ifdef USE_EOS
633 if ( multiplayer == SERVER && ticks % TICKS_PER_SECOND == 0 )
634 {
635 EOS.CurrentLobbyData.updateLobbyDuringGameLoop();
636 }
637 #endif // USE_EOS
638 }
639
640
641 // animate tiles
642 if ( !gamePaused )
643 {
644 int x, y, z;
645 for ( x = 0; x < map.width; x++ )
646 {
647 for ( y = 0; y < map.height; y++ )
648 {
649 for ( z = 0; z < MAPLAYERS; z++ )
650 {
651 int index = z + y * MAPLAYERS + x * MAPLAYERS * map.height;
652 if ( animatedtiles[map.tiles[index]] )
653 {
654 if ( ticks % 10 == 0 )
655 {
656 map.tiles[index]--;
657 if ( !animatedtiles[map.tiles[index]] )
658 {
659 do
660 {
661 map.tiles[index]++;
662 }
663 while ( animatedtiles[map.tiles[index]] );
664 map.tiles[index]--;
665 }
666 }
667 if ( z == 0 )
668 {
669 // water and lava noises
670 if ( ticks % (TICKS_PER_SECOND * 4) == (y + x * map.height) % (TICKS_PER_SECOND * 4) && rand() % 3 == 0 )
671 {
672 if ( lavatiles[map.tiles[index]] )
673 {
674 // bubbling lava
675 playSoundPosLocal( x * 16 + 8, y * 16 + 8, 155, 100 );
676 }
677 else if ( swimmingtiles[map.tiles[index]] )
678 {
679 // running water
680 playSoundPosLocal( x * 16 + 8, y * 16 + 8, 135, 32 );
681 }
682 }
683
684 // lava bubbles
685 if ( lavatiles[map.tiles[index]] )
686 {
687 if ( ticks % 40 == (y + x * map.height) % 40 && rand() % 3 == 0 )
688 {
689 int c, j = 1 + rand() % 2;
690 for ( c = 0; c < j; ++c )
691 {
692 Entity* entity = newEntity(42, 1, map.entities, nullptr); //Gib entity.
693 entity->behavior = &actGib;
694 entity->x = x * 16 + rand() % 16;
695 entity->y = y * 16 + rand() % 16;
696 entity->z = 7.5;
697 entity->flags[PASSABLE] = true;
698 entity->flags[SPRITE] = true;
699 entity->flags[NOUPDATE] = true;
700 entity->flags[UPDATENEEDED] = false;
701 entity->flags[UNCLICKABLE] = true;
702 entity->sizex = 2;
703 entity->sizey = 2;
704 entity->fskill[3] = 0.01;
705 double vel = (rand() % 10) / 20.f;
706 entity->vel_x = vel * cos(entity->yaw);
707 entity->vel_y = vel * sin(entity->yaw);
708 entity->vel_z = -.15 - (rand() % 15) / 100.f;
709 entity->yaw = (rand() % 360) * PI / 180.0;
710 entity->pitch = (rand() % 360) * PI / 180.0;
711 entity->roll = (rand() % 360) * PI / 180.0;
712 if ( multiplayer != CLIENT )
713 {
714 --entity_uids;
715 }
716 entity->setUID(-3);
717 }
718 }
719 }
720 }
721 }
722 }
723 }
724 }
725 }
726
727 // periodic steam achievement check
728 if ( ticks % TICKS_PER_SECOND == 0 )
729 {
730 for ( c = 0; c < MAXPLAYERS; c++ )
731 {
732 if ( client_disconnected[c] )
733 {
734 continue;
735 }
736 int followerCount = list_Size(&stats[c]->FOLLOWERS);
737 if ( followerCount >= 3 )
738 {
739 steamAchievementClient(c, "BARONY_ACH_NATURAL_BORN_LEADER");
740 }
741 if ( stats[c]->GOLD >= 10000 )
742 {
743 steamAchievementClient(c, "BARONY_ACH_FILTHY_RICH");
744 }
745 if ( stats[c]->GOLD >= 100000 )
746 {
747 steamAchievementClient(c, "BARONY_ACH_GILDED");
748 }
749
750 if ( stats[c]->helmet && stats[c]->helmet->type == ARTIFACT_HELM
751 && stats[c]->breastplate && stats[c]->breastplate->type == ARTIFACT_BREASTPIECE
752 && stats[c]->gloves && stats[c]->gloves->type == ARTIFACT_GLOVES
753 && stats[c]->cloak && stats[c]->cloak->type == ARTIFACT_CLOAK
754 && stats[c]->shoes && stats[c]->shoes->type == ARTIFACT_BOOTS )
755 {
756 steamAchievementClient(c, "BARONY_ACH_GIFTS_ETERNALS");
757 }
758
759 if ( stats[c]->type == SKELETON
760 && stats[c]->weapon && stats[c]->weapon->type == ARTIFACT_AXE
761 && stats[c]->cloak && stats[c]->cloak->type == CLOAK_PROTECTION
762 && !stats[c]->gloves && !stats[c]->helmet && !stats[c]->shoes
763 && !stats[c]->breastplate && !stats[c]->mask && !stats[c]->ring
764 && !stats[c]->amulet && !stats[c]->shield )
765 {
766 // nothing but an axe and a cloak.
767 steamAchievementClient(c, "BARONY_ACH_COMEDIAN");
768 }
769
770 if ( stats[c]->EFFECTS[EFF_SHRINE_RED_BUFF]
771 && stats[c]->EFFECTS[EFF_SHRINE_GREEN_BUFF]
772 && stats[c]->EFFECTS[EFF_SHRINE_BLUE_BUFF] )
773 {
774 steamAchievementClient(c, "BARONY_ACH_WELL_PREPARED");
775 }
776
777 if ( achievementStatusRhythmOfTheKnight[c] )
778 {
779 steamAchievementClient(c, "BARONY_ACH_RHYTHM_OF_THE_KNIGHT");
780 }
781 if ( achievementStatusThankTheTank[c] )
782 {
783 steamAchievementClient(c, "BARONY_ACH_THANK_THE_TANK");
784 }
785
786 int bodyguards = 0;
787 int squadGhouls = 0;
788 int badRomance = 0;
789 int familyReunion = 0;
790 int machineHead = 0;
791
792 if ( followerCount >= 4 )
793 {
794 achievementObserver.playerAchievements[c].caughtInAMoshTargets.clear();
795 }
796 for ( node = stats[c]->FOLLOWERS.first; node != nullptr; node = node->next )
797 {
798 Entity* follower = nullptr;
799 if ( (Uint32*)node->element )
800 {
801 follower = uidToEntity(*((Uint32*)node->element));
802 }
803 if ( follower )
804 {
805 Stat* followerStats = follower->getStats();
806 if ( followerStats )
807 {
808 if ( followerStats->type == CRYSTALGOLEM )
809 {
810 ++bodyguards;
811 }
812 if ( followerStats->type == GHOUL && stats[c]->type == SKELETON )
813 {
814 ++squadGhouls;
815 }
816 if ( stats[c]->type == SUCCUBUS && (followerStats->type == SUCCUBUS || followerStats->type == INCUBUS) )
817 {
818 ++badRomance;
819 }
820 if ( stats[c]->type == GOATMAN && followerStats->type == GOATMAN )
821 {
822 ++familyReunion;
823 }
824 if ( followerStats->type == GYROBOT || followerStats->type == SENTRYBOT || followerStats->type == SPELLBOT )
825 {
826 machineHead |= (followerStats->type == GYROBOT);
827 machineHead |= (followerStats->type == SENTRYBOT) << 1;
828 machineHead |= (followerStats->type == SPELLBOT) << 2;
829
830 if ( followerCount >= 4 && !(achievementObserver.playerAchievements[c].caughtInAMosh) )
831 {
832 if ( follower->monsterTarget != 0
833 && (follower->monsterState == MONSTER_STATE_ATTACK || follower->monsterState == MONSTER_STATE_HUNT) &&
834 (followerStats->type == SENTRYBOT || followerStats->type == SPELLBOT) )
835 {
836 auto it = achievementObserver.playerAchievements[c].caughtInAMoshTargets.find(follower->monsterTarget);
837 if ( it != achievementObserver.playerAchievements[c].caughtInAMoshTargets.end() )
838 {
839 // key exists.
840 achievementObserver.playerAchievements[c].caughtInAMoshTargets[follower->monsterTarget] += 1; // increase value
841 if ( achievementObserver.playerAchievements[c].caughtInAMoshTargets[follower->monsterTarget] >= 4 )
842 {
843 achievementObserver.awardAchievement(c, AchievementObserver::BARONY_ACH_CAUGHT_IN_A_MOSH);
844 achievementObserver.playerAchievements[c].caughtInAMosh = true;
845 }
846 }
847 else
848 {
849 achievementObserver.playerAchievements[c].caughtInAMoshTargets.insert(std::make_pair(static_cast<Uint32>(follower->monsterTarget), 1));
850 }
851 }
852 }
853 }
854 }
855 }
856 }
857 if ( bodyguards >= 2 )
858 {
859 steamAchievementClient(c, "BARONY_ACH_BODYGUARDS");
860 }
861 if ( squadGhouls >= 4 )
862 {
863 steamAchievementClient(c, "BARONY_ACH_SQUAD_GHOULS");
864 }
865 if ( badRomance >= 2 )
866 {
867 steamAchievementClient(c, "BARONY_ACH_BAD_ROMANCE");
868 }
869 if ( familyReunion >= 3 )
870 {
871 steamAchievementClient(c, "BARONY_ACH_FAMILY_REUNION");
872 }
873 if ( machineHead == 7 )
874 {
875 steamAchievementClient(c, "BARONY_ACH_MACHINE_HEAD");
876 }
877 if ( achievementObserver.playerAchievements[c].ticksSpentOverclocked >= 250 )
878 {
879 Uint32 increase = achievementObserver.playerAchievements[c].ticksSpentOverclocked / TICKS_PER_SECOND;
880 steamStatisticUpdateClient(c, STEAM_STAT_OVERCLOCKED, STEAM_STAT_INT, increase);
881
882 // add the leftover sub-second ticks result back into the score.
883 achievementObserver.playerAchievements[c].ticksSpentOverclocked = increase % TICKS_PER_SECOND;
884 }
885 }
886 updateGameplayStatisticsInMainLoop();
887 }
888
889 updatePlayerConductsInMainLoop();
890
891 //if( TICKS_PER_SECOND )
892 //generatePathMaps();
893 DebugStats.eventsT3 = std::chrono::high_resolution_clock::now();
894 for ( node = map.entities->first; node != nullptr; node = nextnode )
895 {
896 nextnode = node->next;
897 entity = (Entity*)node->element;
898 if ( entity && !entity->ranbehavior )
899 {
900 if ( !gamePaused || (multiplayer && !client_disconnected[0]) )
901 {
902 ++entity->ticks;
903 }
904 if ( entity->behavior != nullptr )
905 {
906 if ( gameloopFreezeEntities
907 && entity->behavior != &actPlayer
908 && entity->behavior != &actPlayerLimb
909 && entity->behavior != &actHudWeapon
910 && entity->behavior != &actHudShield
911 && entity->behavior != &actHudAdditional
912 && entity->behavior != &actHudArrowModel
913 && entity->behavior != &actLeftHandMagic
914 && entity->behavior != &actRightHandMagic )
915 {
916 continue;
917 }
918 int ox = -1;
919 int oy = -1;
920 if ( !gamePaused || (multiplayer && !client_disconnected[0]) )
921 {
922 ox = static_cast<int>(entity->x) >> 4;
923 oy = static_cast<int>(entity->y) >> 4;
924 if ( !entity->myTileListNode )
925 {
926 TileEntityList.addEntity(*entity);
927 }
928
929 /*if ( entity->getUID() >= 0 && entity->behavior != &actFlame && !entity->flags[INVISIBLE]
930 && entity->behavior != &actDoor && entity->behavior != &actDoorFrame
931 && entity->behavior != &actGate && entity->behavior != &actTorch
932 && entity->behavior != &actSprite && !entity->flags[SPRITE]
933 && entity->skill[28] == 0 )
934 {
935 printlog("DEBUG: Starting Entity sprite: %d", entity->sprite);
936 }*/
937 (*entity->behavior)(entity);
938
939 }
940 if ( entitiesdeleted.first != nullptr )
941 {
942 entitydeletedself = false;
943 for ( node2 = entitiesdeleted.first; node2 != nullptr; node2 = node2->next )
944 {
945 if ( entity == (Entity*)node2->element )
946 {
947 //printlog("DEBUG: Entity deleted self, sprite: %d", entity->sprite);
948 entitydeletedself = true;
949 break;
950 }
951 }
952 if ( entitydeletedself == false )
953 {
954 if ( ox != -1 && oy != -1 )
955 {
956 if ( ox != static_cast<int>(entity->x) >> 4
957 || oy != static_cast<int>(entity->y) >> 4 )
958 {
959 // if entity moved into a new tile, update it's tile position in global tile list.
960 TileEntityList.updateEntity(*entity);
961 }
962 }
963 entity->ranbehavior = true;
964 }
965 nextnode = map.entities->first;
966 list_FreeAll(&entitiesdeleted);
967 }
968 else
969 {
970 if ( ox != -1 && oy != -1 )
971 {
972 if ( ox != static_cast<int>(entity->x) >> 4
973 || oy != static_cast<int>(entity->y) >> 4 )
974 {
975 // if entity moved into a new tile, update it's tile position in global tile list.
976 TileEntityList.updateEntity(*entity);
977 }
978 }
979 entity->ranbehavior = true;
980 nextnode = node->next;
981 }
982 }
983 }
984
985 if ( loadnextlevel == true )
986 {
987 for ( node = map.entities->first; node != nullptr; node = node->next )
988 {
989 entity = (Entity*)node->element;
990 entity->flags[NOUPDATE] = true;
991
992 if ( (entity->behavior == &actThrown || entity->behavior == &actParticleSapCenter) && entity->sprite == 977 )
993 {
994 // boomerang particle, make sure to return on level change.
995 Entity* parent = uidToEntity(entity->parent);
996 if ( parent && parent->behavior == &actPlayer && stats[parent->skill[2]] )
997 {
998 Item* item = newItemFromEntity(entity);
999 if ( !item )
1000 {
1001 continue;
1002 }
1003 item->ownerUid = parent->getUID();
1004 Item* pickedUp = itemPickup(parent->skill[2], item);
1005 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
1006 messagePlayerColor(parent->skill[2], color, language[3746], items[item->type].name_unidentified);
1007 if ( pickedUp )
1008 {
1009 if ( parent->skill[2] == 0 )
1010 {
1011 // pickedUp is the new inventory stack for server, free the original items
1012 free(item);
1013 item = nullptr;
1014 if ( multiplayer != CLIENT && !stats[parent->skill[2]]->weapon )
1015 {
1016 useItem(pickedUp, parent->skill[2]);
1017 }
1018 if ( magicBoomerangHotbarSlot >= 0 )
1019 {
1020 hotbar[magicBoomerangHotbarSlot].item = pickedUp->uid;
1021 for ( int i = 0; i < NUM_HOTBAR_SLOTS; ++i )
1022 {
1023 if ( i != magicBoomerangHotbarSlot && hotbar[i].item == pickedUp->uid )
1024 {
1025 hotbar[i].item = 0;
1026 }
1027 }
1028 }
1029 }
1030 else
1031 {
1032 free(pickedUp); // item is the picked up items (item == pickedUp)
1033 }
1034 }
1035 }
1036 }
1037 }
1038
1039 // hack to fix these things from breaking everything...
1040 hudarm = nullptr;
1041 hudweapon = nullptr;
1042 magicLeftHand = nullptr;
1043 magicRightHand = nullptr;
1044
1045 // stop all sounds
1046 #ifdef USE_FMOD
1047 if ( sound_group )
1048 {
1049 FMOD_ChannelGroup_Stop(sound_group);
1050 }
1051 if ( soundAmbient_group )
1052 {
1053 FMOD_ChannelGroup_Stop(soundAmbient_group);
1054 }
1055 if ( soundEnvironment_group )
1056 {
1057 FMOD_ChannelGroup_Stop(soundEnvironment_group);
1058 }
1059 #elif defined USE_OPENAL
1060 if ( sound_group )
1061 {
1062 OPENAL_ChannelGroup_Stop(sound_group);
1063 }
1064 if ( soundAmbient_group )
1065 {
1066 OPENAL_ChannelGroup_Stop(soundAmbient_group);
1067 }
1068 if ( soundEnvironment_group )
1069 {
1070 OPENAL_ChannelGroup_Stop(soundEnvironment_group);
1071 }
1072 #endif
1073 // stop combat music
1074 // close chests
1075 for ( c = 0; c < MAXPLAYERS; ++c )
1076 {
1077 assailantTimer[c] = 0;
1078 if ( c > 0 && !client_disconnected[c] )
1079 {
1080 if ( openedChest[c] )
1081 {
1082 openedChest[c]->closeChestServer();
1083 }
1084 }
1085 else if ( c == 0 )
1086 {
1087 if ( openedChest[c] )
1088 {
1089 openedChest[c]->closeChest();
1090 }
1091 }
1092 }
1093
1094
1095
1096 // show loading message
1097 loading = true;
1098 drawClearBuffers();
1099 int w, h;
1100 TTF_SizeUTF8(ttf16, language[709], &w, &h);
1101 ttfPrintText(ttf16, (xres - w) / 2, (yres - h) / 2, language[709]);
1102
1103 GO_SwapBuffers(screen);
1104
1105 // copy followers list
1106 list_t tempFollowers[MAXPLAYERS];
1107 bool bCopyFollowers = true;
1108 if ( gameModeManager.getMode() == GameModeManager_t::GAME_MODE_TUTORIAL )
1109 {
1110 bCopyFollowers = false;
1111 }
1112
1113 for ( c = 0; c < MAXPLAYERS; ++c )
1114 {
1115 tempFollowers[c].first = nullptr;
1116 tempFollowers[c].last = nullptr;
1117
1118 node_t* node;
1119 for ( node = stats[c]->FOLLOWERS.first; bCopyFollowers && node != nullptr; node = node->next )
1120 {
1121 Entity* follower = nullptr;
1122 if ( (Uint32*)node->element )
1123 {
1124 follower = uidToEntity(*((Uint32*)node->element));
1125 }
1126 if ( follower )
1127 {
1128 Stat* followerStats = follower->getStats();
1129 if ( followerStats )
1130 {
1131 node_t* newNode = list_AddNodeLast(&tempFollowers[c]);
1132 newNode->element = followerStats->copyStats();
1133 // newNode->deconstructor = &followerStats->~Stat;
1134 newNode->size = sizeof(followerStats);
1135 }
1136 }
1137 }
1138
1139 list_FreeAll(&stats[c]->FOLLOWERS);
1140 }
1141
1142 // unlock some steam achievements
1143 if ( !secretlevel )
1144 {
1145 switch ( currentlevel )
1146 {
1147 case 0:
1148 steamAchievement("BARONY_ACH_ENTER_THE_DUNGEON");
1149 break;
1150 default:
1151 break;
1152 }
1153 }
1154
1155 // signal clients about level change
1156 mapseed = rand();
1157 lastEntityUIDs = entity_uids;
1158 if ( forceMapSeed > 0 )
1159 {
1160 mapseed = forceMapSeed;
1161 forceMapSeed = 0;
1162 }
1163
1164 bool loadingTheSameFloorAsCurrent = false;
1165 if ( skipLevelsOnLoad > 0 )
1166 {
1167 currentlevel += skipLevelsOnLoad;
1168 }
1169 else
1170 {
1171 if ( skipLevelsOnLoad < 0 )
1172 {
1173 currentlevel += skipLevelsOnLoad;
1174 if ( skipLevelsOnLoad == -1 )
1175 {
1176 loadingTheSameFloorAsCurrent = true;
1177 }
1178 }
1179 ++currentlevel;
1180 }
1181 skipLevelsOnLoad = 0;
1182
1183 if ( !secretlevel )
1184 {
1185 switch ( currentlevel )
1186 {
1187 case 5:
1188 steamAchievement("BARONY_ACH_TWISTY_PASSAGES");
1189 // the observer will send out to clients.
1190 achievementObserver.updatePlayerAchievement(clientnum,
1191 AchievementObserver::Achievement::BARONY_ACH_COOP_ESCAPE_MINES, AchievementObserver::ACH_EVENT_NONE);
1192 break;
1193 case 10:
1194 steamAchievement("BARONY_ACH_JUNGLE_FEVER");
1195 break;
1196 case 15:
1197 steamAchievement("BARONY_ACH_SANDMAN");
1198 break;
1199 case 30:
1200 steamAchievement("BARONY_ACH_SPELUNKY");
1201 break;
1202 case 35:
1203 if ( ((completionTime / TICKS_PER_SECOND) / 60) <= 45 )
1204 {
1205 conductGameChallenges[CONDUCT_BLESSED_BOOTS_SPEED] = 1;
1206 }
1207 break;
1208 default:
1209 break;
1210 }
1211 }
1212
1213 if ( multiplayer == SERVER )
1214 {
1215 for ( c = 1; c < MAXPLAYERS; ++c )
1216 {
1217 if ( client_disconnected[c] == true )
1218 {
1219 continue;
1220 }
1221 if ( loadingSameLevelAsCurrent )
1222 {
1223 strcpy((char*)net_packet->data, "LVLR");
1224 }
1225 else
1226 {
1227 strcpy((char*)net_packet->data, "LVLC");
1228 }
1229 net_packet->data[4] = secretlevel;
1230 SDLNet_Write32(mapseed, &net_packet->data[5]);
1231 SDLNet_Write32(lastEntityUIDs, &net_packet->data[9]);
1232 net_packet->data[13] = currentlevel;
1233
1234 if ( loadCustomNextMap.compare("") != 0 )
1235 {
1236 strcpy((char*)(&net_packet->data[14]), loadCustomNextMap.c_str());
1237 net_packet->data[14 + loadCustomNextMap.length()] = 0;
1238 net_packet->len = 14 + loadCustomNextMap.length() + 1;
1239 }
1240 else
1241 {
1242 net_packet->data[14] = 0;
1243 net_packet->len = 15;
1244 }
1245 net_packet->address.host = net_clients[c - 1].host;
1246 net_packet->address.port = net_clients[c - 1].port;
1247 sendPacketSafe(net_sock, -1, net_packet, c - 1);
1248 }
1249 }
1250 loadingSameLevelAsCurrent = false;
1251 darkmap = false;
1252 numplayers = 0;
1253
1254 gameplayCustomManager.readFromFile();
1255 textSourceScript.scriptVariables.clear();
1256
1257 int checkMapHash = -1;
1258 int result = physfsLoadMapFile(currentlevel, mapseed, false, &checkMapHash);
1259 if ( checkMapHash == 0 )
1260 {
1261 conductGameChallenges[CONDUCT_MODDED] = 1;
1262 }
1263
1264 minimapPings.clear(); // clear minimap pings
1265 globalLightModifierActive = GLOBAL_LIGHT_MODIFIER_STOPPED;
1266
1267 // clear follower menu entities.
1268 FollowerMenu.closeFollowerMenuGUI(true);
1269
1270 assignActions(&map);
1271 generatePathMaps();
1272
1273 achievementObserver.updateData();
1274
1275 if ( !strncmp(map.name, "Mages Guild", 11) )
1276 {
1277 for ( c = 0; c < MAXPLAYERS; ++c )
1278 {
1279 if ( players[c] && players[c]->entity )
1280 {
1281 players[c]->entity->modHP(999);
1282 players[c]->entity->modMP(999);
1283 if ( stats[c] && stats[c]->HUNGER < 1450 )
1284 {
1285 stats[c]->HUNGER = 1450;
1286 serverUpdateHunger(c);
1287 }
1288 }
1289 }
1290 messagePlayer(clientnum, language[2599]);
1291
1292 // undo shopkeeper grudge
1293 swornenemies[SHOPKEEPER][HUMAN] = false;
1294 monsterally[SHOPKEEPER][HUMAN] = true;
1295 swornenemies[SHOPKEEPER][AUTOMATON] = false;
1296 monsterally[SHOPKEEPER][AUTOMATON] = true;
1297 }
1298
1299 // (special) unlock temple achievement
1300 if ( secretlevel && currentlevel == 8 )
1301 {
1302 steamAchievement("BARONY_ACH_TRICKS_AND_TRAPS");
1303 }
1304 // flag setting we've reached the lich
1305 if ( !strncmp(map.name, "Boss", 4) || !strncmp(map.name, "Sanctum", 7) )
1306 {
1307 for ( int c = 0; c < MAXPLAYERS; ++c )
1308 {
1309 steamStatisticUpdateClient(c, STEAM_STAT_BACK_TO_BASICS, STEAM_STAT_INT, 1);
1310 }
1311 }
1312
1313 if ( !secretlevel )
1314 {
1315 messagePlayer(clientnum, language[710], currentlevel);
1316 }
1317 else
1318 {
1319 messagePlayer(clientnum, language[711], map.name);
1320 }
1321 if ( !secretlevel && result )
1322 {
1323 switch ( currentlevel )
1324 {
1325 case 2:
1326 messagePlayer(clientnum, language[712]);
1327 break;
1328 case 3:
1329 messagePlayer(clientnum, language[713]);
1330 break;
1331 case 7:
1332 messagePlayer(clientnum, language[714]);
1333 break;
1334 case 8:
1335 messagePlayer(clientnum, language[715]);
1336 break;
1337 case 11:
1338 messagePlayer(clientnum, language[716]);
1339 break;
1340 case 13:
1341 messagePlayer(clientnum, language[717]);
1342 break;
1343 case 16:
1344 messagePlayer(clientnum, language[718]);
1345 break;
1346 case 18:
1347 messagePlayer(clientnum, language[719]);
1348 break;
1349 default:
1350 break;
1351 }
1352 }
1353 if ( MFLAG_DISABLETELEPORT || MFLAG_DISABLEOPENING )
1354 {
1355 messagePlayer(clientnum, language[2382]);
1356 }
1357 if ( MFLAG_DISABLELEVITATION )
1358 {
1359 messagePlayer(clientnum, language[2383]);
1360 }
1361 if ( MFLAG_DISABLEDIGGING )
1362 {
1363 messagePlayer(clientnum, language[2450]);
1364 }
1365 loadnextlevel = false;
1366 loading = false;
1367 fadeout = false;
1368 fadealpha = 255;
1369
1370 for (c = 0; c < MAXPLAYERS; c++)
1371 {
1372 if (players[c] && players[c]->entity && !client_disconnected[c])
1373 {
1374 if ( stats[c] && stats[c]->EFFECTS[EFF_POLYMORPH] && stats[c]->playerPolymorphStorage != NOTHING )
1375 {
1376 players[c]->entity->effectPolymorph = stats[c]->playerPolymorphStorage;
1377 serverUpdateEntitySkill(players[c]->entity, 50); // update visual polymorph effect for clients.
1378 }
1379 if ( stats[c] && stats[c]->EFFECTS[EFF_SHAPESHIFT] && stats[c]->playerShapeshiftStorage != NOTHING )
1380 {
1381 players[c]->entity->effectShapeshift = stats[c]->playerShapeshiftStorage;
1382 serverUpdateEntitySkill(players[c]->entity, 53); // update visual polymorph effect for clients.
1383 }
1384 if ( stats[c] && stats[c]->EFFECTS[EFF_VAMPIRICAURA] && stats[c]->EFFECTS_TIMERS[EFF_VAMPIRICAURA] == -2 )
1385 {
1386 players[c]->entity->playerVampireCurse = 1;
1387 serverUpdateEntitySkill(players[c]->entity, 51); // update curse progression
1388 }
1389
1390 node_t* node;
1391 node_t* gyrobotNode = nullptr;
1392 Entity* gyrobotEntity = nullptr;
1393 std::vector<node_t*> allyRobotNodes;
1394 for ( node = tempFollowers[c].first; node != NULL; node = node->next )
1395 {
1396 Stat* tempStats = (Stat*)node->element;
1397 if ( tempStats && tempStats->type == GYROBOT )
1398 {
1399 gyrobotNode = node;
1400 break;
1401 }
1402 }
1403 for (node = tempFollowers[c].first; node != nullptr; node = node->next)
1404 {
1405 Stat* tempStats = (Stat*)node->element;
1406 if ( tempStats && (tempStats->type == DUMMYBOT
1407 || tempStats->type == SENTRYBOT
1408 || tempStats->type == SPELLBOT) )
1409 {
1410 // gyrobot will pick up these guys into it's inventory, otherwise leave them behind.
1411 if ( gyrobotNode )
1412 {
1413 allyRobotNodes.push_back(node);
1414 }
1415 continue;
1416 }
1417 Entity* monster = summonMonster(tempStats->type, players[c]->entity->x, players[c]->entity->y);
1418 if (monster)
1419 {
1420 if ( node == gyrobotNode )
1421 {
1422 gyrobotEntity = monster;
1423 }
1424 monster->skill[3] = 1; // to mark this monster partially initialized
1425 list_RemoveNode(monster->children.last);
1426
1427 node_t* newNode = list_AddNodeLast(&monster->children);
1428 newNode->element = tempStats->copyStats();
1429 // newNode->deconstructor = &tempStats->~Stat;
1430 newNode->size = sizeof(tempStats);
1431
1432 Stat* monsterStats = (Stat*)newNode->element;
1433 monsterStats->leader_uid = players[c]->entity->getUID();
1434 messagePlayerMonsterEvent(c, 0xFFFFFFFF, *monsterStats, language[721], language[720], MSG_COMBAT);
1435 monster->flags[USERFLAG2] = true;
1436 serverUpdateEntityFlag(monster, USERFLAG2);
1437 /*if (!monsterally[HUMAN][monsterStats->type])
1438 {
1439 }*/
1440 monster->monsterAllyIndex = c;
1441 if ( multiplayer == SERVER )
1442 {
1443 serverUpdateEntitySkill(monster, 42); // update monsterAllyIndex for clients.
1444 }
1445
1446 if ( multiplayer != CLIENT )
1447 {
1448 monster->monsterAllyClass = monsterStats->allyClass;
1449 monster->monsterAllyPickupItems = monsterStats->allyItemPickup;
1450 if ( stats[c]->playerSummonPERCHR != 0 && !strcmp(monsterStats->name, "skeleton knight") )
1451 {
1452 monster->monsterAllySummonRank = (stats[c]->playerSummonPERCHR & 0x0000FF00) >> 8;
1453 }
1454 else if ( stats[c]->playerSummon2PERCHR != 0 && !strcmp(monsterStats->name, "skeleton sentinel") )
1455 {
1456 monster->monsterAllySummonRank = (stats[c]->playerSummon2PERCHR & 0x0000FF00) >> 8;
1457 }
1458 serverUpdateEntitySkill(monster, 46); // update monsterAllyClass
1459 serverUpdateEntitySkill(monster, 44); // update monsterAllyPickupItems
1460 serverUpdateEntitySkill(monster, 50); // update monsterAllySummonRank
1461 }
1462
1463 newNode = list_AddNodeLast(&stats[c]->FOLLOWERS);
1464 newNode->deconstructor = &defaultDeconstructor;
1465 Uint32* myuid = (Uint32*) malloc(sizeof(Uint32));
1466 newNode->element = myuid;
1467 *myuid = monster->getUID();
1468
1469 if ( monsterStats->type == HUMAN && currentlevel == 25 && !strncmp(map.name, "Mages Guild", 11) )
1470 {
1471 steamAchievementClient(c, "BARONY_ACH_ESCORT");
1472 }
1473
1474 if ( c > 0 && multiplayer == SERVER )
1475 {
1476 strcpy((char*)net_packet->data, "LEAD");
1477 SDLNet_Write32((Uint32)monster->getUID(), &net_packet->data[4]);
1478 strcpy((char*)(&net_packet->data[8]), monsterStats->name);
1479 net_packet->data[8 + strlen(monsterStats->name)] = 0;
1480 net_packet->address.host = net_clients[c - 1].host;
1481 net_packet->address.port = net_clients[c - 1].port;
1482 net_packet->len = 8 + strlen(monsterStats->name) + 1;
1483 sendPacketSafe(net_sock, -1, net_packet, c - 1);
1484
1485 serverUpdateAllyStat(c, monster->getUID(), monsterStats->LVL, monsterStats->HP, monsterStats->MAXHP, monsterStats->type);
1486 }
1487
1488 if ( !FollowerMenu.recentEntity && c == clientnum )
1489 {
1490 FollowerMenu.recentEntity = monster;
1491 }
1492 }
1493 else
1494 {
1495 messagePlayerMonsterEvent(c, 0xFFFFFFFF, *tempStats, language[723], language[722], MSG_COMBAT);
1496 }
1497 }
1498 if ( gyrobotEntity && !allyRobotNodes.empty() )
1499 {
1500 Stat* gyroStats = gyrobotEntity->getStats();
1501 for ( auto it = allyRobotNodes.begin(); gyroStats && it != allyRobotNodes.end(); ++it )
1502 {
1503 node_t* botNode = *it;
1504 if ( botNode )
1505 {
1506 Stat* tempStats = (Stat*)botNode->element;
1507 if ( tempStats )
1508 {
1509 ItemType type = WOODEN_SHIELD;
1510 if ( tempStats->type == SENTRYBOT )
1511 {
1512 type = TOOL_SENTRYBOT;
1513 }
1514 else if ( tempStats->type == SPELLBOT )
1515 {
1516 type = TOOL_SPELLBOT;
1517 }
1518 else if ( tempStats->type == DUMMYBOT )
1519 {
1520 type = TOOL_DUMMYBOT;
1521 }
1522 int appearance = monsterTinkeringConvertHPToAppearance(tempStats);
1523 if ( type != WOODEN_SHIELD )
1524 {
1525 Item* item = newItem(type, static_cast<Status>(tempStats->monsterTinkeringStatus),
1526 0, 1, appearance, true, &gyroStats->inventory);
1527 }
1528 }
1529 }
1530 }
1531 }
1532 }
1533 list_FreeAll(&tempFollowers[c]);
1534 }
1535
1536 saveGame();
1537 break;
1538 }
1539 }
1540 for ( node = map.entities->first; node != nullptr; node = node->next )
1541 {
1542 entity = (Entity*)node->element;
1543 entity->ranbehavior = false;
1544 }
1545 DebugStats.eventsT4 = std::chrono::high_resolution_clock::now();
1546 if ( multiplayer == SERVER )
1547 {
1548 // periodically remind clients of the current level
1549 if ( ticks % (TICKS_PER_SECOND * 3) == 0 )
1550 {
1551 for ( c = 1; c < MAXPLAYERS; c++ )
1552 {
1553 if ( client_disconnected[c] == true )
1554 {
1555 continue;
1556 }
1557 strcpy((char*)net_packet->data, "LVLC");
1558 net_packet->data[4] = secretlevel;
1559 SDLNet_Write32(mapseed, &net_packet->data[5]);
1560 SDLNet_Write32(lastEntityUIDs, &net_packet->data[9]);
1561 net_packet->data[13] = currentlevel;
1562 net_packet->address.host = net_clients[c - 1].host;
1563 net_packet->address.port = net_clients[c - 1].port;
1564 net_packet->len = 14;
1565 sendPacketSafe(net_sock, -1, net_packet, c - 1);
1566 }
1567 }
1568
1569 bool updatePlayerHealth = false;
1570 if ( serverSchedulePlayerHealthUpdate != 0 && (ticks - serverSchedulePlayerHealthUpdate) >= TICKS_PER_SECOND * 0.5 )
1571 {
1572 // update if 0.5s have passed since request of health update.
1573 // the scheduled update needs to be reset to 0 to be set again.
1574 updatePlayerHealth = true;
1575 }
1576 else if ( (ticks % (TICKS_PER_SECOND * 3) == 0)
1577 && (serverLastPlayerHealthUpdate == 0 || ((ticks - serverLastPlayerHealthUpdate) >= 2 * TICKS_PER_SECOND))
1578 )
1579 {
1580 // regularly update every 3 seconds, only if the last update was more than 2 seconds ago.
1581 updatePlayerHealth = true;
1582 }
1583
1584 if ( updatePlayerHealth )
1585 {
1586 // send update to all clients for global stats[NUMPLAYERS] struct
1587 serverLastPlayerHealthUpdate = ticks;
1588 serverSchedulePlayerHealthUpdate = 0;
1589 serverUpdatePlayerStats();
1590 }
1591
1592 // send entity info to clients
1593 if ( ticks % (TICKS_PER_SECOND / 8) == 0 )
1594 {
1595 for ( node = map.entities->first; node != nullptr; node = node->next )
1596 {
1597 entity = (Entity*)node->element;
1598 for ( c = 1; c < MAXPLAYERS; ++c )
1599 {
1600 if ( !client_disconnected[c] )
1601 {
1602 if ( entity->flags[UPDATENEEDED] == true && entity->flags[NOUPDATE] == false )
1603 {
1604 // update entity for all clients
1605 if ( entity->getUID() % (TICKS_PER_SECOND * 4) == ticks % (TICKS_PER_SECOND * 4) )
1606 {
1607 sendEntityUDP(entity, c, true);
1608 }
1609 else
1610 {
1611 sendEntityUDP(entity, c, false);
1612 }
1613 }
1614 }
1615 }
1616 }
1617 }
1618
1619 // handle keep alives
1620 for ( c = 1; c < MAXPLAYERS; c++ )
1621 {
1622 if ( client_disconnected[c] )
1623 {
1624 continue;
1625 }
1626 if ( ticks % (TICKS_PER_SECOND * 1) == 0 )
1627 {
1628 // send a keep alive every second
1629 strcpy((char*)net_packet->data, "KPAL");
1630 net_packet->data[4] = clientnum;
1631 net_packet->address.host = net_clients[c - 1].host;
1632 net_packet->address.port = net_clients[c - 1].port;
1633 net_packet->len = 5;
1634 sendPacketSafe(net_sock, -1, net_packet, c - 1);
1635 }
1636 if ( losingConnection[c] && ticks - client_keepalive[c] == 1 )
1637 {
1638 // regained connection
1639 losingConnection[c] = false;
1640 int i;
1641 for ( i = 0; i < MAXPLAYERS; i++ )
1642 {
1643 messagePlayer(i, language[724], c, stats[c]->name);
1644 }
1645 }
1646 else if ( !losingConnection[c] && ticks - client_keepalive[c] == TICKS_PER_SECOND * 30 - 1 )
1647 {
1648 // 30 second timer
1649 losingConnection[c] = true;
1650 int i;
1651 for ( i = 0; i < MAXPLAYERS; i++ )
1652 {
1653 messagePlayer(clientnum, language[725], c, stats[c]->name);
1654 }
1655 }
1656 else if ( !client_disconnected[c] && ticks - client_keepalive[c] >= TICKS_PER_SECOND * 45 - 1 )
1657 {
1658 // additional 15 seconds (kick time)
1659 int i;
1660 for ( i = 0; i < MAXPLAYERS; i++ )
1661 {
1662 messagePlayer(clientnum, language[726], c, stats[c]->name);
1663 }
1664 strcpy((char*)net_packet->data, "KICK");
1665 net_packet->address.host = net_clients[c - 1].host;
1666 net_packet->address.port = net_clients[c - 1].port;
1667 net_packet->len = 4;
1668 sendPacketSafe(net_sock, -1, net_packet, c - 1);
1669 client_disconnected[c] = true;
1670 }
1671 }
1672 }
1673
1674 // update clients on assailant status
1675 if (multiplayer != SINGLE) {
1676 for ( c = 1; c < MAXPLAYERS; c++ )
1677 {
1678 if ( !client_disconnected[c] )
1679 {
1680 if ( oassailant[c] != assailant[c] )
1681 {
1682 oassailant[c] = assailant[c];
1683 strcpy((char*)net_packet->data, "MUSM");
1684 net_packet->address.host = net_clients[c - 1].host;
1685 net_packet->address.port = net_clients[c - 1].port;
1686 net_packet->data[3] = assailant[c];
1687 net_packet->len = 4;
1688 sendPacketSafe(net_sock, -1, net_packet, c - 1);
1689 }
1690 }
1691 }
1692 }
1693 combat = assailant[0];
1694 for ( j = 0; j < MAXPLAYERS; j++ )
1695 {
1696 client_selected[j] = NULL;
1697 }
1698
1699 bool tooManySpells = (list_Size(&spellList) >= INVENTORY_SIZEX * 3);
1700 int backpack_sizey = 3;
1701 if ( stats[clientnum]->cloak && stats[clientnum]->cloak->type == CLOAK_BACKPACK
1702 && (shouldInvertEquipmentBeatitude(stats[clientnum]) ? abs(stats[clientnum]->cloak->beatitude) >= 0 : stats[clientnum]->cloak->beatitude >= 0) )
1703 {
1704 backpack_sizey = 4;
1705 }
1706
1707 if ( tooManySpells && gui_mode == GUI_MODE_INVENTORY && inventory_mode == INVENTORY_MODE_SPELL )
1708 {
1709 INVENTORY_SIZEY = 4 + ((list_Size(&spellList) - (INVENTORY_SIZEX * 3)) / INVENTORY_SIZEX);
1710 }
1711 else if ( backpack_sizey == 4 )
1712 {
1713 INVENTORY_SIZEY = 4;
1714 }
1715 else
1716 {
1717 if ( INVENTORY_SIZEY > 3 && !tooManySpells )
1718 {
1719 // we should rearrange our spells.
1720 for ( node_t* node = stats[clientnum]->inventory.first; node != NULL; node = node->next )
1721 {
1722 int scanx = 0;
1723 int scany = 0;
1724 bool notfree = false;
1725 bool foundaspot = false;
1726 Item* item = (Item*)node->element;
1727 if ( itemCategory(item) != SPELL_CAT )
1728 {
1729 continue;
1730 }
1731 if ( item->appearance >= 1000 )
1732 {
1733 continue; // shaman spells.
1734 }
1735 while ( 1 )
1736 {
1737 for ( scany = 0; scany < 3; scany++ )
1738 {
1739 node_t* node2;
1740 for ( node2 = stats[clientnum]->inventory.first; node2 != NULL; node2 = node2->next )
1741 {
1742 Item* tempItem = (Item*)node2->element;
1743 if ( tempItem == item )
1744 {
1745 continue;
1746 }
1747 if ( tempItem )
1748 {
1749 if ( tempItem->x == scanx && tempItem->y == scany )
1750 {
1751 if ( itemCategory(tempItem) == SPELL_CAT )
1752 {
1753 notfree = true; //Both spells. Can't fit in the same slot.
1754 }
1755 }
1756 }
1757 }
1758 if ( notfree )
1759 {
1760 notfree = false;
1761 continue;
1762 }
1763 item->x = scanx;
1764 item->y = scany;
1765 foundaspot = true;
1766 break;
1767 }
1768 if ( foundaspot )
1769 {
1770 break;
1771 }
1772 scanx++;
1773 }
1774 }
1775 }
1776 INVENTORY_SIZEY = 3;
1777 }
1778
1779 DebugStats.eventsT5 = std::chrono::high_resolution_clock::now();
1780
1781 int bloodCount = 0;
1782 for ( node = stats[clientnum]->inventory.first; node != NULL; node = nextnode )
1783 {
1784 nextnode = node->next;
1785 Item* item = (Item*)node->element;
1786 if ( !item )
1787 {
1788 continue;
1789 }
1790 // unlock achievements for special collected items
1791 switch ( item->type )
1792 {
1793 case ARTIFACT_SWORD:
1794 steamAchievement("BARONY_ACH_KING_ARTHURS_BLADE");
1795 break;
1796 case ARTIFACT_MACE:
1797 steamAchievement("BARONY_ACH_SPUD_LORD");
1798 break;
1799 case ARTIFACT_AXE:
1800 steamAchievement("BARONY_ACH_THANKS_MR_SKELTAL");
1801 break;
1802 case ARTIFACT_SPEAR:
1803 steamAchievement("BARONY_ACH_SPEAR_OF_DESTINY");
1804 break;
1805 default:
1806 break;
1807 }
1808
1809 if ( item->type == FOOD_BLOOD )
1810 {
1811 bloodCount += item->count;
1812 if ( bloodCount >= 20 )
1813 {
1814 steamAchievement("BARONY_ACH_BLOOD_VESSELS");
1815 }
1816 }
1817
1818 if ( itemCategory(item) == WEAPON )
1819 {
1820 if ( item->beatitude >= 10 )
1821 {
1822 steamAchievement("BARONY_ACH_BLESSED");
1823 }
1824 }
1825
1826 // drop any inventory items you don't have room for
1827 if ( itemCategory(item) != SPELL_CAT && (item->x >= INVENTORY_SIZEX || item->y >= backpack_sizey) )
1828 {
1829 messagePlayer(clientnum, language[727], item->getName());
1830 bool droppedAll = false;
1831 while ( item && item->count > 1 )
1832 {
1833 droppedAll = dropItem(item, clientnum);
1834 if ( droppedAll )
1835 {
1836 item = nullptr;
1837 }
1838 }
1839 if ( !droppedAll )
1840 {
1841 dropItem(item, clientnum);
1842 }
1843 }
1844 else
1845 {
1846 if ( auto_appraise_new_items && appraisal_timer == 0 && !(item->identified) )
1847 {
1848 int appraisal_time = getAppraisalTime(item);
1849 if (appraisal_time < auto_appraise_lowest_time)
1850 {
1851 auto_appraise_target = item;
1852 auto_appraise_lowest_time = appraisal_time;
1853 }
1854 }
1855 }
1856 }
1857
1858 DebugStats.eventsT6 = std::chrono::high_resolution_clock::now();
1859
1860 if ( kills[SHOPKEEPER] >= 3 )
1861 {
1862 steamAchievement("BARONY_ACH_PROFESSIONAL_BURGLAR");
1863 }
1864 if ( kills[HUMAN] >= 10 )
1865 {
1866 steamAchievement("BARONY_ACH_HOMICIDAL_MANIAC");
1867 }
1868 }
1869 else if ( multiplayer == CLIENT )
1870 {
1871 // keep alives
1872 if ( multiplayer == CLIENT ) //lol
1873 {
1874 if ( ticks % (TICKS_PER_SECOND * 1) == 0 )
1875 {
1876 // send a keep alive every second
1877 strcpy((char*)net_packet->data, "KPAL");
1878 net_packet->data[4] = clientnum;
1879 net_packet->address.host = net_server.host;
1880 net_packet->address.port = net_server.port;
1881 net_packet->len = 5;
1882 sendPacketSafe(net_sock, -1, net_packet, 0);
1883 }
1884 if ( losingConnection[0] && ticks - client_keepalive[0] == 1 )
1885 {
1886 // regained connection
1887 losingConnection[0] = false;
1888 messagePlayer(i, language[728]);
1889 }
1890 else if ( !losingConnection[0] && ticks - client_keepalive[0] == TICKS_PER_SECOND * 30 - 1 )
1891 {
1892 // 30 second timer
1893 losingConnection[0] = true;
1894 messagePlayer(clientnum, language[729]);
1895 }
1896 else if ( !client_disconnected[c] && ticks - client_keepalive[0] >= TICKS_PER_SECOND * 45 - 1 )
1897 {
1898 // additional 15 seconds (disconnect time)
1899 messagePlayer(clientnum, language[730]);
1900
1901 button_t* button;
1902 pauseGame(2, 0);
1903
1904 // close current window
1905 buttonCloseSubwindow(NULL);
1906 for ( node = button_l.first; node != NULL; node = nextnode )
1907 {
1908 nextnode = node->next;
1909 button = (button_t*)node->element;
1910 if ( button->focused )
1911 {
1912 list_RemoveNode(button->node);
1913 }
1914 }
1915
1916 // create new window
1917 subwindow = 1;
1918 subx1 = xres / 2 - 256;
1919 subx2 = xres / 2 + 256;
1920 suby1 = yres / 2 - 56;
1921 suby2 = yres / 2 + 56;
1922 strcpy(subtext, language[731]);
1923
1924 // close button
1925 button = newButton();
1926 strcpy(button->label, "x");
1927 button->x = subx2 - 20;
1928 button->y = suby1;
1929 button->sizex = 20;
1930 button->sizey = 20;
1931 button->action = &buttonCloseAndEndGameConfirm;
1932 button->visible = 1;
1933 button->focused = 1;
1934 button->key = SDL_SCANCODE_ESCAPE;
1935 button->joykey = joyimpulses[INJOY_MENU_CANCEL];
1936
1937 // okay button
1938 button = newButton();
1939 strcpy(button->label, language[732]);
1940 button->x = subx2 - (subx2 - subx1) / 2 - 28;
1941 button->y = suby2 - 28;
1942 button->sizex = 56;
1943 button->sizey = 20;
1944 button->action = &buttonCloseAndEndGameConfirm;
1945 button->visible = 1;
1946 button->focused = 1;
1947 button->key = SDL_SCANCODE_RETURN;
1948 button->joykey = joyimpulses[INJOY_MENU_NEXT];
1949
1950 client_disconnected[0] = true;
1951 }
1952 }
1953
1954 // animate tiles
1955 if ( !gamePaused )
1956 {
1957 int x, y, z;
1958 for ( x = 0; x < map.width; x++ )
1959 {
1960 for ( y = 0; y < map.height; y++ )
1961 {
1962 for ( z = 0; z < MAPLAYERS; z++ )
1963 {
1964 int index = z + y * MAPLAYERS + x * MAPLAYERS * map.height;
1965 if ( animatedtiles[map.tiles[index]] )
1966 {
1967 if ( ticks % 10 == 0 )
1968 {
1969 map.tiles[index]--;
1970 if ( !animatedtiles[map.tiles[index]] )
1971 {
1972 do
1973 {
1974 map.tiles[index]++;
1975 }
1976 while ( animatedtiles[map.tiles[index]] );
1977 map.tiles[index]--;
1978 }
1979 }
1980 if ( z == 0 )
1981 {
1982 // water and lava noises
1983 if ( ticks % TICKS_PER_SECOND == (y + x * map.height) % TICKS_PER_SECOND && rand() % 3 == 0 )
1984 {
1985 if ( lavatiles[map.tiles[index]] )
1986 {
1987 // bubbling lava
1988 playSoundPosLocal( x * 16 + 8, y * 16 + 8, 155, 100 );
1989 }
1990 else if ( swimmingtiles[map.tiles[index]] )
1991 {
1992 // running water
1993 playSoundPosLocal( x * 16 + 8, y * 16 + 8, 135, 32 );
1994 }
1995 }
1996
1997 // lava bubbles
1998 if ( lavatiles[map.tiles[index]] )
1999 {
2000 if ( ticks % 40 == (y + x * map.height) % 40 && rand() % 3 == 0 )
2001 {
2002 int c, j = 1 + rand() % 2;
2003 for ( c = 0; c < j; c++ )
2004 {
2005 Entity* entity = newEntity(42, 1, map.entities, nullptr); //Gib entity.
2006 entity->behavior = &actGib;
2007 entity->x = x * 16 + rand() % 16;
2008 entity->y = y * 16 + rand() % 16;
2009 entity->z = 7.5;
2010 entity->flags[PASSABLE] = true;
2011 entity->flags[SPRITE] = true;
2012 entity->flags[NOUPDATE] = true;
2013 entity->flags[UPDATENEEDED] = false;
2014 entity->flags[UNCLICKABLE] = true;
2015 entity->sizex = 2;
2016 entity->sizey = 2;
2017 entity->fskill[3] = 0.01;
2018 double vel = (rand() % 10) / 20.f;
2019 entity->vel_x = vel * cos(entity->yaw);
2020 entity->vel_y = vel * sin(entity->yaw);
2021 entity->vel_z = -.15 - (rand() % 15) / 100.f;
2022 entity->yaw = (rand() % 360) * PI / 180.0;
2023 entity->pitch = (rand() % 360) * PI / 180.0;
2024 entity->roll = (rand() % 360) * PI / 180.0;
2025 if ( multiplayer != CLIENT )
2026 {
2027 entity_uids--;
2028 }
2029 entity->setUID(-3);
2030 }
2031 }
2032 }
2033 }
2034 }
2035 }
2036 }
2037 }
2038 }
2039
2040 if ( ticks % TICKS_PER_SECOND == 0 )
2041 {
2042 updateGameplayStatisticsInMainLoop();
2043 }
2044
2045 updatePlayerConductsInMainLoop();
2046
2047 // ask for entity delete update
2048 if ( ticks % 4 == 0 && list_Size(map.entities) )
2049 {
2050 node_t* nodeToCheck = list_Node(map.entities, ticks % list_Size(map.entities));
2051 if ( nodeToCheck )
2052 {
2053 Entity* entity = (Entity*)nodeToCheck->element;
2054 if ( entity )
2055 {
2056 if ( !entity->flags[NOUPDATE] && entity->getUID() > 0 && entity->getUID() != -2 && entity->getUID() != -3 && entity->getUID() != -4 )
2057 {
2058 strcpy((char*)net_packet->data, "ENTE");
2059 net_packet->data[4] = clientnum;
2060 SDLNet_Write32(entity->getUID(), &net_packet->data[5]);
2061 net_packet->address.host = net_server.host;
2062 net_packet->address.port = net_server.port;
2063 net_packet->len = 9;
2064 sendPacket(net_sock, -1, net_packet, 0);
2065 }
2066 }
2067 }
2068 }
2069
2070 // run entity actions
2071 for ( node = map.entities->first; node != nullptr; node = nextnode )
2072 {
2073 nextnode = node->next;
2074 entity = (Entity*)node->element;
2075 if ( entity && !entity->ranbehavior )
2076 {
2077 if ( !gamePaused || (multiplayer && !client_disconnected[0]) )
2078 {
2079 entity->ticks++;
2080 }
2081 if ( entity->behavior != NULL )
2082 {
2083 if ( !gamePaused || (multiplayer && !client_disconnected[0]) )
2084 {
2085 (*entity->behavior)(entity);
2086 if ( entitiesdeleted.first != NULL )
2087 {
2088 entitydeletedself = false;
2089 for ( node2 = entitiesdeleted.first; node2 != NULL; node2 = node2->next )
2090 {
2091 if ( entity == (Entity*)node2->element )
2092 {
2093 entitydeletedself = true;
2094 break;
2095 }
2096 }
2097 if ( entitydeletedself == false )
2098 {
2099 entity->ranbehavior = true;
2100 }
2101 nextnode = map.entities->first;
2102 list_FreeAll(&entitiesdeleted);
2103 }
2104 else
2105 {
2106 entity->ranbehavior = true;
2107 nextnode = node->next;
2108 if ( entity->flags[UPDATENEEDED] && !entity->flags[NOUPDATE] )
2109 {
2110 // adjust entity position
2111 if ( ticks - entity->lastupdate <= TICKS_PER_SECOND / 16 )
2112 {
2113 // interpolate to new position
2114 if ( entity->behavior != &actPlayerLimb || entity->skill[2] != clientnum )
2115 {
2116 double ox = 0, oy = 0, onewx = 0, onewy = 0;
2117
2118 // move the bodyparts of these otherwise the limbs will get left behind in this adjustment.
2119 if ( entity->behavior == &actPlayer || entity->behavior == &actMonster )
2120 {
2121 ox = entity->x;
2122 oy = entity->y;
2123 onewx = entity->new_x;
2124 onewy = entity->new_y;
2125 }
2126 entity->x += (entity->new_x - entity->x) / 4;
2127 entity->y += (entity->new_y - entity->y) / 4;
2128 if ( entity->behavior != &actArrow )
2129 {
2130 // client handles z in actArrow.
2131 entity->z += (entity->new_z - entity->z) / 4;
2132 }
2133
2134 // move the bodyparts of these otherwise the limbs will get left behind in this adjustment.
2135 if ( entity->behavior == &actPlayer || entity->behavior == &actMonster )
2136 {
2137 for ( Entity *bodypart : entity->bodyparts )
2138 {
2139 bodypart->x += entity->x - ox;
2140 bodypart->y += entity->y - oy;
2141 bodypart->new_x += entity->new_x - onewx;
2142 bodypart->new_y += entity->new_y - onewy;
2143 }
2144 }
2145 }
2146 }
2147 // dead reckoning
2148 if ( fabs(entity->vel_x) > 0.0001 || fabs(entity->vel_y) > 0.0001 )
2149 {
2150 double ox = 0, oy = 0, onewx = 0, onewy = 0;
2151 if ( entity->behavior == &actPlayer || entity->behavior == &actMonster )
2152 {
2153 ox = entity->x;
2154 oy = entity->y;
2155 onewx = entity->new_x;
2156 onewy = entity->new_y;
2157 }
2158 real_t dist = clipMove(&entity->x, &entity->y, entity->vel_x, entity->vel_y, entity);
2159 real_t new_dist = clipMove(&entity->new_x, &entity->new_y, entity->vel_x, entity->vel_y, entity);
2160 if ( entity->behavior == &actPlayer || entity->behavior == &actMonster )
2161 {
2162 for (Entity *bodypart : entity->bodyparts)
2163 {
2164 bodypart->x += entity->x - ox;
2165 bodypart->y += entity->y - oy;
2166 bodypart->new_x += entity->new_x - onewx;
2167 bodypart->new_y += entity->new_y - onewy;
2168 }
2169 }
2170 }
2171 if ( entity->behavior != &actArrow )
2172 {
2173 // client handles z in actArrow.
2174 entity->z += entity->vel_z;
2175 entity->new_z += entity->vel_z;
2176 }
2177
2178 // rotate to new angles
2179 double dirYaw = entity->new_yaw - entity->yaw;
2180 while ( dirYaw >= PI )
2181 {
2182 dirYaw -= PI * 2;
2183 }
2184 while ( dirYaw < -PI )
2185 {
2186 dirYaw += PI * 2;
2187 }
2188 entity->yaw += dirYaw / 3;
2189 while ( entity->yaw < 0 )
2190 {
2191 entity->yaw += 2 * PI;
2192 }
2193 while ( entity->yaw >= 2 * PI )
2194 {
2195 entity->yaw -= 2 * PI;
2196 }
2197 double dirPitch = entity->new_pitch - entity->pitch;
2198 if ( entity->behavior != &actArrow )
2199 {
2200 // client handles pitch in actArrow.
2201 while ( dirPitch >= PI )
2202 {
2203 dirPitch -= PI * 2;
2204 }
2205 while ( dirPitch < -PI )
2206 {
2207 dirPitch += PI * 2;
2208 }
2209 entity->pitch += dirPitch / 3;
2210 while ( entity->pitch < 0 )
2211 {
2212 entity->pitch += 2 * PI;
2213 }
2214 while ( entity->pitch >= 2 * PI )
2215 {
2216 entity->pitch -= 2 * PI;
2217 }
2218 }
2219 double dirRoll = entity->new_roll - entity->roll;
2220 while ( dirRoll >= PI )
2221 {
2222 dirRoll -= PI * 2;
2223 }
2224 while ( dirRoll < -PI )
2225 {
2226 dirRoll += PI * 2;
2227 }
2228 entity->roll += dirRoll / 3;
2229 while ( entity->roll < 0 )
2230 {
2231 entity->roll += 2 * PI;
2232 }
2233 while ( entity->roll >= 2 * PI )
2234 {
2235 entity->roll -= 2 * PI;
2236 }
2237
2238 if ( entity->behavior == &actPlayer && entity->skill[2] != clientnum )
2239 {
2240 node_t* tmpNode = nullptr;
2241 int bodypartNum = 0;
2242 for ( bodypartNum = 0, tmpNode = entity->children.first; tmpNode; tmpNode = tmpNode->next, bodypartNum++ )
2243 {
2244 if ( bodypartNum < 9 )
2245 {
2246 continue;
2247 }
2248 if ( bodypartNum > 10 )
2249 {
2250 break;
2251 }
2252 // update the players' head and mask as these will otherwise wait until actPlayer to update their rotation. stops clipping.
2253 if ( bodypartNum == 9 || bodypartNum == 10 )
2254 {
2255 Entity* limb = (Entity*)tmpNode->element;
2256 if ( limb )
2257 {
2258 limb->pitch = entity->pitch;
2259 limb->yaw = entity->yaw;
2260 }
2261 }
2262 }
2263 }
2264 }
2265 }
2266 }
2267 }
2268 }
2269 }
2270 for ( node = map.entities->first; node != nullptr; node = node->next )
2271 {
2272 entity = (Entity*)node->element;
2273 entity->ranbehavior = false;
2274 }
2275 bool tooManySpells = (list_Size(&spellList) >= INVENTORY_SIZEX * 3);
2276 int backpack_sizey = 3;
2277 if ( stats[clientnum]->cloak && stats[clientnum]->cloak->type == CLOAK_BACKPACK
2278 && (shouldInvertEquipmentBeatitude(stats[clientnum]) ? abs(stats[clientnum]->cloak->beatitude) >= 0 : stats[clientnum]->cloak->beatitude >= 0) )
2279 {
2280 backpack_sizey = 4;
2281 }
2282
2283 if ( tooManySpells && gui_mode == GUI_MODE_INVENTORY && inventory_mode == INVENTORY_MODE_SPELL )
2284 {
2285 INVENTORY_SIZEY = 4 + ((list_Size(&spellList) - (INVENTORY_SIZEX * 3)) / INVENTORY_SIZEX);
2286 }
2287 else if ( backpack_sizey == 4 )
2288 {
2289 INVENTORY_SIZEY = 4;
2290 }
2291 else
2292 {
2293 if ( INVENTORY_SIZEY > 3 && !tooManySpells )
2294 {
2295 // we should rearrange our spells.
2296 for ( node_t* node = stats[clientnum]->inventory.first; node != NULL; node = node->next )
2297 {
2298 int scanx = 0;
2299 int scany = 0;
2300 bool notfree = false;
2301 bool foundaspot = false;
2302 Item* item = (Item*)node->element;
2303 if ( itemCategory(item) != SPELL_CAT )
2304 {
2305 continue;
2306 }
2307 if ( item->appearance >= 1000 )
2308 {
2309 continue; // shaman spells.
2310 }
2311 while ( 1 )
2312 {
2313 for ( scany = 0; scany < 3; scany++ )
2314 {
2315 node_t* node2;
2316 for ( node2 = stats[clientnum]->inventory.first; node2 != NULL; node2 = node2->next )
2317 {
2318 Item* tempItem = (Item*)node2->element;
2319 if ( tempItem == item )
2320 {
2321 continue;
2322 }
2323 if ( tempItem )
2324 {
2325 if ( tempItem->x == scanx && tempItem->y == scany )
2326 {
2327 if ( itemCategory(tempItem) == SPELL_CAT )
2328 {
2329 notfree = true; //Both spells. Can't fit in the same slot.
2330 }
2331 }
2332 }
2333 }
2334 if ( notfree )
2335 {
2336 notfree = false;
2337 continue;
2338 }
2339 item->x = scanx;
2340 item->y = scany;
2341 foundaspot = true;
2342 break;
2343 }
2344 if ( foundaspot )
2345 {
2346 break;
2347 }
2348 scanx++;
2349 }
2350 }
2351 }
2352 INVENTORY_SIZEY = 3;
2353 }
2354
2355 for ( node = stats[clientnum]->inventory.first; node != NULL; node = nextnode )
2356 {
2357 nextnode = node->next;
2358 Item* item = (Item*)node->element;
2359 if ( !item )
2360 {
2361 continue;
2362 }
2363 // unlock achievements for special collected items
2364 switch ( item->type )
2365 {
2366 case ARTIFACT_SWORD:
2367 steamAchievement("BARONY_ACH_KING_ARTHURS_BLADE");
2368 break;
2369 case ARTIFACT_MACE:
2370 steamAchievement("BARONY_ACH_SPUD_LORD");
2371 break;
2372 case ARTIFACT_AXE:
2373 steamAchievement("BARONY_ACH_THANKS_MR_SKELTAL");
2374 break;
2375 case ARTIFACT_SPEAR:
2376 steamAchievement("BARONY_ACH_SPEAR_OF_DESTINY");
2377 break;
2378 default:
2379 break;
2380 }
2381
2382 if ( itemCategory(item) == WEAPON )
2383 {
2384 if ( item->beatitude >= 10 )
2385 {
2386 steamAchievement("BARONY_ACH_BLESSED");
2387 }
2388 }
2389
2390 if ( item->type == FOOD_BLOOD && item->count >= 20 )
2391 {
2392 steamAchievement("BARONY_ACH_BLOOD_VESSELS");
2393 }
2394
2395 // drop any inventory items you don't have room for
2396 if ( itemCategory(item) != SPELL_CAT && (item->x >= INVENTORY_SIZEX || item->y >= backpack_sizey) )
2397 {
2398 messagePlayer(clientnum, language[727], item->getName());
2399 bool droppedAll = false;
2400 while ( item && item->count > 1 )
2401 {
2402 droppedAll = dropItem(item, clientnum);
2403 if ( droppedAll )
2404 {
2405 item = nullptr;
2406 }
2407 }
2408 if ( !droppedAll )
2409 {
2410 dropItem(item, clientnum);
2411 }
2412 }
2413 else
2414 {
2415 if ( auto_appraise_new_items && appraisal_timer == 0 && !(item->identified) )
2416 {
2417 int appraisal_time = getAppraisalTime(item);
2418 if (appraisal_time < auto_appraise_lowest_time)
2419 {
2420 auto_appraise_target = item;
2421 auto_appraise_lowest_time = appraisal_time;
2422 }
2423 }
2424 }
2425 }
2426
2427 if ( kills[SHOPKEEPER] >= 3 )
2428 {
2429 steamAchievement("BARONY_ACH_PROFESSIONAL_BURGLAR");
2430 }
2431 if ( kills[HUMAN] >= 10 )
2432 {
2433 steamAchievement("BARONY_ACH_HOMICIDAL_MANIAC");
2434 }
2435 }
2436
2437 // Automatically identify items, shortest time required first
2438 if ( auto_appraise_target != NULL )
2439 {
2440 //Cleanup identify GUI gamecontroller code here.
2441 selectedIdentifySlot = -1;
2442
2443 //identifygui_active = false;
2444 identifygui_appraising = true;
2445 identifyGUIIdentify(auto_appraise_target);
2446 }
2447 }
2448 }
2449
2450 /*-------------------------------------------------------------------------------
2451
2452 handleButtons
2453
2454 Draws buttons and processes clicks
2455
2456 -------------------------------------------------------------------------------*/
2457
2458 //int subx1, subx2, suby1, suby2;
2459 //char subtext[1024];
2460
handleButtons(void)2461 void handleButtons(void)
2462 {
2463 node_t* node;
2464 node_t* nextnode;
2465 button_t* button;
2466 int w = 0, h = 0;
2467
2468 // handle buttons
2469 for ( node = button_l.first; node != NULL; node = nextnode )
2470 {
2471 nextnode = node->next;
2472 if ( node->element == NULL )
2473 {
2474 continue;
2475 }
2476 button = (button_t*)node->element;
2477 if ( button == NULL )
2478 {
2479 continue;
2480 }
2481 if ( !subwindow && button->focused )
2482 {
2483 list_RemoveNode(button->node);
2484 continue;
2485 }
2486 //Hide "Random Character" button if not on first character creation step.
2487 if (!strcmp(button->label, language[733]))
2488 {
2489 if (charcreation_step > 1)
2490 {
2491 button->visible = 0;
2492 }
2493 else
2494 {
2495 button->visible = 1;
2496 }
2497 }
2498 //Hide "Random Name" button if not on character naming screen.
2499 if ( !strcmp(button->label, language[2498]) )
2500 {
2501 if ( charcreation_step != 4 )
2502 {
2503 button->visible = 0;
2504 }
2505 else
2506 {
2507 button->visible = 1;
2508 }
2509 }
2510 if ( button->visible == 0 )
2511 {
2512 continue; // invisible buttons are not processed
2513 }
2514 TTF_SizeUTF8(ttf12, button->label, &w, &h);
2515 if ( subwindow && !button->focused )
2516 {
2517 // unfocused buttons do not work when a subwindow is active
2518 drawWindow(button->x, button->y, button->x + button->sizex, button->y + button->sizey);
2519 ttfPrintText(ttf12, button->x + (button->sizex - w) / 2 - 2, button->y + (button->sizey - h) / 2 + 3, button->label);
2520 }
2521 else
2522 {
2523 if ( keystatus[button->key] && button->key )
2524 {
2525 button->pressed = true;
2526 button->needclick = false;
2527 }
2528 if (button->joykey != -1 && *inputPressed(button->joykey) && rebindaction == -1 )
2529 {
2530 button->pressed = true;
2531 button->needclick = false;
2532 }
2533 if ( mousestatus[SDL_BUTTON_LEFT] )
2534 {
2535 if ( mousex >= button->x && mousex < button->x + button->sizex && omousex >= button->x && omousex < button->x + button->sizex )
2536 {
2537 if ( mousey >= button->y && mousey < button->y + button->sizey && omousey >= button->y && omousey < button->y + button->sizey )
2538 {
2539 node_t* node;
2540 for ( node = button_l.first; node != NULL; node = node->next )
2541 {
2542 if ( node->element == NULL )
2543 {
2544 continue;
2545 }
2546 button_t* button = (button_t*)node->element;
2547 button->pressed = false;
2548 }
2549 button->pressed = true;
2550 }
2551 else if ( !keystatus[button->key] )
2552 {
2553 button->pressed = false;
2554 }
2555 }
2556 else if ( !keystatus[button->key] )
2557 {
2558 button->pressed = false;
2559 }
2560 button->needclick = true;
2561 }
2562 if ( button->pressed )
2563 {
2564 drawDepressed(button->x, button->y, button->x + button->sizex, button->y + button->sizey);
2565 ttfPrintText(ttf12, button->x + (button->sizex - w) / 2 - 2, button->y + (button->sizey - h) / 2 + 3, button->label);
2566 if ( !mousestatus[SDL_BUTTON_LEFT] && !keystatus[button->key] )
2567 {
2568 if ( ( omousex >= button->x && omousex < button->x + button->sizex ) || !button->needclick )
2569 {
2570 if ( ( omousey >= button->y && omousey < button->y + button->sizey ) || !button->needclick )
2571 {
2572 keystatus[button->key] = false;
2573 *inputPressed(button->joykey) = 0;
2574 playSound(139, 64);
2575 if ( button->action != NULL )
2576 {
2577 (*button->action)(button); // run the button's assigned action
2578 if ( deleteallbuttons )
2579 {
2580 deleteallbuttons = false;
2581 button->pressed = false;
2582 break;
2583 }
2584 }
2585 }
2586 }
2587 button->pressed = false;
2588 }
2589 }
2590 else
2591 {
2592 drawWindow(button->x, button->y, button->x + button->sizex, button->y + button->sizey);
2593 ttfPrintText(ttf12, button->x + (button->sizex - w) / 2 - 2, button->y + (button->sizey - h) / 2 + 3, button->label);
2594 }
2595 }
2596
2597 if ( button->outline )
2598 {
2599 //Draw golden border.
2600 //For such things as which settings tab the controller has presently selected.
2601 Uint32 color = SDL_MapRGBA(mainsurface->format, 255, 255, 0, 127);
2602 SDL_Rect pos;
2603 pos.x = button->x;
2604 pos.w = button->sizex;
2605 pos.y = button->y;
2606 pos.h = button->sizey;
2607 drawBox(&pos, color, 127);
2608 //Draw a 2 pixel thick box.
2609 pos.x = button->x + 1;
2610 pos.w = button->sizex - 2;
2611 pos.y = button->y + 1;
2612 pos.h = button->sizey - 2;
2613 drawBox(&pos, color, 127);
2614 }
2615 }
2616 }
2617
2618 /*-------------------------------------------------------------------------------
2619
2620 handleEvents
2621
2622 Handles all SDL events; receives input, updates gamestate, etc.
2623
2624 -------------------------------------------------------------------------------*/
2625
handleEvents(void)2626 void handleEvents(void)
2627 {
2628 double d;
2629 int j;
2630 int runtimes = 0;
2631
2632 // calculate app rate
2633 t = SDL_GetTicks();
2634 timesync = t - ot;
2635 ot = t;
2636
2637 // calculate fps
2638 if ( timesync != 0 )
2639 {
2640 frameval[cycles % AVERAGEFRAMES] = 1.0 / timesync;
2641 }
2642 else
2643 {
2644 frameval[cycles % AVERAGEFRAMES] = 1.0;
2645 }
2646 d = frameval[0];
2647 for (j = 1; j < AVERAGEFRAMES; j++)
2648 {
2649 d += frameval[j];
2650 }
2651 fps = (d / AVERAGEFRAMES) * 1000;
2652
2653 if (game_controller && game_controller->isActive())
2654 {
2655 game_controller->handleAnalog();
2656 }
2657
2658 while ( SDL_PollEvent(&event) ) // poll SDL events
2659 {
2660 // Global events
2661 switch ( event.type )
2662 {
2663 case SDL_QUIT: // if SDL receives the shutdown signal
2664 mainloop = 0;
2665 break;
2666 case SDL_KEYDOWN: // if a key is pressed...
2667 if ( command )
2668 {
2669 if ( event.key.keysym.sym == SDLK_UP )
2670 {
2671 if ( !chosen_command && command_history.last ) //If no command is chosen (user has not tried to go up through the commands yet...
2672 {
2673 //Assign the chosen command as the last thing the user typed.
2674 chosen_command = command_history.last;
2675 strcpy(command_str, ((string_t*)chosen_command->element)->data);
2676 }
2677 else if ( chosen_command )
2678 {
2679 //Scroll up through the list. Do nothing if already at the top.
2680 if ( chosen_command->prev )
2681 {
2682 chosen_command = chosen_command->prev;
2683 strcpy(command_str, ((string_t*)chosen_command->element)->data);
2684 }
2685 }
2686 }
2687 else if ( event.key.keysym.sym == SDLK_DOWN )
2688 {
2689 if ( chosen_command ) //If a command is chosen...
2690 {
2691 //Scroll down through the history, back to the latest command.
2692 if ( chosen_command->next )
2693 {
2694 //Move on to the newer command.
2695 chosen_command = chosen_command->next;
2696 strcpy(command_str, ((string_t*)chosen_command->element)->data);
2697 }
2698 else
2699 {
2700 //Already latest command. Clear the chosen command.
2701 chosen_command = NULL;
2702 strcpy(command_str, "");
2703 }
2704 }
2705 }
2706 }
2707 if ( SDL_IsTextInputActive() )
2708 {
2709 const size_t length = strlen(inputstr);
2710
2711 #ifdef APPLE
2712 if ( (event.key.keysym.sym == SDLK_DELETE || event.key.keysym.sym == SDLK_BACKSPACE) && length > 0 )
2713 {
2714 size_t utfOffset = 1;
2715
2716 if ( length > 1 )
2717 {
2718 const uint32_t result = UTFD::ValidateUTF8Character(inputstr[length - 1]);
2719
2720 if ( result > UTFD::UTF8_REJECT )
2721 {
2722 ++utfOffset;
2723 }
2724 }
2725
2726 inputstr[length - utfOffset] = '\0';
2727 cursorflash = ticks;
2728 }
2729 #else
2730
2731 if ( event.key.keysym.sym == SDLK_BACKSPACE && length > 0 )
2732 {
2733 size_t utfOffset = 1;
2734
2735 if ( length > 1 )
2736 {
2737 const uint32_t result = UTFD::ValidateUTF8Character(inputstr[length - 1]);
2738
2739 if ( result > UTFD::UTF8_REJECT )
2740 {
2741 ++utfOffset;
2742 }
2743 }
2744
2745 inputstr[length - utfOffset] = '\0';
2746 cursorflash = ticks;
2747 }
2748 #endif
2749 else if ( event.key.keysym.sym == SDLK_c && SDL_GetModState()&KMOD_CTRL )
2750 {
2751 SDL_SetClipboardText(inputstr);
2752 cursorflash = ticks;
2753 }
2754 else if ( event.key.keysym.sym == SDLK_v && SDL_GetModState()&KMOD_CTRL )
2755 {
2756 strncpy(inputstr, SDL_GetClipboardText(), inputlen);
2757 cursorflash = ticks;
2758 }
2759 }
2760 #ifdef PANDORA
2761 // Pandora Shoulder as Mouse Button handling
2762 if ( event.key.keysym.sym == SDLK_RCTRL ) { // L
2763 mousestatus[SDL_BUTTON_LEFT] = 1; // set this mouse button to 1
2764 lastkeypressed = 282 + SDL_BUTTON_LEFT;
2765 }
2766 else if ( event.key.keysym.sym == SDLK_RSHIFT ) { // R
2767 mousestatus[SDL_BUTTON_RIGHT] = 1; // set this mouse button to 1
2768 lastkeypressed = 282 + SDL_BUTTON_RIGHT;
2769 }
2770 else
2771 #endif
2772 {
2773 lastkeypressed = event.key.keysym.scancode;
2774 keystatus[event.key.keysym.scancode] = 1; // set this key's index to 1
2775 }
2776 break;
2777 case SDL_KEYUP: // if a key is unpressed...
2778 #ifdef PANDORA
2779 if ( event.key.keysym.sym == SDLK_RCTRL ) { // L
2780 mousestatus[SDL_BUTTON_LEFT] = 0; // set this mouse button to 0
2781 lastkeypressed = 282 + SDL_BUTTON_LEFT;
2782 }
2783 else if ( event.key.keysym.sym == SDLK_RSHIFT ) { // R
2784 mousestatus[SDL_BUTTON_RIGHT] = 0; // set this mouse button to 0
2785 lastkeypressed = 282 + SDL_BUTTON_RIGHT;
2786 }
2787 else
2788 #endif
2789 {
2790 keystatus[event.key.keysym.scancode] = 0; // set this key's index to 0
2791 }
2792 break;
2793 case SDL_TEXTINPUT:
2794 if ( (event.text.text[0] != 'c' && event.text.text[0] != 'C') || !(SDL_GetModState()&KMOD_CTRL) )
2795 {
2796 if ( (event.text.text[0] != 'v' && event.text.text[0] != 'V') || !(SDL_GetModState()&KMOD_CTRL) )
2797 {
2798 strncat(inputstr, event.text.text, std::max<size_t>(0, inputlen - strlen(inputstr)));
2799 cursorflash = ticks;
2800 }
2801 }
2802 break;
2803 case SDL_MOUSEBUTTONDOWN: // if a mouse button is pressed...
2804 mousestatus[event.button.button] = 1; // set this mouse button to 1
2805 lastkeypressed = 282 + event.button.button;
2806 break;
2807 case SDL_MOUSEBUTTONUP: // if a mouse button is released...
2808 mousestatus[event.button.button] = 0; // set this mouse button to 0
2809 buttonclick = 0; // release any buttons that were being held down
2810 gui_clickdrag = false;
2811 break;
2812 case SDL_MOUSEWHEEL:
2813 if ( event.wheel.y > 0 )
2814 {
2815 mousestatus[SDL_BUTTON_WHEELUP] = 1;
2816 lastkeypressed = 286;
2817 }
2818 else if ( event.wheel.y < 0 )
2819 {
2820 mousestatus[SDL_BUTTON_WHEELDOWN] = 1;
2821 lastkeypressed = 287;
2822 }
2823 break;
2824 case SDL_MOUSEMOTION: // if the mouse is moved...
2825 if ( firstmouseevent == true )
2826 {
2827 firstmouseevent = false;
2828 break;
2829 }
2830 menuselect = 0;
2831 mousex = event.motion.x;
2832 mousey = event.motion.y;
2833 #ifdef PANDORA
2834 if ( xres != 800 || yres != 480 ) { // SEB Pandora
2835 mousex = (mousex*xres) / 800;
2836 mousey = (mousey*yres) / 480;
2837 }
2838 #endif
2839 mousexrel += event.motion.xrel;
2840 mouseyrel += event.motion.yrel;
2841
2842 if ( !draw_cursor )
2843 {
2844 draw_cursor = true;
2845 }
2846 break;
2847 case SDL_CONTROLLERBUTTONDOWN: // if joystick button is pressed
2848 joystatus[event.cbutton.button] = 1; // set this button's index to 1
2849 lastkeypressed = 301 + event.cbutton.button;
2850 if ( event.cbutton.button + 301 == joyimpulses[INJOY_MENU_LEFT_CLICK] && ((!shootmode && gui_mode == GUI_MODE_NONE) || gamePaused) && rebindaction == -1 )
2851 {
2852 //Generate a mouse click.
2853 SDL_Event e;
2854
2855 e.type = SDL_MOUSEBUTTONDOWN;
2856 e.button.button = SDL_BUTTON_LEFT;
2857 e.button.clicks = 1; //Single click.
2858 SDL_PushEvent(&e);
2859 }
2860 break;
2861 case SDL_CONTROLLERBUTTONUP: // if joystick button is released
2862 joystatus[event.cbutton.button] = 0; // set this button's index to 0
2863 if ( event.cbutton.button + 301 == joyimpulses[INJOY_MENU_LEFT_CLICK] )
2864 {
2865 //Generate a mouse lift.
2866 SDL_Event e;
2867
2868 e.type = SDL_MOUSEBUTTONUP;
2869 e.button.button = SDL_BUTTON_LEFT;
2870 SDL_PushEvent(&e);
2871 }
2872 break;
2873 case SDL_JOYHATMOTION:
2874 break;
2875 case SDL_USEREVENT: // if the game timer has elapsed
2876 if ( runtimes < 5 )
2877 {
2878 if ( runtimes == 0 )
2879 {
2880 #ifdef SOUND
2881 sound_update(); //Update FMOD and whatnot.
2882 #endif
2883 }
2884 runtimes++;
2885 gameLogic();
2886 mousexrel = 0;
2887 mouseyrel = 0;
2888 }
2889 break;
2890 case SDL_WINDOWEVENT:
2891 if ( event.window.event == SDL_WINDOWEVENT_FOCUS_LOST && mute_audio_on_focus_lost )
2892 {
2893 #ifdef USE_FMOD
2894 if ( music_group )
2895 {
2896 FMOD_ChannelGroup_SetVolume(music_group, 0.f);
2897 }
2898 if ( sound_group )
2899 {
2900 FMOD_ChannelGroup_SetVolume(sound_group, 0.f);
2901 }
2902 if ( soundAmbient_group )
2903 {
2904 FMOD_ChannelGroup_SetVolume(soundAmbient_group, 0.f);
2905 }
2906 if ( soundEnvironment_group )
2907 {
2908 FMOD_ChannelGroup_SetVolume(soundEnvironment_group, 0.f);
2909 }
2910 #endif // USE_FMOD
2911 #ifdef USE_OPENAL
2912 if ( music_group )
2913 {
2914 OPENAL_ChannelGroup_SetVolume(music_group, 0.f);
2915 }
2916 if ( sound_group )
2917 {
2918 OPENAL_ChannelGroup_SetVolume(sound_group, 0.f);
2919 }
2920 if ( soundAmbient_group )
2921 {
2922 OPENAL_ChannelGroup_SetVolume(soundAmbient_group, 0.f);
2923 }
2924 if ( soundEnvironment_group )
2925 {
2926 OPENAL_ChannelGroup_SetVolume(soundEnvironment_group, 0.f);
2927 }
2928 #endif
2929 }
2930 else if ( event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED )
2931 {
2932 #ifdef USE_FMOD
2933 if ( music_group )
2934 {
2935 FMOD_ChannelGroup_SetVolume(music_group, musvolume / 128.f);
2936 }
2937 if ( sound_group )
2938 {
2939 FMOD_ChannelGroup_SetVolume(sound_group, sfxvolume / 128.f);
2940 }
2941 if ( soundAmbient_group )
2942 {
2943 FMOD_ChannelGroup_SetVolume(soundAmbient_group, sfxAmbientVolume / 128.f);
2944 }
2945 if ( soundEnvironment_group )
2946 {
2947 FMOD_ChannelGroup_SetVolume(soundEnvironment_group, sfxEnvironmentVolume / 128.f);
2948 }
2949 #endif // USE_FMOD
2950 #ifdef USE_OPENAL
2951 if ( music_group )
2952 {
2953 OPENAL_ChannelGroup_SetVolume(music_group, musvolume / 128.f);
2954 }
2955 if ( sound_group )
2956 {
2957 OPENAL_ChannelGroup_SetVolume(sound_group, sfxvolume / 128.f);
2958 }
2959 if ( soundAmbient_group )
2960 {
2961 OPENAL_ChannelGroup_SetVolume(soundAmbient_group, sfxAmbientVolume / 128.f);
2962 }
2963 if ( soundEnvironment_group )
2964 {
2965 OPENAL_ChannelGroup_SetVolume(soundEnvironment_group, sfxEnvironmentVolume / 128.f);
2966 }
2967 #endif
2968 }
2969 break;
2970 /*case SDL_CONTROLLERAXISMOTION:
2971 printlog("Controller axis motion detected.\n");
2972 //if (event.caxis.which == 0) //TODO: Multi-controller support.
2973 //{
2974 printlog("Controller 0!\n");
2975 int x = 0, y = 0;
2976 if (event.caxis.axis == 0) //0 = x axis.
2977 {
2978 printlog("X-axis! Value: %d\n", event.caxis.value);
2979 if (event.caxis.value < -gamepad_deadzone)
2980 {
2981 printlog("Left!\n");
2982 x = -1; //Gamepad moved left.
2983 }
2984 else if (event.caxis.value > gamepad_deadzone)
2985 {
2986 printlog("Right!\n");
2987 x = 1; //Gamepad moved right.
2988 }
2989 }
2990 else if (event.caxis.axis == 1)
2991 {
2992 if (event.caxis.value < -gamepad_deadzone)
2993 {
2994 printlog("Up!\n");
2995 y = -1; //Gamepad moved up.
2996 }
2997 else if (event.caxis.value > gamepad_deadzone)
2998 {
2999 printlog("Down!\n");
3000 y = 1; //Gamepad moved down.
3001 }
3002 }
3003
3004 if (x || y)
3005 {
3006 printlog("Generating mouse motion!\n");
3007 SDL_Event e;
3008
3009 e.type = SDL_MOUSEMOTION;
3010 e.motion.x += x;
3011 e.motion.y += y;
3012 e.motion.xrel = x;
3013 e.motion.yrel = y;
3014 SDL_PushEvent(&e);
3015 }
3016 //}
3017 break;*/
3018 }
3019 }
3020 if ( !mousestatus[SDL_BUTTON_LEFT] )
3021 {
3022 omousex = mousex;
3023 omousey = mousey;
3024 }
3025 }
3026
3027 /*-------------------------------------------------------------------------------
3028
3029 timerCallback
3030
3031 A callback function for the game timer which pushes an SDL event
3032
3033 -------------------------------------------------------------------------------*/
3034
timerCallback(Uint32 interval,void * param)3035 Uint32 timerCallback(Uint32 interval, void* param)
3036 {
3037 SDL_Event event;
3038 SDL_UserEvent userevent;
3039
3040 userevent.type = SDL_USEREVENT;
3041 userevent.code = 0;
3042 userevent.data1 = NULL;
3043 userevent.data2 = NULL;
3044
3045 event.type = SDL_USEREVENT;
3046 event.user = userevent;
3047
3048 int c;
3049 bool playeralive = false;
3050 for (c = 0; c < MAXPLAYERS; c++)
3051 if (players[c] && players[c]->entity && !client_disconnected[c])
3052 {
3053 playeralive = true;
3054 }
3055
3056 if ((!gamePaused || multiplayer) && !loading && !intro && playeralive)
3057 {
3058 completionTime++;
3059 }
3060 ticks++;
3061 if (!loading)
3062 {
3063 SDL_PushEvent(&event); // so the game doesn't overload itself while loading
3064 }
3065 return (interval);
3066 }
3067
3068 /*-------------------------------------------------------------------------------
3069
3070 startMessages
3071
3072 prints several messages to the console for game start.
3073
3074 -------------------------------------------------------------------------------*/
3075
startMessages()3076 void startMessages()
3077 {
3078 newString(&messages, 0xFFFFFFFF, language[734], stats[clientnum]->name);
3079 newString(&messages, 0xFFFFFFFF, language[735], getInputName(impulses[IN_STATUS]));
3080 newString(&messages, 0xFFFFFFFF, language[736], getInputName(impulses[IN_USE]));
3081 newString(&messages, 0xFFFFFFFF, language[737]);
3082 }
3083
3084 /*-------------------------------------------------------------------------------
3085
3086 pauseGame
3087
3088 pauses or unpauses the game, depending on its current state
3089
3090 -------------------------------------------------------------------------------*/
3091
pauseGame(int mode,int ignoreplayer)3092 void pauseGame(int mode, int ignoreplayer)
3093 {
3094 int c;
3095
3096 if ( intro )
3097 {
3098 return;
3099 }
3100 if ( mode == 1 && !gamePaused )
3101 {
3102 return;
3103 }
3104 if ( mode == 2 && gamePaused )
3105 {
3106 return;
3107 }
3108 if ( introstage == 9
3109 || introstage == 11 + MOVIE_MIDGAME_BAPHOMET_HUMAN_AUTOMATON
3110 || introstage == 11 + MOVIE_MIDGAME_BAPHOMET_MONSTERS
3111 || introstage == 11 + MOVIE_MIDGAME_HERX_MONSTERS )
3112 {
3113 return;
3114 }
3115
3116 if ( (!gamePaused && mode != 1) || mode == 2 )
3117 {
3118 gamePaused = true;
3119 if ( SDL_GetRelativeMouseMode() )
3120 {
3121 SDL_SetRelativeMouseMode(SDL_FALSE);
3122 }
3123 return; // doesn't disable the game in multiplayer anymore
3124 if ( multiplayer == SERVER )
3125 {
3126 for ( c = 1; c < MAXPLAYERS; c++ )
3127 {
3128 if ( client_disconnected[c] || ignoreplayer == c )
3129 {
3130 continue;
3131 }
3132 strcpy((char*)net_packet->data, "PAUS");
3133 net_packet->data[4] = clientnum;
3134 net_packet->address.host = net_clients[c - 1].host;
3135 net_packet->address.port = net_clients[c - 1].port;
3136 net_packet->len = 5;
3137 sendPacketSafe(net_sock, -1, net_packet, c - 1);
3138 }
3139 }
3140 else if ( multiplayer == CLIENT && ignoreplayer )
3141 {
3142 strcpy((char*)net_packet->data, "PAUS");
3143 net_packet->data[4] = clientnum;
3144 net_packet->address.host = net_server.host;
3145 net_packet->address.port = net_server.port;
3146 net_packet->len = 5;
3147 sendPacketSafe(net_sock, -1, net_packet, 0);
3148 }
3149 }
3150 else if ( (gamePaused && mode != 2) || mode == 1 )
3151 {
3152 buttonCloseSubwindow(NULL);
3153 gamePaused = false;
3154 if ( !SDL_GetRelativeMouseMode() && capture_mouse )
3155 {
3156 SDL_SetRelativeMouseMode(SDL_TRUE);
3157 }
3158 return; // doesn't disable the game in multiplayer anymore
3159 if ( multiplayer == SERVER )
3160 {
3161 for ( c = 1; c < MAXPLAYERS; c++ )
3162 {
3163 if ( client_disconnected[c] || ignoreplayer == c )
3164 {
3165 continue;
3166 }
3167 strcpy((char*)net_packet->data, "UNPS");
3168 net_packet->data[4] = clientnum;
3169 net_packet->address.host = net_clients[c - 1].host;
3170 net_packet->address.port = net_clients[c - 1].port;
3171 net_packet->len = 5;
3172 sendPacketSafe(net_sock, -1, net_packet, c - 1);
3173 }
3174 }
3175 else if ( multiplayer == CLIENT && ignoreplayer )
3176 {
3177 strcpy((char*)net_packet->data, "UNPS");
3178 net_packet->data[4] = clientnum;
3179 net_packet->address.host = net_server.host;
3180 net_packet->address.port = net_server.port;
3181 net_packet->len = 5;
3182 sendPacketSafe(net_sock, -1, net_packet, 0);
3183 }
3184 }
3185 }
3186
3187 /*-------------------------------------------------------------------------------
3188
3189 frameRateLimit
3190
3191 Returns true until the correct number of frames has passed from the
3192 beginning of the last cycle in the main loop.
3193
3194 -------------------------------------------------------------------------------*/
3195
3196 // records the SDL_GetTicks() value at the moment the mainloop restarted
3197 Uint64 lastGameTickCount = 0;
3198 float framerateAccumulatedTime = 0.f;
frameRateLimit(Uint32 maxFrameRate,bool resetAccumulator)3199 bool frameRateLimit( Uint32 maxFrameRate, bool resetAccumulator)
3200 {
3201 float desiredFrameMilliseconds = 1.0f / maxFrameRate;
3202 Uint64 gameTickCount = SDL_GetPerformanceCounter();
3203 Uint64 ticksPerSecond = SDL_GetPerformanceFrequency();
3204 float millisecondsElapsed = (gameTickCount - lastGameTickCount) / static_cast<float>(ticksPerSecond);
3205 lastGameTickCount = gameTickCount;
3206 framerateAccumulatedTime += millisecondsElapsed;
3207
3208 if ( framerateAccumulatedTime < desiredFrameMilliseconds )
3209 {
3210 // if enough time is left wait, otherwise just keep spinning so we don't go over the limit...
3211 return true;
3212 }
3213 else
3214 {
3215 if ( resetAccumulator )
3216 {
3217 framerateAccumulatedTime = 0.f;
3218 }
3219 return false;
3220 }
3221 }
3222
3223 /*-------------------------------------------------------------------------------
3224
3225 main
3226
3227 Initializes game resources, harbors main game loop, and cleans up
3228 afterwards
3229
3230 -------------------------------------------------------------------------------*/
3231
3232 #include <stdio.h>
3233 //#include <unistd.h>
3234 #include <stdlib.h>
3235 #ifdef APPLE
3236 #include <mach-o/dyld.h>
3237 #endif
3238
main(int argc,char ** argv)3239 int main(int argc, char** argv)
3240 {
3241 #ifdef WINDOWS
3242 SetUnhandledExceptionFilter(unhandled_handler);
3243 #endif // WINDOWS
3244
3245 #ifdef LINUX
3246 //Catch segfault stuff.
3247 struct sigaction sa;
3248
3249 memset(&sa, 0, sizeof(struct sigaction));
3250 sigemptyset(&sa.sa_mask);
3251 sa.sa_sigaction = segfault_sigaction;
3252 sa.sa_flags = SA_SIGINFO;
3253
3254 sigaction(SIGSEGV, &sa, NULL);
3255 #endif
3256
3257 try
3258 {
3259 #if defined(APPLE) || defined(BSD)
3260 #ifdef APPLE
3261 uint32_t buffsize = 4096;
3262 char binarypath[buffsize];
3263 int result = _NSGetExecutablePath(binarypath, &buffsize);
3264 #elif defined(BSD)
3265 int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
3266 size_t buffsize = PATH_MAX;
3267 char binarypath[buffsize];
3268 int result = sysctl(mib, 4, binarypath, &buffsize, NULL, 0);
3269 #elif defined(HAIKU)
3270 size_t buffsize = PATH_MAX;
3271 char binarypath[buffsize];
3272 int result = find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, NULL, binarypath, sizeof(binarypath)); // B_OK is 0
3273 #endif
3274 if (result == 0) //It worked.
3275 {
3276 printlog("Binary path: %s\n", binarypath);
3277 char* last = strrchr(binarypath, '/');
3278 *last = '\0';
3279 char execpath[buffsize];
3280 strcpy(execpath, binarypath);
3281 //char* last = strrchr(execpath, '/');
3282 //strcat(execpath, '/');
3283 //strcat(execpath, "/../../../");
3284 printlog("Chrooting to directory: %s\n", execpath);
3285 chdir(execpath);
3286 ///Users/ciprian/barony/barony-sdl2-take2/barony.app/Contents/MacOS/barony
3287 chdir("..");
3288 //chdir("..");
3289 chdir("Resources");
3290 //chdir("..");
3291 //chdir("..");
3292 }
3293 else
3294 {
3295 printlog("Failed to get binary path. Program may not work corectly!\n");
3296 }
3297 #endif
3298 SDL_Rect pos, src;
3299 int c;
3300 //int tilesreceived=0;
3301 //Mix_Music **music, *intromusic, *splashmusic, *creditsmusic;
3302 node_t* node;
3303 Entity* entity;
3304 FILE* fp;
3305 //SDL_Surface *sky_bmp;
3306 light_t* light;
3307
3308 size_t datadirsz = std::min(sizeof(datadir) - 1, strlen(BASE_DATA_DIR));
3309 strncpy(datadir, BASE_DATA_DIR, datadirsz);
3310 datadir[datadirsz] = '\0';
3311 #ifdef WINDOWS
3312 strcpy(outputdir, "./");
3313 #else
3314 char *basepath = getenv("HOME");
3315 #ifdef USE_EOS
3316 #ifdef STEAMWORKS
3317 //Steam + EOS
3318 snprintf(outputdir, sizeof(outputdir), "%s/.barony", basepath);
3319 #else
3320 //Just EOS.
3321 std::string firstDotdir(basepath);
3322 firstDotdir += "/.barony/";
3323 if (access(firstDotdir.c_str(), F_OK) == -1)
3324 {
3325 mkdir(firstDotdir.c_str(), 0777); //Since this mkdir is not equivalent to mkdir -p, have to create each part of the path manually.
3326 }
3327 snprintf(outputdir, sizeof(outputdir), "%s/epicgames", firstDotdir.c_str());
3328 #endif
3329 #else //USE_EOS
3330 //No EOS. Could be Steam though. Or could also not.
3331 snprintf(outputdir, sizeof(outputdir), "%s/.barony", basepath);
3332 #endif
3333 if (access(outputdir, F_OK) == -1)
3334 {
3335 mkdir(outputdir, 0777);
3336 }
3337 #endif
3338 // read command line arguments
3339 if ( argc > 1 )
3340 {
3341 for (c = 1; c < argc; c++)
3342 {
3343 if ( argv[c] != NULL )
3344 {
3345 if ( !strcmp(argv[c], "-windowed") )
3346 {
3347 fullscreen = 0;
3348 }
3349 else if ( !strncmp(argv[c], "-size=", 6) )
3350 {
3351 strncpy(tempstr, argv[c] + 6, strcspn(argv[c] + 6, "x"));
3352 xres = std::max(320, atoi(tempstr));
3353 yres = std::max(200, atoi(argv[c] + 6 + strcspn(argv[c] + 6, "x") + 1));
3354 }
3355 else if ( !strncmp(argv[c], "-map=", 5) )
3356 {
3357 strcpy(maptoload, argv[c] + 5);
3358 loadingmap = true;
3359 }
3360 else if ( !strncmp(argv[c], "-gen=", 5) )
3361 {
3362 strcpy(maptoload, argv[c] + 5);
3363 loadingmap = true;
3364 genmap = true;
3365 }
3366 else if ( !strncmp(argv[c], "-config=", 8) )
3367 {
3368 strcpy(configtoload, argv[c] + 5);
3369 loadingconfig = true;
3370 }
3371 else if (!strncmp(argv[c], "-quickstart=", 12))
3372 {
3373 strcpy(classtoquickstart, argv[c] + 12);
3374 }
3375 else if (!strncmp(argv[c], "-datadir=", 9))
3376 {
3377 datadirsz = std::min(sizeof(datadir) - 1, strlen(argv[c] + 9));
3378 strncpy(datadir, argv[c] + 9, datadirsz);
3379 datadir[datadirsz] = '\0';
3380 }
3381 else if ( !strcmp(argv[c], "-nosound") )
3382 {
3383 no_sound = true;
3384 }
3385 else
3386 {
3387 #ifdef USE_EOS
3388 EOS.CommandLineArgs.push_back(argv[c]);
3389 #endif // USE_EOS
3390 }
3391 }
3392 }
3393 }
3394 printlog("Data path is %s", datadir);
3395 printlog("Output path is %s", outputdir);
3396
3397
3398 // load default language file (english)
3399 if ( loadLanguage("en") )
3400 {
3401 printlog("Fatal error: failed to load default language file!\n");
3402 fclose(logfile);
3403 exit(1);
3404 }
3405
3406 // load config file
3407 if ( loadingconfig )
3408 {
3409 loadConfig(configtoload);
3410 }
3411 else
3412 {
3413 loadDefaultConfig();
3414 }
3415
3416 // initialize engine
3417 if ( (c = initApp("Barony", fullscreen)) )
3418 {
3419 printlog("Critical error: %d\n", c);
3420 #ifdef STEAMWORKS
3421 SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Uh oh",
3422 "Barony has encountered a critical error and cannot start.\n\n"
3423 "Please check the log.txt file in the game directory for additional info\n"
3424 "and verify Steam is running. Alternatively, contact us through our website\n"
3425 "at http://www.baronygame.com/ for support.",
3426 screen);
3427 #elif defined USE_EOS
3428 if ( EOS.appRequiresRestart == EOS_EResult::EOS_Success )
3429 {
3430 // restarting app from launcher.
3431 }
3432 else
3433 {
3434 SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Uh oh",
3435 "Barony has encountered a critical error and cannot start.\n\n"
3436 "Please check the log.txt file in the game directory for additional info,\n"
3437 "and verify the game is launched through the Epic Games Store. \n"
3438 "Alternatively, contact us through our website at http://www.baronygame.com/ for support.",
3439 screen);
3440 }
3441 #else
3442 SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Uh oh",
3443 "Barony has encountered a critical error and cannot start.\n\n"
3444 "Please check the log.txt file in the game directory for additional info,\n"
3445 "or contact us through our website at http://www.baronygame.com/ for support.",
3446 screen);
3447 #endif
3448 deinitApp();
3449 exit(c);
3450 }
3451
3452 // init message
3453 printlog("Barony version: %s\n", VERSION);
3454 time_t timething;
3455 char buffer[32];
3456 struct tm* tm_info;
3457 time(&timething);
3458 tm_info = localtime(&timething);
3459 strftime( buffer, 32, "%Y-%m-%d %H-%M-%S", tm_info );
3460 printlog("Launch time: %s\n", buffer);
3461
3462 if ( (c = initGame()) )
3463 {
3464 printlog("Critical error in initGame: %d\n", c);
3465 SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Uh oh",
3466 "Barony has encountered a critical error and cannot start.\n\n"
3467 "Please check the log.txt file in the game directory for additional info,\n"
3468 "or contact us through our website at http://www.baronygame.com/ for support.",
3469 screen);
3470 deinitGame();
3471 deinitApp();
3472 exit(c);
3473 }
3474 initialized = true;
3475
3476 // initialize map
3477 map.tiles = nullptr;
3478 map.entities = (list_t*) malloc(sizeof(list_t));
3479 map.entities->first = nullptr;
3480 map.entities->last = nullptr;
3481 map.creatures = new list_t;
3482 map.creatures->first = nullptr;
3483 map.creatures->last = nullptr;
3484
3485 // initialize player conducts
3486 setDefaultPlayerConducts();
3487
3488 // instantiate a timer
3489 timer = SDL_AddTimer(1000 / TICKS_PER_SECOND, timerCallback, NULL);
3490 srand(time(NULL));
3491 fountainSeed.seed(rand());
3492
3493 // play splash sound
3494 #ifdef MUSIC
3495 playmusic(splashmusic, false, false, false);
3496 #endif
3497
3498 int old_sdl_ticks = 0;
3499 int indev_timer = 0;
3500
3501 // main loop
3502
3503 printlog("running main loop.\n");
3504 while (mainloop)
3505 {
3506 // record the time at the start of this cycle
3507 lastGameTickCount = SDL_GetPerformanceCounter();
3508 DebugStats.t1StartLoop = std::chrono::high_resolution_clock::now();
3509 // game logic
3510 if ( !intro )
3511 {
3512 // handle network messages
3513 // only run up to % framerate interval (1 / (fps * networkTickrate))
3514 if ( networkTickrate == 0 )
3515 {
3516 networkTickrate = 2;
3517 }
3518 if ( multiplayer == CLIENT )
3519 {
3520 clientHandleMessages(fpsLimit * networkTickrate);
3521 }
3522 else if ( multiplayer == SERVER )
3523 {
3524 serverHandleMessages(fpsLimit * networkTickrate);
3525 }
3526 }
3527 DebugStats.t21PostHandleMessages = std::chrono::high_resolution_clock::now();
3528 handleEvents();
3529 DebugStats.t2PostEvents = std::chrono::high_resolution_clock::now();
3530 // handle steam callbacks
3531 #ifdef STEAMWORKS
3532 if ( g_SteamLeaderboards )
3533 {
3534 g_SteamLeaderboards->ProcessLeaderboardUpload();
3535 }
3536 SteamAPI_RunCallbacks();
3537 #endif
3538 #ifdef USE_EOS
3539 EOS_Platform_Tick(EOS.PlatformHandle);
3540 EOS_Platform_Tick(EOS.ServerPlatformHandle);
3541 EOS.StatGlobalManager.updateQueuedStats();
3542 EOS.AccountManager.handleLogin();
3543 EOS.CrossplayAccountManager.handleLogin();
3544 #endif // USE_EOS
3545
3546 DebugStats.t3SteamCallbacks = std::chrono::high_resolution_clock::now();
3547 if ( intro )
3548 {
3549 globalLightModifierActive = GLOBAL_LIGHT_MODIFIER_STOPPED;
3550 shootmode = false; //Hack because somebody put a shootmode = true where it don't belong, which might and does break stuff.
3551 if ( introstage == -1 )
3552 {
3553 // hack to fix these things from breaking everything...
3554 hudarm = NULL;
3555 hudweapon = NULL;
3556 magicLeftHand = NULL;
3557 magicRightHand = NULL;
3558
3559 // team splash
3560 drawRect(NULL, 0, 255);
3561 drawGear(xres / 2, yres / 2, gearsize, gearrot);
3562 drawLine(xres / 2 - 160, yres / 2 + 112, xres / 2 + 160, yres / 2 + 112, SDL_MapRGB(mainsurface->format, 127, 0, 0), std::min<Uint16>(logoalpha, 255));
3563 printTextFormattedAlpha(font16x16_bmp, (xres / 2) - strlen("Turning Wheel") * 9, yres / 2 + 128, std::min<Uint16>(std::max<Uint16>(0, logoalpha), 255), "Turning Wheel");
3564 if ( (logoalpha >= 255 || keystatus[SDL_SCANCODE_ESCAPE] || *inputPressed(joyimpulses[INJOY_MENU_NEXT]) || *inputPressed(joyimpulses[INJOY_MENU_CANCEL])) && !fadeout )
3565 {
3566 fadeout = true;
3567 }
3568 if ( fadefinished || keystatus[SDL_SCANCODE_ESCAPE] || *inputPressed(joyimpulses[INJOY_MENU_NEXT]) || *inputPressed(joyimpulses[INJOY_MENU_CANCEL]))
3569 {
3570 keystatus[SDL_SCANCODE_ESCAPE] = 0;
3571 *inputPressed(joyimpulses[INJOY_MENU_NEXT]) = 0;
3572 *inputPressed(joyimpulses[INJOY_MENU_CANCEL]) = 0;
3573 fadealpha = 255;
3574 #if (!defined STEAMWORKS && !defined USE_EOS)
3575 introstage = 0;
3576 fadeout = false;
3577 fadefinished = false;
3578 #else
3579 int menuMapType = 0;
3580 switch ( rand() % 4 ) // STEAM VERSION INTRO
3581 {
3582 case 0:
3583 case 1:
3584 case 2:
3585 menuMapType = loadMainMenuMap(true, false);
3586 break;
3587 case 3:
3588 menuMapType = loadMainMenuMap(false, false);
3589 break;
3590 default:
3591 break;
3592 }
3593 numplayers = 0;
3594 multiplayer = 0;
3595 assignActions(&map);
3596 generatePathMaps();
3597 fadeout = true;
3598 fadefinished = false;
3599 if ( !skipintro && !strcmp(classtoquickstart, "") )
3600 {
3601 introstage = 6;
3602 #if defined(USE_FMOD) || defined(USE_OPENAL)
3603 playmusic(introductionmusic, true, false, false);
3604 #endif
3605 }
3606 else
3607 {
3608 introstage = 1;
3609 fadeout = false;
3610 fadefinished = false;
3611 #if defined(USE_FMOD) || defined(USE_OPENAL)
3612 if ( menuMapType == 1 )
3613 {
3614 playmusic(intromusic[2], true, false, false);
3615 }
3616 else
3617 {
3618 playmusic(intromusic[1], true, false, false);
3619 }
3620 #endif
3621 }
3622 #endif
3623 }
3624 }
3625 else if ( introstage == 0 )
3626 {
3627 // hack to fix these things from breaking everything...
3628 hudarm = NULL;
3629 hudweapon = NULL;
3630 magicLeftHand = NULL;
3631 magicRightHand = NULL;
3632
3633 drawRect(NULL, 0, 255);
3634 char* banner_text1 = language[738];
3635 char const * const banner_text2 = "\n\n\n\n\n\n\n - Turning Wheel";
3636 ttfPrintText(ttf16, (xres / 2) - longestline(banner_text1)*TTF16_WIDTH / 2, yres / 2 - TTF16_HEIGHT / 2 * 7, banner_text1);
3637 Uint32 colorBlue = SDL_MapRGBA(mainsurface->format, 0, 92, 255, 255);
3638 ttfPrintTextColor(ttf16, (xres / 2) - longestline(banner_text1)*TTF16_WIDTH / 2, yres / 2 - TTF16_HEIGHT / 2 * 7, colorBlue, true, banner_text2);
3639
3640 int time_passed = 0;
3641 if (old_sdl_ticks == 0)
3642 {
3643 old_sdl_ticks = SDL_GetTicks();
3644 }
3645 time_passed = SDL_GetTicks() - old_sdl_ticks;
3646 old_sdl_ticks = SDL_GetTicks();
3647 indev_timer += time_passed;
3648
3649 int menuMapType = 0;
3650 //if( (*inputPressed(joyimpulses[INJOY_MENU_NEXT]) || *inputPressed(joyimpulses[INJOY_MENU_CANCEL]) || *inputPressed(joyimpulses[INJOY_BACK]) || keystatus[SDL_SCANCODE_ESCAPE] || keystatus[SDL_SCANCODE_SPACE] || keystatus[SDL_SCANCODE_RETURN] || mousestatus[SDL_BUTTON_LEFT] || indev_timer >= indev_displaytime) && !fadeout) {
3651 if ( (*inputPressed(joyimpulses[INJOY_MENU_NEXT]) || *inputPressed(joyimpulses[INJOY_MENU_CANCEL]) || keystatus[SDL_SCANCODE_ESCAPE] || keystatus[SDL_SCANCODE_SPACE] || keystatus[SDL_SCANCODE_RETURN] || mousestatus[SDL_BUTTON_LEFT] || indev_timer >= indev_displaytime) && !fadeout)
3652 {
3653 switch ( rand() % 4 ) // DRM FREE VERSION INTRO
3654 {
3655 case 0:
3656 case 1:
3657 case 2:
3658 menuMapType = loadMainMenuMap(true, false);
3659 break;
3660 case 3:
3661 menuMapType = loadMainMenuMap(false, false);
3662 break;
3663 default:
3664 break;
3665 }
3666 numplayers = 0;
3667 multiplayer = 0;
3668 assignActions(&map);
3669 generatePathMaps();
3670 fadeout = true;
3671 fadefinished = false;
3672 }
3673 if ( fadefinished )
3674 {
3675 if ( !skipintro && !strcmp(classtoquickstart, "") )
3676 {
3677 introstage = 6;
3678 #ifdef MUSIC
3679 playmusic(introductionmusic, true, false, false);
3680 #endif
3681 }
3682 else
3683 {
3684 introstage = 1;
3685 fadeout = false;
3686 fadefinished = false;
3687 #ifdef MUSIC
3688 if ( menuMapType == 1 )
3689 {
3690 playmusic(intromusic[2], true, false, false);
3691 }
3692 else
3693 {
3694 playmusic(intromusic[1], true, false, false);
3695 }
3696 #endif
3697 }
3698 }
3699 }
3700 else
3701 {
3702 if (strcmp(classtoquickstart, ""))
3703 {
3704 for ( c = 0; c <= CLASS_MONK; c++ )
3705 {
3706 if ( !strcmp(classtoquickstart, playerClassLangEntry(c, 0)) )
3707 {
3708 client_classes[0] = c;
3709 break;
3710 }
3711 }
3712
3713 // generate unique game key
3714 prng_seed_time();
3715 uniqueGameKey = prng_get_uint();
3716 if ( !uniqueGameKey )
3717 {
3718 uniqueGameKey++;
3719 }
3720 loading = true;
3721
3722 // hack to fix these things from breaking everything...
3723 hudarm = NULL;
3724 hudweapon = NULL;
3725 magicLeftHand = NULL;
3726 magicRightHand = NULL;
3727
3728 // reset class loadout
3729 stats[0]->sex = static_cast<sex_t>(rand() % 2);
3730 stats[0]->appearance = rand() % NUMAPPEARANCES;
3731 stats[0]->clearStats();
3732 initClass(0);
3733 if ( stats[0]->playerRace != RACE_HUMAN )
3734 {
3735 stats[0]->appearance = 0;
3736 }
3737
3738 strcpy(stats[0]->name, "Avatar");
3739 multiplayer = SINGLE;
3740 fadefinished = false;
3741 fadeout = false;
3742 numplayers = 0;
3743
3744 //TODO: Replace all of this with centralized startGameRoutine().
3745 // setup game
3746 shootmode = true;
3747 // make some messages
3748 startMessages();
3749
3750 //gameplayCustomManager.writeAllToDocument();
3751 gameplayCustomManager.readFromFile();
3752
3753 // load dungeon
3754 mapseed = rand(); //Use prng if decide to make a quickstart for MP...
3755 lastEntityUIDs = entity_uids;
3756 for ( node = map.entities->first; node != nullptr; node = node->next )
3757 {
3758 entity = (Entity*)node->element;
3759 entity->flags[NOUPDATE] = true;
3760 }
3761 if ( loadingmap == false )
3762 {
3763 currentlevel = startfloor;
3764 int checkMapHash = -1;
3765 if ( startfloor )
3766 {
3767 physfsLoadMapFile(currentlevel, 0, true, &checkMapHash);
3768 conductGameChallenges[CONDUCT_CHEATS_ENABLED] = 1;
3769 }
3770 else
3771 {
3772 physfsLoadMapFile(0, 0, true, &checkMapHash);
3773 }
3774 if ( checkMapHash == 0 )
3775 {
3776 conductGameChallenges[CONDUCT_MODDED] = 1;
3777 }
3778 }
3779 else
3780 {
3781 if ( genmap == false )
3782 {
3783 std::string fullMapName = physfsFormatMapName(maptoload);
3784 int checkMapHash = -1;
3785 loadMap(fullMapName.c_str(), &map, map.entities, map.creatures, &checkMapHash);
3786 if ( checkMapHash == 0 )
3787 {
3788 conductGameChallenges[CONDUCT_MODDED] = 1;
3789 }
3790 }
3791 else
3792 {
3793 generateDungeon(maptoload, rand());
3794 }
3795 }
3796 assignActions(&map);
3797 generatePathMaps();
3798
3799 achievementObserver.updateData();
3800
3801 saveGame();
3802
3803 enchantedFeatherScrollSeed.seed(uniqueGameKey);
3804 enchantedFeatherScrollsShuffled.clear();
3805 enchantedFeatherScrollsShuffled = enchantedFeatherScrollsFixedList;
3806 std::shuffle(enchantedFeatherScrollsShuffled.begin(), enchantedFeatherScrollsShuffled.end(), enchantedFeatherScrollSeed);
3807 //for ( auto it = enchantedFeatherScrollsShuffled.begin(); it != enchantedFeatherScrollsShuffled.end(); ++it )
3808 //{
3809 // printlog("Sequence: %d", *it);
3810 //}
3811
3812 // kick off the main loop!
3813 strcpy(classtoquickstart, "");
3814 intro = false;
3815 loading = false;
3816 }
3817 else
3818 {
3819
3820 // draws the menu level "backdrop"
3821 drawClearBuffers();
3822 if ( movie == false )
3823 {
3824 menucam.winx = 0;
3825 menucam.winy = 0;
3826 menucam.winw = xres;
3827 menucam.winh = yres;
3828 light = lightSphere(menucam.x, menucam.y, 16, 64);
3829 raycast(&menucam, REALCOLORS);
3830 glDrawWorld(&menucam, REALCOLORS);
3831 //drawFloors(&menucam);
3832 drawEntities3D(&menucam, REALCOLORS);
3833 list_RemoveNode(light->node);
3834 }
3835
3836 handleMainMenu(intro);
3837
3838 UIToastNotificationManager.drawNotifications(movie, true); // draw this before the cursor
3839
3840 // draw mouse
3841 if (!movie && draw_cursor)
3842 {
3843 pos.x = mousex - cursor_bmp->w / 2;
3844 pos.y = mousey - cursor_bmp->h / 2;
3845 pos.w = 0;
3846 pos.h = 0;
3847 drawImageAlpha(cursor_bmp, NULL, &pos, 192);
3848 }
3849 }
3850 }
3851 }
3852 else
3853 {
3854 if ( multiplayer == CLIENT )
3855 {
3856 // make sure shop inventory is alloc'd
3857 if ( !shopInv )
3858 {
3859 shopInv = (list_t*) malloc(sizeof(list_t));
3860 shopInv->first = NULL;
3861 shopInv->last = NULL;
3862 }
3863 }
3864 #ifdef MUSIC
3865 handleLevelMusic();
3866 #endif
3867 DebugStats.t4Music = std::chrono::high_resolution_clock::now();
3868
3869 // toggling the game menu
3870 if ( (keystatus[SDL_SCANCODE_ESCAPE] || (*inputPressed(joyimpulses[INJOY_PAUSE_MENU]) && rebindaction == -1)) && !command )
3871 {
3872 keystatus[SDL_SCANCODE_ESCAPE] = 0;
3873 *inputPressed(joyimpulses[INJOY_PAUSE_MENU]) = 0;
3874 if ( !shootmode )
3875 {
3876 closeAllGUIs(CLOSEGUI_ENABLE_SHOOTMODE, CLOSEGUI_CLOSE_ALL);
3877 gui_mode = GUI_MODE_INVENTORY;
3878 attributespage = 0;
3879 //proficienciesPage = 0;
3880 }
3881 else
3882 {
3883 pauseGame(0, MAXPLAYERS);
3884 }
3885 }
3886
3887 // main drawing
3888 drawClearBuffers();
3889 for (int c = 0; c < MAXPLAYERS; ++c)
3890 {
3891 auto& camera = cameras[c];
3892 auto& cvars = cameravars[c];
3893 camera.ang += cvars.shakex2;
3894 camera.vang += cvars.shakey2 / 200.0;
3895 }
3896 if ( true )
3897 {
3898 // drunkenness spinning
3899 double cosspin = cos(ticks % 360 * PI / 180.f) * 0.25;
3900 double sinspin = sin(ticks % 360 * PI / 180.f) * 0.25;
3901
3902 int playercount = 0;
3903 for (int c = 0; c < MAXPLAYERS; ++c)
3904 {
3905 if (!client_disconnected[c])
3906 {
3907 ++playercount;
3908 }
3909 }
3910
3911 if (playercount >= 1)
3912 {
3913 //int maximum = splitscreen ? MAXPLAYERS : 1;
3914 for (int c = 0; c < MAXPLAYERS; ++c)
3915 {
3916 if (client_disconnected[c])
3917 {
3918 continue;
3919 }
3920 if ( !splitscreen && c != clientnum )
3921 {
3922 continue;
3923 }
3924 auto& camera = cameras[c];
3925 if ( !splitscreen )
3926 {
3927 camera.winx = 0;
3928 camera.winy = 0;
3929 camera.winw = xres;
3930 camera.winh = yres;
3931 }
3932 else
3933 {
3934 if (playercount == 1)
3935 {
3936 camera.winx = 0;
3937 camera.winy = 0;
3938 camera.winw = xres;
3939 camera.winh = yres;
3940 }
3941 else if (playercount == 2)
3942 {
3943 // divide screen horizontally
3944 camera.winx = 0;
3945 camera.winy = c * yres / 2;
3946 camera.winw = xres;
3947 camera.winh = yres / 2;
3948 }
3949 else if (playercount >= 3)
3950 {
3951 // divide screen into quadrants
3952 camera.winx = (c % 2) * xres / 2;
3953 camera.winy = (c / 2) * yres / 2;
3954 camera.winw = xres / 2;
3955 camera.winh = yres / 2;
3956 }
3957 }
3958 if (shaking && players[c] && players[c]->entity && !gamePaused)
3959 {
3960 camera.ang += cosspin * drunkextend;
3961 camera.vang += sinspin * drunkextend;
3962 }
3963
3964 if ( players[c] && players[c]->entity )
3965 {
3966 if ( usecamerasmoothing )
3967 {
3968 real_t oldYaw = players[c]->entity->yaw;
3969 //printText(font8x8_bmp, 20, 20, "using smooth camera");
3970 handlePlayerCameraBobbing(players[c]->entity, c, true);
3971 handlePlayerMovement(players[c]->entity, c, true);
3972 handlePlayerCameraUpdate(players[c]->entity, c, true);
3973 handlePlayerCameraPosition(players[c]->entity, c, true);
3974 //messagePlayer(0, "%3.2f | %3.2f", players[c]->entity->yaw, oldYaw);
3975 }
3976 }
3977
3978 if ( players[clientnum] && players[clientnum]->entity )
3979 {
3980 if ( players[clientnum]->entity->isBlind() )
3981 {
3982 if ( globalLightModifierActive == GLOBAL_LIGHT_MODIFIER_STOPPED
3983 || (globalLightModifierActive == GLOBAL_LIGHT_MODIFIER_DISSIPATING && globalLightModifier < 1.f) )
3984 {
3985 globalLightModifierActive = GLOBAL_LIGHT_MODIFIER_INUSE;
3986 globalLightModifier = 0.f;
3987 globalLightTelepathyModifier = 0.f;
3988 if ( stats[clientnum]->mask && stats[clientnum]->mask->type == TOOL_BLINDFOLD_TELEPATHY )
3989 {
3990 for ( node_t* mapNode = map.creatures->first; mapNode != nullptr; mapNode = mapNode->next )
3991 {
3992 Entity* mapCreature = (Entity*)mapNode->element;
3993 if ( mapCreature )
3994 {
3995 mapCreature->monsterEntityRenderAsTelepath = 1;
3996 }
3997 }
3998 }
3999 }
4000
4001 int PERModifier = 0;
4002 if ( stats[clientnum] && stats[clientnum]->EFFECTS[EFF_BLIND]
4003 && !stats[clientnum]->EFFECTS[EFF_ASLEEP] && !stats[clientnum]->EFFECTS[EFF_MESSY] )
4004 {
4005 // blind but not messy or asleep = allow PER to let you see the world a little.
4006 PERModifier = players[clientnum]->entity->getPER() / 5;
4007 if ( PERModifier < 0 )
4008 {
4009 PERModifier = 0;
4010 }
4011 }
4012
4013 real_t limit = PERModifier * 0.01;
4014 globalLightModifier = std::min(limit, globalLightModifier + 0.0005);
4015
4016 int telepathyLimit = std::min(64, 48 + players[clientnum]->entity->getPER());
4017 globalLightTelepathyModifier = std::min(telepathyLimit / 255.0, globalLightTelepathyModifier + (0.2 / 255.0));
4018 }
4019 else
4020 {
4021 if ( globalLightModifierActive == GLOBAL_LIGHT_MODIFIER_INUSE )
4022 {
4023 for ( node_t* mapNode = map.creatures->first; mapNode != nullptr; mapNode = mapNode->next )
4024 {
4025 Entity* mapCreature = (Entity*)mapNode->element;
4026 if ( mapCreature )
4027 {
4028 mapCreature->monsterEntityRenderAsTelepath = 0;
4029 }
4030 }
4031 }
4032 globalLightModifierActive = GLOBAL_LIGHT_MODIFIER_DISSIPATING;
4033 globalLightTelepathyModifier = 0.f;
4034 if ( globalLightModifier < 1.f )
4035 {
4036 globalLightModifier += 0.01;
4037 }
4038 else
4039 {
4040 globalLightModifier = 1.01;
4041 globalLightModifierActive = GLOBAL_LIGHT_MODIFIER_STOPPED;
4042 }
4043 }
4044 raycast(&camera, REALCOLORS);
4045 glDrawWorld(&camera, REALCOLORS);
4046
4047 if ( gameplayCustomManager.inUse() && gameplayCustomManager.minimapShareProgress && !splitscreen )
4048 {
4049 for ( int i = 0; i < MAXPLAYERS; ++i )
4050 {
4051 if ( i != clientnum && players[i] && players[i]->entity )
4052 {
4053 real_t x = camera.x;
4054 real_t y = camera.y;
4055 real_t ang = camera.ang;
4056
4057 camera.x = players[i]->entity->x / 16.0;
4058 camera.y = players[i]->entity->y / 16.0;
4059 camera.ang = players[i]->entity->yaw;
4060 raycast(&camera, REALCOLORS, false);
4061 camera.x = x;
4062 camera.y = y;
4063 camera.ang = ang;
4064 }
4065 }
4066 }
4067 }
4068 else
4069 {
4070 raycast(&camera, REALCOLORS);
4071 glDrawWorld(&camera, REALCOLORS);
4072 }
4073
4074 //drawFloors(&camera);
4075 drawEntities3D(&camera, REALCOLORS);
4076 if (shaking && players[clientnum] && players[clientnum]->entity && !gamePaused)
4077 {
4078 camera.ang -= cosspin * drunkextend;
4079 camera.vang -= sinspin * drunkextend;
4080 }
4081
4082 auto& cvars = cameravars[c];
4083 camera.ang -= cvars.shakex2;
4084 camera.vang -= cvars.shakey2 / 200.0;
4085 }
4086 }
4087 }
4088
4089 DebugStats.t5MainDraw = std::chrono::high_resolution_clock::now();
4090
4091 updateMessages();
4092 if ( !nohud )
4093 {
4094 if (splitscreen)
4095 {
4096 for (int c = 0; c < MAXPLAYERS; ++c)
4097 {
4098 if (!client_disconnected[c])
4099 {
4100 handleDamageIndicators(c);
4101 }
4102 }
4103 }
4104 else
4105 {
4106 handleDamageIndicators(0);
4107 }
4108 drawMessages();
4109 }
4110
4111 DebugStats.t6Messages = std::chrono::high_resolution_clock::now();
4112
4113 if ( !gamePaused )
4114 {
4115 // interface
4116 if ( !command && (*inputPressed(impulses[IN_STATUS]) || *inputPressed(joyimpulses[INJOY_STATUS])) )
4117 {
4118 *inputPressed(impulses[IN_STATUS]) = 0;
4119 *inputPressed(joyimpulses[INJOY_STATUS]) = 0;
4120
4121 if ( shootmode )
4122 {
4123 openStatusScreen(GUI_MODE_INVENTORY, INVENTORY_MODE_ITEM);
4124 }
4125 else
4126 {
4127 closeAllGUIs(CLOSEGUI_ENABLE_SHOOTMODE, CLOSEGUI_CLOSE_ALL);
4128 }
4129 }
4130 if (!command && (*inputPressed(impulses[IN_SPELL_LIST]) || *inputPressed(joyimpulses[INJOY_SPELL_LIST]))) //TODO: Move to function in interface or something?
4131 {
4132 *inputPressed(impulses[IN_SPELL_LIST]) = 0;
4133 *inputPressed(joyimpulses[INJOY_SPELL_LIST]) = 0;
4134 gui_mode = GUI_MODE_INVENTORY;
4135 selectedItem = NULL;
4136 inventory_mode = INVENTORY_MODE_SPELL;
4137
4138 if (shootmode)
4139 {
4140 shootmode = false;
4141 attributespage = 0;
4142 //proficienciesPage = 0;
4143 }
4144 }
4145 bool hasSpellbook = false;
4146 if ( stats[clientnum]->shield && itemCategory(stats[clientnum]->shield) == SPELLBOOK )
4147 {
4148 hasSpellbook = true;
4149 }
4150 if (!command &&
4151 (*inputPressed(impulses[IN_CAST_SPELL])
4152 || (shootmode && *inputPressed(joyimpulses[INJOY_GAME_CAST_SPELL]))
4153 || (hasSpellbook && *inputPressed(impulses[IN_DEFEND]))
4154 || (hasSpellbook && shootmode && *inputPressed(joyimpulses[INJOY_GAME_DEFEND])) )
4155 )
4156 {
4157 bool allowCasting = true;
4158 if ( *inputPressed(impulses[IN_CAST_SPELL]) || *inputPressed(impulses[IN_DEFEND]) )
4159 {
4160 if (((impulses[IN_CAST_SPELL] == RIGHT_CLICK_IMPULSE || impulses[IN_DEFEND] == RIGHT_CLICK_IMPULSE)
4161 && gui_mode >= GUI_MODE_INVENTORY
4162 && (mouseInsidePlayerInventory() || mouseInsidePlayerHotbar())
4163 ))
4164 {
4165 allowCasting = false;
4166 }
4167 }
4168
4169 if ( (*inputPressed(impulses[IN_DEFEND]) || (*inputPressed(joyimpulses[INJOY_GAME_DEFEND]))) && hasSpellbook
4170 && players[clientnum] && players[clientnum]->entity )
4171 {
4172 if ( players[clientnum]->entity->effectShapeshift != NOTHING )
4173 {
4174 if ( players[clientnum]->entity->effectShapeshift == CREATURE_IMP )
4175 {
4176 // imp allowed to cast via spellbook.
4177 }
4178 else
4179 {
4180 allowCasting = false;
4181 }
4182 }
4183
4184 if ( *inputPressed(impulses[IN_DEFEND]) && impulses[IN_DEFEND] == 285 && itemMenuOpen ) // bound to right click, has context menu open.
4185 {
4186 allowCasting = false;
4187 }
4188 else
4189 {
4190 if ( allowCasting && stats[clientnum]->EFFECTS[EFF_BLIND] )
4191 {
4192 messagePlayer(clientnum, language[3863]); // prevent casting of spell.
4193 allowCasting = false;
4194 *inputPressed(impulses[IN_DEFEND]) = 0;
4195 *inputPressed(joyimpulses[INJOY_GAME_DEFEND]) = 0;
4196 }
4197 }
4198 }
4199
4200 if ( allowCasting )
4201 {
4202 *inputPressed(impulses[IN_CAST_SPELL]) = 0;
4203 if ( shootmode )
4204 {
4205 *inputPressed(joyimpulses[INJOY_GAME_CAST_SPELL]) = 0;
4206 }
4207 if (players[clientnum] && players[clientnum]->entity)
4208 {
4209 if ( conductGameChallenges[CONDUCT_BRAWLER] || achievementBrawlerMode )
4210 {
4211 if ( achievementBrawlerMode && conductGameChallenges[CONDUCT_BRAWLER] )
4212 {
4213 messagePlayer(clientnum, language[2999]); // prevent casting of spell.
4214 }
4215 else
4216 {
4217 if ( achievementBrawlerMode && selected_spell != nullptr )
4218 {
4219 messagePlayer(clientnum, language[2998]); // notify no longer eligible for achievement but still cast.
4220 }
4221 if ( hasSpellbook && (*inputPressed(impulses[IN_DEFEND]) || *inputPressed(joyimpulses[INJOY_GAME_DEFEND])) )
4222 {
4223 castSpellInit(players[clientnum]->entity->getUID(), getSpellFromID(getSpellIDFromSpellbook(stats[clientnum]->shield->type)), true);
4224 }
4225 else
4226 {
4227 castSpellInit(players[clientnum]->entity->getUID(), selected_spell, false);
4228 }
4229 if ( selected_spell != nullptr )
4230 {
4231 conductGameChallenges[CONDUCT_BRAWLER] = 0;
4232 }
4233 }
4234 }
4235 else
4236 {
4237 if ( hasSpellbook && (*inputPressed(impulses[IN_DEFEND]) || *inputPressed(joyimpulses[INJOY_GAME_DEFEND])) )
4238 {
4239 castSpellInit(players[clientnum]->entity->getUID(), getSpellFromID(getSpellIDFromSpellbook(stats[clientnum]->shield->type)), true);
4240 }
4241 else
4242 {
4243 castSpellInit(players[clientnum]->entity->getUID(), selected_spell, false);
4244 }
4245 }
4246 }
4247 *inputPressed(impulses[IN_DEFEND]) = 0;
4248 *inputPressed(joyimpulses[INJOY_GAME_DEFEND]) = 0;
4249 }
4250 }
4251 if ( !command && *inputPressed(impulses[IN_TOGGLECHATLOG]) || (shootmode && *inputPressed(joyimpulses[INJOY_GAME_TOGGLECHATLOG])) )
4252 {
4253 hide_statusbar = !hide_statusbar;
4254 *inputPressed(impulses[IN_TOGGLECHATLOG]) = 0;
4255 *inputPressed(joyimpulses[INJOY_GAME_TOGGLECHATLOG]) = 0;
4256 playSound(139, 64);
4257 }
4258
4259 if ( !command && (*inputPressed(impulses[IN_FOLLOWERMENU_CYCLENEXT]) || *inputPressed(joyimpulses[INJOY_GAME_FOLLOWERMENU_CYCLE])) )
4260 {
4261 FollowerMenu.selectNextFollower();
4262 proficienciesPage = 1;
4263 if ( shootmode && !lock_right_sidebar )
4264 {
4265 openStatusScreen(GUI_MODE_INVENTORY, INVENTORY_MODE_ITEM);
4266 }
4267 *inputPressed(impulses[IN_FOLLOWERMENU_CYCLENEXT]) = 0;
4268 *inputPressed(joyimpulses[INJOY_GAME_FOLLOWERMENU_CYCLE]) = 0;
4269 }
4270
4271 // commands
4272 if ( ( *inputPressed(impulses[IN_CHAT]) || *inputPressed(impulses[IN_COMMAND]) ) && !command )
4273 {
4274 *inputPressed(impulses[IN_CHAT]) = 0;
4275 cursorflash = ticks;
4276 command = true;
4277 if ( !(*inputPressed(impulses[IN_COMMAND])) )
4278 {
4279 strcpy(command_str, "");
4280 }
4281 else
4282 {
4283 strcpy(command_str, "/");
4284 }
4285 inputstr = command_str;
4286 *inputPressed(impulses[IN_COMMAND]) = 0;
4287 SDL_StartTextInput();
4288
4289 FollowerMenu.closeFollowerMenuGUI();
4290 }
4291 if ( command )
4292 {
4293 if ( !SDL_IsTextInputActive() )
4294 {
4295 SDL_StartTextInput();
4296 inputstr = command_str;
4297 }
4298 //strncpy(command_str,inputstr,127);
4299 inputlen = 127;
4300 if ( keystatus[SDL_SCANCODE_ESCAPE] ) // escape
4301 {
4302 keystatus[SDL_SCANCODE_ESCAPE] = 0;
4303 chosen_command = NULL;
4304 command = 0;
4305 }
4306 if ( keystatus[SDL_SCANCODE_RETURN] ) // enter
4307 {
4308 keystatus[SDL_SCANCODE_RETURN] = 0;
4309 command = false;
4310
4311 strncpy(command_str, messageSanitizePercentSign(command_str, nullptr).c_str(), 127);
4312
4313 if ( multiplayer != CLIENT )
4314 {
4315 if ( command_str[0] == '/' )
4316 {
4317 // backslash invokes command procedure
4318 messagePlayer(clientnum, command_str);
4319 consoleCommand(command_str);
4320 }
4321 else
4322 {
4323 if (strcmp(command_str, ""))
4324 {
4325 char chatstring[256];
4326 strcpy(chatstring, language[739]);
4327 strcat(chatstring, command_str);
4328 Uint32 color = SDL_MapRGBA(mainsurface->format, 0, 255, 255, 255);
4329 messagePlayerColor(clientnum, color, chatstring);
4330 playSound(238, 64);
4331 if ( multiplayer == SERVER )
4332 {
4333 // send message to all clients
4334 for ( c = 1; c < MAXPLAYERS; c++ )
4335 {
4336 if ( client_disconnected[c] )
4337 {
4338 continue;
4339 }
4340 strcpy((char*)net_packet->data, "MSGS");
4341 // strncpy() does not copy N bytes if a terminating null is encountered first
4342 // see http://www.cplusplus.com/reference/cstring/strncpy/
4343 // see https://en.cppreference.com/w/c/string/byte/strncpy
4344 // GCC throws a warning (intended) when the length argument to strncpy() in any
4345 // way depends on strlen(src) to discourage this (and related) construct(s).
4346
4347 strncpy(chatstring, stats[0]->name, 10);
4348 chatstring[std::min<size_t>(strlen(stats[0]->name), 10)] = 0; //TODO: Why are size_t and int being compared?
4349 strcat(chatstring, ": ");
4350 strcat(chatstring, command_str);
4351 SDLNet_Write32(color, &net_packet->data[4]);
4352 strcpy((char*)(&net_packet->data[8]), chatstring);
4353 net_packet->address.host = net_clients[c - 1].host;
4354 net_packet->address.port = net_clients[c - 1].port;
4355 net_packet->len = 8 + strlen(chatstring) + 1;
4356 sendPacketSafe(net_sock, -1, net_packet, c - 1);
4357 }
4358 }
4359 }
4360 else
4361 {
4362 strcpy(command_str, "");
4363 }
4364 }
4365 }
4366 else
4367 {
4368 if ( command_str[0] == '/' )
4369 {
4370 // backslash invokes command procedure
4371 messagePlayer(clientnum, command_str);
4372 consoleCommand(command_str);
4373 }
4374 else
4375 {
4376 if (strcmp(command_str, ""))
4377 {
4378 char chatstring[256];
4379 strcpy(chatstring, language[739]);
4380 strcat(chatstring, command_str);
4381 Uint32 color = SDL_MapRGBA(mainsurface->format, 0, 255, 255, 255);
4382 messagePlayerColor(clientnum, color, chatstring);
4383 playSound(238, 64);
4384
4385 // send message to server
4386 strcpy((char*)net_packet->data, "MSGS");
4387 net_packet->data[4] = clientnum;
4388 SDLNet_Write32(color, &net_packet->data[5]);
4389 strcpy((char*)(&net_packet->data[9]), command_str);
4390 net_packet->address.host = net_server.host;
4391 net_packet->address.port = net_server.port;
4392 net_packet->len = 9 + strlen(command_str) + 1;
4393 sendPacketSafe(net_sock, -1, net_packet, 0);
4394 }
4395 else
4396 {
4397 strcpy(command_str, "");
4398 }
4399 }
4400 }
4401 //In either case, save this in the command history.
4402 if (strcmp(command_str, ""))
4403 {
4404 saveCommand(command_str);
4405 }
4406 chosen_command = NULL;
4407 }
4408 ttfPrintTextFormatted(ttf16, MESSAGE_X_OFFSET, MESSAGE_Y_OFFSET, ">%s", command_str);
4409 if ( (ticks - cursorflash) % TICKS_PER_SECOND < TICKS_PER_SECOND / 2 )
4410 {
4411 int x;
4412 TTF_SizeUTF8(ttf16, command_str, &x, NULL);
4413 ttfPrintTextFormatted(ttf16, MESSAGE_X_OFFSET + x + TTF16_WIDTH, MESSAGE_Y_OFFSET, "_");
4414 }
4415 }
4416 else
4417 {
4418 if ( SDL_IsTextInputActive() )
4419 {
4420 SDL_StopTextInput();
4421 }
4422 }
4423
4424 // other status
4425 if ( shootmode == false )
4426 {
4427 SDL_SetRelativeMouseMode(SDL_FALSE);
4428 }
4429 else
4430 {
4431 //Do these get called every frame? Might be better to move this stuff into an if (went_back_into_shootmode) { ... } thing.
4432 //2-3 years later...yes, it is run every frame.
4433 if (identifygui_appraising)
4434 {
4435 //Close the identify GUI if appraising.
4436 identifygui_active = false;
4437 identifygui_appraising = false;
4438
4439 //Cleanup identify GUI gamecontroller code here.
4440 selectedIdentifySlot = -1;
4441 }
4442
4443 if ( removecursegui_active )
4444 {
4445 closeRemoveCurseGUI();
4446 }
4447
4448 GenericGUI.closeGUI();
4449
4450 if ( book_open )
4451 {
4452 closeBookGUI();
4453 }
4454
4455 gui_clickdrag = false; //Just a catchall to make sure that any ongoing GUI dragging ends when the GUI is closed.
4456
4457 if (capture_mouse)
4458 {
4459 SDL_SetRelativeMouseMode(SDL_TRUE);
4460 }
4461
4462 }
4463
4464 DebugStats.t7Inputs = std::chrono::high_resolution_clock::now();
4465
4466 // Draw the static HUD elements
4467 if ( !nohud )
4468 {
4469 //auto tStartMinimapDraw = std::chrono::high_resolution_clock::now();
4470 drawMinimap(); // Draw the Minimap
4471 /*auto tEndMinimapDraw = std::chrono::high_resolution_clock::now();
4472 double timeTaken = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(tEndMinimapDraw - tStartMinimapDraw).count();
4473 printlog("Minimap draw time: %.5f", timeTaken);*/
4474 drawStatus(); // Draw the Status Bar (Hotbar, Hungry/Minotaur Icons, Tooltips, etc.)
4475 }
4476
4477 DebugStats.t8Status = std::chrono::high_resolution_clock::now();
4478
4479 drawSustainedSpells();
4480 updateAppraisalItemBox();
4481
4482 // inventory and stats
4483 if ( shootmode == false )
4484 {
4485 if (gui_mode == GUI_MODE_INVENTORY)
4486 {
4487 updateCharacterSheet();
4488 updatePlayerInventory();
4489 updateChestInventory();
4490 updateIdentifyGUI();
4491 updateRemoveCurseGUI();
4492 GenericGUI.updateGUI();
4493 updateBookGUI();
4494 //updateRightSidebar();
4495
4496 }
4497 else if (gui_mode == GUI_MODE_MAGIC)
4498 {
4499 updateCharacterSheet();
4500 updateMagicGUI();
4501 }
4502 else if (gui_mode == GUI_MODE_SHOP)
4503 {
4504 updateCharacterSheet();
4505 updatePlayerInventory();
4506 updateShopWindow();
4507 }
4508
4509 if ( proficienciesPage == 1 )
4510 {
4511 drawPartySheet();
4512 }
4513 else
4514 {
4515 drawSkillsSheet();
4516 }
4517 }
4518 else
4519 {
4520 if ( lock_right_sidebar )
4521 {
4522 if ( proficienciesPage == 1 )
4523 {
4524 drawPartySheet();
4525 }
4526 else
4527 {
4528 drawSkillsSheet();
4529 }
4530 }
4531 }
4532 if ( (shootmode == false && gui_mode == GUI_MODE_INVENTORY) || show_game_timer_always )
4533 {
4534 Uint32 sec = (completionTime / TICKS_PER_SECOND) % 60;
4535 Uint32 min = ((completionTime / TICKS_PER_SECOND) / 60) % 60;
4536 Uint32 hour = ((completionTime / TICKS_PER_SECOND) / 60) / 60;
4537 printTextFormatted(font12x12_bmp, xres - 12 * 9, 12, "%02d:%02d:%02d", hour, min, sec);
4538 }
4539
4540 DebugStats.t9GUI = std::chrono::high_resolution_clock::now();
4541
4542 UIToastNotificationManager.drawNotifications(movie, true); // draw this before the cursors
4543
4544 // pointer in inventory screen
4545 if (shootmode == false)
4546 {
4547 if (selectedItem)
4548 {
4549 pos.x = mousex - 15;
4550 pos.y = mousey - 15;
4551 pos.w = 32 * uiscale_inventory;
4552 pos.h = 32 * uiscale_inventory;
4553 drawImageScaled(itemSprite(selectedItem), NULL, &pos);
4554 if ( selectedItem->count > 1 )
4555 {
4556 ttfPrintTextFormatted(ttf8, pos.x + 24 * uiscale_inventory, pos.y + 24 * uiscale_inventory, "%d", selectedItem->count);
4557 }
4558 if ( itemCategory(selectedItem) != SPELL_CAT )
4559 {
4560 if ( itemIsEquipped(selectedItem, clientnum) )
4561 {
4562 pos.y += 16;
4563 drawImage(equipped_bmp, NULL, &pos);
4564 }
4565 else if ( selectedItem->status == BROKEN )
4566 {
4567 pos.y += 16;
4568 drawImage(itembroken_bmp, NULL, &pos);
4569 }
4570 }
4571 else
4572 {
4573 spell_t* spell = getSpellFromItem(selectedItem);
4574 if ( selected_spell == spell &&
4575 (selected_spell_last_appearance == selectedItem->appearance || selected_spell_last_appearance == -1) )
4576 {
4577 pos.y += 16;
4578 drawImage(equipped_bmp, NULL, &pos);
4579 }
4580 }
4581 }
4582 else if ( FollowerMenu.selectMoveTo &&
4583 (FollowerMenu.optionSelected == ALLY_CMD_MOVETO_SELECT
4584 || FollowerMenu.optionSelected == ALLY_CMD_ATTACK_SELECT) )
4585 {
4586 pos.x = mousex - cursor_bmp->w / 2;
4587 pos.y = mousey - cursor_bmp->h / 2;
4588 drawImageAlpha(cursor_bmp, NULL, &pos, 192);
4589 if ( FollowerMenu.optionSelected == ALLY_CMD_MOVETO_SELECT )
4590 {
4591 if ( FollowerMenu.followerToCommand
4592 && (FollowerMenu.followerToCommand->getMonsterTypeFromSprite() == SENTRYBOT
4593 || FollowerMenu.followerToCommand->getMonsterTypeFromSprite() == SPELLBOT)
4594 )
4595 {
4596 ttfPrintTextFormatted(ttf12, pos.x + 24, pos.y + 24, language[3650]);
4597 }
4598 else
4599 {
4600 ttfPrintTextFormatted(ttf12, pos.x + 24, pos.y + 24, language[3039]);
4601 }
4602 }
4603 else
4604 {
4605 if ( !strcmp(FollowerMenu.interactText, "") )
4606 {
4607 if ( FollowerMenu.followerToCommand )
4608 {
4609 int type = FollowerMenu.followerToCommand->getMonsterTypeFromSprite();
4610 if ( FollowerMenu.allowedInteractItems(type)
4611 || FollowerMenu.allowedInteractFood(type)
4612 || FollowerMenu.allowedInteractWorld(type)
4613 )
4614 {
4615 ttfPrintTextFormatted(ttf12, pos.x + 24, pos.y + 24, "Interact with...");
4616 }
4617 else
4618 {
4619 ttfPrintTextFormatted(ttf12, pos.x + 24, pos.y + 24, "Attack...");
4620 }
4621 }
4622 else
4623 {
4624 ttfPrintTextFormatted(ttf12, pos.x + 24, pos.y + 24, "Interact with...");
4625 }
4626 }
4627 else
4628 {
4629 ttfPrintTextFormatted(ttf12, pos.x + 24, pos.y + 24, "%s", FollowerMenu.interactText);
4630 }
4631 }
4632 }
4633 else if (draw_cursor)
4634 {
4635 pos.x = mousex - cursor_bmp->w / 2;
4636 pos.y = mousey - cursor_bmp->h / 2;
4637 pos.w = 0;
4638 pos.h = 0;
4639 drawImageAlpha(cursor_bmp, NULL, &pos, 192);
4640 }
4641 }
4642 else if ( !nohud )
4643 {
4644 pos.x = xres / 2 - cross_bmp->w / 2;
4645 pos.y = yres / 2 - cross_bmp->h / 2;
4646 pos.w = 0;
4647 pos.h = 0;
4648 if ( FollowerMenu.selectMoveTo && (FollowerMenu.optionSelected == ALLY_CMD_MOVETO_SELECT
4649 || FollowerMenu.optionSelected == ALLY_CMD_ATTACK_SELECT) )
4650 {
4651 pos.x = xres / 2 - cursor_bmp->w / 2;
4652 pos.y = yres / 2 - cursor_bmp->h / 2;
4653 drawImageAlpha(cursor_bmp, NULL, &pos, 192);
4654 if ( FollowerMenu.optionSelected == ALLY_CMD_MOVETO_SELECT )
4655 {
4656 if ( FollowerMenu.followerToCommand
4657 && (FollowerMenu.followerToCommand->getMonsterTypeFromSprite() == SENTRYBOT
4658 || FollowerMenu.followerToCommand->getMonsterTypeFromSprite() == SPELLBOT)
4659 )
4660 {
4661 ttfPrintTextFormatted(ttf12, pos.x + 24, pos.y + 24, language[3650]);
4662 }
4663 else
4664 {
4665 ttfPrintTextFormatted(ttf12, pos.x + 24, pos.y + 24, language[3039]);
4666 }
4667 }
4668 else
4669 {
4670 if ( !strcmp(FollowerMenu.interactText, "") )
4671 {
4672 if ( FollowerMenu.followerToCommand )
4673 {
4674 int type = FollowerMenu.followerToCommand->getMonsterTypeFromSprite();
4675 if ( FollowerMenu.allowedInteractItems(type)
4676 || FollowerMenu.allowedInteractFood(type)
4677 || FollowerMenu.allowedInteractWorld(type)
4678 )
4679 {
4680 ttfPrintTextFormatted(ttf12, pos.x + 24, pos.y + 24, "Interact with...");
4681 }
4682 else
4683 {
4684 ttfPrintTextFormatted(ttf12, pos.x + 24, pos.y + 24, "Attack...");
4685 }
4686 }
4687 else
4688 {
4689 ttfPrintTextFormatted(ttf12, pos.x + 24, pos.y + 24, "Interact with...");
4690 }
4691 }
4692 else
4693 {
4694 ttfPrintTextFormatted(ttf12, pos.x + 24, pos.y + 24, "%s", FollowerMenu.interactText);
4695 }
4696 }
4697 }
4698 else
4699 {
4700 if ( players[clientnum] && players[clientnum]->entity && stats[clientnum]
4701 && stats[clientnum]->defending )
4702 {
4703 bool foundTinkeringKit = false;
4704 if ( stats[clientnum]->shield && stats[clientnum]->shield->type == TOOL_TINKERING_KIT )
4705 {
4706 ttfPrintTextFormatted(ttf12, pos.x + 24, pos.y + 24, language[3663]);
4707 }
4708 }
4709 drawImageAlpha(cross_bmp, NULL, &pos, 128);
4710 }
4711 }
4712 }
4713 else if ( !multiplayer )
4714 {
4715 // darken the rest of the screen
4716 src.x = 0;
4717 src.y = 0;
4718 src.w = mainsurface->w;
4719 src.h = mainsurface->h;
4720 drawRect(&src, SDL_MapRGB(mainsurface->format, 0, 0, 0), 127);
4721 }
4722
4723 if ( gamePaused )
4724 {
4725 // handle menu
4726 handleMainMenu(intro);
4727 }
4728 else
4729 {
4730 // draw subwindow
4731 if ( !movie )
4732 {
4733 if ( subwindow )
4734 {
4735 drawWindowFancy(subx1, suby1, subx2, suby2);
4736 if ( subtext != NULL )
4737 {
4738 if ( strncmp(subtext, language[1133], 12) )
4739 {
4740 ttfPrintTextFormatted(ttf12, subx1 + 8, suby1 + 8, subtext);
4741 }
4742 else
4743 {
4744 ttfPrintTextFormatted(ttf16, subx1 + 8, suby1 + 8, subtext);
4745 }
4746 }
4747 }
4748
4749 // process button actions
4750 handleButtons();
4751 }
4752 }
4753
4754 if ( gamePaused ) // draw after main menu windows etc.
4755 {
4756 UIToastNotificationManager.drawNotifications(movie, true); // draw this before the cursor
4757 }
4758
4759 if (((subwindow && !shootmode) || gamePaused) && draw_cursor)
4760 {
4761 pos.x = mousex - cursor_bmp->w / 2;
4762 pos.y = mousey - cursor_bmp->h / 2;
4763 pos.w = 0;
4764 pos.h = 0;
4765 drawImageAlpha(cursor_bmp, NULL, &pos, 192);
4766 }
4767
4768 if ( !shootmode )
4769 {
4770 if ( *inputPressed(impulses[IN_HOTBAR_SCROLL_RIGHT]) )
4771 {
4772 *inputPressed(impulses[IN_HOTBAR_SCROLL_RIGHT]) = 0;
4773 }
4774 if ( *inputPressed(impulses[IN_HOTBAR_SCROLL_LEFT]) )
4775 {
4776 *inputPressed(impulses[IN_HOTBAR_SCROLL_LEFT]) = 0;
4777 }
4778 }
4779 }
4780
4781 // fade in/out effect
4782 if ( fadealpha > 0 )
4783 {
4784 src.x = 0;
4785 src.y = 0;
4786 src.w = mainsurface->w;
4787 src.h = mainsurface->h;
4788 drawRect(&src, SDL_MapRGB(mainsurface->format, 0, 0, 0), fadealpha);
4789 }
4790
4791 // fps counter
4792 if ( showfps )
4793 {
4794 printTextFormatted(font8x8_bmp, 8, 8, "fps = %3.1f", fps);
4795 }
4796
4797 DebugStats.t10FrameLimiter = std::chrono::high_resolution_clock::now();
4798 if ( logCheckMainLoopTimers )
4799 {
4800 std::chrono::duration<double> time_span =
4801 std::chrono::duration_cast<std::chrono::duration<double>>(DebugStats.t10FrameLimiter - DebugStats.t11End);
4802 double timer = time_span.count() * 1000;
4803 if ( timer > ((1000.f / (fps) * 1.4)) )
4804 {
4805 DebugStats.displayStats = true;
4806 DebugStats.storeStats();
4807 DebugStats.storeEventStats();
4808 messagePlayer(clientnum, "Timers: %f total.", timer);
4809 }
4810 if ( DebugStats.displayStats )
4811 {
4812 printTextFormatted(font8x8_bmp, 8, 20, DebugStats.debugOutput);
4813 printTextFormatted(font8x8_bmp, 8, 100, DebugStats.debugEventOutput);
4814 }
4815 }
4816
4817 UIToastNotificationManager.drawNotifications(movie, false);
4818
4819 // update screen
4820 GO_SwapBuffers(screen);
4821
4822 // screenshots
4823 if ( keystatus[SDL_SCANCODE_F6] )
4824 {
4825 keystatus[SDL_SCANCODE_F6] = 0;
4826 takeScreenshot();
4827 }
4828
4829 // frame rate limiter
4830 while ( frameRateLimit(fpsLimit, true) )
4831 {
4832 if ( !intro )
4833 {
4834 // handle network messages
4835 if ( multiplayer == CLIENT )
4836 {
4837 clientHandleMessages(fpsLimit);
4838 }
4839 else if ( multiplayer == SERVER )
4840 {
4841 serverHandleMessages(fpsLimit);
4842 }
4843 }
4844 }
4845
4846 // selectedEntityGimpTimer will only allow the game to process a right click entity click 1-2 times
4847 // otherwise if we interacted with a menu the gimp timer does not increment. (it would have auto reset the status of IN_USE)
4848 if ( !(*inputPressed(impulses[IN_USE])) && !(*inputPressed(joyimpulses[INJOY_GAME_USE])) )
4849 {
4850 selectedEntityGimpTimer = 0;
4851 }
4852 else
4853 {
4854 if ( selectedEntityGimpTimer >= 2 )
4855 {
4856 if ( *inputPressed(impulses[IN_USE]) )
4857 {
4858 *inputPressed(impulses[IN_USE]) = 0;
4859 }
4860 if ( *inputPressed(joyimpulses[INJOY_GAME_USE]) )
4861 {
4862 *inputPressed(joyimpulses[INJOY_GAME_USE]) = 0;
4863 }
4864 }
4865 }
4866
4867 DebugStats.t11End = std::chrono::high_resolution_clock::now();
4868
4869 // increase the cycle count
4870 cycles++;
4871 }
4872 saveConfig("default.cfg");
4873
4874 // deinit
4875 deinitGame();
4876 return deinitApp();
4877 }
4878 catch (const std::exception &exc)
4879 {
4880 // catch anything thrown within try block that derives from std::exception
4881 std::cerr << "UNHANDLED EXCEPTION CAUGHT: " << exc.what() << "\n";
4882 return 1;
4883 }
4884 catch (...)
4885 {
4886 //TODO:
4887 std::cerr << "UNKNOWN EXCEPTION CAUGHT!\n";
4888 return 1;
4889 }
4890 }
4891
storeStats()4892 void DebugStatsClass::storeStats()
4893 {
4894 if ( !displayStats )
4895 {
4896 return;
4897 }
4898 storeOldTimePoints();
4899 double out1 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(t2Stored - t21Stored).count();
4900 double out2 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(t3Stored - t2Stored).count();
4901 double out3 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(t5Stored - t4Stored).count();
4902 double out4 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(t6Stored - t5Stored).count();
4903 double out5 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(t7Stored - t6Messages).count();
4904 double out6 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(t8Stored - t7Stored).count();
4905 double out7 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(t9Stored - t8Stored).count();
4906 double out8 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(t10Stored - t9Stored).count();
4907 double out9 = -1000 * std::chrono::duration_cast<std::chrono::duration<double>>(t11Stored - t10Stored).count();
4908 double out10 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(t21Stored - t1Stored).count();
4909 snprintf(debugOutput, 1023,
4910 "Messages: %4.5fms\nEvents: %4.5fms\nSteamCallbacks: %4.5fms\nMainDraw: %4.5fms\nMessages: %4.5fms\nInputs: %4.5fms\nStatus: %4.5fms\nGUI: %4.5fms\nFrameLimiter: %4.5fms\nEnd: %4.5fms\n",
4911 out10, out1, out2, out3, out4, out5, out6, out7, out8, out9);
4912 }
4913
storeEventStats()4914 void DebugStatsClass::storeEventStats()
4915 {
4916 double out1 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(eventsT2stored - eventsT1stored).count();
4917 double out2 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(eventsT3stored - eventsT2stored).count();
4918 double out3 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(eventsT4stored - eventsT3stored).count();
4919 double out4 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(eventsT5stored - eventsT4stored).count();
4920 double out5 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(eventsT6stored - eventsT5stored).count();
4921
4922 double messages1 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(messagesT1stored - t1StartLoop).count();
4923 double messages2 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(t21Stored - messagesT1stored).count();
4924
4925 snprintf(debugEventOutput, 1023,
4926 "Events1: %4.5fms\nEvents2: %4.5fms\nEvents3: %4.5fms\nEvents4: %4.5fms\nEvents5: %4.5fms\nMessagesT1: %4.5fms\nMessagesT2: %4.5fms\n",
4927 out1, out2, out3, out4, out5, messages1, messages2);
4928 }
4929