/** * 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 *mon) { int k = 0, y, x; struct monster_lore *lore = get_lore(mon->race); /* Too many breeders on the level already */ if (num_repro >= z_info->repro_monster_max) return false; /* Count the adjacent monsters */ for (y = mon->fy - 1; y <= mon->fy + 1; y++) for (x = mon->fx - 1; x <= mon->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(mon->mflag, MFLAG_VISIBLE)) rf_on(lore->flags, RF_MULTIPLY); /* Leave now if not a breeder */ if (!rf_has(mon->race->flags, RF_MULTIPLY)) return false; /* Try to multiply */ if (multiply_monster(mon)) { /* Make a sound */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) sound(MSG_MULTIPLY); /* Multiplying takes energy */ return true; } } return false; }
/** * Helper function used with ranged_helper by do_cmd_throw. */ static struct attack_result make_ranged_throw(object_type *o_ptr, int y, int x) { struct attack_result result = {FALSE, 0, 0, "hit"}; monster_type *m_ptr = cave_monster_at(cave, y, x); monster_race *r_ptr = &r_info[m_ptr->r_idx]; int bonus = p_ptr->state.to_h + o_ptr->to_h; int chance = p_ptr->state.skills[SKILL_TO_HIT_THROW] + bonus * BTH_PLUS_ADJ; int chance2 = chance - distance(p_ptr->py, p_ptr->px, y, x); int multiplier = 1; const struct slay *best_s_ptr = NULL; /* If we missed then we're done */ if (!test_hit(chance2, r_ptr->ac, m_ptr->ml)) return result; result.success = TRUE; improve_attack_modifier(o_ptr, m_ptr, &best_s_ptr, TRUE, FALSE); /* If we have a slay, modify the multiplier appropriately */ if (best_s_ptr != NULL) { result.hit_verb = best_s_ptr->range_verb; multiplier += best_s_ptr->mult; if (best_s_ptr->vuln_flag && rf_has(r_ptr->flags, best_s_ptr->vuln_flag)) multiplier += 1; } /* Apply damage: multiplier, slays, criticals, bonuses */ result.dmg = damroll(o_ptr->dd, o_ptr->ds); result.dmg += o_ptr->to_d; result.dmg *= multiplier; result.dmg = critical_norm(o_ptr->weight, o_ptr->to_h, result.dmg, &result.msg_type); return result; }
/* * A rewrite of monster death that gets rid of some features * That we don't want to deal with. Namely, no notifying the * player and no generation of Morgoth artifacts * * It also replaces drop near with a new function that drops all * the items on the exact square that the monster was on. */ void monster_death_stats(int m_idx) { struct object *obj; struct monster *mon; bool uniq; assert(m_idx > 0); mon = cave_monster(cave, m_idx); /* Check if monster is UNIQUE */ uniq = rf_has(mon->race->flags,RF_UNIQUE); /* Mimicked objects will have already been counted as floor objects */ mon->mimicked_obj = NULL; /* Drop objects being carried */ obj = mon->held_obj; while (obj) { struct object *next = obj->next; /* Object no longer held */ obj->held_m_idx = 0; /* Get data */ get_obj_data(obj, mon->fy, mon->fx, true, uniq); /* Delete the object */ delist_object(cave, obj); object_delete(&obj); /* Next */ obj = next; } /* Forget objects */ mon->held_obj = NULL; }
/** * Monster regeneration of HPs. */ static void regen_monster(struct monster *mon) { /* Regenerate (if needed) */ if (mon->hp < mon->maxhp) { /* Base regeneration */ int frac = mon->maxhp / 100; /* Minimal regeneration rate */ if (!frac) frac = 1; /* Some monsters regenerate quickly */ if (rf_has(mon->race->flags, RF_REGENERATE)) frac *= 2; /* Regenerate */ mon->hp += frac; /* Do not over-regenerate */ if (mon->hp > mon->maxhp) mon->hp = mon->maxhp; /* Redraw (later) if needed */ if (player->upkeep->health_who == mon) player->upkeep->redraw |= (PR_HEALTH); } }
/** * Calculate minimum and desired combat ranges. -BR- */ static void find_range(struct monster *mon) { u16b p_lev, m_lev; u16b p_chp, p_mhp; u16b m_chp, m_mhp; u32b p_val, m_val; /* Monsters will run up to z_info->flee_range grids out of sight */ int flee_range = z_info->max_sight + z_info->flee_range; /* All "afraid" monsters will run away */ if (mon->m_timed[MON_TMD_FEAR]) mon->min_range = flee_range; else { /* Minimum distance - stay at least this far if possible */ mon->min_range = 1; /* Examine player power (level) */ p_lev = player->lev; /* Hack - increase p_lev based on specialty abilities */ /* Examine monster power (level plus morale) */ m_lev = mon->race->level + (mon->midx & 0x08) + 25; /* Simple cases first */ if (m_lev + 3 < p_lev) mon->min_range = flee_range; else if (m_lev - 5 < p_lev) { /* Examine player health */ p_chp = player->chp; p_mhp = player->mhp; /* Examine monster health */ m_chp = mon->hp; m_mhp = mon->maxhp; /* Prepare to optimize the calculation */ p_val = (p_lev * p_mhp) + (p_chp << 2); /* div p_mhp */ m_val = (m_lev * m_mhp) + (m_chp << 2); /* div m_mhp */ /* Strong players scare strong monsters */ if (p_val * m_mhp > m_val * p_mhp) mon->min_range = flee_range; } } if (mon->min_range < flee_range) { /* Creatures that don't move never like to get too close */ if (rf_has(mon->race->flags, RF_NEVER_MOVE)) mon->min_range += 3; /* Spellcasters that don't strike never like to get too close */ if (rf_has(mon->race->flags, RF_NEVER_BLOW)) mon->min_range += 3; } /* Maximum range to flee to */ if (!(mon->min_range < flee_range)) mon->min_range = flee_range; /* Nearby monsters won't run away */ else if (mon->cdis < z_info->turn_range) mon->min_range = 1; /* Now find preferred range */ mon->best_range = mon->min_range; /* Archers are quite happy at a good distance */ //if (rf_has(mon->race->flags, RF_ARCHER)) // mon->best_range += 3; if (mon->race->freq_spell > 24) { /* Breathers like point blank range */ if (flags_test(mon->race->spell_flags, RSF_SIZE, RSF_BREATH_MASK, FLAG_END) && (mon->best_range < 6) && (mon->hp > mon->maxhp / 2)) mon->best_range = 6; /* Other spell casters will sit back and cast */ else mon->best_range += 3; } }
/** * 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; }
/** * Choose "logical" directions for monster movement */ static bool get_moves(struct chunk *c, struct monster *mon, int *dir) { int py = player->py; int px = player->px; int y, x; /* Monsters will run up to z_info->flee_range grids out of sight */ int flee_range = z_info->max_sight + z_info->flee_range; bool done = false; /* Calculate range */ find_range(mon); /* Flow towards the player */ if (get_moves_flow(c, mon)) { /* Extract the "pseudo-direction" */ y = mon->ty - mon->fy; x = mon->tx - mon->fx; } else { /* Head straight for the player */ y = player->py - mon->fy; x = player->px - mon->fx; } /* Normal animal packs try to get the player out of corridors. */ if (rf_has(mon->race->flags, RF_GROUP_AI) && !flags_test(mon->race->flags, RF_SIZE, RF_PASS_WALL, RF_KILL_WALL, FLAG_END)) { int i, open = 0; /* Count empty grids next to player */ for (i = 0; i < 8; i++) { int ry = py + ddy_ddd[i]; int rx = px + ddx_ddd[i]; /* Check grid around the player for room interior (room walls count) * or other empty space */ if (square_ispassable(c, ry, rx) || square_isroom(c, ry, rx)) { /* One more open grid */ open++; } } /* Not in an empty space and strong player */ if ((open < 7) && (player->chp > player->mhp / 2)) { /* Find hiding place */ if (find_hiding(c, mon)) { done = true; y = mon->ty - mon->fy; x = mon->tx - mon->fx; } } } /* Apply fear */ if (!done && (mon->min_range == flee_range)) { /* Try to find safe place */ if (!find_safety(c, mon)) { /* Just leg it away from the player */ y = (-y); x = (-x); } else { /* Set a course for the safe place */ get_moves_fear(c, mon); y = mon->ty - mon->fy; x = mon->tx - mon->fx; } done = true; } /* Monster groups try to surround the player */ if (!done && rf_has(mon->race->flags, RF_GROUP_AI)) { int i, yy = mon->ty, xx = mon->tx; /* If we are not already adjacent */ if (mon->cdis > 1) { /* Find an empty square near the player to fill */ int tmp = randint0(8); for (i = 0; i < 8; i++) { /* Pick squares near player (pseudo-randomly) */ yy = py + ddy_ddd[(tmp + i) & 7]; xx = px + ddx_ddd[(tmp + i) & 7]; /* Ignore filled grids */ if (!square_isempty(cave, yy, xx)) continue; /* Try to fill this hole */ break; } } /* Extract the new "pseudo-direction" */ y = yy - mon->fy; x = xx - mon->fx; } /* Check for no move */ if (!x && !y) return (false); /* Pick the correct direction */ *dir = choose_direction(y, x); /* Want to move */ return (true); }
/* * 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_data target_set_interactive_aux(int y, int x, int mode) { s16b this_o_idx = 0, next_o_idx = 0; cptr s1, s2, s3; bool boring; bool floored; int feat; int floor_list[MAX_FLOOR_STACK]; int floor_num; ui_event_data query; char out_val[256]; char coords[20]; /* Describe the square location */ coords_desc(coords, sizeof(coords), y, x); /* Repeat forever */ while (1) { /* Paranoia */ query.key = ' '; /* Assume boring */ boring = TRUE; /* Default */ s1 = "You see "; s2 = ""; s3 = ""; /* The player */ if (cave_m_idx[y][x] < 0) { /* Description */ s1 = "You are "; /* Preposition */ s2 = "on "; } /* Hack -- hallucination */ if (p_ptr->timed[TMD_IMAGE]) { cptr name = "something strange"; /* Display a message */ if (p_ptr->wizard) { strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s (%d:%d).", s1, s2, s3, name, coords, y, x); } 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); query = inkey_ex(); /* Stop on everything but "return" */ if ((query.key != '\n') && (query.key != '\r')) break; /* Repeat forever */ continue; } /* Actual monsters */ if (cave_m_idx[y][x] > 0) { monster_type *m_ptr = &mon_list[cave_m_idx[y][x]]; monster_race *r_ptr = &r_info[m_ptr->r_idx]; /* Visible */ if (m_ptr->ml) { 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_IND2); /* Hack -- track this monster race */ monster_race_track(m_ptr->r_idx); /* Hack -- health bar for this monster */ health_track(cave_m_idx[y][x]); /* Hack -- handle stuff */ handle_stuff(); /* Interact */ while (1) { /* Recall */ if (recall) { /* Save screen */ screen_save(); /* Recall on screen */ screen_roff(m_ptr->r_idx); /* Command */ query = inkey_ex(); /* Load screen */ screen_load(); } /* Normal */ else { char buf[80]; /* Describe the monster */ look_mon_desc(buf, sizeof(buf), cave_m_idx[y][x]); /* Describe, and prompt for recall */ if (p_ptr->wizard) { strnfmt(out_val, sizeof(out_val), "%s%s%s%s (%s), %s (%d:%d).", s1, s2, s3, m_name, buf, coords, y, x); } 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 */ query = inkey_ex(); } /* Normal commands */ if (query.key != 'r') break; /* Toggle recall */ recall = !recall; } /* Stop on everything but "return"/"space" */ if ((query.key != '\n') && (query.key != '\r') && (query.key != ' ')) break; /* Sometimes stop at "space" key */ if ((query.key == ' ') && !(mode & (TARGET_LOOK))) break; /* Change the intro */ s1 = "It is "; /* Hack -- take account of gender */ if (rf_has(r_ptr->flags, RF_FEMALE)) s1 = "She is "; else if (rf_has(r_ptr->flags, RF_MALE)) s1 = "He is "; /* Use a preposition */ s2 = "carrying "; /* Scan all objects being carried */ for (this_o_idx = m_ptr->hold_o_idx; this_o_idx; this_o_idx = next_o_idx) { char o_name[80]; object_type *o_ptr; /* Get the object */ o_ptr = &o_list[this_o_idx]; /* Get the next object */ next_o_idx = o_ptr->next_o_idx; /* Obtain an object description */ object_desc(o_name, sizeof(o_name), o_ptr, ODESC_PREFIX | ODESC_FULL); /* Describe the object */ if (p_ptr->wizard) { strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s (%d:%d).", s1, s2, s3, o_name, coords, y, x); } else { strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s.", s1, s2, s3, o_name, coords); } prt(out_val, 0, 0); move_cursor_relative(y, x); query = inkey_ex(); /* Stop on everything but "return"/"space" */ if ((query.key != '\n') && (query.key != '\r') && (query.key != ' ')) break; /* Sometimes stop at "space" key */ if ((query.key == ' ') && !(mode & (TARGET_LOOK))) break; /* Change the intro */ s2 = "also carrying "; } /* Double break */ if (this_o_idx) break; /* Use a preposition */ s2 = "on "; } } /* Assume not floored */ floored = FALSE; floor_num = scan_floor(floor_list, N_ELEMENTS(floor_list), y, x, 0x02); /* Scan all marked objects in the grid */ if ((floor_num > 0) && (!(p_ptr->timed[TMD_BLIND]) || (y == p_ptr->py && x == p_ptr->px))) { /* Not boring */ boring = FALSE; track_object(-floor_list[0]); handle_stuff(); /* If there is more than one item... */ if (floor_num > 1) while (1) { floored = TRUE; /* Describe the pile */ if (p_ptr->wizard) { strnfmt(out_val, sizeof(out_val), "%s%s%sa pile of %d objects, %s (%d:%d).", s1, s2, s3, floor_num, coords, y, x); } 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); query = inkey_ex(); /* Display objects */ if (query.key == 'r') { int rdone = 0; int pos; while (!rdone) { /* Save screen */ screen_save(); /* Display */ show_floor(floor_list, floor_num, (OLIST_WEIGHT | OLIST_GOLD)); /* Describe the pile */ prt(out_val, 0, 0); query = inkey_ex(); /* Load screen */ screen_load(); pos = query.key - 'a'; if (0 <= pos && pos < floor_num) { track_object(-floor_list[pos]); handle_stuff(); continue; } rdone = 1; } /* Now that the user's done with the display loop, let's */ /* the outer loop over again */ continue; } /* Done */ break; } /* Only one object to display */ else { char o_name[80]; /* Get the single object in the list */ object_type *o_ptr = &o_list[floor_list[0]]; /* Not boring */ boring = FALSE; /* Obtain an object description */ object_desc(o_name, sizeof(o_name), o_ptr, ODESC_PREFIX | ODESC_FULL); /* Describe the object */ if (p_ptr->wizard) { strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s (%d:%d).", s1, s2, s3, o_name, coords, y, x); } else { strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s.", s1, s2, s3, o_name, coords); } prt(out_val, 0, 0); move_cursor_relative(y, x); query = inkey_ex(); /* Stop on everything but "return"/"space" */ if ((query.key != '\n') && (query.key != '\r') && (query.key != ' ')) break; /* Sometimes stop at "space" key */ if ((query.key == ' ') && !(mode & (TARGET_LOOK))) break; /* Change the intro */ s1 = "It is "; /* Plurals */ if (o_ptr->number != 1) s1 = "They are "; /* Preposition */ s2 = "on "; } } /* Double break */ if (this_o_idx) break; /* Feature (apply "mimic") */ feat = f_info[cave_feat[y][x]].mimic; /* Require knowledge about grid, or ability to see grid */ if (!(cave_info[y][x] & (CAVE_MARK)) && !player_can_see_bold(y,x)) { /* Forget feature */ feat = FEAT_NONE; } /* Terrain feature if needed */ if (boring || (feat > FEAT_INVIS)) { cptr name = f_info[feat].name; /* Hack -- handle unknown grids */ if (feat == FEAT_NONE) name = "unknown grid"; /* Pick a prefix */ if (*s2 && (feat >= FEAT_DOOR_HEAD)) s2 = "in "; /* Pick proper indefinite article */ s3 = (is_a_vowel(name[0])) ? "an " : "a "; /* Hack -- special introduction for store doors */ if ((feat >= FEAT_SHOP_HEAD) && (feat <= FEAT_SHOP_TAIL)) { s3 = "the entrance to the "; } /* Display a message */ if (p_ptr->wizard) { strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s (%d:%d).", s1, s2, s3, name, coords, y, x); } 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); query = inkey_ex(); /* Stop on everything but "return"/"space" */ if ((query.key != '\n') && (query.key != '\r') && (query.key != ' ')) break; } /* Stop on everything but "return" */ if ((query.key != '\n') && (query.key != '\r')) break; } /* Keep going */ return (query); }
/** * Evaluate the whole monster list and write a new one. power and scaled_power * are always adjusted, level, rarity and mexp only if requested. */ errr eval_monster_power(struct monster_race *racelist) { int i, j, iteration; byte lvl; struct monster_race *race = NULL; ang_file *mon_fp; char buf[1024]; bool dump = FALSE; bool wrote = TRUE; /* Allocate arrays */ power = mem_zalloc(z_info->r_max * sizeof(long)); scaled_power = mem_zalloc(z_info->r_max * sizeof(long)); final_hp = mem_zalloc(z_info->r_max * sizeof(long)); final_melee_dam = mem_zalloc(z_info->r_max * sizeof(long)); final_spell_dam = mem_zalloc(z_info->r_max * sizeof(long)); highest_threat = mem_zalloc(z_info->r_max * sizeof(int)); for (iteration = 0; iteration < 3; iteration ++) { long hp, av_hp, dam, av_dam; long *tot_hp = mem_zalloc(z_info->max_depth * sizeof(long)); long *tot_dam = mem_zalloc(z_info->max_depth * sizeof(long)); long *mon_count = mem_zalloc(z_info->max_depth * sizeof(long)); /* Reset the sum of all monster power values */ tot_mon_power = 0; /* Go through r_info and evaluate power ratings & flows. */ for (i = 0; i < z_info->r_max; i++) { /* Point at the "info" */ race = &racelist[i]; /* Set the current level */ lvl = race->level; /* Maximum damage this monster can do in 10 game turns */ dam = eval_max_dam(race, i); /* Adjust hit points based on resistances */ hp = eval_hp_adjust(race); /* Hack -- set exp */ if (lvl == 0) race->mexp = 0L; else { /* Compute depths of non-unique monsters */ if (!rf_has(race->flags, RF_UNIQUE)) { long mexp = (hp * dam) / 25; long threat = highest_threat[i]; /* Compute level algorithmically */ for (j = 1; (mexp > j + 4) || (threat > j + 5); mexp -= j * j, threat -= (j + 4), j++); /* Set level */ lvl = MIN(( j > 250 ? 90 + (j - 250) / 20 : /* Level 90+ */ (j > 130 ? 70 + (j - 130) / 6 : /* Level 70+ */ (j > 40 ? 40 + (j - 40) / 3 : /* Level 40+ */ j))), 99); /* Set level */ if (arg_rebalance) race->level = lvl; } if (arg_rebalance) { /* Hack -- for Ungoliant */ if (hp > 10000) race->mexp = (hp / 25) * (dam / lvl); else race->mexp = (hp * dam) / (lvl * 25); /* Round to 2 significant figures */ if (race->mexp > 100) { if (race->mexp < 1000) { race->mexp = (race->mexp + 5) / 10; race->mexp *= 10; } else if (race->mexp < 10000) { race->mexp = (race->mexp + 50) / 100; race->mexp *= 100; } else if (race->mexp < 100000) { race->mexp = (race->mexp + 500) / 1000; race->mexp *= 1000; } else if (race->mexp < 1000000) { race->mexp = (race->mexp + 5000) / 10000; race->mexp *= 10000; } else if (race->mexp < 10000000) { race->mexp = (race->mexp + 50000) / 100000; race->mexp *= 100000; } } } } /* If we're rebalancing, this is a nop, if not, we restore the * orig value */ lvl = race->level; if ((lvl) && (race->mexp < 1L)) race->mexp = 1L; /* * Hack - We have to use an adjustment factor to prevent overflow. * Try to scale evenly across all levels instead of scaling by level */ hp /= 2; if(hp < 1) hp = 1; final_hp[i] = hp; /* Define the power rating */ power[i] = hp * dam; /* Adjust for group monsters, using somewhat arbitrary * multipliers for now */ if (!rf_has(race->flags, RF_UNIQUE)) { if (race->friends) power[i] *= 3; } /* Adjust for escorts */ if (race->friends_base) power[i] *= 2; /* Adjust for multiplying monsters. This is modified by the speed, * as fast multipliers are much worse than slow ones. We also * adjust for ability to bypass walls or doors. */ if (rf_has(race->flags, RF_MULTIPLY)) { int adj_power; if (flags_test(race->flags, RF_SIZE, RF_KILL_WALL, RF_PASS_WALL, FLAG_END)) adj_power = power[i] * adj_energy(race); else if (flags_test(race->flags, RF_SIZE, RF_OPEN_DOOR, RF_BASH_DOOR, FLAG_END)) adj_power = power[i] * adj_energy(race) * 3 / 2; else adj_power = power[i] * adj_energy(race) / 2; power[i] = MAX(power[i], adj_power); } /* Update the running totals - these will be used as divisors later * Total HP / dam / count for everything up to the current level */ for (j = lvl; j < (lvl == 0 ? lvl + 1: z_info->max_depth); j++) { int count = 10; /* Uniques don't count towards monster power on the level. */ if (rf_has(race->flags, RF_UNIQUE)) continue; /* Specifically placed monsters don't count towards monster * power on the level. */ if (!(race->rarity)) continue; /* Hack -- provide adjustment factor to prevent overflow */ if ((j == 90) && (race->level < 90)) { hp /= 10; dam /= 10; } if ((j == 65) && (race->level < 65)) { hp /= 10; dam /= 10; } if ((j == 40) && (race->level < 40)) { hp /= 10; dam /= 10; } /* Hack - if it's a group monster or multiplying monster, add * several to the count so the averages don't get thrown off */ if (race->friends || race->friends_base) count = 15; if (rf_has(race->flags, RF_MULTIPLY)) { int adj_energy_amt; if (flags_test(race->flags, RF_SIZE, RF_KILL_WALL, RF_PASS_WALL, FLAG_END)) adj_energy_amt = adj_energy(race); else if (flags_test(race->flags, RF_SIZE, RF_OPEN_DOOR, RF_BASH_DOOR, FLAG_END)) adj_energy_amt = adj_energy(race) * 3 / 2; else adj_energy_amt = adj_energy(race) / 2; count = MAX(1, adj_energy_amt) * count; } /* Very rare monsters count less towards total monster power * on the level. */ if (race->rarity > count) { hp = hp * count / race->rarity; dam = dam * count / race->rarity; count = race->rarity; } tot_hp[j] += hp; tot_dam[j] += dam; mon_count[j] += count / race->rarity; } } /* Apply divisors now */ for (i = 0; i < z_info->r_max; i++) { int new_power; /* Point at the "info" */ race = &racelist[i]; /* Extract level */ lvl = race->level; /* Paranoia */ if (tot_hp[lvl] != 0 && tot_dam[lvl] != 0) { scaled_power[i] = power[i]; /* Divide by av HP and av damage for all in-level monsters */ /* Note we have factored in the above 'adjustment factor' */ av_hp = tot_hp[lvl] * 10 / mon_count[lvl]; av_dam = tot_dam[lvl] * 10 / mon_count[lvl]; /* Justifiable paranoia - avoid divide by zero errors */ if (av_hp > 0) scaled_power[i] = scaled_power[i] / av_hp; if (av_dam > 0) scaled_power[i] = scaled_power[i] / av_dam; /* Never less than 1 */ if (power[i] < 1) power[i] = 1; /* Set powers */ if (arg_rebalance) { race->power = power[i]; race->scaled_power = scaled_power[i]; } /* Get power */ new_power = power[i]; /* Compute rarity algorithmically */ for (j = 1; new_power > j; new_power -= j * j, j++); /* Set rarity */ if (arg_rebalance) race->rarity = j; } } mem_free(mon_count); mem_free(tot_dam); mem_free(tot_hp); } /* Determine total monster power */ for (i = 0; i < z_info->r_max; i++) tot_mon_power += r_info[i].scaled_power; if (dump) { /* Dump the power details */ path_build(buf, sizeof(buf), ANGBAND_DIR_USER, "mon_power.txt"); mon_fp = file_open(buf, MODE_WRITE, FTYPE_TEXT); file_putf(mon_fp, "ridx|level|rarity|d_char|name|pwr|scaled|melee|spell|hp\n"); for (i = 0; i < z_info->r_max; i++) { char mbstr[MB_LEN_MAX + 1] = { 0 }; race = &r_info[i]; /* Don't print anything for nonexistent monsters */ if (!race->name) continue; wctomb(mbstr, race->d_char); file_putf(mon_fp, "%d|%d|%d|%s|%s|%d|%d|%d|%d|%d\n", race->ridx, race->level, race->rarity, mbstr, race->name, power[i], scaled_power[i], final_melee_dam[i], final_spell_dam[i], final_hp[i]); } file_close(mon_fp); } /* Write to the user directory */ path_build(buf, sizeof(buf), ANGBAND_DIR_USER, "new_monster.txt"); if (text_lines_to_file(buf, write_monster_entries)) { msg("Failed to create file %s.new", buf); wrote = FALSE; } /* Free power arrays */ mem_free(highest_threat); mem_free(final_spell_dam); mem_free(final_melee_dam); mem_free(final_hp); mem_free(scaled_power); mem_free(power); /* Success */ return wrote ? 0 : -1; }
/** * Show and delete the stacked monster messages. * Some messages are delayed so that they show up after everything else. * This is to avoid things like "The snaga dies. The snaga runs in fear!" * So we only flush messages matching the delay parameter. */ static void flush_monster_messages(bool delay) { int i, r_idx, count; const monster_race *r_ptr; char buf[512]; char *action; bool action_only; /* Show every message */ for (i = 0; i < size_mon_msg; i++) { if (mon_msg[i].delay != delay) continue; /* Cache the monster count */ count = mon_msg[i].mon_count; /* Paranoia */ if (count < 1) continue; /* Start with an empty string */ buf[0] = '\0'; /* Cache the race index */ r_idx = mon_msg[i].mon_race; /* Get the proper message action */ action = get_mon_msg_action(mon_msg[i].msg_code, (count > 1), &r_info[r_idx]); /* Is it a regular race? */ if (r_idx > 0) { /* Get the race */ r_ptr = &r_info[r_idx]; } /* It's the special mark for non-visible monsters */ else { /* No race */ r_ptr = NULL; } /* Monster is marked as invisible */ if(mon_msg[i].mon_flags & 0x04) r_ptr = NULL; /* Special message? */ action_only = (*action == '~'); /* Format the proper message for visible monsters */ if (r_ptr && !action_only) { char race_name[80]; /* Get the race name */ my_strcpy(race_name, r_ptr->name, sizeof(race_name)); /* Uniques */ if (rf_has(r_ptr->flags, RF_UNIQUE)) { /* Just copy the race name */ my_strcpy(buf, (r_ptr->name), sizeof(buf)); } /* We have more than one monster */ else if (count > 1) { /* Get the plural of the race name */ plural_aux(race_name, sizeof(race_name)); /* Put the count and the race name together */ strnfmt(buf, sizeof(buf), "%d %s", count, race_name); } /* Normal lonely monsters */ else { /* Just add a slight flavor */ strnfmt(buf, sizeof(buf), "the %s", race_name); } } /* Format the message for non-viewable monsters if necessary */ else if (!r_ptr && !action_only) { if (count > 1) { /* Show the counter */ strnfmt(buf, sizeof(buf), "%d monsters", count); } else { /* Just one non-visible monster */ my_strcpy(buf, "it", sizeof(buf)); } } /* Special message. Nuke the mark */ if (action_only) ++action; /* Regular message */ else { /* Add special mark. Monster is offscreen */ if (mon_msg[i].mon_flags & 0x02) my_strcat(buf, " (offscreen)", sizeof(buf)); /* Add the separator */ my_strcat(buf, " ", sizeof(buf)); } /* Append the action to the message */ my_strcat(buf, action, sizeof(buf)); /* Capitalize the message */ *buf = toupper((unsigned char)*buf); /* Hack - play sound for fear message */ if (mon_msg[i].msg_code == MON_MSG_FLEE_IN_TERROR) sound(MSG_FLEE); /* Show the message */ msg(buf); } }
static bool describe_origin(textblock *tb, const object_type *o_ptr) { char origin_text[80]; if (o_ptr->origin_depth) strnfmt(origin_text, sizeof(origin_text), "%d feet (level %d)", o_ptr->origin_depth * 50, o_ptr->origin_depth); else my_strcpy(origin_text, "town", sizeof(origin_text)); switch (o_ptr->origin) { case ORIGIN_NONE: case ORIGIN_MIXED: case ORIGIN_STOLEN: return FALSE; case ORIGIN_BIRTH: textblock_append(tb, "An inheritance from your family.\n"); break; case ORIGIN_STORE: textblock_append(tb, "Bought from a store.\n"); break; case ORIGIN_FLOOR: textblock_append(tb, "Found lying on the floor %s %s.\n", (o_ptr->origin_depth ? "at" : "in"), origin_text); break; case ORIGIN_PIT: textblock_append(tb, "Found lying on the floor in a pit at %s.\n", origin_text); break; case ORIGIN_VAULT: textblock_append(tb, "Found lying on the floor in a vault at %s.\n", origin_text); break; case ORIGIN_SPECIAL: textblock_append(tb, "Found lying on the floor of a special room at %s.\n", origin_text); break; case ORIGIN_LABYRINTH: textblock_append(tb, "Found lying on the floor of a labyrinth at %s.\n", origin_text); break; case ORIGIN_CAVERN: textblock_append(tb, "Found lying on the floor of a cavern at %s.\n", origin_text); break; case ORIGIN_RUBBLE: textblock_append(tb, "Found under some rubble at %s.\n", origin_text); break; case ORIGIN_DROP: case ORIGIN_DROP_SPECIAL: case ORIGIN_DROP_PIT: case ORIGIN_DROP_VAULT: case ORIGIN_DROP_SUMMON: case ORIGIN_DROP_BREED: case ORIGIN_DROP_POLY: case ORIGIN_DROP_WIZARD: { const char *name; if (r_info[o_ptr->origin_xtra].ridx) name = r_info[o_ptr->origin_xtra].name; else name = "monster lost to history"; textblock_append(tb, "Dropped by "); if (rf_has(r_info[o_ptr->origin_xtra].flags, RF_UNIQUE)) textblock_append(tb, "%s", name); else textblock_append(tb, "%s%s", is_a_vowel(name[0]) ? "an " : "a ", name); textblock_append(tb, " %s %s.\n", (o_ptr->origin_depth ? "at" : "in"), origin_text); break; } case ORIGIN_DROP_UNKNOWN: textblock_append(tb, "Dropped by an unknown monster %s %s.\n", (o_ptr->origin_depth ? "at" : "in"), origin_text); break; case ORIGIN_ACQUIRE: textblock_append(tb, "Conjured forth by magic %s %s.\n", (o_ptr->origin_depth ? "at" : "in"), origin_text); break; case ORIGIN_CHEAT: textblock_append(tb, "Created by debug option.\n"); break; case ORIGIN_CHEST: textblock_append(tb, "Found in a chest from %s.\n", origin_text); break; } textblock_append(tb, "\n"); return TRUE; }
/** * Allow one item to "absorb" another, assuming they are similar. * * The blending of the "note" field assumes that either (1) one has an * inscription and the other does not, or (2) neither has an inscription. * In both these cases, we can simply use the existing note, unless the * blending object has a note, in which case we use that note. * * These assumptions are enforced by the "object_similar()" code. */ static void object_absorb_merge(struct object *obj1, const struct object *obj2) { int total; /* First object gains any extra knowledge from second */ if (obj1->known && obj2->known) { if (obj2->known->effect) obj1->known->effect = obj1->effect; player_know_object(player, obj1); } /* Merge inscriptions */ if (obj2->note) obj1->note = obj2->note; /* Combine timeouts for rod stacking */ if (tval_can_have_timeout(obj1)) obj1->timeout += obj2->timeout; /* Combine pvals for wands and staves */ if (tval_can_have_charges(obj1) || tval_is_money(obj1)) { total = obj1->pval + obj2->pval; obj1->pval = total >= MAX_PVAL ? MAX_PVAL : total; } /* Combine origin data as best we can */ if (obj1->origin != obj2->origin || obj1->origin_depth != obj2->origin_depth || obj1->origin_xtra != obj2->origin_xtra) { int act = 2; if (obj1->origin_xtra && obj2->origin_xtra) { struct monster_race *race1 = &r_info[obj1->origin_xtra]; struct monster_race *race2 = &r_info[obj2->origin_xtra]; bool r1_uniq = rf_has(race1->flags, RF_UNIQUE) ? true : false; bool r2_uniq = rf_has(race2->flags, RF_UNIQUE) ? true : false; if (r1_uniq && !r2_uniq) act = 0; else if (r2_uniq && !r1_uniq) act = 1; else act = 2; } switch (act) { /* Overwrite with obj2 */ case 1: { obj1->origin = obj2->origin; obj1->origin_depth = obj2->origin_depth; obj1->origin_xtra = obj2->origin_xtra; } /* Set as "mixed" */ case 2: { obj1->origin = ORIGIN_MIXED; } } } }
/** * 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; }
/** * Use various selection criteria (set elsewhere) to restrict monster * generation. * * This function is capable of selecting monsters by: * - racial symbol (may be any of the characters allowed) * - symbol color (may be any of up to four colors). * - racial flag(s) (monster may have any allowed flag) * - breath flag(s) (monster must have exactly the flags specified) * * Uniques may be forbidden, or allowed on rare occasions. * * Some situations (like the elemental war themed level) require special * processing; this is done in helper functions called from this one. */ static bool mon_select(int r_idx) { monster_race *r_ptr = &r_info[r_idx]; bool ok = FALSE; bitflag mon_breath[RSF_SIZE]; /* Require correct breath attack */ rsf_copy(mon_breath, r_ptr->spell_flags); flags_mask(mon_breath, RSF_SIZE, RSF_BREATH_MASK, FLAG_END); /* Special case: Elemental war themed level. */ if (p_ptr->themed_level == THEME_ELEMENTAL) { return (vault_aux_elemental(r_idx)); } /* Special case: Estolad themed level. */ if (p_ptr->themed_level == THEME_ESTOLAD) { if (!(rf_has(r_ptr->flags, RF_RACIAL))) return (FALSE); } /* Require that the monster symbol be correct. */ if (d_char_req[0] != '\0') { if (strchr(d_char_req, r_ptr->d_char) == 0) return (FALSE); } /* Require correct racial type. */ if (racial_flag_mask) { if (!(rf_has(r_ptr->flags, racial_flag_mask))) return (FALSE); /* Hack -- no invisible undead until deep. */ if ((p_ptr->danger < 40) && (rf_has(r_ptr->flags, RF_UNDEAD)) && (rf_has(r_ptr->flags, RF_INVISIBLE))) return (FALSE); } /* Require that monster breaths be exactly those specified. */ /* Exception for winged dragons */ if (!rsf_is_empty(breath_flag_mask)) { /* 'd'ragons */ if (!rsf_is_equal(mon_breath, breath_flag_mask) && (r_ptr->d_char != 'D')) return (FALSE); /* Winged 'D'ragons */ if (r_ptr->d_char == 'D') { if (!rsf_is_subset(mon_breath, breath_flag_mask)) return (FALSE); /* Hack - this deals with all current Ds that need excluding */ if (rsf_has(r_ptr->flags, RSF_BRTH_SOUND)) return (FALSE); } } /* Require that the monster color be correct. */ if (d_attr_req[0]) { /* Check all allowed colors, if given. */ if ((d_attr_req[0]) && (r_ptr->d_attr == d_attr_req[0])) ok = TRUE; if ((d_attr_req[1]) && (r_ptr->d_attr == d_attr_req[1])) ok = TRUE; if ((d_attr_req[2]) && (r_ptr->d_attr == d_attr_req[2])) ok = TRUE; if ((d_attr_req[3]) && (r_ptr->d_attr == d_attr_req[3])) ok = TRUE; /* Hack -- No multihued dragons allowed in the arcane dragon pit. */ if ((strchr(d_char_req, 'd') || strchr(d_char_req, 'D')) && (d_attr_req[0] == TERM_VIOLET) && (rf_has(r_ptr->flags, RSF_BRTH_ACID) || rf_has(r_ptr->flags, RSF_BRTH_ELEC) || rf_has(r_ptr->flags, RSF_BRTH_FIRE) || rf_has(r_ptr->flags, RSF_BRTH_COLD) || rf_has(r_ptr->flags, RSF_BRTH_POIS))) { return (FALSE); } /* Doesn't match any of the given colors? Not good. */ if (!ok) return (FALSE); } /* Usually decline unique monsters. */ if (rf_has(r_ptr->flags, RF_UNIQUE)) { if (!allow_unique) return (FALSE); else if (randint0(5) != 0) return (FALSE); } /* Okay */ return (TRUE); }
/** * Accept characters representing a race or group of monsters and * an (adjusted) depth, and use these to set values for required racial * type, monster symbol, monster symbol color, and breath type. -LM- * * This function is called to set restrictions, point the monster * allocation function to mon_select(), and remake monster allocation. * It undoes all of this things when called with the symbol '\0'. * * Describe the monsters (used by cheat_room) and determine if they * should be neatly ordered or randomly placed (used in monster pits). */ extern char *mon_restrict(char symbol, byte depth, bool * ordered, bool unique_ok) { int i, j; /* Assume no definite name */ char name[80] = "misc"; /* Clear global monster restriction variables. */ allow_unique = unique_ok; for (i = 0; i < 10; i++) d_char_req[i] = '\0'; for (i = 0; i < 4; i++) d_attr_req[i] = 0; racial_flag_mask = 0; rsf_wipe(breath_flag_mask); /* No symbol, no restrictions. */ if (symbol == '\0') { get_mon_num_hook = NULL; get_mon_num_prep(); return ("misc"); } /* Handle the "wild card" symbol '*' */ if (symbol == '*') { for (i = 0; i < 2500; i++) { /* Get a random monster. */ j = randint1(z_info->r_max - 1); /* Must be a real monster */ if (!r_info[j].rarity) continue; /* Try for close to depth, accept in-depth if necessary */ if (i < 200) { if ((!rf_has(r_info[j].flags, RF_UNIQUE)) && (r_info[j].level != 0) && (r_info[j].level <= depth) && (ABS(r_info[j].level - p_ptr->danger) < 1 + (p_ptr->danger / 4))) break; } else { if ((!rf_has(r_info[j].flags, RF_UNIQUE)) && (r_info[j].level != 0) && (r_info[j].level <= depth)) break; } } /* We've found a monster. */ if (i < 2499) { /* ...use that monster's symbol for all monsters. */ symbol = r_info[j].d_char; } else { /* Paranoia - pit stays empty if no monster is found */ return (NULL); } } /* Apply monster restrictions according to symbol. */ switch (symbol) { /* All animals */ case 'A': { strcpy(name, "animal"); racial_flag_mask = RF_ANIMAL; *ordered = FALSE; break; } /* Insects */ case '1': { strcpy(name, "insect"); strcpy(d_char_req, "aclFIK"); *ordered = FALSE; break; } /* Reptiles */ case '2': { strcpy(name, "reptile"); strcpy(d_char_req, "nJR"); *ordered = FALSE; break; } /* Jellies, etc. */ case '3': { strcpy(name, "jelly"); strcpy(d_char_req, "ijm,"); *ordered = FALSE; break; } /* Humans and humaniods */ case 'p': case 'h': { /* 'p's and 'h's can coexist. */ if (randint0(3) == 0) { strcpy(d_char_req, "ph"); /* If so, they will usually all be of similar classes. */ if (randint0(4) != 0) { /* Randomizer. */ i = randint0(5); /* Magicians and necromancers */ if (i == 0) { d_attr_req[0] = TERM_RED; d_attr_req[1] = TERM_L_RED; d_attr_req[2] = TERM_VIOLET; strcpy(name, "school of sorcery"); } /* Priests and paladins */ else if (i == 1) { d_attr_req[0] = TERM_GREEN; d_attr_req[1] = TERM_L_GREEN; d_attr_req[2] = TERM_WHITE; d_attr_req[3] = TERM_L_WHITE; strcpy(name, "temple of piety"); } /* Druids and ninjas */ else if (i == 2) { d_attr_req[0] = TERM_ORANGE; d_attr_req[1] = TERM_YELLOW; strcpy(name, "gathering of nature"); } /* Thieves and assassins */ else if (i == 3) { d_attr_req[0] = TERM_BLUE; d_attr_req[1] = TERM_L_BLUE; d_attr_req[2] = TERM_SLATE; d_attr_req[3] = TERM_L_DARK; strcpy(name, "den of thieves"); } /* Warriors and rangers */ else { d_attr_req[0] = TERM_UMBER; d_attr_req[1] = TERM_L_UMBER; strcpy(name, "fighter's hall"); } } else { strcpy(name, "humans and humanoids"); } } /* Usually, just accept the symbol. */ else { d_char_req[0] = symbol; if (symbol == 'p') strcpy(name, "human"); else if (symbol == 'h') strcpy(name, "humanoid"); } *ordered = FALSE; break; } /* Orcs */ case 'o': { strcpy(name, "orc"); strcpy(d_char_req, "o"); *ordered = TRUE; break; } /* Trolls */ case 'T': { strcpy(name, "troll"); strcpy(d_char_req, "T"); *ordered = TRUE; break; } /* Giants (sometimes ogres at low levels) */ case 'P': { strcpy(name, "giant"); if ((p_ptr->danger < 30) && (randint0(3) == 0)) strcpy(d_char_req, "O"); else strcpy(d_char_req, "P"); *ordered = TRUE; break; } /* Orcs, ogres, trolls, or giants */ case '%': { strcpy(name, "moria"); strcpy(d_char_req, "oOPT"); *ordered = FALSE; break; } /* Monsters found in caves */ case '0': { strcpy(name, "dungeon monsters"); strcpy(d_char_req, "ykoOT"); *ordered = FALSE; break; } /* Monsters found in wilderness caves */ case 'x': { strcpy(name, "underworld monsters"); strcpy(d_char_req, "bgkosuyOTUVXW"); *ordered = FALSE; break; } /* Undead */ case 'N': { /* Sometimes, restrict by symbol. */ if ((depth > 40) && (randint0(3) == 0)) { for (i = 0; i < 500; i++) { /* Find a suitable monster near depth. */ j = randint1(z_info->r_max - 1); /* Require a non-unique undead. */ if (rf_has(r_info[j].flags, RF_UNDEAD) && (!rf_has(r_info[j].flags, RF_UNIQUE)) && (strchr("GLWV", r_info[j].d_char)) && (ABS(r_info[j].level - p_ptr->danger) < 1 + (p_ptr->danger / 4))) { break; } } /* If we find a monster, */ if (i < 499) { /* Use that monster's symbol for all monsters */ d_char_req[0] = r_info[j].d_char; /* No pit name (yet) */ /* In this case, we do order the monsters */ *ordered = TRUE; } else { /* Accept any undead. */ strcpy(name, "undead"); racial_flag_mask = RF_UNDEAD; *ordered = FALSE; } } else { /* No restrictions on symbol. */ strcpy(name, "undead"); racial_flag_mask = RF_UNDEAD; *ordered = FALSE; } break; } /* Demons */ case 'u': case 'U': { strcpy(name, "demon"); if (depth > 55) strcpy(d_char_req, "U"); else if (depth < 40) strcpy(d_char_req, "u"); else strcpy(d_char_req, "uU"); *ordered = TRUE; break; } /* Dragons */ case 'd': case 'D': { strcpy(d_char_req, "dD"); /* Dragons usually associate with others of their kind. */ if (randint0(6) != 0) { /* Dragons of a single kind are ordered. */ *ordered = TRUE; /* Some dragon types are not found everywhere */ if (depth > 70) i = randint0(35); else if (depth > 45) i = randint0(32); else if (depth > 32) i = randint0(30); else if (depth > 23) i = randint0(28); else i = randint0(24); if (i < 4) { flags_init(breath_flag_mask, RSF_SIZE, RSF_BRTH_ACID, FLAG_END); strcpy(name, "dragon - acid"); } else if (i < 8) { flags_init(breath_flag_mask, RSF_SIZE, RSF_BRTH_ELEC, FLAG_END); strcpy(name, "dragon - electricity"); } else if (i < 12) { flags_init(breath_flag_mask, RSF_SIZE, RSF_BRTH_FIRE, FLAG_END); strcpy(name, "dragon - fire"); } else if (i < 16) { flags_init(breath_flag_mask, RSF_SIZE, RSF_BRTH_COLD, FLAG_END); strcpy(name, "dragon - cold"); } else if (i < 20) { flags_init(breath_flag_mask, RSF_SIZE, RSF_BRTH_POIS, FLAG_END); strcpy(name, "dragon - poison"); } else if (i < 24) { flags_init(breath_flag_mask, RSF_SIZE, RSF_BRTH_ACID, RSF_BRTH_ELEC, RSF_BRTH_FIRE, RSF_BRTH_COLD, RSF_BRTH_POIS, FLAG_END); strcpy(name, "dragon - multihued"); } else if (i < 26) { flags_init(breath_flag_mask, RSF_SIZE, RSF_BRTH_CONFU, FLAG_END); strcpy(name, "dragon - confusion"); } else if (i < 28) { flags_init(breath_flag_mask, RSF_SIZE, RSF_BRTH_SOUND, FLAG_END); strcpy(name, "dragon - sound"); } else if (i < 30) { flags_init(breath_flag_mask, RSF_SIZE, RSF_BRTH_LIGHT, RSF_BRTH_DARK, FLAG_END); strcpy(name, "dragon - ethereal"); } /* Chaos, Law, Balance, Power, etc.) */ else { d_attr_req[0] = TERM_VIOLET; d_attr_req[1] = TERM_L_BLUE; d_attr_req[2] = TERM_L_GREEN; strcpy(name, "dragon - arcane"); } } else { strcpy(name, "dragon - mixed"); /* Dragons of all kinds are not ordered. */ *ordered = FALSE; } break; } /* Vortexes and elementals */ case 'v': case 'E': { /* Usually, just have any kind of 'v' or 'E' */ if (randint0(3) != 0) { d_char_req[0] = symbol; if (symbol == 'v') strcpy(name, "vortex"); if (symbol == 'E') strcpy(name, "elemental"); } /* Sometimes, choose both 'v' and 'E's of one element */ else { strcpy(d_char_req, "vE"); i = randint0(4); /* Fire */ if (i == 0) { d_attr_req[0] = TERM_RED; strcpy(name, "fire"); } /* Frost */ if (i == 1) { d_attr_req[0] = TERM_L_WHITE; d_attr_req[1] = TERM_WHITE; strcpy(name, "frost"); } /* Air/electricity */ if (i == 2) { d_attr_req[0] = TERM_L_BLUE; d_attr_req[1] = TERM_BLUE; strcpy(name, "air"); } /* Acid/water/earth */ if (i == 3) { d_attr_req[0] = TERM_GREEN; d_attr_req[1] = TERM_L_UMBER; d_attr_req[2] = TERM_UMBER; d_attr_req[3] = TERM_SLATE; strcpy(name, "earth & water"); } } *ordered = FALSE; break; } /* Special case: mimics and treasure */ case '!': case '?': case '=': case '~': case '|': case '.': case '$': { if (symbol == '$') { strcpy(name, "treasure"); /* Nothing but loot! */ if (randint0(3) == 0) strcpy(d_char_req, "$"); /* Guard the money well. */ else strcpy(d_char_req, "$!?=~|."); } else { /* No treasure. */ strcpy(d_char_req, "!?=~|."); strcpy(name, "mimic"); } *ordered = FALSE; break; } /* Special case: creatures of earth. */ case 'X': case '#': { strcpy(d_char_req, "X#"); strcpy(name, "creatures of earth"); *ordered = FALSE; break; } /* Water creatures. */ case '6': { allow_unique = TRUE; strcpy(d_char_req, "vEZ"); d_attr_req[0] = TERM_SLATE; break; } /* Beings of fire or ice. */ case '7': { allow_unique = TRUE; strcpy(d_char_req, "vE"); if (randint0(2) == 0) d_attr_req[0] = TERM_RED; else { d_attr_req[0] = TERM_L_WHITE; d_attr_req[1] = TERM_WHITE; } break; } /* Space for more monster types here. */ /* Any symbol not handled elsewhere. */ default: { /* Accept the character. */ d_char_req[0] = symbol; /* Some monsters should logically be ordered. */ if (strchr("knosuyzGLMOPTUVW", symbol)) *ordered = TRUE; /* Most should not */ else *ordered = FALSE; break; } } /* If monster pit hasn't been named already, get a name. */ if (streq(name, "misc")) { /* Search a table for a description of the symbol */ for (i = 0; d_char_req_desc[i]; ++i) { if (symbol == d_char_req_desc[i][0]) { /* Get all but the 1st 2 characters of the text. */ sprintf(name, "%s", d_char_req_desc[i] + 2); break; } } } /* Apply our restrictions */ get_mon_num_hook = mon_select; /* Prepare allocation table */ get_mon_num_prep(); /* Return the name. */ return (format("%s", name)); }
/** * Generate a random level. * * Confusingly, this function also generate the town level (level 0). * \param c is the level we're going to end up with, in practice the global cave * \param p is the current player struct, in practice the global player */ void cave_generate(struct chunk **c, struct player *p) { const char *error = "no generation"; int y, x, tries = 0; struct chunk *chunk; assert(c); /* Generate */ for (tries = 0; tries < 100 && error; tries++) { struct dun_data dun_body; error = NULL; /* Mark the dungeon as being unready (to avoid artifact loss, etc) */ character_dungeon = FALSE; /* Allocate global data (will be freed when we leave the loop) */ dun = &dun_body; dun->cent = mem_zalloc(z_info->level_room_max * sizeof(struct loc)); dun->door = mem_zalloc(z_info->level_door_max * sizeof(struct loc)); dun->wall = mem_zalloc(z_info->wall_pierce_max * sizeof(struct loc)); dun->tunn = mem_zalloc(z_info->tunn_grid_max * sizeof(struct loc)); /* Choose a profile and build the level */ dun->profile = choose_profile(p->depth); chunk = dun->profile->builder(p); if (!chunk) { error = "Failed to find builder"; mem_free(dun->cent); mem_free(dun->door); mem_free(dun->wall); mem_free(dun->tunn); continue; } /* Ensure quest monsters */ if (is_quest(chunk->depth)) { int i; for (i = 1; i < z_info->r_max; i++) { monster_race *r_ptr = &r_info[i]; int y, x; /* The monster must be an unseen quest monster of this depth. */ if (r_ptr->cur_num > 0) continue; if (!rf_has(r_ptr->flags, RF_QUESTOR)) continue; if (r_ptr->level != chunk->depth) continue; /* Pick a location and place the monster */ find_empty(chunk, &y, &x); place_new_monster(chunk, y, x, r_ptr, TRUE, TRUE, ORIGIN_DROP); } } /* Clear generation flags. */ for (y = 0; y < chunk->height; y++) { for (x = 0; x < chunk->width; x++) { sqinfo_off(chunk->squares[y][x].info, SQUARE_WALL_INNER); sqinfo_off(chunk->squares[y][x].info, SQUARE_WALL_OUTER); sqinfo_off(chunk->squares[y][x].info, SQUARE_WALL_SOLID); sqinfo_off(chunk->squares[y][x].info, SQUARE_MON_RESTRICT); } } /* Regenerate levels that overflow their maxima */ if (cave_monster_max(chunk) >= z_info->level_monster_max) error = "too many monsters"; if (error) ROOM_LOG("Generation restarted: %s.", error); mem_free(dun->cent); mem_free(dun->door); mem_free(dun->wall); mem_free(dun->tunn); } if (error) quit_fmt("cave_generate() failed 100 times!"); /* Free the old cave, use the new one */ if (*c) cave_free(*c); *c = chunk; /* Place dungeon squares to trigger feeling (not in town) */ if (player->depth) place_feeling(*c); /* Save the town */ else if (!chunk_find_name("Town")) { struct chunk *town = chunk_write(0, 0, z_info->town_hgt, z_info->town_wid, FALSE, FALSE, FALSE); town->name = string_make("Town"); chunk_list_add(town); } (*c)->feeling = calc_obj_feeling(*c) + calc_mon_feeling(*c); /* Validate the dungeon (we could use more checks here) */ chunk_validate_objects(*c); /* The dungeon is ready */ character_dungeon = TRUE; /* Free old and allocate new known level */ if (cave_k) cave_free(cave_k); cave_k = cave_new(cave->height, cave->width); if (!cave->depth) cave_known(); (*c)->created_at = turn; }
/* * Display known monsters. */ static void do_cmd_knowledge_monsters(const char *name, int row) { group_funcs r_funcs = { N_ELEMENTS(monster_group), FALSE, race_name, m_cmp_race, default_group, mon_summary }; member_funcs m_funcs = { display_monster, mon_lore, m_xchar, m_xattr, recall_prompt, 0, 0 }; int *monsters; int m_count = 0; int i; size_t j; for (i = 0; i < z_info->r_max; i++) { monster_race *r_ptr = &r_info[i]; if (!OPT(cheat_know) && !l_list[i].sights) continue; if (!r_ptr->name) continue; if (rf_has(r_ptr->flags, RF_UNIQUE)) m_count++; for (j = 1; j < N_ELEMENTS(monster_group) - 1; j++) { const wchar_t *pat = monster_group[j].chars; if (wcschr(pat, r_ptr->d_char)) m_count++; } } default_join = C_ZNEW(m_count, join_t); monsters = C_ZNEW(m_count, int); m_count = 0; for (i = 0; i < z_info->r_max; i++) { monster_race *r_ptr = &r_info[i]; if (!OPT(cheat_know) && !l_list[i].sights) continue; if (!r_ptr->name) continue; for (j = 0; j < N_ELEMENTS(monster_group) - 1; j++) { const wchar_t *pat = monster_group[j].chars; if (j == 0 && !(rf_has(r_ptr->flags, RF_UNIQUE))) continue; else if (j > 0 && !wcschr(pat, r_ptr->d_char)) continue; monsters[m_count] = m_count; default_join[m_count].oid = i; default_join[m_count++].gid = j; } } display_knowledge("monsters", monsters, m_count, r_funcs, m_funcs, " Sym Kills"); FREE(default_join); FREE(monsters); }
/** * 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); }
/** * 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, u16b *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 = GRID_Y(path_g[i]); int x = GRID_X(path_g[i]); /* * 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 (cave->m_idx[y][x] && cave_monster_at(cave, y, x)->ml) { /* Visible monsters are red. */ monster_type *m_ptr = cave_monster_at(cave, y, x); /* Mimics act as objects */ if (rf_has(m_ptr->race->flags, RF_UNAWARE)) colour = TERM_YELLOW; else colour = TERM_L_RED; } else if (cave->o_idx[y][x] && object_byid(cave->o_idx[y][x])->marked) /* Known objects are yellow. */ colour = TERM_YELLOW; else if (!cave_ispassable(cave, y,x) && ((cave->info[y][x] & (CAVE_MARK)) || player_can_see_bold(y,x))) /* Known walls are blue. */ colour = TERM_BLUE; else if (!(cave->info[y][x] & (CAVE_MARK)) && !player_can_see_bold(y,x)) /* Unknown squares are grey. */ colour = TERM_L_DARK; else /* Unoccupied squares are white. */ colour = TERM_WHITE; /* Draw the path segment */ (void)Term_addch(colour, L'*'); } return i; }
/** * 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; } }
//static struct keypress target_set_interactive_aux(int y, int x, int mode) static ui_event target_set_interactive_aux(int y, int x, int mode) { s16b this_o_idx = 0, next_o_idx = 0; const char *s1, *s2, *s3; bool boring; int floor_list[MAX_FLOOR_STACK]; int floor_num; //struct keypress query; ui_event press; char out_val[256]; 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->m_idx[y][x] < 0) { /* Description */ s1 = "You are "; /* Preposition */ s2 = "on "; } /* Hallucination messes things up */ if (p_ptr->timed[TMD_IMAGE]) { const char *name = "something strange"; /* Display a message */ if (p_ptr->wizard) strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s (%d:%d).", s1, s2, s3, name, coords, y, x); 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); //input = inkey_m(); //if ( press.key = inkey(); /* Stop on everything but "return" */ if (press.key.code == KC_ENTER) continue; return press; } /* Actual monsters */ if (cave->m_idx[y][x] > 0) { monster_type *m_ptr = cave_monster_at(cave, y, x); const monster_lore *l_ptr = get_lore(m_ptr->race); /* Visible */ if (m_ptr->ml && !m_ptr->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_IND2); /* Hack -- track this monster race */ monster_race_track(m_ptr->race); /* Hack -- health bar for this monster */ health_track(p_ptr, m_ptr); /* Hack -- handle stuff */ handle_stuff(p_ptr); /* Interact */ while (1) { /* Recall */ if (recall) { /* Save screen */ screen_save(); /* Recall on screen */ screen_roff(m_ptr->race, l_ptr); /* Command */ press = inkey_m(); /* Load screen */ screen_load(); } /* Normal */ else { char buf[80]; /* Describe the monster */ look_mon_desc(buf, sizeof(buf), cave->m_idx[y][x]); /* Describe, and prompt for recall */ if (p_ptr->wizard) { strnfmt(out_val, sizeof(out_val), "%s%s%s%s (%s), %s (%d:%d).", s1, s2, s3, m_name, buf, coords, y, x); } 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 (this_o_idx = m_ptr->hold_o_idx; this_o_idx; this_o_idx = next_o_idx) { char o_name[80]; object_type *o_ptr; /* Get the object */ o_ptr = object_byid(this_o_idx); /* Get the next object */ next_o_idx = o_ptr->next_o_idx; /* Obtain an object description */ object_desc(o_name, sizeof(o_name), o_ptr, ODESC_PREFIX | ODESC_FULL); /* Describe the object */ if (p_ptr->wizard) { strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s (%d:%d).", s1, s2, s3, o_name, coords, y, x); } /* Disabled since monsters now carry their drops else { strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s.", s1, s2, s3, o_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; /* 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 (this_o_idx) break; /* Use a preposition */ s2 = "on "; } } /* Assume not floored */ floor_num = scan_floor(floor_list, N_ELEMENTS(floor_list), y, x, 0x0A); /* Scan all marked objects in the grid */ if ((floor_num > 0) && (!(p_ptr->timed[TMD_BLIND]) || (y == p_ptr->py && x == p_ptr->px))) { /* Not boring */ boring = FALSE; track_object(-floor_list[0]); handle_stuff(p_ptr); /* If there is more than one item... */ if (floor_num > 1) while (1) { /* Describe the pile */ if (p_ptr->wizard) { strnfmt(out_val, sizeof(out_val), "%s%s%sa pile of %d objects, %s (%d:%d).", s1, s2, s3, floor_num, coords, y, x); } 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)); /* 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(-floor_list[pos]); handle_stuff(p_ptr); continue; } rdone = 1; } /* Now that the user's done with the display loop, let's */ /* the outer loop over again */ continue; } /* Done */ break; } /* Only one object to display */ else { char o_name[80]; /* Get the single object in the list */ object_type *o_ptr = object_byid(floor_list[0]); /* Not boring */ boring = FALSE; /* Obtain an object description */ object_desc(o_name, sizeof(o_name), o_ptr, ODESC_PREFIX | ODESC_FULL); /* Describe the object */ if (p_ptr->wizard) { strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s (%d:%d).", s1, s2, s3, o_name, coords, y, x); } else { strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s.", s1, s2, s3, o_name, coords); } prt(out_val, 0, 0); move_cursor_relative(y, x); 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; /* Change the intro */ s1 = "It is "; /* Plurals */ if (o_ptr->number != 1) s1 = "They are "; /* Preposition */ s2 = "on "; } } /* Double break */ if (this_o_idx) break; name = cave_apparent_name(cave, p_ptr, y, x); /* Terrain feature if needed */ if (boring || cave_isinteresting(cave, y, x)) { /* Hack -- handle unknown grids */ /* Pick a prefix */ if (*s2 && cave_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 (cave_isshop(cave, y, x)) { s3 = "the entrance to the "; } /* Display a message */ if (p_ptr->wizard) { strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s (%d:%d).", s1, s2, s3, name, coords, y, x); } 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; } } /* Keep going */ return (press); }
/** * 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); }
/** * Accept characters representing a race or group of monsters and * an (adjusted) depth, and use these to set values for required * monster base symbol. * * \param monster_type the monster type to be selected, as described below * \param depth the native depth to choose monsters * \param unique_ok whether to allow uniques to be chosen * \return success if the monster allocation table has been rebuilt * * This code has been adapted from Oangband code to make use of monster bases. * * This function is called to set restrictions, point the monster * allocation function to mon_select() or mon_pit_hook(), and remake monster * allocation. * It undoes all of this things when called with monster_type NULL. * If called with a pit profile name, it will get monsters from that profile. * If called with monster_type "random", it will get a random monster base and * describe the monsters by its name (for use by cheat_room). */ bool mon_restrict(const char *monster_type, int depth, bool unique_ok) { int i, j = 0; /* Clear global monster restriction variables. */ allow_unique = unique_ok; for (i = 0; i < 10; i++) base_d_char[i] = '\0'; /* No monster type specified, no restrictions. */ if (monster_type == NULL) { get_mon_num_prep(NULL); return true; } else if (streq(monster_type, "random")) { /* Handle random */ for (i = 0; i < 2500; i++) { /* Get a random monster. */ j = randint1(z_info->r_max - 1); /* Must be a real monster */ if (!r_info[j].rarity) continue; /* Try for close to depth, accept in-depth if necessary */ if (i < 200) { if ((!rf_has(r_info[j].flags, RF_UNIQUE)) && (r_info[j].level != 0) && (r_info[j].level <= depth) && (ABS(r_info[j].level - player->depth) < 1 + (player->depth / 4))) break; } else { if ((!rf_has(r_info[j].flags, RF_UNIQUE)) && (r_info[j].level != 0) && (r_info[j].level <= depth)) break; } } /* We've found a monster. */ if (i < 2499) { /* Use that monster's base type for all monsters. */ my_strcpy(base_d_char, format("%c", r_info[j].base->d_char), sizeof(base_d_char)); /* Prepare allocation table */ get_mon_num_prep(mon_select); return true; } else /* Paranoia - area stays empty if no monster is found */ return false; } else { /* Use a pit profile */ struct pit_profile *profile = lookup_pit_profile(monster_type); /* Accept the profile or leave area empty if none found */ if (profile) dun->pit_type = profile; else return false; /* Prepare allocation table */ get_mon_num_prep(mon_pit_hook); return true; } }
bool describe_origin(textblock * tb, const object_type * o_ptr) { char origin_text[80]; /* Format location of origin */ if (stage_map[o_ptr->origin_stage][DEPTH]) strnfmt(origin_text, sizeof(origin_text), "%s Level %d", locality_name[stage_map[o_ptr->origin_stage][LOCALITY]], stage_map[o_ptr->origin_stage][DEPTH]); else strnfmt(origin_text, sizeof(origin_text), "%s %s", locality_name[stage_map[o_ptr->origin_stage][LOCALITY]], "Town"); switch (o_ptr->origin) { case ORIGIN_NONE: case ORIGIN_MIXED: return FALSE; case ORIGIN_BIRTH: textblock_append(tb, "An inheritance from your family.\n"); break; case ORIGIN_STORE: textblock_append(tb, "Bought from a store in %s.\n", origin_text); break; case ORIGIN_FLOOR: textblock_append(tb, "Found lying on the ground in %s.\n", origin_text); break; case ORIGIN_DROP: { const char *name = r_info[o_ptr->origin_xtra].name; textblock_append(tb, "Dropped by "); if (rf_has(r_info[o_ptr->origin_xtra].flags, RF_UNIQUE) && !rf_has(r_info[o_ptr->origin_xtra].flags, RF_PLAYER_GHOST)) textblock_append(tb, "%s", name); else textblock_append(tb, "%s%s", is_a_vowel(name[0]) ? "an " : "a ", name); textblock_append(tb, " in %s.\n", origin_text); break; } case ORIGIN_DROP_UNKNOWN: textblock_append(tb, "Dropped by an unknown monster in %s.\n", origin_text); break; case ORIGIN_ACQUIRE: textblock_append(tb, "Conjured forth by magic in %s.\n", origin_text); break; case ORIGIN_CHEAT: textblock_append(tb, "Created by debug option.\n"); break; case ORIGIN_CHEST: //if (o_ptr->origin_xtra) if (0) { /* Add in when player ghost issues are fixed */ const char *name = r_info[o_ptr->origin_xtra].name; textblock_append(tb, "Found in a chest dropped by "); if (rf_has(r_info[o_ptr->origin_xtra].flags, RF_UNIQUE) && !rf_has(r_info[o_ptr->origin_xtra].flags, RF_PLAYER_GHOST)) textblock_append(tb, "%s", name); else textblock_append(tb, "%s%s", is_a_vowel(name[0]) ? "an " : "a ", name); textblock_append(tb, " in %s.\n", origin_text); break; } textblock_append(tb, "Found in a chest from %s.\n", origin_text); break; case ORIGIN_RUBBLE: textblock_append(tb, "Found under some rubble in %s.\n", origin_text); break; case ORIGIN_VAULT: textblock_append(tb, "Found in a vault in %s.\n", origin_text); break; case ORIGIN_CHAOS: textblock_append(tb, "Created by the forces of chaos in %s.\n"); break; } return TRUE; }
bool describe_origin(textblock *tb, const object_type *o_ptr) { char origin_text[80]; if (o_ptr->origin_depth) strnfmt(origin_text, sizeof(origin_text), "%d feet (level %d)", o_ptr->origin_depth * 50, o_ptr->origin_depth); else my_strcpy(origin_text, "town", sizeof(origin_text)); switch (o_ptr->origin) { case ORIGIN_NONE: case ORIGIN_MIXED: return FALSE; case ORIGIN_BIRTH: textblock_append(tb, "An inheritance from your family.\n"); break; case ORIGIN_STORE: textblock_append(tb, "Bought from a store.\n"); break; case ORIGIN_FLOOR: textblock_append(tb, "Found lying on the floor %s %s.\n", (o_ptr->origin_depth ? "at" : "in"), origin_text); break; case ORIGIN_DROP: { const char *name = r_info[o_ptr->origin_xtra].name; textblock_append(tb, "Dropped by "); if (rf_has(r_info[o_ptr->origin_xtra].flags, RF_UNIQUE)) textblock_append(tb, "%s", name); else textblock_append(tb, "%s%s", is_a_vowel(name[0]) ? "an " : "a ", name); textblock_append(tb, " %s %s.\n", (o_ptr->origin_depth ? "at" : "in"), origin_text); break; } case ORIGIN_DROP_UNKNOWN: textblock_append(tb, "Dropped by an unknown monster %s %s.\n", (o_ptr->origin_depth ? "at" : "in"), origin_text); break; case ORIGIN_ACQUIRE: textblock_append(tb, "Conjured forth by magic %s %s.\n", (o_ptr->origin_depth ? "at" : "in"), origin_text); break; case ORIGIN_CHEAT: textblock_append(tb, "Created by debug option.\n"); break; case ORIGIN_CHEST: textblock_append(tb, "Found in a chest from %s.\n", origin_text); break; } textblock_append(tb, "\n"); return TRUE; }
/** * Choose the best direction for "flowing". * * Note that ghosts and rock-eaters generally don't flow because they can move * through obstacles. * * Monsters first try to use up-to-date distance information ('sound') as * saved in cave->squares[y][x].noise. Failing that, they'll try using scent * ('scent') which is just old noise information. * * Tracking by 'scent' means that monsters end up near enough the player to * switch to 'sound' (noise), or they end up somewhere the player left via * teleport. Teleporting away from a location will cause the monsters who * were chasing the player to converge on that location as long as the player * is still near enough to "annoy" them without being close enough to chase * directly. */ static bool get_moves_flow(struct chunk *c, struct monster *mon) { int i; int best_scent = 0; int best_noise = 999; int best_direction = 0; bool found_direction = false; int py = player->py, px = player->px; int my = mon->fy, mx = mon->fx; /* Only use this algorithm for passwall monsters if near permanent walls, * to avoid getting snagged */ if (flags_test(mon->race->flags, RF_SIZE, RF_PASS_WALL, RF_KILL_WALL, FLAG_END) && !near_permwall(mon, c)) return (false); /* The player is not currently near the monster grid */ if (c->squares[my][mx].scent < c->squares[py][px].scent) /* If the player has never been near this grid, abort */ if (c->squares[my][mx].scent == 0) return false; /* Monster is too far away to notice the player */ if (c->squares[my][mx].noise > z_info->max_flow_depth) return false; if (c->squares[my][mx].noise > mon->race->aaf) return false; /* If the player can see monster, set target and run towards them */ if (square_isview(c, my, mx)) { mon->ty = player->py; mon->tx = player->px; return false; } /* Check nearby grids, diagonals first */ /* This gives preference to the cardinal directions */ for (i = 7; i >= 0; i--) { /* Get the location */ int y = my + ddy_ddd[i]; int x = mx + ddx_ddd[i]; /* Bounds check */ if (!square_in_bounds(c, y, x)) continue; /* Ignore unvisited/unpassable locations */ if (c->squares[y][x].scent == 0) continue; /* Ignore locations whose data is more stale */ if (c->squares[y][x].scent < best_scent) continue; /* Ignore locations which are farther away */ if (c->squares[y][x].noise > best_noise) continue; /* Ignore lava if they can't handle the heat */ if (square_isfiery(c, y, x) && !rf_has(mon->race->flags, RF_IM_FIRE)) continue; /* Save the noise and time */ best_scent = c->squares[y][x].scent; best_noise = c->squares[y][x].noise; best_direction = i; found_direction = true; } /* Save the location to flow toward */ /* Multiply by 16 to angle slightly toward the player's actual location */ if (found_direction) { int dy = 0, dx = 0; /* Ridiculous - actually multiply by whatever doesn't underflow the * byte for ty and tx. Really should do a better solution - NRM */ for (i = 0; i < 16; i++) if ((py + dy > 0) && (px + dx > 0)) { dy += ddy_ddd[best_direction]; dx += ddx_ddd[best_direction]; } mon->ty = py + dy; mon->tx = px + dx; return true; } return false; }
static long eval_hp_adjust(struct monster_race *race) { long hp; int resists = 1; int hide_bonus = 0; /* Get the monster base hitpoints */ hp = race->avg_hp; /* Never moves with no ranged attacks - high hit points count for less */ if (rf_has(race->flags, RF_NEVER_MOVE) && !(race->freq_innate || race->freq_spell)) { hp /= 2; if (hp < 1) hp = 1; } /* Just assume healers have more staying power */ if (rsf_has(race->spell_flags, RSF_HEAL)) hp = (hp * 6) / 5; /* Miscellaneous improvements */ if (rf_has(race->flags, RF_REGENERATE)) {hp *= 10; hp /= 9;} if (rf_has(race->flags, RF_PASS_WALL)) {hp *= 3; hp /= 2;} /* Calculate hide bonus */ if (rf_has(race->flags, RF_EMPTY_MIND)) hide_bonus += 2; else { if (rf_has(race->flags, RF_COLD_BLOOD)) hide_bonus += 1; if (rf_has(race->flags, RF_WEIRD_MIND)) hide_bonus += 1; } /* Invisibility */ if (rf_has(race->flags, RF_INVISIBLE)) hp = (hp * (race->level + hide_bonus + 1)) / MAX(1, race->level); /* Monsters that can teleport are a hassle, and can easily run away */ if (flags_test(race->spell_flags, RSF_SIZE, RSF_TPORT, RSF_TELE_AWAY, RSF_TELE_LEVEL, FLAG_END)) hp = (hp * 6) / 5; /* Monsters that multiply are tougher to kill */ if (rf_has(race->flags, RF_MULTIPLY)) hp *= 2; /* Monsters with resistances are harder to kill. * Therefore effective slays / brands against them are worth more. */ if (rf_has(race->flags, RF_IM_ACID)) resists += 2; if (rf_has(race->flags, RF_IM_FIRE)) resists += 2; if (rf_has(race->flags, RF_IM_COLD)) resists += 2; if (rf_has(race->flags, RF_IM_ELEC)) resists += 2; if (rf_has(race->flags, RF_IM_POIS)) resists += 2; /* Bonus for multiple basic resists and weapon resists */ if (resists >= 12) resists *= 6; else if (resists >= 10) resists *= 4; else if (resists >= 8) resists *= 3; else if (resists >= 6) resists *= 2; /* If quite resistant, reduce resists by defense holes */ if (resists >= 6) { if (rf_has(race->flags, RF_HURT_ROCK)) resists -= 1; if (rf_has(race->flags, RF_HURT_LIGHT)) resists -= 1; if (!rf_has(race->flags, RF_NO_SLEEP)) resists -= 3; if (!rf_has(race->flags, RF_NO_FEAR)) resists -= 2; if (!rf_has(race->flags, RF_NO_CONF)) resists -= 2; if (!rf_has(race->flags, RF_NO_STUN)) resists -= 1; if (resists < 5) resists = 5; } /* If quite resistant, bonus for high resists */ if (resists >= 3) { if (rf_has(race->flags, RF_IM_WATER)) resists += 1; if (rf_has(race->flags, RF_IM_NETHER)) resists += 1; if (rf_has(race->flags, RF_IM_NEXUS)) resists += 1; if (rf_has(race->flags, RF_IM_DISEN)) resists += 1; } /* Scale resists */ resists = resists * 25; /* Monster resistances */ if (resists < (race->ac + resists) / 3) hp += (hp * resists) / (150 + race->level); else hp += (hp * (race->ac + resists) / 3) / (150 + race->level); /* Boundary control */ if (hp < 1) hp = 1; return (hp); }
/** * Choose a "safe" location near a monster for it to run toward. * * A location is "safe" if it can be reached quickly and the player * is not able to fire into it (it isn't a "clean shot"). So, this will * cause monsters to "duck" behind walls. Hopefully, monsters will also * try to run towards corridor openings if they are in a room. * * This function may take lots of CPU time if lots of monsters are fleeing. * * Return true if a safe location is available. */ static bool find_safety(struct chunk *c, struct monster *mon) { int fy = mon->fy; int fx = mon->fx; int py = player->py; int px = player->px; int i, y, x, dy, dx, d, dis; int gy = 0, gx = 0, gdis = 0; const int *y_offsets; const int *x_offsets; /* Start with adjacent locations, spread further */ for (d = 1; d < 10; d++) { /* Get the lists of points with a distance d from (fx, fy) */ y_offsets = dist_offsets_y[d]; x_offsets = dist_offsets_x[d]; /* Check the locations */ for (i = 0, dx = x_offsets[0], dy = y_offsets[0]; dx != 0 || dy != 0; i++, dx = x_offsets[i], dy = y_offsets[i]) { y = fy + dy; x = fx + dx; /* Skip illegal locations */ if (!square_in_bounds_fully(c, y, x)) continue; /* Skip locations in a wall */ if (!square_ispassable(c, y, x)) continue; /* Ignore grids very far from the player */ if (c->squares[y][x].scent < c->squares[py][px].scent) continue; /* Ignore too-distant grids */ if (c->squares[y][x].noise > c->squares[fy][fx].noise + 2 * d) continue; /* Ignore lava if they can't handle the heat */ if (square_isfiery(c, y, x) && !rf_has(mon->race->flags, RF_IM_FIRE)) continue; /* Check for absence of shot (more or less) */ if (!square_isview(c, y, x)) { /* Calculate distance from player */ dis = distance(y, x, py, px); /* Remember if further than previous */ if (dis > gdis) { gy = y; gx = x; gdis = dis; } } } /* Check for success */ if (gdis > 0) { /* Good location */ mon->ty = gy; mon->tx = gx; /* Found safe place */ return (true); } } /* No safe place */ return (false); }
static long eval_max_dam(struct monster_race *race, int ridx) { int rlev, i; int melee_dam = 0, atk_dam = 0, spell_dam = 0; int dam = 1; /* Extract the monster level, force 1 for town monsters */ rlev = ((race->level >= 1) ? race->level : 1); /* Assume single resist for the elemental attacks */ spell_dam = best_spell_power(race, 1); /* Hack - Apply over 10 rounds */ spell_dam *= 10; /* Scale for frequency and availability of mana / ammo */ if (spell_dam) { int freq = race->freq_spell; /* Hack -- always get 1 shot */ if (freq < 10) freq = 10; /* Adjust for frequency */ spell_dam = spell_dam * freq / 100; } /* Check attacks */ for (i = 0; i < z_info->mon_blows_max; i++) { int effect, method; random_value dice; if (!race->blow) break; /* Extract the attack infomation */ effect = race->blow[i].effect; method = race->blow[i].method; dice = race->blow[i].dice; /* Assume maximum damage */ atk_dam = eval_blow_effect(effect, dice, race->level); /* Factor for dangerous side effects */ if (monster_blow_method_stun(method)) { /* Stun definitely most dangerous*/ atk_dam *= 4; atk_dam /= 3; } else if (monster_blow_method_stun(method)) { /* Cut */ atk_dam *= 7; atk_dam /= 5; } /* Normal melee attack */ if (!rf_has(race->flags, RF_NEVER_BLOW)) { /* Keep a running total */ melee_dam += atk_dam; } } /* Apply damage over 10 rounds. We assume that the monster has to make * contact first. * Hack - speed has more impact on melee as has to stay in contact with * player. * Hack - this is except for pass wall and kill wall monsters which can * always get to the player. * Hack - use different values for huge monsters as they strike out to * range 2. */ if (flags_test(race->flags, RF_SIZE, RF_KILL_WALL, RF_PASS_WALL, FLAG_END)) melee_dam *= 10; else melee_dam = melee_dam * 3 + melee_dam * adj_energy(race) / 7; /* Scale based on attack accuracy. We make a massive number of * assumptions here and just use monster level. */ melee_dam = melee_dam * MIN(45 + rlev * 3, 95) / 100; /* Hack -- Monsters that multiply ignore the following reductions */ if (!rf_has(race->flags, RF_MULTIPLY)) { /*Reduce damamge potential for monsters that move randomly */ if (flags_test(race->flags, RF_SIZE, RF_RAND_25, RF_RAND_50, FLAG_END)) { int reduce = 100; if (rf_has(race->flags, RF_RAND_25)) reduce -= 25; if (rf_has(race->flags, RF_RAND_50)) reduce -= 50; /* Even moving randomly one in 8 times will hit the player */ reduce += (100 - reduce) / 8; /* Adjust the melee damage */ melee_dam = (melee_dam * reduce) / 100; } /* Monsters who can't move are much less of a combat threat */ if (rf_has(race->flags, RF_NEVER_MOVE)) { if (rsf_has(race->spell_flags, RSF_TELE_TO) || rsf_has(race->spell_flags, RSF_BLINK)) { /* Scale for frequency */ melee_dam = melee_dam / 5 + 4 * melee_dam * race->freq_spell / 500; /* Incorporate spell failure chance */ if (!rf_has(race->flags, RF_STUPID)) melee_dam = melee_dam / 5 + 4 * melee_dam * MIN(75 + (rlev + 3) / 4, 100) / 500; } else if (rf_has(race->flags, RF_INVISIBLE)) melee_dam /= 3; else melee_dam /= 5; } } /* But keep at a minimum */ if (melee_dam < 1) melee_dam = 1; /* Combine spell and melee damage */ dam = (spell_dam + melee_dam); highest_threat[ridx] = dam; final_spell_dam[ridx] = spell_dam; final_melee_dam[ridx] = melee_dam; /* Adjust for speed - monster at speed 120 will do double damage, monster * at speed 100 will do half, etc. Bonus for monsters who can haste self */ dam = (dam * adj_energy(race)) / 10; /* Adjust threat for speed -- multipliers are more threatening. */ if (rf_has(race->flags, RF_MULTIPLY)) highest_threat[ridx] = (highest_threat[ridx] * adj_energy(race)) / 5; /* Adjust threat for friends, this can be improved, but is probably good * enough for now. */ if (race->friends) highest_threat[ridx] *= 2; else if (race->friends_base) /* Friends base is weaker, because they are <= monster level */ highest_threat[ridx] = highest_threat[ridx] * 3 / 2; /* But keep at a minimum */ if (dam < 1) dam = 1; /* We're done */ return (dam); }
/** * 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); }