370 lines
9.8 KiB
Markdown
370 lines
9.8 KiB
Markdown
---
|
|
title: Entity Effects
|
|
type: docs
|
|
weight: 3
|
|
---
|
|
|
|
Entity effects are status effects that can include visual components, stat modifiers, and duration-based behaviors.
|
|
|
|
## EntityEffect Configuration
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
// 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:
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
EntityEffect effect = EntityEffect.getAssetMap().getAsset("speed_boost");
|
|
|
|
// Add with default parameters
|
|
controller.addEffect(
|
|
entityRef,
|
|
effect,
|
|
componentAccessor
|
|
);
|
|
```
|
|
|
|
### With Custom Duration
|
|
|
|
```java
|
|
// 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
|
|
|
|
```java
|
|
// Add an effect that never expires
|
|
controller.addInfiniteEffect(
|
|
entityRef,
|
|
EntityEffect.getAssetMap().getIndex(effect.getId()),
|
|
effect,
|
|
componentAccessor
|
|
);
|
|
```
|
|
|
|
## Removing Effects
|
|
|
|
```java
|
|
// Remove specific effect
|
|
controller.removeEffect(
|
|
entityRef,
|
|
EntityEffect.getAssetMap().getIndex(effect.getId()),
|
|
componentAccessor
|
|
);
|
|
```
|
|
|
|
## Overlap Behaviors
|
|
|
|
```java
|
|
public enum OverlapBehavior {
|
|
EXTEND, // Add duration to existing effect
|
|
OVERWRITE, // Replace existing effect
|
|
IGNORE // Don't apply if already active
|
|
}
|
|
```
|
|
|
|
### Usage Examples
|
|
|
|
```java
|
|
// 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
|
|
|
|
```java
|
|
public enum RemovalBehavior {
|
|
COMPLETE, // Remove when complete
|
|
DURATION, // Remove after duration
|
|
INFINITE // Never remove automatically
|
|
}
|
|
```
|
|
|
|
## Value Types for Stats
|
|
|
|
```java
|
|
public enum ValueType {
|
|
Absolute, // Add/subtract flat value
|
|
Percent // Multiply by percentage
|
|
}
|
|
```
|
|
|
|
## Practical Examples
|
|
|
|
### Buff on Command
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
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
|
|
|
|
```java
|
|
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:
|
|
|
|
```java
|
|
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 >}}
|