diff --git a/source/src/cube.h b/source/src/cube.h index 623081f1..0410e07c 100644 --- a/source/src/cube.h +++ b/source/src/cube.h @@ -34,6 +34,8 @@ extern sqr *world, *wmip[]; // map data, the mips are sequential 2D extern header hdr; // current map header extern int sfactor, ssize; // ssize = 2^sfactor extern int cubicsize, mipsize; // cubicsize = ssize^2 +extern uchar *headerextra; // special feature map data +extern int headerextrasize; extern physent *camera1; // camera representing perspective of player, usually player1 extern playerent *player1; // special client ent that receives input and acts as camera extern vector players; // all the other clients (in multiplayer) diff --git a/source/src/tools.cpp b/source/src/tools.cpp index a65c4628..4ac00657 100644 --- a/source/src/tools.cpp +++ b/source/src/tools.cpp @@ -24,6 +24,41 @@ const char *numtime() return numt; } +static const uchar transformenttab[] = { + /* mapformat 1..5 6..7 */ + /* 0 */ /* NOTUSED */ NOTUSED, /* NOTUSED */ NOTUSED, + /* 1 */ /* LIGHT */ LIGHT, /* LIGHT */ LIGHT, + /* 2 */ /* PLAYERSTART */ PLAYERSTART, /* PLAYERSTART */ PLAYERSTART, + /* 3 */ /* I_SHELLS */ I_AMMO, /* I_CLIPS */ I_CLIPS, + /* 4 */ /* I_BULLETS */ I_AMMO, /* I_AMMO */ I_AMMO, + /* 5 */ /* I_ROCKETS */ I_AMMO, /* I_GRENADE */ I_GRENADE, + /* 6 */ /* I_ROUNDS */ I_AMMO, /* I_HEALTH */ I_HEALTH, + /* 7 */ /* I_HEALTH */ I_HEALTH, /* I_ARMOUR */ I_ARMOUR, + /* 8 */ /* I_BOOST */ I_HEALTH, /* I_AKIMBO */ I_AKIMBO, + /* 9 */ /* I_GREENARMOUR */ I_HELMET, /* MAPMODEL */ MAPMODEL, + /* 10 */ /* I_YELLOWARMOUR */ I_ARMOUR, /* CARROT */ CARROT, + /* 11 */ /* I_QUAD */ I_AKIMBO, /* LADDER */ LADDER, + /* 12 */ /* TELEPORT */ NOTUSED, /* CTF_FLAG */ CTF_FLAG, + /* 13 */ /* TELEDEST */ NOTUSED, /* SOUND */ SOUND, + /* 14 */ /* MAPMODEL */ MAPMODEL, /* CLIP */ CLIP, + /* 15 */ /* MONSTER */ NOTUSED, /* PLCLIP */ PLCLIP, + /* 16 */ /* CARROT */ NOTUSED, 16, + /* 17 */ /* JUMPPAD */ NOTUSED, 17 }; + +void transformoldentities(int mapversion, uchar &enttype) +{ + const uchar *usetab = transformenttab + (mapversion > 5 ? 1 : 0); + if(mapversion < 8 && enttype < 18) enttype = usetab[enttype * 2]; +} + +int fixmapheadersize(int version, int headersize) // we can't trust hdr.headersize for file versions < 10 (thx flow) +{ + if(version < 4) return sizeof(header) - sizeof(int) * 16; + else if(version == 7 || version == 8) return sizeof(header) + sizeof(char) * 128; // mediareq + else if(version < 10 || headersize < int(sizeof(header))) return sizeof(header); + return headersize; +} + int mapdims[8]; // min/max X/Y and delta X/Y and min/max Z extern char *maplayout, *testlayout; @@ -33,6 +68,7 @@ extern int checkarea(int, char *); mapstats *loadmapstats(const char *filename, bool getlayout) { + const int sizeof_header = sizeof(header), sizeof_baseheader = sizeof_header - sizeof(int) * 16; static mapstats s; static uchar *enttypes = NULL; static short *entposs = NULL; @@ -44,11 +80,14 @@ mapstats *loadmapstats(const char *filename, bool getlayout) stream *f = opengzfile(filename, "rb"); if(!f) return NULL; - memset(&s.hdr, 0, sizeof(header)); - if(f->read(&s.hdr, sizeof(header)-sizeof(int)*16)!=sizeof(header)-sizeof(int)*16 || (strncmp(s.hdr.head, "CUBE", 4) && strncmp(s.hdr.head, "ACMP",4))) { delete f; return NULL; } + memset(&s.hdr, 0, sizeof_header); + if(f->read(&s.hdr, sizeof_baseheader) != sizeof_baseheader || (strncmp(s.hdr.head, "CUBE", 4) && strncmp(s.hdr.head, "ACMP",4))) { delete f; return NULL; } lilswap(&s.hdr.version, 4); - if(s.hdr.version>MAPVERSION || s.hdr.numents > MAXENTITIES || (s.hdr.version>=4 && f->read(&s.hdr.waterlevel, sizeof(int)*16)!=sizeof(int)*16)) { delete f; return NULL; } - if((s.hdr.version==7 || s.hdr.version==8) && !f->seek(sizeof(char)*128, SEEK_CUR)) { delete f; return NULL; } + s.hdr.headersize = fixmapheadersize(s.hdr.version, s.hdr.headersize); + int restofhead = min(s.hdr.headersize, sizeof_header) - sizeof_baseheader; + if(s.hdr.version > MAXMAPVERSION || s.hdr.numents > MAXENTITIES || + f->read(&s.hdr.waterlevel, restofhead) != restofhead || + !f->seek(clamp(s.hdr.headersize - sizeof_header, 0, MAXHEADEREXTRA), SEEK_CUR)) { delete f; return NULL; } if(s.hdr.version>=4) { lilswap(&s.hdr.waterlevel, 1); @@ -62,7 +101,7 @@ mapstats *loadmapstats(const char *filename, bool getlayout) { f->read(&e, sizeof(persistent_entity)); lilswap((short *)&e, 4); - TRANSFORMOLDENTITIES(s.hdr) + transformoldentities(s.hdr.version, e.type); if(e.type == PLAYERSTART && (e.attr2 == 0 || e.attr2 == 1 || e.attr2 == 100)) s.spawns[e.attr2 == 100 ? 2 : e.attr2]++; if(e.type == CTF_FLAG && (e.attr2 == 0 || e.attr2 == 1)) { s.flags[e.attr2]++; s.flagents[e.attr2] = i; } s.entcnt[e.type]++; diff --git a/source/src/tools.h b/source/src/tools.h index 4204a308..3074aed3 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -907,6 +907,8 @@ struct stream extern const char *timestring(bool local = false, const char *fmt = NULL); extern const char *asctime(); extern const char *numtime(); +extern void transformoldentities(int mapversion, uchar &enttype); +extern int fixmapheadersize(int version, int headersize); extern char *path(char *s); extern char *path(const char *s, bool copy); extern char *unixpath(char *s); diff --git a/source/src/world.cpp b/source/src/world.cpp index 47d5f705..42335820 100644 --- a/source/src/world.cpp +++ b/source/src/world.cpp @@ -8,6 +8,8 @@ sqr *world = NULL; int sfactor, ssize, cubicsize, mipsize; header hdr; +uchar *headerextra = NULL; +int headerextrasize; // main geometric mipmapping routine, recursively rebuild mipmaps within block b. // tries to produce cube out of 4 lower level mips as well as possible, @@ -481,10 +483,6 @@ bool empty_world(int factor, bool force) // main empty world creation routine checkselections(); // assert no selection became invalid - strncpy(hdr.head, "ACMP", 4); - hdr.version = MAPVERSION; - hdr.headersize = sizeof(header); - hdr.sfactor = sfactor; if(copy) { ow->x = ow->y = shrink ? 0 : ssize/4; @@ -501,17 +499,21 @@ bool empty_world(int factor, bool force) // main empty world creation routine entinmap(player1); } else - { - copystring(hdr.maptitle, "Untitled Map by Unknown", 128); + { // all-new map + memset(&hdr, 0, sizeof(header)); + formatstring(hdr.maptitle)("Untitled Map by %s", player1->name); hdr.waterlevel = -100000; - hdr.ambient = 0; setwatercolor(); - loopi(sizeof(hdr.reserved)/sizeof(hdr.reserved[0])) hdr.reserved[i] = 0; loopk(3) loopi(256) hdr.texlists[k][i] = i; ents.shrink(0); block b = { 8, 8, ssize-16, ssize - 16}; edittypexy(SPACE, b); + DELETEA(headerextra); } + strncpy(hdr.head, "ACMP", 4); + hdr.version = MAPVERSION; + hdr.headersize = sizeof(header); + hdr.sfactor = sfactor; calclight(); resetmap(!copy); diff --git a/source/src/world.h b/source/src/world.h index 41e819ac..361cc73e 100644 --- a/source/src/world.h +++ b/source/src/world.h @@ -32,7 +32,8 @@ enum // hardcoded texture numbers DEFAULT_CEIL }; -#define MAPVERSION 9 // bump if map format changes, see worldio.cpp +#define MAXMAPVERSION 10 // defines the highest readable format +#define MAPVERSION 9 // default map format version to be written (bump if map format changes, see worldio.cpp) struct header // map file format header { @@ -66,54 +67,6 @@ struct mapstats bool hasflags; }; -#define TRANSFORMOLDENTITIES(headr) \ - if(e.type==LIGHT) \ - { \ - if(!e.attr2) e.attr2 = 255; /* needed for MAPVERSION<=2 */ \ - if(e.attr1>32) e.attr1 = 32; /* 12_03 and below */ \ - } \ - if(headr.version<6 && strncmp(headr.head,"CUBE",4)==0) /* only render lights, pl starts and map models on old maps // <6 was =6 && headr.version<8) { \ - if( e.type >= I_HELMET && e.type < (MAXENTTYPES - 1) ) { e.type += 1; } \ - } - #define SWS(w,x,y,s) (&(w)[((y)<<(s))+(x)]) #define SW(w,x,y) SWS(w,x,y,sfactor) #define S(x,y) SW(world,x,y) // convenient lookup of a lowest mip cube @@ -121,6 +74,7 @@ struct mapstats #define DEFAULT_FACTOR 8 #define LARGEST_FACTOR 11 // 10 is already insane #define MAXENTITIES 65535 +#define MAXHEADEREXTRA (1<<20) #define SOLID(x) ((x)->type==SOLID) #define MINBORD 2 // 2 cubes from the edge of the world are always solid #define OUTBORD(x,y) ((x)=ssize-MINBORD || (y)>=ssize-MINBORD) diff --git a/source/src/worldio.cpp b/source/src/worldio.cpp index d812d6d2..cc20f1b3 100644 --- a/source/src/worldio.cpp +++ b/source/src/worldio.cpp @@ -218,6 +218,7 @@ void save_world(char *mname) if(!f) { conoutf("could not write map to %s", cgzname); return; } strncpy(hdr.head, "ACMP", 4); // ensure map now declares itself as an AssaultCube map, even if imported as CUBE hdr.version = MAPVERSION; + hdr.headersize = sizeof(header); hdr.numents = 0; loopv(ents) if(ents[i].type!=NOTUSED) hdr.numents++; if(hdr.numents > MAXENTITIES) @@ -226,11 +227,15 @@ void save_world(char *mname) hdr.numents = MAXENTITIES; } header tmp = hdr; + int writeextra = 0; + if(headerextra) tmp.headersize += writeextra = clamp(headerextrasize, 0, MAXHEADEREXTRA); + if(writeextra) tmp.version = 10; // 9 and 10 are the same, but in 10 the headersize is reliable - if we don't need it, stick to 9 lilswap(&tmp.version, 4); lilswap(&tmp.waterlevel, 1); tmp.maprevision += advancemaprevision; lilswap(&tmp.maprevision, 2); f->write(&tmp, sizeof(header)); + if(writeextra) f->write(headerextra, writeextra); int ne = hdr.numents; loopv(ents) { @@ -315,6 +320,7 @@ extern float Mh; bool load_world(char *mname) // still supports all map formats that have existed since the earliest cube betas! { + const int sizeof_header = sizeof(header), sizeof_baseheader = sizeof_header - sizeof(int) * 16; stopwatch watch; watch.start(); @@ -335,14 +341,28 @@ bool load_world(char *mname) // still supports all map formats that have stream *f = opengzfile(cgzname, "rb"); if(!f) { conoutf("\f3could not read map %s", cgzname); return false; } header tmp; - memset(&tmp, 0, sizeof(header)); - if(f->read(&tmp, sizeof(header)-sizeof(int)*16)!=sizeof(header)-sizeof(int)*16) { conoutf("\f3while reading map: header malformatted"); delete f; return false; } - lilswap(&tmp.version, 4); - if(strncmp(tmp.head, "CUBE", 4)!=0 && strncmp(tmp.head, "ACMP",4)!=0) { conoutf("\f3while reading map: header malformatted"); delete f; return false; } - if(tmp.version>MAPVERSION) { conoutf("\f3this map requires a newer version of AssaultCube"); delete f; return false; } + memset(&tmp, 0, sizeof_header); + if(f->read(&tmp, sizeof_baseheader) != sizeof_baseheader || + (strncmp(tmp.head, "CUBE", 4)!=0 && strncmp(tmp.head, "ACMP",4)!=0)) { conoutf("\f3while reading map: header malformatted (1)"); delete f; return false; } + lilswap(&tmp.version, 4); // version, headersize, sfactor, numents + if(tmp.version > MAXMAPVERSION) { conoutf("\f3this map requires a newer version of AssaultCube"); delete f; return false; } if(tmp.sfactorLARGEST_FACTOR || tmp.numents > MAXENTITIES) { conoutf("\f3illegal map size"); delete f; return false; } - if(tmp.version>=4 && f->read(&tmp.waterlevel, sizeof(int)*16)!=sizeof(int)*16) { conoutf("\f3while reading map: header malformatted"); delete f; return false; } - if((tmp.version==7 || tmp.version==8) && !f->seek(sizeof(char)*128, SEEK_CUR)) { conoutf("\f3while reading map: header malformatted"); delete f; return false; } + tmp.headersize = fixmapheadersize(tmp.version, tmp.headersize); + DELETEA(headerextra); + int restofhead = min(tmp.headersize, sizeof_header) - sizeof_baseheader; + if(f->read(&tmp.waterlevel, restofhead) != restofhead) { conoutf("\f3while reading map: header malformatted (2)"); delete f; return false; } + if(tmp.headersize > sizeof_header) + { + headerextrasize = tmp.headersize - sizeof_header; + if(tmp.version < 9) headerextrasize = 0; // throw away mediareq... + else if(headerextrasize > MAXHEADEREXTRA) headerextrasize = MAXHEADEREXTRA; + if(headerextrasize) + { // map file actually has extra header data that we want too preserve + headerextra = new uchar[headerextrasize]; + if(f->read(headerextra, headerextrasize) != headerextrasize) { conoutf("\f3while reading map: header malformatted (3)"); delete f; return false; } + } + f->seek(tmp.headersize - sizeof_header - headerextrasize, SEEK_CUR); // (equivalent to SEEK_SET to tmp.headersize) + } hdr = tmp; loadingscreen("%s", hdr.maptitle); resetmap(); @@ -367,7 +387,12 @@ bool load_world(char *mname) // still supports all map formats that have f->read(&e, sizeof(persistent_entity)); lilswap((short *)&e, 4); e.spawned = false; - TRANSFORMOLDENTITIES(hdr) + if(e.type == LIGHT) + { + if(!e.attr2) e.attr2 = 255; // needed for MAPVERSION<=2 + if(e.attr1>32) e.attr1 = 32; // 12_03 and below + } + transformoldentities(hdr.version, e.type); if(e.type == PLAYERSTART && (e.attr2 == 0 || e.attr2 == 1 || e.attr2 == 100)) { if(e.attr2 == 100)