1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2024-11-08 15:09:54 +00:00
hyperrogue/config.cpp

4284 lines
150 KiB
C++

// Hyperbolic Rogue -- configuration
// Copyright (C) 2017-2018 Zeno Rogue, see 'hyper.cpp' for details
/** \file config.cpp
* \brief Configuration -- initial settings, saving/loading ini files, menus, etc.
*/
#include "hyper.h"
namespace hr {
#if HDR
enum eCentering { face, edge, vertex };
#endif
EX eCentering centering;
EX function<bool()> auto_restrict;
EX void add_to_changed(struct parameter *f);
EX bool return_false() { return false; }
EX string param_esc(string s);
EX void non_editable_reaction() {
if(game_active) { stop_game(); if(!delayed_start) start_game(); }
}
#if HDR
struct parameter_names {
string name;
string legacy_config_name;
parameter_names(const string& s) { name = param_esc(s); legacy_config_name = s; }
parameter_names(const char* s) { name = param_esc(s); legacy_config_name = s; }
parameter_names(const string& p, const string& s) { name = p; legacy_config_name = s; }
};
struct parameter : public std::enable_shared_from_this<parameter> {
function<bool()> restrict;
string name;
string legacy_config_name;
string menu_item_name;
bool menu_item_name_modified;
string help_text;
reaction_t reaction;
char default_key;
bool is_editable;
bool needs_confirm;
virtual bool available() { if(restrict) return restrict(); return true; }
virtual bool affects(void *v) { return false; }
void show_edit_option() { show_edit_option(default_key); }
virtual void show_edit_option(int key) {
println(hlog, "default called!"); }
virtual string search_key() {
return name + "|" + legacy_config_name + "|" + menu_item_name + "|" + help_text;
}
explicit parameter() { restrict = auto_restrict; is_editable = false; needs_confirm = false; }
void be_non_editable() {
reaction = non_editable_reaction;
}
virtual void check_change() { }
reaction_t sets;
parameter *set_sets(const reaction_t& s) { sets = s; return this; }
parameter *set_extra(const reaction_t& r);
parameter *set_reaction(const reaction_t& r);
virtual ~parameter() = default;
virtual bool load_from_animation(const string& s) {
load(s); return false;
}
virtual void load_as_animation(const string& s) {
load(s);
}
virtual bool anim_unchanged() { return true; }
virtual void anim_restore() { }
virtual cld get_cld() { throw hr_exception("parameter has no complex value"); }
virtual void set_cld_raw(cld x) { throw hr_exception("parameter has no complex value"); }
virtual void set_cld(cld value) {
auto bak = get_cld();
set_cld_raw(value);
if(value != bak && reaction) reaction();
}
virtual string save() = 0;
virtual void load(const string& s) {
println(hlog, "cannot load parameter: ", name, " from: ", s);
throw hr_exception("parameter cannot be loaded");
}
virtual bool dosave() = 0;
virtual void reset() = 0;
virtual void set_default() = 0;
virtual void swap_with(parameter*) = 0;
virtual shared_ptr<parameter> clone(struct local_parameter_set& lps, void *value) { println(hlog, "parameter not cloneable: ", name); throw hr_exception("not cloneable"); }
void setup(const parameter_names& s);
};
struct local_parameter_set {
string label;
local_parameter_set* extends;
vector<pair<shared_ptr<parameter>, shared_ptr<parameter>>> swaps;
void pswitch();
local_parameter_set(string l, local_parameter_set *ext = nullptr) : label(l), extends(ext) {}
parameter_names mod(parameter *t);
};
#endif
parameter *parameter::set_extra(const reaction_t& r) {
auto s = sets; set_sets([s, r] { if(s) s(); dialog::get_di().extra_options = r; }); return this;
}
parameter *parameter::set_reaction(const reaction_t& r) {
reaction = r; return this;
}
#if HDR
using paramlist = map<string, std::shared_ptr<parameter>>;
#endif
EX paramlist params;
EX void show_edit_option_enum(char* value, const string& name, const vector<pair<string, string>>& options, char key, parameter *s);
#if HDR
struct list_parameter : parameter {
virtual int get_value() = 0;
virtual void set_value(int i) = 0;
vector<pair<string, string> > options;
list_parameter* editable(const vector<pair<string, string> >& o, string menu_item_name, char key) {
is_editable = true;
options = o;
this->menu_item_name = menu_item_name;
menu_item_name_modified = true;
default_key = key;
return this;
}
void show_edit_option(int key) override;
};
namespace anims {
extern void animate_parameter(parameter*, string);
}
template<class T> struct enum_parameter : list_parameter {
T *value, last_value, dft, anim_value;
int get_value() override { return (int) *value; }
hr::function<void(T)> set_value_to;
void set_value(int i) override { set_value_to((T)i); }
bool affects(void* v) override { return v == value; }
virtual void load_from_raw(const string& s) {
int N = isize(options);
for(int i=0; i<N; i++) if(appears(options[i].first, s)) {
set_value_to((T)i);
return;
}
*value = (T) parseint(s);
}
void check_change() override {
if(*value != last_value) {
last_value = *value;
add_to_changed(this);
}
}
void load(const string& s) override {
auto bak = *value;
load_from_raw(s);
if(*value != bak && reaction) reaction();
}
bool load_from_animation(const string& s) override {
if(anim_value != *value) return false;
load(s);
anim_value = *value;
return true;
}
void load_as_animation(const string& s) override {
load(s);
anim_value = *value;
anims::animate_parameter(this, s);
}
enum_parameter<T>* editable(const vector<pair<string, string> >& o, string menu_item_name, char key) {
list_parameter::editable(o, menu_item_name, key);
return this;
}
enum_parameter<T>* set_need_confirm() {
needs_confirm = true;
return this;
}
virtual cld get_cld() override { return get_value(); }
bool dosave() override { return *value != dft; }
void reset() override { *value = dft; }
string save() override { return its(int(*value)); }
void set_default() override { dft = *value; }
shared_ptr<parameter> clone(struct local_parameter_set& lps, void *value) override;
void swap_with(parameter *s) override { swap(*value, *(((enum_parameter<T>*)s)->value)); }
};
template<class T> struct val_parameter;
template<class T> struct val_parameter : public parameter {
T *value, last_value, anim_value, dft;
bool dosave() override { return *value != dft; }
void reset() override { *value = dft; }
bool affects(void* v) override { return v == value; }
void set_default() override { dft = *value; }
void check_change() override {
if(*value != last_value) {
last_value = *value;
add_to_changed(this);
}
}
bool anim_unchanged() override { return *value == anim_value; }
void anim_restore() override { *value = anim_value; if(reaction) reaction(); }
virtual void load_from_raw(const string& s) { throw hr_exception("load_from_raw not defined"); }
void load(const string& s) override {
auto bak = *value;
load_from_raw(s);
if(*value != bak && reaction) reaction();
}
bool load_from_animation(const string& s) override {
if(anim_value != *value) return false;
load(s);
anim_value = *value;
return true;
}
void load_as_animation(const string& s) override {
load(s);
anim_value = *value;
anims::animate_parameter(this, s);
}
void swap_with(parameter *s) override { swap(*value, *(((val_parameter<T>*)s)->value)); }
};
struct float_parameter : public val_parameter<ld> {
ld min_value, max_value, step;
string unit;
float_parameter *editable(ld min_value, ld max_value, ld step, string menu_item_name, string help_text, char key) {
is_editable = true;
this->min_value = min_value;
this->max_value = max_value;
this->menu_item_name = menu_item_name;
menu_item_name_modified = true;
this->help_text = help_text;
this->step = step;
default_key = key;
return this;
}
function<void(float_parameter*)> modify_me;
float_parameter *modif(const function<void(float_parameter*)>& r) { modify_me = r; return this; }
void show_edit_option(int key) override;
void load_from_raw(const string& s) override { *value = parseld(s); }
cld get_cld() override { return *value; }
void set_cld_raw(cld x) override { *value = real(x); }
string save() override { return fts(*value, 10); }
shared_ptr<parameter> clone(struct local_parameter_set& lps, void *value) override;
};
struct float_parameter_dft : public float_parameter {
void show_edit_option(int key) override;
function<ld()> get_hint;
float_parameter_dft* set_hint(const function<ld()>& f) { get_hint = f; return this; }
};
struct int_parameter : public val_parameter<int> {
int min_value, max_value;
ld step;
function<void(int_parameter*)> modify_me;
int_parameter *modif(const function<void(int_parameter*)>& r) { modify_me = r; return this; }
void show_edit_option(int key) override;
int_parameter *editable(int min_value, int max_value, ld step, string menu_item_name, string help_text, char key) {
this->is_editable = true;
this->min_value = min_value;
this->max_value = max_value;
this->menu_item_name = menu_item_name;
menu_item_name_modified = true;
this->help_text = help_text;
this->step = step;
default_key = key;
return this;
}
cld get_cld() override { return *value; }
void load_from_raw(const string& s) override { *value = parseint(s); }
void set_cld_raw(cld x) override { *value = (int)(real(x) + .5); }
void check_change() override {
if(*value != last_value) {
last_value = *value;
add_to_changed(this);
}
}
string save() override { return its(*value); }
shared_ptr<parameter> clone(struct local_parameter_set& lps, void *value) override;
};
struct string_parameter: public val_parameter<string> {
string save() override { return *value; }
void load_from_raw(const string& s) override { *value = s; }
};
struct char_parameter : public val_parameter<char> {
string save() override { return "\\" + its(*value); }
void show_edit_option(int key) override;
void load_from_raw(const string& s) override {
if(s[0] == '\\' && s.size() > 1) *value = parseint(s.substr(1));
else sscanf(s.c_str(), "%c", value);
}
};
struct bool_parameter : public val_parameter<bool> {
string save() override { return (*value) ? "yes" : "no"; }
reaction_t switcher;
bool_parameter* editable(string cap, char key ) {
is_editable = true;
menu_item_name = cap; default_key = key;
menu_item_name_modified = true;
return this;
}
void show_edit_option(int key) override;
void load_from_raw(const string& s) override {
if(s == "yes") *value = true;
else if(s == "no") *value = false;
else *value = parseint(s);
}
cld get_cld() override { return *value; }
shared_ptr<parameter> clone(struct local_parameter_set& lps, void *value) override;
};
struct color_parameter : public val_parameter<color_t> {
bool has_alpha;
void show_edit_option(int key) override;
color_parameter *editable(string menu_item_name, string help_text, char key) {
this->is_editable = true;
this->menu_item_name = menu_item_name;
menu_item_name_modified = true;
this->help_text = help_text;
default_key = key;
return this;
}
void load_from_raw(const string& s) override { sscanf(s.c_str(), "%x", value); }
string save() override { return itsh(*value); }
shared_ptr<parameter> clone(struct local_parameter_set& lps, void *value) override;
};
/** transmatrix with equality, so we can construct val_parameter<matrix_eq> */
struct matrix_eq : transmatrix {
bool operator == (const transmatrix& t) const {
for(int i=0; i<MAXMDIM; i++) for(int j=0; j<MAXMDIM; j++) if(self[i][j] != t[i][j]) return false;
return true;
}
bool operator != (const transmatrix& t) const {
return ! (self == t);
}
};
struct matrix_parameter : public val_parameter<matrix_eq> {
void reset() override { *value = dft; }
void set_default() override { dft = *value; }
bool dosave() override { return !eqmatrix(*value, dft); }
int dim;
void show_edit_option(int key) override;
matrix_parameter *editable(string menu_item_name, string help_text, char key) {
this->is_editable = true;
this->menu_item_name = menu_item_name;
menu_item_name_modified = true;
this->help_text = help_text;
default_key = key;
return this;
}
string save() override {
shstream ss;
print(ss, "matrix ");
for(int a=0; a<4; a++) for(int b=0; b<4; b++) print(ss, (*value)[a][b], " ");
return ss.s;
}
void load_from_raw(const string& s) override {
if(s.substr(0, 7) == "matrix ") {
shstream ss;
ss.s = s.substr(7);
for(int a=0; a<4; a++) for(int b=0; b<4; b++) scan(ss, (*value)[a][b]);
}
else {
(transmatrix&)*value = parsematrix(s);
}
}
};
struct custom_parameter : public parameter {
cld last_value, anim_value;
function<void(char)> custom_viewer;
function<cld()> custom_value;
function<bool(void*)> custom_affect;
function<void(const string&)> custom_load;
function<string()> custom_save;
function<bool()> custom_do_save;
function<shared_ptr<parameter>(struct local_parameter_set& lps, void *value)> custom_clone;
virtual shared_ptr<parameter> clone(struct local_parameter_set& lps, void *value) {
if(custom_clone) return custom_clone(lps, value);
return parameter::clone(lps, value);
}
void show_edit_option(int key) override { custom_viewer(key); }
bool affects(void *v) override { return custom_affect(v); }
void check_change() override {
if(custom_value() != last_value) {
last_value = custom_value();
add_to_changed(this);
}
}
virtual void load_from_raw(const string& s) {
if(!custom_load) {
println(hlog, "cannot load parameter: ", name, " from: ", s);
throw hr_exception("parameter cannot be loaded");
}
custom_load(s);
}
void load(const string& s) override {
auto bak = get_cld();
load_from_raw(s);
if(bak != get_cld() && reaction) reaction();
}
bool load_from_animation(const string& s) override {
if(anim_value != get_cld()) return false;
load(s);
anim_value = get_cld();
return true;
}
void load_as_animation(const string& s) override {
load(s);
anim_value = get_cld();
anims::animate_parameter(this, s);
}
virtual cld get_cld() override { return custom_value(); }
virtual string save() override { if(custom_save) return custom_save(); else return "not saveable"; }
virtual bool dosave() override { if(custom_do_save) return custom_do_save(); else return false; }
virtual void reset() override {}
virtual void set_default() override {}
virtual void swap_with(parameter*) override {}
};
template<class T> void set_saver_default(T& val) {
for(auto param: params)
if(param.second->affects(&val))
param.second->set_default();
}
#endif
void non_editable() {
dialog::addHelp("Warning: editing this value through this menu may not work correctly");
}
void float_parameter::show_edit_option(int key) {
if(modify_me) modify_me(this);
dialog::addSelItem(XLAT(menu_item_name), fts(*value) + unit, key);
if(*value == use_the_default_value) dialog::lastItem().value = XLAT("default");
dialog::add_action([this] () {
add_to_changed(this);
dialog::editNumber(*value, min_value, max_value, step, dft, XLAT(menu_item_name), help_text);
if(sets) sets();
if(reaction) dialog::get_di().reaction = reaction;
if(!is_editable) dialog::get_di().extra_options = non_editable;
});
}
void float_parameter_dft::show_edit_option(int key) {
if(modify_me) modify_me(this);
dialog::addSelItem(XLAT(menu_item_name), fts(*value) + unit, key);
if(*value == use_the_default_value) dialog::lastItem().value = XLAT("default: ") + fts(get_hint());
dialog::add_action([this] () {
add_to_changed(this);
if(*value == use_the_default_value) *value = get_hint();
dialog::editNumber(*value, min_value, max_value, step, dft, XLAT(menu_item_name), help_text);
if(sets) sets();
if(reaction) dialog::get_di().reaction = reaction;
if(!is_editable) dialog::get_di().extra_options = non_editable;
auto eo = dialog::get_di().extra_options;
dialog::get_di().extra_options = [eo, this] {
dialog::addSelItem(XLAT("use the default value"), "", 'D');
dialog::add_action([this] { *value = use_the_default_value; });
if(eo) eo();
};
});
}
void int_parameter::show_edit_option(int key) {
if(modify_me) modify_me(this);
dialog::addSelItem(XLAT(menu_item_name), its(*value), key);
dialog::add_action([this] () {
add_to_changed(this);
dialog::editNumber(*value, min_value, max_value, step, dft, XLAT(menu_item_name), help_text);
if(sets) sets();
if(reaction) dialog::get_di().reaction = reaction;
if(!is_editable) dialog::get_di().extra_options = non_editable;
});
}
void bool_parameter::show_edit_option(int key) {
dialog::addBoolItem(XLAT(menu_item_name), *value, key);
dialog::add_action([this] () {
add_to_changed(this);
switcher(); if(sets) sets();
if(reaction) reaction();
});
}
void color_parameter::show_edit_option(int key) {
dialog::addColorItem(XLAT(menu_item_name), has_alpha ? *value : addalpha(*value), key);
dialog::add_action([this] () {
dialog::openColorDialog(*value);
dialog::colorAlpha = has_alpha;
dialog::get_di().dialogflags |= sm::SIDE;
});
}
void matrix_parameter::show_edit_option(int key) {
dialog::addMatrixItem(XLAT(menu_item_name), *value, key, dim);
dialog::add_action([this] () {
dialog::editMatrix(*value, XLAT(menu_item_name), help_text, dim);
if(sets) sets();
if(reaction) dialog::get_di().reaction = reaction;
});
}
void char_parameter::show_edit_option(int key) {
string s = s0; s += value;
dialog::addSelItem(XLAT(menu_item_name), s, key);
}
void parameter::setup(const parameter_names& n) {
name = n.name;
legacy_config_name = n.legacy_config_name;
menu_item_name = n.legacy_config_name;
menu_item_name_modified = false;
if(params.count(name)) println(hlog, "ERROR: repeated parameter name ", name);
params[name] = shared_from_this();
}
EX shared_ptr<float_parameter> param_f(ld& val, const parameter_names& n, ld dft) {
shared_ptr<float_parameter> u ( new float_parameter );
u->setup(n);
u->value = &val;
u->last_value = dft;
if(dft == 0) {
u->min_value = -100;
u->max_value = +100;
}
else {
u->min_value = 0;
u->max_value = 2 * dft;
}
u->step = dft / 10;
u->dft = dft;
val = dft;
return u;
}
EX shared_ptr<float_parameter_dft> param_fd(ld& val, const parameter_names& n, ld dft IS(use_the_default_value) ) {
shared_ptr<float_parameter_dft> u ( new float_parameter_dft );
u->setup(n);
u->value = &val;
u->last_value = dft;
u->min_value = -100;
u->max_value = +100;
u->step = 1;
u->dft = dft;
val = dft;
return u;
}
EX string param_esc(string s) {
string out;
for(char c: s)
if(c == ' ' || c == '-' || c == ':')
out += '_';
else
out += c;
return out;
}
EX shared_ptr<int_parameter> param_i(int& val, const parameter_names& n, int dft) {
shared_ptr<int_parameter> u ( new int_parameter );
u->setup(n);
u->value = &val;
u->last_value = dft;
u->dft = dft;
val = dft;
if(dft == 0) {
u->min_value = -100;
u->max_value = +100;
}
else {
u->min_value = 0;
u->max_value = 2 * dft;
}
return u;
}
EX shared_ptr<int_parameter> param_i(int& val, const parameter_names& n) { return param_i(val, n, val); }
EX shared_ptr<bool_parameter> param_b(bool& val, const parameter_names& n, bool dft) {
shared_ptr<bool_parameter> u ( new bool_parameter );
u->setup(n);
u->value = &val;
u->last_value = dft;
u->dft = dft;
u->switcher = [&val] { val = !val; };
val = dft;
return u;
}
EX shared_ptr<color_parameter> param_color(color_t& val, const parameter_names& n, bool has_alpha, color_t dft) {
shared_ptr<color_parameter> u ( new color_parameter );
u->setup(n);
u->value = &val;
u->last_value = dft;
u->dft = dft;
u->has_alpha = has_alpha;
val = dft;
return u;
}
EX shared_ptr<matrix_parameter> param_matrix(transmatrix& val0, const parameter_names& n, int dim) {
matrix_eq& val = (matrix_eq&) val0;
shared_ptr<matrix_parameter> u ( new matrix_parameter );
u->setup(n);
u->value = &val;
u->last_value = val;
u->dft = val;
u->dim = dim;
return u;
}
EX shared_ptr<char_parameter> param_char(char& val, const parameter_names& n, char dft) {
shared_ptr<char_parameter> u ( new char_parameter );
u->setup(n);
u->value = &val;
u->last_value = dft;
u->dft = dft;
val = dft;
return u;
}
EX shared_ptr<string_parameter> param_str(string& val, const parameter_names& n, const string dft) {
shared_ptr<string_parameter> u ( new string_parameter );
u->setup(n);
u->value = &val;
u->last_value = val;
u->dft = dft;
val = dft;
return u;
}
EX shared_ptr<string_parameter> param_str(string& val, const parameter_names& n) { return param_str(val, n, val); }
EX shared_ptr<color_parameter> param_color(color_t& val, const parameter_names& n, bool has_alpha) { return param_color(val, n, has_alpha, val); }
EX shared_ptr<bool_parameter> param_b(bool& val, const parameter_names& n) { return param_b(val, n, val); }
#if HDR
template<class T> shared_ptr<enum_parameter<T>> param_enum(T& val, const parameter_names& n, T dft) {
shared_ptr<enum_parameter<T>> u ( new enum_parameter<T> );
u->setup(n);
u->value = &val;
u->dft = dft;
val = dft;
u->last_value = dft;
auto f = &*u;
u->set_value_to = [f] (T val) { *f->value = val; };
return u;
}
template<class T> shared_ptr<enum_parameter<T>> param_enum(T& val, const parameter_names& n) { return param_enum(val, n, val); }
template<class T> shared_ptr<parameter> enum_parameter<T>::clone(struct local_parameter_set& lps, void *value) { return param_enum(*(T*) value, lps.mod(this), *(T*) value); }
#endif
EX shared_ptr<float_parameter> param_f(ld& val, const parameter_names& n) {
return param_f(val, n, val);
}
parameter_names local_parameter_set::mod(parameter *t) {
return parameter_names(label + t->name, label + t->legacy_config_name);
}
shared_ptr<parameter> int_parameter::clone(struct local_parameter_set& lps, void *value) {
auto val = (int*) value;
return param_i(*val, lps.mod(this), *val);
}
shared_ptr<parameter> bool_parameter::clone(struct local_parameter_set& lps, void *value) {
auto val = (bool*) value;
return param_b(*val, lps.mod(this), *val);
}
shared_ptr<parameter> color_parameter::clone(struct local_parameter_set& lps, void *value) {
auto val = (color_t*) value;
return param_color(*val, lps.mod(this), has_alpha, *val);
}
shared_ptr<parameter> float_parameter::clone(struct local_parameter_set& lps, void *value) {
auto val = (ld*) value;
return param_f(*val, lps.mod(this), *val);
}
#if HDR
template<class T>
shared_ptr<custom_parameter> param_custom_int(T& val, const parameter_names& n, function<void(char)> menuitem, char key) {
shared_ptr<custom_parameter> u ( new custom_parameter );
u->setup(n);
int dft = (int) val;
u->last_value = dft;
u->custom_viewer = menuitem;
u->custom_value = [&val] () { return (int) val; };
u->custom_affect = [&val] (void *v) { return &val == v; };
u->custom_load = [&val] (const string& s) { val = (T) parseint(s); };
u->custom_save = [&val] { return its(int(val)); };
u->custom_do_save = [dft, &val] { return int(val) != dft; };
u->custom_clone = [u] (struct local_parameter_set& lps, void *value) { auto val = (int*) value; return param_i(*val, lps.mod(&*u), *val); };
u->default_key = key;
u->is_editable = true;
return u;
}
#endif
EX shared_ptr<custom_parameter> param_custom_ld(ld& val, const parameter_names& n, function<void(char)> menuitem, char key) {
shared_ptr<custom_parameter> u ( new custom_parameter );
u->setup(n);
ld dft = val;
u->last_value = dft;
u->custom_viewer = menuitem;
u->custom_value = [&val] () { return val; };
u->custom_affect = [&val] (void *v) { return &val == v; };
u->custom_load = [&val] (const string& s) { val = parseld(s); };
u->custom_save = [&val] { return fts(val, 10); };
u->custom_do_save = [dft, &val] { return val != dft; };
u->custom_clone = [u] (struct local_parameter_set& lps, void *value) { auto val = (ld*) value; return param_f(*val, lps.mod(&*u), *val); };
u->default_key = key;
u->is_editable = true;
return u;
}
EX ld bounded_mine_percentage = 0.1;
EX int bounded_mine_quantity, bounded_mine_max;
EX const char *conffile = "hyperrogue.ini";
/* extra space if more geometries are added */
EX array<ld, gGUARD+64> sightranges;
EX bool logfog;
EX videopar vid;
#define DEFAULT_WALLMODE (ISMOBILE ? 3 : 5)
#define DEFAULT_MONMODE (ISMOBILE ? 2 : 4)
EX void android_settings_changed() {
#if ISANDROID
settingsChanged = true;
#endif
}
extern color_t floorcolors[landtypes];
EX charstyle& getcs(int id IS(multi::cpid)) {
if(multi::players>1 && id >= 0 && id < multi::players)
return multi::scs[id];
else
return vid.cs;
}
struct charstyle_old {
int charid;
color_t skincolor, haircolor, dresscolor, swordcolor, dresscolor2, uicolor;
bool lefthanded;
};
struct charstyle_prebow {
int charid;
color_t skincolor, haircolor, dresscolor, swordcolor, dresscolor2, uicolor, eyecolor;
bool lefthanded;
};
EX void hread(hstream& hs, charstyle& cs) {
// before 0xA61A there was no eyecolor
if(hs.get_vernum() < 0xA61A) {
charstyle_old cso;
hread_raw(hs, cso);
cs.charid = cso.charid;
cs.skincolor = cso.skincolor;
cs.haircolor = cso.haircolor;
cs.dresscolor = cso.dresscolor;
cs.swordcolor = cs.eyecolor = cs.bowcolor = cs.bowcolor2 = cso.swordcolor;
if(cs.charid < 4) cs.eyecolor = 0;
cs.dresscolor2 = cso.dresscolor2;
cs.uicolor = cso.uicolor;
cs.lefthanded = cso.lefthanded;
}
else if(hs.get_vernum() < 0xA938) {
charstyle_prebow cso;
hread_raw(hs, cso);
cs.charid = cso.charid;
cs.skincolor = cso.skincolor;
cs.haircolor = cso.haircolor;
cs.dresscolor = cso.dresscolor;
cs.eyecolor = cso.eyecolor;
cs.swordcolor = cs.bowcolor = cs.bowcolor2 = cso.swordcolor;
if(cs.charid < 4) cs.eyecolor = 0;
cs.dresscolor2 = cso.dresscolor2;
cs.uicolor = cso.uicolor;
cs.lefthanded = cso.lefthanded;
}
else hread_raw(hs, cs);
}
EX void hwrite(hstream& hs, const charstyle& cs) {
hwrite_raw(hs, cs);
}
// void hread(hstream& hs, charstyle& cs) { hread_raw(hs, cs); }
// void hwrite(hstream& hs, const charstyle& cs) { hwrite_raw(hs, cs); }
EX string csnameid(int id) {
if(id == 0) return XLAT("male");
if(id == 1) return XLAT("female");
if(id == 2) return XLAT("Prince");
if(id == 3) return XLAT("Princess");
if(id == 4 || id == 5) return XLAT("cat");
if(id == 6 || id == 7) return XLAT("dog");
if(id == 8 || id == 9) return XLATN("Familiar");
return XLAT("none");
}
EX string csname(charstyle& cs) {
return csnameid(cs.charid);
}
EX int playergender() {
return (getcs().charid >= 0 && (getcs().charid&1)) ? GEN_F : GEN_M;
}
EX int princessgender() {
int g = playergender();
if(vid.samegender) return g;
return g == GEN_M ? GEN_F : GEN_M;
}
EX int default_language;
EX int lang() {
if(vid.language >= 0)
return vid.language;
return default_language;
}
EX bool autojoy = true;
EX void paramset(charstyle& cs, string s) {
param_i(cs.charid, s + ".charid");
param_color(cs.skincolor, s + ".skincolor", true, cs.skincolor);
param_color(cs.eyecolor, s + ".eyecolor", true, cs.eyecolor);
param_color(cs.bowcolor, s + ".bowcolor", true, cs.bowcolor);
param_color(cs.bowcolor2, s + ".bowcolor2", true, cs.bowcolor2);
param_color(cs.haircolor, s + ".haircolor", true, cs.haircolor);
param_color(cs.dresscolor, s + ".dresscolor", true, cs.dresscolor);
param_color(cs.swordcolor, s + ".swordcolor", true, cs.swordcolor);
param_color(cs.dresscolor2, s + ".dresscolor2", true, cs.dresscolor2);
param_color(cs.uicolor, s + ".uicolor", true, cs.uicolor);
param_b(cs.lefthanded, s + ".lefthanded");
}
// R:239, G:208, B:207
unsigned int skincolors[] = { 7, 0xD0D0D0FF, 0xEFD0C9FF, 0xC77A58FF, 0xA58869FF, 0x602010FF, 0xFFDCB1FF, 0xEDE4C8FF };
unsigned int haircolors[] = { 8, 0x686868FF, 0x8C684AFF, 0xF2E1AEFF, 0xB55239FF, 0xFFFFFFFF, 0x804000FF, 0x502810FF, 0x301800FF };
unsigned int dresscolors[] = { 6, 0xC00000FF, 0x00C000FF, 0x0000C0FF, 0xC0C000FF, 0xC0C0C0FF, 0x202020FF };
unsigned int dresscolors2[] = { 7, 0x8080FFC0, 0x80FF80C0, 0xFF8080C0, 0xFFFF80C0, 0xFF80FFC0, 0x80FFFFC0, 0xFFFFFF80 };
unsigned int swordcolors[] = { 6, 0xC0C0C0FF, 0xFFFFFFFF, 0xFFC0C0FF, 0xC0C0FFFF, 0x808080FF, 0x202020FF };
unsigned int eyecolors[] = { 4, 0x00C000FF, 0x0000C0FF, 0xC00000FF, 0xC0C000FF, 0x804010FF, 0x00C000FF };
EX void initcs(charstyle &cs) {
cs.charid = 0;
cs.skincolor = 0xD0D0D0FF;
cs.haircolor = 0x686868FF;
cs.dresscolor = 0xC00000FF;
cs.swordcolor = 0xD0D0D0FF;
cs.dresscolor2= 0x8080FFC0;
cs.uicolor = 0xFF0000FF;
cs.eyecolor = 0x603000FF;
cs.bowcolor = 0x603000FF;
cs.bowcolor2 = 0xFFD500FF;
cs.lefthanded = false;
}
EX void savecolortable(colortable& ct, string name) {
for(int i=0; i<isize(ct); i++)
param_color(ct[i], "color:" + name + ":" + its(i), false, ct[i]);
}
EX purehookset hooks_configfile;
EX ld mapfontscale = 100;
EX void initConfig() {
// basic config
param_i(vid.flashtime, "flashtime", 8);
param_i(vid.msglimit, "message limit", 5);
param_i(vid.timeformat, "message log time format", 0);
param_b(resizable, "resizable", true)
-> editable("resizable window", 'r');
param_b(no_find_player, "no_find_player");
param_b(game_keys_scroll, "game_keys_scroll")
-> editable("pure exploration (game keys scroll)", 'P');
param_b(reg3::cubes_reg3, "cubes_reg3");
param_f(linepatterns::tree_starter, "tree_starter")
-> editable(0, 1, 0.05, "tree-drawing parameter", "How much of edges to draw for tree patterns (to show how the tree edges are oriented).", 't');
// param_char(patterns::whichCanvas, "whichCanvas", 0); %TODO
param_i(ccolor::rwalls, "randomwalls");
param_b(vid.grid, "grid");
param_b(models::desitter_projections, "desitter_projections", false);
param_b(nonisotropic_weird_transforms, "nonisotropic_weird_transforms", false);
param_b(arb::apeirogon_consistent_coloring, "apeirogon_consistent_coloring", true)
-> editable("apeirogon_consistent_coloring", 'c');
param_b(arb::apeirogon_hide_grid_edges, "apeirogon_hide_grid_edges", true)
-> editable("apeirogon_hide_grid_edges", 'h');
param_b(arb::apeirogon_simplified_display, "apeirogon_simplified_display", false)
-> editable("simplified display of apeirogons", 'f');
param_b(arb::convert::minimize_on_convert, "tes_minimize_on_convert", false)
-> editable("consider all symmetries when converting", 'm');
param_b(arb::convert::reverse_order, "tes_reverse_order", false)
-> editable("tes reverse order on convert", 'r');
param_b(display_yasc_codes, "yasc", false)
-> editable("YASC codes", 'Y')
-> set_reaction([] {
addMessage(XLAT("YASC codes: Sides-Entity-Restrict-Threat-Wall"));
});
param_b(vid.relative_font, "relative_font", true)
-> editable("set relative font size", 'r')
-> set_reaction(compute_fsize);
param_i(vid.fontscale, "fontscale", 100)
-> editable(25, 400, 10, "font scale", "", 'b')
-> set_reaction(compute_fsize)
-> set_sets([] { dialog::bound_low(0); });
param_f(mapfontscale, "mapfontscale", 100)
-> editable(-400, 400, 10, "map font scale",
"This affects the size of the characters on the ASCII map. This includes ASCII walls/monster display mode, the minimap, minefield values, and various debug features.", 'B')
->set_extra([] {
dialog::get_di().extra_options = [] () { draw_radar(true); };
});
param_i(vid.abs_fsize, "fsize", 12)
-> editable(1, 72, 1, "font size", "", 'b')
-> set_reaction(compute_fsize)
-> set_sets([] { dialog::bound_low(0); });
param_i(vid.mobilecompasssize, "mobile compass size", 0); // ISMOBILE || ISPANDORA ? 30 : 0);
param_i(vid.radarsize, "radarsize size", 120);
param_f(vid.radarrange, "radarrange", 2.5);
param_i(vid.axes, "movement help", 1);
param_b(vid.axes3, "movement help3", true);
param_i(vid.shifttarget, "shift-targetting", 2);
param_i(vid.steamscore, "scores to Steam", 1);
initcs(vid.cs); paramset(vid.cs, "single");
param_b(vid.samegender, "princess choice", false);
param_i(vid.language, "language", -1);
param_b(vid.drawmousecircle, "mouse circle", ISMOBILE || ISPANDORA);
param_b(vid.revcontrol, "reverse control", false);
#if CAP_AUDIO
param_i(musicvolume, "music volume")
->editable(0, 128, 10, "background music volume", "", 'b')
->set_sets(sets_music_volume);
#endif
#if CAP_SDLAUDIO
param_b(music_out_of_focus, "music out of focus", false);
#endif
#if CAP_AUDIO
param_i(effvolume, "sound effect volume")
->editable(0, 128, 10, "sound effects volume", "", 'e')
->set_sets(sets_sfx_volume);
#endif
param_enum(vid.faraway_highlight, parameter_names("faraway_highlight", "highlight faraway monsters"), tlNoThreat)
->editable({{"off", ""}, {"spam", ""}, {"normal monsters", ""}, {"high-threat monsters only", ""}}, "highlight faraway monsters", 'h');
param_i(vid.faraway_highlight_color, "faraway_highlight_color", 50)
-> editable(0, 100, 10, "faraway highlight color", "0 = monster color, 100 = red-light oscillation", 'c');
param_enum(glyphsortorder, parameter_names("glyph_sort", "glyph sort order"), glyphsortorder)
->editable({
{"first on top", ""},
{"first on bottom", ""},
{"last on top", ""},
{"last on bottom", ""},
{"by land", ""},
{"by number", ""}
}, "inventory/kill sorting", 'k');
param_enum(vid.orbmode, parameter_names("orb_mode", "orb display mode"), 2)
->editable({
{"plain", ""},
{"types", ""},
{"icons", ""},
}, "orb display mode", 'o');
param_b(less_in_landscape, "less_in_landscape", false)
->editable("less items/kills in landscape", 'L')
-> set_reaction([] { vid.killreduction = 0; });
param_b(less_in_portrait, "less_in_portrait", false)
->editable("less items/kills in portrait", 'P')
-> set_reaction([] { vid.killreduction = 0; });
// basic graphics
param_b(vid.wantGL, "usingGL", true)
->editable("openGL mode", 'o');
param_i(vid.want_antialias, "antialias", AA_NOGL | AA_FONT | (ISWEB ? AA_MULTI : AA_LINES) | AA_VERSION);
param_b(vid.fineline, "fineline", true);
param_f(vid.linewidth, "linewidth", 1);
param_f(precise_width, "precisewidth", .5);
param_i(perfect_linewidth, "perfect_linewidth", 1);
param_f(linepatterns::width, parameter_names("lpwidth", "pattern-linewidth"), 1);
param_b(fat_edges, "fat-edges");
param_f(vid.sspeed, parameter_names("sspeed", "scrollingspeed"), 0);
param_f(vid.mspeed, parameter_names("mspeed", "movement speed"), 1);
param_f(vid.ispeed, parameter_names("ispeed", "idle speed"), 1);
param_i(vid.aurastr, "aura strength", ISMOBILE ? 0 : 128);
param_i(vid.aurasmoothen, "aura smoothen", 5);
param_enum(vid.graphglyph, parameter_names("graphglyph", "graphical items/kills"), 1)
-> editable({{"letters", ""}, {"auto", ""}, {"images", ""}}, "inventory/kill mode", 'd');
param_i(min_cells_drawn, "min_cells_drawn");
param_b(show_turns, "show_turns", false)
-> editable("show turn count", 'T');
param_i(menu_darkening, "menu_darkening", 2)
-> editable(0, 8, 1, "menu map darkening", "A larger number means darker game map in the background. Set to 8 to disable the background.", 'd')
-> set_sets([] { dialog::bound_low(0); dialog::bound_up(8); dialog::get_di().dialogflags |= sm::DARKEN; });
param_b(centered_menus, "centered_menus", false)
-> editable("centered menus in widescreen", 'c');
param_b(startanims::enabled, "startanim", true)
-> editable("start animations", 's');
param_b(vid.flasheffects, "flasheffects", 1);
param_f(vid.binary_width, parameter_names("bwidth", "binary-tiling-width"), 1);
param_custom_ld(vid.binary_width, "binary tiling width", menuitem_binary_width, 'v');
param_b(fake::multiple_special_draw, "fake_multiple", true);
param_f(hat::hat_param, "hat_param", 1)
-> editable(0, 2, 0.1, "hat/spectre/turtle parameter",
"Apeirodic hat tiling based on: https://arxiv.org/pdf/2303.10798.pdf\n\n"
"This controls the parameter discussed in Section 6. Parameter p is Tile(p, (2-p)√3), scaled so that the area is the same for every p.\n\n"
"Aperiodic spectre tiling based on: https://arxiv.org/abs/2305.17743\n\n"
"In the spectre tiling, set the parameter to 'spectre' value to make all tiles have the same shape."
,
'v'
)
-> set_extra([] {
dialog::addSelItem(XLAT("chevron (periodic)"), "0", 'C');
dialog::add_action([] { dialog::get_ne().s = "0"; dialog::get_ne().apply_edit(); });
dialog::addSelItem(XLAT("hat"), "1", 'H');
dialog::add_action([] { dialog::get_ne().s = "1"; dialog::get_ne().apply_edit(); });
dialog::addSelItem(XLAT("spectre"), "3-√3", 'T');
dialog::add_action([] { dialog::get_ne().s = "3 - sqrt(3)"; dialog::get_ne().apply_edit(); });
dialog::addSelItem(XLAT("turtle"), "1.5", 'T');
dialog::add_action([] { dialog::get_ne().s = "1.5"; dialog::get_ne().apply_edit(); });
dialog::addSelItem(XLAT("comma (periodic)"), "2", ',');
dialog::add_action([] { dialog::get_ne().s = "2"; dialog::get_ne().apply_edit(); });
})
-> set_reaction(hat::reshape);
param_f(hat::hat_param_imag, "hat_param_imag", 0)
-> editable(0, 2, 0.1, "hat parameter (imaginary)",
"Imaginary part of the hat parameter. This corresponds to the usual interpretation of complex numbers in Euclidean planar geometry: rather than shortened or lengthened, the edges are moved in the other dimension.", 'v'
)
-> set_reaction(hat::reshape);
param_b(vid.particles, "extra effects", 1);
param_i(vid.framelimit, "frame limit", 999);
#if !ISMOBWEB
param_b(vid.want_vsync, "vsync", true)
->editable("vsync", 'v');
#endif
param_b(vid.want_fullscreen, "fullscreen", false)
->editable("fullscreen mode", 'f');
param_b(vid.change_fullscr, "fullscreen_change", false)
->editable("use specific fullscreen resolution", 'g');
param_b(vid.relative_window_size, "window_relative", true)
->editable("specify relative window size", 'g');
param_custom_int(vid.xres, "xres", [] (char ch) {}, 0)->restrict = return_false;
param_custom_int(vid.yres, "yres", [] (char ch) {}, 0)->restrict = return_false;
param_i(vid.fullscreen_x, "fullscreen_x", 1280)
-> editable(640, 3840, 640, "fullscreen resolution to use (X)", "", 'x')
-> set_sets([] { dialog::bound_low(640); dialog::get_di().reaction_final = do_request_resolution_change; });
param_i(vid.fullscreen_y, "fullscreen_y", 1024)
-> editable(480, 2160, 480, "fullscreen resolution to use (Y)", "", 'x')
-> set_sets([] { dialog::bound_low(480); dialog::get_di().reaction_final = do_request_resolution_change; });
param_i(vid.window_x, "window_x", 1280)
-> editable(160, 3840, 160, "window resolution to use (X)", "", 'x')
-> set_sets([] { dialog::bound_low(160); dialog::get_di().reaction_final = do_request_resolution_change; });
param_i(vid.window_y, "window_y", 1024)
-> editable(120, 2160, 120, "window resolution to use (Y)", "", 'x')
-> set_sets([] { dialog::bound_low(120); dialog::get_di().reaction_final = do_request_resolution_change; });
param_f(vid.window_rel_x, "window_rel_x", .9)
-> editable(.1, 1, .1, "screen size percentage to use (X)", "", 'x')
-> set_sets([] { dialog::bound_low(.1); dialog::get_di().reaction_final = do_request_resolution_change; });
param_f(vid.window_rel_y, "window_rel_y", .9)
-> editable(.1, 1, .1, "screen size percentage to use (Y)", "", 'x')
-> set_sets([] { dialog::bound_low(.1); dialog::get_di().reaction_final = do_request_resolution_change; });
param_b(vid.darkhepta, "mark heptagons", false);
param_b(logfog, "logfog", false);
for(auto& lp: linepatterns::patterns) {
param_color(lp->color, "lpcolor-" + lp->lpname, true, lp->color);
param_f(lp->multiplier, "lpwidth-" + lp->lpname);
}
// special graphics
param_i(vid.monmode, "monster display mode", DEFAULT_MONMODE);
param_i(vid.wallmode, "wall display mode", DEFAULT_WALLMODE);
param_i(vid.highlightmode, "highlightmode");
param_b(vid.always3, "3D always", false);
param_f(geom3::euclid_embed_scale, "euclid_embed_scale")
-> editable(0, 2, 0.05, "Euclidean embedding scale", "How to scale the Euclidean map, relatively to the 3D absolute unit.", 'X')
-> set_sets([] { dialog::bound_low(0.05); })
-> set_reaction(geom3::apply_settings_light);
param_f(geom3::euclid_embed_scale_y, "euclid_embed_scale_y")
-> editable(0, 2, 0.05, "Euclidean embedding scale Y/X", "This scaling factor affects only the Y coordinate.", 'Y')
-> set_sets([] { dialog::bound_low(0.05); })
-> set_reaction(geom3::apply_settings_light);
param_f(geom3::euclid_embed_rotate, "euclid_embed_rotate")
-> editable(0, 360, 15, "Euclidean embedding rotation", "How to rotate the Euclidean embedding, in degrees.", 'F')
-> set_reaction(geom3::apply_settings_light);
param_enum(embedded_shift_method_choice, "embedded_shift_method", smcBoth)
-> editable({
{"geodesic", "always move on geodesics"},
{"keep levels", "keep the vertical angle of the camera"},
{"mixed", "on geodesics when moving camera manually, keep level when auto-centering"}
}, "view shift for embedded planes", 'H');
param_b(geom3::auto_configure, "auto_configure_3d")
-> editable("set 3D settings automatically", 'A');
param_b(geom3::inverted_embedding, "inverted_3d", false)
-> editable("invert convex/concave", 'I')
-> set_reaction(geom3::apply_settings_full);
param_b(geom3::flat_embedding, "flat_3d", false)
-> editable("flat, not equidistant", 'F')
-> set_reaction(geom3::apply_settings_full);
param_enum(geom3::spatial_embedding, "spatial_embedding", geom3::seDefault)
->editable(geom3::spatial_embedding_options, "3D embedding method", 'E')
->set_reaction(geom3::apply_settings_full);
param_b(memory_saving_mode, "memory_saving_mode", (ISMOBILE || ISPANDORA || ISWEB) ? 1 : 0);
param_i(reserve_limit, "memory_reserve", 128);
param_b(show_memory_warning, "show_memory_warning");
param_b(rug::renderonce, "rug-renderonce");
param_b(rug::rendernogl, "rug-rendernogl");
param_i(rug::texturesize, "rug-texturesize");
#if CAP_RUG
param_f(rug::model_distance, "rug-model-distance");
#endif
param_b(vid.backeffects, "background particle effects", (ISMOBILE || ISPANDORA) ? false : true);
// control
param_i(vid.joyvalue, "vid.joyvalue", 4800);
param_i(vid.joyvalue2, "vid.joyvalue2", 5600);
param_i(vid.joysmooth, "vid.joysmooth", 200);
param_i(vid.joypanthreshold, "vid.joypanthreshold", 2500);
param_f(vid.joypanspeed, "vid.joypanspeed", ISPANDORA ? 0.0001 : 0);
param_b(autojoy, "autojoy");
vid.killreduction = 0;
param_b(vid.skipstart, "skip the start menu", false);
param_b(vid.quickmouse, "quick mouse", !ISPANDORA);
// colors
param_f(crosshair_size, "size:crosshair")
->set_extra(draw_crosshair);
param_color(crosshair_color, "color:crosshair", true, crosshair_color)
->set_extra(draw_crosshair);
param_b(mapeditor::drawplayer, "drawplayer");
param_color((color_t&) ccolor::plain.ctab[0], "color:canvasback", false);
param_color(backcolor, "color:background", false);
param_color(forecolor, "color:foreground", false);
param_color(bordcolor, "color:borders", false);
param_color(ringcolor, "color:ring", true);
param_f(vid.multiplier_ring, parameter_names("mring", "mult:ring"), 1);
param_color(modelcolor, "color:model", true);
param_color(periodcolor, "color:period", true);
param_color(stdgridcolor, "color:stdgrid", true);
param_f(vid.multiplier_grid, parameter_names("mgrid", "mult:grid"), 1);
param_color(dialog::dialogcolor, "color:dialog", false);
for(auto p: ccolor::all)
savecolortable(p->ctab, s0+"canvas:"+p->name);
savecolortable(distcolors, "distance");
savecolortable(minecolors, "mines");
#if CAP_COMPLEX2
savecolortable(brownian::colors, "color:brown");
#endif
for(int i=0; i<motypes; i++)
param_color(minf[i].color, "color:monster:" + its(i), false);
for(int i=0; i<ittypes; i++)
param_color(iinf[i].color, "color:item:" + its(i), false);
for(int i=0; i<landtypes; i++)
param_color(floorcolors[i], "color:land:" + its(i), false);
for(int i=0; i<walltypes; i++)
param_color(winf[i].color, "color:wall:" + its(i), false);
// modes
param_b(shmup::on, "mode-shmup", false)->be_non_editable();
param_b(hardcore, "mode-hardcore", false)->be_non_editable();
param_enum(land_structure, "mode-chaos", lsNiceWalls)->be_non_editable();
#if CAP_INV
param_b(inv::on, "mode-Orb Strategy");
#endif
param_b(peace::on, "mode-peace");
param_b(peace::otherpuzzles, "mode-peace-submode");
param_enum(specialland, parameter_names("specialland", "land for special modes"), specialland)->be_non_editable();
param_b(viewdists, "expansion mode");
param_f(backbrightness, parameter_names("back", "brightness behind sphere"));
param_b(auto_extend, "expansion_auto_extend")
-> editable("extend automatically", 'E');
param_f(vid.ipd, parameter_names("ipd", "interpupilar-distance"), 0.05);
param_f(vid.lr_eyewidth, parameter_names("lr", "eyewidth-lr"), 0.5);
param_f(vid.anaglyph_eyewidth, parameter_names("anaglyph", "eyewidth-anaglyph"), 0.1);
param_f(vid.fov, parameter_names("fov", "field-of-vision"), 90);
param_i(vid.desaturate, "desaturate", 0);
param_enum(vid.stereo_mode, "stereo-mode", vid.stereo_mode)
->editable({
{"OFF", "linear perspective"}, {"anaglyph", "for red-cyan glasses"}, {"side-by-side", "for mobile VR"},
{"ODS", "for rendering 360° VR videos (implemented only in raycaster and some other parts)"},
{"Panini", "Projection believed to be used by Italian painters. Allows very high FOV angles while rendering more straight lines as straight than the stereographic projection."},
{"stereographic", "Stereographic projection allows very high FOV angles."},
{"equirectangular", "for rendering 360° videos (implemented only in raycaster)"},
{"cylindrical", "full vertical (not implemented in raycaster)"}
}, "stereo/high-FOV mode", 'm')
->set_reaction(reset_all_shaders);
param_f(vid.plevel_factor, "plevel_factor", 0.7);
param_b(nohud, "no-hud", false);
param_b(nomap, "nomap", false);
param_b(nofps, "no-fps", false);
#if CAP_IRR
param_f(irr::density, "irregular-density", 2);
param_i(irr::cellcount, "irregular-cellcount", 150);
param_f(irr::quality, "irregular-quality", .2);
param_i(irr::place_attempts, "irregular-place", 10);
param_i(irr::rearrange_max_attempts, "irregular-rearrange-max", 50);
param_i(irr::rearrange_less, "irregular-rearrangeless", 10);
#endif
param_i(vid.linequality, "line quality", 0);
#if CAP_FILES && CAP_SHOT && CAP_ANIMATIONS
param_str(anims::animfile, "animation file format");
#endif
#if CAP_RUG
param_f(rug::move_on_touch, "rug move on touch");
#endif
#if CAP_CRYSTAL
param_f(crystal::compass_probability, parameter_names("cprob", "compass-probability"));
param_b(crystal::view_coordinates, "crystal-coordinates");
#endif
#if CAP_TEXTURE
param_b(texture::texture_aura, "texture-aura", false);
#endif
param_i(vid.use_smart_range, "smart-range", 0);
param_f(vid.smart_range_detail, "smart-range-detail", 8)
->editable(1, 50, 1, "minimum visible cell in pixels", "", 'd')
->set_extra([] { add_cells_drawn('C'); });
param_f(vid.smart_range_detail_3, "smart-range-detail-3", 30)
->editable(1, 50, 1, "minimum visible cell in pixels", "", 'd')
->set_extra([] { add_cells_drawn('C'); });
param_b(vid.smart_area_based, "smart-area-based", false);
param_i(vid.cells_drawn_limit, "limit on cells drawn", 10000);
param_i(vid.cells_generated_limit, "limit on cells generated", 250);
param_enum(diskshape, "disk_shape", dshTiles)
->editable({{"distance in tiles", ""}, {"distance in vertices", ""}, {"geometric distance", ""}
}, "disk shape", 'S')
->set_reaction([] { if(game_active) { stop_game(); start_game(); } });
param_i(req_disksize, "disk_size")
->editable(10, 100000, 10, "disk size", "Play on a disk. Enables the special game rules for small bounded spaces (especially relevant for e.g. Minefield and Halloween). The number given is the number of tiles to use; it is not used exactly, actually the smallest disk above this size is used. Set to 0 to disable.", 'd')
->set_sets([] { dialog::bound_low(0); })
->set_reaction([] { if(game_active) { stop_game(); start_game(); } })
->set_extra([] {
add_edit(diskshape);
});
#if CAP_SOLV
param_f(sn::solrange_xy, "solrange-xy");
param_f(sn::solrange_z, "solrange-z");
#endif
param_i(slr::shader_iterations, "slr-steps");
param_f(slr::range_xy, "slr-range-xy");
param_f(slr::range_z, "slr-range-z");
param_f(arcm::euclidean_edge_length, "arcm-euclid-length");
#if CAP_ARCM
param_str(arcm::current.symbol, "arcm-symbol", "4^5")->be_non_editable();
#endif
param_enum(hybrid::underlying, "product_underlying", hybrid::underlying)->be_non_editable();
for(int i=0; i<isize(ginf); i++) {
if(ginf[i].flags & qELLIPTIC)
sightranges[i] = M_PI;
else if(ginf[i].cclass == gcSphere)
sightranges[i] = TAU;
else if(ginf[i].cclass == gcEuclid)
sightranges[i] = 10;
else if(ginf[i].cclass == gcSL2)
sightranges[i] = 4.5;
else if(ginf[i].cclass == gcHyperbolic && ginf[i].g.gameplay_dimension == 2)
sightranges[i] = 4.5;
else
sightranges[i] = 5;
sightranges[gArchimedean] = 10;
if(i < gBinary3) param_f(sightranges[i], "sight-g" + its(i));
}
ld bonus = 0;
ld emul = 1;
param_b(dialog::onscreen_keyboard, "onscreen_keyboard")
->editable("onscreen keyboard", 'k');
param_b(context_fog, "coolfog");
param_f(sightranges[gBinary3], "sight-binary3", 3.1 + bonus);
param_f(sightranges[gCubeTiling], "sight-cubes", 10);
param_f(sightranges[gCell120], "sight-120cell", TAU);
param_f(sightranges[gECell120], "sight-120cell-elliptic", M_PI);
param_f(sightranges[gRhombic3], "sight-rhombic", 10.5 * emul);
param_f(sightranges[gBitrunc3], "sight-bitrunc", 12 * emul);
param_f(sightranges[gSpace534], "sight-534", 4 + bonus);
param_f(sightranges[gSpace435], "sight-435", 3.8 + bonus);
param_f(sightranges[gCell5], "sight-5cell", TAU);
param_f(sightranges[gCell8], "sight-8cell", TAU);
param_f(sightranges[gECell8], "sight-8cell-elliptic", M_PI);
param_f(sightranges[gCell16], "sight-16cell", TAU);
param_f(sightranges[gECell16], "sight-16cell-elliptic", M_PI);
param_f(sightranges[gCell24], "sight-24cell", TAU);
param_f(sightranges[gECell24], "sight-24cell-elliptic", M_PI);
param_f(sightranges[gCell600], "sight-600cell", TAU);
param_f(sightranges[gECell600], "sight-600cell-elliptic", M_PI);
param_f(sightranges[gHoroTris], "sight-horotris", 2.9 + bonus);
param_f(sightranges[gHoroRec], "sight-hororec", 2.2 + bonus);
param_f(sightranges[gHoroHex], "sight-horohex", 2.75 + bonus);
param_f(sightranges[gKiteDart3], "sight-kd3", 2.25 + bonus);
param_f(sightranges[gField435], "sight-field435", 4 + bonus);
param_f(sightranges[gField534], "sight-field534", 3.8 + bonus);
param_f(sightranges[gSol], "sight-sol");
param_f(sightranges[gNil], "sight-nil", 6.5 + bonus);
param_f(sightranges[gNIH], "sight-nih");
param_f(sightranges[gSolN], "sight-solnih");
param_f(sightranges[gCrystal344], "sight-crystal344", 2.5); /* assume raycasting */
param_f(sightranges[gSpace344], "sight-344", 4.5);
param_f(sightranges[gSpace336], "sight-336", 4);
param_b(vid.sloppy_3d, "sloppy3d", true);
param_i(vid.texture_step, "wall-quality", 4);
param_b(smooth_scrolling, "smooth-scrolling", false);
param_f(mouseaim_sensitivity, "mouseaim_sensitivity", 0.01);
param_b(vid.consider_shader_projection, "shader-projection", true);
param_b(semidirect_rendering, "semidirect_rendering", false)
->editable("semidirect_rendering (perspective on GPU)", 'k');
param_i(forced_center_down, "forced_center_down")
-> editable(0, 100, 10, "forced center down", "make the center not the actual screen center", 'd');
param_b(tortoise::shading_enabled, "tortoise_shading", true);
param_f(bounded_mine_percentage, "bounded_mine_freq")
-> editable(0, 1, 0.01, "fraction of mine in bounded minefield", "", '%')
-> set_reaction([] {
if(game_active) { stop_game(); start_game(); }
});
param_enum(nisot::geodesic_movement, "solv_geodesic_movement", true)
-> editable({{"Lie group", "light, camera, and objects move in lines of constant direction, in the Lie group sense"}, {"geodesics", "light, camera, and objects always take the shortest path"}}, "straight lines", 'G')
-> set_reaction([] {
if(pmodel == mdLiePerspective && nisot::geodesic_movement) pmodel = hyperbolic ? mdPerspective : mdGeodesic;
if(among(pmodel, mdGeodesic, mdPerspective) && !nisot::geodesic_movement) pmodel = mdLiePerspective;
});
param_i(s2xe::qrings, "s2xe-rings");
param_f(rots::underlying_scale, "rots-underlying-scale");
param_b(vid.bubbles_special, "bubbles-special", 1);
param_b(vid.bubbles_threshold, "bubbles-threshold", 1);
param_b(vid.bubbles_all, "bubbles-all", 0);
#if CAP_SHMUP
multi::initConfig();
#endif
param_i(asonov::period_xy, "asonov:period_xy");
param_i(asonov::period_z, "asonov:period_z");
param_i(nilv::nilperiod[0], "nilperiod_x");
param_i(nilv::nilperiod[1], "nilperiod_y");
param_i(nilv::nilperiod[2], "nilperiod_z");
param_enum(neon_mode, "neon_mode", neon_mode)
->editable(
{{"OFF", ""}, {"standard", ""}, {"no boundary mode", ""}, {"neon mode II", ""}, {"illustration mode", ""}},
"neon mode", 'M'
);
param_enum(bow::weapon, "pc_class", bow::weapon)
-> editable({{"blade", "Standard Rogue weapon. Bump into a monster to hit. Most monsters attack you the same way."},
{"crossbow", "Hits all monsters in a straight line, but slow to reload. Press 'f' or click the crossbow icon to target."}},
"weapon selection", 'w')
-> set_need_confirm()
-> set_value_to = [] (bow::eWeapon wpn) { bool b = game_active; if(wpn != bow::weapon) stop_game(); bow::weapon = wpn;
peace::on = false; if(dual::state) dual::disable(); if(multi::players > 1 && !shmup::on) multi::players = 1;
if(b) start_game();
};
param_enum(bow::style, "bow_style", bow::style)
-> editable({{"bull line", "Can go in either direction on odd shapes. 3 turns to reload."},
{"geodesic", "Graph geodesic: any sequence of tiles is OK as long as there are no shortcuts. 4 turns to reload."},
{"geometric", "Approximations of geometric straight lines."}},
"crossbow straight line style", 'l')
-> set_need_confirm()
-> set_value_to = [] (bow::eCrossbowStyle s) { bool b = game_active; if(s != bow::style) stop_game(); bow::style = s; if(b) start_game(); };
param_b(bow::bump_to_shoot, "bump_to_shoot", true)->editable("bump to shoot", 'b');
param_enum(bow::mouse_fire_mode, "mouse_fire_mode", bow::mfmPriority)
->editable({{"explicit", "You need to click crossbow or be close to fire."},
{"priority", "Click on a faraway monster to fire if possible, or move if not."},
{"always", "Clicking on a faraway monster always means an attempt to fire."}},
"mouse auto-fire mode", 'm');
param_enum(vid.msgleft, "message_style", 2)
-> editable({{"centered", ""}, {"left-aligned", ""}, {"line-broken", ""}}, "message style", 'a');
param_enum(neon_nofill, "neon_nofill", neon_nofill);
param_b(noshadow, "noshadow");
param_b(bright, "bright");
param_b(cblind, "cblind");
param_i(berger_limit, "berger_limit");
param_enum(centering, "centering", centering);
param_f(camera_speed, parameter_names("camspd", "camera-speed"), 1);
param_f(camera_rot_speed, parameter_names("camrot", "camera-rot-speed"), 1);
param_f(third_person_rotation, "third_person_rotation", 0);
param_f(vid.stereo_param, "stereo_param", 0.9)
->editable(-1, 1, 0.9, "stereographic/Panini parameter", "1 for full stereographic/Panini projection. Lower values reduce the effect.\n\n"
"HyperRogue uses "
"a quick implementation, so parameter values too close to 1 may "
"be buggy (outside of raycasting); try e.g. 0.9 instead.", 'd')
->set_reaction(reset_all_shaders);
callhooks(hooks_configfile);
#if CAP_SHOT
param_f(levellines, "levellines", 0);
#endif
#if CAP_CONFIG
for(auto s: params) s.second->reset();
#endif
param_custom_int(sightrange_bonus, "sightrange_bonus", menuitem_sightrange_bonus, 'r');
param_custom_int(vid.use_smart_range, "sightrange_style", menuitem_sightrange_style, 's');
param_custom_int(gp::param.first, "Goldberg x", menuitem_change_variation, 0);
param_custom_int(gp::param.second, "Goldberg y", menuitem_change_variation, 0);
param_custom_int(variation, parameter_names("variation", "mode-variation"), menuitem_change_variation, 'v')
->help_text = "variation|dual|bitruncated";
param_custom_int(geometry, parameter_names("geometry", "mode-geometry"), menuitem_change_geometry, 0)
->help_text = "hyperbolic|spherical|Euclidean";
param_i(stamplen, "stamplen");
param_f(anims::period, "animperiod");
param_b(use_custom_land_list, "customland_use")->be_non_editable();
for(int i=0; i<landtypes; i++) {
custom_land_list[i] = true;
custom_land_treasure[i] = 100;
custom_land_difficulty[i] = 100;
custom_land_wandering[i] = 100;
param_b(custom_land_list[i], "customland" + its(i) + "i", true)->be_non_editable();
param_i(custom_land_treasure[i], "customland" + its(i) + "t", 100)->be_non_editable();
param_i(custom_land_difficulty[i], "customland" + its(i) + "d", 100)->be_non_editable();
param_i(custom_land_wandering[i], "customland" + its(i) + "w", 100)->be_non_editable();
}
}
EX bool inSpecialMode() {
return !ls::nice_walls() || ineligible_starting_land || !BITRUNCATED || peace::on ||
#if CAP_TOUR
tour::on ||
#endif
yendor::on || tactic::on || randomPatternsMode ||
geometry != gNormal || pmodel != mdDisk || pconf.alpha != 1 || pconf.scale != 1 ||
rug::rugged || vid.monmode != DEFAULT_MONMODE ||
vid.wallmode != DEFAULT_WALLMODE;
}
EX bool have_current_settings() {
int modecount = 0;
if(inv::on) modecount++;
if(shmup::on) modecount += 10;
#if CAP_TOUR
if(tour::on) modecount += 10;
#endif
if(!ls::nice_walls()) modecount += 10;
if(!BITRUNCATED) modecount += 10;
if(peace::on) modecount += 10;
if(yendor::on) modecount += 10;
if(tactic::on) modecount += 10;
if(randomPatternsMode) modecount += 10;
if(geometry != gNormal) modecount += 10;
if(modecount > 1)
return true;
return false;
}
EX bool have_current_graph_settings() {
if(pconf.xposition || pconf.yposition || pconf.alpha != 1 || pconf.scale != 1)
return true;
if(pmodel != mdDisk || vid.monmode != DEFAULT_MONMODE || vid.wallmode != DEFAULT_WALLMODE)
return true;
if(firstland != laIce || multi::players != 1 || rug::rugged)
return true;
return false;
}
EX void reset_graph_settings() {
pmodel = mdDisk; pconf.alpha = 1; pconf.scale = 1;
pconf.xposition = pconf.yposition = 0;
#if CAP_RUG
if(rug::rugged) rug::close();
#endif
vid.monmode = DEFAULT_MONMODE;
vid.wallmode = DEFAULT_WALLMODE;
}
EX void resetModes(char leave IS('c')) {
while(game_active || gamestack::pushed()) {
if(game_active) stop_game();
if(gamestack::pushed()) gamestack::pop();
}
if(shmup::on != (leave == rg::shmup)) stop_game_and_switch_mode(rg::shmup);
if(inv::on != (leave == rg::inv)) stop_game_and_switch_mode(rg::inv);
/* we do this twice to make sure that stop_game_and_switch_mode switches to the correct land_structure */
for(int i=0; i<2; i++) {
if(leave == rg::chaos && !ls::std_chaos()) stop_game_and_switch_mode(rg::chaos);
if(leave != rg::chaos && !ls::nice_walls()) stop_game_and_switch_mode(rg::chaos);
}
if((!!dual::state) != (leave == rg::dualmode)) stop_game_and_switch_mode(rg::dualmode);
if(peace::on != (leave == rg::peace)) stop_game_and_switch_mode(rg::peace);
#if CAP_TOUR
if(tour::on != (leave == rg::tour)) stop_game_and_switch_mode(rg::tour);
#endif
if(yendor::on != (leave == rg::yendor)) stop_game_and_switch_mode(rg::yendor);
if(tactic::on != (leave == rg::tactic)) stop_game_and_switch_mode(rg::tactic);
if(randomPatternsMode != (leave == rg::randpattern)) stop_game_and_switch_mode(rg::randpattern);
if(multi::players != 1) {
stop_game_and_switch_mode(); multi::players = 1;
}
if(firstland != laIce || specialland != laIce) {
stop_game();
firstland = laIce; specialland = laIce; stop_game_and_switch_mode();
}
set_geometry(gNormal);
set_variation(leave == rg::heptagons ? eVariation::pure : eVariation::bitruncated);
start_game();
}
#if CAP_CONFIG
EX void resetConfig() {
dynamicval<int> rx(vid.xres, 0);
dynamicval<int> ry(vid.yres, 0);
dynamicval<int> rf(vid.fsize, 0);
dynamicval<bool> rfs(vid.full, false);
for(auto s: params)
if(s.second->legacy_config_name.substr(0,5) != "mode-")
s.second->reset();
}
#endif
#if CAP_CONFIG
EX void saveConfig() {
DEBB(DF_INIT, ("save config\n"));
FILE *f = fopen(conffile, "wt");
if(!f) {
addMessage(s0 + "Could not open the config file: " + conffile);
return;
}
{
int pt_depth = 0, pt_camera = 0, pt_alpha = 0;
if(vid.tc_depth > vid.tc_camera) pt_depth++;
if(vid.tc_depth < vid.tc_camera) pt_camera++;
if(vid.tc_depth > vid.tc_alpha ) pt_depth++;
if(vid.tc_depth < vid.tc_alpha ) pt_alpha ++;
if(vid.tc_alpha > vid.tc_camera) pt_alpha++;
if(vid.tc_alpha < vid.tc_camera) pt_camera++;
vid.tc_alpha = pt_alpha;
vid.tc_camera = pt_camera;
vid.tc_depth = pt_depth;
}
for(auto s: params) if(s.second->dosave())
fprintf(f, "%s=%s\n", s.second->name.c_str(), s.second->save().c_str());
fclose(f);
#if !ISMOBILE
addMessage(s0 + "Configuration saved to: " + conffile);
#else
addMessage(s0 + "Configuration saved");
#endif
}
void readf(FILE *f, ld& x) {
double fl = x;
hr::ignore(fscanf(f, "%lf", &fl));
x = fl;
}
map<string, shared_ptr<parameter> > allconfigs;
EX void parseline(const string& str) {
if(str[0] == '#') return;
for(int i=0; i<isize(str); i++) if(str[i] == '=') {
string cname = str.substr(0, i);
if(!allconfigs.count(cname)) {
printf("Warning: unknown config variable: %s\n", str.c_str());
return;
}
auto sav = allconfigs[cname];
sav->load(str.substr(i+1));
return;
}
printf("Warning: config line without equality sign: %s\n", str.c_str());
}
EX void loadNewConfig(FILE *f) {
for(auto& c: params) allconfigs[c.second->name] = allconfigs[c.second->legacy_config_name] = c.second;
string rd;
while(true) {
int c = fgetc(f);
if(c == -1) break;
if(c == 10 || c == 13) {
if(rd != "") parseline(rd);
rd = "";
}
else rd += c;
}
allconfigs.clear();
}
EX void loadConfig() {
DEBB(DF_INIT, ("load config"));
vid.xres = 9999; vid.yres = 9999; vid.framelimit = 999;
FILE *f = fopen(conffile, "rt");
if(f) {
int err;
int fs;
err=fscanf(f, "%d%d%d%d", &vid.xres, &vid.yres, &fs, &vid.fsize);
if(err != 4)
loadNewConfig(f);
else {
vid.full = fs;
#if CAP_LEGACY
loadOldConfig(f);
#endif
}
fclose(f);
DEBB(DF_INIT, ("Loaded configuration: %s\n", conffile));
}
geom3::apply_always3();
polygonal::solve();
check_cgi();
cgi.require_basics();
}
#endif
EX void add_cells_drawn(char c IS('C')) {
dialog::addSelItem(XLAT("cells drawn"), (noclipped ? its(cells_drawn) + " (" + its(noclipped) + ")" : its(cells_drawn)) + " / " + its(vid.cells_drawn_limit), c);
dialog::add_action([] () {
dialog::editNumber(vid.cells_drawn_limit, 100, 1000000, log(10), 10000, XLAT("limit on cells drawn"),
XLAT("This limit exists to protect the engine from freezing when too many cells would be drawn according to the current options.")
);
dialog::scaleLog();
});
if(WDIM == 3 || vid.use_smart_range == 2) {
dialog::addSelItem(XLAT("limit generated cells per frame"), its(vid.cells_generated_limit), 'L');
dialog::add_action([] () {
dialog::editNumber(vid.cells_generated_limit, 1, 1000, log(10), 25, XLAT("limit generated cells per frame"),
XLAT("In the 3D mode, lowering this value may help if the game lags while exploring new areas.")
);
});
}
}
string solhelp() {
#if CAP_SOLV
return XLAT(
"Solv (aka Sol) is a 3D space where directions work in different ways. It is described by the following metric:\n"
"ds² = (eᶻdx)² + (e⁻ᶻdy)² + dz²\n\n"
"You are currently displaying Solv in the perspective projection based on native geodesics. You can control how "
"the fog effects depends on the geodesic distance, and how far object in X/Y/Z coordinates are rendered."
);
#else
return "";
#endif
}
EX void menuitem_sightrange_bonus(char c) {
dialog::addSelItem(XLAT("sight range bonus"), its(sightrange_bonus), c);
dialog::add_action([]{
dialog::editNumber(sightrange_bonus, -5, allowIncreasedSight() ? 3 : 0, 1, 0, XLAT("sight range"),
XLAT("Roughly 42% cells are on the edge of your sight range. Reducing "
"the sight range makes HyperRogue work faster, but also makes "
"the game effectively harder."));
dialog::get_di().reaction = doOvergenerate;
dialog::bound_low(1-getDistLimit());
dialog::bound_up(allowIncreasedSight() ? euclid ? 99 : gp::dist_2() * 5 : 0);
});
}
EX void edit_sightrange_3d(char key, bool fog) {
dialog::addSelItem(fog ? XLAT("3D sight range for the fog effect") : ("3D sight range"), fts(sightranges[geometry]), key);
dialog::add_action([] {
dialog::editNumber(sightranges[geometry], 0, TAU, 0.5, M_PI, XLAT("3D sight range"),
XLAT(
"Sight range for 3D geometries is specified in the absolute units. This value also affects the fog effect.\n\n"
"In spherical geometries, the sight range of 2π will let you see things behind you as if they were in front of you, "
"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"
"In hyperbolic geometries, the number of cells to render depends exponentially on the sight range. More cells to drawn "
"reduces the performance.\n\n"
"Sight range affects the gameplay, and monsters act iff they are visible. Monster generation takes this into account."
)
);
dialog::get_di().extra_options = [] { add_cells_drawn('C'); };
});
}
EX void edit_sightrange() {
cmode = sm::SIDE;
gamescreen();
dialog::init("sight range settings");
add_edit(vid.use_smart_range);
int wdim = WDIM;
#if CAP_RUG
USING_NATIVE_GEOMETRY_IN_RUG;
#endif
if(vid.use_smart_range) {
add_edit(wdim == 2 ? vid.smart_range_detail : vid.smart_range_detail_3);
if(GDIM == 3) edit_sightrange_3d('r', true);
}
else {
if(wdim == 2) {
add_edit(sightrange_bonus);
if(GDIM == 3) edit_sightrange_3d('r', true);
}
if(wdim == 3) edit_sightrange_3d('r', false);
}
#if CAP_SOLV
if(models::is_perspective(pmodel) && sol) {
dialog::addSelItem(XLAT("max difference in X/Y coordinates"), fts(sn::solrange_xy), 'x');
dialog::add_action([] {
dialog::editNumber(sn::solrange_xy, 0.01, 200, 0.1, 50, XLAT("max difference in X/Y coordinates"), solhelp()), dialog::scaleLog();
dialog::get_di().extra_options = [] { add_cells_drawn('C'); };
});
dialog::addSelItem(XLAT("max difference in Z coordinate"), fts(sn::solrange_z), 'z');
dialog::add_action([] {
dialog::editNumber(sn::solrange_z, 0, 20, 0.1, 6, XLAT("max difference in Z coordinates"), solhelp());
dialog::get_di().extra_options = [] { add_cells_drawn('C'); };
});
}
#endif
if(models::is_perspective(pmodel) && sl2) {
dialog::addSelItem(XLAT("max difference in X/Y coordinates"), fts(slr::range_xy), 'x');
dialog::add_action([] {
dialog::editNumber(slr::range_xy, 0, 10, 0.5, 4, XLAT("max difference in X/Y coordinates"), "");
});
dialog::addSelItem(XLAT("max difference in Z coordinate"), fts(slr::range_z), 'x');
dialog::add_action([] {
dialog::editNumber(slr::range_xy, 0, 10, 0.5, 4, XLAT("max difference in Z coordinate"), "");
});
dialog::addSelItem(XLAT("shader_iterations"), its(slr::shader_iterations), 'z');
dialog::add_action([] {
dialog::editNumber(slr::shader_iterations, 0, 50, 1, 10, "", "");
});
}
if(vid.use_smart_range && wdim == 2) {
dialog::addBoolItem_action(XLAT("area-based range"), vid.smart_area_based, 'a');
}
if(vid.use_smart_range == 0 && allowChangeRange() && wdim == 2) {
dialog::addSelItem(XLAT("generation range bonus"), its(genrange_bonus), 'o');
dialog::add_action([] () { genrange_bonus = sightrange_bonus; doOvergenerate(); });
dialog::addSelItem(XLAT("game range bonus"), its(gamerange_bonus), 's');
dialog::add_action([] () { gamerange_bonus = sightrange_bonus; doOvergenerate(); });
}
if(wdim == 3 && !vid.use_smart_range) {
dialog::addBoolItem_action(XLAT("sloppy range checking"), vid.sloppy_3d, 's');
}
if(GDIM == 3 && !vid.use_smart_range) {
dialog::addSelItem(XLAT("limit generation"), fts(extra_generation_distance), 'e');
dialog::add_action([] {
dialog::editNumber(extra_generation_distance, 0, 999, 0.5, 999, XLAT("limit generation"),
"Cells over this distance will not be generated, but they will be drawn if they are already generated and in the sight range."
);
});
}
add_cells_drawn('c');
dialog::display();
}
EX void menuitem_sightrange_style(char c IS('c')) {
dialog::addSelItem(XLAT("draw range based on"),
vid.use_smart_range == 0 ? XLAT("distance") :
vid.use_smart_range == 1 ? XLAT("size (no gen)") :
XLAT("size"),
c
);
dialog::add_action_push([] {
dialog::init(XLAT("draw range based on"));
dialog::addBoolItem(XLAT("draw range based on distance"), vid.use_smart_range == 0, 'd');
dialog::add_action([] () { vid.use_smart_range = 0; popScreen(); edit_sightrange(); });
if(WDIM == 2 && allowIncreasedSight()) {
dialog::addBoolItem(XLAT("draw based on size in the projection (no generation)"), vid.use_smart_range == 1, 'n');
dialog::add_action([] () { vid.use_smart_range = 1; popScreen(); edit_sightrange(); });
}
if(allowChangeRange() && allowIncreasedSight()) {
dialog::addBoolItem(XLAT("draw based on size in the projection (generation)"), vid.use_smart_range == 2, 'g');
dialog::add_action([] () { vid.use_smart_range = 2; popScreen(); edit_sightrange(); });
}
if(!allowChangeRange() || !allowIncreasedSight()) {
dialog::addItem(XLAT("enable the cheat mode for additional options"), 'X');
dialog::add_action(enable_cheat);
}
dialog::display();
});
}
EX void menuitem_sightrange(char c IS('c')) {
#if CAP_SOLV
if(pmodel == mdGeodesic && sol)
dialog::addSelItem(XLAT("sight range settings"), fts(sn::solrange_xy) + "x" + fts(sn::solrange_z), c);
else
#endif
if(vid.use_smart_range)
dialog::addSelItem(XLAT("sight range settings"), fts(WDIM == 3 ? vid.smart_range_detail_3 : vid.smart_range_detail) + " px", c);
else if(WDIM == 3)
dialog::addSelItem(XLAT("sight range settings"), fts(sightranges[geometry]) + "au", c);
else
dialog::addSelItem(XLAT("sight range settings"), hr::format("%+d", sightrange_bonus), c);
dialog::add_action_push(edit_sightrange);
}
EX void sets_sfx_volume() {
#if CAP_AUDIO
dialog::get_di().dialogflags = sm::NOSCR;
#if ISANDROID
dialog::get_di().reaction = [] () {
settingsChanged = true;
};
#endif
dialog::bound_low(0);
dialog::bound_up(MIX_MAX_VOLUME);
#endif
}
EX void sets_music_volume() {
#if CAP_AUDIO
dialog::get_di().dialogflags = sm::NOSCR;
dialog::get_di().reaction = [] () {
#if CAP_SDLAUDIO
Mix_VolumeMusic(musicvolume);
#endif
#if ISANDROID
settingsChanged = true;
#endif
};
dialog::bound_low(0);
dialog::bound_up(MIX_MAX_VOLUME);
#if CAP_SDLAUDIO
dialog::get_di().extra_options = [] {
dialog::addBoolItem_action(XLAT("play music when out of focus"), music_out_of_focus, 'A');
};
#endif
#endif
}
EX void showSpecialEffects() {
cmode = vid.xres > vid.yres * 1.4 ? sm::SIDE : sm::MAYDARK;
gamescreen();
dialog::init(XLAT("extra graphical effects"));
dialog::addBoolItem_action(XLAT("particles on attack"), (vid.particles), 'p');
dialog::addBoolItem_action(XLAT("floating bubbles: special"), vid.bubbles_special, 's');
dialog::addBoolItem_action(XLAT("floating bubbles: treasure thresholds"), vid.bubbles_threshold, 't');
dialog::addBoolItem_action(XLAT("floating bubbles: all treasures"), vid.bubbles_all, 'a');
dialog::addBoolItem_action(XLAT("background particle effects"), (vid.backeffects), 'b');
dialog::addBreak(50);
dialog::addBack();
dialog::display();
}
EX void show_vector_settings() {
cmode = vid.xres > vid.yres * 1.4 ? sm::SIDE : sm::MAYDARK;
gamescreen();
dialog::init(XLAT("vector settings"));
dialog::addSelItem(XLAT("line width"), fts(vid.linewidth), 'w');
dialog::add_action([] {
dialog::editNumber(vid.linewidth, 0, 10, 0.1, 1, XLAT("line width"),
vid.usingGL ? "" : XLAT("Line width setting is only taken into account in OpenGL."));
});
dialog::addSelItem(XLAT("line quality"), its(vid.linequality), 'l');
dialog::add_action([] {
dialog::editNumber(vid.linequality, -3, 5, 1, 1, XLAT("line quality"),
XLAT("Higher numbers make the curved lines smoother, but reduce the performance."));
});
dialog::addBoolItem("perfect width", perfect_linewidth == 2, 'p');
if(perfect_linewidth == 1)
dialog::lastItem().value = XLAT("shots only");
dialog::add_action([] { perfect_linewidth = (1 + perfect_linewidth) % 3; });
dialog::addBoolItem_action("finer lines at the boundary", vid.fineline, 'O');
if(vid.fineline) {
dialog::addSelItem("variable width", fts(precise_width), 'm');
dialog::add_action([] () {
dialog::editNumber(precise_width, 0, 2, 0.1, 0.5,
XLAT("variable width"), XLAT("lines longer than this value will be split into shorter lines, with width computed separately for each of them.")
);
});
}
else dialog::addBreak(100);
add_edit(neon_mode);
dialog::addBreak(100);
dialog::addInfo(XLAT("hint: press Alt while testing modes"));
dialog::addBreak(100);
dialog::addBoolItem_action(XLAT("disable shadows"), noshadow, 'f');
dialog::addBoolItem_action(XLAT("bright mode"), bright, 'g');
dialog::addBoolItem_action(XLAT("colorblind simulation"), cblind, 'h');
dialog::addBoolItem_action(XLAT("no fill in neon mode"), neon_nofill, 'n');
dialog::addBreak(50);
dialog::addBack();
dialog::display();
}
EX void showGraphConfig() {
cmode = vid.xres > vid.yres * 1.4 ? sm::SIDE : sm::MAYDARK;
gamescreen();
dialog::init(XLAT("graphics configuration"));
#if !ISIOS && !ISWEB
add_edit(vid.want_fullscreen);
#if !ISANDROID && !ISFAKEMOBILE
if(vid.want_fullscreen) {
add_edit(vid.change_fullscr);
if(vid.change_fullscr)
add_edit(vid.fullscreen_x), add_edit(vid.fullscreen_y);
else
dialog::addBreak(200);
}
else {
add_edit(vid.relative_window_size);
if(vid.relative_window_size)
add_edit(vid.window_rel_x), add_edit(vid.window_rel_y);
else
add_edit(vid.window_x), add_edit(vid.window_y);
}
#endif
#endif
#if CAP_GLORNOT
add_edit(vid.wantGL);
#endif
#if !ISIOS && !ISANDROID && !ISFAKEMOBILE
if(!vid.usingGL) {
dialog::addBoolItem(XLAT("anti-aliasing"), vid.want_antialias & AA_NOGL, 'O');
dialog::add_action([] {
if(!vid.usingGL)
vid.want_antialias ^= AA_NOGL | AA_FONT;
});
}
else {
dialog::addSelItem(XLAT("anti-aliasing"),
(vid.want_antialias & AA_POLY) ? "polygons" :
(vid.want_antialias & AA_LINES) ? "lines" :
(vid.want_antialias & AA_MULTI) ? "multisampling" :
"NO", 'O');
dialog::add_action([] {
if(vid.want_antialias & AA_MULTI)
vid.want_antialias ^= AA_MULTI;
else if(vid.want_antialias & AA_POLY)
vid.want_antialias ^= AA_POLY | AA_LINES | AA_MULTI;
else if(vid.want_antialias & AA_LINES)
vid.want_antialias |= AA_POLY;
else
vid.want_antialias |= AA_LINES;
});
}
#endif
#if !ISIOS && !ISANDROID && !ISFAKEMOBILE
if(vid.usingGL) {
if(vrhr::active())
dialog::addInfo(XLAT("(vsync disabled in VR)"));
else
add_edit(vid.want_vsync);
}
else
dialog::addBreak(100);
#endif
if(need_to_apply_screen_settings()) {
dialog::addItem(XLAT("apply changes"), 'A');
dialog::add_action(apply_screen_settings);
dialog::addBreak(100);
}
else
dialog::addBreak(200);
add_edit(vid.relative_font);
if(vid.relative_font)
add_edit(vid.fontscale);
else
add_edit(vid.abs_fsize);
add_edit(mapfontscale);
dialog::addSelItem(XLAT("vector settings"), XLAT("width") + " " + fts(vid.linewidth), 'w');
dialog::add_action_push(show_vector_settings);
#if CAP_FRAMELIMIT
dialog::addSelItem(XLAT("framerate limit"), its(vid.framelimit), 'l');
if(getcstat == 'l')
mouseovers = XLAT("Reduce the framerate limit to conserve CPU energy");
#endif
dialog::addSelItem(XLAT("scrolling speed"), fts(vid.sspeed), 'a');
dialog::addSelItem(XLAT("camera movement speed"), fts(camera_speed), 'c');
dialog::add_action([] {
dialog::editNumber(camera_speed, -10, 10, 0.1, 1, XLAT("camera movement speed"),
"This affects:\n\nin 2D: scrolling with arrow keys and Wheel Up\n\nin 3D: camera movement with Home/End."
);
});
dialog::addSelItem(XLAT("camera rotation speed"), fts(camera_rot_speed), 'r');
dialog::add_action([] {
dialog::editNumber(camera_rot_speed, -10, 10, 0.1, 1, XLAT("camera rotation speed"),
"This affects view rotation with Page Up/Down, and in 3D, camera rotation with arrow keys or mouse."
);
});
dialog::addSelItem(XLAT("movement animation speed"), fts(vid.mspeed), 'm');
dialog::addSelItem(XLAT("idle animation speed"), fts(vid.ispeed), 'i');
dialog::add_action([] {
dialog::editNumber(vid.ispeed, 0, 4, 0.1, 1,
XLAT("idle animation speed"),
"0 = disable\n\nThis affects non-movement animations such as orb effects, item rotation, and more."
);
});
dialog::addBoolItem_action(XLAT("flashing effects"), (vid.flasheffects), 'h');
if(getcstat == 'h')
mouseovers = XLAT("Disable if you are photosensitive. Replaces flashing effects such as Orb of Storms lightning with slow, adjustable animations.");
dialog::addItem(XLAT("extra graphical effects"), 'u');
dialog::addBreak(50);
dialog::addBack();
dialog::display();
keyhandler = [] (int sym, int uni) {
dialog::handleNavigation(sym, uni);
char xuni = uni | 96;
if((uni >= 32 && uni < 64) || uni == 'L' || uni == 'C') xuni = uni;
if(xuni == 'u') pushScreen(showSpecialEffects);
else if(xuni == 'a') dialog::editNumber(vid.sspeed, -5, 5, 1, 0,
XLAT("scrolling speed"),
XLAT("+5 = center instantly, -5 = do not center the map")
+ "\n\n" +
XLAT("press Space or Home to center on the PC"));
else if(xuni == 'm') dialog::editNumber(vid.mspeed, -5, 5, 1, 0,
XLAT("movement animation speed"),
XLAT("+5 = move instantly"));
#if CAP_FRAMELIMIT
else if(xuni == 'l') {
dialog::editNumber(vid.framelimit, 5, 300, 10, 300, XLAT("framerate limit"), "");
dialog::bound_low(5);
}
#endif
else if(xuni =='p')
vid.backeffects = !vid.backeffects;
else if(doexiton(sym, uni)) popScreen();
};
}
EX void edit_whatever(char type, int index) {
if(type == 'f') {
dialog::editNumber(whatever[index], -10, 10, 1, 0, XLAT("whatever"),
"f:" + its(index));
}
else {
dialog::editNumber(whateveri[index], -10, 10, 1, 0, XLAT("whatever"),
"i:" + its(index));
}
dialog::get_di().extra_options = [type, index] {
dialog::addItem(XLAT("integer"), 'X');
dialog::add_action( [index] { popScreen(); edit_whatever('i', index); });
dialog::addItem(XLAT("float"), 'Y');
dialog::add_action( [index] { popScreen(); edit_whatever('f', index); });
for(int x=0; x<8; x++) {
dialog::addSelItem(its(x), type == 'i' ? its(whateveri[x]) : fts(whatever[x]), 'A' + x);
dialog::add_action([type,x] { popScreen(); edit_whatever(type, x); });
}
};
}
EX void configureOther() {
cmode = sm::SIDE | sm::MAYDARK;
gamescreen();
dialog::init(XLAT("other settings"));
#if ISSTEAM
dialog::addBoolItem(XLAT("send scores to Steam leaderboards"), (vid.steamscore&1), 'x');
dialog::add_action([] {vid.steamscore = vid.steamscore^1; });
#endif
dialog::addBoolItem_action(XLAT("skip the start menu"), vid.skipstart, 'm');
dialog::addItem(XLAT("memory configuration"), 'y');
dialog::add_action_push(show_memory_menu);
// dialog::addBoolItem_action(XLAT("forget faraway cells"), memory_saving_mode, 'y');
#if CAP_AUDIO
add_edit(musicvolume);
add_edit(effvolume);
#endif
menuitem_sightrange('r');
add_edit(vid.faraway_highlight);
add_edit(vid.faraway_highlight_color);
#ifdef WHATEVER
dialog::addSelItem(XLAT("whatever"), fts(whatever[0]), 'j');
dialog::add_action([] { edit_whatever('f', 0); });
#endif
add_edit(savefile_selection);
dialog::addBreak(50);
dialog::addBack();
dialog::display();
}
EX void configureInterface() {
cmode = sm::SIDE | sm::MAYDARK;
gamescreen();
dialog::init(XLAT("interface"));
#if CAP_TRANS
dialog::addSelItem(XLAT("language"), XLAT("EN"), 'l');
dialog::add_action_push(selectLanguageScreen);
#endif
dialog::addSelItem(XLAT("player character"), numplayers() > 1 ? "" : csname(vid.cs), 'g');
dialog::add_action_push(showCustomizeChar);
if(getcstat == 'g') mouseovers = XLAT("Affects looks and grammar");
dialog::addSelItem(XLAT("message flash time"), its(vid.flashtime), 't');
dialog::add_action([] {
dialog::editNumber(vid.flashtime, 0, 64, 1, 8, XLAT("message flash time"),
XLAT("How long should the messages stay on the screen."));
dialog::bound_low(0);
});
dialog::addSelItem(XLAT("limit messages shown"), its(vid.msglimit), 'z');
dialog::add_action([] {
dialog::editNumber(vid.msglimit, 0, 64, 1, 5, XLAT("limit messages shown"),
XLAT("Maximum number of messages on screen."));
dialog::bound_low(0);
});
add_edit(nohelp);
add_edit(vid.msgleft);
add_edit(glyphsortorder);
add_edit(vid.graphglyph);
add_edit(less_in_landscape);
add_edit(less_in_portrait);
add_edit(display_yasc_codes);
add_edit(vid.orbmode);
dialog::addSelItem(XLAT("draw crosshair"), crosshair_size > 0 ? fts(crosshair_size) : ONOFF(false), 'x');
dialog::add_action([] () {
dialog::editNumber(crosshair_size, 0, 100, 1, 10, XLAT("crosshair size"), XLAT(
"Display a targetting reticle in the center of the screen. Might be useful when exploring 3D modes, "
"as it precisely shows the direction we are going. However, the option is available in all modes."
));
dialog::bound_low(0);
dialog::get_di().extra_options = [] {
draw_crosshair();
dialog::addColorItem(XLAT("crosshair color"), crosshair_color, 'X');
dialog::add_action([] {
dialog::openColorDialog(crosshair_color);
dialog::get_di().extra_options = draw_crosshair;
});
};
});
add_edit(show_turns);
add_edit(menu_darkening);
add_edit(centered_menus);
add_edit(startanims::enabled);
dialog::addBreak(50);
dialog::addBack();
dialog::display();
}
#if CAP_SDLJOY
EX void showJoyConfig() {
cmode = sm::SIDE | sm::MAYDARK;
gamescreen();
dialog::init(XLAT("joystick configuration"));
dialog::addSelItem(XLAT("first joystick position (movement)"), its(joyx)+","+its(joyy), 0);
dialog::addSelItem(XLAT("second joystick position (panning)"), its(panjoyx)+","+its(panjoyy), 0);
dialog::addSelItem(XLAT("joystick mode"), autojoy ? XLAT("automatic") : XLAT("manual"), 'p');
if(getcstat == 'p') {
if(autojoy)
mouseovers = XLAT("joystick mode: automatic (release the joystick to move)");
if(!autojoy)
mouseovers = XLAT("joystick mode: manual (press a button to move)");
}
dialog::addSelItem(XLAT("first joystick: movement threshold"), its(vid.joyvalue), 'a');
dialog::addSelItem(XLAT("first joystick: execute movement threshold"), its(vid.joyvalue2), 'b');
dialog::addSelItem(XLAT("second joystick: pan threshold"), its(vid.joypanthreshold), 'c');
dialog::addSelItem(XLAT("second joystick: panning speed"), fts(vid.joypanspeed * 1000), 'd');
dialog::addSelItem(XLAT("smoothen"), its(vid.joysmooth) + " ms", 'e');
dialog::addBreak(50);
dialog::addBack();
dialog::display();
keyhandler = [] (int sym, int uni) {
dialog::handleNavigation(sym, uni);
if(uni == 'p') autojoy = !autojoy;
else if(uni == 'a') {
dialog::editNumber(vid.joyvalue, 0, 32768, 100, 4800, XLAT("first joystick: movement threshold"), "");
dialog::bound_low(0);
}
else if(uni == 'b') {
dialog::editNumber(vid.joyvalue2, 0, 32768, 100, 5600, XLAT("first joystick: execute movement threshold"), "");
dialog::bound_low(0);
}
else if(uni == 'c') {
dialog::editNumber(vid.joypanthreshold, 0, 32768, 100, 2500, XLAT("second joystick: pan threshold"), "");
dialog::bound_low(0);
}
else if(uni == 'd')
dialog::editNumber(vid.joypanspeed, 0, 1e-2, 1e-5, 1e-4, XLAT("second joystick: panning speed"), "");
else if(uni == 'e')
dialog::editNumber(vid.joypanspeed, 0, 2000, 20, 200, XLAT("smoothen"), "large values help if the joystick is imprecise");
else if(doexiton(sym, uni)) popScreen();
};
}
#endif
EX void projectionDialog() {
vid.tc_alpha = ticks;
dialog::editNumber(vpconf.alpha, -5, 5, .1, 1,
XLAT("projection distance"),
XLAT("HyperRogue uses the Minkowski hyperboloid model internally. "
"Klein and Poincaré models can be obtained by perspective, "
"and the Gans model is obtained by orthogonal projection. "
"See also the conformal mode (in the special modes menu) "
"for more models."));
dialog::get_di().extra_options = [] () {
dialog::addBreak(100);
if(GDIM == 2) dialog::addHelp(XLAT(
"If we are viewing an equidistant g absolute units below a plane, "
"from a point c absolute units above the plane, this corresponds "
"to viewing a Minkowski hyperboloid from a point "
"tanh(g)/tanh(c) units below the center. This in turn corresponds to "
"the Poincaré model for g=c, and Klein-Beltrami model for g=0."));
dialog::addSelItem(sphere ? "stereographic" : "Poincaré model", "1", 'P');
dialog::add_action([] () { *dialog::get_ne().editwhat = 1; vpconf.scale = 1; dialog::get_ne().s = "1"; });
dialog::addSelItem(sphere ? "gnomonic" : "Klein model", "0", 'K');
dialog::add_action([] () { *dialog::get_ne().editwhat = 0; vpconf.scale = 1; dialog::get_ne().s = "0"; });
if(hyperbolic) {
dialog::addSelItem("inverted Poincaré model", "-1", 'I');
dialog::add_action([] () { *dialog::get_ne().editwhat = -1; vpconf.scale = 1; dialog::get_ne().s = "-1"; });
}
dialog::addItem(sphere ? "orthographic" : "Gans model", 'O');
dialog::add_action([] () { vpconf.alpha = vpconf.scale = 999; dialog::get_ne().reset_str(); });
dialog::addItem(sphere ? "towards orthographic" : "towards Gans model", 'T');
dialog::add_action([] () { double d = 1.1; vpconf.alpha *= d; vpconf.scale *= d; dialog::get_ne().reset_str(); });
};
}
EX void menuitem_projection_distance(char key) {
dialog::addSelItem(XLAT("projection distance"), fts(vpconf.alpha) + " (" + current_proj_name() + ")", key);
dialog::add_action(projectionDialog);
}
EX void explain_detail() {
dialog::addHelp(XLAT(
"Objects at distance less than %1 absolute units "
"from the center will be displayed with high "
"detail, and at distance at least %2 with low detail.",
fts(vid.highdetail), fts(vid.middetail)
));
}
EX ld max_fov_angle() {
auto p = get_stereo_param();
if(p >= 1 || p <= -1) return 360;
return acos(-p) * 2 / degree;
}
EX void edit_fov_screen() {
dialog::editNumber(vid.fov, 1, max_fov_angle(), 1, 90, "field of view",
XLAT(
"Horizontal field of view, in angles. "
"This affects the Hypersian Rug mode (even when stereo is OFF) "
"and non-disk models.") + "\n\n" +
XLAT(
"Must be less than %1°. Panini projection can be used to get higher values.",
fts(max_fov_angle())
)
);
dialog::bound_low(1e-8);
dialog::bound_up(max_fov_angle() - 0.01);
dialog::get_di().extra_options = [] {
auto ptr = dynamic_cast<dialog::number_dialog*> (screens.back().target_base());
if(ptr && ptr->vmax != max_fov_angle()) { popScreen(); edit_fov_screen(); return; }
add_edit(vid.stereo_mode, 'M');
if(among(vid.stereo_mode, sPanini, sStereographic)) {
add_edit(vid.stereo_param, 'P');
}
};
}
EX void add_edit_fov(char key IS('f')) {
string sfov = fts(vid.fov) + "°";
if(get_stereo_param()) {
sfov += " / " + fts(max_fov_angle()) + "°";
}
dialog::addSelItem(XLAT("field of view"), sfov, key);
dialog::add_action(edit_fov_screen);
}
bool supported_ods() {
if(!CAP_ODS) return false;
return rug::rugged || (hyperbolic && GDIM == 3);
}
EX void showStereo() {
cmode = sm::SIDE | sm::MAYDARK;
gamescreen();
dialog::init(XLAT("stereo vision config"));
add_edit(vid.stereo_mode);
dialog::addSelItem(XLAT("pupillary distance"), fts(vid.ipd), 'e');
switch(vid.stereo_mode) {
case sAnaglyph:
dialog::addSelItem(XLAT("distance between images"), fts(vid.anaglyph_eyewidth), 'd');
break;
case sLR:
dialog::addSelItem(XLAT("distance between images"), fts(vid.lr_eyewidth), 'd');
break;
case sPanini: case sStereographic:
add_edit(vid.stereo_param);
break;
default:
dialog::addBreak(100);
break;
}
dialog::addSelItem(XLAT("desaturate colors"), its(vid.desaturate)+"%", 'c');
dialog::add_action([] {
dialog::editNumber(vid.desaturate, 0, 100, 10, 0, XLAT("desaturate colors"),
XLAT("Make the game colors less saturated. This is useful in the anaglyph mode.")
);
});
add_edit_fov('f');
dialog::addBack();
dialog::display();
keyhandler = [] (int sym, int uni) {
dialog::handleNavigation(sym, uni);
string help3 = XLAT(
"This allows you to view the world of HyperRogue in three dimensions. "
"Best used with the Hypersian Rug mode. When used in the disk model, "
"this lets you look at the Minkowski hyperboloid (which means the "
"depth of terrain features is actually reversed). It also works with non-disk models, "
"from the conformal menu."
) + " " + XLAT(
"Currently, red-cyan anaglyph glasses and mobile VR googles are supported."
) + "\n\n";
if(uni == 'm') {
vid.stereo_mode = eStereo((1 + vid.stereo_mode) % 4);
if(vid.stereo_mode == sODS && !supported_ods()) vid.stereo_mode = sOFF;
}
else if(uni == 'e')
dialog::editNumber(vid.ipd, -10, 10, 0.01, 0, XLAT("pupillary distance"),
help3 +
XLAT("The distance between your eyes in the represented 3D object. This is given in absolute units.")
), dialog::scaleSinh100();
else if(uni == 'd' && vid.stereo_mode == sAnaglyph)
dialog::editNumber(vid.anaglyph_eyewidth, -1, 1, 0.01, 0, XLAT("distance between images"),
help3 +
XLAT("The distance between your eyes. 1 is the width of the screen."));
else if(uni == 'd' && vid.stereo_mode == sLR)
dialog::editNumber(vid.lr_eyewidth, -1, 1, 0.01, 0, XLAT("distance between images"),
help3 +
XLAT("The distance between your eyes. 1 is the width of the screen."));
else if(doexiton(sym, uni)) popScreen();
};
}
EX void add_edit_wall_quality(char c) {
dialog::addSelItem(XLAT("wall quality"), its(vid.texture_step), c);
dialog::add_action([] {
dialog::editNumber(vid.texture_step, 1, 16, 1, 1, XLAT("wall quality"),
XLAT(
"Controls the number of triangles used for wall surfaces. "
"Higher numbers reduce the performance. "
"This has a strong effect when the walls are curved indeed "
"(floors in 2D geometries, honeycombs based on horospheres, and projections other than native perspective), "
"but otherwise, usually it can be set to 1 without significant adverse effects other "
"than slightly incorrect texturing."
)
);
dialog::bound_low(1);
dialog::bound_up(128);
dialog::get_di().reaction = [] {
#if MAXMDIM >= 4
if(floor_textures) {
delete floor_textures;
floor_textures = NULL;
}
#endif
};
});
}
EX void edit_levellines(char c) {
if(levellines)
dialog::addSelItem(XLAT("level lines"), fts(levellines), c);
else
dialog::addBoolItem(XLAT("level lines"), false, c);
dialog::add_action([] {
dialog::editNumber(levellines, 0, 100, 0.5, 0, XLAT("level lines"),
XLAT(
"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. "
"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.")
);
dialog::get_di().reaction = ray::reset_raycaster;
dialog::get_di().extra_options = [] {
dialog::addBoolItem(XLAT("disable textures"), disable_texture, 'T');
dialog::add_action([] { ray::reset_raycaster(); disable_texture = !disable_texture; });
dialog::addItem(XLAT("disable level lines"), 'D');
dialog::add_action([] { ray::reset_raycaster(); levellines = 0; popScreen(); });
};
dialog::bound_low(0);
});
}
EX geom3::eSpatialEmbedding shown_spatial_embedding() {
if(GDIM == 2) return geom3::seNone;
return geom3::spatial_embedding;
}
EX bool in_tpp() { return pmodel == mdDisk && !models::camera_straight; }
EX void display_embedded_errors() {
using namespace geom3;
auto eucs = [] {
dialog::addItem(XLAT("set square tiling"), 'A'); dialog::add_action([] { dialog::do_if_confirmed( [] { stop_game(); set_geometry(gEuclidSquare); set_variation(eVariation::pure); start_game(); });});
dialog::addItem(XLAT("set hex tiling"), 'B'); dialog::add_action([] { dialog::do_if_confirmed( [] { stop_game(); set_geometry(gEuclid); set_variation(eVariation::pure); start_game(); });});
};
if(among(spatial_embedding, seNil, seProductH, seProductS, seCliffordTorus, seSL2) && (!among(geometry, gEuclid, gEuclidSquare) || !PURE)) {
dialog::addInfo(XLAT("error: currently works only in PURE Euclidean regular square or hex tiling"), 0xC00000);
eucs();
return;
}
if(among(spatial_embedding, seSol, seSolN, seNIH) && (!bt::in() && !among(geometry, gEuclid, gEuclidSquare))) {
dialog::addInfo(XLAT("error: currently works only in pure Euclidean, or binary tiling and similar"), 0xC00000);
eucs();
dialog::addItem(XLAT("set binary tiling variant"), 'C'); dialog::add_action([] { dialog::do_if_confirmed( [] { stop_game(); set_geometry(gBinaryTiling); geom3::switch_fpp(); geom3::switch_fpp(); start_game(); }); });
dialog::addItem(XLAT("set ternary tiling"), 'D'); dialog::add_action([] { dialog::do_if_confirmed( [] { stop_game(); set_geometry(gTernary); geom3::switch_fpp(); geom3::switch_fpp(); start_game(); }); });
dialog::addItem(XLAT("set binary tiling"), 'E'); dialog::add_action([] { dialog::do_if_confirmed( [] { stop_game(); set_geometry(gBinary4); geom3::switch_fpp(); geom3::switch_fpp(); start_game(); }); });
return;
}
if(shmup::on && cgi.emb->no_spin()) {
dialog::addInfo(XLAT("error: this embedding does not work in shmup"), 0xC00000);
return;
}
if(meuclid && spatial_embedding == seCliffordTorus) {
if(!clifford_torus_valid()) {
dialog::addInfo(XLAT("error: this method works only in rectangular torus"), 0xC00000);
dialog::addItem(XLAT("set 20x20 torus"), 'A'); dialog::add_action([] { dialog::do_if_confirmed( [] { stop_game();
auto& T0 = euc::eu_input.user_axes;
T0[0][0] = T0[1][1] = 20;
T0[0][1] = 0;
T0[1][0] = geometry == gEuclid ? 10 : 0;
euc::eu_input.twisted = false;
euc::build_torus3();
geom3::apply_settings_full(); start_game(); }); });
return;
}
}
if(meuclid && spatial_embedding == seProductS) {
#if CAP_RUG
rug::clifford_torus ct;
bool err = sqhypot_d(2, ct.xh) < 1e-3 && sqhypot_d(2, ct.yh) < 1e-3;
if(err) {
dialog::addInfo(XLAT("error: this method works only in cylinder"), 0xC00000);
dialog::addItem(XLAT("set cylinder"), 'A'); dialog::add_action([] { dialog::do_if_confirmed( [] { stop_game();
auto& T0 = euc::eu_input.user_axes;
T0[0][0] = 10;
T0[0][1] = T0[1][0] = T0[1][1] = 0;
euc::eu_input.twisted = false;
euc::build_torus3();
geom3::apply_settings_full(); start_game(); }); });
return;
}
#else
dialog::addInfo(XLAT("error: not supported"), 0xC00000);
#endif
}
if(msphere && !among(spatial_embedding, seNone, seDefault, seLowerCurvature, seMuchLowerCurvature, seProduct, seProductS)) {
dialog::addInfo(XLAT("error: this method does not work in spherical geometry"), 0xC00000);
return;
}
if(mhyperbolic && !among(spatial_embedding, seNone, seDefault, seLowerCurvature, seMuchLowerCurvature, seProduct, seProductH, seSol, seSolN, seNIH)) {
dialog::addInfo(XLAT("error: this method does not work in hyperbolic geometry"), 0xC00000);
return;
}
}
EX void show_spatial_embedding() {
cmode = sm::SIDE | sm::MAYDARK | sm::CENTER | sm::PANNING | sm::SHOWCURSOR;
gamescreen();
dialog::init(XLAT("3D styles"));
auto emb = shown_spatial_embedding();
add_edit(geom3::auto_configure);
dialog::addBreak(100);
auto &seo = geom3::spatial_embedding_options;
for(int i=0; i<isize(seo); i++) {
auto se = geom3::eSpatialEmbedding(i);
dialog::addBoolItem(XLAT(seo[i].first), emb == i, 'a' + i);
dialog::add_action([se] { invoke_embed(se); });
string s = why_wrong(se);
if(s != "")
dialog::items.back().value = (emb == i ? ONOFF(true) : XLAT("needs")) + s;
}
dialog::addBreak(100);
dialog::addHelp(XLAT(seo[emb].second));
display_embedded_errors();
dialog::addBreak(100);
if(geom3::auto_configure) {
if(emb == geom3::seNone) {
dialog::addBoolItem(XLAT("third-person perspective"), in_tpp(), 'T');
dialog::add_action(geom3::switch_tpp);
#if CAP_RUG
dialog::addBoolItem(XLAT("Hypersian Rug"), rug::rugged, 'u');
dialog::add_action([] {
if(in_tpp()) geom3::switch_tpp();
if(!rug::rugged) {
pconf.alpha = 1, pconf.scale = 1; if(!rug::rugged) rug::init();
}
else rug::close();
});
#endif
dialog::addBreak(100);
}
else {
if(geom3::supports_flat()) add_edit(geom3::flat_embedding);
else dialog::addBreak(100);
if(geom3::supports_invert()) add_edit(geom3::inverted_embedding);
else dialog::addBreak(100);
}
}
dialog::addSelItem(XLAT("reset view"), embedded_plane && isize(current_display->radarpoints) == 0 ? XLAT("(fix errors)") : !cells_drawn ? XLAT("(fix errors)") : "", ' ');
dialog::add_action([] {
if(rug::rug_control())
rug::reset_view();
else
fullcenter();
});
dialog::addBreak(100);
dialog::addBack();
dialog::display();
}
EX void show3D_height_details() {
cmode = sm::SIDE | sm::MAYDARK;
gamescreen();
dialog::init(XLAT("3D detailed settings"));
add_edit(vid.wall_height);
dialog::addBreak(50);
add_edit(vid.rock_wall_ratio);
add_edit(vid.human_wall_ratio);
add_edit(vid.lake_top);
add_edit(vid.lake_shallow);
add_edit(vid.lake_bottom);
dialog::addBreak(50);
if(embedded_plane) {
add_edit(auto_remove_roofs);
add_edit(vid.wall_height2);
add_edit(vid.wall_height3);
add_edit(draw_sky);
add_edit(vid.lowsky_height);
add_edit(vid.sky_height);
add_edit(vid.star_height);
add_edit(vid.infdeep_height);
add_edit(vid.sun_size);
add_edit(vid.star_size);
#if MAXMDIM >= 4
add_edit(star_prob);
add_edit(vid.height_limits);
if(euclid && msphere) add_edit(use_euclidean_infinity);
#endif
dialog::addBreak(100);
dialog::addHelp(lalign(0, "absolute altitudes:\n\n"
"depth ", cgi.INFDEEP,
" water ", tie(cgi.BOTTOM, cgi.SHALLOW, cgi.LAKE),
" floor ", cgi.FLOOR,
" eye ", vid.eye,
" walls ", tie(cgi.WALL, cgi.HIGH, cgi.HIGH2),
" star ", cgi.STAR,
" sky ", cgi.SKY,
"\n\n",
"recommended: ", cgi.emb->height_limit(-1), " to ", cgi.emb->height_limit(1)
));
}
else dialog::addInfo(XLAT("more options in 3D engine"));
dialog::addBreak(100);
dialog::addBack();
dialog::display();
}
EX void show3D() {
cmode = sm::SIDE | sm::MAYDARK;
gamescreen();
dialog::init(XLAT("3D configuration"));
#if MAXMDIM >=4
if(WDIM == 2) {
dialog::addSelItem(XLAT("3D style"), XLAT(geom3::spatial_embedding_options[shown_spatial_embedding()].first), 'E');
dialog::add_action_push(show_spatial_embedding);
display_embedded_errors();
dialog::addBreak(50);
}
#endif
if(vid.use_smart_range == 0 && GDIM == 2) {
add_edit(vid.highdetail);
add_edit(vid.middetail);
dialog::addBreak(50);
}
if(WDIM == 2) {
if(cgi.emb->is_euc_scalable()) {
add_edit(geom3::euclid_embed_scale);
add_edit(geom3::euclid_embed_scale_y);
add_edit(geom3::euclid_embed_rotate);
}
add_edit(embedded_shift_method_choice);
add_edit(vid.camera);
if(GDIM == 3)
add_edit(vid.eye);
add_edit(vid.depth);
if(GDIM == 2) {
dialog::addSelItem(XLAT("Projection at the ground level"), fts(pconf.alpha), 'p');
dialog::add_action(projectionDialog);
}
else if(!in_perspective())
dialog::addSelItem(XLAT("projection distance"), fts(pconf.alpha), 'p');
dialog::addBreak(50);
add_edit(vid.wall_height);
dialog::addSelItem(XLAT("3D detailed settings"), "", 'D');
dialog::add_action_push(show3D_height_details);
add_edit(vid.creature_scale);
}
else {
add_edit(vid.creature_scale);
add_edit(vid.height_width);
menuitem_sightrange('s');
}
dialog::addBreak(50);
add_edit(vid.yshift);
if(GDIM == 3) {
dialog::addSelItem(XLAT("mouse aiming sensitivity"), fts(mouseaim_sensitivity), 'a');
dialog::add_action([] () {
dialog::editNumber(mouseaim_sensitivity, -1, 1, 0.002, 0.01, XLAT("mouse aiming sensitivity"), "set to 0 to disable");
});
}
if(true) add_edit(vpconf.cam());
if(GDIM == 2) {
dialog::addSelItem(XLAT("fixed facing"), vid.fixed_facing ? fts(vid.fixed_facing_dir) : XLAT("OFF"), 'f');
dialog::add_action([] () { vid.fixed_facing = !vid.fixed_facing;
if(vid.fixed_facing) {
dialog::editNumber(vid.fixed_facing_dir, 0, 360, 15, 90, "", "");
dialog::get_di().dialogflags |= sm::CENTER;
}
});
}
if(mproduct || embedded_plane)
dialog::addBoolItem_action(XLAT("fixed Y/Z rotation"), vid.fixed_yz, 'Z');
if(WDIM == 2 && GDIM == 3) {
add_edit(vid.pseudohedral);
// add_edit(vid.depth_bonus);
}
if(true) {
dialog::addBreak(50);
dialog::addSelItem(XLAT("projection"), current_proj_name(), 'M');
dialog::add_action_push(models::model_menu);
}
#if CAP_RUG
if(GDIM == 2) {
dialog::addItem(XLAT("configure Hypersian Rug"), 'u');
dialog::add_action_push(rug::show);
}
#endif
#if MAXMDIM >= 4
if(GDIM == 3) add_edit_fov('f');
if(GDIM == 3) {
dialog::addSelItem(XLAT("radar size"), fts(vid.radarsize), 'r');
dialog::add_action([] () {
dialog::editNumber(vid.radarsize, 0, 360, 15, 90, "", XLAT("set to 0 to disable"));
dialog::get_di().extra_options = [] () { draw_radar(true); };
});
}
if(WDIM == 3 && sphere && stretch::factor) {
dialog::addItem(XLAT("Berger sphere limit"), berger_limit);
dialog::add_action([] () {
dialog::editNumber(berger_limit, 0, 10, 1, 2, "",
XLAT("Primitive-based rendering of Berger sphere is currently very slow and low quality. "
"Here you can choose how many images to draw.")
);
});
}
#if CAP_RAY
if(GDIM == 3) {
dialog::addItem(XLAT("configure raycasting"), 'A');
dialog::add_action_push(ray::configure);
}
#endif
edit_levellines('L');
if(WDIM == 3 || (GDIM == 3 && meuclid)) {
dialog::addSelItem(XLAT("radar range"), fts(vid.radarrange), 'R');
dialog::add_action([] () {
dialog::editNumber(vid.radarrange, 0, 10, 0.5, 2, "", XLAT(""));
dialog::get_di().extra_options = [] () { draw_radar(true); };
});
}
if(GDIM == 3) add_edit_wall_quality('W');
#endif
#if CAP_RUG
if(rug::rugged) {
dialog::addBoolItem_action(XLAT("3D monsters/walls on the surface"), rug::spatial_rug, 'S');
}
#endif
if(0);
#if CAP_RUG
else if(rug::rugged && !rug::spatial_rug)
dialog::addBreak(100);
#endif
else if(GDIM == 2 && non_spatial_model())
dialog::addInfo(XLAT("no 3D effects available in this projection"), 0xC00000);
else if(GDIM == 2 && !spatial_graphics)
dialog::addInfo(XLAT("set 3D monsters or walls in basic config first"));
else if(geom3::invalid != "")
dialog::addInfo(XLAT("error: ")+geom3::invalid, 0xC00000);
else
dialog::addInfo(XLAT("parameters set correctly"));
dialog::addBreak(50);
dialog::addItem(XLAT("stereo vision config"), 'e');
dialog::add_action_push(showStereo);
#if CAP_VR
dialog::addBoolItem(XLAT("VR settings"), vrhr::active(), 'v');
dialog::add_action_push(vrhr::show_vr_settings);
#endif
dialog::addBack();
dialog::display();
}
#if HDR
namespace ccolor { struct data; }
#endif
EX shared_ptr<custom_parameter> param_ccolor(ccolor::data*& val, const parameter_names& n) {
shared_ptr<custom_parameter> u ( new custom_parameter );
u->setup(n);
u->custom_value = [&val] { for(int i=0; i<isize(ccolor::all); i++) if(ccolor::all[i] == val) return i; return -1; };
u->last_value = u->custom_value();
u->custom_affect = [&val] (void *v) { return &val == v; };
u->custom_load = [&val] (const string& s) { for(auto c: ccolor::all) if(c->name == s) val = c; };
u->custom_save = [&val] { return val->name; };
u->custom_clone = [u] (struct local_parameter_set& lps, void *value) { auto val = (ccolor::data**) value; return param_ccolor(*val, lps.mod(&*u)); };
return u;
}
EX int config3 = addHook(hooks_configfile, 100, [] {
param_f(vid.eye, "eyelevel", 0)
->editable(-5, 5, .1, "eye level", "", 'E')
->set_extra([] {
dialog::get_di().dialogflags |= sm::CENTER;
vid.tc_camera = ticks;
dialog::addHelp(XLAT("In the FPP mode, the camera will be set at this altitude (before applying shifts)."));
dialog::addBoolItem(XLAT("auto-adjust to eyes on the player model"), vid.auto_eye, 'O');
dialog::get_di().reaction = [] { vid.auto_eye = false; };
dialog::add_action([] () {
vid.auto_eye = !vid.auto_eye;
geom3::do_auto_eye();
});
});
param_b(vid.auto_eye, "auto-eyelevel", false);
param_b(nomenukey, "nomenukey");
param_b(showstartmenu, "showstartmenu");
param_b(draw_centerover, "draw_centerover");
param_enum(nohelp, "help_messages", 0)
-> editable({
{"all", "all context help/welcome messages"},
{"none", "no context help/welcome messages"},
{"automatic", "I know I can press F1 for help"},
}, "context help", 'H');
param_f(vid.creature_scale, parameter_names("creature_scale", "3d-creaturescale"), 1)
->editable(0, 1, .1, "Creature scale", "", 'C')
->set_extra([] { dialog::addInfo(XLAT("changing this during shmup is counted as cheating")); })
->set_reaction([] { if(shmup::on) cheater++; });
param_f(vid.height_width, parameter_names("heiwi", "3d-heightwidth"), 1.5)
->editable(0, 1, .1, "Height to width", "", 'h');
param_f(vid.yshift, parameter_names("yshift", "Y shift"), 0)
->editable(0, 1, .1, "Y shift", "Don't center on the player character.", 'y')
->set_extra([] {
if(WDIM == 3 && pmodel == mdPerspective)
dialog::addBoolItem_action(XLAT("reduce if walls on the way"), vid.use_wall_radar, 'R');
});
param_b(vid.use_wall_radar, "wallradar", true);
param_b(vid.fixed_facing, "fixed facing", 0);
param_f(vid.fixed_facing_dir, "fixed facing dir", 90);
param_b(vid.fixed_yz, "fixed YZ", true);
param_b(frustum_culling, "frustum_culling");
param_b(numerical_minefield, "numerical_minefield")
-> editable("toggle numerical display", 'n');
param_b(mine_hollow, "mine_hollow")
-> editable("hollow mine symbols", 'h');
param_b(mine_markers, "mine_markers")
-> editable("markers on possible mines", 'm');
param_i(mine_opacity, "minefield opacity", 255)
->editable(0, 255, 51, "opacity of undiscovered minefield", "3D modes only\n\n0 = invisible, 255 = fully opaque", 'o');
param_enum(mine_zero_display, "minefield_zero", 1)
->editable({{"OFF", "never display zeros"}, {"3D", "only in 3D modes"}, {"ON", "always display zeros"}}, "display zeros in minefield", 'z');
param_b(dont_display_minecount, "dont_display_minecount");
#if MAXMDIM >= 4
param_enum(draw_sky, "draw_sky", skyAutomatic)
-> editable({{"NO", "do not draw sky"}, {"automatic", ""}, {"skybox", "works only in Euclidean"}, {"always", "might be glitched in some settings"}}, "sky rendering", 's');
param_b(use_euclidean_infinity, "use_euclidean_infinity", true)
-> editable("infinite sky", 'i');
#endif
param_f(linepatterns::parallel_count, "parallel_count")
->editable(0, 24, 1, "number of parallels drawn", "", 'n');
param_f(linepatterns::parallel_max, "parallel_max")
->editable(0, TAU, 15*degree, "last parallel drawn", "", 'n');
param_f(linepatterns::mp_ori, "mp_ori")
->editable(0, TAU, 15*degree, "parallel/meridian orientation", "", 'n');
param_f(linepatterns::meridian_max, "meridian_max");
param_f(linepatterns::meridian_count, "meridian_count");
param_f(linepatterns::meridian_length, "meridian_length");
param_f(linepatterns::meridian_prec, "meridian_prec");
param_f(linepatterns::meridian_prec2, "meridian_prec2");
param_f(linepatterns::dual_length, "dual_length");
param_matrix(linepatterns::dual_angle.v2, "dual_angle", 2);
param_matrix(linepatterns::dual_angle.v3, "dual_angle3", 3);
param_f(twopoint_xscale, "twopoint_xscale");
param_i(twopoint_xshape, "twopoint_xshape");
param_f(twopoint_xwidth, "twopoint_xwidth");
param_f(periodwidth, "periodwidth", 1);
param_b(draw_plain_floors, "draw_plain_floors", false)
->editable("draw plain floors in 3D", 'p');
param_i(default_flooralpha, "floor_alpha")
->editable(0, 255, 15, "floor alpha", "255 = opaque", 'a');
param_f(vid.depth_bonus, "depth_bonus", 0)
->editable(-5, 5, .1, "depth bonus in pseudohedral", "", 'b');
param_enum(vid.pseudohedral, "pseudohedral", phOFF)
->editable(
{{"OFF", "the tiles are curved"},
{"inscribed", "the tiles are inscribed"},
{"circumscribed", "the tiles are circumscribed"}},
"make the tiles flat", 'p');
param_f(vid.depth, parameter_names("depth", "3D depth"), 1)
->editable(0, 5, .1, "Ground level below the plane", "", 'd')
->set_extra([] {
vid.tc_depth = ticks;
help = XLAT(
"Ground level is actually an equidistant surface, "
"%1 absolute units below the plane P. "
"Theoretically, this value affects the world -- "
"for example, eagles could fly %2 times faster by "
"flying above the ground level, on the plane P -- "
"but the actual game mechanics are not affected. ", fts(vid.depth), fts(cosh(vid.depth)));
if(GDIM == 2)
help += XLAT(
"(Distances reported by the vector graphics editor "
"are not about points on the ground level, but "
"about the matching points on the plane P -- "
"divide them by the factor above to get actual "
"distances.)"
);
if(GDIM == 3 && pmodel == mdPerspective && !euclid) {
ld current_camera_level = hdist0(tC0(current_display->radar_transform));
help += "\n\n";
if(abs(current_camera_level) < 1e-6)
help += XLAT(
"The camera is currently exactly on the plane P. "
"The horizon is seen as a straight line."
);
else help += XLAT(
"The camera is currently %1 units above the plane P. "
"This makes you see the floor level as in general perspective projection "
"with parameter %2.", fts(current_camera_level), fts(tan_auto(vid.depth) / tan_auto(current_camera_level)));
}
dialog::addHelp(help);
})
->set_reaction([] {
bool b = vid.tc_alpha < vid.tc_camera;
if(vid.tc_alpha >= vid.tc_depth) vid.tc_alpha = vid.depth - 1;
if(vid.tc_camera >= vid.tc_depth) vid.tc_camera = vid.depth - 1;
if(vid.tc_alpha == vid.tc_camera) (b ? vid.tc_alpha : vid.tc_camera)--;
geom3::apply_settings_light();
});
param_f(vid.camera, parameter_names("camera", "3D camera level"), 1)
->editable(0, 5, .1, "", "", 'c')
->modif([] (float_parameter* x) { x->menu_item_name = (GDIM == 2 ? "Camera level above the plane" : "Z shift"); })
->set_extra([] {
vid.tc_camera = ticks;
if(GDIM == 2)
dialog::addHelp(XLAT(
"Camera is placed %1 absolute units above a plane P in a three-dimensional "
"world. Ground level is actually an equidistant surface, %2 absolute units "
"below the plane P. The plane P (as well as the ground level or any "
"other equidistant surface below it) is viewed at an angle of %3 "
"(the tangent of the angle between the point in "
"the center of your vision and a faraway location is 1/cosh(c) = %4).",
fts(vid.camera),
fts(vid.depth),
fts(atan(1/cosh(vid.camera))*2/degree),
fts(1/cosh(vid.camera))));
if(GDIM == 3)
dialog::addHelp(XLAT("Look from behind."));
if(GDIM == 3 && pmodel == mdPerspective)
dialog::addBoolItem_action(XLAT("reduce if walls on the way"), vid.use_wall_radar, 'R');
});
param_f(vid.wall_height, parameter_names("wall_height", "3D wall height"), .3)
->editable(0, 1, .1, "Height of walls", "", 'w')
->set_extra([] () {
dialog::addHelp(GDIM == 3 ? "" : XLAT(
"The height of walls, in absolute units. For the current values of g and c, "
"wall height of %1 absolute units corresponds to projection value of %2.",
fts(geom3::actual_wall_height()), fts(geom3::factor_to_projection(cgi.WALL))));
dialog::addBoolItem(XLAT("auto-adjust in Goldberg grids"), vid.gp_autoscale_heights, 'O');
dialog::add_action([] () {
vid.gp_autoscale_heights = !vid.gp_autoscale_heights;
});
})
->set_reaction(geom3::apply_settings_light);
param_f(vid.rock_wall_ratio, parameter_names("rock_wall_ratio", "3D rock-wall ratio"), .9)
->editable(0, 1, .1, "Rock-III to wall ratio", "", 'r')
->set_extra([] { dialog::addHelp(XLAT(
"The ratio of Rock III to walls is %1, so Rock III are %2 absolute units high. "
"Length of paths on the Rock III level is %3 of the corresponding length on the "
"ground level.",
fts(vid.rock_wall_ratio), fts(vid.wall_height * vid.rock_wall_ratio),
fts(cosh(vid.depth - vid.wall_height * vid.rock_wall_ratio) / cosh(vid.depth))));
});
param_f(vid.human_wall_ratio, parameter_names("human_wall_ratio", "3D human-wall ratio"), .7)
->editable(0, 1, .1, "Human to wall ratio", "", 'h')
->set_extra([] { dialog::addHelp(XLAT(
"Humans are %1 "
"absolute units high. Your head travels %2 times the distance travelled by your "
"feet.",
fts(vid.wall_height * vid.human_wall_ratio),
fts(cosh(vid.depth - vid.wall_height * vid.human_wall_ratio) / cosh(vid.depth)))
);
});
string unitwarn =
"The unit this value is given in is wall height. "
"Note that, in exponentially expanding spaces, too high values could cause rendering issues. So "
"if you want infinity, values of 5 or similar should be used -- there is no visible difference "
"from infinity and glitches are avoided.";
param_f(vid.lake_top, parameter_names("lake_top", "3D lake top"), .25 / 0.3)
->editable(0, 1, .1, "Level of water surface", unitwarn, 'l');
param_f(vid.lake_shallow, parameter_names("lake_shallow", "3D lake shallow"), .4 / 0.3)
->editable(0, 1, .1, "Level of shallow water", unitwarn, 's');
param_f(vid.lake_bottom, parameter_names("lake_bottom", "3D lake bottom"), .9 / 0.3)
->editable(0, 1, .1, "Level of water bottom", unitwarn, 'k');
param_f(vid.wall_height2, "wall_height2", 2)
->editable(0, 5, .1, "ratio of high walls to normal walls", unitwarn, '2');
param_f(vid.wall_height3, "wall_height3", 3)
->editable(0, 5, .1, "ratio of very high walls to normal walls", unitwarn, '3');
param_f(vid.lowsky_height, "lowsky_height", 2)
->editable(0, 5, .1, "sky fake height", "Sky is rendered at the distance computed based on "
"the sky height, which might be beyond the range visible in fog. To prevent this, "
"the intensity of the fog effect depends on the value here rather than the actual distance. "
"Stars are affected similarly.", '4');
#if MAXMDIM >= 4
param_fd(vid.sky_height, "sky_height")
->set_hint([] { return geom3::to_wh(cgi.SKY); })
->editable(0, 10, .1, "altitude of the sky", unitwarn, '5')
->set_reaction(delete_sky);
#endif
param_fd(vid.star_height, "star_height")
->set_hint([] { return geom3::to_wh(cgi.STAR); })
->editable(0, 10, .1, "altitude of the stars", unitwarn, '6');
param_fd(vid.infdeep_height, "infdeep_height")
->set_hint([] { return geom3::to_wh(cgi.INFDEEP); })
->editable(0, 10, .1, "infinite depth", unitwarn, '7');
param_f(vid.sun_size, "sun_size", 8)
->editable(0, 10, .1, "sun size (relative to item sizes)", "", '8');
param_f(vid.star_size, "star_size", 0.75)
->editable(0, 10, .1, "night star size (relative to item sizes)", "", '9');
#if MAXMDIM >= 4
param_f(star_prob, "star_prob", 0.3)
->editable(0, 1, .01, "star probability", "probability of star per tile", '*');
#endif
param_b(vid.height_limits, "height_limits", true)
->editable("prevent exceeding recommended altitudes", 'l');
param_b(auto_remove_roofs, "auto_remove_roofs", true)
->editable("do not render higher levels if camera too high", 'r');
param_i(vid.tc_depth, "3D TC depth", 1);
param_i(vid.tc_camera, "3D TC camera", 2);
param_i(vid.tc_alpha, "3D TC alpha", 3);
param_f(vid.highdetail, parameter_names("highdetail", "3D highdetail"), 8)
->editable(0, 5, .5, "High detail range", "", 'n')
->set_extra(explain_detail)
->set_reaction([] {
if(vid.highdetail > vid.middetail) vid.middetail = vid.highdetail;
});
param_f(vid.middetail, parameter_names("middetail", "3D middetail"), 8)
->editable(0, 5, .5, "Mid detail range", "", 'm')
->set_extra(explain_detail)
->set_reaction([] {
if(vid.highdetail > vid.middetail) vid.highdetail = vid.middetail;
});
param_i(debug_tiles, "debug_tiles")->editable(0, 2, 1,
"display tile debug values",
"Display cell type IDs, as well as vertex and edge identifiers.\n\n"
"Setting 1 uses the internal shape IDs, while setting 2 in tes files uses "
"the original IDs in case if extra tile types were added to "
"separate mirror images or different football types.", 'd');
param_b(debug_voronoi, "debug_voronoi")->editable(
"display Voronoi tie debug values", 'd');
param_i(horodisk_from, "horodisk_from", -2)->editable(-10, 10, 1,
"land size in horodisk mode",
"Set this to -2 to get perfect horodisks. Smaller values yield less dense horodisks, and "
"larger values might produce horodisks with errors or crashing into each other.", 'H');
param_i(randomwalk_size, "randomwalk_size", 10)->editable(2, 100, 1,
"land size in randomwalk mode",
"The average size of a land in randomwalk mode.", 'R')
->set_reaction([] { if(game_active) { stop_game(); start_game(); } });
param_i(landscape_div, "landscape_div")->editable(1, 100, 1,
"land size in landscape structure",
"Each cell gets three coordinates, each of which change smoothly, using the same method as used for the generation of landscapes e.g. in Dragon Chasms. "
"Then, we find a cell of the bitruncated cubic honeycomb at these cordinates, and this cell determines which land it is. The bigger the value, the larger the lands.", 'R')
->set_sets([] { dialog::bound_low(1); })
->set_reaction([] { if(game_active) { stop_game(); start_game(); } });
param_i(curse_percentage, "curse_percentage")->editable(0, 100, 1,
"curse percentage",
"The percentage of towers in Cursed Walls mode to be manned by Canyon Hags", 'R')
->set_reaction([] { if(game_active) { stop_game(); start_game(); } });
param_f(global_boundary_ratio, "global_boundary_ratio")
->editable(0, 5, 0.1, "Width of cell boundaries",
"How wide should the cell boundaries be.", '0');
param_b(vid.gp_autoscale_heights, "3D Goldberg autoscaling", true);
param_str(scorefile, "savefile");
param_b(savefile_selection, "savefile_selection")
-> editable("select the score/save file on startup", 's')
-> set_reaction([] {
if(savefile_selection)
addMessage(XLAT("Save the config and restart to select another score/save file."));
else if(scorefile == "")
addMessage(XLAT("Save the config to always play without recording your progress."));
else
addMessage(XLAT("Save the config to always use %1.", scorefile));
});
param_ccolor(ccolor::which, "pattern");
});
EX void switchcolor(unsigned int& c, unsigned int* cs) {
dialog::openColorDialog(c, cs);
}
double cc_footphase;
int lmousex, lmousey;
EX void showCustomizeChar() {
cc_footphase += hypot(mousex - lmousex, mousey - lmousey);
lmousex = mousex; lmousey = mousey;
cmode = sm::SIDE | sm::MAYDARK;
gamescreen();
dialog::init(XLAT("Customize character"));
if(shmup::on || multi::players) multi::cpid = multi::cpid_edit % multi::players;
charstyle& cs = getcs();
dialog::addSelItem(XLAT("character"), csname(cs), 'g');
dialog::addColorItem(XLAT("skin color"), cs.skincolor, 's');
dialog::addColorItem(XLAT("eye color"), cs.eyecolor, 'e');
dialog::addColorItem(XLAT("weapon color"), cs.swordcolor, 'w');
dialog::addColorItem(XLAT("hair color"), cs.haircolor, 'h');
if(bow::crossbow_mode()) {
dialog::addColorItem(XLAT("bow color"), cs.bowcolor, 'b');
dialog::addColorItem(XLAT("bowstring color"), cs.bowcolor2, 'c');
}
if(cs.charid >= 1) dialog::addColorItem(XLAT("dress color"), cs.dresscolor, 'd');
else dialog::addBreak(100);
if(cs.charid == 3) dialog::addColorItem(XLAT("dress color II"), cs.dresscolor2, 'f');
else dialog::addBreak(100);
dialog::addColorItem(XLAT("movement color"), cs.uicolor, 'u');
if(!shmup::on && multi::players == 1) dialog::addSelItem(XLAT("save whom"), XLAT1(minf[moPrincess].name), 'p');
if(numplayers() > 1) dialog::addSelItem(XLAT("player"), its(multi::cpid+1), 'a');
dialog::addBoolItem(XLAT("left-handed"), cs.lefthanded, 'l');
dialog::addBreak(50);
dialog::addBack();
dialog::display();
int firsty = dialog::items[0].position / 2;
int scale = firsty - 2 * vid.fsize;
flat_model_enabler fme;
initquickqueue();
transmatrix V = atscreenpos(vid.xres/2, firsty, scale);
double alpha = atan2(mousex - vid.xres/2, mousey - firsty) - 90._deg;
V = V * spin(alpha);
drawMonsterType(moPlayer, NULL, shiftless(V), 0, cc_footphase / scale, NOCOLOR);
quickqueue();
keyhandler = [] (int sym, int uni) {
dialog::handleNavigation(sym, uni);
if(shmup::on || multi::players) multi::cpid = multi::cpid_edit % multi::players;
charstyle& cs = getcs();
bool cat = cs.charid >= 4;
if(uni == 'a') { multi::cpid_edit++; multi::cpid_edit %= 60; }
else if(uni == 'g') {
cs.charid++;
if(cs.charid == 2 && !princess::everSaved && !autocheat) cs.charid = 4;
cs.charid %= 10;
}
else if(uni == 'p') vid.samegender = !vid.samegender;
else if(uni == 's') switchcolor(cs.skincolor, cat ? haircolors : skincolors);
else if(uni == 'h') switchcolor(cs.haircolor, haircolors);
else if(uni == 'w') switchcolor(cs.swordcolor, swordcolors);
else if(uni == 'd') switchcolor(cs.dresscolor, cat ? haircolors : dresscolors);
else if(uni == 'f') switchcolor(cs.dresscolor2, dresscolors2);
else if(uni == 'u') switchcolor(cs.uicolor, eyecolors);
else if(uni == 'e') switchcolor(cs.eyecolor, eyecolors);
else if(uni == 'l') cs.lefthanded = !cs.lefthanded;
else if(uni == 'b') switchcolor(cs.bowcolor, swordcolors);
else if(uni == 'c') switchcolor(cs.bowcolor2, eyecolors);
else if(doexiton(sym, uni)) popScreen();
};
}
EX void refresh_canvas() {
manual_celllister cl;
cl.add(cwt.at);
int at = 0;
while(at < isize(cl.lst)) {
cell *c2 = cl.lst[at];
c2->landparam = ccolor::generateCanvas(c2);
at++;
forCellEx(c3, c2) cl.add(c3);
}
}
EX color_t addalpha(color_t c) { return (c << 8) | 0xFF; }
EX void edit_color_table(colortable& ct, const reaction_t& r IS(reaction_t()), bool has_bit IS(false)) {
cmode = sm::SIDE;
gamescreen();
dialog::init(XLAT("colors & aura"));
for(int i=0; i<isize(ct); i++) {
dialog::addColorItem(its(i), addalpha(ct[i]), 'a'+i);
if(WDIM == 3 && has_bit && !(ct[i] & 0x1000000)) dialog::lastItem().value = XLAT("(no wall)");
dialog::add_action([i, &ct, r, has_bit] () {
if(WDIM == 3 && has_bit) {
ct[i] ^= 0x1000000;
if(!(ct[i] & 0x1000000)) return;
}
dialog::openColorDialog(ct[i]);
if(r) dialog::get_di().reaction = r;
dialog::colorAlpha = false;
dialog::get_di().dialogflags |= sm::SIDE;
});
}
dialog::addItem("add a color", 'A');
dialog::add_action([&ct, r] {
ct.push_back(rand() & 0x1FFFFFF);
if(r) r();
});
if(isize(ct) > 2) {
dialog::addItem("delete a color", 'D');
dialog::add_action([&ct, r] {
ct.pop_back();
if(r) r();
});
}
dialog::addBack();
dialog::display();
}
EX void show_color_dialog() {
cmode = sm::SIDE | sm::DIALOG_STRICT_X;
getcstat = '-';
gamescreen();
dialog::init(XLAT("colors & aura"));
dialog::addColorItem(XLAT("background"), addalpha(backcolor), 'b');
dialog::add_action([] () { dialog::openColorDialog(backcolor); dialog::colorAlpha = false; dialog::get_di().dialogflags |= sm::SIDE; });
if(WDIM == 2 && GDIM == 3 && hyperbolic)
dialog::addBoolItem_action(XLAT("cool fog effect"), context_fog, 'B');
dialog::addColorItem(XLAT("foreground"), addalpha(forecolor), 'f');
dialog::add_action([] () { dialog::openColorDialog(forecolor); dialog::colorAlpha = false; dialog::get_di().dialogflags |= sm::SIDE; });
dialog::addColorItem(XLAT("borders"), addalpha(bordcolor), 'o');
dialog::add_action([] () { dialog::openColorDialog(bordcolor); dialog::colorAlpha = false; dialog::get_di().dialogflags |= sm::SIDE; });
dialog::addColorItem(XLAT("projection boundary"), ringcolor, 'r');
dialog::add_action([] () { dialog::openColorDialog(ringcolor); dialog::get_di().dialogflags |= sm::SIDE; });
dialog::addSelItem(XLAT("boundary width multiplier"), fts(vid.multiplier_ring), 'R');
dialog::add_action([] () { dialog::editNumber(vid.multiplier_ring, 0, 10, 1, 1, XLAT("boundary width multiplier"), ""); });
dialog::addColorItem(XLAT("projection background"), modelcolor, 'c');
dialog::add_action([] () { dialog::openColorDialog(modelcolor); dialog::get_di().dialogflags |= sm::SIDE; });
dialog::addColorItem(XLAT("standard grid color"), stdgridcolor, 'g');
dialog::add_action([] () { vid.grid = true; dialog::openColorDialog(stdgridcolor); dialog::get_di().dialogflags |= sm::SIDE; });
dialog::addSelItem(XLAT("grid width multiplier"), fts(vid.multiplier_grid), 'G');
dialog::add_action([] () { dialog::editNumber(vid.multiplier_grid, 0, 10, 1, 1, XLAT("grid width multiplier"), ""); });
dialog::addSelItem(XLAT("brightness behind the sphere"), fts(backbrightness), 'i');
dialog::add_action([] () { dialog::editNumber(backbrightness, 0, 1, .01, 0.25, XLAT("brightness behind the sphere"),
XLAT("In the orthogonal projection, objects on the other side of the sphere are drawn darker.")); dialog::bound_low(0); });
dialog::addColorItem(XLAT("projection period"), periodcolor, 'p');
dialog::add_action([] () { dialog::openColorDialog(periodcolor); dialog::get_di().dialogflags |= sm::SIDE; });
dialog::addColorItem(XLAT("dialogs"), addalpha(dialog::dialogcolor), 'd');
dialog::add_action([] () { dialog::openColorDialog(dialog::dialogcolor); dialog::colorAlpha = false; dialog::get_di().dialogflags |= sm::SIDE; });
dialog::addBreak(50);
if(specialland == laCanvas && ccolor::which->ctab.size()) {
dialog::addItem(XLAT("pattern colors"), 'P');
dialog::add_action_push([] { edit_color_table(ccolor::which->ctab, refresh_canvas, true); });
if(ccolor::which == &ccolor::shape_mirror) {
dialog::addItem(XLAT("unreversed colors"), 'U');
dialog::add_action_push([] { edit_color_table(ccolor::shape.ctab, refresh_canvas, true); });
}
}
if(cwt.at->land == laMinefield) {
dialog::addItem(XLAT("minefield colors"), 'm');
dialog::add_action_push([] { edit_color_table(minecolors); });
}
if(viewdists) {
dialog::addItem(XLAT("distance colors"), 'd');
dialog::add_action_push([] () {edit_color_table(distcolors); });
}
#if CAP_CRYSTAL
if(cryst && cheater) {
dialog::addItem(XLAT("crystal coordinate colors"), 'C');
dialog::add_action([] () { crystal::view_coordinates = true; pushScreen([] () { edit_color_table(crystal::coordcolors); });});
}
#endif
if(cwt.at->land == laTortoise) {
dialog::addBoolItem_action(XLAT("Galápagos shading"), tortoise::shading_enabled, 'T');
}
dialog::addInfo(XLAT("colors of some game objects can be edited by clicking them."));
dialog::addBreak(50);
dialog::addSelItem(XLAT("aura brightness"), its(vid.aurastr), 'a');
dialog::add_action([] () { dialog::editNumber(vid.aurastr, 0, 256, 10, 128, XLAT("aura brightness"), ""); dialog::bound_low(0); });
dialog::addSelItem(XLAT("aura smoothening factor"), its(vid.aurasmoothen), 's');
dialog::add_action([] () { dialog::editNumber(vid.aurasmoothen, 1, 180, 1, 5, XLAT("aura smoothening factor"), ""); dialog::bound_low(1); });
dialog::addBreak(50);
dialog::addBack();
dialog::display();
keyhandler = [] (int sym, int uni) {
if(uni == '-') {
cell *c = mouseover;
if(!c) return;
else if(c == cwt.at) {
pushScreen(showCustomizeChar);
return;
}
else if(c->monst)
dialog::openColorDialog(minf[c->monst].color);
else if(c->item)
dialog::openColorDialog(iinf[c->item].color);
else if(c->wall)
dialog::openColorDialog(winf[c->wall == waMineMine ? waMineUnknown : c->wall].color);
#if CAP_COMPLEX2
else if(c->land == laBrownian)
dialog::openColorDialog(brownian::get_color_edit(c->landparam));
#endif
else
dialog::openColorDialog(floorcolors[c->land]);
dialog::colorAlpha = false;
dialog::get_di().dialogflags |= sm::SIDE;
return;
}
else dialog::handleNavigation(sym, uni);
if(doexiton(sym, uni)) popScreen();
};
}
#if CAP_CONFIG
EX void resetConfigMenu() {
dialog::init(XLAT("reset all configuration"));
dialog::addInfo("Are you sure?");
dialog::addItem("yes, and delete the config file", 'd');
dialog::addItem("yes", 'y');
dialog::addItem("cancel", 'n');
dialog::addItem("reset the special game modes", 'r');
dialog::display();
keyhandler = [] (int sym, int uni) {
dialog::handleNavigation(sym, uni);
if(uni == 'd') {
resetConfig();
unlink(conffile);
popScreen();
}
else if(uni == 'y') {
printf("resetting config\n");
resetConfig();
printf("config reset\n");
popScreen();
}
else if(uni == 'r')
resetModes();
else if(uni == 'n' || doexiton(sym, uni))
popScreen();
};
}
#endif
#if CAP_TRANS
EX void selectLanguageScreen() {
cmode = sm::SIDE | sm::MAYDARK;
gamescreen();
dialog::init("select language"); // intentionally not translated
int v = vid.language;
dynamicval<int> d(vid.language, -1);
for(int i=0; i<NUMLAN; i++) {
vid.language = i;
dialog::addSelItem(XLAT("EN"), its(100 * transcompleteness[i] / transcompleteness[0]) + "%", 'a'+i);
}
dialog::addBreak(50);
vid.language = -1;
dialog::addBoolItem(XLAT("default") + ": " + XLAT("EN"), v == -1, '0');
dialog::addBack();
dialog::addBreak(50);
vid.language = v;
if(lang() >= 1)
dialog::addHelp(XLAT("add credits for your translation here"));
else
dialog::addHelp(XLAT("original language"));
if(lang() != 0) {
string tw = "";
string s = XLAT("TRANSLATIONWARNING");
if(s != "" && s != "TRANSLATIONWARNING") tw += s;
s = XLAT("TRANSLATIONWARNING2");
if(s != "" && s != "TRANSLATIONWARNING2") { if(tw != "") tw += " "; tw += s; }
if(tw != "") {
dialog::addHelp(tw);
dialog::lastItem().color = 0xFF0000;
}
}
dialog::display();
keyhandler = [] (int sym, int uni) {
dialog::handleNavigation(sym, uni);
if(uni == '0') {
vid.language = -1;
android_settings_changed();
}
else if(uni >= 'a' && uni < 'a'+NUMLAN) {
vid.language = uni - 'a';
android_settings_changed();
}
else if(doexiton(sym, uni))
popScreen();
};
}
#endif
EX void configureMouse() {
cmode = sm::SIDE | sm::MAYDARK;
gamescreen();
dialog::init(XLAT("mouse & touchscreen"));
dialog::addBoolItem_action(XLAT("reverse pointer control"), (vid.revcontrol), 'r');
dialog::addBoolItem_action(XLAT("draw circle around the target"), (vid.drawmousecircle), 'd');
if(GDIM == 3) {
dialog::addBoolItem_action(XLAT("highlight the cell forward"), vid.axes3, 'f');
}
#if ISMOBILE
dialog::addBoolItem(XLAT("targetting ranged Orbs long-click only"), (vid.shifttarget&2), 'i');
#else
dialog::addBoolItem(XLAT("targetting ranged Orbs Shift+click only"), (vid.shifttarget&1), 'i');
#endif
dialog::add_action([] {vid.shifttarget = vid.shifttarget^3; });
#if !ISMOBILE
dialog::addBoolItem_action(XLAT("quick mouse"), vid.quickmouse, 'M');
#endif
dialog::addSelItem(XLAT("move by clicking on compass"), its(vid.mobilecompasssize), 'C');
dialog::add_action([] {
dialog::editNumber(vid.mobilecompasssize, 0, 100, 10, 20, XLAT("compass size"), XLAT("0 to disable"));
// we need to check the moves
dialog::get_di().reaction = checkmove;
dialog::bound_low(0);
});
#if CAP_ORIENTATION
if(GDIM == 2) {
dialog::addSelItem(XLAT("scrolling by device rotation"), ors::choices[ors::mode], '1');
dialog::add_action_push(ors::show);
}
#endif
dialog::addBack();
dialog::display();
}
vector<parameter*> last_changed;
EX void add_to_changed(parameter *f) {
auto orig_f = f;
for(int i=0; i<isize(last_changed); i++) {
if(last_changed[i] == f)
return;
swap(last_changed[i], f);
if(f == orig_f) return;
}
last_changed.push_back(f);
}
EX parameter *find_edit(void *val) {
for(auto& fs: params) {
fs.second->check_change();
if(fs.second->affects(val))
return &*fs.second;
}
return nullptr;
}
EX void add_edit_ptr(void *val) {
int found = 0;
for(auto& fs: params) {
fs.second->check_change();
if(fs.second->affects(val))
fs.second->show_edit_option(), found++;
}
if(found != 1) println(hlog, "found = ", found);
}
EX void add_edit_ptr(void *val, char key) {
int found = 0;
for(auto& fs: params) {
fs.second->check_change();
if(fs.second->affects(val))
fs.second->show_edit_option(key), found++;
}
if(found != 1) println(hlog, "found = ", found);
}
#if HDR
template<class T> void add_edit(T& val) {
add_edit_ptr(&val);
}
template<class T> void add_edit(T& val, char key) {
add_edit_ptr(&val, key);
}
#endif
EX void find_parameter() {
cmode = sm::SIDE | sm::MAYDARK;
gamescreen();
dialog::init(XLAT("find a setting"));
if(dialog::infix != "") mouseovers = dialog::infix;
dialog::start_list(900, 900, '1');
int found = 0;
for(auto& p: params) {
auto& fs = p.second;
string key = fs->search_key();
if(fs->available() && dialog::hasInfix(key)) {
fs->show_edit_option(dialog::list_fake_key++);
found++;
}
}
dialog::end_list();
dialog::addBreak(100);
dialog::addInfo(XLAT("press letters to search"));
dialog::addSelItem(XLAT("matching items"), its(found), 0);
dialog::display();
keyhandler = [] (int sym, int uni) {
dialog::handleNavigation(sym, uni);
if(dialog::editInfix(uni)) dialog::list_skip = 0;
else if(doexiton(sym, uni)) popScreen();
};
}
EX void edit_all_parameters() {
cmode = sm::SIDE | sm::MAYDARK;
gamescreen();
dialog::init(XLAT("recently changed settings"));
for(auto &fs: params) fs.second->check_change();
dialog::start_list(1000, 1000, 'a');
for(auto l: last_changed)
if(l->available())
l->show_edit_option(dialog::list_fake_key++);
dialog::end_list();
dialog::addBreak(100);
dialog::addItem(XLAT("find a setting"), '/');
dialog::add_action_push(find_parameter);
dialog::addBack();
dialog::display();
}
void list_parameter::show_edit_option(int key) {
string opt;
if(get_value() < 0 || get_value() >= isize(options)) opt = its(get_value());
else opt = options[get_value()].first;
dialog::addSelItem(XLAT(menu_item_name), XLAT(opt), key);
reaction_t screen = [this] {
add_to_changed(this);
cmode = sm::SIDE | sm::MAYDARK;
gamescreen();
dialog::init(XLAT(menu_item_name));
dialog::addBreak(100);
int q = isize(options);
int need_list = q > 15 ? 2 : q > 10 ? 1 : 0;
if(need_list >= 2) dialog::start_list(1500, 1500, 'a');
for(int i=0; i<q; i++) {
dialog::addBoolItem(XLAT(options[i].first), get_value() == i, need_list >= 2 ? dialog::list_fake_key++ : 'a' + i);
auto action = [this, i, need_list] { set_value(i); if(reaction) reaction(); if(need_list == 0) popScreen(); };
if(needs_confirm)
dialog::add_action_confirmed(action);
else
dialog::add_action(action);
if(need_list == 0 && options[i].second != "") {
dialog::addBreak(100);
dialog::addHelp(XLAT(options[i].second));
dialog::addBreak(100);
}
}
if(need_list >= 2) dialog::end_list();
dialog::addBreak(100);
if(need_list >= 1 && options[get_value()].second != "") {
string text = options[get_value()].second;
dialog::addHelp(XLAT(text));
dialog::addBreak(100);
}
dialog::addBack();
dialog::display();
};
dialog::add_action_push(screen);
}
EX void showSettings() {
cmode = sm::SIDE | sm::MAYDARK;
gamescreen();
dialog::init(XLAT("settings"));
dialog::addItem(XLAT("interface"), 'i');
dialog::add_action_push(configureInterface);
dialog::addItem(XLAT("general graphics"), 'g');
dialog::add_action_push(showGraphConfig);
dialog::addItem(XLAT("3D configuration"), '9');
dialog::add_action_push(show3D);
dialog::addItem(XLAT("quick options"), 'q');
dialog::add_action_push(showGraphQuickKeys);
dialog::addItem(XLAT("models & projections"), 'p');
dialog::add_action_push(models::quick_model);
dialog::addItem(XLAT("colors & aura"), 'c');
dialog::add_action_push(show_color_dialog);
#if CAP_SHMUP && !ISMOBILE
dialog::addSelItem(XLAT("keyboard & joysticks"), "", 'k');
dialog::add_action(multi::configure);
#endif
dialog::addSelItem(XLAT("mouse & touchscreen"), "", 'm');
dialog::add_action_push(configureMouse);
dialog::addItem(XLAT("other settings"), 'o');
dialog::add_action_push(configureOther);
dialog::addBreak(100);
#if CAP_CONFIG
dialog::addItem(XLAT("find a setting"), '/');
dialog::add_action_push(edit_all_parameters);
dialog::addItem(XLAT("save the current config"), 's');
dialog::add_action(saveConfig);
dialog::addItem(XLAT("reset all configuration"), 'R');
dialog::add_action_push(resetConfigMenu);
#endif
if(getcstat == 's') mouseovers = XLAT("Config file: %1", conffile);
dialog::addBack();
dialog::display();
}
#if CAP_COMMANDLINE
EX int read_color_args() {
using namespace arg;
if(argis("-back")) {
PHASEFROM(2); shift(); backcolor = argcolor(24);
}
else if(argis("-fillmodel")) {
PHASEFROM(2); shift(); modelcolor = argcolor(32);
}
else if(argis("-apeirocolor")) {
PHASEFROM(2); shift(); ccolor::apeirogonal_color = argcolor(32);
}
else if(argis("-ring")) {
PHASEFROM(2); shift(); ringcolor = argcolor(32);
}
else if(argis("-ringw")) {
PHASEFROM(2); shift_arg_formula(vid.multiplier_ring);
}
else if(argis("-stdgrid")) {
PHASEFROM(2); shift(); stdgridcolor = argcolor(32);
}
else if(argis("-gridw")) {
PHASEFROM(2); shift_arg_formula(vid.multiplier_grid);
}
else if(argis("-period")) {
PHASEFROM(2); shift(); periodcolor = argcolor(32);
}
else if(argis("-crosshair")) {
PHASEFROM(2); shift(); crosshair_color = argcolor(32);
shift_arg_formula(crosshair_size);
}
else if(argis("-borders")) {
PHASEFROM(2); shift(); bordcolor = argcolor(24);
}
else if(argis("-fore")) {
PHASEFROM(2); shift(); forecolor = argcolor(24);
}
else if(argis("-title")) {
PHASEFROM(2); shift(); titlecolor = argcolor(24);
}
else if(argis("-dialog")) {
PHASEFROM(2); shift(); dialog::dialogcolor = argcolor(24);
}
else if(argis("-d:color"))
launch_dialog(show_color_dialog);
else return 1;
return 0;
}
EX int read_config_args() {
using namespace arg;
if(argis("-c")) { PHASE(1); shift(); conffile = argcs(); }
// change the configuration from the command line
else if(argis("-aa")) { PHASEFROM(2); shift(); vid.want_antialias = argi(); apply_screen_settings(); }
else if(argis("-lw")) { PHASEFROM(2); shift_arg_formula(vid.linewidth); }
else if(argis("-wm")) { PHASEFROM(2); shift(); vid.wallmode = argi(); }
else if(argis("-mm")) { PHASEFROM(2); shift(); vid.monmode = argi(); }
else if(argis("-noshadow")) { noshadow = true; }
else if(argis("-bright")) { bright = true; }
else if(argis("-gridon")) { vid.grid = true; }
else if(argis("-gridoff")) { vid.grid = false; }
// non-configurable options
else if(argis("-vsync_off")) {
vid.want_vsync = false;
apply_screen_settings();
}
else if(argis("-aura")) {
PHASEFROM(2);
shift(); vid.aurastr = argi();
shift(); vid.aurasmoothen = argi();
}
else if(argis("-nofps")) {
PHASEFROM(2);
nofps = true;
}
else if(argis("-nohud")) {
PHASEFROM(2);
nohud = true;
}
else if(argis("-nomenu")) {
PHASEFROM(2);
nomenukey = true;
}
else if(argis("-nomsg")) {
PHASEFROM(2);
nomsg = true;
}
#if MAXMDIM >= 4
else if(argis("-switch-fpp")) {
PHASEFROM(2);
geom3::switch_fpp();
}
#endif
else if(argis("-switch-tpp")) {
PHASEFROM(2);
geom3::switch_tpp();
}
#if MAXMDIM >= 4
else if(argis("-switch-3d")) {
PHASEFROM(2);
geom3::switch_always3();
}
#endif
else if(argis("-nohelp")) {
PHASEFROM(2);
nohelp = true;
}
else if(argis("-dont_face_pc")) {
PHASEFROM(2);
dont_face_pc = true;
}
#if CAP_TRANS
else if(argis("-lang")) {
PHASEFROM(2); shift(); vid.language = argi();
}
#endif
else if(argis("-vlq")) {
PHASEFROM(2); shift(); vid.linequality = argi();
}
else if(argis("-fov")) {
PHASEFROM(2); shift_arg_formula(vid.fov);
}
else if(argis("-r")) {
PHASEFROM(2);
shift();
if(vid.want_fullscreen) {
int clWidth=0, clHeight=0, clFont=0;
sscanf(argcs(), "%dx%dx%d", &clWidth, &clHeight, &clFont);
vid.change_fullscr = clWidth;
if(clWidth) vid.fullscreen_x = clWidth;
if(clHeight) vid.fullscreen_y = clHeight;
if(clFont) vid.abs_fsize = clFont, vid.relative_font = true;
}
else if(args().find(".") != string::npos) {
vid.relative_window_size = true;
ld dWidth=0, dHeight=0;
sscanf(argcs(), "%lfx%lf", &dWidth, &dHeight);
if(dWidth) vid.window_rel_x = dWidth;
if(dHeight) vid.window_rel_y = dHeight;
}
else {
vid.want_fullscreen = false;
vid.relative_window_size = false;
int clFont=0;
sscanf(argcs(), "%dx%dx%d", &vid.window_x, &vid.window_y, &clFont);
if(clFont) vid.abs_fsize = clFont, vid.relative_font = true;
}
}
else if(argis("-msm")) {
PHASEFROM(2); memory_saving_mode = true;
}
else if(argis("-mrsv")) {
PHASEFROM(2); shift(); reserve_limit = argi(); apply_memory_reserve();
}
else if(argis("-pside")) {
PHASEFROM(2);
permaside = true;
}
else if(argis("-xy")) {
PHASEFROM(2);
shift_arg_formula(pconf.xposition);
shift_arg_formula(pconf.yposition);
}
else if(argis("-fixdir")) {
PHASEFROM(2);
vid.fixed_facing = true;
shift_arg_formula(vid.fixed_facing_dir);
}
else if(argis("-fixdiroff")) {
PHASEFROM(2);
vid.fixed_facing = false;
}
else if(argis("-msmoff")) {
PHASEFROM(2); memory_saving_mode = false;
}
else if(argis("-levellines")) {
PHASEFROM(2); shift_arg_formula(levellines);
}
else if(argis("-level-notexture")) {
PHASEFROM(2); disable_texture = true;
}
else if(argis("-level-texture")) {
PHASEFROM(2); disable_texture = false;
}
else if(argis("-msens")) {
PHASEFROM(2); shift_arg_formula(mouseaim_sensitivity);
}
TOGGLE('o', vid.wantGL, { vid.wantGL = !vid.wantGL; apply_screen_settings();})
TOGGLE('f', vid.want_fullscreen, { vid.want_fullscreen = !vid.want_fullscreen; apply_screen_settings(); })
else if(argis("-noshaders")) {
PHASE(1);
glhr::noshaders = true;
}
else if(argis("-d:sight")) {
PHASEFROM(2); launch_dialog(); edit_sightrange();
}
else if(argis("-d:char")) {
PHASEFROM(2); launch_dialog(showCustomizeChar);
}
else if(argis("-d:3")) {
PHASEFROM(2); launch_dialog(show3D);
}
else if(argis("-d:stereo")) {
PHASEFROM(2); launch_dialog(showStereo);
}
else if(argis("-d:iface")) {
PHASEFROM(2); launch_dialog(configureInterface);
}
else if(argis("-d:graph")) {
PHASEFROM(2); launch_dialog(showGraphConfig);
}
else if(argis("-tstep")) {
PHASEFROM(2); shift(); vid.texture_step = argi();
}
else if(argis("-csc")) {
PHASEFROM(2); shift_arg_formula(vid.creature_scale);
}
else if(argis("-neon")) {
PHASEFROM(2);
shift(); neon_mode = eNeon(argi());
}
else if(argis("-dmc")) {
PHASEFROM(2);
shift(); vid.drawmousecircle = argi();
}
else if(argis("-smooths")) {
PHASEFROM(2);
shift(); smooth_scrolling = argi();
}
else if(argis("-via-shader")) {
PHASEFROM(2);
shift(); vid.consider_shader_projection = argi();
}
else if(argis("-neonnf")) {
PHASEFROM(2);
shift(); neon_nofill = argi();
}
else if(argis("-precw")) {
PHASEFROM(2);
shift_arg_formula(precise_width);
}
else if(argis("-d:all")) {
PHASEFROM(2); launch_dialog(edit_all_parameters);
}
else if(argis("-d:find")) {
PHASEFROM(2); launch_dialog(find_parameter);
}
else if(argis("-d:param")) {
PHASEFROM(2);
shift();
string s = args();
cmode |= sm::SIDE;
for(auto& fs: params) if(fs.first == s) {
dialog::items.clear();
dialog::key_actions.clear();
fs.second->show_edit_option();
for(auto p: dialog::key_actions) { p.second(); return 0; }
println(hlog, "no key action");
return 0;
}
println(hlog, "unknown param to edit: ", s);
}
else if(argis("-char")) {
auto& cs = vid.cs;
shift();
string s = args();
set_char_by_name(cs, s);
}
else return 1;
return 0;
}
EX void set_char_by_name(charstyle& cs, const string& s) {
if(s == "dodek") {
cs.charid = 4;
cs.lefthanded = false;
cs.skincolor = 0x202020FF;
cs.eyecolor = 0x20C000FF;
cs.haircolor = 0x202020FF;
cs.dresscolor =0x424242FF;
cs.swordcolor = 0xF73333FF;
}
else if(s == "rudy") {
cs.charid = 4;
cs.lefthanded = false;
cs.skincolor = 0xA44139FF;
cs.eyecolor = 0xD59533FF;
cs.haircolor = 0xC6634AFF;
cs.dresscolor =0xC6634AFF;
cs.swordcolor = 0x3CBB33FF;
}
else if(s == "running") {
cs.charid = 6;
cs.lefthanded = false;
cs.skincolor = 0xFFFFFFFF;
cs.eyecolor = 0xFF;
cs.haircolor = 0xFFFFFFFF;
cs.dresscolor =0xFFFFFFFF;
cs.swordcolor = 0xFF0000FF;
}
else if(s == "princess") {
cs.charid = 3;
cs.lefthanded = true;
cs.skincolor = 0xEFD0C9FF;
cs.haircolor = 0x301800FF;
cs.eyecolor = 0xC000FF;
cs.dresscolor = 0x408040FF;
cs.swordcolor = 0xFFFFFFFF;
}
else if(s == "worker") {
cs.charid = 2;
cs.skincolor = 0xC77A58FF;
cs.haircolor = 0x502810FF;
cs.dresscolor = 0xC0C000FF;
cs.eyecolor = 0x500040FF;
cs.swordcolor = 0x808080FF;
}
else {
cs.charid = atoi(s.c_str());
cs.lefthanded = cs.charid >= 10;
cs.charid %= 10;
}
}
EX int read_param_args() {
const string& s = arg::args();
auto pos = s.find("=");
if(pos == string::npos) return 1;
string name = s.substr(0, pos);
string value = s.substr(pos+1);
PHASEFROM(2);
if(!params.count(name)) {
println(hlog, "parameter unknown: ", name);
exit(1);
}
params[name]->load_as_animation(value);
return 0;
}
// mode changes:
EX int read_gamemode_args() {
using namespace arg;
if(argis("-P")) {
PHASE(2); shift();
stop_game_and_switch_mode(rg::nothing);
multi::players = argi();
}
TOGGLE('S', shmup::on, stop_game_and_switch_mode(rg::shmup))
TOGGLE('H', hardcore, switchHardcore())
TOGGLE('R', randomPatternsMode, stop_game_and_switch_mode(rg::randpattern))
TOGGLE('i', inv::on, stop_game_and_switch_mode(rg::inv))
else return 1;
return 0;
}
auto ah_config =
addHook(hooks_args, 0, read_config_args) +
addHook(hooks_args, 0, read_param_args) +
addHook(hooks_args, 0, read_gamemode_args) + addHook(hooks_args, 0, read_color_args);
#endif
/* local parameter, for another game */
local_parameter_set* current_lps;
void local_parameter_set::pswitch() {
if(extends) extends->pswitch();
for(auto s: swaps) {
s.first->swap_with(&*s.second);
swap(s.first->name, s.second->name);
}
}
EX void lps_enable(local_parameter_set *lps) {
if(lps == current_lps) return;
if(current_lps) current_lps->pswitch();
current_lps = lps;
if(current_lps) current_lps->pswitch();
}
#if HDR
//template<class T> vector<std::unique_ptr<T>> lps_of_type;
extern vector<void*> lps_of_type;
template<class T, class U> void lps_add(local_parameter_set& lps, T&val, U nvalue) {
int found = 0;
for(auto& fs: params) {
if(fs.second->affects(&val)) {
found++;
T* nv = new T(nvalue);
lps_of_type.emplace_back(nv);
println(hlog, lps.label, " found saver: ", fs.second->name);
auto fs2 = fs.second->clone(lps, nv);
lps.swaps.emplace_back(fs.second, fs2);
return;
}
}
if(found != 1) println(hlog, lps.label, " saver not found");
}
#endif
vector<void*> lps_of_type;
}