/** * Calls a monster from the level and moves it to the desired spot */ int call_monster(int y, int x) { int i, mon_count, choice; int oy, ox; int *mon_indices; struct monster *mon; mon_count = 0; for (i = 1; i < cave_monster_max(cave); i++) { mon = cave_monster(cave, i); /* Figure out how many good monsters there are */ if (can_call_monster(y, x, mon)) mon_count++; } /* There were no good monsters on the level */ if (mon_count == 0) return (0); /* Make the array */ mon_indices = mem_zalloc(mon_count * sizeof(int)); /* Reset mon_count */ mon_count = 0; /* Now go through a second time and store the indices */ for (i = 1; i < cave_monster_max(cave); i++) { mon = cave_monster(cave, i); /* Save the values of the good monster */ if (can_call_monster(y, x, mon)){ mon_indices[mon_count] = i; mon_count++; } } /* Pick one */ choice = randint0(mon_count - 1); /* Get the lucky monster */ mon = cave_monster(cave, mon_indices[choice]); mem_free(mon_indices); /* Extract monster location */ oy = mon->fy; ox = mon->fx; /* Swap the moster */ monster_swap(oy, ox, y, x); /* Wake it up */ mon_clear_timed(mon, MON_TMD_SLEEP, MON_TMD_FLG_NOMESSAGE, false); /* Set it's energy to 0 */ mon->energy = 0; return (mon->race->level); }
/** * Wake a monster or reduce its depth of sleep * * Chance of waking up is dependent only on the player's stealth, but the * amount of sleep reduction takes into account the monster's distance from * the player. Currently straight line distance is used; possibly this * should take into account dungeon structure. */ static void monster_reduce_sleep(struct chunk *c, struct monster *mon) { bool woke_up = false; int stealth = player->state.skills[SKILL_STEALTH]; int player_noise = 1 << (30 - stealth); int notice = randint0(1024); struct monster_lore *lore = get_lore(mon->race); /* 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 (monster_is_obvious(mon)) msg("%s wakes up.", m_name); woke_up = true; } else if ((notice * notice * notice) <= player_noise) { int sleep_reduction = 1; int local_noise = c->noise.grids[mon->fy][mon->fx]; /* Test - wake up faster in hearing distance of the player * Note no dependence on stealth for now */ if ((local_noise > 0) && (local_noise < 50)) { sleep_reduction = (100 / local_noise); } /* Note a complete wakeup */ if (mon->m_timed[MON_TMD_SLEEP] <= sleep_reduction) { woke_up = true; } /* Monster wakes up a bit */ mon_dec_timed(mon, MON_TMD_SLEEP, sleep_reduction, MON_TMD_FLG_NOTIFY, false); /* Update knowledge */ if (monster_is_obvious(mon)) { if (!woke_up && lore->ignore < UCHAR_MAX) lore->ignore++; else if (woke_up && lore->wake < UCHAR_MAX) lore->wake++; lore_update(mon->race, lore); } } }
/** * This routine will Perma-Light all grids in the set passed in. * * This routine is used (only) by "light_room()" * * Dark grids are illuminated. * * Also, process all affected monsters. * * SMART monsters always wake up when illuminated * NORMAL monsters wake up 1/4 the time when illuminated * STUPID monsters wake up 1/10 the time when illuminated */ static void cave_light(struct point_set *ps) { int i; /* Apply flag changes */ for (i = 0; i < ps->n; i++) { int y = ps->pts[i].y; int x = ps->pts[i].x; /* Perma-Light */ sqinfo_on(cave->squares[y][x].info, SQUARE_GLOW); } /* Fully update the visuals */ player->upkeep->update |= (PU_FORGET_VIEW | PU_UPDATE_VIEW | PU_MONSTERS); /* Update stuff */ update_stuff(player); /* Process the grids */ for (i = 0; i < ps->n; i++) { int y = ps->pts[i].y; int x = ps->pts[i].x; /* Redraw the grid */ square_light_spot(cave, y, x); /* Process affected monsters */ if (cave->squares[y][x].mon > 0) { int chance = 25; monster_type *m_ptr = square_monster(cave, y, x); /* Stupid monsters rarely wake up */ if (rf_has(m_ptr->race->flags, RF_STUPID)) chance = 10; /* Smart monsters always wake up */ if (rf_has(m_ptr->race->flags, RF_SMART)) chance = 100; /* Sometimes monsters wake up */ if (m_ptr->m_timed[MON_TMD_SLEEP] && (randint0(100) < chance)) { /* Wake up! */ mon_clear_timed(m_ptr, MON_TMD_SLEEP, MON_TMD_FLG_NOTIFY, FALSE); } } } }
/** * This routine will Perma-Light all grids in the set passed in. * * This routine is used (only) by "light_room()" * * Dark grids are illuminated. * * Also, process all affected monsters. * * SMART monsters always wake up when illuminated * NORMAL monsters wake up 1/4 the time when illuminated * STUPID monsters wake up 1/10 the time when illuminated */ static void cave_light(struct point_set *ps) { int i; /* Apply flag changes */ for (i = 0; i < ps->n; i++) { int y = ps->pts[i].y; int x = ps->pts[i].x; /* Perma-Light */ sqinfo_on(cave->squares[y][x].info, SQUARE_GLOW); } /* Process the grids */ for (i = 0; i < ps->n; i++) { int y = ps->pts[i].y; int x = ps->pts[i].x; /* Redraw the grid */ square_light_spot(cave, y, x); /* Process affected monsters */ if (cave->squares[y][x].mon > 0) { int chance = 25; struct monster *mon = square_monster(cave, y, x); /* Stupid monsters rarely wake up */ if (monster_is_stupid(mon)) chance = 10; /* Smart monsters always wake up */ if (monster_is_smart(mon)) chance = 100; /* Sometimes monsters wake up */ if (mon->m_timed[MON_TMD_SLEEP] && (randint0(100) < chance)) { mon_clear_timed(mon, MON_TMD_SLEEP, MON_TMD_FLG_NOTIFY, false); } } } }
/** * Monster self-healing. * * \param m_ptr is the monster casting * \param rlev is its level * \param seen is whether @ can see it */ static void heal_self(struct monster *m_ptr, int rlev, bool seen) { char m_name[80], m_poss[80]; /* Get the monster name (or "it") */ monster_desc(m_name, sizeof(m_name), m_ptr, MDESC_STANDARD); /* Get the monster possessive ("his"/"her"/"its") */ monster_desc(m_poss, sizeof(m_poss), m_ptr, MDESC_PRO_VIS | MDESC_POSS); /* Heal some */ m_ptr->hp += (rlev * 6); /* Fully healed */ if (m_ptr->hp >= m_ptr->maxhp) { m_ptr->hp = m_ptr->maxhp; if (seen) msg("%s looks REALLY healthy!", m_name); else msg("%s sounds REALLY healthy!", m_name); } else if (seen) { /* Partially healed */ msg("%s looks healthier.", m_name); } else { msg("%s sounds healthier.", m_name); } /* Redraw (later) if needed */ if (p_ptr->health_who == m_ptr) p_ptr->redraw |= (PR_HEALTH); /* Cancel fear */ if (m_ptr->m_timed[MON_TMD_FEAR]) { mon_clear_timed(m_ptr, MON_TMD_FEAR, MON_TMD_FLG_NOMESSAGE, FALSE); msg("%s recovers %s courage.", m_name, m_poss); } }
/** * Attack the monster at the given location with a single blow. */ static bool py_attack_real(int y, int x, bool *fear) { /* Information about the target of the attack */ monster_type *m_ptr = cave_monster(cave, cave->m_idx[y][x]); monster_race *r_ptr = &r_info[m_ptr->r_idx]; char m_name[80]; bool stop = FALSE; /* The weapon used */ object_type *o_ptr = &p_ptr->inventory[INVEN_WIELD]; /* Information about the attack */ int bonus = p_ptr->state.to_h + o_ptr->to_h; int chance = p_ptr->state.skills[SKILL_TO_HIT_MELEE] + bonus * BTH_PLUS_ADJ; bool do_quake = FALSE; bool success = FALSE; /* Default to punching for one damage */ const char *hit_verb = "punch"; int dmg = 1; u32b msg_type = MSG_HIT; /* Extract monster name (or "it") */ monster_desc(m_name, sizeof(m_name), m_ptr, 0); /* Auto-Recall if possible and visible */ if (m_ptr->ml) monster_race_track(m_ptr->r_idx); /* Track a new monster */ if (m_ptr->ml) health_track(p_ptr, cave->m_idx[y][x]); /* Handle player fear (only for invisible monsters) */ if (check_state(p_ptr, OF_AFRAID, p_ptr->state.flags)) { msgt(MSG_AFRAID, "You are too afraid to attack %s!", m_name); return FALSE; } /* Disturb the monster */ mon_clear_timed(cave->m_idx[y][x], MON_TMD_SLEEP, MON_TMD_FLG_NOMESSAGE); /* See if the player hit */ success = test_hit(chance, r_ptr->ac, m_ptr->ml); /* If a miss, skip this hit */ if (!success) { msgt(MSG_MISS, "You miss %s.", m_name); return FALSE; } /* Handle normal weapon */ if (o_ptr->kind) { int i; const struct slay *best_s_ptr = NULL; hit_verb = "hit"; /* Get the best attack from all slays or * brands on all non-launcher equipment */ for (i = INVEN_LEFT; i < INVEN_TOTAL; i++) { struct object *obj = &p_ptr->inventory[i]; if (obj->kind) improve_attack_modifier(obj, m_ptr, &best_s_ptr, TRUE, FALSE); } improve_attack_modifier(o_ptr, m_ptr, &best_s_ptr, TRUE, FALSE); if (best_s_ptr != NULL) hit_verb = best_s_ptr->melee_verb; dmg = damroll(o_ptr->dd, o_ptr->ds); dmg *= (best_s_ptr == NULL) ? 1 : best_s_ptr->mult; dmg += o_ptr->to_d; dmg = critical_norm(o_ptr->weight, o_ptr->to_h, dmg, &msg_type); /* Learn by use for the weapon */ object_notice_attack_plusses(o_ptr); if (check_state(p_ptr, OF_IMPACT, p_ptr->state.flags) && dmg > 50) { do_quake = TRUE; wieldeds_notice_flag(p_ptr, OF_IMPACT); } } /* Learn by use for other equipped items */ wieldeds_notice_on_attack(); /* Apply the player damage bonuses */ dmg += p_ptr->state.to_d; /* No negative damage */ if (dmg <= 0) dmg = 0; /* Tell the player what happened */ if (dmg <= 0) msgt(MSG_MISS, "You fail to harm %s.", m_name); else if (msg_type == MSG_HIT) msgt(MSG_HIT, "You %s %s.", hit_verb, m_name); else if (msg_type == MSG_HIT_GOOD) msgt(MSG_HIT_GOOD, "You %s %s. %s", hit_verb, m_name, "It was a good hit!"); else if (msg_type == MSG_HIT_GREAT) msgt(MSG_HIT_GREAT, "You %s %s. %s", hit_verb, m_name, "It was a great hit!"); else if (msg_type == MSG_HIT_SUPERB) msgt(MSG_HIT_SUPERB, "You %s %s. %s", hit_verb, m_name, "It was a superb hit!"); else if (msg_type == MSG_HIT_HI_GREAT) msgt(MSG_HIT_HI_GREAT, "You %s %s. %s", hit_verb, m_name, "It was a *GREAT* hit!"); else if (msg_type == MSG_HIT_HI_SUPERB) msgt(MSG_HIT_HI_SUPERB, "You %s %s. %s", hit_verb, m_name, "It was a *SUPERB* hit!"); /* Complex message */ if (p_ptr->wizard) msg("You do %d (out of %d) damage.", dmg, m_ptr->hp); /* Confusion attack */ if (p_ptr->confusing) { p_ptr->confusing = FALSE; msg("Your hands stop glowing."); mon_inc_timed(cave->m_idx[y][x], MON_TMD_CONF, (10 + randint0(p_ptr->lev) / 10), MON_TMD_FLG_NOTIFY); } /* Damage, check for fear and death */ stop = mon_take_hit(cave->m_idx[y][x], dmg, fear, NULL); if (stop) (*fear) = FALSE; /* Apply earthquake brand */ if (do_quake) { earthquake(p_ptr->py, p_ptr->px, 10); if (cave->m_idx[y][x] == 0) stop = TRUE; } return stop; }
/** * Move player in the given direction. * * This routine should only be called when energy has been expended. * * Note that this routine handles monsters in the destination grid, * and also handles attempting to move into walls/doors/rubble/etc. */ void move_player(int dir, bool disarm) { int y = player->py + ddy[dir]; int x = player->px + ddx[dir]; int m_idx = cave->squares[y][x].mon; struct monster *mon = cave_monster(cave, m_idx); bool alterable = (square_isknowntrap(cave, y, x) || square_iscloseddoor(cave, y, x)); /* Attack monsters, alter traps/doors on movement, hit obstacles or move */ if (m_idx > 0) { /* Mimics surprise the player */ if (is_mimicking(mon)) { become_aware(mon); /* Mimic wakes up */ mon_clear_timed(mon, MON_TMD_SLEEP, MON_TMD_FLG_NOMESSAGE, false); } else { py_attack(y, x); } } else if (disarm && square_isknown(cave, y, x) && alterable) { /* Auto-repeat if not already repeating */ if (cmd_get_nrepeats() == 0) cmd_set_repeat(99); do_cmd_alter_aux(dir); } else if (player->upkeep->running && square_isknowntrap(cave, y, x)) { /* Stop running before known traps */ disturb(player, 0); } else if (!square_ispassable(cave, y, x)) { disturb(player, 0); /* Notice unknown obstacles, mention known obstacles */ if (!square_isknown(cave, y, x)) { if (square_isrubble(cave, y, x)) { msgt(MSG_HITWALL, "You feel a pile of rubble blocking your way."); square_memorize(cave, y, x); square_light_spot(cave, y, x); } else if (square_iscloseddoor(cave, y, x)) { msgt(MSG_HITWALL, "You feel a door blocking your way."); square_memorize(cave, y, x); square_light_spot(cave, y, x); } else { msgt(MSG_HITWALL, "You feel a wall blocking your way."); square_memorize(cave, y, x); square_light_spot(cave, y, x); } } else { if (square_isrubble(cave, y, x)) msgt(MSG_HITWALL, "There is a pile of rubble blocking your way."); else if (square_iscloseddoor(cave, y, x)) msgt(MSG_HITWALL, "There is a door blocking your way."); else msgt(MSG_HITWALL, "There is a wall blocking your way."); } } else { /* Move player */ monster_swap(player->py, player->px, y, x); /* Handle store doors, or notice objects */ if (square_isshop(cave, y, x)) { disturb(player, 0); event_signal(EVENT_ENTER_STORE); event_remove_handler_type(EVENT_ENTER_STORE); event_signal(EVENT_USE_STORE); event_remove_handler_type(EVENT_USE_STORE); event_signal(EVENT_LEAVE_STORE); event_remove_handler_type(EVENT_LEAVE_STORE); } else { square_know_pile(cave, y, x); cmdq_push(CMD_AUTOPICKUP); } /* Discover invisible traps, set off visible ones */ if (square_issecrettrap(cave, y, x)) { disturb(player, 0); hit_trap(y, x); } else if (square_isknowntrap(cave, y, x)) { disturb(player, 0); hit_trap(y, x); } /* Update view and search */ update_view(cave, player); search(); } player->upkeep->running_firststep = false; }
/** * Open a closed/locked/jammed door or a closed/locked chest. * * Unlocking a locked chest is worth one experience point; since doors are * player lockable, there is no experience for unlocking doors. */ void do_cmd_open(struct command *cmd) { int y, x, dir; struct object *obj; bool more = false; int err; struct monster *m; /* Get arguments */ err = cmd_get_arg_direction(cmd, "direction", &dir); if (err || dir == DIR_UNKNOWN) { int y2, x2; int n_closed_doors, n_locked_chests; n_closed_doors = count_feats(&y2, &x2, square_iscloseddoor, false); n_locked_chests = count_chests(&y2, &x2, CHEST_OPENABLE); if (n_closed_doors + n_locked_chests == 1) { dir = coords_to_dir(y2, x2); cmd_set_arg_direction(cmd, "direction", dir); } else if (cmd_get_direction(cmd, "direction", &dir, false)) { return; } } /* Get location */ y = player->py + ddy[dir]; x = player->px + ddx[dir]; /* Check for chest */ obj = chest_check(y, x, CHEST_OPENABLE); /* Check for door */ if (!obj && !do_cmd_open_test(y, x)) { /* Cancel repeat */ disturb(player, 0); return; } /* Take a turn */ player->upkeep->energy_use = z_info->move_energy; /* Apply confusion */ if (player_confuse_dir(player, &dir, false)) { /* Get location */ y = player->py + ddy[dir]; x = player->px + ddx[dir]; /* Check for chest */ obj = chest_check(y, x, CHEST_OPENABLE); } /* Monster */ m = square_monster(cave, y, x); if (m) { /* Mimics surprise the player */ if (is_mimicking(m)) { become_aware(m); /* Mimic wakes up */ mon_clear_timed(m, MON_TMD_SLEEP, MON_TMD_FLG_NOMESSAGE, false); } else { /* Message */ msg("There is a monster in the way!"); /* Attack */ py_attack(y, x); } } else if (obj) { /* Chest */ more = do_cmd_open_chest(y, x, obj); } else { /* Door */ more = do_cmd_open_aux(y, x); } /* Cancel repeat unless we may continue */ if (!more) disturb(player, 0); }
/* * Move player in the given direction. * * This routine should only be called when energy has been expended. * * Note that this routine handles monsters in the destination grid, * and also handles attempting to move into walls/doors/rubble/etc. */ void move_player(int dir, bool disarm) { int py = p_ptr->py; int px = p_ptr->px; int y = py + ddy[dir]; int x = px + ddx[dir]; int m_idx = cave->m_idx[y][x]; /* Attack monsters */ if (m_idx > 0) { /* Mimics surprise the player */ if (is_mimicking(m_idx)) { become_aware(m_idx); /* Mimic wakes up */ mon_clear_timed(m_idx, MON_TMD_SLEEP, MON_TMD_FLG_NOMESSAGE); } else { py_attack(y, x); } } /* Optionally alter traps/doors on movement */ else if (disarm && (cave->info[y][x] & CAVE_MARK) && (cave_isknowntrap(cave, y, x) || cave_iscloseddoor(cave, y, x))) { /* Auto-repeat if not already repeating */ if (cmd_get_nrepeats() == 0) cmd_set_repeat(99); do_cmd_alter_aux(dir); } /* Cannot walk through walls */ else if (!cave_floor_bold(y, x)) { /* Disturb the player */ disturb(p_ptr, 0, 0); /* Notice unknown obstacles */ if (!(cave->info[y][x] & CAVE_MARK)) { /* Rubble */ if (cave->feat[y][x] == FEAT_RUBBLE) { msgt(MSG_HITWALL, "You feel a pile of rubble blocking your way."); cave->info[y][x] |= (CAVE_MARK); cave_light_spot(cave, y, x); } /* Closed door */ else if (cave->feat[y][x] < FEAT_SECRET) { msgt(MSG_HITWALL, "You feel a door blocking your way."); cave->info[y][x] |= (CAVE_MARK); cave_light_spot(cave, y, x); } /* Wall (or secret door) */ else { msgt(MSG_HITWALL, "You feel a wall blocking your way."); cave->info[y][x] |= (CAVE_MARK); cave_light_spot(cave, y, x); } } /* Mention known obstacles */ else { if (cave->feat[y][x] == FEAT_RUBBLE) msgt(MSG_HITWALL, "There is a pile of rubble blocking your way."); else if (cave->feat[y][x] < FEAT_SECRET) msgt(MSG_HITWALL, "There is a door blocking your way."); else msgt(MSG_HITWALL, "There is a wall blocking your way."); } } /* Normal movement */ else { /* See if trap detection status will change */ bool old_dtrap = ((cave->info2[py][px] & (CAVE2_DTRAP)) != 0); bool new_dtrap = ((cave->info2[y][x] & (CAVE2_DTRAP)) != 0); /* Note the change in the detect status */ if (old_dtrap != new_dtrap) p_ptr->redraw |= (PR_DTRAP); /* Disturb player if the player is about to leave the area */ if (OPT(disturb_detect) && p_ptr->running && !p_ptr->running_firststep && old_dtrap && !new_dtrap) { disturb(p_ptr, 0, 0); return; } /* Move player */ monster_swap(py, px, y, x); /* New location */ y = py = p_ptr->py; x = px = p_ptr->px; /* Searching */ if (p_ptr->searching || (p_ptr->state.skills[SKILL_SEARCH_FREQUENCY] >= 50) || one_in_(50 - p_ptr->state.skills[SKILL_SEARCH_FREQUENCY])) search(FALSE); /* Handle "store doors" */ if ((cave->feat[p_ptr->py][p_ptr->px] >= FEAT_SHOP_HEAD) && (cave->feat[p_ptr->py][p_ptr->px] <= FEAT_SHOP_TAIL)) { /* Disturb */ disturb(p_ptr, 0, 0); cmd_insert(CMD_ENTER_STORE); } /* All other grids (including traps) */ else { /* Handle objects (later) */ p_ptr->notice |= (PN_PICKUP); } /* Discover invisible traps */ if (cave->feat[y][x] == FEAT_INVIS) { /* Disturb */ disturb(p_ptr, 0, 0); /* Message */ msg("You found a trap!"); /* Pick a trap */ pick_trap(y, x); /* Hit the trap */ hit_trap(y, x); } /* Set off an visible trap */ else if (cave_isknowntrap(cave, y, x)) { /* Disturb */ disturb(p_ptr, 0, 0); /* Hit the trap */ hit_trap(y, x); } } p_ptr->running_firststep = FALSE; }
/** * 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 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); }
/** * 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 *m_ptr) { monster_lore *l_ptr = get_lore(m_ptr->race); /* Handle "sleep" */ if (m_ptr->m_timed[MON_TMD_SLEEP]) { bool woke_up = FALSE; /* Anti-stealth */ int notice = randint0(1024); /* Aggravation */ if (player_of_has(player, OF_AGGRAVATE)) { /* Wake the monster and notify player */ mon_clear_timed(m_ptr, MON_TMD_SLEEP, MON_TMD_FLG_NOTIFY, FALSE); woke_up = TRUE; /* Hack See if monster "notices" player */ } else if ((notice * notice * notice) <= player->state.noise) { int d = 1; /* Wake up faster near the player */ if (m_ptr->cdis < 50) d = (100 / m_ptr->cdis); /* Note a complete wakeup */ if (m_ptr->m_timed[MON_TMD_SLEEP] <= d) woke_up = TRUE; /* Monster wakes up a bit */ mon_dec_timed(m_ptr, MON_TMD_SLEEP, d, MON_TMD_FLG_NOTIFY, FALSE); /* Update knowledge */ if (mflag_has(m_ptr->mflag, MFLAG_VISIBLE) && !mflag_has(m_ptr->mflag, MFLAG_UNAWARE)) { if (!woke_up && l_ptr->ignore < MAX_UCHAR) l_ptr->ignore++; else if (woke_up && l_ptr->wake < MAX_UCHAR) l_ptr->wake++; lore_update(m_ptr->race, l_ptr); } } /* Update the health bar */ if (woke_up && mflag_has(m_ptr->mflag, MFLAG_VISIBLE) && !mflag_has(m_ptr->mflag, MFLAG_UNAWARE) && player->upkeep->health_who == m_ptr) player->upkeep->redraw |= (PR_HEALTH); /* Sleeping monsters don't recover in any other ways */ /* If the monster just woke up, then it doesn't act */ return TRUE; } if (m_ptr->m_timed[MON_TMD_FAST]) mon_dec_timed(m_ptr, MON_TMD_FAST, 1, 0, FALSE); if (m_ptr->m_timed[MON_TMD_SLOW]) mon_dec_timed(m_ptr, MON_TMD_SLOW, 1, 0, FALSE); if (m_ptr->m_timed[MON_TMD_STUN]) { int d = 1; /* Make a "saving throw" against stun */ if (randint0(5000) <= m_ptr->race->level * m_ptr->race->level) /* Recover fully */ d = m_ptr->m_timed[MON_TMD_STUN]; /* Hack -- Recover from stun */ mon_dec_timed(m_ptr, MON_TMD_STUN, d, MON_TMD_FLG_NOTIFY, FALSE); } if (m_ptr->m_timed[MON_TMD_CONF]) { int d = randint1(m_ptr->race->level / 10 + 1); mon_dec_timed(m_ptr, MON_TMD_CONF, d, MON_TMD_FLG_NOTIFY, FALSE); } if (m_ptr->m_timed[MON_TMD_FEAR]) { int d = randint1(m_ptr->race->level / 10 + 1); mon_dec_timed(m_ptr, MON_TMD_FEAR, d, MON_TMD_FLG_NOTIFY, FALSE); } /* Don't do anything if stunned */ return m_ptr->m_timed[MON_TMD_STUN] ? TRUE : FALSE; }
/* * Open a closed/locked/jammed door or a closed/locked chest. * * Unlocking a locked door/chest is worth one experience point. */ void do_cmd_open(cmd_code code, cmd_arg args[]) { int y, x, dir; s16b o_idx; bool more = FALSE; dir = args[0].direction; /* Get location */ y = p_ptr->py + ddy[dir]; x = p_ptr->px + ddx[dir]; /* Check for chests */ o_idx = chest_check(y, x); /* Verify legality */ if (!o_idx && !do_cmd_open_test(y, x)) { /* Cancel repeat */ disturb(p_ptr, 0, 0); return; } /* Take a turn */ p_ptr->energy_use = 100; /* Apply confusion */ if (player_confuse_dir(p_ptr, &dir, FALSE)) { /* Get location */ y = p_ptr->py + ddy[dir]; x = p_ptr->px + ddx[dir]; /* Check for chest */ o_idx = chest_check(y, x); } /* Monster */ if (cave->m_idx[y][x] > 0) { int m_idx = cave->m_idx[y][x]; /* Mimics surprise the player */ if (is_mimicking(m_idx)) { become_aware(m_idx); /* Mimic wakes up */ mon_clear_timed(m_idx, MON_TMD_SLEEP, MON_TMD_FLG_NOMESSAGE); } else { /* Message */ msg("There is a monster in the way!"); /* Attack */ py_attack(y, x); } } /* Chest */ else if (o_idx) { /* Open the chest */ more = do_cmd_open_chest(y, x, o_idx); } /* Door */ else { /* Open the door */ more = do_cmd_open_aux(y, x); } /* Cancel repeat unless we may continue */ if (!more) disturb(p_ptr, 0, 0); }
/** * Decreases a monster's hit points by `dam` and handle monster death. * * Hack -- we "delay" fear messages by passing around a "fear" flag. * * We announce monster death (using an optional "death message" (`note`) * if given, and a otherwise a generic killed/destroyed message). * * Returns TRUE if the monster has been killed (and deleted). * * TODO: Consider decreasing monster experience over time, say, by using * "(m_exp * m_lev * (m_lev)) / (p_lev * (m_lev + n_killed))" instead * of simply "(m_exp * m_lev) / (p_lev)", to make the first monster * worth more than subsequent monsters. This would also need to * induce changes in the monster recall code. XXX XXX XXX **/ bool mon_take_hit(struct monster *m_ptr, int dam, bool *fear, const char *note) { s32b div, new_exp, new_exp_frac; monster_lore *l_ptr = get_lore(m_ptr->race); /* Redraw (later) if needed */ if (p_ptr->health_who == m_ptr) p_ptr->redraw |= (PR_HEALTH); /* Wake it up */ mon_clear_timed(m_ptr, MON_TMD_SLEEP, MON_TMD_FLG_NOMESSAGE, FALSE); /* Become aware of its presence */ if (m_ptr->unaware) become_aware(m_ptr); /* Hurt it */ m_ptr->hp -= dam; /* It is dead now */ if (m_ptr->hp < 0) { char m_name[80]; char buf[80]; /* Assume normal death sound */ int soundfx = MSG_KILL; /* Play a special sound if the monster was unique */ if (rf_has(m_ptr->race->flags, RF_UNIQUE)) { if (m_ptr->race->base == lookup_monster_base("Morgoth")) soundfx = MSG_KILL_KING; else soundfx = MSG_KILL_UNIQUE; } /* Extract monster name */ monster_desc(m_name, sizeof(m_name), m_ptr, 0); /* Death by Missile/Spell attack */ if (note) { /* Hack -- allow message suppression */ if (strlen(note) <= 1) { /* Be silent */ } else { char *str = format("%s%s", m_name, note); my_strcap(str); msgt(soundfx, "%s", str); } } /* Death by physical attack -- invisible monster */ else if (!m_ptr->ml) msgt(soundfx, "You have killed %s.", m_name); /* Death by Physical attack -- non-living monster */ else if (monster_is_unusual(m_ptr->race)) msgt(soundfx, "You have destroyed %s.", m_name); /* Death by Physical attack -- living monster */ else msgt(soundfx, "You have slain %s.", m_name); /* Player level */ div = p_ptr->lev; /* Give some experience for the kill */ new_exp = ((long)m_ptr->race->mexp * m_ptr->race->level) / div; /* Handle fractional experience */ new_exp_frac = ((((long)m_ptr->race->mexp * m_ptr->race->level) % div) * 0x10000L / div) + p_ptr->exp_frac; /* Keep track of experience */ if (new_exp_frac >= 0x10000L) { new_exp++; p_ptr->exp_frac = (u16b)(new_exp_frac - 0x10000L); } else p_ptr->exp_frac = (u16b)new_exp_frac; /* When the player kills a Unique, it stays dead */ if (rf_has(m_ptr->race->flags, RF_UNIQUE)) { char unique_name[80]; m_ptr->race->max_num = 0; /* * This gets the correct name if we slay an invisible * unique and don't have See Invisible. */ monster_desc(unique_name, sizeof(unique_name), m_ptr, MDESC_SHOW | MDESC_IND2); /* Log the slaying of a unique */ strnfmt(buf, sizeof(buf), "Killed %s", unique_name); history_add(buf, HISTORY_SLAY_UNIQUE, 0); } /* Gain experience */ player_exp_gain(p_ptr, new_exp); /* Generate treasure */ monster_death(m_ptr, FALSE); /* Recall even invisible uniques or winners */ if (m_ptr->ml || rf_has(m_ptr->race->flags, RF_UNIQUE)) { /* Count kills this life */ if (l_ptr->pkills < MAX_SHORT) l_ptr->pkills++; /* Count kills in all lives */ if (l_ptr->tkills < MAX_SHORT) l_ptr->tkills++; /* Hack -- Auto-recall */ monster_race_track(m_ptr->race); } /* Delete the monster */ delete_monster_idx(m_ptr->midx); /* Not afraid */ (*fear) = FALSE; /* Monster is dead */ return (TRUE); } /* Mega-Hack -- Pain cancels fear */ if (!(*fear) && m_ptr->m_timed[MON_TMD_FEAR] && (dam > 0)) { int tmp = randint1(dam); /* Cure a little fear */ if (tmp < m_ptr->m_timed[MON_TMD_FEAR]) { /* Reduce fear */ mon_dec_timed(m_ptr, MON_TMD_FEAR, tmp, MON_TMD_FLG_NOMESSAGE, FALSE); } /* Cure all the fear */ else { /* Cure fear */ mon_clear_timed(m_ptr, MON_TMD_FEAR, MON_TMD_FLG_NOMESSAGE, FALSE); /* No more fear */ (*fear) = FALSE; } } /* Sometimes a monster gets scared by damage */ if (!m_ptr->m_timed[MON_TMD_FEAR] && !rf_has(m_ptr->race->flags, RF_NO_FEAR) && dam > 0) { int percentage; /* Percentage of fully healthy */ percentage = (100L * m_ptr->hp) / m_ptr->maxhp; /* * Run (sometimes) if at 10% or less of max hit points, * or (usually) when hit for half its current hit points */ if ((randint1(10) >= percentage) || ((dam >= m_ptr->hp) && (randint0(100) < 80))) { int timer = randint1(10) + (((dam >= m_ptr->hp) && (percentage > 7)) ? 20 : ((11 - percentage) * 5)); /* Hack -- note fear */ (*fear) = TRUE; mon_inc_timed(m_ptr, MON_TMD_FEAR, timer, MON_TMD_FLG_NOMESSAGE | MON_TMD_FLG_NOFAIL, FALSE); } } /* Not dead yet */ return (FALSE); }
/** * Attack the monster at the given location with a single blow. */ static bool py_attack_real(int y, int x, bool *fear) { size_t i; /* Information about the target of the attack */ struct monster *mon = square_monster(cave, y, x); char m_name[80]; bool stop = FALSE; /* The weapon used */ struct object *obj = equipped_item_by_slot_name(player, "weapon"); /* Information about the attack */ int chance = py_attack_hit_chance(obj); bool do_quake = FALSE; bool success = FALSE; /* Default to punching for one damage */ char verb[20]; int dmg = 1; u32b msg_type = MSG_HIT; /* Default to punching for one damage */ my_strcpy(verb, "punch", sizeof(verb)); /* Extract monster name (or "it") */ monster_desc(m_name, sizeof(m_name), mon, MDESC_OBJE | MDESC_IND_HID | MDESC_PRO_HID); /* Auto-Recall if possible and visible */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) monster_race_track(player->upkeep, mon->race); /* Track a new monster */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) health_track(player->upkeep, mon); /* Handle player fear (only for invisible monsters) */ if (player_of_has(player, OF_AFRAID)) { msgt(MSG_AFRAID, "You are too afraid to attack %s!", m_name); return FALSE; } /* Disturb the monster */ mon_clear_timed(mon, MON_TMD_SLEEP, MON_TMD_FLG_NOMESSAGE, FALSE); /* See if the player hit */ success = test_hit(chance, mon->race->ac, mflag_has(mon->mflag, MFLAG_VISIBLE)); /* If a miss, skip this hit */ if (!success) { msgt(MSG_MISS, "You miss %s.", m_name); return FALSE; } /* Handle normal weapon */ if (obj) { int j; const struct brand *b = NULL; const struct slay *s = NULL; my_strcpy(verb, "hit", sizeof(verb)); /* Get the best attack from all slays or * brands on all non-launcher equipment */ for (j = 2; j < player->body.count; j++) { struct object *obj = slot_object(player, j); if (obj) improve_attack_modifier(obj, mon, &b, &s, verb, FALSE, TRUE, FALSE); } improve_attack_modifier(obj, mon, &b, &s, verb, FALSE, TRUE, FALSE); dmg = melee_damage(obj, b, s); dmg = critical_norm(obj->weight, obj->to_h, dmg, &msg_type); /* Learn by use for the weapon */ object_notice_attack_plusses(obj); if (player_of_has(player, OF_IMPACT) && dmg > 50) { do_quake = TRUE; equip_notice_flag(player, OF_IMPACT); } } /* Learn by use for other equipped items */ equip_notice_on_attack(player); /* Apply the player damage bonuses */ dmg += player_damage_bonus(&player->state); /* No negative damage; change verb if no damage done */ if (dmg <= 0) { dmg = 0; msg_type = MSG_MISS; my_strcpy(verb, "fail to harm", sizeof(verb)); } for (i = 0; i < N_ELEMENTS(melee_hit_types); i++) { const char *dmg_text = ""; if (msg_type != melee_hit_types[i].msg) continue; if (OPT(show_damage)) dmg_text = format(" (%d)", dmg); if (melee_hit_types[i].text) msgt(msg_type, "You %s %s%s. %s", verb, m_name, dmg_text, melee_hit_types[i].text); else msgt(msg_type, "You %s %s%s.", verb, m_name, dmg_text); } /* Pre-damage side effects */ blow_side_effects(player, mon); /* Damage, check for fear and death */ stop = mon_take_hit(mon, dmg, fear, NULL); if (stop) (*fear) = FALSE; /* Post-damage effects */ if (blow_after_effects(y, x, do_quake)) stop = TRUE; return stop; }
/** * Move player in the given direction. * * This routine should only be called when energy has been expended. * * Note that this routine handles monsters in the destination grid, * and also handles attempting to move into walls/doors/rubble/etc. */ void move_player(int dir, bool disarm) { int py = player->py; int px = player->px; int y = py + ddy[dir]; int x = px + ddx[dir]; int m_idx = cave->squares[y][x].mon; struct monster *mon = cave_monster(cave, m_idx); bool alterable = (square_isknowntrap(cave, y, x) || square_iscloseddoor(cave, y, x)); /* Attack monsters, alter traps/doors on movement, hit obstacles or move */ if (m_idx > 0) { /* Mimics surprise the player */ if (is_mimicking(mon)) { become_aware(mon); /* Mimic wakes up */ mon_clear_timed(mon, MON_TMD_SLEEP, MON_TMD_FLG_NOMESSAGE, false); } else { py_attack(y, x); } } else if (disarm && square_isknown(cave, y, x) && alterable) { /* Auto-repeat if not already repeating */ if (cmd_get_nrepeats() == 0) cmd_set_repeat(99); do_cmd_alter_aux(dir); } else if (player->upkeep->running && square_isknowntrap(cave, y, x)) { /* Stop running before known traps */ disturb(player, 0); } else if (!square_ispassable(cave, y, x)) { /* Disturb the player */ disturb(player, 0); /* Notice unknown obstacles, mention known obstacles */ if (!square_isknown(cave, y, x)) { if (square_isrubble(cave, y, x)) { msgt(MSG_HITWALL, "You feel a pile of rubble blocking your way."); square_memorize(cave, y, x); square_light_spot(cave, y, x); } else if (square_iscloseddoor(cave, y, x)) { msgt(MSG_HITWALL, "You feel a door blocking your way."); square_memorize(cave, y, x); square_light_spot(cave, y, x); } else { msgt(MSG_HITWALL, "You feel a wall blocking your way."); square_memorize(cave, y, x); square_light_spot(cave, y, x); } } else { if (square_isrubble(cave, y, x)) msgt(MSG_HITWALL, "There is a pile of rubble blocking your way."); else if (square_iscloseddoor(cave, y, x)) msgt(MSG_HITWALL, "There is a door blocking your way."); else msgt(MSG_HITWALL, "There is a wall blocking your way."); } } else { /* See if trap detection status will change */ bool old_dtrap = square_isdtrap(cave, py, px); bool new_dtrap = square_isdtrap(cave, y, x); /* Note the change in the detect status */ if (old_dtrap != new_dtrap) player->upkeep->redraw |= (PR_DTRAP); /* Disturb player if the player is about to leave the area */ if (player->upkeep->running && !player->upkeep->running_firststep && old_dtrap && !new_dtrap) { disturb(player, 0); return; } /* Move player */ monster_swap(py, px, y, x); /* New location */ y = py = player->py; x = px = player->px; /* Searching */ if (player->searching || (player->state.skills[SKILL_SEARCH_FREQUENCY] >= 50) || one_in_(50 - player->state.skills[SKILL_SEARCH_FREQUENCY])) search(false); /* Handle store doors, or notice objects */ if (square_isshop(cave, player->py, player->px)) { /* Disturb */ disturb(player, 0); event_signal(EVENT_ENTER_STORE); event_remove_handler_type(EVENT_ENTER_STORE); event_signal(EVENT_USE_STORE); event_remove_handler_type(EVENT_USE_STORE); event_signal(EVENT_LEAVE_STORE); event_remove_handler_type(EVENT_LEAVE_STORE); } else { /* Know objects, queue autopickup */ floor_pile_know(cave, player->py, player->px); cmdq_push(CMD_AUTOPICKUP); } /* Discover invisible traps, set off visible ones */ if (square_issecrettrap(cave, y, x)) { /* Disturb */ disturb(player, 0); /* Hit the trap. */ hit_trap(y, x); } else if (square_isknowntrap(cave, y, x)) { /* Disturb */ disturb(player, 0); /* Hit the trap */ hit_trap(y, x); } } player->upkeep->running_firststep = false; }