1 /**
2  * @file diablo.cpp
3  *
4  * Implementation of the main game initialization functions.
5  */
6 #include "all.h"
7 #include "paths.h"
8 #include "console.h"
9 #include "options.h"
10 #include "../3rdParty/Storm/Source/storm.h"
11 #include "../DiabloUI/diabloui.h"
12 #include <config.h>
13 
14 DEVILUTION_BEGIN_NAMESPACE
15 
16 SDL_Window *ghMainWnd;
17 DWORD glSeedTbl[NUMLEVELS];
18 dungeon_type gnLevelTypeTbl[NUMLEVELS];
19 int glEndSeed[NUMLEVELS];
20 int glMid1Seed[NUMLEVELS];
21 int glMid2Seed[NUMLEVELS];
22 int glMid3Seed[NUMLEVELS];
23 int MouseX;
24 int MouseY;
25 BOOL gbGameLoopStartup;
26 BOOL gbRunGame;
27 BOOL gbRunGameResult;
28 BOOL zoomflag;
29 /** Enable updating of player character, set to false once Diablo dies */
30 BOOL gbProcessPlayers;
31 BOOL gbLoadGame;
32 BOOLEAN cineflag;
33 int force_redraw;
34 BOOL light4flag;
35 int setseed;
36 int PauseMode;
37 bool forceSpawn;
38 bool forceDiablo;
39 bool gbTheoQuest;
40 bool gbCowQuest;
41 bool gbNestArt;
42 bool gbBard;
43 bool gbBarbarian;
44 int sgnTimeoutCurs;
45 char sgbMouseDown;
46 int color_cycle_timer;
47 int gnTickRate;
48 WORD gnTickDelay = 50;
49 /** Game options */
50 Options sgOptions;
51 
52 /* rdata */
53 
54 bool gbForceWindowed = false;
55 bool gbShowIntro = true;
56 bool leveldebug = false;
57 #ifdef _DEBUG
58 bool monstdebug = false;
59 _monster_id DebugMonsters[10];
60 int debugmonsttypes = 0;
61 bool visiondebug = false;
62 int questdebug = -1;
63 bool debug_mode_key_s = false;
64 bool debug_mode_key_w = false;
65 bool debug_mode_key_inverted_v = false;
66 bool debug_mode_dollar_sign = false;
67 bool debug_mode_key_d = false;
68 bool debug_mode_key_i = false;
69 int debug_mode_key_j = 0;
70 int arrowdebug = 0;
71 #endif
72 /** Specifies whether players are in non-PvP mode. */
73 bool gbFriendlyMode = true;
74 /** Specifies players will still damage other players in non-PvP mode. */
75 bool gbFriendlyFire;
76 /** Default quick messages */
77 const char *const spszMsgTbl[] = {
78 	"I need help! Come Here!",
79 	"Follow me.",
80 	"Here's something for you.",
81 	"Now you DIE!"
82 };
83 /** INI files variable names for quick message keys */
84 const char *const spszMsgHotKeyTbl[] = { "F9", "F10", "F11", "F12" };
85 
86 /** To know if these things have been done when we get to the diablo_deinit() function */
87 bool was_archives_init = false;
88 /** To know if surfaces have been initialized or not */
89 bool was_window_init = false;
90 bool was_ui_init = false;
91 bool was_snd_init = false;
92 bool sbWasOptionsLoaded = false;
93 
94 // Controller support:
95 extern void plrctrls_every_frame();
96 extern void plrctrls_after_game_logic();
97 
print_help_and_exit()98 [[noreturn]] static void print_help_and_exit()
99 {
100 	printInConsole("Options:\n");
101 	printInConsole("    %-20s %-30s\n", "-h, --help", "Print this message and exit");
102 	printInConsole("    %-20s %-30s\n", "--version", "Print the version and exit");
103 	printInConsole("    %-20s %-30s\n", "--data-dir", "Specify the folder of diabdat.mpq");
104 	printInConsole("    %-20s %-30s\n", "--save-dir", "Specify the folder of save files");
105 	printInConsole("    %-20s %-30s\n", "--config-dir", "Specify the location of diablo.ini");
106 	printInConsole("    %-20s %-30s\n", "--ttf-dir", "Specify the location of the .ttf font");
107 	printInConsole("    %-20s %-30s\n", "--ttf-name", "Specify the name of a custom .ttf font");
108 	printInConsole("    %-20s %-30s\n", "-n", "Skip startup videos");
109 	printInConsole("    %-20s %-30s\n", "-f", "Display frames per second");
110 	printInConsole("    %-20s %-30s\n", "-x", "Run in windowed mode");
111 	printInConsole("    %-20s %-30s\n", "--spawn", "Force spawn mode even if diabdat.mpq is found");
112 	printInConsole("\nHellfire options:\n");
113 	printInConsole("    %-20s %-30s\n", "--diablo", "Force diablo mode even if hellfire.mpq is found");
114 	printInConsole("    %-20s %-30s\n", "--nestart", "Use alternate nest palette");
115 #ifdef _DEBUG
116 	printInConsole("\nDebug options:\n");
117 	printInConsole("    %-20s %-30s\n", "-d", "Increaased item drops");
118 	printInConsole("    %-20s %-30s\n", "-w", "Enable cheats");
119 	printInConsole("    %-20s %-30s\n", "-$", "Enable god mode");
120 	printInConsole("    %-20s %-30s\n", "-^", "Enable god mode and debug tools");
121 	printInConsole("    %-20s %-30s\n", "-v", "Highlight visibility");
122 	printInConsole("    %-20s %-30s\n", "-i", "Ignore network timeout");
123 	printInConsole("    %-20s %-30s\n", "-j <##>", "Mausoleum warps to given level");
124 	printInConsole("    %-20s %-30s\n", "-l <##> <##>", "Start in level as type");
125 	printInConsole("    %-20s %-30s\n", "-m <##>", "Add debug monster, up to 10 allowed");
126 	printInConsole("    %-20s %-30s\n", "-q <#>", "Force a certain quest");
127 	printInConsole("    %-20s %-30s\n", "-r <##########>", "Set map seed");
128 	printInConsole("    %-20s %-30s\n", "-t <##>", "Set current quest level");
129 #endif
130 	printInConsole("\nReport bugs at https://github.com/diasurgical/devilutionX/\n");
131 	diablo_quit(0);
132 }
133 
diablo_parse_flags(int argc,char ** argv)134 static void diablo_parse_flags(int argc, char **argv)
135 {
136 	for (int i = 1; i < argc; i++) {
137 		if (strcasecmp("-h", argv[i]) == 0 || strcasecmp("--help", argv[i]) == 0) {
138 			print_help_and_exit();
139 		} else if (strcasecmp("--version", argv[i]) == 0) {
140 			printInConsole("%s v%s\n", PROJECT_NAME, PROJECT_VERSION);
141 			diablo_quit(0);
142 		} else if (strcasecmp("--data-dir", argv[i]) == 0) {
143 			SetBasePath(argv[++i]);
144 		} else if (strcasecmp("--save-dir", argv[i]) == 0) {
145 			SetPrefPath(argv[++i]);
146 		} else if (strcasecmp("--config-dir", argv[i]) == 0) {
147 			SetConfigPath(argv[++i]);
148 		} else if (strcasecmp("--ttf-dir", argv[i]) == 0) {
149 			SetTtfPath(argv[++i]);
150 		} else if (strcasecmp("--ttf-name", argv[i]) == 0) {
151 			SetTtfName(argv[++i]);
152 		} else if (strcasecmp("-n", argv[i]) == 0) {
153 			gbShowIntro = false;
154 		} else if (strcasecmp("-f", argv[i]) == 0) {
155 			EnableFrameCount();
156 		} else if (strcasecmp("-x", argv[i]) == 0) {
157 			gbForceWindowed = true;
158 		} else if (strcasecmp("--spawn", argv[i]) == 0) {
159 			forceSpawn = true;
160 		} else if (strcasecmp("--diablo", argv[i]) == 0) {
161 			forceDiablo = true;
162 		} else if (strcasecmp("--nestart", argv[i]) == 0) {
163 			gbNestArt = true;
164 		} else if (strcasecmp("--vanilla", argv[i]) == 0) {
165 			gbVanilla = true;
166 #ifdef _DEBUG
167 		} else if (strcasecmp("-^", argv[i]) == 0) {
168 			debug_mode_key_inverted_v = true;
169 		} else if (strcasecmp("-$", argv[i]) == 0) {
170 			debug_mode_dollar_sign = true;
171 		} else if (strcasecmp("-d", argv[i]) == 0) {
172 			debug_mode_key_d = true;
173 		} else if (strcasecmp("-i", argv[i]) == 0) {
174 			debug_mode_key_i = true;
175 		} else if (strcasecmp("-j", argv[i]) == 0) {
176 			debug_mode_key_j = SDL_atoi(argv[++i]);
177 		} else if (strcasecmp("-l", argv[i]) == 0) {
178 			setlevel = FALSE;
179 			leveldebug = true;
180 			leveltype = (dungeon_type)SDL_atoi(argv[++i]);
181 			currlevel = SDL_atoi(argv[++i]);
182 			plr[0].plrlevel = currlevel;
183 		} else if (strcasecmp("-m", argv[i]) == 0) {
184 			monstdebug = true;
185 			DebugMonsters[debugmonsttypes++] = (_monster_id)SDL_atoi(argv[++i]);
186 		} else if (strcasecmp("-q", argv[i]) == 0) {
187 			questdebug = SDL_atoi(argv[++i]);
188 		} else if (strcasecmp("-r", argv[i]) == 0) {
189 			setseed = SDL_atoi(argv[++i]);
190 		} else if (strcasecmp("-s", argv[i]) == 0) {
191 			debug_mode_key_s = true;
192 		} else if (strcasecmp("-t", argv[i]) == 0) {
193 			leveldebug = true;
194 			setlevel = true;
195 			setlvlnum = SDL_atoi(argv[++i]);
196 		} else if (strcasecmp("-v", argv[i]) == 0) {
197 			visiondebug = true;
198 		} else if (strcasecmp("-w", argv[i]) == 0) {
199 			debug_mode_key_w = true;
200 #endif
201 		} else {
202 			printInConsole("unrecognized option '%s'\n", argv[i]);
203 			print_help_and_exit();
204 		}
205 	}
206 }
207 
FreeGameMem()208 void FreeGameMem()
209 {
210 	music_stop();
211 
212 	MemFreeDbg(pDungeonCels);
213 	MemFreeDbg(pMegaTiles);
214 	MemFreeDbg(pLevelPieces);
215 	MemFreeDbg(pSpecialCels);
216 
217 	FreeMissiles();
218 	FreeMonsters();
219 	FreeObjectGFX();
220 	FreeMonsterSnd();
221 	FreeTownerGFX();
222 }
223 
start_game(interface_mode uMsg)224 static void start_game(interface_mode uMsg)
225 {
226 	zoomflag = TRUE;
227 	CalcViewportGeometry();
228 	cineflag = FALSE;
229 	InitCursor();
230 	InitLightTable();
231 #ifdef _DEBUG
232 	LoadDebugGFX();
233 #endif
234 	assert(ghMainWnd);
235 	music_stop();
236 	InitQol();
237 	ShowProgress(uMsg);
238 	gmenu_init_menu();
239 	InitLevelCursor();
240 	sgnTimeoutCurs = CURSOR_NONE;
241 	sgbMouseDown = CLICK_NONE;
242 	track_repeat_walk(FALSE);
243 }
244 
free_game()245 static void free_game()
246 {
247 	int i;
248 
249 	FreeQol();
250 	FreeControlPan();
251 	FreeInvGFX();
252 	FreeGMenu();
253 	FreeQuestText();
254 	FreeStoreMem();
255 
256 	for (i = 0; i < MAX_PLRS; i++)
257 		FreePlayerGFX(i);
258 
259 	FreeItemGFX();
260 	FreeCursor();
261 	FreeLightTable();
262 #ifdef _DEBUG
263 	FreeDebugGFX();
264 #endif
265 	FreeGameMem();
266 }
267 
268 // Controller support: Actions to run after updating the cursor state.
269 // Defined in SourceX/controls/plctrls.cpp.
270 extern void finish_simulated_mouse_clicks(int current_mouse_x, int current_mouse_y);
271 extern void plrctrls_after_check_curs_move();
272 
ProcessInput()273 static bool ProcessInput()
274 {
275 	if (PauseMode == 2) {
276 		return false;
277 	}
278 
279 	plrctrls_every_frame();
280 
281 	if (!gbIsMultiplayer && gmenu_is_active()) {
282 		force_redraw |= 1;
283 		return false;
284 	}
285 
286 	if (!gmenu_is_active() && sgnTimeoutCurs == CURSOR_NONE) {
287 #ifndef USE_SDL1
288 		finish_simulated_mouse_clicks(MouseX, MouseY);
289 #endif
290 		CheckCursMove();
291 		plrctrls_after_check_curs_move();
292 		track_process();
293 	}
294 
295 	return true;
296 }
297 
run_game_loop(interface_mode uMsg)298 static void run_game_loop(interface_mode uMsg)
299 {
300 	WNDPROC saveProc;
301 	MSG msg;
302 
303 	nthread_ignore_mutex(TRUE);
304 	start_game(uMsg);
305 	assert(ghMainWnd);
306 	saveProc = SetWindowProc(GM_Game);
307 	control_update_life_mana();
308 	run_delta_info();
309 	gbRunGame = TRUE;
310 	gbProcessPlayers = TRUE;
311 	gbRunGameResult = TRUE;
312 	force_redraw = 255;
313 	DrawAndBlit();
314 	LoadPWaterPalette();
315 	PaletteFadeIn(8);
316 	force_redraw = 255;
317 	gbGameLoopStartup = TRUE;
318 	nthread_ignore_mutex(FALSE);
319 
320 	while (gbRunGame) {
321 		while (FetchMessage(&msg)) {
322 			if (msg.message == DVL_WM_QUIT) {
323 				gbRunGameResult = FALSE;
324 				gbRunGame = FALSE;
325 				break;
326 			}
327 			TranslateMessage(&msg);
328 			PushMessage(&msg);
329 		}
330 		if (!gbRunGame)
331 			break;
332 		if (!nthread_has_500ms_passed()) {
333 			ProcessInput();
334 			force_redraw |= 1;
335 			DrawAndBlit();
336 			continue;
337 		}
338 		diablo_color_cyc_logic();
339 		multi_process_network_packets();
340 		game_loop(gbGameLoopStartup);
341 		gbGameLoopStartup = FALSE;
342 		DrawAndBlit();
343 	}
344 
345 	if (gbIsMultiplayer) {
346 		pfile_write_hero();
347 	}
348 
349 	pfile_flush_W();
350 	PaletteFadeOut(8);
351 	NewCursor(CURSOR_NONE);
352 	ClearScreenBuffer();
353 	force_redraw = 255;
354 	scrollrt_draw_game_screen(TRUE);
355 	saveProc = SetWindowProc(saveProc);
356 	assert(saveProc == GM_Game);
357 	free_game();
358 
359 	if (cineflag) {
360 		cineflag = FALSE;
361 		DoEnding();
362 	}
363 }
364 
StartGame(BOOL bNewGame,BOOL bSinglePlayer)365 BOOL StartGame(BOOL bNewGame, BOOL bSinglePlayer)
366 {
367 	BOOL fExitProgram;
368 
369 	gbSelectProvider = TRUE;
370 
371 	do {
372 		fExitProgram = FALSE;
373 		gbLoadGame = FALSE;
374 
375 		if (!NetInit(bSinglePlayer, &fExitProgram)) {
376 			gbRunGameResult = !fExitProgram;
377 			break;
378 		}
379 
380 		gbSelectProvider = FALSE;
381 
382 		if (bNewGame || !gbValidSaveFile) {
383 			InitLevels();
384 			InitQuests();
385 			InitPortals();
386 			InitDungMsgs(myplr);
387 		}
388 		interface_mode uMsg = WM_DIABNEWGAME;
389 		if (gbValidSaveFile && gbLoadGame) {
390 			uMsg = WM_DIABLOADGAME;
391 		}
392 		run_game_loop(uMsg);
393 		NetClose();
394 		pfile_create_player_description(NULL, 0);
395 	} while (gbRunGameResult);
396 
397 	SNetDestroy();
398 	return gbRunGameResult;
399 }
400 
401 /**
402  * @brief Save game configurations to ini file
403  */
SaveOptions()404 static void SaveOptions()
405 {
406 	setIniInt("Diablo", "Intro", sgOptions.Diablo.bIntro);
407 	setIniInt("Hellfire", "Intro", sgOptions.Hellfire.bIntro);
408 	setIniValue("Hellfire", "SItem", sgOptions.Hellfire.szItem);
409 
410 	setIniInt("Audio", "Sound Volume", sgOptions.Audio.nSoundVolume);
411 	setIniInt("Audio", "Music Volume", sgOptions.Audio.nMusicVolume);
412 	setIniInt("Audio", "Walking Sound", sgOptions.Audio.bWalkingSound);
413 	setIniInt("Audio", "Auto Equip Sound", sgOptions.Audio.bAutoEquipSound);
414 
415 #ifndef __vita__
416 	setIniInt("Graphics", "Width", sgOptions.Graphics.nWidth);
417 	setIniInt("Graphics", "Height", sgOptions.Graphics.nHeight);
418 #endif
419 	setIniInt("Graphics", "Fullscreen", sgOptions.Graphics.bFullscreen);
420 #ifndef __vita__
421 	setIniInt("Graphics", "Upscale", sgOptions.Graphics.bUpscale);
422 #endif
423 	setIniInt("Graphics", "Fit to Screen", sgOptions.Graphics.bFitToScreen);
424 	setIniValue("Graphics", "Scaling Quality", sgOptions.Graphics.szScaleQuality);
425 	setIniInt("Graphics", "Integer Scaling", sgOptions.Graphics.bIntegerScaling);
426 	setIniInt("Graphics", "Vertical Sync", sgOptions.Graphics.bVSync);
427 	setIniInt("Graphics", "Blended Transparency", sgOptions.Graphics.bBlendedTransparancy);
428 	setIniInt("Graphics", "Gamma Correction", sgOptions.Graphics.nGammaCorrection);
429 	setIniInt("Graphics", "Color Cycling", sgOptions.Graphics.bColorCycling);
430 	setIniInt("Graphics", "FPS Limiter", sgOptions.Graphics.bFPSLimit);
431 
432 	setIniInt("Game", "Speed", sgOptions.Gameplay.nTickRate);
433 	setIniInt("Game", "Run in Town", sgOptions.Gameplay.bRunInTown);
434 	setIniInt("Game", "Grab Input", sgOptions.Gameplay.bGrabInput);
435 	setIniInt("Game", "Theo Quest", sgOptions.Gameplay.bTheoQuest);
436 	setIniInt("Game", "Cow Quest", sgOptions.Gameplay.bCowQuest);
437 	setIniInt("Game", "Friendly Fire", sgOptions.Gameplay.bFriendlyFire);
438 	setIniInt("Game", "Test Bard", sgOptions.Gameplay.bTestBard);
439 	setIniInt("Game", "Test Barbarian", sgOptions.Gameplay.bTestBarbarian);
440 	setIniInt("Game", "Experience Bar", sgOptions.Gameplay.bExperienceBar);
441 	setIniInt("Game", "Enemy Health Bar", sgOptions.Gameplay.bEnemyHealthBar);
442 	setIniInt("Game", "Auto Gold Pickup", sgOptions.Gameplay.bAutoGoldPickup);
443 	setIniInt("Game", "Adria Refills Mana", sgOptions.Gameplay.bAdriaRefillsMana);
444 	setIniInt("Game", "Auto Equip Weapons", sgOptions.Gameplay.bAutoEquipWeapons);
445 	setIniInt("Game", "Auto Equip Armor", sgOptions.Gameplay.bAutoEquipArmor);
446 	setIniInt("Game", "Auto Equip Helms", sgOptions.Gameplay.bAutoEquipHelms);
447 	setIniInt("Game", "Auto Equip Shields", sgOptions.Gameplay.bAutoEquipShields);
448 	setIniInt("Game", "Auto Equip Jewelry", sgOptions.Gameplay.bAutoEquipJewelry);
449 	setIniInt("Game", "Randomize Quests", sgOptions.Gameplay.bRandomizeQuests);
450 	setIniInt("Game", "Show Monster Type", sgOptions.Gameplay.bShowMonsterType);
451 
452 	setIniValue("Network", "Bind Address", sgOptions.Network.szBindAddress);
453 	setIniInt("Network", "Port", sgOptions.Network.nPort);
454 	setIniValue("Network", "Previous Host", sgOptions.Network.szPreviousHost);
455 
456 	for (int i = 0; i < sizeof(spszMsgTbl) / sizeof(spszMsgTbl[0]); i++)
457 		setIniValue("NetMsg", spszMsgHotKeyTbl[i], sgOptions.Chat.szHotKeyMsgs[i]);
458 
459 	setIniValue("Controller", "Mapping", sgOptions.Controller.szMapping);
460 	setIniInt("Controller", "Swap Shoulder Button Mode", sgOptions.Controller.bSwapShoulderButtonMode);
461 	setIniInt("Controller", "Dpad Hotkeys", sgOptions.Controller.bDpadHotkeys);
462 #ifdef __vita__
463 	setIniInt("Controller", "Enable Rear Touchpad", sgOptions.Controller.bRearTouch);
464 #endif
465 
466 	SaveIni();
467 }
468 
469 /**
470  * @brief Load game configurations from ini file
471  */
LoadOptions()472 static void LoadOptions()
473 {
474 	sgOptions.Diablo.bIntro = getIniBool("Diablo", "Intro", true);
475 	sgOptions.Hellfire.bIntro = getIniBool("Hellfire", "Intro", true);
476 	getIniValue("Hellfire", "SItem", sgOptions.Hellfire.szItem, sizeof(sgOptions.Hellfire.szItem), "");
477 
478 	sgOptions.Audio.nSoundVolume = getIniInt("Audio", "Sound Volume", VOLUME_MAX);
479 	sgOptions.Audio.nMusicVolume = getIniInt("Audio", "Music Volume", VOLUME_MAX);
480 	sgOptions.Audio.bWalkingSound = getIniBool("Audio", "Walking Sound", true);
481 	sgOptions.Audio.bAutoEquipSound = getIniBool("Audio", "Auto Equip Sound", false);
482 
483 #ifndef __vita__
484 	sgOptions.Graphics.nWidth = getIniInt("Graphics", "Width", DEFAULT_WIDTH);
485 	sgOptions.Graphics.nHeight = getIniInt("Graphics", "Height", DEFAULT_HEIGHT);
486 #else
487 	sgOptions.Graphics.nWidth = DEFAULT_WIDTH;
488 	sgOptions.Graphics.nHeight = DEFAULT_HEIGHT;
489 #endif
490 	sgOptions.Graphics.bFullscreen = getIniBool("Graphics", "Fullscreen", true);
491 #if !defined(USE_SDL1) && !defined(__vita__)
492 	sgOptions.Graphics.bUpscale = getIniBool("Graphics", "Upscale", true);
493 #else
494 	sgOptions.Graphics.bUpscale = false;
495 #endif
496 	sgOptions.Graphics.bFitToScreen = getIniBool("Graphics", "Fit to Screen", true);
497 	getIniValue("Graphics", "Scaling Quality", sgOptions.Graphics.szScaleQuality, sizeof(sgOptions.Graphics.szScaleQuality), "2");
498 	sgOptions.Graphics.bIntegerScaling = getIniBool("Graphics", "Integer Scaling", false);
499 	sgOptions.Graphics.bVSync = getIniBool("Graphics", "Vertical Sync", true);
500 	sgOptions.Graphics.bBlendedTransparancy = getIniBool("Graphics", "Blended Transparency", true);
501 	sgOptions.Graphics.nGammaCorrection = getIniInt("Graphics", "Gamma Correction", 100);
502 	sgOptions.Graphics.bColorCycling = getIniBool("Graphics", "Color Cycling", true);
503 	sgOptions.Graphics.bFPSLimit = getIniBool("Graphics", "FPS Limiter", true);
504 
505 	sgOptions.Gameplay.nTickRate = getIniInt("Game", "Speed", 20);
506 	sgOptions.Gameplay.bRunInTown = getIniBool("Game", "Run in Town", false);
507 	sgOptions.Gameplay.bGrabInput = getIniBool("Game", "Grab Input", false);
508 	sgOptions.Gameplay.bTheoQuest = getIniBool("Game", "Theo Quest", false);
509 	sgOptions.Gameplay.bCowQuest = getIniBool("Game", "Cow Quest", false);
510 	sgOptions.Gameplay.bFriendlyFire = getIniBool("Game", "Friendly Fire", true);
511 	sgOptions.Gameplay.bTestBard = getIniBool("Game", "Test Bard", false);
512 	sgOptions.Gameplay.bTestBarbarian = getIniBool("Game", "Test Barbarian", false);
513 	sgOptions.Gameplay.bExperienceBar = getIniBool("Game", "Experience Bar", false);
514 	sgOptions.Gameplay.bEnemyHealthBar = getIniBool("Game", "Enemy Health Bar", false);
515 	sgOptions.Gameplay.bAutoGoldPickup = getIniBool("Game", "Auto Gold Pickup", false);
516 	sgOptions.Gameplay.bAdriaRefillsMana = getIniBool("Game", "Adria Refills Mana", false);
517 	sgOptions.Gameplay.bAutoEquipWeapons = getIniBool("Game", "Auto Equip Weapons", true);
518 	sgOptions.Gameplay.bAutoEquipArmor = getIniBool("Game", "Auto Equip Armor", false);
519 	sgOptions.Gameplay.bAutoEquipHelms = getIniBool("Game", "Auto Equip Helms", false);
520 	sgOptions.Gameplay.bAutoEquipShields = getIniBool("Game", "Auto Equip Shields", false);
521 	sgOptions.Gameplay.bAutoEquipJewelry = getIniBool("Game", "Auto Equip Jewelry", false);
522 	sgOptions.Gameplay.bRandomizeQuests = getIniBool("Game", "Randomize Quests", true);
523 	sgOptions.Gameplay.bShowMonsterType = getIniBool("Game", "Show Monster Type", false);
524 
525 	getIniValue("Network", "Bind Address", sgOptions.Network.szBindAddress, sizeof(sgOptions.Network.szBindAddress), "0.0.0.0");
526 	sgOptions.Network.nPort = getIniInt("Network", "Port", 6112);
527 	getIniValue("Network", "Previous Host", sgOptions.Network.szPreviousHost, sizeof(sgOptions.Network.szPreviousHost), "");
528 
529 	for (int i = 0; i < sizeof(spszMsgTbl) / sizeof(spszMsgTbl[0]); i++)
530 		getIniValue("NetMsg", spszMsgHotKeyTbl[i], sgOptions.Chat.szHotKeyMsgs[i], MAX_SEND_STR_LEN, spszMsgTbl[i]);
531 
532 	getIniValue("Controller", "Mapping", sgOptions.Controller.szMapping, sizeof(sgOptions.Controller.szMapping), "");
533 	sgOptions.Controller.bSwapShoulderButtonMode = getIniBool("Controller", "Swap Shoulder Button Mode", false);
534 	sgOptions.Controller.bDpadHotkeys = getIniBool("Controller", "Dpad Hotkeys", false);
535 #ifdef __vita__
536 	sgOptions.Controller.bRearTouch = getIniBool("Controller", "Enable Rear Touchpad", true);
537 #endif
538 
539 	sbWasOptionsLoaded = true;
540 }
541 
diablo_init_screen()542 static void diablo_init_screen()
543 {
544 	MouseX = gnScreenWidth / 2;
545 	MouseY = gnScreenHeight / 2;
546 	if (!sgbControllerActive)
547 		SetCursorPos(MouseX, MouseY);
548 	ScrollInfo._sdx = 0;
549 	ScrollInfo._sdy = 0;
550 	ScrollInfo._sxoff = 0;
551 	ScrollInfo._syoff = 0;
552 	ScrollInfo._sdir = SDIR_NONE;
553 
554 	ClrDiabloMsg();
555 }
556 
diablo_init()557 static void diablo_init()
558 {
559 	init_create_window();
560 	was_window_init = true;
561 
562 	SFileEnableDirectAccess(TRUE);
563 	init_archives();
564 	was_archives_init = true;
565 
566 	if (forceSpawn)
567 		gbIsSpawn = true;
568 	if (forceDiablo)
569 		gbIsHellfire = false;
570 
571 	gbIsHellfireSaveGame = gbIsHellfire;
572 
573 	UiInitialize();
574 	UiSetSpawned(gbIsSpawn);
575 	was_ui_init = true;
576 
577 	ReadOnlyTest();
578 
579 	InitHash();
580 
581 	diablo_init_screen();
582 
583 	snd_init();
584 	was_snd_init = true;
585 
586 	ui_sound_init();
587 }
588 
diablo_splash()589 static void diablo_splash()
590 {
591 	if (!gbShowIntro)
592 		return;
593 
594 	play_movie("gendata\\logo.smk", TRUE);
595 
596 	if (gbIsHellfire && sgOptions.Hellfire.bIntro) {
597 		play_movie("gendata\\Hellfire.smk", TRUE);
598 		sgOptions.Hellfire.bIntro = false;
599 	}
600 	if (!gbIsHellfire && !gbIsSpawn && sgOptions.Diablo.bIntro) {
601 		play_movie("gendata\\diablo1.smk", TRUE);
602 		sgOptions.Diablo.bIntro = false;
603 	}
604 
605 	UiTitleDialog();
606 }
607 
diablo_deinit()608 static void diablo_deinit()
609 {
610 	if (sbWasOptionsLoaded)
611 		SaveOptions();
612 	if (was_snd_init) {
613 		effects_cleanup_sfx();
614 	}
615 	if (was_ui_init)
616 		UiDestroy();
617 	if (was_archives_init)
618 		init_cleanup();
619 	if (was_window_init)
620 		dx_cleanup(); // Cleanup SDL surfaces stuff, so we have to do it before SDL_Quit().
621 	if (was_fonts_init)
622 		FontsCleanup();
623 	if (SDL_WasInit(SDL_INIT_EVERYTHING & ~SDL_INIT_HAPTIC))
624 		SDL_Quit();
625 }
626 
diablo_quit(int exitStatus)627 void diablo_quit(int exitStatus)
628 {
629 	diablo_deinit();
630 	exit(exitStatus);
631 }
632 
DiabloMain(int argc,char ** argv)633 int DiabloMain(int argc, char **argv)
634 {
635 	diablo_parse_flags(argc, argv);
636 	LoadOptions();
637 	diablo_init();
638 	diablo_splash();
639 	mainmenu_loop();
640 	diablo_deinit();
641 
642 	return 0;
643 }
644 
LeftMouseCmd(BOOL bShift)645 static BOOL LeftMouseCmd(BOOL bShift)
646 {
647 	BOOL bNear;
648 
649 	assert(MouseY < PANEL_TOP || MouseX < PANEL_LEFT || MouseX >= PANEL_LEFT + PANEL_WIDTH);
650 
651 	if (leveltype == DTYPE_TOWN) {
652 		if (pcursitem != -1 && pcurs == CURSOR_HAND)
653 			NetSendCmdLocParam1(TRUE, invflag ? CMD_GOTOGETITEM : CMD_GOTOAGETITEM, cursmx, cursmy, pcursitem);
654 		if (pcursmonst != -1)
655 			NetSendCmdLocParam1(TRUE, CMD_TALKXY, cursmx, cursmy, pcursmonst);
656 		if (pcursitem == -1 && pcursmonst == -1 && pcursplr == -1)
657 			return TRUE;
658 	} else {
659 		bNear = abs(plr[myplr]._px - cursmx) < 2 && abs(plr[myplr]._py - cursmy) < 2;
660 		if (pcursitem != -1 && pcurs == CURSOR_HAND && !bShift) {
661 			NetSendCmdLocParam1(TRUE, invflag ? CMD_GOTOGETITEM : CMD_GOTOAGETITEM, cursmx, cursmy, pcursitem);
662 		} else if (pcursobj != -1 && (!bShift || (bNear && object[pcursobj]._oBreak == 1))) {
663 			NetSendCmdLocParam1(TRUE, pcurs == CURSOR_DISARM ? CMD_DISARMXY : CMD_OPOBJXY, cursmx, cursmy, pcursobj);
664 		} else if (plr[myplr]._pwtype == WT_RANGED) {
665 			if (bShift) {
666 				NetSendCmdLoc(TRUE, CMD_RATTACKXY, cursmx, cursmy);
667 			} else if (pcursmonst != -1) {
668 				if (CanTalkToMonst(pcursmonst)) {
669 					NetSendCmdParam1(TRUE, CMD_ATTACKID, pcursmonst);
670 				} else {
671 					NetSendCmdParam1(TRUE, CMD_RATTACKID, pcursmonst);
672 				}
673 			} else if (pcursplr != -1 && !gbFriendlyMode) {
674 				NetSendCmdParam1(TRUE, CMD_RATTACKPID, pcursplr);
675 			}
676 		} else {
677 			if (bShift) {
678 				if (pcursmonst != -1) {
679 					if (CanTalkToMonst(pcursmonst)) {
680 						NetSendCmdParam1(TRUE, CMD_ATTACKID, pcursmonst);
681 					} else {
682 						NetSendCmdLoc(TRUE, CMD_SATTACKXY, cursmx, cursmy);
683 					}
684 				} else {
685 					NetSendCmdLoc(TRUE, CMD_SATTACKXY, cursmx, cursmy);
686 				}
687 			} else if (pcursmonst != -1) {
688 				NetSendCmdParam1(TRUE, CMD_ATTACKID, pcursmonst);
689 			} else if (pcursplr != -1 && !gbFriendlyMode) {
690 				NetSendCmdParam1(TRUE, CMD_ATTACKPID, pcursplr);
691 			}
692 		}
693 		if (!bShift && pcursitem == -1 && pcursobj == -1 && pcursmonst == -1 && pcursplr == -1)
694 			return TRUE;
695 	}
696 
697 	return FALSE;
698 }
699 
TryIconCurs()700 BOOL TryIconCurs()
701 {
702 	if (pcurs == CURSOR_RESURRECT) {
703 		NetSendCmdParam1(TRUE, CMD_RESURRECT, pcursplr);
704 		return TRUE;
705 	}
706 
707 	if (pcurs == CURSOR_HEALOTHER) {
708 		NetSendCmdParam1(TRUE, CMD_HEALOTHER, pcursplr);
709 		return TRUE;
710 	}
711 
712 	if (pcurs == CURSOR_TELEKINESIS) {
713 		DoTelekinesis();
714 		return TRUE;
715 	}
716 
717 	if (pcurs == CURSOR_IDENTIFY) {
718 		if (pcursinvitem != -1)
719 			CheckIdentify(myplr, pcursinvitem);
720 		else
721 			NewCursor(CURSOR_HAND);
722 		return TRUE;
723 	}
724 
725 	if (pcurs == CURSOR_REPAIR) {
726 		if (pcursinvitem != -1)
727 			DoRepair(myplr, pcursinvitem);
728 		else
729 			NewCursor(CURSOR_HAND);
730 		return TRUE;
731 	}
732 
733 	if (pcurs == CURSOR_RECHARGE) {
734 		if (pcursinvitem != -1)
735 			DoRecharge(myplr, pcursinvitem);
736 		else
737 			NewCursor(CURSOR_HAND);
738 		return TRUE;
739 	}
740 
741 	if (pcurs == CURSOR_OIL) {
742 		if (pcursinvitem != -1)
743 			DoOil(myplr, pcursinvitem);
744 		else
745 			NewCursor(CURSOR_HAND);
746 		return TRUE;
747 	}
748 
749 	if (pcurs == CURSOR_TELEPORT) {
750 		if (pcursmonst != -1)
751 			NetSendCmdParam3(TRUE, CMD_TSPELLID, pcursmonst, plr[myplr]._pTSpell, GetSpellLevel(myplr, plr[myplr]._pTSpell));
752 		else if (pcursplr != -1)
753 			NetSendCmdParam3(TRUE, CMD_TSPELLPID, pcursplr, plr[myplr]._pTSpell, GetSpellLevel(myplr, plr[myplr]._pTSpell));
754 		else
755 			NetSendCmdLocParam2(TRUE, CMD_TSPELLXY, cursmx, cursmy, plr[myplr]._pTSpell, GetSpellLevel(myplr, plr[myplr]._pTSpell));
756 		NewCursor(CURSOR_HAND);
757 		return TRUE;
758 	}
759 
760 	if (pcurs == CURSOR_DISARM && pcursobj == -1) {
761 		NewCursor(CURSOR_HAND);
762 		return TRUE;
763 	}
764 
765 	return FALSE;
766 }
767 
LeftMouseDown(int wParam)768 static BOOL LeftMouseDown(int wParam)
769 {
770 	if (gmenu_left_mouse(TRUE))
771 		return FALSE;
772 
773 	if (control_check_talk_btn())
774 		return FALSE;
775 
776 	if (sgnTimeoutCurs != CURSOR_NONE)
777 		return FALSE;
778 
779 	if (deathflag) {
780 		control_check_btn_press();
781 		return FALSE;
782 	}
783 
784 	if (PauseMode == 2) {
785 		return FALSE;
786 	}
787 	if (doomflag) {
788 		doom_close();
789 		return FALSE;
790 	}
791 
792 	if (spselflag) {
793 		SetSpell();
794 		return FALSE;
795 	}
796 
797 	if (stextflag != STORE_NONE) {
798 		CheckStoreBtn();
799 		return FALSE;
800 	}
801 
802 	bool isShiftHeld = wParam & DVL_MK_SHIFT;
803 
804 	if (MouseY < PANEL_TOP || MouseX < PANEL_LEFT || MouseX >= PANEL_LEFT + PANEL_WIDTH) {
805 		if (!gmenu_is_active() && !TryIconCurs()) {
806 			if (questlog && MouseX > 32 && MouseX < 288 && MouseY > 32 && MouseY < 308) {
807 				QuestlogESC();
808 			} else if (qtextflag) {
809 				qtextflag = FALSE;
810 				stream_stop();
811 			} else if (chrflag && MouseX < SPANEL_WIDTH && MouseY < SPANEL_HEIGHT) {
812 				CheckChrBtns();
813 			} else if (invflag && MouseX > RIGHT_PANEL && MouseY < SPANEL_HEIGHT) {
814 				if (!dropGoldFlag)
815 					CheckInvItem(isShiftHeld);
816 			} else if (sbookflag && MouseX > RIGHT_PANEL && MouseY < SPANEL_HEIGHT) {
817 				CheckSBook();
818 			} else if (pcurs >= CURSOR_FIRSTITEM) {
819 				if (TryInvPut()) {
820 					NetSendCmdPItem(TRUE, CMD_PUTITEM, cursmx, cursmy);
821 					NewCursor(CURSOR_HAND);
822 				}
823 			} else {
824 				if (plr[myplr]._pStatPts != 0 && !spselflag)
825 					CheckLvlBtn();
826 				if (!lvlbtndown)
827 					return LeftMouseCmd(isShiftHeld);
828 			}
829 		}
830 	} else {
831 		if (!talkflag && !dropGoldFlag && !gmenu_is_active())
832 			CheckInvScrn(isShiftHeld);
833 		DoPanBtn();
834 		if (pcurs > CURSOR_HAND && pcurs < CURSOR_FIRSTITEM)
835 			NewCursor(CURSOR_HAND);
836 	}
837 
838 	return FALSE;
839 }
840 
LeftMouseUp(int wParam)841 static void LeftMouseUp(int wParam)
842 {
843 	gmenu_left_mouse(FALSE);
844 	control_release_talk_btn();
845 	bool isShiftHeld = wParam & (DVL_MK_SHIFT | DVL_MK_LBUTTON);
846 	if (panbtndown)
847 		CheckBtnUp();
848 	if (chrbtnactive)
849 		ReleaseChrBtns(isShiftHeld);
850 	if (lvlbtndown)
851 		ReleaseLvlBtn();
852 	if (stextflag != STORE_NONE)
853 		ReleaseStoreBtn();
854 }
855 
RightMouseDown()856 static void RightMouseDown()
857 {
858 	if (!gmenu_is_active() && sgnTimeoutCurs == CURSOR_NONE && PauseMode != 2 && !plr[myplr]._pInvincible) {
859 		if (doomflag) {
860 			doom_close();
861 		} else if (stextflag == STORE_NONE) {
862 			if (spselflag) {
863 				SetSpell();
864 			} else if (MouseY >= SPANEL_HEIGHT
865 			    || (!sbookflag || MouseX <= RIGHT_PANEL)
866 			        && !TryIconCurs()
867 			        && (pcursinvitem == -1 || !UseInvItem(myplr, pcursinvitem))) {
868 				if (pcurs == CURSOR_HAND) {
869 					if (pcursinvitem == -1 || !UseInvItem(myplr, pcursinvitem))
870 						CheckPlrSpell();
871 				} else if (pcurs > CURSOR_HAND && pcurs < CURSOR_FIRSTITEM) {
872 					NewCursor(CURSOR_HAND);
873 				}
874 			}
875 		}
876 	}
877 }
878 
diablo_pause_game()879 void diablo_pause_game()
880 {
881 	if (!gbIsMultiplayer) {
882 		if (PauseMode) {
883 			PauseMode = 0;
884 		} else {
885 			PauseMode = 2;
886 			sound_stop();
887 			track_repeat_walk(FALSE);
888 		}
889 		force_redraw = 255;
890 	}
891 }
892 
diablo_hotkey_msg(DWORD dwMsg)893 static void diablo_hotkey_msg(DWORD dwMsg)
894 {
895 	if (!gbIsMultiplayer) {
896 		return;
897 	}
898 
899 	assert(dwMsg < sizeof(spszMsgTbl) / sizeof(spszMsgTbl[0]));
900 
901 	NetSendCmdString(-1, sgOptions.Chat.szHotKeyMsgs[dwMsg]);
902 }
903 
PressSysKey(int wParam)904 static BOOL PressSysKey(int wParam)
905 {
906 	if (gmenu_is_active() || wParam != DVL_VK_F10)
907 		return FALSE;
908 	diablo_hotkey_msg(1);
909 	return TRUE;
910 }
911 
ReleaseKey(int vkey)912 static void ReleaseKey(int vkey)
913 {
914 	if (vkey == DVL_VK_SNAPSHOT)
915 		CaptureScreen();
916 }
917 
ClosePanels()918 static void ClosePanels()
919 {
920 	if (PANELS_COVER) {
921 		if (!chrflag && !questlog && (invflag || sbookflag) && MouseX < 480 && MouseY < PANEL_TOP) {
922 			SetCursorPos(MouseX + 160, MouseY);
923 		} else if (!invflag && !sbookflag && (chrflag || questlog) && MouseX > 160 && MouseY < PANEL_TOP) {
924 			SetCursorPos(MouseX - 160, MouseY);
925 		}
926 	}
927 	invflag = FALSE;
928 	chrflag = FALSE;
929 	sbookflag = FALSE;
930 	questlog = FALSE;
931 }
932 
PressEscKey()933 bool PressEscKey()
934 {
935 	bool rv = false;
936 
937 	if (doomflag) {
938 		doom_close();
939 		rv = true;
940 	}
941 
942 	if (helpflag) {
943 		helpflag = FALSE;
944 		rv = true;
945 	}
946 
947 	if (qtextflag) {
948 		qtextflag = FALSE;
949 		stream_stop();
950 		rv = true;
951 	}
952 
953 	if (stextflag) {
954 		STextESC();
955 		rv = true;
956 	}
957 
958 	if (msgflag) {
959 		msgdelay = 0;
960 		rv = true;
961 	}
962 
963 	if (talkflag) {
964 		control_reset_talk();
965 		rv = true;
966 	}
967 
968 	if (dropGoldFlag) {
969 		control_drop_gold(DVL_VK_ESCAPE);
970 		rv = true;
971 	}
972 
973 	if (spselflag) {
974 		spselflag = FALSE;
975 		rv = true;
976 	}
977 
978 	if (invflag || chrflag || sbookflag || questlog) {
979 		ClosePanels();
980 		rv = true;
981 	}
982 
983 	return rv;
984 }
985 
PressKey(int vkey)986 static void PressKey(int vkey)
987 {
988 	if (gmenu_presskeys(vkey) || control_presskeys(vkey)) {
989 		return;
990 	}
991 
992 	if (deathflag) {
993 		if (sgnTimeoutCurs != CURSOR_NONE) {
994 			return;
995 		}
996 		if (vkey == DVL_VK_F9) {
997 			diablo_hotkey_msg(0);
998 		}
999 		if (vkey == DVL_VK_F10) {
1000 			diablo_hotkey_msg(1);
1001 		}
1002 		if (vkey == DVL_VK_F11) {
1003 			diablo_hotkey_msg(2);
1004 		}
1005 		if (vkey == DVL_VK_F12) {
1006 			diablo_hotkey_msg(3);
1007 		}
1008 		if (vkey == DVL_VK_RETURN) {
1009 			if (GetAsyncKeyState(DVL_VK_MENU) & 0x8000)
1010 				dx_reinit();
1011 			else
1012 				control_type_message();
1013 		}
1014 		if (vkey != DVL_VK_ESCAPE) {
1015 			return;
1016 		}
1017 	}
1018 	if (vkey == DVL_VK_ESCAPE) {
1019 		if (!PressEscKey()) {
1020 			track_repeat_walk(FALSE);
1021 			gamemenu_on();
1022 		}
1023 		return;
1024 	}
1025 
1026 	if (sgnTimeoutCurs != CURSOR_NONE || dropGoldFlag) {
1027 		return;
1028 	}
1029 	if (vkey == DVL_VK_PAUSE) {
1030 		diablo_pause_game();
1031 		return;
1032 	}
1033 	if (PauseMode == 2) {
1034 		if ((vkey == DVL_VK_RETURN) && (GetAsyncKeyState(DVL_VK_MENU) & 0x8000))
1035 			dx_reinit();
1036 		return;
1037 	}
1038 
1039 	if (vkey == DVL_VK_RETURN) {
1040 		if (GetAsyncKeyState(DVL_VK_MENU) & 0x8000) {
1041 			dx_reinit();
1042 		} else if (stextflag) {
1043 			STextEnter();
1044 		} else if (questlog) {
1045 			QuestlogEnter();
1046 		} else {
1047 			control_type_message();
1048 		}
1049 	} else if (vkey == DVL_VK_F1) {
1050 		if (helpflag) {
1051 			helpflag = FALSE;
1052 		} else if (stextflag != STORE_NONE) {
1053 			ClearPanel();
1054 			AddPanelString("No help available", TRUE); /// BUGFIX: message isn't displayed
1055 			AddPanelString("while in stores", TRUE);
1056 			track_repeat_walk(FALSE);
1057 		} else {
1058 			invflag = FALSE;
1059 			chrflag = FALSE;
1060 			sbookflag = FALSE;
1061 			spselflag = FALSE;
1062 			if (qtextflag && leveltype == DTYPE_TOWN) {
1063 				qtextflag = FALSE;
1064 				stream_stop();
1065 			}
1066 			questlog = FALSE;
1067 			automapflag = FALSE;
1068 			msgdelay = 0;
1069 			gamemenu_off();
1070 			DisplayHelp();
1071 			doom_close();
1072 		}
1073 	}
1074 #ifdef _DEBUG
1075 	else if (vkey == DVL_VK_F2) {
1076 	}
1077 #endif
1078 #ifdef _DEBUG
1079 	else if (vkey == DVL_VK_F3) {
1080 		if (pcursitem != -1) {
1081 			sprintf(
1082 			    tempstr,
1083 			    "IDX = %i  :  Seed = %i  :  CF = %i",
1084 			    item[pcursitem].IDidx,
1085 			    item[pcursitem]._iSeed,
1086 			    item[pcursitem]._iCreateInfo);
1087 			NetSendCmdString(1 << myplr, tempstr);
1088 		}
1089 		sprintf(tempstr, "Numitems : %i", numitems);
1090 		NetSendCmdString(1 << myplr, tempstr);
1091 	}
1092 #endif
1093 #ifdef _DEBUG
1094 	else if (vkey == DVL_VK_F4) {
1095 		PrintDebugQuest();
1096 	}
1097 #endif
1098 	else if (vkey == DVL_VK_F5) {
1099 		if (spselflag) {
1100 			SetSpeedSpell(0);
1101 			return;
1102 		}
1103 		ToggleSpell(0);
1104 		return;
1105 	} else if (vkey == DVL_VK_F6) {
1106 		if (spselflag) {
1107 			SetSpeedSpell(1);
1108 			return;
1109 		}
1110 		ToggleSpell(1);
1111 		return;
1112 	} else if (vkey == DVL_VK_F7) {
1113 		if (spselflag) {
1114 			SetSpeedSpell(2);
1115 			return;
1116 		}
1117 		ToggleSpell(2);
1118 		return;
1119 	} else if (vkey == DVL_VK_F8) {
1120 		if (spselflag) {
1121 			SetSpeedSpell(3);
1122 			return;
1123 		}
1124 		ToggleSpell(3);
1125 		return;
1126 	} else if (vkey == DVL_VK_F9) {
1127 		diablo_hotkey_msg(0);
1128 	} else if (vkey == DVL_VK_F10) {
1129 		diablo_hotkey_msg(1);
1130 	} else if (vkey == DVL_VK_F11) {
1131 		diablo_hotkey_msg(2);
1132 	} else if (vkey == DVL_VK_F12) {
1133 		diablo_hotkey_msg(3);
1134 	} else if (vkey == DVL_VK_UP) {
1135 		if (stextflag) {
1136 			STextUp();
1137 		} else if (questlog) {
1138 			QuestlogUp();
1139 		} else if (helpflag) {
1140 			HelpScrollUp();
1141 		} else if (automapflag) {
1142 			AutomapUp();
1143 		}
1144 	} else if (vkey == DVL_VK_DOWN) {
1145 		if (stextflag) {
1146 			STextDown();
1147 		} else if (questlog) {
1148 			QuestlogDown();
1149 		} else if (helpflag) {
1150 			HelpScrollDown();
1151 		} else if (automapflag) {
1152 			AutomapDown();
1153 		}
1154 	} else if (vkey == DVL_VK_PRIOR) {
1155 		if (stextflag) {
1156 			STextPrior();
1157 		}
1158 	} else if (vkey == DVL_VK_NEXT) {
1159 		if (stextflag) {
1160 			STextNext();
1161 		}
1162 	} else if (vkey == DVL_VK_LEFT) {
1163 		if (automapflag && !talkflag) {
1164 			AutomapLeft();
1165 		}
1166 	} else if (vkey == DVL_VK_RIGHT) {
1167 		if (automapflag && !talkflag) {
1168 			AutomapRight();
1169 		}
1170 	} else if (vkey == DVL_VK_TAB) {
1171 		DoAutoMap();
1172 	} else if (vkey == DVL_VK_SPACE) {
1173 		ClosePanels();
1174 		helpflag = FALSE;
1175 		spselflag = FALSE;
1176 		if (qtextflag && leveltype == DTYPE_TOWN) {
1177 			qtextflag = FALSE;
1178 			stream_stop();
1179 		}
1180 		automapflag = FALSE;
1181 		msgdelay = 0;
1182 		gamemenu_off();
1183 		doom_close();
1184 	}
1185 }
1186 
1187 /**
1188  * @internal `return` must be used instead of `break` to be bin exact as C++
1189  */
PressChar(WPARAM vkey)1190 static void PressChar(WPARAM vkey)
1191 {
1192 	if (gmenu_is_active() || control_talk_last_key(vkey) || sgnTimeoutCurs != CURSOR_NONE || deathflag) {
1193 		return;
1194 	}
1195 	if ((char)vkey == 'p' || (char)vkey == 'P') {
1196 		diablo_pause_game();
1197 		return;
1198 	}
1199 	if (PauseMode == 2) {
1200 		return;
1201 	}
1202 	if (doomflag) {
1203 		doom_close();
1204 		return;
1205 	}
1206 	if (dropGoldFlag) {
1207 		control_drop_gold(vkey);
1208 		return;
1209 	}
1210 
1211 	switch (vkey) {
1212 	case 'G':
1213 	case 'g':
1214 		DecreaseGamma();
1215 		return;
1216 	case 'F':
1217 	case 'f':
1218 		IncreaseGamma();
1219 		return;
1220 	case 'I':
1221 	case 'i':
1222 		if (stextflag == STORE_NONE) {
1223 			invflag = !invflag;
1224 			if (!chrflag && !questlog && PANELS_COVER) {
1225 				if (!invflag) { // We closed the invetory
1226 					if (MouseX < 480 && MouseY < PANEL_TOP) {
1227 						SetCursorPos(MouseX + 160, MouseY);
1228 					}
1229 				} else if (!sbookflag) { // We opened the invetory
1230 					if (MouseX > 160 && MouseY < PANEL_TOP) {
1231 						SetCursorPos(MouseX - 160, MouseY);
1232 					}
1233 				}
1234 			}
1235 			sbookflag = FALSE;
1236 		}
1237 		return;
1238 	case 'C':
1239 	case 'c':
1240 		if (stextflag == STORE_NONE) {
1241 			chrflag = !chrflag;
1242 			if (!invflag && !sbookflag && PANELS_COVER) {
1243 				if (!chrflag) { // We closed the character sheet
1244 					if (MouseX > 160 && MouseY < PANEL_TOP) {
1245 						SetCursorPos(MouseX - 160, MouseY);
1246 					}
1247 				} else if (!questlog) { // We opened the character sheet
1248 					if (MouseX < 480 && MouseY < PANEL_TOP) {
1249 						SetCursorPos(MouseX + 160, MouseY);
1250 					}
1251 				}
1252 			}
1253 			questlog = FALSE;
1254 		}
1255 		return;
1256 	case 'Q':
1257 	case 'q':
1258 		if (stextflag == STORE_NONE) {
1259 			if (!questlog) {
1260 				StartQuestlog();
1261 			} else {
1262 				questlog = FALSE;
1263 			}
1264 			if (!invflag && !sbookflag && PANELS_COVER) {
1265 				if (!questlog) { // We closed the quest log
1266 					if (MouseX > 160 && MouseY < PANEL_TOP) {
1267 						SetCursorPos(MouseX - 160, MouseY);
1268 					}
1269 				} else if (!chrflag) { // We opened the character quest log
1270 					if (MouseX < 480 && MouseY < PANEL_TOP) {
1271 						SetCursorPos(MouseX + 160, MouseY);
1272 					}
1273 				}
1274 			}
1275 			chrflag = FALSE;
1276 		}
1277 		return;
1278 	case 'Z':
1279 	case 'z':
1280 		zoomflag = !zoomflag;
1281 		CalcViewportGeometry();
1282 		return;
1283 	case 'S':
1284 	case 's':
1285 		if (stextflag == STORE_NONE) {
1286 			chrflag = FALSE;
1287 			questlog = FALSE;
1288 			invflag = FALSE;
1289 			sbookflag = FALSE;
1290 			if (!spselflag) {
1291 				DoSpeedBook();
1292 			} else {
1293 				spselflag = FALSE;
1294 			}
1295 			track_repeat_walk(FALSE);
1296 		}
1297 		return;
1298 	case 'B':
1299 	case 'b':
1300 		if (stextflag == STORE_NONE) {
1301 			sbookflag = !sbookflag;
1302 			if (!chrflag && !questlog && PANELS_COVER) {
1303 				if (!sbookflag) { // We closed the invetory
1304 					if (MouseX < 480 && MouseY < PANEL_TOP) {
1305 						SetCursorPos(MouseX + 160, MouseY);
1306 					}
1307 				} else if (!invflag) { // We opened the invetory
1308 					if (MouseX > 160 && MouseY < PANEL_TOP) {
1309 						SetCursorPos(MouseX - 160, MouseY);
1310 					}
1311 				}
1312 			}
1313 			invflag = FALSE;
1314 		}
1315 		return;
1316 	case '+':
1317 	case '=':
1318 		if (automapflag) {
1319 			AutomapZoomIn();
1320 		}
1321 		return;
1322 	case '-':
1323 	case '_':
1324 		if (automapflag) {
1325 			AutomapZoomOut();
1326 		}
1327 		return;
1328 	case 'v': {
1329 		char pszStr[120];
1330 		const char *difficulties[3] = {
1331 			"Normal",
1332 			"Nightmare",
1333 			"Hell",
1334 		};
1335 		sprintf(pszStr, "%s, mode = %s", gszProductName, difficulties[gnDifficulty]);
1336 		NetSendCmdString(1 << myplr, pszStr);
1337 		return;
1338 	}
1339 	case 'V':
1340 		NetSendCmdString(1 << myplr, gszVersionNumber);
1341 		return;
1342 	case '!':
1343 	case '1':
1344 		if (!plr[myplr].SpdList[0].isEmpty() && plr[myplr].SpdList[0]._itype != ITYPE_GOLD) {
1345 			UseInvItem(myplr, INVITEM_BELT_FIRST);
1346 		}
1347 		return;
1348 	case '@':
1349 	case '2':
1350 		if (!plr[myplr].SpdList[1].isEmpty() && plr[myplr].SpdList[1]._itype != ITYPE_GOLD) {
1351 			UseInvItem(myplr, INVITEM_BELT_FIRST + 1);
1352 		}
1353 		return;
1354 	case '#':
1355 	case '3':
1356 		if (!plr[myplr].SpdList[2].isEmpty() && plr[myplr].SpdList[2]._itype != ITYPE_GOLD) {
1357 			UseInvItem(myplr, INVITEM_BELT_FIRST + 2);
1358 		}
1359 		return;
1360 	case '$':
1361 	case '4':
1362 		if (!plr[myplr].SpdList[3].isEmpty() && plr[myplr].SpdList[3]._itype != ITYPE_GOLD) {
1363 			UseInvItem(myplr, INVITEM_BELT_FIRST + 3);
1364 		}
1365 		return;
1366 	case '%':
1367 	case '5':
1368 		if (!plr[myplr].SpdList[4].isEmpty() && plr[myplr].SpdList[4]._itype != ITYPE_GOLD) {
1369 			UseInvItem(myplr, INVITEM_BELT_FIRST + 4);
1370 		}
1371 		return;
1372 	case '^':
1373 	case '6':
1374 		if (!plr[myplr].SpdList[5].isEmpty() && plr[myplr].SpdList[5]._itype != ITYPE_GOLD) {
1375 			UseInvItem(myplr, INVITEM_BELT_FIRST + 5);
1376 		}
1377 		return;
1378 	case '&':
1379 	case '7':
1380 		if (!plr[myplr].SpdList[6].isEmpty() && plr[myplr].SpdList[6]._itype != ITYPE_GOLD) {
1381 			UseInvItem(myplr, INVITEM_BELT_FIRST + 6);
1382 		}
1383 		return;
1384 	case '*':
1385 	case '8':
1386 #ifdef _DEBUG
1387 		if (debug_mode_key_inverted_v || debug_mode_key_w) {
1388 			NetSendCmd(TRUE, CMD_CHEAT_EXPERIENCE);
1389 			return;
1390 		}
1391 #endif
1392 		if (!plr[myplr].SpdList[7].isEmpty() && plr[myplr].SpdList[7]._itype != ITYPE_GOLD) {
1393 			UseInvItem(myplr, INVITEM_BELT_FIRST + 7);
1394 		}
1395 		return;
1396 #ifdef _DEBUG
1397 	case ')':
1398 	case '0':
1399 		if (debug_mode_key_inverted_v) {
1400 			if (arrowdebug > 2) {
1401 				arrowdebug = 0;
1402 			}
1403 			if (arrowdebug == 0) {
1404 				plr[myplr]._pIFlags &= ~ISPL_FIRE_ARROWS;
1405 				plr[myplr]._pIFlags &= ~ISPL_LIGHT_ARROWS;
1406 			}
1407 			if (arrowdebug == 1) {
1408 				plr[myplr]._pIFlags |= ISPL_FIRE_ARROWS;
1409 			}
1410 			if (arrowdebug == 2) {
1411 				plr[myplr]._pIFlags |= ISPL_LIGHT_ARROWS;
1412 			}
1413 			arrowdebug++;
1414 		}
1415 		return;
1416 	case ':':
1417 		if (currlevel == 0 && debug_mode_key_w) {
1418 			SetAllSpellsCheat();
1419 		}
1420 		return;
1421 	case '[':
1422 		if (currlevel == 0 && debug_mode_key_w) {
1423 			TakeGoldCheat();
1424 		}
1425 		return;
1426 	case ']':
1427 		if (currlevel == 0 && debug_mode_key_w) {
1428 			MaxSpellsCheat();
1429 		}
1430 		return;
1431 	case 'a':
1432 		if (debug_mode_key_inverted_v) {
1433 			spelldata[SPL_TELEPORT].sTownSpell = 1;
1434 			plr[myplr]._pSplLvl[plr[myplr]._pSpell]++;
1435 		}
1436 		return;
1437 	case 'D':
1438 		PrintDebugPlayer(TRUE);
1439 		return;
1440 	case 'd':
1441 		PrintDebugPlayer(FALSE);
1442 		return;
1443 	case 'L':
1444 	case 'l':
1445 		if (debug_mode_key_inverted_v) {
1446 			ToggleLighting();
1447 		}
1448 		return;
1449 	case 'M':
1450 		NextDebugMonster();
1451 		return;
1452 	case 'm':
1453 		GetDebugMonster();
1454 		return;
1455 	case 'R':
1456 	case 'r':
1457 		sprintf(tempstr, "seed = %i", glSeedTbl[currlevel]);
1458 		NetSendCmdString(1 << myplr, tempstr);
1459 		sprintf(tempstr, "Mid1 = %i : Mid2 = %i : Mid3 = %i", glMid1Seed[currlevel], glMid2Seed[currlevel], glMid3Seed[currlevel]);
1460 		NetSendCmdString(1 << myplr, tempstr);
1461 		sprintf(tempstr, "End = %i", glEndSeed[currlevel]);
1462 		NetSendCmdString(1 << myplr, tempstr);
1463 		return;
1464 	case 'T':
1465 	case 't':
1466 		if (debug_mode_key_inverted_v) {
1467 			sprintf(tempstr, "PX = %i  PY = %i", plr[myplr]._px, plr[myplr]._py);
1468 			NetSendCmdString(1 << myplr, tempstr);
1469 			sprintf(tempstr, "CX = %i  CY = %i  DP = %i", cursmx, cursmy, dungeon[cursmx][cursmy]);
1470 			NetSendCmdString(1 << myplr, tempstr);
1471 		}
1472 		return;
1473 	case '|':
1474 		if (currlevel == 0 && debug_mode_key_w) {
1475 			GiveGoldCheat();
1476 		}
1477 		return;
1478 #endif
1479 	}
1480 }
1481 
GetMousePos(LPARAM lParam)1482 static void GetMousePos(LPARAM lParam)
1483 {
1484 	MouseX = (short)(lParam & 0xffff);
1485 	MouseY = (short)((lParam >> 16) & 0xffff);
1486 }
1487 
DisableInputWndProc(UINT uMsg,WPARAM wParam,LPARAM lParam)1488 void DisableInputWndProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
1489 {
1490 	switch (uMsg) {
1491 	case DVL_WM_KEYDOWN:
1492 	case DVL_WM_KEYUP:
1493 	case DVL_WM_CHAR:
1494 	case DVL_WM_SYSKEYDOWN:
1495 	case DVL_WM_SYSCOMMAND:
1496 		return;
1497 	case DVL_WM_MOUSEMOVE:
1498 		GetMousePos(lParam);
1499 		return;
1500 	case DVL_WM_LBUTTONDOWN:
1501 		if (sgbMouseDown != CLICK_NONE)
1502 			return;
1503 		sgbMouseDown = CLICK_LEFT;
1504 		return;
1505 	case DVL_WM_LBUTTONUP:
1506 		if (sgbMouseDown != CLICK_LEFT)
1507 			return;
1508 		sgbMouseDown = CLICK_NONE;
1509 		return;
1510 	case DVL_WM_RBUTTONDOWN:
1511 		if (sgbMouseDown != CLICK_NONE)
1512 			return;
1513 		sgbMouseDown = CLICK_RIGHT;
1514 		return;
1515 	case DVL_WM_RBUTTONUP:
1516 		if (sgbMouseDown != CLICK_RIGHT)
1517 			return;
1518 		sgbMouseDown = CLICK_NONE;
1519 		return;
1520 	case DVL_WM_CAPTURECHANGED:
1521 		sgbMouseDown = CLICK_NONE;
1522 		return;
1523 	}
1524 
1525 	MainWndProc(uMsg, wParam, lParam);
1526 }
1527 
GM_Game(UINT uMsg,WPARAM wParam,LPARAM lParam)1528 void GM_Game(UINT uMsg, WPARAM wParam, LPARAM lParam)
1529 {
1530 	switch (uMsg) {
1531 	case DVL_WM_KEYDOWN:
1532 		PressKey(wParam);
1533 		return;
1534 	case DVL_WM_KEYUP:
1535 		ReleaseKey(wParam);
1536 		return;
1537 	case DVL_WM_CHAR:
1538 		PressChar(wParam);
1539 		return;
1540 	case DVL_WM_SYSKEYDOWN:
1541 		if (PressSysKey(wParam))
1542 			return;
1543 		break;
1544 	case DVL_WM_SYSCOMMAND:
1545 		if (wParam == DVL_SC_CLOSE) {
1546 			gbRunGame = FALSE;
1547 			gbRunGameResult = FALSE;
1548 			return;
1549 		}
1550 		break;
1551 	case DVL_WM_MOUSEMOVE:
1552 		GetMousePos(lParam);
1553 		gmenu_on_mouse_move();
1554 		return;
1555 	case DVL_WM_LBUTTONDOWN:
1556 		GetMousePos(lParam);
1557 		if (sgbMouseDown == CLICK_NONE) {
1558 			sgbMouseDown = CLICK_LEFT;
1559 			track_repeat_walk(LeftMouseDown(wParam));
1560 		}
1561 		return;
1562 	case DVL_WM_LBUTTONUP:
1563 		GetMousePos(lParam);
1564 		if (sgbMouseDown == CLICK_LEFT) {
1565 			sgbMouseDown = CLICK_NONE;
1566 			LeftMouseUp(wParam);
1567 			track_repeat_walk(FALSE);
1568 		}
1569 		return;
1570 	case DVL_WM_RBUTTONDOWN:
1571 		GetMousePos(lParam);
1572 		if (sgbMouseDown == CLICK_NONE) {
1573 			sgbMouseDown = CLICK_RIGHT;
1574 			RightMouseDown();
1575 		}
1576 		return;
1577 	case DVL_WM_RBUTTONUP:
1578 		GetMousePos(lParam);
1579 		if (sgbMouseDown == CLICK_RIGHT) {
1580 			sgbMouseDown = CLICK_NONE;
1581 		}
1582 		return;
1583 	case DVL_WM_CAPTURECHANGED:
1584 		sgbMouseDown = CLICK_NONE;
1585 		track_repeat_walk(FALSE);
1586 		break;
1587 	case WM_DIABNEXTLVL:
1588 	case WM_DIABPREVLVL:
1589 	case WM_DIABRTNLVL:
1590 	case WM_DIABSETLVL:
1591 	case WM_DIABWARPLVL:
1592 	case WM_DIABTOWNWARP:
1593 	case WM_DIABTWARPUP:
1594 	case WM_DIABRETOWN:
1595 		if (gbIsMultiplayer)
1596 			pfile_write_hero();
1597 		nthread_ignore_mutex(TRUE);
1598 		PaletteFadeOut(8);
1599 		sound_stop();
1600 		music_stop();
1601 		track_repeat_walk(FALSE);
1602 		sgbMouseDown = CLICK_NONE;
1603 		ShowProgress((interface_mode)uMsg);
1604 		force_redraw = 255;
1605 		DrawAndBlit();
1606 		LoadPWaterPalette();
1607 		if (gbRunGame)
1608 			PaletteFadeIn(8);
1609 		nthread_ignore_mutex(FALSE);
1610 		gbGameLoopStartup = TRUE;
1611 		return;
1612 	}
1613 
1614 	MainWndProc(uMsg, wParam, lParam);
1615 }
1616 
LoadLvlGFX()1617 void LoadLvlGFX()
1618 {
1619 	assert(!pDungeonCels);
1620 
1621 	switch (leveltype) {
1622 	case DTYPE_TOWN:
1623 		if (gbIsHellfire) {
1624 			pDungeonCels = LoadFileInMem("NLevels\\TownData\\Town.CEL", NULL);
1625 			pMegaTiles = LoadFileInMem("NLevels\\TownData\\Town.TIL", NULL);
1626 			pLevelPieces = LoadFileInMem("NLevels\\TownData\\Town.MIN", NULL);
1627 		} else {
1628 			pDungeonCels = LoadFileInMem("Levels\\TownData\\Town.CEL", NULL);
1629 			pMegaTiles = LoadFileInMem("Levels\\TownData\\Town.TIL", NULL);
1630 			pLevelPieces = LoadFileInMem("Levels\\TownData\\Town.MIN", NULL);
1631 		}
1632 		pSpecialCels = LoadFileInMem("Levels\\TownData\\TownS.CEL", NULL);
1633 		break;
1634 	case DTYPE_CATHEDRAL:
1635 		if (currlevel < 21) {
1636 			pDungeonCels = LoadFileInMem("Levels\\L1Data\\L1.CEL", NULL);
1637 			pMegaTiles = LoadFileInMem("Levels\\L1Data\\L1.TIL", NULL);
1638 			pLevelPieces = LoadFileInMem("Levels\\L1Data\\L1.MIN", NULL);
1639 			pSpecialCels = LoadFileInMem("Levels\\L1Data\\L1S.CEL", NULL);
1640 		} else {
1641 			pDungeonCels = LoadFileInMem("NLevels\\L5Data\\L5.CEL", NULL);
1642 			pMegaTiles = LoadFileInMem("NLevels\\L5Data\\L5.TIL", NULL);
1643 			pLevelPieces = LoadFileInMem("NLevels\\L5Data\\L5.MIN", NULL);
1644 			pSpecialCels = LoadFileInMem("NLevels\\L5Data\\L5S.CEL", NULL);
1645 		}
1646 		break;
1647 	case DTYPE_CATACOMBS:
1648 		pDungeonCels = LoadFileInMem("Levels\\L2Data\\L2.CEL", NULL);
1649 		pMegaTiles = LoadFileInMem("Levels\\L2Data\\L2.TIL", NULL);
1650 		pLevelPieces = LoadFileInMem("Levels\\L2Data\\L2.MIN", NULL);
1651 		pSpecialCels = LoadFileInMem("Levels\\L2Data\\L2S.CEL", NULL);
1652 		break;
1653 	case DTYPE_CAVES:
1654 		if (currlevel < 17) {
1655 			pDungeonCels = LoadFileInMem("Levels\\L3Data\\L3.CEL", NULL);
1656 			pMegaTiles = LoadFileInMem("Levels\\L3Data\\L3.TIL", NULL);
1657 			pLevelPieces = LoadFileInMem("Levels\\L3Data\\L3.MIN", NULL);
1658 		} else {
1659 			pDungeonCels = LoadFileInMem("NLevels\\L6Data\\L6.CEL", NULL);
1660 			pMegaTiles = LoadFileInMem("NLevels\\L6Data\\L6.TIL", NULL);
1661 			pLevelPieces = LoadFileInMem("NLevels\\L6Data\\L6.MIN", NULL);
1662 		}
1663 		pSpecialCels = LoadFileInMem("Levels\\L1Data\\L1S.CEL", NULL);
1664 		break;
1665 	case DTYPE_HELL:
1666 		pDungeonCels = LoadFileInMem("Levels\\L4Data\\L4.CEL", NULL);
1667 		pMegaTiles = LoadFileInMem("Levels\\L4Data\\L4.TIL", NULL);
1668 		pLevelPieces = LoadFileInMem("Levels\\L4Data\\L4.MIN", NULL);
1669 		pSpecialCels = LoadFileInMem("Levels\\L2Data\\L2S.CEL", NULL);
1670 		break;
1671 	default:
1672 		app_fatal("LoadLvlGFX");
1673 	}
1674 }
1675 
LoadAllGFX()1676 void LoadAllGFX()
1677 {
1678 	IncProgress();
1679 	IncProgress();
1680 	InitObjectGFX();
1681 	IncProgress();
1682 	InitMissileGFX();
1683 	IncProgress();
1684 }
1685 
1686 /**
1687  * @param lvldir method of entry
1688  */
CreateLevel(int lvldir)1689 void CreateLevel(int lvldir)
1690 {
1691 	switch (leveltype) {
1692 	case DTYPE_TOWN:
1693 		CreateTown(lvldir);
1694 		InitTownTriggers();
1695 		LoadRndLvlPal(0);
1696 		break;
1697 	case DTYPE_CATHEDRAL:
1698 		CreateL5Dungeon(glSeedTbl[currlevel], lvldir);
1699 		InitL1Triggers();
1700 		Freeupstairs();
1701 		if (currlevel < 21) {
1702 			LoadRndLvlPal(1);
1703 		} else {
1704 			LoadRndLvlPal(5);
1705 		}
1706 		break;
1707 	case DTYPE_CATACOMBS:
1708 		CreateL2Dungeon(glSeedTbl[currlevel], lvldir);
1709 		InitL2Triggers();
1710 		Freeupstairs();
1711 		LoadRndLvlPal(2);
1712 		break;
1713 	case DTYPE_CAVES:
1714 		CreateL3Dungeon(glSeedTbl[currlevel], lvldir);
1715 		InitL3Triggers();
1716 		Freeupstairs();
1717 		if (currlevel < 17) {
1718 			LoadRndLvlPal(3);
1719 		} else {
1720 			LoadRndLvlPal(6);
1721 		}
1722 		break;
1723 	case DTYPE_HELL:
1724 		CreateL4Dungeon(glSeedTbl[currlevel], lvldir);
1725 		InitL4Triggers();
1726 		Freeupstairs();
1727 		LoadRndLvlPal(4);
1728 		break;
1729 	default:
1730 		app_fatal("CreateLevel");
1731 	}
1732 }
1733 
UpdateMonsterLights()1734 static void UpdateMonsterLights()
1735 {
1736 	for (int i = 0; i < nummonsters; i++) {
1737 		MonsterStruct *mon = &monster[monstactive[i]];
1738 		if (mon->mlid != NO_LIGHT) {
1739 			if (mon->mlid == plr[myplr]._plid) { // Fix old saves where some monsters had 0 instead of NO_LIGHT
1740 				mon->mlid = NO_LIGHT;
1741 				continue;
1742 			}
1743 
1744 			LightListStruct *lid = &LightList[mon->mlid];
1745 			if (mon->_mx != lid->_lx || mon->_my != lid->_ly) {
1746 				ChangeLightXY(mon->mlid, mon->_mx, mon->_my);
1747 			}
1748 		}
1749 	}
1750 }
1751 
LoadGameLevel(BOOL firstflag,int lvldir)1752 void LoadGameLevel(BOOL firstflag, int lvldir)
1753 {
1754 	int i, j;
1755 	BOOL visited;
1756 
1757 	if (setseed)
1758 		glSeedTbl[currlevel] = setseed;
1759 
1760 	music_stop();
1761 	if (pcurs > CURSOR_HAND && pcurs < CURSOR_FIRSTITEM) {
1762 		NewCursor(CURSOR_HAND);
1763 	}
1764 	SetRndSeed(glSeedTbl[currlevel]);
1765 	IncProgress();
1766 	MakeLightTable();
1767 	LoadLvlGFX();
1768 	IncProgress();
1769 
1770 	if (firstflag) {
1771 		InitInv();
1772 		InitItemGFX();
1773 		InitQuestText();
1774 
1775 		int players = gbIsMultiplayer ? MAX_PLRS : 1;
1776 		for (i = 0; i < players; i++)
1777 			InitPlrGFXMem(i);
1778 
1779 		InitStores();
1780 		InitAutomapOnce();
1781 		InitHelp();
1782 	}
1783 
1784 	SetRndSeed(glSeedTbl[currlevel]);
1785 
1786 	if (leveltype == DTYPE_TOWN)
1787 		SetupTownStores();
1788 
1789 	IncProgress();
1790 	InitAutomap();
1791 
1792 	if (leveltype != DTYPE_TOWN && lvldir != ENTRY_LOAD) {
1793 		InitLighting();
1794 		InitVision();
1795 	}
1796 
1797 	InitLevelMonsters();
1798 	IncProgress();
1799 
1800 	if (!setlevel) {
1801 		CreateLevel(lvldir);
1802 		IncProgress();
1803 		FillSolidBlockTbls();
1804 		SetRndSeed(glSeedTbl[currlevel]);
1805 
1806 		if (leveltype != DTYPE_TOWN) {
1807 			GetLevelMTypes();
1808 			InitThemes();
1809 			LoadAllGFX();
1810 		} else {
1811 			IncProgress();
1812 			IncProgress();
1813 			InitMissileGFX();
1814 			IncProgress();
1815 			IncProgress();
1816 		}
1817 
1818 		IncProgress();
1819 
1820 		if (lvldir == ENTRY_RTNLVL)
1821 			GetReturnLvlPos();
1822 		if (lvldir == ENTRY_WARPLVL)
1823 			GetPortalLvlPos();
1824 
1825 		IncProgress();
1826 
1827 		for (i = 0; i < MAX_PLRS; i++) {
1828 			if (plr[i].plractive && currlevel == plr[i].plrlevel) {
1829 				InitPlayerGFX(i);
1830 				if (lvldir != ENTRY_LOAD)
1831 					InitPlayer(i, firstflag);
1832 			}
1833 		}
1834 
1835 		PlayDungMsgs();
1836 		InitMultiView();
1837 		IncProgress();
1838 
1839 		visited = FALSE;
1840 		int players = gbIsMultiplayer ? MAX_PLRS : 1;
1841 		for (i = 0; i < players; i++) {
1842 			if (plr[i].plractive)
1843 				visited = visited || plr[i]._pLvlVisited[currlevel];
1844 		}
1845 
1846 		SetRndSeed(glSeedTbl[currlevel]);
1847 
1848 		if (leveltype != DTYPE_TOWN) {
1849 			if (firstflag || lvldir == ENTRY_LOAD || !plr[myplr]._pLvlVisited[currlevel] || gbIsMultiplayer) {
1850 				HoldThemeRooms();
1851 				glMid1Seed[currlevel] = GetRndSeed();
1852 				InitMonsters();
1853 				glMid2Seed[currlevel] = GetRndSeed();
1854 				IncProgress();
1855 				InitObjects();
1856 				InitItems();
1857 				if (currlevel < 17)
1858 					CreateThemeRooms();
1859 				IncProgress();
1860 				glMid3Seed[currlevel] = GetRndSeed();
1861 				InitMissiles();
1862 				InitDead();
1863 				glEndSeed[currlevel] = GetRndSeed();
1864 
1865 				if (gbIsMultiplayer)
1866 					DeltaLoadLevel();
1867 
1868 				IncProgress();
1869 				SavePreLighting();
1870 			} else {
1871 				HoldThemeRooms();
1872 				InitMonsters();
1873 				InitMissiles();
1874 				InitDead();
1875 				IncProgress();
1876 				LoadLevel();
1877 				IncProgress();
1878 			}
1879 		} else {
1880 			for (i = 0; i < MAXDUNX; i++) {
1881 				for (j = 0; j < MAXDUNY; j++)
1882 					dFlags[i][j] |= BFLAG_LIT;
1883 			}
1884 
1885 			InitTowners();
1886 			InitItems();
1887 			InitMissiles();
1888 			IncProgress();
1889 
1890 			if (!firstflag && lvldir != ENTRY_LOAD && plr[myplr]._pLvlVisited[currlevel] && !gbIsMultiplayer)
1891 				LoadLevel();
1892 			if (gbIsMultiplayer)
1893 				DeltaLoadLevel();
1894 
1895 			IncProgress();
1896 		}
1897 		if (!gbIsMultiplayer)
1898 			ResyncQuests();
1899 		else
1900 			ResyncMPQuests();
1901 	} else {
1902 		LoadSetMap();
1903 		IncProgress();
1904 		GetLevelMTypes();
1905 		IncProgress();
1906 		InitMonsters();
1907 		IncProgress();
1908 		InitMissileGFX();
1909 		IncProgress();
1910 		InitDead();
1911 		IncProgress();
1912 		FillSolidBlockTbls();
1913 		IncProgress();
1914 
1915 		if (lvldir == ENTRY_WARPLVL)
1916 			GetPortalLvlPos();
1917 		IncProgress();
1918 
1919 		for (i = 0; i < MAX_PLRS; i++) {
1920 			if (plr[i].plractive && currlevel == plr[i].plrlevel) {
1921 				InitPlayerGFX(i);
1922 				if (lvldir != ENTRY_LOAD)
1923 					InitPlayer(i, firstflag);
1924 			}
1925 		}
1926 		IncProgress();
1927 
1928 		InitMultiView();
1929 		IncProgress();
1930 
1931 		if (firstflag || lvldir == ENTRY_LOAD || !plr[myplr]._pSLvlVisited[setlvlnum]) {
1932 			InitItems();
1933 			SavePreLighting();
1934 		} else {
1935 			LoadLevel();
1936 		}
1937 
1938 		InitMissiles();
1939 		IncProgress();
1940 	}
1941 
1942 	SyncPortals();
1943 
1944 	for (i = 0; i < MAX_PLRS; i++) {
1945 		if (plr[i].plractive && plr[i].plrlevel == currlevel && (!plr[i]._pLvlChanging || i == myplr)) {
1946 			if (plr[i]._pHitPoints > 0) {
1947 				if (!gbIsMultiplayer)
1948 					dPlayer[plr[i]._px][plr[i]._py] = i + 1;
1949 				else
1950 					SyncInitPlrPos(i);
1951 			} else {
1952 				dFlags[plr[i]._px][plr[i]._py] |= BFLAG_DEAD_PLAYER;
1953 			}
1954 		}
1955 	}
1956 
1957 	SetDungeonMicros();
1958 
1959 	InitLightMax();
1960 	IncProgress();
1961 	IncProgress();
1962 
1963 	if (firstflag) {
1964 		InitControlPan();
1965 	}
1966 	IncProgress();
1967 	UpdateMonsterLights();
1968 	if (leveltype != DTYPE_TOWN) {
1969 		ProcessLightList();
1970 		ProcessVisionList();
1971 	}
1972 
1973 	if (currlevel >= 21) {
1974 		if (currlevel == 21) {
1975 			items_427ABA(CornerStone.x, CornerStone.y);
1976 		}
1977 		if (quests[Q_NAKRUL]._qactive == QUEST_DONE && currlevel == 24) {
1978 			objects_454BA8();
1979 		}
1980 	}
1981 
1982 	if (currlevel >= 17)
1983 		music_start(currlevel > 20 ? TMUSIC_L5 : TMUSIC_L6);
1984 	else
1985 		music_start(leveltype);
1986 
1987 	while (!IncProgress())
1988 		;
1989 
1990 	if (!gbIsSpawn && setlevel && setlvlnum == SL_SKELKING && quests[Q_SKELKING]._qactive == QUEST_ACTIVE)
1991 		PlaySFX(USFX_SKING1);
1992 }
1993 
game_logic()1994 static void game_logic()
1995 {
1996 	if (!ProcessInput()) {
1997 		return;
1998 	}
1999 	if (gbProcessPlayers) {
2000 		ProcessPlayers();
2001 	}
2002 	if (leveltype != DTYPE_TOWN) {
2003 		ProcessMonsters();
2004 		ProcessObjects();
2005 		ProcessMissiles();
2006 		ProcessItems();
2007 		ProcessLightList();
2008 		ProcessVisionList();
2009 	} else {
2010 		ProcessTowners();
2011 		ProcessItems();
2012 		ProcessMissiles();
2013 	}
2014 
2015 #ifdef _DEBUG
2016 	if (debug_mode_key_inverted_v && GetAsyncKeyState(DVL_VK_SHIFT) & 0x8000) {
2017 		ScrollView();
2018 	}
2019 #endif
2020 
2021 	sound_update();
2022 	ClearPlrMsg();
2023 	CheckTriggers();
2024 	CheckQuests();
2025 	force_redraw |= 1;
2026 	pfile_update(false);
2027 
2028 	plrctrls_after_game_logic();
2029 }
2030 
timeout_cursor(BOOL bTimeout)2031 static void timeout_cursor(BOOL bTimeout)
2032 {
2033 	if (bTimeout) {
2034 		if (sgnTimeoutCurs == CURSOR_NONE && sgbMouseDown == CLICK_NONE) {
2035 			sgnTimeoutCurs = pcurs;
2036 			multi_net_ping();
2037 			ClearPanel();
2038 			AddPanelString("-- Network timeout --", TRUE);
2039 			AddPanelString("-- Waiting for players --", TRUE);
2040 			NewCursor(CURSOR_HOURGLASS);
2041 			force_redraw = 255;
2042 		}
2043 		scrollrt_draw_game_screen(TRUE);
2044 	} else if (sgnTimeoutCurs != CURSOR_NONE) {
2045 		NewCursor(sgnTimeoutCurs);
2046 		sgnTimeoutCurs = CURSOR_NONE;
2047 		ClearPanel();
2048 		force_redraw = 255;
2049 	}
2050 }
2051 
2052 /**
2053  * @param bStartup Process additional ticks before returning
2054  */
game_loop(BOOL bStartup)2055 void game_loop(BOOL bStartup)
2056 {
2057 	int i;
2058 
2059 	i = bStartup ? gnTickRate * 3 : 3;
2060 
2061 	while (i--) {
2062 		if (!multi_handle_delta()) {
2063 			timeout_cursor(TRUE);
2064 			break;
2065 		} else {
2066 			timeout_cursor(FALSE);
2067 			game_logic();
2068 		}
2069 		if (!gbRunGame || !gbIsMultiplayer || !nthread_has_500ms_passed())
2070 			break;
2071 	}
2072 }
2073 
diablo_color_cyc_logic()2074 void diablo_color_cyc_logic()
2075 {
2076 	if (!sgOptions.Graphics.bColorCycling)
2077 		return;
2078 
2079 	if (leveltype == DTYPE_HELL) {
2080 		lighting_color_cycling();
2081 	} else if (currlevel >= 21) {
2082 		palette_update_crypt();
2083 	} else if (currlevel >= 17) {
2084 		palette_update_hive();
2085 	} else if (leveltype == DTYPE_CAVES) {
2086 		palette_update_caves();
2087 	}
2088 }
2089 
2090 DEVILUTION_END_NAMESPACE
2091