// main.cpp: initialisation & main loop #include "engine.h" #ifdef SDL_VIDEO_DRIVER_X11 #include "SDL_syswm.h" #endif float speedmodifier = 0.0f; extern void cleargamma(); void cleanup() { cleanupserver(); SDL_ShowCursor(SDL_TRUE); SDL_SetRelativeMouseMode(SDL_FALSE); if(screen) SDL_SetWindowGrab(screen, SDL_FALSE); cleargamma(); freeocta(worldroot); extern void clear_command(); clear_command(); extern void clear_console(); clear_console(); extern void clear_mdls(); clear_mdls(); extern void clear_sound(); clear_sound(); closelogfile(); #ifdef __APPLE__ if(screen) SDL_SetWindowFullscreen(screen, 0); #endif SDL_Quit(); } extern void writeinitcfg(); void quit() // normal exit { writeinitcfg(); writeservercfg(); abortconnect(); disconnect(); localdisconnect(); writecfg(); cleanup(); exit(EXIT_SUCCESS); } void fatal(const char *s, ...) // failure exit { static int errors = 0; errors++; if(errors <= 2) // print up to one extra recursive error { defvformatstring(msg,s,s); logoutf("%s", msg); if(errors <= 1) // avoid recursion { if(SDL_WasInit(SDL_INIT_VIDEO)) { SDL_ShowCursor(SDL_TRUE); SDL_SetRelativeMouseMode(SDL_FALSE); if(screen) SDL_SetWindowGrab(screen, SDL_FALSE); cleargamma(); #ifdef __APPLE__ if(screen) SDL_SetWindowFullscreen(screen, 0); #endif } SDL_Quit(); SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Cube 2: Sauerbraten fatal error", msg, NULL); } } exit(EXIT_FAILURE); } int curtime = 0, lastmillis = 1, elapsedtime = 0, totalmillis = 1; dynent *player = NULL; int initing = NOT_INITING; bool initwarning(const char *desc, int level, int type) { if(initing < level) { addchange(desc, type); return true; } return false; } VAR(desktopw, 1, 0, 0); VAR(desktoph, 1, 0, 0); int screenw = 0, screenh = 0; SDL_Window *screen = NULL; SDL_GLContext glcontext = NULL; #define SCR_MINW 320 #define SCR_MINH 200 #define SCR_MAXW 10000 #define SCR_MAXH 10000 #define SCR_DEFAULTW 1024 #define SCR_DEFAULTH 768 VARF(scr_w, SCR_MINW, -1, SCR_MAXW, initwarning("screen resolution")); VARF(scr_h, SCR_MINH, -1, SCR_MAXH, initwarning("screen resolution")); VARF(depthbits, 0, 0, 32, initwarning("depth-buffer precision")); VARF(fsaa, -1, -1, 16, initwarning("anti-aliasing")); void writeinitcfg() { stream *f = openutf8file("init.cfg", "w"); if(!f) return; f->printf("// automatically written on exit, DO NOT MODIFY\n// modify settings in game\n"); extern int fullscreen, fullscreendesktop; f->printf("fullscreen %d\n", fullscreen); f->printf("fullscreendesktop %d\n", fullscreendesktop); f->printf("scr_w %d\n", scr_w); f->printf("scr_h %d\n", scr_h); f->printf("depthbits %d\n", depthbits); f->printf("fsaa %d\n", fsaa); extern int usesound, soundchans, soundfreq, soundbufferlen; extern char *audiodriver; f->printf("usesound %d\n", usesound); f->printf("soundchans %d\n", soundchans); f->printf("soundfreq %d\n", soundfreq); f->printf("soundbufferlen %d\n", soundbufferlen); if(audiodriver[0]) f->printf("audiodriver %s\n", escapestring(audiodriver)); delete f; } COMMAND(quit, ""); static void getbackgroundres(int &w, int &h) { float wk = 1, hk = 1; if(w < 1024) wk = 1024.0f/w; if(h < 768) hk = 768.0f/h; wk = hk = max(wk, hk); w = int(ceil(w*wk)); h = int(ceil(h*hk)); } string backgroundcaption = ""; Texture *backgroundmapshot = NULL; string backgroundmapname = ""; char *backgroundmapinfo = NULL; void setbackgroundinfo(const char *caption = NULL, Texture *mapshot = NULL, const char *mapname = NULL, const char *mapinfo = NULL) { renderedframe = false; copystring(backgroundcaption, caption ? caption : ""); backgroundmapshot = mapshot; copystring(backgroundmapname, mapname ? mapname : ""); if(mapinfo != backgroundmapinfo) { DELETEA(backgroundmapinfo); if(mapinfo) backgroundmapinfo = newstring(mapinfo); } } void restorebackground(bool force = false) { if(renderedframe) { if(!force) return; setbackgroundinfo(); } renderbackground(backgroundcaption[0] ? backgroundcaption : NULL, backgroundmapshot, backgroundmapname[0] ? backgroundmapname : NULL, backgroundmapinfo, true); } void bgquad(float x, float y, float w, float h, float tx = 0, float ty = 0, float tw = 1, float th = 1) { gle::begin(GL_TRIANGLE_STRIP); gle::attribf(x, y); gle::attribf(tx, ty); gle::attribf(x+w, y); gle::attribf(tx + tw, ty); gle::attribf(x, y+h); gle::attribf(tx, ty + th); gle::attribf(x+w, y+h); gle::attribf(tx + tw, ty + th); gle::end(); } void renderbackground(const char *caption, Texture *mapshot, const char *mapname, const char *mapinfo, bool restore, bool force) { if(!inbetweenframes && !force) return; if(!restore || force) stopsounds(); // stop sounds while loading int w = screenw, h = screenh; if(forceaspect) w = int(ceil(h*forceaspect)); getbackgroundres(w, h); gettextres(w, h); static int lastupdate = -1, lastw = -1, lasth = -1; //~static float backgroundu = 0, backgroundv = 0, detailu = 0, detailv = 0; static int numdecals = 0; static struct decal { float x, y, size; int side; } decals[12]; if((renderedframe && !mainmenu && lastupdate != lastmillis) || lastw != w || lasth != h) { lastupdate = lastmillis; lastw = w; lasth = h; //~backgroundu = rndscale(1); //~backgroundv = rndscale(1); //~detailu = rndscale(1); //~detailv = rndscale(1); numdecals = sizeof(decals)/sizeof(decals[0]); numdecals = numdecals/3 + rnd((numdecals*2)/3 + 1); float maxsize = min(w, h)/16.0f; loopi(numdecals) { decal d = { rndscale(w), rndscale(h), maxsize/2 + rndscale(maxsize/2), rnd(2) }; decals[i] = d; } } else if(lastupdate != lastmillis) lastupdate = lastmillis; //~if (mapname) { //~defformatstring(backpath, "background/%s.png", mapname); //~if (file_does_indeed_exist(backpath)) { //~settexture(backpath, 0); //~bgquad(0, 0, 1920, 1080, 0, 0, 1920, 1080); //~gle::begin(GL_TRIANGLE_STRIP); //~gle::attribf(0.0f, 0.0f); gle::attribf(0.0f, 0.0f); //~gle::attribf(1.0f, 0.0f); gle::attribf(1920.0f, 0.0f); //~gle::attribf(0.0f, 1.0f); gle::attribf(0.0f, 1.0f); //~gle::attribf(1.0f, 1.0f); gle::attribf(1920.0f, 1.0f); //~gle::end(); //~} //~} else { loopi(restore ? 1 : 3) { hudmatrix.ortho(0, w, h, 0, -1, 1); resethudmatrix(); hudshader->set(); gle::colorf(1, 1, 1); gle::defvertex(2); gle::deftexcoord0(); //~defformatstring(backpath, "background/%s.png", mapname); //~if (file_does_indeed_exist(backpath)) { //~settexture(backpath, 0); settexture("background/daemex.png", 0); bgquad(0, 0, screenw, screenh, 0, 0, 1, 1); //~settexture("data/background.png", 0); //~float bu = w*0.67f/256.0f + backgroundu, bv = h*0.67f/256.0f + backgroundv; //~bgquad(0, 0, w, h, 0, 0, bu, bv); //~glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); //~settexture("data/background_detail.png", 0); //~float du = w*0.8f/512.0f + detailu, dv = h*0.8f/512.0f + detailv; //~bgquad(0, 0, w, h, 0, 0, du, dv); //~settexture("data/background_decal.png", 3); //~gle::begin(GL_QUADS); //~loopj(numdecals) //~{ //~float hsz = decals[j].size, hx = clamp(decals[j].x, hsz, w-hsz), hy = clamp(decals[j].y, hsz, h-hsz), side = decals[j].side; //~gle::attribf(hx-hsz, hy-hsz); gle::attribf(side, 0); //~gle::attribf(hx+hsz, hy-hsz); gle::attribf(1-side, 0); //~gle::attribf(hx+hsz, hy+hsz); gle::attribf(1-side, 1); //~gle::attribf(hx-hsz, hy+hsz); gle::attribf(side, 1); //~} //~gle::end(); float lh = 0.5f*min(w, h), lw = lh*2, lx = 0.5f*(w - lw), ly = 0.5f*(h*0.5f - lh); settexture((maxtexsize ? min(maxtexsize, hwtexsize) : hwtexsize) >= 1024 && (screenw > 1280 || screenh > 800) ? "data/logo_1024.png" : "data/logo.png", 3); bgquad(lx, ly, lw, lh); if(caption) { int tw = text_width(caption); float tsz = 0.04f*min(w, h)/FONTH, tx = 0.5f*(w - tw*tsz), ty = h - 0.075f*1.5f*min(w, h) - 1.25f*FONTH*tsz; pushhudmatrix(); hudmatrix.translate(tx, ty, 0); hudmatrix.scale(tsz, tsz, 1); flushhudmatrix(); draw_text(caption, 0, 0); pophudmatrix(); } if(mapshot || mapname) { int infowidth = 12*FONTH; float sz = 0.35f*min(w, h), msz = (0.75f*min(w, h) - sz)/(infowidth + FONTH), x = 0.5f*(w-sz), y = ly+lh - sz/15; if(mapinfo) { int mw, mh; text_bounds(mapinfo, mw, mh, infowidth); x -= 0.5f*(mw*msz + FONTH*msz); } if(mapshot && mapshot!=notexture) { glBindTexture(GL_TEXTURE_2D, mapshot->id); bgquad(x, y, sz, sz); } else { int qw, qh; text_bounds("?", qw, qh); float qsz = sz*0.5f/max(qw, qh); pushhudmatrix(); hudmatrix.translate(x + 0.5f*(sz - qw*qsz), y + 0.5f*(sz - qh*qsz), 0); hudmatrix.scale(qsz, qsz, 1); flushhudmatrix(); draw_text("?", 0, 0); pophudmatrix(); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } settexture("data/mapshot_frame.png", 3); bgquad(x, y, sz, sz); if(mapname) { int tw = text_width(mapname); float tsz = sz/(8*FONTH), tx = 0.9f*sz - tw*tsz, ty = 0.9f*sz - FONTH*tsz; if(tx < 0.1f*sz) { tsz = 0.1f*sz/tw; tx = 0.1f; } pushhudmatrix(); hudmatrix.translate(x+tx, y+ty, 0); hudmatrix.scale(tsz, tsz, 1); flushhudmatrix(); draw_text(mapname, 0, 0); pophudmatrix(); } if(mapinfo) { pushhudmatrix(); hudmatrix.translate(x+sz+FONTH*msz, y, 0); hudmatrix.scale(msz, msz, 1); flushhudmatrix(); draw_text(mapinfo, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, -1, infowidth); pophudmatrix(); } } glDisable(GL_BLEND); if(!restore) swapbuffers(false); } //~} if(!restore) setbackgroundinfo(caption, mapshot, mapname, mapinfo); } VAR(progressbackground, 0, 0, 1); float loadprogress = 0; void renderprogress(float bar, const char *text, GLuint tex, bool background) // also used during loading { if(!inbetweenframes || drawtex) return; extern int menufps, maxfps; int fps = menufps ? (maxfps ? min(maxfps, menufps) : menufps) : maxfps; if(fps) { static int lastprogress = 0; int ticks = SDL_GetTicks(), diff = ticks - lastprogress; if(bar > 0 && diff >= 0 && diff < (1000 + fps-1)/fps) return; lastprogress = ticks; } clientkeepalive(); // make sure our connection doesn't time out while loading maps etc. SDL_PumpEvents(); // keep the event queue awake to avoid 'beachball' cursor extern int mesa_swap_bug, curvsync; bool forcebackground = progressbackground || (mesa_swap_bug && (curvsync || totalmillis==1)); if(background || forcebackground) restorebackground(forcebackground); int w = screenw, h = screenh; if(forceaspect) w = int(ceil(h*forceaspect)); getbackgroundres(w, h); gettextres(w, h); hudmatrix.ortho(0, w, h, 0, -1, 1); resethudmatrix(); hudshader->set(); gle::colorf(1, 1, 1); gle::defvertex(2); gle::deftexcoord0(); float fh = 0.075f*min(w, h), fw = fh*10, fx = renderedframe ? w - fw - fh/4 : 0.5f*(w - fw), fy = renderedframe ? fh/4 : h - fh*1.5f, fu1 = 0/512.0f, fu2 = 511/512.0f, fv1 = 0/64.0f, fv2 = 52/64.0f; settexture("data/loading_frame.png", 3); bgquad(fx, fy, fw, fh, fu1, fv1, fu2-fu1, fv2-fv1); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); float bw = fw*(511 - 2*17)/511.0f, bh = fh*20/52.0f, bx = fx + fw*17/511.0f, by = fy + fh*16/52.0f, bv1 = 0/32.0f, bv2 = 20/32.0f, su1 = 0/32.0f, su2 = 7/32.0f, sw = fw*7/511.0f, eu1 = 23/32.0f, eu2 = 30/32.0f, ew = fw*7/511.0f, mw = bw - sw - ew, ex = bx+sw + max(mw*bar, fw*7/511.0f); if(bar > 0) { settexture("data/loading_bar.png", 3); gle::begin(GL_QUADS); gle::attribf(bx, by); gle::attribf(su1, bv1); gle::attribf(bx+sw, by); gle::attribf(su2, bv1); gle::attribf(bx+sw, by+bh); gle::attribf(su2, bv2); gle::attribf(bx, by+bh); gle::attribf(su1, bv2); gle::attribf(bx+sw, by); gle::attribf(su2, bv1); gle::attribf(ex, by); gle::attribf(eu1, bv1); gle::attribf(ex, by+bh); gle::attribf(eu1, bv2); gle::attribf(bx+sw, by+bh); gle::attribf(su2, bv2); gle::attribf(ex, by); gle::attribf(eu1, bv1); gle::attribf(ex+ew, by); gle::attribf(eu2, bv1); gle::attribf(ex+ew, by+bh); gle::attribf(eu2, bv2); gle::attribf(ex, by+bh); gle::attribf(eu1, bv2); gle::end(); } if(text) { int tw = text_width(text); float tsz = bh*0.8f/FONTH; if(tw*tsz > mw) tsz = mw/tw; pushhudmatrix(); hudmatrix.translate(bx+sw, by + (bh - FONTH*tsz)/2, 0); hudmatrix.scale(tsz, tsz, 1); flushhudmatrix(); draw_text(text, 0, 0); pophudmatrix(); } glDisable(GL_BLEND); if(tex) { glBindTexture(GL_TEXTURE_2D, tex); float sz = 0.35f*min(w, h), x = 0.5f*(w-sz), y = 0.5f*min(w, h) - sz/15; bgquad(x, y, sz, sz); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); settexture("data/mapshot_frame.png", 3); bgquad(x, y, sz, sz); glDisable(GL_BLEND); } swapbuffers(false); } int keyrepeatmask = 0, textinputmask = 0; Uint32 textinputtime = 0; VAR(textinputfilter, 0, 5, 1000); void keyrepeat(bool on, int mask) { if(on) keyrepeatmask |= mask; else keyrepeatmask &= ~mask; } void textinput(bool on, int mask) { if(on) { if(!textinputmask) { SDL_StartTextInput(); textinputtime = SDL_GetTicks(); } textinputmask |= mask; } else if(textinputmask) { textinputmask &= ~mask; if(!textinputmask) SDL_StopTextInput(); } } #ifdef WIN32 // SDL_WarpMouseInWindow behaves erratically on Windows, so force relative mouse instead. VARN(relativemouse, userelativemouse, 1, 1, 0); #else VARNP(relativemouse, userelativemouse, 0, 1, 1); #endif bool shouldgrab = false, grabinput = false, minimized = false, canrelativemouse = true, relativemouse = false; #ifdef SDL_VIDEO_DRIVER_X11 VAR(sdl_xgrab_bug, 0, 0, 1); #endif void inputgrab(bool on, bool delay = false) { #ifdef SDL_VIDEO_DRIVER_X11 bool wasrelativemouse = relativemouse; #endif if(on) { SDL_ShowCursor(SDL_FALSE); if(canrelativemouse && userelativemouse) { if(SDL_SetRelativeMouseMode(SDL_TRUE) >= 0) { SDL_SetWindowGrab(screen, SDL_TRUE); relativemouse = true; } else { SDL_SetWindowGrab(screen, SDL_FALSE); canrelativemouse = false; relativemouse = false; } } } else { SDL_ShowCursor(SDL_TRUE); if(relativemouse) { SDL_SetWindowGrab(screen, SDL_FALSE); SDL_SetRelativeMouseMode(SDL_FALSE); relativemouse = false; } } shouldgrab = delay; #ifdef SDL_VIDEO_DRIVER_X11 if((relativemouse || wasrelativemouse) && sdl_xgrab_bug) { // Workaround for buggy SDL X11 pointer grabbing union { SDL_SysWMinfo info; uchar buf[sizeof(SDL_SysWMinfo) + 128]; }; SDL_GetVersion(&info.version); if(SDL_GetWindowWMInfo(screen, &info) && info.subsystem == SDL_SYSWM_X11) { if(relativemouse) { uint mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask; XGrabPointer(info.info.x11.display, info.info.x11.window, True, mask, GrabModeAsync, GrabModeAsync, info.info.x11.window, None, CurrentTime); } else XUngrabPointer(info.info.x11.display, CurrentTime); } } #endif } bool initwindowpos = false; void setfullscreen(bool enable) { if(!screen) return; //initwarning(enable ? "fullscreen" : "windowed"); extern int fullscreendesktop; SDL_SetWindowFullscreen(screen, enable ? (fullscreendesktop ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN) : 0); if(!enable) { SDL_SetWindowSize(screen, scr_w, scr_h); if(initwindowpos) { int winx = SDL_WINDOWPOS_CENTERED, winy = SDL_WINDOWPOS_CENTERED; SDL_SetWindowPosition(screen, winx, winy); initwindowpos = false; } } } #ifdef _DEBUG VARF(fullscreen, 0, 0, 1, setfullscreen(fullscreen!=0)); #else VARF(fullscreen, 0, 1, 1, setfullscreen(fullscreen!=0)); #endif void resetfullscreen() { setfullscreen(false); setfullscreen(true); } VARF(fullscreendesktop, 0, 0, 1, if(fullscreen) resetfullscreen()); void screenres(int w, int h) { scr_w = clamp(w, SCR_MINW, SCR_MAXW); scr_h = clamp(h, SCR_MINH, SCR_MAXH); if(screen) { if(fullscreendesktop) { scr_w = min(scr_w, desktopw); scr_h = min(scr_h, desktoph); } if(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN) { if(fullscreendesktop) gl_resize(); else resetfullscreen(); initwindowpos = true; } else { SDL_SetWindowSize(screen, scr_w, scr_h); SDL_SetWindowPosition(screen, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); initwindowpos = false; } } else { initwarning("screen resolution"); } } ICOMMAND(screenres, "ii", (int *w, int *h), screenres(*w, *h)); static void setgamma(int val) { if(screen && SDL_SetWindowBrightness(screen, val/100.0f) < 0) conoutf(CON_ERROR, "Could not set gamma: %s", SDL_GetError()); } static int curgamma = 100; VARFNP(gamma, reqgamma, 30, 100, 300, { if(initing || reqgamma == curgamma) return; curgamma = reqgamma; setgamma(curgamma); }); void restoregamma() { if(initing || reqgamma == 100) return; curgamma = reqgamma; setgamma(curgamma); } void cleargamma() { if(curgamma != 100 && screen) SDL_SetWindowBrightness(screen, 1.0f); } int curvsync = -1; void restorevsync() { if(initing || !glcontext) return; extern int vsync, vsynctear; if(!SDL_GL_SetSwapInterval(vsync ? (vsynctear ? -1 : 1) : 0)) curvsync = vsync; } VARFP(vsync, 0, 0, 1, restorevsync()); VARFP(vsynctear, 0, 0, 1, { if(vsync) restorevsync(); }); void setupscreen() { if(glcontext) { SDL_GL_DeleteContext(glcontext); glcontext = NULL; } if(screen) { SDL_DestroyWindow(screen); screen = NULL; } curvsync = -1; SDL_Rect desktop; if(SDL_GetDisplayBounds(0, &desktop) < 0) fatal("failed querying desktop bounds: %s", SDL_GetError()); desktopw = desktop.w; desktoph = desktop.h; if(scr_h < 0) scr_h = fullscreen ? desktoph : SCR_DEFAULTH; if(scr_w < 0) scr_w = (scr_h*desktopw)/desktoph; scr_w = clamp(scr_w, SCR_MINW, SCR_MAXW); scr_h = clamp(scr_h, SCR_MINH, SCR_MAXH); if(fullscreendesktop) { scr_w = min(scr_w, desktopw); scr_h = min(scr_h, desktoph); } int winx = SDL_WINDOWPOS_UNDEFINED, winy = SDL_WINDOWPOS_UNDEFINED, winw = scr_w, winh = scr_h, flags = SDL_WINDOW_RESIZABLE; if(fullscreen) { if(fullscreendesktop) { winw = desktopw; winh = desktoph; flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; } else flags |= SDL_WINDOW_FULLSCREEN; initwindowpos = true; } SDL_GL_ResetAttributes(); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); #if !defined(WIN32) && !defined(__APPLE__) SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); #endif static const int configs[] = { 0x3, /* try everything */ 0x2, 0x1, /* try disabling one at a time */ 0 /* try disabling everything */ }; int config = 0; if(!depthbits) SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); if(!fsaa) { SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); } loopi(sizeof(configs)/sizeof(configs[0])) { config = configs[i]; if(!depthbits && config&1) continue; if(fsaa<=0 && config&2) continue; if(depthbits) SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, config&1 ? depthbits : 24); if(fsaa>0) { SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, config&2 ? 1 : 0); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, config&2 ? fsaa : 0); } screen = SDL_CreateWindow("Cube 2: Sauerbraten", winx, winy, winw, winh, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS | flags); if(!screen) continue; #ifdef __APPLE__ static const int glversions[] = { 32, 20 }; #else static const int glversions[] = { 33, 32, 31, 30, 20 }; #endif loopj(sizeof(glversions)/sizeof(glversions[0])) { glcompat = glversions[j] <= 30 ? 1 : 0; SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, glversions[j] / 10); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, glversions[j] % 10); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, glversions[j] >= 32 ? SDL_GL_CONTEXT_PROFILE_CORE : 0); glcontext = SDL_GL_CreateContext(screen); if(glcontext) break; } if(glcontext) break; } if(!screen) fatal("failed to create OpenGL window: %s", SDL_GetError()); else if(!glcontext) fatal("failed to create OpenGL context: %s", SDL_GetError()); else { if(depthbits && (config&1)==0) conoutf(CON_WARN, "%d bit z-buffer not supported - disabling", depthbits); if(fsaa>0 && (config&2)==0) conoutf(CON_WARN, "%dx anti-aliasing not supported - disabling", fsaa); } SDL_SetWindowMinimumSize(screen, SCR_MINW, SCR_MINH); SDL_SetWindowMaximumSize(screen, SCR_MAXW, SCR_MAXH); SDL_GetWindowSize(screen, &screenw, &screenh); } void resetgl() { clearchanges(CHANGE_GFX); renderbackground("resetting OpenGL"); extern void cleanupva(); extern void cleanupparticles(); extern void cleanupdecals(); extern void cleanupsky(); extern void cleanupmodels(); extern void cleanupprefabs(); extern void cleanuplightmaps(); extern void cleanupblendmap(); extern void cleanshadowmap(); extern void cleanreflections(); extern void cleanupglare(); extern void cleanupdepthfx(); cleanupva(); cleanupparticles(); cleanupdecals(); cleanupsky(); cleanupmodels(); cleanupprefabs(); cleanuptextures(); cleanuplightmaps(); cleanupblendmap(); cleanshadowmap(); cleanreflections(); cleanupglare(); cleanupdepthfx(); cleanupshaders(); cleanupgl(); setupscreen(); inputgrab(grabinput); gl_init(); inbetweenframes = false; if(!reloadtexture(*notexture) || !reloadtexture("data/logo.png") || !reloadtexture("data/logo_1024.png") || !reloadtexture("data/background.png") || !reloadtexture("data/background_detail.png") || !reloadtexture("data/background_decal.png") || !reloadtexture("data/mapshot_frame.png") || !reloadtexture("data/loading_frame.png") || !reloadtexture("data/loading_bar.png")) fatal("failed to reload core texture"); reloadfonts(); inbetweenframes = true; renderbackground("initializing..."); restoregamma(); restorevsync(); reloadshaders(); reloadtextures(); initlights(); allchanged(true); } COMMAND(resetgl, ""); static queue events; static inline bool filterevent(const SDL_Event &event) { switch(event.type) { case SDL_MOUSEMOTION: if(grabinput && !relativemouse && !(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN)) { if(event.motion.x == screenw / 2 && event.motion.y == screenh / 2) return false; // ignore any motion events generated by SDL_WarpMouse #ifdef __APPLE__ if(event.motion.y == 0) return false; // let mac users drag windows via the title bar #endif } break; } return true; } template static inline bool pumpevents(queue &events) { while(events.empty()) { SDL_PumpEvents(); databuf buf = events.reserve(events.capacity()); int n = SDL_PeepEvents(buf.getbuf(), buf.remaining(), SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT); if(n <= 0) return false; loopi(n) if(filterevent(buf.buf[i])) buf.put(buf.buf[i]); events.addbuf(buf); } return true; } static int interceptkeysym = 0; static int interceptevents(void *data, SDL_Event *event) { switch(event->type) { case SDL_MOUSEMOTION: return 0; case SDL_KEYDOWN: if(event->key.keysym.sym == interceptkeysym) { interceptkeysym = -interceptkeysym; return 0; } break; } return 1; } static void clearinterceptkey() { SDL_DelEventWatch(interceptevents, NULL); interceptkeysym = 0; } bool interceptkey(int sym) { if(!interceptkeysym) { interceptkeysym = sym; SDL_FilterEvents(interceptevents, NULL); if(interceptkeysym < 0) { interceptkeysym = 0; return true; } SDL_AddEventWatch(interceptevents, NULL); } else if(abs(interceptkeysym) != sym) interceptkeysym = sym; SDL_PumpEvents(); if(interceptkeysym < 0) { clearinterceptkey(); interceptkeysym = sym; SDL_FilterEvents(interceptevents, NULL); interceptkeysym = 0; return true; } return false; } static void ignoremousemotion() { SDL_PumpEvents(); SDL_FlushEvent(SDL_MOUSEMOTION); } static void resetmousemotion() { if(grabinput && !relativemouse && !(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN)) { SDL_WarpMouseInWindow(screen, screenw / 2, screenh / 2); } } static void checkmousemotion(int &dx, int &dy) { while(pumpevents(events)) { SDL_Event &event = events.removing(); if(event.type != SDL_MOUSEMOTION) return; dx += event.motion.xrel; dy += event.motion.yrel; events.remove(); } } void checkinput() { if(interceptkeysym) clearinterceptkey(); //int lasttype = 0, lastbut = 0; bool mousemoved = false; int focused = 0; while(pumpevents(events)) { SDL_Event &event = events.remove(); if(focused && event.type!=SDL_WINDOWEVENT) { if(grabinput != (focused>0)) inputgrab(grabinput = focused>0, shouldgrab); focused = 0; } switch(event.type) { case SDL_QUIT: quit(); return; case SDL_TEXTINPUT: if(textinputmask && int(event.text.timestamp-textinputtime) >= textinputfilter) { uchar buf[SDL_TEXTINPUTEVENT_TEXT_SIZE+1]; size_t len = decodeutf8(buf, sizeof(buf)-1, (const uchar *)event.text.text, strlen(event.text.text)); if(len > 0) { buf[len] = '\0'; processtextinput((const char *)buf, len); } } break; case SDL_KEYDOWN: case SDL_KEYUP: if(keyrepeatmask || !event.key.repeat) processkey(event.key.keysym.sym, event.key.state==SDL_PRESSED, event.key.keysym.mod | SDL_GetModState()); break; case SDL_WINDOWEVENT: switch(event.window.event) { case SDL_WINDOWEVENT_CLOSE: quit(); break; case SDL_WINDOWEVENT_FOCUS_GAINED: shouldgrab = true; break; case SDL_WINDOWEVENT_ENTER: shouldgrab = false; focused = 1; break; case SDL_WINDOWEVENT_LEAVE: case SDL_WINDOWEVENT_FOCUS_LOST: shouldgrab = false; focused = -1; break; case SDL_WINDOWEVENT_MINIMIZED: minimized = true; break; case SDL_WINDOWEVENT_MAXIMIZED: case SDL_WINDOWEVENT_RESTORED: minimized = false; break; case SDL_WINDOWEVENT_RESIZED: break; case SDL_WINDOWEVENT_SIZE_CHANGED: { SDL_GetWindowSize(screen, &screenw, &screenh); if(!fullscreendesktop || !(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN)) { scr_w = clamp(screenw, SCR_MINW, SCR_MAXW); scr_h = clamp(screenh, SCR_MINH, SCR_MAXH); } gl_resize(); break; } } break; case SDL_MOUSEMOTION: if(grabinput) { int dx = event.motion.xrel, dy = event.motion.yrel; checkmousemotion(dx, dy); if(!g3d_movecursor(dx, dy)) mousemove(dx, dy); mousemoved = true; } else if(shouldgrab) inputgrab(grabinput = true); break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: //if(lasttype==event.type && lastbut==event.button.button) break; // why?? get event twice without it switch(event.button.button) { case SDL_BUTTON_LEFT: processkey(-1, event.button.state==SDL_PRESSED); break; case SDL_BUTTON_MIDDLE: processkey(-2, event.button.state==SDL_PRESSED); break; case SDL_BUTTON_RIGHT: processkey(-3, event.button.state==SDL_PRESSED); break; case SDL_BUTTON_X1: processkey(-6, event.button.state==SDL_PRESSED); break; case SDL_BUTTON_X2: processkey(-7, event.button.state==SDL_PRESSED); break; } //lasttype = event.type; //lastbut = event.button.button; break; case SDL_MOUSEWHEEL: if(event.wheel.y > 0) { processkey(-4, true); processkey(-4, false); } else if(event.wheel.y < 0) { processkey(-5, true); processkey(-5, false); } break; } } if(focused) { if(grabinput != (focused>0)) inputgrab(grabinput = focused>0, shouldgrab); focused = 0; } if(mousemoved) resetmousemotion(); } void swapbuffers(bool overlay) { gle::disable(); SDL_GL_SwapWindow(screen); } VAR(menufps, 0, 60, 1000); VARP(maxfps, 0, 200, 1000); void limitfps(int &millis, int curmillis) { int limit = (mainmenu || minimized) && menufps ? (maxfps ? min(maxfps, menufps) : menufps) : maxfps; if(!limit) return; static int fpserror = 0; int delay = 1000/limit - (millis-curmillis); if(delay < 0) fpserror = 0; else { fpserror += 1000%limit; if(fpserror >= limit) { ++delay; fpserror -= limit; } if(delay > 0) { SDL_Delay(delay); millis += delay; } } } #if defined(WIN32) && !defined(_DEBUG) && !defined(__GNUC__) void stackdumper(unsigned int type, EXCEPTION_POINTERS *ep) { if(!ep) fatal("unknown type"); EXCEPTION_RECORD *er = ep->ExceptionRecord; CONTEXT *context = ep->ContextRecord; char out[512]; formatstring(out, "Cube 2: Sauerbraten Win32 Exception: 0x%x [0x%x]\n\n", er->ExceptionCode, er->ExceptionCode==EXCEPTION_ACCESS_VIOLATION ? er->ExceptionInformation[1] : -1); SymInitialize(GetCurrentProcess(), NULL, TRUE); #ifdef _AMD64_ STACKFRAME64 sf = {{context->Rip, 0, AddrModeFlat}, {}, {context->Rbp, 0, AddrModeFlat}, {context->Rsp, 0, AddrModeFlat}, 0}; while(::StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(), &sf, context, NULL, ::SymFunctionTableAccess, ::SymGetModuleBase, NULL)) { union { IMAGEHLP_SYMBOL64 sym; char symext[sizeof(IMAGEHLP_SYMBOL64) + sizeof(string)]; }; sym.SizeOfStruct = sizeof(sym); sym.MaxNameLength = sizeof(symext) - sizeof(sym); IMAGEHLP_LINE64 line; line.SizeOfStruct = sizeof(line); DWORD64 symoff; DWORD lineoff; if(SymGetSymFromAddr64(GetCurrentProcess(), sf.AddrPC.Offset, &symoff, &sym) && SymGetLineFromAddr64(GetCurrentProcess(), sf.AddrPC.Offset, &lineoff, &line)) #else STACKFRAME sf = {{context->Eip, 0, AddrModeFlat}, {}, {context->Ebp, 0, AddrModeFlat}, {context->Esp, 0, AddrModeFlat}, 0}; while(::StackWalk(IMAGE_FILE_MACHINE_I386, GetCurrentProcess(), GetCurrentThread(), &sf, context, NULL, ::SymFunctionTableAccess, ::SymGetModuleBase, NULL)) { union { IMAGEHLP_SYMBOL sym; char symext[sizeof(IMAGEHLP_SYMBOL) + sizeof(string)]; }; sym.SizeOfStruct = sizeof(sym); sym.MaxNameLength = sizeof(symext) - sizeof(sym); IMAGEHLP_LINE line; line.SizeOfStruct = sizeof(line); DWORD symoff, lineoff; if(SymGetSymFromAddr(GetCurrentProcess(), sf.AddrPC.Offset, &symoff, &sym) && SymGetLineFromAddr(GetCurrentProcess(), sf.AddrPC.Offset, &lineoff, &line)) #endif { char *del = strrchr(line.FileName, '\\'); concformatstring(out, "%s - %s [%d]\n", sym.Name, del ? del + 1 : line.FileName, line.LineNumber); } } fatal(out); } #endif #define MAXFPSHISTORY 60 int fpspos = 0, fpshistory[MAXFPSHISTORY]; void resetfpshistory() { loopi(MAXFPSHISTORY) fpshistory[i] = 1; fpspos = 0; } void updatefpshistory(int millis) { fpshistory[fpspos++] = max(1, min(1000, millis)); if(fpspos>=MAXFPSHISTORY) fpspos = 0; } void getfps(int &fps, int &bestdiff, int &worstdiff) { int total = fpshistory[MAXFPSHISTORY-1], best = total, worst = total; loopi(MAXFPSHISTORY-1) { int millis = fpshistory[i]; total += millis; if(millis < best) best = millis; if(millis > worst) worst = millis; } fps = (1000*MAXFPSHISTORY)/total; bestdiff = 1000/best-fps; worstdiff = fps-1000/worst; } void getfps_(int *raw) { int fps, bestdiff, worstdiff; if(*raw) fps = 1000/fpshistory[(fpspos+MAXFPSHISTORY-1)%MAXFPSHISTORY]; else getfps(fps, bestdiff, worstdiff); intret(fps); } COMMANDN(getfps, getfps_, "i"); bool inbetweenframes = false, renderedframe = true; static bool findarg(int argc, char **argv, const char *str) { for(int i = 1; i0, dedicated>1); // never returns if dedicated ASSERT(dedicated <= 1); game::initclient(); logoutf("init: video"); SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "0"); #if !defined(WIN32) && !defined(__APPLE__) SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0"); #endif setupscreen(); SDL_ShowCursor(SDL_FALSE); SDL_StopTextInput(); // workaround for spurious text-input events getting sent on first text input toggle? logoutf("init: gl"); gl_checkextensions(); gl_init(); notexture = textureload("packages/textures/texture_error.png"); if(!notexture) fatal("could not find core textures"); logoutf("init: console"); if(!execfile("data/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. if(!execfile("packages/fonts/default.cfg", false)) fatal("cannot find font definitions"); if(!setfont("default")) fatal("no default font specified"); inbetweenframes = true; renderbackground("initializing..."); logoutf("init: world"); camera1 = player = game::iterdynents(0); emptymap(0, true, NULL, false); logoutf("init: sound"); initsound(); logoutf("init: cfg"); initing = INIT_LOAD; execfile("data/keymap.cfg"); execfile("data/stdedit.cfg"); execfile("data/sounds.cfg"); execfile("data/menus.cfg"); execfile("data/heightmap.cfg"); execfile("data/brushes.cfg"); execfile("data/game.cfg"); execfile("data/custom_maps_menu.cfg"); if(game::savedservers()) execfile(game::savedservers(), false); identflags |= IDF_PERSIST; if(!execfile(game::savedconfig(), false)) { execfile(game::defaultconfig()); writecfg(game::restoreconfig()); } execfile(game::autoexec(), false); identflags &= ~IDF_PERSIST; initing = INIT_GAME; game::loadconfigs(); initing = NOT_INITING; logoutf("init: render"); restoregamma(); restorevsync(); loadshaders(); initparticles(); initdecals(); identflags |= IDF_PERSIST; logoutf("init: mainloop"); if(execfile("once.cfg", false)) remove(findfile("once.cfg", "rb")); if(load) { logoutf("init: localconnect"); //localconnect(); game::changemap(load); } if(initscript) execute(initscript); initmumble(); resetfpshistory(); inputgrab(grabinput = true); ignoremousemotion(); for(;;) { static int frames = 0; int millis = getclockmillis(); limitfps(millis, totalmillis); elapsedtime = millis - totalmillis; static int timeerr = 0; int scaledtime = game::scaletime(elapsedtime) + timeerr; curtime = scaledtime/100; timeerr = scaledtime%100; if(!multiplayer(false) && curtime>200) curtime = 200; if(game::ispaused()) curtime = 0; lastmillis += curtime; totalmillis = millis; updatetime(); checkinput(); menuprocess(); tryedit(); if(lastmillis) game::updateworld(); checksleep(lastmillis); serverslice(false, 0); if(frames) updatefpshistory(elapsedtime); frames++; // miscellaneous general game effects recomputecamera(); updateparticles(); updatesounds(); if(minimized) continue; inbetweenframes = false; if(mainmenu) gl_drawmainmenu(); else gl_drawframe(); swapbuffers(); renderedframe = inbetweenframes = true; } ASSERT(0); return EXIT_FAILURE; #if defined(WIN32) && !defined(_DEBUG) && !defined(__GNUC__) } __except(stackdumper(0, GetExceptionInformation()), EXCEPTION_CONTINUE_SEARCH) { return 0; } #endif }