/** * Gives the known effects of using the given item. * * Fills in: * - the effect * - whether the effect can be aimed * - the minimum and maximum time in game turns for the item to recharge * (or zero if it does not recharge) * - the percentage chance of the effect failing when used * * Return false if the object has no effect. */ static bool obj_known_effect(const struct object *obj, struct effect **effect, bool *aimed, int *min_recharge, int *max_recharge, int *failure_chance) { random_value timeout = {0, 0, 0, 0}; *effect = 0; *min_recharge = 0; *max_recharge = 0; *failure_chance = 0; *aimed = false; if (object_effect_is_known(obj)) { *effect = object_effect(obj); timeout = obj->time; if (effect_aim(*effect)) *aimed = true;; } else if (object_effect(obj)) { /* Don't know much - be vague */ *effect = NULL; if (!obj->artifact && effect_aim(object_effect(obj))) *aimed = true; return true; } else { /* No effect - no info */ return false; } if (randcalc(timeout, 0, MAXIMISE) > 0) { *min_recharge = randcalc(timeout, 0, MINIMISE); *max_recharge = randcalc(timeout, 0, MAXIMISE); } if (tval_is_edible(obj) || tval_is_potion(obj) || tval_is_scroll(obj)) { *failure_chance = 0; } else { *failure_chance = get_use_device_chance(obj); } return true; }
/** * Gives the known effects of using the given item. * * Fills in: * - the effect id, or OBJ_KNOWN_PRESENT if there is an effect but details * are unknown * - whether the effect can be aimed * - the minimum and maximum time in game turns for the item to recharge * (or zero if it does not recharge) * - the percentage chance of the effect failing when used * * Return FALSE if the object has no effect. */ static bool obj_known_effect(const struct object *obj, int *effect, bool *aimed, int *min_recharge, int *max_recharge, int *failure_chance) { random_value timeout = {0, 0, 0, 0}; *effect = 0; *min_recharge = 0; *max_recharge = 0; *failure_chance = 0; *aimed = FALSE; if (object_effect_is_known(obj)) { *effect = object_effect(obj); timeout = obj->time; } else if (object_effect(obj)) { /* Don't know much - be vague */ *effect = OBJ_KNOWN_PRESENT; if (!obj->artifact && effect_aim(obj->effect)) *aimed = TRUE; return TRUE; } else { /* No effect - no info */ return FALSE; } if (randcalc(timeout, 0, MAXIMISE) > 0) { *min_recharge = randcalc(timeout, 0, MINIMISE); *max_recharge = randcalc(timeout, 0, MAXIMISE); } if (tval_is_food(obj) || tval_is_potion(obj) || tval_is_scroll(obj)) { *failure_chance = 0; } else { *failure_chance = get_use_device_chance(obj); } return TRUE; }
/** * Determine if an item can "absorb" a second item * * See "object_absorb()" for the actual "absorption" code. * * If permitted, we allow weapons/armor to stack, if "known". * * Missiles will combine if both stacks have the same "known" status. * This is done to make unidentified stacks of missiles useful. * * Food, potions, scrolls, and "easy know" items always stack. * * Chests, and activatable items, except rods, never stack (for various * reasons). */ bool object_stackable(const struct object *obj1, const struct object *obj2, object_stack_t mode) { int i; /* Equipment items don't stack */ if (object_is_equipped(player->body, obj1)) return false; if (object_is_equipped(player->body, obj2)) return false; /* If either item is unknown, do not stack */ if (mode & OSTACK_LIST && obj1->kind != obj1->known->kind) return false; if (mode & OSTACK_LIST && obj2->kind != obj2->known->kind) return false; /* Hack -- identical items cannot be stacked */ if (obj1 == obj2) return false; /* Require identical object kinds */ if (obj1->kind != obj2->kind) return false; /* Different flags don't stack */ if (!of_is_equal(obj1->flags, obj2->flags)) return false; /* Different elements don't stack */ for (i = 0; i < ELEM_MAX; i++) { if (obj1->el_info[i].res_level != obj2->el_info[i].res_level) return false; if ((obj1->el_info[i].flags & (EL_INFO_HATES | EL_INFO_IGNORE)) != (obj2->el_info[i].flags & (EL_INFO_HATES | EL_INFO_IGNORE))) return false; } /* Artifacts never stack */ if (obj1->artifact || obj2->artifact) return false; /* Analyze the items */ if (tval_is_chest(obj1)) { /* Chests never stack */ return false; } else if (tval_is_edible(obj1) || tval_is_potion(obj1) || tval_is_scroll(obj1) || tval_is_rod(obj1)) { /* Food, potions, scrolls and rods all stack nicely, since the kinds are identical, either both will be aware or both will be unaware */ } else if (tval_can_have_charges(obj1) || tval_is_money(obj1)) { /* Gold, staves and wands stack most of the time */ /* Too much gold or too many charges */ if (obj1->pval + obj2->pval > MAX_PVAL) return false; /* ... otherwise ok */ } else if (tval_is_weapon(obj1) || tval_is_armor(obj1) || tval_is_jewelry(obj1) || tval_is_light(obj1)) { bool obj1_is_known = object_fully_known((struct object *)obj1); bool obj2_is_known = object_fully_known((struct object *)obj2); /* Require identical values */ if (obj1->ac != obj2->ac) return false; if (obj1->dd != obj2->dd) return false; if (obj1->ds != obj2->ds) return false; /* Require identical bonuses */ if (obj1->to_h != obj2->to_h) return false; if (obj1->to_d != obj2->to_d) return false; if (obj1->to_a != obj2->to_a) return false; /* Require all identical modifiers */ for (i = 0; i < OBJ_MOD_MAX; i++) if (obj1->modifiers[i] != obj2->modifiers[i]) return (false); /* Require identical ego-item types */ if (obj1->ego != obj2->ego) return false; /* Require identical curses */ if (!curses_are_equal(obj1->curses, obj2->curses)) return false; /* Hack - Never stack recharging wearables ... */ if ((obj1->timeout || obj2->timeout) && !tval_is_light(obj1)) return false; /* ... and lights must have same amount of fuel */ else if ((obj1->timeout != obj2->timeout) && tval_is_light(obj1)) return false; /* Prevent unIDd items stacking with IDd items in the object list */ if (mode & OSTACK_LIST && (obj1_is_known != obj2_is_known)) return false; } else { /* Anything else probably okay */ } /* Require compatible inscriptions */ if (obj1->note && obj2->note && (obj1->note != obj2->note)) return false; /* They must be similar enough */ 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 *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; }
/** * 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; }