/* File: monster.c */ /* Purpose: misc code for monsters */ /* * Copyright (c) 1989 James E. Wilson, Robert A. Koeneke * * This software may be copied and distributed for educational, research, and * not for profit purposes provided that this copyright and statement are * included in all such copies. */ #include "angband.h" static bool spawning = FALSE; /* Get the cave pointer */ cave_type *monster_type::get_c_ptr(void) { return &cave[get_fy()][get_fx()]; } /* Get the monster race pointer */ monster_race *monster_type::get_r_ptr(void) { return &r_info[get_r_idx()]; } /* * Delete a monster by index. * * This function causes the given monster to cease to exist for * all intents and purposes. The monster record is left in place * but the record is wiped, marking it as "dead" (no race index) * so that it can be "skipped" when scanning the monster array, * and "excised" from the "m_fast" array when needed. * * Thus, anyone who makes direct reference to the "m_list[]" array * using monster indexes that may have become invalid should be sure * to verify that the "r_idx" field is non-zero. All references * to "m_list[c_ptr->m_idx]" are guaranteed to be valid, see below. */ void delete_monster_idx(int i) { int x, y; monster_type *m_ptr = &m_list[i]; monster_race *r_ptr = m_ptr->r_ptr; /* Get location */ x = m_ptr->fx; y = m_ptr->fy; /* Hack -- Reduce the racial counter */ r_ptr->cur_num--; /* Hack -- count the number of "reproducers" */ if (r_ptr->flags2 & RF2_MULTIPLY) num_repro--; /* Hack -- remove target monster */ if (i == target_who) target_who = 0; /* Hack -- remove tracked monster */ if (i == health_who) health_track(0); /* Monster is gone */ cave[y][x].m_idx = 0; /* Visual update */ lite_spot(y, x); /* Wipe the Monster */ WIPE(m_ptr, monster_type); } /* * Delete the monster, if any, at a given location */ void delete_monster(int y, int x) { cave_type *c_ptr; /* Paranoia */ if (!in_bounds(y,x)) return; /* Check the grid */ c_ptr = &cave[y][x]; /* Delete the monster (if any) */ if (c_ptr->m_idx) delete_monster_idx(c_ptr->m_idx); } /* * Compact and Reorder the monster list * * This function can be very dangerous, use with caution! * * When actually "compacting" monsters, we base the saving throw * on a combination of monster level, distance from player, and * current "desperation". * * After "compacting" (if needed), we "reorder" the monsters into a more * compact order, and we reset the allocation info, and the "live" array. */ void compact_monsters(int size) { int i, num, cnt; int cur_lev, cur_dis, chance; /* Message (only if compacting) */ if (size) msg_print("Compacting monsters..."); /* Compact at least 'size' objects */ for (num = 0, cnt = 1; num < size; cnt++) { /* Get more vicious each iteration */ cur_lev = 5 * cnt; /* Get closer each iteration */ cur_dis = 5 * (20 - cnt); /* Check all the monsters */ for (i = 1; i < m_max; i++) { monster_type *m_ptr = &m_list[i]; monster_race *r_ptr = m_ptr->r_ptr; /* Paranoia -- skip "dead" monsters */ if (!m_ptr->r_idx) continue; /* Hack -- High level monsters start out "immune" */ if (r_ptr->level > cur_lev) continue; /* Ignore nearby monsters */ if ((cur_dis > 0) && (m_ptr->cdis < cur_dis)) continue; /* Saving throw chance */ chance = 90; /* Only compact "Quest" Monsters in emergencies */ if ((r_ptr->flags1 & RF1_QUESTOR) && (cnt < 1000)) chance = 100; /* Try not to compact Unique Monsters */ if (r_ptr->flags1 & RF1_UNIQUE) chance = 99; /* All monsters get a saving throw */ if (rand_int(100) < chance) continue; /* Delete the monster */ delete_monster_idx(i); /* Count the monster */ num++; } } /* Excise dead monsters (backwards!) */ for (i = m_max - 1; i >= 1; i--) { /* Get the i'th monster */ monster_type *m_ptr = &m_list[i]; /* Skip real monsters */ if (m_ptr->r_idx) continue; /* One less monster */ m_max--; /* Reorder */ if (i != m_max) { int nx = m_list[m_max].get_fx(); int ny = m_list[m_max].get_fy(); /* Update the cave */ cave[ny][nx].m_idx = i; /* Hack -- Update the target */ if (target_who == (int)(m_max)) target_who = i; /* Hack -- Update the health bar */ if (health_who == (int)(m_max)) health_track(i); /* Structure copy */ m_list[i] = m_list[m_max]; /* Wipe the hole */ WIPE(&m_list[m_max], monster_type); } } /* Reset "m_nxt" */ m_nxt = m_max; /* Reset "m_top" */ m_top = 0; /* Collect "live" monsters */ for (i = 0; i < m_max; i++) { /* Collect indexes */ m_fast[m_top++] = i; } } /* * Delete/Remove all the monsters when the player leaves the level * * This is an efficient method of simulating multiple calls to the * "delete_monster()" function, with no visual effects. */ void wipe_m_list() { int i; /* Delete all the monsters */ for (i = m_max - 1; i >= 1; i--) { monster_type *m_ptr = &m_list[i]; monster_race *r_ptr = m_ptr->r_ptr; /* Skip dead monsters */ if (!m_ptr->r_idx) continue; /* Mega-Hack -- preserve Unique's XXX XXX XXX */ /* Hack -- Reduce the racial counter */ r_ptr->cur_num--; /* Monster is gone */ cave[m_ptr->fy][m_ptr->fx].m_idx = 0; /* Wipe the Monster */ WIPE(m_ptr, monster_type); } /* Reset the monster array */ m_nxt = m_max = 1; /* No live monsters */ m_top = 0; /* Hack -- reset "reproducer" count */ num_repro = 0; /* Hack -- no more target */ target_who = 0; /* Hack -- no more tracking */ health_track(0); } /* * Acquires and returns the index of a "free" monster. * * This routine should almost never fail, but it *can* happen. * * Note that this function must maintain the special "m_fast" * array of indexes of "live" monsters. */ s16b m_pop(void) { int i, n, k; /* Normal allocation */ if (m_max < MAX_M_IDX) { /* Access the next hole */ i = m_max; /* Expand the array */ m_max++; /* Update "m_fast" */ m_fast[m_top++] = i; /* Return the index */ return (i); } /* Check for some space */ for (n = 1; n < MAX_M_IDX; n++) { /* Get next space */ i = m_nxt; /* Advance (and wrap) the "next" pointer */ if (++m_nxt >= MAX_M_IDX) m_nxt = 1; /* Skip monsters in use */ if (m_list[i].get_r_idx()) continue; /* Verify space XXX XXX */ if (m_top >= MAX_M_IDX) continue; /* Verify not allocated */ for (k = 0; k < m_top; k++) { /* Hack -- Prevent errors */ if (m_fast[k] == i) i = 0; } /* Oops XXX XXX */ if (!i) continue; /* Update "m_fast" */ m_fast[m_top++] = i; /* Use this monster */ return (i); } /* Warn the player */ if (character_dungeon) msg_print("Too many monsters!"); /* Try not to crash */ return (0); } /* * Choose a monster race that seems "appropriate" to the given level * * This function uses the "allocation table" built in "init.c". * * Note that "town" monsters will *only* be created in the town, * and "normal" monsters will *never* be created in the town. * * There is a small chance (1/50) of "boosting" the given depth by * a small amount (up to four levels), except in the town. * * It is (slightly) more likely to acquire a monster of the given level * than one of a lower level. This is done by choosing several monsters * appropriate to the given level and keeping the "hardest" one. * * Note that we only pick 10000 different monsters before failing. * This may prevent "summon unique monsters" from crashing. * * As far as I can tell, this routine will never return a monster * which cannot be placed in the dungeon at the current time. But * if we are given an overly restrictive hook, we may not terminate. * * XXX XXX XXX Note that the "get_mon_num()" function may fail, especially * if given an abusive "restriction", in which case it will return zero. */ s16b get_mon_num(int level) { int i, p, k; int r_idx; monster_race *r_ptr; /* Obtain the table */ s16b *t_lev = alloc_race_index; race_entry *table = alloc_race_table; /* Hack -- sometimes "boost" level */ if (level > 0) { /* Occasional "nasty" monster */ if (rand_int(NASTY_MON) == 0) { /* Pick a level bonus */ int d = level / 4 + 2; /* Boost the level */ level += ((d < 5) ? d : 5); } /* Occasional "nasty" monster */ if (rand_int(NASTY_MON) == 0) { /* Pick a level bonus */ int d = level / 4 + 2; /* Boost the level */ level += ((d < 5) ? d : 5); } /* Only examine legal levels */ if (level > MAX_DEPTH - 1) level = MAX_DEPTH - 1; } /* Hack -- Pick a monster */ for (k = 0; k < 10000; k++) { /* Town level is easy */ if (level <= 0) { /* Pick a level 0 entry */ i = rand_int(t_lev[0]); } /* Other levels */ else { /* Roll for rerolls */ p = rand_int(100); /* Pick any "appropriate" monster */ i = rand_int(t_lev[level]); /* Try for a "harder" monster twice (10%) */ if (p < 10) { /* Pick another monster at or below the given level */ int j = rand_int(t_lev[level]); /* Keep it if it is "better" */ if (table[i].locale < table[j].locale) i = j; } /* Try for a "harder" monster once (50%) */ if (p < 60) { /* Pick another monster at or below the given level */ int j = rand_int(t_lev[level]); /* Keep it if it is "better" */ if (table[i].locale < table[j].locale) i = j; } /* Hack -- Never make town monsters */ if (table[i].locale == 0) continue; } /* Access the "r_idx" of the chosen monster */ r_idx = table[i].r_idx; /* Hack -- apply the hook (if needed) */ if (get_mon_num_hook && (!(*get_mon_num_hook)(r_idx))) continue; /* Access the actual race */ r_ptr = &r_info[r_idx]; /* Hack -- "unique" monsters must be "unique" */ if ((r_ptr->flags1 & RF1_UNIQUE) && (r_ptr->cur_num >= r_ptr->max_num)) { continue; } /* Depth Monsters never appear out of depth */ if ((r_ptr->flags1 & RF1_FORCE_DEPTH) && (r_ptr->level > dun_level)) { continue; } /* Hack -- Roll for "rarity" */ if (rand_int(table[i].chance) != 0) continue; /* Use that monster */ return (r_idx); } /* Oops */ return (0); } /* * Build a string describing a monster in some way. * * We can correctly describe monsters based on their visibility. * We can force all monsters to be treated as visible or invisible. * We can build nominatives, objectives, possessives, or reflexives. * We can selectively pronominalize hidden, visible, or all monsters. * We can use definite or indefinite descriptions for hidden monsters. * We can use definite or indefinite descriptions for visible monsters. * * Pronominalization involves the gender whenever possible and allowed, * so that by cleverly requesting pronominalization / visibility, you * can get messages like "You hit someone. She screams in agony!". * * Reflexives are acquired by requesting Objective plus Possessive. * * If no m_ptr arg is given (?), the monster is assumed to be hidden, * unless the "Assume Visible" mode is requested. * * If no r_ptr arg is given, it is extracted from m_ptr and r_info * If neither m_ptr nor r_ptr is given, the monster is assumed to * be neuter, singular, and hidden (unless "Assume Visible" is set), * in which case you may be in trouble... :-) * * I am assuming that no monster name is more than 70 characters long, * so that "char desc[80];" is sufficiently large for any result. * * Mode Flags: * 0x01 --> Objective (or Reflexive) * 0x02 --> Possessive (or Reflexive) * 0x04 --> Use indefinites for hidden monsters ("something") * 0x08 --> Use indefinites for visible monsters ("a kobold") * 0x10 --> Pronominalize hidden monsters * 0x20 --> Pronominalize visible monsters * 0x40 --> Assume the monster is hidden * 0x80 --> Assume the monster is visible * * Useful Modes: * 0x00 --> Full nominative name ("the kobold") or "it" * 0x04 --> Full nominative name ("the kobold") or "something" * 0x80 --> Genocide resistance name ("the kobold") * 0x88 --> Killing name ("a kobold") * 0x22 --> Possessive, genderized if visable ("his") or "its" * 0x23 --> Reflexive, genderized if visable ("himself") or "itself" */ void monster_desc(char *desc, monster_type *m_ptr, int mode) { cptr res; monster_race *r_ptr = m_ptr->r_ptr; cptr name = (r_name + r_ptr->name); bool seen, pron; /* Can we "see" it (exists + forced, or visible + not unforced) */ seen = (m_ptr && ((mode & 0x80) || (!(mode & 0x40) && m_ptr->ml))); /* Sexed Pronouns (seen and allowed, or unseen and allowed) */ pron = (m_ptr && ((seen && (mode & 0x20)) || (!seen && (mode & 0x10)))); /* First, try using pronouns, or describing hidden monsters */ if (!seen || pron) { /* an encoding of the monster "sex" */ int kind = 0x00; /* Extract the gender (if applicable) */ if (r_ptr->flags1 & RF1_FEMALE) kind = 0x20; else if (r_ptr->flags1 & RF1_MALE) kind = 0x10; /* Ignore the gender (if desired) */ if (!m_ptr || !pron) kind = 0x00; /* Assume simple result */ res = "it"; /* Brute force: split on the possibilities */ switch (kind + (mode & 0x07)) { /* Neuter, or unknown */ case 0x00: res = "it"; break; case 0x01: res = "it"; break; case 0x02: res = "its"; break; case 0x03: res = "itself"; break; case 0x04: res = "something"; break; case 0x05: res = "something"; break; case 0x06: res = "something's"; break; case 0x07: res = "itself"; break; /* Male (assume human if vague) */ case 0x10: res = "he"; break; case 0x11: res = "him"; break; case 0x12: res = "his"; break; case 0x13: res = "himself"; break; case 0x14: res = "someone"; break; case 0x15: res = "someone"; break; case 0x16: res = "someone's"; break; case 0x17: res = "himself"; break; /* Female (assume human if vague) */ case 0x20: res = "she"; break; case 0x21: res = "her"; break; case 0x22: res = "her"; break; case 0x23: res = "herself"; break; case 0x24: res = "someone"; break; case 0x25: res = "someone"; break; case 0x26: res = "someone's"; break; case 0x27: res = "herself"; break; } /* Copy the result */ (void)strcpy(desc, res); } /* Handle visible monsters, "reflexive" request */ else if ((mode & 0x02) && (mode & 0x01)) { /* The monster is visible, so use its gender */ if (r_ptr->flags1 & RF1_FEMALE) strcpy(desc, "herself"); else if (r_ptr->flags1 & RF1_MALE) strcpy(desc, "himself"); else strcpy(desc, "itself"); } /* Handle all other visible monster requests */ else { /* It could be a Unique */ if (r_ptr->flags1 & RF1_UNIQUE) { /* Start with the name (thus nominative and objective) */ (void)strcpy(desc, name); } /* It could be an indefinite monster */ else if (mode & 0x08) { /* XXX Check plurality for "some" */ /* Indefinite monsters need an indefinite article */ (void)strcpy(desc, is_a_vowel(name[0]) ? "an " : "a "); (void)strcat(desc, name); } /* It could be a normal, definite, monster */ else { /* Definite monsters need a definite article */ strcpy(desc, "the "); strcat(desc, name); } /* Handle the Possessive as a special afterthought */ if (mode & 0x02) { /* XXX Check for trailing "s" */ /* Simply append "apostrophe" and "s" */ strcat(desc, "'s"); } } } /* * Learn about a monster (by "probing" it) */ void lore_do_probe(int m_idx) { monster_type *m_ptr = &m_list[m_idx]; monster_race *r_ptr = m_ptr->r_ptr; /* Hack -- Memorize some flags */ r_ptr->r_flags1 = r_ptr->flags1; r_ptr->r_flags2 = r_ptr->flags2; r_ptr->r_flags3 = r_ptr->flags3; /* Redraw the recall window */ p_ptr->redraw |= PR_RECENT; } /* * Take note that the given monster just dropped some treasure * * Note that learning the "GOOD"/"GREAT" flags gives information * about the treasure (even when the monster is killed for the first * time, such as uniques, and the treasure has not been examined yet). * * This "indirect" method is used to prevent the player from learning * exactly how much treasure a monster can drop from observing only * a single example of a drop. This method actually observes how much * gold and items are dropped, and remembers that information to be * described later by the monster recall code. */ void lore_treasure(int m_idx, int num_item, int num_gold) { monster_type *m_ptr = &m_list[m_idx]; monster_race *r_ptr = m_ptr->r_ptr; /* Note the number of things dropped */ if (num_item > r_ptr->r_drop_item) r_ptr->r_drop_item = num_item; if (num_gold > r_ptr->r_drop_gold) r_ptr->r_drop_gold = num_gold; /* Hack -- memorize the good/great flags */ if (r_ptr->flags1 & RF1_DROP_GOOD) r_ptr->r_flags1 |= RF1_DROP_GOOD; if (r_ptr->flags1 & RF1_DROP_GREAT) r_ptr->r_flags1 |= RF1_DROP_GREAT; } /* * This function updates the monster record of the given monster * * This involves extracting the distance to the player, checking * for visibility (natural, infravision, see-invis, telepathy), * updating the monster visibility flag, redrawing or erasing the * monster when the visibility changes, and taking note of any * "visual" features of the monster (cold-blooded, invisible, etc). * * The only monster fields that are changed here are "cdis" (the * distance from the player), "los" (clearly visible to player), * and "ml" (visible to the player in any way). * * There are a few cases where the calling routine knows that the * distance from the player to the monster has not changed, and so * we have a special parameter to request distance computation. * This lets many calls to this function run very quickly. * * Note that every time a monster moves, we must call this function * for that monster, and update distance. Note that every time the * player moves, we must call this function for every monster, and * update distance. Note that every time the player "state" changes * in certain ways (including "blindness", "infravision", "telepathy", * and "see invisible"), we must call this function for every monster. * * The routines that actually move the monsters call this routine * directly, and the ones that move the player, or notice changes * in the player state, call "update_monsters()". * * Routines that change the "illumination" of grids must also call * this function, since the "visibility" of some monsters may be * based on the illumination of their grid. * * Note that this function is called once per monster every time the * player moves, so it is important to optimize it for monsters which * are far away. Note the optimization which skips monsters which * are far away and were completely invisible last turn. * * Note the optimized "inline" version of the "distance()" function. * * Note that only monsters on the current panel can be "visible", * and then only if they are (1) in line of sight and illuminated * by light or infravision, or (2) nearby and detected by telepathy. * * The player can choose to be disturbed by several things, including * "disturb_enter" (monster becomes "easily" viewable), "disturb_leave" * (monster becomes no longer "easily" viewable), "disturb_move" (monster * which is "easily" viewable moves), and "disturb_near" (monster which * is "easily" viewable, and within 5 grids of the player, moves). This * may or may not be the "best" way to handle disturbance and telepathy, * but it should be better than the old method. */ void update_mon(int m_idx, bool dist) { monster_type *m_ptr = &m_list[m_idx]; monster_race *r_ptr = m_ptr->r_ptr; /* The current monster location */ int fx = m_ptr->fx; int fy = m_ptr->fy; /* Seen at all */ bool flag = FALSE; /* Seen by vision */ bool easy = FALSE; /* Seen by telepathy */ bool hard = FALSE; /* Various extra flags */ bool do_empty_mind = FALSE; bool do_weird_mind = FALSE; bool do_invisible = FALSE; bool do_cold_blood = FALSE; /* Calculate distance */ if (dist) { int d, dy, dx; /* Distance components */ dy = (py > fy) ? (py - fy) : (fy - py); dx = (px > fx) ? (px - fx) : (fx - px); /* Approximate distance */ d = (dy > dx) ? (dy + (dx>>1)) : (dx + (dy>>1)); /* Save the distance (in a byte) */ m_ptr->set_cdis((d < 255) ? d : 255); } /* Process "distant" monsters */ if (m_ptr->cdis > MAX_SIGHT) { /* Ignore unseen monsters */ if (!m_ptr->ml) return; } /* Process "nearby" monsters on the current "panel" */ else if (panel_contains(fy, fx)) { cave_type *c_ptr = &cave[fy][fx]; /* Normal line of sight, and player is not blind */ if ((c_ptr->info & CAVE_VIEW) && (!p_ptr->blind)) { /* Use "infravision" */ if (m_ptr->cdis <= (byte)(p_ptr->see_infra)) { /* Infravision only works on "warm" creatures */ /* Below, we will need to know that infravision failed */ if (r_ptr->flags2 & RF2_COLD_BLOOD) do_cold_blood = TRUE; /* Infravision works */ if (!do_cold_blood) easy = flag = TRUE; } /* Use "illumination" */ if (c_ptr->info & (CAVE_LITE | CAVE_GLOW)) { /* Take note of invisibility */ if (r_ptr->flags2 & RF2_INVISIBLE) do_invisible = TRUE; /* Visible, or detectable, monsters get seen */ if (!do_invisible || p_ptr->see_inv) easy = flag = TRUE; } } /* Telepathy can see all "nearby" monsters with "minds" */ if (p_ptr->telepathy) { /* Empty mind, no telepathy */ if (r_ptr->flags2 & RF2_EMPTY_MIND) { do_empty_mind = TRUE; } /* Weird mind, occasional telepathy */ else if (r_ptr->flags2 & RF2_WEIRD_MIND) { do_weird_mind = TRUE; if (rand_int(100) < 10) hard = flag = TRUE; } /* Normal mind, allow telepathy */ else { hard = flag = TRUE; } /* Apply telepathy */ if (hard) { /* Hack -- Memorize mental flags */ if (r_ptr->flags2 & RF2_SMART) r_ptr->r_flags2 |= RF2_SMART; if (r_ptr->flags2 & RF2_STUPID) r_ptr->r_flags2 |= RF2_STUPID; } } /* Hack -- Wizards have "perfect telepathy" */ if (wizard) flag = TRUE; } /* The monster is now visible */ if (flag) { /* It was previously unseen */ if (!m_ptr->ml) { /* Mark as visible */ m_ptr->set_ml(TRUE); /* Draw the monster */ lite_spot(fy, fx); /* Update health bar as needed */ if (health_who == m_idx) p_ptr->redraw |= (PR_HEALTH); /* Hack -- Count "fresh" sightings */ if (r_ptr->r_sights < MAX_SHORT) r_ptr->r_sights++; } /* Memorize various observable flags */ if (do_empty_mind) r_ptr->r_flags2 |= RF2_EMPTY_MIND; if (do_weird_mind) r_ptr->r_flags2 |= RF2_WEIRD_MIND; if (do_cold_blood) r_ptr->r_flags2 |= RF2_COLD_BLOOD; if (do_invisible) r_ptr->r_flags2 |= RF2_INVISIBLE; /* Efficiency -- Notice multi-hued monsters */ if (r_ptr->flags1 & RF1_ATTR_MULTI) scan_monsters = TRUE; } /* The monster is not visible */ else { /* It was previously seen */ if (m_ptr->ml) { /* Mark as not visible */ m_ptr->set_ml(FALSE); /* Erase the monster */ lite_spot(fy, fx); /* Update health bar as needed */ if (health_who == m_idx) p_ptr->redraw |= (PR_HEALTH); } } /* The monster is now easily visible */ if (easy) { /* Change */ if (!m_ptr->los) { /* Mark as easily visible */ m_ptr->set_los(TRUE); /* Disturb on appearance */ if (disturb_enter) disturb(1, 0); } } /* The monster is not easily visible */ else { /* Change */ if (m_ptr->los) { /* Mark as not easily visible */ m_ptr->set_los(FALSE); /* Disturb on disappearance */ if (disturb_leave) disturb(1, 0); } } } /* * This function simply updates all the (non-dead) monsters (see above). */ void update_monsters(bool dist) { int i; /* Update each (live) monster */ for (i = 1; i < m_max; i++) { monster_type *m_ptr = &m_list[i]; /* Skip dead monsters */ if (!m_ptr->r_idx) continue; /* Update the monster */ update_mon(i, dist); } } /* * Attempt to place a monster of the given race at the given location. * * To give the player a sporting chance, any monster that appears in * line-of-sight and is extremely dangerous can be marked as * "FORCE_SLEEP", which will cause them to be placed with low energy, * which often (but not always) lets the player move before they do. * * This routine refuses to place out-of-depth "FORCE_DEPTH" monsters. * * XXX XXX XXX Use special "here" and "dead" flags for unique monsters, * remove old "cur_num" and "max_num" fields. * * XXX XXX XXX Actually, do something similar for artifacts, to simplify * the "preserve" mode, and to make the "what artifacts" flag more useful. */ static bool place_monster_one(int y, int x, int r_idx, bool slp) { int i; cave_type *c_ptr; monster_type *m_ptr; monster_race *r_ptr = &r_info[r_idx]; cptr name = (r_name + r_ptr->name); /* Verify location */ if (!in_bounds(y,x)) return (FALSE); /* Require empty space */ if (!empty_grid_bold(y, x)) return (FALSE); /* Hack -- no creation on glyph of warding */ if (cave[y][x].feat == CF_GLYPH) return (FALSE); /* Paranoia */ if (!r_idx) return (FALSE); /* Paranoia */ if (!r_ptr->name) return (FALSE); /* Hack -- "unique" monsters must be "unique" */ if ((r_ptr->flags1 & RF1_UNIQUE) && (r_ptr->cur_num >= r_ptr->max_num)) { /* Cannot create */ return FALSE; } /* Depth monsters may NOT be created out of depth */ if ((r_ptr->flags1 & RF1_FORCE_DEPTH) && (dun_level < r_ptr->level)) { /* Cannot create */ return FALSE; } /* Powerful monster */ if (r_ptr->level > dun_level) { /* Unique monsters */ if (r_ptr->flags1 & RF1_UNIQUE) { /* Message for cheaters */ if (cheat_hear) msg_format("Deep Unique (%s).", name); /* Boost rating by twice delta-depth and a bit more */ rating += (r_ptr->level - dun_level) * 2 + 5; } /* Normal monsters */ else { /* Message for cheaters */ if (cheat_hear) msg_format("Deep Monster (%s).", name); /* Boost rating by delta-depth */ rating += (r_ptr->level - dun_level); } } /* Note the monster */ else if (r_ptr->flags1 & RF1_UNIQUE) { /* Unique monsters induce message */ if (cheat_hear) msg_format("Unique (%s).", name); /* Boost rating slightly */ rating += 5; } /* Access the location */ c_ptr = &cave[y][x]; /* Make a new monster */ c_ptr->m_idx = m_pop(); /* Mega-Hack -- catch "failure" */ if (!c_ptr->m_idx) return (FALSE); /* Get a new monster record */ m_ptr = &m_list[c_ptr->m_idx]; /* Save the race */ m_ptr->set_r_idx(r_idx); /* Place the monster at the location */ m_ptr->set_location(x, y); /* Hack -- Count the monsters on the level */ r_ptr->cur_num++; /* Hack -- count the number of "reproducers" */ if (r_ptr->flags2 & RF2_MULTIPLY) num_repro++; /* Assign maximal hitpoints */ if (r_ptr->flags1 & RF1_FORCE_MAXHP) { m_ptr->set_maxhp(maxroll(r_ptr->hdice, r_ptr->hside)); } else { m_ptr->set_maxhp(damroll(r_ptr->hdice, r_ptr->hside)); } /* And start out fully healthy */ m_ptr->set_hp(m_ptr->maxhp); /* Extract the monster base speed */ m_ptr->set_mspeed(r_ptr->speed); /* Hack -- small racial variety */ if (!(r_ptr->flags1 & RF1_UNIQUE)) { /* Allow some small variation per monster */ i = extract_energy[r_ptr->speed] / 10; if (i) m_ptr->set_mspeed(m_ptr->mspeed + rand_spread(0, i)); } /* Hack -- Reduce risk of "instant death by breath weapons" */ if (r_ptr->flags1 & RF1_FORCE_SLEEP) { /* Start out with minimal energy */ m_ptr->set_energy(rand_int(10)); } /* Give a random starting energy */ else { m_ptr->set_energy(rand_int(100)); } /* No "damage" yet */ m_ptr->set_stunned(0); m_ptr->set_confused(0); m_ptr->set_monfear(0); m_ptr->set_temp_speed(0); m_ptr->set_temp_slow(0); /* No knowledge */ m_ptr->set_cdis(0); m_ptr->set_los(FALSE); m_ptr->set_ml(FALSE); /* Was it spawned? */ m_ptr->set_spawned(spawning); /* Update the monster */ update_mon(c_ptr->m_idx, TRUE); /* Assume no sleeping */ m_ptr->set_csleep(0); /* Enforce sleeping if needed */ if (slp && r_ptr->sleep) { int val = r_ptr->sleep; m_ptr->set_csleep((val * 2) + randint(val * 10)); } /* Success */ return TRUE; } /* * Maximum size of a group of monsters */ #define GROUP_MAX 32 /* * Attempt to place a "group" of monsters around the given location */ static bool place_monster_group(int y, int x, int r_idx, bool slp) { monster_race *r_ptr = &r_info[r_idx]; int old, n, i; int total = 0, extra = 0; int hack_n = 0; byte hack_y[GROUP_MAX]; byte hack_x[GROUP_MAX]; /* Pick a group size */ total = randint(13); /* Hard monsters, small groups */ if (r_ptr->level > dun_level) { extra = r_ptr->level - dun_level; extra = 0 - randint(extra); } /* Easy monsters, large groups */ else if (r_ptr->level < dun_level) { extra = dun_level - r_ptr->level; extra = randint(extra); } /* Hack -- limit group reduction */ if (extra > 12) extra = 12; /* Modify the group size */ total += extra; /* Minimum size */ if (total < 1) total = 1; /* Maximum size */ if (total > GROUP_MAX) total = GROUP_MAX; /* Save the rating */ old = rating; /* Start on the monster */ hack_n = 1; hack_x[0] = x; hack_y[0] = y; /* Puddle monsters, breadth first, up to total */ for (n = 0; (n < hack_n) && (hack_n < total); n++) { /* Grab the location */ int hx = hack_x[n]; int hy = hack_y[n]; /* Check each direction, up to total */ for (i = 0; (i < 8) && (hack_n < total); i++) { int mx = hx + ddx_ddd[i]; int my = hy + ddy_ddd[i]; /* Walls and Monsters block flow */ if (!empty_grid_bold(my, mx)) continue; /* Attempt to place another monster */ if (place_monster_one(my, mx, r_idx, slp)) { /* Add it to the "hack" set */ hack_y[hack_n] = my; hack_x[hack_n] = mx; hack_n++; } } } /* Hack -- restore the rating */ rating = old; /* Success */ return (TRUE); } /* * Hack -- help pick an escort type */ static int place_monster_idx = 0; /* * Hack -- help pick an escort type */ static bool place_monster_okay(int r_idx) { monster_race *r_ptr = &r_info[place_monster_idx]; monster_race *z_ptr = &r_info[r_idx]; /* Require similar "race" */ if (z_ptr->r_char != r_ptr->r_char) return (FALSE); /* Skip more advanced monsters */ if (z_ptr->level > r_ptr->level) return (FALSE); /* Skip unique monsters */ if (z_ptr->flags1 & RF1_UNIQUE) return (FALSE); /* Paranoia -- Skip identical monsters */ if (place_monster_idx == r_idx) return (FALSE); /* Okay */ return (TRUE); } /* * Attempt to place a monster of the given race at the given location * * Note that certain monsters are now marked as requiring "friends". * These monsters, if successfully placed, and if the "grp" parameter * is TRUE, will be surrounded by a "group" of identical monsters. * * Note that certain monsters are now marked as requiring an "escort", * which is a collection of monsters with similar "race" but lower level. * * Some monsters induce a fake "group" flag on their escorts. * * Note the "bizarre" use of non-recursion to prevent annoying output * when running a code profiler. */ bool place_monster_aux(int y, int x, int r_idx, bool slp, bool grp) { int i; monster_race *r_ptr = &r_info[r_idx]; /* Place one monster, or fail */ if (!place_monster_one(y, x, r_idx, slp)) return (FALSE); /* Require the "group" flag */ if (!grp) return (TRUE); /* Friends for certain monsters */ if (r_ptr->flags1 & RF1_FRIENDS) { /* Attempt to place a group */ (void)place_monster_group(y, x, r_idx, slp); } /* Escorts for certain monsters */ if (r_ptr->flags1 & RF1_ESCORT) { /* Try to place several "escorts" */ for (i = 0; i < 50; i++) { int nx, ny, z, d = 3; /* Pick a location */ scatter(&ny, &nx, y, x, d, 0); /* Require empty grids */ if (!empty_grid_bold(ny, nx)) continue; /* Set the escort index */ place_monster_idx = r_idx; /* Set the escort hook */ get_mon_num_hook = place_monster_okay; /* Pick a random race */ z = get_mon_num(r_ptr->level); /* Remove restriction */ get_mon_num_hook = NULL; /* Handle failure */ if (!z) break; /* Place a single escort */ (void)place_monster_one(ny, nx, z, slp); /* Place a "group" of escorts if needed */ if ((r_info[z].flags1 & RF1_FRIENDS) || (r_ptr->flags1 & RF1_ESCORTS)) { /* Place a group of monsters */ (void)place_monster_group(ny, nx, z, slp); } } } /* Success */ return (TRUE); } /* * Hack -- attempt to place a monster at the given location * * Attempt to find a monster appropriate to the "monster_level" */ bool place_monster(int y, int x, bool slp, bool grp) { int r_idx; /* Pick a monster */ r_idx = get_mon_num(monster_level); /* Handle failure */ if (!r_idx) return (FALSE); /* Attempt to place the monster */ if (place_monster_aux(y, x, r_idx, slp, grp)) return (TRUE); /* Oops */ return (FALSE); } /* * XXX XXX XXX Player Ghosts are such a hack, they have been completely * removed until Angband 2.8.0, in which there will actually be a small * number of "unique" monsters which will serve as the "player ghosts". * Each will have a place holder for the "name" of a deceased player, * which will be extracted from a "bone" file, or replaced with a * "default" name if a real name is not available. Each ghost will * appear exactly once and will not induce a special feeling. * * Possible methods: * (s) 1 Skeleton * (z) 1 Zombie * (M) 1 Mummy * (G) 1 Polterguiest, 1 Spirit, 1 Ghost, 1 Shadow, 1 Phantom * (W) 1 Wraith * (V) 1 Vampire, 1 Vampire Lord * (L) 1 Lich * * Possible change: Lose 1 ghost, Add "Master Lich" * * Possible change: Lose 2 ghosts, Add "Wraith", Add "Master Lich" * * Possible change: Lose 4 ghosts, lose 1 vampire lord * * Note that ghosts should never sleep, should be very attentive, should * have maximal hitpoints, drop only good (or great) items, should be * cold blooded, evil, undead, immune to poison, sleep, confusion, fear. * * Base monsters: * Skeleton * Zombie * Mummy * Poltergeist * Spirit * Ghost * Vampire * Wraith * Vampire Lord * Shadow * Phantom * Lich * * This routine will simply extract ghost names from files, and * attempt to allocate a player ghost somewhere in the dungeon, * note that normal allocation may also attempt to place ghosts, * so we must work with some form of default names. * * XXX XXX XXX XXX */ /* * Attempt to allocate a random monster in the dungeon. * Place the monster at least "dis" distance from the player. * Use "slp" to choose the initial "sleep" status * Use "monster_level" for the monster level */ bool alloc_monster(int dis, int slp) { int y, x; /* Find a legal, distant, unoccupied, space */ while (1) { /* Pick a location */ y = rand_int(cur_hgt); x = rand_int(cur_wid); /* Require "naked" floor grid */ if (!naked_grid_bold(y,x)) continue; /* Accept far away grids */ if (distance(y, x, py, px) > dis) break; } /* Attempt to place the monster, allow groups */ if (place_monster(y, x, slp, TRUE)) return (TRUE); /* Nope */ return (FALSE); } /* * Hack -- the "type" of the current "summon specific" */ static int summon_specific_type = 0; /* * Hack -- help decide if a monster race is "okay" to summon */ static bool summon_specific_okay(int r_idx) { monster_race *r_ptr = &r_info[r_idx]; bool okay = FALSE; /* Hack -- no specific type specified */ if (!summon_specific_type) return (TRUE); /* Check our requirements */ switch (summon_specific_type) { case SUMMON_ANT: okay = ((r_ptr->r_char == 'a') && !(r_ptr->flags1 & RF1_UNIQUE)); break; case SUMMON_SPIDER: okay = ((r_ptr->r_char == 'S') && !(r_ptr->flags1 & RF1_UNIQUE)); break; case SUMMON_HOUND: okay = (((r_ptr->r_char == 'C') || (r_ptr->r_char == 'Z')) && !(r_ptr->flags1 & RF1_UNIQUE)); break; case SUMMON_HYDRA: okay = ((r_ptr->r_char == 'M') && !(r_ptr->flags1 & RF1_UNIQUE)); break; case SUMMON_ANGEL: okay = ((r_ptr->r_char == 'A') && !(r_ptr->flags1 & RF1_UNIQUE)); break; case SUMMON_DEMON: okay = ((r_ptr->flags3 & RF3_DEMON) && !(r_ptr->flags1 & RF1_UNIQUE)); break; case SUMMON_UNDEAD: okay = ((r_ptr->flags3 & RF3_UNDEAD) && !(r_ptr->flags1 & RF1_UNIQUE)); break; case SUMMON_DRAGON: okay = ((r_ptr->flags3 & RF3_DRAGON) && !(r_ptr->flags1 & RF1_UNIQUE)); break; case SUMMON_HI_UNDEAD: okay = ((r_ptr->r_char == 'L') || (r_ptr->r_char == 'V') || (r_ptr->r_char == 'W')); break; case SUMMON_HI_DRAGON: okay = (r_ptr->r_char == 'D'); break; case SUMMON_WRAITH: okay = ((r_ptr->r_char == 'W') && (r_ptr->flags1 & RF1_UNIQUE)); break; case SUMMON_UNIQUE: okay = (r_ptr->flags1 & RF1_UNIQUE); break; } /* Result */ return (okay); } /* * Place a monster (of the specified "type") near the given * location. Return TRUE iff a monster was actually summoned. * * We will attempt to place the monster up to 10 times before giving up. * * Note: SUMMON_UNIQUE and SUMMON_WRAITH (XXX) will summon Unique's * Note: SUMMON_HI_UNDEAD and SUMMON_HI_DRAGON may summon Unique's * Note: None of the other summon codes will ever summon Unique's. * * This function has been changed. We now take the "monster level" * of the summoning monster as a parameter, and use that, along with * the current dungeon level, to help determine the level * of the desired monster. Note that this is an upper bound, and * also tends to "prefer" monsters of that level. * * Currently, we use the average of the dungeon and monster levels, * and then add five to allow slight increases in monster power. * * Note that this function may not succeed, expecially when summoning * unique monsters, or when the "legal" summon group is very small. * * This would be another place where using a "temporary" monster * allocation table would be a useful optimization. XXX XXX XXX */ bool summon_specific(int y1, int x1, int lev, int type) { int i, x, y, r_idx; bool result = FALSE; /* Try to place it */ for (i = 0; i < 20; ++i) { /* Pick a distance */ int d = (i / 15) + 1; /* Pick a location */ scatter(&y, &x, y1, x1, d, 0); /* Require "empty" floor grid */ if (!empty_grid_bold(y, x)) continue; /* Hack -- no summon on glyph of warding */ if (cave[y][x].feat == CF_GLYPH) continue; /* Save the "summon" type */ summon_specific_type = type; /* Require "okay" monsters */ get_mon_num_hook = summon_specific_okay; /* Choose a monster level */ monster_level = (dun_level + lev) / 2 + 5; /* Pick a monster */ r_idx = get_mon_num(monster_level); /* Restore monster level */ monster_level = dun_level; /* Remove restriction */ get_mon_num_hook = NULL; /* Forget "summon" type */ summon_specific_type = 0; /* Handle failure */ if (!r_idx) return (FALSE); /* Attempt to place the monster (awake, allow groups) */ result = place_monster_aux(y, x, r_idx, FALSE, TRUE); /* Done */ break; } /* Failure */ return (result); } /* * Let the given monster attempt to reproduce. * * Note that "reproduction" REQUIRES empty space. * * Also used for cloning. */ bool multiply_monster(int m_idx) { monster_type *m_ptr = &m_list[m_idx]; int i, y, x; bool result = FALSE; /* Make the monsters marked as spawned */ spawning = TRUE; /* Try up to 18 times */ for (i = 0; i < 18; i++) { int d = 1; /* Pick a location */ scatter(&y, &x, m_ptr->fy, m_ptr->fx, d, 0); /* Require an "empty" floor grid */ if (!empty_grid_bold(y, x)) continue; /* Create a new monster (awake, no groups) */ result = place_monster_aux(y, x, m_ptr->r_idx, FALSE, FALSE); /* Done */ break; } /* Future monsters not spawned */ spawning = FALSE; /* Result */ return result; } /* * Dump a message describing a monster's reaction to damage * * Technically should attempt to treat "Beholder"'s as jelly's */ void message_pain(int m_idx, int dam) { long oldhp, newhp, tmp; int percentage; monster_type *m_ptr = &m_list[m_idx]; monster_race *r_ptr = m_ptr->r_ptr; char m_name[80]; /* Get the monster name */ monster_desc(m_name, m_ptr, 0); /* Notice non-damage */ if (dam == 0) { msg_format("%^s is unharmed.", m_name); return; } /* Note -- subtle fix -CFT */ newhp = (long)(m_ptr->hp); oldhp = newhp + (long)(dam); tmp = (newhp * 100L) / oldhp; percentage = (int)(tmp); /* Jelly's, Mold's, Vortex's, Quthl's */ if (strchr("jmvQ", r_ptr->r_char)) { if (percentage > 95) msg_format("%^s barely notices.", m_name); else if (percentage > 75) msg_format("%^s flinches.", m_name); else if (percentage > 50) msg_format("%^s squelches.", m_name); else if (percentage > 35) msg_format("%^s quivers in pain.", m_name); else if (percentage > 20) msg_format("%^s writhes about.", m_name); else if (percentage > 10) msg_format("%^s writhes in agony.", m_name); else msg_format("%^s jerks limply.", m_name); } /* Dogs and Hounds */ else if (strchr("CZ", r_ptr->r_char)) { if (percentage > 95) msg_format("%^s shrugs off the attack.", m_name); else if (percentage > 75) msg_format("%^s snarls with pain.", m_name); else if (percentage > 50) msg_format("%^s yelps in pain.", m_name); else if (percentage > 35) msg_format("%^s howls in pain.", m_name); else if (percentage > 20) msg_format("%^s howls in agony.", m_name); else if (percentage > 10) msg_format("%^s writhes in agony.", m_name); else msg_format("%^s yelps feebly.", m_name); } /* One type of monsters (ignore,squeal,shriek) */ else if (strchr("FIKMRSXabclqrst", r_ptr->r_char)) { if (percentage > 95) msg_format("%^s ignores the attack.", m_name); else if (percentage > 75) msg_format("%^s grunts with pain.", m_name); else if (percentage > 50) msg_format("%^s squeals in pain.", m_name); else if (percentage > 35) msg_format("%^s shrieks in pain.", m_name); else if (percentage > 20) msg_format("%^s shrieks in agony.", m_name); else if (percentage > 10) msg_format("%^s writhes in agony.", m_name); else msg_format("%^s cries out feebly.", m_name); } /* Another type of monsters (shrug,cry,scream) */ else { if (percentage > 95) msg_format("%^s shrugs off the attack.", m_name); else if (percentage > 75) msg_format("%^s grunts with pain.", m_name); else if (percentage > 50) msg_format("%^s cries out in pain.", m_name); else if (percentage > 35) msg_format("%^s screams in pain.", m_name); else if (percentage > 20) msg_format("%^s screams in agony.", m_name); else if (percentage > 10) msg_format("%^s writhes in agony.", m_name); else msg_format("%^s cries out feebly.", m_name); } } /* Determine a monster's saving throw. For simplicity, for now, assume that the monster's chance is based only on its level. Return TRUE if the monster succeeds, and FALSE if the monster bites it. -GJW */ bool monster_saves(int mlev, int plev) { int prob = 50 + 3 * (mlev - plev); if (prob < 5) prob = 5; else if (prob > 95) prob = 95; return percent(prob); }