2 * @brief フロア生成時にアイテムを配置する
5 * @todo ちょっとギリギリ。後で分割を検討する
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"
47 #define MAX_GOLD 18 /* Number of "gold" entries */
50 * @brief オブジェクト生成テーブルに生成制約を加える /
51 * Apply a "object restriction function" to the "object allocation table"
53 * @details 生成の制約はグローバルのget_obj_index_hook関数ポインタで加える
55 static errr get_obj_index_prep(void)
57 for (auto &entry : alloc_kind_table) {
58 if (!get_obj_index_hook || (*get_obj_index_hook)(entry.index)) {
59 entry.prob2 = entry.prob1;
69 * @brief デバッグ時にアイテム生成情報をメッセージに出力する / Cheat -- describe a created object for the user
70 * @param player_ptr プレイヤーへの参照ポインタ
71 * @param o_ptr デバッグ出力するオブジェクトの構造体参照ポインタ
73 static void object_mention(PlayerType *player_ptr, ItemEntity *o_ptr)
75 object_aware(player_ptr, o_ptr);
76 o_ptr->mark_as_known();
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());
83 static int get_base_floor(FloorType *floor_ptr, BIT_FLAGS mode, std::optional<int> rq_mon_level)
85 if (any_bits(mode, AM_GREAT)) {
87 return *rq_mon_level + 10 + randint1(10);
90 return floor_ptr->object_level + 15;
93 if (any_bits(mode, AM_GOOD)) {
94 return floor_ptr->object_level + 10;
97 return floor_ptr->object_level;
100 static void set_ammo_quantity(ItemEntity *j_ptr)
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);
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 アイテムの生成成功可否
118 bool make_object(PlayerType *player_ptr, ItemEntity *j_ptr, BIT_FLAGS mode, std::optional<int> rq_mon_level)
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;
128 if (get_obj_index_hook) {
129 get_obj_index_prep();
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();
142 j_ptr->generate(bi_id);
145 ItemMagicApplier(player_ptr, j_ptr, floor_ptr->object_level, mode).execute();
146 set_ammo_quantity(j_ptr);
148 object_mention(player_ptr, j_ptr);
155 * @brief 生成階に応じた財宝オブジェクトの生成を行う。
156 * @param floor 現在フロアへの参照
157 * @param j_ptr 生成結果を収めたいアイテムの参照ポインタ
158 * @return 生成に成功したらTRUEを返す。
160 bool make_gold(PlayerType *player_ptr, ItemEntity *j_ptr)
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);
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));
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座標
190 void delete_all_items_from_floor(PlayerType *player_ptr, POSITION y, POSITION x)
193 auto *floor_ptr = player_ptr->current_floor_ptr;
194 if (!in_bounds(floor_ptr, y, x)) {
198 g_ptr = &floor_ptr->grid_array[y][x];
199 for (const auto this_o_idx : g_ptr->o_idx_list) {
201 o_ptr = &floor_ptr->o_list[this_o_idx];
206 g_ptr->o_idx_list.clear();
207 lite_spot(player_ptr, y, x);
211 * @brief 床上のアイテムの数を増やす /
212 * Increase the "number" of an item on the floor
213 * @param player_ptr プレイヤーへの参照ポインタ
214 * @param i_idx 増やしたいアイテムの所持スロット
215 * @param num 増やしたいアイテムの数
217 void floor_item_increase(PlayerType *player_ptr, INVENTORY_IDX i_idx, ITEM_NUMBER num)
219 auto *floor_ptr = player_ptr->current_floor_ptr;
221 auto *o_ptr = &floor_ptr->o_list[i_idx];
222 num += o_ptr->number;
225 } else if (num < 0) {
229 num -= o_ptr->number;
230 o_ptr->number += num;
231 static constexpr auto flags = {
232 SubWindowRedrawingFlag::FLOOR_ITEMS,
233 SubWindowRedrawingFlag::FOUND_ITEMS,
235 RedrawingFlagsUpdater::get_instance().set_flags(flags);
239 * @brief 床上の数の無くなったアイテムスロットを消去する /
240 * Optimize an item on the floor (destroy "empty" items)
241 * @param player_ptr プレイヤーへの参照ポインタ
242 * @param i_idx 消去したいアイテムの所持スロット
244 void floor_item_optimize(PlayerType *player_ptr, INVENTORY_IDX i_idx)
246 auto *o_ptr = &player_ptr->current_floor_ptr->o_list[i_idx];
247 if (!o_ptr->is_valid()) {
254 delete_object_idx(player_ptr, i_idx);
255 static constexpr auto flags = {
256 SubWindowRedrawingFlag::FLOOR_ITEMS,
257 SubWindowRedrawingFlag::FOUND_ITEMS,
259 RedrawingFlagsUpdater::get_instance().set_flags(flags);
263 * @brief オブジェクトを削除する /
264 * Delete a dungeon object
265 * @param player_ptr プレイヤーへの参照ポインタ
266 * @param o_idx 削除対象のオブジェクト構造体ポインタ
268 * Handle "stacks" of objects correctly.
270 void delete_object_idx(PlayerType *player_ptr, OBJECT_IDX o_idx)
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()) {
280 lite_spot(player_ptr, y, x);
285 static constexpr auto flags = {
286 SubWindowRedrawingFlag::FLOOR_ITEMS,
287 SubWindowRedrawingFlag::FOUND_ITEMS,
289 RedrawingFlagsUpdater::get_instance().set_flags(flags);
293 * @brief 床上、モンスター所持でスタックされたアイテムを削除しスタックを補完する / Excise a dungeon object from any stacks
294 * @param floo_ptr 現在フロアへの参照ポインタ
295 * @param o_idx 削除対象のオブジェクト構造体ポインタ
297 void excise_object_idx(FloorType *floor_ptr, OBJECT_IDX o_idx)
299 auto &list = get_o_idx_list_contains(floor_ptr, o_idx);
304 * @brief 指定したOBJECT_IDXを含むリスト(モンスター所持リスト or 床上スタックリスト)への参照を得る
305 * @param floo_ptr 現在フロアへの参照ポインタ
306 * @param o_idx 参照を得るリストに含まれるOBJECT_IDX
307 * @return o_idxを含む ObjectIndexList への参照
309 ObjectIndexList &get_o_idx_list_contains(FloorType *floor_ptr, OBJECT_IDX o_idx)
311 auto *o_ptr = &floor_ptr->o_list[o_idx];
313 if (o_ptr->is_held_by_monster()) {
314 return floor_ptr->m_list[o_ptr->held_m_idx].hold_o_idx_list;
316 return floor_ptr->grid_array[o_ptr->iy][o_ptr->ix].o_idx_list;
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を返す。
330 * The initial location is assumed to be "in_bounds(floor, )".\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
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
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
343 OBJECT_IDX drop_near(PlayerType *player_ptr, ItemEntity *j_ptr, PERCENTAGE chance, POSITION y, POSITION x)
348 OBJECT_IDX o_idx = 0;
354 bool plural = (j_ptr->number != 1);
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)) {
360 msg_format("%sは消えた。", item_name.data());
362 msg_format("The %s disappear%s.", item_name.data(), (plural ? "" : "s"));
365 msg_print(_("(破損)", "(breakage)"));
376 auto *floor_ptr = player_ptr->current_floor_ptr;
377 for (dy = -3; dy <= 3; dy++) {
378 for (dx = -3; dx <= 3; dx++) {
380 d = (dy * dy) + (dx * dx);
387 if (!in_bounds(floor_ptr, ty, tx)) {
390 if (!projectable(player_ptr, y, x, ty, tx)) {
394 g_ptr = &floor_ptr->grid_array[ty][tx];
395 if (!cave_drop_bold(floor_ptr, ty, tx)) {
400 for (const auto this_o_idx : g_ptr->o_idx_list) {
402 o_ptr = &floor_ptr->o_list[this_o_idx];
403 if (object_similar(o_ptr, j_ptr)) {
417 s = 1000 - (d + k * 5);
426 if ((++bn >= 2) && !one_in_(bn)) {
438 if (!flag && !j_ptr->is_fixed_or_random_artifact()) {
440 msg_format("%sは消えた。", item_name.data());
442 msg_format("The %s disappear%s.", item_name.data(), (plural ? "" : "s"));
445 msg_print(_("(床スペースがない)", "(no floor space)"));
451 for (i = 0; !flag && (i < 1000); i++) {
452 ty = rand_spread(by, 1);
453 tx = rand_spread(bx, 1);
455 if (!in_bounds(floor_ptr, ty, tx)) {
462 if (!cave_drop_bold(floor_ptr, by, bx)) {
469 auto &artifact = j_ptr->get_fixed_artifact();
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)) {
482 msg_format("%sは消えた。", item_name.data());
484 msg_format("The %s disappear%s.", item_name.data(), (plural ? "" : "s"));
488 msg_print(_("(床スペースがない)", "(no floor space)"));
492 if (j_ptr->is_fixed_artifact() && !j_ptr->is_known()) {
493 artifact.is_generated = false;
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)) {
520 g_ptr = &floor_ptr->grid_array[by][bx];
521 for (const auto this_o_idx : g_ptr->o_idx_list) {
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);
532 o_idx = o_pop(floor_ptr);
535 if (!done && !o_idx) {
537 msg_format("%sは消えた。", item_name.data());
539 msg_format("The %s disappear%s.", item_name.data(), (plural ? "" : "s"));
542 msg_print(_("(アイテムが多過ぎる)", "(too many objects)"));
545 if (j_ptr->is_fixed_artifact()) {
546 artifact.is_generated = false;
553 (&floor_ptr->o_list[o_idx])->copy_from(j_ptr);
554 j_ptr = &floor_ptr->o_list[o_idx];
557 j_ptr->held_m_idx = 0;
558 g_ptr->o_idx_list.add(floor_ptr, o_idx);
562 if (j_ptr->is_fixed_artifact() && world.character_dungeon) {
563 artifact.floor_id = player_ptr->floor_id;
566 note_spot(player_ptr, by, bx);
567 lite_spot(player_ptr, by, bx);
570 const auto is_located = player_ptr->is_located_at({ by, bx });
572 static constexpr auto flags = {
573 SubWindowRedrawingFlag::FLOOR_ITEMS,
574 SubWindowRedrawingFlag::FOUND_ITEMS,
576 RedrawingFlagsUpdater::get_instance().set_flags(flags);
579 if (chance && is_located) {
580 msg_print(_("何かが足下に転がってきた。", "You feel something roll beneath your feet."));
587 * @brief 床上の魔道具の残り残量メッセージを表示する
588 * @param floo_ptr 現在フロアへの参照ポインタ
589 * @param i_idx メッセージの対象にしたいアイテム所持スロット
591 void floor_item_charges(FloorType *floor_ptr, INVENTORY_IDX i_idx)
593 const auto &item = floor_ptr->o_list[i_idx];
594 if (!item.is_wand_staff() || !item.is_known()) {
599 if (item.pval <= 0) {
600 msg_print("この床上のアイテムは、もう魔力が残っていない。");
602 msg_format("この床上のアイテムは、あと %d 回分の魔力が残っている。", item.pval);
605 if (item.pval != 1) {
606 msg_format("There are %d charges remaining.", item.pval);
608 msg_format("There is %d charge remaining.", item.pval);
614 * @brief 床上のアイテムの残り数メッセージを表示する /
615 * Describe the charges on an item on the floor.
616 * @param floo_ptr 現在フロアへの参照ポインタ
617 * @param i_idx メッセージの対象にしたいアイテム所持スロット
619 void floor_item_describe(PlayerType *player_ptr, INVENTORY_IDX i_idx)
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);
624 if (o_ptr->number <= 0) {
625 msg_format("床上には、もう%sはない。", item_name.data());
627 msg_format("床上には、まだ %sがある。", item_name.data());
630 msg_format("You see %s.", item_name.data());
635 * @brief Choose an item and get auto-picker entry from it.
636 * @todo initial_i_idx をポインタではなく値に変え、戻り値をstd::pairに変える
638 ItemEntity *choose_object(PlayerType *player_ptr, short *initial_i_idx, concptr q, concptr s, BIT_FLAGS option, const ItemTester &item_tester)
641 *initial_i_idx = INVEN_NONE;
644 FixItemTesterSetter setter(item_tester);
646 if (!get_item(player_ptr, &i_idx, q, s, option, item_tester)) {
651 *initial_i_idx = i_idx;
654 if (i_idx == INVEN_FORCE) {
658 return ref_item(player_ptr, i_idx);