Pet Food
Food items grant a pet experience when fed to it. Each food item is defined by a food blueprint — an id, an ItemStackWrapper template for the physical item, and an experience amount.
Food blueprints live in food/*.yml in the plugin's data folder (sub-folders are not required — the loader walks the whole food/ directory) and are loaded by FoodBlueprintService#load(), which is called from AstralPets#loadConfiguration() (i.e. on boot and on /pets reload, see Commands). Every .yml/.yaml file under food/ is parsed as one FoodBlueprint; the id used everywhere else (commands, the item supplier, tab completion) is the blueprint's own id field, not the file name. AstralPets ships one example blueprint at src/main/resources/food/apple.yml.
Minimal example
Top-level fields
Field | Type | Required | Description |
|---|---|---|---|
| String | Yes | Food blueprint identifier. Looked up by |
|
| Yes | Standard AstralCore item template — |
| double | Yes | XP granted to the pet per unit consumed. |
FoodBlueprint also implements ComplexPlaceholder under the food namespace, chaining id, item (→ an item placeholder, e.g. %food_item_name%), and experience. This is what resolves %food_item_name% in the give-food-success/give-food-failed messages (see Commands).
item
A standard ItemStackWrapper. FoodBlueprintService#buildItemStack(FoodBlueprint) resolves it against the root placeholder container and stamps the resulting stack with a food_blueprint_id persistent-data string (the blueprint's id) — this tag is what marks a stack as pet food everywhere else in the plugin (FoodBlueprintService#isFoodItem/#fromItemStack).
Feeding a pet
Feeding is triggered by right-clicking a spawned pet entity while holding a food item in the main hand — not by eating the item. PetInteractListener (a packet listener) reads the client's interact-entity packet, applies a 150ms per-player cooldown, and — for a main-hand interact that isn't an attack — fires PetInteractEvent(player, pet, entity).
EntityListener#onEntityInteract (priority LOW) handles that event:
Resolves the item in the player's main hand through
foodBlueprints().fromItemStack(itemStack).If it is a food item:
If the pet is already at
blueprint().maxLevel(), sendspet-max-leveland stops — no item is consumed and the menu does not open.Otherwise, the amount consumed is the player's whole stack while sneaking, or 1 while not sneaking — capped so the resulting XP never overshoots the amount needed to reach the pet's max level (
amount = ceil(experienceToMax / foodBlueprint.experience())when the uncapped total would exceed it).The consumed amount is removed from the main-hand stack.
PetService#gainExperience(player, pet, entity, foodBlueprint.experience() * amount)is called, and 5HEARTparticles spawn at the pet's location.
If the held item is not food, the interact instead opens the pet menu (
menus().openPetMenu(player, pet)) — right-clicking an empty-handed pet is how its menu is opened.
PetService#gainExperience:
Fires
PetGainExperienceEvent(pet, experienceGained, pet.experience() + experienceGained). It isn't Bukkit-Cancellable, but a listener can zero out (or otherwise adjust)experienceGainedvia its setter; if the value is<= 0afterward, no XP is applied.If the pet is already at max level, sends
pet-max-leveland returns.Otherwise loops: while there's enough accumulated experience to clear the next level's threshold (
pet.experienceForLevel(nextLevel)) and the pet isn't atmaxLevelyet, it firesPetLevelUpEvent(pet, oldLevel, newLevel)(also notCancellable, butnewLevelcan be rewritten by a listener), applies the level, and sendspet-level-up.At max level, experience is reset to
0. The pet's effects are removed and reapplied (see Pet Effects), and the entity's nametag is refreshed.Displays a floating experience indicator: a short-lived, player-only
PacketTextDisplayspawned just in front of the player's view, renderingexperience-gain-indicator("%experience%/%experienceToNextLevel% XP") and auto-removed ~700ms later by a repeating task.
Consuming food items directly
FoodListener#onItemConsume listens for the vanilla PlayerItemConsumeEvent and cancels it whenever foodBlueprints().isFoodItem(event.getItem()) is true. Since food templates are typically built on edible vanilla materials (apple.yml uses apple), this stops a player from just eating the item for its normal hunger/saturation effect — it must be fed to a pet through the interact flow above to have any effect.
Food item supplier
PetsFoodItemSupplier is registered as an ItemStackSupplier under the namespace pets.food (AstralPaperAPI.registerItemStackSupplier("pets.food", this.foodItemSupplier)), alongside the vanilla/hdb/ce suppliers described in ItemStackSupplier. Its completions() set is rebuilt on reload from every loaded food id as Key.key("pets.food", id) — i.e. pets.food:<foodId> (pets.food:apple for the shipped example). get(String id) and get(Player, Key, int amount) both resolve through FoodBlueprintService#findById → #buildItemStack, and keyOf(ItemStack) reverse-looks-up a stack's food id via its persistent-data tag. This is what lets other systems reference a food blueprint as an item source (e.g. pets.food-apple as an ItemStackWrapper material, or through /give), the same way pets:<blueprintId> exposes pet-spawn items under the pets namespace.
Giving food
Permission: pets.give.food. Builds the food item via FoodBlueprintService#buildItemStack(FoodBlueprint) and delivers it through AstralCore's mailbox (MailboxService#giveOrAdd) so it reaches the target even without free inventory space. Reports give-food-success/give-food-failed to the sender. <blueprint> is resolved by FoodBlueprintContextResolver against foodBlueprints().findById(id), with @foodBlueprints tab-completing every loaded food id. See Commands for the full command reference.
Food conversion configuration (currently unused)
config.yml still ships a food-conversion.original-item key, and PetsMessages still declares NOTHING_TO_CONVERT/CONVERSION_SUCCESS (nothing-to-convert/conversion-success in messages.yml):
These are leftovers from a ConvertPetFoodAction menu action (mapped custom items in a player's inventory to food blueprints and converted them in place) that was removed in a later refactor along with its foodConversion() accessor on PetsConfiguration — the record no longer has a field for this key, so it is silently ignored by the config loader, and nothing in the current plugin reads food-conversion or fires either message. Treat both as dead configuration unless the action is reintroduced.