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