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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ run-data
repo
*.blockymodel
*.png
*.blockyanim
31 changes: 19 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@

Hytale Model Loader
=======

A mod for Minecraft Neoforge 1.21.11 that allows the player to import and use Hytale models in game. It converts Hytale's BlockyModel file format into a renderable version inside of Minecraft. Currently, these can be rendered as item or block models with entity models coming in a future version. This is usable with both resource packs and mods.
A mod for Minecraft Neoforge 1.21.11 that allows the player to import and use Hytale models in game. It converts
Hytale's BlockyModel and BLockyAnim file formats into renderable versions inside of Minecraft. Currently, these can be
rendered as item or block models with entity models coming in a future version. This is usable with both resource packs
and mods.

### Creating a Model

Models are defined using `.blockymodel` files (custom binary/text format) and referenced in `.json` model files just like standard Minecraft models. To create the model simply download this mod and when creating a model file make sure to add the loader and model location as shown below.
Refer to the wiki to learn how to create and animate your models.

#### Example: `pot.json`

Expand All @@ -23,40 +25,45 @@ Models are defined using `.blockymodel` files (custom binary/text format) and re
```

**Key fields:**

- `loader` – References the BlockyModelLoader **This is most important**
- `model` – Path to your `.blockymodel` file (This file is best in the models folder but put it wherever)
- `render_type` – Standard Minecraft render type this needs to be changed depending on model transparency
- `textures` – Texture references used by your model


## TODO

### v1.0.0

- [x] Add model parser `.blockymodel`
- [x] Implement custom item/block loader
- [x] Add block rotation support
- [x] Add double-sided face rendering
- [x] Add UV rotation and mirroring support

### v1.1.0
- [x] Check item and block scaling/translating using model json
- [ ] Make bounding boxes fit models
- [ ] Fix and clean up code

### v1.2.0
- [ ] Add parser for animation support `.blockyanim`
- [ ] Load animations in for blocks and items
- [ ] Create animation system to actually play and time these animations
- [x] Check item and block scaling/translating using model json
- [x] Implement custom BlockEntities for animation support since baked models cannot
- [x] Add parser for animation support `.blockyanim`
- [x] Load animations in for blocks
- [x] Create animation system to actually play and time these animations
- [x] Add wiki to show how to use different parts of the mod
- [x] Fix and clean up code

### v2.0.0

- [ ] Implement entity model loading
- [ ] Create in-game model preview/editing tool
- [ ] Support for custom render layers and transparency blending
- [ ] Clean code and docs for v2 release

### v2.1.0

- [ ] Add animation support for entities

### v2.2.0

- [ ] Add player model swapping

## Contributing

Expand Down
7 changes: 2 additions & 5 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=true

#read more on this at https://github.com/neoforged/ModDevGradle?tab=readme-ov-file#better-minecraft-parameter-names--javadoc-parchment
# you can also find the latest versions at: https://parchmentmc.org/docs/getting-started
parchment_minecraft_version=1.21.11
Expand All @@ -19,18 +18,16 @@ minecraft_version=1.21.11
minecraft_version_range=[1.21.11]
# The Neo version must agree with the Minecraft version to get a valid artifact
neo_version=21.11.37-beta

## Mod Properties

# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63}
# Must match the String constant located in the main mod class annotated with @Mod.
mod_id=hytalemodelloader
# The human-readable display name for the mod.
mod_name=HytaleModelLoader
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
mod_license=All Rights Reserved
mod_license=MIT
# The mod version. See https://semver.org/
mod_version=1.0.0
mod_version=1.1.0
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
# This should match the base package used for the mod sources.
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
Expand Down
8 changes: 0 additions & 8 deletions src/main/java/com/litehed/hytalemodels/Config.java

This file was deleted.

6 changes: 2 additions & 4 deletions src/main/java/com/litehed/hytalemodels/HytaleModelLoader.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.litehed.hytalemodels;

import com.litehed.hytalemodels.init.BlockEntityInit;
import com.litehed.hytalemodels.init.BlockInit;
import com.litehed.hytalemodels.init.ItemInit;
import com.mojang.logging.LogUtils;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.config.ModConfig;
import org.slf4j.Logger;

// The value here should match an entry in the META-INF/neoforge.mods.toml file
Expand All @@ -18,9 +18,7 @@ public class HytaleModelLoader {
public HytaleModelLoader(IEventBus modEventBus, ModContainer modContainer) {
// Remember to comment these out for version releases
BlockInit.BLOCKS.register(modEventBus);
BlockEntityInit.BLOCK_ENTITIES.register(modEventBus);
ItemInit.ITEMS.register(modEventBus);

// Register our mod's ModConfigSpec so that FML can create and load the config file for us
modContainer.registerConfig(ModConfig.Type.COMMON, Config.SPEC);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package com.litehed.hytalemodels;

import com.litehed.hytalemodels.modelstuff.BlockyModelLoader;
import com.litehed.hytalemodels.blocks.entity.AnimatedChestRenderer;
import com.litehed.hytalemodels.blocks.entity.CoffinRenderer;
import com.litehed.hytalemodels.blockymodel.BlockyModelLoader;
import com.litehed.hytalemodels.init.BlockEntityInit;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.fml.common.Mod;
import net.neoforged.neoforge.client.event.AddClientReloadListenersEvent;
import net.neoforged.neoforge.client.event.EntityRenderersEvent;
import net.neoforged.neoforge.client.event.ModelEvent;
import net.neoforged.neoforge.client.gui.ConfigurationScreen;
import net.neoforged.neoforge.client.gui.IConfigScreenFactory;
Expand All @@ -32,4 +36,10 @@ public static void onRegisterReloadListeners(AddClientReloadListenersEvent event
event.addListener(BlockyModelLoader.ID, BlockyModelLoader.INSTANCE);
event.addDependency(BlockyModelLoader.ID, VanillaClientListeners.MODELS);
}

@SubscribeEvent
public static void registerEntityRenderers(EntityRenderersEvent.RegisterRenderers event) {
event.registerBlockEntityRenderer(BlockEntityInit.CHEST_TEST_ENT.get(), AnimatedChestRenderer::new);
event.registerBlockEntityRenderer(BlockEntityInit.COFFIN_ENT.get(), CoffinRenderer::new);
}
}
78 changes: 78 additions & 0 deletions src/main/java/com/litehed/hytalemodels/api/NodeTransform.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.litehed.hytalemodels.api;

import org.joml.Quaternionf;
import org.joml.Vector3f;

import static com.mojang.math.Constants.EPSILON;

public record NodeTransform(Vector3f position, Quaternionf rotation, Vector3f scale, boolean visible) {

public NodeTransform(Vector3f position, Quaternionf rotation, Vector3f scale, boolean visible) {
this.position = new Vector3f(position);
this.rotation = new Quaternionf(rotation);
this.scale = new Vector3f(scale);
this.visible = visible;
}

public static NodeTransform translation(Vector3f position) {
return new NodeTransform(position, new Quaternionf(), new Vector3f(1, 1, 1), true);
}

public static NodeTransform rotation(Quaternionf rotation) {
return new NodeTransform(new Vector3f(), rotation, new Vector3f(1, 1, 1), true);
}

public static NodeTransform scale(Vector3f scale) {
return new NodeTransform(new Vector3f(), new Quaternionf(), scale, true);
}

public static NodeTransform visibility(boolean visible) {
return new NodeTransform(new Vector3f(), new Quaternionf(), new Vector3f(1, 1, 1), visible);
}

public static NodeTransform identity() {
return new NodeTransform(new Vector3f(), new Quaternionf(), new Vector3f(1, 1, 1), true);
}

@Override
public Vector3f position() {
return new Vector3f(position);
}

@Override
public Quaternionf rotation() {
return new Quaternionf(rotation);
}

@Override
public Vector3f scale() {
return new Vector3f(scale);
}

/**
* Merges this NodeTransform with another NodeTransform
*
* @param other The other NodeTransform to merge with
* @return The combined node transform
*/
public NodeTransform merge(NodeTransform other) {
Vector3f mergedPos = new Vector3f(this.position).add(other.position);
Quaternionf mergedRot = new Quaternionf(this.rotation).mul(other.rotation);
Vector3f mergedScale = new Vector3f(this.scale).mul(other.scale);
boolean mergedVis = this.visible && other.visible;
return new NodeTransform(mergedPos, mergedRot, mergedScale, mergedVis);
}

/**
* Checks if this NodeTransform is the identity transform
* @return true if this NodeTransform is the identity transform, false otherwise
*/
public boolean isIdentity() {
return position.lengthSquared() < EPSILON &&
rotation.equals(new Quaternionf(), EPSILON) &&
Math.abs(scale.x - 1.0f) < EPSILON &&
Math.abs(scale.y - 1.0f) < EPSILON &&
Math.abs(scale.z - 1.0f) < EPSILON &&
visible;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.litehed.hytalemodels.blocks;
package com.litehed.hytalemodels.api.block;

import net.minecraft.core.Direction;
import net.minecraft.world.item.context.BlockPlaceContext;
Expand All @@ -10,12 +10,12 @@
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.EnumProperty;

public class HytaleTestBlock extends Block {
public class HytaleBlockBase extends Block {

public static final EnumProperty<Direction> FACING = HorizontalDirectionalBlock.FACING;


public HytaleTestBlock(Properties properties) {
public HytaleBlockBase(Properties properties) {
super(properties.noOcclusion());
this.registerDefaultState(this.stateDefinition.any().setValue(FACING, Direction.NORTH));
}
Expand All @@ -31,10 +31,12 @@ public BlockState getStateForPlacement(BlockPlaceContext context) {
return super.getStateForPlacement(context).setValue(FACING, context.getHorizontalDirection().getOpposite());
}

@Override
public BlockState rotate(BlockState state, Rotation rot) {
return state.setValue(FACING, rot.rotate(state.getValue(FACING)));
}

@Override
public BlockState mirror(BlockState state, Mirror mirrorIn) {
return state.rotate(mirrorIn.getRotation(state.getValue(FACING)));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.litehed.hytalemodels.api.block.entity;

import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.world.level.storage.ValueOutput;

public abstract class HytaleBlockEntity extends BlockEntity {

private final String modelName;


public HytaleBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state, String modelName) {
super(type, pos, state);
this.modelName = modelName;

}

/**
* Returns the name of the Blocky model used to render this block entity.
* This is used to locate the {@code .blockymodel} file and the associated texture.
*
* @return the model name; never null or blank
*/
public String getModelName() {
return modelName;
}

/**
* Returns the current animation tick for this block entity.
*
* <p>This value is used alongside the partial tick to compute smooth animation playback.
* Internally, it is multiplied by 4 before being passed to the animation system to match
* Blockyanims expected animation speed. Increment this each game tick in your static
* {@code tick()} method for continuous animations.
*
* @return the current animation tick; must be a non-negative integer
*/
public abstract int getAnimationTick();

@Override
protected void loadAdditional(ValueInput input) {
super.loadAdditional(input);
loadAnimationData(input);
}

@Override
protected void saveAdditional(ValueOutput output) {
super.saveAdditional(output);
saveAnimationData(output);
}


/**
* Override this method to load custom animation or state data from NBT/storage on world load.
* Called automatically during {@link #loadAdditional}.
*
* <p>Default implementation does nothing.
*
* @param input the value input to read from
*/
protected void loadAnimationData(ValueInput input) {
// Default implementation does nothing
}

/**
* Override this method to save custom animation or state data to NBT/storage for persistence.
* Called automatically during {@link #saveAdditional}.
*
* <p>Default implementation does nothing.
*
* @param output the value output to write to
*/
protected void saveAnimationData(ValueOutput output) {
// Default implementation does nothing
}

@Override
public String toString() {
return getClass().getSimpleName() + "{" +
"modelName='" + modelName + '\'' +
", pos=" + worldPosition +
"}";
}
}
Loading