Merge pull request #10272 from MSedgeMC/fix/item-interact-start

Starting an item use posts item_interact_start
This commit is contained in:
Kacper authored and GitHub committed 2026-11-06 09:00:36 +01:00
commit e5cc020034
2 files changed
+96 -8

No files matched your search

+7 -6
View File
@@ -7532,12 +7532,13 @@ impl Game<'_> {
/// game event from the player, at its feet.
pub(super) fn stop_using_item(&mut self, stack: &ItemStack) {
self.using = None;
self.use_vibration(stack);
self.use_vibration(stack, "minecraft:item_interact_finish");
}
/// `ItemStack.causeUseVibration` for `stack` used by the player: an
/// `ITEM_INTERACT_FINISH` game event from the player, at its feet, when the
/// stack's use effects ask for interaction vibrations (the default).
pub(super) fn use_vibration(&mut self, stack: &ItemStack) {
/// `ItemStack.causeUseVibration` for `stack` used by the player: the
/// `event` game event (`ITEM_INTERACT_START` when the use begins,
/// `ITEM_INTERACT_FINISH` when it ends) from the player, at its feet, when
/// the stack's use effects ask for interaction vibrations (the default).
pub(super) fn use_vibration(&mut self, stack: &ItemStack, event: &'static str) {
if !self
.items()
.get(&stack.item)
@@ -7549,7 +7550,7 @@ impl Game<'_> {
return;
};
let ctx = self.player_context(None);
self.post_game_event("minecraft:item_interact_finish", [p.x, p.y, p.z], ctx);
self.post_game_event(event, [p.x, p.y, p.z], ctx);
}
/// `ServerPlayerGameMode.tick` while a player digs: level event 2020 every
/// fourth tick since the START (the hit sound), else 2019 (the crack only),
+89 -2
View File
@@ -32,6 +32,9 @@ impl Game<'_> {
self.using = Some((self.held_slot(), 0));
self.use_duration = duration;
self.use_ticks_set = ticks_set;
// `LivingEntity.startUsingItem`: `causeUseVibration(ITEM_INTERACT_START)`
// when the use effects of the stack ask for interaction vibrations.
self.use_vibration(&stack, "minecraft:item_interact_start");
// A new charge hears the crossbow's loading sounds again
// (`game/crossbow.rs`).
self.crossbow_sounds = [false; 2];
@@ -100,7 +103,7 @@ impl Game<'_> {
// a potion is drunk (no last tick of its own: a mod's tick ran it out).
if self.finish_drinking(slot, stack) {
self.use_item_finished(stack);
self.use_vibration(stack);
self.use_vibration(stack, "minecraft:item_interact_finish");
return;
}
self.using = None;
@@ -113,7 +116,7 @@ impl Game<'_> {
}
self.use_item_finished(stack);
// `LivingEntity.completeUsingItem` ends with `stopUsingItem`.
self.use_vibration(stack);
self.use_vibration(stack, "minecraft:item_interact_finish");
}
/// `LivingEntityUseItemEvent.Finish` after `finishUsingItem` of `stack`
/// (no ticks left): the hand holds what a mod made the result.
@@ -449,3 +452,87 @@ mod tests {
}
}
}
#[cfg(test)]
mod interact_vibration_tests {
use super::*;
/// Runs `f` on a windowless game built from the prepared resources with
/// `held` in the main hand and returns the game events it posted;
/// `None` without the resources.
fn with_game(held: &str, f: impl FnOnce(&mut Game<'_>)) -> Option<Vec<String>> {
use crate::game::{Audio, Options, Progress, Settings, load_resources};
let root = std::path::PathBuf::from(std::env::var_os("HORIZON_TEST_RESOURCES")?);
let settings = Settings::default();
let loaded = load_resources(
root.clone(),
None,
&root,
&settings,
std::time::Instant::now(),
false,
true,
&Progress::new(),
&mut |_, _| unreachable!(),
)
.ok()?;
let mut options = Options::parse(std::iter::empty()).unwrap().unwrap();
options.resources = root.clone();
options.execution_role = crate::game::execution_role::ExecutionRole::IntegratedServer;
let mut game = Game::new(
&loaded.resources,
loaded.pack,
loaded.gui.unwrap(),
options,
settings,
Audio::silent(&root),
None,
)
.unwrap();
game.player = Some(
horizon_client::motion::PlayerMotion::new(
game.motion,
horizon_core::geometry::Vec3::new(10.5, 64.0, -3.25),
)
.unwrap(),
);
*game.held_mut() = Some(ItemStack::new(held, 1));
crate::game::dispense_fixture::start_recording();
f(&mut game);
Some(crate::game::dispense_fixture::stop_recording())
}
/// `LivingEntity.startUsingItem` ends with
/// `this.useItem.causeUseVibration(this, GameEvent.ITEM_INTERACT_START)`,
/// and `ItemStack.causeUseVibration` posts only when the stack's use effects
/// ask for interaction vibrations (the default). `stopUsingItem` posts the
/// finish the same way, which Horizon already did, so a use is a pair of
/// vibrations and an item that turns them off is neither.
#[test]
#[ignore = "needs prepared resources in HORIZON_TEST_RESOURCES"]
fn a_use_start_is_an_item_interact_start_vibration() {
let bow = || ItemStack::new("minecraft:bow", 1);
let Some(lines) = with_game("minecraft:bow", |game| {
assert!(game.start_using_item(), "the bow's use starts");
game.stop_using_item(&bow());
}) else {
return;
};
assert_eq!(
lines,
vec![
"G 10,64,-4 minecraft:item_interact_start".to_owned(),
"G 10,64,-4 minecraft:item_interact_finish".to_owned(),
]
);
// A spear's use effects set `interact_vibrations` false: no vibration.
let spear = || ItemStack::new("minecraft:iron_spear", 1);
let Some(lines) = with_game("minecraft:iron_spear", |game| {
assert!(game.start_using_item(), "the spear's use starts");
game.stop_using_item(&spear());
}) else {
return;
};
assert!(lines.is_empty(), "{lines:?}");
}
}