commit 2033df265506936a7aaa71837015e8e63981bcff Author: RedSavant Date: Wed Jan 21 14:59:11 2026 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7c9516d --- /dev/null +++ b/.gitignore @@ -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/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..a47b236 --- /dev/null +++ b/README.md @@ -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 diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..75305d1 --- /dev/null +++ b/app/build.gradle.kts @@ -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") +} diff --git a/app/src/main/java/org/example/ExamplePlugin.java b/app/src/main/java/org/example/ExamplePlugin.java new file mode 100644 index 0000000..f8b22dc --- /dev/null +++ b/app/src/main/java/org/example/ExamplePlugin.java @@ -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 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() { + } +} diff --git a/app/src/main/java/org/example/MyConfig.java b/app/src/main/java/org/example/MyConfig.java new file mode 100644 index 0000000..0fe7cd6 --- /dev/null +++ b/app/src/main/java/org/example/MyConfig.java @@ -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 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; } +} diff --git a/app/src/main/java/org/example/docs/commands/ArgumentTypesExample.java b/app/src/main/java/org/example/docs/commands/ArgumentTypesExample.java new file mode 100644 index 0000000..9eb9314 --- /dev/null +++ b/app/src/main/java/org/example/docs/commands/ArgumentTypesExample.java @@ -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 countArg; + private final RequiredArg multiplierArg; + private final RequiredArg verboseArg; + private final OptionalArg 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 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 targetArg; + private final OptionalArg 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 execute(CommandContext context) { + PlayerRef targetRef = context.get(targetArg); + + // CORRECTION: PlayerRef doesn't have getPlayer() method + // Instead use getReference() to get Ref + // 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 entityArg; + + public EntityInfoCommand() { + super("entityinfo", "Get entity information"); + entityArg = withRequiredArg("entity", "Entity UUID", ArgTypes.UUID); + } + + @Override + protected CompletableFuture 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 uuidArg; + + public UuidCommand() { + super("uuid", "Parse and display UUID"); + uuidArg = withRequiredArg("uuid", "UUID to parse", ArgTypes.UUID); + } + + @Override + protected CompletableFuture 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 worldArg; + private final OptionalArg 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 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 itemArg; + private final OptionalArg amountArg; + private final OptionalArg 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 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 blockArg; + private final RequiredArg 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 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 effectArg; + private final OptionalArg durationArg; + private final OptionalArg 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 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 particleArg; + private final OptionalArg 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 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 posArg; + + public TeleportPosCommand() { + super("tppos", "Teleport to coordinates"); + posArg = withRequiredArg("position", "Target position", ArgTypes.RELATIVE_POSITION); + } + + @Override + protected CompletableFuture 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 pos1Arg; + private final RequiredArg pos2Arg; + private final RequiredArg 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 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 yArg; + + public SetYCommand() { + super("sety", "Teleport to Y level"); + yArg = withRequiredArg("y", "Y coordinate", ArgTypes.RELATIVE_DOUBLE_COORD); + } + + @Override + protected CompletableFuture 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 rotationArg; + + public LookCommand() { + super("look", "Set player rotation"); + rotationArg = withRequiredArg("rotation", "Pitch Yaw Roll", ArgTypes.ROTATION); + } + + @Override + protected CompletableFuture 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 colorArg; + + public ColorCommand() { + super("color", "Set display color"); + colorArg = withRequiredArg("color", "Color value", ArgTypes.COLOR); + } + + @Override + protected CompletableFuture 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 modeArg; + private final OptionalArg 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 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 rateArg; + + public TickRateCommand() { + super("tickrate", "Set world tick rate"); + rateArg = withRequiredArg("rate", "Tick rate", ArgTypes.TICK_RATE); + } + + @Override + protected CompletableFuture 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 difficultyArg; + + public DifficultyCommand() { + super("difficulty", "Set server difficulty"); + difficultyArg = withRequiredArg( + "level", + "Difficulty level", + ArgTypes.forEnum("Difficulty", Difficulty.class) + ); + } + + @Override + protected CompletableFuture 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> playersArg; + private final RequiredArg 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 execute(CommandContext context) { + List 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 +} diff --git a/app/src/main/java/org/example/docs/commands/CommandContextExample.java b/app/src/main/java/org/example/docs/commands/CommandContextExample.java new file mode 100644 index 0000000..083d7a2 --- /dev/null +++ b/app/src/main/java/org/example/docs/commands/CommandContextExample.java @@ -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 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 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 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 playerArg; + private final RequiredArg 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 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 reasonArg; + + public OptionalArgsCommand() { + super("optionalargs", "Optional arguments example"); + reasonArg = withOptionalArg("reason", "Optional reason", ArgTypes.STRING); + } + + @Override + protected CompletableFuture 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 countArg; // Default: 1 + + public DefaultArgsCommand() { + super("defaultargs", "Default arguments example"); + countArg = withDefaultArg("count", "Number", ArgTypes.INTEGER, 1, "1"); + } + + @Override + protected CompletableFuture 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 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 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 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 targetArg; + + public PlayerRefFallbackCommand() { + super("playerreffallback", "PlayerRef fallback example"); + targetArg = withOptionalArg("target", "Target player", ArgTypes.PLAYER_REF); + } + + @Override + protected CompletableFuture 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 worldArg; + + public WorldFallbackCommand() { + super("worldfallback", "World fallback example"); + worldArg = withOptionalArg("world", "Target world", ArgTypes.WORLD); + } + + @Override + protected CompletableFuture 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 playerArg; + + public ErrorHandlingCommand() { + super("errorhandling", "Error handling example"); + playerArg = withRequiredArg("player", "Target player", ArgTypes.PLAYER_REF); + } + + @Override + protected CompletableFuture 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 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 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 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 itemArg; + private final OptionalArg targetArg; + private final DefaultArg 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 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 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; + } + } +} diff --git a/app/src/main/java/org/example/docs/commands/CreatingCommandsExample.java b/app/src/main/java/org/example/docs/commands/CreatingCommandsExample.java new file mode 100644 index 0000000..0c5d95e --- /dev/null +++ b/app/src/main/java/org/example/docs/commands/CreatingCommandsExample.java @@ -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 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 targetArg; + private final RequiredArg 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 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 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 execute(CommandContext context) { + return null; + } + } + + // From "Optional Arguments" section + public static class GiveWithOptionalCommand extends AbstractCommand { + + private final RequiredArg targetArg; + private final RequiredArg itemArg; + private final OptionalArg 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 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 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 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 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 execute(CommandContext context) { + // This only runs if --confirm was provided + return null; + } + } +} diff --git a/app/src/main/java/org/example/docs/commands/SubcommandsExample.java b/app/src/main/java/org/example/docs/commands/SubcommandsExample.java new file mode 100644 index 0000000..dc39f12 --- /dev/null +++ b/app/src/main/java/org/example/docs/commands/SubcommandsExample.java @@ -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 execute(CommandContext context) { + // This runs when no subcommand is specified + context.sender().sendMessage(Message.raw("Usage: /admin ")); + return null; + } + } + + // ============================================ + // From "Subcommand Implementation" + // CORRECTION: playerRef.getPlayer() doesn't exist + // ============================================ + public static class KickSubCommand extends AbstractCommand { + + private final RequiredArg playerArg; + private final OptionalArg 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 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 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 playerArg; + + public BanSubCommand() { + super("ban", "Ban a player from the server"); + playerArg = withRequiredArg("player", "Player to ban", ArgTypes.PLAYER_REF); + } + + @Override + protected CompletableFuture 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 playerArg; + + public MuteSubCommand() { + super("mute", "Mute a player"); + playerArg = withRequiredArg("player", "Player to mute", ArgTypes.PLAYER_REF); + } + + @Override + protected CompletableFuture 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 execute(CommandContext context) { + // Show usage for /manage users + context.sender().sendMessage(Message.raw("Usage: /manage users ")); + return null; + } + } + + // Nested subcommand stubs + public static class ListUsersCommand extends AbstractCommand { + public ListUsersCommand() { + super("list", "List all users"); + } + + @Override + protected CompletableFuture execute(CommandContext context) { + return null; + } + } + + public static class AddUserCommand extends AbstractCommand { + public AddUserCommand() { + super("add", "Add a user"); + } + + @Override + protected CompletableFuture execute(CommandContext context) { + return null; + } + } + + public static class RemoveUserCommand extends AbstractCommand { + public RemoveUserCommand() { + super("remove", "Remove a user"); + } + + @Override + protected CompletableFuture execute(CommandContext context) { + return null; + } + } + + // Placeholder manage subcommands + public static class ManageWorldsCommand extends AbstractCommand { + public ManageWorldsCommand() { + super("worlds", "World management"); + } + + @Override + protected CompletableFuture execute(CommandContext context) { + return null; + } + } + + public static class ManagePluginsCommand extends AbstractCommand { + public ManagePluginsCommand() { + super("plugins", "Plugin management"); + } + + @Override + protected CompletableFuture 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 execute(CommandContext context) { + return null; + } + } + + public static class TeleportHereCommand extends AbstractCommand { + private final RequiredArg playerArg; + + public TeleportHereCommand() { + super("here", "Teleport player to you"); + addAliases("h", "tome"); + playerArg = withRequiredArg("player", "Player to teleport", ArgTypes.PLAYER_REF); + } + + @Override + protected CompletableFuture 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 execute(CommandContext context) { + return null; + } + } + + // ============================================ + // From "Command Variants" section + // ============================================ + public static class TpCommand extends AbstractCommand { + + private final RequiredArg targetArg; + + public TpCommand() { + super("tp", "Teleport command"); + + // Main variant: /tp + targetArg = withRequiredArg("target", "Player to teleport to", ArgTypes.PLAYER_REF); + + // Add variant: /tp + addUsageVariant(new TpToPlayerVariant()); + + // Note: Position variant would need different argument types + } + + @Override + protected CompletableFuture 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 playerArg; + private final RequiredArg 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 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 execute(CommandContext context) { + return null; + } + } +} diff --git a/app/src/main/java/org/example/docs/coreconcepts/AssetsExample.java b/app/src/main/java/org/example/docs/coreconcepts/AssetsExample.java new file mode 100644 index 0000000..6348646 --- /dev/null +++ b/app/src/main/java/org/example/docs/coreconcepts/AssetsExample.java @@ -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"); + } +} diff --git a/app/src/main/java/org/example/docs/coreconcepts/CodecsExample.java b/app/src/main/java/org/example/docs/coreconcepts/CodecsExample.java new file mode 100644 index 0000000..7c5cdaa --- /dev/null +++ b/app/src/main/java/org/example/docs/coreconcepts/CodecsExample.java @@ -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 stringCodec = Codec.STRING; + Codec intCodec = Codec.INTEGER; + Codec longCodec = Codec.LONG; + Codec floatCodec = Codec.FLOAT; + Codec doubleCodec = Codec.DOUBLE; + Codec boolCodec = Codec.BOOLEAN; + Codec byteCodec = Codec.BYTE; + Codec shortCodec = Codec.SHORT; + } + + // ============================================ + // From "List and Map Codecs" section + // ============================================ + public void testCollectionCodecs() { + // Array of strings + Codec stringArray = new ArrayCodec<>(Codec.STRING, String[]::new); + + // Map with string keys + Codec> 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 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 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 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 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 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 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 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 mainConfig = withConfig("main", MyConfig.CODEC); + // Config dbConfig = withConfig("database", DatabaseConfig.CODEC); +} diff --git a/app/src/main/java/org/example/docs/coreconcepts/RegistriesExample.java b/app/src/main/java/org/example/docs/coreconcepts/RegistriesExample.java new file mode 100644 index 0000000..8966efc --- /dev/null +++ b/app/src/main/java/org/example/docs/coreconcepts/RegistriesExample.java @@ -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 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) repeating); + } + + // ============================================ + // From "Registration Timing" section (lines 159-176) + // ============================================ + // Note: These examples show plugin lifecycle methods + // setup() - Configuration only + // start() - Register everything here +} diff --git a/app/src/main/java/org/example/docs/coreconcepts/ThreadingExample.java b/app/src/main/java/org/example/docs/coreconcepts/ThreadingExample.java new file mode 100644 index 0000000..3dcbe81 --- /dev/null +++ b/app/src/main/java/org/example/docs/coreconcepts/ThreadingExample.java @@ -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 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 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 entityRef = ref.getReference(); + if (entityRef != null) { + applyData(entityRef, data); + } + }); + } + }); + }); + } + + private Object loadFromDatabase(UUID uuid) { return null; } + private void applyData(Ref 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; + } +} diff --git a/app/src/main/java/org/example/docs/effects/DynamicLightsExample.java b/app/src/main/java/org/example/docs/effects/DynamicLightsExample.java new file mode 100644 index 0000000..2660916 --- /dev/null +++ b/app/src/main/java/org/example/docs/effects/DynamicLightsExample.java @@ -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 entityRef, ComponentAccessor 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 entityRef, ComponentAccessor componentAccessor, ColorLight light) { + PersistentDynamicLight persistentLight = new PersistentDynamicLight(light); + componentAccessor.putComponent( + entityRef, + PersistentDynamicLight.getComponentType(), + persistentLight + ); + } + + // ============================================= + // Updating Light Properties (CORRECT - uses Store) + // ============================================= + public void updateLight(Ref entityRef, Store 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 entityRef, ComponentAccessor 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 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 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) + // ============================================= + private final Map, Integer> pulsingEntities = new ConcurrentHashMap<>(); + + public void onTick(float deltaTime, Store store) { + for (Map.Entry, Integer> entry : pulsingEntities.entrySet()) { + Ref 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 entityRef) { + pulsingEntities.put(entityRef, 0); + } + + public void stopPulsingLight(Ref entityRef) { + pulsingEntities.remove(entityRef); + } +} diff --git a/app/src/main/java/org/example/docs/effects/EffectsIndexExample.java b/app/src/main/java/org/example/docs/effects/EffectsIndexExample.java new file mode 100644 index 0000000..62ccb6c --- /dev/null +++ b/app/src/main/java/org/example/docs/effects/EffectsIndexExample.java @@ -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, 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 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 entityRef, ComponentAccessor 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 + // The actual API requires Store which is obtained from EntityStore.getStore() + // ============================================= + public void entityEffectExample(Ref entityRef, Store store, ComponentAccessor 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); + } + } +} diff --git a/app/src/main/java/org/example/docs/effects/EntityEffectsExample.java b/app/src/main/java/org/example/docs/effects/EntityEffectsExample.java new file mode 100644 index 0000000..65e9617 --- /dev/null +++ b/app/src/main/java/org/example/docs/effects/EntityEffectsExample.java @@ -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 +// - 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, 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 instead of EntityStore + // ============================================= + public void getEffectController(Ref entityRef, Store store) { + EffectControllerComponent controller = store.getComponent( + entityRef, + EffectControllerComponent.getComponentType() + ); + } + + // ============================================= + // Adding Effects - Basic Addition (CORRECTED) + // ============================================= + public void addBasicEffect(Ref entityRef, Store store, ComponentAccessor 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 entityRef, Store store, ComponentAccessor 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 entityRef, Store store, ComponentAccessor 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 entityRef, Store store, ComponentAccessor 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 entityRef, Store store, ComponentAccessor 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 playerArg; + private final RequiredArg effectArg; + private final RequiredArg durationArg; + + // Would be injected during plugin init + private Store store; + private ComponentAccessor 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 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 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 store, ComponentAccessor componentAccessor) { + EntityEffect poison = EntityEffect.getAssetMap().getAsset("poison"); + + // CORRECTED: Use player.getReference() instead of player.getEntityRef() + Ref 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 store, ComponentAccessor componentAccessor) { + Ref 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 store, ComponentAccessor componentAccessor) { + EntityEffect buff = EntityEffect.getAssetMap().getAsset("magic_power"); + Ref 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 + } +} diff --git a/app/src/main/java/org/example/docs/effects/ParticlesExample.java b/app/src/main/java/org/example/docs/effects/ParticlesExample.java new file mode 100644 index 0000000..7432f88 --- /dev/null +++ b/app/src/main/java/org/example/docs/effects/ParticlesExample.java @@ -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 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> playerRefs) { + ParticleUtil.spawnParticleEffect( + "magic_trail", + position, + playerRefs, + componentAccessor + ); + } + + // ============================================= + // Advanced: With source entity reference (DOCUMENTED - CORRECT) + // ============================================= + public void advancedSpawn(Vector3d position, Ref sourceEntityRef, List> 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 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> nearbyPlayerRefs) { + if (!nearbyPlayerRefs.isEmpty()) { + ParticleUtil.spawnParticleEffect( + "my_effect", + position, + nearbyPlayerRefs, + componentAccessor + ); + } + } +} diff --git a/app/src/main/java/org/example/docs/entities/EntitiesIndexExample.java b/app/src/main/java/org/example/docs/entities/EntitiesIndexExample.java new file mode 100644 index 0000000..43a3b6a --- /dev/null +++ b/app/src/main/java/org/example/docs/entities/EntitiesIndexExample.java @@ -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())); + } + }); + } +} diff --git a/app/src/main/java/org/example/docs/entities/EntityComponentsExample.java b/app/src/main/java/org/example/docs/entities/EntityComponentsExample.java new file mode 100644 index 0000000..64c5cb5 --- /dev/null +++ b/app/src/main/java/org/example/docs/entities/EntityComponentsExample.java @@ -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 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 store = ref.getStore(); + * Component comp = store.getComponent(ref, ComponentType); + * + * Ref only has: + * - getStore() - returns Store + * - 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 ref = playerRef.getReference(); + if (ref == null || !ref.isValid()) { + return; + } + + // CORRECT PATTERN: get store, then get component from store + Store store = ref.getStore(); + + // ============================================= + // TransformComponent - CORRECT PATTERN + // ============================================= + ComponentType 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 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 nameType = + DisplayNameComponent.getComponentType(); + DisplayNameComponent nameComp = store.getComponent(ref, nameType); + + if (nameComp != null) { + Message name = nameComp.getDisplayName(); + } + + // ============================================= + // EntityScaleComponent - CORRECT PATTERN + // ============================================= + ComponentType 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 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 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 archetype = store.getArchetype(ref); + + // Check for Invulnerable + ComponentType invulnType = + Invulnerable.getComponentType(); + boolean isInvulnerable = archetype.contains(invulnType); + + // Check for Intangible + ComponentType intangibleType = + Intangible.getComponentType(); + boolean isIntangible = archetype.contains(intangibleType); + + // Check for Interactable + ComponentType 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 + }); + } +} diff --git a/app/src/main/java/org/example/docs/entities/EntityHierarchyExample.java b/app/src/main/java/org/example/docs/entities/EntityHierarchyExample.java new file mode 100644 index 0000000..4e8f937 --- /dev/null +++ b/app/src/main/java/org/example/docs/entities/EntityHierarchyExample.java @@ -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, 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 ref = playerRef.getReference(); + if (ref != null && ref.isValid()) { + // This is how you access components in ECS + Store 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 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() + } +} diff --git a/app/src/main/java/org/example/docs/entities/PlayerApiExample.java b/app/src/main/java/org/example/docs/entities/PlayerApiExample.java new file mode 100644 index 0000000..840c07b --- /dev/null +++ b/app/src/main/java/org/example/docs/entities/PlayerApiExample.java @@ -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 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"); + }); + } +} diff --git a/app/src/main/java/org/example/docs/events/AsyncEventsExample.java b/app/src/main/java/org/example/docs/events/AsyncEventsExample.java new file mode 100644 index 0000000..a844915 --- /dev/null +++ b/app/src/main/java/org/example/docs/events/AsyncEventsExample.java @@ -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 - 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 spamCheckFuture = + CompletableFuture.supplyAsync(() -> checkForSpam(event.getContent())); + + CompletableFuture 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 filterContent( + CompletableFuture 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 processChat( + CompletableFuture 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 logChat( + CompletableFuture 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; } +} diff --git a/app/src/main/java/org/example/docs/events/EventSystemExample.java b/app/src/main/java/org/example/docs/events/EventSystemExample.java new file mode 100644 index 0000000..6a2f48c --- /dev/null +++ b/app/src/main/java/org/example/docs/events/EventSystemExample.java @@ -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 + }); + } +} diff --git a/app/src/main/java/org/example/docs/gettingstarted/CreatingPluginExample.java b/app/src/main/java/org/example/docs/gettingstarted/CreatingPluginExample.java new file mode 100644 index 0000000..85671bc --- /dev/null +++ b/app/src/main/java/org/example/docs/gettingstarted/CreatingPluginExample.java @@ -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); + } +} diff --git a/app/src/main/java/org/example/docs/gettingstarted/PluginLifecycleExample.java b/app/src/main/java/org/example/docs/gettingstarted/PluginLifecycleExample.java new file mode 100644 index 0000000..f57c0cd --- /dev/null +++ b/app/src/main/java/org/example/docs/gettingstarted/PluginLifecycleExample.java @@ -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 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(); + } +} diff --git a/app/src/main/java/org/example/docs/interactions/InteractionsIndexExample.java b/app/src/main/java/org/example/docs/interactions/InteractionsIndexExample.java new file mode 100644 index 0000000..94dbeb7 --- /dev/null +++ b/app/src/main/java/org/example/docs/interactions/InteractionsIndexExample.java @@ -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 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; + } + } +} diff --git a/app/src/main/java/org/example/docs/interactions/SelectorsExample.java b/app/src/main/java/org/example/docs/interactions/SelectorsExample.java new file mode 100644 index 0000000..fe5fbf3 --- /dev/null +++ b/app/src/main/java/org/example/docs/interactions/SelectorsExample.java @@ -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 commandBuffer, + Ref 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 only (not BiConsumer with Vector4d!) + public void selectNearbyEntitiesExample(CommandBuffer commandBuffer, + Ref attackerRef, + double range) { + Selector.selectNearbyEntities( + commandBuffer, + attackerRef, + range, + (Consumer>) entityRef -> { + // Process each entity + }, + (Predicate>) entityRef -> { + // Filter predicate - return true to include + return true; + } + ); + } + + // Example from documentation - Select Nearby Entities from position with ComponentAccessor + public void selectNearbyEntitiesFromPosition(ComponentAccessor 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 entityRef) { + // Correct way to check validity + if (entityRef.isValid()) { + // Safe to access entity components + // ... perform operations + } + } +} diff --git a/app/src/main/java/org/example/docs/inventory/ContainersExample.java b/app/src/main/java/org/example/docs/inventory/ContainersExample.java new file mode 100644 index 0000000..934481e --- /dev/null +++ b/app/src/main/java/org/example/docs/inventory/ContainersExample.java @@ -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 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 droppedItems = container.dropAllItemStacks(); + + // Remove all items (returns list) + List removedItems = container.removeAllItemStacks(); + + // Clear entire player inventory + inv.clear(); + + // Drop all from player inventory + List 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 items = List.of( + new ItemStack("iron_ore", 10), + new ItemStack("gold_ore", 5) + ); + + if (container.canAddItemStacks(items)) { + ListTransaction 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 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> 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 + } + } +} diff --git a/app/src/main/java/org/example/docs/inventory/InventoryExample.java b/app/src/main/java/org/example/docs/inventory/InventoryExample.java new file mode 100644 index 0000000..a63b02f --- /dev/null +++ b/app/src/main/java/org/example/docs/inventory/InventoryExample.java @@ -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 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); + } +} diff --git a/app/src/main/java/org/example/docs/inventory/ItemStacksExample.java b/app/src/main/java/org/example/docs/inventory/ItemStacksExample.java new file mode 100644 index 0000000..d32f91a --- /dev/null +++ b/app/src/main/java/org/example/docs/inventory/ItemStacksExample.java @@ -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 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! + } +} diff --git a/app/src/main/java/org/example/docs/inventory/TransactionsExample.java b/app/src/main/java/org/example/docs/inventory/TransactionsExample.java new file mode 100644 index 0000000..a1dfce4 --- /dev/null +++ b/app/src/main/java/org/example/docs/inventory/TransactionsExample.java @@ -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 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 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 items = List.of( + new ItemStack("stone", 64), + new ItemStack("wood", 64) + ); + + ListTransaction transaction = container.addItemStacks(items); + + if (transaction.succeeded()) { + List 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 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! + } +} diff --git a/app/src/main/java/org/example/docs/permissions/PermissionsExample.java b/app/src/main/java/org/example/docs/permissions/PermissionsExample.java new file mode 100644 index 0000000..9ac589a --- /dev/null +++ b/app/src/main/java/org/example/docs/permissions/PermissionsExample.java @@ -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 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 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 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 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 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> vipPermissions = new ConcurrentHashMap<>(); + private final Map> vipGroups = new ConcurrentHashMap<>(); + + @Override + @Nonnull + public String getName() { + return "VIPPermissionProvider"; + } + + @Override + public void addUserPermissions(@Nonnull UUID uuid, @Nonnull Set permissions) { + vipPermissions.computeIfAbsent(uuid, k -> new HashSet<>()).addAll(permissions); + } + + @Override + public void removeUserPermissions(@Nonnull UUID uuid, @Nonnull Set permissions) { + Set userPerms = vipPermissions.get(uuid); + if (userPerms != null) { + userPerms.removeAll(permissions); + } + } + + @Override + public Set getUserPermissions(@Nonnull UUID uuid) { + return vipPermissions.getOrDefault(uuid, Collections.emptySet()); + } + + @Override + public void addGroupPermissions(@Nonnull String group, @Nonnull Set permissions) { + // Not implemented in this example + } + + @Override + public void removeGroupPermissions(@Nonnull String group, @Nonnull Set permissions) { + // Not implemented in this example + } + + @Override + public Set 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 groups = vipGroups.get(uuid); + if (groups != null) { + groups.remove(group); + } + } + + @Override + public Set 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 + } +} diff --git a/app/src/main/java/org/example/docs/reference/AllRegistriesExample.java b/app/src/main/java/org/example/docs/reference/AllRegistriesExample.java new file mode 100644 index 0000000..153d2ea --- /dev/null +++ b/app/src/main/java/org/example/docs/reference/AllRegistriesExample.java @@ -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 + public void commandRegistryExample() { + commands.registerCommand(new AbstractCommand("hello", "my_plugin.commands.hello.description") { + @Override + protected CompletableFuture 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 itemMap = Item.getAssetMap(); + Item sword = itemMap.getAsset("iron_sword"); + + // Get block types - also uses string key directly + DefaultAssetMap blockMap = BlockType.getAssetMap(); + BlockType stone = blockMap.getAsset("stone"); + } +} diff --git a/app/src/main/java/org/example/docs/tasks/TasksExample.java b/app/src/main/java/org/example/docs/tasks/TasksExample.java new file mode 100644 index 0000000..c9c23cb --- /dev/null +++ b/app/src/main/java/org/example/docs/tasks/TasksExample.java @@ -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) and registerTask(ScheduledFuture) +// - 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 saveTask = (ScheduledFuture) 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 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 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 playerData = new ConcurrentHashMap<>(); + private final java.util.Set 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"; } +} diff --git a/app/src/main/java/org/example/docs/ui/CustomPagesExample.java b/app/src/main/java/org/example/docs/ui/CustomPagesExample.java new file mode 100644 index 0000000..309c598 --- /dev/null +++ b/app/src/main/java/org/example/docs/ui/CustomPagesExample.java @@ -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 ref, UICommandBuilder builder, + UIEventBuilder events, Store 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 ref, UICommandBuilder builder, + UIEventBuilder events, Store 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 ref, Store 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 ref = player.getReference(); + Store 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 ref = player.getReference(); + Store 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 items; + + public ShopPage(PlayerRef ref, List items) { + super(ref, CustomPageLifetime.CanDismiss); + this.items = items; + } + + @Override + public void build(Ref ref, UICommandBuilder builder, + UIEventBuilder events, Store 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 ref, Store 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, Store, CustomUIPage, Window...) + public void pageWithWindowsExample(Player player, ItemContainer craftingContainer) { + PageManager pageManager = player.getPageManager(); + Ref ref = player.getReference(); + Store 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 + ); + } +} diff --git a/app/src/main/java/org/example/docs/ui/HudManagerExample.java b/app/src/main/java/org/example/docs/ui/HudManagerExample.java new file mode 100644 index 0000000..348b985 --- /dev/null +++ b/app/src/main/java/org/example/docs/ui/HudManagerExample.java @@ -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 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 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 previous = new HashSet<>(hud.getVisibleHudComponents()); + + // Clear all HUD + hud.setVisibleHudComponents(player.getPlayerRef()); + } + + public void exitCinematicMode(Player player, Set 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 + ); + } +} diff --git a/app/src/main/java/org/example/docs/ui/UiIndexExample.java b/app/src/main/java/org/example/docs/ui/UiIndexExample.java new file mode 100644 index 0000000..3ee7fe4 --- /dev/null +++ b/app/src/main/java/org/example/docs/ui/UiIndexExample.java @@ -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 ref, UICommandBuilder builder, + UIEventBuilder events, Store 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 ref = player.getReference(); + Store 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 + ); + } +} diff --git a/app/src/main/java/org/example/docs/ui/WindowsExample.java b/app/src/main/java/org/example/docs/ui/WindowsExample.java new file mode 100644 index 0000000..61899d0 --- /dev/null +++ b/app/src/main/java/org/example/docs/ui/WindowsExample.java @@ -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 ref = player.getReference(); + Store 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 ref = player.getReference(); + Store 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 +} diff --git a/app/src/main/java/org/example/docs/world/BlocksExample.java b/app/src/main/java/org/example/docs/world/BlocksExample.java new file mode 100644 index 0000000..5cd8517 --- /dev/null +++ b/app/src/main/java/org/example/docs/world/BlocksExample.java @@ -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); + } + } + } + } +} diff --git a/app/src/main/java/org/example/docs/world/ChunksExample.java b/app/src/main/java/org/example/docs/world/ChunksExample.java new file mode 100644 index 0000000..3e8dfbc --- /dev/null +++ b/app/src/main/java/org/example/docs/world/ChunksExample.java @@ -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 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); + } +} diff --git a/app/src/main/java/org/example/docs/world/PlayerRefsExample.java b/app/src/main/java/org/example/docs/world/PlayerRefsExample.java new file mode 100644 index 0000000..8e7c41b --- /dev/null +++ b/app/src/main/java/org/example/docs/world/PlayerRefsExample.java @@ -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 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 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 playerRefs = world.getPlayerRefs(); + + // Get all active players (returns List, not Collection) + List 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)); + } + } +} diff --git a/app/src/main/java/org/example/docs/world/UniverseWorldsExample.java b/app/src/main/java/org/example/docs/world/UniverseWorldsExample.java new file mode 100644 index 0000000..78badb0 --- /dev/null +++ b/app/src/main/java/org/example/docs/world/UniverseWorldsExample.java @@ -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, not Collection + +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 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, not Collection + List players = world.getPlayers(); + + // Get player refs + Collection 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()); + } + }); + } +} diff --git a/app/src/main/resources/manifest.json b/app/src/main/resources/manifest.json new file mode 100644 index 0000000..9a786e9 --- /dev/null +++ b/app/src/main/resources/manifest.json @@ -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": [] +} diff --git a/app/src/test/java/org/example/ExamplePluginTest.java b/app/src/test/java/org/example/ExamplePluginTest.java new file mode 100644 index 0000000..66f2679 --- /dev/null +++ b/app/src/test/java/org/example/ExamplePluginTest.java @@ -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")); + } +} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..a600976 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,6 @@ +plugins { + java +} + +// Root project configuration +// The actual plugin code is in the 'app' module diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..377538c --- /dev/null +++ b/gradle.properties @@ -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 + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..f50a589 --- /dev/null +++ b/gradle/libs.versions.toml @@ -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" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f8e1ee3 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..23449a2 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..adff685 --- /dev/null +++ b/gradlew @@ -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" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..c4bdd3a --- /dev/null +++ b/gradlew.bat @@ -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 diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..08530dc --- /dev/null +++ b/settings.gradle.kts @@ -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") diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000..5e17c1d --- /dev/null +++ b/setup.sh @@ -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