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