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