Skip to main content

steel_core/entity/entities/mobs/passive/chicken/
mod.rs

1//! Vanilla Chicken entity with variant + sound-variant parity, wing-flap
2//! slow-fall, and periodic egg-laying behavior.
3
4use std::str::FromStr;
5use std::sync::{Arc, Weak};
6
7use glam::DVec3;
8use simdnbt::borrow::NbtCompound as BorrowedNbtCompoundView;
9use simdnbt::owned::NbtCompound;
10use steel_macros::entity_behavior;
11use steel_registry::chicken_sound_variant::{ChickenAge, ChickenSoundVariantRef};
12use steel_registry::chicken_variant::ChickenVariantRef;
13use steel_registry::data_components::vanilla_components::{CHICKEN_SOUND_VARIANT, CHICKEN_VARIANT};
14use steel_registry::entity_type::{
15    EntityAttachmentPoint, EntityAttachments, EntityDimensions, EntityTypeRef,
16};
17use steel_registry::item_stack::ItemStack;
18use steel_registry::loot_table::{LootContext, LootTableRef};
19use steel_registry::sound_event::SoundEventRef;
20use steel_registry::vanilla_entity_data::ChickenEntityData;
21use steel_registry::vanilla_item_tags::ItemTag;
22use steel_registry::vanilla_loot_tables;
23use steel_registry::{
24    REGISTRY, RegistryExt, RegistryReference, TaggedRegistryExt, sound_events, vanilla_game_events,
25};
26use steel_utils::locks::SyncMutex;
27use steel_utils::random::legacy_random::LegacyRandom;
28use steel_utils::types::InteractionHand;
29use steel_utils::{BlockPos, BlockStateId, DowncastType, DowncastTypeKey, Identifier};
30
31use crate::behavior::InteractionResult;
32use crate::entity::ai::goal::{
33    BreedGoal, FloatGoal, FollowParentGoal, LookAtPlayerGoal, PanicGoal, RandomLookAroundGoal,
34    TemptGoal, WaterAvoidingRandomStrollGoal,
35};
36use crate::entity::ai::path::PathType;
37use crate::entity::damage::DamageSource;
38use crate::entity::{
39    AgeableMob, AgeableMobBase, Animal, AnimalBase, Entity, EntityBase, EntityBaseLoad, EntityPose,
40    EntitySpawnReason, EntitySyncedData, LivingEntity, LivingEntityBase, Mob, MobBase,
41    PathfinderMob, SpawnGroupData, entity_loot_ref, position_rider_default,
42};
43use crate::physics::MoveResult;
44use crate::player::Player;
45use crate::world::World;
46
47const CHICKEN_BABY_PASSENGER_ATTACHMENTS: [EntityAttachmentPoint; 1] =
48    [EntityAttachmentPoint::new(0.0, 0.375, 0.0)];
49const CHICKEN_BABY_WIDTH: f32 = 0.3;
50const CHICKEN_BABY_HEIGHT: f32 = 0.4;
51const CHICKEN_BABY_EYE_HEIGHT: f32 = 0.28125;
52
53const CHICKEN_BABY_DIMENSIONS: EntityDimensions = EntityDimensions::new_with_attachments(
54    CHICKEN_BABY_WIDTH,
55    CHICKEN_BABY_HEIGHT,
56    CHICKEN_BABY_EYE_HEIGHT,
57    EntityAttachments::new(&CHICKEN_BABY_PASSENGER_ATTACHMENTS, &[], &[], &[]),
58);
59
60/// Flap speed gained per tick while airborne (vanilla `Chicken.aiStep`).
61const FLAP_SPEED_AIR_GAIN: f32 = 4.0;
62/// Flap speed lost per tick while grounded (vanilla `Chicken.aiStep`).
63const FLAP_SPEED_GROUND_LOSS: f32 = 1.0;
64/// Scales the per-tick flap-speed adjustment (vanilla `Chicken.aiStep`).
65const FLAP_SPEED_ADJUST_SCALE: f32 = 0.3;
66const FLAP_SPEED_MIN: f32 = 0.0;
67const FLAP_SPEED_MAX: f32 = 1.0;
68/// Flapping strength restored whenever the chicken is airborne.
69const MIN_FLAPPING_STRENGTH: f32 = 1.0;
70/// Multiplicative flapping-strength decay per tick.
71const FLAPPING_STRENGTH_DECAY: f32 = 0.9;
72/// Scales accumulated flap rotation (vanilla `Chicken.aiStep`).
73const FLAP_ROTATION_SCALE: f32 = 2.0;
74/// Downward velocity multiplier while airborne, producing the chicken slow-fall.
75const FALL_DRAG_Y: f32 = 0.6;
76/// Divisor converting flap speed into the next flap distance threshold.
77const NEXT_FLAP_SPEED_DIVISOR: f32 = 2.0;
78
79/// Minimum ticks between egg lays (vanilla `nextInt(6000) + 6000`).
80const EGG_LAY_MIN_DELAY_TICKS: i32 = 6000;
81/// Range of the randomized egg-lay delay in ticks.
82const EGG_LAY_RANDOM_RANGE_TICKS: i32 = 6000;
83const EGG_LAY_SOUND_VOLUME: f32 = 1.0;
84const EGG_LAY_SOUND_BASE_PITCH: f32 = 1.0;
85/// Pitch jitter around the base egg-lay sound pitch.
86const EGG_LAY_SOUND_PITCH_JITTER: f32 = 0.2;
87/// Offset at which laid eggs spawn above the chicken's feet.
88const EGG_LAY_SPAWN_Y_OFFSET: f64 = 0.0;
89
90/// Experience rewarded for a chicken jockey (vanilla `Chicken.getBaseExperienceReward`).
91const CHICKEN_JOCKEY_EXPERIENCE_REWARD: i32 = 10;
92
93/// Runtime state unique to chickens: wing-flap animation, egg timer, and jockey flag.
94#[derive(Debug, Clone, Copy)]
95struct ChickenState {
96    flap: f32,
97    flap_speed: f32,
98    o_flap: f32,
99    o_flap_speed: f32,
100    flapping: f32,
101    next_flap: f32,
102    egg_time: i32,
103    is_chicken_jockey: bool,
104}
105
106impl ChickenState {
107    const fn new() -> Self {
108        Self {
109            flap: 0.0,
110            flap_speed: 0.0,
111            o_flap: 0.0,
112            o_flap_speed: 0.0,
113            flapping: 1.0,
114            next_flap: 1.0,
115            egg_time: 0,
116            is_chicken_jockey: false,
117        }
118    }
119}
120
121/// Vanilla chicken entity.
122#[entity_behavior(class = "Chicken")]
123pub struct ChickenEntity {
124    base: EntityBase,
125    entity_type: EntityTypeRef,
126    living_base: LivingEntityBase,
127    mob_base: MobBase,
128    ageable_base: AgeableMobBase,
129    animal_base: AnimalBase,
130    chicken_state: SyncMutex<ChickenState>,
131    entity_data: SyncMutex<ChickenEntityData>,
132}
133
134// SAFETY: This key is owned by Steel and uniquely identifies `ChickenEntity`.
135unsafe impl DowncastType for ChickenEntity {
136    const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:entity/chicken");
137}
138
139impl ChickenEntity {
140    /// Creates a new chicken at runtime.
141    #[must_use]
142    pub fn new(entity_type: EntityTypeRef, id: i32, position: DVec3, world: Weak<World>) -> Self {
143        Self::new_with_base(
144            EntityBase::new(id, position, entity_type.dimensions, world),
145            entity_type,
146        )
147    }
148
149    /// Reconstructs a chicken from persisted base entity state.
150    #[must_use]
151    pub fn from_saved(entity_type: EntityTypeRef, load: EntityBaseLoad) -> Self {
152        Self::new_with_base(
153            EntityBase::from_load(load, entity_type.dimensions),
154            entity_type,
155        )
156    }
157
158    fn new_with_base(base: EntityBase, entity_type: EntityTypeRef) -> Self {
159        let living_base = LivingEntityBase::new(entity_type);
160        let mob_base = MobBase::new();
161        let ageable_base = AgeableMobBase::new();
162        let animal_base = AnimalBase::new();
163        AnimalBase::initialize_pathfinding_malus(&mob_base);
164        // Chickens do not avoid water (`setPathfindingMalus(PathType.WATER, 0.0F)`).
165        mob_base
166            .pathfinding_malus()
167            .lock()
168            .set(PathType::Water, 0.0);
169        let mut chicken_state = ChickenState::new();
170        chicken_state.egg_time = Self::roll_egg_lay_time();
171        let mut entity_data = ChickenEntityData::new();
172        living_base.initialize_synced_data(&mut entity_data);
173
174        {
175            // Keep vanilla Chicken goal priorities and speeds in the same order.
176            let mut goal_selector = mob_base.goal_selector().lock();
177            goal_selector.add_goal(0, FloatGoal::new(&mob_base));
178            goal_selector.add_goal(1, PanicGoal::new(1.4));
179            goal_selector.add_goal(2, BreedGoal::new(1.0));
180            goal_selector.add_goal(
181                3,
182                TemptGoal::new(
183                    1.0,
184                    |item_stack| {
185                        REGISTRY
186                            .items
187                            .is_in_tag(item_stack.item(), &ItemTag::CHICKEN_FOOD)
188                    },
189                    false,
190                ),
191            );
192            goal_selector.add_goal(4, FollowParentGoal::new(1.1));
193            goal_selector.add_goal(5, WaterAvoidingRandomStrollGoal::new(1.0));
194            goal_selector.add_goal(6, LookAtPlayerGoal::new(6.0));
195            goal_selector.add_goal(7, RandomLookAroundGoal::new());
196        }
197
198        Self {
199            base,
200            entity_type,
201            living_base,
202            mob_base,
203            ageable_base,
204            animal_base,
205            chicken_state: SyncMutex::new(chicken_state),
206            entity_data: SyncMutex::new(entity_data),
207        }
208    }
209
210    /// Sets the active chicken variant by registry entry.
211    pub fn set_variant(&self, variant: ChickenVariantRef) {
212        self.entity_data
213            .lock()
214            .variant
215            .set(RegistryReference::new(variant));
216    }
217
218    /// Returns the active chicken variant, falling back to temperate when invalid.
219    #[must_use]
220    pub fn variant(&self) -> ChickenVariantRef {
221        self.entity_data.lock().variant.get().value()
222    }
223
224    /// Sets the active chicken sound variant by registry entry.
225    pub fn set_sound_variant(&self, sound_variant: ChickenSoundVariantRef) {
226        self.entity_data
227            .lock()
228            .sound_variant
229            .set(RegistryReference::new(sound_variant));
230    }
231
232    /// Returns the active chicken sound variant, falling back to classic when invalid.
233    #[must_use]
234    pub fn sound_variant(&self) -> ChickenSoundVariantRef {
235        self.entity_data.lock().sound_variant.get().value()
236    }
237
238    fn set_variant_by_key(&self, key: &Identifier) -> bool {
239        let Some(variant) = REGISTRY.chicken_variants.by_key(key) else {
240            return false;
241        };
242        self.set_variant(variant);
243        true
244    }
245
246    fn set_sound_variant_by_key(&self, key: &Identifier) {
247        if let Some(sound_variant) = REGISTRY.chicken_sound_variants.by_key(key) {
248            self.set_sound_variant(sound_variant);
249        }
250    }
251
252    /// Returns the sound set for this chicken's current age (vanilla `Chicken.getSoundSet`).
253    fn current_sound_set(&self) -> &'static ChickenAge {
254        let sound_variant = self.sound_variant();
255        if AgeableMob::is_baby(self) {
256            &sound_variant.baby_sounds
257        } else {
258            &sound_variant.adult_sounds
259        }
260    }
261
262    fn update_dirty_mob_effect_entity_data(&self) {
263        if !self.living_base.take_effects_dirty() {
264            return;
265        }
266
267        let display = self.living_base.mob_effect_display_state();
268
269        {
270            let mut entity_data = self.entity_data.lock();
271            let living = entity_data.living_entity_mut();
272            living.effect_particles.set(display.particles);
273            living.effect_ambience.set(display.ambient);
274        }
275
276        self.entity_data.set_base_invisible_flag(display.invisible);
277        self.entity_data
278            .set_base_glowing_flag(self.has_glowing_tag() || display.glowing);
279    }
280
281    /// Returns whether an item stack matches the vanilla chicken food tag.
282    #[must_use]
283    pub fn is_food(item_stack: &ItemStack) -> bool {
284        REGISTRY
285            .items
286            .is_in_tag(item_stack.item(), &ItemTag::CHICKEN_FOOD)
287    }
288
289    /// Returns vanilla `Chicken.isChickenJockey`.
290    #[must_use]
291    pub fn is_chicken_jockey(&self) -> bool {
292        self.chicken_state.lock().is_chicken_jockey
293    }
294
295    /// Sets vanilla `Chicken.isChickenJockey`.
296    pub fn set_chicken_jockey(&self, is_chicken_jockey: bool) {
297        self.chicken_state.lock().is_chicken_jockey = is_chicken_jockey;
298    }
299
300    /// Returns the remaining ticks before the next egg is laid.
301    fn egg_time(&self) -> i32 {
302        self.chicken_state.lock().egg_time
303    }
304
305    /// Sets the remaining ticks before the next egg is laid.
306    fn set_egg_time(&self, egg_time: i32) {
307        self.chicken_state.lock().egg_time = egg_time;
308    }
309
310    /// Rolls the vanilla randomized delay before the next egg lay.
311    fn roll_egg_lay_time() -> i32 {
312        EGG_LAY_MIN_DELAY_TICKS + rand::random_range(0..EGG_LAY_RANDOM_RANGE_TICKS)
313    }
314
315    /// Runs vanilla `Chicken.aiStep` wing-flap and slow-fall side effects.
316    fn tick_flapping(&self) {
317        let on_ground = self.on_ground();
318        let velocity = self.velocity();
319
320        {
321            let mut state = self.chicken_state.lock();
322            state.o_flap = state.flap;
323            state.o_flap_speed = state.flap_speed;
324            let adjustment = if on_ground {
325                -FLAP_SPEED_GROUND_LOSS
326            } else {
327                FLAP_SPEED_AIR_GAIN
328            };
329            state.flap_speed = (state.flap_speed + adjustment * FLAP_SPEED_ADJUST_SCALE)
330                .clamp(FLAP_SPEED_MIN, FLAP_SPEED_MAX);
331            if !on_ground && state.flapping < MIN_FLAPPING_STRENGTH {
332                state.flapping = MIN_FLAPPING_STRENGTH;
333            }
334            state.flapping *= FLAPPING_STRENGTH_DECAY;
335            state.flap += state.flapping * FLAP_ROTATION_SCALE;
336        }
337
338        if !on_ground && velocity.y < 0.0 {
339            self.set_velocity(DVec3::new(
340                velocity.x,
341                velocity.y * f64::from(FALL_DRAG_Y),
342                velocity.z,
343            ));
344        }
345    }
346
347    /// Runs vanilla `Chicken.aiStep` egg-laying side effects.
348    fn tick_egg_laying(&self) {
349        if self.level().is_none() || !Entity::is_alive(self) || AgeableMob::is_baby(self) {
350            return;
351        }
352
353        let should_lay_egg = {
354            let mut state = self.chicken_state.lock();
355            // Vanilla gates egg laying on `!isChickenJockey()`.
356            if state.is_chicken_jockey {
357                return;
358            }
359            state.egg_time -= 1;
360            state.egg_time <= 0
361        };
362
363        if should_lay_egg {
364            if self.drop_gift_loot_table(&vanilla_loot_tables::GAMEPLAY_CHICKEN_LAY) {
365                let pitch = EGG_LAY_SOUND_BASE_PITCH
366                    + (rand::random::<f32>() - rand::random::<f32>()) * EGG_LAY_SOUND_PITCH_JITTER;
367                self.play_sound(
368                    &sound_events::ENTITY_CHICKEN_EGG,
369                    EGG_LAY_SOUND_VOLUME,
370                    pitch,
371                );
372                self.game_event(&vanilla_game_events::ENTITY_PLACE);
373            }
374            self.set_egg_time(Self::roll_egg_lay_time());
375        }
376    }
377
378    /// Rolls a gift loot table at this entity's position and drops each result.
379    ///
380    /// Mirrors vanilla `LivingEntity.dropFromGiftLootTable` for the
381    /// `gameplay/chicken_lay` table, returning whether any item was dropped.
382    fn drop_gift_loot_table(&self, loot_table: LootTableRef) -> bool {
383        let position = self.position();
384        let mut rng = rand::rng();
385        // Vanilla `LootContextParamSets.GIFT` carries only ORIGIN and THIS_ENTITY.
386        let mut context = LootContext::new(&mut rng)
387            .with_origin(position.x, position.y, position.z)
388            .with_this_entity(entity_loot_ref(self));
389
390        let items = loot_table.get_random_items(&mut context);
391        let dropped_any = !items.is_empty();
392        for item in items {
393            self.spawn_at_location(item, EGG_LAY_SPAWN_Y_OFFSET);
394        }
395        dropped_any
396    }
397}
398
399impl Entity for ChickenEntity {
400    fn base(&self) -> &EntityBase {
401        &self.base
402    }
403
404    fn entity_type(&self) -> EntityTypeRef {
405        self.entity_type
406    }
407
408    fn apply_implicit_item_components(&self, item_stack: &ItemStack) {
409        if let Some(variant) = item_stack.get(CHICKEN_VARIANT) {
410            self.set_variant(variant.value());
411        }
412        if let Some(sound_variant) = item_stack.get(CHICKEN_SOUND_VARIANT) {
413            self.set_sound_variant(sound_variant.value());
414        }
415    }
416
417    fn base_tick(&self) {
418        Mob::base_tick_mob(self);
419    }
420
421    fn dimensions_for_pose(&self, _pose: EntityPose) -> EntityDimensions {
422        let scale = LivingEntity::get_scale(self);
423        if AgeableMob::is_baby(self) {
424            CHICKEN_BABY_DIMENSIONS.scale(scale)
425        } else if self.entity_type.fixed {
426            self.entity_type.dimensions
427        } else {
428            self.entity_type.dimensions.scale(scale)
429        }
430    }
431
432    fn synced_data(&self) -> Option<&dyn EntitySyncedData> {
433        Some(&self.entity_data)
434    }
435
436    fn update_data_before_sync(&self) {
437        self.update_dirty_mob_effect_entity_data();
438    }
439
440    fn play_step_sound(&self, _pos: BlockPos, _block_state: BlockStateId) {
441        self.play_sound(self.current_sound_set().step_sound, 0.15, 1.0);
442    }
443
444    fn is_flapping(&self) -> bool {
445        let fly_dist = self.base().movement_progress().fly_dist();
446        fly_dist > self.chicken_state.lock().next_flap
447    }
448
449    fn on_flap(&self) {
450        let fly_dist = self.base().movement_progress().fly_dist();
451        let mut state = self.chicken_state.lock();
452        state.next_flap = fly_dist + state.flap_speed / NEXT_FLAP_SPEED_DIVISOR;
453    }
454
455    fn position_rider(&self, passenger: &dyn Entity) {
456        position_rider_default(self, passenger);
457        if let Some(living) = passenger.as_living_entity() {
458            living.set_y_body_rot(self.y_body_rot());
459        }
460    }
461
462    fn save_additional(&self, nbt: &mut NbtCompound) {
463        self.save_mob(nbt);
464        self.save_ageable_mob(nbt);
465        self.save_animal(nbt);
466        nbt.insert("IsChickenJockey", i8::from(self.is_chicken_jockey()));
467        nbt.insert("EggLayTime", self.egg_time());
468        nbt.insert("variant", self.variant().key.to_string());
469        nbt.insert("sound_variant", self.sound_variant().key.to_string());
470    }
471
472    fn load_additional(&self, nbt: BorrowedNbtCompoundView<'_, '_>) {
473        self.load_mob(nbt);
474        self.load_ageable_mob(nbt);
475        self.load_animal(nbt);
476
477        self.set_chicken_jockey(nbt.byte("IsChickenJockey").is_some_and(|value| value != 0));
478        if let Some(egg_time) = nbt.int("EggLayTime") {
479            self.set_egg_time(egg_time);
480        }
481        if let Some(variant) = nbt.string("variant")
482            && let Ok(key) = Identifier::from_str(variant.to_str().as_ref())
483        {
484            self.set_variant_by_key(&key);
485        }
486        if let Some(sound_variant) = nbt.string("sound_variant")
487            && let Ok(key) = Identifier::from_str(sound_variant.to_str().as_ref())
488        {
489            self.set_sound_variant_by_key(&key);
490        }
491    }
492}
493
494impl LivingEntity for ChickenEntity {
495    fn living_base(&self) -> &LivingEntityBase {
496        &self.living_base
497    }
498
499    fn get_health(&self) -> f32 {
500        *self.entity_data.lock().living_entity().health.get()
501    }
502
503    fn set_health(&self, health: f32) {
504        let max_health = self.get_max_health();
505        let clamped = health.clamp(0.0, max_health);
506        self.entity_data
507            .lock()
508            .living_entity_mut()
509            .health
510            .set(clamped);
511    }
512
513    fn hurt_sound(&self, _source: &DamageSource) -> Option<SoundEventRef> {
514        Some(self.current_sound_set().hurt_sound)
515    }
516
517    fn death_sound(&self) -> Option<SoundEventRef> {
518        Some(self.current_sound_set().death_sound)
519    }
520
521    fn chicken_loot_variant(&self) -> Option<&Identifier> {
522        Some(&self.variant().key)
523    }
524
525    fn base_experience_reward(&self) -> i32 {
526        if self.is_chicken_jockey() {
527            CHICKEN_JOCKEY_EXPERIENCE_REWARD
528        } else {
529            Animal::base_experience_reward_animal(self)
530        }
531    }
532
533    fn server_ai_step(&self) {
534        Mob::mob_server_ai_step(self);
535    }
536
537    fn ai_step(&self) -> Option<MoveResult> {
538        let result = Mob::mob_ai_step(self);
539
540        AgeableMob::tick_ageable_mob(self);
541        Animal::tick_animal_love(self);
542        self.tick_flapping();
543        self.tick_egg_laying();
544        result
545    }
546}
547
548impl AgeableMob for ChickenEntity {
549    fn ageable_base(&self) -> &AgeableMobBase {
550        &self.ageable_base
551    }
552
553    fn is_age_locked(&self) -> bool {
554        *self.entity_data.lock().ageable_mob().age_locked.get()
555    }
556
557    fn set_age_locked(&self, age_locked: bool) {
558        self.entity_data
559            .lock()
560            .ageable_mob_mut()
561            .age_locked
562            .set(age_locked);
563    }
564
565    fn set_synced_baby(&self, baby: bool) {
566        self.entity_data.lock().ageable_mob_mut().baby.set(baby);
567    }
568
569    fn age_boundary_changed(&self, _baby: bool) {
570        self.refresh_dimensions();
571    }
572
573    fn breed_variant_key(&self) -> Option<&Identifier> {
574        Some(&self.variant().key)
575    }
576
577    fn set_breed_variant_key(&self, key: &Identifier) -> bool {
578        self.set_variant_by_key(key)
579    }
580
581    fn initialize_breed_offspring(&self, partner: &dyn AgeableMob, offspring: &dyn AgeableMob) {
582        let use_self_variant = rand::random::<bool>();
583        let variant_key = if use_self_variant {
584            self.breed_variant_key()
585        } else {
586            partner.breed_variant_key()
587        };
588        let Some(variant_key) = variant_key else {
589            return;
590        };
591
592        if !offspring.set_breed_variant_key(variant_key) {
593            log::error!("chicken offspring could not inherit breeding variant {variant_key}");
594        }
595    }
596}
597
598impl Animal for ChickenEntity {
599    fn animal_base(&self) -> &AnimalBase {
600        &self.animal_base
601    }
602
603    fn is_food(&self, item_stack: &ItemStack) -> bool {
604        ChickenEntity::is_food(item_stack)
605    }
606}
607
608impl Mob for ChickenEntity {
609    fn mob_base(&self) -> &MobBase {
610        &self.mob_base
611    }
612
613    fn tick_goal_selectors(&self) {
614        PathfinderMob::tick_pathfinder_goal_selectors(self);
615    }
616
617    fn tick_path_navigation(&self) {
618        PathfinderMob::tick_pathfinder_path_navigation(self);
619    }
620
621    fn custom_server_ai_step(&self) {
622        Animal::custom_server_ai_step_animal(self);
623    }
624
625    fn ambient_sound(&self) -> Option<SoundEventRef> {
626        Some(self.current_sound_set().ambient_sound)
627    }
628
629    fn remove_when_far_away(&self, _dist_sqr: f64) -> bool {
630        // Chicken jockeys persist like their rider (vanilla `Chicken.removeWhenFarAway`).
631        self.is_chicken_jockey()
632    }
633
634    fn finalize_spawn(
635        &self,
636        world: &Arc<World>,
637        spawn_reason: EntitySpawnReason,
638        group_data: Option<SpawnGroupData>,
639    ) -> Option<SpawnGroupData> {
640        let biome = world.biome_at(self.block_position());
641        // Mirrors the cow/pig convention.
642        let (variant, sound_variant) = {
643            let mut random = LegacyRandom::from_seed(rand::random());
644            let variant = biome.and_then(|biome| {
645                REGISTRY
646                    .chicken_variants
647                    .select_spawn_variant(biome, &mut random)
648            });
649            let sound_variant = REGISTRY.chicken_sound_variants.pick_random(&mut random);
650            (variant, sound_variant)
651        };
652
653        if let Some(variant) = variant {
654            self.set_variant(variant);
655        }
656
657        if let Some(sound_variant) = sound_variant {
658            self.set_sound_variant(sound_variant);
659        }
660
661        self.finalize_spawn_ageable_mob(world, spawn_reason, group_data)
662    }
663
664    fn mob_interact(&self, player: &Player, hand: InteractionHand) -> InteractionResult {
665        Animal::mob_interact_animal(self, player, hand)
666    }
667
668    fn mob_flags(&self) -> i8 {
669        *self.entity_data.lock().mob().mob_flags.get()
670    }
671
672    fn set_mob_flags(&self, flags: i8) {
673        self.entity_data.lock().mob_mut().mob_flags.set(flags);
674    }
675}
676
677impl PathfinderMob for ChickenEntity {}
678
679#[cfg(test)]
680mod tests;