mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-09 02:29:50 -10:00
Merge pull request #11978 from horizon-rust/split/using-item
🤖 Agent: pure move of 237 lines (an item in use, tick by tick) to game/using_item.rs; apply_consume_effect and drop_bundle_content stay in game.rs because an open draft changes them.
This commit is contained in:
7 files changed
+255
-245
No files matched your search
@@ -159,7 +159,7 @@ state stays separate from the library tests. This compiles gameplay twice for ex
|
||||
| Block behaviour | `crates/horizon-world/src/level/` (`mod.rs`, `tick.rs`, `hooks.rs`, `redstone.rs`, `place.rs`, `interact.rs`); game.rs `fn tick_blocks`, `fn process_level_events` |
|
||||
| Mobs | L: `mobs.rs` (types), `mobs/` (`ai.rs`, `goals.rs`, `brain.rs`, `*_brain.rs`, `nav.rs`, `path.rs`, `species.rs`, `spawning.rs`, `render.rs`, `anim.rs`); G: `creatures.rs` (ticking, spawning, mob events), one file per mob family (`dragon.rs`, `wither.rs`, `villagers.rs`, `piglins.rs`, `golems.rs`...), `natural_spawning.rs` |
|
||||
| Other entities | L: `entities.rs`, `projectiles.rs`, `arrows.rs`; G: `boats.rs`, `minecarts.rs`, `mounts.rs`, `armor_stands.rs`, `paintings.rs`, `frames.rs`, `fireworks.rs`, `tnt.rs`, `explosion.rs` |
|
||||
| Player actions | game.rs (`fn use_hand`, `fn use_item`, `fn attack`, `fn tick_mining`, `fn break_block`); G: `hand.rs`, `combat.rs`, `consume.rs`, `potions.rs`, `shield.rs`, `crossbow.rs`, `trident.rs`, `fishing.rs`; L: `motion.rs` + `motion/` (movement), `collisions.rs`; crate `horizon-items`: `items.rs`, `item_data.rs`, `enchant.rs`, `blocks.rs` |
|
||||
| Player actions | game.rs (`fn use_hand`, `fn use_item`, `fn attack`, `fn tick_mining`, `fn break_block`); G: `hand.rs`, `combat.rs`, `consume.rs`, `using_item.rs` (an item in use, tick by tick), `potions.rs`, `shield.rs`, `crossbow.rs`, `trident.rs`, `fishing.rs`; L: `motion.rs` + `motion/` (movement), `collisions.rs`; crate `horizon-items`: `items.rs`, `item_data.rs`, `enchant.rs`, `blocks.rs` |
|
||||
| Inventory and containers | L: `menu.rs` (`AbstractContainerMenu.clicked`), `container_screen.rs`, `containers.rs`, `creative.rs`, `crafting.rs`, `recipe_book*.rs`, `stations.rs`, `merchant.rs`; game.rs `fn menu_ops`, `fn click_inventory`; G: `stations.rs`, `trading.rs`, `recipe_book.rs`, `machines.rs` |
|
||||
| Commands | parse: crate `horizon-command` (`args.rs`, `selector.rs`, `tree_*.rs`, `vanilla.rs`); run: G: `commands.rs` (dispatch) + `commands/*.rs` (`admin`, `execute`, `items`, `players`, `world_cmds`...); L: `scoreboard_cmd.rs`, `data_cmd.rs`; `horizon-client-core`: `nbt_path.rs`, `scoreboard.rs`; G: `functions.rs`, `command_blocks.rs` |
|
||||
| UI screens | `Screen` enum in game.rs; L: `title.rs` + `title/`, `options_ui.rs`, `multiplayer.rs`, `pack_screen.rs`, `graphics_ui.rs`, `screen_draw.rs`, `ui.rs` (widgets), `hud.rs`, `hud_layout.rs`, `hud_overlays.rs`, `chat.rs`, `tab_list.rs`; G: `startup_menu.rs`, `screens.rs` (pause, death), `options.rs`, `lan_screen.rs`, `chat.rs`, `sign_edit.rs`, `books.rs`, `creative_screen.rs` |
|
||||
|
||||
@@ -421,6 +421,7 @@ mod trident;
|
||||
mod tutorial;
|
||||
mod tweaks;
|
||||
mod ui_control;
|
||||
mod using_item;
|
||||
mod vault;
|
||||
mod vehicle_teleport;
|
||||
mod villagers;
|
||||
@@ -7537,55 +7538,6 @@ impl Game<'_> {
|
||||
game_master: self.can_use_game_master_blocks(),
|
||||
}
|
||||
}
|
||||
/// `Consumable.startConsuming` for a held consumable: food needs
|
||||
/// `Player.canEat` (`Consumable.canConsume`), anything else is always
|
||||
/// consumed. The right click is held for `consume_seconds`.
|
||||
fn start_eating(&mut self, info: &horizon_client::items::ItemInfo) -> bool {
|
||||
let Some(stack) = self.held().cloned() else {
|
||||
return false;
|
||||
};
|
||||
if info.consumable.is_none()
|
||||
|| (info.food.is_some()
|
||||
&& (self.player.is_none() || !self.can_eat(&stack, info.can_always_eat)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// as Consumable.startConsuming does, no time is eaten at once, without a use
|
||||
// (the server's eating reaches a client through the inventory).
|
||||
if let Some(consumable) = info.consumable.as_ref()
|
||||
&& item_use::consumes_at_once(consumable.ticks())
|
||||
{
|
||||
if self.remote.is_none() {
|
||||
// the cooldown of ItemStack.use's applyAfterUseComponentSideEffects.
|
||||
self.apply_use_cooldown(&stack);
|
||||
self.finish_consuming(info, consumable, &stack);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
self.start_using_item();
|
||||
true
|
||||
}
|
||||
/// `InstrumentItem.use` for a goat horn: the player holds it up for the
|
||||
/// instrument's `use_duration`, its sound plays at the player
|
||||
/// (`SoundSource.RECORDS`, volume `range / 16`), `INSTRUMENT_PLAY` is
|
||||
/// posted, the used statistic counts and the horn is on cooldown as long.
|
||||
/// A horn whose instrument is unknown fails, which still ends the click.
|
||||
fn use_instrument(&mut self, stack: &ItemStack) {
|
||||
let Some(instrument) = self.res.items().instrument(stack) else {
|
||||
return;
|
||||
};
|
||||
self.start_using_item();
|
||||
self.play_player_bound_sound(&instrument.sound, 2, instrument.range / 16.0, 1.0, true);
|
||||
self.post_entity_event(
|
||||
"minecraft:instrument_play",
|
||||
horizon_client::mobs::Who::Player,
|
||||
);
|
||||
if instrument.use_duration > 0.0 {
|
||||
let group = horizon_client::cooldowns::group(stack, None);
|
||||
self.add_cooldown(group, instrument.use_ticks());
|
||||
}
|
||||
self.award_stat("used", &stack.item, 1);
|
||||
}
|
||||
fn apply_consume_effect(&mut self, effect: &horizon_client::items::ConsumeEffect) {
|
||||
use horizon_client::items::ConsumeEffect;
|
||||
match effect {
|
||||
@@ -7646,152 +7598,6 @@ impl Game<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
fn consume_held(&mut self) {
|
||||
if self.creative {
|
||||
return;
|
||||
}
|
||||
let cell = self.held_mut();
|
||||
if let Some(s) = cell.as_mut() {
|
||||
s.count -= 1;
|
||||
if s.count == 0 {
|
||||
*cell = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn tick_using(&mut self) {
|
||||
let Some(hand) = self.used_hand() else {
|
||||
return;
|
||||
};
|
||||
self.with_hand(hand, Self::tick_used_item);
|
||||
}
|
||||
/// `LivingEntity.updatingUsingItem` for the item in use, its hand acting.
|
||||
fn tick_used_item(&mut self) {
|
||||
let Some((slot, ticks)) = self.using else {
|
||||
return;
|
||||
};
|
||||
let Some(stack) = self.stack_in(slot).cloned() else {
|
||||
self.using = None;
|
||||
return;
|
||||
};
|
||||
// Switching hotbar slots only ends a main-hand use.
|
||||
let hand_changed = slot != self.selected && slot != shield::OFFHAND_SLOT;
|
||||
if !self.right || hand_changed || self.screen != Screen::Playing {
|
||||
self.using = None;
|
||||
self.release_used_item(&stack, ticks, hand_changed);
|
||||
return;
|
||||
}
|
||||
// ServerPlayer.updateUsingItem: USING_ITEM every tick of the use.
|
||||
self.trigger_using_item(&stack);
|
||||
let Some(info) = self.items().get(&stack.item).cloned() else {
|
||||
self.using = None;
|
||||
return;
|
||||
};
|
||||
// a mod's tick ran the use out: finished now, or frozen until let go
|
||||
// for an item used on release (CrossbowItem.useOnRelease).
|
||||
if !self.use_item_ticked(&stack, ticks) {
|
||||
if &*stack.item != "minecraft:crossbow" {
|
||||
self.complete_using_item(slot, &stack, &info);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if self.blocks_attacks(&stack).is_some() {
|
||||
// ShieldItem: used for 72000 ticks, i.e. until released.
|
||||
self.item_on_use_tick(&stack, ticks);
|
||||
self.using = Some((slot, ticks + 1));
|
||||
return;
|
||||
}
|
||||
if self.tick_drinking(slot, ticks, &stack) {
|
||||
if self.using.is_none() {
|
||||
self.use_item_finished(&stack);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if &*stack.item == "minecraft:bow" {
|
||||
self.using = Some((slot, ticks + 1));
|
||||
return;
|
||||
}
|
||||
if &*stack.item == "minecraft:crossbow" {
|
||||
self.tick_crossbow(slot, ticks, &stack);
|
||||
return;
|
||||
}
|
||||
// TridentItem and EnderEyeItem: used until released (an eye's
|
||||
// getUseDuration 0 never counts down to finishUsingItem).
|
||||
if matches!(&*stack.item, "minecraft:trident" | "minecraft:ender_eye") {
|
||||
self.using = Some((slot, ticks + 1));
|
||||
return;
|
||||
}
|
||||
// ItemStack.onUseTick: a spear stabs what its speed allows, until released.
|
||||
if horizon_client::spear::Kinetic::of(&stack.item).is_some() {
|
||||
self.spear_tick(&stack, ticks);
|
||||
self.using = Some((slot, ticks + 1));
|
||||
return;
|
||||
}
|
||||
if &*stack.item == "minecraft:spyglass" {
|
||||
// SpyglassItem.getUseDuration (1200, as a mod left it), then
|
||||
// completeUsingItem: finishUsingItem lowers it, and the finish hook.
|
||||
match item_use::count_down(ticks, self.use_duration) {
|
||||
Some(ticks) => self.using = Some((slot, ticks)),
|
||||
None => self.complete_using_item(slot, &stack, &info),
|
||||
}
|
||||
return;
|
||||
}
|
||||
if let Some(duration) = self
|
||||
.res
|
||||
.items()
|
||||
.instrument(&stack)
|
||||
.map(|i| i.use_ticks().max(0) as u32)
|
||||
{
|
||||
// InstrumentItem.getUseDuration: held up that long. When it runs
|
||||
// out, LivingEntity.stopUsingItem ends the use and causes the
|
||||
// interaction vibration; letting go of the button already does.
|
||||
if ticks + 1 < duration {
|
||||
self.using = Some((slot, ticks + 1));
|
||||
} else {
|
||||
self.stop_using_item(&stack);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if &*stack.item == "minecraft:brush" {
|
||||
self.tick_brush(&stack, slot, ticks);
|
||||
return;
|
||||
}
|
||||
if info.bundle {
|
||||
self.tick_bundle(slot, ticks);
|
||||
return;
|
||||
}
|
||||
if self.mod_use(&stack) {
|
||||
self.tick_mod_use(slot, ticks, &stack);
|
||||
return;
|
||||
}
|
||||
let Some(consumable) = info.consumable.as_ref() else {
|
||||
self.using = None;
|
||||
return;
|
||||
};
|
||||
// ItemStack.onUseTick: Consumable.shouldEmitParticlesAndSounds with
|
||||
// the remaining ticks before this tick counts them down.
|
||||
// the duration is getUseDuration's when the use started, as a mod's
|
||||
// Start and Tick left it.
|
||||
let remaining = self.use_duration - ticks as i32;
|
||||
if consumable.should_emit(remaining) {
|
||||
self.emit_consume(consumable, &stack, 5);
|
||||
}
|
||||
self.item_on_use_tick(&stack, ticks);
|
||||
match item_use::count_down(ticks, self.use_duration) {
|
||||
Some(ticks) => self.using = Some((slot, ticks)),
|
||||
None => self.complete_using_item(slot, &stack, &info),
|
||||
}
|
||||
}
|
||||
/// `BundleItem.onUseTick` and `getUseDuration` (200): the first tick and
|
||||
/// then every second tick from the 11th on drop one stack out of the
|
||||
/// bundle in `slot`; the use ends after 200 ticks.
|
||||
fn tick_bundle(&mut self, slot: usize, ticks: u32) {
|
||||
const USE_DURATION: u32 = 200;
|
||||
let remaining = USE_DURATION - ticks.min(USE_DURATION);
|
||||
if ticks == 0 || (remaining < USE_DURATION - 10 && remaining.is_multiple_of(2)) {
|
||||
self.drop_bundle_content(slot);
|
||||
}
|
||||
self.using = (ticks + 1 < USE_DURATION).then_some((slot, ticks + 1));
|
||||
}
|
||||
/// `BundleItem.dropContent`: `removeOneItemFromBundle` and `Player.drop`,
|
||||
/// then the drop sound and the used statistic.
|
||||
fn drop_bundle_content(&mut self, slot: usize) {
|
||||
@@ -7828,48 +7634,6 @@ impl Game<'_> {
|
||||
);
|
||||
self.award_stat("used", &item, 1);
|
||||
}
|
||||
/// `LivingEntity.releaseUsingItem` (`Item.releaseUsing`) for `stack`,
|
||||
/// used for `ticks`, its hand acting: a bow or a trident lets go (not
|
||||
/// when its hand changed), a spyglass stops scoping, and a mod's own
|
||||
/// release follows. A mod's `Stop` may skip the release. `stopUsingItem`
|
||||
/// ends the use after it.
|
||||
pub(super) fn release_used_item(&mut self, stack: &ItemStack, ticks: u32, hand_changed: bool) {
|
||||
let canceled = !hand_changed && !self.use_item_stopped(stack, ticks);
|
||||
let (release, spyglass) = item_use::release_parts(canceled, hand_changed);
|
||||
match &*stack.item {
|
||||
"minecraft:bow" if release => self.release_bow(stack, self.ticks_held(stack, ticks)),
|
||||
"minecraft:trident" if release => self.release_trident(self.ticks_held(stack, ticks)),
|
||||
"minecraft:spyglass" if spyglass => self.stop_spyglass(),
|
||||
_ => {}
|
||||
}
|
||||
if release {
|
||||
self.mod_release_using(stack, ticks);
|
||||
}
|
||||
self.stop_using_item(stack);
|
||||
}
|
||||
/// `LivingEntity.stopUsingItem`: the use of `stack` is over (`useItem` and
|
||||
/// its remaining ticks are cleared), and when the use effects of the stack
|
||||
/// ask for it, `ItemStack.causeUseVibration` sends an `ITEM_INTERACT_FINISH`
|
||||
/// 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, "minecraft:item_interact_finish");
|
||||
}
|
||||
/// `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) {
|
||||
let default = self.items().get(&stack.item).map(|i| i.interact_vibrations);
|
||||
if !item_use::interact_vibrations(stack, default) {
|
||||
return;
|
||||
}
|
||||
let Some(p) = self.player.as_ref().map(|p| p.position) else {
|
||||
return;
|
||||
};
|
||||
let ctx = self.player_context(None);
|
||||
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),
|
||||
/// for everyone around, the digger included (`levelEvent(null, ...)`).
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
//! An item in use, tick by tick (`LivingEntity.startUsingItem`, `updatingUsingItem`,
|
||||
//! `releaseUsingItem`, `stopUsingItem`): eating and drinking, goat horns, bundles, and the
|
||||
//! vibrations a use makes.
|
||||
use super::*;
|
||||
|
||||
impl Game<'_> {
|
||||
/// `Consumable.startConsuming` for a held consumable: food needs
|
||||
/// `Player.canEat` (`Consumable.canConsume`), anything else is always
|
||||
/// consumed. The right click is held for `consume_seconds`.
|
||||
pub(super) fn start_eating(&mut self, info: &horizon_client::items::ItemInfo) -> bool {
|
||||
let Some(stack) = self.held().cloned() else {
|
||||
return false;
|
||||
};
|
||||
if info.consumable.is_none()
|
||||
|| (info.food.is_some()
|
||||
&& (self.player.is_none() || !self.can_eat(&stack, info.can_always_eat)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// as Consumable.startConsuming does, no time is eaten at once, without a use
|
||||
// (the server's eating reaches a client through the inventory).
|
||||
if let Some(consumable) = info.consumable.as_ref()
|
||||
&& item_use::consumes_at_once(consumable.ticks())
|
||||
{
|
||||
if self.remote.is_none() {
|
||||
// the cooldown of ItemStack.use's applyAfterUseComponentSideEffects.
|
||||
self.apply_use_cooldown(&stack);
|
||||
self.finish_consuming(info, consumable, &stack);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
self.start_using_item();
|
||||
true
|
||||
}
|
||||
/// `InstrumentItem.use` for a goat horn: the player holds it up for the
|
||||
/// instrument's `use_duration`, its sound plays at the player
|
||||
/// (`SoundSource.RECORDS`, volume `range / 16`), `INSTRUMENT_PLAY` is
|
||||
/// posted, the used statistic counts and the horn is on cooldown as long.
|
||||
/// A horn whose instrument is unknown fails, which still ends the click.
|
||||
pub(super) fn use_instrument(&mut self, stack: &ItemStack) {
|
||||
let Some(instrument) = self.res.items().instrument(stack) else {
|
||||
return;
|
||||
};
|
||||
self.start_using_item();
|
||||
self.play_player_bound_sound(&instrument.sound, 2, instrument.range / 16.0, 1.0, true);
|
||||
self.post_entity_event(
|
||||
"minecraft:instrument_play",
|
||||
horizon_client::mobs::Who::Player,
|
||||
);
|
||||
if instrument.use_duration > 0.0 {
|
||||
let group = horizon_client::cooldowns::group(stack, None);
|
||||
self.add_cooldown(group, instrument.use_ticks());
|
||||
}
|
||||
self.award_stat("used", &stack.item, 1);
|
||||
}
|
||||
|
||||
pub(super) fn consume_held(&mut self) {
|
||||
if self.creative {
|
||||
return;
|
||||
}
|
||||
let cell = self.held_mut();
|
||||
if let Some(s) = cell.as_mut() {
|
||||
s.count -= 1;
|
||||
if s.count == 0 {
|
||||
*cell = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(super) fn tick_using(&mut self) {
|
||||
let Some(hand) = self.used_hand() else {
|
||||
return;
|
||||
};
|
||||
self.with_hand(hand, Self::tick_used_item);
|
||||
}
|
||||
/// `LivingEntity.updatingUsingItem` for the item in use, its hand acting.
|
||||
pub(super) fn tick_used_item(&mut self) {
|
||||
let Some((slot, ticks)) = self.using else {
|
||||
return;
|
||||
};
|
||||
let Some(stack) = self.stack_in(slot).cloned() else {
|
||||
self.using = None;
|
||||
return;
|
||||
};
|
||||
// Switching hotbar slots only ends a main-hand use.
|
||||
let hand_changed = slot != self.selected && slot != shield::OFFHAND_SLOT;
|
||||
if !self.right || hand_changed || self.screen != Screen::Playing {
|
||||
self.using = None;
|
||||
self.release_used_item(&stack, ticks, hand_changed);
|
||||
return;
|
||||
}
|
||||
// ServerPlayer.updateUsingItem: USING_ITEM every tick of the use.
|
||||
self.trigger_using_item(&stack);
|
||||
let Some(info) = self.items().get(&stack.item).cloned() else {
|
||||
self.using = None;
|
||||
return;
|
||||
};
|
||||
// a mod's tick ran the use out: finished now, or frozen until let go
|
||||
// for an item used on release (CrossbowItem.useOnRelease).
|
||||
if !self.use_item_ticked(&stack, ticks) {
|
||||
if &*stack.item != "minecraft:crossbow" {
|
||||
self.complete_using_item(slot, &stack, &info);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if self.blocks_attacks(&stack).is_some() {
|
||||
// ShieldItem: used for 72000 ticks, i.e. until released.
|
||||
self.item_on_use_tick(&stack, ticks);
|
||||
self.using = Some((slot, ticks + 1));
|
||||
return;
|
||||
}
|
||||
if self.tick_drinking(slot, ticks, &stack) {
|
||||
if self.using.is_none() {
|
||||
self.use_item_finished(&stack);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if &*stack.item == "minecraft:bow" {
|
||||
self.using = Some((slot, ticks + 1));
|
||||
return;
|
||||
}
|
||||
if &*stack.item == "minecraft:crossbow" {
|
||||
self.tick_crossbow(slot, ticks, &stack);
|
||||
return;
|
||||
}
|
||||
// TridentItem and EnderEyeItem: used until released (an eye's
|
||||
// getUseDuration 0 never counts down to finishUsingItem).
|
||||
if matches!(&*stack.item, "minecraft:trident" | "minecraft:ender_eye") {
|
||||
self.using = Some((slot, ticks + 1));
|
||||
return;
|
||||
}
|
||||
// ItemStack.onUseTick: a spear stabs what its speed allows, until released.
|
||||
if horizon_client::spear::Kinetic::of(&stack.item).is_some() {
|
||||
self.spear_tick(&stack, ticks);
|
||||
self.using = Some((slot, ticks + 1));
|
||||
return;
|
||||
}
|
||||
if &*stack.item == "minecraft:spyglass" {
|
||||
// SpyglassItem.getUseDuration (1200, as a mod left it), then
|
||||
// completeUsingItem: finishUsingItem lowers it, and the finish hook.
|
||||
match item_use::count_down(ticks, self.use_duration) {
|
||||
Some(ticks) => self.using = Some((slot, ticks)),
|
||||
None => self.complete_using_item(slot, &stack, &info),
|
||||
}
|
||||
return;
|
||||
}
|
||||
if let Some(duration) = self
|
||||
.res
|
||||
.items()
|
||||
.instrument(&stack)
|
||||
.map(|i| i.use_ticks().max(0) as u32)
|
||||
{
|
||||
// InstrumentItem.getUseDuration: held up that long. When it runs
|
||||
// out, LivingEntity.stopUsingItem ends the use and causes the
|
||||
// interaction vibration; letting go of the button already does.
|
||||
if ticks + 1 < duration {
|
||||
self.using = Some((slot, ticks + 1));
|
||||
} else {
|
||||
self.stop_using_item(&stack);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if &*stack.item == "minecraft:brush" {
|
||||
self.tick_brush(&stack, slot, ticks);
|
||||
return;
|
||||
}
|
||||
if info.bundle {
|
||||
self.tick_bundle(slot, ticks);
|
||||
return;
|
||||
}
|
||||
if self.mod_use(&stack) {
|
||||
self.tick_mod_use(slot, ticks, &stack);
|
||||
return;
|
||||
}
|
||||
let Some(consumable) = info.consumable.as_ref() else {
|
||||
self.using = None;
|
||||
return;
|
||||
};
|
||||
// ItemStack.onUseTick: Consumable.shouldEmitParticlesAndSounds with
|
||||
// the remaining ticks before this tick counts them down.
|
||||
// the duration is getUseDuration's when the use started, as a mod's
|
||||
// Start and Tick left it.
|
||||
let remaining = self.use_duration - ticks as i32;
|
||||
if consumable.should_emit(remaining) {
|
||||
self.emit_consume(consumable, &stack, 5);
|
||||
}
|
||||
self.item_on_use_tick(&stack, ticks);
|
||||
match item_use::count_down(ticks, self.use_duration) {
|
||||
Some(ticks) => self.using = Some((slot, ticks)),
|
||||
None => self.complete_using_item(slot, &stack, &info),
|
||||
}
|
||||
}
|
||||
/// `BundleItem.onUseTick` and `getUseDuration` (200): the first tick and
|
||||
/// then every second tick from the 11th on drop one stack out of the
|
||||
/// bundle in `slot`; the use ends after 200 ticks.
|
||||
pub(super) fn tick_bundle(&mut self, slot: usize, ticks: u32) {
|
||||
const USE_DURATION: u32 = 200;
|
||||
let remaining = USE_DURATION - ticks.min(USE_DURATION);
|
||||
if ticks == 0 || (remaining < USE_DURATION - 10 && remaining.is_multiple_of(2)) {
|
||||
self.drop_bundle_content(slot);
|
||||
}
|
||||
self.using = (ticks + 1 < USE_DURATION).then_some((slot, ticks + 1));
|
||||
}
|
||||
|
||||
/// `LivingEntity.releaseUsingItem` (`Item.releaseUsing`) for `stack`,
|
||||
/// used for `ticks`, its hand acting: a bow or a trident lets go (not
|
||||
/// when its hand changed), a spyglass stops scoping, and a mod's own
|
||||
/// release follows. A mod's `Stop` may skip the release. `stopUsingItem`
|
||||
/// ends the use after it.
|
||||
pub(super) fn release_used_item(&mut self, stack: &ItemStack, ticks: u32, hand_changed: bool) {
|
||||
let canceled = !hand_changed && !self.use_item_stopped(stack, ticks);
|
||||
let (release, spyglass) = item_use::release_parts(canceled, hand_changed);
|
||||
match &*stack.item {
|
||||
"minecraft:bow" if release => self.release_bow(stack, self.ticks_held(stack, ticks)),
|
||||
"minecraft:trident" if release => self.release_trident(self.ticks_held(stack, ticks)),
|
||||
"minecraft:spyglass" if spyglass => self.stop_spyglass(),
|
||||
_ => {}
|
||||
}
|
||||
if release {
|
||||
self.mod_release_using(stack, ticks);
|
||||
}
|
||||
self.stop_using_item(stack);
|
||||
}
|
||||
/// `LivingEntity.stopUsingItem`: the use of `stack` is over (`useItem` and
|
||||
/// its remaining ticks are cleared), and when the use effects of the stack
|
||||
/// ask for it, `ItemStack.causeUseVibration` sends an `ITEM_INTERACT_FINISH`
|
||||
/// 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, "minecraft:item_interact_finish");
|
||||
}
|
||||
/// `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) {
|
||||
let default = self.items().get(&stack.item).map(|i| i.interact_vibrations);
|
||||
if !item_use::interact_vibrations(stack, default) {
|
||||
return;
|
||||
}
|
||||
let Some(p) = self.player.as_ref().map(|p| p.position) else {
|
||||
return;
|
||||
};
|
||||
let ctx = self.player_context(None);
|
||||
self.post_game_event(event, [p.x, p.y, p.z], ctx);
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ command above to inspect the selection rather than relying on historical counts.
|
||||
| 12 | Guests' actions on a server (98) | `G/lan_actions.rs` (`fn as_guest`, `fn tick_guests`), `G/lan_{attributes,pvp,chat,crafting,stations,trading,mount_menu,waypoints}.rs` | server_player_combat, server_player_damage, server_player_targets, lan_e2e (all) |
|
||||
| 13 | Modpack porting, mod content (97) | `crates/horizon-port-pack/`, `L/mod_content.rs`, `tools/port_*.py` | `cargo test -p horizon-port-pack`; `python3 tools/mod_port_harness.py` |
|
||||
| 14 | Mob AI (96) | `L/mobs/ai.rs`, `goals.rs`, `brain.rs`, `*_brain.rs`, `nav.rs`, `path.rs`, `control.rs` | mob_ai (probe `examples/mob_ai_probe.rs`; known failure on main: drowned_*, #290) |
|
||||
| 15 | Items and item use (96) | `I/items.rs`, `I/item_data.rs`, `G/hand.rs`, `G/consume.rs`, `G/potions.rs`, `R/effects.rs`; game.rs `fn use_hand`, `fn use_item` | use_item_on, item_use, item_use_lifecycle, consumable, consume_effect, status_effect |
|
||||
| 15 | Items and item use (96) | `I/items.rs`, `I/item_data.rs`, `G/hand.rs`, `G/consume.rs`, `G/using_item.rs`, `G/potions.rs`, `R/effects.rs`; game.rs `fn use_hand`, `fn use_item` | use_item_on, item_use, item_use_lifecycle, consumable, consume_effect, status_effect |
|
||||
| 16 | Options and settings screens (93) | `R/options_ui.rs`, `G/options.rs`, `R/settings.rs`, `R/options_file.rs`, `R/graphics_ui.rs`, `R/graphics.rs` | options, toggle_keys |
|
||||
| 17 | HUD, chat, tab list (93) | `R/hud.rs`, `R/hud_layout.rs`, `R/hud_overlays.rs`, `R/chat.rs`, `G/chat.rs`, `R/tab_list.rs`, `L/locator_bar.rs`, `G/locator_bar.rs` | hud, tab_list, locator_bar, chat_screen, secure_chat |
|
||||
| 18 | Command execution (88) | `G/commands.rs`, `G/commands/*.rs`, `L/scoreboard_cmd.rs`, `L/data_cmd.rs`, `K/scoreboard.rs`, `G/functions.rs` | command_exec (corpus `tools/command_exec_corpus.py`), scoreboard, server_ops_feedback, lan_e2e commands |
|
||||
|
||||
@@ -210,8 +210,8 @@ InkSacItem.tryApplyToSign inline crates/horizon-client/src/game/sign_edit.rs::Ga
|
||||
InstrumentItem.create skip:java builds the horn stack
|
||||
InstrumentItem.getInstrument skip:data instrument read from the item data
|
||||
InstrumentItem.getUseDuration port crates/horizon-client/src/game/item_draw.rs::use_duration goat horn use duration 72000
|
||||
InstrumentItem.play inline crates/horizon-client/src/game.rs::Game::use_instrument the instrument's sound at the player (SoundSource.RECORDS, volume `range / 16`), `INSTRUMENT_PLAY` and the use-duration cooldown
|
||||
InstrumentItem.use port crates/horizon-client/src/game.rs::Game::use_instrument holds the horn up for the instrument's use duration, plays it, counts the used statistic; an unknown instrument fails
|
||||
InstrumentItem.play inline crates/horizon-client/src/game/using_item.rs::Game::use_instrument the instrument's sound at the player (SoundSource.RECORDS, volume `range / 16`), `INSTRUMENT_PLAY` and the use-duration cooldown
|
||||
InstrumentItem.use port crates/horizon-client/src/game/using_item.rs::Game::use_instrument holds the horn up for the instrument's use duration, plays it, counts the used statistic; an unknown instrument fails
|
||||
Instruments.bootstrap skip:data registry bootstrap of the instrument list
|
||||
Instruments.register skip:data registry bootstrap of the instrument list
|
||||
ItemFrameItem.mayPlace port crates/horizon-client/src/game/frames.rs::Game::place_item_frame
|
||||
|
||||
|
@@ -92,8 +92,8 @@ LivingEntity.canGlide inline crates/horizon-client/src/motion.rs::MotionWorld::p
|
||||
LivingEntity.onItemPickup port crates/horizon-client/src/game/sulfur_cubes.rs::Game::sulfur_cube_pickups gap: player: only a sulfur cube's pickup fires thrown_item_picked_up_by_entity; a piglin picking up gold you threw does not, so Oh Shiny never unlocks
|
||||
LivingEntity.isUsingItem inline crates/horizon-client/src/game/remote.rs::RemoteInputWorld::using_item Game::using is Some; a mob's is extra.using_item above 0
|
||||
LivingEntity.setLivingEntityFlag skip:java bit accessor; the flags are built when synced in game/lan.rs::mob_meta
|
||||
LivingEntity.getTicksUsingItem inline crates/horizon-client/src/game.rs::Game::tick_used_item the tick count kept in Game::using
|
||||
LivingEntity.stopUsingItem port crates/horizon-client/src/game.rs::Game::stop_using_item using is dropped when the use ends, and ItemStack.causeUseVibration sends ITEM_INTERACT_FINISH at the player when the stack's use effects ask for it
|
||||
LivingEntity.getTicksUsingItem inline crates/horizon-client/src/game/using_item.rs::Game::tick_used_item the tick count kept in Game::using
|
||||
LivingEntity.stopUsingItem port crates/horizon-client/src/game/using_item.rs::Game::stop_using_item using is dropped when the use ends, and ItemStack.causeUseVibration sends ITEM_INTERACT_FINISH at the player when the stack's use effects ask for it
|
||||
LivingEntity.getLocalBoundsForPose inline crates/horizon-client/src/game/mounts.rs::rider_poses the rider's box in each dismount pose, standing, crouching then swimming, from the pose's dimensions at its scale; the strider dismount tests it (strider_dismount_location), the other vehicles use their own side checks
|
||||
LivingEntity.wouldNotSuffocateAtTargetPose port crates/horizon-client/src/pose.rs::MotionWorld::can_fit_pose gap: minor: the player's poses only; a camel changing pose is not checked
|
||||
LivingEntity.canUsePortal port crates/horizon-client/src/game/nether_portal.rs::Game::mob_can_use_portal gap: minor: a sleeping mob is not excluded
|
||||
@@ -130,7 +130,7 @@ Mob.hasValidEquippableItemForSlot skip:other Horizon stores only items that fit
|
||||
Mob.createEquipmentParams inline crates/horizon-client/src/game/mob_gear.rs::Game::equip_from_table EntityLootParams with the mob as `this`
|
||||
Mob.getEquipmentForSlot inline crates/horizon-client/src/mobs/finalize.rs::Ctx::armor the material table leather, copper, golden, chainmail, iron, diamond by piece
|
||||
Mob.enchantSpawnedEquipment port crates/horizon-client/src/game/mob_gear.rs::Game::enchant_slot chance times the local difficulty's special multiplier
|
||||
Mob.usePlayerItem port crates/horizon-client/src/game.rs::Game::consume_held gap: minor: no use_remainder (a bottle or bowl a food item leaves behind)
|
||||
Mob.usePlayerItem port crates/horizon-client/src/game/using_item.rs::Game::consume_held gap: minor: no use_remainder (a bottle or bowl a food item leaves behind)
|
||||
Mob.setNoAi skip:java plain field Mob::no_ai, synced as a flag in game/lan.rs::mob_meta
|
||||
Mob.setLeftHanded skip:java plain field Extra::left_handed, synced in game/lan.rs::mob_meta
|
||||
Mob.setAggressive skip:java plain field Ai::aggressive, synced in game/lan.rs::mob_meta
|
||||
|
||||
|
@@ -66,7 +66,7 @@ FireworkRocketItem.getEntityJustOutsideOfBlockPos port crates/horizon-client/src
|
||||
HangingEntityItem.mayPlace port crates/horizon-client/src/game/paintings.rs::Game::place_painting paintings take no vertical face; an item frame (ItemFrameItem.mayPlace) may go on one and only needs the build height, which place_item_frame checks
|
||||
HangingEntityItem.appendHoverText port crates/horizon-client/src/game/stations.rs::Game::stack_tooltip_lines
|
||||
Item.getDestroySpeed port crates/horizon-items/src/items.rs::Items::mining_speed
|
||||
Item.finishUsingItem port crates/horizon-client/src/game.rs::Game::consume_held the consumable effects are applied by apply_consume_effect
|
||||
Item.finishUsingItem port crates/horizon-client/src/game/using_item.rs::Game::consume_held the consumable effects are applied by apply_consume_effect
|
||||
Item.getBarColor port crates/horizon-client-render/src/hud_overlays.rs::damage_bar width and hue from the damage fraction
|
||||
Item.mineBlock port crates/horizon-client/src/game.rs::Game::mine_block_with_held
|
||||
Item.isCorrectToolForDrops port crates/horizon-items/src/items.rs::Items::correct_for_drops
|
||||
|
||||
|
Reference in new issue
Block a user