diff --git a/crawl-ref/docs/crawl.6 b/crawl-ref/docs/crawl.6 index 665caacc8f1..48b14ba04e4 100644 --- a/crawl-ref/docs/crawl.6 +++ b/crawl-ref/docs/crawl.6 @@ -11,6 +11,7 @@ .Op Fl test .Op Fl sprint-map Ar map .Op Fl sprint +.Op Fl onward .Op Fl species Ar species .Op Fl seed Ar num .Op Fl script Ar file diff --git a/crawl-ref/docs/crawl_manual.rst b/crawl-ref/docs/crawl_manual.rst index 78f01d9c066..9346babf67e 100644 --- a/crawl-ref/docs/crawl_manual.rst +++ b/crawl-ref/docs/crawl_manual.rst @@ -75,6 +75,11 @@ Hints Mode for Dungeon Crawl Dungeon Sprint Start one of several single-map challenge mode games of Crawl. +Onward + Start a standard game of Crawl in which dying offers a choice: continue in + the dungeon where you fell, with full health and magic, at the cost of half + your score. Onward games keep their own saves and score lists. + Instructions View the instructional help text you are currently reading. diff --git a/crawl-ref/docs/options_guide.txt b/crawl-ref/docs/options_guide.txt index a9edeb800f4..69808bf11bf 100644 --- a/crawl-ref/docs/options_guide.txt +++ b/crawl-ref/docs/options_guide.txt @@ -358,7 +358,7 @@ name = Delilah type = normal Choose a game type. Valid values are "normal", "seeded", "tutorial", - "arena", "sprint", "descent", and "hints". If explicitly set (not just + "arena", "sprint", "descent", "onward", and "hints". If explicitly set (not just left to the default), this sets the default game type at the main menu. If the main menu is bypassed (e.g. by setting `name`), this option can be used to determine the game type. "descent" is only valid in alpha diff --git a/crawl-ref/source/Makefile.obj b/crawl-ref/source/Makefile.obj index 376ea00cf12..29363dc0934 100644 --- a/crawl-ref/source/Makefile.obj +++ b/crawl-ref/source/Makefile.obj @@ -313,6 +313,7 @@ catch2-tests/test_files.o \ catch2-tests/test_items.o \ catch2-tests/test_mon-util.o \ catch2-tests/test_ng-init-branches.o \ +catch2-tests/test_onward.o \ catch2-tests/test_player.o \ catch2-tests/test_player_fixture.o \ catch2-tests/test_randbook.o \ diff --git a/crawl-ref/source/catch2-tests/test_onward.cc b/crawl-ref/source/catch2-tests/test_onward.cc new file mode 100644 index 00000000000..2b3f6d626d7 --- /dev/null +++ b/crawl-ref/source/catch2-tests/test_onward.cc @@ -0,0 +1,96 @@ +#include "catch_amalgamated.hpp" + +#include "AppHdr.h" +#include "game-type.h" +#include "hiscores.h" +#include "initfile.h" +#include "player.h" +#include "state.h" +#include "unwind.h" + +#include "test_player_fixture.h" + +// Every game-type switch in the codebase has a `default:` label, so a new +// mode that is missing from one compiles fine and silently behaves like a +// normal game (shared save directory, shared scorefile). These pin each one. +TEST_CASE("Onward mode is a distinct game type everywhere", "[single-file]") +{ + unwind_var gt(crawl_state.type, GAME_TYPE_ONWARD); + + REQUIRE(crawl_state.game_is_onward()); + REQUIRE_FALSE(crawl_state.game_is_normal()); + REQUIRE_FALSE(crawl_state.game_is_sprint()); + REQUIRE_FALSE(crawl_state.game_is_descent()); + + // Behaves like a normal dungeon game apart from death. + REQUIRE(crawl_state.game_has_random_floors()); + REQUIRE(crawl_state.game_saves_prefs()); + + // Never shares files with real games. + REQUIRE(gametype_to_str(GAME_TYPE_ONWARD) == "onward"); + REQUIRE(crawl_state.game_type_name() == "Onward"); + REQUIRE(crawl_state.game_savedir_path() == "onward/"); + REQUIRE(crawl_state.game_type_qualifier() == "-onward"); +} + +TEST_CASE("Onward continues halve the score each time", "[single-file]") +{ + REQUIRE(apply_onward_penalty(1000, 0) == 1000); + REQUIRE(apply_onward_penalty(1000, 1) == 500); + REQUIRE(apply_onward_penalty(1000, 3) == 125); + REQUIRE(apply_onward_penalty(3, 2) == 0); + // Far more continues than bits in an int must not be undefined behaviour. + REQUIRE(apply_onward_penalty(1000000, 100) == 0); +} + +TEST_CASE("Onward continue is refused when the score cannot pay", + "[single-file]") +{ + // Need at least 2 points: half rounds down to >= 1 spent and >= 1 kept. + REQUIRE_FALSE(onward_can_afford_continue(0)); + REQUIRE_FALSE(onward_can_afford_continue(1)); + REQUIRE(onward_can_afford_continue(2)); + // Cost rounds up (remainder rounds down): 3 -> keep 1, spend 2. + REQUIRE(apply_onward_penalty(3, 1) == 1); + REQUIRE(onward_can_afford_continue(3)); +} + +TEST_CASE_METHOD(MockPlayerYouTestsFixture, + "Onward continues are counted on the player", "[single-file]") +{ + REQUIRE(you.onward_continues() == 0); + you.props[ONWARD_CONTINUES_KEY].get_int()++; + you.props[ONWARD_CONTINUES_KEY].get_int()++; + REQUIRE(you.onward_continues() == 2); +} + +TEST_CASE("Onward continues survive an xlog round trip and are described", + "[single-file]") +{ + unwind_var gt(crawl_state.type, GAME_TYPE_ONWARD); + + scorefile_entry se; + REQUIRE(se.parse("v=0.34.1:lv=0.1:name=Test:race=Minotaur:cls=Fighter" + ":xl=5:sc=250:ktyp=mon:killer=an ogre:onward=2" + ":start=20260830000000S:end=20260830000100S")); + REQUIRE(se.raw_string().find(":onward=2") != string::npos); + // Two paid continues plus the death that ended the game. + REQUIRE(hiscores_format_single_long(se, true).find("250 (3 deaths) Test") + != string::npos); + REQUIRE(hiscores_format_single(se).find("250 (3 deaths) Test") + != string::npos); + + scorefile_entry win; + REQUIRE(win.parse("v=0.34.1:lv=0.1:name=Win:race=Minotaur:cls=Fighter" + ":xl=27:sc=900000:ktyp=winning:onward=1" + ":start=20260830000000S:end=20260830000100S")); + REQUIRE(hiscores_format_single(win).find("900000 (1 death) Win") + != string::npos); + + scorefile_entry clean; + REQUIRE(clean.parse("v=0.34.1:lv=0.1:name=Clean:race=Minotaur:cls=Fighter" + ":xl=27:sc=900000:ktyp=winning" + ":start=20260830000000S:end=20260830000100S")); + REQUIRE(hiscores_format_single(clean).find("900000 (0 deaths) Clean") + != string::npos); +} diff --git a/crawl-ref/source/game-type.h b/crawl-ref/source/game-type.h index f30e3eef43f..0b62c2a0817 100644 --- a/crawl-ref/source/game-type.h +++ b/crawl-ref/source/game-type.h @@ -15,5 +15,6 @@ enum game_type GAME_TYPE_HIGH_SCORES, GAME_TYPE_CUSTOM_SEED, GAME_TYPE_DESCENT, + GAME_TYPE_ONWARD, NUM_GAME_TYPE }; diff --git a/crawl-ref/source/hiscores.cc b/crawl-ref/source/hiscores.cc index 22e58fb64f2..f9c566f97e1 100644 --- a/crawl-ref/source/hiscores.cc +++ b/crawl-ref/source/hiscores.cc @@ -772,6 +772,7 @@ void scorefile_entry::init_from(const scorefile_entry &se) penance = se.penance; wiz_mode = se.wiz_mode; explore_mode = se.explore_mode; + onward_continues = se.onward_continues; birth_time = se.birth_time; death_time = se.death_time; real_time = se.real_time; @@ -1086,6 +1087,7 @@ void scorefile_entry::init_with_fields() penance = fields->int_field("pen"); wiz_mode = fields->int_field("wiz"); explore_mode = fields->int_field("explore"); + onward_continues = fields->int_field("onward"); birth_time = _parse_time(fields->str_field("start")); death_time = _parse_time(fields->str_field("end")); @@ -1183,6 +1185,10 @@ void scorefile_entry::set_base_xlog_fields() const fields->add_field("wiz", "%d", wiz_mode); if (explore_mode) fields->add_field("explore", "%d", explore_mode); + // Onward games always carry the field, so a clean win is visibly onward=0 + // rather than indistinguishable from a normal game's entry. + if (crawl_state.game_is_onward() || onward_continues) + fields->add_field("onward", "%d", onward_continues); fields->add_field("start", "%s", make_date_string(birth_time).c_str()); fields->add_field("dur", "%d", (int)real_time); @@ -1540,6 +1546,7 @@ void scorefile_entry::reset() penance = -1; wiz_mode = 0; explore_mode = 0; + onward_continues = 0; birth_time = 0; death_time = 0; real_time = -1; @@ -1692,6 +1699,12 @@ void scorefile_entry::init(time_t dt) ASSERT(crawl_state.game_is_sprint()); // only sprint should use custom scores + if (crawl_state.game_is_onward()) + { + onward_continues = you.onward_continues(); + points = apply_onward_penalty(points, onward_continues); + } + race = you.species; job = you.char_class; @@ -1937,12 +1950,42 @@ void scorefile_entry::fixup_char_name() } } +// Onward mode: every continue halves whatever the run would otherwise be +// worth, so a win that never died keeps the full score and a late death still +// costs far more than an early one. +int apply_onward_penalty(int points, int continues) +{ + for (int i = 0; i < continues && points > 0; ++i) + points /= 2; + return points; +} + +bool onward_can_afford_continue(int points) +{ + // Need >= 2 so half rounds down to >= 1 spent and >= 1 kept. + return points >= 2; +} + +string scorefile_entry::onward_deaths_desc() const +{ + if (!crawl_state.game_is_onward() && !onward_continues) + return ""; + // Continues are the deaths that were paid for; a game that ended in + // death has one more. + const bool died = death_type != KILLED_BY_WINNING + && death_type != KILLED_BY_QUITTING + && death_type != KILLED_BY_LEAVING; + const int deaths = onward_continues + (died ? 1 : 0); + return make_stringf(" (%d death%s)", deaths, deaths == 1 ? "" : "s"); +} + string scorefile_entry::single_cdesc() const { string scname; scname = chop_string(name, 10); - return make_stringf("%8d %s %s-%02d%s", points, scname.c_str(), + return make_stringf("%8d%s %s %s-%02d%s", points, + onward_deaths_desc().c_str(), scname.c_str(), race_class_name.c_str(), lvl, (wiz_mode == 1) ? "W" : (explore_mode == 1) ? "E" : ""); } @@ -2024,13 +2067,14 @@ scorefile_entry::character_description(death_desc_verbosity verbosity) const // Please excuse the following bit of mess in the name of flavour ;) if (verbose) { - desc = make_stringf("%8d %s the %s (level %d", - points, name.c_str(), title.c_str(), lvl); + desc = make_stringf("%8d%s %s the %s (level %d", + points, onward_deaths_desc().c_str(), name.c_str(), + title.c_str(), lvl); } else { - desc = make_stringf("%8d %s the %s %s (level %d", - points, name.c_str(), + desc = make_stringf("%8d%s %s the %s %s (level %d", + points, onward_deaths_desc().c_str(), name.c_str(), _species_name(race).c_str(), _job_name(job), lvl); } diff --git a/crawl-ref/source/hiscores.h b/crawl-ref/source/hiscores.h index dc4080b8767..59c75942788 100644 --- a/crawl-ref/source/hiscores.h +++ b/crawl-ref/source/hiscores.h @@ -27,6 +27,10 @@ string hiscores_print_list(int display_count, int format, int newest_entry, int& void hiscores_print_all(int display_count = -1, int format = SCORE_TERSE); void show_hiscore_table(); +int apply_onward_penalty(int points, int continues); +// Onward mode: a run can only be continued while it can pay the halving cost +// and keep at least one point; below that, death is final. +bool onward_can_afford_continue(int points); string hiscores_format_single(const scorefile_entry &se); string hiscores_format_single_long(const scorefile_entry &se, bool verbose = false); @@ -122,6 +126,7 @@ class scorefile_entry int penance; // penance uint8_t wiz_mode; // character used wiz mode uint8_t explore_mode; // character used explore mode + int onward_continues; // Onward mode: times the player paid to continue time_t birth_time; // start time of character time_t death_time; // end time of character time_t real_time; // real playing time in seconds @@ -178,6 +183,10 @@ class scorefile_entry string raw_string() const; bool parse(const string &line); + // Onward mode: how many times this character died, counting the death + // that ended the game. Empty for other modes. + string onward_deaths_desc() const; + string hiscore_line(death_desc_verbosity verbosity) const; string character_description(death_desc_verbosity) const; diff --git a/crawl-ref/source/initfile.cc b/crawl-ref/source/initfile.cc index 45db97da1e1..9504fda1cbe 100644 --- a/crawl-ref/source/initfile.cc +++ b/crawl-ref/source/initfile.cc @@ -202,7 +202,8 @@ static map _game_modes() {"arena", GAME_TYPE_ARENA}, {"sprint", GAME_TYPE_SPRINT}, {"tutorial", GAME_TYPE_TUTORIAL}, - {"hints", GAME_TYPE_HINTS} + {"hints", GAME_TYPE_HINTS}, + {"onward", GAME_TYPE_ONWARD} }; if (Version::ReleaseType == VER_ALPHA) modes["descent"] = GAME_TYPE_DESCENT; @@ -1220,6 +1221,8 @@ string gametype_to_str(game_type type) return "hints"; case GAME_TYPE_DESCENT: return "descent"; + case GAME_TYPE_ONWARD: + return "onward"; default: return "none"; } @@ -4589,6 +4592,7 @@ enum commandline_option_type CLO_GAMETYPES_JSON, CLO_EDIT_BONES, CLO_DESCENT, + CLO_ONWARD, #if defined(UNIX) || defined(USE_TILE_LOCAL) CLO_HEADLESS, #endif @@ -4642,7 +4646,7 @@ static const char *cmd_ops[] = "print-charset", "tutorial", "wizard", "explore", "no-save", "no-player-bones", "gdb", "no-gdb", "nogdb", "throttle", "no-throttle", "lua-max-memory", "playable-json", "branches-json", "save-json", - "gametypes-json", "bones", "descent", + "gametypes-json", "bones", "descent", "onward", #if defined(UNIX) || defined(USE_TILE_LOCAL) "headless", #endif @@ -5360,6 +5364,8 @@ static string _gametype_to_clo(game_type g) return cmd_ops[CLO_SPRINT]; case GAME_TYPE_DESCENT: // no CLO? return cmd_ops[CLO_DESCENT]; + case GAME_TYPE_ONWARD: + return cmd_ops[CLO_ONWARD]; case GAME_TYPE_HINTS: // no CLO? case GAME_TYPE_NORMAL: default: @@ -5889,6 +5895,11 @@ bool parse_args(int argc, char **argv, bool rc_only) Options.game.type = GAME_TYPE_DESCENT; break; + case CLO_ONWARD: + if (!rc_only) + Options.game.type = GAME_TYPE_ONWARD; + break; + case CLO_SPRINT_MAP: if (!next_is_param) return false; diff --git a/crawl-ref/source/main.cc b/crawl-ref/source/main.cc index c1001c59ff0..fa4fa5de98c 100644 --- a/crawl-ref/source/main.cc +++ b/crawl-ref/source/main.cc @@ -518,6 +518,7 @@ static void _show_commandline_options_help() puts(" -save-version Save file version for the given player"); puts(" -sprint select Sprint"); puts(" -sprint-map preselect a Sprint map"); + puts(" -onward select Onward (continue after death for half your score)"); puts(" -tutorial select the Tutorial"); #ifdef WIZARD puts(" -wizard allow access to wizard mode"); @@ -1070,7 +1071,13 @@ static void _input() if (you.pending_revival) { revive(); - bring_to_safety(); + // Onward continues put you back where you fell, unless the floor + // itself would just kill you again. + if (!crawl_state.game_is_onward() + || is_feat_dangerous(env.grid(you.pos()), true)) + { + bring_to_safety(); + } redraw_screen(); update_screen(); } diff --git a/crawl-ref/source/ng-setup.cc b/crawl-ref/source/ng-setup.cc index 8a80663a34b..4f6b96b744d 100644 --- a/crawl-ref/source/ng-setup.cc +++ b/crawl-ref/source/ng-setup.cc @@ -350,6 +350,7 @@ void setup_game(const newgame_def& ng, { case GAME_TYPE_NORMAL: case GAME_TYPE_DESCENT: + case GAME_TYPE_ONWARD: case GAME_TYPE_CUSTOM_SEED: case GAME_TYPE_TUTORIAL: case GAME_TYPE_SPRINT: diff --git a/crawl-ref/source/ouch.cc b/crawl-ref/source/ouch.cc index 3d0f6dd37d6..3cc2e6a5c93 100644 --- a/crawl-ref/source/ouch.cc +++ b/crawl-ref/source/ouch.cc @@ -41,6 +41,7 @@ #include "item-prop.h" #include "items.h" #include "libutil.h" +#include "macro.h" #include "message.h" #include "mgen-data.h" #include "mon-death.h" @@ -989,6 +990,84 @@ static void _print_endgame_messages(scorefile_entry &se) * @param see_source whether the attacker was visible to you * @param death_source_name the attacker's name if it is already dead. */ +// "You die..." and Xom's commentary, then wait for a key. Split out so the +// Onward prompt can show the death before asking about it. +static void _announce_death(const scorefile_entry &se) +{ + canned_msg(MSG_YOU_DIE); + xom_death_message((kill_method_type) se.get_death_type()); + more(); +} + +// The player is dead, but gets back up at the start of their next turn (see +// revive()). Shared by Felid extra lives and Onward continues. +static void _schedule_revival(const scorefile_entry &se, + kill_method_type death_type, bool announce) +{ + you.pending_revival = true; + + stop_delay(true); + + // You wouldn't want to lose this accomplishment to a crash, would you? + // Especially if you manage to trigger one via lua somehow... + if (!crawl_state.disables[DIS_SAVE_CHECKPOINTS]) + save_game(false); + + if (announce) + _announce_death(se); + + if (death_type != KILLED_BY_ZOT) + _place_player_corpse(death_type == KILLED_BY_DISINT); +} + +// Quitting, winning and leaving end the game but are not deaths, so Onward +// has nothing to offer for them. +static bool _onward_can_continue(kill_method_type death_type) +{ + return death_type != KILLED_BY_QUITTING + && death_type != KILLED_BY_WINNING + && death_type != KILLED_BY_LEAVING; +} + +// Onward mode: offer to carry on where the player fell for half their score. +// `se` already reflects earlier continues, so the cost shown is what this one +// takes. The death is shown and acknowledged first, and any keys still queued +// from the fight are dropped, so the answer has to be a deliberate Y or N. +static bool _onward_continue(const scorefile_entry &se) +{ + _announce_death(se); + // FLUSH_ON_FAILURE exists in every supported version and is on by + // default; it drops keys queued during the fight. + flush_input_buffer(FLUSH_ON_FAILURE); + + const int score = se.get_score(); + const int cost = score - apply_onward_penalty(score, 1); + // Nothing left to pay with (0 points, or a single point that halving + // would take entirely): this death is final. + if (!onward_can_afford_continue(score)) + { + mprf(MSGCH_WARN, "Sorry, you don't even have the points to come back. " + "Goodbye."); + more(); + return false; + } + const string prompt = make_stringf( + "Continue in the dungeon at the cost of half your points " + "(%d point%s)?", cost, cost == 1 ? "" : "s"); + const bool answer = yesno(prompt.c_str(), false, 0); + // A dropped connection mid-prompt must not decide for the player; keep + // the character and let them choose again if they die again. + if (crawl_state.seen_hups) + return true; + if (!answer) + return false; + + take_note(Note(NOTE_MESSAGE, 0, 0, + make_stringf("Continued onward at the cost of %d point%s.", + cost, cost == 1 ? "" : "s"))); + return true; +} + void ouch(int dam, kill_method_type death_type, mid_t source, const char *aux, bool see_source, const char *death_source_name) { @@ -1260,23 +1339,23 @@ void ouch(int dam, kill_method_type death_type, mid_t source, const char *aux, you.deaths++; you.lives--; - you.pending_revival = true; - take_note(Note(NOTE_LOSE_LIFE, you.lives)); - stop_delay(true); - - // You wouldn't want to lose this accomplishment to a crash, would you? - // Especially if you manage to trigger one via lua somehow... - if (!crawl_state.disables[DIS_SAVE_CHECKPOINTS]) - save_game(false); + _schedule_revival(se, death_type, true); + return; + } + // Onward mode: death is a choice. Felid lives are spent first because + // they are free; this costs half the score, so ask. No max HP means + // revive() would kill us straight back here, so that death is final. + if (crawl_state.game_is_onward() && _onward_can_continue(death_type) + && you.hp_max > 0 && _onward_continue(se)) + { + mark_milestone("death", lowercase_first(se.long_kill_message()).c_str()); - canned_msg(MSG_YOU_DIE); - xom_death_message((kill_method_type) se.get_death_type()); - more(); + you.deaths++; + you.props[ONWARD_CONTINUES_KEY].get_int()++; - if (death_type != KILLED_BY_ZOT) - _place_player_corpse(death_type == KILLED_BY_DISINT); + _schedule_revival(se, death_type, false); return; } diff --git a/crawl-ref/source/player.cc b/crawl-ref/source/player.cc index ad84a545e96..f5f0894724d 100644 --- a/crawl-ref/source/player.cc +++ b/crawl-ref/source/player.cc @@ -8698,3 +8698,9 @@ bool player::allies_forbidden() return get_mutation_level(MUT_NO_LOVE) || have_passive(passive_t::no_allies); } + +int player::onward_continues() const +{ + return props.exists(ONWARD_CONTINUES_KEY) + ? props[ONWARD_CONTINUES_KEY].get_int() : 0; +} diff --git a/crawl-ref/source/player.h b/crawl-ref/source/player.h index f90a680b0f9..9380c103850 100644 --- a/crawl-ref/source/player.h +++ b/crawl-ref/source/player.h @@ -46,6 +46,9 @@ #define FUGUE_KEY "fugue_of_the_fallen_bonus" #define FORCE_MAPPABLE_KEY "force_mappable" #define MANA_REGEN_AMULET_ACTIVE "mana_regen_amulet_active" +// Onward mode: how many times the player has paid half their score to keep +// going after a death. Lives in props so it needs no save-format change. +#define ONWARD_CONTINUES_KEY "onward_continues" #define TEMP_WATERWALK_KEY "temp_waterwalk" #define EMERGENCY_FLIGHT_KEY "emergency_flight" #define PARALYSED_BY_KEY "paralysed_by" @@ -589,6 +592,7 @@ class player : public actor int get_hit_dice() const override; int get_experience_level() const override; int get_max_xl() const; + int onward_continues() const; bool is_player() const override { #ifndef DEBUG_GLOBALS diff --git a/crawl-ref/source/startup.cc b/crawl-ref/source/startup.cc index d3607d3aaec..146398f5392 100644 --- a/crawl-ref/source/startup.cc +++ b/crawl-ref/source/startup.cc @@ -434,6 +434,9 @@ static const vector entries = "than the tutorial."}, {GAME_TYPE_DESCENT, "Dungeon Descent", "Mode with a branching, one-way path through the Dungeon." }, + {GAME_TYPE_ONWARD, "Onward", + "A normal game where death offers a way back, at the cost of half " + "your score each time." }, {GAME_TYPE_SPRINT, "Dungeon Sprint", "Hard, fixed single level game mode." }, {GAME_TYPE_INSTRUCTIONS, "Instructions", "Help menu." }, @@ -741,6 +744,7 @@ class UIStartupMenu : public Widget { case GAME_TYPE_NORMAL: case GAME_TYPE_DESCENT: + case GAME_TYPE_ONWARD: case GAME_TYPE_CUSTOM_SEED: case GAME_TYPE_TUTORIAL: case GAME_TYPE_SPRINT: @@ -921,6 +925,7 @@ void UIStartupMenu::menu_item_activated(int id) { case GAME_TYPE_NORMAL: case GAME_TYPE_DESCENT: + case GAME_TYPE_ONWARD: case GAME_TYPE_CUSTOM_SEED: case GAME_TYPE_TUTORIAL: case GAME_TYPE_SPRINT: diff --git a/crawl-ref/source/state.cc b/crawl-ref/source/state.cc index ee266502de8..39dab017456 100644 --- a/crawl-ref/source/state.cc +++ b/crawl-ref/source/state.cc @@ -555,12 +555,14 @@ bool game_state::player_is_dead() const bool game_state::game_has_random_floors() const { - return game_is_normal() || game_is_hints() || game_is_descent(); + return game_is_normal() || game_is_hints() || game_is_descent() + || game_is_onward(); } bool game_state::game_saves_prefs() const { - return game_is_normal() || game_is_hints() || game_is_descent(); + return game_is_normal() || game_is_hints() || game_is_descent() + || game_is_onward(); } bool game_state::game_is_valid_type() const @@ -605,6 +607,12 @@ bool game_state::game_is_descent() const return type == GAME_TYPE_DESCENT; } +bool game_state::game_is_onward() const +{ + ASSERT(game_is_valid_type()); + return type == GAME_TYPE_ONWARD; +} + bool game_state::game_is_hints_tutorial() const { return game_is_hints() || game_is_tutorial(); @@ -635,6 +643,8 @@ string game_state::game_type_name_for(game_type _type) return "Dungeon Sprint"; case GAME_TYPE_DESCENT: return "Dungeon Descent"; + case GAME_TYPE_ONWARD: + return "Onward"; case NUM_GAME_TYPE: return "Unknown"; } @@ -664,6 +674,8 @@ string game_state::game_savedir_path() const return "sprint/"; case GAME_TYPE_DESCENT: return "descent/"; + case GAME_TYPE_ONWARD: + return "onward/"; default: return ""; } @@ -678,6 +690,7 @@ string game_state::game_type_qualifier() const case GAME_TYPE_HINTS: case GAME_TYPE_TUTORIAL: case GAME_TYPE_DESCENT: + case GAME_TYPE_ONWARD: return "-" + gametype_to_str(type); default: return ""; diff --git a/crawl-ref/source/state.h b/crawl-ref/source/state.h index 7626e02c587..a705d45588f 100644 --- a/crawl-ref/source/state.h +++ b/crawl-ref/source/state.h @@ -231,6 +231,7 @@ struct game_state bool game_is_hints() const; bool game_is_hints_tutorial() const; bool game_is_descent() const; + bool game_is_onward() const; bool game_has_random_floors() const; bool game_saves_prefs() const; diff --git a/crawl-ref/source/tilepick.cc b/crawl-ref/source/tilepick.cc index 0faeb6edabe..3a8a94707ac 100644 --- a/crawl-ref/source/tilepick.cc +++ b/crawl-ref/source/tilepick.cc @@ -3607,6 +3607,7 @@ tileidx_t tileidx_gametype(const game_type gtype) { case GAME_TYPE_NORMAL: case GAME_TYPE_CUSTOM_SEED: + case GAME_TYPE_ONWARD: return TILEG_STARTUP_STONESOUP; case GAME_TYPE_DESCENT: return TILEG_STARTUP_IRONSOUP; diff --git a/crawl-ref/source/webserver/games.d/base.yaml b/crawl-ref/source/webserver/games.d/base.yaml index e543b224aca..df606678130 100644 --- a/crawl-ref/source/webserver/games.d/base.yaml +++ b/crawl-ref/source/webserver/games.d/base.yaml @@ -92,6 +92,11 @@ games: name: Descent options: - -descent + - id: onward-web-trunk + template: trunk + name: Onward + options: + - -onward - id: tut-web-trunk template: trunk name: Tutorial