This commit is contained in:
2026-01-20 20:33:59 +01:00
commit b16a40e431
583 changed files with 87339 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
---
title: Advanced
type: docs
weight: 6
---
Advanced systems for networking, effects, and other specialized functionality.
{{< cards >}}
{{< card link="networking" title="Networking" subtitle="Network communication and protocols" icon="wifi" >}}
{{< card link="effects" title="Effects" subtitle="Particles, dynamic lights, and entity effects" icon="sparkles" >}}
{{< /cards >}}
## Networking
Network communication allows plugins to send custom data between server and clients:
```java
// Send custom packet
CustomPacket packet = new CustomPacket(data);
player.sendPacket(packet);
```
## Effects
Create visual effects using particles, lights, and entity animations:
```java
// Spawn particles
world.spawnParticles(ParticleType.SMOKE, position, count);
// Add dynamic light
entity.addComponent(new DynamicLight(Color.YELLOW, 10.0f));
```

View File

@@ -0,0 +1,34 @@
---
title: Avancé
type: docs
weight: 6
---
Systèmes avancés pour le réseau, les effets et autres fonctionnalités spécialisées.
{{< cards >}}
{{< card link="networking" title="Réseau" subtitle="Communication réseau et protocoles" icon="wifi" >}}
{{< card link="effects" title="Effets" subtitle="Particules, lumières dynamiques et effets d'entité" icon="sparkles" >}}
{{< /cards >}}
## Réseau
La communication réseau permet aux plugins d'envoyer des données personnalisées entre le serveur et les clients :
```java
// Envoyer un paquet personnalisé
CustomPacket packet = new CustomPacket(data);
player.sendPacket(packet);
```
## Effets
Créez des effets visuels avec des particules, lumières et animations d'entités :
```java
// Faire apparaître des particules
world.spawnParticles(ParticleType.SMOKE, position, count);
// Ajouter une lumière dynamique
entity.addComponent(new DynamicLight(Color.YELLOW, 10.0f));
```

View File

@@ -0,0 +1,92 @@
---
title: Effects
type: docs
weight: 10
---
Visual effects in Hytale include particles, dynamic lights, and entity status effects.
{{< cards >}}
{{< card link="particles" title="Particles" subtitle="Spawn and configure particle systems" >}}
{{< card link="dynamic-lights" title="Dynamic Lights" subtitle="Glow effects on entities" >}}
{{< card link="entity-effects" title="Entity Effects" subtitle="Status effects with visuals" >}}
{{< /cards >}}
## Overview
Hytale provides three complementary visual effect systems:
| System | Purpose | Use Cases |
|--------|---------|-----------|
| **Particles** | Visual atmosphere and feedback | Explosions, trails, ambient effects |
| **Dynamic Lights** | Glow effects on entities | Glowing items, magic auras |
| **Entity Effects** | Status effects with visuals | Buffs, debuffs, status indicators |
## Quick Start
### Spawning Particles
```java
import com.hypixel.hytale.server.core.universe.world.ParticleUtil;
// Spawn particles at a position
ParticleUtil.spawnParticleEffect(
"explosion_small", // Particle system ID
position, // Vector3d position
componentAccessor
);
```
### Adding Dynamic Light
```java
import com.hypixel.hytale.protocol.ColorLight;
import com.hypixel.hytale.server.core.modules.entity.component.DynamicLight;
// Create a colored light (radius, R, G, B)
ColorLight light = new ColorLight(
(byte) 15, // Radius
(byte) 255, // Red
(byte) 100, // Green
(byte) 50 // Blue
);
// Add to entity
DynamicLight dynamicLight = new DynamicLight(light);
componentAccessor.putComponent(entityRef, DynamicLight.getComponentType(), dynamicLight);
```
### Applying Entity Effects
```java
import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent;
// Get effect controller
EffectControllerComponent controller = store.getComponent(
entityRef,
EffectControllerComponent.getComponentType()
);
// Apply effect
EntityEffect effect = EntityEffect.getAssetMap().getAsset("fire_resistance");
controller.addEffect(entityRef, effect, componentAccessor);
```
## Key Classes
| Class | Package | Purpose |
|-------|---------|---------|
| `ParticleUtil` | server.core.universe.world | Spawn particles |
| `ParticleSystem` | asset.type.particle.config | System configuration |
| `DynamicLight` | modules.entity.component | Entity glow effects |
| `ColorLight` | protocol | Light color/radius data |
| `EntityEffect` | asset.type.entityeffect.config | Status effect definition |
## Network Considerations
{{< callout type="info" >}}
**Network Details:**
- Particles are sent within 75 blocks by default (`DEFAULT_PARTICLE_DISTANCE`)
- DynamicLight changes sync automatically when marked as outdated
- Entity effects sync via `EffectControllerComponent`
{{< /callout >}}

View File

@@ -0,0 +1,92 @@
---
title: Effets
type: docs
weight: 10
---
Les effets visuels dans Hytale incluent les particules, les lumières dynamiques et les effets de statut d'entité.
{{< cards >}}
{{< card link="particles" title="Particules" subtitle="Générer et configurer les systèmes de particules" >}}
{{< card link="dynamic-lights" title="Lumières Dynamiques" subtitle="Effets de lueur sur les entités" >}}
{{< card link="entity-effects" title="Effets d'Entité" subtitle="Effets de statut avec visuels" >}}
{{< /cards >}}
## Aperçu
Hytale fournit trois systèmes d'effets visuels complémentaires :
| Système | Objectif | Cas d'Usage |
|---------|----------|-------------|
| **Particules** | Atmosphère visuelle et feedback | Explosions, traînées, effets ambiants |
| **Lumières Dynamiques** | Effets de lueur sur les entités | Objets lumineux, auras magiques |
| **Effets d'Entité** | Effets de statut avec visuels | Buffs, debuffs, indicateurs de statut |
## Démarrage Rapide
### Générer des Particules
```java
import com.hypixel.hytale.server.core.universe.world.ParticleUtil;
// Générer des particules à une position
ParticleUtil.spawnParticleEffect(
"explosion_small", // ID du système de particules
position, // Position Vector3d
componentAccessor
);
```
### Ajouter une Lumière Dynamique
```java
import com.hypixel.hytale.protocol.ColorLight;
import com.hypixel.hytale.server.core.modules.entity.component.DynamicLight;
// Créer une lumière colorée (rayon, R, V, B)
ColorLight light = new ColorLight(
(byte) 15, // Rayon
(byte) 255, // Rouge
(byte) 100, // Vert
(byte) 50 // Bleu
);
// Ajouter à l'entité
DynamicLight dynamicLight = new DynamicLight(light);
componentAccessor.putComponent(entityRef, DynamicLight.getComponentType(), dynamicLight);
```
### Appliquer des Effets d'Entité
```java
import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent;
// Obtenir le contrôleur d'effets
EffectControllerComponent controller = store.getComponent(
entityRef,
EffectControllerComponent.getComponentType()
);
// Appliquer l'effet
EntityEffect effect = EntityEffect.getAssetMap().getAsset("fire_resistance");
controller.addEffect(entityRef, effect, componentAccessor);
```
## Classes Principales
| Classe | Package | Objectif |
|--------|---------|----------|
| `ParticleUtil` | server.core.universe.world | Générer des particules |
| `ParticleSystem` | asset.type.particle.config | Configuration du système |
| `DynamicLight` | modules.entity.component | Effets de lueur d'entité |
| `ColorLight` | protocol | Données couleur/rayon de lumière |
| `EntityEffect` | asset.type.entityeffect.config | Définition d'effet de statut |
## Considérations Réseau
{{< callout type="info" >}}
**Détails Réseau :**
- Les particules sont envoyées dans un rayon de 75 blocs par défaut (`DEFAULT_PARTICLE_DISTANCE`)
- Les changements de DynamicLight se synchronisent automatiquement quand marqués obsolètes
- Les effets d'entité se synchronisent via `EffectControllerComponent`
{{< /callout >}}

View File

@@ -0,0 +1,268 @@
---
title: Dynamic Lights
type: docs
weight: 2
---
Dynamic lights add glow effects to entities using the `DynamicLight` component.
## ColorLight Structure
The `ColorLight` class defines light properties:
```java
import com.hypixel.hytale.protocol.ColorLight;
public class ColorLight {
public byte radius; // 0-255 (light radius/intensity)
public byte red; // 0-255 (red channel)
public byte green; // 0-255 (green channel)
public byte blue; // 0-255 (blue channel)
public ColorLight(byte radius, byte red, byte green, byte blue) {
this.radius = radius;
this.red = red;
this.green = green;
this.blue = blue;
}
}
```
## DynamicLight Component
Add temporary glow effects to entities:
```java
import com.hypixel.hytale.server.core.modules.entity.component.DynamicLight;
import com.hypixel.hytale.protocol.ColorLight;
// Create colored light (radius, R, G, B)
ColorLight light = new ColorLight(
(byte) 15, // Radius (0-255)
(byte) 255, // Red (0-255)
(byte) 100, // Green (0-255)
(byte) 50 // Blue (0-255)
);
// Add DynamicLight component
DynamicLight dynamicLight = new DynamicLight(light);
componentAccessor.putComponent(
entityRef,
DynamicLight.getComponentType(),
dynamicLight
);
```
## PersistentDynamicLight Component
Use this component when light should be saved with entity data:
```java
import com.hypixel.hytale.server.core.modules.entity.component.PersistentDynamicLight;
// Create persistent light
PersistentDynamicLight persistentLight = new PersistentDynamicLight(light);
componentAccessor.putComponent(
entityRef,
PersistentDynamicLight.getComponentType(),
persistentLight
);
```
## Updating Light Properties
```java
// Get existing light component
DynamicLight dynamicLight = store.getComponent(
entityRef,
DynamicLight.getComponentType()
);
if (dynamicLight != null) {
// Create new light properties
ColorLight newLight = new ColorLight(
(byte) 20, // New radius
(byte) 0, // No red
(byte) 255, // Full green
(byte) 100 // Some blue
);
// Update and mark for network sync
dynamicLight.setColorLight(newLight);
// isNetworkOutdated is set automatically
}
```
## Removing Light
```java
// Remove the component to disable light
componentAccessor.removeComponent(
entityRef,
DynamicLight.getComponentType()
);
```
## Common Light Configurations
### Fire/Warm Glow
```java
ColorLight fireLight = new ColorLight(
(byte) 12,
(byte) 255, // High red
(byte) 150, // Medium orange
(byte) 50 // Low blue
);
```
### Ice/Cold Glow
```java
ColorLight iceLight = new ColorLight(
(byte) 10,
(byte) 100, // Low red
(byte) 200, // Medium green
(byte) 255 // High blue
);
```
### Magic/Purple Glow
```java
ColorLight magicLight = new ColorLight(
(byte) 15,
(byte) 200, // High red
(byte) 50, // Low green
(byte) 255 // High blue
);
```
### Healing/Green Glow
```java
ColorLight healLight = new ColorLight(
(byte) 10,
(byte) 50, // Low red
(byte) 255, // High green
(byte) 100 // Medium blue
);
```
## Practical Examples
### Adding a Glow Effect to a Player
```java
// Add temporary glow effect to a player
public void addGlowEffect(Player player, ComponentAccessor<EntityStore> componentAccessor,
ScheduledExecutorService scheduler) {
World world = player.getWorld();
// Add temporary glow
ColorLight glow = new ColorLight(
(byte) 8,
(byte) 255,
(byte) 215,
(byte) 0 // Gold color
);
DynamicLight light = new DynamicLight(glow);
Ref<EntityStore> entityRef = player.getReference();
componentAccessor.putComponent(
entityRef,
DynamicLight.getComponentType(),
light
);
// Remove after 3 seconds using standard Java scheduling
scheduler.schedule(() -> {
world.execute(() -> {
if (entityRef.isValid()) {
componentAccessor.removeComponent(
entityRef,
DynamicLight.getComponentType()
);
}
});
}, 3, TimeUnit.SECONDS);
}
```
{{< callout type="warning" >}}
**Note:** Use `player.getReference()` to get the entity reference, not `player.getEntityRef()`. Use standard Java `ScheduledExecutorService` for delayed tasks.
{{< /callout >}}
### Pulsing Light Effect
Implement pulsing effects by updating the light in a tick handler:
```java
// Track pulsing entities
private final Map<Ref<EntityStore>, Integer> pulsingEntities = new ConcurrentHashMap<>();
// Call this method each tick
public void onTick(float deltaTime) {
for (Map.Entry<Ref<EntityStore>, Integer> entry : pulsingEntities.entrySet()) {
Ref<EntityStore> entityRef = entry.getKey();
int tick = entry.getValue() + 1;
entry.setValue(tick);
// Calculate pulsing intensity
double pulse = Math.sin(tick * 0.1) * 0.5 + 0.5;
byte radius = (byte) (5 + pulse * 10);
ColorLight light = new ColorLight(
radius,
(byte) 255,
(byte) (int)(100 + pulse * 100),
(byte) 50
);
DynamicLight dynamicLight = store.getComponent(
entityRef,
DynamicLight.getComponentType()
);
if (dynamicLight != null) {
dynamicLight.setColorLight(light);
}
}
}
public void startPulsingLight(Ref<EntityStore> entityRef) {
pulsingEntities.put(entityRef, 0);
}
public void stopPulsingLight(Ref<EntityStore> entityRef) {
pulsingEntities.remove(entityRef);
}
```
## Network Synchronization
{{< callout type="info" >}}
**Sync Details:**
- `DynamicLight` changes are automatically marked for sync when using `setColorLight()`
- The `isNetworkOutdated` flag triggers client updates
- Use `consumeNetworkOutdated()` before manual sync operations
{{< /callout >}}
## DynamicLight vs PersistentDynamicLight
| Feature | DynamicLight | PersistentDynamicLight |
|---------|--------------|------------------------|
| Saved to disk | No | Yes |
| Survives restart | No | Yes |
| Use case | Temporary effects | Permanent glows |
| Performance | Lighter | Slightly heavier |
## Best Practices
{{< callout type="warning" >}}
**Light Guidelines:**
- Keep radius values reasonable (5-20 for most effects)
- Don't add lights to too many entities simultaneously
- Remove temporary lights when no longer needed
- Use `PersistentDynamicLight` only when persistence is required
{{< /callout >}}

View File

@@ -0,0 +1,268 @@
---
title: Lumières Dynamiques
type: docs
weight: 2
---
Les lumières dynamiques ajoutent des effets de lueur aux entités en utilisant le composant `DynamicLight`.
## Structure ColorLight
La classe `ColorLight` définit les propriétés de la lumière :
```java
import com.hypixel.hytale.protocol.ColorLight;
public class ColorLight {
public byte radius; // 0-255 (rayon/intensité de la lumière)
public byte red; // 0-255 (canal rouge)
public byte green; // 0-255 (canal vert)
public byte blue; // 0-255 (canal bleu)
public ColorLight(byte radius, byte red, byte green, byte blue) {
this.radius = radius;
this.red = red;
this.green = green;
this.blue = blue;
}
}
```
## Composant DynamicLight
Ajoutez des effets de lueur temporaires aux entités :
```java
import com.hypixel.hytale.server.core.modules.entity.component.DynamicLight;
import com.hypixel.hytale.protocol.ColorLight;
// Créer une lumière colorée (rayon, R, V, B)
ColorLight light = new ColorLight(
(byte) 15, // Rayon (0-255)
(byte) 255, // Rouge (0-255)
(byte) 100, // Vert (0-255)
(byte) 50 // Bleu (0-255)
);
// Ajouter le composant DynamicLight
DynamicLight dynamicLight = new DynamicLight(light);
componentAccessor.putComponent(
entityRef,
DynamicLight.getComponentType(),
dynamicLight
);
```
## Composant PersistentDynamicLight
Utilisez ce composant quand la lumière doit être sauvegardée avec les données de l'entité :
```java
import com.hypixel.hytale.server.core.modules.entity.component.PersistentDynamicLight;
// Créer une lumière persistante
PersistentDynamicLight persistentLight = new PersistentDynamicLight(light);
componentAccessor.putComponent(
entityRef,
PersistentDynamicLight.getComponentType(),
persistentLight
);
```
## Mettre à Jour les Propriétés de Lumière
```java
// Obtenir le composant de lumière existant
DynamicLight dynamicLight = store.getComponent(
entityRef,
DynamicLight.getComponentType()
);
if (dynamicLight != null) {
// Créer de nouvelles propriétés de lumière
ColorLight newLight = new ColorLight(
(byte) 20, // Nouveau rayon
(byte) 0, // Pas de rouge
(byte) 255, // Vert maximum
(byte) 100 // Un peu de bleu
);
// Mettre à jour et marquer pour sync réseau
dynamicLight.setColorLight(newLight);
// isNetworkOutdated est défini automatiquement
}
```
## Supprimer la Lumière
```java
// Supprimer le composant pour désactiver la lumière
componentAccessor.removeComponent(
entityRef,
DynamicLight.getComponentType()
);
```
## Configurations de Lumière Courantes
### Lueur Feu/Chaleur
```java
ColorLight fireLight = new ColorLight(
(byte) 12,
(byte) 255, // Rouge élevé
(byte) 150, // Orange moyen
(byte) 50 // Bleu faible
);
```
### Lueur Glace/Froid
```java
ColorLight iceLight = new ColorLight(
(byte) 10,
(byte) 100, // Rouge faible
(byte) 200, // Vert moyen
(byte) 255 // Bleu élevé
);
```
### Lueur Magie/Violet
```java
ColorLight magicLight = new ColorLight(
(byte) 15,
(byte) 200, // Rouge élevé
(byte) 50, // Vert faible
(byte) 255 // Bleu élevé
);
```
### Lueur Soin/Vert
```java
ColorLight healLight = new ColorLight(
(byte) 10,
(byte) 50, // Rouge faible
(byte) 255, // Vert élevé
(byte) 100 // Bleu moyen
);
```
## Exemples Pratiques
### Ajouter un Effet de Lueur à un Joueur
```java
// Ajouter un effet de lueur temporaire à un joueur
public void addGlowEffect(Player player, ComponentAccessor<EntityStore> componentAccessor,
ScheduledExecutorService scheduler) {
World world = player.getWorld();
// Ajouter une lueur temporaire
ColorLight glow = new ColorLight(
(byte) 8,
(byte) 255,
(byte) 215,
(byte) 0 // Couleur dorée
);
DynamicLight light = new DynamicLight(glow);
Ref<EntityStore> entityRef = player.getReference();
componentAccessor.putComponent(
entityRef,
DynamicLight.getComponentType(),
light
);
// Supprimer après 3 secondes avec le scheduling Java standard
scheduler.schedule(() -> {
world.execute(() -> {
if (entityRef.isValid()) {
componentAccessor.removeComponent(
entityRef,
DynamicLight.getComponentType()
);
}
});
}, 3, TimeUnit.SECONDS);
}
```
{{< callout type="warning" >}}
**Note :** Utilisez `player.getReference()` pour obtenir la référence d'entité, pas `player.getEntityRef()`. Utilisez le `ScheduledExecutorService` Java standard pour les tâches différées.
{{< /callout >}}
### Effet de Lumière Pulsante
Implémentez les effets pulsants en mettant à jour la lumière dans un gestionnaire de tick :
```java
// Suivre les entités pulsantes
private final Map<Ref<EntityStore>, Integer> pulsingEntities = new ConcurrentHashMap<>();
// Appeler cette méthode à chaque tick
public void onTick(float deltaTime) {
for (Map.Entry<Ref<EntityStore>, Integer> entry : pulsingEntities.entrySet()) {
Ref<EntityStore> entityRef = entry.getKey();
int tick = entry.getValue() + 1;
entry.setValue(tick);
// Calculer l'intensité pulsante
double pulse = Math.sin(tick * 0.1) * 0.5 + 0.5;
byte radius = (byte) (5 + pulse * 10);
ColorLight light = new ColorLight(
radius,
(byte) 255,
(byte) (int)(100 + pulse * 100),
(byte) 50
);
DynamicLight dynamicLight = store.getComponent(
entityRef,
DynamicLight.getComponentType()
);
if (dynamicLight != null) {
dynamicLight.setColorLight(light);
}
}
}
public void startPulsingLight(Ref<EntityStore> entityRef) {
pulsingEntities.put(entityRef, 0);
}
public void stopPulsingLight(Ref<EntityStore> entityRef) {
pulsingEntities.remove(entityRef);
}
```
## Synchronisation Réseau
{{< callout type="info" >}}
**Détails de Sync :**
- Les changements de `DynamicLight` sont automatiquement marqués pour sync lors de l'utilisation de `setColorLight()`
- Le flag `isNetworkOutdated` déclenche les mises à jour client
- Utilisez `consumeNetworkOutdated()` avant les opérations de sync manuelles
{{< /callout >}}
## DynamicLight vs PersistentDynamicLight
| Fonctionnalité | DynamicLight | PersistentDynamicLight |
|----------------|--------------|------------------------|
| Sauvegardé sur disque | Non | Oui |
| Survit au redémarrage | Non | Oui |
| Cas d'usage | Effets temporaires | Lueurs permanentes |
| Performance | Plus léger | Légèrement plus lourd |
## Bonnes Pratiques
{{< callout type="warning" >}}
**Directives pour les Lumières :**
- Gardez des valeurs de rayon raisonnables (5-20 pour la plupart des effets)
- N'ajoutez pas de lumières à trop d'entités simultanément
- Supprimez les lumières temporaires quand plus nécessaires
- Utilisez `PersistentDynamicLight` seulement quand la persistance est requise
{{< /callout >}}

View 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 >}}

View File

@@ -0,0 +1,369 @@
---
title: Effets d'Entité
type: docs
weight: 3
---
Les effets d'entité sont des effets de statut qui peuvent inclure des composants visuels, des modificateurs de stats et des comportements basés sur la durée.
## Configuration EntityEffect
```java
public class EntityEffect {
protected String id;
protected String name; // Clé de localisation
protected ApplicationEffects applicationEffects; // Effets visuels/audio
protected String modelChange; // Changer le modèle de l'entité
protected float duration; // Durée par défaut (secondes)
protected boolean infinite; // N'expire jamais ?
protected boolean debuff; // Est un effet négatif ?
protected String statusEffectIcon; // Icône UI
// Dégâts et stats
protected Int2FloatMap entityStats; // Modificateurs de stats
protected ValueType valueType; // Absolute ou Percent
// Comportement
protected OverlapBehavior overlapBehavior; // EXTEND, OVERWRITE, IGNORE
protected RemovalBehavior removalBehavior; // COMPLETE, DURATION, INFINITE
protected boolean invulnerable; // Accorder l'invulnérabilité
}
```
## Accéder aux Assets d'Effets
```java
// Obtenir un effet par ID
EntityEffect effect = EntityEffect.getAssetMap().getAsset("fire_resistance");
// Obtenir l'index de l'effet pour les opérations
int effectIndex = EntityEffect.getAssetMap().getIndex(effect.getId());
```
## EffectControllerComponent
Le `EffectControllerComponent` gère les effets actifs sur une entité :
```java
import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent;
// Obtenir le contrôleur d'effets de l'entité
EffectControllerComponent controller = store.getComponent(
entityRef,
EffectControllerComponent.getComponentType()
);
```
## Ajouter des Effets
### Ajout Basique
```java
EntityEffect effect = EntityEffect.getAssetMap().getAsset("speed_boost");
// Ajouter avec les paramètres par défaut
controller.addEffect(
entityRef,
effect,
componentAccessor
);
```
### Avec Durée Personnalisée
```java
// Ajouter avec durée personnalisée et comportement de superposition
controller.addEffect(
entityRef,
effect,
100.0f, // Durée en secondes
OverlapBehavior.EXTEND, // Comment gérer la superposition
componentAccessor
);
```
### Effets Infinis
```java
// Ajouter un effet qui n'expire jamais
controller.addInfiniteEffect(
entityRef,
EntityEffect.getAssetMap().getIndex(effect.getId()),
effect,
componentAccessor
);
```
## Supprimer des Effets
```java
// Supprimer un effet spécifique
controller.removeEffect(
entityRef,
EntityEffect.getAssetMap().getIndex(effect.getId()),
componentAccessor
);
```
## Comportements de Superposition
```java
public enum OverlapBehavior {
EXTEND, // Ajouter la durée à l'effet existant
OVERWRITE, // Remplacer l'effet existant
IGNORE // Ne pas appliquer si déjà actif
}
```
### Exemples d'Utilisation
```java
// Étendre la durée si déjà appliqué
controller.addEffect(
entityRef,
effect,
30.0f,
OverlapBehavior.EXTEND,
componentAccessor
);
// Remplacer avec une durée fraîche
controller.addEffect(
entityRef,
effect,
30.0f,
OverlapBehavior.OVERWRITE,
componentAccessor
);
// Appliquer seulement si pas déjà actif
controller.addEffect(
entityRef,
effect,
30.0f,
OverlapBehavior.IGNORE,
componentAccessor
);
```
## Comportements de Suppression
```java
public enum RemovalBehavior {
COMPLETE, // Supprimer quand terminé
DURATION, // Supprimer après la durée
INFINITE // Ne jamais supprimer automatiquement
}
```
## Types de Valeurs pour les Stats
```java
public enum ValueType {
Absolute, // Ajouter/soustraire une valeur fixe
Percent // Multiplier par un pourcentage
}
```
## Exemples Pratiques
### Buff par Commande
```java
public class BuffCommand extends AbstractCommand {
private final RequiredArg<PlayerRef> playerArg;
private final RequiredArg<String> effectArg;
private final RequiredArg<Integer> durationArg;
// Serait défini lors de l'initialisation du plugin
private Store<EntityStore> store;
private ComponentAccessor<EntityStore> componentAccessor;
public BuffCommand() {
super("buff", "effects.command.buff.description");
this.playerArg = withRequiredArg("player", "Joueur cible", ArgTypes.PLAYER_REF);
this.effectArg = withRequiredArg("effect", "ID de l'effet", ArgTypes.STRING);
this.durationArg = withRequiredArg("duration", "Durée en secondes", 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("Joueur non trouvé"));
return null;
}
EntityEffect effect = EntityEffect.getAssetMap().getAsset(effectId);
if (effect == null) {
ctx.sender().sendMessage(Message.raw("Effet inconnu : " + 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("Appliqué " + effectId + " à " + target.getUsername()));
return null;
}
}
```
{{< callout type="warning" >}}
**Notes API :**
- Utilisez `withRequiredArg()` pour définir les arguments, pas `addArgument()`
- Utilisez `arg.get(ctx)` pour récupérer les valeurs, pas `ctx.getArg()`
- Utilisez `ctx.sender()` pas `ctx.getSender()`
- Utilisez `target.getUsername()` pas `target.getName()`
- Utilisez `target.getReference()` pour obtenir la référence d'entité (PlayerRef n'a pas `getPlayer()`)
{{< /callout >}}
### Effet de Dégâts dans le Temps
```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("Vous avez été empoisonné !"));
}
```
### Invulnérabilité Temporaire
```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, // Durée fraîche
componentAccessor
);
}
}
```
### Effacer Tous les Effets
```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) {
// Utiliser la méthode clearEffects intégrée
controller.clearEffects(
entityRef,
componentAccessor
);
}
player.sendMessage(Message.raw("Tous les effets effacés !"));
}
```
## Combiner avec d'Autres Effets
Les effets d'entité fonctionnent bien avec les particules et les lumières dynamiques :
```java
public void applyMagicBuff(Player player, Store<EntityStore> store,
ComponentAccessor<EntityStore> componentAccessor) {
Ref<EntityStore> entityRef = player.getReference();
// Appliquer l'effet de statut
EntityEffect buff = EntityEffect.getAssetMap().getAsset("magic_power");
EffectControllerComponent controller = store.getComponent(
entityRef,
EffectControllerComponent.getComponentType()
);
if (controller != null && buff != null) {
controller.addEffect(entityRef, buff, componentAccessor);
}
// Ajouter une lueur visuelle
ColorLight glow = new ColorLight((byte) 10, (byte) 200, (byte) 50, (byte) 255);
DynamicLight light = new DynamicLight(glow);
componentAccessor.putComponent(
entityRef,
DynamicLight.getComponentType(),
light
);
// Obtenir la position via TransformComponent pour les particules
TransformComponent transform = store.getComponent(entityRef, TransformComponent.getComponentType());
if (transform != null) {
ParticleUtil.spawnParticleEffect(
"magic_aura",
transform.getPosition(),
componentAccessor
);
}
}
```
{{< callout type="info" >}}
**Note :** Utilisez `player.getReference()` pour obtenir la référence d'entité. La position doit être obtenue via `TransformComponent`, pas directement depuis Player.
{{< /callout >}}
## Bonnes Pratiques
{{< callout type="info" >}}
**Directives pour les Effets :**
- Utilisez `EXTEND` pour les buffs cumulables afin de récompenser l'application répétée
- Utilisez `OVERWRITE` pour les effets qui doivent réinitialiser leur durée
- Utilisez `IGNORE` pour empêcher l'empilement d'effets quand indésirable
- Vérifiez toujours si l'effet existe avant de l'appliquer
- Considérez les performances avec de nombreux effets simultanés
{{< /callout >}}

View File

@@ -0,0 +1,788 @@
---
title: Particles
type: docs
weight: 1
---
The particle system in Hytale provides rich visual effects through configurable emitters and renderers. Create explosions, trails, auras, and complex visual effects with precise control over behavior and appearance.
**Package:** `com.hypixel.hytale.server.core.asset.type.particle`
{{< cards cols="3" >}}
{{< card link="#spawning-particles" title="Spawning" subtitle="Create particle effects" icon="sparkles" >}}
{{< card link="#particle-system-configuration" title="Configuration" subtitle="System and spawner setup" icon="adjustments" >}}
{{< card link="#particle-attractors" title="Attractors" subtitle="Forces and movement" icon="arrow-circle-right" >}}
{{< /cards >}}
---
## Spawning Particles
### ParticleUtil Methods
{{< tabs items="Basic,Advanced,WorldParticle" >}}
{{< tab >}}
```java
import com.hypixel.hytale.server.core.universe.world.ParticleUtil;
// Basic spawn at position (auto-finds nearby players)
ParticleUtil.spawnParticleEffect(
"explosion_small", // Particle system ID
position, // Vector3d position
componentAccessor
);
// Spawn for specific players
ParticleUtil.spawnParticleEffect(
"magic_trail",
position,
playerRefs, // List<Ref<EntityStore>>
componentAccessor
);
```
**Broadcast Distance:** `DEFAULT_PARTICLE_DISTANCE = 75` blocks
{{< /tab >}}
{{< tab >}}
```java
// With source entity reference
ParticleUtil.spawnParticleEffect(
"attack_swing",
position,
sourceEntityRef, // Entity that spawned this
playerRefs,
componentAccessor
);
// With rotation control
ParticleUtil.spawnParticleEffect(
"directional_beam",
position,
yaw, pitch, roll, // Rotation angles
sourceEntityRef,
playerRefs,
componentAccessor
);
// With scale and color
ParticleUtil.spawnParticleEffect(
"colored_burst",
position,
yaw, pitch, roll,
2.0f, // Scale multiplier
new Color(255, 100, 50, 255), // RGBA color
playerRefs,
componentAccessor
);
```
{{< /tab >}}
{{< tab >}}
```java
import com.hypixel.hytale.server.core.asset.type.particle.config.WorldParticle;
// Full control with WorldParticle wrapper
WorldParticle worldParticle = new WorldParticle(
"my_particle_system",
new Color(255, 100, 50, 255), // RGBA color override
1.5f, // Scale multiplier
new Vector3f(0, 1, 0), // Position offset
new Direction(0, 0, 0) // Rotation offset
);
ParticleUtil.spawnParticleEffect(
worldParticle,
position,
sourceRef,
playerRefs,
componentAccessor
);
```
{{< /tab >}}
{{< /tabs >}}
---
## Particle System Configuration
### ParticleSystem Class
The root configuration for a particle effect:
```java
public class ParticleSystem {
protected String id; // Unique identifier
protected ParticleSpawnerGroup[] spawners; // Spawner configurations
protected float lifeSpan; // System lifetime (seconds)
protected float cullDistance; // Distance before culling
protected float boundingRadius; // Collision bounding box
protected boolean isImportant; // Network priority flag
}
```
| Field | Type | Description |
|-------|------|-------------|
| `id` | String | Unique identifier for the particle system |
| `spawners` | ParticleSpawnerGroup[] | Array of spawner configurations |
| `lifeSpan` | float | How long the system lives (seconds) |
| `cullDistance` | float | Distance at which particles are culled |
| `boundingRadius` | float | Bounding sphere for culling calculations |
| `isImportant` | boolean | If true, prioritized in network sync |
### ParticleSpawner Configuration
Controls how individual particles are emitted:
{{< tabs items="Properties,Emission,Movement,Rendering" >}}
{{< tab >}}
```java
public class ParticleSpawner {
protected String id;
protected EmitShape shape;
protected RangeVector3f emitOffset;
protected boolean useEmitDirection;
protected Range totalParticles;
protected float lifeSpan;
protected int maxConcurrentParticles;
protected Rangef particleLifeSpan;
protected Rangef spawnRate;
protected boolean spawnBurst;
protected InitialVelocity initialVelocity;
protected ParticleAttractor[] attractors;
protected FXRenderMode renderMode;
protected float lightInfluence;
protected Particle particle;
}
```
{{< /tab >}}
{{< tab >}}
**Emission Properties:**
| Property | Type | Description |
|----------|------|-------------|
| `shape` | EmitShape | Sphere or Cube emission shape |
| `emitOffset` | RangeVector3f | Random offset range from origin |
| `useEmitDirection` | boolean | Use spawn direction for velocity |
| `totalParticles` | Range | Min/max total particles to emit |
| `spawnRate` | Rangef | Particles per second |
| `spawnBurst` | boolean | Emit all at once vs over time |
**Example emission setup:**
```java
// Burst explosion - all at once
spawner.setSpawnBurst(true);
spawner.setTotalParticles(new Range(50, 100));
// Continuous stream
spawner.setSpawnBurst(false);
spawner.setSpawnRate(new Rangef(10, 20)); // 10-20 per second
spawner.setLifeSpan(5.0f); // Emit for 5 seconds
```
{{< /tab >}}
{{< tab >}}
**Movement Properties:**
| Property | Type | Description |
|----------|------|-------------|
| `initialVelocity` | InitialVelocity | Starting velocity configuration |
| `attractors` | ParticleAttractor[] | Forces applied to particles |
**Initial Velocity:**
```java
public class InitialVelocity {
protected float speed; // Base speed
protected float speedVariance; // Random variance
protected Vector3f direction; // Base direction
protected float coneAngle; // Spread angle (degrees)
}
```
{{< /tab >}}
{{< tab >}}
**Rendering Properties:**
| Property | Type | Description |
|----------|------|-------------|
| `renderMode` | FXRenderMode | How particles are blended |
| `lightInfluence` | float | How much lighting affects particles |
| `particle` | Particle | Texture and animation config |
{{< /tab >}}
{{< /tabs >}}
---
## Emit Shapes
Control the volume from which particles spawn:
```java
public enum EmitShape {
Sphere, // Emit from spherical volume
Cube // Emit from cubic volume
}
```
{{< tabs items="Sphere,Cube" >}}
{{< tab >}}
**Spherical Emission:**
Particles spawn within a spherical volume, with direction pointing outward from center:
```java
// Configure spherical emission
spawner.setShape(EmitShape.Sphere);
spawner.setEmitOffset(new RangeVector3f(
new Rangef(-1, 1), // X range (radius)
new Rangef(-1, 1), // Y range (radius)
new Rangef(-1, 1) // Z range (radius)
));
// Use emit direction for outward velocity
spawner.setUseEmitDirection(true);
```
Best for: Explosions, bursts, radial effects
{{< /tab >}}
{{< tab >}}
**Cubic Emission:**
Particles spawn within an axis-aligned box volume:
```java
// Configure cubic emission
spawner.setShape(EmitShape.Cube);
spawner.setEmitOffset(new RangeVector3f(
new Rangef(-2, 2), // X range
new Rangef(0, 3), // Y range (above ground)
new Rangef(-2, 2) // Z range
));
```
Best for: Area effects, rain, ground effects
{{< /tab >}}
{{< /tabs >}}
---
## Render Modes
Determine how particles blend with the scene:
```java
public enum FXRenderMode {
BlendLinear, // Standard transparency blending
BlendAdd, // Additive blending (bright/glowing)
Erosion, // Erosion/dissolve effect
Distortion // Distortion/refraction effect
}
```
{{< tabs items="BlendLinear,BlendAdd,Erosion,Distortion" >}}
{{< tab >}}
### BlendLinear
Standard alpha blending. Best for:
- Smoke
- Dust
- Clouds
- Solid-looking particles
```java
spawner.setRenderMode(FXRenderMode.BlendLinear);
```
{{< /tab >}}
{{< tab >}}
### BlendAdd
Additive blending - particles add light to the scene. Best for:
- Fire
- Sparks
- Magic effects
- Glowing particles
- Light beams
```java
spawner.setRenderMode(FXRenderMode.BlendAdd);
```
{{< callout type="info" >}}
Use `BlendAdd` for any glowing or luminous effects. Multiple overlapping particles will create brighter spots.
{{< /callout >}}
{{< /tab >}}
{{< tab >}}
### Erosion
Creates dissolving/eroding visual effect. Best for:
- Disintegration
- Dissolve transitions
- Energy dissipation
```java
spawner.setRenderMode(FXRenderMode.Erosion);
```
{{< /tab >}}
{{< tab >}}
### Distortion
Refracts the background, creating heat-shimmer effects. Best for:
- Heat waves
- Portals
- Energy fields
- Underwater effects
```java
spawner.setRenderMode(FXRenderMode.Distortion);
```
{{< callout type="warning" >}}
Distortion effects are more GPU-intensive. Use sparingly.
{{< /callout >}}
{{< /tab >}}
{{< /tabs >}}
---
## Particle Attractors
Apply forces to particles for dynamic movement:
```java
public class ParticleAttractor {
protected Vector3f position; // Local attractor position
protected Vector3f radialAxis; // Radial force direction
protected float radius; // Influence radius
// Accelerations (continuous forces)
protected float radialAcceleration; // Outward/inward acceleration
protected float radialTangentAcceleration; // Tangential (orbit) acceleration
protected Vector3f linearAcceleration; // Direct linear acceleration
// Impulses (one-time forces)
protected float radialImpulse; // Outward/inward impulse
protected float radialTangentImpulse; // Tangential impulse
protected Vector3f linearImpulse; // Direct linear impulse
// Damping
protected Vector3f dampingMultiplier; // Velocity reduction per frame
}
```
### Force Types
{{< tabs items="Radial,Tangential,Linear,Damping" >}}
{{< tab >}}
**Radial Forces:**
Push particles toward or away from the attractor position:
```java
ParticleAttractor attractor = new ParticleAttractor();
attractor.setPosition(new Vector3f(0, 0, 0)); // Center of system
// Outward explosion
attractor.setRadialAcceleration(10.0f); // Positive = outward
// Inward pull (black hole effect)
attractor.setRadialAcceleration(-5.0f); // Negative = inward
// Instant outward burst
attractor.setRadialImpulse(20.0f);
```
{{< /tab >}}
{{< tab >}}
**Tangential Forces:**
Create swirling/orbiting motion around the attractor:
```java
ParticleAttractor attractor = new ParticleAttractor();
attractor.setPosition(new Vector3f(0, 0, 0));
attractor.setRadialAxis(new Vector3f(0, 1, 0)); // Orbit around Y axis
// Clockwise swirl
attractor.setRadialTangentAcceleration(5.0f);
// Counter-clockwise
attractor.setRadialTangentAcceleration(-5.0f);
```
{{< /tab >}}
{{< tab >}}
**Linear Forces:**
Apply constant directional force (like gravity or wind):
```java
ParticleAttractor attractor = new ParticleAttractor();
// Gravity (downward)
attractor.setLinearAcceleration(new Vector3f(0, -9.8f, 0));
// Wind (horizontal)
attractor.setLinearAcceleration(new Vector3f(2.0f, 0, 0));
// Upward buoyancy
attractor.setLinearAcceleration(new Vector3f(0, 3.0f, 0));
```
{{< /tab >}}
{{< tab >}}
**Damping:**
Slow particles down over time:
```java
ParticleAttractor attractor = new ParticleAttractor();
// Uniform damping (air resistance)
attractor.setDampingMultiplier(new Vector3f(0.98f, 0.98f, 0.98f));
// Strong horizontal damping, weak vertical
attractor.setDampingMultiplier(new Vector3f(0.9f, 0.99f, 0.9f));
// No damping (particles maintain velocity)
attractor.setDampingMultiplier(new Vector3f(1.0f, 1.0f, 1.0f));
```
{{< callout type="info" >}}
Damping values < 1.0 slow particles. Values of 0.98-0.99 give realistic air resistance.
{{< /callout >}}
{{< /tab >}}
{{< /tabs >}}
---
## Particle Visual Configuration
Configure particle appearance and animation:
```java
public class Particle {
protected String texture; // Texture atlas path
protected Size frameSize; // Frame dimensions
protected ParticleUVOption uvOption; // None, Animated, Random
protected SoftParticle softParticle; // Soft particle blending
protected float softParticlesFadeFactor; // 0.1 to 2.0
// Animation
protected ParticleAnimationFrame initialAnimationFrame;
protected Map<Integer, ParticleAnimationFrame> animation;
}
```
### Animation Options
```java
public enum ParticleUVOption {
None, // Single static frame
Animated, // Play through frames in sequence
Random // Random frame per particle
}
```
### Animation Frames
```java
public class ParticleAnimationFrame {
protected int frame; // Frame number in atlas
protected Rangef scale; // Size range
protected Rangef alpha; // Transparency range (0-1)
protected Color color; // Color tint
protected Rangef rotation; // Rotation range (degrees)
}
```
---
## Accessing Particle Assets
```java
// Get particle system by ID
ParticleSystem system = ParticleSystem.getAssetMap().getAsset("explosion_large");
// Get spawner configuration
ParticleSpawner spawner = ParticleSpawner.getAssetMap().getAsset("fire_spawner");
// Use in command arguments
// ArgTypes.PARTICLE_SYSTEM for command parameters
```
---
## Common Patterns
### Event-Based Particles
{{< callout type="warning" >}}
**Note:** The examples below are simplified. Entity doesn't have a direct `getPosition()` method. In real code, obtain position via `TransformComponent` from the entity's store. Example:
```java
TransformComponent transform = store.getComponent(entityRef, TransformComponent.getComponentType());
Vector3d position = transform.getPosition();
```
{{< /callout >}}
{{< tabs items="Block Break,Combat,Death" >}}
{{< tab >}}
```java
@Subscribe
public void onBlockBreak(BreakBlockEvent event) {
// Use getTargetBlock() - not getPosition()
Vector3i blockPos = event.getTargetBlock();
Vector3d pos = new Vector3d(
blockPos.x + 0.5,
blockPos.y + 0.5,
blockPos.z + 0.5
);
// Spawn break particles at block center
ParticleUtil.spawnParticleEffect(
"block_break_particles",
pos,
componentAccessor
);
}
```
{{< /tab >}}
{{< tab >}}
```java
@Subscribe
public void onEntityDamage(EntityDamageEvent event) {
Entity target = event.getTarget();
Vector3d hitPos = target.getPosition().add(0, 1, 0);
// Blood/damage effect
ParticleUtil.spawnParticleEffect(
"damage_hit",
hitPos,
componentAccessor
);
// Critical hit special effect
if (event.isCritical()) {
ParticleUtil.spawnParticleEffect(
"critical_hit_sparks",
hitPos,
0, 0, 0, // rotation
1.5f, // larger scale
new Color(255, 215, 0, 255), // gold color
getNearbyPlayers(hitPos, 50),
componentAccessor
);
}
}
```
{{< /tab >}}
{{< tab >}}
```java
@Subscribe
public void onEntityDeath(EntityDeathEvent event) {
Entity entity = event.getEntity();
Vector3d deathPos = entity.getPosition();
// Death particles
ParticleUtil.spawnParticleEffect(
"entity_death_poof",
deathPos,
componentAccessor
);
// Soul rising effect for players
if (entity instanceof Player) {
ParticleUtil.spawnParticleEffect(
"soul_ascend",
deathPos.add(0, 0.5, 0),
componentAccessor
);
}
}
```
{{< /tab >}}
{{< /tabs >}}
### Continuous Effects
{{< tabs items="Player Trail,Aura,Area Effect" >}}
{{< tab >}}
```java
// Spawn particle trail at player position
public void spawnTrailEffect(Player player, Store<EntityStore> store,
ComponentAccessor<EntityStore> componentAccessor) {
if (hasTrailEffect(player)) {
// Get position from TransformComponent
Ref<EntityStore> entityRef = player.getReference();
TransformComponent transform = store.getComponent(entityRef, TransformComponent.getComponentType());
if (transform != null) {
Vector3d position = transform.getPosition();
ParticleUtil.spawnParticleEffect(
"magic_trail",
position,
componentAccessor
);
}
}
}
```
{{< callout type="warning" >}}
**Note:** Player doesn't have a `getPosition()` method. Get position via `TransformComponent` from the entity store.
{{< /callout >}}
{{< /tab >}}
{{< tab >}}
```java
// Persistent aura around player
public class AuraManager {
private final Map<UUID, Boolean> activeAuras = new HashMap<>();
private Store<EntityStore> store;
private ComponentAccessor<EntityStore> componentAccessor;
public void enableAura(Player player) {
// Use player.getPlayerRef().getUuid() - not player.getUuid()
activeAuras.put(player.getPlayerRef().getUuid(), true);
}
@Subscribe
public void onTick(ServerTickEvent event) {
for (Map.Entry<UUID, Boolean> entry : activeAuras.entrySet()) {
if (entry.getValue()) {
Player player = getPlayer(entry.getKey());
if (player != null) {
// Get position from TransformComponent
Ref<EntityStore> entityRef = player.getReference();
TransformComponent transform = store.getComponent(
entityRef, TransformComponent.getComponentType());
if (transform != null) {
Vector3d pos = transform.getPosition();
ParticleUtil.spawnParticleEffect(
"magic_aura",
new Vector3d(pos.getX(), pos.getY() + 1, pos.getZ()),
componentAccessor
);
}
}
}
}
}
}
```
{{< /tab >}}
{{< tab >}}
```java
// Area healing effect
public void createHealingZone(Vector3d center, double radius, int durationTicks) {
// Spawn ring particles
for (int i = 0; i < 16; i++) {
double angle = (2 * Math.PI * i) / 16;
Vector3d pos = center.add(
Math.cos(angle) * radius,
0.1,
Math.sin(angle) * radius
);
ParticleUtil.spawnParticleEffect(
"healing_sparkle",
pos,
componentAccessor
);
}
// Central pillar effect
ParticleUtil.spawnParticleEffect(
"healing_pillar",
center,
componentAccessor
);
}
```
{{< /tab >}}
{{< /tabs >}}
---
## Performance Guidelines
{{< callout type="warning" >}}
**Performance Notes:**
- Default broadcast distance: **75 blocks** (`DEFAULT_PARTICLE_DISTANCE`)
- Particles use `SpawnParticleSystem` packet (ID: 152)
- Set `isImportant = true` for critical visual feedback
- Limit particle count for client performance
- Use `maxConcurrentParticles` to cap active particles
{{< /callout >}}
### Optimization Tips
| Tip | Description |
|-----|-------------|
| Limit total particles | Keep under 100-200 for burst effects |
| Use appropriate `cullDistance` | Don't render particles too far away |
| Batch spawns | Spawn multiple particles in same tick |
| Use `spawnBurst` wisely | Bursts are cheaper than continuous |
| Consider player count | More players = more network traffic |
```java
// Good: Efficient particle spawning
public void spawnEfficiently(Vector3d position) {
// Only spawn for nearby players
List<PlayerRef> nearbyPlayers = getNearbyPlayers(position, 50);
if (!nearbyPlayers.isEmpty()) {
ParticleUtil.spawnParticleEffect(
"my_effect",
position,
nearbyPlayers, // Limited audience
componentAccessor
);
}
}
```
---
## Related Topics
- [Entity Effects]({{< relref "entity-effects" >}}) - Status effects with visual components
- [Networking]({{< relref "/advanced/networking" >}}) - How particles are broadcast
- [Events]({{< relref "/core-concepts/events" >}}) - Triggering particles from events

View File

@@ -0,0 +1,788 @@
---
title: Particules
type: docs
weight: 1
---
Le système de particules de Hytale fournit des effets visuels riches à travers des émetteurs et renderers configurables. Créez des explosions, traînées, auras et effets visuels complexes avec un contrôle précis sur le comportement et l'apparence.
**Package :** `com.hypixel.hytale.server.core.asset.type.particle`
{{< cards cols="3" >}}
{{< card link="#générer-des-particules" title="Génération" subtitle="Créer des effets de particules" icon="sparkles" >}}
{{< card link="#configuration-du-système-de-particules" title="Configuration" subtitle="Config système et spawner" icon="adjustments" >}}
{{< card link="#attracteurs-de-particules" title="Attracteurs" subtitle="Forces et mouvement" icon="arrow-circle-right" >}}
{{< /cards >}}
---
## Générer des Particules
### Méthodes ParticleUtil
{{< tabs items="Basique,Avancé,WorldParticle" >}}
{{< tab >}}
```java
import com.hypixel.hytale.server.core.universe.world.ParticleUtil;
// Génération basique à une position (trouve auto les joueurs proches)
ParticleUtil.spawnParticleEffect(
"explosion_small", // ID du système de particules
position, // Position Vector3d
componentAccessor
);
// Générer pour des joueurs spécifiques
ParticleUtil.spawnParticleEffect(
"magic_trail",
position,
playerRefs, // List<Ref<EntityStore>>
componentAccessor
);
```
**Distance de Diffusion :** `DEFAULT_PARTICLE_DISTANCE = 75` blocs
{{< /tab >}}
{{< tab >}}
```java
// Avec référence à l'entité source
ParticleUtil.spawnParticleEffect(
"attack_swing",
position,
sourceEntityRef, // Entité qui a généré ceci
playerRefs,
componentAccessor
);
// Avec contrôle de rotation
ParticleUtil.spawnParticleEffect(
"directional_beam",
position,
yaw, pitch, roll, // Angles de rotation
sourceEntityRef,
playerRefs,
componentAccessor
);
// Avec échelle et couleur
ParticleUtil.spawnParticleEffect(
"colored_burst",
position,
yaw, pitch, roll,
2.0f, // Multiplicateur d'échelle
new Color(255, 100, 50, 255), // Couleur RGBA
playerRefs,
componentAccessor
);
```
{{< /tab >}}
{{< tab >}}
```java
import com.hypixel.hytale.server.core.asset.type.particle.config.WorldParticle;
// Contrôle complet avec le wrapper WorldParticle
WorldParticle worldParticle = new WorldParticle(
"my_particle_system",
new Color(255, 100, 50, 255), // Override couleur RGBA
1.5f, // Multiplicateur d'échelle
new Vector3f(0, 1, 0), // Offset de position
new Direction(0, 0, 0) // Offset de rotation
);
ParticleUtil.spawnParticleEffect(
worldParticle,
position,
sourceRef,
playerRefs,
componentAccessor
);
```
{{< /tab >}}
{{< /tabs >}}
---
## Configuration du Système de Particules
### Classe ParticleSystem
La configuration racine pour un effet de particules :
```java
public class ParticleSystem {
protected String id; // Identifiant unique
protected ParticleSpawnerGroup[] spawners; // Configurations de spawners
protected float lifeSpan; // Durée de vie du système (secondes)
protected float cullDistance; // Distance avant culling
protected float boundingRadius; // Boîte englobante de collision
protected boolean isImportant; // Flag de priorité réseau
}
```
| Champ | Type | Description |
|-------|------|-------------|
| `id` | String | Identifiant unique du système de particules |
| `spawners` | ParticleSpawnerGroup[] | Tableau de configurations de spawners |
| `lifeSpan` | float | Durée de vie du système (secondes) |
| `cullDistance` | float | Distance à laquelle les particules sont culled |
| `boundingRadius` | float | Sphère englobante pour les calculs de culling |
| `isImportant` | boolean | Si vrai, prioritisé dans la sync réseau |
### Configuration ParticleSpawner
Contrôle comment les particules individuelles sont émises :
{{< tabs items="Propriétés,Émission,Mouvement,Rendu" >}}
{{< tab >}}
```java
public class ParticleSpawner {
protected String id;
protected EmitShape shape;
protected RangeVector3f emitOffset;
protected boolean useEmitDirection;
protected Range totalParticles;
protected float lifeSpan;
protected int maxConcurrentParticles;
protected Rangef particleLifeSpan;
protected Rangef spawnRate;
protected boolean spawnBurst;
protected InitialVelocity initialVelocity;
protected ParticleAttractor[] attractors;
protected FXRenderMode renderMode;
protected float lightInfluence;
protected Particle particle;
}
```
{{< /tab >}}
{{< tab >}}
**Propriétés d'Émission :**
| Propriété | Type | Description |
|-----------|------|-------------|
| `shape` | EmitShape | Forme d'émission Sphere ou Cube |
| `emitOffset` | RangeVector3f | Plage d'offset aléatoire depuis l'origine |
| `useEmitDirection` | boolean | Utiliser la direction de spawn pour la vélocité |
| `totalParticles` | Range | Nombre min/max total de particules à émettre |
| `spawnRate` | Rangef | Particules par seconde |
| `spawnBurst` | boolean | Émettre tout d'un coup vs sur la durée |
**Exemple de config d'émission :**
```java
// Explosion burst - tout d'un coup
spawner.setSpawnBurst(true);
spawner.setTotalParticles(new Range(50, 100));
// Flux continu
spawner.setSpawnBurst(false);
spawner.setSpawnRate(new Rangef(10, 20)); // 10-20 par seconde
spawner.setLifeSpan(5.0f); // Émettre pendant 5 secondes
```
{{< /tab >}}
{{< tab >}}
**Propriétés de Mouvement :**
| Propriété | Type | Description |
|-----------|------|-------------|
| `initialVelocity` | InitialVelocity | Configuration de vélocité initiale |
| `attractors` | ParticleAttractor[] | Forces appliquées aux particules |
**Vélocité Initiale :**
```java
public class InitialVelocity {
protected float speed; // Vitesse de base
protected float speedVariance; // Variance aléatoire
protected Vector3f direction; // Direction de base
protected float coneAngle; // Angle de dispersion (degrés)
}
```
{{< /tab >}}
{{< tab >}}
**Propriétés de Rendu :**
| Propriété | Type | Description |
|-----------|------|-------------|
| `renderMode` | FXRenderMode | Comment les particules sont blendées |
| `lightInfluence` | float | Influence de l'éclairage sur les particules |
| `particle` | Particle | Config texture et animation |
{{< /tab >}}
{{< /tabs >}}
---
## Formes d'Émission
Contrôle le volume depuis lequel les particules apparaissent :
```java
public enum EmitShape {
Sphere, // Émettre depuis un volume sphérique
Cube // Émettre depuis un volume cubique
}
```
{{< tabs items="Sphere,Cube" >}}
{{< tab >}}
**Émission Sphérique :**
Les particules apparaissent dans un volume sphérique, avec la direction pointant vers l'extérieur du centre :
```java
// Configurer l'émission sphérique
spawner.setShape(EmitShape.Sphere);
spawner.setEmitOffset(new RangeVector3f(
new Rangef(-1, 1), // Plage X (rayon)
new Rangef(-1, 1), // Plage Y (rayon)
new Rangef(-1, 1) // Plage Z (rayon)
));
// Utiliser la direction d'émission pour la vélocité vers l'extérieur
spawner.setUseEmitDirection(true);
```
Idéal pour : Explosions, bursts, effets radiaux
{{< /tab >}}
{{< tab >}}
**Émission Cubique :**
Les particules apparaissent dans un volume de boîte aligné sur les axes :
```java
// Configurer l'émission cubique
spawner.setShape(EmitShape.Cube);
spawner.setEmitOffset(new RangeVector3f(
new Rangef(-2, 2), // Plage X
new Rangef(0, 3), // Plage Y (au-dessus du sol)
new Rangef(-2, 2) // Plage Z
));
```
Idéal pour : Effets de zone, pluie, effets au sol
{{< /tab >}}
{{< /tabs >}}
---
## Modes de Rendu
Détermine comment les particules se mélangent avec la scène :
```java
public enum FXRenderMode {
BlendLinear, // Blending de transparence standard
BlendAdd, // Blending additif (brillant/lumineux)
Erosion, // Effet d'érosion/dissolution
Distortion // Effet de distorsion/réfraction
}
```
{{< tabs items="BlendLinear,BlendAdd,Erosion,Distortion" >}}
{{< tab >}}
### BlendLinear
Blending alpha standard. Idéal pour :
- Fumée
- Poussière
- Nuages
- Particules d'apparence solide
```java
spawner.setRenderMode(FXRenderMode.BlendLinear);
```
{{< /tab >}}
{{< tab >}}
### BlendAdd
Blending additif - les particules ajoutent de la lumière à la scène. Idéal pour :
- Feu
- Étincelles
- Effets magiques
- Particules lumineuses
- Rayons de lumière
```java
spawner.setRenderMode(FXRenderMode.BlendAdd);
```
{{< callout type="info" >}}
Utilisez `BlendAdd` pour tout effet lumineux ou brillant. Plusieurs particules superposées créeront des zones plus lumineuses.
{{< /callout >}}
{{< /tab >}}
{{< tab >}}
### Erosion
Crée un effet visuel de dissolution/érosion. Idéal pour :
- Désintégration
- Transitions de dissolution
- Dissipation d'énergie
```java
spawner.setRenderMode(FXRenderMode.Erosion);
```
{{< /tab >}}
{{< tab >}}
### Distortion
Réfracte l'arrière-plan, créant des effets de miroitement de chaleur. Idéal pour :
- Vagues de chaleur
- Portails
- Champs d'énergie
- Effets sous-marins
```java
spawner.setRenderMode(FXRenderMode.Distortion);
```
{{< callout type="warning" >}}
Les effets de distorsion sont plus intensifs en GPU. Utilisez avec parcimonie.
{{< /callout >}}
{{< /tab >}}
{{< /tabs >}}
---
## Attracteurs de Particules
Applique des forces aux particules pour un mouvement dynamique :
```java
public class ParticleAttractor {
protected Vector3f position; // Position locale de l'attracteur
protected Vector3f radialAxis; // Direction de la force radiale
protected float radius; // Rayon d'influence
// Accélérations (forces continues)
protected float radialAcceleration; // Accélération vers l'extérieur/intérieur
protected float radialTangentAcceleration; // Accélération tangentielle (orbite)
protected Vector3f linearAcceleration; // Accélération linéaire directe
// Impulsions (forces ponctuelles)
protected float radialImpulse; // Impulsion vers l'extérieur/intérieur
protected float radialTangentImpulse; // Impulsion tangentielle
protected Vector3f linearImpulse; // Impulsion linéaire directe
// Amortissement
protected Vector3f dampingMultiplier; // Réduction de vélocité par frame
}
```
### Types de Forces
{{< tabs items="Radiale,Tangentielle,Linéaire,Amortissement" >}}
{{< tab >}}
**Forces Radiales :**
Pousse les particules vers ou loin de la position de l'attracteur :
```java
ParticleAttractor attractor = new ParticleAttractor();
attractor.setPosition(new Vector3f(0, 0, 0)); // Centre du système
// Explosion vers l'extérieur
attractor.setRadialAcceleration(10.0f); // Positif = vers l'extérieur
// Attraction vers l'intérieur (effet trou noir)
attractor.setRadialAcceleration(-5.0f); // Négatif = vers l'intérieur
// Burst instantané vers l'extérieur
attractor.setRadialImpulse(20.0f);
```
{{< /tab >}}
{{< tab >}}
**Forces Tangentielles :**
Crée un mouvement tourbillonnant/orbital autour de l'attracteur :
```java
ParticleAttractor attractor = new ParticleAttractor();
attractor.setPosition(new Vector3f(0, 0, 0));
attractor.setRadialAxis(new Vector3f(0, 1, 0)); // Orbite autour de l'axe Y
// Tourbillon horaire
attractor.setRadialTangentAcceleration(5.0f);
// Anti-horaire
attractor.setRadialTangentAcceleration(-5.0f);
```
{{< /tab >}}
{{< tab >}}
**Forces Linéaires :**
Applique une force directionnelle constante (comme la gravité ou le vent) :
```java
ParticleAttractor attractor = new ParticleAttractor();
// Gravité (vers le bas)
attractor.setLinearAcceleration(new Vector3f(0, -9.8f, 0));
// Vent (horizontal)
attractor.setLinearAcceleration(new Vector3f(2.0f, 0, 0));
// Flottabilité vers le haut
attractor.setLinearAcceleration(new Vector3f(0, 3.0f, 0));
```
{{< /tab >}}
{{< tab >}}
**Amortissement :**
Ralentit les particules au fil du temps :
```java
ParticleAttractor attractor = new ParticleAttractor();
// Amortissement uniforme (résistance de l'air)
attractor.setDampingMultiplier(new Vector3f(0.98f, 0.98f, 0.98f));
// Fort amortissement horizontal, faible vertical
attractor.setDampingMultiplier(new Vector3f(0.9f, 0.99f, 0.9f));
// Pas d'amortissement (particules maintiennent leur vélocité)
attractor.setDampingMultiplier(new Vector3f(1.0f, 1.0f, 1.0f));
```
{{< callout type="info" >}}
Les valeurs d'amortissement < 1.0 ralentissent les particules. Des valeurs de 0.98-0.99 donnent une résistance de l'air réaliste.
{{< /callout >}}
{{< /tab >}}
{{< /tabs >}}
---
## Configuration Visuelle des Particules
Configure l'apparence et l'animation des particules :
```java
public class Particle {
protected String texture; // Chemin de l'atlas de texture
protected Size frameSize; // Dimensions du frame
protected ParticleUVOption uvOption; // None, Animated, Random
protected SoftParticle softParticle; // Blending soft particle
protected float softParticlesFadeFactor; // 0.1 à 2.0
// Animation
protected ParticleAnimationFrame initialAnimationFrame;
protected Map<Integer, ParticleAnimationFrame> animation;
}
```
### Options d'Animation
```java
public enum ParticleUVOption {
None, // Frame unique statique
Animated, // Jouer les frames en séquence
Random // Frame aléatoire par particule
}
```
### Frames d'Animation
```java
public class ParticleAnimationFrame {
protected int frame; // Numéro de frame dans l'atlas
protected Rangef scale; // Plage de taille
protected Rangef alpha; // Plage de transparence (0-1)
protected Color color; // Teinte de couleur
protected Rangef rotation; // Plage de rotation (degrés)
}
```
---
## Accéder aux Assets de Particules
```java
// Obtenir un système de particules par ID
ParticleSystem system = ParticleSystem.getAssetMap().getAsset("explosion_large");
// Obtenir la configuration d'un spawner
ParticleSpawner spawner = ParticleSpawner.getAssetMap().getAsset("fire_spawner");
// Utiliser dans les arguments de commande
// ArgTypes.PARTICLE_SYSTEM pour les paramètres de commande
```
---
## Patterns Courants
### Particules Basées sur les Événements
{{< callout type="warning" >}}
**Note :** Les exemples ci-dessous sont simplifiés. Entity n'a pas de méthode `getPosition()` directe. Dans le code réel, obtenez la position via `TransformComponent` depuis le store de l'entité. Exemple :
```java
TransformComponent transform = store.getComponent(entityRef, TransformComponent.getComponentType());
Vector3d position = transform.getPosition();
```
{{< /callout >}}
{{< tabs items="Destruction Bloc,Combat,Mort" >}}
{{< tab >}}
```java
@Subscribe
public void onBlockBreak(BreakBlockEvent event) {
// Utiliser getTargetBlock() - pas getPosition()
Vector3i blockPos = event.getTargetBlock();
Vector3d pos = new Vector3d(
blockPos.x + 0.5,
blockPos.y + 0.5,
blockPos.z + 0.5
);
// Générer des particules de destruction au centre du bloc
ParticleUtil.spawnParticleEffect(
"block_break_particles",
pos,
componentAccessor
);
}
```
{{< /tab >}}
{{< tab >}}
```java
@Subscribe
public void onEntityDamage(EntityDamageEvent event) {
Entity target = event.getTarget();
Vector3d hitPos = target.getPosition().add(0, 1, 0);
// Effet de sang/dégâts
ParticleUtil.spawnParticleEffect(
"damage_hit",
hitPos,
componentAccessor
);
// Effet spécial coup critique
if (event.isCritical()) {
ParticleUtil.spawnParticleEffect(
"critical_hit_sparks",
hitPos,
0, 0, 0, // rotation
1.5f, // échelle plus grande
new Color(255, 215, 0, 255), // couleur or
getNearbyPlayers(hitPos, 50),
componentAccessor
);
}
}
```
{{< /tab >}}
{{< tab >}}
```java
@Subscribe
public void onEntityDeath(EntityDeathEvent event) {
Entity entity = event.getEntity();
Vector3d deathPos = entity.getPosition();
// Particules de mort
ParticleUtil.spawnParticleEffect(
"entity_death_poof",
deathPos,
componentAccessor
);
// Effet d'âme qui monte pour les joueurs
if (entity instanceof Player) {
ParticleUtil.spawnParticleEffect(
"soul_ascend",
deathPos.add(0, 0.5, 0),
componentAccessor
);
}
}
```
{{< /tab >}}
{{< /tabs >}}
### Effets Continus
{{< tabs items="Traînée Joueur,Aura,Effet de Zone" >}}
{{< tab >}}
```java
// Générer une traînée de particules à la position du joueur
public void spawnTrailEffect(Player player, Store<EntityStore> store,
ComponentAccessor<EntityStore> componentAccessor) {
if (hasTrailEffect(player)) {
// Obtenir la position via TransformComponent
Ref<EntityStore> entityRef = player.getReference();
TransformComponent transform = store.getComponent(entityRef, TransformComponent.getComponentType());
if (transform != null) {
Vector3d position = transform.getPosition();
ParticleUtil.spawnParticleEffect(
"magic_trail",
position,
componentAccessor
);
}
}
}
```
{{< callout type="warning" >}}
**Note :** Player n'a pas de méthode `getPosition()`. Obtenez la position via `TransformComponent` depuis le store d'entités.
{{< /callout >}}
{{< /tab >}}
{{< tab >}}
```java
// Aura persistante autour du joueur
public class AuraManager {
private final Map<UUID, Boolean> activeAuras = new HashMap<>();
private Store<EntityStore> store;
private ComponentAccessor<EntityStore> componentAccessor;
public void enableAura(Player player) {
// Utiliser player.getPlayerRef().getUuid() - pas player.getUuid()
activeAuras.put(player.getPlayerRef().getUuid(), true);
}
@Subscribe
public void onTick(ServerTickEvent event) {
for (Map.Entry<UUID, Boolean> entry : activeAuras.entrySet()) {
if (entry.getValue()) {
Player player = getPlayer(entry.getKey());
if (player != null) {
// Obtenir la position via TransformComponent
Ref<EntityStore> entityRef = player.getReference();
TransformComponent transform = store.getComponent(
entityRef, TransformComponent.getComponentType());
if (transform != null) {
Vector3d pos = transform.getPosition();
ParticleUtil.spawnParticleEffect(
"magic_aura",
new Vector3d(pos.getX(), pos.getY() + 1, pos.getZ()),
componentAccessor
);
}
}
}
}
}
}
```
{{< /tab >}}
{{< tab >}}
```java
// Effet de zone de soin
public void createHealingZone(Vector3d center, double radius, int durationTicks) {
// Générer des particules en anneau
for (int i = 0; i < 16; i++) {
double angle = (2 * Math.PI * i) / 16;
Vector3d pos = center.add(
Math.cos(angle) * radius,
0.1,
Math.sin(angle) * radius
);
ParticleUtil.spawnParticleEffect(
"healing_sparkle",
pos,
componentAccessor
);
}
// Effet pilier central
ParticleUtil.spawnParticleEffect(
"healing_pillar",
center,
componentAccessor
);
}
```
{{< /tab >}}
{{< /tabs >}}
---
## Directives de Performance
{{< callout type="warning" >}}
**Notes de Performance :**
- Distance de diffusion par défaut : **75 blocs** (`DEFAULT_PARTICLE_DISTANCE`)
- Les particules utilisent le paquet `SpawnParticleSystem` (ID : 152)
- Définissez `isImportant = true` pour les feedbacks visuels critiques
- Limitez le nombre de particules pour les performances client
- Utilisez `maxConcurrentParticles` pour limiter les particules actives
{{< /callout >}}
### Conseils d'Optimisation
| Conseil | Description |
|---------|-------------|
| Limiter le total de particules | Gardez sous 100-200 pour les effets burst |
| Utiliser une `cullDistance` appropriée | Ne pas rendre les particules trop éloignées |
| Grouper les spawns | Générer plusieurs particules dans le même tick |
| Utiliser `spawnBurst` judicieusement | Les bursts sont moins coûteux que le continu |
| Considérer le nombre de joueurs | Plus de joueurs = plus de trafic réseau |
```java
// Bon : Génération de particules efficace
public void spawnEfficiently(Vector3d position) {
// Générer seulement pour les joueurs proches
List<PlayerRef> nearbyPlayers = getNearbyPlayers(position, 50);
if (!nearbyPlayers.isEmpty()) {
ParticleUtil.spawnParticleEffect(
"my_effect",
position,
nearbyPlayers, // Audience limitée
componentAccessor
);
}
}
```
---
## Sujets Connexes
- [Effets d'Entité]({{< relref "entity-effects" >}}) - Effets de statut avec composants visuels
- [Réseau]({{< relref "/advanced/networking" >}}) - Comment les particules sont diffusées
- [Événements]({{< relref "/core-concepts/events" >}}) - Déclencher des particules depuis les événements

View File

@@ -0,0 +1,272 @@
---
title: Networking
type: docs
weight: 6
---
The Hytale server uses a custom networking system for client-server communication. Understanding how data is synchronized and transmitted is essential for creating responsive and efficient plugins.
{{< cards cols="2" >}}
{{< card link="packets" title="Packets" subtitle="Network packet reference and usage" icon="server" >}}
{{< card link="sync" title="Synchronization" subtitle="Entity and world state synchronization" icon="refresh" >}}
{{< /cards >}}
---
## Overview
The networking system in Hytale handles:
- **Client-Server Communication**: Bidirectional packet transmission
- **Entity Synchronization**: Keeping entity states consistent across clients
- **World Updates**: Broadcasting block changes and world events
- **Player Actions**: Processing and validating player inputs
{{< callout type="info" >}}
Most networking operations are handled automatically by the server. Plugins typically interact with the high-level APIs rather than raw packets.
{{< /callout >}}
---
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Server │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ World │ │ Entity │ │ Plugin │ │
│ │ State │ │ Manager │ │ Events │ │
│ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │
│ │ │ │ │
│ └────────────────┼─────────────────────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ │ Network │ │
│ │ Layer │ │
│ └─────┬─────┘ │
└──────────────────────────┼──────────────────────────────────┘
┌────────────┼────────────┐
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ Client │ │ Client │ │ Client │
│ 1 │ │ 2 │ │ N │
└─────────┘ └─────────┘ └─────────┘
```
---
## Key Concepts
### Broadcast Distance
Different types of data have different broadcast distances:
| Data Type | Default Distance | Description |
|-----------|------------------|-------------|
| Particles | 75 blocks | `DEFAULT_PARTICLE_DISTANCE` |
| Entities | View distance | Based on server configuration |
| Sounds | 16 blocks | Default sound range |
| Block changes | Chunk-based | Sent to players with loaded chunks |
### Network Outdated Pattern
The server uses a "network outdated" pattern to efficiently synchronize state:
```java
// Components implement this pattern for network synchronization
public interface NetworkSyncable {
boolean isNetworkOutdated();
void markNetworkOutdated();
void clearNetworkOutdated();
}
```
When a component's state changes:
1. The component marks itself as `networkOutdated`
2. The sync system detects outdated components
3. Updates are batched and sent to relevant clients
4. The outdated flag is cleared after sending
---
## Common Patterns
### Sending Messages to Players
```java
// Send to a specific player
player.sendMessage(Message.raw("Hello!"));
// Send to all players in a world
for (Player p : world.getPlayers()) {
p.sendMessage(Message.raw("World announcement"));
}
// Send to all online players
for (Player p : server.getOnlinePlayers()) {
p.sendMessage(Message.raw("Server broadcast"));
}
```
### Spawning Particles (Network Broadcast)
```java
// Spawn particles visible to nearby players
world.spawnParticle(particleSystem, position);
// The server handles:
// 1. Finding players within DEFAULT_PARTICLE_DISTANCE (75 blocks)
// 2. Sending SpawnParticleSystem packet (ID: 152) to those players
// 3. Client-side rendering
```
### Entity Position Updates
```java
// When you modify an entity's position
entity.setPosition(newPosition);
// The server automatically:
// 1. Marks the TransformComponent as network outdated
// 2. Batches position updates
// 3. Sends updates to players tracking this entity
```
---
## Best Practices
{{< tabs items="Performance,Security,Reliability" >}}
{{< tab >}}
**Minimize Network Traffic:**
```java
// BAD: Sending updates every tick
@Subscribe
public void onTick(TickEvent event) {
for (Player p : server.getOnlinePlayers()) {
p.sendMessage(Message.raw("Tick!")); // Don't do this!
}
}
// GOOD: Batch updates and use intervals
private int tickCounter = 0;
@Subscribe
public void onTick(TickEvent event) {
tickCounter++;
if (tickCounter >= 20) { // Every second
tickCounter = 0;
broadcastScoreboard();
}
}
```
**Use Appropriate Broadcast Distances:**
```java
// Only notify nearby players for local events
Vector3d eventPos = event.getPosition();
double range = 50.0;
for (Player p : world.getPlayers()) {
if (p.getPosition().distanceTo(eventPos) <= range) {
p.sendMessage(Message.raw("Something happened nearby!"));
}
}
```
{{< /tab >}}
{{< tab >}}
**Validate Client Input:**
```java
// Note: PlayerInteractEvent is deprecated. For block interactions, use UseBlockEvent.
getEventRegistry().register(PlayerInteractEvent.class, event -> {
PlayerRef playerRef = event.getPlayerRef();
Vector3i targetBlock = event.getTargetBlock();
if (targetBlock != null && playerRef != null) {
// Get player position via PlayerRef's transform
Transform transform = playerRef.getTransform();
if (transform != null) {
Vector3d playerPos = transform.getPosition();
// Note: Vector3i uses getX(), getY(), getZ() - NOT x(), y(), z()
Vector3d targetPos = new Vector3d(targetBlock.getX(), targetBlock.getY(), targetBlock.getZ());
double distance = playerPos.distance(targetPos);
if (distance > MAX_INTERACTION_DISTANCE) {
event.setCancelled(true);
return;
}
}
}
});
```
**Rate Limit Actions:**
```java
private final Map<UUID, Long> lastAction = new HashMap<>();
private static final long COOLDOWN_MS = 1000;
public boolean canPerformAction(Player player) {
long now = System.currentTimeMillis();
Long last = lastAction.get(player.getUuid());
if (last != null && now - last < COOLDOWN_MS) {
return false;
}
lastAction.put(player.getUuid(), now);
return true;
}
```
{{< /tab >}}
{{< tab >}}
**Handle Disconnections:**
```java
@Subscribe
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
UUID playerId = event.getPlayerRef().getUuid();
// Clean up player-specific data
playerData.remove(playerId);
pendingOperations.removeIf(op -> op.getPlayerId().equals(playerId));
}
```
**Use PlayerRef for Async Operations:**
```java
// PlayerRef is safe to use across async boundaries
PlayerRef playerRef = event.getPlayerRef();
World world = event.getWorld();
CompletableFuture.runAsync(() -> {
// Some async operation
processData();
}).thenAccept(result -> {
// Return to world thread for game state changes
world.execute(() -> {
// PlayerRef.getPlayer() does NOT exist - send message directly on PlayerRef
playerRef.sendMessage(Message.raw("Operation complete!"));
});
});
```
{{< /tab >}}
{{< /tabs >}}
---
## Related Topics
- [Player API]({{< relref "/world/entities/player-api" >}}) - Player networking methods
- [Events]({{< relref "/core-concepts/events" >}}) - Network-related events
- [Entity Components]({{< relref "/world/entities/entity-components" >}}) - Synchronized component data

View File

@@ -0,0 +1,266 @@
---
title: Networking
type: docs
weight: 6
---
The Hytale server uses a custom networking system for client-server communication. Understanding how data is synchronized and transmitted is essential for creating responsive and efficient plugins.
{{< cards cols="2" >}}
{{< card link="packets" title="Packets" subtitle="Network packet reference and usage" icon="server" >}}
{{< card link="sync" title="Synchronization" subtitle="Entity and world state synchronization" icon="refresh" >}}
{{< /cards >}}
---
## Overview
The networking system in Hytale handles:
- **Client-Server Communication**: Bidirectional packet transmission
- **Entity Synchronization**: Keeping entity states consistent across clients
- **World Updates**: Broadcasting block changes and world events
- **Player Actions**: Processing and validating player inputs
{{< callout type="info" >}}
Most networking operations are handled automatically by the server. Plugins typically interact with the high-level APIs rather than raw packets.
{{< /callout >}}
---
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Server │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ World │ │ Entity │ │ Plugin │ │
│ │ State │ │ Manager │ │ Events │ │
│ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │
│ │ │ │ │
│ └────────────────┼─────────────────────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ │ Network │ │
│ │ Layer │ │
│ └─────┬─────┘ │
└──────────────────────────┼──────────────────────────────────┘
┌────────────┼────────────┐
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ Client │ │ Client │ │ Client │
│ 1 │ │ 2 │ │ N │
└─────────┘ └─────────┘ └─────────┘
```
---
## Key Concepts
### Broadcast Distance
Different types of data have different broadcast distances:
| Data Type | Default Distance | Description |
|-----------|------------------|-------------|
| Particles | 75 blocks | `DEFAULT_PARTICLE_DISTANCE` |
| Entities | View distance | Based on server configuration |
| Sounds | 16 blocks | Default sound range |
| Block changes | Chunk-based | Sent to players with loaded chunks |
### Network Outdated Pattern
The server uses a "network outdated" pattern to efficiently synchronize state:
```java
// Components implement this pattern for network synchronization
public interface NetworkSyncable {
boolean isNetworkOutdated();
void markNetworkOutdated();
void clearNetworkOutdated();
}
```
When a component's state changes:
1. The component marks itself as `networkOutdated`
2. The sync system detects outdated components
3. Updates are batched and sent to relevant clients
4. The outdated flag is cleared after sending
---
## Common Patterns
### Sending Messages to Players
```java
// Send to a specific player
player.sendMessage(Message.raw("Hello!"));
// Send to all players in a world
for (Player p : world.getPlayers()) {
p.sendMessage(Message.raw("World announcement"));
}
// Send to all online players
for (Player p : server.getOnlinePlayers()) {
p.sendMessage(Message.raw("Server broadcast"));
}
```
### Spawning Particles (Network Broadcast)
```java
// Spawn particles visible to nearby players
world.spawnParticle(particleSystem, position);
// The server handles:
// 1. Finding players within DEFAULT_PARTICLE_DISTANCE (75 blocks)
// 2. Sending SpawnParticleSystem packet (ID: 152) to those players
// 3. Client-side rendering
```
### Entity Position Updates
```java
// When you modify an entity's position
entity.setPosition(newPosition);
// The server automatically:
// 1. Marks the TransformComponent as network outdated
// 2. Batches position updates
// 3. Sends updates to players tracking this entity
```
---
## Best Practices
{{< tabs items="Performance,Security,Reliability" >}}
{{< tab >}}
**Minimize Network Traffic:**
```java
// BAD: Sending updates every tick
@Subscribe
public void onTick(TickEvent event) {
for (Player p : server.getOnlinePlayers()) {
p.sendMessage(Message.raw("Tick!")); // Don't do this!
}
}
// GOOD: Batch updates and use intervals
private int tickCounter = 0;
@Subscribe
public void onTick(TickEvent event) {
tickCounter++;
if (tickCounter >= 20) { // Every second
tickCounter = 0;
broadcastScoreboard();
}
}
```
**Use Appropriate Broadcast Distances:**
```java
// Only notify nearby players for local events
Vector3d eventPos = event.getPosition();
double range = 50.0;
for (Player p : world.getPlayers()) {
if (p.getPosition().distanceTo(eventPos) <= range) {
p.sendMessage(Message.raw("Something happened nearby!"));
}
}
```
{{< /tab >}}
{{< tab >}}
**Validate Client Input:**
```java
// Note: PlayerInteractEvent is deprecated. For block interactions, use UseBlockEvent.
getEventRegistry().register(PlayerInteractEvent.class, event -> {
Player player = event.getPlayer();
Vector3i targetBlock = event.getTargetBlock();
if (targetBlock != null) {
// Validate distance
Vector3d targetPos = new Vector3d(targetBlock.x(), targetBlock.y(), targetBlock.z());
double distance = player.getPosition().distance(targetPos);
if (distance > MAX_INTERACTION_DISTANCE) {
event.setCancelled(true);
return;
}
}
});
```
**Rate Limit Actions:**
```java
private final Map<UUID, Long> lastAction = new HashMap<>();
private static final long COOLDOWN_MS = 1000;
public boolean canPerformAction(Player player) {
long now = System.currentTimeMillis();
Long last = lastAction.get(player.getUuid());
if (last != null && now - last < COOLDOWN_MS) {
return false;
}
lastAction.put(player.getUuid(), now);
return true;
}
```
{{< /tab >}}
{{< tab >}}
**Handle Disconnections:**
```java
@Subscribe
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
UUID playerId = event.getPlayerRef().getUuid();
// Clean up player-specific data
playerData.remove(playerId);
pendingOperations.removeIf(op -> op.getPlayerId().equals(playerId));
}
```
**Use PlayerRef for Async Operations:**
```java
// PlayerRef is safe to use across async boundaries
PlayerRef playerRef = event.getPlayerRef();
CompletableFuture.runAsync(() -> {
// Some async operation
processData();
}).thenAccept(result -> {
// Player might have disconnected
Player player = playerRef.getPlayer();
if (player != null) {
player.sendMessage(Message.raw("Operation complete!"));
}
});
```
{{< /tab >}}
{{< /tabs >}}
---
## Related Topics
- [Player API]({{< relref "/world/entities/player-api" >}}) - Player networking methods
- [Events]({{< relref "/core-concepts/events" >}}) - Network-related events
- [Entity Components]({{< relref "/world/entities/entity-components" >}}) - Synchronized component data

View File

@@ -0,0 +1,414 @@
---
title: Packets
type: docs
weight: 1
---
Hytale uses a binary packet protocol for efficient client-server communication. While most networking is handled internally, understanding the packet system helps when debugging or implementing advanced features.
{{< callout type="warning" >}}
Direct packet manipulation is generally not recommended. Use the high-level APIs provided by the plugin system whenever possible.
{{< /callout >}}
---
## Packet Structure
All packets follow a common structure:
```
┌──────────────┬───────────────┬─────────────────────┐
│ Packet ID │ Payload Size │ Payload │
│ (varint) │ (varint) │ (variable bytes) │
└──────────────┴───────────────┴─────────────────────┘
```
| Field | Type | Description |
|-------|------|-------------|
| Packet ID | VarInt | Unique identifier for packet type |
| Payload Size | VarInt | Size of payload in bytes |
| Payload | bytes | Packet-specific data |
---
## Known Packet IDs
{{< tabs items="Client-bound,Server-bound" >}}
{{< tab >}}
Packets sent from server to client:
| ID | Name | Description |
|----|------|-------------|
| 152 | SpawnParticleSystem | Spawns particles at a location |
| -- | EntitySpawn | Spawns an entity on the client |
| -- | EntityDespawn | Removes an entity from the client |
| -- | EntityMove | Updates entity position |
| -- | BlockChange | Updates a single block |
| -- | ChunkData | Sends chunk block data |
| -- | ChatMessage | Sends chat/system message |
| -- | PlaySound | Plays a sound effect |
| -- | SetWeather | Changes weather state |
| -- | TimeUpdate | Synchronizes world time |
| -- | PlayerInfo | Updates player list data |
{{< callout type="info" >}}
Packet IDs marked with `--` are internal and subject to change between versions.
{{< /callout >}}
{{< /tab >}}
{{< tab >}}
Packets sent from client to server:
| ID | Name | Description |
|----|------|-------------|
| -- | PlayerPosition | Player movement update |
| -- | PlayerAction | Player action (attack, use, etc.) |
| -- | ChatMessage | Chat message from player |
| -- | BlockBreak | Block breaking progress |
| -- | BlockPlace | Block placement request |
| -- | UseItem | Item use request |
| -- | Interaction | Entity/block interaction |
| -- | MouseMotion | Mouse movement data |
| -- | MouseButton | Mouse button press/release |
{{< /tab >}}
{{< /tabs >}}
---
## Particle System Packet
The particle system packet (ID: 152) is used to spawn visual effects:
```java
// Internal packet structure (for reference only)
public class SpawnParticleSystemPacket {
private ParticleSystem particleSystem;
private Vector3d position;
private Vector3f rotation; // Optional
private Vector3f scale; // Optional
private Entity attachedEntity; // Optional
}
```
**Broadcast Distance:** `DEFAULT_PARTICLE_DISTANCE = 75` blocks
### Spawning Particles via API
```java
// Simple particle spawn
world.spawnParticle(particleSystem, position);
// Particle with rotation
world.spawnParticle(particleSystem, position, rotation);
// Particle attached to entity
world.spawnParticle(particleSystem, entity);
```
---
## Entity Packets
### Entity Spawn
When an entity enters a player's view distance, the server sends entity spawn data:
```java
// Automatic - when entity is created in world
Entity entity = world.spawnEntity(entityType, position);
// Server automatically notifies nearby clients
// Manual control via entity visibility
entity.setVisibleTo(player, true); // Force show to specific player
entity.setVisibleTo(player, false); // Hide from specific player
```
### Entity Updates
Entity state changes are batched and sent periodically:
| Component | Update Frequency | Priority |
|-----------|------------------|----------|
| TransformComponent | Every tick if changed | High |
| Velocity | Every tick if changed | High |
| Health | On change | Medium |
| DisplayName | On change | Low |
| Equipment | On change | Medium |
| Effects | On change | Low |
```java
// Position updates are automatic
entity.setPosition(newPos);
// Velocity updates are automatic
entity.setVelocity(newVelocity);
// Display name updates
entity.setComponent(DisplayNameComponent.class, new DisplayNameComponent("New Name"));
```
---
## Block Packets
### Single Block Change
```java
// Server-side API
world.setBlock(position, blockType);
// Automatically sends BlockChange to players with loaded chunk
// With block state
world.setBlock(position, blockType, blockState);
```
### Multi-Block Change
For bulk updates, changes are batched by chunk:
```java
// Multiple blocks in same chunk are batched
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
world.setBlock(new Vector3i(chunkX * 16 + x, 64, chunkZ * 16 + z), blockType);
}
}
// Server batches these into a single multi-block update packet
```
---
## Sound Packets
### Playing Sounds
```java
// Play sound at position
world.playSound(soundEvent, position, volume, pitch);
// Play sound to specific player
player.playSound(soundEvent, position, volume, pitch);
// Play sound from entity
entity.playSound(soundEvent, volume, pitch);
```
### Sound Categories
| Category | Description | Default Volume |
|----------|-------------|----------------|
| MASTER | Overall volume | 1.0 |
| MUSIC | Background music | 0.5 |
| AMBIENT | Environmental sounds | 0.8 |
| BLOCKS | Block interaction sounds | 1.0 |
| ENTITIES | Entity sounds | 1.0 |
| PLAYERS | Player-made sounds | 1.0 |
| UI | User interface sounds | 1.0 |
---
## Chat/Message Packets
### Message Types
```java
// Chat message (appears in chat)
player.sendMessage(Message.raw("Hello!"));
// Translation-based message
player.sendMessage(Message.translation("my.translation.key")
.param("player", player.getDisplayName()));
```
### Rich Text Formatting
Hytale's Message class supports styling through method chaining:
```java
// Raw text message
Message message = Message.raw("Hello, World!");
// Colored message (using java.awt.Color)
Message colored = Message.raw("Important!")
.color(Color.RED);
// Message with parameters (for translations)
Message parameterized = Message.translation("quest.completed")
.param("player", playerName)
.param("reward", "100 XP");
// Bold/italic formatting
Message styled = Message.raw("Notice!")
.bold(true)
.italic(false);
// Inserting multiple messages
Message combined = Message.empty()
.insert(Message.raw("Part 1"))
.insert(Message.raw(" - "))
.insert(Message.raw("Part 2"));
```
---
## Network Events
### Intercepting Network Activity
```java
public class NetworkMonitorPlugin extends ServerPlugin {
@Subscribe
public void onPlayerConnect(PlayerConnectEvent event) {
// New connection established
log.info("Player connected: " + event.getUsername());
}
@Subscribe
public void onPlayerSetupConnect(PlayerSetupConnectEvent event) {
// Before connection is fully established
// Can be cancelled
if (isBlacklisted(event.getUuid())) {
event.setCancelled(true);
}
}
@Subscribe
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
// Connection closed
log.info("Player disconnected: " + event.getPlayerRef().getUuid());
}
}
```
---
## Performance Considerations
{{< tabs items="Optimization,Debugging" >}}
{{< tab >}}
### Reducing Network Load
**1. Batch Updates:**
```java
// Instead of sending many small updates
world.setBlock(pos1, block);
world.setBlock(pos2, block);
world.setBlock(pos3, block);
// The server automatically batches these per tick
// No manual batching needed
```
**2. Use Appropriate Update Rates:**
```java
// For frequently changing data, consider throttling
private long lastUpdate = 0;
private static final long UPDATE_INTERVAL_MS = 50; // 20 updates/sec
public void updateDisplay() {
long now = System.currentTimeMillis();
if (now - lastUpdate >= UPDATE_INTERVAL_MS) {
lastUpdate = now;
sendDisplayUpdate();
}
}
```
**3. Limit Broadcast Scope:**
```java
// Only send to players who need the update
double maxDistance = 100.0;
Vector3d sourcePos = event.getPosition();
// Note: Player doesn't have getPosition() directly
// Use PlayerRef to get position via getTransform()
for (PlayerRef playerRef : world.getPlayerRefs()) {
Transform transform = playerRef.getTransform();
if (transform != null) {
double distance = transform.getPosition().distance(sourcePos);
if (distance <= maxDistance) {
playerRef.sendMessage(notification);
}
}
}
```
{{< /tab >}}
{{< tab >}}
### Debugging Network Issues
**1. Log Network Activity:**
```java
@Subscribe
public void onPlayerConnect(PlayerConnectEvent event) {
log.debug("Connect from {} ({})",
event.getUsername(),
event.getUuid());
}
@Subscribe
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
log.debug("Disconnect: {}",
event.getPlayerRef().getUuid());
}
```
**2. Monitor Packet Timing:**
```java
// Track message round-trip time
private final Map<UUID, Long> pingTimes = new ConcurrentHashMap<>();
public void sendPing(Player player) {
pingTimes.put(player.getUuid(), System.currentTimeMillis());
player.sendMessage(Message.raw("ping"));
}
// In start() method:
getEventRegistry().register(PlayerChatEvent.class, event -> {
if (event.getContent().equals("pong")) {
Long sent = pingTimes.remove(event.getSender().getUuid());
if (sent != null) {
long rtt = System.currentTimeMillis() - sent;
getLogger().at(Level.INFO).log("RTT for " + event.getSender().getUsername() + ": " + rtt + "ms");
}
}
});
```
{{< /tab >}}
{{< /tabs >}}
---
## Security Notes
{{< callout type="error" >}}
**Never trust client data!** Always validate:
- Positions (within valid range, not too far from player)
- Actions (player has permission, cooldown respected)
- Resources (item counts, block placements)
{{< /callout >}}
```java
// Example: Validating a block placement
getEventRegistry().register(PlaceBlockEvent.class, event -> {
Vector3i targetPos = event.getTargetBlock();
// Validate world bounds
// Note: Vector3i uses getY(), NOT y()
if (targetPos.getY() < 0 || targetPos.getY() > 255) {
event.setCancelled(true);
return;
}
// Note: PlaceBlockEvent is an ECS event - player access is through
// the component system, not direct getPlayer() calls.
// For player-specific validation, consider using entity components.
});
```

View File

@@ -0,0 +1,407 @@
---
title: Packets
type: docs
weight: 1
---
Hytale uses a binary packet protocol for efficient client-server communication. While most networking is handled internally, understanding the packet system helps when debugging or implementing advanced features.
{{< callout type="warning" >}}
Direct packet manipulation is generally not recommended. Use the high-level APIs provided by the plugin system whenever possible.
{{< /callout >}}
---
## Packet Structure
All packets follow a common structure:
```
┌──────────────┬───────────────┬─────────────────────┐
│ Packet ID │ Payload Size │ Payload │
│ (varint) │ (varint) │ (variable bytes) │
└──────────────┴───────────────┴─────────────────────┘
```
| Field | Type | Description |
|-------|------|-------------|
| Packet ID | VarInt | Unique identifier for packet type |
| Payload Size | VarInt | Size of payload in bytes |
| Payload | bytes | Packet-specific data |
---
## Known Packet IDs
{{< tabs items="Client-bound,Server-bound" >}}
{{< tab >}}
Packets sent from server to client:
| ID | Name | Description |
|----|------|-------------|
| 152 | SpawnParticleSystem | Spawns particles at a location |
| -- | EntitySpawn | Spawns an entity on the client |
| -- | EntityDespawn | Removes an entity from the client |
| -- | EntityMove | Updates entity position |
| -- | BlockChange | Updates a single block |
| -- | ChunkData | Sends chunk block data |
| -- | ChatMessage | Sends chat/system message |
| -- | PlaySound | Plays a sound effect |
| -- | SetWeather | Changes weather state |
| -- | TimeUpdate | Synchronizes world time |
| -- | PlayerInfo | Updates player list data |
{{< callout type="info" >}}
Packet IDs marked with `--` are internal and subject to change between versions.
{{< /callout >}}
{{< /tab >}}
{{< tab >}}
Packets sent from client to server:
| ID | Name | Description |
|----|------|-------------|
| -- | PlayerPosition | Player movement update |
| -- | PlayerAction | Player action (attack, use, etc.) |
| -- | ChatMessage | Chat message from player |
| -- | BlockBreak | Block breaking progress |
| -- | BlockPlace | Block placement request |
| -- | UseItem | Item use request |
| -- | Interaction | Entity/block interaction |
| -- | MouseMotion | Mouse movement data |
| -- | MouseButton | Mouse button press/release |
{{< /tab >}}
{{< /tabs >}}
---
## Particle System Packet
The particle system packet (ID: 152) is used to spawn visual effects:
```java
// Internal packet structure (for reference only)
public class SpawnParticleSystemPacket {
private ParticleSystem particleSystem;
private Vector3d position;
private Vector3f rotation; // Optional
private Vector3f scale; // Optional
private Entity attachedEntity; // Optional
}
```
**Broadcast Distance:** `DEFAULT_PARTICLE_DISTANCE = 75` blocks
### Spawning Particles via API
```java
// Simple particle spawn
world.spawnParticle(particleSystem, position);
// Particle with rotation
world.spawnParticle(particleSystem, position, rotation);
// Particle attached to entity
world.spawnParticle(particleSystem, entity);
```
---
## Entity Packets
### Entity Spawn
When an entity enters a player's view distance, the server sends entity spawn data:
```java
// Automatic - when entity is created in world
Entity entity = world.spawnEntity(entityType, position);
// Server automatically notifies nearby clients
// Manual control via entity visibility
entity.setVisibleTo(player, true); // Force show to specific player
entity.setVisibleTo(player, false); // Hide from specific player
```
### Entity Updates
Entity state changes are batched and sent periodically:
| Component | Update Frequency | Priority |
|-----------|------------------|----------|
| TransformComponent | Every tick if changed | High |
| Velocity | Every tick if changed | High |
| Health | On change | Medium |
| DisplayName | On change | Low |
| Equipment | On change | Medium |
| Effects | On change | Low |
```java
// Position updates are automatic
entity.setPosition(newPos);
// Velocity updates are automatic
entity.setVelocity(newVelocity);
// Display name updates
entity.setComponent(DisplayNameComponent.class, new DisplayNameComponent("New Name"));
```
---
## Block Packets
### Single Block Change
```java
// Server-side API
world.setBlock(position, blockType);
// Automatically sends BlockChange to players with loaded chunk
// With block state
world.setBlock(position, blockType, blockState);
```
### Multi-Block Change
For bulk updates, changes are batched by chunk:
```java
// Multiple blocks in same chunk are batched
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
world.setBlock(new Vector3i(chunkX * 16 + x, 64, chunkZ * 16 + z), blockType);
}
}
// Server batches these into a single multi-block update packet
```
---
## Sound Packets
### Playing Sounds
```java
// Play sound at position
world.playSound(soundEvent, position, volume, pitch);
// Play sound to specific player
player.playSound(soundEvent, position, volume, pitch);
// Play sound from entity
entity.playSound(soundEvent, volume, pitch);
```
### Sound Categories
| Category | Description | Default Volume |
|----------|-------------|----------------|
| MASTER | Overall volume | 1.0 |
| MUSIC | Background music | 0.5 |
| AMBIENT | Environmental sounds | 0.8 |
| BLOCKS | Block interaction sounds | 1.0 |
| ENTITIES | Entity sounds | 1.0 |
| PLAYERS | Player-made sounds | 1.0 |
| UI | User interface sounds | 1.0 |
---
## Chat/Message Packets
### Message Types
```java
// Chat message (appears in chat)
player.sendMessage(Message.raw("Hello!"));
// Translation-based message
player.sendMessage(Message.translation("my.translation.key")
.param("player", player.getDisplayName()));
```
### Rich Text Formatting
Hytale's Message class supports styling through method chaining:
```java
// Raw text message
Message message = Message.raw("Hello, World!");
// Colored message (using java.awt.Color)
Message colored = Message.raw("Important!")
.color(Color.RED);
// Message with parameters (for translations)
Message parameterized = Message.translation("quest.completed")
.param("player", playerName)
.param("reward", "100 XP");
// Bold/italic formatting
Message styled = Message.raw("Notice!")
.bold(true)
.italic(false);
// Inserting multiple messages
Message combined = Message.empty()
.insert(Message.raw("Part 1"))
.insert(Message.raw(" - "))
.insert(Message.raw("Part 2"));
```
---
## Network Events
### Intercepting Network Activity
```java
public class NetworkMonitorPlugin extends ServerPlugin {
@Subscribe
public void onPlayerConnect(PlayerConnectEvent event) {
// New connection established
log.info("Player connected: " + event.getUsername());
}
@Subscribe
public void onPlayerSetupConnect(PlayerSetupConnectEvent event) {
// Before connection is fully established
// Can be cancelled
if (isBlacklisted(event.getUuid())) {
event.setCancelled(true);
}
}
@Subscribe
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
// Connection closed
log.info("Player disconnected: " + event.getPlayerRef().getUuid());
}
}
```
---
## Performance Considerations
{{< tabs items="Optimization,Debugging" >}}
{{< tab >}}
### Reducing Network Load
**1. Batch Updates:**
```java
// Instead of sending many small updates
world.setBlock(pos1, block);
world.setBlock(pos2, block);
world.setBlock(pos3, block);
// The server automatically batches these per tick
// No manual batching needed
```
**2. Use Appropriate Update Rates:**
```java
// For frequently changing data, consider throttling
private long lastUpdate = 0;
private static final long UPDATE_INTERVAL_MS = 50; // 20 updates/sec
public void updateDisplay() {
long now = System.currentTimeMillis();
if (now - lastUpdate >= UPDATE_INTERVAL_MS) {
lastUpdate = now;
sendDisplayUpdate();
}
}
```
**3. Limit Broadcast Scope:**
```java
// Only send to players who need the update
double maxDistance = 100.0;
Vector3d sourcePos = event.getPosition();
for (Player player : world.getPlayers()) {
if (player.getPosition().distanceTo(sourcePos) <= maxDistance) {
player.sendMessage(notification);
}
}
```
{{< /tab >}}
{{< tab >}}
### Debugging Network Issues
**1. Log Network Activity:**
```java
@Subscribe
public void onPlayerConnect(PlayerConnectEvent event) {
log.debug("Connect from {} ({})",
event.getUsername(),
event.getUuid());
}
@Subscribe
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
log.debug("Disconnect: {}",
event.getPlayerRef().getUuid());
}
```
**2. Monitor Packet Timing:**
```java
// Track message round-trip time
private final Map<UUID, Long> pingTimes = new ConcurrentHashMap<>();
public void sendPing(Player player) {
pingTimes.put(player.getUuid(), System.currentTimeMillis());
player.sendMessage(Message.raw("ping"));
}
// In start() method:
getEventRegistry().register(PlayerChatEvent.class, event -> {
if (event.getContent().equals("pong")) {
Long sent = pingTimes.remove(event.getSender().getUuid());
if (sent != null) {
long rtt = System.currentTimeMillis() - sent;
getLogger().at(Level.INFO).log("RTT for " + event.getSender().getUsername() + ": " + rtt + "ms");
}
}
});
```
{{< /tab >}}
{{< /tabs >}}
---
## Security Notes
{{< callout type="error" >}}
**Never trust client data!** Always validate:
- Positions (within valid range, not too far from player)
- Actions (player has permission, cooldown respected)
- Resources (item counts, block placements)
{{< /callout >}}
```java
// Example: Validating a block placement
getEventRegistry().register(PlaceBlockEvent.class, event -> {
Vector3i targetPos = event.getTargetBlock();
// Validate world bounds
if (targetPos.y() < 0 || targetPos.y() > 255) {
event.setCancelled(true);
return;
}
// Note: PlaceBlockEvent is an ECS event - player access is through
// the component system, not direct getPlayer() calls.
// For player-specific validation, consider using entity components.
});
```

View File

@@ -0,0 +1,467 @@
---
title: Synchronization
type: docs
weight: 2
---
Hytale uses an efficient synchronization system to keep game state consistent between the server and all connected clients. This page explains how entity and world data is synchronized and how to work effectively with the sync system.
---
## Overview
The synchronization system handles:
- **Entity State**: Position, rotation, velocity, health, equipment
- **World State**: Block changes, chunk loading/unloading
- **Player Data**: Inventory, effects, permissions
- **Game Events**: Particles, sounds, animations
{{< callout type="info" >}}
Synchronization is automatic for most operations. The server tracks what needs to be sent to each client and batches updates efficiently.
{{< /callout >}}
---
## The Network Outdated Pattern
Hytale uses a "dirty flag" pattern called `isNetworkOutdated` to track which components need synchronization:
```java
// Simplified component sync interface
public interface NetworkSyncable {
/**
* Check if this component has changes that need to be sent to clients
*/
boolean isNetworkOutdated();
/**
* Mark this component as having pending changes
*/
void markNetworkOutdated();
/**
* Clear the outdated flag after changes have been sent
*/
void clearNetworkOutdated();
}
```
### How It Works
```
┌─────────────────────────────────────────────────────────────┐
│ Update Cycle │
├─────────────────────────────────────────────────────────────┤
│ 1. Game Logic 2. Mark Outdated 3. Sync Phase │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ entity.set │ → │ component. │ → │ Send to │ │
│ │ Position() │ │ markNetwork │ │ clients │ │
│ │ │ │ Outdated() │ │ │ │
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
│ │ │
│ 4. Clear Flag │ │
│ ┌─────────────┐ │ │
│ │ clearNetwork│ ←────────────────────────────────┘ │
│ │ Outdated() │ │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
---
## Entity Synchronization
### Components That Sync
| Component | Sync Trigger | Data Sent |
|-----------|--------------|-----------|
| TransformComponent | Position/rotation change | x, y, z, pitch, yaw, roll |
| Velocity | Velocity change | vx, vy, vz |
| Health | Health change | current, max |
| DisplayNameComponent | Name change | display name |
| EntityScaleComponent | Scale change | scale factor |
| ActiveAnimationComponent | Animation change | animation state |
### Automatic Sync
Most entity modifications automatically trigger synchronization:
```java
// All of these automatically sync to clients
entity.setPosition(new Vector3d(100, 64, 200));
entity.setVelocity(new Vector3d(0, 1, 0));
Player player = (Player) entity;
player.setHealth(10.0f);
```
### Manual Component Updates
When modifying components directly, the sync flag is managed automatically:
```java
// Getting a component and modifying it
TransformComponent transform = entity.getComponent(TransformComponent.class);
if (transform != null) {
// Internal methods handle markNetworkOutdated()
transform.setPosition(newPosition);
}
// Setting a new component
entity.setComponent(DisplayNameComponent.class,
new DisplayNameComponent(Message.raw("Custom Name")));
```
---
## World Synchronization
### Block Updates
Block changes are synchronized to players who have the relevant chunks loaded:
```java
// Single block change
world.setBlock(position, blockType);
// The server:
// 1. Updates the block in world data
// 2. Marks the chunk as having pending updates
// 3. On next sync tick, sends BlockChange to relevant players
```
### Chunk Loading
Chunks are sent to players based on view distance:
```java
// When a player moves or joins
// 1. Server calculates which chunks should be visible
// 2. New chunks are queued for sending
// 3. Chunks out of range are marked for unloading
// Events you can listen to:
@Subscribe
public void onChunkLoad(ChunkPreLoadProcessEvent event) {
// Chunk is about to be loaded for processing
}
@Subscribe
public void onChunkUnload(ChunkUnloadEvent event) {
// Chunk is being unloaded
}
@Subscribe
public void onChunkSave(ChunkSaveEvent event) {
// Chunk is being saved
}
```
### View Distance
```java
// Players receive updates based on their view distance
// This is configured server-side
// Particles have a fixed distance
public static final int DEFAULT_PARTICLE_DISTANCE = 75;
// Entity sync uses view distance (configurable)
// Block sync uses chunk loading range
```
---
## Player Data Synchronization
### Inventory Sync
Inventory changes sync automatically:
```java
Player player = ...;
Inventory inventory = player.getInventory();
// These trigger sync automatically
inventory.addItem(itemStack);
inventory.removeItem(slot);
inventory.setItem(slot, itemStack);
```
### Effects Sync
Status effects are synchronized when added or removed:
```java
// Adding an effect syncs to the player
player.addEffect(effect, duration, amplifier);
// Removing syncs the removal
player.removeEffect(effectType);
// Clearing all effects
player.clearEffects();
```
---
## Performance Optimization
### Batching
The server automatically batches updates:
{{< tabs items="How Batching Works,Example" >}}
{{< tab >}}
Updates are collected throughout each tick and sent together:
```
Tick Start
├── Game Logic: entity1.setPosition(...)
├── Game Logic: entity2.setPosition(...)
├── Game Logic: entity3.setHealth(...)
├── Game Logic: world.setBlock(...)
├── Game Logic: world.setBlock(...)
Tick End
└── Sync Phase: Send all updates in batched packets
```
Benefits:
- Fewer packets sent
- Lower network overhead
- More efficient compression
- Reduced client processing
{{< /tab >}}
{{< tab >}}
```java
// This code results in ONE batched update, not 100 individual packets
public void updateManyBlocks(World world) {
for (int x = 0; x < 10; x++) {
for (int z = 0; z < 10; z++) {
world.setBlock(
new Vector3i(baseX + x, 64, baseZ + z),
blockType
);
}
}
// All 100 block changes are batched and sent together
}
```
{{< /tab >}}
{{< /tabs >}}
### Delta Compression
Only changed data is sent:
```java
// If an entity only moves (position changes)
// Only position data is sent, not health, inventory, etc.
entity.setPosition(newPosition);
// Sends: position update only
entity.setHealth(newHealth);
// Sends: health update only
```
### Relevancy Filtering
Updates are only sent to relevant clients:
```java
// Entity updates go to players tracking that entity
// (players within view distance)
// Block updates go to players with that chunk loaded
// Sound effects respect distance
world.playSound(soundEvent, position, volume, pitch);
// Only players within hearing range receive this
```
---
## Custom Synchronization
### Creating Synced Data
If you need to sync custom data, use player metadata or entity components:
```java
// Using player metadata (persisted per-player)
public class CustomSyncPlugin extends ServerPlugin {
private static final String CUSTOM_DATA_KEY = "myplugin:custom_data";
public void setCustomData(Player player, int value) {
player.setMetadata(CUSTOM_DATA_KEY, value);
// Send update to player
sendCustomDataUpdate(player, value);
}
public int getCustomData(Player player) {
Object data = player.getMetadata(CUSTOM_DATA_KEY);
return data instanceof Integer ? (Integer) data : 0;
}
private void sendCustomDataUpdate(Player player, int value) {
// Use messages to inform client of data changes
// (custom client mod required to interpret this)
player.sendMessage(Message.raw("")); // or use a custom channel
}
}
```
### Entity Visibility Control
Control which entities players can see:
```java
// Hide an entity from a specific player
entity.setVisibleTo(player, false);
// Show a previously hidden entity
entity.setVisibleTo(player, true);
// Check visibility
boolean canSee = entity.isVisibleTo(player);
```
---
## Debugging Sync Issues
{{< tabs items="Common Issues,Diagnostic Tools" >}}
{{< tab >}}
**Entity Not Appearing:**
```java
// Check if entity is in a loaded chunk
Chunk chunk = world.getChunk(entity.getChunkPosition());
if (chunk == null || !chunk.isLoaded()) {
log.warn("Entity in unloaded chunk!");
}
// Check visibility
if (!entity.isVisibleTo(player)) {
log.warn("Entity hidden from player!");
}
// Check distance
double distance = entity.getPosition().distanceTo(player.getPosition());
if (distance > viewDistance) {
log.warn("Entity out of view distance: " + distance);
}
```
**Block Changes Not Syncing:**
```java
// Verify chunk is loaded for player
Vector3i chunkPos = new Vector3i(
blockPos.x() >> 4,
0,
blockPos.z() >> 4
);
// Check if player has chunk loaded
// (implementation depends on server API)
```
**Delayed Updates:**
```java
// Updates are sent at end of tick
// If you need immediate sync, there's usually no way to force it
// Design your code to work with batched updates
```
{{< /tab >}}
{{< tab >}}
**Logging Sync Activity:**
```java
@Subscribe
public void onEntityMove(EntityMoveEvent event) {
log.debug("Entity {} moved to {}",
event.getEntity().getUuid(),
event.getNewPosition());
}
@Subscribe
public void onBlockChange(BlockChangeEvent event) {
log.debug("Block at {} changed to {}",
event.getPosition(),
event.getNewBlockType());
}
```
**Monitoring Sync Timing:**
```java
// Track update frequency for an entity
private final Map<UUID, Long> lastUpdateTime = new HashMap<>();
public void onEntityUpdate(Entity entity) {
long now = System.currentTimeMillis();
Long last = lastUpdateTime.put(entity.getUuid(), now);
if (last != null) {
long delta = now - last;
if (delta < 50) { // Less than 1 tick
log.warn("Rapid updates on entity {}: {}ms apart",
entity.getUuid(), delta);
}
}
}
```
{{< /tab >}}
{{< /tabs >}}
---
## Best Practices
{{< callout type="info" >}}
**Summary of best practices:**
1. **Trust automatic sync** - Don't try to manually trigger synchronization
2. **Batch your updates** - Make multiple changes within a single tick when possible
3. **Use events** - Listen for sync-related events to react to state changes
4. **Consider view distance** - Don't waste resources updating entities far from players
5. **Handle disconnections** - Clean up player-specific sync data when they leave
{{< /callout >}}
```java
// Example: Efficient entity spawning with proper sync
public class EfficientSpawnPlugin extends ServerPlugin {
public void spawnManyEntities(World world, Vector3d center, int count) {
// Spawn all entities in one tick - they'll be batched
for (int i = 0; i < count; i++) {
Vector3d offset = new Vector3d(
Math.random() * 10 - 5,
0,
Math.random() * 10 - 5
);
Entity entity = world.spawnEntity(
entityType,
center.add(offset)
);
// Configure entity (still same tick, still batched)
entity.setComponent(DisplayNameComponent.class,
new DisplayNameComponent(Message.raw("Entity #" + i)));
}
// All spawns sent in one batched update at end of tick
}
}
```

View File

@@ -0,0 +1,467 @@
---
title: Synchronization
type: docs
weight: 2
---
Hytale uses an efficient synchronization system to keep game state consistent between the server and all connected clients. This page explains how entity and world data is synchronized and how to work effectively with the sync system.
---
## Overview
The synchronization system handles:
- **Entity State**: Position, rotation, velocity, health, equipment
- **World State**: Block changes, chunk loading/unloading
- **Player Data**: Inventory, effects, permissions
- **Game Events**: Particles, sounds, animations
{{< callout type="info" >}}
Synchronization is automatic for most operations. The server tracks what needs to be sent to each client and batches updates efficiently.
{{< /callout >}}
---
## The Network Outdated Pattern
Hytale uses a "dirty flag" pattern called `isNetworkOutdated` to track which components need synchronization:
```java
// Simplified component sync interface
public interface NetworkSyncable {
/**
* Check if this component has changes that need to be sent to clients
*/
boolean isNetworkOutdated();
/**
* Mark this component as having pending changes
*/
void markNetworkOutdated();
/**
* Clear the outdated flag after changes have been sent
*/
void clearNetworkOutdated();
}
```
### How It Works
```
┌─────────────────────────────────────────────────────────────┐
│ Update Cycle │
├─────────────────────────────────────────────────────────────┤
│ 1. Game Logic 2. Mark Outdated 3. Sync Phase │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ entity.set │ → │ component. │ → │ Send to │ │
│ │ Position() │ │ markNetwork │ │ clients │ │
│ │ │ │ Outdated() │ │ │ │
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
│ │ │
│ 4. Clear Flag │ │
│ ┌─────────────┐ │ │
│ │ clearNetwork│ ←────────────────────────────────┘ │
│ │ Outdated() │ │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
---
## Entity Synchronization
### Components That Sync
| Component | Sync Trigger | Data Sent |
|-----------|--------------|-----------|
| TransformComponent | Position/rotation change | x, y, z, pitch, yaw, roll |
| Velocity | Velocity change | vx, vy, vz |
| Health | Health change | current, max |
| DisplayNameComponent | Name change | display name |
| EntityScaleComponent | Scale change | scale factor |
| ActiveAnimationComponent | Animation change | animation state |
### Automatic Sync
Most entity modifications automatically trigger synchronization:
```java
// All of these automatically sync to clients
entity.setPosition(new Vector3d(100, 64, 200));
entity.setVelocity(new Vector3d(0, 1, 0));
Player player = (Player) entity;
player.setHealth(10.0f);
```
### Manual Component Updates
When modifying components directly, the sync flag is managed automatically:
```java
// Getting a component and modifying it
TransformComponent transform = entity.getComponent(TransformComponent.class);
if (transform != null) {
// Internal methods handle markNetworkOutdated()
transform.setPosition(newPosition);
}
// Setting a new component
entity.setComponent(DisplayNameComponent.class,
new DisplayNameComponent(Message.raw("Custom Name")));
```
---
## World Synchronization
### Block Updates
Block changes are synchronized to players who have the relevant chunks loaded:
```java
// Single block change
world.setBlock(position, blockType);
// The server:
// 1. Updates the block in world data
// 2. Marks the chunk as having pending updates
// 3. On next sync tick, sends BlockChange to relevant players
```
### Chunk Loading
Chunks are sent to players based on view distance:
```java
// When a player moves or joins
// 1. Server calculates which chunks should be visible
// 2. New chunks are queued for sending
// 3. Chunks out of range are marked for unloading
// Events you can listen to:
@Subscribe
public void onChunkLoad(ChunkPreLoadProcessEvent event) {
// Chunk is about to be loaded for processing
}
@Subscribe
public void onChunkUnload(ChunkUnloadEvent event) {
// Chunk is being unloaded
}
@Subscribe
public void onChunkSave(ChunkSaveEvent event) {
// Chunk is being saved
}
```
### View Distance
```java
// Players receive updates based on their view distance
// This is configured server-side
// Particles have a fixed distance
public static final int DEFAULT_PARTICLE_DISTANCE = 75;
// Entity sync uses view distance (configurable)
// Block sync uses chunk loading range
```
---
## Player Data Synchronization
### Inventory Sync
Inventory changes sync automatically:
```java
Player player = ...;
Inventory inventory = player.getInventory();
// These trigger sync automatically
inventory.addItem(itemStack);
inventory.removeItem(slot);
inventory.setItem(slot, itemStack);
```
### Effects Sync
Status effects are synchronized when added or removed:
```java
// Adding an effect syncs to the player
player.addEffect(effect, duration, amplifier);
// Removing syncs the removal
player.removeEffect(effectType);
// Clearing all effects
player.clearEffects();
```
---
## Performance Optimization
### Batching
The server automatically batches updates:
{{< tabs items="How Batching Works,Example" >}}
{{< tab >}}
Updates are collected throughout each tick and sent together:
```
Tick Start
├── Game Logic: entity1.setPosition(...)
├── Game Logic: entity2.setPosition(...)
├── Game Logic: entity3.setHealth(...)
├── Game Logic: world.setBlock(...)
├── Game Logic: world.setBlock(...)
Tick End
└── Sync Phase: Send all updates in batched packets
```
Benefits:
- Fewer packets sent
- Lower network overhead
- More efficient compression
- Reduced client processing
{{< /tab >}}
{{< tab >}}
```java
// This code results in ONE batched update, not 100 individual packets
public void updateManyBlocks(World world) {
for (int x = 0; x < 10; x++) {
for (int z = 0; z < 10; z++) {
world.setBlock(
new Vector3i(baseX + x, 64, baseZ + z),
blockType
);
}
}
// All 100 block changes are batched and sent together
}
```
{{< /tab >}}
{{< /tabs >}}
### Delta Compression
Only changed data is sent:
```java
// If an entity only moves (position changes)
// Only position data is sent, not health, inventory, etc.
entity.setPosition(newPosition);
// Sends: position update only
entity.setHealth(newHealth);
// Sends: health update only
```
### Relevancy Filtering
Updates are only sent to relevant clients:
```java
// Entity updates go to players tracking that entity
// (players within view distance)
// Block updates go to players with that chunk loaded
// Sound effects respect distance
world.playSound(soundEvent, position, volume, pitch);
// Only players within hearing range receive this
```
---
## Custom Synchronization
### Creating Synced Data
If you need to sync custom data, use player metadata or entity components:
```java
// Using player metadata (persisted per-player)
public class CustomSyncPlugin extends ServerPlugin {
private static final String CUSTOM_DATA_KEY = "myplugin:custom_data";
public void setCustomData(Player player, int value) {
player.setMetadata(CUSTOM_DATA_KEY, value);
// Send update to player
sendCustomDataUpdate(player, value);
}
public int getCustomData(Player player) {
Object data = player.getMetadata(CUSTOM_DATA_KEY);
return data instanceof Integer ? (Integer) data : 0;
}
private void sendCustomDataUpdate(Player player, int value) {
// Use messages to inform client of data changes
// (custom client mod required to interpret this)
player.sendMessage(Message.raw("")); // or use a custom channel
}
}
```
### Entity Visibility Control
Control which entities players can see:
```java
// Hide an entity from a specific player
entity.setVisibleTo(player, false);
// Show a previously hidden entity
entity.setVisibleTo(player, true);
// Check visibility
boolean canSee = entity.isVisibleTo(player);
```
---
## Debugging Sync Issues
{{< tabs items="Common Issues,Diagnostic Tools" >}}
{{< tab >}}
**Entity Not Appearing:**
```java
// Check if entity is in a loaded chunk
Chunk chunk = world.getChunk(entity.getChunkPosition());
if (chunk == null || !chunk.isLoaded()) {
log.warn("Entity in unloaded chunk!");
}
// Check visibility
if (!entity.isVisibleTo(player)) {
log.warn("Entity hidden from player!");
}
// Check distance
double distance = entity.getPosition().distanceTo(player.getPosition());
if (distance > viewDistance) {
log.warn("Entity out of view distance: " + distance);
}
```
**Block Changes Not Syncing:**
```java
// Verify chunk is loaded for player
Vector3i chunkPos = new Vector3i(
blockPos.x() >> 4,
0,
blockPos.z() >> 4
);
// Check if player has chunk loaded
// (implementation depends on server API)
```
**Delayed Updates:**
```java
// Updates are sent at end of tick
// If you need immediate sync, there's usually no way to force it
// Design your code to work with batched updates
```
{{< /tab >}}
{{< tab >}}
**Logging Sync Activity:**
```java
@Subscribe
public void onEntityMove(EntityMoveEvent event) {
log.debug("Entity {} moved to {}",
event.getEntity().getUuid(),
event.getNewPosition());
}
@Subscribe
public void onBlockChange(BlockChangeEvent event) {
log.debug("Block at {} changed to {}",
event.getPosition(),
event.getNewBlockType());
}
```
**Monitoring Sync Timing:**
```java
// Track update frequency for an entity
private final Map<UUID, Long> lastUpdateTime = new HashMap<>();
public void onEntityUpdate(Entity entity) {
long now = System.currentTimeMillis();
Long last = lastUpdateTime.put(entity.getUuid(), now);
if (last != null) {
long delta = now - last;
if (delta < 50) { // Less than 1 tick
log.warn("Rapid updates on entity {}: {}ms apart",
entity.getUuid(), delta);
}
}
}
```
{{< /tab >}}
{{< /tabs >}}
---
## Best Practices
{{< callout type="info" >}}
**Summary of best practices:**
1. **Trust automatic sync** - Don't try to manually trigger synchronization
2. **Batch your updates** - Make multiple changes within a single tick when possible
3. **Use events** - Listen for sync-related events to react to state changes
4. **Consider view distance** - Don't waste resources updating entities far from players
5. **Handle disconnections** - Clean up player-specific sync data when they leave
{{< /callout >}}
```java
// Example: Efficient entity spawning with proper sync
public class EfficientSpawnPlugin extends ServerPlugin {
public void spawnManyEntities(World world, Vector3d center, int count) {
// Spawn all entities in one tick - they'll be batched
for (int i = 0; i < count; i++) {
Vector3d offset = new Vector3d(
Math.random() * 10 - 5,
0,
Math.random() * 10 - 5
);
Entity entity = world.spawnEntity(
entityType,
center.add(offset)
);
// Configure entity (still same tick, still batched)
entity.setComponent(DisplayNameComponent.class,
new DisplayNameComponent(Message.raw("Entity #" + i)));
}
// All spawns sent in one batched update at end of tick
}
}
```