#include #include "SDL/SDL.h" #include "SDL/SDL_net.h" #include "SDL/SDL_thread.h" #include "data_localgame.h" #include "netgame.h" #include "graphic.h" #include "utils.h" #include "game.h" //#include "events.h" #include "loader.h" #include "timing.h" #include "netlem_state_machine.h" // Application version number #include "netlem.h" #define STATS_TICKCOUNT 10 // Thread arguments structures struct _networkReadProc_args { client_t *client; tick_t *tick; tick_t *lastServerTick; int *drift_ms; }; struct _loadLevelProc_args { serverParams_t *serverParams; gameIni_t *gIni; gameRess_t *gRess; gameGraphics_t *gGraph; Uint8 *loadProgress; }; // Material for thread synchronization and collaboration SDL_sem *semGameStart; SDL_sem *semLoadLevel; // Thread entry points int networkReadProc(void *a); int loadLevelProc(void *a); // Client-specific functions void signals(int signum); void processLocalEvents(SDL_Rect *terrain, SDL_Rect *viewport, gameObjectsState_t *objStates, localParams_t *params); int act(tick_t *tick, int loadProgress, TCPsocket sockClient, gameObjectsState_t *objStates); int updateGraphics(gameObjectsState_t *objStates, localParams_t *params, gameGraphics_t *gGraph); int main(int argc, char **argv) { int drift_ms=0, endMainLoop, result; tick_t tick=0, lastServerTick=0; Uint32 timeBefore_ms[STATS_TICKCOUNT], beforeWait[STATS_TICKCOUNT], wantWait[STATS_TICKCOUNT], t; Uint8 loadProgress=0; double fps, wantWaitMean; client_t client; serverParams_t serverParams; localParams_t localParams; gameConfig_t conf; gameIni_t gIni; gameRess_t gRess; gameGraphics_t gGraph; gameObjectsState_t gObjStates; char logMsg[128]; struct _networkReadProc_args args; struct _loadLevelProc_args args2; IPaddress ip; if(argc!=2) { fprintf(stderr, "Usage: %s \n", argv[0]); return 1; } // Log system initialization openLog(NULL); // Starting log message snprintf(logMsg, 128, "NetLemmings version %i.%i", NetLemmings_VERSION_MAJOR, NetLemmings_VERSION_MINOR); logs(LOG_INFO, logMsg); // Server name resolution and connection if(SDLNet_ResolveHost(&ip,argv[1],9999)==-1) { logs2(LOG_ERROR, "main(), SDLNet_ResolveHost()", SDLNet_GetError()); return 2; } client.sockClient=SDLNet_TCP_Open(&ip); if(!client.sockClient) { logs2(LOG_ERROR,"main(), SDLNet_TCP_Open()", SDLNet_GetError()); return 3; } // Levelpack file loading //loadLevelPacks(); // Config file loading loadGameConfig(&conf); memset(&localParams, 0, sizeof(localParams_t)); // Libraries initialization result=init(WIN_CAPTION, &conf, &gGraph); if(result!=0) { logs(LOG_ERROR,"main(), init()"); return 3; } atexit(SDL_Quit); signal(2,signals); //TODO TESTS gObjStates.cursor.s = createSurface(32, 32); SDL_FillRect(gObjStates.cursor.s, &(gObjStates.cursor.s->clip_rect), 0x11223344); gObjStates.cursor.zOrder=999; gObjStates.allObj=&gObjStates.cursor; gObjStates.objCount=1; // Synchronization tools intialization semGameStart=SDL_CreateSemaphore(0); semLoadLevel=SDL_CreateSemaphore(0); // Some data initialization memset(&serverParams,0,sizeof(serverParams_t)); // Network reader thread to process remote events and time sync args.client=&client; args.tick=&tick; args.drift_ms=&drift_ms; args.lastServerTick=&lastServerTick; SDL_Thread *networkReadThread = SDL_CreateThread(networkReadProc, &args); if(!networkReadThread) { logs2(LOG_ERROR,"main(), SDL_CreateThread()", SDL_GetError()); return 4; } // Worker thread for loading levels asynchronously (the thread begin with a SDL_Wait(semLevelLoadingStart)) args2.serverParams=&serverParams; args2.gIni=&gIni; args2.gRess=&gRess; args2.gGraph=&gGraph; args2.loadProgress=&loadProgress; SDL_Thread *loadLevelThread = SDL_CreateThread(loadLevelProc, &args2); if(!loadLevelThread) { logs2(LOG_ERROR,"main(), SDL_CreateThread()", SDL_GetError()); return 4; //FIXME : autre num } //TODO : faire les menus et ne pas forcer ça ici if ( changeStateAndNotify(eMultiLoading, client.sockClient) == eNull ) { logs2(LOG_ERROR,"main()", "Could not force eMultiLoading state"); return 6; } // Statistics arrays initialization (for checking if sync and wait routines are ok) memset(timeBefore_ms, 0, STATS_TICKCOUNT*sizeof(Uint32)); memset(beforeWait, 0, STATS_TICKCOUNT*sizeof(Uint32)); memset(wantWait, 0, STATS_TICKCOUNT*sizeof(Uint32)); t=0; // Main game loop endMainLoop=0; while(!endMainLoop) { // Store loop begin date in a STATS_TICKCOUNT entries rotate buffer t=(t+1)%STATS_TICKCOUNT; timeBefore_ms[t] = SDL_GetTicks(); // Process local player keyboard and mouse events // (note: remote events are processed by network read thread) processLocalEvents(&(gGraph.surfaces.terrain->clip_rect), &(gGraph.viewport), &gObjStates, &localParams ); endMainLoop=act(&tick, loadProgress, client.sockClient, &gObjStates); // Display that new game state to the local user updateGraphics(&gObjStates, &localParams, &gGraph); // Delay that we have to wait for the next frame (depends on execution time and network time drift) wantWait[t]=waitForNextTick(timeBefore_ms[t], drift_ms); // Compute & display FPS mean value on STATS_TICKCOUNT ticks if (t==0) { fps=(STATS_TICKCOUNT*1000.0)/(timeBefore_ms[t]-timeBefore_ms[(t+1)%STATS_TICKCOUNT]); wantWaitMean=0; for(;tsurfaces.objectsSurf[i]); SDL_WaitThread(networkReadThread, &result); sprintf(logMsg, "networkReadThread terminated with code %i", result); SDL_SemPost(semLoadLevel); SDL_WaitThread(loadLevelThread, &result); sprintf(logMsg, "loadLevelThread terminated with code %i", result); SDL_DestroySemaphore(semGameStart); SDL_DestroySemaphore(semLoadLevel); logs(LOG_DEBUG, logMsg); closeLog(); return 0; } void signals(int signum) { static int force=0; char buf[128]; sprintf(buf, "Caught signal %i", signum); logs(LOG_WARN, buf); if(!force) { changeState(eEnd); force=1; logs(LOG_WARN, "Trying to stop smoothly..."); } else { logs(LOG_WARN, "Emergency stop"); exit(1); } } int act(tick_t *tick, int loadProgress, TCPsocket sockClient, gameObjectsState_t *objStates) { int res; switch(getState()) { case eMultiLoading: if(loadProgress==100) { changeStateAndNotify(eMultiWaitLoading,sockClient); } break; case eMultiWaitLoading: res=SDL_SemTryWait(semGameStart); switch (res) { case -1: logs2(LOG_ERROR, "main(), SDL_SemTryWait()", SDL_GetError()); return 7; break; case 0: //FIXME : check return value changeState(eMultiGame); break; default: break; } break; case eSingleGame: case eMultiGame: // Make game evolve from the current state to the next time chunk (ie. frame, or tick) play((*tick)++, objStates); break; default: return 1; break; } return 0; } int networkReadProc(void *a) { int result; event_t e; //char logMsg[128]; struct _networkReadProc_args *args = (struct _networkReadProc_args *)a; while( getState() != eEnd ) { // logs(LOG_DEBUG, "Waiting event"); result=receiveEvent(args->client,&e); if (result != 0) { logs(LOG_WARN, "networkReadProc(), receiveEvents() error"); changeState(eEnd); continue; } // logs(LOG_DEBUG, "Got event"); //FIXME : WTF dans cette gestion de temps ??? Ou est l'implémentation réelle telle que décrite dans archi.txt *(args->drift_ms)=updateDriftOnEventReception(*(args->tick), e.serverTick ); *(args->lastServerTick)=e.serverTick; // sprintf(logMsg, "serverTick==%i, tick==%i, drift_ms==%i\n", e.serverTick, *(args->tick), *(args->drift_ms)); // logs(LOG_DEBUG, logMsg); result=SDL_SemPost(semGameStart); if (result!=0) { logs2(LOG_ERROR, "main(), SDL_SemPost()", SDL_GetError()); return 1; } switch(e.type) { case eLemAction: //TODO break; case eTimeSync: // Rien à faire dans ce cas, la synchro du temps se fait quelque soti le tyep d'évènement break; default: logs(LOG_ERROR, "serveClient(), Unknown event type"); } } logs(LOG_DEBUG, "networkReadProc() : end"); return 0; } int startLoadLevel() { int result; logs(LOG_INFO, "Start game loading"); result=SDL_SemPost(semLoadLevel); return result; } //FIXME : defines cnetraliser #define PATH_STYLE "../styles" #define PATH_LEVEL "../level" int loadLevel(struct _loadLevelProc_args *args) { int res,len; char *filepath; // Setting default values memset(args->gIni,0,sizeof(gameIni_t)); *(args->loadProgress)=1; // Loading lvl ini file len=strlen(PATH_LEVEL)+strlen("1_orig")+strlen("lvl0003")+strlen("//.ini")+1; filepath=malloc(sizeof(char)*len); snprintf(filepath, len, "%s/%s/%s.ini", PATH_LEVEL, "1_orig", "lvl0003"); res=loadIni(args->gIni, filepath); free(filepath); if ( res != 0 ) { logs2(LOG_ERROR, "loadLevel(), loadIni()", "Could not load level ini file"); return 2; } args->gGraph->viewport.x = args->gIni->level.xPos; *(args->loadProgress)=10; // Check if we found a "style =" line in level ini file if (args->gIni->level.style==NULL) { logs2(LOG_ERROR, "loadLevel()", "No valid style detected"); return 3; } args->gIni->style.name=args->gIni->level.style; // Loading style ini file len=strlen(PATH_STYLE)+2*strlen(args->gIni->level.style)+strlen("//.ini")+1; filepath=malloc(sizeof(char)*len); snprintf(filepath, len, "%s/%s/%s.ini", PATH_STYLE, args->gIni->level.style, args->gIni->level.style); res=loadIni(args->gIni, filepath); free(filepath); if (res!=0) { logs2(LOG_ERROR, "loadLevel(), loadIni()", "Could not load style ini file"); return 4; } *(args->loadProgress)=20; res=loadRessources(args->gIni, args->gRess); if (res!=0) { logs(LOG_ERROR, "loadLevel(), loadRessources()"); return 5; } *(args->loadProgress)=50; res=paintTerrain(args->gIni, args->gRess, args->gGraph); if (res!=0) { logs(LOG_ERROR, "loadLevel(), paintTerrain()"); return 6; } *(args->loadProgress)=80; return 0; } int loadLevelProc(void *a) { int res; char strerr[8]; struct _loadLevelProc_args *args = (struct _loadLevelProc_args *)a; logs(LOG_WARN, "loadLevelProc(), beginnng"); while( getState() != eEnd ) { res=SDL_SemWait(semLoadLevel); if (res!=0) { logs2(LOG_ERROR, "main(), SDL_SemTryWaitTimeout()", SDL_GetError()); } logs(LOG_WARN, "loadLevelProc(), start load level"); res=loadLevel(args); if (res!=0) { sprintf(strerr, "%i", res); logs2(LOG_ERROR, "loadLevelProc(), loadLevel()", strerr); } else { *(args->loadProgress)=100; } logs(LOG_WARN, "loadLevelProc(), end load level"); } logs(LOG_WARN, "loadLevelProc(), end thread"); return 0; } #define MAP_SCROLL_BOUND 32 #define MAP_SCROLL_SPEED 16 void processLocalEvents(SDL_Rect *terrain, SDL_Rect *viewport, gameObjectsState_t *objStates, localParams_t *params) { static int mouseActive=1, mouseX=100, mouseY=100; SDL_Event event; while( SDL_PollEvent( &event ) ) { switch (event.type) { case SDL_KEYDOWN: switch(event.key.keysym.sym){ //case SDLK_HOME : decalFps = 0; break; //case SDLK_END : decalFps = FPS-11; break; //case SDLK_PAGEUP : if(decalFps>0){decalFps -=1;}break; //case SDLK_PAGEDOWN : if(decalFps<(FPS-1)){decalFps +=1;}break; //case SDLK_w : paint_stencil = (paint_stencil==0)? 1 : 0 ; break; // case SDLK_x : switchMiniMapMode= (switchMiniMapMode==0)? 1 : ((switchMiniMapMode==1)? 2 : ((switchMiniMapMode==2)? 3 : 0)) ;permission=0;break; // case SDLK_p :{ // zoomX=((zoomX >= 1. )? zoomX + 1. : zoomX + 0.1 ); // zoomY=((zoomY >= 1. )? zoomY + 1. : zoomY + 0.1 ); // if(zoomX > 10.) {zoomX=10.;} // if(zoomY > 10.) {zoomY=10.;} // break; // } // case SDLK_m :{ // zoomX=((zoomX > 1. )? zoomX - 1. : zoomX - 0.1 ); // zoomY=((zoomY > 1. )? zoomY - 1. : zoomY - 0.1 ); // if(zoomX <= 0.) {zoomX=0.1;} // if(zoomY <= 0.) {zoomY=0.1;} // break; // } case SDLK_ESCAPE: changeState(eEnd); break; case SDLK_d: params->debugFlags ^= DEBUG_DIRTYRECTANGLES; break; default: break; } break; case SDL_ACTIVEEVENT: if ( event.active.state == SDL_APPMOUSEFOCUS ) { mouseActive = event.active.gain; } break; case SDL_MOUSEMOTION: mouseX = event.motion.x; mouseY = event.motion.y; objStates->cursor.dirty=1; break; case SDL_MOUSEBUTTONDOWN: //err=mouse_action(&gInit, mouseX, mouseY,camera.x,camera.y ); break; case SDL_QUIT: changeState(eEnd); break; } } switch(getState()) { case eMultiGame: if (mouseActive) { if(mouseY <= terrain->h){ if (mouseX > (viewport->w - MAP_SCROLL_BOUND)){ if (viewport->x < (terrain->w - viewport->w ) ) { viewport->x += MAP_SCROLL_SPEED; } } if (mouseX < MAP_SCROLL_BOUND){ if (viewport->x >= MAP_SCROLL_SPEED ) { viewport->x -= MAP_SCROLL_SPEED; } } } } break; default: break; } objStates->cursor.pos.x = mouseX - objStates->cursor.s->w/2 + viewport->x; objStates->cursor.pos.y = mouseY - objStates->cursor.s->h/2 + viewport->y; return; } int updateGraphics(gameObjectsState_t *objStates, localParams_t *params, gameGraphics_t *gGraph) { int i, res; static SDL_Rect lastViewport= {0,0,0,0}; SDL_Rect srcRect, dstRect; SDL_Surface *tmpSurf; struct gameObjectState *o; char logMsg[256]; //static Uint32 tmpColor=0; //TODO : modifier les calques switch(getState()) { case eMultiWaitLoading: //TODO break; case eMultiGame: // If we had a camera movement, we have to refesh all the screen tmpSurf=gGraph->surfaces.tmpSurf; // SDL_FillRect(tmpSurf, &(tmpSurf->clip_rect), tmpColor); // tmpColor+=0x11223344; if ( lastViewport.x == gGraph->viewport.x && (params->debugFlags & DEBUG_DIRTYRECTANGLES) == 0 ) { // We use a dirty rectangle method for performance for(i=0; iobjCount; i++) { o=objStates->allObj+i; if ( /*TODO intersect(viewport, dstRect) &&*/ o->dirty == 1 ) { dstRect.x=o->prevPos.x-gGraph->viewport.x; dstRect.y=o->prevPos.y-gGraph->viewport.y; //TODO : pas cencé setter w et h ici dstRect.w=o->s->clip_rect.w; dstRect.h=o->s->clip_rect.h; srcRect.x=o->prevPos.x;//-lastViewport.x; srcRect.y=o->prevPos.y;//-lastViewport.y; srcRect.w=o->s->clip_rect.w; srcRect.h=o->s->clip_rect.h; res=repaint(objStates, &gGraph->surfaces, &srcRect, tmpSurf); if ( res != 0 ) { logs(LOG_WARN, "updateGraphics(), repaint() failed"); } if ( (params->debugFlags & DEBUG_DIRTYRECTANGLES) == 0 ) { refresh(tmpSurf, &srcRect, gGraph->screen, dstRect); } dstRect.x=o->pos.x-gGraph->viewport.x; dstRect.y=o->pos.y-gGraph->viewport.y; //dstRect.w=o->s->clip_rect.w; //dstRect.h=o->s->clip_rect.h; srcRect.x=o->pos.x; srcRect.y=o->pos.y; //srcRect.w=o->s->clip_rect.w; //srcRect.h=o->s->clip_rect.h; snprintf(logMsg, 256, "refresh item %d / %d \n\t Carateristiques:\n\t-viewport {%d;%d}\n\t-lastViewport {%d;%d}\n\t-dstRect {%d;%d;%d;%d}\n\t-srcRect {%d;%d;%d;%d} \n", i+1, objStates->objCount, gGraph->viewport.x,gGraph->viewport.y, lastViewport.x,lastViewport.y, dstRect.x,dstRect.y, dstRect.w,dstRect.h, srcRect.x,srcRect.y, srcRect.w,srcRect.h); logs(LOG_DEBUG, logMsg); res=repaint(objStates, &gGraph->surfaces, &srcRect, tmpSurf); if ( res != 0 ) { logs(LOG_WARN, "updateGraphics(), repaint() failed"); } if ( (params->debugFlags & DEBUG_DIRTYRECTANGLES) == 0 ) { refresh(tmpSurf, &srcRect, gGraph->screen, dstRect); } o->prevPos.x = o->pos.x; o->prevPos.y = o->pos.y; o->dirty=0; } } } else { dstRect.x=0; dstRect.y=0; srcRect.x=gGraph->viewport.x; srcRect.y=gGraph->viewport.y; srcRect.w=gGraph->screen->clip_rect.w; srcRect.h=gGraph->screen->clip_rect.h; if ( lastViewport.x != gGraph->viewport.x ) { lastViewport.x=gGraph->viewport.x; // update surface : tmpSurf res=repaint(objStates, &gGraph->surfaces, &srcRect, tmpSurf); if ( res != 0 ) { logs(LOG_WARN, "updateGraphics(), repaint() failed"); } } // update screen refresh(tmpSurf, &srcRect, gGraph->screen, dstRect); if ( (params->debugFlags & DEBUG_DIRTYRECTANGLES) == DEBUG_DIRTYRECTANGLES ) { SDL_LockSurface(tmpSurf); for(i=0; i < tmpSurf->pitch * tmpSurf->h ; ++i) { ((Uint8 *)tmpSurf->pixels)[i] *= 0.9; } SDL_UnlockSurface(tmpSurf); } } break; default: break; } return 0; }