ClearCursor signals CURSOR_UPDATE on every call, an empty cursor included, as 1.12.1's shared tail does (#432)
Fixes #425 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
e26e234d03
commit
24a8fb274c
@@ -223,8 +223,8 @@ pub(super) fn item_link_name(link: Option<&str>) -> String {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// `ClearCursor()`: drops any payload and frees an item's source slot; an empty cursor fires
|
||||
/// nothing.
|
||||
/// `ClearCursor()` (`0x495190`): drops any payload and frees an item's source slot, then signals
|
||||
/// `CURSOR_UPDATE` from its shared tail (`0x49529b`) on every call, an empty cursor included.
|
||||
pub(crate) fn clear_cursor(model: &mut Model) {
|
||||
// Any clear first cancels an armed gift wrap, whatever its parameters (`0x495190` opens with
|
||||
// `0x5edf10`): the wrapping paper unlocks and the cursor mode returns to the base.
|
||||
@@ -233,25 +233,12 @@ pub(crate) fn clear_cursor(model: &mut Model) {
|
||||
model.ui_cursor = None;
|
||||
model.ui_cursor_dirty = true;
|
||||
}
|
||||
match model.cursor.take() {
|
||||
Some(CursorPayload::Item(item)) => {
|
||||
queue_cursor_update(model);
|
||||
queue_lock_changed(model, item.bag, item.slot);
|
||||
}
|
||||
Some(
|
||||
CursorPayload::Spell(_)
|
||||
| CursorPayload::Action(_)
|
||||
| CursorPayload::Macro(_)
|
||||
| CursorPayload::PetAction(_)
|
||||
| CursorPayload::StablePet(_)
|
||||
// Coins: the purse was never debited, so nothing goes back.
|
||||
| CursorPayload::Money(_)
|
||||
// A vendor row clears with no packet (`0x49525f`).
|
||||
| CursorPayload::Merchant(_),
|
||||
) => {
|
||||
queue_cursor_update(model);
|
||||
}
|
||||
None => {}
|
||||
// Coins go back to nothing, as the purse was never debited; a vendor row clears with no
|
||||
// packet (`0x49525f`).
|
||||
let taken = model.cursor.take();
|
||||
queue_cursor_update(model);
|
||||
if let Some(CursorPayload::Item(item)) = taken {
|
||||
queue_lock_changed(model, item.bag, item.slot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -742,6 +729,20 @@ mod tests {
|
||||
assert!(s.cursor_payload().is_none());
|
||||
}
|
||||
|
||||
/// The clear's shared tail (`0x49529b`) signals on every call: with nothing held, one
|
||||
/// `CURSOR_UPDATE` and no grid event.
|
||||
#[test]
|
||||
fn clear_cursor_with_nothing_held_still_signals() {
|
||||
let s = UiScript::new().unwrap();
|
||||
let before = s.model_ref().pending_events.len();
|
||||
s.run("ClearCursor()").unwrap();
|
||||
let fired: Vec<String> = s.model_ref().pending_events[before..]
|
||||
.iter()
|
||||
.map(|(e, _)| e.clone())
|
||||
.collect();
|
||||
assert_eq!(fired, vec!["CURSOR_UPDATE"]);
|
||||
}
|
||||
|
||||
/// Clearing a spell on terrain is the deviation documented on `world_drop_click`.
|
||||
#[test]
|
||||
fn world_drop_terrain_and_nothing_both_clear_a_spell_payload() {
|
||||
|
||||
@@ -142,15 +142,18 @@ pub(super) fn put_item_in_backpack(model: &mut Model) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// `PickupBagFromSlot(inventorySlot)` (`0x4c7b00`): clears the cursor before it looks at the slot,
|
||||
/// where the reference clears only a held item (`0x4c7b70`, `0x4c7b79`); takes only a container
|
||||
/// `PickupBagFromSlot(inventorySlot)` (`0x4c7b00`): clears a held payload before it looks at the
|
||||
/// slot, where the reference clears only a held item (`0x4c7b70`, `0x4c7b79`); with nothing held it
|
||||
/// skips the clear and its `CURSOR_UPDATE`, as the reference does. Takes only a container
|
||||
/// (`0x4c7bb3`), and never places or swaps. The client has no empty-bag rule: the server refuses
|
||||
/// with `EQUIP_ERR_CAN_ONLY_DO_WITH_EMPTY_BAGS`.
|
||||
pub(super) fn pickup_bag_from_slot(model: &mut Model, live: u32) {
|
||||
if !accepts(live) {
|
||||
return;
|
||||
}
|
||||
clear_cursor(model);
|
||||
if model.cursor.is_some() {
|
||||
clear_cursor(model);
|
||||
}
|
||||
let picked = usize::try_from(live)
|
||||
.ok()
|
||||
.and_then(|slot| model.inv_slot("player", slot))
|
||||
@@ -227,6 +230,17 @@ mod tests {
|
||||
InventorySlots, UiScript,
|
||||
};
|
||||
|
||||
/// With nothing held the reference skips the clear (`0x4c7b6a`-`0x4c7b70`), so a drag from an
|
||||
/// empty bag slot signals nothing.
|
||||
#[test]
|
||||
fn an_empty_cursor_skips_the_clear_and_its_signal() {
|
||||
let s = UiScript::new().unwrap();
|
||||
let before = s.model_ref().pending_events.len();
|
||||
s.run("PickupBagFromSlot(20)").unwrap();
|
||||
assert!(s.cursor_payload().is_none());
|
||||
assert_eq!(s.model_ref().pending_events.len(), before);
|
||||
}
|
||||
|
||||
/// An item that is a bag: `equip_slots` is `find_equip_slot(INVTYPE_BAG)`.
|
||||
fn bag_slot_view(item_id: u32) -> InvSlotView {
|
||||
InvSlotView {
|
||||
|
||||
@@ -1114,8 +1114,9 @@ mod tests {
|
||||
(n("updates"), n("shows"), n("hides"))
|
||||
}
|
||||
|
||||
/// The grab goes through the cursor setter (`0x4950f0`): `CURSOR_UPDATE` at `0x495159`, and
|
||||
/// no `ACTIONBAR_SHOWGRID` since mode 5 is not mode 7.
|
||||
/// The grab goes through the cursor setter (`0x4950f0`): its `ClearCursor(1,1)` (`0x495112`)
|
||||
/// signals `CURSOR_UPDATE` from the clear's tail, the set signals it again (`0x495159`), and no
|
||||
/// `ACTIONBAR_SHOWGRID` since mode 5 is not mode 7.
|
||||
#[test]
|
||||
fn a_vendor_grab_fires_cursor_update_but_not_the_bar_grid() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
@@ -1124,12 +1125,12 @@ mod tests {
|
||||
s.run("PickupMerchantItem(1)").unwrap();
|
||||
assert_eq!(
|
||||
cursor_event_counts(&mut s),
|
||||
(1, 0, 0),
|
||||
(2, 0, 0),
|
||||
"(CURSOR_UPDATE, ACTIONBAR_SHOWGRID, ACTIONBAR_HIDEGRID) after a vendor grab"
|
||||
);
|
||||
// The toggle-off is a plain clear: one more CURSOR_UPDATE, no HIDEGRID.
|
||||
s.run("PickupMerchantItem(1)").unwrap();
|
||||
assert_eq!(cursor_event_counts(&mut s), (2, 0, 0));
|
||||
assert_eq!(cursor_event_counts(&mut s), (3, 0, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user