Pet Effects
Effects are the passive bonuses a pet blueprint grants its owner. Each blueprint carries an effects: map — <effect-id>: { type: "...", ... } — deserialized entry-by-entry into a WrappedPetEffect by WrappedPetEffectTypeSerializer. The type key selects a PetEffect implementation by its registered string id in PetEffectsRegistry; everything else in the entry deserializes straight into that implementation's own @ConfigSerializable record.
Pet#applyEffect/Pet#removeEffect iterate a pet's blueprint().effects() and call PetEffect#apply/#remove on each wrapped effect. This runs on PetEquipEvent/PetUnequipEvent, on PlayerDataLoadedEvent, and after every level-up — see Passive effects. Some effect types (money, storage, rideable, no-fall) have no-op apply/remove bodies and are instead read directly off the blueprint at the point of use — each per-effect section below notes which.
Common config keys
Key | Type | Description |
|---|---|---|
| String | The registered effect id (see the summary table below). Selects which |
| Component (MiniMessage) | Rendered as one line of |
| Component (MiniMessage) | Rendered by |
| Component (MiniMessage) | Optional. Used instead of |
|
| Level → incremental amount granted at that level (not a running total). |
WrappedPetEffectTypeSerializer builds WrappedPetEffect#cumulativeValues by walking values in level order and summing as it goes, so the wrapper's own totalValue(level) (used by %value%/%maxValue% and by PetBlueprint#getEffectLevel) is always a running total. Independently, every PetEffect implementation exposes the same arithmetic itself through the interface default value(level) (values().entrySet().stream().filter(key <= level).sum()), which is what implementations call from their own apply(). maxLevel is the highest key present in values; maxValue is the cumulative total at that key.
Effects run asynchronously by default (PetEffect#async() defaults to true, so Pet#applyEffect/#removeEffect dispatch them via Bukkit.getScheduler().runTaskAsynchronously) — potion is the only implementation that overrides this to false, since Player#addPotionEffect/#removePotionEffect must run on the main thread.
Summary
Registered id | Class | Target system | Async |
|---|---|---|---|
| AstralStats | Yes | |
| Core economy ( | Yes (no-op | |
| Bukkit | No | |
| Pet's own | Yes (no-op | |
| Pet mounting ( | Yes (no-op | |
| Fall damage ( | Yes (no-op | |
| AstralShop ( | Yes | |
| AstralJobs ( | Yes | |
| AstralRotatingShop ( | Yes |
stats
Adds an AstralStats StatModifier to the owner for as long as the pet is equipped. StatPetEffect.apply() calls StatsAPI.addModifier(player, stat, new StatModifier(key, stat, ModifierSource.OTHER, EquipmentSlot.SADDLE, value(level), modifierType)); remove() calls StatsAPI.removeModifier. The modifier's key is modifier-key suffixed with the pet's UUID (PetUtils#modifyKey), so multiple pets — even of the same blueprint — never collide on the same modifier key.
Field | Type | Description |
|---|---|---|
| Key | AstralStats stat key the modifier applies to (e.g. |
| Key | Base key for the registered |
|
|
|
|
| Level → incremental modifier amount. |
money
Periodic passive income. MoneyPetEffect.apply()/remove() are no-ops — the payout is driven entirely by EconomyEffectTask, an async repeating task (runTaskTimerAsynchronously, 10s initial delay / 30s period) registered in AstralPets#onEnable. Every run, for every online player's active pets, it finds each MoneyPetEffect on the blueprint and pays out when all of the following hold:
moneyEffect.value(pet.level())(the cumulative amount) is non-zero.pet.equippedSince()is non-zero (the pet is actually equipped).at least
intervalhas elapsed since bothequippedSinceand the pet'slastMoneyGainTime.
Payouts from every eligible pet on a player are summed into one economyService.deposit(...) call, then each contributing pet's individual %amount% is reported separately via the pet-generated-money message (%pet_name% + %amount%, see messages.yml), and that pet's lastMoneyGainTime is updated.
Field | Type | Description |
|---|---|---|
|
| Level → incremental currency amount paid per interval. |
| Duration | Minimum time between payouts for this effect, parsed by |
potion
Applies an infinite-duration Bukkit PotionEffect to the owner while equipped. PotionPetEffect.apply() computes amplifier = (int) value(level) and, if greater than 0, calls effect.createEffect(PotionEffect.INFINITE_DURATION, amplifier - 1) — so values should be keyed with the potion level (1 = the vanilla "I" tier), not the raw Bukkit amplifier; the effect subtracts 1 internally. remove() calls player.removePotionEffect(effect). Runs synchronously (async() returns false).
Field | Type | Description |
|---|---|---|
|
| Registry key from |
|
| Level → incremental potion-level amount; cumulative total minus 1 is the applied amplifier. |
storage
Sizes the pet's own PetInventory. StoragePetEffect.apply()/remove() are no-ops — the size isn't tied to the equip lifecycle at all. Pet#build() sets the initial size from getEffectLevel(StoragePetEffect.class, 1), and Pet#handleLevelUps() recomputes it (getEffectLevel(StoragePetEffect.class, level)) after every level-up, resizing the backing array if it changed. Because of this, storage size (and therefore whether [open-pet-container] opens anything) depends only on the pet's own level, not on whether it's currently active/equipped.
Field | Type | Description |
|---|---|---|
|
| Level → incremental inventory slots. Cumulative total is the pet's current storage size. |
rideable
Gates whether the pet can be mounted. RideablePetEffect.apply()/remove() are no-ops; EntityService#ride, PigBaseEntity, and ParrotBaseEntity each check blueprint().getEffectLevel(RideablePetEffect.class, pet.level()) >= 1 directly. Riding additionally requires the blueprint's own entity.rideable: true — the effect only unlocks it once the pet's level also grants a cumulative value of at least 1; below that, [ride-pet]/ interacting with the spawned pet reports pet-cannot-be-rode with the level at which riding unlocks (PetBlueprint#levelUntilValue).
Field | Type | Description |
|---|---|---|
|
| Level → incremental value; riding unlocks once the cumulative total reaches |
no-fall
Cancels fall damage for the owner. NoFallPetEffect.apply()/remove() are no-ops; EffectListener's EntityDamageEvent handler checks, for every one of the player's active pets, blueprint().getEffectLevel(NoFallPetEffect.class, pet.level()) > 0 and cancels the event if any active pet qualifies — the pet does not need to be spawned, only equipped.
Field | Type | Description |
|---|---|---|
|
| Level → incremental value; fall damage is cancelled once the cumulative total is greater than |
shop-modifier
Adds an AstralShop price ShopModifier for the owner. ShopModifierPetEffect.apply() divides the cumulative value by 100 and registers an item-scoped ShopItemModifier (if item-id is set), a category-scoped ShopCategoryModifier (if category-id is set instead), or a global ShopModifier otherwise — via ShopAPI.addModifier. The registered key is key suffixed with the pet's UUID and, when present, the item-id/category-id (PetUtils#modifyKey).
Field | Type | Description |
|---|---|---|
| Key | Base key for the registered |
|
|
|
|
|
|
|
| Level → incremental amount; applied as |
| String? | Optional. Scopes the modifier to one shop item instead of the whole shop. |
| String? | Optional (ignored if |
jobs-modifier
Adds an AstralJobs JobModifier for the owner. JobModifierPetEffect.apply() registers the modifier via JobsAPI.addModifier using the raw cumulative value (unlike shop-modifier/rotating-shop-modifier, it is not divided by 100). The registered key is modifier-key suffixed with the pet's UUID and the modifier scope name.
Field | Type | Description |
|---|---|---|
| Key | Base key for the registered |
|
|
|
|
|
|
| String? | Optional. Scopes the modifier to one job; omitted/ |
|
| Level → incremental amount, used as-is (not divided by |
rotating-shop-modifier
Adds an AstralRotatingShop price modifier for the owner. RotatingShopModifierPetEffect.apply() calls RotatingShopAPI.addModifier(playerId, key, value(level) / 100); remove() calls RotatingShopAPI.removeModifier. The registered key is modifier-key suffixed with the pet's UUID.
Field | Type | Description |
|---|---|---|
| Key | Base key for the registered rotating-shop modifier. |
|
| Level → incremental amount; applied as |
See Pet Blueprints for the surrounding blueprint shape, Passive effects for the equip/unequip lifecycle that drives most of these, and Placeholders for how display/next-level-display/display-stats-max render inside %pet_effects%/%pet_nextLevelEffects%.