Jump to content
Search In
  • More options...
Find results that contain...
Find results in...

[SOLVED] [1.16] Custom Sign won't render


kiou.23
 Share

Recommended Posts

I have registered blocks for both a Standing Sign, and Wall Sign and I have registered an item for the sign.

I did reflection for registering a wood type, and am using this wood type in the signs

I have the texture for the wood type under textures/entity/signs

and I have created the blockstate and model files, copying the vanilla ones (where the blockstates point to a model file that only has a texture for particles)

 

However, the Sign still won't render, and the edit sign screen won't show up.

Sometimes the game crashes when I place a sign, with a NPE, but it isn't consistent.

 

Sign block registration:

EFFETE_SIGN = registerNoItem("effete_sign", () -> new StandingSignBlock(
	AbstractBlock.Properties.create(Material.NETHER_WOOD, MaterialColor.LIME)
		.doesNotBlockMovement().hardnessAndResistance(1.0F).sound(SoundType.WOOD), Main.EFFETE_WOOD_TYPE));
EFFETE_WALL_SIGN = registerNoItem("effete_wall_sign", () -> new WallSignBlock(
	AbstractBlock.Properties.create(Material.NETHER_WOOD, MaterialColor.LIME)
		.doesNotBlockMovement().hardnessAndResistance(1.0F).sound(SoundType.WOOD).lootFrom(EFFETE_SIGN.get()), Main.EFFETE_WOOD_TYPE));

 

Blockstate and model files:

{
  "textures": {
    "particle": "effetewood:block/effete_planks"
  }
}
{
  "variants": {
    "": {
      "model": "effetewood:block/effete_sign"
    }
  }
}

 

Wood Type registration and Reflection code:

private void setup(final FMLCommonSetupEvent event)  {
	reflectionSetup();

  	try {
		EFFETE_WOOD_TYPE = (WoodType) registerWoodTypeMethod.invoke(null, woodTypeConstructor.newInstance("effete"));
	} catch (Exception e) {
		LOGGER.error("Error registering wood type");
		e.printStackTrace();
	}
}

private void reflectionSetup() {
	if (woodTypeConstructor != null) woodTypeConstructor.setAccessible(true);
	if (registerWoodTypeMethod != null) registerWoodTypeMethod.setAccessible(true);
}

private static final Constructor<WoodType> woodTypeConstructor;
private static final Method registerWoodTypeMethod;

private static Constructor<WoodType> getWoodTypeConstructor() {
	try {
		return WoodType.class.getDeclaredConstructor(String.class);
	} catch (Exception e) {
		LOGGER.error("Exception in getWoodTypeConstructor: " + e.getMessage());
		e.printStackTrace();
		return  null;
	}
}

private static Method getRegisterWoodTypeMethod(){
	try {
		return WoodType.class.getDeclaredMethod("register", WoodType.class);
	} catch (Exception e) {
		LOGGER.error("Exception in getRegisterWoodTypeMethod: " + e.getMessage());
		e.printStackTrace();
		return null;
	}
}

static {
	woodTypeConstructor = getWoodTypeConstructor();
	registerWoodTypeMethod = getRegisterWoodTypeMethod();
}

 

Edited by kiou.23
Link to comment
Share on other sites

  • You do not need to use reflection to invoke the constructor, just make a subclass.
  • That is never how you handle exceptions. Think about what happens if these reflection things fail - you now don't have a WoodType instance. What does your sign do then? Return null? Then it's going to crash - but with some random null pointer exception somewhere, which has no bearing on the actual error (the reflection failed). Make your reflection code throw an exception if it fails! That said - you need to use ObfuscationReflectionHelper and SRG names, or your code will not work outside the development environment.
  • Sign textures are baked in Atlases.collectAllMaterials - which is called from the static initializer of ModelBakery. This class will be initialized very early, way before any mod loading happens probably. That means, Minecraft won't have your wood type when it is stichting textures / baking models. You probably need to hack them in in some way in ModelRegistryEvent.
Link to comment
Share on other sites

1 hour ago, diesieben07 said:
  • You do not need to use reflection to invoke the constructor, just make a subclass.

I had asked on discord and someone suggested using reflection. but yeah, a subclass is way better

This is correct?

public class ModWoodType extends WoodType {

    public static final WoodType EFFETE = new ModWoodType("effete");

    protected ModWoodType(String name) {
        super(name);
    }
}
1 hour ago, diesieben07 said:
  • That is never how you handle exceptions. Think about what happens if these reflection things fail - you now don't have a WoodType instance. What does your sign do then? Return null? Then it's going to crash - but with some random null pointer exception somewhere, which has no bearing on the actual error (the reflection failed). Make your reflection code throw an exception if it fails! That said - you need to use ObfuscationReflectionHelper and SRG names, or your code will not work outside the development environment.

Yeah, you told me that in another thread. But since I don't need to use reflection, I'm going to postpone studying error handling to another week

1 hour ago, diesieben07 said:
  • Sign textures are baked in Atlases.collectAllMaterials - which is called from the static initializer of ModelBakery. This class will be initialized very early, way before any mod loading happens probably. That means, Minecraft won't have your wood type when it is stichting textures / baking models. You probably need to hack them in in some way in ModelRegistryEvent.

And how should I go about doing that?

I tried:

@SubscribeEvent
public static void modelRegistryEvent(ModelRegistryEvent e) {
	Atlases.SIGN_MATERIALS.put(ModWoodType.EFFETE, new RenderMaterial(Atlases.SIGN_ATLAS, new ResourceLocation("effetewood:entity/signs/effete")));
}

It still doesn't work (The edit screen pops up for less than a second, and appear to have the missing pink/black texture), and the actual block just is invisible

(dumb question: I don't need to specify "textures/entity/..." in the resouce location, right?)

Edited by kiou.23
Link to comment
Share on other sites

6 minutes ago, kiou.23 said:

But since I don't need to use reflection, I'm going to postpone studying error handling to another week

You still need reflection to register it.

 

6 minutes ago, kiou.23 said:

And how should I go about doing that?

I don't know. It might be better to just make your own renderer and not use this vanilla stuff.

Link to comment
Share on other sites

9 minutes ago, diesieben07 said:

You still need reflection to register it.

Alright, where can I learn about the ObsfuscationReflectionHelper and SRG names? (and by register, you mean registering to the VALUES field from the WoodType class?)

10 minutes ago, diesieben07 said:

I don't know. It might be better to just make your own renderer and not use this vanilla stuff.

doesn't this do it?

Atlases.SIGN_MATERIALS.put(ModWoodType.EFFETE, new RenderMaterial(Atlases.SIGN_ATLAS, new ResourceLocation("effetewood:entity/signs/effete")));

if it doesn't, can you point me towards how I would write my own renderer?

Link to comment
Share on other sites

15 minutes ago, kiou.23 said:

Alright, where can I learn about the ObsfuscationReflectionHelper and SRG names?

ObfuscationReflectionHelper has javadocs. SRG names can be obtained from forge-bot on the Forge discord.

 

17 minutes ago, kiou.23 said:

doesn't this do it?


Atlases.SIGN_MATERIALS.put(ModWoodType.EFFETE, new RenderMaterial(Atlases.SIGN_ATLAS, new ResourceLocation("effetewood:entity/signs/effete")));

if it doesn't, can you point me towards how I would write my own renderer?

No, because SIGN_MATERIALS is copied into ModelBakery.LOCATIONS_BUILTIN_TEXTURES in Atlases.collectAllMaterials which is called from ModelBakery static init, which happens way before this event. Any changes to SIGN_MATERIALS later will be ignored.

You'd have to also add it to LOCATIONS_BUILTIN_TEXTURES, but I don't know if that would even be enough - worth a try though.

 

Link to comment
Share on other sites

14 minutes ago, diesieben07 said:

No, because SIGN_MATERIALS is copied into ModelBakery.LOCATIONS_BUILTIN_TEXTURES in Atlases.collectAllMaterials which is called from ModelBakery static init, which happens way before this event. Any changes to SIGN_MATERIALS later will be ignored.

You'd have to also add it to LOCATIONS_BUILTIN_TEXTURES, but I don't know if that would even be enough - worth a try though.

I'll try it (Edit: nvm, no idea how to try it, and it may not even work)

there seems to be another problem tho: I changed the wood type of the sign to OAK, just to see if it rendered with a vanilla wood type, and nope. it's still invisible. however, the edit screen has the oak sign texture (it still only stays up for milliseconds)

this has to be related to something else, right?

Edited by kiou.23
Link to comment
Share on other sites

3 minutes ago, diesieben07 said:

You cannot use the vanilla sign blocks, because they use SignTileEntity, which is only marked valid for the vanilla signs. You need to make your own TileEntityType and use that.

so I also need to write my own Sign blocks? and can I just make them a subclass of the vanilla classes?

Link to comment
Share on other sites

Okay, so my Custom Sign Tile Entity has to extend SignTileEntity, because PlayerEntity#openSignEditor() expects a SignTileEntity.

but from there I can't call super() and pass in my custom Tile Entity Type, because the TileEntityType for SignTileEntity is hardcoded in the constructor.

How to proceed?

Link to comment
Share on other sites

6 minutes ago, kiou.23 said:

Okay, so my Custom Sign Tile Entity has to extend SignTileEntity, because PlayerEntity#openSignEditor() expects a SignTileEntity.

but from there I can't call super() and pass in my custom Tile Entity Type, because the TileEntityType for SignTileEntity is hardcoded in the constructor.

How to proceed?

Override getType.

Link to comment
Share on other sites

Did the classes, now the edit screen shows up, and if I use a vanilla WoodType for the sign, the texture shows up in the edit screen (not in the actual block tho, it still won't render)

the changes are on github.

How should I go about rendering the Sign again? I'll need to write my own TileEntityRenderer?

Link to comment
Share on other sites

There are several problems which all contribute to the things you are seeing (or rather not seeing):

  • Your event handler for ModelRegistryEvent is not called, because you registered it to the wrong event bus. ModelRegistryEvent is fired on FMLJavaModLoadingContext.get().getModEventBus(), but you used MinecraftForge.EVENT_BUS.
  • You create the RenderMaterial wrongly. Minecraft will use Atlases.getSignMaterial to create the material - which does not match up with what you are doing here. You need to also use that method. This means that the "minecraft" namespace will be used, there is no way around that. Because of this you should prefix the name of your WoodType with your ModID, so "effetewood_effete", to avoid collisions not only with the texture but also the WoodType registry. Then your texture also needs to be in minecraft/textures/entity/signs/effetewood_effete.png naturally.
  • In ModelRegistryEvent you also need to add the RenderMaterial to the set of built-in textures (ModelBakery.LOCATIONS_BUILTIN_TEXTURES) so that Minecraft will actually load the texture and stitch it onto the atlas. Because the field is protected, you can use a little hack to avoid having to use reflection:
    static class FakeBakery extends ModelBakery {
        private FakeBakery(IResourceManager resourceManagerIn, BlockColors blockColorsIn, IProfiler profilerIn, int maxMipmapLevel)
        {
            super(resourceManagerIn, blockColorsIn, profilerIn, maxMipmapLevel);
        }
    
        static Set<RenderMaterial> getBuiltinTextures() {
            return ModelBakery.LOCATIONS_BUILTIN_TEXTURES;
        }
    }

    Now you can just call FakeBakery.getBuiltinTextures to get the field and add your RenderMaterial to it.

  • Because you now have your own tile entity type, you need to tell Minecraft to use the SignTileEntityRenderer for it. Use ClientRegistry.bindTileEntityRenderer in FMLClientSetupEvent for that.
  • Registering the wood type in ModelRegistryEvent means it won't be registered on the server. However FMLCommonSetupEvent is too late, so you have to do it in the mod constructor. Unfortunately in this case, mod construction (like many startup events, e.g. FMLCommonSetupEvent) runs in parallel on a threadpool, which means you cannot just register your WoodType there (the underlying data structure ObjectArraySet is not threadsafe). With normal event handlers (like FMLCommonSetupEvent) you can just use enqueueWork on the event to run something synchronously on the main thread. However in this case we are in the mod constructor, which doesn't receive an event instance. There is still a DeferredWorkQueue for us to use though (i.e. call enqueueWork to run something on the main thread): DeferredWorkQueue.lookup(Optional.of(FMLConstructModEvent.class)).get().enqueueWork with a Runnable lambda will do the trick. In there you can then safely register your WoodType.

With these fixes the custom sign now works:

image.png.d5f6a2183145fd9e7d8ad1bda27f05e7.png

  • Thanks 1
Link to comment
Share on other sites

Wow that's alot, I was trying to do the brute force way of simply reimplementing all the classes over, but it didn't quite work.

I'll take a look at your solution and try doing that. Thank you a lot for taking the time to help me with this!

(is it fine for me to dm you on discord if I face a roadblock again? relevant to adding this signs of course)

Link to comment
Share on other sites

6 minutes ago, kiou.23 said:

is it fine for me to dm you on discord if I face a roadblock again? relevant to adding this signs of course

probably better just to ask in the modder support channels - your chances of getting a quick response are better. I am not the only person who can help :D If I'm online on discord and see it, I'll reply - otherwise someone else will.

  • Like 1
Link to comment
Share on other sites

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
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.

 Share



  • Recently Browsing

    No registered users viewing this page.

  • Posts

    • Thanks for your reply! I have done it using data generation now. It works well for world generation, and I have realised that I don't care about saplings, so it works well. For anyone looking to do something similar, I include files to show what I did. They are just a bit hacky and incomplete now, but they can probably easily be generalised.   import static net.anju.larus.data.ResourceLocationUtil.prefix; import static net.anju.larus.data.ResourceLocationUtil.slash; @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault public abstract class LarusFeatureProvider<T> implements DataProvider { private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); protected final Set<FeatureEntry> featureEntries = new HashSet<>(); private final DataGenerator generator; protected LarusFeatureProvider(DataGenerator generator, Registry<T> registry) { this.generator = generator; for (Map.Entry<ResourceKey<T>, T> entry : registry.entrySet()) { ResourceKey<T> key = entry.getKey(); T feature = entry.getValue(); JsonElement jsonElement = getJson(feature); if (jsonElement != null) { FeatureEntry featureEntry = new FeatureEntry(prefix(key.location(), key.getRegistryName()), jsonElement); featureEntries.add(featureEntry); } } } private void addFeatures() { for (FeatureEntry featureEntry : featureEntries) { modifyFeatureEntry(featureEntry); } } protected abstract void modifyFeatureEntry(FeatureEntry featureEntry); @Override public void run(HashCache cache) { addFeatures(); writeFiles(cache, this.generator.getOutputFolder(), featureEntries); } // Actually write out the tables in the output folder private static void writeFiles(HashCache cache, Path outputFolder, Set<FeatureEntry> featureEntries) { for (FeatureEntry featureEntry : featureEntries) { if (featureEntry.isModified()) { ResourceLocation key = featureEntry.resourceLocation; JsonElement jsonElement = featureEntry.jsonElement; Path path = getPath(outputFolder, key); try { DataProvider.save(GSON, cache, jsonElement, path); } catch (IOException e) { LarusMod.LOGGER.error("Couldn't write data {}", key, e); } } } } @Override public String getName() { return "Larus features"; } @Nullable public JsonElement getJson(T feature) { if (feature instanceof ConfiguredFeature<?, ?> configuredFeature) { Optional<JsonElement> optional = ConfiguredFeature.DIRECT_CODEC.encodeStart(JsonOps.INSTANCE, configuredFeature).result(); return optional.orElse(null); } return null; } public static Path getPath(Path outputFolder, ResourceLocation key) { return outputFolder.resolve("data/" + slash(key) + ".json"); } protected static class FeatureEntry { boolean modified = false; private final ResourceLocation resourceLocation; private final JsonElement jsonElement; private FeatureEntry(ResourceLocation resourceLocation, JsonElement jsonElement) { this.resourceLocation = resourceLocation; this.jsonElement = jsonElement; } public JsonElement getJsonElement() { return jsonElement; } protected void markModified() { this.modified = true; } private boolean isModified() { return this.modified; } } } public class LarusLeavesFeatureProvider extends LarusFeatureProvider<ConfiguredFeature<?, ?>> { public LarusLeavesFeatureProvider(DataGenerator generator) { super(generator, BuiltinRegistries.CONFIGURED_FEATURE); } @Override protected void modifyFeatureEntry(FeatureEntry featureEntry) { JsonElement jsonElement = featureEntry.getJsonElement(); if (jsonElement.isJsonObject()) { JsonObject jsonObject = jsonElement.getAsJsonObject(); if (jsonObject.has("config")) { JsonObject config = jsonObject.get("config").getAsJsonObject(); if (config.has("foliage_provider")) { JsonObject mcFoliage = config.get("foliage_provider").getAsJsonObject(); JsonObject larusFoliage = larusFoliage(mcFoliage); if (larusFoliage != null) { config.add("foliage_provider", larusFoliage); featureEntry.markModified(); } } } } } @Nullable protected JsonObject larusFoliage(JsonObject mcFoliage) { JsonElement type = mcFoliage.get("type"); if (type.getAsString().equals("minecraft:simple_state_provider")) { JsonObject state = mcFoliage.get("state").getAsJsonObject(); // Change leaves String mcLeaves = state.get("Name").getAsString(); LarusLeavesBlock larusLeavesBlock = larusLeavesBlock(mcLeaves); if (larusLeavesBlock == null || larusLeavesBlock.getRegistryName() == null) return null; String larusLeaves = larusLeavesBlock.getRegistryName().toString(); state.add("Name", new JsonPrimitive(larusLeaves)); // Transform properties JsonObject mcProperties = state.get("Properties").getAsJsonObject(); transformProperties(mcProperties, larusLeavesBlock); } else { LarusMod.LOGGER.warn("Unexpected type for MC foliage " + mcFoliage); } return mcFoliage; } protected void transformProperties(JsonObject properties, LarusLeavesBlock larusLeavesBlock) { if (larusLeavesBlock instanceof LarusDeciduousLeavesBlock) properties.add("leafy", new JsonPrimitive("green")); } @Nullable protected LarusLeavesBlock larusLeavesBlock(String mcLeaves) { switch (mcLeaves) { case "minecraft:oak_leaves" -> { return LarusBlocks.OAK_LEAVES.get(); } case "minecraft:birch_leaves" -> { return LarusBlocks.BIRCH_LEAVES.get(); } case "minecraft:acacia_leaves" -> { return LarusBlocks.ACACIA_LEAVES.get(); } case "minecraft:jungle_leaves" -> { return LarusBlocks.JUNGLE_LEAVES.get(); } case "minecraft:dark_oak_leaves" -> { return LarusBlocks.DARK_OAK_LEAVES.get(); } case "minecraft:spruce_leaves" -> { return LarusBlocks.SPRUCE_LEAVES.get(); } } return null; } }  
    • the reason for that is the PlayerModel positions and rotations are set after the RenderPlayerEvent.Pre is fired change to Event to RenderPlayerEvent.Post
    • you can use BiomeLoadingEvent, loop through the Features you get from the Event and remove the Feature if it's the AmethystGeode Feature
  • Topics

  • Who's Online (See full list)

×
×
  • Create New...

Important Information

By using this site, you agree to our Privacy Policy.