1 // Hyperbolic Rogue -- map editor and vector graphics editor
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file mapedit.cpp
5  *  \brief HyperRogue editors (map and vector graphics)
6  */
7 
8 #include "hyper.h"
9 namespace hr {
10 
11 EX namespace mapeditor {
12 
13   EX bool drawing_tool;
14   EX bool intexture;
15 
16   #if HDR
17   enum eShapegroup { sgPlayer, sgMonster, sgItem, sgFloor, sgWall };
18   static const int USERSHAPEGROUPS = 5;
19   #endif
20 
21   EX color_t dtfill = 0;
22   EX color_t dtcolor = 0x000000FF;
23   EX ld dtwidth = .02;
24 
25   /* drawing_tool shapes */
26   struct dtshape {
27     cell *where;
28     color_t col, fill;
29     ld lw;
30 
31     virtual void rotate(const transmatrix& T) = 0;
32     virtual void save(hstream& hs) = 0;
33     virtual dtshape* load(hstream& hs) = 0;
34     virtual void draw(const shiftmatrix& V) = 0;
35     virtual ld distance(hyperpoint h) = 0;
~dtshapehr::mapeditor::dtshape36     virtual ~dtshape() {}
37     };
38 
39   struct dtline : dtshape {
40 
41     hyperpoint s, e;
42 
rotatehr::mapeditor::dtline43     void rotate(const transmatrix& T) override {
44       s = T * s;
45       e = T * e;
46       }
savehr::mapeditor::dtline47     void save(hstream& hs) override {
48       hs.write<char>(1);
49       hs.write(s);
50       hs.write(e);
51       }
loadhr::mapeditor::dtline52     dtshape *load(hstream& hs) override {
53       hs.read(s);
54       hs.read(e);
55       return this;
56       }
drawhr::mapeditor::dtline57     void draw(const shiftmatrix& V) override {
58       queueline(V*s, V*e, col, 2 + vid.linequality);
59       }
distancehr::mapeditor::dtline60     ld distance(hyperpoint h) override {
61       return hdist(s, h) + hdist(e, h) - hdist(s, e);
62       }
63     };
64 
65   struct dtcircle : dtshape {
66 
67     hyperpoint s; ld radius;
68 
rotatehr::mapeditor::dtcircle69     void rotate(const transmatrix& T) override {
70       s = T * s;
71       }
savehr::mapeditor::dtcircle72     void save(hstream& hs) override {
73       hs.write<char>(2);
74       hs.write(s);
75       hs.write(radius);
76       }
loadhr::mapeditor::dtcircle77     dtshape *load(hstream& hs) override {
78       hs.read(s);
79       hs.read(radius);
80       return this;
81       }
drawhr::mapeditor::dtcircle82     void draw(const shiftmatrix& V) override {
83       ld len = sin_auto(radius);
84       int ll = ceil(360 * len);
85       shiftmatrix W = V * rgpushxto0(s);
86       for(int i=0; i<=ll; i++)
87         curvepoint(xspinpush0(360*degree*i/ll, radius));
88       queuecurve(W, col, fill, PPR::LINE);
89       }
90 
distancehr::mapeditor::dtcircle91     ld distance(hyperpoint h) override {
92       return abs(hdist(s, h) - radius);
93       }
94     };
95 
96   struct dttext : dtshape {
97     hyperpoint where;
98     ld size;
99     string caption;
100 
rotatehr::mapeditor::dttext101     void rotate(const transmatrix& T) override {
102       where = T * where;
103       }
104 
savehr::mapeditor::dttext105     void save(hstream& hs) override {
106       hs.write<char>(4);
107       hs.write(where);
108       hs.write(size);
109       hs.write(caption);
110       }
111 
loadhr::mapeditor::dttext112     dtshape *load(hstream& hs) override {
113       hs.read(where);
114       hs.read(size);
115       hs.read(caption);
116       return this;
117       }
118 
drawhr::mapeditor::dttext119     void draw(const shiftmatrix& V) override {
120       queuestr(V * rgpushxto0(where), size, caption, col);
121       }
122 
distancehr::mapeditor::dttext123     ld distance(hyperpoint h) override {
124       return hdist(h, where);
125       }
126     };
127 
128   struct dtfree : dtshape {
129 
130     vector<hyperpoint> lh;
131 
rotatehr::mapeditor::dtfree132     void rotate(const transmatrix& T) override {
133       for(auto& h: lh) h = T * h;
134       }
savehr::mapeditor::dtfree135     void save(hstream& hs) override {
136       hs.write<char>(3);
137       hs.write(lh);
138       }
loadhr::mapeditor::dtfree139     dtshape *load(hstream& hs) override {
140       hs.read(lh);
141       return this;
142       }
drawhr::mapeditor::dtfree143     void draw(const shiftmatrix& V) override {
144       for(auto& p: lh) curvepoint(p);
145       queuecurve(V, col, fill, PPR::LINE);
146       }
147 
distancehr::mapeditor::dtfree148     ld distance(hyperpoint h) override {
149       ld mind = 99;
150       for(auto h1: lh) mind = min(mind, hdist(h, h1));
151       return mind;
152       }
153     };
154 
155   vector<unique_ptr<dtshape>> dtshapes;
156 
clear_dtshapes()157   EX void clear_dtshapes() { dtshapes.clear(); }
158 
draw_dtshapes()159   EX void draw_dtshapes() {
160 #if CAP_EDIT
161     for(auto& shp: dtshapes) {
162       if(shp == nullptr) continue;
163       auto& sh = *shp;
164       cell*& c = sh.where;
165       for(const shiftmatrix& V: current_display->all_drawn_copies[c]) {
166         dynamicval<ld> lw(vid.linewidth, vid.linewidth * sh.lw);
167         sh.draw(V);
168         }
169       }
170 
171     if(drawing_tool && (cmode & sm::DRAW)) {
172       shiftpoint moh = GDIM == 2 ? mouseh : find_mouseh3();
173       dynamicval<ld> lw(vid.linewidth, vid.linewidth * dtwidth * 100);
174       if(holdmouse && mousekey == 'c')
175         queue_hcircle(rgpushxto0(lstart), hdist(lstart, moh));
176       else if(holdmouse && mousekey == 'l')
177         queueline(lstart, moh, dtcolor, 4 + vid.linequality, PPR::LINE);
178       else if(!holdmouse) {
179         shiftmatrix T = rgpushxto0(moh);
180         queueline(T * xpush0(-.1), T * xpush0(.1), dtcolor);
181         queueline(T * ypush0(-.1), T * ypush0(.1), dtcolor);
182         }
183       }
184 #endif
185     }
186 
187   /** dtshapes takes ownership of sh */
dt_add(cell * where,dtshape * sh)188   void dt_add(cell *where, dtshape *sh) {
189     sh->where = where;
190     sh->col = dtcolor;
191     sh->fill = dtfill;
192     sh->lw = dtwidth * 100;
193 
194     dtshapes.push_back(unique_ptr<dtshape>(sh));
195     }
196 
dt_add_line(shiftpoint h1,shiftpoint h2,int maxl)197   EX void dt_add_line(shiftpoint h1, shiftpoint h2, int maxl) {
198     if(hdist(h1, h2) > 1 && maxl > 0) {
199       shiftpoint h3 = mid(h1, h2);
200       dt_add_line(h1, h3, maxl-1);
201       dt_add_line(h3, h2, maxl-1);
202       return;
203       }
204     cell *b = centerover;
205 
206     auto xh1 = inverse_shift(ggmatrix(b), h1);
207     virtualRebase(b, xh1);
208 
209     auto l = new dtline;
210     l->s = xh1;
211     l->e = inverse_shift(ggmatrix(b), h2);
212     dt_add(b, l);
213     }
214 
dt_add_circle(shiftpoint h1,shiftpoint h2)215   EX void dt_add_circle(shiftpoint h1, shiftpoint h2) {
216     cell *b = centerover;
217 
218     auto d = hdist(h1, h2);
219 
220     auto xh1 = inverse_shift(ggmatrix(b), h1);
221     virtualRebase(b, xh1);
222 
223     auto l = new dtcircle;
224     l->s = xh1;
225     l->radius = d;
226     dt_add(b, l);
227     }
228 
dt_add_text(shiftpoint h,ld size,string cap)229   EX void dt_add_text(shiftpoint h, ld size, string cap) {
230     cell *b = centerover;
231 
232     auto xh = inverse_shift(ggmatrix(b), h);
233     virtualRebase(b, xh);
234 
235     auto l = new dttext;
236     l->where = xh;
237     l->size = size;
238     l->caption = cap;
239     dt_add(b, l);
240     l->col >>= 8;
241     }
242 
load_shape(hstream & hs)243   dtshape *load_shape(hstream& hs) {
244     char type = hs.get<char>();
245     switch(type) {
246       case 1:
247         return (new dtline)->load(hs);
248       case 2:
249         return (new dtcircle)->load(hs);
250       case 3:
251         return (new dtfree)->load(hs);
252       case 4:
253         return (new dttext)->load(hs);
254       default:
255         return nullptr;
256       }
257     }
258 
259   dtfree *cfree;
260   cell *cfree_at;
261 
dt_finish()262   EX void dt_finish() {
263     cfree = nullptr;
264     cfree_at = nullptr;
265     }
266 
dt_add_free(shiftpoint h)267   EX void dt_add_free(shiftpoint h) {
268 
269     cell *b = centerover;
270     shiftmatrix T = rgpushxto0(h);
271     auto T1 = inverse_shift(ggmatrix(b), T);
272     virtualRebase(b, T1);
273 
274     if(cfree)
275       cfree->lh.push_back(inverse_shift(ggmatrix(cfree_at), tC0(T)));
276 
277     if(b != cfree_at && !(dtfill && cfree_at)) {
278       cfree = new dtfree;
279       dt_add(b, cfree);
280       cfree->lh.push_back(tC0(T1));
281       cfree_at = b;
282       }
283     }
284 
dt_erase(shiftpoint h)285   EX void dt_erase(shiftpoint h) {
286     ld nearest = 1;
287     int nearest_id = -1;
288     int id = -1;
289     for(auto& shp: dtshapes) {
290       id++;
291       if(shp == nullptr) continue;
292       auto& sh = *shp;
293       cell*& c = sh.where;
294       for(const shiftmatrix& V: current_display->all_drawn_copies[c]) {
295         ld dist = sh.distance(inverse_shift(V, h));
296         if(dist < nearest) nearest = dist, nearest_id = id;
297         }
298       }
299     if(nearest_id >= 0)
300       dtshapes.erase(dtshapes.begin() + nearest_id);
301     }
302 
303   EX shiftpoint lstart;
304   EX hyperpoint lstart_rel;
305   cell *lstartcell;
306   ld front_edit = 0.5;
307   enum class eFront { sphere_camera, sphere_center, equidistants, const_x, const_y };
308   eFront front_config;
309   ld front_step = 0.1;
310 
311   #if HDR
312   struct editwhat {
313     double dist;
314     int rotid, symid, pointid;
315     bool side;
316     cell *c;
317     };
318   #endif
319   EX editwhat ew, ewsearch;
320   EX bool autochoose = ISMOBILE;
321 
scaleall(ld z,bool keep_mouse)322   EX void scaleall(ld z, bool keep_mouse) {
323 
324      if(keep_mouse) {
325        ld mrx = (.0 + mousex - current_display->xcenter) / vpconf.scale;
326        ld mry = (.0 + mousey - current_display->ycenter) / vpconf.scale;
327 
328        if(vid.xres > vid.yres) {
329          vpconf.xposition += (vpconf.scale - vpconf.scale*z) * mrx / current_display->scrsize;
330          vpconf.yposition += (vpconf.scale - vpconf.scale*z) * mry / current_display->scrsize;
331          }
332        }
333 
334      vpconf.scale *= z;
335      // printf("scale = " LDF "\n", vpconf.scale);
336      #if CAP_TEXTURE
337      // display(texture::itt);
338      texture::config.itt = xyscale(texture::config.itt, 1/z);
339      if(false && texture::config.tstate) {
340        calcparam();
341        texture::config.perform_mapping();
342        if(texture::config.tstate == texture::tsAdjusting)
343          texture::config.finish_mapping();
344        }
345      #endif
346      }
347 
348 #if CAP_EDIT
349   EX map<int, cell*> modelcell;
350 
351   void handleKeyMap(int sym, int uni);
352 
applyModelcell(cell * c)353   EX void applyModelcell(cell *c) {
354     if(patterns::whichPattern == 'H') return;
355     auto si = patterns::getpatterninfo0(c);
356     cell *c2 = modelcell[si.id];
357     if(c2) {
358       c->wall = c2->wall;
359       c->land = c2->land;
360       c->monst = c2->monst;
361       c->item = c2->item;
362       c->landparam = c2->landparam;
363       c->wparam = c2->wparam;
364       c->mondir = c2->mondir;
365       c->stuntime = c2->stuntime;
366       c->hitpoints = c2->hitpoints;
367       if(c2->mondir != NODIR) {
368         auto si2 = patterns::getpatterninfo0(c2);
369         c->mondir = gmod(c2->mondir - si2.dir + si.dir, c->type);
370         // todo reflect
371         }
372       }
373     }
374 #endif
375 EX }
376 
377 #if HDR
378 struct hstream;
379 struct fhstream;
380 #endif
381 
382 EX namespace mapstream {
383 #if CAP_EDIT
384 
385   EX std::map<cell*, int> cellids;
386   EX vector<cell*> cellbyid;
387   EX vector<char> relspin;
388 
load_drawing_tool(fhstream & hs)389   void load_drawing_tool(fhstream& hs) {
390     using namespace mapeditor;
391     if(hs.vernum < 0xA82A) return;
392     int i = hs.get<int>();
393     while(i--) {
394       auto sh = load_shape(hs);
395       if(!sh) continue;
396       hs.read(sh->col);
397       hs.read(sh->fill);
398       hs.read(sh->lw);
399       int id = hs.get<int>();
400       sh->where = cellbyid[id];
401       sh->rotate(spin(currentmap->spin_angle(sh->where, relspin[id]) - currentmap->spin_angle(sh->where, 0)));
402       dtshapes.push_back(unique_ptr<dtshape>(sh));
403       }
404     }
405 
save_drawing_tool(hstream & hs)406   void save_drawing_tool(hstream& hs) {
407     using namespace mapeditor;
408     hs.write<int>(isize(dtshapes));
409     for(auto& shp: dtshapes) {
410       if(shp == nullptr) { hs.write<char>(0); }
411       else {
412         auto& sh = *shp;
413         sh.save(hs);
414         hs.write(sh.col);
415         hs.write(sh.fill);
416         hs.write(sh.lw);
417         hs.write(cellids[sh.where]);
418         }
419       }
420     }
421 
422 
addToQueue(cell * c)423   void addToQueue(cell* c) {
424     if(cellids.count(c)) return;
425     int numcells = isize(cellbyid);
426     cellbyid.push_back(c);
427     cellids[c] = numcells;
428     }
429 
fixspin(int rspin,int dir,int t,int vernum)430   EX int fixspin(int rspin, int dir, int t, int vernum) {
431     if(vernum < 11018 && dir == 14)
432       return NODIR;
433     else if(vernum < 11018 && dir == 15)
434       return NOBARRIERS;
435     else if(dir >= 0 && dir < t)
436       return (dir + rspin) % t;
437     else
438       return dir;
439     }
440 #endif
441 
save_geometry(hstream & f)442   EX void save_geometry(hstream& f) {
443     f.write(geometry);
444     char nbtype = char(variation);
445     f.write(nbtype);
446     #if CAP_GP
447     if(GOLDBERG || INVERSE) {
448       f.write(gp::param.first);
449       f.write(gp::param.second);
450       }
451     #endif
452     #if CAP_FIELD
453     if(geometry == gFieldQuotient) {
454       using namespace fieldpattern;
455       f.write(quotient_field_changed);
456       if(quotient_field_changed) {
457         f.write(current_extra);
458         f.write(fgeomextras[current_extra].current_prime_id);
459         }
460       }
461     #endif
462     #if CAP_CRYSTAL
463     if(cryst) {
464       f.write(ginf[gCrystal].sides);
465       if(ginf[gCrystal].sides == 8)
466         f.write(ginf[gCrystal].vertex);
467       }
468     #endif
469     #if CAP_ARCM
470     if(geometry == gArchimedean) f.write(arcm::current.symbol);
471     #endif
472     if(geometry == gArbitrary) {
473       f.write<bool>(rulegen::known());
474       f.write<bool>(arb::convert::in());
475       if(arb::convert::in()) {
476         dynamicval<eGeometry> dg(geometry, arb::convert::base_geometry);
477         dynamicval<eVariation> dv(variation, arb::convert::base_variation);
478         save_geometry(f);
479         }
480       else
481         f.write(arb::current.filename);
482       }
483     if(geometry == gNil) {
484       f.write(S7);
485       f.write(nilv::nilperiod);
486       }
487     #if CAP_SOLV
488     if(geometry == gArnoldCat) {
489       f.write(asonov::period_xy);
490       f.write(asonov::period_z);
491       }
492     #endif
493     if(prod) {
494       f.write(hybrid::csteps);
495       f.write(product::cspin);
496       f.write(product::cmirror);
497       }
498     if(rotspace) {
499       f.write(hybrid::csteps);
500       }
501     if(hybri) {
502       hybrid::in_underlying_geometry([&] { save_geometry(f); });
503       }
504     if(fake::in()) {
505       f.write(fake::around);
506       fake::in_underlying_geometry([&] { save_geometry(f); });
507       }
508     if(bt::in())
509       f.write(vid.binary_width);
510     if(euc::in()) {
511       f.write(euc::eu_input.user_axes);
512       f.write(euc::eu_input.twisted);
513       }
514     f.write(mine_adjacency_rule);
515     }
516 
load_geometry(hstream & f)517   EX void load_geometry(hstream& f) {
518     auto vernum = f.get_vernum();
519     f.read(geometry);
520     char nbtype;
521     f.read(nbtype);
522     variation = eVariation(nbtype);
523     #if CAP_GP
524     if(GOLDBERG || INVERSE) {
525       f.read(gp::param.first);
526       f.read(gp::param.second);
527       }
528     #endif
529     #if CAP_CRYSTAL
530     if(cryst && vernum >= 10504) {
531       int sides;
532       f.read(sides);
533       #if CAP_CRYSTAL
534       crystal::set_crystal(sides);
535       #endif
536       if(sides == 8) {
537         int vertices;
538         eVariation v = variation;
539         f.read(vertices);
540         if(vertices == 3) {
541           set_variation(eVariation::bitruncated);
542           set_variation(v);
543           }
544         }
545       }
546     #endif
547     #if CAP_FIELD
548     if(geometry == gFieldQuotient) {
549       using namespace fieldpattern;
550       f.read(quotient_field_changed);
551       if(quotient_field_changed) {
552         f.read(current_extra);
553         auto& ge = fgeomextras[current_extra];
554         auto& id = ge.current_prime_id;
555         f.read(id);
556         if(vernum < 0xA80C) switch(ge.base) {
557           case gNormal: id++; break;
558           case g45: id++; break;
559           case g46: id+=2; break;
560           case g47: id++; break;
561           default: ;
562           }
563         enableFieldChange();
564         }
565       }
566     #endif
567     if(geometry == gArbitrary) {
568       bool rk = vernum >= 0xA905 && f.get<bool>();
569       bool ac = vernum >= 0xA905 && f.get<bool>();
570       if(ac) {
571         load_geometry(f);
572         arb::convert::convert();
573         arb::convert::activate();
574         }
575       else {
576         string s;
577         f.read(s);
578         arb::run(s);
579         stop_game();
580         }
581       if(rk) rulegen::prepare_rules();
582       }
583     #if CAP_ARCM
584     if(geometry == gArchimedean) {
585       string& symbol = arcm::current.symbol;
586       symbol = f.get<string>();
587       arcm::current.parse();
588       if(arcm::current.errors > 0) {
589         printf("Errors! %s\n", arcm::current.errormsg.c_str());
590         }
591       }
592     #endif
593     if(geometry == gNil && vernum >= 0xA80C) {
594       f.read(S7);
595       f.read(nilv::nilperiod);
596       nilv::set_flags();
597       }
598     #if CAP_SOLV
599     if(geometry == gArnoldCat && vernum >= 0xA80C) {
600       f.read(asonov::period_xy);
601       f.read(asonov::period_z);
602       asonov::set_flags();
603       }
604     #endif
605     if(geometry == gProduct && vernum >= 0xA80C) {
606       f.read(hybrid::csteps);
607       if(vernum >= 0xA80D) f.read(product::cspin);
608       if(vernum >= 0xA833) f.read(product::cmirror);
609       }
610     if(geometry == gRotSpace && vernum >= 0xA833) {
611       f.read(hybrid::csteps);
612       }
613     if(hybri && vernum >= 0xA80C) {
614       auto g = geometry;
615       load_geometry(f);
616       set_geometry(g);
617       }
618     if(fake::in()) {
619       ld ar;
620       f.read(ar);
621       fake::around = ar;
622       load_geometry(f);
623       fake::change_around();
624       }
625     if(bt::in() && vernum >= 0xA80C)
626       f.read(vid.binary_width);
627     if(euc::in() && vernum >= 0xA80D) {
628       f.read(euc::eu_input.user_axes);
629       f.read(euc::eu_input.twisted);
630       }
631     if(vernum >= 0xA810)
632       f.read(mine_adjacency_rule);
633     }
634 
635   EX hookset<void(fhstream&)> hooks_savemap, hooks_loadmap_old;
636   EX hookset<void(fhstream&, int)> hooks_loadmap;
637 
save_start()638   EX cell *save_start() {
639     return (bounded || euclid || prod || arcm::in() || INVERSE) ? currentmap->gamestart() : cwt.at->master->c7;
640     }
641 
642 #if CAP_EDIT
save_only_map(fhstream & f)643   void save_only_map(fhstream& f) {
644     f.write(patterns::whichPattern);
645     save_geometry(f);
646 
647     // game settings
648     f.write(safety);
649     f.write(autocheat);
650     f.write(gen_wandering);
651     f.write(reptilecheat);
652     f.write(timerghost);
653     f.write(patterns::canvasback);
654     f.write(patterns::whichShape);
655     f.write(patterns::subpattern_flags);
656     f.write(patterns::whichCanvas);
657     f.write(patterns::displaycodes);
658     f.write(canvas_default_wall);
659     f.write(mapeditor::drawplayer);
660     if(patterns::whichCanvas == 'f') f.write(patterns::color_formula);
661 
662     {
663     int i = ittypes; f.write(i);
664     for(int k=0; k<i; k++) f.write(items[k]);
665     i = motypes; f.write(i);
666     for(int k=0; k<i; k++) f.write(kills[k]);
667     }
668 
669     addToQueue(save_start());
670     for(int i=0; i<isize(cellbyid); i++) {
671       cell *c = cellbyid[i];
672       if(i) {
673         bool ok = false;
674         for(int j=0; j<c->type; j++) if(c->move(j) && cellids.count(c->move(j)) &&
675           cellids[c->move(j)] < i) {
676           int32_t i = cellids[c->move(j)];
677           f.write(i);
678           f.write_char(c->c.spin(j));
679           f.write_char(j);
680           ok = true;
681           break;
682           }
683         if(!ok) {
684           println(hlog, "parent not found for ", c, "!");
685           for(int j=0; j<c->type; j++) println(hlog, j, ": ", c->move(j), "; ", int(cellids.count(c->move(j)) ? cellids[c->move(j)] : -1));
686           throw hr_exception("parent not found");
687           }
688         }
689       f.write_char(c->land);
690       f.write_char(c->mondir);
691       f.write_char(c->monst);
692       if(c->monst == moTortoise)
693         f.write(tortoise::emap[c] = tortoise::getb(c));
694       f.write_char(c->wall);
695       if(dice::on(c)) {
696         auto& dat = dice::data[c];
697         f.write_char(dice::get_die_id(dat.which));
698         f.write_char(dat.val);
699         f.write_char(dat.dir);
700         f.write_char(dat.mirrored);
701         }
702       // f.write_char(c->barleft);
703       // f.write_char(c->barright);
704       f.write_char(c->item);
705       if(c->item == itBabyTortoise)
706         f.write(tortoise::babymap[c]);
707       f.write_char(c->mpdist);
708       // f.write_char(c->bardir);
709       f.write(c->wparam); f.write(c->landparam);
710       f.write_char(c->stuntime); f.write_char(c->hitpoints);
711       for(int j=0; j<c->type; j++) {
712         cell *c2 = c->move(j);
713         if(c2 && c2->land != laNone) addToQueue(c2);
714         }
715       }
716     printf("cells saved = %d\n", isize(cellbyid));
717     int32_t n = -1; f.write(n);
718     int32_t id = cellids.count(cwt.at) ? cellids[cwt.at] : -1;
719     f.write(id);
720 
721     save_drawing_tool(f);
722 
723     f.write(vid.always3);
724     f.write(mutantphase);
725     f.write(rosewave);
726     f.write(rosephase);
727     f.write(turncount);
728     int rms = isize(rosemap); f.write(rms);
729     for(auto p: rosemap) f.write(cellids[p.first]), f.write(p.second);
730     f.write(multi::players);
731     if(multi::players > 1)
732       for(int i=0; i<multi::players; i++)
733         f.write(cellids[multi::player[i].at]);
734 
735     callhooks(hooks_savemap, f);
736     f.write<int>(0);
737 
738     cellids.clear();
739     cellbyid.clear();
740     }
741 
load_usershapes(fhstream & f)742   void load_usershapes(fhstream& f) {
743     if(f.vernum >= 7400) while(true) {
744       int i = f.get<int>();
745       if(i == -1) break;
746       #if CAP_POLY
747       int j = f.get<int>(), l = f.get<int>();
748       if(i >= 4) i = 3;
749       if(i<0 || i >= mapeditor::USERSHAPEGROUPS) break;
750       if(l<0 || l >= USERLAYERS) break;
751 
752       initShape(i, j);
753       usershapelayer& ds(usershapes[i][j]->d[l]);
754 
755       if(f.vernum >= 11008) f.read(ds.zlevel);
756 
757       f.read(ds.sym); f.read(ds.rots); f.read(ds.color);
758       ds.list.clear();
759       int siz = f.get<int>();
760 
761       ds.shift = f.get<hyperpoint>();
762       ds.spin = f.get<hyperpoint>();
763 
764       for(int i=0; i<siz; i++)
765         ds.list.push_back(f.get<hyperpoint>());
766       #else
767       printf("cannot read shapes\n"); exit(1);
768       #endif
769       }
770     }
771 
load_only_map(fhstream & f)772   void load_only_map(fhstream& f) {
773     stop_game();
774     if(f.vernum >= 10420 && f.vernum < 10503) {
775       int i;
776       f.read(i);
777       patterns::whichPattern = patterns::ePattern(i);
778       }
779     else if(f.vernum >= 7400) f.read(patterns::whichPattern);
780 
781     if(f.vernum >= 10203)
782       load_geometry(f);
783 
784     check_cgi();
785     cgi.require_basics();
786 
787     usershape_changes++;
788 
789     initcells();
790     if(shmup::on) shmup::init();
791 
792     if(f.vernum >= 10505) {
793       // game settings
794       f.read(safety);
795       bool b;
796       f.read(b); if(b) autocheat = true;
797       f.read(gen_wandering);
798       f.read(reptilecheat);
799       f.read(timerghost);
800       f.read(patterns::canvasback);
801       f.read(patterns::whichShape);
802       f.read(patterns::subpattern_flags);
803       f.read(patterns::whichCanvas);
804       f.read(patterns::displaycodes);
805       if(f.vernum >= 0xA816)
806         f.read(canvas_default_wall);
807       f.read(mapeditor::drawplayer);
808       if(patterns::whichCanvas == 'f') f.read(patterns::color_formula);
809 
810       int i;
811       f.read(i); if(i > ittypes || i < 0) throw hstream_exception();
812       for(int k=0; k<i; k++) f.read(items[k]);
813       f.read(i); if(i > motypes || i < 0) throw hstream_exception();
814       for(int k=0; k<i; k++) f.read(kills[k]);
815       }
816 
817     int sub = hybri ? 2 : 0;
818     while(true) {
819       cell *c;
820       int rspin;
821 
822       if(isize(cellbyid) == 0) {
823         c = currentmap->gamestart();
824         rspin = 0;
825         }
826       else {
827         int32_t parent = f.get<int>();
828 
829         if(parent<0 || parent >= isize(cellbyid)) break;
830         int dir = f.read_char();
831         cell *c2 = cellbyid[parent];
832         dir = fixspin(relspin[parent], dir, c2->type - sub, f.vernum);
833         c = createMov(c2, dir);
834         // printf("%p:%d,%d -> %p\n", c2, relspin[parent], dir, c);
835 
836         // spinval becomes xspinval
837         rspin = gmod(c2->c.spin(dir) - f.read_char(), c->type - sub);
838         if(GDIM == 3 && rspin && !hybri) {
839           println(hlog, "rspin in 3D");
840           throw hstream_exception();
841           }
842         }
843 
844       cellbyid.push_back(c);
845       relspin.push_back(rspin);
846       c->land = (eLand) f.read_char();
847       c->mondir = fixspin(rspin, f.read_char(), c->type - sub, f.vernum);
848       c->monst = (eMonster) f.read_char();
849       if(c->monst == moTortoise && f.vernum >= 11001)
850         f.read(tortoise::emap[c]);
851       c->wall = (eWall) f.read_char();
852       if(dice::on(c)) {
853         auto& dat = dice::data[c];
854         dat.which = dice::get_by_id(f.read_char());
855         dat.val = f.read_char();
856         dat.dir = fixspin(rspin, f.read_char(), c->type, f.vernum);
857         if(f.vernum >= 0xA902)
858           dat.mirrored = f.read_char();
859         }
860       // c->barleft = (eLand) f.read_char();
861       // c->barright = (eLand) f.read_char();
862       c->item = (eItem) f.read_char();
863       if(c->item == itBabyTortoise && f.vernum >= 11001)
864         f.read(tortoise::babymap[c]);
865       c->mpdist = f.read_char();
866       c->bardir = NOBARRIERS;
867       // fixspin(rspin, f.read_char(), c->type);
868       if(f.vernum < 7400) {
869         short z;
870         f.read(z);
871         c->wparam = z;
872         }
873       else f.read(c->wparam);
874       f.read(c->landparam);
875       // backward compatibility
876       if(f.vernum < 7400 && !isIcyLand(c->land)) c->landparam = HEAT(c);
877       c->stuntime = f.read_char();
878       c->hitpoints = f.read_char();
879 
880       if(patterns::whichPattern)
881         mapeditor::modelcell[patterns::getpatterninfo0(c).id] = c;
882       }
883 
884     int32_t whereami = f.get<int>();
885     if(whereami >= 0 && whereami < isize(cellbyid))
886       cwt.at = cellbyid[whereami];
887     else cwt.at = currentmap->gamestart();
888 
889     for(int i=0; i<isize(cellbyid); i++) {
890       cell *c = cellbyid[i];
891       if(c->bardir != NODIR && c->bardir != NOBARRIERS)
892         extendBarrier(c);
893       }
894 
895     for(int d=BARLEV-1; d>=0; d--)
896     for(int i=0; i<isize(cellbyid); i++) {
897       cell *c = cellbyid[i];
898       if(c->mpdist <= d)
899         for(int j=0; j<c->type; j++) {
900           cell *c2 = createMov(c, j);
901           setdist(c2, d+1, c);
902           }
903       }
904 
905     relspin.clear();
906 
907     if(shmup::on) shmup::init();
908 
909     timerstart = time(NULL); turncount = 0;
910     sagephase = 0; hardcoreAt = 0;
911     timerstopped = false;
912     savecount = 0; savetime = 0;
913     cheater = 1;
914 
915     load_drawing_tool(f);
916 
917     dynamicval<bool> a3(vid.always3, vid.always3);
918     if(f.vernum >= 0xA616) { f.read(vid.always3); geom3::apply_always3(); }
919 
920     if(f.vernum < 0xA61A) load_usershapes(f);
921 
922     if(f.vernum >= 11005) {
923       f.read(mutantphase);
924       f.read(rosewave);
925       f.read(rosephase);
926       f.read(turncount);
927       int i; f.read(i);
928       if(i) havewhat |= HF_ROSE;
929       while(i--) {
930         int cid; int val; f.read(cid); f.read(val);
931         if(cid >= 0 && cid < isize(cellbyid)) rosemap[cellbyid[cid]] = val;
932         }
933       f.read(multi::players);
934       if(multi::players > 1)
935         for(int i=0; i<multi::players; i++) {
936           auto& mp = multi::player[i];
937           int whereami = f.get<int>();
938           if(whereami >= 0 && whereami < isize(cellbyid))
939             mp.at = cellbyid[whereami];
940           else
941             mp.at = currentmap->gamestart();
942           mp.spin = 0,
943           mp.mirrored = false;
944           }
945       }
946 
947     if(f.vernum >= 0xA848) {
948       int i;
949       f.read(i);
950       while(i) {
951         callhooks(hooks_loadmap, f, i);
952         f.read(i);
953         }
954       }
955     else
956       callhooks(hooks_loadmap_old, f);
957 
958     cellbyid.clear();
959     restartGraph();
960     bfs();
961     game_active = true;
962     }
963 
save_usershapes(fhstream & f)964   void save_usershapes(fhstream& f) {
965     int32_t n;
966     #if CAP_POLY
967     for(int i=0; i<mapeditor::USERSHAPEGROUPS; i++) for(auto usp: usershapes[i]) {
968       usershape *us = usp.second;
969       if(!us) continue;
970 
971       for(int l=0; l<USERLAYERS; l++) if(isize(us->d[l].list)) {
972         usershapelayer& ds(us->d[l]);
973         f.write(i); f.write(usp.first); f.write(l);
974         f.write(ds.zlevel);
975         f.write(ds.sym); f.write(ds.rots); f.write(ds.color);
976         n = isize(ds.list); f.write(n);
977         f.write(ds.shift);
978         f.write(ds.spin);
979         for(int i=0; i<isize(ds.list); i++) f.write(ds.list[i]);
980         }
981       }
982     #endif
983     n = -1; f.write(n);
984     }
985 
saveMap(const char * fname)986   EX bool saveMap(const char *fname) {
987     fhstream f(fname, "wb");
988     if(!f.f) return false;
989     f.write(f.vernum);
990     f.write(dual::state);
991     // make sure we save in correct order
992     if(dual::state) dual::switch_to(1);
993     dual::split_or_do([&] { save_only_map(f); });
994     save_usershapes(f);
995     return true;
996     }
997 
loadMap(const string & fname)998   EX bool loadMap(const string& fname) {
999     fhstream f(fname, "rb");
1000     if(!f.f) return false;
1001     f.read(f.vernum);
1002     if(f.vernum > 10505 && f.vernum < 11000)
1003       f.vernum = 11005;
1004     auto ds = dual::state;
1005     if(f.vernum >= 0xA61A)
1006       f.read(ds);
1007     else
1008       ds = 0;
1009     if(ds == 1 && dual::state == 0) dual::enable();
1010     if(ds == 0 && dual::state == 1) dual::disable();
1011     dual::split_or_do([&] { load_only_map(f); });
1012     if(dual::state) dual::assign_landsides();
1013     if(f.vernum >= 0xA61A)
1014       load_usershapes(f);
1015     return true;
1016     }
1017 
1018 #endif
1019 EX }
1020 
1021 EX namespace mapeditor {
1022 
1023   EX bool drawplayer = true;
1024 
1025   EX cell *drawcell;
1026 
1027 #if CAP_EDIT
1028   int paintwhat = 0;
1029   int painttype = 0;
1030   int paintstatueid = 0;
1031   int radius = 0;
1032   string paintwhat_str = "clear monster";
1033 
1034   cellwalker copysource;
1035 
1036   int whichpart;
1037 
1038   const char *mapeditorhelp =
1039     "This mode allows you to edit the map.\n\n"
1040     "NOTE: Use at your own risk. Combinations which never "
1041     "appear in the real game may work in an undefined way "
1042     "(do not work, look strangely, give strange messages, or crash the game).\n\n"
1043     "To get the most of this editor, "
1044     "some knowledge of inner workings of HyperRogue is required. "
1045     "Each cell has four main fields: land type, wall type, monster type, item type. "
1046     "The same wall type (especially \"none\", \"sea\", or \"bonfire\") may look or "
1047     "work a bit differently, based on the land it is in. Sometimes an object may "
1048     "appear twice on the list due to subtle differences (for example, Demons could "
1049     "move next turn or not).\n\n"
1050     "Press w, i, l, or m to choose which aspect of cells to change, "
1051     "then just click on the cells and they will change. Press 'c' while "
1052     "hovering over a cell to copy that cell, this copies all information about it. "
1053     "When copying large areas or placing multi-tile monsters, it might be important where "
1054     "on the cell you are clicking.\n\n"
1055     "You can also press 0-9 to apply your changes to a greater radius. "
1056     "This also affects the copy/paste feature, allowing to copy a larger area.\n\n"
1057     "Press F2 to save the current map (and F3 to load it). If you try this after "
1058     "a long game of HyperRogue (without using Orbs of Safety), the filesize will "
1059     "be very large! "
1060     "Note however that large structures, such as "
1061     "Great Walls, large circles and horocycles, are destroyed by this.\n\n"
1062     "Press 'b' to mark cells as boundaries. Such cells, and cells beyond "
1063     "them, are not copied by the copy/paste feature, nor saved by the "
1064     "save feature.\n\n";
1065 
1066   const char* patthelp =
1067    "Press 'r' to choose a regular pattern. When a pattern is on, "
1068    "editing a cell automatically edits all cells which are "
1069    "equivalent according to this pattern. You can choose from "
1070    "several patterns, and choose which symmetries matter "
1071    "for equivalence. Also, you can press Space to switch between "
1072    "the map and graphics editor quickly -- note that editing floors "
1073    "with the graphics editor also adheres to the pattern.";
1074 
mehelptext()1075   string mehelptext() {
1076     return XLAT(mapeditorhelp) + XLAT(patthelp);
1077     }
1078 
1079   struct undo_info {
1080     cell *c;
1081     eWall w;
1082     eItem i;
1083     eMonster m;
1084     eLand l;
1085     char wparam;
1086     int32_t lparam;
1087     char dir;
1088     };
1089 
1090   vector<undo_info> undo;
1091 
checkEq(undo_info & u)1092   bool checkEq(undo_info& u) {
1093     return u.w == u.c->wall && u.i == u.c->item && u.m == u.c->monst && u.l == u.c->land &&
1094       u.dir == u.c->mondir && u.lparam == u.c->landparam && u.wparam == u.c->wparam;
1095     }
1096 
saveUndo(cell * c)1097   void saveUndo(cell *c) {
1098     undo_info u;
1099     u.c=c; u.w = c->wall; u.i = c->item; u.m = c->monst; u.l = c->land; u.dir = c->mondir;
1100     u.wparam = c->wparam; u.lparam = c->landparam;
1101     undo.push_back(u);
1102     }
1103 
lastUndo()1104   undo_info& lastUndo() { return undo[isize(undo)-1]; }
1105 
undoLock()1106   void undoLock() {
1107     if(!isize(undo) || lastUndo().c) {
1108       undo_info i; i.c = NULL; undo.push_back(i);
1109       }
1110     }
1111 
applyUndo()1112   void applyUndo() {
1113     while(isize(undo) && !lastUndo().c) undo.pop_back();
1114     while(isize(undo)) {
1115       undo_info& i(lastUndo());
1116       if(!i.c) break;
1117       i.c->wall = i.w;
1118       i.c->item = i.i;
1119       i.c->monst = i.m;
1120       i.c->land = i.l;
1121       i.c->mondir = i.dir;
1122       i.c->wparam = i.wparam;
1123       i.c->landparam = i.lparam;
1124       undo.pop_back();
1125       }
1126     }
1127 
checkUndo()1128   void checkUndo() {
1129     if(checkEq(undo[isize(undo)-1])) undo.pop_back();
1130     }
1131 
itc(int k)1132   int itc(int k) {
1133     if(k == 0) return 0;
1134     if(k == 1) return 0x40;
1135     if(k == 2) return 0x80;
1136     if(k == 3) return 0xFF;
1137     return 0x20;
1138     }
1139 
1140   bool choosefile = false;
1141 
editor_fsize()1142   int editor_fsize() {
1143     return min(vid.fsize + 5, (vid.yres - 16) /32 );
1144     }
1145 
displayFunctionKeys()1146   void displayFunctionKeys() {
1147     int fs = editor_fsize();
1148     displayButton(8, vid.yres-8-fs*11, XLAT("F1 = help"), SDLK_F1, 0);
1149     displayButton(8, vid.yres-8-fs*10, XLAT("F2 = save"), SDLK_F2, 0);
1150     displayButton(8, vid.yres-8-fs*9, XLAT("F3 = load"), SDLK_F3, 0);
1151     displayButton(8, vid.yres-8-fs*7, drawing_tool ? XLAT("F5 = clear") : XLAT("F5 = restart"), SDLK_F5, 0);
1152     #if CAP_SHOT
1153     displayButton(8, vid.yres-8-fs*6, XLAT("F6 = HQ shot"), SDLK_F6, 0);
1154     #endif
1155     displayButton(8, vid.yres-8-fs*5, XLAT("F7 = player on/off"), SDLK_F7, 0);
1156     displayButton(8, vid.yres-8-fs*3, XLAT("SPACE = map/graphics"), ' ', 0);
1157     displayButton(8, vid.yres-8-fs*2, XLAT("ESC = return to the game"), SDLK_ESCAPE, 0);
1158     }
1159 
1160   EX set<cell*> affected;
1161   EX set<int> affected_id;
1162 
showMapEditor()1163   EX void showMapEditor() {
1164     cmode = sm::MAP;
1165     gamescreen(0);
1166 
1167     int fs = editor_fsize();
1168 
1169     affected.clear();
1170     affected_id.clear();
1171     if(lmouseover) {
1172       celllister cl(lmouseover, radius, 10000, nullptr);
1173       for(cell *c: cl.lst) affected.insert(c), affected_id.insert(patterns::getpatterninfo0(c).id);
1174       }
1175 
1176     getcstat = '-';
1177 
1178     displayfr(8, 8 + fs, 2, vid.fsize, paintwhat_str, forecolor, 0);
1179     displayfr(8, 8+fs*2, 2, vid.fsize, XLAT("use at your own risk!"), 0x800000, 0);
1180 
1181     displayButton(8, 8+fs*4, XLAT("0-9 = radius (%1)", its(radius)), ('0' + (radius+1)%10), 0);
1182     displayButton(8, 8+fs*5, XLAT("b = boundary"), 'b', 0);
1183     displayButton(8, 8+fs*6, XLAT("m = monsters"), 'm', 0);
1184     displayButton(8, 8+fs*7, XLAT("w = walls"), 'w', 0);
1185     displayButton(8, 8+fs*8, XLAT("i = items"), 'i', 0);
1186     displayButton(8, 8+fs*9, XLAT("l = lands"), 'l', 0);
1187     displayButton(8, 8+fs*10, XLAT("c = copy"), 'c', 0);
1188     displayButton(8, 8+fs*11, XLAT("u = undo"), 'u', 0);
1189     if(painttype == 4)
1190       displayButton(8, 8+fs*12, XLAT("f = flip %1", ONOFF(copysource.mirrored)), 'u', 0);
1191     displayButton(8, 8+fs*13, XLAT("r = regular"), 'r', 0);
1192     displayButton(8, 8+fs*14, XLAT("p = paint"), 'p', 0);
1193 
1194     displayFunctionKeys();
1195     displayButton(8, vid.yres-8-fs*4, XLAT("F8 = settings"), SDLK_F8, 0);
1196 
1197     keyhandler = handleKeyMap;
1198     }
1199 
spillinc()1200   int spillinc() {
1201     if(radius>=2) return 0;
1202     if(anyshiftclick) return -1;
1203     return 1;
1204     }
1205 
drawcellShapeGroup()1206   EX eShapegroup drawcellShapeGroup() {
1207     if(drawcell == cwt.at && drawplayer) return sgPlayer;
1208     if(drawcell->wall == waEditStatue) return sgWall;
1209     if(drawcell->monst) return sgMonster;
1210     if(drawcell->item) return sgItem;
1211     return sgFloor;
1212     }
1213 
drawcellShapeID()1214   EX int drawcellShapeID() {
1215     if(drawcell == cwt.at && drawplayer) return vid.cs.charid;
1216     if(drawcell->wall == waEditStatue) return drawcell->wparam;
1217     if(drawcell->monst) return drawcell->monst;
1218     if(drawcell->item) return drawcell->item;
1219     auto si = patterns::getpatterninfo0(drawcell);
1220     return si.id;
1221     }
1222 
editingShape(eShapegroup group,int id)1223   bool editingShape(eShapegroup group, int id) {
1224     if(group != mapeditor::drawcellShapeGroup()) return false;
1225     return id == drawcellShapeID();
1226     }
1227 
editCell(const pair<cellwalker,cellwalker> & where)1228   void editCell(const pair<cellwalker, cellwalker>& where) {
1229     cell *c = where.first.at;
1230     int cdir = where.first.spin;
1231     saveUndo(c);
1232     switch(painttype) {
1233       case 0: {
1234         eMonster last = c->monst;
1235         c->monst = eMonster(paintwhat);
1236         c->hitpoints = 3;
1237         c->stuntime = 0;
1238         c->mondir = cdir;
1239 
1240         if((isWorm(c) || isIvy(c) || isMutantIvy(c)) && c->move(cdir) &&
1241           !isWorm(c->move(cdir)) && !isIvy(c->move(cdir)))
1242           c->mondir = NODIR;
1243 
1244         if(c->monst == moMimic) {
1245           c->monst = moNone;
1246           mirror::createMirror(cellwalker(c, cdir, true), 0);
1247           c->monst = moMimic;
1248           }
1249 
1250         if(c->monst ==moTortoise && last == moTortoise) {
1251           cell *c1 = c;
1252           for(int i=0; i<100; i++) c1 = c1->cmove(hrand(c1->type));
1253           tortoise::emap[c] = tortoise::getRandomBits();
1254           }
1255 
1256         if(isDie(c->monst)) {
1257           if(!dice::generate_random(c)) c->monst = moNone;
1258           }
1259         break;
1260         }
1261       case 1: {
1262         eItem last = c->item;
1263         c->item = eItem(paintwhat);
1264         if(c->item == itBabyTortoise)
1265           tortoise::babymap[c] = getBits(c) ^ (last == itBabyTortoise ? tortoise::getRandomBits() : 0);
1266         break;
1267         }
1268       case 2: {
1269         eLand last = c->land;
1270         c->land = eLand(paintwhat);
1271         if(isIcyLand(c) && isIcyLand(last))
1272            HEAT(c) += spillinc() / 100.;
1273         else if(last == laDryForest && c->land == laDryForest)
1274           c->landparam += spillinc();
1275         else if(last == laOcean && c->land == laOcean)
1276           c->landparam += spillinc();
1277         else if(last == laHive && c->land == laHive)
1278           c->landparam += spillinc();
1279         else
1280           c->landparam = 0;
1281         break;
1282         }
1283       case 3: {
1284         eWall last = c->wall;
1285         c->wall = eWall(paintwhat);
1286 
1287         if(last != c->wall) {
1288           if(hasTimeout(c))
1289             c->wparam = 10;
1290           else if(c->wall == waWaxWall)
1291             c->landparam = hrand(0xFFFFFF + 1);
1292           }
1293         else if(hasTimeout(c))
1294           c->wparam += spillinc();
1295 
1296         if(c->wall == waEditStatue) {
1297           c->wparam = paintstatueid;
1298           c->mondir = cdir;
1299           }
1300 
1301         if(isDie(c->wall)) {
1302           if(!dice::generate_random(c)) c->wall = waNone;
1303           }
1304 
1305         break;
1306         }
1307       case 5:
1308         c->land = laNone;
1309         c->wall = waNone;
1310         c->item = itNone;
1311         c->monst = moNone;
1312         c->landparam = 0;
1313         // c->tmp = -1;
1314         break;
1315       case 6:
1316         c->land = laCanvas;
1317         c->wall = GDIM == 3 ? waWaxWall : waNone;
1318         c->landparam = paintwhat >> 8;
1319         break;
1320       case 4: {
1321         cell *copywhat = where.second.at;
1322         c->wall = copywhat->wall;
1323         c->item = copywhat->item;
1324         c->land = copywhat->land;
1325         c->monst = copywhat->monst;
1326         c->landparam = copywhat->landparam;
1327         c->wparam = copywhat->wparam;
1328         c->hitpoints = copywhat->hitpoints;
1329         c->stuntime = copywhat->stuntime;
1330         if(dice::on(c)) dice::data[c] = dice::data[copywhat];
1331         if(copywhat->mondir == NODIR) c->mondir = NODIR;
1332         else c->mondir = gmod((where.first.mirrored == where.second.mirrored ? 1 : -1) * (copywhat->mondir - where.second.spin) + cdir, c->type);
1333         break;
1334         }
1335       case 7:
1336         if(c) {
1337           copysource = c;
1338           painttype = 4;
1339           paintwhat_str = XLAT("copying");
1340           }
1341         break;
1342       }
1343     checkUndo();
1344     }
1345 
1346   vector<pair<cellwalker, cellwalker> > spill_list;
1347 
list_spill(cellwalker tgt,cellwalker src,manual_celllister & cl)1348   void list_spill(cellwalker tgt, cellwalker src, manual_celllister& cl) {
1349     spill_list.clear();
1350     spill_list.emplace_back(tgt, src);
1351     if(painttype == 7) return;
1352     int crad = 0, nextstepat = 0;
1353     for(int i=0; i<isize(spill_list); i++) {
1354       if(i == nextstepat) {
1355         crad++; nextstepat = isize(spill_list);
1356         if(crad > radius) break;
1357         }
1358       auto sd = spill_list[i];
1359       for(int i=0; i<sd.first.at->type; i++) {
1360         auto sd2 = sd;
1361         sd2.first = sd2.first + i + wstep;
1362         if(!cl.add(sd2.first.at)) continue;
1363         if(sd2.second.at) {
1364           sd2.second = sd2.second + i + wstep;
1365           if(sd2.second.at->land == laNone) continue;
1366           }
1367         spill_list.push_back(sd2);
1368         }
1369       }
1370     }
1371 
editAt(cellwalker where,manual_celllister & cl)1372   void editAt(cellwalker where, manual_celllister& cl) {
1373 
1374     if(painttype == 4 && radius) {
1375       if(where.at->type != copysource.at->type) return;
1376       if(where.spin<0) where.spin=0;
1377       if(BITRUNCATED && !ctof(mouseover) && ((where.spin&1) != (copysource.spin&1)))
1378         where += 1;
1379       }
1380     if(painttype != 4) copysource.at = NULL;
1381     list_spill(where, copysource, cl);
1382 
1383     for(auto& st: spill_list)
1384       editCell(st);
1385     }
1386 
allInPattern(cellwalker where)1387   void allInPattern(cellwalker where) {
1388 
1389     manual_celllister cl;
1390     if(!patterns::whichPattern) {
1391       editAt(where, cl);
1392       return;
1393       }
1394 
1395     cl.add(where.at);
1396 
1397     int at = 0;
1398     while(at < isize(cl.lst)) {
1399       cell *c2 = cl.lst[at];
1400       at++;
1401 
1402       forCellEx(c3, c2) cl.add(c3);
1403       }
1404 
1405     auto si = patterns::getpatterninfo0(where.at);
1406     int cdir = where.spin;
1407     if(cdir >= 0) cdir = cdir - si.dir;
1408 
1409     for(cell* c2: cl.lst) {
1410       auto si2 = patterns::getpatterninfo0(c2);
1411       if(si2.id == si.id) {
1412         editAt(cellwalker(c2, cdir>=0 ? cdir + si2.dir : -1), cl);
1413         modelcell[si2.id] = c2;
1414         }
1415       }
1416     }
1417 
mouseover_cw(bool fix)1418   cellwalker mouseover_cw(bool fix) {
1419     int d = neighborId(mouseover, mouseover2);
1420     if(d == -1 && fix) d = hrand(mouseover->type);
1421     return cellwalker(mouseover, d);
1422     }
1423 
save_level()1424   void save_level() {
1425     #if ISWEB
1426     mapstream::saveMap("web.lev");
1427     offer_download("web.lev", "mime/type");
1428     #else
1429     dialog::openFileDialog(levelfile, XLAT("level to save:"), ".lev", [] () {
1430       if(mapstream::saveMap(levelfile.c_str())) {
1431         addMessage(XLAT("Map saved to %1", levelfile));
1432         return true;
1433         }
1434       else {
1435         addMessage(XLAT("Failed to save map to %1", levelfile));
1436         return false;
1437         }
1438       });
1439     #endif
1440     }
1441 
load_level()1442   void load_level() {
1443     #if ISWEB
1444     offer_choose_file([] {
1445       mapstream::loadMap("data.txt");
1446       });
1447     #else
1448     dialog::openFileDialog(levelfile, XLAT("level to load:"), ".lev", [] () {
1449       if(mapstream::loadMap(levelfile.c_str())) {
1450         addMessage(XLAT("Map loaded from %1", levelfile));
1451         return true;
1452         }
1453       else {
1454         addMessage(XLAT("Failed to load map from %1", levelfile));
1455         return false;
1456         }
1457       });
1458     #endif
1459     }
1460 
showList()1461   void showList() {
1462     dialog::v.clear();
1463     if(painttype == 4) painttype = 0;
1464     switch(painttype) {
1465       case 0:
1466         for(int i=0; i<motypes; i++) {
1467           eMonster m = eMonster(i);
1468           if(
1469             m == moTongue || m == moPlayer || m == moFireball || m == moBullet ||
1470             m == moFlailBullet || m == moShadow || m == moAirball ||
1471             m == moWolfMoved || m == moGolemMoved ||
1472             m == moTameBomberbirdMoved || m == moKnightMoved ||
1473             m == moDeadBug || m == moLightningBolt || m == moDeadBird ||
1474             m == moMouseMoved || m == moPrincessMoved || m == moPrincessArmedMoved) ;
1475           else if(m == moDragonHead) dialog::vpush(i, "Dragon Head");
1476           else dialog::vpush(i, minf[i].name);
1477           }
1478         break;
1479       case 1:
1480         for(int i=0; i<ittypes; i++) dialog::vpush(i, iinf[i].name);
1481         break;
1482       case 2:
1483         for(int i=0; i<landtypes; i++) dialog::vpush(i, linf[i].name);
1484         break;
1485       case 3:
1486         for(int i=0; i<walltypes; i++) if(i != waChasmD) dialog::vpush(i, winf[i].name);
1487         break;
1488       }
1489     // sort(v.begin(), v.end());
1490 
1491     if(dialog::infix != "") mouseovers = dialog::infix;
1492 
1493     int q = dialog::v.size();
1494     int percolumn = vid.yres / (vid.fsize+5) - 4;
1495     int columns = 1 + (q-1) / percolumn;
1496 
1497     for(int i=0; i<q; i++) {
1498       int x = 16 + (vid.xres * (i/percolumn)) / columns;
1499       int y = (vid.fsize+5) * (i % percolumn) + vid.fsize*2;
1500 
1501       int actkey = 1000 + i;
1502       string vv = dialog::v[i].first;
1503       if(i < 9) { vv += ": "; vv += ('1' + i); }
1504 
1505       displayButton(x, y, vv, actkey, 0);
1506       }
1507     keyhandler = [] (int sym, int uni) {
1508       if(uni >= '1' && uni <= '9') uni = 1000 + uni - '1';
1509       if(sym == SDLK_RETURN || sym == SDLK_KP_ENTER || sym == '-' || sym == SDLK_KP_MINUS) uni = 1000;
1510       for(int z=0; z<isize(dialog::v); z++) if(1000 + z == uni) {
1511         paintwhat = dialog::v[z].second;
1512         paintwhat_str = dialog::v[z].first;
1513 
1514         mousepressed = false;
1515         popScreen();
1516 
1517         if(painttype == 3 && paintwhat == waEditStatue)
1518           dialog::editNumber(paintstatueid, 0, 127, 1, 1, XLAT1("editable statue"),
1519             XLAT("These statues are designed to have their graphics edited in the Vector Graphics Editor. Each number has its own, separate graphics.")
1520             );
1521         return;
1522         }
1523       if(dialog::editInfix(uni)) ;
1524       else if(doexiton(sym, uni)) popScreen();
1525       };
1526     }
1527 
handleKeyMap(int sym,int uni)1528   void handleKeyMap(int sym, int uni) {
1529     handlePanning(sym, uni);
1530 
1531     // left-clicks are coded with '-', and right-clicks are coded with sym F1
1532     if(uni == '-' && !holdmouse) undoLock();
1533     if(uni == '-' && mouseover) {
1534       allInPattern(mouseover_cw(false));
1535       holdmouse = true;
1536       }
1537 
1538     if(mouseover) for(int i=0; i<mouseover->type; i++) createMov(mouseover, i);
1539     if(uni == 'u') applyUndo();
1540     else if(uni == 'v' || sym == SDLK_F10 || sym == SDLK_ESCAPE) popScreen();
1541     else if(uni >= '0' && uni <= '9') radius = uni - '0';
1542     else if(uni == 'm') pushScreen(showList), painttype = 0, dialog::infix = "";
1543     else if(uni == 'i') pushScreen(showList), painttype = 1, dialog::infix = "";
1544     else if(uni == 'l') pushScreen(showList), painttype = 2, dialog::infix = "";
1545     else if(uni == 'w') pushScreen(showList), painttype = 3, dialog::infix = "";
1546     else if(uni == 'r') pushScreen(patterns::showPattern);
1547     else if(uni == 't' && mouseover) {
1548       playermoved = true;
1549       cwt = mouseover_cw(true);
1550       }
1551     else if(uni == 'b') painttype = 5, paintwhat_str = XLAT("boundary");
1552     else if(uni == 'p') {
1553       painttype = 6;
1554       paintwhat_str = "paint";
1555       dialog::openColorDialog((unsigned&)(paintwhat = (painttype ==6 ? paintwhat : 0x808080)));
1556       }
1557     else if(uni == 'G')
1558       push_debug_screen();
1559     else if(sym == SDLK_F5) {
1560       dialog::push_confirm_dialog([] { restart_game(); }, XLAT("Are you sure you want to clear the map?"));
1561       }
1562     else if(sym == SDLK_F2) save_level();
1563     else if(sym == SDLK_F3) load_level();
1564 #if CAP_SHOT
1565     else if(sym == SDLK_F6) {
1566       pushScreen(shot::menu);
1567       }
1568 #endif
1569     else if(sym == SDLK_F7) {
1570       drawplayer = !drawplayer;
1571       }
1572     else if(sym == SDLK_F8) {
1573       pushScreen(map_settings);
1574       }
1575     else if(uni == 'c' && mouseover) {
1576       copysource = mouseover_cw(true);
1577       painttype = 4;
1578       paintwhat_str = XLAT("copying");
1579       }
1580     else if(uni == 'c') {
1581       painttype = 7;
1582       paintwhat_str = XLAT("select area to copy");
1583       }
1584     else if(uni == 'f') {
1585       copysource.mirrored = !copysource.mirrored;
1586       }
1587     else if(uni == 'h' || sym == SDLK_F1)
1588       gotoHelp(mehelptext());
1589     else if(uni == ' ') {
1590       popScreen();
1591       pushScreen(showDrawEditor);
1592       initdraw(mouseover ? mouseover : cwt.at);
1593       }
1594     }
1595 
1596 // VECTOR GRAPHICS EDITOR
1597 
1598   const char* drawhelp =
1599    "In this mode you can draw your own player characters, "
1600    "floors, monsters, and items. Press 'e' while hovering over "
1601    "an object to edit it. Start drawing shapes with 'n', and "
1602    "add extra vertices with 'a'. Press 0-9 to draw symmetric "
1603    "pictures easily. More complex pictures can "
1604    "be created by using several layers ('l'). See the edges of "
1605    "the screen for more keys.";
1606 
drawhelptext()1607   string drawhelptext() {
1608     return XLAT(drawhelp);
1609     }
1610 
1611   int dslayer;
1612   bool coloring;
1613   color_t colortouse = 0xC0C0C0FFu;
1614   // fake key sent to change the color
1615   static const int COLORKEY = (-10000);
1616 
1617   EX shiftmatrix drawtrans, drawtransnew;
1618 
1619   #if CAP_POLY
loadShape(int sg,int id,hpcshape & sh,int d,int layer)1620   void loadShape(int sg, int id, hpcshape& sh, int d, int layer) {
1621     usershapelayer *dsCur = &usershapes[sg][id]->d[layer];
1622     dsCur->list.clear();
1623     dsCur->sym = d==2;
1624     for(int i=sh.s; i < sh.s + (sh.e-sh.s)/d; i++)
1625       dsCur->list.push_back(cgi.hpc[i]);
1626     }
1627   #endif
1628 
drawGhosts(cell * c,const shiftmatrix & V,int ct)1629   EX void drawGhosts(cell *c, const shiftmatrix& V, int ct) {
1630     if(!(cmode & sm::MAP)) return;
1631     if(darken != 0) return;
1632     if(GDIM == 2 && mouseout()) return;
1633     if(patterns::whichPattern) {
1634       if(!affected_id.count(patterns::getpatterninfo0(c).id)) return;
1635       }
1636     else {
1637       if(!affected.count(c)) return;
1638       }
1639     queuecircleat1(c, V, .78, 0x00FFFFFF);
1640     }
1641 
1642   hyperpoint ccenter = C02;
1643   hyperpoint coldcenter = C02;
1644 
1645   unsigned gridcolor = 0xC0C0C040;
1646 
in_front_dist(ld d)1647   shiftpoint in_front_dist(ld d) {
1648 
1649     ld ys = current_display->xsize/2;
1650     double mx = current_display->tanfov * (mousex - current_display->xcenter)/ys;
1651     double my = current_display->tanfov * (mousey - current_display->ycenter)/ys/pconf.stretch;
1652     hyperpoint tgt = point3(mx, my, 1);
1653     tgt *= d / hypot_d(3, tgt);
1654 
1655     return shiftless(direct_exp(lp_iapply(tgt))); /* todo direct_shift */
1656     }
1657 
find_mouseh3()1658   EX shiftpoint find_mouseh3() {
1659     if(vrhr::active())
1660       return mouseh;
1661     if(front_config == eFront::sphere_camera)
1662       return in_front_dist(front_edit);
1663     ld step = 0.01;
1664     ld cdist = 0;
1665 
1666     auto idt = z_inverse(unshift(drawtrans));
1667 
1668     auto qu = [&] (ld d) {
1669       ld d1 = front_edit;
1670       shiftpoint h1 = in_front_dist(d);
1671       if(front_config == eFront::sphere_center)
1672         d1 = geo_dist(drawtrans * C0, h1);
1673       if(front_config == eFront::equidistants) {
1674         hyperpoint h = idt * unshift(in_front_dist(d));
1675         d1 = asin_auto(h[2]);
1676         }
1677       if(front_config == eFront::const_x) {
1678         hyperpoint h = idt * unshift(in_front_dist(d));
1679         d1 = asin_auto(h[0]);
1680         }
1681       if(front_config == eFront::const_y) {
1682         hyperpoint h = idt * unshift(in_front_dist(d));
1683         d1 = asin_auto(h[1]);
1684         }
1685       return pow(d1 - front_edit, 2);
1686       };
1687 
1688     ld bq = qu(cdist);
1689     while(abs(step) > 1e-10) {
1690       ld cq = qu(cdist + step);
1691       if(cq < bq) cdist += step, bq = cq;
1692       else step *= -.5;
1693       }
1694     return in_front_dist(cdist);
1695     }
1696 
1697   int parallels = 12, meridians = 6;
1698   ld equi_range = 1;
1699 
drawGrid()1700   EX void drawGrid() {
1701     if(!drawcell) drawcell = cwt.at;
1702     color_t lightgrid = gridcolor;
1703     lightgrid -= (lightgrid & 0xFF) / 2;
1704     shiftmatrix d2 = drawtrans * rgpushxto0(ccenter) * rspintox(gpushxto0(ccenter) * coldcenter);
1705 
1706     if(GDIM == 3) {
1707       queuecircleat(mapeditor::drawcell, 1, 0x80D080FF);
1708       color_t cols[4] = { 0x80D080FF, 0x80D080FF, 0xFFFFFF40, 0x00000040 };
1709       if(true) {
1710         shiftmatrix t = rgpushxto0(find_mouseh3());
1711         for(int i=0; i<4; i++)
1712           queueline(t * cpush0(i&1, 0.1), t * cpush0(i&1, -0.1), cols[i], -1, i < 2 ? PPR::LINE : PPR::SUPERLINE);
1713         }
1714       if(front_config == eFront::sphere_center) for(int i=0; i<4; i+=2) {
1715         auto pt = [&] (ld a, ld b) {
1716           return direct_exp(spin(a*degree) * cspin(0, 2, b*degree) * xtangent(front_edit));
1717           };
1718         for(int ai=0; ai<parallels; ai++) {
1719           ld a = ai * 360 / parallels;
1720           for(int b=-90; b<90; b+=5) curvepoint(pt(a,b));
1721           queuecurve(d2, cols[i + ((ai*4) % parallels != 0)], 0, i < 2 ? PPR::LINE : PPR::SUPERLINE);
1722           }
1723         for(int bi=1-meridians; bi<meridians; bi++) {
1724           ld b = 90 * bi / meridians;
1725           for(int a=0; a<=360; a+=5) curvepoint(pt(a, b));
1726           queuecurve(d2, cols[i + (bi != 0)], 0, i < 2 ? PPR::LINE : PPR::SUPERLINE);
1727           }
1728         }
1729       transmatrix T;
1730       if(front_config == eFront::equidistants) T = Id;
1731       else if(front_config == eFront::const_x) T = cspin(2, 0, M_PI/2);
1732       else if(front_config == eFront::const_y) T = cspin(2, 1, M_PI/2);
1733       else return;
1734       for(int i=0; i<4; i+=2) {
1735         for(int u=2; u<=20; u++) {
1736           PRING(d) {
1737             curvepoint(T * xspinpush(M_PI*d/cgi.S42, u/20.) * zpush0(front_edit));
1738             }
1739           queuecurve(d2, cols[i + (u%5 != 0)], 0, i < 2 ? PPR::LINE : PPR::SUPERLINE);
1740           }
1741         for(int d=0; d<cgi.S84; d++) {
1742           for(int u=0; u<=20; u++) curvepoint(T * xspinpush(M_PI*d/cgi.S42, u/20.) * zpush(front_edit) * C0);
1743           queuecurve(d2, cols[i + (d % (cgi.S84/drawcell->type) != 0)], 0, i < 2 ? PPR::LINE : PPR::SUPERLINE);
1744           }
1745         }
1746       return;
1747       }
1748 
1749     for(int d=0; d<cgi.S84; d++) {
1750       unsigned col = (d % (cgi.S84/drawcell->type) == 0) ? gridcolor : lightgrid;
1751       queueline(d2 * C0, d2 * xspinpush0(M_PI*d/cgi.S42, 1), col, 4 + vid.linequality);
1752       }
1753     for(int u=2; u<=20; u++) {
1754       PRING(d) {
1755         curvepoint(xspinpush0(M_PI*d/cgi.S42, u/20.));
1756         }
1757       queuecurve(d2, (u%5==0) ? gridcolor : lightgrid, 0, PPR::LINE);
1758       }
1759     queueline(drawtrans*ccenter, drawtrans*coldcenter, gridcolor, 4 + vid.linequality);
1760     }
1761 
1762   void drawHandleKey(int sym, int uni);
1763 
1764   static ld brush_sizes[10] = {
1765     0.001, 0.002, 0.005, 0.0075, 0.01, 0.015, 0.02, 0.05, 0.075, 0.1};
1766 
1767   static unsigned texture_colors[] = {
1768     11,
1769     0x000000FF,
1770     0xFFFFFFFF,
1771     0xFF0000FF,
1772     0xFFFF00FF,
1773     0x00FF00FF,
1774     0x00FFFFFF,
1775     0x0000FFFF,
1776     0xFF00FFFF,
1777     0xC0C0C0FF,
1778     0x808080FF,
1779     0x404040FF,
1780     0x804000FF
1781     };
1782 
1783   bool area_in_pi = false;
1784 
compute_area(hpcshape & sh)1785   ld compute_area(hpcshape& sh) {
1786     ld area = 0;
1787     for(int i=sh.s; i<sh.e-1; i++) {
1788       hyperpoint h1 = cgi.hpc[i];
1789       hyperpoint h2 = cgi.hpc[i+1];
1790       if(euclid)
1791         area += (h2[1] + h1[1]) * (h2[0] - h1[0]) / 2;
1792       else {
1793         hyperpoint rh2 = gpushxto0(h1) * h2;
1794         hyperpoint rh1 = gpushxto0(h2) * h1;
1795         // ld a1 = atan2(h1[1], h1[0]);
1796         // ld a2 = atan2(h2[1], h2[0]);
1797         ld b1 = atan2(rh1[1], rh1[0]);
1798         ld b2 = atan2(rh2[1], rh2[0]);
1799         // C0 -> H1 -> H2 -> C0
1800         // at C0: (a1-a2)
1801         // at H1: (rh2 - a1 - M_PI)
1802         // at H2: (a2+M_PI - rh1)
1803         // total: rh2 - rh1
1804         // ld z = degree;
1805         ld x = b2 - b1 + M_PI;
1806         while(x > M_PI) x -= 2 * M_PI;
1807         while(x < -M_PI) x += 2 * M_PI;
1808         area += x;
1809         }
1810       }
1811     return area;
1812     }
1813 
1814 #define EDITING_TRIANGLES (GDIM == 3)
1815 
showDrawEditor()1816   EX void showDrawEditor() {
1817 #if CAP_POLY
1818     cmode = sm::DRAW;
1819     gamescreen(0);
1820     drawGrid();
1821     if(callhandlers(false, hooks_prestats)) return;
1822 
1823     if(!mouseout()) getcstat = '-';
1824 
1825     int sg;
1826     string line1, line2;
1827 
1828     usershape *us = NULL;
1829 
1830 #if CAP_TEXTURE
1831     if(texture::config.tstate != texture::tsActive && intexture) {
1832       intexture = false; drawing_tool = true;
1833       }
1834 #endif
1835 
1836     bool freedraw = drawing_tool || intexture;
1837 
1838 #if CAP_TEXTURE
1839     if(intexture) {
1840       sg = 16;
1841       line1 = "texture";
1842       line2 = "";
1843       texture::config.data.update();
1844       freedraw = true;
1845       drawing_tool = false;
1846       }
1847 #endif
1848 
1849     if(!freedraw) {
1850 
1851       sg = drawcellShapeGroup();
1852 
1853       switch(sg) {
1854         case sgPlayer:
1855           line1 = XLAT("character");
1856           line2 = csname(vid.cs);
1857           break;
1858 
1859         case sgMonster:
1860           line1 = XLAT("monster");
1861           line2 = XLAT1(minf[drawcell->monst].name);
1862           break;
1863 
1864         case sgItem:
1865           line1 = XLAT("item");
1866           line2 = XLAT1(iinf[drawcell->item].name);
1867           break;
1868 
1869         case sgFloor:
1870           line1 = GDIM == 3 ? XLAT("pick something") : XLAT("floor");
1871           line2 = "#" + its(drawcellShapeID());
1872           break;
1873 
1874         case sgWall:
1875           line1 = XLAT("statue");
1876           line2 = "#" + its(drawcellShapeID());
1877           break;
1878         }
1879 
1880       us =usershapes[drawcellShapeGroup()][drawcellShapeID()];
1881       }
1882 
1883     int fs = editor_fsize();
1884 
1885     // displayButton(8, 8+fs*9, XLAT("l = lands"), 'l', 0);
1886     displayfr(8, 8+fs, 2, vid.fsize, line1, 0xC0C0C0, 0);
1887 
1888     if(!freedraw) {
1889       if(sg == sgFloor)
1890         displayButton(8, 8+fs*2, line2 + XLAT(" (r = complex tesselations)"), 'r', 0);
1891       else
1892         displayfr(8, 8+fs*2, 2, vid.fsize, line2, 0xC0C0C0, 0);
1893       displayButton(8, 8+fs*3, GDIM == 3 ? XLAT("l = color group: %1", its(dslayer)) : XLAT("l = layers: %1", its(dslayer)), 'l', 0);
1894       }
1895 
1896     if(us && isize(us->d[dslayer].list)) {
1897       usershapelayer& ds(us->d[dslayer]);
1898       if(!EDITING_TRIANGLES) {
1899         displayButton(8, 8+fs*4, XLAT("1-9 = rotations: %1", its(ds.rots)), '1' + (ds.rots % 9), 0);
1900         displayButton(8, 8+fs*5, ds.sym ? XLAT("0 = symmetry") : XLAT("0 = asymmetry"), '0', 0);
1901         }
1902 
1903       displayfr(8, 8+fs*7, 2, vid.fsize, XLAT("%1 vertices", its(isize(ds.list))), 0xC0C0C0, 0);
1904       displaymm('a', 8, 8+fs*8, 2, vid.fsize, XLAT("a = add v"), 0);
1905       if(!EDITING_TRIANGLES) {
1906         if(autochoose) {
1907           displaymm('m', 8, 8+fs*9, 2, vid.fsize, XLAT("m = move v"), 0);
1908           displaymm('d', 8, 8+fs*10, 2, vid.fsize, XLAT("d = delete v"), 0);
1909           }
1910         else {
1911           displayButton(8, 8+fs*9, XLAT("m = move v"), 'm', 0);
1912           displayButton(8, 8+fs*10, XLAT("d = delete v"), 'd', 0);
1913           }
1914         displaymm('c', 8, 8+fs*11, 2, vid.fsize, autochoose ? XLAT("autochoose") : XLAT("c = choose"), 0);
1915         displayButton(8, 8+fs*12, XLAT("b = switch auto"), 'b', 0);
1916         }
1917       else {
1918         displayfr(8, 8+fs*9, 2, vid.fsize, XLAT("c = reuse"), 0xC0C0C0, 0);
1919         displayfr(8, 8+fs*10, 2, vid.fsize, XLAT("d = delete"), 0xC0C0C0, 0);
1920         }
1921 
1922       if(GDIM == 2) {
1923         displayfr(8, 8+fs*14, 2, vid.fsize, XLAT("t = shift"), 0xC0C0C0, 0);
1924         displayfr(8, 8+fs*15, 2, vid.fsize, XLAT("y = spin"), 0xC0C0C0, 0);
1925         }
1926       if(mousekey == 'g')
1927         displayButton(8, 8+fs*16, XLAT("p = grid color"), 'p', 0);
1928       else
1929         displayButton(8, 8+fs*16, XLAT("p = paint"), 'p', 0);
1930       if(GDIM == 2)
1931         displayfr(8, 8+fs*17, 2, vid.fsize, XLAT("z = z-level"), 0xC0C0C0, 0);
1932 
1933       }
1934 #if CAP_TEXTURE
1935     else if(freedraw) {
1936       displayButton(8, 8+fs*2, texture::texturesym ? XLAT("0 = symmetry") : XLAT("0 = asymmetry"), '0', 0);
1937       if(mousekey == 'g')
1938         displayButton(8, 8+fs*16, XLAT("p = grid color"), 'p', 0);
1939       else
1940         displayButton(8, 8+fs*16, XLAT("p = color"), 'p', 0);
1941       if(drawing_tool)
1942         displayButton(8, 8+fs*17, XLAT("f = fill") + (dtfill ? " (on)" : " (off)"), 'f', 0);
1943       displayButton(8, 8+fs*4, XLAT("b = brush size: %1", fts(dtwidth)), 'b', 0);
1944       displayButton(8, 8+fs*5, XLAT("u = undo"), 'u', 0);
1945       displaymm('d', 8, 8+fs*7, 2, vid.fsize, XLAT("d = draw"), 0);
1946       displaymm('l', 8, 8+fs*8, 2, vid.fsize, XLAT("l = line"), 0);
1947       displaymm('c', 8, 8+fs*9, 2, vid.fsize, XLAT("c = circle"), 0);
1948       if(drawing_tool)
1949         displaymm('T', 8, 8+fs*10, 2, vid.fsize, XLAT("T = text"), 0);
1950       if(drawing_tool)
1951         displaymm('e', 8, 8+fs*11, 2, vid.fsize, XLAT("e = erase"), 0);
1952       int s = isize(texture::config.data.pixels_to_draw);
1953       if(s) displaymm(0, 8, 8+fs*12, 2, vid.fsize, its(s), 0);
1954       }
1955 #endif
1956     else {
1957       displaymm('n', 8, 8+fs*5, 2, vid.fsize, XLAT("'n' to start"), 0);
1958       displaymm('u', 8, 8+fs*6, 2, vid.fsize, XLAT("'u' to load current"), 0);
1959       if(mousekey == 'a' || mousekey == 'd' || mousekey == 'd' ||
1960         mousekey == 'c') mousekey = 'n';
1961       }
1962 
1963     if(GDIM == 3)
1964       displayfr(8, 8+fs*19, 2, vid.fsize, (front_config == eFront::sphere_camera ? XLAT("z = camera") : front_config == eFront::sphere_center ? XLAT("z = spheres") :
1965         nonisotropic && front_config == eFront::equidistants ? XLAT("Z =") :
1966         nonisotropic && front_config == eFront::const_x ? XLAT("X =") :
1967         nonisotropic && front_config == eFront::const_y ? XLAT("Y =") :
1968         XLAT("z = equi")) + " " + fts(front_edit), 0xC0C0C0, 0);
1969 
1970     displaymm('g', vid.xres-8, 8+fs*4, 2, vid.fsize, XLAT("g = grid"), 16);
1971 
1972 #if CAP_TEXTURE
1973     if(freedraw) for(int i=0; i<10; i++) {
1974       if(8 + fs * (6+i) < vid.yres - 8 - fs * 7)
1975         displayColorButton(vid.xres-8, 8+fs*(6+i), "###", 1000 + i, 16, 1, dialog::displaycolor(texture_colors[i+1]));
1976 
1977       if(displayfr(vid.xres-8 - fs * 3, 8+fs*(6+i), 0, vid.fsize, its(i+1), dtwidth == brush_sizes[i] ? 0xFF8000 : 0xC0C0C0, 16))
1978         getcstat = 2000+i;
1979       }
1980 
1981     if(!freedraw)
1982       displaymm('e', vid.xres-8, 8+fs, 2, vid.fsize, XLAT("e = edit this"), 16);
1983 #endif
1984 
1985     if(!mouseout()) {
1986       hyperpoint mh;
1987       if(GDIM == 2) {
1988         transmatrix T = z_inverse(unshift(drawtrans)) * rgpushxto0(ccenter); /* todo? */
1989         mh = spintox(gpushxto0(ccenter) * coldcenter) * T * unshift(mouseh);
1990         }
1991       else
1992         mh = inverse_shift(drawtrans, find_mouseh3());
1993 
1994       displayfr(vid.xres-8, vid.yres-8-fs*7, 2, vid.fsize, XLAT("x: %1", fts(mh[0],4)), 0xC0C0C0, 16);
1995       displayfr(vid.xres-8, vid.yres-8-fs*6, 2, vid.fsize, XLAT("y: %1", fts(mh[1],4)), 0xC0C0C0, 16);
1996       displayfr(vid.xres-8, vid.yres-8-fs*5, 2, vid.fsize, XLAT("z: %1", fts(mh[2],4)), 0xC0C0C0, 16);
1997       if(MDIM == 4)
1998         displayfr(vid.xres-8, vid.yres-8-fs*4, 2, vid.fsize, XLAT("w: %1", fts(mh[3],4)), 0xC0C0C0, 16);
1999       mh = inverse_exp(shiftless(mh));
2000       displayfr(vid.xres-8, vid.yres-8-fs*3, 2, vid.fsize, XLAT("r: %1", fts(hypot_d(3, mh),4)), 0xC0C0C0, 16);
2001       if(GDIM == 3) {
2002         displayfr(vid.xres-8, vid.yres-8-fs, 2, vid.fsize, XLAT("ϕ: %1°", fts(-atan2(mh[2], hypot_d(2, mh)) / degree,4)), 0xC0C0C0, 16);
2003         displayfr(vid.xres-8, vid.yres-8-fs*2, 2, vid.fsize, XLAT("λ: %1°", fts(-atan2(mh[1], mh[0]) / degree,4)), 0xC0C0C0, 16);
2004         }
2005       else {
2006         displayfr(vid.xres-8, vid.yres-8-fs*2, 2, vid.fsize, XLAT("ϕ: %1°", fts(-atan2(mh[1], mh[0]) / degree,4)), 0xC0C0C0, 16);
2007         }
2008       }
2009 
2010     if(us) {
2011       cgi.require_usershapes();
2012       auto& sh = cgi.ushr[&us->d[dslayer]];
2013       if(sh.e >= sh.s + 3)
2014         displayButton(vid.xres-8, vid.yres-8-fs*8, XLAT("area: %1", area_in_pi ? fts(compute_area(sh) / M_PI, 4) + "π" : fts(compute_area(sh), 4)), 'w', 16);
2015       }
2016 
2017 
2018     displayFunctionKeys();
2019 
2020     keyhandler = drawHandleKey;
2021 #else
2022     popScreen();
2023 #endif
2024     }
2025 
2026 #if CAP_POLY
loadShapes(int sg,int id)2027   void loadShapes(int sg, int id) {
2028     delete usershapes[sg][id];
2029     usershapes[sg][id] = NULL;
2030 
2031     initquickqueue();
2032 
2033     dynamicval<bool> ws(mmspatial, false);
2034 
2035     auto sId = shiftless(Id);
2036     if(sg == 0) {
2037       multi::cpid = id, drawMonsterType(moPlayer, drawcell, sId, 0xC0C0C0, 0, 0xC0C0C0);
2038       }
2039     else if(sg == 1) {
2040       drawMonsterType(eMonster(id), drawcell, sId, minf[id].color, 0, minf[id].color);
2041       }
2042     else if(sg == 2) {
2043       drawItemType(eItem(id), drawcell, sId, iinf[id].color, 0, false);
2044       }
2045     else {
2046       draw_qfi(drawcell, sId, 0, PPR::FLOOR);
2047       }
2048 
2049     sortquickqueue();
2050 
2051     int layer = 0;
2052 
2053     initShape(sg, id);
2054 
2055     for(int i=0; i<isize(ptds); i++) {
2056       auto pp = dynamic_cast<dqi_poly*> (&*ptds[i]);
2057       if(!pp) continue;
2058       auto& ptd = *pp;
2059 
2060       int cnt = ptd.cnt;
2061 
2062       usershapelayer *dsCur = &usershapes[sg][id]->d[layer];
2063       dsCur->list.clear();
2064       dsCur->color = ptd.color;
2065       dsCur->sym = false;
2066       dsCur->rots = 1;
2067       dsCur->zlevel = 0;
2068 
2069       for(auto& v: cgi.symmetriesAt)
2070         if(v[0] == ptd.offset) {
2071           dsCur->rots = v[1];
2072           dsCur->sym = v[2] == 2;
2073           }
2074 
2075       int d = dsCur->rots * (dsCur->sym ? 2 : 1);
2076 
2077       for(int i=0; i < cnt/d; i++)
2078         dsCur->list.push_back(unshift(ptd.V) * glhr::gltopoint((*ptd.tab)[i+ptd.offset]));
2079 
2080       layer++;
2081       if(layer == USERLAYERS) break;
2082       }
2083     usershape_changes++;
2084     }
2085 
applyToShape(int sg,int id,int uni,hyperpoint mh)2086   void applyToShape(int sg, int id, int uni, hyperpoint mh) {
2087     bool haveshape = usershapes[sg][id];
2088     bool xnew = false;
2089 
2090     if(uni == '-') uni = mousekey;
2091 
2092     if(!haveshape) {
2093       if(uni == 'n')
2094         initShape(sg, id);
2095       else if(uni == 'u') ;
2096       else if(uni >= '0' && uni <= '9') {
2097         initShape(sg, id);
2098         xnew = true;
2099         }
2100       else
2101         return;
2102       }
2103 
2104     usershapelayer *dsCur = &usershapes[sg][id]->d[dslayer];
2105 
2106     if(uni == 'n' || xnew) {
2107       dsCur->list.clear();
2108       dsCur->list.push_back(mh);
2109       usershape_changes++;
2110       }
2111 
2112     if(uni == 'u')
2113       loadShapes(sg, id);
2114 
2115     if(uni == 'z' && haveshape && GDIM == 2)
2116       dialog::editNumber(dsCur->zlevel, -10, +10, 0.1, 0, XLAT("z-level"),
2117         XLAT("Changing the z-level will make this layer affected by the parallax effect."));
2118 
2119     if(EDITING_TRIANGLES) {
2120       if(uni == 'a') {
2121         dsCur->list.push_back(mh);
2122         uni = 0;
2123         usershape_changes++;
2124         }
2125       else if(uni == 'c' || uni == 'd' || uni == 'm') {
2126         hyperpoint best = mh;
2127         hyperpoint onscr;
2128         applymodel(drawtrans * mh, onscr);
2129         println(hlog, "onscr = ", onscr);
2130         ld dist = HUGE_VAL;
2131         for(auto& layer: usershapes[sg][id]->d)
2132         for(const hyperpoint& h: layer.list) {
2133           hyperpoint h1;
2134           applymodel(drawtrans * h, h1);
2135           ld d = sqhypot_d(2, h1 - onscr);
2136           if(d < dist) dist = d, best = h;
2137           }
2138         if(uni == 'c') dsCur->list.push_back(best);
2139         else if(uni == 'd') {
2140           vector<hyperpoint> oldlist = move(dsCur->list);
2141           dsCur->list.clear();
2142           int i;
2143           for(i=0; i<isize(oldlist); i+=3)
2144             if(oldlist[i] != best && oldlist[i+1] != best && oldlist[i+2] != best)
2145               dsCur->list.push_back(oldlist[i]),
2146               dsCur->list.push_back(oldlist[i+1]),
2147               dsCur->list.push_back(oldlist[i+2]);
2148           for(; i<isize(oldlist); i++)
2149             if(oldlist[i] != best)
2150               dsCur->list.push_back(oldlist[i]);
2151           }
2152         usershape_changes++;
2153         uni = 0;
2154         }
2155       else if(uni == COLORKEY) dsCur->color = colortouse;
2156       else if(uni != 'D') uni = 0;
2157       }
2158 
2159     if(uni == 'a' && haveshape) {
2160       mh = spin(2*M_PI*-ew.rotid/dsCur->rots) * mh;
2161       if(ew.symid) mh = Mirror * mh;
2162 
2163       if(ew.pointid < 0 || ew.pointid >= isize(dsCur->list))
2164         ew.pointid = isize(dsCur->list)-1, ew.side = 1;
2165 
2166       dsCur->list.insert(dsCur->list.begin()+ew.pointid+(ew.side?1:0), mh);
2167       if(ew.side) ew.pointid++;
2168       usershape_changes++;
2169       }
2170 
2171     if(uni == 'D') {
2172       delete usershapes[sg][id];
2173       usershapes[sg][id] = NULL;
2174       }
2175 
2176     if(uni == 'm' || uni == 'd') {
2177 
2178       int i = ew.pointid;
2179 
2180       if(i < 0 || i >= isize(dsCur->list)) return;
2181 
2182       mh = spin(2*M_PI*-ew.rotid/dsCur->rots) * mh;
2183       if(ew.symid) mh = Mirror * mh;
2184 
2185       if(uni == 'm' || uni == 'M')
2186         dsCur->list[i] = mh;
2187       if(uni == 'd' || uni == 'b') {
2188         dsCur->list.erase(dsCur->list.begin() + i);
2189         if(ew.side == 1 && ew.pointid >= i) ew.pointid--;
2190         if(ew.side == 0 && ew.pointid > i) ew.pointid--;
2191         }
2192       usershape_changes++;
2193       }
2194 
2195     if(uni == 'K') {
2196       if(vid.cs.charid >= 4) {
2197         loadShape(sg, id, cgi.shCatBody, 2, 0);
2198         loadShape(sg, id, cgi.shCatHead, 2, 1);
2199         }
2200       else {
2201         if(!(vid.cs.charid&1)) loadShape(sg, id, cgi.shPBody, 2, 0);
2202         else loadShape(sg, id, cgi.shFemaleBody, 2, 0);
2203 
2204         loadShape(sg, id, cgi.shPSword, 1, 1);
2205 
2206         if(vid.cs.charid&1)
2207           loadShape(sg, id, cgi.shFemaleDress, 2, 2);
2208 
2209         /* if(vid.cs.charid&1)
2210           loadShape(sg, id, cgi.shPrincessDress, 1, 3);
2211         else
2212           loadShape(sg, id, cgi.shPrinceDress, 2, 3); */
2213 
2214         loadShape(sg, id, cgi.shRatCape2, 1, 3);
2215 
2216         if(vid.cs.charid&1)
2217           loadShape(sg, id, cgi.shFemaleHair, 2, 4);
2218         else
2219           loadShape(sg, id, cgi.shPHead, 2, 4);
2220 
2221         loadShape(sg, id, cgi.shPFace, 2, 5);
2222         }
2223 
2224       // loadShape(sg, id, cgi.shWolf, 2, dslayer);
2225 
2226       usershape_changes++;
2227       }
2228 
2229     if(uni == '+') dsCur->rots++;
2230 
2231     if(uni >= '1' && uni <= '9') {
2232       dsCur->rots = uni - '0';
2233       if(dsCur->rots == 9) dsCur->rots = 21;
2234       usershape_changes++;
2235       }
2236     if(uni == '0') {
2237       dsCur->sym = !dsCur->sym;
2238       usershape_changes++;
2239       }
2240 
2241     if(uni == 't') {
2242       dsCur->shift = mh;
2243       usershape_changes++;
2244       }
2245     if(uni == 'y') {
2246       dsCur->spin = mh;
2247       usershape_changes++;
2248       }
2249 
2250     if(uni == COLORKEY) dsCur->color = colortouse;
2251     }
2252 
writeHyperpoint(hstream & f,hyperpoint h)2253   void writeHyperpoint(hstream& f, hyperpoint h) {
2254     println(f, spaced_of(&h[0], MDIM));
2255     }
2256 
readHyperpoint(fhstream & f)2257   hyperpoint readHyperpoint(fhstream& f) {
2258     hyperpoint h;
2259     for(int i=0; i<MDIM; i++) scan(f, h[i]);
2260     return h;
2261     }
2262 
drawHelpLine()2263   string drawHelpLine() {
2264     return XLAT("vector graphics editor");
2265     }
2266 
2267   bool onelayeronly;
2268 
loadPicFile(const string & s)2269   bool loadPicFile(const string& s) {
2270     fhstream f(picfile, "rt");
2271     if(!f.f) {
2272       addMessage(XLAT("Failed to load pictures from %1", picfile));
2273       return false;
2274       }
2275     scanline(f);
2276     scan(f, f.vernum);
2277     printf("vernum = %x\n", f.vernum);
2278     if(f.vernum == 0) {
2279       addMessage(XLAT("Failed to load pictures from %1", picfile));
2280       return false;
2281       }
2282 
2283     if(f.vernum >= 0xA0A0) {
2284       int tg, wp;
2285       int nt;
2286       scan(f, tg, nt, wp, patterns::subpattern_flags);
2287       patterns::whichPattern = patterns::ePattern(wp);
2288       set_geometry(eGeometry(tg));
2289       set_variation(eVariation(nt));
2290       start_game();
2291       }
2292 
2293     while(true) {
2294       int i, j, l, sym, rots, siz;
2295       color_t color;
2296       if(!scan(f, i, j, l, sym, rots, color, siz)) break;
2297       if(i == -1) break;
2298       if(siz < 0 || siz > 1000) break;
2299 
2300       if(i >= 4) {
2301         if(i < 8) patterns::whichPattern = patterns::ePattern("xxxxfpzH"[i]);
2302         patterns::subpattern_flags = 0;
2303         i = 3;
2304         }
2305 
2306       initShape(i, j);
2307       usershapelayer& ds(usershapes[i][j]->d[l]);
2308       if(f.vernum >= 0xA608) scan(f, ds.zlevel);
2309       ds.shift = readHyperpoint(f);
2310       ds.spin = readHyperpoint(f);
2311       ds.list.clear();
2312       for(int i=0; i<siz; i++) {
2313         ds.list.push_back(readHyperpoint(f));
2314         writeHyperpoint(hlog, ds.list[i]);
2315         }
2316       ds.sym = sym;
2317       ds.rots = rots;
2318       ds.color = color;
2319       }
2320     addMessage(XLAT("Pictures loaded from %1", picfile));
2321 
2322     usershape_changes++;
2323     return true;
2324     }
2325 
savePicFile(const string & s)2326   bool savePicFile(const string& s) {
2327     fhstream f(picfile, "wt");
2328     if(!f.f) {
2329       addMessage(XLAT("Failed to save pictures to %1", picfile));
2330       return false;
2331       }
2332     println(f, "HyperRogue saved picture");
2333     println(f, f.vernum);
2334     if(f.vernum >= 0xA0A0)
2335       println(f, spaced(geometry, int(variation), patterns::whichPattern, patterns::subpattern_flags));
2336     for(int i=0; i<USERSHAPEGROUPS; i++) for(auto usp: usershapes[i]) {
2337       usershape *us = usp.second;
2338       if(!us) continue;
2339 
2340       for(int l=0; l<USERLAYERS; l++) if(isize(us->d[l].list)) {
2341         usershapelayer& ds(us->d[l]);
2342         println(f, spaced(i, usp.first, l, ds.sym, ds.rots, ds.color, int(isize(ds.list))));
2343         print(f, spaced(ds.zlevel), " ");
2344         writeHyperpoint(f, ds.shift);
2345         writeHyperpoint(f, ds.spin);
2346         println(f);
2347         for(int i=0; i<isize(ds.list); i++)
2348           writeHyperpoint(f, ds.list[i]);
2349         }
2350       }
2351     println(f, "-1");
2352     addMessage(XLAT("Pictures saved to %1", picfile));
2353     return true;
2354     }
2355 
drawHandleKey(int sym,int uni)2356   void drawHandleKey(int sym, int uni) {
2357 
2358     if(uni == PSEUDOKEY_WHEELUP && GDIM == 3 && front_step) {
2359       front_edit += front_step * shiftmul; return;
2360       }
2361 
2362     if(uni == PSEUDOKEY_WHEELDOWN && GDIM == 3 && front_step) {
2363       front_edit -= front_step * shiftmul; return;
2364       }
2365 
2366     handlePanning(sym, uni);
2367 
2368     if(uni == SETMOUSEKEY) {
2369        if(mousekey == newmousekey)
2370          mousekey = '-';
2371        else
2372          mousekey = newmousekey;
2373        }
2374 
2375     if(uni == 'w') area_in_pi = !area_in_pi;
2376 
2377     if(uni == 'r') {
2378       pushScreen(patterns::showPattern);
2379       if(drawplayer)
2380         addMessage(XLAT("Hint: use F7 to edit floor under the player"));
2381       }
2382 
2383     shiftpoint mh = GDIM == 2 ? mouseh : find_mouseh3();
2384     hyperpoint mh1 = inverse_shift(drawtrans, mh);
2385 
2386     bool clickused = false;
2387 
2388     if((uni == 'p' && mousekey == 'g') || (uni == 'g' && coldcenter == ccenter && ccenter == mh1)) {
2389       static unsigned grid_colors[] = {
2390         8,
2391         0x00000040,
2392         0xFFFFFF40,
2393         0xFF000040,
2394         0x0000F040,
2395         0x00000080,
2396         0xFFFFFF80,
2397         0xFF000080,
2398         0x0000F080,
2399         };
2400       dialog::openColorDialog(gridcolor, grid_colors);
2401       clickused = true;
2402       }
2403 
2404     char mkuni = uni == '-' ? mousekey : uni;
2405 
2406     if(mkuni == 'g')
2407       coldcenter = ccenter, ccenter = mh1, clickused = true;
2408 
2409     if(uni == 'd' || uni == 'l' || uni == 'c' || uni == 'e' || uni == 'T')
2410       mousekey = uni;
2411 
2412     if(drawing_tool) {
2413       if(sym == SDLK_F2) save_level();
2414       if(sym == SDLK_F3) load_level();
2415       if(sym == SDLK_F5) {
2416         dialog::push_confirm_dialog([] {
2417           stop_game();
2418           enable_canvas();
2419           canvas_default_wall = waInvisibleFloor;
2420           patterns::whichCanvas = 'g';
2421           patterns::canvasback = 0xFFFFFF;
2422           dtcolor = (forecolor << 8) | 255;
2423           drawplayer = false;
2424           vid.use_smart_range = 2;
2425           start_game();
2426           },
2427           XLAT("Are you sure you want to restart? This will let you draw on a blank screen.")
2428           );
2429         }
2430       }
2431 
2432     if(uni == ' ' && (cheater || autocheat)) {
2433       drawing_tool = !drawing_tool;
2434       if(!drawing_tool) {
2435         popScreen();
2436         pushScreen(showMapEditor);
2437         }
2438       }
2439 
2440     if(uni == 'z' && GDIM == 3) {
2441       dialog::editNumber(front_edit, 0, 5, 0.1, 0.5, XLAT("z-level"), "");
2442       dialog::extra_options = [] () {
2443         dialog::addBoolItem(XLAT("The distance from the camera to added points."), front_config == eFront::sphere_camera, 'A');
2444         dialog::add_action([] { front_config = eFront::sphere_camera; });
2445         dialog::addBoolItem(XLAT("place points at fixed radius"), front_config == eFront::sphere_center, 'B');
2446         dialog::add_action([] { front_config = eFront::sphere_center; });
2447         dialog::addBoolItem(nonisotropic ? XLAT("place points on surfaces of const Z") : XLAT("place points on equidistant surfaces"), front_config == eFront::equidistants, 'C');
2448         dialog::add_action([] { front_config = eFront::equidistants; });
2449         if(nonisotropic) {
2450           dialog::addBoolItem(XLAT("place points on surfaces of const X"), front_config == eFront::const_x, 'D');
2451           dialog::add_action([] { front_config = eFront::const_x; });
2452           dialog::addBoolItem(XLAT("place points on surfaces of const Y"), front_config == eFront::const_y, 'E');
2453           dialog::add_action([] { front_config = eFront::const_y; });
2454           }
2455         dialog::addSelItem(XLAT("mousewheel step"), fts(front_step), 'S');
2456         dialog::add_action([] {
2457           popScreen();
2458           dialog::editNumber(front_step, -10, 10, 0.1, 0.1, XLAT("mousewheel step"), "hint: shift for finer steps");
2459           });
2460         if(front_config == eFront::sphere_center) {
2461           dialog::addSelItem(XLAT("parallels to draw"), its(parallels), 'P');
2462           dialog::add_action([] {
2463             popScreen();
2464             dialog::editNumber(parallels, 0, 72, 1, 12, XLAT("parallels to draw"), "");
2465             });
2466           dialog::addSelItem(XLAT("meridians to draw"), its(meridians), 'M');
2467           dialog::add_action([] {
2468             popScreen();
2469             dialog::editNumber(meridians, 0, 72, 1, 12, XLAT("meridians to draw"), "");
2470             });
2471           }
2472         else if(front_config != eFront::sphere_camera) {
2473           dialog::addSelItem(XLAT("range of grid to draw"), fts(equi_range), 'R');
2474           dialog::add_action([] {
2475             popScreen();
2476             dialog::editNumber(equi_range, 0, 5, 0.1, 1, XLAT("range of grid to draw"), "");
2477             });
2478           }
2479         };
2480       }
2481 
2482     if(sym == SDLK_F7) {
2483       drawplayer = !drawplayer;
2484       }
2485 
2486 #if CAP_SHOT
2487     else if(sym == SDLK_F6) {
2488       pushScreen(shot::menu);
2489       }
2490 #endif
2491 
2492     if(sym == SDLK_ESCAPE) popScreen();
2493 
2494     if(sym == SDLK_F1) {
2495       gotoHelp(drawhelptext());
2496       }
2497 
2498     if(sym == SDLK_F10) popScreen();
2499 
2500     (void)clickused;
2501 
2502 
2503     bool freedraw = drawing_tool || intexture;
2504 
2505     if(freedraw) {
2506       if(lstartcell) lstart = ggmatrix(lstartcell) * lstart_rel;
2507 
2508 #if CAP_TEXTURE
2509       int tcolor = (dtcolor >> 8) | ((dtcolor & 0xFF) << 24);
2510 #endif
2511 
2512       if(uni == '-' && !clickused) {
2513         if(mousekey == 'e') {
2514           dt_erase(mh);
2515           clickused = true;
2516           }
2517         else if(mousekey == 'l' || mousekey == 'c' || mousekey == 'T') {
2518           if(!holdmouse) lstart = mh, lstartcell = mouseover, lstart_rel = inverse_shift(ggmatrix(mouseover), lstart), holdmouse = true;
2519           }
2520 #if CAP_TEXTURE
2521         else if(intexture) {
2522           if(!holdmouse) texture::config.data.undoLock();
2523           texture::drawPixel(mouseover, mh, tcolor);
2524           holdmouse = true; lstartcell = NULL;
2525           }
2526 #endif
2527         else {
2528           dt_add_free(mh);
2529           holdmouse = true;
2530           }
2531         }
2532 
2533       if(sym == PSEUDOKEY_RELEASE) {
2534         printf("release\n");
2535 #if CAP_TEXTURE
2536         if(mousekey == 'l' && intexture) {
2537           texture::config.data.undoLock();
2538           texture::where = mouseover;
2539           texture::drawPixel(mouseover, mh, tcolor);
2540           texture::drawLine(mh, lstart, tcolor);
2541           lstartcell = NULL;
2542           }
2543         else
2544 #endif
2545         if(mousekey == 'l') {
2546           dt_add_line(mh, lstart, 10);
2547           lstartcell = NULL;
2548           }
2549 #if CAP_TEXTURE
2550         else if(mousekey == 'c' && intexture) {
2551           texture::config.data.undoLock();
2552           ld rad = hdist(lstart, mh);
2553           int circp = int(1 + 3 * (circlelength(rad) / dtwidth));
2554           if(circp > 1000) circp = 1000;
2555           shiftmatrix T = rgpushxto0(lstart);
2556           texture::where = lstartcell;
2557           for(int i=0; i<circp; i++)
2558             texture::drawPixel(T * xspinpush0(2 * M_PI * i / circp, rad), tcolor);
2559           lstartcell = NULL;
2560           }
2561 #endif
2562         else if(mousekey == 'c') {
2563           dt_add_circle(lstart, mh);
2564           lstartcell = NULL;
2565           }
2566         else if(mousekey == 'T') {
2567           static string text = "";
2568           dialog::edit_string(text, "", "");
2569           shiftpoint h = mh;
2570           dialog::reaction_final = [h] {
2571             if(text != "")
2572               dt_add_text(h, dtwidth * 50, text);
2573             };
2574           lstartcell = nullptr;
2575           }
2576         else
2577           dt_finish();
2578         }
2579 
2580       if(uni >= 1000 && uni < 1010)
2581         dtcolor = texture_colors[uni - 1000 + 1];
2582 
2583       if(uni >= 2000 && uni < 2010)
2584         dtwidth = brush_sizes[uni - 2000];
2585 
2586 #if CAP_TEXTURE
2587       if(uni == '0')
2588         texture::texturesym = !texture::texturesym;
2589 
2590       if(uni == 'u') {
2591         texture::config.data.undo();
2592         }
2593 #endif
2594 
2595       if(uni == 'p') {
2596         if(!clickused)
2597           dialog::openColorDialog(dtcolor, texture_colors);
2598         }
2599 
2600       if(uni == 'f') {
2601         if(dtfill == dtcolor)
2602           dtfill = 0;
2603         else
2604           dtfill = dtcolor;
2605         }
2606 
2607       if(uni == 'b')
2608         dialog::editNumber(dtwidth, 0, 0.1, 0.005, 0.02, XLAT("brush size"), XLAT("brush size"));
2609       }
2610 
2611     else {
2612       dslayer %= USERLAYERS;
2613 
2614       applyToShape(drawcellShapeGroup(), drawcellShapeID(), uni, mh1);
2615 
2616       if(uni == 'e' || (uni == '-' && mousekey == 'e')) {
2617         initdraw(mouseover ? mouseover : cwt.at);
2618         }
2619       if(uni == 'l') { dslayer++; dslayer %= USERLAYERS; }
2620       if(uni == 'L') { dslayer--; if(dslayer < 0) dslayer += USERLAYERS; }
2621       if(uni == 'l' - 96) onelayeronly = !onelayeronly;
2622 
2623       if(uni == 'c') ew = ewsearch;
2624       if(uni == 'b') autochoose = !autochoose;
2625 
2626       if(uni == 'S') {
2627         for(int i=0; i<USERSHAPEGROUPS; i++) for(auto usp: usershapes[i]) {
2628           auto us = usp.second;
2629           if(!us) continue;
2630 
2631           for(int l=0; l<USERLAYERS; l++) if(isize(us->d[l].list)) {
2632             usershapelayer& ds(us->d[l]);
2633             println(hlog, spaced("//", i, usp.first, l, "[", ds.color, double(ds.zlevel), "]"));
2634             print(hlog, " ID, ", us->d[l].rots, ", ", us->d[l].sym?2:1, ", ");
2635             for(int i=0; i<isize(us->d[l].list); i++) {
2636               for(int d=0; d<GDIM; d++) print(hlog, fts(us->d[l].list[i][d]), ", ");
2637               print(hlog, " ");
2638               }
2639             println(hlog);
2640             }
2641           }
2642         }
2643 
2644       if(uni == 'p') {
2645         dialog::openColorDialog(colortouse);
2646         dialog::reaction = [] () {
2647           drawHandleKey(COLORKEY, COLORKEY);
2648           };
2649         }
2650 
2651       if(sym == SDLK_F2)
2652         dialog::openFileDialog(picfile, XLAT("pics to save:"), ".pic",
2653           [] () {
2654             return savePicFile(picfile);
2655             });
2656 
2657       if(sym == SDLK_F3)
2658         dialog::openFileDialog(picfile, XLAT("pics to load:"), ".pic",
2659           [] () {
2660             return loadPicFile(picfile);
2661             });
2662 
2663       if(sym == SDLK_F5) {
2664         for(int i=0; i<USERSHAPEGROUPS; i++) {
2665           for(auto us: usershapes[i])
2666             if(us.second) delete us.second;
2667           usershapes[i].clear();
2668           }
2669         }
2670       }
2671     }
2672 #endif
2673 
__anon55f918221902() 2674   auto hooks = addHook(hooks_clearmemory, 0, [] () {
2675     if(mapeditor::painttype == 4)
2676       mapeditor::painttype = 0, mapeditor::paintwhat = 0,
2677       mapeditor::paintwhat_str = "clear monster";
2678     mapeditor::copysource.at = NULL;
2679     mapeditor::undo.clear();
2680     if(!cheater) patterns::displaycodes = false;
2681     if(!cheater) patterns::whichShape = 0;
2682     modelcell.clear();
2683     mapeditor::dtshapes.clear();
2684     dt_finish();
2685     drawcell = nullptr;
2686     }) +
__anon55f918221a02() 2687   addHook(hooks_removecells, 0, [] () {
2688     modelcell.clear();
2689     set_if_removed(mapeditor::copysource.at, NULL);
2690     });
2691 #endif
2692 
initdraw(cell * c)2693   EX void initdraw(cell *c) {
2694     #if CAP_EDIT
2695     mapeditor::drawcell = c;
2696     ew.c = c;
2697     ew.rotid = 0;
2698     ew.symid = 0;
2699     ew.pointid = -1;
2700     ew.side = 0;
2701     ewsearch = ew;
2702     ccenter = coldcenter = C0;
2703     #endif
2704     }
2705 
2706   transmatrix textrans;
2707 
queue_hcircle(shiftmatrix Ctr,ld radius)2708   EX void queue_hcircle(shiftmatrix Ctr, ld radius) {
2709     vector<hyperpoint> pts;
2710     int circp = int(6 * pow(2, vid.linequality));
2711     if(radius > 0.04) circp *= 2;
2712     if(radius > .1) circp *= 2;
2713 
2714     for(int j=0; j<circp; j++)
2715       pts.push_back(xspinpush0(M_PI*j*2/circp, radius));
2716     for(int j=0; j<circp; j++) curvepoint(pts[j]);
2717     curvepoint(pts[0]);
2718     queuecurve(Ctr, dtcolor, 0, PPR::LINE);
2719     }
2720 
2721 #if CAP_POLY
haveUserShape(eShapegroup group,int id)2722   EX bool haveUserShape(eShapegroup group, int id) {
2723   #if !CAP_EDIT
2724     return false;
2725   #else
2726     return usershapes[group].count(id) && usershapes[group][id];
2727   #endif
2728     }
2729 #endif
2730 
2731 #if CAP_TEXTURE
draw_texture_ghosts(cell * c,const shiftmatrix & V)2732   EX void draw_texture_ghosts(cell *c, const shiftmatrix& V) {
2733     if(!c) return;
2734     if(holdmouse && !lstartcell) return;
2735     cell *ls = lstartcell ? lstartcell : lmouseover;
2736     if(!ls) return;
2737 
2738     auto sio = patterns::getpatterninfo0(ls);
2739     auto sih = patterns::getpatterninfo0(c);
2740 
2741     if(sio.id == sih.id) {
2742       if(c == ls)
2743         textrans = z_inverse(V.T * applyPatterndir(ls, sio));
2744 
2745       transmatrix mh = textrans * rgpushxto0(unshift(mouseh, V.shift));
2746       transmatrix ml = textrans * rgpushxto0(unshift(lstart, V.shift));
2747 
2748       for(int j=0; j<=texture::texturesym; j++)
2749       for(int i=0; i<c->type; i += sih.symmetries) {
2750         shiftmatrix M2 = V * applyPatterndir(c, sih) * spin(2*M_PI*i/c->type);
2751         if(j) M2 = M2 * Mirror;
2752         switch(holdmouse ? mousekey : 'd') {
2753           case 'c':
2754             queue_hcircle(M2 * ml, hdist(lstart, mouseh));
2755             break;
2756           case 'l':
2757             queueline(M2 * mh * C0, M2 * ml * C0, dtcolor, 4 + vid.linequality, PPR::LINE);
2758             break;
2759           default:
2760             queue_hcircle(M2 * mh, dtwidth);
2761           }
2762         }
2763       }
2764     }
2765 #endif
2766 
2767 #if CAP_POLY
drawUserShape(const shiftmatrix & V,eShapegroup group,int id,color_t color,cell * c,PPR prio IS (PPR::DEFAULT))2768   EX bool drawUserShape(const shiftmatrix& V, eShapegroup group, int id, color_t color, cell *c, PPR prio IS(PPR::DEFAULT)) {
2769   #if !CAP_EDIT
2770     return false;
2771   #else
2772 
2773     // floors handled separately
2774     if(c && c == drawcell && editingShape(group, id) && group != sgFloor)
2775       drawtrans = V;
2776 
2777     usershape *us = usershapes[group][id];
2778     if(us) {
2779       cgi.require_usershapes();
2780       for(int i=0; i<USERLAYERS; i++) {
2781         if(i != dslayer && onelayeronly) continue;
2782         usershapelayer& ds(us->d[i]);
2783         hpcshape& sh(cgi.ushr[&ds]);
2784 
2785         if(sh.s != sh.e) {
2786           auto& last = queuepolyat(mmscale(V, GDIM == 3 ? 0 : geom3::lev_to_factor(ds.zlevel)), sh, ds.color ? ds.color : color, prio);
2787           if(GDIM == 3) {
2788             last.tinf = &user_triangles_texture;
2789             last.offset_texture = ds.texture_offset;
2790             }
2791           }
2792         }
2793       }
2794 
2795     if(cmode & sm::DRAW) {
2796 
2797       if(c == drawcell && EDITING_TRIANGLES && mapeditor::editingShape(group, id)) {
2798         if(!us) return false;
2799         usershapelayer &ds(us->d[mapeditor::dslayer]);
2800         for(int i=0; i<isize(ds.list); i++) {
2801           int j = (i%3 == 2 ? i-2 : i+1);
2802           if(j < isize(ds.list))
2803             queueline(V * ds.list[i], V * ds.list[j], 0xFF00FFFF, -1, PPR::SUPERLINE);
2804           queuestr(V * ds.list[i], 10, "x", 0xFF00FF);
2805           }
2806         }
2807 
2808       if(mapeditor::editingShape(group, id) && !EDITING_TRIANGLES) {
2809 
2810         /* for(int a=0; a<isize(ds.list); a++) {
2811           hyperpoint P2 = V * ds.list[a];
2812 
2813           int xc, yc, sc;
2814           getcoord(P2, xc, yc, sc);
2815           queuestr(xc, yc, sc, 10, "x",
2816             a == 0 ? 0x00FF00 :
2817             a == isize(ds.list)-1 ? 0xFF0000 :
2818             0xFFFF00);
2819           } */
2820 
2821         if(!us) return false;
2822 
2823         usershapelayer &ds(us->d[mapeditor::dslayer]);
2824 
2825         shiftpoint moh = GDIM == 2 ? mouseh : find_mouseh3();
2826 
2827         hyperpoint mh = inverse_shift(mapeditor::drawtrans, moh);
2828 
2829         for(int a=0; a<ds.rots; a++)
2830         for(int b=0; b<(ds.sym?2:1); b++) {
2831 
2832           if(mouseout()) break;
2833 
2834           shiftpoint P2 = V * spin(2*M_PI*a/ds.rots) * (b?Mirror*mh:mh);
2835 
2836           queuestr(P2, 10, "x", 0xFF00FF);
2837           }
2838 
2839         if(isize(ds.list) == 0) return us;
2840 
2841         shiftpoint Plast = V * spin(-2*M_PI/ds.rots) * (ds.sym?Mirror*ds.list[0]:ds.list[isize(ds.list)-1]);
2842         int state = 0;
2843         int gstate = 0;
2844         double dist2 = 0;
2845         shiftpoint lpsm;
2846 
2847         for(int a=0; a<ds.rots; a++)
2848         for(int b=0; b<(ds.sym?2:1); b++) {
2849 
2850           hyperpoint mh2 = spin(2*M_PI*-ew.rotid/ds.rots) * mh;
2851           if(ew.symid) mh2 = Mirror * mh2;
2852           shiftpoint pseudomouse = V * spin(2*M_PI*a/ds.rots) * mirrorif(mh2, b);
2853 
2854           for(int t=0; t<isize(ds.list); t++) {
2855             int ti = b ? isize(ds.list)-1-t : t;
2856 
2857             shiftpoint P2 = V * spin(2*M_PI*a/ds.rots) * mirrorif(ds.list[ti], b);
2858 
2859             if(!mouseout()) {
2860               double d = hdist(moh, P2);
2861               if(d < ewsearch.dist)
2862                 ewsearch.dist = d,
2863                 ewsearch.rotid = a,
2864                 ewsearch.symid = b,
2865                 ewsearch.pointid = ti,
2866                 ewsearch.c = c,
2867                 ewsearch.side = b,
2868                 state = 1,
2869                 dist2 = d + hdist(moh, Plast) - hdist(P2, Plast);
2870 
2871               else if(state == 1) {
2872                 double dist3 = d + hdist(moh, Plast) - hdist(P2, Plast);
2873                 if(dist3 < dist2)
2874                   ewsearch.side = !ewsearch.side;
2875                 state = 2;
2876                 }
2877               }
2878 
2879             queuestr(P2, 10, "o",
2880               0xC000C0);
2881 
2882             if(!mouseout()) {
2883               if(gstate == 1) queueline(lpsm, P2, 0x90000080), gstate = 0;
2884               if(ti == ew.pointid) {
2885                 queueline(pseudomouse, P2, 0xF0000080);
2886                 if(ew.side == (b==1)) queueline(pseudomouse, Plast, 0x90000080);
2887                 else gstate = 1, lpsm = pseudomouse;
2888                 }
2889               }
2890 
2891             Plast = P2;
2892             }
2893 
2894           }
2895 
2896         if(gstate == 1) queueline(lpsm, V * ds.list[0], 0x90000080), gstate = 0;
2897         if(state == 1) {
2898           shiftpoint P2 = V * ds.list[0];
2899           if(hdist(moh, P2) + hdist(moh, Plast) - hdist(P2, Plast) < dist2)
2900             ewsearch.side = 1;
2901           }
2902         }
2903 
2904       }
2905 
2906     return us;
2907   #endif
2908     }
2909 #endif
2910 
map_settings()2911   EX void map_settings() {
2912     cmode = sm::SIDE | sm::MAYDARK;
2913     gamescreen(1);
2914 
2915     dialog::init(XLAT("Map settings"));
2916 
2917     dialog::addBoolItem_action_neg(XLAT("disable wandering monsters"), gen_wandering, 'w');
2918 
2919     if(gen_wandering) {
2920       dialog::addBoolItem_action_neg(XLAT("disable ghost timer"), timerghost, 'g');
2921       }
2922     else dialog::addBreak(100);
2923 
2924     dialog::addBoolItem_action(XLAT("simple pattern generation"), reptilecheat, 'p');
2925     dialog::addInfo(XLAT("(e.g. pure Reptile pattern)"));
2926 
2927     dialog::addBoolItem_action(XLAT("safety generation"), safety, 's');
2928     dialog::addInfo(XLAT("(no treasure, no dangers)"));
2929 
2930     dialog::addBoolItem(XLAT("god mode"), autocheat, 'G');
2931     dialog::add_action([] () { autocheat = true; });
2932     dialog::addInfo(XLAT("(unlock all, allow cheats, normal character display, cannot be turned off!)"));
2933 
2934     dialog::addItem(XLAT("change the pattern/color of new Canvas cells"), 'c');
2935     dialog::add_action_push(patterns::showPrePatternNoninstant);
2936 
2937     dialog::addItem(XLAT("configure WFC"), 'W');
2938     dialog::add_action_push(wfc::wfc_menu);
2939 
2940     dialog::addItem(XLAT("edit cell values"), 'G');
2941     dialog::add_action(push_debug_screen);
2942 
2943     dialog::addBack();
2944     dialog::display();
2945     }
2946 EX }
2947 
2948 #if CAP_EDIT
2949 EX string levelfile = "hyperrogue.lev";
2950 EX const char *loadlevel = NULL;
2951 EX string picfile = "hyperrogue.pic";
2952 
2953 #if CAP_COMMANDLINE
2954 
read_editor_args()2955 int read_editor_args() {
2956   using namespace arg;
2957   if(argis("-lev")) { shift(); levelfile = args(); }
2958   else if(argis("-pic")) { shift(); picfile = args(); }
2959   else if(argis("-load")) { PHASE(3); shift(); mapstream::loadMap(args()); }
2960   else if(argis("-save")) { PHASE(3); shift(); mapstream::saveMap(args().c_str()); }
2961   else if(argis("-d:draw")) { PHASE(3);
2962     #if CAP_EDIT
2963     start_game();
2964     mapeditor::drawing_tool = true;
2965     mapeditor::initdraw(cwt.at);
2966     launch_dialog(mapeditor::showDrawEditor);
2967     #endif
2968     }
2969   #if CAP_POLY
2970   else if(argis("-dred")) {
2971     PHASEFROM(2);
2972     mapeditor::dtcolor = 0xFF0000FF;
2973     mapeditor::dtwidth = 0.1;
2974     }
2975   else if(argis("-picload")) { PHASE(3); shift(); mapeditor::loadPicFile(args()); }
2976   #endif
2977   else return 1;
2978   return 0;
2979   }
2980 
2981 auto ah_editor = addHook(hooks_args, 0, read_editor_args);
2982 #endif
2983 #endif
2984 }
2985