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