Update script to write to vendor/hytale-server
This commit is contained in:
434
docs/08-asset-system.md
Normal file
434
docs/08-asset-system.md
Normal file
@@ -0,0 +1,434 @@
|
||||
# Asset System
|
||||
|
||||
The Hytale asset system manages game assets including blocks, items, models, sounds, particles, and more. This guide covers how to work with assets and create custom content.
|
||||
|
||||
## Overview
|
||||
|
||||
Assets are defined in JSON files and loaded by the `AssetStore`. The server provides a type-safe asset system with:
|
||||
|
||||
- JSON-based asset definitions
|
||||
- Codec-based serialization
|
||||
- Asset type registration
|
||||
- Asset pack support for mods
|
||||
|
||||
## Asset Store
|
||||
|
||||
### Accessing Assets
|
||||
|
||||
```java
|
||||
// Get the asset store
|
||||
AssetStore store = HytaleAssetStore.get();
|
||||
|
||||
// Get a specific asset by ID
|
||||
BlockType block = store.get(BlockType.class, "stone");
|
||||
ItemType item = store.get(ItemType.class, "sword");
|
||||
|
||||
// Get all assets of a type
|
||||
Collection<BlockType> allBlocks = store.getAll(BlockType.class);
|
||||
```
|
||||
|
||||
### Asset Loading
|
||||
|
||||
Assets are loaded from:
|
||||
|
||||
1. Core game assets
|
||||
2. Plugin asset packs (when `IncludesAssetPack: true` in manifest)
|
||||
|
||||
## Asset Types
|
||||
|
||||
The server supports many built-in asset types located in `server/core/asset/type/`:
|
||||
|
||||
| Asset Type | Description |
|
||||
|------------|-------------|
|
||||
| `BlockType` | Block definitions (stone, dirt, etc.) |
|
||||
| `ItemType` | Item definitions |
|
||||
| `ModelAsset` | 3D model definitions |
|
||||
| `SoundEvent` | Sound effect definitions |
|
||||
| `ParticleAsset` | Particle system definitions |
|
||||
| `WeatherAsset` | Weather type definitions |
|
||||
| `EnvironmentAsset` | Environment settings |
|
||||
| `FluidAsset` | Fluid definitions |
|
||||
| `EntityEffect` | Entity effect definitions |
|
||||
| `BiomeAsset` | Biome definitions |
|
||||
| `StructureAsset` | Structure/prefab definitions |
|
||||
|
||||
## Blocks
|
||||
|
||||
### Block Type Definition
|
||||
|
||||
Block types are defined in JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "mymod:custom_block",
|
||||
"name": "Custom Block",
|
||||
"model": "mymod:models/custom_block",
|
||||
"hardness": 2.0,
|
||||
"resistance": 6.0,
|
||||
"drops": ["mymod:custom_block"],
|
||||
"sounds": {
|
||||
"break": "block.stone.break",
|
||||
"place": "block.stone.place",
|
||||
"step": "block.stone.step"
|
||||
},
|
||||
"properties": {
|
||||
"transparent": false,
|
||||
"solid": true,
|
||||
"flammable": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Block States
|
||||
|
||||
Blocks can have multiple states (orientations, variations):
|
||||
|
||||
```java
|
||||
// Get block state
|
||||
BlockState state = BlockStates.get("stone");
|
||||
|
||||
// Create state with properties
|
||||
BlockState orientedBlock = BlockStates.get("log", Map.of(
|
||||
"axis", "y"
|
||||
));
|
||||
|
||||
// Check block properties
|
||||
boolean isSolid = state.isSolid();
|
||||
boolean isTransparent = state.isTransparent();
|
||||
```
|
||||
|
||||
### Registering Block States
|
||||
|
||||
```java
|
||||
@Override
|
||||
protected void setup() {
|
||||
getBlockStateRegistry().register("custom_block", CustomBlockState.class);
|
||||
}
|
||||
```
|
||||
|
||||
## Items
|
||||
|
||||
### Item Type Definition
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "mymod:custom_sword",
|
||||
"name": "Custom Sword",
|
||||
"model": "mymod:models/custom_sword",
|
||||
"maxStackSize": 1,
|
||||
"durability": 500,
|
||||
"itemType": "weapon",
|
||||
"damage": 7.0,
|
||||
"attackSpeed": 1.6,
|
||||
"enchantable": true
|
||||
}
|
||||
```
|
||||
|
||||
### Working with Items
|
||||
|
||||
```java
|
||||
// Get item type
|
||||
ItemType swordType = store.get(ItemType.class, "mymod:custom_sword");
|
||||
|
||||
// Create item stack
|
||||
ItemStack stack = new ItemStack(swordType, 1);
|
||||
|
||||
// Set item data
|
||||
stack.setDurability(400);
|
||||
stack.setCustomName("Legendary Sword");
|
||||
|
||||
// Add to inventory
|
||||
player.getInventory().addItem(stack);
|
||||
```
|
||||
|
||||
## Models
|
||||
|
||||
### Model Asset Definition
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "mymod:models/custom_block",
|
||||
"type": "block",
|
||||
"textures": {
|
||||
"all": "mymod:textures/custom_block"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [0, 0, 0],
|
||||
"to": [16, 16, 16],
|
||||
"faces": {
|
||||
"north": {"texture": "#all"},
|
||||
"south": {"texture": "#all"},
|
||||
"east": {"texture": "#all"},
|
||||
"west": {"texture": "#all"},
|
||||
"up": {"texture": "#all"},
|
||||
"down": {"texture": "#all"}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Sounds
|
||||
|
||||
### Sound Event Definition
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "mymod:custom_sound",
|
||||
"sounds": [
|
||||
{
|
||||
"name": "mymod:sounds/custom1",
|
||||
"volume": 1.0,
|
||||
"pitch": 1.0,
|
||||
"weight": 1
|
||||
},
|
||||
{
|
||||
"name": "mymod:sounds/custom2",
|
||||
"volume": 0.8,
|
||||
"pitch": 1.2,
|
||||
"weight": 1
|
||||
}
|
||||
],
|
||||
"category": "block"
|
||||
}
|
||||
```
|
||||
|
||||
### Playing Sounds
|
||||
|
||||
```java
|
||||
// Get sound event
|
||||
SoundEvent sound = store.get(SoundEvent.class, "mymod:custom_sound");
|
||||
|
||||
// Play at position
|
||||
world.playSound(sound, position, volume, pitch);
|
||||
|
||||
// Play to specific player
|
||||
player.playSound(sound, volume, pitch);
|
||||
|
||||
// Play to all nearby players
|
||||
world.playSoundNearby(sound, position, radius, volume, pitch);
|
||||
```
|
||||
|
||||
## Particles
|
||||
|
||||
### Particle Asset Definition
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "mymod:custom_particle",
|
||||
"texture": "mymod:textures/particles/custom",
|
||||
"lifetime": 20,
|
||||
"scale": 1.0,
|
||||
"gravity": 0.04,
|
||||
"color": [1.0, 1.0, 1.0, 1.0],
|
||||
"fadeOut": true
|
||||
}
|
||||
```
|
||||
|
||||
### Spawning Particles
|
||||
|
||||
```java
|
||||
// Get particle asset
|
||||
ParticleAsset particle = store.get(ParticleAsset.class, "mymod:custom_particle");
|
||||
|
||||
// Spawn particles
|
||||
world.spawnParticle(particle, position, count, spreadX, spreadY, spreadZ, speed);
|
||||
|
||||
// Spawn for specific player
|
||||
player.spawnParticle(particle, position, count);
|
||||
```
|
||||
|
||||
## Custom Asset Types
|
||||
|
||||
### Defining a Custom Asset Type
|
||||
|
||||
```java
|
||||
public class MyCustomAsset {
|
||||
public static final Codec<MyCustomAsset> CODEC = BuilderCodec.of(MyCustomAsset::new)
|
||||
.with("id", Codec.STRING, a -> a.id)
|
||||
.with("value", Codec.INTEGER, a -> a.value, 0)
|
||||
.with("enabled", Codec.BOOLEAN, a -> a.enabled, true)
|
||||
.build();
|
||||
|
||||
private final String id;
|
||||
private final int value;
|
||||
private final boolean enabled;
|
||||
|
||||
public MyCustomAsset(String id, int value, boolean enabled) {
|
||||
this.id = id;
|
||||
this.value = value;
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getId() { return id; }
|
||||
public int getValue() { return value; }
|
||||
public boolean isEnabled() { return enabled; }
|
||||
}
|
||||
```
|
||||
|
||||
### Registering Custom Asset Types
|
||||
|
||||
```java
|
||||
@Override
|
||||
protected void setup() {
|
||||
getAssetRegistry().register(MyCustomAsset.class, MyCustomAsset.CODEC);
|
||||
}
|
||||
```
|
||||
|
||||
### Using Custom Assets
|
||||
|
||||
```java
|
||||
@Override
|
||||
protected void start() {
|
||||
AssetStore store = HytaleAssetStore.get();
|
||||
|
||||
// Get all custom assets
|
||||
Collection<MyCustomAsset> assets = store.getAll(MyCustomAsset.class);
|
||||
|
||||
for (MyCustomAsset asset : assets) {
|
||||
if (asset.isEnabled()) {
|
||||
processAsset(asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Asset Packs
|
||||
|
||||
### Creating an Asset Pack
|
||||
|
||||
1. Set `IncludesAssetPack: true` in your `manifest.json`
|
||||
2. Create an `assets/` directory in your JAR
|
||||
3. Organize assets following Hytale's structure:
|
||||
|
||||
```
|
||||
assets/
|
||||
├── mymod/
|
||||
│ ├── blocks/
|
||||
│ │ └── custom_block.json
|
||||
│ ├── items/
|
||||
│ │ └── custom_item.json
|
||||
│ ├── models/
|
||||
│ │ ├── block/
|
||||
│ │ │ └── custom_block.json
|
||||
│ │ └── item/
|
||||
│ │ └── custom_item.json
|
||||
│ ├── textures/
|
||||
│ │ ├── blocks/
|
||||
│ │ │ └── custom_block.png
|
||||
│ │ └── items/
|
||||
│ │ └── custom_item.png
|
||||
│ └── sounds/
|
||||
│ └── custom_sound.ogg
|
||||
```
|
||||
|
||||
### Asset Namespacing
|
||||
|
||||
Assets are namespaced by plugin ID:
|
||||
|
||||
```
|
||||
{group}:{name} -> mymod:custom_block
|
||||
```
|
||||
|
||||
Reference assets using their full namespaced ID to avoid conflicts.
|
||||
|
||||
## Entity Assets
|
||||
|
||||
### Entity Type Definition
|
||||
|
||||
Entities can have associated assets:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "mymod:custom_mob",
|
||||
"name": "Custom Mob",
|
||||
"model": "mymod:models/entity/custom_mob",
|
||||
"textures": {
|
||||
"default": "mymod:textures/entity/custom_mob"
|
||||
},
|
||||
"animations": {
|
||||
"idle": "mymod:animations/custom_mob_idle",
|
||||
"walk": "mymod:animations/custom_mob_walk",
|
||||
"attack": "mymod:animations/custom_mob_attack"
|
||||
},
|
||||
"sounds": {
|
||||
"hurt": "mymod:entity.custom_mob.hurt",
|
||||
"death": "mymod:entity.custom_mob.death",
|
||||
"ambient": "mymod:entity.custom_mob.ambient"
|
||||
},
|
||||
"attributes": {
|
||||
"maxHealth": 20.0,
|
||||
"movementSpeed": 0.3,
|
||||
"attackDamage": 4.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Weather Assets
|
||||
|
||||
### Weather Definition
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "mymod:custom_weather",
|
||||
"name": "Custom Weather",
|
||||
"particles": "mymod:weather_particle",
|
||||
"skyColor": [0.5, 0.5, 0.7],
|
||||
"fogColor": [0.6, 0.6, 0.8],
|
||||
"lightLevel": 0.7,
|
||||
"sounds": {
|
||||
"ambient": "mymod:weather.custom.ambient"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Assets
|
||||
|
||||
### Environment Definition
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "mymod:custom_environment",
|
||||
"skybox": "mymod:textures/skybox/custom",
|
||||
"ambientLight": 0.4,
|
||||
"sunLight": 1.0,
|
||||
"fogDensity": 0.02,
|
||||
"music": "mymod:music/custom_ambient"
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use namespaces** - Always prefix asset IDs with your mod namespace
|
||||
2. **Follow conventions** - Use lowercase, underscores for asset IDs
|
||||
3. **Optimize textures** - Use appropriate resolutions and compression
|
||||
4. **Provide fallbacks** - Handle missing assets gracefully
|
||||
5. **Document assets** - Comment complex asset definitions
|
||||
6. **Test loading** - Verify all assets load correctly
|
||||
7. **Version assets** - Track asset changes with mod versions
|
||||
8. **Reuse when possible** - Reference existing assets instead of duplicating
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Asset Not Found
|
||||
|
||||
```java
|
||||
// Check if asset exists
|
||||
if (store.has(MyAsset.class, "mymod:asset_id")) {
|
||||
MyAsset asset = store.get(MyAsset.class, "mymod:asset_id");
|
||||
}
|
||||
```
|
||||
|
||||
### Asset Loading Errors
|
||||
|
||||
- Check JSON syntax
|
||||
- Verify codec definitions match JSON structure
|
||||
- Ensure all referenced assets (textures, models) exist
|
||||
- Check namespace spelling
|
||||
|
||||
### Asset Pack Not Loading
|
||||
|
||||
- Verify `IncludesAssetPack: true` in manifest
|
||||
- Check asset directory structure
|
||||
- Ensure JAR file includes assets
|
||||
Reference in New Issue
Block a user