Astral Realms Documentation Help

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

type

String

The registered effect id (see the summary table below). Selects which PetEffect class deserializes the rest of the entry.

display

Component (MiniMessage)

Rendered as one line of %pet_effects% at the pet's current level. Supports %value%/%maxValue% — see Effect display sub-keys.

next-level-display

Component (MiniMessage)

Rendered by %pet_nextLevelEffects% for the upcoming level; a single %value% bound to the incremental value for level + 1.

display-stats-max

Component (MiniMessage)

Optional. Used instead of display once WrappedPetEffect#isMaxed(level) is true for the pet's current level.

values

Map<Integer, Double>

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

stats

StatPetEffect

AstralStats

Yes

money

MoneyPetEffect

Core economy (EconomyService)

Yes (no-op apply/remove)

potion

PotionPetEffect

Bukkit PotionEffect

No

storage

StoragePetEffect

Pet's own PetInventory

Yes (no-op apply/remove)

rideable

RideablePetEffect

Pet mounting (EntityService)

Yes (no-op apply/remove)

no-fall

NoFallPetEffect

Fall damage (EffectListener)

Yes (no-op apply/remove)

shop-modifier

ShopModifierPetEffect

AstralShop (ShopAPI)

Yes

jobs-modifier

JobModifierPetEffect

AstralJobs (JobsAPI)

Yes

rotating-shop-modifier

RotatingShopModifierPetEffect

AstralRotatingShop (RotatingShopAPI)

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.

effects: example-stats: type: "stats" display: "%value% Stats" next-level-display: "+%value% Stats" stat: "astralrealms:speed" modifier-key: "astralpets:speed_boost" modifier-type: "PERCENTAGE" modifier-values: 1: 10 2: 20 3: 30

Field

Type

Description

stat

Key

AstralStats stat key the modifier applies to (e.g. astralrealms:speed).

modifier-key

Key

Base key for the registered StatModifier; suffixed per-pet as above.

modifier-type

ModifierType

RELATIVE, FLAT, or PERCENTAGE.

values

Map<Integer, Double>

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 interval has elapsed since both equippedSince and the pet's lastMoneyGainTime.

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.

effects: passive-income: type: "money" display: "$%value% / 30m" next-level-display: "+$%value% / 30m" values: 1: 50 10: 25 interval: "30m"

Field

Type

Description

values

Map<Integer, Double>

Level → incremental currency amount paid per interval.

interval

Duration

Minimum time between payouts for this effect, parsed by DurationParser (y/d/h/m/s/ms, e.g. "30m", "1h 30m").

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).

effects: speed: type: "potion" display: "Speed %value%" next-level-display: "+%value% Speed level" effect: "speed" values: 1: 1 10: 1

Field

Type

Description

effect

PotionEffectType

Registry key from RegistryKey.MOB_EFFECT (e.g. speed, jump_boost, regeneration).

values

Map<Integer, Double>

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.

effects: storage: type: "storage" display: "%value% slots" next-level-display: "+%value% slots" values: 1: 9 10: 9

Field

Type

Description

values

Map<Integer, Double>

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).

effects: rideable: type: "rideable" display: "Rideable" values: 5: 1

Field

Type

Description

values

Map<Integer, Double>

Level → incremental value; riding unlocks once the cumulative total reaches 1.

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.

effects: no-fall: type: "no-fall" display: "No Fall Damage" values: 15: 1

Field

Type

Description

values

Map<Integer, Double>

Level → incremental value; fall damage is cancelled once the cumulative total is greater than 0.

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).

effects: shop-modifier: type: "shop-modifier" display: "%value% Shop Modifier" next-level-display: "+%value% Shop Modifier" key: "astralpets:shop_modifier" scope: BUY # Sell or Both modifier-type: "RELATIVE" # Absolute or Relative values: 1: 5 2: 10

Field

Type

Description

key

Key

Base key for the registered ShopModifier.

scope

ShopModifierScope

BUY, SELL, or BOTH.

modifier-type

ShopModifierType

RELATIVE or ABSOLUTE.

values

Map<Integer, Double>

Level → incremental amount; applied as cumulativeValue / 100.

item-id

String?

Optional. Scopes the modifier to one shop item instead of the whole shop.

category-id

String?

Optional (ignored if item-id is set). Scopes the modifier to one shop category.

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.

effects: jobs-modifier: type: "jobs-modifier" display: "%value% Jobs Modifier" next-level-display: "+%value% Jobs Modifier" modifier-key: "astralpets:jobs_modifier" modifier-scope: MONEY # MONEY or EXPERIENCE modifier-type: "RELATIVE" # Absolute or Relative job-id: null # Optional, if not specified it will apply to all jobs values: 1: 5 2: 10

Field

Type

Description

modifier-key

Key

Base key for the registered JobModifier.

modifier-scope

JobModifierScope

MONEY, EXPERIENCE, or GLOBAL.

modifier-type

JobModifierType

RELATIVE or ABSOLUTE.

job-id

String?

Optional. Scopes the modifier to one job; omitted/null applies to every job.

values

Map<Integer, Double>

Level → incremental amount, used as-is (not divided by 100).

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.

effects: rotating-shop-modifier: type: "rotating-shop-modifier" display: "%value%% Rotating Shop Discount" next-level-display: "+%value%% Rotating Shop Discount" modifier-key: "astralpets:rotating_shop_modifier" values: 1: 5 2: 10

Field

Type

Description

modifier-key

Key

Base key for the registered rotating-shop modifier.

values

Map<Integer, Double>

Level → incremental amount; applied as cumulativeValue / 100.

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%.

Last modified: 25 July 2026