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

View File

@@ -0,0 +1,53 @@
---
title: Entities
type: docs
weight: 7
---
Entities are all dynamic objects in the game world - players, creatures, items, and more. Hytale uses an Entity Component System (ECS) architecture.
{{< cards >}}
{{< card link="entity-hierarchy" title="Entity Hierarchy" subtitle="Entity → LivingEntity → Player" >}}
{{< card link="entity-components" title="Entity Components" subtitle="Transform, BoundingBox, and more" >}}
{{< card link="spawning-entities" title="Spawning Entities" subtitle="Create and spawn custom entities" >}}
{{< card link="player-api" title="Player API" subtitle="Player-specific methods and data" >}}
{{< card link="npc" title="NPC System" subtitle="AI, navigation, and behaviors" >}}
{{< card link="spawning" title="Spawning System" subtitle="Markers, beacons, and suppression" >}}
{{< card link="flocking-behavior" title="Flocking Behavior" subtitle="NPC group coordination" >}}
{{< card link="mounts" title="Mounts" subtitle="Riding entities and vehicles" >}}
{{< card link="inventory" title="Inventory" subtitle="Items, containers, and transactions" icon="archive" >}}
{{< /cards >}}
## Entity Hierarchy
```
Entity
├── LivingEntity
│ ├── Player
│ └── Creature
└── BlockEntity
```
## Quick Example
```java
getEventRegistry().register(PlayerConnectEvent.class, event -> {
Player player = event.getPlayer();
if (player != null) {
// Get position via TransformComponent
TransformComponent transform = player.getTransformComponent();
Vector3d pos = transform.getPosition();
// Get world
World world = player.getWorld();
// Send message (requires Message object)
player.sendMessage(Message.raw("Welcome at " + pos.toString()));
}
});
```
{{< callout type="info" >}}
`event.getPlayer()` may return null if the player hasn't fully loaded yet. Always check for null. For position access, `getTransformComponent()` is deprecated - prefer ECS component access in new code.
{{< /callout >}}

View File

@@ -0,0 +1,53 @@
---
title: Entités
type: docs
weight: 7
---
Les entités sont tous les objets dynamiques dans le monde du jeu - joueurs, créatures, items, et plus. Hytale utilise une architecture Entity Component System (ECS).
{{< cards >}}
{{< card link="entity-hierarchy" title="Hiérarchie des Entités" subtitle="Entity → LivingEntity → Player" >}}
{{< card link="entity-components" title="Composants d'Entité" subtitle="Transform, BoundingBox, et plus" >}}
{{< card link="spawning-entities" title="Faire Apparaître des Entités" subtitle="Créer et spawner des entités personnalisées" >}}
{{< card link="player-api" title="API Joueur" subtitle="Méthodes et données spécifiques aux joueurs" >}}
{{< card link="npc" title="Système NPC" subtitle="IA, navigation, et comportements" >}}
{{< card link="spawning" title="Système de Spawning" subtitle="Marqueurs, beacons, et suppression" >}}
{{< card link="flocking-behavior" title="Comportement de Groupe" subtitle="Coordination de groupe NPC" >}}
{{< card link="mounts" title="Montures" subtitle="Chevaucher entites et vehicules" >}}
{{< card link="inventory" title="Inventaire" subtitle="Items, conteneurs et transactions" icon="archive" >}}
{{< /cards >}}
## Hiérarchie des Entités
```
Entity
├── LivingEntity
│ ├── Player
│ └── Creature
└── BlockEntity
```
## Exemple Rapide
```java
getEventRegistry().register(PlayerConnectEvent.class, event -> {
Player player = event.getPlayer();
if (player != null) {
// Obtenir la position via TransformComponent
TransformComponent transform = player.getTransformComponent();
Vector3d pos = transform.getPosition();
// Obtenir le monde
World world = player.getWorld();
// Envoyer un message (nécessite un objet Message)
player.sendMessage(Message.raw("Bienvenue à " + pos.toString()));
}
});
```
{{< callout type="info" >}}
`event.getPlayer()` peut retourner null si le joueur n'est pas encore complètement chargé. Vérifiez toujours la valeur null. Pour l'accès à la position, `getTransformComponent()` est déprécié - préférez l'accès aux composants ECS dans le nouveau code.
{{< /callout >}}

View File

@@ -0,0 +1,828 @@
---
title: Entity Components
type: docs
weight: 2
---
Hytale uses an Entity Component System (ECS) where entities are composed of modular components. Each component adds specific functionality or data to an entity.
## Component Categories
{{< cards cols="3" >}}
{{< card link="#transform--position" title="Transform & Position" subtitle="Location, rotation, collision" >}}
{{< card link="#display--visual" title="Display & Visual" subtitle="Models, animations, effects" >}}
{{< card link="#behavior-markers" title="Behavior Markers" subtitle="Interaction flags" >}}
{{< card link="#physics" title="Physics" subtitle="Position data, collision" >}}
{{< card link="#audio" title="Audio" subtitle="Movement sounds" >}}
{{< card link="#custom-components" title="Custom Components" subtitle="Plugin-defined components" >}}
{{< /cards >}}
---
## Component Access Pattern
Before diving into specific components, understand how to access components in Hytale's ECS:
```java
// Get component type from the component class
ComponentType<EntityStore, TransformComponent> transformType =
TransformComponent.getComponentType();
// Get Ref<EntityStore> from PlayerRef or other source
Ref<EntityStore> ref = playerRef.getReference();
if (ref == null || !ref.isValid()) {
return;
}
// Get Store from Ref, then access component via Store
Store<EntityStore> store = ref.getStore();
TransformComponent transform = store.getComponent(ref, transformType);
// Check if entity has a component using Archetype
Archetype<EntityStore> archetype = store.getArchetype(ref);
boolean hasComponent = archetype.contains(transformType);
```
{{< callout type="warning" >}}
**Important:** `Ref<EntityStore>` does NOT have `getComponent()` or `hasComponent()` methods directly. You must get the `Store` first via `ref.getStore()`, then call `store.getComponent(ref, componentType)`.
{{< /callout >}}
{{< callout type="info" >}}
All entity components implement `Component<EntityStore>` and provide a static `getComponentType()` method to retrieve the `ComponentType` for component access.
{{< /callout >}}
---
## Transform & Position
### TransformComponent
Core component for entity position, rotation, and chunk reference.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| position | `Vector3d` | World position (x, y, z) |
| rotation | `Vector3f` | Rotation angles (x, y, z) |
| chunkRef | `Ref<ChunkStore>` | Reference to containing chunk |
{{< /tab >}}
{{< tab >}}
- `getPosition()` - Returns world position as Vector3d
- `setPosition(Vector3d)` - Sets world position (uses `assign()`)
- `getRotation()` - Returns rotation angles
- `setRotation(Vector3f)` - Sets rotation angles (uses `assign()`)
- `teleportPosition(Vector3d)` - Teleports entity to position
- `getTransform()` - Returns Transform object combining position/rotation
- `getChunkRef()` - Returns chunk reference (`Ref<ChunkStore>`)
- `getChunk()` - **Deprecated** - Returns WorldChunk directly
{{< callout type="warning" >}}
`getChunk()` is deprecated and marked for removal. Use `getChunkRef()` instead.
{{< /callout >}}
{{< /tab >}}
{{< tab >}}
```java
// Get store from ref
Store<EntityStore> store = ref.getStore();
ComponentType<EntityStore, TransformComponent> transformType =
TransformComponent.getComponentType();
TransformComponent transform = store.getComponent(ref, transformType);
if (transform != null) {
// Get current position
Vector3d pos = transform.getPosition();
getLogger().at(Level.INFO).log("Entity at: " + pos.x + ", " + pos.y + ", " + pos.z);
// Get rotation
Vector3f rot = transform.getRotation();
// Get chunk reference (recommended)
Ref<ChunkStore> chunkRef = transform.getChunkRef();
}
```
{{< /tab >}}
{{< /tabs >}}
---
### BoundingBox
Defines the collision bounds of an entity using a `Box` object.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| boundingBox | `Box` | The collision box with min/max corners |
| detailBoxes | `Map<String, DetailBox[]>` | Named detail boxes for precise collision |
{{< /tab >}}
{{< tab >}}
- `getBoundingBox()` - Returns the `Box` object
- `setBoundingBox(Box)` - Sets the bounding box
- `getDetailBoxes()` - Returns map of detail boxes (can be null)
{{< /tab >}}
{{< tab >}}
```java
ComponentType<EntityStore, BoundingBox> boundsType = BoundingBox.getComponentType();
BoundingBox bounds = store.getComponent(ref, boundsType);
// Access the Box object
Box box = bounds.getBoundingBox();
// Box provides min/max as Vector3d
Vector3d min = box.getMin();
Vector3d max = box.getMax();
// Box utility methods
double width = box.width(); // max.x - min.x
double height = box.height(); // max.y - min.y
double depth = box.depth(); // max.z - min.z
// Center point
double centerX = box.middleX();
double centerY = box.middleY();
double centerZ = box.middleZ();
// Check intersection with another box
Box otherBox = otherBounds.getBoundingBox();
if (box.isIntersecting(otherBox)) {
handleCollision();
}
// Modify box (mutates in place)
box.offset(1.0, 0, 0); // Move box by offset
box.scale(2.0f); // Scale box
// Access detail boxes for precise collision
Map<String, DetailBox[]> details = bounds.getDetailBoxes();
if (details != null) {
for (Map.Entry<String, DetailBox[]> entry : details.entrySet()) {
// Process named detail boxes
}
}
```
{{< /tab >}}
{{< /tabs >}}
#### The Box Class
The `Box` class (`com.hypixel.hytale.math.shape.Box`) is a fundamental shape class:
| Method | Description |
|--------|-------------|
| `width()` | Returns width (X axis) |
| `height()` | Returns height (Y axis) |
| `depth()` | Returns depth (Z axis) |
| `middleX/Y/Z()` | Returns center coordinates |
| `getVolume()` | Returns total volume |
| `isIntersecting(Box)` | Checks intersection with another box |
| `containsPosition(x,y,z)` | Checks if point is inside |
| `offset(x,y,z)` | Moves box by offset (mutates) |
| `scale(float)` | Scales box (mutates) |
| `union(Box)` | Expands to contain another box |
| `clone()` | Creates a copy |
---
### CollisionResultComponent
Stores collision detection results from the physics system.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| collisionResult | `CollisionResult` | Full collision computation result |
| collisionStartPosition | `Vector3d` | Position where collision started |
| collisionPositionOffset | `Vector3d` | Offset from collision |
| pendingCollisionCheck | `boolean` | Whether collision check is pending |
{{< /tab >}}
{{< tab >}}
- `getCollisionResult()` - Returns the `CollisionResult` object
- `isPendingCollisionCheck()` - Returns true if collision needs recomputation
{{< /tab >}}
{{< tab >}}
```java
ComponentType<EntityStore, CollisionResultComponent> collisionType =
CollisionResultComponent.getComponentType();
CollisionResultComponent collision = store.getComponent(ref,collisionType);
// Get collision result object
CollisionResult result = collision.getCollisionResult();
// Check for block collisions
int blockCollisionCount = result.getBlockCollisionCount();
if (blockCollisionCount > 0) {
BlockCollisionData firstCollision = result.getFirstBlockCollision();
// Process collision
}
// Check if sliding (on ground)
if (result.isSliding) {
// Entity is sliding on a surface
double slideStart = result.slideStart;
double slideEnd = result.slideEnd;
}
// Check character collisions (if enabled)
int charCollisionCount = result.getCharacterCollisionCount();
CharacterCollisionData charCollision = result.getFirstCharacterCollision();
// Access trigger blocks (collision triggers)
CollisionDataArray<BlockCollisionData> triggers = result.getTriggerBlocks();
```
{{< /tab >}}
{{< /tabs >}}
{{< callout type="info" >}}
The `CollisionResult` class is complex and handles block collisions, character collisions, slides, and trigger blocks. It's primarily used internally by the collision system.
{{< /callout >}}
---
### PositionDataComponent
Tracks block type IDs at entity's position.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| insideBlockTypeId | `int` | Block type ID entity is inside |
| standingOnBlockTypeId | `int` | Block type ID entity is standing on |
{{< /tab >}}
{{< tab >}}
- `getInsideBlockTypeId()` - Returns block type ID entity is inside
- `setInsideBlockTypeId(int)` - Sets inside block type ID
- `getStandingOnBlockTypeId()` - Returns block type ID beneath entity
- `setStandingOnBlockTypeId(int)` - Sets standing-on block type ID
{{< /tab >}}
{{< tab >}}
```java
ComponentType<EntityStore, PositionDataComponent> posDataType =
PositionDataComponent.getComponentType();
PositionDataComponent posData = store.getComponent(ref,posDataType);
// Check what block entity is standing on
int standingOn = posData.getStandingOnBlockTypeId();
// Check what block entity is inside
int insideBlock = posData.getInsideBlockTypeId();
// Use block type registry to get actual BlockType
BlockType blockType = BlockType.getAssetMap().getAsset(standingOn);
if (blockType != null) {
getLogger().at(Level.INFO).log("Standing on: " + blockType.getId());
}
```
{{< /tab >}}
{{< /tabs >}}
---
## Display & Visual
### DisplayNameComponent
Controls the display name shown above an entity.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| displayName | `Message` | The displayed name as a Message object |
{{< /tab >}}
{{< tab >}}
- `getDisplayName()` - Returns the `Message` display name
{{< /tab >}}
{{< tab >}}
```java
ComponentType<EntityStore, DisplayNameComponent> nameType =
DisplayNameComponent.getComponentType();
DisplayNameComponent nameComp = store.getComponent(ref,nameType);
// Get current display name
Message name = nameComp.getDisplayName();
// Message is the formatted text object used throughout Hytale
// Use Message.raw() or Message formatting methods to create new names
```
{{< /tab >}}
{{< /tabs >}}
{{< callout type="info" >}}
**Message Type:** Display names use Hytale's `Message` class, not plain strings. The `Message` class supports formatted text, colors, and localization.
{{< /callout >}}
---
### ModelComponent
Links an entity to its visual 3D model.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| model | `Model` | Reference to the model |
| isNetworkOutdated | `boolean` | Network sync flag |
{{< /tab >}}
{{< tab >}}
- `getModel()` - Returns the Model
- `setModel(Model)` - Sets the model
{{< /tab >}}
{{< tab >}}
```java
ComponentType<EntityStore, ModelComponent> modelType = ModelComponent.getComponentType();
ModelComponent model = store.getComponent(ref,modelType);
// Get current model
Model currentModel = model.getModel();
// Set new model
model.setModel(newModel);
```
{{< /tab >}}
{{< /tabs >}}
---
### EntityScaleComponent
Controls the scale/size of an entity.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| scale | `float` | Uniform scale factor (default: 1.0) |
| isNetworkOutdated | `boolean` | Network sync flag |
{{< /tab >}}
{{< tab >}}
- `getScale()` - Returns the scale factor (float)
- `setScale(float)` - Sets the uniform scale factor
{{< /tab >}}
{{< tab >}}
```java
ComponentType<EntityStore, EntityScaleComponent> scaleType =
EntityScaleComponent.getComponentType();
EntityScaleComponent scale = store.getComponent(ref,scaleType);
// Get current scale
float currentScale = scale.getScale(); // Default: 1.0
// Make entity twice as large
scale.setScale(2.0f);
// Make entity half size
scale.setScale(0.5f);
// Giant boss spawn effect - track growth in tick handler
private final Map<Ref<EntityStore>, Float> growingBosses = new ConcurrentHashMap<>();
public void startGrowBoss(Ref<EntityStore> bossRef) {
EntityScaleComponent scale = store.getComponent(bossRef, scaleType);
scale.setScale(0.1f); // Start small
growingBosses.put(bossRef, 0.1f);
}
// Call this from your tick handler (every 5 ticks)
public void onTick() {
growingBosses.entrySet().removeIf(entry -> {
Ref<EntityStore> bossRef = entry.getKey();
float currentScale = entry.getValue();
if (currentScale >= 1.0f) {
return true; // Remove from map
}
EntityScaleComponent scale = store.getComponent(bossRef, scaleType);
if (scale != null) {
float newScale = currentScale + 0.1f;
scale.setScale(newScale);
entry.setValue(newScale);
}
return false; // Keep in map
});
}
```
{{< /tab >}}
{{< /tabs >}}
{{< callout type="warning" >}}
**Uniform Scale Only:** Hytale's EntityScaleComponent uses a single `float` for uniform scaling on all axes, not per-axis Vector3f scaling.
{{< /callout >}}
---
### ActiveAnimationComponent
Manages currently playing animations per animation slot.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| activeAnimations | `String[]` | Animation names per slot |
| isNetworkOutdated | `boolean` | Network sync flag |
{{< /tab >}}
{{< tab >}}
- `getActiveAnimations()` - Returns array of animation names indexed by `AnimationSlot`
- `setPlayingAnimation(AnimationSlot, String)` - Sets animation for a specific slot
{{< /tab >}}
{{< tab >}}
```java
ComponentType<EntityStore, ActiveAnimationComponent> animType =
ActiveAnimationComponent.getComponentType();
ActiveAnimationComponent anim = store.getComponent(ref,animType);
// Get all active animations
String[] animations = anim.getActiveAnimations();
// Set animation for specific slot
anim.setPlayingAnimation(AnimationSlot.Action, "attack_swing");
anim.setPlayingAnimation(AnimationSlot.Movement, "walk");
// Clear animation on a slot
anim.setPlayingAnimation(AnimationSlot.Emote, null);
```
{{< /tab >}}
{{< /tabs >}}
#### AnimationSlot Enum
Hytale uses animation slots to layer animations:
| Slot | Value | Purpose |
|------|-------|---------|
| `Movement` | 0 | Locomotion (walk, run, jump) |
| `Status` | 1 | Status effects |
| `Action` | 2 | Actions (attack, use item) |
| `Face` | 3 | Facial expressions |
| `Emote` | 4 | Social emotes |
---
### DynamicLight
Emits dynamic light from an entity using a `ColorLight` structure.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| colorLight | `ColorLight` | Light data (radius, RGB) |
| isNetworkOutdated | `boolean` | Network sync flag |
{{< /tab >}}
{{< tab >}}
- `getColorLight()` - Returns the `ColorLight` object
- `setColorLight(ColorLight)` - Sets the light configuration
{{< /tab >}}
{{< tab >}}
```java
ComponentType<EntityStore, DynamicLight> lightType = DynamicLight.getComponentType();
DynamicLight light = store.getComponent(ref,lightType);
// Get current light
ColorLight currentLight = light.getColorLight();
// Create new light configuration
// ColorLight has public fields: radius, red, green, blue (all bytes)
ColorLight torchLight = new ColorLight();
torchLight.radius = 8; // Light radius
torchLight.red = (byte)255; // Warm orange color
torchLight.green = (byte)200;
torchLight.blue = (byte)100;
light.setColorLight(torchLight);
// Or use constructor
ColorLight blueLight = new ColorLight(
(byte)12, // radius
(byte)50, // red
(byte)100, // green
(byte)255 // blue
);
light.setColorLight(blueLight);
```
{{< /tab >}}
{{< /tabs >}}
#### ColorLight Structure
The `ColorLight` class (`com.hypixel.hytale.protocol.ColorLight`) has public fields:
| Field | Type | Description |
|-------|------|-------------|
| `radius` | `byte` | Light radius |
| `red` | `byte` | Red component (0-255) |
| `green` | `byte` | Green component (0-255) |
| `blue` | `byte` | Blue component (0-255) |
---
### HeadRotation
Controls head rotation independently from body.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| rotation | `Vector3f` | Head rotation (x, y, z) |
{{< /tab >}}
{{< tab >}}
- `getRotation()` - Returns head rotation as Vector3f
- `setRotation(Vector3f)` - Sets head rotation (uses `assign()`)
- `getDirection()` - Returns look direction as Vector3d
- `getAxisDirection()` - Returns axis-aligned direction as Vector3i
- `teleportRotation(Vector3f)` - Teleports head rotation
{{< /tab >}}
{{< tab >}}
```java
ComponentType<EntityStore, HeadRotation> headType = HeadRotation.getComponentType();
HeadRotation head = store.getComponent(ref,headType);
// Get current head rotation
Vector3f rot = head.getRotation();
// Set head rotation (pitch, yaw, roll)
head.setRotation(new Vector3f(-30, 45, 0)); // Look up-right
// Get look direction as normalized vector
Vector3d direction = head.getDirection();
// Get axis direction (discrete: -1, 0, or 1 per axis)
Vector3i axisDir = head.getAxisDirection();
// Teleport rotation (instant, no interpolation)
head.teleportRotation(new Vector3f(0, 180, 0)); // Face backward
```
{{< /tab >}}
{{< /tabs >}}
---
## Behavior Markers
Singleton components that act as flags modifying entity behavior. They use the singleton pattern with an `INSTANCE` field.
### Interactable
{{< badge "Singleton" >}}
Marks an entity as interactable by players.
```java
ComponentType<EntityStore, Interactable> type = Interactable.getComponentType();
// Check if entity has component using archetype
Store<EntityStore> store = ref.getStore();
Archetype<EntityStore> archetype = store.getArchetype(ref);
if (archetype.contains(type)) {
// Entity is interactable
}
// Component is singleton - use INSTANCE
Interactable component = Interactable.INSTANCE;
```
---
### Invulnerable
{{< badge "Singleton" >}}
Makes an entity immune to damage.
```java
ComponentType<EntityStore, Invulnerable> type = Invulnerable.getComponentType();
// Check invulnerability in damage handler
getEventRegistry().register(EntityDamageEvent.class, event -> {
Ref<EntityStore> targetRef = event.getTarget();
if (targetRef != null && targetRef.isValid()) {
Store<EntityStore> store = targetRef.getStore();
Archetype<EntityStore> archetype = store.getArchetype(targetRef);
if (archetype.contains(type)) {
event.setCancelled(true);
}
}
});
```
---
### Intangible
{{< badge "Singleton" >}}
Makes an entity pass through other entities and blocks (no collision).
```java
ComponentType<EntityStore, Intangible> type = Intangible.getComponentType();
// Ghost entity - no collision
Store<EntityStore> store = ref.getStore();
Archetype<EntityStore> archetype = store.getArchetype(ref);
if (archetype.contains(type)) {
// Entity passes through obstacles
}
```
---
## Audio
### MovementAudioComponent
Handles movement-related sounds with a predicate system.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| shouldHearPredicate | `ShouldHearPredicate` | Predicate for who hears sounds |
| lastInsideBlockTypeId | `int` | Last block entity was inside |
| nextMoveInRepeat | `float` | Timer for movement sound repeat |
{{< /tab >}}
{{< tab >}}
- `getShouldHearPredicate(Ref<EntityStore>)` - Returns predicate for sound hearing
- `getLastInsideBlockTypeId()` - Returns last inside block ID
- `setLastInsideBlockTypeId(int)` - Sets last inside block ID
- `canMoveInRepeat()` - Returns true if repeat timer is active
- `tickMoveInRepeat(float)` - Advances repeat timer, returns true if elapsed
- `setNextMoveInRepeat(float)` - Sets next repeat interval
{{< /tab >}}
{{< tab >}}
```java
ComponentType<EntityStore, MovementAudioComponent> audioType =
MovementAudioComponent.getComponentType();
MovementAudioComponent movementAudio = store.getComponent(ref,audioType);
// Get predicate for who should hear movement sounds
MovementAudioComponent.ShouldHearPredicate predicate =
movementAudio.getShouldHearPredicate(entityRef);
// The predicate returns true for entities that should hear the sound
// (excludes the owner by default)
boolean shouldHear = predicate.test(otherEntityRef);
// Check move-in repeat timer
if (movementAudio.canMoveInRepeat()) {
float dt = 1.0f / 20.0f; // Delta time
if (movementAudio.tickMoveInRepeat(dt)) {
// Timer elapsed, play repeat sound
}
}
// Disable repeat sounds
movementAudio.setNextMoveInRepeat(MovementAudioComponent.NO_REPEAT);
```
{{< /tab >}}
{{< /tabs >}}
---
## Custom Components
### Creating Custom Components
Create custom components by implementing `Component<EntityStore>`:
```java
public class CustomDataComponent implements Component<EntityStore> {
// Static component type reference (set during registration)
private static ComponentType<EntityStore, CustomDataComponent> TYPE;
// Component data
private int level = 1;
private String faction = "neutral";
public static ComponentType<EntityStore, CustomDataComponent> getComponentType() {
return TYPE;
}
public int getLevel() { return level; }
public void setLevel(int level) { this.level = level; }
public String getFaction() { return faction; }
public void setFaction(String faction) { this.faction = faction; }
@Override
@Nonnull
public Component<EntityStore> clone() {
CustomDataComponent copy = new CustomDataComponent();
copy.level = this.level;
copy.faction = this.faction;
return copy;
}
}
```
### Registering Components
Register components during plugin start:
```java
@Override
public void start() {
// Register with EntityStoreRegistry
ComponentType<EntityStore, CustomDataComponent> type =
getEntityStoreRegistry().register(
"custom_data",
CustomDataComponent.class,
CustomDataComponent::new
);
// Store type reference for static access
CustomDataComponent.TYPE = type;
}
```
### Using Custom Components
```java
// Get component type
ComponentType<EntityStore, CustomDataComponent> type =
CustomDataComponent.getComponentType();
// Get store from ref
Store<EntityStore> store = ref.getStore();
// Check if entity has component
Archetype<EntityStore> archetype = store.getArchetype(ref);
if (archetype.contains(type)) {
CustomDataComponent data = store.getComponent(ref, type);
if (data.getLevel() > 10) {
// High level entity logic
}
}
```
---
## Best Practices
{{< callout type="info" >}}
**Component Guidelines:**
- Use `getComponentType()` static method to get the `ComponentType` for access
- Always check if a component exists using `hasComponent()` before accessing
- Register custom components in `start()`, after all plugins have completed setup
- Implement `clone()` for components that need copying
- Use singleton pattern (static `INSTANCE`) for marker components
{{< /callout >}}
{{< callout type="warning" >}}
**Network Sync:** Many components have an `isNetworkOutdated` flag. This is managed automatically by the network system - avoid setting it manually unless you understand the networking layer.
{{< /callout >}}
{{< callout type="error" >}}
**Thread Safety:** Component access should be done on the world's ticking thread. Use `world.isInThread()` to verify, or use the task system for cross-thread operations.
{{< /callout >}}
---
## Component Type Reference
| Component | Package | Type | Purpose |
|-----------|---------|------|---------|
| TransformComponent | entity.component | Data | Position, rotation, chunk |
| BoundingBox | entity.component | Data | Collision bounds |
| CollisionResultComponent | entity.component | Data | Collision results |
| PositionDataComponent | entity.component | Data | Block context |
| DisplayNameComponent | entity.component | Data | Visible name |
| ModelComponent | entity.component | Data | 3D model |
| EntityScaleComponent | entity.component | Data | Size scaling |
| ActiveAnimationComponent | entity.component | Data | Animations |
| DynamicLight | entity.component | Data | Light emission |
| HeadRotation | entity.component | Data | Head direction |
| MovementAudioComponent | entity.component | Data | Movement sounds |
| Interactable | entity.component | Singleton | Can be interacted |
| Invulnerable | entity.component | Singleton | Cannot be damaged |
| Intangible | entity.component | Singleton | No collision |

View File

@@ -0,0 +1,828 @@
---
title: Entity Components
type: docs
weight: 2
---
Hytale uses an Entity Component System (ECS) where entities are composed of modular components. Each component adds specific functionality or data to an entity.
## Component Categories
{{< cards cols="3" >}}
{{< card link="#transform--position" title="Transform & Position" subtitle="Location, rotation, collision" >}}
{{< card link="#display--visual" title="Display & Visual" subtitle="Models, animations, effects" >}}
{{< card link="#behavior-markers" title="Behavior Markers" subtitle="Interaction flags" >}}
{{< card link="#physics" title="Physics" subtitle="Position data, collision" >}}
{{< card link="#audio" title="Audio" subtitle="Movement sounds" >}}
{{< card link="#custom-components" title="Custom Components" subtitle="Plugin-defined components" >}}
{{< /cards >}}
---
## Component Access Pattern
Before diving into specific components, understand how to access components in Hytale's ECS:
```java
// Get component type from the component class
ComponentType<EntityStore, TransformComponent> transformType =
TransformComponent.getComponentType();
// Get Ref<EntityStore> from PlayerRef or other source
Ref<EntityStore> ref = playerRef.getReference();
if (ref == null || !ref.isValid()) {
return;
}
// Get Store from Ref, then access component via Store
Store<EntityStore> store = ref.getStore();
TransformComponent transform = store.getComponent(ref, transformType);
// Check if entity has a component using Archetype
Archetype<EntityStore> archetype = store.getArchetype(ref);
boolean hasComponent = archetype.contains(transformType);
```
{{< callout type="warning" >}}
**Important:** `Ref<EntityStore>` does NOT have `getComponent()` or `hasComponent()` methods directly. You must get the `Store` first via `ref.getStore()`, then call `store.getComponent(ref, componentType)`.
{{< /callout >}}
{{< callout type="info" >}}
All entity components implement `Component<EntityStore>` and provide a static `getComponentType()` method to retrieve the `ComponentType` for component access.
{{< /callout >}}
---
## Transform & Position
### TransformComponent
Core component for entity position, rotation, and chunk reference.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| position | `Vector3d` | World position (x, y, z) |
| rotation | `Vector3f` | Rotation angles (x, y, z) |
| chunkRef | `Ref<ChunkStore>` | Reference to containing chunk |
{{< /tab >}}
{{< tab >}}
- `getPosition()` - Returns world position as Vector3d
- `setPosition(Vector3d)` - Sets world position (uses `assign()`)
- `getRotation()` - Returns rotation angles
- `setRotation(Vector3f)` - Sets rotation angles (uses `assign()`)
- `teleportPosition(Vector3d)` - Teleports entity to position
- `getTransform()` - Returns Transform object combining position/rotation
- `getChunkRef()` - Returns chunk reference (`Ref<ChunkStore>`)
- `getChunk()` - **Deprecated** - Returns WorldChunk directly
{{< callout type="warning" >}}
`getChunk()` is deprecated and marked for removal. Use `getChunkRef()` instead.
{{< /callout >}}
{{< /tab >}}
{{< tab >}}
```java
// Get store from ref
Store<EntityStore> store = ref.getStore();
ComponentType<EntityStore, TransformComponent> transformType =
TransformComponent.getComponentType();
TransformComponent transform = store.getComponent(ref, transformType);
if (transform != null) {
// Get current position
Vector3d pos = transform.getPosition();
getLogger().at(Level.INFO).log("Entity at: " + pos.x + ", " + pos.y + ", " + pos.z);
// Get rotation
Vector3f rot = transform.getRotation();
// Get chunk reference (recommended)
Ref<ChunkStore> chunkRef = transform.getChunkRef();
}
```
{{< /tab >}}
{{< /tabs >}}
---
### BoundingBox
Defines the collision bounds of an entity using a `Box` object.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| boundingBox | `Box` | The collision box with min/max corners |
| detailBoxes | `Map<String, DetailBox[]>` | Named detail boxes for precise collision |
{{< /tab >}}
{{< tab >}}
- `getBoundingBox()` - Returns the `Box` object
- `setBoundingBox(Box)` - Sets the bounding box
- `getDetailBoxes()` - Returns map of detail boxes (can be null)
{{< /tab >}}
{{< tab >}}
```java
ComponentType<EntityStore, BoundingBox> boundsType = BoundingBox.getComponentType();
BoundingBox bounds = store.getComponent(ref, boundsType);
// Access the Box object
Box box = bounds.getBoundingBox();
// Box provides min/max as Vector3d
Vector3d min = box.getMin();
Vector3d max = box.getMax();
// Box utility methods
double width = box.width(); // max.x - min.x
double height = box.height(); // max.y - min.y
double depth = box.depth(); // max.z - min.z
// Center point
double centerX = box.middleX();
double centerY = box.middleY();
double centerZ = box.middleZ();
// Check intersection with another box
Box otherBox = otherBounds.getBoundingBox();
if (box.isIntersecting(otherBox)) {
handleCollision();
}
// Modify box (mutates in place)
box.offset(1.0, 0, 0); // Move box by offset
box.scale(2.0f); // Scale box
// Access detail boxes for precise collision
Map<String, DetailBox[]> details = bounds.getDetailBoxes();
if (details != null) {
for (Map.Entry<String, DetailBox[]> entry : details.entrySet()) {
// Process named detail boxes
}
}
```
{{< /tab >}}
{{< /tabs >}}
#### The Box Class
The `Box` class (`com.hypixel.hytale.math.shape.Box`) is a fundamental shape class:
| Method | Description |
|--------|-------------|
| `width()` | Returns width (X axis) |
| `height()` | Returns height (Y axis) |
| `depth()` | Returns depth (Z axis) |
| `middleX/Y/Z()` | Returns center coordinates |
| `getVolume()` | Returns total volume |
| `isIntersecting(Box)` | Checks intersection with another box |
| `containsPosition(x,y,z)` | Checks if point is inside |
| `offset(x,y,z)` | Moves box by offset (mutates) |
| `scale(float)` | Scales box (mutates) |
| `union(Box)` | Expands to contain another box |
| `clone()` | Creates a copy |
---
### CollisionResultComponent
Stores collision detection results from the physics system.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| collisionResult | `CollisionResult` | Full collision computation result |
| collisionStartPosition | `Vector3d` | Position where collision started |
| collisionPositionOffset | `Vector3d` | Offset from collision |
| pendingCollisionCheck | `boolean` | Whether collision check is pending |
{{< /tab >}}
{{< tab >}}
- `getCollisionResult()` - Returns the `CollisionResult` object
- `isPendingCollisionCheck()` - Returns true if collision needs recomputation
{{< /tab >}}
{{< tab >}}
```java
ComponentType<EntityStore, CollisionResultComponent> collisionType =
CollisionResultComponent.getComponentType();
CollisionResultComponent collision = store.getComponent(ref,collisionType);
// Get collision result object
CollisionResult result = collision.getCollisionResult();
// Check for block collisions
int blockCollisionCount = result.getBlockCollisionCount();
if (blockCollisionCount > 0) {
BlockCollisionData firstCollision = result.getFirstBlockCollision();
// Process collision
}
// Check if sliding (on ground)
if (result.isSliding) {
// Entity is sliding on a surface
double slideStart = result.slideStart;
double slideEnd = result.slideEnd;
}
// Check character collisions (if enabled)
int charCollisionCount = result.getCharacterCollisionCount();
CharacterCollisionData charCollision = result.getFirstCharacterCollision();
// Access trigger blocks (collision triggers)
CollisionDataArray<BlockCollisionData> triggers = result.getTriggerBlocks();
```
{{< /tab >}}
{{< /tabs >}}
{{< callout type="info" >}}
The `CollisionResult` class is complex and handles block collisions, character collisions, slides, and trigger blocks. It's primarily used internally by the collision system.
{{< /callout >}}
---
### PositionDataComponent
Tracks block type IDs at entity's position.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| insideBlockTypeId | `int` | Block type ID entity is inside |
| standingOnBlockTypeId | `int` | Block type ID entity is standing on |
{{< /tab >}}
{{< tab >}}
- `getInsideBlockTypeId()` - Returns block type ID entity is inside
- `setInsideBlockTypeId(int)` - Sets inside block type ID
- `getStandingOnBlockTypeId()` - Returns block type ID beneath entity
- `setStandingOnBlockTypeId(int)` - Sets standing-on block type ID
{{< /tab >}}
{{< tab >}}
```java
ComponentType<EntityStore, PositionDataComponent> posDataType =
PositionDataComponent.getComponentType();
PositionDataComponent posData = store.getComponent(ref,posDataType);
// Check what block entity is standing on
int standingOn = posData.getStandingOnBlockTypeId();
// Check what block entity is inside
int insideBlock = posData.getInsideBlockTypeId();
// Use block type registry to get actual BlockType
BlockType blockType = BlockType.getAssetMap().getAsset(standingOn);
if (blockType != null) {
getLogger().at(Level.INFO).log("Standing on: " + blockType.getId());
}
```
{{< /tab >}}
{{< /tabs >}}
---
## Display & Visual
### DisplayNameComponent
Controls the display name shown above an entity.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| displayName | `Message` | The displayed name as a Message object |
{{< /tab >}}
{{< tab >}}
- `getDisplayName()` - Returns the `Message` display name
{{< /tab >}}
{{< tab >}}
```java
ComponentType<EntityStore, DisplayNameComponent> nameType =
DisplayNameComponent.getComponentType();
DisplayNameComponent nameComp = store.getComponent(ref,nameType);
// Get current display name
Message name = nameComp.getDisplayName();
// Message is the formatted text object used throughout Hytale
// Use Message.raw() or Message formatting methods to create new names
```
{{< /tab >}}
{{< /tabs >}}
{{< callout type="info" >}}
**Message Type:** Display names use Hytale's `Message` class, not plain strings. The `Message` class supports formatted text, colors, and localization.
{{< /callout >}}
---
### ModelComponent
Links an entity to its visual 3D model.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| model | `Model` | Reference to the model |
| isNetworkOutdated | `boolean` | Network sync flag |
{{< /tab >}}
{{< tab >}}
- `getModel()` - Returns the Model
- `setModel(Model)` - Sets the model
{{< /tab >}}
{{< tab >}}
```java
ComponentType<EntityStore, ModelComponent> modelType = ModelComponent.getComponentType();
ModelComponent model = store.getComponent(ref,modelType);
// Get current model
Model currentModel = model.getModel();
// Set new model
model.setModel(newModel);
```
{{< /tab >}}
{{< /tabs >}}
---
### EntityScaleComponent
Controls the scale/size of an entity.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| scale | `float` | Uniform scale factor (default: 1.0) |
| isNetworkOutdated | `boolean` | Network sync flag |
{{< /tab >}}
{{< tab >}}
- `getScale()` - Returns the scale factor (float)
- `setScale(float)` - Sets the uniform scale factor
{{< /tab >}}
{{< tab >}}
```java
ComponentType<EntityStore, EntityScaleComponent> scaleType =
EntityScaleComponent.getComponentType();
EntityScaleComponent scale = store.getComponent(ref,scaleType);
// Get current scale
float currentScale = scale.getScale(); // Default: 1.0
// Make entity twice as large
scale.setScale(2.0f);
// Make entity half size
scale.setScale(0.5f);
// Giant boss spawn effect - track growth in tick handler
private final Map<Ref<EntityStore>, Float> growingBosses = new ConcurrentHashMap<>();
public void startGrowBoss(Ref<EntityStore> bossRef) {
EntityScaleComponent scale = store.getComponent(bossRef, scaleType);
scale.setScale(0.1f); // Start small
growingBosses.put(bossRef, 0.1f);
}
// Call this from your tick handler (every 5 ticks)
public void onTick() {
growingBosses.entrySet().removeIf(entry -> {
Ref<EntityStore> bossRef = entry.getKey();
float currentScale = entry.getValue();
if (currentScale >= 1.0f) {
return true; // Remove from map
}
EntityScaleComponent scale = store.getComponent(bossRef, scaleType);
if (scale != null) {
float newScale = currentScale + 0.1f;
scale.setScale(newScale);
entry.setValue(newScale);
}
return false; // Keep in map
});
}
```
{{< /tab >}}
{{< /tabs >}}
{{< callout type="warning" >}}
**Uniform Scale Only:** Hytale's EntityScaleComponent uses a single `float` for uniform scaling on all axes, not per-axis Vector3f scaling.
{{< /callout >}}
---
### ActiveAnimationComponent
Manages currently playing animations per animation slot.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| activeAnimations | `String[]` | Animation names per slot |
| isNetworkOutdated | `boolean` | Network sync flag |
{{< /tab >}}
{{< tab >}}
- `getActiveAnimations()` - Returns array of animation names indexed by `AnimationSlot`
- `setPlayingAnimation(AnimationSlot, String)` - Sets animation for a specific slot
{{< /tab >}}
{{< tab >}}
```java
ComponentType<EntityStore, ActiveAnimationComponent> animType =
ActiveAnimationComponent.getComponentType();
ActiveAnimationComponent anim = store.getComponent(ref,animType);
// Get all active animations
String[] animations = anim.getActiveAnimations();
// Set animation for specific slot
anim.setPlayingAnimation(AnimationSlot.Action, "attack_swing");
anim.setPlayingAnimation(AnimationSlot.Movement, "walk");
// Clear animation on a slot
anim.setPlayingAnimation(AnimationSlot.Emote, null);
```
{{< /tab >}}
{{< /tabs >}}
#### AnimationSlot Enum
Hytale uses animation slots to layer animations:
| Slot | Value | Purpose |
|------|-------|---------|
| `Movement` | 0 | Locomotion (walk, run, jump) |
| `Status` | 1 | Status effects |
| `Action` | 2 | Actions (attack, use item) |
| `Face` | 3 | Facial expressions |
| `Emote` | 4 | Social emotes |
---
### DynamicLight
Emits dynamic light from an entity using a `ColorLight` structure.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| colorLight | `ColorLight` | Light data (radius, RGB) |
| isNetworkOutdated | `boolean` | Network sync flag |
{{< /tab >}}
{{< tab >}}
- `getColorLight()` - Returns the `ColorLight` object
- `setColorLight(ColorLight)` - Sets the light configuration
{{< /tab >}}
{{< tab >}}
```java
ComponentType<EntityStore, DynamicLight> lightType = DynamicLight.getComponentType();
DynamicLight light = store.getComponent(ref,lightType);
// Get current light
ColorLight currentLight = light.getColorLight();
// Create new light configuration
// ColorLight has public fields: radius, red, green, blue (all bytes)
ColorLight torchLight = new ColorLight();
torchLight.radius = 8; // Light radius
torchLight.red = (byte)255; // Warm orange color
torchLight.green = (byte)200;
torchLight.blue = (byte)100;
light.setColorLight(torchLight);
// Or use constructor
ColorLight blueLight = new ColorLight(
(byte)12, // radius
(byte)50, // red
(byte)100, // green
(byte)255 // blue
);
light.setColorLight(blueLight);
```
{{< /tab >}}
{{< /tabs >}}
#### ColorLight Structure
The `ColorLight` class (`com.hypixel.hytale.protocol.ColorLight`) has public fields:
| Field | Type | Description |
|-------|------|-------------|
| `radius` | `byte` | Light radius |
| `red` | `byte` | Red component (0-255) |
| `green` | `byte` | Green component (0-255) |
| `blue` | `byte` | Blue component (0-255) |
---
### HeadRotation
Controls head rotation independently from body.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| rotation | `Vector3f` | Head rotation (x, y, z) |
{{< /tab >}}
{{< tab >}}
- `getRotation()` - Returns head rotation as Vector3f
- `setRotation(Vector3f)` - Sets head rotation (uses `assign()`)
- `getDirection()` - Returns look direction as Vector3d
- `getAxisDirection()` - Returns axis-aligned direction as Vector3i
- `teleportRotation(Vector3f)` - Teleports head rotation
{{< /tab >}}
{{< tab >}}
```java
ComponentType<EntityStore, HeadRotation> headType = HeadRotation.getComponentType();
HeadRotation head = store.getComponent(ref,headType);
// Get current head rotation
Vector3f rot = head.getRotation();
// Set head rotation (pitch, yaw, roll)
head.setRotation(new Vector3f(-30, 45, 0)); // Look up-right
// Get look direction as normalized vector
Vector3d direction = head.getDirection();
// Get axis direction (discrete: -1, 0, or 1 per axis)
Vector3i axisDir = head.getAxisDirection();
// Teleport rotation (instant, no interpolation)
head.teleportRotation(new Vector3f(0, 180, 0)); // Face backward
```
{{< /tab >}}
{{< /tabs >}}
---
## Behavior Markers
Singleton components that act as flags modifying entity behavior. They use the singleton pattern with an `INSTANCE` field.
### Interactable
{{< badge "Singleton" >}}
Marks an entity as interactable by players.
```java
ComponentType<EntityStore, Interactable> type = Interactable.getComponentType();
// Check if entity has component using archetype
Store<EntityStore> store = ref.getStore();
Archetype<EntityStore> archetype = store.getArchetype(ref);
if (archetype.contains(type)) {
// Entity is interactable
}
// Component is singleton - use INSTANCE
Interactable component = Interactable.INSTANCE;
```
---
### Invulnerable
{{< badge "Singleton" >}}
Makes an entity immune to damage.
```java
ComponentType<EntityStore, Invulnerable> type = Invulnerable.getComponentType();
// Check invulnerability in damage handler
getEventRegistry().register(EntityDamageEvent.class, event -> {
Ref<EntityStore> targetRef = event.getTarget();
if (targetRef != null && targetRef.isValid()) {
Store<EntityStore> store = targetRef.getStore();
Archetype<EntityStore> archetype = store.getArchetype(targetRef);
if (archetype.contains(type)) {
event.setCancelled(true);
}
}
});
```
---
### Intangible
{{< badge "Singleton" >}}
Makes an entity pass through other entities and blocks (no collision).
```java
ComponentType<EntityStore, Intangible> type = Intangible.getComponentType();
// Ghost entity - no collision
Store<EntityStore> store = ref.getStore();
Archetype<EntityStore> archetype = store.getArchetype(ref);
if (archetype.contains(type)) {
// Entity passes through obstacles
}
```
---
## Audio
### MovementAudioComponent
Handles movement-related sounds with a predicate system.
**Package:** `com.hypixel.hytale.server.core.modules.entity.component`
{{< tabs items="Fields,Methods,Example" >}}
{{< tab >}}
| Field | Type | Description |
|-------|------|-------------|
| shouldHearPredicate | `ShouldHearPredicate` | Predicate for who hears sounds |
| lastInsideBlockTypeId | `int` | Last block entity was inside |
| nextMoveInRepeat | `float` | Timer for movement sound repeat |
{{< /tab >}}
{{< tab >}}
- `getShouldHearPredicate(Ref<EntityStore>)` - Returns predicate for sound hearing
- `getLastInsideBlockTypeId()` - Returns last inside block ID
- `setLastInsideBlockTypeId(int)` - Sets last inside block ID
- `canMoveInRepeat()` - Returns true if repeat timer is active
- `tickMoveInRepeat(float)` - Advances repeat timer, returns true if elapsed
- `setNextMoveInRepeat(float)` - Sets next repeat interval
{{< /tab >}}
{{< tab >}}
```java
ComponentType<EntityStore, MovementAudioComponent> audioType =
MovementAudioComponent.getComponentType();
MovementAudioComponent movementAudio = store.getComponent(ref,audioType);
// Get predicate for who should hear movement sounds
MovementAudioComponent.ShouldHearPredicate predicate =
movementAudio.getShouldHearPredicate(entityRef);
// The predicate returns true for entities that should hear the sound
// (excludes the owner by default)
boolean shouldHear = predicate.test(otherEntityRef);
// Check move-in repeat timer
if (movementAudio.canMoveInRepeat()) {
float dt = 1.0f / 20.0f; // Delta time
if (movementAudio.tickMoveInRepeat(dt)) {
// Timer elapsed, play repeat sound
}
}
// Disable repeat sounds
movementAudio.setNextMoveInRepeat(MovementAudioComponent.NO_REPEAT);
```
{{< /tab >}}
{{< /tabs >}}
---
## Custom Components
### Creating Custom Components
Create custom components by implementing `Component<EntityStore>`:
```java
public class CustomDataComponent implements Component<EntityStore> {
// Static component type reference (set during registration)
private static ComponentType<EntityStore, CustomDataComponent> TYPE;
// Component data
private int level = 1;
private String faction = "neutral";
public static ComponentType<EntityStore, CustomDataComponent> getComponentType() {
return TYPE;
}
public int getLevel() { return level; }
public void setLevel(int level) { this.level = level; }
public String getFaction() { return faction; }
public void setFaction(String faction) { this.faction = faction; }
@Override
@Nonnull
public Component<EntityStore> clone() {
CustomDataComponent copy = new CustomDataComponent();
copy.level = this.level;
copy.faction = this.faction;
return copy;
}
}
```
### Registering Components
Register components during plugin start:
```java
@Override
public void start() {
// Register with EntityStoreRegistry
ComponentType<EntityStore, CustomDataComponent> type =
getEntityStoreRegistry().register(
"custom_data",
CustomDataComponent.class,
CustomDataComponent::new
);
// Store type reference for static access
CustomDataComponent.TYPE = type;
}
```
### Using Custom Components
```java
// Get component type
ComponentType<EntityStore, CustomDataComponent> type =
CustomDataComponent.getComponentType();
// Get store from ref
Store<EntityStore> store = ref.getStore();
// Check if entity has component
Archetype<EntityStore> archetype = store.getArchetype(ref);
if (archetype.contains(type)) {
CustomDataComponent data = store.getComponent(ref, type);
if (data.getLevel() > 10) {
// High level entity logic
}
}
```
---
## Best Practices
{{< callout type="info" >}}
**Component Guidelines:**
- Use `getComponentType()` static method to get the `ComponentType` for access
- Always check if a component exists using `hasComponent()` before accessing
- Register custom components in `start()`, after all plugins have completed setup
- Implement `clone()` for components that need copying
- Use singleton pattern (static `INSTANCE`) for marker components
{{< /callout >}}
{{< callout type="warning" >}}
**Network Sync:** Many components have an `isNetworkOutdated` flag. This is managed automatically by the network system - avoid setting it manually unless you understand the networking layer.
{{< /callout >}}
{{< callout type="error" >}}
**Thread Safety:** Component access should be done on the world's ticking thread. Use `world.isInThread()` to verify, or use the task system for cross-thread operations.
{{< /callout >}}
---
## Component Type Reference
| Component | Package | Type | Purpose |
|-----------|---------|------|---------|
| TransformComponent | entity.component | Data | Position, rotation, chunk |
| BoundingBox | entity.component | Data | Collision bounds |
| CollisionResultComponent | entity.component | Data | Collision results |
| PositionDataComponent | entity.component | Data | Block context |
| DisplayNameComponent | entity.component | Data | Visible name |
| ModelComponent | entity.component | Data | 3D model |
| EntityScaleComponent | entity.component | Data | Size scaling |
| ActiveAnimationComponent | entity.component | Data | Animations |
| DynamicLight | entity.component | Data | Light emission |
| HeadRotation | entity.component | Data | Head direction |
| MovementAudioComponent | entity.component | Data | Movement sounds |
| Interactable | entity.component | Singleton | Can be interacted |
| Invulnerable | entity.component | Singleton | Cannot be damaged |
| Intangible | entity.component | Singleton | No collision |

View File

@@ -0,0 +1,259 @@
---
title: Entity Hierarchy
type: docs
weight: 1
---
Hytale uses an **Entity Component System (ECS)** architecture. Unlike traditional OOP where entities have methods directly on them, Hytale entities are composed of components stored in an `EntityStore`.
{{< callout type="warning" >}}
**ECS Architecture:** Entity classes like `Player` and `LivingEntity` are themselves `Component<EntityStore>` instances. Data is accessed through component lookup, not direct method calls on entities.
{{< /callout >}}
## Class Hierarchy Overview
{{< filetree/container >}}
{{< filetree/folder name="Entity (Component)" state="open" >}}
{{< filetree/folder name="LivingEntity" state="open" >}}
{{< filetree/file name="Player" >}}
{{< /filetree/folder >}}
{{< /filetree/folder >}}
{{< /filetree/container >}}
---
## Entity (Base Class)
**Package:** `com.hypixel.hytale.server.core.entity`
The base class for all entities. `Entity` implements `Component<EntityStore>` - it is stored as a component in the ECS system.
### Available Methods
| Method | Return Type | Description | Notes |
|--------|-------------|-------------|-------|
| `getWorld()` | `World` | The world this entity is in | |
| `getUuid()` | `UUID` | Unique identifier | Deprecated |
| `wasRemoved()` | `boolean` | Whether entity was removed | |
| `remove()` | `boolean` | Remove entity from world | |
| `getNetworkId()` | `int` | Network ID for this entity | Deprecated |
| `getTransformComponent()` | `TransformComponent` | Position/rotation data | Deprecated |
| `getReference()` | `Ref<EntityStore>` | ECS reference | |
```java
// Example: Accessing entity data
Entity entity = ...; // obtained from event or lookup
if (!entity.wasRemoved()) {
World world = entity.getWorld();
// Position access (deprecated but works)
TransformComponent transform = entity.getTransformComponent();
Vector3d position = transform.getPosition();
// ECS reference for component access
Ref<EntityStore> ref = entity.getReference();
if (ref != null && ref.isValid()) {
Store<EntityStore> store = ref.getStore();
// Access other components via store
}
}
```
---
## LivingEntity
**Package:** `com.hypixel.hytale.server.core.entity`
Extends Entity with inventory capabilities. Base class for entities that can hold items.
### Available Methods
| Method | Return Type | Description |
|--------|-------------|-------------|
| `getInventory()` | `Inventory` | Entity's inventory |
| `setInventory(Inventory)` | `Inventory` | Set inventory |
| `getCurrentFallDistance()` | `double` | Current fall distance |
| `getStatModifiersManager()` | `StatModifiersManager` | Stat modifiers |
```java
LivingEntity living = (LivingEntity) entity;
// Inventory access
Inventory inventory = living.getInventory();
// Fall distance tracking
double fallDistance = living.getCurrentFallDistance();
// Stat modifiers
StatModifiersManager stats = living.getStatModifiersManager();
```
{{< callout type="info" >}}
Health, damage, and effects are managed through the ECS system, not directly on LivingEntity. See [Entity Components]({{< relref "entity-components" >}}) for component-based data access.
{{< /callout >}}
---
## Player
**Package:** `com.hypixel.hytale.server.core.entity.entities`
Extends LivingEntity with player-specific features.
### Available Methods
| Method | Return Type | Description |
|--------|-------------|-------------|
| `sendMessage(Message)` | `void` | Send message to player |
| `hasPermission(String)` | `boolean` | Check permission |
| `hasPermission(String, boolean)` | `boolean` | Check with default |
| `getDisplayName()` | `String` | Player display name |
| `getGameMode()` | `GameMode` | Current game mode |
| `getInventory()` | `Inventory` | Player inventory |
| `getPlayerRef()` | `PlayerRef` | Thread-safe reference (deprecated) |
| `getWindowManager()` | `WindowManager` | Window management |
| `getPageManager()` | `PageManager` | Page management |
| `getHudManager()` | `HudManager` | HUD management |
| `getHotbarManager()` | `HotbarManager` | Hotbar management |
```java
Player player = event.getPlayer();
if (player != null) {
// Send messages (requires Message object)
player.sendMessage(Message.raw("Welcome!"));
player.sendMessage(Message.translation("greeting.key")
.param("name", player.getDisplayName()));
// Check permissions
if (player.hasPermission("admin.teleport")) {
// Has permission
}
// Get display name
String name = player.getDisplayName();
// Get inventory
Inventory inventory = player.getInventory();
// Get position via TransformComponent (deprecated)
TransformComponent transform = player.getTransformComponent();
Vector3d position = transform.getPosition();
}
```
---
## PlayerRef (Thread-Safe Reference)
**Package:** `com.hypixel.hytale.server.core.universe`
`PlayerRef` provides thread-safe access to player data. Use it for async operations or storing references.
### Available Methods
| Method | Return Type | Description |
|--------|-------------|-------------|
| `getUsername()` | `String` | Player username |
| `getUuid()` | `UUID` | Player UUID |
| `getWorldUuid()` | `UUID` | Current world UUID |
| `sendMessage(Message)` | `void` | Send message |
| `getReference()` | `Ref<EntityStore>` | ECS reference (may be null) |
| `getPacketHandler()` | `PacketHandler` | Network handler |
```java
// Get PlayerRef from event
PlayerRef playerRef = event.getPlayerRef();
// Thread-safe access to player data
String username = playerRef.getUsername();
UUID uuid = playerRef.getUuid();
// Send message (thread-safe)
playerRef.sendMessage(Message.raw("Hello, " + username + "!"));
// Get ECS reference for component access
Ref<EntityStore> ref = playerRef.getReference();
if (ref != null && ref.isValid()) {
Store<EntityStore> store = ref.getStore();
// Access Player component
Player player = store.getComponent(ref, Player.getComponentType());
// Access TransformComponent for position
TransformComponent transform = store.getComponent(ref, TransformComponent.getComponentType());
if (transform != null) {
Vector3d position = transform.getPosition();
}
}
```
---
## ECS Component Access Pattern
The correct way to access entity data in Hytale's ECS:
```java
getEventRegistry().register(PlayerConnectEvent.class, event -> {
PlayerRef playerRef = event.getPlayerRef();
// Get ECS reference
Ref<EntityStore> ref = playerRef.getReference();
if (ref == null || !ref.isValid()) {
return;
}
// Get store for component access
Store<EntityStore> store = ref.getStore();
// Access various components
Player player = store.getComponent(ref, Player.getComponentType());
TransformComponent transform = store.getComponent(ref, TransformComponent.getComponentType());
if (player != null && transform != null) {
Vector3d position = transform.getPosition();
player.sendMessage(Message.raw("You are at: " + position.toString()));
}
});
```
---
## Type Checking & Casting
Use pattern matching for safe type checking:
```java
public void handleEntity(Entity entity) {
if (entity instanceof Player player) {
player.sendMessage(Message.raw("You're a player!"));
} else if (entity instanceof LivingEntity living) {
Inventory inv = living.getInventory();
}
}
```
---
## Best Practices
{{< callout type="info" >}}
**Entity Handling Guidelines:**
- Always check `wasRemoved()` before using stored entity references
- Use `PlayerRef` for storing player references across ticks
- Access position via `TransformComponent` (deprecated methods work but ECS is preferred)
- Check `ref.isValid()` before accessing ECS components
{{< /callout >}}
{{< callout type="warning" >}}
**Thread Safety:** Entity objects are not thread-safe. Use `PlayerRef` for async operations. Never store direct entity references in long-lived data structures without proper cleanup.
{{< /callout >}}
{{< callout type="error" >}}
**Critical:** Never store `Entity` or `Player` references in static fields or maps without proper cleanup. Use UUIDs and look up entities when needed.
{{< /callout >}}

View File

@@ -0,0 +1,259 @@
---
title: Hiérarchie des Entités
type: docs
weight: 1
---
Hytale utilise une architecture **Entity Component System (ECS)**. Contrairement à la POO traditionnelle où les entités ont des méthodes directement sur elles, les entités Hytale sont composées de composants stockés dans un `EntityStore`.
{{< callout type="warning" >}}
**Architecture ECS:** Les classes d'entités comme `Player` et `LivingEntity` sont elles-mêmes des instances de `Component<EntityStore>`. Les données sont accessibles via la recherche de composants, pas via des appels de méthodes directs sur les entités.
{{< /callout >}}
## Aperçu de la Hiérarchie de Classes
{{< filetree/container >}}
{{< filetree/folder name="Entity (Component)" state="open" >}}
{{< filetree/folder name="LivingEntity" state="open" >}}
{{< filetree/file name="Player" >}}
{{< /filetree/folder >}}
{{< /filetree/folder >}}
{{< /filetree/container >}}
---
## Entity (Classe de Base)
**Package:** `com.hypixel.hytale.server.core.entity`
La classe de base pour toutes les entités. `Entity` implémente `Component<EntityStore>` - elle est stockée comme composant dans le système ECS.
### Méthodes Disponibles
| Méthode | Type de Retour | Description | Notes |
|---------|----------------|-------------|-------|
| `getWorld()` | `World` | Le monde où se trouve l'entité | |
| `getUuid()` | `UUID` | Identifiant unique | Dépréciée |
| `wasRemoved()` | `boolean` | Si l'entité a été supprimée | |
| `remove()` | `boolean` | Supprimer l'entité du monde | |
| `getNetworkId()` | `int` | ID réseau de l'entité | Dépréciée |
| `getTransformComponent()` | `TransformComponent` | Données de position/rotation | Dépréciée |
| `getReference()` | `Ref<EntityStore>` | Référence ECS | |
```java
// Exemple: Accéder aux données d'entité
Entity entity = ...; // obtenue depuis un événement ou une recherche
if (!entity.wasRemoved()) {
World world = entity.getWorld();
// Accès à la position (dépréciée mais fonctionne)
TransformComponent transform = entity.getTransformComponent();
Vector3d position = transform.getPosition();
// Référence ECS pour l'accès aux composants
Ref<EntityStore> ref = entity.getReference();
if (ref != null && ref.isValid()) {
Store<EntityStore> store = ref.getStore();
// Accéder à d'autres composants via le store
}
}
```
---
## LivingEntity
**Package:** `com.hypixel.hytale.server.core.entity`
Étend Entity avec des capacités d'inventaire. Classe de base pour les entités qui peuvent porter des objets.
### Méthodes Disponibles
| Méthode | Type de Retour | Description |
|---------|----------------|-------------|
| `getInventory()` | `Inventory` | Inventaire de l'entité |
| `setInventory(Inventory)` | `Inventory` | Définir l'inventaire |
| `getCurrentFallDistance()` | `double` | Distance de chute actuelle |
| `getStatModifiersManager()` | `StatModifiersManager` | Modificateurs de stats |
```java
LivingEntity living = (LivingEntity) entity;
// Accès à l'inventaire
Inventory inventory = living.getInventory();
// Suivi de la distance de chute
double fallDistance = living.getCurrentFallDistance();
// Modificateurs de stats
StatModifiersManager stats = living.getStatModifiersManager();
```
{{< callout type="info" >}}
La santé, les dégâts et les effets sont gérés via le système ECS, pas directement sur LivingEntity. Voir [Composants d'Entité]({{< relref "entity-components" >}}) pour l'accès aux données basé sur les composants.
{{< /callout >}}
---
## Player
**Package:** `com.hypixel.hytale.server.core.entity.entities`
Étend LivingEntity avec des fonctionnalités spécifiques aux joueurs.
### Méthodes Disponibles
| Méthode | Type de Retour | Description |
|---------|----------------|-------------|
| `sendMessage(Message)` | `void` | Envoyer un message au joueur |
| `hasPermission(String)` | `boolean` | Vérifier une permission |
| `hasPermission(String, boolean)` | `boolean` | Vérifier avec valeur par défaut |
| `getDisplayName()` | `String` | Nom d'affichage du joueur |
| `getGameMode()` | `GameMode` | Mode de jeu actuel |
| `getInventory()` | `Inventory` | Inventaire du joueur |
| `getPlayerRef()` | `PlayerRef` | Référence thread-safe (dépréciée) |
| `getWindowManager()` | `WindowManager` | Gestion des fenêtres |
| `getPageManager()` | `PageManager` | Gestion des pages |
| `getHudManager()` | `HudManager` | Gestion du HUD |
| `getHotbarManager()` | `HotbarManager` | Gestion de la barre d'action |
```java
Player player = event.getPlayer();
if (player != null) {
// Envoyer des messages (nécessite un objet Message)
player.sendMessage(Message.raw("Bienvenue !"));
player.sendMessage(Message.translation("greeting.key")
.param("name", player.getDisplayName()));
// Vérifier les permissions
if (player.hasPermission("admin.teleport")) {
// A la permission
}
// Obtenir le nom d'affichage
String name = player.getDisplayName();
// Obtenir l'inventaire
Inventory inventory = player.getInventory();
// Obtenir la position via TransformComponent (dépréciée)
TransformComponent transform = player.getTransformComponent();
Vector3d position = transform.getPosition();
}
```
---
## PlayerRef (Référence Thread-Safe)
**Package:** `com.hypixel.hytale.server.core.universe`
`PlayerRef` fournit un accès thread-safe aux données du joueur. Utilisez-le pour les opérations async ou pour stocker des références.
### Méthodes Disponibles
| Méthode | Type de Retour | Description |
|---------|----------------|-------------|
| `getUsername()` | `String` | Nom d'utilisateur du joueur |
| `getUuid()` | `UUID` | UUID du joueur |
| `getWorldUuid()` | `UUID` | UUID du monde actuel |
| `sendMessage(Message)` | `void` | Envoyer un message |
| `getReference()` | `Ref<EntityStore>` | Référence ECS (peut être null) |
| `getPacketHandler()` | `PacketHandler` | Gestionnaire réseau |
```java
// Obtenir PlayerRef depuis l'événement
PlayerRef playerRef = event.getPlayerRef();
// Accès thread-safe aux données du joueur
String username = playerRef.getUsername();
UUID uuid = playerRef.getUuid();
// Envoyer un message (thread-safe)
playerRef.sendMessage(Message.raw("Bonjour, " + username + " !"));
// Obtenir la référence ECS pour l'accès aux composants
Ref<EntityStore> ref = playerRef.getReference();
if (ref != null && ref.isValid()) {
Store<EntityStore> store = ref.getStore();
// Accéder au composant Player
Player player = store.getComponent(ref, Player.getComponentType());
// Accéder au TransformComponent pour la position
TransformComponent transform = store.getComponent(ref, TransformComponent.getComponentType());
if (transform != null) {
Vector3d position = transform.getPosition();
}
}
```
---
## Pattern d'Accès aux Composants ECS
La bonne façon d'accéder aux données d'entité dans l'ECS de Hytale:
```java
getEventRegistry().register(PlayerConnectEvent.class, event -> {
PlayerRef playerRef = event.getPlayerRef();
// Obtenir la référence ECS
Ref<EntityStore> ref = playerRef.getReference();
if (ref == null || !ref.isValid()) {
return;
}
// Obtenir le store pour l'accès aux composants
Store<EntityStore> store = ref.getStore();
// Accéder à divers composants
Player player = store.getComponent(ref, Player.getComponentType());
TransformComponent transform = store.getComponent(ref, TransformComponent.getComponentType());
if (player != null && transform != null) {
Vector3d position = transform.getPosition();
player.sendMessage(Message.raw("Vous êtes à : " + position.toString()));
}
});
```
---
## Vérification de Type & Cast
Utilisez le pattern matching pour une vérification de type sûre:
```java
public void handleEntity(Entity entity) {
if (entity instanceof Player player) {
player.sendMessage(Message.raw("Vous êtes un joueur !"));
} else if (entity instanceof LivingEntity living) {
Inventory inv = living.getInventory();
}
}
```
---
## Bonnes Pratiques
{{< callout type="info" >}}
**Directives de Gestion des Entités:**
- Toujours vérifier `wasRemoved()` avant d'utiliser des références d'entités stockées
- Utiliser `PlayerRef` pour stocker des références de joueurs entre les ticks
- Accéder à la position via `TransformComponent` (les méthodes dépréciées fonctionnent mais l'ECS est préféré)
- Vérifier `ref.isValid()` avant d'accéder aux composants ECS
{{< /callout >}}
{{< callout type="warning" >}}
**Sécurité des Threads:** Les objets Entity ne sont pas thread-safe. Utilisez `PlayerRef` pour les opérations async. Ne stockez jamais de références directes d'entités dans des structures de données à longue durée de vie sans nettoyage approprié.
{{< /callout >}}
{{< callout type="error" >}}
**Critique:** Ne stockez jamais de références `Entity` ou `Player` dans des champs statiques ou des maps sans nettoyage approprié. Utilisez les UUID et recherchez les entités quand nécessaire.
{{< /callout >}}

View File

@@ -0,0 +1,302 @@
---
title: Flocking Behavior
type: docs
weight: 10
---
The flock system enables NPCs to form and behave as coordinated groups.
**Package:** `com.hypixel.hytale.server.flock`
## Overview
Flocks are entity-based groups that allow NPCs to:
- Move and act as a coordinated unit
- Share damage information across members
- Spawn in configurable group sizes
- Have designated leaders
## Architecture
```
Flock System
├── Components
│ ├── Flock - Group entity component
│ ├── FlockMembership - Member reference
│ └── PersistentFlockData - Saved flock data
├── Assets
│ ├── FlockAsset - Flock configuration
│ ├── RangeSizeFlockAsset - Range-based size
│ └── WeightedSizeFlockAsset - Weighted size
├── Systems
│ ├── FlockSystems - Core flock logic
│ ├── FlockMembershipSystems - Member management
│ └── FlockDeathSystems - Death handling
└── NPC Components
├── BodyMotionFlock - Movement behavior
├── ActionFlockJoin/Leave - Join/leave actions
└── SensorFlockLeader - Leader detection
```
## Flock Component
The flock entity holds shared group state:
```java
public class Flock implements Component<EntityStore> {
// Shared damage data for combat coordination
private DamageData currentDamageData;
private DamageData nextDamageData;
// Leader-specific damage data
private DamageData currentLeaderDamageData;
private DamageData nextLeaderDamageData;
// Persistent group configuration
private PersistentFlockData flockData;
// Removal status
public enum FlockRemovedStatus {
NOT_REMOVED,
DISSOLVED,
UNLOADED
}
}
```
## FlockMembership Component
Each NPC member has a membership component:
```java
public class FlockMembership implements Component<EntityStore> {
// Reference to the flock entity
private Ref<EntityStore> flockRef;
}
```
## Creating Flocks
### Spawn with Flock
```java
// Spawn an NPC with its flock
Ref<EntityStore> flockRef = FlockPlugin.trySpawnFlock(
npcRef, // Initial NPC reference
npc, // NPC component
store, // Entity store
roleIndex, // NPC role index
position, // Spawn position
rotation, // Spawn rotation
flockDefinition, // FlockAsset config (determines size)
postSpawnCallback // Called for each spawned member
);
```
### Create Empty Flock
```java
// Create flock entity for manual member management
Ref<EntityStore> flockRef = FlockPlugin.createFlock(store, role);
// Or with explicit configuration
Ref<EntityStore> flockRef = FlockPlugin.createFlock(
store,
flockAsset, // FlockAsset configuration
allowedRoles // Roles that can join
);
```
### Join Existing Flock
```java
// Add NPC to existing flock
FlockMembershipSystems.join(npcRef, flockRef, store);
```
## Flock Assets
### FlockAsset
Base configuration for flocks:
```java
public class FlockAsset {
// Asset identifier
private String id;
// Pick group size for spawning
public abstract int pickFlockSize();
}
```
### RangeSizeFlockAsset
Flock with random size in range:
```json
{
"Type": "RangeSize",
"Id": "wolf_pack",
"MinSize": 3,
"MaxSize": 8
}
```
### WeightedSizeFlockAsset
Flock with weighted random size:
```json
{
"Type": "WeightedSize",
"Id": "deer_herd",
"Sizes": [
{ "Size": 2, "Weight": 1 },
{ "Size": 4, "Weight": 2 },
{ "Size": 6, "Weight": 1 }
]
}
```
## NPC Core Components
### BodyMotionFlock
Controls flocking movement behavior:
```json
{
"Type": "Flock",
"SeparationWeight": 1.5,
"AlignmentWeight": 1.0,
"CohesionWeight": 1.0
}
```
### ActionFlockJoin
Join a flock:
```json
{
"Type": "JoinFlock",
"FlockId": "wolf_pack"
}
```
### ActionFlockLeave
Leave current flock:
```json
{
"Type": "LeaveFlock"
}
```
### SensorFlockLeader
Detect flock leader:
```json
{
"Type": "FlockLeader",
"Output": "leader_ref"
}
```
### EntityFilterFlock
Filter entities by flock membership:
```json
{
"Type": "Flock",
"IncludeSelf": false,
"OnlyMembers": true
}
```
## Damage Sharing
Flocks share combat information:
```java
// Get damage data for the flock
Flock flock = store.getComponent(flockRef, Flock.getComponentType());
DamageData damageData = flock.getDamageData();
// Track kills for the flock
flock.onTargetKilled(componentAccessor, targetRef);
```
### Double Buffering
Damage data uses double buffering to avoid race conditions:
```java
// Called each tick
flock.swapDamageDataBuffers();
// currentDamageData contains last tick's data
// nextDamageData accumulates current tick's data
```
## Conditions
### FlockSizeCondition
Check flock size in decision making:
```json
{
"Type": "FlockSize",
"Min": 2,
"Max": 10
}
```
## Plugin Access
```java
FlockPlugin flockPlugin = FlockPlugin.get();
// Component types
ComponentType<EntityStore, Flock> flockType =
flockPlugin.getFlockComponentType();
ComponentType<EntityStore, FlockMembership> membershipType =
flockPlugin.getFlockMembershipComponentType();
ComponentType<EntityStore, PersistentFlockData> dataType =
flockPlugin.getPersistentFlockDataComponentType();
```
## Utility Methods
```java
// Check if entity is in a flock
boolean isMember = FlockPlugin.isFlockMember(npcRef, store);
// Get flock reference from entity
Ref<EntityStore> flockRef = FlockPlugin.getFlockReference(npcRef, store);
// Get flock component from entity
Flock flock = FlockPlugin.getFlock(store, npcRef);
```
## Flock Spawning Behavior
When spawning a flock:
1. Initial NPC is created
2. Flock entity is created with membership
3. Additional members spawn at same location
4. Members spread with slight random offset
5. Each member joins the flock
```java
// Members spawn with random offset
memberTransform.getPosition().assign(
x + RandomExtra.randomRange(-0.5, 0.5),
offsetY,
z + RandomExtra.randomRange(-0.5, 0.5)
);
```

View File

@@ -0,0 +1,302 @@
---
title: Comportement de Groupe
type: docs
weight: 10
---
Le systeme de groupe (flock) permet aux NPCs de former et se comporter comme des unites coordonnees.
**Package:** `com.hypixel.hytale.server.flock`
## Apercu
Les groupes sont des ensembles bases sur des entites qui permettent aux NPCs de:
- Se deplacer et agir comme une unite coordonnee
- Partager les informations de degats entre membres
- Apparaitre en tailles de groupe configurables
- Avoir des leaders designes
## Architecture
```
Systeme Groupe
├── Composants
│ ├── Flock - Composant entite groupe
│ ├── FlockMembership - Reference membre
│ └── PersistentFlockData - Donnees groupe sauvegardees
├── Assets
│ ├── FlockAsset - Configuration groupe
│ ├── RangeSizeFlockAsset - Taille par plage
│ └── WeightedSizeFlockAsset - Taille ponderee
├── Systemes
│ ├── FlockSystems - Logique groupe principale
│ ├── FlockMembershipSystems - Gestion membres
│ └── FlockDeathSystems - Gestion mort
└── Composants NPC
├── BodyMotionFlock - Comportement mouvement
├── ActionFlockJoin/Leave - Actions joindre/quitter
└── SensorFlockLeader - Detection leader
```
## Composant Flock
L'entite groupe contient l'etat partage:
```java
public class Flock implements Component<EntityStore> {
// Donnees degats partagees pour coordination combat
private DamageData currentDamageData;
private DamageData nextDamageData;
// Donnees degats specifiques leader
private DamageData currentLeaderDamageData;
private DamageData nextLeaderDamageData;
// Configuration groupe persistante
private PersistentFlockData flockData;
// Statut de suppression
public enum FlockRemovedStatus {
NOT_REMOVED,
DISSOLVED,
UNLOADED
}
}
```
## Composant FlockMembership
Chaque NPC membre a un composant d'appartenance:
```java
public class FlockMembership implements Component<EntityStore> {
// Reference vers l'entite groupe
private Ref<EntityStore> flockRef;
}
```
## Creation de Groupes
### Spawn avec Groupe
```java
// Faire apparaitre un NPC avec son groupe
Ref<EntityStore> flockRef = FlockPlugin.trySpawnFlock(
npcRef, // Reference NPC initial
npc, // Composant NPC
store, // Store entite
roleIndex, // Index role NPC
position, // Position spawn
rotation, // Rotation spawn
flockDefinition, // Config FlockAsset (determine taille)
postSpawnCallback // Appele pour chaque membre spawne
);
```
### Creer Groupe Vide
```java
// Creer entite groupe pour gestion manuelle des membres
Ref<EntityStore> flockRef = FlockPlugin.createFlock(store, role);
// Ou avec configuration explicite
Ref<EntityStore> flockRef = FlockPlugin.createFlock(
store,
flockAsset, // Configuration FlockAsset
allowedRoles // Roles pouvant rejoindre
);
```
### Rejoindre Groupe Existant
```java
// Ajouter NPC a groupe existant
FlockMembershipSystems.join(npcRef, flockRef, store);
```
## Assets de Groupe
### FlockAsset
Configuration de base pour groupes:
```java
public class FlockAsset {
// Identifiant asset
private String id;
// Choisir taille groupe pour spawn
public abstract int pickFlockSize();
}
```
### RangeSizeFlockAsset
Groupe avec taille aleatoire dans plage:
```json
{
"Type": "RangeSize",
"Id": "wolf_pack",
"MinSize": 3,
"MaxSize": 8
}
```
### WeightedSizeFlockAsset
Groupe avec taille aleatoire ponderee:
```json
{
"Type": "WeightedSize",
"Id": "deer_herd",
"Sizes": [
{ "Size": 2, "Weight": 1 },
{ "Size": 4, "Weight": 2 },
{ "Size": 6, "Weight": 1 }
]
}
```
## Composants Core NPC
### BodyMotionFlock
Controle comportement mouvement de groupe:
```json
{
"Type": "Flock",
"SeparationWeight": 1.5,
"AlignmentWeight": 1.0,
"CohesionWeight": 1.0
}
```
### ActionFlockJoin
Rejoindre un groupe:
```json
{
"Type": "JoinFlock",
"FlockId": "wolf_pack"
}
```
### ActionFlockLeave
Quitter groupe actuel:
```json
{
"Type": "LeaveFlock"
}
```
### SensorFlockLeader
Detecter leader du groupe:
```json
{
"Type": "FlockLeader",
"Output": "leader_ref"
}
```
### EntityFilterFlock
Filtrer entites par appartenance groupe:
```json
{
"Type": "Flock",
"IncludeSelf": false,
"OnlyMembers": true
}
```
## Partage de Degats
Les groupes partagent informations de combat:
```java
// Obtenir donnees degats du groupe
Flock flock = store.getComponent(flockRef, Flock.getComponentType());
DamageData damageData = flock.getDamageData();
// Suivre kills pour le groupe
flock.onTargetKilled(componentAccessor, targetRef);
```
### Double Buffering
Les donnees de degats utilisent double buffering pour eviter conditions de course:
```java
// Appele chaque tick
flock.swapDamageDataBuffers();
// currentDamageData contient donnees du tick precedent
// nextDamageData accumule donnees du tick actuel
```
## Conditions
### FlockSizeCondition
Verifier taille groupe dans prise de decision:
```json
{
"Type": "FlockSize",
"Min": 2,
"Max": 10
}
```
## Acces au Plugin
```java
FlockPlugin flockPlugin = FlockPlugin.get();
// Types de composants
ComponentType<EntityStore, Flock> flockType =
flockPlugin.getFlockComponentType();
ComponentType<EntityStore, FlockMembership> membershipType =
flockPlugin.getFlockMembershipComponentType();
ComponentType<EntityStore, PersistentFlockData> dataType =
flockPlugin.getPersistentFlockDataComponentType();
```
## Methodes Utilitaires
```java
// Verifier si entite dans un groupe
boolean isMember = FlockPlugin.isFlockMember(npcRef, store);
// Obtenir reference groupe depuis entite
Ref<EntityStore> flockRef = FlockPlugin.getFlockReference(npcRef, store);
// Obtenir composant groupe depuis entite
Flock flock = FlockPlugin.getFlock(store, npcRef);
```
## Comportement de Spawn des Groupes
Lors du spawn d'un groupe:
1. NPC initial est cree
2. Entite groupe est creee avec appartenance
3. Membres additionnels spawn au meme emplacement
4. Membres se dispersent avec leger offset aleatoire
5. Chaque membre rejoint le groupe
```java
// Membres spawn avec offset aleatoire
memberTransform.getPosition().assign(
x + RandomExtra.randomRange(-0.5, 0.5),
offsetY,
z + RandomExtra.randomRange(-0.5, 0.5)
);
```

View File

@@ -0,0 +1,57 @@
---
title: Inventory
type: docs
weight: 7
---
The inventory system in Hytale handles items, containers, and player inventories.
{{< cards >}}
{{< card link="itemstacks" title="ItemStacks" subtitle="Working with immutable item stacks" >}}
{{< card link="containers" title="Containers" subtitle="Inventory and container types" >}}
{{< card link="transactions" title="Transactions" subtitle="Safe item modifications" >}}
{{< /cards >}}
## Overview
The inventory system follows an immutable pattern:
- **ItemStack** - Immutable representation of items
- **Inventory** - Player's personal inventory
- **ItemContainer** - Generic container for items
- **Transactions** - Safe way to modify items
## Quick Start
```java
import com.hypixel.hytale.server.core.inventory.Inventory;
import com.hypixel.hytale.server.core.inventory.ItemStack;
import java.util.logging.Level;
// Get player inventory
Player player = ...;
Inventory inv = player.getInventory();
// Get item in hand (via Inventory, not directly on Player)
ItemStack hand = inv.getItemInHand();
// Check if item exists
if (hand != null && !hand.isEmpty()) {
// Use getItemId() - Item.getName() doesn't exist
getLogger().at(Level.INFO).log("Holding: " + hand.getItemId());
}
```
## Key Concepts
{{< callout type="info" >}}
ItemStack is **immutable**. Methods like `withQuantity()` return a new ItemStack rather than modifying the original.
{{< /callout >}}
```java
// Immutable pattern
ItemStack original = new ItemStack("iron_sword", 1);
ItemStack modified = original.withQuantity(5); // New instance
// original still has quantity of 1
// modified has quantity of 5
```

View File

@@ -0,0 +1,57 @@
---
title: Inventaire
type: docs
weight: 7
---
Le système d'inventaire dans Hytale gère les objets, conteneurs et inventaires des joueurs.
{{< cards >}}
{{< card link="itemstacks" title="ItemStacks" subtitle="Travailler avec les piles d'objets immuables" >}}
{{< card link="containers" title="Conteneurs" subtitle="Types d'inventaires et conteneurs" >}}
{{< card link="transactions" title="Transactions" subtitle="Modifications sûres des objets" >}}
{{< /cards >}}
## Aperçu
Le système d'inventaire suit un pattern immuable :
- **ItemStack** - Représentation immuable des objets
- **Inventory** - Inventaire personnel du joueur
- **ItemContainer** - Conteneur générique pour les objets
- **Transactions** - Moyen sûr de modifier les objets
## Démarrage Rapide
```java
import com.hypixel.hytale.server.core.inventory.Inventory;
import com.hypixel.hytale.server.core.inventory.ItemStack;
import java.util.logging.Level;
// Obtenir l'inventaire du joueur
Player player = ...;
Inventory inv = player.getInventory();
// Obtenir l'objet en main (via Inventory, pas directement sur Player)
ItemStack hand = inv.getItemInHand();
// Vérifier si l'objet existe
if (hand != null && !hand.isEmpty()) {
// Utiliser getItemId() - Item.getName() n'existe pas
getLogger().at(Level.INFO).log("Tient : " + hand.getItemId());
}
```
## Concepts Clés
{{< callout type="info" >}}
ItemStack est **immuable**. Les méthodes comme `withQuantity()` retournent un nouveau ItemStack plutôt que de modifier l'original.
{{< /callout >}}
```java
// Pattern immuable
ItemStack original = new ItemStack("iron_sword", 1);
ItemStack modified = original.withQuantity(5); // Nouvelle instance
// original a toujours une quantité de 1
// modified a une quantité de 5
```

View File

@@ -0,0 +1,668 @@
---
title: Containers
type: docs
weight: 2
---
Containers hold and manage collections of ItemStacks in Hytale. The inventory system uses `ItemContainer` as the base abstraction with `Inventory` as the player's specialized multi-section container.
## Inventory Structure
Player inventories are composed of multiple `ItemContainer` sections:
```java
Inventory inv = player.getInventory();
// Get individual sections
ItemContainer storage = inv.getStorage(); // Main storage (36 slots default)
ItemContainer hotbar = inv.getHotbar(); // Hotbar (9 slots default)
ItemContainer armor = inv.getArmor(); // Armor slots
ItemContainer utility = inv.getUtility(); // Utility items (4 slots default)
ItemContainer backpack = inv.getBackpack(); // Backpack (expandable)
// Combined containers for operations across multiple sections
CombinedItemContainer hotbarFirst = inv.getCombinedHotbarFirst();
CombinedItemContainer storageFirst = inv.getCombinedStorageFirst();
```
### Section IDs
Each section has a constant ID for operations:
| Section | ID | Constant |
|---------|----|----|
| Hotbar | -1 | `Inventory.HOTBAR_SECTION_ID` |
| Storage | -2 | `Inventory.STORAGE_SECTION_ID` |
| Armor | -3 | `Inventory.ARMOR_SECTION_ID` |
| Utility | -5 | `Inventory.UTILITY_SECTION_ID` |
| Tools | -8 | `Inventory.TOOLS_SECTION_ID` (deprecated) |
| Backpack | -9 | `Inventory.BACKPACK_SECTION_ID` |
## ItemContainer Basics
### Reading Items
```java
ItemContainer container = inv.getStorage();
// Get capacity (NOT getSize!)
short capacity = container.getCapacity();
// Get item at slot (slot is SHORT type)
ItemStack item = container.getItemStack((short) 0);
// Check if empty
boolean empty = container.isEmpty();
// Safe item access
ItemStack stack = container.getItemStack((short) slot);
if (!ItemStack.isEmpty(stack)) {
String itemId = stack.getItemId();
int quantity = stack.getQuantity();
}
```
### Setting Items
```java
ItemContainer container = inv.getHotbar();
// Set item at slot - returns a Transaction
ItemStackSlotTransaction transaction = container.setItemStackForSlot(
(short) 0,
new ItemStack("iron_sword", 1)
);
// Check if operation succeeded
if (transaction.succeeded()) {
// Item was set successfully
}
// Clear a slot
container.setItemStackForSlot((short) 0, null);
```
### Adding Items
```java
ItemContainer container = inv.getStorage();
ItemStack toAdd = new ItemStack("wood_plank", 64);
// Add to first available slot (stacks with existing items first)
ItemStackTransaction transaction = container.addItemStack(toAdd);
// Check for remaining items that couldn't fit
ItemStack remainder = transaction.getRemainder();
if (!ItemStack.isEmpty(remainder)) {
// Some items couldn't fit
int leftover = remainder.getQuantity();
}
// Add to specific slot
ItemStackSlotTransaction slotTransaction = container.addItemStackToSlot(
(short) 5,
toAdd
);
// Check if container can fit items before adding
if (container.canAddItemStack(toAdd)) {
container.addItemStack(toAdd);
}
```
### Removing Items
```java
// Remove from specific slot
SlotTransaction transaction = container.removeItemStackFromSlot((short) 0);
// Remove specific quantity from slot
ItemStackSlotTransaction removeTransaction = container.removeItemStackFromSlot(
(short) 0,
10 // quantity to remove
);
// Remove specific item type from anywhere in container
ItemStack toRemove = new ItemStack("iron_ore", 5);
ItemStackTransaction itemTransaction = container.removeItemStack(toRemove);
// Check if items can be removed before removing
if (container.canRemoveItemStack(toRemove)) {
container.removeItemStack(toRemove);
}
```
## Active Slots
Players have active slots for hotbar and utility items:
```java
Inventory inv = player.getInventory();
// Get active hotbar slot (0-8)
byte activeHotbar = inv.getActiveHotbarSlot();
// Set active hotbar slot
inv.setActiveHotbarSlot((byte) 3);
// Get item currently in hand
ItemStack handItem = inv.getItemInHand();
// Get/set active utility slot
byte activeUtility = inv.getActiveUtilitySlot();
inv.setActiveUtilitySlot((byte) 1);
// Get utility item
ItemStack utilityItem = inv.getUtilityItem();
```
## Moving Items
### Between Slots
```java
Inventory inv = player.getInventory();
ItemContainer storage = inv.getStorage();
ItemContainer hotbar = inv.getHotbar();
// Move item from one slot to another in same container
MoveTransaction<SlotTransaction> transaction = storage.moveItemStackFromSlotToSlot(
(short) 0, // from slot
64, // quantity
storage, // to container
(short) 5 // to slot
);
// Move between different containers
storage.moveItemStackFromSlotToSlot(
(short) 0,
32,
hotbar,
(short) 0
);
// Use Inventory's moveItem for section-based moves
inv.moveItem(
Inventory.STORAGE_SECTION_ID, // from section
5, // from slot
10, // quantity
Inventory.HOTBAR_SECTION_ID, // to section
0 // to slot
);
```
### Smart Moving
```java
// Smart move considers item type for optimal placement
inv.smartMoveItem(
Inventory.STORAGE_SECTION_ID,
0, // slot
64, // quantity
SmartMoveType.EquipOrMergeStack // tries to equip armor or merge stacks
);
```
## Iterating Containers
```java
ItemContainer container = inv.getStorage();
// Iterate all non-empty slots
container.forEach((slot, itemStack) -> {
getLogger().at(Level.INFO).log("Slot " + slot + ": " +
itemStack.getItemId() + " x" + itemStack.getQuantity());
});
// Count items matching a condition
int swordCount = container.countItemStacks(
stack -> stack.getItemId().contains("sword")
);
// Check if container has stackable items
ItemStack testStack = new ItemStack("stone", 1);
boolean hasStackable = container.containsItemStacksStackableWith(testStack);
```
## Checking Contents
```java
ItemContainer container = inv.getStorage();
// Check if inventory contains specific item type and quantity
public boolean hasItems(ItemContainer container, String itemId, int amount) {
int count = container.countItemStacks(
stack -> stack.getItemId().equals(itemId)
);
return count >= amount;
}
// Find first slot with specific item
public short findSlotWithItem(ItemContainer container, String itemId) {
for (short i = 0; i < container.getCapacity(); i++) {
ItemStack stack = container.getItemStack(i);
if (!ItemStack.isEmpty(stack) && stack.getItemId().equals(itemId)) {
return i;
}
}
return -1; // Not found
}
// Count empty slots
public int countEmptySlots(ItemContainer container) {
int empty = 0;
for (short i = 0; i < container.getCapacity(); i++) {
if (ItemStack.isEmpty(container.getItemStack(i))) {
empty++;
}
}
return empty;
}
```
## Clearing Containers
```java
ItemContainer container = inv.getStorage();
// Clear entire container
ClearTransaction transaction = container.clear();
// Drop all items (returns list of dropped items)
List<ItemStack> droppedItems = container.dropAllItemStacks();
// Remove all items (returns list)
List<ItemStack> removedItems = container.removeAllItemStacks();
// Clear entire player inventory
inv.clear();
// Drop all from player inventory
List<ItemStack> allDropped = inv.dropAllItemStacks();
```
## Sorting
Available `SortType` values: `NAME`, `TYPE`, `RARITY`
```java
ItemContainer container = inv.getStorage();
// Sort items
container.sortItems(SortType.NAME);
// Sort via Inventory (also saves sort preference)
inv.sortStorage(SortType.NAME);
inv.setSortType(SortType.RARITY); // Or TYPE
```
## Container Events
```java
// Register for container change events
container.registerChangeEvent(event -> {
ItemContainer changedContainer = event.container();
Transaction transaction = event.transaction();
getLogger().at(Level.INFO).log("Container changed!");
});
// With priority
container.registerChangeEvent(EventPriority.EARLY, event -> {
// Handle early
});
```
## Transaction System
All modification operations return Transaction objects:
```java
// Transactions track success and changes
ItemStackTransaction transaction = container.addItemStack(itemStack);
if (transaction.succeeded()) {
ItemStack remainder = transaction.getRemainder();
// ...
}
// Slot transactions include slot info
ItemStackSlotTransaction slotTransaction = container.setItemStackForSlot(
(short) 0,
itemStack
);
if (slotTransaction.succeeded()) {
short slot = slotTransaction.getSlot();
ItemStack before = slotTransaction.getSlotBefore();
ItemStack after = slotTransaction.getSlotAfter();
}
```
## Operation Parameters
Most container operations support optional parameters:
```java
// Default values
DEFAULT_ADD_ALL_OR_NOTHING = false; // Partial adds allowed
DEFAULT_REMOVE_ALL_OR_NOTHING = true; // Only full removals
DEFAULT_FULL_STACKS = false; // Can split stacks
DEFAULT_EXACT_AMOUNT = true; // Exact quantities only
DEFAULT_FILTER = true; // Apply slot filters
// Add with parameters
container.addItemStack(itemStack, allOrNothing, fullStacks, filter);
// allOrNothing: if true, fails entirely if not all items fit
// fullStacks: if true, only fills empty slots (no stacking)
// filter: if true, respects slot filters
```
## Material and Resource Removal
Containers support removing items by material type or resource type (used for crafting):
{{< callout type="info" >}}
**MaterialQuantity constructor:** `MaterialQuantity(itemId, resourceTypeId, tag, quantity, metadata)`
At least one of `itemId`, `resourceTypeId`, or `tag` must be non-null.
{{< /callout >}}
```java
// Remove by material using itemId
MaterialQuantity materialByItem = new MaterialQuantity("iron_ingot", null, null, 5, null);
if (container.canRemoveMaterial(materialByItem)) {
MaterialTransaction transaction = container.removeMaterial(materialByItem);
}
// Remove by material using resourceTypeId
MaterialQuantity materialByResource = new MaterialQuantity(null, "iron", null, 5, null);
if (container.canRemoveMaterial(materialByResource)) {
MaterialTransaction transaction = container.removeMaterial(materialByResource);
}
// Remove by resource type (simpler constructor)
ResourceQuantity resource = new ResourceQuantity("wood", 10);
if (container.canRemoveResource(resource)) {
ResourceTransaction transaction = container.removeResource(resource);
}
// Remove by tag index
if (container.canRemoveTag(tagIndex, quantity)) {
TagTransaction transaction = container.removeTag(tagIndex, quantity);
}
// Bulk removal
List<MaterialQuantity> materials = List.of(
new MaterialQuantity("iron_ingot", null, null, 2, null),
new MaterialQuantity(null, null, "Wood", 5, null) // Using tag
);
if (container.canRemoveMaterials(materials)) {
container.removeMaterials(materials);
}
```
## Bulk Operations
Add or remove multiple items at once:
```java
// Add multiple items
List<ItemStack> items = List.of(
new ItemStack("iron_ore", 10),
new ItemStack("gold_ore", 5)
);
if (container.canAddItemStacks(items)) {
ListTransaction<ItemStackTransaction> transaction = container.addItemStacks(items);
}
// Remove multiple items
if (container.canRemoveItemStacks(items)) {
container.removeItemStacks(items);
}
// Add items in order (preserves slot positions)
container.addItemStacksOrdered(items);
container.addItemStacksOrdered((short) 5, items); // Starting at slot 5
```
## Advanced Move Operations
```java
// Move all items to another container
ListTransaction<MoveTransaction<ItemStackTransaction>> result =
storage.moveAllItemStacksTo(hotbar, backpack);
// Move all items matching a condition
storage.moveAllItemStacksTo(
item -> item.getItemId().contains("ore"),
hotbar
);
// Quick stack: only moves items that can stack with existing items
storage.quickStackTo(hotbar);
// Combine small stacks into one slot
container.combineItemStacksIntoSlot(targetContainer, (short) 0);
// Swap items between containers
storage.swapItems(
(short) 0, // source position
hotbar, // target container
(short) 0, // destination position
(short) 5 // number of slots to swap
);
```
## Replace Operations
```java
// Replace item in slot if it matches expected
ItemStackSlotTransaction transaction = container.replaceItemStackInSlot(
(short) 0,
expectedItem, // must match current item to proceed
newItem
);
// Replace all items using a function
container.replaceAll((slot, existing) -> {
if (existing.getItemId().equals("old_item")) {
return new ItemStack("new_item", existing.getQuantity());
}
return existing;
});
```
## Slot Filters
Control which items can go in which slots:
```java
// Set global filter for the container
container.setGlobalFilter(FilterType.WHITELIST);
// Set filter for specific slot
container.setSlotFilter(
FilterActionType.ADD, // Filter on add operations
(short) 0, // Slot
slotFilter // Filter implementation
);
```
## Best Practices
{{< callout type="info" >}}
**Container Tips:**
- Slot indices are `short`, not `int` - cast appropriately
- Always check `ItemStack.isEmpty(stack)` - handles both null and empty
- Use `getQuantity()` not `getCount()`
- Check transaction success with `succeeded()`
- Use combined containers for cross-section operations
- Player inventory changes trigger `LivingEntityInventoryChangeEvent`
{{< /callout >}}
```java
// Safe pattern for working with containers
public void safeAddItem(ItemContainer container, ItemStack item) {
if (ItemStack.isEmpty(item)) {
return;
}
if (!container.canAddItemStack(item)) {
// Handle full container
return;
}
ItemStackTransaction transaction = container.addItemStack(item);
if (!transaction.succeeded()) {
// Handle failure
}
}
```
## ItemContainer API Reference
### Core Methods
| Method | Returns | Description |
|--------|---------|-------------|
| `getCapacity()` | `short` | Total number of slots |
| `getItemStack(short)` | `ItemStack?` | Get item at slot |
| `isEmpty()` | `boolean` | True if no items |
| `clone()` | `ItemContainer` | Clone the container |
| `clear()` | `ClearTransaction` | Remove all items |
### Add Operations
| Method | Returns | Description |
|--------|---------|-------------|
| `canAddItemStack(ItemStack)` | `boolean` | Check if can add |
| `canAddItemStack(ItemStack, fullStacks, filter)` | `boolean` | Check with options |
| `addItemStack(ItemStack)` | `ItemStackTransaction` | Add to first available |
| `addItemStack(ItemStack, allOrNothing, fullStacks, filter)` | `ItemStackTransaction` | Add with options |
| `canAddItemStackToSlot(short, ItemStack, allOrNothing, filter)` | `boolean` | Check if can add to slot |
| `addItemStackToSlot(short, ItemStack)` | `ItemStackSlotTransaction` | Add to specific slot |
| `addItemStackToSlot(short, ItemStack, allOrNothing, filter)` | `ItemStackSlotTransaction` | Add to slot with options |
| `canAddItemStacks(List<ItemStack>)` | `boolean` | Check if can add multiple |
| `addItemStacks(List<ItemStack>)` | `ListTransaction` | Add multiple items |
| `addItemStacksOrdered(List<ItemStack>)` | `ListTransaction` | Add in order |
| `addItemStacksOrdered(short offset, List<ItemStack>)` | `ListTransaction` | Add in order from offset |
### Set Operations
| Method | Returns | Description |
|--------|---------|-------------|
| `setItemStackForSlot(short, ItemStack)` | `ItemStackSlotTransaction` | Set item at slot |
| `setItemStackForSlot(short, ItemStack, filter)` | `ItemStackSlotTransaction` | Set with filter option |
| `replaceItemStackInSlot(short, ItemStack expected, ItemStack new)` | `ItemStackSlotTransaction` | Replace if matches |
| `replaceAll(SlotReplacementFunction)` | `ListTransaction` | Replace all items |
### Remove Operations
| Method | Returns | Description |
|--------|---------|-------------|
| `removeItemStackFromSlot(short)` | `SlotTransaction` | Remove entire slot |
| `removeItemStackFromSlot(short, filter)` | `SlotTransaction` | Remove with filter |
| `removeItemStackFromSlot(short, quantity)` | `ItemStackSlotTransaction` | Remove quantity |
| `removeItemStackFromSlot(short, ItemStack, quantity)` | `ItemStackSlotTransaction` | Remove matching item |
| `canRemoveItemStack(ItemStack)` | `boolean` | Check if can remove |
| `removeItemStack(ItemStack)` | `ItemStackTransaction` | Remove item type |
| `canRemoveItemStacks(List<ItemStack>)` | `boolean` | Check if can remove multiple |
| `removeItemStacks(List<ItemStack>)` | `ListTransaction` | Remove multiple items |
| `removeAllItemStacks()` | `List<ItemStack>` | Remove and return all |
| `dropAllItemStacks()` | `List<ItemStack>` | Drop all (respects cantDrop) |
| `dropAllItemStacks(filter)` | `List<ItemStack>` | Drop with filter option |
### Material/Resource/Tag Removal
| Method | Returns | Description |
|--------|---------|-------------|
| `canRemoveMaterial(MaterialQuantity)` | `boolean` | Check material removal |
| `removeMaterial(MaterialQuantity)` | `MaterialTransaction` | Remove by material |
| `removeMaterialFromSlot(short, MaterialQuantity)` | `MaterialSlotTransaction` | Remove material from slot |
| `canRemoveMaterials(List<MaterialQuantity>)` | `boolean` | Check multiple materials |
| `removeMaterials(List<MaterialQuantity>)` | `ListTransaction` | Remove multiple materials |
| `canRemoveResource(ResourceQuantity)` | `boolean` | Check resource removal |
| `removeResource(ResourceQuantity)` | `ResourceTransaction` | Remove by resource |
| `removeResourceFromSlot(short, ResourceQuantity)` | `ResourceSlotTransaction` | Remove resource from slot |
| `canRemoveResources(List<ResourceQuantity>)` | `boolean` | Check multiple resources |
| `removeResources(List<ResourceQuantity>)` | `ListTransaction` | Remove multiple resources |
| `canRemoveTag(tagIndex, quantity)` | `boolean` | Check tag removal |
| `removeTag(tagIndex, quantity)` | `TagTransaction` | Remove by tag |
| `removeTagFromSlot(short, tagIndex, quantity)` | `TagSlotTransaction` | Remove tag from slot |
### Move Operations
| Method | Returns | Description |
|--------|---------|-------------|
| `moveItemStackFromSlot(short, ItemContainer)` | `MoveTransaction` | Move slot to container |
| `moveItemStackFromSlot(short, quantity, ItemContainer)` | `MoveTransaction` | Move quantity |
| `moveItemStackFromSlot(short, ItemContainer...)` | `ListTransaction` | Move to multiple containers |
| `moveItemStackFromSlotToSlot(short, quantity, ItemContainer, short)` | `MoveTransaction` | Move to specific slot |
| `moveAllItemStacksTo(ItemContainer...)` | `ListTransaction` | Move all items |
| `moveAllItemStacksTo(Predicate, ItemContainer...)` | `ListTransaction` | Move matching items |
| `quickStackTo(ItemContainer...)` | `ListTransaction` | Move stackable items only |
| `combineItemStacksIntoSlot(ItemContainer, short)` | `ListTransaction` | Combine stacks into slot |
| `swapItems(short srcPos, ItemContainer, short destPos, short length)` | `ListTransaction` | Swap item ranges |
### Utility Methods
| Method | Returns | Description |
|--------|---------|-------------|
| `forEach(ShortObjectConsumer)` | `void` | Iterate non-empty slots |
| `forEachWithMeta(consumer, meta)` | `void` | Iterate with metadata |
| `countItemStacks(Predicate)` | `int` | Count matching items (total quantity) |
| `containsItemStacksStackableWith(ItemStack)` | `boolean` | Check for stackable items |
| `sortItems(SortType)` | `ListTransaction` | Sort container |
| `registerChangeEvent(Consumer)` | `EventRegistration` | Listen for changes |
| `registerChangeEvent(EventPriority, Consumer)` | `EventRegistration` | Listen with priority |
| `setGlobalFilter(FilterType)` | `void` | Set container filter |
| `setSlotFilter(FilterActionType, short, SlotFilter)` | `void` | Set slot filter |
| `containsContainer(ItemContainer)` | `boolean` | Check if contains container |
### Static Methods
| Method | Returns | Description |
|--------|---------|-------------|
| `copy(from, to, remainder)` | `T` | Copy items between containers |
| `ensureContainerCapacity(container, capacity, supplier, remainder)` | `T` | Ensure container has capacity |
| `getNewContainer(capacity, supplier)` | `ItemContainer` | Create or get empty |
| `getMatchingResourceType(Item, resourceId)` | `ItemResourceType?` | Find resource type for item |
| `validateQuantity(int)` | `void` | Throws if < 0 |
| `validateSlotIndex(short, capacity)` | `void` | Throws if out of bounds |
### Static Constants
| Field | Type | Description |
|-------|------|-------------|
| `CODEC` | `CodecMapCodec<ItemContainer>` | Serialization codec |
| `DEFAULT_ADD_ALL_OR_NOTHING` | `boolean` | false |
| `DEFAULT_REMOVE_ALL_OR_NOTHING` | `boolean` | true |
| `DEFAULT_FULL_STACKS` | `boolean` | false |
| `DEFAULT_EXACT_AMOUNT` | `boolean` | true |
| `DEFAULT_FILTER` | `boolean` | true |
### Nested Classes
| Class | Description |
|-------|-------------|
| `ItemContainerChangeEvent` | Event record with `container()` and `transaction()` |
## Inventory API Reference
| Method | Returns | Description |
|--------|---------|-------------|
| `getStorage()` | `ItemContainer` | Main storage section |
| `getHotbar()` | `ItemContainer` | Hotbar section |
| `getArmor()` | `ItemContainer` | Armor section |
| `getUtility()` | `ItemContainer` | Utility section |
| `getBackpack()` | `ItemContainer` | Backpack section |
| `getSectionById(int)` | `ItemContainer?` | Get section by ID |
| `getItemInHand()` | `ItemStack?` | Currently held item |
| `getActiveHotbarSlot()` | `byte` | Active hotbar slot |
| `setActiveHotbarSlot(byte)` | `void` | Set active hotbar |
| `getCombinedHotbarFirst()` | `CombinedItemContainer` | Hotbar+Storage combined |
| `moveItem(...)` | `void` | Move between sections |
| `clear()` | `void` | Clear all sections |

View File

@@ -0,0 +1,668 @@
---
title: Conteneurs
type: docs
weight: 2
---
Les conteneurs gèrent des collections d'ItemStacks dans Hytale. Le système d'inventaire utilise `ItemContainer` comme abstraction de base avec `Inventory` comme conteneur multi-sections spécialisé du joueur.
## Structure de l'Inventaire
Les inventaires des joueurs sont composés de plusieurs sections `ItemContainer` :
```java
Inventory inv = player.getInventory();
// Obtenir les sections individuelles
ItemContainer storage = inv.getStorage(); // Stockage principal (36 slots par défaut)
ItemContainer hotbar = inv.getHotbar(); // Hotbar (9 slots par défaut)
ItemContainer armor = inv.getArmor(); // Emplacements d'armure
ItemContainer utility = inv.getUtility(); // Objets utilitaires (4 slots par défaut)
ItemContainer backpack = inv.getBackpack(); // Sac à dos (extensible)
// Conteneurs combinés pour opérations multi-sections
CombinedItemContainer hotbarFirst = inv.getCombinedHotbarFirst();
CombinedItemContainer storageFirst = inv.getCombinedStorageFirst();
```
### IDs de Section
Chaque section a un ID constant pour les opérations :
| Section | ID | Constante |
|---------|----|----|
| Hotbar | -1 | `Inventory.HOTBAR_SECTION_ID` |
| Stockage | -2 | `Inventory.STORAGE_SECTION_ID` |
| Armure | -3 | `Inventory.ARMOR_SECTION_ID` |
| Utilitaire | -5 | `Inventory.UTILITY_SECTION_ID` |
| Outils | -8 | `Inventory.TOOLS_SECTION_ID` (déprécié) |
| Sac à dos | -9 | `Inventory.BACKPACK_SECTION_ID` |
## Bases d'ItemContainer
### Lire les Objets
```java
ItemContainer container = inv.getStorage();
// Obtenir la capacité (PAS getSize!)
short capacity = container.getCapacity();
// Obtenir l'objet au slot (slot est de type SHORT)
ItemStack item = container.getItemStack((short) 0);
// Vérifier si vide
boolean empty = container.isEmpty();
// Accès sécurisé aux objets
ItemStack stack = container.getItemStack((short) slot);
if (!ItemStack.isEmpty(stack)) {
String itemId = stack.getItemId();
int quantity = stack.getQuantity();
}
```
### Définir des Objets
```java
ItemContainer container = inv.getHotbar();
// Définir l'objet au slot - retourne une Transaction
ItemStackSlotTransaction transaction = container.setItemStackForSlot(
(short) 0,
new ItemStack("iron_sword", 1)
);
// Vérifier si l'opération a réussi
if (transaction.succeeded()) {
// L'objet a été défini avec succès
}
// Vider un slot
container.setItemStackForSlot((short) 0, null);
```
### Ajouter des Objets
```java
ItemContainer container = inv.getStorage();
ItemStack toAdd = new ItemStack("wood_plank", 64);
// Ajouter au premier slot disponible (empile avec les objets existants d'abord)
ItemStackTransaction transaction = container.addItemStack(toAdd);
// Vérifier les objets restants qui n'ont pas pu rentrer
ItemStack remainder = transaction.getRemainder();
if (!ItemStack.isEmpty(remainder)) {
// Certains objets n'ont pas pu rentrer
int leftover = remainder.getQuantity();
}
// Ajouter à un slot spécifique
ItemStackSlotTransaction slotTransaction = container.addItemStackToSlot(
(short) 5,
toAdd
);
// Vérifier si le conteneur peut accueillir les objets avant d'ajouter
if (container.canAddItemStack(toAdd)) {
container.addItemStack(toAdd);
}
```
### Retirer des Objets
```java
// Retirer d'un slot spécifique
SlotTransaction transaction = container.removeItemStackFromSlot((short) 0);
// Retirer une quantité spécifique du slot
ItemStackSlotTransaction removeTransaction = container.removeItemStackFromSlot(
(short) 0,
10 // quantité à retirer
);
// Retirer un type d'objet spécifique de n'importe où dans le conteneur
ItemStack toRemove = new ItemStack("iron_ore", 5);
ItemStackTransaction itemTransaction = container.removeItemStack(toRemove);
// Vérifier si les objets peuvent être retirés avant de retirer
if (container.canRemoveItemStack(toRemove)) {
container.removeItemStack(toRemove);
}
```
## Slots Actifs
Les joueurs ont des slots actifs pour la hotbar et les objets utilitaires :
```java
Inventory inv = player.getInventory();
// Obtenir le slot actif de la hotbar (0-8)
byte activeHotbar = inv.getActiveHotbarSlot();
// Définir le slot actif de la hotbar
inv.setActiveHotbarSlot((byte) 3);
// Obtenir l'objet actuellement en main
ItemStack handItem = inv.getItemInHand();
// Obtenir/définir le slot utilitaire actif
byte activeUtility = inv.getActiveUtilitySlot();
inv.setActiveUtilitySlot((byte) 1);
// Obtenir l'objet utilitaire
ItemStack utilityItem = inv.getUtilityItem();
```
## Déplacer des Objets
### Entre les Slots
```java
Inventory inv = player.getInventory();
ItemContainer storage = inv.getStorage();
ItemContainer hotbar = inv.getHotbar();
// Déplacer un objet d'un slot à un autre dans le même conteneur
MoveTransaction<SlotTransaction> transaction = storage.moveItemStackFromSlotToSlot(
(short) 0, // depuis slot
64, // quantité
storage, // vers conteneur
(short) 5 // vers slot
);
// Déplacer entre différents conteneurs
storage.moveItemStackFromSlotToSlot(
(short) 0,
32,
hotbar,
(short) 0
);
// Utiliser moveItem de l'Inventory pour les déplacements par section
inv.moveItem(
Inventory.STORAGE_SECTION_ID, // depuis section
5, // depuis slot
10, // quantité
Inventory.HOTBAR_SECTION_ID, // vers section
0 // vers slot
);
```
### Déplacement Intelligent
```java
// Le déplacement intelligent considère le type d'objet pour un placement optimal
inv.smartMoveItem(
Inventory.STORAGE_SECTION_ID,
0, // slot
64, // quantité
SmartMoveType.EquipOrMergeStack // tente d'équiper l'armure ou fusionner les piles
);
```
## Itérer les Conteneurs
```java
ItemContainer container = inv.getStorage();
// Itérer tous les slots non vides
container.forEach((slot, itemStack) -> {
getLogger().at(Level.INFO).log("Slot " + slot + ": " +
itemStack.getItemId() + " x" + itemStack.getQuantity());
});
// Compter les objets correspondant à une condition
int swordCount = container.countItemStacks(
stack -> stack.getItemId().contains("sword")
);
// Vérifier si le conteneur a des objets empilables
ItemStack testStack = new ItemStack("stone", 1);
boolean hasStackable = container.containsItemStacksStackableWith(testStack);
```
## Vérifier le Contenu
```java
ItemContainer container = inv.getStorage();
// Vérifier si l'inventaire contient un type et quantité d'objet spécifiques
public boolean hasItems(ItemContainer container, String itemId, int amount) {
int count = container.countItemStacks(
stack -> stack.getItemId().equals(itemId)
);
return count >= amount;
}
// Trouver le premier slot avec un objet spécifique
public short findSlotWithItem(ItemContainer container, String itemId) {
for (short i = 0; i < container.getCapacity(); i++) {
ItemStack stack = container.getItemStack(i);
if (!ItemStack.isEmpty(stack) && stack.getItemId().equals(itemId)) {
return i;
}
}
return -1; // Non trouvé
}
// Compter les slots vides
public int countEmptySlots(ItemContainer container) {
int empty = 0;
for (short i = 0; i < container.getCapacity(); i++) {
if (ItemStack.isEmpty(container.getItemStack(i))) {
empty++;
}
}
return empty;
}
```
## Vider les Conteneurs
```java
ItemContainer container = inv.getStorage();
// Vider tout le conteneur
ClearTransaction transaction = container.clear();
// Lâcher tous les objets (retourne la liste des objets lâchés)
List<ItemStack> droppedItems = container.dropAllItemStacks();
// Retirer tous les objets (retourne la liste)
List<ItemStack> removedItems = container.removeAllItemStacks();
// Vider tout l'inventaire du joueur
inv.clear();
// Lâcher tout de l'inventaire du joueur
List<ItemStack> allDropped = inv.dropAllItemStacks();
```
## Tri
Valeurs `SortType` disponibles : `NAME`, `TYPE`, `RARITY`
```java
ItemContainer container = inv.getStorage();
// Trier les objets
container.sortItems(SortType.NAME);
// Trier via Inventory (sauvegarde aussi la préférence de tri)
inv.sortStorage(SortType.NAME);
inv.setSortType(SortType.RARITY); // Ou TYPE
```
## Événements de Conteneur
```java
// S'enregistrer pour les événements de changement de conteneur
container.registerChangeEvent(event -> {
ItemContainer changedContainer = event.container();
Transaction transaction = event.transaction();
getLogger().at(Level.INFO).log("Conteneur modifié !");
});
// Avec priorité
container.registerChangeEvent(EventPriority.EARLY, event -> {
// Gérer tôt
});
```
## Système de Transactions
Toutes les opérations de modification retournent des objets Transaction :
```java
// Les transactions suivent le succès et les changements
ItemStackTransaction transaction = container.addItemStack(itemStack);
if (transaction.succeeded()) {
ItemStack remainder = transaction.getRemainder();
// ...
}
// Les transactions de slot incluent les infos du slot
ItemStackSlotTransaction slotTransaction = container.setItemStackForSlot(
(short) 0,
itemStack
);
if (slotTransaction.succeeded()) {
short slot = slotTransaction.getSlot();
ItemStack before = slotTransaction.getSlotBefore();
ItemStack after = slotTransaction.getSlotAfter();
}
```
## Paramètres d'Opération
La plupart des opérations de conteneur supportent des paramètres optionnels :
```java
// Valeurs par défaut
DEFAULT_ADD_ALL_OR_NOTHING = false; // Ajouts partiels autorisés
DEFAULT_REMOVE_ALL_OR_NOTHING = true; // Uniquement retraits complets
DEFAULT_FULL_STACKS = false; // Peut diviser les piles
DEFAULT_EXACT_AMOUNT = true; // Quantités exactes uniquement
DEFAULT_FILTER = true; // Applique les filtres de slot
// Ajouter avec paramètres
container.addItemStack(itemStack, allOrNothing, fullStacks, filter);
// allOrNothing: si true, échoue entièrement si tous les objets ne rentrent pas
// fullStacks: si true, remplit uniquement les slots vides (pas d'empilement)
// filter: si true, respecte les filtres de slot
```
## Retrait par Matériau et Ressource
Les conteneurs supportent le retrait d'objets par type de matériau ou de ressource (utilisé pour le craft) :
{{< callout type="info" >}}
**Constructeur MaterialQuantity :** `MaterialQuantity(itemId, resourceTypeId, tag, quantity, metadata)`
Au moins un parmi `itemId`, `resourceTypeId`, ou `tag` doit être non-null.
{{< /callout >}}
```java
// Retirer par matériau en utilisant itemId
MaterialQuantity materialByItem = new MaterialQuantity("iron_ingot", null, null, 5, null);
if (container.canRemoveMaterial(materialByItem)) {
MaterialTransaction transaction = container.removeMaterial(materialByItem);
}
// Retirer par matériau en utilisant resourceTypeId
MaterialQuantity materialByResource = new MaterialQuantity(null, "iron", null, 5, null);
if (container.canRemoveMaterial(materialByResource)) {
MaterialTransaction transaction = container.removeMaterial(materialByResource);
}
// Retirer par type de ressource (constructeur plus simple)
ResourceQuantity resource = new ResourceQuantity("wood", 10);
if (container.canRemoveResource(resource)) {
ResourceTransaction transaction = container.removeResource(resource);
}
// Retirer par index de tag
if (container.canRemoveTag(tagIndex, quantity)) {
TagTransaction transaction = container.removeTag(tagIndex, quantity);
}
// Retrait en masse
List<MaterialQuantity> materials = List.of(
new MaterialQuantity("iron_ingot", null, null, 2, null),
new MaterialQuantity(null, null, "Wood", 5, null) // Avec tag
);
if (container.canRemoveMaterials(materials)) {
container.removeMaterials(materials);
}
```
## Opérations en Masse
Ajouter ou retirer plusieurs objets à la fois :
```java
// Ajouter plusieurs objets
List<ItemStack> items = List.of(
new ItemStack("iron_ore", 10),
new ItemStack("gold_ore", 5)
);
if (container.canAddItemStacks(items)) {
ListTransaction<ItemStackTransaction> transaction = container.addItemStacks(items);
}
// Retirer plusieurs objets
if (container.canRemoveItemStacks(items)) {
container.removeItemStacks(items);
}
// Ajouter les objets dans l'ordre (préserve les positions de slot)
container.addItemStacksOrdered(items);
container.addItemStacksOrdered((short) 5, items); // À partir du slot 5
```
## Opérations de Déplacement Avancées
```java
// Déplacer tous les objets vers un autre conteneur
ListTransaction<MoveTransaction<ItemStackTransaction>> result =
storage.moveAllItemStacksTo(hotbar, backpack);
// Déplacer tous les objets correspondant à une condition
storage.moveAllItemStacksTo(
item -> item.getItemId().contains("ore"),
hotbar
);
// Quick stack : déplace uniquement les objets qui peuvent s'empiler avec des objets existants
storage.quickStackTo(hotbar);
// Combiner les petites piles dans un slot
container.combineItemStacksIntoSlot(targetContainer, (short) 0);
// Échanger des objets entre conteneurs
storage.swapItems(
(short) 0, // position source
hotbar, // conteneur cible
(short) 0, // position destination
(short) 5 // nombre de slots à échanger
);
```
## Opérations de Remplacement
```java
// Remplacer l'objet dans le slot s'il correspond à l'attendu
ItemStackSlotTransaction transaction = container.replaceItemStackInSlot(
(short) 0,
expectedItem, // doit correspondre à l'objet actuel pour continuer
newItem
);
// Remplacer tous les objets avec une fonction
container.replaceAll((slot, existing) -> {
if (existing.getItemId().equals("old_item")) {
return new ItemStack("new_item", existing.getQuantity());
}
return existing;
});
```
## Filtres de Slot
Contrôler quels objets peuvent aller dans quels slots :
```java
// Définir un filtre global pour le conteneur
container.setGlobalFilter(FilterType.WHITELIST);
// Définir un filtre pour un slot spécifique
container.setSlotFilter(
FilterActionType.ADD, // Filtre sur les opérations d'ajout
(short) 0, // Slot
slotFilter // Implémentation du filtre
);
```
## Bonnes Pratiques
{{< callout type="info" >}}
**Conseils pour les Conteneurs :**
- Les indices de slot sont `short`, pas `int` - castez correctement
- Vérifiez toujours `ItemStack.isEmpty(stack)` - gère null et vide
- Utilisez `getQuantity()` pas `getCount()`
- Vérifiez le succès de la transaction avec `succeeded()`
- Utilisez les conteneurs combinés pour les opérations multi-sections
- Les changements d'inventaire du joueur déclenchent `LivingEntityInventoryChangeEvent`
{{< /callout >}}
```java
// Pattern sécurisé pour travailler avec les conteneurs
public void safeAddItem(ItemContainer container, ItemStack item) {
if (ItemStack.isEmpty(item)) {
return;
}
if (!container.canAddItemStack(item)) {
// Gérer le conteneur plein
return;
}
ItemStackTransaction transaction = container.addItemStack(item);
if (!transaction.succeeded()) {
// Gérer l'échec
}
}
```
## Référence API ItemContainer
### Méthodes Principales
| Méthode | Retour | Description |
|--------|---------|-------------|
| `getCapacity()` | `short` | Nombre total de slots |
| `getItemStack(short)` | `ItemStack?` | Obtenir l'objet au slot |
| `isEmpty()` | `boolean` | True si aucun objet |
| `clone()` | `ItemContainer` | Cloner le conteneur |
| `clear()` | `ClearTransaction` | Retirer tous les objets |
### Opérations d'Ajout
| Méthode | Retour | Description |
|--------|---------|-------------|
| `canAddItemStack(ItemStack)` | `boolean` | Vérifier si peut ajouter |
| `canAddItemStack(ItemStack, fullStacks, filter)` | `boolean` | Vérifier avec options |
| `addItemStack(ItemStack)` | `ItemStackTransaction` | Ajouter au premier disponible |
| `addItemStack(ItemStack, allOrNothing, fullStacks, filter)` | `ItemStackTransaction` | Ajouter avec options |
| `canAddItemStackToSlot(short, ItemStack, allOrNothing, filter)` | `boolean` | Vérifier si peut ajouter au slot |
| `addItemStackToSlot(short, ItemStack)` | `ItemStackSlotTransaction` | Ajouter à un slot spécifique |
| `addItemStackToSlot(short, ItemStack, allOrNothing, filter)` | `ItemStackSlotTransaction` | Ajouter au slot avec options |
| `canAddItemStacks(List<ItemStack>)` | `boolean` | Vérifier si peut ajouter plusieurs |
| `addItemStacks(List<ItemStack>)` | `ListTransaction` | Ajouter plusieurs objets |
| `addItemStacksOrdered(List<ItemStack>)` | `ListTransaction` | Ajouter dans l'ordre |
| `addItemStacksOrdered(short offset, List<ItemStack>)` | `ListTransaction` | Ajouter dans l'ordre depuis offset |
### Opérations de Définition
| Méthode | Retour | Description |
|--------|---------|-------------|
| `setItemStackForSlot(short, ItemStack)` | `ItemStackSlotTransaction` | Définir l'objet au slot |
| `setItemStackForSlot(short, ItemStack, filter)` | `ItemStackSlotTransaction` | Définir avec option filtre |
| `replaceItemStackInSlot(short, ItemStack attendu, ItemStack nouveau)` | `ItemStackSlotTransaction` | Remplacer si correspondance |
| `replaceAll(SlotReplacementFunction)` | `ListTransaction` | Remplacer tous les objets |
### Opérations de Retrait
| Méthode | Retour | Description |
|--------|---------|-------------|
| `removeItemStackFromSlot(short)` | `SlotTransaction` | Retirer tout le slot |
| `removeItemStackFromSlot(short, filter)` | `SlotTransaction` | Retirer avec filtre |
| `removeItemStackFromSlot(short, quantity)` | `ItemStackSlotTransaction` | Retirer une quantité |
| `removeItemStackFromSlot(short, ItemStack, quantity)` | `ItemStackSlotTransaction` | Retirer objet correspondant |
| `canRemoveItemStack(ItemStack)` | `boolean` | Vérifier si peut retirer |
| `removeItemStack(ItemStack)` | `ItemStackTransaction` | Retirer type d'objet |
| `canRemoveItemStacks(List<ItemStack>)` | `boolean` | Vérifier si peut retirer plusieurs |
| `removeItemStacks(List<ItemStack>)` | `ListTransaction` | Retirer plusieurs objets |
| `removeAllItemStacks()` | `List<ItemStack>` | Retirer et retourner tous |
| `dropAllItemStacks()` | `List<ItemStack>` | Lâcher tous (respecte cantDrop) |
| `dropAllItemStacks(filter)` | `List<ItemStack>` | Lâcher avec option filtre |
### Retrait Matériau/Ressource/Tag
| Méthode | Retour | Description |
|--------|---------|-------------|
| `canRemoveMaterial(MaterialQuantity)` | `boolean` | Vérifier retrait matériau |
| `removeMaterial(MaterialQuantity)` | `MaterialTransaction` | Retirer par matériau |
| `removeMaterialFromSlot(short, MaterialQuantity)` | `MaterialSlotTransaction` | Retirer matériau du slot |
| `canRemoveMaterials(List<MaterialQuantity>)` | `boolean` | Vérifier plusieurs matériaux |
| `removeMaterials(List<MaterialQuantity>)` | `ListTransaction` | Retirer plusieurs matériaux |
| `canRemoveResource(ResourceQuantity)` | `boolean` | Vérifier retrait ressource |
| `removeResource(ResourceQuantity)` | `ResourceTransaction` | Retirer par ressource |
| `removeResourceFromSlot(short, ResourceQuantity)` | `ResourceSlotTransaction` | Retirer ressource du slot |
| `canRemoveResources(List<ResourceQuantity>)` | `boolean` | Vérifier plusieurs ressources |
| `removeResources(List<ResourceQuantity>)` | `ListTransaction` | Retirer plusieurs ressources |
| `canRemoveTag(tagIndex, quantity)` | `boolean` | Vérifier retrait tag |
| `removeTag(tagIndex, quantity)` | `TagTransaction` | Retirer par tag |
| `removeTagFromSlot(short, tagIndex, quantity)` | `TagSlotTransaction` | Retirer tag du slot |
### Opérations de Déplacement
| Méthode | Retour | Description |
|--------|---------|-------------|
| `moveItemStackFromSlot(short, ItemContainer)` | `MoveTransaction` | Déplacer slot vers conteneur |
| `moveItemStackFromSlot(short, quantity, ItemContainer)` | `MoveTransaction` | Déplacer quantité |
| `moveItemStackFromSlot(short, ItemContainer...)` | `ListTransaction` | Déplacer vers plusieurs conteneurs |
| `moveItemStackFromSlotToSlot(short, quantity, ItemContainer, short)` | `MoveTransaction` | Déplacer vers slot spécifique |
| `moveAllItemStacksTo(ItemContainer...)` | `ListTransaction` | Déplacer tous les objets |
| `moveAllItemStacksTo(Predicate, ItemContainer...)` | `ListTransaction` | Déplacer objets correspondants |
| `quickStackTo(ItemContainer...)` | `ListTransaction` | Déplacer uniquement empilables |
| `combineItemStacksIntoSlot(ItemContainer, short)` | `ListTransaction` | Combiner piles dans slot |
| `swapItems(short srcPos, ItemContainer, short destPos, short length)` | `ListTransaction` | Échanger plages d'objets |
### Méthodes Utilitaires
| Méthode | Retour | Description |
|--------|---------|-------------|
| `forEach(ShortObjectConsumer)` | `void` | Itérer slots non vides |
| `forEachWithMeta(consumer, meta)` | `void` | Itérer avec métadonnées |
| `countItemStacks(Predicate)` | `int` | Compter objets correspondants (quantité totale) |
| `containsItemStacksStackableWith(ItemStack)` | `boolean` | Vérifier objets empilables |
| `sortItems(SortType)` | `ListTransaction` | Trier conteneur |
| `registerChangeEvent(Consumer)` | `EventRegistration` | Écouter les changements |
| `registerChangeEvent(EventPriority, Consumer)` | `EventRegistration` | Écouter avec priorité |
| `setGlobalFilter(FilterType)` | `void` | Définir filtre conteneur |
| `setSlotFilter(FilterActionType, short, SlotFilter)` | `void` | Définir filtre slot |
| `containsContainer(ItemContainer)` | `boolean` | Vérifier si contient conteneur |
### Méthodes Statiques
| Méthode | Retour | Description |
|--------|---------|-------------|
| `copy(from, to, remainder)` | `T` | Copier objets entre conteneurs |
| `ensureContainerCapacity(container, capacity, supplier, remainder)` | `T` | S'assurer de la capacité |
| `getNewContainer(capacity, supplier)` | `ItemContainer` | Créer ou obtenir vide |
| `getMatchingResourceType(Item, resourceId)` | `ItemResourceType?` | Trouver type ressource pour objet |
| `validateQuantity(int)` | `void` | Lance exception si < 0 |
| `validateSlotIndex(short, capacity)` | `void` | Lance exception si hors limites |
### Constantes Statiques
| Champ | Type | Description |
|-------|------|-------------|
| `CODEC` | `CodecMapCodec<ItemContainer>` | Codec de sérialisation |
| `DEFAULT_ADD_ALL_OR_NOTHING` | `boolean` | false |
| `DEFAULT_REMOVE_ALL_OR_NOTHING` | `boolean` | true |
| `DEFAULT_FULL_STACKS` | `boolean` | false |
| `DEFAULT_EXACT_AMOUNT` | `boolean` | true |
| `DEFAULT_FILTER` | `boolean` | true |
### Classes Imbriquées
| Classe | Description |
|-------|-------------|
| `ItemContainerChangeEvent` | Record événement avec `container()` et `transaction()` |
## Référence API Inventory
| Méthode | Retour | Description |
|--------|---------|-------------|
| `getStorage()` | `ItemContainer` | Section de stockage principal |
| `getHotbar()` | `ItemContainer` | Section hotbar |
| `getArmor()` | `ItemContainer` | Section armure |
| `getUtility()` | `ItemContainer` | Section utilitaire |
| `getBackpack()` | `ItemContainer` | Section sac à dos |
| `getSectionById(int)` | `ItemContainer?` | Obtenir section par ID |
| `getItemInHand()` | `ItemStack?` | Objet actuellement tenu |
| `getActiveHotbarSlot()` | `byte` | Slot actif de la hotbar |
| `setActiveHotbarSlot(byte)` | `void` | Définir hotbar active |
| `getCombinedHotbarFirst()` | `CombinedItemContainer` | Hotbar+Stockage combinés |
| `moveItem(...)` | `void` | Déplacer entre sections |
| `clear()` | `void` | Vider toutes les sections |

View File

@@ -0,0 +1,354 @@
---
title: ItemStacks
type: docs
weight: 1
---
ItemStack represents a stack of items in Hytale with quantity, durability, and metadata.
## Creating ItemStacks
ItemStacks are created using constructors with an item ID:
```java
// Create by item ID
ItemStack sword = new ItemStack("iron_sword");
// Create with quantity
ItemStack materials = new ItemStack("wood_plank", 64);
// Create with quantity and metadata
BsonDocument metadata = new BsonDocument();
ItemStack customItem = new ItemStack("iron_sword", 1, metadata);
// Create with full parameters (durability)
ItemStack damagedSword = new ItemStack("iron_sword", 1, 50.0, 100.0, null);
// Parameters: itemId, quantity, durability, maxDurability, metadata
```
## ItemStack Properties
```java
ItemStack stack = new ItemStack("iron_sword", 1);
// Get the item ID
String itemId = stack.getItemId(); // "iron_sword"
// Get the Item asset
Item item = stack.getItem();
// Get quantity (NOT getCount!)
int quantity = stack.getQuantity();
// Check if empty
boolean empty = stack.isEmpty();
// Check validity
boolean valid = stack.isValid();
// Durability
double durability = stack.getDurability();
double maxDurability = stack.getMaxDurability();
boolean unbreakable = stack.isUnbreakable(); // true if maxDurability <= 0
boolean broken = stack.isBroken(); // true if durability == 0
```
## Modifying ItemStacks
ItemStack uses a `with*` pattern that returns NEW instances:
```java
ItemStack stack = new ItemStack("iron_sword", 1);
// Change quantity - returns NEW ItemStack or null if quantity is 0
ItemStack moreItems = stack.withQuantity(32);
// Change durability
ItemStack damaged = stack.withDurability(50.0);
// Increase durability
ItemStack repaired = stack.withIncreasedDurability(25.0);
// Restore full durability
ItemStack fullyRepaired = stack.withRestoredDurability(100.0);
// Change max durability
ItemStack stronger = stack.withMaxDurability(200.0);
// Change state (for items with states)
ItemStack newState = stack.withState("activated");
// Add/modify metadata
ItemStack withMeta = stack.withMetadata(metadataDocument);
// Add specific metadata value
ItemStack tagged = stack.withMetadata("CustomKey", Codec.STRING, "CustomValue");
```
{{< callout type="warning" >}}
**Important:** `withQuantity(0)` returns `null`, not an empty ItemStack. Always check for null when decreasing quantity!
{{< /callout >}}
## The EMPTY Constant
Use `ItemStack.EMPTY` for empty stacks:
```java
// Static empty instance (singleton)
ItemStack empty = ItemStack.EMPTY;
// Check for empty
if (stack.isEmpty()) {
// Stack is empty
}
// Static helper method
if (ItemStack.isEmpty(stack)) {
// Handles null and empty stacks
}
```
## Comparing ItemStacks
```java
ItemStack a = new ItemStack("iron_sword", 1);
ItemStack b = new ItemStack("iron_sword", 5);
ItemStack c = new ItemStack("diamond_sword", 1);
// Check if stackable (same itemId, durability, maxDurability, AND metadata)
// Note: Different quantities can stack, but durability values must match exactly
boolean canStack = a.isStackableWith(b);
// Check equivalent type (same itemId and metadata, ignores durability values)
boolean sameType = a.isEquivalentType(b);
// Check same item type only (just itemId comparison)
boolean sameItem = ItemStack.isSameItemType(a, c); // false
// Static helpers (handle nulls safely)
ItemStack.isStackableWith(a, b);
ItemStack.isEquivalentType(a, b);
```
## Working with Metadata
ItemStack supports BSON metadata for custom data:
```java
// Create metadata codec
KeyedCodec<String> OWNER_KEY = new KeyedCodec<>("Owner", Codec.STRING);
// Add metadata
ItemStack withOwner = stack.withMetadata(OWNER_KEY, "PlayerName");
// Read metadata
String owner = stack.getFromMetadataOrNull(OWNER_KEY);
// Read with key and codec
Integer level = stack.getFromMetadataOrNull("Level", Codec.INTEGER);
// Read with default from BuilderCodec
MyData data = stack.getFromMetadataOrDefault("Data", MyData.CODEC);
```
## Block Items
Check if an item can be placed as a block:
```java
ItemStack stack = new ItemStack("stone", 1);
// Get associated block key (null if not a block item)
String blockKey = stack.getBlockKey();
if (blockKey != null) {
// This item can be placed as a block
}
// Check via Item asset
Item item = stack.getItem();
if (item.hasBlockType()) {
String blockId = item.getBlockId();
}
```
## Common Patterns
### Consuming Items
```java
public ItemStack consumeOne(ItemStack stack) {
if (stack == null || stack.isEmpty()) {
return null;
}
int newQuantity = stack.getQuantity() - 1;
// withQuantity returns null if quantity is 0
return stack.withQuantity(newQuantity);
}
// Usage with container
public void useItem(ItemContainer container, short slot) {
ItemStack current = container.getItemStack(slot);
ItemStack remaining = consumeOne(current);
// remaining may be null if stack was depleted
container.setItemStackForSlot(slot, remaining);
}
```
### Checking Item Type
```java
public boolean isHoldingSword(ItemStack hand) {
if (hand == null || hand.isEmpty()) {
return false;
}
// Check item type by ID
return hand.getItemId().contains("sword");
// Or check via Item asset
// return hand.getItem().getCategory().equals("weapon");
}
```
### Splitting Stacks
```java
public ItemStack[] splitStack(ItemStack stack, int splitAmount) {
if (stack == null || stack.isEmpty()) {
return null;
}
int currentQuantity = stack.getQuantity();
if (splitAmount >= currentQuantity) {
return new ItemStack[] { stack, null };
}
// Create two stacks
ItemStack remaining = stack.withQuantity(currentQuantity - splitAmount);
ItemStack split = stack.withQuantity(splitAmount);
return new ItemStack[] { remaining, split };
}
```
### Merging Stacks
```java
public ItemStack[] mergeStacks(ItemStack target, ItemStack source) {
if (!target.isStackableWith(source)) {
return new ItemStack[] { target, source }; // Can't merge
}
int maxStack = target.getItem().getMaxStack();
int totalQuantity = target.getQuantity() + source.getQuantity();
if (totalQuantity <= maxStack) {
// Full merge
return new ItemStack[] {
target.withQuantity(totalQuantity),
null
};
}
// Partial merge
return new ItemStack[] {
target.withQuantity(maxStack),
source.withQuantity(totalQuantity - maxStack)
};
}
```
## Best Practices
{{< callout type="info" >}}
**Remember:**
- Use `getQuantity()` not `getCount()` - Hytale uses "quantity"
- `withQuantity(0)` returns `null` - check for this!
- Use `ItemStack.isEmpty(stack)` to handle both null and empty
- ItemStacks are mostly immutable - `with*` methods return new instances
- Use `isStackableWith()` before attempting to merge stacks
- `ResourceType.Id` must not be null when creating items via codecs
{{< /callout >}}
```java
// Good: Handle null from withQuantity
ItemStack result = stack.withQuantity(newQuantity);
if (result == null) {
// Stack depleted, handle appropriately
}
// Good: Safe empty check
if (ItemStack.isEmpty(stack)) {
// Handles both null and empty ItemStack.EMPTY
}
// Bad: Ignoring the returned value
stack.withQuantity(10); // Returns new stack, original unchanged!
```
## ItemStack API Reference
### Instance Methods
| Method | Returns | Description |
|--------|---------|-------------|
| `getItemId()` | `String` | The item type identifier |
| `getItem()` | `Item` | The Item asset (returns `Item.UNKNOWN` if not found) |
| `getQuantity()` | `int` | Stack size |
| `getDurability()` | `double` | Current durability |
| `getMaxDurability()` | `double` | Maximum durability |
| `isEmpty()` | `boolean` | True if itemId equals "Empty" |
| `isUnbreakable()` | `boolean` | True if maxDurability <= 0 |
| `isBroken()` | `boolean` | True if NOT unbreakable AND durability == 0 |
| `isValid()` | `boolean` | True if empty OR item asset exists |
| `isStackableWith(ItemStack)` | `boolean` | Same itemId, durability, maxDurability, metadata |
| `isEquivalentType(ItemStack)` | `boolean` | Same itemId and metadata (ignores durability) |
| `getBlockKey()` | `String?` | Block ID if item is placeable, null otherwise |
| `getOverrideDroppedItemAnimation()` | `boolean` | Animation override flag |
| `getMetadata()` | `BsonDocument?` | **Deprecated** - Returns cloned metadata |
| `getFromMetadataOrNull(KeyedCodec)` | `T?` | Get typed metadata value |
| `getFromMetadataOrNull(String, Codec)` | `T?` | Get typed metadata by key |
| `getFromMetadataOrDefault(String, BuilderCodec)` | `T` | Get metadata with default |
### Modifier Methods (return new ItemStack)
| Method | Returns | Description |
|--------|---------|-------------|
| `withQuantity(int)` | `ItemStack?` | **Returns null if quantity is 0** |
| `withDurability(double)` | `ItemStack` | Clamped to [0, maxDurability] |
| `withMaxDurability(double)` | `ItemStack` | Also clamps current durability |
| `withIncreasedDurability(double)` | `ItemStack` | Add to current durability |
| `withRestoredDurability(double)` | `ItemStack` | Set both durability and max |
| `withState(String)` | `ItemStack` | Change item state |
| `withMetadata(BsonDocument)` | `ItemStack` | Replace all metadata |
| `withMetadata(KeyedCodec, T)` | `ItemStack` | Set typed metadata |
| `withMetadata(String, Codec, T)` | `ItemStack` | Set metadata by key |
| `withMetadata(String, BsonValue)` | `ItemStack` | Set raw BSON value |
| `setOverrideDroppedItemAnimation(boolean)` | `void` | **Mutates in place** |
### Static Fields
| Field | Type | Description |
|-------|------|-------------|
| `EMPTY` | `ItemStack` | Singleton empty stack (itemId = "Empty") |
| `EMPTY_ARRAY` | `ItemStack[]` | Empty array constant |
| `CODEC` | `BuilderCodec<ItemStack>` | Serialization codec |
### Static Methods
| Method | Returns | Description |
|--------|---------|-------------|
| `isEmpty(ItemStack)` | `boolean` | Null-safe empty check |
| `isStackableWith(ItemStack, ItemStack)` | `boolean` | Null-safe stackable check |
| `isEquivalentType(ItemStack, ItemStack)` | `boolean` | Null-safe type check |
| `isSameItemType(ItemStack, ItemStack)` | `boolean` | Compare itemId only |
| `fromPacket(ItemQuantity)` | `ItemStack?` | Create from network packet |
### Nested Classes
| Class | Description |
|-------|-------------|
| `ItemStack.Metadata` | Contains `BLOCK_STATE` constant for block state metadata key |

View File

@@ -0,0 +1,354 @@
---
title: ItemStacks
type: docs
weight: 1
---
ItemStack représente une pile d'objets dans Hytale avec quantité, durabilité et métadonnées.
## Créer des ItemStacks
Les ItemStacks sont créés en utilisant des constructeurs avec un ID d'objet :
```java
// Créer par ID d'objet
ItemStack sword = new ItemStack("iron_sword");
// Créer avec quantité
ItemStack materials = new ItemStack("wood_plank", 64);
// Créer avec quantité et métadonnées
BsonDocument metadata = new BsonDocument();
ItemStack customItem = new ItemStack("iron_sword", 1, metadata);
// Créer avec tous les paramètres (durabilité)
ItemStack damagedSword = new ItemStack("iron_sword", 1, 50.0, 100.0, null);
// Paramètres: itemId, quantity, durability, maxDurability, metadata
```
## Propriétés d'ItemStack
```java
ItemStack stack = new ItemStack("iron_sword", 1);
// Obtenir l'ID de l'objet
String itemId = stack.getItemId(); // "iron_sword"
// Obtenir l'asset Item
Item item = stack.getItem();
// Obtenir la quantité (PAS getCount!)
int quantity = stack.getQuantity();
// Vérifier si vide
boolean empty = stack.isEmpty();
// Vérifier la validité
boolean valid = stack.isValid();
// Durabilité
double durability = stack.getDurability();
double maxDurability = stack.getMaxDurability();
boolean unbreakable = stack.isUnbreakable(); // true si maxDurability <= 0
boolean broken = stack.isBroken(); // true si durability == 0
```
## Modifier les ItemStacks
ItemStack utilise un pattern `with*` qui retourne de NOUVELLES instances :
```java
ItemStack stack = new ItemStack("iron_sword", 1);
// Changer la quantité - retourne un NOUVEL ItemStack ou null si quantité est 0
ItemStack moreItems = stack.withQuantity(32);
// Changer la durabilité
ItemStack damaged = stack.withDurability(50.0);
// Augmenter la durabilité
ItemStack repaired = stack.withIncreasedDurability(25.0);
// Restaurer la durabilité complète
ItemStack fullyRepaired = stack.withRestoredDurability(100.0);
// Changer la durabilité max
ItemStack stronger = stack.withMaxDurability(200.0);
// Changer l'état (pour les objets avec états)
ItemStack newState = stack.withState("activated");
// Ajouter/modifier les métadonnées
ItemStack withMeta = stack.withMetadata(metadataDocument);
// Ajouter une valeur de métadonnée spécifique
ItemStack tagged = stack.withMetadata("CustomKey", Codec.STRING, "CustomValue");
```
{{< callout type="warning" >}}
**Important :** `withQuantity(0)` retourne `null`, pas un ItemStack vide. Vérifiez toujours null quand vous diminuez la quantité !
{{< /callout >}}
## La Constante EMPTY
Utilisez `ItemStack.EMPTY` pour les piles vides :
```java
// Instance vide statique (singleton)
ItemStack empty = ItemStack.EMPTY;
// Vérifier si vide
if (stack.isEmpty()) {
// La pile est vide
}
// Méthode helper statique
if (ItemStack.isEmpty(stack)) {
// Gère les piles null et vides
}
```
## Comparer les ItemStacks
```java
ItemStack a = new ItemStack("iron_sword", 1);
ItemStack b = new ItemStack("iron_sword", 5);
ItemStack c = new ItemStack("diamond_sword", 1);
// Vérifier si empilables (même itemId, durability, maxDurability ET metadata)
// Note: Des quantités différentes peuvent s'empiler, mais les durabilités doivent correspondre exactement
boolean canStack = a.isStackableWith(b);
// Vérifier type équivalent (même itemId et metadata, ignore les durabilités)
boolean sameType = a.isEquivalentType(b);
// Vérifier même type d'objet seulement (juste itemId)
boolean sameItem = ItemStack.isSameItemType(a, c); // false
// Helpers statiques (gèrent les nulls en sécurité)
ItemStack.isStackableWith(a, b);
ItemStack.isEquivalentType(a, b);
```
## Travailler avec les Métadonnées
ItemStack supporte les métadonnées BSON pour les données personnalisées :
```java
// Créer un codec de métadonnée
KeyedCodec<String> OWNER_KEY = new KeyedCodec<>("Owner", Codec.STRING);
// Ajouter des métadonnées
ItemStack withOwner = stack.withMetadata(OWNER_KEY, "PlayerName");
// Lire les métadonnées
String owner = stack.getFromMetadataOrNull(OWNER_KEY);
// Lire avec clé et codec
Integer level = stack.getFromMetadataOrNull("Level", Codec.INTEGER);
// Lire avec défaut depuis BuilderCodec
MyData data = stack.getFromMetadataOrDefault("Data", MyData.CODEC);
```
## Objets de Bloc
Vérifier si un objet peut être placé comme bloc :
```java
ItemStack stack = new ItemStack("stone", 1);
// Obtenir la clé de bloc associée (null si pas un objet de bloc)
String blockKey = stack.getBlockKey();
if (blockKey != null) {
// Cet objet peut être placé comme bloc
}
// Vérifier via l'asset Item
Item item = stack.getItem();
if (item.hasBlockType()) {
String blockId = item.getBlockId();
}
```
## Patterns Courants
### Consommer des Objets
```java
public ItemStack consumeOne(ItemStack stack) {
if (stack == null || stack.isEmpty()) {
return null;
}
int newQuantity = stack.getQuantity() - 1;
// withQuantity retourne null si quantité est 0
return stack.withQuantity(newQuantity);
}
// Utilisation avec conteneur
public void useItem(ItemContainer container, short slot) {
ItemStack current = container.getItemStack(slot);
ItemStack remaining = consumeOne(current);
// remaining peut être null si la pile est épuisée
container.setItemStackForSlot(slot, remaining);
}
```
### Vérifier le Type d'Objet
```java
public boolean isHoldingSword(ItemStack hand) {
if (hand == null || hand.isEmpty()) {
return false;
}
// Vérifier le type d'objet par ID
return hand.getItemId().contains("sword");
// Ou vérifier via l'asset Item
// return hand.getItem().getCategory().equals("weapon");
}
```
### Diviser des Piles
```java
public ItemStack[] splitStack(ItemStack stack, int splitAmount) {
if (stack == null || stack.isEmpty()) {
return null;
}
int currentQuantity = stack.getQuantity();
if (splitAmount >= currentQuantity) {
return new ItemStack[] { stack, null };
}
// Créer deux piles
ItemStack remaining = stack.withQuantity(currentQuantity - splitAmount);
ItemStack split = stack.withQuantity(splitAmount);
return new ItemStack[] { remaining, split };
}
```
### Fusionner des Piles
```java
public ItemStack[] mergeStacks(ItemStack target, ItemStack source) {
if (!target.isStackableWith(source)) {
return new ItemStack[] { target, source }; // Ne peut pas fusionner
}
int maxStack = target.getItem().getMaxStack();
int totalQuantity = target.getQuantity() + source.getQuantity();
if (totalQuantity <= maxStack) {
// Fusion complète
return new ItemStack[] {
target.withQuantity(totalQuantity),
null
};
}
// Fusion partielle
return new ItemStack[] {
target.withQuantity(maxStack),
source.withQuantity(totalQuantity - maxStack)
};
}
```
## Bonnes Pratiques
{{< callout type="info" >}}
**Rappelez-vous :**
- Utilisez `getQuantity()` pas `getCount()` - Hytale utilise "quantity"
- `withQuantity(0)` retourne `null` - vérifiez cela !
- Utilisez `ItemStack.isEmpty(stack)` pour gérer null et vide
- Les ItemStacks sont principalement immuables - les méthodes `with*` retournent de nouvelles instances
- Utilisez `isStackableWith()` avant de tenter de fusionner des piles
- `ResourceType.Id` ne doit pas être null lors de la création d'objets via codecs
{{< /callout >}}
```java
// Bon : Gérer null de withQuantity
ItemStack result = stack.withQuantity(newQuantity);
if (result == null) {
// Pile épuisée, gérer appropriément
}
// Bon : Vérification vide sécurisée
if (ItemStack.isEmpty(stack)) {
// Gère à la fois null et ItemStack.EMPTY vide
}
// Mauvais : Ignorer la valeur retournée
stack.withQuantity(10); // Retourne nouvelle pile, original inchangé !
```
## Référence API ItemStack
### Méthodes d'Instance
| Méthode | Retour | Description |
|--------|---------|-------------|
| `getItemId()` | `String` | L'identifiant du type d'objet |
| `getItem()` | `Item` | L'asset Item (retourne `Item.UNKNOWN` si non trouvé) |
| `getQuantity()` | `int` | Taille de la pile |
| `getDurability()` | `double` | Durabilité actuelle |
| `getMaxDurability()` | `double` | Durabilité maximum |
| `isEmpty()` | `boolean` | True si itemId égale "Empty" |
| `isUnbreakable()` | `boolean` | True si maxDurability <= 0 |
| `isBroken()` | `boolean` | True si PAS incassable ET durability == 0 |
| `isValid()` | `boolean` | True si vide OU l'asset existe |
| `isStackableWith(ItemStack)` | `boolean` | Même itemId, durability, maxDurability, metadata |
| `isEquivalentType(ItemStack)` | `boolean` | Même itemId et metadata (ignore durabilité) |
| `getBlockKey()` | `String?` | ID bloc si plaçable, null sinon |
| `getOverrideDroppedItemAnimation()` | `boolean` | Flag d'override animation |
| `getMetadata()` | `BsonDocument?` | **Déprécié** - Retourne metadata clonée |
| `getFromMetadataOrNull(KeyedCodec)` | `T?` | Obtenir valeur metadata typée |
| `getFromMetadataOrNull(String, Codec)` | `T?` | Obtenir metadata par clé |
| `getFromMetadataOrDefault(String, BuilderCodec)` | `T` | Obtenir metadata avec défaut |
### Méthodes de Modification (retournent nouveau ItemStack)
| Méthode | Retour | Description |
|--------|---------|-------------|
| `withQuantity(int)` | `ItemStack?` | **Retourne null si quantité est 0** |
| `withDurability(double)` | `ItemStack` | Borné à [0, maxDurability] |
| `withMaxDurability(double)` | `ItemStack` | Borne aussi la durabilité actuelle |
| `withIncreasedDurability(double)` | `ItemStack` | Ajoute à la durabilité |
| `withRestoredDurability(double)` | `ItemStack` | Définit durabilité et max |
| `withState(String)` | `ItemStack` | Change l'état de l'objet |
| `withMetadata(BsonDocument)` | `ItemStack` | Remplace toutes les metadata |
| `withMetadata(KeyedCodec, T)` | `ItemStack` | Définit metadata typée |
| `withMetadata(String, Codec, T)` | `ItemStack` | Définit metadata par clé |
| `withMetadata(String, BsonValue)` | `ItemStack` | Définit valeur BSON brute |
| `setOverrideDroppedItemAnimation(boolean)` | `void` | **Mute en place** |
### Champs Statiques
| Champ | Type | Description |
|-------|------|-------------|
| `EMPTY` | `ItemStack` | Pile vide singleton (itemId = "Empty") |
| `EMPTY_ARRAY` | `ItemStack[]` | Constante tableau vide |
| `CODEC` | `BuilderCodec<ItemStack>` | Codec de sérialisation |
### Méthodes Statiques
| Méthode | Retour | Description |
|--------|---------|-------------|
| `isEmpty(ItemStack)` | `boolean` | Vérification vide null-safe |
| `isStackableWith(ItemStack, ItemStack)` | `boolean` | Vérification empilable null-safe |
| `isEquivalentType(ItemStack, ItemStack)` | `boolean` | Vérification type null-safe |
| `isSameItemType(ItemStack, ItemStack)` | `boolean` | Compare itemId seulement |
| `fromPacket(ItemQuantity)` | `ItemStack?` | Créer depuis paquet réseau |
### Classes Imbriquées
| Classe | Description |
|-------|-------------|
| `ItemStack.Metadata` | Contient constante `BLOCK_STATE` pour clé metadata état bloc |

View File

@@ -0,0 +1,430 @@
---
title: Transactions
type: docs
weight: 3
---
Transactions track the results of inventory operations in Hytale. Every modification to an `ItemContainer` returns a Transaction object that describes what happened.
## Transaction Interface
All transactions implement the base `Transaction` interface:
```java
public interface Transaction {
// Did the operation succeed?
boolean succeeded();
// Was a specific slot modified?
boolean wasSlotModified(short slot);
}
```
## Transaction Types
### SlotTransaction
Tracks changes to a single slot:
```java
SlotTransaction transaction = container.removeItemStackFromSlot((short) 0);
if (transaction.succeeded()) {
// What was in the slot before
ItemStack before = transaction.getSlotBefore();
// What is in the slot now
ItemStack after = transaction.getSlotAfter();
// The item that was removed/output
ItemStack output = transaction.getOutput();
// Which slot was affected
short slot = transaction.getSlot();
// What type of action (ADD, REMOVE, REPLACE)
ActionType action = transaction.getAction();
}
```
### ItemStackSlotTransaction
Extended slot transaction with additional details:
```java
ItemStackSlotTransaction transaction = container.setItemStackForSlot(
(short) 0,
new ItemStack("iron_sword", 1)
);
if (transaction.succeeded()) {
short slot = transaction.getSlot();
ItemStack before = transaction.getSlotBefore();
ItemStack after = transaction.getSlotAfter();
// Check options used
boolean wasFiltered = transaction.isFilter();
boolean wasAllOrNothing = transaction.isAllOrNothing();
}
```
### ItemStackTransaction
Tracks operations that may affect multiple slots:
```java
ItemStack toAdd = new ItemStack("stone", 128);
ItemStackTransaction transaction = container.addItemStack(toAdd);
if (transaction.succeeded()) {
// Items that couldn't fit (null if all fit)
ItemStack remainder = transaction.getRemainder();
// Original item we tried to add
ItemStack query = transaction.getQuery();
// List of all slot transactions that occurred
List<ItemStackSlotTransaction> slotTransactions = transaction.getSlotTransactions();
for (ItemStackSlotTransaction slotTx : slotTransactions) {
getLogger().at(Level.INFO).log("Modified slot " + slotTx.getSlot());
}
}
```
### MoveTransaction
Tracks moving items between containers:
```java
MoveTransaction<SlotTransaction> transaction = storage.moveItemStackFromSlotToSlot(
(short) 0,
32,
hotbar,
(short) 0
);
if (transaction.succeeded()) {
// Transaction for removing from source
SlotTransaction removeTransaction = transaction.getRemoveTransaction();
// Transaction for adding to destination
SlotTransaction addTransaction = transaction.getAddTransaction();
// The destination container
ItemContainer destination = transaction.getOtherContainer();
// Direction of the move
MoveType moveType = transaction.getMoveType();
}
```
### ListTransaction
Wraps multiple transactions:
```java
List<ItemStack> items = List.of(
new ItemStack("stone", 64),
new ItemStack("wood", 64)
);
ListTransaction<ItemStackTransaction> transaction = container.addItemStacks(items);
if (transaction.succeeded()) {
List<ItemStackTransaction> results = transaction.getList();
for (ItemStackTransaction result : results) {
if (result.succeeded()) {
ItemStack remainder = result.getRemainder();
// ...
}
}
}
```
## ActionType
Operations are categorized by action type:
```java
public enum ActionType {
SET, // Items set (add=true, remove=false, destroy=true)
ADD, // Items added to slot (add=true, remove=false, destroy=false)
REMOVE, // Items removed from slot (add=false, remove=true, destroy=false)
REPLACE // Slot contents replaced (add=true, remove=true, destroy=false)
}
// Check action characteristics
if (action.isAdd()) { /* operation adds items */ }
if (action.isRemove()) { /* operation removes items */ }
if (action.isDestroy()) { /* operation destroys slot contents */ }
```
## MoveType
Direction of move operations:
```java
public enum MoveType {
MOVE_TO_SELF, // Items being moved TO this container
MOVE_FROM_SELF // Items being moved FROM this container
}
```
## Common Patterns
### Check Before Modify
```java
// Safe pattern: verify first, then execute
public boolean safeTransfer(ItemContainer from, ItemContainer to, String itemId, int amount) {
ItemStack toRemove = new ItemStack(itemId, amount);
// Check both operations can succeed
if (!from.canRemoveItemStack(toRemove)) {
return false; // Not enough items
}
if (!to.canAddItemStack(toRemove)) {
return false; // No space
}
// Execute removal
ItemStackTransaction removeResult = from.removeItemStack(toRemove);
if (!removeResult.succeeded()) {
return false;
}
// Execute addition
ItemStack removed = removeResult.getQuery();
ItemStackTransaction addResult = to.addItemStack(removed);
// Handle any remainder
ItemStack remainder = addResult.getRemainder();
if (!ItemStack.isEmpty(remainder)) {
// Put remainder back
from.addItemStack(remainder);
}
return true;
}
```
### Handling Remainders
```java
public void giveItemSafe(Player player, String itemId, int quantity) {
ItemContainer storage = player.getInventory().getStorage();
ItemStack item = new ItemStack(itemId, quantity);
ItemStackTransaction result = storage.addItemStack(item);
if (!result.succeeded()) {
player.sendMessage(Message.raw("Inventory full!"));
return;
}
ItemStack remainder = result.getRemainder();
if (!ItemStack.isEmpty(remainder)) {
player.sendMessage(Message.raw(
"Only received " + (quantity - remainder.getQuantity()) + " items, " +
"inventory full!"
));
}
}
```
### Atomic Operations
```java
// allOrNothing=true ensures partial operations don't happen
public boolean buyItem(Player player, String itemId, int price, int quantity) {
Inventory inv = player.getInventory();
ItemContainer storage = inv.getStorage();
ItemStack currency = new ItemStack("gold_coin", price);
ItemStack item = new ItemStack(itemId, quantity);
// Check both operations can succeed fully
if (!storage.canRemoveItemStack(currency)) {
player.sendMessage(Message.raw("Not enough gold!"));
return false;
}
if (!storage.canAddItemStack(item)) {
player.sendMessage(Message.raw("Inventory full!"));
return false;
}
// Remove currency with allOrNothing=true
ItemStackTransaction removeResult = storage.removeItemStack(currency, true, true);
if (!removeResult.succeeded()) {
return false;
}
// Add item
ItemStackTransaction addResult = storage.addItemStack(item, true, false, true);
if (!addResult.succeeded()) {
// Rollback: return the currency
storage.addItemStack(currency);
return false;
}
return true;
}
```
### Tracking Changes
```java
// Use wasSlotModified to check specific slots
public void onContainerChange(ItemContainer.ItemContainerChangeEvent event) {
Transaction transaction = event.transaction();
// Check if hotbar slot 0 was affected
if (transaction.wasSlotModified((short) 0)) {
getLogger().at(Level.INFO).log("First slot was modified!");
}
// Check all hotbar slots
for (short i = 0; i < 9; i++) {
if (transaction.wasSlotModified(i)) {
getLogger().at(Level.INFO).log("Hotbar slot " + i + " modified");
}
}
}
```
## Transaction Options
Many operations accept optional parameters:
| Parameter | Description | Default |
|-----------|-------------|---------|
| `allOrNothing` | If true, operation fails if not all items can be processed | `false` |
| `fullStacks` | If true, only add to empty slots (not partial stacks) | `false` |
| `exactAmount` | If true, must remove exact quantity requested | `true` |
| `filter` | If true, respect slot filters | `true` |
```java
// Default behavior
container.addItemStack(item);
// With options
container.addItemStack(item, true, false, true); // allOrNothing, fullStacks, filter
container.removeItemStack(item, true, true); // allOrNothing, filter
```
## Best Practices
{{< callout type="info" >}}
**Transaction Tips:**
- Always check `succeeded()` before accessing results
- Handle remainders when adding items
- Use `canAddItemStack()`/`canRemoveItemStack()` for pre-validation
- Use `allOrNothing=true` for critical operations
- Check `wasSlotModified()` to track specific slot changes
{{< /callout >}}
```java
// Good: Check success and handle remainder
ItemStackTransaction tx = container.addItemStack(item);
if (tx.succeeded()) {
ItemStack remainder = tx.getRemainder();
if (!ItemStack.isEmpty(remainder)) {
// Handle leftover items
}
}
// Bad: Assume success
container.addItemStack(item); // Might fail silently!
```
## Transaction API Reference
### Transaction Interface
| Method | Returns | Description |
|--------|---------|-------------|
| `succeeded()` | `boolean` | True if operation succeeded |
| `wasSlotModified(short)` | `boolean` | True if specific slot was modified |
### SlotTransaction
| Method | Returns | Description |
|--------|---------|-------------|
| `getSlot()` | `short` | The affected slot index |
| `getAction()` | `ActionType` | Type of action performed |
| `getSlotBefore()` | `ItemStack?` | Contents before operation |
| `getSlotAfter()` | `ItemStack?` | Contents after operation |
| `getOutput()` | `ItemStack?` | Items removed/output |
| `isAllOrNothing()` | `boolean` | allOrNothing parameter used |
| `isExactAmount()` | `boolean` | exactAmount parameter used |
| `isFilter()` | `boolean` | filter parameter used |
| Static | Type | Description |
|--------|------|-------------|
| `FAILED_ADD` | `SlotTransaction` | Pre-built failed add transaction |
### ItemStackSlotTransaction (extends SlotTransaction)
| Method | Returns | Description |
|--------|---------|-------------|
| `isAddToExistingSlot()` | `boolean` | True if added to existing stack |
| `getQuery()` | `ItemStack?` | Original item requested |
| `getRemainder()` | `ItemStack?` | Items that couldn't fit |
### ItemStackTransaction
| Method | Returns | Description |
|--------|---------|-------------|
| `getAction()` | `ActionType?` | Type of action performed |
| `getQuery()` | `ItemStack?` | Original item requested |
| `getRemainder()` | `ItemStack?` | Items that couldn't fit |
| `isAllOrNothing()` | `boolean` | allOrNothing parameter used |
| `isFilter()` | `boolean` | filter parameter used |
| `getSlotTransactions()` | `List<ItemStackSlotTransaction>` | All slot transactions |
| Static | Type | Description |
|--------|------|-------------|
| `FAILED_ADD` | `ItemStackTransaction` | Pre-built failed add transaction |
### MoveTransaction<T extends Transaction>
| Method | Returns | Description |
|--------|---------|-------------|
| `getRemoveTransaction()` | `SlotTransaction` | Transaction for removal |
| `getMoveType()` | `MoveType` | Direction of move |
| `getOtherContainer()` | `ItemContainer` | The other container involved |
| `getAddTransaction()` | `T` | Transaction for addition |
| `toInverted(ItemContainer)` | `MoveTransaction<T>` | Create inverted view for other container |
### ListTransaction<T extends Transaction>
| Method | Returns | Description |
|--------|---------|-------------|
| `getList()` | `List<T>` | All transactions in list |
| `size()` | `int` | Number of transactions |
| Static | Type | Description |
|--------|------|-------------|
| `EMPTY_SUCCESSFUL_TRANSACTION` | `ListTransaction<?>` | Pre-built empty success |
| `EMPTY_FAILED_TRANSACTION` | `ListTransaction<?>` | Pre-built empty failure |
| `getEmptyTransaction(boolean)` | `ListTransaction<T>` | Get empty success/failure |
### ActionType Enum
| Value | isAdd | isRemove | isDestroy | Description |
|-------|-------|----------|-----------|-------------|
| `SET` | true | false | true | Set slot contents |
| `ADD` | true | false | false | Add items to slot |
| `REMOVE` | false | true | false | Remove items from slot |
| `REPLACE` | true | true | false | Replace slot contents |
### MoveType Enum
| Value | Description |
|-------|-------------|
| `MOVE_TO_SELF` | Items moving to this container |
| `MOVE_FROM_SELF` | Items moving from this container |

View File

@@ -0,0 +1,430 @@
---
title: Transactions
type: docs
weight: 3
---
Les transactions suivent les résultats des opérations d'inventaire dans Hytale. Chaque modification d'un `ItemContainer` retourne un objet Transaction qui décrit ce qui s'est passé.
## Interface Transaction
Toutes les transactions implémentent l'interface de base `Transaction` :
```java
public interface Transaction {
// L'opération a-t-elle réussi ?
boolean succeeded();
// Un slot spécifique a-t-il été modifié ?
boolean wasSlotModified(short slot);
}
```
## Types de Transactions
### SlotTransaction
Suit les changements d'un seul slot :
```java
SlotTransaction transaction = container.removeItemStackFromSlot((short) 0);
if (transaction.succeeded()) {
// Ce qui était dans le slot avant
ItemStack before = transaction.getSlotBefore();
// Ce qui est dans le slot maintenant
ItemStack after = transaction.getSlotAfter();
// L'objet qui a été retiré/sorti
ItemStack output = transaction.getOutput();
// Quel slot a été affecté
short slot = transaction.getSlot();
// Quel type d'action (ADD, REMOVE, REPLACE)
ActionType action = transaction.getAction();
}
```
### ItemStackSlotTransaction
Transaction de slot étendue avec des détails supplémentaires :
```java
ItemStackSlotTransaction transaction = container.setItemStackForSlot(
(short) 0,
new ItemStack("iron_sword", 1)
);
if (transaction.succeeded()) {
short slot = transaction.getSlot();
ItemStack before = transaction.getSlotBefore();
ItemStack after = transaction.getSlotAfter();
// Vérifier les options utilisées
boolean wasFiltered = transaction.isFilter();
boolean wasAllOrNothing = transaction.isAllOrNothing();
}
```
### ItemStackTransaction
Suit les opérations qui peuvent affecter plusieurs slots :
```java
ItemStack toAdd = new ItemStack("stone", 128);
ItemStackTransaction transaction = container.addItemStack(toAdd);
if (transaction.succeeded()) {
// Objets qui n'ont pas pu rentrer (null si tous ont rentré)
ItemStack remainder = transaction.getRemainder();
// Objet original qu'on a essayé d'ajouter
ItemStack query = transaction.getQuery();
// Liste de toutes les transactions de slot qui ont eu lieu
List<ItemStackSlotTransaction> slotTransactions = transaction.getSlotTransactions();
for (ItemStackSlotTransaction slotTx : slotTransactions) {
getLogger().at(Level.INFO).log("Slot modifié " + slotTx.getSlot());
}
}
```
### MoveTransaction
Suit le déplacement d'objets entre conteneurs :
```java
MoveTransaction<SlotTransaction> transaction = storage.moveItemStackFromSlotToSlot(
(short) 0,
32,
hotbar,
(short) 0
);
if (transaction.succeeded()) {
// Transaction pour le retrait de la source
SlotTransaction removeTransaction = transaction.getRemoveTransaction();
// Transaction pour l'ajout à la destination
SlotTransaction addTransaction = transaction.getAddTransaction();
// Le conteneur de destination
ItemContainer destination = transaction.getOtherContainer();
// Direction du déplacement
MoveType moveType = transaction.getMoveType();
}
```
### ListTransaction
Encapsule plusieurs transactions :
```java
List<ItemStack> items = List.of(
new ItemStack("stone", 64),
new ItemStack("wood", 64)
);
ListTransaction<ItemStackTransaction> transaction = container.addItemStacks(items);
if (transaction.succeeded()) {
List<ItemStackTransaction> results = transaction.getList();
for (ItemStackTransaction result : results) {
if (result.succeeded()) {
ItemStack remainder = result.getRemainder();
// ...
}
}
}
```
## ActionType
Les opérations sont catégorisées par type d'action :
```java
public enum ActionType {
SET, // Objets définis (add=true, remove=false, destroy=true)
ADD, // Objets ajoutés au slot (add=true, remove=false, destroy=false)
REMOVE, // Objets retirés du slot (add=false, remove=true, destroy=false)
REPLACE // Contenu du slot remplacé (add=true, remove=true, destroy=false)
}
// Vérifier les caractéristiques de l'action
if (action.isAdd()) { /* opération ajoute des objets */ }
if (action.isRemove()) { /* opération retire des objets */ }
if (action.isDestroy()) { /* opération détruit le contenu du slot */ }
```
## MoveType
Direction des opérations de déplacement :
```java
public enum MoveType {
MOVE_TO_SELF, // Objets déplacés VERS ce conteneur
MOVE_FROM_SELF // Objets déplacés DEPUIS ce conteneur
}
```
## Patterns Courants
### Vérifier Avant de Modifier
```java
// Pattern sûr : vérifier d'abord, puis exécuter
public boolean safeTransfer(ItemContainer from, ItemContainer to, String itemId, int amount) {
ItemStack toRemove = new ItemStack(itemId, amount);
// Vérifier que les deux opérations peuvent réussir
if (!from.canRemoveItemStack(toRemove)) {
return false; // Pas assez d'objets
}
if (!to.canAddItemStack(toRemove)) {
return false; // Pas de place
}
// Exécuter le retrait
ItemStackTransaction removeResult = from.removeItemStack(toRemove);
if (!removeResult.succeeded()) {
return false;
}
// Exécuter l'ajout
ItemStack removed = removeResult.getQuery();
ItemStackTransaction addResult = to.addItemStack(removed);
// Gérer les restes éventuels
ItemStack remainder = addResult.getRemainder();
if (!ItemStack.isEmpty(remainder)) {
// Remettre les restes
from.addItemStack(remainder);
}
return true;
}
```
### Gérer les Restes
```java
public void giveItemSafe(Player player, String itemId, int quantity) {
ItemContainer storage = player.getInventory().getStorage();
ItemStack item = new ItemStack(itemId, quantity);
ItemStackTransaction result = storage.addItemStack(item);
if (!result.succeeded()) {
player.sendMessage(Message.raw("Inventaire plein !"));
return;
}
ItemStack remainder = result.getRemainder();
if (!ItemStack.isEmpty(remainder)) {
player.sendMessage(Message.raw(
"Seulement reçu " + (quantity - remainder.getQuantity()) + " objets, " +
"inventaire plein !"
));
}
}
```
### Opérations Atomiques
```java
// allOrNothing=true garantit que les opérations partielles ne se produisent pas
public boolean buyItem(Player player, String itemId, int price, int quantity) {
Inventory inv = player.getInventory();
ItemContainer storage = inv.getStorage();
ItemStack currency = new ItemStack("gold_coin", price);
ItemStack item = new ItemStack(itemId, quantity);
// Vérifier que les deux opérations peuvent réussir complètement
if (!storage.canRemoveItemStack(currency)) {
player.sendMessage(Message.raw("Pas assez d'or !"));
return false;
}
if (!storage.canAddItemStack(item)) {
player.sendMessage(Message.raw("Inventaire plein !"));
return false;
}
// Retirer la monnaie avec allOrNothing=true
ItemStackTransaction removeResult = storage.removeItemStack(currency, true, true);
if (!removeResult.succeeded()) {
return false;
}
// Ajouter l'objet
ItemStackTransaction addResult = storage.addItemStack(item, true, false, true);
if (!addResult.succeeded()) {
// Annuler : rendre la monnaie
storage.addItemStack(currency);
return false;
}
return true;
}
```
### Suivre les Changements
```java
// Utiliser wasSlotModified pour vérifier des slots spécifiques
public void onContainerChange(ItemContainer.ItemContainerChangeEvent event) {
Transaction transaction = event.transaction();
// Vérifier si le slot 0 de la hotbar a été affecté
if (transaction.wasSlotModified((short) 0)) {
getLogger().at(Level.INFO).log("Premier slot modifié !");
}
// Vérifier tous les slots de la hotbar
for (short i = 0; i < 9; i++) {
if (transaction.wasSlotModified(i)) {
getLogger().at(Level.INFO).log("Slot hotbar " + i + " modifié");
}
}
}
```
## Options de Transaction
De nombreuses opérations acceptent des paramètres optionnels :
| Paramètre | Description | Par défaut |
|-----------|-------------|---------|
| `allOrNothing` | Si vrai, l'opération échoue si tous les objets ne peuvent pas être traités | `false` |
| `fullStacks` | Si vrai, ajoute seulement aux slots vides (pas aux piles partielles) | `false` |
| `exactAmount` | Si vrai, doit retirer exactement la quantité demandée | `true` |
| `filter` | Si vrai, respecte les filtres de slot | `true` |
```java
// Comportement par défaut
container.addItemStack(item);
// Avec options
container.addItemStack(item, true, false, true); // allOrNothing, fullStacks, filter
container.removeItemStack(item, true, true); // allOrNothing, filter
```
## Bonnes Pratiques
{{< callout type="info" >}}
**Conseils pour les Transactions :**
- Toujours vérifier `succeeded()` avant d'accéder aux résultats
- Gérer les restes lors de l'ajout d'objets
- Utiliser `canAddItemStack()`/`canRemoveItemStack()` pour la pré-validation
- Utiliser `allOrNothing=true` pour les opérations critiques
- Vérifier `wasSlotModified()` pour suivre les changements de slots spécifiques
{{< /callout >}}
```java
// Bon : Vérifier le succès et gérer les restes
ItemStackTransaction tx = container.addItemStack(item);
if (tx.succeeded()) {
ItemStack remainder = tx.getRemainder();
if (!ItemStack.isEmpty(remainder)) {
// Gérer les objets restants
}
}
// Mauvais : Supposer le succès
container.addItemStack(item); // Pourrait échouer silencieusement !
```
## Référence API Transaction
### Interface Transaction
| Méthode | Retour | Description |
|--------|---------|-------------|
| `succeeded()` | `boolean` | True si l'opération a réussi |
| `wasSlotModified(short)` | `boolean` | True si le slot spécifique a été modifié |
### SlotTransaction
| Méthode | Retour | Description |
|--------|---------|-------------|
| `getSlot()` | `short` | Index du slot affecté |
| `getAction()` | `ActionType` | Type d'action effectuée |
| `getSlotBefore()` | `ItemStack?` | Contenu avant l'opération |
| `getSlotAfter()` | `ItemStack?` | Contenu après l'opération |
| `getOutput()` | `ItemStack?` | Objets retirés/sortis |
| `isAllOrNothing()` | `boolean` | Paramètre allOrNothing utilisé |
| `isExactAmount()` | `boolean` | Paramètre exactAmount utilisé |
| `isFilter()` | `boolean` | Paramètre filter utilisé |
| Statique | Type | Description |
|----------|------|-------------|
| `FAILED_ADD` | `SlotTransaction` | Transaction d'ajout échouée pré-construite |
### ItemStackSlotTransaction (étend SlotTransaction)
| Méthode | Retour | Description |
|--------|---------|-------------|
| `isAddToExistingSlot()` | `boolean` | True si ajouté à une pile existante |
| `getQuery()` | `ItemStack?` | Objet original demandé |
| `getRemainder()` | `ItemStack?` | Objets qui n'ont pas pu rentrer |
### ItemStackTransaction
| Méthode | Retour | Description |
|--------|---------|-------------|
| `getAction()` | `ActionType?` | Type d'action effectuée |
| `getQuery()` | `ItemStack?` | Objet original demandé |
| `getRemainder()` | `ItemStack?` | Objets qui n'ont pas pu rentrer |
| `isAllOrNothing()` | `boolean` | Paramètre allOrNothing utilisé |
| `isFilter()` | `boolean` | Paramètre filter utilisé |
| `getSlotTransactions()` | `List<ItemStackSlotTransaction>` | Toutes les transactions de slot |
| Statique | Type | Description |
|----------|------|-------------|
| `FAILED_ADD` | `ItemStackTransaction` | Transaction d'ajout échouée pré-construite |
### MoveTransaction<T extends Transaction>
| Méthode | Retour | Description |
|--------|---------|-------------|
| `getRemoveTransaction()` | `SlotTransaction` | Transaction de retrait |
| `getMoveType()` | `MoveType` | Direction du déplacement |
| `getOtherContainer()` | `ItemContainer` | L'autre conteneur impliqué |
| `getAddTransaction()` | `T` | Transaction d'ajout |
| `toInverted(ItemContainer)` | `MoveTransaction<T>` | Créer vue inversée pour l'autre conteneur |
### ListTransaction<T extends Transaction>
| Méthode | Retour | Description |
|--------|---------|-------------|
| `getList()` | `List<T>` | Toutes les transactions de la liste |
| `size()` | `int` | Nombre de transactions |
| Statique | Type | Description |
|----------|------|-------------|
| `EMPTY_SUCCESSFUL_TRANSACTION` | `ListTransaction<?>` | Succès vide pré-construit |
| `EMPTY_FAILED_TRANSACTION` | `ListTransaction<?>` | Échec vide pré-construit |
| `getEmptyTransaction(boolean)` | `ListTransaction<T>` | Obtenir succès/échec vide |
### Enum ActionType
| Valeur | isAdd | isRemove | isDestroy | Description |
|--------|-------|----------|-----------|-------------|
| `SET` | true | false | true | Définir le contenu du slot |
| `ADD` | true | false | false | Ajouter des objets au slot |
| `REMOVE` | false | true | false | Retirer des objets du slot |
| `REPLACE` | true | true | false | Remplacer le contenu du slot |
### Enum MoveType
| Valeur | Description |
|--------|-------------|
| `MOVE_TO_SELF` | Objets déplacés vers ce conteneur |
| `MOVE_FROM_SELF` | Objets déplacés depuis ce conteneur |

View File

@@ -0,0 +1,291 @@
---
title: Mounts
type: docs
weight: 11
---
The mount system allows entities to ride other entities or sit on blocks.
**Package:** `com.hypixel.hytale.builtin.mounts`
## Overview
The mount system supports:
- Riding NPC entities
- Sitting on block-based seats
- Minecart vehicle mechanics
- Mount-specific movement controls
## Architecture
```
Mount System
├── Components
│ ├── NPCMountComponent - Mount NPC configuration
│ ├── MountedComponent - "I am riding something"
│ ├── MountedByComponent - "Something rides me"
│ ├── BlockMountComponent - Block seat
│ └── MinecartComponent - Minecart vehicle
├── Systems
│ ├── MountSystems - Core mount logic
│ └── NPCMountSystems - NPC-specific logic
├── Interactions
│ ├── MountInteraction - Mount NPC
│ ├── SeatingInteraction - Sit on block
│ └── SpawnMinecartInteraction - Create minecart
└── Commands
├── MountCommand - Force mount
├── MountCheckCommand - Check status
└── DismountCommand - Force dismount
```
## Components
### NPCMountComponent
Attached to NPCs that can be mounted:
```java
public class NPCMountComponent implements Component<EntityStore> {
// Player currently riding (if any)
private PlayerRef ownerPlayerRef;
// Original NPC role before mounting
private int originalRoleIndex;
// Mount configuration
// Movement speeds, abilities, etc.
}
```
### MountedComponent
Attached to entities that are riding something:
```java
public class MountedComponent implements Component<EntityStore> {
// Reference to what we're riding
private Ref<EntityStore> mountRef;
}
```
### MountedByComponent
Attached to entities being ridden:
```java
public class MountedByComponent implements Component<EntityStore> {
// Reference to the rider
private Ref<EntityStore> riderRef;
}
```
### BlockMountComponent
Chunk component for block-based seating:
```java
public class BlockMountComponent implements Component<ChunkStore> {
// Block position and configuration
// Player currently seated
}
```
### MinecartComponent
Vehicle component for minecarts:
```java
public class MinecartComponent implements Component<EntityStore> {
// Rail movement configuration
// Speed, acceleration, etc.
}
```
## Mounting NPCs
### Mount Interaction
Players mount NPCs via interaction:
```java
// Registered as "Mount" interaction type
Interaction.CODEC.register("Mount",
MountInteraction.class,
MountInteraction.CODEC);
```
### Mounting Process
1. Player interacts with mountable NPC
2. `NPCMountComponent` is added/configured
3. `MountedComponent` added to player
4. `MountedByComponent` added to NPC
5. NPC role changes to "mounted" behavior
6. Player movement settings updated
### Dismounting
Dismounting can happen:
- Player manually dismounts
- Player disconnects
- Mount or player dies
- Command forces dismount
```java
// Dismount NPC
MountPlugin.dismountNpc(store, mountEntityId);
// Reset player movement settings
MountPlugin.resetOriginalPlayerMovementSettings(playerRef, store);
```
## Block Seating
### Seating Interaction
Players sit on blocks via interaction:
```java
// Registered as "Seating" interaction type
Interaction.CODEC.register("Seating",
SeatingInteraction.class,
SeatingInteraction.CODEC);
```
### Block Mount API
```java
BlockMountAPI api = BlockMountAPI.get();
// Check if block is a seat
boolean isSeat = api.isSeat(world, blockPos);
// Get seated player
PlayerRef seated = api.getSeatedPlayer(world, blockPos);
```
## Minecarts
### Spawn Minecart Interaction
Create minecarts via interaction:
```java
// Registered as "SpawnMinecart" interaction type
Interaction.CODEC.register("SpawnMinecart",
SpawnMinecartInteraction.class,
SpawnMinecartInteraction.CODEC);
```
### Minecart Behavior
- Follows rail tracks
- Configurable speed and acceleration
- Can carry players and items
- Collision with other minecarts
## NPC Core Component
NPCs can use mount actions:
```java
// Registered core component type
NPCPlugin.get().registerCoreComponentType("Mount", BuilderActionMount::new);
```
```json
{
"Type": "Mount",
"MountTarget": "player",
"Duration": 10.0
}
```
## Commands
### /mount
Force mount a player on an NPC:
```
/mount <player> <npc_selector>
```
### /mountcheck
Check mount status:
```
/mountcheck <player>
```
### /dismount
Force dismount a player:
```
/dismount <player>
```
## Plugin Access
```java
MountPlugin mounts = MountPlugin.getInstance();
// Component types
ComponentType<EntityStore, NPCMountComponent> npcMountType =
mounts.getMountComponentType();
ComponentType<EntityStore, MountedComponent> mountedType =
mounts.getMountedComponentType();
ComponentType<EntityStore, MountedByComponent> mountedByType =
mounts.getMountedByComponentType();
ComponentType<EntityStore, MinecartComponent> minecartType =
mounts.getMinecartComponentType();
ComponentType<ChunkStore, BlockMountComponent> blockMountType =
mounts.getBlockMountComponentType();
```
## Systems
### Mount Tracking
```java
// Update mount position tracking
MountSystems.TrackerUpdate
// Remove mount tracking on entity removal
MountSystems.TrackerRemove
```
### Death Handling
```java
// Dismount player when they die
NPCMountSystems.DismountOnPlayerDeath
// Dismount when mount dies
NPCMountSystems.DismountOnMountDeath
```
### Movement
```java
// Handle player mount input
MountSystems.HandleMountInput
// Teleport mounted entity with mount
MountSystems.TeleportMountedEntity
```
## Events
### Player Disconnect
When player disconnects while mounted:
```java
// Automatically dismount on disconnect
getEventRegistry().register(PlayerDisconnectEvent.class,
MountPlugin::onPlayerDisconnect);
```

View File

@@ -0,0 +1,291 @@
---
title: Montures
type: docs
weight: 11
---
Le systeme de montures permet aux entites de chevaucher d'autres entites ou s'asseoir sur des blocs.
**Package:** `com.hypixel.hytale.builtin.mounts`
## Apercu
Le systeme de montures supporte:
- Chevaucher des entites NPC
- S'asseoir sur des sieges bases sur blocs
- Mecaniques de vehicules minecart
- Controles de mouvement specifiques aux montures
## Architecture
```
Systeme Montures
├── Composants
│ ├── NPCMountComponent - Config monture NPC
│ ├── MountedComponent - "Je chevauche quelque chose"
│ ├── MountedByComponent - "Quelque chose me chevauche"
│ ├── BlockMountComponent - Siege bloc
│ └── MinecartComponent - Vehicule minecart
├── Systemes
│ ├── MountSystems - Logique monture principale
│ └── NPCMountSystems - Logique specifique NPC
├── Interactions
│ ├── MountInteraction - Monter NPC
│ ├── SeatingInteraction - S'asseoir sur bloc
│ └── SpawnMinecartInteraction - Creer minecart
└── Commandes
├── MountCommand - Forcer montage
├── MountCheckCommand - Verifier statut
└── DismountCommand - Forcer demontage
```
## Composants
### NPCMountComponent
Attache aux NPCs qui peuvent etre montes:
```java
public class NPCMountComponent implements Component<EntityStore> {
// Joueur chevauchant actuellement (si present)
private PlayerRef ownerPlayerRef;
// Role NPC original avant montage
private int originalRoleIndex;
// Configuration monture
// Vitesses mouvement, capacites, etc.
}
```
### MountedComponent
Attache aux entites qui chevauchent quelque chose:
```java
public class MountedComponent implements Component<EntityStore> {
// Reference vers ce qu'on chevauche
private Ref<EntityStore> mountRef;
}
```
### MountedByComponent
Attache aux entites etant chevauchees:
```java
public class MountedByComponent implements Component<EntityStore> {
// Reference vers le cavalier
private Ref<EntityStore> riderRef;
}
```
### BlockMountComponent
Composant chunk pour sieges bases sur blocs:
```java
public class BlockMountComponent implements Component<ChunkStore> {
// Position bloc et configuration
// Joueur actuellement assis
}
```
### MinecartComponent
Composant vehicule pour minecarts:
```java
public class MinecartComponent implements Component<EntityStore> {
// Configuration mouvement rails
// Vitesse, acceleration, etc.
}
```
## Monter des NPCs
### Interaction Monture
Les joueurs montent les NPCs via interaction:
```java
// Enregistre comme type interaction "Mount"
Interaction.CODEC.register("Mount",
MountInteraction.class,
MountInteraction.CODEC);
```
### Processus de Montage
1. Joueur interagit avec NPC montable
2. `NPCMountComponent` est ajoute/configure
3. `MountedComponent` ajoute au joueur
4. `MountedByComponent` ajoute au NPC
5. Role NPC change vers comportement "monte"
6. Parametres mouvement joueur mis a jour
### Demontage
Le demontage peut survenir:
- Joueur demonte manuellement
- Joueur se deconnecte
- Monture ou joueur meurt
- Commande force demontage
```java
// Demonter NPC
MountPlugin.dismountNpc(store, mountEntityId);
// Reinitialiser parametres mouvement joueur
MountPlugin.resetOriginalPlayerMovementSettings(playerRef, store);
```
## Sieges de Blocs
### Interaction Siege
Les joueurs s'assoient sur blocs via interaction:
```java
// Enregistre comme type interaction "Seating"
Interaction.CODEC.register("Seating",
SeatingInteraction.class,
SeatingInteraction.CODEC);
```
### API Monture Bloc
```java
BlockMountAPI api = BlockMountAPI.get();
// Verifier si bloc est un siege
boolean isSeat = api.isSeat(world, blockPos);
// Obtenir joueur assis
PlayerRef seated = api.getSeatedPlayer(world, blockPos);
```
## Minecarts
### Interaction Spawn Minecart
Creer minecarts via interaction:
```java
// Enregistre comme type interaction "SpawnMinecart"
Interaction.CODEC.register("SpawnMinecart",
SpawnMinecartInteraction.class,
SpawnMinecartInteraction.CODEC);
```
### Comportement Minecart
- Suit les rails
- Vitesse et acceleration configurables
- Peut transporter joueurs et items
- Collision avec autres minecarts
## Composant Core NPC
Les NPCs peuvent utiliser actions de monture:
```java
// Type composant core enregistre
NPCPlugin.get().registerCoreComponentType("Mount", BuilderActionMount::new);
```
```json
{
"Type": "Mount",
"MountTarget": "player",
"Duration": 10.0
}
```
## Commandes
### /mount
Forcer un joueur a monter un NPC:
```
/mount <joueur> <selecteur_npc>
```
### /mountcheck
Verifier statut monture:
```
/mountcheck <joueur>
```
### /dismount
Forcer demontage d'un joueur:
```
/dismount <joueur>
```
## Acces au Plugin
```java
MountPlugin mounts = MountPlugin.getInstance();
// Types de composants
ComponentType<EntityStore, NPCMountComponent> npcMountType =
mounts.getMountComponentType();
ComponentType<EntityStore, MountedComponent> mountedType =
mounts.getMountedComponentType();
ComponentType<EntityStore, MountedByComponent> mountedByType =
mounts.getMountedByComponentType();
ComponentType<EntityStore, MinecartComponent> minecartType =
mounts.getMinecartComponentType();
ComponentType<ChunkStore, BlockMountComponent> blockMountType =
mounts.getBlockMountComponentType();
```
## Systemes
### Suivi Monture
```java
// Mettre a jour suivi position monture
MountSystems.TrackerUpdate
// Supprimer suivi monture a suppression entite
MountSystems.TrackerRemove
```
### Gestion Mort
```java
// Demonter joueur quand il meurt
NPCMountSystems.DismountOnPlayerDeath
// Demonter quand monture meurt
NPCMountSystems.DismountOnMountDeath
```
### Mouvement
```java
// Gerer input joueur sur monture
MountSystems.HandleMountInput
// Teleporter entite montee avec monture
MountSystems.TeleportMountedEntity
```
## Evenements
### Deconnexion Joueur
Quand joueur se deconnecte en montant:
```java
// Demonter automatiquement a deconnexion
getEventRegistry().register(PlayerDisconnectEvent.class,
MountPlugin::onPlayerDisconnect);
```

View File

@@ -0,0 +1,86 @@
---
title: NPC System
type: docs
weight: 10
---
The NPC (Non-Player Character) system provides a complete framework for creating intelligent game characters with autonomous behavior, decision-making, and navigation capabilities.
**Package:** `com.hypixel.hytale.server.npc`
{{< cards >}}
{{< card link="npc-basics" title="NPC Basics" subtitle="Creating and configuring NPCs" >}}
{{< card link="npc-components" title="NPC Components" subtitle="Core components and data" >}}
{{< card link="npc-ai" title="NPC AI" subtitle="Blackboard, decisions, and sensors" >}}
{{< card link="npc-movement" title="NPC Movement" subtitle="Navigation and pathfinding" >}}
{{< card link="npc-commands" title="NPC Commands" subtitle="Admin and debug commands" >}}
{{< /cards >}}
## Architecture Overview
The NPC system is built on several interconnected subsystems:
```
NPCEntity
├── Blackboard (shared state/memory)
├── Role (behavioral template)
│ ├── Instructions (high-level goals)
│ ├── Sensors (perception)
│ └── Actions (behaviors)
├── DecisionMaker (AI logic)
│ ├── Evaluators (condition checking)
│ └── Options (action selection)
└── Movement
├── MotionController (movement execution)
├── PathFollower (path tracking)
└── NavigationGraph (A* pathfinding)
```
## Quick Example
```java
// Register NPC-related event
getEventRegistry().register(NPCSpawnEvent.class, event -> {
NPCEntity npc = event.getNPC();
// Access blackboard for state
Blackboard blackboard = npc.getBlackboard();
// Get current role
Role role = npc.getRole();
// Check if NPC has target
if (blackboard.hasTarget()) {
Entity target = blackboard.getTarget();
}
});
```
## Key Classes
| Class | Description |
|-------|-------------|
| `NPCEntity` | Base class for all NPCs |
| `NPCPlugin` | Plugin entry point for NPC system |
| `Blackboard` | Shared state container for NPC data |
| `Role` | Defines NPC behavior template |
| `DecisionMaker` | AI decision logic |
| `MotionController` | Movement execution |
| `PathFollower` | Path tracking and following |
## Subpackages
| Package | Files | Description |
|---------|-------|-------------|
| `corecomponents/` | 327 | Core ECS components for NPCs |
| `asset/` | 152 | NPC asset configuration |
| `util/` | 50 | Utility classes |
| `blackboard/` | 30 | State management |
| `movement/` | 27 | Movement behaviors |
| `systems/` | 25 | ECS systems |
| `commands/` | 23 | Admin commands |
| `decisionmaker/` | 22 | AI decision logic |
| `sensorinfo/` | 20 | Perception system |
| `role/` | 17 | Role definitions |
| `instructions/` | 14 | High-level behaviors |
| `navigation/` | 12 | Pathfinding |

View File

@@ -0,0 +1,86 @@
---
title: Système NPC
type: docs
weight: 10
---
Le système NPC (Non-Player Character) fournit un framework complet pour créer des personnages de jeu intelligents avec un comportement autonome, une prise de décision et des capacités de navigation.
**Package:** `com.hypixel.hytale.server.npc`
{{< cards >}}
{{< card link="npc-basics" title="Bases NPC" subtitle="Création et configuration des NPCs" >}}
{{< card link="npc-components" title="Composants NPC" subtitle="Composants de base et données" >}}
{{< card link="npc-ai" title="IA NPC" subtitle="Blackboard, décisions et capteurs" >}}
{{< card link="npc-movement" title="Mouvement NPC" subtitle="Navigation et pathfinding" >}}
{{< card link="npc-commands" title="Commandes NPC" subtitle="Commandes admin et debug" >}}
{{< /cards >}}
## Vue d'Ensemble de l'Architecture
Le système NPC est construit sur plusieurs sous-systèmes interconnectés :
```
NPCEntity
├── Blackboard (état/mémoire partagé)
├── Role (template comportemental)
│ ├── Instructions (objectifs haut niveau)
│ ├── Sensors (perception)
│ └── Actions (comportements)
├── DecisionMaker (logique IA)
│ ├── Evaluators (vérification conditions)
│ └── Options (sélection actions)
└── Movement
├── MotionController (exécution mouvement)
├── PathFollower (suivi de chemin)
└── NavigationGraph (pathfinding A*)
```
## Exemple Rapide
```java
// Enregistrer un événement lié aux NPCs
getEventRegistry().register(NPCSpawnEvent.class, event -> {
NPCEntity npc = event.getNPC();
// Accéder au blackboard pour l'état
Blackboard blackboard = npc.getBlackboard();
// Obtenir le rôle actuel
Role role = npc.getRole();
// Vérifier si le NPC a une cible
if (blackboard.hasTarget()) {
Entity target = blackboard.getTarget();
}
});
```
## Classes Principales
| Classe | Description |
|--------|-------------|
| `NPCEntity` | Classe de base pour tous les NPCs |
| `NPCPlugin` | Point d'entrée plugin pour le système NPC |
| `Blackboard` | Conteneur d'état partagé pour les données NPC |
| `Role` | Définit le template de comportement NPC |
| `DecisionMaker` | Logique de décision IA |
| `MotionController` | Exécution des mouvements |
| `PathFollower` | Suivi et parcours de chemin |
## Sous-packages
| Package | Fichiers | Description |
|---------|----------|-------------|
| `corecomponents/` | 327 | Composants ECS de base pour NPCs |
| `asset/` | 152 | Configuration des assets NPC |
| `util/` | 50 | Classes utilitaires |
| `blackboard/` | 30 | Gestion d'état |
| `movement/` | 27 | Comportements de mouvement |
| `systems/` | 25 | Systèmes ECS |
| `commands/` | 23 | Commandes admin |
| `decisionmaker/` | 22 | Logique de décision IA |
| `sensorinfo/` | 20 | Système de perception |
| `role/` | 17 | Définitions de rôles |
| `instructions/` | 14 | Comportements haut niveau |
| `navigation/` | 12 | Pathfinding |

View File

@@ -0,0 +1,373 @@
---
title: NPC AI
type: docs
weight: 3
---
The NPC AI system provides intelligent decision-making through blackboards, decision makers, sensors, and instructions.
**Packages:**
- `com.hypixel.hytale.server.npc.blackboard`
- `com.hypixel.hytale.server.npc.decisionmaker`
- `com.hypixel.hytale.server.npc.sensorinfo`
- `com.hypixel.hytale.server.npc.instructions`
## Blackboard System
The Blackboard is a shared memory space where NPC components communicate through key-value pairs.
### Blackboard Class
```java
public class Blackboard {
private Map<BlackboardKey<?>, Object> data;
// Store value
public <T> void set(BlackboardKey<T> key, T value);
// Retrieve value
public <T> T get(BlackboardKey<T> key);
public <T> T getOrDefault(BlackboardKey<T> key, T defaultValue);
// Check existence
public boolean has(BlackboardKey<?> key);
// Remove value
public void remove(BlackboardKey<?> key);
// Clear all
public void clear();
}
```
### BlackboardKey
Type-safe keys for blackboard access:
```java
// Predefined keys
public class BlackboardKeys {
public static final BlackboardKey<Entity> TARGET =
new BlackboardKey<>("target", Entity.class);
public static final BlackboardKey<Vector3d> HOME_POSITION =
new BlackboardKey<>("home_position", Vector3d.class);
public static final BlackboardKey<Float> ALERT_LEVEL =
new BlackboardKey<>("alert_level", Float.class);
public static final BlackboardKey<Boolean> IN_COMBAT =
new BlackboardKey<>("in_combat", Boolean.class);
}
// Custom keys
BlackboardKey<String> CUSTOM_KEY = new BlackboardKey<>("custom_data", String.class);
```
### Using the Blackboard
```java
NPCEntity npc = // get NPC
Blackboard bb = npc.getBlackboard();
// Set target
bb.set(BlackboardKeys.TARGET, targetEntity);
// Get home position
Vector3d home = bb.getOrDefault(BlackboardKeys.HOME_POSITION, npc.getPosition());
// Check combat status
if (bb.getOrDefault(BlackboardKeys.IN_COMBAT, false)) {
// Handle combat
}
```
## Decision Maker System
The Decision Maker evaluates options and selects the best action for the NPC to take.
### DecisionMaker Interface
```java
public interface DecisionMaker {
// Evaluate and select best option
Option evaluate(NPCEntity npc, Blackboard blackboard);
// Get all available options
List<Option> getOptions();
// Add option
void addOption(Option option);
}
```
### Option Class
Options represent possible actions:
```java
public class Option {
private String id;
private Evaluator evaluator;
private Action action;
private float basePriority;
// Calculate score based on context
public float evaluate(NPCEntity npc, Blackboard blackboard);
// Execute the action
public void execute(NPCEntity npc, Blackboard blackboard);
}
```
### Evaluator Interface
Evaluators calculate option scores:
```java
public interface Evaluator {
// Return score from 0.0 to 1.0
float evaluate(NPCEntity npc, Blackboard blackboard);
}
// Built-in evaluators
public class Evaluators {
// Returns 1.0 if target exists
public static final Evaluator HAS_TARGET = (npc, bb) ->
bb.has(BlackboardKeys.TARGET) ? 1.0f : 0.0f;
// Returns health percentage
public static final Evaluator HEALTH_PERCENT = (npc, bb) ->
npc.getHealth() / npc.getMaxHealth();
// Returns 1.0 if at home
public static final Evaluator AT_HOME = (npc, bb) -> {
Vector3d home = bb.get(BlackboardKeys.HOME_POSITION);
return npc.getPosition().distance(home) < 5.0 ? 1.0f : 0.0f;
};
}
```
### Creating a Decision Maker
```java
DecisionMaker dm = new StandardDecisionMaker();
// Add attack option
dm.addOption(new Option(
"attack",
Evaluators.HAS_TARGET,
new AttackAction(),
10.0f // High priority
));
// Add flee option
dm.addOption(new Option(
"flee",
(npc, bb) -> npc.getHealth() < 20 ? 1.0f : 0.0f,
new FleeAction(),
15.0f // Higher priority when triggered
));
// Add wander option
dm.addOption(new Option(
"wander",
(npc, bb) -> 0.3f, // Low constant score
new WanderAction(),
1.0f // Low priority fallback
));
npc.setDecisionMaker(dm);
```
## Sensor System
Sensors gather information about the world and update the blackboard.
### Sensor Interface
```java
public interface Sensor {
// Process sensor input
void sense(NPCEntity npc, Blackboard blackboard, float deltaTime);
// Get sensor type
String getSensorType();
}
```
### SensorInfo Classes
Sensor information containers:
```java
// Visual detection info
public class VisualSensorInfo {
private List<Entity> visibleEntities;
private float detectionRange;
private float fieldOfView;
public List<Entity> getVisibleEntities();
public boolean canSee(Entity entity);
}
// Audio detection info
public class AudioSensorInfo {
private List<SoundEvent> heardSounds;
private float hearingRange;
public List<SoundEvent> getHeardSounds();
public Vector3d getLoudestSoundPosition();
}
// Threat detection info
public class ThreatSensorInfo {
private List<Entity> threats;
private Entity primaryThreat;
private float threatLevel;
}
```
### Built-in Sensors
```java
// Visual sensor - detects visible entities
public class VisualSensor implements Sensor {
private float range = 20.0f;
private float fov = 120.0f; // degrees
@Override
public void sense(NPCEntity npc, Blackboard bb, float dt) {
List<Entity> visible = findVisibleEntities(npc);
bb.set(BlackboardKeys.VISIBLE_ENTITIES, visible);
}
}
// Proximity sensor - detects nearby entities
public class ProximitySensor implements Sensor {
private float range = 5.0f;
@Override
public void sense(NPCEntity npc, Blackboard bb, float dt) {
List<Entity> nearby = findNearbyEntities(npc, range);
bb.set(BlackboardKeys.NEARBY_ENTITIES, nearby);
}
}
// Damage sensor - reacts to damage taken
public class DamageSensor implements Sensor {
@Override
public void sense(NPCEntity npc, Blackboard bb, float dt) {
if (npc.wasRecentlyDamaged()) {
bb.set(BlackboardKeys.LAST_ATTACKER, npc.getLastAttacker());
bb.set(BlackboardKeys.ALERT_LEVEL, 1.0f);
}
}
}
```
## Instruction System
Instructions define high-level behavioral goals.
### Instruction Interface
```java
public interface Instruction {
// Check if instruction should activate
boolean shouldActivate(NPCEntity npc, Blackboard blackboard);
// Execute instruction logic
void execute(NPCEntity npc, Blackboard blackboard, float deltaTime);
// Check if instruction is complete
boolean isComplete(NPCEntity npc, Blackboard blackboard);
// Get priority
float getPriority();
}
```
### Built-in Instructions
```java
// Wander instruction
public class WanderInstruction implements Instruction {
private float wanderRadius;
private float minWaitTime;
private float maxWaitTime;
@Override
public boolean shouldActivate(NPCEntity npc, Blackboard bb) {
return !bb.has(BlackboardKeys.TARGET);
}
}
// Guard instruction
public class GuardInstruction implements Instruction {
private Vector3d guardPosition;
private float guardRadius;
@Override
public void execute(NPCEntity npc, Blackboard bb, float dt) {
if (npc.getPosition().distance(guardPosition) > guardRadius) {
bb.set(BlackboardKeys.MOVE_TARGET, guardPosition);
}
}
}
// Follow instruction
public class FollowInstruction implements Instruction {
private Entity followTarget;
private float followDistance;
}
// Patrol instruction
public class PatrolInstruction implements Instruction {
private List<Vector3d> patrolPoints;
private int currentPoint;
}
```
## Combining AI Systems
```java
public void setupNPCAI(NPCEntity npc) {
// Configure blackboard
Blackboard bb = npc.getBlackboard();
bb.set(BlackboardKeys.HOME_POSITION, npc.getPosition());
bb.set(BlackboardKeys.AGGRO_RANGE, 15.0f);
// Add sensors
npc.addSensor(new VisualSensor(20.0f, 120.0f));
npc.addSensor(new ProximitySensor(5.0f));
npc.addSensor(new DamageSensor());
// Configure decision maker
DecisionMaker dm = new StandardDecisionMaker();
dm.addOption(new Option("attack", hasHostileTarget, attackAction, 10.0f));
dm.addOption(new Option("flee", lowHealth, fleeAction, 15.0f));
dm.addOption(new Option("patrol", isGuard, patrolAction, 5.0f));
dm.addOption(new Option("idle", always, idleAction, 1.0f));
npc.setDecisionMaker(dm);
// Add instructions
npc.addInstruction(new GuardInstruction(guardPost, 10.0f));
npc.addInstruction(new ReactToThreatInstruction());
}
```
## Best Practices
{{< callout type="info" >}}
**AI Guidelines:**
- Use the Blackboard for all inter-component communication
- Keep Evaluators simple and fast - they run frequently
- Use appropriate sensor ranges to balance awareness vs performance
- Design Instructions to be interruptible
- Test AI behavior with various scenarios
{{< /callout >}}
{{< callout type="warning" >}}
**Performance:** Large numbers of NPCs with complex AI can impact performance. Consider:
- Reducing sensor update frequency for distant NPCs
- Using LOD (Level of Detail) for AI complexity
- Limiting pathfinding requests
{{< /callout >}}

View File

@@ -0,0 +1,373 @@
---
title: IA NPC
type: docs
weight: 3
---
Le système d'IA NPC fournit une prise de décision intelligente via les blackboards, decision makers, capteurs et instructions.
**Packages:**
- `com.hypixel.hytale.server.npc.blackboard`
- `com.hypixel.hytale.server.npc.decisionmaker`
- `com.hypixel.hytale.server.npc.sensorinfo`
- `com.hypixel.hytale.server.npc.instructions`
## Système Blackboard
Le Blackboard est un espace mémoire partagé où les composants NPC communiquent via des paires clé-valeur.
### Classe Blackboard
```java
public class Blackboard {
private Map<BlackboardKey<?>, Object> data;
// Stocker une valeur
public <T> void set(BlackboardKey<T> key, T value);
// Récupérer une valeur
public <T> T get(BlackboardKey<T> key);
public <T> T getOrDefault(BlackboardKey<T> key, T defaultValue);
// Vérifier l'existence
public boolean has(BlackboardKey<?> key);
// Supprimer une valeur
public void remove(BlackboardKey<?> key);
// Tout effacer
public void clear();
}
```
### BlackboardKey
Clés typées pour l'accès au blackboard :
```java
// Clés prédéfinies
public class BlackboardKeys {
public static final BlackboardKey<Entity> TARGET =
new BlackboardKey<>("target", Entity.class);
public static final BlackboardKey<Vector3d> HOME_POSITION =
new BlackboardKey<>("home_position", Vector3d.class);
public static final BlackboardKey<Float> ALERT_LEVEL =
new BlackboardKey<>("alert_level", Float.class);
public static final BlackboardKey<Boolean> IN_COMBAT =
new BlackboardKey<>("in_combat", Boolean.class);
}
// Clés personnalisées
BlackboardKey<String> CUSTOM_KEY = new BlackboardKey<>("custom_data", String.class);
```
### Utiliser le Blackboard
```java
NPCEntity npc = // obtenir le NPC
Blackboard bb = npc.getBlackboard();
// Définir la cible
bb.set(BlackboardKeys.TARGET, targetEntity);
// Obtenir la position d'origine
Vector3d home = bb.getOrDefault(BlackboardKeys.HOME_POSITION, npc.getPosition());
// Vérifier le statut de combat
if (bb.getOrDefault(BlackboardKeys.IN_COMBAT, false)) {
// Gérer le combat
}
```
## Système Decision Maker
Le Decision Maker évalue les options et sélectionne la meilleure action pour le NPC.
### Interface DecisionMaker
```java
public interface DecisionMaker {
// Évaluer et sélectionner la meilleure option
Option evaluate(NPCEntity npc, Blackboard blackboard);
// Obtenir toutes les options disponibles
List<Option> getOptions();
// Ajouter une option
void addOption(Option option);
}
```
### Classe Option
Les options représentent les actions possibles :
```java
public class Option {
private String id;
private Evaluator evaluator;
private Action action;
private float basePriority;
// Calculer le score selon le contexte
public float evaluate(NPCEntity npc, Blackboard blackboard);
// Exécuter l'action
public void execute(NPCEntity npc, Blackboard blackboard);
}
```
### Interface Evaluator
Les évaluateurs calculent les scores des options :
```java
public interface Evaluator {
// Retourne un score de 0.0 à 1.0
float evaluate(NPCEntity npc, Blackboard blackboard);
}
// Évaluateurs intégrés
public class Evaluators {
// Retourne 1.0 si une cible existe
public static final Evaluator HAS_TARGET = (npc, bb) ->
bb.has(BlackboardKeys.TARGET) ? 1.0f : 0.0f;
// Retourne le pourcentage de vie
public static final Evaluator HEALTH_PERCENT = (npc, bb) ->
npc.getHealth() / npc.getMaxHealth();
// Retourne 1.0 si à la maison
public static final Evaluator AT_HOME = (npc, bb) -> {
Vector3d home = bb.get(BlackboardKeys.HOME_POSITION);
return npc.getPosition().distance(home) < 5.0 ? 1.0f : 0.0f;
};
}
```
### Créer un Decision Maker
```java
DecisionMaker dm = new StandardDecisionMaker();
// Ajouter option d'attaque
dm.addOption(new Option(
"attack",
Evaluators.HAS_TARGET,
new AttackAction(),
10.0f // Haute priorité
));
// Ajouter option de fuite
dm.addOption(new Option(
"flee",
(npc, bb) -> npc.getHealth() < 20 ? 1.0f : 0.0f,
new FleeAction(),
15.0f // Priorité plus haute quand déclenché
));
// Ajouter option d'errance
dm.addOption(new Option(
"wander",
(npc, bb) -> 0.3f, // Score constant bas
new WanderAction(),
1.0f // Basse priorité par défaut
));
npc.setDecisionMaker(dm);
```
## Système de Capteurs
Les capteurs collectent des informations sur le monde et mettent à jour le blackboard.
### Interface Sensor
```java
public interface Sensor {
// Traiter l'entrée du capteur
void sense(NPCEntity npc, Blackboard blackboard, float deltaTime);
// Obtenir le type de capteur
String getSensorType();
}
```
### Classes SensorInfo
Conteneurs d'informations de capteurs :
```java
// Info de détection visuelle
public class VisualSensorInfo {
private List<Entity> visibleEntities;
private float detectionRange;
private float fieldOfView;
public List<Entity> getVisibleEntities();
public boolean canSee(Entity entity);
}
// Info de détection audio
public class AudioSensorInfo {
private List<SoundEvent> heardSounds;
private float hearingRange;
public List<SoundEvent> getHeardSounds();
public Vector3d getLoudestSoundPosition();
}
// Info de détection de menace
public class ThreatSensorInfo {
private List<Entity> threats;
private Entity primaryThreat;
private float threatLevel;
}
```
### Capteurs Intégrés
```java
// Capteur visuel - détecte les entités visibles
public class VisualSensor implements Sensor {
private float range = 20.0f;
private float fov = 120.0f; // degrés
@Override
public void sense(NPCEntity npc, Blackboard bb, float dt) {
List<Entity> visible = findVisibleEntities(npc);
bb.set(BlackboardKeys.VISIBLE_ENTITIES, visible);
}
}
// Capteur de proximité - détecte les entités proches
public class ProximitySensor implements Sensor {
private float range = 5.0f;
@Override
public void sense(NPCEntity npc, Blackboard bb, float dt) {
List<Entity> nearby = findNearbyEntities(npc, range);
bb.set(BlackboardKeys.NEARBY_ENTITIES, nearby);
}
}
// Capteur de dégâts - réagit aux dégâts subis
public class DamageSensor implements Sensor {
@Override
public void sense(NPCEntity npc, Blackboard bb, float dt) {
if (npc.wasRecentlyDamaged()) {
bb.set(BlackboardKeys.LAST_ATTACKER, npc.getLastAttacker());
bb.set(BlackboardKeys.ALERT_LEVEL, 1.0f);
}
}
}
```
## Système d'Instructions
Les instructions définissent des objectifs comportementaux de haut niveau.
### Interface Instruction
```java
public interface Instruction {
// Vérifier si l'instruction doit s'activer
boolean shouldActivate(NPCEntity npc, Blackboard blackboard);
// Exécuter la logique de l'instruction
void execute(NPCEntity npc, Blackboard blackboard, float deltaTime);
// Vérifier si l'instruction est complète
boolean isComplete(NPCEntity npc, Blackboard blackboard);
// Obtenir la priorité
float getPriority();
}
```
### Instructions Intégrées
```java
// Instruction d'errance
public class WanderInstruction implements Instruction {
private float wanderRadius;
private float minWaitTime;
private float maxWaitTime;
@Override
public boolean shouldActivate(NPCEntity npc, Blackboard bb) {
return !bb.has(BlackboardKeys.TARGET);
}
}
// Instruction de garde
public class GuardInstruction implements Instruction {
private Vector3d guardPosition;
private float guardRadius;
@Override
public void execute(NPCEntity npc, Blackboard bb, float dt) {
if (npc.getPosition().distance(guardPosition) > guardRadius) {
bb.set(BlackboardKeys.MOVE_TARGET, guardPosition);
}
}
}
// Instruction de suivi
public class FollowInstruction implements Instruction {
private Entity followTarget;
private float followDistance;
}
// Instruction de patrouille
public class PatrolInstruction implements Instruction {
private List<Vector3d> patrolPoints;
private int currentPoint;
}
```
## Combiner les Systèmes d'IA
```java
public void setupNPCAI(NPCEntity npc) {
// Configurer le blackboard
Blackboard bb = npc.getBlackboard();
bb.set(BlackboardKeys.HOME_POSITION, npc.getPosition());
bb.set(BlackboardKeys.AGGRO_RANGE, 15.0f);
// Ajouter les capteurs
npc.addSensor(new VisualSensor(20.0f, 120.0f));
npc.addSensor(new ProximitySensor(5.0f));
npc.addSensor(new DamageSensor());
// Configurer le decision maker
DecisionMaker dm = new StandardDecisionMaker();
dm.addOption(new Option("attack", hasHostileTarget, attackAction, 10.0f));
dm.addOption(new Option("flee", lowHealth, fleeAction, 15.0f));
dm.addOption(new Option("patrol", isGuard, patrolAction, 5.0f));
dm.addOption(new Option("idle", always, idleAction, 1.0f));
npc.setDecisionMaker(dm);
// Ajouter les instructions
npc.addInstruction(new GuardInstruction(guardPost, 10.0f));
npc.addInstruction(new ReactToThreatInstruction());
}
```
## Bonnes Pratiques
{{< callout type="info" >}}
**Directives IA :**
- Utilisez le Blackboard pour toute communication inter-composants
- Gardez les Évaluateurs simples et rapides - ils s'exécutent fréquemment
- Utilisez des portées de capteurs appropriées pour équilibrer conscience vs performance
- Concevez les Instructions pour être interruptibles
- Testez le comportement IA avec différents scénarios
{{< /callout >}}
{{< callout type="warning" >}}
**Performance :** Un grand nombre de NPCs avec une IA complexe peut impacter les performances. Considérez :
- Réduire la fréquence de mise à jour des capteurs pour les NPCs distants
- Utiliser le LOD (Level of Detail) pour la complexité IA
- Limiter les requêtes de pathfinding
{{< /callout >}}

View File

@@ -0,0 +1,217 @@
---
title: NPC Basics
type: docs
weight: 1
---
This guide covers the fundamentals of creating and configuring NPCs in Hytale.
**Package:** `com.hypixel.hytale.server.npc`
## NPCEntity Class
`NPCEntity` is the base class for all non-player characters. It extends the entity hierarchy and provides NPC-specific functionality.
```java
public class NPCEntity extends LivingEntity {
// Core NPC functionality
private Blackboard blackboard;
private Role role;
private DecisionMaker decisionMaker;
private MotionController motionController;
}
```
### Creating an NPC
NPCs are typically created through the asset system or spawned programmatically:
```java
// Spawn NPC from asset
NPCEntity npc = world.spawnNPC("villager", position);
// Configure NPC after spawn
npc.setRole(customRole);
npc.getBlackboard().setHomePosition(position);
```
## NPC Assets
NPC definitions are configured through YAML asset files:
```yaml
# npc/villager.yaml
Type: NPC
Id: villager
DisplayName: "Villager"
Model: models/characters/villager
Role: roles/villager_role
Stats:
Health: 100
Speed: 3.0
Components:
- Type: NPCBrain
- Type: Interactable
- Type: DialogueCapable
```
### Asset Structure
| Property | Type | Description |
|----------|------|-------------|
| `Type` | String | Must be "NPC" |
| `Id` | String | Unique identifier |
| `DisplayName` | String | Display name |
| `Model` | String | Model asset reference |
| `Role` | String | Default role reference |
| `Stats` | Object | Base statistics |
| `Components` | Array | Component configurations |
## NPC Roles
Roles define the behavioral template for an NPC. They specify what instructions, sensors, and actions the NPC can use.
```yaml
# roles/villager_role.yaml
Type: Role
Id: villager_role
Instructions:
- Wander
- ReactToThreats
- Interact
Sensors:
- Type: VisualSensor
Range: 15.0
- Type: AudioSensor
Range: 10.0
Actions:
- Walk
- Run
- Talk
- Trade
```
### Role Components
```java
public class Role {
private List<Instruction> instructions;
private List<Sensor> sensors;
private List<Action> availableActions;
// Get active instruction
public Instruction getCurrentInstruction();
// Check if action is available
public boolean hasAction(String actionId);
}
```
## NPC Systems
The NPC module registers several ECS systems for processing NPC behavior:
| System | Description |
|--------|-------------|
| `NPCBrainSystem` | Processes AI decisions |
| `NPCMovementSystem` | Handles movement updates |
| `NPCSensorSystem` | Processes sensor inputs |
| `NPCAnimationSystem` | Updates animations |
| `NPCInteractionSystem` | Handles interactions |
### System Registration
```java
public class NPCPlugin extends JavaPlugin {
@Override
public void start() {
// Systems are auto-registered by NPCPlugin
// Custom systems can be added:
getEntityStoreRegistry().registerSystem(
new CustomNPCSystem()
);
}
}
```
## NPC Utilities
The `util/` package provides helper classes:
### NPCUtils
```java
// Find nearest NPC
NPCEntity nearest = NPCUtils.findNearest(position, world, 50.0);
// Get all NPCs in area
List<NPCEntity> npcsInArea = NPCUtils.getNPCsInRadius(
position, world, 25.0
);
// Check line of sight
boolean canSee = NPCUtils.hasLineOfSight(npc, target);
```
### NPCSpawner
```java
// Spawn with configuration
NPCEntity npc = NPCSpawner.spawn(
world,
"villager",
position,
config -> {
config.setRole("merchant");
config.setFaction("town");
}
);
```
## Common Patterns
### Setting Up an NPC Shop
```java
NPCEntity merchant = world.spawnNPC("merchant", shopPosition);
merchant.getBlackboard().set("shop_inventory", inventory);
merchant.getBlackboard().set("is_merchant", true);
merchant.setRole(merchantRole);
```
### Creating a Guard NPC
```java
NPCEntity guard = world.spawnNPC("guard", guardPosition);
Blackboard bb = guard.getBlackboard();
bb.setPatrolPath(patrolWaypoints);
bb.setHostileToFactions(List.of("bandits", "monsters"));
guard.setRole(guardRole);
```
### NPC with Custom Behavior
```java
NPCEntity custom = world.spawnNPC("custom_npc", position);
// Add custom component
custom.getEntityStore().addComponent(new CustomBehaviorComponent());
// Set custom decision maker
custom.setDecisionMaker(new CustomDecisionMaker());
```
## Best Practices
{{< callout type="info" >}}
**NPC Guidelines:**
- Always set a Role for NPCs to define their behavior
- Use the Blackboard for all NPC state storage
- Configure appropriate sensors for the NPC's awareness needs
- Use the asset system for NPC definitions when possible
- Consider performance with large numbers of NPCs
{{< /callout >}}
{{< callout type="warning" >}}
**Thread Safety:** NPC operations should be performed on the world's ticking thread. Use `world.isInThread()` to verify before making changes.
{{< /callout >}}

View File

@@ -0,0 +1,217 @@
---
title: Bases NPC
type: docs
weight: 1
---
Ce guide couvre les fondamentaux de la création et de la configuration des NPCs dans Hytale.
**Package:** `com.hypixel.hytale.server.npc`
## Classe NPCEntity
`NPCEntity` est la classe de base pour tous les personnages non-joueurs. Elle étend la hiérarchie d'entités et fournit des fonctionnalités spécifiques aux NPCs.
```java
public class NPCEntity extends LivingEntity {
// Fonctionnalités NPC de base
private Blackboard blackboard;
private Role role;
private DecisionMaker decisionMaker;
private MotionController motionController;
}
```
### Créer un NPC
Les NPCs sont généralement créés via le système d'assets ou générés programmatiquement :
```java
// Générer un NPC depuis un asset
NPCEntity npc = world.spawnNPC("villager", position);
// Configurer le NPC après génération
npc.setRole(customRole);
npc.getBlackboard().setHomePosition(position);
```
## Assets NPC
Les définitions NPC sont configurées via des fichiers d'assets YAML :
```yaml
# npc/villager.yaml
Type: NPC
Id: villager
DisplayName: "Villageois"
Model: models/characters/villager
Role: roles/villager_role
Stats:
Health: 100
Speed: 3.0
Components:
- Type: NPCBrain
- Type: Interactable
- Type: DialogueCapable
```
### Structure d'Asset
| Propriété | Type | Description |
|-----------|------|-------------|
| `Type` | String | Doit être "NPC" |
| `Id` | String | Identifiant unique |
| `DisplayName` | String | Nom d'affichage |
| `Model` | String | Référence asset modèle |
| `Role` | String | Référence rôle par défaut |
| `Stats` | Object | Statistiques de base |
| `Components` | Array | Configurations de composants |
## Rôles NPC
Les rôles définissent le template comportemental d'un NPC. Ils spécifient quelles instructions, capteurs et actions le NPC peut utiliser.
```yaml
# roles/villager_role.yaml
Type: Role
Id: villager_role
Instructions:
- Wander
- ReactToThreats
- Interact
Sensors:
- Type: VisualSensor
Range: 15.0
- Type: AudioSensor
Range: 10.0
Actions:
- Walk
- Run
- Talk
- Trade
```
### Composants de Rôle
```java
public class Role {
private List<Instruction> instructions;
private List<Sensor> sensors;
private List<Action> availableActions;
// Obtenir l'instruction active
public Instruction getCurrentInstruction();
// Vérifier si une action est disponible
public boolean hasAction(String actionId);
}
```
## Systèmes NPC
Le module NPC enregistre plusieurs systèmes ECS pour traiter le comportement des NPCs :
| Système | Description |
|---------|-------------|
| `NPCBrainSystem` | Traite les décisions IA |
| `NPCMovementSystem` | Gère les mises à jour de mouvement |
| `NPCSensorSystem` | Traite les entrées des capteurs |
| `NPCAnimationSystem` | Met à jour les animations |
| `NPCInteractionSystem` | Gère les interactions |
### Enregistrement des Systèmes
```java
public class NPCPlugin extends JavaPlugin {
@Override
public void start() {
// Les systèmes sont auto-enregistrés par NPCPlugin
// Des systèmes personnalisés peuvent être ajoutés :
getEntityStoreRegistry().registerSystem(
new CustomNPCSystem()
);
}
}
```
## Utilitaires NPC
Le package `util/` fournit des classes d'aide :
### NPCUtils
```java
// Trouver le NPC le plus proche
NPCEntity nearest = NPCUtils.findNearest(position, world, 50.0);
// Obtenir tous les NPCs dans une zone
List<NPCEntity> npcsInArea = NPCUtils.getNPCsInRadius(
position, world, 25.0
);
// Vérifier la ligne de vue
boolean canSee = NPCUtils.hasLineOfSight(npc, target);
```
### NPCSpawner
```java
// Générer avec configuration
NPCEntity npc = NPCSpawner.spawn(
world,
"villager",
position,
config -> {
config.setRole("merchant");
config.setFaction("town");
}
);
```
## Patterns Courants
### Configurer une Boutique NPC
```java
NPCEntity merchant = world.spawnNPC("merchant", shopPosition);
merchant.getBlackboard().set("shop_inventory", inventory);
merchant.getBlackboard().set("is_merchant", true);
merchant.setRole(merchantRole);
```
### Créer un NPC Garde
```java
NPCEntity guard = world.spawnNPC("guard", guardPosition);
Blackboard bb = guard.getBlackboard();
bb.setPatrolPath(patrolWaypoints);
bb.setHostileToFactions(List.of("bandits", "monsters"));
guard.setRole(guardRole);
```
### NPC avec Comportement Personnalisé
```java
NPCEntity custom = world.spawnNPC("custom_npc", position);
// Ajouter un composant personnalisé
custom.getEntityStore().addComponent(new CustomBehaviorComponent());
// Définir un decision maker personnalisé
custom.setDecisionMaker(new CustomDecisionMaker());
```
## Bonnes Pratiques
{{< callout type="info" >}}
**Directives NPC :**
- Définissez toujours un Rôle pour les NPCs pour définir leur comportement
- Utilisez le Blackboard pour tout stockage d'état NPC
- Configurez les capteurs appropriés pour les besoins de perception du NPC
- Utilisez le système d'assets pour les définitions NPC quand possible
- Considérez les performances avec un grand nombre de NPCs
{{< /callout >}}
{{< callout type="warning" >}}
**Thread Safety :** Les opérations NPC doivent être effectuées sur le thread de tick du monde. Utilisez `world.isInThread()` pour vérifier avant de faire des changements.
{{< /callout >}}

View File

@@ -0,0 +1,374 @@
---
title: NPC Commands
type: docs
weight: 5
---
The NPC system includes 23 admin and debug commands for managing and testing NPCs.
**Package:** `com.hypixel.hytale.server.npc.commands`
## Spawning Commands
### /npc spawn
Spawns an NPC at a location:
```
/npc spawn <npc_type> [position] [--role <role>] [--name <name>]
```
**Arguments:**
| Argument | Type | Description |
|----------|------|-------------|
| `npc_type` | String | NPC asset ID |
| `position` | Position | Spawn location (default: player position) |
| `--role` | String | Override default role |
| `--name` | String | Custom display name |
**Examples:**
```
/npc spawn villager
/npc spawn guard ~ ~1 ~ --role patrol_guard
/npc spawn merchant --name "Bob the Trader"
```
### /npc despawn
Removes an NPC:
```
/npc despawn <target>
```
**Arguments:**
| Argument | Type | Description |
|----------|------|-------------|
| `target` | NPC | Target NPC (raycast or selector) |
### /npc despawnall
Removes all NPCs in radius:
```
/npc despawnall [radius]
```
**Arguments:**
| Argument | Type | Description |
|----------|------|-------------|
| `radius` | Float | Radius in blocks (default: 50) |
## Information Commands
### /npc info
Displays NPC information:
```
/npc info [target]
```
**Output:**
```
NPC Info: Villager (villager_001)
Position: 100.5, 64.0, -200.3
Role: villager_role
State: IDLE
Health: 100/100
Target: None
Current Instruction: Wander
```
### /npc list
Lists all NPCs:
```
/npc list [--radius <radius>] [--type <type>]
```
**Arguments:**
| Argument | Type | Description |
|----------|------|-------------|
| `--radius` | Float | Search radius |
| `--type` | String | Filter by NPC type |
### /npc debug
Toggles debug visualization:
```
/npc debug <mode>
```
**Modes:**
| Mode | Description |
|------|-------------|
| `path` | Show pathfinding |
| `sensors` | Show sensor ranges |
| `state` | Show AI state |
| `target` | Show targeting |
| `all` | Show everything |
| `off` | Disable debug |
## Behavior Commands
### /npc role
Changes NPC role:
```
/npc role <target> <role>
```
**Examples:**
```
/npc role @nearest guard_role
/npc role @e[type=villager] merchant_role
```
### /npc state
Forces NPC state:
```
/npc state <target> <state>
```
**States:**
```
/npc state @nearest IDLE
/npc state @nearest ATTACKING
/npc state @nearest FLEEING
```
### /npc target
Sets NPC target:
```
/npc target <npc> <target_entity>
```
**Examples:**
```
/npc target @nearest @p
/npc target guard_01 @e[type=zombie,limit=1]
```
### /npc cleartarget
Clears NPC target:
```
/npc cleartarget <target>
```
## Movement Commands
### /npc moveto
Commands NPC to move to position:
```
/npc moveto <target> <position>
```
**Examples:**
```
/npc moveto @nearest ~ ~ ~10
/npc moveto guard_01 100 64 -200
```
### /npc follow
Commands NPC to follow entity:
```
/npc follow <npc> <target> [distance]
```
**Examples:**
```
/npc follow @nearest @p 3.0
```
### /npc stop
Stops NPC movement:
```
/npc stop <target>
```
### /npc patrol
Sets patrol path:
```
/npc patrol <target> <point1> <point2> [point3...]
```
**Examples:**
```
/npc patrol guard_01 0 64 0 10 64 0 10 64 10 0 64 10
```
### /npc home
Sets NPC home position:
```
/npc home <target> [position]
```
## Blackboard Commands
### /npc blackboard get
Gets blackboard value:
```
/npc blackboard get <target> <key>
```
**Examples:**
```
/npc blackboard get @nearest alert_level
/npc blackboard get guard_01 home_position
```
### /npc blackboard set
Sets blackboard value:
```
/npc blackboard set <target> <key> <value>
```
**Examples:**
```
/npc blackboard set @nearest alert_level 1.0
/npc blackboard set @nearest is_hostile true
```
### /npc blackboard clear
Clears blackboard:
```
/npc blackboard clear <target> [key]
```
## AI Commands
### /npc think
Forces AI decision cycle:
```
/npc think <target>
```
### /npc instruction
Forces instruction:
```
/npc instruction <target> <instruction>
```
**Examples:**
```
/npc instruction @nearest flee
/npc instruction guard_01 attack
```
### /npc sensor
Toggles sensor:
```
/npc sensor <target> <sensor> <enabled>
```
**Examples:**
```
/npc sensor @nearest visual false
/npc sensor @nearest audio true
```
## Faction Commands
### /npc faction
Sets NPC faction:
```
/npc faction <target> <faction>
```
**Examples:**
```
/npc faction @nearest town_guard
/npc faction villager_01 merchants
```
### /npc relation
Sets faction relation:
```
/npc relation <faction1> <faction2> <relation>
```
**Relations:** `ALLIED`, `FRIENDLY`, `NEUTRAL`, `UNFRIENDLY`, `HOSTILE`
**Examples:**
```
/npc relation guards bandits HOSTILE
/npc relation merchants town_guard ALLIED
```
## Command Permissions
| Command | Permission |
|---------|------------|
| `/npc spawn` | `hytale.command.npc.spawn` |
| `/npc despawn` | `hytale.command.npc.despawn` |
| `/npc info` | `hytale.command.npc.info` |
| `/npc debug` | `hytale.command.npc.debug` |
| `/npc role` | `hytale.command.npc.role` |
| `/npc blackboard` | `hytale.command.npc.blackboard` |
## Registering Custom NPC Commands
```java
public class MyNPCCommand extends AbstractCommand {
public MyNPCCommand() {
super("npc custom", "Custom NPC command");
withRequiredArg("target", "Target NPC", ArgTypes.NPC_REF);
}
@Override
protected CompletableFuture<Void> execute(CommandContext ctx) {
NPCEntity npc = ctx.get("target");
// Custom logic
return null;
}
}
// Register in plugin setup
getCommandRegistry().register(new MyNPCCommand());
```
## Best Practices
{{< callout type="info" >}}
**Command Guidelines:**
- Use selectors like `@nearest` for targeting NPCs
- Debug commands are invaluable for testing AI
- Blackboard commands allow runtime behavior modification
- Use permission checks for admin commands
{{< /callout >}}

View File

@@ -0,0 +1,374 @@
---
title: Commandes NPC
type: docs
weight: 5
---
Le système NPC inclut 23 commandes admin et debug pour gérer et tester les NPCs.
**Package:** `com.hypixel.hytale.server.npc.commands`
## Commandes de Spawn
### /npc spawn
Fait apparaître un NPC à une position :
```
/npc spawn <npc_type> [position] [--role <role>] [--name <name>]
```
**Arguments :**
| Argument | Type | Description |
|----------|------|-------------|
| `npc_type` | String | ID d'asset NPC |
| `position` | Position | Position de spawn (défaut: position du joueur) |
| `--role` | String | Remplacer le rôle par défaut |
| `--name` | String | Nom d'affichage personnalisé |
**Exemples :**
```
/npc spawn villager
/npc spawn guard ~ ~1 ~ --role patrol_guard
/npc spawn merchant --name "Bob le Marchand"
```
### /npc despawn
Supprime un NPC :
```
/npc despawn <target>
```
**Arguments :**
| Argument | Type | Description |
|----------|------|-------------|
| `target` | NPC | NPC cible (raycast ou sélecteur) |
### /npc despawnall
Supprime tous les NPCs dans un rayon :
```
/npc despawnall [radius]
```
**Arguments :**
| Argument | Type | Description |
|----------|------|-------------|
| `radius` | Float | Rayon en blocs (défaut: 50) |
## Commandes d'Information
### /npc info
Affiche les informations d'un NPC :
```
/npc info [target]
```
**Sortie :**
```
NPC Info: Villageois (villager_001)
Position: 100.5, 64.0, -200.3
Role: villager_role
State: IDLE
Health: 100/100
Target: None
Current Instruction: Wander
```
### /npc list
Liste tous les NPCs :
```
/npc list [--radius <radius>] [--type <type>]
```
**Arguments :**
| Argument | Type | Description |
|----------|------|-------------|
| `--radius` | Float | Rayon de recherche |
| `--type` | String | Filtrer par type de NPC |
### /npc debug
Active/désactive la visualisation debug :
```
/npc debug <mode>
```
**Modes :**
| Mode | Description |
|------|-------------|
| `path` | Afficher le pathfinding |
| `sensors` | Afficher les portées des capteurs |
| `state` | Afficher l'état IA |
| `target` | Afficher le ciblage |
| `all` | Tout afficher |
| `off` | Désactiver le debug |
## Commandes de Comportement
### /npc role
Change le rôle du NPC :
```
/npc role <target> <role>
```
**Exemples :**
```
/npc role @nearest guard_role
/npc role @e[type=villager] merchant_role
```
### /npc state
Force l'état du NPC :
```
/npc state <target> <state>
```
**États :**
```
/npc state @nearest IDLE
/npc state @nearest ATTACKING
/npc state @nearest FLEEING
```
### /npc target
Définit la cible du NPC :
```
/npc target <npc> <target_entity>
```
**Exemples :**
```
/npc target @nearest @p
/npc target guard_01 @e[type=zombie,limit=1]
```
### /npc cleartarget
Efface la cible du NPC :
```
/npc cleartarget <target>
```
## Commandes de Mouvement
### /npc moveto
Ordonne au NPC de se déplacer vers une position :
```
/npc moveto <target> <position>
```
**Exemples :**
```
/npc moveto @nearest ~ ~ ~10
/npc moveto guard_01 100 64 -200
```
### /npc follow
Ordonne au NPC de suivre une entité :
```
/npc follow <npc> <target> [distance]
```
**Exemples :**
```
/npc follow @nearest @p 3.0
```
### /npc stop
Arrête le mouvement du NPC :
```
/npc stop <target>
```
### /npc patrol
Définit un chemin de patrouille :
```
/npc patrol <target> <point1> <point2> [point3...]
```
**Exemples :**
```
/npc patrol guard_01 0 64 0 10 64 0 10 64 10 0 64 10
```
### /npc home
Définit la position d'origine du NPC :
```
/npc home <target> [position]
```
## Commandes Blackboard
### /npc blackboard get
Obtient une valeur du blackboard :
```
/npc blackboard get <target> <key>
```
**Exemples :**
```
/npc blackboard get @nearest alert_level
/npc blackboard get guard_01 home_position
```
### /npc blackboard set
Définit une valeur du blackboard :
```
/npc blackboard set <target> <key> <value>
```
**Exemples :**
```
/npc blackboard set @nearest alert_level 1.0
/npc blackboard set @nearest is_hostile true
```
### /npc blackboard clear
Efface le blackboard :
```
/npc blackboard clear <target> [key]
```
## Commandes IA
### /npc think
Force un cycle de décision IA :
```
/npc think <target>
```
### /npc instruction
Force une instruction :
```
/npc instruction <target> <instruction>
```
**Exemples :**
```
/npc instruction @nearest flee
/npc instruction guard_01 attack
```
### /npc sensor
Active/désactive un capteur :
```
/npc sensor <target> <sensor> <enabled>
```
**Exemples :**
```
/npc sensor @nearest visual false
/npc sensor @nearest audio true
```
## Commandes de Faction
### /npc faction
Définit la faction du NPC :
```
/npc faction <target> <faction>
```
**Exemples :**
```
/npc faction @nearest town_guard
/npc faction villager_01 merchants
```
### /npc relation
Définit la relation entre factions :
```
/npc relation <faction1> <faction2> <relation>
```
**Relations :** `ALLIED`, `FRIENDLY`, `NEUTRAL`, `UNFRIENDLY`, `HOSTILE`
**Exemples :**
```
/npc relation guards bandits HOSTILE
/npc relation merchants town_guard ALLIED
```
## Permissions des Commandes
| Commande | Permission |
|----------|------------|
| `/npc spawn` | `hytale.command.npc.spawn` |
| `/npc despawn` | `hytale.command.npc.despawn` |
| `/npc info` | `hytale.command.npc.info` |
| `/npc debug` | `hytale.command.npc.debug` |
| `/npc role` | `hytale.command.npc.role` |
| `/npc blackboard` | `hytale.command.npc.blackboard` |
## Enregistrer des Commandes NPC Personnalisées
```java
public class MyNPCCommand extends AbstractCommand {
public MyNPCCommand() {
super("npc custom", "Commande NPC personnalisée");
withRequiredArg("target", "NPC cible", ArgTypes.NPC_REF);
}
@Override
protected CompletableFuture<Void> execute(CommandContext ctx) {
NPCEntity npc = ctx.get("target");
// Logique personnalisée
return null;
}
}
// Enregistrer dans le setup du plugin
getCommandRegistry().register(new MyNPCCommand());
```
## Bonnes Pratiques
{{< callout type="info" >}}
**Directives des Commandes :**
- Utilisez des sélecteurs comme `@nearest` pour cibler les NPCs
- Les commandes debug sont précieuses pour tester l'IA
- Les commandes blackboard permettent la modification du comportement à l'exécution
- Utilisez les vérifications de permissions pour les commandes admin
{{< /callout >}}

View File

@@ -0,0 +1,348 @@
---
title: NPC Components
type: docs
weight: 2
---
NPC components are ECS components that store data and state for NPCs. The system includes over 300 core components for various NPC functionalities.
**Package:** `com.hypixel.hytale.server.npc.corecomponents`
## Core Components Overview
NPC components follow the ECS pattern where components are pure data containers attached to entity stores.
```java
// Access NPC components
EntityStore store = npc.getEntityStore();
NPCBrainComponent brain = store.getComponent(NPCBrainComponent.class);
```
## Brain Components
### NPCBrainComponent
The main AI processing component:
```java
public class NPCBrainComponent {
private DecisionMaker decisionMaker;
private float thinkInterval;
private float lastThinkTime;
public void setDecisionMaker(DecisionMaker maker);
public DecisionMaker getDecisionMaker();
public boolean shouldThink(float currentTime);
}
```
### NPCMemoryComponent
Stores NPC memories and knowledge:
```java
public class NPCMemoryComponent {
private Map<String, Memory> memories;
private float memoryDuration;
public void remember(String key, Object value, float duration);
public <T> T recall(String key, Class<T> type);
public boolean hasMemory(String key);
public void forget(String key);
}
```
## State Components
### NPCStateComponent
Current behavioral state:
```java
public class NPCStateComponent {
private NPCState currentState;
private NPCState previousState;
private float stateEnterTime;
public enum NPCState {
IDLE,
WALKING,
RUNNING,
ATTACKING,
FLEEING,
INTERACTING,
SLEEPING,
DEAD
}
}
```
### NPCAlertComponent
Alertness and awareness level:
```java
public class NPCAlertComponent {
private AlertLevel alertLevel;
private float alertDecayRate;
private Entity alertSource;
public enum AlertLevel {
RELAXED, // Normal state
CURIOUS, // Something caught attention
ALERT, // Actively investigating
ALARMED, // Threat detected
COMBAT // In combat
}
}
```
## Target Components
### NPCTargetComponent
Current target tracking:
```java
public class NPCTargetComponent {
private Ref<EntityStore> currentTarget;
private TargetType targetType;
private float targetAcquiredTime;
private Vector3d lastKnownPosition;
public enum TargetType {
HOSTILE,
FRIENDLY,
NEUTRAL,
OBJECT
}
}
```
### NPCFocusComponent
Visual focus point:
```java
public class NPCFocusComponent {
private Vector3d focusPoint;
private Ref<EntityStore> focusEntity;
private float focusStrength;
private boolean shouldLookAt;
}
```
## Movement Components
### NPCMovementComponent
Movement configuration:
```java
public class NPCMovementComponent {
private float walkSpeed;
private float runSpeed;
private float turnSpeed;
private boolean canJump;
private boolean canSwim;
private boolean canClimb;
}
```
### NPCPathComponent
Path following data:
```java
public class NPCPathComponent {
private List<Vector3d> currentPath;
private int currentWaypointIndex;
private float pathRecalculateInterval;
private float lastPathTime;
public Vector3d getCurrentWaypoint();
public Vector3d getNextWaypoint();
public boolean hasReachedWaypoint(Vector3d position, float threshold);
}
```
### NPCNavigationComponent
Navigation settings:
```java
public class NPCNavigationComponent {
private float avoidanceRadius;
private float pathfindingRange;
private int maxPathLength;
private NavigationFlags flags;
public static class NavigationFlags {
public boolean avoidWater;
public boolean avoidFire;
public boolean canUseDoors;
public boolean canBreakBlocks;
}
}
```
## Combat Components
### NPCCombatComponent
Combat capabilities:
```java
public class NPCCombatComponent {
private float attackRange;
private float attackCooldown;
private float lastAttackTime;
private DamageType preferredDamageType;
private List<String> availableAttacks;
}
```
### NPCAggroComponent
Aggression management:
```java
public class NPCAggroComponent {
private Map<Ref<EntityStore>, Float> aggroTable;
private float aggroDecayRate;
private float aggroRange;
public void addAggro(Ref<EntityStore> entity, float amount);
public void removeAggro(Ref<EntityStore> entity);
public Ref<EntityStore> getHighestAggroTarget();
}
```
## Social Components
### NPCFactionComponent
Faction affiliation:
```java
public class NPCFactionComponent {
private String factionId;
private Map<String, FactionRelation> relations;
public enum FactionRelation {
ALLIED,
FRIENDLY,
NEUTRAL,
UNFRIENDLY,
HOSTILE
}
public FactionRelation getRelation(String otherFaction);
}
```
### NPCDialogueComponent
Dialogue capabilities:
```java
public class NPCDialogueComponent {
private String dialogueTreeId;
private Map<String, Boolean> dialogueFlags;
private Ref<EntityStore> currentSpeaker;
public boolean hasDialogue();
public void startDialogue(Player player);
}
```
## Utility Components
### NPCScheduleComponent
Daily schedule:
```java
public class NPCScheduleComponent {
private Map<Integer, ScheduleEntry> schedule;
public static class ScheduleEntry {
public int startHour;
public int endHour;
public String activity;
public Vector3d location;
}
public ScheduleEntry getCurrentActivity(int worldHour);
}
```
### NPCInventoryComponent
NPC inventory:
```java
public class NPCInventoryComponent {
private ItemContainer inventory;
private ItemStack equippedWeapon;
private ItemStack equippedArmor;
public ItemStack getEquippedWeapon();
public void equipItem(ItemStack item);
}
```
## Registering Custom Components
```java
public class MyPlugin extends JavaPlugin {
@Override
public void start() {
// Register custom NPC component
getEntityStoreRegistry().registerComponent(
"custom_npc_data",
CustomNPCComponent.class,
CustomNPCComponent::new
);
}
}
public class CustomNPCComponent {
private String customData;
private int customValue;
// Component data fields
}
```
## Component Access Patterns
```java
// Safe component access
public void processNPC(NPCEntity npc) {
EntityStore store = npc.getEntityStore();
// Check if component exists
if (store.hasComponent(NPCCombatComponent.class)) {
NPCCombatComponent combat = store.getComponent(NPCCombatComponent.class);
// Process combat logic
}
// Get or create component
NPCStateComponent state = store.getOrCreateComponent(
NPCStateComponent.class,
NPCStateComponent::new
);
}
```
## Best Practices
{{< callout type="info" >}}
**Component Guidelines:**
- Components should be pure data - no complex logic
- Use components for state that needs to persist
- Access components through EntityStore, not directly
- Check component existence before access
- Use appropriate component for each type of data
{{< /callout >}}

View File

@@ -0,0 +1,348 @@
---
title: Composants NPC
type: docs
weight: 2
---
Les composants NPC sont des composants ECS qui stockent les données et l'état des NPCs. Le système inclut plus de 300 composants de base pour diverses fonctionnalités NPC.
**Package:** `com.hypixel.hytale.server.npc.corecomponents`
## Vue d'Ensemble des Composants
Les composants NPC suivent le pattern ECS où les composants sont des conteneurs de données pures attachés aux entity stores.
```java
// Accéder aux composants NPC
EntityStore store = npc.getEntityStore();
NPCBrainComponent brain = store.getComponent(NPCBrainComponent.class);
```
## Composants Cerveau
### NPCBrainComponent
Le composant principal de traitement IA :
```java
public class NPCBrainComponent {
private DecisionMaker decisionMaker;
private float thinkInterval;
private float lastThinkTime;
public void setDecisionMaker(DecisionMaker maker);
public DecisionMaker getDecisionMaker();
public boolean shouldThink(float currentTime);
}
```
### NPCMemoryComponent
Stocke les mémoires et connaissances du NPC :
```java
public class NPCMemoryComponent {
private Map<String, Memory> memories;
private float memoryDuration;
public void remember(String key, Object value, float duration);
public <T> T recall(String key, Class<T> type);
public boolean hasMemory(String key);
public void forget(String key);
}
```
## Composants d'État
### NPCStateComponent
État comportemental actuel :
```java
public class NPCStateComponent {
private NPCState currentState;
private NPCState previousState;
private float stateEnterTime;
public enum NPCState {
IDLE,
WALKING,
RUNNING,
ATTACKING,
FLEEING,
INTERACTING,
SLEEPING,
DEAD
}
}
```
### NPCAlertComponent
Niveau de vigilance et de conscience :
```java
public class NPCAlertComponent {
private AlertLevel alertLevel;
private float alertDecayRate;
private Entity alertSource;
public enum AlertLevel {
RELAXED, // État normal
CURIOUS, // Quelque chose a attiré l'attention
ALERT, // Investigation active
ALARMED, // Menace détectée
COMBAT // En combat
}
}
```
## Composants de Cible
### NPCTargetComponent
Suivi de cible actuel :
```java
public class NPCTargetComponent {
private Ref<EntityStore> currentTarget;
private TargetType targetType;
private float targetAcquiredTime;
private Vector3d lastKnownPosition;
public enum TargetType {
HOSTILE,
FRIENDLY,
NEUTRAL,
OBJECT
}
}
```
### NPCFocusComponent
Point de focus visuel :
```java
public class NPCFocusComponent {
private Vector3d focusPoint;
private Ref<EntityStore> focusEntity;
private float focusStrength;
private boolean shouldLookAt;
}
```
## Composants de Mouvement
### NPCMovementComponent
Configuration de mouvement :
```java
public class NPCMovementComponent {
private float walkSpeed;
private float runSpeed;
private float turnSpeed;
private boolean canJump;
private boolean canSwim;
private boolean canClimb;
}
```
### NPCPathComponent
Données de suivi de chemin :
```java
public class NPCPathComponent {
private List<Vector3d> currentPath;
private int currentWaypointIndex;
private float pathRecalculateInterval;
private float lastPathTime;
public Vector3d getCurrentWaypoint();
public Vector3d getNextWaypoint();
public boolean hasReachedWaypoint(Vector3d position, float threshold);
}
```
### NPCNavigationComponent
Paramètres de navigation :
```java
public class NPCNavigationComponent {
private float avoidanceRadius;
private float pathfindingRange;
private int maxPathLength;
private NavigationFlags flags;
public static class NavigationFlags {
public boolean avoidWater;
public boolean avoidFire;
public boolean canUseDoors;
public boolean canBreakBlocks;
}
}
```
## Composants de Combat
### NPCCombatComponent
Capacités de combat :
```java
public class NPCCombatComponent {
private float attackRange;
private float attackCooldown;
private float lastAttackTime;
private DamageType preferredDamageType;
private List<String> availableAttacks;
}
```
### NPCAggroComponent
Gestion de l'aggro :
```java
public class NPCAggroComponent {
private Map<Ref<EntityStore>, Float> aggroTable;
private float aggroDecayRate;
private float aggroRange;
public void addAggro(Ref<EntityStore> entity, float amount);
public void removeAggro(Ref<EntityStore> entity);
public Ref<EntityStore> getHighestAggroTarget();
}
```
## Composants Sociaux
### NPCFactionComponent
Affiliation de faction :
```java
public class NPCFactionComponent {
private String factionId;
private Map<String, FactionRelation> relations;
public enum FactionRelation {
ALLIED,
FRIENDLY,
NEUTRAL,
UNFRIENDLY,
HOSTILE
}
public FactionRelation getRelation(String otherFaction);
}
```
### NPCDialogueComponent
Capacités de dialogue :
```java
public class NPCDialogueComponent {
private String dialogueTreeId;
private Map<String, Boolean> dialogueFlags;
private Ref<EntityStore> currentSpeaker;
public boolean hasDialogue();
public void startDialogue(Player player);
}
```
## Composants Utilitaires
### NPCScheduleComponent
Programme journalier :
```java
public class NPCScheduleComponent {
private Map<Integer, ScheduleEntry> schedule;
public static class ScheduleEntry {
public int startHour;
public int endHour;
public String activity;
public Vector3d location;
}
public ScheduleEntry getCurrentActivity(int worldHour);
}
```
### NPCInventoryComponent
Inventaire NPC :
```java
public class NPCInventoryComponent {
private ItemContainer inventory;
private ItemStack equippedWeapon;
private ItemStack equippedArmor;
public ItemStack getEquippedWeapon();
public void equipItem(ItemStack item);
}
```
## Enregistrer des Composants Personnalisés
```java
public class MyPlugin extends JavaPlugin {
@Override
public void start() {
// Enregistrer un composant NPC personnalisé
getEntityStoreRegistry().registerComponent(
"custom_npc_data",
CustomNPCComponent.class,
CustomNPCComponent::new
);
}
}
public class CustomNPCComponent {
private String customData;
private int customValue;
// Champs de données du composant
}
```
## Patterns d'Accès aux Composants
```java
// Accès sécurisé aux composants
public void processNPC(NPCEntity npc) {
EntityStore store = npc.getEntityStore();
// Vérifier si le composant existe
if (store.hasComponent(NPCCombatComponent.class)) {
NPCCombatComponent combat = store.getComponent(NPCCombatComponent.class);
// Traiter la logique de combat
}
// Obtenir ou créer un composant
NPCStateComponent state = store.getOrCreateComponent(
NPCStateComponent.class,
NPCStateComponent::new
);
}
```
## Bonnes Pratiques
{{< callout type="info" >}}
**Directives des Composants :**
- Les composants doivent être des données pures - pas de logique complexe
- Utilisez les composants pour l'état qui doit persister
- Accédez aux composants via EntityStore, pas directement
- Vérifiez l'existence du composant avant l'accès
- Utilisez le composant approprié pour chaque type de données
{{< /callout >}}

View File

@@ -0,0 +1,365 @@
---
title: NPC Movement
type: docs
weight: 4
---
The NPC movement system handles navigation, pathfinding, and motion control for NPCs.
**Packages:**
- `com.hypixel.hytale.server.npc.movement`
- `com.hypixel.hytale.server.npc.navigation`
## Motion Controller
The `MotionController` executes movement commands and manages NPC locomotion.
### MotionController Class
```java
public class MotionController {
private NPCEntity npc;
private MovementState state;
private float currentSpeed;
private Vector3d targetVelocity;
// Movement commands
public void moveTo(Vector3d target);
public void moveInDirection(Vector3d direction);
public void stop();
// Speed control
public void setSpeed(float speed);
public void walk();
public void run();
public void sprint();
// State queries
public boolean isMoving();
public boolean hasReachedTarget();
public MovementState getState();
}
```
### Movement States
```java
public enum MovementState {
IDLE, // Not moving
WALKING, // Normal movement
RUNNING, // Fast movement
SPRINTING, // Maximum speed
JUMPING, // In air (jump)
FALLING, // In air (fall)
SWIMMING, // In water
CLIMBING, // On ladder/vine
SLIDING // On slope
}
```
### Using MotionController
```java
NPCEntity npc = // get NPC
MotionController motion = npc.getMotionController();
// Move to position
motion.moveTo(targetPosition);
// Set movement speed
motion.run(); // or motion.setSpeed(5.0f);
// Check if arrived
if (motion.hasReachedTarget()) {
// Destination reached
}
// Stop movement
motion.stop();
```
## Path Follower
The `PathFollower` tracks and follows calculated paths.
### PathFollower Class
```java
public class PathFollower {
private List<Vector3d> path;
private int currentIndex;
private float waypointRadius;
private boolean smoothPath;
// Path management
public void setPath(List<Vector3d> path);
public void clearPath();
public boolean hasPath();
// Following
public Vector3d getNextWaypoint();
public void advanceToNextWaypoint();
public boolean hasReachedWaypoint(Vector3d position);
// Progress
public float getPathProgress(); // 0.0 to 1.0
public int getRemainingWaypoints();
}
```
### Path Following Example
```java
PathFollower pathFollower = npc.getPathFollower();
MotionController motion = npc.getMotionController();
// Set a path
pathFollower.setPath(calculatedPath);
// In update loop
if (pathFollower.hasPath()) {
Vector3d nextWaypoint = pathFollower.getNextWaypoint();
// Move towards waypoint
motion.moveTo(nextWaypoint);
// Check if reached
if (pathFollower.hasReachedWaypoint(npc.getPosition())) {
pathFollower.advanceToNextWaypoint();
}
}
```
## Navigation Graph
The navigation system uses A* pathfinding on a navigation graph.
### NavigationGraph Class
```java
public class NavigationGraph {
// Find path between points
public List<Vector3d> findPath(
Vector3d start,
Vector3d end,
NavigationConfig config
);
// Check if point is navigable
public boolean isNavigable(Vector3d position);
// Get nearest navigable point
public Vector3d getNearestNavigablePoint(Vector3d position);
}
```
### NavigationConfig
```java
public class NavigationConfig {
private float maxDistance; // Maximum path length
private float stepHeight; // Max step up height
private float entityWidth; // Entity collision width
private float entityHeight; // Entity collision height
private boolean canSwim; // Allow water paths
private boolean canClimb; // Allow ladder/vine paths
private boolean canOpenDoors; // Allow door traversal
private Set<String> avoidBlocks; // Blocks to avoid
// Builder pattern
public static NavigationConfig builder()
.maxDistance(100.0f)
.stepHeight(1.0f)
.entityWidth(0.6f)
.entityHeight(1.8f)
.canSwim(false)
.build();
}
```
### Pathfinding Example
```java
NavigationGraph navGraph = world.getNavigationGraph();
NavigationConfig config = NavigationConfig.builder()
.maxDistance(50.0f)
.canSwim(true)
.build();
List<Vector3d> path = navGraph.findPath(
npc.getPosition(),
targetPosition,
config
);
if (path != null && !path.isEmpty()) {
npc.getPathFollower().setPath(path);
}
```
## Movement Behaviors
Pre-built movement behaviors for common patterns.
### WanderBehavior
```java
public class WanderBehavior {
private float wanderRadius;
private float minPauseDuration;
private float maxPauseDuration;
public WanderBehavior(float radius) {
this.wanderRadius = radius;
}
public void update(NPCEntity npc, float deltaTime) {
if (!npc.getMotionController().isMoving()) {
Vector3d wanderTarget = calculateWanderTarget(npc);
npc.getMotionController().moveTo(wanderTarget);
}
}
}
```
### FollowBehavior
```java
public class FollowBehavior {
private Entity target;
private float followDistance;
private float catchUpDistance;
public void update(NPCEntity npc, float deltaTime) {
float distance = npc.getPosition().distance(target.getPosition());
if (distance > catchUpDistance) {
npc.getMotionController().run();
} else if (distance > followDistance) {
npc.getMotionController().walk();
npc.getMotionController().moveTo(target.getPosition());
} else {
npc.getMotionController().stop();
}
}
}
```
### PatrolBehavior
```java
public class PatrolBehavior {
private List<Vector3d> patrolPoints;
private int currentPointIndex;
private boolean loop;
private float waitTimeAtPoint;
public void update(NPCEntity npc, float deltaTime) {
Vector3d currentTarget = patrolPoints.get(currentPointIndex);
MotionController motion = npc.getMotionController();
if (motion.hasReachedTarget()) {
// Wait at point
currentPointIndex = (currentPointIndex + 1) % patrolPoints.size();
} else {
motion.moveTo(currentTarget);
}
}
}
```
### FleeBehavior
```java
public class FleeBehavior {
private float fleeDistance;
private Entity threat;
public void update(NPCEntity npc, float deltaTime) {
Vector3d awayFromThreat = npc.getPosition()
.subtract(threat.getPosition())
.normalize()
.multiply(fleeDistance);
Vector3d fleeTarget = npc.getPosition().add(awayFromThreat);
npc.getMotionController().sprint();
npc.getMotionController().moveTo(fleeTarget);
}
}
```
## Obstacle Avoidance
```java
public class ObstacleAvoidance {
private float avoidanceRadius;
private float lookAheadDistance;
public Vector3d calculateAvoidanceVector(
NPCEntity npc,
Vector3d desiredDirection
) {
// Cast rays to detect obstacles
List<RaycastHit> obstacles = castAvoidanceRays(npc, desiredDirection);
if (obstacles.isEmpty()) {
return desiredDirection;
}
// Calculate avoidance steering
Vector3d avoidance = Vector3d.ZERO;
for (RaycastHit hit : obstacles) {
Vector3d away = npc.getPosition().subtract(hit.position).normalize();
avoidance = avoidance.add(away);
}
return desiredDirection.add(avoidance.normalize()).normalize();
}
}
```
## Movement Systems
ECS systems that process NPC movement:
```java
// Movement update system
public class NPCMovementSystem implements System {
@Override
public void update(float deltaTime) {
for (NPCEntity npc : npcsWithMovement) {
MotionController motion = npc.getMotionController();
PathFollower path = npc.getPathFollower();
// Update path following
if (path.hasPath()) {
updatePathFollowing(npc, motion, path, deltaTime);
}
// Apply movement
motion.update(deltaTime);
}
}
}
```
## Best Practices
{{< callout type="info" >}}
**Movement Guidelines:**
- Use NavigationConfig appropriate for the NPC type
- Cache paths when possible to avoid frequent recalculation
- Use path smoothing for more natural movement
- Consider entity size when pathfinding
- Implement obstacle avoidance for dynamic environments
{{< /callout >}}
{{< callout type="warning" >}}
**Pathfinding Performance:**
- Limit pathfinding requests per frame
- Use shorter max distances when possible
- Cache frequently used paths
- Consider hierarchical pathfinding for large worlds
{{< /callout >}}

View File

@@ -0,0 +1,365 @@
---
title: Mouvement NPC
type: docs
weight: 4
---
Le système de mouvement NPC gère la navigation, le pathfinding et le contrôle de mouvement pour les NPCs.
**Packages:**
- `com.hypixel.hytale.server.npc.movement`
- `com.hypixel.hytale.server.npc.navigation`
## Motion Controller
Le `MotionController` exécute les commandes de mouvement et gère la locomotion des NPCs.
### Classe MotionController
```java
public class MotionController {
private NPCEntity npc;
private MovementState state;
private float currentSpeed;
private Vector3d targetVelocity;
// Commandes de mouvement
public void moveTo(Vector3d target);
public void moveInDirection(Vector3d direction);
public void stop();
// Contrôle de vitesse
public void setSpeed(float speed);
public void walk();
public void run();
public void sprint();
// Requêtes d'état
public boolean isMoving();
public boolean hasReachedTarget();
public MovementState getState();
}
```
### États de Mouvement
```java
public enum MovementState {
IDLE, // Immobile
WALKING, // Mouvement normal
RUNNING, // Mouvement rapide
SPRINTING, // Vitesse maximum
JUMPING, // En l'air (saut)
FALLING, // En l'air (chute)
SWIMMING, // Dans l'eau
CLIMBING, // Sur échelle/liane
SLIDING // Sur pente
}
```
### Utiliser MotionController
```java
NPCEntity npc = // obtenir le NPC
MotionController motion = npc.getMotionController();
// Se déplacer vers une position
motion.moveTo(targetPosition);
// Définir la vitesse de mouvement
motion.run(); // ou motion.setSpeed(5.0f);
// Vérifier si arrivé
if (motion.hasReachedTarget()) {
// Destination atteinte
}
// Arrêter le mouvement
motion.stop();
```
## Path Follower
Le `PathFollower` suit et parcourt les chemins calculés.
### Classe PathFollower
```java
public class PathFollower {
private List<Vector3d> path;
private int currentIndex;
private float waypointRadius;
private boolean smoothPath;
// Gestion de chemin
public void setPath(List<Vector3d> path);
public void clearPath();
public boolean hasPath();
// Suivi
public Vector3d getNextWaypoint();
public void advanceToNextWaypoint();
public boolean hasReachedWaypoint(Vector3d position);
// Progression
public float getPathProgress(); // 0.0 à 1.0
public int getRemainingWaypoints();
}
```
### Exemple de Suivi de Chemin
```java
PathFollower pathFollower = npc.getPathFollower();
MotionController motion = npc.getMotionController();
// Définir un chemin
pathFollower.setPath(calculatedPath);
// Dans la boucle de mise à jour
if (pathFollower.hasPath()) {
Vector3d nextWaypoint = pathFollower.getNextWaypoint();
// Se déplacer vers le waypoint
motion.moveTo(nextWaypoint);
// Vérifier si atteint
if (pathFollower.hasReachedWaypoint(npc.getPosition())) {
pathFollower.advanceToNextWaypoint();
}
}
```
## Navigation Graph
Le système de navigation utilise le pathfinding A* sur un graphe de navigation.
### Classe NavigationGraph
```java
public class NavigationGraph {
// Trouver un chemin entre deux points
public List<Vector3d> findPath(
Vector3d start,
Vector3d end,
NavigationConfig config
);
// Vérifier si un point est navigable
public boolean isNavigable(Vector3d position);
// Obtenir le point navigable le plus proche
public Vector3d getNearestNavigablePoint(Vector3d position);
}
```
### NavigationConfig
```java
public class NavigationConfig {
private float maxDistance; // Longueur max du chemin
private float stepHeight; // Hauteur max de marche
private float entityWidth; // Largeur de collision entité
private float entityHeight; // Hauteur de collision entité
private boolean canSwim; // Autoriser chemins aquatiques
private boolean canClimb; // Autoriser échelles/lianes
private boolean canOpenDoors; // Autoriser traversée de portes
private Set<String> avoidBlocks; // Blocs à éviter
// Pattern builder
public static NavigationConfig builder()
.maxDistance(100.0f)
.stepHeight(1.0f)
.entityWidth(0.6f)
.entityHeight(1.8f)
.canSwim(false)
.build();
}
```
### Exemple de Pathfinding
```java
NavigationGraph navGraph = world.getNavigationGraph();
NavigationConfig config = NavigationConfig.builder()
.maxDistance(50.0f)
.canSwim(true)
.build();
List<Vector3d> path = navGraph.findPath(
npc.getPosition(),
targetPosition,
config
);
if (path != null && !path.isEmpty()) {
npc.getPathFollower().setPath(path);
}
```
## Comportements de Mouvement
Comportements de mouvement pré-construits pour les patterns courants.
### WanderBehavior
```java
public class WanderBehavior {
private float wanderRadius;
private float minPauseDuration;
private float maxPauseDuration;
public WanderBehavior(float radius) {
this.wanderRadius = radius;
}
public void update(NPCEntity npc, float deltaTime) {
if (!npc.getMotionController().isMoving()) {
Vector3d wanderTarget = calculateWanderTarget(npc);
npc.getMotionController().moveTo(wanderTarget);
}
}
}
```
### FollowBehavior
```java
public class FollowBehavior {
private Entity target;
private float followDistance;
private float catchUpDistance;
public void update(NPCEntity npc, float deltaTime) {
float distance = npc.getPosition().distance(target.getPosition());
if (distance > catchUpDistance) {
npc.getMotionController().run();
} else if (distance > followDistance) {
npc.getMotionController().walk();
npc.getMotionController().moveTo(target.getPosition());
} else {
npc.getMotionController().stop();
}
}
}
```
### PatrolBehavior
```java
public class PatrolBehavior {
private List<Vector3d> patrolPoints;
private int currentPointIndex;
private boolean loop;
private float waitTimeAtPoint;
public void update(NPCEntity npc, float deltaTime) {
Vector3d currentTarget = patrolPoints.get(currentPointIndex);
MotionController motion = npc.getMotionController();
if (motion.hasReachedTarget()) {
// Attendre au point
currentPointIndex = (currentPointIndex + 1) % patrolPoints.size();
} else {
motion.moveTo(currentTarget);
}
}
}
```
### FleeBehavior
```java
public class FleeBehavior {
private float fleeDistance;
private Entity threat;
public void update(NPCEntity npc, float deltaTime) {
Vector3d awayFromThreat = npc.getPosition()
.subtract(threat.getPosition())
.normalize()
.multiply(fleeDistance);
Vector3d fleeTarget = npc.getPosition().add(awayFromThreat);
npc.getMotionController().sprint();
npc.getMotionController().moveTo(fleeTarget);
}
}
```
## Évitement d'Obstacles
```java
public class ObstacleAvoidance {
private float avoidanceRadius;
private float lookAheadDistance;
public Vector3d calculateAvoidanceVector(
NPCEntity npc,
Vector3d desiredDirection
) {
// Lancer des rayons pour détecter les obstacles
List<RaycastHit> obstacles = castAvoidanceRays(npc, desiredDirection);
if (obstacles.isEmpty()) {
return desiredDirection;
}
// Calculer la direction d'évitement
Vector3d avoidance = Vector3d.ZERO;
for (RaycastHit hit : obstacles) {
Vector3d away = npc.getPosition().subtract(hit.position).normalize();
avoidance = avoidance.add(away);
}
return desiredDirection.add(avoidance.normalize()).normalize();
}
}
```
## Systèmes de Mouvement
Systèmes ECS qui traitent le mouvement des NPCs :
```java
// Système de mise à jour de mouvement
public class NPCMovementSystem implements System {
@Override
public void update(float deltaTime) {
for (NPCEntity npc : npcsWithMovement) {
MotionController motion = npc.getMotionController();
PathFollower path = npc.getPathFollower();
// Mettre à jour le suivi de chemin
if (path.hasPath()) {
updatePathFollowing(npc, motion, path, deltaTime);
}
// Appliquer le mouvement
motion.update(deltaTime);
}
}
}
```
## Bonnes Pratiques
{{< callout type="info" >}}
**Directives de Mouvement :**
- Utilisez un NavigationConfig approprié au type de NPC
- Mettez en cache les chemins quand possible pour éviter les recalculs fréquents
- Utilisez le lissage de chemin pour un mouvement plus naturel
- Considérez la taille de l'entité lors du pathfinding
- Implémentez l'évitement d'obstacles pour les environnements dynamiques
{{< /callout >}}
{{< callout type="warning" >}}
**Performance du Pathfinding :**
- Limitez les requêtes de pathfinding par frame
- Utilisez des distances max plus courtes quand possible
- Mettez en cache les chemins fréquemment utilisés
- Considérez le pathfinding hiérarchique pour les grands mondes
{{< /callout >}}

View File

@@ -0,0 +1,904 @@
---
title: Player API
type: docs
weight: 4
---
The Player class provides extensive APIs for player management, including communication, health, inventory, permissions, and more.
## API Overview
{{< cards cols="3" >}}
{{< card link="#identity--connection" title="Identity & Connection" subtitle="Name, UUID, network" >}}
{{< card link="#communication" title="Communication" subtitle="Messages, titles, sounds" >}}
{{< card link="#health--combat" title="Health & Combat" subtitle="HP, damage, effects" >}}
{{< card link="#inventory-api" title="Inventory" subtitle="Items, slots, armor" >}}
{{< card link="#permissions" title="Permissions" subtitle="Access control" >}}
{{< card link="#movement--teleportation" title="Movement" subtitle="Teleport, position" >}}
{{< /cards >}}
---
## Identity & Connection
### Basic Information
{{< tabs items="Methods,Example" >}}
{{< tab >}}
| Method | Return Type | Description |
|--------|-------------|-------------|
| `getUuid()` | `UUID` | Unique player identifier |
| `getDisplayName()` | `String` | Display name (from PlayerRef username) |
| `getClientViewRadius()` | `int` | Player's view radius in chunks |
| `getViewRadius()` | `int` | Effective view radius (min of client and server) |
| `hasPermission(String)` | `boolean` | Check permission |
| `sendMessage(Message)` | `void` | Send message to player |
| `getInventory()` | `Inventory` | Player's inventory |
| `getGameMode()` | `GameMode` | Current game mode |
{{< /tab >}}
{{< tab >}}
```java
// Get entity reference from command context
Ref<EntityStore> ref = context.senderAsPlayerRef();
// Access Player component via ref.getStore()
Player player = ref.getStore().getComponent(ref, Player.getComponentType());
UUID uuid = player.getUuid();
String displayName = player.getDisplayName();
int viewRadius = player.getViewRadius();
GameMode mode = player.getGameMode();
// Check permission
if (player.hasPermission("myplugin.admin")) {
player.sendMessage(Message.raw("You are an admin!"));
}
// Get PlayerRef for more info
PlayerRef playerRef = ref.getStore().getComponent(ref, PlayerRef.getComponentType());
String username = playerRef.getUsername();
String language = playerRef.getLanguage();
```
{{< /tab >}}
{{< /tabs >}}
### PlayerRef (Thread-Safe Reference)
{{< callout type="info" >}}
**Important:** `PlayerRef` is a component that provides thread-safe access to player data. It's stored in the entity component system and manages the player's connection, position, and various managers.
{{< /callout >}}
{{< tabs items="Methods,Entity Reference,Connection & Messaging,Server Transfer" >}}
{{< tab >}}
| Method | Return Type | Description |
|--------|-------------|-------------|
| `getUuid()` | `UUID` | Player's unique identifier |
| `getUsername()` | `String` | Player's username |
| `getLanguage()` | `String` | Player's language setting |
| `setLanguage(String)` | `void` | Update language |
| `getReference()` | `Ref<EntityStore>` | Entity reference (null if not in world) |
| `getHolder()` | `Holder<EntityStore>` | Entity holder (when not in store) |
| `isValid()` | `boolean` | Whether reference or holder exists |
| `getTransform()` | `Transform` | Current position and rotation |
| `getHeadRotation()` | `Vector3f` | Head rotation |
| `getPacketHandler()` | `PacketHandler` | Network handler |
| `getChunkTracker()` | `ChunkTracker` | Chunk loading tracker |
| `getHiddenPlayersManager()` | `HiddenPlayersManager` | Player visibility manager |
{{< /tab >}}
{{< tab >}}
```java
// Get entity reference from command context
Ref<EntityStore> ref = context.senderAsPlayerRef();
// Get PlayerRef via ref.getStore()
PlayerRef playerRef = ref.getStore().getComponent(ref, PlayerRef.getComponentType());
// Check if player is valid
if (playerRef != null && playerRef.isValid()) {
UUID uuid = playerRef.getUuid();
String username = playerRef.getUsername();
// Get entity reference
Ref<EntityStore> entityRef = playerRef.getReference();
if (entityRef != null) {
// Player is in a world - access via entityRef.getStore()
Player player = entityRef.getStore().getComponent(entityRef, Player.getComponentType());
}
}
// Get component from PlayerRef (convenience method)
Player playerComponent = playerRef.getComponent(Player.getComponentType());
```
{{< /tab >}}
{{< tab >}}
```java
// Send message to player
playerRef.sendMessage(Message.translation("welcome.message"));
// Get packet handler for more advanced operations
PacketHandler handler = playerRef.getPacketHandler();
// Get player position info
Transform transform = playerRef.getTransform();
Vector3d position = transform.getPosition();
Vector3f rotation = transform.getRotation();
Vector3f headRotation = playerRef.getHeadRotation();
// Update position (called internally by systems)
playerRef.updatePosition(world, transform, headRotation);
```
{{< /tab >}}
{{< tab >}}
```java
// Transfer player to another server (for server networks)
playerRef.referToServer("play.example.com", 25565);
// With custom data payload
byte[] referralData = serializePlayerData(playerRef);
playerRef.referToServer("play.example.com", 25565, referralData);
// Note: Max referral data size is 4096 bytes
```
{{< /tab >}}
{{< /tabs >}}
---
## Communication
### Sending Messages
{{< tabs items="Chat Messages,Translation Messages,Notifications" >}}
{{< tab >}}
```java
// Get entity reference from command context
Ref<EntityStore> ref = context.senderAsPlayerRef();
// Send chat message via PlayerRef
PlayerRef playerRef = ref.getStore().getComponent(ref, PlayerRef.getComponentType());
// Simple message
playerRef.sendMessage(Message.raw("Hello, World!"));
// Raw message
playerRef.sendMessage(Message.raw("Plain text message"));
// Using Message interface (IMessageReceiver)
Player player = ref.getStore().getComponent(ref, Player.getComponentType());
player.sendMessage(Message.raw("Message via Player interface"));
```
{{< /tab >}}
{{< tab >}}
```java
// Using translation keys
playerRef.sendMessage(Message.translation("welcome.message"));
// With parameters
playerRef.sendMessage(
Message.translation("greeting.message")
.param("name", playerRef.getUsername())
);
// With color
playerRef.sendMessage(
Message.translation("server.warning")
.color("#ff5555")
);
```
{{< /tab >}}
{{< tab >}}
```java
// Hytale uses Notifications instead of titles/action bars
PacketHandler handler = playerRef.getPacketHandler();
// Simple notification
NotificationUtil.sendNotification(handler, "Welcome to the server!");
// Notification with style
NotificationUtil.sendNotification(handler,
Message.raw("Achievement Unlocked!"),
NotificationStyle.Default
);
// With secondary message
NotificationUtil.sendNotification(handler,
Message.raw("Quest Complete"),
Message.raw("You earned 100 XP"),
NotificationStyle.Default
);
// With icon
NotificationUtil.sendNotification(handler,
Message.raw("New Item"),
"icon_name",
NotificationStyle.Default
);
// With item display
NotificationUtil.sendNotification(handler,
Message.raw("You received:"),
Message.raw("Diamond x10"),
itemWithMetadata,
NotificationStyle.Default
);
// Send to all players in universe
NotificationUtil.sendNotificationToUniverse(
Message.raw("Server restarting in 5 minutes!")
);
```
{{< /tab >}}
{{< /tabs >}}
### Playing Sounds
```java
// Play 3D sound to player
SoundUtil.playSoundEvent3dToPlayer(
playerRef,
TempAssetIdUtil.getSoundEventIndex("SFX_UI_Notification"),
SoundCategory.UI,
position,
componentAccessor
);
// Play 2D sound (no position)
SoundUtil.playSoundEvent2d(
ref,
TempAssetIdUtil.getSoundEventIndex("SFX_Player_Pickup_Item"),
SoundCategory.UI,
componentAccessor
);
// Play sound via world
world.playSound(soundEvent, position, volume, pitch);
```
---
## Health & Stats
{{< callout type="info" >}}
Hytale uses an **EntityStats system** for managing health and other stats. Stats are managed via `EntityStatComponent` and include Health, Oxygen, Stamina, Mana, SignatureEnergy, and Ammo.
{{< /callout >}}
### Entity Stats System
{{< tabs items="Available Stats,Accessing Stats,Modifying Stats" >}}
{{< tab >}}
| Stat | Description |
|------|-------------|
| `Health` | Entity health points |
| `Oxygen` | Breath/oxygen level |
| `Stamina` | Physical stamina |
| `Mana` | Magical energy |
| `SignatureEnergy` | Special ability energy |
| `Ammo` | Ammunition count |
These stats are defined via asset configuration and indexed at runtime.
```java
// Get stat indices (from DefaultEntityStatTypes)
int healthIndex = DefaultEntityStatTypes.getHealth();
int staminaIndex = DefaultEntityStatTypes.getStamina();
int manaIndex = DefaultEntityStatTypes.getMana();
```
{{< /tab >}}
{{< tab >}}
```java
// Get entity reference from command context
Ref<EntityStore> ref = context.senderAsPlayerRef();
// Get EntityStatComponent from entity via ref.getStore()
EntityStatComponent statsComponent = ref.getStore().getComponent(
ref,
EntityStatComponent.getComponentType()
);
// Get specific stat value
int healthIndex = DefaultEntityStatTypes.getHealth();
float currentHealth = statsComponent.getValue(healthIndex);
float maxHealth = statsComponent.getMaxValue(healthIndex);
// Check percentage
float healthPercent = currentHealth / maxHealth;
```
{{< /tab >}}
{{< tab >}}
```java
// Modify stat values through the stats system
// Stats are typically modified through the EntityStatsModule
// and related systems (DamageSystems, HealSystems, etc.)
// Example: Apply damage via component
// The DamageSystems handle damage application
ref.getStore().putComponent(ref, Damage.getComponentType(),
new Damage(damageAmount, damageSource));
```
{{< /tab >}}
{{< /tabs >}}
### Invulnerability
```java
// Get entity reference from command context
Ref<EntityStore> ref = context.senderAsPlayerRef();
// Make player invulnerable (adds Invulnerable component)
ref.getStore().putComponent(ref, Invulnerable.getComponentType(), Invulnerable.INSTANCE);
// Remove invulnerability
ref.getStore().tryRemoveComponent(ref, Invulnerable.getComponentType());
// Check invulnerability
boolean isInvulnerable = ref.getStore().getArchetype(ref)
.contains(Invulnerable.getComponentType());
// Note: Creative mode automatically sets Invulnerable
```
### Spawn Protection
```java
// Get entity reference from command context
Ref<EntityStore> ref = context.senderAsPlayerRef();
// Players have temporary invulnerability after spawning
Player player = ref.getStore().getComponent(ref, Player.getComponentType());
// Check if player has spawn protection (3 seconds after spawn)
if (player.hasSpawnProtection()) {
// Player is temporarily invulnerable
}
// Check if waiting for client ready
if (player.isWaitingForClientReady()) {
// Player hasn't finished loading yet
}
```
---
## Inventory API
### Basic Inventory Operations
{{< tabs items="Get Items,Set Items,Utility Methods" >}}
{{< tab >}}
```java
// Get inventory
PlayerInventory inventory = player.getInventory();
// Get item in hand
ItemStack heldItem = player.getHeldItem();
ItemStack mainHand = player.getMainHandItem();
ItemStack offHand = player.getOffHandItem();
// Get specific slot
ItemStack slot5 = inventory.getItem(5);
// Get armor
ArmorInventory armor = player.getArmor();
ItemStack helmet = armor.getHelmet();
ItemStack chestplate = armor.getChestplate();
ItemStack leggings = armor.getLeggings();
ItemStack boots = armor.getBoots();
```
{{< /tab >}}
{{< tab >}}
```java
// Set held item
ItemStack sword = new ItemStack(ItemTypes.DIAMOND_SWORD);
player.setHeldItem(sword);
// Set specific slot
inventory.setItem(0, new ItemStack(ItemTypes.APPLE, 64));
// Set armor
armor.setHelmet(new ItemStack(ItemTypes.DIAMOND_HELMET));
armor.setChestplate(new ItemStack(ItemTypes.DIAMOND_CHESTPLATE));
armor.setLeggings(new ItemStack(ItemTypes.DIAMOND_LEGGINGS));
armor.setBoots(new ItemStack(ItemTypes.DIAMOND_BOOTS));
// Clear slot
inventory.setItem(5, null);
```
{{< /tab >}}
{{< tab >}}
```java
// Give item (finds empty slot)
ItemStack diamond = new ItemStack(ItemTypes.DIAMOND, 64);
boolean success = player.giveItem(diamond);
if (!success) {
// Inventory full, drop at feet
player.dropItem(diamond);
}
// Check for item
if (inventory.contains(ItemTypes.DIAMOND)) {
getLogger().at(Level.INFO).log("Player has diamonds");
}
// Remove item
inventory.remove(ItemTypes.DIAMOND, 10);
// Clear inventory
inventory.clear();
// Count items
int diamondCount = inventory.countItem(ItemTypes.DIAMOND);
```
{{< /tab >}}
{{< /tabs >}}
### Advanced Inventory
```java
// Open another inventory (chest, custom UI)
Inventory chest = world.getBlockEntity(chestPos).getInventory();
player.openInventory(chest);
// Create custom inventory
Inventory customInv = new Inventory(27, "Shop"); // 27 slots
customInv.setItem(13, new ItemStack(ItemTypes.DIAMOND, 1));
player.openInventory(customInv);
// Close open inventory
player.closeInventory();
// Get cursor item (what player is holding with mouse)
ItemStack cursor = player.getCursorItem();
player.setCursorItem(null);
```
---
## Permissions
### Permission Checking
{{< tabs items="Basic Checks,Permission Levels,Custom Permissions" >}}
{{< tab >}}
```java
// Check single permission
if (player.hasPermission("myplugin.admin")) {
player.sendMessage("You are an admin!");
}
// Check operator status
if (player.isOp()) {
player.sendMessage("You are an operator!");
}
// Multiple permission check
if (player.hasPermission("myplugin.fly") ||
player.hasPermission("myplugin.admin")) {
enableFlight(player);
}
```
{{< /tab >}}
{{< tab >}}
```java
// Permission hierarchy example
public boolean hasMinimumRank(Player player, String rank) {
switch (rank) {
case "owner":
return player.hasPermission("rank.owner");
case "admin":
return player.hasPermission("rank.admin") ||
player.hasPermission("rank.owner");
case "mod":
return player.hasPermission("rank.mod") ||
player.hasPermission("rank.admin") ||
player.hasPermission("rank.owner");
default:
return true;
}
}
```
{{< /tab >}}
{{< tab >}}
```java
// Grant permission
player.addPermission("myplugin.vip");
// Revoke permission
player.removePermission("myplugin.vip");
// Get all permissions
Set<String> permissions = player.getPermissions();
for (String perm : permissions) {
getLogger().at(Level.INFO).log("Has permission: " + perm);
}
// Set operator status
player.setOp(true);
```
{{< /tab >}}
{{< /tabs >}}
---
## Movement & Teleportation
### Position & Movement
{{< tabs items="Position Methods,Movement State,Velocity" >}}
{{< tab >}}
| Method | Return Type | Description |
|--------|-------------|-------------|
| `getPosition()` | `Vector3d` | Current position |
| `setPosition(Vector3d)` | `void` | Set position |
| `getEyePosition()` | `Vector3d` | Eye level position |
| `getRotation()` | `Vector3f` | Look direction |
| `setRotation(Vector3f)` | `void` | Set look direction |
| `getEyeDirection()` | `Vector3d` | Normalized look vector |
{{< /tab >}}
{{< tab >}}
| Method | Return Type | Description |
|--------|-------------|-------------|
| `isOnGround()` | `boolean` | Whether on solid ground |
| `isSprinting()` | `boolean` | Whether sprinting |
| `setSprinting(boolean)` | `void` | Set sprint state |
| `isSneaking()` | `boolean` | Whether sneaking |
| `setSneaking(boolean)` | `void` | Set sneak state |
| `isFlying()` | `boolean` | Whether flying |
| `setFlying(boolean)` | `void` | Set flying state |
| `isSwimming()` | `boolean` | Whether swimming |
{{< /tab >}}
{{< tab >}}
```java
// Get velocity
Vector3d velocity = player.getVelocity();
// Set velocity (launch player)
player.setVelocity(new Vector3d(0, 1.5, 0));
// Add to velocity
player.addVelocity(new Vector3d(1, 0, 0));
// Apply knockback
player.knockback(new Vector3d(-0.5, 0.5, -0.5));
```
{{< /tab >}}
{{< /tabs >}}
### Teleportation
{{< callout type="warning" >}}
Hytale uses a **component-based teleportation system**. You don't call `player.teleport()` directly. Instead, you add a `Teleport` component to the entity, and the system handles the teleportation.
{{< /callout >}}
{{< tabs items="Basic Usage,Cross-World,Options,System Details" >}}
{{< tab >}}
```java
// Get player reference from command context
Ref<EntityStore> playerRef = context.senderAsPlayerRef();
// Teleport to position in same world
Teleport teleport = new Teleport(
new Vector3d(100, 64, 100), // position
new Vector3f(0, 90, 0) // rotation (pitch, yaw, roll)
);
// Add the teleport component to trigger teleportation
playerRef.getStore().putComponent(playerRef, Teleport.getComponentType(), teleport);
// Using Transform
Transform destination = new Transform(position, rotation);
Teleport teleportFromTransform = new Teleport(destination);
```
{{< /tab >}}
{{< tab >}}
```java
// Get player reference from command context
Ref<EntityStore> playerRef = context.senderAsPlayerRef();
// Teleport to another world
World targetWorld = Universe.get().getWorld("other_world");
Teleport teleport = new Teleport(
targetWorld, // target world
new Vector3d(0, 64, 0), // position
new Vector3f(0, 0, 0) // rotation
);
playerRef.getStore().putComponent(playerRef, Teleport.getComponentType(), teleport);
// The TeleportSystems.PlayerMoveSystem handles:
// - Same world: Direct position update
// - Different world: Player removal and re-addition to target world
```
{{< /tab >}}
{{< tab >}}
```java
// Teleport with options using fluent builder
Teleport teleport = new Teleport(targetWorld, position, rotation)
.withHeadRotation(new Vector3f(0, 45, 0)) // Set head rotation
.withResetRoll() // Reset roll to 0
.withoutVelocityReset(); // Keep current velocity
// By default:
// - resetVelocity = true (player stops moving)
// - headRotation = same as body rotation
```
{{< /tab >}}
{{< tab >}}
The teleportation flow:
1. **Add Teleport component** to entity
2. **TeleportSystems.PlayerMoveSystem** detects the component
3. System sends `ClientTeleport` packet to player
4. **PendingTeleport** component tracks the teleport
5. When client confirms, position is updated
6. **Teleport component is removed** automatically
```java
// Internal packet sent:
ClientTeleport packet = new ClientTeleport(
teleportId,
new ModelTransform(position, direction, headRotation),
resetVelocity
);
```
{{< /tab >}}
{{< /tabs >}}
---
## Game Mode
{{< callout type="info" >}}
Hytale currently has **two game modes**: `Adventure` and `Creative`. These differ from Minecraft's game modes.
{{< /callout >}}
{{< tabs items="Available Modes,Getting/Setting,Creative Features" >}}
{{< tab >}}
| Mode | Value | Description |
|------|-------|-------------|
| `GameMode.Adventure` | 0 | Standard gameplay with survival mechanics |
| `GameMode.Creative` | 1 | Creative mode with building freedom |
{{< /tab >}}
{{< tab >}}
```java
// Get current game mode
GameMode mode = player.getGameMode();
// Check game mode
if (mode == GameMode.Creative) {
// Creative mode logic
} else if (mode == GameMode.Adventure) {
// Adventure mode logic
}
// Set game mode (requires component accessor)
// Note: This is typically done through systems/commands
Player.setGameMode(playerRef, GameMode.Creative, componentAccessor);
```
{{< /tab >}}
{{< tab >}}
When in Creative mode:
- Player is marked as `Invulnerable`
- `canFly` is enabled in MovementManager
- `canDecreaseItemStackDurability` returns false
- `canApplyItemStackPenalties` returns false
- Permission groups from GameModeType are applied
```java
// GameMode affects these behaviors automatically:
// - Flight enabled/disabled
// - Invulnerability
// - Item durability loss
// - Block placement restrictions
```
{{< /tab >}}
{{< /tabs >}}
---
## Server Actions
### Disconnecting Players
```java
// Get entity reference from command context
Ref<EntityStore> ref = context.senderAsPlayerRef();
// Disconnect player via PacketHandler
PlayerRef playerRef = ref.getStore().getComponent(ref, PlayerRef.getComponentType());
// Disconnect with reason
playerRef.getPacketHandler().disconnect("You have been kicked!");
// Disconnect with translation message
playerRef.getPacketHandler().disconnect(
Message.translation("server.kick.reason").toString()
);
```
### Player Lookup
```java
// Get all online players through Universe
Universe universe = Universe.get();
// Players are accessed through world contexts
World world = universe.getWorld("main");
// Iterate players in world via entity store
// Players are entities with PlayerRef component
world.getEntityStore().forEach((ref, store) -> {
PlayerRef playerRef = store.getComponent(ref, PlayerRef.getComponentType());
if (playerRef != null) {
String username = playerRef.getUsername();
UUID uuid = playerRef.getUuid();
// Process player...
}
});
```
### Server Transfer
```java
// Get entity reference from command context
Ref<EntityStore> ref = context.senderAsPlayerRef();
// Transfer player to another server (for server networks)
PlayerRef playerRef = ref.getStore().getComponent(ref, PlayerRef.getComponentType());
// Redirect player to another server
playerRef.referToServer("play.example.com", 25565);
// With custom data (max 4096 bytes)
byte[] playerData = serializePlayerState(playerRef);
playerRef.referToServer("play.example.com", 25565, playerData);
```
---
## Practical Examples
### Welcome Plugin
```java
public class WelcomePlugin extends ServerPlugin {
@Override
public void setup(PluginSetup pluginSetup) {
// Register event listeners
}
@Override
public void start() {
// Subscribe to player connect event
HytaleServer.get().getEventBus()
.subscribe(PlayerConnectEvent.class, this::onPlayerConnect);
}
private void onPlayerConnect(PlayerConnectEvent event) {
String username = event.getUsername();
UUID uuid = event.getUuid();
getLogger().at(Level.INFO).log("Player connected: " + username + " (" + uuid + ")");
// Send welcome message after player is ready
// Note: PlayerReadyEvent has String key, so use registerGlobal
getEventRegistry().registerGlobal(PlayerReadyEvent.class, readyEvent -> {
Player player = readyEvent.getPlayer();
player.sendMessage(Message.raw("Welcome to the server, " + username + "!"));
});
}
@Override
public void shutdown() {
// Cleanup
}
}
```
### Permission Check Example
```java
public class PermissionPlugin extends ServerPlugin {
@Override
public void start() {
CommandRegistry registry = getCommandRegistry();
registry.register("admin", context -> {
CommandSender sender = context.getSource();
// Check permission
if (sender instanceof Player player) {
if (!player.hasPermission("myplugin.admin")) {
player.sendMessage(Message.raw("No permission!"));
return;
}
player.sendMessage(Message.raw("You are an admin!"));
}
});
}
}
```
### Inventory Management
```java
// Get entity reference from command context
Ref<EntityStore> ref = context.senderAsPlayerRef();
// Working with player inventory
Player player = ref.getStore().getComponent(ref, Player.getComponentType());
Inventory inventory = player.getInventory();
// Add item to inventory
ItemStack itemStack = new ItemStack(itemType, amount);
inventory.addItem(itemStack);
// Send inventory update to client
player.sendInventory();
// Access hotbar
HotbarManager hotbar = player.getHotbarManager();
```
---
## Best Practices
{{< callout type="info" >}}
**Player API Guidelines:**
- Use `PlayerRef` to get thread-safe player information
- Check `isValid()` before operations on stored references
- Use permissions via `player.hasPermission()` for access control
- Understand the component system: players are entities with components
{{< /callout >}}
{{< callout type="warning" >}}
**Component System:** Players in Hytale are entities managed by an ECS. Access player data via `ref.getStore()` and appropriate component types (`Player.getComponentType()`, `PlayerRef.getComponentType()`).
{{< /callout >}}
{{< callout type="error" >}}
**Critical:** Never store `Ref<EntityStore>` or `Player` references directly in static fields. Use `UUID` for storage and retrieve references via the component system when needed.
{{< /callout >}}

View File

@@ -0,0 +1,904 @@
---
title: Player API
type: docs
weight: 4
---
The Player class provides extensive APIs for player management, including communication, health, inventory, permissions, and more.
## API Overview
{{< cards cols="3" >}}
{{< card link="#identity--connection" title="Identity & Connection" subtitle="Name, UUID, network" >}}
{{< card link="#communication" title="Communication" subtitle="Messages, titles, sounds" >}}
{{< card link="#health--combat" title="Health & Combat" subtitle="HP, damage, effects" >}}
{{< card link="#inventory-api" title="Inventory" subtitle="Items, slots, armor" >}}
{{< card link="#permissions" title="Permissions" subtitle="Access control" >}}
{{< card link="#movement--teleportation" title="Movement" subtitle="Teleport, position" >}}
{{< /cards >}}
---
## Identity & Connection
### Basic Information
{{< tabs items="Methods,Example" >}}
{{< tab >}}
| Method | Return Type | Description |
|--------|-------------|-------------|
| `getUuid()` | `UUID` | Unique player identifier |
| `getDisplayName()` | `String` | Display name (from PlayerRef username) |
| `getClientViewRadius()` | `int` | Player's view radius in chunks |
| `getViewRadius()` | `int` | Effective view radius (min of client and server) |
| `hasPermission(String)` | `boolean` | Check permission |
| `sendMessage(Message)` | `void` | Send message to player |
| `getInventory()` | `Inventory` | Player's inventory |
| `getGameMode()` | `GameMode` | Current game mode |
{{< /tab >}}
{{< tab >}}
```java
// Get entity reference from command context
Ref<EntityStore> ref = context.senderAsPlayerRef();
// Access Player component via ref.getStore()
Player player = ref.getStore().getComponent(ref, Player.getComponentType());
UUID uuid = player.getUuid();
String displayName = player.getDisplayName();
int viewRadius = player.getViewRadius();
GameMode mode = player.getGameMode();
// Check permission
if (player.hasPermission("myplugin.admin")) {
player.sendMessage(Message.raw("You are an admin!"));
}
// Get PlayerRef for more info
PlayerRef playerRef = ref.getStore().getComponent(ref, PlayerRef.getComponentType());
String username = playerRef.getUsername();
String language = playerRef.getLanguage();
```
{{< /tab >}}
{{< /tabs >}}
### PlayerRef (Thread-Safe Reference)
{{< callout type="info" >}}
**Important:** `PlayerRef` is a component that provides thread-safe access to player data. It's stored in the entity component system and manages the player's connection, position, and various managers.
{{< /callout >}}
{{< tabs items="Methods,Entity Reference,Connection & Messaging,Server Transfer" >}}
{{< tab >}}
| Method | Return Type | Description |
|--------|-------------|-------------|
| `getUuid()` | `UUID` | Player's unique identifier |
| `getUsername()` | `String` | Player's username |
| `getLanguage()` | `String` | Player's language setting |
| `setLanguage(String)` | `void` | Update language |
| `getReference()` | `Ref<EntityStore>` | Entity reference (null if not in world) |
| `getHolder()` | `Holder<EntityStore>` | Entity holder (when not in store) |
| `isValid()` | `boolean` | Whether reference or holder exists |
| `getTransform()` | `Transform` | Current position and rotation |
| `getHeadRotation()` | `Vector3f` | Head rotation |
| `getPacketHandler()` | `PacketHandler` | Network handler |
| `getChunkTracker()` | `ChunkTracker` | Chunk loading tracker |
| `getHiddenPlayersManager()` | `HiddenPlayersManager` | Player visibility manager |
{{< /tab >}}
{{< tab >}}
```java
// Get entity reference from command context
Ref<EntityStore> ref = context.senderAsPlayerRef();
// Get PlayerRef via ref.getStore()
PlayerRef playerRef = ref.getStore().getComponent(ref, PlayerRef.getComponentType());
// Check if player is valid
if (playerRef != null && playerRef.isValid()) {
UUID uuid = playerRef.getUuid();
String username = playerRef.getUsername();
// Get entity reference
Ref<EntityStore> entityRef = playerRef.getReference();
if (entityRef != null) {
// Player is in a world - access via entityRef.getStore()
Player player = entityRef.getStore().getComponent(entityRef, Player.getComponentType());
}
}
// Get component from PlayerRef (convenience method)
Player playerComponent = playerRef.getComponent(Player.getComponentType());
```
{{< /tab >}}
{{< tab >}}
```java
// Send message to player
playerRef.sendMessage(Message.translation("welcome.message"));
// Get packet handler for more advanced operations
PacketHandler handler = playerRef.getPacketHandler();
// Get player position info
Transform transform = playerRef.getTransform();
Vector3d position = transform.getPosition();
Vector3f rotation = transform.getRotation();
Vector3f headRotation = playerRef.getHeadRotation();
// Update position (called internally by systems)
playerRef.updatePosition(world, transform, headRotation);
```
{{< /tab >}}
{{< tab >}}
```java
// Transfer player to another server (for server networks)
playerRef.referToServer("play.example.com", 25565);
// With custom data payload
byte[] referralData = serializePlayerData(playerRef);
playerRef.referToServer("play.example.com", 25565, referralData);
// Note: Max referral data size is 4096 bytes
```
{{< /tab >}}
{{< /tabs >}}
---
## Communication
### Sending Messages
{{< tabs items="Chat Messages,Translation Messages,Notifications" >}}
{{< tab >}}
```java
// Get entity reference from command context
Ref<EntityStore> ref = context.senderAsPlayerRef();
// Send chat message via PlayerRef
PlayerRef playerRef = ref.getStore().getComponent(ref, PlayerRef.getComponentType());
// Simple message
playerRef.sendMessage(Message.raw("Hello, World!"));
// Raw message
playerRef.sendMessage(Message.raw("Plain text message"));
// Using Message interface (IMessageReceiver)
Player player = ref.getStore().getComponent(ref, Player.getComponentType());
player.sendMessage(Message.raw("Message via Player interface"));
```
{{< /tab >}}
{{< tab >}}
```java
// Using translation keys
playerRef.sendMessage(Message.translation("welcome.message"));
// With parameters
playerRef.sendMessage(
Message.translation("greeting.message")
.param("name", playerRef.getUsername())
);
// With color
playerRef.sendMessage(
Message.translation("server.warning")
.color("#ff5555")
);
```
{{< /tab >}}
{{< tab >}}
```java
// Hytale uses Notifications instead of titles/action bars
PacketHandler handler = playerRef.getPacketHandler();
// Simple notification
NotificationUtil.sendNotification(handler, "Welcome to the server!");
// Notification with style
NotificationUtil.sendNotification(handler,
Message.raw("Achievement Unlocked!"),
NotificationStyle.Default
);
// With secondary message
NotificationUtil.sendNotification(handler,
Message.raw("Quest Complete"),
Message.raw("You earned 100 XP"),
NotificationStyle.Default
);
// With icon
NotificationUtil.sendNotification(handler,
Message.raw("New Item"),
"icon_name",
NotificationStyle.Default
);
// With item display
NotificationUtil.sendNotification(handler,
Message.raw("You received:"),
Message.raw("Diamond x10"),
itemWithMetadata,
NotificationStyle.Default
);
// Send to all players in universe
NotificationUtil.sendNotificationToUniverse(
Message.raw("Server restarting in 5 minutes!")
);
```
{{< /tab >}}
{{< /tabs >}}
### Playing Sounds
```java
// Play 3D sound to player
SoundUtil.playSoundEvent3dToPlayer(
playerRef,
TempAssetIdUtil.getSoundEventIndex("SFX_UI_Notification"),
SoundCategory.UI,
position,
componentAccessor
);
// Play 2D sound (no position)
SoundUtil.playSoundEvent2d(
ref,
TempAssetIdUtil.getSoundEventIndex("SFX_Player_Pickup_Item"),
SoundCategory.UI,
componentAccessor
);
// Play sound via world
world.playSound(soundEvent, position, volume, pitch);
```
---
## Health & Stats
{{< callout type="info" >}}
Hytale uses an **EntityStats system** for managing health and other stats. Stats are managed via `EntityStatComponent` and include Health, Oxygen, Stamina, Mana, SignatureEnergy, and Ammo.
{{< /callout >}}
### Entity Stats System
{{< tabs items="Available Stats,Accessing Stats,Modifying Stats" >}}
{{< tab >}}
| Stat | Description |
|------|-------------|
| `Health` | Entity health points |
| `Oxygen` | Breath/oxygen level |
| `Stamina` | Physical stamina |
| `Mana` | Magical energy |
| `SignatureEnergy` | Special ability energy |
| `Ammo` | Ammunition count |
These stats are defined via asset configuration and indexed at runtime.
```java
// Get stat indices (from DefaultEntityStatTypes)
int healthIndex = DefaultEntityStatTypes.getHealth();
int staminaIndex = DefaultEntityStatTypes.getStamina();
int manaIndex = DefaultEntityStatTypes.getMana();
```
{{< /tab >}}
{{< tab >}}
```java
// Get entity reference from command context
Ref<EntityStore> ref = context.senderAsPlayerRef();
// Get EntityStatComponent from entity via ref.getStore()
EntityStatComponent statsComponent = ref.getStore().getComponent(
ref,
EntityStatComponent.getComponentType()
);
// Get specific stat value
int healthIndex = DefaultEntityStatTypes.getHealth();
float currentHealth = statsComponent.getValue(healthIndex);
float maxHealth = statsComponent.getMaxValue(healthIndex);
// Check percentage
float healthPercent = currentHealth / maxHealth;
```
{{< /tab >}}
{{< tab >}}
```java
// Modify stat values through the stats system
// Stats are typically modified through the EntityStatsModule
// and related systems (DamageSystems, HealSystems, etc.)
// Example: Apply damage via component
// The DamageSystems handle damage application
ref.getStore().putComponent(ref, Damage.getComponentType(),
new Damage(damageAmount, damageSource));
```
{{< /tab >}}
{{< /tabs >}}
### Invulnerability
```java
// Get entity reference from command context
Ref<EntityStore> ref = context.senderAsPlayerRef();
// Make player invulnerable (adds Invulnerable component)
ref.getStore().putComponent(ref, Invulnerable.getComponentType(), Invulnerable.INSTANCE);
// Remove invulnerability
ref.getStore().tryRemoveComponent(ref, Invulnerable.getComponentType());
// Check invulnerability
boolean isInvulnerable = ref.getStore().getArchetype(ref)
.contains(Invulnerable.getComponentType());
// Note: Creative mode automatically sets Invulnerable
```
### Spawn Protection
```java
// Get entity reference from command context
Ref<EntityStore> ref = context.senderAsPlayerRef();
// Players have temporary invulnerability after spawning
Player player = ref.getStore().getComponent(ref, Player.getComponentType());
// Check if player has spawn protection (3 seconds after spawn)
if (player.hasSpawnProtection()) {
// Player is temporarily invulnerable
}
// Check if waiting for client ready
if (player.isWaitingForClientReady()) {
// Player hasn't finished loading yet
}
```
---
## Inventory API
### Basic Inventory Operations
{{< tabs items="Get Items,Set Items,Utility Methods" >}}
{{< tab >}}
```java
// Get inventory
PlayerInventory inventory = player.getInventory();
// Get item in hand
ItemStack heldItem = player.getHeldItem();
ItemStack mainHand = player.getMainHandItem();
ItemStack offHand = player.getOffHandItem();
// Get specific slot
ItemStack slot5 = inventory.getItem(5);
// Get armor
ArmorInventory armor = player.getArmor();
ItemStack helmet = armor.getHelmet();
ItemStack chestplate = armor.getChestplate();
ItemStack leggings = armor.getLeggings();
ItemStack boots = armor.getBoots();
```
{{< /tab >}}
{{< tab >}}
```java
// Set held item
ItemStack sword = new ItemStack(ItemTypes.DIAMOND_SWORD);
player.setHeldItem(sword);
// Set specific slot
inventory.setItem(0, new ItemStack(ItemTypes.APPLE, 64));
// Set armor
armor.setHelmet(new ItemStack(ItemTypes.DIAMOND_HELMET));
armor.setChestplate(new ItemStack(ItemTypes.DIAMOND_CHESTPLATE));
armor.setLeggings(new ItemStack(ItemTypes.DIAMOND_LEGGINGS));
armor.setBoots(new ItemStack(ItemTypes.DIAMOND_BOOTS));
// Clear slot
inventory.setItem(5, null);
```
{{< /tab >}}
{{< tab >}}
```java
// Give item (finds empty slot)
ItemStack diamond = new ItemStack(ItemTypes.DIAMOND, 64);
boolean success = player.giveItem(diamond);
if (!success) {
// Inventory full, drop at feet
player.dropItem(diamond);
}
// Check for item
if (inventory.contains(ItemTypes.DIAMOND)) {
getLogger().at(Level.INFO).log("Player has diamonds");
}
// Remove item
inventory.remove(ItemTypes.DIAMOND, 10);
// Clear inventory
inventory.clear();
// Count items
int diamondCount = inventory.countItem(ItemTypes.DIAMOND);
```
{{< /tab >}}
{{< /tabs >}}
### Advanced Inventory
```java
// Open another inventory (chest, custom UI)
Inventory chest = world.getBlockEntity(chestPos).getInventory();
player.openInventory(chest);
// Create custom inventory
Inventory customInv = new Inventory(27, "Shop"); // 27 slots
customInv.setItem(13, new ItemStack(ItemTypes.DIAMOND, 1));
player.openInventory(customInv);
// Close open inventory
player.closeInventory();
// Get cursor item (what player is holding with mouse)
ItemStack cursor = player.getCursorItem();
player.setCursorItem(null);
```
---
## Permissions
### Permission Checking
{{< tabs items="Basic Checks,Permission Levels,Custom Permissions" >}}
{{< tab >}}
```java
// Check single permission
if (player.hasPermission("myplugin.admin")) {
player.sendMessage("You are an admin!");
}
// Check operator status
if (player.isOp()) {
player.sendMessage("You are an operator!");
}
// Multiple permission check
if (player.hasPermission("myplugin.fly") ||
player.hasPermission("myplugin.admin")) {
enableFlight(player);
}
```
{{< /tab >}}
{{< tab >}}
```java
// Permission hierarchy example
public boolean hasMinimumRank(Player player, String rank) {
switch (rank) {
case "owner":
return player.hasPermission("rank.owner");
case "admin":
return player.hasPermission("rank.admin") ||
player.hasPermission("rank.owner");
case "mod":
return player.hasPermission("rank.mod") ||
player.hasPermission("rank.admin") ||
player.hasPermission("rank.owner");
default:
return true;
}
}
```
{{< /tab >}}
{{< tab >}}
```java
// Grant permission
player.addPermission("myplugin.vip");
// Revoke permission
player.removePermission("myplugin.vip");
// Get all permissions
Set<String> permissions = player.getPermissions();
for (String perm : permissions) {
getLogger().at(Level.INFO).log("Has permission: " + perm);
}
// Set operator status
player.setOp(true);
```
{{< /tab >}}
{{< /tabs >}}
---
## Movement & Teleportation
### Position & Movement
{{< tabs items="Position Methods,Movement State,Velocity" >}}
{{< tab >}}
| Method | Return Type | Description |
|--------|-------------|-------------|
| `getPosition()` | `Vector3d` | Current position |
| `setPosition(Vector3d)` | `void` | Set position |
| `getEyePosition()` | `Vector3d` | Eye level position |
| `getRotation()` | `Vector3f` | Look direction |
| `setRotation(Vector3f)` | `void` | Set look direction |
| `getEyeDirection()` | `Vector3d` | Normalized look vector |
{{< /tab >}}
{{< tab >}}
| Method | Return Type | Description |
|--------|-------------|-------------|
| `isOnGround()` | `boolean` | Whether on solid ground |
| `isSprinting()` | `boolean` | Whether sprinting |
| `setSprinting(boolean)` | `void` | Set sprint state |
| `isSneaking()` | `boolean` | Whether sneaking |
| `setSneaking(boolean)` | `void` | Set sneak state |
| `isFlying()` | `boolean` | Whether flying |
| `setFlying(boolean)` | `void` | Set flying state |
| `isSwimming()` | `boolean` | Whether swimming |
{{< /tab >}}
{{< tab >}}
```java
// Get velocity
Vector3d velocity = player.getVelocity();
// Set velocity (launch player)
player.setVelocity(new Vector3d(0, 1.5, 0));
// Add to velocity
player.addVelocity(new Vector3d(1, 0, 0));
// Apply knockback
player.knockback(new Vector3d(-0.5, 0.5, -0.5));
```
{{< /tab >}}
{{< /tabs >}}
### Teleportation
{{< callout type="warning" >}}
Hytale uses a **component-based teleportation system**. You don't call `player.teleport()` directly. Instead, you add a `Teleport` component to the entity, and the system handles the teleportation.
{{< /callout >}}
{{< tabs items="Basic Usage,Cross-World,Options,System Details" >}}
{{< tab >}}
```java
// Get player reference from command context
Ref<EntityStore> playerRef = context.senderAsPlayerRef();
// Teleport to position in same world
Teleport teleport = new Teleport(
new Vector3d(100, 64, 100), // position
new Vector3f(0, 90, 0) // rotation (pitch, yaw, roll)
);
// Add the teleport component to trigger teleportation
playerRef.getStore().putComponent(playerRef, Teleport.getComponentType(), teleport);
// Using Transform
Transform destination = new Transform(position, rotation);
Teleport teleportFromTransform = new Teleport(destination);
```
{{< /tab >}}
{{< tab >}}
```java
// Get player reference from command context
Ref<EntityStore> playerRef = context.senderAsPlayerRef();
// Teleport to another world
World targetWorld = Universe.get().getWorld("other_world");
Teleport teleport = new Teleport(
targetWorld, // target world
new Vector3d(0, 64, 0), // position
new Vector3f(0, 0, 0) // rotation
);
playerRef.getStore().putComponent(playerRef, Teleport.getComponentType(), teleport);
// The TeleportSystems.PlayerMoveSystem handles:
// - Same world: Direct position update
// - Different world: Player removal and re-addition to target world
```
{{< /tab >}}
{{< tab >}}
```java
// Teleport with options using fluent builder
Teleport teleport = new Teleport(targetWorld, position, rotation)
.withHeadRotation(new Vector3f(0, 45, 0)) // Set head rotation
.withResetRoll() // Reset roll to 0
.withoutVelocityReset(); // Keep current velocity
// By default:
// - resetVelocity = true (player stops moving)
// - headRotation = same as body rotation
```
{{< /tab >}}
{{< tab >}}
The teleportation flow:
1. **Add Teleport component** to entity
2. **TeleportSystems.PlayerMoveSystem** detects the component
3. System sends `ClientTeleport` packet to player
4. **PendingTeleport** component tracks the teleport
5. When client confirms, position is updated
6. **Teleport component is removed** automatically
```java
// Internal packet sent:
ClientTeleport packet = new ClientTeleport(
teleportId,
new ModelTransform(position, direction, headRotation),
resetVelocity
);
```
{{< /tab >}}
{{< /tabs >}}
---
## Game Mode
{{< callout type="info" >}}
Hytale currently has **two game modes**: `Adventure` and `Creative`. These differ from Minecraft's game modes.
{{< /callout >}}
{{< tabs items="Available Modes,Getting/Setting,Creative Features" >}}
{{< tab >}}
| Mode | Value | Description |
|------|-------|-------------|
| `GameMode.Adventure` | 0 | Standard gameplay with survival mechanics |
| `GameMode.Creative` | 1 | Creative mode with building freedom |
{{< /tab >}}
{{< tab >}}
```java
// Get current game mode
GameMode mode = player.getGameMode();
// Check game mode
if (mode == GameMode.Creative) {
// Creative mode logic
} else if (mode == GameMode.Adventure) {
// Adventure mode logic
}
// Set game mode (requires component accessor)
// Note: This is typically done through systems/commands
Player.setGameMode(playerRef, GameMode.Creative, componentAccessor);
```
{{< /tab >}}
{{< tab >}}
When in Creative mode:
- Player is marked as `Invulnerable`
- `canFly` is enabled in MovementManager
- `canDecreaseItemStackDurability` returns false
- `canApplyItemStackPenalties` returns false
- Permission groups from GameModeType are applied
```java
// GameMode affects these behaviors automatically:
// - Flight enabled/disabled
// - Invulnerability
// - Item durability loss
// - Block placement restrictions
```
{{< /tab >}}
{{< /tabs >}}
---
## Server Actions
### Disconnecting Players
```java
// Get entity reference from command context
Ref<EntityStore> ref = context.senderAsPlayerRef();
// Disconnect player via PacketHandler
PlayerRef playerRef = ref.getStore().getComponent(ref, PlayerRef.getComponentType());
// Disconnect with reason
playerRef.getPacketHandler().disconnect("You have been kicked!");
// Disconnect with translation message
playerRef.getPacketHandler().disconnect(
Message.translation("server.kick.reason").toString()
);
```
### Player Lookup
```java
// Get all online players through Universe
Universe universe = Universe.get();
// Players are accessed through world contexts
World world = universe.getWorld("main");
// Iterate players in world via entity store
// Players are entities with PlayerRef component
world.getEntityStore().forEach((ref, store) -> {
PlayerRef playerRef = store.getComponent(ref, PlayerRef.getComponentType());
if (playerRef != null) {
String username = playerRef.getUsername();
UUID uuid = playerRef.getUuid();
// Process player...
}
});
```
### Server Transfer
```java
// Get entity reference from command context
Ref<EntityStore> ref = context.senderAsPlayerRef();
// Transfer player to another server (for server networks)
PlayerRef playerRef = ref.getStore().getComponent(ref, PlayerRef.getComponentType());
// Redirect player to another server
playerRef.referToServer("play.example.com", 25565);
// With custom data (max 4096 bytes)
byte[] playerData = serializePlayerState(playerRef);
playerRef.referToServer("play.example.com", 25565, playerData);
```
---
## Practical Examples
### Welcome Plugin
```java
public class WelcomePlugin extends ServerPlugin {
@Override
public void setup(PluginSetup pluginSetup) {
// Register event listeners
}
@Override
public void start() {
// Subscribe to player connect event
HytaleServer.get().getEventBus()
.subscribe(PlayerConnectEvent.class, this::onPlayerConnect);
}
private void onPlayerConnect(PlayerConnectEvent event) {
String username = event.getUsername();
UUID uuid = event.getUuid();
getLogger().at(Level.INFO).log("Player connected: " + username + " (" + uuid + ")");
// Send welcome message after player is ready
// Note: PlayerReadyEvent has String key, so use registerGlobal
getEventRegistry().registerGlobal(PlayerReadyEvent.class, readyEvent -> {
Player player = readyEvent.getPlayer();
player.sendMessage(Message.raw("Welcome to the server, " + username + "!"));
});
}
@Override
public void shutdown() {
// Cleanup
}
}
```
### Permission Check Example
```java
public class PermissionPlugin extends ServerPlugin {
@Override
public void start() {
CommandRegistry registry = getCommandRegistry();
registry.register("admin", context -> {
CommandSender sender = context.getSource();
// Check permission
if (sender instanceof Player player) {
if (!player.hasPermission("myplugin.admin")) {
player.sendMessage(Message.raw("No permission!"));
return;
}
player.sendMessage(Message.raw("You are an admin!"));
}
});
}
}
```
### Inventory Management
```java
// Get entity reference from command context
Ref<EntityStore> ref = context.senderAsPlayerRef();
// Working with player inventory
Player player = ref.getStore().getComponent(ref, Player.getComponentType());
Inventory inventory = player.getInventory();
// Add item to inventory
ItemStack itemStack = new ItemStack(itemType, amount);
inventory.addItem(itemStack);
// Send inventory update to client
player.sendInventory();
// Access hotbar
HotbarManager hotbar = player.getHotbarManager();
```
---
## Best Practices
{{< callout type="info" >}}
**Player API Guidelines:**
- Use `PlayerRef` to get thread-safe player information
- Check `isValid()` before operations on stored references
- Use permissions via `player.hasPermission()` for access control
- Understand the component system: players are entities with components
{{< /callout >}}
{{< callout type="warning" >}}
**Component System:** Players in Hytale are entities managed by an ECS. Access player data via `ref.getStore()` and appropriate component types (`Player.getComponentType()`, `PlayerRef.getComponentType()`).
{{< /callout >}}
{{< callout type="error" >}}
**Critical:** Never store `Ref<EntityStore>` or `Player` references directly in static fields. Use `UUID` for storage and retrieve references via the component system when needed.
{{< /callout >}}

View File

@@ -0,0 +1,266 @@
---
title: Spawning Entities
type: docs
weight: 3
---
Create and spawn entities into the world by registering entity classes and using the World API.
## Registering Entities
Before spawning entities, register them with the EntityRegistry in your plugin's `start()` method:
```java
@Override
public void start() {
// Register entity with key, class, constructor, and codec
getEntityRegistry().registerEntity(
"my_custom_entity",
CustomEntity.class,
world -> new CustomEntity(world),
CustomEntity.CODEC
);
}
```
### Registration Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| key | `String` | Unique identifier for the entity type |
| clazz | `Class<T>` | The entity class |
| constructor | `Function<World, T>` | Factory function that creates entity instances |
| codec | `DirectDecodeCodec<T>` | Serialization codec for persistence |
## Entity Base Classes
Hytale provides two main entity base classes:
### Entity
Base class for all entities. Implements `Component<EntityStore>`.
```java
public class SimpleEntity extends Entity {
public static final DirectDecodeCodec<SimpleEntity> CODEC =
Entity.CODEC.extend(SimpleEntity.class, SimpleEntity::new);
public SimpleEntity() {
super();
}
public SimpleEntity(World world) {
super(world);
}
}
```
### LivingEntity
Extended base class for entities with health, inventory, and stats.
```java
public class CustomMob extends LivingEntity {
public static final DirectDecodeCodec<CustomMob> CODEC =
LivingEntity.CODEC.extend(CustomMob.class, CustomMob::new);
public CustomMob() {
super();
}
public CustomMob(World world) {
super(world);
}
@Override
protected Inventory createDefaultInventory() {
return new Inventory(36); // 36 slots
}
}
```
## Spawning Entities
Use the World API to spawn registered entities:
```java
// Get the world
World world = player.getWorld();
// Create entity instance
CustomMob mob = new CustomMob(world);
// Define spawn position and rotation
Vector3d position = new Vector3d(100, 64, 200);
Vector3f rotation = new Vector3f(0, 90, 0); // Facing east
// Spawn the entity (deprecated but functional)
world.spawnEntity(mob, position, rotation);
```
### addEntity Method
For more control over spawn reasons, use `addEntity`:
```java
// AddReason options: SPAWN, LOAD, TRANSFER
world.addEntity(mob, position, rotation, AddReason.SPAWN);
```
{{< callout type="warning" >}}
**Deprecation Notice:** Both `spawnEntity` and `addEntity` methods on World are deprecated. The modern approach uses the component-based EntityStore system directly. However, these methods still work and are the simplest way to spawn entities.
{{< /callout >}}
## Entity Lifecycle
### Loading into World
When an entity is added to a world:
```java
// Called automatically when entity is added
entity.loadIntoWorld(world);
// Entity receives network ID
int networkId = entity.getNetworkId();
// Reference is created for component access
Ref<EntityStore> ref = entity.getReference();
```
### Removing Entities
```java
// Remove an entity from the world
boolean removed = entity.remove();
// Check if entity was already removed
if (entity.wasRemoved()) {
// Entity no longer valid
}
```
## Entity Events
### EntityRemoveEvent
Listen for entity removal:
```java
getEventRegistry().register(EntityRemoveEvent.class, event -> {
Entity entity = event.getEntity();
getLogger().at(Level.INFO).log("Entity removed: " + entity.getClass().getSimpleName());
});
```
### LivingEntityInventoryChangeEvent
Track inventory changes on living entities:
```java
getEventRegistry().register(LivingEntityInventoryChangeEvent.class, event -> {
LivingEntity entity = event.getEntity();
// Handle inventory change
});
```
{{< callout type="info" >}}
**No Spawn Event:** Hytale does not have an EntitySpawnEvent. To track spawns, consider implementing custom logic in your entity constructor or using the component system's add callbacks.
{{< /callout >}}
## Complete Example
```java
public class MyPlugin extends JavaPlugin {
@Override
public void start() {
// Register custom entity
getEntityRegistry().registerEntity(
"boss_monster",
BossMonster.class,
world -> new BossMonster(world),
BossMonster.CODEC
);
// Listen for removal
getEventRegistry().register(EntityRemoveEvent.class, this::onEntityRemove);
}
private void onEntityRemove(EntityRemoveEvent event) {
if (event.getEntity() instanceof BossMonster) {
getLogger().at(Level.INFO).log("Boss monster was defeated!");
}
}
public void spawnBoss(Player player) {
World world = player.getWorld();
// Create boss at player's location
BossMonster boss = new BossMonster(world);
// Get player position and add offset
Vector3d spawnPos = player.getTransformComponent().getPosition();
spawnPos = new Vector3d(spawnPos.x + 5, spawnPos.y, spawnPos.z);
// Face the player
Vector3f rotation = new Vector3f(0, 180, 0);
// Spawn
world.spawnEntity(boss, spawnPos, rotation);
}
}
public class BossMonster extends LivingEntity {
public static final DirectDecodeCodec<BossMonster> CODEC =
LivingEntity.CODEC.extend(BossMonster.class, BossMonster::new);
public BossMonster() {
super();
}
public BossMonster(World world) {
super(world);
}
@Override
protected Inventory createDefaultInventory() {
return new Inventory(9); // Small inventory
}
@Override
public boolean isCollidable() {
return true;
}
}
```
## Thread Safety
{{< callout type="error" >}}
**Important:** Entity spawning should be done on the world's ticking thread. If calling from an async context, use the world's executor:
{{< /callout >}}
```java
// Safe spawning from async context
world.execute(() -> {
world.spawnEntity(entity, position, rotation);
});
```
## Spawn Validation
The world validates spawn positions:
```java
// Position requirements:
// - X and Z must be within +/- 33554432
// - Y must be >= -32
// Invalid positions will throw IllegalArgumentException
try {
world.spawnEntity(entity, invalidPosition, rotation);
} catch (IllegalArgumentException e) {
getLogger().warning("Invalid spawn position: " + e.getMessage());
}
```

View File

@@ -0,0 +1,266 @@
---
title: Faire Apparaître des Entités
type: docs
weight: 3
---
Créez et faites apparaître des entités dans le monde en enregistrant des classes d'entités et en utilisant l'API World.
## Enregistrer des Entités
Avant de faire apparaître des entités, enregistrez-les avec l'EntityRegistry dans la méthode `start()` de votre plugin :
```java
@Override
public void start() {
// Enregistrer l'entité avec clé, classe, constructeur et codec
getEntityRegistry().registerEntity(
"my_custom_entity",
CustomEntity.class,
world -> new CustomEntity(world),
CustomEntity.CODEC
);
}
```
### Paramètres d'Enregistrement
| Paramètre | Type | Description |
|-----------|------|-------------|
| key | `String` | Identifiant unique pour le type d'entité |
| clazz | `Class<T>` | La classe de l'entité |
| constructor | `Function<World, T>` | Fonction factory qui crée les instances |
| codec | `DirectDecodeCodec<T>` | Codec de sérialisation pour la persistance |
## Classes de Base d'Entité
Hytale fournit deux classes de base principales :
### Entity
Classe de base pour toutes les entités. Implémente `Component<EntityStore>`.
```java
public class SimpleEntity extends Entity {
public static final DirectDecodeCodec<SimpleEntity> CODEC =
Entity.CODEC.extend(SimpleEntity.class, SimpleEntity::new);
public SimpleEntity() {
super();
}
public SimpleEntity(World world) {
super(world);
}
}
```
### LivingEntity
Classe de base étendue pour les entités avec santé, inventaire et statistiques.
```java
public class CustomMob extends LivingEntity {
public static final DirectDecodeCodec<CustomMob> CODEC =
LivingEntity.CODEC.extend(CustomMob.class, CustomMob::new);
public CustomMob() {
super();
}
public CustomMob(World world) {
super(world);
}
@Override
protected Inventory createDefaultInventory() {
return new Inventory(36); // 36 emplacements
}
}
```
## Faire Apparaître des Entités
Utilisez l'API World pour faire apparaître des entités enregistrées :
```java
// Obtenir le monde
World world = player.getWorld();
// Créer l'instance de l'entité
CustomMob mob = new CustomMob(world);
// Définir la position et rotation de spawn
Vector3d position = new Vector3d(100, 64, 200);
Vector3f rotation = new Vector3f(0, 90, 0); // Face à l'est
// Faire apparaître l'entité (déprécié mais fonctionnel)
world.spawnEntity(mob, position, rotation);
```
### Méthode addEntity
Pour plus de contrôle sur les raisons de spawn, utilisez `addEntity` :
```java
// Options AddReason : SPAWN, LOAD, TRANSFER
world.addEntity(mob, position, rotation, AddReason.SPAWN);
```
{{< callout type="warning" >}}
**Avis de Dépréciation :** Les méthodes `spawnEntity` et `addEntity` sur World sont dépréciées. L'approche moderne utilise directement le système EntityStore basé sur les composants. Cependant, ces méthodes fonctionnent toujours et sont le moyen le plus simple de faire apparaître des entités.
{{< /callout >}}
## Cycle de Vie des Entités
### Chargement dans le Monde
Quand une entité est ajoutée à un monde :
```java
// Appelé automatiquement quand l'entité est ajoutée
entity.loadIntoWorld(world);
// L'entité reçoit un ID réseau
int networkId = entity.getNetworkId();
// Une référence est créée pour l'accès aux composants
Ref<EntityStore> ref = entity.getReference();
```
### Supprimer des Entités
```java
// Supprimer une entité du monde
boolean removed = entity.remove();
// Vérifier si l'entité a déjà été supprimée
if (entity.wasRemoved()) {
// L'entité n'est plus valide
}
```
## Événements d'Entité
### EntityRemoveEvent
Écouter la suppression d'entités :
```java
getEventRegistry().register(EntityRemoveEvent.class, event -> {
Entity entity = event.getEntity();
getLogger().at(Level.INFO).log("Entité supprimée: " + entity.getClass().getSimpleName());
});
```
### LivingEntityInventoryChangeEvent
Suivre les changements d'inventaire sur les entités vivantes :
```java
getEventRegistry().register(LivingEntityInventoryChangeEvent.class, event -> {
LivingEntity entity = event.getEntity();
// Gérer le changement d'inventaire
});
```
{{< callout type="info" >}}
**Pas d'Événement de Spawn :** Hytale n'a pas d'EntitySpawnEvent. Pour suivre les spawns, envisagez d'implémenter une logique personnalisée dans le constructeur de votre entité ou d'utiliser les callbacks d'ajout du système de composants.
{{< /callout >}}
## Exemple Complet
```java
public class MyPlugin extends JavaPlugin {
@Override
public void start() {
// Enregistrer l'entité personnalisée
getEntityRegistry().registerEntity(
"boss_monster",
BossMonster.class,
world -> new BossMonster(world),
BossMonster.CODEC
);
// Écouter les suppressions
getEventRegistry().register(EntityRemoveEvent.class, this::onEntityRemove);
}
private void onEntityRemove(EntityRemoveEvent event) {
if (event.getEntity() instanceof BossMonster) {
getLogger().at(Level.INFO).log("Le boss a été vaincu !");
}
}
public void spawnBoss(Player player) {
World world = player.getWorld();
// Créer le boss à la position du joueur
BossMonster boss = new BossMonster(world);
// Obtenir la position du joueur et ajouter un décalage
Vector3d spawnPos = player.getTransformComponent().getPosition();
spawnPos = new Vector3d(spawnPos.x + 5, spawnPos.y, spawnPos.z);
// Faire face au joueur
Vector3f rotation = new Vector3f(0, 180, 0);
// Spawner
world.spawnEntity(boss, spawnPos, rotation);
}
}
public class BossMonster extends LivingEntity {
public static final DirectDecodeCodec<BossMonster> CODEC =
LivingEntity.CODEC.extend(BossMonster.class, BossMonster::new);
public BossMonster() {
super();
}
public BossMonster(World world) {
super(world);
}
@Override
protected Inventory createDefaultInventory() {
return new Inventory(9); // Petit inventaire
}
@Override
public boolean isCollidable() {
return true;
}
}
```
## Thread Safety
{{< callout type="error" >}}
**Important :** Le spawn d'entités doit être fait sur le thread de tick du monde. Si vous appelez depuis un contexte asynchrone, utilisez l'exécuteur du monde :
{{< /callout >}}
```java
// Spawn sécurisé depuis un contexte asynchrone
world.execute(() -> {
world.spawnEntity(entity, position, rotation);
});
```
## Validation du Spawn
Le monde valide les positions de spawn :
```java
// Exigences de position :
// - X et Z doivent être dans +/- 33554432
// - Y doit être >= -32
// Les positions invalides lèvent IllegalArgumentException
try {
world.spawnEntity(entity, invalidPosition, rotation);
} catch (IllegalArgumentException e) {
getLogger().warning("Position de spawn invalide: " + e.getMessage());
}
```

View File

@@ -0,0 +1,112 @@
---
title: Spawning
type: docs
weight: 6
---
The spawning system provides comprehensive NPC spawn management through markers, beacons, world spawning, and suppression mechanics.
**Package:** `com.hypixel.hytale.server.spawning`
## Architecture
```
SpawningPlugin
├── Managers
│ ├── WorldSpawnManager - World-level NPC spawning
│ ├── BeaconSpawnManager - Beacon-based spawning
│ └── SpawnManager<W, S> - Base spawn manager
├── Assets
│ ├── SpawnMarker - Marker spawn definitions
│ ├── SpawnSuppression - Suppression zones
│ ├── WorldNPCSpawn - World spawn configs
│ └── BeaconNPCSpawn - Beacon spawn configs
├── Components
│ ├── SpawnMarkerEntity - Marker entity data
│ ├── SpawnSuppressionComponent - Entity suppression
│ ├── LocalSpawnController - Player-local spawning
│ ├── WorldSpawnData - World spawn state
│ └── ChunkSpawnData - Chunk spawn state
├── Controllers
│ ├── SpawnController - Base controller
│ ├── BeaconSpawnController - Beacon controller
│ └── LocalSpawnController - Local controller
├── Systems
│ ├── WorldSpawningSystem - World spawn logic
│ ├── SpawnMarkerSystems - Marker processing
│ ├── SpawnBeaconSystems - Beacon processing
│ └── SpawnSuppressionSystems - Suppression logic
├── Interactions
│ └── TriggerSpawnMarkersInteraction
└── Commands
└── SpawnCommand (enable, disable, beacons, markers, populate, stats, suppression)
```
## Spawn Types
### Spawn Markers
Static spawn points that spawn NPCs with configurable respawn timing:
```java
SpawnMarker marker = SpawnMarker.getAssetMap().getAsset("village_guard");
IWeightedMap<SpawnConfiguration> npcs = marker.getWeightedConfigurations();
```
**Asset Location:** `NPC/Spawn/Markers/`
### Spawn Beacons
Dynamic spawn points associated with entities that trigger spawning in a radius:
```java
BeaconSpawnManager manager = SpawningPlugin.get().getBeaconSpawnManager();
List<BeaconSpawnWrapper> beacons = manager.getBeaconSpawns(environmentId);
```
**Asset Location:** `NPC/Spawn/Beacons/`
### World Spawning
Ambient NPC spawning based on environment and biome:
```java
WorldSpawnManager manager = SpawningPlugin.get().getWorldSpawnManager();
```
**Asset Location:** `NPC/Spawn/World/`
### Spawn Suppression
Zones that prevent NPC spawning within a radius:
```java
SpawnSuppression suppression = SpawnSuppression.getAssetMap().getAsset("safe_zone");
double radius = suppression.getRadius();
int[] suppressedGroups = suppression.getSuppressedGroupIds();
```
**Asset Location:** `NPC/Spawn/Suppression/`
## Commands
| Command | Description |
|---------|-------------|
| `/spawning enable [world]` | Enable NPC spawning in world |
| `/spawning disable [world]` | Disable NPC spawning in world |
| `/spawning beacons` | Beacon spawn management |
| `/spawning markers` | Spawn marker management |
| `/spawning populate` | Force spawn population |
| `/spawning stats` | View spawning statistics |
| `/spawning suppression` | Suppression zone management |
**Alias:** `/sp`
## Section Contents
{{< cards >}}
{{< card link="world-spawning" title="World Spawning" subtitle="Environment-based NPC spawning" icon="globe" >}}
{{< card link="spawn-suppression" title="Spawn Suppression" subtitle="Prevent spawns in areas" icon="shield-exclamation" >}}
{{< card link="local-spawning" title="Local Spawning" subtitle="Player-proximity spawning" icon="user" >}}
{{< card link="spawner-assets" title="Spawner Assets" subtitle="Markers and beacons configuration" icon="document-text" >}}
{{< /cards >}}

View File

@@ -0,0 +1,112 @@
---
title: Spawning
type: docs
weight: 6
---
Le systeme de spawning fournit une gestion complete du spawn de NPCs via des marqueurs, beacons, spawning mondial et mecaniques de suppression.
**Package:** `com.hypixel.hytale.server.spawning`
## Architecture
```
SpawningPlugin
├── Managers
│ ├── WorldSpawnManager - Spawning NPC niveau monde
│ ├── BeaconSpawnManager - Spawning base sur beacons
│ └── SpawnManager<W, S> - Manager de spawn de base
├── Assets
│ ├── SpawnMarker - Definitions de spawn marqueur
│ ├── SpawnSuppression - Zones de suppression
│ ├── WorldNPCSpawn - Configs spawn monde
│ └── BeaconNPCSpawn - Configs spawn beacon
├── Composants
│ ├── SpawnMarkerEntity - Donnees entite marqueur
│ ├── SpawnSuppressionComponent - Suppression entite
│ ├── LocalSpawnController - Spawning local joueur
│ ├── WorldSpawnData - Etat spawn monde
│ └── ChunkSpawnData - Etat spawn chunk
├── Controleurs
│ ├── SpawnController - Controleur de base
│ ├── BeaconSpawnController - Controleur beacon
│ └── LocalSpawnController - Controleur local
├── Systemes
│ ├── WorldSpawningSystem - Logique spawn monde
│ ├── SpawnMarkerSystems - Traitement marqueurs
│ ├── SpawnBeaconSystems - Traitement beacons
│ └── SpawnSuppressionSystems - Logique suppression
├── Interactions
│ └── TriggerSpawnMarkersInteraction
└── Commandes
└── SpawnCommand (enable, disable, beacons, markers, populate, stats, suppression)
```
## Types de Spawn
### Marqueurs de Spawn
Points de spawn statiques qui font apparaitre des NPCs avec timing de reapparition configurable:
```java
SpawnMarker marker = SpawnMarker.getAssetMap().getAsset("village_guard");
IWeightedMap<SpawnConfiguration> npcs = marker.getWeightedConfigurations();
```
**Emplacement Asset:** `NPC/Spawn/Markers/`
### Beacons de Spawn
Points de spawn dynamiques associes a des entites qui declenchent le spawning dans un rayon:
```java
BeaconSpawnManager manager = SpawningPlugin.get().getBeaconSpawnManager();
List<BeaconSpawnWrapper> beacons = manager.getBeaconSpawns(environmentId);
```
**Emplacement Asset:** `NPC/Spawn/Beacons/`
### Spawning Mondial
Spawning ambiant de NPCs base sur l'environnement et le biome:
```java
WorldSpawnManager manager = SpawningPlugin.get().getWorldSpawnManager();
```
**Emplacement Asset:** `NPC/Spawn/World/`
### Suppression de Spawn
Zones qui empechent le spawn de NPCs dans un rayon:
```java
SpawnSuppression suppression = SpawnSuppression.getAssetMap().getAsset("safe_zone");
double radius = suppression.getRadius();
int[] suppressedGroups = suppression.getSuppressedGroupIds();
```
**Emplacement Asset:** `NPC/Spawn/Suppression/`
## Commandes
| Commande | Description |
|----------|-------------|
| `/spawning enable [monde]` | Activer le spawning NPC dans le monde |
| `/spawning disable [monde]` | Desactiver le spawning NPC dans le monde |
| `/spawning beacons` | Gestion des spawn beacons |
| `/spawning markers` | Gestion des marqueurs de spawn |
| `/spawning populate` | Forcer la population de spawn |
| `/spawning stats` | Voir les statistiques de spawning |
| `/spawning suppression` | Gestion des zones de suppression |
**Alias:** `/sp`
## Contenu de la Section
{{< cards >}}
{{< card link="world-spawning" title="Spawning Mondial" subtitle="Spawning NPC base sur l'environnement" icon="globe" >}}
{{< card link="spawn-suppression" title="Suppression de Spawn" subtitle="Empecher les spawns dans des zones" icon="shield-exclamation" >}}
{{< card link="local-spawning" title="Spawning Local" subtitle="Spawning par proximite joueur" icon="user" >}}
{{< card link="spawner-assets" title="Assets Spawner" subtitle="Configuration marqueurs et beacons" icon="document-text" >}}
{{< /cards >}}

View File

@@ -0,0 +1,212 @@
---
title: Local Spawning
type: docs
weight: 3
---
Local spawning manages NPC spawning around players, ensuring active gameplay areas have appropriate creature density.
**Package:** `com.hypixel.hytale.server.spawning.local`
## Architecture
```
Local Spawning
├── Components
│ ├── LocalSpawnController - Player spawn controller
│ ├── LocalSpawnBeacon - Local spawn point
│ └── LocalSpawnState - Spawn state tracking
└── Systems
├── LocalSpawnSetupSystem - Initialize local spawning
├── LocalSpawnControllerSystem - Process controllers
├── LocalSpawnBeaconSystem - Process beacons
└── LocalSpawnForceTriggerSystem - Manual triggers
```
## LocalSpawnController
Controls local spawning around a player:
```java
public class LocalSpawnController implements Component<EntityStore> {
private double timeToNextRunSeconds;
public void setTimeToNextRunSeconds(double seconds);
public boolean tickTimeToNextRunSeconds(float dt);
}
```
### Component Access
```java
// Get component type
ComponentType<EntityStore, LocalSpawnController> type =
SpawningPlugin.get().getLocalSpawnControllerComponentType();
// Access on player entity
LocalSpawnController controller = store.getComponent(playerRef, type);
```
### Spawn Timing
The controller uses a configurable delay before spawning:
```java
// Initial delay configured in SpawningPlugin
double joinDelay = SpawningPlugin.get().getLocalSpawnControllerJoinDelay();
```
This prevents immediate spawning when a player joins, allowing time for world loading.
## LocalSpawnBeacon
Defines local spawn points around players:
```java
public class LocalSpawnBeacon {
// Position and configuration for local spawns
// Dynamically created based on player position
}
```
### Beacon Usage
Local spawn beacons are automatically managed:
1. Created around active players
2. Updated as players move
3. Removed when players leave area
## LocalSpawnState
Tracks local spawn state:
```java
public class LocalSpawnState {
// Current spawn state for a local spawn controller
// Tracks active spawns and cooldowns
}
```
## Systems
### LocalSpawnSetupSystem
Initializes local spawning for players:
```java
getEntityStoreRegistry().registerSystem(new LocalSpawnSetupSystem());
```
This system:
- Attaches `LocalSpawnController` to new players
- Configures initial spawn parameters
- Sets up spawn beacons
### LocalSpawnControllerSystem
Processes spawn controllers each tick:
```java
getEntityStoreRegistry().registerSystem(new LocalSpawnControllerSystem());
```
This system:
- Decrements spawn timers
- Checks spawn conditions
- Triggers spawn jobs when ready
### LocalSpawnBeaconSystem
Manages local spawn beacons:
```java
getEntityStoreRegistry().registerSystem(new LocalSpawnBeaconSystem());
```
This system:
- Updates beacon positions
- Processes beacon spawn logic
- Handles beacon lifecycle
### LocalSpawnForceTriggerSystem
Handles forced spawn triggers:
```java
getEntityStoreRegistry().registerSystem(new LocalSpawnForceTriggerSystem());
```
Used for:
- Debug commands
- Event-triggered spawning
- Manual population
## API Usage
### Get Local Spawn Controller
```java
ComponentType<EntityStore, LocalSpawnController> type =
LocalSpawnController.getComponentType();
LocalSpawnController controller = store.getComponent(playerRef, type);
if (controller != null) {
// Player has local spawning enabled
}
```
### Force Spawn Update
```java
// Reset spawn timer to trigger immediate check
LocalSpawnController controller = store.getComponent(playerRef, type);
controller.setTimeToNextRunSeconds(0);
```
### Check Spawn Ready
```java
// Check if spawn timer has elapsed
boolean ready = controller.tickTimeToNextRunSeconds(deltaTime);
if (ready) {
// Time to attempt spawning
}
```
## Configuration
### Join Delay
Configure the delay before local spawning starts for new players:
```java
double delay = SpawningPlugin.get().getLocalSpawnControllerJoinDelay();
```
This prevents:
- Immediate spawn-in ambushes
- Overwhelming new players
- Spawning before world is loaded
### Spawn Radius
Local spawning uses a configured radius around players:
```java
// Spawn radius determines how far from player NPCs can spawn
// Configured per spawn definition
```
## Integration with World Spawning
Local spawning works alongside world spawning:
| System | Scope | Trigger |
|--------|-------|---------|
| World Spawning | Chunk-based | Chunk loading |
| Local Spawning | Player-centric | Player proximity |
Both systems:
- Respect spawn suppression
- Use same NPC pool
- Share spawn limits

View File

@@ -0,0 +1,212 @@
---
title: Spawning Local
type: docs
weight: 3
---
Le spawning local gere le spawn de NPCs autour des joueurs, assurant que les zones de jeu actives ont une densite de creatures appropriee.
**Package:** `com.hypixel.hytale.server.spawning.local`
## Architecture
```
Spawning Local
├── Composants
│ ├── LocalSpawnController - Controleur spawn joueur
│ ├── LocalSpawnBeacon - Point de spawn local
│ └── LocalSpawnState - Suivi etat spawn
└── Systemes
├── LocalSpawnSetupSystem - Initialiser spawning local
├── LocalSpawnControllerSystem - Traiter controleurs
├── LocalSpawnBeaconSystem - Traiter beacons
└── LocalSpawnForceTriggerSystem - Declencheurs manuels
```
## LocalSpawnController
Controle le spawning local autour d'un joueur:
```java
public class LocalSpawnController implements Component<EntityStore> {
private double timeToNextRunSeconds;
public void setTimeToNextRunSeconds(double seconds);
public boolean tickTimeToNextRunSeconds(float dt);
}
```
### Acces au Composant
```java
// Obtenir type de composant
ComponentType<EntityStore, LocalSpawnController> type =
SpawningPlugin.get().getLocalSpawnControllerComponentType();
// Acceder sur entite joueur
LocalSpawnController controller = store.getComponent(playerRef, type);
```
### Timing de Spawn
Le controleur utilise un delai configurable avant spawning:
```java
// Delai initial configure dans SpawningPlugin
double joinDelay = SpawningPlugin.get().getLocalSpawnControllerJoinDelay();
```
Cela empeche le spawning immediat quand un joueur rejoint, laissant le temps au monde de charger.
## LocalSpawnBeacon
Definit les points de spawn locaux autour des joueurs:
```java
public class LocalSpawnBeacon {
// Position et configuration pour spawns locaux
// Cree dynamiquement base sur position joueur
}
```
### Utilisation des Beacons
Les beacons de spawn locaux sont geres automatiquement:
1. Crees autour des joueurs actifs
2. Mis a jour quand joueurs bougent
3. Supprimes quand joueurs quittent la zone
## LocalSpawnState
Suit l'etat de spawn local:
```java
public class LocalSpawnState {
// Etat de spawn actuel pour un controleur local
// Suit spawns actifs et cooldowns
}
```
## Systemes
### LocalSpawnSetupSystem
Initialise le spawning local pour les joueurs:
```java
getEntityStoreRegistry().registerSystem(new LocalSpawnSetupSystem());
```
Ce systeme:
- Attache `LocalSpawnController` aux nouveaux joueurs
- Configure parametres de spawn initiaux
- Configure les beacons de spawn
### LocalSpawnControllerSystem
Traite les controleurs de spawn a chaque tick:
```java
getEntityStoreRegistry().registerSystem(new LocalSpawnControllerSystem());
```
Ce systeme:
- Decremente les timers de spawn
- Verifie les conditions de spawn
- Declenche jobs de spawn quand pret
### LocalSpawnBeaconSystem
Gere les beacons de spawn locaux:
```java
getEntityStoreRegistry().registerSystem(new LocalSpawnBeaconSystem());
```
Ce systeme:
- Met a jour positions des beacons
- Traite logique spawn beacon
- Gere cycle de vie beacon
### LocalSpawnForceTriggerSystem
Gere les declencheurs de spawn forces:
```java
getEntityStoreRegistry().registerSystem(new LocalSpawnForceTriggerSystem());
```
Utilise pour:
- Commandes debug
- Spawning declenche par evenement
- Population manuelle
## Utilisation de l'API
### Obtenir Controleur Spawn Local
```java
ComponentType<EntityStore, LocalSpawnController> type =
LocalSpawnController.getComponentType();
LocalSpawnController controller = store.getComponent(playerRef, type);
if (controller != null) {
// Le joueur a le spawning local active
}
```
### Forcer Mise a Jour Spawn
```java
// Reset timer spawn pour declencher verification immediate
LocalSpawnController controller = store.getComponent(playerRef, type);
controller.setTimeToNextRunSeconds(0);
```
### Verifier Pret pour Spawn
```java
// Verifier si timer spawn ecoule
boolean ready = controller.tickTimeToNextRunSeconds(deltaTime);
if (ready) {
// Temps de tenter spawning
}
```
## Configuration
### Delai de Connexion
Configurer le delai avant que spawning local demarre pour nouveaux joueurs:
```java
double delay = SpawningPlugin.get().getLocalSpawnControllerJoinDelay();
```
Cela empeche:
- Embuscades immediates au spawn
- Submerger les nouveaux joueurs
- Spawning avant chargement monde
### Rayon de Spawn
Le spawning local utilise un rayon configure autour des joueurs:
```java
// Le rayon de spawn determine a quelle distance du joueur les NPCs peuvent spawn
// Configure par definition de spawn
```
## Integration avec Spawning Mondial
Le spawning local fonctionne avec le spawning mondial:
| Systeme | Portee | Declencheur |
|---------|--------|-------------|
| Spawning Mondial | Base chunk | Chargement chunk |
| Spawning Local | Centre joueur | Proximite joueur |
Les deux systemes:
- Respectent la suppression de spawn
- Utilisent le meme pool NPC
- Partagent les limites de spawn

View File

@@ -0,0 +1,249 @@
---
title: Spawn Suppression
type: docs
weight: 2
---
Spawn suppression allows preventing NPC spawning within defined areas, useful for safe zones, player bases, and controlled environments.
**Package:** `com.hypixel.hytale.server.spawning.suppression`
## Architecture
```
Spawn Suppression
├── Assets
│ └── SpawnSuppression - Suppression zone definitions
├── Components
│ ├── SpawnSuppressionComponent - Entity-attached suppression
│ ├── SpawnSuppressionController - Suppression logic
│ ├── ChunkSuppressionEntry - Chunk suppression data
│ └── ChunkSuppressionQueue - Pending suppressions
├── Systems
│ ├── SpawnSuppressionSystems - Main suppression logic
│ ├── ChunkSuppressionSystems - Chunk-level processing
│ └── SpawnMarkerSuppressionSystem - Marker suppression
└── Utilities
├── SuppressionSpanHelper - Span calculations
└── SpawnSuppressorEntry - Suppressor tracking
```
## SpawnSuppression Asset
### Asset Configuration
```yaml
# NPC/Spawn/Suppression/safe_zone.json
{
"Id": "safe_zone",
"SuppressionRadius": 50.0,
"SuppressedGroups": ["hostile", "neutral_aggressive"],
"SuppressSpawnMarkers": true
}
```
### Asset Fields
| Field | Type | Description |
|-------|------|-------------|
| `Id` | String | Unique suppression identifier |
| `SuppressionRadius` | Double | Radius of suppression effect |
| `SuppressedGroups` | String[] | NPCGroup IDs to suppress |
| `SuppressSpawnMarkers` | Boolean | Also suppress spawn markers |
### Asset Access
```java
// Get suppression asset
SpawnSuppression suppression = SpawnSuppression.getAssetMap().getAsset("safe_zone");
// Get properties
double radius = suppression.getRadius();
int[] suppressedGroups = suppression.getSuppressedGroupIds();
boolean suppressMarkers = suppression.isSuppressSpawnMarkers();
```
## SpawnSuppressionComponent
Attaches suppression behavior to entities:
```java
public class SpawnSuppressionComponent implements Component<EntityStore> {
private String spawnSuppression; // Asset ID reference
public String getSpawnSuppression();
public void setSpawnSuppression(String spawnSuppression);
}
```
### Component Usage
```java
// Get component type
ComponentType<EntityStore, SpawnSuppressionComponent> type =
SpawningPlugin.get().getSpawnSuppressorComponentType();
// Attach to entity
SpawnSuppressionComponent comp = new SpawnSuppressionComponent("safe_zone");
store.setComponent(entityRef, type, comp);
// Read from entity
SpawnSuppressionComponent existing = store.getComponent(entityRef, type);
String suppressionId = existing.getSpawnSuppression();
```
### Component Codec
```java
public static final BuilderCodec<SpawnSuppressionComponent> CODEC = BuilderCodec.builder(...)
.append(new KeyedCodec<>("SpawnSuppression", Codec.STRING), ...)
.build();
```
## Suppression Controller
### SpawnSuppressionController
Manages active suppression zones:
```java
// Controller tracks active suppressions
// Calculates affected chunks
// Updates suppression state
```
## Chunk Suppression
### ChunkSuppressionEntry
Tracks suppression state per chunk:
```java
// Records which suppressions affect each chunk
// Cached for efficient spawn checks
```
### ChunkSuppressionQueue
Queue of pending suppression updates:
```java
// Handles async suppression calculations
// Processes additions and removals
```
### ChunkSuppressionSystems
Processes chunk-level suppression:
```java
getEntityStoreRegistry().registerSystem(new ChunkSuppressionSystems());
```
## Suppression Systems
### SpawnSuppressionSystems
Main suppression logic:
```java
// Processes SpawnSuppressionComponent entities
// Updates affected chunk data
// Triggers suppression state changes
```
### SpawnMarkerSuppressionSystem
Handles spawn marker suppression:
```java
// Disables markers within suppression radius
// Re-enables when suppression removed
```
## Suppression Utilities
### SuppressionSpanHelper
Calculates suppression spans:
```java
SuppressionSpanHelper helper = new SuppressionSpanHelper();
// Computes which chunks fall within suppression radius
// Handles edge cases at chunk boundaries
```
### SpawnSuppressorEntry
Tracks individual suppressor entities:
```java
// Links entity reference to suppression config
// Enables efficient lookup and cleanup
```
## API Usage
### Create Suppression Zone
```java
// 1. Define asset in JSON
// NPC/Spawn/Suppression/player_base.json
// 2. Attach component to entity
SpawnSuppressionComponent comp = new SpawnSuppressionComponent("player_base");
store.setComponent(entityRef, SpawnSuppressionComponent.getComponentType(), comp);
```
### Check If Position Suppressed
```java
// Suppression is checked automatically during spawn attempts
// The spawning system queries suppression state per chunk
```
### Remove Suppression
```java
// Remove component to disable suppression
store.removeComponent(entityRef, SpawnSuppressionComponent.getComponentType());
```
## Suppression Behavior
### Radius Calculation
Suppression uses 3D distance with optimization:
- X/Z: Affects entire chunks within radius
- Y: Uses exact distance calculation
This allows NPCs to spawn in caves below or sky above a suppression zone.
### Group Filtering
Only specified NPC groups are suppressed:
```yaml
{
"SuppressedGroups": ["hostile"]
// Hostile NPCs blocked
// Friendly/neutral NPCs can still spawn
}
```
### Marker Suppression
When `SuppressSpawnMarkers` is true:
- Spawn markers within radius stop functioning
- They resume when suppression is removed
- Does not permanently delete markers
## Commands
Access via `/spawning suppression`:
| Subcommand | Description |
|------------|-------------|
| `list` | List active suppressions |
| `info <id>` | Show suppression details |
| `clear <id>` | Remove suppression |

View File

@@ -0,0 +1,249 @@
---
title: Suppression de Spawn
type: docs
weight: 2
---
La suppression de spawn permet d'empecher le spawn de NPCs dans des zones definies, utile pour les zones sures, bases de joueurs et environnements controles.
**Package:** `com.hypixel.hytale.server.spawning.suppression`
## Architecture
```
Suppression de Spawn
├── Assets
│ └── SpawnSuppression - Definitions zones de suppression
├── Composants
│ ├── SpawnSuppressionComponent - Suppression attachee entite
│ ├── SpawnSuppressionController - Logique de suppression
│ ├── ChunkSuppressionEntry - Donnees suppression chunk
│ └── ChunkSuppressionQueue - Suppressions en attente
├── Systemes
│ ├── SpawnSuppressionSystems - Logique suppression principale
│ ├── ChunkSuppressionSystems - Traitement niveau chunk
│ └── SpawnMarkerSuppressionSystem - Suppression marqueurs
└── Utilitaires
├── SuppressionSpanHelper - Calculs d'etendue
└── SpawnSuppressorEntry - Suivi suppresseurs
```
## Asset SpawnSuppression
### Configuration Asset
```yaml
# NPC/Spawn/Suppression/safe_zone.json
{
"Id": "safe_zone",
"SuppressionRadius": 50.0,
"SuppressedGroups": ["hostile", "neutral_aggressive"],
"SuppressSpawnMarkers": true
}
```
### Champs Asset
| Champ | Type | Description |
|-------|------|-------------|
| `Id` | String | Identifiant unique de suppression |
| `SuppressionRadius` | Double | Rayon d'effet de suppression |
| `SuppressedGroups` | String[] | IDs NPCGroup a supprimer |
| `SuppressSpawnMarkers` | Boolean | Supprimer aussi les marqueurs |
### Acces Asset
```java
// Obtenir asset de suppression
SpawnSuppression suppression = SpawnSuppression.getAssetMap().getAsset("safe_zone");
// Obtenir proprietes
double radius = suppression.getRadius();
int[] suppressedGroups = suppression.getSuppressedGroupIds();
boolean suppressMarkers = suppression.isSuppressSpawnMarkers();
```
## SpawnSuppressionComponent
Attache le comportement de suppression aux entites:
```java
public class SpawnSuppressionComponent implements Component<EntityStore> {
private String spawnSuppression; // Reference ID asset
public String getSpawnSuppression();
public void setSpawnSuppression(String spawnSuppression);
}
```
### Utilisation du Composant
```java
// Obtenir type de composant
ComponentType<EntityStore, SpawnSuppressionComponent> type =
SpawningPlugin.get().getSpawnSuppressorComponentType();
// Attacher a une entite
SpawnSuppressionComponent comp = new SpawnSuppressionComponent("safe_zone");
store.setComponent(entityRef, type, comp);
// Lire depuis entite
SpawnSuppressionComponent existing = store.getComponent(entityRef, type);
String suppressionId = existing.getSpawnSuppression();
```
### Codec du Composant
```java
public static final BuilderCodec<SpawnSuppressionComponent> CODEC = BuilderCodec.builder(...)
.append(new KeyedCodec<>("SpawnSuppression", Codec.STRING), ...)
.build();
```
## Controleur de Suppression
### SpawnSuppressionController
Gere les zones de suppression actives:
```java
// Le controleur suit les suppressions actives
// Calcule les chunks affectes
// Met a jour l'etat de suppression
```
## Suppression de Chunk
### ChunkSuppressionEntry
Suit l'etat de suppression par chunk:
```java
// Enregistre quelles suppressions affectent chaque chunk
// Mis en cache pour verifications spawn efficaces
```
### ChunkSuppressionQueue
File d'attente de mises a jour de suppression:
```java
// Gere les calculs de suppression async
// Traite ajouts et suppressions
```
### ChunkSuppressionSystems
Traite la suppression niveau chunk:
```java
getEntityStoreRegistry().registerSystem(new ChunkSuppressionSystems());
```
## Systemes de Suppression
### SpawnSuppressionSystems
Logique de suppression principale:
```java
// Traite les entites SpawnSuppressionComponent
// Met a jour les donnees de chunk affectes
// Declenche les changements d'etat de suppression
```
### SpawnMarkerSuppressionSystem
Gere la suppression des marqueurs de spawn:
```java
// Desactive les marqueurs dans le rayon de suppression
// Reactive quand suppression supprimee
```
## Utilitaires de Suppression
### SuppressionSpanHelper
Calcule les etendues de suppression:
```java
SuppressionSpanHelper helper = new SuppressionSpanHelper();
// Calcule quels chunks tombent dans le rayon
// Gere les cas limites aux frontieres de chunk
```
### SpawnSuppressorEntry
Suit les entites suppresseurs individuelles:
```java
// Lie reference entite a config suppression
// Permet lookup et nettoyage efficaces
```
## Utilisation de l'API
### Creer une Zone de Suppression
```java
// 1. Definir asset en JSON
// NPC/Spawn/Suppression/player_base.json
// 2. Attacher composant a entite
SpawnSuppressionComponent comp = new SpawnSuppressionComponent("player_base");
store.setComponent(entityRef, SpawnSuppressionComponent.getComponentType(), comp);
```
### Verifier si Position Supprimee
```java
// La suppression est verifiee automatiquement lors des tentatives de spawn
// Le systeme de spawn interroge l'etat de suppression par chunk
```
### Supprimer une Suppression
```java
// Retirer composant pour desactiver suppression
store.removeComponent(entityRef, SpawnSuppressionComponent.getComponentType());
```
## Comportement de Suppression
### Calcul de Rayon
La suppression utilise une distance 3D avec optimisation:
- X/Z: Affecte des chunks entiers dans le rayon
- Y: Utilise calcul de distance exact
Cela permet aux NPCs de spawn dans les grottes en dessous ou le ciel au-dessus d'une zone de suppression.
### Filtrage par Groupe
Seuls les groupes NPC specifies sont supprimes:
```yaml
{
"SuppressedGroups": ["hostile"]
// NPCs hostiles bloques
// NPCs amicaux/neutres peuvent toujours spawn
}
```
### Suppression de Marqueurs
Quand `SuppressSpawnMarkers` est vrai:
- Les marqueurs dans le rayon cessent de fonctionner
- Ils reprennent quand suppression retiree
- Ne supprime pas definitivement les marqueurs
## Commandes
Acces via `/spawning suppression`:
| Sous-commande | Description |
|---------------|-------------|
| `list` | Lister les suppressions actives |
| `info <id>` | Afficher details suppression |
| `clear <id>` | Retirer suppression |

View File

@@ -0,0 +1,304 @@
---
title: Spawner Assets
type: docs
weight: 4
---
Spawner assets define how and where NPCs spawn, including markers for static spawn points and beacons for dynamic spawning.
**Package:** `com.hypixel.hytale.server.spawning.assets`
## Spawn Markers
### SpawnMarker Asset
Markers are static spawn points that spawn NPCs with configurable respawn timing.
**Asset Location:** `NPC/Spawn/Markers/`
```yaml
# NPC/Spawn/Markers/village_guard.json
{
"Id": "village_guard",
"Model": "spawn_marker_hostile",
"ExclusionRadius": 15.0,
"MaxDropHeight": 2.0,
"RealtimeRespawn": true,
"ManualTrigger": false,
"DeactivationDistance": 40.0,
"DeactivationTime": 5.0,
"NPCs": [
{
"Name": "guard_soldier",
"Weight": 70.0,
"RealtimeRespawnTime": 120.0
},
{
"Name": "guard_captain",
"Weight": 30.0,
"RealtimeRespawnTime": 300.0
}
]
}
```
### SpawnMarker Fields
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `Id` | String | Required | Unique marker identifier |
| `Model` | String | Config default | Visual model in creative mode |
| `NPCs` | SpawnConfiguration[] | Required | Weighted NPC list |
| `ExclusionRadius` | Double | 0 | Player exclusion radius |
| `MaxDropHeight` | Double | 2.0 | Max spawn height offset |
| `RealtimeRespawn` | Boolean | false | Use real vs game time |
| `ManualTrigger` | Boolean | false | Require manual activation |
| `DeactivationDistance` | Double | 40.0 | Distance to deactivate |
| `DeactivationTime` | Double | 5.0 | Seconds before deactivation |
### SpawnConfiguration
Individual NPC spawn entry in weighted pool:
```java
public class SpawnConfiguration implements IWeightedElement {
protected String npc; // NPC role name
protected double weight; // Spawn weight
protected double realtimeRespawnTime; // Seconds (realtime)
protected Duration spawnAfterGameTime; // Duration (game time)
protected String flockDefinitionId; // Optional flock
}
```
```yaml
{
"Name": "forest_deer",
"Weight": 50.0,
"RealtimeRespawnTime": 60.0,
"SpawnAfterGameTime": "PT1H",
"Flock": "deer_herd"
}
```
### Respawn Timing
Choose between realtime or game time respawning:
**Realtime:** Uses `RealtimeRespawnTime` (seconds)
```yaml
{
"RealtimeRespawn": true,
"NPCs": [{ "RealtimeRespawnTime": 120.0 }]
}
```
**Game Time:** Uses `SpawnAfterGameTime` (ISO 8601 duration)
```yaml
{
"RealtimeRespawn": false,
"NPCs": [{ "SpawnAfterGameTime": "P1DT6H" }]
}
```
Duration format: `P[days]DT[hours]H[minutes]M[seconds]S`
### Flock Spawning
Spawn a group of NPCs around the main spawn:
```yaml
{
"Name": "wolf_alpha",
"Flock": "wolf_pack"
}
```
The flock definition specifies additional NPCs to spawn around the main one.
## Spawn Beacons
### BeaconNPCSpawn
Dynamic spawn points that spawn NPCs within a radius.
**Asset Location:** `NPC/Spawn/Beacons/`
```yaml
# NPC/Spawn/Beacons/dungeon_spawner.json
{
"Id": "dungeon_spawner",
"NPCRole": "skeleton_warrior",
"SpawnWeight": 10,
"MinGroupSize": 2,
"MaxGroupSize": 5,
"Environments": ["dungeon_dark"]
}
```
### SpawnBeacon Component
Entities with spawn beacon behavior:
```java
public class SpawnBeacon {
// Configuration for beacon spawning
// Triggers spawning within radius
}
```
### Beacon Systems
```java
// SpawnBeaconSystems processes active beacons
getEntityStoreRegistry().registerSystem(new SpawnBeaconSystems());
// BeaconSpatialSystem handles spatial queries
getEntityStoreRegistry().registerSystem(new BeaconSpatialSystem());
```
## World Spawn Configuration
### WorldNPCSpawn
Environment-based ambient spawning.
**Asset Location:** `NPC/Spawn/World/`
```yaml
# NPC/Spawn/World/forest_fauna.json
{
"Id": "forest_fauna",
"NPCRole": "rabbit",
"SpawnWeight": 20,
"MinGroupSize": 1,
"MaxGroupSize": 3,
"Environments": ["forest", "grassland"],
"LightType": "Day",
"MinLightLevel": 8
}
```
### NPCSpawn Base Fields
| Field | Type | Description |
|-------|------|-------------|
| `NPCRole` | String | NPC role to spawn |
| `SpawnWeight` | Integer | Spawn probability weight |
| `MinGroupSize` | Integer | Minimum NPCs to spawn |
| `MaxGroupSize` | Integer | Maximum NPCs to spawn |
### Environment Filtering
Restrict spawning to specific environments:
```yaml
{
"Environments": ["cave_dark", "dungeon"]
}
```
### Light Level Filtering
```yaml
{
"LightType": "Night",
"MinLightLevel": 0,
"MaxLightLevel": 7
}
```
Light types: `Any`, `Day`, `Night`
## Spawn Marker Entity
### SpawnMarkerEntity Component
Entity representing a spawn marker in the world:
```java
ComponentType<EntityStore, SpawnMarkerEntity> type =
SpawningPlugin.get().getSpawnMarkerEntityComponentType();
```
### Marker Block State
Spawn markers can be placed as blocks:
```java
public class SpawnMarkerBlockState {
// Block-based spawn marker
}
public class SpawnMarkerBlockReference {
// Reference to marker block
}
```
## Interactions
### TriggerSpawnMarkersInteraction
Manually trigger spawn markers via interaction:
```yaml
# Item or block interaction
{
"Type": "TriggerSpawnMarkers",
"MarkerIds": ["ambush_1", "ambush_2"],
"Radius": 50.0
}
```
Registered as:
```java
getCodecRegistry(Interaction.CODEC).register(
"TriggerSpawnMarkers",
TriggerSpawnMarkersInteraction.class,
TriggerSpawnMarkersInteraction.CODEC
);
```
## API Usage
### Access Spawn Marker Assets
```java
// Get marker asset
SpawnMarker marker = SpawnMarker.getAssetMap().getAsset("village_guard");
// Get weighted configurations
IWeightedMap<SpawnConfiguration> npcs = marker.getWeightedConfigurations();
// Get random NPC from pool
SpawnConfiguration selected = npcs.getRandom(random);
String npcRole = selected.getNpc();
```
### Access Spawn Suppression
```java
SpawnSuppression suppression = SpawnSuppression.getAssetMap().getAsset("safe_zone");
double radius = suppression.getRadius();
```
### Validate Marker Asset
```java
// Validation happens on plugin start
// Warns about:
// - Missing respawn times
// - Conflicting realtime/game time settings
// - Invalid NPC roles
```
## Asset Loading Order
Spawn assets have dependencies:
```java
// Markers load after models and NPC roles
HytaleAssetStore.builder(...)
.loadsAfter(ModelAsset.class)
.loadsAfter(NPCRole.class)
.build();
```

View File

@@ -0,0 +1,304 @@
---
title: Assets Spawner
type: docs
weight: 4
---
Les assets spawner definissent comment et ou les NPCs apparaissent, incluant les marqueurs pour points de spawn statiques et les beacons pour spawning dynamique.
**Package:** `com.hypixel.hytale.server.spawning.assets`
## Marqueurs de Spawn
### Asset SpawnMarker
Les marqueurs sont des points de spawn statiques qui font apparaitre des NPCs avec timing de reapparition configurable.
**Emplacement Asset:** `NPC/Spawn/Markers/`
```yaml
# NPC/Spawn/Markers/village_guard.json
{
"Id": "village_guard",
"Model": "spawn_marker_hostile",
"ExclusionRadius": 15.0,
"MaxDropHeight": 2.0,
"RealtimeRespawn": true,
"ManualTrigger": false,
"DeactivationDistance": 40.0,
"DeactivationTime": 5.0,
"NPCs": [
{
"Name": "guard_soldier",
"Weight": 70.0,
"RealtimeRespawnTime": 120.0
},
{
"Name": "guard_captain",
"Weight": 30.0,
"RealtimeRespawnTime": 300.0
}
]
}
```
### Champs SpawnMarker
| Champ | Type | Defaut | Description |
|-------|------|--------|-------------|
| `Id` | String | Requis | Identifiant unique marqueur |
| `Model` | String | Defaut config | Modele visuel en mode creatif |
| `NPCs` | SpawnConfiguration[] | Requis | Liste NPC ponderee |
| `ExclusionRadius` | Double | 0 | Rayon exclusion joueur |
| `MaxDropHeight` | Double | 2.0 | Offset hauteur spawn max |
| `RealtimeRespawn` | Boolean | false | Temps reel vs temps jeu |
| `ManualTrigger` | Boolean | false | Necessiter activation manuelle |
| `DeactivationDistance` | Double | 40.0 | Distance de desactivation |
| `DeactivationTime` | Double | 5.0 | Secondes avant desactivation |
### SpawnConfiguration
Entree spawn NPC individuelle dans pool pondere:
```java
public class SpawnConfiguration implements IWeightedElement {
protected String npc; // Nom role NPC
protected double weight; // Poids de spawn
protected double realtimeRespawnTime; // Secondes (temps reel)
protected Duration spawnAfterGameTime; // Duree (temps jeu)
protected String flockDefinitionId; // Flock optionnel
}
```
```yaml
{
"Name": "forest_deer",
"Weight": 50.0,
"RealtimeRespawnTime": 60.0,
"SpawnAfterGameTime": "PT1H",
"Flock": "deer_herd"
}
```
### Timing de Reapparition
Choisir entre reapparition temps reel ou temps de jeu:
**Temps Reel:** Utilise `RealtimeRespawnTime` (secondes)
```yaml
{
"RealtimeRespawn": true,
"NPCs": [{ "RealtimeRespawnTime": 120.0 }]
}
```
**Temps de Jeu:** Utilise `SpawnAfterGameTime` (duree ISO 8601)
```yaml
{
"RealtimeRespawn": false,
"NPCs": [{ "SpawnAfterGameTime": "P1DT6H" }]
}
```
Format duree: `P[jours]DT[heures]H[minutes]M[secondes]S`
### Spawning de Flock
Faire apparaitre un groupe de NPCs autour du spawn principal:
```yaml
{
"Name": "wolf_alpha",
"Flock": "wolf_pack"
}
```
La definition flock specifie les NPCs additionnels a spawn autour du principal.
## Beacons de Spawn
### BeaconNPCSpawn
Points de spawn dynamiques qui font apparaitre des NPCs dans un rayon.
**Emplacement Asset:** `NPC/Spawn/Beacons/`
```yaml
# NPC/Spawn/Beacons/dungeon_spawner.json
{
"Id": "dungeon_spawner",
"NPCRole": "skeleton_warrior",
"SpawnWeight": 10,
"MinGroupSize": 2,
"MaxGroupSize": 5,
"Environments": ["dungeon_dark"]
}
```
### Composant SpawnBeacon
Entites avec comportement beacon de spawn:
```java
public class SpawnBeacon {
// Configuration pour spawning beacon
// Declenche spawning dans un rayon
}
```
### Systemes Beacon
```java
// SpawnBeaconSystems traite les beacons actifs
getEntityStoreRegistry().registerSystem(new SpawnBeaconSystems());
// BeaconSpatialSystem gere les requetes spatiales
getEntityStoreRegistry().registerSystem(new BeaconSpatialSystem());
```
## Configuration Spawn Mondial
### WorldNPCSpawn
Spawning ambiant base sur l'environnement.
**Emplacement Asset:** `NPC/Spawn/World/`
```yaml
# NPC/Spawn/World/forest_fauna.json
{
"Id": "forest_fauna",
"NPCRole": "rabbit",
"SpawnWeight": 20,
"MinGroupSize": 1,
"MaxGroupSize": 3,
"Environments": ["forest", "grassland"],
"LightType": "Day",
"MinLightLevel": 8
}
```
### Champs de Base NPCSpawn
| Champ | Type | Description |
|-------|------|-------------|
| `NPCRole` | String | Role NPC a spawner |
| `SpawnWeight` | Integer | Poids probabilite spawn |
| `MinGroupSize` | Integer | Minimum NPCs a spawner |
| `MaxGroupSize` | Integer | Maximum NPCs a spawner |
### Filtrage par Environnement
Restreindre spawning a des environnements specifiques:
```yaml
{
"Environments": ["cave_dark", "dungeon"]
}
```
### Filtrage par Niveau de Lumiere
```yaml
{
"LightType": "Night",
"MinLightLevel": 0,
"MaxLightLevel": 7
}
```
Types de lumiere: `Any`, `Day`, `Night`
## Entite Marqueur de Spawn
### Composant SpawnMarkerEntity
Entite representant un marqueur de spawn dans le monde:
```java
ComponentType<EntityStore, SpawnMarkerEntity> type =
SpawningPlugin.get().getSpawnMarkerEntityComponentType();
```
### Etat Bloc Marqueur
Les marqueurs de spawn peuvent etre places comme blocs:
```java
public class SpawnMarkerBlockState {
// Marqueur de spawn base bloc
}
public class SpawnMarkerBlockReference {
// Reference au bloc marqueur
}
```
## Interactions
### TriggerSpawnMarkersInteraction
Declencher manuellement des marqueurs de spawn via interaction:
```yaml
# Interaction item ou bloc
{
"Type": "TriggerSpawnMarkers",
"MarkerIds": ["ambush_1", "ambush_2"],
"Radius": 50.0
}
```
Enregistre comme:
```java
getCodecRegistry(Interaction.CODEC).register(
"TriggerSpawnMarkers",
TriggerSpawnMarkersInteraction.class,
TriggerSpawnMarkersInteraction.CODEC
);
```
## Utilisation de l'API
### Acceder aux Assets Marqueur de Spawn
```java
// Obtenir asset marqueur
SpawnMarker marker = SpawnMarker.getAssetMap().getAsset("village_guard");
// Obtenir configurations ponderees
IWeightedMap<SpawnConfiguration> npcs = marker.getWeightedConfigurations();
// Obtenir NPC aleatoire depuis pool
SpawnConfiguration selected = npcs.getRandom(random);
String npcRole = selected.getNpc();
```
### Acceder a la Suppression de Spawn
```java
SpawnSuppression suppression = SpawnSuppression.getAssetMap().getAsset("safe_zone");
double radius = suppression.getRadius();
```
### Valider Asset Marqueur
```java
// La validation se fait au demarrage du plugin
// Avertit sur:
// - Temps de reapparition manquants
// - Parametres temps reel/jeu conflictuels
// - Roles NPC invalides
```
## Ordre de Chargement Assets
Les assets spawn ont des dependances:
```java
// Les marqueurs chargent apres modeles et roles NPC
HytaleAssetStore.builder(...)
.loadsAfter(ModelAsset.class)
.loadsAfter(NPCRole.class)
.build();
```

View File

@@ -0,0 +1,260 @@
---
title: World Spawning
type: docs
weight: 1
---
World spawning manages ambient NPC spawning based on environment, biome, and chunk conditions.
**Package:** `com.hypixel.hytale.server.spawning.world`
## Architecture
```
World Spawning
├── Manager
│ └── WorldSpawnManager - Central spawn coordination
├── Components
│ ├── WorldSpawnData - World-level spawn state
│ ├── ChunkSpawnData - Per-chunk spawn tracking
│ ├── ChunkSpawnedNPCData - Spawned NPC records
│ └── SpawnJobData - Active spawn jobs
├── Systems
│ ├── WorldSpawningSystem - Main spawning logic
│ ├── WorldSpawnTrackingSystem - Track spawned entities
│ ├── WorldSpawnJobSystems - Job processing
│ ├── ChunkSpawningSystems - Chunk-level spawning
│ └── MoonPhaseChangeEventSystem - Lunar spawn modifiers
└── Config
├── WorldNPCSpawn - Spawn definitions
├── NPCSpawn - Base spawn config
└── RoleSpawnParameters - Role-specific params
```
## WorldSpawnManager
Central manager for world-level NPC spawning:
```java
WorldSpawnManager manager = SpawningPlugin.get().getWorldSpawnManager();
```
The manager coordinates spawning across chunks based on:
- Environment type
- Biome conditions
- Light levels
- Moon phase
- Player proximity
## World Spawn Configuration
### WorldNPCSpawn Asset
```yaml
# NPC/Spawn/World/forest_creatures.json
{
"Id": "forest_creatures",
"NPCRole": "deer",
"SpawnWeight": 10,
"MinGroupSize": 1,
"MaxGroupSize": 3,
"Environments": ["forest", "plains"],
"LightType": "Day",
"MinLightLevel": 8,
"MaxLightLevel": 15
}
```
### NPCSpawn Base Configuration
```java
public class NPCSpawn {
protected String npcRole; // NPC role to spawn
protected int spawnWeight; // Spawn probability weight
protected int minGroupSize; // Minimum group size
protected int maxGroupSize; // Maximum group size
}
```
### RoleSpawnParameters
Per-role spawn parameters:
```java
public class RoleSpawnParameters {
// Role-specific spawn configuration
// Defines constraints and behaviors per NPC type
}
```
## Environment-Based Spawning
### EnvironmentSpawnParameters
```java
EnvironmentSpawnParameters params = new EnvironmentSpawnParameters();
// Parameters specific to environment type
```
### Light Type Filtering
```java
public enum LightType {
Any, // Spawn at any light level
Day, // Only spawn during day
Night // Only spawn at night
}
```
Used with `LightRangePredicate` to filter spawn positions:
```java
LightRangePredicate predicate = new LightRangePredicate(minLight, maxLight, lightType);
boolean canSpawn = predicate.test(world, position);
```
## Chunk-Level Management
### ChunkSpawnData
Tracks spawn state per chunk:
```java
ComponentType<ChunkStore, ChunkSpawnData> type =
SpawningPlugin.get().getChunkSpawnDataComponentType();
ChunkSpawnData data = chunkStore.getComponent(chunkRef, type);
```
### ChunkSpawnedNPCData
Records which NPCs were spawned in a chunk:
```java
// Tracks spawned entities for cleanup and respawning
```
### ChunkEnvironmentSpawnData
Environment-specific spawn data per chunk:
```java
// Caches environment conditions for spawn decisions
```
## Spawn Job System
### SpawnJob
Base class for spawn operations:
```java
public class SpawnJob {
// Asynchronous spawn operation
}
```
### SpawnJobData Component
Tracks active spawn jobs:
```java
SpawnJobData jobData = store.getComponent(worldRef, SpawnJobData.getComponentType());
```
### WorldSpawnJobSystems
Processes spawn jobs:
```java
// System that executes pending spawn jobs
// Handles spawn position validation
// Creates NPCs when conditions are met
```
## World Spawn Systems
### WorldSpawningSystem
Main system for world spawning logic:
```java
getEntityStoreRegistry().registerSystem(new WorldSpawningSystem());
```
This system:
1. Checks spawn conditions per chunk
2. Selects appropriate NPC types
3. Validates spawn positions
4. Creates spawn jobs
### WorldSpawnTrackingSystem
Tracks spawned entities for management:
```java
// Monitors spawned NPCs
// Handles despawn when conditions change
// Updates spawn counts
```
### MoonPhaseChangeEventSystem
Adjusts spawning based on moon phase:
```java
// Modifies spawn rates during different moon phases
// Enables special night spawns during full moon
```
## Position Selection
### FloodFillPositionSelector
Finds valid spawn positions using flood fill:
```java
FloodFillPositionSelector selector = new FloodFillPositionSelector();
// Searches for suitable spawn locations
```
### RandomChunkColumnIterator
Iterates through random positions in a chunk column:
```java
RandomChunkColumnIterator iterator = new RandomChunkColumnIterator();
// Provides random positions for spawn attempts
```
### ChunkColumnMask
Masks specific areas within a chunk:
```java
ChunkColumnMask mask = new ChunkColumnMask();
// Excludes certain positions from spawning
```
## API Usage
### Check World Spawn State
```java
WorldSpawnData worldData = store.getComponent(worldRef, WorldSpawnData.getComponentType());
```
### Get Spawn Wrapper
```java
WorldSpawnWrapper wrapper = manager.getSpawnWrapper(spawnIndex);
WorldNPCSpawn spawn = wrapper.getSpawn();
```
## Spawn Statistics
Track spawning metrics with `WorldNPCSpawnStat`:
```java
// Records spawn attempts, successes, and failures
// Used by /spawning stats command
```

View File

@@ -0,0 +1,260 @@
---
title: Spawning Mondial
type: docs
weight: 1
---
Le spawning mondial gere le spawn ambiant de NPCs base sur l'environnement, le biome et les conditions de chunk.
**Package:** `com.hypixel.hytale.server.spawning.world`
## Architecture
```
Spawning Mondial
├── Manager
│ └── WorldSpawnManager - Coordination centrale du spawn
├── Composants
│ ├── WorldSpawnData - Etat spawn niveau monde
│ ├── ChunkSpawnData - Suivi spawn par chunk
│ ├── ChunkSpawnedNPCData - Enregistrements NPCs spawnes
│ └── SpawnJobData - Jobs de spawn actifs
├── Systemes
│ ├── WorldSpawningSystem - Logique principale de spawn
│ ├── WorldSpawnTrackingSystem - Suivi entites spawnees
│ ├── WorldSpawnJobSystems - Traitement des jobs
│ ├── ChunkSpawningSystems - Spawning niveau chunk
│ └── MoonPhaseChangeEventSystem - Modificateurs lunaires
└── Config
├── WorldNPCSpawn - Definitions de spawn
├── NPCSpawn - Config spawn de base
└── RoleSpawnParameters - Params specifiques role
```
## WorldSpawnManager
Manager central pour le spawning NPC niveau monde:
```java
WorldSpawnManager manager = SpawningPlugin.get().getWorldSpawnManager();
```
Le manager coordonne le spawning a travers les chunks base sur:
- Type d'environnement
- Conditions de biome
- Niveaux de lumiere
- Phase lunaire
- Proximite des joueurs
## Configuration Spawn Mondial
### Asset WorldNPCSpawn
```yaml
# NPC/Spawn/World/forest_creatures.json
{
"Id": "forest_creatures",
"NPCRole": "deer",
"SpawnWeight": 10,
"MinGroupSize": 1,
"MaxGroupSize": 3,
"Environments": ["forest", "plains"],
"LightType": "Day",
"MinLightLevel": 8,
"MaxLightLevel": 15
}
```
### Configuration de Base NPCSpawn
```java
public class NPCSpawn {
protected String npcRole; // Role NPC a spawner
protected int spawnWeight; // Poids de probabilite
protected int minGroupSize; // Taille groupe minimum
protected int maxGroupSize; // Taille groupe maximum
}
```
### RoleSpawnParameters
Parametres de spawn par role:
```java
public class RoleSpawnParameters {
// Configuration spawn specifique au role
// Definit contraintes et comportements par type NPC
}
```
## Spawning Base sur l'Environnement
### EnvironmentSpawnParameters
```java
EnvironmentSpawnParameters params = new EnvironmentSpawnParameters();
// Parametres specifiques au type d'environnement
```
### Filtrage par Type de Lumiere
```java
public enum LightType {
Any, // Spawn a tout niveau de lumiere
Day, // Spawn uniquement le jour
Night // Spawn uniquement la nuit
}
```
Utilise avec `LightRangePredicate` pour filtrer les positions de spawn:
```java
LightRangePredicate predicate = new LightRangePredicate(minLight, maxLight, lightType);
boolean canSpawn = predicate.test(world, position);
```
## Gestion Niveau Chunk
### ChunkSpawnData
Suit l'etat de spawn par chunk:
```java
ComponentType<ChunkStore, ChunkSpawnData> type =
SpawningPlugin.get().getChunkSpawnDataComponentType();
ChunkSpawnData data = chunkStore.getComponent(chunkRef, type);
```
### ChunkSpawnedNPCData
Enregistre quels NPCs ont ete spawnes dans un chunk:
```java
// Suit les entites spawnees pour nettoyage et respawn
```
### ChunkEnvironmentSpawnData
Donnees de spawn specifiques a l'environnement par chunk:
```java
// Met en cache les conditions d'environnement pour decisions de spawn
```
## Systeme de Jobs de Spawn
### SpawnJob
Classe de base pour operations de spawn:
```java
public class SpawnJob {
// Operation de spawn asynchrone
}
```
### Composant SpawnJobData
Suit les jobs de spawn actifs:
```java
SpawnJobData jobData = store.getComponent(worldRef, SpawnJobData.getComponentType());
```
### WorldSpawnJobSystems
Traite les jobs de spawn:
```java
// Systeme qui execute les jobs de spawn en attente
// Gere la validation des positions de spawn
// Cree les NPCs quand conditions remplies
```
## Systemes de Spawn Mondial
### WorldSpawningSystem
Systeme principal pour la logique de spawn mondial:
```java
getEntityStoreRegistry().registerSystem(new WorldSpawningSystem());
```
Ce systeme:
1. Verifie les conditions de spawn par chunk
2. Selectionne les types NPC appropries
3. Valide les positions de spawn
4. Cree les jobs de spawn
### WorldSpawnTrackingSystem
Suit les entites spawnees pour gestion:
```java
// Surveille les NPCs spawnes
// Gere le despawn quand conditions changent
// Met a jour les compteurs de spawn
```
### MoonPhaseChangeEventSystem
Ajuste le spawning selon la phase lunaire:
```java
// Modifie les taux de spawn pendant differentes phases
// Active spawns nocturnes speciaux pendant pleine lune
```
## Selection de Position
### FloodFillPositionSelector
Trouve les positions de spawn valides via flood fill:
```java
FloodFillPositionSelector selector = new FloodFillPositionSelector();
// Recherche des emplacements de spawn adequats
```
### RandomChunkColumnIterator
Itere a travers des positions aleatoires dans une colonne de chunk:
```java
RandomChunkColumnIterator iterator = new RandomChunkColumnIterator();
// Fournit positions aleatoires pour tentatives de spawn
```
### ChunkColumnMask
Masque des zones specifiques dans un chunk:
```java
ChunkColumnMask mask = new ChunkColumnMask();
// Exclut certaines positions du spawning
```
## Utilisation de l'API
### Verifier l'Etat de Spawn Mondial
```java
WorldSpawnData worldData = store.getComponent(worldRef, WorldSpawnData.getComponentType());
```
### Obtenir un Wrapper de Spawn
```java
WorldSpawnWrapper wrapper = manager.getSpawnWrapper(spawnIndex);
WorldNPCSpawn spawn = wrapper.getSpawn();
```
## Statistiques de Spawn
Suivre les metriques de spawn avec `WorldNPCSpawnStat`:
```java
// Enregistre tentatives, succes et echecs de spawn
// Utilise par la commande /spawning stats
```