Init
This commit is contained in:
369
content/advanced/effects/entity-effects.en.md
Normal file
369
content/advanced/effects/entity-effects.en.md
Normal file
@@ -0,0 +1,369 @@
|
||||
---
|
||||
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 >}}
|
||||
Reference in New Issue
Block a user