Skip to content

feat(events): fire Bukkit EntityBreedEvent for animal breeding (1.21.11) - #577

Open
KostiaFed wants to merge 1 commit into
CardboardPowered:ver/1.21.11from
KostiaFed:feat/entity-breed-event-1.21.11
Open

feat(events): fire Bukkit EntityBreedEvent for animal breeding (1.21.11)#577
KostiaFed wants to merge 1 commit into
CardboardPowered:ver/1.21.11from
KostiaFed:feat/entity-breed-event-1.21.11

Conversation

@KostiaFed

@KostiaFed KostiaFed commented Aug 6, 2026

Copy link
Copy Markdown

Backport of #576 to ver/1.21.11.

Problem

CraftEventFactory.callEntityBreedEvent(...) already exists on this branch, but its only caller is VillagerMakeLoveMixin. No vanilla animal ever produces a Bukkit EntityBreedEvent, so any plugin that tracks breeding (AdvancedAchievements was the reported case, but this affects every such plugin) silently sees nothing when cats, sheep, cows, … reproduce.

Where the hook goes and why

Breeding funnels through Animal#spawnChildFromBreeding:

public void spawnChildFromBreeding(ServerLevel level, Animal partner) {
    AgeableMob offspring = this.getBreedOffspring(level, partner);
    if (offspring == null) return;
    offspring.setBaby(true);
    offspring.snapTo(this.getX(), this.getY(), this.getZ(), 0.0f, 0.0f);
    this.finalizeSpawnChildFromBreeding(level, partner, offspring); // <-- event fires here
    level.addFreshEntityWithPassengers(offspring);
}

Injecting right before finalizeSpawnChildFromBreeding is the only point where the child already exists but nothing is committed yet — parents are still in love, ages are not reset, no experience has dropped, and the child is not in the world. Cancelling there leaves the world exactly as it was. This mirrors where CraftBukkit patches the same method.

Callers covered: BreedGoal#breed() and the brain-based AnimalMakeLove, i.e. every animal that produces a child entity.

Why foxes get their own mixin

Fox$FoxBreedGoal overrides BreedGoal#breed() and never calls spawnChildFromBreeding — it creates the kit itself so it can copy both parents' trusted players. Without a dedicated hook, foxes would be the one vanilla mob that spawns a baby without an EntityBreedEvent.

Frog, Sniffer and Turtle are deliberately left alone: they lay an egg and pass offspring == null, so there is no child to report — same as Bukkit/Paper.

Event semantics

  • mother / father / childthis, the partner, and the freshly created offspring.
  • breedergetLoveCause() of the mother, falling back to the father's; the same source vanilla uses to award the ANIMALS_BRED stat. Breeding with no player involved fires the event with a null breeder rather than skipping it.
  • breedingItem — mirrors CraftBukkit's Animal#breedItem, captured in mobInteract just before the food is consumed and exposed to the fox goal through the new AnimalBridge. May be null, which the Bukkit API allows.
  • experience — computed before the event, offered to plugins, and honoured when the orb is spawned; setExperience(0) drops no orb at all. Still gated on the vanilla MOB_DROPS game rule.
  • cancelled — no child, no age/love reset, no experience. Parents stay in love, so the goal retries, matching CraftBukkit.

Relation to the 26.1 change

This is the same patch as #576, applied unchanged. The vanilla breeding code is identical between 1.21.11 and 26.1.2 — same packages (animal.fox.Fox$FoxBreedGoal, animal.feline.Cat, animal.sheep.Sheep), same method descriptors, and the same bytecode offsets in spawnChildFromBreeding (0→47), finalizeSpawnChildFromBreeding (0→113), Animal#mobInteract (the two usePlayerItem calls at 49 and 77) and Fox$FoxBreedGoal#breed. Every @At target and ordinal therefore applies verbatim; the cherry-pick was conflict-free.

Testing

The behaviour was verified on a live Fabric 26.1.2 server (loader 0.19.2, fabric-api 0.149.1) with a probe plugin listening on EntityBreedEvent:

Scenario Result
Sheep child=SHEEP mother=SHEEP father=SHEEP breeder=null exp=3
Ocelot (same BreedGoal path as cats) child=OCELOT … exp=1
Fox (dedicated goal) child=FOX … exp=5
Pigs, listener cancels event fires, no child, no orb
Cows, setExperience(0) event fires, child spawns, no orb
Chickens, setExperience(37) orb with value 37
Server startup no InvalidMixinException, clean boot

Not exercised headlessly: the non-null breeder / bredWith path, which needs a real player feeding the animals. A 1.21.11 build and in-game run are still to be done on this branch — happy to hold the PR until someone confirms.

Cardboard already had CraftEventFactory.callEntityBreedEvent, but the only
caller was VillagerMakeLoveMixin, so no vanilla animal ever produced a Bukkit
EntityBreedEvent. Plugins that track breeding (AdvancedAchievements and
friends) therefore never saw cats, sheep or anything else reproduce.

Hook Animal#spawnChildFromBreeding right before finalizeSpawnChildFromBreeding:
the child exists there, but the parents' love/age are not reset yet, no
experience has dropped and the child is not in the world, so cancelling the
event leaves the world untouched. This is the same point CraftBukkit patches.

Fox breeding needs its own hook because Fox$FoxBreedGoal overrides
BreedGoal#breed() and spawns the kit itself (to copy trusted players) instead
of calling spawnChildFromBreeding. Frog, Sniffer and Turtle are deliberately
left alone: they lay an egg and never create a child entity, matching
Bukkit/Paper behaviour.

- mother/father/child/breeder are taken from vanilla's own love-cause chain
- breedingItem mirrors CraftBukkit's Animal#breedItem, captured in mobInteract
  and exposed through the new AnimalBridge (may be null, which Bukkit allows)
- experience is offered to the event and honoured afterwards; 0 drops no orb
- breeding without a player yields a null breeder instead of skipping the event

Backport of the ver/26.1 change. The vanilla breeding code is identical in
1.21.11 and 26.1.2 down to the bytecode offsets of spawnChildFromBreeding,
finalizeSpawnChildFromBreeding, Animal#mobInteract and Fox$FoxBreedGoal#breed,
so every injection point applies unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant