Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions content/docs/af-ZA/contributing/pr-guidelines.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ To ensure a smooth and efficient review process, both contributors and reviewers
4. **Code Quality**
- Ensure your code follows the project's coding standards and best practices.
- Include comments where necessary to explain complex logic.
- Always test your changes locally before submitting the PR to catch any issues early.
- Format your code using `bun format` to maintain consistency across the codebase.
5. **Testing**
- Test your changes thoroughly before submitting the PR.
- If applicable, include unit tests or integration tests to verify your changes.
Expand Down
74 changes: 74 additions & 0 deletions content/docs/af-ZA/contributing/translate.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
title: Translate
description: How to translate HytaleModding to your language
---

# Translating HytaleModding

HytaleModding is translated with the help of the community using **Crowdin**. If you want to help translate or improve existing translations, this is the right place.

## Steps to Translate

All translations are managed **via Crowdin**.

1. Visit [our Crowdin project](https://crowdin.com/project/hytalemodding) and log in if required.
2. Click on your language.
3. Click on the file/article you wish to translate. We recommend starting with Official Documentation, and then Server Plugin Documentation.
4. Start translating!
5. Approved translations will appear on the website.

### Video Guide

<iframe src="https://www.youtube.com/embed/NHiG1rDRgks" allowFullScreen />

## How to discuss

You can always report issues or discuss specific topics directly on Crowdin, but we recommend using Discord instead of the Crowdin comment system to keep everything organized in one place.

1. Join [our Discord](https://discord.gg/hytalemodding)
2. Open `#translations` channel. Here you can discuss or ask questions that are not related to a specific language.
3. If you want to join a thread, you are required to run `/translator <your-language>` command here. The bot will add you to the thread for your language.

## Translation Guidelines

- You need to be confident in what you are translating. You should be able to read the text you just translated and understand the meaning of it very easily.
- It should use the simplest form of language possible. Use English words if the translation is unknown to the majority in your country.
- Use your imagination, trust yourself. Feel free to change any context as you wish as long as it can still get the same point across and is understandable.
- These guidelines are more important than language-specific guidelines, but less important than Hytale terminology and rules.

## What not to translate

We request you all to not translate these specific sections of the website:

### Callout types

Callouts are the boxes that contain important information. We request you to not translate the types as they only work in English.

**Example:**

```mdx
<Callout type="warning" title="Translate this title!">
Translate the text inside the callout!
</Callout>
```

In this example, please don't translate the word warning, the rest you can translate.

### Icons

Icon names must not be translated.
They are technical identifiers mapped directly to existing icons. Translating them will break the icon rendering.

```mdx
icon: Globe
```

---

{/*
For translators who will edit this page:

- You are required to translate the Guidelines as they change.
- General guidelines should be translated but not edited.
- Learn how to use Markdown from writing_guides.mdx
*/}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Hytale provides comprehensive support for 3D modeling and texture art creation,
Hytale uses **Blockbench** as the primary tool for 3D modeling and animation:

* **Official support**: Used for "creating Hytale models, textures, and animations"
* **Special plugin**: Hytale will have a dedicated Blockbench plugin, to be released ASAP
* **Special plugin**: Hytale has a dedicated [Blockbench Plugin](https://www.blockbench.net/plugins/hytale_plugin).
* **Direct compatibility**: The plugin allows creators to "create Hytale-compatible models, textures, and animations directly in Blockbench"
* **Replaces legacy tools**: Fully replaces the old Hytale Model Maker to better support established creative workflows in the community
* **Safety features**: Only allows you to add things compatible with the engine, so no need to worry about breaking anything
Expand Down
247 changes: 247 additions & 0 deletions content/docs/af-ZA/guides/ecs/block-components.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
---
title: Block Components
description: Learn how to use the block components.
authors:
- name: Bird
link: https://discord.com/users/495709580068651009
- name: oskarscot
link: https://oskar.scot
---

# Overview

In this guide, you'll learn how to use block components to create a ticking block.

<Callout>
For a more complete reference, you may also want to review FarmingSystems.Ticking, as much of the underlying logic is based on its implementation.
</Callout>

## Steps

### 1. ExampleBlock - holds block behavior

```java
public class ExampleBlock implements Component<ChunkStore> {

public static final BuilderCodec CODEC = BuilderCodec.builder(ExampleBlock.class, ExampleBlock::new).build();

// Components usually require a copy constructor as well but since we don't actually hold any data in this component this is not neccessary
public ExampleBlock() { }

public static ComponentType getComponentType() {
return ExamplePlugin.get().getExampleBlockComponentType();
}

@Nullable
public Component<ChunkStore> clone() {
return new ExampleBlock();
}
}
```

ExampleBlock is a custom component that stores the behavior and data for your ticking block. Here, it simply places an Ice Block at x + 1 relative to its current position when it ticks.

---

### 2. ExampleInitializer - marks blocks as ticking when placed

```java
public class ExampleInitializer extends RefSystem {

@Override
public void onEntityAdded(@Nonnull Ref ref, @Nonnull AddReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer) {
BlockModule.BlockStateInfo info = (BlockModule.BlockStateInfo) commandBuffer.getComponent(ref, BlockModule.BlockStateInfo.getComponentType());
if (info == null) return;

ExampleBlock generator = (ExampleBlock) commandBuffer.getComponent(ref, ExamplePlugin.get().getExampleBlockComponentType());
if (generator != null) {
int x = ChunkUtil.xFromBlockInColumn(info.getIndex());
int y = ChunkUtil.yFromBlockInColumn(info.getIndex());
int z = ChunkUtil.zFromBlockInColumn(info.getIndex());

WorldChunk worldChunk = (WorldChunk) commandBuffer.getComponent(info.getChunkRef(), WorldChunk.getComponentType());
if (worldChunk != null) {
worldChunk.setTicking(x, y, z, true);
}
}
}

@Override
public void onEntityRemove(@Nonnull Ref ref, @Nonnull RemoveReason reason, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer) {
}

@Override
public Query getQuery() {
return Query.and(BlockModule.BlockStateInfo.getComponentType(), ExamplePlugin.get().getExampleBlockComponentType());
}
}
```

ExampleInitializer is a RefSystem that reacts when block entities with the ExampleBlock component are added or removed. This is crucial for marking blocks as ticking when they're first placed.

**Key Points:**

- Tells the game that this block should tick, allowing `ExampleSystem` to process it.

```java
worldChunk.setTicking(x, y, z, true);
```

---

### 3. ExampleSystem - handles ticking

```java
public class ExampleSystem extends EntityTickingSystem {

public void tick(float dt, int index, @Nonnull ArchetypeChunk archetypeChunk, @Nonnull Store store, @Nonnull CommandBuffer commandBuffer) {
BlockSection blocks = (BlockSection) archetypeChunk.getComponent(index, BlockSection.getComponentType());

assert blocks != null;

if (blocks.getTickingBlocksCountCopy() != 0) {
ChunkSection section = (ChunkSection) archetypeChunk.getComponent(index, ChunkSection.getComponentType());

assert section != null;

BlockComponentChunk blockComponentChunk = (BlockComponentChunk) commandBuffer.getComponent(section.getChunkColumnReference(), BlockComponentChunk.getComponentType());

assert blockComponentChunk != null;

blocks.forEachTicking(blockComponentChunk, commandBuffer, section.getY(), (blockComponentChunk1, commandBuffer1, localX, localY, localZ, blockId) -> {
Ref<ChunkStore> blockRef = blockComponentChunk.getEntityReference(ChunkUtil.indexBlockInColumn(localX, localY, localZ));
if (blockRef == null) {
return BlockTickStrategy.IGNORED;
} else {
ExampleBlock exampleBlock = (ExampleBlock) commandBuffer1.getComponent(blockRef, ExampleBlock.getComponentType());
if (exampleBlock != null) {
WorldChunk worldChunk = (WorldChunk) commandBuffer.getComponent(section.getChunkColumnReference(), WorldChunk.getComponentType());
World world = worldChunk.getWorld();

int globalX = localX + (worldChunk.getX() * 32);
int globalZ = localZ + (worldChunk.getZ() * 32);

// We need to execute setBlock on the world thread as you cannot call store functions from a system
// This is because of the architecture of the server, depending on your needs you can also use the CommandBuffer
world.execute(() -> {
world.setBlock(globalX + 1, localY, globalZ, "Rock_Ice");
});

return BlockTickStrategy.CONTINUE;

} else {
return BlockTickStrategy.IGNORED;
}
}
});
}
}

@Nullable
public Query getQuery() {
return Query.and(BlockSection.getComponentType(), ChunkSection.getComponentType());;
}
}
```

ExampleSystem is an EntityTickingSystem that runs every tick to execute the logic for all ticking blocks with the ExampleBlock component.

**Key Points:**

- **Get the block component**

```java
ExampleBlock exampleBlock = (ExampleBlock) commandBuffer.getComponent(blockRef, ExampleBlock.getComponentType());
```

- **Convert local chunk coordinates to world coordinates**

```java
int globalX = localX + (worldChunk.getX() * 32);
int globalZ = localZ + (worldChunk.getZ() * 32);
```

- **Run the block logic**

```java
exampleBlock.runBlockAction(globalX, localY, globalZ, worldChunk.getWorld());
```

- **Keep the block ticking in the next tick**

```java
return BlockTickStrategy.CONTINUE;
```

---

### 4. ExamplePlugin - registers components and systems

```java
public class ExamplePlugin extends JavaPlugin {
protected static ExamplePlugin instance;
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
private ComponentType exampleBlockComponentType;

public static ExamplePlugin get() {
return instance;
}

public ExamplePlugin(@Nonnull JavaPluginInit init) {
super(init);
LOGGER.atInfo().log("Hello from " + this.getName() + " version " + this.getManifest().getVersion().toString());
}

@Override
protected void setup() {
instance = this;
LOGGER.atInfo().log("Setting up plugin " + this.getName());
this.exampleBlockComponentType = this.getChunkStoreRegistry().registerComponent(ExampleBlock.class, "ExampleBlock", ExampleBlock.CODEC);
}

@Override
protected void start() {
this.getChunkStoreRegistry().registerSystem(new ExampleSystem());
this.getChunkStoreRegistry().registerSystem(new ExampleInitializer());
}

public ComponentType getExampleBlockComponentType() {
return this.exampleBlockComponentType;
}
}
```

---

### 5. Configure In-Game

![block-component-1](/assets/guides/block-component-1.png)

![block-component-2](/assets/guides/block-component-2.png)

With this logic, the block will now continuously place an Ice Block at the coordinates x + 1 relative to its current position every time it ticks.

---

## Common Issues

### NullPointerException on Startup

**Error message:**

```java
java.lang.NullPointerException: Cannot invoke "com.hypixel.hytale.component.query.Query.validateRegistry(com.hypixel.hytale.component.ComponentRegistry)" because "query" is null
```

**Cause:**
This error occurs when your module is loaded **before** the required Hytale modules.

**Fix:**
Add `EntityModule` and `BlockModule` as dependencies in your `manifest.json` to ensure proper load order.

```json
"Dependencies": {
"Hytale:EntityModule": "*",
"Hytale:BlockModule": "*"
}
```
13 changes: 7 additions & 6 deletions content/docs/af-ZA/guides/ecs/hytale-ecs-theory.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ for now this is the basic understanding for the `ChunkStore`. You can use it to

## Holder

A Holder is essentially a blueprint for an entity. Before an entity exists in the Store (and thus in the world), is exists as a `Holder`. It collects and holds all the necessary components (data). You can compare it analogous to shopping cart. You grab all components you need and once you have everything, check out at the store which will take your cart and create a valid entity ID and hand you back a receipt (a Ref).
A Holder is essentially a blueprint for an entity. Before an entity exists in the Store (and thus in the world), it exists as a `Holder`. It collects and holds all the necessary components (data). You can compare it analogous to shopping cart. You grab all components you need and once you have everything, check out at the store which will take your cart and create a valid entity ID and hand you back a receipt (a Ref).

Let's take a look at an example: initializing players. In `Universe`, the `addPlayer` method demonstrates it perfectly.
When a player connects, we don't immediately throw them into the ECS. We first construct their data in a Holder.
Expand Down Expand Up @@ -167,7 +167,7 @@ MyComponent comp = commandBuffer.getComponent(ref, componentType);

Codecs handle serialization and deserialization of components. Hytale uses them to save and load entity data to and from disk as well as sending component data over the network. When creating a custom component, you must also create a corresponding Codec.

There are mutliple Codec types already implemented in the default Codec Interface:
There are multiple Codec types already implemented in the default Codec Interface:

- Codec.STRING
- Codec.BOOLEAN
Expand Down Expand Up @@ -215,7 +215,7 @@ This might seem tedious at first but it ensures that your component can be corre
KeyedCodec<Integer> exampleInt = new KeyedCodec<Integer>("ExampleIntIdForCodec", Codec.INTEGER);
```

- **Setter Function**: This is a lambda function that takes an instance of your component and returns the value of the field you want to serialize. For example, if you have a field called `myCustomField` in your custom component, your getter function would look like this:
- **Setter Function**: This is a lambda function that takes an instance of your component + the value to set and sets the field value on the component. For example, if you have a field called `myCustomField` in your custom component, your setter function would look like this:
```java
(data, value) -> data.myCustomField = value
```
Expand Down Expand Up @@ -276,7 +276,7 @@ public class PoisonComponent implements Component<EntityStore> {

You can also add validators to your Codec fields to ensure that the data being serialized/deserialized meets certain criteria. For example, if you want to ensure that the `damagePerTick` field is always non-negative or the poison name is never null, you can add a validator like this:

````java
```java
.append(
new KeyedCodec<Float>("DamagePerTick", Codec.FLOAT),
(data, value) -> data.damagePerTick = value,
Expand All @@ -292,14 +292,15 @@ You can also add validators to your Codec fields to ensure that the data being s
)
.addValidator(Validators.nonNull())
.add()
```

### Complex Codec Examples

If you want to have a map inside your component, you can use the MapCodec class to build a KeyedCoded for it. Here's an example of how to create a Codec for a Map field:
If you want to have a map inside your component, you can use the MapCodec class to build a KeyedCodec for it. Here's an example of how to create a Codec for a Map field:

```java
var damagePerTick = new KeyedCodec<>("DamagePerTick", new MapCodec<>(Codec.FLOAT, HashMap<String, Float>::new));
````
```

<Callout type="info">
This also works with custom Objects as long as you have a Codec defined for the Object type.
Expand Down
Loading
Loading