HyCodeYourTale

Applying Effects

Applying Effects

Dokumentace k aplikaci, správě a odstraňování efektů v Hytale.

---

EffectControllerComponent

Hlavní komponenta pro správu efektů na entitě.

// com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent

public class EffectControllerComponent implements Component {

// Získání component type
public static ComponentType getComponentType() {
return EntityModule.get().getEffectControllerComponentType();
}

// Aktivní efekty
protected final Int2ObjectMap activeEffects;

// Network synchronizace
protected ObjectList changes;
protected boolean isNetworkOutdated;

// Invulnerability tracking
protected boolean isInvulnerable;

// Model change tracking
protected Model originalModel;
protected int activeModelChangeEntityEffectIndex;
}

---

Získání EffectController

// Z Store
public void getEffectController(Ref entityRef, Store store) {
EffectControllerComponent effectController = store.getComponent(
entityRef,
EffectControllerComponent.getComponentType()
);

if (effectController != null) {
// Práce s efekty
}
}

// Z CommandBuffer (v systémech)
public void getEffectControllerInSystem(Ref entityRef, CommandBuffer commandBuffer) {
EffectControllerComponent effectController = commandBuffer.getComponent(
entityRef,
EffectControllerComponent.getComponentType()
);
}

---

Přidání Efektu

Základní metoda

// Přidání efektu s výchozím nastavením z asset definice
public boolean addEffect(
@Nonnull Ref ownerRef,
@Nonnull EntityEffect entityEffect,
@Nonnull ComponentAccessor componentAccessor
) {
int entityEffectIndex = EntityEffect.getAssetMap().getIndex(entityEffect.getId());
if (entityEffectIndex == Integer.MIN_VALUE) {
return false;
}
return this.addEffect(ownerRef, entityEffectIndex, entityEffect, componentAccessor);
}

S vlastní dobou trvání a overlap behavior

public boolean addEffect(
@Nonnull Ref ownerRef,
@Nonnull EntityEffect entityEffect,
float duration,
@Nonnull OverlapBehavior overlapBehavior,
@Nonnull ComponentAccessor componentAccessor
) {
int entityEffectIndex = EntityEffect.getAssetMap().getIndex(entityEffect.getId());
if (entityEffectIndex == Integer.MIN_VALUE) {
return false;
}
return this.addEffect(ownerRef, entityEffectIndex, entityEffect, duration, overlapBehavior, componentAccessor);
}

Nekonečný efekt

public boolean addInfiniteEffect(
@Nonnull Ref ownerRef,
int entityEffectIndex,
@Nonnull EntityEffect entityEffect,
@Nonnull ComponentAccessor componentAccessor
) {
if (!LivingEntityEffectSystem.canApplyEffect(ownerRef, entityEffect, componentAccessor)) {
return false;
}

ActiveEntityEffect currentActiveEntityEffect = this.activeEffects.get(entityEffectIndex);
if (currentActiveEntityEffect == null) {
currentActiveEntityEffect = new ActiveEntityEffect(
entityEffect.getId(),
entityEffectIndex,
true, // infinite
entityEffect.isInvulnerable()
);
this.activeEffects.put(entityEffectIndex, currentActiveEntityEffect);

if (EntityUtils.getEntity(ownerRef, componentAccessor) instanceof LivingEntity livingEntity) {
livingEntity.getStatModifiersManager().setRecalculate(true);
}

this.invalidateCache();
} else if (!currentActiveEntityEffect.isInfinite()) {
currentActiveEntityEffect.infinite = true;
}

// Nastavení model change pokud je definován
this.setModelChange(ownerRef, entityEffect, entityEffectIndex, componentAccessor);

// Přidání network změny
this.addChange(new EntityEffectUpdate(
EffectOp.Add,
entityEffectIndex,
currentActiveEntityEffect.remainingDuration,
true, // infinite
currentActiveEntityEffect.debuff,
currentActiveEntityEffect.statusEffectIcon
));

return true;
}

---

Praktické Příklady

Aplikace efektu na hráče

public void applyEffectToPlayer(Player player, String effectId, float duration) {
World world = player.getWorld();

world.execute(() -> {
Ref playerRef = player.getRef();
Store store = playerRef.getStore();

// Získání efektu z assets
EntityEffect entityEffect = EntityEffect.getAssetMap().getAsset(effectId);
if (entityEffect == null) {
getLogger().atWarn().log("Unknown effect: " + effectId);
return;
}

// Získání effect controlleru
EffectControllerComponent effectController = store.getComponent(
playerRef,
EffectControllerComponent.getComponentType()
);

if (effectController != null) {
boolean success = effectController.addEffect(
playerRef,
entityEffect,
duration,
OverlapBehavior.OVERWRITE,
store
);

if (success) {
getLogger().atInfo().log("Applied effect {} to player", effectId);
}
}
});
}

Příkaz pro aplikaci efektu

// Zjednodušený příklad na základě PlayerEffectApplyCommand

public class MyEffectCommand extends AbstractPlayerCommand {

private final RequiredArg effectArg =
withRequiredArg("effect", "desc", ArgTypes.EFFECT_ASSET);

private final DefaultArg durationArg =
withDefaultArg("duration", "desc", ArgTypes.FLOAT, 100.0F, "100")
.addValidator(Validators.greaterThan(0.0F));

public MyEffectCommand() {
super("myeffect", "desc");
requirePermission(HytalePermissions.of("myplugin.effect"));
}

@Override
protected void execute(
CommandContext context,
Store store,
Ref ref,
PlayerRef playerRef,
World world
) {
EntityEffect effect = effectArg.get(context);
float duration = durationArg.get(context);

EffectControllerComponent effectController = store.getComponent(
ref,
EffectControllerComponent.getComponentType()
);

if (effectController != null) {
effectController.addEffect(ref, effect, duration, OverlapBehavior.OVERWRITE, store);
context.sendMessage(Message.raw("Effect applied: " + effect.getId()));
}
}
}

---

Odebrání Efektu

Odebrání podle indexu

public void removeEffect(
@Nonnull Ref ownerRef,
int entityEffectIndex,
@Nonnull ComponentAccessor componentAccessor
) {
EntityEffect entityEffect = EntityEffect.getAssetMap().getAsset(entityEffectIndex);
if (entityEffect == null) {
throw new IllegalArgumentException("Unknown EntityEffect with index: " + entityEffectIndex);
}
this.removeEffect(ownerRef, entityEffectIndex, entityEffect.getRemovalBehavior(), componentAccessor);
}

Odebrání s konkrétním RemovalBehavior

public void removeEffect(
@Nonnull Ref ownerRef,
int entityEffectIndex,
@Nonnull RemovalBehavior removalBehavior,
@Nonnull ComponentAccessor componentAccessor
) {
ActiveEntityEffect activeEffect = this.activeEffects.get(entityEffectIndex);
if (activeEffect != null) {
// Reset model change pokud je aktivní
this.tryResetModelChange(ownerRef, activeEffect.getEntityEffectIndex(), componentAccessor);

switch (removalBehavior) {
case COMPLETE:
// Kompletní odstranění
this.activeEffects.remove(entityEffectIndex);
if (EntityUtils.getEntity(ownerRef, componentAccessor) instanceof LivingEntity livingEntity) {
livingEntity.getStatModifiersManager().setRecalculate(true);
}
this.addChange(new EntityEffectUpdate(EffectOp.Remove, entityEffectIndex, 0.0F, false, false, ""));
this.invalidateCache();
return;

case INFINITE:
// Odstranění infinite flagu
activeEffect.infinite = false;
break;

case DURATION:
// Nastavení duration na 0
activeEffect.remainingDuration = 0.0F;
break;
}

if (EntityUtils.getEntity(ownerRef, componentAccessor) instanceof LivingEntity livingEntity) {
livingEntity.getStatModifiersManager().setRecalculate(true);
}

this.addChange(new EntityEffectUpdate(
EffectOp.Remove,
entityEffectIndex,
activeEffect.remainingDuration,
activeEffect.infinite,
activeEffect.debuff,
activeEffect.statusEffectIcon
));
}
}

Odebrání všech efektů

public void clearEffects(
@Nonnull Ref ownerRef,
@Nonnull ComponentAccessor componentAccessor
) {
IntSet keys = new IntArraySet(this.activeEffects.keySet());
for (int effect : keys) {
this.removeEffect(ownerRef, effect, componentAccessor);
}

this.invalidateCache();

// Reset modelu pokud byl změněn
if (this.originalModel != null) {
componentAccessor.putComponent(
ownerRef,
ModelComponent.getComponentType(),
new ModelComponent(this.originalModel)
);
this.originalModel = null;
}
}

---

Interakce pro Efekty

ApplyEffectInteraction

// com.hypixel.hytale.server.core.modules.interaction.interaction.config.none.simple.ApplyEffectInteraction

public class ApplyEffectInteraction extends SimpleInstantInteraction {

private String effectId;
private InteractionTarget entityTarget = InteractionTarget.USER;

@Override
protected void firstRun(InteractionType type, InteractionContext context, CooldownHandler cooldownHandler) {
if (this.effectId == null) return;

EntityEffect entityEffect = EntityEffect.getAssetMap().getAsset(this.effectId);
if (entityEffect == null) return;

Ref ref = context.getEntity();
Ref targetRef = this.entityTarget.getEntity(context, ref);

if (targetRef != null && targetRef.isValid()) {
CommandBuffer commandBuffer = context.getCommandBuffer();
EffectControllerComponent effectController = commandBuffer.getComponent(
targetRef,
EffectControllerComponent.getComponentType()
);

if (effectController != null) {
effectController.addEffect(targetRef, entityEffect, commandBuffer);
}
}
}
}

ClearEntityEffectInteraction

// com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.ClearEntityEffectInteraction

public class ClearEntityEffectInteraction extends SimpleInstantInteraction {

protected String entityEffectId;
private InteractionTarget entityTarget = InteractionTarget.USER;

@Override
protected void firstRun(InteractionType type, InteractionContext context, CooldownHandler cooldownHandler) {
Ref ref = context.getEntity();
Ref targetRef = this.entityTarget.getEntity(context, ref);

if (targetRef != null && targetRef.isValid()) {
EntityEffect entityEffect = EntityEffect.getAssetMap().getAsset(this.entityEffectId);
if (entityEffect != null) {
CommandBuffer commandBuffer = context.getCommandBuffer();
EffectControllerComponent effectController = commandBuffer.getComponent(
targetRef,
EffectControllerComponent.getComponentType()
);

if (effectController != null) {
effectController.removeEffect(
targetRef,
EntityEffect.getAssetMap().getIndex(this.entityEffectId),
commandBuffer
);
}
}
}
}
}

---

LivingEntityEffectSystem

ECS systém který tickuje všechny efekty.

// com.hypixel.hytale.server.core.modules.entity.livingentity.LivingEntityEffectSystem

public class LivingEntityEffectSystem extends EntityTickingSystem {

@Override
public Query getQuery() {
return EffectControllerComponent.getComponentType();
}

@Override
public void tick(
float dt,
int index,
ArchetypeChunk archetypeChunk,
Store store,
CommandBuffer commandBuffer
) {
EffectControllerComponent effectController = archetypeChunk.getComponent(
index,
EffectControllerComponent.getComponentType()
);

Int2ObjectMap activeEffects = effectController.getActiveEffects();
if (activeEffects.isEmpty()) return;

IndexedLookupTableAssetMap effectAssetMap = EntityEffect.getAssetMap();
Ref entityRef = archetypeChunk.getReferenceTo(index);
ObjectIterator iterator = activeEffects.values().iterator();
EntityStatMap entityStatMap = commandBuffer.getComponent(entityRef, EntityStatMap.getComponentType());
boolean invalidated = false;
boolean invulnerable = false;

while (iterator.hasNext()) {
ActiveEntityEffect activeEffect = iterator.next();
int effectIndex = activeEffect.getEntityEffectIndex();
EntityEffect entityEffect = effectAssetMap.getAsset(effectIndex);

if (entityEffect == null) {
iterator.remove();
invalidated = true;
continue;
}

// Kontrola zda může být efekt aplikován (např. Burn ve vodě)
if (!canApplyEffect(entityRef, entityEffect, commandBuffer)) {
iterator.remove();
invalidated = true;
continue;
}

// Tick efektu
float tickDelta = Math.min(activeEffect.getRemainingDuration(), dt);
activeEffect.tick(commandBuffer, entityRef, entityEffect, entityStatMap, tickDelta);

if (activeEffects.isEmpty()) return;

// Kontrola expirace
if (!activeEffect.isInfinite() && activeEffect.getRemainingDuration() <= 0.0F) {
iterator.remove();
effectController.tryResetModelChange(entityRef, activeEffect.getEntityEffectIndex(), commandBuffer);
invalidated = true;
}

if (activeEffect.isInvulnerable()) {
invulnerable = true;
}
}

effectController.setInvulnerable(invulnerable);

if (invalidated) {
effectController.invalidateCache();
if (EntityUtils.getEntity(index, archetypeChunk) instanceof LivingEntity livingEntity) {
livingEntity.getStatModifiersManager().setRecalculate(true);
}
}
}

// Kontrola zda může být efekt aplikován
public static boolean canApplyEffect(
Ref ownerRef,
EntityEffect entityEffect,
ComponentAccessor componentAccessor
) {
// Speciální logika pro Burn efekt
if ("Burn".equals(entityEffect.getId())) {
// Burn je odstraněn když je entita ve vodě
TransformComponent transform = componentAccessor.getComponent(ownerRef, TransformComponent.getComponentType());
BoundingBox boundingBox = componentAccessor.getComponent(ownerRef, BoundingBox.getComponentType());
World world = componentAccessor.getExternalData().getWorld();

// Kontrola všech bloků v bounding boxu
return boundingBox.getBoundingBox().forEachBlock(
transform.getPosition(),
chunkAccessor,
(x, y, z, accessor) -> {
WorldChunk chunk = accessor.getChunkIfInMemory(...);
return chunk == null || !chunk.getBlockType(x, y, z).getId().contains("Fluid_Water");
}
);
}
return true;
}
}

---

Kontrola Aktivních Efektů

// Získání všech aktivních efektů
public Int2ObjectMap getActiveEffects() {
return this.activeEffects;
}

// Získání indexů aktivních efektů (cached)
public int[] getActiveEffectIndexes() {
if (this.cachedActiveEffectIndexes == null) {
if (this.activeEffects.isEmpty()) {
this.cachedActiveEffectIndexes = ArrayUtil.EMPTY_INT_ARRAY;
} else {
this.cachedActiveEffectIndexes = this.activeEffects.keySet().toIntArray();
}
}
return this.cachedActiveEffectIndexes;
}

// Kontrola konkrétního efektu
public boolean hasEffect(String effectId) {
int effectIndex = EntityEffect.getAssetMap().getIndex(effectId);
return this.activeEffects.containsKey(effectIndex);
}

// Kontrola invulnerability
public boolean isInvulnerable() {
return this.isInvulnerable;
}

---

Network Synchronizace

// Kontrola zda jsou změny k odeslání
public boolean consumeNetworkOutdated() {
boolean temp = this.isNetworkOutdated;
this.isNetworkOutdated = false;
return temp;
}

// Získání změn pro síť
public EntityEffectUpdate[] consumeChanges() {
return this.changes.toArray(EntityEffectUpdate[]::new);
}

// Vyčištění změn
public void clearChanges() {
this.changes.clear();
}

// Vytvoření init updatů pro nové klienty
public EntityEffectUpdate[] createInitUpdates() {
EntityEffectUpdate[] changeArray = new EntityEffectUpdate[this.activeEffects.size()];
int index = 0;

for (Entry entry : Int2ObjectMaps.fastIterable(this.activeEffects)) {
ActiveEntityEffect effect = entry.getValue();
changeArray[index++] = new EntityEffectUpdate(
EffectOp.Add,
entry.getIntKey(),
effect.remainingDuration,
effect.infinite,
effect.debuff,
effect.statusEffectIcon
);
}

return changeArray;
}

---

CODEC pro Serializaci

public static final BuilderCodec CODEC = BuilderCodec.builder(
EffectControllerComponent.class,
EffectControllerComponent::new
)
.append(
new KeyedCodec<>("ActiveEntityEffects", new ArrayCodec<>(ActiveEntityEffect.CODEC, ActiveEntityEffect[]::new)),
EffectControllerComponent::addActiveEntityEffects,
EffectControllerComponent::getAllActiveEntityEffects
)
.add()
.build();

---

Shrnutí API

| Metoda | Popis |
|--------|-------|
| addEffect(ref, effect, accessor) | Přidá efekt s výchozím nastavením |
| addEffect(ref, effect, duration, overlap, accessor) | Přidá efekt s vlastní délkou |
| addInfiniteEffect(ref, index, effect, accessor) | Přidá nekonečný efekt |
| removeEffect(ref, index, accessor) | Odebere efekt |
| removeEffect(ref, index, removal, accessor) | Odebere efekt s konkrétním chováním |
| clearEffects(ref, accessor) | Odebere všechny efekty |
| getActiveEffects() | Vrátí mapu aktivních efektů |
| getActiveEffectIndexes() | Vrátí pole indexů aktivních efektů |
| isInvulnerable() | Zjistí zda je entita nezranitelná |

| InteractionTarget | Cíl |
|-------------------|-----|
| USER | Entita která interaguje |
| TARGET | Cílová entita |
| PROJECTILE | Projektil |

Last updated: 20. ledna 2026