LCOV - code coverage report
Current view: top level - engine/world - worldio.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 3.6 % 639 23
Test Date: 2026-07-09 06:56:18 Functions: 20.0 % 20 4

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

Generated by: LCOV version 2.0-1