Event Handler and Events: How to set up and use Forge Events
Updating to 1.8
As far as events go, the change is simple: change EVERY import with 'cpw.mods.fml...' to 'net.minecraftforge.fml...'
That's it, but they need to be changed in EVERY file, which can be tedious, though if you're smart rather than hard-working, you could write a script.
(Don't forget to check out POST#2 for the tutorial on IExtendedEntityProperties!)
Preamble: If you're looking to have armor do something special every tick, there is a much easier way than using either Tick or Event Handlers. See my post here if that's what you're trying to do. It will be worth your time, trust me
Tutorial:
If you want to change any type of default Minecraft behavior, chances are there is a Forge Event that handles it.
There are Player Events, Living Events, Item Events, World Events, TerrainGenEvents, Minecart Events... there's just so much you can do with these it's incredible.
I personally prefer using EventHandler over TickHandler for this very reason - most things you could ever want to do already have a framework built to handle it, whereas Tick Handler you have to build it all yourself.
Creating and Using Your Event Handler:
IMPORTANT!!! Do NOT name your EventHandler 'EventHandler' - that's already the name of a Forge class. Also, do NOT edit any of the Forge classes. You need to create a new class to handle events.
Step 1: Create TutEventHandler class
// Note that this step is the same in both 1.6.4 and 1.7.2.
public class TutEventHandler
{
}
Step 2: Register your event handler class to EVENT_BUS either in 'load' or 'postInit' methods in your main mod. Note that this step is the same in both 1.6.4 and 1.7.2.
@EventHandler
public void load(FMLInitializationEvent event)
{
// IMPORTANT: Be sure to register your handler on the correct bus!!! (see below)
// the majority of events use the MinecraftForge event bus:
MinecraftForge.EVENT_BUS.register(new TutEventHandler());
// but some are on the FML bus:
FMLCommonHandler.instance().bus().register(new YourFMLEventHandler());
}
Registering to the Correct BUS
You have followed all the steps and your event handling methods just do not seem to be working, what could possibly be going on? Well, each event is posted to a different event bus, and if your event handler is registered to the incorrect bus, then your method will never get called. The vast majority of events are posted to the MinecraftForge.EVENT_BUS, but there are several other event buses:
1. MinecraftForge.EVENT_BUS: Most events get posted to this bus.
2. MinecraftForge.TERRAIN_GEN_BUS: Most world generation events happen here, such as Populate, Decorate, etc., with the strange exception that Pre and Post events are on the regular EVENT_BUS
4. FML Events: these become very important in 1.7.2, as this is where TickEvents and KeyInputEvents are posted, with TickHandler and KeyHandler no longer existing.
It is very important to register your event handler to the correct event bus, and only put those events that get posted to a certain event bus in a handler registered to that bus, or your event handling will fail.
You're finished! That was easy But it doesn't do anything right now, so on to step 3.
Step 3: Add events to your event handler (an example)
Look through MinecraftForge event types for ones you want to use and add them to your EventHandler by creating a new method with the appropriate Event as a parameter.
1.6.4: It must be prefaced by "@ForgeSubscribe" so that it gets called automatically at the right times.
1.7.2: It must be prefaced by "@SubscribeEvent" so that it gets called automatically at the right times.
Do not, I repeat do NOT edit the event classes directly. Also, you do NOT need to make a class extending the Event class.
// In your TutEventHandler class - the name of the method doesn't matter
// Only the Event type parameter is what's important (see below for explanations of some types)
@ForgeSubscribe
public void onLivingUpdateEvent(LivingUpdateEvent event)
{
// This event has an Entity variable, access it like this:
event.entity;
// do something to player every update tick:
if (event.entity instanceof EntityPlayer)
{
EntityPlayer player = (EntityPlayer) event.entity;
ItemStack heldItem = player.getHeldItem();
if (heldItem != null && heldItem.itemID == Item.arrow.itemID) {
player.capabilities.allowFlying = true;
}
else {
player.capabilities.allowFlying = player.capabilities.isCreativeMode ? true : false;
}
}
If you're ever curious what variables the Event stores, type ' event. ' in Eclipse and it will bring up a menu of all the methods and variables. Or go to the implementation by ctrl-clicking on the class name.
Step 4: Using Events in your custom classes
Forge Events are all hooked into automatically from vanilla code, but say you made a custom Bow and want it to use ArrowNock and ArrowLoose events? You need to post them to the event bus in your item code.
/** ArrowNockEvent should be placed in 'onItemRightClick' */
@Override
public ItemStack onItemRightClick(ItemStack itemstack, World world, EntityPlayer player)
{
// Create the event and post it
ArrowNockEvent event = new ArrowNockEvent(player, itemstack);
MinecraftForge.EVENT_BUS.post(event);
if (event.isCanceled())
{
// you could do other stuff here as well
return event.result;
}
player.setItemInUse(itemstack, this.getMaxItemUseDuration(itemstack));
}
return itemstack;
}
/** ArrowLooseEvent should be placed in 'onPlayerStoppedUsing' */
@Override
public void onPlayerStoppedUsing(ItemStack itemstack, World world, EntityPlayer player, int par4)
{
// Ticks in use is max duration minus par4, which is equal to max duration - 1 for every tick in use
int ticksInUse = this.getMaxItemUseDuration(itemstack) - par4;
ArrowLooseEvent event = new ArrowLooseEvent(player, itemstack, ticksInUse);
MinecraftForge.EVENT_BUS.post(event);
if (event.isCanceled()) { return; }
// ticksInUse might be modified by the Event in your EventHandler, so reassign it here:
ticksInUse = event.charge;
// Do whatever else you want with the itemstack like fire an arrow or cast a spell
}
Step 5: Adding events to your EventHandler
Look through MinecraftForge event types for ones you want to use and add them to your EventHandler by creating a new method with the appropriate Event as a parameter.
1.6.4: It must be prefaced by "@ForgeSubscribe" so that it gets called automatically at the right times.
1.7.2: It must be prefaced by "@SubscribeEvent" so that it gets called automatically at the right times.
Do not, I repeat do NOT edit the event classes directly. Also, you do NOT need to make a class extending the Event class.
The following is a template for whatever event method you want to make. Name it whatever you want, but use the correct Event Type from above.
Event variables are accessed by using 'event.variableName' I give the variable names for many Events below.
@ForgeSubscribe
public void methodName(EventType event)
{
// do whatever you want here
}
A Sampling of Different Forge Events and Some Potential Uses
First, a word of warning: many of these events ONLY get called on one side or the other, so if something is not working as expected, check which side(s) the event is being called on and it may surprise you.
An easy way to check is to put a line of debugging code at the beginning of each event method, so long as that event has access to some kind of entity:
@ForgeSubscribe
public void someEventMethod(SomeEvent event) {
System.out.println("Some event called; is this the client side? " + event.entity.worldObj.isRemote);
}
// IMPORTANT: The following were for 1.6.4; while many have not changed, some most certainly have.
// Always check the net.minecraftforge.event package for the available events, no matter what version
// of Minecraft you are modding for.
1. ArrowNockEvent
Variables: EntityPlayer player, ItemStack result
Usually called from 'onItemRightClick'.
Uses:It is cancelable, so if some conditions are not met (e.g. no arrows in inventory) you could cancel it and stop the player from setting the item in use. One thing I use it for is to set a boolean 'isAiming', which I can then interrupt if the player takes damage or some such (using another boolean 'wasInterrupted')
2. ArrowLooseEvent
Variables: EntityPlayer player, ItemStack bow, int charge
Usually called from 'onPlayerStoppedUsing'. It is also cancelable.
Uses: I use it in tandem with the above to check if the player was interrupted, and if so, cancel the event.
3. EntityConstructing
Variables: Entity entity
Called for every Entity when its constructor is called.
Uses: Useful if you need to add ExtendedEntityProperties.
4. EntityJoinWorldEvent
Variables: Entity entity, World world
Called when an entity joins the world for the first time.
Uses: Useful for synchronizing ExtendedEntityProperties, giving your player an item when spawned or any other number of things.
5. LivingUpdateEvent
Variables: EntityLivingBase entity
Called every tick at the beginning of the entity's onUpdate method.
Uses: This is probably the most useful Event. You can allow players to fly if holding an item or wearing your armor set, you can modify a player's fall speed here, add potion effects or anything else you can imagine. It's really really handy.
6. LivingDropsEvent
Variables: EntityLivingBase entity, DamageSource source, ArrayList drops, int lootingLevel, boolean recentlyHit, int specialDropValue
Called when an entity is killed and drops items.
Uses: Handy if you want to modify a vanilla mobs drops or only drop your custom item if it was killed from your custom DamageSource. You can also remove items from drops, adjust it based on the looting enchantment level of the item used to kill it, etc. Pretty useful.
7. LivingFallEvent
Variables: EntityLivingBase entity, float distance
Called when the entity hits the ground after a fall, but before damage is calculated.
SPECIAL NOTE: This event is NOT called while in Creative Mode; PlayerFlyableFallEvent is called instead
Uses: It is cancelable, so 'event.setCanceled(true)' will preclude further processing of the fall.
You can also modify the distance fallen here, but keep in mind this is ONLY on impact. If you want
to modify fall distance only while certain conditions are met, better to do it in LivingUpdateEvent.
Also, be sure you are modifying 'event.distance' and NOT 'entity.fallDistance' or you won't change the outcome of the fall
8. LivingJumpEvent
Variables: EntityLivingBase entity
Called whenever entity jumps.
Uses: Useful for entity.motionY += 10.0D. Just give it a try
9. LivingAttackEvent
Variables: EntityLivingBase entity, DamageSource source, float ammount
Called when an entity is attacked, but before any damage is applied
Uses: Cancelable. Here you can do pre-processing of an attack before LivingHurtEvent is called. The source entity of the attack is stored in DamageSource, and you can adjust the damage to be dealt however you see fit. Basically the same uses as LivingHurtEvent, but done sooner.
NOTE: This method is only called on the server side EXCEPT when an EntityPlayer is the source of the attack, in which case it is called on both sides.
10. LivingHurtEvent
Variables: EntityLivingBase entity, DamageSource source, float ammount
Called when an entity is damaged, but before any damage is applied
Uses: Another super useful one if you have custom armor that reduces fire damage, increases damage taken from magic, do something if ammount is greater than current health or whatever.
11. LivingDeathEvent
Variables: EntityLivingBase entity, DamageSource source
Called when an entity dies; cancelable!
Uses: Recall that DamageSource has lots of variables, too, such as getEntity() that returns the entity that caused the damage and thus that killed the current entity. All sorts of things you could do with that. This is also the place to cancel death and resurrect yourself, or set a timer for resurrection. If you have data you want to persist through player death, such as IExtendedEntityProperties, you can save that here as well.
12. EntityInteractEvent
Variables: EntityPlayer player, Entity target
Called when the player right-clicks on an entity, such as a cow
Uses: Gee, you could do anything with this. One use could be getting milk into your custom bucket...
13. EntityItemPickupEvent
Variables: EntityPlayer player, EntityItem item
Called when the player picks up an item
Uses: This one is useful for special items that need handling on pickup; an example would be if you made
something similar to experience orbs, mana orbs for instance, that replenish mana rather than adding an item to inventory
14. HarvestCheck
Variables: EntityPlayer player, Block block, boolean success
Called when the player breaks a block before the block releases its drops
Uses: Coupled with the BreakSpeed event, this is perhaps the best way to change the behavior of mining.
Advanced Information
Setting Priority
Note that event priority works exactly the same in 1.7.2, other than the primary annotation changing to @SubscribeEvent.
Priority is the order in which all listeners listening to a posted event are called. A listener is a method with the @ForgeSubscribe annotation, and is said to be actively listening if the class containing the method was registered to the MinecraftForge EVENT_BUS. Whenever an event is posted matching the parameters of the listener, the listener's method is called.
When there are multiple listeners listening to a single event, the order in which they are called is important if one listener's functionality relies on having first or last access to the event in process, or if it relies on information set by a prior listener. This can be especially useful with Cancelable events.
A single Event can be handled multiple times by different handlers or even within the same handler provided the methods have different names:
@ForgeSubscribe
public void onLivingHurt(LivingHurtEvent event) {}
@ForgeSubscribe
public void onPlayerHurt(LivingHurtEvent event) {}
Both methods will be called each time a LivingHurtEvent is posted to the EVENT_BUS in the order they are added to the event listener (see IEventListener), which in the case above is simply their order in the code. The order can be controlled by appending (priority=VALUE) to the @ForgeSubscribe annotation, where VALUE is defined in the EventPriority enum class. HIGHEST priority is always called first, while LOWEST priority is called last.
// this method will now be called after 'onPlayerHurt()'
@ForgeSubscribe(priority=LOWEST)
public void onLivingHurt(LivingHurtEvent event) {}
@ForgeSubscribe(priority=HIGHEST)
public void onPlayerHurt(LivingHurtEvent event) {}
If two listeners have the same priority level, then the order is again controlled by the order in which they are added. In order to control the flow for such a case within a mod, the methods can be placed in separate 'event handler' classes to be registered in the order desired:
// In the case of identical priority levels, PlayerHurtHandler will process first
MinecraftForge.EVENT_BUS.register(new PlayerHurtHandler());
MinecraftForge.EVENT_BUS.register(new LivingHurtHandler());
For multiple Mods that affect the same events, the order of mod registration would have the same effect.
Cancelable Events
Events with the @Cancelable annotation have the special quality of being cancelable. Once an event is canceled, subsequent listeners will not process the event unless provided with special annotation:
@ForgeSubscribe // default priority, so it will be called first
public void onLivingHurt(LivingHurtEvent event) {
event.setCanceled(true);
}
@ForgeSubscribe(priority=LOWEST, receiveCanceled=true)
public void onPlayerHurt(LivingHurtEvent event) {
// un-cancel the event
event.setCanceled(false);
}
By controlling the order in which each listener method is called, it is usually possible to avoid un-canceling a previously canceled event, although exceptional circumstances do arise; in those cases, extra care must be taken to avoid making logical errors.
More will be added as I learn. Thanks to GotoLink for his excellent explanations regarding priority.
1.7.2 TickEvents and Creating a TickHandler
!!! STOP !!!
Chances are, you do NOT need to create a tick handler for whatever it is you are doing. There are many methods built-in to Minecraft that already act as tick handlers, and it is ALWAYS better to use them when you can. Why? Because they tick only when the object in question actually exists, whereas a generic tick handler processes every tick no matter what. Here are some of the pre-made tickers at your disposal:
Entity#onUpdate: called every tick for each Entity; to manipulate vanilla entities, use LivingUpdateEvent
TileEntity#onUpdate: called for tile entities every tick unless you tell it not to tick
Item#onUpdate: called every tick while the specific item is in a player's inventory
Item#onArmorTick: called only for armor each tick that it is equipped
Block#updateTick: may be called randomly based on the block's tick rate, or it may be scheduled
As you can see, nearly everything you could ever want to tick already has that capability. If what you want to do simply cannot be handled using one of the built-in tick methods, then and ONLY THEN should you consider creating a tick handler. Here's how to do so.
In 1.7.2, as I'm sure many of you have noticed, the TickHandler class is gone, replaced by what appears to be a single TickEvent that we must now subscribe to in order to achieve the same functionality. Below, I will break it down and explain what exactly is going on and how to use it effectively.
Step 1: Determine Which TickEvent to Use
There are actually five different subclasses of TickEvent, each called on a specific in-game tick and on a specific side or sides. It is very important to understand the difference between these events and to use the appropriate one:
ServerTickEvent: called on the server side only
ClientTickEvent: called on the client side only
WorldTickEvent: both sides
PlayerTickEvent: both sides
RenderTickEvent: client side only and called each render tick
When creating a TickHandler, be sure NOT to subscribe to the generic "TickEvent", as that will listen to every single tick type on every single tick, which is just wasting processing time.
Once you decide which tick you need, then it is time to create a new TickHandler class.
Step 2: Create a TickHandler
For now, we are just going to use the old name TickHandler, though we are really creating an event handler to listen to a specific tick. As an example, I will create a RenderTickEvent handler, since that is what I used in Zelda Sword Skills to make the spin attack motion smooth.
// RenderTick is client side only, so we can place the SideOnly annotation here if we want:
@SideOnly(Side.CLIENT)
public class RenderTickHandler {
// we only need one method here, and you can name it whatever you want, but
// I like to name it according to the tick to which I am listening:
@SubscribeEvent
public void onRenderTick(RenderTickEvent event) {
// now you can do whatever you want during each render tick, such as rotate the player's view
}
}
If you need to do different things at the beginning of the tick than at the end of the tick, you can check the tick event's phase in your onWhateverTick method; this is applicable for all TickEvents.
if (event.phase == Phase.START) {
// this is the equivalent of tickStart
}
if (event.phase == Phase.END) {
// and this is the equivalent of tickEnd
}
// if you have a lot of stuff going on in each of those, you could separate them into separate methods:
@SubscribeEvent
public void onRenderTick(RenderTickEvent event) {
if (event.phase == Phase.START) {
onTickStart();
} else {
onTickEnd();
}
}
One other thing to mention here is that since we are on the client side, we might be using Minecraft.getMinecraft() quite a lot to access things like the world, player, etc. Since it would be very wasteful to have to do this each and every tick, a better way is to store the instance of Minecraft when you construct your tick handler:
@SideOnly(Side.CLIENT)
public class RenderTickHandler {
/** Stores an instance of Minecraft for easy access */
private Minecraft mc;
// create a constructor that takes a Minecraft argument; now we have it whenever we need it
public RenderTickHandler(Minecraft mc) {
this.mc = mc;
}
@SubscribeEvent
public void onRenderTick(RenderTickEvent event) {
if (event.phase == Phase.START) {
// we will make the player spin constantly in circles:
mc.thePlayer.rotationYaw += 10.0F;
}
}
}
Step 3: Registering Your TickHandler
All TickEvents need to be registered to the FMLCommonHandler event bus, NOT the MinecraftForge EVENT_BUS. For our client-sided render tick handler, we will do this in the ClientProxy, because we will crash the game if we try to register it in a method that is run on the server.
public class CommonProxy {
/**
* We will call this method from our main mod class' FMLPreInitializationEvent method
*/
public void initialize() {
// since we are not registering a tick handler that ticks on the server, we will not put anything here for now
// but if you had a WorldTickEvent or PlayerTickEvent, for example, this is where you should register it
// if you try to register the RenderTickHandler here, your game WILL crash
}
}
public class ClientProxy extends CommonProxy {
// Our ClientProxy method only gets run on the client side, so it is safe to register our RenderTickHandler here
@Override
public void initialize() {
// calling super will register any 2-sided tick handlers you have that are registered in the CommonProxy
// this is important since the CommonProxy will only register it on the server side, and you will need it
// registered on the client as well; however, we do not have any at this point
super.initialize();
// here we register our RenderTickHandler - be sure to pass in the instance of Minecraft!
FMLCommonHandler.instance().bus().register(new RenderTickHandler(Minecraft.getMinecraft()));
// this is also an ideal place to register things like KeyBindings
}
}
That should be all you need to use TickEvents successfully and efficiently. Remember, NEVER subscribe to TickEvent, ONLY subscribe to the specific type of tick event that you really need, just as you would never subscribe to Event...
Good luck with 1.7.2!
Well, I hope that was helpful! If you have any other creative ways of using Events that you'd like to share, or you'd like an explanation of a specific event I didn't cover, leave a comment below and I'll add it in.
In this tutorial I will cover how to add variables to an entity by using Forge's IExtendedEntityProperties. How did I learn all this stuff, you ask? Well, it's all pretty well documented within the Forge code itself. Hover your mouse over most Forge methods and you'll get a great 'tooltip' pop up that explains pretty much everything about it. Or just open up the class you're curious about.
Go ahead, give it a try! If it still doesn't make sense, then read on
We will add mana to all players, show how to use it, and add gold coins to all EntityLivingBase entities.
We will also use our custom data in a Gui which involves setting up a packet handler to send information to the client.
Finally, we will make it so our custom data persists even when the player dies and respawns.
If you read all the way through and follow all of the steps correctly, your custom properties will also be multi-player compatible straight out of the box, so to speak. Tested using the Eclipse server.
Prerequisites:
1. Know how to set up and use Forge Events. See my tutorial on creating an EventHandler.
2. Willingness to read carefully.
NOTES: Updating from 1.6.4 to 1.7.2
First, be sure you have read and understood how to set up and use IExtendedEntityProperties in 1.6.4, or these notes will not help you very much. This is simply how to update existing code to the new 1.7.2 setup.
Updating IExtendedEntityProperties and the EventHandler
Not much has changed here:
private static final String getSaveKey(EntityPlayer player) {
// no longer a username field, so use the command sender name instead:
return player.getCommandSenderName() + ":" + EXT_PROP_NAME;
}
public static final void loadProxyData(EntityPlayer player) {
ExtendedPlayer playerData = ExtendedPlayer.get(player);
NBTTagCompound savedData = CommonProxy.getEntityData(getSaveKey(player));
if (savedData != null) { playerData.loadNBTData(savedData); }
// we are replacing the entire sync() method with a single line; more on packets later
// data can by synced just by sending the appropriate packet, as everything is handled internally by the packet class
TutorialMain.packetPipeline.sendTo(new SyncPlayerPropsPacket(player), (EntityPlayerMP) player);
}
// remove the public void sync() method; it is no longer needed
For the EventHandler, pretty much all you have to do is replace @ForgeSubscribe with @SubscribeEvent and fix your imports. Everything else should work the same as before. Easy.
Once you have copied and pasted that code (seriously, it's that simple) into your project, you will need to make a new package for your packets and register them with the PacketPipeline. I do it like so:
// modify this method from the wiki in the PacketPipeline class:
public void initialise() {
// NOTE: Be sure to change the channel from "TUT" to whatever you are using!!!
this.channels = NetworkRegistry.INSTANCE.newChannel("TUT", this);
// line added by me to register all my packets:
registerPackets();
}
// And add this method right below it
// IMPORTANT: Always remember to add your newly created packet classes here or you WILL crash
public void registerPackets() {
registerPacket(OpenGuiPacket.class);
registerPacket(SyncPlayerPropsPacket.class);
}
That's really all you have to do. Then you just make the packet classes themselves.
OpenGuiPacket
public class OpenGuiPacket extends AbstractPacket
{
// this will store the id of the gui to open
private int id;
// The basic, no-argument constructor MUST be included to use the new automated handling
public OpenGuiPacket() {}
// if there are any class fields, be sure to provide a constructor that allows
// for them to be initialized, and use that constructor when sending the packet
public OpenGuiPacket(int id) {
this.id = id;
}
@Override
public void encodeInto(ChannelHandlerContext ctx, ByteBuf buffer) {
// basic Input/Output operations, very much like DataOutputStream
buffer.writeInt(id);
}
@Override
public void decodeInto(ChannelHandlerContext ctx, ByteBuf buffer) {
// basic Input/Output operations, very much like DataInputStream
id = buffer.readInt();
}
@Override
public void handleClientSide(EntityPlayer player) {
// for opening a GUI, we don't need to do anything here
}
@Override
public void handleServerSide(EntityPlayer player) {
// because we sent the gui's id with the packet, we can handle all cases with one line:
player.openGui(TutorialMain.instance, id, player.worldObj, (int) player.posX, (int) player.posY, (int) player.posZ);
}
}
SyncPlayerPropsPacket
public class SyncPlayerPropsPacket extends AbstractPacket
{
// Previously, we've been writing each field in our properties one at a time,
// but that is really annoying, and we've already done it in the save and load
// NBT methods anyway, so here's a slick way to efficiently send all of your
// extended data, and no matter how much you add or remove, you'll never have
// to change the packet / synchronization of your data.
// this will store our ExtendedPlayer data, allowing us to easily read and write
private NBTTagCompound data;
// The basic, no-argument constructor MUST be included to use the new automated handling
public SyncPlayerPropsPacket() {}
// We need to initialize our data, so provide a suitable constructor:
public SyncPlayerPropsPacket(EntityPlayer player) {
// create a new tag compound
data = new NBTTagCompound();
// and save our player's data into it
ExtendedPlayer.get(player).saveNBTData(data);
}
@Override
public void encodeInto(ChannelHandlerContext ctx, ByteBuf buffer) {
// ByteBufUtils provides a convenient method for writing the compound
ByteBufUtils.writeTag(buffer, data);
}
@Override
public void decodeInto(ChannelHandlerContext ctx, ByteBuf buffer) {
// luckily, ByteBufUtils provides an easy way to read the NBT
data = ByteBufUtils.readTag(buffer);
}
@Override
public void handleClientSide(EntityPlayer player) {
// now we can just load the NBTTagCompound data directly; one and done, folks
ExtendedPlayer.get(player).loadNBTData(data);
}
@Override
public void handleServerSide(EntityPlayer player) {
// we never send this packet to the server, so do nothing here
}
}
Do note, however, that it is not good practice to synchronize everything all the time; if, for example, you have lots of different things that change often in your extended properties such as mana, experience, levels, etc., it is better to have separate packets for each, so that you can update your mana without having to update everything else at the same time. Much easier on the network this way, but for the time being, we will just stick with a single packet.
GuiManaBar
In the Gui, fontRenderer is now fontRendererObj, and you need to change to I18n.format if you were using that to translate the names at all. Use the updated IInventory method names for getInventoryName(), etc
Updating the Item class
Everything is pretty much exactly the same, with a few minor differences:
1. Remove the int parameter from the constructor; item IDs are history
2. Change 'Icon' to 'IIcon' (two I's)
3. Change 'IconRegister' to 'IIconRegister' (two I's)
4. Update imports
NOTES: Updating from Forge 804 to 871/883
Three things you'll need to change in your GUI files:
1. I18n.func_135053_a() is now I18n.getString()
2. mc.func_110434_K() is now mc.renderEngine OR mc.getTextureManager()
3. renderEngine.func_110577_a() is now renderEngine.bindTexture()
IMPORTANT NOTE [1.6.4 only]
Using IExtendedEntityProperties adds new data to entities, and the server handles initializing, maintaining, loading and saving of all data. For multi-player compatibility outside of the Eclipse server environment, you must require your mod to be installed server-side for the data to exist. This means your @NetworkMod line will look like this:
If you need to display any of this information, such as in a Gui, you need to send a packet to the client with the data to display. This is the only kind of information the client needs to know. For a more complete explanation, see this post (#46).
Thanks to Pawaox for bringing this to my attention, and Seigneur_Necron for confirming the requirements.
Now on to the tutorial!
Step 1: Create a class that implements IExtendedEntityProperties
Since we are first making variables specific to EntityPlayer, we will call this class "ExtendedPlayer" so it's always obvious what kind of entity can use it. This will be important if you add different variables to different entities.
public class ExtendedPlayer implements IExtendedEntityProperties
{
/*
Here I create a constant EXT_PROP_NAME for this class of properties. You need a unique name for every instance of IExtendedEntityProperties you make, and doing it at the top of each class as a constant makes
it very easy to organize and avoid typos. It's easiest to keep the same constant name in every class, as it will be distinguished by the class name: ExtendedPlayer.EXT_PROP_NAME vs. ExtendedEntity.EXT_PROP_NAME
Note that a single entity can have multiple extended properties, so each property should have a unique name. Try to come up with something more unique than the tutorial example.
*/
public final static String EXT_PROP_NAME = "ExtendedPlayer";
// I always include the entity to which the properties belong for easy access
// It's final because we won't be changing which player it is
private final EntityPlayer player;
// Declare other variables you want to add here
// We're adding mana to the player, so we'll need current and max mana
private int currentMana, maxMana;
/*
The default constructor takes no arguments, but I put in the Entity so I can initialize the above variable 'player'
Also, it's best to initialize any other variables you may have added, just like in any constructor.
*/
public ExtendedPlayer(EntityPlayer player)
{
this.player = player;
// Start with max mana. Every player starts with the same amount.
this.currentMana = this.maxMana = 50;
}
/**
* Used to register these extended properties for the player during EntityConstructing event
* This method is for convenience only; it will make your code look nicer
*/
public static final void register(EntityPlayer player)
{
player.registerExtendedProperties(ExtendedPlayer.EXT_PROP_NAME, new ExtendedPlayer(player));
}
/**
* Returns ExtendedPlayer properties for player
* This method is for convenience only; it will make your code look nicer
*/
public static final ExtendedPlayer get(EntityPlayer player)
{
return (ExtendedPlayer) player.getExtendedProperties(EXT_PROP_NAME);
}
// Save any custom data that needs saving here
@Override
public void saveNBTData(NBTTagCompound compound)
{
// We need to create a new tag compound that will save everything for our Extended Properties
NBTTagCompound properties = new NBTTagCompound();
// We only have 2 variables currently; save them both to the new tag
properties.setInteger("CurrentMana", this.currentMana);
properties.setInteger("MaxMana", this.maxMana);
/*
Now add our custom tag to the player's tag with a unique name (our property's name). This will allow you to save multiple types of properties and distinguish between them. If you only have one type, it isn't as important, but it will still avoid conflicts between your tag names and vanilla tag names. For instance, if you add some "Items" tag, that will conflict with vanilla. Not good. So just use a unique tag name.
*/
compound.setTag(EXT_PROP_NAME, properties);
}
// Load whatever data you saved
@Override
public void loadNBTData(NBTTagCompound compound)
{
// Here we fetch the unique tag compound we set for this class of Extended Properties
NBTTagCompound properties = (NBTTagCompound) compound.getTag(EXT_PROP_NAME);
// Get our data from the custom tag compound
this.currentMana = properties.getInteger("CurrentMana");
this.maxMana = properties.getInteger("MaxMana");
// Just so you know it's working, add this line:
System.out.println("[TUT PROPS] Mana from NBT: " + this.currentMana + "/" + this.maxMana);
}
/*
I personally have yet to find a use for this method. If you know of any,
please let me know and I'll add it in!
*/
@Override
public void init(Entity entity, World world)
{
}
/*
That's it for the IExtendedEntityProperties methods, but we need to add a few of our own in order to interact with our new variables. For now, let's make one method to consume mana and one to replenish it.
*/
/**
* Returns true if the amount of mana was consumed or false
* if the player's current mana was insufficient
*/
public boolean consumeMana(int amount)
{
// Does the player have enough mana?
boolean sufficient = amount <= this.currentMana;
// Consume the amount anyway; if it's more than the player's current mana,
// mana will be set to 0
this.currentMana -= (amount < this.currentMana ? amount : this.currentMana);
// Return if the player had enough mana
return sufficient;
}
/**
* Simple method sets current mana to max mana
*/
public void replenishMana()
{
this.currentMana = this.maxMana;
}
}
Step 2: Register the ExtendedPlayer class in your EventHandler
In order to access our newly created extended player properties, we need to register them for every instance of EntityPlayer. That just means we're creating a new instance of the class so we can access it.
Registering of IExtendedEntityProperties is all done in the EntityConstructing event.
public class TutEventHandler
{
@ForgeSubscribe
public void onEntityConstructing(EntityConstructing event)
{
/*
Be sure to check if the entity being constructed is the correct type for the extended properties you're about to add! The null check may not be necessary - I only use it to make sure properties are only registered once per entity
*/
if (event.entity instanceof EntityPlayer && ExtendedPlayer.get((EntityPlayer) event.entity) == null)
// This is how extended properties are registered using our convenient method from earlier
ExtendedPlayer.register((EntityPlayer) event.entity);
// That will call the constructor as well as cause the init() method
// to be called automatically
// If you didn't make the two convenient methods from earlier, your code would be
// much uglier:
if (event.entity instanceof EntityPlayer && event.entity.getExtendedProperties(ExtendedPlayer.EXT_PROP_NAME) == null)
event.entity.registerExtendedProperties(ExtendedPlayer.EXT_PROP_NAME, new ExtendedPlayer((EntityPlayer) event.entity));
}
That's it! All players will now start with a pool of mana, so we just have to do
something with it.
Step 3.1: Using our new ExtendedPlayer Properties in an Item
For the sake of demonstration, we'll make a very basic item called... wait for it...
'ItemUseMana'. I should be naming mountains with this kind of stuff.
Anyways, here's our new ItemUseMana class. Since you probably know all about items already, I will only discuss things related to IExtendedEntityProperties.
public class ItemUseMana extends Item
{
public ItemUseMana(int id) {
super(id);
}
@Override
public ItemStack onItemRightClick(ItemStack itemstack, World world, EntityPlayer player)
{
if (!world.isRemote)
{
/*
Due to the length of code needed to get extended entity properties, I always find it handy to create a local variable named 'props' for whatever properties I need.
Also, getExtendedProperties("name") returns the type 'IExtendedEntityProperties', so you need to cast it as your extended properties type for it to work.
Old, ugly method:
ExtendedPlayer props = (ExtendedPlayer) player.getExtendedProperties(ExtendedPlayer.EXT_PROP_NAME);
This is using Seigneur_Necron's slick method (will be used from here on):
*/
ExtendedPlayer props = ExtendedPlayer.get(player);
// Here we'll use the method we made to see if the player has enough mana to do something
// We'll print something to the console for debugging, but I'm sure you'll find a much
// better action to perform.
if (props.consumeMana(15))
{
System.out.println("[MANA ITEM] Player had enough mana. Do something awesome!");
}
else
{
System.out.println("[MANA ITEM] Player ran out of mana. Sad face.");
props.replenishMana();
}
}
return itemstack;
}
}
Try it out and check the console. Hooray! It should have worked. If not, double-check that you have registered your EventHandler in your main mod load method.
For the sake of convenience, here is the code:
@EventHandler
public void load(FMLInitializationEvent event)
{
MinecraftForge.EVENT_BUS.register(new TutEventHandler());
}
Step 3.2: Using our new ExtendedPlayer Properties in an Event
Events are another really great place to use additional properties. Since we only added mana, we will pretend that it is in fact a spell that prevents damage from falling up to a certain distance.
In order to do this, we'll need to add a getCurrentMana method to our ExtendedPlayer class. It just returns 'this.currentMana'
Next, we add a LivingFallEvent to our EventHandler:
@ForgeSubscribe
public void onLivingFallEvent(LivingFallEvent event)
{
// Remember that so far we have only added ExtendedPlayer properties
// so check if it's the right kind of entity first
if (event.entity instanceof EntityPlayer)
{
ExtendedPlayer props = ExtendedPlayer.get((EntityPlayer) event.entity);
// This 'if' statement just saves a little processing time and
// makes it so we only deplete mana from a fall that would injure the player
if (event.distance > 3.0F && props.getCurrentMana() > 0)
{
// Some debugging statements so you can see what's happening
System.out.println("[EVENT] Fall distance: " + event.distance);
System.out.println("[EVENT] Current mana: " + props.getCurrentMana());
/*
We need to make a local variable to store the amount to reduce both the distance and mana, otherwise when we reduce one, we have no way to tell by how much to reduce the other
Alternatively, you could just try to consumeMana for the amount of the fall distance and, if it returns true, set the fall distance to 0, but today we're going for a cushioning effect instead.
If you want mana to be used efficiently, you would only reduce the fall distance by enough to reduce it to 3.0F (3 blocks), thus ensuring the player will take no damage while minimizing mana consumed.
Be sure you put (event.distance - 3.0F) in parentheses or you'll have a nasty bug with your mana! It has to do with the way "x < y ? a : b" parses parameters.
*/
float reduceby = props.getCurrentMana() < (event.distance - 3.0F) ? props.getCurrentMana() : (event.distance - 3.0F);
event.distance -= reduceby;
// Cast reduceby to 'int' to match our method parameter
props.consumeMana((int) reduceby);
System.out.println("[EVENT] Adjusted fall distance: " + event.distance);
}
}
}
Step 3.3: Using our new Extended Properties in a Gui Overlay
By request, here is a guide that will get you a functional mana bar on your screen. It's a bit lengthy, so bear with me.
Here we will create a mana bar display in the upper-left corner of the screen using currentMana and maxMana from our ExtendedPlayer class.
The first part of this section is from http://www.minecraft...iki/Gui_Overlay I highly recommend you read that tutorial before continuing on, as it contains lots of great information related to this topic.
This is our GuiManaBar class:
@SideOnly(Side.CLIENT)
public class GuiManaBar extends Gui
{
private Minecraft mc;
/* (my added notes:)
ResourceLocation takes 2 arguments: your mod id and the path to your texture file, starting from the folder 'textures/' from '/src/minecraft/assets/yourmodid/', or you can write it all as a single argument.
The texture file must be 256x256 (or multiples thereof)
If you want a texture to test out the tutorial with, I've uploaded the mana_bar.png to my github page:
https://github.com/coolAlias/Forge_Tutorials/tree/master/textures/gui
*/
private static final ResourceLocation texturepath = new ResourceLocation("tutorial", "textures/gui/mana_bar.png");
public GuiManaBar(Minecraft mc)
{
super();
// We need this to invoke the render engine.
this.mc = mc;
}
//
// This event is called by GuiIngameForge during each frame by
// GuiIngameForge.pre() and GuiIngameForce.post().
//
@ForgeSubscribe(priority = EventPriority.NORMAL)
public void onRenderExperienceBar(RenderGameOverlayEvent event)
{
// We draw after the ExperienceBar has drawn. The event raised by GuiIngameForge.pre()
// will return true from isCancelable. If you call event.setCanceled(true) in
// that case, the portion of rendering which this event represents will be canceled.
// We want to draw *after* the experience bar is drawn, so we make sure isCancelable() returns
// false and that the eventType represents the ExperienceBar event.
if (event.isCancelable() || event.type != ElementType.EXPERIENCE)
{
return;
}
/** Start of my tutorial */
// Get our extended player properties and assign it locally so we can easily access it
ExtendedPlayer props = ExtendedPlayer.get(this.mc.thePlayer);
// If for some reason these properties don't exist (perhaps in multiplayer?)
// or the player doesn't have mana, return. Note that I added a new method
// 'getMaxMana()' to ExtendedPlayer for this purpose
if (props == null || props.getMaxMana() == 0)
{
return;
}
// Starting position for the mana bar - 2 pixels from the top left corner.
int xPos = 2;
int yPos = 2;
// The center of the screen can be gotten like this during this event:
// int xPos = event.resolution.getScaledWidth() / 2;
// int yPos = event.resolution.getScaledHeight() / 2;
// Be sure to offset based on your texture size or your texture will not be truly centered:
// int xPos = (event.resolution.getScaledWidth() + textureWidth) / 2;
// int yPos = (event.resolution.getScaledHeight() + textureHeight) / 2;
// setting all color values to 1.0F will render the texture as it looks in your texture file
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
// Somewhere in Minecraft vanilla code it says to do this because of a lighting bug
GL11.glDisable(GL11.GL_LIGHTING);
// Bind your texture to the render engine
this.mc.getTextureManager().bindTexture(texturepath);
/*
The parameters for drawTexturedModalRect are as follows:
drawTexturedModalRect(int x, int y, int u, int v, int width, int height);
x and y are the on-screen position at which to render.
u and v are the coordinates of the most upper-left pixel in your texture file from which to start drawing.
width and height are how many pixels to render from the start point (u, v)
*/
// First draw the background layer. In my texture file, it starts at the upper-
// left corner (x=0, y=0), ends at 50 pixels (so it's 51 pixels long) and is 4 pixels thick (y value)
this.drawTexturedModalRect(xPos, yPos, 0, 0, 51, 4);
// Then draw the foreground; it's located just below the background in my
// texture file, so it starts at x=0, y=4, is only 2 pixels thick and 49 length
// Why y=4 and not y=5? Y starts at 0, so 0,1,2,3 = 4 pixels for the background
// However, we want the length to be based on current mana, so we need a new variable:
int manabarwidth = (int)(((float) props.getCurrentMana() / props.getMaxMana()) * 49));
System.out.println("[GUI MANA] Current mana bar width: " + manabarwidth);
// Now we can draw our mana bar at yPos+1 so it centers in the background:
this.drawTexturedModalRect(xPos, yPos + 1, 0, 4, manabarwidth, 2);
}
}
If you want to make your mana bar look really sweet with some transparency like the hotbar slots, then you'll have to add a little extra code and make sure your texture is set up correctly. An easy way to do that is to open the minecraft jar in 7-zip and extract the texture containing the hotbar (called 'widgets'), then copy the transparent part of the texture into your file.
Note for the following code, you'll need to use 'mana_bar2.png' instead.
// Add this block of code before you draw the section of your texture containing transparency
GL11.glEnable(GL11.GL_BLEND);
GL11.glDisable(GL11.GL_DEPTH_TEST);
GL11.glDepthMask(false);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
GL11.glDisable(GL11.GL_ALPHA_TEST);
// Here we draw the background bar which contains a transparent section; note the new size
drawTexturedModalRect(xPos, yPos, 0, 0, 56, 9);
// You can keep drawing without changing anything
int manabarwidth = (int)(((float) props.getCurrentMana() / props.getMaxMana()) * 49);
drawTexturedModalRect(xPos + 3, yPos + 3, 0, 9, manabarwidth, 3);
// NOTE: be sure to reset the openGL settings after you're done or your character model will be messed up
GL11.glDisable(GL11.GL_BLEND);
GL11.glEnable(GL11.GL_DEPTH_TEST);
GL11.glDepthMask(true);
Now when you use up your mana, the background bar will be semi-transparent. Cool! Thanks to Draco18s and Glenn over at Minecraftforge forums for this piece of code.
You will need to add this code to your main mod class postInit method in order to register your new GuiManaBar overlay as an active event (just like registering your EventHandler), otherwise nothing will appear. Recall that the Gui is client side only, so you will get an error if you try to register it on both sides. The easiest solution is to register it in your ClientProxy; for this tutorial, I'll place it in the ClientProxy's registerRenderers() method.
public void registerRenderers() {
MinecraftForge.EVENT_BUS.register(new GuiManaBar(Minecraft.getMinecraft()));
}
Alright, try it out. There should be a horizontal mana bar in the upper-left corner of your screen. Now use our ItemUseMana once or twice. Notice the mana bar doesn't update.
What's going on?
Well, currently only the server knows how much mana the player has. We never told the client that the player even has mana, let alone how much! For many purposes, this is fine. However, since a Gui is only rendered client side, this is a case where we will need to use packets to synchronize the server/client.
NOTE: 1.7.2 users, please see the section at the top of the post about updating to 1.7.2 - the following packet information is for 1.6.4!!!
Ok, moving on.
Make a packet handler class like in the above tutorial:
public class TutorialPacketHandler implements IPacketHandler
{
// Don't need to do anything here.
public TutorialPacketHandler() {}
@Override
public void onPacketData(INetworkManager manager, Packet250CustomPayload packet, Player player)
{
// This is a good place to parse through channels if you have multiple channels
if (packet.channel.equals("tutchannel")) {
handleExtendedProperties(packet, player);
}
}
// Making different methods to handle each channel helps keep things tidy:
private void handleExtendedProperties(Packet250CustomPayload packet, Player player)
{
DataInputStream inputStream = new DataInputStream(new ByteArrayInputStream(packet.data));
ExtendedPlayer props = ExtendedPlayer.get((EntityPlayer) player);
// Everything we read here should match EXACTLY the order in which we wrote it
// to the output stream in our ExtendedPlayer sync() method.
try {
props.setMaxMana(inputStream.readInt());
props.setCurrentMana(inputStream.readInt());
} catch (IOException e) {
e.printStackTrace();
return;
}
// Just so you can see in the console that it's working:
System.out.println("[PACKET] Mana from packet: " + props.getCurrentMana() + "/" + props.getMaxMana());
}
}
Then modify the following line in your main mod class to the below:
That's it for setting up the Packet Handler framework, now we'll set up a method to send packets from within our ExtendedPlayer class. I like to give this method the same name in all of my IExtendedEntityProperties classes, just to make it easy on myself.
/**
* Sends a packet to the client containing information stored on the server
* for ExtendedPlayer
*/
public final void sync()
{
ByteArrayOutputStream bos = new ByteArrayOutputStream(8);
DataOutputStream outputStream = new DataOutputStream(bos);
// We'll write max mana first so when we set current mana client
// side, it doesn't get set to 0 (see methods below)
try {
outputStream.writeInt(this.maxMana);
outputStream.writeInt(this.currentMana);
} catch (Exception ex) {
ex.printStackTrace();
}
Packet250CustomPayload packet = new Packet250CustomPayload("tutchannel", bos.toByteArray());
// We only want to send from the server to the client
if (!player.worldObj.isRemote) {
EntityPlayerMP player1 = (EntityPlayerMP) player;
PacketDispatcher.sendPacketToPlayer(packet, (Player) player1);
}
}
Okay, well that will send a packet whenever we call it. Problem is, we don't call it anywhere yet. You could do it in onLivingUpdate or some such, but that would unnecessarily spam packets which would be no good. We only want to call it when any of the information stored in ExtendedPlayer changes, in our case, when current or max mana is modified.
Here you can see my implementations for setCurrentMana and setMaxMana:
/**
* Sets current mana to amount or maxMana, whichever is lesser
*/
public void setCurrentMana(int amount)
{
this.currentMana = (amount < this.maxMana ? amount : this.maxMana);
this.sync();
}
/**
* Sets max mana to amount or 0 if amount is less than 0
*/
public void setMaxMana(int amount)
{
this.maxMana = (amount > 0 ? amount : 0);
this.sync();
}
Note that we add a call to sync() our ExtendedProperties in each of these methods because they changed our stored variables. We also need to sync the properties in any other methods that do so, like consumeMana and replenishMana.
Do note, however, that it is not good practice to synchronize everything all the time; if, for example, you have lots of different things that change often in your extended properties such as mana, experience, levels, etc., it is better to have separate packets for each, so that you can update your mana without having to update everything else at the same time. Much easier on the network this way, but for the time being, we will just stick with a single packet.
Another time we need to sync properties is after the entity is loaded from NBT, as that is only done server side and we want the information for our GuiManaBar. Because we can't send and receive packets before everything is loaded, we can't do it from within the readFromNBT method. Guess where we can do it from? That's right, our EventHandler!
Add this to your EventHandler's onEntityJoinWorldEvent method, as this event occurs after everything (the world, entities, etc) is loaded but before anything really happens in the game. As a bonus, it's only called once per entity, so you're not spamming packets.
@ForgeSubscribe
public void onEntityJoinWorld(EntityJoinWorldEvent event)
{
//Only need to synchronize when the world is remote (i.e. we're on the server side)
// and only for player entities, as that's what we need for the GuiManaBar
if (!event.entity.worldObj.isRemote && event.entity instanceof EntityPlayer)
ExtendedPlayer.get((EntityPlayer) event.entity).sync();
}
Anyways, that's a lot of work just to get a little mana bar, but it should all be working
correctly now. Fire it up and try for yourself!
Step 3.4: Using DataWatcher to Synchronize
We saw in section 3.3 how to synchronize data between the server and the client using packets, but Minecraft has a built-in functionality specifically to keep certain kinds of variables synchronized between server and client that is highly suited to our needs. It's called 'DataWatcher'. Please head over to the Minecraft Forge Wiki and read this tutorial first, then come back.
Okay, simple enough. DataWatcher is best used for things that are constantly fluctuating, such as health, hunger, or, in our case, current mana. Using DataWatcher instead of packets has the advantage of... we don't have to use packets! But only for the variable we watch. Everything else will still need to use packets, but now we can send packets much less frequently.
To use DataWatcher, first we need to add a new watchable object to the player. We can do this in the constructor of our ExtendedPlayer properties. First, I will define the index to use, in case we need to change it later due to unforseen conflicts with other DataWatcher objects (if you read the earlier tutorial, you'll know there are only 32 available slots, so this is an important step to save yourself hassle later).
public static final int MANA_WATCHER = 20;
public ExtendedPlayer(EntityPlayer player)
{
this.player = player;
this.maxMana = 50;
// This adds the new object at our defined index and sets the value to max mana,
// since we should have full mana when first constructing
this.player.getDataWatcher().addObject(MANA_WATCHER, this.maxMana);
}
Notice that we no longer set the variable 'currentMana'? That's because it is now stored in DataWatcher, so we no longer need it. Delete that variable from the class. Errors will show up - these are all the places we need to change.
First we'll fix NBT saving and loading.
// To save, we simply replace 'currentMana' with getWatchableObjectInt
properties.setInteger("CurrentMana", this.player.getDataWatcher().getWatchableObjectInt(MANA_WATCHER));
// To load, we need to use 'updateObject' NOT 'addObject', as the object is already added.
// If you try to add it again, you will get an error
this.player.getDataWatcher().updateObject(MANA_WATCHER, properties.getInteger("CurrentMana"));
Next we need to fix all the methods that deal with changing currentMana:
// This method gets a little messier, unfortunately, due to the unwieldy length of getting information
// from DataWatcher vs. referencing a local variable, so we'll create a local variable instead
public final boolean consumeMana(int amount)
{
// This variable makes it easier to write the rest of the method
int mana = this.player.getDataWatcher().getWatchableObjectInt(MANA_WATCHER);
// These two lines are the same as before
boolean sufficient = amount <= mana;
mana -= (amount < mana ? amount : mana);
// Update the data watcher object with the new value
this.player.getDataWatcher().updateObject(MANA_WATCHER, mana);
// note that we no longer need to call 'sync()' to update the client
return sufficient;
}
// This method cleans up nicely - no more call to 'sync()' means no custom packet to handle!
public final void replenishMana()
{
this.player.getDataWatcher().updateObject(MANA_WATCHER, this.maxMana);
}
// Simple change
public final int getCurrentMana()
{
return this.player.getDataWatcher().getWatchableObjectInt(MANA_WATCHER);
}
// Again, use 'updateObject'; we don't need to 'sync()' here anymore
public final void setCurrentMana(int amount)
{
this.player.getDataWatcher().updateObject(MANA_WATCHER, (amount < this.maxMana ? amount : this.maxMana));
}
Now remove 'currentMana' from your sync() method and packet handler, but keep in mind you still need to synchronize 'maxMana' with a packet, so you'll need to keep the sync() method. Note that the packet size will now be '4' if you've followed along exactly.
Why not use DataWatcher for maxMana too? Well you could, but since it rarely changes, it would be a waste of such a limited resource.
That's pretty much it. Now current mana will stay in sync for the gui display automatically and you'll send far fewer packets. If you ever run into a conflict with another watchable object using the same index '20', you only need to change the value of MANA_WATCHER to update every instance in your code.
Step 4.1: Adding another kind of Extended Properties
Now we're going to add a variable to all EntityLivingBase entities in addition to the ones we added above for EntityPlayer. We only want players to have mana, but we want every creature under the sun to have riches for us to plunder!
Well, guess what it will be named? That's right, ExtendedLivingBase! this class, as it will be almost exactly like the one we just made, but with one difference: we're going to use the init() method so we can use the Random from World object to randomize the amount of gold each entitylivingbase has.
public class ExtendedLivingBase implements IExtendedEntityProperties
{
public final static String EXT_PROP_NAME = "ExtendedLivingBase";
private final EntityLivingBase entity;
private int gold;
public ExtendedLivingBase(EntityLivingBase entity)
{
this.entity = entity;
}
/**
* Used to register these extended properties for the entity during EntityConstructing event
* This method is for convenience only; it will make your code look nicer
*/
public static final void register(EntityLivingBase entity)
{
entity.registerExtendedProperties(ExtendedLiving.EXT_PROP_NAME, new ExtendedLiving(entity));
}
/**
* Returns ExtendedLiving properties for entity
* This method is for convenience only; it will make your code look nicer
*/
public static final ExtendedLiving get(EntityLivingBase entity)
{
return (ExtendedLiving) entity.getExtendedProperties(EXT_PROP_NAME);
}
@Override
public void saveNBTData(NBTTagCompound compound)
{
compound.setInteger("Gold", this.gold);
}
@Override
public void loadNBTData(NBTTagCompound compound)
{
this.gold = compound.getInteger("Gold");
System.out.println("[LIVING BASE] Gold from NBT: " + this.gold);
}
@Override
public void init(Entity entity, World world)
{
// Gives a random amount of gold between 0 and 15
this.gold = world.rand.nextInt(16);
System.out.println("[LIVING BASE] Gold: " + this.gold);
}
}
Be sure to register it in your EventHandler! onEntityConstructing should now look like this:
@ForgeSubscribe
public void onEntityConstructing(EntityConstructing event)
{
// From last time:
if (event.entity instanceof EntityPlayer && ExtendedPlayer.get((EntityPlayer) event.entity) == null)
ExtendedPlayer.register((EntityPlayer) event.entity);
/* New stuff:
Be sure not to use 'else if' here. A player, for example, is both an EntityPlayer
AND an EntityLivingBase, so should have both extended properties
*/
if (event.entity instanceof EntityLivingBase)
/*
Just like above but we change 'ExtendedPlayer' to 'ExtendedLivingBase'
and cast event.entity to EntityLivingBase.
Isn't it nice to use the constant variable EXT_PROP_NAME in all
of our IExtendedEntityProperty classes? So easy to remember and it
stores a different name for each class. Nice.
*/
ExtendedLiving.register((EntityLivingBase) event.entity);
/* Old, cumbersome method:
event.entity.registerExtendedProperties(ExtendedLivingBase.EXT_PROP_NAME,
new ExtendedLivingBase((EntityLivingBase) event.entity));
*/
// Remember, this will also call the init() method automatically
}
Finished! Go ahead and give it a try, see how much gold everyone is getting!
Step 4.2: Wait, what the heck?!
What's with all these errors, you ask? Well, to be honest, I'm not really sure. Something to do with the way the init() method is called leads to the ExtendedProperties being null somehow. No idea.
Good news is, I know how to get around it. Move everything that seems like it should go in init() to your EventHandler onEntityJoinWorldEvent. Be sure to leave the init method totally empty.
@ForgeSubscribe
public void onEntityJoinWorld(EntityJoinWorldEvent event)
{
if (event.entity instanceof EntityLivingBase)
{
ExtendedLiving props = ExtendedLiving.get((EntityLivingBase) event.entity);
// Gives a random amount of gold between 0 and 15
props.addGold(event.entity.worldObj.rand.nextInt(16));
System.out.println("[LIVING BASE] Gold: " + props.getGold());
}
}
Be sure to create the 'addGold(int)' and 'getGold()' methods in ExtendedLivingBase.
Now try it. Yay! It works!
In conjunction with a custom ItemGoldCoin, LivingDropsEvent and EntityItemPickupEvent, I'm sure you can see how this could be used to store large amounts of coin without clogging up the inventory.
Step 5: Getting your custom data to persist through player death
First, many thanks and godly praise upon the legend that is Mithion, creator of IExtendedEntityProperties.
Without him, not only this particular section, but all of this stuff would not be possible. Truly amazing work
by him that allows us to do so much with ease.
Just had to be said. Anyways, as some have noticed, if you die and respawn, the data you so carefully created, registered, sent in packets and saved to NBT is RESET to the initial values when the player dies. This has to do with the way player NBT is stored and retrieved during death, and there's nothing we can do about it. At least, nothing directly.
We need to find a way to store the IExtendedEntityProperties data outside of the player when the player dies, and retrieve it when the player respawns. I knew this, but I never would have thought of where to store it without Mithion's assistance. Though I guess it could be stored anywhere that persists...
Also thanks to Seigneur_Necron for some excellent pointers on good coding practice. This helped clean
up aspects of the code and improve the overall quality. Merci!
Enough blabbing. The most convenient way to store extended properties is bundled as NBT, and we'll be storing it in a HashMap with the player's username as the key. We'll store this map in our CommonProxy class.
public class CommonProxy implements IGuiHandler
{
/** Used to store IExtendedEntityProperties data temporarily between player death and respawn */
private static final Map<String, NBTTagCompound> extendedEntityData = new HashMap<String, NBTTagCompound>();
public void registerRenderers() {}
@Override
public Object getServerGuiElement(int guiId, EntityPlayer player, World world, int x, int y, int z)
{
return null;
}
@Override
public Object getClientGuiElement(int guiId, EntityPlayer player, World world, int x, int y, int z)
{
return null;
}
/**
* Adds an entity's custom data to the map for temporary storage
* @param compound An NBT Tag Compound that stores the IExtendedEntityProperties data only
*/
public static void storeEntityData(String name, NBTTagCompound compound)
{
extendedEntityData.put(name, compound);
}
/**
* Removes the compound from the map and returns the NBT tag stored for name or null if none exists
*/
public static NBTTagCompound getEntityData(String name)
{
return extendedEntityData.remove(name);
}
}
Ok, now we have the framework up that will store our data externally to the player's NBT, which we can access from our EventHandler. Now all we need to do is store the data when the player dies and retrieve it when the player re-joins the world.
We don't use LivingSpawnEvent because that event is not triggered during the process of dying and being respawned, as much as you would think otherwise.
// These are methods in the EventHandler class, in case you don't know that by now
// we need to add this new event - it is called for every living entity upon death
@ForgeSubscribe
public void onLivingDeathEvent(LivingDeathEvent event)
{
// we only want to save data for players (most likely, anyway)
if (!event.entity.worldObj.isRemote && event.entity instanceof EntityPlayer)
{
// NOTE: See step 6 for a way to do this all in one line!!!
// create a new NBT Tag Compound to store the IExtendedEntityProperties data
NBTTagCompound playerData = new NBTTagCompound();
// write the data to the new compound
((ExtendedPlayer)(event.entity.getExtendedProperties(ExtendedPlayer.EXT_PROP_NAME))).saveNBTData(playerData);
// and store it in our proxy
proxy.storeEntityData(((EntityPlayer) event.entity).username, playerData);
// call our handy static one-liner to save custom data to the proxy
ExtendedPlayer.saveProxyData((EntityPlayer) event.entity);
}
}
// we already have this event, but we need to modify it some
@ForgeSubscribe
public void onEntityJoinWorld(EntityJoinWorldEvent event)
{
if (!event.entity.worldObj.isRemote && event.entity instanceof EntityPlayer)
{
// NOTE: See step 6 for a way to do this all in one line!!!
// before syncing the properties, we must first check if the player has some saved in the proxy
// recall that 'getEntityData' also removes it from the map, so be sure to store it locally
NBTTagCompound playerData = proxy.getEntityData(((EntityPlayer) event.entity).username);
// make sure the compound isn't null
if (playerData != null) {
// then load the data back into the player's IExtendedEntityProperties
((ExtendedPlayer)(event.entity.getExtendedProperties(ExtendedPlayer.EXT_PROP_NAME))).loadNBTData(playerData);
}
// finally, we sync the data between server and client (we did this earlier in 3.3)
((ExtendedPlayer)(event.entity.getExtendedProperties(ExtendedPlayer.EXT_PROP_NAME))).syncExtendedProperties();
}
}
But wait, didn't we add TWO extended properties? How can a single entity store two different properties?
Easy, append the properties name to the username when storing and retrieving. That way, each property is stored with its own unique name and the player it belongs to.
// save player data:
proxy.storeEntityData(((EntityPlayer) event.entity).username + ExtendedPlayer.EXT_PROP_NAME, playerData);
// save living data:
proxy.storeEntityData(((EntityPlayer) event.entity).username + ExtendedLiving.EXT_PROP_NAME, playerData);
// load player data:
NBTTagCompound playerData = proxy.getEntityData(((EntityPlayer) event.entity).username + ExtendedPlayer.EXT_PROP_NAME);
// load living data
NBTTagCompound playerData = proxy.getEntityData(((EntityPlayer) event.entity).username + ExtendedLiving.EXT_PROP_NAME);
Note that this is still saving data ONLY for player entities, but a player with multiple types of Extended Properties.
This will also allow inter-mod compatibility if, for example, two mods both add custom data to the player using IExtendedEntityProperties AND you've given your properties a unique name (NOT something like "ExtendedPlayer", which I used in the tutorial).
Step 6: Improvements to the code, courtesy of Seigneur_Necron
Some of these I incorporated into the above tutorial, but there are more that I didn't. Here are some really
great tips from Seigneur_Necron.
6.1: Create a simple method that returns your ExtendedProperties
public static ExtendedPlayer get(EntityPlayer player)
{
return (ExtendedPlayer) player.getExtendedProperties(EXT_PROP_NAME);
}
Here's how it looks in use. I know which one I prefer
// Seigneur_Necron's elegance:
ExtendedPlayer props = ExtendedPlayer.get(player);
// vs my original super-cumbersome method:
ExtendedPlayer props = (ExtendedPlayer) event.entity.getExtendedProperties(ExtendedPlayer.EXT_PROP_NAME);
6.2: Create methods to save / load ExtendedProperty data to your CommonProxy
/**
* Makes it look nicer in the methods save/loadProxyData
*/
private static String getSaveKey(EntityPlayer player) {
return player.username + ":" + EXT_PROP_NAME;
}
/**
* Does everything I did in onLivingDeathEvent and it's static,
* so you now only need to use the following in the above event:
* ExtendedPlayer.saveProxyData((EntityPlayer) event.entity));
*/
public static void saveProxyData(EntityPlayer player) {
ExtendedPlayer playerData = ExtendedPlayer.get(player);
NBTTagCompound savedData = new NBTTagCompound();
playerData.saveNBTData(savedData);
// Note that we made the CommonProxy method storeEntityData static,
// so now we don't need an instance of CommonProxy to use it! Great!
CommonProxy.storeEntityData(getSaveKey(player), savedData);
}
/**
* This cleans up the onEntityJoinWorld event by replacing most of the code
* with a single line: ExtendedPlayer.loadProxyData((EntityPlayer) event.entity));
*/
public static void loadProxyData(EntityPlayer player) {
ExtendedPlayer playerData = ExtendedPlayer.get(player);
NBTTagCompound savedData = CommonProxy.getEntityData(getSaveKey(player));
if(savedData != null) {
playerData.loadNBTData(savedData);
}
// note we renamed 'syncExtendedProperties' to 'syncProperties' because yay, it's shorter
playerData.syncProperties();
}
6.3: Create a 'register' method to clean up EntityConstructing code:
/**
* Used to register these extended properties for the player during EntityConstructing event
*/
// Note that it's static, so we can call it like this in the event:
// ExtendedPlayer.register((EntityPlayer) event.entity);
public static void register(EntityPlayer player)
{
player.registerExtendedProperties(ExtendedPlayer.EXT_PROP_NAME, new ExtendedPlayer(player));
}
6.4: Fancy packet handling stuff that is a bit beyond me, but I'll put it here for completeness.
Thanks again to Seigneur_Necron for these great tips!
And that's how it's done, folks. Happy modding.
FINAL WORDS:
I was thinking, if you are making more than one class that implements IExtendedEntityProperties, it might behoove you to create your own interface extending IExtendedEntityProperties so that all of your classes' methods are guaranteed to be consistent.
For example, it might look something like this:
public interface ICustomProperties extends IExtendedEntityProperties
{
public void register(EntityPlayer player);
public <T extends IExtendedEntityProperties> T get(EntityPlayer player);
public String getSaveKey(EntityPlayer player);
public void saveProxyData(EntityPlayer player);
public void loadProxyData(EntityPlayer player);
public void syncProperties();
}
Perhaps change 'EntityPlayer' to a generic of Entity to make the interface more widely applicable.
Only thing is, that does away with our ability to call these without an instance of the class, as they are no longer static. I'm just learning myself, so there may be a way around that limitation. Please share if you know
EDIT: Ha, of course right after I posted this Seigneur_Necron added his code which does just this AND still lets you use the static methods because the interface is hidden in the parent class, and the subclass uses static methods to access it. Genius. I can't wait for his full code to be available, but in the meantime, check out what he's posted here.
Don't forget to up the green arrow on any posts that help you!
Will this all work in 1.5.2? I heard there was almost no change in code from 1.5.2 to 1.6.2. Is that true?
I'm afraid I don't know - I've never used 1.5.2... but there is one way to find out - try it! I'll see about putting my tutorials all on one page, too. Seems like a solid idea
I'm thanking you for not only creating the IExtended tutorial but also for linking to the packet tutorial
Thank you for writing tutorials on non-covered topics and for linking to pre-written guides on other material!
:)I'm adding the event and iextended properties tutorials to my tutorial database as we speak
I'm thanking you for not only creating the IExtended tutorial but also for linking to the packet tutorial
Thank you for writing tutorials on non-covered topics and for linking to pre-written guides on other material!
I'm adding the event and iextended properties tutorials to my tutorial database as we speak
Just doing what I can to thank everyone for making all those great guides already out there No sense re-inventing the wheel!
Thank you thank you thank you soooooo much That was exactly the tutorial I wanted, and everything in it made sense! You sir, are amazing.
Do you know how to render it in a gui in the game, like the experience bar? I will be forever in your debt if you can do that
Wow, I'm glad to have helped!
Check out this tutorial - it covers making a gui like you're describing, but using current potion effects. Just swap out the potion effects and use your variables instead.
As an example, I just added the following code right below where the potion effects are rendered in the GuiBuffBar:
private static final int SCROLL_ICON_SIZE = 28, SCROLL_ICONS_PER_ROW = 9;
private ItemStack lastHeld;
private InventoryWand wand;
if (this.mc.thePlayer.getHeldItem() != null && this.mc.thePlayer.getHeldItem().getItem() instanceof ItemWand)
{
if (this.lastHeld != this.mc.thePlayer.getHeldItem())
{
wand = new InventoryWand(this.mc.thePlayer.getHeldItem());
this.lastHeld = this.mc.thePlayer.getHeldItem();
}
// set onscreen position
xPos = 2;
yPos = 26;
// setting all color values to 1.0F will render the texture as it looks in your texture file
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
GL11.glDisable(GL11.GL_LIGHTING);
// Draws spell scroll icon, if any
if (wand != null && wand.getStackInSlot(InventoryWand.ACTIVE_SLOT) != null)
{
ResourceLocation spellIcon = new ResourceLocation(ModInfo.ID, "textures/gui/containers/spellicons.png");
int iconIndex = ((ItemScroll) wand.getStackInSlot(InventoryWand.ACTIVE_SLOT).getItem()).getStatusIconIndex();
this.mc.func_110434_K().func_110577_a(spellIcon);
this.drawTexturedModalRect(xPos, yPos, iconIndex % SCROLL_ICONS_PER_ROW * SCROLL_ICON_SIZE,
iconIndex / SCROLL_ICONS_PER_ROW * SCROLL_ICON_SIZE, SCROLL_ICON_SIZE, SCROLL_ICON_SIZE);
}
}
So whenever the player has a wand equipped (which can hold several different spells), whichever spell is currently selected will render on the screen so the player knows what will be cast.
If you're looking to make a sort of mana bar or something, you'd just put two textures next to each other in your texture file, one for the background and one a full bar, then only render the full bar up to position x 'currentMana / maxMana.'
Let's look at the parameters for drawTexturedModalRec and you'll see what I mean:
drawTexturedModalRect(int x, int y, int u, int v, int width, int height);
x and y are the on-screen position at which to render.
u and v are the coordinates of the most upper-left pixel in your texture file from which to start drawing.
width and height are how many pixels to render from the start point (u, v)
So if you have a 256x256 Gui texture file, you could call:
drawTexturedModalRect(6, 12, 32, 56, 36, 18);
This would draw the 36x18 pixel-section starting at pixel position 32,56 in your texture file and render on screen at x=6, y = 12 (pretty much the upper left corner).
Let's say you define your MANA_BAR_LENGTH as a constant (36 in our case, but it doesn't matter), you would get the ratio to draw by using:
int manabarwidth = (int)(((float) props.getCurrentMana() / props.getMaxMana()) * MANA_BAR_LENGTH);
You have to cast to float to get a proper ratio between 0.0 and 1.0, multiply it by the max length of the mana bar, then recast to int for the draw method. Say we had half mana, 0.5F * 36 (our defined constant) = 18, which we get by substituting the variable 'manabarwidth' for '36' in the draw method:
Sorry that kind of dragged on... but that's the gist of it
I'll tell you what, I'll add this in as section 3.3 of the tutorial
EDIT: Sorry, I had the (float) cast incorrectly on the mana bar, I fixed it above, but here it is, just in case:
int manabarwidth = (int)(((float) props.getCurrentMana() / props.getMaxMana()) * MANA_BAR_LENGTH);
Alright, I added the lengthy section "Step 3.3: Using our new Extended Properties in a Gui Overlay"
It covers everything you'll need to get a functional mana bar displaying on screen, which just so happens to require sending packets! Yay, a bonus mini-tutorial
Alright, I added the lengthy section "Step 3.3: Using our new Extended Properties in a Gui Overlay"
It covers everything you'll need to get a functional mana bar displaying on screen, which just so happens to require sending packets! Yay, a bonus mini-tutorial
Thanks! I have seen that GUI Overlay tutorial before, but the bindTexture method isn't used anymore, and there were a few other problems. I'll look at your bonus mini-tutorial in a bit
Thanks! I have seen that GUI Overlay tutorial before, but the bindTexture method isn't used anymore, and there were a few other problems. I'll look at your bonus mini-tutorial in a bit
You can EASILY find the fix for bindTexture, just google "minecraft bindTexture 1.6" It's a few hundred posts about it.. ^^
Uploaded the gui texture files for the mana bar and another tutorial I did on creating an item that stores an inventory. They can be found on my github page here.
The textures aren't pretty, but might be useful for people having trouble with textures, or people like myself who are just too lazy to make textures when trying to get something to work for the first time
Weird - this was working perfectly, then after updating forge to the latest, the mana bar would calculate to length 0 if less than max mana. Updated the formula so it works now:
Hey, just wondering but is there a way to stop the variables you add from resetting when you die? I want the player to keep their max "mana", or it would be too hard to get...
Hey, just wondering but is there a way to stop the variables you add from resetting when you die? I want the player to keep their max "mana", or it would be too hard to get...
Oh, I haven't tried that yet I imagine you'd just have to reload (i.e. sync) the extended properties when the player respawns, as the data is all still there. You might have to re-register the properties, too. I'll have a look at it and put that in the tutorial - good point!
This is turning into an interesting problem. It appears that on death, the player's NBT compound is reset (at least as far as ForgeData is concerned, which is where our data is stored).
// This is from loading a game with saved EntityPlayer data:
[EVENT] Player's NBT to string: ForgeData:[]
// And this is after I died and respawned:
[EVENT] Player's NBT to string: :[]
I'm not sure how to tackle this, but I know it can be done as there are plenty of mods that do this. When I figure it out, I'll post it here and update the tutorial.
The solution might involve using 'player.addStat' instead of ExtendedEntityProperties, as those are consistent across deaths.
Updating to 1.8
That's it, but they need to be changed in EVERY file, which can be tedious, though if you're smart rather than hard-working, you could write a script.
(Don't forget to check out POST#2 for the tutorial on IExtendedEntityProperties!)
Preamble: If you're looking to have armor do something special every tick, there is a much easier way than using either Tick or Event Handlers. See my post here if that's what you're trying to do. It will be worth your time, trust me
Tutorial:
If you want to change any type of default Minecraft behavior, chances are there is a Forge Event that handles it.
There are Player Events, Living Events, Item Events, World Events, TerrainGenEvents, Minecart Events... there's just so much you can do with these it's incredible.
I personally prefer using EventHandler over TickHandler for this very reason - most things you could ever want to do already have a framework built to handle it, whereas Tick Handler you have to build it all yourself.
Creating and Using Your Event Handler:
Step 1: Create TutEventHandler class
Step 2: Register your event handler class to EVENT_BUS either in 'load' or 'postInit' methods in your main mod. Note that this step is the same in both 1.6.4 and 1.7.2.
Registering to the Correct BUS
1. MinecraftForge.EVENT_BUS: Most events get posted to this bus.
2. MinecraftForge.TERRAIN_GEN_BUS: Most world generation events happen here, such as Populate, Decorate, etc., with the strange exception that Pre and Post events are on the regular EVENT_BUS
3. MinecraftForge.ORE_GEN_BUS: Ore generation, obviously
4. FML Events: these become very important in 1.7.2, as this is where TickEvents and KeyInputEvents are posted, with TickHandler and KeyHandler no longer existing.
It is very important to register your event handler to the correct event bus, and only put those events that get posted to a certain event bus in a handler registered to that bus, or your event handling will fail.
Step 3: Add events to your event handler (an example)
Look through MinecraftForge event types for ones you want to use and add them to your EventHandler by creating a new method with the appropriate Event as a parameter.
1.6.4: It must be prefaced by "@ForgeSubscribe" so that it gets called automatically at the right times.
1.7.2: It must be prefaced by "@SubscribeEvent" so that it gets called automatically at the right times.
Do not, I repeat do NOT edit the event classes directly. Also, you do NOT need to make a class extending the Event class.
If you're ever curious what variables the Event stores, type ' event. ' in Eclipse and it will bring up a menu of all the methods and variables. Or go to the implementation by ctrl-clicking on the class name.
Step 4: Using Events in your custom classes
Forge Events are all hooked into automatically from vanilla code, but say you made a custom Bow and want it to use ArrowNock and ArrowLoose events? You need to post them to the event bus in your item code.
Step 5: Adding events to your EventHandler
Look through MinecraftForge event types for ones you want to use and add them to your EventHandler by creating a new method with the appropriate Event as a parameter.
1.6.4: It must be prefaced by "@ForgeSubscribe" so that it gets called automatically at the right times.
1.7.2: It must be prefaced by "@SubscribeEvent" so that it gets called automatically at the right times.
Do not, I repeat do NOT edit the event classes directly. Also, you do NOT need to make a class extending the Event class.
The following is a template for whatever event method you want to make. Name it whatever you want, but use the correct Event Type from above.
Event variables are accessed by using 'event.variableName' I give the variable names for many Events below.
A Sampling of Different Forge Events and Some Potential Uses
An easy way to check is to put a line of debugging code at the beginning of each event method, so long as that event has access to some kind of entity:
// IMPORTANT: The following were for 1.6.4; while many have not changed, some most certainly have.
// Always check the net.minecraftforge.event package for the available events, no matter what version
// of Minecraft you are modding for.
1. ArrowNockEvent
Variables: EntityPlayer player, ItemStack result
Usually called from 'onItemRightClick'.
Uses:It is cancelable, so if some conditions are not met (e.g. no arrows in inventory) you could cancel it and stop the player from setting the item in use. One thing I use it for is to set a boolean 'isAiming', which I can then interrupt if the player takes damage or some such (using another boolean 'wasInterrupted')
2. ArrowLooseEvent
Variables: EntityPlayer player, ItemStack bow, int charge
Usually called from 'onPlayerStoppedUsing'. It is also cancelable.
Uses: I use it in tandem with the above to check if the player was interrupted, and if so, cancel the event.
3. EntityConstructing
Variables: Entity entity
Called for every Entity when its constructor is called.
Uses: Useful if you need to add ExtendedEntityProperties.
4. EntityJoinWorldEvent
Variables: Entity entity, World world
Called when an entity joins the world for the first time.
Uses: Useful for synchronizing ExtendedEntityProperties, giving your player an item when spawned or any other number of things.
5. LivingUpdateEvent
Variables: EntityLivingBase entity
Called every tick at the beginning of the entity's onUpdate method.
Uses: This is probably the most useful Event. You can allow players to fly if holding an item or wearing your armor set, you can modify a player's fall speed here, add potion effects or anything else you can imagine. It's really really handy.
6. LivingDropsEvent
Variables: EntityLivingBase entity, DamageSource source, ArrayList drops, int lootingLevel, boolean recentlyHit, int specialDropValue
Called when an entity is killed and drops items.
Uses: Handy if you want to modify a vanilla mobs drops or only drop your custom item if it was killed from your custom DamageSource. You can also remove items from drops, adjust it based on the looting enchantment level of the item used to kill it, etc. Pretty useful.
7. LivingFallEvent
Variables: EntityLivingBase entity, float distance
Called when the entity hits the ground after a fall, but before damage is calculated.
SPECIAL NOTE: This event is NOT called while in Creative Mode; PlayerFlyableFallEvent is called instead
Uses: It is cancelable, so 'event.setCanceled(true)' will preclude further processing of the fall.
You can also modify the distance fallen here, but keep in mind this is ONLY on impact. If you want
to modify fall distance only while certain conditions are met, better to do it in LivingUpdateEvent.
Also, be sure you are modifying 'event.distance' and NOT 'entity.fallDistance' or you won't change the outcome of the fall
8. LivingJumpEvent
Variables: EntityLivingBase entity
Called whenever entity jumps.
Uses: Useful for entity.motionY += 10.0D. Just give it a try
9. LivingAttackEvent
Variables: EntityLivingBase entity, DamageSource source, float ammount
Called when an entity is attacked, but before any damage is applied
Uses: Cancelable. Here you can do pre-processing of an attack before LivingHurtEvent is called. The source entity of the attack is stored in DamageSource, and you can adjust the damage to be dealt however you see fit. Basically the same uses as LivingHurtEvent, but done sooner.
NOTE: This method is only called on the server side EXCEPT when an EntityPlayer is the source of the attack, in which case it is called on both sides.
10. LivingHurtEvent
Variables: EntityLivingBase entity, DamageSource source, float ammount
Called when an entity is damaged, but before any damage is applied
Uses: Another super useful one if you have custom armor that reduces fire damage, increases damage taken from magic, do something if ammount is greater than current health or whatever.
11. LivingDeathEvent
Variables: EntityLivingBase entity, DamageSource source
Called when an entity dies; cancelable!
Uses: Recall that DamageSource has lots of variables, too, such as getEntity() that returns the entity that caused the damage and thus that killed the current entity. All sorts of things you could do with that. This is also the place to cancel death and resurrect yourself, or set a timer for resurrection. If you have data you want to persist through player death, such as IExtendedEntityProperties, you can save that here as well.
12. EntityInteractEvent
Variables: EntityPlayer player, Entity target
Called when the player right-clicks on an entity, such as a cow
Uses: Gee, you could do anything with this. One use could be getting milk into your custom bucket...
13. EntityItemPickupEvent
Variables: EntityPlayer player, EntityItem item
Called when the player picks up an item
Uses: This one is useful for special items that need handling on pickup; an example would be if you made
something similar to experience orbs, mana orbs for instance, that replenish mana rather than adding an item to inventory
14. HarvestCheck
Variables: EntityPlayer player, Block block, boolean success
Called when the player breaks a block before the block releases its drops
Uses: Coupled with the BreakSpeed event, this is perhaps the best way to change the behavior of mining.
Advanced Information
Priority is the order in which all listeners listening to a posted event are called. A listener is a method with the @ForgeSubscribe annotation, and is said to be actively listening if the class containing the method was registered to the MinecraftForge EVENT_BUS. Whenever an event is posted matching the parameters of the listener, the listener's method is called.
When there are multiple listeners listening to a single event, the order in which they are called is important if one listener's functionality relies on having first or last access to the event in process, or if it relies on information set by a prior listener. This can be especially useful with Cancelable events.
A single Event can be handled multiple times by different handlers or even within the same handler provided the methods have different names:
Both methods will be called each time a LivingHurtEvent is posted to the EVENT_BUS in the order they are added to the event listener (see IEventListener), which in the case above is simply their order in the code. The order can be controlled by appending (priority=VALUE) to the @ForgeSubscribe annotation, where VALUE is defined in the EventPriority enum class. HIGHEST priority is always called first, while LOWEST priority is called last.
If two listeners have the same priority level, then the order is again controlled by the order in which they are added. In order to control the flow for such a case within a mod, the methods can be placed in separate 'event handler' classes to be registered in the order desired:
For multiple Mods that affect the same events, the order of mod registration would have the same effect.
Cancelable Events
By controlling the order in which each listener method is called, it is usually possible to avoid un-canceling a previously canceled event, although exceptional circumstances do arise; in those cases, extra care must be taken to avoid making logical errors.
More will be added as I learn. Thanks to GotoLink for his excellent explanations regarding priority.
1.7.2 TickEvents and Creating a TickHandler
Chances are, you do NOT need to create a tick handler for whatever it is you are doing. There are many methods built-in to Minecraft that already act as tick handlers, and it is ALWAYS better to use them when you can. Why? Because they tick only when the object in question actually exists, whereas a generic tick handler processes every tick no matter what. Here are some of the pre-made tickers at your disposal:
Entity#onUpdate: called every tick for each Entity; to manipulate vanilla entities, use LivingUpdateEvent
TileEntity#onUpdate: called for tile entities every tick unless you tell it not to tick
Item#onUpdate: called every tick while the specific item is in a player's inventory
Item#onArmorTick: called only for armor each tick that it is equipped
Block#updateTick: may be called randomly based on the block's tick rate, or it may be scheduled
As you can see, nearly everything you could ever want to tick already has that capability. If what you want to do simply cannot be handled using one of the built-in tick methods, then and ONLY THEN should you consider creating a tick handler. Here's how to do so.
In 1.7.2, as I'm sure many of you have noticed, the TickHandler class is gone, replaced by what appears to be a single TickEvent that we must now subscribe to in order to achieve the same functionality. Below, I will break it down and explain what exactly is going on and how to use it effectively.
Step 1: Determine Which TickEvent to Use
ServerTickEvent: called on the server side only
ClientTickEvent: called on the client side only
WorldTickEvent: both sides
PlayerTickEvent: both sides
RenderTickEvent: client side only and called each render tick
When creating a TickHandler, be sure NOT to subscribe to the generic "TickEvent", as that will listen to every single tick type on every single tick, which is just wasting processing time.
Once you decide which tick you need, then it is time to create a new TickHandler class.
If you need to do different things at the beginning of the tick than at the end of the tick, you can check the tick event's phase in your onWhateverTick method; this is applicable for all TickEvents.
One other thing to mention here is that since we are on the client side, we might be using Minecraft.getMinecraft() quite a lot to access things like the world, player, etc. Since it would be very wasteful to have to do this each and every tick, a better way is to store the instance of Minecraft when you construct your tick handler:
Good luck with 1.7.2!
Be sure to check out my other tutorials as well:
- Modding With APIs
- EventHandler and IExtendedEntityProperties
- Custom Inventories in Items and Players
- Multi-Input/Output Furnace
- Rendering Your Custom Item Texture
- Overriding shift-click in custom Containers
- Using Potions in Crafting Recipes
- Enchanted Books and Crafting Recipes
And here are some other useful tutorials:
- DataWatcher: another way to keep data synchronized between client/server
- Adding Custom Sounds, Fluids, etc. by Mazetar: http://www.minecraft...undpool-190713/
- See Mazetar's List for the best tutorials from around the web!
Lastly, don't forget to up the green arrow on any posts that help you!
In this tutorial I will cover how to add variables to an entity by using Forge's IExtendedEntityProperties. How did I learn all this stuff, you ask? Well, it's all pretty well documented within the Forge code itself. Hover your mouse over most Forge methods and you'll get a great 'tooltip' pop up that explains pretty much everything about it. Or just open up the class you're curious about.
Go ahead, give it a try! If it still doesn't make sense, then read on
We will add mana to all players, show how to use it, and add gold coins to all EntityLivingBase entities.
We will also use our custom data in a Gui which involves setting up a packet handler to send information to the client.
Finally, we will make it so our custom data persists even when the player dies and respawns.
If you read all the way through and follow all of the steps correctly, your custom properties will also be multi-player compatible straight out of the box, so to speak. Tested using the Eclipse server.
Prerequisites:
1. Know how to set up and use Forge Events. See my tutorial on creating an EventHandler.
2. Willingness to read carefully.
NOTES: Updating from 1.6.4 to 1.7.2
First, be sure you have read and understood how to set up and use IExtendedEntityProperties in 1.6.4, or these notes will not help you very much. This is simply how to update existing code to the new 1.7.2 setup.
Updating IExtendedEntityProperties and the EventHandler
Not much has changed here:
For the EventHandler, pretty much all you have to do is replace @ForgeSubscribe with @SubscribeEvent and fix your imports. Everything else should work the same as before. Easy.
Updating the Network
First, go check out the wiki tutorial: http://www.minecraft...Packet_Handling
Once you have copied and pasted that code (seriously, it's that simple) into your project, you will need to make a new package for your packets and register them with the PacketPipeline. I do it like so:
That's really all you have to do. Then you just make the packet classes themselves.
OpenGuiPacket
SyncPlayerPropsPacket
Do note, however, that it is not good practice to synchronize everything all the time; if, for example, you have lots of different things that change often in your extended properties such as mana, experience, levels, etc., it is better to have separate packets for each, so that you can update your mana without having to update everything else at the same time. Much easier on the network this way, but for the time being, we will just stick with a single packet.
GuiManaBar
In the Gui, fontRenderer is now fontRendererObj, and you need to change to I18n.format if you were using that to translate the names at all. Use the updated IInventory method names for getInventoryName(), etc
Updating the Item class
Everything is pretty much exactly the same, with a few minor differences:
1. Remove the int parameter from the constructor; item IDs are history
2. Change 'Icon' to 'IIcon' (two I's)
3. Change 'IconRegister' to 'IIconRegister' (two I's)
4. Update imports
NOTES: Updating from Forge 804 to 871/883
Three things you'll need to change in your GUI files:
1. I18n.func_135053_a() is now I18n.getString()
2. mc.func_110434_K() is now mc.renderEngine OR mc.getTextureManager()
3. renderEngine.func_110577_a() is now renderEngine.bindTexture()
IMPORTANT NOTE [1.6.4 only]
If you need to display any of this information, such as in a Gui, you need to send a packet to the client with the data to display. This is the only kind of information the client needs to know. For a more complete explanation, see this post (#46).
Thanks to Pawaox for bringing this to my attention, and Seigneur_Necron for confirming the requirements.
Step 1: Create a class that implements IExtendedEntityProperties
Since we are first making variables specific to EntityPlayer, we will call this class "ExtendedPlayer" so it's always obvious what kind of entity can use it. This will be important if you add different variables to different entities.
In order to access our newly created extended player properties, we need to register them for every instance of EntityPlayer. That just means we're creating a new instance of the class so we can access it.
Registering of IExtendedEntityProperties is all done in the EntityConstructing event.
That's it! All players will now start with a pool of mana, so we just have to do
something with it.
For the sake of demonstration, we'll make a very basic item called... wait for it...
'ItemUseMana'. I should be naming mountains with this kind of stuff.
Anyways, here's our new ItemUseMana class. Since you probably know all about items already, I will only discuss things related to IExtendedEntityProperties.
Try it out and check the console. Hooray! It should have worked. If not, double-check that you have registered your EventHandler in your main mod load method.
For the sake of convenience, here is the code:
Events are another really great place to use additional properties. Since we only added mana, we will pretend that it is in fact a spell that prevents damage from falling up to a certain distance.
In order to do this, we'll need to add a getCurrentMana method to our ExtendedPlayer class. It just returns 'this.currentMana'
Next, we add a LivingFallEvent to our EventHandler:
By request, here is a guide that will get you a functional mana bar on your screen. It's a bit lengthy, so bear with me.
Here we will create a mana bar display in the upper-left corner of the screen using currentMana and maxMana from our ExtendedPlayer class.
The first part of this section is from http://www.minecraft...iki/Gui_Overlay I highly recommend you read that tutorial before continuing on, as it contains lots of great information related to this topic.
This is our GuiManaBar class:
If you want to make your mana bar look really sweet with some transparency like the hotbar slots, then you'll have to add a little extra code and make sure your texture is set up correctly. An easy way to do that is to open the minecraft jar in 7-zip and extract the texture containing the hotbar (called 'widgets'), then copy the transparent part of the texture into your file.
Note for the following code, you'll need to use 'mana_bar2.png' instead.
Now when you use up your mana, the background bar will be semi-transparent. Cool! Thanks to Draco18s and Glenn over at Minecraftforge forums for this piece of code.
Alright, try it out. There should be a horizontal mana bar in the upper-left corner of your screen. Now use our ItemUseMana once or twice. Notice the mana bar doesn't update.
What's going on?
Well, currently only the server knows how much mana the player has. We never told the client that the player even has mana, let alone how much! For many purposes, this is fine. However, since a Gui is only rendered client side, this is a case where we will need to use packets to synchronize the server/client.
NOTE: 1.7.2 users, please see the section at the top of the post about updating to 1.7.2 - the following packet information is for 1.6.4!!!
Please take a few minutes to read up on Packet Handling, as I'm not going to cover it in much detail here: http://www.minecraft...Packet_Handling
Ok, moving on.
Make a packet handler class like in the above tutorial:
That's it for setting up the Packet Handler framework, now we'll set up a method to send packets from within our ExtendedPlayer class. I like to give this method the same name in all of my IExtendedEntityProperties classes, just to make it easy on myself.
Here you can see my implementations for setCurrentMana and setMaxMana:
Do note, however, that it is not good practice to synchronize everything all the time; if, for example, you have lots of different things that change often in your extended properties such as mana, experience, levels, etc., it is better to have separate packets for each, so that you can update your mana without having to update everything else at the same time. Much easier on the network this way, but for the time being, we will just stick with a single packet.
Another time we need to sync properties is after the entity is loaded from NBT, as that is only done server side and we want the information for our GuiManaBar. Because we can't send and receive packets before everything is loaded, we can't do it from within the readFromNBT method. Guess where we can do it from? That's right, our EventHandler!
Add this to your EventHandler's onEntityJoinWorldEvent method, as this event occurs after everything (the world, entities, etc) is loaded but before anything really happens in the game. As a bonus, it's only called once per entity, so you're not spamming packets.
Anyways, that's a lot of work just to get a little mana bar, but it should all be working
correctly now. Fire it up and try for yourself!
We saw in section 3.3 how to synchronize data between the server and the client using packets, but Minecraft has a built-in functionality specifically to keep certain kinds of variables synchronized between server and client that is highly suited to our needs. It's called 'DataWatcher'. Please head over to the Minecraft Forge Wiki and read this tutorial first, then come back.
Okay, simple enough. DataWatcher is best used for things that are constantly fluctuating, such as health, hunger, or, in our case, current mana. Using DataWatcher instead of packets has the advantage of... we don't have to use packets! But only for the variable we watch. Everything else will still need to use packets, but now we can send packets much less frequently.
To use DataWatcher, first we need to add a new watchable object to the player. We can do this in the constructor of our ExtendedPlayer properties. First, I will define the index to use, in case we need to change it later due to unforseen conflicts with other DataWatcher objects (if you read the earlier tutorial, you'll know there are only 32 available slots, so this is an important step to save yourself hassle later).
Notice that we no longer set the variable 'currentMana'? That's because it is now stored in DataWatcher, so we no longer need it. Delete that variable from the class. Errors will show up - these are all the places we need to change.
First we'll fix NBT saving and loading.
Next we need to fix all the methods that deal with changing currentMana:
Now remove 'currentMana' from your sync() method and packet handler, but keep in mind you still need to synchronize 'maxMana' with a packet, so you'll need to keep the sync() method. Note that the packet size will now be '4' if you've followed along exactly.
Why not use DataWatcher for maxMana too? Well you could, but since it rarely changes, it would be a waste of such a limited resource.
That's pretty much it. Now current mana will stay in sync for the gui display automatically and you'll send far fewer packets. If you ever run into a conflict with another watchable object using the same index '20', you only need to change the value of MANA_WATCHER to update every instance in your code.
Now we're going to add a variable to all EntityLivingBase entities in addition to the ones we added above for EntityPlayer. We only want players to have mana, but we want every creature under the sun to have riches for us to plunder!
Well, guess what it will be named? That's right, ExtendedLivingBase! this class, as it will be almost exactly like the one we just made, but with one difference: we're going to use the init() method so we can use the Random from World object to randomize the amount of gold each entitylivingbase has.
Be sure to register it in your EventHandler! onEntityConstructing should now look like this:
Finished! Go ahead and give it a try, see how much gold everyone is getting!
What's with all these errors, you ask? Well, to be honest, I'm not really sure. Something to do with the way the init() method is called leads to the ExtendedProperties being null somehow. No idea.
Good news is, I know how to get around it. Move everything that seems like it should go in init() to your EventHandler onEntityJoinWorldEvent. Be sure to leave the init method totally empty.
Be sure to create the 'addGold(int)' and 'getGold()' methods in ExtendedLivingBase.
Now try it. Yay! It works!
In conjunction with a custom ItemGoldCoin, LivingDropsEvent and EntityItemPickupEvent, I'm sure you can see how this could be used to store large amounts of coin without clogging up the inventory.
First, many thanks and godly praise upon the legend that is Mithion, creator of IExtendedEntityProperties.
Without him, not only this particular section, but all of this stuff would not be possible. Truly amazing work
by him that allows us to do so much with ease.
Just had to be said. Anyways, as some have noticed, if you die and respawn, the data you so carefully created, registered, sent in packets and saved to NBT is RESET to the initial values when the player dies. This has to do with the way player NBT is stored and retrieved during death, and there's nothing we can do about it. At least, nothing directly.
We need to find a way to store the IExtendedEntityProperties data outside of the player when the player dies, and retrieve it when the player respawns. I knew this, but I never would have thought of where to store it without Mithion's assistance. Though I guess it could be stored anywhere that persists...
Also thanks to Seigneur_Necron for some excellent pointers on good coding practice. This helped clean
up aspects of the code and improve the overall quality. Merci!
Enough blabbing. The most convenient way to store extended properties is bundled as NBT, and we'll be storing it in a HashMap with the player's username as the key. We'll store this map in our CommonProxy class.
We don't use LivingSpawnEvent because that event is not triggered during the process of dying and being respawned, as much as you would think otherwise.
Easy, append the properties name to the username when storing and retrieving. That way, each property is stored with its own unique name and the player it belongs to.
Note that this is still saving data ONLY for player entities, but a player with multiple types of Extended Properties.
This will also allow inter-mod compatibility if, for example, two mods both add custom data to the player using IExtendedEntityProperties AND you've given your properties a unique name (NOT something like "ExtendedPlayer", which I used in the tutorial).
Some of these I incorporated into the above tutorial, but there are more that I didn't. Here are some really
great tips from Seigneur_Necron.
6.1: Create a simple method that returns your ExtendedProperties
Here's how it looks in use. I know which one I prefer
FINAL WORDS:
I was thinking, if you are making more than one class that implements IExtendedEntityProperties, it might behoove you to create your own interface extending IExtendedEntityProperties so that all of your classes' methods are guaranteed to be consistent.
For example, it might look something like this:
Perhaps change 'EntityPlayer' to a generic of Entity to make the interface more widely applicable.
Only thing is, that does away with our ability to call these without an instance of the class, as they are no longer static. I'm just learning myself, so there may be a way around that limitation. Please share if you know
EDIT: Ha, of course right after I posted this Seigneur_Necron added his code which does just this AND still lets you use the static methods because the interface is hidden in the parent class, and the subclass uses static methods to access it. Genius. I can't wait for his full code to be available, but in the meantime, check out what he's posted here.
Don't forget to up the green arrow on any posts that help you!
I'm afraid I don't know - I've never used 1.5.2... but there is one way to find out - try it! I'll see about putting my tutorials all on one page, too. Seems like a solid idea
Thank you for writing tutorials on non-covered topics and for linking to pre-written guides on other material!
:)I'm adding the event and iextended properties tutorials to my tutorial database as we speak
Just doing what I can to thank everyone for making all those great guides already out there No sense re-inventing the wheel!
Thank you thank you thank you soooooo much That was exactly the tutorial I wanted, and everything in it made sense! You sir, are amazing.
Do you know how to render it in a gui in the game, like the experience bar? I will be forever in your debt if you can do that
Bleach Mod
Wow, I'm glad to have helped!
Check out this tutorial - it covers making a gui like you're describing, but using current potion effects. Just swap out the potion effects and use your variables instead.
As an example, I just added the following code right below where the potion effects are rendered in the GuiBuffBar:
If you're looking to make a sort of mana bar or something, you'd just put two textures next to each other in your texture file, one for the background and one a full bar, then only render the full bar up to position x 'currentMana / maxMana.'
Let's look at the parameters for drawTexturedModalRec and you'll see what I mean:
x and y are the on-screen position at which to render.
u and v are the coordinates of the most upper-left pixel in your texture file from which to start drawing.
width and height are how many pixels to render from the start point (u, v)
So if you have a 256x256 Gui texture file, you could call:
This would draw the 36x18 pixel-section starting at pixel position 32,56 in your texture file and render on screen at x=6, y = 12 (pretty much the upper left corner).
Let's say you define your MANA_BAR_LENGTH as a constant (36 in our case, but it doesn't matter), you would get the ratio to draw by using:
int manabarwidth = (int)(((float) props.getCurrentMana() / props.getMaxMana()) * MANA_BAR_LENGTH);
You have to cast to float to get a proper ratio between 0.0 and 1.0, multiply it by the max length of the mana bar, then recast to int for the draw method. Say we had half mana, 0.5F * 36 (our defined constant) = 18, which we get by substituting the variable 'manabarwidth' for '36' in the draw method:
Sorry that kind of dragged on... but that's the gist of it
I'll tell you what, I'll add this in as section 3.3 of the tutorial
EDIT: Sorry, I had the (float) cast incorrectly on the mana bar, I fixed it above, but here it is, just in case:
int manabarwidth = (int)(((float) props.getCurrentMana() / props.getMaxMana()) * MANA_BAR_LENGTH);
<3
It covers everything you'll need to get a functional mana bar displaying on screen, which just so happens to require sending packets! Yay, a bonus mini-tutorial
Thanks! I have seen that GUI Overlay tutorial before, but the bindTexture method isn't used anymore, and there were a few other problems. I'll look at your bonus mini-tutorial in a bit
Bleach Mod
You can EASILY find the fix for bindTexture, just google "minecraft bindTexture 1.6" It's a few hundred posts about it.. ^^
Right..... (facepalm)
Anyway, thank you so much coolAlias! It works perfectly! I'll send you a link to the mod when it's released, if you're interested
Thanks!!!
Bleach Mod
The textures aren't pretty, but might be useful for people having trouble with textures, or people like myself who are just too lazy to make textures when trying to get something to work for the first time
Just a parentheses change
Bleach Mod
Oh, I haven't tried that yet I imagine you'd just have to reload (i.e. sync) the extended properties when the player respawns, as the data is all still there. You might have to re-register the properties, too. I'll have a look at it and put that in the tutorial - good point!
This is turning into an interesting problem. It appears that on death, the player's NBT compound is reset (at least as far as ForgeData is concerned, which is where our data is stored).
I'm not sure how to tackle this, but I know it can be done as there are plenty of mods that do this. When I figure it out, I'll post it here and update the tutorial.
The solution might involve using 'player.addStat' instead of ExtendedEntityProperties, as those are consistent across deaths.
I have to admit, I got a little help from a huge name on this one. Thanks a ton Mithion!
You are honestly the best person in the world
Bleach Mod