mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
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.
This commit is contained in:
@@ -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: <https://minecraft.wiki/w/Parrot>
|
||||
@@ -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<Player>, 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<Player>,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user