diff --git a/src/obj-info.c b/src/obj-info.c index 6dcf24100..f08e47784 100644 --- a/src/obj-info.c +++ b/src/obj-info.c @@ -458,7 +458,7 @@ static bool describe_brands(textblock *tb, const struct object *obj) /** * Sum over the critical levels to get the expected number of - * dice added when a crtical happens. + * dice added when a critical happens. */ static struct my_rational sum_criticals(const struct critical_level *head) { @@ -490,13 +490,17 @@ static struct my_rational sum_criticals(const struct critical_level *head) * Account for criticals in the calculation of melee prowess for O-combat; * crit chance * average number of dice added * - * Return value is 100x number of dice + * \param state points to the state for the player of interest. + * \param obj is the melee weapon of interest. + * \param dice is dereferenced and set to 100 * crit chance * average number + * of dice added. + * \param frac_dice is dereferenced and set to the fractional part truncated + * from *dice when converted to an integer. */ -static unsigned int calculate_melee_crits(struct player_state *state, - const struct object *obj) +static void calculate_melee_crits(struct player_state *state, + const struct object *obj, unsigned int *dice, + struct my_rational *frac_dice) { - unsigned int dice; - if (z_info->m_crit_level_head) { /* * Optimistically assume the target is visible (so @@ -556,50 +560,48 @@ static unsigned int calculate_melee_crits(struct player_state *state, if (chance.n < chance.d) { /* * Critical only happens some of the time. - * Scale by chance and 100. Round to the nearest - * integer. + * Scale by the chance and 100. */ chance = my_rational_product(&chance, &z_info->m_max_added); - dice = my_rational_to_uint(&chance, 100, &tr); - if (dice < UINT_MAX && tr >= chance.d / 2) { - ++dice; - } + *dice = my_rational_to_uint(&chance, 100, &tr); + *frac_dice = my_rational_construct(tr, chance.d); } else { - /* - * Critical always happens. Scale by 100 and - * round to the nearest integer. - */ - dice = my_rational_to_uint( + /* Critical always happens. Scale by 100. */ + *dice = my_rational_to_uint( &z_info->m_max_added, 100, &tr); - if (dice < UINT_MAX && tr >= z_info->m_max_added.d / 2) { - ++dice; - } + *frac_dice = my_rational_construct(tr, + z_info->m_max_added.d); } } else { /* No critical levels defined so no additional damage. */ - dice = 0; + *dice = 0; + *frac_dice = my_rational_construct(0, 1); } - - return dice; } /** * Missile crits follow the same approach as melee crits. + * + * \param state points to the state for the player of interest. + * \param obj is the missile of interest. + * \param launcher is the launcher of interest or NULL for a thrown missile. + * \param dice is dereferenced and set to 100 * crit chance * average number + * of dice added. + * \param frac_dice is dereferenced and set to the fractional part truncated + * from *dice when converted to an integer. */ -static unsigned int calculate_missile_crits(struct player_state *state, - const struct object *obj, - const struct object *launcher) +static void calculate_missile_crits(struct player_state *state, + const struct object *obj, const struct object *launcher, + unsigned int *dice, struct my_rational *frac_dice) { - unsigned int dice; - if (z_info->r_crit_level_head) { /* * Optimistically assume the target is obvious, that this * is the first try, and that target's range incurs no * penalty to hit. Thus, can use chance_of_missile_hit_base() * rather than chance_of_missile_hit()). Also optimistically - * assume that the target is visible so that armsman will be + * assume that the target is visible so that marksman will be * useful. Pessimistically assume that the target is not * sleeping. Otherwise, these calculations must agree with * those in player-attack.c's critical_shot(). @@ -658,32 +660,24 @@ static unsigned int calculate_missile_crits(struct player_state *state, if (chance.n < chance.d) { /* * Critical only happens some of the time. - * Scale by chance and 100. Round to the nearest - * integer. + * Scale by chance and 100. */ chance = my_rational_product(&chance, &z_info->r_max_added); - dice = my_rational_to_uint(&chance, 100, &tr); - if (dice < UINT_MAX && tr >= chance.d / 2) { - ++dice; - } + *dice = my_rational_to_uint(&chance, 100, &tr); + *frac_dice = my_rational_construct(tr, chance.d); } else { - /* - * Critical always happens. Scale by 100 and - * round to the nearest integer. - */ - dice = my_rational_to_uint( + /* Critical always happens. Scale by 100. */ + *dice = my_rational_to_uint( &z_info->r_max_added, 100, &tr); - if (dice < UINT_MAX && tr >= z_info->m_max_added.d / 2) { - ++dice; - } + *frac_dice = my_rational_construct(tr, + z_info->r_max_added.d); } } else { /* No critical levels defined so no additional damage. */ - dice = 0; + *dice = 0; + *frac_dice = my_rational_construct(0, 1); } - - return dice; } /** @@ -891,25 +885,41 @@ static bool describe_blows(textblock *tb, const struct object *obj) /** * 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. + * the player uses the given weapon. * - * Returns the number of slays populated in slay_list[] and slay_damage[]. + * \param obj is the melee weapon or launched/thrown missile to evaluate. + * \param normal_damage is dereferenced and set to the average damage per + * turn times ten if no brands or slays are effective. + * \param brand_damage must point to z_info->brand_max ints. brand_damage[i] + * is set to the average damage per turn times ten with the ith brand from the + * global brands array if that brand is present and is not overridden by a + * more powerful brand that is also present for the same element; otherwise, + * brand_damage[i] is not modified. + * \param slay_damage must point to z_info->slay_max ints. slay_damage[i] + * is set to the average damage per turn times ten with the ith slay from the + * global slays array if that slay is present and is not overridden by a + * more powerful slay that is also present for the same monsters; otherwise, + * slay_damage[i] is not modified. + * \param nonweap_slay is dereferenced and set to true if an off-weapon slay + * or brand affects the damage or to false if no off-weapon slay or brand + * affects the damage. + * \param thow causes, if true, the damage to be calculated as if obj is + * thrown. + * \return true if there is at least one known brand or slay that could + * affect the damage; otherwise, return false. * * 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, +bool obj_known_damage(const struct object *obj, int *normal_damage, int *brand_damage, int *slay_damage, bool *nonweap_slay, bool throw) { int i; int dice, sides, die_average, total_dam; + unsigned int added_dice, remainder; + struct my_rational frac_dice, frac_temp; + int temp0, round; int deadliness = obj->known->to_d; int old_blows = 0; bool *total_brands; @@ -945,16 +955,25 @@ static bool obj_known_damage(const struct object *obj, int *normal_damage, /* Get the number of additional dice from criticals (x100) */ if (weapon) { - dice += calculate_melee_crits(&state, obj); + calculate_melee_crits(&state, obj, &added_dice, &frac_dice); + dice += added_dice; old_blows = state.num_blows; } else if (ammo) { - dice += calculate_missile_crits(&player->state, obj, bow); + calculate_missile_crits(&player->state, obj, bow, + &added_dice, &frac_dice); + dice += added_dice; } else { + unsigned int thrown_scl = 2 + player->lev / 12; + if (of_has(obj->known->flags, OF_PERFECT_BALANCE)) { dice *= 2; } - dice += calculate_missile_crits(&player->state, obj, NULL); - dice *= 2 + player->lev / 12; + calculate_missile_crits(&player->state, obj, NULL, + &added_dice, &frac_dice); + dice += added_dice; + dice *= thrown_scl; + dice += my_rational_to_uint(&frac_dice, thrown_scl, &remainder); + frac_dice = my_rational_construct(remainder, frac_dice.d); } if (ammo) multiplier = player->state.ammo_mult; @@ -1032,23 +1051,38 @@ static bool obj_known_damage(const struct object *obj, int *normal_damage, /* Include brand in stated average (x10), deflate (/1000) */ brand_average = die_average * brands[i].multiplier; + round = brand_average % 1000; brand_average /= 1000; /* Damage per hit is now dice * die average, (still x1000) */ - total_dam = (dice * brand_average); + temp0 = dice * brand_average + (dice * round) / 1000 + + my_rational_to_uint(&frac_dice, brand_average, + &remainder); + frac_temp = my_rational_construct(remainder, frac_dice.d); + round = (dice * round) % 1000 + + my_rational_to_uint(&frac_temp, 1000, &remainder); + if (remainder >= (frac_temp.d + 1) / 2) { + ++round; + } /* Now adjust for blows and shots and deflate again */ if (weapon) { - total_dam *= old_blows; + total_dam = old_blows * temp0 + + (old_blows * round) / 1000; + round = total_dam % 10000; total_dam /= 10000; - total_dam += (add * old_blows) / 10; + total_dam += (add * old_blows) / 10 + + ((round >= 5000) ? 1 : 0); } else if (ammo) { - total_dam *= player->state.num_shots; + total_dam = player->state.num_shots * temp0 + + (player->state.num_shots * round) / 1000; + round = total_dam % 1000; total_dam /= 1000; - total_dam += add * player->state.num_shots; + total_dam += add * player->state.num_shots + + ((round >= 500) ? 1 : 0); } else { - total_dam /= 100; - total_dam += add * 10; + total_dam = temp0 / 100 + add * 10 + + ((temp0 % 100 >= 50) ? 1 : 0); } brand_damage[i] = total_dam; @@ -1065,38 +1099,64 @@ static bool obj_known_damage(const struct object *obj, int *normal_damage, /* Include slay in stated average (x10), deflate (/1000) */ slay_average = die_average * slays[i].multiplier; + round = slay_average % 1000; slay_average /= 1000; /* Damage per hit is now dice * die average, (still x1000) */ - total_dam = (dice * slay_average); + temp0 = dice * slay_average + (dice * round) / 1000 + + my_rational_to_uint(&frac_dice, slay_average, + &remainder); + frac_temp = my_rational_construct(remainder, frac_dice.d); + round = (dice * round) % 1000 + + my_rational_to_uint(&frac_temp, 1000, &remainder); + if (remainder >= (frac_temp.d + 1) / 2) { + ++round; + } /* Now adjust for blows and shots and deflate again */ if (weapon) { - total_dam *= old_blows; + total_dam = old_blows * temp0 + + (old_blows * round) / 1000; + round = total_dam % 10000; total_dam /= 10000; - total_dam += (add * old_blows) / 10; + total_dam += (add * old_blows) / 10 + + ((round >= 5000) ? 1 : 0); } else if (ammo) { - total_dam *= player->state.num_shots; + total_dam = player->state.num_shots * temp0 + + (player->state.num_shots * round) / 1000; + round = total_dam % 1000; total_dam /= 1000; - total_dam += add * player->state.num_shots; + total_dam += add * player->state.num_shots + + ((round >= 500) ? 1 : 0); } else { - total_dam /= 100; - total_dam += add * 10; + total_dam = temp0 / 100 + add * 10 + + ((temp0 % 100 >= 50) ? 1 : 0); } slay_damage[i] = total_dam; } /* Normal damage, not considering brands or slays */ - total_dam = (dice * die_average) / 1000; + temp0 = dice * die_average + + my_rational_to_uint(&frac_dice, die_average, &remainder); + if (remainder >= (frac_dice.d + 1) / 2) { + ++temp0; + } + round = temp0 % 1000; + temp0 /= 1000; if (weapon) { - total_dam *= old_blows; + total_dam = old_blows * temp0 + (old_blows * round) / 1000; + round = total_dam % 1000; total_dam /= 1000; + total_dam += (round >= 500) ? 1 : 0; } else if (ammo) { - total_dam *= player->state.num_shots; + total_dam = player->state.num_shots * temp0 + + (player->state.num_shots * round) / 1000; + round = total_dam % 100; total_dam /= 100; + total_dam += (round >= 50) ? 1 : 0; } else { - total_dam /= 10; + total_dam = temp0 / 10 + ((temp0 % 10 >= 5) ? 1 : 0); } *normal_damage = total_dam; diff --git a/src/obj-info.h b/src/obj-info.h index eb92b8d2a..0ef2d4306 100644 --- a/src/obj-info.h +++ b/src/obj-info.h @@ -40,4 +40,9 @@ textblock *object_info_ego(struct ego_item *ego); void object_info_spoil(ang_file *f, const struct object *obj, int wrap); void object_info_chardump(ang_file *f, const struct object *obj, int indent, int wrap); +/* These are public so unit test cases can use them. */ +bool obj_known_damage(const struct object *obj, int *normal_damage, + int *brand_damage, int *slay_damage, bool *nonweap_slay, + bool throw); + #endif /* OBJECT_INFO_H */ diff --git a/src/player-attack.c b/src/player-attack.c index 5c934dcca..5ad6a63fb 100644 --- a/src/player-attack.c +++ b/src/player-attack.c @@ -1505,7 +1505,7 @@ static void ranged_helper(struct player *p, struct object *bow, /** * Helper function used with ranged_helper by do_cmd_fire. */ -static struct attack_result make_ranged_shot(struct player *p, +struct attack_result make_ranged_shot(struct player *p, struct object *ammo, struct loc grid, int tries, bool super) @@ -1564,7 +1564,7 @@ static struct attack_result make_ranged_shot(struct player *p, /** * Helper function used with ranged_helper by do_cmd_throw. */ -static struct attack_result make_ranged_throw(struct player *p, +struct attack_result make_ranged_throw(struct player *p, struct object *obj, struct loc grid, int tries, bool super) diff --git a/src/player-attack.h b/src/player-attack.h index 2eda3fc6b..1b95ec5ee 100644 --- a/src/player-attack.h +++ b/src/player-attack.h @@ -80,4 +80,10 @@ void apply_deadliness(int *die_average, int deadliness); extern void py_attack(struct player *p, struct loc grid); extern bool py_attack_real(struct player *p, struct loc grid, bool *fear); +/* These are public for use by unit test cases. */ +struct attack_result make_ranged_shot(struct player *p, struct object *ammo, + struct loc grid, int tries, bool super); +struct attack_result make_ranged_throw(struct player *p, struct object *obj, + struct loc grid, int tries, bool super); + #endif /* !PLAYER_ATTACK_H */ diff --git a/src/tests/object/info.c b/src/tests/object/info.c new file mode 100644 index 000000000..5a06c8410 --- /dev/null +++ b/src/tests/object/info.c @@ -0,0 +1,2112 @@ +/* object/info.c */ +/* Exercise functions from obj-info.{h,c}. */ + +#include "unit-test.h" +#include "unit-test-data.h" +#include "test-utils.h" +#include "init.h" +#include "mon-make.h" +#include "mon-spell.h" +#include "obj-gear.h" +#include "obj-info.h" +#include "obj-knowledge.h" +#include "obj-make.h" +#include "obj-pile.h" +#include "obj-slays.h" +#include "obj-util.h" +#include "player-attack.h" +#include "player-birth.h" +#include "player-calcs.h" +#include "player-timed.h" +#include "player-util.h" +#include "z-color.h" +#include "z-virt.h" + +struct info_test_state { + struct monster_base target_base; + struct monster_race target_race; + struct monster *target; + int *damage_results; + int *oi_brands; + int *oi_slays; +}; + +/* + * This is the number of hits to record to get a Monte Carlo calculation for + * an average damage. + */ +#define NHITS (10000) + +/* + * This is the number of standard deviations of the mean an average from the + * Monte Carlo simulation has to be from the object information result to + * declare a test failure. + */ +#define STDFAIL (5.0) + +/* + * Set up a macro so details about the averages that are too far apart are + * visible when run with the -v option. + */ +#define check_averages(desc, oi, mca, mcv) \ + { \ + double dev = 0.1 * oi - mca; \ + if ((dev < -0.05 || dev > 0.05) \ + && dev * dev > STDFAIL * STDFAIL * mc_var) { \ + if (verbose) { \ + showfail(); \ + (void)printf(" %s:%d: %s %d.%d object info too far from Monte Carlo result %.2f with %.4f variance\n", \ + suite_name, \ + __LINE__, \ + desc, \ + oi / 10, oi % 10, \ + mca, mcv); \ + } \ + return 1; \ + } \ + } + +static void fill_in_monster_base(struct monster_base *base); +static void fill_in_monster_race(struct monster_race *race, + struct monster_base *base); + +int setup_tests(void **state) +{ + struct monster_group_info gi = { 0, 0 }; + struct info_test_state *ts; + + set_file_paths(); + init_angband(); +#ifdef UNIX + /* Necessary for creating the randart file. */ + create_needed_dirs(); +#endif + + /* Set up for recording damage results. */ + ts = mem_alloc(sizeof(*ts)); + ts->damage_results = mem_alloc(NHITS * sizeof(*ts->damage_results)); + ts->oi_brands = mem_alloc(z_info->brand_max * sizeof(*ts->oi_brands)); + ts->oi_slays = mem_alloc(z_info->slay_max * sizeof(*ts->oi_slays)); + + /* Set up the player. */ + if (!player_make_simple(NULL, NULL, "Tester")) { + mem_free(ts->oi_slays); + mem_free(ts->oi_brands); + mem_free(ts->damage_results); + mem_free(ts); + cleanup_angband(); + return 1; + } + + /* Give the player something to operate in. */ + cave = t_build_arena(10, 10); + player_place(cave, player, loc(cave->width / 2, cave->height / 2)); + + /* Give the player something to attack. */ + fill_in_monster_base(&ts->target_base); + fill_in_monster_race(&ts->target_race, &ts->target_base); + place_new_monster(cave, loc(player->grid.x + 1, player->grid.y), + &ts->target_race, false, false, gi, ORIGIN_DROP_WIZARD); + ts->target = square_monster(cave, + loc(player->grid.x + 1, player->grid.y)); + + /* + * Mark the monster as visible since the object information + * calculations assume that it is visible. + */ + mflag_on(ts->target->mflag, MFLAG_VISIBLE); + + *state = ts; + return 0; +} + +int teardown_tests(void *state) +{ + struct info_test_state *ts = (struct info_test_state*)state; + + wipe_mon_list(cave, player); + cleanup_angband(); + mem_free(ts->oi_slays); + mem_free(ts->oi_brands); + mem_free(ts->damage_results); + mem_free(ts); + return 0; +} + +static void fill_in_monster_base(struct monster_base *base) +{ + static char name[20] = "blob"; + static char text[20] = "blob"; + + base->next = NULL; + base->name = name; + base->text = text; + rf_wipe(base->flags); + base->d_char = L'b'; + base->pain = NULL; +} + +static void fill_in_monster_race(struct monster_race *race, + struct monster_base *base) +{ + static char name[20] = "white blob"; + static char text[20] = "white blob"; + + race->next = NULL; + race->ridx = 1; + race->name = name; + race->text = text; + race->plural = NULL; + race->base = base; + race->avg_hp = 5000; + race->ac = 1; + race->sleep = 0; + race->hearing = 20; + race->smell = 20; + race->speed = 110; + race->light = 0; + race->mexp = 0; + race->freq_innate = 0; + race->freq_spell = 0; + race->spell_power = 0; + rf_wipe(race->flags); + /* + * Allow for at least two slays and two brands to be effective but do + * give immunity to at least two brands and two slays. Do not give an + * enhanced vulnerability to a brand since the object information + * calculations never account for those. + */ + rf_on(race->flags, RF_UNDEAD); + rf_on(race->flags, RF_EVIL); + rf_on(race->flags, RF_IM_COLD); + rf_on(race->flags, RF_IM_POIS); + rsf_wipe(race->spell_flags); + race->blow = &test_blow[0]; + race->level = 1; + race->rarity = 1; + race->d_attr = COLOUR_WHITE; + race->d_char = base->d_char; + race->max_num = 100; + race->cur_num = 0; + race->drops = NULL; + race->friends = NULL; + race->friends_base = NULL; + race->mimic_kinds = NULL; + race->shapes = NULL; + race->num_shapes = 0; +} + +static void wipe_brands_slays(struct object *weapon) +{ + if (weapon->brands) { + mem_free(weapon->brands); + } + weapon->brands = NULL; + if (weapon->slays) { + mem_free(weapon->slays); + } + weapon->slays = NULL; + if (weapon->known) { + if (weapon->known->brands) { + mem_free(weapon->known->brands); + weapon->known->brands = NULL; + } + if (weapon->known->slays) { + mem_free(weapon->known->slays); + weapon->known->slays = NULL; + } + } +} + +static bool is_similar_brand(int i, int j) +{ + return i == j || streq(brands[i].name, brands[j].name); +} + +static bool object_has_similar_brand(const struct object *o, int i) +{ + int j; + + /* It has no brands at all - easy. */ + if (!o->brands) { + return false; + } + /* It already has that brand - easy. */ + if (o->brands[i]) { + return true; + } + /* Look for a similar brand. */ + j = 1; + while (1) { + if (j >= z_info->brand_max) { + return false; + } + if (o->brands[j] && is_similar_brand(j, i)) { + return true; + } + ++j; + } +} + +static bool object_has_similar_slay(const struct object *o, int i) +{ + int j; + + /* It has no slays at all - easy. */ + if (!o->slays) { + return false; + } + /* It already has that slay - easy. */ + if (o->slays[i]) { + return true; + } + /* Look for a similar slay. */ + j = 1; + while (1) { + if (j >= z_info->slay_max) { + return false; + } + if (o->slays[j] && same_monsters_slain(j, i)) { + return true; + } + ++j; + } +} + +static int add_random_effective_brand(struct player *p, struct object *o, + struct monster *m) +{ + int i_add = 0, n = 0, i; + + for (i = 1; i < z_info->brand_max; ++i) { + if (!rf_has(m->race->flags, brands[i].resist_flag) + && !object_has_similar_brand(o, i)) { + ++n; + if (one_in_(n)) { + i_add = i; + } + } + } + if (i_add == 0) { + return 0; + } + if (!o->brands) { + o->brands = mem_zalloc(z_info->brand_max * sizeof(*o->brands)); + } + o->brands[i_add] = true; + object_learn_brand(p, o, i_add); + player_know_object(p, o); + return i_add; +} + +static int add_random_ineffective_brand(struct player *p, struct object *o, + struct monster *m) +{ + int i_add = 0, n = 0, i; + + for (i = 1; i < z_info->brand_max; ++i) { + if (rf_has(m->race->flags, brands[i].resist_flag) + && !object_has_similar_brand(o, i)) { + ++n; + if (one_in_(n)) { + i_add = i; + } + } + } + if (i_add == 0) { + return 0; + } + if (!o->brands) { + o->brands = mem_zalloc(z_info->brand_max * sizeof(*o->brands)); + } + o->brands[i_add] = true; + object_learn_brand(p, o, i_add); + player_know_object(p, o); + return i_add; +} + +static int add_random_effective_slay(struct player *p, struct object *o, + struct monster *m) +{ + int i_add = 0, n = 0, i; + + for (i = 1; i < z_info->slay_max; ++i) { + if ((rf_has(m->race->flags, slays[i].race_flag) || + (slays[i].base && streq(slays[i].base, + m->race->base->name))) + && !object_has_similar_slay(o, i)) { + ++n; + if (one_in_(n)) { + i_add = i; + } + } + } + if (i_add == 0) { + return 0; + } + if (!o->slays) { + o->slays = mem_zalloc(z_info->slay_max * sizeof(*o->slays)); + } + o->slays[i_add] = true; + object_learn_slay(p, o, i_add); + player_know_object(p, o); + return i_add; +} + +static int add_random_ineffective_slay(struct player *p, struct object *o, + struct monster *m) +{ + int i_add = 0, n = 0, i; + + for (i = 1; i < z_info->slay_max; ++i) { + if ((!rf_has(m->race->flags, slays[i].race_flag) && + (!slays[i].base || !streq(slays[i].base, + m->race->base->name))) + && !object_has_similar_slay(o, i)) { + ++n; + if (one_in_(n)) { + i_add = i; + } + } + } + if (i_add == 0) { + return 0; + } + if (!o->slays) { + o->slays = mem_zalloc(z_info->slay_max * sizeof(*o->slays)); + } + o->slays[i_add] = true; + object_learn_slay(p, o, i_add); + player_know_object(p, o); + return i_add; +} + +static int add_random_effective_temporary_brand(struct player *p, + struct monster *m) +{ + int i_add = -1, n = 0, i; + + for (i = 0; i < TMD_MAX; ++i) { + if (timed_effects[i].temp_brand > 0 + && timed_effects[i].temp_brand < z_info->brand_max + && !rf_has(m->race->flags, brands[timed_effects[i].temp_brand].resist_flag)) { + ++n; + if (one_in_(n)) { + i_add = i; + } + } + } + if (i_add != -1) { + (void)player_set_timed(p, i_add, 10, false, false); + } + return i_add; +} + +static int add_random_ineffective_temporary_brand(struct player *p, + struct monster *m) +{ + int i_add = -1, n = 0, i; + + for (i = 0; i < TMD_MAX; ++i) { + if (timed_effects[i].temp_brand > 0 + && timed_effects[i].temp_brand < z_info->brand_max + && rf_has(m->race->flags, brands[timed_effects[i].temp_brand].resist_flag)) { + ++n; + if (one_in_(n)) { + i_add = i; + } + } + } + if (i_add != -1) { + (void)player_set_timed(p, i_add, 10, false, false); + } + return i_add; +} + +static int add_random_effective_temporary_slay(struct player *p, + struct monster *m) +{ + int i_add = -1, n = 0, i; + + for (i = 0; i < TMD_MAX; ++i) { + if (timed_effects[i].temp_slay > 0 + && timed_effects[i].temp_slay < z_info->slay_max + && (rf_has(m->race->flags, slays[timed_effects[i].temp_slay].race_flag) + || (slays[timed_effects[i].temp_slay].base + && streq(slays[timed_effects[i].temp_slay].base, + m->race->base->name)))) { + ++n; + if (one_in_(n)) { + i_add = i; + } + } + } + if (i_add != -1) { + (void)player_set_timed(p, i_add, 10, false, false); + } + return i_add; +} + +static int add_random_ineffective_temporary_slay(struct player *p, + struct monster *m) +{ + int i_add = -1, n = 0, i; + + for (i = 0; i < TMD_MAX; ++i) { + if (timed_effects[i].temp_slay > 0 + && timed_effects[i].temp_slay < z_info->slay_max + && (!rf_has(m->race->flags, slays[timed_effects[i].temp_slay].race_flag) + && (!slays[timed_effects[i].temp_slay].base + || !streq(slays[timed_effects[i].temp_slay].base, + m->race->base->name)))) { + ++n; + if (one_in_(n)) { + i_add = i; + } + } + } + if (i_add != -1) { + (void)player_set_timed(p, i_add, 10, false, false); + } + return i_add; +} + +static void remove_all_temporary_brands_and_slays(struct player *p) +{ + int i; + + for (i = 0; i < TMD_MAX; ++i) { + if ((timed_effects[i].temp_brand > 0 + && timed_effects[i].temp_brand < z_info->brand_max) + || (timed_effects[i].temp_slay > 0 + && timed_effects[i].temp_slay < z_info->slay_max)) { + (void)player_set_timed(p, i, 0, false, false); + } + } +} + +static void collect_damage_results(double *avg, double *avg_var, int *work, + struct player *p, struct monster *m, struct object *weapon, + struct object *launcher, bool throw) +{ + int weapon_slot = -1; + int launcher_slot = -1; + struct object *old_weapon = NULL; + struct object *old_launcher = NULL; + int i = 0; + int iavg, ivar; + struct my_rational favg, fvar; + + if (launcher) { + launcher_slot = wield_slot(launcher); + old_launcher = p->body.slots[launcher_slot].obj; + p->body.slots[launcher_slot].obj = launcher; + if (!old_launcher) { + ++p->upkeep->equip_cnt; + } + p->upkeep->total_weight += launcher->weight + - ((old_launcher) ? old_launcher->weight : 0); + p->upkeep->update |= (PU_BONUS); + } else if (!throw) { + weapon_slot = wield_slot(weapon); + old_weapon = p->body.slots[weapon_slot].obj; + p->body.slots[weapon_slot].obj = weapon; + if (!old_weapon && weapon) { + ++p->upkeep->equip_cnt; + } else if (old_weapon && !weapon) { + assert(p->upkeep->equip_cnt > 0); + --p->upkeep->equip_cnt; + } + p->upkeep->total_weight += + ((weapon) ? weapon->weight : 0) + - ((old_weapon) ? old_weapon->weight : 0); + p->upkeep->update |= (PU_BONUS); + } + update_stuff(p); + + while (i < NHITS) { + if (launcher || throw) { + struct attack_result ar; + + /* Need a missile of some sort. */ + assert(weapon); + if (launcher) { + ar = make_ranged_shot(p, weapon, m->grid, 0, + false); + } else { + ar = make_ranged_throw(p, weapon, m->grid, 0, + false); + } + mem_free(ar.hit_verb); + if (ar.success) { + work[i] = ar.dmg; + ++i; + } + } else { + int16_t old_hp = m->hp; + bool afraid = false; + + (void)py_attack_real(p, m->grid, &afraid); + /* + * Will not work if hits can have non-positive damage. + * Could test for the message string (but that'll break + * if the strings change). Testing the message type is + * no better than testing the monster's HP since + * py_attack_real() currently uses MSG_MISS for hits + * that do not do positive damage. + */ + if (m->hp < old_hp) { + int dam = old_hp - m->hp; + + work[i] = dam; + ++i; + /* + * Heal the monster so it won't die while + * collecting statistics. + */ + m->hp = old_hp; + } + } + } + + iavg = mean(work, NHITS, &favg); + ivar = variance(work, NHITS, true, true, &fvar); + *avg = (double)iavg + (double)favg.n / (double)favg.d; + *avg_var = (double)ivar + (double)fvar.n / (double)fvar.d; + + /* Scale results to be per turn rather than per attack. */ + if (launcher) { + double shots_per_turn = 0.1 * p->state.num_shots; + + *avg *= shots_per_turn; + *avg_var *= shots_per_turn * shots_per_turn; + } else if (!throw) { + double blows_per_turn = 0.01 * p->state.num_blows; + + *avg *= blows_per_turn; + *avg_var *= blows_per_turn * blows_per_turn; + } + + /* Restore the player's equipment. */ + if (launcher_slot != -1) { + assert(launcher && p->body.slots[launcher_slot].obj == launcher); + p->body.slots[launcher_slot].obj = old_launcher; + if (!old_launcher) { + assert(p->upkeep->equip_cnt > 0); + --p->upkeep->equip_cnt; + } + p->upkeep->total_weight += + ((old_launcher) ? old_launcher->weight : 0) + - launcher->weight; + p->upkeep->update |= (PU_BONUS); + } + if (weapon_slot != -1) { + assert(p->body.slots[weapon_slot].obj == weapon); + p->body.slots[weapon_slot].obj = old_weapon; + if (old_weapon && !weapon) { + ++p->upkeep->equip_cnt; + } else if (!old_weapon && weapon) { + assert(p->upkeep->equip_cnt > 0); + --p->upkeep->equip_cnt; + } + p->upkeep->total_weight += + ((old_weapon) ? old_weapon->weight : 0) + - ((weapon) ? weapon->weight : 0); + p->upkeep->update |= (PU_BONUS); + } + update_stuff(p); +} + +static int test_melee_weapon_damage_info(void *state) +{ + struct info_test_state *ts = (struct info_test_state*)state; + struct object_kind *weapon_kind, *gloves_kind; + struct object *weapon, *gloves, *old_gloves; + double mc_avg, mc_var; + int gloves_slot, oi_avg, iadd0, iadd1; + bool has_brand_slay, has_nonweap; + + remove_all_temporary_brands_and_slays(player); + weapon_kind = get_obj_num(1, false, TV_SWORD); + if (!weapon_kind) { + /* + * The depth-dependent lookup did not work so try using the + * first kind. + */ + weapon_kind = lookup_kind(TV_SWORD, 1); + } + notnull(weapon_kind); + weapon = object_new(); + object_prep(weapon, weapon_kind, 1, MINIMISE); + /* Give it a damage bonus and multiple dice of damage. */ + weapon->to_d = 2; + weapon->dd = 3; + weapon->ds = 8; + /* Make sure the player knows its properties. */ + weapon->known = object_new(); + wipe_brands_slays(weapon); + object_set_base_known(player, weapon); + object_touch(player, weapon); + object_learn_on_wield(player, weapon); + + gloves_kind = get_obj_num(1, false, TV_GLOVES); + if (!gloves_kind) { + /* + * The depth-dependent lookup did not work so try using the + * first kind. + */ + gloves_kind = lookup_kind(TV_GLOVES, 1); + } + notnull(gloves_kind); + gloves = object_new(); + object_prep(gloves, gloves_kind, 1, MINIMISE); + /* Make sure the player knows its properties. */ + gloves->known = object_new(); + wipe_brands_slays(gloves); + object_set_base_known(player, gloves); + object_touch(player, gloves); + object_learn_on_wield(player, gloves); + /* Wield them. */ + gloves_slot = wield_slot(gloves); + old_gloves = player->body.slots[gloves_slot].obj; + player->body.slots[gloves_slot].obj = gloves; + if (!old_gloves) { + ++player->upkeep->equip_cnt; + } + player->upkeep->total_weight += gloves->weight + - ((old_gloves) ? old_gloves->weight : 0); + player->upkeep->update |= (PU_BONUS); + update_stuff(player); + + /* Check armed combat without a slay or brand. */ + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, false); + eq(has_nonweap, false); + check_averages("melee; no brand or slay;", oi_avg, mc_avg, mc_var); + + /* Check armed combat with a brand on the weapon. */ + iadd0 = add_random_effective_brand(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg); + check_averages("melee; one useful brand;", ts->oi_brands[iadd0], mc_avg, + mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_ineffective_brand(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg); + check_averages("melee; one useless brand;", oi_avg, mc_avg, mc_var); + + /* Check armed combat with a slay on the weapon. */ + wipe_brands_slays(weapon); + iadd0 = add_random_effective_slay(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_slays[iadd0] > oi_avg); + check_averages("melee; one useful slay;", ts->oi_slays[iadd0], mc_avg, + mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_ineffective_slay(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_slays[iadd0] > oi_avg); + check_averages("melee; one useless slay;", oi_avg, mc_avg, mc_var); + + /* Check armed combat with more than one slay or brand on the weapon. */ + wipe_brands_slays(weapon); + iadd0 = add_random_effective_brand(player, weapon, ts->target); + iadd1 = add_random_effective_brand(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + ts->oi_brands[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg && ts->oi_brands[iadd1] > oi_avg); + /* Whichever is better should take effect. */ + oi_avg = (ts->oi_brands[iadd0] > ts->oi_brands[iadd1]) + ? ts->oi_brands[iadd0] : ts->oi_brands[iadd1]; + check_averages("melee; two useful brands;", oi_avg, mc_avg, mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_effective_brand(player, weapon, ts->target); + iadd1 = add_random_effective_slay(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_brands[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg && ts->oi_slays[iadd1] > oi_avg); + /* Whichever is better should take effect. */ + oi_avg = (ts->oi_brands[iadd0] > ts->oi_slays[iadd1]) + ? ts->oi_brands[iadd0] : ts->oi_slays[iadd1]; + check_averages("melee; one useful brand; one useful slay;", oi_avg, + mc_avg, mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_effective_slay(player, weapon, ts->target); + iadd1 = add_random_effective_slay(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_slays[iadd0] > oi_avg && ts->oi_slays[iadd1] > oi_avg); + /* Whichever is better should take effect. */ + oi_avg = (ts->oi_slays[iadd0] > ts->oi_slays[iadd1]) + ? ts->oi_slays[iadd0] : ts->oi_slays[iadd1]; + check_averages("melee; two useful slays;", oi_avg, mc_avg, mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_effective_brand(player, weapon, ts->target); + iadd1 = add_random_ineffective_brand(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + ts->oi_brands[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg && ts->oi_brands[iadd1] > oi_avg); + check_averages("melee; one useful brand; one useless brand;", + ts->oi_brands[iadd0], mc_avg, mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_effective_brand(player, weapon, ts->target); + iadd1 = add_random_ineffective_slay(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_brands[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg && ts->oi_slays[iadd1] > oi_avg); + check_averages("melee; one useful brand; one useless slay;", + ts->oi_brands[iadd0], mc_avg, mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_ineffective_brand(player, weapon, ts->target); + iadd1 = add_random_effective_slay(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_brands[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg && ts->oi_slays[iadd1] > oi_avg); + check_averages("melee; one useless brand; one useful slay;", + ts->oi_slays[iadd1], mc_avg, mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_effective_slay(player, weapon, ts->target); + iadd1 = add_random_ineffective_slay(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_slays[iadd0] > oi_avg && ts->oi_slays[iadd1] > oi_avg); + check_averages("melee; one useful slay; one useless slay;", + ts->oi_slays[iadd0], mc_avg, mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_ineffective_brand(player, weapon, ts->target); + iadd1 = add_random_ineffective_brand(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + ts->oi_brands[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg && ts->oi_brands[iadd1] > oi_avg); + check_averages("melee; two useless brands;", oi_avg, mc_avg, mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_ineffective_brand(player, weapon, ts->target); + iadd1 = add_random_ineffective_slay(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_brands[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg && ts->oi_slays[iadd1] > oi_avg); + check_averages("melee; one useless brand; one useless slay;", + oi_avg, mc_avg, mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_ineffective_slay(player, weapon, ts->target); + iadd1 = add_random_ineffective_slay(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_slays[iadd0] > oi_avg && ts->oi_slays[iadd1] > oi_avg); + check_averages("melee; two useless slays;", oi_avg, mc_avg, mc_var); + + /* Check armed combat with an off-weapon brand. */ + iadd0 = add_random_effective_brand(player, gloves, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + wipe_brands_slays(weapon); + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, true); + require(ts->oi_brands[iadd0] > oi_avg); + check_averages("melee; useful brand on gloves;", ts->oi_brands[iadd0], + mc_avg, mc_var); + + wipe_brands_slays(gloves); + iadd0 = add_random_ineffective_brand(player, gloves, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, true); + require(ts->oi_brands[iadd0] > oi_avg); + check_averages("melee; useless brand on gloves;", oi_avg, mc_avg, + mc_var); + + iadd0 = add_random_effective_temporary_brand(player, ts->target); + require(iadd0 >= 0 && iadd0 < TMD_MAX); + ts->oi_brands[timed_effects[iadd0].temp_brand] = 0; + wipe_brands_slays(gloves); + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, true); + require(ts->oi_brands[timed_effects[iadd0].temp_brand] > oi_avg); + oi_avg = ts->oi_brands[timed_effects[iadd0].temp_brand]; + check_averages("melee; useful temporary brand;", oi_avg, mc_avg, + mc_var); + + remove_all_temporary_brands_and_slays(player); + iadd0 = add_random_ineffective_temporary_brand(player, ts->target); + require(iadd0 >= 0 && iadd0 < TMD_MAX); + ts->oi_brands[timed_effects[iadd0].temp_brand] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, true); + require(ts->oi_brands[timed_effects[iadd0].temp_brand] > oi_avg); + check_averages("melee; useless temporary brand;", oi_avg, mc_avg, + mc_var); + + /* Check armed combat with an off-weapon slay. */ + remove_all_temporary_brands_and_slays(player); + iadd0 = add_random_effective_slay(player, gloves, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, true); + require(ts->oi_slays[iadd0] > oi_avg); + check_averages("melee; useful slay on gloves;", ts->oi_slays[iadd0], + mc_avg, mc_var); + + wipe_brands_slays(gloves); + iadd0 = add_random_ineffective_slay(player, gloves, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, true); + require(ts->oi_slays[iadd0] > oi_avg); + check_averages("melee; useless slay on gloves;", oi_avg, mc_avg, + mc_var); + + wipe_brands_slays(gloves); + iadd0 = add_random_effective_temporary_slay(player, ts->target); + require(iadd0 >= 0 && iadd0 < TMD_MAX); + ts->oi_slays[timed_effects[iadd0].temp_slay] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, true); + require(ts->oi_slays[timed_effects[iadd0].temp_slay] > oi_avg); + oi_avg = ts->oi_slays[timed_effects[iadd0].temp_slay]; + check_averages("melee; useful temporary slay;", oi_avg, + mc_avg, mc_var); + + remove_all_temporary_brands_and_slays(player); + iadd0 = add_random_ineffective_temporary_slay(player, ts->target); + require(iadd0 >= 0 && iadd0 < TMD_MAX); + ts->oi_slays[timed_effects[iadd0].temp_slay] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, false); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, true); + require(ts->oi_slays[timed_effects[iadd0].temp_slay] > oi_avg); + check_averages("melee; useless temporary slay;", oi_avg, + mc_avg, mc_var); + + /* Take off the gloves. */ + player->body.slots[gloves_slot].obj = old_gloves; + if (!old_gloves) { + assert(player->upkeep->equip_cnt > 0); + --player->upkeep->equip_cnt; + } + player->upkeep->total_weight += + ((old_gloves) ? old_gloves->weight : 0) - gloves->weight; + player->upkeep->update |= (PU_BONUS); + update_stuff(player); + + if (gloves->known) { + object_free(gloves->known); + gloves->known = NULL; + } + object_free(gloves); + if (weapon->known) { + object_free(weapon->known); + weapon->known = NULL; + } + object_free(weapon); + remove_all_temporary_brands_and_slays(player); + + ok; +} + +static int test_launched_weapon_damage_info(void *state) +{ + struct info_test_state *ts = (struct info_test_state*)state; + struct object_kind *launcher_kind, *ammo_kind, *gloves_kind; + struct object *launcher, *old_launcher, *ammo, *gloves, *old_gloves; + double mc_avg, mc_var; + int launcher_slot, gloves_slot, ammo_tval, oi_avg, iadd0, iadd1; + bool has_brand_slay, has_nonweap; + + remove_all_temporary_brands_and_slays(player); + + /* Set up the launcher. */ + launcher_kind = get_obj_num(1, false, TV_BOW); + if (!launcher_kind) { + /* + * The depth-dependent lookup did not work so try using the + * first kind. + */ + launcher_kind = lookup_kind(TV_BOW, 1); + } + notnull(launcher_kind); + launcher = object_new(); + object_prep(launcher, launcher_kind, 1, MINIMISE); + /* Give it a damage bonus and extra shots. */ + launcher->to_d = 2; + launcher->modifiers[OBJ_MOD_SHOTS] = 4; + /* Make sure the player knows its properties. */ + launcher->known = object_new(); + wipe_brands_slays(launcher); + object_set_base_known(player, launcher); + object_touch(player, launcher); + object_learn_on_wield(player, launcher); + /* Wield it. */ + launcher_slot = wield_slot(launcher); + old_launcher = player->body.slots[launcher_slot].obj; + player->body.slots[launcher_slot].obj = launcher; + if (!old_launcher) { + ++player->upkeep->equip_cnt; + } + player->upkeep->total_weight += launcher->weight + - ((old_launcher) ? old_launcher->weight : 0); + player->upkeep->update |= (PU_BONUS); + update_stuff(player); + + /* Set up a piece of ammo. */ + if (kf_has(launcher->kind->kind_flags, KF_SHOOTS_SHOTS)) { + ammo_tval = TV_SHOT; + } else if (kf_has(launcher->kind->kind_flags, KF_SHOOTS_ARROWS)) { + ammo_tval = TV_ARROW; + } else if (kf_has(launcher->kind->kind_flags, KF_SHOOTS_BOLTS)) { + ammo_tval = TV_BOLT; + } else { + ammo_tval = 0; + } + require(ammo_tval > 0); + ammo_kind = get_obj_num(1, false, ammo_tval); + if (!ammo_kind) { + /* + * The depth-dependent lookup did not work so try using the + * first kind. + */ + ammo_kind = lookup_kind(ammo_tval, 1); + } + notnull(ammo_kind); + ammo = object_new(); + object_prep(ammo, ammo_kind, 1, MINIMISE); + /* Give it a damage bonus and multiple dice of damage. */ + ammo->to_d = 3; + ammo->dd = 2; + ammo->ds = 4; + /* Make sure the player knows its properties. */ + ammo->known = object_new(); + wipe_brands_slays(ammo); + object_set_base_known(player, ammo); + object_touch(player, ammo); + object_learn_on_wield(player, ammo); + + gloves_kind = get_obj_num(1, false, TV_GLOVES); + if (!gloves_kind) { + /* + * The depth-dependent lookup did not work so try using the + * first kind. + */ + gloves_kind = lookup_kind(TV_GLOVES, 1); + } + notnull(gloves_kind); + gloves = object_new(); + object_prep(gloves, gloves_kind, 1, MINIMISE); + /* Make sure the player knows its properties. */ + gloves->known = object_new(); + wipe_brands_slays(gloves); + object_set_base_known(player, gloves); + object_touch(player, gloves); + object_learn_on_wield(player, gloves); + /* Wield them. */ + gloves_slot = wield_slot(gloves); + old_gloves = player->body.slots[gloves_slot].obj; + player->body.slots[gloves_slot].obj = gloves; + if (!old_gloves) { + ++player->upkeep->equip_cnt; + } + player->upkeep->total_weight += gloves->weight + - ((old_gloves) ? old_gloves->weight : 0); + player->upkeep->update |= (PU_BONUS); + update_stuff(player); + + /* + * Check with a missile and launcher that do not have any slays or + * brands. + */ + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, false); + eq(has_nonweap, false); + check_averages("launched; no brand or slay;", oi_avg, mc_avg, mc_var); + + /* Check with a missile that has a brand and nothing on the launcher. */ + iadd0 = add_random_effective_brand(player, ammo, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg); + check_averages("launched; ammo has one useful brand;", + ts->oi_brands[iadd0], mc_avg, mc_var); + + wipe_brands_slays(ammo); + iadd0 = add_random_ineffective_brand(player, ammo, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg); + check_averages("launched; ammo has one useless brand;", oi_avg, + mc_avg, mc_var); + + /* Check with a launcher that has a brand and nothing on the missile. */ + wipe_brands_slays(ammo); + iadd0 = add_random_effective_brand(player, launcher, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg); + check_averages("launched; launcher has one useful brand;", + ts->oi_brands[iadd0], mc_avg, mc_var); + + wipe_brands_slays(launcher); + iadd0 = add_random_ineffective_brand(player, launcher, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg); + check_averages("launched; launcher has one useless brand;", oi_avg, + mc_avg, mc_var); + + /* Check with a missile that has a slay and nothing on the launcher. */ + wipe_brands_slays(launcher); + iadd0 = add_random_effective_slay(player, ammo, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_slays[iadd0] > oi_avg); + check_averages("launched; ammo has one useful slay;", + ts->oi_slays[iadd0], mc_avg, mc_var); + + wipe_brands_slays(ammo); + iadd0 = add_random_ineffective_slay(player, ammo, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_slays[iadd0] > oi_avg); + check_averages("launched; ammo has one useless slay;", oi_avg, + mc_avg, mc_var); + + /* Check with a launcher that has a slay and nothing on the missile. */ + wipe_brands_slays(ammo); + iadd0 = add_random_effective_slay(player, launcher, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + require(ts->oi_slays[iadd0] > oi_avg); + eq(has_brand_slay, true); + eq(has_nonweap, false); + check_averages("launched; launcher has one useful slay;", + ts->oi_slays[iadd0], mc_avg, mc_var); + + wipe_brands_slays(launcher); + iadd0 = add_random_ineffective_slay(player, launcher, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_slays[iadd0] > oi_avg); + check_averages("launched; launcher has one useless slay;", oi_avg, + mc_avg, mc_var); + + /* Check with a missile and launcher that have slays or brands. */ + wipe_brands_slays(launcher); + iadd0 = add_random_effective_brand(player, launcher, ts->target); + iadd1 = add_random_effective_brand(player, ammo, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + ts->oi_brands[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + if (is_similar_brand(iadd0, iadd1)) { + if (brands[iadd0].multiplier > brands[iadd1].multiplier) { + require(ts->oi_brands[iadd0] > oi_avg); + } else if (brands[iadd0].multiplier + < brands[iadd1].multiplier) { + require(ts->oi_brands[iadd1] > oi_avg); + } else { + require(iadd0 == iadd1 + && ts->oi_brands[iadd0] > oi_avg); + } + } else { + require(ts->oi_brands[iadd0] > oi_avg + && ts->oi_brands[iadd1] > oi_avg); + } + /* Whichever is better should take effect. */ + oi_avg = (ts->oi_brands[iadd0] > ts->oi_brands[iadd1]) + ? ts->oi_brands[iadd0] : ts->oi_brands[iadd1]; + check_averages("launched; ammo and launcher have useful brands;", + oi_avg, mc_avg, mc_var); + + wipe_brands_slays(ammo); + wipe_brands_slays(launcher); + iadd0 = add_random_effective_brand(player, launcher, ts->target); + iadd1 = add_random_effective_slay(player, ammo, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_brands[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg && ts->oi_slays[iadd1] > oi_avg); + /* Whichever is better should take effect. */ + oi_avg = (ts->oi_brands[iadd0] > ts->oi_slays[iadd1]) + ? ts->oi_brands[iadd0] : ts->oi_slays[iadd1]; + check_averages("launched; ammo has useful slay; launcher has useful brand;", + oi_avg, mc_avg, mc_var); + + wipe_brands_slays(ammo); + wipe_brands_slays(launcher); + iadd0 = add_random_effective_slay(player, launcher, ts->target); + iadd1 = add_random_effective_brand(player, ammo, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + require(iadd1 > 0 && iadd1 < z_info->brand_max); + ts->oi_slays[iadd0] = 0; + ts->oi_brands[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_slays[iadd0] > oi_avg && ts->oi_brands[iadd1] > oi_avg); + /* Whichever is better should take effect. */ + oi_avg = (ts->oi_slays[iadd0] > ts->oi_brands[iadd1]) + ? ts->oi_slays[iadd0] : ts->oi_brands[iadd1]; + check_averages("launched; ammo has useful brand; launcher has useful slay;", + oi_avg, mc_avg, mc_var); + + wipe_brands_slays(ammo); + wipe_brands_slays(launcher); + iadd0 = add_random_effective_slay(player, launcher, ts->target); + iadd1 = add_random_effective_slay(player, ammo, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + if (same_monsters_slain(iadd0, iadd1)) { + if (slays[iadd0].multiplier > slays[iadd1].multiplier) { + require(ts->oi_slays[iadd0] > oi_avg); + } else if (slays[iadd0].multiplier + < slays[iadd1].multiplier) { + require(ts->oi_slays[iadd1] > oi_avg); + } else { + require(iadd0 == iadd1 && ts->oi_slays[iadd0] > oi_avg); + } + } else { + require(ts->oi_slays[iadd0] > oi_avg + && ts->oi_slays[iadd1] > oi_avg); + } + /* Whichever is better should take effect. */ + oi_avg = (ts->oi_slays[iadd0] > ts->oi_slays[iadd1]) + ? ts->oi_slays[iadd0] : ts->oi_slays[iadd1]; + check_averages("launched; ammo and launcher have useful slay;", + oi_avg, mc_avg, mc_var); + + wipe_brands_slays(ammo); + wipe_brands_slays(launcher); + iadd0 = add_random_effective_brand(player, launcher, ts->target); + iadd1 = add_random_ineffective_brand(player, ammo, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + ts->oi_brands[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg); + check_averages("launched; ammo has useless brand; launcher has useful brand;", + ts->oi_brands[iadd0], mc_avg, mc_var); + + wipe_brands_slays(ammo); + wipe_brands_slays(launcher); + iadd0 = add_random_ineffective_brand(player, launcher, ts->target); + iadd1 = add_random_effective_brand(player, ammo, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + ts->oi_brands[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd1] > oi_avg); + check_averages("launched; ammo has useful brand; launcher has useless brand;", + ts->oi_brands[iadd1], mc_avg, mc_var); + + wipe_brands_slays(ammo); + wipe_brands_slays(launcher); + iadd0 = add_random_effective_brand(player, launcher, ts->target); + iadd1 = add_random_ineffective_slay(player, ammo, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_brands[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg); + check_averages("launched; ammo has useless slay; launcher has useful brand;", + ts->oi_brands[iadd0], mc_avg, mc_var); + + wipe_brands_slays(ammo); + wipe_brands_slays(launcher); + iadd0 = add_random_ineffective_brand(player, launcher, ts->target); + iadd1 = add_random_effective_slay(player, ammo, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_brands[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_slays[iadd1] > oi_avg); + check_averages("launched; ammo has useful slay; launcher has useless brand;", + ts->oi_slays[iadd1], mc_avg, mc_var); + + wipe_brands_slays(ammo); + wipe_brands_slays(launcher); + iadd0 = add_random_ineffective_slay(player, launcher, ts->target); + iadd1 = add_random_effective_brand(player, ammo, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + require(iadd1 > 0 && iadd1 < z_info->brand_max); + ts->oi_slays[iadd0] = 0; + ts->oi_brands[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd1] > oi_avg); + check_averages("launched; ammo has useful brand; launcher has useless slay;", + ts->oi_brands[iadd1], mc_avg, mc_var); + + wipe_brands_slays(ammo); + wipe_brands_slays(launcher); + iadd0 = add_random_effective_slay(player, launcher, ts->target); + iadd1 = add_random_ineffective_brand(player, ammo, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + require(iadd1 > 0 && iadd1 < z_info->brand_max); + ts->oi_slays[iadd0] = 0; + ts->oi_brands[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_slays[iadd0] > oi_avg); + check_averages("launched; ammo has useless brand; launcher has useful slay;", + ts->oi_slays[iadd0], mc_avg, mc_var); + + wipe_brands_slays(ammo); + wipe_brands_slays(launcher); + iadd0 = add_random_ineffective_slay(player, launcher, ts->target); + iadd1 = add_random_effective_slay(player, ammo, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_slays[iadd1] > oi_avg); + check_averages("launched; ammo has useful slay; launcher has useless slay;", + ts->oi_slays[iadd1], mc_avg, mc_var); + + wipe_brands_slays(ammo); + wipe_brands_slays(launcher); + iadd0 = add_random_effective_slay(player, launcher, ts->target); + iadd1 = add_random_ineffective_slay(player, ammo, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_slays[iadd0] > oi_avg); + check_averages("launched; ammo has useless slay; launcher has useful slay;", + ts->oi_slays[iadd0], mc_avg, mc_var); + + wipe_brands_slays(launcher); + wipe_brands_slays(ammo); + iadd0 = add_random_ineffective_brand(player, launcher, ts->target); + iadd1 = add_random_ineffective_brand(player, ammo, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + ts->oi_brands[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + if (is_similar_brand(iadd0, iadd1)) { + if (brands[iadd0].multiplier > brands[iadd1].multiplier) { + require(ts->oi_brands[iadd0] > oi_avg); + } else if (brands[iadd0].multiplier + < brands[iadd1].multiplier) { + require(ts->oi_brands[iadd1] > oi_avg); + } else { + require(iadd0 == iadd1 + && ts->oi_brands[iadd0] > oi_avg); + } + } else { + require(ts->oi_brands[iadd0] > oi_avg + && ts->oi_brands[iadd1] > oi_avg); + } + check_averages("launched; ammo and launcher have useless brands;", + oi_avg, mc_avg, mc_var); + + wipe_brands_slays(ammo); + wipe_brands_slays(launcher); + iadd0 = add_random_ineffective_brand(player, launcher, ts->target); + iadd1 = add_random_ineffective_slay(player, ammo, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_brands[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg && ts->oi_slays[iadd1] > oi_avg); + check_averages("launched; ammo has useless slay; launcher has useless brand;", + oi_avg, mc_avg, mc_var); + + wipe_brands_slays(ammo); + wipe_brands_slays(launcher); + iadd0 = add_random_ineffective_slay(player, launcher, ts->target); + iadd1 = add_random_ineffective_brand(player, ammo, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + require(iadd1 > 0 && iadd1 < z_info->brand_max); + ts->oi_slays[iadd0] = 0; + ts->oi_brands[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_slays[iadd0] > oi_avg && ts->oi_brands[iadd1] > oi_avg); + check_averages("launched; ammo has useless brand; launcher has useless slay;", + oi_avg, mc_avg, mc_var); + + wipe_brands_slays(ammo); + wipe_brands_slays(launcher); + iadd0 = add_random_ineffective_slay(player, launcher, ts->target); + iadd1 = add_random_ineffective_slay(player, ammo, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, true); + eq(has_nonweap, false); + if (same_monsters_slain(iadd0, iadd1)) { + if (slays[iadd0].multiplier > slays[iadd1].multiplier) { + require(ts->oi_slays[iadd0] > oi_avg); + } else if (slays[iadd0].multiplier + < slays[iadd1].multiplier) { + require(ts->oi_slays[iadd1] > oi_avg); + } else { + require(iadd0 == iadd1 && ts->oi_slays[iadd0] > oi_avg); + } + } else { + require(ts->oi_slays[iadd0] > oi_avg + && ts->oi_slays[iadd1] > oi_avg); + } + check_averages("launched; ammo and launcher have useless slay;", + oi_avg, mc_avg, mc_var); + + /* Check with an off-weapon slay or brand. Should have no effect. */ + iadd0 = add_random_effective_brand(player, gloves, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + wipe_brands_slays(ammo); + wipe_brands_slays(launcher); + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, false); + eq(has_nonweap, false); + check_averages("launched; useful brand on gloves;", oi_avg, mc_avg, + mc_var); + + iadd0 = add_random_effective_temporary_brand(player, ts->target); + require(iadd0 >= 0 && iadd0 < TMD_MAX); + ts->oi_brands[timed_effects[iadd0].temp_brand] = 0; + wipe_brands_slays(gloves); + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, false); + eq(has_nonweap, false); + check_averages("launched; temporary brand;", oi_avg, mc_avg, mc_var); + + remove_all_temporary_brands_and_slays(player); + iadd0 = add_random_effective_slay(player, gloves, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, false); + eq(has_nonweap, false); + check_averages("launched; useful slay on gloves;", oi_avg, mc_avg, + mc_var); + + wipe_brands_slays(gloves); + iadd0 = add_random_effective_temporary_slay(player, ts->target); + require(iadd0 >= 0 && iadd0 < TMD_MAX); + ts->oi_slays[timed_effects[iadd0].temp_slay] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, ammo, launcher, false); + has_brand_slay = obj_known_damage(ammo, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, false); + eq(has_brand_slay, false); + eq(has_nonweap, false); + check_averages("launched; temporary slay;", oi_avg, mc_avg, mc_var); + + /* Take off the gloves. */ + player->body.slots[gloves_slot].obj = old_gloves; + if (!old_gloves) { + assert(player->upkeep->equip_cnt > 0); + --player->upkeep->equip_cnt; + } + player->upkeep->total_weight += + ((old_gloves) ? old_gloves->weight : 0) - gloves->weight; + player->upkeep->update |= (PU_BONUS); + update_stuff(player); + + /* Take off the launcher. */ + player->body.slots[launcher_slot].obj = old_launcher; + if (!old_launcher) { + assert(player->upkeep->equip_cnt > 0); + --player->upkeep->equip_cnt; + } + player->upkeep->total_weight += + ((old_launcher) ? old_launcher->weight : 0) - launcher->weight; + player->upkeep->update |= (PU_BONUS); + update_stuff(player); + + if (gloves->known) { + object_free(gloves->known); + gloves->known = NULL; + } + object_free(gloves); + if (ammo->known) { + object_free(ammo->known); + } + object_free(ammo); + if (launcher->known) { + object_free(launcher->known); + launcher->known = NULL; + } + object_free(launcher); + remove_all_temporary_brands_and_slays(player); + + ok; +} + +static int test_thrown_weapon_damage_info(void *state) +{ + struct info_test_state *ts = (struct info_test_state*)state; + struct object_kind *weapon_kind, *gloves_kind; + struct object *weapon, *gloves, *old_gloves; + double mc_avg, mc_var; + int gloves_slot, oi_avg, iadd0, iadd1; + bool has_brand_slay, has_nonweap; + + remove_all_temporary_brands_and_slays(player); + weapon_kind = lookup_kind(TV_SWORD, lookup_sval(TV_SWORD, "dagger")); + notnull(weapon_kind); + weapon = object_new(); + object_prep(weapon, weapon_kind, 1, MINIMISE); + /* Make sure the player knows its properties. */ + weapon->known = object_new(); + wipe_brands_slays(weapon); + object_set_base_known(player, weapon); + object_touch(player, weapon); + object_learn_on_wield(player, weapon); + + gloves_kind = get_obj_num(1, false, TV_GLOVES); + if (!gloves_kind) { + /* + * The depth-dependent lookup did not work so try using the + * first kind. + */ + gloves_kind = lookup_kind(TV_GLOVES, 1); + } + notnull(gloves_kind); + gloves = object_new(); + object_prep(gloves, gloves_kind, 1, MINIMISE); + /* Make sure the player knows its properties. */ + gloves->known = object_new(); + wipe_brands_slays(gloves); + object_set_base_known(player, gloves); + object_touch(player, gloves); + object_learn_on_wield(player, gloves); + /* Wield them. */ + gloves_slot = wield_slot(gloves); + old_gloves = player->body.slots[gloves_slot].obj; + player->body.slots[gloves_slot].obj = gloves; + if (!old_gloves) { + ++player->upkeep->equip_cnt; + } + player->upkeep->total_weight += gloves->weight + - ((old_gloves) ? old_gloves->weight : 0); + player->upkeep->update |= (PU_BONUS); + update_stuff(player); + + /* Check with no brand or slay on the thrown weapon. */ + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, true); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, true); + eq(has_brand_slay, false); + eq(has_nonweap, false); + check_averages("thrown; no brand or slay;", oi_avg, mc_avg, mc_var); + + /* Check with a brand on the thrown weapon. */ + iadd0 = add_random_effective_brand(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, true); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, true); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg); + check_averages("thrown; one useful brand;", ts->oi_brands[iadd0], + mc_avg, mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_ineffective_brand(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, true); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, true); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg); + check_averages("thrown; one useless brand;", oi_avg, mc_avg, mc_var); + + /* Check with a slay on the thrown weapon. */ + iadd0 = add_random_effective_slay(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, true); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, true); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_slays[iadd0] > oi_avg); + check_averages("thrown; one useful slay;", ts->oi_slays[iadd0], + mc_avg, mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_ineffective_slay(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, true); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, true); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_slays[iadd0] > oi_avg); + check_averages("thrown; one useless slay;", oi_avg, mc_avg, mc_var); + + /* Check with more than one brand or slay on the weapon. */ + wipe_brands_slays(weapon); + iadd0 = add_random_effective_brand(player, weapon, ts->target); + iadd1 = add_random_effective_brand(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + ts->oi_brands[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, true); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, true); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg && ts->oi_brands[iadd1] > oi_avg); + /* Whichever is better should take effect. */ + oi_avg = (ts->oi_brands[iadd0] > ts->oi_brands[iadd1]) + ? ts->oi_brands[iadd0] : ts->oi_brands[iadd1]; + check_averages("thrown; two useful brands;", oi_avg, mc_avg, mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_effective_brand(player, weapon, ts->target); + iadd1 = add_random_effective_slay(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_brands[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, true); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, true); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg && ts->oi_slays[iadd1] > oi_avg); + /* Whichever is better should take effect. */ + oi_avg = (ts->oi_brands[iadd0] > ts->oi_slays[iadd1]) + ? ts->oi_brands[iadd0] : ts->oi_slays[iadd1]; + check_averages("thrown; one useful brand; one useful slay;", oi_avg, + mc_avg, mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_effective_slay(player, weapon, ts->target); + iadd1 = add_random_effective_slay(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, true); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, true); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_slays[iadd0] > oi_avg && ts->oi_slays[iadd1] > oi_avg); + /* Whichever is better should take effect. */ + oi_avg = (ts->oi_slays[iadd0] > ts->oi_slays[iadd1]) + ? ts->oi_slays[iadd0] : ts->oi_slays[iadd1]; + check_averages("thrown; two useful slays;", oi_avg, mc_avg, mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_effective_brand(player, weapon, ts->target); + iadd1 = add_random_ineffective_brand(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + ts->oi_brands[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, true); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, true); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg && ts->oi_brands[iadd1] > oi_avg); + check_averages("thrown; one useful brand; one useless brand;", + ts->oi_brands[iadd0], mc_avg, mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_effective_brand(player, weapon, ts->target); + iadd1 = add_random_ineffective_slay(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_brands[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, true); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, true); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg && ts->oi_slays[iadd1] > oi_avg); + check_averages("thrown; one useful brand; one useless slay;", + ts->oi_brands[iadd0], mc_avg, mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_ineffective_brand(player, weapon, ts->target); + iadd1 = add_random_effective_slay(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_brands[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, true); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, true); + require(ts->oi_brands[iadd0] > oi_avg && ts->oi_slays[iadd1] > oi_avg); + eq(has_brand_slay, true); + eq(has_nonweap, false); + check_averages("thrown; one useless brand; one useful slay;", + ts->oi_slays[iadd1], mc_avg, mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_effective_slay(player, weapon, ts->target); + iadd1 = add_random_ineffective_slay(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, true); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, true); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_slays[iadd0] > oi_avg && ts->oi_slays[iadd1] > oi_avg); + check_averages("thrown; one useful slay; one useless slay;", + ts->oi_slays[iadd0], mc_avg, mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_ineffective_brand(player, weapon, ts->target); + iadd1 = add_random_ineffective_brand(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + ts->oi_brands[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, true); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, true); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg && ts->oi_brands[iadd1] > oi_avg); + check_averages("thrown; two useless brands;", oi_avg, mc_avg, mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_ineffective_brand(player, weapon, ts->target); + iadd1 = add_random_ineffective_slay(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_brands[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, true); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, true); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_brands[iadd0] > oi_avg && ts->oi_slays[iadd1] > oi_avg); + check_averages("thrown; one useless brand; one useless slay;", + oi_avg, mc_avg, mc_var); + + wipe_brands_slays(weapon); + iadd0 = add_random_ineffective_slay(player, weapon, ts->target); + iadd1 = add_random_ineffective_slay(player, weapon, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + require(iadd1 > 0 && iadd1 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + ts->oi_slays[iadd1] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, true); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, true); + eq(has_brand_slay, true); + eq(has_nonweap, false); + require(ts->oi_slays[iadd0] > oi_avg && ts->oi_slays[iadd1] > oi_avg); + check_averages("thrown; two useless slays;", oi_avg, mc_avg, mc_var); + + /* Check with an off-weapon slay or brand. Should have no effect. */ + iadd0 = add_random_effective_brand(player, gloves, ts->target); + require(iadd0 > 0 && iadd0 < z_info->brand_max); + ts->oi_brands[iadd0] = 0; + wipe_brands_slays(weapon); + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, true); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, true); + eq(has_brand_slay, false); + eq(has_nonweap, false); + check_averages("thrown; useful brand on gloves;", oi_avg, mc_avg, + mc_var); + + iadd0 = add_random_effective_temporary_brand(player, ts->target); + require(iadd0 >= 0 && iadd0 < TMD_MAX); + ts->oi_brands[timed_effects[iadd0].temp_brand] = 0; + wipe_brands_slays(gloves); + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, true); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, true); + eq(has_brand_slay, false); + eq(has_nonweap, false); + check_averages("thrown; temporary brand;", oi_avg, mc_avg, mc_var); + + remove_all_temporary_brands_and_slays(player); + iadd0 = add_random_effective_slay(player, gloves, ts->target); + require(iadd0 > 0 && iadd0 < z_info->slay_max); + ts->oi_slays[iadd0] = 0; + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, true); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, true); + eq(has_brand_slay, false); + eq(has_nonweap, false); + check_averages("thrown; useful slay on gloves;", oi_avg, mc_avg, + mc_var); + + remove_all_temporary_brands_and_slays(player); + iadd0 = add_random_effective_temporary_slay(player, ts->target); + require(iadd0 >= 0 && iadd0 < TMD_MAX); + ts->oi_slays[timed_effects[iadd0].temp_slay] = 0; + wipe_brands_slays(gloves); + collect_damage_results(&mc_avg, &mc_var, ts->damage_results, player, + ts->target, weapon, NULL, true); + has_brand_slay = obj_known_damage(weapon, &oi_avg, ts->oi_brands, + ts->oi_slays, &has_nonweap, true); + eq(has_brand_slay, false); + eq(has_nonweap, false); + check_averages("thrown; temporary slay;", oi_avg, mc_avg, mc_var); + + /* Take off the gloves. */ + player->body.slots[gloves_slot].obj = old_gloves; + if (!old_gloves) { + assert(player->upkeep->equip_cnt > 0); + --player->upkeep->equip_cnt; + } + player->upkeep->total_weight += + ((old_gloves) ? old_gloves->weight : 0) - gloves->weight; + player->upkeep->update |= (PU_BONUS); + update_stuff(player); + + if (gloves->known) { + object_free(gloves->known); + gloves->known = NULL; + } + object_free(gloves); + if (weapon->known) { + object_free(weapon->known); + weapon->known = NULL; + } + object_free(weapon); + remove_all_temporary_brands_and_slays(player); + + ok; +} + +const char *suite_name = "object/info"; +struct test tests[] = { + { "melee weapon damage info", test_melee_weapon_damage_info }, + { "launched weapon damage info", test_launched_weapon_damage_info }, + { "thrown weapon damage info", test_thrown_weapon_damage_info }, + { NULL, NULL } +}; diff --git a/src/tests/object/suite.mk b/src/tests/object/suite.mk index 3a79981ae..c3e27867e 100644 --- a/src/tests/object/suite.mk +++ b/src/tests/object/suite.mk @@ -1 +1,7 @@ -TESTPROGS += object/alloc object/attack object/util object/pile object/slays +TESTPROGS += \ + object/alloc \ + object/attack \ + object/info \ + object/pile \ + object/slays \ + object/util