Line data Source code
1 : // worldio.cpp: loading & saving of maps and savegames
2 :
3 : #include "../libprimis-headers/cube.h"
4 : #include "../../shared/geomexts.h"
5 : #include "../../shared/glexts.h"
6 : #include "../../shared/stream.h"
7 :
8 : #include "light.h"
9 : #include "octaedit.h"
10 : #include "octaworld.h"
11 : #include "raycube.h"
12 : #include "world.h"
13 :
14 : #include "interface/console.h"
15 : #include "interface/cs.h"
16 : #include "interface/menus.h"
17 :
18 : #include "render/octarender.h"
19 : #include "render/renderwindow.h"
20 : #include "render/shaderparam.h"
21 : #include "render/texture.h"
22 :
23 : static std::string clientmap = "";
24 :
25 1 : static void validmapname(char *dst, const char *src, const char *prefix = nullptr, const char *alt = "untitled", size_t maxlen = 100)
26 : {
27 1 : if(prefix)
28 : {
29 0 : while(*prefix)
30 : {
31 0 : *dst++ = *prefix++;
32 : }
33 : }
34 1 : const char *start = dst;
35 1 : if(src)
36 : {
37 1 : for(int i = 0; i < static_cast<int>(maxlen); ++i)
38 : {
39 1 : char c = *src++;
40 1 : if(iscubealnum(c) || c == '_' || c == '-' || c == '/' || c == '\\')
41 : {
42 0 : *dst++ = c;
43 : }
44 : else
45 : {
46 1 : break;
47 : }
48 : }
49 : }
50 1 : if(dst > start)
51 : {
52 0 : *dst = '\0';
53 : }
54 1 : else if(dst != alt)
55 : {
56 1 : copystring(dst, alt, maxlen);
57 : }
58 1 : }
59 :
60 : //used in iengine.h
61 0 : const char * getclientmap()
62 : {
63 0 : return clientmap.c_str();
64 : }
65 :
66 0 : void setmapname(const char * newname)
67 : {
68 0 : clientmap = std::string(newname);
69 0 : }
70 :
71 0 : bool cubeworld::loadmapheader(stream *f, const char *ogzname, mapheader &hdr, octaheader &ohdr) const
72 : {
73 0 : if(f->read(&hdr, 3*sizeof(int)) != 3*sizeof(int))
74 : {
75 0 : conoutf(Console_Error, "map %s has malformatted header", ogzname);
76 0 : return false;
77 : }
78 0 : if(!std::memcmp(hdr.magic, "TMAP", 4))
79 : {
80 0 : if(hdr.version>currentmapversion)
81 : {
82 0 : conoutf(Console_Error, "map %s requires a newer version of Tesseract", ogzname);
83 0 : return false;
84 : }
85 0 : if(f->read(&hdr.worldsize, 6*sizeof(int)) != 6*sizeof(int))
86 : {
87 0 : conoutf(Console_Error, "map %s has malformatted header", ogzname);
88 0 : return false;
89 : }
90 0 : if(hdr.worldsize <= 0|| hdr.numents < 0)
91 : {
92 0 : conoutf(Console_Error, "map %s has malformatted header", ogzname);
93 0 : return false;
94 : }
95 : }
96 0 : else if(!std::memcmp(hdr.magic, "OCTA", 4))
97 : {
98 0 : if(hdr.version!=octaversion)
99 : {
100 0 : conoutf(Console_Error, "map %s uses an unsupported map format version", ogzname);
101 0 : return false;
102 : }
103 0 : if(f->read(&ohdr.worldsize, 7*sizeof(int)) != 7*sizeof(int))
104 : {
105 0 : conoutf(Console_Error, "map %s has malformatted header", ogzname);
106 0 : return false;
107 : }
108 0 : if(ohdr.worldsize <= 0|| ohdr.numents < 0)
109 : {
110 0 : conoutf(Console_Error, "map %s has malformatted header", ogzname);
111 0 : return false;
112 : }
113 0 : std::memcpy(hdr.magic, "TMAP", 4);
114 0 : hdr.version = 0;
115 0 : hdr.headersize = sizeof(hdr);
116 0 : hdr.worldsize = ohdr.worldsize;
117 0 : hdr.numents = ohdr.numents;
118 0 : hdr.numvars = ohdr.numvars;
119 0 : hdr.numvslots = ohdr.numvslots;
120 : }
121 : else
122 : {
123 0 : conoutf(Console_Error, "map %s uses an unsupported map type", ogzname);
124 0 : return false;
125 : }
126 :
127 0 : return true;
128 : }
129 :
130 : VARP(savebak, 0, 2, 2);
131 :
132 0 : void cubeworld::setmapfilenames(const char *fname, const char *cname)
133 : {
134 : string name;
135 0 : validmapname(name, fname);
136 0 : formatstring(ogzname, "media/map/%s.ogz", name);
137 0 : formatstring(picname, "media/map/%s.png", name);
138 0 : if(savebak==1)
139 : {
140 0 : formatstring(bakname, "media/map/%s.BAK", name);
141 : }
142 : else
143 : {
144 : string baktime;
145 0 : time_t t = std::time(nullptr);
146 0 : size_t len = std::strftime(baktime, sizeof(baktime), "%Y-%m-%d_%H.%M.%S", std::localtime(&t));
147 0 : baktime[std::min(len, sizeof(baktime)-1)] = '\0';
148 0 : formatstring(bakname, "media/map/%s_%s.BAK", name, baktime);
149 : }
150 0 : validmapname(name, cname ? cname : fname);
151 0 : formatstring(cfgname, "media/map/%s.cfg", name);
152 0 : path(ogzname);
153 0 : path(bakname);
154 0 : path(cfgname);
155 0 : path(picname);
156 0 : }
157 :
158 1 : void mapcfgname()
159 : {
160 1 : const char *mname = clientmap.c_str();
161 : string name;
162 1 : validmapname(name, mname);
163 1 : DEF_FORMAT_STRING(cfgname, "media/map/%s.cfg", name);
164 1 : path(cfgname);
165 1 : result(cfgname);
166 1 : }
167 :
168 0 : void backup(const char *name, const char *backupname)
169 : {
170 : string backupfile;
171 0 : copystring(backupfile, findfile(backupname, "wb"));
172 0 : std::remove(backupfile);
173 0 : std::rename(findfile(name, "wb"), backupfile);
174 0 : }
175 :
176 : enum OctaSave
177 : {
178 : OctaSave_Children = 0,
179 : OctaSave_Empty,
180 : OctaSave_Solid,
181 : OctaSave_Normal
182 : };
183 :
184 : static int savemapprogress = 0;
185 :
186 0 : void cubeworld::savec(const std::array<cube, 8> &c, const ivec &o, int size, stream * const f)
187 : {
188 0 : if((savemapprogress++&0xFFF)==0)
189 : {
190 0 : renderprogress(static_cast<float>(savemapprogress)/allocnodes, "saving octree...");
191 : }
192 0 : for(int i = 0; i < 8; ++i) //loop through children (there's always eight in an octree)
193 : {
194 0 : ivec co(i, o, size);
195 0 : if(c[i].children) //recursively note existence of children & call this fxn again
196 : {
197 0 : f->putchar(OctaSave_Children);
198 0 : savec(*(c[i].children), co, size>>1, f);
199 : }
200 : else //once we're done with all cube children within cube *c given
201 : {
202 0 : int oflags = 0,
203 0 : surfmask = 0,
204 0 : totalverts = 0;
205 0 : if(c[i].material!=Mat_Air)
206 : {
207 0 : oflags |= 0x40;
208 : }
209 0 : if(c[i].isempty()) //don't need tons of info saved if we know it's just empty
210 : {
211 0 : f->putchar(oflags | OctaSave_Empty);
212 : }
213 : else
214 : {
215 0 : if(c[i].merged)
216 : {
217 0 : oflags |= 0x80;
218 : }
219 0 : if(c[i].ext)
220 : {
221 0 : for(int j = 0; j < 6; ++j)
222 : {
223 : {
224 0 : const surfaceinfo &surf = c[i].ext->surfaces[j];
225 0 : if(!surf.used())
226 : {
227 0 : continue;
228 : }
229 0 : oflags |= 0x20;
230 0 : surfmask |= 1<<j;
231 0 : totalverts += surf.totalverts();
232 : }
233 : }
234 : }
235 0 : if(c[i].issolid())
236 : {
237 0 : f->putchar(oflags | OctaSave_Solid);
238 : }
239 : else
240 : {
241 0 : f->putchar(oflags | OctaSave_Normal);
242 0 : f->write(c[i].edges, 12);
243 : }
244 : }
245 :
246 0 : for(int j = 0; j < 6; ++j) //for each face (there's always six) save the texture slot
247 : {
248 0 : f->put<ushort>(c[i].texture[j]);
249 : }
250 0 : if(oflags&0x40) //0x40 is the code for a material (water, lava, alpha, etc.)
251 : {
252 0 : f->put<ushort>(c[i].material);
253 : }
254 0 : if(oflags&0x80) //0x80 is the code for a merged cube (remipping merged this cube with neighbors)
255 : {
256 0 : f->putchar(c[i].merged);
257 : }
258 0 : if(oflags&0x20)
259 : {
260 0 : f->putchar(surfmask);
261 0 : f->putchar(totalverts);
262 0 : for(int j = 0; j < 6; ++j)
263 : {
264 0 : if(surfmask&(1<<j))
265 : {
266 0 : surfaceinfo surf = c[i].ext->surfaces[j]; //intentional copy
267 0 : const vertinfo *verts = c[i].ext->verts() + surf.verts;
268 0 : int layerverts = surf.numverts&Face_MaxVerts,
269 0 : numverts = surf.totalverts(),
270 0 : vertmask = 0,
271 0 : vertorder = 0,
272 0 : dim = DIMENSION(j),
273 0 : vc = C[dim],
274 0 : vr = R[dim];
275 0 : if(numverts)
276 : {
277 0 : if(c[i].merged&(1<<j))
278 : {
279 0 : vertmask |= 0x04;
280 0 : if(layerverts == 4)
281 : {
282 0 : ivec v[4] = { verts[0].getxyz(), verts[1].getxyz(), verts[2].getxyz(), verts[3].getxyz() };
283 0 : for(int k = 0; k < 4; ++k)
284 : {
285 0 : const ivec &v0 = v[k],
286 0 : &v1 = v[(k+1)&3],
287 0 : &v2 = v[(k+2)&3],
288 0 : &v3 = v[(k+3)&3];
289 0 : if(v1[vc] == v0[vc] && v1[vr] == v2[vr] && v3[vc] == v2[vc] && v3[vr] == v0[vr])
290 : {
291 0 : vertmask |= 0x01;
292 0 : vertorder = k;
293 0 : break;
294 : }
295 : }
296 : }
297 : }
298 : else
299 : {
300 0 : int vis = visibletris(c[i], j, co, size);
301 0 : if(vis&4 || faceconvexity(c[i], j) < 0)
302 : {
303 0 : vertmask |= 0x01;
304 : }
305 0 : if(layerverts < 4 && vis&2)
306 : {
307 0 : vertmask |= 0x02;
308 : }
309 : }
310 0 : bool matchnorm = true;
311 0 : for(int k = 0; k < numverts; ++k)
312 : {
313 0 : const vertinfo &v = verts[k];
314 0 : if(v.norm)
315 : {
316 0 : vertmask |= 0x80;
317 0 : if(v.norm != verts[0].norm)
318 : {
319 0 : matchnorm = false;
320 : }
321 : }
322 : }
323 0 : if(matchnorm)
324 : {
325 0 : vertmask |= 0x08;
326 : }
327 : }
328 0 : surf.verts = vertmask;
329 0 : f->write(&surf, sizeof(surf));
330 0 : bool hasxyz = (vertmask&0x04)!=0,
331 0 : hasnorm = (vertmask&0x80)!=0;
332 0 : if(layerverts == 4)
333 : {
334 0 : if(hasxyz && vertmask&0x01)
335 : {
336 0 : ivec v0 = verts[vertorder].getxyz(),
337 0 : v2 = verts[(vertorder+2)&3].getxyz();
338 0 : f->put<ushort>(v0[vc]); f->put<ushort>(v0[vr]);
339 0 : f->put<ushort>(v2[vc]); f->put<ushort>(v2[vr]);
340 0 : hasxyz = false;
341 : }
342 : }
343 0 : if(hasnorm && vertmask&0x08)
344 : {
345 0 : f->put<ushort>(verts[0].norm);
346 0 : hasnorm = false;
347 : }
348 0 : if(hasxyz || hasnorm)
349 : {
350 0 : for(int k = 0; k < layerverts; ++k)
351 : {
352 0 : const vertinfo &v = verts[(k+vertorder)%layerverts];
353 0 : if(hasxyz)
354 : {
355 0 : ivec xyz = v.getxyz();
356 0 : f->put<ushort>(xyz[vc]); f->put<ushort>(xyz[vr]);
357 : }
358 0 : if(hasnorm)
359 : {
360 0 : f->put<ushort>(v.norm);
361 : }
362 : }
363 : }
364 : }
365 : }
366 : }
367 : }
368 : }
369 0 : }
370 :
371 : std::array<cube, 8> *loadchildren(stream *f, const ivec &co, int size, bool &failed);
372 :
373 : /**
374 : * @param Loads a cube, possibly containing its child cubes.
375 : *
376 : * Sets the contents of the cube passed depending on the leading flag embedded
377 : * in the string.
378 : *
379 : * If OctaSave_Children, begins recursive loading of cubes into the passed cube's `children` field
380 : *
381 : * If OctaSave_Empty, clears the cube
382 : *
383 : * If OctaSave_Solid, fills the cube completely
384 : *
385 : * If OctaSave_Normal, reads and sets the twelve edges of the cube
386 : *
387 : * If none of these are passed, failed flag is set and nothing is done.
388 : *
389 : * Once OctaSave_Empty/Solid/Normal has been initiated, loads texture, material,
390 : * normal data, and other meta information for the cube c passed
391 : */
392 0 : void loadc(stream *f, cube &c, const ivec &co, int size, bool &failed)
393 : {
394 : static constexpr uint layerdup (1<<7); //if numverts is larger than this, get additional precision
395 :
396 0 : int octsav = f->getchar();
397 0 : switch(octsav&0x7)
398 : {
399 0 : case OctaSave_Children:
400 0 : c.children = loadchildren(f, co, size>>1, failed);
401 0 : return;
402 :
403 0 : case OctaSave_Empty:
404 : {
405 0 : setcubefaces(c, faceempty);
406 0 : break;
407 : }
408 0 : case OctaSave_Solid:
409 : {
410 0 : setcubefaces(c, facesolid);
411 0 : break;
412 : }
413 0 : case OctaSave_Normal:
414 : {
415 0 : f->read(c.edges, 12);
416 0 : break;
417 : }
418 0 : default:
419 : {
420 0 : failed = true;
421 0 : return;
422 : }
423 : }
424 0 : for(uint i = 0; i < 6; ++i)
425 : {
426 0 : c.texture[i] = f->get<ushort>();
427 : }
428 0 : if(octsav&0x40)
429 : {
430 0 : c.material = f->get<ushort>();
431 : }
432 0 : if(octsav&0x80)
433 : {
434 0 : c.merged = f->getchar();
435 : }
436 0 : if(octsav&0x20)
437 : {
438 0 : int surfmask = f->getchar(),
439 0 : totalverts = std::max(f->getchar(), 0);
440 0 : newcubeext(c, totalverts, false);
441 0 : c.ext->surfaces.fill({0,0});
442 0 : std::memset(c.ext->verts(), 0, totalverts*sizeof(vertinfo));
443 0 : int offset = 0;
444 0 : for(int i = 0; i < 6; ++i)
445 : {
446 0 : if(surfmask&(1<<i))
447 : {
448 0 : surfaceinfo &surf = c.ext->surfaces[i];
449 0 : f->read(&surf, sizeof(surf));
450 0 : int vertmask = surf.verts,
451 0 : numverts = surf.totalverts();
452 0 : if(!numverts)
453 : {
454 0 : surf.verts = 0;
455 0 : continue;
456 : }
457 0 : surf.verts = offset;
458 0 : vertinfo *verts = c.ext->verts() + offset;
459 0 : offset += numverts;
460 0 : std::array<ivec, 4> v;
461 0 : ivec n(0,0,0),
462 0 : vo = ivec(co).mask(0xFFF).shl(3);
463 0 : int layerverts = surf.numverts&Face_MaxVerts, dim = DIMENSION(i), vc = C[dim], vr = R[dim], bias = 0;
464 0 : genfaceverts(c, i, v);
465 0 : bool hasxyz = (vertmask&0x04)!=0,
466 0 : hasnorm = (vertmask&0x80)!=0;
467 0 : if(hasxyz)
468 : {
469 0 : ivec e1, e2, e3;
470 0 : n.cross((e1 = v[1]).sub(v[0]), (e2 = v[2]).sub(v[0]));
471 0 : if(!n)
472 : {
473 0 : n.cross(e2, (e3 = v[3]).sub(v[0]));
474 : }
475 0 : bias = -n.dot(ivec(v[0]).mul(size).add(vo));
476 : }
477 : else
478 : {
479 0 : int vis = layerverts < 4 ? (vertmask&0x02 ? 2 : 1) : 3, order = vertmask&0x01 ? 1 : 0, k = 0;
480 0 : verts[k++].setxyz(v[order].mul(size).add(vo));
481 0 : if(vis&1)
482 : {
483 0 : verts[k++].setxyz(v[order+1].mul(size).add(vo));
484 : }
485 0 : verts[k++].setxyz(v[order+2].mul(size).add(vo));
486 0 : if(vis&2)
487 : {
488 0 : verts[k++].setxyz(v[(order+3)&3].mul(size).add(vo));
489 : }
490 : }
491 0 : if(layerverts == 4)
492 : {
493 0 : if(hasxyz && vertmask&0x01)
494 : {
495 0 : ushort c1 = f->get<ushort>(),
496 0 : r1 = f->get<ushort>(),
497 0 : c2 = f->get<ushort>(),
498 0 : r2 = f->get<ushort>();
499 0 : ivec xyz;
500 0 : xyz[vc] = c1;
501 0 : xyz[vr] = r1;
502 0 : xyz[dim] = n[dim] ? -(bias + n[vc]*xyz[vc] + n[vr]*xyz[vr])/n[dim] : vo[dim];
503 0 : verts[0].setxyz(xyz);
504 0 : xyz[vc] = c1;
505 0 : xyz[vr] = r2;
506 0 : xyz[dim] = n[dim] ? -(bias + n[vc]*xyz[vc] + n[vr]*xyz[vr])/n[dim] : vo[dim];
507 0 : verts[1].setxyz(xyz);
508 0 : xyz[vc] = c2;
509 0 : xyz[vr] = r2;
510 0 : xyz[dim] = n[dim] ? -(bias + n[vc]*xyz[vc] + n[vr]*xyz[vr])/n[dim] : vo[dim];
511 0 : verts[2].setxyz(xyz);
512 0 : xyz[vc] = c2;
513 0 : xyz[vr] = r1;
514 0 : xyz[dim] = n[dim] ? -(bias + n[vc]*xyz[vc] + n[vr]*xyz[vr])/n[dim] : vo[dim];
515 0 : verts[3].setxyz(xyz);
516 0 : hasxyz = false;
517 : }
518 : }
519 0 : if(hasnorm && vertmask&0x08)
520 : {
521 0 : ushort norm = f->get<ushort>();
522 0 : for(int k = 0; k < layerverts; ++k)
523 : {
524 0 : verts[k].norm = norm;
525 : }
526 0 : hasnorm = false;
527 : }
528 0 : if(hasxyz || hasnorm)
529 : {
530 0 : for(int k = 0; k < layerverts; ++k)
531 : {
532 0 : vertinfo &v = verts[k];
533 0 : if(hasxyz)
534 : {
535 0 : ivec xyz;
536 0 : xyz[vc] = f->get<ushort>(); xyz[vr] = f->get<ushort>();
537 0 : xyz[dim] = n[dim] ? -(bias + n[vc]*xyz[vc] + n[vr]*xyz[vr])/n[dim] : vo[dim];
538 0 : v.setxyz(xyz);
539 : }
540 0 : if(hasnorm)
541 : {
542 0 : v.norm = f->get<ushort>();
543 : }
544 : }
545 : }
546 0 : if(surf.numverts & layerdup)
547 : {
548 0 : for(int k = 0; k < layerverts; ++k)
549 : {
550 0 : f->get<ushort>();
551 0 : f->get<ushort>();
552 : }
553 : }
554 : }
555 : }
556 : }
557 : }
558 :
559 : /**
560 : * @brief Returns a heap-allocated std::array of cubes read from a file.
561 : *
562 : * These cubes must be freed using freeocta() when destroyed to prevent a leak.
563 : *
564 : * All eight cubes are read, unless the stream does not contain a valid leading
565 : * digit (see OctaSave enum), whereupon all loading thereafter is not executed.
566 : */
567 0 : std::array<cube, 8> *loadchildren(stream *f, const ivec &co, int size, bool &failed)
568 : {
569 0 : std::array<cube, 8> *c = newcubes();
570 0 : for(int i = 0; i < 8; ++i)
571 : {
572 0 : loadc(f, (*c)[i], ivec(i, co, size), size, failed);
573 0 : if(failed)
574 : {
575 0 : break;
576 : }
577 : }
578 0 : return c;
579 : }
580 :
581 : VAR(debugvars, 0, 0, 1);
582 :
583 0 : static void savevslot(stream *f, const VSlot &vs, int prev)
584 : {
585 0 : f->put<int>(vs.changed);
586 0 : f->put<int>(prev);
587 0 : if(vs.changed & (1 << VSlot_ShParam))
588 : {
589 0 : f->put<ushort>(vs.params.size());
590 0 : for(const SlotShaderParam& p : vs.params)
591 : {
592 0 : f->put<ushort>(std::strlen(p.name));
593 0 : f->write(p.name, std::strlen(p.name));
594 0 : for(int k = 0; k < 4; ++k)
595 : {
596 0 : f->put<float>(p.val[k]);
597 : }
598 : }
599 : }
600 0 : if(vs.changed & (1 << VSlot_Scale))
601 : {
602 0 : f->put<float>(vs.scale);
603 : }
604 0 : if(vs.changed & (1 << VSlot_Rotation))
605 : {
606 0 : f->put<int>(vs.rotation);
607 : }
608 0 : if(vs.changed & (1 << VSlot_Angle))
609 : {
610 0 : f->put<float>(vs.angle.x);
611 0 : f->put<float>(vs.angle.y);
612 0 : f->put<float>(vs.angle.z);
613 : }
614 0 : if(vs.changed & (1 << VSlot_Offset))
615 : {
616 0 : for(int k = 0; k < 2; ++k)
617 : {
618 0 : f->put<int>(vs.offset[k]);
619 : }
620 : }
621 0 : if(vs.changed & (1 << VSlot_Scroll))
622 : {
623 0 : for(int k = 0; k < 2; ++k)
624 : {
625 0 : f->put<float>(vs.scroll[k]);
626 : }
627 : }
628 0 : if(vs.changed & (1 << VSlot_Alpha))
629 : {
630 0 : f->put<float>(vs.alphafront);
631 0 : f->put<float>(vs.alphaback);
632 : }
633 0 : if(vs.changed & (1 << VSlot_Color))
634 : {
635 0 : for(int k = 0; k < 3; ++k)
636 : {
637 0 : f->put<float>(vs.colorscale[k]);
638 : }
639 : }
640 0 : if(vs.changed & (1 << VSlot_Refract))
641 : {
642 0 : f->put<float>(vs.refractscale);
643 0 : for(int k = 0; k < 3; ++k)
644 : {
645 0 : f->put<float>(vs.refractcolor[k]);
646 : }
647 : }
648 0 : }
649 :
650 0 : void savevslots(stream *f, int numvslots)
651 : {
652 0 : if(vslots.empty())
653 : {
654 0 : return;
655 : }
656 0 : int *prev = new int[numvslots];
657 0 : std::memset(prev, -1, numvslots*sizeof(int));
658 0 : for(int i = 0; i < numvslots; ++i)
659 : {
660 0 : VSlot *vs = vslots[i];
661 0 : if(vs->changed)
662 : {
663 0 : continue;
664 : }
665 : for(;;)
666 : {
667 0 : VSlot *cur = vs;
668 : do
669 : {
670 0 : vs = vs->next;
671 0 : } while(vs && vs->index >= numvslots);
672 0 : if(!vs)
673 : {
674 0 : break;
675 : }
676 0 : prev[vs->index] = cur->index;
677 0 : }
678 : }
679 0 : int lastroot = 0;
680 0 : for(int i = 0; i < numvslots; ++i)
681 : {
682 0 : VSlot &vs = *vslots[i];
683 0 : if(!vs.changed)
684 : {
685 0 : continue;
686 : }
687 0 : if(lastroot < i)
688 : {
689 0 : f->put<int>(-(i - lastroot));
690 : }
691 0 : savevslot(f, vs, prev[i]);
692 0 : lastroot = i+1;
693 : }
694 0 : if(lastroot < numvslots)
695 : {
696 0 : f->put<int>(-(numvslots - lastroot));
697 : }
698 0 : delete[] prev;
699 : }
700 :
701 0 : void loadvslot(stream *f, VSlot &vs, int changed)
702 : {
703 0 : vs.changed = changed;
704 0 : if(vs.changed & (1 << VSlot_ShParam))
705 : {
706 0 : int numparams = f->get<ushort>();
707 : string name;
708 0 : for(int i = 0; i < numparams; ++i)
709 : {
710 0 : vs.params.emplace_back();
711 0 : SlotShaderParam &p = vs.params.back();
712 0 : int nlen = f->get<ushort>();
713 0 : f->read(name, std::min(nlen, maxstrlen-1));
714 0 : name[std::min(nlen, maxstrlen-1)] = '\0';
715 0 : if(nlen >= maxstrlen)
716 : {
717 0 : f->seek(nlen - (maxstrlen-1), SEEK_CUR);
718 : }
719 0 : p.name = getshaderparamname(name);
720 0 : p.loc = SIZE_MAX;
721 0 : for(int k = 0; k < 4; ++k)
722 : {
723 0 : p.val[k] = f->get<float>();
724 : }
725 : }
726 : }
727 : //vslot properties (set by e.g. v-commands)
728 0 : if(vs.changed & (1 << VSlot_Scale)) //scale <factor>
729 : {
730 0 : vs.scale = f->get<float>();
731 : }
732 0 : if(vs.changed & (1 << VSlot_Rotation)) //rotate <index>
733 : {
734 0 : vs.rotation = std::clamp(f->get<int>(), 0, 7);
735 : }
736 : /*
737 : * angle uses three parameters to prebake sine/cos values for the angle it
738 : * stores despite there being only one parameter (angle) passed
739 : */
740 0 : if(vs.changed & (1 << VSlot_Angle)) //angle <angle>
741 : {
742 0 : for(int k = 0; k < 3; ++k)
743 : {
744 0 : vs.angle[k] = f->get<float>();
745 : }
746 : }
747 0 : if(vs.changed & (1 << VSlot_Offset)) //offset <x> <y>
748 : {
749 0 : for(int k = 0; k < 2; ++k)
750 : {
751 0 : vs.offset[k] = f->get<int>();
752 : }
753 : }
754 0 : if(vs.changed & (1 << VSlot_Scroll)) //scroll <x> <y>
755 : {
756 0 : for(int k = 0; k < 2; ++k)
757 : {
758 0 : vs.scroll[k] = f->get<float>();
759 : }
760 : }
761 0 : if(vs.changed & (1 << VSlot_Alpha)) //alpha <f> <b>
762 : {
763 0 : vs.alphafront = f->get<float>();
764 0 : vs.alphaback = f->get<float>();
765 : }
766 0 : if(vs.changed & (1 << VSlot_Color)) //color <r> <g> <b>
767 : {
768 0 : for(int k = 0; k < 3; ++k)
769 : {
770 0 : vs.colorscale[k] = f->get<float>();
771 : }
772 : }
773 0 : if(vs.changed & (1 << VSlot_Refract)) //refract <r> <g> <b>
774 : {
775 0 : vs.refractscale = f->get<float>();
776 0 : for(int k = 0; k < 3; ++k)
777 : {
778 0 : vs.refractcolor[k] = f->get<float>();
779 : }
780 : }
781 0 : }
782 :
783 0 : void loadvslots(stream *f, int numvslots)
784 : {
785 : //no point if loading 0 vslots
786 0 : if(numvslots == 0)
787 : {
788 0 : return;
789 : }
790 0 : uint *prev = new uint[numvslots];
791 0 : if(!prev)
792 : {
793 0 : return;
794 : }
795 0 : std::memset(prev, -1, numvslots*sizeof(int));
796 0 : while(numvslots > 0)
797 : {
798 0 : int changed = f->get<int>();
799 0 : if(changed < 0)
800 : {
801 0 : for(int i = 0; i < -changed; ++i)
802 : {
803 0 : vslots.push_back(new VSlot(nullptr, vslots.size()));
804 : }
805 0 : numvslots += changed;
806 : }
807 : else
808 : {
809 0 : prev[vslots.size()] = f->get<int>();
810 0 : vslots.push_back(new VSlot(nullptr, vslots.size()));
811 0 : loadvslot(f, *vslots.back(), changed);
812 0 : numvslots--;
813 : }
814 : }
815 0 : for(uint i = 0; i < vslots.size(); i++)
816 : {
817 0 : if(vslots.size() > prev[i])
818 : {
819 0 : vslots.at(prev[i])->next = vslots[i];
820 : }
821 : }
822 0 : delete[] prev;
823 : }
824 :
825 0 : bool cubeworld::save_world(const char *mname, const char *gameident)
826 : {
827 0 : if(!*mname)
828 : {
829 0 : mname = clientmap.c_str();
830 : }
831 0 : setmapfilenames(*mname ? mname : "untitled");
832 0 : if(savebak)
833 : {
834 0 : backup(ogzname, bakname);
835 : }
836 0 : stream *f = opengzfile(ogzname, "wb");
837 0 : if(!f)
838 : {
839 0 : conoutf(Console_Warn, "could not write map to %s", ogzname);
840 0 : return false;
841 : }
842 0 : uint numvslots = vslots.size();
843 0 : if(!multiplayer)
844 : {
845 0 : numvslots = compactvslots();
846 0 : allchanged();
847 : }
848 :
849 0 : savemapprogress = 0;
850 0 : renderprogress(0, "saving map...");
851 :
852 : mapheader hdr;
853 0 : std::memcpy(hdr.magic, "TMAP", 4);
854 0 : hdr.version = currentmapversion;
855 0 : hdr.headersize = sizeof(hdr);
856 0 : hdr.worldsize = mapsize();
857 0 : hdr.numents = 0;
858 0 : const std::vector<extentity *> &ents = entities::getents();
859 0 : for(extentity* const& e : ents)
860 : {
861 0 : if(e->type!=EngineEnt_Empty)
862 : {
863 0 : hdr.numents++;
864 : }
865 : }
866 0 : hdr.numvars = 0;
867 0 : hdr.numvslots = numvslots;
868 0 : for(auto& [k, id] : idents)
869 : {
870 0 : if((id.type == Id_Var || id.type == Id_FloatVar || id.type == Id_StringVar) &&
871 0 : id.flags&Idf_Override &&
872 0 : !(id.flags&Idf_ReadOnly) &&
873 0 : id.flags&Idf_Overridden)
874 : {
875 0 : hdr.numvars++;
876 : }
877 : }
878 0 : f->write(&hdr, sizeof(hdr));
879 :
880 0 : for(auto& [k, id] : idents)
881 : {
882 0 : if((id.type!=Id_Var && id.type!=Id_FloatVar && id.type!=Id_StringVar) ||
883 0 : !(id.flags&Idf_Override) ||
884 0 : id.flags&Idf_ReadOnly ||
885 0 : !(id.flags&Idf_Overridden))
886 : {
887 0 : continue;
888 : }
889 0 : f->putchar(id.type);
890 0 : f->put<ushort>(std::strlen(id.name));
891 0 : f->write(id.name, std::strlen(id.name));
892 0 : switch(id.type)
893 : {
894 0 : case Id_Var:
895 0 : if(debugvars)
896 : {
897 0 : conoutf(Console_Debug, "wrote var %s: %d", id.name, *id.val.storage.i);
898 : }
899 0 : f->put<int>(*id.val.storage.i);
900 0 : break;
901 :
902 0 : case Id_FloatVar:
903 0 : if(debugvars)
904 : {
905 0 : conoutf(Console_Debug, "wrote fvar %s: %f", id.name, *id.val.storage.f);
906 : }
907 0 : f->put<float>(*id.val.storage.f);
908 0 : break;
909 :
910 0 : case Id_StringVar:
911 0 : if(debugvars)
912 : {
913 0 : conoutf(Console_Debug, "wrote svar %s: %s", id.name, *id.val.storage.s);
914 : }
915 0 : f->put<ushort>(std::strlen(*id.val.storage.s));
916 0 : f->write(*id.val.storage.s, std::strlen(*id.val.storage.s));
917 0 : break;
918 : }
919 : }
920 0 : if(debugvars)
921 : {
922 0 : conoutf(Console_Debug, "wrote %d vars", hdr.numvars);
923 : }
924 0 : f->putchar(static_cast<int>(std::strlen(gameident)));
925 0 : f->write(gameident, static_cast<int>(std::strlen(gameident)+1));
926 : //=== padding for compatibility (extent properties no longer a feature)
927 0 : f->put<ushort>(0);
928 0 : f->put<ushort>(0);
929 0 : f->write(0, 0);
930 : //=== end of padding
931 0 : f->put<ushort>(texmru.size());
932 0 : for(const ushort &i : texmru)
933 : {
934 0 : f->put<ushort>(i);
935 : }
936 0 : for(const entity *i : ents)
937 : {
938 0 : if(i->type!=EngineEnt_Empty)
939 : {
940 0 : entity tmp = *i;
941 0 : f->write(&tmp, sizeof(entity));
942 : }
943 : }
944 0 : savevslots(f, numvslots);
945 0 : renderprogress(0, "saving octree...");
946 0 : savec(*worldroot, ivec(0, 0, 0), rootworld.mapsize()>>1, f);
947 0 : delete f;
948 0 : conoutf("wrote map file %s", ogzname);
949 0 : return true;
950 : }
951 :
952 0 : uint cubeworld::getmapcrc() const
953 : {
954 0 : return mapcrc;
955 : }
956 :
957 0 : void cubeworld::clearmapcrc()
958 : {
959 0 : mapcrc = 0;
960 0 : }
961 :
962 0 : bool cubeworld::load_world(const char *mname, const char *gameident, const char *gameinfo, const char *cname)
963 : {
964 0 : const int loadingstart = SDL_GetTicks();
965 0 : setmapfilenames(mname, cname);
966 0 : stream *f = opengzfile(ogzname, "rb");
967 0 : if(!f)
968 : {
969 0 : conoutf(Console_Error, "could not read map %s", ogzname);
970 0 : return false;
971 : }
972 : mapheader hdr;
973 : octaheader ohdr;
974 0 : std::memset(&ohdr, 0, sizeof(ohdr));
975 0 : if(!loadmapheader(f, ogzname, hdr, ohdr))
976 : {
977 0 : delete f;
978 0 : return false;
979 : }
980 0 : resetmap();
981 0 : const Texture *mapshot = textureload(picname, 3, true, false);
982 0 : renderbackground("loading...", mapshot, mname, gameinfo);
983 0 : setvar("mapversion", hdr.version, true, false);
984 0 : renderprogress(0, "clearing world...");
985 0 : freeocta(worldroot);
986 0 : worldroot = nullptr;
987 0 : int loadedworldscale = 0;
988 0 : while(1<<loadedworldscale < hdr.worldsize)
989 : {
990 0 : loadedworldscale++;
991 : }
992 0 : worldscale = loadedworldscale;
993 0 : renderprogress(0, "loading vars...");
994 0 : for(int i = 0; i < hdr.numvars; ++i)
995 : {
996 0 : const int type = f->getchar(),
997 0 : ilen = f->get<ushort>();
998 : string name;
999 0 : f->read(name, std::min(ilen, maxstrlen-1));
1000 0 : name[std::min(ilen, maxstrlen-1)] = '\0';
1001 0 : if(ilen >= maxstrlen)
1002 : {
1003 0 : f->seek(ilen - (maxstrlen-1), SEEK_CUR);
1004 : }
1005 0 : const ident *id = getident(name);
1006 : tagval val;
1007 : string str;
1008 0 : switch(type)
1009 : {
1010 0 : case Id_Var:
1011 : {
1012 0 : val.setint(f->get<int>());
1013 0 : break;
1014 : }
1015 0 : case Id_FloatVar:
1016 : {
1017 0 : val.setfloat(f->get<float>());
1018 0 : break;
1019 : }
1020 0 : case Id_StringVar:
1021 : {
1022 0 : const int slen = f->get<ushort>();
1023 0 : f->read(str, std::min(slen, maxstrlen-1));
1024 0 : str[std::min(slen, maxstrlen-1)] = '\0';
1025 0 : if(slen >= maxstrlen)
1026 : {
1027 0 : f->seek(slen - (maxstrlen-1), SEEK_CUR);
1028 : }
1029 0 : val.setstr(str);
1030 0 : break;
1031 : }
1032 0 : default:
1033 : {
1034 0 : continue;
1035 : }
1036 0 : }
1037 0 : if(id && id->flags&Idf_Override)
1038 : {
1039 0 : switch(id->type)
1040 : {
1041 0 : case Id_Var:
1042 : {
1043 0 : const int i = val.getint();
1044 0 : if(id->val.i.min <= id->val.i.max && i >= id->val.i.min && i <= id->val.i.max)
1045 : {
1046 0 : setvar(name, i);
1047 0 : if(debugvars)
1048 : {
1049 0 : conoutf(Console_Debug, "read var %s: %d", name, i);
1050 : }
1051 : }
1052 0 : break;
1053 : }
1054 0 : case Id_FloatVar:
1055 : {
1056 0 : const float f = val.getfloat();
1057 0 : if(id->val.f.min <= id->val.f.max && f >= id->val.f.min && f <= id->val.f.max)
1058 : {
1059 0 : setfvar(name, f);
1060 0 : if(debugvars)
1061 : {
1062 0 : conoutf(Console_Debug, "read fvar %s: %f", name, f);
1063 : }
1064 : }
1065 0 : break;
1066 : }
1067 0 : case Id_StringVar:
1068 : {
1069 0 : setsvar(name, val.getstr());
1070 0 : if(debugvars)
1071 : {
1072 0 : conoutf(Console_Debug, "read svar %s: %s", name, val.getstr());
1073 : }
1074 0 : break;
1075 : }
1076 : }
1077 : }
1078 : }
1079 0 : if(debugvars)
1080 : {
1081 0 : conoutf(Console_Debug, "read %d vars", hdr.numvars);
1082 : }
1083 : string gametype;
1084 0 : bool samegame = true;
1085 0 : const int len = f->getchar();
1086 0 : if(len >= 0)
1087 : {
1088 0 : f->read(gametype, len+1);
1089 : }
1090 0 : gametype[std::max(len, 0)] = '\0';
1091 0 : if(std::strcmp(gametype, gameident)!=0)
1092 : {
1093 0 : samegame = false;
1094 0 : conoutf(Console_Warn, "WARNING: loading map from %s game, ignoring entities except for lights/mapmodels", gametype);
1095 : }
1096 0 : int eif = f->get<ushort>(),
1097 0 : extrasize = f->get<ushort>();
1098 0 : std::vector<char> extras;
1099 0 : extras.reserve(extrasize);
1100 0 : f->read(&(*extras.begin()), extrasize);
1101 0 : texmru.clear();
1102 0 : const ushort nummru = f->get<ushort>();
1103 0 : for(int i = 0; i < nummru; ++i)
1104 : {
1105 0 : texmru.push_back(f->get<ushort>());
1106 : }
1107 0 : renderprogress(0, "loading entities...");
1108 0 : std::vector<extentity *> &ents = entities::getents();
1109 0 : for(int i = 0; i < (std::min(hdr.numents, maxents)); ++i)
1110 : {
1111 0 : extentity &e = *entities::newentity();
1112 0 : ents.push_back(&e);
1113 0 : f->read(&e, sizeof(entity));
1114 : //delete entities from other games
1115 0 : if(!samegame)
1116 : {
1117 0 : if(eif > 0)
1118 : {
1119 0 : f->seek(eif, SEEK_CUR);
1120 : }
1121 0 : if(e.type>=EngineEnt_GameSpecific)
1122 : {
1123 0 : entities::deleteentity(ents.back());
1124 0 : ents.pop_back();
1125 0 : continue;
1126 : }
1127 : }
1128 0 : if(!insideworld(e.o))
1129 : {
1130 0 : if(e.type != EngineEnt_Light && e.type != EngineEnt_Spotlight)
1131 : {
1132 0 : conoutf(Console_Warn, "warning: ent outside of world: index %d (%f, %f, %f)", i, e.o.x, e.o.y, e.o.z);
1133 : }
1134 : }
1135 : }
1136 0 : if(hdr.numents > maxents)
1137 : {
1138 0 : conoutf(Console_Warn, "warning: map has %d entities", hdr.numents);
1139 0 : f->seek((hdr.numents-maxents)*(samegame ? sizeof(entity) : eif), SEEK_CUR);
1140 : }
1141 0 : renderprogress(0, "loading slots...");
1142 0 : loadvslots(f, hdr.numvslots);
1143 0 : renderprogress(0, "loading octree...");
1144 0 : bool failed = false;
1145 0 : worldroot = loadchildren(f, ivec(0, 0, 0), hdr.worldsize>>1, failed);
1146 0 : if(failed)
1147 : {
1148 0 : conoutf(Console_Error, "garbage in map");
1149 : }
1150 0 : renderprogress(0, "validating...");
1151 0 : validatec(worldroot, hdr.worldsize>>1);
1152 :
1153 0 : mapcrc = f->getcrc();
1154 0 : delete f;
1155 0 : conoutf("read map %s (%.1f seconds)", ogzname, (SDL_GetTicks()-loadingstart)/1000.0f);
1156 0 : clearmainmenu();
1157 :
1158 0 : identflags |= Idf_Overridden;
1159 0 : execfile("config/default_map_settings.cfg", false);
1160 0 : execfile(cfgname, false);
1161 0 : identflags &= ~Idf_Overridden;
1162 0 : renderbackground("loading...", mapshot, mname, gameinfo);
1163 :
1164 0 : if(maptitle[0] && std::strcmp(maptitle, "Untitled Map by Unknown"))
1165 : {
1166 0 : conoutf(Console_Echo, "%s", maptitle);
1167 : }
1168 0 : return true;
1169 : }
1170 :
1171 1 : void initworldiocmds()
1172 : {
1173 1 : addcommand("mapcfgname", reinterpret_cast<identfun>(mapcfgname), "", Id_Command);
1174 2 : addcommand("mapversion", reinterpret_cast<identfun>(+[] () {intret(currentmapversion);}), "", Id_Command);
1175 1 : }
|