Right-clicking a bag or worn item puts a held item back first, as 1.12.1 clears the cursor before any use (#431)
- Right-clicking a bag or worn item puts a held item back first, as 1.12.1 clears the cursor before any use - The bag frame's end-to-end test: a right-click with a held item puts it back, then uses the slot Fixes #413 Fixes #421 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
12bba15345
commit
e26e234d03
@@ -272,14 +272,6 @@ pub(super) fn drain_container_uses(
|
||||
}
|
||||
}
|
||||
for (bag, slot) in script.take_container_uses() {
|
||||
// Any right-click, a sell or a deposit included, cancels an armed gift wrap: the
|
||||
// reference's use path clears the cursor first (`0x4fa198`).
|
||||
if let Some(w) = script.cancel_gift_wrap() {
|
||||
debug!(
|
||||
"ui_items: right-click cancels the armed gift wrap on bag {} slot {}",
|
||||
w.bag, w.slot
|
||||
);
|
||||
}
|
||||
let slot0 = u8::try_from(slot.saturating_sub(1)).ok();
|
||||
// With a merchant open, the click sells the whole stack (`CMSG_SELL_ITEM`, count 0).
|
||||
if let (true, Some(vendor)) = (merchant.is_open(), merchant.vendor) {
|
||||
|
||||
@@ -1143,20 +1143,19 @@ fn shipped_bag_frame_drives_end_to_end() {
|
||||
"a pickup alone queues no move"
|
||||
);
|
||||
|
||||
// Right-click while holding: the right arm has no cursor test (ContainerFrame.lua:583-599).
|
||||
// What the reference client does with a use while the cursor holds an item is untraced.
|
||||
// Right-click while holding: the right arm has no cursor test (ContainerFrame.lua:583-599),
|
||||
// and `UseContainerItem` clears the cursor before it reads the slot (`0x4fa198`).
|
||||
s.mouse_button(bx, by, "RightButton", true);
|
||||
s.mouse_button(bx, by, "RightButton", false);
|
||||
assert_eq!(
|
||||
s.take_container_uses(),
|
||||
vec![(0, 1)],
|
||||
"the reference's right arm uses the slot even with a full cursor"
|
||||
"the right arm uses the slot even with a full cursor"
|
||||
);
|
||||
assert!(
|
||||
s.cursor_item().is_some(),
|
||||
"…and leaves the held item where it was"
|
||||
s.cursor_item().is_none(),
|
||||
"…after putting the held item back"
|
||||
);
|
||||
s.run("ClearCursor()").unwrap();
|
||||
|
||||
s.mouse_button(bx, by, "RightButton", true);
|
||||
s.mouse_button(bx, by, "RightButton", false);
|
||||
|
||||
@@ -199,18 +199,6 @@ impl super::UiScript {
|
||||
cursor::queue_lock_changed(&mut model, bag, slot);
|
||||
}
|
||||
|
||||
/// Disarm a gift wrap, unlocking the paper and resetting the cursor, but keeping any held
|
||||
/// payload, which `ClearCursor` would drop. How the reference's right-click cancels a wrap is
|
||||
/// untraced.
|
||||
pub fn cancel_gift_wrap(&mut self) -> Option<PendingWrap> {
|
||||
let mut model = self.model_mut();
|
||||
let wrap = model.pending_wrap.take()?;
|
||||
cursor::queue_lock_changed(&mut model, wrap.bag, wrap.slot);
|
||||
model.ui_cursor = None;
|
||||
model.ui_cursor_dirty = true;
|
||||
Some(wrap)
|
||||
}
|
||||
|
||||
/// The armed gift wrap, if any; the app reads it to tell whether a right-click cancels one.
|
||||
pub fn gift_wrap_armed(&self) -> Option<PendingWrap> {
|
||||
self.model_ref().pending_wrap
|
||||
@@ -623,10 +611,11 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
"UseContainerItem",
|
||||
lua.create_function(|lua, (bag, slot, _rest): (i64, u32, mlua::MultiValue)| {
|
||||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||||
// Every right-click clears the cursor before it reads the slot (`0x4fa198`): a held
|
||||
// payload goes back, never placed, and an armed gift wrap is cancelled.
|
||||
cursor::clear_cursor(&mut model);
|
||||
if model.repair_mode {
|
||||
// The right-click clears the cursor first (`0x4fa198`), then repairs the item in
|
||||
// the slot (`0x4fa1a6`, `0x4fa1da`): a held payload goes back, never placed.
|
||||
cursor::clear_cursor(&mut model);
|
||||
// Then the item in the slot is repaired (`0x4fa1a6`, `0x4fa1da`).
|
||||
let occupied = model
|
||||
.containers
|
||||
.get(&bag)
|
||||
@@ -899,6 +888,39 @@ mod tests {
|
||||
assert!(s.take_container_uses().is_empty(), "drained");
|
||||
}
|
||||
|
||||
/// A right-click clears the cursor before it reads the slot (`0x4fa198`): a held item goes
|
||||
/// back to its slot, unlocked, and the clicked item is still used.
|
||||
#[test]
|
||||
fn right_click_puts_a_held_item_back_then_uses_the_clicked_one() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_container(0, Some(backpack()));
|
||||
s.run("PickupContainerItem(0, 1)").unwrap();
|
||||
assert!(s.cursor_item().is_some());
|
||||
|
||||
s.run("UseContainerItem(0, 4)").unwrap();
|
||||
assert!(s.cursor_payload().is_none(), "the held item went back");
|
||||
assert!(
|
||||
!s.eval::<bool>("local _, _, locked = GetContainerItemInfo(0, 1) return locked")
|
||||
.unwrap(),
|
||||
"its slot unlocks"
|
||||
);
|
||||
assert!(s.take_container_moves().is_empty(), "never placed");
|
||||
assert_eq!(s.take_container_uses(), vec![(0, 4)]);
|
||||
}
|
||||
|
||||
/// The same clear cancels an armed gift wrap (`0x495190` opens with `0x5edf10`), and the
|
||||
/// clicked item is used, not wrapped.
|
||||
#[test]
|
||||
fn right_click_cancels_an_armed_gift_wrap_then_uses_the_clicked_one() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_container(0, Some(backpack()));
|
||||
s.arm_gift_wrap(0, 1);
|
||||
s.run("UseContainerItem(0, 4)").unwrap();
|
||||
assert_eq!(s.gift_wrap_armed(), None);
|
||||
assert!(s.take_container_wraps().is_empty(), "nothing wrapped");
|
||||
assert_eq!(s.take_container_uses(), vec![(0, 4)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pickup_then_place_swaps_and_clears_cursor() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
|
||||
@@ -155,16 +155,16 @@ pub(super) fn auto_equip_cursor_item(model: &mut Model) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// `UseInventoryItem(id)`, the doll slot's right-click (`PaperDollFrame.lua:658-659`): the app
|
||||
/// sends `CMSG_USE_ITEM` with bag 255 and the 0-based slot, dropping an empty slot unsent. In
|
||||
/// repair mode the cursor clears first (`0x4c79a9`) and a worn item is repaired, locked or not
|
||||
/// (`0x4c79c4`): a held payload goes back, never placed.
|
||||
/// `UseInventoryItem(id)`, the doll slot's right-click (`PaperDollFrame.lua:658-659`): the cursor
|
||||
/// clears before the slot is read (`0x4c79a9`), so a held payload goes back, never placed; then
|
||||
/// the app sends `CMSG_USE_ITEM` with bag 255 and the 0-based slot, dropping an empty slot unsent,
|
||||
/// or in repair mode a worn item is repaired, locked or not (`0x4c79c4`).
|
||||
pub(super) fn use_inventory_item(model: &mut Model, id: u32) {
|
||||
super::clear_cursor(model);
|
||||
if !model.repair_mode {
|
||||
model.inventory_uses.push(id);
|
||||
return;
|
||||
}
|
||||
super::clear_cursor(model);
|
||||
if (1..=19).contains(&id)
|
||||
&& model
|
||||
.inv_slot("player", id as usize)
|
||||
@@ -602,6 +602,25 @@ mod tests {
|
||||
assert!(s.take_inventory_uses().is_empty(), "drained");
|
||||
}
|
||||
|
||||
/// A right-click clears the cursor before it reads the slot (`0x4c79a9`): a held item goes
|
||||
/// back, and the worn item is still used.
|
||||
#[test]
|
||||
fn right_click_puts_a_held_item_back_then_uses_the_worn_one() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_inventory_slots(doll_slots());
|
||||
s.run("PickupInventoryItem(11)").unwrap();
|
||||
assert!(s.cursor_item().is_some());
|
||||
|
||||
s.run("UseInventoryItem(19)").unwrap();
|
||||
assert!(s.cursor_payload().is_none(), "the ring went back");
|
||||
assert!(
|
||||
!s.eval::<bool>("return IsInventoryItemLocked(11)").unwrap(),
|
||||
"its slot unlocks"
|
||||
);
|
||||
assert!(s.take_container_moves().is_empty(), "never placed");
|
||||
assert_eq!(s.take_inventory_uses(), vec![19]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repair_mode_queues_an_equipped_slot_without_picking_it_up() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user