1 // main.cpp: initialisation & main loop
2 
3 #include "engine.h"
4 
5 extern void cleargamma();
6 
cleanup()7 void cleanup()
8 {
9     recorder::stop();
10     cleanupserver();
11     SDL_ShowCursor(SDL_TRUE);
12     SDL_SetRelativeMouseMode(SDL_FALSE);
13     if(screen) SDL_SetWindowGrab(screen, SDL_FALSE);
14     cleargamma();
15     freeocta(worldroot);
16     UI::cleanup();
17     extern void clear_command(); clear_command();
18     extern void clear_console(); clear_console();
19     extern void clear_models();  clear_models();
20     extern void clear_sound();   clear_sound();
21     closelogfile();
22     ovr::destroy();
23     #ifdef __APPLE__
24         if(screen) SDL_SetWindowFullscreen(screen, 0);
25     #endif
26     SDL_Quit();
27 }
28 
29 extern void writeinitcfg();
30 
quit()31 void quit()                     // normal exit
32 {
33     writeinitcfg();
34     writeservercfg();
35     abortconnect();
36     disconnect();
37     localdisconnect();
38     writecfg();
39     cleanup();
40     exit(EXIT_SUCCESS);
41 }
42 
fatal(const char * s,...)43 void fatal(const char *s, ...)    // failure exit
44 {
45     static int errors = 0;
46     errors++;
47 
48     if(errors <= 2) // print up to one extra recursive error
49     {
50         defvformatstring(msg,s,s);
51         logoutf("%s", msg);
52 
53         if(errors <= 1) // avoid recursion
54         {
55             if(SDL_WasInit(SDL_INIT_VIDEO))
56             {
57                 SDL_ShowCursor(SDL_TRUE);
58                 SDL_SetRelativeMouseMode(SDL_FALSE);
59                 if(screen) SDL_SetWindowGrab(screen, SDL_FALSE);
60                 cleargamma();
61                 #ifdef __APPLE__
62                     if(screen) SDL_SetWindowFullscreen(screen, 0);
63                 #endif
64             }
65             SDL_Quit();
66             SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Tesseract fatal error", msg, NULL);
67         }
68     }
69 
70     exit(EXIT_FAILURE);
71 }
72 
73 VAR(desktopw, 1, 0, 0);
74 VAR(desktoph, 1, 0, 0);
75 int screenw = 0, screenh = 0;
76 SDL_Window *screen = NULL;
77 SDL_GLContext glcontext = NULL;
78 
79 int curtime = 0, lastmillis = 1, elapsedtime = 0, totalmillis = 1;
80 
81 dynent *player = NULL;
82 
83 int initing = NOT_INITING;
84 
initwarning(const char * desc,int level,int type)85 bool initwarning(const char *desc, int level, int type)
86 {
87     if(initing < level)
88     {
89         addchange(desc, type);
90         return true;
91     }
92     return false;
93 }
94 
95 #define SCR_MINW 320
96 #define SCR_MINH 200
97 #define SCR_MAXW 10000
98 #define SCR_MAXH 10000
99 #define SCR_DEFAULTW 1024
100 #define SCR_DEFAULTH 768
101 VARFN(screenw, scr_w, SCR_MINW, -1, SCR_MAXW, initwarning("screen resolution"));
102 VARFN(screenh, scr_h, SCR_MINH, -1, SCR_MAXH, initwarning("screen resolution"));
103 
writeinitcfg()104 void writeinitcfg()
105 {
106     stream *f = openutf8file("config/init.cfg", "w");
107     if(!f) return;
108     f->printf("// automatically written on exit, DO NOT MODIFY\n// modify settings in game\n");
109     extern int fullscreen;
110     f->printf("fullscreen %d\n", fullscreen);
111     f->printf("screenw %d\n", scr_w);
112     f->printf("screenh %d\n", scr_h);
113     extern int sound, soundchans, soundfreq, soundbufferlen;
114     f->printf("sound %d\n", sound);
115     f->printf("soundchans %d\n", soundchans);
116     f->printf("soundfreq %d\n", soundfreq);
117     f->printf("soundbufferlen %d\n", soundbufferlen);
118     delete f;
119 }
120 
121 COMMAND(quit, "");
122 
getbackgroundres(int & w,int & h)123 static void getbackgroundres(int &w, int &h)
124 {
125     float wk = 1, hk = 1;
126     if(w < 1024) wk = 1024.0f/w;
127     if(h < 768) hk = 768.0f/h;
128     wk = hk = max(wk, hk);
129     w = int(ceil(w*wk));
130     h = int(ceil(h*hk));
131 }
132 
133 string backgroundcaption = "";
134 Texture *backgroundmapshot = NULL;
135 string backgroundmapname = "";
136 char *backgroundmapinfo = NULL;
137 
bgquad(float x,float y,float w,float h,float tx=0,float ty=0,float tw=1,float th=1)138 void bgquad(float x, float y, float w, float h, float tx = 0, float ty = 0, float tw = 1, float th = 1)
139 {
140     gle::begin(GL_TRIANGLE_STRIP);
141     gle::attribf(x,   y);   gle::attribf(tx,      ty);
142     gle::attribf(x+w, y);   gle::attribf(tx + tw, ty);
143     gle::attribf(x,   y+h); gle::attribf(tx,      ty + th);
144     gle::attribf(x+w, y+h); gle::attribf(tx + tw, ty + th);
145     gle::end();
146 }
147 
renderbackgroundview(int w,int h,const char * caption,Texture * mapshot,const char * mapname,const char * mapinfo)148 void renderbackgroundview(int w, int h, const char *caption, Texture *mapshot, const char *mapname, const char *mapinfo)
149 {
150     static int lastupdate = -1, lastw = -1, lasth = -1;
151     static float backgroundu = 0, backgroundv = 0;
152     if((renderedframe && !mainmenu && lastupdate != lastmillis) || lastw != w || lasth != h)
153     {
154         lastupdate = lastmillis;
155         lastw = w;
156         lasth = h;
157 
158         backgroundu = rndscale(1);
159         backgroundv = rndscale(1);
160     }
161     else if(lastupdate != lastmillis) lastupdate = lastmillis;
162 
163     hudmatrix.ortho(0, w, h, 0, -1, 1);
164     resethudmatrix();
165     hudshader->set();
166 
167     gle::defvertex(2);
168     gle::deftexcoord0();
169 
170     gle::colorf(1, 1, 1);
171     settexture("media/interface/background.png", 0);
172     float bu = w*0.67f/256.0f, bv = h*0.67f/256.0f;
173     bgquad(0, 0, w, h, backgroundu, backgroundv, bu, bv);
174 
175     glEnable(GL_BLEND);
176     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
177 
178     settexture("media/interface/shadow.png", 3);
179     bgquad(0, 0, w, h);
180 
181     glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
182 
183     float lh = 0.5f*min(w, h), lw = lh*2,
184           lx = 0.5f*(w - lw), ly = 0.5f*(h*0.5f - lh);
185     settexture((maxtexsize ? min(maxtexsize, hwtexsize) : hwtexsize) >= 1024 && (hudw > 1280 || hudh > 800) ? "<premul>media/interface/logo_1024.png" : "<premul>media/interface/logo.png", 3);
186     bgquad(lx, ly, lw, lh);
187 
188     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
189 
190     if(caption)
191     {
192         int tw = text_width(caption);
193         float tsz = 0.04f*min(w, h)/FONTH,
194               tx = 0.5f*(w - tw*tsz), ty = h - 0.075f*1.5f*min(w, h) - FONTH*tsz;
195         pushhudmatrix();
196         hudmatrix.translate(tx, ty, 0);
197         hudmatrix.scale(tsz, tsz, 1);
198         flushhudmatrix();
199         draw_text(caption, 0, 0);
200         pophudmatrix();
201     }
202     if(mapshot || mapname)
203     {
204         float infowidth = 14*FONTH, sz = 0.35f*min(w, h), msz = (0.85f*min(w, h) - sz)/(infowidth + FONTH), x = 0.5f*w, y = ly+lh - sz/15, mx = 0, my = 0, mw = 0, mh = 0;
205         if(mapinfo)
206         {
207             text_boundsf(mapinfo, mw, mh, infowidth);
208             x -= 0.5f*mw*msz;
209             if(mapshot && mapshot!=notexture)
210             {
211                 x -= 0.5f*FONTH*msz;
212                 mx = sz + FONTH*msz;
213             }
214         }
215         if(mapshot && mapshot!=notexture)
216         {
217             x -= 0.5f*sz;
218             glBindTexture(GL_TEXTURE_2D, mapshot->id);
219             bgquad(x, y, sz, sz);
220         }
221         if(mapname)
222         {
223             float tw = text_widthf(mapname), tsz = sz/(8*FONTH), tx = max(0.5f*(mw*msz - tw*tsz), 0.0f);
224             pushhudmatrix();
225             hudmatrix.translate(x+mx+tx, y, 0);
226             hudmatrix.scale(tsz, tsz, 1);
227             flushhudmatrix();
228             draw_text(mapname, 0, 0);
229             pophudmatrix();
230             my = 1.5f*FONTH*tsz;
231         }
232         if(mapinfo)
233         {
234             pushhudmatrix();
235             hudmatrix.translate(x+mx, y+my, 0);
236             hudmatrix.scale(msz, msz, 1);
237             flushhudmatrix();
238             draw_text(mapinfo, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, -1, infowidth);
239             pophudmatrix();
240         }
241     }
242 
243     glDisable(GL_BLEND);
244 
245     gle::disable();
246 }
247 
renderbackground(const char * caption,Texture * mapshot,const char * mapname,const char * mapinfo,bool force)248 void renderbackground(const char *caption, Texture *mapshot, const char *mapname, const char *mapinfo, bool force)
249 {
250     if(!inbetweenframes && !force) return;
251 
252     stopsounds(); // stop sounds while loading
253 
254     int w = hudw, h = hudh;
255     if(forceaspect) w = int(ceil(h*forceaspect));
256     getbackgroundres(w, h);
257     gettextres(w, h);
258 
259     if(force)
260     {
261         renderbackgroundview(w, h, caption, mapshot, mapname, mapinfo);
262         return;
263     }
264 
265     loopi(3)
266     {
267         if(ovr::enabled)
268         {
269             aspect = forceaspect ? forceaspect : hudw/float(hudh);
270             for(viewidx = 0; viewidx < 2; viewidx++, hudx += hudw)
271             {
272                 if(!i)
273                 {
274                     glBindFramebuffer_(GL_FRAMEBUFFER, ovr::lensfbo[viewidx]);
275                     glViewport(0, 0, hudw, hudh);
276                     glClearColor(0, 0, 0, 0);
277                     glClear(GL_COLOR_BUFFER_BIT);
278                     renderbackgroundview(w, h, caption, mapshot, mapname, mapinfo);
279                 }
280                 ovr::warp();
281             }
282             viewidx = 0;
283             hudx = 0;
284         }
285         else renderbackgroundview(w, h, caption, mapshot, mapname, mapinfo);
286         swapbuffers(false);
287     }
288 
289     renderedframe = false;
290     copystring(backgroundcaption, caption ? caption : "");
291     backgroundmapshot = mapshot;
292     copystring(backgroundmapname, mapname ? mapname : "");
293     if(mapinfo != backgroundmapinfo)
294     {
295         DELETEA(backgroundmapinfo);
296         if(mapinfo) backgroundmapinfo = newstring(mapinfo);
297     }
298 }
299 
restorebackground(int w,int h)300 void restorebackground(int w, int h)
301 {
302     if(renderedframe) return;
303     renderbackgroundview(w, h, backgroundcaption[0] ? backgroundcaption : NULL, backgroundmapshot, backgroundmapname[0] ? backgroundmapname : NULL, backgroundmapinfo);
304 }
305 
306 float loadprogress = 0;
307 
renderprogressview(int w,int h,float bar,const char * text)308 void renderprogressview(int w, int h, float bar, const char *text)   // also used during loading
309 {
310     hudmatrix.ortho(0, w, h, 0, -1, 1);
311     resethudmatrix();
312     hudshader->set();
313 
314     gle::defvertex(2);
315     gle::deftexcoord0();
316 
317     gle::colorf(1, 1, 1);
318 
319     float fh = 0.060f*min(w, h), fw = fh*15,
320           fx = renderedframe ? w - fw - fh/4 : 0.5f*(w - fw),
321           fy = renderedframe ? fh/4 : h - fh*1.5f;
322     settexture("media/interface/loading_frame.png", 3);
323     bgquad(fx, fy, fw, fh);
324 
325     glEnable(GL_BLEND);
326     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
327 
328     float bw = fw*(512 - 2*8)/512.0f, bh = fh*20/32.0f,
329           bx = fx + fw*8/512.0f, by = fy + fh*6/32.0f,
330           su1 = 0/32.0f, su2 = 8/32.0f, sw = fw*8/512.0f,
331           eu1 = 24/32.0f, eu2 = 32/32.0f, ew = fw*8/512.0f,
332           mw = bw - sw - ew,
333           ex = bx+sw + max(mw*bar, fw*8/512.0f);
334     if(bar > 0)
335     {
336         settexture("media/interface/loading_bar.png", 3);
337         bgquad(bx, by, sw, bh, su1, 0, su2-su1, 1);
338         bgquad(bx+sw, by, ex-(bx+sw), bh, su2, 0, eu1-su2, 1);
339         bgquad(ex, by, ew, bh, eu1, 0, eu2-eu1, 1);
340     }
341 
342     if(text)
343     {
344         int tw = text_width(text);
345         float tsz = bh*0.6f/FONTH;
346         if(tw*tsz > mw) tsz = mw/tw;
347         pushhudmatrix();
348         hudmatrix.translate(bx+sw, by + (bh - FONTH*tsz)/2, 0);
349         hudmatrix.scale(tsz, tsz, 1);
350         flushhudmatrix();
351         draw_text(text, 0, 0);
352         pophudmatrix();
353     }
354 
355     glDisable(GL_BLEND);
356 
357     gle::disable();
358 }
359 
renderprogress(float bar,const char * text,bool background)360 void renderprogress(float bar, const char *text, bool background)   // also used during loading
361 {
362     if(!inbetweenframes || drawtex) return;
363 
364     clientkeepalive();      // make sure our connection doesn't time out while loading maps etc.
365 
366     #ifdef __APPLE__
367     interceptkey(SDLK_UNKNOWN); // keep the event queue awake to avoid 'beachball' cursor
368     #endif
369 
370     int w = hudw, h = hudh;
371     if(forceaspect) w = int(ceil(h*forceaspect));
372     getbackgroundres(w, h);
373     gettextres(w, h);
374 
375     if(ovr::enabled)
376     {
377         aspect = forceaspect ? forceaspect : hudw/float(hudh);
378         for(viewidx = 0; viewidx < 2; viewidx++, hudx += hudw)
379         {
380             glBindFramebuffer_(GL_FRAMEBUFFER, ovr::lensfbo[viewidx]);
381             glViewport(0, 0, hudw, hudh);
382             if(background)
383             {
384                 glClearColor(0, 0, 0, 0);
385                 glClear(GL_COLOR_BUFFER_BIT);
386                 restorebackground(w, h);
387             }
388             renderprogressview(w, h, bar, text);
389             ovr::warp();
390         }
391         viewidx = 0;
392         hudx = 0;
393     }
394     else
395     {
396         if(background) restorebackground(w, h);
397         renderprogressview(w, h, bar, text);
398     }
399     swapbuffers(false);
400 }
401 
402 VARNP(relativemouse, userelativemouse, 0, 1, 1);
403 
404 bool shouldgrab = false, grabinput = false, minimized = false, canrelativemouse = true, relativemouse = false;
405 int keyrepeatmask = 0, textinputmask = 0;
406 
keyrepeat(bool on,int mask)407 void keyrepeat(bool on, int mask)
408 {
409     if(on) keyrepeatmask |= mask;
410     else keyrepeatmask &= ~mask;
411 }
412 
textinput(bool on,int mask)413 void textinput(bool on, int mask)
414 {
415     if(on)
416     {
417         if(!textinputmask) SDL_StartTextInput();
418         textinputmask |= mask;
419     }
420     else
421     {
422         textinputmask &= ~mask;
423         if(!textinputmask) SDL_StopTextInput();
424     }
425 }
426 
inputgrab(bool on)427 void inputgrab(bool on)
428 {
429     if(on)
430     {
431         SDL_ShowCursor(SDL_FALSE);
432         if(canrelativemouse && userelativemouse)
433         {
434             if(SDL_SetRelativeMouseMode(SDL_TRUE) >= 0)
435             {
436                 SDL_SetWindowGrab(screen, SDL_TRUE);
437                 relativemouse = true;
438             }
439             else
440             {
441                 SDL_SetWindowGrab(screen, SDL_FALSE);
442                 canrelativemouse = false;
443                 relativemouse = false;
444             }
445         }
446     }
447     else
448     {
449         SDL_ShowCursor(SDL_TRUE);
450         if(relativemouse)
451         {
452             SDL_SetRelativeMouseMode(SDL_FALSE);
453             SDL_SetWindowGrab(screen, SDL_FALSE);
454             relativemouse = false;
455         }
456     }
457     shouldgrab = false;
458 }
459 
460 bool initwindowpos = false;
461 
setfullscreen(bool enable)462 void setfullscreen(bool enable)
463 {
464     if(!screen) return;
465     //initwarning(enable ? "fullscreen" : "windowed");
466     SDL_SetWindowFullscreen(screen, enable ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
467     if(!enable)
468     {
469         SDL_SetWindowSize(screen, scr_w, scr_h);
470         if(initwindowpos)
471         {
472             int winx = SDL_WINDOWPOS_CENTERED, winy = SDL_WINDOWPOS_CENTERED;
473             if(ovr::enabled) winx = winy = 0;
474             SDL_SetWindowPosition(screen, winx, winy);
475             initwindowpos = false;
476         }
477     }
478 }
479 
480 #ifdef _DEBUG
481 VARF(fullscreen, 0, 0, 1, setfullscreen(fullscreen!=0));
482 #else
483 VARF(fullscreen, 0, 1, 1, setfullscreen(fullscreen!=0));
484 #endif
485 
screenres(int w,int h)486 void screenres(int w, int h)
487 {
488     scr_w = clamp(w, SCR_MINW, SCR_MAXW);
489     scr_h = clamp(h, SCR_MINH, SCR_MAXH);
490     if(screen)
491     {
492         scr_w = min(scr_w, desktopw);
493         scr_h = min(scr_h, desktoph);
494         if(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN) gl_resize();
495         else SDL_SetWindowSize(screen, scr_w, scr_h);
496     }
497     else
498     {
499         initwarning("screen resolution");
500     }
501 }
502 
503 ICOMMAND(screenres, "ii", (int *w, int *h), screenres(*w, *h));
504 
setgamma(int val)505 static void setgamma(int val)
506 {
507     if(screen && SDL_SetWindowBrightness(screen, val/100.0f) < 0) conoutf(CON_ERROR, "Could not set gamma: %s", SDL_GetError());
508 }
509 
510 static int curgamma = 100;
511 VARFP(gamma, 30, 100, 300,
512 {
513     if(initing || gamma == curgamma) return;
514     curgamma = gamma;
515     setgamma(curgamma);
516 });
517 
restoregamma()518 void restoregamma()
519 {
520     if(initing || curgamma == 100) return;
521     setgamma(curgamma);
522 }
523 
cleargamma()524 void cleargamma()
525 {
526     if(curgamma != 100 && screen) SDL_SetWindowBrightness(screen, 1.0f);
527 }
528 
restorevsync()529 void restorevsync()
530 {
531     if(initing || !glcontext) return;
532     extern int vsync, vsynctear;
533     SDL_GL_SetSwapInterval(vsync ? (vsynctear ? -1 : 1) : 0);
534 }
535 
536 VARFP(vsync, 0, 0, 1, restorevsync());
537 VARFP(vsynctear, 0, 0, 1, { if(vsync) restorevsync(); });
538 
539 VAR(dbgmodes, 0, 0, 1);
540 
setupscreen()541 void setupscreen()
542 {
543     if(glcontext)
544     {
545         SDL_GL_DeleteContext(glcontext);
546         glcontext = NULL;
547     }
548     if(screen)
549     {
550         SDL_DestroyWindow(screen);
551         screen = NULL;
552     }
553 
554     SDL_DisplayMode desktop;
555     if(SDL_GetDesktopDisplayMode(0, &desktop) < 0) fatal("failed querying desktop display mode: %s", SDL_GetError());
556     desktopw = desktop.w;
557     desktoph = desktop.h;
558 
559     if(scr_h < 0) scr_h = SCR_DEFAULTH;
560     if(scr_w < 0) scr_w = (scr_h*desktopw)/desktoph;
561     scr_w = min(scr_w, desktopw);
562     scr_h = min(scr_h, desktoph);
563 
564     int winx = SDL_WINDOWPOS_UNDEFINED, winy = SDL_WINDOWPOS_UNDEFINED, winw = scr_w, winh = scr_h, flags = SDL_WINDOW_RESIZABLE;
565     if(fullscreen)
566     {
567         winw = desktopw;
568         winh = desktoph;
569         flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
570         initwindowpos = true;
571     }
572     if(ovr::enabled) winx = winy = 0;
573 
574     SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
575     SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);
576     SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
577     screen = SDL_CreateWindow("Tesseract", winx, winy, winw, winh, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS | flags);
578     if(!screen) fatal("failed to create OpenGL window: %s", SDL_GetError());
579 
580     SDL_SetWindowMinimumSize(screen, SCR_MINW, SCR_MINH);
581     SDL_SetWindowMaximumSize(screen, SCR_MAXW, SCR_MAXH);
582 
583     static const struct { int major, minor; } coreversions[] = { { 4, 0 }, { 3, 3 }, { 3, 2 }, { 3, 1 }, { 3, 0 } };
584     loopi(sizeof(coreversions)/sizeof(coreversions[0]))
585     {
586         SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, coreversions[i].major);
587         SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, coreversions[i].minor);
588         SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
589         glcontext = SDL_GL_CreateContext(screen);
590         if(glcontext) break;
591     }
592     if(!glcontext)
593     {
594         SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
595         SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
596         SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);
597         glcontext = SDL_GL_CreateContext(screen);
598         if(!glcontext) fatal("failed to create OpenGL context: %s", SDL_GetError());
599     }
600 
601     SDL_GetWindowSize(screen, &screenw, &screenh);
602     renderw = min(scr_w, screenw);
603     renderh = min(scr_h, screenh);
604     hudw = screenw;
605     hudh = screenh;
606 }
607 
resetgl()608 void resetgl()
609 {
610     clearchanges(CHANGE_GFX|CHANGE_SHADERS);
611 
612     renderbackground("resetting OpenGL");
613 
614     recorder::cleanup();
615     cleanupva();
616     cleanupparticles();
617     cleanupstains();
618     cleanupsky();
619     cleanupmodels();
620     cleanupprefabs();
621     cleanuptextures();
622     cleanupblendmap();
623     cleanuplights();
624     cleanupshaders();
625     cleanupgl();
626 
627     setupscreen();
628 
629     inputgrab(grabinput);
630 
631     gl_init();
632 
633     inbetweenframes = false;
634     if(!reloadtexture(*notexture) ||
635        !reloadtexture("<premul>media/interface/logo.png") ||
636        !reloadtexture("<premul>media/interface/logo_1024.png") ||
637        !reloadtexture("media/interface/background.png") ||
638        !reloadtexture("media/interface/shadow.png") ||
639        !reloadtexture("media/interface/mapshot_frame.png") ||
640        !reloadtexture("media/interface/loading_frame.png") ||
641        !reloadtexture("media/interface/loading_bar.png"))
642         fatal("failed to reload core texture");
643     reloadfonts();
644     inbetweenframes = true;
645     renderbackground("initializing...");
646     restoregamma();
647     restorevsync();
648     initgbuffer();
649     reloadshaders();
650     reloadtextures();
651     initlights();
652     allchanged(true);
653 }
654 
655 COMMAND(resetgl, "");
656 
657 vector<SDL_Event> events;
658 
pushevent(const SDL_Event & e)659 void pushevent(const SDL_Event &e)
660 {
661     events.add(e);
662 }
663 
filterevent(const SDL_Event & event)664 static bool filterevent(const SDL_Event &event)
665 {
666     switch(event.type)
667     {
668         case SDL_MOUSEMOTION:
669             if(grabinput && !relativemouse && !(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN))
670             {
671                 if(event.motion.x == screenw / 2 && event.motion.y == screenh / 2)
672                     return false;  // ignore any motion events generated by SDL_WarpMouse
673                 #ifdef __APPLE__
674                 if(event.motion.y == 0)
675                     return false;  // let mac users drag windows via the title bar
676                 #endif
677             }
678             break;
679     }
680     return true;
681 }
682 
pollevent(SDL_Event & event)683 static inline bool pollevent(SDL_Event &event)
684 {
685     while(SDL_PollEvent(&event))
686     {
687         if(filterevent(event)) return true;
688     }
689     return false;
690 }
691 
interceptkey(int sym)692 bool interceptkey(int sym)
693 {
694     static int lastintercept = SDLK_UNKNOWN;
695     int len = lastintercept == sym ? events.length() : 0;
696     SDL_Event event;
697     while(pollevent(event))
698     {
699         switch(event.type)
700         {
701             case SDL_MOUSEMOTION: break;
702             default: pushevent(event); break;
703         }
704     }
705     lastintercept = sym;
706     if(sym != SDLK_UNKNOWN) for(int i = len; i < events.length(); i++)
707     {
708         if(events[i].type == SDL_KEYDOWN && events[i].key.keysym.sym == sym) { events.remove(i); return true; }
709     }
710     return false;
711 }
712 
ignoremousemotion()713 static void ignoremousemotion()
714 {
715     SDL_Event e;
716     SDL_PumpEvents();
717     while(SDL_PeepEvents(&e, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION));
718 }
719 
resetmousemotion()720 static void resetmousemotion()
721 {
722     if(grabinput && !relativemouse && !(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN))
723     {
724         SDL_WarpMouseInWindow(screen, screenw / 2, screenh / 2);
725     }
726 }
727 
checkmousemotion(int & dx,int & dy)728 static void checkmousemotion(int &dx, int &dy)
729 {
730     loopv(events)
731     {
732         SDL_Event &event = events[i];
733         if(event.type != SDL_MOUSEMOTION)
734         {
735             if(i > 0) events.remove(0, i);
736             return;
737         }
738         dx += event.motion.xrel;
739         dy += event.motion.yrel;
740     }
741     events.setsize(0);
742     SDL_Event event;
743     while(pollevent(event))
744     {
745         if(event.type != SDL_MOUSEMOTION)
746         {
747             events.add(event);
748             return;
749         }
750         dx += event.motion.xrel;
751         dy += event.motion.yrel;
752     }
753 }
754 
checkinput()755 void checkinput()
756 {
757     SDL_Event event;
758     //int lasttype = 0, lastbut = 0;
759     bool mousemoved = false;
760     while(events.length() || pollevent(event))
761     {
762         if(events.length()) event = events.remove(0);
763 
764         switch(event.type)
765         {
766             case SDL_QUIT:
767                 quit();
768                 return;
769 
770             case SDL_TEXTINPUT:
771             {
772                 uchar buf[SDL_TEXTINPUTEVENT_TEXT_SIZE+1];
773                 size_t len = decodeutf8(buf, sizeof(buf)-1, (const uchar *)event.text.text, strlen(event.text.text));
774                 if(len > 0) { buf[len] = '\0'; processtextinput((const char *)buf, len); }
775                 break;
776             }
777 
778             case SDL_KEYDOWN:
779             case SDL_KEYUP:
780                 if(keyrepeatmask || !event.key.repeat)
781                     processkey(event.key.keysym.sym, event.key.state==SDL_PRESSED);
782                 break;
783 
784             case SDL_WINDOWEVENT:
785                 switch(event.window.event)
786                 {
787                     case SDL_WINDOWEVENT_CLOSE:
788                         quit();
789                         break;
790 
791                     case SDL_WINDOWEVENT_FOCUS_GAINED:
792                         shouldgrab = true;
793                         break;
794                     case SDL_WINDOWEVENT_ENTER:
795                         inputgrab(grabinput = true);
796                         break;
797 
798                     case SDL_WINDOWEVENT_LEAVE:
799                     case SDL_WINDOWEVENT_FOCUS_LOST:
800                         inputgrab(grabinput = false);
801                         break;
802 
803                     case SDL_WINDOWEVENT_MINIMIZED:
804                         minimized = true;
805                         break;
806 
807                     case SDL_WINDOWEVENT_MAXIMIZED:
808                     case SDL_WINDOWEVENT_RESTORED:
809                         minimized = false;
810                         break;
811 
812                     case SDL_WINDOWEVENT_RESIZED:
813                         break;
814 
815                     case SDL_WINDOWEVENT_SIZE_CHANGED:
816                         SDL_GetWindowSize(screen, &screenw, &screenh);
817                         if(!(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN))
818                         {
819                             scr_w = clamp(screenw, SCR_MINW, SCR_MAXW);
820                             scr_h = clamp(screenh, SCR_MINH, SCR_MAXH);
821                         }
822                         gl_resize();
823                         break;
824                 }
825                 break;
826 
827             case SDL_MOUSEMOTION:
828                 if(grabinput)
829                 {
830                     int dx = event.motion.xrel, dy = event.motion.yrel;
831                     checkmousemotion(dx, dy);
832                     if(!UI::movecursor(dx, dy)) mousemove(dx, dy);
833                     mousemoved = true;
834                 }
835                 else if(shouldgrab) inputgrab(grabinput = true);
836                 break;
837 
838             case SDL_MOUSEBUTTONDOWN:
839             case SDL_MOUSEBUTTONUP:
840                 //if(lasttype==event.type && lastbut==event.button.button) break; // why?? get event twice without it
841                 switch(event.button.button)
842                 {
843                     case SDL_BUTTON_LEFT: processkey(-1, event.button.state==SDL_PRESSED); break;
844                     case SDL_BUTTON_MIDDLE: processkey(-2, event.button.state==SDL_PRESSED); break;
845                     case SDL_BUTTON_RIGHT: processkey(-3, event.button.state==SDL_PRESSED); break;
846                     case SDL_BUTTON_X1: processkey(-6, event.button.state==SDL_PRESSED); break;
847                     case SDL_BUTTON_X2: processkey(-7, event.button.state==SDL_PRESSED); break;
848                 }
849                 //lasttype = event.type;
850                 //lastbut = event.button.button;
851                 break;
852 
853             case SDL_MOUSEWHEEL:
854                 if(event.wheel.y > 0) { processkey(-4, true); processkey(-4, false); }
855                 else if(event.wheel.y < 0) { processkey(-5, true); processkey(-5, false); }
856                 break;
857         }
858     }
859     if(mousemoved) resetmousemotion();
860 }
861 
swapbuffers(bool overlay)862 void swapbuffers(bool overlay)
863 {
864     recorder::capture(overlay);
865     SDL_GL_SwapWindow(screen);
866 }
867 
868 VAR(menufps, 0, 60, 1000);
869 VARP(maxfps, 0, 125, 1000);
870 
limitfps(int & millis,int curmillis)871 void limitfps(int &millis, int curmillis)
872 {
873     int limit = (mainmenu || minimized) && menufps ? (maxfps ? min(maxfps, menufps) : menufps) : maxfps;
874     if(!limit) return;
875     static int fpserror = 0;
876     int delay = 1000/limit - (millis-curmillis);
877     if(delay < 0) fpserror = 0;
878     else
879     {
880         fpserror += 1000%limit;
881         if(fpserror >= limit)
882         {
883             ++delay;
884             fpserror -= limit;
885         }
886         if(delay > 0)
887         {
888             SDL_Delay(delay);
889             millis += delay;
890         }
891     }
892 }
893 
894 #if defined(WIN32) && !defined(_DEBUG) && !defined(__GNUC__)
stackdumper(unsigned int type,EXCEPTION_POINTERS * ep)895 void stackdumper(unsigned int type, EXCEPTION_POINTERS *ep)
896 {
897     if(!ep) fatal("unknown type");
898     EXCEPTION_RECORD *er = ep->ExceptionRecord;
899     CONTEXT *context = ep->ContextRecord;
900     char out[512];
901     formatstring(out, "Tesseract Win32 Exception: 0x%x [0x%x]\n\n", er->ExceptionCode, er->ExceptionCode==EXCEPTION_ACCESS_VIOLATION ? er->ExceptionInformation[1] : -1);
902     SymInitialize(GetCurrentProcess(), NULL, TRUE);
903 #ifdef _AMD64_
904     STACKFRAME64 sf = {{context->Rip, 0, AddrModeFlat}, {}, {context->Rbp, 0, AddrModeFlat}, {context->Rsp, 0, AddrModeFlat}, 0};
905     while(::StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(), &sf, context, NULL, ::SymFunctionTableAccess, ::SymGetModuleBase, NULL))
906     {
907         union { IMAGEHLP_SYMBOL64 sym; char symext[sizeof(IMAGEHLP_SYMBOL64) + sizeof(string)]; };
908         sym.SizeOfStruct = sizeof(sym);
909         sym.MaxNameLength = sizeof(symext) - sizeof(sym);
910         IMAGEHLP_LINE64 line;
911         line.SizeOfStruct = sizeof(line);
912         DWORD64 symoff;
913         DWORD lineoff;
914         if(SymGetSymFromAddr64(GetCurrentProcess(), sf.AddrPC.Offset, &symoff, &sym) && SymGetLineFromAddr64(GetCurrentProcess(), sf.AddrPC.Offset, &lineoff, &line))
915 #else
916     STACKFRAME sf = {{context->Eip, 0, AddrModeFlat}, {}, {context->Ebp, 0, AddrModeFlat}, {context->Esp, 0, AddrModeFlat}, 0};
917     while(::StackWalk(IMAGE_FILE_MACHINE_I386, GetCurrentProcess(), GetCurrentThread(), &sf, context, NULL, ::SymFunctionTableAccess, ::SymGetModuleBase, NULL))
918     {
919         union { IMAGEHLP_SYMBOL sym; char symext[sizeof(IMAGEHLP_SYMBOL) + sizeof(string)]; };
920         sym.SizeOfStruct = sizeof(sym);
921         sym.MaxNameLength = sizeof(symext) - sizeof(sym);
922         IMAGEHLP_LINE line;
923         line.SizeOfStruct = sizeof(line);
924         DWORD symoff, lineoff;
925         if(SymGetSymFromAddr(GetCurrentProcess(), sf.AddrPC.Offset, &symoff, &sym) && SymGetLineFromAddr(GetCurrentProcess(), sf.AddrPC.Offset, &lineoff, &line))
926 #endif
927         {
928             char *del = strrchr(line.FileName, '\\');
929             concformatstring(out, "%s - %s [%d]\n", sym.Name, del ? del + 1 : line.FileName, line.LineNumber);
930         }
931     }
932     fatal(out);
933 }
934 #endif
935 
936 #define MAXFPSHISTORY 60
937 
938 int fpspos = 0, fpshistory[MAXFPSHISTORY];
939 
940 void resetfpshistory()
941 {
942     loopi(MAXFPSHISTORY) fpshistory[i] = 1;
943     fpspos = 0;
944 }
945 
946 void updatefpshistory(int millis)
947 {
948     fpshistory[fpspos++] = max(1, min(1000, millis));
949     if(fpspos>=MAXFPSHISTORY) fpspos = 0;
950 }
951 
952 void getframemillis(float &avg, float &bestdiff, float &worstdiff)
953 {
954     int total = fpshistory[MAXFPSHISTORY-1], best = total, worst = total;
955     loopi(MAXFPSHISTORY-1)
956     {
957         int millis = fpshistory[i];
958         total += millis;
959         if(millis < best) best = millis;
960         if(millis > worst) worst = millis;
961     }
962 
963     avg = total/float(MAXFPSHISTORY);
964     best = best - avg;
965     worstdiff = avg - worst;
966 }
967 
968 void getfps(int &fps, int &bestdiff, int &worstdiff)
969 {
970     int total = fpshistory[MAXFPSHISTORY-1], best = total, worst = total;
971     loopi(MAXFPSHISTORY-1)
972     {
973         int millis = fpshistory[i];
974         total += millis;
975         if(millis < best) best = millis;
976         if(millis > worst) worst = millis;
977     }
978 
979     fps = (1000*MAXFPSHISTORY)/total;
980     bestdiff = 1000/best-fps;
981     worstdiff = fps-1000/worst;
982 }
983 
984 void getfps_(int *raw)
985 {
986     if(*raw) floatret(1000.0f/fpshistory[(fpspos+MAXFPSHISTORY-1)%MAXFPSHISTORY]);
987     else
988     {
989         int fps, bestdiff, worstdiff;
990         getfps(fps, bestdiff, worstdiff);
991         intret(fps);
992     }
993 }
994 
995 COMMANDN(getfps, getfps_, "i");
996 
997 bool inbetweenframes = false, renderedframe = true;
998 
999 static bool findarg(int argc, char **argv, const char *str)
1000 {
1001     for(int i = 1; i<argc; i++) if(strstr(argv[i], str)==argv[i]) return true;
1002     return false;
1003 }
1004 
1005 static int clockrealbase = 0, clockvirtbase = 0;
1006 static void clockreset() { clockrealbase = SDL_GetTicks(); clockvirtbase = totalmillis; }
1007 VARFP(clockerror, 990000, 1000000, 1010000, clockreset());
1008 VARFP(clockfix, 0, 0, 1, clockreset());
1009 
1010 int getclockmillis()
1011 {
1012     int millis = SDL_GetTicks() - clockrealbase;
1013     if(clockfix) millis = int(millis*(double(clockerror)/1000000));
1014     millis += clockvirtbase;
1015     return max(millis, totalmillis);
1016 }
1017 
1018 VAR(numcpus, 1, 1, 16);
1019 
1020 int main(int argc, char **argv)
1021 {
1022     #ifdef WIN32
1023     //atexit((void (__cdecl *)(void))_CrtDumpMemoryLeaks);
1024     #ifndef _DEBUG
1025     #ifndef __GNUC__
1026     __try {
1027     #endif
1028     #endif
1029     #endif
1030 
1031     setlogfile(NULL);
1032 
1033     int dedicated = 0;
1034     char *load = NULL, *initscript = NULL;
1035 
1036     initing = INIT_RESET;
1037     for(int i = 1; i<argc; i++)
1038     {
1039         if(argv[i][0]=='-') switch(argv[i][1])
1040         {
1041             case 'u':
1042             {
1043                 const char *dir = sethomedir(&argv[i][2]);
1044                 if(dir) logoutf("Using home directory: %s", dir);
1045                 break;
1046             }
1047         }
1048     }
1049     execfile("config/init.cfg", false);
1050     for(int i = 1; i<argc; i++)
1051     {
1052         if(argv[i][0]=='-') switch(argv[i][1])
1053         {
1054             case 'u': /* parsed first */ break;
1055             case 'k':
1056             {
1057                 const char *dir = addpackagedir(&argv[i][2]);
1058                 if(dir) logoutf("Adding package directory: %s", dir);
1059                 break;
1060             }
1061             case 'g': logoutf("Setting log file: %s", &argv[i][2]); setlogfile(&argv[i][2]); break;
1062             case 'd': dedicated = atoi(&argv[i][2]); if(dedicated<=0) dedicated = 2; break;
1063             case 'w': scr_w = clamp(atoi(&argv[i][2]), SCR_MINW, SCR_MAXW); if(!findarg(argc, argv, "-h")) scr_h = -1; break;
1064             case 'h': scr_h = clamp(atoi(&argv[i][2]), SCR_MINH, SCR_MAXH); if(!findarg(argc, argv, "-w")) scr_w = -1; break;
1065             case 'v': vsync = atoi(&argv[i][2]); if(vsync < 0) { vsynctear = 1; vsync = 1; } else vsynctear = 0; break;
1066             case 'f': fullscreen = atoi(&argv[i][2]); break;
1067             case 'l':
1068             {
1069                 char pkgdir[] = "media/";
1070                 load = strstr(path(&argv[i][2]), path(pkgdir));
1071                 if(load) load += sizeof(pkgdir)-1;
1072                 else load = &argv[i][2];
1073                 break;
1074             }
1075             case 'x': initscript = &argv[i][2]; break;
1076             default: if(!serveroption(argv[i])) gameargs.add(argv[i]); break;
1077         }
1078         else gameargs.add(argv[i]);
1079     }
1080 
1081     numcpus = clamp(SDL_GetCPUCount(), 1, 16);
1082 
1083     if(dedicated <= 1)
1084     {
1085         logoutf("init: sdl");
1086 
1087         int par = 0;
1088         #ifdef _DEBUG
1089         par = SDL_INIT_NOPARACHUTE;
1090         #endif
1091 
1092         if(SDL_Init(SDL_INIT_TIMER|SDL_INIT_VIDEO|SDL_INIT_AUDIO|par)<0) fatal("Unable to initialize SDL: %s", SDL_GetError());
1093     }
1094 
1095     logoutf("init: net");
1096     if(enet_initialize()<0) fatal("Unable to initialise network module");
1097     atexit(enet_deinitialize);
1098     enet_time_set(0);
1099 
1100     logoutf("init: game");
1101     game::parseoptions(gameargs);
1102     initserver(dedicated>0, dedicated>1);  // never returns if dedicated
1103     ASSERT(dedicated <= 1);
1104     game::initclient();
1105 
1106     logoutf("init: video");
1107     SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "0");
1108     #if !defined(WIN32) && !defined(__APPLE__)
1109     SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
1110     #endif
1111     setupscreen();
1112     SDL_ShowCursor(SDL_FALSE);
1113     SDL_StopTextInput(); // workaround for spurious text-input events getting sent on first text input toggle?
1114 
1115     logoutf("init: gl");
1116     gl_checkextensions();
1117     gl_init();
1118     notexture = textureload("media/texture/game/notexture.png");
1119     if(!notexture) fatal("could not find core textures");
1120 
1121     logoutf("init: console");
1122     if(!execfile("config/stdlib.cfg", false)) fatal("cannot find data files (you are running from the wrong folder, try .bat file in the main folder)");   // this is the first file we load.
1123     if(!execfile("config/font.cfg", false)) fatal("cannot find font definitions");
1124     if(!setfont("default")) fatal("no default font specified");
1125 
1126     UI::setup();
1127 
1128     inbetweenframes = true;
1129     renderbackground("initializing...");
1130 
1131     logoutf("init: world");
1132     camera1 = player = game::iterdynents(0);
1133     emptymap(0, true, NULL, false);
1134 
1135     logoutf("init: sound");
1136     initsound();
1137 
1138     logoutf("init: cfg");
1139     initing = INIT_LOAD;
1140     execfile("config/keymap.cfg");
1141     execfile("config/stdedit.cfg");
1142     execfile(game::gameconfig());
1143     execfile("config/sound.cfg");
1144     execfile("config/ui.cfg");
1145     execfile("config/heightmap.cfg");
1146     execfile("config/blendbrush.cfg");
1147     if(game::savedservers()) execfile(game::savedservers(), false);
1148 
1149     identflags |= IDF_PERSIST;
1150 
1151     if(!execfile(game::savedconfig(), false))
1152     {
1153         execfile(game::defaultconfig());
1154         writecfg(game::restoreconfig());
1155     }
1156     execfile(game::autoexec(), false);
1157 
1158     identflags &= ~IDF_PERSIST;
1159 
1160     initing = INIT_GAME;
1161     game::loadconfigs();
1162 
1163     initing = NOT_INITING;
1164 
1165     logoutf("init: render");
1166     restoregamma();
1167     restorevsync();
1168     initgbuffer();
1169     loadshaders();
1170     initparticles();
1171     initstains();
1172 
1173     identflags |= IDF_PERSIST;
1174 
1175     logoutf("init: mainloop");
1176 
1177     if(execfile("once.cfg", false)) remove(findfile("once.cfg", "rb"));
1178 
1179     if(load)
1180     {
1181         logoutf("init: localconnect");
1182         //localconnect();
1183         game::changemap(load);
1184     }
1185 
1186     if(initscript) execute(initscript);
1187 
1188     initmumble();
1189     resetfpshistory();
1190 
1191     inputgrab(grabinput = true);
1192     ignoremousemotion();
1193 
1194     for(;;)
1195     {
1196         static int frames = 0;
1197         int millis = getclockmillis();
1198         limitfps(millis, totalmillis);
1199         elapsedtime = millis - totalmillis;
1200         static int timeerr = 0;
1201         int scaledtime = game::scaletime(elapsedtime) + timeerr;
1202         curtime = scaledtime/100;
1203         timeerr = scaledtime%100;
1204         if(!multiplayer(false) && curtime>200) curtime = 200;
1205         if(game::ispaused()) curtime = 0;
1206         lastmillis += curtime;
1207         totalmillis = millis;
1208         updatetime();
1209 
1210         checkinput();
1211         ovr::update();
1212         UI::update();
1213         menuprocess();
1214         tryedit();
1215 
1216         if(lastmillis) game::updateworld();
1217 
1218         checksleep(lastmillis);
1219 
1220         serverslice(false, 0);
1221 
1222         if(frames) updatefpshistory(elapsedtime);
1223         frames++;
1224 
1225         // miscellaneous general game effects
1226         recomputecamera();
1227         updateparticles();
1228         updatesounds();
1229 
1230         if(minimized) continue;
1231 
1232         gl_setupframe(!mainmenu);
1233 
1234         inbetweenframes = false;
1235         gl_drawframe();
1236         swapbuffers();
1237         renderedframe = inbetweenframes = true;
1238     }
1239 
1240     ASSERT(0);
1241     return EXIT_FAILURE;
1242 
1243     #if defined(WIN32) && !defined(_DEBUG) && !defined(__GNUC__)
1244     } __except(stackdumper(0, GetExceptionInformation()), EXCEPTION_CONTINUE_SEARCH) { return 0; }
1245     #endif
1246 }
1247