Pet Blueprints
A blueprint defines one pet species: its display item, the entity it spawns as, its rarity, and the effects that scale with level. Blueprints live in plugins/AstralPets/blueprints/*.yml (sub-folders are scanned recursively) and are loaded by BlueprintService — each file deserialises into exactly one PetBlueprint.
Example
A shipped blueprint, blueprints/epic/moohhh.yml:
Note what the blueprint no longer carries: the lore calls transformers instead of spelling out how each effect reads, and each effect declares a label and a unit instead of three display strings.
Top-level fields
Field | Type | Required | Description |
|---|---|---|---|
| String | Yes | Blueprint identifier. If two files resolve to the same |
| Component (MiniMessage) | Yes | Name given to a newly-created |
| String | Yes | Id of a rarity defined in |
| int | No | Highest level the pet can reach. Defaults to |
| ItemStackWrapper | Yes | The pet's inventory/menu representation (see |
| EntityBlueprint | Yes | The world entity spawned when the pet is summoned (see |
|
| No | Named effect entries that scale with level (see |
|
| No | Ids of the skins a pet of this blueprint may wear. A skin outside this list can never be applied. Ids that no |
If id, default-name, item, entity, or rarity is missing, PetBlueprintTypeSerializer throws and the file fails to load (BlueprintService logs the error and continues with the remaining files).
item
item is a standard AstralCore ItemStackWrapper — the same template used by menu items, so material, name, lore, enchantments, item-flags, amount, and item components all work the same way. See Menu Items for the full field reference.
The name and lore are placeholder-aware and are re-rendered against the specific Pet instance every time the item is built (PetService.buildItemStack). In addition to the general AstralCore placeholders, the pet placeholders are available directly (unprefixed, since the item template is resolved with the pet registered as the current placeholder context):
Placeholder | Resolves to |
|---|---|
| The pet's current (possibly player-renamed) display name. |
| The pet's current level. |
| Experience accumulated toward the next level (resets to |
| Every effect bound to this pet's level, as a provider — iterate it with a |
| A transformer from the plugin's |
The built ItemStack carries the full Pet (id, name, blueprint, experience, level, inventory) serialised into its PDC, so a live pet item always reflects that specific pet's state — not just the blueprint template.
entity
entity (PetBlueprint.EntityBlueprint) describes the world entity spawned when the pet is summoned.
Field | Type | Description |
|---|---|---|
| Bukkit | The type the pet is rendered as, e.g. |
| String | Id of the AstralMobs blueprint the pet spawns as: its goals ( |
| boolean | Spawns the pet as a baby variant, where the rendered |
| boolean | Whether the entity can be mounted at all. This is only half the gate: the pet also needs its |
| Component (MiniMessage) | First line of the floating nametag rendered above the entity. Supports |
| Component (MiniMessage) | Second line of the floating nametag, rendered below |
|
| Namespaced Bukkit attribute keys (e.g. |
Where behaviour comes from
A pet is spawned through AstralMobs (MobsAPI.spawnOwnedMob), so what it does — how it follows its owner, how it moves, which idle animations it plays, what it is rendered as — is configured in its AstralMobs blueprint, not here. The old per-type PetEntity subclasses are no longer used: EntityFactory builds one PetEntity for every pet whatever its type.
What the pet blueprint still decides is the pet-domain layer on top: the rendered type used to size the spawn spot, the two-line nametag (name/sub-name), whether riding is possible at all (rideable), and attribute overrides.
Attributes are applied in layers, each winning over the last:
the AstralMobs blueprint's own,
this blueprint's
entity.attributes,the pet's skin, applied last.
effects
Each entry under effects is a WrappedPetEffect — it wraps one of the registered effect implementations (type) with the display text and per-level value scaling shared by every effect. Available types: stats, money, potion, storage, rideable, no-fall, shop-modifier, jobs-modifier, rotating-shop-modifier. Field-by-field behavior of each type is documented on Pet Effects; the shared shape every entry accepts is:
Field | Type | Description |
|---|---|---|
| String | The effect implementation id. Unknown types fail blueprint loading — except for the three hook-backed types, which are skipped with a warning when their plugin is absent. |
| Component | What the effect is called to a player ("Vente Lianes"). The one piece of display text a blueprint still owns. |
| String | How its value reads — |
|
| Per-level value map: key is the pet level, value is the amount added at that level. Either key name is accepted — |
…effect-specific fields | — | e.g. |
How the value map drives scaling
values/modifier-values entries do not need one per level — but the two consumers of the map read it differently:
The value an effect actually applies (e.g. the stat modifier
StatPetEffectgrants) sums every entry whose key is at or below the pet's current level, so it's safe to leave gaps between levels.Displayed totals, "is maxed" checks, and the
storageeffect's inventory-size lookup read a precomputed running total, looked up by the highest level at or below the one asked for. Gaps are therefore fine here too: an effect that gains at level 5 and next gains at 10 reports the level-5 total for levels 6 through 9. The table is stored sorted, so a blueprint may list its levels out of order.
The first level whose entry is non-zero is the effect's unlock level, and the highest key present is its maxLevel (the cumulative total there being its maxValue) — both are what a display line asks about rather than deriving.
The storage effect type is special-cased: PetBlueprint.build() reads its value at level 1 to size the pet's initial storage inventory, and Pet.handleLevelUps() re-reads it at the pet's new level after every level-up to resize the inventory in place.
Rarities
Rarities are shared across all blueprints and defined once in rarities.yml (not per-file), loaded into a RaritiesConfiguration at plugin startup/reload, before blueprints are loaded:
Field | Type | Description |
|---|---|---|
| String | Identifier blueprints reference via their own |
| Component (MiniMessage) | Rarity label, e.g. used in menus/lore. |
|
| Hex or named colour the rarity's name is written in. |
|
| Optional. The lighter tone its detail text is written in — effect lines, lore headers. Read as |
| String (Crunch expression) | Formula, in terms of a single variable |
rarities.yml ships the five rarities above (common, uncommon, rare, epic, legendary), which are the ids the shipped blueprints reference.
How leveling works
Pet.experienceForLevel(level) compiles the pet's rarity level-expression with Crunch (variable level) and evaluates it for the level being levelled into. That result is the experience required to go from level - 1 to level — not a cumulative lifetime total. PetService.gainExperience adds incoming experience to pet.experience(), then loops: while the accumulated amount is at least experienceForLevel(pet.level() + 1), it subtracts that requirement, fires a PetLevelUpEvent, and increments the pet's level (stopping at max-level, where any surplus experience is discarded). %pet_experience% and %pet_nextLevelExperience% (see Placeholders) reflect this same per-level counter.
Using the rare rarity as an example — 36+(level-1)^2 — reaching level 2 costs 36 + (2-1)^2 = 37 XP, reaching level 3 costs 36 + (3-1)^2 = 40 XP, and so on; each figure is the cost of that single level, not a running total.
Item suppliers
AstralPets registers a pets ItemStackSupplier (PetsItemSupplier) with AstralPaperAPI at startup. Anything elsewhere in AstralCore/AstralItems that accepts a generic supplier-backed item key (menus, shops, kits, /items give-style commands) can reference a pet blueprint as pets:<blueprint-id> — e.g. pets:moohhh. Two sibling suppliers are registered the same way: pets.food:<food-id> for food and pets.skins:<skin-id> for skins. Resolving that key builds a fresh level-1 pet from the blueprint (a new UUID each time) via PetService.buildItemStack(PetBlueprint), distinct from /pets give, which uses the same builder directly rather than going through the supplier lookup. The supplier's completion list is rebuilt from BlueprintService.ids() on every reload, and PetsItemSupplier.keyOf(ItemStack) reverses an existing pet item back to its blueprint's key by reading the Pet stored in its PDC.
Hot reload
/pets reload (pets.reload) calls AstralPets.loadConfiguration(), which reloads rarities.yml before BlueprintService.load() re-reads every file under blueprints/ — so a blueprint referencing a rarity added in the same reload will resolve correctly. Skins are loaded just before the blueprints, and cross-checked against every blueprint's skins list just after. The blueprint registry and the max-level preview cache (BlueprintService.cachedPets(), one synthetic max-level Pet per blueprint, exposed as %pets_blueprints% for menus that list every blueprint without requiring a player to own one) are cleared and rebuilt from scratch, and PetsItemSupplier/PetsFoodItemSupplier completions are refreshed immediately after.