Jump to content

Forge hooks and signs


cad97

Recommended Posts

I've been trying to replicate the function of TrazLander's CommandBlockSigns+ MCEdit filter within modded Minecraft. I came up with a system that would work, but I don't know if it is possible to execute within Forge as it is in the latest release.

 

TL;DR : is there a way to hook into TileEntitySign's completion so that I can do things with the text of a sign when placed? (The easy way to do this would just be to make a new block as extension of BlockSign (i.e. CommandBlockSign) but I would like to keep convertibility to vanilla for exporting.)

 

Longer version :

The way this mod would work is any time a sign is placed and the player is finished writing to it, the mod would grab it, check if it is a valid sign for the system, and if so, log it into a .json file (the json part works wonderfully, though I spent an evening figuring out how to do it - I'm noobish with Java I/O).  When a sign is destroyed the mod would check for a log for the sign and if there was one, remove it.  (This is an easy hook.)  When executing commands the mod would intercept the command, find tokens (as defined by the filter) and replace them as appropriate, then forward the command (and I believe I have found the token for this). The problem with this system, however, is that I have been unable to find a @SubscribeEvent that I could subscribe to to allow me to do this logging of signs.

 

Any help is appreciated.

Link to comment
Share on other sites

Hi

 

The editing appears to be done in GuiEditSign and completion of the sign is in actionPerformed; I don't see any suitable hooks there.

 

I have two suggestions then-

1) you could use ASM + Reflection to modify GuiEditSign.actionPerformed() to call your own code

2) You could write your own MyGuiEditSign extends GuiEditSign, override actionPerformed, and use the GuiOpenEvent to intercept the vanilla GuiEditSign and replace it with your MyGuiEditSign instead (replace event.gui with your own)

 

Never tried those, but they should both work with a bit of tweaking.

 

-TGG

 

Link to comment
Share on other sites

At the very least this got me to update Forge xD I was on 10.12.1.1060 which was the Recommended when I installed Forge on this machine in my enviroment, but now the Recommended build has been updated to 10.12.2.1121, literally last night xD

 

Aaaaaanyway: in 1060, the only GUI event was GuiOpenEvent. In 1069, however, "bspkrs: New GuiScreen events" was pushed. I'm updating now to have a look.

 

(In other notes, in looking through "all" of the events to find one for this I had only looked in subpackages of net.minecraftforge.event and not net.minecraftforge.client.event. You learn something new every day!)

Link to comment
Share on other sites

Well, I'm able to grab the GUI with the new GUI hooks!

@SubscribeEvent
public void on(ActionPerformedEvent.Pre event)
{
	if (event.gui instanceof GuiEditSign)
	{
		GuiEditSign ges = (GuiEditSign) event.gui;
	}
}

The one problem is this line in GuiEditSign:

    /** Reference to the sign object. */
    private TileEntitySign tileSign;

 

Looks like I'll be overwriting the GuiEditSign after all. Oh well, at least I got a Forge update!

Link to comment
Share on other sites

I've not messed with Java Reflection before (mainly because I haven't had to and it's such an advanced thing), so I'm probably missing something here, but this is the code that I wrote after doing a bit of research on the javadocs for reflection:

@SubscribeEvent
public void onSignDone(ActionPerformedEvent.Pre event) throws NoSuchFieldException, SecurityException,
	IllegalArgumentException, IllegalAccessException // yes I know this throws chain is messy
{
	if (event.gui instanceof GuiEditSign)
	{
		GuiEditSign ges = (GuiEditSign) event.gui;
		TileEntitySign tileSign = (TileEntitySign) ges.getClass().getField("tileSign")
			.get(null);
		System.out.println(Arrays.asList(tileSign.signText));
	}
}

However, upon hitting the "Done" on a sign, my Minecraft immediately crashes with this error:

 

 

net.minecraft.util.ReportedException: Updating screen events
at net.minecraft.client.Minecraft.runTick(Minecraft.java:1701) ~[Minecraft.class:?]
at net.minecraft.client.Minecraft.runGameLoop(Minecraft.java:996) ~[Minecraft.class:?]
at net.minecraft.client.Minecraft.run(Minecraft.java:912) [Minecraft.class:?]
at net.minecraft.client.main.Main.main(Main.java:112) [Main.class:?]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.7.0_55]
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[?:1.7.0_55]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[?:1.7.0_55]
at java.lang.reflect.Method.invoke(Unknown Source) ~[?:1.7.0_55]
at net.minecraft.launchwrapper.Launch.launch(Launch.java:134) [launchwrapper-1.9.jar:?]
at net.minecraft.launchwrapper.Launch.main(Launch.java:28) [launchwrapper-1.9.jar:?]
Caused by: java.lang.IllegalAccessException: Class cad97.commandblocksigns.CommandBlockSignsListener can not access a member of class net.minecraft.client.gui.inventory.GuiEditSign with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Unknown Source) ~[?:1.7.0_55]
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(Unknown Source) ~[?:1.7.0_55]
at java.lang.reflect.AccessibleObject.checkAccess(Unknown Source) ~[?:1.7.0_55]
at java.lang.reflect.Field.get(Unknown Source) ~[?:1.7.0_55]
at cad97.commandblocksigns.CommandBlockSignsListener.onSignDone(CommandBlockSignsListener.java:20) ~[CommandBlockSignsListener.class:?]
at cpw.mods.fml.common.eventhandler.ASMEventHandler_4_CommandBlockSignsListener_onSignDone_Pre.invoke(.dynamic) ~[?:?]
at cpw.mods.fml.common.eventhandler.ASMEventHandler.invoke(ASMEventHandler.java:51) ~[ASMEventHandler.class:?]
at cpw.mods.fml.common.eventhandler.EventBus.post(EventBus.java:122) ~[EventBus.class:?]
at net.minecraft.client.gui.GuiScreen.mouseClicked(GuiScreen.java:250) ~[GuiScreen.class:?]
at net.minecraft.client.gui.GuiScreen.handleMouseInput(GuiScreen.java:351) ~[GuiScreen.class:?]
at net.minecraft.client.gui.GuiScreen.handleInput(GuiScreen.java:315) ~[GuiScreen.class:?]
at net.minecraft.client.Minecraft.runTick(Minecraft.java:1687) ~[Minecraft.class:?]
... 9 more

 

 

 

(fun side note, the minecraft crash comment was "// Why is it breaking :(" - exactly what I'm thinking right now xD)

Link to comment
Share on other sites

Ok... this is really weird. With the same code I am getting a different error now ?!!!??

 

 

package cad97.commandblocksigns;

import java.util.Arrays;

import net.minecraft.client.gui.inventory.GuiEditSign;
import net.minecraft.tileentity.TileEntitySign;
import net.minecraftforge.client.event.GuiScreenEvent.ActionPerformedEvent;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;

public class CommandBlockSignsListener
{
@SubscribeEvent
public void onSignDone(ActionPerformedEvent.Pre event) throws NoSuchFieldException, SecurityException,
	IllegalArgumentException, IllegalAccessException // yes I know this throws chain is messy
{
	if (event.gui instanceof GuiEditSign)
	{
		GuiEditSign ges = (GuiEditSign) event.gui;
		TileEntitySign tileSign = (TileEntitySign) ges.getClass().getField("tileSign")
			.get(null);
		System.out.println(Arrays.asList(tileSign.signText));
	}
}
}

java.lang.NoSuchFieldException: tileSign
at java.lang.Class.getField(Unknown Source)
at cad97.commandblocksigns.CommandBlockSignsListener.onSignDone(CommandBlockSignsListener.java:19)
at cpw.mods.fml.common.eventhandler.ASMEventHandler_4_CommandBlockSignsListener_onSignDone_Pre.invoke(.dynamic)
at cpw.mods.fml.common.eventhandler.ASMEventHandler.invoke(ASMEventHandler.java:51)
at cpw.mods.fml.common.eventhandler.EventBus.post(EventBus.java:122)
at net.minecraft.client.gui.GuiScreen.mouseClicked(GuiScreen.java:250)
at net.minecraft.client.gui.GuiScreen.handleMouseInput(GuiScreen.java:351)
at net.minecraft.client.gui.GuiScreen.handleInput(GuiScreen.java:315)
at net.minecraft.client.Minecraft.runTick(Minecraft.java:1687)
at net.minecraft.client.Minecraft.runGameLoop(Minecraft.java:996)
at net.minecraft.client.Minecraft.run(Minecraft.java:912)
at net.minecraft.client.main.Main.main(Main.java:112)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at net.minecraft.launchwrapper.Launch.launch(Launch.java:134)
at net.minecraft.launchwrapper.Launch.main(Launch.java:28)

 

 

 

I assume that what you are talking about for caching the getField would be something like this if it weren't giving me the NoSuchFieldException:

 

 

package cad97.commandblocksigns;

import java.lang.reflect.Field;
import java.util.Arrays;

import net.minecraft.client.gui.inventory.GuiEditSign;
import net.minecraft.tileentity.TileEntitySign;
import net.minecraftforge.client.event.GuiScreenEvent.ActionPerformedEvent;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;

public class CommandBlockSignsListener
{
private Field f;

@SubscribeEvent
public void onSignDone(ActionPerformedEvent.Pre event) throws NoSuchFieldException,
	SecurityException, IllegalArgumentException, IllegalAccessException
// yes I know this throws chain is undesirable I'll add a catch later
{
	if (event.gui instanceof GuiEditSign)
	{
		if (f == null)
		{
			GuiEditSign ges = (GuiEditSign) event.gui;
			f = ges.getClass().getField("tileSign");
			f.setAccessible(true);
		}
		TileEntitySign tileSign = (TileEntitySign) f.get(null);
		System.out.println(Arrays.asList(tileSign.signText));
	}
}
}

 

 

Link to comment
Share on other sites

Well I fixed it! Can you spot the difference?

@SubscribeEvent
public void onSignDone(ActionPerformedEvent.Pre event) throws NoSuchFieldException,
	SecurityException, IllegalArgumentException, IllegalAccessException
// yes I know this throws chain is undesirable I'll add a catch later
{
	if (event.gui instanceof GuiEditSign)
	{
		GuiEditSign ges = (GuiEditSign) event.gui;
		if (f == null)
		{
			f = ges.getClass().getDeclaredField("tileSign");
			f.setAccessible(true);
		}
		TileEntitySign tileSign = (TileEntitySign) f.get(ges);
		System.out.println(Arrays.asList(tileSign.signText));
	}
}

 

[spoiler=differences]

It's two lines.

 

Looking back at my code after a break, I realized that before I was using .getDeclaredField("tileSign") but switched somewhere to .getField("tileSign"). Looking at the documentation both should work, but something about the way they work only grabs it properly with .getDeclaredField("tileSign").

 

After I changed this, I got a NullPointer on the line with .get(null). [[GAH THAT THUNDER SCARED ME]] [[OK, breathe. in. out. in. out. Ok I'm fine.]] Reading the javadoc for this made me realize just why you can instantiate the field without a specific object reference and use the same one - you pass the specific reference to .get(). So, now the code works.

 

I'm actually going to move the Field instantiate to a static constructor to simplify the actual hook. It'll work, though, so no need for me to put it here :D

 

 

 

A great big THANK YOU (as in the button AND an internet hug (assuming you aren't a hug-hater or anything) #hugamodder (this is not a thing to my knowledge)) to the people who helped me. I couldn't have figured this out on my own. I love doing this and I love learning - THANK YOU for helping me :D

Link to comment
Share on other sites

If you're interested, here is my final code.

package cad97.commandblocksigns;

import java.lang.reflect.Field;

import net.minecraft.client.gui.inventory.GuiEditSign;
import net.minecraft.tileentity.TileEntitySign;
import net.minecraftforge.client.event.GuiScreenEvent.ActionPerformedEvent;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;

public class CommandBlockSignsListener
{
static
{
	try
	{
		f = GuiEditSign.class.getDeclaredField("tileSign");
	} catch (NoSuchFieldException | SecurityException e)
	{
		e.printStackTrace();
	}
}

private static Field f;

@SubscribeEvent
public void onSignDone(ActionPerformedEvent.Pre event)
{
	if (event.gui instanceof GuiEditSign)
	{
		if (f != null)
		{
			try
			{
				f.setAccessible(true);
				GuiEditSign ges = (GuiEditSign) event.gui;
				TileEntitySign tileSign;
				tileSign = (TileEntitySign) f.get(ges);
				if (WaypointSign.isValidWaypointSign(tileSign))
				{
					WaypointSign.register(tileSign.getWorldObj(), new WaypointSign(tileSign.signText));
				}
			} catch (IllegalArgumentException | IllegalAccessException e)
			{
				e.printStackTrace();
			}
		}
	}
}
}

 

I unfortunately can't do f.setAccessible(true); in the static constructor; Eclipse tells me "Cannot reference a field before it is defined." Oh well, re-setting one boolean every time is no real cost  :P

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



×
×
  • Create New...

Important Information

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