1 // main.cpp: initialisation & main loop
2 
3 #include "engine.h"
4 #include <signal.h>
5 
6 string caption = "";
7 
setcaption(const char * text,const char * text2)8 void setcaption(const char *text, const char *text2)
9 {
10     static string prevtext = "", prevtext2 = "";
11     if(strcmp(text, prevtext) || strcmp(text2, prevtext2))
12     {
13         copystring(prevtext, text);
14         copystring(prevtext2, text2);
15         formatstring(caption, "%s v%s-%s%d-%s (%s)%s%s%s%s", versionname, versionstring, versionplatname, versionarch, versionbranch, versionrelease, text[0] ? ": " : "", text, text2[0] ? " - " : "", text2);
16         if(screen) SDL_SetWindowTitle(screen, caption);
17     }
18 }
19 
20 int keyrepeatmask = 0, textinputmask = 0;
21 Uint32 textinputtime = 0;
22 VAR(0, textinputfilter, 0, 5, 1000);
23 
keyrepeat(bool on,int mask)24 void keyrepeat(bool on, int mask)
25 {
26     if(on) keyrepeatmask |= mask;
27     else keyrepeatmask &= ~mask;
28 }
29 
textinput(bool on,int mask)30 void textinput(bool on, int mask)
31 {
32     if(on)
33     {
34         if(!textinputmask)
35         {
36             SDL_StartTextInput();
37             textinputtime = SDL_GetTicks();
38         }
39         textinputmask |= mask;
40     }
41     else
42     {
43         textinputmask &= ~mask;
44         if(!textinputmask) SDL_StopTextInput();
45     }
46 }
47 
48 VARN(IDF_PERSIST, relativemouse, userelativemouse, 0, 1, 1);
49 
50 bool shouldgrab = false, grabinput = false, canrelativemouse = true, relativemouse = false;
51 
inputgrab(bool on)52 void inputgrab(bool on)
53 {
54     if(on)
55     {
56         SDL_ShowCursor(SDL_FALSE);
57         if(canrelativemouse && userelativemouse)
58         {
59             if(SDL_SetRelativeMouseMode(SDL_TRUE) >= 0)
60             {
61                 SDL_SetWindowGrab(screen, SDL_TRUE);
62                 relativemouse = true;
63             }
64             else
65             {
66                 SDL_SetWindowGrab(screen, SDL_FALSE);
67                 canrelativemouse = false;
68                 relativemouse = false;
69             }
70         }
71     }
72     else
73     {
74         SDL_ShowCursor(SDL_TRUE);
75         if(relativemouse)
76         {
77             SDL_SetRelativeMouseMode(SDL_FALSE);
78             SDL_SetWindowGrab(screen, SDL_FALSE);
79             relativemouse = false;
80         }
81     }
82     shouldgrab = false;
83 }
84 
85 extern void cleargamma();
86 
cleanup()87 void cleanup()
88 {
89     recorder::stop();
90     cleanupserver();
91     SDL_ShowCursor(SDL_TRUE);
92     SDL_SetRelativeMouseMode(SDL_FALSE);
93     if(screen) SDL_SetWindowGrab(screen, SDL_FALSE);
94     cleargamma();
95     freeocta(worldroot);
96     extern void clear_command();    clear_command();
97     extern void clear_console();    clear_console();
98     extern void clear_mdls();       clear_mdls();
99     stopsound();
100     #ifdef __APPLE__
101         if(screen) SDL_SetWindowFullscreen(screen, 0);
102     #endif
103     SDL_Quit();
104 }
105 
quit()106 void quit()                  // normal exit
107 {
108     extern void writeinitcfg();
109     extern void writeservercfg();
110     inbetweenframes = false;
111     writeinitcfg();
112     writeservercfg();
113     writecfg();
114     abortconnect();
115     disconnect(true);
116     cleanup();
117     exit(EXIT_SUCCESS);
118 }
119 
120 volatile int errors = 0;
fatal(const char * s,...)121 void fatal(const char *s, ...)    // failure exit
122 {
123     if(++errors <= 2) // print up to one extra recursive error
124     {
125         defvformatbigstring(msg, s, s);
126         if(logfile) logoutf("%s", msg);
127         #ifndef WIN32
128         fprintf(stderr, "%s\n", msg);
129         #endif
130         if(errors <= 1) // avoid recursion
131         {
132             if(SDL_WasInit(SDL_INIT_VIDEO))
133             {
134                 SDL_ShowCursor(SDL_TRUE);
135                 SDL_SetRelativeMouseMode(SDL_FALSE);
136                 if(screen) SDL_SetWindowGrab(screen, SDL_FALSE);
137                 cleargamma();
138                 #ifdef __APPLE__
139                     if(screen) SDL_SetWindowFullscreen(screen, 0);
140                 #endif
141             }
142             SDL_Quit();
143             defformatstring(cap, "%s: Error", versionname);
144             SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, cap, msg, NULL);
145         }
146     }
147     exit(EXIT_FAILURE);
148 }
149 
150 int initing = NOT_INITING;
initwarning(const char * desc,int level,int type,bool force)151 bool initwarning(const char *desc, int level, int type, bool force)
152 {
153     if(initing < level)
154     {
155         addchange(desc, type, force);
156         return true;
157     }
158     return false;
159 }
160 
161 VAR(IDF_READONLY, desktopw, 1, 0, 0);
162 VAR(IDF_READONLY, desktoph, 1, 0, 0);
163 int screenw = 0, screenh = 0;
164 SDL_Window *screen = NULL;
165 SDL_GLContext glcontext = NULL;
166 
167 #define SCR_MINW 320
168 #define SCR_MINH 200
169 #define SCR_MAXW 10000
170 #define SCR_MAXH 10000
171 #define SCR_DEFAULTW 1024
172 #define SCR_DEFAULTH 768
173 VARF(0, scr_w, SCR_MINW, -1, SCR_MAXW, initwarning("screen resolution"));
174 VARF(0, scr_h, SCR_MINH, -1, SCR_MAXH, initwarning("screen resolution"));
175 VARF(0, depthbits, 0, 0, 32, initwarning("depth-buffer precision"));
176 VARF(0, fsaa, -1, -1, 16, initwarning("anti-aliasing"));
177 
writeinitcfg()178 void writeinitcfg()
179 {
180     stream *f = openutf8file("init.cfg", "w");
181     if(!f) return;
182     f->printf("// automatically written on exit, DO NOT MODIFY\n// modify settings in game\n");
183     extern int fullscreen, fullscreendesktop;
184     f->printf("fullscreen %d\n", fullscreen);
185     f->printf("fullscreendesktop %d\n", fullscreendesktop);
186     f->printf("scr_w %d\n", scr_w);
187     f->printf("scr_h %d\n", scr_h);
188     f->printf("depthbits %d\n", depthbits);
189     f->printf("fsaa %d\n", fsaa);
190     extern int soundmono, soundmixchans, soundbuflen, soundfreq;
191     f->printf("soundmono %d\n", soundmono);
192     f->printf("soundmixchans %d\n", soundmixchans);
193     f->printf("soundbuflen %d\n", soundbuflen);
194     f->printf("soundfreq %d\n", soundfreq);
195     f->printf("verbose %d\n", verbose);
196     extern int noconfigfile;
197     f->printf("noconfigfile %d\n", noconfigfile);
198     delete f;
199 }
200 
201 VAR(IDF_PERSIST, compresslevel, 0, 9, 9);
202 VAR(IDF_PERSIST, imageformat, IFMT_NONE+1, IFMT_PNG, IFMT_MAX-1);
203 
screenshot(char * sname)204 void screenshot(char *sname)
205 {
206     ImageData image(screenw, screenh, 3);
207     glPixelStorei(GL_PACK_ALIGNMENT, 1);
208     glReadPixels(0, 0, screenw, screenh, GL_RGB, GL_UNSIGNED_BYTE, image.data);
209     string fname;
210     if(sname && *sname) copystring(fname, sname);
211     else formatstring(fname, "screenshots/%s", *filetimeformat ? gettime(filetimelocal ? currenttime : clocktime, filetimeformat) : (*mapname ? mapname : "screen"));
212     saveimage(fname, image, imageformat, compresslevel, true);
213 }
214 
215 ICOMMAND(0, screenshot, "s", (char *s), if(!(identflags&IDF_WORLD)) screenshot(s));
216 ICOMMAND(0, quit, "", (void), if(!(identflags&IDF_WORLD)) quit());
217 
218 bool initwindowpos = false;
219 
setfullscreen(bool enable)220 void setfullscreen(bool enable)
221 {
222     if(!screen) return;
223     //initwarning(enable ? "fullscreen" : "windowed");
224     extern int fullscreendesktop;
225     SDL_SetWindowFullscreen(screen, enable ? (fullscreendesktop ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN) : 0);
226     if(!enable)
227     {
228         SDL_SetWindowSize(screen, scr_w, scr_h);
229         if(initwindowpos)
230         {
231             int winx = SDL_WINDOWPOS_CENTERED, winy = SDL_WINDOWPOS_CENTERED;
232             SDL_SetWindowPosition(screen, winx, winy);
233             initwindowpos = false;
234         }
235     }
236 }
237 
238 VARF(0, fullscreen, 0, 1, 1, if(!(identflags&IDF_WORLD)) setfullscreen(fullscreen!=0));
239 
resetfullscreen()240 void resetfullscreen()
241 {
242     setfullscreen(false);
243     setfullscreen(true);
244 }
245 
246 VARF(0, fullscreendesktop, 0, 0, 1, if(!(identflags&IDF_WORLD) && fullscreen) resetfullscreen());
247 
screenres(int w,int h)248 void screenres(int w, int h)
249 {
250     scr_w = clamp(w, SCR_MINW, SCR_MAXW);
251     scr_h = clamp(h, SCR_MINH, SCR_MAXH);
252     if(screen)
253     {
254         if(fullscreendesktop)
255         {
256             scr_w = min(scr_w, desktopw);
257             scr_h = min(scr_h, desktoph);
258         }
259         if(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN)
260         {
261             if(fullscreendesktop) gl_resize();
262             else resetfullscreen();
263         }
264         else SDL_SetWindowSize(screen, scr_w, scr_h);
265     }
266     else
267     {
268         initwarning("screen resolution");
269     }
270 }
271 
272 ICOMMAND(0, screenres, "ii", (int *w, int *h), screenres(*w, *h));
273 
setgamma(int val)274 static void setgamma(int val)
275 {
276     if(screen && SDL_SetWindowBrightness(screen, val/100.0f) < 0) conoutf("\frCould not set gamma: %s", SDL_GetError());
277 }
278 
279 static int curgamma = 100;
280 VARF(IDF_PERSIST, gamma, 30, 100, 300,
281 {
282     if(initing || gamma == curgamma) return;
283     curgamma = gamma;
284     setgamma(curgamma);
285 });
286 
restoregamma()287 void restoregamma()
288 {
289     if(initing || curgamma == 100) return;
290     setgamma(curgamma);
291 }
292 
cleargamma()293 void cleargamma()
294 {
295     if(curgamma != 100 && screen) SDL_SetWindowBrightness(screen, 1.0f);
296 }
297 
298 int curvsync = -1;
restorevsync()299 void restorevsync()
300 {
301     if(initing || !glcontext) return;
302     extern int vsync, vsynctear;
303     if(!SDL_GL_SetSwapInterval(vsync ? (vsynctear ? -1 : 1) : 0))
304         curvsync = vsync;
305 }
306 
307 VARF(IDF_PERSIST, vsync, 0, 0, 1, restorevsync());
308 VARF(IDF_PERSIST, vsynctear, 0, 0, 1, { if(vsync) restorevsync(); });
309 
setupscreen()310 void setupscreen()
311 {
312     if(glcontext)
313     {
314         SDL_GL_DeleteContext(glcontext);
315         glcontext = NULL;
316     }
317     if(screen)
318     {
319         SDL_DestroyWindow(screen);
320         screen = NULL;
321     }
322     curvsync = -1;
323 
324     SDL_Rect desktop;
325     if(SDL_GetDisplayBounds(0, &desktop) < 0) fatal("failed querying desktop bounds: %s", SDL_GetError());
326     desktopw = desktop.w;
327     desktoph = desktop.h;
328 
329     if(scr_h < 0) scr_h = fullscreen ? desktoph : SCR_DEFAULTH;
330     if(scr_w < 0) scr_w = (scr_h*desktopw)/desktoph;
331     scr_w = clamp(scr_w, SCR_MINW, SCR_MAXW);
332     scr_h = clamp(scr_h, SCR_MINH, SCR_MAXH);
333     if(fullscreendesktop)
334     {
335         scr_w = min(scr_w, desktopw);
336         scr_h = min(scr_h, desktoph);
337     }
338 
339     int winx = SDL_WINDOWPOS_UNDEFINED, winy = SDL_WINDOWPOS_UNDEFINED, winw = scr_w, winh = scr_h, flags = SDL_WINDOW_RESIZABLE;
340     if(fullscreen)
341     {
342         if(fullscreendesktop)
343         {
344             winw = desktopw;
345             winh = desktoph;
346             flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
347         }
348         else flags |= SDL_WINDOW_FULLSCREEN;
349         initwindowpos = true;
350     }
351 
352     SDL_GL_ResetAttributes();
353     SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
354     static const int configs[] =
355     {
356         0x3, /* try everything */
357         0x2, 0x1, /* try disabling one at a time */
358         0 /* try disabling everything */
359     };
360     int config = 0;
361     if(!depthbits) SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
362     if(!fsaa)
363     {
364         SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
365         SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
366     }
367     loopi(sizeof(configs)/sizeof(configs[0]))
368     {
369         config = configs[i];
370         if(!depthbits && config&1) continue;
371         if(fsaa<=0 && config&2) continue;
372         if(depthbits) SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, config&1 ? depthbits : 24);
373         if(fsaa>0)
374         {
375             SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, config&2 ? 1 : 0);
376             SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, config&2 ? fsaa : 0);
377         }
378         screen = SDL_CreateWindow(caption, winx, winy, winw, winh, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS | flags);
379         if(!screen) continue;
380 
381     #ifdef __APPLE__
382         static const int glversions[] = { 32, 20 };
383     #else
384         static const int glversions[] = { 33, 32, 31, 30, 20 };
385     #endif
386         loopj(sizeof(glversions)/sizeof(glversions[0]))
387         {
388             glcompat = glversions[j] <= 30 ? 1 : 0;
389             SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, glversions[j] / 10);
390             SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, glversions[j] % 10);
391             SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, glversions[j] >= 32 ? SDL_GL_CONTEXT_PROFILE_CORE : 0);
392             glcontext = SDL_GL_CreateContext(screen);
393             if(glcontext) break;
394         }
395         if(glcontext) break;
396     }
397     if(!screen) fatal("failed to create OpenGL window: %s", SDL_GetError());
398     else if(!glcontext) fatal("failed to create OpenGL context: %s", SDL_GetError());
399     else
400     {
401         if(depthbits && (config&1)==0) conoutf("\fr%d bit z-buffer not supported - disabling", depthbits);
402         if(fsaa>0 && (config&2)==0) conoutf("\fr%dx anti-aliasing not supported - disabling", fsaa);
403     }
404 
405     SDL_SetWindowMinimumSize(screen, SCR_MINW, SCR_MINH);
406     SDL_SetWindowMaximumSize(screen, SCR_MAXW, SCR_MAXH);
407 
408     SDL_GetWindowSize(screen, &screenw, &screenh);
409 }
410 
resetgl()411 void resetgl()
412 {
413     clearchanges(CHANGE_GFX);
414 
415     progress(0, "resetting OpenGL..");
416 
417     extern void cleanupva();
418     extern void cleanupparticles();
419     extern void cleanupdecals();
420     extern void cleanupblobs();
421     extern void cleanupsky();
422     extern void cleanupmodels();
423     extern void cleanupprefabs();
424     extern void cleanuplightmaps();
425     extern void cleanupblendmap();
426     extern void cleanshadowmap();
427     extern void cleanreflections();
428     extern void cleanupglare();
429     extern void cleanupdepthfx();
430     recorder::cleanup();
431     cleanupva();
432     cleanupparticles();
433     cleanupdecals();
434     cleanupblobs();
435     cleanupsky();
436     cleanupmodels();
437     cleanupprefabs();
438     cleanuptextures();
439     cleanuplightmaps();
440     cleanupblendmap();
441     cleanshadowmap();
442     cleanreflections();
443     cleanupglare();
444     cleanupdepthfx();
445     cleanupshaders();
446     cleanupgl();
447 
448     setupscreen();
449     inputgrab(grabinput);
450     gl_init();
451 
452     inbetweenframes = false;
453     if(!reloadtexture(notexturetex) || !reloadtexture(blanktex) || !reloadtexture(logotex) || !reloadtexture(badgetex))
454         fatal("failed to reload core textures");
455     reloadfonts();
456     inbetweenframes = true;
457     progress(0, "initializing...");
458     restoregamma();
459     restorevsync();
460     reloadshaders();
461     reloadtextures();
462     initlights();
463     allchanged(true);
464 }
465 
466 ICOMMAND(0, resetgl, "", (void), if(!(identflags&IDF_WORLD)) resetgl());
467 
468 bool warping = false, minimized = false;
469 
470 vector<SDL_Event> events;
471 
pushevent(const SDL_Event & e)472 void pushevent(const SDL_Event &e)
473 {
474     events.add(e);
475 }
476 
filterevent(const SDL_Event & event)477 static bool filterevent(const SDL_Event &event)
478 {
479     switch(event.type)
480     {
481         case SDL_MOUSEMOTION:
482             if(grabinput && !relativemouse && !(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN))
483             {
484                 if(warping && event.motion.x == screenw / 2 && event.motion.y == screenh / 2)
485                     return false;  // ignore any motion events generated by SDL_WarpMouse
486                 #ifdef __APPLE__
487                 if(event.motion.y == 0)
488                     return false;  // let mac users drag windows via the title bar
489                 #endif
490             }
491             break;
492     }
493     return true;
494 }
495 
pollevent(SDL_Event & event)496 static inline bool pollevent(SDL_Event &event)
497 {
498     while(SDL_PollEvent(&event))
499     {
500         if(filterevent(event)) return true;
501     }
502     return false;
503 }
504 
interceptkey(int sym,int mod)505 bool interceptkey(int sym, int mod)
506 {
507     SDL_Event event;
508     while(pollevent(event)) switch(event.type)
509     {
510         case SDL_KEYDOWN:
511             if(event.key.keysym.sym == sym && (!mod || SDL_GetModState()&mod))
512                 return true;
513         default:
514             pushevent(event);
515             break;
516     }
517     return false;
518 }
519 
ignoremousemotion()520 static void ignoremousemotion()
521 {
522     SDL_Event e;
523     SDL_PumpEvents();
524     while(SDL_PeepEvents(&e, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION));
525 }
526 
resetcursor(bool warp,bool reset)527 void resetcursor(bool warp, bool reset)
528 {
529     if(warp && grabinput && !relativemouse && !(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN))
530     {
531         SDL_WarpMouseInWindow(screen, screenw/2, screenh/2);
532         warping = true;
533     }
534     if(reset) cursorx = cursory = 0.5f;
535 }
536 
checkmousemotion(int & dx,int & dy)537 static void checkmousemotion(int &dx, int &dy)
538 {
539     loopv(events)
540     {
541         SDL_Event &event = events[i];
542         if(event.type != SDL_MOUSEMOTION)
543         {
544             if(i > 0) events.remove(0, i);
545             return;
546         }
547         dx += event.motion.xrel;
548         dy += event.motion.yrel;
549     }
550     events.setsize(0);
551     SDL_Event event;
552     while(pollevent(event))
553     {
554         if(event.type != SDL_MOUSEMOTION)
555         {
556             events.add(event);
557             return;
558         }
559         dx += event.motion.xrel;
560         dy += event.motion.yrel;
561     }
562 }
563 
checkinput()564 void checkinput()
565 {
566     SDL_Event event;
567     //int lasttype = 0, lastbut = 0;
568     bool mousemoved = false, shouldwarp = false;
569     while(events.length() || pollevent(event))
570     {
571         if(events.length()) event = events.remove(0);
572 
573         switch (event.type)
574         {
575             case SDL_QUIT:
576                 quit();
577                 return;
578 
579             case SDL_TEXTINPUT:
580                 if(textinputmask && int(event.text.timestamp-textinputtime) >= textinputfilter)
581                 {
582                     uchar buf[SDL_TEXTINPUTEVENT_TEXT_SIZE+1];
583                     size_t len = decodeutf8(buf, sizeof(buf)-1, (const uchar *)event.text.text, strlen(event.text.text));
584                     if(len > 0) { buf[len] = '\0'; processtextinput((const char *)buf, len); }
585                 }
586                 break;
587 
588             case SDL_KEYDOWN:
589             case SDL_KEYUP:
590                 if(keyrepeatmask || !event.key.repeat)
591                     processkey(event.key.keysym.sym, event.key.state==SDL_PRESSED);
592                 break;
593 
594             case SDL_WINDOWEVENT:
595                 switch(event.window.event)
596                 {
597                     case SDL_WINDOWEVENT_CLOSE:
598                         quit();
599                         break;
600 
601                     case SDL_WINDOWEVENT_FOCUS_GAINED:
602                         shouldgrab = true;
603                         break;
604                     case SDL_WINDOWEVENT_ENTER:
605                         inputgrab(grabinput = true);
606                         break;
607 
608                     case SDL_WINDOWEVENT_LEAVE:
609                     case SDL_WINDOWEVENT_FOCUS_LOST:
610                         inputgrab(grabinput = false);
611                         break;
612 
613                     case SDL_WINDOWEVENT_MINIMIZED:
614                         minimized = true;
615                         break;
616 
617                     case SDL_WINDOWEVENT_MAXIMIZED:
618                     case SDL_WINDOWEVENT_RESTORED:
619                         minimized = false;
620                         break;
621 
622                     case SDL_WINDOWEVENT_RESIZED:
623                         break;
624 
625                     case SDL_WINDOWEVENT_SIZE_CHANGED:
626                     {
627                         SDL_GetWindowSize(screen, &screenw, &screenh);
628                         if(!fullscreendesktop || !(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN))
629                         {
630                             scr_w = clamp(screenw, SCR_MINW, SCR_MAXW);
631                             scr_h = clamp(screenh, SCR_MINH, SCR_MAXH);
632                         }
633                         gl_resize();
634                         break;
635                     }
636                 }
637                 break;
638 
639             case SDL_MOUSEMOTION:
640                 if(grabinput)
641                 {
642                     int dx = event.motion.xrel, dy = event.motion.yrel;
643                     checkmousemotion(dx, dy);
644                     shouldwarp = game::mousemove(dx, dy, event.motion.x, event.motion.y, screenw, screenh); // whether game controls engine cursor
645                     mousemoved = true;
646                 }
647                 else if(shouldgrab) inputgrab(grabinput = true);
648                 break;
649 
650             case SDL_MOUSEBUTTONDOWN:
651             case SDL_MOUSEBUTTONUP:
652                 //if(lasttype==event.type && lastbut==event.button.button) break; // why?? get event twice without it
653                 switch(event.button.button)
654                 {
655                     case SDL_BUTTON_LEFT: processkey(-1, event.button.state==SDL_PRESSED); break;
656                     case SDL_BUTTON_MIDDLE: processkey(-2, event.button.state==SDL_PRESSED); break;
657                     case SDL_BUTTON_RIGHT: processkey(-3, event.button.state==SDL_PRESSED); break;
658                     case SDL_BUTTON_X1: processkey(-6, event.button.state==SDL_PRESSED); break;
659                     case SDL_BUTTON_X2: processkey(-7, event.button.state==SDL_PRESSED); break;
660                 }
661                 //lasttype = event.type;
662                 //lastbut = event.button.button;
663                 break;
664 
665             case SDL_MOUSEWHEEL:
666                 if(event.wheel.y > 0) { processkey(-4, true); processkey(-4, false); }
667                 else if(event.wheel.y < 0) { processkey(-5, true); processkey(-5, false); }
668                 break;
669         }
670     }
671     if(mousemoved)
672     {
673         warping = false;
674         if(grabinput && shouldwarp) resetcursor(true, false);
675     }
676 }
677 
swapbuffers(bool overlay)678 void swapbuffers(bool overlay)
679 {
680     recorder::capture(overlay);
681     gle::disable();
682     SDL_GL_SwapWindow(screen);
683 }
684 
685 VAR(IDF_PERSIST, maxfps, 0, 200, 1000);
686 VAR(IDF_PERSIST, menufps, 0, 60, 1000);
687 
limitfps(int & millis,int curmillis)688 void limitfps(int &millis, int curmillis)
689 {
690     int limit = (hasnoview() || minimized) && menufps ? (maxfps ? min(maxfps, menufps) : menufps) : maxfps;
691     if(!limit) return;
692     static int fpserror = 0;
693     int delay = 1000/limit - (millis-curmillis);
694     if(delay < 0) fpserror = 0;
695     else
696     {
697         fpserror += 1000%limit;
698         if(fpserror >= limit)
699         {
700             ++delay;
701             fpserror -= limit;
702         }
703         if(delay > 0)
704         {
705             SDL_Delay(delay);
706             millis += delay;
707         }
708     }
709 }
710 
711 #ifdef WIN32
712 // Force Optimus setups to use the NVIDIA GPU
713 extern "C"
714 {
715 #ifdef __GNUC__
716 __attribute__((dllexport))
717 #else
718 __declspec(dllexport)
719 #endif
720     DWORD NvOptimusEnablement = 1;
721 
722 #ifdef __GNUC__
723 __attribute__((dllexport))
724 #else
725 __declspec(dllexport)
726 #endif
727     DWORD AmdPowerXpressRequestHighPerformance = 1;
728 }
729 #endif
730 
731 #if defined(WIN32) && !defined(_DEBUG) && !defined(__GNUC__)
stackdumper(unsigned int type,EXCEPTION_POINTERS * ep)732 void stackdumper(unsigned int type, EXCEPTION_POINTERS *ep)
733 {
734     if(!ep) fatal("unknown type");
735     EXCEPTION_RECORD *er = ep->ExceptionRecord;
736     CONTEXT *context = ep->ContextRecord;
737     bigstring out;
738     formatstring(out, "%s Win32 Exception: 0x%x [0x%x]\n\n", versionname, er->ExceptionCode, er->ExceptionCode==EXCEPTION_ACCESS_VIOLATION ? er->ExceptionInformation[1] : -1);
739     SymInitialize(GetCurrentProcess(), NULL, TRUE);
740 #ifdef _AMD64_
741     STACKFRAME64 sf = {{context->Rip, 0, AddrModeFlat}, {}, {context->Rbp, 0, AddrModeFlat}, {context->Rsp, 0, AddrModeFlat}, 0};
742     while(::StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(), &sf, context, NULL, ::SymFunctionTableAccess, ::SymGetModuleBase, NULL))
743     {
744         union { IMAGEHLP_SYMBOL64 sym; char symext[sizeof(IMAGEHLP_SYMBOL64) + sizeof(bigstring)]; };
745         sym.SizeOfStruct = sizeof(sym);
746         sym.MaxNameLength = sizeof(symext) - sizeof(sym);
747         IMAGEHLP_LINE64 line;
748         line.SizeOfStruct = sizeof(line);
749         DWORD64 symoff;
750         DWORD lineoff;
751         if(SymGetSymFromAddr64(GetCurrentProcess(), sf.AddrPC.Offset, &symoff, &sym) && SymGetLineFromAddr64(GetCurrentProcess(), sf.AddrPC.Offset, &lineoff, &line))
752 #else
753     STACKFRAME sf = {{context->Eip, 0, AddrModeFlat}, {}, {context->Ebp, 0, AddrModeFlat}, {context->Esp, 0, AddrModeFlat}, 0};
754     while(::StackWalk(IMAGE_FILE_MACHINE_I386, GetCurrentProcess(), GetCurrentThread(), &sf, context, NULL, ::SymFunctionTableAccess, ::SymGetModuleBase, NULL))
755     {
756         union { IMAGEHLP_SYMBOL sym; char symext[sizeof(IMAGEHLP_SYMBOL) + sizeof(bigstring)]; };
757         sym.SizeOfStruct = sizeof(sym);
758         sym.MaxNameLength = sizeof(symext) - sizeof(sym);
759         IMAGEHLP_LINE line;
760         line.SizeOfStruct = sizeof(line);
761         DWORD symoff, lineoff;
762         if(SymGetSymFromAddr(GetCurrentProcess(), sf.AddrPC.Offset, &symoff, &sym) && SymGetLineFromAddr(GetCurrentProcess(), sf.AddrPC.Offset, &lineoff, &line))
763 #endif
764         {
765             char *del = strrchr(line.FileName, '\\');
766             concformatstring(out, "%s - %s [%d]\n", sym.Name, del ? del + 1 : line.FileName, line.LineNumber);
767         }
768     }
769     fatal(out);
770 }
771 #endif
772 
773 #define MAXFPSHISTORY 60
774 
775 int fpspos = 0, fpshistory[MAXFPSHISTORY];
776 
777 void getfps(int &fps, int &bestdiff, int &worstdiff)
778 {
779     int total = fpshistory[MAXFPSHISTORY-1], best = total, worst = total;
780     loopi(MAXFPSHISTORY-1)
781     {
782         int millis = fpshistory[i];
783         total += millis;
784         if(millis < best) best = millis;
785         if(millis > worst) worst = millis;
786     }
787 
788     fps = (1000*MAXFPSHISTORY)/total;
789     bestdiff = 1000/best-fps;
790     worstdiff = fps-1000/worst;
791 }
792 
793 void getfps_(int *raw)
794 {
795     int fps, bestdiff, worstdiff;
796     if(*raw) fps = 1000/fpshistory[(fpspos+MAXFPSHISTORY-1)%MAXFPSHISTORY];
797     else getfps(fps, bestdiff, worstdiff);
798     intret(fps);
799 }
800 
801 COMMANDN(0, getfps, getfps_, "i");
802 
803 VAR(0, curfps, 1, 0, -1);
804 VAR(0, bestfps, 1, 0, -1);
805 VAR(0, bestfpsdiff, 1, 0, -1);
806 VAR(0, worstfps, 1, 0, -1);
807 VAR(0, worstfpsdiff, 1, 0, -1);
808 
809 void resetfps()
810 {
811     loopi(MAXFPSHISTORY) fpshistory[i] = 1;
812     fpspos = 0;
813 }
814 
815 void updatefps(int frames, int millis)
816 {
817     fpshistory[fpspos++] = max(1, min(1000, millis));
818     if(fpspos >= MAXFPSHISTORY) fpspos = 0;
819 
820     int fps, bestdiff, worstdiff;
821     getfps(fps, bestdiff, worstdiff);
822 
823     curfps = fps;
824     bestfps = fps+bestdiff;
825     bestfpsdiff = bestdiff;
826     worstfps = fps-worstdiff;
827     worstfpsdiff = worstdiff;
828 }
829 
830 bool inbetweenframes = false, renderedframe = true;
831 
832 static bool findarg(int argc, char **argv, const char *str)
833 {
834     for(int i = 1; i<argc; i++) if(strstr(argv[i], str)==argv[i]) return true;
835     return false;
836 }
837 
838 bool progressing = false;
839 
840 FVAR(0, loadprogress, 0, 0, 1);
841 SVAR(0, progresstitle, "");
842 SVAR(0, progresstext, "");
843 FVAR(0, progressamt, 0, 0, 1);
844 FVAR(0, progresspart, 0, 0, 1);
845 VAR(IDF_PERSIST, progressdelay, 0, 100, VAR_MAX);
846 VAR(IDF_PERSIST, progressupdate, 0, 0, 1);
847 int lastprogress = 0;
848 
849 void progress(float bar1, const char *text1, float bar2, const char *text2)
850 {
851     if(progressing || !inbetweenframes || drawtex) return;
852     int ticks = SDL_GetTicks();
853     if(lastprogress > 0 && ticks < 0) lastprogress = 1-INT_MAX;
854     if((vsync || !progressupdate || bar1 > 0) && ticks-lastprogress < progressdelay) return;
855     lastprogress = ticks;
856     clientkeepalive();
857 
858     #ifdef __APPLE__
859     interceptkey(SDLK_UNKNOWN); // keep the event queue awake to avoid 'beachball' cursor
860     #endif
861 
862     setsvar("progresstitle", text1 ? text1 : "please wait..");
863     setfvar("progressamt", bar1);
864     setsvar("progresstext", text2 ? text2 : "");
865     setfvar("progresspart", bar2);
866     if(verbose >= 4)
867     {
868         if(text2) conoutf("%s [%.2f%%], %s [%.2f%%]", text1, bar1*100.f, text2, bar2*100.f);
869         else if(text1) conoutf("%s [%.2f%%]", text1, bar1*100.f);
870         else conoutf("progressing [%.2f%%]", bar1*100.f);
871     }
872 
873     progressing = true;
874     loopi(2) { drawnoview(); swapbuffers(false); }
875     progressing = false;
876 }
877 
878 
879 bool pixeling = false;
880 bvec pixel(0, 0, 0);
881 char *pixelact = NULL;
882 
883 ICOMMAND(0, printpixel, "", (void), conoutft(CON_SELF, "pixel = 0x%.6X (%d, %d, %d)", pixel.tohexcolor(), pixel.r, pixel.g, pixel.b));
884 ICOMMAND(0, getpixel, "i", (int *n), {
885     switch(*n)
886     {
887         case 1: intret(pixel.r); break;
888         case 2: intret(pixel.g); break;
889         case 3: intret(pixel.b); break;
890         case 0: default: intret(pixel.tohexcolor()); break;
891     }
892 });
893 
894 void readpixel(char *act)
895 {
896     if(pixeling) return;
897     if(!editmode) { conoutf("\froperation only allowed in edit mode"); return; }
898     if(pixelact) delete[] pixelact;
899     pixelact = act && *act ? newstring(act) : NULL;
900     pixeling = true;
901 }
902 ICOMMAND(0, readpixel, "s", (char *act), readpixel(act));
903 
904 VAR(0, numcpus, 1, 1, 16);
905 
906 int main(int argc, char **argv)
907 {
908     #ifdef WIN32
909     //atexit((void (__cdecl *)(void))_CrtDumpMemoryLeaks);
910     #ifndef _DEBUG
911     #ifndef __GNUC__
912     __try {
913     #endif
914     #endif
915     #endif
916 
917     currenttime = time(NULL); // initialise
918     clocktime = mktime(gmtime(&currenttime));
919     clockoffset = currenttime-clocktime;
920 
921     setlogfile(NULL);
922     setlocations(true);
923     setverinfo(argv[0]);
924 
925     char *initscript = NULL;
926     initing = INIT_RESET;
927 
928     // redeclipse:// URI support
929     // examples:
930     // redeclipse://password@hostname:port
931     // redeclipse://hostname:port
932     // redeclipse://hostname
933     // (password and port are optional)
934     const char reprotoprefix[] = "redeclipse://";
935     const int reprotolen = sizeof(reprotoprefix) - 1;
936     char *reprotoarg = NULL;
937     char *connectstr = NULL;
938     char *connectpassword = NULL;
939     char *connecthost = NULL;
940     int connectport = SERVER_PORT;
941 
942     // try to parse home directory argument
943     // (has to be parsed first to be able to set the logfile path correctly)
944     for(int i = 1; i<argc; i++)
945     {
946         if(argv[i][0]=='-') switch(argv[i][1])
947         {
948             case 'h': serveroption(argv[i]); break;
949         }
950     }
951     setlogfile("log.txt");
952     execfile("init.cfg", false);
953 
954     // parse the rest of the arguments
955     for(int i = 1; i<argc; i++)
956     {
957         // switches that can modify both server and client behavior
958         if(argv[i][0]=='-') switch(argv[i][1])
959         {
960             case 'h': /* parsed first */ break;
961             case 'r': /* compat, ignore */ break;
962             case 'd':
963             {
964                 switch(argv[i][2])
965                 {
966                     case 'w': scr_w = clamp(atoi(&argv[i][3]), SCR_MINW, SCR_MAXW); if(!findarg(argc, argv, "-dh")) scr_h = -1; break;
967                     case 'h': scr_h = clamp(atoi(&argv[i][3]), SCR_MINH, SCR_MAXH); if(!findarg(argc, argv, "-dw")) scr_w = -1; break;
968                     case 'd': depthbits = atoi(&argv[i][3]); break;
969                     case 'c': /* compat, ignore */ break;
970                     case 'a': fsaa = atoi(&argv[i][3]); break;
971                     case 'v': /* compat, ignore */ break;
972                     case 'f': fullscreen = atoi(&argv[i][3]); break;
973                     case 's': /* compat, ignore */ break;
974                     case 'u': /* compat, ignore */ break;
975                     default: conoutf("\frunknown display option %c", argv[i][2]); break;
976                 }
977                 break;
978             }
979             case 'x': initscript = &argv[i][2]; break;
980             default:
981                 if(!serveroption(argv[i])) gameargs.add(argv[i]);
982                 break;
983         }
984 
985         // will only parse the first argument that is possibly a redeclipse:// URL argument and ignore any following
986         else if(!strncmp(argv[i], reprotoprefix, reprotolen) && !reprotoarg)
987         {
988             reprotoarg = newstring(argv[i]);
989             connectstr = newstring(reprotoarg + reprotolen);
990 
991             // check if there's actually text after the protocol prefix
992             if(!*connectstr) continue;
993 
994             // skip trailing slashes (if any)
995             char* slashchr = strchr(connectstr, '/');
996             if(slashchr) *slashchr = '\0';
997 
998             connecthost = strchr(connectstr, '@');
999             if(connecthost)
1000             {
1001                 connectpassword = connectstr;
1002                 *connecthost++ = '\0';
1003             }
1004             else connecthost = connectstr;
1005 
1006             char *portbuf = strchr(connecthost, ':');
1007             if(portbuf)
1008             {
1009                 *portbuf++ = '\0';
1010                 connectport = parseint(portbuf);
1011                 if(!connectport) connectport = SERVER_PORT;
1012             }
1013         }
1014 
1015         // unmatched arguments
1016         else gameargs.add(argv[i]);
1017     }
1018 
1019     initing = NOT_INITING;
1020 
1021     numcpus = clamp(SDL_GetCPUCount(), 1, 16);
1022 
1023     conoutf("loading enet..");
1024     if(enet_initialize()<0) fatal("Unable to initialise network module");
1025     atexit(enet_deinitialize);
1026     enet_time_set(0);
1027 
1028     conoutf("loading game..");
1029     initgame();
1030 
1031     conoutf("loading sdl..");
1032 
1033     //#ifdef WIN32
1034     //SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
1035     //#endif
1036 
1037     if(SDL_Init(SDL_INIT_TIMER|SDL_INIT_VIDEO|SDL_INIT_AUDIO) < 0) fatal("error initialising SDL: %s", SDL_GetError());
1038 
1039     conoutf("loading video..");
1040     setcaption("please wait..");
1041     SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "0");
1042     SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "0");
1043     setupscreen();
1044     SDL_ShowCursor(SDL_FALSE);
1045     SDL_StopTextInput(); // workaround for spurious text-input events getting sent on first text input toggle?
1046 
1047     signal(SIGINT, fatalsignal);
1048     signal(SIGILL, fatalsignal);
1049     signal(SIGABRT, fatalsignal);
1050     signal(SIGFPE, fatalsignal);
1051     signal(SIGSEGV, fatalsignal);
1052     signal(SIGTERM, shutdownsignal);
1053 #if !defined(WIN32) && !defined(__APPLE__)
1054     signal(SIGHUP, reloadsignal);
1055     signal(SIGQUIT, fatalsignal);
1056     signal(SIGKILL, fatalsignal);
1057     signal(SIGPIPE, fatalsignal);
1058     signal(SIGALRM, fatalsignal);
1059 #endif
1060 
1061     conoutf("loading gl..");
1062     gl_checkextensions();
1063     gl_init();
1064     if(!(notexture = textureload(notexturetex)) || !(blanktexture = textureload(blanktex)))
1065         fatal("could not find core textures");
1066 
1067     conoutf("loading sound..");
1068     initsound();
1069 
1070     game::start();
1071 
1072     conoutf("loading defaults..");
1073     if(!execfile("config/stdlib.cfg", false)) fatal("cannot find data files");
1074     if(!setfont("default")) fatal("no default font specified");
1075     inbetweenframes = true;
1076     progress(0, "please wait..");
1077 
1078     conoutf("loading world..");
1079     progress(0, "loading world..");
1080     emptymap(0, true, NULL, true);
1081 
1082     conoutf("loading config..");
1083     progress(0, "loading config..");
1084     rehash(false);
1085     smartmusic(true, true);
1086 
1087     conoutf("loading required data..");
1088     progress(0, "loading required data..");
1089     restoregamma();
1090     restorevsync();
1091     loadshaders();
1092     preloadtextures();
1093     initparticles();
1094     initdecals();
1095 
1096     trytofindocta();
1097     conoutf("loading main..");
1098     progress(0, "loading main..");
1099     if(initscript) execute(initscript, true);
1100 
1101     capslockon = capslocked();
1102     numlockon = numlocked();
1103     ignoremousemotion();
1104 
1105     localconnect(false);
1106     resetfps();
1107 
1108     if(reprotoarg)
1109     {
1110         if(connecthost && *connecthost) connectserv(connecthost, connectport, connectpassword);
1111         else conoutf("\frmalformed commandline argument: %s", reprotoarg);
1112     }
1113 
1114     // housekeeping
1115     if(connectstr)
1116     {
1117         delete[] connectstr;
1118         connectstr = NULL;
1119     }
1120     if(reprotoarg)
1121     {
1122         delete[] reprotoarg;
1123         reprotoarg = NULL;
1124     }
1125 
1126     for(int frameloops = 0; ; frameloops = frameloops >= INT_MAX-1 ? MAXFPSHISTORY+1 : frameloops+1)
1127     {
1128         curtextscale = textscale;
1129         int elapsed = updatetimer(true);
1130         updatefps(frameloops, elapsed);
1131         checkinput();
1132         menuprocess();
1133 
1134         if(frameloops)
1135         {
1136             RUNWORLD("on_update");
1137             game::updateworld();
1138         }
1139 
1140         checksleep(lastmillis);
1141         serverslice();
1142         ircslice();
1143         if(frameloops)
1144         {
1145             game::recomputecamera(screenw, screenh);
1146             hud::update(screenw, screenh);
1147             setviewcell(camera1->o);
1148             updatetextures();
1149             updateparticles();
1150             updatesounds();
1151             UI::update();
1152             if(!minimized)
1153             {
1154                 inbetweenframes = renderedframe = false;
1155                 gl_drawframe();
1156                 renderedframe = true;
1157                 swapbuffers();
1158                 inbetweenframes = true;
1159             }
1160             if(pixeling)
1161             {
1162                 if(editmode)
1163                 {
1164                     glReadPixels(screenw/2, screenh/2, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &pixel.v[0]);
1165                     if(pixelact) execute(pixelact);
1166                 }
1167                 if(pixelact) delete[] pixelact;
1168                 pixelact = NULL;
1169                 pixeling = false;
1170             }
1171             if(*progresstitle || progressamt > 0)
1172             {
1173                 setsvar("progresstitle", "");
1174                 setsvar("progresstext", "");
1175                 setfvar("progressamt", 0.f);
1176                 setfvar("progresspart", 0.f);
1177             }
1178             setcaption(game::gametitle(), game::gametext());
1179         }
1180     }
1181 
1182     ASSERT(0);
1183     return EXIT_FAILURE;
1184 
1185 #if defined(WIN32) && !defined(_DEBUG) && !defined(__GNUC__)
1186     } __except(stackdumper(0, GetExceptionInformation()), EXCEPTION_CONTINUE_SEARCH) { return 0; }
1187 #endif
1188 }
1189