-
Notifications
You must be signed in to change notification settings - Fork 27
Expand file tree
/
Copy pathEventHandlerTutorial.java
More file actions
483 lines (404 loc) · 22.3 KB
/
EventHandlerTutorial.java
File metadata and controls
483 lines (404 loc) · 22.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
/** Implementing and Using Forge's EventHandler */
/*
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.
This tutorial will cover:
1. Building and using an Event Handler
2. Advanced information for handling events
3. A sampling of Event types and their possible uses
*/
/**
* Step 1: Create TutEventHandler class
*/
/*
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.
*/
public class TutEventHandler
{
}
/**
* Step 2: Registering your EventHandler
*/
/*
Resgister 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, but see below for more information on the different event buses.
*/
@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());
}
/*
NOTE: 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
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.
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
}
/*
* 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.
/*
* Advanced Information: 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.
*/
/*
Now for some examples of Event types, their variables, when they are called and what you might do with them, but
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);
}
/*
Now on to the events! But 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 events are from 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 player's 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<EntityItem> 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.
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.
*/
/**
* 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!
*/