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