/** * @file * @brief Misc function used to render the dungeon. **/ #include "AppHdr.h" #include "view.h" #include "shout.h" #include #include #include #include #include #include "act-iter.h" #include "attitude-change.h" #include "cio.h" #include "cloud.h" #include "clua.h" #include "colour.h" #include "coord.h" #include "coordit.h" #include "database.h" #include "delay.h" #include "dgn-overview.h" #include "directn.h" #include "effects.h" #include "env.h" #include "exclude.h" #include "feature.h" #include "files.h" #include "fprop.h" #include "godabil.h" #include "godconduct.h" #include "godpassive.h" #include "godwrath.h" #include "hints.h" #include "libutil.h" #include "message.h" #include "misc.h" #include "mon-behv.h" #include "mon-death.h" #include "mon-poly.h" #include "mon-util.h" #include "options.h" #include "notes.h" #include "output.h" #include "player.h" #include "random.h" #include "religion.h" #include "showsymb.h" #include "state.h" #include "strings.h" #include "target.h" #include "terrain.h" #include "tilemcache.h" #include "traps.h" #include "travel.h" #include "unicode.h" #include "viewchar.h" #include "viewmap.h" #include "xom.h" #ifdef USE_TILE #include "tilepick.h" #include "tilepick-p.h" #include "tileview.h" #endif //#define DEBUG_PANE_BOUNDS static bool _show_terrain = false; crawl_view_geometry crawl_view; bool handle_seen_interrupt(monster* mons, vector* msgs_buf) { activity_interrupt_data aid(mons); if (mons->seen_context) aid.context = mons->seen_context; // XXX: Hack to make the 'seen' monster spec flag work. else if (testbits(mons->flags, MF_WAS_IN_VIEW) || testbits(mons->flags, MF_SEEN)) { aid.context = SC_ALREADY_SEEN; } else aid.context = SC_NEWLY_SEEN; if (!mons_is_safe(mons) && !mons_class_flag(mons->type, M_NO_EXP_GAIN) || mons->type == MONS_BALLISTOMYCETE && mons->number > 0) { return interrupt_activity(AI_SEE_MONSTER, aid, msgs_buf); } seen_monster(mons); return false; } void flush_comes_into_view() { if (!you.turn_is_over || (!you_are_delayed() && !crawl_state.is_repeating_cmd())) { return; } monster* mon = crawl_state.which_mon_acting(); if (!mon || !mon->alive() || (mon->flags & MF_WAS_IN_VIEW) || !you.can_see(mon)) { return; } handle_seen_interrupt(mon); } void seen_monsters_react() { if (you.duration[DUR_TIME_STEP] || crawl_state.game_is_arena()) return; for (monster_near_iterator mi(you.pos()); mi; ++mi) { if ((mi->asleep() || mons_is_wandering(*mi)) && check_awaken(*mi) #ifdef EUCLIDEAN || you.prev_move.abs() == 2 && x_chance_in_y(2, 5) && check_awaken(*mi) #endif ) { behaviour_event(*mi, ME_ALERT, &you, you.pos(), false); // That might have caused a pacified monster to leave the level. if (!(*mi)->alive()) continue; handle_monster_shouts(*mi); } if (!mi->visible_to(&you)) continue; beogh_follower_convert(*mi); gozag_check_bribe(*mi); slime_convert(*mi); // Trigger Duvessa & Dowan upgrades if (mi->props.exists(ELVEN_ENERGIZE_KEY)) { mi->props.erase(ELVEN_ENERGIZE_KEY); elven_twin_energize(*mi); } #if TAG_MAJOR_VERSION == 34 else if (mi->props.exists(OLD_DUVESSA_ENERGIZE_KEY)) { mi->props.erase(OLD_DUVESSA_ENERGIZE_KEY); elven_twin_energize(*mi); } else if (mi->props.exists(OLD_DOWAN_ENERGIZE_KEY)) { mi->props.erase(OLD_DOWAN_ENERGIZE_KEY); elven_twin_energize(*mi); } #endif } } static string _desc_mons_type_map(map types) { string message; unsigned int count = 1; for (map::iterator it = types.begin(); it != types.end(); ++it) { string name; description_level_type desc; if (it->second == 1) desc = DESC_A; else desc = DESC_PLAIN; name = mons_type_name(it->first, desc); if (it->second > 1) { name = make_stringf("%d %s", it->second, pluralise(name).c_str()); } message += name; if (count == types.size() - 1) message += " and "; else if (count < types.size()) message += ", "; ++count; } return make_stringf("%s come into view.", message.c_str()); } static monster_type _mons_genus_keep_uniques(monster_type mc) { return mons_is_unique(mc) ? mc : mons_genus(mc); } /** * Monster list simplification * * When too many monsters come into view at once, we group the ones with the * same genus, starting with the most represented genus. * * @param types monster types and the number of monster for each type. * @param genera monster genera and the number of monster for each genus. */ static void _genus_factoring(map &types, map &genera) { monster_type genus = MONS_NO_MONSTER; int num = 0; map::iterator it; // Find the most represented genus. for (it = genera.begin(); it != genera.end(); ++it) if (it->second > num) { genus = it->first; num = it->second; } // The most represented genus has a single member. // No more factoring is possible, we're done. if (num == 1) { genera.clear(); return; } genera.erase(genus); it = types.begin(); do { if (_mons_genus_keep_uniques(it->first) != genus) { ++it; continue; } // This genus has a single monster type. Can't factor. if (it->second == num) return; types.erase(it++); } while (it != types.end()); types[genus] = num; } void update_monsters_in_view() { const unsigned int max_msgs = 4; int num_hostile = 0; vector msgs; vector monsters; for (monster_iterator mi; mi; ++mi) { if (mons_near(*mi)) { if (mi->attitude == ATT_HOSTILE) num_hostile++; if (mi->visible_to(&you)) { if (handle_seen_interrupt(*mi, &msgs)) monsters.push_back(*mi); seen_monster(*mi); } else mi->flags &= ~MF_WAS_IN_VIEW; } else if (!you.turn_is_over) { if (mi->flags & MF_WAS_IN_VIEW) { // Reset client id so the player doesn't know (for sure) he // has seen this monster before when it reappears. mi->reset_client_id(); } mi->flags &= ~MF_WAS_IN_VIEW; // If the monster hasn't been seen by the time that the player // gets control back then seen_context is out of date. mi->seen_context = SC_NONE; } } if (!msgs.empty()) { unsigned int size = monsters.size(); map types; map genera; // This is the plural for genus! const monster* target = NULL; for (unsigned int i = 0; i < size; ++i) { const monster_type type = monsters[i]->type; types[type]++; genera[_mons_genus_keep_uniques(type)]++; } if (size == 1) mprf(MSGCH_MONSTER_WARNING, "%s", msgs[0].c_str()); else { while (types.size() > max_msgs && !genera.empty()) _genus_factoring(types, genera); mprf(MSGCH_MONSTER_WARNING, "%s", _desc_mons_type_map(types).c_str()); } bool warning = false; string warning_msg = you_worship(GOD_ZIN) ? "Zin warns you:" : "Ashenzari warns you:"; warning_msg += " "; for (unsigned int i = 0; i < size; ++i) { const monster* mon = monsters[i]; if (!target && player_mutation_level(MUT_SCREAM) && x_chance_in_y(3 + player_mutation_level(MUT_SCREAM) * 3, 100)) { target = mon; } if (!mon->props.exists("ash_id") && !mon->props.exists("zin_id")) continue; monster_info mi(mon); if (warning) warning_msg += " "; else warning = true; string monname; if (size == 1) monname = mon->pronoun(PRONOUN_SUBJECTIVE); else if (mon->type == MONS_DANCING_WEAPON) monname = "There"; else if (types[mon->type] == 1) monname = mon->full_name(DESC_THE); else monname = mon->full_name(DESC_A); warning_msg += uppercase_first(monname); warning_msg += " is"; if (you_worship(GOD_ZIN)) { warning_msg += " a foul "; if (mon->has_ench(ENCH_GLOWING_SHAPESHIFTER)) warning_msg += "glowing "; warning_msg += "shapeshifter"; } else { warning_msg += get_monster_equipment_desc(mi, DESC_IDENTIFIED, DESC_NONE); } warning_msg += "."; } if (warning) { mprf(MSGCH_GOD, "%s", warning_msg.c_str()); #ifndef USE_TILE_LOCAL if (you_worship(GOD_ZIN)) update_monster_pane(); #endif } if (target) yell(target); if (player_under_penance(GOD_GOZAG)) { counted_monster_list mon_count; vector mons; for (unsigned int i = 0; i < monsters.size(); i++) { monster *mon = monsters[i]; if (mon->wont_attack()) continue; int bribability = gozag_type_bribable(mon->type, true); if (bribability && x_chance_in_y(bribability, GOZAG_MAX_BRIBABILITY)) { mon_count.add(mon); mons.push_back(mon); } } if (mons.size() > 0) { string msg = make_stringf("Gozag incites %s against you.", mon_count.describe().c_str()); if (strwidth(msg) >= get_number_of_cols() - 2) msg = "Gozag incites your enemies against you."; mprf(MSGCH_GOD, GOD_GOZAG, "%s", msg.c_str()); for (unsigned int i = 0; i < mons.size(); i++) gozag_incite(mons[i]); dec_penance(GOD_GOZAG, mons.size()); } } } // Xom thinks it's hilarious the way the player picks up an ever // growing entourage of monsters while running through the Abyss. // To approximate this, if the number of hostile monsters in view // is greater than it ever was for this particular trip to the // Abyss, Xom is stimulated in proportion to the number of // hostile monsters. Thus if the entourage doesn't grow, then // Xom becomes bored. if (player_in_branch(BRANCH_ABYSS) && you.attribute[ATTR_ABYSS_ENTOURAGE] < num_hostile) { you.attribute[ATTR_ABYSS_ENTOURAGE] = num_hostile; xom_is_stimulated(12 * num_hostile); } } // We logically associate a difficulty parameter with each tile on each level, // to make deterministic magic mapping work. This function returns the // difficulty parameters for each tile on the current level, whose difficulty // is less than a certain amount. // // Random difficulties are used in the few cases where we want repeated maps // to give different results; scrolls and cards, since they are a finite // resource. static const FixedArray& _tile_difficulties(bool random) { // We will often be called with the same level parameter and cutoff, so // cache this (DS with passive mapping autoexploring could be 5000 calls // in a second or so). static FixedArray cache; static int cache_seed = -1; if (random) { cache_seed = -1; for (int y = Y_BOUND_1; y <= Y_BOUND_2; ++y) for (int x = X_BOUND_1; x <= X_BOUND_2; ++x) cache[x][y] = random2(100); return cache; } // must not produce the magic value (-1) int seed = (static_cast(you.where_are_you) << 8) + you.depth ^ you.game_seeds[SEED_PASSIVE_MAP] & 0x7fffffff; if (seed == cache_seed) return cache; cache_seed = seed; for (int y = Y_BOUND_1; y <= Y_BOUND_2; ++y) for (int x = X_BOUND_1; x <= X_BOUND_2; ++x) cache[x][y] = hash_rand(100, seed, y * GXM + x); return cache; } // Returns true if it succeeded. bool magic_mapping(int map_radius, int proportion, bool suppress_msg, bool force, bool deterministic, coord_def pos) { if (!in_bounds(pos)) pos = you.pos(); if (!force && testbits(env.level_flags, LFLAG_NO_MAP)) { if (!suppress_msg) canned_msg(MSG_DISORIENTED); return false; } const bool wizard_map = (you.wizard && map_radius == 1000); if (map_radius < 5) map_radius = 5; // now gradually weaker with distance: const int pfar = dist_range(map_radius * 7 / 10); const int very_far = dist_range(map_radius * 9 / 10); bool did_map = false; int num_altars = 0; int num_shops_portals = 0; const FixedArray& difficulty = _tile_difficulties(!deterministic); for (radius_iterator ri(pos, map_radius, C_ROUND); ri; ++ri) { if (!wizard_map) { int threshold = proportion; const int dist = distance2(you.pos(), *ri); if (dist > very_far) threshold = threshold / 3; else if (dist > pfar) threshold = threshold * 2 / 3; if (difficulty(*ri) > threshold) continue; } if (env.map_knowledge(*ri).changed()) { env.map_knowledge(*ri).clear(); env.map_seen.set(*ri, false); } if (!wizard_map && (env.map_knowledge(*ri).seen() || env.map_knowledge(*ri).mapped())) continue; const dungeon_feature_type feat = grd(*ri); bool open = true; if (feat_is_solid(feat) && !feat_is_closed_door(feat)) { open = false; for (adjacent_iterator ai(*ri); ai; ++ai) { if (map_bounds(*ai) && (!feat_is_opaque(grd(*ai)) || feat_is_closed_door(grd(*ai)))) { open = true; break; } } } if (open) { if (wizard_map) { env.map_knowledge(*ri).set_feature(grd(*ri), 0, feat_is_trap(grd(*ri)) ? get_trap_type(*ri) : TRAP_UNASSIGNED); } else if (!env.map_knowledge(*ri).feat()) env.map_knowledge(*ri).set_feature(magic_map_base_feat(grd(*ri))); if (emphasise(*ri)) env.map_knowledge(*ri).flags |= MAP_EMPHASIZE; if (wizard_map) { if (is_notable_terrain(feat)) seen_notable_thing(feat, *ri); set_terrain_seen(*ri); #ifdef USE_TILE tile_wizmap_terrain(*ri); #endif } else { set_terrain_mapped(*ri); if (get_feature_dchar(feat) == DCHAR_ALTAR) num_altars++; else if (get_feature_dchar(feat) == DCHAR_ARCH) num_shops_portals++; } did_map = true; } } if (!suppress_msg) { if (did_map) mpr("You feel aware of your surroundings."); else canned_msg(MSG_DISORIENTED); vector sensed; if (num_altars > 0) { sensed.push_back(make_stringf("%d altar%s", num_altars, num_altars > 1 ? "s" : "")); } if (num_shops_portals > 0) { const char* plur = num_shops_portals > 1 ? "s" : ""; sensed.push_back(make_stringf("%d shop%s/portal%s", num_shops_portals, plur, plur)); } if (!sensed.empty()) mpr_comma_separated_list("You sensed ", sensed); } return did_map; } void fully_map_level() { for (rectangle_iterator ri(1); ri; ++ri) { bool ok = false; for (adjacent_iterator ai(*ri, false); ai; ++ai) if (grd(*ai) >= DNGN_MINSEE) ok = true; if (!ok) continue; env.map_knowledge(*ri).set_feature(grd(*ri), 0, feat_is_trap(grd(*ri)) ? get_trap_type(*ri) : TRAP_UNASSIGNED); set_terrain_seen(*ri); #ifdef USE_TILE tile_wizmap_terrain(*ri); #endif if (igrd(*ri) != NON_ITEM) env.map_knowledge(*ri).set_detected_item(); env.pgrid(*ri) |= FPROP_SEEN_OR_NOEXP; } } // Is the given monster near (in LOS of) the player? bool mons_near(const monster* mons) { ASSERT(mons); if (crawl_state.game_is_arena() || crawl_state.arena_suspended) return true; return you.see_cell(mons->pos()); } bool mon_enemies_around(const monster* mons) { // If the monster has a foe, return true. if (mons->foe != MHITNOT && mons->foe != MHITYOU) return true; if (crawl_state.game_is_arena()) { // If the arena-mode code in _handle_behaviour() hasn't set a foe then // we don't have one. return false; } else if (mons->wont_attack()) { // Additionally, if an ally is nearby and *you* have a foe, // consider it as the ally's enemy too. return mons_near(mons) && there_are_monsters_nearby(true); } else { // For hostile monster* you* are the main enemy. return mons_near(mons); } } // Returns a string containing a representation of the map. Leading and // trailing spaces are trimmed from each line. Leading and trailing empty // lines are also snipped. string screenshot() { vector lines(crawl_view.viewsz.y); unsigned int lsp = GXM; for (int y = 0; y < crawl_view.viewsz.y; y++) { string line; for (int x = 0; x < crawl_view.viewsz.x; x++) { // in grid coords const coord_def gc = view2grid(crawl_view.viewp + coord_def(x, y)); ucs_t ch = (!map_bounds(gc)) ? ' ' : (gc == you.pos()) ? mons_char(you.symbol) : get_cell_glyph(gc).ch; line += stringize_glyph(ch); } // right-trim the line for (int x = line.length() - 1; x >= 0; x--) if (line[x] == ' ') line.erase(x); else break; // see how much it can be left-trimmed for (unsigned int x = 0; x < line.length(); x++) if (line[x] != ' ') { if (lsp > x) lsp = x; break; } lines[y] = line; } for (unsigned int y = 0; y < lines.size(); y++) lines[y].erase(0, lsp); // actually trim from the left while (!lines.empty() && lines.back().empty()) lines.pop_back(); // then from the bottom ostringstream ss; unsigned int y = 0; for (y = 0; y < lines.size() && lines[y].empty(); y++) ; // ... and from the top for (; y < lines.size(); y++) ss << lines[y] << "\n"; return ss.str(); } int viewmap_flash_colour() { if (_show_terrain) return BLACK; else if (you.attribute[ATTR_SHADOWS]) return LIGHTGREY; else if (you.berserk()) return RED; return BLACK; } // Updates one square of the view area. Should only be called for square // in LOS. void view_update_at(const coord_def &pos) { if (pos == you.pos()) return; show_update_at(pos); #ifndef USE_TILE_LOCAL if (!env.map_knowledge(pos).visible()) return; cglyph_t g = get_cell_glyph(pos); int flash_colour = you.flash_colour == BLACK ? viewmap_flash_colour() : you.flash_colour; monster_type mons = env.map_knowledge(pos).monster(); int cell_colour = flash_colour && (mons == MONS_NO_MONSTER || mons_class_is_firewood(mons)) ? real_colour(flash_colour) : g.col; const coord_def vp = grid2view(pos); // Don't draw off-screen. if (crawl_view.in_viewport_v(vp)) { cgotoxy(vp.x, vp.y, GOTO_DNGN); put_colour_ch(cell_colour, g.ch); } // Force colour back to normal, else clrscr() will flood screen // with this colour on DOS. textcolor(LIGHTGREY); #endif #ifdef USE_TILE_WEB tiles.mark_for_redraw(pos); #endif } #ifndef USE_TILE_LOCAL void flash_monster_colour(const monster* mon, colour_t fmc_colour, int fmc_delay) { if (you.can_see(mon)) { colour_t old_flash_colour = you.flash_colour; coord_def c(mon->pos()); you.flash_colour = fmc_colour; view_update_at(c); update_screen(); delay(fmc_delay); you.flash_colour = old_flash_colour; view_update_at(c); update_screen(); } } #endif bool view_update() { if (you.num_turns > you.last_view_update) { viewwindow(); return true; } return false; } void flash_view(colour_t colour, targetter *where) { you.flash_colour = colour; you.flash_where = where; viewwindow(false); } void flash_view_delay(colour_t colour, int flash_delay, targetter *where) { flash_view(colour, where); scaled_delay(flash_delay); flash_view(0); } static void _debug_pane_bounds() { #ifdef DEBUG_PANE_BOUNDS // Doesn't work for HUD because print_stats() overwrites it. if (crawl_view.mlistsz.y > 0) { textcolor(WHITE); cgotoxy(1,1, GOTO_MLIST); cprintf("+ L"); cgotoxy(crawl_view.mlistsz.x-4, crawl_view.mlistsz.y, GOTO_MLIST); cprintf("L +"); } cgotoxy(1,1, GOTO_STAT); cprintf("+ H"); cgotoxy(crawl_view.hudsz.x-3, crawl_view.hudsz.y, GOTO_STAT); cprintf("H +"); cgotoxy(1,1, GOTO_MSG); cprintf("+ M"); cgotoxy(crawl_view.msgsz.x-2, crawl_view.msgsz.y, GOTO_MSG); cprintf("M +"); cgotoxy(crawl_view.viewp.x, crawl_view.viewp.y); cprintf("+V"); cgotoxy(crawl_view.viewp.x+crawl_view.viewsz.x-2, crawl_view.viewp.y+crawl_view.viewsz.y-1); cprintf("V+"); textcolor(LIGHTGREY); #endif } enum update_flags { UF_AFFECT_EXCLUDES = (1 << 0), UF_ADDED_EXCLUDE = (1 << 1), }; // Do various updates when the player sees a cell. Returns whether // exclusion LOS might have been affected. static int player_view_update_at(const coord_def &gc) { maybe_remove_autoexclusion(gc); int ret = 0; // Set excludes in a radius of 1 around harmful clouds genereated // by neither monsters nor the player. const int cloudidx = env.cgrid(gc); if (cloudidx != EMPTY_CLOUD && !crawl_state.game_is_arena()) { cloud_struct &cl = env.cloud[cloudidx]; cloud_type ctype = cl.type; bool did_exclude = false; if (!cl.temporary() && is_damaging_cloud(cl.type, false)) { int size; // Steam clouds are less dangerous than the other ones, // so don't exclude the neighbour cells. if (ctype == CLOUD_STEAM && cl.exclusion_radius() == 1) size = 0; else size = cl.exclusion_radius(); bool was_exclusion = is_exclude_root(gc); set_exclude(gc, size, false, false, true); if (!did_exclude && !was_exclusion) ret |= UF_ADDED_EXCLUDE; } } // Print hints mode messages for features in LOS. if (crawl_state.game_is_hints()) hints_observe_cell(gc); if (env.map_knowledge(gc).changed() || !env.map_knowledge(gc).seen()) ret |= UF_AFFECT_EXCLUDES; set_terrain_visible(gc); if (!(env.pgrid(gc) & FPROP_SEEN_OR_NOEXP)) { env.pgrid(gc) |= FPROP_SEEN_OR_NOEXP; if (!crawl_state.game_is_arena()) { const int density = env.density ? env.density : 2000; did_god_conduct(DID_EXPLORATION, density); you.exploration += div_rand_round(1<<16, density); } } #ifdef USE_TILE const coord_def ep = grid2show(gc); // We remove any references to mcache when // writing to the background. env.tile_bk_fg(gc) = env.tile_fg(ep); env.tile_bk_bg(gc) = env.tile_bg(ep); env.tile_bk_cloud(gc) = env.tile_cloud(ep); #endif return ret; } static void player_view_update() { if (crawl_state.game_is_arena()) { for (rectangle_iterator ri(crawl_view.vgrdc, LOS_MAX_RANGE); ri; ++ri) player_view_update_at(*ri); // no need to do excludes on the arena return; } vector update_excludes; bool need_update = false; for (radius_iterator ri(you.pos(), you.xray_vision ? LOS_NONE : LOS_DEFAULT); ri; ++ri) { int flags = player_view_update_at(*ri); if (flags & UF_AFFECT_EXCLUDES) update_excludes.push_back(*ri); if (flags & UF_ADDED_EXCLUDE) need_update = true; } // Update exclusion LOS for possibly affected excludes. update_exclusion_los(update_excludes); // Catch up on deferred updates for cloud excludes. if (need_update) deferred_exclude_update(); } static void _draw_out_of_bounds(screen_cell_t *cell) { cell->glyph = ' '; cell->colour = DARKGREY; #ifdef USE_TILE cell->tile.fg = 0; cell->tile.bg = tileidx_out_of_bounds(you.where_are_you); #endif } static void _draw_outside_los(screen_cell_t *cell, const coord_def &gc) { // Outside the env.show area. cglyph_t g = get_cell_glyph(gc); cell->glyph = g.ch; cell->colour = g.col; #ifdef USE_TILE tileidx_out_of_los(&cell->tile.fg, &cell->tile.bg, &cell->tile.cloud, gc); #endif } static void _draw_player(screen_cell_t *cell, const coord_def &gc, const coord_def &ep, bool anim_updates) { // Player overrides everything in cell. cell->glyph = mons_char(you.symbol); cell->colour = mons_class_colour(you.symbol); if (you.swimming()) { if (grd(gc) == DNGN_DEEP_WATER) cell->colour = BLUE; else cell->colour = CYAN; } if (Options.use_fake_player_cursor) cell->colour |= COLFLAG_REVERSE; cell->colour = real_colour(cell->colour); #ifdef USE_TILE cell->tile.fg = env.tile_fg(ep) = tileidx_player(); cell->tile.bg = env.tile_bg(ep); cell->tile.cloud = env.tile_cloud(ep); if (anim_updates) tile_apply_animations(cell->tile.bg, &env.tile_flv(gc)); #else UNUSED(anim_updates); #endif } static void _draw_los(screen_cell_t *cell, const coord_def &gc, const coord_def &ep, bool anim_updates) { cglyph_t g = get_cell_glyph(gc); cell->glyph = g.ch; cell->colour = g.col; #ifdef USE_TILE cell->tile.fg = env.tile_fg(ep); cell->tile.bg = env.tile_bg(ep); cell->tile.cloud = env.tile_cloud(ep); if (anim_updates) tile_apply_animations(cell->tile.bg, &env.tile_flv(gc)); #else UNUSED(anim_updates); #endif } //--------------------------------------------------------------- // // Draws the main window using the character set returned // by get_show_glyph(). // // If show_updates is set, env.show and dependent structures // are updated. Should be set if anything in view has changed. // // If tiles_only is set, only the tile view will be updated. This // is only relevant for Webtiles. //--------------------------------------------------------------- void viewwindow(bool show_updates, bool tiles_only) { // The player could be at (0,0) if we are called during level-gen; this can // happen via mpr -> interrupt_activity -> stop_delay -> runrest::stop if (you.duration[DUR_TIME_STEP] || you.pos().origin()) return; screen_cell_t *cell(crawl_view.vbuf); // The buffer is not initialised when run from 'monster'; abort early. if (!cell) return; // Update the animation of cells only once per turn. const bool anim_updates = (you.last_view_update != you.num_turns); // Except for elemental colours, which should be updated every refresh. you.frame_no++; #ifdef USE_TILE tiles.clear_text_tags(TAG_NAMED_MONSTER); if (show_updates) mcache.clear_nonref(); #endif if (show_updates || _show_terrain) { if (!is_map_persistent()) ash_detect_portals(false); #ifdef USE_TILE tile_draw_floor(); tile_draw_rays(true); tiles.clear_overlays(); #endif show_init(_show_terrain); } if (show_updates) player_view_update(); bool run_dont_draw = you.running && Options.travel_delay < 0 && (!you.running.is_explore() || Options.explore_delay < 0); if (run_dont_draw || you.asleep()) { // Reset env.show if we munged it. if (_show_terrain) show_init(); return; } cursor_control cs(false); int flash_colour = you.flash_colour; if (flash_colour == BLACK) flash_colour = viewmap_flash_colour(); const coord_def tl = coord_def(1, 1); const coord_def br = crawl_view.viewsz; for (rectangle_iterator ri(tl, br); ri; ++ri) { // in grid coords const coord_def gc = view2grid(*ri); if (you.flash_where && you.flash_where->is_affected(gc) <= 0) draw_cell(cell, gc, anim_updates, 0); else draw_cell(cell, gc, anim_updates, flash_colour); cell++; } you.last_view_update = you.num_turns; #ifndef USE_TILE_LOCAL #ifdef USE_TILE_WEB tiles_crt_control crt(false); #endif if (!tiles_only) { puttext(crawl_view.viewp.x, crawl_view.viewp.y, crawl_view.vbuf); update_monster_pane(); } #endif #ifdef USE_TILE tiles.set_need_redraw(you.running ? Options.tile_runrest_rate : 0); tiles.load_dungeon(crawl_view.vbuf, crawl_view.vgrdc); tiles.update_tabs(); #endif // Leaving it this way because short flashes can occur in long ones, // and this simply works without requiring a stack. you.flash_colour = BLACK; you.flash_where = 0; // Reset env.show if we munged it. if (_show_terrain) show_init(); _debug_pane_bounds(); } void draw_cell(screen_cell_t *cell, const coord_def &gc, bool anim_updates, int flash_colour) { #ifdef USE_TILE cell->tile.clear(); #endif const coord_def ep = grid2show(gc); if (!map_bounds(gc)) _draw_out_of_bounds(cell); else if (!crawl_view.in_los_bounds_g(gc)) _draw_outside_los(cell, gc); else if (gc == you.pos() && you.on_current_level && !_show_terrain && !crawl_state.game_is_arena() && !crawl_state.arena_suspended) { _draw_player(cell, gc, ep, anim_updates); } else if (you.see_cell(gc) && you.on_current_level) _draw_los(cell, gc, ep, anim_updates); else _draw_outside_los(cell, gc); cell->flash_colour = BLACK; // Alter colour if flashing the characters vision. if (flash_colour) { if (!you.see_cell(gc)) cell->colour = DARKGREY; #ifdef USE_TILE_LOCAL else cell->colour = real_colour(flash_colour); #else else if (gc != you.pos()) { monster_type mons = env.map_knowledge(gc).monster(); if (mons == MONS_NO_MONSTER || mons_class_is_firewood(mons)) cell->colour = real_colour(flash_colour); } #endif cell->flash_colour = cell->colour; } else if (crawl_state.darken_range) { if (!crawl_state.darken_range->valid_aim(gc)) { cell->colour = DARKGREY; #ifdef USE_TILE if (you.see_cell(gc)) cell->tile.bg |= TILE_FLAG_OOR; #endif } } #ifdef USE_TILE_LOCAL // Grey out grids that cannot be reached due to beholders. else if (you.get_beholder(gc)) cell->tile.bg |= TILE_FLAG_OOR; else if (you.get_fearmonger(gc)) cell->tile.bg |= TILE_FLAG_OOR; tile_apply_properties(gc, cell->tile); #elif defined(USE_TILE_WEB) // For webtiles, we only grey out visible tiles else if (you.get_beholder(gc) && you.see_cell(gc)) cell->tile.bg |= TILE_FLAG_OOR; else if (you.get_fearmonger(gc) && you.see_cell(gc)) cell->tile.bg |= TILE_FLAG_OOR; tile_apply_properties(gc, cell->tile); #endif } void toggle_show_terrain() { _show_terrain = !_show_terrain; if (_show_terrain) mpr("Showing terrain only."); else mpr("Returning to normal view."); } void reset_show_terrain() { _show_terrain = false; } //////////////////////////////////////////////////////////////////////////// // Term resize handling (generic). void handle_terminal_resize(bool redraw) { crawl_state.terminal_resized = false; if (crawl_state.terminal_resize_handler) (*crawl_state.terminal_resize_handler)(); else crawl_view.init_geometry(); if (redraw) redraw_screen(); }