Jump to content

[1.9][UNSOLVED] Custom stairs blockstate .json not working w. upside-down stairs


Recommended Posts

Posted

So I've been converting all my blocks to use the Forge blockstate .json format instead of the vanilla one, and changing my item models so that most block model jsons are unnecessary, allowing me to reduce the total number of .json files in my mod, which is pretty much entirely comprised of basic-shaped blocks (full cubes, slabs, stairs, fences, etc.) It has gone great so far up until this point. I'm currently trying to create a blockstates .json for my custom stairs using the Forge format. (In the example here its the "stairs_of_red_normal"... keep in mind that unlike my previous examples in my mod, the shade "normal" is not metadata here but just part of the name as the stairs metadata didn't have enough room for my "shade" property I used on my other blocks.)

 

I've created 2 model .jsons, "cube_quarter_horizontal" & "cube_eighth", which are basically equivalent to the top half of an outer stairs and the top half of a straight stairs respectively. I'm using these with the vanilla "half_slab" model to try and contruct the proper stairs shape using the forge blockstates .json format.

 

The stairs work perfectly for bottom half stairs, but for top half (upside-down) stairs, the models are the same as their top half counterparts (though the block itself changes). Interestingly, it looks like an east-facing stairs is the exception to the rule, as it DOES rotate the model 180 degrees when it is placed upside-down.

 

Looking at the .json, "east" is the only value of "facing" that does not have a y rotation specified (remember that y is left-right in block .jsons and x is up-down), so I have a feeling that there is a limit to the number of rotations you can apply to a model or submodel in the Forge blockstates format. Is this the case? Can I not rotate the entire model in more than one axis? Is this a limitation of Forge blockstates, vanilla, or is it a bug? Is there anyway around this? Logically, I would think that the .json I've created should work. Any ideas?

 

blockstates/stairs_of_red_normal.json (Forge blockstates .json)

{
"forge_marker": 1,
"defaults": {
	"textures": {
		"bottom": "colore:blocks/block_of_red_normal",
		"top": "colore:blocks/block_of_red_normal",
		"side": "colore:blocks/block_of_red_normal"
	},
	"model": "half_slab"
},
"variants": {
	"facing": {
		"east": {
		},
		"south": {
			"y": 90
		},
		"west": {
			"y": 180
		},
		"north": {
			"y": 270
		}
	},
	"shape": {
		"straight": {
			"submodel": {
				"quarter": { "model": "colore:cube_quarter_horizontal" }
			}
		},
		"outer_right": {
			"submodel": {
				"eighth": { "model": "colore:cube_eighth" }
			}
		},
		"outer_left": {
			"submodel": {
				"eighth": { "model": "colore:cube_eighth", "y": 270 }
			}
		},
		"inner_right": {
			"submodel": {
				"quarter": { "model": "colore:cube_quarter_horizontal" },
				"eighth": { "model": "colore:cube_eighth" , "y": 90 }
			}
		},
		"inner_left": {
			"submodel": {
				"quarter": { "model": "colore:cube_quarter_horizontal" },
				"eighth": { "model": "colore:cube_eighth", "y": 180 }
			}
		}
	},
	"half": {
		"bottom": {

		},
		"top": {
			"x": 180
		}
	}
}
}

 

models/block/cube_eighth.json (block model which is literally the vanilla outer_stairs model with the bottom slab shape removed and is used in outer & inner stairs shapes)

{
    "elements": [
        {   "from": [ 8, 8, 8 ],
            "to": [ 16, 16, 16 ],
            "faces": {
                "down":  { "uv": [ 8, 0, 16,  8 ], "texture": "#bottom", "cullface": "down" },
                "up":    { "uv": [ 8, 8, 16, 16 ], "texture": "#top", "cullface": "up" },
                "north": { "uv": [ 0, 0,  8,  8 ], "texture": "#side" },
                "south": { "uv": [ 8, 0, 16,  8 ], "texture": "#side", "cullface": "south" },
                "west":  { "uv": [ 8, 0, 16,  8 ], "texture": "#side" },
                "east":  { "uv": [ 0, 0,  8,  8 ], "texture": "#side", "cullface": "east" }
            }
        }
    ]
}

 

models/block/cube_quarter_horizontal.json (cube_eighth extended in the z direction and used for straight and inner stairs shapes)

{
    "elements": [
        {   "from": [ 8, 8, 0 ],
            "to": [ 16, 16, 16 ],
            "faces": {
                "down":  { "uv": [ 8, 0, 16,  8 ], "texture": "#bottom", "cullface": "down" },
                "up":    { "uv": [ 8, 8, 16, 16 ], "texture": "#top", "cullface": "up" },
                "north": { "uv": [ 0, 0,  8,  8 ], "texture": "#side" },
                "south": { "uv": [ 8, 0, 16,  8 ], "texture": "#side", "cullface": "south" },
                "west":  { "uv": [ 8, 0, 16,  8 ], "texture": "#side" },
                "east":  { "uv": [ 0, 0,  8,  8 ], "texture": "#side", "cullface": "east" }
            }
        }
    ]
}

Colore - The mod that adds monochrome blocks in every color of the rainbow!

http://www.minecraftforge.net/forum/index.php?topic=35149

 

If you're looking to learn how to make mods for 1.9.4, I wrote a helpful article with links to a lot of useful resources for learning Minecraft 1.9.4 modding!

 

http://supergeniuszeb.com/mods/a-helpful-list-of-minecraft-1-9-4-modding-resources/

Posted

Interestingly, it looks like an east-facing stairs is the exception to the rule, as it DOES rotate the model 180 degrees when it is placed upside-down.

When you rotate an asymmetric model 180 around z or x axis to turn stairs upside-down, then you will change one facing in the process (x-axis flips N-S; z flips E-W). If you have trouble visualizing this in your head, then get something wedge-shaped that you can turn in your hands.

 

Looking at the .json, "east" is the only value of "facing" that does not have an y rotation specified

Then for this particular model, the default facing is east (the rotation zero need not be written into the json file).

 

remember that y is left-right in block .jsons and x is up-down

Are you sure? I thought Y was vertical, hence rotating around it to face E-S-W-N. Don't confuse the axis of rotation with the faces around it. Get that wedge in your hands and start playing with it to see what I mean.

 

I have a feeling that there is a limit to the number of rotations you can apply to a model or submodel in the Forge blockstates format. Is this the case?

I think so: You may apply X, Y & Z rotations. That's 3 axes. You should never need more.

The debugger is a powerful and necessary tool in any IDE, so learn how to use it. You'll be able to tell us more and get better help here if you investigate your runtime problems in the debugger before posting.

Posted

Interestingly, it looks like an east-facing stairs is the exception to the rule, as it DOES rotate the model 180 degrees when it is placed upside-down.

When you rotate an asymmetric model 180 around z or x axis to turn stairs upside-down, then you will change one facing in the process (x-axis flips N-S; z flips E-W). If you have trouble visualizing this in your head, then get something wedge-shaped that you can turn in your hands.

 

Looking at the .json, "east" is the only value of "facing" that does not have an y rotation specified

Then for this particular model, the default facing is east (the rotation zero need not be written into the json file).

 

remember that y is left-right in block .jsons and x is up-down

Are you sure? I thought Y was vertical, hence rotating around it to face E-S-W-N. Don't confuse the axis of rotation with the faces around it. Get that wedge in your hands and start playing with it to see what I mean.

 

I have a feeling that there is a limit to the number of rotations you can apply to a model or submodel in the Forge blockstates format. Is this the case?

I think so: You may apply X, Y & Z rotations. That's 3 axes. You should never need more.

Whoops. Yeah, you're right about the axis. And I know the zero rotation didn't need to be written, but strangely enough, when I specify it to be zero, the east-facing stairs stops being an anomaly. But the thing about the x rotation is that it isn't even being applied at all. As in the upside-down stairs look identical to right-side-up stairs of the same "facing" and "shape" values. If I remove the y rotations, the x rotations suddenly work, but (of course) the stairs model will only face one direction.

 

So while yes, the models would look wrong if they were simply rotated 180 degrees in the x axis, that's not my problem because the models aren't getting rotated in the x axis at all. It's like it won't let me rotate the entire model in more than one axis at a time, so I can only have either y-rotation or x-rotation, but not both. Is this intended behavior, is it a bug, or am I missing something in my .json?

Colore - The mod that adds monochrome blocks in every color of the rainbow!

http://www.minecraftforge.net/forum/index.php?topic=35149

 

If you're looking to learn how to make mods for 1.9.4, I wrote a helpful article with links to a lot of useful resources for learning Minecraft 1.9.4 modding!

 

http://supergeniuszeb.com/mods/a-helpful-list-of-minecraft-1-9-4-modding-resources/

Posted

Because you're using submodels, I can't help any further. I just don't know how those pieces will be put together. Most of my experience is with 1.8 anyway, and I suspect that the json processing is still evolving.

 

In 1.8, I could use the Forge advanced json to separate modeling from texturing, but I could not use it to separate the different aspects of modeling. In other words, I could not use one property to choose a model, and then use a different property to set its rotation. All of the modeling had to be set together (model name, rotations, uv-lock...). I wasn't forced into a complete Cartesian product, but I did end up writing more tedious lines than I had expected.

 

The debugger is a powerful and necessary tool in any IDE, so learn how to use it. You'll be able to tell us more and get better help here if you investigate your runtime problems in the debugger before posting.

Posted

Because you're using submodels, I can't help any further. I just don't know how those pieces will be put together. Most of my experience is with 1.8 anyway, and I suspect that the json processing is still evolving.

 

In 1.8, I could use the Forge advanced json to separate modeling from texturing, but I could not use it to separate the different aspects of modeling. In other words, I could not use one property to choose a model, and then use a different property to set its rotation. All of the modeling had to be set together (model name, rotations, uv-lock...). I wasn't forced into a complete Cartesian product, but I did end up writing more tedious lines than I had expected.

 

Hmmm... it looks like I'm stuck then, unless someone else has an answer or the format gets updated. I guess I'll have to go back to the vanilla format. One more question, though. Is it possible to have define what happens if 2 or more properties are true in the Forge blockstates format? (As in ' "shade=true,half=false,north=true,shape=straight":{} ' like in the vanilla format or something like that.) Because I haven't found any examples where you can define what happens with more than one condition at a time, and I feel like that may be one advantage of the vanilla format that should be added to Forge's format if it doesn't already exist. If that's possible in the Forge format, I'll be able to still do what I want to do, albeit in a more lengthy way and using the vanilla stairs models instead of constructing models from submodels.

Colore - The mod that adds monochrome blocks in every color of the rainbow!

http://www.minecraftforge.net/forum/index.php?topic=35149

 

If you're looking to learn how to make mods for 1.9.4, I wrote a helpful article with links to a lot of useful resources for learning Minecraft 1.9.4 modding!

 

http://supergeniuszeb.com/mods/a-helpful-list-of-minecraft-1-9-4-modding-resources/

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • So me and a couple of friends are playing with a shitpost mod pack and one of the mods in the pack is corail tombstone and for some reason there is a problem with it, where on death to fire the player will get kicked out of the server and the tombstone will not spawn basically deleting an entire inventory, it doesn't matter what type of fire it is, whether it's from vanilla fire/lava, or from modded fire like ice&fire/lycanites and it's common enough to where everyone on the server has experienced at least once or twice and it doesn't give any crash log. a solution to this would be much appreciated thank you!
    • It is 1.12.2 - I have no idea if there is a 1.12 pack
    • Okay, but does the modpack works with 1.12 or just with 1.12.2, because I need the Forge client specifically for Minecraft 1.12, not 1.12.2
    • Version 1.19 - Forge 41.0.63 I want to create a wolf entity that I can ride, so far it seems to be working, but the problem is that when I get on the wolf, I can’t control it. I then discovered that the issue is that the server doesn’t detect that I’m riding the wolf, so I’m struggling with synchronization. However, it seems to not be working properly. As I understand it, the server receives the packet but doesn’t register it correctly. I’m a bit new to Java, and I’ll try to provide all the relevant code and prints *The comments and prints are translated by chatgpt since they were originally in Spanish* Thank you very much in advance No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. MountableWolfEntity package com.vals.valscraft.entity; import com.vals.valscraft.network.MountSyncPacket; import com.vals.valscraft.network.NetworkHandler; import net.minecraft.client.Minecraft; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.animal.Wolf; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.Entity; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.network.PacketDistributor; public class MountableWolfEntity extends Wolf { private boolean hasSaddle; private static final EntityDataAccessor<Byte> DATA_ID_FLAGS = SynchedEntityData.defineId(MountableWolfEntity.class, EntityDataSerializers.BYTE); public MountableWolfEntity(EntityType<? extends Wolf> type, Level level) { super(type, level); this.hasSaddle = false; } @Override protected void defineSynchedData() { super.defineSynchedData(); this.entityData.define(DATA_ID_FLAGS, (byte)0); } public static AttributeSupplier.Builder createAttributes() { return Wolf.createAttributes() .add(Attributes.MAX_HEALTH, 20.0) .add(Attributes.MOVEMENT_SPEED, 0.3); } @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { ItemStack itemstack = player.getItemInHand(hand); if (itemstack.getItem() == Items.SADDLE && !this.hasSaddle()) { if (!player.isCreative()) { itemstack.shrink(1); } this.setSaddle(true); return InteractionResult.SUCCESS; } else if (!level.isClientSide && this.hasSaddle()) { player.startRiding(this); MountSyncPacket packet = new MountSyncPacket(true); // 'true' means the player is mounted NetworkHandler.CHANNEL.sendToServer(packet); // Ensure the server handles the packet return InteractionResult.SUCCESS; } return InteractionResult.PASS; } @Override public void travel(Vec3 travelVector) { if (this.isVehicle() && this.getControllingPassenger() instanceof Player) { System.out.println("The wolf has a passenger."); System.out.println("The passenger is a player."); Player player = (Player) this.getControllingPassenger(); // Ensure the player is the controller this.setYRot(player.getYRot()); this.yRotO = this.getYRot(); this.setXRot(player.getXRot() * 0.5F); this.setRot(this.getYRot(), this.getXRot()); this.yBodyRot = this.getYRot(); this.yHeadRot = this.yBodyRot; float forward = player.zza; float strafe = player.xxa; if (forward <= 0.0F) { forward *= 0.25F; } this.flyingSpeed = this.getSpeed() * 0.1F; this.setSpeed((float) this.getAttributeValue(Attributes.MOVEMENT_SPEED) * 1.5F); this.setDeltaMovement(new Vec3(strafe, travelVector.y, forward).scale(this.getSpeed())); this.calculateEntityAnimation(this, false); } else { // The wolf does not have a passenger or the passenger is not a player System.out.println("No player is mounted, or the passenger is not a player."); super.travel(travelVector); } } public boolean hasSaddle() { return this.hasSaddle; } public void setSaddle(boolean hasSaddle) { this.hasSaddle = hasSaddle; } @Override protected void dropEquipment() { super.dropEquipment(); if (this.hasSaddle()) { this.spawnAtLocation(Items.SADDLE); this.setSaddle(false); } } @SubscribeEvent public static void onServerTick(TickEvent.ServerTickEvent event) { if (event.phase == TickEvent.Phase.START) { MinecraftServer server = net.minecraftforge.server.ServerLifecycleHooks.getCurrentServer(); if (server != null) { for (ServerPlayer player : server.getPlayerList().getPlayers()) { if (player.isPassenger() && player.getVehicle() instanceof MountableWolfEntity) { MountableWolfEntity wolf = (MountableWolfEntity) player.getVehicle(); System.out.println("Tick: " + player.getName().getString() + " is correctly mounted on " + wolf); } } } } } private boolean lastMountedState = false; @Override public void tick() { super.tick(); if (!this.level.isClientSide) { // Only on the server boolean isMounted = this.isVehicle() && this.getControllingPassenger() instanceof Player; // Only print if the state changed if (isMounted != lastMountedState) { if (isMounted) { Player player = (Player) this.getControllingPassenger(); // Verify the passenger is a player System.out.println("Server: Player " + player.getName().getString() + " is now mounted."); } else { System.out.println("Server: The wolf no longer has a passenger."); } lastMountedState = isMounted; } } } @Override public void addPassenger(Entity passenger) { super.addPassenger(passenger); if (passenger instanceof Player) { Player player = (Player) passenger; if (!this.level.isClientSide && player instanceof ServerPlayer) { // Send the packet to the server to indicate the player is mounted NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new MountSyncPacket(true)); } } } @Override public void removePassenger(Entity passenger) { super.removePassenger(passenger); if (passenger instanceof Player) { Player player = (Player) passenger; if (!this.level.isClientSide && player instanceof ServerPlayer) { // Send the packet to the server to indicate the player is no longer mounted NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new MountSyncPacket(false)); } } } @Override public boolean isControlledByLocalInstance() { Entity entity = this.getControllingPassenger(); return entity instanceof Player; } @Override public void positionRider(Entity passenger) { if (this.hasPassenger(passenger)) { double xOffset = Math.cos(Math.toRadians(this.getYRot() + 90)) * 0.4; double zOffset = Math.sin(Math.toRadians(this.getYRot() + 90)) * 0.4; passenger.setPos(this.getX() + xOffset, this.getY() + this.getPassengersRidingOffset() + passenger.getMyRidingOffset(), this.getZ() + zOffset); } } } MountSyncPacket package com.vals.valscraft.network; import com.vals.valscraft.entity.MountableWolfEntity; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; public class MountSyncPacket { private final boolean isMounted; public MountSyncPacket(boolean isMounted) { this.isMounted = isMounted; } public void encode(FriendlyByteBuf buffer) { buffer.writeBoolean(isMounted); } public static MountSyncPacket decode(FriendlyByteBuf buffer) { return new MountSyncPacket(buffer.readBoolean()); } public void handle(NetworkEvent.Context context) { context.enqueueWork(() -> { ServerPlayer player = context.getSender(); // Get the player from the context if (player != null) { // Verifies if the player has dismounted if (!isMounted) { Entity vehicle = player.getVehicle(); if (vehicle instanceof MountableWolfEntity wolf) { // Logic to remove the player as a passenger wolf.removePassenger(player); System.out.println("Server: Player " + player.getName().getString() + " is no longer mounted."); } } } }); context.setPacketHandled(true); // Marks the packet as handled } } networkHandler package com.vals.valscraft.network; import com.vals.valscraft.valscraft; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.network.NetworkRegistry; import net.minecraftforge.network.simple.SimpleChannel; import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; public class NetworkHandler { private static final String PROTOCOL_VERSION = "1"; public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel( new ResourceLocation(valscraft.MODID, "main"), () -> PROTOCOL_VERSION, PROTOCOL_VERSION::equals, PROTOCOL_VERSION::equals ); public static void init() { int packetId = 0; // Register the mount synchronization packet CHANNEL.registerMessage( packetId++, MountSyncPacket.class, MountSyncPacket::encode, MountSyncPacket::decode, (msg, context) -> msg.handle(context.get()) // Get the context with context.get() ); } }  
  • Topics

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.