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(¤ttime));
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