Init
This commit is contained in:
53
content/world/entities/_index.en.md
Normal file
53
content/world/entities/_index.en.md
Normal 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 >}}
|
||||
53
content/world/entities/_index.fr.md
Normal file
53
content/world/entities/_index.fr.md
Normal 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 >}}
|
||||
828
content/world/entities/entity-components.en.md
Normal file
828
content/world/entities/entity-components.en.md
Normal 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 |
|
||||
828
content/world/entities/entity-components.fr.md
Normal file
828
content/world/entities/entity-components.fr.md
Normal 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 |
|
||||
259
content/world/entities/entity-hierarchy.en.md
Normal file
259
content/world/entities/entity-hierarchy.en.md
Normal 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 >}}
|
||||
|
||||
259
content/world/entities/entity-hierarchy.fr.md
Normal file
259
content/world/entities/entity-hierarchy.fr.md
Normal 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 >}}
|
||||
|
||||
302
content/world/entities/flocking-behavior.en.md
Normal file
302
content/world/entities/flocking-behavior.en.md
Normal 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)
|
||||
);
|
||||
```
|
||||
302
content/world/entities/flocking-behavior.fr.md
Normal file
302
content/world/entities/flocking-behavior.fr.md
Normal 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)
|
||||
);
|
||||
```
|
||||
57
content/world/entities/inventory/_index.en.md
Normal file
57
content/world/entities/inventory/_index.en.md
Normal 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
|
||||
```
|
||||
57
content/world/entities/inventory/_index.fr.md
Normal file
57
content/world/entities/inventory/_index.fr.md
Normal 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
|
||||
```
|
||||
668
content/world/entities/inventory/containers.en.md
Normal file
668
content/world/entities/inventory/containers.en.md
Normal 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 |
|
||||
668
content/world/entities/inventory/containers.fr.md
Normal file
668
content/world/entities/inventory/containers.fr.md
Normal 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 |
|
||||
354
content/world/entities/inventory/itemstacks.en.md
Normal file
354
content/world/entities/inventory/itemstacks.en.md
Normal 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 |
|
||||
354
content/world/entities/inventory/itemstacks.fr.md
Normal file
354
content/world/entities/inventory/itemstacks.fr.md
Normal 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 |
|
||||
430
content/world/entities/inventory/transactions.en.md
Normal file
430
content/world/entities/inventory/transactions.en.md
Normal 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 |
|
||||
430
content/world/entities/inventory/transactions.fr.md
Normal file
430
content/world/entities/inventory/transactions.fr.md
Normal 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 |
|
||||
291
content/world/entities/mounts.en.md
Normal file
291
content/world/entities/mounts.en.md
Normal 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);
|
||||
```
|
||||
291
content/world/entities/mounts.fr.md
Normal file
291
content/world/entities/mounts.fr.md
Normal 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);
|
||||
```
|
||||
86
content/world/entities/npc/_index.en.md
Normal file
86
content/world/entities/npc/_index.en.md
Normal 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 |
|
||||
86
content/world/entities/npc/_index.fr.md
Normal file
86
content/world/entities/npc/_index.fr.md
Normal 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 |
|
||||
373
content/world/entities/npc/npc-ai.en.md
Normal file
373
content/world/entities/npc/npc-ai.en.md
Normal 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 >}}
|
||||
373
content/world/entities/npc/npc-ai.fr.md
Normal file
373
content/world/entities/npc/npc-ai.fr.md
Normal 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 >}}
|
||||
217
content/world/entities/npc/npc-basics.en.md
Normal file
217
content/world/entities/npc/npc-basics.en.md
Normal 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 >}}
|
||||
217
content/world/entities/npc/npc-basics.fr.md
Normal file
217
content/world/entities/npc/npc-basics.fr.md
Normal 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 >}}
|
||||
374
content/world/entities/npc/npc-commands.en.md
Normal file
374
content/world/entities/npc/npc-commands.en.md
Normal 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 >}}
|
||||
374
content/world/entities/npc/npc-commands.fr.md
Normal file
374
content/world/entities/npc/npc-commands.fr.md
Normal 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 >}}
|
||||
348
content/world/entities/npc/npc-components.en.md
Normal file
348
content/world/entities/npc/npc-components.en.md
Normal 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 >}}
|
||||
348
content/world/entities/npc/npc-components.fr.md
Normal file
348
content/world/entities/npc/npc-components.fr.md
Normal 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 >}}
|
||||
365
content/world/entities/npc/npc-movement.en.md
Normal file
365
content/world/entities/npc/npc-movement.en.md
Normal 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 >}}
|
||||
365
content/world/entities/npc/npc-movement.fr.md
Normal file
365
content/world/entities/npc/npc-movement.fr.md
Normal 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 >}}
|
||||
904
content/world/entities/player-api.en.md
Normal file
904
content/world/entities/player-api.en.md
Normal 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 >}}
|
||||
|
||||
904
content/world/entities/player-api.fr.md
Normal file
904
content/world/entities/player-api.fr.md
Normal 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 >}}
|
||||
|
||||
266
content/world/entities/spawning-entities.en.md
Normal file
266
content/world/entities/spawning-entities.en.md
Normal 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());
|
||||
}
|
||||
```
|
||||
266
content/world/entities/spawning-entities.fr.md
Normal file
266
content/world/entities/spawning-entities.fr.md
Normal 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());
|
||||
}
|
||||
```
|
||||
112
content/world/entities/spawning/_index.en.md
Normal file
112
content/world/entities/spawning/_index.en.md
Normal 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 >}}
|
||||
112
content/world/entities/spawning/_index.fr.md
Normal file
112
content/world/entities/spawning/_index.fr.md
Normal 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 >}}
|
||||
212
content/world/entities/spawning/local-spawning.en.md
Normal file
212
content/world/entities/spawning/local-spawning.en.md
Normal 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
|
||||
212
content/world/entities/spawning/local-spawning.fr.md
Normal file
212
content/world/entities/spawning/local-spawning.fr.md
Normal 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
|
||||
249
content/world/entities/spawning/spawn-suppression.en.md
Normal file
249
content/world/entities/spawning/spawn-suppression.en.md
Normal 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 |
|
||||
249
content/world/entities/spawning/spawn-suppression.fr.md
Normal file
249
content/world/entities/spawning/spawn-suppression.fr.md
Normal 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 |
|
||||
304
content/world/entities/spawning/spawner-assets.en.md
Normal file
304
content/world/entities/spawning/spawner-assets.en.md
Normal 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();
|
||||
```
|
||||
304
content/world/entities/spawning/spawner-assets.fr.md
Normal file
304
content/world/entities/spawning/spawner-assets.fr.md
Normal 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();
|
||||
```
|
||||
260
content/world/entities/spawning/world-spawning.en.md
Normal file
260
content/world/entities/spawning/world-spawning.en.md
Normal 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
|
||||
```
|
||||
260
content/world/entities/spawning/world-spawning.fr.md
Normal file
260
content/world/entities/spawning/world-spawning.fr.md
Normal 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
|
||||
```
|
||||
Reference in New Issue
Block a user