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