void wr_monsters(void) { int i, j; if (p_ptr->is_dead) return; /* Total monsters */ wr_u16b(cave_monster_max(cave)); /* Dump the monsters */ for (i = 1; i < cave_monster_max(cave); i++) { byte unaware = 0; const monster_type *m_ptr = cave_monster(cave, i); wr_s16b(m_ptr->r_idx); wr_byte(m_ptr->fy); wr_byte(m_ptr->fx); wr_s16b(m_ptr->hp); wr_s16b(m_ptr->maxhp); wr_byte(m_ptr->mspeed); wr_byte(m_ptr->energy); wr_byte(MON_TMD_MAX); for (j = 0; j < MON_TMD_MAX; j++) wr_s16b(m_ptr->m_timed[j]); if (m_ptr->unaware) unaware |= 0x01; wr_byte(unaware); wr_byte(0); } }
/** * Calls a monster from the level and moves it to the desired spot */ int call_monster(int y, int x) { int i, mon_count, choice; int oy, ox; int *mon_indices; struct monster *mon; mon_count = 0; for (i = 1; i < cave_monster_max(cave); i++) { mon = cave_monster(cave, i); /* Figure out how many good monsters there are */ if (can_call_monster(y, x, mon)) mon_count++; } /* There were no good monsters on the level */ if (mon_count == 0) return (0); /* Make the array */ mon_indices = mem_zalloc(mon_count * sizeof(int)); /* Reset mon_count */ mon_count = 0; /* Now go through a second time and store the indices */ for (i = 1; i < cave_monster_max(cave); i++) { mon = cave_monster(cave, i); /* Save the values of the good monster */ if (can_call_monster(y, x, mon)){ mon_indices[mon_count] = i; mon_count++; } } /* Pick one */ choice = randint0(mon_count - 1); /* Get the lucky monster */ mon = cave_monster(cave, mon_indices[choice]); mem_free(mon_indices); /* Extract monster location */ oy = mon->fy; ox = mon->fx; /* Swap the moster */ monster_swap(oy, ox, y, x); /* Wake it up */ mon_clear_timed(mon, MON_TMD_SLEEP, MON_TMD_FLG_NOMESSAGE, false); /* Set it's energy to 0 */ mon->energy = 0; return (mon->race->level); }
/** * Housekeeping after the processing of a player command */ static void process_player_cleanup(void) { int i; /* Significant */ if (player->upkeep->energy_use) { /* Use some energy */ player->energy -= player->upkeep->energy_use; /* Increment the total energy counter */ player->total_energy += player->upkeep->energy_use; /* Do nothing else if player has auto-dropped stuff */ if (!player->upkeep->dropping) { /* Hack -- constant hallucination */ if (player->timed[TMD_IMAGE]) player->upkeep->redraw |= (PR_MAP); /* Shimmer multi-hued monsters */ for (i = 1; i < cave_monster_max(cave); i++) { struct monster *mon = cave_monster(cave, i); if (!mon->race) continue; if (!rf_has(mon->race->flags, RF_ATTR_MULTI)) continue; square_light_spot(cave, mon->fy, mon->fx); } /* Clear NICE flag, and show marked monsters */ for (i = 1; i < cave_monster_max(cave); i++) { struct monster *mon = cave_monster(cave, i); mflag_off(mon->mflag, MFLAG_NICE); if (mflag_has(mon->mflag, MFLAG_MARK)) { if (!mflag_has(mon->mflag, MFLAG_SHOW)) { mflag_off(mon->mflag, MFLAG_MARK); update_mon(mon, cave, false); } } } } } /* Clear SHOW flag and player drop status */ for (i = 1; i < cave_monster_max(cave); i++) { struct monster *mon = cave_monster(cave, i); mflag_off(mon->mflag, MFLAG_SHOW); } player->upkeep->dropping = false; /* Hack - update needed first because inventory may have changed */ update_stuff(player); redraw_stuff(player); }
/** * Allow monsters on a frozen persistent level to recover */ void restore_monsters(void) { int i; struct monster *mon; /* Get the number of turns that have passed */ int num_turns = turn - cave->turn; /* Process the monsters (backwards) */ for (i = cave_monster_max(cave) - 1; i >= 1; i--) { int status, status_red; /* Access the monster */ mon = cave_monster(cave, i); /* Regenerate */ regen_monster(mon, num_turns / 100); /* Handle timed effects */ status_red = num_turns * turn_energy(mon->mspeed) / z_info->move_energy; if (status_red > 0) { for (status = 0; status < MON_TMD_MAX; status++) { if (mon->m_timed[status]) { mon_dec_timed(mon, status, status_red, 0, false); } } } } }
/** * Display the monster list statically. This will force the list to be * displayed to the provided dimensions. Contents will be adjusted accordingly. * * In order to support more efficient monster flicker animations, this function * uses a shared list object so that it's not constantly allocating and freeing * the list. * * \param height is the height of the list. * \param width is the width of the list. */ void monster_list_show_subwindow(int height, int width) { textblock *tb; monster_list_t *list; int i; if (height < 1 || width < 1) return; tb = textblock_new(); list = monster_list_shared_instance(); /* Force an update if detected monsters */ for (i = 1; i < cave_monster_max(cave); i++) { if (mflag_has(cave_monster(cave, i)->mflag, MFLAG_MARK)) { list->creation_turn = -1; break; } } monster_list_reset(list); monster_list_collect(list); monster_list_get_glyphs(list); monster_list_sort(list, monster_list_standard_compare); /* Draw the list to exactly fit the subwindow. */ monster_list_format_textblock(list, tb, height, width, NULL, NULL); textui_textblock_place(tb, SCREEN_REGION, NULL); textblock_free(tb); }
/** * Return TRUE if the list needs to be updated. Usually this is each turn or if * the number of cave monsters changes. */ static bool monster_list_needs_update(const monster_list_t *list) { if (list == NULL || list->entries == NULL) return FALSE; return list->creation_turn != turn || (int)list->entries_size < cave_monster_max(cave); }
/** * Returns the index of a "free" monster, or 0 if no slot is available. * * This routine should almost never fail, but it *can* happen. * The calling code must check for and handle a 0 return. */ static s16b mon_pop(void) { int m_idx; /* Normal allocation */ if (cave_monster_max(cave) < z_info->m_max) { /* Get the next hole */ m_idx = cave_monster_max(cave); /* Expand the array */ cave->mon_max++; /* Count monsters */ cave->mon_cnt++; return m_idx; } /* Recycle dead monsters if we've run out of room */ for (m_idx = 1; m_idx < cave_monster_max(cave); m_idx++) { monster_type *m_ptr; /* Get the monster */ m_ptr = cave_monster(cave, m_idx); /* Skip live monsters */ if (m_ptr->r_idx) continue; /* Count monsters */ cave->mon_cnt++; /* Use this monster */ return m_idx; } /* Warn the player if no index is available * (except during dungeon creation) */ if (character_dungeon) msg("Too many monsters!"); /* Try not to crash */ return 0; }
/** * Returns the index of a "free" monster, or 0 if no slot is available. * * This routine should almost never fail, but it *can* happen. * The calling code must check for and handle a 0 return. */ s16b mon_pop(struct chunk *c) { int m_idx; /* Normal allocation */ if (cave_monster_max(c) < z_info->level_monster_max) { /* Get the next hole */ m_idx = cave_monster_max(c); /* Expand the array */ c->mon_max++; /* Count monsters */ c->mon_cnt++; return m_idx; } /* Recycle dead monsters if we've run out of room */ for (m_idx = 1; m_idx < cave_monster_max(c); m_idx++) { struct monster *mon = cave_monster(c, m_idx); /* Skip live monsters */ if (mon->race) continue; /* Count monsters */ c->mon_cnt++; /* Use this monster */ return m_idx; } /* Warn the player if no index is available * (except during dungeon creation) */ if (character_dungeon) msg("Too many monsters!"); /* Try not to crash */ return 0; }
/** * Zero out the contents of a monster list. If needed, this function will * reallocate the entry list if the number of monsters has changed. */ void monster_list_reset(monster_list_t *list) { if (list == NULL || list->entries == NULL) return; if (!monster_list_needs_update(list)) return; if ((int)list->entries_size < cave_monster_max(cave)) { list->entries = mem_realloc(list->entries, sizeof(list->entries[0]) * cave_monster_max(cave)); list->entries_size = cave_monster_max(cave); } memset(list->entries, 0, list->entries_size * sizeof(monster_list_entry_t)); memset(list->total_entries, 0, MONSTER_LIST_SECTION_MAX * sizeof(u16b)); memset(list->total_monsters, 0, MONSTER_LIST_SECTION_MAX * sizeof(u16b)); list->distinct_entries = 0; list->creation_turn = 0; list->sorted = FALSE; }
/** * Clear 'moved' status from all monsters. * * Clear noise if appropriate. */ void reset_monsters(void) { int i; struct monster *mon; /* Process the monsters (backwards) */ for (i = cave_monster_max(cave) - 1; i >= 1; i--) { /* Access the monster */ mon = cave_monster(cave, i); /* Monster is ready to go again */ mflag_off(mon->mflag, MFLAG_HANDLED); } }
/** * This will scan the dungeon for monsters and then kill each * and every last one. */ static void scan_for_monsters(void) { int i; /* Go through the monster list */ for (i = 1; i < cave_monster_max(cave); i++) { struct monster *mon = cave_monster(cave, i); /* Skip dead monsters */ if (!mon->race) continue; stats_monster(mon, i); } }
/** * Clear 'moved' status from all monsters. * * Clear noise if appropriate. */ void reset_monsters(void) { int i; monster_type *m_ptr; /* Process the monsters (backwards) */ for (i = cave_monster_max(cave) - 1; i >= 1; i--) { /* Access the monster */ m_ptr = cave_monster(cave, i); /* Monster is ready to go again */ mflag_off(m_ptr->mflag, MFLAG_HANDLED); } }
/** * Deletes all the monsters when the player leaves the level. * * This is an efficient method of simulating multiple calls to the * "delete_monster()" function, with no visual effects. * * Note that we must delete the objects the monsters are carrying, but we * do nothing with mimicked objects. */ void wipe_mon_list(struct chunk *c, struct player *p) { int m_idx; /* Delete all the monsters */ for (m_idx = cave_monster_max(c) - 1; m_idx >= 1; m_idx--) { struct monster *mon = cave_monster(c, m_idx); struct object *held_obj = mon ? mon->held_obj : NULL; /* Skip dead monsters */ if (!mon->race) continue; /* Delete all the objects */ if (held_obj) { /* Go through all held objects and check for artifacts */ struct object *obj = held_obj; while (obj) { if (obj->artifact && !(obj->known && obj->known->artifact)) obj->artifact->created = false; obj = obj->next; } object_pile_free(held_obj); } /* Reduce the racial counter */ mon->race->cur_num--; /* Monster is gone */ c->squares[mon->fy][mon->fx].mon = 0; /* Wipe the Monster */ memset(mon, 0, sizeof(struct monster)); } /* Reset "cave->mon_max" */ c->mon_max = 1; /* Reset "mon_cnt" */ c->mon_cnt = 0; /* Hack -- reset "reproducer" count */ num_repro = 0; /* Hack -- no more target */ target_set_monster(0); /* Hack -- no more tracking */ health_track(p->upkeep, 0); }
/** * Allocate a new monster list based on the size of the current cave's monster * array. */ monster_list_t *monster_list_new(void) { monster_list_t *list = mem_zalloc(sizeof(monster_list_t)); size_t size = cave_monster_max(cave); if (list == NULL) return NULL; list->entries = mem_zalloc(size * sizeof(monster_list_entry_t)); if (list->entries == NULL) { mem_free(list); return NULL; } list->entries_size = size; return list; }
/* * Hack -- Delete all nearby monsters */ static void do_cmd_wiz_zap(int d) { int i; /* Banish everyone nearby */ for (i = 1; i < cave_monster_max(cave); i++) { monster_type *m_ptr = cave_monster(cave, i); /* Skip dead monsters */ if (!m_ptr->r_idx) continue; /* Skip distant monsters */ if (m_ptr->cdis > d) continue; /* Delete the monster */ delete_monster_idx(i); } /* Update monster list window */ p_ptr->redraw |= PR_MONLIST; }
/** * Deletes all the monsters when the player leaves the level. * * This is an efficient method of simulating multiple calls to the * "delete_monster()" function, with no visual effects. * * Note that we do not delete the objects the monsters are carrying; * that must be taken care of separately via wipe_o_list(). */ void wipe_mon_list(struct cave *c, struct player *p) { int m_idx; /* Delete all the monsters */ for (m_idx = cave_monster_max(cave) - 1; m_idx >= 1; m_idx--) { monster_type *m_ptr = cave_monster(cave, m_idx); monster_race *r_ptr = &r_info[m_ptr->r_idx]; /* Skip dead monsters */ if (!m_ptr->r_idx) continue; /* Hack -- Reduce the racial counter */ r_ptr->cur_num--; /* Monster is gone */ c->m_idx[m_ptr->fy][m_ptr->fx] = 0; /* Wipe the Monster */ (void)WIPE(m_ptr, monster_type); } /* Reset "cave->mon_max" */ cave->mon_max = 1; /* Reset "mon_cnt" */ cave->mon_cnt = 0; /* Hack -- reset "reproducer" count */ num_repro = 0; /* Hack -- no more target */ target_set_monster(0); /* Hack -- no more tracking */ health_track(p, 0); }
/** * Handle things that need updating once every 10 game turns */ void process_world(struct chunk *c) { int i, y, x; /* Compact the monster list if we're approaching the limit */ if (cave_monster_count(cave) + 32 > z_info->level_monster_max) compact_monsters(64); /* Too many holes in the monster list - compress */ if (cave_monster_count(cave) + 32 < cave_monster_max(cave)) compact_monsters(0); /*** Check the Time ***/ /* Play an ambient sound at regular intervals. */ if (!(turn % ((10L * z_info->day_length) / 4))) play_ambient_sound(); /*** Handle stores and sunshine ***/ if (!player->depth) { /* Daybreak/Nighfall in town */ if (!(turn % ((10L * z_info->day_length) / 2))) { bool dawn; /* Check for dawn */ dawn = (!(turn % (10L * z_info->day_length))); /* Day breaks */ if (dawn) msg("The sun has risen."); /* Night falls */ else msg("The sun has fallen."); /* Illuminate */ cave_illuminate(c, dawn); } } else { /* Update the stores once a day (while in the dungeon). The changes are not actually made until return to town, to avoid giving details away in the knowledge menu. */ if (!(turn % (10L * z_info->store_turns))) daycount++; } /* Check for creature generation */ if (one_in_(z_info->alloc_monster_chance)) (void)pick_and_place_distant_monster(cave, player, z_info->max_sight + 5, true, player->depth); /*** Damage over Time ***/ /* Take damage from poison */ if (player->timed[TMD_POISONED]) take_hit(player, 1, "poison"); /* Take damage from cuts */ if (player->timed[TMD_CUT]) { /* Mortal wound or Deep Gash */ if (player->timed[TMD_CUT] > TMD_CUT_SEVERE) i = 3; /* Severe cut */ else if (player->timed[TMD_CUT] > TMD_CUT_NASTY) i = 2; /* Other cuts */ else i = 1; /* Take damage */ take_hit(player, i, "a fatal wound"); } /*** Check the Food, and Regenerate ***/ /* Digest normally */ if (!(turn % 100)) { /* Basic digestion rate based on speed */ i = turn_energy(player->state.speed) * 2; /* Regeneration takes more food */ if (player_of_has(player, OF_REGEN)) i += 30; /* Slow digestion takes less food */ if (player_of_has(player, OF_SLOW_DIGEST)) i /= 5; /* Minimal digestion */ if (i < 1) i = 1; /* Digest some food */ player_set_food(player, player->food - i); } /* Getting Faint */ if (player->food < PY_FOOD_FAINT) { /* Faint occasionally */ if (!player->timed[TMD_PARALYZED] && one_in_(10)) { /* Message */ msg("You faint from the lack of food."); disturb(player, 1); /* Faint (bypass free action) */ (void)player_inc_timed(player, TMD_PARALYZED, 1 + randint0(5), true, false); } } /* Starve to death (slowly) */ if (player->food < PY_FOOD_STARVE) { /* Calculate damage */ i = (PY_FOOD_STARVE - player->food) / 10; /* Take damage */ take_hit(player, i, "starvation"); } /* Regenerate Hit Points if needed */ if (player->chp < player->mhp) player_regen_hp(player); /* Regenerate mana if needed */ if (player->csp < player->msp) player_regen_mana(player); /* Timeout various things */ decrease_timeouts(); /* Process light */ player_update_light(player); /*** Process Inventory ***/ /* Handle experience draining */ if (player_of_has(player, OF_DRAIN_EXP)) { if ((player->exp > 0) && one_in_(10)) { s32b d = damroll(10, 6) + (player->exp / 100) * z_info->life_drain_percent; player_exp_lose(player, d / 10, false); } equip_learn_flag(player, OF_DRAIN_EXP); } /* Recharge activatable objects and rods */ recharge_objects(); /* Notice things after time */ if (!(turn % 100)) equip_learn_after_time(player); /* Decrease trap timeouts */ for (y = 0; y < cave->height; y++) { for (x = 0; x < cave->width; x++) { struct trap *trap = cave->squares[y][x].trap; while (trap) { if (trap->timeout) { trap->timeout--; if (!trap->timeout) square_light_spot(cave, y, x); } trap = trap->next; } } } /*** Involuntary Movement ***/ /* Delayed Word-of-Recall */ if (player->word_recall) { /* Count down towards recall */ player->word_recall--; /* Activate the recall */ if (!player->word_recall) { /* Disturbing! */ disturb(player, 0); /* Determine the level */ if (player->depth) { msgt(MSG_TPLEVEL, "You feel yourself yanked upwards!"); dungeon_change_level(player, 0); } else { msgt(MSG_TPLEVEL, "You feel yourself yanked downwards!"); /* Force descent to a lower level if allowed */ if (OPT(player, birth_force_descend) && player->max_depth < z_info->max_depth - 1 && !is_quest(player->max_depth)) { player->max_depth = dungeon_get_next_level(player->max_depth, 1); } /* New depth - back to max depth or 1, whichever is deeper */ dungeon_change_level(player, player->max_depth < 1 ? 1: player->max_depth); } } } /* Delayed Deep Descent */ if (player->deep_descent) { /* Count down towards recall */ player->deep_descent--; /* Activate the recall */ if (player->deep_descent == 0) { int target_increment; int target_depth = player->max_depth; /* Calculate target depth */ target_increment = (4 / z_info->stair_skip) + 1; target_depth = dungeon_get_next_level(player->max_depth, target_increment); disturb(player, 0); /* Determine the level */ if (target_depth > player->depth) { msgt(MSG_TPLEVEL, "The floor opens beneath you!"); dungeon_change_level(player, target_depth); } else { /* Otherwise do something disastrous */ msgt(MSG_TPLEVEL, "You are thrown back in an explosion!"); effect_simple(EF_DESTRUCTION, "0", 0, 5, 0, NULL); } } } }
/** * Process all the "live" monsters, once per game turn. * * During each game turn, we scan through the list of all the "live" monsters, * (backwards, so we can excise any "freshly dead" monsters), energizing each * monster, and allowing fully energized monsters to move, attack, pass, etc. * * This function and its children are responsible for a considerable fraction * of the processor time in normal situations, greater if the character is * resting. */ void process_monsters(struct chunk *c, int minimum_energy) { int i; int mspeed; /* Only process some things every so often */ bool regen = false; /* Regenerate hitpoints and mana every 100 game turns */ if (turn % 100 == 0) regen = true; /* Process the monsters (backwards) */ for (i = cave_monster_max(c) - 1; i >= 1; i--) { struct monster *mon; bool moving; /* Handle "leaving" */ if (player->is_dead || player->upkeep->generate_level) break; /* Get a 'live' monster */ mon = cave_monster(c, i); if (!mon->race) continue; /* Ignore monsters that have already been handled */ if (mflag_has(mon->mflag, MFLAG_HANDLED)) continue; /* Not enough energy to move yet */ if (mon->energy < minimum_energy) continue; /* Does this monster have enough energy to move? */ moving = mon->energy >= z_info->move_energy ? true : false; /* Prevent reprocessing */ mflag_on(mon->mflag, MFLAG_HANDLED); /* Handle monster regeneration if requested */ if (regen) regen_monster(mon); /* Calculate the net speed */ mspeed = mon->mspeed; if (mon->m_timed[MON_TMD_FAST]) mspeed += 10; if (mon->m_timed[MON_TMD_SLOW]) mspeed -= 10; /* Give this monster some energy */ mon->energy += turn_energy(mspeed); /* End the turn of monsters without enough energy to move */ if (!moving) continue; /* Use up "some" energy */ mon->energy -= z_info->move_energy; /* Mimics lie in wait */ if (is_mimicking(mon)) continue; /* Check if the monster is active */ if (monster_check_active(c, mon)) { /* Process timed effects - skip turn if necessary */ if (process_monster_timed(c, mon)) continue; /* Set this monster to be the current actor */ c->mon_current = i; /* Process the monster */ process_monster(c, mon); /* Monster is no longer current */ c->mon_current = -1; } } /* Update monster visibility after this */ /* XXX This may not be necessary */ player->upkeep->update |= PU_MONSTERS; }
/** * Collect monster information from the current cave's monster list. */ void monster_list_collect(monster_list_t *list) { int i; if (list == NULL || list->entries == NULL) return; /* Use cave_monster_max() here in case the monster list isn't compacted. */ for (i = 1; i < cave_monster_max(cave); i++) { struct monster *mon = cave_monster(cave, i); monster_list_entry_t *entry = NULL; int j, field; bool los = FALSE; /* Only consider visible, known monsters */ if (!mflag_has(mon->mflag, MFLAG_VISIBLE) || mflag_has(mon->mflag, MFLAG_UNAWARE)) continue; /* Find or add a list entry. */ for (j = 0; j < (int)list->entries_size; j++) { if (list->entries[j].race == NULL) { /* We found an empty slot, so add this race here. */ entry = &list->entries[j]; memset(entry, 0, sizeof(monster_list_entry_t)); entry->race = mon->race; break; } else if (list->entries[j].race == mon->race) { /* We found a matching race and we'll use that. */ entry = &list->entries[j]; break; } } if (entry == NULL) continue; /* Always collect the latest monster attribute so that flicker * animation works. If this is 0, it needs to be replaced by * the standard glyph in the UI */ entry->attr = mon->attr; /* Skip the projection and location checks if nothing has changed. */ if (!monster_list_needs_update(list)) continue; /* * Check for LOS * Hack - we should use (mon->mflag & (MFLAG_VIEW)) here, * but this does not catch monsters detected by ESP which are * targetable, so we cheat and use projectable() instead */ los = projectable(cave, player->py, player->px, mon->fy, mon->fx, PROJECT_NONE); field = (los) ? MONSTER_LIST_SECTION_LOS : MONSTER_LIST_SECTION_ESP; entry->count[field]++; if (mon->m_timed[MON_TMD_SLEEP] > 0) entry->asleep[field]++; /* Store the location offset from the player; this is only used for * monster counts of 1 */ entry->dx = mon->fx - player->px; entry->dy = mon->fy - player->py; } /* Skip calculations if nothing has changed, otherwise this will yield * incorrect numbers. */ if (!monster_list_needs_update(list)) return; /* Collect totals for easier calculations of the list. */ for (i = 0; i < (int)list->entries_size; i++) { if (list->entries[i].race == NULL) continue; if (list->entries[i].count[MONSTER_LIST_SECTION_LOS] > 0) list->total_entries[MONSTER_LIST_SECTION_LOS]++; if (list->entries[i].count[MONSTER_LIST_SECTION_ESP] > 0) list->total_entries[MONSTER_LIST_SECTION_ESP]++; list->total_monsters[MONSTER_LIST_SECTION_LOS] += list->entries[i].count[MONSTER_LIST_SECTION_LOS]; list->total_monsters[MONSTER_LIST_SECTION_ESP] += list->entries[i].count[MONSTER_LIST_SECTION_ESP]; list->distinct_entries++; } list->creation_turn = turn; list->sorted = FALSE; }
/** * 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; }
/** * Compacts and reorders the monster list. * * This function can be very dangerous, use with caution! * * When `num_to_compact` is 0, we just reorder the monsters into a more compact * order, eliminating any "holes" left by dead monsters. If `num_to_compact` is * positive, then we delete at least that many monsters and then reorder. * We try not to delete monsters that are high level or close to the player. * Each time we make a full pass through the monster list, if we haven't * deleted enough monsters, we relax our bounds a little to accept * monsters of a slightly higher level, and monsters slightly closer to * the player. */ void compact_monsters(int num_to_compact) { int m_idx, num_compacted, iter; int max_lev, min_dis, chance; /* Message (only if compacting) */ if (num_to_compact) msg("Compacting monsters..."); /* Compact at least 'num_to_compact' objects */ for (num_compacted = 0, iter = 1; num_compacted < num_to_compact; iter++) { /* Get more vicious each iteration */ max_lev = 5 * iter; /* Get closer each iteration */ min_dis = 5 * (20 - iter); /* Check all the monsters */ for (m_idx = 1; m_idx < cave_monster_max(cave); m_idx++) { struct monster *mon = cave_monster(cave, m_idx); /* Skip "dead" monsters */ if (!mon->race) continue; /* High level monsters start out "immune" */ if (mon->race->level > max_lev) continue; /* Ignore nearby monsters */ if ((min_dis > 0) && (mon->cdis < min_dis)) continue; /* Saving throw chance */ chance = 90; /* Only compact "Quest" Monsters in emergencies */ if (rf_has(mon->race->flags, RF_QUESTOR) && (iter < 1000)) chance = 100; /* Try not to compact Unique Monsters */ if (rf_has(mon->race->flags, RF_UNIQUE)) chance = 99; /* All monsters get a saving throw */ if (randint0(100) < chance) continue; /* Delete the monster */ delete_monster(mon->fy, mon->fx); /* Count the monster */ num_compacted++; } } /* Excise dead monsters (backwards!) */ for (m_idx = cave_monster_max(cave) - 1; m_idx >= 1; m_idx--) { struct monster *mon = cave_monster(cave, m_idx); /* Skip real monsters */ if (mon->race) continue; /* Move last monster into open hole */ compact_monsters_aux(cave_monster_max(cave) - 1, m_idx); /* Compress "cave->mon_max" */ cave->mon_max--; } }