summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--crawl-ref/source/acr.cc3
-rw-r--r--crawl-ref/source/beam.cc2
-rw-r--r--crawl-ref/source/describe.cc2
-rw-r--r--crawl-ref/source/direct.cc6
-rw-r--r--crawl-ref/source/dungeon.cc11
-rw-r--r--crawl-ref/source/effects.cc2
-rw-r--r--crawl-ref/source/enum.h18
-rw-r--r--crawl-ref/source/externs.h42
-rw-r--r--crawl-ref/source/fight.cc3
-rw-r--r--crawl-ref/source/items.cc38
-rw-r--r--crawl-ref/source/items.h3
-rw-r--r--crawl-ref/source/makeitem.cc126
-rw-r--r--crawl-ref/source/mon-data.h2
-rw-r--r--crawl-ref/source/mon-util.cc498
-rw-r--r--crawl-ref/source/monstuff.cc326
-rw-r--r--crawl-ref/source/monstuff.h7
-rw-r--r--crawl-ref/source/mstuff2.cc18
-rw-r--r--crawl-ref/source/stuff.cc23
-rw-r--r--crawl-ref/source/stuff.h1
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);