1 // Hyperbolic Rogue -- configuration
2 // Copyright (C) 2017-2018 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file config.cpp
5  *  \brief Configuration -- initial settings, saving/loading ini files, menus, etc.
6  */
7 
8 #include "hyper.h"
9 namespace hr {
10 
11 #if HDR
12 enum eCentering { face, edge, vertex };
13 #endif
14 
15 EX eCentering centering;
16 
17 EX function<bool()> auto_restrict;
18 
19 EX void add_to_changed(struct setting *f);
20 
return_false()21 EX bool return_false() { return false; }
22 
23 #if HDR
24 struct supersaver {
25   string name;
26   virtual string save() = 0;
27   virtual void load(const string& s) = 0;
28   virtual bool dosave() = 0;
29   virtual void reset() = 0;
affectshr::supersaver30   virtual bool affects(void* v) { return false; }
31   virtual void set_default() = 0;
32   virtual ~supersaver() = default;
33   };
34 
35 typedef vector<shared_ptr<supersaver>> saverlist;
36 
37 extern saverlist savers;
38 
39 struct setting {
40   function<bool()> restrict;
41   string parameter_name;
42   string config_name;
43   string menu_item_name;
44   string help_text;
45   reaction_t reaction;
46   char default_key;
47   cld last_value;
48   bool is_editable;
availablehr::setting49   virtual bool available() { if(restrict) return restrict(); return true; }
affectshr::setting50   virtual bool affects(void *v) { return false; }
add_as_saverhr::setting51   virtual void add_as_saver() {}
show_edit_optionhr::setting52   void show_edit_option() { show_edit_option(default_key); }
show_edit_optionhr::setting53   virtual void show_edit_option(char key) {
54     println(hlog, "default called!"); }
search_keyhr::setting55   virtual string search_key() {
56     return parameter_name + "|" + config_name + "|" + menu_item_name + "|" + help_text;
57     }
58   virtual cld get_cld() = 0;
settinghr::setting59   explicit setting() { restrict = auto_restrict; is_editable = false; }
check_changehr::setting60   virtual void check_change() {
61     cld val = get_cld();
62     if(val != last_value) {
63       last_value = val;
64       add_to_changed(this);
65       }
66     }
67   reaction_t sets;
set_setshr::setting68   setting *set_sets(const reaction_t& s) { sets = s; return this; }
69   setting *set_extra(const reaction_t& r);
70   setting *set_reaction(const reaction_t& r);
71   virtual ~setting() = default;
load_fromhr::setting72   virtual void load_from(const string& s) {
73     println(hlog, "cannot load this parameter");
74     exit(1);
75     }
76   };
77 #endif
78 
set_extra(const reaction_t & r)79 setting *setting::set_extra(const reaction_t& r) {
80   auto s = sets; set_sets([s, r] { if(s) s(); dialog::extra_options = r; }); return this;
81   }
82 
set_reaction(const reaction_t & r)83 setting *setting::set_reaction(const reaction_t& r) {
84   reaction = r; return this;
85   }
86 
87 EX map<string, std::unique_ptr<setting>> params;
88 
89 EX void show_edit_option_enum(char* value, const string& name, const vector<pair<string, string>>& options, char key, setting *s);
90 
91 #if HDR
92 struct list_setting : setting {
93   virtual int get_value() = 0;
94   virtual void set_value(int i) = 0;
95   vector<pair<string, string> > options;
editablehr::list_setting96   list_setting* editable(const vector<pair<string, string> >& o, string menu_item_name, char key) {
97     is_editable = true;
98     options = o;
99     this->menu_item_name = menu_item_name;
100     default_key = key;
101     return this;
102     }
103   void show_edit_option(char key) override;
104   };
105 
106 template<class T> struct enum_setting : list_setting {
107   T *value;
108   T dft;
get_valuehr::enum_setting109   int get_value() override { return (int) *value; }
set_valuehr::enum_setting110   void set_value(int i) override { *value = (T) i; }
affectshr::enum_setting111   bool affects(void* v) override { return v == value; }
112   void add_as_saver() override;
get_cldhr::enum_setting113   cld get_cld() override { return get_value(); }
load_fromhr::enum_setting114   void load_from(const string& s) override {
115     *value = (T) parseint(s);
116     }
117   };
118 
119 struct float_setting : public setting {
120   ld *value;
121   ld dft;
122   ld min_value, max_value, step;
123   string unit;
editablehr::float_setting124   float_setting *editable(ld min_value, ld max_value, ld step, string menu_item_name, string help_text, char key) {
125     is_editable = true;
126     this->min_value = min_value;
127     this->max_value = max_value;
128     this->menu_item_name = menu_item_name;
129     this->help_text = help_text;
130     this->step = step;
131     default_key = key;
132     return this;
133     }
134   function<void(float_setting*)> modify_me;
modifhr::float_setting135   float_setting *modif(const function<void(float_setting*)>& r) { modify_me = r; return this; }
136   void add_as_saver() override;
affectshr::float_setting137   bool affects(void *v) override { return v == value; }
138   void show_edit_option(char key) override;
get_cldhr::float_setting139   cld get_cld() override { return *value; }
140   void load_from(const string& s) override;
141   };
142 
143 struct int_setting : public setting {
144   int *value;
145   int dft;
146   int min_value, max_value;
147   ld step;
148   void add_as_saver() override;
149   function<void(int_setting*)> modify_me;
modifhr::int_setting150   int_setting *modif(const function<void(int_setting*)>& r) { modify_me = r; return this; }
affectshr::int_setting151   bool affects(void *v) override { return v == value; }
152   void show_edit_option(char key) override;
get_cldhr::int_setting153   cld get_cld() override { return *value; }
editablehr::int_setting154   int_setting *editable(int min_value, int max_value, ld step, string menu_item_name, string help_text, char key) {
155     this->min_value = min_value;
156     this->max_value = max_value;
157     this->menu_item_name = menu_item_name;
158     this->help_text = help_text;
159     this->step = step;
160     default_key = key;
161     return this;
162     }
163 
load_fromhr::int_setting164   void load_from(const string& s) override {
165     *value = parseint(s);
166     }
167   };
168 
169 struct bool_setting : public setting {
170   bool *value;
171   bool dft;
172   void add_as_saver() override;
173   reaction_t switcher;
editablehr::bool_setting174   bool_setting* editable(string cap, char key ) {
175     is_editable = true;
176     menu_item_name = cap; default_key = key; return this;
177     }
affectshr::bool_setting178   bool affects(void *v) override { return v == value; }
179   void show_edit_option(char key) override;
get_cldhr::bool_setting180   cld get_cld() override { return *value ? 1 : 0; }
load_fromhr::bool_setting181   void load_from(const string& s) override {
182     *value = parseint(s);
183     }
184   };
185 
186 struct custom_setting : public setting {
187   function<void(char)> custom_viewer;
188   function<cld()> custom_value;
189   function<bool(void*)> custom_affect;
show_edit_optionhr::custom_setting190   void show_edit_option(char key) override { custom_viewer(key); }
get_cldhr::custom_setting191   cld get_cld() override { return custom_value(); }
affectshr::custom_setting192   bool affects(void *v) override { return custom_affect(v); }
193   };
194 
195 #if CAP_CONFIG
196 
197 template<class T> struct dsaver : supersaver {
198   T& val;
199   T dft;
dosavehr::dsaver200   bool dosave() override { return val != dft; }
resethr::dsaver201   void reset() override { val = dft; }
dsaverhr::dsaver202   explicit dsaver(T& val) : val(val) { }
affectshr::dsaver203   bool affects(void* v) override { return v == &val; }
set_defaulthr::dsaver204   void set_default() override { dft = val; }
205   };
206 
207 template<class T> struct saver : dsaver<T> {};
208 
addsaver(T & i,U name,V dft)209 template<class T, class U, class V> void addsaver(T& i, U name, V dft) {
210   auto s = make_shared<saver<T>> (i);
211   s->dft = dft;
212   s->name = name;
213   savers.push_back(s);
214   }
215 
addsaver(T & i,string name)216 template<class T> void addsaver(T& i, string name) {
217   addsaver(i, name, i);
218   }
219 
removesaver(T & val)220 template<class T> void removesaver(T& val) {
221   for(int i=0; i<isize(savers); i++)
222     if(savers[i]->affects(&val))
223       savers.erase(savers.begin() + i);
224   }
225 
set_saver_default(T & val)226 template<class T> void set_saver_default(T& val) {
227   for(auto sav: savers)
228     if(sav->affects(&val))
229       sav->set_default();
230   }
231 
232 template<class T> struct saverenum : supersaver {
233   T& val;
234   T dft;
saverenumhr::saverenum235   explicit saverenum(T& v) : val(v) { }
dosavehr::saverenum236   bool dosave() override { return val != dft; }
resethr::saverenum237   void reset() override { val = dft; }
savehr::saverenum238   string save() override { return its(int(val)); }
loadhr::saverenum239   void load(const string& s) override { val = (T) atoi(s.c_str()); }
affectshr::saverenum240   bool affects(void* v) override { return v == &val; }
set_defaulthr::saverenum241   void set_default() override { dft = val; }
242   };
243 
addsaverenum(T & i,U name,T dft)244 template<class T, class U> void addsaverenum(T& i, U name, T dft) {
245   auto s = make_shared<saverenum<T>> (i);
246   s->dft = dft;
247   s->name = name;
248   savers.push_back(s);
249   }
250 
addsaverenum(T & i,U name)251 template<class T, class U> void addsaverenum(T& i, U name) {
252   addsaverenum(i, name, i);
253   }
254 
255 template<> struct saver<int> : dsaver<int> {
saverhr::saver256   explicit saver(int& val) : dsaver<int>(val) { }
savehr::saver257   string save() override { return its(val); }
loadhr::saver258   void load(const string& s) override { val = atoi(s.c_str()); }
259   };
260 
261 template<> struct saver<char> : dsaver<char> {
saverhr::saver262   explicit saver(char& val) : dsaver<char>(val) { }
savehr::saver263   string save() override { return its(val); }
loadhr::saver264   void load(const string& s) override { val = atoi(s.c_str()); }
265   };
266 
267 template<> struct saver<bool> : dsaver<bool> {
saverhr::saver268   explicit saver(bool& val) : dsaver<bool>(val) { }
savehr::saver269   string save() override { return val ? "yes" : "no"; }
loadhr::saver270   void load(const string& s) override { val = isize(s) && s[0] == 'y'; }
271   };
272 
273 template<> struct saver<unsigned> : dsaver<unsigned> {
saverhr::saver274   explicit saver(unsigned& val) : dsaver<unsigned>(val) { }
savehr::saver275   string save() override { return itsh(val); }
loadhr::saver276   void load(const string& s) override { val = (unsigned) strtoll(s.c_str(), NULL, 16); }
277   };
278 
279 template<> struct saver<string> : dsaver<string> {
saverhr::saver280   explicit saver(string& val) : dsaver<string>(val) { }
savehr::saver281   string save() override { return val; }
loadhr::saver282   void load(const string& s) override { val = s; }
283   };
284 
285 template<> struct saver<ld> : dsaver<ld> {
saverhr::saver286   explicit saver(ld& val) : dsaver<ld>(val) { }
savehr::saver287   string save() override { return fts(val, 10); }
loadhr::saver288   void load(const string& s) override {
289     if(s == "0.0000000000e+000") ; // ignore!
290     else val = atof(s.c_str());
291     }
292   };
293 #endif
294 #endif
295 
add_as_saver()296 void float_setting::add_as_saver() {
297 #if CAP_CONFIG
298   addsaver(*value, config_name, dft);
299 #endif
300   }
301 
add_as_saver()302 void int_setting::add_as_saver() {
303 #if CAP_CONFIG
304   addsaver(*value, config_name, dft);
305 #endif
306   }
307 
add_as_saver()308 void bool_setting::add_as_saver() {
309 #if CAP_CONFIG
310   addsaver(*value, config_name, dft);
311 #endif
312   }
313 
load_from(const string & s)314 void float_setting::load_from(const string& s) {
315   anims::animate_parameter(*value, s, reaction);
316   }
317 
non_editable()318 void non_editable() {
319   dialog::addHelp("Warning: editing this value through this menu may not work correctly");
320   }
321 
show_edit_option(char key)322 void float_setting::show_edit_option(char key) {
323   if(modify_me) modify_me(this);
324   dialog::addSelItem(XLAT(menu_item_name), fts(*value) + unit, key);
325   dialog::add_action([this] () {
326     add_to_changed(this);
327     dialog::editNumber(*value, min_value, max_value, step, dft, XLAT(menu_item_name), help_text);
328     if(sets) sets();
329     if(reaction) dialog::reaction = reaction;
330     if(!is_editable) dialog::extra_options = non_editable;
331     });
332   }
333 
show_edit_option(char key)334 void int_setting::show_edit_option(char key) {
335   if(modify_me) modify_me(this);
336   dialog::addSelItem(XLAT(menu_item_name), its(*value), key);
337   dialog::add_action([this] () {
338     add_to_changed(this);
339     dialog::editNumber(*value, min_value, max_value, step, dft, XLAT(menu_item_name), help_text);
340     if(sets) sets();
341     if(reaction) dialog::reaction = reaction;
342     if(!is_editable) dialog::extra_options = non_editable;
343     });
344   }
345 
show_edit_option(char key)346 void bool_setting::show_edit_option(char key) {
347   dialog::addBoolItem(XLAT(menu_item_name), *value, key);
348   dialog::add_action([this] () {
349     add_to_changed(this);
350     switcher(); if(sets) sets();
351     if(reaction) reaction();
352     });
353   }
354 
param_f(ld & val,const string p,const string s,ld dft)355 EX float_setting *param_f(ld& val, const string p, const string s, ld dft) {
356   unique_ptr<float_setting> u ( new float_setting );
357   u->parameter_name = p;
358   u->config_name = s;
359   u->menu_item_name = s;
360   u->value = &val;
361   u->last_value = dft;
362   if(dft == 0) {
363     u->min_value = -100;
364     u->max_value = +100;
365     }
366   else {
367     u->min_value = 0;
368     u->max_value = 2 * dft;
369     }
370   u->step = dft / 10;
371   u->dft = dft;
372   val = dft;
373   u->add_as_saver();
374   auto f = &*u;
375   params[u->parameter_name] = std::move(u);
376   return f;
377   }
378 
param_esc(string s)379 EX string param_esc(string s) {
380   string out;
381   for(char c: s)
382     if(c == ' ' || c == '-' || c == ':')
383       out += '_';
384     else
385       out += c;
386   return out;
387   }
388 
param_i(int & val,const string s,int dft)389 EX int_setting *param_i(int& val, const string s, int dft) {
390   unique_ptr<int_setting> u ( new int_setting );
391   u->parameter_name = param_esc(s);
392   u->config_name = s;
393   u->menu_item_name = s;
394   u->value = &val;
395   u->last_value = dft;
396   u->dft = dft;
397   val = dft;
398   if(dft == 0) {
399     u->min_value = -100;
400     u->max_value = +100;
401     }
402   else {
403     u->min_value = 0;
404     u->max_value = 2 * dft;
405     }
406   u->add_as_saver();
407   auto f = &*u;
408   params[u->parameter_name] = std::move(u);
409   return f;
410   }
411 
param_i(int & val,const string s)412 EX int_setting *param_i(int& val, const string s) { return param_i(val, s, val); }
413 
param_b(bool & val,const string s,bool dft)414 EX bool_setting *param_b(bool& val, const string s, bool dft) {
415   unique_ptr<bool_setting> u ( new bool_setting );
416   u->parameter_name = param_esc(s);
417   u->config_name = s;
418   u->menu_item_name = s;
419   u->value = &val;
420   u->last_value = dft;
421   u->dft = dft;
422   u->switcher = [&val] { val = !val; };
423   val = dft;
424   u->add_as_saver();
425   auto f = &*u;
426   params[u->parameter_name] = std::move(u);
427   return f;
428   }
429 
param_b(bool & val,const string s)430 EX bool_setting *param_b(bool& val, const string s) { return param_b(val, s, val); }
431 
432 #if HDR
add_as_saver()433 template<class T> void enum_setting<T>::add_as_saver() {
434 #if CAP_CONFIG
435   addsaverenum(*value, config_name, dft);
436 #endif
437   }
438 
param_enum(T & val,const string p,const string s,T dft)439 template<class T> enum_setting<T> *param_enum(T& val, const string p, const string s, T dft) {
440   unique_ptr<enum_setting<T>> u ( new enum_setting<T> );
441   u->parameter_name = p;
442   u->config_name = s;
443   u->menu_item_name = s;
444   u->value = &val;
445   u->dft = dft;
446   val = dft;
447   u->last_value = u->get_cld();
448   u->add_as_saver();
449   auto f = &*u;
450   params[p] = std::move(u);
451   return f;
452   }
453 #endif
454 
param_f(ld & val,const string s)455 EX float_setting* param_f(ld& val, const string s) {
456   return param_f(val, param_esc(s), s, val);
457   }
458 
param_f(ld & val,const string p,const string s)459 EX float_setting* param_f(ld& val, const string p, const string s) {
460   return param_f(val, p, s, val);
461   }
462 
param_f(ld & val,const string s,ld dft)463 EX float_setting* param_f(ld& val, const string s, ld dft) {
464   return param_f(val, param_esc(s), s, dft);
465   }
466 
467 #if HDR
468 template<class T>
param_custom(T & val,const string & s,function<void (char)> menuitem,char key)469 custom_setting* param_custom(T& val, const string& s, function<void(char)> menuitem, char key) {
470   unique_ptr<custom_setting> u ( new custom_setting );
471   u->parameter_name = param_esc(s);
472   u->config_name = s;
473   u->menu_item_name = s;
474   u->last_value = (int) val;
475   u->custom_viewer = menuitem;
476   u->custom_value = [&val] () { return (int) val; };
477   u->custom_affect = [&val] (void *v) { return &val == v; };
478   u->default_key = key;
479   u->is_editable = true;
480   auto f = &*u;
481   params[s] = std::move(u);
482   return f;
483   }
484 #endif
485 
486 EX ld bounded_mine_percentage = 0.1;
487 EX int bounded_mine_quantity, bounded_mine_max;
488 
489 EX const char *conffile = "hyperrogue.ini";
490 
491 /* extra space if more geometries are added */
492 EX array<ld, gGUARD+64> sightranges;
493 
494 EX videopar vid;
495 
496 #define DEFAULT_WALLMODE (ISMOBILE ? 3 : 5)
497 #define DEFAULT_MONMODE  (ISMOBILE ? 2 : 4)
498 
android_settings_changed()499 EX void android_settings_changed() {
500   #if ISANDROID
501   settingsChanged = true;
502   #endif
503   }
504 
505 extern color_t floorcolors[landtypes];
506 
getcs(int id IS (multi::cpid))507 EX charstyle& getcs(int id IS(multi::cpid)) {
508   if(multi::players>1 && id >= 0 && id < multi::players)
509     return multi::scs[id];
510   else
511     return vid.cs;
512   }
513 
514 struct charstyle_old {
515   int charid;
516   color_t skincolor, haircolor, dresscolor, swordcolor, dresscolor2, uicolor;
517   bool lefthanded;
518   };
519 
hread(hstream & hs,charstyle & cs)520 EX void hread(hstream& hs, charstyle& cs) {
521   // before 0xA61A there was no eyecolor
522   if(hs.get_vernum() < 0xA61A) {
523     charstyle_old cso;
524     hread_raw(hs, cso);
525     cs.charid = cso.charid;
526     cs.skincolor = cso.skincolor;
527     cs.haircolor = cso.haircolor;
528     cs.dresscolor = cso.dresscolor;
529     cs.swordcolor = cs.eyecolor = cso.swordcolor;
530     if(cs.charid < 4) cs.eyecolor = 0;
531     cs.dresscolor2 = cso.dresscolor2;
532     cs.uicolor = cso.uicolor;
533     cs.lefthanded = cso.lefthanded;
534     }
535   else hread_raw(hs, cs);
536   }
537 
hwrite(hstream & hs,const charstyle & cs)538 EX void hwrite(hstream& hs, const charstyle& cs) {
539   hwrite_raw(hs, cs);
540   }
541 
542 // void hread(hstream& hs, charstyle& cs) { hread_raw(hs, cs); }
543 // void hwrite(hstream& hs, const charstyle& cs) { hwrite_raw(hs, cs); }
544 
csnameid(int id)545 EX string csnameid(int id) {
546   if(id == 0) return XLAT("male");
547   if(id == 1) return XLAT("female");
548   if(id == 2) return XLAT("Prince");
549   if(id == 3) return XLAT("Princess");
550   if(id == 4 || id == 5) return XLAT("cat");
551   if(id == 6 || id == 7) return XLAT("dog");
552   if(id == 8 || id == 9) return XLATN("Familiar");
553   return XLAT("none");
554   }
555 
csname(charstyle & cs)556 EX string csname(charstyle& cs) {
557   return csnameid(cs.charid);
558   }
559 
playergender()560 EX int playergender() {
561   return (getcs().charid >= 0 && (getcs().charid&1)) ? GEN_F : GEN_M;
562   }
princessgender()563 EX int princessgender() {
564   int g = playergender();
565   if(vid.samegender) return g;
566   return g == GEN_M ? GEN_F : GEN_M;
567   }
568 
569 EX int default_language;
570 
lang()571 EX int lang() {
572   if(vid.language >= 0)
573     return vid.language;
574   return default_language;
575   }
576 
577 EX bool autojoy = true;
578 
579 #if CAP_CONFIG
580 saverlist savers;
581 #endif
582 
583 #if !CAP_CONFIG
addsaver(T & i,U name,V dft)584 template<class T, class U, class V> void addsaver(T& i, U name, V dft) {
585   i = dft;
586   }
587 
addsaver(T & i,U name)588 template<class T, class U> void addsaver(T& i, U name) {}
addsaverenum(T & i,U name)589 template<class T, class U> void addsaverenum(T& i, U name) {}
addsaverenum(T & i,U name,T dft)590 template<class T, class U> void addsaverenum(T& i, U name, T dft) { i = dft; }
591 #endif
592 
addsaver(charstyle & cs,string s)593 EX void addsaver(charstyle& cs, string s) {
594   addsaver(cs.charid, s + ".charid");
595   addsaver(cs.skincolor, s + ".skincolor");
596   addsaver(cs.eyecolor, s + ".eyecolor");
597   addsaver(cs.haircolor, s + ".haircolor");
598   addsaver(cs.dresscolor, s + ".dresscolor");
599   addsaver(cs.swordcolor, s + ".swordcolor");
600   addsaver(cs.dresscolor2, s + ".dresscolor2");
601   addsaver(cs.uicolor, s + ".uicolor");
602   addsaver(cs.lefthanded, s + ".lefthanded");
603   }
604 
605 // R:239, G:208, B:207
606 
607 unsigned int skincolors[]  = { 7, 0xD0D0D0FF, 0xEFD0C9FF, 0xC77A58FF, 0xA58869FF, 0x602010FF, 0xFFDCB1FF, 0xEDE4C8FF };
608 unsigned int haircolors[]  = { 8, 0x686868FF, 0x8C684AFF, 0xF2E1AEFF, 0xB55239FF, 0xFFFFFFFF, 0x804000FF, 0x502810FF, 0x301800FF };
609 unsigned int dresscolors[] = { 6, 0xC00000FF, 0x00C000FF, 0x0000C0FF, 0xC0C000FF, 0xC0C0C0FF, 0x202020FF };
610 unsigned int dresscolors2[] = { 7, 0x8080FFC0, 0x80FF80C0, 0xFF8080C0, 0xFFFF80C0, 0xFF80FFC0, 0x80FFFFC0, 0xFFFFFF80 };
611 unsigned int swordcolors[] = { 6, 0xC0C0C0FF, 0xFFFFFFFF, 0xFFC0C0FF, 0xC0C0FFFF, 0x808080FF, 0x202020FF };
612 unsigned int eyecolors[] = { 4, 0x00C000FF, 0x0000C0FF, 0xC00000FF, 0xC0C000FF, 0x804010FF, 0x00C000FF };
613 
initcs(charstyle & cs)614 EX void initcs(charstyle &cs) {
615   cs.charid     = 0;
616   cs.skincolor  = 0xD0D0D0FF;
617   cs.haircolor  = 0x686868FF;
618   cs.dresscolor = 0xC00000FF;
619   cs.swordcolor = 0xD0D0D0FF;
620   cs.dresscolor2= 0x8080FFC0;
621   cs.uicolor    = 0xFF0000FF;
622   cs.eyecolor   = 0x603000FF;
623   cs.lefthanded = false;
624   }
625 
savecolortable(colortable & ct,string name)626 EX void savecolortable(colortable& ct, string name) {
627   for(int i=0; i<isize(ct); i++)
628     addsaver(ct[i], "color:" + name + ":" + its(i));
629   }
630 
631 EX purehookset hooks_configfile;
632 
initConfig()633 EX void initConfig() {
634 
635   // basic config
636   param_i(vid.flashtime, "flashtime", 8);
637   param_enum(vid.msgleft, "message_style", "message style", 2)
638     -> editable({{"centered", ""}, {"left-aligned", ""}, {"line-broken", ""}}, "message style", 'a');
639 
640   param_i(vid.msglimit, "message limit", 5);
641   param_i(vid.timeformat, "message log time format", 0);
642 
643   param_b(resizable, "resizable", true)
644   -> editable("resizable window", 'r');
645 
646   param_b(display_yasc_codes, "yasc", false)
647   -> editable("YASC codes", 'Y')
648   -> set_reaction([] {
649     addMessage(XLAT("YASC codes: Sides-Entity-Restrict-Threat-Wall"));
650     });
651 
652   param_b(vid.relative_font, "relative_font", true)
653   -> editable("set relative font size", 'r')
654   -> set_reaction(compute_fsize);
655   param_i(vid.fontscale, "fontscale", 100)
656   -> editable(25, 400, 10, "font scale", "", 'b')
657   -> set_reaction(compute_fsize)
658   -> set_sets([] { dialog::bound_low(0); });
659 
660   param_i(vid.abs_fsize, "fsize", 12)
661   -> editable(1, 72, 1, "font size", "", 'b')
662   -> set_reaction(compute_fsize)
663   -> set_sets([] { dialog::bound_low(0); });
664 
665   param_i(vid.mobilecompasssize, "mobile compass size", 0); // ISMOBILE || ISPANDORA ? 30 : 0);
666   param_i(vid.radarsize, "radarsize size", 120);
667   addsaver(vid.radarrange, "radarrange", 2.5);
668   param_i(vid.axes, "movement help", 1);
669   param_b(vid.axes3, "movement help3", true);
670   param_i(vid.shifttarget, "shift-targetting", 2);
671   addsaver(vid.steamscore, "scores to Steam", 1);
672   initcs(vid.cs); addsaver(vid.cs, "single");
673   param_b(vid.samegender, "princess choice", false);
674   addsaver(vid.language, "language", -1);
675   param_b(vid.drawmousecircle, "mouse circle", ISMOBILE || ISPANDORA);
676   param_b(vid.revcontrol, "reverse control", false);
677   #if CAP_AUDIO
678   param_i(musicvolume, "music volume")
679   ->editable(0, 128, 10, "background music volume", "", 'b')
680   ->set_sets(sets_music_volume);
681   #endif
682   #if CAP_SDLAUDIO
683   addsaver(music_out_of_focus, "music out of focus", false);
684   #endif
685   #if CAP_AUDIO
686   param_i(effvolume, "sound effect volume")
687   ->editable(0, 128, 10, "sound effects volume", "", 'e')
688   ->set_sets(sets_sfx_volume);
689   #endif
690 
691   param_enum(vid.faraway_highlight, "faraway_highlight", "highlight faraway monsters", tlNoThreat)
692     ->editable({{"off", ""}, {"spam", ""}, {"normal monsters", ""}, {"high-threat monsters only", ""}}, "highlight faraway monsters", 'h');
693 
694   param_i(vid.faraway_highlight_color, "faraway_highlight_color", 50)
695   -> editable(0, 100, 10, "faraway highlight color", "0 = monster color, 100 = red-light oscillation", 'c');
696 
697   param_enum(glyphsortorder, "glyph_sort", "glyph sort order", glyphsortorder)
698     ->editable({
699       {"first on top", ""},
700       {"first on bottom", ""},
701       {"last on top", ""},
702       {"last on bottom", ""},
703       {"by land", ""},
704       {"by number", ""}
705       }, "inventory/kill sorting", 'k');
706 
707   // basic graphics
708 
709   param_b(vid.wantGL, "usingGL", true)
710   ->editable("openGL mode", 'o');
711 
712   addsaver(vid.want_antialias, "antialias", AA_NOGL | AA_FONT | (ISWEB ? AA_MULTI : AA_LINES) | AA_VERSION);
713   addsaver(vid.fineline, "fineline", true);
714   param_f(vid.linewidth, "linewidth", 1);
715   addsaver(precise_width, "precisewidth", .5);
716   addsaver(perfect_linewidth, "perfect_linewidth", 1);
717   param_f(linepatterns::width, "lpwidth", "pattern-linewidth", 1);
718   addsaver(fat_edges, "fat-edges");
719   param_f(vid.sspeed, "sspeed", "scrollingspeed", 0);
720   param_f(vid.mspeed, "mspeed", "movement speed", 1);
721   addsaver(vid.aurastr, "aura strength", ISMOBILE ? 0 : 128);
722   addsaver(vid.aurasmoothen, "aura smoothen", 5);
723   param_enum(vid.graphglyph, "graphglyph", "graphical items/kills", 1)
724   -> editable({{"letters", ""}, {"auto", ""}, {"images", ""}}, "inventory/kill mode", 'd');
725 
726   param_f(vid.binary_width, "bwidth", "binary-tiling-width", 1);
727   param_custom(vid.binary_width, "binary tiling width", menuitem_binary_width, 'v');
728 
729   addsaver(vid.particles, "extra effects", 1);
730   param_i(vid.framelimit, "frame limit", 999);
731 
732   #if !ISMOBWEB
733   param_b(vid.want_vsync, "vsync", true)
734   ->editable("vsync", 'v');
735   #endif
736 
737   param_b(vid.want_fullscreen, "fullscreen", false)
738   ->editable("fullscreen mode", 'f');
739   param_b(vid.change_fullscr, "fullscreen_change", false)
740   ->editable("use specific fullscreen resolution", 'g');
741   param_b(vid.relative_window_size, "window_relative", true)
742   ->editable("specify relative window size", 'g');
743 
744   param_custom(vid.xres, "xres", [] (char ch) {}, 0)->restrict = return_false;
745   param_custom(vid.yres, "yres", [] (char ch) {}, 0)->restrict = return_false;
746 
747   param_i(vid.fullscreen_x, "fullscreen_x", 1280)
748   -> editable(640, 3840, 640, "fullscreen resolution to use (X)", "", 'x')
749   -> set_sets([] { dialog::bound_low(640); });
750 
751   param_i(vid.fullscreen_y, "fullscreen_y", 1024)
752   -> editable(480, 2160, 480, "fullscreen resolution to use (Y)", "", 'x')
753   -> set_sets([] { dialog::bound_low(480); });
754 
755   param_i(vid.window_x, "window_x", 1280)
756   -> editable(160, 3840, 160, "window resolution to use (X)", "", 'x')
757   -> set_sets([] { dialog::bound_low(160); });
758 
759   param_i(vid.window_y, "window_y", 1024)
760   -> editable(120, 2160, 120, "window resolution to use (Y)", "", 'x')
761   -> set_sets([] { dialog::bound_low(120); });
762 
763   param_f(vid.window_rel_x, "window_rel_x", .9)
764   -> editable(.1, 1, .1, "screen size percentage to use (X)", "", 'x')
765   -> set_sets([] { dialog::bound_low(.1); });
766 
767   param_f(vid.window_rel_y, "window_rel_y", .9)
768   -> editable(.1, 1, .1, "screen size percentage to use (Y)", "", 'x')
769   -> set_sets([] { dialog::bound_low(.1); });
770 
771   param_b(vid.darkhepta, "mark heptagons", false);
772 
773   for(auto& lp: linepatterns::patterns) {
774     addsaver(lp->color, "lpcolor-" + lp->lpname);
775     addsaver(lp->multiplier, "lpwidth-" + lp->lpname);
776     }
777 
778   // special graphics
779 
780   addsaver(vid.monmode, "monster display mode", DEFAULT_MONMODE);
781   addsaver(vid.wallmode, "wall display mode", DEFAULT_WALLMODE);
782   addsaver(vid.highlightmode, "highlightmode");
783 
784   addsaver(vid.always3, "3D always", false);
785 
786   addsaver(memory_saving_mode, "memory_saving_mode", (ISMOBILE || ISPANDORA || ISWEB) ? 1 : 0);
787   param_i(reserve_limit, "memory_reserve", 128);
788   addsaver(show_memory_warning, "show_memory_warning");
789 
790   addsaver(rug::renderonce, "rug-renderonce");
791   addsaver(rug::rendernogl, "rug-rendernogl");
792   addsaver(rug::texturesize, "rug-texturesize");
793 #if CAP_RUG
794   param_f(rug::model_distance, "rug_model_distance", "rug-model-distance");
795 #endif
796 
797   param_b(vid.backeffects, "background particle effects", (ISMOBILE || ISPANDORA) ? false : true);
798   // control
799 
800   param_i(vid.joyvalue, "vid.joyvalue", 4800);
801   param_i(vid.joyvalue2, "vid.joyvalue2", 5600);
802   param_i(vid.joysmooth, "vid.joysmooth", 200);
803   param_i(vid.joypanthreshold, "vid.joypanthreshold", 2500);
804   param_f(vid.joypanspeed, "vid.joypanspeed", ISPANDORA ? 0.0001 : 0);
805   addsaver(autojoy, "autojoy");
806 
807   vid.killreduction = 0;
808 
809   param_b(vid.skipstart, "skip the start menu", false);
810   param_b(vid.quickmouse, "quick mouse", !ISPANDORA);
811 
812   // colors
813 
814   param_f(crosshair_size, "size:crosshair");
815   addsaver(crosshair_color, "color:crosshair");
816 
817   addsaver(backcolor, "color:background");
818   addsaver(forecolor, "color:foreground");
819   addsaver(bordcolor, "color:borders");
820   addsaver(ringcolor, "color:ring");
821   param_f(vid.multiplier_ring, "mring", "mult:ring", 1);
822   addsaver(modelcolor, "color:model");
823   addsaver(periodcolor, "color:period");
824   addsaver(stdgridcolor, "color:stdgrid");
825   param_f(vid.multiplier_grid, "mgrid", "mult:grid", 1);
826   addsaver(dialog::dialogcolor, "color:dialog");
827   for(auto& p: colortables)
828     savecolortable(p.second, s0+"canvas"+p.first);
829   savecolortable(distcolors, "distance");
830   savecolortable(minecolors, "mines");
831   #if CAP_COMPLEX2
832   savecolortable(brownian::colors, "color:brown");
833   #endif
834 
835   for(int i=0; i<motypes; i++)
836     addsaver(minf[i].color, "color:monster:" + its(i));
837   for(int i=0; i<ittypes; i++)
838     addsaver(iinf[i].color, "color:item:" + its(i));
839   for(int i=0; i<landtypes; i++)
840     addsaver(floorcolors[i], "color:land:" + its(i));
841   for(int i=0; i<walltypes; i++)
842     addsaver(winf[i].color, "color:wall:" + its(i));
843 
844   // modes
845 
846   addsaverenum(geometry, "mode-geometry");
847   addsaver(shmup::on, "mode-shmup", false);
848   addsaver(hardcore, "mode-hardcore", false);
849   addsaverenum(land_structure, "mode-chaos");
850   #if CAP_INV
851   addsaver(inv::on, "mode-Orb Strategy");
852   #endif
853   addsaverenum(variation, "mode-variation", eVariation::bitruncated);
854   addsaver(peace::on, "mode-peace");
855   addsaver(peace::otherpuzzles, "mode-peace-submode");
856   addsaverenum(specialland, "land for special modes");
857 
858   addsaver(viewdists, "expansion mode");
859   param_f(backbrightness, "back", "brightness behind sphere");
860 
861   param_f(vid.ipd, "ipd", "interpupilar-distance", 0.05);
862   param_f(vid.lr_eyewidth, "lr", "eyewidth-lr", 0.5);
863   param_f(vid.anaglyph_eyewidth, "anaglyph", "eyewidth-anaglyph", 0.1);
864   param_f(vid.fov, "fov", "field-of-vision", 90);
865   addsaver(vid.desaturate, "desaturate", 0);
866 
867   param_enum(vid.stereo_mode, "stereo_mode", "stereo-mode", vid.stereo_mode)
868     ->editable({{"OFF", ""}, {"anaglyph", ""}, {"side-by-side", ""}
869     #if CAP_ODS
870     , {"ODS", ""}
871     #endif
872     }, "stereo mode", 'm');
873 
874   param_f(vid.plevel_factor, "plevel_factor", 0.7);
875 
876   #if CAP_GP
877   addsaver(gp::param.first, "goldberg-x", gp::param.first);
878   addsaver(gp::param.second, "goldberg-y", gp::param.second);
879   #endif
880 
881   param_b(nohud, "no-hud", false);
882   param_b(nofps, "no-fps", false);
883 
884   #if CAP_IRR
885   addsaver(irr::density, "irregular-density", 2);
886   addsaver(irr::cellcount, "irregular-cellcount", 150);
887   addsaver(irr::quality, "irregular-quality", .2);
888   addsaver(irr::place_attempts, "irregular-place", 10);
889   addsaver(irr::rearrange_max_attempts, "irregular-rearrange-max", 50);
890   addsaver(irr::rearrange_less, "irregular-rearrangeless", 10);
891   #endif
892 
893   param_i(vid.linequality, "line quality", 0);
894 
895   #if CAP_FILES && CAP_SHOT && CAP_ANIMATIONS
896   addsaver(anims::animfile, "animation file format");
897   #endif
898 
899   #if CAP_RUG
900   addsaver(rug::move_on_touch, "rug move on touch");
901   #endif
902 
903   #if CAP_CRYSTAL
904   param_f(crystal::compass_probability, "cprob", "compass-probability");
905   addsaver(crystal::view_coordinates, "crystal-coordinates");
906   #endif
907 
908 #if CAP_TEXTURE
909   param_b(texture::texture_aura, "texture-aura", false);
910 #endif
911 
912   addsaver(vid.use_smart_range, "smart-range", 0);
913   param_f(vid.smart_range_detail, "smart-range-detail", 8)
914   ->editable(1, 50, 1, "minimum visible cell in pixels", "", 'd');
915 
916   param_f(vid.smart_range_detail_3, "smart-range-detail-3", 30)
917   ->editable(1, 50, 1, "minimum visible cell in pixels", "", 'd');
918 
919   param_b(vid.smart_area_based, "smart-area-based", false);
920   param_i(vid.cells_drawn_limit, "limit on cells drawn", 10000);
921   param_i(vid.cells_generated_limit, "limit on cells generated", 250);
922 
923   #if CAP_SOLV
924   addsaver(sn::solrange_xy, "solrange-xy");
925   addsaver(sn::solrange_z, "solrange-z");
926   #endif
927   addsaver(slr::steps, "slr-steps");
928   addsaver(slr::range_xy, "slr-range-xy");
929 
930   param_f(arcm::euclidean_edge_length, "arcm-euclid-length");
931 
932   #if CAP_ARCM
933   addsaver(arcm::current.symbol, "arcm-symbol", "4^5");
934   #endif
935   addsaverenum(hybrid::underlying, "product-underlying");
936 
937   for(int i=0; i<isize(ginf); i++) {
938     if(ginf[i].flags & qELLIPTIC)
939       sightranges[i] = M_PI;
940     else if(ginf[i].cclass == gcSphere)
941       sightranges[i] = 2 * M_PI;
942     else if(ginf[i].cclass == gcEuclid)
943       sightranges[i] = 10;
944     else if(ginf[i].cclass == gcSL2)
945       sightranges[i] = 4.5;
946     else if(ginf[i].cclass == gcHyperbolic && ginf[i].g.gameplay_dimension == 2)
947       sightranges[i] = 4.5;
948     else
949       sightranges[i] = 5;
950     sightranges[gArchimedean] = 10;
951     if(i < gBinary3) addsaver(sightranges[i], "sight-g" + its(i));
952     }
953 
954   ld bonus = 0;
955   ld emul = 1;
956 
957   param_b(dialog::onscreen_keyboard, "onscreen_keyboard")
958   ->editable("onscreen keyboard", 'k');
959 
960   param_b(context_fog, "coolfog");
961 
962   addsaver(sightranges[gBinary3], "sight-binary3", 3.1 + bonus);
963   addsaver(sightranges[gCubeTiling], "sight-cubes", 10);
964   addsaver(sightranges[gCell120], "sight-120cell", 2 * M_PI);
965   addsaver(sightranges[gECell120], "sight-120cell-elliptic", M_PI);
966   addsaver(sightranges[gRhombic3], "sight-rhombic", 10.5 * emul);
967   addsaver(sightranges[gBitrunc3], "sight-bitrunc", 12 * emul);
968   addsaver(sightranges[gSpace534], "sight-534", 4 + bonus);
969   addsaver(sightranges[gSpace435], "sight-435", 3.8 + bonus);
970 
971   addsaver(sightranges[gCell5], "sight-5cell", 2 * M_PI);
972   addsaver(sightranges[gCell8], "sight-8cell", 2 * M_PI);
973   addsaver(sightranges[gECell8], "sight-8cell-elliptic", M_PI);
974   addsaver(sightranges[gCell16], "sight-16cell", 2 * M_PI);
975   addsaver(sightranges[gECell16], "sight-16cell-elliptic", M_PI);
976   addsaver(sightranges[gCell24], "sight-24cell", 2 * M_PI);
977   addsaver(sightranges[gECell24], "sight-24cell-elliptic", M_PI);
978   addsaver(sightranges[gCell600], "sight-600cell", 2 * M_PI);
979   addsaver(sightranges[gECell600], "sight-600cell-elliptic", M_PI);
980   addsaver(sightranges[gHoroTris], "sight-horotris", 2.9 + bonus);
981   addsaver(sightranges[gHoroRec], "sight-hororec", 2.2 + bonus);
982   addsaver(sightranges[gHoroHex], "sight-horohex", 2.75 + bonus);
983 
984   addsaver(sightranges[gKiteDart3], "sight-kd3", 2.25 + bonus);
985 
986   addsaver(sightranges[gField435], "sight-field435", 4 + bonus);
987   addsaver(sightranges[gField534], "sight-field534", 3.8 + bonus);
988   addsaver(sightranges[gSol], "sight-sol");
989   addsaver(sightranges[gNil], "sight-nil", 6.5 + bonus);
990   addsaver(sightranges[gNIH], "sight-nih");
991   addsaver(sightranges[gSolN], "sight-solnih");
992 
993   addsaver(sightranges[gCrystal344], "sight-crystal344", 2.5); /* assume raycasting */
994   addsaver(sightranges[gSpace344], "sight-344", 4.5);
995   addsaver(sightranges[gSpace336], "sight-336", 4);
996 
997   param_b(vid.sloppy_3d, "sloppy3d", true);
998 
999   param_i(vid.texture_step, "wall-quality", 4);
1000 
1001   param_b(smooth_scrolling, "smooth-scrolling", false);
1002   addsaver(mouseaim_sensitivity, "mouseaim_sensitivity", 0.01);
1003 
1004   param_b(vid.consider_shader_projection, "shader-projection", true);
1005 
1006   param_b(tortoise::shading_enabled, "tortoise_shading", true);
1007 
1008   addsaver(bounded_mine_percentage, "bounded_mine_percentage");
1009 
1010   param_b(nisot::geodesic_movement, "solv_geodesic_movement", true);
1011 
1012   addsaver(s2xe::qrings, "s2xe-rings");
1013   addsaver(rots::underlying_scale, "rots-underlying-scale");
1014 
1015   param_b(vid.bubbles_special, "bubbles-special", 1);
1016   param_b(vid.bubbles_threshold, "bubbles-threshold", 1);
1017   param_b(vid.bubbles_all, "bubbles-all", 0);
1018 
1019 #if CAP_SHMUP
1020   multi::initConfig();
1021 #endif
1022 
1023   addsaver(asonov::period_xy, "asonov:period_xy");
1024   addsaver(asonov::period_z, "asonov:period_z");
1025   addsaver(nilv::nilperiod[0], "nilperiod_x");
1026   addsaver(nilv::nilperiod[1], "nilperiod_y");
1027   addsaver(nilv::nilperiod[2], "nilperiod_z");
1028 
1029   param_enum(neon_mode, "neon_mode", "neon_mode", neon_mode)
1030     ->editable(
1031         {{"OFF", ""}, {"standard", ""}, {"no boundary mode", ""}, {"neon mode II", ""}, {"illustration mode", ""}},
1032         "neon mode", 'M'
1033         );
1034 
1035   addsaverenum(neon_nofill, "neon_nofill");
1036   param_b(noshadow, "noshadow");
1037   param_b(bright, "bright");
1038   param_b(cblind, "cblind");
1039 
1040   addsaver(berger_limit, "berger_limit");
1041 
1042   addsaverenum(centering, "centering");
1043 
1044   param_f(camera_speed, "camspd", "camera-speed", 1);
1045   param_f(camera_rot_speed, "camrot", "camera-rot-speed", 1);
1046 
1047   param_f(panini_alpha, "panini_alpha", 0);
1048   param_f(stereo_alpha, "stereo_alpha", 0);
1049 
1050   callhooks(hooks_configfile);
1051 
1052   #if CAP_SHOT
1053   addsaver(levellines, "levellines");
1054   #endif
1055 
1056 #if CAP_CONFIG
1057   for(auto s: savers) s->reset();
1058 #endif
1059 
1060   param_custom(sightrange_bonus, "sightrange_bonus", menuitem_sightrange_bonus, 'r');
1061   param_custom(vid.use_smart_range, "sightrange_style", menuitem_sightrange_style, 's');
1062 
1063   param_custom(gp::param.first, "Goldberg x", menuitem_change_variation, 0);
1064   param_custom(gp::param.second, "Goldberg y", menuitem_change_variation, 0);
1065   param_custom(variation, "variation", menuitem_change_variation, 'v')
1066   ->help_text = "variation|dual|bitruncated";
1067   param_custom(geometry, "geometry", menuitem_change_geometry, 0)
1068   ->help_text = "hyperbolic|spherical|Euclidean";
1069 
1070   param_i(stamplen, "stamplen");
1071   }
1072 
inSpecialMode()1073 EX bool inSpecialMode() {
1074   return !ls::nice_walls() || ineligible_starting_land || !BITRUNCATED || peace::on ||
1075   #if CAP_TOUR
1076     tour::on ||
1077   #endif
1078     yendor::on || tactic::on || randomPatternsMode ||
1079     geometry != gNormal || pmodel != mdDisk || pconf.alpha != 1 || pconf.scale != 1 ||
1080     rug::rugged || vid.monmode != DEFAULT_MONMODE ||
1081     vid.wallmode != DEFAULT_WALLMODE;
1082   }
1083 
have_current_settings()1084 EX bool have_current_settings() {
1085   int modecount = 0;
1086   if(inv::on) modecount++;
1087   if(shmup::on) modecount += 10;
1088 #if CAP_TOUR
1089   if(tour::on) modecount += 10;
1090 #endif
1091   if(!ls::nice_walls()) modecount += 10;
1092   if(!BITRUNCATED) modecount += 10;
1093   if(peace::on) modecount += 10;
1094   if(yendor::on) modecount += 10;
1095   if(tactic::on) modecount += 10;
1096   if(randomPatternsMode) modecount += 10;
1097   if(geometry != gNormal) modecount += 10;
1098 
1099   if(modecount > 1)
1100     return true;
1101 
1102   return false;
1103   }
1104 
have_current_graph_settings()1105 EX bool have_current_graph_settings() {
1106   if(pconf.xposition || pconf.yposition || pconf.alpha != 1 || pconf.scale != 1)
1107     return true;
1108   if(pmodel != mdDisk || vid.monmode != DEFAULT_MONMODE || vid.wallmode != DEFAULT_WALLMODE)
1109     return true;
1110   if(firstland != laIce || multi::players != 1 || rug::rugged)
1111     return true;
1112 
1113   return false;
1114   }
1115 
reset_graph_settings()1116 EX void reset_graph_settings() {
1117   pmodel = mdDisk; pconf.alpha = 1; pconf.scale = 1;
1118   pconf.xposition = pconf.yposition = 0;
1119   #if CAP_RUG
1120   if(rug::rugged) rug::close();
1121   #endif
1122 
1123   vid.monmode = DEFAULT_MONMODE;
1124   vid.wallmode = DEFAULT_WALLMODE;
1125   }
1126 
1127 EX void resetModes(char leave IS('c')) {
1128   while(game_active || gamestack::pushed()) {
1129     if(game_active) stop_game();
1130     if(gamestack::pushed()) gamestack::pop();
1131     }
1132   if(shmup::on != (leave == rg::shmup)) stop_game_and_switch_mode(rg::shmup);
1133   if(inv::on != (leave == rg::inv)) stop_game_and_switch_mode(rg::inv);
1134 
1135   /* we do this twice to make sure that stop_game_and_switch_mode switches to the correct land_structure */
1136   for(int i=0; i<2; i++) {
1137     if(leave == rg::chaos && !ls::std_chaos()) stop_game_and_switch_mode(rg::chaos);
1138     if(leave != rg::chaos && !ls::nice_walls()) stop_game_and_switch_mode(rg::chaos);
1139     }
1140 
1141   if((!!dual::state) != (leave == rg::dualmode)) stop_game_and_switch_mode(rg::dualmode);
1142 
1143   if(peace::on != (leave == rg::peace)) stop_game_and_switch_mode(rg::peace);
1144 #if CAP_TOUR
1145   if(tour::on != (leave == rg::tour)) stop_game_and_switch_mode(rg::tour);
1146 #endif
1147   if(yendor::on != (leave == rg::yendor)) stop_game_and_switch_mode(rg::yendor);
1148   if(tactic::on != (leave == rg::tactic)) stop_game_and_switch_mode(rg::tactic);
1149   if(randomPatternsMode != (leave == rg::randpattern)) stop_game_and_switch_mode(rg::randpattern);
1150   if(multi::players != 1) {
1151     stop_game_and_switch_mode(); multi::players = 1;
1152     }
1153   if(firstland != laIce || specialland != laIce) {
1154     stop_game();
1155     firstland = laIce; specialland = laIce; stop_game_and_switch_mode();
1156     }
1157 
1158   set_geometry(gNormal);
1159   set_variation(leave == rg::heptagons ? eVariation::pure : eVariation::bitruncated);
1160 
1161   start_game();
1162   }
1163 
1164 #if CAP_CONFIG
resetConfig()1165 EX void resetConfig() {
1166   dynamicval<int> rx(vid.xres, 0);
1167   dynamicval<int> ry(vid.yres, 0);
1168   dynamicval<int> rf(vid.fsize, 0);
1169   dynamicval<bool> rfs(vid.full, false);
1170   for(auto s: savers)
1171     if(s->name.substr(0,5) != "mode-")
1172       s->reset();
1173   }
1174 #endif
1175 
1176 #if CAP_CONFIG
saveConfig()1177 EX void saveConfig() {
1178   DEBB(DF_INIT, ("save config\n"));
1179   FILE *f = fopen(conffile, "wt");
1180   if(!f) {
1181     addMessage(s0 + "Could not open the config file: " + conffile);
1182     return;
1183     }
1184 
1185   {
1186   int pt_depth = 0, pt_camera = 0, pt_alpha = 0;
1187   if(vid.tc_depth > vid.tc_camera) pt_depth++;
1188   if(vid.tc_depth < vid.tc_camera) pt_camera++;
1189   if(vid.tc_depth > vid.tc_alpha ) pt_depth++;
1190   if(vid.tc_depth < vid.tc_alpha ) pt_alpha ++;
1191   if(vid.tc_alpha > vid.tc_camera) pt_alpha++;
1192   if(vid.tc_alpha < vid.tc_camera) pt_camera++;
1193   vid.tc_alpha = pt_alpha;
1194   vid.tc_camera = pt_camera;
1195   vid.tc_depth = pt_depth;
1196   }
1197 
1198   for(auto s: savers) if(s->dosave())
1199     fprintf(f, "%s=%s\n", s->name.c_str(), s->save().c_str());
1200 
1201   fclose(f);
1202 #if !ISMOBILE
1203   addMessage(s0 + "Configuration saved to: " + conffile);
1204 #else
1205   addMessage(s0 + "Configuration saved");
1206 #endif
1207   }
1208 
readf(FILE * f,ld & x)1209 void readf(FILE *f, ld& x) {
1210   double fl = x;
1211   hr::ignore(fscanf(f, "%lf", &fl));
1212   x = fl;
1213   }
1214 
1215 map<string, shared_ptr<supersaver> > allconfigs;
1216 
parseline(const string & str)1217 EX void parseline(const string& str) {
1218   if(str[0] == '#') return;
1219   for(int i=0; i<isize(str); i++) if(str[i] == '=') {
1220     string cname = str.substr(0, i);
1221     if(!allconfigs.count(cname)) {
1222       printf("Warning: unknown config variable: %s\n", str.c_str());
1223       return;
1224       }
1225     auto sav = allconfigs[cname];
1226     sav->load(str.substr(i+1));
1227     return;
1228     }
1229   printf("Warning: config line without equality sign: %s\n", str.c_str());
1230   }
1231 
loadNewConfig(FILE * f)1232 EX void loadNewConfig(FILE *f) {
1233   for(auto& c: savers) allconfigs[c->name] = c;
1234   string rd;
1235   while(true) {
1236     int c = fgetc(f);
1237     if(c == -1) break;
1238     if(c == 10 || c == 13) {
1239       if(rd != "") parseline(rd);
1240       rd = "";
1241       }
1242     else rd += c;
1243     }
1244   allconfigs.clear();
1245   }
1246 
loadConfig()1247 EX void loadConfig() {
1248 
1249   DEBB(DF_INIT, ("load config"));
1250   vid.xres = 9999; vid.yres = 9999; vid.framelimit = 999;
1251   FILE *f = fopen(conffile, "rt");
1252   if(f) {
1253     int err;
1254     int fs;
1255     err=fscanf(f, "%d%d%d%d", &vid.xres, &vid.yres, &fs, &vid.fsize);
1256     if(err != 4)
1257       loadNewConfig(f);
1258     else {
1259       vid.full = fs;
1260       #if CAP_LEGACY
1261       loadOldConfig(f);
1262       #endif
1263       }
1264 
1265     fclose(f);
1266     DEBB(DF_INIT, ("Loaded configuration: %s\n", conffile));
1267     }
1268 
1269   geom3::apply_always3();
1270   polygonal::solve();
1271   check_cgi();
1272   cgi.prepare_basics();
1273   }
1274 #endif
1275 
1276 EX void add_cells_drawn(char c IS('C')) {
1277   dialog::addSelItem(XLAT("cells drawn"), (noclipped ? its(cells_drawn) + " (" + its(noclipped) + ")" : its(cells_drawn)) + " / " + its(vid.cells_drawn_limit), c);
__anond343dad31302() 1278   dialog::add_action([] () {
1279     dialog::editNumber(vid.cells_drawn_limit, 100, 1000000, log(10), 10000, XLAT("limit on cells drawn"),
1280       XLAT("This limit exists to protect the engine from freezing when too many cells would be drawn according to the current options.")
1281       );
1282     dialog::scaleLog();
1283     });
1284   if(WDIM == 3 || vid.use_smart_range == 2) {
1285     dialog::addSelItem(XLAT("limit generated cells per frame"), its(vid.cells_generated_limit), 'L');
__anond343dad31402() 1286     dialog::add_action([] () {
1287       dialog::editNumber(vid.cells_generated_limit, 1, 1000, log(10), 25, XLAT("limit generated cells per frame"),
1288         XLAT("In the 3D mode, lowering this value may help if the game lags while exploring new areas.")
1289         );
1290       });
1291     }
1292   }
1293 
solhelp()1294 string solhelp() {
1295 #if CAP_SOLV
1296   return XLAT(
1297     "Solv (aka Sol) is a 3D space where directions work in different ways. It is described by the following metric:\n"
1298     "ds² = (eᶻdx)² + (e⁻ᶻdy)² + dz²\n\n"
1299     "You are currently displaying Solv in the perspective projection based on native geodesics. You can control how "
1300     "the fog effects depends on the geodesic distance, and how far object in X/Y/Z coordinates are rendered."
1301     );
1302 #else
1303   return "";
1304 #endif
1305   }
1306 
menuitem_sightrange_bonus(char c)1307 EX void menuitem_sightrange_bonus(char c) {
1308   dialog::addSelItem(XLAT("sight range bonus"), its(sightrange_bonus), c);
1309   dialog::add_action([]{
1310     dialog::editNumber(sightrange_bonus, -5, allowIncreasedSight() ? 3 : 0, 1, 0, XLAT("sight range"),
1311       XLAT("Roughly 42% cells are on the edge of your sight range. Reducing "
1312       "the sight range makes HyperRogue work faster, but also makes "
1313       "the game effectively harder."));
1314     dialog::reaction = doOvergenerate;
1315     dialog::bound_low(1-getDistLimit());
1316     dialog::bound_up(allowIncreasedSight() ? euclid ? 99 : gp::dist_2() * 5 : 0);
1317     });
1318   }
1319 
edit_sightrange()1320 EX void edit_sightrange() {
1321   #if CAP_RUG
1322   USING_NATIVE_GEOMETRY_IN_RUG;
1323   #endif
1324   gamescreen(0);
1325   dialog::init("sight range settings");
1326   add_edit(vid.use_smart_range);
1327   if(vid.use_smart_range)
1328     add_edit(WDIM == 2 ? vid.smart_range_detail : vid.smart_range_detail_3);
1329   else {
1330     if(WDIM == 2) {
1331       add_edit(sightrange_bonus);
1332       if(GDIM == 3) {
1333         dialog::addSelItem(XLAT("3D sight range for the fog effect"), fts(sightranges[geometry]), 'r');
1334         dialog::add_action([] {
1335           dialog::editNumber(sightranges[geometry], 0, 2 * M_PI, 0.5, M_PI, XLAT("fog effect"), "");
1336           });
1337         }
1338       }
1339     if(WDIM == 3) {
1340       dialog::addSelItem(XLAT("3D sight range"), fts(sightranges[geometry]), 'r');
1341       dialog::add_action([] {
1342       dialog::editNumber(sightranges[geometry], 0, 2 * M_PI, 0.5, M_PI, XLAT("3D sight range"),
1343         XLAT(
1344           "Sight range for 3D geometries is specified in the absolute units. This value also affects the fog effect.\n\n"
1345           "In spherical geometries, the sight range of 2π will let you see things behind you as if they were in front of you, "
1346           "and the sight range of π (or more) will let you see things on the antipodal point just as if they were close to you.\n\n"
1347           "In hyperbolic geometries, the number of cells to render depends exponentially on the sight range. More cells to drawn "
1348           "reduces the performance.\n\n"
1349           "Sight range affects the gameplay, and monsters act iff they are visible. Monster generation takes this into account."
1350           )
1351         );
1352         });
1353       }
1354     }
1355   #if CAP_SOLV
1356   if(pmodel == mdGeodesic && sol) {
1357     dialog::addSelItem(XLAT("max difference in X/Y coordinates"), fts(sn::solrange_xy), 'x');
1358     dialog::add_action([] {
1359       dialog::editNumber(sn::solrange_xy, 0.01, 200, 0.1, 50, XLAT("max difference in X/Y coordinates"), solhelp()), dialog::scaleLog();
1360       });
1361     dialog::addSelItem(XLAT("max difference in Z coordinate"), fts(sn::solrange_z), 'z');
1362     dialog::add_action([] {
1363       dialog::editNumber(sn::solrange_z, 0, 20, 0.1, 6, XLAT("max difference in Z coordinates"), solhelp());
1364       });
1365     }
1366   #endif
1367   if(pmodel == mdGeodesic && sl2) {
1368     dialog::addSelItem(XLAT("max difference in X/Y coordinates"), fts(slr::range_xy), 'x');
1369     dialog::add_action([] {
1370       dialog::editNumber(slr::range_xy, 0, 10, 0.5, 4, XLAT("max difference in X/Y coordinates"), "");
1371       });
1372     dialog::addSelItem(XLAT("steps"), its(slr::steps), 'z');
1373     dialog::add_action([] {
1374       dialog::editNumber(slr::steps, 0, 50, 1, 10, "", "");
1375       });
1376     }
1377   if(vid.use_smart_range && WDIM == 2) {
1378     dialog::addBoolItem_action(XLAT("area-based range"), vid.smart_area_based, 'a');
1379     }
1380   if(vid.use_smart_range == 0 && allowChangeRange() && WDIM == 2) {
1381     dialog::addSelItem(XLAT("generation range bonus"), its(genrange_bonus), 'o');
1382     dialog::add_action([] () { genrange_bonus = sightrange_bonus; doOvergenerate(); });
1383     dialog::addSelItem(XLAT("game range bonus"), its(gamerange_bonus), 's');
1384     dialog::add_action([] () { gamerange_bonus = sightrange_bonus; doOvergenerate(); });
1385     }
1386   if(WDIM == 3 && !vid.use_smart_range) {
1387     dialog::addBoolItem_action(XLAT("sloppy range checking"), vid.sloppy_3d, 's');
1388     }
1389   if(GDIM == 3 && !vid.use_smart_range) {
1390     dialog::addSelItem(XLAT("limit generation"), fts(extra_generation_distance), 'e');
1391     dialog::add_action([] {
1392       dialog::editNumber(extra_generation_distance, 0, 999, 0.5, 999, XLAT("limit generation"),
1393         "Cells over this distance will not be generated, but they will be drawn if they are already generated and in the sight range."
1394         );
1395       });
1396     }
1397   add_cells_drawn('c');
1398   dialog::display();
1399   }
1400 
1401 EX void menuitem_sightrange_style(char c IS('c')) {
1402   dialog::addSelItem(XLAT("draw range based on"),
1403     vid.use_smart_range == 0 ? XLAT("distance") :
1404     vid.use_smart_range == 1 ? XLAT("size (no gen)") :
1405     XLAT("size"),
1406     c
1407     );
__anond343dad31f02null1408   dialog::add_action_push([] {
1409     dialog::init(XLAT("draw range based on"));
1410     dialog::addBoolItem(XLAT("draw range based on distance"), vid.use_smart_range == 0, 'd');
1411     dialog::add_action([] () { vid.use_smart_range = 0; popScreen(); edit_sightrange(); });
1412     if(WDIM == 2 && allowIncreasedSight()) {
1413       dialog::addBoolItem(XLAT("draw based on size in the projection (no generation)"), vid.use_smart_range == 1, 'n');
1414       dialog::add_action([] () { vid.use_smart_range = 1; popScreen(); edit_sightrange(); });
1415       }
1416     if(allowChangeRange() && allowIncreasedSight()) {
1417       dialog::addBoolItem(XLAT("draw based on size in the projection (generation)"), vid.use_smart_range == 2, 'g');
1418       dialog::add_action([] () { vid.use_smart_range = 2; popScreen(); edit_sightrange(); });
1419       }
1420     if(!allowChangeRange() || !allowIncreasedSight()) {
1421       dialog::addItem(XLAT("enable the cheat mode for additional options"), 'X');
1422       dialog::add_action(enable_cheat);
1423       }
1424     dialog::display();
1425     });
1426   }
1427 
1428 EX void menuitem_sightrange(char c IS('c')) {
1429   #if CAP_SOLV
1430   if(pmodel == mdGeodesic && sol)
1431     dialog::addSelItem(XLAT("sight range settings"), fts(sn::solrange_xy) + "x" + fts(sn::solrange_z), c);
1432   else
1433   #endif
1434   if(vid.use_smart_range)
1435     dialog::addSelItem(XLAT("sight range settings"), fts(WDIM == 3 ? vid.smart_range_detail_3 : vid.smart_range_detail) + " px", c);
1436   else if(WDIM == 3)
1437     dialog::addSelItem(XLAT("sight range settings"), fts(sightranges[geometry]) + "au", c);
1438   else
1439     dialog::addSelItem(XLAT("sight range settings"), format("%+d", sightrange_bonus), c);
1440   dialog::add_action_push(edit_sightrange);
1441   }
1442 
sets_sfx_volume()1443 EX void sets_sfx_volume() {
1444 #if CAP_AUDIO
1445   dialog::numberdark = dialog::DONT_SHOW;
1446   #if ISANDROID
1447   dialog::reaction = [] () {
1448     settingsChanged = true;
1449     };
1450   #endif
1451   dialog::bound_low(0);
1452   dialog::bound_up(MIX_MAX_VOLUME);
1453 #endif
1454   }
1455 
sets_music_volume()1456 EX void sets_music_volume() {
1457 #if CAP_AUDIO
1458   dialog::numberdark = dialog::DONT_SHOW;
1459   dialog::reaction = [] () {
1460     #if CAP_SDLAUDIO
1461     Mix_VolumeMusic(musicvolume);
1462     #endif
1463     #if ISANDROID
1464     settingsChanged = true;
1465     #endif
1466     };
1467   dialog::bound_low(0);
1468   dialog::bound_up(MIX_MAX_VOLUME);
1469   #if CAP_SDLAUDIO
1470   dialog::extra_options = [] {
1471     dialog::addBoolItem_action(XLAT("play music when out of focus"), music_out_of_focus, 'A');
1472     };
1473   #endif
1474 #endif
1475   }
1476 
showSpecialEffects()1477 EX void showSpecialEffects() {
1478   cmode = vid.xres > vid.yres * 1.4 ? sm::SIDE : sm::MAYDARK;
1479   gamescreen(0);
1480   dialog::init(XLAT("extra graphical effects"));
1481 
1482   dialog::addBoolItem_action(XLAT("particles on attack"), (vid.particles), 'p');
1483   dialog::addBoolItem_action(XLAT("floating bubbles: special"), vid.bubbles_special, 's');
1484   dialog::addBoolItem_action(XLAT("floating bubbles: treasure thresholds"), vid.bubbles_threshold, 't');
1485   dialog::addBoolItem_action(XLAT("floating bubbles: all treasures"), vid.bubbles_all, 'a');
1486   dialog::addBoolItem_action(XLAT("background particle effects"), (vid.backeffects), 'b');
1487 
1488   dialog::addBreak(50);
1489   dialog::addBack();
1490   dialog::display();
1491   }
1492 
show_vector_settings()1493 EX void show_vector_settings() {
1494   cmode = vid.xres > vid.yres * 1.4 ? sm::SIDE : sm::MAYDARK;
1495   gamescreen(0);
1496   dialog::init(XLAT("vector settings"));
1497 
1498   dialog::addSelItem(XLAT("line width"), fts(vid.linewidth), 'w');
1499   dialog::add_action([] {
1500      dialog::editNumber(vid.linewidth, 0, 10, 0.1, 1, XLAT("line width"),
1501        vid.usingGL ? "" : XLAT("Line width setting is only taken into account in OpenGL."));
1502      });
1503 
1504   dialog::addSelItem(XLAT("line quality"), its(vid.linequality), 'l');
1505   dialog::add_action([] {
1506     dialog::editNumber(vid.linequality, -3, 5, 1, 1, XLAT("line quality"),
1507       XLAT("Higher numbers make the curved lines smoother, but reduce the performance."));
1508     });
1509 
1510   dialog::addBoolItem("perfect width", perfect_linewidth == 2, 'p');
1511   if(perfect_linewidth == 1)
1512     dialog::lastItem().value = XLAT("shots only");
1513   dialog::add_action([] { perfect_linewidth = (1 + perfect_linewidth) % 3; });
1514 
1515   dialog::addBoolItem_action("finer lines at the boundary", vid.fineline, 'O');
1516 
1517   if(vid.fineline) {
1518     dialog::addSelItem("variable width", fts(precise_width), 'm');
1519     dialog::add_action([] () {
1520       dialog::editNumber(precise_width, 0, 2, 0.1, 0.5,
1521         XLAT("variable width"), XLAT("lines longer than this value will be split into shorter lines, with width computed separately for each of them.")
1522         );
1523       });
1524     }
1525   else dialog::addBreak(100);
1526 
1527   add_edit(neon_mode);
1528   dialog::addBreak(100);
1529   dialog::addInfo(XLAT("hint: press Alt while testing modes"));
1530   dialog::addBreak(100);
1531   dialog::addBoolItem_action(XLAT("disable shadows"), noshadow, 'f');
1532   dialog::addBoolItem_action(XLAT("bright mode"), bright, 'g');
1533   dialog::addBoolItem_action(XLAT("colorblind simulation"), cblind, 'h');
1534 
1535   dialog::addBoolItem_action(XLAT("no fill in neon mode"), neon_nofill, 'n');
1536 
1537   dialog::addBreak(50);
1538   dialog::addBack();
1539   dialog::display();
1540   }
1541 
showGraphConfig()1542 EX void showGraphConfig() {
1543   cmode = vid.xres > vid.yres * 1.4 ? sm::SIDE : sm::MAYDARK;
1544   gamescreen(0);
1545 
1546   dialog::init(XLAT("graphics configuration"));
1547 
1548 #if !ISIOS && !ISWEB
1549   add_edit(vid.want_fullscreen);
1550 
1551   #if !ISANDROID && !ISFAKEMOBILE
1552   if(vid.want_fullscreen) {
1553     add_edit(vid.change_fullscr);
1554     if(vid.change_fullscr)
1555       add_edit(vid.fullscreen_x), add_edit(vid.fullscreen_y);
1556     else
1557       dialog::addBreak(200);
1558     }
1559   else {
1560     add_edit(vid.relative_window_size);
1561     if(vid.relative_window_size)
1562       add_edit(vid.window_rel_x), add_edit(vid.window_rel_y);
1563     else
1564       add_edit(vid.window_x), add_edit(vid.window_y);
1565     }
1566   #endif
1567 #endif
1568 
1569   #if CAP_GLORNOT
1570   add_edit(vid.wantGL);
1571   #endif
1572 
1573   #if !ISIOS && !ISANDROID && !ISFAKEMOBILE
1574   if(!vid.usingGL) {
1575     dialog::addBoolItem(XLAT("anti-aliasing"), vid.want_antialias & AA_NOGL, 'O');
1576     dialog::add_action([] {
1577       if(!vid.usingGL)
1578         vid.want_antialias ^= AA_NOGL | AA_FONT;
1579       });
1580     }
1581   else {
1582     dialog::addSelItem(XLAT("anti-aliasing"),
1583       (vid.want_antialias & AA_POLY) ? "polygons" :
1584       (vid.want_antialias & AA_LINES) ? "lines" :
1585       (vid.want_antialias & AA_MULTI) ? "multisampling" :
1586       "NO", 'O');
1587     dialog::add_action([] {
1588       if(vid.want_antialias & AA_MULTI)
1589         vid.want_antialias ^= AA_MULTI;
1590       else if(vid.want_antialias & AA_POLY)
1591         vid.want_antialias ^= AA_POLY | AA_LINES | AA_MULTI;
1592       else if(vid.want_antialias & AA_LINES)
1593         vid.want_antialias |= AA_POLY;
1594       else
1595         vid.want_antialias |= AA_LINES;
1596       });
1597     }
1598   #endif
1599 
1600   #if !ISIOS && !ISANDROID && !ISFAKEMOBILE
1601   if(vid.usingGL) {
1602     if(vrhr::active())
1603       dialog::addInfo(XLAT("(vsync disabled in VR)"));
1604     else
1605       add_edit(vid.want_vsync);
1606     }
1607   else
1608     dialog::addBreak(100);
1609   #endif
1610 
1611   if(need_to_apply_screen_settings()) {
1612     dialog::addItem(XLAT("apply changes"), 'A');
1613     dialog::add_action(apply_screen_settings);
1614     dialog::addBreak(100);
1615     }
1616   else
1617     dialog::addBreak(200);
1618 
1619   add_edit(vid.relative_font);
1620   if(vid.relative_font)
1621     add_edit(vid.fontscale);
1622   else
1623     add_edit(vid.abs_fsize);
1624 
1625   dialog::addSelItem(XLAT("vector settings"), XLAT("width") + " " + fts(vid.linewidth), 'w');
1626   dialog::add_action_push(show_vector_settings);
1627 
1628   #if CAP_FRAMELIMIT
1629   dialog::addSelItem(XLAT("framerate limit"), its(vid.framelimit), 'l');
1630   if(getcstat == 'l')
1631     mouseovers = XLAT("Reduce the framerate limit to conserve CPU energy");
1632   #endif
1633 
1634   dialog::addSelItem(XLAT("scrolling speed"), fts(vid.sspeed), 'a');
1635 
1636   dialog::addSelItem(XLAT("camera movement speed"), fts(camera_speed), 'c');
1637   dialog::add_action([] {
1638     dialog::editNumber(camera_speed, -10, 10, 0.1, 1, XLAT("camera movement speed"),
1639       "This affects:\n\nin 2D: scrolling with arrow keys and Wheel Up\n\nin 3D: camera movement with Home/End."
1640       );
1641     });
1642   dialog::addSelItem(XLAT("camera rotation speed"), fts(camera_rot_speed), 'r');
1643   dialog::add_action([] {
1644     dialog::editNumber(camera_rot_speed, -10, 10, 0.1, 1, XLAT("camera rotation speed"),
1645       "This affects view rotation with Page Up/Down, and in 3D, camera rotation with arrow keys or mouse."
1646       );
1647     });
1648 
1649   dialog::addSelItem(XLAT("movement animation speed"), fts(vid.mspeed), 'm');
1650 
1651   dialog::addItem(XLAT("extra graphical effects"), 'u');
1652 
1653   dialog::addBreak(50);
1654   dialog::addBack();
1655   dialog::display();
1656 
1657   keyhandler = [] (int sym, int uni) {
1658     dialog::handleNavigation(sym, uni);
1659 
1660     char xuni = uni | 96;
1661 
1662     if((uni >= 32 && uni < 64) || uni == 'L' || uni == 'C') xuni = uni;
1663 
1664     if(xuni == 'u') pushScreen(showSpecialEffects);
1665 
1666     else if(xuni == 'a') dialog::editNumber(vid.sspeed, -5, 5, 1, 0,
1667       XLAT("scrolling speed"),
1668       XLAT("+5 = center instantly, -5 = do not center the map")
1669       + "\n\n" +
1670       XLAT("press Space or Home to center on the PC"));
1671 
1672     else if(xuni == 'm') dialog::editNumber(vid.mspeed, -5, 5, 1, 0,
1673       XLAT("movement animation speed"),
1674       XLAT("+5 = move instantly"));
1675 
1676   #if CAP_FRAMELIMIT
1677     else if(xuni == 'l') {
1678       dialog::editNumber(vid.framelimit, 5, 300, 10, 300, XLAT("framerate limit"), "");
1679       dialog::bound_low(5);
1680       }
1681   #endif
1682 
1683     else if(xuni =='p')
1684       vid.backeffects = !vid.backeffects;
1685 
1686     else if(doexiton(sym, uni)) popScreen();
1687     };
1688   }
1689 
edit_whatever(char type,int index)1690 EX void edit_whatever(char type, int index) {
1691   if(type == 'f') {
1692     dialog::editNumber(whatever[index], -10, 10, 1, 0, XLAT("whatever"),
1693       "f:" + its(index));
1694     }
1695   else {
1696     dialog::editNumber(whateveri[index], -10, 10, 1, 0, XLAT("whatever"),
1697       "i:" + its(index));
1698     }
1699   dialog::extra_options = [type, index] {
1700     dialog::addItem(XLAT("integer"), 'X');
1701     dialog::add_action( [index] { popScreen(); edit_whatever('i', index); });
1702     dialog::addItem(XLAT("float"), 'Y');
1703     dialog::add_action( [index] { popScreen(); edit_whatever('f', index); });
1704     for(int x=0; x<8; x++) {
1705       dialog::addSelItem(its(x), type == 'i' ? its(whateveri[x]) : fts(whatever[x]), 'A' + x);
1706       dialog::add_action([type,x] { popScreen(); edit_whatever(type, x); });
1707       }
1708     };
1709   }
1710 
configureOther()1711 EX void configureOther() {
1712   gamescreen(3);
1713 
1714   dialog::init(XLAT("other settings"));
1715 
1716 #if ISSTEAM
1717   dialog::addBoolItem(XLAT("send scores to Steam leaderboards"), (vid.steamscore&1), 'x');
1718   dialog::add_action([] {vid.steamscore = vid.steamscore^1; });
1719 #endif
1720 
1721   dialog::addBoolItem_action(XLAT("skip the start menu"), vid.skipstart, 'm');
1722 
1723   dialog::addItem(XLAT("memory configuration"), 'y');
1724   dialog::add_action_push(show_memory_menu);
1725 
1726   // dialog::addBoolItem_action(XLAT("forget faraway cells"), memory_saving_mode, 'y');
1727 
1728 #if CAP_AUDIO
1729   add_edit(musicvolume);
1730   add_edit(effvolume);
1731 #endif
1732 
1733   menuitem_sightrange('r');
1734 
1735   add_edit(vid.faraway_highlight);
1736   add_edit(vid.faraway_highlight_color);
1737 
1738 #ifdef WHATEVER
1739   dialog::addSelItem(XLAT("whatever"), fts(whatever[0]), 'j');
1740   dialog::add_action([] { edit_whatever('f', 0); });
1741 #endif
1742 
1743   dialog::addBreak(50);
1744   dialog::addBack();
1745 
1746   dialog::display();
1747   }
1748 
configureInterface()1749 EX void configureInterface() {
1750   gamescreen(3);
1751   dialog::init(XLAT("interface"));
1752 
1753 #if CAP_TRANS
1754   dialog::addSelItem(XLAT("language"), XLAT("EN"), 'l');
1755   dialog::add_action_push(selectLanguageScreen);
1756 #endif
1757 
1758   dialog::addSelItem(XLAT("player character"), numplayers() > 1 ? "" : csname(vid.cs), 'g');
1759   dialog::add_action_push(showCustomizeChar);
1760   if(getcstat == 'g') mouseovers = XLAT("Affects looks and grammar");
1761 
1762   dialog::addSelItem(XLAT("message flash time"), its(vid.flashtime), 't');
1763   dialog::add_action([] {
1764     dialog::editNumber(vid.flashtime, 0, 64, 1, 8, XLAT("message flash time"),
1765       XLAT("How long should the messages stay on the screen."));
1766     dialog::bound_low(0);
1767     });
1768 
1769   dialog::addSelItem(XLAT("limit messages shown"), its(vid.msglimit), 'z');
1770   dialog::add_action([] {
1771     dialog::editNumber(vid.msglimit, 0, 64, 1, 5, XLAT("limit messages shown"),
1772       XLAT("Maximum number of messages on screen."));
1773     dialog::bound_low(0);
1774     });
1775 
1776   add_edit(vid.msgleft);
1777 
1778   add_edit(glyphsortorder);
1779   add_edit(vid.graphglyph);
1780 
1781   add_edit(display_yasc_codes);
1782 
1783   dialog::addSelItem(XLAT("draw crosshair"), crosshair_size > 0 ? fts(crosshair_size) : ONOFF(false), 'x');
1784   dialog::add_action([] () {
1785     dialog::editNumber(crosshair_size, 0, 100, 1, 10, XLAT("crosshair size"), XLAT(
1786       "Display a targetting reticle in the center of the screen. Might be useful when exploring 3D modes, "
1787       "as it precisely shows the direction we are going. However, the option is available in all modes."
1788       ));
1789     dialog::bound_low(0);
1790     dialog::extra_options = [] {
1791       dialog::addColorItem(XLAT("crosshair color"), crosshair_color, 'X');
1792       dialog::add_action([] { dialog::openColorDialog(crosshair_color); });
1793       };
1794     });
1795 
1796   dialog::addBreak(50);
1797   dialog::addBack();
1798 
1799   dialog::display();
1800   }
1801 
1802 #if CAP_SDLJOY
showJoyConfig()1803 EX void showJoyConfig() {
1804   gamescreen(4);
1805 
1806   dialog::init(XLAT("joystick configuration"));
1807 
1808   dialog::addSelItem(XLAT("first joystick position (movement)"), its(joyx)+","+its(joyy), 0);
1809   dialog::addSelItem(XLAT("second joystick position (panning)"), its(panjoyx)+","+its(panjoyy), 0);
1810 
1811   dialog::addSelItem(XLAT("joystick mode"), autojoy ? XLAT("automatic") : XLAT("manual"), 'p');
1812   if(getcstat == 'p') {
1813     if(autojoy)
1814       mouseovers = XLAT("joystick mode: automatic (release the joystick to move)");
1815     if(!autojoy)
1816       mouseovers = XLAT("joystick mode: manual (press a button to move)");
1817     }
1818 
1819   dialog::addSelItem(XLAT("first joystick: movement threshold"), its(vid.joyvalue), 'a');
1820   dialog::addSelItem(XLAT("first joystick: execute movement threshold"), its(vid.joyvalue2), 'b');
1821   dialog::addSelItem(XLAT("second joystick: pan threshold"), its(vid.joypanthreshold), 'c');
1822   dialog::addSelItem(XLAT("second joystick: panning speed"), fts(vid.joypanspeed * 1000), 'd');
1823   dialog::addSelItem(XLAT("smoothen"), its(vid.joysmooth) + " ms", 'e');
1824 
1825   dialog::addBreak(50);
1826   dialog::addBack();
1827   dialog::display();
1828 
1829   keyhandler = [] (int sym, int uni) {
1830     dialog::handleNavigation(sym, uni);
1831     if(uni == 'p') autojoy = !autojoy;
1832     else if(uni == 'a') {
1833       dialog::editNumber(vid.joyvalue, 0, 32768, 100, 4800, XLAT("first joystick: movement threshold"), "");
1834       dialog::bound_low(0);
1835       }
1836     else if(uni == 'b') {
1837       dialog::editNumber(vid.joyvalue2, 0, 32768, 100, 5600, XLAT("first joystick: execute movement threshold"), "");
1838       dialog::bound_low(0);
1839       }
1840     else if(uni == 'c') {
1841       dialog::editNumber(vid.joypanthreshold, 0, 32768, 100, 2500, XLAT("second joystick: pan threshold"), "");
1842       dialog::bound_low(0);
1843       }
1844     else if(uni == 'd')
1845       dialog::editNumber(vid.joypanspeed, 0, 1e-2, 1e-5, 1e-4, XLAT("second joystick: panning speed"), "");
1846     else if(uni == 'e')
1847       dialog::editNumber(vid.joypanspeed, 0, 2000, 20, 200, XLAT("smoothen"), "large values help if the joystick is imprecise");
1848 
1849     else if(doexiton(sym, uni)) popScreen();
1850     };
1851   }
1852 #endif
1853 
projectionDialog()1854 EX void projectionDialog() {
1855   vid.tc_alpha = ticks;
1856   dialog::editNumber(vpconf.alpha, -5, 5, .1, 1,
1857     XLAT("projection distance"),
1858     XLAT("HyperRogue uses the Minkowski hyperboloid model internally. "
1859     "Klein and Poincaré models can be obtained by perspective, "
1860     "and the Gans model is obtained by orthogonal projection. "
1861     "See also the conformal mode (in the special modes menu) "
1862     "for more models."));
1863   dialog::extra_options = [] () {
1864     dialog::addBreak(100);
1865     if(GDIM == 2) dialog::addHelp(XLAT(
1866       "If we are viewing an equidistant g absolute units below a plane, "
1867       "from a point c absolute units above the plane, this corresponds "
1868       "to viewing a Minkowski hyperboloid from a point "
1869       "tanh(g)/tanh(c) units below the center. This in turn corresponds to "
1870       "the Poincaré model for g=c, and Klein-Beltrami model for g=0."));
1871     dialog::addSelItem(sphere ? "stereographic" : "Poincaré model", "1", 'P');
1872     dialog::add_action([] () { *dialog::ne.editwhat = 1; vpconf.scale = 1; dialog::ne.s = "1"; });
1873     dialog::addSelItem(sphere ? "gnomonic" : "Klein model", "0", 'K');
1874     dialog::add_action([] () { *dialog::ne.editwhat = 0; vpconf.scale = 1; dialog::ne.s = "0"; });
1875     if(hyperbolic) {
1876       dialog::addSelItem("inverted Poincaré model", "-1", 'I');
1877       dialog::add_action([] () { *dialog::ne.editwhat = -1; vpconf.scale = 1; dialog::ne.s = "-1"; });
1878       }
1879     dialog::addItem(sphere ? "orthographic" : "Gans model", 'O');
1880     dialog::add_action([] () { vpconf.alpha = vpconf.scale = 999; dialog::ne.s = dialog::disp(vpconf.alpha); });
1881     dialog::addItem(sphere ? "towards orthographic" : "towards Gans model", 'T');
1882     dialog::add_action([] () { double d = 1.1; vpconf.alpha *= d; vpconf.scale *= d; dialog::ne.s = dialog::disp(vpconf.alpha); });
1883     };
1884   }
1885 
menuitem_projection_distance(char key)1886 EX void menuitem_projection_distance(char key) {
1887   dialog::addSelItem(XLAT("projection distance"), fts(vpconf.alpha) + " (" + current_proj_name() + ")", key);
1888   dialog::add_action(projectionDialog);
1889   }
1890 
explain_detail()1891 EX void explain_detail() {
1892   dialog::addHelp(XLAT(
1893     "Objects at distance less than %1 absolute units "
1894     "from the center will be displayed with high "
1895     "detail, and at distance at least %2 with low detail.",
1896     fts(vid.highdetail), fts(vid.middetail)
1897     ));
1898   }
1899 
max_fov_angle()1900 EX ld max_fov_angle() {
1901   auto& p = panini_alpha ? panini_alpha : stereo_alpha;
1902   if(p >= 1 || p <= -1) return 360;
1903   return acos(-p) * 2 / degree;
1904   }
1905 
1906 EX void add_edit_fov(char key IS('f'), bool pop IS(false)) {
1907 
1908   string sfov = fts(vid.fov) + "°";
1909   if(panini_alpha || stereo_alpha) {
1910     sfov += " / " + fts(max_fov_angle()) + "°";
1911     }
1912   dialog::addSelItem(XLAT("field of view"), sfov, key);
__anond343dad34102null1913   dialog::add_action([=] {
1914     if(pop) popScreen();
1915     dialog::editNumber(vid.fov, 1, max_fov_angle(), 1, 90, "field of view",
1916       XLAT(
1917         "Horizontal field of view, in angles. "
1918         "This affects the Hypersian Rug mode (even when stereo is OFF) "
1919         "and non-disk models.") + "\n\n" +
1920       XLAT(
1921         "Must be less than %1°. Panini projection can be used to get higher values.",
1922         fts(max_fov_angle())
1923         )
1924         );
1925     dialog::bound_low(1e-8);
1926     dialog::bound_up(max_fov_angle() - 0.01);
1927     string quick =
1928       XLAT(
1929         "HyperRogue uses "
1930         "a quick implementation, so parameter values too close to 1 may "
1931         "be buggy (outside of raycasting); try e.g. 0.9 instead."
1932         );
1933     dialog::extra_options = [quick] {
1934       dialog::addSelItem(XLAT("Panini projection"), fts(panini_alpha), 'P');
1935       dialog::add_action([quick] {
1936         popScreen();
1937         dialog::editNumber(panini_alpha, 0, 1, 0.1, 0, "Panini parameter",
1938           XLAT(
1939             "The Panini projection is an alternative perspective projection "
1940             "which allows very wide field-of-view values.\n\n") + quick
1941             );
1942         #if CAP_GL
1943         dialog::reaction = reset_all_shaders;
1944         #endif
1945         dialog::extra_options = [] {
1946           add_edit_fov('F', true);
1947           };
1948         });
1949       dialog::addSelItem(XLAT("spherical perspective projection"), fts(stereo_alpha), 'S');
1950       dialog::add_action([quick] {
1951         popScreen();
1952         dialog::editNumber(stereo_alpha, 0, 1, 0.1, 0, "spherical perspective parameter",
1953           XLAT(
1954             "Set to 1 to get stereographic projection, "
1955             "which allows very wide field-of-view values.\n\n") + quick
1956             );
1957         #if CAP_GL
1958         dialog::reaction = reset_all_shaders;
1959         #endif
1960         dialog::extra_options = [] {
1961           add_edit_fov('F', true);
1962           };
1963         });
1964       };
1965     });
1966   }
1967 
supported_ods()1968 bool supported_ods() {
1969   if(!CAP_ODS) return false;
1970   return rug::rugged || (hyperbolic && GDIM == 3);
1971   }
1972 
showStereo()1973 EX void showStereo() {
1974   cmode = sm::SIDE | sm::MAYDARK;
1975   gamescreen(0);
1976   dialog::init(XLAT("stereo vision config"));
1977 
1978   add_edit(vid.stereo_mode);
1979 
1980   dialog::addSelItem(XLAT("pupillary distance"), fts(vid.ipd), 'e');
1981 
1982   switch(vid.stereo_mode) {
1983     case sAnaglyph:
1984       dialog::addSelItem(XLAT("distance between images"), fts(vid.anaglyph_eyewidth), 'd');
1985       break;
1986     case sLR:
1987       dialog::addSelItem(XLAT("distance between images"), fts(vid.lr_eyewidth), 'd');
1988       break;
1989     default:
1990       dialog::addBreak(100);
1991       break;
1992     }
1993 
1994   dialog::addSelItem(XLAT("desaturate colors"), its(vid.desaturate)+"%", 'c');
1995   dialog::add_action([] {
1996     dialog::editNumber(vid.desaturate, 0, 100, 10, 0, XLAT("desaturate colors"),
1997       XLAT("Make the game colors less saturated. This is useful in the anaglyph mode.")
1998       );
1999     });
2000 
2001   add_edit_fov('f');
2002 
2003   dialog::addBack();
2004   dialog::display();
2005 
2006   keyhandler = [] (int sym, int uni) {
2007     dialog::handleNavigation(sym, uni);
2008 
2009     string help3 = XLAT(
2010       "This allows you to view the world of HyperRogue in three dimensions. "
2011       "Best used with the Hypersian Rug mode. When used in the disk model, "
2012       "this lets you look at the Minkowski hyperboloid (which means the "
2013       "depth of terrain features is actually reversed). It also works with non-disk models, "
2014       "from the conformal menu."
2015        ) + " " + XLAT(
2016        "Currently, red-cyan anaglyph glasses and mobile VR googles are supported."
2017         ) + "\n\n";
2018 
2019     if(uni == 'm') {
2020       vid.stereo_mode = eStereo((1 + vid.stereo_mode) % 4);
2021       if(vid.stereo_mode == sODS && !supported_ods()) vid.stereo_mode = sOFF;
2022       }
2023 
2024     else if(uni == 'e')
2025       dialog::editNumber(vid.ipd, -10, 10, 0.01, 0, XLAT("pupillary distance"),
2026         help3 +
2027         XLAT("The distance between your eyes in the represented 3D object. This is given in absolute units.")
2028         ), dialog::scaleSinh100();
2029 
2030     else if(uni == 'd' && vid.stereo_mode == sAnaglyph)
2031       dialog::editNumber(vid.anaglyph_eyewidth, -1, 1, 0.01, 0, XLAT("distance between images"),
2032         help3 +
2033         XLAT("The distance between your eyes. 1 is the width of the screen."));
2034 
2035     else if(uni == 'd' && vid.stereo_mode == sLR)
2036       dialog::editNumber(vid.lr_eyewidth, -1, 1, 0.01, 0, XLAT("distance between images"),
2037         help3 +
2038         XLAT("The distance between your eyes. 1 is the width of the screen."));
2039 
2040     else if(doexiton(sym, uni)) popScreen();
2041     };
2042   }
2043 
add_edit_wall_quality(char c)2044 EX void add_edit_wall_quality(char c) {
2045   dialog::addSelItem(XLAT("wall quality"), its(vid.texture_step), c);
2046   dialog::add_action([] {
2047     dialog::editNumber(vid.texture_step, 1, 16, 1, 1, XLAT("wall quality"),
2048       XLAT(
2049       "Controls the number of triangles used for wall surfaces. "
2050       "Higher numbers reduce the performance. "
2051       "This has a strong effect when the walls are curved indeed "
2052       "(floors in 2D geometries, honeycombs based on horospheres, and projections other than native perspective), "
2053       "but otherwise, usually it can be set to 1 without significant adverse effects other "
2054       "than slightly incorrect texturing."
2055       )
2056       );
2057     dialog::bound_low(1);
2058     dialog::bound_up(128);
2059     dialog::reaction = [] {
2060       #if MAXMDIM >= 4
2061       if(floor_textures) {
2062         delete floor_textures;
2063         floor_textures = NULL;
2064         }
2065       #endif
2066       };
2067     });
2068   }
2069 
edit_levellines(char c)2070 EX void edit_levellines(char c) {
2071   if(levellines)
2072     dialog::addSelItem(XLAT("level lines"), fts(levellines), c);
2073   else
2074     dialog::addBoolItem(XLAT("level lines"), false, c);
2075   dialog::add_action([] {
2076     dialog::editNumber(levellines, 0, 100, 0.5, 0, XLAT("level lines"),
2077       XLAT(
2078         "This feature superimposes level lines on the rendered screen. These lines depend on the Z coordinate. In 3D hyperbolic the Z coordinate is taken from the Klein model. "
2079         "Level lines can be used to observe the curvature: circles correspond to positive curvature, while hyperbolas correspond to negative. See e.g. the Hypersian Rug mode.")
2080       );
2081     dialog::reaction = ray::reset_raycaster;
2082     dialog::extra_options = [] {
2083       dialog::addBoolItem(XLAT("disable textures"), disable_texture, 'T');
2084       dialog::add_action([] { ray::reset_raycaster(); disable_texture = !disable_texture; });
2085       dialog::addItem(XLAT("disable level lines"), 'D');
2086       dialog::add_action([] { ray::reset_raycaster(); levellines = 0; popScreen(); });
2087       };
2088     dialog::bound_low(0);
2089     });
2090   }
2091 
show3D()2092 EX void show3D() {
2093   cmode = sm::SIDE | sm::MAYDARK;
2094   gamescreen(0);
2095   dialog::init(XLAT("3D configuration"));
2096 
2097   if(GDIM == 2) {
2098     dialog::addBoolItem(XLAT("configure TPP automatically"), pmodel == mdDisk && pconf.camera_angle, 'T');
2099     dialog::add_action(geom3::switch_tpp);
2100     }
2101 
2102 #if MAXMDIM >=4
2103   if(WDIM == 2) {
2104     dialog::addBoolItem(XLAT("configure FPP automatically"), GDIM == 3, 'F');
2105     dialog::add_action(geom3::switch_fpp);
2106     }
2107 #endif
2108   dialog::addBreak(50);
2109 
2110 #if MAXMDIM >= 4
2111   if(WDIM == 2) {
2112     dialog::addBoolItem(XLAT("use the full 3D models"), vid.always3, 'U');
2113     dialog::add_action(geom3::switch_always3);
2114     }
2115 #endif
2116   if(vid.use_smart_range == 0 && GDIM == 2) {
2117     add_edit(vid.highdetail);
2118     add_edit(vid.middetail);
2119     dialog::addBreak(50);
2120     }
2121 
2122   if(WDIM == 2) {
2123     add_edit(vid.camera);
2124     if(GDIM == 3)
2125       add_edit(vid.eye);
2126 
2127     add_edit(vid.depth);
2128 
2129     if(GDIM == 2) {
2130       dialog::addSelItem(XLAT("Projection at the ground level"), fts(pconf.alpha), 'p');
2131       dialog::add_action(projectionDialog);
2132       }
2133     else if(!in_perspective())
2134       dialog::addSelItem(XLAT("projection distance"), fts(pconf.alpha), 'p');
2135 
2136     dialog::addBreak(50);
2137     add_edit(vid.wall_height);
2138 
2139     add_edit(vid.rock_wall_ratio);
2140     add_edit(vid.human_wall_ratio);
2141     add_edit(vid.lake_top);
2142     add_edit(vid.lake_bottom);
2143     if(scale_used())
2144       add_edit(vid.creature_scale);
2145     }
2146   else {
2147     add_edit(vid.creature_scale);
2148     add_edit(vid.height_width);
2149     menuitem_sightrange('s');
2150     }
2151 
2152   dialog::addBreak(50);
2153   add_edit(vid.yshift);
2154   if(GDIM == 3) {
2155     dialog::addSelItem(XLAT("mouse aiming sensitivity"), fts(mouseaim_sensitivity), 'a');
2156     dialog::add_action([] () {
2157       dialog::editNumber(mouseaim_sensitivity, -1, 1, 0.002, 0.01, XLAT("mouse aiming sensitivity"), "set to 0 to disable");
2158       });
2159     }
2160   if(true) {
2161     dialog::addSelItem(XLAT("camera rotation"), fts(vpconf.camera_angle), 'S');
2162     dialog::add_action([] {
2163       dialog::editNumber(vpconf.camera_angle, -180, 180, 5, 0, XLAT("camera rotation"),
2164         XLAT("Rotate the camera. Can be used to obtain a first person perspective, "
2165         "or third person perspective when combined with Y shift.")
2166         );
2167       });
2168     }
2169   if(GDIM == 2) {
2170     dialog::addSelItem(XLAT("fixed facing"), vid.fixed_facing ? fts(vid.fixed_facing_dir) : XLAT("OFF"), 'f');
2171     dialog::add_action([] () { vid.fixed_facing = !vid.fixed_facing;
2172       if(vid.fixed_facing) {
2173         dialog::editNumber(vid.fixed_facing_dir, 0, 360, 15, 90, "", "");
2174         dialog::dialogflags |= sm::CENTER;
2175         }
2176       });
2177     }
2178 
2179   if((WDIM == 2 && GDIM == 3) || prod)
2180     dialog::addBoolItem_action(XLAT("fixed Y/Z rotation"), vid.fixed_yz, 'Z');
2181 
2182   if(true) {
2183     dialog::addBreak(50);
2184     dialog::addSelItem(XLAT("projection"), current_proj_name(), 'M');
2185     dialog::add_action_push(models::model_menu);
2186     }
2187   #if MAXMDIM >= 4
2188   if(GDIM == 3) add_edit_fov('f');
2189   if(GDIM == 3) {
2190     dialog::addSelItem(XLAT("radar size"), fts(vid.radarsize), 'r');
2191     dialog::add_action([] () {
2192       dialog::editNumber(vid.radarsize, 0, 360, 15, 90, "", XLAT("set to 0 to disable"));
2193       dialog::extra_options = [] () { draw_radar(true); };
2194       });
2195     }
2196 
2197   if(WDIM == 3 && sphere && stretch::factor) {
2198     dialog::addItem(XLAT("Berger sphere limit"), berger_limit);
2199     dialog::add_action([] () {
2200       dialog::editNumber(berger_limit, 0, 10, 1, 2, "",
2201         XLAT("Primitive-based rendering of Berger sphere is currently very slow and low quality. "
2202           "Here you can choose how many images to draw.")
2203         );
2204       });
2205     }
2206 
2207   #if CAP_RAY
2208   if(GDIM == 3) {
2209     dialog::addItem(XLAT("configure raycasting"), 'A');
2210     dialog::add_action_push(ray::configure);
2211     }
2212   #endif
2213 
2214   edit_levellines('L');
2215 
2216   if(WDIM == 3 || (GDIM == 3 && euclid)) {
2217     dialog::addSelItem(XLAT("radar range"), fts(vid.radarrange), 'R');
2218     dialog::add_action([] () {
2219       dialog::editNumber(vid.radarrange, 0, 10, 0.5, 2, "", XLAT(""));
2220       dialog::extra_options = [] () { draw_radar(true); };
2221       });
2222     }
2223   if(GDIM == 3) add_edit_wall_quality('W');
2224   #endif
2225 
2226   #if CAP_RUG
2227   if(rug::rugged) {
2228     dialog::addBoolItem_action(XLAT("3D monsters/walls on the surface"), rug::spatial_rug, 'S');
2229     }
2230   #endif
2231 
2232   if(0);
2233   #if CAP_RUG
2234   else if(rug::rugged && !rug::spatial_rug)
2235     dialog::addBreak(100);
2236   #endif
2237   else if(GDIM == 2 && non_spatial_model())
2238     dialog::addInfo(XLAT("no 3D effects available in this projection"), 0xC00000);
2239   else if(GDIM == 2 && !spatial_graphics)
2240     dialog::addInfo(XLAT("set 3D monsters or walls in basic config first"));
2241   else if(geom3::invalid != "")
2242     dialog::addInfo(XLAT("error: ")+geom3::invalid, 0xC00000);
2243   else
2244     dialog::addInfo(XLAT("parameters set correctly"));
2245   dialog::addBreak(50);
2246   dialog::addItem(XLAT("stereo vision config"), 'e');
2247   dialog::add_action_push(showStereo);
2248 
2249   #if CAP_VR
2250   dialog::addBoolItem(XLAT("VR settings"), vrhr::active(), 'v');
2251   dialog::add_action_push(vrhr::show_vr_settings);
2252   #endif
2253 
2254   dialog::addBack();
2255   dialog::display();
2256   }
2257 
__anond343dad35702null2258 EX int config3 = addHook(hooks_configfile, 100, [] {
2259   param_f(vid.eye, "eyelevel", 0)
2260     ->editable(-5, 5, .1, "eye level", "", 'E')
2261     ->set_extra([] {
2262       dialog::dialogflags |= sm::CENTER;
2263       vid.tc_depth = ticks;
2264 
2265       dialog::addHelp(XLAT("In the FPP mode, the camera will be set at this altitude (before applying shifts)."));
2266 
2267       dialog::addBoolItem(XLAT("auto-adjust to eyes on the player model"), vid.auto_eye, 'O');
2268       dialog::reaction = [] { vid.auto_eye = false; };
2269       dialog::add_action([] () {
2270         vid.auto_eye = !vid.auto_eye;
2271         geom3::do_auto_eye();
2272         });
2273       });
2274 
2275   addsaver(vid.auto_eye, "auto-eyelevel", false);
2276 
2277   param_f(vid.creature_scale, "creature_scale", "3d-creaturescale", 1)
2278     ->editable(0, 1, .1, "Creature scale", "", 'C');
2279   param_f(vid.height_width, "heiwi", "3d-heightwidth", 1.5)
2280     ->editable(0, 1, .1, "Height to width", "", 'h');
2281   param_f(vid.yshift, "yshift", "Y shift", 0)
2282     ->editable(0, 1, .1, "Y shift", "Don't center on the player character.", 'y')
2283     ->set_extra([] {
2284       if(WDIM == 3 && pmodel == mdPerspective)
2285         dialog::addBoolItem_action(XLAT("reduce if walls on the way"), vid.use_wall_radar, 'R');
2286       });
2287   addsaver(vid.use_wall_radar, "wallradar", true);
2288   addsaver(vid.fixed_facing, "fixed facing", 0);
2289   addsaver(vid.fixed_facing_dir, "fixed facing dir", 90);
2290   addsaver(vid.fixed_yz, "fixed YZ", true);
2291   param_f(vid.depth, "depth", "3D depth", 1)
2292     ->editable(0, 5, .1, "Ground level below the plane", "", 'd')
2293     ->set_extra([] {
2294         vid.tc_depth = ticks;
2295         help = XLAT(
2296           "Ground level is actually an equidistant surface, "
2297           "%1 absolute units below the plane P. "
2298           "Theoretically, this value affects the world -- "
2299           "for example, eagles could fly %2 times faster by "
2300           "flying above the ground level, on the plane P -- "
2301           "but the actual game mechanics are not affected. ", fts(vid.depth), fts(cosh(vid.depth)));
2302         if(GDIM == 2)
2303           help += XLAT(
2304             "(Distances reported by the vector graphics editor "
2305             "are not about points on the ground level, but "
2306             "about the matching points on the plane P -- "
2307             "divide them by the factor above to get actual "
2308             "distances.)"
2309             );
2310         if(GDIM == 3 && pmodel == mdPerspective && !euclid) {
2311           ld current_camera_level = hdist0(tC0(radar_transform));
2312           help += "\n\n";
2313           if(abs(current_camera_level) < 1e-6)
2314             help += XLAT(
2315               "The camera is currently exactly on the plane P. "
2316               "The horizon is seen as a straight line."
2317               );
2318           else help += XLAT(
2319               "The camera is currently %1 units above the plane P. "
2320               "This makes you see the floor level as in general perspective projection "
2321               "with parameter %2.", fts(current_camera_level), fts(tan_auto(vid.depth) / tan_auto(current_camera_level)));
2322           }
2323         dialog::addHelp(help);
2324         });
2325   param_f(vid.camera, "camera", "3D camera level", 1)
2326     ->editable(0, 5, .1, "", "", 'c')
2327     ->modif([] (float_setting* x) { x->menu_item_name = (GDIM == 2 ? "Camera level above the plane" : "Z shift"); })
2328     ->set_extra([] {
2329        vid.tc_camera = ticks;
2330        if(GDIM == 2)
2331        dialog::addHelp(XLAT(
2332          "Camera is placed %1 absolute units above a plane P in a three-dimensional "
2333          "world. Ground level is actually an equidistant surface, %2 absolute units "
2334          "below the plane P. The plane P (as well as the ground level or any "
2335          "other equidistant surface below it) is viewed at an angle of %3 "
2336          "(the tangent of the angle between the point in "
2337          "the center of your vision and a faraway location is 1/cosh(c) = %4).",
2338          fts(vid.camera),
2339          fts(vid.depth),
2340          fts(atan(1/cosh(vid.camera))*2/degree),
2341          fts(1/cosh(vid.camera))));
2342        if(GDIM == 3)
2343          dialog::addHelp(XLAT("Look from behind."));
2344        if(GDIM == 3 && pmodel == mdPerspective)
2345          dialog::addBoolItem_action(XLAT("reduce if walls on the way"), vid.use_wall_radar, 'R');
2346        });
2347   param_f(vid.wall_height, "wall_height", "3D wall height", .3)
2348     ->editable(0, 1, .1, "Height of walls", "", 'w')
2349     ->set_extra([] () {
2350         dialog::addHelp(GDIM == 3 ? "" : XLAT(
2351           "The height of walls, in absolute units. For the current values of g and c, "
2352           "wall height of %1 absolute units corresponds to projection value of %2.",
2353           fts(geom3::actual_wall_height()), fts(geom3::factor_to_projection(cgi.WALL))));
2354         dialog::addBoolItem(XLAT("auto-adjust in Goldberg grids"), vid.gp_autoscale_heights, 'O');
2355         dialog::add_action([] () {
2356           vid.gp_autoscale_heights = !vid.gp_autoscale_heights;
2357           });
2358         });
2359   param_f(vid.rock_wall_ratio, "rock_wall_ratio", "3D rock-wall ratio", .9)
2360     ->editable(0, 1, .1, "Rock-III to wall ratio", "", 'r')
2361     ->set_extra([] { dialog::addHelp(XLAT(
2362         "The ratio of Rock III to walls is %1, so Rock III are %2 absolute units high. "
2363         "Length of paths on the Rock III level is %3 of the corresponding length on the "
2364         "ground level.",
2365         fts(vid.rock_wall_ratio), fts(vid.wall_height * vid.rock_wall_ratio),
2366         fts(cosh(vid.depth - vid.wall_height * vid.rock_wall_ratio) / cosh(vid.depth))));
2367         });
2368   param_f(vid.human_wall_ratio, "human_wall_ratio", "3D human-wall ratio", .7)
2369     ->editable(0, 1, .1, "Human to wall ratio", "", 'h')
2370     ->set_extra([] { dialog::addHelp(XLAT(
2371         "Humans are %1 "
2372         "absolute units high. Your head travels %2 times the distance travelled by your "
2373         "feet.",
2374         fts(vid.wall_height * vid.human_wall_ratio),
2375         fts(cosh(vid.depth - vid.wall_height * vid.human_wall_ratio) / cosh(vid.depth)))
2376         );
2377         });
2378   param_f(vid.lake_top, "lake_top", "3D lake top", .25)
2379     ->editable(0, 1, .1, "Level of water surface", "", 'l');
2380   param_f(vid.lake_bottom, "lake_bottom", "3D lake bottom", .9)
2381     ->editable(0, 1, .1, "Level of water bottom", "", 'k');
2382   addsaver(vid.tc_depth, "3D TC depth", 1);
2383   addsaver(vid.tc_camera, "3D TC camera", 2);
2384   addsaver(vid.tc_alpha, "3D TC alpha", 3);
2385   param_f(vid.highdetail, "highdetail", "3D highdetail", 8)
2386     ->editable(0, 5, .5, "High detail range", "", 'n')
2387     ->set_extra(explain_detail)
2388     ->set_reaction([] {
2389       if(vid.highdetail > vid.middetail) vid.middetail = vid.highdetail;
2390       });
2391   param_f(vid.middetail, "middetail", "3D middetail", 8)
2392     ->editable(0, 5, .5, "Mid detail range", "", 'm')
2393     ->set_extra(explain_detail)
2394     ->set_reaction([] {
2395       if(vid.highdetail > vid.middetail) vid.highdetail = vid.middetail;
2396       });
2397   addsaver(vid.gp_autoscale_heights, "3D Goldberg autoscaling", true);
2398   });
2399 
switchcolor(unsigned int & c,unsigned int * cs)2400 EX void switchcolor(unsigned int& c, unsigned int* cs) {
2401   dialog::openColorDialog(c, cs);
2402   }
2403 
2404 
2405 double cc_footphase;
2406 int lmousex, lmousey;
2407 
showCustomizeChar()2408 EX void showCustomizeChar() {
2409 
2410   cc_footphase += hypot(mousex - lmousex, mousey - lmousey);
2411   lmousex = mousex; lmousey = mousey;
2412 
2413   gamescreen(4);
2414   dialog::init(XLAT("Customize character"));
2415 
2416   if(shmup::on || multi::players) multi::cpid = multi::cpid_edit % multi::players;
2417   charstyle& cs = getcs();
2418 
2419   dialog::addSelItem(XLAT("character"), csname(cs), 'g');
2420   dialog::addColorItem(XLAT("skin color"), cs.skincolor, 's');
2421   dialog::addColorItem(XLAT("eye color"), cs.eyecolor, 'e');
2422   dialog::addColorItem(XLAT("weapon color"), cs.swordcolor, 'w');
2423   dialog::addColorItem(XLAT("hair color"), cs.haircolor, 'h');
2424 
2425   if(cs.charid >= 1) dialog::addColorItem(XLAT("dress color"), cs.dresscolor, 'd');
2426   else dialog::addBreak(100);
2427   if(cs.charid == 3) dialog::addColorItem(XLAT("dress color II"), cs.dresscolor2, 'f');
2428   else dialog::addBreak(100);
2429 
2430   dialog::addColorItem(XLAT("movement color"), cs.uicolor, 'u');
2431 
2432   if(!shmup::on && multi::players == 1) dialog::addSelItem(XLAT("save whom"), XLAT1(minf[moPrincess].name), 'p');
2433 
2434   if(numplayers() > 1) dialog::addSelItem(XLAT("player"), its(multi::cpid+1), 'a');
2435 
2436   dialog::addBoolItem(XLAT("left-handed"), cs.lefthanded, 'l');
2437 
2438   dialog::addBreak(50);
2439   dialog::addBack();
2440   dialog::display();
2441 
2442   int firsty = dialog::items[0].position / 2;
2443   int scale = firsty - 2 * vid.fsize;
2444 
2445   flat_model_enabler fme;
2446 
2447   initquickqueue();
2448   transmatrix V = atscreenpos(vid.xres/2, firsty, scale);
2449 
2450   double alpha = atan2(mousex - vid.xres/2, mousey - firsty) - M_PI/2;
2451   V = V * spin(alpha);
2452   drawMonsterType(moPlayer, NULL, shiftless(V), 0, cc_footphase / scale, NOCOLOR);
2453   quickqueue();
2454 
2455   keyhandler = [] (int sym, int uni) {
2456     dialog::handleNavigation(sym, uni);
2457 
2458     if(shmup::on || multi::players) multi::cpid = multi::cpid_edit % multi::players;
2459     charstyle& cs = getcs();
2460     bool cat = cs.charid >= 4;
2461     if(uni == 'a') { multi::cpid_edit++; multi::cpid_edit %= 60; }
2462     else if(uni == 'g') {
2463       cs.charid++;
2464       if(cs.charid == 2 && !princess::everSaved && !autocheat) cs.charid = 4;
2465       cs.charid %= 10;
2466       }
2467     else if(uni == 'p') vid.samegender = !vid.samegender;
2468     else if(uni == 's') switchcolor(cs.skincolor, cat ? haircolors : skincolors);
2469     else if(uni == 'h') switchcolor(cs.haircolor, haircolors);
2470     else if(uni == 'w') switchcolor(cs.swordcolor, swordcolors);
2471     else if(uni == 'd') switchcolor(cs.dresscolor, cat ? haircolors : dresscolors);
2472     else if(uni == 'f') switchcolor(cs.dresscolor2, dresscolors2);
2473     else if(uni == 'u') switchcolor(cs.uicolor, eyecolors);
2474     else if(uni == 'e') switchcolor(cs.eyecolor, eyecolors);
2475     else if(uni == 'l') cs.lefthanded = !cs.lefthanded;
2476     else if(doexiton(sym, uni)) popScreen();
2477     };
2478   }
2479 
refresh_canvas()2480 EX void refresh_canvas() {
2481   manual_celllister cl;
2482   cl.add(cwt.at);
2483 
2484   int at = 0;
2485   while(at < isize(cl.lst)) {
2486     cell *c2 = cl.lst[at];
2487     c2->landparam = patterns::generateCanvas(c2);
2488     at++;
2489 
2490     forCellEx(c3, c2) cl.add(c3);
2491     }
2492   }
2493 
edit_color_table(colortable & ct,const reaction_t & r IS (reaction_t ()),bool has_bit IS (false))2494 EX void edit_color_table(colortable& ct, const reaction_t& r IS(reaction_t()), bool has_bit IS(false)) {
2495   cmode = sm::SIDE;
2496   gamescreen(0);
2497   dialog::init(XLAT("colors & aura"));
2498 
2499   for(int i=0; i<isize(ct); i++) {
2500     dialog::addColorItem(its(i), ct[i] << 8, 'a'+i);
2501     if(WDIM == 3 && has_bit && !(ct[i] & 0x1000000)) dialog::lastItem().value = XLAT("(no wall)");
2502     dialog::add_action([i, &ct, r, has_bit] () {
2503       if(WDIM == 3 && has_bit) {
2504         ct[i] ^= 0x1000000;
2505         if(!(ct[i] & 0x1000000)) return;
2506         }
2507       dialog::openColorDialog(ct[i]);
2508       dialog::reaction = r;
2509       dialog::colorAlpha = false;
2510       dialog::dialogflags |= sm::SIDE;
2511       });
2512     }
2513 
2514   dialog::addBack();
2515   dialog::display();
2516   }
2517 
show_color_dialog()2518 EX void show_color_dialog() {
2519   cmode = sm::SIDE | sm::DIALOG_STRICT_X;
2520   getcstat = '-';
2521   gamescreen(0);
2522   dialog::init(XLAT("colors & aura"));
2523 
2524   dialog::addColorItem(XLAT("background"), backcolor << 8, 'b');
2525   dialog::add_action([] () { dialog::openColorDialog(backcolor); dialog::colorAlpha = false; dialog::dialogflags |= sm::SIDE; });
2526 
2527   if(WDIM == 2 && GDIM == 3 && hyperbolic)
2528     dialog::addBoolItem_action(XLAT("cool fog effect"), context_fog, 'B');
2529 
2530   dialog::addColorItem(XLAT("foreground"), forecolor << 8, 'f');
2531   dialog::add_action([] () { dialog::openColorDialog(forecolor); dialog::colorAlpha = false; dialog::dialogflags |= sm::SIDE; });
2532 
2533   dialog::addColorItem(XLAT("borders"), bordcolor << 8, 'o');
2534   dialog::add_action([] () { dialog::openColorDialog(bordcolor); dialog::colorAlpha = false; dialog::dialogflags |= sm::SIDE; });
2535 
2536   dialog::addColorItem(XLAT("projection boundary"), ringcolor, 'r');
2537   dialog::add_action([] () { dialog::openColorDialog(ringcolor); dialog::dialogflags |= sm::SIDE; });
2538 
2539   dialog::addSelItem(XLAT("boundary width multiplier"), fts(vid.multiplier_ring), 'R');
2540   dialog::add_action([] () { dialog::editNumber(vid.multiplier_ring, 0, 10, 1, 1, XLAT("boundary width multiplier"), ""); });
2541 
2542   dialog::addColorItem(XLAT("projection background"), modelcolor, 'c');
2543   dialog::add_action([] () { dialog::openColorDialog(modelcolor); dialog::dialogflags |= sm::SIDE; });
2544 
2545   dialog::addColorItem(XLAT("standard grid color"), stdgridcolor, 'g');
2546   dialog::add_action([] () { vid.grid = true; dialog::openColorDialog(stdgridcolor); dialog::dialogflags |= sm::SIDE; });
2547 
2548   dialog::addSelItem(XLAT("grid width multiplier"), fts(vid.multiplier_grid), 'G');
2549   dialog::add_action([] () { dialog::editNumber(vid.multiplier_grid, 0, 10, 1, 1, XLAT("grid width multiplier"), ""); });
2550 
2551   dialog::addSelItem(XLAT("brightness behind the sphere"), fts(backbrightness), 'i');
2552   dialog::add_action([] () { dialog::editNumber(backbrightness, 0, 1, .01, 0.25, XLAT("brightness behind the sphere"),
2553     XLAT("In the orthogonal projection, objects on the other side of the sphere are drawn darker.")); dialog::bound_low(0); });
2554 
2555   dialog::addColorItem(XLAT("projection period"), periodcolor, 'p');
2556   dialog::add_action([] () { dialog::openColorDialog(periodcolor); dialog::dialogflags |= sm::SIDE; });
2557 
2558   dialog::addColorItem(XLAT("dialogs"), dialog::dialogcolor << 8, 'd');
2559   dialog::add_action([] () { dialog::openColorDialog(dialog::dialogcolor); dialog::colorAlpha = false; dialog::dialogflags |= sm::SIDE; });
2560 
2561   dialog::addBreak(50);
2562   if(specialland == laCanvas && colortables.count(patterns::whichCanvas)) {
2563     dialog::addItem(XLAT("pattern colors"), 'P');
2564     dialog::add_action_push([] { edit_color_table(colortables[patterns::whichCanvas], refresh_canvas, true); });
2565     }
2566 
2567   if(cwt.at->land == laMinefield) {
2568     dialog::addItem(XLAT("minefield colors"), 'm');
2569     dialog::add_action_push([] { edit_color_table(minecolors); });
2570     }
2571 
2572   if(viewdists) {
2573     dialog::addItem(XLAT("distance colors"), 'd');
2574     dialog::add_action_push([] () {edit_color_table(distcolors); });
2575     }
2576 
2577   #if CAP_CRYSTAL
2578   if(cryst && cheater) {
2579     dialog::addItem(XLAT("crystal coordinate colors"), 'C');
2580     dialog::add_action([] () { crystal::view_coordinates = true; pushScreen([] () { edit_color_table(crystal::coordcolors); });});
2581     }
2582   #endif
2583 
2584   if(cwt.at->land == laTortoise) {
2585     dialog::addBoolItem_action(XLAT("Galápagos shading"), tortoise::shading_enabled, 'T');
2586     }
2587 
2588   dialog::addInfo(XLAT("colors of some game objects can be edited by clicking them."));
2589 
2590   dialog::addBreak(50);
2591 
2592   dialog::addSelItem(XLAT("aura brightness"), its(vid.aurastr), 'a');
2593   dialog::add_action([] () { dialog::editNumber(vid.aurastr, 0, 256, 10, 128, XLAT("aura brightness"), ""); dialog::bound_low(0); });
2594 
2595   dialog::addSelItem(XLAT("aura smoothening factor"), its(vid.aurasmoothen), 's');
2596   dialog::add_action([] () { dialog::editNumber(vid.aurasmoothen, 1, 180, 1, 5, XLAT("aura smoothening factor"), ""); dialog::bound_low(1); });
2597 
2598   dialog::addBreak(50);
2599   dialog::addBack();
2600   dialog::display();
2601 
2602   keyhandler = [] (int sym, int uni) {
2603     if(uni == '-') {
2604       cell *c = mouseover;
2605       if(!c) return;
2606       else if(c == cwt.at) {
2607         pushScreen(showCustomizeChar);
2608         return;
2609         }
2610       else if(c->monst)
2611         dialog::openColorDialog(minf[c->monst].color);
2612       else if(c->item)
2613         dialog::openColorDialog(iinf[c->item].color);
2614       else if(c->wall)
2615         dialog::openColorDialog(winf[c->wall == waMineMine ? waMineUnknown : c->wall].color);
2616       #if CAP_COMPLEX2
2617       else if(c->land == laBrownian)
2618         dialog::openColorDialog(brownian::get_color_edit(c->landparam));
2619       #endif
2620       else
2621         dialog::openColorDialog(floorcolors[c->land]);
2622       dialog::colorAlpha = false;
2623       dialog::dialogflags |= sm::SIDE;
2624       return;
2625       }
2626     else dialog::handleNavigation(sym, uni);
2627     if(doexiton(sym, uni)) popScreen();
2628     };
2629   }
2630 
2631 #if CAP_CONFIG
resetConfigMenu()2632 EX void resetConfigMenu() {
2633   dialog::init(XLAT("reset all configuration"));
2634   dialog::addInfo("Are you sure?");
2635   dialog::addItem("yes, and delete the config file", 'd');
2636   dialog::addItem("yes", 'y');
2637   dialog::addItem("cancel", 'n');
2638   dialog::addItem("reset the special game modes", 'r');
2639   dialog::display();
2640   keyhandler = [] (int sym, int uni) {
2641     dialog::handleNavigation(sym, uni);
2642 
2643     if(uni == 'd') {
2644       resetConfig();
2645       unlink(conffile);
2646       popScreen();
2647       }
2648     else if(uni == 'y') {
2649       printf("resetting config\n");
2650       resetConfig();
2651       printf("config reset\n");
2652       popScreen();
2653       }
2654     else if(uni == 'r')
2655       resetModes();
2656     else if(uni == 'n' || doexiton(sym, uni))
2657       popScreen();
2658 
2659     };
2660   }
2661 #endif
2662 
2663 #if CAP_TRANS
selectLanguageScreen()2664 EX void selectLanguageScreen() {
2665   gamescreen(4);
2666   dialog::init("select language"); // intentionally not translated
2667 
2668   int v = vid.language;
2669   dynamicval<int> d(vid.language, -1);
2670 
2671   for(int i=0; i<NUMLAN-1 || i == v; i++) {
2672     vid.language = i;
2673     dialog::addSelItem(XLAT("EN"), its(100 * transcompleteness[i] / transcompleteness[0]) + "%", 'a'+i);
2674     }
2675 
2676   dialog::addBreak(50);
2677   vid.language = -1;
2678   dialog::addBoolItem(XLAT("default") + ": " + XLAT("EN"), v == -1, '0');
2679   dialog::addBack();
2680 
2681   dialog::addBreak(50);
2682 
2683   vid.language = v;
2684   if(lang() >= 1)
2685     dialog::addHelp(XLAT("add credits for your translation here"));
2686   else
2687     dialog::addHelp(XLAT("original language"));
2688 
2689   if(lang() != 0) {
2690     string tw = "";
2691     string s = XLAT("TRANSLATIONWARNING");
2692     if(s != "" && s != "TRANSLATIONWARNING") tw += s;
2693     s = XLAT("TRANSLATIONWARNING2");
2694     if(s != "" && s != "TRANSLATIONWARNING2") { if(tw != "") tw += " "; tw += s; }
2695     if(tw != "") {
2696       dialog::addHelp(tw);
2697       dialog::lastItem().color = 0xFF0000;
2698       }
2699     }
2700 
2701   dialog::display();
2702 
2703   keyhandler = []   (int sym, int uni) {
2704     dialog::handleNavigation(sym, uni);
2705 
2706     if(uni == '0') {
2707       vid.language = -1;
2708       android_settings_changed();
2709       }
2710 
2711     else if(uni >= 'a' && uni < 'a'+NUMLAN) {
2712       vid.language = uni - 'a';
2713       android_settings_changed();
2714       }
2715 
2716     else if(doexiton(sym, uni))
2717       popScreen();
2718     };
2719   }
2720 #endif
2721 
configureMouse()2722 EX void configureMouse() {
2723   gamescreen(1);
2724   dialog::init(XLAT("mouse & touchscreen"));
2725 
2726   dialog::addBoolItem_action(XLAT("reverse pointer control"), (vid.revcontrol), 'r');
2727 
2728   dialog::addBoolItem_action(XLAT("draw circle around the target"), (vid.drawmousecircle), 'd');
2729 
2730   if(GDIM == 3) {
2731     dialog::addBoolItem_action(XLAT("highlight the cell forward"), vid.axes3, 'f');
2732     }
2733 
2734 #if ISMOBILE
2735   dialog::addBoolItem(XLAT("targetting ranged Orbs long-click only"), (vid.shifttarget&2), 'i');
2736 #else
2737   dialog::addBoolItem(XLAT("targetting ranged Orbs Shift+click only"), (vid.shifttarget&1), 'i');
2738 #endif
2739   dialog::add_action([] {vid.shifttarget = vid.shifttarget^3; });
2740 
2741   #if !ISMOBILE
2742   dialog::addBoolItem_action(XLAT("quick mouse"), vid.quickmouse, 'M');
2743   #endif
2744 
2745   dialog::addSelItem(XLAT("move by clicking on compass"), its(vid.mobilecompasssize), 'C');
2746   dialog::add_action([] {
2747     dialog::editNumber(vid.mobilecompasssize, 0, 100, 10, 20, XLAT("compass size"), XLAT("0 to disable"));
2748     // we need to check the moves
2749     dialog::reaction = checkmove;
2750     dialog::bound_low(0);
2751     });
2752 
2753   #if CAP_ORIENTATION
2754   if(GDIM == 2) {
2755     dialog::addSelItem(XLAT("scrolling by device rotation"), ors::choices[ors::mode], '1');
2756     dialog::add_action_push(ors::show);
2757     }
2758   #endif
2759 
2760   dialog::addBack();
2761   dialog::display();
2762   }
2763 
2764 vector<setting*> last_changed;
2765 
add_to_changed(setting * f)2766 EX void add_to_changed(setting *f) {
2767   auto orig_f = f;
2768   for(int i=0; i<isize(last_changed); i++) {
2769     if(last_changed[i] == f)
2770       return;
2771     swap(last_changed[i], f);
2772     if(f == orig_f) return;
2773     }
2774   last_changed.push_back(f);
2775   }
2776 
find_edit(void * val)2777 EX setting *find_edit(void *val) {
2778   for(auto& fs: params) {
2779     fs.second->check_change();
2780     if(fs.second->affects(val))
2781       return &*fs.second;
2782     }
2783   return nullptr;
2784   }
2785 
add_edit_ptr(void * val)2786 EX void add_edit_ptr(void *val) {
2787   int found = 0;
2788   for(auto& fs: params) {
2789     fs.second->check_change();
2790     if(fs.second->affects(val))
2791       fs.second->show_edit_option(), found++;
2792     }
2793   if(found != 1) println(hlog, "found = ", found);
2794   }
2795 
2796 #if HDR
add_edit(T & val)2797 template<class T> void add_edit(T& val) {
2798   add_edit_ptr(&val);
2799   }
2800 #endif
2801 
find_setting()2802 EX void find_setting() {
2803   gamescreen(1);
2804 
2805   dialog::init(XLAT("find a setting"));
2806   if(dialog::infix != "") mouseovers = dialog::infix;
2807 
2808   vector<setting*> found;
2809 
2810   for(auto& p: params) {
2811     auto& fs = p.second;
2812     string key = fs->search_key();
2813     if(fs->available() && dialog::hasInfix(key))
2814       found.push_back(&*fs);
2815     }
2816 
2817   for(int i=0; i<9; i++) {
2818     if(i < isize(found)) {
2819       found[i]->show_edit_option('1' + i);
2820       }
2821     else dialog::addBreak(100);
2822     }
2823 
2824   dialog::addBreak(100);
2825   dialog::addInfo(XLAT("press letters to search"));
2826   dialog::addSelItem(XLAT("matching items"), its(isize(found)), 0);
2827   dialog::display();
2828 
2829   keyhandler = [] (int sym, int uni) {
2830     dialog::handleNavigation(sym, uni);
2831     if(dialog::editInfix(uni)) ;
2832     else if(doexiton(sym, uni)) popScreen();
2833     };
2834   }
2835 
edit_all_settings()2836 EX void edit_all_settings() {
2837   gamescreen(1);
2838   dialog::init(XLAT("recently changed settings"));
2839 
2840   for(auto &fs: params) fs.second->check_change();
2841 
2842   int id = 0;
2843   for(auto l: last_changed)
2844     if(l->available() && id < 10)
2845     l->show_edit_option('a'+(id++));
2846 
2847   dialog::addBreak(100);
2848   dialog::addItem(XLAT("find a setting"), '/');
2849   dialog::add_action_push(find_setting);
2850   dialog::addBack();
2851   dialog::display();
2852   }
2853 
show_edit_option(char key)2854 void list_setting::show_edit_option(char key) {
2855   string opt = options[get_value()].first;
2856   dialog::addSelItem(XLAT(menu_item_name), XLAT(opt), key);
2857   dialog::add_action_push([this] {
2858     add_to_changed(this);
2859     gamescreen(2);
2860     dialog::init(XLAT(menu_item_name));
2861     dialog::addBreak(100);
2862     int q = isize(options);
2863     for(int i=0; i<q; i++) {
2864       dialog::addBoolItem(XLAT(options[i].first), get_value() == i, 'a'+i);
2865       dialog::add_action([this, i] { set_value(i); popScreen(); });
2866       dialog::addBreak(100);
2867       if(options[i].second != "") {
2868         dialog::addHelp(XLAT(options[i].second));
2869         dialog::addBreak(100);
2870         }
2871       }
2872     dialog::addBreak(100);
2873     dialog::addBack();
2874     dialog::display();
2875     });
2876   }
2877 
showSettings()2878 EX void showSettings() {
2879   gamescreen(1);
2880   dialog::init(XLAT("settings"));
2881 
2882   dialog::addItem(XLAT("interface"), 'i');
2883   dialog::add_action_push(configureInterface);
2884 
2885   dialog::addItem(XLAT("general graphics"), 'g');
2886   dialog::add_action_push(showGraphConfig);
2887 
2888   dialog::addItem(XLAT("3D configuration"), '9');
2889   dialog::add_action_push(show3D);
2890 
2891   dialog::addItem(XLAT("quick options"), 'q');
2892   dialog::add_action_push(showGraphQuickKeys);
2893 
2894   dialog::addItem(XLAT("models & projections"), 'p');
2895   dialog::add_action_push(models::quick_model);
2896 
2897   dialog::addItem(XLAT("colors & aura"), 'c');
2898   dialog::add_action_push(show_color_dialog);
2899 
2900 #if CAP_SHMUP && !ISMOBILE
2901   dialog::addSelItem(XLAT("keyboard & joysticks"), "", 'k');
2902   dialog::add_action(multi::configure);
2903 #endif
2904 
2905   dialog::addSelItem(XLAT("mouse & touchscreen"), "", 'm');
2906   dialog::add_action_push(configureMouse);
2907 
2908   dialog::addItem(XLAT("other settings"), 'o');
2909   dialog::add_action_push(configureOther);
2910 
2911   dialog::addBreak(100);
2912 
2913 #if CAP_CONFIG
2914   dialog::addItem(XLAT("recently changed settings"), '/');
2915   dialog::add_action_push(edit_all_settings);
2916 
2917   dialog::addItem(XLAT("save the current config"), 's');
2918   dialog::add_action(saveConfig);
2919 
2920   dialog::addItem(XLAT("reset all configuration"), 'R');
2921   dialog::add_action_push(resetConfigMenu);
2922 #endif
2923 
2924   if(getcstat == 's') mouseovers = XLAT("Config file: %1", conffile);
2925 
2926   dialog::addBack();
2927   dialog::display();
2928   }
2929 
2930 #if CAP_COMMANDLINE
2931 
read_color_args()2932 EX int read_color_args() {
2933   using namespace arg;
2934 
2935   if(argis("-back")) {
2936     PHASEFROM(2); shift(); backcolor = arghex();
2937     }
2938   else if(argis("-fillmodel")) {
2939     PHASEFROM(2); shift(); modelcolor = arghex();
2940     }
2941   else if(argis("-ring")) {
2942     PHASEFROM(2); shift(); ringcolor = arghex();
2943     }
2944   else if(argis("-ringw")) {
2945     PHASEFROM(2); shift_arg_formula(vid.multiplier_ring);
2946     }
2947   else if(argis("-stdgrid")) {
2948     PHASEFROM(2); shift(); stdgridcolor = arghex();
2949     }
2950   else if(argis("-gridw")) {
2951     PHASEFROM(2); shift_arg_formula(vid.multiplier_grid);
2952     }
2953   else if(argis("-period")) {
2954     PHASEFROM(2); shift(); periodcolor = arghex();
2955     }
2956   else if(argis("-crosshair")) {
2957     PHASEFROM(2); shift(); crosshair_color = arghex();
2958     shift_arg_formula(crosshair_size);
2959     }
2960   else if(argis("-borders")) {
2961     PHASEFROM(2); shift(); bordcolor = arghex();
2962     }
2963   else if(argis("-fore")) {
2964     PHASEFROM(2); shift(); forecolor = arghex();
2965     }
2966   else if(argis("-dialog")) {
2967     PHASEFROM(2); shift(); dialog::dialogcolor = arghex();
2968     }
2969   else if(argis("-d:color"))
2970     launch_dialog(show_color_dialog);
2971   else return 1;
2972   return 0;
2973   }
2974 
read_config_args()2975 EX int read_config_args() {
2976   using namespace arg;
2977 
2978   if(argis("-c")) { PHASE(1); shift(); conffile = argcs(); }
2979 // change the configuration from the command line
2980   else if(argis("-aa")) { PHASEFROM(2); shift(); vid.want_antialias = argi(); apply_screen_settings(); }
2981   else if(argis("-lw")) { PHASEFROM(2); shift_arg_formula(vid.linewidth); }
2982   else if(argis("-wm")) { PHASEFROM(2); shift(); vid.wallmode = argi(); }
2983   else if(argis("-mm")) { PHASEFROM(2); shift(); vid.monmode = argi(); }
2984 
2985   else if(argis("-noshadow")) { noshadow = true; }
2986   else if(argis("-bright")) { bright = true; }
2987   else if(argis("-gridon")) { vid.grid = true; }
2988   else if(argis("-gridoff")) { vid.grid = false; }
2989 
2990 // non-configurable options
2991   else if(argis("-vsync_off")) {
2992     vid.want_vsync = false;
2993     apply_screen_settings();
2994     }
2995   else if(argis("-aura")) {
2996     PHASEFROM(2);
2997     shift(); vid.aurastr = argi();
2998     shift(); vid.aurasmoothen = argi();
2999     }
3000   else if(argis("-nofps")) {
3001     PHASEFROM(2);
3002     nofps = true;
3003     }
3004   else if(argis("-nohud")) {
3005     PHASEFROM(2);
3006     nohud = true;
3007     }
3008   else if(argis("-nomenu")) {
3009     PHASEFROM(2);
3010     nomenukey = true;
3011     }
3012   else if(argis("-nomsg")) {
3013     PHASEFROM(2);
3014     nomsg = true;
3015     }
3016 #if MAXMDIM >= 4
3017   else if(argis("-switch-fpp")) {
3018     PHASEFROM(2);
3019     geom3::switch_fpp();
3020     }
3021 #endif
3022   else if(argis("-switch-tpp")) {
3023     PHASEFROM(2);
3024     geom3::switch_tpp();
3025     }
3026 #if MAXMDIM >= 4
3027   else if(argis("-switch-3d")) {
3028     PHASEFROM(2);
3029     geom3::switch_always3();
3030     }
3031 #endif
3032   else if(argis("-nohelp")) {
3033     PHASEFROM(2);
3034     nohelp = true;
3035     }
3036   else if(argis("-dont_face_pc")) {
3037     PHASEFROM(2);
3038     dont_face_pc = true;
3039     }
3040 
3041 #if CAP_TRANS
3042   else if(argis("-lang")) {
3043     PHASEFROM(2); shift(); vid.language = argi();
3044     }
3045 #endif
3046   else if(argis("-vlq")) {
3047     PHASEFROM(2); shift(); vid.linequality = argi();
3048     }
3049   else if(argis("-fov")) {
3050     PHASEFROM(2); shift_arg_formula(vid.fov);
3051     }
3052   else if(argis("-r")) {
3053     PHASEFROM(2);
3054     shift();
3055     int clWidth=0, clHeight=0, clFont=0;
3056     sscanf(argcs(), "%dx%dx%d", &clWidth, &clHeight, &clFont);
3057     if(clWidth) vid.xres = clWidth;
3058     if(clHeight) vid.yres = clHeight;
3059     if(clFont) vid.abs_fsize = clFont, vid.relative_font = true;
3060     }
3061   else if(argis("-msm")) {
3062     PHASEFROM(2); memory_saving_mode = true;
3063     }
3064   else if(argis("-mrsv")) {
3065     PHASEFROM(2); shift(); reserve_limit = argi(); apply_memory_reserve();
3066     }
3067   else if(argis("-yca")) {
3068     PHASEFROM(2);
3069     shift_arg_formula(vid.yshift);
3070     shift_arg_formula(pconf.camera_angle);
3071     }
3072   else if(argis("-pside")) {
3073     PHASEFROM(2);
3074     permaside = true;
3075     }
3076   else if(argis("-xy")) {
3077     PHASEFROM(2);
3078     shift_arg_formula(pconf.xposition);
3079     shift_arg_formula(pconf.yposition);
3080     }
3081   else if(argis("-fixdir")) {
3082     PHASEFROM(2);
3083     vid.fixed_facing = true;
3084     shift_arg_formula(vid.fixed_facing_dir);
3085     }
3086   else if(argis("-fixdiroff")) {
3087     PHASEFROM(2);
3088     vid.fixed_facing = false;
3089     }
3090   else if(argis("-msmoff")) {
3091     PHASEFROM(2); memory_saving_mode = false;
3092     }
3093   else if(argis("-levellines")) {
3094     PHASEFROM(2); shift_arg_formula(levellines);
3095     }
3096   else if(argis("-level-notexture")) {
3097     PHASEFROM(2); disable_texture = true;
3098     }
3099   else if(argis("-level-texture")) {
3100     PHASEFROM(2); disable_texture = false;
3101     }
3102   else if(argis("-msens")) {
3103     PHASEFROM(2); shift_arg_formula(mouseaim_sensitivity);
3104     }
3105   TOGGLE('o', vid.wantGL, { vid.wantGL = !vid.wantGL; apply_screen_settings();})
3106   TOGGLE('f', vid.want_fullscreen, { vid.want_fullscreen = !vid.want_fullscreen; apply_screen_settings(); })
3107   else if(argis("-noshaders")) {
3108     PHASE(1);
3109     glhr::noshaders = true;
3110     }
3111   else if(argis("-d:sight")) {
3112     PHASEFROM(2); launch_dialog(); edit_sightrange();
3113     }
3114   else if(argis("-d:char")) {
3115     PHASEFROM(2); launch_dialog(showCustomizeChar);
3116     }
3117   else if(argis("-d:3")) {
3118     PHASEFROM(2); launch_dialog(show3D);
3119     }
3120   else if(argis("-d:stereo")) {
3121     PHASEFROM(2); launch_dialog(showStereo);
3122     }
3123   else if(argis("-d:iface")) {
3124     PHASEFROM(2); launch_dialog(configureInterface);
3125     }
3126   else if(argis("-d:graph")) {
3127     PHASEFROM(2); launch_dialog(showGraphConfig);
3128     }
3129   else if(argis("-tstep")) {
3130     PHASEFROM(2); shift(); vid.texture_step = argi();
3131     }
3132   else if(argis("-csc")) {
3133     PHASEFROM(2); shift_arg_formula(vid.creature_scale);
3134     }
3135   else if(argis("-neon")) {
3136     PHASEFROM(2);
3137     shift(); neon_mode = eNeon(argi());
3138     }
3139   else if(argis("-dmc")) {
3140     PHASEFROM(2);
3141     shift(); vid.drawmousecircle = argi();
3142     }
3143   else if(argis("-smooths")) {
3144     PHASEFROM(2);
3145     shift(); smooth_scrolling = argi();
3146     }
3147   else if(argis("-via-shader")) {
3148     PHASEFROM(2);
3149     shift(); vid.consider_shader_projection = argi();
3150     }
3151   else if(argis("-neonnf")) {
3152     PHASEFROM(2);
3153     shift(); neon_nofill = argi();
3154     }
3155   else if(argis("-precw")) {
3156     PHASEFROM(2);
3157     shift_arg_formula(precise_width);
3158     }
3159   else if(argis("-d:all")) {
3160     PHASEFROM(2); launch_dialog(edit_all_settings);
3161     }
3162   else if(argis("-char")) {
3163     auto& cs = vid.cs;
3164     shift();
3165     string s = args();
3166     if(s == "dodek") {
3167       cs.charid = 4;
3168       cs.lefthanded = false;
3169       cs.skincolor = 0x202020FF;
3170       cs.eyecolor = 0x20C000FF;
3171       cs.haircolor = 0x202020FF;
3172       cs.dresscolor =0x424242FF;
3173       cs.swordcolor = 0xF73333FF;
3174       }
3175     else if(s == "rudy") {
3176       cs.charid = 4;
3177       cs.lefthanded = false;
3178       cs.skincolor = 0xA44139FF;
3179       cs.eyecolor = 0xD59533FF;
3180       cs.haircolor = 0xC6634AFF;
3181       cs.dresscolor =0xC6634AFF;
3182       cs.swordcolor = 0x3CBB33FF;
3183       }
3184     else if(s == "running") {
3185       cs.charid = 6;
3186       cs.lefthanded = false;
3187       cs.skincolor = 0xFFFFFFFF;
3188       cs.eyecolor = 0xFF;
3189       cs.haircolor = 0xFFFFFFFF;
3190       cs.dresscolor =0xFFFFFFFF;
3191       cs.swordcolor = 0xFF0000FF;
3192       }
3193     else if(s == "princess") {
3194       cs.charid = 3;
3195       cs.lefthanded = true;
3196       cs.skincolor  = 0xEFD0C9FF;
3197       cs.haircolor  = 0x301800FF;
3198       cs.eyecolor   = 0xC000FF;
3199       cs.dresscolor = 0x408040FF;
3200       cs.swordcolor = 0xFFFFFFFF;
3201       }
3202     else if(s == "worker") {
3203       cs.charid = 2;
3204       cs.skincolor = 0xC77A58FF;
3205       cs.haircolor = 0x502810FF;
3206       cs.dresscolor = 0xC0C000FF;
3207       cs.eyecolor = 0x500040FF;
3208       cs.swordcolor = 0x808080FF;
3209       }
3210     else {
3211       cs.charid = argi();
3212       cs.lefthanded = cs.charid >= 10;
3213       cs.charid %= 10;
3214       }
3215     }
3216   else return 1;
3217   return 0;
3218   }
3219 
read_param_args()3220 EX int read_param_args() {
3221   const string& s = arg::args();
3222   auto pos = s.find("=");
3223   if(pos == string::npos) return 1;
3224   string name = s.substr(0, pos);
3225   string value = s.substr(pos+1);
3226   PHASEFROM(2);
3227   if(!params.count(name))  {
3228     println(hlog, "parameter unknown: ", name);
3229     exit(1);
3230     }
3231   params[name]->load_from(value);
3232   return 0;
3233   }
3234 
3235 // mode changes:
3236 
read_gamemode_args()3237 EX int read_gamemode_args() {
3238   using namespace arg;
3239 
3240   if(argis("-P")) {
3241     PHASE(2); shift();
3242     stop_game_and_switch_mode(rg::nothing);
3243     multi::players = argi();
3244     }
3245   TOGGLE('S', shmup::on, stop_game_and_switch_mode(rg::shmup))
3246   TOGGLE('H', hardcore, switchHardcore())
3247   TOGGLE('R', randomPatternsMode, stop_game_and_switch_mode(rg::randpattern))
3248   TOGGLE('i', inv::on, stop_game_and_switch_mode(rg::inv))
3249 
3250   else return 1;
3251   return 0;
3252   }
3253 
3254 auto ah_config =
3255   addHook(hooks_args, 0, read_config_args) +
3256   addHook(hooks_args, 0, read_param_args) +
3257   addHook(hooks_args, 0, read_gamemode_args) + addHook(hooks_args, 0, read_color_args);
3258 #endif
3259 
3260 }
3261