// main.cpp: initialisation & main loop #include "cube.h" void cleanup(char *msg) // single program exit point; { if(clientlogfile) clientlogfile->fflush(); if(!msg) { cleanupclient(); audiomgr.soundcleanup(); cleanupserver(); extern void cleargamma(); cleargamma(); } SDL_ShowCursor(SDL_TRUE); SDL_SetRelativeMouseMode(SDL_FALSE); if(msg) { #ifdef WIN32 MessageBox(NULL, msg, "AssaultCube fatal error", MB_OK|MB_SYSTEMMODAL|MB_ICONERROR); #else printf("%s", msg); #endif if(clientlogfile) { clientlogfile->printf("%s\n", msg); dumpexecutionstack(clientlogfile); } } SDL_Quit(); } VAR(resetcfg, 0, 0, 1); void quit() // normal exit { if(clientlogfile) clientlogfile->fflush(); const char *onquit = getalias("onQuit"); setcontext("hook", "onQuit"); if(onquit && onquit[0]) execute(onquit); resetcontext(); alias("onQuit", ""); alias("mapstartonce", ""); extern void writeinitcfg(); writeinitcfg(); writeservercfg(); writepcksourcecfg(); if(resetcfg) deletecfg(); else writecfg(); savehistory(); writemapmodelattributes(); entropy_save(); writeallxmaps(); cleanup(NULL); popscontext(); DELETEP(clientlogfile); exit(EXIT_SUCCESS); } COMMAND(quit, ""); void fatal(const char *s, ...) // failure exit { static int errors = 0; errors++; if(errors <= 2) { defvformatstring(msg,s,s); if(errors <= 1) { defvformatstring(msg,s,s); defformatstring(msgerr)("%s (%s)\n", msg, SDL_GetError()); cleanup(msgerr); } else puts(msg); } DELETEP(clientlogfile); exit(EXIT_FAILURE); } static int initing = NOT_INITING; static bool restoredinits = false; bool initwarning(const char *desc, int level, int type) { if(initing < level) { addchange(desc, type); return true; } return false; } SDL_Window *screen = NULL; SDL_GLContext glcontext = 0; VAR(screenw, 1, 0, 0); // actual current resolution of screen/window VAR(screenh, 1, 0, 0); #define SCR_MINW 320 #define SCR_MINH 200 #define SCR_MAXW 10000 #define SCR_MAXH 10000 VARF(scr_w, SCR_MINW, -1, SCR_MAXW, initwarning("screen resolution")); // screen or window resolution, except for "fullscreen desktop" mode VARF(scr_h, SCR_MINH, -1, SCR_MAXH, initwarning("screen resolution")); VARF(depthbits, 0, 0, 32, initwarning("depth-buffer precision")); VARF(stencilbits, 0, 0, 32, initwarning("stencil-buffer precision")); VARF(fsaa, -1, -1, 16, initwarning("anti-aliasing")); VARF(vsync, -1, -1, 1, initwarning("vertical sync")); VAR(desktopw, 1, 0, 0); // resolution of desktop (assumed to be maximum resolution) VAR(desktoph, 1, 0, 0); #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 static bool shouldgrab = false, grabinput = false, minimized = false, centerwindow = true, 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 } void setfullscreen(bool enable) { if(!screen) return; if(enable == !(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN)) { extern int fullscreendesktop; SDL_SetWindowFullscreen(screen, enable ? (fullscreendesktop ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN) : 0); inputgrab(grabinput = enable || grabinput); if(!enable) SDL_SetWindowSize(screen, scr_w, scr_h); if(!enable && centerwindow) SDL_SetWindowPosition(screen, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); } } VARF(fullscreen, 0, 1, 1, setfullscreen(fullscreen != 0)); void resetfullscreen() { setfullscreen(false); setfullscreen(true); } VARF(fullscreendesktop, 0, 1, 1, if(fullscreen) resetfullscreen()); SVARFP(lang, "en", filterlang(lang, lang)); void writeinitcfg() { if(!restoredinits) return; filerotate("config/init", "cfg", CONFIGROTATEMAX); // keep five old init config sets stream *f = openfile(path("config/init.cfg", true), "w"); if(!f) return; f->printf("// automatically written on exit, DO NOT MODIFY\n// modify settings in game\n"); 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("stencilbits %d\n", stencilbits); f->printf("fsaa %d\n", fsaa); f->printf("vsync %d\n", vsync); extern int audio, soundchannels, igraphdefaultframetime; f->printf("audio %d\n", audio > 0 ? 1 : 0); f->printf("soundchannels %d\n", soundchannels); f->printf("igraphdefaultframetime %d\n", igraphdefaultframetime); if(lang && *lang) f->printf("lang %s\n", lang); extern void writezipmodconfig(stream *f); writezipmodconfig(f); delete f; } #if 0 VARP(highprocesspriority, 0, 1, 1); void setprocesspriority(bool high) { #if defined(WIN32) && !defined(_DEBUG) SetPriorityClass(GetCurrentProcess(), high && highprocesspriority && fullscreen ? HIGH_PRIORITY_CLASS : NORMAL_PRIORITY_CLASS); #endif } #endif VARP(pngcompress, 0, 9, 9); void writepngchunk(stream *f, const char *type, const void *data = NULL, uint len = 0) { f->putbig(len); f->write(type, 4); if(data) f->write(data, len); uint crc = crc32(0, Z_NULL, 0); crc = ~crc32(crc, (const Bytef *)type, 4); if(data) loopi(len) enet_crc32_inc(&crc, ((const uchar *)data)[i]); f->putbig(~crc); } int save_png(const char *filename, SDL_Surface *image) { uchar *data = (uchar *)image->pixels; int iw = image->w, ih = image->h, pitch = image->pitch; stream *f = openfile(filename, "wb"); if(!f) { conoutf("could not write to %s", filename); return -1; } const char *scores = asciiscores(true); uchar signature[] = { 137, 80, 78, 71, 13, 10, 26, 10 }; f->write(signature, sizeof(signature)); struct pngihdr { uint width, height; uchar bitdepth, colortype, compress, filter, interlace; } ihdr = { bigswap(iw), bigswap(ih), 8, 2, 0, 0, 0 }; writepngchunk(f, "IHDR", (uchar *)&ihdr, 13); int idat = f->tell(); uint len = 0; f->write("\0\0\0\0IDAT", 8); uint crc = crc32(0, Z_NULL, 0); crc = crc32(crc, (const Bytef *)"IDAT", 4); z_stream z; z.zalloc = NULL; z.zfree = NULL; z.opaque = NULL; if(deflateInit(&z, pngcompress) != Z_OK) goto error; uchar buf[1<<12]; z.next_out = (Bytef *)buf; z.avail_out = sizeof(buf); loopi(ih) { uchar filter = 0; loopj(2) { z.next_in = j ? (Bytef *)data + i*pitch : (Bytef *)&filter; z.avail_in = j ? iw*3 : 1; while(z.avail_in > 0) { if(deflate(&z, Z_NO_FLUSH) != Z_OK) goto cleanuperror; #define FLUSHZ do { \ int flush = sizeof(buf) - z.avail_out; \ crc = crc32(crc, buf, flush); \ len += flush; \ f->write(buf, flush); \ z.next_out = (Bytef *)buf; \ z.avail_out = sizeof(buf); \ } while(0) FLUSHZ; } } } for(;;) { int err = deflate(&z, Z_FINISH); if(err != Z_OK && err != Z_STREAM_END) goto cleanuperror; FLUSHZ; if(err == Z_STREAM_END) break; } deflateEnd(&z); f->seek(idat, SEEK_SET); f->putbig(len); f->seek(0, SEEK_END); f->putbig(crc); writepngchunk(f, "tEXt", "\123o\165rc\145\0\101ss\141" "\165\154\164\103\165\142\145", 18); writepngchunk(f, "tEXt", scores - 8, strlen(scores) + 8); writepngchunk(f, "IEND"); delete f; return 0; cleanuperror: deflateEnd(&z); error: delete f; return -1; } #include "jpegenc.h" void mapscreenshot(const char *imagepath, bool mapshot, int fileformat, float scale, int height, int quality) { extern int minimaplastsize; int src_w = mapshot ? minimaplastsize : screenw; int src_h = mapshot ? minimaplastsize : screenh; int dst_w = mapshot ? src_w : src_w * scale, img_w = dst_w; int dst_h = mapshot ? src_h : src_h * scale, img_h = dst_h; if(height) { // create 4:3 of exact height if(dst_w >= (dst_h * 4) / 3) { // 4:3 or wider: crop width dst_w = (src_w * height) / src_h; img_h = dst_h = height; img_w = (img_h * 4) / 3; } else { // probably 5:4: crop height img_h = height; dst_w = img_w = (img_h * 4) / 3; dst_h = (src_h * dst_w) / src_w; } } SDL_Surface *image = creatergbsurface(img_w, img_h); if(!image) return; int tmpdstsize = dst_w * dst_h * 3, tmpdstpitch = dst_w * 3; uchar *tmpdst = new uchar[tmpdstsize], *dst = (uchar *)image->pixels; if(mapshot) { extern GLuint minimaptex; if(minimaptex) { glPixelStorei(GL_PACK_ALIGNMENT, 1); glBindTexture(GL_TEXTURE_2D, minimaptex); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, tmpdst); } else { conoutf("no mapshot prepared!"); SDL_FreeSurface(image); delete[] tmpdst; return; } loopi(img_h) { // copy image memcpy(dst, &tmpdst[tmpdstpitch * i], image->pitch); dst += image->pitch; } } else { glPixelStorei(GL_PACK_ALIGNMENT, 1); if(src_w != dst_w || src_h != dst_h) { // scale image uchar *tmpsrc = new uchar[src_w * src_h * 3]; glReadPixels(0, 0, src_w, src_h, GL_RGB, GL_UNSIGNED_BYTE, tmpsrc); scaletexture(tmpsrc, src_w, src_h, 3, tmpdst, dst_w, dst_h); delete[] tmpsrc; } else glReadPixels(0, 0, dst_w, dst_h, GL_RGB, GL_UNSIGNED_BYTE, tmpdst); int crop_w = height ? ((dst_w - img_w) / 2) * 3 : 0; int crop_h = height ? (dst_h - img_h) / 2 : 0; loopirev(img_h) { // flip image memcpy(dst, &tmpdst[tmpdstpitch * (i + crop_h) + crop_w], image->pitch); dst += image->pitch; } } entropy_add_block(tmpdst, tmpdstsize); delete[] tmpdst; if(!mapshot && imagepath) audiomgr.playsound(S_CAMERA); switch(fileformat) { case 0: // bmp { stream *file = openfile(imagepath, "wb"); if(!file) conoutf("failed to create: %s", imagepath); else { SDL_SaveBMP_RW(image, file->rwops(), 1); delete file; } break; } case 1: // jpeg { jpegenc jpegencoder; jpegencoder.encode(imagepath, image, quality, asciiscores(true)); break; } case 2: // png if(save_png(imagepath, image) < 0) conoutf("\f3Error saving png file"); break; } SDL_FreeSurface(image); } FVARP(screenshotscale, 0.1f, 1.0f, 1.0f); VARP(jpegquality, 10, 85, 100); // best: 100 - good: 85 [default] - bad: 70 - terrible: 50 VARP(screenshottype, 0, 1, 2); const char *getscrext() { const char *screxts[] = { ".bmp", ".jpg", ".png" }; return screxts[screenshottype % 3]; } COMMANDF(getscrext, "", () { result(getscrext()); }); void screenshot(const char *filename) { static string buf; if(filename && filename[0]) formatstring(buf)("screenshots/%s%s", filename, getscrext()); else if(getclientmap()[0]) formatstring(buf)("screenshots/%s_%s_%s%s", timestring(), behindpath(getclientmap()), modestr(gamemode, true), getscrext()); else formatstring(buf)("screenshots/%s%s", timestring(), getscrext()); path(buf); mapscreenshot(buf, false, screenshottype, screenshotscale, 0, jpegquality); } COMMAND(screenshot, "s"); void mapshot() { defformatstring(buf)("screenshots" PATHDIVS "mapshot_%s_%s%s", behindpath(getclientmap()), timestring(), getscrext()); mapscreenshot(buf, true, screenshottype, screenshotscale, 0, jpegquality); } COMMAND(mapshot, ""); void screenshotpreview(int *res) { static int lastres = 240; defformatstring(buf)("packages" PATHDIVS "maps" PATHDIVS "%spreview" PATHDIVS "%s.jpg", securemapcheck(getclientmap(), false) ? "official" PATHDIVS : "", behindpath(getclientmap())); mapscreenshot(buf, false, 1, 1.0f, (lastres = clamp(((*res ? *res : lastres) / 48) * 48, 144, 480)), 80); reloadtexture(*textureload(buf, 3)); } COMMAND(screenshotpreview, "i"); bool needsautoscreenshot = false; void updatescreensize() { SDL_GetWindowSize(screen, &screenw, &screenh); if(screenh < 1) screenh = 1; VIRTW = screenw * VIRTH / screenh; glViewport(0, 0, screenw, screenh); } void screenres(int w, int h) { if(!screen) return; scr_w = clamp(w, SCR_MINW, SCR_MAXW); scr_h = clamp(h, SCR_MINH, SCR_MAXH); if(fullscreendesktop) { scr_w = min(scr_w, desktopw); scr_h = min(scr_h, desktoph); } if(!(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN)) SDL_SetWindowSize(screen, scr_w, scr_h); else if(!fullscreendesktop) initwarning("screen resolution"); } #if defined(WIN32) || defined(__APPLE__) || !defined(WIN32) void setresdata(char *s, enet_uint32 c) { extern hashtable &resdata; resdata[newstring(s)] = c; } #endif COMMANDF(screenres, "ii", (int *w, int *h) { screenres(*w, *h); }); int setgamma(int newgamma) // replacement for SDL_SetGamma { return screen ? SDL_SetWindowBrightness(screen, newgamma/100.0f) : -1; } static int curgamma = 100; VARNFP(gamma, vgamma, 30, 100, 300, { if(vgamma != curgamma) { if(setgamma(vgamma) == -1) conoutf("Could not set gamma: %s", SDL_GetError()); curgamma = vgamma; } }); void cleargamma() { if(curgamma != 100) setgamma(100); } void restoregamma() { if(curgamma != 100) { setgamma(100); setgamma(curgamma); } } void getdisplayresolutions() { string res = ""; int lh = 0, lw = 0, n = SDL_GetNumDisplayModes(0); SDL_DisplayMode mode; loopi(n) { if(SDL_GetDisplayMode(0, i, &mode) == 0 && lw != mode.w && lh != mode.h) concatformatstring(res, "%s%d %d", *res ? " " : "", (lw = mode.w), (lh = mode.h)); } result(res); } COMMAND(getdisplayresolutions, ""); void setupscreen(int &useddepthbits, int &usedfsaa) { if(glcontext) { SDL_GL_DeleteContext(glcontext); glcontext = NULL; } if(screen) { SDL_DestroyWindow(screen); screen = NULL; } 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_w < 0 || scr_h < 0) { scr_w = desktopw; scr_h = desktoph; } // first run: set to fullscreen res int modes = SDL_GetNumDisplayModes(0); if(modes >= 1) { bool hasmode = false; SDL_DisplayMode mode; loopi(modes) { if(SDL_GetDisplayMode(0, i, &mode) == 0 && scr_w <= mode.w && scr_h <= mode.h) { hasmode = true; break; } } if(!hasmode && SDL_GetDisplayMode(0, 0, &mode) == 0) { scr_w = mode.w; scr_h = mode.h; } } int winw = fullscreen && fullscreendesktop ? desktopw : scr_w; int winh = fullscreen && fullscreendesktop ? desktoph : scr_h; int flags = SDL_WINDOW_RESIZABLE; if(fullscreen) flags |= fullscreendesktop ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN; static int configs[] = { 0x7, /* try everything */ 0x6, 0x5, 0x3, /* try disabling one at a time */ 0x4, 0x2, 0x1, /* try disabling two at a time */ 0 /* try disabling everything */ }; int config = 0; SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0); if(!depthbits) SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); 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(!stencilbits && config&2) continue; if(fsaa<=0 && config&4) continue; if(depthbits) SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, config&1 ? depthbits : 16); if(stencilbits) { SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, config&2 ? stencilbits : 0); hasstencil = (config&2)!=0; } else hasstencil = false; if(fsaa>0) { SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, config&4 ? 1 : 0); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, config&4 ? fsaa : 0); } SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); screen = SDL_CreateWindow("AssaultCube", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, winw, winh, SDL_WINDOW_OPENGL | flags); if(screen) { glcontext = SDL_GL_CreateContext(screen); if(glcontext) break; SDL_DestroyWindow(screen); } } if(!screen) fatal("Unable to create OpenGL screen"); else { if(depthbits && (config&1)==0) conoutf("%d bit z-buffer not supported - disabling", depthbits); if(stencilbits && (config&2)==0) conoutf("%d bit stencil buffer not supported - disabling", stencilbits); if(fsaa>0 && (config&4)==0) conoutf("%dx anti-aliasing not supported - disabling", fsaa); } if(vsync>=0) SDL_GL_SetSwapInterval(vsync); updatescreensize(); inputgrab(grabinput = fullscreen ? true : false); useddepthbits = config&1 ? depthbits : 0; usedfsaa = config&2 ? fsaa : 0; } extern int hirestextures; void resetgl() { clearchanges(CHANGE_GFX); loadingscreen(); extern void cleanupparticles(); extern void cleanupmodels(); extern void cleanuptextures(); extern void cleanuptmus(); extern void cleanupgl(); cleanupparticles(); cleanupmodels(); cleanuptextures(); cleanuptmus(); cleanupgl(); uniformtexres = !hirestextures; c2skeepalive(); int useddepthbits = 0, usedfsaa = 0; setupscreen(useddepthbits, usedfsaa); gl_init(screenw, screenh, useddepthbits, usedfsaa); extern void reloadfonts(); extern Texture *startscreen; c2skeepalive(); if(!reloadtexture(*notexture) || !reloadtexture(*startscreen)) fatal("failed to reload core texture"); loadingscreen(); c2skeepalive(); restoregamma(); c2skeepalive(); reloadfonts(); reloadtextures(); c2skeepalive(); drawscope(true); preload_playermodels(); c2skeepalive(); preload_hudguns(); c2skeepalive(); preload_entmodels(); c2skeepalive(); preload_mapmodels(); c2skeepalive(); } COMMAND(resetgl, ""); VARFP(maxfps, 0, 200, 1000, if(maxfps && maxfps < 25) maxfps = 25); void limitfps(int &millis, int curmillis) { if(!maxfps) return; static int fpserror = 0; int delay = 1000/maxfps - (millis-curmillis); if(delay < 0) fpserror = 0; else { fpserror += 1000%maxfps; if(fpserror >= maxfps) { ++delay; fpserror -= maxfps; } if(delay > 0) { SDL_Delay(delay); millis += delay; } } } int lowfps = 30, highfps = 40; void fpsrange(int *low, int *high) { if(*low>*high || *low<1) return; lowfps = *low; highfps = *high; } COMMAND(fpsrange, "ii"); int keyrepeatmask = 0, textinputmask = 0; // bunches of flags for the same functions Uint32 textinputtime = 0; 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 { textinputmask &= ~mask; if(!textinputmask) SDL_StopTextInput(); } } vector events; void pushevent(const SDL_Event &e) { events.add(e); } bool interceptkey(int sym) { static int lastintercept = SDLK_UNKNOWN; int len = lastintercept == sym ? events.length() : 0; SDL_Event event; while(SDL_PollEvent(&event)) switch(event.type) { case SDL_MOUSEMOTION: break; default: pushevent(event); break; } lastintercept = sym; if(sym != SDLK_UNKNOWN) for(int i = len; i < events.length(); i++) { if(events[i].type == SDL_KEYDOWN && events[i].key.keysym.sym == sym) { events.remove(i); return true; } } return false; } static void resetmousemotion() { if (grabinput && !relativemouse && !(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN)) { SDL_WarpMouseInWindow(screen, screenw / 2, screenh / 2); } } static inline bool skipmousemotion(SDL_Event &event) { if(event.type != SDL_MOUSEMOTION) return true; if(!(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN)) { if(event.motion.x == screenw / 2 && event.motion.y == screenh / 2) return true; // ignore any motion events generated SDL_WarpMouse } return false; } static void checkmousemotion(int &dx, int &dy) { loopv(events) { SDL_Event &event = events[i]; if(skipmousemotion(event)) { if(i > 0) events.remove(0, i); return; } dx += event.motion.xrel; dy += event.motion.yrel; } events.setsize(0); SDL_Event event; while(SDL_PollEvent(&event)) { if(skipmousemotion(event)) { events.add(event); return; } dx += event.motion.xrel; dy += event.motion.yrel; } } int ignoremouse = 5, bootstrapentropy = 2; #define EVENTDEBUG(x) x //#define EVENTDEBUG(x) EVENTDEBUG(VAR(debugevents, 0, 0, 2)); void checkinput() { SDL_Event event; Uint32 lasttype = 0, lastbut = 0; int tdx=0,tdy=0; int focused = 0; while(events.length() || SDL_PollEvent(&event)) { if(events.length()) event = events.remove(0); EVENTDEBUG(int thres = 1; defformatstring(eb)("EVENT %d", event.type)); if (focused && event.type != SDL_WINDOWEVENT) { if (grabinput != (focused > 0)) inputgrab(grabinput = focused > 0, shouldgrab); focused = 0; } switch(event.type) { case SDL_QUIT: quit(); break; case SDL_KEYDOWN: if(bootstrapentropy > 0 && (--bootstrapentropy & 2)) mapscreenshot(NULL, false, -1, 1000.0f / (1000 + rnd(400)), 0, 0); case SDL_KEYUP: EVENTDEBUG(thres = 2; concatstring(eb, event.type == SDL_KEYUP ? "(SDL_KEYUP)" : "(SDL_KEYDOWN)")); entropy_add_byte(event.key.keysym.sym ^ totalmillis); EVENTDEBUG(concatformatstring(eb, " sym %d (%Xh), scancode %d (%Xh), state %d, repeat %d", event.key.keysym.sym, event.key.keysym.sym, event.key.keysym.scancode, event.key.keysym.scancode, event.key.state, event.key.repeat)); if(event.key.keysym.sym == SDLK_SCANCODE_MASK) event.key.keysym.sym |= event.key.keysym.scancode; // workaround SDL 2.0.5 bug which returns sym == 40000000h for all dead keys if(!event.key.repeat || keyrepeatmask) keypress(event.key.keysym.sym, event.key.keysym.scancode, event.key.state==SDL_PRESSED, (SDL_Keymod)event.key.keysym.mod); break; case SDL_TEXTINPUT: EVENTDEBUG(thres = 2; concatformatstring(eb, "(SDL_TEXTINPUT) %s %d %d", escapestring(event.text.text), textinputmask, int(event.text.timestamp - textinputtime))); if(textinputmask && int(event.text.timestamp - textinputtime) >= 5) // mute textinput for a few milliseconds, to avoid receiving the "t" that switched on the console { processtextinput(event.text.text); } break; case SDL_WINDOWEVENT: { EVENTDEBUG(concatstring(eb, "(SDL_WINDOWEVENT) ")); switch(event.window.event) { case SDL_WINDOWEVENT_SHOWN: // window has been shown EVENTDEBUG(concatstring(eb, " SDL_WINDOWEVENT_SHOWN")); break; case SDL_WINDOWEVENT_HIDDEN: // window has been hidden EVENTDEBUG(concatstring(eb, " SDL_WINDOWEVENT_HIDDEN")); break; case SDL_WINDOWEVENT_EXPOSED: // window has been exposed and should be redrawn EVENTDEBUG(concatstring(eb, " SDL_WINDOWEVENT_EXPOSED")); break; case SDL_WINDOWEVENT_MOVED: // window has been moved to data1, data2 EVENTDEBUG(concatformatstring(eb, " SDL_WINDOWEVENT_MOVED x %d, y %d", event.window.data1, event.window.data2)); if(!(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN)) centerwindow = false; break; case SDL_WINDOWEVENT_RESIZED: // window has been resized to data1 x data2; this is event is always preceded by SDL_WINDOWEVENT_SIZE_CHANGED EVENTDEBUG(concatformatstring(eb, " SDL_WINDOWEVENT_RESIZED %d x %d", event.window.data1, event.window.data2)); if(!fullscreendesktop || !(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN)) { scr_w = clamp(event.window.data1, SCR_MINW, SCR_MAXW); scr_h = clamp(event.window.data2, SCR_MINH, SCR_MAXH); } break; case SDL_WINDOWEVENT_SIZE_CHANGED: // window size has changed, either as a result of an API call or through the system or user changing the window size; this event is followed by SDL_WINDOWEVENT_RESIZED if the size was changed by an external event, i.e. the user or the window manager updatescreensize(); EVENTDEBUG(concatformatstring(eb, " SDL_WINDOWEVENT_SIZE_CHANGED %dx%d", screenw, screenh)); break; case SDL_WINDOWEVENT_MINIMIZED: // window has been minimized EVENTDEBUG(concatstring(eb, " SDL_WINDOWEVENT_MINIMIZED")); inputgrab(false); minimized = 1; break; case SDL_WINDOWEVENT_MAXIMIZED: // window has been maximized case SDL_WINDOWEVENT_RESTORED: // window has been restored to normal size and position EVENTDEBUG(concatstring(eb, event.window.event == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" : "SDL_WINDOWEVENT_MAXIMIZED")); minimized = 0; break; case SDL_WINDOWEVENT_ENTER: // window has gained mouse focus EVENTDEBUG(concatstring(eb, " SDL_WINDOWEVENT_ENTER")); shouldgrab = false; focused = 1; break; case SDL_WINDOWEVENT_LEAVE: // window has lost mouse focus EVENTDEBUG(concatstring(eb, " SDL_WINDOWEVENT_LEAVE")); shouldgrab = false; focused = -1; break; case SDL_WINDOWEVENT_FOCUS_GAINED: // window has gained keyboard focus EVENTDEBUG(concatstring(eb, " SDL_WINDOWEVENT_FOCUS_GAINED")); shouldgrab = true; break; case SDL_WINDOWEVENT_FOCUS_LOST: // window has lost keyboard focus EVENTDEBUG(concatstring(eb, " SDL_WINDOWEVENT_FOCUS_LOST")); shouldgrab = false; focused = -1; break; } break; } case SDL_MOUSEMOTION: EVENTDEBUG(thres = 2; concatformatstring(eb, "(SDL_MOUSEMOTION) x %d, y %d, dx %d, dy %d", event.motion.x, event.motion.y, event.motion.xrel, event.motion.yrel)); if(ignoremouse) { ignoremouse--; break; } if(grabinput && !skipmousemotion(event)) { int dx = event.motion.xrel, dy = event.motion.yrel; entropy_add_byte(dx ^ (256 - dy)); checkmousemotion(dx, dy); resetmousemotion(); tdx+=dx;tdy+=dy; } else if (shouldgrab) inputgrab(grabinput = true); break; case SDL_MOUSEBUTTONDOWN: if(!grabinput) // click-to-grab { EVENTDEBUG(concatformatstring(eb, "(SDL_MOUSEBUTTONDOWN) button %d, state %d, clicks %d, x %d, y %d", event.button.button, event.button.state, event.button.clicks, event.button.x, event.button.y)); inputgrab(grabinput = true); #ifdef WIN32 if(!fullscreen) { // If AC runs in windowed mode on Windows 10 and if you press WIN+D, then move mouse on desktop, press WIN+D again, click in the AC window, // the mouse cursor remains visible and the mouse is not contrained to the AC window anymore. It is as if SDL2 does not properly give focus // to the AC window and so our workaround is to explicitly trigger the focus by hiding and showing the window. This issue exists in // SDL 2.0.12 and is fixed in 2.0.14 however we currently can not use 2.0.14 because it has problems with handling of ALT+TAB hotkey behavior. // Please re-test and remove this workaround once a fixed version of SDL is available. SDL_HideWindow(screen); SDL_ShowWindow(screen); } #endif break; } case SDL_MOUSEBUTTONUP: EVENTDEBUG(concatformatstring(eb, "(SDL_MOUSEBUTTON%s) button %d, state %d, clicks %d, x %d, y %d", event.type == SDL_MOUSEBUTTONUP ? "UP" : "DOWN", event.button.button, event.button.state, event.button.clicks, event.button.x, event.button.y)); if(lasttype==event.type && lastbut==event.button.button) break; keypress(-(event.button.button > 3 ? (event.button.button + 4) : event.button.button), 0, event.button.state != SDL_RELEASED); lasttype = event.type; lastbut = event.button.button; break; case SDL_MOUSEWHEEL: EVENTDEBUG(concatformatstring(eb, "(SDL_MOUSEWHEEL) x %d, y %d", event.wheel.x, event.wheel.y)); if(event.wheel.y) { int key = event.wheel.y > 0 ? SDL_AC_BUTTON_WHEELUP : SDL_AC_BUTTON_WHEELDOWN; keypress(key, 0, true); // Emulate SDL1-style mouse wheel events by immediately "releasing" the wheel "button" keypress(key, 0, false); } if(event.wheel.x) { int key = event.wheel.x > 0 ? SDL_AC_BUTTON_RIGHT : SDL_AC_BUTTON_LEFT; keypress(key, 0, true); keypress(key, 0, false); } break; } EVENTDEBUG(if(debugevents >= thres) conoutf("%s", eb)); } if(tdx || tdy) { entropy_add_byte(tdy + 5 * tdx); mousemove(tdx, tdy); } if (focused) { if (grabinput != (focused > 0)) inputgrab(grabinput = focused > 0, shouldgrab); focused = 0; } } VARF(gamespeed, 10, 100, 1000, if(multiplayer()) gamespeed = 100); VARF(paused, 0, 0, 1, if(multiplayer()) paused = 0); bool inmainloop = false; static int clockrealbase = 0, clockvirtbase = 0; static void clockreset() { clockrealbase = SDL_GetTicks(); clockvirtbase = totalmillis; } VARFP(clockerror, 990000, 1000000, 1010000, clockreset()); VARFP(clockfix, 0, 0, 1, clockreset()); const char *rndmapname() { vector maps; listfiles("packages/maps/official", "cgz", maps); if (maps.length() > 0) { char *map = newstring(maps[rnd(maps.length())]); maps.deletearrays(); return map; } else return ""; } #define AUTOSTARTPATH "config" PATHDIVS "autostart" PATHDIVS void autostartscripts(const char *prefix) { static vector *files = NULL; if(!files) { // first run: fetch file names and sort them files = new vector; listfiles(AUTOSTARTPATH, "cfg", *files, stringsort); } loopv(*files) { if(*prefix && strncmp((*files)[i], prefix, strlen(prefix))) continue; defformatstring(p)(AUTOSTARTPATH "%s.cfg", (*files)[i]); execfile(p); delstring(files->remove(i--)); } } void createconfigtemplates(const char *templatezip) // create customisable config files in homedir - only if missing { vector files; void *mz = zipmanualopen(openfile(templatezip, "rb"), files); if(mz) { loopv(files) { const char *filename = behindpath(files[i]); // only look for config files in the zip file, ignore all paths in the zip if(strlen(filename) > 4 && !strcmp(filename + strlen(filename) - 4, ".cfg")) { defformatstring(fname)("config" PATHDIVS "%s", files[i]); if(getfilesize(fname) <= 0) // config does not exist or is empty { stream *zf = openfile(fname, "wb"); if(zf) { zipmanualread(mz, i, zf, MAXCFGFILESIZE); // fetch file content from zip, write to new config file delete zf; conoutf("created %s from template %s", fname, templatezip); } } } } zipmanualclose(mz); } findfile(AUTOSTARTPATH "dummy", "w"); // create empty autostart directory, if it doesn't exist yet } void connectprotocol(char *protocolstring) // assaultcube://example.org[:28763][/][?[port=28763][&password=secret]] { urlparse u; u.set(protocolstring); if(strcmp(u.scheme, "assaultcube") || !*u.domain) { conoutf("\f3bad commandline syntax (\"%s\")", protocolstring); return; } if(*u.userpassword) clientlogf(" ignoring user:password part of url (%s)", u.userpassword); if(*u.fragment) clientlogf(" ignoring fragment part of url (%s)", u.fragment); if(*u.path) clientlogf(" ignoring path part of url (%s)", u.path); int port = ATOI(u.port); const char *q = u.query, *passwd = NULL; while(q && *q) { if(*q == '&') q++; if(!strncmp(q, "port=", 5)) { q += 5; port = ATOI(q); q = strchr(q, '&'); } else if(!strncmp(q, "password=", 9)) { q += 9; const char *e = strchr(q, '&'); passwd = e ? newstring(q, e - q) : newstring(q); q = e; } else break; } defformatstring(cmd)("connect %s %d %s", u.domain, port, passwd ? passwd : ""); DEBUGCODE(clientlogf("connectprotocol: %s", cmd)); addsleep(5, cmd, true); DELSTRING(passwd); } #ifdef THISISNOTDEFINED //WIN32 static char *parsecommandline(const char *src, vector &args) { char *buf = new char[strlen(src) + 1], *dst = buf; for(;;) { while(isspace(*src)) src++; if(!*src) break; args.add(dst); for(bool quoted = false; *src && (quoted || !isspace(*src)); src++) { if(*src != '"') *dst++ = *src; else if(dst > buf && src[-1] == '\\') dst[-1] = '"'; else quoted = !quoted; } *dst++ = '\0'; } args.add(NULL); return buf; } int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw) { vector args; char *buf = parsecommandline(GetCommandLine(), args); SDL_SetModuleHandle(hInst); int status = SDL_main(args.length()-1, args.getbuf()); delete[] buf; exit(status); return 0; } #endif void initclientlog() // rotate old logfiles and create new one { filerotate("clientlog", "txt", 5); // keep five old logfiles clientlogfile = openfile("clientlog.txt", "w"); if(clientlogfile) { if(bootclientlog && bootclientlog->length()) clientlogfile->write(bootclientlog->getbuf(), bootclientlog->length()); } else conoutf("could not create logfile \"%s\"", findfile("clientlog.txt", "w")); DELETEP(bootclientlog); } #ifdef _DEBUG void sanitychecks() { ASSERT((GMMASK__BOT ^ GMMASK__MP ^ GMMASK__TEAM ^ GMMASK__FFA ^ GMMASK__TEAMSPAWN ^ GMMASK__FFASPAWN) == GMMASK__ALL); float fa = 1 << LARGEST_FACTOR, fb = fa; while(fa != fa + fb) fb /= 2; ASSERT(2 * fb < NEARZERO); } #endif #define DEFAULTPROFILEPATH "profile" int main(int argc, char **argv) { DEBUGCODE(sanitychecks()); extern struct servercommandline scl; #ifdef WIN32 //atexit((void (__cdecl *)(void))_CrtDumpMemoryLeaks); #ifndef _DEBUG #ifndef __GNUC__ __try { #endif #endif // VS2017+ does not support %n in format strings by default due to security concerns. // However, currently we are making use of this functionality and therefore it is being enabled below. // In the future we should get rid of this. See also https://docs.microsoft.com/en-us/previous-versions/hf4y5e3w(v=vs.140)?redirectedfrom=MSDN _set_printf_count_output(1); #endif bool dedicated = false; bool quitdirectly = false; char *initscript = NULL; char *initdemo = NULL; setlocale(LC_ALL, "POSIX"); if(bootclientlog) cvecprintf(*bootclientlog, "######## start logging: %s\n", timestring(true)); const char *initmap = rndmapname(); loopi(NUMGUNS) crosshairnames[i] = gunnames[i] = guns[i].modelname; crosshairnames[GUN_AKIMBO] = gunnames[GUN_AKIMBO] = "akimbo"; crosshairnames[CROSSHAIR_DEFAULT] = "default"; crosshairnames[CROSSHAIR_TEAMMATE] = "teammate"; crosshairnames[CROSSHAIR_SCOPE] = "scope"; crosshairnames[CROSSHAIR_EDIT] = "edit"; crosshairnames[CROSSHAIR_NUM] = gunnames[NUMGUNS] = ""; pushscontext(IEXC_CFG); persistidents = false; #define initlog(s) clientlogf("init: " s) if(*stream_capabilities()) clientlogf("info: %s", stream_capabilities()); initing = INIT_RESET; for(int i = 1; igetlil(); loopi(n) { string s; f->read(s, sizeof(string)); enet_uint32 c = f->getlil(); setresdata(s, c); } delete f; } autostartscripts("_beforesaved_"); initing = INIT_LOAD; if(!execfile("config/saved.cfg")) { exec("config/defaults.cfg"); bootstrapentropy += 5 + rnd(7); } autostartscripts("_aftersaved_"); exechook(HOOK_SP_MP, "afterinit", ""); autostartscripts(""); // all remaining scripts execfile("config/autoexec.cfg"); exechook(HOOK_SP_MP, "autoexec", ""); initing = NOT_INITING; uniformtexres = !hirestextures; initlog("models"); preload_playermodels(); preload_hudguns(); preload_entmodels(); initlog("docs"); persistidents = false; execfile("config/docs.cfg"); persistidents = true; initlog("localconnect"); extern string clientmap; if(initdemo) { extern int gamemode; gamemode = -1; copystring(clientmap, initdemo); } else copystring(clientmap, initmap); // ac_complex for 1.0, ac_shine for 1.1, .. localconnect(); if(initscript) execute(initscript); initlog("mainloop"); inputgrab(grabinput = true); inmainloop = true; #ifdef _DEBUG int lastflush = 0; #endif if(scl.logident[0]) { // "-Nxxx" sets the number of client log lines to xxx (after init) extern int clientloglinesremaining; clientloglinesremaining = atoi(scl.logident); // dual-use for scl.logident } for(;;) { static int frames = 0; static float fps = 10.0f; int millis = SDL_GetTicks() - clockrealbase; if(clockfix) millis = int(millis*(double(clockerror)/1000000)); millis += clockvirtbase; if(millis 5000 ? skipmillis - 5000 : min(1000, skipmillis); curtime += skipmillisnow; skipmillis -= skipmillisnow; } if(paused) curtime = 0; } lastmillis += curtime; totalmillis = millis; checkinput(); if(lastmillis) updateworld(curtime, lastmillis); if(needsautoscreenshot) showscores(true); serverslice(0); if(elapsed) fps = (1000.0f/elapsed+fps*10)/11; // avoid DIV-by-0 frames++; audiomgr.updateaudio(); computeraytable(camera1->o.x, camera1->o.y, dynfov()); if(frames>3 && !minimized) { gl_drawframe(screenw, screenh, fpshighfps ? fps/highfps : 1.0f), fps, elapsed); if(frames>4) SDL_GL_SwapWindow(screen); } if(needsautoscreenshot) { showscores(true); // draw again after swapping buffers, to make sure menu is captured // in the screenshot regardless of which frame buffer is current if(!minimized) { gl_drawframe(screenw, screenh, fpshighfps ? fps/highfps : 1.0f), fps, elapsed); } addsleep(0, "screenshot"); needsautoscreenshot = false; } #ifdef _DEBUG if(millis>lastflush+60000) { fflush(stdout); lastflush = millis; } #endif pollautodownloadresponse(); } quit(); return EXIT_SUCCESS; #if defined(WIN32) && !defined(_DEBUG) && !defined(__GNUC__) } __except(stackdumper(0, GetExceptionInformation()), EXCEPTION_CONTINUE_SEARCH) { return 0; } #endif } VAR(version, 1, AC_VERSION, 0); VAR(protocol, 1, PROTOCOL_VERSION, 0);