OSDN Git Service

Merge pull request #3367 from Hourier/Remove-memcpy-1
[hengbandforosx/hengbandosx.git] / src / inventory / inventory-object.cpp
1 #include "inventory/inventory-object.h"
2 #include "core/window-redrawer.h"
3 #include "flavor/flavor-describer.h"
4 #include "floor/floor-object.h"
5 #include "inventory/inventory-slot-types.h"
6 #include "object-hook/hook-weapon.h"
7 #include "object/object-info.h"
8 #include "object/object-mark-types.h"
9 #include "object/object-stack.h"
10 #include "object/object-value.h"
11 #include "player-info/equipment-info.h"
12 #include "spell-realm/spells-craft.h"
13 #include "system/item-entity.h"
14 #include "system/player-type-definition.h"
15 #include "system/redrawing-flags-updater.h"
16 #include "util/object-sort.h"
17 #include "view/display-messages.h"
18 #include "view/object-describer.h"
19
20 void vary_item(PlayerType *player_ptr, INVENTORY_IDX item, ITEM_NUMBER num)
21 {
22     if (item >= 0) {
23         inven_item_increase(player_ptr, item, num);
24         inven_item_describe(player_ptr, item);
25         inven_item_optimize(player_ptr, item);
26         return;
27     }
28
29     floor_item_increase(player_ptr, 0 - item, num);
30     floor_item_describe(player_ptr, 0 - item);
31     floor_item_optimize(player_ptr, 0 - item);
32 }
33
34 /*!
35  * @brief アイテムを増減させ残り所持数メッセージを表示する /
36  * Increase the "number" of an item in the inventory
37  * @param player_ptr プレイヤーへの参照ポインタ
38  * @param item 所持数を増やしたいプレイヤーのアイテム所持スロット
39  * @param num 増やしたい量
40  */
41 void inven_item_increase(PlayerType *player_ptr, INVENTORY_IDX item, ITEM_NUMBER num)
42 {
43     auto *o_ptr = &player_ptr->inventory_list[item];
44     num += o_ptr->number;
45     if (num > 255) {
46         num = 255;
47     } else if (num < 0) {
48         num = 0;
49     }
50
51     num -= o_ptr->number;
52     if (num == 0) {
53         return;
54     }
55
56     o_ptr->number += num;
57     auto &rfu = RedrawingFlagsUpdater::get_instance();
58     const auto flags_srf = {
59         StatusRedrawingFlag::BONUS,
60         StatusRedrawingFlag::MP,
61         StatusRedrawingFlag::COMBINATION,
62     };
63     rfu.set_flags(flags_srf);
64     const auto flags_swrf = {
65         SubWindowRedrawingFlag::INVENTORY,
66         SubWindowRedrawingFlag::EQUIPMENT,
67     };
68     rfu.set_flags(flags_swrf);
69     if (o_ptr->number || !player_ptr->ele_attack) {
70         return;
71     }
72
73     if (!(item == INVEN_MAIN_HAND) && !(item == INVEN_SUB_HAND)) {
74         return;
75     }
76
77     if (has_melee_weapon(player_ptr, enum2i(INVEN_MAIN_HAND + INVEN_SUB_HAND) - item)) {
78         return;
79     }
80
81     set_ele_attack(player_ptr, 0, 0);
82 }
83
84 /*!
85  * @brief 所持アイテムスロットから所持数のなくなったアイテムを消去する /
86  * Erase an inventory slot if it has no more items
87  * @param player_ptr プレイヤーへの参照ポインタ
88  * @param item 消去したいプレイヤーのアイテム所持スロット
89  */
90 void inven_item_optimize(PlayerType *player_ptr, INVENTORY_IDX item)
91 {
92     auto *o_ptr = &player_ptr->inventory_list[item];
93     if (!o_ptr->is_valid()) {
94         return;
95     }
96     if (o_ptr->number) {
97         return;
98     }
99
100     auto &rfu = RedrawingFlagsUpdater::get_instance();
101     if (item >= INVEN_MAIN_HAND) {
102         player_ptr->equip_cnt--;
103         (&player_ptr->inventory_list[item])->wipe();
104         const auto flags_srf = {
105             StatusRedrawingFlag::BONUS,
106             StatusRedrawingFlag::TORCH,
107             StatusRedrawingFlag::MP,
108         };
109         rfu.set_flags(flags_srf);
110         const auto flags_swrf = {
111             SubWindowRedrawingFlag::EQUIPMENT,
112             SubWindowRedrawingFlag::SPELL,
113         };
114         rfu.set_flags(flags_swrf);
115         return;
116     }
117
118     player_ptr->inven_cnt--;
119     int i;
120     for (i = item; i < INVEN_PACK; i++) {
121         player_ptr->inventory_list[i] = player_ptr->inventory_list[i + 1];
122     }
123
124     (&player_ptr->inventory_list[i])->wipe();
125     const auto flags = {
126         SubWindowRedrawingFlag::INVENTORY,
127         SubWindowRedrawingFlag::SPELL,
128     };
129     rfu.set_flags(flags);
130 }
131
132 /*!
133  * @brief 所持スロットから床下にオブジェクトを落とすメインルーチン /
134  * Drop (some of) a non-cursed inventory/equipment item
135  * @param player_ptr プレイヤーへの参照ポインタ
136  * @param item 所持テーブルのID
137  * @param amt 落としたい個数
138  * @details
139  * The object will be dropped "near" the current location
140  */
141 void drop_from_inventory(PlayerType *player_ptr, INVENTORY_IDX item, ITEM_NUMBER amt)
142 {
143     ItemEntity forge;
144     ItemEntity *q_ptr;
145     auto *o_ptr = &player_ptr->inventory_list[item];
146     if (amt <= 0) {
147         return;
148     }
149
150     if (amt > o_ptr->number) {
151         amt = o_ptr->number;
152     }
153
154     if (item >= INVEN_MAIN_HAND) {
155         item = inven_takeoff(player_ptr, item, amt);
156         o_ptr = &player_ptr->inventory_list[item];
157     }
158
159     q_ptr = &forge;
160     q_ptr->copy_from(o_ptr);
161     distribute_charges(o_ptr, q_ptr, amt);
162
163     q_ptr->number = amt;
164     const auto item_name = describe_flavor(player_ptr, q_ptr, 0);
165     msg_format(_("%s(%c)を落とした。", "You drop %s (%c)."), item_name.data(), index_to_label(item));
166     (void)drop_near(player_ptr, q_ptr, 0, player_ptr->y, player_ptr->x);
167     vary_item(player_ptr, item, -amt);
168 }
169
170 /*!
171  * @brief プレイヤーの所持スロットに存在するオブジェクトをまとめなおす /
172  * Combine items in the pack
173  * @details
174  * Note special handling of the "overflow" slot
175  */
176 void combine_pack(PlayerType *player_ptr)
177 {
178     bool flag = false;
179     bool is_first_combination = true;
180     bool combined = true;
181     while (is_first_combination || combined) {
182         is_first_combination = false;
183         combined = false;
184
185         for (int i = INVEN_PACK; i > 0; i--) {
186             ItemEntity *o_ptr;
187             o_ptr = &player_ptr->inventory_list[i];
188             if (!o_ptr->is_valid()) {
189                 continue;
190             }
191             for (int j = 0; j < i; j++) {
192                 ItemEntity *j_ptr;
193                 j_ptr = &player_ptr->inventory_list[j];
194                 if (!j_ptr->is_valid()) {
195                     continue;
196                 }
197
198                 /*
199                  * Get maximum number of the stack if these
200                  * are similar, get zero otherwise.
201                  */
202                 int max_num = object_similar_part(j_ptr, o_ptr);
203
204                 bool is_max = (max_num != 0) && (j_ptr->number < max_num);
205                 if (!is_max) {
206                     continue;
207                 }
208
209                 if (o_ptr->number + j_ptr->number <= max_num) {
210                     flag = true;
211                     object_absorb(j_ptr, o_ptr);
212                     player_ptr->inven_cnt--;
213                     int k;
214                     for (k = i; k < INVEN_PACK; k++) {
215                         player_ptr->inventory_list[k] = player_ptr->inventory_list[k + 1];
216                     }
217
218                     (&player_ptr->inventory_list[k])->wipe();
219                 } else {
220                     int old_num = o_ptr->number;
221                     int remain = j_ptr->number + o_ptr->number - max_num;
222                     object_absorb(j_ptr, o_ptr);
223                     o_ptr->number = remain;
224                     const auto tval = o_ptr->bi_key.tval();
225                     if (tval == ItemKindType::ROD) {
226                         o_ptr->pval = o_ptr->pval * remain / old_num;
227                         o_ptr->timeout = o_ptr->timeout * remain / old_num;
228                     }
229
230                     if (tval == ItemKindType::WAND) {
231                         o_ptr->pval = o_ptr->pval * remain / old_num;
232                     }
233                 }
234
235                 RedrawingFlagsUpdater::get_instance().set_flag(SubWindowRedrawingFlag::INVENTORY);
236                 combined = true;
237                 break;
238             }
239         }
240     }
241
242     if (flag) {
243         msg_print(_("ザックの中のアイテムをまとめ直した。", "You combine some items in your pack."));
244     }
245 }
246
247 /*!
248  * @brief プレイヤーの所持スロットに存在するオブジェクトを並び替える /
249  * Reorder items in the pack
250  * @param player_ptr プレイヤーへの参照ポインタ
251  * @details
252  * Note special handling of the "overflow" slot
253  */
254 void reorder_pack(PlayerType *player_ptr)
255 {
256     int i, j, k;
257     int32_t o_value;
258     ItemEntity forge;
259     ItemEntity *q_ptr;
260     ItemEntity *o_ptr;
261     bool flag = false;
262
263     for (i = 0; i < INVEN_PACK; i++) {
264         if ((i == INVEN_PACK) && (player_ptr->inven_cnt == INVEN_PACK)) {
265             break;
266         }
267
268         o_ptr = &player_ptr->inventory_list[i];
269         if (!o_ptr->is_valid()) {
270             continue;
271         }
272
273         o_value = o_ptr->get_price();
274         for (j = 0; j < INVEN_PACK; j++) {
275             if (object_sort_comp(player_ptr, o_ptr, o_value, &player_ptr->inventory_list[j])) {
276                 break;
277             }
278         }
279
280         if (j >= i) {
281             continue;
282         }
283
284         flag = true;
285         q_ptr = &forge;
286         q_ptr->copy_from(&player_ptr->inventory_list[i]);
287         for (k = i; k > j; k--) {
288             (&player_ptr->inventory_list[k])->copy_from(&player_ptr->inventory_list[k - 1]);
289         }
290
291         (&player_ptr->inventory_list[j])->copy_from(q_ptr);
292         RedrawingFlagsUpdater::get_instance().set_flag(SubWindowRedrawingFlag::INVENTORY);
293     }
294
295     if (flag) {
296         msg_print(_("ザックの中のアイテムを並べ直した。", "You reorder some items in your pack."));
297     }
298 }
299
300 /*!
301  * @brief オブジェクトをプレイヤーが拾って所持スロットに納めるメインルーチン /
302  * Add an item to the players inventory, and return the slot used.
303  * @param o_ptr 拾うオブジェクトの構造体参照ポインタ
304  * @return 収められた所持スロットのID、拾うことができなかった場合-1を返す。
305  * @details
306  * If the new item can combine with an existing item in the inventory,\n
307  * it will do so, using "object_similar()" and "object_absorb()", else,\n
308  * the item will be placed into the "proper" location in the inventory.\n
309  *\n
310  * This function can be used to "over-fill" the player's pack, but only\n
311  * once, and such an action must trigger the "overflow" code immediately.\n
312  * Note that when the pack is being "over-filled", the new item must be\n
313  * placed into the "overflow" slot, and the "overflow" must take place\n
314  * before the pack is reordered, but (optionally) after the pack is\n
315  * combined.  This may be tricky.  See "dungeon.c" for info.\n
316  *\n
317  * Note that this code must remove any location/stack information\n
318  * from the object once it is placed into the inventory.\n
319  */
320 int16_t store_item_to_inventory(PlayerType *player_ptr, ItemEntity *o_ptr)
321 {
322     INVENTORY_IDX i, j, k;
323     INVENTORY_IDX n = -1;
324
325     ItemEntity *j_ptr;
326     auto &rfu = RedrawingFlagsUpdater::get_instance();
327     const auto flags_swrf = {
328         SubWindowRedrawingFlag::INVENTORY,
329         SubWindowRedrawingFlag::PLAYER,
330     };
331     for (j = 0; j < INVEN_PACK; j++) {
332         j_ptr = &player_ptr->inventory_list[j];
333         if (!j_ptr->is_valid()) {
334             continue;
335         }
336
337         n = j;
338         if (object_similar(j_ptr, o_ptr)) {
339             object_absorb(j_ptr, o_ptr);
340             rfu.set_flag(StatusRedrawingFlag::BONUS);
341             rfu.set_flags(flags_swrf);
342             return j;
343         }
344     }
345
346     if (player_ptr->inven_cnt > INVEN_PACK) {
347         return -1;
348     }
349
350     for (j = 0; j <= INVEN_PACK; j++) {
351         j_ptr = &player_ptr->inventory_list[j];
352         if (!j_ptr->is_valid()) {
353             break;
354         }
355     }
356
357     i = j;
358     if (i < INVEN_PACK) {
359         const auto o_value = o_ptr->get_price();
360         for (j = 0; j < INVEN_PACK; j++) {
361             if (object_sort_comp(player_ptr, o_ptr, o_value, &player_ptr->inventory_list[j])) {
362                 break;
363             }
364         }
365
366         i = j;
367         for (k = n; k >= i; k--) {
368             (&player_ptr->inventory_list[k + 1])->copy_from(&player_ptr->inventory_list[k]);
369         }
370
371         (&player_ptr->inventory_list[i])->wipe();
372     }
373
374     (&player_ptr->inventory_list[i])->copy_from(o_ptr);
375     j_ptr = &player_ptr->inventory_list[i];
376     j_ptr->held_m_idx = 0;
377     j_ptr->iy = j_ptr->ix = 0;
378     j_ptr->marked.clear().set(OmType::TOUCHED);
379
380     player_ptr->inven_cnt++;
381     const auto flags_srf = {
382         StatusRedrawingFlag::BONUS,
383         StatusRedrawingFlag::COMBINATION,
384         StatusRedrawingFlag::REORDER,
385     };
386     rfu.set_flags(flags_srf);
387     rfu.set_flags(flags_swrf);
388     return i;
389 }
390
391 /*!
392  * @brief アイテムを拾う際にザックから溢れずに済むかを判定する /
393  * Check if we have space for an item in the pack without overflow
394  * @param player_ptr プレイヤーへの参照ポインタ
395  * @param o_ptr 拾いたいオブジェクトの構造体参照ポインタ
396  * @return 溢れずに済むならTRUEを返す
397  */
398 bool check_store_item_to_inventory(PlayerType *player_ptr, const ItemEntity *o_ptr)
399 {
400     if (player_ptr->inven_cnt < INVEN_PACK) {
401         return true;
402     }
403
404     for (int j = 0; j < INVEN_PACK; j++) {
405         auto *j_ptr = &player_ptr->inventory_list[j];
406         if (!j_ptr->is_valid()) {
407             continue;
408         }
409
410         if (object_similar(j_ptr, o_ptr)) {
411             return true;
412         }
413     }
414
415     return false;
416 }
417
418 /*!
419  * @brief 装備スロットからオブジェクトを外すメインルーチン /
420  * Take off (some of) a non-cursed equipment item
421  * @param player_ptr プレイヤーへの参照ポインタ
422  * @param item オブジェクトを外したい所持テーブルのID
423  * @param amt 外したい個数
424  * @return 収められた所持スロットのID、拾うことができなかった場合-1を返す。
425  * @details
426  * Note that only one item at a time can be wielded per slot.\n
427  * Note that taking off an item when "full" may cause that item\n
428  * to fall to the ground.\n
429  * Return the inventory slot into which the item is placed.\n
430  */
431 INVENTORY_IDX inven_takeoff(PlayerType *player_ptr, INVENTORY_IDX item, ITEM_NUMBER amt)
432 {
433     INVENTORY_IDX slot;
434     ItemEntity forge;
435     ItemEntity *q_ptr;
436     ItemEntity *o_ptr;
437     concptr act;
438     o_ptr = &player_ptr->inventory_list[item];
439     if (amt <= 0) {
440         return -1;
441     }
442
443     if (amt > o_ptr->number) {
444         amt = o_ptr->number;
445     }
446     q_ptr = &forge;
447     q_ptr->copy_from(o_ptr);
448     q_ptr->number = amt;
449     const auto item_name = describe_flavor(player_ptr, q_ptr, 0);
450     if (((item == INVEN_MAIN_HAND) || (item == INVEN_SUB_HAND)) && o_ptr->is_melee_weapon()) {
451         act = _("を装備からはずした", "You were wielding");
452     } else if (item == INVEN_BOW) {
453         act = _("を装備からはずした", "You were holding");
454     } else if (item == INVEN_LITE) {
455         act = _("を光源からはずした", "You were holding");
456     } else {
457         act = _("を装備からはずした", "You were wearing");
458     }
459
460     inven_item_increase(player_ptr, item, -amt);
461     inven_item_optimize(player_ptr, item);
462
463     slot = store_item_to_inventory(player_ptr, q_ptr);
464 #ifdef JP
465     msg_format("%s(%c)%s。", item_name.data(), index_to_label(slot), act);
466 #else
467     msg_format("%s %s (%c).", act, item_name.data(), index_to_label(slot));
468 #endif
469
470     return slot;
471 }