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.
Minimal example
The plugin ships a single example blueprint, blueprints/simple.yml:
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 |
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 |
| One lore line per configured effect, using each effect'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 | Mob type to spawn, e.g. |
| boolean | Spawns the pet as a baby variant. Only honoured for entity types that resolve to |
| 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. |
Supported entity behaviors
Any Bukkit EntityType can be used as entity.type — unrecognised types fall back to the generic PetEntity packet implementation with no goal selector (it just follows its owner). The following types resolve to a dedicated PetEntity subclass with type-appropriate movement and rendering:
| Implementation |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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. |
| Component | Text shown for |
| Component | Text used to preview the next level's gain, with |
| Component | Optional. Overrides |
|
| 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 keyed by the exact level — a level with no explicit entry (and no prior entry carried forward into it) resolves to0. In practice blueprints define an entry at every level they want the display/scaling to reflect.
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 color associated with the rarity. |
| String (Crunch expression) | Formula, in terms of a single variable |
rarities.yml ships the five rarities above (common, uncommon, rare, epic, legendary) — none of them is named test, the rarity id referenced by the shipped simple.yml blueprint.
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 shipped simple.yml's 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:simple for the shipped example. 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. 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.