/** * Describe things that look like lights. */ static bool describe_light(textblock *tb, const struct object *obj, oinfo_detail_t mode) { int rad = 0; bool uses_fuel = false; int refuel_turns = 0; bool terse = mode & OINFO_TERSE ? true : false; if (!obj_known_light(obj, mode, &rad, &uses_fuel, &refuel_turns)) return false; textblock_append(tb, "Radius "); textblock_append_c(tb, COLOUR_L_GREEN, format("%d", rad)); textblock_append(tb, " light."); if (tval_is_light(obj)) { if (!obj->artifact && !uses_fuel) textblock_append(tb, " No fuel required."); if (!terse) { if (refuel_turns) textblock_append(tb, " Refills other lanterns up to %d turns of fuel.", refuel_turns); else textblock_append(tb, " Cannot be refueled."); } } textblock_append(tb, "\n"); return true; }
/** * Describe blows. */ static bool describe_blows(textblock *tb, const struct object *obj) { int i; struct blow_info blow_info[STAT_RANGE * 2]; /* (Very) theoretical max */ int num_entries = 0; num_entries = obj_known_blows(obj, STAT_RANGE * 2, blow_info); if (num_entries == 0) return false; /* First entry is always current blows (+0, +0) */ textblock_append_c(tb, COLOUR_L_GREEN, "%d.%d ", blow_info[0].centiblows / 100, (blow_info[0].centiblows / 10) % 10); textblock_append(tb, "blow%s/round.\n", (blow_info[0].centiblows > 100) ? "s" : ""); /* Then list combinations that give more blows / speed boost */ for (i = 1; i < num_entries; i++) { struct blow_info entry = blow_info[i]; if (entry.centiblows % 10 == 0) { textblock_append(tb, "With +%d STR and +%d DEX you would get %d.%d blows\n", entry.str_plus, entry.dex_plus, (entry.centiblows / 100), (entry.centiblows / 10) % 10); } else { textblock_append(tb, "With +%d STR and +%d DEX you would attack a bit faster\n", entry.str_plus, entry.dex_plus); } } return true; }
static bool describe_food(textblock *tb, const object_type *o_ptr, bool subjective, bool full) { /* Describe boring bits */ if ((o_ptr->tval == TV_FOOD || o_ptr->tval == TV_POTION) && o_ptr->pval[DEFAULT_PVAL]) { /* Sometimes adjust for player speed */ int multiplier = extract_energy[p_ptr->state.speed]; if (!subjective) multiplier = 10; if (object_is_known(o_ptr) || full) { textblock_append(tb, "Nourishes for around "); textblock_append_c(tb, TERM_L_GREEN, "%d", (o_ptr->pval[DEFAULT_PVAL] / 2) * multiplier / 10); textblock_append(tb, " turns.\n"); } else { textblock_append(tb, "Provides some nourishment.\n"); } return TRUE; } return FALSE; }
/* * Describe other bonuses. */ static bool describe_bonus(textblock *tb, const object_type *o_ptr, oinfo_detail_t mode) { int j, count = 0; bool full = mode & OINFO_FULL; bool dummy = mode & OINFO_DUMMY; bool terse = mode & OINFO_TERSE; if (((o_ptr->ident) & IDENT_WORN) || dummy || full) { for (j = 0; j < MAX_P_BONUS; j++) { if (o_ptr->bonus_other[j] != 0) count++; } } if (count > 0) { byte attr = (o_ptr->bonus_other[0] > 0 ? TERM_L_GREEN : TERM_ORANGE); if (!terse) { textblock_append(tb, "It gives "); if (dummy) textblock_append(tb, "up to "); } /* Bonuses */ for (j = 0; j < MAX_P_BONUS; j++) { if (o_ptr->bonus_other[j] == 0) continue; attr = (o_ptr->bonus_other[j] > 0 ? TERM_L_GREEN : TERM_ORANGE); textblock_append_c(tb, attr, "%d ", o_ptr->bonus_other[j]); if (!terse) textblock_append(tb, "to your "); textblock_append_c(tb, attr, othername[j]); if (count >= (terse ? 2 : 3)) textblock_append(tb, ", "); if ((count == 2) && !terse) textblock_append(tb, " and "); if (count == 1) textblock_append(tb, ". "); count--; } textblock_append(tb, "\n"); return TRUE; } return FALSE; }
/** * Describe combat advantages. */ static bool describe_combat(textblock *tb, const struct object *obj) { struct object *bow = equipped_item_by_slot_name(player, "shooting"); bool weapon = tval_is_melee_weapon(obj); bool ammo = (player->state.ammo_tval == obj->tval) && (bow); int range, break_chance; bool impactful, thrown_effect, too_heavy; obj_known_misc_combat(obj, &thrown_effect, &range, &impactful, &break_chance, &too_heavy); if (!weapon && !ammo) { if (thrown_effect) { textblock_append(tb, "It can be thrown at creatures with damaging effect.\n"); return true; } else return false; } textblock_append_c(tb, COLOUR_L_WHITE, "Combat info:\n"); if (too_heavy) textblock_append_c(tb, COLOUR_L_RED, "You are too weak to use this weapon.\n"); describe_blows(tb, obj); if (!weapon) { /* Ammo */ textblock_append(tb, "Hits targets up to "); textblock_append_c(tb, COLOUR_L_GREEN, format("%d", range)); textblock_append(tb, " feet away.\n"); } describe_damage(tb, obj); if (impactful) textblock_append(tb, "Sometimes creates earthquakes on impact.\n"); if (ammo) { textblock_append_c(tb, COLOUR_L_GREEN, "%d%%", break_chance); textblock_append(tb, " chance of breaking upon contact.\n"); } /* Something has been said */ return true; }
/** * Describe objects that can be used for digging. */ static bool describe_digger(textblock *tb, const struct object *obj) { int i; int deciturns[DIGGING_MAX]; struct object *obj1 = (struct object *) obj; static const char *names[4] = { "rubble", "magma veins", "quartz veins", "granite" }; /* Get useful info or print nothing */ if (!obj_known_digging(obj1, deciturns)) return false; for (i = DIGGING_RUBBLE; i < DIGGING_DOORS; i++) { if (i == 0 && deciturns[0] > 0) { if (tval_is_melee_weapon(obj)) textblock_append(tb, "Clears "); else textblock_append(tb, "With this item, your current weapon clears "); } if (i == 3 || (i != 0 && deciturns[i] == 0)) textblock_append(tb, "and "); if (deciturns[i] == 0) { textblock_append_c(tb, COLOUR_L_RED, "doesn't affect "); textblock_append(tb, "%s.\n", names[i]); break; } textblock_append(tb, "%s in ", names[i]); if (deciturns[i] == 10) { textblock_append_c(tb, COLOUR_L_GREEN, "1 "); } else if (deciturns[i] < 100) { textblock_append_c(tb, COLOUR_GREEN, "%d.%d ", deciturns[i]/10, deciturns[i]%10); } else { textblock_append_c(tb, (deciturns[i] < 1000) ? COLOUR_YELLOW : COLOUR_RED, "%d ", (deciturns[i]+5)/10); } textblock_append(tb, "turn%s%s", deciturns[i] == 10 ? "" : "s", (i == 3) ? ".\n" : ", "); } return true; }
/* * Describe stat modifications. */ static bool describe_stats(textblock *tb, const object_type *o_ptr, bitflag flags[MAX_PVALS][OF_SIZE], oinfo_detail_t mode) { const char *descs[N_ELEMENTS(pval_flags)]; size_t count, i; bool full = mode & OINFO_FULL; bool dummy = mode & OINFO_DUMMY; bool search = FALSE; if (!o_ptr->num_pvals && !dummy) return FALSE; for (i = 0; i < o_ptr->num_pvals; i++) { count = info_collect(tb, pval_flags, N_ELEMENTS(pval_flags), flags[i], descs); if (count) { if ((object_this_pval_is_visible(o_ptr, i) || full) && !dummy) textblock_append_c(tb, (o_ptr->pval[i] > 0) ? TERM_L_GREEN : TERM_RED, "%+i ", o_ptr->pval[i]); else textblock_append(tb, "Affects your "); info_out_list(tb, descs, count); } if (of_has(flags[i], OF_SEARCH)) search = TRUE; } if (search) { if ((object_this_pval_is_visible(o_ptr, which_pval(o_ptr, OF_SEARCH)) || full) && !dummy) { textblock_append_c(tb, (o_ptr->pval[which_pval(o_ptr, OF_SEARCH)] > 0) ? TERM_L_GREEN : TERM_RED, "%+i%% ", o_ptr->pval[which_pval(o_ptr, OF_SEARCH)] * 5); textblock_append(tb, "to searching.\n"); } else if (count) textblock_append(tb, "Also affects your searching skill.\n"); else textblock_append(tb, "Affects your searching skill.\n"); } return TRUE; }
/* * Describe things that look like lights. */ static bool describe_light(textblock * tb, const object_type * o_ptr, bool terse) { int rad = 0; bool artifact = artifact_p(o_ptr); bool is_light = (o_ptr->tval == TV_LIGHT) ? TRUE : FALSE; if (o_ptr->tval != TV_LIGHT && !of_has(o_ptr->flags_obj, OF_LIGHT)) return FALSE; /* Work out radius */ if (artifact && is_light) rad = 3; else if (is_light) rad = 2; if (of_has(o_ptr->flags_obj, OF_LIGHT)) rad++; /* Describe here */ if (!terse) textblock_append(tb, "It provides radius "); else textblock_append(tb, "Radius "); textblock_append_c(tb, TERM_L_GREEN, format("%d", rad)); if (artifact) textblock_append(tb, " light forever."); else textblock_append(tb, " light."); if (!terse && is_light && !artifact) { const char *name = (o_ptr->sval == SV_LIGHT_TORCH) ? "torches" : "lanterns"; int turns = (o_ptr->sval == SV_LIGHT_TORCH) ? FUEL_TORCH : FUEL_LAMP; textblock_append(tb, " Refills other %s up to %d turns of fuel. ", name, turns); } textblock_append(tb, "\n"); return TRUE; }
/** * Describe stat modifications. */ static bool describe_stats(textblock *tb, const struct object *obj, oinfo_detail_t mode) { size_t count = 0, i; bool detail = false; /* Don't give exact plusses for faked ego items as each real one will * be different */ bool suppress_details = mode & (OINFO_EGO | OINFO_FAKE) ? true : false; /* Fact of but not size of mods is known for egos and flavoured items * the player is aware of */ bool known_effect = false; if (obj->known->ego) known_effect = true; if (tval_can_have_flavor_k(obj->kind) && object_flavor_is_aware(obj)) known_effect = true; /* See what we've got */ for (i = 0; i < OBJ_MOD_MAX; i++) if (obj->known->modifiers[i]) { count++; detail = true; } if (!count) return false; for (i = 0; i < OBJ_MOD_MAX; i++) { const char *desc = lookup_obj_property(OBJ_PROPERTY_MOD, i)->name; int val = obj->known->modifiers[i]; if (!val) continue; /* Actual object */ if (detail && !suppress_details) { int attr = (val > 0) ? COLOUR_L_GREEN : COLOUR_RED; textblock_append_c(tb, attr, "%+i %s.\n", val, desc); } else if (known_effect) /* Ego type or jewellery description */ textblock_append(tb, "Affects your %s\n", desc); } return true; }
/** * Describe stat modifications. */ static bool describe_stats(textblock *tb, const struct object *obj, oinfo_detail_t mode) { size_t count = 0, i; bool detail = false; /* Don't give exact plusses for faked ego items as each real one will * be different */ bool suppress_details = mode & OINFO_EGO ? true : false; /* Fact of but not size of mods is known for egos and flavoured items * the player is aware of */ bool known_effect = false; if (object_ego_is_visible(obj)) known_effect = true; if (tval_can_have_flavor_k(obj->kind) && object_flavor_is_aware(obj)) known_effect = true; /* See what we've got */ for (i = 0; i < N_ELEMENTS(mod_flags); i++) if (obj->modifiers[mod_flags[i].flag] != 0 && mod_flags[i].name[0]) { count++; /* Either all mods are visible, or none are */ if (object_this_mod_is_visible(obj, mod_flags[i].flag)) detail = true; } if (!count) return false; for (i = 0; i < N_ELEMENTS(mod_flags); i++) { const char *desc = mod_flags[i].name; int val = obj->modifiers[mod_flags[i].flag]; if (!val) continue; if (!mod_flags[i].name[0]) continue; if (detail && !suppress_details) { int attr = (val > 0) ? COLOUR_L_GREEN : COLOUR_RED; textblock_append_c(tb, attr, "%+i %s.\n", val, desc); } else if (known_effect) textblock_append(tb, "Affects your %s\n", desc); } return true; }
/* * Describe things that look like lights. */ static bool describe_light(textblock *tb, const object_type *o_ptr, const bitflag flags[OF_SIZE], bool terse) { int rad = 0; bool artifact = o_ptr->artifact; bool no_fuel = of_has(flags, OF_NO_FUEL) ? TRUE : FALSE; bool is_light = (o_ptr->tval == TV_LIGHT) ? TRUE : FALSE; if (o_ptr->tval != TV_LIGHT && !of_has(flags, OF_LIGHT)) return FALSE; /* Work out radius */ if (artifact && is_light) rad = 3; else if (is_light) rad = 2; if (of_has(flags, OF_LIGHT)) rad++; /* Describe here */ textblock_append(tb, "Radius "); textblock_append_c(tb, TERM_L_GREEN, format("%d", rad)); if (no_fuel && !artifact) textblock_append(tb, " light. No fuel required."); else if (is_light && o_ptr->sval == SV_LIGHT_TORCH) textblock_append(tb, " light, reduced when running out of fuel."); else textblock_append(tb, " light."); if (!terse && is_light && !no_fuel) { const char *name = (o_ptr->sval == SV_LIGHT_TORCH) ? "torches" : "lanterns"; int turns = (o_ptr->sval == SV_LIGHT_TORCH) ? FUEL_TORCH : FUEL_LAMP; textblock_append(tb, " Refills other %s up to %d turns of fuel.", name, turns); } textblock_append(tb, "\n"); return TRUE; }
/** * Allow the standard list formatted to be bypassed for special cases. * * Returning TRUE will bypass any other formatteding in * monster_list_format_textblock(). * * \param list is the monster list to format. * \param tb is the textblock to produce or NULL if only the dimensions need to * be calculated. * \param max_lines is the maximum number of lines that can be displayed. * \param max_width is the maximum line width that can be displayed. * \param max_height_result is returned with the number of lines needed to * format the list without truncation. * \param max_width_result is returned with the width needed to format the list * without truncation. * \return TRUE if further formatting should be bypassed. */ static bool monster_list_format_special(const monster_list_t *list, textblock *tb, int max_lines, int max_width, size_t *max_height_result, size_t *max_width_result) { if (player->timed[TMD_IMAGE] > 0) { /* Hack - message needs newline to calculate width properly. */ const char *message = "Your hallucinations are too wild to see things clearly.\n"; if (max_height_result != NULL) *max_height_result = 1; if (max_width_result != NULL) *max_width_result = strlen(message); if (tb != NULL) textblock_append_c(tb, COLOUR_ORANGE, "%s", message); return TRUE; } return FALSE; }
/** * Describe an item's curses. */ static bool describe_curses(textblock *tb, const struct object *obj, const bitflag flags[OF_SIZE]) { int i; struct curse_data *c = obj->known->curses; if (!c) return false; for (i = 1; i < z_info->curse_max; i++) { if (c[i].power) { textblock_append(tb, "It "); textblock_append_c(tb, COLOUR_L_RED, curses[i].desc); if (c[i].power == 100) { textblock_append(tb, "; this curse cannot be removed"); } textblock_append(tb, ".\n"); } } return true; }
/* * Describe brands. */ static bool describe_brands(textblock * tb, const object_type * o_ptr, oinfo_detail_t mode) { int j, brand = 0; bool full = mode & OINFO_FULL; bool terse = mode & OINFO_TERSE; byte attr = TERM_L_UMBER; for (j = 0; j < MAX_P_BRAND; j++) if (if_has(o_ptr->id_other, OBJECT_ID_BASE_BRAND + j) || (full && (o_ptr->multiple_brand[j] > MULTIPLE_BASE))) brand++; if (brand > 0) { if (terse) textblock_append(tb, "Branded with "); else textblock_append(tb, "It does extra damage from "); /* Brands */ for (j = 0; j < MAX_P_BRAND; j++) { if (!if_has(o_ptr->id_other, OBJECT_ID_BASE_BRAND + j) && !(full && (o_ptr->multiple_brand[j] > MULTIPLE_BASE))) continue; textblock_append_c(tb, attr, brandee[j]); if (brand >= 3) textblock_append(tb, ", "); if (brand == 2) textblock_append(tb, " and "); if (brand == 1) textblock_append(tb, ". "); brand--; } textblock_append(tb, "\n"); return TRUE; } return FALSE; }
/** * Describe stat modifications. */ static bool describe_stats(textblock *tb, const struct object *obj, oinfo_detail_t mode) { size_t count = 0, i; bool detail = FALSE; /* Don't give exact pluses for faked ego items as each real one will be different */ bool suppress_details = mode & OINFO_EGO ? TRUE : FALSE; /* See what we've got */ for (i = 0; i < N_ELEMENTS(mod_flags); i++) if (obj->modifiers[mod_flags[i].flag] != 0 && mod_flags[i].name[0]) { count++; /* Either all mods are visible, or none are */ if (object_this_mod_is_visible(obj, i)) detail = TRUE; } if (!count) return FALSE; for (i = 0; i < N_ELEMENTS(mod_flags); i++) { const char *desc = mod_flags[i].name; int val = obj->modifiers[mod_flags[i].flag]; if (!val) continue; if (!mod_flags[i].name[0]) continue; if (detail && !suppress_details) { int attr = (val > 0) ? COLOUR_L_GREEN : COLOUR_RED; textblock_append_c(tb, attr, "%+i %s.\n", val, desc); } else textblock_append(tb, "Affects your %s\n", desc); } return TRUE; }
/** * 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; }
/* * Describe stat modifications. */ static bool describe_stats(textblock * tb, const object_type * o_ptr, oinfo_detail_t mode) { int j, min = 0, max = 0, count = 0; bool full = mode & OINFO_FULL; bool dummy = mode & OINFO_DUMMY; bool terse = mode & OINFO_TERSE; if (((o_ptr->ident) & IDENT_WORN) || dummy || full) { for (j = 0; j < A_MAX; j++) { if (o_ptr->bonus_stat[j] != 0) count++; if (o_ptr->bonus_stat[j] < min) min = o_ptr->bonus_stat[j]; if (o_ptr->bonus_stat[j] > max) max = o_ptr->bonus_stat[j]; } } if (count > 0) { byte attr = (o_ptr->bonus_stat[A_STR] > 0 ? TERM_L_GREEN : TERM_ORANGE); if (!terse) { textblock_append(tb, "It gives "); if (dummy) textblock_append(tb, "up to "); } /* Special case: all stats */ if (min == max) textblock_append_c(tb, attr, "%d to all your stats. ", min); /* Some stats */ else { for (j = 0; j < A_MAX; j++) { if (o_ptr->bonus_stat[j] == 0) continue; attr = (o_ptr->bonus_stat[j] > 0 ? TERM_L_GREEN : TERM_ORANGE); textblock_append_c(tb, attr, "%d ", o_ptr->bonus_stat[j]); if (!terse) textblock_append(tb, "to your "); textblock_append_c(tb, attr, statname[j]); if (count >= (terse ? 2 : 3)) textblock_append(tb, ", "); if ((count == 2) && !terse) textblock_append(tb, " and "); if (count == 1) textblock_append(tb, ". "); count--; } } textblock_append(tb, "\n"); return TRUE; } return FALSE; }
/* * Describe objects that can be used for digging. */ static bool describe_digger(textblock *tb, const object_type *o_ptr, oinfo_detail_t mode) { bool full = mode & OINFO_FULL; player_state st; object_type inven[INVEN_TOTAL]; int sl = wield_slot(o_ptr); int i; bitflag f[OF_SIZE]; int chances[4]; /* These are out of 1600 */ static const char *names[4] = { "rubble", "magma veins", "quartz veins", "granite" }; /* abort if we are a dummy object */ if (mode & OINFO_DUMMY) return FALSE; if (full) object_flags(o_ptr, f); else object_flags_known(o_ptr, f); if (sl < 0 || (sl != INVEN_WIELD && !of_has(f, OF_TUNNEL))) return FALSE; memcpy(inven, p_ptr->inventory, INVEN_TOTAL * sizeof(object_type)); /* * Hack -- if we examine a ring that is worn on the right finger, * we shouldn't put a copy of it on the left finger before calculating * digging skills. */ if (o_ptr != &p_ptr->inventory[INVEN_RIGHT]) inven[sl] = *o_ptr; calc_bonuses(inven, &st, TRUE); chances[0] = st.skills[SKILL_DIGGING] * 8; chances[1] = (st.skills[SKILL_DIGGING] - 10) * 4; chances[2] = (st.skills[SKILL_DIGGING] - 20) * 2; chances[3] = (st.skills[SKILL_DIGGING] - 40) * 1; for (i = 0; i < 4; i++) { int chance = MAX(0, MIN(1600, chances[i])); int decis = chance ? (16000 / chance) : 0; if (i == 0 && chance > 0) { if (sl == INVEN_WIELD) textblock_append(tb, "Clears "); else textblock_append(tb, "With this item, your current weapon clears "); } if (i == 3 || (i != 0 && chance == 0)) textblock_append(tb, "and "); if (chance == 0) { textblock_append_c(tb, TERM_L_RED, "doesn't affect "); textblock_append(tb, "%s.\n", names[i]); break; } textblock_append(tb, "%s in ", names[i]); if (chance == 1600) { textblock_append_c(tb, TERM_L_GREEN, "1 "); } else if (decis < 100) { textblock_append_c(tb, TERM_GREEN, "%d.%d ", decis/10, decis%10); } else { textblock_append_c(tb, (decis < 1000) ? TERM_YELLOW : TERM_RED, "%d ", (decis+5)/10); } textblock_append(tb, "turn%s%s", decis == 10 ? "" : "s", (i == 3) ? ".\n" : ", "); } return TRUE; }
/* * Describe combat advantages. */ static bool describe_combat(textblock *tb, const object_type *o_ptr, oinfo_detail_t mode) { bool full = mode & OINFO_FULL; const char *desc[SL_MAX] = { 0 }; int i; int mult[SL_MAX]; int cnt, dam, total_dam, plus = 0; int xtra_postcrit = 0, xtra_precrit = 0; int crit_mult, crit_div, crit_add; int str_plus, dex_plus, old_blows = 0, new_blows, extra_blows; int str_faster = -1, str_done = -1; object_type *bow = &p_ptr->inventory[INVEN_BOW]; bitflag f[OF_SIZE], tmp_f[OF_SIZE], mask[OF_SIZE]; bool weapon = (wield_slot(o_ptr) == INVEN_WIELD); bool ammo = (p_ptr->state.ammo_tval == o_ptr->tval) && (bow->kind); int multiplier = 1; /* Create the "all slays" mask */ create_mask(mask, FALSE, OFT_SLAY, OFT_KILL, OFT_BRAND, OFT_MAX); /* Abort if we've nothing to say */ if (mode & OINFO_DUMMY) return FALSE; if (!weapon && !ammo) { /* Potions can have special text */ if (o_ptr->tval != TV_POTION || o_ptr->dd == 0 || o_ptr->ds == 0 || !object_flavor_is_aware(o_ptr)) return FALSE; textblock_append(tb, "It can be thrown at creatures with damaging effect.\n"); return TRUE; } if (full) { object_flags(o_ptr, f); } else { object_flags_known(o_ptr, f); } textblock_append_c(tb, TERM_L_WHITE, "Combat info:\n"); if (weapon) { /* * Get the player's hypothetical state, were they to be * wielding this item. */ player_state state; int dex_plus_bound; int str_plus_bound; object_type inven[INVEN_TOTAL]; memcpy(inven, p_ptr->inventory, INVEN_TOTAL * sizeof(object_type)); inven[INVEN_WIELD] = *o_ptr; if (full) object_know_all_flags(&inven[INVEN_WIELD]); calc_bonuses(inven, &state, TRUE); dex_plus_bound = STAT_RANGE - state.stat_ind[A_DEX]; str_plus_bound = STAT_RANGE - state.stat_ind[A_STR]; dam = ((o_ptr->ds + 1) * o_ptr->dd * 5); xtra_postcrit = state.dis_to_d * 10; if (object_attack_plusses_are_visible(o_ptr)) { xtra_precrit += o_ptr->to_d * 10; plus += o_ptr->to_h; } calculate_melee_crits(&state, o_ptr->weight, plus, &crit_mult, &crit_add, &crit_div); /* Warn about heavy weapons */ if (adj_str_hold[state.stat_ind[A_STR]] < o_ptr->weight / 10) textblock_append_c(tb, TERM_L_RED, "You are too weak to use this weapon.\n"); textblock_append_c(tb, TERM_L_GREEN, "%d.%d ", state.num_blows / 100, (state.num_blows / 10) % 10); textblock_append(tb, "blow%s/round.\n", (state.num_blows > 100) ? "s" : ""); /* Check to see if extra STR or DEX would yield extra blows */ old_blows = state.num_blows; extra_blows = 0; /* First we need to look for extra blows on other items, as * state does not track these */ for (i = INVEN_BOW; i < INVEN_TOTAL; i++) { if (!p_ptr->inventory[i].kind) continue; object_flags_known(&p_ptr->inventory[i], tmp_f); if (of_has(tmp_f, OF_BLOWS)) extra_blows += p_ptr->inventory[i].pval[which_pval(&p_ptr->inventory[i], OF_BLOWS)]; } /* Then we add blows from the weapon being examined */ if (of_has(f, OF_BLOWS)) extra_blows += o_ptr->pval[which_pval(o_ptr, OF_BLOWS)]; /* Then we check for extra "real" blows */ for (dex_plus = 0; dex_plus < dex_plus_bound; dex_plus++) { for (str_plus = 0; str_plus < str_plus_bound; str_plus++) { state.stat_ind[A_STR] += str_plus; state.stat_ind[A_DEX] += dex_plus; new_blows = calc_blows(o_ptr, &state, extra_blows); /* Test to make sure that this extra blow is a * new str/dex combination, not a repeat */ if ((new_blows - new_blows % 10) > (old_blows - old_blows % 10) && (str_plus < str_done || str_done == -1)) { textblock_append(tb, "With +%d STR and +%d DEX you would get %d.%d blows\n", str_plus, dex_plus, (new_blows / 100), (new_blows / 10) % 10); state.stat_ind[A_STR] -= str_plus; state.stat_ind[A_DEX] -= dex_plus; str_done = str_plus; break; } /* If the combination doesn't increment * the displayed blows number, it might still * take a little less energy */ if (new_blows > old_blows && (str_plus < str_faster || str_faster == -1) && (str_plus < str_done || str_done == -1)) { textblock_append(tb, "With +%d STR and +%d DEX you would attack a bit faster\n", str_plus, dex_plus); state.stat_ind[A_STR] -= str_plus; state.stat_ind[A_DEX] -= dex_plus; str_faster = str_plus; continue; } state.stat_ind[A_STR] -= str_plus; state.stat_ind[A_DEX] -= dex_plus; } } } else { int tdis = 6 + 2 * p_ptr->state.ammo_mult; if (object_attack_plusses_are_visible(o_ptr)) plus += o_ptr->to_h; calculate_missile_crits(&p_ptr->state, o_ptr->weight, plus, &crit_mult, &crit_add, &crit_div); /* Calculate damage */ dam = ((o_ptr->ds + 1) * o_ptr->dd * 5); if (object_attack_plusses_are_visible(o_ptr)) dam += (o_ptr->to_d * 10); if (object_attack_plusses_are_visible(bow)) dam += (bow->to_d * 10); /* Apply brands/slays from the shooter to the ammo, but only if known * Note that this is not dependent on mode, so that viewing shop-held * ammo (fully known) does not leak information about launcher */ object_flags_known(bow, tmp_f); of_union(f, tmp_f); textblock_append(tb, "Hits targets up to "); textblock_append_c(tb, TERM_L_GREEN, format("%d", tdis * 10)); textblock_append(tb, " feet away.\n"); } /* Collect slays */ /* Melee weapons get slays and brands from other items now */ if (weapon) { bool nonweap = FALSE; for (i = INVEN_LEFT; i < INVEN_TOTAL; i++) { if (!p_ptr->inventory[i].kind) continue; object_flags_known(&p_ptr->inventory[i], tmp_f); of_inter(tmp_f, mask); /* strip out non-slays */ if (of_union(f, tmp_f)) nonweap = TRUE; } if (nonweap) textblock_append(tb, "This weapon may benefit from one or more off-weapon brands or slays.\n"); } textblock_append(tb, "Average damage/round: "); if (ammo) multiplier = p_ptr->state.ammo_mult; cnt = list_slays(f, mask, desc, NULL, mult, TRUE); for (i = 0; i < cnt; i++) { int melee_adj_mult = ammo ? 0 : 1; /* ammo mult adds fully, melee mult is times 1, so adds 1 less */ /* Include bonus damage and slay in stated average */ total_dam = dam * (multiplier + mult[i] - melee_adj_mult) + xtra_precrit; total_dam = (total_dam * crit_mult + crit_add) / crit_div; total_dam += xtra_postcrit; if (weapon) total_dam = (total_dam * old_blows) / 100; else total_dam *= p_ptr->state.num_shots; if (total_dam <= 0) textblock_append_c(tb, TERM_L_RED, "%d", 0); else if (total_dam % 10) textblock_append_c(tb, TERM_L_GREEN, "%d.%d", total_dam / 10, total_dam % 10); else textblock_append_c(tb, TERM_L_GREEN, "%d", total_dam / 10); textblock_append(tb, " vs. %s, ", desc[i]); } if (cnt) textblock_append(tb, "and "); /* Include bonus damage in stated average */ total_dam = dam * multiplier + xtra_precrit; total_dam = (total_dam * crit_mult + crit_add) / crit_div; total_dam += xtra_postcrit; if (weapon) total_dam = (total_dam * old_blows) / 100; else total_dam *= p_ptr->state.num_shots; if (total_dam <= 0) textblock_append_c(tb, TERM_L_RED, "%d", 0); else if (total_dam % 10) textblock_append_c(tb, TERM_L_GREEN, "%d.%d", total_dam / 10, total_dam % 10); else textblock_append_c(tb, TERM_L_GREEN, "%d", total_dam / 10); if (cnt) textblock_append(tb, " vs. others"); textblock_append(tb, ".\n"); /* Note the impact flag */ if (of_has(f, OF_IMPACT)) textblock_append(tb, "Sometimes creates earthquakes on impact.\n"); /* Add breakage chance */ if (ammo) { int chance = breakage_chance(o_ptr, TRUE); textblock_append_c(tb, TERM_L_GREEN, "%d%%", chance); textblock_append(tb, " chance of breaking upon contact.\n"); } /* You always have something to say... */ return TRUE; }
/* * Describe damage. */ static bool describe_damage(textblock *tb, const object_type *o_ptr, player_state state, bitflag f[OF_SIZE], oinfo_detail_t mode) { const char *desc[SL_MAX] = { 0 }; size_t i, cnt; int mult[SL_MAX]; int dice, sides, dam, total_dam, plus = 0; int xtra_postcrit = 0, xtra_precrit = 0; int crit_mult, crit_div, crit_add; int old_blows = 0; object_type *bow = &p_ptr->inventory[INVEN_BOW]; bitflag tmp_f[OF_SIZE], mask[OF_SIZE]; bool weapon = (wield_slot(o_ptr) == INVEN_WIELD); bool ammo = (p_ptr->state.ammo_tval == o_ptr->tval) && (bow->kind); int multiplier = 1; bool full = mode & OINFO_FULL; /* Create the "all slays" mask */ create_mask(mask, FALSE, OFT_SLAY, OFT_KILL, OFT_BRAND, OFT_MAX); /* Use displayed dice if real dice not known */ if (full || object_attack_plusses_are_visible(o_ptr)) { dice = o_ptr->dd; sides = o_ptr->ds; } else { dice = o_ptr->kind->dd; sides = o_ptr->kind->ds; } /* Calculate damage */ dam = ((sides + 1) * dice * 5); if (weapon) { xtra_postcrit = state.dis_to_d * 10; if (object_attack_plusses_are_visible(o_ptr) || full) { xtra_precrit += o_ptr->to_d * 10; plus += o_ptr->to_h; } calculate_melee_crits(&state, o_ptr->weight, plus, &crit_mult, &crit_add, &crit_div); old_blows = state.num_blows; } else { /* Ammo */ if (object_attack_plusses_are_visible(o_ptr) || full) plus += o_ptr->to_h; calculate_missile_crits(&p_ptr->state, o_ptr->weight, plus, &crit_mult, &crit_add, &crit_div); if (object_attack_plusses_are_visible(o_ptr) || full) dam += (o_ptr->to_d * 10); if (object_attack_plusses_are_visible(bow)) dam += (bow->to_d * 10); /* Apply brands/slays from the shooter to the ammo, but only if known * Note that this is not dependent on mode, so that viewing shop-held * ammo (fully known) does not leak information about launcher */ object_flags_known(bow, tmp_f); of_union(f, tmp_f); } /* Collect slays */ /* Melee weapons get slays and brands from other items now */ if (weapon) { bool nonweap_slay = FALSE; for (i = INVEN_LEFT; i < INVEN_TOTAL; i++) { if (!p_ptr->inventory[i].kind) continue; object_flags_known(&p_ptr->inventory[i], tmp_f); /* Strip out non-slays */ of_inter(tmp_f, mask); if (of_union(f, tmp_f)) nonweap_slay = TRUE; } if (nonweap_slay) textblock_append(tb, "This weapon may benefit from one or more off-weapon brands or slays.\n"); } textblock_append(tb, "Average damage/round: "); if (ammo) multiplier = p_ptr->state.ammo_mult; /* Output damage for creatures effected by the brands or slays */ cnt = list_slays(f, mask, desc, NULL, mult, TRUE); for (i = 0; i < cnt; i++) { /* ammo mult adds fully, melee mult is times 1, so adds 1 less */ int melee_adj_mult = ammo ? 0 : 1; /* Include bonus damage and slay in stated average */ total_dam = dam * (multiplier + mult[i] - melee_adj_mult) + xtra_precrit; total_dam = (total_dam * crit_mult + crit_add) / crit_div; total_dam += xtra_postcrit; if (weapon) total_dam = (total_dam * old_blows) / 100; else total_dam *= p_ptr->state.num_shots; if (total_dam <= 0) textblock_append_c(tb, TERM_L_RED, "%d", 0); else if (total_dam % 10) textblock_append_c(tb, TERM_L_GREEN, "%d.%d", total_dam / 10, total_dam % 10); else textblock_append_c(tb, TERM_L_GREEN, "%d", total_dam / 10); textblock_append(tb, " vs. %s, ", desc[i]); } if (cnt) textblock_append(tb, "and "); /* Include bonus damage in stated average */ total_dam = dam * multiplier + xtra_precrit; total_dam = (total_dam * crit_mult + crit_add) / crit_div; total_dam += xtra_postcrit; /* Normal damage, not considering brands or slays */ if (weapon) total_dam = (total_dam * old_blows) / 100; else total_dam *= p_ptr->state.num_shots; if (total_dam <= 0) textblock_append_c(tb, TERM_L_RED, "%d", 0); else if (total_dam % 10) textblock_append_c(tb, TERM_L_GREEN, "%d.%d", total_dam / 10, total_dam % 10); else textblock_append_c(tb, TERM_L_GREEN, "%d", total_dam / 10); if (cnt) textblock_append(tb, " vs. others"); textblock_append(tb, ".\n"); return TRUE; }
/* * Describe combat advantages. */ static bool describe_combat(textblock *tb, const object_type *o_ptr, oinfo_detail_t mode) { bool full = mode & OINFO_FULL; object_type *bow = &p_ptr->inventory[INVEN_BOW]; bitflag f[OF_SIZE]; bool weapon = (wield_slot(o_ptr) == INVEN_WIELD); bool ammo = (p_ptr->state.ammo_tval == o_ptr->tval) && (bow->kind); /* The player's hypothetical state, were they to wield this item */ player_state state; /* Abort if we've nothing to say */ if (mode & OINFO_DUMMY) return FALSE; if (!weapon && !ammo) { /* Potions can have special text */ if (o_ptr->tval != TV_POTION || o_ptr->dd == 0 || o_ptr->ds == 0 || !object_flavor_is_aware(o_ptr)) return FALSE; textblock_append(tb, "It can be thrown at creatures with damaging effect.\n"); return TRUE; } if (full) object_flags(o_ptr, f); else object_flags_known(o_ptr, f); textblock_append_c(tb, TERM_L_WHITE, "Combat info:\n"); if (weapon) { object_type inven[INVEN_TOTAL]; memcpy(inven, p_ptr->inventory, INVEN_TOTAL * sizeof(object_type)); inven[INVEN_WIELD] = *o_ptr; if (full) object_know_all_flags(&inven[INVEN_WIELD]); /* Calculate the player's hypothetical state */ calc_bonuses(inven, &state, TRUE); /* Warn about heavy weapons */ if (adj_str_hold[state.stat_ind[A_STR]] < o_ptr->weight / 10) textblock_append_c(tb, TERM_L_RED, "You are too weak to use this weapon.\n"); /* Describe blows */ describe_blows(tb, o_ptr, state, f); } else { /* Ammo */ /* Range of the weapon */ int tdis = 6 + 2 * p_ptr->state.ammo_mult; /* Output the range */ textblock_append(tb, "Hits targets up to "); textblock_append_c(tb, TERM_L_GREEN, format("%d", tdis * 10)); textblock_append(tb, " feet away.\n"); } /* Describe damage */ describe_damage(tb, o_ptr, state, f, mode); /* Note the impact flag */ if (of_has(f, OF_IMPACT)) textblock_append(tb, "Sometimes creates earthquakes on impact.\n"); /* Add breakage chance */ if (ammo) { int chance = breakage_chance(o_ptr, TRUE); textblock_append_c(tb, TERM_L_GREEN, "%d%%", chance); textblock_append(tb, " chance of breaking upon contact.\n"); } /* Something has been said */ return TRUE; }
/** * Format a section of the monster list: a header followed by monster list * entry rows. * * This function will process each entry for the given section. It will display: * - monster char; * - number of monsters; * - monster name (truncated, if needed to fit the line); * - whether or not the monster is asleep (and how many if in a group); * - monster distance from the player (aligned to the right side of the list). * By passing in a NULL textblock, the maximum line width of the section can be * found. * * \param list is the monster list to format. * \param tb is the textblock to produce or NULL if only the dimensions need to * be calculated. * \param section is the section of the monster list to format. * \param lines_to_display are the number of entries to display (not including * the header). * \param max_width is the maximum line width. * \param prefix is the beginning of the header; the remainder is appended with * the number of monsters. * \param show_others is used to append "other monsters" to the header, * after the number of monsters. * \param max_width_result is returned with the width needed to format the list * without truncation. */ static void monster_list_format_section(const monster_list_t *list, textblock *tb, monster_list_section_t section, int lines_to_display, int max_width, const char *prefix, bool show_others, size_t *max_width_result) { int remaining_monster_total = 0; int line_count = 0; int index; int total; char line_buffer[200]; const char *punctuation = (lines_to_display == 0) ? "." : ":"; const char *others = (show_others) ? "other " : ""; size_t max_line_length = 0; if (list == NULL || list->entries == NULL) return; total = list->distinct_entries; if (list->total_monsters[section] == 0) { max_line_length = strnfmt(line_buffer, sizeof(line_buffer), "%s no monsters.\n", prefix); if (tb != NULL) textblock_append(tb, "%s", line_buffer); /* Force a minimum width so that the prompt doesn't get cut off. */ if (max_width_result != NULL) *max_width_result = MAX(max_line_length, 40); return; } max_line_length = strnfmt(line_buffer, sizeof(line_buffer), "%s %d %smonster%s%s\n", prefix, list->total_monsters[section], others, PLURAL(list->total_monsters[section]), punctuation); if (tb != NULL) textblock_append(tb, "%s", line_buffer); for (index = 0; index < total && line_count < lines_to_display; index++) { char asleep[20] = { '\0' }; char location[20] = { '\0' }; byte line_attr; size_t full_width; size_t name_width; line_buffer[0] = '\0'; if (list->entries[index].count[section] == 0) continue; /* Only display directions for the case of a single monster. */ if (list->entries[index].count[section] == 1) { const char *direction1 = (list->entries[index].dy <= 0) ? "N" : "S"; const char *direction2 = (list->entries[index].dx <= 0) ? "W" : "E"; strnfmt(location, sizeof(location), " %d %s %d %s", abs(list->entries[index].dy), direction1, abs(list->entries[index].dx), direction2); } /* Get width available for monster name and sleep tag: 2 for char and * space; location includes padding; last -1 for some reason? */ full_width = max_width - 2 - utf8_strlen(location) - 1; if (list->entries[index].asleep[section] > 1) strnfmt(asleep, sizeof(asleep), " (%d asleep)", list->entries[index].asleep[section]); else if (list->entries[index].asleep[section] == 1) strnfmt(asleep, sizeof(asleep), " (asleep)"); /* Clip the monster name to fit, and append the sleep tag. */ name_width = MIN(full_width - utf8_strlen(asleep), sizeof(line_buffer)); get_mon_name(line_buffer, sizeof(line_buffer), list->entries[index].race, list->entries[index].count[section]); utf8_clipto(line_buffer, name_width); my_strcat(line_buffer, asleep, sizeof(line_buffer)); /* Calculate the width of the line for dynamic sizing; use a fixed max * width for location and monster char. */ max_line_length = MAX(max_line_length, utf8_strlen(line_buffer) + 12 + 2); /* textblock_append_pict will safely add the monster symbol, * regardless of ASCII/graphics mode. */ if (tb != NULL && tile_width == 1 && tile_height == 1) { textblock_append_pict(tb, list->entries[index].attr, monster_x_char[list->entries[index].race->ridx]); textblock_append(tb, " "); } /* Add the left-aligned and padded monster name which will align the * location to the right. */ if (tb != NULL) { /* Hack - Because monster race strings are UTF8, we have to add * additional padding for any raw bytes that might be consolidated * into one displayed character. */ full_width += strlen(line_buffer) - utf8_strlen(line_buffer); line_attr = monster_list_entry_line_color(&list->entries[index]); textblock_append_c(tb, line_attr, "%-*s%s\n", full_width, line_buffer, location); } line_count++; } /* Don't worry about the "...others" line, since it's probably shorter * than what's already printed. */ if (max_width_result != NULL) *max_width_result = max_line_length; /* Bail since we don't have enough room to display the remaining count or * since we've displayed them all. */ if (lines_to_display <= 0 || lines_to_display >= list->total_entries[section]) return; /* Sum the remaining monsters; start where we left off in the above loop. */ while (index < total) { remaining_monster_total += list->entries[index].count[section]; index++; } if (tb != NULL) textblock_append(tb, "%6s...and %d others.\n", " ", remaining_monster_total); }
/* * Describe blows. */ static bool describe_blows(textblock *tb, const object_type *o_ptr, player_state state, bitflag f[OF_SIZE]) { int str_plus, dex_plus, old_blows = 0, new_blows, extra_blows; int str_faster = -1, str_done = -1; int dex_plus_bound; int str_plus_bound; int i; bitflag tmp_f[OF_SIZE]; dex_plus_bound = STAT_RANGE - state.stat_ind[A_DEX]; str_plus_bound = STAT_RANGE - state.stat_ind[A_STR]; /* Write to the text block */ textblock_append_c(tb, TERM_L_GREEN, "%d.%d ", state.num_blows / 100, (state.num_blows / 10) % 10); textblock_append(tb, "blow%s/round.\n", (state.num_blows > 100) ? "s" : ""); /* Check to see if extra STR or DEX would yield extra blows */ old_blows = state.num_blows; extra_blows = 0; /* First we need to look for extra blows on other items, as * state does not track these */ for (i = INVEN_BOW; i < INVEN_TOTAL; i++) { if (!p_ptr->inventory[i].kind) continue; object_flags_known(&p_ptr->inventory[i], tmp_f); if (of_has(tmp_f, OF_BLOWS)) extra_blows += p_ptr->inventory[i].pval[which_pval(&p_ptr->inventory[i], OF_BLOWS)]; } /* Then we add blows from the weapon being examined */ if (of_has(f, OF_BLOWS)) extra_blows += o_ptr->pval[which_pval(o_ptr, OF_BLOWS)]; /* Then we check for extra "real" blows */ for (dex_plus = 0; dex_plus < dex_plus_bound; dex_plus++) { for (str_plus = 0; str_plus < str_plus_bound; str_plus++) { state.stat_ind[A_STR] += str_plus; state.stat_ind[A_DEX] += dex_plus; new_blows = calc_blows(o_ptr, &state, extra_blows); /* Test to make sure that this extra blow is a * new str/dex combination, not a repeat */ if ((new_blows - new_blows % 10) > (old_blows - old_blows % 10) && (str_plus < str_done || str_done == -1)) { textblock_append(tb, "With +%d STR and +%d DEX you would get %d.%d blows\n", str_plus, dex_plus, (new_blows / 100), (new_blows / 10) % 10); state.stat_ind[A_STR] -= str_plus; state.stat_ind[A_DEX] -= dex_plus; str_done = str_plus; break; } /* If the combination doesn't increment * the displayed blows number, it might still * take a little less energy */ if (new_blows > old_blows && (str_plus < str_faster || str_faster == -1) && (str_plus < str_done || str_done == -1)) { textblock_append(tb, "With +%d STR and +%d DEX you would attack a bit faster\n", str_plus, dex_plus); state.stat_ind[A_STR] -= str_plus; state.stat_ind[A_DEX] -= dex_plus; str_faster = str_plus; continue; } state.stat_ind[A_STR] -= str_plus; state.stat_ind[A_DEX] -= dex_plus; } } return TRUE; }
/** * Describe damage. */ static bool describe_damage(textblock *tb, const struct object *obj) { bool nonweap_slay = false; int normal_damage; struct brand *brand, *brands = NULL; struct slay *slay, *slays = NULL; bool has_brands_or_slays; /* Collect brands and slays */ has_brands_or_slays = obj_known_damage(obj, &normal_damage, &brands, &slays, &nonweap_slay); /* Mention slays and brands from other items */ if (nonweap_slay) textblock_append(tb, "This weapon may benefit from one or more off-weapon brands or slays.\n"); textblock_append(tb, "Average damage/round: "); /* Output damage for creatures effected by the brands */ brand = brands; while (brand) { if (brand->damage <= 0) textblock_append_c(tb, COLOUR_L_RED, "%d", 0); else if (brand->damage % 10) textblock_append_c(tb, COLOUR_L_GREEN, "%d.%d", brand->damage / 10, brand->damage % 10); else textblock_append_c(tb, COLOUR_L_GREEN, "%d",brand->damage / 10); textblock_append(tb, " vs. creatures not resistant to %s, ", brand->name); brand = brand->next; } /* Output damage for creatures effected by the slays */ slay = slays; while (slay) { if (slay->damage <= 0) textblock_append_c(tb, COLOUR_L_RED, "%d", 0); else if (slay->damage % 10) textblock_append_c(tb, COLOUR_L_GREEN, "%d.%d", slay->damage / 10, slay->damage % 10); else textblock_append_c(tb, COLOUR_L_GREEN, "%d", slay->damage / 10); textblock_append(tb, " vs. %s, ", slay->name); slay = slay->next; } if (has_brands_or_slays) textblock_append(tb, "and "); if (normal_damage <= 0) textblock_append_c(tb, COLOUR_L_RED, "%d", 0); else if (normal_damage % 10) textblock_append_c(tb, COLOUR_L_GREEN, "%d.%d", normal_damage / 10, normal_damage % 10); else textblock_append_c(tb, COLOUR_L_GREEN, "%d", normal_damage / 10); if (has_brands_or_slays) textblock_append(tb, " vs. others"); textblock_append(tb, ".\n"); free_brand(brands); free_slay(slays); return true; }
/** * Outputs the damage we do/would do with the weapon */ static bool describe_weapon_damage(textblock * tb, const object_type * o_ptr, oinfo_detail_t mode) { object_type *i_ptr; int i, j; bool full = mode & OINFO_FULL; bool terse = mode & OINFO_TERSE; bool first = TRUE; int show_m_tohit; int brand[MAX_P_BRAND], slay[MAX_P_SLAY]; player_state state; object_type inven[INVEN_TOTAL]; /* Abort if we've nothing to say */ if (mode & OINFO_DUMMY) return FALSE; /* Extract the slays and brands */ for (j = 0; j < MAX_P_SLAY; j++) slay[j] = (if_has(o_ptr->id_other, OBJECT_ID_BASE_SLAY + j) || full) ? o_ptr->multiple_slay[j] : MULTIPLE_BASE; for (j = 0; j < MAX_P_BRAND; j++) brand[j] = (if_has(o_ptr->id_other, OBJECT_ID_BASE_BRAND + j) || full) ? o_ptr->multiple_brand[j] : MULTIPLE_BASE; /* Check rings for additional brands (slays) */ for (i = 0; i < 2; i++) { i_ptr = &p_ptr->inventory[INVEN_LEFT + i]; /* If wearing a ring */ if (i_ptr->k_idx) { /* Pick up any brands (and slays!) */ for (j = 0; j < MAX_P_SLAY; j++) slay[j] = MAX(slay[j], ((if_has(i_ptr->id_other, OBJECT_ID_BASE_SLAY + j) || full) ? i_ptr->multiple_slay[j] : MULTIPLE_BASE)); for (j = 0; j < MAX_P_BRAND; j++) brand[j] = MAX(brand[j], ((if_has(i_ptr->id_other, OBJECT_ID_BASE_BRAND + j) || full) ? i_ptr->multiple_brand[j] : MULTIPLE_BASE)); } } /* temporary elemental brands */ if (p_ptr->special_attack & (ATTACK_ACID)) brand[P_BRAND_ACID] = MAX(brand[P_BRAND_ACID], BRAND_BOOST_NORMAL); if (p_ptr->special_attack & (ATTACK_ELEC)) brand[P_BRAND_ELEC] = MAX(brand[P_BRAND_ELEC], BRAND_BOOST_NORMAL); if (p_ptr->special_attack & (ATTACK_FIRE)) brand[P_BRAND_FIRE] = MAX(brand[P_BRAND_FIRE], BRAND_BOOST_NORMAL); if (p_ptr->special_attack & (ATTACK_COLD)) brand[P_BRAND_COLD] = MAX(brand[P_BRAND_COLD], BRAND_BOOST_NORMAL); if (p_ptr->special_attack & (ATTACK_POIS)) brand[P_BRAND_POIS] = MAX(brand[P_BRAND_POIS], BRAND_BOOST_NORMAL); if (p_ptr->special_attack & (ATTACK_HOLY)) slay[P_SLAY_EVIL] = MAX(slay[P_SLAY_EVIL], SLAY_BOOST_SMALL); /* * Get the player's hypothetical state, were they to be * wielding this item (setting irrelevant shield state). */ memcpy(inven, p_ptr->inventory, INVEN_TOTAL * sizeof(object_type)); inven[INVEN_WIELD] = *o_ptr; state.shield_on_back = FALSE; calc_bonuses(inven, &state, TRUE); show_m_tohit = state.dis_to_h; if (if_has(o_ptr->id_other, IF_TO_H) || full) show_m_tohit += o_ptr->to_h; if (terse) { textblock_append_c(tb, TERM_L_GREEN, "%d ", state.num_blow); textblock_append(tb, "blow%s av. dam. ", (state.num_blow) ? "s" : ""); } else { textblock_append(tb, "\nWielding it you would have "); textblock_append_c(tb, TERM_L_GREEN, "%d ", state.num_blow); textblock_append(tb, "blow%s and do an average damage per blow of ", (state.num_blow) ? "s" : ""); } for (i = 0; i < MAX_P_SLAY; i++) { if (slay[i] > MULTIPLE_BASE) output_dam(tb, &state, o_ptr, slay[i], slayee[i], &first, mode); } for (i = 0; i < MAX_P_BRAND; i++) { if (brand[i] > MULTIPLE_BASE) { char buf[40]; strnfmt(buf, sizeof(buf), "non %s resistant creatures", brandee[i]); output_dam(tb, &state, o_ptr, brand[i], buf, &first, mode); } } output_dam(tb, &state, o_ptr, MULTIPLE_BASE, (first) ? "all monsters" : "other monsters", &first, mode); if (terse) { textblock_append(tb, ". + "); textblock_append_c(tb, TERM_L_GREEN, "%d", show_m_tohit); textblock_append(tb, " to skill. "); } else { textblock_append(tb, ". Your + to Skill would be "); textblock_append_c(tb, TERM_L_GREEN, "%d", show_m_tohit); textblock_append(tb, ". "); } return TRUE; }
/* * Describe immunities granted by an object. */ static bool describe_immune(textblock * tb, const object_type * o_ptr, oinfo_detail_t mode) { int res = 0, imm = 0, vul = 0, j; bool full = mode & OINFO_FULL; bool dummy = mode & OINFO_DUMMY; bool terse = mode & OINFO_TERSE; bool prev = FALSE; /* Check for resists and vulnerabilities */ for (j = 0; j < MAX_P_RES; j++) { if (!if_has(o_ptr->id_other, OBJECT_ID_BASE_RESIST + j) && !full) continue; if (o_ptr->percent_res[j] == RES_BOOST_IMMUNE) imm++; else if (o_ptr->percent_res[j] < RES_LEVEL_BASE) { res++; } else if (o_ptr->percent_res[j] > RES_LEVEL_BASE) vul++; } /* Immunities */ if (imm) { textblock_append(tb, "Provides "); textblock_append_c(tb, TERM_BLUE, "immunity "); textblock_append(tb, "to "); /* Loop for number of attributes in this group. */ for (j = 0; j < 4; j++) { if (o_ptr->percent_res[j] > RES_BOOST_IMMUNE) continue; if (!if_has(o_ptr->id_other, OBJECT_ID_BASE_RESIST + j) && !full) continue; /* List the attribute description, in its proper place. */ if (terse) textblock_append(tb, resists[j].name); else textblock_append_c(tb, resists[j].attr, resists[j].name); if (imm >= (terse ? 2 : 3)) textblock_append(tb, ", "); if ((imm == 2) && !terse) textblock_append(tb, " and "); imm--; } /* End sentence. */ textblock_append(tb, ". "); prev = TRUE; } /* Resistances */ if (res) { textblock_append(tb, "Provides "); textblock_append_c(tb, TERM_L_BLUE, "resistance "); textblock_append(tb, "to "); /* Loop for number of attributes in this group. */ for (j = 0; j < MAX_P_RES; j++) { if (o_ptr->percent_res[j] >= RES_LEVEL_BASE) continue; if (o_ptr->percent_res[j] == RES_BOOST_IMMUNE) continue; if (!if_has(o_ptr->id_other, OBJECT_ID_BASE_RESIST + j) && !full) continue; /* List the attribute description, in its proper place. */ if (terse) textblock_append(tb, resists[j].name); else textblock_append_c(tb, resists[j].attr, resists[j].name); textblock_append(tb, "("); if (dummy) textblock_append(tb, "about "); textblock_append(tb, "%d%%)", RES_LEVEL_BASE - o_ptr->percent_res[j]); if (res >= (terse ? 2 : 3)) textblock_append(tb, ", "); if ((res == 2) && !terse) textblock_append(tb, " and "); res--; } /* End sentence. */ textblock_append(tb, ". "); prev = TRUE; } /* Vulnerabilities */ if (vul) { textblock_append(tb, "Makes you "); textblock_append_c(tb, TERM_ORANGE, "vulnerable "); textblock_append(tb, "to "); /* Loop for number of attributes in this group. */ for (j = 0; j < MAX_P_RES; j++) { if (o_ptr->percent_res[j] <= RES_LEVEL_BASE) continue; if (!if_has(o_ptr->id_other, OBJECT_ID_BASE_RESIST + j)) continue; /* List the attribute description, in its proper place. */ if (terse) textblock_append(tb, resists[j].name); else textblock_append_c(tb, resists[j].attr, resists[j].name); textblock_append(tb, "("); if (dummy) textblock_append(tb, "about "); textblock_append(tb, "%d%%)", o_ptr->percent_res[j] - RES_LEVEL_BASE); if (vul >= (terse ? 2 : 3)) textblock_append(tb, ", "); if ((vul == 2) && !terse) textblock_append(tb, " and "); vul--; } /* End sentence. */ textblock_append(tb, ". "); prev = TRUE; } return prev; }
/** * Display the ammo damage done with a multiplier */ static void output_ammo_dam(textblock * tb, player_state * state, const object_type * o_ptr, int mult, const char *against, bool * first, bool perfect, oinfo_detail_t mode) { object_type *b_ptr = &p_ptr->inventory[INVEN_BOW]; int dam, die_average, add = 0, i, deadliness, crit, chance, dice = o_ptr->dd; /* Throwing weapon, or launched missile? */ bool thrown = !is_missile(o_ptr); bool full = mode & OINFO_FULL; /* Average damage for one standard side (x10) */ die_average = (10 * (o_ptr->ds + 1)) / 2; /* Apply the launcher multiplier. */ if (!thrown) die_average *= p_ptr->state.ammo_mult; /* Multiply by slay or brand (x10) */ die_average *= mult; /* Record the addend */ if (mult > 10) add = (mult - 10); /* Multiply by deadliness (x100) */ deadliness = state->dis_to_d; if (if_has(o_ptr->id_other, IF_TO_D) || full) deadliness += o_ptr->to_d; if (if_has(b_ptr->id_other, IF_TO_D) || full) deadliness += b_ptr->to_d; if (deadliness > 150) deadliness = 150; if (deadliness < -150) deadliness = -150; if (deadliness >= 0) die_average *= (100 + deadliness_conversion[deadliness]); else { i = deadliness_conversion[ABS(deadliness)]; if (i >= 100) die_average = 0; else die_average *= (100 - i); } /* Get critical chance (x 10) */ chance = state->skills[SKILL_TO_HIT_BOW] + state->dis_to_h; if (if_has(o_ptr->id_other, IF_TO_H) || full) chance += o_ptr->to_h; if ((!thrown) && (if_has(b_ptr->id_other, IF_TO_H) || full)) chance += b_ptr->to_h; if (thrown) chance = chance * 3 / 2; chance = (100 * chance) / (chance + 360); if (player_has(PF_MARKSMAN)) chance = 100 - (83 * (100 - chance)) / 100; crit = 116 * chance; crit /= 1000; /* Increase dice */ if (thrown && perfect) dice *= 2; dice = dice * 10 + crit; if (thrown) dice *= 2 + p_ptr->lev / 12; /* Multiply by number of sides */ dam = die_average * dice; CHECK_FIRST("", *first); if ((dam > 50000) || (add > 0)) textblock_append_c(tb, TERM_L_GREEN, "%d", add + dam / 100000); else textblock_append_c(tb, TERM_L_RED, "0"); textblock_append(tb, " against %s", against); }
/* * Describe an object's effect, if any. */ static bool describe_effect(textblock * tb, const object_type * o_ptr, oinfo_detail_t mode) { const object_kind *k_ptr = &k_info[o_ptr->k_idx]; const char *desc; random_value timeout = { 0, 0, 0, 0 }; bool full = mode & OINFO_FULL; bool subjective = mode & OINFO_SUBJ; bool terse = mode & OINFO_TERSE; int effect = 0, fail; if (wearable_p(o_ptr)) { /* Wearable + effect <=> activates */ if ((o_ptr->ident & IDENT_WORN) || full) { effect = o_ptr->effect; timeout = o_ptr->time; } else if (object_effect(o_ptr)) { textblock_append(tb, "It can be activated.\n"); return TRUE; } } else { /* Sometimes only print activation info */ if (terse) return FALSE; if ((object_aware_p(o_ptr) && kf_has(k_ptr->flags_kind, KF_EASY_KNOW)) || full) { effect = o_ptr->effect; timeout = o_ptr->time; } else if (object_effect(o_ptr)) { if (effect_aim(k_ptr->effect)) textblock_append(tb, "It can be aimed.\n"); else if (o_ptr->tval == TV_FOOD) textblock_append(tb, "It can be eaten.\n"); else if (o_ptr->tval == TV_POTION) textblock_append(tb, "It can be drunk.\n"); else if (o_ptr->tval == TV_SCROLL) textblock_append(tb, "It can be read.\n"); else textblock_append(tb, "It can be activated.\n"); return TRUE; } } /* Forget it without an effect */ if (!effect) return FALSE; /* Obtain the description */ desc = effect_desc(effect); if (!desc) return FALSE; if (effect_aim(effect)) textblock_append(tb, "When aimed, it "); else if (o_ptr->tval == TV_FOOD) textblock_append(tb, "When eaten, it "); else if (o_ptr->tval == TV_POTION) textblock_append(tb, "When drunk, it "); else if (o_ptr->tval == TV_SCROLL) textblock_append(tb, "When read, it "); else textblock_append(tb, "When activated, it "); /* Print a colourised description */ do { if (isdigit((unsigned char) *desc)) textblock_append_c(tb, TERM_L_GREEN, "%c", *desc); else textblock_append(tb, "%c", *desc); } while (*desc++); textblock_append(tb, ".\n"); if (randcalc(timeout, 0, MAXIMISE) > 0) { int min_time, max_time; /* Sometimes adjust for player speed */ int multiplier = extract_energy[p_ptr->state.pspeed]; if (!subjective) multiplier = 10; textblock_append(tb, "Takes "); /* Correct for player speed */ min_time = randcalc(timeout, 0, MINIMISE) * multiplier / 10; max_time = randcalc(timeout, 0, MAXIMISE) * multiplier / 10; textblock_append_c(tb, TERM_L_GREEN, "%d", min_time); if (min_time != max_time) { textblock_append(tb, " to "); textblock_append_c(tb, TERM_L_GREEN, "%d", max_time); } textblock_append(tb, " turns to recharge"); if (subjective && p_ptr->state.pspeed != 110) textblock_append(tb, " at your current speed"); textblock_append(tb, ".\n"); } if (!subjective || o_ptr->tval == TV_FOOD || o_ptr->tval == TV_POTION || o_ptr->tval == TV_SCROLL) { return TRUE; } else { fail = get_use_device_chance(o_ptr); textblock_append(tb, "Your chance of success is %d.%d%%\n", (1000 - fail) / 10, (1000 - fail) % 10); } return TRUE; }
/* * Describe an object's effect, if any. */ static bool describe_effect(textblock *tb, const object_type *o_ptr, bool full, bool only_artifacts, bool subjective) { const char *desc; random_value timeout = {0, 0, 0, 0}; int effect = 0, fail; if (o_ptr->artifact) { if (object_effect_is_known(o_ptr) || full) { effect = o_ptr->artifact->effect; timeout = o_ptr->artifact->time; } else if (object_effect(o_ptr)) { textblock_append(tb, "It can be activated.\n"); return TRUE; } } else { /* Sometimes only print artifact activation info */ if (only_artifacts == TRUE) return FALSE; if (object_effect_is_known(o_ptr) || full) { effect = o_ptr->kind->effect; timeout = o_ptr->kind->time; } else if (object_effect(o_ptr) != 0) { if (effect_aim(o_ptr->kind->effect)) textblock_append(tb, "It can be aimed.\n"); else if (o_ptr->tval == TV_FOOD) textblock_append(tb, "It can be eaten.\n"); else if (o_ptr->tval == TV_POTION) textblock_append(tb, "It can be drunk.\n"); else if (o_ptr->tval == TV_SCROLL) textblock_append(tb, "It can be read.\n"); else textblock_append(tb, "It can be activated.\n"); return TRUE; } } /* Forget it without an effect */ if (!effect) return FALSE; /* Obtain the description */ desc = effect_desc(effect); if (!desc) return FALSE; if (effect_aim(effect)) textblock_append(tb, "When aimed, it "); else if (o_ptr->tval == TV_FOOD) textblock_append(tb, "When eaten, it "); else if (o_ptr->tval == TV_POTION) textblock_append(tb, "When drunk, it "); else if (o_ptr->tval == TV_SCROLL) textblock_append(tb, "When read, it "); else textblock_append(tb, "When activated, it "); /* Print a colourised description */ do { if (isdigit((unsigned char) *desc)) textblock_append_c(tb, TERM_L_GREEN, "%c", *desc); else textblock_append(tb, "%c", *desc); } while (*desc++); textblock_append(tb, ".\n"); if (randcalc(timeout, 0, MAXIMISE) > 0) { int min_time, max_time; /* Sometimes adjust for player speed */ int multiplier = extract_energy[p_ptr->state.speed]; if (!subjective) multiplier = 10; textblock_append(tb, "Takes "); /* Correct for player speed */ min_time = randcalc(timeout, 0, MINIMISE) * multiplier / 10; max_time = randcalc(timeout, 0, MAXIMISE) * multiplier / 10; textblock_append_c(tb, TERM_L_GREEN, "%d", min_time); if (min_time != max_time) { textblock_append(tb, " to "); textblock_append_c(tb, TERM_L_GREEN, "%d", max_time); } textblock_append(tb, " turns to recharge"); if (subjective && p_ptr->state.speed != 110) textblock_append(tb, " at your current speed"); textblock_append(tb, ".\n"); } if (!subjective || o_ptr->tval == TV_FOOD || o_ptr->tval == TV_POTION || o_ptr->tval == TV_SCROLL) { return TRUE; } else { fail = get_use_device_chance(o_ptr); textblock_append(tb, "Your chance of success is %d.%d%%\n", (1000 - fail) / 10, (1000 - fail) % 10); } return TRUE; }
/* * Describe slays. */ static bool describe_slays(textblock * tb, const object_type * o_ptr, oinfo_detail_t mode) { int j, slay = 0, kill = 0; bool full = mode & OINFO_FULL; bool terse = mode & OINFO_TERSE; byte attr = TERM_RED; for (j = 0; j < MAX_P_SLAY; j++) if (if_has(o_ptr->id_other, OBJECT_ID_BASE_SLAY + j) || (full && (o_ptr->multiple_slay[j] > MULTIPLE_BASE))) { slay++; /* Hack for great banes */ if ((j == P_SLAY_ANIMAL) && (o_ptr->multiple_slay[j] > SLAY_BOOST_MINOR)) { slay--; kill++; } if ((j == P_SLAY_EVIL) && (o_ptr->multiple_slay[j] > SLAY_BOOST_SMALL)) { slay--; kill++; } if ((j > P_SLAY_EVIL) && (o_ptr->multiple_slay[j] > SLAY_BOOST_NORMAL)) { slay--; kill++; } } if (!slay && !kill) return FALSE; if (slay > 0) { textblock_append(tb, "It slays "); /* Slays */ for (j = 0; j < MAX_P_SLAY; j++) { if (!if_has(o_ptr->id_other, OBJECT_ID_BASE_SLAY + j) && !(full && (o_ptr->multiple_slay[j] > MULTIPLE_BASE))) continue; if ((j == P_SLAY_ANIMAL) && (o_ptr->multiple_slay[j] > SLAY_BOOST_MINOR)) continue; if ((j == P_SLAY_EVIL) && (o_ptr->multiple_slay[j] > SLAY_BOOST_SMALL)) continue; if ((j > P_SLAY_EVIL) && (o_ptr->multiple_slay[j] > SLAY_BOOST_NORMAL)) continue; textblock_append_c(tb, attr, slayee[j]); if (slay >= 3) textblock_append(tb, ", "); if (slay == 2) textblock_append(tb, " and "); if (slay == 1) textblock_append(tb, ". "); slay--; } } if (kill > 0) { if (terse) textblock_append(tb, "Great bane of "); else textblock_append(tb, "It is a great bane of "); /* Great banes */ for (j = 0; j < MAX_P_SLAY; j++) { if (!if_has(o_ptr->id_other, OBJECT_ID_BASE_SLAY + j) && !(full && (o_ptr->multiple_slay[j] > MULTIPLE_BASE))) continue; if ((j == P_SLAY_ANIMAL) && (o_ptr->multiple_slay[j] <= SLAY_BOOST_MINOR)) continue; if ((j == P_SLAY_EVIL) && (o_ptr->multiple_slay[j] <= SLAY_BOOST_SMALL)) continue; if ((j > P_SLAY_EVIL) && (o_ptr->multiple_slay[j] <= SLAY_BOOST_NORMAL)) continue; textblock_append_c(tb, attr, slayee[j]); if (slay >= 3) textblock_append(tb, ", "); if (slay == 2) textblock_append(tb, " and "); if (slay == 1) textblock_append(tb, ". "); slay--; } } textblock_append(tb, "\n"); return TRUE; }