diff --git a/source/src/clientgame.cpp b/source/src/clientgame.cpp index dffb6c76..357c22b0 100644 --- a/source/src/clientgame.cpp +++ b/source/src/clientgame.cpp @@ -1117,15 +1117,16 @@ COMMAND(showmapstats, ""); VARP(showmodedescriptions, 0, 1, 1); extern bool canceldownloads; -void startmap(const char *name, bool reset) // called just after a map load +void startmap(const char *name, bool reset, bool norespawn) // called just after a map load { canceldownloads = false; copystring(clientmap, name); + + if(norespawn) return; + sendmapidenttoserver = true; - // Added by Rick if(m_botmode) BotManager.BeginMap(name); else kickallbots(); - // End add by Rick clearbounceents(); preparectf(!m_flags); suicided = -1; diff --git a/source/src/console.cpp b/source/src/console.cpp index 483e11a0..21f4e851 100644 --- a/source/src/console.cpp +++ b/source/src/console.cpp @@ -347,6 +347,7 @@ void mapmsg(char *s) filterrichtext(text, s); filterservdesc(text, text); copystring(hdr.maptitle, text, 128); + unsavededits = 1; } void getmapmsg(void) diff --git a/source/src/cube.h b/source/src/cube.h index 623081f1..dcecccce 100644 --- a/source/src/cube.h +++ b/source/src/cube.h @@ -39,6 +39,7 @@ extern playerent *player1; // special client ent that receives inpu extern vector players; // all the other clients (in multiplayer) extern vector bounceents; extern bool editmode; +extern int unsavededits; extern vector ents; // map entities extern vector eh_ents; // edithide entities extern vec worldpos, camup, camright, camdir; // current target of the crosshair in the world diff --git a/source/src/editing.cpp b/source/src/editing.cpp index 6ca9c102..8d03662e 100644 --- a/source/src/editing.cpp +++ b/source/src/editing.cpp @@ -24,6 +24,7 @@ int lasttype = 0, lasttex = 0; sqr rtex; VAR(editing, 1, 0, 0); +VAR(unsavededits, 1, 0, 0); bool editmetakeydown = false; COMMANDF(editmeta, "d", (bool on) { editmetakeydown = on; } ); @@ -302,12 +303,12 @@ void pruneundos(int maxremain) // bound memory loopvrev(undos) { u += undos[i]->xs * undos[i]->ys; - if(u > maxremain) delete[] (uchar *)undos.remove(i); + if(u > maxremain) freeblockp(undos.remove(i)); } loopvrev(redos) { r += redos[i]->xs * redos[i]->ys; - if(r > maxremain) delete[] (uchar *)redos.remove(i); + if(r > maxremain) freeblockp(redos.remove(i)); } } @@ -323,6 +324,7 @@ void makeundo(block &sel) storeposition(sel.p); undos.add(blockcopy(sel)); pruneundos(undomegs<<20); + unsavededits = 1; } void restoreposition(short p[]) @@ -350,6 +352,7 @@ void editundo() if(editmetakeydown) restoreposition(*p); blockpaste(*p); freeblock(p); + unsavededits = 1; } void editredo() @@ -361,6 +364,7 @@ void editredo() if(editmetakeydown) restoreposition(*p); blockpaste(*p); freeblock(p); + unsavededits = 1; } extern int worldiodebug; @@ -418,8 +422,9 @@ int rlencodeundo(int type, vector &t, block *s) return t.length(); } -void backupeditundo(vector &buf, int undolimit, int redolimit) +int backupeditundo(vector &buf, int undolimit, int redolimit) { + int numundo = 0; vector tmp; loopvrev(undos) { @@ -427,6 +432,7 @@ void backupeditundo(vector &buf, int undolimit, int redolimit) undolimit -= rlencodeundo(10, tmp, undos[i]); if(undolimit < 0) break; buf.put(tmp.getbuf(), tmp.length()); + numundo++; #ifdef _DEBUG if(worldiodebug) clientlogf(" written undo x %d, y %d, xs %d, ys %d, compressed length %d", undos[i]->x, undos[i]->y, undos[i]->xs, undos[i]->ys, tmp.length()); #endif @@ -442,6 +448,7 @@ void backupeditundo(vector &buf, int undolimit, int redolimit) #endif } putuint(buf, 0); + return numundo; } vector copybuffers; @@ -604,6 +611,7 @@ void edittex(int type, int dir) edittexxy(type, t, sels[i]); addmsg(SV_EDITT, "ri6", sels[i].x, sels[i].y, sels[i].xs, sels[i].ys, type, t); } + unsavededits = 1; } void settex(int texture, int type) @@ -627,6 +635,7 @@ void settex(int texture, int type) edittexxy(type, t, sels[i]); addmsg(SV_EDITT, "ri6", sels[i].x, sels[i].y, sels[i].xs, sels[i].ys, type, t); } + unsavededits = 1; } void replace() @@ -645,6 +654,7 @@ void replace() } block b = { 0, 0, ssize, ssize }; remip(b); + unsavededits = 1; } void edittypexy(int type, block &sel) @@ -846,6 +856,7 @@ void movemap(int xo, int yo, int zo) // move whole map entinmap(player1); calclight(); resetmap(false); + unsavededits = 1; } void selfliprotate(block &sel, int dir) @@ -961,6 +972,7 @@ void transformclipentities() // transforms all clip entities to tag clips, if t while(thisrun); loopi(ssize) loopj(ssize) { sqr *s = S(i,j); if(s->tag & TAGCLIP) s->tag &= ~TAGPLCLIP; } conoutf("changed %d clip entities to tagged clip areas", total); + unsavededits = 1; } COMMAND(transformclipentities, ""); diff --git a/source/src/main.cpp b/source/src/main.cpp index ee490743..092eae05 100644 --- a/source/src/main.cpp +++ b/source/src/main.cpp @@ -38,6 +38,7 @@ void quit() // normal exit if(resetcfg) deletecfg(); else writecfg(); savehistory(); + writeallxmaps(); cleanup(NULL); popscontext(); exit(EXIT_SUCCESS); @@ -1176,6 +1177,8 @@ int main(int argc, char **argv) exec("config/admin.cfg"); execfile("config/servers.cfg"); loadhistory(); + int xmn = loadallxmaps(); + if(xmn) conoutf("loaded %d xmaps", xmn); per_idents = true; static char resdata[] = { 112, 97, 99, 107, 97, 103, 101, 115, 47, 116, 101, 120, 116, 117, 114, 101, 115, 47, 107, 117, 114, 116, 47, 107, 108, 105, 116, 101, 50, 46, 106, 112, 103, 0 }; diff --git a/source/src/protos.h b/source/src/protos.h index 547dbed2..d7a26cbb 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -403,7 +403,7 @@ struct serverstate { int autoteam; int mastermode; int matchteamsize; void reset extern struct serverstate servstate; extern void updateworld(int curtime, int lastmillis); extern void resetmap(bool mrproper = true); -extern void startmap(const char *name, bool reset = true); +extern void startmap(const char *name, bool reset = true, bool norespawn = false); extern void changemap(const char *name); extern void initclient(); extern void deathstate(playerent *pl); @@ -500,7 +500,9 @@ extern void removedynlights(physent *owner); extern block *blockcopy(const block &b); extern void blockpaste(const block &b, int bx, int by, bool light); extern void blockpaste(const block &b); +extern void freeblockp(block *b); extern void freeblock(block *&b); +extern block *duplicateblock(const block *s); // worldrender extern void render_world(float vx, float vy, float vh, float changelod, int yaw, int pitch, float fov, float fovy, int w, int h); @@ -591,8 +593,10 @@ extern void editheightxy(bool isfloor, int amount, block &sel); //extern bool noteditmode(); extern bool noteditmode(const char* func = NULL); extern void pruneundos(int maxremain = 0); +extern void storeposition(short p[]); +extern void restoreposition(short p[]); extern void restoreeditundo(ucharbuf &q); -extern void backupeditundo(vector &buf, int undolimit, int redolimit); +extern int backupeditundo(vector &buf, int undolimit, int redolimit); // renderhud enum @@ -680,6 +684,9 @@ extern uchar *readmcfggz(char *name, int *size, int *sizegz); extern void rlencodecubes(vector &f, sqr *s, int len, bool preservesolids); extern void rldecodecubes(ucharbuf &f, sqr *s, int len, int version, bool silent); extern void clearheaderextras(); +extern void xmapbackup(const char *nickprefix, const char *nick); +extern void writeallxmaps(); +extern int loadallxmaps(); // physics extern float raycube(const vec &o, const vec &ray, vec &surface); diff --git a/source/src/renderhud.cpp b/source/src/renderhud.cpp index 68323c1a..278059bc 100644 --- a/source/src/renderhud.cpp +++ b/source/src/renderhud.cpp @@ -946,6 +946,8 @@ void gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwat formatstring(c_val)("%d", xtraverts); c_num = xtraverts>3999?(xtraverts>5999?(xtraverts>7999?3:2):1):0; TXTCOLRGB draw_text(c_val, ttll - text_width(c_val), top+320, c_r, c_g, c_b); + + if(unsavededits) draw_text("U", ttll - text_width("U"), top - 90); } else { @@ -966,6 +968,7 @@ void gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwat } defformatstring(c_val)("fps %d", curfps); draw_text(c_val, VIRTW*2 - ( text_width(c_val) + FONTH ), VIRTH*2 - 3*FONTH/2); + if(unsavededits) draw_text("U", VIRTW*2 - text_width("U") - FONTH, VIRTH*2 - 5*FONTH/2); } } if(!intermission && clockdisplay!=0 && lastgametimeupdate!=0) diff --git a/source/src/tools.h b/source/src/tools.h index 3ff6c25d..e7186c15 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -162,7 +162,7 @@ inline bool issimilar (char s, char d) } #define MAXMAPNAMELEN 64 -inline bool validmapname(char *s) +inline bool validmapname(const char *s) { int len = strlen(s); if(len > MAXMAPNAMELEN) return false; diff --git a/source/src/water.cpp b/source/src/water.cpp index 7e59a85c..2b665cfe 100644 --- a/source/src/water.cpp +++ b/source/src/water.cpp @@ -4,7 +4,7 @@ int wx1, wy1, wx2, wy2; float wsx1, wsy1, wsx2, wsy2; VARP(watersubdiv, 1, 4, 64); -VARF(waterlevel, -128, -128, 127, if(!noteditmode("waterlevel")) hdr.waterlevel = waterlevel); +VARF(waterlevel, -128, -128, 127, if(!noteditmode("waterlevel")) { hdr.waterlevel = waterlevel; unsavededits = 1; }); void setwatercolor(const char *r, const char *g, const char *b, const char *a) { @@ -22,6 +22,7 @@ void setwatercolor(const char *r, const char *g, const char *b, const char *a) hdr.watercolor[2] = 20; hdr.watercolor[3] = 178; } + unsavededits = 1; } COMMANDN(watercolour, setwatercolor, "ssss"); diff --git a/source/src/world.cpp b/source/src/world.cpp index d7ede346..12bde3a6 100644 --- a/source/src/world.cpp +++ b/source/src/world.cpp @@ -191,6 +191,7 @@ void entproperty(int prop, int amount) loc->drop(); } if(changedents.find(n) == -1) changedents.add(n); // apply ent changes later (reduces network traffic) + unsavededits = 1; } hashtable mapinfo, &resdata = mapinfo; @@ -246,6 +247,7 @@ void delent() { case LIGHT: calclight(); break; } + unsavededits = 1; } int findtype(char *what) @@ -310,6 +312,7 @@ entity *newentity(int index, int x, int y, int z, char *what, int v1, int v2, in case LIGHT: calclight(); break; case SOUND: audiomgr.preloadmapsound(e); break; } + unsavededits = 1; return index<0 ? &ents.last() : &ents[index]; } @@ -333,6 +336,7 @@ void clearents(char *name) { entity &e = ents[i]; if(e.type==type) e.type = NOTUSED; + unsavededits = 1; } switch(type) { @@ -365,6 +369,7 @@ void scalelights(int f, int intens) scalecomp(e.attr3, intens); scalecomp(e.attr4, intens); } + unsavededits = 1; } calclight(); } @@ -460,6 +465,7 @@ bool empty_world(int factor, bool force) // main empty world creation routine if(copy && oldfactor == factor) return false; bool shrink = factor < oldfactor; bool clearmap = !copy && world; + if(clearmap && unsavededits) { xmapbackup("newmap", ""); unsavededits = 0; } if(copy) ow = blockcopy(shrink ? bs : be); extern char *mlayout; @@ -491,6 +497,7 @@ bool empty_world(int factor, bool force) // main empty world creation routine } else { // all-new map + int oldunsavededits = unsavededits; memset(&hdr, 0, sizeof(header)); formatstring(hdr.maptitle)("Untitled Map by %s", player1->name); setvar("waterlevel", (hdr.waterlevel = -100000)); @@ -500,6 +507,7 @@ bool empty_world(int factor, bool force) // main empty world creation routine block b = { 8, 8, ssize-16, ssize - 16}; edittypexy(SPACE, b); clearheaderextras(); + unsavededits = oldunsavededits; } strncpy(hdr.head, "ACMP", 4); hdr.version = MAPVERSION; @@ -744,6 +752,7 @@ void mapmrproper(bool manual) conoutf("before: %d / %d / %d / %d / %d / %d / %d", sta[0], sta[1], sta[2], sta[3], sta[4], sta[5], sta[6]); conoutf("idealized: %d / %d / %d / %d / %d / %d / %d", stb[0], stb[1], stb[2], stb[3], stb[4], stb[5], stb[6]); conoutf("final count: %d / %d / %d / %d / %d / %d / %d", stc[0], stc[1], stc[2], stc[3], stc[4], stc[5], stc[6]); + unsavededits = 1; } } COMMANDF(mapmrproper, "", () { mapmrproper(true); }); diff --git a/source/src/worldio.cpp b/source/src/worldio.cpp index e5f93a35..f5755db3 100644 --- a/source/src/worldio.cpp +++ b/source/src/worldio.cpp @@ -273,6 +273,7 @@ struct headerextra headerextra() : len(0), flags(0), data(NULL) {} headerextra(int l, int f, uchar *d) : len(l), flags(f), data(NULL) { if(d) { data = new uchar[len]; memcpy(data, d, len); } } ~headerextra() { DELETEA(data); } + headerextra *duplicate() { return new headerextra(len, flags, data); } }; vector headerextras; @@ -464,6 +465,7 @@ void save_world(char *mname, bool skipoptimise, bool addcomfort) rlencodecubes(rawcubes, world, cubicsize, skipoptimise); // if skipoptimize -> keep properties of solid cubes (forces format 10) f->write(rawcubes.getbuf(), rawcubes.length()); delete f; + unsavededits = 0; conoutf("wrote map file %s %s%s", cgzname, skipoptimise ? "without optimisation" : "(optimised)", addcomfort ? " (including editing history)" : ""); } VARP(preserveundosonsave, 0, 0, 1); @@ -506,6 +508,8 @@ void rebuildtexlists() // checks the texlists, if they still contain all possib } } +static string lastloadedconfigfile; + 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; @@ -528,6 +532,8 @@ 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; } + if(unsavededits) xmapbackup("load_map_", mname); + unsavededits = 0; DEBUG("reading map \"" << cgzname << "\""); header tmp; memset(&tmp, 0, sizeof_header); @@ -660,6 +666,7 @@ bool load_world(char *mname) // still supports all map formats that have execfile("config/default_map_settings.cfg"); execfile(pcfname); execfile(mcfname); + copystring(lastloadedconfigfile, mcfname); parseheaderextra(); neverpersist = false; per_idents = true; @@ -711,3 +718,361 @@ bool load_world(char *mname) // still supports all map formats that have startmap(mname); return true; } + +// support reading and writing binary data in config files + +static bool hexbinenabled = false; +static vector hexbin; + +void hexbinwrite(stream *f, void *data, int len, bool ascii = true) // write block of binary data as hex values with up to 24 bytes per line +{ + string asc; + uchar *s = (uchar *) data; + while(len > 0) + { + int chunk = min(len, 24); + f->printf("hexbinchunk"); + loopi(chunk) + { + asc[i] = isalnum(*s) ? *s : '.'; asc[i + 1] = '\0'; + f->printf(" %02x", int(*s++)); + } + f->printf(ascii ? " // %s\n" : "\n", asc); + len -= chunk; + } +} + +COMMANDF(hexbinchunk, "v", (char **args, int numargs) // read up to 24 bytes from the command's arguments to the hexbin buffer +{ + if(!hexbinenabled) { conoutf("hexbinchunk out of context"); return; } + loopi(numargs) hexbin.add(strtol(args[i], NULL, 16)); +}); + + +// multi-map editmode extensions (xmap) +// +// keep several (versions of) maps in memory at the same time +// compare maps and visualise differences +// +// the general assumption is, that ram is cheap, and that a copy of a size-9 map should be below 10MB (including undo data) + +#define XMAPVERSION 1001001 + +VARP(persistentxmaps, 0, 1, 1); // save all xmaps on exit, restore them at game start + +struct xmap +{ + string nick; // unique handle + string name; + vector headerextras; + vector ents; + sqr *world; + header hdr; + int ssize, cubicsize, numundo; + string mcfname; + short position[5]; + + xmap() : world(NULL), ssize(0), cubicsize(0), numundo(0) { *nick = *name = *mcfname = '\0'; } + + xmap(const char *nnick) // take the current map and store it in an xmap + { + copystring(nick, nnick); + copystring(name, getclientmap()); + hdr = ::hdr; ssize = ::ssize; cubicsize = ::cubicsize; + loopv(::headerextras) headerextras.add(::headerextras[i]->duplicate()); + world = new sqr[cubicsize]; + memcpy(world, ::world, cubicsize * sizeof(sqr)); + loopv(::ents) if(::ents[i].type != NOTUSED) ents.add() = ::ents[i]; + copystring(mcfname, lastloadedconfigfile); // may have been "official" or not + storeposition(position); + vector tmp; // add undo/redo data in compressed form as temporary headerextra + numundo = backupeditundo(tmp, MAXHEADEREXTRA, MAXHEADEREXTRA); + headerextras.add(new headerextra(tmp.length(), HX_EDITUNDO, tmp.getbuf())); + } + + ~xmap() + { + headerextras.deletecontents(); + delete world; + ents.setsize(0); + } + + void restoreent(int i) + { + entity &e = ::ents.add(); + memcpy(&e, &ents[i], sizeof(persistent_entity)); + e.spawned = true; + } + + void restore() // overwrite the current map with the contents of an xmap + { + setnames(name); + copystring(lastloadedconfigfile, mcfname); + ::hdr = hdr; + clearheaderextras(); + resetmap(true); + curmaprevision = hdr.maprevision; + setvar("waterlevel", hdr.waterlevel); + ::ents.shrink(0); + loopv(ents) restoreent(i); + delete[] ::world; + setupworld(hdr.sfactor); + memcpy(::world, world, cubicsize * sizeof(sqr)); + calclight(); // includes full remip() + loopv(headerextras) ::headerextras.add(headerextras[i]->duplicate()); + pushscontext(IEXC_MAPCFG); // untrusted altogether + per_idents = false; + neverpersist = true; + execfile("config/default_map_settings.cfg"); + execfile(pcfname); + execfile(lastloadedconfigfile); + parseheaderextra(); + neverpersist = false; + per_idents = true; + popscontext(); + loadsky(NULL, true); + startmap(name, false, true); // "start" but don't respawn: basically just set clientmap + restoreposition(position); + } + + void write(stream *f) // write xmap as cubescript to a file + { + f->printf("restorexmap version %d %d %d\n", XMAPVERSION, isbigendian() ? 1 : 0, sizeof(world)); // it is a non-portable binary file format, so we have to check... + f->printf("restorexmap names \"%s\" \"%s\"\n", name, mcfname); + f->printf("restorexmap sizes %d %d %d\n", ssize, cubicsize, numundo); + hexbinwrite(f, &hdr, sizeof(header)); + f->printf("restorexmap header\n"); + uLongf worldsize = cubicsize * sizeof(sqr), gzbufsize = (worldsize * 11) / 10; // gzip the world, so the file will only be big instead of huge + uchar *gzbuf = new uchar[gzbufsize]; + if(compress2(gzbuf, &gzbufsize, (uchar *)world, worldsize, 9) != Z_OK) gzbufsize = 0; + hexbinwrite(f, gzbuf, gzbufsize, false); + f->printf("restorexmap world\n"); + DELETEA(gzbuf); + loopv(headerextras) + { + hexbinwrite(f, headerextras[i]->data, headerextras[i]->len); + f->printf("restorexmap headerextra %d // %s\n", headerextras[i]->flags, hx_name(headerextras[i]->flags)); + } + loopv(ents) // entities are stored as plain text - you may edit them + { + persistent_entity &e = ents[i]; + f->printf("restorexmap ent %d %d %d %d %d %d %d %d // %s\n", e.type, e.x, e.y, e.z, e.attr1, e.attr2, e.attr3, e.attr4, e.type >= 0 && e.type < MAXENTTYPES ? entnames[e.type] : "unknown"); + } + f->printf("restorexmap position %d %d %d %d %d // EOF, don't touch this\n\n", position[0], position[1], position[2], position[3], position[4]); + } +}; + +static vector xmaps; +static xmap *bak, *xmjigsaw; // only bak needs to be deleted before reuse + +#define SPEDIT if(noteditmode("xmap") || multiplayer()) return // only allowed in non-coop editmode +#define SPEDITDIFF if(noteditmode("xmap") || multiplayer() || nodiff()) return // only allowed in non-coop editmode after xmap_diff + +void xmapdelete(xmap *&xm) // make sure, we don't point to deleted xmaps +{ + DELETEP(xm); +} + +void xmapbackup(const char *nickprefix, const char *nick) // throw away existing backup and make a new one +{ + xmapdelete(bak); + defformatstring(text)("%s%s", nickprefix, nick); + bak = new xmap(text); + if(unsavededits) conoutf("\f3stored backup of unsaved edits on map '%s'; to restore, type \"/xmap_restore\"", bak->name); +} + +const char *xmapdescstring(xmap *xm, bool shortform = false) +{ + static string s[2]; + static int toggle; + toggle = !toggle; + formatstring(s[toggle])("\"%s\": %s rev %d, %d ents%c size %d, hdrs %d, %d undo steps", xm->nick, xm->name, xm->hdr.maprevision, xm->ents.length(), + shortform ? '\0' : ',', xm->hdr.sfactor, xm->headerextras.length() - 1, xm->numundo); + return s[toggle]; +} + +xmap *getxmapbynick(const char *nick, int *index = NULL, bool errmsg = true) +{ + if(*nick) loopvrev(xmaps) + { + if(!strcmp(nick, xmaps[i]->nick)) + { + if(index) *index = i; + return xmaps[i]; + } + } + if(errmsg) conoutf("xmap \"%s\" not found", nick); + return NULL; +} + +COMMANDF(xmap_list, "", () // list xmaps (and status) +{ + loopv(xmaps) conoutf("xmap %d %s", i, xmapdescstring(xmaps[i])); + if(xmaps.length() == 0) conoutf("no xmaps in memory"); + if(bak) conoutf("backup stored before %s", xmapdescstring(bak)); +} ); + +COMMANDF(xmap_store, "s", (const char *nick) // store current map in an xmap buffer +{ + if(noteditmode("xmap_store")) return; // (may also be used in coopedit) + if(!*nick) return; + if(!validmapname(nick)) { conoutf("sry, %s is not a valid xmap nickname", nick); return; } + int i; + if(getxmapbynick(nick, &i, false)) { xmap *xm = xmaps.remove(i); xmapdelete(xm); } // overwrite existing same nick + xmaps.add(new xmap(nick)); + unsavededits = 0; + conoutf("stored xmap %s", xmapdescstring(xmaps.last())); +} ); + +COMMANDF(xmap_delete, "s", (const char *nick) // delete xmap buffer +{ + if(!*nick) return; + int i; + if(getxmapbynick(nick, &i)) + { + xmap *xm = xmaps.remove(i); + xmapdelete(xm); + conoutf("deleted xmap \"%s\"", nick); + } +} ); + +COMMANDF(xmap_restore, "s", (const char *nick) // use xmap as current map +{ + SPEDIT; + if(*nick) + { + xmap *xm = getxmapbynick(nick); + if(!xm) return; + xmapbackup("restore_xmap_", nick); // keep backup, so we can restore from the restore :) + xm->restore(); + unsavededits = 0; + conoutf("restored xmap %s", xmapdescstring(xm)); + } + else if(bak) + { + bak->restore(); + unsavededits = 0; + conoutf("restored backup created before %s", xmapdescstring(bak)); + } +} ); + +void restorexmap(char **args, int numargs) // read an xmap from a cubescript file +{ + const char *cmdnames[] = { "version", "names", "sizes", "header", "world", "headerextra", "ent", "position" }; + const char cmdnumarg[] = { 3, 2, 3, 0, 0, 1, 8, 5 }; + + if(!xmjigsaw || numargs < 1) return; // { conoutf("restorexmap out of context"); return; } + bool abort = false; + int cmd = -1; + loopi(sizeof(cmdnames)/sizeof(cmdnames[0])) if(!strcmp(cmdnames[i], args[0])) { cmd = i; break; } + if(cmd < 0 || numargs != cmdnumarg[cmd] + 1) { conoutf("restorexmap error"); return; } + switch(cmd) + { + case 0: // version + if(ATOI(args[1]) != XMAPVERSION || ATOI(args[2]) != (isbigendian() ? 1 : 0) || ATOI(args[3]) != sizeof(world)) + { + conoutf("restorexmap: file is from different game version"); + abort = true; + } + break; + case 1: // names + copystring(xmjigsaw->name, args[1]); + copystring(xmjigsaw->mcfname, args[2]); + break; + case 2: // sizes + xmjigsaw->ssize = ATOI(args[1]); + xmjigsaw->cubicsize = ATOI(args[2]); + xmjigsaw->numundo = ATOI(args[3]); + break; + case 3: // header + if(hexbin.length() == sizeof(header)) memcpy(&xmjigsaw->hdr, hexbin.getbuf(), sizeof(header)); + else abort = true; + break; + case 4: // world + { + int rawworldsize = xmjigsaw->cubicsize * sizeof(sqr); + xmjigsaw->world = new sqr[rawworldsize]; + uLongf rawsize = rawworldsize; + if(uncompress((uchar *)xmjigsaw->world, &rawsize, hexbin.getbuf(), hexbin.length()) != Z_OK || rawsize - rawworldsize != 0) abort = true; + break; + } + case 5: // headerextra + xmjigsaw->headerextras.add(new headerextra(hexbin.length(), ATOI(args[1]), hexbin.getbuf())); + break; + case 6: // ent + { + persistent_entity &e = xmjigsaw->ents.add(); + int a[8]; + loopi(8) a[i] = ATOI(args[i + 1]); + e.type = a[0]; e.x = a[1]; e.y = a[2]; e.z = a[3]; e.attr1 = a[4]; e.attr2 = a[5]; e.attr3 = a[6]; e.attr4 = a[7]; + break; + } + case 7: // position - this is also the last command and will finish the xmap + { + loopi(5) xmjigsaw->position[i] = ATOI(args[i + 1]); + int i; + if(getxmapbynick(xmjigsaw->nick, &i, false)) { xmap *xm = xmaps.remove(i); xmapdelete(xm); } // overwrite existing same nick + if(!xmjigsaw->world) abort = true; + else + { + xmaps.add(xmjigsaw); + xmjigsaw = NULL; // no abort! + } + } + } + hexbin.setsize(0); + if(abort) DELETEP(xmjigsaw); +} +COMMAND(restorexmap, "v"); + +static const char *bakprefix = "__.backup.__", *xmapspath = "mapediting/xmaps"; +char *xmapfilename(const char *nick, const char *prefix = "") { static defformatstring(fn)("%s/%s%s.xmap", xmapspath, prefix, nick); return fn; } + +void writexmap(xmap *xm, const char *prefix = "") +{ + stream *f = openfile(xmapfilename(xm->nick, prefix), "w"); + xm->write(f); + delete f; +} + +void writeallxmaps() // at game exit, write all xmaps to cubescript files in mapediting/xmaps +{ + if(!persistentxmaps) return; + loopv(xmaps) writexmap(xmaps[i]); + if(unsavededits) xmapbackup("gameend", ""); + if(bak) writexmap(bak, bakprefix); +} + +void loadxmap(const char *nick) +{ + DELETEP(xmjigsaw); + xmjigsaw = new xmap(); + copystring(xmjigsaw->nick, nick); + hexbinenabled = true; + execfile(xmapfilename(nick)); + hexbinenabled = false; + hexbin.setsize(0); // just to make sure +} + +int loadallxmaps() // at game start, load all xmaps from mapediting/xmaps +{ + vector xmapnames; + string filename; + listfiles(xmapspath, "xmap", xmapnames); + xmapnames.sort(stringsort); + loopv(xmapnames) + { + loadxmap(xmapnames[i]); + copystring(filename, xmapfilename(xmapnames[i])); + backup(filename, xmapfilename(xmapnames[i], "backups/")); // move to backup folder - we will write new files at exit + delete[] xmapnames[i]; + } + loopv(xmaps) if(!strncmp(xmaps[i]->nick, bakprefix, strlen(bakprefix))) + { + xmapdelete(bak); + bak = xmaps.remove(i); + copystring(bak->nick, bak->nick + strlen(bakprefix)); + break; + } + return xmapnames.length(); +} diff --git a/source/src/worldlight.cpp b/source/src/worldlight.cpp index 3650b2f2..4b722e94 100644 --- a/source/src/worldlight.cpp +++ b/source/src/worldlight.cpp @@ -174,7 +174,7 @@ void fullbrightlight(int level) lastcalclight = totalmillis; } -VARF(ambient, 0, 0, 0xFFFFFF, if(!noteditmode("ambient")) { hdr.ambient = ambient; calclight(); }); +VARF(ambient, 0, 0, 0xFFFFFF, if(!noteditmode("ambient")) { hdr.ambient = ambient; calclight(); unsavededits = 1;}); void calclight() { @@ -398,8 +398,21 @@ void blockpaste(const block &b) // fast version, used by dynlight remipmore(b); } -void freeblock(block *&b) +void freeblockp(block *b) { - if(b) { delete[] (uchar *)b; b = NULL; } + delete[] (uchar *)b; +} + +void freeblock(block *&b) +{ + if(b) { freeblockp(b); b = NULL; } +} + +block *duplicateblock(const block *s) +{ + size_t len = sizeof(block) + s->xs * s->ys * sizeof(sqr); + block *b = (block *)new uchar[len]; + memcpy(b, s, len); + return b; }