Initial commit
This commit is contained in:
27
.gitignore
vendored
Normal file
27
.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Gradle
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
app/build/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
.settings/
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Hytale
|
||||||
|
*.jar
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
.hytale-downloader-credentials.json
|
||||||
|
|
||||||
|
# Setup
|
||||||
|
.bin/
|
||||||
|
server/
|
||||||
|
src-ref/
|
||||||
99
README.md
Normal file
99
README.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# Example Hytale Plugin
|
||||||
|
|
||||||
|
This is an example Hytale server plugin demonstrating the basic structure and setup.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
example-mod/
|
||||||
|
├── app/
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── main/
|
||||||
|
│ │ │ ├── java/org/example/
|
||||||
|
│ │ │ │ └── ExamplePlugin.java
|
||||||
|
│ │ │ └── resources/
|
||||||
|
│ │ │ └── plugin.json
|
||||||
|
│ │ └── test/
|
||||||
|
│ ├── build.gradle.kts
|
||||||
|
├── gradle/
|
||||||
|
├── server/
|
||||||
|
├── build.gradle.kts
|
||||||
|
├── settings.gradle.kts
|
||||||
|
└── gradlew / gradlew.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### Pre-setup
|
||||||
|
```bash
|
||||||
|
# Download the hytale-downloader & cfr (DO IT ONCE)
|
||||||
|
./setup --download
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
```bash
|
||||||
|
# Download the HytaleServer, finally located here ./server/HytaleServer.jar
|
||||||
|
./setup --setup
|
||||||
|
```
|
||||||
|
|
||||||
|
### Decompile
|
||||||
|
```bash
|
||||||
|
# Decompile ./server/HytaleServer.jar to get the source, finally located here ./src-ref
|
||||||
|
./setup --decompile
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build the plugin
|
||||||
|
./gradlew build
|
||||||
|
|
||||||
|
# The JAR will be in app/build/libs/ExamplePlugin-1.0.0.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Build the plugin with `./gradlew build`
|
||||||
|
2. Copy `app/build/libs/ExamplePlugin-1.0.0.jar` to your Hytale server's `mods/` folder
|
||||||
|
3. Start the server
|
||||||
|
|
||||||
|
## Plugin Structure
|
||||||
|
|
||||||
|
### manifest.json
|
||||||
|
|
||||||
|
The plugin manifest file defines metadata about your plugin:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Group": "org.example",
|
||||||
|
"Name": "ExamplePlugin",
|
||||||
|
"Version": "1.0.0",
|
||||||
|
"Description": "An example Hytale server plugin",
|
||||||
|
"Authors": [
|
||||||
|
{
|
||||||
|
"Name": "Your Name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Main": "org.example.ExamplePlugin",
|
||||||
|
"ServerVersion": "*",
|
||||||
|
"Dependencies": {},
|
||||||
|
"OptionalDependencies": {},
|
||||||
|
"DisabledByDefault": false,
|
||||||
|
"IncludesAssetPack": false,
|
||||||
|
"SubPlugins": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Main Plugin Class
|
||||||
|
|
||||||
|
Your plugin class extends `JavaPlugin` and implements lifecycle methods:
|
||||||
|
|
||||||
|
- `setup()` - Called during server setup, register configs here
|
||||||
|
- `start()` - Called when plugin starts, register commands/events here
|
||||||
|
- `shutdown()` - Called when plugin stops, cleanup resources here
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Java 25 or later
|
||||||
29
app/build.gradle.kts
Normal file
29
app/build.gradle.kts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
plugins {
|
||||||
|
java
|
||||||
|
}
|
||||||
|
|
||||||
|
group = "org.example"
|
||||||
|
version = "1.0.0"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(25)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// Hytale Server API - compile only since it's provided at runtime
|
||||||
|
compileOnly(files("../server/Server/HytaleServer.jar"))
|
||||||
|
|
||||||
|
// Testing
|
||||||
|
testImplementation(libs.junit)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.jar {
|
||||||
|
// Set the archive name
|
||||||
|
archiveBaseName.set("ExamplePlugin")
|
||||||
|
}
|
||||||
33
app/src/main/java/org/example/ExamplePlugin.java
Normal file
33
app/src/main/java/org/example/ExamplePlugin.java
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package org.example;
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.server.core.util.Config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main example plugin.
|
||||||
|
* Individual documentation examples are in org.example.docs package.
|
||||||
|
*/
|
||||||
|
public class ExamplePlugin extends JavaPlugin {
|
||||||
|
private Config<MyConfig> config;
|
||||||
|
|
||||||
|
public ExamplePlugin(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
config = withConfig(MyConfig.CODEC);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
// Register commands from documentation examples
|
||||||
|
getCommandRegistry().registerCommand(new org.example.docs.commands.CreatingCommandsExample.HelloCommand());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
}
|
||||||
|
}
|
||||||
31
app/src/main/java/org/example/MyConfig.java
Normal file
31
app/src/main/java/org/example/MyConfig.java
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package org.example;
|
||||||
|
|
||||||
|
import com.hypixel.hytale.codec.Codec;
|
||||||
|
import com.hypixel.hytale.codec.KeyedCodec;
|
||||||
|
import com.hypixel.hytale.codec.builder.BuilderCodec;
|
||||||
|
|
||||||
|
public class MyConfig {
|
||||||
|
private String serverName;
|
||||||
|
private int maxPlayers = 20; // Default value
|
||||||
|
private boolean debugMode = false;
|
||||||
|
|
||||||
|
public static final BuilderCodec<MyConfig> CODEC = BuilderCodec.builder(MyConfig.class, MyConfig::new)
|
||||||
|
.append(new KeyedCodec<>("ServerName", Codec.STRING),
|
||||||
|
(config, val) -> config.serverName = val,
|
||||||
|
config -> config.serverName)
|
||||||
|
.add()
|
||||||
|
.append(new KeyedCodec<>("MaxPlayers", Codec.INTEGER),
|
||||||
|
(config, val) -> config.maxPlayers = val,
|
||||||
|
config -> config.maxPlayers)
|
||||||
|
.add()
|
||||||
|
.append(new KeyedCodec<>("DebugMode", Codec.BOOLEAN),
|
||||||
|
(config, val) -> config.debugMode = val,
|
||||||
|
config -> config.debugMode)
|
||||||
|
.add()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Getters...
|
||||||
|
public String getServerName() { return serverName; }
|
||||||
|
public int getMaxPlayers() { return maxPlayers; }
|
||||||
|
public boolean isDebugMode() { return debugMode; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,595 @@
|
|||||||
|
package org.example.docs.commands;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/commands/argument-types.en.md
|
||||||
|
// This file tests that ALL argument-types documentation examples compile correctly
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.command.system.AbstractCommand;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.CommandContext;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.universe.Universe;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.item.config.Item;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
import com.hypixel.hytale.protocol.GameMode;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector3f;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector3i;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector3d;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeDoublePosition;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeIntPosition;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.types.Coord;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests argument-types.en.md documentation
|
||||||
|
*
|
||||||
|
* IMPORTANT CORRECTIONS FOUND:
|
||||||
|
* - Documentation uses Component.text() which doesn't exist - use Message.raw() instead
|
||||||
|
* - Documentation uses playerRef.getPlayer() which doesn't exist - use getReference() for ECS
|
||||||
|
* - Documentation uses playerRef.getWorld() which doesn't exist - use getWorldUuid()
|
||||||
|
* - Documentation should use context.sendMessage(Message) instead of context.sender().sendMessage()
|
||||||
|
*/
|
||||||
|
public class ArgumentTypesExample {
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Primitive Types" section - Examples tab
|
||||||
|
// ============================================
|
||||||
|
public static class MathCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<Integer> countArg;
|
||||||
|
private final RequiredArg<Double> multiplierArg;
|
||||||
|
private final RequiredArg<Boolean> verboseArg;
|
||||||
|
private final OptionalArg<String> labelArg;
|
||||||
|
|
||||||
|
public MathCommand() {
|
||||||
|
super("math", "Perform mathematical operations");
|
||||||
|
countArg = withRequiredArg("count", "Number of iterations", ArgTypes.INTEGER);
|
||||||
|
multiplierArg = withRequiredArg("multiplier", "Multiplication factor", ArgTypes.DOUBLE);
|
||||||
|
verboseArg = withRequiredArg("verbose", "Show detailed output", ArgTypes.BOOLEAN);
|
||||||
|
labelArg = withOptionalArg("label", "Optional label", ArgTypes.STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
int count = context.get(countArg);
|
||||||
|
double mult = context.get(multiplierArg);
|
||||||
|
boolean verbose = context.get(verboseArg);
|
||||||
|
String label = context.get(labelArg); // null if not provided
|
||||||
|
|
||||||
|
double result = count * mult;
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
String output = (label != null ? label + ": " : "") +
|
||||||
|
count + " × " + mult + " = " + result;
|
||||||
|
// CORRECTION: Use context.sendMessage(Message.raw()) instead of context.sender().sendMessage()
|
||||||
|
context.sendMessage(Message.raw(output));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Player and Entity Types" - PlayerRef tab
|
||||||
|
// CORRECTION: playerRef.getPlayer() doesn't exist
|
||||||
|
// ============================================
|
||||||
|
public static class TeleportCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<PlayerRef> targetArg;
|
||||||
|
private final OptionalArg<PlayerRef> destinationArg;
|
||||||
|
|
||||||
|
public TeleportCommand() {
|
||||||
|
super("tp", "Teleport players");
|
||||||
|
targetArg = withRequiredArg("target", "Player to teleport", ArgTypes.PLAYER_REF);
|
||||||
|
destinationArg = withOptionalArg("destination", "Destination player", ArgTypes.PLAYER_REF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
PlayerRef targetRef = context.get(targetArg);
|
||||||
|
|
||||||
|
// CORRECTION: PlayerRef doesn't have getPlayer() method
|
||||||
|
// Instead use getReference() to get Ref<EntityStore>
|
||||||
|
// For now, we use username which IS available
|
||||||
|
String targetUsername = targetRef.getUsername();
|
||||||
|
|
||||||
|
if (context.provided(destinationArg)) {
|
||||||
|
PlayerRef destRef = context.get(destinationArg);
|
||||||
|
String destUsername = destRef.getUsername();
|
||||||
|
context.sendMessage(Message.raw("Would teleport " + targetUsername + " to " + destUsername));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Player and Entity Types" - Entity tab
|
||||||
|
// ============================================
|
||||||
|
public static class EntityInfoCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<UUID> entityArg;
|
||||||
|
|
||||||
|
public EntityInfoCommand() {
|
||||||
|
super("entityinfo", "Get entity information");
|
||||||
|
entityArg = withRequiredArg("entity", "Entity UUID", ArgTypes.UUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
UUID entityId = context.get(entityArg);
|
||||||
|
|
||||||
|
// Find entity in worlds
|
||||||
|
for (World world : Universe.get().getWorlds().values()) {
|
||||||
|
// Search for entity by UUID
|
||||||
|
// Entity lookup depends on world API
|
||||||
|
}
|
||||||
|
|
||||||
|
context.sendMessage(Message.raw("Entity lookup complete"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Player and Entity Types" - UUID tab
|
||||||
|
// ============================================
|
||||||
|
public static class UuidCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<UUID> uuidArg;
|
||||||
|
|
||||||
|
public UuidCommand() {
|
||||||
|
super("uuid", "Parse and display UUID");
|
||||||
|
uuidArg = withRequiredArg("uuid", "UUID to parse", ArgTypes.UUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
UUID uuid = context.get(uuidArg);
|
||||||
|
context.sendMessage(Message.raw(
|
||||||
|
"UUID: " + uuid.toString() + "\nVersion: " + uuid.version()
|
||||||
|
));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "World Types" section
|
||||||
|
// CORRECTION: Uses Component.text() which doesn't exist
|
||||||
|
// ============================================
|
||||||
|
public static class WorldTeleportCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<World> worldArg;
|
||||||
|
private final OptionalArg<RelativeDoublePosition> positionArg;
|
||||||
|
|
||||||
|
public WorldTeleportCommand() {
|
||||||
|
super("wtp", "Teleport to another world");
|
||||||
|
worldArg = withRequiredArg("world", "Destination world", ArgTypes.WORLD);
|
||||||
|
positionArg = withOptionalArg("position", "Spawn position", ArgTypes.RELATIVE_POSITION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
// CORRECTION: Use context.sender() instanceof Player, then cast
|
||||||
|
if (!(context.sender() instanceof Player player)) {
|
||||||
|
// CORRECTION: Use Message.raw() not Component.text()
|
||||||
|
context.sendMessage(Message.raw("Only players can use this command!"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
World targetWorld = context.get(worldArg);
|
||||||
|
|
||||||
|
// Note: World.getSpawnPosition() and player.teleport() would need verification
|
||||||
|
// For now just show the concept works
|
||||||
|
context.sendMessage(Message.raw("Would teleport to world: " + targetWorld.getName()));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Asset Types" - Items tab
|
||||||
|
// CORRECTION: Uses Component.text() and ItemStack.of() which may not exist
|
||||||
|
// ============================================
|
||||||
|
public static class GiveCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<Item> itemArg;
|
||||||
|
private final OptionalArg<Integer> amountArg;
|
||||||
|
private final OptionalArg<PlayerRef> targetArg;
|
||||||
|
|
||||||
|
public GiveCommand() {
|
||||||
|
super("give", "Give items to players");
|
||||||
|
itemArg = withRequiredArg("item", "Item to give", ArgTypes.ITEM_ASSET);
|
||||||
|
amountArg = withOptionalArg("amount", "Stack size", ArgTypes.INTEGER);
|
||||||
|
targetArg = withOptionalArg("player", "Target player", ArgTypes.PLAYER_REF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
Item item = context.get(itemArg);
|
||||||
|
int amount = context.provided(amountArg) ? context.get(amountArg) : 1;
|
||||||
|
|
||||||
|
// CORRECTION: playerRef.getPlayer() doesn't exist
|
||||||
|
// We can only get username from PlayerRef
|
||||||
|
String targetName;
|
||||||
|
if (context.provided(targetArg)) {
|
||||||
|
targetName = context.get(targetArg).getUsername();
|
||||||
|
} else if (context.sender() instanceof Player p) {
|
||||||
|
targetName = p.getDisplayName();
|
||||||
|
} else {
|
||||||
|
context.sendMessage(Message.raw("Specify a player!"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECTION: Use Message.raw() instead of Component.text()
|
||||||
|
context.sendMessage(
|
||||||
|
Message.raw("Gave " + amount + "x " + item.getId() + " to " + targetName)
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Asset Types" - Blocks tab
|
||||||
|
// ============================================
|
||||||
|
public static class SetBlockCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<BlockType> blockArg;
|
||||||
|
private final RequiredArg<RelativeIntPosition> positionArg;
|
||||||
|
|
||||||
|
public SetBlockCommand() {
|
||||||
|
super("setblock", "Set a block at position");
|
||||||
|
blockArg = withRequiredArg("block", "Block type", ArgTypes.BLOCK_TYPE_ASSET);
|
||||||
|
positionArg = withRequiredArg("position", "Target position", ArgTypes.RELATIVE_BLOCK_POSITION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
if (!(context.sender() instanceof Player player)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockType blockType = context.get(blockArg);
|
||||||
|
// Note: player.getBlockPosition() would need verification
|
||||||
|
// RelativeIntPosition.resolve() takes a Vector3i
|
||||||
|
|
||||||
|
context.sendMessage(
|
||||||
|
Message.raw("Set block to " + blockType.getId())
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Asset Types" - Effects tab
|
||||||
|
// ============================================
|
||||||
|
public static class EffectCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<EntityEffect> effectArg;
|
||||||
|
private final OptionalArg<Integer> durationArg;
|
||||||
|
private final OptionalArg<Integer> amplifierArg;
|
||||||
|
|
||||||
|
public EffectCommand() {
|
||||||
|
super("effect", "Apply effect to player");
|
||||||
|
effectArg = withRequiredArg("effect", "Effect to apply", ArgTypes.EFFECT_ASSET);
|
||||||
|
durationArg = withOptionalArg("duration", "Duration in ticks", ArgTypes.INTEGER);
|
||||||
|
amplifierArg = withOptionalArg("amplifier", "Effect level", ArgTypes.INTEGER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
if (!(context.sender() instanceof Player player)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityEffect effect = context.get(effectArg);
|
||||||
|
int duration = context.provided(durationArg) ? context.get(durationArg) : 600;
|
||||||
|
int amplifier = context.provided(amplifierArg) ? context.get(amplifierArg) : 0;
|
||||||
|
|
||||||
|
// Note: player.addEffect() would need verification
|
||||||
|
context.sendMessage(Message.raw("Applied effect: " + effect.getId()));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Asset Types" - Particles tab
|
||||||
|
// ============================================
|
||||||
|
public static class ParticleCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<ParticleSystem> particleArg;
|
||||||
|
private final OptionalArg<RelativeDoublePosition> positionArg;
|
||||||
|
|
||||||
|
public ParticleCommand() {
|
||||||
|
super("particle", "Spawn particle effect");
|
||||||
|
particleArg = withRequiredArg("particle", "Particle system", ArgTypes.PARTICLE_SYSTEM);
|
||||||
|
positionArg = withOptionalArg("position", "Spawn position", ArgTypes.RELATIVE_POSITION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
if (!(context.sender() instanceof Player player)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParticleSystem particle = context.get(particleArg);
|
||||||
|
// Note: Would need to verify World.spawnParticle() exists
|
||||||
|
|
||||||
|
context.sendMessage(Message.raw("Spawned particle: " + particle.getId()));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Position Types" - 3D Positions tab
|
||||||
|
// ============================================
|
||||||
|
public static class TeleportPosCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<RelativeDoublePosition> posArg;
|
||||||
|
|
||||||
|
public TeleportPosCommand() {
|
||||||
|
super("tppos", "Teleport to coordinates");
|
||||||
|
posArg = withRequiredArg("position", "Target position", ArgTypes.RELATIVE_POSITION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
if (!(context.sender() instanceof Player player)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
RelativeDoublePosition relPos = context.get(posArg);
|
||||||
|
|
||||||
|
// Note: Need to verify player.getPosition() and relPos.resolve() APIs
|
||||||
|
// player.teleport(absolutePos) would also need verification
|
||||||
|
|
||||||
|
context.sendMessage(Message.raw("Teleported to position"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Position Types" - Block Positions tab
|
||||||
|
// ============================================
|
||||||
|
public static class FillCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<RelativeIntPosition> pos1Arg;
|
||||||
|
private final RequiredArg<RelativeIntPosition> pos2Arg;
|
||||||
|
private final RequiredArg<BlockType> blockArg;
|
||||||
|
|
||||||
|
public FillCommand() {
|
||||||
|
super("fill", "Fill region with blocks");
|
||||||
|
pos1Arg = withRequiredArg("from", "First corner", ArgTypes.RELATIVE_BLOCK_POSITION);
|
||||||
|
pos2Arg = withRequiredArg("to", "Second corner", ArgTypes.RELATIVE_BLOCK_POSITION);
|
||||||
|
blockArg = withRequiredArg("block", "Block type", ArgTypes.BLOCK_TYPE_ASSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
if (!(context.sender() instanceof Player player)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RelativeIntPosition values
|
||||||
|
RelativeIntPosition from = context.get(pos1Arg);
|
||||||
|
RelativeIntPosition to = context.get(pos2Arg);
|
||||||
|
BlockType block = context.get(blockArg);
|
||||||
|
|
||||||
|
// Note: Need to verify these APIs exist
|
||||||
|
// player.getBlockPosition() returns Vector3i?
|
||||||
|
// world.setBlock(Vector3i, BlockType)?
|
||||||
|
|
||||||
|
context.sendMessage(Message.raw("Filled region with " + block.getId()));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Position Types" - Single Coords tab
|
||||||
|
// ============================================
|
||||||
|
public static class SetYCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<Coord> yArg;
|
||||||
|
|
||||||
|
public SetYCommand() {
|
||||||
|
super("sety", "Teleport to Y level");
|
||||||
|
yArg = withRequiredArg("y", "Y coordinate", ArgTypes.RELATIVE_DOUBLE_COORD);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
if (!(context.sender() instanceof Player player)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Coord relY = context.get(yArg);
|
||||||
|
// Note: relY.resolve(double) API needs verification
|
||||||
|
|
||||||
|
context.sendMessage(Message.raw("Set Y level"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Vector Types" - Examples tab
|
||||||
|
// ============================================
|
||||||
|
public static class LookCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<Vector3f> rotationArg;
|
||||||
|
|
||||||
|
public LookCommand() {
|
||||||
|
super("look", "Set player rotation");
|
||||||
|
rotationArg = withRequiredArg("rotation", "Pitch Yaw Roll", ArgTypes.ROTATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
if (!(context.sender() instanceof Player player)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f rotation = context.get(rotationArg);
|
||||||
|
// rotation.x = pitch, rotation.y = yaw, rotation.z = roll
|
||||||
|
// Note: player.setRotation() API needs verification
|
||||||
|
|
||||||
|
// CORRECTION: Vector3f uses public fields, not methods
|
||||||
|
context.sendMessage(Message.raw(
|
||||||
|
"Rotation set to Pitch: " + rotation.x +
|
||||||
|
", Yaw: " + rotation.y +
|
||||||
|
", Roll: " + rotation.z
|
||||||
|
));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Special Types" - Color tab
|
||||||
|
// ============================================
|
||||||
|
public static class ColorCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<Integer> colorArg;
|
||||||
|
|
||||||
|
public ColorCommand() {
|
||||||
|
super("color", "Set display color");
|
||||||
|
colorArg = withRequiredArg("color", "Color value", ArgTypes.COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
int color = context.get(colorArg);
|
||||||
|
|
||||||
|
int alpha = (color >> 24) & 0xFF;
|
||||||
|
int red = (color >> 16) & 0xFF;
|
||||||
|
int green = (color >> 8) & 0xFF;
|
||||||
|
int blue = color & 0xFF;
|
||||||
|
|
||||||
|
context.sendMessage(
|
||||||
|
Message.raw("Color: ARGB(" + alpha + ", " + red + ", " + green + ", " + blue + ")")
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Special Types" - Game Mode tab
|
||||||
|
// ============================================
|
||||||
|
public static class GameModeCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<GameMode> modeArg;
|
||||||
|
private final OptionalArg<PlayerRef> playerArg;
|
||||||
|
|
||||||
|
public GameModeCommand() {
|
||||||
|
super("gamemode", "Change game mode");
|
||||||
|
modeArg = withRequiredArg("mode", "Game mode", ArgTypes.GAME_MODE);
|
||||||
|
playerArg = withOptionalArg("player", "Target player", ArgTypes.PLAYER_REF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
GameMode mode = context.get(modeArg);
|
||||||
|
|
||||||
|
String targetName;
|
||||||
|
if (context.provided(playerArg)) {
|
||||||
|
PlayerRef ref = context.get(playerArg);
|
||||||
|
targetName = ref.getUsername();
|
||||||
|
} else if (context.sender() instanceof Player p) {
|
||||||
|
targetName = p.getDisplayName();
|
||||||
|
} else {
|
||||||
|
context.sendMessage(Message.raw("Specify a player!"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set game mode using ChangeGameModeEvent or component
|
||||||
|
context.sendMessage(Message.raw("Game mode changed to " + mode.name() + " for " + targetName));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Special Types" - Tick Rate tab
|
||||||
|
// ============================================
|
||||||
|
public static class TickRateCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<Integer> rateArg;
|
||||||
|
|
||||||
|
public TickRateCommand() {
|
||||||
|
super("tickrate", "Set world tick rate");
|
||||||
|
rateArg = withRequiredArg("rate", "Tick rate", ArgTypes.TICK_RATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
if (!(context.sender() instanceof Player player)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tps = context.get(rateArg); // Returns Integer (TPS)
|
||||||
|
// Set tick rate on world...
|
||||||
|
|
||||||
|
context.sendMessage(Message.raw("Tick rate set to " + tps + " TPS"));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Custom Argument Types" - Enum Arguments section
|
||||||
|
// ============================================
|
||||||
|
public enum Difficulty {
|
||||||
|
EASY, NORMAL, HARD, EXTREME
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DifficultyCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<Difficulty> difficultyArg;
|
||||||
|
|
||||||
|
public DifficultyCommand() {
|
||||||
|
super("difficulty", "Set server difficulty");
|
||||||
|
difficultyArg = withRequiredArg(
|
||||||
|
"level",
|
||||||
|
"Difficulty level",
|
||||||
|
ArgTypes.forEnum("Difficulty", Difficulty.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
Difficulty level = context.get(difficultyArg);
|
||||||
|
// Apply difficulty setting
|
||||||
|
context.sendMessage(
|
||||||
|
Message.raw("Difficulty set to " + level.name())
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Custom Argument Types" - List Arguments section
|
||||||
|
// ============================================
|
||||||
|
public static class MultiTeleportCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<List<PlayerRef>> playersArg;
|
||||||
|
private final RequiredArg<RelativeDoublePosition> positionArg;
|
||||||
|
|
||||||
|
public MultiTeleportCommand() {
|
||||||
|
super("multitp", "Teleport multiple players");
|
||||||
|
playersArg = withListRequiredArg("players", "Target players", ArgTypes.PLAYER_REF);
|
||||||
|
positionArg = withRequiredArg("position", "Destination", ArgTypes.RELATIVE_POSITION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
List<PlayerRef> playerRefs = context.get(playersArg);
|
||||||
|
RelativeDoublePosition relPos = context.get(positionArg);
|
||||||
|
|
||||||
|
// Note: playerRef.getPlayer() doesn't exist, use getReference()
|
||||||
|
for (PlayerRef ref : playerRefs) {
|
||||||
|
// Would need to use ECS to teleport
|
||||||
|
String username = ref.getUsername();
|
||||||
|
}
|
||||||
|
|
||||||
|
context.sendMessage(
|
||||||
|
Message.raw("Teleported " + playerRefs.size() + " players")
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Argument Validation section - .validate() method usage would need verification
|
||||||
|
}
|
||||||
@@ -0,0 +1,399 @@
|
|||||||
|
package org.example.docs.commands;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/commands/command-context.en.md
|
||||||
|
// This file tests that ALL command-context documentation examples compile correctly
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.command.system.AbstractCommand;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.CommandContext;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.CommandSender;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.item.config.Item;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests command-context.en.md documentation
|
||||||
|
*
|
||||||
|
* IMPORTANT CORRECTIONS FOUND:
|
||||||
|
* - Line 46: context.sender().sendMessage("text") should use Message.raw()
|
||||||
|
* - Line 189: playerRef.getWorld() doesn't exist - use getWorldUuid()
|
||||||
|
* - Line 201: playerRef.sendMessage() exists, but Message.of() doesn't - use Message.raw()
|
||||||
|
*/
|
||||||
|
public class CommandContextExample {
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Accessing the Context" section
|
||||||
|
// ============================================
|
||||||
|
public static class BasicContextCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
public BasicContextCommand() {
|
||||||
|
super("basiccontext", "Shows basic context usage");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
// Use context here
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Command Sender" section
|
||||||
|
// ============================================
|
||||||
|
public static class SenderCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
public SenderCommand() {
|
||||||
|
super("sender", "Demonstrates sender access");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
CommandSender sender = context.sender();
|
||||||
|
|
||||||
|
// Send messages (must use Message class)
|
||||||
|
sender.sendMessage(Message.raw("Hello!"));
|
||||||
|
sender.sendMessage(Message.translation("my.translation.key"));
|
||||||
|
|
||||||
|
// Check permissions
|
||||||
|
if (sender.hasPermission("myplugin.admin")) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Checking Sender Type" section
|
||||||
|
// CORRECTION: sendMessage needs Message.raw()
|
||||||
|
// ============================================
|
||||||
|
public static class SenderTypeCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
public SenderTypeCommand() {
|
||||||
|
super("sendertype", "Checks sender type");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
if (context.sender() instanceof Player) {
|
||||||
|
Player player = (Player) context.sender();
|
||||||
|
// Player-specific logic
|
||||||
|
} else {
|
||||||
|
// Console or other sender
|
||||||
|
// CORRECTION: Use Message.raw() instead of raw String
|
||||||
|
context.sender().sendMessage(Message.raw("This command requires a player!"));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Getting Arguments" - Required Arguments
|
||||||
|
// ============================================
|
||||||
|
public static class RequiredArgsCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<PlayerRef> playerArg;
|
||||||
|
private final RequiredArg<Integer> countArg;
|
||||||
|
|
||||||
|
public RequiredArgsCommand() {
|
||||||
|
super("requiredargs", "Required arguments example");
|
||||||
|
playerArg = withRequiredArg("player", "Target player", ArgTypes.PLAYER_REF);
|
||||||
|
countArg = withRequiredArg("count", "Count value", ArgTypes.INTEGER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
// In execute():
|
||||||
|
PlayerRef player = context.get(playerArg); // Never null for required args
|
||||||
|
int count = context.get(countArg); // Never null for required args
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Getting Arguments" - Optional Arguments
|
||||||
|
// ============================================
|
||||||
|
public static class OptionalArgsCommand extends AbstractCommand {
|
||||||
|
private final OptionalArg<String> reasonArg;
|
||||||
|
|
||||||
|
public OptionalArgsCommand() {
|
||||||
|
super("optionalargs", "Optional arguments example");
|
||||||
|
reasonArg = withOptionalArg("reason", "Optional reason", ArgTypes.STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
// Get value (may be null if not provided)
|
||||||
|
String reason = context.get(reasonArg);
|
||||||
|
|
||||||
|
// Check if provided before using
|
||||||
|
if (context.provided(reasonArg)) {
|
||||||
|
String providedReason = context.get(reasonArg);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Getting Arguments" - Default Arguments
|
||||||
|
// ============================================
|
||||||
|
public static class DefaultArgsCommand extends AbstractCommand {
|
||||||
|
private final DefaultArg<Integer> countArg; // Default: 1
|
||||||
|
|
||||||
|
public DefaultArgsCommand() {
|
||||||
|
super("defaultargs", "Default arguments example");
|
||||||
|
countArg = withDefaultArg("count", "Number", ArgTypes.INTEGER, 1, "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
// Always returns a value (never null)
|
||||||
|
int count = context.get(countArg); // Returns default if not specified
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Getting Arguments" - Flag Arguments
|
||||||
|
// ============================================
|
||||||
|
public static class FlagArgsCommand extends AbstractCommand {
|
||||||
|
private final FlagArg silentFlag;
|
||||||
|
|
||||||
|
public FlagArgsCommand() {
|
||||||
|
super("flagargs", "Flag arguments example");
|
||||||
|
silentFlag = withFlagArg("silent", "Silent mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
// Check if flag was provided
|
||||||
|
boolean isSilent = context.provided(silentFlag);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Input String" section
|
||||||
|
// ============================================
|
||||||
|
public static class InputStringCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
public InputStringCommand() {
|
||||||
|
super("inputstring", "Shows input string access");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
String input = context.getInputString();
|
||||||
|
// For "/give player123 sword 5" -> "give player123 sword 5"
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "The Command" section
|
||||||
|
// ============================================
|
||||||
|
public static class CommandInfoCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
public CommandInfoCommand() {
|
||||||
|
super("commandinfo", "Shows command info access");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
AbstractCommand command = context.getCalledCommand();
|
||||||
|
String commandName = command.getName();
|
||||||
|
String fullName = command.getFullyQualifiedName(); // e.g., "admin kick"
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "PLAYER_REF Fallback" section
|
||||||
|
// CORRECTION: PlayerRef doesn't have getPlayer() or getWorld()
|
||||||
|
// ============================================
|
||||||
|
public static class PlayerRefFallbackCommand extends AbstractCommand {
|
||||||
|
private final OptionalArg<PlayerRef> targetArg;
|
||||||
|
|
||||||
|
public PlayerRefFallbackCommand() {
|
||||||
|
super("playerreffallback", "PlayerRef fallback example");
|
||||||
|
targetArg = withOptionalArg("target", "Target player", ArgTypes.PLAYER_REF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
PlayerRef target = context.get(targetArg);
|
||||||
|
|
||||||
|
// Manual fallback if null
|
||||||
|
// CORRECTION: Use getPlayerRef() from Player (though deprecated)
|
||||||
|
if (target == null && context.sender() instanceof Player player) {
|
||||||
|
target = player.getPlayerRef(); // Note: getPlayerRef() is deprecated
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "World Argument Fallback" section
|
||||||
|
// ============================================
|
||||||
|
public static class WorldFallbackCommand extends AbstractCommand {
|
||||||
|
private final OptionalArg<World> worldArg;
|
||||||
|
|
||||||
|
public WorldFallbackCommand() {
|
||||||
|
super("worldfallback", "World fallback example");
|
||||||
|
worldArg = withOptionalArg("world", "Target world", ArgTypes.WORLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
World world = context.get(worldArg);
|
||||||
|
|
||||||
|
// Manual fallback if null
|
||||||
|
if (world == null && context.sender() instanceof Player player) {
|
||||||
|
world = player.getWorld(); // Note: Need to verify this exists
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Error Handling" section
|
||||||
|
// CORRECTION: PlayerRef doesn't have getPlayer()
|
||||||
|
// ============================================
|
||||||
|
public static class ErrorHandlingCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<PlayerRef> playerArg;
|
||||||
|
|
||||||
|
public ErrorHandlingCommand() {
|
||||||
|
super("errorhandling", "Error handling example");
|
||||||
|
playerArg = withRequiredArg("player", "Target player", ArgTypes.PLAYER_REF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
PlayerRef playerRef = context.get(playerArg);
|
||||||
|
|
||||||
|
// CORRECTION: PlayerRef doesn't have getPlayer() - use ECS pattern
|
||||||
|
// Instead, we can check if the reference is valid
|
||||||
|
Ref<EntityStore> ref = playerRef.getReference();
|
||||||
|
if (ref == null || !ref.isValid()) {
|
||||||
|
throw new GeneralCommandException(
|
||||||
|
Message.translation("error.player.offline")
|
||||||
|
.param("player", playerRef.getUsername())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue execution
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Asynchronous Commands" section
|
||||||
|
// CORRECTION: playerRef.getWorld() doesn't exist
|
||||||
|
// ============================================
|
||||||
|
public static class AsyncCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
public AsyncCommand() {
|
||||||
|
super("asynccmd", "Async command example");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
if (!(context.sender() instanceof Player player)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerRef playerRef = player.getPlayerRef(); // Note: deprecated
|
||||||
|
// CORRECTION: Use getWorldUuid() instead of getWorld()
|
||||||
|
// Or use player.getWorld() directly
|
||||||
|
World world = player.getWorld();
|
||||||
|
|
||||||
|
return CompletableFuture.runAsync(() -> {
|
||||||
|
// Async operation (e.g., database query)
|
||||||
|
// PlayerData data = database.loadData(playerRef.getUuid());
|
||||||
|
|
||||||
|
// Return to world thread for game logic
|
||||||
|
world.execute(() -> {
|
||||||
|
Ref<EntityStore> ref = playerRef.getReference();
|
||||||
|
if (ref != null) {
|
||||||
|
// applyData(ref, data);
|
||||||
|
playerRef.sendMessage(Message.raw("Data loaded!"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Complete Example" section
|
||||||
|
// CORRECTION: playerRef.getPlayer() doesn't exist
|
||||||
|
// ============================================
|
||||||
|
public static class GiveCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
private final RequiredArg<Item> itemArg;
|
||||||
|
private final OptionalArg<PlayerRef> targetArg;
|
||||||
|
private final DefaultArg<Integer> countArg;
|
||||||
|
private final FlagArg silentFlag;
|
||||||
|
|
||||||
|
public GiveCommand() {
|
||||||
|
super("give", "Give items to a player");
|
||||||
|
|
||||||
|
itemArg = withRequiredArg("item", "Item to give", ArgTypes.ITEM_ASSET);
|
||||||
|
targetArg = withOptionalArg("target", "Target player", ArgTypes.PLAYER_REF);
|
||||||
|
countArg = withDefaultArg("count", "Amount", ArgTypes.INTEGER, 1, "1");
|
||||||
|
silentFlag = withFlagArg("silent", "Don't broadcast");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
// Get arguments
|
||||||
|
Item item = context.get(itemArg);
|
||||||
|
int count = context.get(countArg); // Uses default if not specified
|
||||||
|
boolean silent = context.provided(silentFlag);
|
||||||
|
|
||||||
|
// Get target with fallback to sender
|
||||||
|
PlayerRef targetRef = context.get(targetArg);
|
||||||
|
if (targetRef == null && context.sender() instanceof Player senderPlayer) {
|
||||||
|
targetRef = senderPlayer.getPlayerRef(); // Note: deprecated
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetRef == null) {
|
||||||
|
throw new GeneralCommandException(
|
||||||
|
Message.raw("Must specify a target player!")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate target is online
|
||||||
|
// CORRECTION: PlayerRef doesn't have getPlayer()
|
||||||
|
// Use getReference() to check if valid
|
||||||
|
Ref<EntityStore> ref = targetRef.getReference();
|
||||||
|
if (ref == null || !ref.isValid()) {
|
||||||
|
throw new GeneralCommandException(
|
||||||
|
Message.raw("Player is not online!")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute - give item to player
|
||||||
|
// ... (would use ECS/inventory APIs)
|
||||||
|
|
||||||
|
// Feedback
|
||||||
|
if (!silent) {
|
||||||
|
context.sender().sendMessage(Message.raw(
|
||||||
|
"Gave " + count + "x " + item.getId() + " to " + targetRef.getUsername()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
package org.example.docs.commands;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/commands/creating-commands.en.md
|
||||||
|
// This file tests that ALL commands documentation examples compile correctly
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.command.system.AbstractCommand;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.CommandContext;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes;
|
||||||
|
// For ECS operations: import com.hypixel.hytale.component.Ref;
|
||||||
|
// For ECS operations: import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.item.config.Item;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests creating-commands.en.md documentation
|
||||||
|
*/
|
||||||
|
public class CreatingCommandsExample {
|
||||||
|
|
||||||
|
// From "Basic Command Structure" section
|
||||||
|
public static class HelloCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
public HelloCommand() {
|
||||||
|
super("hello", "Sends a greeting message");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
context.sendMessage(Message.raw("Hello, World!"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// From "Command with Arguments" section
|
||||||
|
public static class GiveCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
private final RequiredArg<PlayerRef> targetArg;
|
||||||
|
private final RequiredArg<Item> itemArg;
|
||||||
|
|
||||||
|
public GiveCommand() {
|
||||||
|
super("give", "Give an item to a player");
|
||||||
|
|
||||||
|
targetArg = withRequiredArg("player", "Target player", ArgTypes.PLAYER_REF);
|
||||||
|
itemArg = withRequiredArg("item", "Item to give", ArgTypes.ITEM_ASSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
PlayerRef targetRef = context.get(targetArg);
|
||||||
|
Item item = context.get(itemArg);
|
||||||
|
|
||||||
|
// PlayerRef provides direct access to player info
|
||||||
|
String username = targetRef.getUsername();
|
||||||
|
|
||||||
|
// For ECS operations, use getReference() to access the EntityStore
|
||||||
|
// Ref<EntityStore> entityRef = targetRef.getReference();
|
||||||
|
|
||||||
|
// Give item to player...
|
||||||
|
context.sendMessage(Message.raw("Gave item to " + username));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// From "Adding Aliases" section
|
||||||
|
public static class TeleportCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
public TeleportCommand() {
|
||||||
|
super("teleport", "Teleport to a location");
|
||||||
|
addAliases("tp", "warp");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// From "Optional Arguments" section
|
||||||
|
public static class GiveWithOptionalCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
private final RequiredArg<PlayerRef> targetArg;
|
||||||
|
private final RequiredArg<Item> itemArg;
|
||||||
|
private final OptionalArg<Integer> countArg;
|
||||||
|
|
||||||
|
public GiveWithOptionalCommand() {
|
||||||
|
super("giveopt", "Give items to a player");
|
||||||
|
|
||||||
|
targetArg = withRequiredArg("player", "Target player", ArgTypes.PLAYER_REF);
|
||||||
|
itemArg = withRequiredArg("item", "Item to give", ArgTypes.ITEM_ASSET);
|
||||||
|
countArg = withOptionalArg("count", "Number of items", ArgTypes.INTEGER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
PlayerRef targetRef = context.get(targetArg);
|
||||||
|
Item item = context.get(itemArg);
|
||||||
|
Integer count = context.get(countArg); // May be null
|
||||||
|
|
||||||
|
int amount = count != null ? count : 1;
|
||||||
|
// Give items to player...
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// From "Default Arguments" section
|
||||||
|
public static class GiveWithDefaultCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
private final DefaultArg<Integer> countArg;
|
||||||
|
|
||||||
|
public GiveWithDefaultCommand() {
|
||||||
|
super("givedef", "Give items to a player");
|
||||||
|
|
||||||
|
countArg = withDefaultArg("count", "Number of items",
|
||||||
|
ArgTypes.INTEGER, 1, "defaults to 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
int count = context.get(countArg); // Never null, uses default
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// From "Flag Arguments" section
|
||||||
|
public static class BroadcastCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
private final FlagArg silentFlag;
|
||||||
|
|
||||||
|
public BroadcastCommand() {
|
||||||
|
super("broadcast", "Send a message to all players");
|
||||||
|
|
||||||
|
silentFlag = withFlagArg("silent", "Don't show sender name");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
boolean silent = context.provided(silentFlag);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// From "Requiring Confirmation" section
|
||||||
|
public static class ResetCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
public ResetCommand() {
|
||||||
|
super("reset", "Reset all player data", true); // requiresConfirmation = true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
// This only runs if --confirm was provided
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,331 @@
|
|||||||
|
package org.example.docs.commands;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/commands/subcommands.en.md
|
||||||
|
// This file tests that ALL subcommands documentation examples compile correctly
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.command.system.AbstractCommand;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.CommandContext;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests subcommands.en.md documentation
|
||||||
|
*
|
||||||
|
* IMPORTANT CORRECTIONS FOUND:
|
||||||
|
* - playerRef.getPlayer() doesn't exist - PlayerRef only provides username, UUID, and getReference()
|
||||||
|
* - Player.kick(String) may not exist with that signature - needs verification
|
||||||
|
*/
|
||||||
|
public class SubcommandsExample {
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Creating Subcommands" - Basic Structure
|
||||||
|
// ============================================
|
||||||
|
public static class AdminCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
public AdminCommand() {
|
||||||
|
super("admin", "Administration commands");
|
||||||
|
|
||||||
|
// Add subcommands
|
||||||
|
addSubCommand(new KickSubCommand());
|
||||||
|
addSubCommand(new BanSubCommand());
|
||||||
|
addSubCommand(new MuteSubCommand());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
// This runs when no subcommand is specified
|
||||||
|
context.sender().sendMessage(Message.raw("Usage: /admin <kick|ban|mute>"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Subcommand Implementation"
|
||||||
|
// CORRECTION: playerRef.getPlayer() doesn't exist
|
||||||
|
// ============================================
|
||||||
|
public static class KickSubCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
private final RequiredArg<PlayerRef> playerArg;
|
||||||
|
private final OptionalArg<String> reasonArg;
|
||||||
|
|
||||||
|
public KickSubCommand() {
|
||||||
|
super("kick", "Kick a player from the server");
|
||||||
|
|
||||||
|
playerArg = withRequiredArg("player", "Player to kick", ArgTypes.PLAYER_REF);
|
||||||
|
reasonArg = withOptionalArg("reason", "Kick reason", ArgTypes.STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
PlayerRef target = context.get(playerArg);
|
||||||
|
String reason = context.get(reasonArg);
|
||||||
|
|
||||||
|
// CORRECTION: PlayerRef doesn't have getPlayer() method
|
||||||
|
// We can only verify the player is online via getReference()
|
||||||
|
Ref<EntityStore> ref = target.getReference();
|
||||||
|
if (ref != null && ref.isValid()) {
|
||||||
|
// To kick, would need to access the PacketHandler or use events
|
||||||
|
// target.getPacketHandler().disconnect(reason);
|
||||||
|
String kickReason = reason != null ? reason : "Kicked by admin";
|
||||||
|
context.sender().sendMessage(Message.raw("Kicked " + target.getUsername() + ": " + kickReason));
|
||||||
|
} else {
|
||||||
|
context.sender().sendMessage(Message.raw("Player " + target.getUsername() + " is not online"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder subcommands for AdminCommand
|
||||||
|
public static class BanSubCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<PlayerRef> playerArg;
|
||||||
|
|
||||||
|
public BanSubCommand() {
|
||||||
|
super("ban", "Ban a player from the server");
|
||||||
|
playerArg = withRequiredArg("player", "Player to ban", ArgTypes.PLAYER_REF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
PlayerRef target = context.get(playerArg);
|
||||||
|
context.sender().sendMessage(Message.raw("Banned " + target.getUsername()));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MuteSubCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<PlayerRef> playerArg;
|
||||||
|
|
||||||
|
public MuteSubCommand() {
|
||||||
|
super("mute", "Mute a player");
|
||||||
|
playerArg = withRequiredArg("player", "Player to mute", ArgTypes.PLAYER_REF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
PlayerRef target = context.get(playerArg);
|
||||||
|
context.sender().sendMessage(Message.raw("Muted " + target.getUsername()));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Command Collections" section
|
||||||
|
// ============================================
|
||||||
|
public static class ManageCommand extends AbstractCommandCollection {
|
||||||
|
|
||||||
|
public ManageCommand() {
|
||||||
|
super("manage", "Management commands");
|
||||||
|
|
||||||
|
addSubCommand(new ManageUsersCommand());
|
||||||
|
addSubCommand(new ManageWorldsCommand());
|
||||||
|
addSubCommand(new ManagePluginsCommand());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Nested Subcommands" section
|
||||||
|
// ============================================
|
||||||
|
public static class ManageUsersCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
public ManageUsersCommand() {
|
||||||
|
super("users", "User management");
|
||||||
|
|
||||||
|
addSubCommand(new ListUsersCommand()); // /manage users list
|
||||||
|
addSubCommand(new AddUserCommand()); // /manage users add
|
||||||
|
addSubCommand(new RemoveUserCommand()); // /manage users remove
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
// Show usage for /manage users
|
||||||
|
context.sender().sendMessage(Message.raw("Usage: /manage users <list|add|remove>"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nested subcommand stubs
|
||||||
|
public static class ListUsersCommand extends AbstractCommand {
|
||||||
|
public ListUsersCommand() {
|
||||||
|
super("list", "List all users");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AddUserCommand extends AbstractCommand {
|
||||||
|
public AddUserCommand() {
|
||||||
|
super("add", "Add a user");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RemoveUserCommand extends AbstractCommand {
|
||||||
|
public RemoveUserCommand() {
|
||||||
|
super("remove", "Remove a user");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder manage subcommands
|
||||||
|
public static class ManageWorldsCommand extends AbstractCommand {
|
||||||
|
public ManageWorldsCommand() {
|
||||||
|
super("worlds", "World management");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ManagePluginsCommand extends AbstractCommand {
|
||||||
|
public ManagePluginsCommand() {
|
||||||
|
super("plugins", "Plugin management");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Subcommand Aliases" section
|
||||||
|
// ============================================
|
||||||
|
public static class TeleportCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
public TeleportCommand() {
|
||||||
|
super("teleport", "Teleport commands");
|
||||||
|
addAliases("tp");
|
||||||
|
|
||||||
|
addSubCommand(new TeleportHereCommand());
|
||||||
|
addSubCommand(new TeleportAllCommand());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TeleportHereCommand extends AbstractCommand {
|
||||||
|
private final RequiredArg<PlayerRef> playerArg;
|
||||||
|
|
||||||
|
public TeleportHereCommand() {
|
||||||
|
super("here", "Teleport player to you");
|
||||||
|
addAliases("h", "tome");
|
||||||
|
playerArg = withRequiredArg("player", "Player to teleport", ArgTypes.PLAYER_REF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
PlayerRef target = context.get(playerArg);
|
||||||
|
context.sender().sendMessage(Message.raw("Teleporting " + target.getUsername() + " to you"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TeleportAllCommand extends AbstractCommand {
|
||||||
|
public TeleportAllCommand() {
|
||||||
|
super("all", "Teleport all players");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Command Variants" section
|
||||||
|
// ============================================
|
||||||
|
public static class TpCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
private final RequiredArg<PlayerRef> targetArg;
|
||||||
|
|
||||||
|
public TpCommand() {
|
||||||
|
super("tp", "Teleport command");
|
||||||
|
|
||||||
|
// Main variant: /tp <player>
|
||||||
|
targetArg = withRequiredArg("target", "Player to teleport to", ArgTypes.PLAYER_REF);
|
||||||
|
|
||||||
|
// Add variant: /tp <player> <destination>
|
||||||
|
addUsageVariant(new TpToPlayerVariant());
|
||||||
|
|
||||||
|
// Note: Position variant would need different argument types
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
// Teleport sender to target player
|
||||||
|
PlayerRef target = context.get(targetArg);
|
||||||
|
context.sender().sendMessage(Message.raw("Teleporting to " + target.getUsername()));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Variant Implementation" section
|
||||||
|
// ============================================
|
||||||
|
public static class TpToPlayerVariant extends AbstractCommand {
|
||||||
|
|
||||||
|
private final RequiredArg<PlayerRef> playerArg;
|
||||||
|
private final RequiredArg<PlayerRef> destinationArg;
|
||||||
|
|
||||||
|
public TpToPlayerVariant() {
|
||||||
|
// Note: Documentation says "no name for variants - use description only"
|
||||||
|
// But AbstractCommand requires name, so we pass empty or description
|
||||||
|
super("Teleport one player to another");
|
||||||
|
|
||||||
|
playerArg = withRequiredArg("player", "Player to teleport", ArgTypes.PLAYER_REF);
|
||||||
|
destinationArg = withRequiredArg("destination", "Destination player", ArgTypes.PLAYER_REF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
PlayerRef player = context.get(playerArg);
|
||||||
|
PlayerRef destination = context.get(destinationArg);
|
||||||
|
// Teleport player to destination
|
||||||
|
context.sender().sendMessage(Message.raw("Teleporting " + player.getUsername() + " to " + destination.getUsername()));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Permission Inheritance" section
|
||||||
|
// ============================================
|
||||||
|
public static class KickWithPermissionCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
public KickWithPermissionCommand() {
|
||||||
|
super("kick", "Kick a player");
|
||||||
|
// Custom permission instead of auto-generated
|
||||||
|
// Note: requirePermission() method needs verification
|
||||||
|
// requirePermission("myplugin.admin.kick");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package org.example.docs.coreconcepts;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/core-concepts/assets.en.md
|
||||||
|
// This file tests that ALL assets documentation examples compile correctly
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.item.config.Item;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.ItemStack;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.logger.HytaleLogger;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests ALL assets.en.md documentation examples
|
||||||
|
*/
|
||||||
|
public class AssetsExample {
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Working with Items" section (lines 27-37)
|
||||||
|
// ============================================
|
||||||
|
public void getItemExample(HytaleLogger logger) {
|
||||||
|
// Get an item by identifier
|
||||||
|
Item sword = Item.getAssetMap().getAsset("hytale:iron_sword");
|
||||||
|
|
||||||
|
// Check if item exists
|
||||||
|
if (sword != null) {
|
||||||
|
logger.at(Level.INFO).log("Found item: " + sword.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Creating ItemStacks" section (lines 41-54)
|
||||||
|
// ============================================
|
||||||
|
public void createItemStackExample() {
|
||||||
|
// Create an ItemStack with quantity
|
||||||
|
ItemStack stack = new ItemStack("hytale:iron_sword", 5);
|
||||||
|
|
||||||
|
// Or with just the item ID (quantity defaults to 1)
|
||||||
|
ItemStack singleItem = new ItemStack("hytale:iron_sword");
|
||||||
|
|
||||||
|
// Access properties
|
||||||
|
String itemId = stack.getItemId();
|
||||||
|
int quantity = stack.getQuantity();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Getting a BlockType" section (lines 60-63)
|
||||||
|
// ============================================
|
||||||
|
public void getBlockTypeExample() {
|
||||||
|
BlockType stone = BlockType.getAssetMap().getAsset("hytale:stone");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Block in World" section (lines 67-75)
|
||||||
|
// Note: Requires Player and World context
|
||||||
|
// ============================================
|
||||||
|
public void blockInWorldExample(Player player) {
|
||||||
|
int x = 0, y = 0, z = 0;
|
||||||
|
|
||||||
|
// Get block at position
|
||||||
|
World world = player.getWorld();
|
||||||
|
BlockType blockType = world.getBlockType(x, y, z);
|
||||||
|
|
||||||
|
// Set block at position
|
||||||
|
world.setBlock(x, y, z, "hytale:stone");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
package org.example.docs.coreconcepts;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/core-concepts/codecs.en.md
|
||||||
|
// This file tests that ALL codecs documentation examples compile correctly
|
||||||
|
|
||||||
|
import com.hypixel.hytale.codec.Codec;
|
||||||
|
import com.hypixel.hytale.codec.KeyedCodec;
|
||||||
|
import com.hypixel.hytale.codec.builder.BuilderCodec;
|
||||||
|
import com.hypixel.hytale.codec.codecs.array.ArrayCodec;
|
||||||
|
import com.hypixel.hytale.codec.codecs.map.ObjectMapCodec;
|
||||||
|
import com.hypixel.hytale.codec.codecs.EnumCodec;
|
||||||
|
import com.hypixel.hytale.codec.validation.Validators;
|
||||||
|
import com.hypixel.hytale.server.core.util.Config;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests ALL codecs.en.md documentation examples
|
||||||
|
*/
|
||||||
|
public class CodecsExample {
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Basic Codecs" section - testing all primitive codecs
|
||||||
|
// ============================================
|
||||||
|
public void testBasicCodecs() {
|
||||||
|
Codec<String> stringCodec = Codec.STRING;
|
||||||
|
Codec<Integer> intCodec = Codec.INTEGER;
|
||||||
|
Codec<Long> longCodec = Codec.LONG;
|
||||||
|
Codec<Float> floatCodec = Codec.FLOAT;
|
||||||
|
Codec<Double> doubleCodec = Codec.DOUBLE;
|
||||||
|
Codec<Boolean> boolCodec = Codec.BOOLEAN;
|
||||||
|
Codec<Byte> byteCodec = Codec.BYTE;
|
||||||
|
Codec<Short> shortCodec = Codec.SHORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "List and Map Codecs" section
|
||||||
|
// ============================================
|
||||||
|
public void testCollectionCodecs() {
|
||||||
|
// Array of strings
|
||||||
|
Codec<String[]> stringArray = new ArrayCodec<>(Codec.STRING, String[]::new);
|
||||||
|
|
||||||
|
// Map with string keys
|
||||||
|
Codec<Map<String, Integer>> stringIntMap = new ObjectMapCodec<>(
|
||||||
|
Codec.INTEGER,
|
||||||
|
LinkedHashMap::new,
|
||||||
|
key -> key, // Key to string
|
||||||
|
str -> str // String to key
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Enum Codecs" section
|
||||||
|
// ============================================
|
||||||
|
public enum GameMode {
|
||||||
|
SURVIVAL, CREATIVE, ADVENTURE
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testEnumCodec() {
|
||||||
|
Codec<GameMode> gameModeCodec = new EnumCodec<>(GameMode.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Required vs Optional Fields" section
|
||||||
|
// ============================================
|
||||||
|
public static class ConfigWithValidator {
|
||||||
|
private String serverName;
|
||||||
|
private int maxPlayers = 20;
|
||||||
|
|
||||||
|
public static final BuilderCodec<ConfigWithValidator> CODEC = BuilderCodec.builder(ConfigWithValidator.class, ConfigWithValidator::new)
|
||||||
|
.append(new KeyedCodec<>("ServerName", Codec.STRING),
|
||||||
|
(config, val) -> config.serverName = val,
|
||||||
|
config -> config.serverName)
|
||||||
|
.addValidator(Validators.nonNull()) // Makes field required
|
||||||
|
.add()
|
||||||
|
.append(new KeyedCodec<>("MaxPlayers", Codec.INTEGER),
|
||||||
|
(config, val) -> config.maxPlayers = val,
|
||||||
|
config -> config.maxPlayers)
|
||||||
|
.add() // Optional - uses default value from class
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Nested Codecs" section
|
||||||
|
// ============================================
|
||||||
|
public static class GeneralSettings {
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public static final BuilderCodec<GeneralSettings> CODEC = BuilderCodec.builder(GeneralSettings.class, GeneralSettings::new)
|
||||||
|
.append(new KeyedCodec<>("Name", Codec.STRING),
|
||||||
|
(s, val) -> s.name = val,
|
||||||
|
s -> s.name)
|
||||||
|
.add()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DatabaseSettings {
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
public static final BuilderCodec<DatabaseSettings> CODEC = BuilderCodec.builder(DatabaseSettings.class, DatabaseSettings::new)
|
||||||
|
.append(new KeyedCodec<>("Url", Codec.STRING),
|
||||||
|
(s, val) -> s.url = val,
|
||||||
|
s -> s.url)
|
||||||
|
.add()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ServerSettings {
|
||||||
|
private GeneralSettings general;
|
||||||
|
private DatabaseSettings database;
|
||||||
|
|
||||||
|
public static final BuilderCodec<ServerSettings> CODEC = BuilderCodec.builder(ServerSettings.class, ServerSettings::new)
|
||||||
|
.append(new KeyedCodec<>("General", GeneralSettings.CODEC),
|
||||||
|
(s, val) -> s.general = val,
|
||||||
|
s -> s.general)
|
||||||
|
.add()
|
||||||
|
.append(new KeyedCodec<>("Database", DatabaseSettings.CODEC),
|
||||||
|
(s, val) -> s.database = val,
|
||||||
|
s -> s.database)
|
||||||
|
.add()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Inheritance" section
|
||||||
|
// ============================================
|
||||||
|
public static class BaseEntity {
|
||||||
|
protected String id;
|
||||||
|
|
||||||
|
public BaseEntity() {}
|
||||||
|
|
||||||
|
public static final BuilderCodec<BaseEntity> BASE_CODEC = BuilderCodec.builder(BaseEntity.class, BaseEntity::new)
|
||||||
|
.append(new KeyedCodec<>("Id", Codec.STRING),
|
||||||
|
(e, val) -> e.id = val,
|
||||||
|
e -> e.id)
|
||||||
|
.add()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PlayerEntity extends BaseEntity {
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
public PlayerEntity() { super(); }
|
||||||
|
|
||||||
|
public static final BuilderCodec<PlayerEntity> CODEC = BuilderCodec.builder(PlayerEntity.class, PlayerEntity::new, BaseEntity.BASE_CODEC)
|
||||||
|
.append(new KeyedCodec<>("Username", Codec.STRING),
|
||||||
|
(e, val) -> e.username = val,
|
||||||
|
e -> e.username)
|
||||||
|
.add()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Named Configs" section - pattern only (requires plugin context)
|
||||||
|
// ============================================
|
||||||
|
// In a plugin class:
|
||||||
|
// Config<MyConfig> mainConfig = withConfig("main", MyConfig.CODEC);
|
||||||
|
// Config<DatabaseConfig> dbConfig = withConfig("database", DatabaseConfig.CODEC);
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package org.example.docs.coreconcepts;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/core-concepts/registries.en.md
|
||||||
|
// This file tests that ALL registries documentation examples compile correctly
|
||||||
|
|
||||||
|
import com.hypixel.hytale.event.EventPriority;
|
||||||
|
import com.hypixel.hytale.event.EventRegistry;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.CommandRegistry;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
|
||||||
|
import com.hypixel.hytale.server.core.task.TaskRegistry;
|
||||||
|
import com.hypixel.hytale.logger.HytaleLogger;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests ALL registries.en.md documentation examples
|
||||||
|
*/
|
||||||
|
public class RegistriesExample {
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "EventRegistry" section (lines 43-65)
|
||||||
|
// ============================================
|
||||||
|
public void registerEvents(EventRegistry eventRegistry, HytaleLogger logger) {
|
||||||
|
// Simple registration
|
||||||
|
eventRegistry.register(PlayerConnectEvent.class, event -> {
|
||||||
|
logger.at(Level.INFO).log("Player connecting: " + event.getPlayerRef().getUsername());
|
||||||
|
});
|
||||||
|
|
||||||
|
// With priority
|
||||||
|
eventRegistry.register(
|
||||||
|
EventPriority.EARLY,
|
||||||
|
PlayerConnectEvent.class,
|
||||||
|
this::onPlayerConnect
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onPlayerConnect(PlayerConnectEvent event) {
|
||||||
|
// handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "CommandRegistry" section (lines 30-38)
|
||||||
|
// Note: Documentation says register() but should be registerCommand()
|
||||||
|
// ============================================
|
||||||
|
public void registerCommands(CommandRegistry commandRegistry) {
|
||||||
|
// getCommandRegistry().registerCommand(new MyCommand());
|
||||||
|
// Note: Cannot test without actual command class
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "TaskRegistry" section (lines 94-129)
|
||||||
|
// ============================================
|
||||||
|
public void registerTasks(TaskRegistry taskRegistry, HytaleLogger logger) {
|
||||||
|
// Async operation
|
||||||
|
CompletableFuture<Void> task = CompletableFuture.runAsync(() -> {
|
||||||
|
// Background work
|
||||||
|
});
|
||||||
|
taskRegistry.registerTask(task);
|
||||||
|
|
||||||
|
// Delayed operation (3 seconds)
|
||||||
|
CompletableFuture.delayedExecutor(3, TimeUnit.SECONDS)
|
||||||
|
.execute(() -> {
|
||||||
|
logger.at(Level.INFO).log("Delayed task executed!");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Repeating operation (every 5 minutes)
|
||||||
|
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
ScheduledFuture<?> repeating = scheduler.scheduleAtFixedRate(() -> {
|
||||||
|
logger.at(Level.INFO).log("Repeating task!");
|
||||||
|
}, 0, 5, TimeUnit.MINUTES);
|
||||||
|
taskRegistry.registerTask((ScheduledFuture<Void>) repeating);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Registration Timing" section (lines 159-176)
|
||||||
|
// ============================================
|
||||||
|
// Note: These examples show plugin lifecycle methods
|
||||||
|
// setup() - Configuration only
|
||||||
|
// start() - Register everything here
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package org.example.docs.coreconcepts;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/core-concepts/threading.en.md
|
||||||
|
// This file tests that ALL threading documentation examples compile correctly
|
||||||
|
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.event.EventRegistry;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerChatEvent;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests ALL threading.en.md documentation examples
|
||||||
|
*/
|
||||||
|
public class ThreadingExample {
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Per-World Threading" section (lines 17-25)
|
||||||
|
// ============================================
|
||||||
|
public void perWorldThreadingExample(Player player) {
|
||||||
|
BlockType blockType = null; // Placeholder
|
||||||
|
Object position = null; // Placeholder
|
||||||
|
|
||||||
|
World world = player.getWorld();
|
||||||
|
|
||||||
|
// Check if we're on this world's thread
|
||||||
|
if (world.isInThread()) {
|
||||||
|
// Safe to modify world state
|
||||||
|
// world.setBlock(position, blockType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Async Operations" section (lines 35-49)
|
||||||
|
// Note: Fixed to use PlayerRef.sendMessage() instead of getPlayer()
|
||||||
|
// ============================================
|
||||||
|
public void asyncOperationsExample(Player player) {
|
||||||
|
PlayerRef ref = player.getPlayerRef();
|
||||||
|
World world = player.getWorld();
|
||||||
|
|
||||||
|
CompletableFuture.supplyAsync(() -> {
|
||||||
|
// This runs on a worker thread
|
||||||
|
return loadDataFromDatabase(ref.getUuid());
|
||||||
|
}).thenAccept(data -> {
|
||||||
|
// PlayerRef.sendMessage() is thread-safe
|
||||||
|
ref.sendMessage(Message.raw("Data loaded: " + data));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String loadDataFromDatabase(UUID uuid) {
|
||||||
|
return "sample data";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "PlayerRef" section (lines 63-80)
|
||||||
|
// Note: Fixed - removed getWorld() and getPlayer() which don't exist
|
||||||
|
// ============================================
|
||||||
|
public void playerRefExample(Player player) {
|
||||||
|
// Store reference (thread-safe)
|
||||||
|
PlayerRef playerRef = player.getPlayerRef();
|
||||||
|
|
||||||
|
// Safe to access on any thread:
|
||||||
|
UUID uuid = playerRef.getUuid();
|
||||||
|
String username = playerRef.getUsername();
|
||||||
|
String language = playerRef.getLanguage();
|
||||||
|
|
||||||
|
// Get current world UUID (may change if player switches worlds)
|
||||||
|
UUID worldUuid = playerRef.getWorldUuid();
|
||||||
|
|
||||||
|
// Send message directly (thread-safe)
|
||||||
|
playerRef.sendMessage(Message.raw("Hello!"));
|
||||||
|
|
||||||
|
// For ECS operations, get the entity reference
|
||||||
|
Ref<EntityStore> entityRef = playerRef.getReference(); // null if not in world
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Checking Thread Context" section (lines 84-94)
|
||||||
|
// ============================================
|
||||||
|
public void checkThreadContextExample(Player player) {
|
||||||
|
World world = player.getWorld();
|
||||||
|
|
||||||
|
// Check if on world's thread
|
||||||
|
if (!world.isInThread()) {
|
||||||
|
throw new IllegalStateException("Must be called from world thread!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug: print current thread
|
||||||
|
System.out.println("Current thread: " + Thread.currentThread().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Database Operations" section (lines 100-115)
|
||||||
|
// Note: Fixed to use PlayerRef.sendMessage()
|
||||||
|
// ============================================
|
||||||
|
public void savePlayer(Player player) {
|
||||||
|
PlayerRef ref = player.getPlayerRef();
|
||||||
|
Object data = collectPlayerData(player);
|
||||||
|
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
// Runs on worker thread
|
||||||
|
saveToDatabase(ref.getUuid(), data);
|
||||||
|
}).thenRun(() -> {
|
||||||
|
// Notify player (PlayerRef.sendMessage is thread-safe)
|
||||||
|
ref.sendMessage(Message.raw("Saved!"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object collectPlayerData(Player player) { return null; }
|
||||||
|
private void saveToDatabase(UUID uuid, Object data) {}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Loading Data on Join" section (lines 120-138)
|
||||||
|
// Note: Fixed to use Ref<EntityStore> instead of Player
|
||||||
|
// ============================================
|
||||||
|
public void loadingDataOnJoinExample(EventRegistry eventRegistry) {
|
||||||
|
eventRegistry.register(PlayerConnectEvent.class, event -> {
|
||||||
|
PlayerRef ref = event.getPlayerRef();
|
||||||
|
World world = event.getWorld();
|
||||||
|
|
||||||
|
CompletableFuture.supplyAsync(() -> {
|
||||||
|
// Load data on worker thread
|
||||||
|
return loadFromDatabase(ref.getUuid());
|
||||||
|
}).thenAccept(data -> {
|
||||||
|
if (world != null && data != null) {
|
||||||
|
world.execute(() -> {
|
||||||
|
// Back on world thread - safe to access ECS
|
||||||
|
Ref<EntityStore> entityRef = ref.getReference();
|
||||||
|
if (entityRef != null) {
|
||||||
|
applyData(entityRef, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object loadFromDatabase(UUID uuid) { return null; }
|
||||||
|
private void applyData(Ref<EntityStore> entityRef, Object data) {}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Async Events" section (lines 143-156)
|
||||||
|
// Note: PlayerChatEvent has String key, so use registerAsyncGlobal
|
||||||
|
// ============================================
|
||||||
|
public void asyncEventsExample(EventRegistry eventRegistry) {
|
||||||
|
eventRegistry.registerAsyncGlobal(
|
||||||
|
PlayerChatEvent.class,
|
||||||
|
future -> future.thenApply(event -> {
|
||||||
|
// This runs asynchronously
|
||||||
|
// Can perform slow operations here
|
||||||
|
String filtered = filterMessage(event.getContent());
|
||||||
|
event.setContent(filtered);
|
||||||
|
return event;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String filterMessage(String content) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
package org.example.docs.effects;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/effects/dynamic-lights.en.md
|
||||||
|
// Tests the Dynamic Lights documentation examples
|
||||||
|
//
|
||||||
|
// FINDINGS:
|
||||||
|
// - getTaskRegistry().schedule() does NOT exist - TaskRegistry only has registerTask()
|
||||||
|
// - ref.getPlayer() does NOT exist - PlayerRef doesn't have this method
|
||||||
|
// - player.getEntityRef() does NOT exist - use player.getReference()
|
||||||
|
// - Need to use standard Java concurrency for scheduling
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.protocol.ColorLight;
|
||||||
|
import com.hypixel.hytale.server.core.modules.entity.component.DynamicLight;
|
||||||
|
import com.hypixel.hytale.server.core.modules.entity.component.PersistentDynamicLight;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.component.ComponentAccessor;
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.component.Store;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests Dynamic Lights documentation examples
|
||||||
|
*/
|
||||||
|
public class DynamicLightsExample extends JavaPlugin {
|
||||||
|
|
||||||
|
private ScheduledExecutorService scheduler;
|
||||||
|
|
||||||
|
public DynamicLightsExample(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void start() {
|
||||||
|
scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void shutdown() {
|
||||||
|
if (scheduler != null) {
|
||||||
|
scheduler.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// ColorLight Structure (DOCUMENTED - CORRECT)
|
||||||
|
// =============================================
|
||||||
|
public void colorLightStructure() {
|
||||||
|
ColorLight light = new ColorLight(
|
||||||
|
(byte) 15, // Radius (0-255)
|
||||||
|
(byte) 255, // Red (0-255)
|
||||||
|
(byte) 100, // Green (0-255)
|
||||||
|
(byte) 50 // Blue (0-255)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// DynamicLight Component (DOCUMENTED - CORRECT)
|
||||||
|
// =============================================
|
||||||
|
public void addDynamicLight(Ref<EntityStore> entityRef, ComponentAccessor<EntityStore> componentAccessor) {
|
||||||
|
ColorLight light = new ColorLight(
|
||||||
|
(byte) 15,
|
||||||
|
(byte) 255,
|
||||||
|
(byte) 100,
|
||||||
|
(byte) 50
|
||||||
|
);
|
||||||
|
|
||||||
|
DynamicLight dynamicLight = new DynamicLight(light);
|
||||||
|
componentAccessor.putComponent(
|
||||||
|
entityRef,
|
||||||
|
DynamicLight.getComponentType(),
|
||||||
|
dynamicLight
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// PersistentDynamicLight Component (DOCUMENTED - CORRECT)
|
||||||
|
// =============================================
|
||||||
|
public void addPersistentLight(Ref<EntityStore> entityRef, ComponentAccessor<EntityStore> componentAccessor, ColorLight light) {
|
||||||
|
PersistentDynamicLight persistentLight = new PersistentDynamicLight(light);
|
||||||
|
componentAccessor.putComponent(
|
||||||
|
entityRef,
|
||||||
|
PersistentDynamicLight.getComponentType(),
|
||||||
|
persistentLight
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Updating Light Properties (CORRECT - uses Store<EntityStore>)
|
||||||
|
// =============================================
|
||||||
|
public void updateLight(Ref<EntityStore> entityRef, Store<EntityStore> store) {
|
||||||
|
DynamicLight dynamicLight = store.getComponent(
|
||||||
|
entityRef,
|
||||||
|
DynamicLight.getComponentType()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (dynamicLight != null) {
|
||||||
|
ColorLight newLight = new ColorLight(
|
||||||
|
(byte) 20,
|
||||||
|
(byte) 0,
|
||||||
|
(byte) 255,
|
||||||
|
(byte) 100
|
||||||
|
);
|
||||||
|
dynamicLight.setColorLight(newLight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Removing Light (DOCUMENTED - CORRECT)
|
||||||
|
// =============================================
|
||||||
|
public void removeLight(Ref<EntityStore> entityRef, ComponentAccessor<EntityStore> componentAccessor) {
|
||||||
|
componentAccessor.removeComponent(
|
||||||
|
entityRef,
|
||||||
|
DynamicLight.getComponentType()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Common Light Configurations (DOCUMENTED - CORRECT)
|
||||||
|
// =============================================
|
||||||
|
public void lightConfigurations() {
|
||||||
|
ColorLight fireLight = new ColorLight((byte) 12, (byte) 255, (byte) 150, (byte) 50);
|
||||||
|
ColorLight iceLight = new ColorLight((byte) 10, (byte) 100, (byte) 200, (byte) 255);
|
||||||
|
ColorLight magicLight = new ColorLight((byte) 15, (byte) 200, (byte) 50, (byte) 255);
|
||||||
|
ColorLight healLight = new ColorLight((byte) 10, (byte) 50, (byte) 255, (byte) 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Adding a Glow Effect to a Player - CORRECTED VERSION
|
||||||
|
// DOCUMENTED CODE HAS ISSUES:
|
||||||
|
// - ref.getPlayer() does NOT exist
|
||||||
|
// - getTaskRegistry().schedule() does NOT exist
|
||||||
|
// - player.getEntityRef() should be player.getReference()
|
||||||
|
// =============================================
|
||||||
|
public void addGlowEffectCorrected(Player player, ComponentAccessor<EntityStore> componentAccessor) {
|
||||||
|
PlayerRef ref = player.getPlayerRef();
|
||||||
|
World world = player.getWorld();
|
||||||
|
|
||||||
|
ColorLight glow = new ColorLight(
|
||||||
|
(byte) 8,
|
||||||
|
(byte) 255,
|
||||||
|
(byte) 215,
|
||||||
|
(byte) 0
|
||||||
|
);
|
||||||
|
|
||||||
|
DynamicLight light = new DynamicLight(glow);
|
||||||
|
// CORRECT: Use player.getReference() instead of player.getEntityRef()
|
||||||
|
Ref<EntityStore> entityRef = player.getReference();
|
||||||
|
componentAccessor.putComponent(
|
||||||
|
entityRef,
|
||||||
|
DynamicLight.getComponentType(),
|
||||||
|
light
|
||||||
|
);
|
||||||
|
|
||||||
|
// CORRECT: Use standard Java scheduling instead of getTaskRegistry().schedule()
|
||||||
|
scheduler.schedule(() -> {
|
||||||
|
world.execute(() -> {
|
||||||
|
// Check if entity is still valid
|
||||||
|
if (entityRef.isValid()) {
|
||||||
|
componentAccessor.removeComponent(
|
||||||
|
entityRef,
|
||||||
|
DynamicLight.getComponentType()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 3, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Pulsing Light Effect (CORRECTED - uses Store<EntityStore>)
|
||||||
|
// =============================================
|
||||||
|
private final Map<Ref<EntityStore>, Integer> pulsingEntities = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public void onTick(float deltaTime, Store<EntityStore> store) {
|
||||||
|
for (Map.Entry<Ref<EntityStore>, Integer> entry : pulsingEntities.entrySet()) {
|
||||||
|
Ref<EntityStore> entityRef = entry.getKey();
|
||||||
|
int tick = entry.getValue() + 1;
|
||||||
|
entry.setValue(tick);
|
||||||
|
|
||||||
|
double pulse = Math.sin(tick * 0.1) * 0.5 + 0.5;
|
||||||
|
byte radius = (byte) (5 + pulse * 10);
|
||||||
|
|
||||||
|
ColorLight light = new ColorLight(
|
||||||
|
radius,
|
||||||
|
(byte) 255,
|
||||||
|
(byte) (int)(100 + pulse * 100),
|
||||||
|
(byte) 50
|
||||||
|
);
|
||||||
|
|
||||||
|
DynamicLight dynamicLight = store.getComponent(
|
||||||
|
entityRef,
|
||||||
|
DynamicLight.getComponentType()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (dynamicLight != null) {
|
||||||
|
dynamicLight.setColorLight(light);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startPulsingLight(Ref<EntityStore> entityRef) {
|
||||||
|
pulsingEntities.put(entityRef, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopPulsingLight(Ref<EntityStore> entityRef) {
|
||||||
|
pulsingEntities.remove(entityRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package org.example.docs.effects;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/effects/_index.en.md
|
||||||
|
// Tests the Effects index page documentation examples
|
||||||
|
//
|
||||||
|
// FINDINGS:
|
||||||
|
// - Vector3d is from com.hypixel.hytale.math.vector.Vector3d, NOT org.joml
|
||||||
|
// - store.getComponent needs a Store<EntityStore>, not EntityStore directly
|
||||||
|
// - player.getEntityRef() does NOT exist - use player.getReference()
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.ParticleUtil;
|
||||||
|
import com.hypixel.hytale.protocol.ColorLight;
|
||||||
|
import com.hypixel.hytale.server.core.modules.entity.component.DynamicLight;
|
||||||
|
import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.component.ComponentAccessor;
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.component.Store;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector3d;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests Effects index documentation examples
|
||||||
|
*/
|
||||||
|
public class EffectsIndexExample extends JavaPlugin {
|
||||||
|
|
||||||
|
public EffectsIndexExample(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Quick Start: Spawning Particles
|
||||||
|
// =============================================
|
||||||
|
public void spawnParticleExample(Vector3d position, ComponentAccessor<EntityStore> componentAccessor) {
|
||||||
|
// Spawn particles at a position
|
||||||
|
ParticleUtil.spawnParticleEffect(
|
||||||
|
"explosion_small", // Particle system ID
|
||||||
|
position, // Vector3d position
|
||||||
|
componentAccessor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Quick Start: Adding Dynamic Light
|
||||||
|
// =============================================
|
||||||
|
public void dynamicLightExample(Ref<EntityStore> entityRef, ComponentAccessor<EntityStore> componentAccessor) {
|
||||||
|
// Create a colored light (radius, R, G, B)
|
||||||
|
ColorLight light = new ColorLight(
|
||||||
|
(byte) 15, // Radius
|
||||||
|
(byte) 255, // Red
|
||||||
|
(byte) 100, // Green
|
||||||
|
(byte) 50 // Blue
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add to entity
|
||||||
|
DynamicLight dynamicLight = new DynamicLight(light);
|
||||||
|
componentAccessor.putComponent(entityRef, DynamicLight.getComponentType(), dynamicLight);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Quick Start: Applying Entity Effects
|
||||||
|
// NOTE: Documentation uses store.getComponent() but EntityStore != Store<EntityStore>
|
||||||
|
// The actual API requires Store<EntityStore> which is obtained from EntityStore.getStore()
|
||||||
|
// =============================================
|
||||||
|
public void entityEffectExample(Ref<EntityStore> entityRef, Store<EntityStore> store, ComponentAccessor<EntityStore> componentAccessor) {
|
||||||
|
// Get effect controller
|
||||||
|
EffectControllerComponent controller = store.getComponent(
|
||||||
|
entityRef,
|
||||||
|
EffectControllerComponent.getComponentType()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Apply effect
|
||||||
|
EntityEffect effect = EntityEffect.getAssetMap().getAsset("fire_resistance");
|
||||||
|
if (controller != null && effect != null) {
|
||||||
|
controller.addEffect(entityRef, effect, componentAccessor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,318 @@
|
|||||||
|
package org.example.docs.effects;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/effects/entity-effects.en.md
|
||||||
|
// Tests the Entity Effects documentation examples
|
||||||
|
//
|
||||||
|
// FINDINGS (DOCUMENTED CODE HAS ISSUES):
|
||||||
|
// - OverlapBehavior is at com.hypixel.hytale.server.core.asset.type.entityeffect.config.OverlapBehavior
|
||||||
|
// - BuffCommand.execute() returns void, should return CompletableFuture<Void>
|
||||||
|
// - ctx.getArg("name", Type.class) does NOT exist - use requiredArg.get(ctx)
|
||||||
|
// - target.getPlayer() does NOT exist - PlayerRef doesn't have this method
|
||||||
|
// - ctx.getSender() should be ctx.sender()
|
||||||
|
// - target.getName() should be target.getUsername()
|
||||||
|
// - player.sendMessage("text") should use Message.raw("text")
|
||||||
|
// - player.getEntityRef() does NOT exist - use player.getReference()
|
||||||
|
// - store.getComponent() requires Store<EntityStore>, not EntityStore
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.entityeffect.config.OverlapBehavior;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.entityeffect.config.RemovalBehavior;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.ParticleUtil;
|
||||||
|
import com.hypixel.hytale.server.core.modules.entity.component.DynamicLight;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
import com.hypixel.hytale.protocol.ColorLight;
|
||||||
|
import com.hypixel.hytale.component.ComponentAccessor;
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.component.Store;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.AbstractCommand;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.CommandContext;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests Entity Effects documentation examples
|
||||||
|
*/
|
||||||
|
public class EntityEffectsExample extends JavaPlugin {
|
||||||
|
|
||||||
|
public EntityEffectsExample(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Accessing Effect Assets (DOCUMENTED - CORRECT)
|
||||||
|
// =============================================
|
||||||
|
public void accessEffectAssets() {
|
||||||
|
// Get effect by ID
|
||||||
|
EntityEffect effect = EntityEffect.getAssetMap().getAsset("fire_resistance");
|
||||||
|
|
||||||
|
// Get effect index for operations
|
||||||
|
if (effect != null) {
|
||||||
|
int effectIndex = EntityEffect.getAssetMap().getIndex(effect.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// EffectControllerComponent - Getting controller
|
||||||
|
// CORRECTED: Uses Store<EntityStore> instead of EntityStore
|
||||||
|
// =============================================
|
||||||
|
public void getEffectController(Ref<EntityStore> entityRef, Store<EntityStore> store) {
|
||||||
|
EffectControllerComponent controller = store.getComponent(
|
||||||
|
entityRef,
|
||||||
|
EffectControllerComponent.getComponentType()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Adding Effects - Basic Addition (CORRECTED)
|
||||||
|
// =============================================
|
||||||
|
public void addBasicEffect(Ref<EntityStore> entityRef, Store<EntityStore> store, ComponentAccessor<EntityStore> componentAccessor) {
|
||||||
|
EffectControllerComponent controller = store.getComponent(
|
||||||
|
entityRef,
|
||||||
|
EffectControllerComponent.getComponentType()
|
||||||
|
);
|
||||||
|
|
||||||
|
EntityEffect effect = EntityEffect.getAssetMap().getAsset("speed_boost");
|
||||||
|
|
||||||
|
if (controller != null && effect != null) {
|
||||||
|
controller.addEffect(
|
||||||
|
entityRef,
|
||||||
|
effect,
|
||||||
|
componentAccessor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Adding Effects - With Custom Duration (CORRECTED)
|
||||||
|
// =============================================
|
||||||
|
public void addEffectWithDuration(Ref<EntityStore> entityRef, Store<EntityStore> store, ComponentAccessor<EntityStore> componentAccessor) {
|
||||||
|
EffectControllerComponent controller = store.getComponent(
|
||||||
|
entityRef,
|
||||||
|
EffectControllerComponent.getComponentType()
|
||||||
|
);
|
||||||
|
|
||||||
|
EntityEffect effect = EntityEffect.getAssetMap().getAsset("speed_boost");
|
||||||
|
|
||||||
|
if (controller != null && effect != null) {
|
||||||
|
controller.addEffect(
|
||||||
|
entityRef,
|
||||||
|
effect,
|
||||||
|
100.0f, // Duration in seconds
|
||||||
|
OverlapBehavior.EXTEND, // How to handle overlap
|
||||||
|
componentAccessor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Adding Effects - Infinite Effects (CORRECTED)
|
||||||
|
// =============================================
|
||||||
|
public void addInfiniteEffect(Ref<EntityStore> entityRef, Store<EntityStore> store, ComponentAccessor<EntityStore> componentAccessor) {
|
||||||
|
EffectControllerComponent controller = store.getComponent(
|
||||||
|
entityRef,
|
||||||
|
EffectControllerComponent.getComponentType()
|
||||||
|
);
|
||||||
|
|
||||||
|
EntityEffect effect = EntityEffect.getAssetMap().getAsset("permanent_buff");
|
||||||
|
|
||||||
|
if (controller != null && effect != null) {
|
||||||
|
controller.addInfiniteEffect(
|
||||||
|
entityRef,
|
||||||
|
EntityEffect.getAssetMap().getIndex(effect.getId()),
|
||||||
|
effect,
|
||||||
|
componentAccessor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Removing Effects (CORRECTED)
|
||||||
|
// =============================================
|
||||||
|
public void removeEffect(Ref<EntityStore> entityRef, Store<EntityStore> store, ComponentAccessor<EntityStore> componentAccessor) {
|
||||||
|
EffectControllerComponent controller = store.getComponent(
|
||||||
|
entityRef,
|
||||||
|
EffectControllerComponent.getComponentType()
|
||||||
|
);
|
||||||
|
|
||||||
|
EntityEffect effect = EntityEffect.getAssetMap().getAsset("speed_boost");
|
||||||
|
|
||||||
|
if (controller != null && effect != null) {
|
||||||
|
controller.removeEffect(
|
||||||
|
entityRef,
|
||||||
|
EntityEffect.getAssetMap().getIndex(effect.getId()),
|
||||||
|
componentAccessor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Overlap Behaviors Usage (CORRECTED)
|
||||||
|
// =============================================
|
||||||
|
public void overlapBehaviorExamples(Ref<EntityStore> entityRef, Store<EntityStore> store, ComponentAccessor<EntityStore> componentAccessor) {
|
||||||
|
EffectControllerComponent controller = store.getComponent(
|
||||||
|
entityRef,
|
||||||
|
EffectControllerComponent.getComponentType()
|
||||||
|
);
|
||||||
|
|
||||||
|
EntityEffect effect = EntityEffect.getAssetMap().getAsset("buff");
|
||||||
|
|
||||||
|
if (controller != null && effect != null) {
|
||||||
|
// Extend duration if already applied
|
||||||
|
controller.addEffect(entityRef, effect, 30.0f, OverlapBehavior.EXTEND, componentAccessor);
|
||||||
|
|
||||||
|
// Replace with fresh duration
|
||||||
|
controller.addEffect(entityRef, effect, 30.0f, OverlapBehavior.OVERWRITE, componentAccessor);
|
||||||
|
|
||||||
|
// Only apply if not already active
|
||||||
|
controller.addEffect(entityRef, effect, 30.0f, OverlapBehavior.IGNORE, componentAccessor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Buff Command - CORRECTED VERSION
|
||||||
|
// Fixed: return type, argument API, sender API, PlayerRef API
|
||||||
|
// =============================================
|
||||||
|
public static class BuffCommandCorrected extends AbstractCommand {
|
||||||
|
private final RequiredArg<PlayerRef> playerArg;
|
||||||
|
private final RequiredArg<String> effectArg;
|
||||||
|
private final RequiredArg<Integer> durationArg;
|
||||||
|
|
||||||
|
// Would be injected during plugin init
|
||||||
|
private Store<EntityStore> store;
|
||||||
|
private ComponentAccessor<EntityStore> componentAccessor;
|
||||||
|
|
||||||
|
public BuffCommandCorrected() {
|
||||||
|
super("buff", "effects.command.buff.description");
|
||||||
|
this.playerArg = withRequiredArg("player", "Target player", ArgTypes.PLAYER_REF);
|
||||||
|
this.effectArg = withRequiredArg("effect", "Effect ID", ArgTypes.STRING);
|
||||||
|
this.durationArg = withRequiredArg("duration", "Duration in seconds", ArgTypes.INTEGER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext ctx) {
|
||||||
|
PlayerRef target = playerArg.get(ctx);
|
||||||
|
String effectId = effectArg.get(ctx);
|
||||||
|
int duration = durationArg.get(ctx);
|
||||||
|
|
||||||
|
// CORRECTED: Use target.getReference() - PlayerRef doesn't have getPlayer()
|
||||||
|
Ref<EntityStore> entityRef = target.getReference();
|
||||||
|
if (entityRef == null || !entityRef.isValid()) {
|
||||||
|
ctx.sender().sendMessage(Message.raw("Player not found"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityEffect effect = EntityEffect.getAssetMap().getAsset(effectId);
|
||||||
|
if (effect == null) {
|
||||||
|
ctx.sender().sendMessage(Message.raw("Unknown effect: " + effectId));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
EffectControllerComponent controller = store.getComponent(
|
||||||
|
entityRef,
|
||||||
|
EffectControllerComponent.getComponentType()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (controller != null) {
|
||||||
|
controller.addEffect(
|
||||||
|
entityRef,
|
||||||
|
effect,
|
||||||
|
(float) duration,
|
||||||
|
OverlapBehavior.EXTEND,
|
||||||
|
componentAccessor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECTED: target.getUsername() instead of target.getName()
|
||||||
|
ctx.sender().sendMessage(Message.raw("Applied " + effectId + " to " + target.getUsername()));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Damage Over Time Effect - CORRECTED
|
||||||
|
// Uses player.getReference() and Message.raw()
|
||||||
|
// =============================================
|
||||||
|
public void applyPoison(Player player, float duration, Store<EntityStore> store, ComponentAccessor<EntityStore> componentAccessor) {
|
||||||
|
EntityEffect poison = EntityEffect.getAssetMap().getAsset("poison");
|
||||||
|
|
||||||
|
// CORRECTED: Use player.getReference() instead of player.getEntityRef()
|
||||||
|
Ref<EntityStore> entityRef = player.getReference();
|
||||||
|
EffectControllerComponent controller = store.getComponent(
|
||||||
|
entityRef,
|
||||||
|
EffectControllerComponent.getComponentType()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (controller != null && poison != null) {
|
||||||
|
controller.addEffect(
|
||||||
|
entityRef,
|
||||||
|
poison,
|
||||||
|
duration,
|
||||||
|
OverlapBehavior.EXTEND,
|
||||||
|
componentAccessor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECTED: Use Message.raw()
|
||||||
|
player.getPlayerRef().sendMessage(Message.raw("You have been poisoned!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Clear All Effects - CORRECTED
|
||||||
|
// =============================================
|
||||||
|
public void clearAllEffects(Player player, Store<EntityStore> store, ComponentAccessor<EntityStore> componentAccessor) {
|
||||||
|
Ref<EntityStore> entityRef = player.getReference();
|
||||||
|
EffectControllerComponent controller = store.getComponent(
|
||||||
|
entityRef,
|
||||||
|
EffectControllerComponent.getComponentType()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (controller != null) {
|
||||||
|
controller.clearEffects(
|
||||||
|
entityRef,
|
||||||
|
componentAccessor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECTED: Use Message.raw()
|
||||||
|
player.getPlayerRef().sendMessage(Message.raw("All effects cleared!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Combining with Other Effects - CORRECTED
|
||||||
|
// player.getPosition() does NOT exist on Player
|
||||||
|
// =============================================
|
||||||
|
public void applyMagicBuff(Player player, Store<EntityStore> store, ComponentAccessor<EntityStore> componentAccessor) {
|
||||||
|
EntityEffect buff = EntityEffect.getAssetMap().getAsset("magic_power");
|
||||||
|
Ref<EntityStore> entityRef = player.getReference();
|
||||||
|
|
||||||
|
EffectControllerComponent controller = store.getComponent(
|
||||||
|
entityRef,
|
||||||
|
EffectControllerComponent.getComponentType()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (controller != null && buff != null) {
|
||||||
|
controller.addEffect(entityRef, buff, componentAccessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add visual glow
|
||||||
|
ColorLight glow = new ColorLight((byte) 10, (byte) 200, (byte) 50, (byte) 255);
|
||||||
|
DynamicLight light = new DynamicLight(glow);
|
||||||
|
componentAccessor.putComponent(
|
||||||
|
entityRef,
|
||||||
|
DynamicLight.getComponentType(),
|
||||||
|
light
|
||||||
|
);
|
||||||
|
|
||||||
|
// NOTE: player.getPosition() does NOT exist
|
||||||
|
// Would need to get position from TransformComponent via the entity store
|
||||||
|
}
|
||||||
|
}
|
||||||
144
app/src/main/java/org/example/docs/effects/ParticlesExample.java
Normal file
144
app/src/main/java/org/example/docs/effects/ParticlesExample.java
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package org.example.docs.effects;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/effects/particles.en.md
|
||||||
|
// Tests the Particles documentation examples
|
||||||
|
//
|
||||||
|
// FINDINGS (DOCUMENTED CODE HAS ISSUES):
|
||||||
|
// - Vector3d is from com.hypixel.hytale.math.vector.Vector3d, NOT org.joml
|
||||||
|
// - event.getPosition().x() - Vector3i uses getX(), not x() (but also has public fields x,y,z)
|
||||||
|
// - target.getPosition() - Entity doesn't have getPosition() directly
|
||||||
|
// - player.getPosition() - Player doesn't have getPosition()
|
||||||
|
// - player.getUuid() - should be player.getPlayerRef().getUuid()
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.ParticleUtil;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSpawner;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.entity.Entity;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.ecs.BreakBlockEvent;
|
||||||
|
import com.hypixel.hytale.component.ComponentAccessor;
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.component.Store;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector3d;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector3i;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests Particles documentation examples
|
||||||
|
*/
|
||||||
|
public class ParticlesExample extends JavaPlugin {
|
||||||
|
|
||||||
|
private ComponentAccessor<EntityStore> componentAccessor;
|
||||||
|
|
||||||
|
public ParticlesExample(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Basic Spawn (DOCUMENTED - CORRECT)
|
||||||
|
// =============================================
|
||||||
|
public void basicSpawn(Vector3d position) {
|
||||||
|
ParticleUtil.spawnParticleEffect(
|
||||||
|
"explosion_small",
|
||||||
|
position,
|
||||||
|
componentAccessor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Spawn for specific players (DOCUMENTED - CORRECT)
|
||||||
|
// =============================================
|
||||||
|
public void spawnForPlayers(Vector3d position, List<Ref<EntityStore>> playerRefs) {
|
||||||
|
ParticleUtil.spawnParticleEffect(
|
||||||
|
"magic_trail",
|
||||||
|
position,
|
||||||
|
playerRefs,
|
||||||
|
componentAccessor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Advanced: With source entity reference (DOCUMENTED - CORRECT)
|
||||||
|
// =============================================
|
||||||
|
public void advancedSpawn(Vector3d position, Ref<EntityStore> sourceEntityRef, List<Ref<EntityStore>> playerRefs) {
|
||||||
|
ParticleUtil.spawnParticleEffect(
|
||||||
|
"attack_swing",
|
||||||
|
position,
|
||||||
|
sourceEntityRef,
|
||||||
|
playerRefs,
|
||||||
|
componentAccessor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Accessing Particle Assets (DOCUMENTED - CORRECT)
|
||||||
|
// =============================================
|
||||||
|
public void accessAssets() {
|
||||||
|
ParticleSystem system = ParticleSystem.getAssetMap().getAsset("explosion_large");
|
||||||
|
ParticleSpawner spawner = ParticleSpawner.getAssetMap().getAsset("fire_spawner");
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Event-Based Particles: Block Break
|
||||||
|
// DOCUMENTED CODE HAS ISSUE:
|
||||||
|
// event.getPosition().x() should work since Vector3i has public field x
|
||||||
|
// But documentation uses x() method which doesn't exist
|
||||||
|
// CORRECT: Use .x, .y, .z public fields OR getX(), getY(), getZ()
|
||||||
|
// =============================================
|
||||||
|
public void onBlockBreak(BreakBlockEvent event) {
|
||||||
|
// CORRECTED: Use getTargetBlock(), not getPosition()
|
||||||
|
Vector3i blockPos = event.getTargetBlock();
|
||||||
|
|
||||||
|
// Use public fields x, y, z
|
||||||
|
Vector3d pos = new Vector3d(
|
||||||
|
blockPos.x + 0.5,
|
||||||
|
blockPos.y + 0.5,
|
||||||
|
blockPos.z + 0.5
|
||||||
|
);
|
||||||
|
|
||||||
|
ParticleUtil.spawnParticleEffect(
|
||||||
|
"block_break_particles",
|
||||||
|
pos,
|
||||||
|
componentAccessor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Aura Manager - CORRECTED
|
||||||
|
// player.getUuid() should be player.getPlayerRef().getUuid()
|
||||||
|
// player.getPosition() doesn't exist
|
||||||
|
// =============================================
|
||||||
|
private final Map<UUID, Boolean> activeAuras = new HashMap<>();
|
||||||
|
|
||||||
|
public void enableAura(Player player) {
|
||||||
|
// CORRECTED: Use player.getPlayerRef().getUuid()
|
||||||
|
activeAuras.put(player.getPlayerRef().getUuid(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Tick-based aura spawning would need to get player position
|
||||||
|
// from TransformComponent, not from player.getPosition()
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Efficient spawning pattern (DOCUMENTED - CORRECT pattern)
|
||||||
|
// =============================================
|
||||||
|
public void spawnEfficiently(Vector3d position, List<Ref<EntityStore>> nearbyPlayerRefs) {
|
||||||
|
if (!nearbyPlayerRefs.isEmpty()) {
|
||||||
|
ParticleUtil.spawnParticleEffect(
|
||||||
|
"my_effect",
|
||||||
|
position,
|
||||||
|
nearbyPlayerRefs,
|
||||||
|
componentAccessor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package org.example.docs.entities;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/entities/_index.en.md
|
||||||
|
// Tests the "Quick Example" code block
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector3d;
|
||||||
|
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests _index.en.md documentation
|
||||||
|
*
|
||||||
|
* NOTE: The doc shows player.getPosition() but this method doesn't exist.
|
||||||
|
* Position is accessed via getTransformComponent().getPosition() (deprecated)
|
||||||
|
* or through ECS component access.
|
||||||
|
*/
|
||||||
|
public class EntitiesIndexExample extends JavaPlugin {
|
||||||
|
|
||||||
|
public EntitiesIndexExample(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
// From "Quick Example" section - CORRECTED version
|
||||||
|
getEventRegistry().register(PlayerConnectEvent.class, event -> {
|
||||||
|
// getPlayer() is @Deprecated but exists
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
|
||||||
|
if (player != null) {
|
||||||
|
// getPosition() doesn't exist on Player - use TransformComponent
|
||||||
|
// getTransformComponent() is deprecated but works
|
||||||
|
TransformComponent transform = player.getTransformComponent();
|
||||||
|
Vector3d pos = transform.getPosition();
|
||||||
|
|
||||||
|
// Get world - this works
|
||||||
|
World world = player.getWorld();
|
||||||
|
|
||||||
|
// sendMessage requires Message object, not String
|
||||||
|
player.sendMessage(Message.raw("Welcome at " + pos.toString()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
package org.example.docs.entities;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/entities/entity-components.en.md
|
||||||
|
// Tests the entity component documentation examples
|
||||||
|
//
|
||||||
|
// CRITICAL FINDING:
|
||||||
|
// The documentation shows: entityRef.getComponent(type) and entityRef.hasComponent(type)
|
||||||
|
// But Ref<EntityStore> does NOT have these methods!
|
||||||
|
// Correct pattern: store.getComponent(ref, type) where store = ref.getStore()
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
|
||||||
|
import com.hypixel.hytale.server.core.modules.entity.component.DisplayNameComponent;
|
||||||
|
import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox;
|
||||||
|
import com.hypixel.hytale.server.core.modules.entity.component.EntityScaleComponent;
|
||||||
|
import com.hypixel.hytale.server.core.modules.entity.component.DynamicLight;
|
||||||
|
import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation;
|
||||||
|
import com.hypixel.hytale.server.core.modules.entity.component.Invulnerable;
|
||||||
|
import com.hypixel.hytale.server.core.modules.entity.component.Intangible;
|
||||||
|
import com.hypixel.hytale.server.core.modules.entity.component.Interactable;
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.component.Store;
|
||||||
|
import com.hypixel.hytale.component.ComponentType;
|
||||||
|
import com.hypixel.hytale.component.Archetype;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector3d;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector3f;
|
||||||
|
import com.hypixel.hytale.math.shape.Box;
|
||||||
|
import com.hypixel.hytale.protocol.ColorLight;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests entity-components.en.md documentation
|
||||||
|
*
|
||||||
|
* IMPORTANT: The documentation shows entityRef.getComponent(type) but this is WRONG!
|
||||||
|
* The correct pattern is:
|
||||||
|
* Store<EntityStore> store = ref.getStore();
|
||||||
|
* Component comp = store.getComponent(ref, ComponentType);
|
||||||
|
*
|
||||||
|
* Ref<EntityStore> only has:
|
||||||
|
* - getStore() - returns Store<EntityStore>
|
||||||
|
* - getIndex() - returns int
|
||||||
|
* - isValid() - returns boolean
|
||||||
|
* - validate() - throws if invalid
|
||||||
|
*/
|
||||||
|
public class EntityComponentsExample extends JavaPlugin {
|
||||||
|
|
||||||
|
public EntityComponentsExample(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
getEventRegistry().register(PlayerConnectEvent.class, event -> {
|
||||||
|
PlayerRef playerRef = event.getPlayerRef();
|
||||||
|
|
||||||
|
// Get ECS reference
|
||||||
|
Ref<EntityStore> ref = playerRef.getReference();
|
||||||
|
if (ref == null || !ref.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT PATTERN: get store, then get component from store
|
||||||
|
Store<EntityStore> store = ref.getStore();
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// TransformComponent - CORRECT PATTERN
|
||||||
|
// =============================================
|
||||||
|
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 rotation = transform.getRotation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// BoundingBox - CORRECT PATTERN
|
||||||
|
// =============================================
|
||||||
|
ComponentType<EntityStore, BoundingBox> boundsType =
|
||||||
|
BoundingBox.getComponentType();
|
||||||
|
BoundingBox bounds = store.getComponent(ref, boundsType);
|
||||||
|
|
||||||
|
if (bounds != null) {
|
||||||
|
Box box = bounds.getBoundingBox();
|
||||||
|
// Box has methods like width(), height(), depth()
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// DisplayNameComponent - CORRECT PATTERN
|
||||||
|
// =============================================
|
||||||
|
ComponentType<EntityStore, DisplayNameComponent> nameType =
|
||||||
|
DisplayNameComponent.getComponentType();
|
||||||
|
DisplayNameComponent nameComp = store.getComponent(ref, nameType);
|
||||||
|
|
||||||
|
if (nameComp != null) {
|
||||||
|
Message name = nameComp.getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// EntityScaleComponent - CORRECT PATTERN
|
||||||
|
// =============================================
|
||||||
|
ComponentType<EntityStore, EntityScaleComponent> scaleType =
|
||||||
|
EntityScaleComponent.getComponentType();
|
||||||
|
EntityScaleComponent scale = store.getComponent(ref, scaleType);
|
||||||
|
|
||||||
|
if (scale != null) {
|
||||||
|
float currentScale = scale.getScale();
|
||||||
|
// scale.setScale(2.0f); // Would make entity 2x larger
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// DynamicLight - CORRECT PATTERN
|
||||||
|
// =============================================
|
||||||
|
ComponentType<EntityStore, DynamicLight> lightType =
|
||||||
|
DynamicLight.getComponentType();
|
||||||
|
DynamicLight light = store.getComponent(ref, lightType);
|
||||||
|
|
||||||
|
if (light != null) {
|
||||||
|
ColorLight colorLight = light.getColorLight();
|
||||||
|
// colorLight has public fields: radius, red, green, blue
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// HeadRotation - CORRECT PATTERN
|
||||||
|
// =============================================
|
||||||
|
ComponentType<EntityStore, HeadRotation> headType =
|
||||||
|
HeadRotation.getComponentType();
|
||||||
|
HeadRotation head = store.getComponent(ref, headType);
|
||||||
|
|
||||||
|
if (head != null) {
|
||||||
|
Vector3f rot = head.getRotation();
|
||||||
|
Vector3d direction = head.getDirection();
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Singleton Marker Components
|
||||||
|
// =============================================
|
||||||
|
// To check if an entity has a marker component, use archetype
|
||||||
|
Archetype<EntityStore> archetype = store.getArchetype(ref);
|
||||||
|
|
||||||
|
// Check for Invulnerable
|
||||||
|
ComponentType<EntityStore, Invulnerable> invulnType =
|
||||||
|
Invulnerable.getComponentType();
|
||||||
|
boolean isInvulnerable = archetype.contains(invulnType);
|
||||||
|
|
||||||
|
// Check for Intangible
|
||||||
|
ComponentType<EntityStore, Intangible> intangibleType =
|
||||||
|
Intangible.getComponentType();
|
||||||
|
boolean isIntangible = archetype.contains(intangibleType);
|
||||||
|
|
||||||
|
// Check for Interactable
|
||||||
|
ComponentType<EntityStore, Interactable> interactableType =
|
||||||
|
Interactable.getComponentType();
|
||||||
|
boolean isInteractable = archetype.contains(interactableType);
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// WRONG PATTERNS (from the documentation):
|
||||||
|
// =============================================
|
||||||
|
// entityRef.getComponent(transformType); // WRONG - Ref doesn't have getComponent
|
||||||
|
// entityRef.hasComponent(type); // WRONG - Ref doesn't have hasComponent
|
||||||
|
// entityRef.getAccessor(); // WRONG - Ref doesn't have getAccessor
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
package org.example.docs.entities;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/entities/entity-hierarchy.en.md
|
||||||
|
// This file tests the entity hierarchy documentation examples
|
||||||
|
//
|
||||||
|
// CRITICAL FINDING:
|
||||||
|
// The documentation describes a traditional OOP entity API that does NOT exist.
|
||||||
|
// Hytale uses ECS (Entity Component System) architecture where:
|
||||||
|
// - Entity is a Component<EntityStore>, not a standalone class with methods
|
||||||
|
// - Position is accessed via TransformComponent
|
||||||
|
// - Health, damage, effects are managed through separate systems/components
|
||||||
|
// - Methods like getPosition(), setPosition(), getHealth(), damage() DON'T EXIST
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.entity.Entity;
|
||||||
|
import com.hypixel.hytale.server.core.entity.LivingEntity;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.Inventory;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.component.Store;
|
||||||
|
import com.hypixel.hytale.component.Holder;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector3d;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests entity-hierarchy.en.md documentation
|
||||||
|
*
|
||||||
|
* IMPORTANT: Most methods described in the documentation do NOT exist.
|
||||||
|
* This file demonstrates what ACTUALLY works vs what the doc claims.
|
||||||
|
*/
|
||||||
|
public class EntityHierarchyExample extends JavaPlugin {
|
||||||
|
|
||||||
|
public EntityHierarchyExample(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
// Test Player methods that actually exist
|
||||||
|
getEventRegistry().register(PlayerConnectEvent.class, event -> {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
PlayerRef playerRef = event.getPlayerRef();
|
||||||
|
World world = event.getWorld();
|
||||||
|
|
||||||
|
if (player != null) {
|
||||||
|
// =============================================
|
||||||
|
// METHODS THAT ACTUALLY EXIST on Player:
|
||||||
|
// =============================================
|
||||||
|
|
||||||
|
// Send message (requires Message object, not String!)
|
||||||
|
player.sendMessage(Message.raw("Hello!"));
|
||||||
|
|
||||||
|
// Check permissions - EXISTS
|
||||||
|
boolean hasAdmin = player.hasPermission("admin.teleport");
|
||||||
|
|
||||||
|
// Get display name - EXISTS
|
||||||
|
String displayName = player.getDisplayName();
|
||||||
|
|
||||||
|
// Get world - EXISTS on Entity
|
||||||
|
World playerWorld = player.getWorld();
|
||||||
|
|
||||||
|
// Get inventory - EXISTS on LivingEntity
|
||||||
|
Inventory inventory = player.getInventory();
|
||||||
|
|
||||||
|
// Get UUID - EXISTS (but deprecated)
|
||||||
|
UUID uuid = player.getUuid();
|
||||||
|
|
||||||
|
// wasRemoved() - EXISTS (not isRemoved())
|
||||||
|
boolean removed = player.wasRemoved();
|
||||||
|
|
||||||
|
// getTransformComponent() - EXISTS but deprecated
|
||||||
|
TransformComponent transform = player.getTransformComponent();
|
||||||
|
Vector3d position = transform.getPosition();
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// METHODS THAT DON'T EXIST (documented but wrong):
|
||||||
|
// =============================================
|
||||||
|
// player.getPosition() - DOESN'T EXIST
|
||||||
|
// player.setPosition() - DOESN'T EXIST (use moveTo with ECS)
|
||||||
|
// player.isValid() - DOESN'T EXIST
|
||||||
|
// player.getId() - use getNetworkId() (deprecated)
|
||||||
|
// player.getEntityType() - DOESN'T EXIST
|
||||||
|
// player.hasComponent() - DOESN'T EXIST (Entity IS a Component)
|
||||||
|
// player.getComponent() - DOESN'T EXIST (use Store.getComponent)
|
||||||
|
// player.getName() - use getDisplayName() or playerRef.getUsername()
|
||||||
|
// player.sendMessage(String) - requires Message.raw()
|
||||||
|
// player.sendTitle() - DOESN'T EXIST
|
||||||
|
// player.sendActionBar() - DOESN'T EXIST
|
||||||
|
// player.getHealth() - DOESN'T EXIST
|
||||||
|
// player.setHealth() - DOESN'T EXIST
|
||||||
|
// player.damage() - DOESN'T EXIST
|
||||||
|
// player.addEffect() - DOESN'T EXIST
|
||||||
|
// player.getVelocity() - DOESN'T EXIST (use Velocity component)
|
||||||
|
// player.setVelocity() - DOESN'T EXIST
|
||||||
|
// player.giveItem() - DOESN'T EXIST (use inventory.addItem)
|
||||||
|
// player.kick() - DOESN'T EXIST
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// PlayerRef - what actually exists:
|
||||||
|
// =============================================
|
||||||
|
String username = playerRef.getUsername();
|
||||||
|
UUID uuid = playerRef.getUuid();
|
||||||
|
UUID worldUuid = playerRef.getWorldUuid();
|
||||||
|
|
||||||
|
// Send message via PlayerRef - EXISTS
|
||||||
|
playerRef.sendMessage(Message.raw("Hello via PlayerRef!"));
|
||||||
|
|
||||||
|
// Get ECS reference - EXISTS
|
||||||
|
Ref<EntityStore> ref = playerRef.getReference();
|
||||||
|
if (ref != null && ref.isValid()) {
|
||||||
|
// This is how you access components in ECS
|
||||||
|
Store<EntityStore> store = ref.getStore();
|
||||||
|
Player playerComponent = store.getComponent(ref, Player.getComponentType());
|
||||||
|
TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// LivingEntity - what actually exists:
|
||||||
|
// =============================================
|
||||||
|
public void livingEntityExample(LivingEntity living) {
|
||||||
|
// getInventory() - EXISTS
|
||||||
|
Inventory inventory = living.getInventory();
|
||||||
|
|
||||||
|
// getWorld() - EXISTS (inherited from Entity)
|
||||||
|
World world = living.getWorld();
|
||||||
|
|
||||||
|
// wasRemoved() - EXISTS
|
||||||
|
boolean removed = living.wasRemoved();
|
||||||
|
|
||||||
|
// getCurrentFallDistance() - EXISTS
|
||||||
|
double fallDistance = living.getCurrentFallDistance();
|
||||||
|
|
||||||
|
// getStatModifiersManager() - EXISTS
|
||||||
|
var statManager = living.getStatModifiersManager();
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Methods that DON'T EXIST on LivingEntity:
|
||||||
|
// =============================================
|
||||||
|
// getHealth(), setHealth(), getMaxHealth(), setMaxHealth()
|
||||||
|
// isDead(), heal()
|
||||||
|
// damage(double, DamageSource)
|
||||||
|
// addEffect(), removeEffect(), hasEffect(), clearEffects()
|
||||||
|
// getVelocity(), setVelocity(), knockback()
|
||||||
|
// isOnGround(), isSprinting(), isSneaking()
|
||||||
|
// setInvulnerable(), isInvulnerable()
|
||||||
|
// getKiller(), getLastDamageSource()
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Entity - what actually exists:
|
||||||
|
// =============================================
|
||||||
|
public void entityExample(Entity entity) {
|
||||||
|
// getWorld() - EXISTS
|
||||||
|
World world = entity.getWorld();
|
||||||
|
|
||||||
|
// getUuid() - EXISTS but deprecated
|
||||||
|
UUID uuid = entity.getUuid();
|
||||||
|
|
||||||
|
// wasRemoved() - EXISTS (not isRemoved())
|
||||||
|
boolean removed = entity.wasRemoved();
|
||||||
|
|
||||||
|
// remove() - EXISTS
|
||||||
|
// entity.remove();
|
||||||
|
|
||||||
|
// getNetworkId() - EXISTS but deprecated (not getId())
|
||||||
|
int networkId = entity.getNetworkId();
|
||||||
|
|
||||||
|
// getTransformComponent() - EXISTS but deprecated
|
||||||
|
TransformComponent transform = entity.getTransformComponent();
|
||||||
|
|
||||||
|
// getReference() - EXISTS
|
||||||
|
Ref<EntityStore> ref = entity.getReference();
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Methods that DON'T EXIST on Entity:
|
||||||
|
// =============================================
|
||||||
|
// getPosition(), setPosition()
|
||||||
|
// getRotation(), setRotation()
|
||||||
|
// getId() - use getNetworkId()
|
||||||
|
// isValid() - check ref.isValid()
|
||||||
|
// getEntityType()
|
||||||
|
// getChunk()
|
||||||
|
// getBoundingBox()
|
||||||
|
// hasComponent(), getComponent(), addComponent(), removeComponent()
|
||||||
|
// isRemoved() - use wasRemoved()
|
||||||
|
// getTicksLived()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
package org.example.docs.entities;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/entities/player-api.en.md
|
||||||
|
// Tests the Player API documentation examples
|
||||||
|
//
|
||||||
|
// FINDINGS:
|
||||||
|
// Many methods documented do NOT exist on Player class.
|
||||||
|
// Player is a LivingEntity with limited direct methods.
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.Inventory;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
import com.hypixel.hytale.protocol.GameMode;
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.component.Store;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector3d;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector3f;
|
||||||
|
import com.hypixel.hytale.math.vector.Transform;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests player-api.en.md documentation
|
||||||
|
*
|
||||||
|
* IMPORTANT: Many methods in the documentation DON'T EXIST on Player:
|
||||||
|
* - getPosition(), setPosition() - use TransformComponent
|
||||||
|
* - getVelocity(), setVelocity(), addVelocity(), knockback() - don't exist
|
||||||
|
* - isSprinting(), setSprinting(), isSneaking(), setSneaking() - don't exist
|
||||||
|
* - isFlying(), setFlying(), isSwimming() - don't exist
|
||||||
|
* - isOnGround() - doesn't exist
|
||||||
|
* - isOp(), setOp(), addPermission(), removePermission(), getPermissions() - don't exist
|
||||||
|
* - giveItem(), dropItem(), getHeldItem() - don't exist
|
||||||
|
* - openInventory(), closeInventory(), getCursorItem() - don't exist
|
||||||
|
* - kick() - use playerRef.getPacketHandler().disconnect()
|
||||||
|
*/
|
||||||
|
public class PlayerApiExample extends JavaPlugin {
|
||||||
|
|
||||||
|
public PlayerApiExample(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
getEventRegistry().register(PlayerConnectEvent.class, event -> {
|
||||||
|
PlayerRef playerRef = event.getPlayerRef();
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Methods that ACTUALLY EXIST on Player:
|
||||||
|
// =============================================
|
||||||
|
|
||||||
|
if (player != null) {
|
||||||
|
// getUuid() - EXISTS (deprecated)
|
||||||
|
UUID uuid = player.getUuid();
|
||||||
|
|
||||||
|
// getDisplayName() - EXISTS
|
||||||
|
String displayName = player.getDisplayName();
|
||||||
|
|
||||||
|
// sendMessage(Message) - EXISTS
|
||||||
|
player.sendMessage(Message.raw("Hello!"));
|
||||||
|
|
||||||
|
// hasPermission(String) - EXISTS
|
||||||
|
boolean hasAdmin = player.hasPermission("admin.perm");
|
||||||
|
|
||||||
|
// getGameMode() - EXISTS
|
||||||
|
GameMode mode = player.getGameMode();
|
||||||
|
|
||||||
|
// getInventory() - EXISTS (inherited from LivingEntity)
|
||||||
|
Inventory inventory = player.getInventory();
|
||||||
|
|
||||||
|
// getViewRadius() / getClientViewRadius() - EXISTS
|
||||||
|
int viewRadius = player.getViewRadius();
|
||||||
|
int clientViewRadius = player.getClientViewRadius();
|
||||||
|
|
||||||
|
// getWorld() - EXISTS (inherited from Entity)
|
||||||
|
var world = player.getWorld();
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// PlayerRef - Methods that EXIST:
|
||||||
|
// =============================================
|
||||||
|
|
||||||
|
// getUuid() - EXISTS
|
||||||
|
UUID uuid = playerRef.getUuid();
|
||||||
|
|
||||||
|
// getUsername() - EXISTS
|
||||||
|
String username = playerRef.getUsername();
|
||||||
|
|
||||||
|
// getWorldUuid() - EXISTS
|
||||||
|
UUID worldUuid = playerRef.getWorldUuid();
|
||||||
|
|
||||||
|
// sendMessage(Message) - EXISTS
|
||||||
|
playerRef.sendMessage(Message.raw("Hello via PlayerRef!"));
|
||||||
|
|
||||||
|
// getReference() - EXISTS
|
||||||
|
Ref<EntityStore> ref = playerRef.getReference();
|
||||||
|
|
||||||
|
// getPacketHandler() - EXISTS
|
||||||
|
var packetHandler = playerRef.getPacketHandler();
|
||||||
|
|
||||||
|
// getTransform() - EXISTS
|
||||||
|
Transform transform = playerRef.getTransform();
|
||||||
|
if (transform != null) {
|
||||||
|
Vector3d position = transform.getPosition();
|
||||||
|
Vector3f rotation = transform.getRotation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// getHeadRotation() - EXISTS
|
||||||
|
Vector3f headRotation = playerRef.getHeadRotation();
|
||||||
|
|
||||||
|
// isValid() - EXISTS
|
||||||
|
boolean isValid = playerRef.isValid();
|
||||||
|
|
||||||
|
// referToServer() - EXISTS
|
||||||
|
// playerRef.referToServer("play.example.com", 25565);
|
||||||
|
|
||||||
|
// getChunkTracker() - EXISTS
|
||||||
|
var chunkTracker = playerRef.getChunkTracker();
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// WRONG PATTERNS (from documentation):
|
||||||
|
// =============================================
|
||||||
|
// player.getPosition() - DOESN'T EXIST
|
||||||
|
// player.setPosition(Vector3d) - DOESN'T EXIST
|
||||||
|
// player.getVelocity() - DOESN'T EXIST
|
||||||
|
// player.setVelocity(Vector3d) - DOESN'T EXIST
|
||||||
|
// player.addVelocity(Vector3d) - DOESN'T EXIST
|
||||||
|
// player.knockback(Vector3d) - DOESN'T EXIST
|
||||||
|
// player.isSprinting() - DOESN'T EXIST
|
||||||
|
// player.setSprinting(boolean) - DOESN'T EXIST
|
||||||
|
// player.isSneaking() - DOESN'T EXIST
|
||||||
|
// player.setSneaking(boolean) - DOESN'T EXIST
|
||||||
|
// player.isFlying() - DOESN'T EXIST
|
||||||
|
// player.setFlying(boolean) - DOESN'T EXIST
|
||||||
|
// player.isSwimming() - DOESN'T EXIST
|
||||||
|
// player.isOnGround() - DOESN'T EXIST
|
||||||
|
// player.getEyePosition() - DOESN'T EXIST
|
||||||
|
// player.getEyeDirection() - DOESN'T EXIST
|
||||||
|
// player.isOp() - DOESN'T EXIST
|
||||||
|
// player.setOp(boolean) - DOESN'T EXIST
|
||||||
|
// player.addPermission(String) - DOESN'T EXIST
|
||||||
|
// player.removePermission(String) - DOESN'T EXIST
|
||||||
|
// player.getPermissions() - DOESN'T EXIST
|
||||||
|
// player.giveItem(ItemStack) - DOESN'T EXIST
|
||||||
|
// player.dropItem(ItemStack) - DOESN'T EXIST
|
||||||
|
// player.getHeldItem() - DOESN'T EXIST
|
||||||
|
// player.setHeldItem(ItemStack) - DOESN'T EXIST
|
||||||
|
// player.openInventory(Inventory) - DOESN'T EXIST
|
||||||
|
// player.closeInventory() - DOESN'T EXIST
|
||||||
|
// player.getCursorItem() - DOESN'T EXIST
|
||||||
|
// player.kick(String) - DOESN'T EXIST - use packetHandler.disconnect()
|
||||||
|
|
||||||
|
// Correct way to disconnect a player:
|
||||||
|
// playerRef.getPacketHandler().disconnect("Reason");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,320 @@
|
|||||||
|
package org.example.docs.events;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/events/async-events.en.md
|
||||||
|
// This file tests that ALL async events documentation examples compile correctly
|
||||||
|
|
||||||
|
import com.hypixel.hytale.event.EventPriority;
|
||||||
|
import com.hypixel.hytale.event.EventRegistry;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerChatEvent;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.universe.Universe;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.logger.HytaleLogger;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests async-events.en.md documentation
|
||||||
|
*
|
||||||
|
* IMPORTANT NOTE:
|
||||||
|
* - PlayerChatEvent implements IAsyncEvent<String> - it has a String key
|
||||||
|
* - Therefore, you MUST use registerAsyncGlobal() not registerAsync()
|
||||||
|
* - The simple registerAsync(Class, handler) only works for Void key events
|
||||||
|
*/
|
||||||
|
public class AsyncEventsExample {
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Registering Async Handlers" section
|
||||||
|
// CORRECTION: Must use registerAsyncGlobal() for PlayerChatEvent
|
||||||
|
// ============================================
|
||||||
|
public void registerAsyncHandlers(EventRegistry eventRegistry) {
|
||||||
|
// PlayerChatEvent has String key - MUST use registerAsyncGlobal()
|
||||||
|
eventRegistry.registerAsyncGlobal(PlayerChatEvent.class, future -> {
|
||||||
|
return future.thenApply(event -> {
|
||||||
|
// This runs asynchronously
|
||||||
|
String filtered = filterContent(event.getContent());
|
||||||
|
event.setContent(filtered);
|
||||||
|
return event;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "With Priority" section
|
||||||
|
// ============================================
|
||||||
|
public void registerWithPriority(EventRegistry eventRegistry) {
|
||||||
|
eventRegistry.registerAsyncGlobal(
|
||||||
|
EventPriority.EARLY,
|
||||||
|
PlayerChatEvent.class,
|
||||||
|
future -> future.thenApply(event -> {
|
||||||
|
// Runs early in async chain
|
||||||
|
return event;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Sequential Operations" section
|
||||||
|
// ============================================
|
||||||
|
public void sequentialOperations(EventRegistry eventRegistry, HytaleLogger logger) {
|
||||||
|
eventRegistry.registerAsyncGlobal(PlayerChatEvent.class, future -> {
|
||||||
|
return future
|
||||||
|
.thenApply(event -> {
|
||||||
|
// Step 1: Filter content
|
||||||
|
event.setContent(filterProfanity(event.getContent()));
|
||||||
|
return event;
|
||||||
|
})
|
||||||
|
.thenApply(event -> {
|
||||||
|
// Step 2: Add formatting
|
||||||
|
event.setContent(addChatFormatting(event.getContent()));
|
||||||
|
return event;
|
||||||
|
})
|
||||||
|
.thenApply(event -> {
|
||||||
|
// Step 3: Log
|
||||||
|
logChatMessage(event.getSender(), event.getContent());
|
||||||
|
return event;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Parallel Operations" section
|
||||||
|
// ============================================
|
||||||
|
public void parallelOperations(EventRegistry eventRegistry) {
|
||||||
|
eventRegistry.registerAsyncGlobal(PlayerChatEvent.class, future -> {
|
||||||
|
return future.thenCompose(event -> {
|
||||||
|
// Start multiple async operations in parallel
|
||||||
|
CompletableFuture<Boolean> spamCheckFuture =
|
||||||
|
CompletableFuture.supplyAsync(() -> checkForSpam(event.getContent()));
|
||||||
|
|
||||||
|
CompletableFuture<Boolean> linkCheckFuture =
|
||||||
|
CompletableFuture.supplyAsync(() -> checkForLinks(event.getContent()));
|
||||||
|
|
||||||
|
// Combine results
|
||||||
|
return spamCheckFuture.thenCombine(linkCheckFuture, (isSpam, hasLinks) -> {
|
||||||
|
if (isSpam || hasLinks) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Error Handling" section
|
||||||
|
// ============================================
|
||||||
|
public void errorHandling(EventRegistry eventRegistry, HytaleLogger logger) {
|
||||||
|
eventRegistry.registerAsyncGlobal(PlayerChatEvent.class, future -> {
|
||||||
|
return future
|
||||||
|
.thenApply(event -> {
|
||||||
|
// May throw exception
|
||||||
|
riskyOperation(event);
|
||||||
|
return event;
|
||||||
|
})
|
||||||
|
.exceptionally(throwable -> {
|
||||||
|
// Handle error
|
||||||
|
logger.at(Level.SEVERE).log("Async event failed: " + throwable.getMessage());
|
||||||
|
return null; // Event will be skipped
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Switching to Main Thread" section
|
||||||
|
// ============================================
|
||||||
|
public void switchToMainThread(EventRegistry eventRegistry) {
|
||||||
|
eventRegistry.registerAsyncGlobal(PlayerChatEvent.class, future -> {
|
||||||
|
return future.thenApply(event -> {
|
||||||
|
// Async: Check external database
|
||||||
|
boolean isMuted = checkMuteStatus(event.getSender().getUuid());
|
||||||
|
|
||||||
|
if (isMuted) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
|
||||||
|
// Schedule world thread work for player notification
|
||||||
|
World world = Universe.get().getWorld(event.getSender().getWorldUuid());
|
||||||
|
if (world != null) {
|
||||||
|
world.execute(() -> {
|
||||||
|
event.getSender().sendMessage(Message.raw("You are muted!"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Complete Chat Handler Example" section
|
||||||
|
// ============================================
|
||||||
|
public static class ChatPlugin extends JavaPlugin {
|
||||||
|
|
||||||
|
public ChatPlugin(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
// Early priority: Content filtering
|
||||||
|
getEventRegistry().registerAsyncGlobal(
|
||||||
|
EventPriority.EARLY,
|
||||||
|
PlayerChatEvent.class,
|
||||||
|
this::filterContent
|
||||||
|
);
|
||||||
|
|
||||||
|
// Normal priority: Standard processing
|
||||||
|
getEventRegistry().registerAsyncGlobal(
|
||||||
|
PlayerChatEvent.class,
|
||||||
|
this::processChat
|
||||||
|
);
|
||||||
|
|
||||||
|
// Late priority: Logging
|
||||||
|
getEventRegistry().registerAsyncGlobal(
|
||||||
|
EventPriority.LATE,
|
||||||
|
PlayerChatEvent.class,
|
||||||
|
this::logChat
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompletableFuture<PlayerChatEvent> filterContent(
|
||||||
|
CompletableFuture<PlayerChatEvent> future) {
|
||||||
|
return future.thenApply(event -> {
|
||||||
|
String content = event.getContent();
|
||||||
|
|
||||||
|
// Filter profanity
|
||||||
|
String filtered = filterProfanityStatic(content);
|
||||||
|
if (!filtered.equals(content)) {
|
||||||
|
event.setContent(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompletableFuture<PlayerChatEvent> processChat(
|
||||||
|
CompletableFuture<PlayerChatEvent> future) {
|
||||||
|
return future.thenApply(event -> {
|
||||||
|
if (event.isCancelled()) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerRef sender = event.getSender();
|
||||||
|
|
||||||
|
// Custom formatter with rank prefix
|
||||||
|
event.setFormatter((playerRef, msg) -> {
|
||||||
|
String prefix = getRankPrefix(playerRef);
|
||||||
|
return Message.raw(prefix + playerRef.getUsername() + ": " + msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
return event;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompletableFuture<PlayerChatEvent> logChat(
|
||||||
|
CompletableFuture<PlayerChatEvent> future) {
|
||||||
|
return future.thenApply(event -> {
|
||||||
|
if (!event.isCancelled()) {
|
||||||
|
logToDatabase(
|
||||||
|
event.getSender().getUuid(),
|
||||||
|
event.getContent(),
|
||||||
|
System.currentTimeMillis()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stubs for methods used in the example
|
||||||
|
private static String filterProfanityStatic(String content) { return content; }
|
||||||
|
private static String getRankPrefix(PlayerRef ref) { return "[Player] "; }
|
||||||
|
private static void logToDatabase(java.util.UUID uuid, String content, long time) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Database Integration Example" section
|
||||||
|
// ============================================
|
||||||
|
public static class ChatDatabasePlugin extends JavaPlugin {
|
||||||
|
|
||||||
|
private final ExecutorService dbExecutor = Executors.newFixedThreadPool(4);
|
||||||
|
private Database database = null; // Stub
|
||||||
|
|
||||||
|
public ChatDatabasePlugin(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
getEventRegistry().registerAsyncGlobal(PlayerChatEvent.class, future -> {
|
||||||
|
return future.thenCompose(event -> {
|
||||||
|
// Run database check on dedicated thread pool
|
||||||
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
|
try {
|
||||||
|
// Check if player is muted in database
|
||||||
|
boolean muted = database.isPlayerMuted(event.getSender().getUuid());
|
||||||
|
if (muted) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
} catch (Exception e) {
|
||||||
|
getLogger().at(Level.SEVERE).log("Database error: " + e.getMessage());
|
||||||
|
return event; // Allow message on DB error
|
||||||
|
}
|
||||||
|
}, dbExecutor);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
dbExecutor.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stub interface
|
||||||
|
private interface Database {
|
||||||
|
boolean isPlayerMuted(java.util.UUID uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Best Practices" safe pattern section
|
||||||
|
// ============================================
|
||||||
|
public void safePatternExample(EventRegistry eventRegistry) {
|
||||||
|
eventRegistry.registerAsyncGlobal(PlayerChatEvent.class, future -> {
|
||||||
|
return future.thenApply(event -> {
|
||||||
|
// Async work here...
|
||||||
|
String processed = processMessage(event.getContent());
|
||||||
|
event.setContent(processed);
|
||||||
|
|
||||||
|
// Safe player notification via PlayerRef
|
||||||
|
PlayerRef sender = event.getSender();
|
||||||
|
World world = Universe.get().getWorld(sender.getWorldUuid());
|
||||||
|
if (world != null) {
|
||||||
|
world.execute(() -> {
|
||||||
|
sender.sendMessage(Message.raw("Message processed"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stub methods used in examples
|
||||||
|
private String filterContent(String content) { return content; }
|
||||||
|
private String filterProfanity(String content) { return content; }
|
||||||
|
private String addChatFormatting(String content) { return content; }
|
||||||
|
private void logChatMessage(PlayerRef sender, String content) {}
|
||||||
|
private boolean checkForSpam(String content) { return false; }
|
||||||
|
private boolean checkForLinks(String content) { return false; }
|
||||||
|
private void riskyOperation(PlayerChatEvent event) {}
|
||||||
|
private boolean checkMuteStatus(java.util.UUID uuid) { return false; }
|
||||||
|
private String processMessage(String content) { return content; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package org.example.docs.events;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/events/event-system.en.md
|
||||||
|
// This file tests that ALL events documentation examples compile correctly
|
||||||
|
|
||||||
|
import com.hypixel.hytale.event.EventPriority;
|
||||||
|
import com.hypixel.hytale.event.EventRegistry;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerDisconnectEvent;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerReadyEvent;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.logger.HytaleLogger;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests event-system.en.md documentation
|
||||||
|
*
|
||||||
|
* IMPORTANT NOTE on Event Registration:
|
||||||
|
* - Events with Void key (like PlayerConnectEvent) can use register(Class, Consumer)
|
||||||
|
* - Events with other key types (like PlayerReadyEvent with String key) must use registerGlobal(Class, Consumer)
|
||||||
|
*/
|
||||||
|
public class EventSystemExample {
|
||||||
|
|
||||||
|
public void registerEvents(EventRegistry eventRegistry, HytaleLogger logger) {
|
||||||
|
// PlayerConnectEvent has Void key, can use simple register()
|
||||||
|
eventRegistry.register(PlayerConnectEvent.class, event -> {
|
||||||
|
PlayerRef playerRef = event.getPlayerRef();
|
||||||
|
logger.at(Level.INFO).log("Player connecting: " + playerRef.getUsername());
|
||||||
|
});
|
||||||
|
|
||||||
|
// PlayerReadyEvent has String key - MUST use registerGlobal()
|
||||||
|
eventRegistry.registerGlobal(PlayerReadyEvent.class, event -> {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
logger.at(Level.INFO).log("Player ready: " + player.getDisplayName());
|
||||||
|
});
|
||||||
|
|
||||||
|
// PlayerDisconnectEvent - check its key type to determine which method to use
|
||||||
|
eventRegistry.registerGlobal(PlayerDisconnectEvent.class, event -> {
|
||||||
|
// Use event methods to access player info
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register with specific priority (only works for Void key events)
|
||||||
|
eventRegistry.register(EventPriority.EARLY, PlayerConnectEvent.class, event -> {
|
||||||
|
// This runs before NORMAL priority handlers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package org.example.docs.gettingstarted;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/getting-started/creating-a-plugin.en.md
|
||||||
|
// This file tests that ALL code examples from the page compile correctly
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests creating-a-plugin.en.md documentation
|
||||||
|
*
|
||||||
|
* From "Main Plugin Class" section (lines 85-118)
|
||||||
|
*/
|
||||||
|
public class CreatingPluginExample extends JavaPlugin {
|
||||||
|
|
||||||
|
public CreatingPluginExample(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
// Called during plugin initialization
|
||||||
|
// Register configs, prepare resources
|
||||||
|
getLogger().at(Level.INFO).log("MyPlugin is setting up!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
// Called when the plugin starts
|
||||||
|
// Register commands, events, entities
|
||||||
|
getLogger().at(Level.INFO).log("MyPlugin has started!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
// Called when the plugin is stopping
|
||||||
|
// Clean up resources
|
||||||
|
getLogger().at(Level.INFO).log("MyPlugin is shutting down!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Logger API" section (lines 129-138)
|
||||||
|
// ============================================
|
||||||
|
public void testLoggerAPI() {
|
||||||
|
String playerName = "TestPlayer";
|
||||||
|
|
||||||
|
// Basic logging
|
||||||
|
getLogger().at(Level.INFO).log("Information message");
|
||||||
|
getLogger().at(Level.WARNING).log("Warning message");
|
||||||
|
getLogger().at(Level.SEVERE).log("Error message");
|
||||||
|
|
||||||
|
// With formatting
|
||||||
|
getLogger().at(Level.INFO).log("Player %s joined the game", playerName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package org.example.docs.gettingstarted;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/getting-started/plugin-lifecycle.en.md
|
||||||
|
// This file tests that ALL code examples from the page compile correctly
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.PluginState;
|
||||||
|
import com.hypixel.hytale.server.core.util.Config;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
// Importing MyConfig from another example
|
||||||
|
import org.example.MyConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests plugin-lifecycle.en.md documentation
|
||||||
|
*/
|
||||||
|
public class PluginLifecycleExample extends JavaPlugin {
|
||||||
|
|
||||||
|
private Config<MyConfig> config;
|
||||||
|
|
||||||
|
public PluginLifecycleExample(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "setup()" section (lines 37-45)
|
||||||
|
// ============================================
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
// Register a configuration file
|
||||||
|
config = withConfig(MyConfig.CODEC);
|
||||||
|
|
||||||
|
getLogger().at(Level.INFO).log("Setup complete!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "start()" section (lines 66-85)
|
||||||
|
// Note: Fixed getCommandRegistry().register() -> registerCommand()
|
||||||
|
// Note: Fixed PlayerRef import path
|
||||||
|
// ============================================
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
// Register commands
|
||||||
|
// getCommandRegistry().registerCommand(new MyCommand());
|
||||||
|
|
||||||
|
// Register events
|
||||||
|
getEventRegistry().register(PlayerConnectEvent.class, this::onPlayerConnect);
|
||||||
|
|
||||||
|
getLogger().at(Level.INFO).log("Plugin started!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onPlayerConnect(PlayerConnectEvent event) {
|
||||||
|
// See PlayerRef documentation for thread-safe player references
|
||||||
|
PlayerRef playerRef = event.getPlayerRef();
|
||||||
|
getLogger().at(Level.INFO).log("Player connecting: " + playerRef.getUsername());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "shutdown()" section (lines 101-109)
|
||||||
|
// ============================================
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
// Save any pending data
|
||||||
|
savePlayerData();
|
||||||
|
|
||||||
|
getLogger().at(Level.INFO).log("Plugin shutdown complete!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void savePlayerData() {
|
||||||
|
// Placeholder for save logic
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// From "Checking Plugin State" section (lines 150-160)
|
||||||
|
// ============================================
|
||||||
|
public void testPluginState() {
|
||||||
|
if (isEnabled()) {
|
||||||
|
// Plugin is running
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDisabled()) {
|
||||||
|
// Plugin is not running
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current state
|
||||||
|
PluginState state = getState();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package org.example.docs.interactions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example code from: hytale-docs/content/interactions/_index.en.md
|
||||||
|
* Tests the Interaction System documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap;
|
||||||
|
import com.hypixel.hytale.protocol.InteractionState;
|
||||||
|
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction;
|
||||||
|
|
||||||
|
public class InteractionsIndexExample {
|
||||||
|
|
||||||
|
// CORRECTED Example from documentation - Get Interaction
|
||||||
|
// Documentation was WRONG: getAsset() takes int, not String!
|
||||||
|
public void getInteractionExample() {
|
||||||
|
// Get the asset map
|
||||||
|
IndexedLookupTableAssetMap<String, Interaction> assetMap = Interaction.getAssetMap();
|
||||||
|
|
||||||
|
// Get index from string ID
|
||||||
|
int index = assetMap.getIndex("my_interaction");
|
||||||
|
|
||||||
|
// Get interaction by index
|
||||||
|
Interaction interaction = assetMap.getAsset(index);
|
||||||
|
|
||||||
|
// Or combined (but check for null!)
|
||||||
|
if (index != Integer.MIN_VALUE) {
|
||||||
|
Interaction myInteraction = assetMap.getAsset(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - InteractionState enum
|
||||||
|
// CORRECTED order: Finished(0), Skip(1), ItemChanged(2), Failed(3), NotFinished(4)
|
||||||
|
public void interactionStateExample() {
|
||||||
|
InteractionState state = InteractionState.NotFinished;
|
||||||
|
|
||||||
|
// Check state
|
||||||
|
switch (state) {
|
||||||
|
case Finished: // value 0
|
||||||
|
break;
|
||||||
|
case Skip: // value 1
|
||||||
|
break;
|
||||||
|
case ItemChanged: // value 2
|
||||||
|
break;
|
||||||
|
case Failed: // value 3
|
||||||
|
break;
|
||||||
|
case NotFinished: // value 4
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package org.example.docs.interactions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example code from: hytale-docs/content/interactions/selectors.en.md
|
||||||
|
* Tests the Selectors documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import com.hypixel.hytale.component.CommandBuffer;
|
||||||
|
import com.hypixel.hytale.component.ComponentAccessor;
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector3d;
|
||||||
|
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector.Selector;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
public class SelectorsExample {
|
||||||
|
|
||||||
|
// Example from documentation - Select Nearby Blocks
|
||||||
|
public void selectNearbyBlocksExample(CommandBuffer<EntityStore> commandBuffer,
|
||||||
|
Ref<EntityStore> attackerRef,
|
||||||
|
double range) {
|
||||||
|
Selector.selectNearbyBlocks(
|
||||||
|
commandBuffer,
|
||||||
|
attackerRef,
|
||||||
|
range,
|
||||||
|
(x, y, z) -> {
|
||||||
|
// Process each block position
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - Select Nearby Blocks from position
|
||||||
|
public void selectNearbyBlocksFromPosition(Vector3d position, double range) {
|
||||||
|
Selector.selectNearbyBlocks(
|
||||||
|
position,
|
||||||
|
range,
|
||||||
|
(x, y, z) -> {
|
||||||
|
// Process each block position
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECTED Example from documentation - Select Nearby Entities
|
||||||
|
// The consumer receives Ref<EntityStore> only (not BiConsumer with Vector4d!)
|
||||||
|
public void selectNearbyEntitiesExample(CommandBuffer<EntityStore> commandBuffer,
|
||||||
|
Ref<EntityStore> attackerRef,
|
||||||
|
double range) {
|
||||||
|
Selector.selectNearbyEntities(
|
||||||
|
commandBuffer,
|
||||||
|
attackerRef,
|
||||||
|
range,
|
||||||
|
(Consumer<Ref<EntityStore>>) entityRef -> {
|
||||||
|
// Process each entity
|
||||||
|
},
|
||||||
|
(Predicate<Ref<EntityStore>>) entityRef -> {
|
||||||
|
// Filter predicate - return true to include
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - Select Nearby Entities from position with ComponentAccessor
|
||||||
|
public void selectNearbyEntitiesFromPosition(ComponentAccessor<EntityStore> componentAccessor,
|
||||||
|
Vector3d position,
|
||||||
|
double range) {
|
||||||
|
Selector.selectNearbyEntities(
|
||||||
|
componentAccessor,
|
||||||
|
position,
|
||||||
|
range,
|
||||||
|
entityRef -> {
|
||||||
|
// Process each entity
|
||||||
|
},
|
||||||
|
entityRef -> true // Filter or null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECTED Example from documentation - Entity Validity Check
|
||||||
|
// Documentation was wrong: entityRef.get().isValid() should be entityRef.isValid()
|
||||||
|
public void entityValidityExample(Ref<EntityStore> entityRef) {
|
||||||
|
// Correct way to check validity
|
||||||
|
if (entityRef.isValid()) {
|
||||||
|
// Safe to access entity components
|
||||||
|
// ... perform operations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,499 @@
|
|||||||
|
package org.example.docs.inventory;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/inventory/containers.en.md
|
||||||
|
// Tests the Containers documentation examples
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.Inventory;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.ItemStack;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.container.SortType;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.MaterialQuantity;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.ResourceQuantity;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.transaction.ItemStackSlotTransaction;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.transaction.SlotTransaction;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.transaction.MoveTransaction;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.transaction.ClearTransaction;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.transaction.ListTransaction;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.transaction.MaterialTransaction;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.transaction.ResourceTransaction;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.transaction.TagTransaction;
|
||||||
|
import com.hypixel.hytale.event.EventPriority;
|
||||||
|
import com.hypixel.hytale.protocol.SmartMoveType;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests Containers documentation
|
||||||
|
*/
|
||||||
|
public class ContainersExample extends JavaPlugin {
|
||||||
|
|
||||||
|
public ContainersExample(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
// All testing done in methods below
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Inventory Structure - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void inventoryStructure(Player player) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// ItemContainer Basics - Reading Items - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void readingItems(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
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) 0);
|
||||||
|
if (!ItemStack.isEmpty(stack)) {
|
||||||
|
String itemId = stack.getItemId();
|
||||||
|
int quantity = stack.getQuantity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Setting Items - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void settingItems(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void addingItems(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void removingItems(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
ItemContainer container = inv.getStorage();
|
||||||
|
|
||||||
|
// 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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void activeSlots(Player player) {
|
||||||
|
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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void movingItems(Player player) {
|
||||||
|
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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void smartMoving(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
|
||||||
|
// 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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void iteratingContainers(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public boolean hasItems(ItemContainer container, String itemId, int amount) {
|
||||||
|
int count = container.countItemStacks(
|
||||||
|
stack -> stack.getItemId().equals(itemId)
|
||||||
|
);
|
||||||
|
return count >= amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void clearingContainers(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
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 - CORRECT
|
||||||
|
// Available SortType values: NAME, TYPE, RARITY (no QUANTITY!)
|
||||||
|
// =============================================
|
||||||
|
public void sorting(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void containerEvents(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
ItemContainer container = inv.getStorage();
|
||||||
|
|
||||||
|
// Register for container change events
|
||||||
|
container.registerChangeEvent(event -> {
|
||||||
|
ItemContainer changedContainer = event.container();
|
||||||
|
// Transaction transaction = event.transaction(); // Possible name collision
|
||||||
|
|
||||||
|
getLogger().at(Level.INFO).log("Container changed!");
|
||||||
|
});
|
||||||
|
|
||||||
|
// With priority
|
||||||
|
container.registerChangeEvent(EventPriority.EARLY, event -> {
|
||||||
|
// Handle early
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Bulk Operations - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void bulkOperations(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
ItemContainer container = inv.getStorage();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Material and Resource Removal - CORRECT
|
||||||
|
// MaterialQuantity constructor: (itemId, resourceTypeId, tag, quantity, metadata)
|
||||||
|
// At least one of itemId, resourceTypeId, or tag must be non-null
|
||||||
|
// =============================================
|
||||||
|
public void materialResourceRemoval(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
ItemContainer container = inv.getStorage();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
int tagIndex = 0;
|
||||||
|
int quantity = 5;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Advanced Move Operations - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void advancedMoveOperations(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
ItemContainer storage = inv.getStorage();
|
||||||
|
ItemContainer hotbar = inv.getHotbar();
|
||||||
|
ItemContainer backpack = inv.getBackpack();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
storage.combineItemStacksIntoSlot(storage, (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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void replaceOperations(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
ItemContainer container = inv.getStorage();
|
||||||
|
|
||||||
|
ItemStack expectedItem = new ItemStack("iron_sword", 1);
|
||||||
|
ItemStack newItem = new ItemStack("diamond_sword", 1);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Safe pattern for working with containers - CORRECT
|
||||||
|
// =============================================
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
package org.example.docs.inventory;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/inventory/_index.en.md and itemstacks.en.md
|
||||||
|
// Tests the inventory documentation examples
|
||||||
|
//
|
||||||
|
// FINDINGS:
|
||||||
|
// - player.getItemInHand() does NOT exist - use player.getInventory().getItemInHand()
|
||||||
|
// - item.getName() does NOT exist on Item - use item.getId() or stack.getItemId()
|
||||||
|
// - getQuantity() is correct (not getCount())
|
||||||
|
// - withQuantity(0) returns null
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.Inventory;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.ItemStack;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.item.config.Item;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.codec.Codec;
|
||||||
|
import com.hypixel.hytale.codec.KeyedCodec;
|
||||||
|
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests inventory documentation
|
||||||
|
*/
|
||||||
|
public class InventoryExample extends JavaPlugin {
|
||||||
|
|
||||||
|
public InventoryExample(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
getEventRegistry().register(PlayerConnectEvent.class, event -> {
|
||||||
|
PlayerRef playerRef = event.getPlayerRef();
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
|
||||||
|
if (player != null) {
|
||||||
|
// =============================================
|
||||||
|
// Getting Inventory - CORRECT
|
||||||
|
// =============================================
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Getting Item in Hand - CORRECT pattern
|
||||||
|
// Note: player.getItemInHand() does NOT exist!
|
||||||
|
// Must use inventory method
|
||||||
|
// =============================================
|
||||||
|
ItemStack hand = inv.getItemInHand();
|
||||||
|
|
||||||
|
// Check if item exists
|
||||||
|
if (hand != null && !hand.isEmpty()) {
|
||||||
|
// Item.getName() does NOT exist!
|
||||||
|
// Use getId() or getItemId()
|
||||||
|
String itemId = hand.getItemId();
|
||||||
|
getLogger().at(Level.INFO).log("Holding: " + itemId);
|
||||||
|
|
||||||
|
// Or via Item asset
|
||||||
|
Item item = hand.getItem();
|
||||||
|
String id = item.getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// ItemStack Creation - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void itemStackCreation() {
|
||||||
|
// Create by item ID
|
||||||
|
ItemStack sword = new ItemStack("iron_sword");
|
||||||
|
|
||||||
|
// Create with quantity
|
||||||
|
ItemStack materials = new ItemStack("wood_plank", 64);
|
||||||
|
|
||||||
|
// Create with full parameters
|
||||||
|
ItemStack damagedSword = new ItemStack("iron_sword", 1, 50.0, 100.0, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// ItemStack Properties - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void itemStackProperties() {
|
||||||
|
ItemStack stack = new ItemStack("iron_sword", 1);
|
||||||
|
|
||||||
|
// Get the item ID
|
||||||
|
String itemId = stack.getItemId();
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
boolean broken = stack.isBroken();
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Modifying ItemStacks - CORRECT (immutable pattern)
|
||||||
|
// =============================================
|
||||||
|
public void modifyingItemStacks() {
|
||||||
|
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
|
||||||
|
ItemStack newState = stack.withState("activated");
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// EMPTY constant - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void emptyStack() {
|
||||||
|
// Static empty instance (singleton)
|
||||||
|
ItemStack empty = ItemStack.EMPTY;
|
||||||
|
|
||||||
|
// Static helper method (handles null and empty)
|
||||||
|
ItemStack stack = new ItemStack("iron_sword", 1);
|
||||||
|
if (ItemStack.isEmpty(stack)) {
|
||||||
|
// Handles null and empty stacks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Comparing ItemStacks - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void comparingItemStacks() {
|
||||||
|
ItemStack a = new ItemStack("iron_sword", 1);
|
||||||
|
ItemStack b = new ItemStack("iron_sword", 5);
|
||||||
|
ItemStack c = new ItemStack("diamond_sword", 1);
|
||||||
|
|
||||||
|
// Check if stackable
|
||||||
|
boolean canStack = a.isStackableWith(b);
|
||||||
|
|
||||||
|
// Check equivalent type
|
||||||
|
boolean sameType = a.isEquivalentType(b);
|
||||||
|
|
||||||
|
// Check same item type only
|
||||||
|
boolean sameItem = ItemStack.isSameItemType(a, c);
|
||||||
|
|
||||||
|
// Static helpers (handle nulls safely)
|
||||||
|
ItemStack.isStackableWith(a, b);
|
||||||
|
ItemStack.isEquivalentType(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Working with Metadata - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void metadataExample() {
|
||||||
|
ItemStack stack = new ItemStack("iron_sword", 1);
|
||||||
|
|
||||||
|
// 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 = withOwner.getFromMetadataOrNull(OWNER_KEY);
|
||||||
|
|
||||||
|
// Read with key and codec
|
||||||
|
Integer level = stack.getFromMetadataOrNull("Level", Codec.INTEGER);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Common Patterns - CORRECT
|
||||||
|
// =============================================
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,289 @@
|
|||||||
|
package org.example.docs.inventory;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/inventory/itemstacks.en.md
|
||||||
|
// Tests the ItemStacks documentation examples
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.ItemStack;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.item.config.Item;
|
||||||
|
import com.hypixel.hytale.codec.Codec;
|
||||||
|
import com.hypixel.hytale.codec.KeyedCodec;
|
||||||
|
import org.bson.BsonDocument;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests ItemStacks documentation
|
||||||
|
*/
|
||||||
|
public class ItemStacksExample extends JavaPlugin {
|
||||||
|
|
||||||
|
public ItemStacksExample(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
// All testing done in methods below
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Creating ItemStacks - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void creatingItemStacks() {
|
||||||
|
// 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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void itemStackProperties() {
|
||||||
|
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 - CORRECT (immutable pattern)
|
||||||
|
// =============================================
|
||||||
|
public void modifyingItemStacks() {
|
||||||
|
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
|
||||||
|
BsonDocument metadataDocument = new BsonDocument();
|
||||||
|
ItemStack withMeta = stack.withMetadata(metadataDocument);
|
||||||
|
|
||||||
|
// Add specific metadata value
|
||||||
|
ItemStack tagged = stack.withMetadata("CustomKey", Codec.STRING, "CustomValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// The EMPTY Constant - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void emptyConstant() {
|
||||||
|
// Static empty instance (singleton)
|
||||||
|
ItemStack empty = ItemStack.EMPTY;
|
||||||
|
|
||||||
|
ItemStack stack = new ItemStack("iron_sword", 1);
|
||||||
|
// Check for empty
|
||||||
|
if (stack.isEmpty()) {
|
||||||
|
// Stack is empty
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static helper method
|
||||||
|
if (ItemStack.isEmpty(stack)) {
|
||||||
|
// Handles null and empty stacks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Comparing ItemStacks - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void comparingItemStacks() {
|
||||||
|
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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void metadataExample() {
|
||||||
|
ItemStack stack = new ItemStack("iron_sword", 1);
|
||||||
|
|
||||||
|
// 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 = withOwner.getFromMetadataOrNull(OWNER_KEY);
|
||||||
|
|
||||||
|
// Read with key and codec
|
||||||
|
Integer level = stack.getFromMetadataOrNull("Level", Codec.INTEGER);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Block Items - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void blockItems() {
|
||||||
|
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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
|
||||||
|
// Consuming Items
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void bestPractices() {
|
||||||
|
ItemStack stack = new ItemStack("iron_sword", 1);
|
||||||
|
int newQuantity = 10;
|
||||||
|
|
||||||
|
// 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!
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,362 @@
|
|||||||
|
package org.example.docs.inventory;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/inventory/transactions.en.md
|
||||||
|
// Tests the Transactions documentation examples
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.Inventory;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.ItemStack;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.transaction.Transaction;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.transaction.SlotTransaction;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.transaction.ItemStackSlotTransaction;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.transaction.MoveTransaction;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.transaction.ListTransaction;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.transaction.ActionType;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.transaction.MoveType;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests Transactions documentation
|
||||||
|
*/
|
||||||
|
public class TransactionsExample extends JavaPlugin {
|
||||||
|
|
||||||
|
public TransactionsExample(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
// All testing done in methods below
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// SlotTransaction - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void slotTransactionExample(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
ItemContainer container = inv.getStorage();
|
||||||
|
|
||||||
|
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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void itemStackSlotTransactionExample(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
ItemContainer container = inv.getStorage();
|
||||||
|
|
||||||
|
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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void itemStackTransactionExample(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
ItemContainer container = inv.getStorage();
|
||||||
|
|
||||||
|
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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void moveTransactionExample(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
ItemContainer storage = inv.getStorage();
|
||||||
|
ItemContainer hotbar = inv.getHotbar();
|
||||||
|
|
||||||
|
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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void listTransactionExample(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
ItemContainer container = inv.getStorage();
|
||||||
|
|
||||||
|
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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void actionTypeExample(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
ItemContainer container = inv.getStorage();
|
||||||
|
|
||||||
|
SlotTransaction transaction = container.removeItemStackFromSlot((short) 0);
|
||||||
|
if (transaction.succeeded()) {
|
||||||
|
ActionType action = transaction.getAction();
|
||||||
|
|
||||||
|
// Check action characteristics
|
||||||
|
if (action.isAdd()) { /* operation adds items */ }
|
||||||
|
if (action.isRemove()) { /* operation removes items */ }
|
||||||
|
if (action.isDestroy()) { /* operation destroys slot contents */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// MoveType - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void moveTypeExample(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
ItemContainer storage = inv.getStorage();
|
||||||
|
ItemContainer hotbar = inv.getHotbar();
|
||||||
|
|
||||||
|
MoveTransaction<SlotTransaction> transaction = storage.moveItemStackFromSlotToSlot(
|
||||||
|
(short) 0,
|
||||||
|
32,
|
||||||
|
hotbar,
|
||||||
|
(short) 0
|
||||||
|
);
|
||||||
|
|
||||||
|
if (transaction.succeeded()) {
|
||||||
|
MoveType moveType = transaction.getMoveType();
|
||||||
|
// MOVE_TO_SELF: Items being moved TO this container
|
||||||
|
// MOVE_FROM_SELF: Items being moved FROM this container
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Common Patterns - Check Before Modify - CORRECT
|
||||||
|
// =============================================
|
||||||
|
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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void transactionOptions(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
ItemContainer container = inv.getStorage();
|
||||||
|
ItemStack item = new ItemStack("stone", 64);
|
||||||
|
|
||||||
|
// 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 - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void bestPractices(Player player) {
|
||||||
|
Inventory inv = player.getInventory();
|
||||||
|
ItemContainer container = inv.getStorage();
|
||||||
|
ItemStack item = new ItemStack("stone", 64);
|
||||||
|
|
||||||
|
// 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!
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,301 @@
|
|||||||
|
package org.example.docs.permissions;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/permissions/_index.en.md
|
||||||
|
// Tests the Permissions documentation examples
|
||||||
|
//
|
||||||
|
// FINDINGS:
|
||||||
|
// - HytaleServer.get().getPermissionsModule() does NOT exist - use PermissionsModule.get()
|
||||||
|
// - PlayerGroupEvent.isAdded() does NOT exist - use PlayerGroupEvent.Added or PlayerGroupEvent.Removed
|
||||||
|
// - PlayerGroupEvent.getGroup() does NOT exist - use getGroupName()
|
||||||
|
// - GroupPermissionChangeEvent uses separate classes: Added and Removed
|
||||||
|
// - PermissionProvider interface does NOT have hasPermission(UUID, String)
|
||||||
|
// - The interface has: getUserPermissions(), getGroupPermissions(), getGroupsForUser(), etc.
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.permissions.PermissionsModule;
|
||||||
|
import com.hypixel.hytale.server.core.permissions.provider.PermissionProvider;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.permissions.PlayerGroupEvent;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.permissions.GroupPermissionChangeEvent;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.permissions.PlayerPermissionChangeEvent;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.CommandContext;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests Permissions documentation
|
||||||
|
*/
|
||||||
|
public class PermissionsExample extends JavaPlugin {
|
||||||
|
|
||||||
|
public PermissionsExample(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
// Register permission events
|
||||||
|
registerEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void start() {
|
||||||
|
// Add custom permission provider
|
||||||
|
PermissionsModule permissions = PermissionsModule.get();
|
||||||
|
permissions.addProvider(new VIPPermissionProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Accessing Permissions Module - CORRECT
|
||||||
|
// Note: HytaleServer.get().getPermissionsModule() does NOT exist!
|
||||||
|
// =============================================
|
||||||
|
public void accessPermissionsModule() {
|
||||||
|
// CORRECT way to access
|
||||||
|
PermissionsModule permissions = PermissionsModule.get();
|
||||||
|
|
||||||
|
// WRONG - this method does not exist:
|
||||||
|
// PermissionsModule permissions = HytaleServer.get().getPermissionsModule();
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Checking Permissions - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void checkingPermissions(Player player, CommandContext ctx) {
|
||||||
|
PermissionsModule permissions = PermissionsModule.get();
|
||||||
|
UUID playerUuid = player.getPlayerRef().getUuid();
|
||||||
|
|
||||||
|
// Check if player has permission
|
||||||
|
boolean canEdit = permissions.hasPermission(playerUuid, "hytale.editor.asset");
|
||||||
|
|
||||||
|
// Check with default value
|
||||||
|
boolean canBuild = permissions.hasPermission(playerUuid, "myplugin.build", false);
|
||||||
|
|
||||||
|
// Check in command context
|
||||||
|
if (permissions.hasPermission(ctx.sender().getUuid(), "myplugin.admin")) {
|
||||||
|
// Admin-only logic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// User Permissions - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void userPermissions(Player player) {
|
||||||
|
PermissionsModule permissions = PermissionsModule.get();
|
||||||
|
UUID playerUuid = player.getPlayerRef().getUuid();
|
||||||
|
|
||||||
|
// Add permissions
|
||||||
|
permissions.addUserPermission(playerUuid, Set.of(
|
||||||
|
"myplugin.feature.create",
|
||||||
|
"myplugin.feature.delete"
|
||||||
|
));
|
||||||
|
|
||||||
|
// Remove permissions
|
||||||
|
permissions.removeUserPermission(playerUuid, Set.of(
|
||||||
|
"myplugin.feature.delete"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Group Permissions - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void groupPermissions(Player player) {
|
||||||
|
PermissionsModule permissions = PermissionsModule.get();
|
||||||
|
UUID playerUuid = player.getPlayerRef().getUuid();
|
||||||
|
|
||||||
|
// Add player to a group
|
||||||
|
permissions.addUserToGroup(playerUuid, "moderators");
|
||||||
|
|
||||||
|
// Remove player from a group
|
||||||
|
permissions.removeUserFromGroup(playerUuid, "moderators");
|
||||||
|
|
||||||
|
// Get all groups for a player
|
||||||
|
Set<String> groups = permissions.getGroupsForUser(playerUuid);
|
||||||
|
|
||||||
|
// Add permissions to a group
|
||||||
|
permissions.addGroupPermission("moderators", Set.of(
|
||||||
|
"server.kick",
|
||||||
|
"server.mute"
|
||||||
|
));
|
||||||
|
|
||||||
|
// Remove permissions from a group
|
||||||
|
permissions.removeGroupPermission("moderators", Set.of(
|
||||||
|
"server.mute"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Permission Events - CORRECT
|
||||||
|
// Note: Events use subclasses like PlayerGroupEvent.Added
|
||||||
|
// =============================================
|
||||||
|
public void registerEvents() {
|
||||||
|
// Player ADDED to group
|
||||||
|
getEventRegistry().register(PlayerGroupEvent.Added.class, event -> {
|
||||||
|
UUID playerUuid = event.getPlayerUuid();
|
||||||
|
String group = event.getGroupName(); // NOT getGroup()!
|
||||||
|
getLogger().at(Level.INFO).log("Player joined group: " + group);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Player REMOVED from group
|
||||||
|
getEventRegistry().register(PlayerGroupEvent.Removed.class, event -> {
|
||||||
|
UUID playerUuid = event.getPlayerUuid();
|
||||||
|
String group = event.getGroupName();
|
||||||
|
getLogger().at(Level.INFO).log("Player left group: " + group);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Group permissions ADDED
|
||||||
|
getEventRegistry().register(GroupPermissionChangeEvent.Added.class, event -> {
|
||||||
|
String group = event.getGroupName();
|
||||||
|
Set<String> added = event.getAddedPermissions();
|
||||||
|
getLogger().at(Level.INFO).log("Group " + group + " got permissions: " + added);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Group permissions REMOVED
|
||||||
|
getEventRegistry().register(GroupPermissionChangeEvent.Removed.class, event -> {
|
||||||
|
String group = event.getGroupName();
|
||||||
|
Set<String> removed = event.getRemovedPermissions();
|
||||||
|
getLogger().at(Level.INFO).log("Group " + group + " lost permissions: " + removed);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Player permissions ADDED
|
||||||
|
getEventRegistry().register(PlayerPermissionChangeEvent.PermissionsAdded.class, event -> {
|
||||||
|
UUID playerUuid = event.getPlayerUuid();
|
||||||
|
Set<String> added = event.getAddedPermissions();
|
||||||
|
getLogger().at(Level.INFO).log("Player got permissions: " + added);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Player permissions REMOVED
|
||||||
|
getEventRegistry().register(PlayerPermissionChangeEvent.PermissionsRemoved.class, event -> {
|
||||||
|
UUID playerUuid = event.getPlayerUuid();
|
||||||
|
Set<String> removed = event.getRemovedPermissions();
|
||||||
|
getLogger().at(Level.INFO).log("Player lost permissions: " + removed);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Custom Permission Provider - CORRECT interface
|
||||||
|
// Note: PermissionProvider does NOT have hasPermission(UUID, String)!
|
||||||
|
// =============================================
|
||||||
|
public static class VIPPermissionProvider implements PermissionProvider {
|
||||||
|
private final Map<UUID, Set<String>> vipPermissions = new ConcurrentHashMap<>();
|
||||||
|
private final Map<UUID, Set<String>> vipGroups = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nonnull
|
||||||
|
public String getName() {
|
||||||
|
return "VIPPermissionProvider";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addUserPermissions(@Nonnull UUID uuid, @Nonnull Set<String> permissions) {
|
||||||
|
vipPermissions.computeIfAbsent(uuid, k -> new HashSet<>()).addAll(permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUserPermissions(@Nonnull UUID uuid, @Nonnull Set<String> permissions) {
|
||||||
|
Set<String> userPerms = vipPermissions.get(uuid);
|
||||||
|
if (userPerms != null) {
|
||||||
|
userPerms.removeAll(permissions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getUserPermissions(@Nonnull UUID uuid) {
|
||||||
|
return vipPermissions.getOrDefault(uuid, Collections.emptySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addGroupPermissions(@Nonnull String group, @Nonnull Set<String> permissions) {
|
||||||
|
// Not implemented in this example
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeGroupPermissions(@Nonnull String group, @Nonnull Set<String> permissions) {
|
||||||
|
// Not implemented in this example
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getGroupPermissions(@Nonnull String group) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addUserToGroup(@Nonnull UUID uuid, @Nonnull String group) {
|
||||||
|
vipGroups.computeIfAbsent(uuid, k -> new HashSet<>()).add(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUserFromGroup(@Nonnull UUID uuid, @Nonnull String group) {
|
||||||
|
Set<String> groups = vipGroups.get(uuid);
|
||||||
|
if (groups != null) {
|
||||||
|
groups.remove(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getGroupsForUser(@Nonnull UUID uuid) {
|
||||||
|
return vipGroups.getOrDefault(uuid, Collections.emptySet());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Role-Based Access Example - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public class RoleManager {
|
||||||
|
private final PermissionsModule permissions;
|
||||||
|
|
||||||
|
public RoleManager() {
|
||||||
|
this.permissions = PermissionsModule.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void promoteToModerator(UUID playerUuid) {
|
||||||
|
permissions.addUserToGroup(playerUuid, "moderators");
|
||||||
|
permissions.addUserPermission(playerUuid, Set.of(
|
||||||
|
"server.kick",
|
||||||
|
"server.mute",
|
||||||
|
"server.warn"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void demoteFromModerator(UUID playerUuid) {
|
||||||
|
permissions.removeUserFromGroup(playerUuid, "moderators");
|
||||||
|
permissions.removeUserPermission(playerUuid, Set.of(
|
||||||
|
"server.kick",
|
||||||
|
"server.mute",
|
||||||
|
"server.warn"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canModerate(UUID playerUuid) {
|
||||||
|
return permissions.getGroupsForUser(playerUuid).contains("moderators");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Feature Gating Example - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void useFeature(Player player, String featureName) {
|
||||||
|
UUID uuid = player.getPlayerRef().getUuid();
|
||||||
|
PermissionsModule perms = PermissionsModule.get();
|
||||||
|
|
||||||
|
String permission = "myplugin.feature." + featureName;
|
||||||
|
if (!perms.hasPermission(uuid, permission)) {
|
||||||
|
player.sendMessage(Message.translation("error.no_permission"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute feature
|
||||||
|
executeFeature(player, featureName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeFeature(Player player, String featureName) {
|
||||||
|
// Feature implementation
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package org.example.docs.reference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example code from: hytale-docs/content/reference/all-registries.en.md
|
||||||
|
* Tests the All Registries documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import com.hypixel.hytale.assetstore.map.DefaultAssetMap;
|
||||||
|
import com.hypixel.hytale.event.EventPriority;
|
||||||
|
import com.hypixel.hytale.event.EventRegistry;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.item.config.Item;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.AbstractCommand;
|
||||||
|
import com.hypixel.hytale.server.core.command.system.CommandContext;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerDisconnectEvent;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class AllRegistriesExample {
|
||||||
|
|
||||||
|
// Placeholder - cannot extend JavaPlugin directly in test
|
||||||
|
private EventRegistry events;
|
||||||
|
private com.hypixel.hytale.server.core.command.system.CommandRegistry commands;
|
||||||
|
private java.util.logging.Logger logger;
|
||||||
|
|
||||||
|
// Example from documentation - Event Registration
|
||||||
|
public void eventRegistryExample() {
|
||||||
|
// Use getPlayerRef() - getPlayer() is deprecated
|
||||||
|
events.register(PlayerConnectEvent.class, event -> {
|
||||||
|
PlayerRef playerRef = event.getPlayerRef();
|
||||||
|
playerRef.sendMessage(Message.raw("Welcome!"));
|
||||||
|
});
|
||||||
|
|
||||||
|
events.register(EventPriority.FIRST, PlayerDisconnectEvent.class, event -> {
|
||||||
|
PlayerRef playerRef = event.getPlayerRef();
|
||||||
|
logger.log(Level.INFO, playerRef.getUsername() + " left");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - Command Registration
|
||||||
|
// AbstractCommand.execute() returns CompletableFuture<Void>
|
||||||
|
public void commandRegistryExample() {
|
||||||
|
commands.registerCommand(new AbstractCommand("hello", "my_plugin.commands.hello.description") {
|
||||||
|
@Override
|
||||||
|
protected CompletableFuture<Void> execute(CommandContext ctx) {
|
||||||
|
ctx.sender().sendMessage(Message.raw("Hello, World!"));
|
||||||
|
return null; // Return null for synchronous completion
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECTED Example from documentation - Accessing Assets
|
||||||
|
// Item.getAssetMap() returns DefaultAssetMap which takes String keys directly
|
||||||
|
// (Unlike Interaction which uses IndexedLookupTableAssetMap with int indices)
|
||||||
|
public void accessingAssetsExample() {
|
||||||
|
// Get items by string ID directly
|
||||||
|
DefaultAssetMap<String, Item> itemMap = Item.getAssetMap();
|
||||||
|
Item sword = itemMap.getAsset("iron_sword");
|
||||||
|
|
||||||
|
// Get block types - also uses string key directly
|
||||||
|
DefaultAssetMap<String, BlockType> blockMap = BlockType.getAssetMap();
|
||||||
|
BlockType stone = blockMap.getAsset("stone");
|
||||||
|
}
|
||||||
|
}
|
||||||
244
app/src/main/java/org/example/docs/tasks/TasksExample.java
Normal file
244
app/src/main/java/org/example/docs/tasks/TasksExample.java
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
package org.example.docs.tasks;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/tasks/_index.en.md, task-registry.en.md, async-operations.en.md
|
||||||
|
// Tests the Tasks documentation examples
|
||||||
|
//
|
||||||
|
// FINDINGS:
|
||||||
|
// - TaskRegistry API is correctly documented: registerTask(CompletableFuture<Void>) and registerTask(ScheduledFuture<Void>)
|
||||||
|
// - Documentation correctly warns that runAsync(), runLater(), runRepeating() do NOT exist
|
||||||
|
// - Standard Java concurrency (CompletableFuture, ScheduledExecutorService) is the correct approach
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.universe.Universe;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
import com.hypixel.hytale.server.core.task.TaskRegistration;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests Tasks documentation
|
||||||
|
*/
|
||||||
|
public class TasksExample extends JavaPlugin {
|
||||||
|
|
||||||
|
private ScheduledExecutorService scheduler;
|
||||||
|
|
||||||
|
public TasksExample(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
// Register player connect event for async data loading demo
|
||||||
|
getEventRegistry().register(PlayerConnectEvent.class, event -> {
|
||||||
|
PlayerRef playerRef = event.getPlayerRef();
|
||||||
|
World world = event.getWorld();
|
||||||
|
loadPlayerData(playerRef, world);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void start() {
|
||||||
|
// Create scheduler for repeating tasks
|
||||||
|
scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
|
||||||
|
// Schedule auto-save every 5 minutes
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ScheduledFuture<Void> saveTask = (ScheduledFuture<Void>) scheduler.scheduleAtFixedRate(
|
||||||
|
() -> {
|
||||||
|
saveAllData();
|
||||||
|
getLogger().at(Level.INFO).log("Auto-save complete");
|
||||||
|
},
|
||||||
|
5, // Initial delay
|
||||||
|
5, // Period
|
||||||
|
TimeUnit.MINUTES
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register for tracking
|
||||||
|
getTaskRegistry().registerTask(saveTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void shutdown() {
|
||||||
|
if (scheduler != null) {
|
||||||
|
scheduler.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Basic Async Pattern - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void basicAsyncExample(Player player) {
|
||||||
|
PlayerRef playerRef = player.getPlayerRef();
|
||||||
|
World world = player.getWorld();
|
||||||
|
|
||||||
|
CompletableFuture<Void> task = CompletableFuture.runAsync(() -> {
|
||||||
|
// This runs on a background thread
|
||||||
|
// Do heavy work here
|
||||||
|
String result = "Heavy computation result";
|
||||||
|
|
||||||
|
// Return to world thread for game state changes
|
||||||
|
world.execute(() -> {
|
||||||
|
playerRef.sendMessage(Message.raw("Result: " + result));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register with TaskRegistry for tracking
|
||||||
|
getTaskRegistry().registerTask(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Delayed Task - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void delayedTaskExample(PlayerRef playerRef, World world) {
|
||||||
|
// Run after 3 seconds
|
||||||
|
CompletableFuture.delayedExecutor(3, TimeUnit.SECONDS)
|
||||||
|
.execute(() -> {
|
||||||
|
world.execute(() -> {
|
||||||
|
playerRef.sendMessage(Message.raw("3 seconds have passed!"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Async Data Loading - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void loadPlayerData(PlayerRef playerRef, World world) {
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
// Load from database (blocking I/O is OK here)
|
||||||
|
String data = simulateDatabaseLoad(playerRef.getUuid());
|
||||||
|
|
||||||
|
// Return to world thread for game state changes
|
||||||
|
world.execute(() -> {
|
||||||
|
Ref<EntityStore> ref = playerRef.getReference();
|
||||||
|
if (ref != null && ref.isValid()) {
|
||||||
|
playerRef.sendMessage(Message.raw("Data loaded: " + data));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Countdown Timer - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void startCountdown(PlayerRef playerRef, int seconds) {
|
||||||
|
World world = Universe.get().getWorld(playerRef.getWorldUuid());
|
||||||
|
if (world == null) return;
|
||||||
|
|
||||||
|
AtomicInteger remaining = new AtomicInteger(seconds);
|
||||||
|
|
||||||
|
ScheduledExecutorService countdownScheduler = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
countdownScheduler.scheduleAtFixedRate(() -> {
|
||||||
|
int count = remaining.decrementAndGet();
|
||||||
|
|
||||||
|
world.execute(() -> {
|
||||||
|
if (count > 0) {
|
||||||
|
playerRef.sendMessage(Message.raw("Starting in: " + count));
|
||||||
|
} else {
|
||||||
|
playerRef.sendMessage(Message.raw("Go!"));
|
||||||
|
countdownScheduler.shutdown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 0, 1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Thread-Safe Collections - CORRECT
|
||||||
|
// =============================================
|
||||||
|
private final Map<UUID, String> playerData = new ConcurrentHashMap<>();
|
||||||
|
private final java.util.Set<UUID> processing = ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
|
public void processPlayer(PlayerRef playerRef, World world) {
|
||||||
|
UUID uuid = playerRef.getUuid();
|
||||||
|
|
||||||
|
// Prevent duplicate processing
|
||||||
|
if (!processing.add(uuid)) {
|
||||||
|
return; // Already processing
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
String data = simulateHeavyComputation(uuid);
|
||||||
|
playerData.put(uuid, data);
|
||||||
|
|
||||||
|
world.execute(() -> {
|
||||||
|
playerRef.sendMessage(Message.raw("Processing complete!"));
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
processing.remove(uuid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Error Handling - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void safeAsyncOperation(PlayerRef playerRef, World world) {
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
riskyOperation(playerRef.getUuid());
|
||||||
|
|
||||||
|
world.execute(() -> {
|
||||||
|
playerRef.sendMessage(Message.raw("Operation successful!"));
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
getLogger().at(Level.SEVERE).log("Async operation failed: " + e.getMessage());
|
||||||
|
|
||||||
|
world.execute(() -> {
|
||||||
|
playerRef.sendMessage(Message.raw("Operation failed. Please try again."));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Chaining Async Operations - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void chainedOperations(PlayerRef playerRef, World world) {
|
||||||
|
CompletableFuture
|
||||||
|
.supplyAsync(() -> {
|
||||||
|
// First async operation
|
||||||
|
return fetchFromDatabase(playerRef.getUuid());
|
||||||
|
})
|
||||||
|
.thenApplyAsync(data -> {
|
||||||
|
// Second async operation
|
||||||
|
return processData(data);
|
||||||
|
})
|
||||||
|
.thenAccept(result -> {
|
||||||
|
// Return to world thread with final result
|
||||||
|
world.execute(() -> {
|
||||||
|
playerRef.sendMessage(Message.raw("Final result: " + result));
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.exceptionally(e -> {
|
||||||
|
getLogger().at(Level.SEVERE).log("Chain failed: " + e.getMessage());
|
||||||
|
world.execute(() -> {
|
||||||
|
playerRef.sendMessage(Message.raw("Operation failed."));
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
private void saveAllData() { }
|
||||||
|
private String simulateDatabaseLoad(UUID uuid) { return "data"; }
|
||||||
|
private String simulateHeavyComputation(UUID uuid) { return "result"; }
|
||||||
|
private void riskyOperation(UUID uuid) throws Exception { }
|
||||||
|
private String fetchFromDatabase(UUID uuid) { return "data"; }
|
||||||
|
private String processData(String data) { return "processed"; }
|
||||||
|
}
|
||||||
208
app/src/main/java/org/example/docs/ui/CustomPagesExample.java
Normal file
208
app/src/main/java/org/example/docs/ui/CustomPagesExample.java
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
package org.example.docs.ui;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example code from: hytale-docs/content/ui/custom-pages.en.md
|
||||||
|
* Tests the Custom Pages documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.component.Store;
|
||||||
|
import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime;
|
||||||
|
import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType;
|
||||||
|
import com.hypixel.hytale.protocol.packets.interface_.Page;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.player.pages.CustomUIPage;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.player.pages.PageManager;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.player.windows.ContainerWindow;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
|
||||||
|
import com.hypixel.hytale.server.core.ui.builder.EventData;
|
||||||
|
import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder;
|
||||||
|
import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CustomPagesExample {
|
||||||
|
|
||||||
|
// Example from documentation - WelcomePage
|
||||||
|
public static class WelcomePage extends CustomUIPage {
|
||||||
|
|
||||||
|
public WelcomePage(PlayerRef playerRef) {
|
||||||
|
super(playerRef, CustomPageLifetime.CanDismiss);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void build(Ref<EntityStore> ref, UICommandBuilder builder,
|
||||||
|
UIEventBuilder events, Store<EntityStore> store) {
|
||||||
|
// Load a UI template from assets
|
||||||
|
builder.append("ui/welcome_screen");
|
||||||
|
|
||||||
|
// Set dynamic values
|
||||||
|
builder.set("#player_name", playerRef.getUsername());
|
||||||
|
builder.set("#server_name", "My Server");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - MenuPage with events
|
||||||
|
public static class MenuPage extends CustomUIPage {
|
||||||
|
|
||||||
|
public MenuPage(PlayerRef ref) {
|
||||||
|
super(ref, CustomPageLifetime.CanDismiss);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void build(Ref<EntityStore> ref, UICommandBuilder builder,
|
||||||
|
UIEventBuilder events, Store<EntityStore> store) {
|
||||||
|
builder.append("ui/main_menu");
|
||||||
|
|
||||||
|
// Use addEventBinding with CustomUIEventBindingType and EventData
|
||||||
|
events.addEventBinding(CustomUIEventBindingType.Activating, "#play_button",
|
||||||
|
EventData.of("Action", "play"));
|
||||||
|
events.addEventBinding(CustomUIEventBindingType.Activating, "#quit_button",
|
||||||
|
EventData.of("Action", "quit"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleDataEvent(Ref<EntityStore> ref, Store<EntityStore> store, String rawData) {
|
||||||
|
// rawData contains JSON like: {"Action":"play"}
|
||||||
|
// Parse and handle accordingly
|
||||||
|
if (rawData.contains("\"Action\":\"play\"")) {
|
||||||
|
startGame();
|
||||||
|
} else if (rawData.contains("\"Action\":\"quit\"")) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startGame() {
|
||||||
|
// Game logic...
|
||||||
|
close(); // Close the page
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - Opening Pages
|
||||||
|
public void openingPagesExample(Player player) {
|
||||||
|
PageManager pageManager = player.getPageManager();
|
||||||
|
|
||||||
|
// Open custom page
|
||||||
|
pageManager.openCustomPage(
|
||||||
|
player.getReference(),
|
||||||
|
player.getReference().getStore(),
|
||||||
|
new MenuPage(player.getPlayerRef())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - Closing Pages
|
||||||
|
public void closingPagesExample(Player player) {
|
||||||
|
PageManager pageManager = player.getPageManager();
|
||||||
|
Ref<EntityStore> ref = player.getReference();
|
||||||
|
Store<EntityStore> store = ref.getStore();
|
||||||
|
|
||||||
|
// From outside
|
||||||
|
pageManager.setPage(ref, store, Page.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - Built-in pages
|
||||||
|
// NOTE: Documentation was wrong - Page.Settings doesn't exist
|
||||||
|
// Available: None, Bench, Inventory, ToolsSettings, Map, MachinimaEditor, ContentCreation, Custom
|
||||||
|
public void builtInPagesExample(Player player) {
|
||||||
|
PageManager pageManager = player.getPageManager();
|
||||||
|
Ref<EntityStore> ref = player.getReference();
|
||||||
|
Store<EntityStore> store = ref.getStore();
|
||||||
|
|
||||||
|
// Open built-in page (e.g., Inventory)
|
||||||
|
pageManager.setPage(ref, store, Page.Inventory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - ShopPage
|
||||||
|
public static class ShopItem {
|
||||||
|
private final String name;
|
||||||
|
private final int price;
|
||||||
|
|
||||||
|
public ShopItem(String name, int price) {
|
||||||
|
this.name = name;
|
||||||
|
this.price = price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() { return name; }
|
||||||
|
public int getPrice() { return price; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ShopPage extends CustomUIPage {
|
||||||
|
|
||||||
|
private int selectedItem = -1;
|
||||||
|
private final List<ShopItem> items;
|
||||||
|
|
||||||
|
public ShopPage(PlayerRef ref, List<ShopItem> items) {
|
||||||
|
super(ref, CustomPageLifetime.CanDismiss);
|
||||||
|
this.items = items;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void build(Ref<EntityStore> ref, UICommandBuilder builder,
|
||||||
|
UIEventBuilder events, Store<EntityStore> store) {
|
||||||
|
builder.append("ui/shop");
|
||||||
|
|
||||||
|
// Populate items
|
||||||
|
for (int i = 0; i < items.size(); i++) {
|
||||||
|
ShopItem item = items.get(i);
|
||||||
|
builder.set("#item_" + i + "_name", item.getName());
|
||||||
|
builder.set("#item_" + i + "_price", item.getPrice());
|
||||||
|
events.addEventBinding(CustomUIEventBindingType.Activating, "#item_" + i,
|
||||||
|
EventData.of("Action", "select").append("Index", String.valueOf(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
events.addEventBinding(CustomUIEventBindingType.Activating, "#buy_button",
|
||||||
|
EventData.of("Action", "buy"));
|
||||||
|
events.addEventBinding(CustomUIEventBindingType.Activating, "#close_button",
|
||||||
|
EventData.of("Action", "close"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleDataEvent(Ref<EntityStore> ref, Store<EntityStore> store, String rawData) {
|
||||||
|
// rawData contains JSON like: {"Action":"select","Index":"0"}
|
||||||
|
if (rawData.contains("\"Action\":\"select\"")) {
|
||||||
|
// Extract index from JSON
|
||||||
|
int indexStart = rawData.indexOf("\"Index\":\"") + 9;
|
||||||
|
int indexEnd = rawData.indexOf("\"", indexStart);
|
||||||
|
selectedItem = Integer.parseInt(rawData.substring(indexStart, indexEnd));
|
||||||
|
updateSelection();
|
||||||
|
} else if (rawData.contains("\"Action\":\"buy\"") && selectedItem >= 0) {
|
||||||
|
purchaseItem(selectedItem);
|
||||||
|
} else if (rawData.contains("\"Action\":\"close\"")) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSelection() {
|
||||||
|
UICommandBuilder update = new UICommandBuilder();
|
||||||
|
update.set("#selected_index", selectedItem);
|
||||||
|
update.set("#selected_name", items.get(selectedItem).getName());
|
||||||
|
sendUpdate(update);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void purchaseItem(int index) {
|
||||||
|
// Purchase logic...
|
||||||
|
rebuild(); // Refresh UI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - Page with Windows
|
||||||
|
// NOTE: The documentation shows openCustomPageWithWindows but the signature is:
|
||||||
|
// openCustomPageWithWindows(Ref<EntityStore>, Store<EntityStore>, CustomUIPage, Window...)
|
||||||
|
public void pageWithWindowsExample(Player player, ItemContainer craftingContainer) {
|
||||||
|
PageManager pageManager = player.getPageManager();
|
||||||
|
Ref<EntityStore> ref = player.getReference();
|
||||||
|
Store<EntityStore> store = ref.getStore();
|
||||||
|
|
||||||
|
// Custom crafting UI with windows
|
||||||
|
// NOTE: ContainerWindow takes just ItemContainer, not windowId/WindowType
|
||||||
|
ContainerWindow inputWindow = new ContainerWindow(craftingContainer);
|
||||||
|
|
||||||
|
pageManager.openCustomPageWithWindows(
|
||||||
|
ref, store,
|
||||||
|
new WelcomePage(player.getPlayerRef()),
|
||||||
|
inputWindow
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
149
app/src/main/java/org/example/docs/ui/HudManagerExample.java
Normal file
149
app/src/main/java/org/example/docs/ui/HudManagerExample.java
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
package org.example.docs.ui;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example code from: hytale-docs/content/ui/hud-manager.en.md
|
||||||
|
* Tests the HUD Manager documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import com.hypixel.hytale.protocol.packets.interface_.HudComponent;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.player.hud.CustomUIHud;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.player.hud.HudManager;
|
||||||
|
import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class HudManagerExample {
|
||||||
|
|
||||||
|
// Example from documentation - Accessing HUD Manager
|
||||||
|
public void accessingHudManager(Player player) {
|
||||||
|
HudManager hudManager = player.getHudManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - Showing Components
|
||||||
|
public void showingComponents(Player player) {
|
||||||
|
HudManager hudManager = player.getHudManager();
|
||||||
|
|
||||||
|
// Show specific components (additive)
|
||||||
|
hudManager.showHudComponents(player.getPlayerRef(),
|
||||||
|
HudComponent.Health,
|
||||||
|
HudComponent.Stamina,
|
||||||
|
HudComponent.Hotbar
|
||||||
|
);
|
||||||
|
|
||||||
|
// Show from a Set
|
||||||
|
Set<HudComponent> components = Set.of(
|
||||||
|
HudComponent.Chat,
|
||||||
|
HudComponent.Notifications
|
||||||
|
);
|
||||||
|
hudManager.showHudComponents(player.getPlayerRef(), components);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - Hiding Components
|
||||||
|
public void hidingComponents(Player player) {
|
||||||
|
HudManager hudManager = player.getHudManager();
|
||||||
|
|
||||||
|
// Hide specific components
|
||||||
|
hudManager.hideHudComponents(player.getPlayerRef(),
|
||||||
|
HudComponent.Compass,
|
||||||
|
HudComponent.Speedometer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - Setting Components
|
||||||
|
public void settingComponents(Player player) {
|
||||||
|
HudManager hudManager = player.getHudManager();
|
||||||
|
|
||||||
|
// Set exact visible components (replaces all)
|
||||||
|
hudManager.setVisibleHudComponents(player.getPlayerRef(),
|
||||||
|
HudComponent.Hotbar,
|
||||||
|
HudComponent.Health
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clear all HUD components (empty)
|
||||||
|
hudManager.setVisibleHudComponents(player.getPlayerRef());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - Querying Visible Components
|
||||||
|
public void queryingComponents(Player player) {
|
||||||
|
HudManager hudManager = player.getHudManager();
|
||||||
|
|
||||||
|
Set<HudComponent> visible = hudManager.getVisibleHudComponents();
|
||||||
|
|
||||||
|
if (visible.contains(HudComponent.Health)) {
|
||||||
|
// Health bar is visible
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - Resetting HUD
|
||||||
|
public void resettingHud(Player player) {
|
||||||
|
HudManager hudManager = player.getHudManager();
|
||||||
|
|
||||||
|
// Reset to default components and clear custom HUD
|
||||||
|
hudManager.resetHud(player.getPlayerRef());
|
||||||
|
|
||||||
|
// Reset entire UI state
|
||||||
|
hudManager.resetUserInterface(player.getPlayerRef());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - Custom HUD Overlay
|
||||||
|
public static class ScoreboardHud extends CustomUIHud {
|
||||||
|
|
||||||
|
public ScoreboardHud(PlayerRef ref) {
|
||||||
|
super(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void build(UICommandBuilder builder) {
|
||||||
|
builder.append("ui/scoreboard");
|
||||||
|
builder.set("#score", 1500);
|
||||||
|
builder.set("#rank", "Gold");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void customHudOverlay(Player player) {
|
||||||
|
HudManager hudManager = player.getHudManager();
|
||||||
|
|
||||||
|
// Set custom HUD
|
||||||
|
hudManager.setCustomHud(player.getPlayerRef(), new ScoreboardHud(player.getPlayerRef()));
|
||||||
|
|
||||||
|
// Remove custom HUD
|
||||||
|
hudManager.setCustomHud(player.getPlayerRef(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - Cinematic Mode
|
||||||
|
public void enterCinematicMode(Player player) {
|
||||||
|
HudManager hud = player.getHudManager();
|
||||||
|
|
||||||
|
// Store current state if needed for restoration
|
||||||
|
Set<HudComponent> previous = new HashSet<>(hud.getVisibleHudComponents());
|
||||||
|
|
||||||
|
// Clear all HUD
|
||||||
|
hud.setVisibleHudComponents(player.getPlayerRef());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void exitCinematicMode(Player player, Set<HudComponent> restore) {
|
||||||
|
player.getHudManager().setVisibleHudComponents(player.getPlayerRef(), restore);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - Minimal HUD Mode
|
||||||
|
public void setMinimalHud(Player player) {
|
||||||
|
player.getHudManager().setVisibleHudComponents(player.getPlayerRef(),
|
||||||
|
HudComponent.Hotbar,
|
||||||
|
HudComponent.Health,
|
||||||
|
HudComponent.Reticle
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - Builder Mode HUD
|
||||||
|
public void enableBuilderHud(Player player) {
|
||||||
|
HudManager hud = player.getHudManager();
|
||||||
|
hud.showHudComponents(player.getPlayerRef(),
|
||||||
|
HudComponent.BuilderToolsLegend,
|
||||||
|
HudComponent.BuilderToolsMaterialSlotSelector,
|
||||||
|
HudComponent.BlockVariantSelector
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
66
app/src/main/java/org/example/docs/ui/UiIndexExample.java
Normal file
66
app/src/main/java/org/example/docs/ui/UiIndexExample.java
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package org.example.docs.ui;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example code from: hytale-docs/content/ui/_index.en.md
|
||||||
|
* Tests the UI system overview documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.component.Store;
|
||||||
|
import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime;
|
||||||
|
import com.hypixel.hytale.protocol.packets.interface_.HudComponent;
|
||||||
|
import com.hypixel.hytale.protocol.packets.interface_.Page;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.player.hud.HudManager;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.player.pages.CustomUIPage;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.player.pages.PageManager;
|
||||||
|
import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder;
|
||||||
|
import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
|
||||||
|
public class UiIndexExample {
|
||||||
|
|
||||||
|
// Example from documentation - Custom Page
|
||||||
|
public static class MyPage extends CustomUIPage {
|
||||||
|
|
||||||
|
public MyPage(PlayerRef ref) {
|
||||||
|
super(ref, CustomPageLifetime.CanDismiss);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void build(Ref<EntityStore> ref, UICommandBuilder builder,
|
||||||
|
UIEventBuilder events, Store<EntityStore> store) {
|
||||||
|
// Build UI using builder.set(), builder.append(), etc.
|
||||||
|
builder.append("ui/my_template");
|
||||||
|
builder.set("#title", "Welcome!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - Page Manager
|
||||||
|
public void pageManagerExample(Player player) {
|
||||||
|
Ref<EntityStore> ref = player.getReference();
|
||||||
|
Store<EntityStore> store = ref.getStore();
|
||||||
|
PageManager pageManager = player.getPageManager();
|
||||||
|
|
||||||
|
// Open a custom page
|
||||||
|
pageManager.openCustomPage(ref, store, new MyPage(player.getPlayerRef()));
|
||||||
|
|
||||||
|
// Close to normal view
|
||||||
|
pageManager.setPage(ref, store, Page.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - HUD Manager
|
||||||
|
public void hudManagerExample(Player player) {
|
||||||
|
HudManager hudManager = player.getHudManager();
|
||||||
|
|
||||||
|
// Hide all HUD components
|
||||||
|
hudManager.setVisibleHudComponents(player.getPlayerRef());
|
||||||
|
|
||||||
|
// Show specific components
|
||||||
|
hudManager.showHudComponents(player.getPlayerRef(),
|
||||||
|
HudComponent.Hotbar,
|
||||||
|
HudComponent.Health
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
83
app/src/main/java/org/example/docs/ui/WindowsExample.java
Normal file
83
app/src/main/java/org/example/docs/ui/WindowsExample.java
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package org.example.docs.ui;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example code from: hytale-docs/content/ui/windows.en.md
|
||||||
|
* Tests the Windows documentation
|
||||||
|
*
|
||||||
|
* NOTE: The documentation has some errors:
|
||||||
|
* - ContainerWindow constructor is ContainerWindow(ItemContainer), not ContainerWindow(id, WindowType, container)
|
||||||
|
* - BlockWindow is abstract and cannot be instantiated directly
|
||||||
|
* - ItemStackContainerWindow constructor is ItemStackContainerWindow(ItemStackItemContainer)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.component.Store;
|
||||||
|
import com.hypixel.hytale.protocol.packets.interface_.Page;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.player.pages.PageManager;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.player.windows.ContainerWindow;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.player.windows.ItemStackContainerWindow;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.player.windows.WindowManager;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
|
||||||
|
import com.hypixel.hytale.server.core.inventory.container.ItemStackItemContainer;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
|
||||||
|
public class WindowsExample {
|
||||||
|
|
||||||
|
// Example from documentation - Window Manager
|
||||||
|
public void windowManagerExample(Player player) {
|
||||||
|
WindowManager windowManager = player.getWindowManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - ContainerWindow
|
||||||
|
// CORRECTED: Constructor is ContainerWindow(ItemContainer)
|
||||||
|
public void containerWindowExample(ItemContainer container) {
|
||||||
|
// Open a container window
|
||||||
|
ContainerWindow window = new ContainerWindow(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - ItemStackContainerWindow
|
||||||
|
// CORRECTED: Constructor is ItemStackContainerWindow(ItemStackItemContainer)
|
||||||
|
public void itemStackContainerWindowExample(ItemStackItemContainer itemStackContainer) {
|
||||||
|
ItemStackContainerWindow window = new ItemStackContainerWindow(itemStackContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - Opening Windows
|
||||||
|
// CORRECTED: ContainerWindow takes just ItemContainer
|
||||||
|
public void openingWindowsExample(Player player, ItemContainer chestContainer) {
|
||||||
|
PageManager pageManager = player.getPageManager();
|
||||||
|
Ref<EntityStore> ref = player.getReference();
|
||||||
|
Store<EntityStore> store = ref.getStore();
|
||||||
|
|
||||||
|
// Open page with inventory windows
|
||||||
|
ContainerWindow chestWindow = new ContainerWindow(chestContainer);
|
||||||
|
|
||||||
|
boolean success = pageManager.setPageWithWindows(
|
||||||
|
ref, store,
|
||||||
|
Page.None, // or Page.Custom
|
||||||
|
true, // canCloseThroughInteraction
|
||||||
|
chestWindow
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example from documentation - Chest Container UI
|
||||||
|
// CORRECTED: ContainerWindow takes just ItemContainer
|
||||||
|
public void openChest(Player player, ItemContainer chestContainer) {
|
||||||
|
PageManager pageManager = player.getPageManager();
|
||||||
|
Ref<EntityStore> ref = player.getReference();
|
||||||
|
Store<EntityStore> store = ref.getStore();
|
||||||
|
|
||||||
|
ContainerWindow window = new ContainerWindow(chestContainer);
|
||||||
|
|
||||||
|
pageManager.setPageWithWindows(
|
||||||
|
ref, store,
|
||||||
|
Page.None,
|
||||||
|
true, // Allow closing via interaction
|
||||||
|
window
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: BlockWindow is abstract - cannot be instantiated directly
|
||||||
|
// The documentation example is incorrect
|
||||||
|
// You would need to use a concrete subclass or the builtin interactions
|
||||||
|
}
|
||||||
197
app/src/main/java/org/example/docs/world/BlocksExample.java
Normal file
197
app/src/main/java/org/example/docs/world/BlocksExample.java
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
package org.example.docs.world;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/world/blocks.en.md
|
||||||
|
// Tests the blocks documentation examples
|
||||||
|
//
|
||||||
|
// FINDINGS:
|
||||||
|
// - Vector3i uses getX(), getY(), getZ() - NOT x(), y(), z()
|
||||||
|
// - Rotation values are None, Ninety, OneEighty, TwoSeventy - NOT North, East, etc.
|
||||||
|
// - World does NOT implement BlockAccessor directly - placeBlock is on WorldChunk
|
||||||
|
// - For placeBlock with rotation, you MUST get the chunk first:
|
||||||
|
// WorldChunk chunk = world.getChunk(ChunkUtil.indexChunkFromBlock(x, z));
|
||||||
|
// chunk.placeBlock(x, y, z, "block_key", rotation...);
|
||||||
|
// - World-level methods available: setBlock, getBlock, breakBlock, getBlockType
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.server.core.universe.Universe;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.ecs.BreakBlockEvent;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.ecs.PlaceBlockEvent;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.ecs.DamageBlockEvent;
|
||||||
|
import com.hypixel.hytale.math.util.ChunkUtil;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector3i;
|
||||||
|
import com.hypixel.hytale.protocol.BlockMaterial;
|
||||||
|
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests blocks.en.md documentation
|
||||||
|
*/
|
||||||
|
public class BlocksExample extends JavaPlugin {
|
||||||
|
|
||||||
|
public BlocksExample(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
// =============================================
|
||||||
|
// Block Events
|
||||||
|
// =============================================
|
||||||
|
|
||||||
|
// BreakBlockEvent
|
||||||
|
getEventRegistry().register(BreakBlockEvent.class, event -> {
|
||||||
|
Vector3i position = event.getTargetBlock();
|
||||||
|
BlockType blockType = event.getBlockType();
|
||||||
|
|
||||||
|
getLogger().at(Level.INFO).log("Block broken at " + position);
|
||||||
|
|
||||||
|
// Cancel if needed
|
||||||
|
// event.setCancelled(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// PlaceBlockEvent
|
||||||
|
getEventRegistry().register(PlaceBlockEvent.class, event -> {
|
||||||
|
Vector3i position = event.getTargetBlock();
|
||||||
|
RotationTuple rotation = event.getRotation();
|
||||||
|
|
||||||
|
getLogger().at(Level.INFO).log("Block placed at " + position);
|
||||||
|
|
||||||
|
// Cancel if needed
|
||||||
|
// event.setCancelled(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// DamageBlockEvent
|
||||||
|
getEventRegistry().register(DamageBlockEvent.class, event -> {
|
||||||
|
Vector3i position = event.getTargetBlock();
|
||||||
|
BlockType blockType = event.getBlockType();
|
||||||
|
|
||||||
|
// Cancel to prevent damage
|
||||||
|
// event.setCancelled(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Block manipulation examples
|
||||||
|
// =============================================
|
||||||
|
public void blockManipulationExamples(World world) {
|
||||||
|
int x = 100, y = 64, z = 200;
|
||||||
|
|
||||||
|
// Get block ID at position
|
||||||
|
int blockId = world.getBlock(x, y, z);
|
||||||
|
int blockIdVec = world.getBlock(new Vector3i(x, y, z));
|
||||||
|
|
||||||
|
// Get BlockType at position
|
||||||
|
BlockType blockType = world.getBlockType(x, y, z);
|
||||||
|
BlockType blockTypeVec = world.getBlockType(new Vector3i(x, y, z));
|
||||||
|
|
||||||
|
// Check if block is empty
|
||||||
|
if (blockType == BlockType.EMPTY) {
|
||||||
|
// Air or empty space
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set block by block type key
|
||||||
|
world.setBlock(x, y, z, "stone");
|
||||||
|
world.setBlock(x, y, z, "oak_planks", 0);
|
||||||
|
|
||||||
|
// Set block by BlockType object - use getId() to get the key string
|
||||||
|
BlockType stone = BlockType.getAssetMap().getAsset("stone");
|
||||||
|
if (stone != null) {
|
||||||
|
world.setBlock(x, y, z, stone.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get block ID index (useful for internal operations)
|
||||||
|
int stoneId = BlockType.getAssetMap().getIndex("stone");
|
||||||
|
// NOTE: World.setBlock takes a String blockTypeKey, NOT a block ID int
|
||||||
|
// Use the string key directly:
|
||||||
|
world.setBlock(x, y, z, "stone");
|
||||||
|
|
||||||
|
// Place block with rotation
|
||||||
|
// NOTE: placeBlock is on WorldChunk (BlockAccessor), NOT on World directly!
|
||||||
|
// Must get the chunk first
|
||||||
|
WorldChunk chunk = world.getChunk(ChunkUtil.indexChunkFromBlock(x, z));
|
||||||
|
if (chunk != null) {
|
||||||
|
// Rotation values are None, Ninety, OneEighty, TwoSeventy
|
||||||
|
chunk.placeBlock(x, y, z, "oak_log",
|
||||||
|
Rotation.Ninety, // yaw (90 degrees)
|
||||||
|
Rotation.None, // pitch
|
||||||
|
Rotation.None // roll
|
||||||
|
);
|
||||||
|
|
||||||
|
// Place with rotation tuple
|
||||||
|
chunk.placeBlock(x, y, z, "oak_log",
|
||||||
|
RotationTuple.of(Rotation.Ninety, Rotation.None, Rotation.None),
|
||||||
|
0, // settings
|
||||||
|
true // validatePlacement
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Break block (requires settings parameter)
|
||||||
|
world.breakBlock(x, y, z, 0); // 0 = default settings
|
||||||
|
|
||||||
|
// Block interaction state
|
||||||
|
BlockType currentBlock = world.getBlockType(x, y, z);
|
||||||
|
if (currentBlock != null) {
|
||||||
|
world.setBlockInteractionState(
|
||||||
|
new Vector3i(x, y, z),
|
||||||
|
currentBlock,
|
||||||
|
"open"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// BlockType asset access
|
||||||
|
// =============================================
|
||||||
|
public void blockTypeExamples() {
|
||||||
|
// Get block type by ID
|
||||||
|
BlockType blockType = BlockType.getAssetMap().getAsset("stone");
|
||||||
|
|
||||||
|
if (blockType != null) {
|
||||||
|
// Get block ID
|
||||||
|
String blockId = blockType.getId();
|
||||||
|
|
||||||
|
// Get material
|
||||||
|
BlockMaterial material = blockType.getMaterial();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get block ID index
|
||||||
|
int blockIndex = BlockType.getAssetMap().getIndex("stone");
|
||||||
|
|
||||||
|
// Get block from index
|
||||||
|
BlockType fromIndex = BlockType.getAssetMap().getAsset(blockIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Utility methods
|
||||||
|
// NOTE: Uses getX(), getY(), getZ() - NOT x(), y(), z()
|
||||||
|
// =============================================
|
||||||
|
public boolean containsBlockType(World world, Vector3i min, Vector3i max, String blockTypeKey) {
|
||||||
|
for (int x = min.getX(); x <= max.getX(); x++) {
|
||||||
|
for (int y = min.getY(); y <= max.getY(); y++) {
|
||||||
|
for (int z = min.getZ(); z <= max.getZ(); z++) {
|
||||||
|
BlockType block = world.getBlockType(x, y, z);
|
||||||
|
if (block != null && block.getId().equals(blockTypeKey)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fillArea(World world, Vector3i min, Vector3i max, String blockTypeKey) {
|
||||||
|
for (int x = min.getX(); x <= max.getX(); x++) {
|
||||||
|
for (int y = min.getY(); y <= max.getY(); y++) {
|
||||||
|
for (int z = min.getZ(); z <= max.getZ(); z++) {
|
||||||
|
world.setBlock(x, y, z, blockTypeKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
156
app/src/main/java/org/example/docs/world/ChunksExample.java
Normal file
156
app/src/main/java/org/example/docs/world/ChunksExample.java
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
package org.example.docs.world;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/world/chunks.en.md
|
||||||
|
// Tests the chunks documentation examples
|
||||||
|
//
|
||||||
|
// FINDINGS:
|
||||||
|
// - Vector3d uses public fields x, y, z OR getX(), getY(), getZ() - NOT x(), y(), z()
|
||||||
|
// - Vector3i uses getX(), getY(), getZ() - NOT x(), y(), z()
|
||||||
|
// - Player.getPosition() does NOT exist - use PlayerRef.getTransform().getPosition()
|
||||||
|
// - Vector2i uses getX(), getY() - NOT x(), y()
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.server.core.universe.Universe;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector3d;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector3i;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector2i;
|
||||||
|
import com.hypixel.hytale.math.vector.Transform;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests chunks.en.md documentation
|
||||||
|
*
|
||||||
|
* IMPORTANT:
|
||||||
|
* - Player.getPosition() DOESN'T EXIST - use PlayerRef.getTransform().getPosition()
|
||||||
|
* - Vector3d uses x, y, z fields or getX(), getY(), getZ() - NOT x(), y(), z()
|
||||||
|
* - Vector3i uses getX(), getY(), getZ() - NOT x(), y(), z()
|
||||||
|
*/
|
||||||
|
public class ChunksExample extends JavaPlugin {
|
||||||
|
|
||||||
|
public ChunksExample(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
// =============================================
|
||||||
|
// Chunk Basics
|
||||||
|
// =============================================
|
||||||
|
World world = Universe.get().getWorld("default");
|
||||||
|
int x = 100, y = 64, z = 200;
|
||||||
|
|
||||||
|
// Access blocks within a chunk through the World API
|
||||||
|
BlockType blockType = world.getBlockType(x, y, z);
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Chunk Coordinates
|
||||||
|
// =============================================
|
||||||
|
int blockX = 100, blockZ = 200;
|
||||||
|
|
||||||
|
// Convert block coordinates to chunk coordinates
|
||||||
|
int chunkX = blockX >> 4; // Divide by 16
|
||||||
|
int chunkZ = blockZ >> 4;
|
||||||
|
|
||||||
|
// Convert chunk coordinates to block coordinates (corner)
|
||||||
|
int startBlockX = chunkX << 4; // Multiply by 16
|
||||||
|
int startBlockZ = chunkZ << 4;
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Chunk Loading - CORRECTED
|
||||||
|
// =============================================
|
||||||
|
getEventRegistry().register(PlayerConnectEvent.class, event -> {
|
||||||
|
PlayerRef playerRef = event.getPlayerRef();
|
||||||
|
|
||||||
|
// CORRECT: Get position from PlayerRef.getTransform()
|
||||||
|
// Player.getPosition() does NOT exist!
|
||||||
|
Transform transform = playerRef.getTransform();
|
||||||
|
if (transform != null) {
|
||||||
|
Vector3d pos = transform.getPosition();
|
||||||
|
// Vector3d uses getX(), getY(), getZ() - NOT x(), y(), z()
|
||||||
|
int pChunkX = (int) pos.getX() >> 4;
|
||||||
|
int pChunkZ = (int) pos.getZ() >> 4;
|
||||||
|
|
||||||
|
getLogger().at(Level.INFO).log("Player at chunk: " + pChunkX + ", " + pChunkZ);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Working with Chunks
|
||||||
|
// =============================================
|
||||||
|
public void iterateChunkBlocks(World world, int chunkX, int chunkZ) {
|
||||||
|
int startX = chunkX << 4;
|
||||||
|
int startZ = chunkZ << 4;
|
||||||
|
|
||||||
|
for (int x = startX; x < startX + 16; x++) {
|
||||||
|
for (int z = startZ; z < startZ + 16; z++) {
|
||||||
|
for (int y = 0; y < 256; y++) {
|
||||||
|
BlockType blockType = world.getBlockType(x, y, z);
|
||||||
|
// Process block...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processChunk(World world, int chunkX, int chunkZ) {
|
||||||
|
int baseX = chunkX << 4;
|
||||||
|
int baseZ = chunkZ << 4;
|
||||||
|
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
int worldX = baseX + x;
|
||||||
|
int worldZ = baseZ + z;
|
||||||
|
// Process...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Async chunk processing - CORRECTED
|
||||||
|
// =============================================
|
||||||
|
public void processChunksAsync(PlayerRef playerRef, World world) {
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
// Heavy chunk processing here
|
||||||
|
Map<Vector3i, String> results = new HashMap<>();
|
||||||
|
// ... process
|
||||||
|
|
||||||
|
// Return to world thread to apply results
|
||||||
|
world.execute(() -> {
|
||||||
|
for (var entry : results.entrySet()) {
|
||||||
|
Vector3i pos = entry.getKey();
|
||||||
|
// CORRECT: Vector3i uses getX(), getY(), getZ()
|
||||||
|
world.setBlock(pos.getX(), pos.getY(), pos.getZ(), entry.getValue());
|
||||||
|
}
|
||||||
|
playerRef.sendMessage(Message.raw("Chunk processing complete!"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Get chunk coords from player - CORRECTED
|
||||||
|
// =============================================
|
||||||
|
public Vector2i getChunkCoords(PlayerRef playerRef) {
|
||||||
|
// CORRECT: Use PlayerRef.getTransform() - Player.getPosition() doesn't exist!
|
||||||
|
Transform transform = playerRef.getTransform();
|
||||||
|
if (transform == null) {
|
||||||
|
return new Vector2i(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3d pos = transform.getPosition();
|
||||||
|
// CORRECT: Vector3d uses getX(), getZ() - NOT x(), z()
|
||||||
|
int chunkX = (int) pos.getX() >> 4;
|
||||||
|
int chunkZ = (int) pos.getZ() >> 4;
|
||||||
|
return new Vector2i(chunkX, chunkZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
117
app/src/main/java/org/example/docs/world/PlayerRefsExample.java
Normal file
117
app/src/main/java/org/example/docs/world/PlayerRefsExample.java
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
package org.example.docs.world;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/world/player-refs.en.md
|
||||||
|
// Tests the PlayerRef documentation examples
|
||||||
|
//
|
||||||
|
// FINDINGS:
|
||||||
|
// - PlayerRef.getName() does NOT exist - use getUsername()
|
||||||
|
// - PlayerRef.getPlayer() does NOT exist
|
||||||
|
// - PlayerRef.sendMessage(Message) exists directly, not via getPlayer()
|
||||||
|
// - Player.getPlayerRef() EXISTS
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.server.core.universe.Universe;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerDisconnectEvent;
|
||||||
|
import com.hypixel.hytale.server.core.Message;
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests player-refs.en.md documentation
|
||||||
|
*/
|
||||||
|
public class PlayerRefsExample extends JavaPlugin {
|
||||||
|
private final Map<UUID, PlayerData> playerData = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public PlayerRefsExample(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
// =============================================
|
||||||
|
// Getting a PlayerRef - CORRECT
|
||||||
|
// =============================================
|
||||||
|
getEventRegistry().register(PlayerConnectEvent.class, event -> {
|
||||||
|
PlayerRef ref = event.getPlayerRef();
|
||||||
|
|
||||||
|
// PlayerRef methods that EXIST:
|
||||||
|
UUID uuid = ref.getUuid();
|
||||||
|
String name = ref.getUsername(); // NOT getName()!
|
||||||
|
|
||||||
|
// sendMessage is DIRECT on PlayerRef
|
||||||
|
ref.sendMessage(Message.raw("Welcome!"));
|
||||||
|
|
||||||
|
// Get entity reference
|
||||||
|
Ref<EntityStore> entityRef = ref.getReference();
|
||||||
|
|
||||||
|
// Check validity
|
||||||
|
boolean valid = ref.isValid();
|
||||||
|
|
||||||
|
// Store player data
|
||||||
|
playerData.put(ref.getUuid(), new PlayerData(ref));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Disconnect handling
|
||||||
|
getEventRegistry().register(PlayerDisconnectEvent.class, event -> {
|
||||||
|
playerData.remove(event.getPlayerRef().getUuid());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// World PlayerRefs - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void worldPlayerRefs() {
|
||||||
|
World world = Universe.get().getWorld("default");
|
||||||
|
if (world != null) {
|
||||||
|
// Get all player refs in this world
|
||||||
|
Collection<PlayerRef> playerRefs = world.getPlayerRefs();
|
||||||
|
|
||||||
|
// Get all active players (returns List, not Collection)
|
||||||
|
List<Player> players = world.getPlayers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Cross-World Messaging - CORRECT
|
||||||
|
// =============================================
|
||||||
|
public void sendGlobalMessage(PlayerRef ref, String message) {
|
||||||
|
// PlayerRef.sendMessage handles cross-world messaging directly
|
||||||
|
ref.sendMessage(Message.raw(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void broadcastAll(String message) {
|
||||||
|
Message msg = Message.raw(message);
|
||||||
|
for (World world : Universe.get().getWorlds().values()) {
|
||||||
|
for (PlayerRef ref : world.getPlayerRefs()) {
|
||||||
|
ref.sendMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple player data class
|
||||||
|
static class PlayerData {
|
||||||
|
private final PlayerRef playerRef;
|
||||||
|
private int score;
|
||||||
|
|
||||||
|
public PlayerData(PlayerRef ref) {
|
||||||
|
this.playerRef = ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addScore(int amount) {
|
||||||
|
this.score += amount;
|
||||||
|
playerRef.sendMessage(Message.raw("Score: " + score));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package org.example.docs.world;
|
||||||
|
|
||||||
|
// Example from: hytale-docs/content/world/universe-and-worlds.en.md
|
||||||
|
// Tests the universe and worlds documentation examples
|
||||||
|
//
|
||||||
|
// FINDINGS:
|
||||||
|
// - universe.hasWorld() does NOT exist - use universe.getWorld(name) != null
|
||||||
|
// - world.getEntities() does NOT exist on World
|
||||||
|
// - world.setBlock(x, y, z, blockType) wrong - takes String, not BlockType
|
||||||
|
// - world.getSpawnPoint() does NOT exist - access via WorldConfig.getSpawnProvider()
|
||||||
|
// - world.getPlayers() returns List<Player>, not Collection<Player>
|
||||||
|
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import com.hypixel.hytale.server.core.universe.Universe;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.universe.PlayerRef;
|
||||||
|
import com.hypixel.hytale.server.core.entity.entities.Player;
|
||||||
|
import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
|
||||||
|
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector3i;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests universe-and-worlds.en.md documentation
|
||||||
|
*/
|
||||||
|
public class UniverseWorldsExample extends JavaPlugin {
|
||||||
|
|
||||||
|
public UniverseWorldsExample(JavaPluginInit init) {
|
||||||
|
super(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() {
|
||||||
|
// =============================================
|
||||||
|
// Universe - CORRECT patterns
|
||||||
|
// =============================================
|
||||||
|
Universe universe = Universe.get();
|
||||||
|
|
||||||
|
// Get specific world
|
||||||
|
World world = universe.getWorld("default");
|
||||||
|
|
||||||
|
// Get all worlds
|
||||||
|
Map<String, World> worlds = universe.getWorlds();
|
||||||
|
|
||||||
|
// Check if world exists - CORRECT way (hasWorld doesn't exist!)
|
||||||
|
if (universe.getWorld("creative") != null) {
|
||||||
|
// World exists
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// World - CORRECT patterns
|
||||||
|
// =============================================
|
||||||
|
if (world != null) {
|
||||||
|
// Get world name
|
||||||
|
String name = world.getName();
|
||||||
|
|
||||||
|
// Get all players - returns List<Player>, not Collection<Player>
|
||||||
|
List<Player> players = world.getPlayers();
|
||||||
|
|
||||||
|
// Get player refs
|
||||||
|
Collection<PlayerRef> playerRefs = world.getPlayerRefs();
|
||||||
|
|
||||||
|
// NOTE: world.getEntities() does NOT exist on World!
|
||||||
|
// Entities are accessed through EntityStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// World Operations - CORRECT patterns
|
||||||
|
// =============================================
|
||||||
|
if (world != null) {
|
||||||
|
int x = 100, y = 64, z = 200;
|
||||||
|
|
||||||
|
// Get block at position
|
||||||
|
BlockType blockType = world.getBlockType(x, y, z);
|
||||||
|
BlockType blockTypeVec = world.getBlockType(new Vector3i(x, y, z));
|
||||||
|
|
||||||
|
// Set block - takes String, NOT BlockType!
|
||||||
|
world.setBlock(x, y, z, "stone");
|
||||||
|
|
||||||
|
// If you have a BlockType, use getId()
|
||||||
|
if (blockType != null) {
|
||||||
|
world.setBlock(x, y, z, blockType.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: world.getSpawnPoint() does NOT exist directly!
|
||||||
|
// Access spawn via: world.getWorldConfig().getSpawnProvider()
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// World from Event - CORRECT
|
||||||
|
// =============================================
|
||||||
|
getEventRegistry().register(PlayerConnectEvent.class, event -> {
|
||||||
|
PlayerRef playerRef = event.getPlayerRef();
|
||||||
|
World eventWorld = event.getWorld(); // Can be null!
|
||||||
|
|
||||||
|
if (eventWorld != null) {
|
||||||
|
getLogger().at(Level.INFO).log("Player joined world: " + eventWorld.getName());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
18
app/src/main/resources/manifest.json
Normal file
18
app/src/main/resources/manifest.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"Group": "org.example",
|
||||||
|
"Name": "ExamplePlugin",
|
||||||
|
"Version": "1.0.0",
|
||||||
|
"Description": "An example Hytale server plugin",
|
||||||
|
"Authors": [
|
||||||
|
{
|
||||||
|
"Name": "Your Name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Main": "org.example.ExamplePlugin",
|
||||||
|
"ServerVersion": "*",
|
||||||
|
"Dependencies": {},
|
||||||
|
"OptionalDependencies": {},
|
||||||
|
"DisabledByDefault": false,
|
||||||
|
"IncludesAssetPack": false,
|
||||||
|
"SubPlugins": []
|
||||||
|
}
|
||||||
12
app/src/test/java/org/example/ExamplePluginTest.java
Normal file
12
app/src/test/java/org/example/ExamplePluginTest.java
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package org.example;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class ExamplePluginTest {
|
||||||
|
@Test
|
||||||
|
public void pluginJsonExists() {
|
||||||
|
// Verify manifest.json exists in resources
|
||||||
|
assertNotNull(getClass().getClassLoader().getResource("manifest.json"));
|
||||||
|
}
|
||||||
|
}
|
||||||
6
build.gradle.kts
Normal file
6
build.gradle.kts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
plugins {
|
||||||
|
java
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root project configuration
|
||||||
|
// The actual plugin code is in the 'app' module
|
||||||
5
gradle.properties
Normal file
5
gradle.properties
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# This file was generated by the Gradle 'init' task.
|
||||||
|
# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties
|
||||||
|
|
||||||
|
org.gradle.configuration-cache=true
|
||||||
|
|
||||||
10
gradle/libs.versions.toml
Normal file
10
gradle/libs.versions.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# This file was generated by the Gradle 'init' task.
|
||||||
|
# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format
|
||||||
|
|
||||||
|
[versions]
|
||||||
|
guava = "33.4.6-jre"
|
||||||
|
junit = "4.13.2"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
guava = { module = "com.google.guava:guava", version.ref = "guava" }
|
||||||
|
junit = { module = "junit:junit", version.ref = "junit" }
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
248
gradlew
vendored
Normal file
248
gradlew
vendored
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
93
gradlew.bat
vendored
Normal file
93
gradlew.bat
vendored
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%"=="" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
7
settings.gradle.kts
Normal file
7
settings.gradle.kts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
plugins {
|
||||||
|
// Apply the foojay-resolver plugin to allow automatic download of JDKs
|
||||||
|
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.name = "example-plugin"
|
||||||
|
include("app")
|
||||||
140
setup.sh
Normal file
140
setup.sh
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
BIN_DIR="$SCRIPT_DIR/.bin"
|
||||||
|
SERVER_DIR="$SCRIPT_DIR/server"
|
||||||
|
SRC_REF_DIR="$SCRIPT_DIR/src-ref"
|
||||||
|
|
||||||
|
|
||||||
|
# Détection de l'OS
|
||||||
|
detect_os() {
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Linux*) echo "linux-amd64" ;;
|
||||||
|
Darwin*) echo "darwin-amd64" ;;
|
||||||
|
CYGWIN*|MINGW*|MSYS*) echo "windows-amd64.exe" ;;
|
||||||
|
*) echo "unknown" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
OS_SUFFIX=$(detect_os)
|
||||||
|
if [ "$OS_SUFFIX" = "unknown" ]; then
|
||||||
|
echo "Erreur: OS non supporté"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
download() {
|
||||||
|
mkdir -p "$BIN_DIR"
|
||||||
|
|
||||||
|
echo "Téléchargement de hytale-downloader..."
|
||||||
|
curl -L -o "$BIN_DIR/hytale-downloader.zip" "https://downloader.hytale.com/hytale-downloader.zip"
|
||||||
|
unzip -o "$BIN_DIR/hytale-downloader.zip" -d "$BIN_DIR"
|
||||||
|
rm "$BIN_DIR/hytale-downloader.zip"
|
||||||
|
|
||||||
|
echo "Téléchargement de CFR..."
|
||||||
|
curl -L -o "$BIN_DIR/cfr-0.152.jar" "https://www.benf.org/other/cfr/cfr-0.152.jar"
|
||||||
|
|
||||||
|
# Rendre les binaires exécutables
|
||||||
|
chmod +x "$BIN_DIR"/hytale-downloader-* 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "Téléchargement terminé."
|
||||||
|
}
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
DOWNLOADER="$BIN_DIR/hytale-downloader-$OS_SUFFIX"
|
||||||
|
if [ ! -f "$DOWNLOADER" ]; then
|
||||||
|
echo "Erreur: $DOWNLOADER non trouvé."
|
||||||
|
echo "Exécutez d'abord: ./setup.sh --download"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Téléchargement du serveur Hytale..."
|
||||||
|
if [ ! -d "$SERVER_DIR" ]; then
|
||||||
|
mkdir $SERVER_DIR
|
||||||
|
fi
|
||||||
|
if [ ! -f "$SERVER_DIR/server.zip" ]; then
|
||||||
|
"$DOWNLOADER" --download-path "$SERVER_DIR/server.zip"
|
||||||
|
fi
|
||||||
|
echo "Décompression de l'archive du serveur Hytale"
|
||||||
|
unzip -o "$SERVER_DIR/server.zip" -d "$SERVER_DIR"
|
||||||
|
echo "Téléchargement terminée."
|
||||||
|
}
|
||||||
|
|
||||||
|
update_safe() {
|
||||||
|
echo "Are you sure ?"
|
||||||
|
echo "It will delete the old server/Assets.zip server/server.zip server/Server/* "
|
||||||
|
echo "Use ./setup.sh --update-sure"
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
DOWNLOADER="$BIN_DIR/hytale-downloader-$OS_SUFFIX"
|
||||||
|
echo "Téléchargement du serveur Hytale..."
|
||||||
|
if [ ! -d "$SERVER_DIR" ]; then
|
||||||
|
mkdir $SERVER_DIR
|
||||||
|
fi
|
||||||
|
if [ -f "$SERVER_DIR/server.zip" ]; then
|
||||||
|
rm $SERVER_DIR/server.zip
|
||||||
|
fi
|
||||||
|
if [ -f "$SERVER_DIR/Assets.zip" ]; then
|
||||||
|
rm $SERVER_DIR/Assets.zip
|
||||||
|
fi
|
||||||
|
if [ -d "$SERVER_DIR/Server.zip" ]; then
|
||||||
|
rm -rf $SERVER_DIR/Server
|
||||||
|
fi
|
||||||
|
if [ ! -f "$SERVER_DIR/server.zip" ]; then
|
||||||
|
"$DOWNLOADER" --download-path "$SERVER_DIR/server.zip"
|
||||||
|
fi
|
||||||
|
echo "Décompression de l'archive du serveur Hytale"
|
||||||
|
unzip -o "$SERVER_DIR/server.zip" -d "$SERVER_DIR"
|
||||||
|
echo "Téléchargement terminée."
|
||||||
|
}
|
||||||
|
|
||||||
|
update-pre() {
|
||||||
|
DOWNLOADER="$BIN_DIR/hytale-downloader-$OS_SUFFIX"
|
||||||
|
echo "Téléchargement du serveur Hytale..."
|
||||||
|
if [ ! -d "$SERVER_DIR" ]; then
|
||||||
|
mkdir $SERVER_DIR
|
||||||
|
fi
|
||||||
|
if [ -f "$SERVER_DIR/server.zip" ]; then
|
||||||
|
rm $SERVER_DIR/server.zip
|
||||||
|
fi
|
||||||
|
if [ -f "$SERVER_DIR/Assets.zip" ]; then
|
||||||
|
rm $SERVER_DIR/Assets.zip
|
||||||
|
fi
|
||||||
|
if [ -d "$SERVER_DIR/Server.zip" ]; then
|
||||||
|
rm -rf $SERVER_DIR/Server
|
||||||
|
fi
|
||||||
|
if [ ! -f "$SERVER_DIR/server.zip" ]; then
|
||||||
|
"$DOWNLOADER" --download-path "$SERVER_DIR/server.zip" --patchline pre-release
|
||||||
|
fi
|
||||||
|
echo "Décompression de l'archive du serveur Hytale"
|
||||||
|
unzip -o "$SERVER_DIR/server.zip" -d "$SERVER_DIR"
|
||||||
|
echo "Téléchargement terminée."
|
||||||
|
}
|
||||||
|
|
||||||
|
decompile() {
|
||||||
|
echo "Décompilation avec CFR..."
|
||||||
|
rm -rf "$SRC_REF_DIR/*"
|
||||||
|
java -jar "$BIN_DIR/cfr-0.152.jar" "$SERVER_DIR/Server/HytaleServer.jar" --outputdir "$SRC_REF_DIR"
|
||||||
|
echo "Décompilation terminée."
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
--download) download ;;
|
||||||
|
--setup) setup ;;
|
||||||
|
--decompile)decompile ;;
|
||||||
|
--update) update ;;
|
||||||
|
--update-pre) update-pre ;;
|
||||||
|
*)
|
||||||
|
echo "Usage: ./setup.sh [--download|--setup]"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " --download Télécharge les outils (hytale-downloader, CFR)"
|
||||||
|
echo " --setup Configure l'environnement (télécharge le serveur)"
|
||||||
|
echo " --update Télécharge la mise à jour"
|
||||||
|
echo " --update-pre Télécharge la mise à jour pre-release"
|
||||||
|
echo " --decompile Décompile le serveur"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Reference in New Issue
Block a user