/** * Check if a monster should stagger or not. Always stagger when confused, * but also deal with random movement for RAND_25 and _50 monsters. */ static bool process_monster_should_stagger(struct monster *mon) { struct monster_lore *lore = get_lore(mon->race); int chance = 0; /* Confused */ if (mon->m_timed[MON_TMD_CONF]) return true; /* RAND_25 and RAND_50 are cumulative */ if (rf_has(mon->race->flags, RF_RAND_25)) { chance += 25; if (mflag_has(mon->mflag, MFLAG_VISIBLE)) rf_on(lore->flags, RF_RAND_25); } if (rf_has(mon->race->flags, RF_RAND_50)) { chance += 50; if (mflag_has(mon->mflag, MFLAG_VISIBLE)) rf_on(lore->flags, RF_RAND_50); } return randint0(100) < chance; }
/** * Determine if a monster makes a reasonable target * * The concept of "targetting" was stolen from "Morgul" (?) * * The player can target any location, or any "target-able" monster. * * Currently, a monster is "target_able" if it is visible, and if * the player can hit it with a projection, and the player is not * hallucinating. This allows use of "use closest target" macros. */ bool target_able(struct monster *m) { return m && m->race && mflag_has(m->mflag, MFLAG_VISIBLE) && !mflag_has(m->mflag, MFLAG_UNAWARE) && projectable(cave, player->py, player->px, m->fy, m->fx, PROJECT_NONE) && !player->timed[TMD_IMAGE]; }
/** * Housekeeping after the processing of a player command */ static void process_player_cleanup(void) { int i; /* Significant */ if (player->upkeep->energy_use) { /* Use some energy */ player->energy -= player->upkeep->energy_use; /* Increment the total energy counter */ player->total_energy += player->upkeep->energy_use; /* Do nothing else if player has auto-dropped stuff */ if (!player->upkeep->dropping) { /* Hack -- constant hallucination */ if (player->timed[TMD_IMAGE]) player->upkeep->redraw |= (PR_MAP); /* Shimmer multi-hued monsters */ for (i = 1; i < cave_monster_max(cave); i++) { struct monster *mon = cave_monster(cave, i); if (!mon->race) continue; if (!rf_has(mon->race->flags, RF_ATTR_MULTI)) continue; square_light_spot(cave, mon->fy, mon->fx); } /* Clear NICE flag, and show marked monsters */ for (i = 1; i < cave_monster_max(cave); i++) { struct monster *mon = cave_monster(cave, i); mflag_off(mon->mflag, MFLAG_NICE); if (mflag_has(mon->mflag, MFLAG_MARK)) { if (!mflag_has(mon->mflag, MFLAG_SHOW)) { mflag_off(mon->mflag, MFLAG_MARK); update_mon(mon, cave, false); } } } } } /* Clear SHOW flag and player drop status */ for (i = 1; i < cave_monster_max(cave); i++) { struct monster *mon = cave_monster(cave, i); mflag_off(mon->mflag, MFLAG_SHOW); } player->upkeep->dropping = false; /* Hack - update needed first because inventory may have changed */ update_stuff(player); redraw_stuff(player); }
/** * Display the monster list statically. This will force the list to be * displayed to the provided dimensions. Contents will be adjusted accordingly. * * In order to support more efficient monster flicker animations, this function * uses a shared list object so that it's not constantly allocating and freeing * the list. * * \param height is the height of the list. * \param width is the width of the list. */ void monster_list_show_subwindow(int height, int width) { textblock *tb; monster_list_t *list; int i; if (height < 1 || width < 1) return; tb = textblock_new(); list = monster_list_shared_instance(); /* Force an update if detected monsters */ for (i = 1; i < cave_monster_max(cave); i++) { if (mflag_has(cave_monster(cave, i)->mflag, MFLAG_MARK)) { list->creation_turn = -1; break; } } monster_list_reset(list); monster_list_collect(list); monster_list_get_glyphs(list); monster_list_sort(list, monster_list_standard_compare); /* Draw the list to exactly fit the subwindow. */ monster_list_format_textblock(list, tb, height, width, NULL, NULL); textui_textblock_place(tb, SCREEN_REGION, NULL); textblock_free(tb); }
/** * Helper function used with ranged_helper by do_cmd_throw. */ static struct attack_result make_ranged_throw(struct object *obj, int y, int x) { char *hit_verb = mem_alloc(20*sizeof(char)); struct attack_result result = {FALSE, 0, 0, hit_verb}; struct monster *mon = square_monster(cave, y, x); int chance = chance_of_missile_hit(player, obj, NULL, y, x); int multiplier = 1; const struct brand *b = NULL; const struct slay *s = NULL; my_strcpy(hit_verb, "hits", sizeof(hit_verb)); /* If we missed then we're done */ if (!test_hit(chance, mon->race->ac, mflag_has(mon->mflag, MFLAG_VISIBLE))) return result; result.success = TRUE; improve_attack_modifier(obj, mon, &b, &s, result.hit_verb, TRUE, TRUE, FALSE); result.dmg = ranged_damage(obj, NULL, b, s, multiplier); result.dmg = critical_norm(obj->weight, obj->to_h, result.dmg, &result.msg_type); return result; }
/** * Helper function used with ranged_helper by do_cmd_fire. */ static struct attack_result make_ranged_shot(struct object *ammo, int y, int x) { char *hit_verb = mem_alloc(20 * sizeof(char)); struct attack_result result = {FALSE, 0, 0, hit_verb}; struct object *bow = equipped_item_by_slot_name(player, "shooting"); struct monster *mon = square_monster(cave, y, x); int chance = chance_of_missile_hit(player, ammo, bow, y, x); int multiplier = player->state.ammo_mult; const struct brand *b = NULL; const struct slay *s = NULL; my_strcpy(hit_verb, "hits", sizeof(hit_verb)); /* Did we hit it (penalize distance travelled) */ if (!test_hit(chance, mon->race->ac, mflag_has(mon->mflag, MFLAG_VISIBLE))) return result; result.success = TRUE; improve_attack_modifier(ammo, mon, &b, &s, result.hit_verb, TRUE, TRUE, FALSE); improve_attack_modifier(bow, mon, &b, &s, result.hit_verb, TRUE, TRUE, FALSE); result.dmg = ranged_damage(ammo, bow, b, s, multiplier); result.dmg = critical_shot(ammo->weight, ammo->to_h, result.dmg, &result.msg_type); object_notice_attack_plusses(bow); return result; }
/** * Attack the monster at the given location * * We get blows until energy drops below that required for another blow, or * until the target monster dies. Each blow is handled by py_attack_real(). * We don't allow @ to spend more than 100 energy in one go, to avoid slower * monsters getting double moves. */ void py_attack(int y, int x) { int blow_energy = 100 * z_info->move_energy / player->state.num_blows; int blows = 0; bool fear = FALSE; struct monster *mon = square_monster(cave, y, x); /* disturb the player */ disturb(player, 0); /* Initialize the energy used */ player->upkeep->energy_use = 0; /* Attack until energy runs out or enemy dies. We limit energy use to 100 * to avoid giving monsters a possible double move. */ while (player->energy >= blow_energy * (blows + 1)) { bool stop = py_attack_real(y, x, &fear); player->upkeep->energy_use += blow_energy; if (player->upkeep->energy_use + blow_energy > z_info->move_energy || stop) break; blows++; } /* Hack - delay fear messages */ if (fear && mflag_has(mon->mflag, MFLAG_VISIBLE)) { char m_name[80]; /* Don't set monster_desc flags, since add_monster_message does string * processing on m_name */ monster_desc(m_name, sizeof(m_name), mon, MDESC_DEFAULT); add_monster_message(m_name, mon, MON_MSG_FLEE_IN_TERROR, TRUE); } }
/** * Try to push past / kill another monster. Returns true on success. */ static bool process_monster_try_push(struct chunk *c, struct monster *mon, const char *m_name, int nx, int ny) { struct monster *mon1 = square_monster(c, ny, nx); struct monster_lore *lore = get_lore(mon->race); /* Kill weaker monsters */ int kill_ok = rf_has(mon->race->flags, RF_KILL_BODY); /* Move weaker monsters if they can swap places */ /* (not in a wall) */ int move_ok = (rf_has(mon->race->flags, RF_MOVE_BODY) && square_ispassable(c, mon->fy, mon->fx)); if (compare_monsters(mon, mon1) > 0) { /* Learn about pushing and shoving */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) { rf_on(lore->flags, RF_KILL_BODY); rf_on(lore->flags, RF_MOVE_BODY); } if (kill_ok || move_ok) { /* Get the names of the monsters involved */ char n_name[80]; monster_desc(n_name, sizeof(n_name), mon1, MDESC_IND_HID); /* Reveal mimics */ if (is_mimicking(mon1)) become_aware(mon1); /* Note if visible */ if (mflag_has(mon->mflag, MFLAG_VISIBLE) && mflag_has(mon->mflag, MFLAG_VIEW)) msg("%s %s %s.", m_name, kill_ok ? "tramples over" : "pushes past", n_name); /* Monster ate another monster */ if (kill_ok) delete_monster(ny, nx); monster_swap(mon->fy, mon->fx, ny, nx); return true; } } return false; }
/** * True if the player's current target is in LOS. */ bool target_sighted(void) { return target_okay() && panel_contains(target_y, target_x) && /* either the target is a grid and is visible, or it is a monster * that is visible */ ((!target_who && square_isseen(cave, target_y, target_x)) || (target_who && mflag_has(target_who->mflag, MFLAG_VISIBLE))); }
/** * Handles the "death" of a monster. * * Disperses treasures carried by the monster centered at the monster location. * Note that objects dropped may disappear in crowded rooms. * * Checks for "Quest" completion when a quest monster is killed. * * Note that only the player can induce "monster_death()" on Uniques. * Thus (for now) all Quest monsters should be Uniques. * * If `stats` is true, then we skip updating the monster memory. This is * used by stats-generation code, for efficiency. */ void monster_death(struct monster *mon, bool stats) { int dump_item = 0; int dump_gold = 0; struct object *obj = mon->held_obj; bool visible = (mflag_has(mon->mflag, MFLAG_VISIBLE) || rf_has(mon->race->flags, RF_UNIQUE)); /* Delete any mimicked objects */ if (mon->mimicked_obj) object_delete(&mon->mimicked_obj); /* Drop objects being carried */ while (obj) { struct object *next = obj->next; /* Object no longer held */ obj->held_m_idx = 0; pile_excise(&mon->held_obj, obj); /* Count it and drop it - refactor once origin is a bitflag */ if (!stats) { if (tval_is_money(obj) && (obj->origin != ORIGIN_STOLEN)) dump_gold++; else if (!tval_is_money(obj) && ((obj->origin == ORIGIN_DROP) || (obj->origin == ORIGIN_DROP_PIT) || (obj->origin == ORIGIN_DROP_VAULT) || (obj->origin == ORIGIN_DROP_SUMMON) || (obj->origin == ORIGIN_DROP_SPECIAL) || (obj->origin == ORIGIN_DROP_BREED) || (obj->origin == ORIGIN_DROP_POLY) || (obj->origin == ORIGIN_DROP_WIZARD))) dump_item++; } /* Change origin if monster is invisible, unless we're in stats mode */ if (!visible && !stats) obj->origin = ORIGIN_DROP_UNKNOWN; drop_near(cave, obj, 0, mon->fy, mon->fx, true); obj = next; } /* Forget objects */ mon->held_obj = NULL; /* Take note of any dropped treasure */ if (visible && (dump_item || dump_gold)) lore_treasure(mon, dump_item, dump_gold); /* Update monster list window */ player->upkeep->redraw |= PR_MONLIST; /* Check if we finished a quest */ quest_check(mon); }
/** * Determine if a given grid may be "walked" */ static bool do_cmd_walk_test(int y, int x) { int m_idx = cave->squares[y][x].mon; struct monster *mon = cave_monster(cave, m_idx); /* Allow attack on visible monsters if unafraid */ if (m_idx > 0 && mflag_has(mon->mflag, MFLAG_VISIBLE) && !is_mimicking(mon)) { /* Handle player fear */ if (player_of_has(player, OF_AFRAID)) { /* Extract monster name (or "it") */ char m_name[80]; monster_desc(m_name, sizeof(m_name), mon, MDESC_DEFAULT); /* Message */ msgt(MSG_AFRAID, "You are too afraid to attack %s!", m_name); /* Nope */ return (false); } return (true); } /* If we don't know the grid, allow attempts to walk into it */ if (!square_isknown(cave, y, x)) return true; /* Require open space */ if (!square_ispassable(cave, y, x)) { if (square_isrubble(cave, y, x)) /* Rubble */ msgt(MSG_HITWALL, "There is a pile of rubble in the way!"); else if (square_iscloseddoor(cave, y, x)) /* Door */ return true; else if (square_isbright(cave, y, x)) /* Lava */ msgt(MSG_HITWALL, "The heat of the lava turns you away!"); else /* Wall */ msgt(MSG_HITWALL, "There is a wall in the way!"); /* Cancel repeat */ disturb(player, 0); /* Nope */ return (false); } /* Okay */ return (true); }
/** * Attempt to reproduce, if possible. All monsters are checked here for * lore purposes, the unfit fail. */ static bool process_monster_multiply(struct chunk *c, struct monster *m_ptr) { int oy = m_ptr->fy; int ox = m_ptr->fx; int k = 0, y, x; monster_lore *l_ptr = get_lore(m_ptr->race); /* Too many breeders on the level already */ if (num_repro >= z_info->repro_monster_max) return FALSE; /* Count the adjacent monsters */ for (y = oy - 1; y <= m_ptr->fy + 1; y++) for (x = ox - 1; x <= m_ptr->fx + 1; x++) if (c->squares[y][x].mon > 0) k++; /* Multiply slower in crowded areas */ if ((k < 4) && (k == 0 || one_in_(k * z_info->repro_monster_rate))) { /* Successful breeding attempt, learn about that now */ if (mflag_has(m_ptr->mflag, MFLAG_VISIBLE)) rf_on(l_ptr->flags, RF_MULTIPLY); /* Leave now if not a breeder */ if (!rf_has(m_ptr->race->flags, RF_MULTIPLY)) return FALSE; /* Try to multiply */ if (multiply_monster(m_ptr)) { /* Make a sound */ if (mflag_has(m_ptr->mflag, MFLAG_VISIBLE)) sound(MSG_MULTIPLY); /* Multiplying takes energy */ return TRUE; } } return FALSE; }
/** * Determine if a given location is "interesting" */ bool target_accept(int y, int x) { object_type *obj; /* Player grids are always interesting */ if (cave->squares[y][x].mon < 0) return (TRUE); /* Handle hallucination */ if (player->timed[TMD_IMAGE]) return (FALSE); /* Visible monsters */ if (cave->squares[y][x].mon > 0) { monster_type *m_ptr = square_monster(cave, y, x); /* Visible monsters */ if (mflag_has(m_ptr->mflag, MFLAG_VISIBLE) && !mflag_has(m_ptr->mflag, MFLAG_UNAWARE)) return (TRUE); } /* Traps */ if (square_isvisibletrap(cave, y, x)) return(TRUE); /* Scan all objects in the grid */ for (obj = square_object(cave, y, x); obj; obj = obj->next) /* Memorized object */ if (obj->marked && !ignore_item_ok(obj)) return (TRUE); /* Interesting memorized features */ if (square_ismark(cave, y, x) && !square_isboring(cave, y, x)) return (TRUE); /* Nope */ return (FALSE); }
/** * Determine if a given location is "interesting" */ bool target_accept(int y, int x) { struct object *obj; /* Player grids are always interesting */ if (cave->squares[y][x].mon < 0) return (true); /* Handle hallucination */ if (player->timed[TMD_IMAGE]) return (false); /* Visible monsters */ if (cave->squares[y][x].mon > 0) { struct monster *mon = square_monster(cave, y, x); /* Visible monsters */ if (mflag_has(mon->mflag, MFLAG_VISIBLE) && !mflag_has(mon->mflag, MFLAG_UNAWARE)) return (true); } /* Traps */ if (square_isvisibletrap(cave, y, x)) return(true); /* Scan all objects in the grid */ for (obj = square_object(cave_k, y, x); obj; obj = obj->next) /* Memorized object */ if (!ignore_known_item_ok(obj)) return (true); /* Interesting memorized features */ if (square_isknown(cave, y, x) && square_isinteresting(cave, y, x)) return (true); /* Nope */ return (false); }
/** * Determine whether a monster is active or passive */ static bool monster_check_active(struct chunk *c, struct monster *mon) { /* Character is inside scanning range */ if (mon->cdis <= mon->race->aaf) mflag_on(mon->mflag, MFLAG_ACTIVE); /* Monster is hurt */ else if (mon->hp < mon->maxhp) mflag_on(mon->mflag, MFLAG_ACTIVE); /* Monster can "see" the player (checked backwards) */ else if (square_isview(c, mon->fy, mon->fx)) mflag_on(mon->mflag, MFLAG_ACTIVE); /* Monster can "smell" the player from far away (flow) */ else if (monster_can_flow(c, mon)) mflag_on(mon->mflag, MFLAG_ACTIVE); /* Otherwise go passive */ else mflag_off(mon->mflag, MFLAG_ACTIVE); return mflag_has(mon->mflag, MFLAG_ACTIVE) ? true : false; }
/** * Determine whether a monster is active or passive */ static bool monster_check_active(struct chunk *c, struct monster *mon) { if ((mon->cdis <= mon->race->hearing) && monster_passes_walls(mon)) { /* Character is inside scanning range, monster can go straight there */ mflag_on(mon->mflag, MFLAG_ACTIVE); } else if (mon->hp < mon->maxhp) { /* Monster is hurt */ mflag_on(mon->mflag, MFLAG_ACTIVE); } else if (square_isview(c, mon->fy, mon->fx)) { /* Monster can "see" the player (checked backwards) */ mflag_on(mon->mflag, MFLAG_ACTIVE); } else if (monster_can_hear(c, mon)) { /* Monster can hear the player */ mflag_on(mon->mflag, MFLAG_ACTIVE); } else if (monster_can_smell(c, mon)) { /* Monster can smell the player */ mflag_on(mon->mflag, MFLAG_ACTIVE); } else { /* Otherwise go passive */ mflag_off(mon->mflag, MFLAG_ACTIVE); } return mflag_has(mon->mflag, MFLAG_ACTIVE) ? true : false; }
/** * Update the current "run" path * * Return TRUE if the running should be stopped */ static bool run_test(void) { int py = player->py; int px = player->px; int prev_dir; int new_dir; int row, col; int i, max, inv; int option, option2; /* No options yet */ option = 0; option2 = 0; /* Where we came from */ prev_dir = run_old_dir; /* Range of newly adjacent grids */ max = (prev_dir & 0x01) + 1; /* Look at every newly adjacent square. */ for (i = -max; i <= max; i++) { struct object *obj; /* New direction */ new_dir = cycle[chome[prev_dir] + i]; /* New location */ row = py + ddy[new_dir]; col = px + ddx[new_dir]; /* Visible monsters abort running */ if (cave->squares[row][col].mon > 0) { monster_type *m_ptr = square_monster(cave, row, col); /* Visible monster */ if (mflag_has(m_ptr->mflag, MFLAG_VISIBLE)) return (TRUE); } /* Visible objects abort running */ for (obj = square_object(cave, row, col); obj; obj = obj->next) /* Visible object */ if (obj->marked && !ignore_item_ok(obj)) return (TRUE); /* Assume unknown */ inv = TRUE; /* Check memorized grids */ if (square_ismark(cave, row, col)) { bool notice = square_noticeable(cave, row, col); /* Interesting feature */ if (notice) return (TRUE); /* The grid is "visible" */ inv = FALSE; } /* Analyze unknown grids and floors */ if (inv || square_ispassable(cave, row, col)) { /* Looking for open area */ if (run_open_area) { /* Nothing */ } else if (!option) { /* The first new direction. */ option = new_dir; } else if (option2) { /* Three new directions. Stop running. */ return (TRUE); } else if (option != cycle[chome[prev_dir] + i - 1]) { /* Two non-adjacent new directions. Stop running. */ return (TRUE); } else if (new_dir & 0x01) { /* Two new (adjacent) directions (case 1) */ option2 = new_dir; } else { /* Two new (adjacent) directions (case 2) */ option2 = option; option = new_dir; } } else { /* Obstacle, while looking for open area */ if (run_open_area) { if (i < 0) { /* Break to the right */ run_break_right = TRUE; } else if (i > 0) { /* Break to the left */ run_break_left = TRUE; } } } } /* Look at every soon to be newly adjacent square. */ for (i = -max; i <= max; i++) { /* New direction */ new_dir = cycle[chome[prev_dir] + i]; /* New location */ row = py + ddy[prev_dir] + ddy[new_dir]; col = px + ddx[prev_dir] + ddx[new_dir]; /* HACK: Ugh. Sometimes we come up with illegal bounds. This will * treat the symptom but not the disease. */ if (row >= cave->height || col >= cave->width) continue; if (row < 0 || col < 0) continue; /* Visible monsters abort running */ if (cave->squares[row][col].mon > 0) { monster_type *m_ptr = square_monster(cave, row, col); /* Visible monster */ if (mflag_has(m_ptr->mflag, MFLAG_VISIBLE) && !is_mimicking(m_ptr)) return (TRUE); } } /* Looking for open area */ if (run_open_area) { /* Hack -- look again */ for (i = -max; i < 0; i++) { new_dir = cycle[chome[prev_dir] + i]; row = py + ddy[new_dir]; col = px + ddx[new_dir]; /* Unknown grid or non-wall */ /* Was: square_ispassable(cave, row, col) */ if (!square_ismark(cave, row, col) || (square_ispassable(cave, row, col))) { /* Looking to break right */ if (run_break_right) { return (TRUE); } } else { /* Obstacle */ /* Looking to break left */ if (run_break_left) { return (TRUE); } } } /* Hack -- look again */ for (i = max; i > 0; i--) { new_dir = cycle[chome[prev_dir] + i]; row = py + ddy[new_dir]; col = px + ddx[new_dir]; /* Unknown grid or non-wall */ /* Was: square_ispassable(cave, row, col) */ if (!square_ismark(cave, row, col) || (square_ispassable(cave, row, col))) { /* Looking to break left */ if (run_break_left) { return (TRUE); } } else { /* Obstacle */ /* Looking to break right */ if (run_break_right) { return (TRUE); } } } } else { /* Not looking for open area */ /* No options */ if (!option) { return (TRUE); } else if (!option2) { /* One option */ /* Primary option */ run_cur_dir = option; /* No other options */ run_old_dir = option; } else { /* Two options, examining corners */ /* Primary option */ run_cur_dir = option; /* Hack -- allow curving */ run_old_dir = option2; } } /* About to hit a known wall, stop */ if (see_wall(run_cur_dir, py, px)) return (TRUE); /* Failure */ return (FALSE); }
/** * Called from project() to affect the player * * Called for projections with the PROJECT_PLAY flag set, which includes * bolt, beam, ball and breath effects. * * \param who is the monster list index of the caster * \param r is the distance from the centre of the effect * \param y * \param x the coordinates of the grid being handled * \param dam is the "damage" from the effect at distance r from the centre * \param typ is the projection (GF_) type * \return whether the effects were obvious * * If "r" is non-zero, then the blast was centered elsewhere; the damage * is reduced in project() before being passed in here. This can happen if a * monster breathes at the player and hits a wall instead. * * We assume the player is aware of some effect, and always return "TRUE". */ bool project_p(int who, int r, int y, int x, int dam, int typ) { bool blind, seen; bool obvious = TRUE; /* Source monster */ monster_type *m_ptr; /* Monster name (for damage) */ char killer[80]; project_player_handler_f player_handler = player_handlers[typ]; project_player_handler_context_t context = { who, r, y, x, dam, typ, obvious, }; /* No player here */ if (!(cave->squares[y][x].mon < 0)) return (FALSE); /* Never affect projector */ if (cave->squares[y][x].mon == who) return (FALSE); /* Source monster */ m_ptr = cave_monster(cave, who); /* Player blind-ness */ blind = (player->timed[TMD_BLIND] ? TRUE : FALSE); /* Extract the "see-able-ness" */ seen = (!blind && mflag_has(m_ptr->mflag, MFLAG_VISIBLE)); /* Get the monster's real name */ monster_desc(killer, sizeof(killer), m_ptr, MDESC_DIED_FROM); /* Let player know what is going on */ if (!seen) msg("You are hit by %s!", gf_blind_desc(typ)); /* Adjust damage for resistance, immunity or vulnerability, and apply it */ dam = adjust_dam(player, typ, dam, RANDOMISE, player->state.el_info[typ].res_level); if (dam) take_hit(player, dam, killer); /* Handle side effects */ if (player_handler != NULL) player_handler(&context); obvious = context.obvious; /* Disturb */ disturb(player, 1); /* Return "Anything seen?" */ return (obvious); }
/** * Process a monster's timed effects, e.g. decrease them. * * Returns true if the monster is skipping its turn. */ static bool process_monster_timed(struct chunk *c, struct monster *mon) { struct monster_lore *lore = get_lore(mon->race); /* Handle "sleep" */ if (mon->m_timed[MON_TMD_SLEEP]) { bool woke_up = false; /* Anti-stealth */ int notice = randint0(1024); /* Aggravation */ if (player_of_has(player, OF_AGGRAVATE)) { char m_name[80]; /* Wake the monster */ mon_clear_timed(mon, MON_TMD_SLEEP, MON_TMD_FLG_NOMESSAGE, false); /* Get the monster name */ monster_desc(m_name, sizeof(m_name), mon, MDESC_CAPITAL | MDESC_IND_HID); /* Notify the player if aware */ if (mflag_has(mon->mflag, MFLAG_VISIBLE) && !mflag_has(mon->mflag, MFLAG_UNAWARE)) msg("%s wakes up.", m_name); woke_up = true; } else if ((notice * notice * notice) <= player->state.noise) { /* See if monster "notices" player */ int d = 1; /* Wake up faster near the player */ if (mon->cdis < 50) d = (100 / mon->cdis); /* Note a complete wakeup */ if (mon->m_timed[MON_TMD_SLEEP] <= d) woke_up = true; /* Monster wakes up a bit */ mon_dec_timed(mon, MON_TMD_SLEEP, d, MON_TMD_FLG_NOTIFY, false); /* Update knowledge */ if (mflag_has(mon->mflag, MFLAG_VISIBLE) && !mflag_has(mon->mflag, MFLAG_UNAWARE)) { if (!woke_up && lore->ignore < UCHAR_MAX) lore->ignore++; else if (woke_up && lore->wake < UCHAR_MAX) lore->wake++; lore_update(mon->race, lore); } } /* Sleeping monsters don't recover in any other ways */ /* If the monster just woke up, then it doesn't act */ return true; } if (mon->m_timed[MON_TMD_FAST]) mon_dec_timed(mon, MON_TMD_FAST, 1, 0, false); if (mon->m_timed[MON_TMD_SLOW]) mon_dec_timed(mon, MON_TMD_SLOW, 1, 0, false); if (mon->m_timed[MON_TMD_STUN]) { int d = 1; /* Make a "saving throw" against stun */ if (randint0(5000) <= mon->race->level * mon->race->level) /* Recover fully */ d = mon->m_timed[MON_TMD_STUN]; /* Hack -- Recover from stun */ mon_dec_timed(mon, MON_TMD_STUN, d, MON_TMD_FLG_NOTIFY, false); } if (mon->m_timed[MON_TMD_CONF]) { int d = randint1(mon->race->level / 10 + 1); mon_dec_timed(mon, MON_TMD_CONF, d, MON_TMD_FLG_NOTIFY, false); } if (mon->m_timed[MON_TMD_FEAR]) { int d = randint1(mon->race->level / 10 + 1); mon_dec_timed(mon, MON_TMD_FEAR, d, MON_TMD_FLG_NOTIFY, false); } /* Don't do anything if stunned */ return mon->m_timed[MON_TMD_STUN] ? true : false; }
/** * Process all the "live" monsters, once per game turn. * * During each game turn, we scan through the list of all the "live" monsters, * (backwards, so we can excise any "freshly dead" monsters), energizing each * monster, and allowing fully energized monsters to move, attack, pass, etc. * * This function and its children are responsible for a considerable fraction * of the processor time in normal situations, greater if the character is * resting. */ void process_monsters(struct chunk *c, int minimum_energy) { int i; int mspeed; /* Only process some things every so often */ bool regen = false; /* Regenerate hitpoints and mana every 100 game turns */ if (turn % 100 == 0) regen = true; /* Process the monsters (backwards) */ for (i = cave_monster_max(c) - 1; i >= 1; i--) { struct monster *mon; bool moving; /* Handle "leaving" */ if (player->is_dead || player->upkeep->generate_level) break; /* Get a 'live' monster */ mon = cave_monster(c, i); if (!mon->race) continue; /* Ignore monsters that have already been handled */ if (mflag_has(mon->mflag, MFLAG_HANDLED)) continue; /* Not enough energy to move yet */ if (mon->energy < minimum_energy) continue; /* Does this monster have enough energy to move? */ moving = mon->energy >= z_info->move_energy ? true : false; /* Prevent reprocessing */ mflag_on(mon->mflag, MFLAG_HANDLED); /* Handle monster regeneration if requested */ if (regen) regen_monster(mon); /* Calculate the net speed */ mspeed = mon->mspeed; if (mon->m_timed[MON_TMD_FAST]) mspeed += 10; if (mon->m_timed[MON_TMD_SLOW]) mspeed -= 10; /* Give this monster some energy */ mon->energy += turn_energy(mspeed); /* End the turn of monsters without enough energy to move */ if (!moving) continue; /* Use up "some" energy */ mon->energy -= z_info->move_energy; /* Mimics lie in wait */ if (is_mimicking(mon)) continue; /* Check if the monster is active */ if (monster_check_active(c, mon)) { /* Process timed effects - skip turn if necessary */ if (process_monster_timed(c, mon)) continue; /* Set this monster to be the current actor */ c->mon_current = i; /* Process the monster */ process_monster(c, mon); /* Monster is no longer current */ c->mon_current = -1; } } /* Update monster visibility after this */ /* XXX This may not be necessary */ player->upkeep->update |= PU_MONSTERS; }
/** * Attempts to set the timer of the given monster effect to `timer`. * * Checks to see if the monster resists the effect, using mon_resist_effect(). * If not, the effect is set to `timer` turns. If `timer` is 0, or if the * effect timer was 0, or if MON_TMD_FLG_NOTIFY is set in `flag`, then a * message is printed, unless MON_TMD_FLG_NOMESSAGE is set in `flag`. * * Set a timed monster event to 'v'. Give messages if the right flags are set. * Check if the monster is able to resist the spell. Mark the lore. * Returns true if the monster was affected. * Return false if the monster was unaffected. */ static bool mon_set_timed(struct monster *mon, int ef_idx, int timer, u16b flag, bool id) { bool check_resist = false; bool resisted = false; struct mon_timed_effect *effect; int m_note = 0; int old_timer; assert(ef_idx >= 0 && ef_idx < MON_TMD_MAX); effect = &effects[ef_idx]; assert(mon); assert(mon->race); old_timer = mon->m_timed[ef_idx]; /* No change */ if (old_timer == timer) return false; if (timer == 0) { /* Turning off, usually mention */ m_note = effect->message_end; flag |= MON_TMD_FLG_NOTIFY; } else if (old_timer == 0) { /* Turning on, usually mention */ flag |= MON_TMD_FLG_NOTIFY; m_note = effect->message_begin; check_resist = true; } else if (timer > old_timer) { /* Different message for increases, but don't automatically mention. */ m_note = effect->message_increase; check_resist = true; } /* Decreases don't get a message */ /* Determine if the monster resisted or not, if appropriate */ if (check_resist) resisted = mon_resist_effect(mon, ef_idx, timer, flag); if (resisted) m_note = MON_MSG_UNAFFECTED; else mon->m_timed[ef_idx] = timer; if (player->upkeep->health_who == mon) player->upkeep->redraw |= (PR_HEALTH); /* Update the visuals, as appropriate. */ player->upkeep->redraw |= (PR_MONLIST); /* Print a message if there is one, if the effect allows for it, and if * either the monster is visible, or we're trying to ID something */ if (m_note && !(flag & MON_TMD_FLG_NOMESSAGE) && (flag & MON_TMD_FLG_NOTIFY) && ((mflag_has(mon->mflag, MFLAG_VISIBLE) && !mflag_has(mon->mflag, MFLAG_UNAWARE)) || id)) { char m_name[80]; monster_desc(m_name, sizeof(m_name), mon, MDESC_IND_HID); add_monster_message(m_name, mon, m_note, true); } return !resisted; }
bool findpath(int y, int x) { int i, j, k; int dir = 10; bool try_again; int cur_distance; fill_terrain_info(); terrain[player->py - oy][player->px - ox] = 1; if ((x >= ox) && (x < ex) && (y >= oy) && (y < ey)) { if ((cave->squares[y][x].mon > 0) && mflag_has(square_monster(cave, y, x)->mflag, MFLAG_VISIBLE)) terrain[y - oy][x - ox] = MAX_PF_LENGTH; terrain[y - oy][x - ox] = MAX_PF_LENGTH; } else { bell("Target out of range."); return (FALSE); } /* * And now starts the very naive and very * inefficient pathfinding algorithm */ do { try_again = FALSE; for (j = oy + 1; j < ey - 1; j++) { for (i = ox + 1; i < ex - 1; i++) { cur_distance = terrain[j - oy][i - ox] + 1; if ((cur_distance > 0) && (cur_distance < MAX_PF_LENGTH)) { for (dir = 1; dir < 10; dir++) { int next_y, next_x; if (dir == 5) dir++; next_y = j - oy + ddy[dir]; next_x = i - ox + ddx[dir]; MARK_DISTANCE(terrain[next_y][next_x], cur_distance); } } } } if (terrain[y - oy][x - ox] < MAX_PF_LENGTH) try_again = FALSE; } while (try_again) ; /* Failure */ if (terrain[y - oy][x - ox] == MAX_PF_LENGTH) { bell("Target space unreachable."); return (FALSE); } /* Success */ i = x; j = y; pf_result_index = 0; while ((i != player->px) || (j != player->py)) { cur_distance = terrain[j - oy][i - ox] - 1; for (k = 0; k < 8; k++) { dir = dir_search[k]; if (terrain[j - oy + ddy[dir]][i - ox + ddx[dir]] == cur_distance) break; } /* Should never happen */ assert ((dir != 10) && (dir != 5)); pf_result[pf_result_index++] = '0' + (char)(10 - dir); i += ddx[dir]; j += ddy[dir]; } pf_result_index--; return (TRUE); }
/** * Work out if a monster can move through the grid, if necessary bashing * down doors in the way. * * Returns true if the monster is able to move through the grid. */ static bool process_monster_can_move(struct chunk *c, struct monster *mon, const char *m_name, int nx, int ny, bool *did_something) { struct monster_lore *lore = get_lore(mon->race); /* Only fiery creatures can handle lava */ if (square_isfiery(c, ny, nx) && !rf_has(mon->race->flags, RF_IM_FIRE)) return false; /* Floor is open? */ if (square_ispassable(c, ny, nx)) return true; /* Permanent wall in the way */ if (square_iswall(c, ny, nx) && square_isperm(c, ny, nx)) return false; /* Normal wall, door, or secret door in the way */ /* There's some kind of feature in the way, so learn about * kill-wall and pass-wall now */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) { rf_on(lore->flags, RF_PASS_WALL); rf_on(lore->flags, RF_KILL_WALL); } /* Monster may be able to deal with walls and doors */ if (rf_has(mon->race->flags, RF_PASS_WALL)) { return true; } else if (rf_has(mon->race->flags, RF_KILL_WALL)) { /* Remove the wall */ square_destroy_wall(c, ny, nx); /* Note changes to viewable region */ if (square_isview(c, ny, nx)) player->upkeep->update |= (PU_UPDATE_VIEW | PU_MONSTERS); /* Fully update the flow since terrain changed */ player->upkeep->update |= (PU_FORGET_FLOW | PU_UPDATE_FLOW); return true; } else if (square_iscloseddoor(c, ny, nx) || square_issecretdoor(c, ny, nx)) { bool may_bash = rf_has(mon->race->flags, RF_BASH_DOOR) && one_in_(2); /* Take a turn */ *did_something = true; /* Learn about door abilities */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) { rf_on(lore->flags, RF_OPEN_DOOR); rf_on(lore->flags, RF_BASH_DOOR); } /* Creature can open or bash doors */ if (!rf_has(mon->race->flags, RF_OPEN_DOOR) && !rf_has(mon->race->flags, RF_BASH_DOOR)) return false; /* Stuck door -- try to unlock it */ if (square_islockeddoor(c, ny, nx)) { int k = square_door_power(c, ny, nx); if (randint0(mon->hp / 10) > k) { if (may_bash) msg("%s slams against the door.", m_name); else msg("%s fiddles with the lock.", m_name); /* Reduce the power of the door by one */ square_set_door_lock(c, ny, nx, k - 1); } } else { /* Handle viewable doors */ if (square_isview(c, ny, nx)) player->upkeep->update |= (PU_UPDATE_VIEW | PU_MONSTERS); /* Closed or secret door -- open or bash if allowed */ if (may_bash) { square_smash_door(c, ny, nx); msg("You hear a door burst open!"); disturb(player, 0); /* Fall into doorway */ return true; } else if (rf_has(mon->race->flags, RF_OPEN_DOOR)) { square_open_door(c, ny, nx); } } } return false; }
/** * Examine a grid, return a keypress. * * The "mode" argument contains the "TARGET_LOOK" bit flag, which * indicates that the "space" key should scan through the contents * of the grid, instead of simply returning immediately. This lets * the "look" command get complete information, without making the * "target" command annoying. * * The "info" argument contains the "commands" which should be shown * inside the "[xxx]" text. This string must never be empty, or grids * containing monsters will be displayed with an extra comma. * * Note that if a monster is in the grid, we update both the monster * recall info and the health bar info to track that monster. * * This function correctly handles multiple objects per grid, and objects * and terrain features in the same grid, though the latter never happens. * * This function must handle blindness/hallucination. */ static ui_event target_set_interactive_aux(int y, int x, int mode) { struct object *obj = NULL; const char *s1, *s2, *s3; bool boring; int floor_max = z_info->floor_size; struct object **floor_list = mem_zalloc(floor_max * sizeof(*floor_list)); int floor_num; ui_event press; char out_val[TARGET_OUT_VAL_SIZE]; char coords[20]; const char *name; /* Describe the square location */ coords_desc(coords, sizeof(coords), y, x); /* Repeat forever */ while (1) { /* Paranoia */ press.type = EVT_KBRD; press.key.code = ' '; press.key.mods = 0; /* Assume boring */ boring = TRUE; /* Default */ s1 = "You see "; s2 = ""; s3 = ""; /* The player */ if (cave->squares[y][x].mon < 0) { /* Description */ s1 = "You are "; /* Preposition */ s2 = "on "; } /* Hallucination messes things up */ if (player->timed[TMD_IMAGE]) { const char *name = "something strange"; /* Display a message */ if (player->wizard) strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s (%d:%d, cost=%d, when=%d).", s1, s2, s3, name, coords, y, x, (int)cave->squares[y][x].cost, (int)cave->squares[y][x].when); else strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s.", s1, s2, s3, name, coords); prt(out_val, 0, 0); move_cursor_relative(y, x); press.key = inkey(); /* Stop on everything but "return" */ if (press.key.code == KC_ENTER) continue; mem_free(floor_list); return press; } /* Actual monsters */ if (cave->squares[y][x].mon > 0) { monster_type *m_ptr = square_monster(cave, y, x); const monster_lore *l_ptr = get_lore(m_ptr->race); /* Visible */ if (mflag_has(m_ptr->mflag, MFLAG_VISIBLE) && !mflag_has(m_ptr->mflag, MFLAG_UNAWARE)) { bool recall = FALSE; char m_name[80]; /* Not boring */ boring = FALSE; /* Get the monster name ("a kobold") */ monster_desc(m_name, sizeof(m_name), m_ptr, MDESC_IND_VIS); /* Hack -- track this monster race */ monster_race_track(player->upkeep, m_ptr->race); /* Hack -- health bar for this monster */ health_track(player->upkeep, m_ptr); /* Hack -- handle stuff */ handle_stuff(player); /* Interact */ while (1) { /* Recall or target */ if (recall) { lore_show_interactive(m_ptr->race, l_ptr); press = inkey_m(); } else { char buf[80]; /* Describe the monster */ look_mon_desc(buf, sizeof(buf), cave->squares[y][x].mon); /* Describe, and prompt for recall */ if (player->wizard) { strnfmt(out_val, sizeof(out_val), "%s%s%s%s (%s), %s (%d:%d, cost=%d, when=%d).", s1, s2, s3, m_name, buf, coords, y, x, (int)cave->squares[y][x].cost, (int)cave->squares[y][x].when); } else { strnfmt(out_val, sizeof(out_val), "%s%s%s%s (%s), %s.", s1, s2, s3, m_name, buf, coords); } prt(out_val, 0, 0); /* Place cursor */ move_cursor_relative(y, x); /* Command */ press = inkey_m(); } /* Normal commands */ if ((press.type == EVT_MOUSE) && (press.mouse.button == 1) && (KEY_GRID_X(press) == x) && (KEY_GRID_Y(press) == y)) recall = !recall; else if ((press.type == EVT_KBRD) && (press.key.code == 'r')) recall = !recall; else break; } if (press.type == EVT_MOUSE) { /* Stop on right click */ if (press.mouse.button == 2) break; /* Sometimes stop at "space" key */ if (press.mouse.button && !(mode & (TARGET_LOOK))) break; } else { /* Stop on everything but "return"/"space" */ if (press.key.code != KC_ENTER && press.key.code != ' ') break; /* Sometimes stop at "space" key */ if ((press.key.code == ' ') && !(mode & (TARGET_LOOK))) break; } /* Take account of gender */ if (rf_has(m_ptr->race->flags, RF_FEMALE)) s1 = "She is "; else if (rf_has(m_ptr->race->flags, RF_MALE)) s1 = "He is "; else s1 = "It is "; /* Use a verb */ s2 = "carrying "; /* Scan all objects being carried */ for (obj = m_ptr->held_obj; obj; obj = obj->next) { char o_name[80]; /* Obtain an object description */ object_desc(o_name, sizeof(o_name), obj, ODESC_PREFIX | ODESC_FULL); /* Describe the object */ if (player->wizard) { strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s (%d:%d, cost=%d, when=%d).", s1, s2, s3, o_name, coords, y, x, (int)cave->squares[y][x].cost, (int)cave->squares[y][x].when); } prt(out_val, 0, 0); move_cursor_relative(y, x); press = inkey_m(); if (press.type == EVT_MOUSE) { /* Stop on right click */ if (press.mouse.button == 2) break; /* Sometimes stop at "space" key */ if (press.mouse.button && !(mode & (TARGET_LOOK))) break; } else { /* Stop on everything but "return"/"space" */ if ((press.key.code != KC_ENTER) && (press.key.code != ' ')) break; /* Sometimes stop at "space" key */ if ((press.key.code == ' ') && !(mode & (TARGET_LOOK))) break; } /* Change the intro */ s2 = "also carrying "; } /* Double break */ if (obj) break; /* Use a preposition */ s2 = "on "; } } /* A trap */ if (square_isvisibletrap(cave, y, x)) { struct trap *trap = cave->squares[y][x].trap; /* Not boring */ boring = FALSE; /* Interact */ while (1) { /* Change the intro */ if (cave->squares[y][x].mon < 0) { s1 = "You are "; s2 = "on "; } else { s1 = "You see "; s2 = ""; } /* Pick proper indefinite article */ s3 = (is_a_vowel(trap->kind->desc[0])) ? "an " : "a "; /* Describe, and prompt for recall */ if (player->wizard) { strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s (%d:%d, cost=%d, when=%d).", s1, s2, s3, trap->kind->name, coords, y, x, (int)cave->squares[y][x].cost, (int)cave->squares[y][x].when); } else { strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s.", s1, s2, s3, trap->kind->desc, coords); } prt(out_val, 0, 0); /* Place cursor */ move_cursor_relative(y, x); /* Command */ press = inkey_m(); /* Stop on everything but "return"/"space" */ if ((press.key.code != KC_ENTER) && (press.key.code != ' ')) break; /* Sometimes stop at "space" key */ if ((press.key.code == ' ') && !(mode & (TARGET_LOOK))) break; } } /* Double break */ if (square_isvisibletrap(cave, y, x)) break; /* Assume not floored */ floor_num = scan_floor(floor_list, floor_max, y, x, 0x0A, NULL); /* Scan all marked objects in the grid */ if ((floor_num > 0) && (!(player->timed[TMD_BLIND]) || (y == player->py && x == player->px))) { /* Not boring */ boring = FALSE; track_object(player->upkeep, floor_list[0]); handle_stuff(player); /* If there is more than one item... */ if (floor_num > 1) while (1) { /* Describe the pile */ if (player->wizard) { strnfmt(out_val, sizeof(out_val), "%s%s%sa pile of %d objects, %s (%d:%d, cost=%d, when=%d).", s1, s2, s3, floor_num, coords, y, x, (int)cave->squares[y][x].cost, (int)cave->squares[y][x].when); } else { strnfmt(out_val, sizeof(out_val), "%s%s%sa pile of %d objects, %s.", s1, s2, s3, floor_num, coords); } prt(out_val, 0, 0); move_cursor_relative(y, x); press = inkey_m(); /* Display objects */ if (((press.type == EVT_MOUSE) && (press.mouse.button == 1) && (KEY_GRID_X(press) == x) && (KEY_GRID_Y(press) == y)) || ((press.type == EVT_KBRD) && (press.key.code == 'r'))) { int rdone = 0; int pos; while (!rdone) { /* Save screen */ screen_save(); /* Display */ show_floor(floor_list, floor_num, (OLIST_WEIGHT | OLIST_GOLD), NULL); /* Describe the pile */ prt(out_val, 0, 0); press = inkey_m(); /* Load screen */ screen_load(); if (press.type == EVT_MOUSE) { pos = press.mouse.y-1; } else { pos = press.key.code - 'a'; } if (0 <= pos && pos < floor_num) { track_object(player->upkeep, floor_list[pos]); handle_stuff(player); continue; } rdone = 1; } /* Now that the user's done with the display loop, * let's do the outer loop over again */ continue; } /* Done */ break; } /* Only one object to display */ else { /* Get the single object in the list */ struct object *obj = floor_list[0]; /* Allow user to recall an object */ press = target_recall_loop_object(obj, y, x, out_val, s1, s2, s3, coords); /* Stop on everything but "return"/"space" */ if ((press.key.code != KC_ENTER) && (press.key.code != ' ')) break; /* Sometimes stop at "space" key */ if ((press.key.code == ' ') && !(mode & (TARGET_LOOK))) break; /* Plurals */ s1 = VERB_AGREEMENT(obj->number, "It is ", "They are "); /* Preposition */ s2 = "on "; } } /* Double break */ if (obj) break; name = square_apparent_name(cave, player, y, x); /* Terrain feature if needed */ if (boring || square_isinteresting(cave, y, x)) { /* Hack -- handle unknown grids */ /* Pick a prefix */ if (*s2 && square_isdoor(cave, y, x)) s2 = "in "; /* Pick proper indefinite article */ s3 = (is_a_vowel(name[0])) ? "an " : "a "; /* Hack -- special introduction for store doors */ if (square_isshop(cave, y, x)) s3 = "the entrance to the "; /* Display a message */ if (player->wizard) { strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s (%d:%d, cost=%d, when=%d).", s1, s2, s3, name, coords, y, x, (int)cave->squares[y][x].cost, (int)cave->squares[y][x].when); } else { strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s.", s1, s2, s3, name, coords); } prt(out_val, 0, 0); move_cursor_relative(y, x); press = inkey_m(); if (press.type == EVT_MOUSE) { /* Stop on right click */ if (press.mouse.button == 2) break; } else { /* Stop on everything but "return"/"space" */ if ((press.key.code != KC_ENTER) && (press.key.code != ' ')) break; } } /* Stop on everything but "return" */ if (press.type == EVT_MOUSE) { /* Stop on right click */ if (press.mouse.button != 2) break; } else { if (press.key.code != KC_ENTER) break; } } mem_free(floor_list); /* Keep going */ return (press); }
/** * This function takes a grid location (x, y) and extracts information the * player is allowed to know about it, filling in the grid_data structure * passed in 'g'. * * The information filled in is as follows: * - g->f_idx is filled in with the terrain's feature type, or FEAT_NONE * if the player doesn't know anything about the grid. The function * makes use of the "mimic" field in terrain in order to allow one * feature to look like another (hiding secret doors, invisible traps, * etc). This will return the terrain type the player "Knows" about, * not necessarily the real terrain. * - g->m_idx is set to the monster index, or 0 if there is none (or the * player doesn't know it). * - g->first_kind is set to the object_kind of the first object in a grid * that the player knows about, or NULL for no objects. * - g->muliple_objects is TRUE if there is more than one object in the * grid that the player knows and cares about (to facilitate any special * floor stack symbol that might be used). * - g->in_view is TRUE if the player can currently see the grid - this can * be used to indicate field-of-view, such as through the OPT(view_bright_light) * option. * - g->lighting is set to indicate the lighting level for the grid: * LIGHTING_DARK for unlit grids, LIGHTING_LIT for inherently light * grids (lit rooms, etc), LIGHTING_TORCH for grids lit by the player's * light source, and LIGHTING_LOS for grids in the player's line of sight. * Note that lighting is always LIGHTING_LIT for known "interesting" grids * like walls. * - g->is_player is TRUE if the player is on the given grid. * - g->hallucinate is TRUE if the player is hallucinating something "strange" * for this grid - this should pick a random monster to show if the m_idx * is non-zero, and a random object if first_kind is non-zero. * * NOTES: * This is called pretty frequently, whenever a grid on the map display * needs updating, so don't overcomplicate it. * * Terrain is remembered separately from objects and monsters, so can be * shown even when the player can't "see" it. This leads to things like * doors out of the player's view still change from closed to open and so on. * * TODO: * Hallucination is currently disabled (it was a display-level hack before, * and we need it to be a knowledge-level hack). The idea is that objects * may turn into different objects, monsters into different monsters, and * terrain may be objects, monsters, or stay the same. */ void map_info(unsigned y, unsigned x, grid_data *g) { object_type *obj; assert(x < (unsigned) cave->width); assert(y < (unsigned) cave->height); /* Default "clear" values, others will be set later where appropriate. */ g->first_kind = NULL; g->trap = NULL; g->multiple_objects = FALSE; g->lighting = LIGHTING_DARK; g->unseen_object = FALSE; g->unseen_money = FALSE; /* Use real feature (remove later) */ g->f_idx = cave->squares[y][x].feat; if (f_info[g->f_idx].mimic) g->f_idx = f_info[g->f_idx].mimic; g->in_view = (square_isseen(cave, y, x)) ? TRUE : FALSE; g->is_player = (cave->squares[y][x].mon < 0) ? TRUE : FALSE; g->m_idx = (g->is_player) ? 0 : cave->squares[y][x].mon; g->hallucinate = player->timed[TMD_IMAGE] ? TRUE : FALSE; g->trapborder = (square_isdedge(cave, y, x)) ? TRUE : FALSE; if (g->in_view) { g->lighting = LIGHTING_LOS; if (!square_isglow(cave, y, x) && OPT(view_yellow_light)) g->lighting = LIGHTING_TORCH; /* Remember seen feature */ cave_k->squares[y][x].feat = cave->squares[y][x].feat; } else if (!square_ismark(cave, y, x)) { g->f_idx = FEAT_NONE; //cave_k->squares[y][x].feat = FEAT_NONE; } else if (square_isglow(cave, y, x)) { g->lighting = LIGHTING_LIT; } /* Use known feature */ /* g->f_idx = cave_k->squares[y][x].feat; if (f_info[g->f_idx].mimic) g->f_idx = f_info[g->f_idx].mimic;*/ /* There is a trap in this square */ if (square_istrap(cave, y, x) && square_ismark(cave, y, x)) { struct trap *trap = cave->squares[y][x].trap; /* Scan the square trap list */ while (trap) { if (trf_has(trap->flags, TRF_TRAP) || trf_has(trap->flags, TRF_RUNE)) { /* Accept the trap */ g->trap = trap; break; } trap = trap->next; } } /* Objects */ for (obj = square_object(cave, y, x); obj; obj = obj->next) { if (obj->marked == MARK_AWARE) { /* Distinguish between unseen money and objects */ if (tval_is_money(obj)) { g->unseen_money = TRUE; } else { g->unseen_object = TRUE; } } else if (obj->marked == MARK_SEEN && !ignore_item_ok(obj)) { if (!g->first_kind) { g->first_kind = obj->kind; } else { g->multiple_objects = TRUE; break; } } } /* Monsters */ if (g->m_idx > 0) { /* If the monster isn't "visible", make sure we don't list it.*/ monster_type *m_ptr = cave_monster(cave, g->m_idx); if (!mflag_has(m_ptr->mflag, MFLAG_VISIBLE)) g->m_idx = 0; } /* Rare random hallucination on non-outer walls */ if (g->hallucinate && g->m_idx == 0 && g->first_kind == 0) { if (one_in_(128) && (int) g->f_idx != FEAT_PERM) g->m_idx = 1; else if (one_in_(128) && (int) g->f_idx != FEAT_PERM) /* if hallucinating, we just need first_kind to not be NULL */ g->first_kind = k_info; else g->hallucinate = FALSE; } assert((int) g->f_idx <= FEAT_PASS_RUBBLE); if (!g->hallucinate) assert((int)g->m_idx < cave->mon_max); /* All other g fields are 'flags', mostly booleans. */ }
/** * Determines whether the given monster successfully resists the given effect. * * If MON_TMD_FLG_NOFAIL is set in `flag`, this returns false. * Then we determine if the monster resists the effect for some racial * reason. For example, the monster might have the NO_SLEEP flag, in which * case it always resists sleep. Or if it breathes chaos, it always resists * confusion. If the given monster doesn't resist for any of these reasons, * then it makes a saving throw. If MON_TMD_MON_SOURCE is set in `flag`, * indicating that another monster caused this effect, then the chance of * success on the saving throw just depends on the monster's native depth. * Otherwise, the chance of success decreases as `timer` increases. * * Also marks the lore for any appropriate resists. */ static bool mon_resist_effect(const struct monster *mon, int ef_idx, int timer, u16b flag) { struct mon_timed_effect *effect; int resist_chance; struct monster_lore *lore; assert(ef_idx >= 0 && ef_idx < MON_TMD_MAX); assert(mon); effect = &effects[ef_idx]; lore = get_lore(mon->race); /* Hasting never fails */ if (ef_idx == MON_TMD_FAST) return (false); /* Some effects are marked to never fail */ if (flag & MON_TMD_FLG_NOFAIL) return (false); /* A sleeping monster resists further sleeping */ if (ef_idx == MON_TMD_SLEEP && mon->m_timed[ef_idx]) return (true); /* If the monster resists innately, learn about it */ if (rf_has(mon->race->flags, effect->flag_resist)) { if (mflag_has(mon->mflag, MFLAG_VISIBLE)) rf_on(lore->flags, effect->flag_resist); return (true); } /* Monsters with specific breaths resist stunning */ if (ef_idx == MON_TMD_STUN && (rsf_has(mon->race->spell_flags, RSF_BR_SOUN) || rsf_has(mon->race->spell_flags, RSF_BR_WALL))) { /* Add the lore */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) { if (rsf_has(mon->race->spell_flags, RSF_BR_SOUN)) rsf_on(lore->spell_flags, RSF_BR_SOUN); if (rsf_has(mon->race->spell_flags, RSF_BR_WALL)) rsf_on(lore->spell_flags, RSF_BR_WALL); } return (true); } /* Monsters with specific breaths resist confusion */ if ((ef_idx == MON_TMD_CONF) && rsf_has(mon->race->spell_flags, RSF_BR_CHAO)) { /* Add the lore */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) if (rsf_has(mon->race->spell_flags, RSF_BR_CHAO)) rsf_on(lore->spell_flags, RSF_BR_CHAO); return (true); } /* Inertia breathers resist slowing */ if (ef_idx == MON_TMD_SLOW && rsf_has(mon->race->spell_flags, RSF_BR_INER)){ rsf_on(lore->spell_flags, RSF_BR_INER); return (true); } /* Calculate the chance of the monster making its saving throw. */ if (ef_idx == MON_TMD_SLEEP) timer /= 25; /* Hack - sleep uses much bigger numbers */ if (flag & MON_TMD_MON_SOURCE) resist_chance = mon->race->level; else resist_chance = mon->race->level + 40 - (timer / 2); if (randint0(100) < resist_chance) return (true); /* Uniques are doubly hard to affect */ if (rf_has(mon->race->flags, RF_UNIQUE)) if (randint0(100) < resist_chance) return (true); return (false); }
/** * Grab all objects from the grid. */ void process_monster_grab_objects(struct chunk *c, struct monster *mon, const char *m_name, int nx, int ny) { struct monster_lore *lore = get_lore(mon->race); struct object *obj; bool visible = mflag_has(mon->mflag, MFLAG_VISIBLE); /* Learn about item pickup behavior */ for (obj = square_object(c, ny, nx); obj; obj = obj->next) { if (!tval_is_money(obj) && visible) { rf_on(lore->flags, RF_TAKE_ITEM); rf_on(lore->flags, RF_KILL_ITEM); break; } } /* Abort if can't pickup/kill */ if (!rf_has(mon->race->flags, RF_TAKE_ITEM) && !rf_has(mon->race->flags, RF_KILL_ITEM)) { return; } /* Take or kill objects on the floor */ obj = square_object(c, ny, nx); while (obj) { char o_name[80]; bool safe = obj->artifact ? true : false; struct object *next = obj->next; /* Skip gold */ if (tval_is_money(obj)) { obj = next; continue; } /* Skip mimicked objects */ if (obj->mimicking_m_idx) { obj = next; continue; } /* Get the object name */ object_desc(o_name, sizeof(o_name), obj, ODESC_PREFIX | ODESC_FULL); /* React to objects that hurt the monster */ if (react_to_slay(obj, mon)) safe = true; /* Try to pick up, or crush */ if (safe) { /* Only give a message for "take_item" */ if (rf_has(mon->race->flags, RF_TAKE_ITEM) && visible && square_isview(c, ny, nx) && !ignore_item_ok(obj)) { /* Dump a message */ msg("%s tries to pick up %s, but fails.", m_name, o_name); } } else if (rf_has(mon->race->flags, RF_TAKE_ITEM)) { /* Describe observable situations */ if (square_isseen(c, ny, nx) && !ignore_item_ok(obj)) msg("%s picks up %s.", m_name, o_name); /* Carry the object */ square_excise_object(c, ny, nx, obj); monster_carry(c, mon, obj); square_note_spot(c, ny, nx); square_light_spot(c, ny, nx); } else { /* Describe observable situations */ if (square_isseen(c, ny, nx) && !ignore_item_ok(obj)) msgt(MSG_DESTROY, "%s crushes %s.", m_name, o_name); /* Delete the object */ square_excise_object(c, ny, nx, obj); delist_object(c, obj); object_delete(&obj); square_note_spot(c, ny, nx); square_light_spot(c, ny, nx); } /* Next object */ obj = next; } }
/** * Process a monster * * In several cases, we directly update the monster lore * * Note that a monster is only allowed to "reproduce" if there * are a limited number of "reproducing" monsters on the current * level. This should prevent the level from being "swamped" by * reproducing monsters. It also allows a large mass of mice to * prevent a louse from multiplying, but this is a small price to * pay for a simple multiplication method. * * XXX Monster fear is slightly odd, in particular, monsters will * fixate on opening a door even if they cannot open it. Actually, * the same thing happens to normal monsters when they hit a door * * In addition, monsters which *cannot* open or bash down a door * will still stand there trying to open it... XXX XXX XXX * * Technically, need to check for monster in the way combined * with that monster being in a wall (or door?) XXX */ static void process_monster(struct chunk *c, struct monster *mon) { struct monster_lore *lore = get_lore(mon->race); bool did_something = false; int i; int dir = 0; bool stagger = false; char m_name[80]; /* Get the monster name */ monster_desc(m_name, sizeof(m_name), mon, MDESC_CAPITAL | MDESC_IND_HID); /* Try to multiply - this can use up a turn */ if (process_monster_multiply(c, mon)) return; /* Attempt to cast a spell */ if (make_attack_spell(mon)) return; /* Work out what kind of movement to use - AI or staggered movement */ if (!process_monster_should_stagger(mon)) { if (!get_moves(c, mon, &dir)) return; } else { stagger = true; } /* Process moves */ for (i = 0; i < 5 && !did_something; i++) { int oy = mon->fy; int ox = mon->fx; /* Get the direction (or stagger) */ int d = (stagger ? ddd[randint0(8)] : side_dirs[dir][i]); /* Get the destination */ int ny = oy + ddy[d]; int nx = ox + ddx[d]; /* Check if we can move */ if (!process_monster_can_move(c, mon, m_name, nx, ny, &did_something)) continue; /* Try to break the glyph if there is one. This can happen multiple * times per turn because failure does not break the loop */ if (square_iswarded(c, ny, nx) && !process_monster_glyph(c, mon, nx, ny)) continue; /* The player is in the way. */ if (square_isplayer(c, ny, nx)) { /* Learn about if the monster attacks */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) rf_on(lore->flags, RF_NEVER_BLOW); /* Some monsters never attack */ if (rf_has(mon->race->flags, RF_NEVER_BLOW)) continue; /* Otherwise, attack the player */ make_attack_normal(mon, player); did_something = true; break; } else { /* Some monsters never move */ if (rf_has(mon->race->flags, RF_NEVER_MOVE)) { /* Learn about lack of movement */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) rf_on(lore->flags, RF_NEVER_MOVE); return; } } /* A monster is in the way, try to push past/kill */ if (square_monster(c, ny, nx)) { did_something = process_monster_try_push(c, mon, m_name, nx, ny); } else { /* Otherwise we can just move */ monster_swap(oy, ox, ny, nx); did_something = true; } /* Scan all objects in the grid, if we reached it */ if (mon == square_monster(c, ny, nx)) { monster_desc(m_name, sizeof(m_name), mon, MDESC_CAPITAL | MDESC_IND_HID); process_monster_grab_objects(c, mon, m_name, nx, ny); } } if (did_something) { /* Learn about no lack of movement */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) rf_on(lore->flags, RF_NEVER_MOVE); /* Possible disturb */ if (mflag_has(mon->mflag, MFLAG_VISIBLE) && mflag_has(mon->mflag, MFLAG_VIEW) && OPT(player, disturb_near)) disturb(player, 0); } /* Hack -- get "bold" if out of options */ if (!did_something && mon->m_timed[MON_TMD_FEAR]) mon_clear_timed(mon, MON_TMD_FEAR, MON_TMD_FLG_NOTIFY, false); /* If we see an unaware monster do something, become aware of it */ if (did_something && mflag_has(mon->mflag, MFLAG_UNAWARE)) become_aware(mon); }
/** * Collect monster information from the current cave's monster list. */ void monster_list_collect(monster_list_t *list) { int i; if (list == NULL || list->entries == NULL) return; /* Use cave_monster_max() here in case the monster list isn't compacted. */ for (i = 1; i < cave_monster_max(cave); i++) { struct monster *mon = cave_monster(cave, i); monster_list_entry_t *entry = NULL; int j, field; bool los = FALSE; /* Only consider visible, known monsters */ if (!mflag_has(mon->mflag, MFLAG_VISIBLE) || mflag_has(mon->mflag, MFLAG_UNAWARE)) continue; /* Find or add a list entry. */ for (j = 0; j < (int)list->entries_size; j++) { if (list->entries[j].race == NULL) { /* We found an empty slot, so add this race here. */ entry = &list->entries[j]; memset(entry, 0, sizeof(monster_list_entry_t)); entry->race = mon->race; break; } else if (list->entries[j].race == mon->race) { /* We found a matching race and we'll use that. */ entry = &list->entries[j]; break; } } if (entry == NULL) continue; /* Always collect the latest monster attribute so that flicker * animation works. If this is 0, it needs to be replaced by * the standard glyph in the UI */ entry->attr = mon->attr; /* Skip the projection and location checks if nothing has changed. */ if (!monster_list_needs_update(list)) continue; /* * Check for LOS * Hack - we should use (mon->mflag & (MFLAG_VIEW)) here, * but this does not catch monsters detected by ESP which are * targetable, so we cheat and use projectable() instead */ los = projectable(cave, player->py, player->px, mon->fy, mon->fx, PROJECT_NONE); field = (los) ? MONSTER_LIST_SECTION_LOS : MONSTER_LIST_SECTION_ESP; entry->count[field]++; if (mon->m_timed[MON_TMD_SLEEP] > 0) entry->asleep[field]++; /* Store the location offset from the player; this is only used for * monster counts of 1 */ entry->dx = mon->fx - player->px; entry->dy = mon->fy - player->py; } /* Skip calculations if nothing has changed, otherwise this will yield * incorrect numbers. */ if (!monster_list_needs_update(list)) return; /* Collect totals for easier calculations of the list. */ for (i = 0; i < (int)list->entries_size; i++) { if (list->entries[i].race == NULL) continue; if (list->entries[i].count[MONSTER_LIST_SECTION_LOS] > 0) list->total_entries[MONSTER_LIST_SECTION_LOS]++; if (list->entries[i].count[MONSTER_LIST_SECTION_ESP] > 0) list->total_entries[MONSTER_LIST_SECTION_ESP]++; list->total_monsters[MONSTER_LIST_SECTION_LOS] += list->entries[i].count[MONSTER_LIST_SECTION_LOS]; list->total_monsters[MONSTER_LIST_SECTION_ESP] += list->entries[i].count[MONSTER_LIST_SECTION_ESP]; list->distinct_entries++; } list->creation_turn = turn; list->sorted = FALSE; }
/** * Draw a visible path over the squares between (x1,y1) and (x2,y2). * * The path consists of "*", which are white except where there is a * monster, object or feature in the grid. * * This routine has (at least) three weaknesses: * - remembered objects/walls which are no longer present are not shown, * - squares which (e.g.) the player has walked through in the dark are * treated as unknown space. * - walls which appear strange due to hallucination aren't treated correctly. * * The first two result from information being lost from the dungeon arrays, * which requires changes elsewhere */ static int draw_path(u16b path_n, struct loc *path_g, wchar_t *c, int *a, int y1, int x1) { int i; bool on_screen; /* No path, so do nothing. */ if (path_n < 1) return 0; /* The starting square is never drawn, but notice if it is being * displayed. In theory, it could be the last such square. */ on_screen = panel_contains(y1, x1); /* Draw the path. */ for (i = 0; i < path_n; i++) { byte colour; /* Find the co-ordinates on the level. */ int y = path_g[i].y; int x = path_g[i].x; struct monster *mon = square_monster(cave, y, x); struct object *obj = square_object(cave, y, x); /* * As path[] is a straight line and the screen is oblong, * there is only section of path[] on-screen. * If the square being drawn is visible, this is part of it. * If none of it has been drawn, continue until some of it * is found or the last square is reached. * If some of it has been drawn, finish now as there are no * more visible squares to draw. */ if (panel_contains(y,x)) on_screen = TRUE; else if (on_screen) break; else continue; /* Find the position on-screen */ move_cursor_relative(y,x); /* This square is being overwritten, so save the original. */ Term_what(Term->scr->cx, Term->scr->cy, a+i, c+i); /* Choose a colour. */ if (mon && mflag_has(mon->mflag, MFLAG_VISIBLE)) { /* Mimics act as objects */ if (rf_has(mon->race->flags, RF_UNAWARE)) colour = COLOUR_YELLOW; else /* Visible monsters are red. */ colour = COLOUR_L_RED; } else if (obj && obj->marked) /* Known objects are yellow. */ colour = COLOUR_YELLOW; else if ((!square_isprojectable(cave, y,x) && square_ismark(cave, y, x)) || square_isseen(cave, y, x)) /* Known walls are blue. */ colour = COLOUR_BLUE; else if (!square_ismark(cave, y, x) && !square_isseen(cave, y, x)) /* Unknown squares are grey. */ colour = COLOUR_L_DARK; else /* Unoccupied squares are white. */ colour = COLOUR_WHITE; /* Draw the path segment */ (void)Term_addch(colour, L'*'); } return i; }