1 // loading and saving of savegames & demos, dumps the spawn state of all mapents, the full state of all dynents (monsters + player)
2 
3 #include "cube.h"
4 
5 extern int islittleendian;
6 
7 gzFile f = NULL;
8 bool demorecording = false;
9 bool demoplayback = false;
10 bool demoloading = false;
11 dvector playerhistory;
12 int democlientnum = 0;
13 
14 void startdemo();
15 
gzput(int i)16 void gzput(int i) { gzputc(f, i); };
gzputi(int i)17 void gzputi(int i) { gzwrite(f, &i, sizeof(int)); };
gzputv(vec & v)18 void gzputv(vec &v) { gzwrite(f, &v, sizeof(vec)); };
19 
gzcheck(int a,int b)20 void gzcheck(int a, int b) { if(a!=b) fatal("savegame file corrupt (short)"); };
gzget()21 int gzget() { char c = gzgetc(f); return c; };
gzgeti()22 int gzgeti() { int i; gzcheck(gzread(f, &i, sizeof(int)), sizeof(int)); return i; };
gzgetv(vec & v)23 void gzgetv(vec &v) { gzcheck(gzread(f, &v, sizeof(vec)), sizeof(vec)); };
24 
stop()25 void stop()
26 {
27     if(f)
28     {
29         if(demorecording) gzputi(-1);
30         gzclose(f);
31     };
32     f = NULL;
33     demorecording = false;
34     demoplayback = false;
35     demoloading = false;
36     loopv(playerhistory) zapdynent(playerhistory[i]);
37     playerhistory.setsize(0);
38 };
39 
stopifrecording()40 void stopifrecording() { if(demorecording) stop(); };
41 
savestate(char * fn)42 void savestate(char *fn)
43 {
44     stop();
45     f = gzopen(fn, "wb9");
46     if(!f) { conoutf("could not write %s", fn); return; };
47     gzwrite(f, (void *)"CUBESAVE", 8);
48     gzputc(f, islittleendian);
49     gzputi(SAVEGAMEVERSION);
50     gzputi(sizeof(dynent));
51     gzwrite(f, getclientmap(), _MAXDEFSTR);
52     gzputi(gamemode);
53     gzputi(ents.length());
54     loopv(ents) gzputc(f, ents[i].spawned);
55     gzwrite(f, player1, sizeof(dynent));
56     dvector &monsters = getmonsters();
57     gzputi(monsters.length());
58     loopv(monsters) gzwrite(f, monsters[i], sizeof(dynent));
59     gzputi(players.length());
60     loopv(players)
61     {
62         gzput(players[i]==NULL);
63         gzwrite(f, players[i], sizeof(dynent));
64     };
65 };
66 
savegame(char * name)67 void savegame(char *name)
68 {
69     if(!m_classicsp) { conoutf("can only save classic sp games"); return; };
70     sprintf_sd(fn)("savegames/%s.csgz", name);
71     savestate(fn);
72     stop();
73     conoutf("wrote %s", fn);
74 };
75 
loadstate(char * fn)76 void loadstate(char *fn)
77 {
78     stop();
79     if(multiplayer()) return;
80     f = gzopen(fn, "rb9");
81     if(!f) { conoutf("could not open %s", fn); return; };
82 
83     string buf;
84     gzread(f, buf, 8);
85     if(strncmp(buf, "CUBESAVE", 8)) goto out;
86     if(gzgetc(f)!=islittleendian) goto out;     // not supporting save->load accross incompatible architectures simpifies things a LOT
87     if(gzgeti()!=SAVEGAMEVERSION || gzgeti()!=sizeof(dynent)) goto out;
88     string mapname;
89     gzread(f, mapname, _MAXDEFSTR);
90     nextmode = gzgeti();
91     changemap(mapname); // continue below once map has been loaded and client & server have updated
92     return;
93     out:
94     conoutf("aborting: savegame/demo from a different version of cube or cpu architecture");
95     stop();
96 };
97 
loadgame(char * name)98 void loadgame(char *name)
99 {
100     sprintf_sd(fn)("savegames/%s.csgz", name);
101     loadstate(fn);
102 };
103 
loadgameout()104 void loadgameout()
105 {
106     stop();
107     conoutf("loadgame incomplete: savegame from a different version of this map");
108 };
109 
loadgamerest()110 void loadgamerest()
111 {
112     if(demoplayback || !f) return;
113 
114     if(gzgeti()!=ents.length()) return loadgameout();
115     loopv(ents)
116     {
117         ents[i].spawned = gzgetc(f)!=0;
118         if(ents[i].type==CARROT && !ents[i].spawned) trigger(ents[i].attr1, ents[i].attr2, true);
119     };
120     restoreserverstate(ents);
121 
122     gzread(f, player1, sizeof(dynent));
123     player1->lastaction = lastmillis;
124 
125     int nmonsters = gzgeti();
126     dvector &monsters = getmonsters();
127     if(nmonsters!=monsters.length()) return loadgameout();
128     loopv(monsters)
129     {
130         gzread(f, monsters[i], sizeof(dynent));
131         monsters[i]->enemy = player1;                                       // lazy, could save id of enemy instead
132         monsters[i]->lastaction = monsters[i]->trigger = lastmillis+500;    // also lazy, but no real noticable effect on game
133         if(monsters[i]->state==CS_DEAD) monsters[i]->lastaction = 0;
134     };
135     restoremonsterstate();
136 
137     int nplayers = gzgeti();
138     loopi(nplayers) if(!gzget())
139     {
140         dynent *d = getclient(i);
141         assert(d);
142         gzread(f, d, sizeof(dynent));
143     };
144 
145     conoutf("savegame restored");
146     if(demoloading) startdemo(); else stop();
147 };
148 
149 // demo functions
150 
151 int starttime = 0;
152 int playbacktime = 0;
153 int ddamage, bdamage;
154 vec dorig;
155 
record(char * name)156 void record(char *name)
157 {
158     if(m_sp) { conoutf("cannot record singleplayer games"); return; };
159     int cn = getclientnum();
160     if(cn<0) return;
161     sprintf_sd(fn)("demos/%s.cdgz", name);
162     savestate(fn);
163     gzputi(cn);
164     conoutf("started recording demo to %s", fn);
165     demorecording = true;
166     starttime = lastmillis;
167 	ddamage = bdamage = 0;
168 };
169 
demodamage(int damage,vec & o)170 void demodamage(int damage, vec &o) { ddamage = damage; dorig = o; };
demoblend(int damage)171 void demoblend(int damage) { bdamage = damage; };
172 
incomingdemodata(uchar * buf,int len,bool extras)173 void incomingdemodata(uchar *buf, int len, bool extras)
174 {
175     if(!demorecording) return;
176     gzputi(lastmillis-starttime);
177     gzputi(len);
178     gzwrite(f, buf, len);
179     gzput(extras);
180     if(extras)
181     {
182         gzput(player1->gunselect);
183         gzput(player1->lastattackgun);
184         gzputi(player1->lastaction-starttime);
185         gzputi(player1->gunwait);
186         gzputi(player1->health);
187         gzputi(player1->armour);
188         gzput(player1->armourtype);
189         loopi(NUMGUNS) gzput(player1->ammo[i]);
190         gzput(player1->state);
191 		gzputi(bdamage);
192 		bdamage = 0;
193 		gzputi(ddamage);
194 		if(ddamage)	{ gzputv(dorig); ddamage = 0; };
195         // FIXME: add all other client state which is not send through the network
196     };
197 };
198 
demo(char * name)199 void demo(char *name)
200 {
201     sprintf_sd(fn)("demos/%s.cdgz", name);
202     loadstate(fn);
203     demoloading = true;
204 };
205 
stopreset()206 void stopreset()
207 {
208     conoutf("demo stopped (%d msec elapsed)", lastmillis-starttime);
209     stop();
210     loopv(players) zapdynent(players[i]);
211     disconnect(0, 0);
212 };
213 
214 VAR(demoplaybackspeed, 10, 100, 1000);
scaletime(int t)215 int scaletime(int t) { return (int)(t*(100.0f/demoplaybackspeed))+starttime; };
216 
readdemotime()217 void readdemotime()
218 {
219     if(gzeof(f) || (playbacktime = gzgeti())==-1)
220     {
221         stopreset();
222         return;
223     };
224     playbacktime = scaletime(playbacktime);
225 };
226 
startdemo()227 void startdemo()
228 {
229     democlientnum = gzgeti();
230     demoplayback = true;
231     starttime = lastmillis;
232     conoutf("now playing demo");
233     dynent *d = getclient(democlientnum);
234     assert(d);
235     *d = *player1;
236     readdemotime();
237 };
238 
239 VAR(demodelaymsec, 0, 120, 500);
240 
catmulrom(vec & z,vec & a,vec & b,vec & c,float s,vec & dest)241 void catmulrom(vec &z, vec &a, vec &b, vec &c, float s, vec &dest)		// spline interpolation
242 {
243 	vec t1 = b, t2 = c;
244 
245 	vsub(t1, z); vmul(t1, 0.5f)
246 	vsub(t2, a); vmul(t2, 0.5f);
247 
248 	float s2 = s*s;
249 	float s3 = s*s2;
250 
251 	dest = a;
252 	vec t = b;
253 
254 	vmul(dest, 2*s3 - 3*s2 + 1);
255 	vmul(t,   -2*s3 + 3*s2);     vadd(dest, t);
256     vmul(t1,     s3 - 2*s2 + s); vadd(dest, t1);
257 	vmul(t2,     s3 -   s2);     vadd(dest, t2);
258 };
259 
fixwrap(dynent * a,dynent * b)260 void fixwrap(dynent *a, dynent *b)
261 {
262 	while(b->yaw-a->yaw>180)  a->yaw += 360;
263 	while(b->yaw-a->yaw<-180) a->yaw -= 360;
264 };
265 
demoplaybackstep()266 void demoplaybackstep()
267 {
268     while(demoplayback && lastmillis>=playbacktime)
269     {
270         int len = gzgeti();
271         if(len<1 || len>MAXTRANS)
272         {
273             conoutf("error: huge packet during demo play (%d)", len);
274             stopreset();
275             return;
276         };
277         uchar buf[MAXTRANS];
278         gzread(f, buf, len);
279         localservertoclient(buf, len);  // update game state
280 
281         dynent *target = players[democlientnum];
282         assert(target);
283 
284 		int extras;
285         if(extras = gzget())     // read additional client side state not present in normal network stream
286         {
287             target->gunselect = gzget();
288             target->lastattackgun = gzget();
289             target->lastaction = scaletime(gzgeti());
290             target->gunwait = gzgeti();
291             target->health = gzgeti();
292             target->armour = gzgeti();
293             target->armourtype = gzget();
294             loopi(NUMGUNS) target->ammo[i] = gzget();
295             target->state = gzget();
296             target->lastmove = playbacktime;
297 			if(bdamage = gzgeti()) damageblend(bdamage);
298 			if(ddamage = gzgeti()) { gzgetv(dorig); particle_splash(3, ddamage, 1000, dorig); };
299             // FIXME: set more client state here
300         };
301 
302         // insert latest copy of player into history
303         if(extras && (playerhistory.empty() || playerhistory.last()->lastupdate!=playbacktime))
304         {
305             dynent *d = newdynent();
306             *d = *target;
307             d->lastupdate = playbacktime;
308             playerhistory.add(d);
309             if(playerhistory.length()>20)
310             {
311                 zapdynent(playerhistory[0]);
312                 playerhistory.remove(0);
313             };
314         };
315 
316         readdemotime();
317     };
318 
319     if(demoplayback)
320     {
321         int itime = lastmillis-demodelaymsec;
322         loopvrev(playerhistory) if(playerhistory[i]->lastupdate<itime)      // find 2 positions in history that surround interpolation time point
323         {
324             dynent *a = playerhistory[i];
325             dynent *b = a;
326             if(i+1<playerhistory.length()) b = playerhistory[i+1];
327             *player1 = *b;
328             if(a!=b)                                // interpolate pos & angles
329             {
330 				dynent *c = b;
331 				if(i+2<playerhistory.length()) c = playerhistory[i+2];
332 				dynent *z = a;
333 				if(i-1>=0) z = playerhistory[i-1];
334 				//if(a==z || b==c) printf("* %d\n", lastmillis);
335 				float bf = (itime-a->lastupdate)/(float)(b->lastupdate-a->lastupdate);
336 				fixwrap(a, player1);
337 				fixwrap(c, player1);
338 				fixwrap(z, player1);
339 				vdist(dist, v, z->o, c->o);
340 				if(dist<16)		// if teleport or spawn, dont't interpolate
341 				{
342 					catmulrom(z->o, a->o, b->o, c->o, bf, player1->o);
343 					catmulrom(*(vec *)&z->yaw, *(vec *)&a->yaw, *(vec *)&b->yaw, *(vec *)&c->yaw, bf, *(vec *)&player1->yaw);
344 				};
345 				fixplayer1range();
346 			};
347             break;
348         };
349         //if(player1->state!=CS_DEAD) showscores(false);
350     };
351 };
352 
stopn()353 void stopn() { if(demoplayback) stopreset(); else stop(); conoutf("demo stopped"); };
354 
355 COMMAND(record, ARG_1STR);
356 COMMAND(demo, ARG_1STR);
357 COMMANDN(stop, stopn, ARG_NONE);
358 
359 COMMAND(savegame, ARG_1STR);
360 COMMAND(loadgame, ARG_1STR);
361