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