home *** CD-ROM | disk | FTP | other *** search
Wrap
// loading and saving of savegames & demos, dumps the spawn state of all mapents, the full state of all dynents (monsters + player) #include "cube.h" gzFile f = NULL; bool demorecording = false; bool demoplayback = false; bool demoloading = false; vector<playerent *> playerhistory; int democlientnum = 0; void startdemo(); void gzcheck(int a, int b) { if(a!=b) fatal("savegame file corrupt (short)"); } void gzput(int i) { if(gzputc(f, i)==-1) fatal("gzputc"); } void gzputi(int i) { gzcheck(gzwrite(f, &i, sizeof(int)), sizeof(int)); } void gzputv(vec &v) { gzcheck(gzwrite(f, &v, sizeof(vec)), sizeof(vec)); } int gzget() { char c = gzgetc(f); return c; } int gzgeti() { int i; gzcheck(gzread(f, &i, sizeof(int)), sizeof(int)); return i; } void gzgetv(vec &v) { gzcheck(gzread(f, &v, sizeof(vec)), sizeof(vec)); } void stop() { if(f) { if(demorecording) gzputi(-1); gzclose(f); } f = NULL; demorecording = false; demoplayback = false; demoloading = false; loopv(playerhistory) zapplayer(playerhistory[i]); playerhistory.setsize(0); setvar("gamespeed", 100); extern void recomputecamera(); recomputecamera(); } void stopifrecording() { if(demorecording) stop(); } void savestate(char *fn) { stop(); f = gzopen(fn, "wb9"); if(!f) { conoutf("could not write %s", fn); return; } gzwrite(f, (void *)"CUBESAVE", 8); gzputc(f, SDL_BYTEORDER==SDL_LIL_ENDIAN ? 1 : 0); gzputi(SAVEGAMEVERSION); gzputi(sizeof(playerent)); gzwrite(f, getclientmap(), _MAXDEFSTR); gzputi(gamemode); gzputi(ents.length()); loopv(ents) gzputc(f, ents[i].spawned); gzwrite(f, player1, sizeof(playerent)); gzputi(players.length()); loopv(players) { gzput(players[i]==NULL); gzwrite(f, players[i], sizeof(playerent)); } } void savegame(char *name) { conoutf("can only save classic sp games"); return; } void loadstate(char *fn) { stop(); if(multiplayer()) return; f = gzopen(fn, "rb9"); if(!f) { conoutf("could not open %s", fn); return; } string buf; gzread(f, buf, 8); if(strncmp(buf, "CUBESAVE", 8)) goto out; if(gzgetc(f)!=(SDL_BYTEORDER==SDL_LIL_ENDIAN ? 1 : 0)) goto out; // not supporting save->load accross incompatible architectures simpifies things a LOT if(gzgeti()!=SAVEGAMEVERSION || gzgeti()!=sizeof(playerent)) goto out; string mapname; gzread(f, mapname, _MAXDEFSTR); nextmode = gzgeti(); changemap(mapname); // continue below once map has been loaded and client & server have updated return; out: conoutf("aborting: savegame/demo from a different version of cube or cpu architecture"); stop(); } void loadgame(char *name) { s_sprintfd(fn)("savegames/%s.csgz", name); loadstate(fn); } void loadgameout() { stop(); conoutf("loadgame incomplete: savegame from a different version of this map"); } void loadgamerest() { if(demoplayback || !f) return; if(gzgeti()!=ents.length()) return loadgameout(); loopv(ents) { ents[i].spawned = gzgetc(f)!=0; } restoreserverstate(ents); gzread(f, player1, sizeof(playerent)); player1->lastaction = lastmillis; int nplayers = gzgeti(); loopi(nplayers) if(!gzget()) { playerent *d = newclient(i); ASSERT(d); gzread(f, d, sizeof(playerent)); d->lastaction = d->lastpain = lastmillis; } conoutf("savegame restored"); if(demoloading) startdemo(); else stop(); } // demo functions int starttime = 0; int playbacktime = 0; int ddamage, bdamage; vec dorig; int demomillis() { return lastmillis-starttime; } void record(char *name) { int cn = getclientnum(); if(cn<0) return; s_sprintfd(fn)("demos/%s.cdgz", name); savestate(fn); gzputi(cn); conoutf("started recording demo to %s", fn); demorecording = true; starttime = lastmillis; ddamage = bdamage = 0; //fixme player1->lastaction = player1->lastanimswitchtime[0] = player1->lastanimswitchtime[1] = lastmillis; } void demodamage(int damage, vec &o) { ddamage = damage; dorig = o; } void demoblend(int damage) { bdamage = damage; } void incomingdemodata(int chan, uchar *buf, int len, bool extras) { if(!demorecording) return; gzputi(lastmillis-starttime); gzputi(len); gzput(chan); gzwrite(f, buf, len); gzput(extras); if(extras) { gzput(player1->gunselect); gzput(player1->lastattackgun); gzputi(player1->gunwait); ASSERT(player1->lastaction == 0 || player1->lastaction-starttime >= 0); gzputi(player1->lastaction-starttime); gzputi(player1->lastanimswitchtime[0]-starttime); gzputi(player1->lastanimswitchtime[1]-starttime); loopi(NUMGUNS) { gzput(player1->ammo[i]); gzput(player1->mag[i]); } gzput(player1->akimbo ? 1 : 0 | (player1->reloading ? 1 : 0) << 1 | (player1->weaponchanging ? 1 : 0) << 2); switch(player1->gunselect) { case GUN_GRENADE: gzputi(player1->thrownademillis-starttime); break; case GUN_PISTOL: if(player1->akimbo) { gzputi(player1->akimbolastaction[0]-starttime); gzputi(player1->akimbolastaction[1]-starttime); } break; case GUN_SNIPER: gzput(scoped ? 1 : 0); break; } gzputi(player1->health); gzputi(player1->armour); gzput(player1->state); gzputi(bdamage); bdamage = 0; gzputi(ddamage); if(ddamage) { gzputv(dorig); ddamage = 0; } // FIXME: add all other client state which is not send through the network } } void settingsbackup(bool save) { static string name, team; static int gunselect; if(save) { s_strcpy(name, player1->name); s_strcpy(team, player1->team); gunselect = player1->gunselect; } else { s_strcpy(player1->name, name); s_strcpy(player1->team, team); player1->gunselect = gunselect; } } void demo(char *name) { settingsbackup(true); s_sprintfd(fn)("demos/%s.cdgz", name); loadstate(fn); demoloading = true; } void stopreset() { conoutf("demo stopped (%d msec elapsed)", lastmillis-starttime); player1 = newplayerent(); disconnect(0, 0); settingsbackup(false); } VAR(demoplaybackspeed, 10, 100, 1000); int scaletime(int t) { return (int)(t*(100.0f/demoplaybackspeed))+starttime; } void readdemotime() { if(gzeof(f) || (playbacktime = gzgeti())==-1) { stopreset(); return; } playbacktime = scaletime(playbacktime); } bool demopaused = false; void togglepause() { demopaused = !demopaused; } playerent *demoplayer = player1; bool firstpersondemo = true; bool localdemoplayer1st() { return demoplayback && demoplayer == player1 && firstpersondemo; } void shiftdemoplayer(int i) { if(!i) return; if(demoplayer == player1) { firstpersondemo = !firstpersondemo; if(!firstpersondemo) return; } vector<playerent *> plrs; loopv(players) if(players[i] != NULL) plrs.add(players[i]); int cur = plrs.find(demoplayer); if(cur >= 0) demoplayer = plrs[(cur+i) % plrs.length()]; } void startdemo() { democlientnum = gzgeti(); demoplayback = true; starttime = lastmillis; conoutf("now playing demo"); while(democlientnum>=players.length()) players.add(NULL); players[democlientnum] = player1; demoplayer = player1; readdemotime(); } VAR(demodelaymsec, 0, 120, 500); void catmulrom(vec &z, vec &a, vec &b, vec &c, float s, vec &dest) // spline interpolation { float s2 = s*s; float s3 = s*s2; dest = a; dest.mul(2*s3 - 3*s2 + 1); dest.add(vec(b).mul(-2*s3 + 3*s2)); dest.add(vec(b).sub(z).mul(0.5f).mul(s3 - 2*s2 + s)); dest.add(vec(c).sub(a).mul(0.5f).mul(s3 - s2)); } void fixwrap(dynent *a, dynent *b) { while(b->yaw-a->yaw>180) a->yaw += 360; while(b->yaw-a->yaw<-180) a->yaw -= 360; } void demoplaybackstep() { while(demoplayback && lastmillis>=playbacktime) { int len = gzgeti(); if(len<1 || len>MAXTRANS) { conoutf("error: huge packet during demo play (%d)", len); stopreset(); return; } int chan = gzget(); uchar buf[MAXTRANS]; gzread(f, buf, len); localservertoclient(chan, buf, len); // update game state playerent *target = (playerent *)players[democlientnum]; ASSERT(target); int extras; if((extras = gzget())) // read additional client side state not present in normal network stream { target->gunselect = gzget(); target->lastattackgun = gzget(); target->gunwait = gzgeti(); target->lastaction = scaletime(gzgeti()); //ASSERT(target->lastaction >= 0); if(target->lastaction < 0) target->lastaction = 0; target->lastanimswitchtime[0] = scaletime(gzgeti()); target->lastanimswitchtime[1] = scaletime(gzgeti()); loopi(NUMGUNS) { target->ammo[i] = gzget(); target->mag[i] = gzget(); } uchar flags = gzget(); target->akimbo = flags&1 ? true : false; target->reloading = (flags>>1)&1 ? true : false; target->weaponchanging = (flags>>2)&1 ? true : false; switch(target->gunselect) { case GUN_GRENADE: target->thrownademillis = scaletime(gzgeti()); break; case GUN_PISTOL: if(player1->akimbo) { target->akimbolastaction[0] = scaletime(gzgeti()); target->akimbolastaction[1] = scaletime(gzgeti()); } break; case GUN_SNIPER: scoped = gzget() ? true : false; break; } target->health = gzgeti(); target->armour = gzgeti(); target->state = gzget(); if((bdamage = gzgeti())) damageblend(bdamage); if((ddamage = gzgeti())) { gzgetv(dorig); particle_splash(3, ddamage, 1000, dorig); }; // FIXME: set more client state here target->lastmove = playbacktime; } // insert latest copy of player into history if(extras && (playerhistory.empty() || playerhistory.last()->lastupdate!=playbacktime)) { playerent *d = newplayerent(); *d = *target; d->lastupdate = playbacktime; playerhistory.add(d); if(playerhistory.length()>20) { zapplayer(playerhistory[0]); playerhistory.remove(0); } } readdemotime(); } if(demoplayback) { if(playerhistory.length()) memcpy(player1, playerhistory.last(), sizeof(playerent)); int itime = lastmillis-demodelaymsec; loopvrev(playerhistory) if(playerhistory[i]->lastupdate<itime) // find 2 positions in history that surround interpolation time point { playerent *a = playerhistory[i]; playerent *b = a; if(i+1<playerhistory.length()) b = playerhistory[i+1]; player1->o = b->o; player1->yaw = b->yaw; player1->pitch = b->pitch; if(a!=b) // interpolate pos & angles { dynent *c = b; if(i+2<playerhistory.length()) c = playerhistory[i+2]; dynent *z = a; if(i-1>=0) z = playerhistory[i-1]; //if(a==z || b==c) printf("* %d\n", lastmillis); float bf = (itime-a->lastupdate)/(float)(b->lastupdate-a->lastupdate); fixwrap(a, player1); fixwrap(c, player1); fixwrap(z, player1); if(z->o.dist(c->o)<16) // if teleport or spawn, dont't interpolate { catmulrom(z->o, a->o, b->o, c->o, bf, player1->o); catmulrom(*(vec *)&z->yaw, *(vec *)&a->yaw, *(vec *)&b->yaw, *(vec *)&c->yaw, bf, *(vec *)&player1->yaw); } fixcamerarange(player1); } break; } //if(player1->state!=CS_DEAD) showscores(false); } } void stopn() { if(demoplayback) stopreset(); else stop(); conoutf("demo stopped"); } COMMAND(record, ARG_1STR); COMMAND(demo, ARG_1STR); COMMANDN(stop, stopn, ARG_NONE); COMMANDN(demopause, togglepause, ARG_NONE); COMMANDN(demoplayer, shiftdemoplayer, ARG_1INT); COMMAND(savegame, ARG_1STR); COMMAND(loadgame, ARG_1STR);