Apply the new ground_level method.
[crawl.git] / crawl-ref / source / quiver.cc
blob3999fee78ea31f0db931a51230cc216d35b17e1b
1 /*
2 * File: quiver.cc
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
8 */
10 #include "AppHdr.h"
12 #include "quiver.h"
14 #include "env.h"
15 #include "invent.h"
16 #include "item_use.h"
17 #include "itemprop.h"
18 #include "items.h"
19 #include "options.h"
20 #include "player.h"
21 #include "stuff.h"
22 #include "tags.h"
24 #include <algorithm>
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,
31 bool force = true);
33 // ----------------------------------------------------------------------
34 // player_quiver
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);
43 // Return:
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
46 // item.
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,
51 // is returned)
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]);
58 if (slot == -1)
60 // Not in inv, but caller can at least get the type of the item.
61 if (item_out)
62 *item_out = &m_last_used_of_type[m_last_used_type];
64 else
66 // Return the item in inv, since it will have an accurate count.
67 if (item_out)
68 *item_out = &you.inv[slot];
71 if (slot_out)
72 *slot_out = 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
81 int slot;
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.
87 if (slot == -1)
89 std::vector<int> order;
90 _get_fire_order(order, false, you.weapon());
91 if (order.size())
92 slot = order[0];
95 // If we can't find anything, tell caller why.
96 if (slot == -1)
98 std::vector<int> full_fire_order;
99 _get_fire_order(full_fire_order, true, you.weapon());
100 if (no_item_reason == NULL)
102 // nothing
104 else if (full_fire_order.size() == 0)
106 *no_item_reason = "No suitable missiles.";
108 else
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));
117 else
119 *no_item_reason = make_stringf(
120 "Nothing suitable (ignored '=f'-inscribed item on '%c').",
121 index_to_letter(skipped_item));
126 return slot;
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" :
169 "crossbows");
172 void choose_item_for_quiver()
174 int slot = prompt_invent_item("Quiver which item? (- for none, * to show all)",
175 MT_INVLIST,
176 OSEL_THROWABLE, true, true, true, '-',
177 you.equip[EQ_WEAPON], NULL, OPER_QUIVER);
179 if (prompt_failed(slot))
180 return;
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" :
192 "crossbow");
193 return;
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.");
200 return;
202 else
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.");
209 return;
213 quiver_item(slot);
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;
224 return;
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;
238 else
240 const launch_retval projected = is_launched(&you, you.weapon(),
241 item);
243 // Don't do anything if this item is not really fit for throwing.
244 if (projected == LRET_FUMBLED)
245 return;
247 #ifdef DEBUG_QUIVER
248 mprf(MSGCH_DIAGNOSTICS, "item %s is for throwing",
249 item.name(DESC_PLAIN).c_str());
250 #endif
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.
263 on_item_fired(item);
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();
272 if (weapon == NULL)
274 if (m_last_weapon.base_type != OBJ_UNASSIGNED)
276 m_last_weapon.base_type = OBJ_UNASSIGNED;
277 m_last_used_type = AMMO_THROW;
280 else
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;
301 else
303 // We might need to update the quiver...
304 int qv_slot = get_fire_item();
305 if (qv_slot == slot)
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);
316 #ifdef DEBUG_QUIVER
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]);
320 #endif
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;
333 else
334 return;
337 #ifdef DEBUG_QUIVER
338 mpr("Recalculating fire order...", MSGCH_DIAGNOSTICS);
339 #endif
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;
353 else
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;
361 break;
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);
398 return;
401 for (int i_inv = inv_start; i_inv < ENDOFPACK; i_inv++)
403 const item_def& item = you.inv[i_inv];
404 if (!item.defined())
405 continue;
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))
414 continue;
417 // Don't do anything if this item is not really fit for throwing.
418 if (is_launched(&you, you.weapon(), item) == LRET_FUMBLED)
419 continue;
421 // =f prevents item from being in fire order.
422 if (!ignore_inscription_etc
423 && strstr(item.inscription.c_str(), "=f"))
425 continue;
428 for (unsigned int i_flags = 0; i_flags < Options.fire_order.size();
429 i_flags++)
432 if (_item_matches(item, (fire_type) Options.fire_order[i_flags],
433 launcher))
435 order.push_back((i_flags<<16) | (i_inv & 0xffff));
436 break;
441 std::sort(order.begin(), order.end());
443 for (unsigned int i = 0; i < order.size(); i++)
444 order[i] &= 0xffff;
447 // ----------------------------------------------------------------------
448 // Save/load
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 // ----------------------------------------------------------------------
481 // Identify helper
482 // ----------------------------------------------------------------------
484 preserve_quiver_slots::preserve_quiver_slots()
486 if (!you.m_quiver)
487 return;
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()
501 if (!you.m_quiver)
502 return;
504 for (unsigned int i = 0; i < ARRAYSZ(m_last_used_of_type); i++)
506 const int slot = m_last_used_of_type[i];
507 if (slot != -1)
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 // ----------------------------------------------------------------------
517 // Helpers
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)
529 return (true);
531 if (item.base_type == OBJ_MISSILES)
533 if ((types & FIRE_DART) && item.sub_type == MI_DART)
534 return (true);
535 if ((types & FIRE_STONE) && item.sub_type == MI_STONE)
536 return (true);
537 if ((types & FIRE_JAVELIN) && item.sub_type == MI_JAVELIN)
538 return (true);
539 if ((types & FIRE_ROCK) && item.sub_type == MI_LARGE_ROCK)
540 return (true);
541 if ((types & FIRE_NET) && item.sub_type == MI_THROWING_NET)
542 return (true);
544 if (types & FIRE_LAUNCHER)
546 if (launcher && item.launched_by(*launcher))
547 return (true);
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))
556 return (true);
558 if ((types & FIRE_DAGGER) && item.sub_type == WPN_DAGGER)
559 return (true);
560 if ((types & FIRE_SPEAR) && item.sub_type == WPN_SPEAR)
561 return (true);
562 if ((types & FIRE_HAND_AXE) && item.sub_type == WPN_HAND_AXE)
563 return (true);
564 if ((types & FIRE_CLUB) && item.sub_type == WPN_CLUB)
565 return (true);
567 return (false);
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)
574 if (!item.defined())
575 return -1;
577 if (in_inventory(item) && _items_similar(item, you.inv[item.link], false))
578 return (item.link);
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))
587 return 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))
598 return i;
602 return -1;
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)
609 if (weapon == NULL)
610 return AMMO_THROW;
611 if (weapon->base_type != OBJ_WEAPONS)
612 return AMMO_THROW;
614 switch (weapon->sub_type)
616 case WPN_BLOWGUN:
617 return AMMO_BLOWGUN;
618 case WPN_SLING:
619 return AMMO_SLING;
620 case WPN_BOW:
621 case WPN_LONGBOW:
622 return AMMO_BOW;
623 case WPN_CROSSBOW:
624 return AMMO_CROSSBOW;
625 default:
626 return AMMO_THROW;
630 static bool _items_similar(const item_def& a, const item_def& b, bool force)
632 if (!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);