Repair mode ends on a right mouse-down in the world and when a targeted spell arms, as in 1.12.1 (#435)
- Casting a targeted spell ends repair mode, as 1.12.1's targeting arm writes its cursor over Repair - A right mouse-down on the world ends repair mode, as 1.12.1's WorldFrame hook resets the cursor to Point Fixes #420 Fixes #422 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
cc14233a2e
commit
2376fff914
@@ -20,7 +20,7 @@ use crate::net::{ClientCommand, Embodied, NetCommands, TeleportMessage, Worldpor
|
||||
use crate::ui_script::InspectMode;
|
||||
use benilla_assets::coords::wow_to_bevy;
|
||||
use benilla_assets::AssetSet;
|
||||
use benilla_world::interact::{WorldClick, WorldRightClick, WorldRightPress};
|
||||
use benilla_world::interact::{WorldClick, WorldRightClick};
|
||||
use benilla_world::schedule::WorldStage;
|
||||
|
||||
mod arc;
|
||||
@@ -327,6 +327,18 @@ impl Plugin for PlayerPlugin {
|
||||
.before(follow::steer_follow)
|
||||
.in_set(crate::char_select::InWorldGated),
|
||||
)
|
||||
// The world's right mouse-down, off that latch and ahead of `control`, the button's
|
||||
// binding, as `0x483c40` runs its hook before `ExecuteBinding`; off in capture mode, like
|
||||
// `control`.
|
||||
.add_systems(
|
||||
Update,
|
||||
camera::send_world_right_press
|
||||
.in_set(WorldStage::Input)
|
||||
.after(camera::latch_world_mouse)
|
||||
.before(control)
|
||||
.run_if(not(resource_exists::<crate::run_mode::CaptureMode>))
|
||||
.in_set(crate::char_select::InWorldGated),
|
||||
)
|
||||
// `/follow` steers before `control`, so the player's own turn input lands after it and
|
||||
// wins over the follow's steering.
|
||||
.add_systems(
|
||||
|
||||
@@ -700,6 +700,18 @@ pub(super) fn latch_world_mouse(
|
||||
rig.world_mouse.update(&buttons, world_press);
|
||||
}
|
||||
|
||||
/// The right button's down edge in the world, before the press is judged a click or a drag:
|
||||
/// `CGWorldFrame::OnMouseDown` (`0x483c40`) runs its hook `0x492c20` ahead of the button's binding,
|
||||
/// so ground targeting's cancel and the repair-mode reset read it before the look session.
|
||||
pub(super) fn send_world_right_press(
|
||||
rig: Res<CameraControl>,
|
||||
mut world_right_press: MessageWriter<WorldRightPress>,
|
||||
) {
|
||||
if rig.world_mouse.down(LookButton::Right) {
|
||||
world_right_press.write(WorldRightPress);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
// `pub(crate)` for the scripted camera park's query; `FlyCam::park` is its one lever.
|
||||
pub(crate) struct FlyCam {
|
||||
@@ -747,7 +759,6 @@ pub(super) fn run_look_session(
|
||||
click_consumed: bool,
|
||||
world_click: &mut MessageWriter<WorldClick>,
|
||||
world_right_click: &mut MessageWriter<WorldRightClick>,
|
||||
world_right_press: &mut MessageWriter<WorldRightPress>,
|
||||
left_click: &mut Option<PressGesture>,
|
||||
right_click: &mut Option<PressGesture>,
|
||||
look_cfg: LookConfig,
|
||||
@@ -757,11 +768,6 @@ pub(super) fn run_look_session(
|
||||
// Seconds on the app clock, for the press predicate's two time gates.
|
||||
now: f32,
|
||||
) {
|
||||
// The right button's down edge, before the press is judged a click or a drag: the reference's
|
||||
// WorldFrame OnMouseDown fires at the press either way. Ground targeting's cancel reads it.
|
||||
if rig.world_mouse.down(LookButton::Right) {
|
||||
world_right_press.write(WorldRightPress);
|
||||
}
|
||||
// A chord is a both-button run, never a select: the reference kills the pending click and arms
|
||||
// none while another primary's binding is held (`0x514ac1`, `0x51481a`).
|
||||
if rig.world_mouse.both() {
|
||||
@@ -1855,6 +1861,67 @@ mod tests {
|
||||
assert!(!rig.world_mouse.held(LookButton::Left));
|
||||
}
|
||||
|
||||
/// A right mouse-down in the world ends repair mode (`0x492c68`), from the latch through the
|
||||
/// press edge to the reset; the same press on a UI frame, or one that drops a held payload
|
||||
/// (`0x492b50`), leaves it standing.
|
||||
#[test]
|
||||
fn a_right_press_in_the_world_ends_repair_mode_and_one_on_the_ui_does_not() {
|
||||
use benilla_ui::script::{MerchantState, UiScript};
|
||||
use bevy::ecs::system::RunSystemOnce;
|
||||
use bevy::math::DVec2;
|
||||
|
||||
// One right press with repair mode armed; returns whether repair mode survived it.
|
||||
let press = |over_ui: bool, payload_held: bool| {
|
||||
let mut world = World::new();
|
||||
let mut buttons = ButtonInput::<MouseButton>::default();
|
||||
buttons.press(MouseButton::Right);
|
||||
world.insert_resource(buttons);
|
||||
world.insert_resource(crate::ui_script::PointerOverUi(over_ui));
|
||||
world.insert_resource(crate::ui_script::CursorPayloadHeld(payload_held));
|
||||
world.init_resource::<CameraControl>();
|
||||
world.init_resource::<Messages<WorldRightPress>>();
|
||||
world.spawn((
|
||||
Camera::default(),
|
||||
FlyCam {
|
||||
yaw: 0.0,
|
||||
pitch: 0.0,
|
||||
speed: 0.0,
|
||||
},
|
||||
));
|
||||
let mut window = Window::default();
|
||||
window.set_physical_cursor_position(Some(DVec2::new(100.0, 100.0)));
|
||||
world.spawn((window, PrimaryWindow));
|
||||
let mut script = UiScript::new().unwrap();
|
||||
script.set_merchant(Some(MerchantState {
|
||||
can_repair: true,
|
||||
..MerchantState::default()
|
||||
}));
|
||||
script.run("ShowRepairCursor()").unwrap();
|
||||
assert!(script.repair_mode(), "the repair vendor arms repair mode");
|
||||
world.insert_non_send_resource(script);
|
||||
|
||||
world.run_system_once(latch_world_mouse).unwrap();
|
||||
world.run_system_once(send_world_right_press).unwrap();
|
||||
world
|
||||
.run_system_once(crate::ui_merchant::end_repair_mode_on_right_press)
|
||||
.unwrap();
|
||||
world.non_send_resource::<UiScript>().repair_mode()
|
||||
};
|
||||
|
||||
assert!(
|
||||
!press(false, false),
|
||||
"a right press in the world ends repair mode"
|
||||
);
|
||||
assert!(
|
||||
press(true, false),
|
||||
"a right press on a UI frame never reaches the world's hook"
|
||||
);
|
||||
assert!(
|
||||
press(false, true),
|
||||
"a press that drops a held payload is consumed before the hook"
|
||||
);
|
||||
}
|
||||
|
||||
/// The classifier (`0x510960`) reads the camera's command word, not the character's velocity,
|
||||
/// and its priority order decides when several states hold.
|
||||
#[test]
|
||||
|
||||
@@ -74,11 +74,7 @@ pub(super) fn control(
|
||||
mut body: BodyQuery,
|
||||
window: Single<(&mut Window, &mut CursorOptions), With<PrimaryWindow>>,
|
||||
// A press starts a look and arms a click test, which the release settles on time and travel.
|
||||
mut world_clicks: (
|
||||
MessageWriter<WorldClick>,
|
||||
MessageWriter<WorldRightClick>,
|
||||
MessageWriter<WorldRightPress>,
|
||||
),
|
||||
mut world_clicks: (MessageWriter<WorldClick>, MessageWriter<WorldRightClick>),
|
||||
mut click_test: (
|
||||
Local<Option<camera::PressGesture>>,
|
||||
Local<Option<camera::PressGesture>>,
|
||||
@@ -244,7 +240,6 @@ pub(super) fn control(
|
||||
click_consumed.0,
|
||||
&mut world_clicks.0,
|
||||
&mut world_clicks.1,
|
||||
&mut world_clicks.2,
|
||||
left_click,
|
||||
right_click,
|
||||
look_cfg,
|
||||
|
||||
@@ -282,9 +282,13 @@ impl Plugin for TargetPlugin {
|
||||
hover::update_hover,
|
||||
hover::update_hovered_object,
|
||||
cursor_mode::classify_cursor,
|
||||
// The right-press cancel (the reference's OnMouseDown hook) before the cursor
|
||||
// drive, so the press frame already reads the mode cleared.
|
||||
crate::spell::targeting::cancel_targeting_on_right_press,
|
||||
// The right press's two legs of the reference's OnMouseDown hook (`0x492c20`),
|
||||
// the targeting cancel and the repair-mode reset, before the cursor drive, so
|
||||
// the press frame already reads both modes cleared.
|
||||
(
|
||||
crate::spell::targeting::cancel_targeting_on_right_press,
|
||||
crate::ui_merchant::end_repair_mode_on_right_press,
|
||||
),
|
||||
// Overwrites the classifier's verdict while the targeting cursor is up, as the
|
||||
// reference's dispatcher runs this branch before the object classifier.
|
||||
crate::spell::targeting::drive_targeting_cursor,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//! [`feed_repair_all_cost`] the repair-all total, and [`drain_merchant`] sends the Lua intents. A bag click sells through [`crate::ui_items`].
|
||||
|
||||
use benilla_protocol::messages::{buy_result, sell_result, VendorItem};
|
||||
use benilla_world::interact::WorldRightPress;
|
||||
use bevy::prelude::*;
|
||||
|
||||
use benilla_ui::script::{ItemStatsHead, MerchantItem, MerchantState, ScriptValue, UiScript};
|
||||
@@ -630,6 +631,22 @@ fn drain_merchant(
|
||||
}
|
||||
}
|
||||
|
||||
/// A right mouse-down in the world ends repair mode: the WorldFrame hook `0x492c20` resets the
|
||||
/// Repair base mode to Point (`0x492c68`) and consumes nothing. A held payload pre-empts the hook
|
||||
/// (`0x492b50`), and a press over a UI frame never reaches it.
|
||||
pub(crate) fn end_repair_mode_on_right_press(
|
||||
mut presses: MessageReader<WorldRightPress>,
|
||||
payload_held: Res<crate::ui_script::CursorPayloadHeld>,
|
||||
script: Option<NonSendMut<UiScript>>,
|
||||
) {
|
||||
if presses.read().last().is_none() || payload_held.0 {
|
||||
return;
|
||||
}
|
||||
if let Some(mut script) = script {
|
||||
script.end_repair_mode();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -128,6 +128,12 @@ impl super::UiScript {
|
||||
pub fn repair_mode(&self) -> bool {
|
||||
self.model_ref().repair_mode
|
||||
}
|
||||
|
||||
/// Leave repair mode, as `HideRepairCursor` does, for the world's right mouse-down, which
|
||||
/// resets the Repair base mode to Point (`0x492c68`).
|
||||
pub fn end_repair_mode(&mut self) {
|
||||
self.model_mut().repair_mode = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 1 or nil, as the client pushes a usable flag (`pushnumber(1.0)` / `pushnil`).
|
||||
@@ -911,6 +917,31 @@ mod tests {
|
||||
assert!(s.take_container_moves().is_empty());
|
||||
}
|
||||
|
||||
/// The targeting arm writes the Cast base mode over Repair (`0x6e50b0`) and its end restores
|
||||
/// Point (`0x6e49f5`, `0x6e554c`), so arming ends repair mode for good. The idle feed pushes
|
||||
/// false every frame, which must leave repair mode standing.
|
||||
#[test]
|
||||
fn arming_spell_targeting_ends_repair_mode_and_disarming_does_not_restore_it() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
let mut vendor = stock();
|
||||
vendor.can_repair = true;
|
||||
s.set_merchant(Some(vendor));
|
||||
s.run("ShowRepairCursor()").unwrap();
|
||||
s.set_spell_targeting(false);
|
||||
assert!(s.repair_mode(), "the idle feed keeps repair mode");
|
||||
|
||||
s.set_spell_targeting(true);
|
||||
assert!(!s.repair_mode(), "the targeting arm ends repair mode");
|
||||
assert!(s.eval::<bool>("return InRepairMode() == nil").unwrap());
|
||||
|
||||
s.set_spell_targeting(false);
|
||||
assert!(
|
||||
!s.repair_mode(),
|
||||
"ending the targeting restores Point, not Repair"
|
||||
);
|
||||
assert!(s.eval::<bool>("return InRepairMode() == nil").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clearing_the_merchant_empties_it() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
|
||||
@@ -131,9 +131,15 @@ impl super::UiScript {
|
||||
}
|
||||
|
||||
/// Whether the spell-targeting cursor is up, for `SpellIsTargeting()` and
|
||||
/// `SpellStopTargeting()`; pushed each frame before the input pass runs the ESC chain.
|
||||
/// `SpellStopTargeting()`; pushed each frame before the input pass runs the ESC chain. Arming
|
||||
/// ends repair mode, as `HideRepairCursor` does: the targeting arm writes the Cast base mode
|
||||
/// over Repair (`0x6e50b0`), and its end restores Point (`0x6e49f5`, `0x6e554c`), never Repair.
|
||||
pub fn set_spell_targeting(&mut self, targeting: bool) {
|
||||
self.model_mut().spell_targeting = targeting;
|
||||
let mut model = self.model_mut();
|
||||
model.spell_targeting = targeting;
|
||||
if targeting {
|
||||
model.repair_mode = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Drain the `SpellStopTargeting()` trigger, the ESC chain's rung (`UIParent.lua:1490`); the
|
||||
|
||||
@@ -55,8 +55,8 @@ pub struct WorldRightClick;
|
||||
|
||||
/// The right button's press in the world (off the UI, or any press while a look holds the hidden
|
||||
/// cursor), sent before the click test starts, as the reference's `CGWorldFrame::OnMouseDown`
|
||||
/// (`0x483c40`), where ground targeting's right-click cancel hangs (`0x492c20`). It consumes
|
||||
/// nothing, so the turn and the release's context click still run.
|
||||
/// (`0x483c40`), where ground targeting's right-click cancel and the repair-mode reset hang
|
||||
/// (`0x492c20`). It consumes nothing, so the turn and the release's context click still run.
|
||||
#[derive(Message, Clone, Copy)]
|
||||
pub struct WorldRightPress;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user