From 44b7cccc45277e9ee6fd44b79ef50b5be5f78216 Mon Sep 17 00:00:00 2001 From: Maxime Chaffraix <160416029+Sawlyer@users.noreply.github.com> Date: Mon, 3 Aug 2026 05:47:39 +0200 Subject: [PATCH] fix(entity): feeding a parrot a cookie poisons and kills it (#2564) `ParrotEntity` had no interact handler, so a cookie used on a parrot did nothing at all. Vanilla `Parrot.mobInteract` consumes the cookie, applies poison for 900 ticks and then kills the parrot. The branch is gated on the `parrot_poisonous_food` item tag rather than on the cookie id, matching vanilla. Anything else falls through to the default mob interaction, so leashing and name tags keep working. --- pumpkin/src/entity/passive/parrot.rs | 91 +++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/pumpkin/src/entity/passive/parrot.rs b/pumpkin/src/entity/passive/parrot.rs index e2a34ffdd..10c9e9f99 100644 --- a/pumpkin/src/entity/passive/parrot.rs +++ b/pumpkin/src/entity/passive/parrot.rs @@ -1,16 +1,25 @@ use std::sync::{Arc, Weak}; +use pumpkin_data::damage::DamageType; +use pumpkin_data::effect::StatusEffect; use pumpkin_data::entity::EntityType; +use pumpkin_data::item_stack::ItemStack; +use pumpkin_data::tag::{self, Taggable}; use crate::entity::{ - Entity, NBTStorage, + Entity, EntityBase, EntityBaseFuture, NBTStorage, ai::goal::{ look_around::RandomLookAroundGoal, look_at_entity::LookAtEntityGoal, swim::SwimGoal, wander_around::WanderAroundGoal, }, mob::{Mob, MobEntity}, + player::Player, }; +/// Duration in ticks of the poison a parrot gets from eating a cookie, matching +/// vanilla `Parrot.mobInteract`. +const COOKIE_POISON_DURATION: i32 = 900; + /// Represents a Parrot, a passive flying mob that can mimic nearby mob sounds. /// /// Wiki: @@ -42,6 +51,38 @@ impl ParrotEntity { mob_arc } + + /// Feeds the parrot a cookie: it is poisoned and then killed, as in vanilla + /// `Parrot.mobInteract`. + async fn eat_cookie(&self, player: &Arc, item_stack: &mut ItemStack) { + item_stack.decrement_unless_creative(player.gamemode.load(), 1); + + self.mob_entity + .living_entity + .add_effect(pumpkin_data::potion::Effect { + effect_type: &StatusEffect::POISON, + duration: COOKIE_POISON_DURATION, + amplifier: 0, + ambient: false, + show_particles: true, + show_icon: true, + blend: true, + }) + .await; + + // Vanilla guards this call with `player.isCreative() || !this.isInvulnerable()`, + // but `hurt` re-checks invulnerability itself and `player_attack` doesn't bypass + // it, so the guard only skips a call that would do nothing anyway. + self.damage_with_context( + self, + f32::MAX, + DamageType::PLAYER_ATTACK, + None, + Some(player.as_ref()), + Some(player.as_ref()), + ) + .await; + } } impl NBTStorage for ParrotEntity {} @@ -50,4 +91,52 @@ impl Mob for ParrotEntity { fn get_mob_entity(&self) -> &MobEntity { &self.mob_entity } + + fn mob_interact<'a>( + &'a self, + player: &'a Arc, + item_stack: &'a mut ItemStack, + ) -> EntityBaseFuture<'a, bool> { + Box::pin(async move { + // Vanilla checks the poisonous food tag last, after taming, which isn't + // implemented yet. Nothing in `parrot_food` is also in + // `parrot_poisonous_food`, so the two branches can't be confused. + if !item_stack + .get_item() + .has_tag(&tag::Item::MINECRAFT_PARROT_POISONOUS_FOOD) + { + return self.mob_entity.mob_interact(player, item_stack).await; + } + + self.eat_cookie(player, item_stack).await; + true + }) + } +} + +#[cfg(test)] +mod tests { + use super::COOKIE_POISON_DURATION; + use pumpkin_data::item::Item; + use pumpkin_data::tag::{self, Taggable}; + + /// The interaction is gated on the vanilla `parrot_poisonous_food` tag rather than + /// on a hardcoded cookie id, so check the tag actually resolves the way the + /// interaction assumes. + #[test] + fn cookie_is_poisonous_parrot_food() { + assert!(Item::COOKIE.has_tag(&tag::Item::MINECRAFT_PARROT_POISONOUS_FOOD)); + } + + /// Seeds tame a parrot in vanilla and must not reach the poison branch. + #[test] + fn parrot_food_is_not_poisonous() { + assert!(!Item::WHEAT_SEEDS.has_tag(&tag::Item::MINECRAFT_PARROT_POISONOUS_FOOD)); + assert!(!Item::COOKED_CHICKEN.has_tag(&tag::Item::MINECRAFT_PARROT_POISONOUS_FOOD)); + } + + #[test] + fn poison_lasts_45_seconds() { + assert_eq!(COOKIE_POISON_DURATION, 900); + } }