/** * Helper function used with ranged_helper by do_cmd_fire. */ static struct attack_result make_ranged_shot(struct object *ammo, int y, int x) { char *hit_verb = mem_alloc(20 * sizeof(char)); struct attack_result result = {FALSE, 0, 0, hit_verb}; struct object *bow = equipped_item_by_slot_name(player, "shooting"); struct monster *mon = square_monster(cave, y, x); int chance = chance_of_missile_hit(player, ammo, bow, y, x); int multiplier = player->state.ammo_mult; const struct brand *b = NULL; const struct slay *s = NULL; my_strcpy(hit_verb, "hits", sizeof(hit_verb)); /* Did we hit it (penalize distance travelled) */ if (!test_hit(chance, mon->race->ac, mflag_has(mon->mflag, MFLAG_VISIBLE))) return result; result.success = TRUE; improve_attack_modifier(ammo, mon, &b, &s, result.hit_verb, TRUE, TRUE, FALSE); improve_attack_modifier(bow, mon, &b, &s, result.hit_verb, TRUE, TRUE, FALSE); result.dmg = ranged_damage(ammo, bow, b, s, multiplier); result.dmg = critical_shot(ammo->weight, ammo->to_h, result.dmg, &result.msg_type); object_notice_attack_plusses(bow); return result; }
/* Make doors */ static void project_feature_handler_MAKE_DOOR(project_feature_handler_context_t *context) { const int x = context->x; const int y = context->y; /* Require a grid without monsters */ if (square_monster(cave, y, x)) return; /* Require a floor grid */ if (!square_isfloor(cave, y, x)) return; /* Push objects off the grid */ if (square_object(cave, y, x)) push_object(y,x); /* Create closed door */ square_add_door(cave, y, x, true); /* Observe */ if (square_isknown(cave, y, x)) context->obvious = true; /* Update the visuals */ player->upkeep->update |= (PU_UPDATE_VIEW | PU_MONSTERS); }
/** * Helper function used with ranged_helper by do_cmd_throw. */ static struct attack_result make_ranged_throw(struct object *obj, int y, int x) { char *hit_verb = mem_alloc(20*sizeof(char)); struct attack_result result = {FALSE, 0, 0, hit_verb}; struct monster *mon = square_monster(cave, y, x); int chance = chance_of_missile_hit(player, obj, NULL, y, x); int multiplier = 1; const struct brand *b = NULL; const struct slay *s = NULL; my_strcpy(hit_verb, "hits", sizeof(hit_verb)); /* If we missed then we're done */ if (!test_hit(chance, mon->race->ac, mflag_has(mon->mflag, MFLAG_VISIBLE))) return result; result.success = TRUE; improve_attack_modifier(obj, mon, &b, &s, result.hit_verb, TRUE, TRUE, FALSE); result.dmg = ranged_damage(obj, NULL, b, s, multiplier); result.dmg = critical_norm(obj->weight, obj->to_h, result.dmg, &result.msg_type); return result; }
/** * Attack the monster at the given location * * We get blows until energy drops below that required for another blow, or * until the target monster dies. Each blow is handled by py_attack_real(). * We don't allow @ to spend more than 100 energy in one go, to avoid slower * monsters getting double moves. */ void py_attack(int y, int x) { int blow_energy = 100 * z_info->move_energy / player->state.num_blows; int blows = 0; bool fear = FALSE; struct monster *mon = square_monster(cave, y, x); /* disturb the player */ disturb(player, 0); /* Initialize the energy used */ player->upkeep->energy_use = 0; /* Attack until energy runs out or enemy dies. We limit energy use to 100 * to avoid giving monsters a possible double move. */ while (player->energy >= blow_energy * (blows + 1)) { bool stop = py_attack_real(y, x, &fear); player->upkeep->energy_use += blow_energy; if (player->upkeep->energy_use + blow_energy > z_info->move_energy || stop) break; blows++; } /* Hack - delay fear messages */ if (fear && mflag_has(mon->mflag, MFLAG_VISIBLE)) { char m_name[80]; /* Don't set monster_desc flags, since add_monster_message does string * processing on m_name */ monster_desc(m_name, sizeof(m_name), mon, MDESC_DEFAULT); add_monster_message(m_name, mon, MON_MSG_FLEE_IN_TERROR, TRUE); } }
/** * Attempts to place a copy of the given monster at the given position in * the dungeon. * * All of the monster placement routines eventually call this function. This * is what actually puts the monster in the dungeon (i.e., it notifies the cave * and sets the monsters position). The dungeon loading code also calls this * function directly. * * `origin` is the item origin to use for any monster drops (e.g. ORIGIN_DROP, * ORIGIN_DROP_PIT, etc.) The dungeon loading code calls this with origin = 0, * which prevents the monster's drops from being generated again. * * Returns the m_idx of the newly copied monster, or 0 if the placement fails. */ s16b place_monster(struct chunk *c, int y, int x, struct monster *mon, byte origin) { s16b m_idx; struct monster *new_mon; assert(square_in_bounds(c, y, x)); assert(!square_monster(c, y, x)); /* Get a new record */ m_idx = mon_pop(c); if (!m_idx) return 0; /* Copy the monster */ new_mon = cave_monster(c, m_idx); memcpy(new_mon, mon, sizeof(struct monster)); /* Set the ID */ new_mon->midx = m_idx; /* Set the location */ c->squares[y][x].mon = new_mon->midx; new_mon->fy = y; new_mon->fx = x; assert(square_monster(c, y, x) == new_mon); update_mon(new_mon, c, true); /* Hack -- Count the number of "reproducers" */ if (rf_has(new_mon->race->flags, RF_MULTIPLY)) num_repro++; /* Count racial occurrences */ new_mon->race->cur_num++; /* Create the monster's drop, if any */ if (origin) (void)mon_create_drop(c, new_mon, origin); /* Make mimics start mimicking */ if (origin && new_mon->race->mimic_kinds) { mon_create_mimicked_object(c, new_mon, m_idx); } /* Result */ return m_idx; }
/** * This routine will Perma-Light all grids in the set passed in. * * This routine is used (only) by "light_room()" * * Dark grids are illuminated. * * Also, process all affected monsters. * * SMART monsters always wake up when illuminated * NORMAL monsters wake up 1/4 the time when illuminated * STUPID monsters wake up 1/10 the time when illuminated */ static void cave_light(struct point_set *ps) { int i; /* Apply flag changes */ for (i = 0; i < ps->n; i++) { int y = ps->pts[i].y; int x = ps->pts[i].x; /* Perma-Light */ sqinfo_on(cave->squares[y][x].info, SQUARE_GLOW); } /* Fully update the visuals */ player->upkeep->update |= (PU_FORGET_VIEW | PU_UPDATE_VIEW | PU_MONSTERS); /* Update stuff */ update_stuff(player); /* Process the grids */ for (i = 0; i < ps->n; i++) { int y = ps->pts[i].y; int x = ps->pts[i].x; /* Redraw the grid */ square_light_spot(cave, y, x); /* Process affected monsters */ if (cave->squares[y][x].mon > 0) { int chance = 25; monster_type *m_ptr = square_monster(cave, y, x); /* Stupid monsters rarely wake up */ if (rf_has(m_ptr->race->flags, RF_STUPID)) chance = 10; /* Smart monsters always wake up */ if (rf_has(m_ptr->race->flags, RF_SMART)) chance = 100; /* Sometimes monsters wake up */ if (m_ptr->m_timed[MON_TMD_SLEEP] && (randint0(100) < chance)) { /* Wake up! */ mon_clear_timed(m_ptr, MON_TMD_SLEEP, MON_TMD_FLG_NOTIFY, FALSE); } } } }
/** * Apply blow after effects */ static bool blow_after_effects(int y, int x, bool quake) { /* Apply earthquake brand */ if (quake) { effect_simple(EF_EARTHQUAKE, "0", 0, 10, 0, NULL); /* Monster may be dead or moved */ if (!square_monster(cave, y, x)) return TRUE; } return FALSE; }
/** * Check if the monster can move any monster on the relevant grid */ static bool monster_can_move(struct chunk *c, struct monster *mon, int y, int x) { struct monster *mon1 = square_monster(c, y, x); /* No monster */ if (!mon1) return true; if (rf_has(mon->race->flags, RF_MOVE_BODY) && compare_monsters(mon, mon1) > 0) { return true; } return false; }
/** * Try to push past / kill another monster. Returns true on success. */ static bool process_monster_try_push(struct chunk *c, struct monster *mon, const char *m_name, int nx, int ny) { struct monster *mon1 = square_monster(c, ny, nx); struct monster_lore *lore = get_lore(mon->race); /* Kill weaker monsters */ int kill_ok = rf_has(mon->race->flags, RF_KILL_BODY); /* Move weaker monsters if they can swap places */ /* (not in a wall) */ int move_ok = (rf_has(mon->race->flags, RF_MOVE_BODY) && square_ispassable(c, mon->fy, mon->fx)); if (compare_monsters(mon, mon1) > 0) { /* Learn about pushing and shoving */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) { rf_on(lore->flags, RF_KILL_BODY); rf_on(lore->flags, RF_MOVE_BODY); } if (kill_ok || move_ok) { /* Get the names of the monsters involved */ char n_name[80]; monster_desc(n_name, sizeof(n_name), mon1, MDESC_IND_HID); /* Reveal mimics */ if (is_mimicking(mon1)) become_aware(mon1); /* Note if visible */ if (mflag_has(mon->mflag, MFLAG_VISIBLE) && mflag_has(mon->mflag, MFLAG_VIEW)) msg("%s %s %s.", m_name, kill_ok ? "tramples over" : "pushes past", n_name); /* Monster ate another monster */ if (kill_ok) delete_monster(ny, nx); monster_swap(mon->fy, mon->fx, ny, nx); return true; } } return false; }
/** * Set target to closest monster. */ bool target_set_closest(int mode) { int y, x; struct monster *mon; char m_name[80]; struct point_set *targets; /* Cancel old target */ target_set_monster(0); /* Get ready to do targetting */ targets = target_get_monsters(mode); /* If nothing was prepared, then return */ if (point_set_size(targets) < 1) { msg("No Available Target."); point_set_dispose(targets); return false; } /* Find the first monster in the queue */ y = targets->pts[0].y; x = targets->pts[0].x; mon = square_monster(cave, y, x); /* Target the monster, if possible */ if (!target_able(mon)) { msg("No Available Target."); point_set_dispose(targets); return false; } /* Target the monster */ monster_desc(m_name, sizeof(m_name), mon, MDESC_CAPITAL); if (!(mode & TARGET_QUIET)) msg("%s is targeted.", m_name); /* Set up target information */ monster_race_track(player->upkeep, mon->race); health_track(player->upkeep, mon); target_set_monster(mon); point_set_dispose(targets); return true; }
/** * Return a target set of target_able monsters. */ struct point_set *target_get_monsters(int mode, monster_predicate pred) { int y, x; int min_y, min_x, max_y, max_x; struct point_set *targets = point_set_new(TS_INITIAL_SIZE); /* Get the current panel */ get_panel(&min_y, &min_x, &max_y, &max_x); /* Scan for targets */ for (y = min_y; y < max_y; y++) { for (x = min_x; x < max_x; x++) { struct loc grid = loc(x, y); /* Check bounds */ if (!square_in_bounds_fully(cave, grid)) continue; /* Require "interesting" contents */ if (!target_accept(y, x)) continue; /* Special mode */ if (mode & (TARGET_KILL)) { struct monster *mon = square_monster(cave, grid); /* Must contain a monster */ if (mon == NULL) continue; /* Must be a targettable monster */ if (!target_able(mon)) continue; /* Must be the right sort of monster */ if (pred && !pred(mon)) continue; } /* Save the location */ add_to_point_set(targets, grid); } } sort(targets->pts, point_set_size(targets), sizeof(*(targets->pts)), cmp_distance); return targets; }
/** * This routine will Perma-Light all grids in the set passed in. * * This routine is used (only) by "light_room()" * * Dark grids are illuminated. * * Also, process all affected monsters. * * SMART monsters always wake up when illuminated * NORMAL monsters wake up 1/4 the time when illuminated * STUPID monsters wake up 1/10 the time when illuminated */ static void cave_light(struct point_set *ps) { int i; /* Apply flag changes */ for (i = 0; i < ps->n; i++) { int y = ps->pts[i].y; int x = ps->pts[i].x; /* Perma-Light */ sqinfo_on(cave->squares[y][x].info, SQUARE_GLOW); } /* Process the grids */ for (i = 0; i < ps->n; i++) { int y = ps->pts[i].y; int x = ps->pts[i].x; /* Redraw the grid */ square_light_spot(cave, y, x); /* Process affected monsters */ if (cave->squares[y][x].mon > 0) { int chance = 25; struct monster *mon = square_monster(cave, y, x); /* Stupid monsters rarely wake up */ if (monster_is_stupid(mon)) chance = 10; /* Smart monsters always wake up */ if (monster_is_smart(mon)) chance = 100; /* Sometimes monsters wake up */ if (mon->m_timed[MON_TMD_SLEEP] && (randint0(100) < chance)) { mon_clear_timed(mon, MON_TMD_SLEEP, MON_TMD_FLG_NOTIFY, false); } } } }
/** * Set the target to a location */ void target_set_location(int y, int x) { /* Legal target */ if (square_in_bounds_fully(cave, y, x)) { struct monster *mon = square_monster(cave, y, x); /* Save target info */ target_set = TRUE; target_who = NULL; if (mon && target_able(mon)) target_who = mon; target_y = y; target_x = x; return; } /* Reset target info */ target_set = FALSE; target_who = 0; target_y = 0; target_x = 0; }
/** * Determine if a given location is "interesting" */ bool target_accept(int y, int x) { struct loc grid = loc(x, y); struct object *obj; /* Player grids are always interesting */ if (square(cave, grid).mon < 0) return true; /* Handle hallucination */ if (player->timed[TMD_IMAGE]) return false; /* Obvious monsters */ if (square(cave, grid).mon > 0) { struct monster *mon = square_monster(cave, grid); if (monster_is_obvious(mon)) { return true; } } /* Traps */ if (square_isvisibletrap(cave, grid)) return true; /* Scan all objects in the grid */ for (obj = square_object(player->cave, grid); obj; obj = obj->next) { /* Memorized object */ if ((obj->kind == unknown_item_kind) || !ignore_known_item_ok(obj)) { return true; } } /* Interesting memorized features */ if (square_isknown(cave, grid) && square_isinteresting(cave, grid)) { return true; } /* Nope */ return false; }
/** * Determine if a given location is "interesting" */ bool target_accept(int y, int x) { struct object *obj; /* Player grids are always interesting */ if (cave->squares[y][x].mon < 0) return (true); /* Handle hallucination */ if (player->timed[TMD_IMAGE]) return (false); /* Visible monsters */ if (cave->squares[y][x].mon > 0) { struct monster *mon = square_monster(cave, y, x); /* Visible monsters */ if (mflag_has(mon->mflag, MFLAG_VISIBLE) && !mflag_has(mon->mflag, MFLAG_UNAWARE)) return (true); } /* Traps */ if (square_isvisibletrap(cave, y, x)) return(true); /* Scan all objects in the grid */ for (obj = square_object(cave_k, y, x); obj; obj = obj->next) /* Memorized object */ if (!ignore_known_item_ok(obj)) return (true); /* Interesting memorized features */ if (square_isknown(cave, y, x) && square_isinteresting(cave, y, x)) return (true); /* Nope */ return (false); }
/** * Determine if a given location is "interesting" */ bool target_accept(int y, int x) { object_type *obj; /* Player grids are always interesting */ if (cave->squares[y][x].mon < 0) return (TRUE); /* Handle hallucination */ if (player->timed[TMD_IMAGE]) return (FALSE); /* Visible monsters */ if (cave->squares[y][x].mon > 0) { monster_type *m_ptr = square_monster(cave, y, x); /* Visible monsters */ if (mflag_has(m_ptr->mflag, MFLAG_VISIBLE) && !mflag_has(m_ptr->mflag, MFLAG_UNAWARE)) return (TRUE); } /* Traps */ if (square_isvisibletrap(cave, y, x)) return(TRUE); /* Scan all objects in the grid */ for (obj = square_object(cave, y, x); obj; obj = obj->next) /* Memorized object */ if (obj->marked && !ignore_item_ok(obj)) return (TRUE); /* Interesting memorized features */ if (square_ismark(cave, y, x) && !square_isboring(cave, y, x)) return (TRUE); /* Nope */ return (FALSE); }
/** * Return a target set of target_able monsters. */ struct point_set *target_get_monsters(int mode) { int y, x; int min_y, min_x, max_y, max_x; struct point_set *targets = point_set_new(TS_INITIAL_SIZE); /* Get the current panel */ get_panel(&min_y, &min_x, &max_y, &max_x); /* Scan for targets */ for (y = min_y; y < max_y; y++) { for (x = min_x; x < max_x; x++) { /* Check bounds */ if (!square_in_bounds_fully(cave, y, x)) continue; /* Require "interesting" contents */ if (!target_accept(y, x)) continue; /* Special mode */ if (mode & (TARGET_KILL)) { /* Must contain a monster */ if (!(cave->squares[y][x].mon > 0)) continue; /* Must be a targettable monster */ if (!target_able(square_monster(cave, y, x))) continue; } /* Save the location */ add_to_point_set(targets, y, x); } } sort(targets->pts, point_set_size(targets), sizeof(*(targets->pts)), cmp_distance); return targets; }
/** * Take one step along the current "run" path * * Called with a real direction to begin a new run, and with zero * to continue a run in progress. */ void run_step(int dir) { /* Start or continue run */ if (dir) { /* Initialize */ run_init(dir); /* Hack -- Set the run counter */ player->upkeep->running = 1000; /* Calculate torch radius */ player->upkeep->update |= (PU_TORCH); } else { /* Continue running */ if (!player->upkeep->running_withpathfind) { /* Update regular running */ if (run_test()) { /* Disturb */ disturb(player, 0); return; } } else if (pf_result_index < 0) { /* Pathfinding, and the path is finished */ disturb(player, 0); player->upkeep->running_withpathfind = false; return; } else { int y = player->py + ddy[pf_result[pf_result_index] - '0']; int x = player->px + ddx[pf_result[pf_result_index] - '0']; if (pf_result_index == 0) { /* Known wall */ if (square_isknown(cave, y, x) && !square_ispassable(cave, y, x)) { disturb(player, 0); player->upkeep->running_withpathfind = false; return; } } else if (pf_result_index > 0) { struct object *obj; /* If the player has computed a path that is going to end up * in a wall, we notice this and convert to a normal run. This * allows us to click on unknown areas to explore the map. * * We have to look ahead two, otherwise we don't know which is * the last direction moved and don't initialise the run * properly. */ y = player->py + ddy[pf_result[pf_result_index] - '0']; x = player->px + ddx[pf_result[pf_result_index] - '0']; /* Known wall */ if (square_isknown(cave, y, x) && !square_ispassable(cave, y, x)) { disturb(player, 0); player->upkeep->running_withpathfind = false; return; } /* Visible monsters abort running */ if (cave->squares[y][x].mon > 0) { struct monster *mon = square_monster(cave, y, x); /* Visible monster */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) { disturb(player, 0); player->upkeep->running_withpathfind = false; return; } } /* Visible objects abort running */ for (obj = square_object(cave, y, x); obj; obj = obj->next) /* Visible object */ if (obj->known && !ignore_item_ok(obj)) { disturb(player, 0); player->upkeep->running_withpathfind = false; return; } /* Get step after */ y = y + ddy[pf_result[pf_result_index - 1] - '0']; x = x + ddx[pf_result[pf_result_index - 1] - '0']; /* Known wall, so run the direction we were going */ if (square_isknown(cave, y, x) && !square_ispassable(cave, y, x)) { player->upkeep->running_withpathfind = false; run_init(pf_result[pf_result_index] - '0'); } } /* Now actually run the step if we're still going */ run_cur_dir = pf_result[pf_result_index--] - '0'; } } /* Decrease counter if it hasn't been cancelled */ if (player->upkeep->running) player->upkeep->running--; else if (!player->upkeep->running_withpathfind) return; /* Take time */ player->upkeep->energy_use = z_info->move_energy; /* Move the player; running straight into a trap == trying to disarm */ move_player(run_cur_dir, dir ? true : false); /* Prepare the next step */ if (player->upkeep->running) { cmdq_push(CMD_RUN); cmd_set_arg_direction(cmdq_peek(), "direction", 0); } }
/** * 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); }
/** * Places a monster (of the specified "type") near the given * location. Return the siummoned monster's level iff a monster was * actually summoned. * * We will attempt to place the monster up to 10 times before giving up. * * This function takes the "monster level" * of the summoning monster as a parameter, and use that, along with * the current dungeon level, to help determine the level of the * desired monster. Note that this is an upper bound, and also * tends to "prefer" monsters of that level. Currently, we use * the average of the dungeon and monster levels, and then add * five to allow slight increases in monster power. * * Note that we use the new "monster allocation table" creation code * to restrict the "get_mon_num()" function to the set of "legal" * monsters, making this function much faster and more reliable. * * Note that this function may not succeed, though this is very rare. */ int summon_specific(int y1, int x1, int lev, int type, bool delay, bool call) { int i, x = 0, y = 0; struct monster *mon; struct monster_race *race; /* Look for a location, allow up to 4 squares away */ for (i = 0; i < 60; ++i) { /* Pick a distance */ int d = (i / 15) + 1; /* Pick a location */ scatter(cave, &y, &x, y1, x1, d, true); /* Require "empty" floor grid */ if (!square_isempty(cave, y, x)) continue; /* No summon on glyphs */ if (square_iswarded(cave, y, x) || square_isdecoyed(cave, y, x)) { continue; } /* Okay */ break; } /* Failure */ if (i == 60) return (0); /* Save the "summon" type */ summon_specific_type = type; /* Use the new calling scheme if requested */ if (call && (type != summon_name_to_idx("UNIQUE")) && (type != summon_name_to_idx("WRAITH"))) { return (call_monster(y, x)); } /* Prepare allocation table */ get_mon_num_prep(summon_specific_okay); /* Pick a monster, using the level calculation */ race = get_mon_num((player->depth + lev) / 2 + 5); /* Prepare allocation table */ get_mon_num_prep(NULL); /* Handle failure */ if (!race) return (0); /* Attempt to place the monster (awake, don't allow groups) */ if (!place_new_monster(cave, y, x, race, false, false, ORIGIN_DROP_SUMMON)) return (0); /* Success, return the level of the monster */ mon = square_monster(cave, y, x); /* If delay, try to let the player act before the summoned monsters, * including slowing down faster monsters for one turn */ /* XXX should this now be hold monster for a turn? */ if (delay) { mon->energy = 0; if (mon->race->speed > player->state.speed) mon_inc_timed(mon, MON_TMD_SLOW, 1, MON_TMD_FLG_NOMESSAGE, false); } return (mon->race->level); }
/** * Attack the monster at the given location with a single blow. */ static bool py_attack_real(int y, int x, bool *fear) { size_t i; /* Information about the target of the attack */ struct monster *mon = square_monster(cave, y, x); char m_name[80]; bool stop = FALSE; /* The weapon used */ struct object *obj = equipped_item_by_slot_name(player, "weapon"); /* Information about the attack */ int chance = py_attack_hit_chance(obj); bool do_quake = FALSE; bool success = FALSE; /* Default to punching for one damage */ char verb[20]; int dmg = 1; u32b msg_type = MSG_HIT; /* Default to punching for one damage */ my_strcpy(verb, "punch", sizeof(verb)); /* Extract monster name (or "it") */ monster_desc(m_name, sizeof(m_name), mon, MDESC_OBJE | MDESC_IND_HID | MDESC_PRO_HID); /* Auto-Recall if possible and visible */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) monster_race_track(player->upkeep, mon->race); /* Track a new monster */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) health_track(player->upkeep, mon); /* Handle player fear (only for invisible monsters) */ if (player_of_has(player, OF_AFRAID)) { msgt(MSG_AFRAID, "You are too afraid to attack %s!", m_name); return FALSE; } /* Disturb the monster */ mon_clear_timed(mon, MON_TMD_SLEEP, MON_TMD_FLG_NOMESSAGE, FALSE); /* See if the player hit */ success = test_hit(chance, mon->race->ac, mflag_has(mon->mflag, MFLAG_VISIBLE)); /* If a miss, skip this hit */ if (!success) { msgt(MSG_MISS, "You miss %s.", m_name); return FALSE; } /* Handle normal weapon */ if (obj) { int j; const struct brand *b = NULL; const struct slay *s = NULL; my_strcpy(verb, "hit", sizeof(verb)); /* Get the best attack from all slays or * brands on all non-launcher equipment */ for (j = 2; j < player->body.count; j++) { struct object *obj = slot_object(player, j); if (obj) improve_attack_modifier(obj, mon, &b, &s, verb, FALSE, TRUE, FALSE); } improve_attack_modifier(obj, mon, &b, &s, verb, FALSE, TRUE, FALSE); dmg = melee_damage(obj, b, s); dmg = critical_norm(obj->weight, obj->to_h, dmg, &msg_type); /* Learn by use for the weapon */ object_notice_attack_plusses(obj); if (player_of_has(player, OF_IMPACT) && dmg > 50) { do_quake = TRUE; equip_notice_flag(player, OF_IMPACT); } } /* Learn by use for other equipped items */ equip_notice_on_attack(player); /* Apply the player damage bonuses */ dmg += player_damage_bonus(&player->state); /* No negative damage; change verb if no damage done */ if (dmg <= 0) { dmg = 0; msg_type = MSG_MISS; my_strcpy(verb, "fail to harm", sizeof(verb)); } for (i = 0; i < N_ELEMENTS(melee_hit_types); i++) { const char *dmg_text = ""; if (msg_type != melee_hit_types[i].msg) continue; if (OPT(show_damage)) dmg_text = format(" (%d)", dmg); if (melee_hit_types[i].text) msgt(msg_type, "You %s %s%s. %s", verb, m_name, dmg_text, melee_hit_types[i].text); else msgt(msg_type, "You %s %s%s.", verb, m_name, dmg_text); } /* Pre-damage side effects */ blow_side_effects(player, mon); /* Damage, check for fear and death */ stop = mon_take_hit(mon, dmg, fear, NULL); if (stop) (*fear) = FALSE; /* Post-damage effects */ if (blow_after_effects(y, x, do_quake)) stop = TRUE; return stop; }
/** * This is a helper function used by do_cmd_throw and do_cmd_fire. * * It abstracts out the projectile path, display code, identify and clean up * logic, while using the 'attack' parameter to do work particular to each * kind of attack. */ static void ranged_helper(struct object *obj, int dir, int range, int shots, ranged_attack attack) { int i, j; char o_name[80]; int path_n; struct loc path_g[256]; /* Start at the player */ int x = player->px; int y = player->py; /* Predict the "target" location */ int ty = y + 99 * ddy[dir]; int tx = x + 99 * ddx[dir]; bool hit_target = FALSE; bool none_left = FALSE; struct object *missile; /* Check for target validity */ if ((dir == 5) && target_okay()) { int taim; target_get(&tx, &ty); taim = distance(y, x, ty, tx); if (taim > range) { char msg[80]; strnfmt(msg, sizeof(msg), "Target out of range by %d squares. Fire anyway? ", taim - range); if (!get_check(msg)) return; } } /* Sound */ sound(MSG_SHOOT); /* Describe the object */ object_desc(o_name, sizeof(o_name), obj, ODESC_FULL | ODESC_SINGULAR); /* Actually "fire" the object -- Take a partial turn */ player->upkeep->energy_use = (z_info->move_energy / shots); /* Calculate the path */ path_n = project_path(path_g, range, y, x, ty, tx, 0); /* Hack -- Handle stuff */ handle_stuff(player); /* Start at the player */ x = player->px; y = player->py; /* Project along the path */ for (i = 0; i < path_n; ++i) { struct monster *mon = NULL; int ny = path_g[i].y; int nx = path_g[i].x; bool see = square_isseen(cave, ny, nx); /* Stop before hitting walls */ if (!(square_ispassable(cave, ny, nx)) && !(square_isprojectable(cave, ny, nx))) break; /* Advance */ x = nx; y = ny; /* Tell the UI to display the missile */ event_signal_missile(EVENT_MISSILE, obj, see, y, x); /* Try the attack on the monster at (x, y) if any */ mon = square_monster(cave, y, x); if (mon) { int visible = mflag_has(mon->mflag, MFLAG_VISIBLE); bool fear = FALSE; const char *note_dies = monster_is_unusual(mon->race) ? " is destroyed." : " dies."; struct attack_result result = attack(obj, y, x); int dmg = result.dmg; u32b msg_type = result.msg_type; char hit_verb[20]; my_strcpy(hit_verb, result.hit_verb, sizeof(hit_verb)); mem_free(result.hit_verb); if (result.success) { hit_target = TRUE; object_notice_attack_plusses(obj); /* Learn by use for other equipped items */ equip_notice_to_hit_on_attack(player); /* No negative damage; change verb if no damage done */ if (dmg <= 0) { dmg = 0; msg_type = MSG_MISS; my_strcpy(hit_verb, "fails to harm", sizeof(hit_verb)); } if (!visible) { /* Invisible monster */ msgt(MSG_SHOOT_HIT, "The %s finds a mark.", o_name); } else { for (j = 0; j < (int)N_ELEMENTS(ranged_hit_types); j++) { char m_name[80]; const char *dmg_text = ""; if (msg_type != ranged_hit_types[j].msg) continue; if (OPT(show_damage)) dmg_text = format(" (%d)", dmg); monster_desc(m_name, sizeof(m_name), mon, MDESC_OBJE); if (ranged_hit_types[j].text) msgt(msg_type, "Your %s %s %s%s. %s", o_name, hit_verb, m_name, dmg_text, ranged_hit_types[j].text); else msgt(msg_type, "Your %s %s %s%s.", o_name, hit_verb, m_name, dmg_text); } /* Track this monster */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) { monster_race_track(player->upkeep, mon->race); health_track(player->upkeep, mon); } } /* Hit the monster, check for death */ if (!mon_take_hit(mon, dmg, &fear, note_dies)) { message_pain(mon, dmg); if (fear && mflag_has(mon->mflag, MFLAG_VISIBLE)) { char m_name[80]; monster_desc(m_name, sizeof(m_name), mon, MDESC_DEFAULT); add_monster_message(m_name, mon, MON_MSG_FLEE_IN_TERROR, TRUE); } } } /* Stop the missile */ break; } /* Stop if non-projectable but passable */ if (!(square_isprojectable(cave, ny, nx))) break; } /* Get the missile */ if (object_is_carried(player, obj)) missile = gear_object_for_use(obj, 1, TRUE, &none_left); else missile = floor_object_for_use(obj, 1, TRUE, &none_left); /* Drop (or break) near that location */ drop_near(cave, missile, breakage_chance(missile, hit_target), y, x, TRUE); }
bool findpath(int y, int x) { int i, j, k; int dir = 10; bool try_again; int cur_distance; fill_terrain_info(); terrain[player->py - oy][player->px - ox] = 1; if ((x >= ox) && (x < ex) && (y >= oy) && (y < ey)) { if ((cave->squares[y][x].mon > 0) && mflag_has(square_monster(cave, y, x)->mflag, MFLAG_VISIBLE)) terrain[y - oy][x - ox] = MAX_PF_LENGTH; terrain[y - oy][x - ox] = MAX_PF_LENGTH; } else { bell("Target out of range."); return (FALSE); } /* * And now starts the very naive and very * inefficient pathfinding algorithm */ do { try_again = FALSE; for (j = oy + 1; j < ey - 1; j++) { for (i = ox + 1; i < ex - 1; i++) { cur_distance = terrain[j - oy][i - ox] + 1; if ((cur_distance > 0) && (cur_distance < MAX_PF_LENGTH)) { for (dir = 1; dir < 10; dir++) { int next_y, next_x; if (dir == 5) dir++; next_y = j - oy + ddy[dir]; next_x = i - ox + ddx[dir]; MARK_DISTANCE(terrain[next_y][next_x], cur_distance); } } } } if (terrain[y - oy][x - ox] < MAX_PF_LENGTH) try_again = FALSE; } while (try_again) ; /* Failure */ if (terrain[y - oy][x - ox] == MAX_PF_LENGTH) { bell("Target space unreachable."); return (FALSE); } /* Success */ i = x; j = y; pf_result_index = 0; while ((i != player->px) || (j != player->py)) { cur_distance = terrain[j - oy][i - ox] - 1; for (k = 0; k < 8; k++) { dir = dir_search[k]; if (terrain[j - oy + ddy[dir]][i - ox + ddx[dir]] == cur_distance) break; } /* Should never happen */ assert ((dir != 10) && (dir != 5)); pf_result[pf_result_index++] = '0' + (char)(10 - dir); i += ddx[dir]; j += ddy[dir]; } pf_result_index--; return (TRUE); }
/** * Handle "target" and "look". * * Note that this code can be called from "get_aim_dir()". * * Currently, when "flag" is true, that is, when * "interesting" grids are being used, and a directional key is used, we * only scroll by a single panel, in the direction requested, and check * for any interesting grids on that panel. The "correct" solution would * actually involve scanning a larger set of grids, including ones in * panels which are adjacent to the one currently scanned, but this is * overkill for this function. XXX XXX * * Hack -- targetting/observing an "outer border grid" may induce * problems, so this is not currently allowed. * * The player can use the direction keys to move among "interesting" * grids in a heuristic manner, or the "space", "+", and "-" keys to * move through the "interesting" grids in a sequential manner, or * can enter "location" mode, and use the direction keys to move one * grid at a time in any direction. The "t" (set target) command will * only target a monster (as opposed to a location) if the monster is * target_able and the "interesting" mode is being used. * * The current grid is described using the "look" method above, and * a new command may be entered at any time, but note that if the * "TARGET_LOOK" bit flag is set (or if we are in "location" mode, * where "space" has no obvious meaning) then "space" will scan * through the description of the current grid until done, instead * of immediately jumping to the next "interesting" grid. This * allows the "target" command to retain its old semantics. * * The "*", "+", and "-" keys may always be used to jump immediately * to the next (or previous) interesting grid, in the proper mode. * * The "return" key may always be used to scan through a complete * grid description (forever). * * This command will cancel any old target, even if used from * inside the "look" command. * * * 'mode' is one of TARGET_LOOK or TARGET_KILL. * 'x' and 'y' are the initial position of the target to be highlighted, * or -1 if no location is specified. * Returns TRUE if a target has been successfully set, FALSE otherwise. */ bool target_set_interactive(int mode, int x, int y) { int py = player->py; int px = player->px; int path_n; struct loc path_g[256]; int i, d, m, t, bd; int wid, hgt, help_prompt_loc; bool done = FALSE; bool flag = TRUE; bool help = FALSE; ui_event press; /* These are used for displaying the path to the target */ wchar_t *path_char = mem_zalloc(z_info->max_range * sizeof(wchar_t)); int *path_attr = mem_zalloc(z_info->max_range * sizeof(int)); struct point_set *targets; /* If we haven't been given an initial location, start on the player, otherwise honour it by going into "free targetting" mode. */ if (x == -1 || y == -1) { x = player->px; y = player->py; } else { flag = FALSE; } /* Cancel target */ target_set_monster(0); /* Calculate the window location for the help prompt */ Term_get_size(&wid, &hgt); help_prompt_loc = hgt - 1; /* Display the help prompt */ prt("Press '?' for help.", help_prompt_loc, 0); /* Prepare the target set */ targets = target_get_monsters(mode); /* Start near the player */ m = 0; /* Interact */ while (!done) { bool path_drawn = FALSE; /* Interesting grids if chosen and there are any, otherwise arbitrary */ if (flag && point_set_size(targets)) { y = targets->pts[m].y; x = targets->pts[m].x; /* Adjust panel if needed */ if (adjust_panel_help(y, x, help)) handle_stuff(player); /* Update help */ if (help) { bool good_target = target_able(square_monster(cave, y, x)); target_display_help(good_target, !(flag && point_set_size(targets))); } /* Find the path. */ path_n = project_path(path_g, z_info->max_range, py, px, y, x, PROJECT_THRU); /* Draw the path in "target" mode. If there is one */ if (mode & (TARGET_KILL)) path_drawn = draw_path(path_n, path_g, path_char, path_attr, py, px); /* Describe and Prompt */ press = target_set_interactive_aux(y, x, mode); /* Remove the path */ if (path_drawn) load_path(path_n, path_g, path_char, path_attr); /* Assume no "direction" */ d = 0; /* Analyze */ if (press.type == EVT_MOUSE) { if (press.mouse.button == 3) { /* give the target selection command */ press.mouse.button = 2; press.mouse.mods = KC_MOD_CONTROL; } if (press.mouse.button == 2) { y = KEY_GRID_Y(press); x = KEY_GRID_X(press); if (press.mouse.mods & KC_MOD_CONTROL) { /* same as keyboard target selection command below */ struct monster *m = square_monster(cave, y, x); if (target_able(m)) { /* Set up target information */ monster_race_track(player->upkeep, m->race); health_track(player->upkeep, m); target_set_monster(m); done = TRUE; } else { bell("Illegal target!"); } } else if (press.mouse.mods & KC_MOD_ALT) { /* go to spot - same as 'g' command below */ cmdq_push(CMD_PATHFIND); cmd_set_arg_point(cmdq_peek(), "point", y, x); done = TRUE; } else { /* cancel look mode */ done = TRUE; } } else { y = KEY_GRID_Y(press); x = KEY_GRID_X(press); if (square_monster(cave, y, x) || square_object(cave, y, x)) { /* reset the flag, to make sure we stay in this * mode if something is actually there */ flag = FALSE; /* scan the interesting list and see if there is * anything here */ for (i = 0; i < point_set_size(targets); i++) { if ((y == targets->pts[i].y) && (x == targets->pts[i].x)) { m = i; flag = TRUE; break; } } } else { flag = FALSE; } } } else switch (press.key.code) { case ESCAPE: case 'q': { done = TRUE; break; } case ' ': case '*': case '+': { if (++m == point_set_size(targets)) m = 0; break; } case '-': { if (m-- == 0) m = point_set_size(targets) - 1; break; } case 'p': { /* Recenter around player */ verify_panel(); /* Handle stuff */ handle_stuff(player); y = player->py; x = player->px; } case 'o': { flag = FALSE; break; } case 'm': { break; } case 't': case '5': case '0': case '.': { struct monster *m = square_monster(cave, y, x); if (target_able(m)) { health_track(player->upkeep, m); target_set_monster(m); done = TRUE; } else { bell("Illegal target!"); } break; } case 'g': { cmdq_push(CMD_PATHFIND); cmd_set_arg_point(cmdq_peek(), "point", y, x); done = TRUE; break; } case '?': { help = !help; /* Redraw main window */ player->upkeep->redraw |= (PR_BASIC | PR_EXTRA | PR_MAP | PR_EQUIP); Term_clear(); handle_stuff(player); if (!help) prt("Press '?' for help.", help_prompt_loc, 0); break; } default: { /* Extract direction */ d = target_dir(press.key); /* Oops */ if (!d) bell("Illegal command for target mode!"); break; } } /* Hack -- move around */ if (d) { int old_y = targets->pts[m].y; int old_x = targets->pts[m].x; /* Find a new monster */ i = target_pick(old_y, old_x, ddy[d], ddx[d], targets); /* Scroll to find interesting grid */ if (i < 0) { int old_wy = Term->offset_y; int old_wx = Term->offset_x; /* Change if legal */ if (change_panel(d)) { /* Recalculate interesting grids */ point_set_dispose(targets); targets = target_get_monsters(mode); /* Find a new monster */ i = target_pick(old_y, old_x, ddy[d], ddx[d], targets); /* Restore panel if needed */ if ((i < 0) && modify_panel(Term, old_wy, old_wx)) { /* Recalculate interesting grids */ point_set_dispose(targets); targets = target_get_monsters(mode); } /* Handle stuff */ handle_stuff(player); } } /* Use interesting grid if found */ if (i >= 0) m = i; } } else { /* Update help */ if (help) { bool good_target = target_able(square_monster(cave, y, x)); target_display_help(good_target, !(flag && point_set_size(targets))); } /* Find the path. */ path_n = project_path(path_g, z_info->max_range, py, px, y, x, PROJECT_THRU); /* Draw the path in "target" mode. If there is one */ if (mode & (TARGET_KILL)) path_drawn = draw_path (path_n, path_g, path_char, path_attr, py, px); /* Describe and Prompt (enable "TARGET_LOOK") */ press = target_set_interactive_aux(y, x, mode | TARGET_LOOK); /* Remove the path */ if (path_drawn) load_path(path_n, path_g, path_char, path_attr); /* Assume no direction */ d = 0; /* Analyze the keypress */ if (press.type == EVT_MOUSE) { if (press.mouse.button == 3) { /* give the target selection command */ press.mouse.button = 2; press.mouse.mods = KC_MOD_CONTROL; } if (press.mouse.button == 2) { if (mode & (TARGET_KILL)) { if ((y == KEY_GRID_Y(press)) && (x == KEY_GRID_X(press))) { d = -1; } } y = KEY_GRID_Y(press); x = KEY_GRID_X(press); if (press.mouse.mods & KC_MOD_CONTROL) { /* same as keyboard target selection command below */ target_set_location(y, x); done = TRUE; } else if (press.mouse.mods & KC_MOD_ALT) { /* go to spot - same as 'g' command below */ cmdq_push(CMD_PATHFIND); cmd_set_arg_point(cmdq_peek(), "point", y, x); done = TRUE; } else { /* cancel look mode */ done = TRUE; if (d == -1) { target_set_location(y, x); d = 0; } } } else { int dungeon_hgt = cave->height; int dungeon_wid = cave->width; y = KEY_GRID_Y(press); x = KEY_GRID_X(press); if (press.mouse.y <= 1) { /* move the screen north */ y--; } else if (press.mouse.y >= (Term->hgt - 2)) { /* move the screen south */ y++; } else if (press.mouse.x <= COL_MAP) { /* move the screen in west */ x--; } else if (press.mouse.x >= (Term->wid - 2)) { /* move the screen east */ x++; } if (y < 0) y = 0; if (x < 0) x = 0; if (y >= dungeon_hgt-1) y = dungeon_hgt-1; if (x >= dungeon_wid-1) x = dungeon_wid-1; /* Adjust panel if needed */ if (adjust_panel_help(y, x, help)) { /* Handle stuff */ handle_stuff(player); /* Recalculate interesting grids */ point_set_dispose(targets); targets = target_get_monsters(mode); } if (square_monster(cave, y, x) || square_object(cave, y, x)) { /* scan the interesting list and see if there in * anything here */ for (i = 0; i < point_set_size(targets); i++) { if ((y == targets->pts[i].y) && (x == targets->pts[i].x)) { m = i; flag = TRUE; break; } } } else { flag = FALSE; } } } else switch (press.key.code) { case ESCAPE: case 'q': { done = TRUE; break; } case ' ': case '*': case '+': case '-': { break; } case 'p': { /* Recenter around player */ verify_panel(); /* Handle stuff */ handle_stuff(player); y = player->py; x = player->px; } case 'o': { break; } case 'm': { flag = TRUE; m = 0; bd = 999; /* Pick a nearby monster */ for (i = 0; i < point_set_size(targets); i++) { t = distance(y, x, targets->pts[i].y, targets->pts[i].x); /* Pick closest */ if (t < bd) { m = i; bd = t; } } /* Nothing interesting */ if (bd == 999) flag = FALSE; break; } case 't': case '5': case '0': case '.': { target_set_location(y, x); done = TRUE; break; } case 'g': { cmdq_push(CMD_PATHFIND); cmd_set_arg_point(cmdq_peek(), "point", y, x); done = TRUE; break; } case '?': { help = !help; /* Redraw main window */ player->upkeep->redraw |= (PR_BASIC | PR_EXTRA | PR_MAP | PR_EQUIP); Term_clear(); handle_stuff(player); if (!help) prt("Press '?' for help.", help_prompt_loc, 0); break; } default: { /* Extract a direction */ d = target_dir(press.key); /* Oops */ if (!d) bell("Illegal command for target mode!"); break; } } /* Handle "direction" */ if (d) { int dungeon_hgt = cave->height; int dungeon_wid = cave->width; /* Move */ x += ddx[d]; y += ddy[d]; /* Slide into legality */ if (x >= dungeon_wid - 1) x--; else if (x <= 0) x++; /* Slide into legality */ if (y >= dungeon_hgt - 1) y--; else if (y <= 0) y++; /* Adjust panel if needed */ if (adjust_panel_help(y, x, help)) { /* Handle stuff */ handle_stuff(player); /* Recalculate interesting grids */ point_set_dispose(targets); targets = target_get_monsters(mode); } } } } /* Forget */ point_set_dispose(targets); /* Redraw as necessary */ if (help) { player->upkeep->redraw |= (PR_BASIC | PR_EXTRA | PR_MAP | PR_EQUIP); Term_clear(); } else { prt("", 0, 0); prt("", help_prompt_loc, 0); player->upkeep->redraw |= (PR_DEPTH | PR_STATUS); } /* Recenter around player */ verify_panel(); /* Handle stuff */ handle_stuff(player); mem_free(path_attr); mem_free(path_char); /* Failure to set target */ if (!target_is_set()) return (FALSE); /* Success */ return (TRUE); }
/** * 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; }
/** * 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); }
/** * Attempts to place a monster of the given race at the given location. * * If `sleep` is true, the monster is placed with its default sleep value, * which is given in monster.txt. * * `origin` is the item origin to use for any monster drops (e.g. ORIGIN_DROP, * ORIGIN_DROP_PIT, etc.) * * To give the player a sporting chance, some especially dangerous * monsters are marked as "FORCE_SLEEP" in monster.txt, which will * cause them to be placed with low energy. This helps ensure that * if such a monster suddenly appears in line-of-sight (due to a * summon, for instance), the player gets a chance to move before * they do. * * This routine refuses to place out-of-depth "FORCE_DEPTH" monsters. * * This is the only function which may place a monster in the dungeon, * except for the savefile loading code, which calls place_monster() * directly. */ static bool place_new_monster_one(struct chunk *c, int y, int x, struct monster_race *race, bool sleep, byte origin) { int i; struct monster *mon; struct monster monster_body; assert(square_in_bounds(c, y, x)); assert(race && race->name); /* Not where monsters already are */ if (square_monster(c, y, x)) return false; /* Not where the player already is */ if ((player->py == y) && (player->px == x)) return false; /* Prevent monsters from being placed where they cannot walk, but allow other feature types */ if (!square_is_monster_walkable(c, y, x)) return false; /* No creation on glyph of warding */ if (square_iswarded(c, y, x)) return false; /* "unique" monsters must be "unique" */ if (rf_has(race->flags, RF_UNIQUE) && race->cur_num >= race->max_num) return (false); /* Depth monsters may NOT be created out of depth */ if (rf_has(race->flags, RF_FORCE_DEPTH) && player->depth < race->level) return (false); /* Add to level feeling, note uniques for cheaters */ c->mon_rating += race->level * race->level; /* Check out-of-depth-ness */ if (race->level > c->depth) { if (rf_has(race->flags, RF_UNIQUE)) { /* OOD unique */ if (OPT(player, cheat_hear)) msg("Deep unique (%s).", race->name); } else { /* Normal monsters but OOD */ if (OPT(player, cheat_hear)) msg("Deep monster (%s).", race->name); } /* Boost rating by power per 10 levels OOD */ c->mon_rating += (race->level - c->depth) * race->level * race->level; } else if (rf_has(race->flags, RF_UNIQUE) && OPT(player, cheat_hear)) msg("Unique (%s).", race->name); /* Get local monster */ mon = &monster_body; /* Clean out the monster */ memset(mon, 0, sizeof(struct monster)); /* Save the race */ mon->race = race; /* Enforce sleeping if needed */ if (sleep && race->sleep) { int val = race->sleep; mon->m_timed[MON_TMD_SLEEP] = ((val * 2) + randint1(val * 10)); } /* Uniques get a fixed amount of HP */ if (rf_has(race->flags, RF_UNIQUE)) mon->maxhp = race->avg_hp; else { mon->maxhp = mon_hp(race, RANDOMISE); mon->maxhp = MAX(mon->maxhp, 1); } /* And start out fully healthy */ mon->hp = mon->maxhp; /* Extract the monster base speed */ mon->mspeed = race->speed; /* Hack -- small racial variety */ if (!rf_has(race->flags, RF_UNIQUE)) { /* Allow some small variation per monster */ i = turn_energy(race->speed) / 10; if (i) mon->mspeed += rand_spread(0, i); } /* Give a random starting energy */ mon->energy = (byte)randint0(50); /* Force monster to wait for player */ if (rf_has(race->flags, RF_FORCE_SLEEP)) mflag_on(mon->mflag, MFLAG_NICE); /* Radiate light? */ if (rf_has(race->flags, RF_HAS_LIGHT)) player->upkeep->update |= PU_UPDATE_VIEW; /* Is this obviously a monster? (Mimics etc. aren't) */ if (rf_has(race->flags, RF_UNAWARE)) mflag_on(mon->mflag, MFLAG_CAMOUFLAGE); else mflag_off(mon->mflag, MFLAG_CAMOUFLAGE); /* Set the color if necessary */ if (rf_has(race->flags, RF_ATTR_RAND)) mon->attr = randint1(BASIC_COLORS - 1); /* Place the monster in the dungeon */ if (!place_monster(c, y, x, mon, origin)) return (false); /* Success */ return (true); }
/** * Update the current "run" path * * Return TRUE if the running should be stopped */ static bool run_test(void) { int py = player->py; int px = player->px; int prev_dir; int new_dir; int row, col; int i, max, inv; int option, option2; /* No options yet */ option = 0; option2 = 0; /* Where we came from */ prev_dir = run_old_dir; /* Range of newly adjacent grids */ max = (prev_dir & 0x01) + 1; /* Look at every newly adjacent square. */ for (i = -max; i <= max; i++) { struct object *obj; /* New direction */ new_dir = cycle[chome[prev_dir] + i]; /* New location */ row = py + ddy[new_dir]; col = px + ddx[new_dir]; /* Visible monsters abort running */ if (cave->squares[row][col].mon > 0) { monster_type *m_ptr = square_monster(cave, row, col); /* Visible monster */ if (mflag_has(m_ptr->mflag, MFLAG_VISIBLE)) return (TRUE); } /* Visible objects abort running */ for (obj = square_object(cave, row, col); obj; obj = obj->next) /* Visible object */ if (obj->marked && !ignore_item_ok(obj)) return (TRUE); /* Assume unknown */ inv = TRUE; /* Check memorized grids */ if (square_ismark(cave, row, col)) { bool notice = square_noticeable(cave, row, col); /* Interesting feature */ if (notice) return (TRUE); /* The grid is "visible" */ inv = FALSE; } /* Analyze unknown grids and floors */ if (inv || square_ispassable(cave, row, col)) { /* Looking for open area */ if (run_open_area) { /* Nothing */ } else if (!option) { /* The first new direction. */ option = new_dir; } else if (option2) { /* Three new directions. Stop running. */ return (TRUE); } else if (option != cycle[chome[prev_dir] + i - 1]) { /* Two non-adjacent new directions. Stop running. */ return (TRUE); } else if (new_dir & 0x01) { /* Two new (adjacent) directions (case 1) */ option2 = new_dir; } else { /* Two new (adjacent) directions (case 2) */ option2 = option; option = new_dir; } } else { /* Obstacle, while looking for open area */ if (run_open_area) { if (i < 0) { /* Break to the right */ run_break_right = TRUE; } else if (i > 0) { /* Break to the left */ run_break_left = TRUE; } } } } /* Look at every soon to be newly adjacent square. */ for (i = -max; i <= max; i++) { /* New direction */ new_dir = cycle[chome[prev_dir] + i]; /* New location */ row = py + ddy[prev_dir] + ddy[new_dir]; col = px + ddx[prev_dir] + ddx[new_dir]; /* HACK: Ugh. Sometimes we come up with illegal bounds. This will * treat the symptom but not the disease. */ if (row >= cave->height || col >= cave->width) continue; if (row < 0 || col < 0) continue; /* Visible monsters abort running */ if (cave->squares[row][col].mon > 0) { monster_type *m_ptr = square_monster(cave, row, col); /* Visible monster */ if (mflag_has(m_ptr->mflag, MFLAG_VISIBLE) && !is_mimicking(m_ptr)) return (TRUE); } } /* Looking for open area */ if (run_open_area) { /* Hack -- look again */ for (i = -max; i < 0; i++) { new_dir = cycle[chome[prev_dir] + i]; row = py + ddy[new_dir]; col = px + ddx[new_dir]; /* Unknown grid or non-wall */ /* Was: square_ispassable(cave, row, col) */ if (!square_ismark(cave, row, col) || (square_ispassable(cave, row, col))) { /* Looking to break right */ if (run_break_right) { return (TRUE); } } else { /* Obstacle */ /* Looking to break left */ if (run_break_left) { return (TRUE); } } } /* Hack -- look again */ for (i = max; i > 0; i--) { new_dir = cycle[chome[prev_dir] + i]; row = py + ddy[new_dir]; col = px + ddx[new_dir]; /* Unknown grid or non-wall */ /* Was: square_ispassable(cave, row, col) */ if (!square_ismark(cave, row, col) || (square_ispassable(cave, row, col))) { /* Looking to break left */ if (run_break_left) { return (TRUE); } } else { /* Obstacle */ /* Looking to break right */ if (run_break_right) { return (TRUE); } } } } else { /* Not looking for open area */ /* No options */ if (!option) { return (TRUE); } else if (!option2) { /* One option */ /* Primary option */ run_cur_dir = option; /* No other options */ run_old_dir = option; } else { /* Two options, examining corners */ /* Primary option */ run_cur_dir = option; /* Hack -- allow curving */ run_old_dir = option2; } } /* About to hit a known wall, stop */ if (see_wall(run_cur_dir, py, px)) return (TRUE); /* Failure */ return (FALSE); }
/** * Open a closed/locked/jammed door or a closed/locked chest. * * Unlocking a locked chest is worth one experience point; since doors are * player lockable, there is no experience for unlocking doors. */ void do_cmd_open(struct command *cmd) { int y, x, dir; struct object *obj; bool more = false; int err; struct monster *m; /* Get arguments */ err = cmd_get_arg_direction(cmd, "direction", &dir); if (err || dir == DIR_UNKNOWN) { int y2, x2; int n_closed_doors, n_locked_chests; n_closed_doors = count_feats(&y2, &x2, square_iscloseddoor, false); n_locked_chests = count_chests(&y2, &x2, CHEST_OPENABLE); if (n_closed_doors + n_locked_chests == 1) { dir = coords_to_dir(y2, x2); cmd_set_arg_direction(cmd, "direction", dir); } else if (cmd_get_direction(cmd, "direction", &dir, false)) { return; } } /* Get location */ y = player->py + ddy[dir]; x = player->px + ddx[dir]; /* Check for chest */ obj = chest_check(y, x, CHEST_OPENABLE); /* Check for door */ if (!obj && !do_cmd_open_test(y, x)) { /* Cancel repeat */ disturb(player, 0); return; } /* Take a turn */ player->upkeep->energy_use = z_info->move_energy; /* Apply confusion */ if (player_confuse_dir(player, &dir, false)) { /* Get location */ y = player->py + ddy[dir]; x = player->px + ddx[dir]; /* Check for chest */ obj = chest_check(y, x, CHEST_OPENABLE); } /* Monster */ m = square_monster(cave, y, x); if (m) { /* Mimics surprise the player */ if (is_mimicking(m)) { become_aware(m); /* Mimic wakes up */ mon_clear_timed(m, MON_TMD_SLEEP, MON_TMD_FLG_NOMESSAGE, false); } else { /* Message */ msg("There is a monster in the way!"); /* Attack */ py_attack(y, x); } } else if (obj) { /* Chest */ more = do_cmd_open_chest(y, x, obj); } else { /* Door */ more = do_cmd_open_aux(y, x); } /* Cancel repeat unless we may continue */ if (!more) disturb(player, 0); }
/** * Attempts to place a copy of the given monster at the given position in * the dungeon. * * All of the monster placement routines eventually call this function. This * is what actually puts the monster in the dungeon (i.e., it notifies the cave * and sets the monsters position). The dungeon loading code also calls this * function directly. * * `origin` is the item origin to use for any monster drops (e.g. ORIGIN_DROP, * ORIGIN_DROP_PIT, etc.) The dungeon loading code calls this with origin = 0, * which prevents the monster's drops from being generated again. * * Returns the m_idx of the newly copied monster, or 0 if the placement fails. */ s16b place_monster(struct chunk *c, int y, int x, struct monster *mon, byte origin) { s16b m_idx; struct monster *new_mon; assert(square_in_bounds(c, y, x)); assert(!square_monster(c, y, x)); /* Get a new record */ m_idx = mon_pop(c); if (!m_idx) return 0; /* Copy the monster */ new_mon = cave_monster(c, m_idx); memcpy(new_mon, mon, sizeof(struct monster)); /* Set the ID */ new_mon->midx = m_idx; /* Set the location */ c->squares[y][x].mon = new_mon->midx; new_mon->fy = y; new_mon->fx = x; assert(square_monster(c, y, x) == new_mon); update_mon(new_mon, c, true); /* Hack -- Count the number of "reproducers" */ if (rf_has(new_mon->race->flags, RF_MULTIPLY)) num_repro++; /* Count racial occurrences */ new_mon->race->cur_num++; /* Create the monster's drop, if any */ if (origin) (void)mon_create_drop(c, new_mon, origin); /* Make mimics start mimicking */ if (origin && new_mon->race->mimic_kinds) { struct object *obj; struct object_kind *kind = new_mon->race->mimic_kinds->kind; struct monster_mimic *mimic_kind; int i = 1; /* Pick a random object kind to mimic */ for (mimic_kind = new_mon->race->mimic_kinds; mimic_kind; mimic_kind = mimic_kind->next, i++) { if (one_in_(i)) kind = mimic_kind->kind; } if (tval_is_money_k(kind)) { obj = make_gold(player->depth, kind->name); } else { obj = object_new(); object_prep(obj, kind, new_mon->race->level, RANDOMISE); apply_magic(obj, new_mon->race->level, true, false, false, false); obj->number = 1; obj->origin = ORIGIN_DROP_MIMIC; obj->origin_depth = player->depth; } obj->mimicking_m_idx = m_idx; new_mon->mimicked_obj = obj; /* Put the object on the floor if it goes, otherwise no mimicry */ if (floor_carry(c, y, x, obj, false)) { list_object(c, obj); } else { /* Clear the mimicry */ obj->mimicking_m_idx = 0; new_mon->mimicked_obj = NULL; /* Give the object to the monster if appropriate */ if (rf_has(new_mon->race->flags, RF_MIMIC_INV)) { monster_carry(c, new_mon, obj); } else { /* Otherwise delete the mimicked object */ object_delete(&obj); } } } /* Result */ return m_idx; }