OSDN Git Service

Merge branch 'master' of https://github.com/hengband/hengband
[hengbandforosx/hengbandosx.git] / src / floor / floor-object.cpp
1 /*!
2  * @brief フロア生成時にアイテムを配置する
3  * @date 2020/06/01
4  * @author Hourier
5  * @todo ちょっとギリギリ。後で分割を検討する
6  */
7
8 #include "floor/floor-object.h"
9 #include "artifact/fixed-art-generator.h"
10 #include "core/window-redrawer.h"
11 #include "flavor/flavor-describer.h"
12 #include "flavor/object-flavor-types.h"
13 #include "floor/cave.h"
14 #include "game-option/birth-options.h"
15 #include "game-option/cheat-options.h"
16 #include "game-option/cheat-types.h"
17 #include "grid/feature.h"
18 #include "grid/grid.h"
19 #include "inventory/inventory-slot-types.h"
20 #include "inventory/item-getter.h"
21 #include "main/sound-definitions-table.h"
22 #include "main/sound-of-music.h"
23 #include "object-enchant/item-apply-magic.h"
24 #include "object-enchant/item-magic-applier.h"
25 #include "object-enchant/special-object-flags.h"
26 #include "object/object-info.h"
27 #include "object/object-kind-hook.h"
28 #include "object/object-stack.h"
29 #include "perception/object-perception.h"
30 #include "system/alloc-entries.h"
31 #include "system/artifact-type-definition.h"
32 #include "system/floor-type-definition.h"
33 #include "system/grid-type-definition.h"
34 #include "system/item-entity.h"
35 #include "system/monster-entity.h"
36 #include "system/player-type-definition.h"
37 #include "system/redrawing-flags-updater.h"
38 #include "system/system-variables.h"
39 #include "target/projection-path-calculator.h"
40 #include "util/bit-flags-calculator.h"
41 #include "view/display-messages.h"
42 #include "window/display-sub-windows.h"
43 #include "wizard/wizard-messages.h"
44 #include "world/world-object.h"
45 #include "world/world.h"
46
47 #define MAX_GOLD 18 /* Number of "gold" entries */
48
49 /*!
50  * @brief オブジェクト生成テーブルに生成制約を加える /
51  * Apply a "object restriction function" to the "object allocation table"
52  * @return 常に0を返す。
53  * @details 生成の制約はグローバルのget_obj_index_hook関数ポインタで加える
54  */
55 static errr get_obj_index_prep(void)
56 {
57     for (auto &entry : alloc_kind_table) {
58         if (!get_obj_index_hook || (*get_obj_index_hook)(entry.index)) {
59             entry.prob2 = entry.prob1;
60         } else {
61             entry.prob2 = 0;
62         }
63     }
64
65     return 0;
66 }
67
68 /*!
69  * @brief デバッグ時にアイテム生成情報をメッセージに出力する / Cheat -- describe a created object for the user
70  * @param player_ptr プレイヤーへの参照ポインタ
71  * @param o_ptr デバッグ出力するオブジェクトの構造体参照ポインタ
72  */
73 static void object_mention(PlayerType *player_ptr, ItemEntity *o_ptr)
74 {
75     object_aware(player_ptr, o_ptr);
76     o_ptr->mark_as_known();
77
78     o_ptr->ident |= (IDENT_FULL_KNOWN);
79     const auto item_name = describe_flavor(player_ptr, o_ptr, 0);
80     msg_format_wizard(player_ptr, CHEAT_OBJECT, _("%sを生成しました。", "%s was generated."), item_name.data());
81 }
82
83 static int get_base_floor(FloorType *floor_ptr, BIT_FLAGS mode, std::optional<int> rq_mon_level)
84 {
85     if (any_bits(mode, AM_GREAT)) {
86         if (rq_mon_level) {
87             return *rq_mon_level + 10 + randint1(10);
88         }
89
90         return floor_ptr->object_level + 15;
91     }
92
93     if (any_bits(mode, AM_GOOD)) {
94         return floor_ptr->object_level + 10;
95     }
96
97     return floor_ptr->object_level;
98 }
99
100 static void set_ammo_quantity(ItemEntity *j_ptr)
101 {
102     auto is_ammo = j_ptr->is_ammo();
103     is_ammo |= j_ptr->bi_key.tval() == ItemKindType::SPIKE;
104     if (is_ammo && !j_ptr->is_fixed_artifact()) {
105         j_ptr->number = Dice::roll(6, 7);
106     }
107 }
108
109 /*!
110  * @brief 生成階に応じたベースアイテムの生成を行う。
111  * Attempt to make an object (normal or good/great)
112  * @param player_ptr プレイヤーへの参照ポインタ
113  * @param j_ptr 生成結果を収めたいオブジェクト構造体の参照ポインタ
114  * @param mode オプションフラグ
115  * @param rq_mon_level ランダムクエスト討伐対象のレベル。ランダムクエスト以外の生成であれば無効値
116  * @return アイテムの生成成功可否
117  */
118 bool make_object(PlayerType *player_ptr, ItemEntity *j_ptr, BIT_FLAGS mode, std::optional<int> rq_mon_level)
119 {
120     auto *floor_ptr = player_ptr->current_floor_ptr;
121     auto prob = any_bits(mode, AM_GOOD) ? 10 : 1000;
122     auto base = get_base_floor(floor_ptr, mode, rq_mon_level);
123     if (!one_in_(prob) || !make_artifact_special(player_ptr, j_ptr)) {
124         if (any_bits(mode, AM_GOOD) && !get_obj_index_hook) {
125             get_obj_index_hook = kind_is_good;
126         }
127
128         if (get_obj_index_hook) {
129             get_obj_index_prep();
130         }
131
132         auto bi_id = get_obj_index(floor_ptr, base, mode);
133         if (get_obj_index_hook) {
134             get_obj_index_hook = nullptr;
135             get_obj_index_prep();
136         }
137
138         if (bi_id == 0) {
139             return false;
140         }
141
142         j_ptr->generate(bi_id);
143     }
144
145     ItemMagicApplier(player_ptr, j_ptr, floor_ptr->object_level, mode).execute();
146     set_ammo_quantity(j_ptr);
147     if (cheat_peek) {
148         object_mention(player_ptr, j_ptr);
149     }
150
151     return true;
152 }
153
154 /*!
155  * @brief 生成階に応じた財宝オブジェクトの生成を行う。
156  * @param floor 現在フロアへの参照
157  * @param j_ptr 生成結果を収めたいアイテムの参照ポインタ
158  * @return 生成に成功したらTRUEを返す。
159  */
160 bool make_gold(PlayerType *player_ptr, ItemEntity *j_ptr)
161 {
162     const auto &floor = *player_ptr->current_floor_ptr;
163     auto i = ((randint1(floor.object_level + 2) + 2) / 2) - 1;
164     if (one_in_(CHANCE_BASEITEM_LEVEL_BOOST)) {
165         i += randint1(floor.object_level + 1);
166     }
167
168     if (coin_type) {
169         i = coin_type;
170     }
171
172     if (i >= MAX_GOLD) {
173         i = MAX_GOLD - 1;
174     }
175
176     j_ptr->generate(OBJ_GOLD_LIST + i);
177     const auto &baseitems = BaseitemList::get_instance();
178     const auto base = baseitems.get_baseitem(OBJ_GOLD_LIST + i).cost;
179     j_ptr->pval = (base + (8L * randint1(base)) + randint1(8));
180     return true;
181 }
182
183 /*!
184  * @brief フロア中のアイテムを全て削除する / Deletes all objects at given location
185  * Delete a dungeon object
186  * @param player_ptr プレイヤーへの参照ポインタ
187  * @param y 削除したフロアマスのY座標
188  * @param x 削除したフロアマスのX座標
189  */
190 void delete_all_items_from_floor(PlayerType *player_ptr, POSITION y, POSITION x)
191 {
192     Grid *g_ptr;
193     auto *floor_ptr = player_ptr->current_floor_ptr;
194     if (!in_bounds(floor_ptr, y, x)) {
195         return;
196     }
197
198     g_ptr = &floor_ptr->grid_array[y][x];
199     for (const auto this_o_idx : g_ptr->o_idx_list) {
200         ItemEntity *o_ptr;
201         o_ptr = &floor_ptr->o_list[this_o_idx];
202         o_ptr->wipe();
203         floor_ptr->o_cnt--;
204     }
205
206     g_ptr->o_idx_list.clear();
207     lite_spot(player_ptr, y, x);
208 }
209
210 /*!
211  * @brief 床上のアイテムの数を増やす /
212  * Increase the "number" of an item on the floor
213  * @param player_ptr プレイヤーへの参照ポインタ
214  * @param i_idx 増やしたいアイテムの所持スロット
215  * @param num 増やしたいアイテムの数
216  */
217 void floor_item_increase(PlayerType *player_ptr, INVENTORY_IDX i_idx, ITEM_NUMBER num)
218 {
219     auto *floor_ptr = player_ptr->current_floor_ptr;
220
221     auto *o_ptr = &floor_ptr->o_list[i_idx];
222     num += o_ptr->number;
223     if (num > 255) {
224         num = 255;
225     } else if (num < 0) {
226         num = 0;
227     }
228
229     num -= o_ptr->number;
230     o_ptr->number += num;
231     static constexpr auto flags = {
232         SubWindowRedrawingFlag::FLOOR_ITEMS,
233         SubWindowRedrawingFlag::FOUND_ITEMS,
234     };
235     RedrawingFlagsUpdater::get_instance().set_flags(flags);
236 }
237
238 /*!
239  * @brief 床上の数の無くなったアイテムスロットを消去する /
240  * Optimize an item on the floor (destroy "empty" items)
241  * @param player_ptr プレイヤーへの参照ポインタ
242  * @param i_idx 消去したいアイテムの所持スロット
243  */
244 void floor_item_optimize(PlayerType *player_ptr, INVENTORY_IDX i_idx)
245 {
246     auto *o_ptr = &player_ptr->current_floor_ptr->o_list[i_idx];
247     if (!o_ptr->is_valid()) {
248         return;
249     }
250     if (o_ptr->number) {
251         return;
252     }
253
254     delete_object_idx(player_ptr, i_idx);
255     static constexpr auto flags = {
256         SubWindowRedrawingFlag::FLOOR_ITEMS,
257         SubWindowRedrawingFlag::FOUND_ITEMS,
258     };
259     RedrawingFlagsUpdater::get_instance().set_flags(flags);
260 }
261
262 /*!
263  * @brief オブジェクトを削除する /
264  * Delete a dungeon object
265  * @param player_ptr プレイヤーへの参照ポインタ
266  * @param o_idx 削除対象のオブジェクト構造体ポインタ
267  * @details
268  * Handle "stacks" of objects correctly.
269  */
270 void delete_object_idx(PlayerType *player_ptr, OBJECT_IDX o_idx)
271 {
272     ItemEntity *j_ptr;
273     auto *floor_ptr = player_ptr->current_floor_ptr;
274     excise_object_idx(floor_ptr, o_idx);
275     j_ptr = &floor_ptr->o_list[o_idx];
276     if (!j_ptr->is_held_by_monster()) {
277         POSITION y, x;
278         y = j_ptr->iy;
279         x = j_ptr->ix;
280         lite_spot(player_ptr, y, x);
281     }
282
283     j_ptr->wipe();
284     floor_ptr->o_cnt--;
285     static constexpr auto flags = {
286         SubWindowRedrawingFlag::FLOOR_ITEMS,
287         SubWindowRedrawingFlag::FOUND_ITEMS,
288     };
289     RedrawingFlagsUpdater::get_instance().set_flags(flags);
290 }
291
292 /*!
293  * @brief 床上、モンスター所持でスタックされたアイテムを削除しスタックを補完する / Excise a dungeon object from any stacks
294  * @param floo_ptr 現在フロアへの参照ポインタ
295  * @param o_idx 削除対象のオブジェクト構造体ポインタ
296  */
297 void excise_object_idx(FloorType *floor_ptr, OBJECT_IDX o_idx)
298 {
299     auto &list = get_o_idx_list_contains(floor_ptr, o_idx);
300     list.remove(o_idx);
301 }
302
303 /*!
304  * @brief 指定したOBJECT_IDXを含むリスト(モンスター所持リスト or 床上スタックリスト)への参照を得る
305  * @param floo_ptr 現在フロアへの参照ポインタ
306  * @param o_idx 参照を得るリストに含まれるOBJECT_IDX
307  * @return o_idxを含む ObjectIndexList への参照
308  */
309 ObjectIndexList &get_o_idx_list_contains(FloorType *floor_ptr, OBJECT_IDX o_idx)
310 {
311     auto *o_ptr = &floor_ptr->o_list[o_idx];
312
313     if (o_ptr->is_held_by_monster()) {
314         return floor_ptr->m_list[o_ptr->held_m_idx].hold_o_idx_list;
315     } else {
316         return floor_ptr->grid_array[o_ptr->iy][o_ptr->ix].o_idx_list;
317     }
318 }
319
320 /*!
321  * @brief 生成済のオブジェクトをフロアの所定の位置に落とす。
322  * Let an object fall to the ground at or near a location.
323  * @param player_ptr プレイヤーへの参照ポインタ
324  * @param j_ptr 落としたいオブジェクト構造体の参照ポインタ
325  * @param chance ドロップの消滅率(%)
326  * @param y 配置したいフロアのY座標
327  * @param x 配置したいフロアのX座標
328  * @return 生成に成功したらオブジェクトのIDを返す。
329  * @details
330  * The initial location is assumed to be "in_bounds(floor, )".\n
331  *\n
332  * This function takes a parameter "chance".  This is the percentage\n
333  * chance that the item will "disappear" instead of drop.  If the object\n
334  * has been thrown, then this is the chance of disappearance on contact.\n
335  *\n
336  * Hack -- this function uses "chance" to determine if it should produce\n
337  * some form of "description" of the drop event (under the player).\n
338  *\n
339  * We check several locations to see if we can find a location at which\n
340  * the object can combine, stack, or be placed.  Artifacts will try very\n
341  * hard to be placed, including "teleporting" to a useful grid if needed.\n
342  */
343 OBJECT_IDX drop_near(PlayerType *player_ptr, ItemEntity *j_ptr, PERCENTAGE chance, POSITION y, POSITION x)
344 {
345     int i, k, d, s;
346     POSITION dy, dx;
347     POSITION ty, tx = 0;
348     OBJECT_IDX o_idx = 0;
349     Grid *g_ptr;
350     bool flag = false;
351     bool done = false;
352 #ifdef JP
353 #else
354     bool plural = (j_ptr->number != 1);
355 #endif
356     const auto &world = AngbandWorld::get_instance();
357     const auto item_name = describe_flavor(player_ptr, j_ptr, (OD_OMIT_PREFIX | OD_NAME_ONLY));
358     if (!j_ptr->is_fixed_or_random_artifact() && evaluate_percent(chance)) {
359 #ifdef JP
360         msg_format("%sは消えた。", item_name.data());
361 #else
362         msg_format("The %s disappear%s.", item_name.data(), (plural ? "" : "s"));
363 #endif
364         if (world.wizard) {
365             msg_print(_("(破損)", "(breakage)"));
366         }
367
368         return 0;
369     }
370
371     int bs = -1;
372     int bn = 0;
373
374     POSITION by = y;
375     POSITION bx = x;
376     auto *floor_ptr = player_ptr->current_floor_ptr;
377     for (dy = -3; dy <= 3; dy++) {
378         for (dx = -3; dx <= 3; dx++) {
379             bool comb = false;
380             d = (dy * dy) + (dx * dx);
381             if (d > 10) {
382                 continue;
383             }
384
385             ty = y + dy;
386             tx = x + dx;
387             if (!in_bounds(floor_ptr, ty, tx)) {
388                 continue;
389             }
390             if (!projectable(player_ptr, y, x, ty, tx)) {
391                 continue;
392             }
393
394             g_ptr = &floor_ptr->grid_array[ty][tx];
395             if (!cave_drop_bold(floor_ptr, ty, tx)) {
396                 continue;
397             }
398
399             k = 0;
400             for (const auto this_o_idx : g_ptr->o_idx_list) {
401                 ItemEntity *o_ptr;
402                 o_ptr = &floor_ptr->o_list[this_o_idx];
403                 if (object_similar(o_ptr, j_ptr)) {
404                     comb = true;
405                 }
406
407                 k++;
408             }
409
410             if (!comb) {
411                 k++;
412             }
413             if (k > 99) {
414                 continue;
415             }
416
417             s = 1000 - (d + k * 5);
418             if (s < bs) {
419                 continue;
420             }
421
422             if (s > bs) {
423                 bn = 0;
424             }
425
426             if ((++bn >= 2) && !one_in_(bn)) {
427                 continue;
428             }
429
430             bs = s;
431             by = ty;
432             bx = tx;
433
434             flag = true;
435         }
436     }
437
438     if (!flag && !j_ptr->is_fixed_or_random_artifact()) {
439 #ifdef JP
440         msg_format("%sは消えた。", item_name.data());
441 #else
442         msg_format("The %s disappear%s.", item_name.data(), (plural ? "" : "s"));
443 #endif
444         if (world.wizard) {
445             msg_print(_("(床スペースがない)", "(no floor space)"));
446         }
447
448         return 0;
449     }
450
451     for (i = 0; !flag && (i < 1000); i++) {
452         ty = rand_spread(by, 1);
453         tx = rand_spread(bx, 1);
454
455         if (!in_bounds(floor_ptr, ty, tx)) {
456             continue;
457         }
458
459         by = ty;
460         bx = tx;
461
462         if (!cave_drop_bold(floor_ptr, by, bx)) {
463             continue;
464         }
465
466         flag = true;
467     }
468
469     auto &artifact = j_ptr->get_fixed_artifact();
470     if (!flag) {
471         int candidates = 0, pick;
472         for (ty = 1; ty < floor_ptr->height - 1; ty++) {
473             for (tx = 1; tx < floor_ptr->width - 1; tx++) {
474                 if (cave_drop_bold(floor_ptr, ty, tx)) {
475                     candidates++;
476                 }
477             }
478         }
479
480         if (!candidates) {
481 #ifdef JP
482             msg_format("%sは消えた。", item_name.data());
483 #else
484             msg_format("The %s disappear%s.", item_name.data(), (plural ? "" : "s"));
485 #endif
486
487             if (world.wizard) {
488                 msg_print(_("(床スペースがない)", "(no floor space)"));
489             }
490
491             if (preserve_mode) {
492                 if (j_ptr->is_fixed_artifact() && !j_ptr->is_known()) {
493                     artifact.is_generated = false;
494                 }
495             }
496
497             return 0;
498         }
499
500         pick = randint1(candidates);
501         for (ty = 1; ty < floor_ptr->height - 1; ty++) {
502             for (tx = 1; tx < floor_ptr->width - 1; tx++) {
503                 if (cave_drop_bold(floor_ptr, ty, tx)) {
504                     pick--;
505                     if (!pick) {
506                         break;
507                     }
508                 }
509             }
510
511             if (!pick) {
512                 break;
513             }
514         }
515
516         by = ty;
517         bx = tx;
518     }
519
520     g_ptr = &floor_ptr->grid_array[by][bx];
521     for (const auto this_o_idx : g_ptr->o_idx_list) {
522         ItemEntity *o_ptr;
523         o_ptr = &floor_ptr->o_list[this_o_idx];
524         if (object_similar(o_ptr, j_ptr)) {
525             object_absorb(o_ptr, j_ptr);
526             done = true;
527             break;
528         }
529     }
530
531     if (!done) {
532         o_idx = o_pop(floor_ptr);
533     }
534
535     if (!done && !o_idx) {
536 #ifdef JP
537         msg_format("%sは消えた。", item_name.data());
538 #else
539         msg_format("The %s disappear%s.", item_name.data(), (plural ? "" : "s"));
540 #endif
541         if (world.wizard) {
542             msg_print(_("(アイテムが多過ぎる)", "(too many objects)"));
543         }
544
545         if (j_ptr->is_fixed_artifact()) {
546             artifact.is_generated = false;
547         }
548
549         return 0;
550     }
551
552     if (!done) {
553         (&floor_ptr->o_list[o_idx])->copy_from(j_ptr);
554         j_ptr = &floor_ptr->o_list[o_idx];
555         j_ptr->iy = by;
556         j_ptr->ix = bx;
557         j_ptr->held_m_idx = 0;
558         g_ptr->o_idx_list.add(floor_ptr, o_idx);
559         done = true;
560     }
561
562     if (j_ptr->is_fixed_artifact() && world.character_dungeon) {
563         artifact.floor_id = player_ptr->floor_id;
564     }
565
566     note_spot(player_ptr, by, bx);
567     lite_spot(player_ptr, by, bx);
568     sound(SOUND_DROP);
569
570     const auto is_located = player_ptr->is_located_at({ by, bx });
571     if (is_located) {
572         static constexpr auto flags = {
573             SubWindowRedrawingFlag::FLOOR_ITEMS,
574             SubWindowRedrawingFlag::FOUND_ITEMS,
575         };
576         RedrawingFlagsUpdater::get_instance().set_flags(flags);
577     }
578
579     if (chance && is_located) {
580         msg_print(_("何かが足下に転がってきた。", "You feel something roll beneath your feet."));
581     }
582
583     return o_idx;
584 }
585
586 /*!
587  * @brief 床上の魔道具の残り残量メッセージを表示する
588  * @param floo_ptr 現在フロアへの参照ポインタ
589  * @param i_idx メッセージの対象にしたいアイテム所持スロット
590  */
591 void floor_item_charges(FloorType *floor_ptr, INVENTORY_IDX i_idx)
592 {
593     const auto &item = floor_ptr->o_list[i_idx];
594     if (!item.is_wand_staff() || !item.is_known()) {
595         return;
596     }
597
598 #ifdef JP
599     if (item.pval <= 0) {
600         msg_print("この床上のアイテムは、もう魔力が残っていない。");
601     } else {
602         msg_format("この床上のアイテムは、あと %d 回分の魔力が残っている。", item.pval);
603     }
604 #else
605     if (item.pval != 1) {
606         msg_format("There are %d charges remaining.", item.pval);
607     } else {
608         msg_format("There is %d charge remaining.", item.pval);
609     }
610 #endif
611 }
612
613 /*!
614  * @brief 床上のアイテムの残り数メッセージを表示する /
615  * Describe the charges on an item on the floor.
616  * @param floo_ptr 現在フロアへの参照ポインタ
617  * @param i_idx メッセージの対象にしたいアイテム所持スロット
618  */
619 void floor_item_describe(PlayerType *player_ptr, INVENTORY_IDX i_idx)
620 {
621     auto *o_ptr = &player_ptr->current_floor_ptr->o_list[i_idx];
622     const auto item_name = describe_flavor(player_ptr, o_ptr, 0);
623 #ifdef JP
624     if (o_ptr->number <= 0) {
625         msg_format("床上には、もう%sはない。", item_name.data());
626     } else {
627         msg_format("床上には、まだ %sがある。", item_name.data());
628     }
629 #else
630     msg_format("You see %s.", item_name.data());
631 #endif
632 }
633
634 /*
635  * @brief Choose an item and get auto-picker entry from it.
636  * @todo initial_i_idx をポインタではなく値に変え、戻り値をstd::pairに変える
637  */
638 ItemEntity *choose_object(PlayerType *player_ptr, short *initial_i_idx, concptr q, concptr s, BIT_FLAGS option, const ItemTester &item_tester)
639 {
640     if (initial_i_idx) {
641         *initial_i_idx = INVEN_NONE;
642     }
643
644     FixItemTesterSetter setter(item_tester);
645     short i_idx;
646     if (!get_item(player_ptr, &i_idx, q, s, option, item_tester)) {
647         return nullptr;
648     }
649
650     if (initial_i_idx) {
651         *initial_i_idx = i_idx;
652     }
653
654     if (i_idx == INVEN_FORCE) {
655         return nullptr;
656     }
657
658     return ref_item(player_ptr, i_idx);
659 }