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