Init
This commit is contained in:
34
content/advanced/_index.en.md
Normal file
34
content/advanced/_index.en.md
Normal 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));
|
||||
```
|
||||
34
content/advanced/_index.fr.md
Normal file
34
content/advanced/_index.fr.md
Normal 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));
|
||||
```
|
||||
92
content/advanced/effects/_index.en.md
Normal file
92
content/advanced/effects/_index.en.md
Normal 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 >}}
|
||||
92
content/advanced/effects/_index.fr.md
Normal file
92
content/advanced/effects/_index.fr.md
Normal 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 >}}
|
||||
268
content/advanced/effects/dynamic-lights.en.md
Normal file
268
content/advanced/effects/dynamic-lights.en.md
Normal 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 >}}
|
||||
268
content/advanced/effects/dynamic-lights.fr.md
Normal file
268
content/advanced/effects/dynamic-lights.fr.md
Normal 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 >}}
|
||||
369
content/advanced/effects/entity-effects.en.md
Normal file
369
content/advanced/effects/entity-effects.en.md
Normal file
@@ -0,0 +1,369 @@
|
||||
---
|
||||
title: Entity Effects
|
||||
type: docs
|
||||
weight: 3
|
||||
---
|
||||
|
||||
Entity effects are status effects that can include visual components, stat modifiers, and duration-based behaviors.
|
||||
|
||||
## EntityEffect Configuration
|
||||
|
||||
```java
|
||||
public class EntityEffect {
|
||||
protected String id;
|
||||
protected String name; // Localization key
|
||||
|
||||
protected ApplicationEffects applicationEffects; // Visual/audio effects
|
||||
|
||||
protected String modelChange; // Change entity model
|
||||
protected float duration; // Default duration (seconds)
|
||||
|
||||
protected boolean infinite; // Never expires?
|
||||
protected boolean debuff; // Is a negative effect?
|
||||
protected String statusEffectIcon; // UI icon
|
||||
|
||||
// Damage and stats
|
||||
protected Int2FloatMap entityStats; // Stat modifiers
|
||||
protected ValueType valueType; // Absolute or Percent
|
||||
|
||||
// Behavior
|
||||
protected OverlapBehavior overlapBehavior; // EXTEND, OVERWRITE, IGNORE
|
||||
protected RemovalBehavior removalBehavior; // COMPLETE, DURATION, INFINITE
|
||||
|
||||
protected boolean invulnerable; // Grant invulnerability
|
||||
}
|
||||
```
|
||||
|
||||
## Accessing Effect Assets
|
||||
|
||||
```java
|
||||
// Get effect by ID
|
||||
EntityEffect effect = EntityEffect.getAssetMap().getAsset("fire_resistance");
|
||||
|
||||
// Get effect index for operations
|
||||
int effectIndex = EntityEffect.getAssetMap().getIndex(effect.getId());
|
||||
```
|
||||
|
||||
## EffectControllerComponent
|
||||
|
||||
The `EffectControllerComponent` manages active effects on an entity:
|
||||
|
||||
```java
|
||||
import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent;
|
||||
|
||||
// Get effect controller from entity
|
||||
EffectControllerComponent controller = store.getComponent(
|
||||
entityRef,
|
||||
EffectControllerComponent.getComponentType()
|
||||
);
|
||||
```
|
||||
|
||||
## Adding Effects
|
||||
|
||||
### Basic Addition
|
||||
|
||||
```java
|
||||
EntityEffect effect = EntityEffect.getAssetMap().getAsset("speed_boost");
|
||||
|
||||
// Add with default parameters
|
||||
controller.addEffect(
|
||||
entityRef,
|
||||
effect,
|
||||
componentAccessor
|
||||
);
|
||||
```
|
||||
|
||||
### With Custom Duration
|
||||
|
||||
```java
|
||||
// Add with custom duration and overlap behavior
|
||||
controller.addEffect(
|
||||
entityRef,
|
||||
effect,
|
||||
100.0f, // Duration in seconds
|
||||
OverlapBehavior.EXTEND, // How to handle overlap
|
||||
componentAccessor
|
||||
);
|
||||
```
|
||||
|
||||
### Infinite Effects
|
||||
|
||||
```java
|
||||
// Add an effect that never expires
|
||||
controller.addInfiniteEffect(
|
||||
entityRef,
|
||||
EntityEffect.getAssetMap().getIndex(effect.getId()),
|
||||
effect,
|
||||
componentAccessor
|
||||
);
|
||||
```
|
||||
|
||||
## Removing Effects
|
||||
|
||||
```java
|
||||
// Remove specific effect
|
||||
controller.removeEffect(
|
||||
entityRef,
|
||||
EntityEffect.getAssetMap().getIndex(effect.getId()),
|
||||
componentAccessor
|
||||
);
|
||||
```
|
||||
|
||||
## Overlap Behaviors
|
||||
|
||||
```java
|
||||
public enum OverlapBehavior {
|
||||
EXTEND, // Add duration to existing effect
|
||||
OVERWRITE, // Replace existing effect
|
||||
IGNORE // Don't apply if already active
|
||||
}
|
||||
```
|
||||
|
||||
### Usage Examples
|
||||
|
||||
```java
|
||||
// Extend duration if already applied
|
||||
controller.addEffect(
|
||||
entityRef,
|
||||
effect,
|
||||
30.0f,
|
||||
OverlapBehavior.EXTEND,
|
||||
componentAccessor
|
||||
);
|
||||
|
||||
// Replace with fresh duration
|
||||
controller.addEffect(
|
||||
entityRef,
|
||||
effect,
|
||||
30.0f,
|
||||
OverlapBehavior.OVERWRITE,
|
||||
componentAccessor
|
||||
);
|
||||
|
||||
// Only apply if not already active
|
||||
controller.addEffect(
|
||||
entityRef,
|
||||
effect,
|
||||
30.0f,
|
||||
OverlapBehavior.IGNORE,
|
||||
componentAccessor
|
||||
);
|
||||
```
|
||||
|
||||
## Removal Behaviors
|
||||
|
||||
```java
|
||||
public enum RemovalBehavior {
|
||||
COMPLETE, // Remove when complete
|
||||
DURATION, // Remove after duration
|
||||
INFINITE // Never remove automatically
|
||||
}
|
||||
```
|
||||
|
||||
## Value Types for Stats
|
||||
|
||||
```java
|
||||
public enum ValueType {
|
||||
Absolute, // Add/subtract flat value
|
||||
Percent // Multiply by percentage
|
||||
}
|
||||
```
|
||||
|
||||
## Practical Examples
|
||||
|
||||
### Buff on Command
|
||||
|
||||
```java
|
||||
public class BuffCommand extends AbstractCommand {
|
||||
private final RequiredArg<PlayerRef> playerArg;
|
||||
private final RequiredArg<String> effectArg;
|
||||
private final RequiredArg<Integer> durationArg;
|
||||
|
||||
// Would be set during plugin initialization
|
||||
private Store<EntityStore> store;
|
||||
private ComponentAccessor<EntityStore> componentAccessor;
|
||||
|
||||
public BuffCommand() {
|
||||
super("buff", "effects.command.buff.description");
|
||||
this.playerArg = withRequiredArg("player", "Target player", ArgTypes.PLAYER_REF);
|
||||
this.effectArg = withRequiredArg("effect", "Effect ID", ArgTypes.STRING);
|
||||
this.durationArg = withRequiredArg("duration", "Duration in seconds", ArgTypes.INTEGER);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CompletableFuture<Void> execute(CommandContext ctx) {
|
||||
PlayerRef target = playerArg.get(ctx);
|
||||
String effectId = effectArg.get(ctx);
|
||||
int duration = durationArg.get(ctx);
|
||||
|
||||
Ref<EntityStore> entityRef = target.getReference();
|
||||
if (entityRef == null || !entityRef.isValid()) {
|
||||
ctx.sender().sendMessage(Message.raw("Player not found"));
|
||||
return null;
|
||||
}
|
||||
|
||||
EntityEffect effect = EntityEffect.getAssetMap().getAsset(effectId);
|
||||
if (effect == null) {
|
||||
ctx.sender().sendMessage(Message.raw("Unknown effect: " + effectId));
|
||||
return null;
|
||||
}
|
||||
|
||||
EffectControllerComponent controller = store.getComponent(
|
||||
entityRef,
|
||||
EffectControllerComponent.getComponentType()
|
||||
);
|
||||
|
||||
if (controller != null) {
|
||||
controller.addEffect(
|
||||
entityRef,
|
||||
effect,
|
||||
(float) duration,
|
||||
OverlapBehavior.EXTEND,
|
||||
componentAccessor
|
||||
);
|
||||
}
|
||||
|
||||
ctx.sender().sendMessage(Message.raw("Applied " + effectId + " to " + target.getUsername()));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{{< callout type="warning" >}}
|
||||
**API Notes:**
|
||||
- Use `withRequiredArg()` to define arguments, not `addArgument()`
|
||||
- Use `arg.get(ctx)` to retrieve values, not `ctx.getArg()`
|
||||
- Use `ctx.sender()` not `ctx.getSender()`
|
||||
- Use `target.getUsername()` not `target.getName()`
|
||||
- Use `target.getReference()` to get entity reference (PlayerRef doesn't have `getPlayer()`)
|
||||
{{< /callout >}}
|
||||
|
||||
### Damage Over Time Effect
|
||||
|
||||
```java
|
||||
public void applyPoison(Player player, float duration, Store<EntityStore> store,
|
||||
ComponentAccessor<EntityStore> componentAccessor) {
|
||||
EntityEffect poison = EntityEffect.getAssetMap().getAsset("poison");
|
||||
|
||||
Ref<EntityStore> entityRef = player.getReference();
|
||||
EffectControllerComponent controller = store.getComponent(
|
||||
entityRef,
|
||||
EffectControllerComponent.getComponentType()
|
||||
);
|
||||
|
||||
if (controller != null && poison != null) {
|
||||
controller.addEffect(
|
||||
entityRef,
|
||||
poison,
|
||||
duration,
|
||||
OverlapBehavior.EXTEND,
|
||||
componentAccessor
|
||||
);
|
||||
}
|
||||
|
||||
player.sendMessage(Message.raw("You have been poisoned!"));
|
||||
}
|
||||
```
|
||||
|
||||
### Temporary Invulnerability
|
||||
|
||||
```java
|
||||
public void grantInvulnerability(Player player, float seconds, Store<EntityStore> store,
|
||||
ComponentAccessor<EntityStore> componentAccessor) {
|
||||
EntityEffect invuln = EntityEffect.getAssetMap().getAsset("invulnerability");
|
||||
|
||||
Ref<EntityStore> entityRef = player.getReference();
|
||||
EffectControllerComponent controller = store.getComponent(
|
||||
entityRef,
|
||||
EffectControllerComponent.getComponentType()
|
||||
);
|
||||
|
||||
if (controller != null && invuln != null) {
|
||||
controller.addEffect(
|
||||
entityRef,
|
||||
invuln,
|
||||
seconds,
|
||||
OverlapBehavior.OVERWRITE, // Fresh duration
|
||||
componentAccessor
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Clear All Effects
|
||||
|
||||
```java
|
||||
public void clearAllEffects(Player player, Store<EntityStore> store,
|
||||
ComponentAccessor<EntityStore> componentAccessor) {
|
||||
Ref<EntityStore> entityRef = player.getReference();
|
||||
EffectControllerComponent controller = store.getComponent(
|
||||
entityRef,
|
||||
EffectControllerComponent.getComponentType()
|
||||
);
|
||||
|
||||
if (controller != null) {
|
||||
// Use the built-in clearEffects method
|
||||
controller.clearEffects(
|
||||
entityRef,
|
||||
componentAccessor
|
||||
);
|
||||
}
|
||||
|
||||
player.sendMessage(Message.raw("All effects cleared!"));
|
||||
}
|
||||
```
|
||||
|
||||
## Combining with Other Effects
|
||||
|
||||
Entity effects work well with particles and dynamic lights:
|
||||
|
||||
```java
|
||||
public void applyMagicBuff(Player player, Store<EntityStore> store,
|
||||
ComponentAccessor<EntityStore> componentAccessor) {
|
||||
Ref<EntityStore> entityRef = player.getReference();
|
||||
|
||||
// Apply status effect
|
||||
EntityEffect buff = EntityEffect.getAssetMap().getAsset("magic_power");
|
||||
EffectControllerComponent controller = store.getComponent(
|
||||
entityRef,
|
||||
EffectControllerComponent.getComponentType()
|
||||
);
|
||||
if (controller != null && buff != null) {
|
||||
controller.addEffect(entityRef, buff, componentAccessor);
|
||||
}
|
||||
|
||||
// Add visual glow
|
||||
ColorLight glow = new ColorLight((byte) 10, (byte) 200, (byte) 50, (byte) 255);
|
||||
DynamicLight light = new DynamicLight(glow);
|
||||
componentAccessor.putComponent(
|
||||
entityRef,
|
||||
DynamicLight.getComponentType(),
|
||||
light
|
||||
);
|
||||
|
||||
// Get position from TransformComponent for particle spawn
|
||||
TransformComponent transform = store.getComponent(entityRef, TransformComponent.getComponentType());
|
||||
if (transform != null) {
|
||||
ParticleUtil.spawnParticleEffect(
|
||||
"magic_aura",
|
||||
transform.getPosition(),
|
||||
componentAccessor
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{{< callout type="info" >}}
|
||||
**Note:** Use `player.getReference()` to get the entity reference. Position must be obtained from `TransformComponent`, not directly from Player.
|
||||
{{< /callout >}}
|
||||
|
||||
## Best Practices
|
||||
|
||||
{{< callout type="info" >}}
|
||||
**Effect Guidelines:**
|
||||
- Use `EXTEND` for stackable buffs to reward repeated application
|
||||
- Use `OVERWRITE` for effects that should reset duration
|
||||
- Use `IGNORE` to prevent effect stacking when undesired
|
||||
- Always check if effect exists before applying
|
||||
- Consider performance with many simultaneous effects
|
||||
{{< /callout >}}
|
||||
369
content/advanced/effects/entity-effects.fr.md
Normal file
369
content/advanced/effects/entity-effects.fr.md
Normal 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 >}}
|
||||
788
content/advanced/effects/particles.en.md
Normal file
788
content/advanced/effects/particles.en.md
Normal 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
|
||||
788
content/advanced/effects/particles.fr.md
Normal file
788
content/advanced/effects/particles.fr.md
Normal 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
|
||||
272
content/advanced/networking/_index.en.md
Normal file
272
content/advanced/networking/_index.en.md
Normal 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
|
||||
266
content/advanced/networking/_index.fr.md
Normal file
266
content/advanced/networking/_index.fr.md
Normal 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
|
||||
414
content/advanced/networking/packets.en.md
Normal file
414
content/advanced/networking/packets.en.md
Normal 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.
|
||||
});
|
||||
```
|
||||
407
content/advanced/networking/packets.fr.md
Normal file
407
content/advanced/networking/packets.fr.md
Normal 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.
|
||||
});
|
||||
```
|
||||
467
content/advanced/networking/sync.en.md
Normal file
467
content/advanced/networking/sync.en.md
Normal 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
|
||||
}
|
||||
}
|
||||
```
|
||||
467
content/advanced/networking/sync.fr.md
Normal file
467
content/advanced/networking/sync.fr.md
Normal 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
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user