// worldio.cpp: loading & saving of maps and savegames #include "engine.h" void validmapname(char *dst, const char *src, const char *prefix = NULL, const char *alt = "untitled", size_t maxlen = 100) { if(prefix) while(*prefix) *dst++ = *prefix++; const char *start = dst; if(src) loopi(maxlen) { char c = *src++; if(iscubealnum(c) || c == '_' || c == '-' || c == '/' || c == '\\') *dst++ = c; else break; } if(dst > start) *dst = '\0'; else if(dst != alt) copystring(dst, alt, maxlen); } void fixmapname(char *name) { validmapname(name, name, NULL, ""); } void getmapfilenames(const char *fname, const char *cname, char *pakname, char *mapname, char *cfgname) { if(!cname) cname = fname; string name; validmapname(name, cname); char *slash = strpbrk(name, "/\\"); if(slash) { copystring(pakname, name, slash-name+1); copystring(cfgname, slash+1, MAXSTRLEN); } else { copystring(pakname, "maps", MAXSTRLEN); copystring(cfgname, name, MAXSTRLEN); } validmapname(mapname, fname, strpbrk(fname, "/\\") ? NULL : "maps/"); } static void fixent(entity &e, int version) { if(version <= 10 && e.type >= 7) e.type++; if(version <= 12 && e.type >= 8) e.type++; if(version <= 14 && e.type >= ET_MAPMODEL && e.type <= 16) { if(e.type == 16) e.type = ET_MAPMODEL; else e.type++; } if(version <= 20 && e.type >= ET_ENVMAP) e.type++; if(version <= 21 && e.type >= ET_PARTICLES) e.type++; if(version <= 22 && e.type >= ET_SOUND) e.type++; if(version <= 23 && e.type >= ET_SPOTLIGHT) e.type++; if(version <= 30 && (e.type == ET_MAPMODEL || e.type == ET_PLAYERSTART)) e.attr1 = (int(e.attr1)+180)%360; if(version <= 31 && e.type == ET_MAPMODEL) { int yaw = (int(e.attr1)%360 + 360)%360 + 7; e.attr1 = yaw - yaw%15; } } bool loadents(const char *fname, vector &ents, uint *crc) { string pakname, mapname, mcfgname, ogzname; getmapfilenames(fname, NULL, pakname, mapname, mcfgname); formatstring(ogzname, "packages/%s.ogz", mapname); path(ogzname); stream *f = opengzfile(ogzname, "rb"); if(!f) return false; octaheader hdr; if(f->read(&hdr, 7*sizeof(int)) != 7*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } lilswap(&hdr.version, 6); if(memcmp(hdr.magic, "OCTA", 4) || hdr.worldsize <= 0|| hdr.numents < 0) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } if(hdr.version>MAPVERSION) { conoutf(CON_ERROR, "map %s requires a newer version of Cube 2: Sauerbraten", ogzname); delete f; return false; } compatheader chdr; if(hdr.version <= 28) { if(f->read(&chdr.lightprecision, sizeof(chdr) - 7*sizeof(int)) != sizeof(chdr) - 7*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } } else { int extra = 0; if(hdr.version <= 29) extra++; if(f->read(&hdr.blendmap, sizeof(hdr) - (7+extra)*sizeof(int)) != sizeof(hdr) - (7+extra)*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } } if(hdr.version <= 28) { lilswap(&chdr.lightprecision, 3); hdr.blendmap = chdr.blendmap; hdr.numvars = 0; hdr.numvslots = 0; } else { lilswap(&hdr.blendmap, 2); if(hdr.version <= 29) hdr.numvslots = 0; else lilswap(&hdr.numvslots, 1); } loopi(hdr.numvars) { int type = f->getchar(), ilen = f->getlil(); f->seek(ilen, SEEK_CUR); switch(type) { case ID_VAR: f->getlil(); break; case ID_FVAR: f->getlil(); break; case ID_SVAR: { int slen = f->getlil(); f->seek(slen, SEEK_CUR); break; } } } string gametype; copystring(gametype, "fps"); bool samegame = true; int eif = 0; if(hdr.version>=16) { int len = f->getchar(); f->read(gametype, len+1); } if(strcmp(gametype, game::gameident())) { samegame = false; conoutf(CON_WARN, "WARNING: loading map from %s game, ignoring entities except for lights/mapmodels", gametype); } if(hdr.version>=16) { eif = f->getlil(); int extrasize = f->getlil(); f->seek(extrasize, SEEK_CUR); } if(hdr.version<14) { f->seek(256, SEEK_CUR); } else { ushort nummru = f->getlil(); f->seek(nummru*sizeof(ushort), SEEK_CUR); } loopi(min(hdr.numents, MAXENTS)) { entity &e = ents.add(); f->read(&e, sizeof(entity)); lilswap(&e.o.x, 3); lilswap(&e.attr1, 5); fixent(e, hdr.version); if(eif > 0) f->seek(eif, SEEK_CUR); if(samegame) { entities::readent(e, NULL, hdr.version); } else if(e.type>=ET_GAMESPECIFIC || hdr.version<=14) { ents.pop(); continue; } } if(crc) { f->seek(0, SEEK_END); *crc = f->getcrc(); } delete f; return true; } #ifndef STANDALONE string ogzname, bakname, cfgname, picname; VARP(savebak, 0, 2, 2); void setmapfilenames(const char *fname, const char *cname = NULL) { string pakname, mapname, mcfgname; getmapfilenames(fname, cname, pakname, mapname, mcfgname); formatstring(ogzname, "packages/%s.ogz", mapname); if(savebak==1) formatstring(bakname, "packages/%s.BAK", mapname); else formatstring(bakname, "packages/%s_%d.BAK", mapname, totalmillis); formatstring(cfgname, "packages/%s/%s.cfg", pakname, mcfgname); formatstring(picname, "packages/%s.png", mapname); path(ogzname); path(bakname); path(cfgname); path(picname); } void mapcfgname() { const char *mname = game::getclientmap(); string pakname, mapname, mcfgname; getmapfilenames(mname, NULL, pakname, mapname, mcfgname); defformatstring(cfgname, "packages/%s/%s.cfg", pakname, mcfgname); path(cfgname); result(cfgname); } COMMAND(mapcfgname, ""); void backup(char *name, char *backupname) { string backupfile; copystring(backupfile, findfile(backupname, "wb")); remove(backupfile); rename(findfile(name, "wb"), backupfile); } enum { OCTSAV_CHILDREN = 0, OCTSAV_EMPTY, OCTSAV_SOLID, OCTSAV_NORMAL, OCTSAV_LODCUBE }; static int savemapprogress = 0; void savec(cube *c, const ivec &o, int size, stream *f, bool nolms) { if((savemapprogress++&0xFFF)==0) renderprogress(float(savemapprogress)/allocnodes, "saving octree..."); loopi(8) { ivec co(i, o, size); if(c[i].children) { f->putchar(OCTSAV_CHILDREN); savec(c[i].children, co, size>>1, f, nolms); } else { int oflags = 0, surfmask = 0, totalverts = 0; if(c[i].material!=MAT_AIR) oflags |= 0x40; if(isempty(c[i])) f->putchar(oflags | OCTSAV_EMPTY); else { if(!nolms) { if(c[i].merged) oflags |= 0x80; if(c[i].ext) loopj(6) { const surfaceinfo &surf = c[i].ext->surfaces[j]; if(!surf.used()) continue; oflags |= 0x20; surfmask |= 1<putchar(oflags | OCTSAV_SOLID); else { f->putchar(oflags | OCTSAV_NORMAL); f->write(c[i].edges, 12); } } loopj(6) f->putlil(c[i].texture[j]); if(oflags&0x40) f->putlil(c[i].material); if(oflags&0x80) f->putchar(c[i].merged); if(oflags&0x20) { f->putchar(surfmask); f->putchar(totalverts); loopj(6) if(surfmask&(1<surfaces[j]; vertinfo *verts = c[i].ext->verts() + surf.verts; int layerverts = surf.numverts&MAXFACEVERTS, numverts = surf.totalverts(), vertmask = 0, vertorder = 0, uvorder = 0, dim = dimension(j), vc = C[dim], vr = R[dim]; if(numverts) { if(c[i].merged&(1<write(&surf, sizeof(surfaceinfo)); bool hasxyz = (vertmask&0x04)!=0, hasuv = (vertmask&0x40)!=0, hasnorm = (vertmask&0x80)!=0; if(layerverts == 4) { if(hasxyz && vertmask&0x01) { ivec v0 = verts[vertorder].getxyz(), v2 = verts[(vertorder+2)&3].getxyz(); f->putlil(v0[vc]); f->putlil(v0[vr]); f->putlil(v2[vc]); f->putlil(v2[vr]); hasxyz = false; } if(hasuv && vertmask&0x02) { const vertinfo &v0 = verts[uvorder], &v2 = verts[(uvorder+2)&3]; f->putlil(v0.u); f->putlil(v0.v); f->putlil(v2.u); f->putlil(v2.v); if(surf.numverts&LAYER_DUP) { const vertinfo &b0 = verts[4+uvorder], &b2 = verts[4+((uvorder+2)&3)]; f->putlil(b0.u); f->putlil(b0.v); f->putlil(b2.u); f->putlil(b2.v); } hasuv = false; } } if(hasnorm && vertmask&0x08) { f->putlil(verts[0].norm); hasnorm = false; } if(hasxyz || hasuv || hasnorm) loopk(layerverts) { const vertinfo &v = verts[(k+vertorder)%layerverts]; if(hasxyz) { ivec xyz = v.getxyz(); f->putlil(xyz[vc]); f->putlil(xyz[vr]); } if(hasuv) { f->putlil(v.u); f->putlil(v.v); } if(hasnorm) f->putlil(v.norm); } if(surf.numverts&LAYER_DUP) loopk(layerverts) { const vertinfo &v = verts[layerverts + (k+vertorder)%layerverts]; if(hasuv) { f->putlil(v.u); f->putlil(v.v); } } } } } } } struct surfacecompat { uchar texcoords[8]; uchar w, h; ushort x, y; uchar lmid, layer; }; struct normalscompat { bvec normals[4]; }; struct mergecompat { ushort u1, u2, v1, v2; }; cube *loadchildren(stream *f, const ivec &co, int size, bool &failed); void convertoldsurfaces(cube &c, const ivec &co, int size, surfacecompat *srcsurfs, int hassurfs, normalscompat *normals, int hasnorms, mergecompat *merges, int hasmerges) { surfaceinfo dstsurfs[6]; vertinfo verts[6*2*MAXFACEVERTS]; int totalverts = 0, numsurfs = 6; memset(dstsurfs, 0, sizeof(dstsurfs)); loopi(6) if((hassurfs|hasnorms|hasmerges)&(1<layer&2) { blend = &srcsurfs[numsurfs++]; dst.lmid[0] = src->lmid; dst.lmid[1] = blend->lmid; dst.numverts |= LAYER_BLEND; if(blend->lmid >= LMID_RESERVED && (src->x != blend->x || src->y != blend->y || src->w != blend->w || src->h != blend->h || memcmp(src->texcoords, blend->texcoords, sizeof(src->texcoords)))) dst.numverts |= LAYER_DUP; } else if(src->layer == 1) { dst.lmid[1] = src->lmid; dst.numverts |= LAYER_BOTTOM; } else { dst.lmid[0] = src->lmid; dst.numverts |= LAYER_TOP; } } else dst.numverts |= LAYER_TOP; bool uselms = hassurfs&(1<= LMID_RESERVED || dst.lmid[1] >= LMID_RESERVED || dst.numverts&~LAYER_TOP), usemerges = hasmerges&(1< 0 && (pos[k] == pos[0] || pos[k] == pos[k-1])) continue; vertinfo &dv = curverts[numverts++]; dv.setxyz(pos[k]); if(uselms) { float u = src->x + (src->texcoords[k*2] / 255.0f) * (src->w - 1), v = src->y + (src->texcoords[k*2+1] / 255.0f) * (src->h - 1); dv.u = ushort(floor(clamp((u) * float(USHRT_MAX+1)/LM_PACKW + 0.5f, 0.0f, float(USHRT_MAX)))); dv.v = ushort(floor(clamp((v) * float(USHRT_MAX+1)/LM_PACKH + 0.5f, 0.0f, float(USHRT_MAX)))); } else dv.u = dv.v = 0; dv.norm = usenorms && normals[i].normals[k] != bvec(128, 128, 128) ? encodenormal(normals[i].normals[k].tonormal().normalize()) : 0; } dst.verts = totalverts; dst.numverts |= numverts; totalverts += numverts; if(dst.numverts&LAYER_DUP) loopk(4) { if(k > 0 && (pos[k] == pos[0] || pos[k] == pos[k-1])) continue; vertinfo &bv = verts[totalverts++]; bv.setxyz(pos[k]); bv.u = ushort(floor(clamp((blend->x + (blend->texcoords[k*2] / 255.0f) * (blend->w - 1)) * float(USHRT_MAX+1)/LM_PACKW, 0.0f, float(USHRT_MAX)))); bv.v = ushort(floor(clamp((blend->y + (blend->texcoords[k*2+1] / 255.0f) * (blend->h - 1)) * float(USHRT_MAX+1)/LM_PACKH, 0.0f, float(USHRT_MAX)))); bv.norm = usenorms && normals[i].normals[k] != bvec(128, 128, 128) ? encodenormal(normals[i].normals[k].tonormal().normalize()) : 0; } } } setsurfaces(c, dstsurfs, verts, totalverts); } static inline int convertoldmaterial(int mat) { return ((mat&7)<>3)&3)<>5)&7)<getchar(); switch(octsav&0x7) { case OCTSAV_CHILDREN: c.children = loadchildren(f, co, size>>1, failed); return; case OCTSAV_LODCUBE: haschildren = true; break; case OCTSAV_EMPTY: emptyfaces(c); break; case OCTSAV_SOLID: solidfaces(c); break; case OCTSAV_NORMAL: f->read(c.edges, 12); break; default: failed = true; return; } loopi(6) c.texture[i] = mapversion<14 ? f->getchar() : f->getlil(); if(mapversion < 7) f->seek(3, SEEK_CUR); else if(mapversion <= 31) { uchar mask = f->getchar(); if(mask & 0x80) { int mat = f->getchar(); if(mapversion < 27) { static const ushort matconv[] = { MAT_AIR, MAT_WATER, MAT_CLIP, MAT_GLASS|MAT_CLIP, MAT_NOCLIP, MAT_LAVA|MAT_DEATH, MAT_GAMECLIP, MAT_DEATH }; c.material = size_t(mat) < sizeof(matconv)/sizeof(matconv[0]) ? matconv[mat] : MAT_AIR; } else c.material = convertoldmaterial(mat); } surfacecompat surfaces[12]; normalscompat normals[6]; mergecompat merges[6]; int hassurfs = 0, hasnorms = 0, hasmerges = 0; if(mask & 0x3F) { int numsurfs = 6; loopi(numsurfs) { if(i >= 6 || mask & (1 << i)) { f->read(&surfaces[i], sizeof(surfacecompat)); lilswap(&surfaces[i].x, 2); if(mapversion < 10) ++surfaces[i].lmid; if(mapversion < 18) { if(surfaces[i].lmid >= LMID_AMBIENT1) ++surfaces[i].lmid; if(surfaces[i].lmid >= LMID_BRIGHT1) ++surfaces[i].lmid; } if(mapversion < 19) { if(surfaces[i].lmid >= LMID_DARK) surfaces[i].lmid += 2; } if(i < 6) { if(mask & 0x40) { hasnorms |= 1<read(&normals[i], sizeof(normalscompat)); } if(surfaces[i].layer != 0 || surfaces[i].lmid != LMID_AMBIENT) hassurfs |= 1<>4) | ((hassurfs&0x03)<<4); } } if(mapversion >= 20) { if(octsav&0x80) { int merged = f->getchar(); c.merged = merged&0x3F; if(merged&0x80) { int mask = f->getchar(); if(mask) { hasmerges = mask&0x3F; loopi(6) if(mask&(1<read(m, sizeof(mergecompat)); lilswap(&m->u1, 4); if(mapversion <= 25) { int uorigin = m->u1 & 0xE000, vorigin = m->v1 & 0xE000; m->u1 = (m->u1 - uorigin) << 2; m->u2 = (m->u2 - uorigin) << 2; m->v1 = (m->v1 - vorigin) << 2; m->v2 = (m->v2 - vorigin) << 2; } } } } } } if(hassurfs || hasnorms || hasmerges) convertoldsurfaces(c, co, size, surfaces, hassurfs, normals, hasnorms, merges, hasmerges); } else { if(octsav&0x40) { if(mapversion <= 32) { int mat = f->getchar(); c.material = convertoldmaterial(mat); } else c.material = f->getlil(); } if(octsav&0x80) c.merged = f->getchar(); if(octsav&0x20) { int surfmask, totalverts; surfmask = f->getchar(); totalverts = max(f->getchar(), 0); newcubeext(c, totalverts, false); memset(c.ext->surfaces, 0, sizeof(c.ext->surfaces)); memset(c.ext->verts(), 0, totalverts*sizeof(vertinfo)); int offset = 0; loopi(6) if(surfmask&(1<surfaces[i]; f->read(&surf, sizeof(surfaceinfo)); int vertmask = surf.verts, numverts = surf.totalverts(); if(!numverts) { surf.verts = 0; continue; } surf.verts = offset; vertinfo *verts = c.ext->verts() + offset; offset += numverts; ivec v[4], n, vo = ivec(co).mask(0xFFF).shl(3); int layerverts = surf.numverts&MAXFACEVERTS, dim = dimension(i), vc = C[dim], vr = R[dim], bias = 0; genfaceverts(c, i, v); bool hasxyz = (vertmask&0x04)!=0, hasuv = (vertmask&0x40)!=0, hasnorm = (vertmask&0x80)!=0; if(hasxyz) { ivec e1, e2, e3; n.cross((e1 = v[1]).sub(v[0]), (e2 = v[2]).sub(v[0])); if(n.iszero()) n.cross(e2, (e3 = v[3]).sub(v[0])); bias = -n.dot(ivec(v[0]).mul(size).add(vo)); } else { int vis = layerverts < 4 ? (vertmask&0x02 ? 2 : 1) : 3, order = vertmask&0x01 ? 1 : 0, k = 0; verts[k++].setxyz(v[order].mul(size).add(vo)); if(vis&1) verts[k++].setxyz(v[order+1].mul(size).add(vo)); verts[k++].setxyz(v[order+2].mul(size).add(vo)); if(vis&2) verts[k++].setxyz(v[(order+3)&3].mul(size).add(vo)); } if(layerverts == 4) { if(hasxyz && vertmask&0x01) { ushort c1 = f->getlil(), r1 = f->getlil(), c2 = f->getlil(), r2 = f->getlil(); ivec xyz; xyz[vc] = c1; xyz[vr] = r1; xyz[dim] = n[dim] ? -(bias + n[vc]*xyz[vc] + n[vr]*xyz[vr])/n[dim] : vo[dim]; verts[0].setxyz(xyz); xyz[vc] = c1; xyz[vr] = r2; xyz[dim] = n[dim] ? -(bias + n[vc]*xyz[vc] + n[vr]*xyz[vr])/n[dim] : vo[dim]; verts[1].setxyz(xyz); xyz[vc] = c2; xyz[vr] = r2; xyz[dim] = n[dim] ? -(bias + n[vc]*xyz[vc] + n[vr]*xyz[vr])/n[dim] : vo[dim]; verts[2].setxyz(xyz); xyz[vc] = c2; xyz[vr] = r1; xyz[dim] = n[dim] ? -(bias + n[vc]*xyz[vc] + n[vr]*xyz[vr])/n[dim] : vo[dim]; verts[3].setxyz(xyz); hasxyz = false; } if(hasuv && vertmask&0x02) { int uvorder = (vertmask&0x30)>>4; vertinfo &v0 = verts[uvorder], &v1 = verts[(uvorder+1)&3], &v2 = verts[(uvorder+2)&3], &v3 = verts[(uvorder+3)&3]; v0.u = f->getlil(); v0.v = f->getlil(); v2.u = f->getlil(); v2.v = f->getlil(); v1.u = v0.u; v1.v = v2.v; v3.u = v2.u; v3.v = v0.v; if(surf.numverts&LAYER_DUP) { vertinfo &b0 = verts[4+uvorder], &b1 = verts[4+((uvorder+1)&3)], &b2 = verts[4+((uvorder+2)&3)], &b3 = verts[4+((uvorder+3)&3)]; b0.u = f->getlil(); b0.v = f->getlil(); b2.u = f->getlil(); b2.v = f->getlil(); b1.u = b0.u; b1.v = b2.v; b3.u = b2.u; b3.v = b0.v; } hasuv = false; } } if(hasnorm && vertmask&0x08) { ushort norm = f->getlil(); loopk(layerverts) verts[k].norm = norm; hasnorm = false; } if(hasxyz || hasuv || hasnorm) loopk(layerverts) { vertinfo &v = verts[k]; if(hasxyz) { ivec xyz; xyz[vc] = f->getlil(); xyz[vr] = f->getlil(); xyz[dim] = n[dim] ? -(bias + n[vc]*xyz[vc] + n[vr]*xyz[vr])/n[dim] : vo[dim]; v.setxyz(xyz); } if(hasuv) { v.u = f->getlil(); v.v = f->getlil(); } if(hasnorm) v.norm = f->getlil(); } if(surf.numverts&LAYER_DUP) loopk(layerverts) { vertinfo &v = verts[k+layerverts], &t = verts[k]; v.setxyz(t.x, t.y, t.z); if(hasuv) { v.u = f->getlil(); v.v = f->getlil(); } v.norm = t.norm; } } } } c.children = (haschildren ? loadchildren(f, co, size>>1, failed) : NULL); } cube *loadchildren(stream *f, const ivec &co, int size, bool &failed) { cube *c = newcubes(); loopi(8) { loadc(f, c[i], ivec(i, co, size), size, failed); if(failed) break; } return c; } VAR(dbgvars, 0, 0, 1); void savevslot(stream *f, VSlot &vs, int prev) { f->putlil(vs.changed); f->putlil(prev); if(vs.changed & (1<putlil(vs.params.length()); loopv(vs.params) { SlotShaderParam &p = vs.params[i]; f->putlil(strlen(p.name)); f->write(p.name, strlen(p.name)); loopk(4) f->putlil(p.val[k]); } } if(vs.changed & (1<putlil(vs.scale); if(vs.changed & (1<putlil(vs.rotation); if(vs.changed & (1<putlil(vs.offset.x); f->putlil(vs.offset.y); } if(vs.changed & (1<putlil(vs.scroll.x); f->putlil(vs.scroll.y); } if(vs.changed & (1<putlil(vs.layer); if(vs.changed & (1<putlil(vs.alphafront); f->putlil(vs.alphaback); } if(vs.changed & (1<putlil(vs.colorscale[k]); } } void savevslots(stream *f, int numvslots) { if(vslots.empty()) return; int *prev = new int[numvslots]; memset(prev, -1, numvslots*sizeof(int)); loopi(numvslots) { VSlot *vs = vslots[i]; if(vs->changed) continue; for(;;) { VSlot *cur = vs; do vs = vs->next; while(vs && vs->index >= numvslots); if(!vs) break; prev[vs->index] = cur->index; } } int lastroot = 0; loopi(numvslots) { VSlot &vs = *vslots[i]; if(!vs.changed) continue; if(lastroot < i) f->putlil(-(i - lastroot)); savevslot(f, vs, prev[i]); lastroot = i+1; } if(lastroot < numvslots) f->putlil(-(numvslots - lastroot)); delete[] prev; } void loadvslot(stream *f, VSlot &vs, int changed) { vs.changed = changed; if(vs.changed & (1<getlil(); string name; loopi(numparams) { SlotShaderParam &p = vs.params.add(); int nlen = f->getlil(); f->read(name, min(nlen, MAXSTRLEN-1)); name[min(nlen, MAXSTRLEN-1)] = '\0'; if(nlen >= MAXSTRLEN) f->seek(nlen - (MAXSTRLEN-1), SEEK_CUR); p.name = getshaderparamname(name); p.loc = -1; loopk(4) p.val[k] = f->getlil(); } } if(vs.changed & (1<getlil(); if(vs.changed & (1<getlil(), 0, 7); if(vs.changed & (1<getlil(); vs.offset.y = f->getlil(); } if(vs.changed & (1<getlil(); vs.scroll.y = f->getlil(); } if(vs.changed & (1<getlil(); if(vs.changed & (1<getlil(); vs.alphaback = f->getlil(); } if(vs.changed & (1<getlil(); } } void loadvslots(stream *f, int numvslots) { int *prev = new (false) int[numvslots]; if(!prev) return; memset(prev, -1, numvslots*sizeof(int)); while(numvslots > 0) { int changed = f->getlil(); if(changed < 0) { loopi(-changed) vslots.add(new VSlot(NULL, vslots.length())); numvslots += changed; } else { prev[vslots.length()] = f->getlil(); loadvslot(f, *vslots.add(new VSlot(NULL, vslots.length())), changed); numvslots--; } } loopv(vslots) if(vslots.inrange(prev[i])) vslots[prev[i]]->next = vslots[i]; delete[] prev; } bool save_world(const char *mname, bool nolms) { if(!*mname) mname = game::getclientmap(); setmapfilenames(mname); if(savebak) backup(ogzname, bakname); stream *f = opengzfile(ogzname, "wb"); if(!f) { conoutf(CON_WARN, "could not write map to %s", ogzname); return false; } int numvslots = vslots.length(); if(!nolms && !multiplayer(false)) { numvslots = compactvslots(); allchanged(); } savemapprogress = 0; renderprogress(0, "saving map..."); octaheader hdr; memcpy(hdr.magic, "OCTA", 4); hdr.version = MAPVERSION; hdr.headersize = sizeof(hdr); hdr.worldsize = worldsize; hdr.numents = 0; const vector &ents = entities::getents(); loopv(ents) if(ents[i]->type!=ET_EMPTY || nolms) hdr.numents++; hdr.numpvs = nolms ? 0 : getnumviewcells(); hdr.lightmaps = nolms ? 0 : lightmaps.length(); hdr.blendmap = shouldsaveblendmap(); hdr.numvars = 0; hdr.numvslots = numvslots; enumerate(idents, ident, id, { if((id.type == ID_VAR || id.type == ID_FVAR || id.type == ID_SVAR) && id.flags&IDF_OVERRIDE && !(id.flags&IDF_READONLY) && id.flags&IDF_OVERRIDDEN) hdr.numvars++; }); lilswap(&hdr.version, 9); f->write(&hdr, sizeof(hdr)); enumerate(idents, ident, id, { if((id.type!=ID_VAR && id.type!=ID_FVAR && id.type!=ID_SVAR) || !(id.flags&IDF_OVERRIDE) || id.flags&IDF_READONLY || !(id.flags&IDF_OVERRIDDEN)) continue; f->putchar(id.type); f->putlil(strlen(id.name)); f->write(id.name, strlen(id.name)); switch(id.type) { case ID_VAR: if(dbgvars) conoutf(CON_DEBUG, "wrote var %s: %d", id.name, *id.storage.i); f->putlil(*id.storage.i); break; case ID_FVAR: if(dbgvars) conoutf(CON_DEBUG, "wrote fvar %s: %f", id.name, *id.storage.f); f->putlil(*id.storage.f); break; case ID_SVAR: if(dbgvars) conoutf(CON_DEBUG, "wrote svar %s: %s", id.name, *id.storage.s); f->putlil(strlen(*id.storage.s)); f->write(*id.storage.s, strlen(*id.storage.s)); break; } }); if(dbgvars) conoutf(CON_DEBUG, "wrote %d vars", hdr.numvars); f->putchar((int)strlen(game::gameident())); f->write(game::gameident(), (int)strlen(game::gameident())+1); f->putlil(entities::extraentinfosize()); vector extras; game::writegamedata(extras); f->putlil(extras.length()); f->write(extras.getbuf(), extras.length()); f->putlil(texmru.length()); loopv(texmru) f->putlil(texmru[i]); char *ebuf = new char[entities::extraentinfosize()]; loopv(ents) { if(ents[i]->type!=ET_EMPTY || nolms) { entity tmp = *ents[i]; lilswap(&tmp.o.x, 3); lilswap(&tmp.attr1, 5); f->write(&tmp, sizeof(entity)); entities::writeent(*ents[i], ebuf); if(entities::extraentinfosize()) f->write(ebuf, entities::extraentinfosize()); } } delete[] ebuf; savevslots(f, numvslots); renderprogress(0, "saving octree..."); savec(worldroot, ivec(0, 0, 0), worldsize>>1, f, nolms); if(!nolms) { if(lightmaps.length()) renderprogress(0, "saving lightmaps..."); loopv(lightmaps) { LightMap &lm = lightmaps[i]; f->putchar(lm.type | (lm.unlitx>=0 ? 0x80 : 0)); if(lm.unlitx>=0) { f->putlil(ushort(lm.unlitx)); f->putlil(ushort(lm.unlity)); } f->write(lm.data, lm.bpp*LM_PACKW*LM_PACKH); renderprogress(float(i+1)/lightmaps.length(), "saving lightmaps..."); } if(getnumviewcells()>0) { renderprogress(0, "saving pvs..."); savepvs(f); } } if(shouldsaveblendmap()) { renderprogress(0, "saving blendmap..."); saveblendmap(f); } delete f; conoutf("wrote map file %s", ogzname); return true; } static uint mapcrc = 0; uint getmapcrc() { return mapcrc; } void clearmapcrc() { mapcrc = 0; } bool load_world(const char *mname, const char *cname) // still supports all map formats that have existed since the earliest cube betas! { int loadingstart = SDL_GetTicks(); setmapfilenames(mname, cname); stream *f = opengzfile(ogzname, "rb"); if(!f) { conoutf(CON_ERROR, "could not read map %s", ogzname); return false; } octaheader hdr; if(f->read(&hdr, 7*sizeof(int)) != 7*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } lilswap(&hdr.version, 6); if(memcmp(hdr.magic, "OCTA", 4) || hdr.worldsize <= 0|| hdr.numents < 0) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } if(hdr.version>MAPVERSION) { conoutf(CON_ERROR, "map %s requires a newer version of Cube 2: Sauerbraten", ogzname); delete f; return false; } compatheader chdr; if(hdr.version <= 28) { if(f->read(&chdr.lightprecision, sizeof(chdr) - 7*sizeof(int)) != sizeof(chdr) - 7*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } } else { int extra = 0; if(hdr.version <= 29) extra++; if(f->read(&hdr.blendmap, sizeof(hdr) - (7+extra)*sizeof(int)) != sizeof(hdr) - (7+extra)*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } } resetmap(); Texture *mapshot = textureload(picname, 3, true, false); renderbackground("loading...", mapshot, mname, game::getmapinfo()); game::loadingmap(cname ? cname : mname); setvar("mapversion", hdr.version, true, false); if(hdr.version <= 28) { lilswap(&chdr.lightprecision, 3); if(chdr.lightprecision) setvar("lightprecision", chdr.lightprecision); if(chdr.lighterror) setvar("lighterror", chdr.lighterror); if(chdr.bumperror) setvar("bumperror", chdr.bumperror); setvar("lightlod", chdr.lightlod); if(chdr.ambient) setvar("ambient", chdr.ambient); setvar("skylight", (int(chdr.skylight[0])<<16) | (int(chdr.skylight[1])<<8) | int(chdr.skylight[2])); setvar("watercolour", (int(chdr.watercolour[0])<<16) | (int(chdr.watercolour[1])<<8) | int(chdr.watercolour[2]), true); setvar("waterfallcolour", (int(chdr.waterfallcolour[0])<<16) | (int(chdr.waterfallcolour[1])<<8) | int(chdr.waterfallcolour[2])); setvar("lavacolour", (int(chdr.lavacolour[0])<<16) | (int(chdr.lavacolour[1])<<8) | int(chdr.lavacolour[2])); setvar("fullbright", 0, true); if(chdr.lerpsubdivsize || chdr.lerpangle) setvar("lerpangle", chdr.lerpangle); if(chdr.lerpsubdivsize) { setvar("lerpsubdiv", chdr.lerpsubdiv); setvar("lerpsubdivsize", chdr.lerpsubdivsize); } setsvar("maptitle", chdr.maptitle); hdr.blendmap = chdr.blendmap; hdr.numvars = 0; hdr.numvslots = 0; } else { lilswap(&hdr.blendmap, 2); if(hdr.version <= 29) hdr.numvslots = 0; else lilswap(&hdr.numvslots, 1); } renderprogress(0, "clearing world..."); freeocta(worldroot); worldroot = NULL; int worldscale = 0; while(1<getchar(), ilen = f->getlil(); string name; f->read(name, min(ilen, MAXSTRLEN-1)); name[min(ilen, MAXSTRLEN-1)] = '\0'; if(ilen >= MAXSTRLEN) f->seek(ilen - (MAXSTRLEN-1), SEEK_CUR); ident *id = getident(name); bool exists = id && id->type == type && id->flags&IDF_OVERRIDE; switch(type) { case ID_VAR: { int val = f->getlil(); if(exists && id->minval <= id->maxval) setvar(name, val); if(dbgvars) conoutf(CON_DEBUG, "read var %s: %d", name, val); break; } case ID_FVAR: { float val = f->getlil(); if(exists && id->minvalf <= id->maxvalf) setfvar(name, val); if(dbgvars) conoutf(CON_DEBUG, "read fvar %s: %f", name, val); break; } case ID_SVAR: { int slen = f->getlil(); string val; f->read(val, min(slen, MAXSTRLEN-1)); val[min(slen, MAXSTRLEN-1)] = '\0'; if(slen >= MAXSTRLEN) f->seek(slen - (MAXSTRLEN-1), SEEK_CUR); if(exists) setsvar(name, val); if(dbgvars) conoutf(CON_DEBUG, "read svar %s: %s", name, val); break; } } } if(dbgvars) conoutf(CON_DEBUG, "read %d vars", hdr.numvars); string gametype; copystring(gametype, "fps"); bool samegame = true; int eif = 0; if(hdr.version>=16) { int len = f->getchar(); f->read(gametype, len+1); } if(strcmp(gametype, game::gameident())!=0) { samegame = false; conoutf(CON_WARN, "WARNING: loading map from %s game, ignoring entities except for lights/mapmodels", gametype); } if(hdr.version>=16) { eif = f->getlil(); int extrasize = f->getlil(); vector extras; f->read(extras.pad(extrasize), extrasize); if(samegame) game::readgamedata(extras); } texmru.shrink(0); if(hdr.version<14) { uchar oldtl[256]; f->read(oldtl, sizeof(oldtl)); loopi(256) texmru.add(oldtl[i]); } else { ushort nummru = f->getlil(); loopi(nummru) texmru.add(f->getlil()); } renderprogress(0, "loading entities..."); vector &ents = entities::getents(); int einfosize = entities::extraentinfosize(); char *ebuf = einfosize > 0 ? new char[einfosize] : NULL; loopi(min(hdr.numents, MAXENTS)) { extentity &e = *entities::newentity(); ents.add(&e); f->read(&e, sizeof(entity)); lilswap(&e.o.x, 3); lilswap(&e.attr1, 5); fixent(e, hdr.version); if(samegame) { if(einfosize > 0) f->read(ebuf, einfosize); entities::readent(e, ebuf, mapversion); } else { if(eif > 0) f->seek(eif, SEEK_CUR); if(e.type>=ET_GAMESPECIFIC || hdr.version<=14) { entities::deleteentity(ents.pop()); continue; } } if(!insideworld(e.o)) { if(e.type != ET_LIGHT && e.type != ET_SPOTLIGHT) { conoutf(CON_WARN, "warning: ent outside of world: enttype[%s] index %d (%f, %f, %f)", entities::entname(e.type), i, e.o.x, e.o.y, e.o.z); } } if(hdr.version <= 14 && e.type == ET_MAPMODEL) { e.o.z += e.attr3; if(e.attr4) conoutf(CON_WARN, "warning: mapmodel ent (index %d) uses texture slot %d", i, e.attr4); e.attr3 = e.attr4 = 0; } } if(ebuf) delete[] ebuf; if(hdr.numents > MAXENTS) { conoutf(CON_WARN, "warning: map has %d entities", hdr.numents); f->seek((hdr.numents-MAXENTS)*(samegame ? sizeof(entity) + einfosize : eif), SEEK_CUR); } renderprogress(0, "loading slots..."); loadvslots(f, hdr.numvslots); renderprogress(0, "loading octree..."); bool failed = false; worldroot = loadchildren(f, ivec(0, 0, 0), hdr.worldsize>>1, failed); if(failed) conoutf(CON_ERROR, "garbage in map"); renderprogress(0, "validating..."); validatec(worldroot, hdr.worldsize>>1); if(!failed) { if(hdr.version >= 7) loopi(hdr.lightmaps) { renderprogress(i/(float)hdr.lightmaps, "loading lightmaps..."); LightMap &lm = lightmaps.add(); if(hdr.version >= 17) { int type = f->getchar(); lm.type = type&0x7F; if(hdr.version >= 20 && type&0x80) { lm.unlitx = f->getlil(); lm.unlity = f->getlil(); } } if(lm.type&LM_ALPHA && (lm.type&LM_TYPE)!=LM_BUMPMAP1) lm.bpp = 4; lm.data = new uchar[lm.bpp*LM_PACKW*LM_PACKH]; f->read(lm.data, lm.bpp * LM_PACKW * LM_PACKH); lm.finalize(); } if(hdr.version >= 25 && hdr.numpvs > 0) loadpvs(f, hdr.numpvs); if(hdr.version >= 28 && hdr.blendmap) loadblendmap(f, hdr.blendmap); } mapcrc = f->getcrc(); delete f; conoutf("read map %s (%.1f seconds)", ogzname, (SDL_GetTicks()-loadingstart)/1000.0f); clearmainmenu(); identflags |= IDF_OVERRIDDEN; execfile("data/default_map_settings.cfg", false); execfile(cfgname, false); identflags &= ~IDF_OVERRIDDEN; extern void fixlightmapnormals(); if(hdr.version <= 25) fixlightmapnormals(); extern void fixrotatedlightmaps(); if(hdr.version <= 31) fixrotatedlightmaps(); preloadusedmapmodels(true); game::preload(); flushpreloadedmodels(); preloadmapsounds(); entitiesinoctanodes(); attachentities(); initlights(); allchanged(true); renderbackground("loading...", mapshot, mname, game::getmapinfo()); if(maptitle[0] && strcmp(maptitle, "Untitled Map by Unknown")) conoutf(CON_ECHO, "%s", maptitle); startmap(cname ? cname : mname); return true; } void savecurrentmap() { save_world(game::getclientmap()); } void savemap(char *mname) { save_world(mname); } COMMAND(savemap, "s"); COMMAND(savecurrentmap, ""); void writeobj(char *name) { defformatstring(fname, "%s.obj", name); stream *f = openfile(path(fname), "w"); if(!f) return; f->printf("# obj file of Cube 2 level\n\n"); defformatstring(mtlname, "%s.mtl", name); path(mtlname); f->printf("mtllib %s\n\n", mtlname); vector verts; vector texcoords; hashtable shareverts(1<<16); hashtable sharetc(1<<16); hashtable > mtls(1<<8); vector usedmtl; vec bbmin(1e16f, 1e16f, 1e16f), bbmax(-1e16f, -1e16f, -1e16f); loopv(valist) { vtxarray &va = *valist[i]; ushort *edata = NULL; vertex *vdata = NULL; if(!readva(&va, edata, vdata)) continue; ushort *idx = edata; loopj(va.texs) { elementset &es = va.eslist[j]; if(usedmtl.find(es.texture) < 0) usedmtl.add(es.texture); vector &keys = mtls[es.texture]; loopk(es.length[1]) { int n = idx[k] - va.voffset; const vertex &v = vdata[n]; const vec &pos = v.pos; const vec2 &tc = v.tc; ivec2 &key = keys.add(); key.x = shareverts.access(pos, verts.length()); if(key.x == verts.length()) { verts.add(pos); loopl(3) { bbmin[l] = min(bbmin[l], pos[l]); bbmax[l] = max(bbmax[l], pos[l]); } } key.y = sharetc.access(tc, texcoords.length()); if(key.y == texcoords.length()) texcoords.add(tc); } idx += es.length[1]; } delete[] edata; delete[] vdata; } vec center(-(bbmax.x + bbmin.x)/2, -(bbmax.y + bbmin.y)/2, -bbmin.z); loopv(verts) { vec v = verts[i]; v.add(center); if(v.y != floor(v.y)) f->printf("v %.3f ", -v.y); else f->printf("v %d ", int(-v.y)); if(v.z != floor(v.z)) f->printf("%.3f ", v.z); else f->printf("%d ", int(v.z)); if(v.x != floor(v.x)) f->printf("%.3f\n", v.x); else f->printf("%d\n", int(v.x)); } f->printf("\n"); loopv(texcoords) { const vec2 &tc = texcoords[i]; f->printf("vt %.6f %.6f\n", tc.x, 1-tc.y); } f->printf("\n"); usedmtl.sort(); loopv(usedmtl) { vector &keys = mtls[usedmtl[i]]; f->printf("g slot%d\n", usedmtl[i]); f->printf("usemtl slot%d\n\n", usedmtl[i]); for(int i = 0; i < keys.length(); i += 3) { f->printf("f"); loopk(3) f->printf(" %d/%d", keys[i+2-k].x+1, keys[i+2-k].y+1); f->printf("\n"); } f->printf("\n"); } delete f; f = openfile(mtlname, "w"); if(!f) return; f->printf("# mtl file of Cube 2 level\n\n"); loopv(usedmtl) { VSlot &vslot = lookupvslot(usedmtl[i], false); f->printf("newmtl slot%d\n", usedmtl[i]); f->printf("map_Kd %s\n", vslot.slot->sts.empty() ? notexture->name : path(makerelpath("packages", vslot.slot->sts[0].name))); f->printf("\n"); } delete f; } COMMAND(writeobj, "s"); #endif