/** * 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); } } } } }
static byte adj_energy(struct monster_race *race) { unsigned i = race->speed + (rsf_has(race->spell_flags,RSF_HASTE) ? 5 : 0); /* Fastest monster in the game is currently +30, but bounds check anyway */ return turn_energy(MIN(i, N_ELEMENTS(extract_energy) - 1)); }
/** * Describes a food item */ static bool describe_food(textblock *tb, const struct object *obj, bool subjective) { int nourishment = obj_known_food(obj); if (nourishment) { /* Sometimes adjust for player speed */ int multiplier = turn_energy(player->state.speed); if (!subjective) multiplier = 10; if (nourishment == OBJ_KNOWN_PRESENT) { textblock_append(tb, "Provides some nourishment.\n"); } else { textblock_append(tb, "Nourishes for around "); textblock_append_c(tb, COLOUR_L_GREEN, "%d", nourishment * multiplier / 10); textblock_append(tb, " turns.\n"); } return TRUE; } return FALSE; }
/** * 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; }
/** * The main game loop. * * This function will run until the player needs to enter a command, or closes * the game, or the character dies. */ void run_game_loop(void) { /* Tidy up after the player's command */ process_player_cleanup(); /* Keep processing the player until they use some energy or * another command is needed */ while (player->upkeep->playing) { process_player(); if (player->upkeep->energy_use) break; else return; } /* The player may still have enough energy to move, so we run another * player turn before processing the rest of the world */ while (player->energy >= z_info->move_energy) { /* Do any necessary animations */ event_signal(EVENT_ANIMATE); /* Process monster with even more energy first */ process_monsters(cave, player->energy + 1); if (player->is_dead || !player->upkeep->playing || player->upkeep->generate_level) break; /* Process the player until they use some energy */ while (player->upkeep->playing) { process_player(); if (player->upkeep->energy_use) break; else return; } } /* Now that the player's turn is fully complete, we run the main loop * until player input is needed again */ while (true) { notice_stuff(player); handle_stuff(player); event_signal(EVENT_REFRESH); /* Process the rest of the world, give the player energy and * increment the turn counter unless we need to stop playing or * generate a new level */ if (player->is_dead || !player->upkeep->playing) return; else if (!player->upkeep->generate_level) { /* Process the rest of the monsters */ process_monsters(cave, 0); /* Mark all monsters as ready to act when they have the energy */ reset_monsters(); /* Refresh */ notice_stuff(player); handle_stuff(player); event_signal(EVENT_REFRESH); if (player->is_dead || !player->upkeep->playing) return; /* Process the world every ten turns */ if (!(turn % 10) && !player->upkeep->generate_level) { process_world(cave); /* Refresh */ notice_stuff(player); handle_stuff(player); event_signal(EVENT_REFRESH); if (player->is_dead || !player->upkeep->playing) return; } /* Give the player some energy */ player->energy += turn_energy(player->state.speed); /* Count game turns */ turn++; } /* Make a new level if requested */ if (player->upkeep->generate_level) { if (character_dungeon) on_leave_level(); cave_generate(&cave, player); on_new_level(); player->upkeep->generate_level = false; } /* If the player has enough energy to move they now do so, after * any monsters with more energy take their turns */ while (player->energy >= z_info->move_energy) { /* Do any necessary animations */ event_signal(EVENT_ANIMATE); /* Process monster with even more energy first */ process_monsters(cave, player->energy + 1); if (player->is_dead || !player->upkeep->playing || player->upkeep->generate_level) break; /* Process the player until they use some energy */ while (player->upkeep->playing) { process_player(); if (player->upkeep->energy_use) break; else return; } } } }
/** * 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); } } } }
/** * Describe an object's effect, if any. */ static bool describe_effect(textblock *tb, const struct object *obj, bool only_artifacts, bool subjective) { char desc[200]; struct effect *effect = NULL; bool aimed = false; int min_time, max_time, failure_chance; /* Sometimes we only print artifact activation info */ if (only_artifacts && !obj->artifact) return false; if (obj_known_effect(obj, &effect, &aimed, &min_time, &max_time, &failure_chance) == false) return false; /* Effect not known, mouth platitudes */ if (!effect && object_effect(obj)) { if (aimed) textblock_append(tb, "It can be aimed.\n"); else if (tval_is_edible(obj)) textblock_append(tb, "It can be eaten.\n"); else if (tval_is_potion(obj)) textblock_append(tb, "It can be drunk.\n"); else if (tval_is_scroll(obj)) textblock_append(tb, "It can be read.\n"); else textblock_append(tb, "It can be activated.\n"); return true; } /* Activations get a special message */ if (obj->activation && obj->activation->desc) { textblock_append(tb, "When activated, it "); textblock_append(tb, obj->activation->desc); } else { int random_choices = 0; /* Get descriptions for all the effects */ effect = object_effect(obj); if (!effect_desc(effect)) return false; if (aimed) textblock_append(tb, "When aimed, it "); else if (tval_is_edible(obj)) textblock_append(tb, "When eaten, it "); else if (tval_is_potion(obj)) textblock_append(tb, "When quaffed, it "); else if (tval_is_scroll(obj)) textblock_append(tb, "When read, it "); else textblock_append(tb, "When activated, it "); /* Print a colourised description */ while (effect) { char *next_char = desc; int roll = 0; random_value value = { 0, 0, 0, 0 }; char dice_string[20]; int boost, level = obj->kind->level; /* Get the level */ if (obj->artifact) level = obj->artifact->level; else if (obj->ego) level = obj->ego->level; /* Get the boost */ boost = MAX(player->state.skills[SKILL_DEVICE] - level, 0); if (effect->dice != NULL) roll = dice_roll(effect->dice, &value); /* Deal with special random effect */ if (effect->index == EF_RANDOM) random_choices = roll + 1; /* Get the possible dice strings */ if (value.dice && value.base) strnfmt(dice_string, sizeof(dice_string), "%d+%dd%d", value.base, value.dice, value.sides); else if (value.dice) strnfmt(dice_string, sizeof(dice_string), "%dd%d", value.dice, value.sides); else strnfmt(dice_string, sizeof(dice_string), "%d", value.base); /* Check all the possible types of description format */ switch (base_descs[effect->index].efinfo_flag) { /* Healing sometimes has a minimum percentage */ case EFINFO_HEAL: { char min_string[50]; if (value.m_bonus) strnfmt(min_string, sizeof(min_string), " (or %d%%, whichever is greater)", value.m_bonus); else strnfmt(min_string, sizeof(min_string), ""); strnfmt(desc, sizeof(desc), effect_desc(effect), dice_string, min_string); break; } /* Nourishment is just a flat amount */ case EFINFO_CONST: { strnfmt(desc, sizeof(desc), effect_desc(effect), value.base/2); break; } case EFINFO_CURE: { strnfmt(desc, sizeof(desc), effect_desc(effect), timed_idx_to_desc(effect->params[0])); break; } case EFINFO_TIMED: { strnfmt(desc, sizeof(desc), effect_desc(effect), timed_idx_to_desc(effect->params[0]), dice_string); break; } case EFINFO_STAT: { strnfmt(desc, sizeof(desc), effect_desc(effect), mod_flags[effect->params[0]].name); break; } case EFINFO_SEEN: { strnfmt(desc, sizeof(desc), effect_desc(effect), gf_desc(effect->params[0])); break; } case EFINFO_SUMM: { strnfmt(desc, sizeof(desc), effect_desc(effect), summon_desc(effect->params[0])); break; } /* Only currently used for the player, but can handle monsters */ case EFINFO_TELE: { if (effect->params[0]) strnfmt(desc, sizeof(desc), effect_desc(effect), "a monster", value.base); else strnfmt(desc, sizeof(desc), effect_desc(effect), "you", value.base); break; } case EFINFO_QUAKE: { strnfmt(desc, sizeof(desc), effect_desc(effect), effect->params[1]); break; } case EFINFO_LIGHT: { strnfmt(desc, sizeof(desc), effect_desc(effect), dice_string, effect->params[1]); break; } /* Object generated balls are elemental */ case EFINFO_BALL: { strnfmt(desc, sizeof(desc), effect_desc(effect), elements[effect->params[0]].name, effect->params[1], dice_string); if (boost) my_strcat(desc, format(", which your device skill increases by %d per cent", boost), sizeof(desc)); break; } /* Bolts that inflict status */ case EFINFO_BOLT: { strnfmt(desc, sizeof(desc), effect_desc(effect), gf_desc(effect->params[0])); break; } /* Bolts and beams that damage */ case EFINFO_BOLTD: { strnfmt(desc, sizeof(desc), effect_desc(effect), gf_desc(effect->params[0]), dice_string); if (boost) my_strcat(desc, format(", which your device skill increases by %d per cent", boost), sizeof(desc)); break; } case EFINFO_TOUCH: { strnfmt(desc, sizeof(desc), effect_desc(effect), gf_desc(effect->params[0])); break; } case EFINFO_NONE: { strnfmt(desc, sizeof(desc), effect_desc(effect)); break; } default: { msg("Bad effect description passed to describe_effect(). Please report this bug."); return false; } } do { if (isdigit((unsigned char) *next_char)) textblock_append_c(tb, COLOUR_L_GREEN, "%c", *next_char); else textblock_append(tb, "%c", *next_char); } while (*next_char++); /* Random choices need special treatment - note that this code * assumes that RANDOM and the random choices will be the last * effect in the object/activation description */ if (random_choices >= 1) { if (effect->index == EF_RANDOM) ; else if (random_choices > 2) textblock_append(tb, ", "); else if (random_choices == 2) textblock_append(tb, " or "); random_choices--; } else if (effect->next) { if (effect->next->next && (effect->next->index != EF_RANDOM)) textblock_append(tb, ", "); else textblock_append(tb, " and "); } effect = effect->next; } } textblock_append(tb, ".\n"); if (min_time || max_time) { /* Sometimes adjust for player speed */ int multiplier = turn_energy(player->state.speed); if (!subjective) multiplier = 10; textblock_append(tb, "Takes "); /* Correct for player speed */ min_time = (min_time * multiplier) / 10; max_time = (max_time * multiplier) / 10; textblock_append_c(tb, COLOUR_L_GREEN, "%d", min_time); if (min_time != max_time) { textblock_append(tb, " to "); textblock_append_c(tb, COLOUR_L_GREEN, "%d", max_time); } textblock_append(tb, " turns to recharge"); if (subjective && player->state.speed != 110) textblock_append(tb, " at your current speed"); textblock_append(tb, ".\n"); } if (failure_chance > 0) { textblock_append(tb, "Your chance of success is %d.%d%%\n", (1000 - failure_chance) / 10, (1000 - failure_chance) % 10); } return true; }
/** * 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); }
/** * Describe an object's effect, if any. */ static bool describe_effect(textblock *tb, const struct object *obj, bool only_artifacts, bool subjective) { char desc[200]; struct effect *e; int effect = 0; bool aimed = FALSE; int min_time, max_time, failure_chance; /* Sometimes we only print artifact activation info */ if (only_artifacts && !obj->artifact) return FALSE; if (obj_known_effect(obj, &effect, &aimed, &min_time, &max_time, &failure_chance) == FALSE) return FALSE; /* We don't know much */ if (effect == OBJ_KNOWN_PRESENT) { if (aimed) textblock_append(tb, "It can be aimed.\n"); else if (tval_is_edible(obj)) textblock_append(tb, "It can be eaten.\n"); else if (tval_is_potion(obj)) textblock_append(tb, "It can be drunk.\n"); else if (tval_is_scroll(obj)) textblock_append(tb, "It can be read.\n"); else textblock_append(tb, "It can be activated.\n"); return TRUE; } /* Obtain the description */ e = obj->effect; if (!effect_desc(e)) return FALSE; if (aimed) textblock_append(tb, "When aimed, it "); else if (tval_is_edible(obj)) textblock_append(tb, "When eaten, it "); else if (tval_is_potion(obj)) textblock_append(tb, "When quaffed, it "); else if (tval_is_scroll(obj)) textblock_append(tb, "When read, it "); else textblock_append(tb, "When activated, it "); /* Print a colourised description */ while (e) { char *next_char = desc; random_value value = { 0, 0, 0, 0 }; char dice_string[20]; if (e->dice != NULL) (void) dice_roll(e->dice, &value); /* Get the possible dice strings */ if (value.dice && value.base) strnfmt(dice_string, sizeof(dice_string), "%d+%dd%d", value.base, value.dice, value.sides); else if (value.dice) strnfmt(dice_string, sizeof(dice_string), "%dd%d", value.dice, value.sides); else strnfmt(dice_string, sizeof(dice_string), "%d", value.base); /* Check all the possible types of description format */ switch (base_descs[e->index].efinfo_flag) { /* Healing sometimes has a minimum percentage */ case EFINFO_HEAL: { char min_string[50]; if (value.m_bonus) strnfmt(min_string, sizeof(min_string), " (or %d%%, whichever is greater)", value.m_bonus); else strnfmt(min_string, sizeof(min_string), ""); strnfmt(desc, sizeof(desc), effect_desc(e), dice_string, min_string); break; } /* Nourishment is just a flat amount */ case EFINFO_FEED: { strnfmt(desc, sizeof(desc), effect_desc(e), value.base); break; } case EFINFO_CURE: { strnfmt(desc, sizeof(desc), effect_desc(e), timed_idx_to_desc(e->params[0])); break; } case EFINFO_TIMED: { strnfmt(desc, sizeof(desc), effect_desc(e), timed_idx_to_desc(e->params[0]), dice_string); break; } case EFINFO_STAT: { strnfmt(desc, sizeof(desc), effect_desc(e), mod_flags[e->params[0]].name); break; } case EFINFO_SEEN: { strnfmt(desc, sizeof(desc), effect_desc(e), gf_desc(e->params[0])); break; } case EFINFO_SUMM: { strnfmt(desc, sizeof(desc), effect_desc(e), summon_desc(e->params[0])); break; } /* Only currently used for the player, but can handle monsters */ case EFINFO_TELE: { if (e->params[0]) strnfmt(desc, sizeof(desc), effect_desc(e), "a monster", value.base); else strnfmt(desc, sizeof(desc), effect_desc(e), "you", value.base); break; } case EFINFO_QUAKE: { strnfmt(desc, sizeof(desc), effect_desc(e), e->params[1]); break; } case EFINFO_LIGHT: { strnfmt(desc, sizeof(desc), effect_desc(e), dice_string, e->params[1]); break; } /* Object generated balls are elemental */ case EFINFO_BALL: { strnfmt(desc, sizeof(desc), effect_desc(e), elements[e->params[0]].name, e->params[1], dice_string); break; } /* Bolts that inflict status */ case EFINFO_BOLT: { strnfmt(desc, sizeof(desc), effect_desc(e), gf_desc(e->params[0])); break; } /* Bolts and beams that damage */ case EFINFO_BOLTD: { strnfmt(desc, sizeof(desc), effect_desc(e), gf_desc(e->params[0]), dice_string); break; } case EFINFO_TOUCH: { strnfmt(desc, sizeof(desc), effect_desc(e), gf_desc(e->params[0])); break; } default:strnfmt(desc, sizeof(desc), effect_desc(e)); break; } do { if (isdigit((unsigned char) *next_char)) textblock_append_c(tb, COLOUR_L_GREEN, "%c", *next_char); else textblock_append(tb, "%c", *next_char); } while (*next_char++); if (e->next) { if (e->next->next) textblock_append(tb, ", "); else textblock_append(tb, " and "); } e = e->next; } textblock_append(tb, ".\n"); if (min_time || max_time) { /* Sometimes adjust for player speed */ int multiplier = turn_energy(player->state.speed); if (!subjective) multiplier = 10; textblock_append(tb, "Takes "); /* Correct for player speed */ min_time *= multiplier / 10; max_time *= multiplier / 10; textblock_append_c(tb, COLOUR_L_GREEN, "%d", min_time); if (min_time != max_time) { textblock_append(tb, " to "); textblock_append_c(tb, COLOUR_L_GREEN, "%d", max_time); } textblock_append(tb, " turns to recharge"); if (subjective && player->state.speed != 110) textblock_append(tb, " at your current speed"); textblock_append(tb, ".\n"); } if (failure_chance > 0) { textblock_append(tb, "Your chance of success is %d.%d%%\n", (1000 - failure_chance) / 10, (1000 - failure_chance) % 10); } return TRUE; }