From: <gi...@cr...> - 2025-07-18 15:25:16
|
via b117d8a9e7f2e9a6408b85e652c84bb349c154c1 (commit) via 13e22a4e1b9ce4fa623913a91c99fb9dab7be964 (commit) via 8fab406d756587161dac8f83a8ae68798a78ae00 (commit) via 1ca2ae5227a956e7fc87e05e2f9c0de3d36e6e48 (commit) via a83e6f9d55fefd55eab61655bb351e9a8ee05d4e (commit) from 5bb89448b4a79aae45e433372bfde864c6bd2d45 (commit) ----------------------------------------------------------------------- commit b117d8a9e7f2e9a6408b85e652c84bb349c154c1 Author: DracoOmega <dra...@gm...> Date: Fri Jul 18 12:50:16 2025 -0230 More thoroughly prevent some crashes with inventory menus 89b82f3 was apparently insufficient (even if 0 was the most common case by far). I'm not 100% sure where non-zero non-letter slots are coming from, so I don't entirely like papering over it like this, but it's possible they're harmless. (Or at least, we can wait and see if any other problem happens besides this sort issue.) commit 13e22a4e1b9ce4fa623913a91c99fb9dab7be964 Author: DracoOmega <dra...@gm...> Date: Fri Jul 18 12:44:23 2025 -0230 Don't indefinitely wait to heal off poison in nonliving forms (Ogregutan) Transforming into something with infinite rPois while already poisoned merely suspends the poison, and it cannot be rested off in those forms. So don't waste time trying. commit 8fab406d756587161dac8f83a8ae68798a78ae00 Author: DracoOmega <dra...@gm...> Date: Fri Jul 18 12:42:13 2025 -0230 Don't automap a consumable to an occupied letter (Moanerette) Autoassignments set by consumable_shortcut neglected to consider that a player could have manually remapped an item to a reserved letter, and would give that out to the new item anyway. Now, it should fall back on another slot in this case, as if no default was set. commit 1ca2ae5227a956e7fc87e05e2f9c0de3d36e6e48 Author: DracoOmega <dra...@gm...> Date: Fri Jul 18 12:39:51 2025 -0230 Change how the drop/'i' menus work (various) In response to repeated struggles players expressed with the current setup (which can map multiple items to the same letter on 'd'/'i'), this changes these menus over to a multi-paged model. Each of the 4 inventory sub-types (gear, potions, scrolls, evocables) now display on a seperate page, which is accessed by left/right arrows. This means that each letter uniquely maps to a single item on each of these pages. Selections on previous pages are remembered (so the player can still multi-drop items of multiple categories). Some downsides is that it's no longer possible to see your complete inventory in a single list, and using ',' to drop all useless items no longer works (you'd need to cycle through each of the 4 pages manually to drop useless items of each type). But after discussing this with several players, I think this is the lesser of two evils; hopefully this will feel more intuitive for players to interact with. The implementation is a little rough, but a more elegant approach would seem to require considerably more menu code rewriting, and hopefully this can suffice. commit a83e6f9d55fefd55eab61655bb351e9a8ee05d4e Author: DracoOmega <dra...@gm...> Date: Fri Jul 18 08:47:19 2025 -0230 Fix =g not letting you remap throwables (Undo) ----------------------------------------------------------------------- Summary of changes: crawl-ref/source/adjust.cc | 5 +- crawl-ref/source/invent.cc | 219 +++++++++++++++++++++----------- crawl-ref/source/invent.h | 19 ++- crawl-ref/source/items.cc | 20 ++- crawl-ref/source/menu.h | 1 + crawl-ref/source/object-selector-type.h | 1 + crawl-ref/source/travel.cc | 8 ++ 7 files changed, 194 insertions(+), 79 deletions(-) diff --git a/crawl-ref/source/adjust.cc b/crawl-ref/source/adjust.cc index 8a76f84e96..6b748ec52e 100644 --- a/crawl-ref/source/adjust.cc +++ b/crawl-ref/source/adjust.cc @@ -68,9 +68,10 @@ void adjust_item(operation_types oper, item_def* to_adjust) if (!to_adjust) { + int sel = oper == OPER_EQUIP ? OSEL_GEAR : default_osel(oper); + const int from_slot = prompt_invent_item("Adjust which item?", - menu_type::invlist, default_osel(oper), - oper); + menu_type::invlist, sel, oper); if (prompt_failed(from_slot)) return; diff --git a/crawl-ref/source/invent.cc b/crawl-ref/source/invent.cc index 078c9b587e..8a7085b07e 100644 --- a/crawl-ref/source/invent.cc +++ b/crawl-ref/source/invent.cc @@ -310,7 +310,8 @@ InvMenu::InvMenu(int mflags) : Menu((mflags & MF_NOSELECT) ? mflags : (mflags | MF_ARROWS_SELECT), "inventory"), type(menu_type::invlist), pre_select(nullptr), - title_annotate(nullptr), _mode_special_drop(false) + title_annotate(nullptr), cur_osel(0), + _mode_special_drop(false) { menu_action = ACT_EXAMINE; // default if (!Options.single_column_item_menus) @@ -350,6 +351,20 @@ string slot_description() void InvMenu::set_title(const string &s) { + if ((flags & MF_PAGED_INVENTORY)) + { + string str; + switch (cur_osel) + { + case 0: str = "Gear: " + slot_description(); break; + case 1: str = "Potions: "; break; + case 2: str = "Scrolls: "; break; + case 3: str = "Evocable Items: "; break; + } + set_title(new InvTitle(this, str, title_annotate)); + return; + } + set_title(new InvTitle(this, s.empty() ? "Inventory: " + slot_description() : s, title_annotate)); @@ -385,59 +400,115 @@ int InvMenu::pre_process(int key) return key; } -bool InvMenu::process_key(int key) +void InvMenu::cycle_page(int dir) { - // Allow tab to move between item categories (since using item category - // hotkeys in the drop menu doesn't really work for this purpose as it will - // select many things at once instead). - if (key == CK_RIGHT || key == CK_LEFT) + const static int modes[] = { - // Find the first category below our current cursor position. - int start = last_hovered >= 0 ? last_hovered : 0; - int target = -1; - if (key == CK_RIGHT) - { - for (size_t i = start; i < items.size(); ++i) - { - if (items[i]->level == MEL_SUBTITLE) - { - target = i+1; - break; - } - } - } - // Find the first category above our current cursor position. - else if (key == CK_LEFT) + OSEL_GEAR, + OBJ_POTIONS, + OBJ_SCROLLS, + OSEL_EVOKABLE, + }; + + // Determine new page + const int old_osel = cur_osel; + cur_osel += dir; + if (cur_osel < 0) + cur_osel = ARRAYSZ(modes) - 1; + if (cur_osel >= static_cast<int>(ARRAYSZ(modes))) + cur_osel = 0; + + // Save selected items from our current page + get_selected(&sel); + offscreen_sel[old_osel] = get_selitems(); + deselect_all(); + + // Clear old entries and load new ones based on the new page + clear(); + load_inv_items(modes[cur_osel]); + update_more(); + reset(); + update_menu(true); + + // If this page is empty, go to the next one. + // XXX: (Theoretically, this could cause an infinite loop, but other code + // should already prevent opening a menu when you have no items.) + if (items.empty()) + cycle_page(dir); + + // If the player has selected items on this new page previously, restore + // those selections. + for (SelItem& sel_item : offscreen_sel[cur_osel]) + { + for (size_t i = 0; i < items.size(); ++i) { - for (int i = start - 2; i >= 0; --i) + InvEntry *inv = dynamic_cast<InvEntry*>(items[i]); + if (!inv) + continue; + + if (inv->item->link == sel_item.item->link) { - if (items[i]->level == MEL_SUBTITLE) - { - target = i+1; - break; - } + select_index(i, sel_item.quantity); + break; } } + } + get_selected(&sel); + update_title(); +} - // Stop if we didn't find any. - if (target < 0) - return true; - - // Otherwise, hover the first item of this category and try to display - // the entire category on screen (or as much as we can, anyway.) - auto snap_range = hotkey_range(items[target]->hotkeys.back()); - snap_in_page(snap_range.second); - set_hovered(snap_range.first); -#ifdef USE_TILE_WEB - webtiles_update_scroll_pos(true); -#endif - +bool InvMenu::process_key(int key) +{ + if (key == CK_LEFT && (flags & MF_PAGED_INVENTORY)) + { + cycle_page(-1); + return true; + } + else if (key == CK_RIGHT && (flags & MF_PAGED_INVENTORY)) + { + cycle_page(1); return true; } return Menu::process_key(key); } +bool InvMenu::process_command(command_type cmd) +{ + if (cmd == CMD_MENU_ACCEPT_SELECTION && (flags & MF_PAGED_INVENTORY)) + { + get_selected(&sel); + return false; + } + else if (cmd == CMD_MENU_EXIT && (flags & MF_PAGED_INVENTORY)) + { + // Must clear offscreen selection or exiting the menu will still act + // upon those items. + for (size_t i = 0; i < ARRAYSZ(offscreen_sel); ++i) + offscreen_sel[i].clear(); + sel.clear(); + lastch = CK_ESCAPE; // XX is this correct? + return is_set(MF_UNCANCEL) && !crawl_state.seen_hups; + } + + return Menu::process_command(cmd); +} + +string InvMenu::get_select_count_string(int) const +{ + if (flags & MF_PAGED_INVENTORY) + { + vector<SelItem> all_sel = get_selitems(true); + if (all_sel.empty()) + return ""; + + return make_stringf(" %d item%s", (int)all_sel.size(), + all_sel.size() > 1 ? "s" : ""); + } + + return Menu::get_select_count_string(0); +} + static bool _item_is_permadrop_candidate(const item_def &item) { // Known, non-artefact items of the types you see on the '\' menu proper. @@ -783,7 +854,7 @@ int sort_item_qty(const InvEntry *a) } int sort_item_slot(const InvEntry *a) { - return a->item->slot > 0 ? letter_to_index(a->item->slot) : 0; + return isalpha(a->item->slot) ? letter_to_index(a->item->slot) : 0; } bool sort_item_identified(const InvEntry *a) @@ -1060,7 +1131,7 @@ void InvMenu::do_preselect(InvEntry *ie) } } -vector<SelItem> InvMenu::get_selitems() const +vector<SelItem> InvMenu::get_selitems(bool include_offscreen) const { vector<SelItem> selected_items; for (MenuEntry *me : sel) @@ -1069,6 +1140,20 @@ vector<SelItem> InvMenu::get_selitems() const selected_items.emplace_back(inv->item->link, inv->selected_qty, inv->item, inv->has_star()); } + + if (include_offscreen) + { + for (int i = 0; i < static_cast<int>(ARRAYSZ(offscreen_sel)); ++i) + { + if (cur_osel == i) + continue; + + selected_items.insert(selected_items.end(), + offscreen_sel[i].begin(), + offscreen_sel[i].end()); + } + } + return selected_items; } @@ -1207,7 +1292,7 @@ vector<SelItem> select_items(const vector<const item_def*> &items, new_flags |= menu.get_flags() & MF_USE_TWO_COLUMNS; menu.set_flags(new_flags); menu.show(); - selected = menu.get_selitems(); + selected = menu.get_selitems(true); } return selected; } @@ -1313,6 +1398,10 @@ bool item_is_selected(const item_def &i, int selector) case OSEL_JEWELLERY_OR_TALISMAN: return i.base_type == OBJ_JEWELLERY || i.base_type == OBJ_TALISMANS; + case OSEL_GEAR: + return item_is_selected(i, OSEL_EQUIPABLE) + || i.base_type == OBJ_MISSILES; + default: return false; } @@ -1392,18 +1481,23 @@ static int _invent_select(const char *title = nullptr, if (title && menu.item_count()) menu.set_title(title); + // Cycle through all pages to properly apply pre-selections immediately. + for (int i = 0; i < 4; ++i) + menu.cycle_page(1); + menu.show(true); if (items) - *items = menu.get_selitems(); + *items = menu.get_selitems(true); return menu.getkey(); } void display_inventory() { - InvMenu menu(MF_SINGLESELECT | MF_ALLOW_FORMATTING | MF_SECONDARY_SCROLL); - menu.load_inv_items(OSEL_ANY, -1); + InvMenu menu(MF_SINGLESELECT | MF_ALLOW_FORMATTING | MF_SECONDARY_SCROLL + | MF_PAGED_INVENTORY); + menu.load_inv_items(OSEL_GEAR, -1); menu.set_type(menu_type::describe); menu.show(true); @@ -1414,29 +1508,6 @@ void display_inventory() } } -static string _drop_selitem_text(const vector<MenuEntry*> *s) -{ - bool extraturns = false; - - if (s->empty()) - return ""; - - for (MenuEntry *entry : *s) - { - const item_def *item = static_cast<item_def *>(entry->data); - if (item_is_equipped(*item)) - { - extraturns = true; - break; - } - } - - return make_stringf(" (%u%s turn%s)", - (unsigned int)s->size(), - extraturns? "+" : "", - s->size() > 1? "s" : ""); -} - static string _drop_prompt(bool as_menu_title, bool menu_autopickup_mode) { string prompt_base; @@ -1444,7 +1515,7 @@ static string _drop_prompt(bool as_menu_title, bool menu_autopickup_mode) if (as_menu_title && menu_autopickup_mode) prompt_base = "Drop (and turn off autopickup for) what? "; else if (as_menu_title) - prompt_base = "Drop what? "; + prompt_base = "Drop what? (Left/Right to switch category) "; else prompt_base = "Drop what? "; return prompt_base + slot_description() + " (_ for help)"; @@ -1470,13 +1541,13 @@ vector<SelItem> prompt_drop_items(const vector<SelItem> &preselected_items) // multi-select some items to drop _invent_select("", menu_type::drop, - OSEL_ANY, + OSEL_GEAR, -1, - MF_MULTISELECT | MF_ALLOW_FILTER | MF_SELECT_QTY, + MF_MULTISELECT | MF_ALLOW_FILTER | MF_SELECT_QTY | MF_PAGED_INVENTORY, _drop_menu_titlefn, &items, &Options.drop_filter, - _drop_selitem_text, + nullptr, &preselected_items); return items; diff --git a/crawl-ref/source/invent.h b/crawl-ref/source/invent.h index 541e3423a3..b2d2ba4ec2 100644 --- a/crawl-ref/source/invent.h +++ b/crawl-ref/source/invent.h @@ -54,6 +54,14 @@ struct SelItem : slot(s), quantity(q), item(it), has_star(do_star) { } + + bool operator== (const SelItem &o) const + { + return slot == o.slot + && quantity == o.quantity + && item == o.item + && has_star == o.has_star; + } }; typedef string (*invtitle_annotator)(const Menu *m, const string &oldtitle); @@ -158,7 +166,7 @@ public: void load_inv_items(int item_selector = OSEL_ANY, int excluded_slot = -1, function<MenuEntry* (MenuEntry*)> procfn = nullptr); - vector<SelItem> get_selitems() const; + vector<SelItem> get_selitems(bool include_offscreen = false) const; const menu_sort_condition *find_menu_sort_condition() const; void sort_menu(vector<InvEntry*> &items, const menu_sort_condition *cond); @@ -166,12 +174,16 @@ public: // Drop menu only: if true, dropped items are removed from autopickup. bool mode_special_drop() const; + void cycle_page(int dir); + protected: void do_preselect(InvEntry *ie); void select_item_index(int idx, int qty) override; bool examine_index(int i) override; int pre_process(int key) override; bool process_key(int key) override; + bool process_command(command_type cmd) override; + string get_select_count_string(int count) const override; virtual bool skip_process_command(int keyin) override; virtual bool is_selectable(int index) const override; virtual string help_key() const override; @@ -183,6 +195,11 @@ protected: invtitle_annotator title_annotate; string temp_title; + // Current tab in MF_PAGED_INVENTORY menus. + // Has no effect if that flag is not set. + int cur_osel; + vector<SelItem> offscreen_sel[4]; + private: bool _mode_special_drop; }; diff --git a/crawl-ref/source/items.cc b/crawl-ref/source/items.cc index 84e2f42df0..f0abd8f3c0 100644 --- a/crawl-ref/source/items.cc +++ b/crawl-ref/source/items.cc @@ -2152,6 +2152,7 @@ static int _letter_for_consumable(item_def& item, bool first_pickup) { // If this is an identified item, first check the consumable_slot option to // see any default assignment and use this. + const operation_types oper = item_to_oper(&item); if (item.is_identified()) { char key = 0; @@ -2174,7 +2175,23 @@ static int _letter_for_consumable(item_def& item, bool first_pickup) } if (key > 0 && key != ' ') - return key; + { + // Verify that the player hasn't manually remapped something to this + // key already. (If they have, just jump to the next step.) + bool conflict = false; + for (int i = MAX_GEAR; i < ENDOFPACK; ++i) + { + if (you.inv[i].defined() && item_to_oper(&you.inv[i]) == oper + && you.inv[i].slot == key) + { + conflict = true; + break; + } + } + + if (!conflict) + return key; + } } if (!first_pickup) @@ -2230,7 +2247,6 @@ static int _letter_for_consumable(item_def& item, bool first_pickup) // Check which slots are strictly used already. bool used_slots[52] = {false}; - operation_types oper = item_to_oper(&item); for (int i = MAX_GEAR; i < ENDOFPACK; ++i) { if (you.inv[i].defined() && item_to_oper(&you.inv[i]) == oper diff --git a/crawl-ref/source/menu.h b/crawl-ref/source/menu.h index 74980a1a96..07e65d6585 100644 --- a/crawl-ref/source/menu.h +++ b/crawl-ref/source/menu.h @@ -280,6 +280,7 @@ enum MenuFlag MF_ARROWS_SELECT = 0x40000, ///< arrow keys select, rather than scroll MF_SHOW_EMPTY = 0x80000, ///< don't auto-exit empty menus MF_GRID_LAYOUT = 0x100000, ///< use a grid-style layout, filling by width first + MF_PAGED_INVENTORY = 0x200000, ///< uses left/right to switch between different inventory categories }; class UIMenu; diff --git a/crawl-ref/source/object-selector-type.h b/crawl-ref/source/object-selector-type.h index 53a2d329d3..0519d1d8fa 100644 --- a/crawl-ref/source/object-selector-type.h +++ b/crawl-ref/source/object-selector-type.h @@ -25,4 +25,5 @@ enum object_selector OSEL_ARTEFACT_WEAPON = -21, OSEL_MARKED_ITEMS = -22, OSEL_JEWELLERY_OR_TALISMAN = -23, + OSEL_GEAR = -24, // Equippable items + throwables }; diff --git a/crawl-ref/source/travel.cc b/crawl-ref/source/travel.cc index 779837dd47..4847b59a18 100644 --- a/crawl-ref/source/travel.cc +++ b/crawl-ref/source/travel.cc @@ -1121,6 +1121,14 @@ command_type travel() continue; } + // Poison doesn't wear off while in this state, so don't try waiting + // for it. + if (type == DUR_POISONING + && (you.is_nonliving() || you.is_lifeless_undead())) + { + continue; + } + // Save the player's position, so we can catch the degenerate case // where this results in us waiting indefinitely. you.props[AUTO_REST_STATUS_POS] = you.pos(); -- Dungeon Crawl Stone Soup |