/** * Gets miscellaneous combat information about the given object. * * Fills in whether there is a special effect when thrown in `thrown effect`, * the `range` in ft (or zero if not ammo), whether the weapon has the * impact flag set, the percentage chance of breakage and whether it is * too heavy to be weilded effectively at the moment. */ static void obj_known_misc_combat(const struct object *obj, bool *thrown_effect, int *range, bool *impactful, int *break_chance, bool *too_heavy) { 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); bitflag f[OF_SIZE]; *thrown_effect = *impactful = *too_heavy = false; *range = *break_chance = 0; get_known_flags(obj, 0, f); if (!weapon && !ammo) { /* Potions can have special text */ if (tval_is_potion(obj) && obj->dd != 0 && obj->ds != 0 && object_flavor_is_aware(obj)) *thrown_effect = true; } if (ammo) *range = 10 * MIN(6 + 2 * player->state.ammo_mult, z_info->max_range); /* Note the impact flag */ *impactful = of_has(f, OF_IMPACT); /* Add breakage chance */ *break_chance = breakage_chance(obj, true); /* Is the weapon too heavy? */ if (weapon) { struct player_state state; int weapon_slot = slot_by_name(player, "weapon"); struct object *current = equipped_item_by_slot_name(player, "weapon"); /* Pretend we're wielding the object */ player->body.slots[weapon_slot].obj = (struct object *) obj; /* Calculate the player's hypothetical state */ calc_bonuses(player, &state, true, false); /* Stop pretending */ player->body.slots[weapon_slot].obj = current; /* Warn about heavy weapons */ *too_heavy = state.heavy_wield; } }
/** * Front-end command which fires at the nearest target with default ammo. */ void do_cmd_fire_at_nearest(void) { int i, dir = DIR_TARGET; struct object *ammo = NULL; struct object *bow = equipped_item_by_slot_name(player, "shooting"); /* Require a usable launcher */ if (!bow || !player->state.ammo_tval) { msg("You have nothing to fire with."); return; } /* Find first eligible ammo in the quiver */ for (i = 0; i < z_info->quiver_size; i++) { if (!player->upkeep->quiver[i]) continue; if (player->upkeep->quiver[i]->tval != player->state.ammo_tval) continue; ammo = player->upkeep->quiver[i]; break; } /* Require usable ammo */ if (!ammo) { msg("You have no ammunition in the quiver to fire."); return; } /* Require foe */ if (!target_set_closest(TARGET_KILL | TARGET_QUIET)) return; /* Fire! */ cmdq_push(CMD_FIRE); cmd_set_arg_item(cmdq_peek(), "item", ammo); cmd_set_arg_target(cmdq_peek(), "target", dir); }
/** * Helper function used with ranged_helper by do_cmd_fire. */ static struct attack_result make_ranged_shot(struct object *ammo, int y, int x) { char *hit_verb = mem_alloc(20 * sizeof(char)); struct attack_result result = {FALSE, 0, 0, hit_verb}; struct object *bow = equipped_item_by_slot_name(player, "shooting"); struct monster *mon = square_monster(cave, y, x); int chance = chance_of_missile_hit(player, ammo, bow, y, x); int multiplier = player->state.ammo_mult; const struct brand *b = NULL; const struct slay *s = NULL; my_strcpy(hit_verb, "hits", sizeof(hit_verb)); /* Did we hit it (penalize distance travelled) */ if (!test_hit(chance, mon->race->ac, mflag_has(mon->mflag, MFLAG_VISIBLE))) return result; result.success = TRUE; improve_attack_modifier(ammo, mon, &b, &s, result.hit_verb, TRUE, TRUE, FALSE); improve_attack_modifier(bow, mon, &b, &s, result.hit_verb, TRUE, TRUE, FALSE); result.dmg = ranged_damage(ammo, bow, b, s, multiplier); result.dmg = critical_shot(ammo->weight, ammo->to_h, result.dmg, &result.msg_type); object_notice_attack_plusses(bow); return result; }
/** * Gets miscellaneous combat information about the given object. * * Fills in whether there is a special effect when thrown in `thrown effect`, * the `range` in ft (or zero if not ammo), the percentage chance of breakage * and whether it is too heavy to be wielded effectively at the moment. */ static void obj_known_misc_combat(const struct object *obj, bool *thrown_effect, int *range, int *break_chance, bool *heavy) { 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); *thrown_effect = *heavy = false; *range = *break_chance = 0; if (!weapon && !ammo) { /* Potions can have special text */ if (tval_is_potion(obj) && obj->dd != 0 && obj->ds != 0 && object_flavor_is_aware(obj)) *thrown_effect = true; } if (ammo) *range = 10 * MIN(6 + 2 * player->state.ammo_mult, z_info->max_range); /* Add breakage chance */ *break_chance = breakage_chance(obj, true); /* Is the weapon too heavy? */ if (weapon) { struct player_state state; int weapon_slot = slot_by_name(player, "weapon"); struct object *current = equipped_item_by_slot_name(player, "weapon"); /* Pretend we're wielding the object */ player->body.slots[weapon_slot].obj = (struct object *) obj; /* Calculate the player's hypothetical state */ memcpy(&state, &player->state, sizeof(state)); state.stat_ind[STAT_STR] = 0; //Hack - NRM state.stat_ind[STAT_DEX] = 0; //Hack - NRM calc_bonuses(player, &state, true, false); /* Stop pretending */ player->body.slots[weapon_slot].obj = current; /* Warn about heavy weapons */ *heavy = state.heavy_wield; } }
static struct panel *get_panel_combat(void) { struct panel *p = panel_allocate(9); struct object *obj; int bth, dam, hit; int melee_dice = 1, melee_sides = 1; /* AC */ panel_line(p, COLOUR_L_BLUE, "Armor", "[%d,%+d]", player->known_state.ac, player->known_state.to_a); /* Melee */ obj = equipped_item_by_slot_name(player, "weapon"); bth = (player->state.skills[SKILL_TO_HIT_MELEE] * 10) / BTH_PLUS_ADJ; dam = player->known_state.to_d + (obj ? obj->known->to_d : 0); hit = player->known_state.to_h + (obj ? obj->known->to_h : 0); panel_space(p); if (obj) { melee_dice = obj->dd; melee_sides = obj->ds; } panel_line(p, COLOUR_L_BLUE, "Melee", "%dd%d,%+d", melee_dice, melee_sides, dam); panel_line(p, COLOUR_L_BLUE, "To-hit", "%d,%+d", bth / 10, hit); panel_line(p, COLOUR_L_BLUE, "Blows", "%d.%d/turn", player->state.num_blows / 100, (player->state.num_blows / 10 % 10)); /* Ranged */ obj = equipped_item_by_slot_name(player, "shooting"); bth = (player->state.skills[SKILL_TO_HIT_BOW] * 10) / BTH_PLUS_ADJ; hit = player->known_state.to_h + (obj ? obj->known->to_h : 0); dam = obj ? obj->known->to_d : 0; panel_space(p); panel_line(p, COLOUR_L_BLUE, "Shoot to-dam", "%+d", dam); panel_line(p, COLOUR_L_BLUE, "To-hit", "%d,%+d", bth / 10, hit); panel_line(p, COLOUR_L_BLUE, "Shots", "%d.%d/turn", player->state.num_shots / 10, player->state.num_shots % 10); return p; }
/** * 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; }
/** * Fire an object from the quiver, pack or floor at a target. */ void do_cmd_fire(struct command *cmd) { int dir; int range = MIN(6 + 2 * player->state.ammo_mult, z_info->max_range); int shots = player->state.num_shots; ranged_attack attack = make_ranged_shot; struct object *bow = equipped_item_by_slot_name(player, "shooting"); struct object *obj; /* Get arguments */ if (cmd_get_item(cmd, "item", &obj, /* Prompt */ "Fire which ammunition?", /* Error */ "You have no ammunition to fire.", /* Filter */ obj_can_fire, /* Choice */ USE_INVEN | USE_QUIVER | USE_FLOOR | QUIVER_TAGS) != CMD_OK) return; if (cmd_get_target(cmd, "target", &dir) == CMD_OK) player_confuse_dir(player, &dir, FALSE); else return; /* Require a usable launcher */ if (!bow || !player->state.ammo_tval) { msg("You have nothing to fire with."); return; } /* Check the item being fired is usable by the player. */ if (!item_is_available(obj, NULL, USE_QUIVER | USE_INVEN | USE_FLOOR)) { msg("That item is not within your reach."); return; } /* Check the ammo can be used with the launcher */ if (obj->tval != player->state.ammo_tval) { msg("That ammo cannot be fired by your current weapon."); return; } ranged_helper(obj, dir, range, shots, attack); }
/** * Gets information about the average damage/turn that can be inflicted if * the player wields the given weapon. * * Fills in the damage against normal adversaries in `normal_damage`, as well * as the slays on the weapon in slay_list[] and corresponding damages in * slay_damage[]. These must both be at least SL_MAX long to be safe. * `nonweap_slay` is set to whether other items being worn could add to the * damage done by branding attacks. * * Returns the number of slays populated in slay_list[] and slay_damage[]. * * Note that the results are meaningless if called on a fake ego object as * the actual ego may have different properties. */ static bool obj_known_damage(const struct object *obj, int *normal_damage, struct brand **brand_list, struct slay **slay_list, bool *nonweap_slay) { int i; 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; struct brand *brand; struct slay *slay; 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 multiplier = 1; struct player_state state; int weapon_slot = slot_by_name(player, "weapon"); struct object *current_weapon = slot_object(player, weapon_slot); /* Pretend we're wielding the object if it's a weapon */ if (weapon) player->body.slots[weapon_slot].obj = (struct object *) obj; /* Calculate the player's hypothetical state */ calc_bonuses(player, &state, true, false); /* Stop pretending */ player->body.slots[weapon_slot].obj = current_weapon; /* Use displayed dice if real dice not known */ if (object_attack_plusses_are_visible(obj)) { dice = obj->dd; sides = obj->ds; } else { dice = obj->kind->dd; sides = obj->kind->ds; } /* Calculate damage */ dam = ((sides + 1) * dice * 5); if (weapon) { xtra_postcrit = state.to_d * 10; if (object_attack_plusses_are_visible(obj)) { xtra_precrit += obj->to_d * 10; plus += obj->to_h; } calculate_melee_crits(&state, obj->weight, plus, &crit_mult, &crit_add, &crit_div); old_blows = state.num_blows; } else { /* Ammo */ if (object_attack_plusses_are_visible(obj)) plus += obj->to_h; calculate_missile_crits(&player->state, obj->weight, plus, &crit_mult, &crit_add, &crit_div); if (object_attack_plusses_are_visible(obj)) dam += (obj->to_d * 10); if (object_attack_plusses_are_visible(bow)) dam += (bow->to_d * 10); } if (ammo) multiplier = player->state.ammo_mult; /* Get the brands */ *brand_list = brand_collect(obj->known->brands, ammo ? bow->known : NULL); /* Get the slays */ *slay_list = slay_collect(obj->known->slays, ammo ? bow->known : NULL); /* Melee weapons may get slays and brands from other items */ *nonweap_slay = false; if (weapon) { for (i = 2; i < player->body.count; i++) { struct object *slot_obj = slot_object(player, i); struct brand *new_brand; struct slay *new_slay; if (!slot_obj) continue; if (slot_obj->known->brands || slot_obj->known->slays) *nonweap_slay = true; else continue; /* Replace the old lists with new ones */ new_brand = brand_collect(*brand_list, slot_obj->known); new_slay = slay_collect(*slay_list, slot_obj->known); free_brand(*brand_list); free_slay(*slay_list); *brand_list = new_brand; *slay_list = new_slay; } } /* Get damage for each brand on the objects */ for (brand = *brand_list; brand; brand = brand->next) { /* 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 + brand->multiplier - 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 *= player->state.num_shots; brand->damage = total_dam; } /* Get damage for each slay on the objects */ for (slay = *slay_list; slay; slay = slay->next) { /* 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 + slay->multiplier - 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 *= player->state.num_shots; slay->damage = total_dam; } /* 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 *= player->state.num_shots; *normal_damage = total_dam; return (*slay_list || *brand_list); }
/** * Attack the monster at the given location with a single blow. */ static bool py_attack_real(int y, int x, bool *fear) { size_t i; /* Information about the target of the attack */ struct monster *mon = square_monster(cave, y, x); char m_name[80]; bool stop = FALSE; /* The weapon used */ struct object *obj = equipped_item_by_slot_name(player, "weapon"); /* Information about the attack */ int chance = py_attack_hit_chance(obj); bool do_quake = FALSE; bool success = FALSE; /* Default to punching for one damage */ char verb[20]; int dmg = 1; u32b msg_type = MSG_HIT; /* Default to punching for one damage */ my_strcpy(verb, "punch", sizeof(verb)); /* Extract monster name (or "it") */ monster_desc(m_name, sizeof(m_name), mon, MDESC_OBJE | MDESC_IND_HID | MDESC_PRO_HID); /* Auto-Recall if possible and visible */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) monster_race_track(player->upkeep, mon->race); /* Track a new monster */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) health_track(player->upkeep, mon); /* Handle player fear (only for invisible monsters) */ if (player_of_has(player, OF_AFRAID)) { msgt(MSG_AFRAID, "You are too afraid to attack %s!", m_name); return FALSE; } /* Disturb the monster */ mon_clear_timed(mon, MON_TMD_SLEEP, MON_TMD_FLG_NOMESSAGE, FALSE); /* See if the player hit */ success = test_hit(chance, mon->race->ac, mflag_has(mon->mflag, MFLAG_VISIBLE)); /* If a miss, skip this hit */ if (!success) { msgt(MSG_MISS, "You miss %s.", m_name); return FALSE; } /* Handle normal weapon */ if (obj) { int j; const struct brand *b = NULL; const struct slay *s = NULL; my_strcpy(verb, "hit", sizeof(verb)); /* Get the best attack from all slays or * brands on all non-launcher equipment */ for (j = 2; j < player->body.count; j++) { struct object *obj = slot_object(player, j); if (obj) improve_attack_modifier(obj, mon, &b, &s, verb, FALSE, TRUE, FALSE); } improve_attack_modifier(obj, mon, &b, &s, verb, FALSE, TRUE, FALSE); dmg = melee_damage(obj, b, s); dmg = critical_norm(obj->weight, obj->to_h, dmg, &msg_type); /* Learn by use for the weapon */ object_notice_attack_plusses(obj); if (player_of_has(player, OF_IMPACT) && dmg > 50) { do_quake = TRUE; equip_notice_flag(player, OF_IMPACT); } } /* Learn by use for other equipped items */ equip_notice_on_attack(player); /* Apply the player damage bonuses */ dmg += player_damage_bonus(&player->state); /* No negative damage; change verb if no damage done */ if (dmg <= 0) { dmg = 0; msg_type = MSG_MISS; my_strcpy(verb, "fail to harm", sizeof(verb)); } for (i = 0; i < N_ELEMENTS(melee_hit_types); i++) { const char *dmg_text = ""; if (msg_type != melee_hit_types[i].msg) continue; if (OPT(show_damage)) dmg_text = format(" (%d)", dmg); if (melee_hit_types[i].text) msgt(msg_type, "You %s %s%s. %s", verb, m_name, dmg_text, melee_hit_types[i].text); else msgt(msg_type, "You %s %s%s.", verb, m_name, dmg_text); } /* Pre-damage side effects */ blow_side_effects(player, mon); /* Damage, check for fear and death */ stop = mon_take_hit(mon, dmg, fear, NULL); if (stop) (*fear) = FALSE; /* Post-damage effects */ if (blow_after_effects(y, x, do_quake)) stop = TRUE; return stop; }
/** * Gets information about the average damage/turn that can be inflicted if * the player wields the given weapon. * * Fills in the damage against normal adversaries in `normal_damage`, as well * as the slays on the weapon in slay_list[] and corresponding damages in * slay_damage[]. These must both be at least SL_MAX long to be safe. * `nonweap_slay` is set to whether other items being worn could add to the * damage done by branding attacks. * * Returns the number of slays populated in slay_list[] and slay_damage[]. * * Note that the results are meaningless if called on a fake ego object as * the actual ego may have different properties. */ static bool obj_known_damage(const struct object *obj, int *normal_damage, int *brand_damage, int *slay_damage, bool *nonweap_slay) { int i; 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; bool *total_brands; bool *total_slays; bool has_brands_or_slays = false; 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 melee_adj_mult = ammo ? 0 : 1; int multiplier = 1; struct player_state state; int weapon_slot = slot_by_name(player, "weapon"); struct object *current_weapon = slot_object(player, weapon_slot); /* Pretend we're wielding the object if it's a weapon */ if (weapon) player->body.slots[weapon_slot].obj = (struct object *) obj; /* Calculate the player's hypothetical state */ memcpy(&state, &player->state, sizeof(state)); state.stat_ind[STAT_STR] = 0; //Hack - NRM state.stat_ind[STAT_DEX] = 0; //Hack - NRM calc_bonuses(player, &state, true, false); /* Stop pretending */ player->body.slots[weapon_slot].obj = current_weapon; /* Finish if dice not known */ dice = obj->known->dd; sides = obj->known->ds; if (!dice || !sides) return false; /* Calculate damage */ dam = ((sides + 1) * dice * 5); if (weapon) { xtra_postcrit = state.to_d * 10; xtra_precrit += obj->known->to_d * 10; plus += obj->known->to_h; calculate_melee_crits(&state, obj->weight, plus, &crit_mult, &crit_add, &crit_div); old_blows = state.num_blows; } else { /* Ammo */ plus += obj->known->to_h; calculate_missile_crits(&player->state, obj->weight, plus, &crit_mult, &crit_add, &crit_div); dam += (obj->known->to_d * 10); dam += (bow->known->to_d * 10); } if (ammo) multiplier = player->state.ammo_mult; /* Get the brands */ total_brands = mem_zalloc(z_info->brand_max * sizeof(bool)); copy_brands(&total_brands, obj->known->brands); if (ammo && bow->known) copy_brands(&total_brands, bow->known->brands); /* Get the slays */ total_slays = mem_zalloc(z_info->slay_max * sizeof(bool)); copy_slays(&total_slays, obj->known->slays); if (ammo && bow->known) copy_slays(&total_slays, bow->known->slays); /* Melee weapons may get slays and brands from other items */ *nonweap_slay = false; if (weapon) { for (i = 2; i < player->body.count; i++) { struct object *slot_obj = slot_object(player, i); if (!slot_obj) continue; if (slot_obj->known->brands || slot_obj->known->slays) *nonweap_slay = true; else continue; /* Replace the old lists with new ones */ copy_brands(&total_brands, slot_obj->known->brands); copy_slays(&total_slays, slot_obj->known->slays); } } /* Get damage for each brand on the objects */ for (i = 1; i < z_info->brand_max; i++) { /* Must have the brand */ if (total_brands[i]) has_brands_or_slays = true; else continue; /* Include bonus damage and brand in stated average */ total_dam = dam * (multiplier + brands[i].multiplier - 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 *= player->state.num_shots; brand_damage[i] = total_dam; } /* Get damage for each slay on the objects */ for (i = 1; i < z_info->slay_max; i++) { /* Must have the slay */ if (total_slays[i]) has_brands_or_slays = true; else continue; /* Include bonus damage and slay in stated average */ total_dam = dam * (multiplier + slays[i].multiplier - 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 *= player->state.num_shots; slay_damage[i] = total_dam; } /* 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 *= player->state.num_shots; *normal_damage = total_dam; mem_free(total_brands); mem_free(total_slays); return has_brands_or_slays; }