From 86d488e3efa0b1d8ec12223527235e3ef3a5fec2 Mon Sep 17 00:00:00 2001 From: dshaligram Date: Wed, 31 Dec 2008 08:41:27 +0000 Subject: Add arena mode, activated on the command-line by 'crawl -arena "monster v monster"' (eg: crawl -arena "Sigmund v Jessica") to let monsters fight each other undisturbed by the player. Good to examine monster AI and monster behaviour when the player is AWOL. git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@8059 c06c8d41-db1a-0410-9941-cceddc491573 --- crawl-ref/source/abyss.cc | 2 +- crawl-ref/source/acr.cc | 29 ++- crawl-ref/source/arena.cc | 450 +++++++++++++++++++++++++++++++++ crawl-ref/source/arena.h | 8 + crawl-ref/source/dat/arena.des | 26 ++ crawl-ref/source/dat/clua/loadmaps.lua | 2 + crawl-ref/source/directn.h | 5 + crawl-ref/source/dungeon.cc | 62 +++-- crawl-ref/source/dungeon.h | 12 +- crawl-ref/source/externs.h | 2 + crawl-ref/source/fight.cc | 6 +- crawl-ref/source/initfile.cc | 14 +- crawl-ref/source/initfile.h | 2 + crawl-ref/source/makefile.obj | 1 + crawl-ref/source/mapdef.cc | 21 +- crawl-ref/source/mapdef.h | 9 +- crawl-ref/source/message.cc | 7 + crawl-ref/source/mon-util.cc | 12 +- crawl-ref/source/monplace.cc | 4 + crawl-ref/source/monspeak.cc | 3 +- crawl-ref/source/monstuff.cc | 53 +++- crawl-ref/source/mstuff2.cc | 4 - crawl-ref/source/output.cc | 3 + crawl-ref/source/player.cc | 6 + crawl-ref/source/state.cc | 2 +- crawl-ref/source/state.h | 2 + crawl-ref/source/stuff.cc | 2 +- crawl-ref/source/view.cc | 49 +++- 28 files changed, 731 insertions(+), 67 deletions(-) create mode 100644 crawl-ref/source/arena.cc create mode 100644 crawl-ref/source/arena.h create mode 100644 crawl-ref/source/dat/arena.des (limited to 'crawl-ref') diff --git a/crawl-ref/source/abyss.cc b/crawl-ref/source/abyss.cc index a1003b1385..910b85eec7 100644 --- a/crawl-ref/source/abyss.cc +++ b/crawl-ref/source/abyss.cc @@ -398,7 +398,7 @@ void area_shift(void) { #ifdef DEBUG_ABYSS mprf(MSGCH_DIAGNOSTICS, "area_shift() - player at pos (%d, %d)", - you.youpos.x, you.youpos.y); + you.position.x, you.position.y); #endif // Preserve floor props around the player, primarily so that diff --git a/crawl-ref/source/acr.cc b/crawl-ref/source/acr.cc index bd122ecf91..960b612392 100644 --- a/crawl-ref/source/acr.cc +++ b/crawl-ref/source/acr.cc @@ -43,6 +43,7 @@ #include "abl-show.h" #include "abyss.h" +#include "arena.h" #include "branch.h" #include "chardump.h" #include "cio.h" @@ -135,6 +136,8 @@ char info[ INFO_SIZE ]; // messaging queue extern'd everywhere {dlb} int stealth; // externed in view.cc +void world_reacts(); + static key_recorder repeat_again_rec; // Clockwise, around the compass from north (same order as enum RUN_DIR) @@ -160,7 +163,6 @@ static void _close_door(coord_def move); static void _start_running( int dir, int mode ); static void _prep_input(); -static void _world_reacts(); static command_type _get_next_cmd(); static keycode_type _get_next_keycode(); static command_type _keycode_to_command( keycode_type key ); @@ -1527,7 +1529,7 @@ static void _input() crawl_state.cancel_cmd_repeat("Cannot move, cancelling command " "repetition."); - _world_reacts(); + world_reacts(); return; } @@ -1547,13 +1549,13 @@ static void _input() if (you_are_delayed() && current_delay_action() != DELAY_MACRO_PROCESS_KEY) { if (you.time_taken) - _world_reacts(); + world_reacts(); return; } if (you.turn_is_over) { - _world_reacts(); + world_reacts(); return; } @@ -1622,7 +1624,7 @@ static void _input() if (apply_berserk_penalty) _do_berserk_no_combat_penalty(); - _world_reacts(); + world_reacts(); } else viewwindow(true, false); @@ -3049,7 +3051,7 @@ static void _check_sanctuary() decrease_sanctuary_radius(); } -static void _world_reacts() +void world_reacts() { crawl_state.clear_god_acting(); @@ -3080,7 +3082,8 @@ static void _world_reacts() search_around(false); // Check nonadjacent squares too. } - stealth = check_stealth(); + if (!crawl_state.arena) + stealth = check_stealth(); #ifdef DEBUG_STEALTH // Too annoying for regular diagnostics. @@ -3090,7 +3093,7 @@ static void _world_reacts() if (you.special_wield != SPWLD_NONE) special_wielded(); - if (one_chance_in(10)) + if (!crawl_state.arena && one_chance_in(10)) { // this is instantaneous if (player_teleport() > 0 && one_chance_in(100 / player_teleport())) @@ -3099,7 +3102,7 @@ static void _world_reacts() you_teleport_now( false, true ); // to new area of the Abyss } - if (env.cgrid(you.pos()) != EMPTY_CLOUD) + if (!crawl_state.arena && env.cgrid(you.pos()) != EMPTY_CLOUD) in_a_cloud(); if (you.level_type == LEVEL_DUNGEON && you.duration[DUR_TELEPATHY]) @@ -3149,7 +3152,7 @@ static void _world_reacts() viewwindow(true, true); - if (Options.stash_tracking) + if (Options.stash_tracking && !crawl_state.arena) { StashTrack.update_visible_stashes( Options.stash_tracking == STM_ALL ? StashTracker::ST_AGGRESSIVE @@ -3872,6 +3875,12 @@ static bool _initialise(void) } #endif + if (crawl_state.arena) + { + run_arena(); + end(0, false); + } + // Sets up a new game. const bool newc = new_game(); if (!newc) diff --git a/crawl-ref/source/arena.cc b/crawl-ref/source/arena.cc new file mode 100644 index 0000000000..95855fc328 --- /dev/null +++ b/crawl-ref/source/arena.cc @@ -0,0 +1,450 @@ +#include "AppHdr.h" + +#include "externs.h" +#include "arena.h" +#include "chardump.h" +#include "cio.h" +#include "dungeon.h" +#include "initfile.h" +#include "libutil.h" +#include "maps.h" +#include "message.h" +#include "mon-util.h" +#include "monstuff.h" +#include "monplace.h" +#include "output.h" +#include "skills2.h" +#include "spl-util.h" +#include "state.h" +#include "version.h" +#include "view.h" + +extern void world_reacts(); + +namespace arena +{ + // A faction is just a big list of monsters. Monsters will be dropped + // around the appropriate marker. + struct faction + { + std::string desc; + mons_list members; + bool friendly; + + faction(bool fr) : members(), friendly(fr) { } + + void place_at(const coord_def &pos); + + void clear() + { + members.clear(); + } + }; + + int total_trials = 0; + + int trials_done = 0; + int team_a_wins = 0; + bool allow_summons = true; + faction faction_a(true); + faction faction_b(false); + + void adjust_monsters() + { + if (!allow_summons) + { + for (int m = 0; m < MAX_MONSTERS; ++m) + { + monsters *mons(&menv[m]); + if (!mons->alive()) + continue; + + monster_spells &spells(mons->spells); + for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i) + { + spell_type sp = spells[i]; + if (spell_typematch(sp, SPTYP_SUMMONING)) + spells[i] = SPELL_NO_SPELL; + } + } + } + } + + void faction::place_at(const coord_def &pos) + { + ASSERT(in_bounds(pos)); + for (int i = 0, size = members.size(); i < size; ++i) + { + mons_spec spec = members.get_monster(i); + + if (friendly) + spec.attitude = ATT_FRIENDLY; + + for (int q = 0; q < spec.quantity; ++q) + { + const coord_def place = + find_newmons_square_contiguous(MONS_GIANT_BAT, pos, 6); + if (!in_bounds(place)) + break; + + const int imon = dgn_place_monster(spec, you.your_level, + place, false, true, false); + if (imon == -1) + end(1, false, "Failed to create monster at (%d,%d)", + place.x, place.y); + } + } + } + + void center_print(unsigned sz, std::string text, int number = -1) + { + if (number >= 0) + text = make_stringf("(%d) %s", number, text.c_str()); + + if (text.length() > sz) + text = text.substr(0, sz); + + int padding = (sz - text.length()) / 2 + text.length(); + cprintf("%*s", padding, text.c_str()); + } + + void setup_level() + { + dgn_reset_level(); + + for (int x = 0; x < GXM; ++x) + for (int y = 0; y < GYM; ++y) + grd[x][y] = DNGN_ROCK_WALL; + + unwind_bool gen(Generating_Level, true); + + typedef unwind_var< std::set > unwind_stringset; + + const unwind_stringset mtags(you.uniq_map_tags); + const unwind_stringset mnames(you.uniq_map_names); + + const map_def *map = random_map_for_tag("arena_level", false); + ASSERT(map); + dgn_place_map(map, true, true); + + if (!env.rock_colour) + env.rock_colour = CYAN; + if (!env.floor_colour) + env.floor_colour = LIGHTGREY; + } + + std::string find_monster_spec() + { + if (!SysEnv.arena_teams.empty()) + return (SysEnv.arena_teams); + throw std::string("No monsters specified for the arena."); + } + + void parse_faction(faction &fact, std::string spec) + throw (std::string) + { + fact.clear(); + fact.desc = spec; + + std::vector monsters = split_string(",", spec); + for (int i = 0, size = monsters.size(); i < size; ++i) + { + const std::string err = fact.members.add_mons(monsters[i], false); + if (!err.empty()) + throw err; + } + } + + void parse_monster_spec() + throw (std::string) + { + std::string spec = find_monster_spec(); + + allow_summons = !strip_tag(spec, "no_summons"); + + const int ntrials = strip_number_tag(spec, "t:"); + if (ntrials != TAG_UNFOUND && ntrials >= 1 && ntrials <= 99 + && !total_trials) + total_trials = ntrials; + + std::vector factions = split_string(" v ", spec); + + if (factions.size() == 1) + factions = split_string(" vs ", spec); + + if (factions.size() != 2) + throw make_stringf("Expected arena monster spec \"xxx v yyy\", " + "but got \"%s\"", spec.c_str()); + + try + { + parse_faction(faction_a, factions[0]); + parse_faction(faction_b, factions[1]); + } + catch (const std::string &err) + { + throw make_stringf("Bad monster spec \"%s\": %s", + spec.c_str(), + err.c_str()); + } + } + + void setup_monsters() + throw (std::string) + { + unwind_var< FixedVector > + uniq(you.unique_creatures); + + parse_monster_spec(); + coord_def place_a(dgn_find_feature_marker(DNGN_STONE_STAIRS_UP_I)); + coord_def place_b(dgn_find_feature_marker(DNGN_STONE_STAIRS_DOWN_I)); + faction_a.place_at(place_a); + faction_b.place_at(place_b); + adjust_monsters(); + } + + void show_fight_banner(bool after_fight = false) + { + int line = 1; + + cgotoxy(1, line++, GOTO_STAT); + textcolor(WHITE); + center_print(crawl_view.hudsz.x, + "Crawl " VER_NUM VER_QUAL " " VERSION_DETAIL); + line++; + + cgotoxy(1, line++, GOTO_STAT); + textcolor(YELLOW); + center_print(crawl_view.hudsz.x, faction_a.desc, + total_trials ? team_a_wins : -1); + cgotoxy(1, line++, GOTO_STAT); + textcolor(LIGHTGREY); + center_print(crawl_view.hudsz.x, "vs"); + cgotoxy(1, line++, GOTO_STAT); + textcolor(YELLOW); + center_print(crawl_view.hudsz.x, faction_b.desc, + total_trials ? trials_done - team_a_wins : -1); + + if (total_trials > 1 && trials_done < total_trials) + { + cgotoxy(1, line++, GOTO_STAT); + textcolor(BROWN); + center_print(crawl_view.hudsz.x, + make_stringf("Round %d of %d", + after_fight ? trials_done + : trials_done + 1, + total_trials)); + } + else + { + cgotoxy(1, line++, GOTO_STAT); + textcolor(BROWN); + clear_to_end_of_line(); + } + } + + void setup_others() + { + you.species = SP_HUMAN; + you.char_class = JOB_FIGHTER; + you.experience_level = 27; + + you.mutation[MUT_ACUTE_VISION] = 3; + + coord_def yplace(dgn_find_feature_marker(DNGN_ESCAPE_HATCH_UP)); + // Fix up the viewport. + you.moveto(yplace); + + strcpy(you.your_name, "Arena"); + + you.hp = you.hp_max = 99; + you.your_level = 20; + + Options.show_gold_turns = false; + + show_fight_banner(); + } + + void expand_mlist(int exp) + { + crawl_view.mlistp.y -= exp; + crawl_view.mlistsz.y += exp; + } + + void setup_fight() + throw (std::string) + { + //no_messages mx; + setup_level(); + + // Monster set up may block waiting for matchups. + setup_monsters(); + + setup_others(); + } + + // Returns true as long as at least one member of each faction is alive. + bool fight_is_on() + { + bool found_friend = false; + bool found_enemy = false; + for (int i = 0; i < MAX_MONSTERS; ++i) + { + const monsters *mons(&menv[i]); + if (mons->alive()) + { + if (mons->attitude == ATT_FRIENDLY) + found_friend = true; + else if (mons->attitude == ATT_HOSTILE) + found_enemy = true; + if (found_friend && found_enemy) + return (true); + } + } + return (false); + } + + void report_foes() + { + for (int i = 0; i < MAX_MONSTERS; ++i) + { + monsters *mons(&menv[i]); + if (mons->alive()) + { + if (mons->type == MONS_SIGMUND) + { + coord_def where; + if (mons->get_foe()) + where = mons->get_foe()->pos(); + mprf("%s (%d,%d) foe: %s (%d,%d)", + mons->name(DESC_PLAIN).c_str(), + mons->pos().x, mons->pos().y, + mons->get_foe()? mons->get_foe()->name(DESC_PLAIN).c_str() + : "(none)", + where.x, where.y); + } + } + } + } + + void fixup_foes() + { + for (int i = 0; i < MAX_MONSTERS; ++i) + { + monsters *mons(&menv[i]); + if (mons->alive()) + { + behaviour_event(mons, ME_DISTURB, MHITNOT, mons->pos()); + } + } + } + + bool friendlies_win() + { + for (int i = 0; i < MAX_MONSTERS; ++i) + { + monsters *mons(&menv[i]); + if (mons->alive()) + return (mons->attitude == ATT_FRIENDLY); + } + return (false); + } + + void do_fight() + { + mesclr(true); + { + cursor_control coff(false); + while (fight_is_on()) + { + if (kbhit() && getch() == ESCAPE) + end(0, false, "Canceled contest at user request"); + + viewwindow(true, false); + unwind_var pos(you.position); + // Move hero offscreen. + you.position.y = -1; + you.time_taken = 10; + //report_foes(); + world_reacts(); + delay(Options.arena_delay); + mesclr(); + } + viewwindow(true, false); + } + + mesclr(); + + const bool team_a_won = friendlies_win(); + + trials_done++; + + if (team_a_won) + team_a_wins++; + + show_fight_banner(true); + + mprf("Winner: %s!", + team_a_won ? faction_a.desc.c_str() : faction_b.desc.c_str()); + } + + void global_setup() + { + expand_mlist(5); + } + + void write_results() + { + if (FILE *f = fopen("arena.result", "w")) + { + fprintf(f, "%d-%d\n", team_a_wins, trials_done - team_a_wins); + fclose(f); + } + } + + void write_error(const std::string &error) + { + if (FILE *f = fopen("arena.result", "w")) + { + fprintf(f, "err: %s\n", error.c_str()); + fclose(f); + } + } + + void simulate() + { + init_level_connectivity(); + do + { + try + { + setup_fight(); + } + catch (const std::string &error) + { + write_error(error); + end(0, false, "%s", error.c_str()); + } + do_fight(); + + if (trials_done < total_trials) + delay(Options.arena_delay * 8); + } while (trials_done < total_trials); + + if (total_trials > 0) + { + mprf("Final score: %s (%d); %s (%d)", + faction_a.desc.c_str(), team_a_wins, + faction_b.desc.c_str(), trials_done - team_a_wins); + delay(Options.arena_delay * 8); + } + + write_results(); + } +} + +void run_arena() +{ + arena::global_setup(); + arena::simulate(); +} diff --git a/crawl-ref/source/arena.h b/crawl-ref/source/arena.h new file mode 100644 index 0000000000..864e643d24 --- /dev/null +++ b/crawl-ref/source/arena.h @@ -0,0 +1,8 @@ +#ifndef ARENA_H +#define ARENA_H + +#include "AppHdr.h" + +void run_arena(); + +#endif diff --git a/crawl-ref/source/dat/arena.des b/crawl-ref/source/dat/arena.des new file mode 100644 index 0000000000..1e4a708a83 --- /dev/null +++ b/crawl-ref/source/dat/arena.des @@ -0,0 +1,26 @@ +NAME: arena_level +TAGS: arena_level no_mons_gen +MARKER: A = feat: stone_stairs_up_i +MARKER: B = feat: stone_stairs_down_i +MARKER: O = feat: escape_hatch_up +SUBST: A = ., B = ., O = . +ORIENT: encompass +MAP +XXXXXXXXXXXXXXXXX +X...............X +X.......B.......X +X...............X +X...............X +X...............X +X...............X +X...............X +X.......O.......X +X...............X +X...............X +X...............X +X...............X +X...............X +X.......A.......X +X...............X +XXXXXXXXXXXXXXXXX +ENDMAP diff --git a/crawl-ref/source/dat/clua/loadmaps.lua b/crawl-ref/source/dat/clua/loadmaps.lua index fcabf8907d..e5396aef14 100644 --- a/crawl-ref/source/dat/clua/loadmaps.lua +++ b/crawl-ref/source/dat/clua/loadmaps.lua @@ -13,6 +13,8 @@ local des_files = { -- Example vaults, included here so that Crawl will syntax-check them "didact.des", + "arena.des", + "altar.des", "bazaar.des", "entry.des", "elf.des", "float.des", "hells.des", "hive.des", "icecave.des", "lab.des", "lair.des", "large.des", "layout.des", "mini.des", "minitomb.des", "orc.des", "pan.des", "sewer.des", "temple.des", diff --git a/crawl-ref/source/directn.h b/crawl-ref/source/directn.h index 3d95e9f8d7..1759496612 100644 --- a/crawl-ref/source/directn.h +++ b/crawl-ref/source/directn.h @@ -74,6 +74,11 @@ public: return viewp + viewhalfsz; } + coord_def glosc() const + { + return (glos1 + glos2) / 2; + } + bool in_grid_los(const coord_def &c) const { return (c.x >= glos1.x && c.x <= glos2.x diff --git a/crawl-ref/source/dungeon.cc b/crawl-ref/source/dungeon.cc index ed876851e6..b16ec0e371 100644 --- a/crawl-ref/source/dungeon.cc +++ b/crawl-ref/source/dungeon.cc @@ -86,7 +86,6 @@ coord_def spec_room::random_spot() const // DUNGEON BUILDERS static void _build_dungeon_level(int level_number, int level_type); -static void _reset_level(); static bool _valid_dungeon_level(int level_number, int level_type); static bool _find_in_area(int sx, int sy, int ex, int ey, @@ -296,7 +295,7 @@ bool builder(int level_number, int level_type) mapgen_report_map_build_start(); #endif - _reset_level(); + dgn_reset_level(); // If we're getting low on available retries, disable random vaults // and minivaults (special levels will still be placed). @@ -885,7 +884,7 @@ static bool _valid_dungeon_level(int level_number, int level_type) return (true); } -static void _reset_level() +void dgn_reset_level() { dgn_level_vetoed = false; Level_Unique_Maps.clear(); @@ -4218,7 +4217,7 @@ bool dgn_place_map(const map_def *mdef, bool clobber, bool make_no_exits, { // For encompass maps, clear the entire level. unwind_bool levgen(Generating_Level, true); - _reset_level(); + dgn_reset_level(); dungeon_events.clear(); const bool res = dgn_place_map(mdef, clobber, make_no_exits, where); @@ -4629,9 +4628,9 @@ static void _dgn_give_mon_spec_items(mons_spec &mspec, } -bool dgn_place_monster(mons_spec &mspec, - int monster_level, const coord_def& where, - bool force_pos, bool generate_awake, bool patrolling) +int dgn_place_monster(mons_spec &mspec, + int monster_level, const coord_def& where, + bool force_pos, bool generate_awake, bool patrolling) { if (mspec.mid != -1) { @@ -4655,8 +4654,11 @@ bool dgn_place_monster(mons_spec &mspec, { // Don't place a unique monster a second time. // (Boris is handled specially.) - if (mons_is_unique(mid) && you.unique_creatures[mid]) - return (false); + if (mons_is_unique(mid) && you.unique_creatures[mid] + && !crawl_state.arena) + { + return (-1); + } const int montype = mons_class_is_zombified(mid) ? mspec.monbase : mid; @@ -4701,9 +4703,31 @@ bool dgn_place_monster(mons_spec &mspec, mg.power = monster_level; mg.behaviour = (m_generate_awake) ? BEH_WANDER : BEH_SLEEP; + switch (mspec.attitude) + { + case ATT_FRIENDLY: + mg.behaviour = BEH_FRIENDLY; + break; + case ATT_NEUTRAL: + mg.behaviour = BEH_NEUTRAL; + break; + case ATT_GOOD_NEUTRAL: + mg.behaviour = BEH_GOOD_NEUTRAL; + break; + default: + break; + } mg.base_type = mspec.monbase; mg.number = mspec.number; mg.colour = mspec.colour; + + coord_def place(where); + if (!force_pos && mgrd(place) != NON_MONSTER + && mg.cls != RANDOM_MONSTER && mg.cls < NUM_MONSTERS) + { + place = find_newmons_square_contiguous(mg.cls, where, 6); + } + mg.pos = where; if (mons_class_is_zombified(mg.base_type)) @@ -4723,9 +4747,9 @@ bool dgn_place_monster(mons_spec &mspec, const int mindex = place_monster(mg, true); if (mindex != -1 && mspec.items.size() > 0) _dgn_give_mon_spec_items(mspec, mindex, mid, monster_level); - return (mindex != -1); + return (mindex); } - return (false); + return (-1); } static bool _dgn_place_monster( const vault_placement &place, mons_spec &mspec, @@ -4737,8 +4761,8 @@ static bool _dgn_place_monster( const vault_placement &place, mons_spec &mspec, const bool patrolling = mspec.patrolling || place.map.has_tag("patrolling"); - return dgn_place_monster(mspec, monster_level, where, false, - generate_awake, patrolling); + return (-1 != dgn_place_monster(mspec, monster_level, where, false, + generate_awake, patrolling)); } static bool _dgn_place_one_monster( const vault_placement &place, @@ -7702,7 +7726,7 @@ static coord_def _dgn_find_closest_to_stone_stairs(coord_def base_pos) return (np.nearest); } -static coord_def _dgn_find_feature_marker(dungeon_feature_type feat) +coord_def dgn_find_feature_marker(dungeon_feature_type feat) { std::vector markers = env.markers.get_all(); for (int i = 0, size = markers.size(); i < size; ++i) @@ -7720,7 +7744,7 @@ static coord_def _dgn_find_feature_marker(dungeon_feature_type feat) static coord_def _dgn_find_labyrinth_entry_point() { - return (_dgn_find_feature_marker(DNGN_ENTER_LABYRINTH)); + return (dgn_find_feature_marker(DNGN_ENTER_LABYRINTH)); } coord_def dgn_find_nearby_stair(dungeon_feature_type stair_to_find, @@ -7736,7 +7760,7 @@ coord_def dgn_find_nearby_stair(dungeon_feature_type stair_to_find, if (stair_to_find == DNGN_EXIT_PORTAL_VAULT) { - const coord_def pos(_dgn_find_feature_marker(stair_to_find)); + const coord_def pos(dgn_find_feature_marker(stair_to_find)); if (in_bounds(pos)) { if (map_marker *marker = env.markers.find(pos, MAT_FEATURE)) @@ -7761,7 +7785,7 @@ coord_def dgn_find_nearby_stair(dungeon_feature_type stair_to_find, if (stair_to_find == DNGN_STONE_ARCH) { - const coord_def pos(_dgn_find_feature_marker(stair_to_find)); + const coord_def pos(dgn_find_feature_marker(stair_to_find)); if (in_bounds(pos) && grd(pos) == stair_to_find) return (pos); } @@ -7781,7 +7805,7 @@ coord_def dgn_find_nearby_stair(dungeon_feature_type stair_to_find, if (stair_to_find == your_branch().exit_stairs) { - const coord_def pos(_dgn_find_feature_marker(DNGN_STONE_STAIRS_UP_I)); + const coord_def pos(dgn_find_feature_marker(DNGN_STONE_STAIRS_UP_I)); if (in_bounds(pos) && grd(pos) == stair_to_find) return (pos); } @@ -7922,7 +7946,7 @@ coord_def dgn_find_nearby_stair(dungeon_feature_type stair_to_find, } // Last attempt: look for marker. - const coord_def pos(_dgn_find_feature_marker(stair_to_find)); + const coord_def pos(dgn_find_feature_marker(stair_to_find)); if (in_bounds(pos)) return (pos); diff --git a/crawl-ref/source/dungeon.h b/crawl-ref/source/dungeon.h index bcfa1d6d16..04c84cdfd3 100644 --- a/crawl-ref/source/dungeon.h +++ b/crawl-ref/source/dungeon.h @@ -324,6 +324,8 @@ void write_level_connectivity(writer &th); bool builder(int level_number, int level_type); +coord_def dgn_find_feature_marker(dungeon_feature_type feat); + // Set floor/wall colour based on the mons_alloc array. Used for // Abyss and Pan. void dgn_set_colours_from_monsters(); @@ -344,10 +346,10 @@ coord_def dgn_find_nearby_stair(dungeon_feature_type stair_to_find, coord_def base_pos, bool find_closest); class mons_spec; -bool dgn_place_monster(mons_spec &mspec, - int monster_level, const coord_def& where, - bool force_pos = false, bool generate_awake = false, - bool patrolling = false); +int dgn_place_monster(mons_spec &mspec, + int monster_level, const coord_def& where, + bool force_pos = false, bool generate_awake = false, + bool patrolling = false); class item_list; void dgn_place_multiple_items(item_list &list, @@ -360,6 +362,8 @@ bool unset_level_flags(unsigned long flags, bool silent = false); void dgn_set_lt_callback(std::string level_type_name, std::string callback_name); +void dgn_reset_level(); + // Returns true if the given square is okay for use by any character, // but always false for squares in non-transparent vaults. This // function returns sane results only immediately after dungeon generation diff --git a/crawl-ref/source/externs.h b/crawl-ref/source/externs.h index 97c91451fb..30d890de26 100644 --- a/crawl-ref/source/externs.h +++ b/crawl-ref/source/externs.h @@ -2022,6 +2022,8 @@ public: bool pickup_dropped; // Pickup dropped objects int travel_delay; // How long to pause between travel moves + int arena_delay; + // Messages that stop travel std::vector travel_stop_message; std::vector force_more_message; diff --git a/crawl-ref/source/fight.cc b/crawl-ref/source/fight.cc index 1f9c08369e..9f8a75d8ea 100644 --- a/crawl-ref/source/fight.cc +++ b/crawl-ref/source/fight.cc @@ -51,6 +51,7 @@ #include "spells4.h" #include "spl-mis.h" #include "spl-util.h" +#include "state.h" #include "stuff.h" #include "transfor.h" #include "traps.h" @@ -369,9 +370,10 @@ void melee_attack::init_attack() defender_shield = defender->shield(); water_attack = is_water_attack(attacker, defender); - attacker_visible = attacker->visible(); + attacker_visible = attacker->visible() || crawl_state.arena; attacker_invisible = (!attacker_visible && see_grid(attacker->pos())); - defender_visible = (defender && defender->visible()); + defender_visible = (defender && + (defender->visible() || crawl_state.arena)); defender_invisible = (!defender_visible && defender && see_grid(defender->pos())); needs_message = (attacker_visible || defender_visible); diff --git a/crawl-ref/source/initfile.cc b/crawl-ref/source/initfile.cc index 5032753e8b..7cfcbe5c3e 100644 --- a/crawl-ref/source/initfile.cc +++ b/crawl-ref/source/initfile.cc @@ -710,6 +710,8 @@ void game_options::reset_options() travel_delay = 20; travel_stair_cost = 500; + arena_delay = 600; + // Sort only pickup menus by default. sort_menus.clear(); set_menu_sort("pickup: true"); @@ -3280,6 +3282,7 @@ enum commandline_option_type { CLO_MORGUE, CLO_MACRO, CLO_MAPSTAT, + CLO_ARENA, CLO_NOPS }; @@ -3287,7 +3290,7 @@ enum commandline_option_type { static const char *cmd_ops[] = { "scores", "name", "race", "class", "pizza", "plain", "dir", "rc", "rcdir", "tscores", "vscores", "scorefile", "morgue", "macro", - "mapstat" + "mapstat", "arena" }; const int num_cmd_ops = CLO_NOPS; @@ -3421,6 +3424,15 @@ bool parse_args( int argc, char **argv, bool rc_only ) SysEnv.map_gen_iters = 100; break; + case CLO_ARENA: + crawl_state.arena = true; + if (next_is_param) + { + SysEnv.arena_teams = next_arg; + nextUsed = true; + } + break; + case CLO_MACRO: if (!next_is_param) return (false); diff --git a/crawl-ref/source/initfile.h b/crawl-ref/source/initfile.h index eae3c3d03c..471eba7387 100644 --- a/crawl-ref/source/initfile.h +++ b/crawl-ref/source/initfile.h @@ -74,6 +74,8 @@ public: int map_gen_iters; + std::string arena_teams; + public: void add_rcdir(const std::string &dir); }; diff --git a/crawl-ref/source/makefile.obj b/crawl-ref/source/makefile.obj index dc1bce8539..8e02163420 100644 --- a/crawl-ref/source/makefile.obj +++ b/crawl-ref/source/makefile.obj @@ -4,6 +4,7 @@ OBJECTS = \ abl-show.o \ abyss.o \ acr.o \ +arena.o \ beam.o \ branch.o \ chardump.o \ diff --git a/crawl-ref/source/mapdef.cc b/crawl-ref/source/mapdef.cc index af02c2b429..f9b0c14001 100644 --- a/crawl-ref/source/mapdef.cc +++ b/crawl-ref/source/mapdef.cc @@ -2090,6 +2090,22 @@ mons_list::mons_spec_slot mons_list::parse_mons_spec(std::string spec) mspec.patrolling = strip_tag(mon_str, "patrolling"); mspec.band = strip_tag(mon_str, "band"); + if (!mon_str.empty() && isdigit(mon_str[0])) + { + // Look for space after initial digits. + std::string::size_type pos = + mon_str.find_first_not_of("0123456789"); + if (pos != std::string::npos && mon_str[pos] == ' ') + { + const std::string mcount = mon_str.substr(0, pos); + const int count = atoi(mcount.c_str()); + if (count >= 1 && count <= 99) + mspec.quantity = count; + + mon_str = mon_str.substr(pos); + } + } + // place:Elf:7 to choose monsters appropriate for that level, // for example. const std::string place = strip_tag_prefix(mon_str, "place:"); @@ -2179,8 +2195,9 @@ mons_list::mons_spec_slot mons_list::parse_mons_spec(std::string spec) } else if (mons_class_itemuse(mid) < MONUSE_STARTING_EQUIPMENT) { - error = make_stringf("Monster '%s' can't use items.", - mon_str.c_str()); + if (mid != MONS_DANCING_WEAPON || mspec.items.size() > 1) + error = make_stringf("Monster '%s' can't use items.", + mon_str.c_str()); } } diff --git a/crawl-ref/source/mapdef.h b/crawl-ref/source/mapdef.h index 2d43e59519..faca8b7c0a 100644 --- a/crawl-ref/source/mapdef.h +++ b/crawl-ref/source/mapdef.h @@ -417,7 +417,9 @@ class mons_spec int mid; level_id place; monster_type monbase; // Base monster for zombies and dracs. + mon_attitude_type attitude; int number; // Head count for hydras + int quantity; // Number of monsters (usually 1). int genweight, mlevel; bool fix_mons; bool generate_awake; @@ -432,9 +434,10 @@ class mons_spec int num = 0, int gw = 10, int ml = 0, bool _fixmons = false, bool awaken = false, bool patrol = false) - : mid(id), place(), monbase(base), number(num), genweight(gw), - mlevel(ml), fix_mons(_fixmons), generate_awake(awaken), - patrolling(false), band(false), colour(BLACK), items() + : mid(id), place(), monbase(base), attitude(ATT_HOSTILE), number(num), + quantity(1), genweight(gw), mlevel(ml), fix_mons(_fixmons), + generate_awake(awaken), patrolling(false), band(false), + colour(BLACK), items() { } }; diff --git a/crawl-ref/source/message.cc b/crawl-ref/source/message.cc index f47545b7a1..1efdc0f2a5 100644 --- a/crawl-ref/source/message.cc +++ b/crawl-ref/source/message.cc @@ -909,6 +909,13 @@ void more(void) } #endif + if (crawl_state.arena) + { + delay(Options.arena_delay); + mesclr(); + return; + } + if (crawl_state.is_replaying_keys() || autoclear_more) { mesclr(); diff --git a/crawl-ref/source/mon-util.cc b/crawl-ref/source/mon-util.cc index 9314094e1f..2ac2063a78 100644 --- a/crawl-ref/source/mon-util.cc +++ b/crawl-ref/source/mon-util.cc @@ -46,6 +46,7 @@ #include "shopping.h" // for item values #include "spells3.h" #include "spl-util.h" +#include "state.h" #include "stuff.h" #include "terrain.h" #include "tiles.h" @@ -1972,15 +1973,14 @@ static std::string _str_monam(const monsters& mon, description_level_type desc, // (Uniques don't get this, because their names are proper nouns.) if (!mons_is_unique(mon.type)) { + const bool use_your = !crawl_state.arena && mons_friendly(&mon); switch (desc) { case DESC_CAP_THE: - result = (mons_friendly(&mon) ? "Your " - : "The "); + result = (use_your ? "Your " : "The "); break; case DESC_NOCAP_THE: - result = (mons_friendly(&mon) ? "your " - : "the "); + result = (use_your ? "your " : "the "); break; case DESC_CAP_A: result = "A "; @@ -7870,7 +7870,9 @@ std::string do_mon_str_replacements(const std::string &in_msg, msg = replace_all(msg, "@the_monster@", name); msg = replace_all(msg, "@The_monster@", name); } - else if (monster->attitude == ATT_FRIENDLY && !mons_is_unique(monster->type) + else if (monster->attitude == ATT_FRIENDLY + && !mons_is_unique(monster->type) + && !crawl_state.arena && player_monster_visible(monster)) { nocap = DESC_PLAIN; diff --git a/crawl-ref/source/monplace.cc b/crawl-ref/source/monplace.cc index ad7701530e..a9bfdeb5f8 100644 --- a/crawl-ref/source/monplace.cc +++ b/crawl-ref/source/monplace.cc @@ -23,6 +23,7 @@ #include "mon-util.h" #include "player.h" #include "religion.h" +#include "state.h" #include "stuff.h" #include "spells4.h" #include "terrain.h" @@ -226,6 +227,9 @@ static void _hell_spawn_random_monsters() // one_chance_in(value) checks with the new x_chance_in_y(5, value). (jpeg) void spawn_random_monsters() { + if (crawl_state.arena) + return; + #ifdef DEBUG_MON_CREATION mpr("in spawn_random_monsters()", MSGCH_DIAGNOSTICS); #endif diff --git a/crawl-ref/source/monspeak.cc b/crawl-ref/source/monspeak.cc index 2619563f2b..86de314347 100644 --- a/crawl-ref/source/monspeak.cc +++ b/crawl-ref/source/monspeak.cc @@ -36,6 +36,7 @@ #include "religion.h" #include "spells2.h" #include "spells4.h" +#include "state.h" #include "stuff.h" #include "view.h" @@ -266,7 +267,7 @@ bool mons_speaks(const monsters *monster) prefixes.push_back("neutral"); } - else if (mons_friendly(monster)) + else if (mons_friendly(monster) && !crawl_state.arena) prefixes.push_back("friendly"); else prefixes.push_back("hostile"); diff --git a/crawl-ref/source/monstuff.cc b/crawl-ref/source/monstuff.cc index ea3118f5ec..f630cbeb59 100644 --- a/crawl-ref/source/monstuff.cc +++ b/crawl-ref/source/monstuff.cc @@ -509,7 +509,8 @@ static void _give_adjusted_experience(monsters *monster, killer_type killer, // Give a message for monsters dying out of sight. if (need_xp_msg && exp_gain > 0 - && (!mons_near(monster) || !you.can_see(monster))) + && (!mons_near(monster) || !you.can_see(monster)) + && !crawl_state.arena) { mpr("You feel a bit more experienced."); } @@ -3403,6 +3404,41 @@ static bool _handle_monster_patrolling(monsters *mon) return (false); } +static void _arena_set_foe(monsters *mons) +{ + const int mind = monster_index(mons); + + int nearest = -1; + int best_distance = -1; + for (int i = 0; i < MAX_MONSTERS; ++i) + { + if (mind == i) + continue; + + const monsters *other(&menv[i]); + if (!other->alive() || mons_aligned(mind, i)) + continue; + + const int distance = grid_distance(mons->pos(), other->pos()); + if (best_distance == -1 || distance < best_distance) + { + best_distance = distance; + nearest = i; + } + } + + if (nearest != -1) + { + mons->foe = nearest; + mons->target = menv[nearest].pos(); + } + else + { + mons->foe = MHITNOT; + mons->target = mons->pos(); + } +} + //--------------------------------------------------------------- // // handle_behaviour @@ -3441,6 +3477,7 @@ static void _handle_behaviour(monsters *mon) bool patrolling = mon->is_patrolling(); static std::vector e; static int e_index = -1; + // Check for confusion -- early out. if (mon->has_ench(ENCH_CONFUSION)) { @@ -3448,6 +3485,15 @@ static void _handle_behaviour(monsters *mon) return; } + if (crawl_state.arena) + { + if (!mon->get_foe() || one_chance_in(3)) + mon->foe = MHITNOT; + if (mon->foe == MHITNOT || mon->foe == MHITYOU) + _arena_set_foe(mon); + return; + } + if (mons_wall_shielded(mon) && grid_is_solid(mon->pos())) { // Monster is safe, so it's behaviour can be simplified to fleeing. @@ -4060,7 +4106,7 @@ bool simple_monster_message(const monsters *monster, const char *event, description_level_type descrip) { - if (mons_near( monster ) + if ((mons_near( monster ) || crawl_state.arena) && (channel == MSGCH_MONSTER_SPELL || channel == MSGCH_FRIEND_SPELL || player_monster_visible(monster))) { @@ -4250,7 +4296,9 @@ static void _handle_movement(monsters *monster) delta = you.pos() - monster->pos(); } else + { delta = monster->target - monster->pos(); + } // Move the monster. mmov.x = (delta.x > 0) ? 1 : ((delta.x < 0) ? -1 : 0); @@ -6526,6 +6574,7 @@ static void _handle_monster_move(int i, monsters *monster) } _handle_behaviour(monster); + ASSERT(!crawl_state.arena || monster->foe != MHITYOU); // Submerging monsters will hide from clouds. if (cloud_num != EMPTY_CLOUD && _mons_avoids_cloud(monster, cl_type) diff --git a/crawl-ref/source/mstuff2.cc b/crawl-ref/source/mstuff2.cc index 90e32f49e6..bb70bb8907 100644 --- a/crawl-ref/source/mstuff2.cc +++ b/crawl-ref/source/mstuff2.cc @@ -166,10 +166,6 @@ void mons_cast(monsters *monster, bolt &pbolt, spell_type spell_cast, // Targeted spells need a valid target. ASSERT(!(flags & SPFLAG_TARGETING_MASK) || in_bounds(pbolt.target)); - - // Don't target harmful spells at self unless confused. - ASSERT(monster->pos() != pbolt.target || monster->confused() - || (flags & (SPFLAG_HELPFUL | SPFLAG_ESCAPE | SPFLAG_RECOVERY))); #endif if (do_noise) diff --git a/crawl-ref/source/output.cc b/crawl-ref/source/output.cc index 22a9e1a186..b9ebfd1c6a 100644 --- a/crawl-ref/source/output.cc +++ b/crawl-ref/source/output.cc @@ -1500,6 +1500,9 @@ void get_monster_pane_info(std::vector& mons) // they have to be consolidated, and 1 otherwise. int update_monster_pane() { + if (!map_bounds(you.pos())) + return (-1); + const int max_print = crawl_view.mlistsz.y; textbackground(BLACK); diff --git a/crawl-ref/source/player.cc b/crawl-ref/source/player.cc index f04a9e4094..1bc4a67654 100644 --- a/crawl-ref/source/player.cc +++ b/crawl-ref/source/player.cc @@ -2919,6 +2919,9 @@ void forget_map(unsigned char chance_forgotten, bool force) void gain_exp( unsigned int exp_gained, unsigned int* actual_gain, unsigned int* actual_avail_gain) { + if (crawl_state.arena) + return; + if (player_equip_ego_type( EQ_BODY_ARMOUR, SPARM_ARCHMAGI )) exp_gained = div_rand_round( exp_gained, 4 ); @@ -6941,6 +6944,9 @@ bool player::visible_to(const actor *looker) const bool player::can_see(const actor *target) const { + if (crawl_state.arena) + return target->visible_to(this); + if (this == target) return visible_to(target); diff --git a/crawl-ref/source/state.cc b/crawl-ref/source/state.cc index f6eafa288b..d076bc8ebf 100644 --- a/crawl-ref/source/state.cc +++ b/crawl-ref/source/state.cc @@ -25,7 +25,7 @@ game_state::game_state() : mouse_enabled(false), waiting_for_command(false), terminal_resized(false), io_inited(false), need_save(false), saving_game(false), updating_scores(false), seen_hups(0), - map_stat_gen(false), unicode_ok(false), glyph2strfn(NULL), + map_stat_gen(false), arena(false), unicode_ok(false), glyph2strfn(NULL), multibyte_strlen(NULL), terminal_resize_handler(NULL), terminal_resize_check(NULL), doing_prev_cmd_again(false), prev_cmd(CMD_NO_CMD), repeat_cmd(CMD_NO_CMD), cmd_repeat_count(0), diff --git a/crawl-ref/source/state.h b/crawl-ref/source/state.h index 24c4fc45b8..fe8ab01938 100644 --- a/crawl-ref/source/state.h +++ b/crawl-ref/source/state.h @@ -42,6 +42,8 @@ struct game_state bool map_stat_gen; // Set if we're generating stats on maps. + bool arena; // Set if we're in arena mode. + bool unicode_ok; // Is unicode support available? std::string (*glyph2strfn)(unsigned glyph); diff --git a/crawl-ref/source/stuff.cc b/crawl-ref/source/stuff.cc index ee9a82986b..320fb7c66f 100644 --- a/crawl-ref/source/stuff.cc +++ b/crawl-ref/source/stuff.cc @@ -766,7 +766,7 @@ void end(int exit_code, bool print_error, const char *format, ...) } #if defined(WIN32CONSOLE) || defined(DOS) || defined(DGL_PAUSE_AFTER_ERROR) - if (exit_code) + if (exit_code && !crawl_state.arena) { fprintf(stderr, "Hit Enter to continue...\n"); getchar(); diff --git a/crawl-ref/source/view.cc b/crawl-ref/source/view.cc index 93a918605d..ce20205bca 100644 --- a/crawl-ref/source/view.cc +++ b/crawl-ref/source/view.cc @@ -202,6 +202,8 @@ void set_envmap_col( int x, int y, int colour ) bool is_sanctuary(const coord_def& p) { + if (!map_bounds(p)) + return (false); return (testbits(env.map(p).property, FPROP_SANCTUARY_1) || testbits(env.map(p).property, FPROP_SANCTUARY_2)); } @@ -1183,7 +1185,7 @@ void force_monster_shout(monsters* monster) inline static bool _update_monster_grid(const monsters *monster) { - const coord_def e = monster->pos() - you.pos() + coord_def(9,9); + const coord_def e = monster->pos() - crawl_view.glosc() + coord_def(9,9); if (!player_monster_visible( monster )) { @@ -1476,11 +1478,13 @@ inline static void _update_item_grid(const coord_def &gp, const coord_def &ep) void item_grid() { - for (radius_iterator ri(you.pos(), LOS_RADIUS, true, false); ri; ++ri) + const coord_def c(crawl_view.glosc()); + for (radius_iterator ri(c, LOS_RADIUS, true, false); + ri; ++ri) { if (igrd(*ri) != NON_ITEM) { - const coord_def ep = *ri - you.pos() + coord_def(9, 9); + const coord_def ep = *ri - c + coord_def(9, 9); if (env.show(ep)) _update_item_grid(*ri, ep); } @@ -1505,7 +1509,8 @@ void get_mons_glyph( const monsters *mons, unsigned *glych, inline static void _update_cloud_grid(int cloudno) { int which_colour = LIGHTGREY; - const coord_def e = env.cloud[cloudno].pos - you.pos() + coord_def(9,9); + const coord_def e = env.cloud[cloudno].pos - crawl_view.glosc() + + coord_def(9,9); switch (env.cloud[cloudno].type) { @@ -2813,6 +2818,18 @@ void losight(env_show_grid &sh, // clear out sh sh.init(0); + if (crawl_state.arena) + { + for (int y = -ENV_SHOW_OFFSET; y <= ENV_SHOW_OFFSET; ++y) + for (int x = -ENV_SHOW_OFFSET; x <= ENV_SHOW_OFFSET; ++x) + { + const coord_def pos = center + coord_def(x, y); + if (map_bounds(pos)) + sh[x + sh_xo][y + sh_yo] = gr(pos); + } + return; + } + const unsigned int num_cellrays = compressed_ray_x.size(); const unsigned int num_words = (num_cellrays + LONGSIZE - 1) / LONGSIZE; @@ -3912,6 +3929,9 @@ bool mons_near(const monsters *monster, unsigned short foe) if (foe == MHITYOU) { + if (crawl_state.arena) + return (true); + if ( grid_distance(monster->pos(), you.pos()) <= LOS_RADIUS ) { const coord_def diff = monster->pos() - you.pos() + coord_def(9,9); @@ -3971,7 +3991,8 @@ bool see_grid( const env_show_grid &show, // Answers the question: "Is a grid within character's line of sight?" bool see_grid( const coord_def &p ) { - return see_grid(env.show, you.pos(), p); + return (crawl_state.arena && crawl_view.in_grid_los(p)) + || see_grid(env.show, you.pos(), p); } // Answers the question: "Would a grid be within character's line of sight, @@ -4932,8 +4953,11 @@ static void _update_env_show(const coord_def &gp, const coord_def &ep) _update_item_grid(gp, ep); const int cloud = env.cgrid(gp); - if (cloud != EMPTY_CLOUD && env.cloud[cloud].type != CLOUD_NONE) + if (cloud != EMPTY_CLOUD && env.cloud[cloud].type != CLOUD_NONE + && env.cloud[cloud].pos == gp) + { _update_cloud_grid(cloud); + } const monsters *mons = monster_at(gp); if (mons && mons->alive()) @@ -5064,11 +5088,14 @@ void viewwindow(bool draw_it, bool do_updates) int count_x, count_y; - losight( env.show, grd, you.pos() ); // Must be done first. + if (map_bounds(you.pos())) + { + losight( env.show, grd, you.pos() ); // Must be done first. - // What would be visible, if all of the translucent walls were - // made opaque. - losight( env.no_trans_show, grd, you.pos(), true ); + // What would be visible, if all of the translucent walls were + // made opaque. + losight( env.no_trans_show, grd, you.pos(), true ); + } #ifdef USE_TILE tile_draw_floor(); @@ -5180,7 +5207,7 @@ void viewwindow(bool draw_it, bool do_updates) tileb[bufcount + 1] = bg | tile_unseen_flag(gc); #endif } - else if (gc == you.pos()) + else if (gc == you.pos() && !crawl_state.arena) { int object = env.show(ep); unsigned short colour = env.show_col(ep); -- cgit v1.2.3-54-g00ecf