From b0649b5ea5a812df152fb25e5ee91c0528c00a6e Mon Sep 17 00:00:00 2001 From: ac-stef Date: Sun, 11 May 2014 00:12:27 +0200 Subject: [PATCH] multiple map buffers for map editing: "xmaps" An xmap holds everything related to map editing at a certain moment: it is a map editing snapshot. It contains the name, the header, the geometry, the entities, the extra headers, the undo/redo data and the cameraposition from the moment, the snapshot was taken. Xmaps are held in memory and, if persistentxmaps is set to "1", are saved at game end and restored at game start. xmap_list lists all xmaps currently in memory xmap_store create a snapshot and store it under the given nickname (nicknames have to qualify as valid filenames) xmap_restore restores the named snapshot. Also creates a backup snapshot of the current editing data. xmap_restore restores the last automatic backup snapshot. Automatic snapshots are taken, a) before a snapshot is restored, b) before a new map is loaded (if there were unsaved edits on the map) and c) when the game ends (if there were unsaved edits). xmap_delete deletes the named snapshot. Unsaved edits are flagged in the HUD with an "U" in the lower right corner of the screen. The files with the persistent xmaps are stored in mapediting/xmaps. After they were restored at game start, the files are moved to mapediting/xmaps/backups. Xmap files are cubescript files. --- source/src/clientgame.cpp | 7 +- source/src/console.cpp | 1 + source/src/cube.h | 1 + source/src/editing.cpp | 18 +- source/src/main.cpp | 3 + source/src/protos.h | 11 +- source/src/renderhud.cpp | 3 + source/src/tools.h | 2 +- source/src/water.cpp | 3 +- source/src/world.cpp | 9 + source/src/worldio.cpp | 365 ++++++++++++++++++++++++++++++++++++++ source/src/worldlight.cpp | 19 +- 12 files changed, 429 insertions(+), 13 deletions(-) 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; }