1 // editing.cpp: most map editing commands go here, entity editing commands are in world.cpp
2 
3 #include "cube.h"
4 
5 bool editmode = false;
6 
7 // the current selections, used by almost all editing commands
8 // invariant: all code assumes that these are kept inside MINBORD distance of the edge of the map
9 // => selections are checked when they are made or when the world is reloaded
10 
11 vector<block> sels;
12 
13 #define loopselxy(sel, b) { makeundo(sel); loop(x,(sel).xs) loop(y,(sel).ys) { sqr *s = S((sel).x+x, (sel).y+y); b; } remip(sel); }
14 #define loopselsxy(b) { loopv(sels) loopselxy(sels[i], b); }
15 
16 int cx, cy, ch;
17 
18 int curedittex[] = { -1, -1, -1 };
19 
20 bool dragging = false;
21 int lastx, lasty, lasth;
22 
23 int lasttype = 0, lasttex = 0;
24 sqr rtex;
25 
26 VAR(editing, 1, 0, 0);
27 
toggleedit(bool force)28 void toggleedit(bool force)
29 {
30     if(player1->state==CS_DEAD) return;                   // do not allow dead players to edit to avoid state confusion
31     if(!force && !editmode && !allowedittoggle()) return; // not in most multiplayer modes
32     if(!(editmode = !editmode))
33     {
34         entinmap(player1);                                // find spawn closest to current floating pos
35     }
36     else
37     {
38         //put call to clear/restart gamemode
39         player1->attacking = false;
40     }
41     keyrepeat(editmode);
42     editing = editmode ? 1 : 0;
43     //2011oct16:flowtron:keep spectator state
44     //spectators are ghosts, no toggling of editmode for them!
45     player1->state = player1->state==CS_SPECTATE?CS_SPECTATE:(editing ? CS_EDITING : CS_ALIVE);
46     if(editing && player1->onladder) player1->onladder = false;
47     if(editing && (player1->weaponsel->type == GUN_SNIPER && ((sniperrifle *)player1->weaponsel)->scoped)) ((sniperrifle *)player1->weaponsel)->onownerdies(); // or ondeselecting()
48     if(!force) addmsg(SV_EDITMODE, "ri", editing);
49 }
50 
edittoggle()51 void edittoggle() { toggleedit(false); }
52 
53 COMMAND(edittoggle, "");
54 
correctsel(block & s)55 bool correctsel(block &s)                                       // ensures above invariant
56 {
57     int bsize = ssize - MINBORD;
58     if(s.xs+s.x>bsize) s.xs = bsize-s.x;
59     if(s.ys+s.y>bsize) s.ys = bsize-s.y;
60     if(s.xs<=0 || s.ys<=0) return false;
61     else return !OUTBORD(s.x, s.y);
62 }
63 
noteditmode(const char * func)64 bool noteditmode(const char* func)
65 {
66     if(!editmode)
67     {
68         if(func && func[0]!='\0') conoutf("\f4[\f3%s\f4]\f5 is only allowed in edit mode", func);
69         else conoutf("this function is only allowed in edit mode");
70     }
71     return !editmode;
72 }
73 
selset()74 inline bool selset()
75 {
76     return (sels.length() > 0);
77 }
78 
noselection()79 bool noselection()
80 {
81     if(!selset()) conoutf("no selection");
82     return !selset();
83 }
84 
editinfo()85 char *editinfo()
86 {
87     static string info;
88     if(!editmode) return NULL;
89     int e = closestent();
90     if(e<0) return NULL;
91     entity &c = ents[e];
92     string selinfo = "no selection";
93     if(selset()) formatstring(selinfo)("selection = (%d, %d)", (sels.last()).xs, (sels.last()).ys);
94     formatstring(info)("closest entity = %s (%d, %d, %d, %d), %s", entnames[c.type], c.attr1, c.attr2, c.attr3, c.attr4, selinfo);
95     return info;
96 }
97 
98 
99 #define EDITSEL   if(noteditmode("EDITSEL") || noselection()) return
100 #define EDITSELMP if(noteditmode("EDITSELMP") || noselection() || multiplayer()) return
101 #define EDITMP    if(noteditmode("EDITMP") || multiplayer()) return
102 
103 // multiple sels
104 
105 // add a selection to the list
addselection(int x,int y,int xs,int ys,int h)106 void addselection(int x, int y, int xs, int ys, int h)
107 {
108     block &s = sels.add();
109     s.x = x; s.y = y; s.xs = xs; s.ys = ys; s.h = h;
110     if(!correctsel(s)) { sels.drop(); }
111 }
112 
113 // reset all selections
resetselections()114 void resetselections()
115 {
116     sels.shrink(0);
117 }
118 
119 // reset all invalid selections
checkselections()120 void checkselections()
121 {
122     loopv(sels) if(!correctsel(sels[i])) sels.remove(i);
123 }
124 
125 // update current selection, or add a new one
makesel(bool isnew)126 void makesel(bool isnew)
127 {
128     block &cursel = sels.last(); //RR 10/12/12 - FIXEME, error checking should happen with "isnew", not here checking if it really is new.
129     if(isnew || sels.length() == 0) addselection(min(lastx, cx), min(lasty, cy), iabs(lastx-cx)+1, iabs(lasty-cy)+1, max(lasth, ch));
130     else
131     {
132         cursel.x = min(lastx, cx); cursel.y = min(lasty, cy);
133         cursel.xs = iabs(lastx-cx)+1; cursel.ys = iabs(lasty-cy)+1;
134         cursel.h = max(lasth, ch);
135         correctsel(cursel);
136     }
137     if(selset()) rtex = *S((sels.last()).x, (sels.last()).y);
138 }
139 
140 #define SEL_ATTR(attr) { string buf = ""; loopv(sels) { concatformatstring(buf, "%d ", sels[i].attr); } result(buf); }
141 COMMANDF(selx, "", (void) { SEL_ATTR(x); });
142 COMMANDF(sely, "", (void) { SEL_ATTR(y); });
143 COMMANDF(selxs, "", (void) { SEL_ATTR(xs); });
144 COMMANDF(selys, "", (void) { SEL_ATTR(ys); });
145 #undef SEL_ATTR
146 
147 VAR(flrceil,0,0,2);
148 VAR(editaxis,0,0,13);
149 VARP(showgrid,0,1,1);
150 
151 // VC8 optimizer screws up rendering somehow if this is an actual function
152 #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))
153 
cursorupdate()154 void cursorupdate()                                     // called every frame from hud
155 {
156     flrceil = ((int)(camera1->pitch>=0))*2;
157     int cyaw = ((int) camera1->yaw) % 180;
158     editaxis = editmode ? (fabs(camera1->pitch) > 65 ? 13 : (cyaw < 45 || cyaw > 135 ? 12 : 11)) : 0;
159 
160     volatile float x = worldpos.x;                      // volatile needed to prevent msvc7 optimizer bug?
161     volatile float y = worldpos.y;
162     volatile float z = worldpos.z;
163 
164     cx = (int)x;
165     cy = (int)y;
166 
167     if(OUTBORD(cx, cy)) return;
168     sqr *s = S(cx,cy);
169 
170     if(fabs(sheight(s,s,z)-z)>1)                        // selected wall
171     {
172         x += x>camera1->o.x ? 0.5f : -0.5f;             // find right wall cube
173         y += y>camera1->o.y ? 0.5f : -0.5f;
174 
175         cx = (int)x;
176         cy = (int)y;
177 
178         if(OUTBORD(cx, cy)) return;
179     }
180 
181     if(dragging) { makesel(false); };
182 
183     const int GRIDSIZE = 5;
184     const float GRIDW = 0.5f;
185     const float GRID8 = 2.0f;
186     const float GRIDS = 2.0f;
187     const int GRIDM = 0x7;
188 
189     // render editing grid
190 
191     if(showgrid)
192     {
193         for(int ix = cx-GRIDSIZE; ix<=cx+GRIDSIZE; ix++) for(int iy = cy-GRIDSIZE; iy<=cy+GRIDSIZE; iy++)
194         {
195 
196             if(OUTBORD(ix, iy)) continue;
197             sqr *s = S(ix,iy);
198             if(SOLID(s)) continue;
199             float h1 = sheight(s, s, z);
200             float h2 = sheight(s, SWS(s,1,0,sfactor), z);
201             float h3 = sheight(s, SWS(s,1,1,sfactor), z);
202             float h4 = sheight(s, SWS(s,0,1,sfactor), z);
203             if(s->tag) linestyle(GRIDW, 0xFF, 0x40, 0x40);
204             else if(s->type==FHF || s->type==CHF) linestyle(GRIDW, 0x80, 0xFF, 0x80);
205             else linestyle(GRIDW, 0x80, 0x80, 0x80);
206             block b = { ix, iy, 1, 1 };
207             box(b, h1, h2, h3, h4);
208             linestyle(GRID8, 0x40, 0x40, 0xFF);
209             if(!(ix&GRIDM))   line(ix,   iy,   h1, ix,   iy+1, h4);
210             if(!((ix+1)&GRIDM)) line(ix+1, iy,   h2, ix+1, iy+1, h3);
211             if(!(iy&GRIDM))   line(ix,   iy,   h1, ix+1, iy,   h2);
212             if(!((iy+1)&GRIDM)) line(ix,   iy+1, h4, ix+1, iy+1, h3);
213         }
214 
215         if(!SOLID(s))
216         {
217             float ih = sheight(s, s, z);
218             linestyle(GRIDS, 0xFF, 0xFF, 0xFF);
219             block b = { cx, cy, 1, 1 };
220             box(b, ih, sheight(s, SWS(s,1,0,sfactor), z), sheight(s, SWS(s,1,1,sfactor), z), sheight(s, SWS(s,0,1,sfactor), z));
221             linestyle(GRIDS, 0xFF, 0x00, 0x00);
222             dot(cx, cy, ih);
223             ch = (int)ih;
224         }
225     }
226 
227     if(selset())
228     {
229         linestyle(GRIDS, 0xFF, 0x40, 0x40);
230         loopv(sels) box(sels[i], (float)sels[i].h, (float)sels[i].h, (float)sels[i].h, (float)sels[i].h);
231     }
232 
233     glLineWidth(1);
234 }
235 
236 vector<block *> undos;                                  // unlimited undo
237 VAR(undomegs, 0, 1, 10);                                // bounded by n megs
238 
pruneundos(int maxremain)239 void pruneundos(int maxremain)                          // bound memory
240 {
241     int t = 0;
242     loopvrev(undos)
243     {
244         t += undos[i]->xs*undos[i]->ys*sizeof(sqr);
245         if(t>maxremain) delete[] (uchar *)undos.remove(i);
246     }
247 }
248 
makeundo(block & sel)249 void makeundo(block &sel)
250 {
251     undos.add(blockcopy(sel));
252     pruneundos(undomegs<<20);
253 }
254 
editundo()255 void editundo()
256 {
257     EDITMP;
258     if(undos.empty()) { conoutf("nothing more to undo"); return; }
259     block *p = undos.pop();
260     blockpaste(*p);
261     freeblock(p);
262 }
263 
264 vector<block *> copybuffers;
265 
resetcopybuffers()266 void resetcopybuffers()
267 {
268     loopv(copybuffers) freeblock(copybuffers[i]);
269     copybuffers.shrink(0);
270 }
271 
copy()272 void copy()
273 {
274     EDITSELMP;
275     resetcopybuffers();
276     loopv(sels)
277     {
278         block *b = blockcopy(sels[i]);
279         copybuffers.add(b);
280     }
281 
282 }
283 
paste()284 void paste()
285 {
286     EDITSELMP;
287     if(!copybuffers.length()) { conoutf("nothing to paste"); return; }
288 
289     loopv(sels)
290     {
291         block &sel = sels[i];
292         int selx = sel.x;
293         int sely = sel.y;
294 
295         loopvj(copybuffers)
296         {
297             block *copyblock = copybuffers[j];
298             int dx = copyblock->x - copybuffers[0]->x, dy = copyblock->y - copybuffers[0]->y;
299 
300             sel.xs = copyblock->xs;
301             sel.ys = copyblock->ys;
302             sel.x = selx + dx;
303             sel.y = sely + dy;
304             if(!correctsel(sel) || sel.xs!=copyblock->xs || sel.ys!=copyblock->ys) { conoutf("incorrect selection"); return; }
305             makeundo(sel);
306             blockpaste(*copyblock, sel.x, sel.y, true);
307         }
308         remipmore(sel);
309     }
310 }
311 
312 // Count the walls of type "type" contained in the current selection
countwalls(int * type)313 void countwalls(int *type)
314 {
315     int counter = 0;
316     EDITSELMP;
317     if(*type < 0 || *type >= MAXTYPE)
318     {
319         conoutf("invalid type");
320         intret(0);
321     }
322     loopselsxy(if(s->type==*type) counter++)
323     intret(counter);
324 }
325 
tofronttex()326 void tofronttex()                                       // maintain most recently used of the texture lists when applying texture
327 {
328     loopi(3)
329     {
330         int c = curedittex[i];
331         if(c>=0)
332         {
333             uchar *p = hdr.texlists[i];
334             int t = p[c];
335             for(int a = c-1; a>=0; a--) p[a+1] = p[a];
336             p[0] = t;
337             curedittex[i] = -1;
338         }
339     }
340 }
341 
editdrag(bool isdown)342 void editdrag(bool isdown)
343 {
344     if((dragging = isdown))
345     {
346         lastx = cx;
347         lasty = cy;
348         lasth = ch;
349         tofronttex();
350 
351         bool ctrlpressed = false;
352 
353         if (identexists("newselkeys"))
354         {
355             extern vector<keym> keyms;
356             vector<char *> elems;
357             explodelist(getalias("newselkeys"), elems);
358 
359             loopi(keyms.length()) if(keyms[i].pressed) loopj(elems.length())
360             {
361                 if (strcmp(keyms[i].name, elems[j]) == 0)
362                 {
363                     ctrlpressed = true;
364                     break;
365                 }
366             }
367         }
368 
369         if(!ctrlpressed) resetselections();
370     }
371     makesel(isdown);
372 }
373 
374 // the core editing function. all the *xy functions perform the core operations
375 // and are also called directly from the network, the function below it is strictly
376 // triggered locally. They all have very similar structure.
377 
editheightxy(bool isfloor,int amount,block & sel)378 void editheightxy(bool isfloor, int amount, block &sel)
379 {
380     loopselxy(sel, if(isfloor)
381     {
382         s->floor += amount;
383         if(s->floor>=s->ceil) s->floor = s->ceil-1;
384     }
385     else
386     {
387         s->ceil += amount;
388         if(s->ceil<=s->floor) s->ceil = s->floor+1;
389     });
390 }
391 
editheight(int * flr,int * amount)392 void editheight(int *flr, int *amount)
393 {
394     EDITSEL;
395     bool isfloor = *flr==0;
396     loopv(sels)
397     {
398         editheightxy(isfloor, *amount, sels[i]);
399         addmsg(SV_EDITH, "ri6", sels[i].x, sels[i].y, sels[i].xs, sels[i].ys, isfloor, *amount);
400     }
401 }
402 
403 COMMAND(editheight, "ii");
404 
405 // texture type : 0 floor, 1 wall, 2 ceil, 3 upper wall
406 
edittexxy(int type,int t,block & sel)407 void edittexxy(int type, int t, block &sel)
408 {
409     loopselxy(sel, switch(type)
410     {
411         case 0: s->ftex = t; break;
412         case 1: s->wtex = t; break;
413         case 2: s->ctex = t; break;
414         case 3: s->utex = t; break;
415     });
416 }
417 
edittex(int type,int dir)418 void edittex(int type, int dir)
419 {
420     EDITSEL;
421     if(type<0 || type>3) return;
422     if(type!=lasttype) { tofronttex(); lasttype = type; }
423     int atype = type==3 ? 1 : type;
424     int i = curedittex[atype];
425     i = i<0 ? 0 : i+dir;
426     curedittex[atype] = i = min(max(i, 0), 255);
427     int t = lasttex = hdr.texlists[atype][i];
428     loopv(sels)
429     {
430         edittexxy(type, t, sels[i]);
431         addmsg(SV_EDITT, "ri6", sels[i].x, sels[i].y, sels[i].xs, sels[i].ys, type, t);
432     }
433 }
434 
settex(int texture,int type)435 void settex(int texture, int type)
436 {
437     EDITSEL;
438     if(type<0 || type>3) return;
439     int atype = type==3 ? 1 : type;
440     int t = -1;
441     loopi(256) if(texture == (int)hdr.texlists[atype][i])
442     {
443         t = (int)hdr.texlists[atype][i];
444         break;
445     }
446     if(t<0)
447     {
448         conoutf("invalid/unavaible texture");
449         return;
450     }
451     loopv(sels)
452     {
453         edittexxy(type, t, sels[i]);
454         addmsg(SV_EDITT, "ri6", sels[i].x, sels[i].y, sels[i].xs, sels[i].ys, type, t);
455     }
456 }
457 
replace()458 void replace()
459 {
460     EDITSELMP;
461     loop(x,ssize) loop(y,ssize)
462     {
463         sqr *s = S(x, y);
464         switch(lasttype)
465         {
466             case 0: if(s->ftex == rtex.ftex) s->ftex = lasttex; break;
467             case 1: if(s->wtex == rtex.wtex) s->wtex = lasttex; break;
468             case 2: if(s->ctex == rtex.ctex) s->ctex = lasttex; break;
469             case 3: if(s->utex == rtex.utex) s->utex = lasttex; break;
470         }
471     }
472     block b = { 0, 0, ssize, ssize };
473     remip(b);
474 }
475 
edittypexy(int type,block & sel)476 void edittypexy(int type, block &sel)
477 {
478     loopselxy(sel, s->type = type);
479 }
480 
edittype(int type)481 void edittype(int type)
482 {
483     EDITSEL;
484     loopv(sels)
485     {
486         block &sel = sels[i];
487         if(type==CORNER && (sel.xs!=sel.ys || sel.xs==3 || (sel.xs>4 && sel.xs!=8)
488                        || sel.x&~-sel.xs || sel.y&~-sel.ys))
489                        { conoutf("corner selection must be power of 2 aligned"); return; }
490         edittypexy(type, sel);
491         addmsg(SV_EDITS, "ri5", sel.x, sel.y, sel.xs, sel.ys, type);
492     }
493 }
494 
heightfield(int * t)495 void heightfield(int *t) { edittype(*t==0 ? FHF : CHF); }
solid(int * t)496 void solid(int *t)       { edittype(*t==0 ? SPACE : SOLID); }
corner()497 void corner()           { edittype(CORNER); }
498 
499 COMMAND(heightfield, "i");
500 COMMAND(solid, "i");
501 COMMAND(corner, "");
502 
editequalisexy(bool isfloor,block & sel)503 void editequalisexy(bool isfloor, block &sel)
504 {
505     int low = 127, hi = -128;
506     loopselxy(sel,
507     {
508         if(s->floor<low) low = s->floor;
509         if(s->ceil>hi) hi = s->ceil;
510     });
511     loopselxy(sel,
512     {
513         if(isfloor) s->floor = low; else s->ceil = hi;
514         if(s->floor>=s->ceil) s->floor = s->ceil-1;
515     });
516 }
517 
equalize(int * flr)518 void equalize(int *flr)
519 {
520     bool isfloor = *flr==0;
521     EDITSEL;
522     loopv(sels)
523     {
524         block &sel = sels[i];
525         editequalisexy(isfloor, sel);
526         addmsg(SV_EDITE, "ri5", sel.x, sel.y, sel.xs, sel.ys, isfloor);
527     }
528 }
529 
530 COMMAND(equalize, "i");
531 
setvdeltaxy(int delta,block & sel)532 void setvdeltaxy(int delta, block &sel)
533 {
534     loopselxy(sel, s->vdelta = max(s->vdelta+delta, 0));
535     remipmore(sel);
536 }
537 
setvdelta(int delta)538 void setvdelta(int delta)
539 {
540     EDITSEL;
541     loopv(sels)
542     {
543         setvdeltaxy(delta, sels[i]);
544         addmsg(SV_EDITD, "ri5", sels[i].x, sels[i].y, sels[i].xs, sels[i].ys, delta);
545     }
546 }
547 
548 const int MAXARCHVERT = 50;
549 int archverts[MAXARCHVERT][MAXARCHVERT];
550 bool archvinit = false;
551 
archvertex(int * span,int * vert,int * delta)552 void archvertex(int *span, int *vert, int *delta)
553 {
554     if(!archvinit)
555     {
556         archvinit = true;
557         loop(s,MAXARCHVERT) loop(v,MAXARCHVERT) archverts[s][v] = 0;
558     }
559     if(*span>=MAXARCHVERT || *vert>=MAXARCHVERT || *span<0 || *vert<0) return;
560     archverts[*span][*vert] = *delta;
561 }
562 
arch(int * sidedelta)563 void arch(int *sidedelta)
564 {
565     EDITSELMP;
566     loopv(sels)
567     {
568         block &sel = sels[i];
569         sel.xs++;
570         sel.ys++;
571         if(sel.xs>MAXARCHVERT) sel.xs = MAXARCHVERT;
572         if(sel.ys>MAXARCHVERT) sel.ys = MAXARCHVERT;
573         loopselxy(sel, s->vdelta =
574             sel.xs>sel.ys
575                 ? (archverts[sel.xs-1][x] + (y==0 || y==sel.ys-1 ? *sidedelta : 0))
576                 : (archverts[sel.ys-1][y] + (x==0 || x==sel.xs-1 ? *sidedelta : 0)));
577         remipmore(sel);
578     }
579 }
580 
slope(int xd,int yd)581 void slope(int xd, int yd)
582 {
583     EDITSELMP;
584     loopv(sels)
585     {
586         block &sel = sels[i];
587         int off = 0;
588         if(xd<0) off -= xd*sel.xs;
589         if(yd<0) off -= yd*sel.ys;
590         sel.xs++;
591         sel.ys++;
592         loopselxy(sel, s->vdelta = xd*x+yd*y+off);
593         remipmore(sel);
594     }
595 }
596 
perlin(int scale,int seed,int psize)597 void perlin(int scale, int seed, int psize)
598 {
599     EDITSELMP;
600     loopv(sels)
601     {
602         block &sel = sels[i];
603         sel.xs++;
604         sel.ys++;
605         makeundo(sel);
606         sel.xs--;
607         sel.ys--;
608         perlinarea(sel, scale, seed, psize);
609         sel.xs++;
610         sel.ys++;
611         remipmore(sel);
612         sel.xs--;
613         sel.ys--;
614     }
615 }
616 
617 VARF(fullbright, 0, 0, 1,
618     if(fullbright)
619     {
620         if(noteditmode("fullbright")) return;
621         fullbrightlight();
622     }
623     else calclight();
624 );
625 
edittag(int * tag)626 void edittag(int *tag)
627 {
628     EDITSELMP;
629     loopselsxy(s->tag = *tag);
630 }
631 
newent(char * what,int * a1,int * a2,int * a3,int * a4)632 void newent(char *what, int *a1, int *a2, int *a3, int *a4)
633 {
634     EDITSEL;
635     loopv(sels) newentity(-1, sels[i].x, sels[i].y, (int)camera1->o.z, what, *a1, *a2, *a3, *a4);
636 }
637 
movemap(int xo,int yo,int zo)638 void movemap(int xo, int yo, int zo) // move whole map
639 {
640     EDITMP;
641     if(!worldbordercheck(MINBORD + max(-xo, 0), MINBORD + max(xo, 0), MINBORD + max(-yo, 0), MINBORD + max(yo, 0), max(zo, 0), max(-zo, 0)))
642     {
643         conoutf("not enough space to move the map");
644         return;
645     }
646     if(xo || yo)
647     {
648         block b = { max(-xo, 0), max(-yo, 0), ssize - iabs(xo), ssize - iabs(yo) }, *cp = blockcopy(b);
649         cp->x = max(xo, 0);
650         cp->y = max(yo, 0);
651         blockpaste(*cp);
652         freeblock(cp);
653     }
654     if(zo)
655     {
656         loop(x, ssize) loop(y, ssize)
657         {
658             S(x,y)->floor = max(-128, S(x,y)->floor + zo);
659             S(x,y)->ceil = min(127, S(x,y)->ceil + zo);
660         }
661         hdr.waterlevel += zo;
662     }
663     loopv(ents)
664     {
665         ents[i].x += xo;
666         ents[i].y += yo;
667         ents[i].z += zo;
668         if(OUTBORD(ents[i].x, ents[i].y)) ents[i].type = NOTUSED;
669     }
670     player1->o.x += xo;
671     player1->o.y += yo;
672     entinmap(player1);
673     calclight();
674     resetmap(false);
675 }
676 
selfliprotate(block & sel,int dir)677 void selfliprotate(block &sel, int dir)
678 {
679     makeundo(sel);
680     block *org = blockcopy(sel);
681     const sqr *q = (const sqr *)(org + 1);
682     int x1 = sel.x, x2 = sel.x + sel.xs - 1, y1 = sel.y, y2 = sel.y + sel.ys - 1, x, y;
683     switch(dir)
684     {
685         case 1: // 90 degree clockwise
686             for(x = x2; x >= x1; x--) for(y = y1; y <= y2; y++) *S(x,y) = *q++;
687             break;
688         case 2: // 180 degree
689             for(y = y2; y >= y1; y--) for(x = x2; x >= x1; x--) *S(x,y) = *q++;
690             break;
691         case 3: // 90 degree counterclockwise
692             for(x = x1; x <= x2; x++) for(y = y2; y >= y1; y--) *S(x,y) = *q++;
693             break;
694         case 11: // flip x-axis
695             for(y = y1; y <= y2; y++) for(x = x2; x >= x1; x--) *S(x,y) = *q++;
696             break;
697         case 12: // flip y-axis
698             for(y = y2; y >= y1; y--) for(x = x1; x <= x2; x++) *S(x,y) = *q++;
699             break;
700     }
701     remipmore(sel);
702     freeblock(org);
703 }
704 
selectionrotate(int dir)705 void selectionrotate(int dir)
706 {
707     EDITSELMP;
708     dir %= 4;
709     if(dir < 0) dir += 4;
710     if(!dir) return;
711     loopv(sels)
712     {
713         block &sel = sels[i];
714         if(sel.xs != sel.ys) dir = 2;
715         selfliprotate(sel, dir);
716     }
717 }
718 
selectionflip(char * axis)719 void selectionflip(char *axis)
720 {
721     EDITSELMP;
722     if(!axis || !*axis) return;
723     char c = toupper(*axis);
724     if(c != 'X' && c != 'Y') return;
725     loopv(sels) selfliprotate(sels[i], c == 'X' ? 11 : 12);
726 }
727 
728 COMMANDF(select, "iiii", (int *x, int *y, int *xs, int *ys) { resetselections(); addselection(*x, *y, *xs, *ys, 0); });
729 COMMANDF(addselection, "iiii", (int *x, int *y, int *xs, int *ys) { addselection(*x, *y, *xs, *ys, 0); });
730 COMMAND(resetselections, "");
731 COMMAND(edittag, "i");
732 COMMAND(replace, "");
733 COMMAND(archvertex, "iii");
734 COMMAND(arch, "i");
735 COMMANDF(slope, "ii", (int *xd, int *yd) { slope(*xd, *yd); });
736 COMMANDF(vdelta, "i", (int *d) { setvdelta(*d); });
737 COMMANDN(undo, editundo, "");
738 COMMAND(copy, "");
739 COMMAND(paste, "");
740 COMMANDF(edittex, "ii", (int *type, int *dir) { edittex(*type, *dir); });
741 COMMAND(newent, "siiii");
742 COMMANDF(perlin, "iii", (int *sc, int *se, int *ps) { perlin(*sc, *se, *ps); });
743 COMMANDF(movemap, "iii", (int *x, int *y, int *z) { movemap(*x, *y, *z); });
744 COMMANDF(selectionrotate, "i", (int *d) { selectionrotate(*d); });
745 COMMAND(selectionflip, "s");
746 COMMAND(countwalls, "i");
747 COMMANDF(settex, "ii", (int *texture, int *type) { settex(*texture, *type); });
748