Simplify a couple of things in quiver.cc
[crawl:newnewvaults.git] / crawl-ref / source / quiver.cc
1 /**
2  * @file
3  * @brief Player quiver functionality
4  *
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 **/
9
10 #include "AppHdr.h"
11
12 #include "quiver.h"
13
14 #include "env.h"
15 #include "invent.h"
16 #include "itemprop.h"
17 #include "items.h"
18 #include "libutil.h"
19 #include "options.h"
20 #include "player.h"
21 #include "stuff.h"
22 #include "tags.h"
23 #include "throw.h"
24
25 #include <algorithm>
26
27 static int _get_pack_slot(const item_def&);
28 static ammo_t _get_weapon_ammo_type(const item_def*);
29 static bool _item_matches(const item_def &item, fire_type types,
30                           const item_def* launcher, bool manual);
31 static bool _items_similar(const item_def& a, const item_def& b,
32                            bool force = true);
33
34 // ----------------------------------------------------------------------
35 // player_quiver
36 // ----------------------------------------------------------------------
37
38 player_quiver::player_quiver()
39     : m_last_used_type(AMMO_THROW)
40 {
41     COMPILE_CHECK(ARRAYSZ(m_last_used_of_type) == NUM_AMMO);
42 }
43
44 // Return:
45 //   *slot_out filled in with the inv slot of the item we would like
46 //   to fire by default.  If -1, the inv doesn't contain our desired
47 //   item.
48 //
49 //   *item_out filled in with item we would like to fire by default.
50 //   This can be returned even if the item is not in inv (although if
51 //   it is in inv, a reference to the inv item, with accurate count,
52 //   is returned)
53 //
54 // This is the item that will be displayed in Qv:
55 //
56 void player_quiver::get_desired_item(const item_def** item_out, int* slot_out) const
57 {
58     const int slot = _get_pack_slot(m_last_used_of_type[m_last_used_type]);
59     if (slot == -1)
60     {
61         // Not in inv, but caller can at least get the type of the item.
62         if (item_out)
63             *item_out = &m_last_used_of_type[m_last_used_type];
64     }
65     else
66     {
67         // Return the item in inv, since it will have an accurate count.
68         if (item_out)
69             *item_out = &you.inv[slot];
70     }
71
72     if (slot_out)
73         *slot_out = slot;
74 }
75
76 // Return inv slot of item that should be fired by default.
77 // This differs from get_desired_item; that method can return
78 // an item that is not in inventory, while this one cannot.
79 // If no item can be found, return the reason why.
80 int player_quiver::get_fire_item(string* no_item_reason) const
81 {
82     // Felids have no use for the quiver.
83     if (you.species == SP_FELID)
84     {
85         if (no_item_reason != NULL)
86             *no_item_reason = "You can't grasp things well enough to throw them.";
87         return -1;
88     }
89     int slot;
90     const item_def* desired_item;
91
92     get_desired_item(&desired_item, &slot);
93
94     // If not in inv, try the head of the fire order.
95     if (slot == -1)
96     {
97         vector<int> order;
98         _get_fire_order(order, false, you.weapon(), false);
99         if (!order.empty())
100             slot = order[0];
101     }
102
103     // If we can't find anything, tell caller why.
104     if (slot == -1)
105     {
106         vector<int> full_fire_order;
107         _get_fire_order(full_fire_order, true, you.weapon(), false);
108         if (no_item_reason == NULL)
109         {
110             // nothing
111         }
112         else if (full_fire_order.empty())
113             *no_item_reason = "No suitable missiles.";
114         else
115         {
116             const int skipped_item = full_fire_order[0];
117             if (skipped_item < Options.fire_items_start)
118             {
119                 *no_item_reason = make_stringf(
120                     "Nothing suitable (fire_items_start = '%c').",
121                     index_to_letter(Options.fire_items_start));
122             }
123             else
124             {
125                 *no_item_reason = make_stringf(
126                     "Nothing suitable (ignored '=f'-inscribed item on '%c').",
127                     index_to_letter(skipped_item));
128             }
129         }
130     }
131
132     return slot;
133 }
134
135 void player_quiver::set_quiver(const item_def &item, ammo_t ammo_type)
136 {
137     m_last_used_of_type[ammo_type] = item;
138     m_last_used_of_type[ammo_type].quantity = 1;
139     m_last_used_type  = ammo_type;
140     you.redraw_quiver = true;
141 }
142
143 void player_quiver::empty_quiver(ammo_t ammo_type)
144 {
145     m_last_used_of_type[ammo_type] = item_def();
146     m_last_used_of_type[ammo_type].quantity = 0;
147     m_last_used_type  = ammo_type;
148     you.redraw_quiver = true;
149 }
150
151 static bool _wielded_slot_no_quiver(int slot)
152 {
153     return (slot == you.equip[EQ_WEAPON]
154             && you.inv[slot].base_type == OBJ_WEAPONS
155             && (get_weapon_brand(you.inv[slot]) != SPWPN_RETURNING
156                 || you.skills[SK_THROWING] == 0));
157 }
158
159 void quiver_item(int slot)
160 {
161     const item_def item = you.inv[slot];
162     ASSERT(item.defined());
163
164     ammo_t t = AMMO_THROW;
165     const item_def *weapon = you.weapon();
166     if (weapon && item.launched_by(*weapon))
167         t = _get_weapon_ammo_type(weapon);
168
169     you.m_quiver->set_quiver(you.inv[slot], t);
170     mprf("Quivering %s for %s.", you.inv[slot].name(DESC_INVENTORY).c_str(),
171          t == AMMO_THROW    ? "throwing" :
172          t == AMMO_BLOWGUN  ? "blowguns" :
173          t == AMMO_SLING    ? "slings" :
174          t == AMMO_BOW      ? "bows" :
175                               "crossbows");
176 }
177
178 void choose_item_for_quiver()
179 {
180     if (you.species == SP_FELID)
181     {
182         mpr("You can't grasp things well enough to throw them.");
183         return;
184     }
185     int slot = prompt_invent_item("Quiver which item? (- for none, * to show all)",
186                                   MT_INVLIST,
187                                   OSEL_THROWABLE, true, true, true, '-',
188                                   you.equip[EQ_WEAPON], NULL, OPER_QUIVER);
189
190     if (prompt_failed(slot))
191         return;
192
193     if (slot == PROMPT_GOT_SPECIAL)  // '-' or empty quiver
194     {
195         ammo_t t = _get_weapon_ammo_type(you.weapon());
196         you.m_quiver->empty_quiver(t);
197
198         mprf("Reset %s quiver to default.",
199              t == AMMO_THROW    ? "throwing" :
200              t == AMMO_BLOWGUN  ? "blowgun" :
201              t == AMMO_SLING    ? "sling" :
202              t == AMMO_BOW      ? "bow" :
203                                   "crossbow");
204         return;
205     }
206     else if (_wielded_slot_no_quiver(slot))
207     {
208         // Don't quiver a wielded weapon unless it's a weapon of returning
209         // and we've got some throwing skill.
210         mpr("You can't quiver wielded weapons.");
211         return;
212     }
213     else
214     {
215         for (int i = EQ_MIN_ARMOUR; i <= EQ_MAX_WORN; i++)
216         {
217             if (you.equip[i] == slot)
218             {
219                 mpr("You can't quiver worn items.");
220                 return;
221             }
222         }
223     }
224     quiver_item(slot);
225 }
226
227 // Notification that item was fired with 'f'.
228 void player_quiver::on_item_fired(const item_def& item, bool explicitly_chosen)
229 {
230     if (!explicitly_chosen)
231     {
232         // If the item was not actively chosen, i.e. just automatically
233         // passed into the quiver, don't change any of the quiver settings.
234         you.redraw_quiver = true;
235         return;
236     }
237     // If item matches the launcher, put it in that launcher's last-used item.
238     // Otherwise, it goes into last hand-thrown item.
239
240     const item_def *weapon = you.weapon();
241
242     if (weapon && item.launched_by(*weapon))
243     {
244         const ammo_t t = _get_weapon_ammo_type(weapon);
245         m_last_used_of_type[t] = item;
246         m_last_used_of_type[t].quantity = 1;    // 0 makes it invalid :(
247         m_last_used_type = t;
248     }
249     else
250     {
251         const launch_retval projected = is_launched(&you, you.weapon(),
252                                                     item);
253
254         // Don't do anything if this item is not really fit for throwing.
255         if (projected == LRET_FUMBLED)
256             return;
257
258 #ifdef DEBUG_QUIVER
259         mprf(MSGCH_DIAGNOSTICS, "item %s is for throwing",
260              item.name(DESC_PLAIN).c_str());
261 #endif
262         m_last_used_of_type[AMMO_THROW] = item;
263         m_last_used_of_type[AMMO_THROW].quantity = 1;
264         m_last_used_type = AMMO_THROW;
265     }
266
267     you.redraw_quiver = true;
268 }
269
270 // Notification that item was fired with 'f' 'i'
271 void player_quiver::on_item_fired_fi(const item_def& item)
272 {
273     // Currently no difference.
274     on_item_fired(item);
275 }
276
277 // Called when the player might have switched weapons, or might have
278 // picked up something interesting.
279 void player_quiver::on_weapon_changed()
280 {
281     // Only switch m_last_used_type if weapon really changed
282     const item_def* weapon = you.weapon();
283     if (weapon == NULL)
284     {
285         if (m_last_weapon.base_type != OBJ_UNASSIGNED)
286         {
287             m_last_weapon.base_type = OBJ_UNASSIGNED;
288             m_last_used_type = AMMO_THROW;
289         }
290     }
291     else
292     {
293         if (!_items_similar(*weapon, m_last_weapon))
294         {
295             // Weapon type changed.
296             m_last_weapon = *weapon;
297             m_last_used_type = _get_weapon_ammo_type(weapon);
298         }
299     }
300
301     _maybe_fill_empty_slot();
302 }
303
304 void player_quiver::on_inv_quantity_changed(int slot, int amt)
305 {
306     if (m_last_used_of_type[m_last_used_type].base_type == OBJ_UNASSIGNED)
307     {
308         // Empty quiver.  Maybe we can fill it now?
309         _maybe_fill_empty_slot();
310         you.redraw_quiver = true;
311     }
312     else
313     {
314         // We might need to update the quiver...
315         int qv_slot = get_fire_item();
316         if (qv_slot == slot)
317             you.redraw_quiver = true;
318     }
319 }
320
321 // If current quiver slot is empty, fill it with something useful.
322 void player_quiver::_maybe_fill_empty_slot()
323 {
324     // Felids have no use for the quiver.
325     if (you.species == SP_FELID)
326         return;
327
328     const item_def* weapon = you.weapon();
329     const ammo_t slot = _get_weapon_ammo_type(weapon);
330
331 #ifdef DEBUG_QUIVER
332     mprf(MSGCH_DIAGNOSTICS, "last quiver item: %s; link %d, wpn: %d",
333          m_last_used_of_type[slot].name(DESC_PLAIN).c_str(),
334          m_last_used_of_type[slot].link, you.equip[EQ_WEAPON]);
335 #endif
336
337     bool unquiver_weapon = false;
338     if (m_last_used_of_type[slot].defined())
339     {
340         // If we're wielding an item previously quivered, the quiver may need
341         // to be cleared. Else, any already quivered item is valid and we
342         // don't need to do anything else.
343         if (m_last_used_of_type[slot].link == you.equip[EQ_WEAPON]
344             && you.equip[EQ_WEAPON] != -1)
345         {
346             unquiver_weapon = true;
347         }
348         else
349             return;
350     }
351
352 #ifdef DEBUG_QUIVER
353     mpr("Recalculating fire order...", MSGCH_DIAGNOSTICS);
354 #endif
355
356     const launch_retval desired_ret =
357          (weapon && is_range_weapon(*weapon)) ? LRET_LAUNCHED : LRET_THROWN;
358
359     vector<int> order;
360     _get_fire_order(order, false, weapon, false);
361
362     if (unquiver_weapon && order.empty())
363     {
364         // Setting the quantity to zero will force the quiver to be empty,
365         // should nothing else be found.  We also set the base type to
366         // OBJ_UNASSIGNED so this is not an invalid object with a real type,
367         // as that would trigger an assertion on saving.
368         m_last_used_of_type[slot].base_type = OBJ_UNASSIGNED;
369         m_last_used_of_type[slot].quantity = 0;
370     }
371     else
372     {
373         for (unsigned int i = 0; i < order.size(); i++)
374         {
375             if (is_launched(&you, weapon, you.inv[order[i]]) == desired_ret)
376             {
377                 m_last_used_of_type[slot] = you.inv[order[i]];
378                 m_last_used_of_type[slot].quantity = 1;
379                 break;
380             }
381         }
382     }
383 }
384
385 void player_quiver::get_fire_order(vector<int>& v, bool manual) const
386 {
387     _get_fire_order(v, false, you.weapon(), manual);
388 }
389
390 // Get a sorted list of items to show in the fire interface.
391 //
392 // If ignore_inscription_etc, ignore =f and Options.fire_items_start.
393 // This is used for generating informational error messages, when the
394 // fire order is empty.
395 //
396 // launcher determines what items match the 'launcher' fire_order type.
397 void player_quiver::_get_fire_order(vector<int>& order,
398                                      bool ignore_inscription_etc,
399                                      const item_def* launcher,
400                                      bool manual) const
401 {
402     const int inv_start = (ignore_inscription_etc ? 0
403                                                   : Options.fire_items_start);
404
405     // If in a net, cannot throw anything, and can only launch from blowgun.
406     if (you.attribute[ATTR_HELD])
407     {
408         if (launcher && launcher->sub_type == WPN_BLOWGUN)
409         {
410             for (int i_inv = inv_start; i_inv < ENDOFPACK; i_inv++)
411                 if (you.inv[i_inv].defined()
412                     && you.inv[i_inv].launched_by(*launcher))
413                 {
414                     order.push_back(i_inv);
415                 }
416         }
417         return;
418     }
419
420     for (int i_inv = inv_start; i_inv < ENDOFPACK; i_inv++)
421     {
422         const item_def& item = you.inv[i_inv];
423         if (!item.defined())
424             continue;
425
426         // Don't quiver a wielded weapon unless it's a weapon of returning
427         // and we've got some throwing skill.
428         if (you.equip[EQ_WEAPON] == i_inv
429             && you.inv[i_inv].base_type == OBJ_WEAPONS
430             && (get_weapon_brand(you.inv[i_inv]) != SPWPN_RETURNING
431                 || you.skills[SK_THROWING] == 0))
432         {
433             continue;
434         }
435
436         // Don't do anything if this item is not really fit for throwing.
437         if (is_launched(&you, you.weapon(), item) == LRET_FUMBLED)
438             continue;
439
440         // =f prevents item from being in fire order.
441         if (!ignore_inscription_etc
442             && strstr(item.inscription.c_str(), manual ? "=F" : "=f"))
443         {
444             continue;
445         }
446
447         for (unsigned int i_flags = 0; i_flags < Options.fire_order.size();
448              i_flags++)
449         {
450             if (_item_matches(item, (fire_type) Options.fire_order[i_flags],
451                               launcher, manual))
452             {
453                 order.push_back((i_flags<<16) | (i_inv & 0xffff));
454                 break;
455             }
456         }
457     }
458
459     sort(order.begin(), order.end());
460
461     for (unsigned int i = 0; i < order.size(); i++)
462         order[i] &= 0xffff;
463 }
464
465 // ----------------------------------------------------------------------
466 // Save/load
467 // ----------------------------------------------------------------------
468
469 static const short QUIVER_COOKIE = short(0xb015);
470 void player_quiver::save(writer& outf) const
471 {
472     marshallShort(outf, QUIVER_COOKIE);
473
474     marshallItem(outf, m_last_weapon);
475     marshallInt(outf, m_last_used_type);
476     marshallInt(outf, ARRAYSZ(m_last_used_of_type));
477
478     for (unsigned int i = 0; i < ARRAYSZ(m_last_used_of_type); i++)
479         marshallItem(outf, m_last_used_of_type[i]);
480 }
481
482 void player_quiver::load(reader& inf)
483 {
484     const short cooky = unmarshallShort(inf);
485     ASSERT(cooky == QUIVER_COOKIE); (void)cooky;
486
487     unmarshallItem(inf, m_last_weapon);
488     m_last_used_type = (ammo_t)unmarshallInt(inf);
489     ASSERT(m_last_used_type >= AMMO_THROW && m_last_used_type < NUM_AMMO);
490
491     const unsigned int count = unmarshallInt(inf);
492     ASSERT(count <= ARRAYSZ(m_last_used_of_type));
493
494     for (unsigned int i = 0; i < count; i++)
495         unmarshallItem(inf, m_last_used_of_type[i]);
496 }
497
498 // ----------------------------------------------------------------------
499 // Identify helper
500 // ----------------------------------------------------------------------
501
502 preserve_quiver_slots::preserve_quiver_slots()
503 {
504     if (!you.m_quiver)
505         return;
506
507     COMPILE_CHECK(ARRAYSZ(m_last_used_of_type) ==
508                   ARRAYSZ(you.m_quiver->m_last_used_of_type));
509
510     for (unsigned int i = 0; i < ARRAYSZ(m_last_used_of_type); i++)
511     {
512         m_last_used_of_type[i] =
513             _get_pack_slot(you.m_quiver->m_last_used_of_type[i]);
514     }
515 }
516
517 preserve_quiver_slots::~preserve_quiver_slots()
518 {
519     if (!you.m_quiver)
520         return;
521
522     for (unsigned int i = 0; i < ARRAYSZ(m_last_used_of_type); i++)
523     {
524         const int slot = m_last_used_of_type[i];
525         if (slot != -1)
526         {
527             you.m_quiver->m_last_used_of_type[i] = you.inv[slot];
528             you.m_quiver->m_last_used_of_type[i].quantity = 1;
529         }
530     }
531     you.redraw_quiver = true;
532 }
533
534 // ----------------------------------------------------------------------
535 // Helpers
536 // ----------------------------------------------------------------------
537
538 // Helper for _get_fire_order.
539 // Types may actually contain more than one fire_type.
540 static bool _item_matches(const item_def &item, fire_type types,
541                           const item_def* launcher, bool manual)
542 {
543     ASSERT(item.defined());
544
545     if (types & FIRE_INSCRIBED)
546         if (item.inscription.find(manual ? "+F" : "+f", 0) != string::npos)
547             return true;
548
549     if (item.base_type == OBJ_MISSILES)
550     {
551         if ((types & FIRE_DART) && item.sub_type == MI_DART)
552             return true;
553         if ((types & FIRE_STONE) && item.sub_type == MI_STONE)
554             return true;
555         if ((types & FIRE_JAVELIN) && item.sub_type == MI_JAVELIN)
556             return true;
557         if ((types & FIRE_ROCK) && item.sub_type == MI_LARGE_ROCK)
558             return true;
559         if ((types & FIRE_NET) && item.sub_type == MI_THROWING_NET)
560             return true;
561
562         if (types & FIRE_LAUNCHER)
563         {
564             if (launcher && item.launched_by(*launcher))
565                 return true;
566         }
567     }
568     else if (item.base_type == OBJ_WEAPONS && is_throwable(&you, item))
569     {
570         if ((types & FIRE_RETURNING)
571             && item.special == SPWPN_RETURNING
572             && item_ident(item, ISFLAG_KNOW_TYPE))
573         {
574             return true;
575         }
576         if ((types & FIRE_DAGGER) && item.sub_type == WPN_DAGGER)
577             return true;
578         if ((types & FIRE_SPEAR) && item.sub_type == WPN_SPEAR)
579             return true;
580         if ((types & FIRE_HAND_AXE) && item.sub_type == WPN_HAND_AXE)
581             return true;
582         if ((types & FIRE_CLUB) && item.sub_type == WPN_CLUB)
583             return true;
584     }
585     return false;
586 }
587
588 // Returns inv slot that contains an item that looks like item,
589 // or -1 if not in inv.
590 static int _get_pack_slot(const item_def& item)
591 {
592     if (!item.defined())
593         return -1;
594
595     if (in_inventory(item) && _items_similar(item, you.inv[item.link], false))
596         return item.link;
597
598     // First try to find the exact same item.
599     for (int i = 0; i < ENDOFPACK; i++)
600     {
601         const item_def &inv_item = you.inv[i];
602         if (inv_item.quantity && _items_similar(item, inv_item, false)
603             && !_wielded_slot_no_quiver(i))
604         {
605             return i;
606         }
607     }
608
609     // If that fails, try to find an item sufficiently similar.
610     for (int i = 0; i < ENDOFPACK; i++)
611     {
612         const item_def &inv_item = you.inv[i];
613         if (inv_item.quantity && _items_similar(item, inv_item, true)
614             && !_wielded_slot_no_quiver(i))
615         {
616             return i;
617         }
618     }
619
620     return -1;
621 }
622
623 // Returns the type of ammo used by the player's equipped weapon,
624 // or AMMO_THROW if it's not a launcher.
625 static ammo_t _get_weapon_ammo_type(const item_def* weapon)
626 {
627     if (weapon == NULL)
628         return AMMO_THROW;
629     if (weapon->base_type != OBJ_WEAPONS)
630         return AMMO_THROW;
631
632     switch (weapon->sub_type)
633     {
634         case WPN_BLOWGUN:
635             return AMMO_BLOWGUN;
636         case WPN_SLING:
637             return AMMO_SLING;
638         case WPN_BOW:
639         case WPN_LONGBOW:
640             return AMMO_BOW;
641         case WPN_CROSSBOW:
642             return AMMO_CROSSBOW;
643         default:
644             return AMMO_THROW;
645     }
646 }
647
648 static bool _items_similar(const item_def& a, const item_def& b, bool force)
649 {
650     return (items_similar(a, b) && (force || a.slot == b.slot));
651 }