Files
Documentation/content/advanced/effects/entity-effects.en.md
2026-01-20 20:33:59 +01:00

9.8 KiB

title, type, weight
title type weight
Entity Effects docs 3

Entity effects are status effects that can include visual components, stat modifiers, and duration-based behaviors.

EntityEffect Configuration

public class EntityEffect {
    protected String id;
    protected String name;                      // Localization key

    protected ApplicationEffects applicationEffects;  // Visual/audio effects

    protected String modelChange;               // Change entity model
    protected float duration;                   // Default duration (seconds)

    protected boolean infinite;                 // Never expires?
    protected boolean debuff;                   // Is a negative effect?
    protected String statusEffectIcon;          // UI icon

    // Damage and stats
    protected Int2FloatMap entityStats;         // Stat modifiers
    protected ValueType valueType;              // Absolute or Percent

    // Behavior
    protected OverlapBehavior overlapBehavior;  // EXTEND, OVERWRITE, IGNORE
    protected RemovalBehavior removalBehavior;  // COMPLETE, DURATION, INFINITE

    protected boolean invulnerable;             // Grant invulnerability
}

Accessing Effect Assets

// Get effect by ID
EntityEffect effect = EntityEffect.getAssetMap().getAsset("fire_resistance");

// Get effect index for operations
int effectIndex = EntityEffect.getAssetMap().getIndex(effect.getId());

EffectControllerComponent

The EffectControllerComponent manages active effects on an entity:

import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent;

// Get effect controller from entity
EffectControllerComponent controller = store.getComponent(
    entityRef,
    EffectControllerComponent.getComponentType()
);

Adding Effects

Basic Addition

EntityEffect effect = EntityEffect.getAssetMap().getAsset("speed_boost");

// Add with default parameters
controller.addEffect(
    entityRef,
    effect,
    componentAccessor
);

With Custom Duration

// Add with custom duration and overlap behavior
controller.addEffect(
    entityRef,
    effect,
    100.0f,                      // Duration in seconds
    OverlapBehavior.EXTEND,      // How to handle overlap
    componentAccessor
);

Infinite Effects

// Add an effect that never expires
controller.addInfiniteEffect(
    entityRef,
    EntityEffect.getAssetMap().getIndex(effect.getId()),
    effect,
    componentAccessor
);

Removing Effects

// Remove specific effect
controller.removeEffect(
    entityRef,
    EntityEffect.getAssetMap().getIndex(effect.getId()),
    componentAccessor
);

Overlap Behaviors

public enum OverlapBehavior {
    EXTEND,     // Add duration to existing effect
    OVERWRITE,  // Replace existing effect
    IGNORE      // Don't apply if already active
}

Usage Examples

// Extend duration if already applied
controller.addEffect(
    entityRef,
    effect,
    30.0f,
    OverlapBehavior.EXTEND,
    componentAccessor
);

// Replace with fresh duration
controller.addEffect(
    entityRef,
    effect,
    30.0f,
    OverlapBehavior.OVERWRITE,
    componentAccessor
);

// Only apply if not already active
controller.addEffect(
    entityRef,
    effect,
    30.0f,
    OverlapBehavior.IGNORE,
    componentAccessor
);

Removal Behaviors

public enum RemovalBehavior {
    COMPLETE,   // Remove when complete
    DURATION,   // Remove after duration
    INFINITE    // Never remove automatically
}

Value Types for Stats

public enum ValueType {
    Absolute,   // Add/subtract flat value
    Percent     // Multiply by percentage
}

Practical Examples

Buff on Command

public class BuffCommand extends AbstractCommand {
    private final RequiredArg<PlayerRef> playerArg;
    private final RequiredArg<String> effectArg;
    private final RequiredArg<Integer> durationArg;

    // Would be set during plugin initialization
    private Store<EntityStore> store;
    private ComponentAccessor<EntityStore> componentAccessor;

    public BuffCommand() {
        super("buff", "effects.command.buff.description");
        this.playerArg = withRequiredArg("player", "Target player", ArgTypes.PLAYER_REF);
        this.effectArg = withRequiredArg("effect", "Effect ID", ArgTypes.STRING);
        this.durationArg = withRequiredArg("duration", "Duration in seconds", ArgTypes.INTEGER);
    }

    @Override
    protected CompletableFuture<Void> execute(CommandContext ctx) {
        PlayerRef target = playerArg.get(ctx);
        String effectId = effectArg.get(ctx);
        int duration = durationArg.get(ctx);

        Ref<EntityStore> entityRef = target.getReference();
        if (entityRef == null || !entityRef.isValid()) {
            ctx.sender().sendMessage(Message.raw("Player not found"));
            return null;
        }

        EntityEffect effect = EntityEffect.getAssetMap().getAsset(effectId);
        if (effect == null) {
            ctx.sender().sendMessage(Message.raw("Unknown effect: " + effectId));
            return null;
        }

        EffectControllerComponent controller = store.getComponent(
            entityRef,
            EffectControllerComponent.getComponentType()
        );

        if (controller != null) {
            controller.addEffect(
                entityRef,
                effect,
                (float) duration,
                OverlapBehavior.EXTEND,
                componentAccessor
            );
        }

        ctx.sender().sendMessage(Message.raw("Applied " + effectId + " to " + target.getUsername()));
        return null;
    }
}

{{< callout type="warning" >}} API Notes:

  • Use withRequiredArg() to define arguments, not addArgument()
  • Use arg.get(ctx) to retrieve values, not ctx.getArg()
  • Use ctx.sender() not ctx.getSender()
  • Use target.getUsername() not target.getName()
  • Use target.getReference() to get entity reference (PlayerRef doesn't have getPlayer()) {{< /callout >}}

Damage Over Time Effect

public void applyPoison(Player player, float duration, Store<EntityStore> store,
                        ComponentAccessor<EntityStore> componentAccessor) {
    EntityEffect poison = EntityEffect.getAssetMap().getAsset("poison");

    Ref<EntityStore> entityRef = player.getReference();
    EffectControllerComponent controller = store.getComponent(
        entityRef,
        EffectControllerComponent.getComponentType()
    );

    if (controller != null && poison != null) {
        controller.addEffect(
            entityRef,
            poison,
            duration,
            OverlapBehavior.EXTEND,
            componentAccessor
        );
    }

    player.sendMessage(Message.raw("You have been poisoned!"));
}

Temporary Invulnerability

public void grantInvulnerability(Player player, float seconds, Store<EntityStore> store,
                                  ComponentAccessor<EntityStore> componentAccessor) {
    EntityEffect invuln = EntityEffect.getAssetMap().getAsset("invulnerability");

    Ref<EntityStore> entityRef = player.getReference();
    EffectControllerComponent controller = store.getComponent(
        entityRef,
        EffectControllerComponent.getComponentType()
    );

    if (controller != null && invuln != null) {
        controller.addEffect(
            entityRef,
            invuln,
            seconds,
            OverlapBehavior.OVERWRITE,  // Fresh duration
            componentAccessor
        );
    }
}

Clear All Effects

public void clearAllEffects(Player player, Store<EntityStore> store,
                            ComponentAccessor<EntityStore> componentAccessor) {
    Ref<EntityStore> entityRef = player.getReference();
    EffectControllerComponent controller = store.getComponent(
        entityRef,
        EffectControllerComponent.getComponentType()
    );

    if (controller != null) {
        // Use the built-in clearEffects method
        controller.clearEffects(
            entityRef,
            componentAccessor
        );
    }

    player.sendMessage(Message.raw("All effects cleared!"));
}

Combining with Other Effects

Entity effects work well with particles and dynamic lights:

public void applyMagicBuff(Player player, Store<EntityStore> store,
                           ComponentAccessor<EntityStore> componentAccessor) {
    Ref<EntityStore> entityRef = player.getReference();

    // Apply status effect
    EntityEffect buff = EntityEffect.getAssetMap().getAsset("magic_power");
    EffectControllerComponent controller = store.getComponent(
        entityRef,
        EffectControllerComponent.getComponentType()
    );
    if (controller != null && buff != null) {
        controller.addEffect(entityRef, buff, componentAccessor);
    }

    // Add visual glow
    ColorLight glow = new ColorLight((byte) 10, (byte) 200, (byte) 50, (byte) 255);
    DynamicLight light = new DynamicLight(glow);
    componentAccessor.putComponent(
        entityRef,
        DynamicLight.getComponentType(),
        light
    );

    // Get position from TransformComponent for particle spawn
    TransformComponent transform = store.getComponent(entityRef, TransformComponent.getComponentType());
    if (transform != null) {
        ParticleUtil.spawnParticleEffect(
            "magic_aura",
            transform.getPosition(),
            componentAccessor
        );
    }
}

{{< callout type="info" >}} Note: Use player.getReference() to get the entity reference. Position must be obtained from TransformComponent, not directly from Player. {{< /callout >}}

Best Practices

{{< callout type="info" >}} Effect Guidelines:

  • Use EXTEND for stackable buffs to reward repeated application
  • Use OVERWRITE for effects that should reset duration
  • Use IGNORE to prevent effect stacking when undesired
  • Always check if effect exists before applying
  • Consider performance with many simultaneous effects {{< /callout >}}