// editing.cpp: most map editing commands go here, entity editing commands are in world.cpp #include "cube.h" bool editmode = false; // the current selection, used by almost all editing commands // invariant: all code assumes that these are kept inside MINBORD distance of the edge of the map block sel = { variable("selx", 0, 0, 4096, &sel.x, NULL, false), variable("sely", 0, 0, 4096, &sel.y, NULL, false), variable("selxs", 0, 0, 4096, &sel.xs, NULL, false), variable("selys", 0, 0, 4096, &sel.ys, NULL, false), }; int selh = 0; bool selset = false; #define loopselxy(b) { makeundo(); loop(x,sel.xs) loop(y,sel.ys) { sqr *s = S(sel.x+x, sel.y+y); b; } remip(sel); } int cx, cy, ch; int curedittex[] = { -1, -1, -1 }; bool dragging = false; int lastx, lasty, lasth; int lasttype = 0, lasttex = 0; sqr rtex; VAR(editing, 1, 0, 0); void toggleedit() { if(player1->state==CS_DEAD) return; // do not allow dead players to edit to avoid state confusion if(!editmode && !allowedittoggle()) return; // not in most multiplayer modes if(!(editmode = !editmode)) { entinmap(player1); // find spawn closest to current floating pos } else { player1->health = 100; //put call to clear/restart gamemode projreset(); player1->attacking = false; } keyrepeat(editmode); selset = false; editing = editmode; } COMMANDN(edittoggle, toggleedit, ARG_NONE); char *editinfo() { static string info; if(!editmode) return NULL; int e = closestent(); if(e<0) return NULL; entity &c = ents[e]; s_sprintf(info)("closest entity = %s (%d, %d, %d, %d), selection = (%d, %d)", entnames[c.type], c.attr1, c.attr2, c.attr3, c.attr4, sel.xs, sel.ys); return info; } void correctsel() // ensures above invariant { selset = !OUTBORD(sel.x, sel.y); int bsize = ssize-MINBORD; if(sel.xs+sel.x>bsize) sel.xs = bsize-sel.x; if(sel.ys+sel.y>bsize) sel.ys = bsize-sel.y; if(sel.xs<=0 || sel.ys<=0) selset = false; } bool noteditmode() { correctsel(); if(!editmode) conoutf("this function is only allowed in edit mode"); return !editmode; } bool noselection() { if(!selset) conoutf("no selection"); return !selset; } #define EDITSEL if(noteditmode() || noselection()) return; #define EDITSELMP if(noteditmode() || noselection() || multiplayer()) return; #define EDITMP if(noteditmode() || multiplayer()) return; void selectpos(int x, int y, int xs, int ys) { block s = { x, y, xs, ys }; sel = s; selh = 0; correctsel(); } void makesel() { block s = { min(lastx,cx), min(lasty,cy), abs(lastx-cx)+1, abs(lasty-cy)+1 }; sel = s; selh = max(lasth,ch); correctsel(); if(selset) rtex = *S(sel.x, sel.y); } VAR(flrceil,0,0,2); // VC8 optimizer screws up rendering somehow if this is an actual function #define sheight(s,t,z) (!flrceil ? (s->type==FHF ? s->floor-t->vdelta/4.0f : (float)s->floor) : (s->type==CHF ? s->ceil+t->vdelta/4.0f : (float)s->ceil)) void cursorupdate() // called every frame from hud { flrceil = ((int)(camera1->pitch>=0))*2; volatile float x = worldpos.x; // volatile needed to prevent msvc7 optimizer bug? volatile float y = worldpos.y; volatile float z = worldpos.z; cx = (int)x; cy = (int)y; if(OUTBORD(cx, cy)) return; sqr *s = S(cx,cy); if(fabs(sheight(s,s,z)-z)>1) // selected wall { x += x>camera1->o.x ? 0.5f : -0.5f; // find right wall cube y += y>camera1->o.y ? 0.5f : -0.5f; cx = (int)x; cy = (int)y; if(OUTBORD(cx, cy)) return; } if(dragging) makesel(); const int GRIDSIZE = 5; const float GRIDW = 0.5f; const float GRID8 = 2.0f; const float GRIDS = 2.0f; const int GRIDM = 0x7; // render editing grid for(int ix = cx-GRIDSIZE; ix<=cx+GRIDSIZE; ix++) for(int iy = cy-GRIDSIZE; iy<=cy+GRIDSIZE; iy++) { if(OUTBORD(ix, iy)) continue; sqr *s = S(ix,iy); if(SOLID(s)) continue; float h1 = sheight(s, s, z); float h2 = sheight(s, SWS(s,1,0,ssize), z); float h3 = sheight(s, SWS(s,1,1,ssize), z); float h4 = sheight(s, SWS(s,0,1,ssize), z); if(s->tag) linestyle(GRIDW, 0xFF, 0x40, 0x40); else if(s->type==FHF || s->type==CHF) linestyle(GRIDW, 0x80, 0xFF, 0x80); else linestyle(GRIDW, 0x80, 0x80, 0x80); block b = { ix, iy, 1, 1 }; box(b, h1, h2, h3, h4); linestyle(GRID8, 0x40, 0x40, 0xFF); if(!(ix&GRIDM)) line(ix, iy, h1, ix, iy+1, h4); if(!(ix+1&GRIDM)) line(ix+1, iy, h2, ix+1, iy+1, h3); if(!(iy&GRIDM)) line(ix, iy, h1, ix+1, iy, h2); if(!(iy+1&GRIDM)) line(ix, iy+1, h4, ix+1, iy+1, h3); } if(!SOLID(s)) { float ih = sheight(s, s, z); linestyle(GRIDS, 0xFF, 0xFF, 0xFF); block b = { cx, cy, 1, 1 }; box(b, ih, sheight(s, SWS(s,1,0,ssize), z), sheight(s, SWS(s,1,1,ssize), z), sheight(s, SWS(s,0,1,ssize), z)); linestyle(GRIDS, 0xFF, 0x00, 0x00); dot(cx, cy, ih); ch = (int)ih; } if(selset) { linestyle(GRIDS, 0xFF, 0x40, 0x40); box(sel, (float)selh, (float)selh, (float)selh, (float)selh); } } vector undos; // unlimited undo VAR(undomegs, 0, 1, 10); // bounded by n megs void pruneundos(int maxremain) // bound memory { int t = 0; loopvrev(undos) { t += undos[i]->xs*undos[i]->ys*sizeof(sqr); if(t>maxremain) delete undos.remove(i); } } void makeundo() { undos.add(blockcopy(sel)); pruneundos(undomegs<<20); } void editundo() { EDITMP; if(undos.empty()) { conoutf("nothing more to undo"); return; } block *p = undos.pop(); blockpaste(*p); freeblock(p); } block *copybuf = NULL; void copy() { EDITSELMP; freeblock(copybuf); copybuf = blockcopy(sel); } void paste() { EDITMP; if(!copybuf) { conoutf("nothing to paste"); return; } sel.xs = copybuf->xs; sel.ys = copybuf->ys; correctsel(); if(!selset || sel.xs!=copybuf->xs || sel.ys!=copybuf->ys) { conoutf("incorrect selection"); return; } makeundo(); copybuf->x = sel.x; copybuf->y = sel.y; blockpaste(*copybuf); } void tofronttex() // maintain most recently used of the texture lists when applying texture { loopi(3) { int c = curedittex[i]; if(c>=0) { uchar *p = hdr.texlists[i]; int t = p[c]; for(int a = c-1; a>=0; a--) p[a+1] = p[a]; p[0] = t; curedittex[i] = -1; } } } void editdrag(bool isdown) { if(dragging = isdown) { lastx = cx; lasty = cy; lasth = ch; selset = false; tofronttex(); } makesel(); } // the core editing function. all the *xy functions perform the core operations // and are also called directly from the network, the function below it is strictly // triggered locally. They all have very similar structure. void editheightxy(bool isfloor, int amount, block &sel) { loopselxy(if(isfloor) { s->floor += amount; if(s->floor>=s->ceil) s->floor = s->ceil-1; } else { s->ceil += amount; if(s->ceil<=s->floor) s->ceil = s->floor+1; }); } void editheight(int flr, int amount) { EDITSEL; bool isfloor = flr==0; editheightxy(isfloor, amount, sel); addmsg(SV_EDITH, "ri6", sel.x, sel.y, sel.xs, sel.ys, isfloor, amount); } COMMAND(editheight, ARG_2INT); void edittexxy(int type, int t, block &sel) { loopselxy(switch(type) { case 0: s->ftex = t; break; case 1: s->wtex = t; break; case 2: s->ctex = t; break; case 3: s->utex = t; break; }); } void edittex(int type, int dir) { EDITSEL; if(type<0 || type>3) return; if(type!=lasttype) { tofronttex(); lasttype = type; } int atype = type==3 ? 1 : type; int i = curedittex[atype]; i = i<0 ? 0 : i+dir; curedittex[atype] = i = min(max(i, 0), 255); int t = lasttex = hdr.texlists[atype][i]; edittexxy(type, t, sel); addmsg(SV_EDITT, "ri6", sel.x, sel.y, sel.xs, sel.ys, type, t); } void replace() { EDITSELMP; loop(x,ssize) loop(y,ssize) { sqr *s = S(x, y); switch(lasttype) { case 0: if(s->ftex == rtex.ftex) s->ftex = lasttex; break; case 1: if(s->wtex == rtex.wtex) s->wtex = lasttex; break; case 2: if(s->ctex == rtex.ctex) s->ctex = lasttex; break; case 3: if(s->utex == rtex.utex) s->utex = lasttex; break; } } block b = { 0, 0, ssize, ssize }; remip(b); } void edittypexy(int type, block &sel) { loopselxy(s->type = type); } void edittype(int type) { EDITSEL; if(type==CORNER && (sel.xs!=sel.ys || sel.xs==3 || sel.xs>4 && sel.xs!=8 || sel.x&~-sel.xs || sel.y&~-sel.ys)) { conoutf("corner selection must be power of 2 aligned"); return; } edittypexy(type, sel); addmsg(SV_EDITS, "ri5", sel.x, sel.y, sel.xs, sel.ys, type); } void heightfield(int t) { edittype(t==0 ? FHF : CHF); } void solid(int t) { edittype(t==0 ? SPACE : SOLID); } void corner() { edittype(CORNER); } COMMAND(heightfield, ARG_1INT); COMMAND(solid, ARG_1INT); COMMAND(corner, ARG_NONE); void editequalisexy(bool isfloor, block &sel) { int low = 127, hi = -128; loopselxy( { if(s->floorfloor; if(s->ceil>hi) hi = s->ceil; }); loopselxy( { if(isfloor) s->floor = low; else s->ceil = hi; if(s->floor>=s->ceil) s->floor = s->ceil-1; }); } void equalize(int flr) { bool isfloor = flr==0; EDITSEL; editequalisexy(isfloor, sel); addmsg(SV_EDITE, "ri5", sel.x, sel.y, sel.xs, sel.ys, isfloor); } COMMAND(equalize, ARG_1INT); void setvdeltaxy(int delta, block &sel) { loopselxy(s->vdelta = max(s->vdelta+delta, 0)); remipmore(sel); } void setvdelta(int delta) { EDITSEL; setvdeltaxy(delta, sel); addmsg(SV_EDITD, "ri5", sel.x, sel.y, sel.xs, sel.ys, delta); } const int MAXARCHVERT = 50; int archverts[MAXARCHVERT][MAXARCHVERT]; bool archvinit = false; void archvertex(int span, int vert, int delta) { if(!archvinit) { archvinit = true; loop(s,MAXARCHVERT) loop(v,MAXARCHVERT) archverts[s][v] = 0; } if(span>=MAXARCHVERT || vert>=MAXARCHVERT || span<0 || vert<0) return; archverts[span][vert] = delta; } void arch(int sidedelta, int _a) { EDITSELMP; sel.xs++; sel.ys++; if(sel.xs>MAXARCHVERT) sel.xs = MAXARCHVERT; if(sel.ys>MAXARCHVERT) sel.ys = MAXARCHVERT; loopselxy(s->vdelta = sel.xs>sel.ys ? (archverts[sel.xs-1][x] + (y==0 || y==sel.ys-1 ? sidedelta : 0)) : (archverts[sel.ys-1][y] + (x==0 || x==sel.xs-1 ? sidedelta : 0))); remipmore(sel); } void slope(int xd, int yd) { EDITSELMP; int off = 0; if(xd<0) off -= xd*sel.xs; if(yd<0) off -= yd*sel.ys; sel.xs++; sel.ys++; loopselxy(s->vdelta = xd*x+yd*y+off); remipmore(sel); } void perlin(int scale, int seed, int psize) { EDITSELMP; sel.xs++; sel.ys++; makeundo(); sel.xs--; sel.ys--; perlinarea(sel, scale, seed, psize); sel.xs++; sel.ys++; remipmore(sel); sel.xs--; sel.ys--; } VARF(fullbright, 0, 0, 1, if(fullbright) { if(noteditmode()) return; loopi(mipsize) world[i].r = world[i].g = world[i].b = 176; } ); void edittag(int tag) { EDITSELMP; loopselxy(s->tag = tag); } void newent(char *what, char *a1, char *a2, char *a3, char *a4) { EDITSEL; newentity(sel.x, sel.y, (int)camera1->o.z, what, ATOI(a1), ATOI(a2), ATOI(a3), ATOI(a4)); } COMMANDN(select, selectpos, ARG_4INT); COMMAND(edittag, ARG_1INT); COMMAND(replace, ARG_NONE); COMMAND(archvertex, ARG_3INT); COMMAND(arch, ARG_2INT); COMMAND(slope, ARG_2INT); COMMANDN(vdelta, setvdelta, ARG_1INT); COMMANDN(undo, editundo, ARG_NONE); COMMAND(copy, ARG_NONE); COMMAND(paste, ARG_NONE); COMMAND(edittex, ARG_2INT); COMMAND(newent, ARG_5STR); COMMAND(perlin, ARG_3INT);