3 * Summary: Player quiver functionality
5 * - Only change last_used when actually using
6 * - Not changing Qv; nobody knows about internals
7 * - Track last_used of each type so each weapon can do the right thing
26 static int _get_pack_slot(const item_def
&);
27 static ammo_t
_get_weapon_ammo_type(const item_def
*);
28 static bool _item_matches(const item_def
&item
, fire_type types
,
29 const item_def
* launcher
);
30 static bool _items_similar(const item_def
& a
, const item_def
& b
,
33 // ----------------------------------------------------------------------
35 // ----------------------------------------------------------------------
37 player_quiver::player_quiver()
38 : m_last_used_type(AMMO_THROW
)
40 COMPILE_CHECK(ARRAYSZ(m_last_used_of_type
) == NUM_AMMO
, a
);
44 // *slot_out filled in with the inv slot of the item we would like
45 // to fire by default. If -1, the inv doesn't contain our desired
48 // *item_out filled in with item we would like to fire by default.
49 // This can be returned even if the item is not in inv (although if
50 // it is in inv, a reference to the inv item, with accurate count,
53 // This is the item that will be displayed in Qv:
55 void player_quiver::get_desired_item(const item_def
** item_out
, int* slot_out
) const
57 const int slot
= _get_pack_slot(m_last_used_of_type
[m_last_used_type
]);
60 // Not in inv, but caller can at least get the type of the item.
62 *item_out
= &m_last_used_of_type
[m_last_used_type
];
66 // Return the item in inv, since it will have an accurate count.
68 *item_out
= &you
.inv
[slot
];
75 // Return inv slot of item that should be fired by default.
76 // This differs from get_desired_item; that method can return
77 // an item that is not in inventory, while this one cannot.
78 // If no item can be found, return the reason why.
79 int player_quiver::get_fire_item(std::string
* no_item_reason
) const
82 const item_def
* desired_item
;
84 get_desired_item(&desired_item
, &slot
);
86 // If not in inv, try the head of the fire order.
89 std::vector
<int> order
;
90 _get_fire_order(order
, false, you
.weapon());
95 // If we can't find anything, tell caller why.
98 std::vector
<int> full_fire_order
;
99 _get_fire_order(full_fire_order
, true, you
.weapon());
100 if (no_item_reason
== NULL
)
104 else if (full_fire_order
.size() == 0)
106 *no_item_reason
= "No suitable missiles.";
110 const int skipped_item
= full_fire_order
[0];
111 if (skipped_item
< Options
.fire_items_start
)
113 *no_item_reason
= make_stringf(
114 "Nothing suitable (fire_items_start = '%c').",
115 index_to_letter(Options
.fire_items_start
));
119 *no_item_reason
= make_stringf(
120 "Nothing suitable (ignored '=f'-inscribed item on '%c').",
121 index_to_letter(skipped_item
));
129 void player_quiver::set_quiver(const item_def
&item
, ammo_t ammo_type
)
131 m_last_used_of_type
[ammo_type
] = item
;
132 m_last_used_of_type
[ammo_type
].quantity
= 1;
133 m_last_used_type
= ammo_type
;
134 you
.redraw_quiver
= true;
137 void player_quiver::empty_quiver(ammo_t ammo_type
)
139 m_last_used_of_type
[ammo_type
] = item_def();
140 m_last_used_of_type
[ammo_type
].quantity
= 0;
141 m_last_used_type
= ammo_type
;
142 you
.redraw_quiver
= true;
145 static bool _wielded_slot_no_quiver(int slot
)
147 return (slot
== you
.equip
[EQ_WEAPON
]
148 && you
.inv
[slot
].base_type
== OBJ_WEAPONS
149 && (get_weapon_brand(you
.inv
[slot
]) != SPWPN_RETURNING
150 || you
.skills
[SK_THROWING
] == 0));
153 void quiver_item(int slot
)
155 const item_def item
= you
.inv
[slot
];
156 ASSERT(item
.defined());
158 ammo_t t
= AMMO_THROW
;
159 const item_def
*weapon
= you
.weapon();
160 if (weapon
&& item
.launched_by(*weapon
))
161 t
= _get_weapon_ammo_type(weapon
);
163 you
.m_quiver
->set_quiver(you
.inv
[slot
], t
);
164 mprf("Quivering %s for %s.", you
.inv
[slot
].name(DESC_INVENTORY
).c_str(),
165 t
== AMMO_THROW
? "throwing" :
166 t
== AMMO_BLOWGUN
? "blowguns" :
167 t
== AMMO_SLING
? "slings" :
168 t
== AMMO_BOW
? "bows" :
172 void choose_item_for_quiver()
174 int slot
= prompt_invent_item("Quiver which item? (- for none, * to show all)",
176 OSEL_THROWABLE
, true, true, true, '-',
177 you
.equip
[EQ_WEAPON
], NULL
, OPER_QUIVER
);
179 if (prompt_failed(slot
))
182 if (slot
== PROMPT_GOT_SPECIAL
) // '-' or empty quiver
184 ammo_t t
= _get_weapon_ammo_type(you
.weapon());
185 you
.m_quiver
->empty_quiver(t
);
187 mprf("Reset %s quiver to default.",
188 t
== AMMO_THROW
? "throwing" :
189 t
== AMMO_BLOWGUN
? "blowgun" :
190 t
== AMMO_SLING
? "sling" :
191 t
== AMMO_BOW
? "bow" :
195 else if (_wielded_slot_no_quiver(slot
))
197 // Don't quiver a wielded weapon unless it's a weapon of returning
198 // and we've got some throwing skill.
199 mpr("You can't quiver wielded weapons.");
204 for (int i
= EQ_MIN_ARMOUR
; i
<= EQ_MAX_WORN
; i
++)
206 if (you
.equip
[i
] == slot
)
208 mpr("You can't quiver worn items.");
216 // Notification that item was fired with 'f'.
217 void player_quiver::on_item_fired(const item_def
& item
, bool explicitly_chosen
)
219 if (!explicitly_chosen
)
221 // If the item was not actively chosen, i.e. just automatically
222 // passed into the quiver, don't change any of the quiver settings.
223 you
.redraw_quiver
= true;
226 // If item matches the launcher, put it in that launcher's last-used item.
227 // Otherwise, it goes into last hand-thrown item.
229 const item_def
*weapon
= you
.weapon();
231 if (weapon
&& item
.launched_by(*weapon
))
233 const ammo_t t
= _get_weapon_ammo_type(weapon
);
234 m_last_used_of_type
[t
] = item
;
235 m_last_used_of_type
[t
].quantity
= 1; // 0 makes it invalid :(
236 m_last_used_type
= t
;
240 const launch_retval projected
= is_launched(&you
, you
.weapon(),
243 // Don't do anything if this item is not really fit for throwing.
244 if (projected
== LRET_FUMBLED
)
248 mprf(MSGCH_DIAGNOSTICS
, "item %s is for throwing",
249 item
.name(DESC_PLAIN
).c_str());
251 m_last_used_of_type
[AMMO_THROW
] = item
;
252 m_last_used_of_type
[AMMO_THROW
].quantity
= 1;
253 m_last_used_type
= AMMO_THROW
;
256 you
.redraw_quiver
= true;
259 // Notification that item was fired with 'f' 'i'
260 void player_quiver::on_item_fired_fi(const item_def
& item
)
262 // Currently no difference.
266 // Called when the player might have switched weapons, or might have
267 // picked up something interesting.
268 void player_quiver::on_weapon_changed()
270 // Only switch m_last_used_type if weapon really changed
271 const item_def
* weapon
= you
.weapon();
274 if (m_last_weapon
.base_type
!= OBJ_UNASSIGNED
)
276 m_last_weapon
.base_type
= OBJ_UNASSIGNED
;
277 m_last_used_type
= AMMO_THROW
;
282 if (!_items_similar(*weapon
, m_last_weapon
))
284 // Weapon type changed.
285 m_last_weapon
= *weapon
;
286 m_last_used_type
= _get_weapon_ammo_type(weapon
);
290 _maybe_fill_empty_slot();
293 void player_quiver::on_inv_quantity_changed(int slot
, int amt
)
295 if (m_last_used_of_type
[m_last_used_type
].base_type
== OBJ_UNASSIGNED
)
297 // Empty quiver. Maybe we can fill it now?
298 _maybe_fill_empty_slot();
299 you
.redraw_quiver
= true;
303 // We might need to update the quiver...
304 int qv_slot
= get_fire_item();
306 you
.redraw_quiver
= true;
310 // If current quiver slot is empty, fill it with something useful.
311 void player_quiver::_maybe_fill_empty_slot()
313 const item_def
* weapon
= you
.weapon();
314 const ammo_t slot
= _get_weapon_ammo_type(weapon
);
317 mprf(MSGCH_DIAGNOSTICS
, "last quiver item: %s; link %d, wpn: %d",
318 m_last_used_of_type
[slot
].name(DESC_PLAIN
).c_str(),
319 m_last_used_of_type
[slot
].link
, you
.equip
[EQ_WEAPON
]);
322 bool unquiver_weapon
= false;
323 if (m_last_used_of_type
[slot
].defined())
325 // If we're wielding an item previously quivered, the quiver may need
326 // to be cleared. Else, any already quivered item is valid and we
327 // don't need to do anything else.
328 if (m_last_used_of_type
[slot
].link
== you
.equip
[EQ_WEAPON
]
329 && you
.equip
[EQ_WEAPON
] != -1)
331 unquiver_weapon
= true;
338 mpr("Recalculating fire order...", MSGCH_DIAGNOSTICS
);
341 const launch_retval desired_ret
=
342 (weapon
&& is_range_weapon(*weapon
)) ? LRET_LAUNCHED
: LRET_THROWN
;
344 std::vector
<int> order
;
345 _get_fire_order(order
, false, weapon
);
347 if (unquiver_weapon
&& order
.empty())
349 // Setting the quantity to zero will force the quiver to be empty,
350 // should nothing else be found.
351 m_last_used_of_type
[slot
].quantity
= 0;
355 for (unsigned int i
= 0; i
< order
.size(); i
++)
357 if (is_launched(&you
, weapon
, you
.inv
[order
[i
]]) == desired_ret
)
359 m_last_used_of_type
[slot
] = you
.inv
[order
[i
]];
360 m_last_used_of_type
[slot
].quantity
= 1;
367 void player_quiver::get_fire_order(std::vector
<int>& v
) const
369 _get_fire_order(v
, false, you
.weapon());
372 // Get a sorted list of items to show in the fire interface.
374 // If ignore_inscription_etc, ignore =f and Options.fire_items_start.
375 // This is used for generating informational error messages, when the
376 // fire order is empty.
378 // launcher determines what items match the 'launcher' fire_order type.
379 void player_quiver::_get_fire_order(std::vector
<int>& order
,
380 bool ignore_inscription_etc
,
381 const item_def
* launcher
) const
383 const int inv_start
= (ignore_inscription_etc
? 0
384 : Options
.fire_items_start
);
386 // If in a net, cannot throw anything, and can only launch from blowgun.
387 if (you
.attribute
[ATTR_HELD
])
389 if (launcher
&& launcher
->sub_type
== WPN_BLOWGUN
)
391 for (int i_inv
= inv_start
; i_inv
< ENDOFPACK
; i_inv
++)
392 if (you
.inv
[i_inv
].defined()
393 && you
.inv
[i_inv
].launched_by(*launcher
))
395 order
.push_back(i_inv
);
401 for (int i_inv
= inv_start
; i_inv
< ENDOFPACK
; i_inv
++)
403 const item_def
& item
= you
.inv
[i_inv
];
407 // Don't quiver a wielded weapon unless it's a weapon of returning
408 // and we've got some throwing skill.
409 if (you
.equip
[EQ_WEAPON
] == i_inv
410 && you
.inv
[i_inv
].base_type
== OBJ_WEAPONS
411 && (get_weapon_brand(you
.inv
[i_inv
]) != SPWPN_RETURNING
412 || you
.skills
[SK_THROWING
] == 0))
417 // Don't do anything if this item is not really fit for throwing.
418 if (is_launched(&you
, you
.weapon(), item
) == LRET_FUMBLED
)
421 // =f prevents item from being in fire order.
422 if (!ignore_inscription_etc
423 && strstr(item
.inscription
.c_str(), "=f"))
428 for (unsigned int i_flags
= 0; i_flags
< Options
.fire_order
.size();
432 if (_item_matches(item
, (fire_type
) Options
.fire_order
[i_flags
],
435 order
.push_back((i_flags
<<16) | (i_inv
& 0xffff));
441 std::sort(order
.begin(), order
.end());
443 for (unsigned int i
= 0; i
< order
.size(); i
++)
447 // ----------------------------------------------------------------------
449 // ----------------------------------------------------------------------
451 static const short QUIVER_COOKIE
= short(0xb015);
452 void player_quiver::save(writer
& outf
) const
454 marshallShort(outf
, QUIVER_COOKIE
);
456 marshallItem(outf
, m_last_weapon
);
457 marshallInt(outf
, m_last_used_type
);
458 marshallInt(outf
, ARRAYSZ(m_last_used_of_type
));
460 for (unsigned int i
= 0; i
< ARRAYSZ(m_last_used_of_type
); i
++)
461 marshallItem(outf
, m_last_used_of_type
[i
]);
464 void player_quiver::load(reader
& inf
)
466 const short cooky
= unmarshallShort(inf
);
467 ASSERT(cooky
== QUIVER_COOKIE
); (void)cooky
;
469 unmarshallItem(inf
, m_last_weapon
);
470 m_last_used_type
= (ammo_t
)unmarshallInt(inf
);
471 ASSERT(m_last_used_type
>= AMMO_THROW
&& m_last_used_type
< NUM_AMMO
);
473 const unsigned int count
= unmarshallInt(inf
);
474 ASSERT(count
<= ARRAYSZ(m_last_used_of_type
));
476 for (unsigned int i
= 0; i
< count
; i
++)
477 unmarshallItem(inf
, m_last_used_of_type
[i
]);
480 // ----------------------------------------------------------------------
482 // ----------------------------------------------------------------------
484 preserve_quiver_slots::preserve_quiver_slots()
489 COMPILE_CHECK(ARRAYSZ(m_last_used_of_type
) ==
490 ARRAYSZ(you
.m_quiver
->m_last_used_of_type
), a
);
492 for (unsigned int i
= 0; i
< ARRAYSZ(m_last_used_of_type
); i
++)
494 m_last_used_of_type
[i
] =
495 _get_pack_slot(you
.m_quiver
->m_last_used_of_type
[i
]);
499 preserve_quiver_slots::~preserve_quiver_slots()
504 for (unsigned int i
= 0; i
< ARRAYSZ(m_last_used_of_type
); i
++)
506 const int slot
= m_last_used_of_type
[i
];
509 you
.m_quiver
->m_last_used_of_type
[i
] = you
.inv
[slot
];
510 you
.m_quiver
->m_last_used_of_type
[i
].quantity
= 1;
513 you
.redraw_quiver
= true;
516 // ----------------------------------------------------------------------
518 // ----------------------------------------------------------------------
520 // Helper for _get_fire_order.
521 // Types may actually contain more than one fire_type.
522 static bool _item_matches(const item_def
&item
, fire_type types
,
523 const item_def
* launcher
)
525 ASSERT(item
.defined());
527 if (types
& FIRE_INSCRIBED
)
528 if (item
.inscription
.find("+f", 0) != std::string::npos
)
531 if (item
.base_type
== OBJ_MISSILES
)
533 if ((types
& FIRE_DART
) && item
.sub_type
== MI_DART
)
535 if ((types
& FIRE_STONE
) && item
.sub_type
== MI_STONE
)
537 if ((types
& FIRE_JAVELIN
) && item
.sub_type
== MI_JAVELIN
)
539 if ((types
& FIRE_ROCK
) && item
.sub_type
== MI_LARGE_ROCK
)
541 if ((types
& FIRE_NET
) && item
.sub_type
== MI_THROWING_NET
)
544 if (types
& FIRE_LAUNCHER
)
546 if (launcher
&& item
.launched_by(*launcher
))
550 else if (item
.base_type
== OBJ_WEAPONS
&& is_throwable(&you
, item
))
552 if ((types
& FIRE_RETURNING
)
553 && item
.special
== SPWPN_RETURNING
554 && item_ident(item
, ISFLAG_KNOW_TYPE
))
558 if ((types
& FIRE_DAGGER
) && item
.sub_type
== WPN_DAGGER
)
560 if ((types
& FIRE_SPEAR
) && item
.sub_type
== WPN_SPEAR
)
562 if ((types
& FIRE_HAND_AXE
) && item
.sub_type
== WPN_HAND_AXE
)
564 if ((types
& FIRE_CLUB
) && item
.sub_type
== WPN_CLUB
)
570 // Returns inv slot that contains an item that looks like item,
571 // or -1 if not in inv.
572 static int _get_pack_slot(const item_def
& item
)
577 if (in_inventory(item
) && _items_similar(item
, you
.inv
[item
.link
], false))
580 // First try to find the exact same item.
581 for (int i
= 0; i
< ENDOFPACK
; i
++)
583 const item_def
& inv_item
= you
.inv
[i
];
584 if (inv_item
.quantity
&& _items_similar(item
, you
.inv
[i
], false)
585 && !_wielded_slot_no_quiver(i
))
591 // If that fails, try to find an item sufficiently similar.
592 for (int i
= 0; i
< ENDOFPACK
; i
++)
594 const item_def
& inv_item
= you
.inv
[i
];
595 if (inv_item
.quantity
&& _items_similar(item
, you
.inv
[i
], true)
596 && !_wielded_slot_no_quiver(i
))
605 // Returns the type of ammo used by the player's equipped weapon,
606 // or AMMO_THROW if it's not a launcher.
607 static ammo_t
_get_weapon_ammo_type(const item_def
* weapon
)
611 if (weapon
->base_type
!= OBJ_WEAPONS
)
614 switch (weapon
->sub_type
)
624 return AMMO_CROSSBOW
;
630 static bool _items_similar(const item_def
& a
, const item_def
& b
, bool force
)
633 return (items_similar(a
, b
) && a
.slot
== b
.slot
);
635 // This is a reasonable implementation for now.
636 return items_stack(a
, b
, force
);