LCOV - code coverage report
Current view: top level - engine/world - worldio.cpp (source / functions) Hit Total Coverage
Test: Libprimis Test Coverage Lines: 23 639 3.6 %
Date: 2024-11-22 05:07:59 Functions: 4 20 20.0 %

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

Generated by: LCOV version 1.14