Saltar a contenido

Creando tu Primer Mod Completo

Este tutorial te guiará paso a paso en la creación de un mod completo para Hytale, combinando early plugins con contenido personalizado. Crearás bloques, items y entidades custom con sus propias interacciones.

Proyecto: "Mystic Ores"

Vamos a crear un mod que agrega:

  • Bloque: Mystic Ore (mineral mágico)
  • Item: Mystic Gem (gema obtenida del mineral)
  • Item: Mystic Staff (bastón con poderes)
  • Interacción: Spell Casting (lanzar hechizos)
  • Sistema: Mana management

Paso 1: Estructura del Proyecto

Crear Directorios

mkdir mystic-ores-mod
cd mystic-ores-mod

# Estructura de código
mkdir -p src/main/java/com/ejemplo/mysticores
mkdir -p src/main/java/com/ejemplo/mysticores/interactions
mkdir -p src/main/java/com/ejemplo/mysticores/systems
mkdir -p src/main/resources/META-INF/services

# Estructura de assets
mkdir -p assets/blocks
mkdir -p assets/items
mkdir -p assets/interactions
mkdir -p assets/models
mkdir -p assets/textures

Estructura Final

mystic-ores-mod/
├── src/
│   └── main/
│       ├── java/
│       │   └── com/
│       │       └── ejemplo/
│       │           └── mysticores/
│       │               ├── MysticOresPlugin.java
│       │               ├── MysticOres.java
│       │               ├── interactions/
│       │               │   ├── MineMysticOreInteraction.java
│       │               │   ├── CastSpellInteraction.java
│       │               │   └── ChargeManaInteraction.java
│       │               └── systems/
│       │                   └── ManaSystem.java
│       └── resources/
│           └── META-INF/
│               └── services/
│                   └── com.hypixel.hytale.plugin.early.ClassTransformer
├── assets/
│   ├── blocks/
│   │   └── mystic_ore.json
│   ├── items/
│   │   ├── mystic_gem.json
│   │   └── mystic_staff.json
│   ├── interactions/
│   │   ├── mine_mystic_ore.json
│   │   ├── cast_spell.json
│   │   └── charge_mana.json
│   ├── models/
│   │   ├── mystic_ore.vox
│   │   ├── mystic_gem.vox
│   │   └── mystic_staff.vox
│   └── textures/
│       ├── mystic_ore.png
│       ├── mystic_gem.png
│       └── mystic_staff.png
├── build.gradle
├── settings.gradle
├── mod.json
├── README.md
└── LICENSE

Paso 2: Configurar Build System

build.gradle

plugins {
    id 'java'
}

group = 'com.ejemplo.mysticores'
version = '1.0.0'

sourceCompatibility = '17'
targetCompatibility = '17'

repositories {
    mavenCentral()
}

dependencies {
    // Hytale API (descompilada)
    compileOnly files('libs/hytale-api.jar')

    // ASM para transformación de bytecode
    implementation 'org.ow2.asm:asm:9.6'
    implementation 'org.ow2.asm:asm-commons:9.6'
    implementation 'org.ow2.asm:asm-util:9.6'

    // FastUtil (usado por Hytale)
    compileOnly 'it.unimi.dsi:fastutil:8.5.12'

    // Testing
    testImplementation 'org.junit.jupiter:junit-jupiter:5.9.3'
}

jar {
    archiveFileName = 'mystic-ores-${version}.jar'

    manifest {
        attributes(
            'Implementation-Title': 'Mystic Ores',
            'Implementation-Version': version,
            'Mod-Id': 'mysticores',
            'Mod-Name': 'Mystic Ores',
            'Main-Class': 'com.ejemplo.mysticores.MysticOresPlugin'
        )
    }

    // Incluir dependencias
    from {
        configurations.runtimeClasspath.collect {
            it.isDirectory() ? it : zipTree(it)
        }
    }

    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

tasks.named('test') {
    useJUnitPlatform()
}

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}

settings.gradle

rootProject.name = 'mystic-ores-mod'

mod.json

{
  "id": "mysticores",
  "name": "Mystic Ores",
  "version": "1.0.0",
  "description": "Adds magical ores and mystical items to Hytale",
  "authors": ["Tu Nombre"],
  "website": "https://ejemplo.com/mystic-ores",
  "license": "MIT",
  "icon": "assets/textures/icon.png",

  "dependencies": {
    "hytale": ">=1.0.0"
  },

  "earlyPlugin": {
    "main": "com.ejemplo.mysticores.MysticOresPlugin",
    "priority": 100
  },

  "assets": {
    "blocks": "assets/blocks",
    "items": "assets/items",
    "interactions": "assets/interactions",
    "models": "assets/models",
    "textures": "assets/textures"
  }
}

Paso 3: Early Plugin Base

MysticOresPlugin.java

package com.ejemplo.mysticores;

import com.hypixel.hytale.plugin.early.ClassTransformer;
import org.objectweb.asm.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * Early plugin para Mystic Ores.
 *
 * Este plugin se carga antes del servidor y:
 * - Inyecta inicialización del mod
 * - Registra todos los componentes custom
 */
public class MysticOresPlugin implements ClassTransformer {

    private static final String TARGET_CLASS =
        "com.hypixel.hytale.server.core.HytaleServer";

    @Override
    public int priority() {
        return 100;
    }

    @Override
    @Nullable
    public byte[] transform(@Nonnull String className,
                           @Nonnull String classPath,
                           @Nonnull byte[] bytecode) {
        try {
            if (className.equals(TARGET_CLASS)) {
                System.out.println("[MysticOres] Initializing mod...");
                return injectModInitialization(bytecode);
            }
        } catch (Exception e) {
            System.err.println("[MysticOres] Error transforming " +
                className + ": " + e.getMessage());
            e.printStackTrace();
        }

        return null;
    }

    /**
     * Inyecta llamada a MysticOres.initialize() en el constructor
     * de HytaleServer.
     */
    private byte[] injectModInitialization(byte[] bytecode) {
        ClassReader reader = new ClassReader(bytecode);
        ClassWriter writer = new ClassWriter(
            reader,
            ClassWriter.COMPUTE_FRAMES
        );

        ClassVisitor visitor = new ClassVisitor(Opcodes.ASM9, writer) {
            @Override
            public MethodVisitor visitMethod(int access, String name,
                                             String descriptor, String signature,
                                             String[] exceptions) {
                MethodVisitor mv = super.visitMethod(
                    access, name, descriptor, signature, exceptions
                );

                // Interceptar constructor
                if (name.equals("<init>")) {
                    return new ConstructorVisitor(Opcodes.ASM9, mv);
                }

                return mv;
            }
        };

        reader.accept(visitor, 0);
        return writer.toByteArray();
    }

    /**
     * MethodVisitor que inyecta la inicialización del mod.
     */
    private static class ConstructorVisitor extends MethodVisitor {

        public ConstructorVisitor(int api, MethodVisitor mv) {
            super(api, mv);
        }

        @Override
        public void visitCode() {
            super.visitCode();

            // Inyectar: MysticOres.initialize();
            mv.visitMethodInsn(
                Opcodes.INVOKESTATIC,
                "com/ejemplo/mysticores/MysticOres",
                "initialize",
                "()V",
                false
            );
        }
    }
}

Service Loader Registration

src/main/resources/META-INF/services/com.hypixel.hytale.plugin.early.ClassTransformer:

com.ejemplo.mysticores.MysticOresPlugin

Paso 4: Mod Core

MysticOres.java

package com.ejemplo.mysticores;

import com.ejemplo.mysticores.interactions.*;
import com.ejemplo.mysticores.systems.ManaSystem;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction;
import java.util.List;

/**
 * Clase principal del mod Mystic Ores.
 *
 * Maneja el registro de todo el contenido custom.
 */
public class MysticOres {

    public static final String MOD_ID = "mysticores";
    public static final String MOD_NAME = "Mystic Ores";
    public static final String VERSION = "1.0.0";

    private static boolean initialized = false;

    /**
     * Inicializa el mod.
     * Llamado desde MysticOresPlugin durante el inicio del servidor.
     */
    public static void initialize() {
        if (initialized) {
            System.out.println("[MysticOres] Already initialized, skipping");
            return;
        }

        System.out.println("========================================");
        System.out.println("  " + MOD_NAME + " v" + VERSION);
        System.out.println("  Initializing mod...");
        System.out.println("========================================");

        try {
            // Inicializar sistemas
            initializeSystems();

            // Registrar interacciones
            registerInteractions();

            // Los bloques e items se cargan automáticamente desde assets/

            initialized = true;
            System.out.println("[MysticOres] Initialization complete!");

        } catch (Exception e) {
            System.err.println("[MysticOres] Error during initialization: " +
                e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * Inicializa sistemas del mod.
     */
    private static void initializeSystems() {
        System.out.println("[MysticOres] Initializing systems...");

        // Inicializar sistema de mana
        ManaSystem.initialize();

        System.out.println("[MysticOres] Systems initialized");
    }

    /**
     * Registra todas las interacciones custom.
     */
    private static void registerInteractions() {
        System.out.println("[MysticOres] Registering interactions...");

        var store = Interaction.getAssetStore();
        store.loadAssets(MOD_ID + ":" + MOD_ID, List.of(
            new MineMysticOreInteraction(),
            new CastSpellInteraction(),
            new ChargeManaInteraction()
        ));

        System.out.println("[MysticOres] Registered 3 interactions");
    }

    /**
     * Verifica si el mod está inicializado.
     */
    public static boolean isInitialized() {
        return initialized;
    }
}

Paso 5: Sistema de Mana

ManaSystem.java

package com.ejemplo.mysticores.systems;

import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.meta.MetaKey;
import com.hypixel.hytale.server.core.meta.MetaRegistry;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Sistema de gestión de mana para jugadores.
 *
 * Almacena y gestiona el mana de cada jugador usando el Meta System.
 */
public class ManaSystem {

    // Registro de metadatos para jugadores
    private static final MetaRegistry<Player> PLAYER_META_REGISTRY =
        new MetaRegistry<>();

    // Key para almacenar datos de mana
    private static MetaKey<ManaData> MANA_KEY;

    // Cache local para acceso rápido
    private static final Map<Player, ManaData> MANA_CACHE =
        new ConcurrentHashMap<>();

    // Configuración
    private static final int DEFAULT_MAX_MANA = 100;
    private static final int DEFAULT_REGEN_RATE = 1; // por segundo

    /**
     * Inicializa el sistema de mana.
     */
    public static void initialize() {
        System.out.println("[ManaSystem] Initializing...");

        // Registrar metadata key para mana
        MANA_KEY = PLAYER_META_REGISTRY.registerMetaObject(
            player -> new ManaData(DEFAULT_MAX_MANA),
            true,  // Persistente
            "mysticores:mana",
            ManaData.CODEC
        );

        System.out.println("[ManaSystem] Initialized");
    }

    /**
     * Obtiene los datos de mana de un jugador.
     */
    public static ManaData getMana(Player player) {
        return MANA_CACHE.computeIfAbsent(player, p -> {
            // Intentar cargar desde meta store
            var metaStore = p.getMetaStore();
            if (metaStore != null) {
                ManaData data = metaStore.getMetaObject(MANA_KEY);
                if (data != null) {
                    return data;
                }
            }

            // Crear nuevo si no existe
            return new ManaData(DEFAULT_MAX_MANA);
        });
    }

    /**
     * Consume mana de un jugador.
     *
     * @return true si había suficiente mana
     */
    public static boolean consumeMana(Player player, int amount) {
        ManaData mana = getMana(player);

        if (mana.current < amount) {
            return false;
        }

        mana.current -= amount;
        saveMana(player, mana);
        return true;
    }

    /**
     * Restaura mana de un jugador.
     */
    public static void restoreMana(Player player, int amount) {
        ManaData mana = getMana(player);
        mana.current = Math.min(mana.current + amount, mana.max);
        saveMana(player, mana);
    }

    /**
     * Establece el mana máximo de un jugador.
     */
    public static void setMaxMana(Player player, int maxMana) {
        ManaData mana = getMana(player);
        mana.max = maxMana;
        mana.current = Math.min(mana.current, maxMana);
        saveMana(player, mana);
    }

    /**
     * Guarda los datos de mana en el meta store.
     */
    private static void saveMana(Player player, ManaData data) {
        var metaStore = player.getMetaStore();
        if (metaStore != null) {
            metaStore.putMetaObject(MANA_KEY, data);
        }
        MANA_CACHE.put(player, data);
    }

    /**
     * Limpia cache cuando un jugador se desconecta.
     */
    public static void cleanupPlayer(Player player) {
        MANA_CACHE.remove(player);
    }

    /**
     * Datos de mana de un jugador.
     */
    public static class ManaData {
        public int current;
        public int max;

        public ManaData(int max) {
            this.current = max;
            this.max = max;
        }

        public ManaData(int current, int max) {
            this.current = current;
            this.max = max;
        }

        /**
         * Codec para serialización.
         */
        public static final Codec<ManaData> CODEC = Codec.INT.flatMap(
            current -> Codec.INT.map(
                max -> new ManaData(current, max)
            )
        );

        public float getPercentage() {
            return max == 0 ? 0 : (float) current / max;
        }

        @Override
        public String toString() {
            return current + "/" + max + " (" +
                String.format("%.1f%%", getPercentage() * 100) + ")";
        }
    }
}

Paso 6: Interacciones Custom

MineMysticOreInteraction.java

package com.ejemplo.mysticores.interactions;

import com.hypixel.hytale.protocol.InteractionState;
import com.hypixel.hytale.protocol.InteractionType;
import com.hypixel.hytale.server.core.entity.InteractionContext;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction;

/**
 * Interacción para minar Mystic Ore.
 *
 * Otorga Mystic Gems cuando se mina el bloque.
 */
public class MineMysticOreInteraction extends SimpleInstantInteraction {

    public MineMysticOreInteraction() {
        this.id = "mysticores:mine_mystic_ore";
        this.runTime = 3.0f; // 3 segundos para minar
    }

    @Override
    protected void firstRun(InteractionType type,
                           InteractionContext context,
                           CooldownHandler cooldownHandler) {
        var entityRef = context.getEntity();
        var buffer = context.getCommandBuffer();
        var entity = buffer.getEntity(entityRef);

        if (!(entity instanceof Player)) {
            context.getState().state = InteractionState.Failed;
            return;
        }

        Player player = (Player) entity;

        // Calcular gemas obtenidas (1-3 aleatorio)
        int gemCount = 1 + (int) (Math.random() * 3);

        // Agregar gemas al inventario
        // (Simplificado - en producción usar ItemStack y addToInventory)
        player.sendMessage("§a¡Obtuviste " + gemCount + " Mystic Gems!");

        // Efecto de partículas (ejemplo)
        // spawnParticles(player.getPosition(), "mysticores:mining_sparkle");

        context.getState().state = InteractionState.Finished;
    }
}

CastSpellInteraction.java

package com.ejemplo.mysticores.interactions;

import com.ejemplo.mysticores.systems.ManaSystem;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.protocol.InteractionState;
import com.hypixel.hytale.protocol.InteractionType;
import com.hypixel.hytale.server.core.entity.InteractionContext;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction;

/**
 * Interacción para lanzar hechizos con el Mystic Staff.
 *
 * Consume mana y lanza un proyectil mágico.
 */
public class CastSpellInteraction extends SimpleInstantInteraction {

    private static final int MANA_COST = 20;
    private static final float SPELL_DAMAGE = 15.0f;

    public CastSpellInteraction() {
        this.id = "mysticores:cast_spell";
        this.runTime = 0.5f; // Animación de casting
    }

    @Override
    protected void firstRun(InteractionType type,
                           InteractionContext context,
                           CooldownHandler cooldownHandler) {
        var entityRef = context.getEntity();
        var buffer = context.getCommandBuffer();
        var entity = buffer.getEntity(entityRef);

        if (!(entity instanceof Player)) {
            context.getState().state = InteractionState.Failed;
            return;
        }

        Player player = (Player) entity;

        // Verificar mana
        if (!ManaSystem.consumeMana(player, MANA_COST)) {
            player.sendMessage("§c¡No tienes suficiente mana!");
            context.getState().state = InteractionState.Failed;
            return;
        }

        // Lanzar proyectil
        launchSpellProjectile(player);

        // Mostrar mana restante
        var mana = ManaSystem.getMana(player);
        player.sendMessage("§b✦ Mana: " + mana.toString());

        context.getState().state = InteractionState.Finished;
    }

    /**
     * Lanza un proyectil mágico en la dirección del jugador.
     */
    private void launchSpellProjectile(Player player) {
        Vector3d position = player.getPosition();
        Vector3d direction = player.getLookDirection();

        // Crear proyectil
        // (Simplificado - en producción crear entidad de proyectil)
        System.out.println("[CastSpell] Launching spell from " + position +
            " in direction " + direction);

        // Efectos visuales
        // spawnParticles(position, "mysticores:spell_trail");
        // playSound(position, "mysticores:spell_cast");

        // En implementación real:
        // - Crear entidad de proyectil
        // - Aplicar física y colisiones
        // - Detectar impactos
        // - Aplicar daño
    }
}

ChargeManaInteraction.java

package com.ejemplo.mysticores.interactions;

import com.ejemplo.mysticores.systems.ManaSystem;
import com.hypixel.hytale.protocol.InteractionState;
import com.hypixel.hytale.protocol.InteractionType;
import com.hypixel.hytale.server.core.entity.InteractionContext;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction;

/**
 * Interacción para recargar mana.
 *
 * El jugador medita para restaurar mana.
 */
public class ChargeManaInteraction extends SimpleInstantInteraction {

    private static final int MANA_RESTORE = 50;

    public ChargeManaInteraction() {
        this.id = "mysticores:charge_mana";
        this.runTime = 2.0f; // 2 segundos de meditación
    }

    @Override
    protected void firstRun(InteractionType type,
                           InteractionContext context,
                           CooldownHandler cooldownHandler) {
        var entityRef = context.getEntity();
        var buffer = context.getCommandBuffer();
        var entity = buffer.getEntity(entityRef);

        if (!(entity instanceof Player)) {
            context.getState().state = InteractionState.Failed;
            return;
        }

        Player player = (Player) entity;

        // Restaurar mana
        ManaSystem.restoreMana(player, MANA_RESTORE);

        // Mostrar mana actualizado
        var mana = ManaSystem.getMana(player);
        player.sendMessage("§b✦ Mana restaurado: " + mana.toString());

        // Efectos visuales
        // spawnParticles(player.getPosition(), "mysticores:mana_charge");
        // playSound(player.getPosition(), "mysticores:charge_complete");

        context.getState().state = InteractionState.Finished;
    }
}

Paso 7: Definiciones de Assets

assets/blocks/mystic_ore.json

{
  "id": "mysticores:mystic_ore",
  "name": "Mystic Ore",
  "type": "Ore",

  "hardness": 4.0,
  "resistance": 3.0,

  "model": "mysticores:models/mystic_ore",
  "texture": "mysticores:textures/mystic_ore",

  "drops": [
    {
      "item": "mysticores:mystic_gem",
      "count": {
        "min": 1,
        "max": 3
      },
      "chance": 1.0
    }
  ],

  "interactions": {
    "Mine": "mysticores:mine_mystic_ore"
  },

  "light": {
    "emission": 5
  },

  "sounds": {
    "break": "hytale:block.stone.break",
    "place": "hytale:block.stone.place",
    "step": "hytale:block.stone.step"
  },

  "metadata": {
    "rarity": "Rare",
    "biomes": ["Mountains", "Underground"],
    "minDepth": 16,
    "maxDepth": 64
  }
}

assets/items/mystic_gem.json

{
  "id": "mysticores:mystic_gem",
  "name": "Mystic Gem",
  "type": "Material",

  "maxStackSize": 64,

  "model": "mysticores:models/mystic_gem",
  "texture": "mysticores:textures/mystic_gem",

  "rarity": "Rare",

  "metadata": {
    "description": "A mysterious gem pulsing with magical energy",
    "glowing": true
  }
}

assets/items/mystic_staff.json

{
  "id": "mysticores:mystic_staff",
  "name": "Mystic Staff",
  "type": "Tool",

  "maxStackSize": 1,
  "durability": 500,

  "model": "mysticores:models/mystic_staff",
  "texture": "mysticores:textures/mystic_staff",

  "interactions": {
    "RightClick": "mysticores:cast_spell",
    "Sneak+RightClick": "mysticores:charge_mana"
  },

  "damage": 5,
  "attackSpeed": 1.2,

  "rarity": "Epic",

  "crafting": {
    "recipe": [
      ["", "", "mysticores:mystic_gem"],
      ["", "hytale:stick", ""],
      ["hytale:stick", "", ""]
    ]
  },

  "metadata": {
    "description": "A staff infused with mystical power",
    "manaCost": 20,
    "spellDamage": 15
  }
}

Paso 8: Compilar y Empaquetar

Compilar el Mod

# Limpiar builds anteriores
./gradlew clean

# Compilar
./gradlew build

# El JAR estará en: build/libs/mystic-ores-1.0.0.jar

Estructura del JAR

mystic-ores-1.0.0.jar
├── com/
│   └── ejemplo/
│       └── mysticores/
│           ├── MysticOresPlugin.class
│           ├── MysticOres.class
│           ├── interactions/
│           └── systems/
├── META-INF/
│   ├── services/
│   │   └── com.hypixel.hytale.plugin.early.ClassTransformer
│   └── MANIFEST.MF
└── (dependencias incluidas)

Empaquetar con Assets

# Crear directorio de distribución
mkdir -p dist/mystic-ores

# Copiar JAR del plugin
cp build/libs/mystic-ores-1.0.0.jar dist/mystic-ores/

# Copiar assets
cp -r assets dist/mystic-ores/

# Copiar metadata
cp mod.json dist/mystic-ores/
cp README.md dist/mystic-ores/
cp LICENSE dist/mystic-ores/

# Crear archivo ZIP para distribución
cd dist
zip -r mystic-ores-1.0.0.zip mystic-ores/

Paso 9: Instalación y Pruebas

Instalar en Servidor

# Estructura del servidor
servidor/
├── earlyplugins/
   └── mystic-ores-1.0.0.jar
├── mods/
   └── mystic-ores/
       ├── assets/
       └── mod.json
└── hytale-server.jar
# Copiar early plugin
cp mystic-ores-1.0.0.jar /ruta/servidor/earlyplugins/

# Copiar assets y metadata
cp -r assets /ruta/servidor/mods/mystic-ores/
cp mod.json /ruta/servidor/mods/mystic-ores/

Ejecutar Servidor

cd /ruta/servidor
java -jar hytale-server.jar --accept-early-plugins

Salida esperada:

[EarlyPlugin] Found: mystic-ores-1.0.0.jar
[EarlyPlugin] Loading transformer: com.ejemplo.mysticores.MysticOresPlugin (priority=100)
[MysticOres] Initializing mod...
========================================
  Mystic Ores v1.0.0
  Initializing mod...
========================================
[MysticOres] Initializing systems...
[ManaSystem] Initializing...
[ManaSystem] Initialized
[MysticOres] Systems initialized
[MysticOres] Registering interactions...
[MysticOres] Registered 3 interactions
[MysticOres] Initialization complete!
INFO - HytaleServer starting...

Probar en el Juego

  1. Encontrar Mystic Ore
  2. Generar o colocar bloque de Mystic Ore
  3. Minar para obtener Mystic Gems

  4. Craftear Mystic Staff

  5. Usar receta definida en el JSON
  6. Obtener Mystic Staff

  7. Lanzar Hechizos

  8. Click derecho con el staff
  9. Verificar consumo de mana
  10. Ver efectos del hechizo

  11. Recargar Mana

  12. Shift + Click derecho
  13. Verificar que el mana se restaura

Paso 10: Debugging y Optimización

Logging Detallado

// Agregar modo debug
private static final boolean DEBUG = Boolean.getBoolean("mysticores.debug");

public static void debug(String message) {
    if (DEBUG) {
        System.out.println("[MysticOres-DEBUG] " + message);
    }
}

Pruebas Unitarias

package com.ejemplo.mysticores;

import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

public class ManaSystemTest {

    @Test
    public void testManaConsumption() {
        // Crear jugador mock
        Player player = createMockPlayer();

        // Inicializar con 100 mana
        ManaSystem.setMaxMana(player, 100);

        // Consumir 20 mana
        boolean success = ManaSystem.consumeMana(player, 20);

        assertTrue(success);
        assertEquals(80, ManaSystem.getMana(player).current);
    }

    @Test
    public void testInsufficientMana() {
        Player player = createMockPlayer();
        ManaSystem.setMaxMana(player, 100);

        // Intentar consumir más mana del disponible
        boolean success = ManaSystem.consumeMana(player, 150);

        assertFalse(success);
        assertEquals(100, ManaSystem.getMana(player).current);
    }
}

Mejoras Futuras

Features Adicionales

  1. Múltiples Hechizos
  2. Fire Spell
  3. Ice Spell
  4. Lightning Spell

  5. Regeneración Pasiva de Mana

  6. Tick system para regen automático
  7. Configuración de velocidad de regen

  8. UI de Mana

  9. Barra de mana en HUD
  10. Indicadores visuales

  11. Sistema de Niveles

  12. Aumentar mana máxima con niveles
  13. Desbloquear hechizos más poderosos

  14. Efectos Visuales Avanzados

  15. Partículas custom
  16. Shaders
  17. Animaciones

Distribución del Mod

README.md

# Mystic Ores

Un mod que agrega minerales mágicos y objetos místicos a Hytale.

## Features

- **Mystic Ore**: Mineral mágico que se encuentra en las profundidades
- **Mystic Gem**: Gema obtenida al minar Mystic Ore
- **Mystic Staff**: Bastón mágico que lanza hechizos
- **Sistema de Mana**: Gestión de energía mágica

## Instalación

1. Descarga `mystic-ores-1.0.0.zip`
2. Extrae el contenido
3. Copia `mystic-ores-1.0.0.jar` a `earlyplugins/`
4. Copia la carpeta `assets/` a `mods/mystic-ores/`
5. Inicia el servidor con `--accept-early-plugins`

## Uso

### Obtener Mystic Gems
Mina bloques de Mystic Ore para obtener 1-3 Mystic Gems.

### Craftear Mystic Staff
[ ] [ ] [G] [ ] [S] [ ] [S] [ ] [ ]

G = Mystic Gem S = Stick

### Lanzar Hechizos
- Click derecho: Lanzar hechizo (cuesta 20 mana)
- Shift + Click derecho: Recargar mana (+50)

## Configuración

Edita `mods/mystic-ores/config.properties`:

```properties
mana.default-max=100
mana.regen-rate=1
spell.damage=15
spell.cost=20

Desarrollo

Ver CONTRIBUTING.md

Licencia

MIT License - Ver LICENSE ```

Próximos Pasos


¿Problemas? Consulta Troubleshooting