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