diff options
-rw-r--r-- | crawl-ref/source/acr.cc | 3 | ||||
-rw-r--r-- | crawl-ref/source/beam.cc | 2 | ||||
-rw-r--r-- | crawl-ref/source/describe.cc | 2 | ||||
-rw-r--r-- | crawl-ref/source/direct.cc | 6 | ||||
-rw-r--r-- | crawl-ref/source/dungeon.cc | 11 | ||||
-rw-r--r-- | crawl-ref/source/effects.cc | 2 | ||||
-rw-r--r-- | crawl-ref/source/enum.h | 18 | ||||
-rw-r--r-- | crawl-ref/source/externs.h | 42 | ||||
-rw-r--r-- | crawl-ref/source/fight.cc | 3 | ||||
-rw-r--r-- | crawl-ref/source/items.cc | 38 | ||||
-rw-r--r-- | crawl-ref/source/items.h | 3 | ||||
-rw-r--r-- | crawl-ref/source/makeitem.cc | 126 | ||||
-rw-r--r-- | crawl-ref/source/mon-data.h | 2 | ||||
-rw-r--r-- | crawl-ref/source/mon-util.cc | 498 | ||||
-rw-r--r-- | crawl-ref/source/monstuff.cc | 326 | ||||
-rw-r--r-- | crawl-ref/source/monstuff.h | 7 | ||||
-rw-r--r-- | crawl-ref/source/mstuff2.cc | 18 | ||||
-rw-r--r-- | crawl-ref/source/stuff.cc | 23 | ||||
-rw-r--r-- | crawl-ref/source/stuff.h | 1 |
19 files changed, 787 insertions, 344 deletions
diff --git a/crawl-ref/source/acr.cc b/crawl-ref/source/acr.cc index 5aadc5dadd..1c69cb9e9a 100644 --- a/crawl-ref/source/acr.cc +++ b/crawl-ref/source/acr.cc @@ -1865,10 +1865,7 @@ static void decrement_durations() if (you.duration[DUR_GLAMOUR] > 1) //jmf: actually GLAMOUR_RELOAD, like you.duration[DUR_GLAMOUR]--; // the breath weapon delay else if (you.duration[DUR_GLAMOUR] == 1) - { you.duration[DUR_GLAMOUR] = 0; - //FIXME: cute message or not? - } if (you.duration[DUR_TELEPORT] > 1) you.duration[DUR_TELEPORT]--; diff --git a/crawl-ref/source/beam.cc b/crawl-ref/source/beam.cc index bc71a07450..14f844329e 100644 --- a/crawl-ref/source/beam.cc +++ b/crawl-ref/source/beam.cc @@ -3340,7 +3340,7 @@ static int affect_player( bolt &beam ) you.duration[DUR_LIQUID_FLAMES] += random2avg(7, 3) + 1; } - // simple cases for scroll burns FIXME + // simple cases for scroll burns if (beam.flavour == BEAM_LAVA || beam.name == "hellfire") expose_player_to_element(BEAM_LAVA, burn_power); diff --git a/crawl-ref/source/describe.cc b/crawl-ref/source/describe.cc index b6eced76c3..5fe62d7f5c 100644 --- a/crawl-ref/source/describe.cc +++ b/crawl-ref/source/describe.cc @@ -4872,7 +4872,7 @@ void describe_monsters(monsters& mons) description << "$Monster Inventory:$"; has_item = true; } - description << " " << i + description << " " << i << ") " << mitm[mons.inv[i]].name(DESC_NOCAP_A, false, true); } } diff --git a/crawl-ref/source/direct.cc b/crawl-ref/source/direct.cc index 7598971924..1f4529fefc 100644 --- a/crawl-ref/source/direct.cc +++ b/crawl-ref/source/direct.cc @@ -1565,7 +1565,7 @@ static void describe_cell(int mx, int my) const int mon_wep = menv[i].inv[MSLOT_WEAPON]; const int mon_arm = menv[i].inv[MSLOT_ARMOUR]; - mprf("%s. ('v' to describe)", str_monam(menv[i], DESC_CAP_A).c_str()); + mprf("%s.", str_monam(menv[i], DESC_CAP_A).c_str()); if (menv[i].type != MONS_DANCING_WEAPON && mon_wep != NON_ITEM) { @@ -1577,10 +1577,10 @@ static void describe_cell(int mx, int my) // 2-headed ogres can wield 2 weapons if ((menv[i].type == MONS_TWO_HEADED_OGRE || menv[i].type == MONS_ETTIN) - && menv[i].inv[MSLOT_MISSILE] != NON_ITEM) + && menv[i].inv[MSLOT_ALT_WEAPON] != NON_ITEM) { msg << " and " - << mitm[menv[i].inv[MSLOT_MISSILE]].name(DESC_NOCAP_A); + << mitm[menv[i].inv[MSLOT_ALT_WEAPON]].name(DESC_NOCAP_A); } msg << "."; mpr(msg.str().c_str()); diff --git a/crawl-ref/source/dungeon.cc b/crawl-ref/source/dungeon.cc index eb937b5b4e..e45a663084 100644 --- a/crawl-ref/source/dungeon.cc +++ b/crawl-ref/source/dungeon.cc @@ -1507,12 +1507,14 @@ static void place_traps(int level_number) if (env.trap[i].type != TRAP_UNASSIGNED) continue; + int tries = 200; do { - env.trap[i].x = 10 + random2(GXM - 20); - env.trap[i].y = 10 + random2(GYM - 20); + env.trap[i].x = random2(GXM); + env.trap[i].y = random2(GYM); } - while (grd[env.trap[i].x][env.trap[i].y] != DNGN_FLOOR); + while (grd[env.trap[i].x][env.trap[i].y] != DNGN_FLOOR + && --tries > 0); trap_type &trap_type = env.trap[i].type; trap_type = random_trap_for_level(level_number); @@ -5895,9 +5897,6 @@ bool place_specific_trap(int spec_x, int spec_y, grd[spec_x][spec_y] = DNGN_UNDISCOVERED_TRAP; return true; } - - if (tcount >= MAX_TRAPS - 1) - return false; } return false; diff --git a/crawl-ref/source/effects.cc b/crawl-ref/source/effects.cc index e04ba8b87e..656618228e 100644 --- a/crawl-ref/source/effects.cc +++ b/crawl-ref/source/effects.cc @@ -87,7 +87,7 @@ int torment_monsters(int x, int y, int pow, int caster) aux = "Symbol of Torment"; break; case TORMENT_SPWLD: - // FIXME: If we ever make any other weapon / randart + // XXX: If we ever make any other weapon / randart // eligible to torment, this will be incorrect. aux = "Sceptre of Torment"; break; diff --git a/crawl-ref/source/enum.h b/crawl-ref/source/enum.h index 4cf476bef2..0dc20e6ff4 100644 --- a/crawl-ref/source/enum.h +++ b/crawl-ref/source/enum.h @@ -1907,7 +1907,6 @@ enum mons_class_flags M_EVIL = (1<<20), // monster vulnerable to holy spells M_UNIQUE = (1<<21), // monster is a unique - M_ACID_SPLASH = (1<<22), // Passive acid splash when hit. M_SPECIAL_ABILITY = (1<<26), // XXX: eventually make these spells? @@ -2480,17 +2479,20 @@ enum mon_holy_type // matches (char) H_foo in mon-util.h, see: monster_holiness( MH_PLANT // plants }; +// Adding slots breaks saves. YHBW. enum mon_inv_type // (int) menv[].inv[] { - MSLOT_WEAPON, - MSLOT_MISSILE, // although it is a second weapon for MONS_TWO_HEADED_OGRE - how to reconcile cleanly? {dlb} + MSLOT_WEAPON, // Primary weapon (melee) + MSLOT_ALT_WEAPON, // Alternate weapon, ranged or second melee weapon + // for monsters that can use two weapons. + MSLOT_MISSILE, MSLOT_ARMOUR, - MSLOT_MISCELLANY, //mv: used for misc. obj. (7 Aug 2001) - MSLOT_POTION, // mv: now used only for potions (7 Aug 2001) - MSLOT_WAND, // + MSLOT_MISCELLANY, + MSLOT_POTION, + MSLOT_WAND, MSLOT_SCROLL, - MSLOT_GOLD, //mv: used for money :) (7 Aug 2001) - NUM_MONSTER_SLOTS = 8 // value must remain 8 for savefile compatibility {dlb} + MSLOT_GOLD, + NUM_MONSTER_SLOTS }; // order of these is important: diff --git a/crawl-ref/source/externs.h b/crawl-ref/source/externs.h index 89b419129c..89bb2b5fd5 100644 --- a/crawl-ref/source/externs.h +++ b/crawl-ref/source/externs.h @@ -521,6 +521,12 @@ public: bool cursed() const; int book_number() const; + // Returns index in mitm array. Results are undefined if this item is + // not in the array! + int index() const; + + bool launched_by(const item_def &launcher) const; + void clear() { *this = item_def(); @@ -980,7 +986,7 @@ public: unsigned char y; unsigned char target_x; unsigned char target_y; - FixedVector<int, 8> inv; + FixedVector<short, NUM_MONSTER_SLOTS> inv; monster_spells spells; mon_attitude_type attitude; beh_type behaviour; @@ -1052,8 +1058,32 @@ public: int damage_brand(int attk = -1); item_def *slot_item(equipment_type eq); + item_def *mslot_item(mon_inv_type sl); item_def *weapon(int which_attack = -1); + item_def *launcher(); + item_def *missiles(); + int missile_count(); item_def *shield(); + void wield_melee_weapon(int near = -1); + void swap_weapons(int near = -1); + + bool pickup_item(item_def &item, int near = -1, bool force = false); + void pickup_message(const item_def &item, int near); + bool pickup_wand(item_def &item, int near); + bool pickup_scroll(item_def &item, int near); + bool pickup_potion(item_def &item, int near); + bool pickup_gold(item_def &item, int near); + bool pickup_launcher(item_def &launcher, int near); + bool pickup_melee_weapon(item_def &item, int near); + bool pickup_throwable_weapon(item_def &item, int near); + bool pickup_weapon(item_def &item, int near, bool force); + bool pickup_missile(item_def &item, int near); + bool eat_corpse(item_def &carrion, int near); + void equip(const item_def &item, int slot, int near = -1); + void unequip(const item_def &item, int slot, int near = -1); + + bool can_use_missile(const item_def &item) const; + std::string name(description_level_type type) const; std::string name(description_level_type type, bool force_visible) const; std::string pronoun(pronoun_type pro) const; @@ -1123,6 +1153,16 @@ public: private: void init_with(const monsters &mons); + void swap_slots(mon_inv_type a, mon_inv_type b); + bool need_message(int &near) const; + + bool pickup(item_def &item, int slot, int near, bool force_merge = false); + void equip_weapon(const item_def &item, int near); + + bool drop_item(int eslot, int near); + bool wants_weapon(const item_def &item) const; + bool can_throw_rocks() const; + void lose_pickup_energy(); }; struct cloud_struct diff --git a/crawl-ref/source/fight.cc b/crawl-ref/source/fight.cc index ddda531a92..f8d8cab9e3 100644 --- a/crawl-ref/source/fight.cc +++ b/crawl-ref/source/fight.cc @@ -3020,6 +3020,9 @@ void melee_attack::mons_perform_attack_rounds() { const int nrounds = atk->type == MONS_HYDRA? atk->number : 4; const coord_def pos = defender->pos(); + + // Melee combat, tell attacker to wield its melee weapon. + atk->wield_melee_weapon(); for (attack_number = 0; attack_number < nrounds; ++attack_number) { diff --git a/crawl-ref/source/items.cc b/crawl-ref/source/items.cc index 79048b4938..85c5e9b3c1 100644 --- a/crawl-ref/source/items.cc +++ b/crawl-ref/source/items.cc @@ -1167,22 +1167,27 @@ unsigned long ident_flags(const item_def &item) return (flags); } -bool items_stack( const item_def &item1, const item_def &item2 ) +bool items_stack( const item_def &item1, const item_def &item2, + bool force_merge ) { // both items must be stackable - if (!is_stackable_item( item1 ) || !is_stackable_item( item2 )) + if (!force_merge + && (!is_stackable_item( item1 ) || !is_stackable_item( item2 ))) return (false); // base and sub-types must always be the same to stack if (item1.base_type != item2.base_type || item1.sub_type != item2.sub_type) return (false); + ASSERT(force_merge || item1.base_type != OBJ_WEAPONS); + if (item1.base_type == OBJ_GOLD) return (true); // These classes also require pluses and special - if (item1.base_type == OBJ_MISSILES - || item1.base_type == OBJ_MISCELLANY) // only runes + if (item1.base_type == OBJ_WEAPONS // only throwing weapons + || item1.base_type == OBJ_MISSILES + || item1.base_type == OBJ_MISCELLANY) // only runes { if (item1.plus != item2.plus || item1.plus2 != item2.plus2 @@ -1445,6 +1450,19 @@ void move_item_to_grid( int *const obj, int x, int y ) } } } + // Non-stackable item that's been fudge-stacked (monster throwing weapons). + // Explode the stack when dropped. + else if (mitm[*obj].quantity > 1) + { + while (mitm[*obj].quantity > 1) + { + // If we can't copy the items out, we lose the surplus. + if (copy_item_to_grid(mitm[*obj], x, y, 1, false)) + --mitm[*obj].quantity; + else + mitm[*obj].quantity = 1; + } + } ASSERT( *obj != NON_ITEM ); @@ -2920,3 +2938,15 @@ bool item_def::cursed() const { return (item_cursed(*this)); } + +bool item_def::launched_by(const item_def &launcher) const +{ + if (base_type != OBJ_MISSILES) + return (false); + return (sub_type == fires_ammo_type(launcher)); +} + +int item_def::index() const +{ + return (this - mitm.buffer()); +} diff --git a/crawl-ref/source/items.h b/crawl-ref/source/items.h index e16d756ee3..d5953b523b 100644 --- a/crawl-ref/source/items.h +++ b/crawl-ref/source/items.h @@ -29,7 +29,8 @@ void move_item_to_grid( int *const obj, int x, int y ); void move_item_stack_to_grid( int x, int y, int targ_x, int targ_y ); int move_item_to_player( int obj, int quant_got, bool quiet = false ); bool is_stackable_item( const item_def &item ); -bool items_stack( const item_def &item1, const item_def &item2 ); +bool items_stack( const item_def &item1, const item_def &item2, + bool force = false ); item_def find_item_type(object_class_type base_type, std::string name); diff --git a/crawl-ref/source/makeitem.cc b/crawl-ref/source/makeitem.cc index b65ee8991a..997b0b90f1 100644 --- a/crawl-ref/source/makeitem.cc +++ b/crawl-ref/source/makeitem.cc @@ -1455,8 +1455,9 @@ int items( int allow_uniques, // not just true-false, if (one_chance_in(4)) { - set_weapon_special(p, (coinflip() ? SPWPN_FLAMING - : SPWPN_FREEZING)); + set_weapon_special(p, + (coinflip() ? SPWPN_FLAMING + : SPWPN_FREEZING)); } if (one_chance_in(8)) @@ -2896,7 +2897,10 @@ static bool weapon_is_visibly_special(const item_def &item) && get_equip_race(item) == 0; } -static void give_monster_item(monsters *mon, int thing, bool force_item = false) +static void give_monster_item( + monsters *mon, int thing, + bool force_item = false, + bool (monsters::*pickupfn)(item_def&, int) = NULL) { item_def &mthing = mitm[thing]; @@ -2905,62 +2909,10 @@ static void give_monster_item(monsters *mon, int thing, bool force_item = false) mthing.link = NON_ITEM; unset_ident_flags(mthing, ISFLAG_IDENT_MASK); - switch (mthing.base_type) - { - case OBJ_WEAPONS: - { - const int slot = mon->inv[MSLOT_WEAPON] == NON_ITEM? 0 : 1; - mon->inv[slot] = thing; - break; - } - case OBJ_MISSILES: - mon->inv[MSLOT_MISSILE] = thing; - break; - case OBJ_SCROLLS: - mon->inv[MSLOT_SCROLL] = thing; - break; - case OBJ_GOLD: - mon->inv[MSLOT_GOLD] = thing; - break; - case OBJ_POTIONS: - mon->inv[MSLOT_POTION] = thing; - break; - case OBJ_MISCELLANY: - mon->inv[MSLOT_MISCELLANY] = thing; - break; - case OBJ_WANDS: - mon->inv[MSLOT_WAND] = thing; - break; - case OBJ_ARMOUR: - { - mon->inv[MSLOT_ARMOUR] = thing; - - mon->ac += property( mthing, PARM_AC ); - - const int armour_plus = mthing.plus; - - ASSERT(abs(armour_plus) < 20); - - if (abs(armour_plus) < 20) - mon->ac += armour_plus; - - mon->ev += property( mthing, PARM_EVASION ) / 2; - - if (mon->ev < 1) - mon->ev = 1; // This *shouldn't* happen. - - break; - } - default: - break; - } - const mon_holy_type mholy = mons_holiness(mon); - if (get_weapon_brand( mthing ) == SPWPN_PROTECTION ) - mon->ac += 5; - else if (get_weapon_brand(mthing) == SPWPN_DISRUPTION - && mholy == MH_UNDEAD) + if (get_weapon_brand(mthing) == SPWPN_DISRUPTION + && mholy == MH_UNDEAD) { set_item_ego_type( mthing, OBJ_WEAPONS, SPWPN_NORMAL ); } @@ -2970,6 +2922,19 @@ static void give_monster_item(monsters *mon, int thing, bool force_item = false) set_item_ego_type( mthing, OBJ_WEAPONS, SPWPN_NORMAL ); } + const int speed_inc = mon->speed_increment; + if (!(pickupfn? (mon->*pickupfn)(mthing, false) + : mon->pickup_item(mthing, false, true))) + { +#ifdef DEBUG_DIAGNOSTICS + mprf(MSGCH_WARN, "Destroying %s because %s doesn't want it!", + mthing.name(DESC_PLAIN).c_str(), mon->name(DESC_PLAIN).c_str()); +#endif + destroy_item(thing); + return ; + } + mon->speed_increment = speed_inc; + if (!force_item || mthing.colour == BLACK) item_colour( mthing ); } @@ -3646,11 +3611,10 @@ static void give_ammo(monsters *mon, int level, { // mv: gives ammunition // note that item_race is not reset for this section - if (mon->inv[MSLOT_WEAPON] != NON_ITEM - && is_range_weapon( mitm[mon->inv[MSLOT_WEAPON]] )) + if (const item_def *launcher = mon->launcher()) { const object_class_type xitc = OBJ_MISSILES; - const int xitt = fires_ammo_type(mitm[mon->inv[MSLOT_WEAPON]]); + const int xitt = fires_ammo_type(*launcher); const int thing_created = items( 0, xitc, xitt, true, level, item_race ); @@ -3665,11 +3629,49 @@ static void give_ammo(monsters *mon, int level, SPMSL_CURARE : SPMSL_POISONED); - mitm[thing_created].x = 0; - mitm[thing_created].y = 0; mitm[thing_created].flags = 0; give_monster_item(mon, thing_created); } // end if needs ammo + else + { + // Give some monsters throwing weapons. + int weap_type = WPN_UNKNOWN; + int qty = 0; + switch (mon->type) + { + case MONS_ORC_WARRIOR: + if (one_chance_in( + you.where_are_you == BRANCH_ORCISH_MINES? 9 : 20)) + { + weap_type = + random_choose(WPN_HAND_AXE, WPN_SPEAR, -1); + qty = random_range(4, 8); + } + break; + + case MONS_ORC: + if (one_chance_in(20)) + { + weap_type = + random_choose(WPN_HAND_AXE, WPN_SPEAR, -1); + qty = random_range(2, 5); + } + break; + } + + if (weap_type == WPN_UNKNOWN) + return ; + + const int thing_created = + items( 0, OBJ_WEAPONS, weap_type, true, level, item_race ); + if (thing_created != NON_ITEM) + { + mitm[thing_created].quantity = qty; + mitm[thing_created].flags = 0; + give_monster_item(mon, thing_created, false, + &monsters::pickup_throwable_weapon); + } + } } void give_armour(monsters *mon, int level) diff --git a/crawl-ref/source/mon-data.h b/crawl-ref/source/mon-data.h index 921df2c19c..0970b58672 100644 --- a/crawl-ref/source/mon-data.h +++ b/crawl-ref/source/mon-data.h @@ -500,7 +500,7 @@ 1500, 15, MONS_OGRE, MONS_TWO_HEADED_OGRE, MH_NATURAL, -4, { {AT_HIT, AF_PLAIN, 17}, {AT_HIT, AF_PLAIN, 13}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} }, { 6, 3, 5, 0 }, - 1, 4, 8, 7, MST_NO_SPELLS, CE_CONTAMINATED, Z_BIG, S_SHOUT2, I_NORMAL, + 1, 4, 10, 7, MST_NO_SPELLS, CE_CONTAMINATED, Z_BIG, S_SHOUT2, I_NORMAL, MONUSE_STARTING_EQUIPMENT, SIZE_LARGE } , diff --git a/crawl-ref/source/mon-util.cc b/crawl-ref/source/mon-util.cc index 42e95001b3..4e558e789a 100644 --- a/crawl-ref/source/mon-util.cc +++ b/crawl-ref/source/mon-util.cc @@ -1606,6 +1606,11 @@ bool mons_wields_two_weapons(const monsters *m) return (m->type == MONS_TWO_HEADED_OGRE || m->type == MONS_ETTIN); } +bool mons_eats_corpses(const monsters *m) +{ + return (m->type == MONS_NECROPHAGE || m->type == MONS_GHOUL); +} + bool mons_is_summoned(const monsters *m) { return (m->has_ench(ENCH_ABJ)); @@ -1615,7 +1620,7 @@ bool mons_is_summoned(const monsters *m) // caller's responsibility. int mons_offhand_weapon_index(const monsters *m) { - return (m->inv[1]); + return (m->inv[MSLOT_ALT_WEAPON]); } int mons_base_damage_brand(const monsters *m) @@ -2283,6 +2288,27 @@ int monsters::damage_brand(int which_attack) return (!is_range_weapon(*mweap)? get_weapon_brand(*mweap) : SPWPN_NORMAL); } +item_def *monsters::missiles() +{ + return (inv[MSLOT_MISSILE] != NON_ITEM? &mitm[inv[MSLOT_MISSILE]] : NULL); +} + +int monsters::missile_count() +{ + if (const item_def *missile = missiles()) + return (missile->quantity); + return (0); +} + +item_def *monsters::launcher() +{ + item_def *weap = mslot_item(MSLOT_WEAPON); + if (weap && is_range_weapon(*weap)) + return (weap); + weap = mslot_item(MSLOT_ALT_WEAPON); + return (weap && is_range_weapon(*weap)? weap : NULL); +} + item_def *monsters::weapon(int which_attack) { if (which_attack > 1) @@ -2306,20 +2332,482 @@ item_def *monsters::weapon(int which_attack) return (weap == NON_ITEM? NULL : &mitm[weap]); } -static int equip_slot_to_mslot(equipment_type eq) + +bool monsters::can_throw_rocks() const +{ + return (type == MONS_STONE_GIANT || type == MONS_CYCLOPS); +} + +bool monsters::can_use_missile(const item_def &item) const +{ + // Pretty simplistic at the moment. We allow monsters to pick up + // missiles without the corresponding launcher, assuming that sufficient + // wandering may get them to stumble upon the launcher. + + if (item.base_type == OBJ_WEAPONS) + return (is_throwable(item)); + + if (item.base_type != OBJ_MISSILES) + return (false); + + if (item.sub_type == MI_LARGE_ROCK && !can_throw_rocks()) + return (false); + + return (true); +} + +void monsters::swap_slots(mon_inv_type a, mon_inv_type b) +{ + const int swap = inv[a]; + inv[a] = inv[b]; + inv[b] = swap; +} + +void monsters::equip_weapon(const item_def &item, int near) +{ + const int brand = get_weapon_brand(item); + if (brand == SPWPN_PROTECTION) + ac += 5; + + if (brand != SPWPN_NORMAL && need_message(near)) + { + switch (brand) + { + case SPWPN_FLAMING: + mpr("It bursts into flame!"); + break; + case SPWPN_FREEZING: + mpr("It glows with a cold blue light!"); + break; + case SPWPN_HOLY_WRATH: + mpr("It softly glows with a divine radiance!"); + break; + case SPWPN_ELECTROCUTION: + mpr("You hear the crackle of electricity."); + break; + case SPWPN_VENOM: + mpr("It begins to drip with poison!"); + break; + case SPWPN_DRAINING: + mpr("You sense an unholy aura."); + break; + case SPWPN_FLAME: + mpr("It glows red for a moment."); + break; + case SPWPN_FROST: + mpr("It is covered in frost."); + break; + case SPWPN_DISRUPTION: + mpr("You sense a holy aura."); + break; + case SPWPN_RETURNING: + mpr("It wiggles slightly."); + break; + } + } +} + +void monsters::equip(const item_def &item, int slot, int near) +{ + switch (item.base_type) + { + case OBJ_WEAPONS: + if (need_message(near)) + mprf("%s wields %s.", name(DESC_CAP_THE).c_str(), + item.name(DESC_NOCAP_A).c_str()); + equip_weapon(item, near); + break; + case OBJ_ARMOUR: + { + ac += property( item, PARM_AC ); + + const int armour_plus = item.plus; + ASSERT(abs(armour_plus) < 20); + if (abs(armour_plus) < 20) + ac += armour_plus; + ev += property( item, PARM_EVASION ) / 2; + + if (ev < 1) + ev = 1; // This *shouldn't* happen. + break; + } + default: + break; + } +} + +void monsters::unequip(const item_def &item, int slot, int near) +{ + // XXX: Handle armour removal when armour swapping is implemented. + switch (item.base_type) + { + case OBJ_WEAPONS: + if (get_weapon_brand(item) == SPWPN_PROTECTION) + ac -= 5; + break; + + default: + break; + } +} + +void monsters::lose_pickup_energy() +{ + if (speed_increment > 25 && speed < speed_increment) + speed_increment -= speed; +} + +void monsters::pickup_message(const item_def &item, int near) +{ + if (need_message(near)) + mprf("%s picks up %s.", + name(DESC_CAP_THE).c_str(), + item.base_type == OBJ_GOLD? "some gold" + : item.name(DESC_NOCAP_A).c_str()); +} + +bool monsters::pickup(item_def &item, int slot, int near, bool force_merge) +{ + if (inv[slot] != NON_ITEM) + { + if (items_stack(item, mitm[inv[slot]], force_merge)) + { + pickup_message(item, near); + inc_mitm_item_quantity( inv[slot], item.quantity ); + destroy_item(item.index()); + equip(item, slot, near); + lose_pickup_energy(); + return (true); + } + return (false); + } + + const int index = item.index(); + unlink_item(index); + inv[slot] = index; + + pickup_message(item, near); + equip(item, slot, near); + lose_pickup_energy(); + return (true); +} + +bool monsters::drop_item(int eslot, int near) +{ + if (eslot < 0 || eslot >= NUM_MONSTER_SLOTS) + return (false); + + int index = inv[eslot]; + if (index == NON_ITEM) + return (true); + + // Cannot drop cursed weapon or armour. + if ((eslot == MSLOT_WEAPON || eslot == MSLOT_ARMOUR) + && mitm[index].cursed()) + return (false); + + const std::string iname = mitm[index].name(DESC_NOCAP_A); + move_item_to_grid(&index, x, y); + + if (index == inv[eslot]) + return (false); + + if (need_message(near)) + mprf("%s drops %s.", name(DESC_CAP_THE).c_str(), iname.c_str()); + + inv[eslot] = NON_ITEM; + return (true); +} + +bool monsters::pickup_launcher(item_def &launch, int near) +{ + const int mdam_rating = mons_weapon_damage_rating(launch); + const missile_type mt = fires_ammo_type(launch); + int eslot = -1; + for (int i = MSLOT_WEAPON; i <= MSLOT_ALT_WEAPON; ++i) + { + if (const item_def *elaunch = mslot_item(static_cast<mon_inv_type>(i))) + { + if (is_range_weapon(*elaunch)) + continue; + + return (fires_ammo_type(*elaunch) == mt + && mons_weapon_damage_rating(*elaunch) < mdam_rating + && drop_item(i, near) && pickup(launch, i, near)); + } + else + eslot = i; + } + + return (eslot == -1? false : pickup(launch, eslot, near)); +} + +bool monsters::pickup_melee_weapon(item_def &item, int near) +{ + if (mons_wields_two_weapons(this)) + { + // If we have either weapon slot free, pick up the weapon. + if (inv[MSLOT_WEAPON] == NON_ITEM) + return pickup(item, MSLOT_WEAPON, near); + else if (inv[MSLOT_ALT_WEAPON] == NON_ITEM) + return pickup(item, MSLOT_ALT_WEAPON, near); + } + + const int mdam_rating = mons_weapon_damage_rating(item); + int eslot = -1; + bool has_melee = false; + for (int i = MSLOT_WEAPON; i <= MSLOT_ALT_WEAPON; ++i) + { + if (const item_def *weap = mslot_item(static_cast<mon_inv_type>(i))) + { + if (is_range_weapon(*weap)) + continue; + has_melee = true; + if (mons_weapon_damage_rating(*weap) < mdam_rating) + return (drop_item(i, near) && pickup(item, i, near)); + } + else + eslot = i; + } + + if (eslot == MSLOT_ALT_WEAPON && inv[MSLOT_WEAPON] == NON_ITEM) + eslot = MSLOT_WEAPON; + + return (eslot == -1 || has_melee? false : pickup(item, eslot, near)); +} + +// Arbitrary damage adjustment for quantity of missiles. So sue me. +static int q_adj_damage(int damage, int qty) +{ + return (damage * std::min(qty, 8)); +} + +bool monsters::pickup_throwable_weapon(item_def &item, int near) +{ + if (mslot_item(MSLOT_MISSILE) && pickup(item, MSLOT_MISSILE, near, true)) + return (true); + + item_def *launch = NULL; + const int exist_missile = mons_pick_best_missile(this, &launch, true); + if (exist_missile == NON_ITEM + || (q_adj_damage(mons_missile_damage(launch, &mitm[exist_missile]), + mitm[exist_missile].quantity) + < + q_adj_damage(mons_thrown_weapon_damage(&item), item.quantity))) + { + if (inv[MSLOT_MISSILE] != NON_ITEM && !drop_item(MSLOT_MISSILE, near)) + return (false); + return pickup(item, MSLOT_MISSILE, near); + } + return (false); +} + +bool monsters::wants_weapon(const item_def &weap) const +{ + if (is_fixed_artefact( weap )) + return (false); + + // wimpy monsters (Kob, gob) shouldn't pick up halberds etc + // of course, this also block knives {dlb}: + if ((::mons_species(type) == MONS_KOBOLD + || ::mons_species(type) == MONS_GOBLIN) + && property( weap, PWPN_HIT ) <= 0) + { + return (false); + } + + // Nobody picks up giant clubs: + if (weap.sub_type == WPN_GIANT_CLUB + || weap.sub_type == WPN_GIANT_SPIKED_CLUB) + { + return (false); + } + + const int brand = get_weapon_brand(weap); + const int holy = holiness(); + if (brand == SPWPN_DISRUPTION && holy == MH_UNDEAD) + return (false); + + if (brand == SPWPN_HOLY_WRATH + && (holy == MH_DEMONIC || holy == MH_UNDEAD)) + { + return (false); + } + + return (true); +} + +bool monsters::pickup_weapon(item_def &item, int near, bool force) +{ + if (!force && !wants_weapon(item)) + return (false); + + // Weapon pickup involves: + // - If we have no weapons, always pick this up. + // - If this is a melee weapon and we already have a melee weapon, pick + // it up if it is superior to the one we're carrying (and drop the + // one we have). + // - If it is a ranged weapon, and we already have a ranged weapon, + // pick it up if it is better than the one we have. + // - If it is a throwable weapon, and we're carrying no missiles (or our + // missiles are the same type), pick it up. + + if (is_range_weapon(item)) + return (pickup_launcher(item, near)); + + if (pickup_melee_weapon(item, near)) + return (true); + + return (can_use_missile(item) && pickup_throwable_weapon(item, near)); +} + +bool monsters::pickup_missile(item_def &item, int near) +{ + // XXX: Missile pickup could get a lot smarter if we allow monsters to + // drop their existing missiles and pick up new stuff, but that's too + // much work for now. + + const item_def *miss = missiles(); + if (miss && items_stack(*miss, item)) + return (pickup(item, MSLOT_MISSILE, near)); + + if (!can_use_missile(item)) + return (false); + + return pickup(item, MSLOT_MISSILE, near); +} + +bool monsters::pickup_wand(item_def &item, int near) +{ + return pickup(item, MSLOT_WEAPON, near); +} + +bool monsters::pickup_scroll(item_def &item, int near) +{ + return pickup(item, MSLOT_SCROLL, near); +} + +bool monsters::pickup_potion(item_def &item, int near) +{ + return pickup(item, MSLOT_POTION, near); +} + +bool monsters::pickup_gold(item_def &item, int near) +{ + return pickup(item, MSLOT_GOLD, near); +} + +bool monsters::eat_corpse(item_def &carrion, int near) +{ + if (!mons_eats_corpses(this)) + return (false); + + hit_points += 1 + random2(mons_weight(carrion.plus)) / 100; + + // limited growth factor here -- should 77 really be the cap? {dlb}: + if (hit_points > 100) + hit_points = 100; + + if (hit_points > max_hit_points) + max_hit_points = hit_points; + + if (need_message(near)) + mprf("%s eats %s.", name(DESC_CAP_THE).c_str(), + carrion.name(DESC_NOCAP_THE).c_str()); + + destroy_item( carrion.index() ); + return (true); +} + +bool monsters::pickup_item(item_def &item, int near, bool force) +{ + // Never pick up stuff when we're in battle. + if (behaviour != BEH_WANDER && !force) + return (false); + + // Jellies are not handled here. + switch (item.base_type) + { + case OBJ_WEAPONS: + return pickup_weapon(item, near, force); + case OBJ_ARMOUR: + return pickup(item, MSLOT_ARMOUR, near); + case OBJ_MISSILES: + return pickup_missile(item, near); + case OBJ_WANDS: + return pickup_wand(item, near); + case OBJ_SCROLLS: + return pickup_scroll(item, near); + case OBJ_CORPSES: + return eat_corpse(item, near); + case OBJ_MISCELLANY: + return pickup(item, MSLOT_MISCELLANY, near); + case OBJ_GOLD: + return pickup_gold(item, near); + default: + return (false); + } +} + +bool monsters::need_message(int &near) const +{ + return near != -1? near : (near = visible()); +} + +void monsters::swap_weapons(int near) +{ + const item_def *weap = mslot_item(MSLOT_WEAPON); + const item_def *alt = mslot_item(MSLOT_ALT_WEAPON); + + if (weap) + unequip(*weap, MSLOT_WEAPON, !alt && need_message(near)); + + swap_slots(MSLOT_WEAPON, MSLOT_ALT_WEAPON); + + if (need_message(near)) + { + if (!alt && weap) + mprf("%s unwields %s.", name(DESC_CAP_THE).c_str(), + weap->name(DESC_NOCAP_A).c_str()); + } + + if (alt) + equip(*alt, MSLOT_WEAPON, near); + + // Monsters can swap weapons really fast. :-) + if ((weap || alt) && speed_increment >= 2) + speed_increment -= 2; +} + +void monsters::wield_melee_weapon(int near) +{ + const item_def *weap = mslot_item(MSLOT_WEAPON); + if (!weap || (!weap->cursed() && is_range_weapon(*weap))) + { + const item_def *alt = mslot_item(MSLOT_ALT_WEAPON); + if (alt && (!weap || !is_range_weapon(*alt))) + swap_weapons(near); + } +} + +static mon_inv_type equip_slot_to_mslot(equipment_type eq) { switch (eq) { case EQ_WEAPON: return MSLOT_WEAPON; case EQ_BODY_ARMOUR: return MSLOT_ARMOUR; - default: return (-1); + default: return (NUM_MONSTER_SLOTS); } } item_def *monsters::slot_item(equipment_type eq) { - int mslot = equip_slot_to_mslot(eq); - int mindex = mslot == -1? NON_ITEM : inv[mslot]; + return mslot_item(equip_slot_to_mslot(eq)); +} + +item_def *monsters::mslot_item(mon_inv_type mslot) +{ + const int mindex = mslot == NUM_MONSTER_SLOTS? NON_ITEM : inv[mslot]; return (mindex == NON_ITEM? NULL: &mitm[mindex]); } diff --git a/crawl-ref/source/monstuff.cc b/crawl-ref/source/monstuff.cc index 51cf2dea9f..d65704daf9 100644 --- a/crawl-ref/source/monstuff.cc +++ b/crawl-ref/source/monstuff.cc @@ -3253,6 +3253,72 @@ static bool handle_spell( monsters *monster, bolt & beem ) return (true); } // end handle_spell() +// Returns a rough estimate of damage from throwing the wielded weapon. +int mons_thrown_weapon_damage(const item_def *weap) +{ + if (!weap || get_weapon_brand(*weap) != SPWPN_RETURNING) + return (0); + return std::max(0, (property(*weap, PWPN_DAMAGE) + weap->plus2 / 2)); +} + +// Returns a rough estimate of damage from firing/throwing missile. +int mons_missile_damage(const item_def *launch, + const item_def *missile) +{ + if (!missile || (!launch && !is_throwable(*missile))) + return (0); + + const int missile_damage = property(*missile, PWPN_DAMAGE) / 2 + 1; + const int launch_damage = launch? property(*launch, PWPN_DAMAGE) : 0; + return std::max(0, launch_damage + missile_damage); +} + +int mons_weapon_damage_rating(const item_def &launcher) +{ + return (property(launcher, PWPN_DAMAGE) + launcher.plus2); +} + +// Given the monster's current weapon and alt weapon (either or both of +// which may be NULL), works out whether using missiles or throwing the +// main weapon (with returning brand) is better. If using missiles that +// need a launcher, sets *launcher to the launcher. +// +// If the monster has no ranged weapon attack, returns NON_ITEM. +// +int mons_pick_best_missile(monsters *mons, item_def **launcher, + bool ignore_melee) +{ + *launcher = NULL; + item_def *melee = NULL, *launch = NULL; + for (int i = MSLOT_WEAPON; i <= MSLOT_ALT_WEAPON; ++i) + { + if (item_def *item = mons->mslot_item(static_cast<mon_inv_type>(i))) + { + if (is_range_weapon(*item)) + launch = item; + else if (!ignore_melee) + melee = item; + } + } + + const item_def *missiles = mons->missiles(); + if (launch && missiles && !missiles->launched_by(*launch)) + launch = NULL; + + const int tdam = mons_thrown_weapon_damage(melee); + const int fdam = mons_missile_damage(launch, missiles); + + if (!tdam && !fdam) + return (NON_ITEM); + else if (tdam >= fdam) + return (melee->index()); + else + { + *launcher = launch; + return (missiles->index()); + } +} + //--------------------------------------------------------------- // // handle_throw @@ -3264,9 +3330,9 @@ static bool handle_spell( monsters *monster, bolt & beem ) static bool handle_throw(monsters *monster, bolt & beem) { // yes, there is a logic to this ordering {dlb}: - if (monster->has_ench(ENCH_CONFUSION) - || monster->behaviour == BEH_SLEEP - || monster->has_ench(ENCH_SUBMERGED)) + if (monster->incapacitated() + || monster->asleep() + || monster->submerged()) { return (false); } @@ -3274,56 +3340,22 @@ static bool handle_throw(monsters *monster, bolt & beem) if (mons_itemuse(monster->type) < MONUSE_OPEN_DOORS) return (false); - int mon_item; - const int mon_wpn = monster->inv[MSLOT_WEAPON]; - bool returning = false; - - // weapons of returning can be thrown - if ( mon_wpn != NON_ITEM && - get_weapon_brand(mitm[mon_wpn]) == SPWPN_RETURNING ) - { - mon_item = mon_wpn; - returning = true; - } - else - { - mon_item = monster->inv[MSLOT_MISSILE]; - } - - if (mon_item == NON_ITEM || !is_valid_item( mitm[mon_item] )) + if (one_chance_in(5)) return (false); - // don't allow offscreen throwing.. for now. + // don't allow offscreen throwing for now. if (monster->foe == MHITYOU && !mons_near(monster)) return (false); - // poor 2-headed ogres {dlb} - if (monster->type == MONS_TWO_HEADED_OGRE || monster->type == MONS_ETTIN) - return (false); - // recent addition {GDL} - monsters won't throw if they can do melee. // wastes valuable ammo, and most monsters are better at melee anyway. if (adjacent( beem.target_x, beem.target_y, monster->x, monster->y )) return (false); - if (one_chance_in(5)) - return (false); - - // new (GDL) - don't throw idiotic stuff. It's a waste of time. - int wepClass = mitm[mon_item].base_type; - int wepType = mitm[mon_item].sub_type; - - int weapon = monster->inv[MSLOT_WEAPON]; - - int lnchClass = (weapon != NON_ITEM) ? mitm[weapon].base_type : -1; - int lnchType = (weapon != NON_ITEM) ? mitm[weapon].sub_type : 0; - - bool thrown = false; - bool launched = false; + item_def *launcher = NULL; + const int mon_item = mons_pick_best_missile(monster, &launcher); - throw_type( lnchClass, lnchType, wepClass, wepType, launched, thrown ); - - if (!launched && !thrown && !returning) + if (mon_item == NON_ITEM || !is_valid_item(mitm[mon_item])) return (false); // ok, we'll try it. @@ -3341,6 +3373,9 @@ static bool handle_throw(monsters *monster, bolt & beem) // good idea? if (mons_should_fire( beem )) { + if (launcher && launcher != monster->mslot_item(MSLOT_WEAPON)) + monster->swap_weapons(); + beem.name.clear(); return (mons_throw( monster, beem, mon_item )); } @@ -3798,44 +3833,6 @@ void handle_monsters(void) } } // end handle_monster() -static bool monster_wants_weapon(const monsters *monster, const item_def &weap) -{ - if (is_fixed_artefact( weap )) - return (false); - - if (is_random_artefact( weap )) - return (false); - - // wimpy monsters (Kob, gob) shouldn't pick up halberds etc - // of course, this also block knives {dlb}: - if ((mons_species(monster->type) == MONS_KOBOLD - || mons_species(monster->type) == MONS_GOBLIN) - && property( weap, PWPN_HIT ) <= 0) - { - return (false); - } - - // Nobody picks up giant clubs: - if (weap.sub_type == WPN_GIANT_CLUB - || weap.sub_type == WPN_GIANT_SPIKED_CLUB) - { - return (false); - } - - const int brand = get_weapon_brand(weap); - const int holiness = monster->holiness(); - if (brand == SPWPN_DISRUPTION && holiness == MH_UNDEAD) - return (false); - - if (brand == SPWPN_HOLY_WRATH - && (holiness == MH_DEMONIC || holiness == MH_UNDEAD)) - { - return (false); - } - - return (true); -} - static bool is_item_jelly_edible(const item_def &item) { // don't eat artefacts (note that unrandarts are randarts) @@ -3952,172 +3949,15 @@ static bool handle_pickup(monsters *monster) } // end "if jellies" // Note: Monsters only look at top of stacks. - item = igrd[monster->x][monster->y]; - - switch (mitm[item].base_type) + + for (item = igrd[monster->x][monster->y]; item != NON_ITEM; ) { - case OBJ_WEAPONS: - if (monster->inv[MSLOT_WEAPON] != NON_ITEM) - return (false); - - if (!monster_wants_weapon(monster, mitm[item])) - return (false); - - monster->inv[MSLOT_WEAPON] = item; - - if (get_weapon_brand(mitm[monster->inv[MSLOT_WEAPON]]) == SPWPN_PROTECTION) - { - monster->ac += 3; - } - - if (monsterNearby) - { - mprf("%s picks up %s.", str_monam(*monster, DESC_CAP_THE).c_str(), - mitm[monster->inv[MSLOT_WEAPON]].name(DESC_NOCAP_A).c_str()); - } - break; - - case OBJ_MISSILES: - // don't pick up if we're in combat, and there isn't much there - if (mitm[item].quantity < 5 || monster->behaviour != BEH_WANDER) - return (false); - - if (monster->inv[MSLOT_MISSILE] != NON_ITEM - && mitm[monster->inv[MSLOT_MISSILE]].sub_type == mitm[item].sub_type - && mitm[monster->inv[MSLOT_MISSILE]].plus == mitm[item].plus - && mitm[monster->inv[MSLOT_MISSILE]].special == mitm[item].special) - { - if (monsterNearby) - { - mprf("%s picks up %s.", - str_monam(*monster, DESC_CAP_THE).c_str(), - mitm[item].name(DESC_NOCAP_A).c_str()); - } - - inc_mitm_item_quantity( monster->inv[MSLOT_MISSILE], - mitm[item].quantity ); - - dec_mitm_item_quantity( item, mitm[item].quantity ); - return (true); - } - - // nobody bothers to pick up rocks if they don't already have some: - if (mitm[item].sub_type == MI_LARGE_ROCK) - return (false); - - // monsters with powerful melee attacks don't bother - if (mons_damage(monster->type, 0) > 5) - return (false); - - monster->inv[MSLOT_MISSILE] = item; - - if (monsterNearby) - { - mprf("%s picks up %s.", str_monam(*monster, DESC_CAP_THE).c_str(), - mitm[item].name(DESC_NOCAP_A).c_str()); - } - break; - - case OBJ_WANDS: - if (monster->inv[MSLOT_WAND] != NON_ITEM) - return (false); - - monster->inv[MSLOT_WAND] = item; - - if (monsterNearby) - { - mprf("%s picks up %s.", str_monam(*monster, DESC_CAP_THE).c_str(), - mitm[item].name(DESC_NOCAP_A).c_str()); - } - break; - - case OBJ_SCROLLS: - if (monster->inv[MSLOT_SCROLL] != NON_ITEM) - return (false); - - monster->inv[MSLOT_SCROLL] = item; - - if (monsterNearby) - { - mprf("%s picks up %s.", str_monam(*monster, DESC_CAP_THE).c_str(), - mitm[item].name(DESC_NOCAP_A).c_str()); - } - break; - - case OBJ_POTIONS: - if (monster->inv[MSLOT_POTION] != NON_ITEM) - return (false); - - monster->inv[MSLOT_POTION] = item; - - if (monsterNearby) - { - mprf("%s picks up %s.", str_monam(*monster, DESC_CAP_THE).c_str(), - mitm[item].name(DESC_NOCAP_A).c_str()); - } - break; - - case OBJ_CORPSES: - if (monster->type != MONS_NECROPHAGE && monster->type != MONS_GHOUL) - return (false); - - monster->hit_points += 1 + random2(mons_weight(mitm[item].plus))/100; - - // limited growth factor here -- should 77 really be the cap? {dlb}: - if (monster->hit_points > 100) - monster->hit_points = 100; - - if (monster->hit_points > monster->max_hit_points) - monster->max_hit_points = monster->hit_points; - - if (monsterNearby) - { - mprf("%s eats %s.", str_monam(*monster, DESC_CAP_THE).c_str(), - mitm[item].name(DESC_NOCAP_THE).c_str()); - } - - destroy_item( item ); - return (true); - - case OBJ_GOLD: //mv - monsters now pick up gold (19 May 2001) - if (monsterNearby) - { - mprf("%s picks up some gold.", - str_monam(*monster, DESC_CAP_THE).c_str()); - } - - if (monster->inv[MSLOT_GOLD] != NON_ITEM) - { - // transfer gold to monster's object, destroy ground object - inc_mitm_item_quantity( monster->inv[MSLOT_GOLD], - mitm[item].quantity ); - - destroy_item( item ); + item_def &topickup = mitm[item]; + item = topickup.link; + if (monster->pickup_item(topickup, monsterNearby)) return (true); - } - else - { - monster->inv[MSLOT_GOLD] = item; - } - break; - - default: - return (false); } - - // Item has been picked-up, move to monster inventory. - mitm[item].x = 0; - mitm[item].y = 0; - - // Monster's only take the top item of stacks, so relink the - // top item, and unlink the item. - igrd[monster->x][monster->y] = mitm[item].link; - mitm[item].link = NON_ITEM; - - if (monster->speed_increment > 25) - monster->speed_increment -= monster->speed; - - return (true); + return (false); } // end handle_pickup() static void jelly_grows(monsters *monster) diff --git a/crawl-ref/source/monstuff.h b/crawl-ref/source/monstuff.h index 2381e34832..e9edad576b 100644 --- a/crawl-ref/source/monstuff.h +++ b/crawl-ref/source/monstuff.h @@ -162,4 +162,11 @@ void seen_monster(struct monsters *monster); bool shift_monster( struct monsters *mon, int x = 0, int y = 0 ); +int mons_weapon_damage_rating(const item_def &launcher); +int mons_pick_best_missile(monsters *mons, item_def **launcher, + bool ignore_melee = false); +int mons_missile_damage(const item_def *launch, + const item_def *missile); +int mons_thrown_weapon_damage(const item_def *weap); + #endif diff --git a/crawl-ref/source/mstuff2.cc b/crawl-ref/source/mstuff2.cc index e128f70ae2..ff14046cd8 100644 --- a/crawl-ref/source/mstuff2.cc +++ b/crawl-ref/source/mstuff2.cc @@ -1033,6 +1033,8 @@ bool mons_throw(struct monsters *monster, struct bolt &pbolt, int hand_used) int lnchClass = (weapon != NON_ITEM) ? mitm[weapon].base_type : -1; int lnchType = (weapon != NON_ITEM) ? mitm[weapon].sub_type : 0; + const bool skilled = mons_class_flag(monster->type, M_FIGHTER); + item_def item = mitm[hand_used]; // copy changed for venom launchers item.quantity = 1; @@ -1070,13 +1072,13 @@ bool mons_throw(struct monsters *monster, struct bolt &pbolt, int hand_used) // Darts are easy. if (wepClass == OBJ_MISSILES && wepType == MI_DART) { - baseHit = 5; + baseHit = 11; hitMult = 40; damMult = 25; } else { - baseHit = 0; + baseHit = 6; hitMult = 30; damMult = 25; } @@ -1272,15 +1274,23 @@ bool mons_throw(struct monsters *monster, struct bolt &pbolt, int hand_used) // add everything up. pbolt.hit = baseHit + random2avg(exHitBonus, 2) + ammoHitBonus; - pbolt.damage = dice_def( 1, baseDam + random2avg(exDamBonus, 2) + ammoDamBonus ); + pbolt.damage = + dice_def( 1, baseDam + random2avg(exDamBonus, 2) + ammoDamBonus ); if (launched) { pbolt.damage.size += lnchDamBonus; pbolt.hit += lnchHitBonus; } - pbolt.damage.size = diceMult * pbolt.damage.size / 100; + + // Skilled archers get better to-hit and damage. + if (skilled) + { + pbolt.hit = pbolt.hit * 120 / 100; + pbolt.damage.size = pbolt.damage.size * 120 / 100; + } + scale_dice(pbolt.damage); // decrease inventory diff --git a/crawl-ref/source/stuff.cc b/crawl-ref/source/stuff.cc index 615a21a18b..2f9d0c2bcf 100644 --- a/crawl-ref/source/stuff.cc +++ b/crawl-ref/source/stuff.cc @@ -21,6 +21,7 @@ #include "stuff.h" #include "view.h" +#include <cstdarg> #include <sstream> #include <iomanip> @@ -192,6 +193,28 @@ int random_range(int low, int high) return (low + random2(high - low + 1)); } +int random_choose(int first, ...) +{ + va_list args; + va_start(args, first); + + int chosen = first, count = 1, nargs = 100; + + while (nargs-- > 0) + { + const int pick = va_arg(args, int); + if (pick == -1) + break; + if (one_chance_in(++count)) + chosen = pick; + } + + ASSERT(nargs > 0); + + va_end(args); + return (chosen); +} + int random2( int max ) { if (max <= 1) diff --git a/crawl-ref/source/stuff.h b/crawl-ref/source/stuff.h index 618f83d38f..23da3a4bd0 100644 --- a/crawl-ref/source/stuff.h +++ b/crawl-ref/source/stuff.h @@ -34,6 +34,7 @@ int div_rand_round( int num, int den ); bool one_chance_in(int a_million); int random2(int randmax); int random_range(int low, int high); +int random_choose(int first, ...); unsigned long random_int(); int random2avg( int max, int rolls ); int bestroll(int max, int rolls); |