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