Solved Generics in a listener

Discussion in 'Plugin Development' started by Goblom, Jun 9, 2014.

Thread Status:
Not open for further replies.
  1. Offline

    Goblom

    I am writing a mingame that uses a special ability class that has events in it... I want to compensate for 3 events while only using 1 event, but i also partially do not want to have all 3 event methods in the ability class...

    The current class looks like this...

    Code:java
    1.  
    2. package org.goblom.treasurehunt.api;
    3.  
    4. import org.bukkit.entity.Player;
    5. import org.bukkit.event.EventHandler;
    6. import org.bukkit.event.Listener;
    7. import org.bukkit.event.entity.EntityShootBowEvent;
    8. import org.bukkit.event.entity.ProjectileLaunchEvent;
    9. import org.bukkit.event.player.PlayerInteractEvent;
    10. import org.bukkit.inventory.ItemStack;
    11.  
    12. /**
    13. *
    14. * @author Goblom
    15. */
    16. public abstract class Ability implements Listener {
    17.  
    18. public abstract String getName();
    19.  
    20. public abstract ItemStack getItem();
    21.  
    22. public abstract String getDescription();
    23.  
    24. @EventHandler
    25. public void onInteract(PlayerInteractEvent event) {}
    26.  
    27. @EventHandler
    28. public void onShootBow(EntityShootBowEvent event) {}
    29.  
    30. @EventHandler
    31. public void onProjectileLaunch(ProjectileLaunchEvent event) {}
    32. }
    33.  


    but i want it to work like this... (using generics)
    Code:java
    1. /*
    2. * Copyright 2014 Goblom.
    3. */
    4.  
    5. package org.goblom.treasurehunt.api;
    6.  
    7. import org.bukkit.event.Event;
    8. import org.bukkit.event.EventHandler;
    9. import org.bukkit.event.Listener;
    10. import org.bukkit.inventory.ItemStack;
    11.  
    12. /**
    13. *
    14. * @author Goblom
    15. */
    16. public abstract class Ability<EVENT extends Event> implements Listener {
    17.  
    18. public abstract String getName();
    19.  
    20. public abstract ItemStack getItem();
    21.  
    22. public abstract String getDescription();
    23.  
    24. @EventHandler
    25. public abstract void onEvent(EVENT event);
    26. }
     
  2. Offline

    Tzeentchful


    As far as im aware bukkit event system does not support generics.
    Though you might be able to manually add your own RegisteredListener object to the handler list for each event.
    Then check instanceof when it gets called.
     
  3. Offline

    Jogy34

    The EntityShootBowEvent and ProjectileLaunchEvent can both be put under an EntityEvent but the PlayerInteractEvent is a PlayerEvent which gets separated from other Entities. You could listen for a plain old Event but then everything would end up going through that and could potentially cause a lot of lag.

    Something you could do is in one listener class you listen for all of the events you need and then whenever you find one you send it into a single method in your Ability class (I think this is what Tzeentchful may have been getting at). So for instance in some event checker class you're listening for you EntityShootBowEvent, ProjectileLaunchEvent, and PlayerInteractEvent. Whenever one of these gets called, you call your onEvent() method in your Ability class with the event.
     
  4. Offline

    RawCode

    read source code of cbukkit and implement your own event registrator with support of generics.
     
  5. Offline

    Goblom

    How exactly is that done, i know plugins like "Per World Plugins" does that but i have never understood how it was done.
     
  6. Offline

    RingOfStorms

    Instead of messing with internals I would just make a custom event that encompasses all of the events.

    First off, make your custom event:
    Gist Link
    Code:java
    1.  
    2. import org.bukkit.event.Event;
    3. import org.bukkit.event.HandlerList;
    4. import org.bukkit.event.entity.EntityShootBowEvent;
    5. import org.bukkit.event.entity.ProjectileLaunchEvent;
    6. import org.bukkit.event.player.PlayerInteractEvent;
    7.  
    8. public class PlayerAbilityEvent extends Event {
    9.  
    10. private static final HandlerList handler = new HandlerList();
    11.  
    12. @Override
    13. public HandlerList getHandlers() {
    14. return handler;
    15. }
    16.  
    17. public static HandlerList getHandlerList () {
    18. return handler;
    19. }
    20.  
    21. private final Event event;
    22. private final int type;
    23.  
    24. public PlayerAbilityEvent (Event event) {
    25. if(event instanceof PlayerInteractEvent)
    26. type = 1;
    27. else if(event instanceof EntityShootBowEvent)
    28. type = 2;
    29. else if(event instanceof ProjectileLaunchEvent)
    30. type = 3;
    31. else type = 0;
    32. if(type == 0)
    33. throw new IllegalArgumentException("The tst event can only accept PlayerInteractEvent, EntityShootBowEvent, and ProjectileLaunchEvent events.");
    34. this.event = event;
    35. }
    36.  
    37. public boolean isInteractEvent () {
    38. return type == 1;
    39. }
    40.  
    41. public boolean isEntityShootBowEvent () {
    42. return type == 2;
    43. }
    44.  
    45. public boolean isProjectileLaunchEvent () {
    46. return type == 3;
    47. }
    48.  
    49. public int getType () {
    50. return type;
    51. }
    52.  
    53. public PlayerInteractEvent getPlayerInteractEvent () {
    54. if(isInteractEvent())
    55. return (PlayerInteractEvent) event;
    56. else
    57. return null;
    58. }
    59.  
    60. public EntityShootBowEvent getEntityShootBowEvent () {
    61. if(isEntityShootBowEvent())
    62. return (EntityShootBowEvent) event;
    63. else
    64. return null;
    65. }
    66.  
    67. public ProjectileLaunchEvent getProjectileLaunchEvent () {
    68. if(isProjectileLaunchEvent())
    69. return (ProjectileLaunchEvent) event;
    70. else
    71. return null;
    72. }
    73. }
    74.  


    Then make a one time listener for the new event
    Gist Link
    Code:java
    1.  
    2. import org.bukkit.Bukkit;
    3. import org.bukkit.event.EventHandler;
    4. import org.bukkit.event.Listener;
    5. import org.bukkit.event.entity.EntityShootBowEvent;
    6. import org.bukkit.event.entity.ProjectileLaunchEvent;
    7. import org.bukkit.event.player.PlayerInteractEvent;
    8. import org.bukkit.plugin.java.JavaPlugin;
    9.  
    10. public class AbilityListener implements Listener {
    11.  
    12. public AbilityListener (JavaPlugin plug) {
    13. plug.getServer().getPluginManager().registerEvents(this, plug);
    14. }
    15.  
    16. @EventHandler
    17. public void onInteract (PlayerInteractEvent e) {
    18. PlayerAbilityEvent event = new PlayerAbilityEvent(e);
    19. Bukkit.getServer().getPluginManager().callEvent(event);
    20. }
    21.  
    22. @EventHandler
    23. public void onShootBow (EntityShootBowEvent e) {
    24. PlayerAbilityEvent event = new PlayerAbilityEvent(e);
    25. Bukkit.getServer().getPluginManager().callEvent(event);
    26. }
    27.  
    28. @EventHandler
    29. public void onProjectileLaunch (ProjectileLaunchEvent e) {
    30. PlayerAbilityEvent event = new PlayerAbilityEvent(e);
    31. Bukkit.getServer().getPluginManager().callEvent(event);
    32. }
    33. }
    34.  


    Make sure to turn on your listener in the onEnable function of your JavaPlugin
    Code:java
    1.  
    2. public void onEnable() {
    3. new AbilityListener(this);
    4. }
    5.  


    Now you can make your Ability class with the one custom event
    Gist Link
    Code:java
    1.  
    2. import org.bukkit.event.EventHandler;
    3. import org.bukkit.event.Listener;
    4. import org.bukkit.event.entity.EntityShootBowEvent;
    5. import org.bukkit.event.entity.ProjectileLaunchEvent;
    6. import org.bukkit.event.player.PlayerInteractEvent;
    7.  
    8. public class Ability implements Listener {
    9.  
    10. /*
    11. * .... snip
    12. */
    13.  
    14. @EventHandler
    15. public void onAbility (PlayerAbilityEvent e) {
    16. if(e.isInteractEvent()) {
    17. PlayerInteractEvent event = e.getPlayerInteractEvent();
    18. //Do stuff with event
    19. }else if(e.isEntityShootBowEvent()) {
    20. EntityShootBowEvent event = e.getEntityShootBowEvent();
    21. //Do stuff with event
    22. }else if(e.isProjectileLaunchEvent()) {
    23. ProjectileLaunchEvent event = e.getProjectileLaunchEvent();
    24. //Do stuff with event
    25. }
    26. }
    27. }
    28.  
     
    BorisTheTerrible likes this.
  7. Offline

    RawCode

    Goblom
    Read source of cbukkit and use magical combination of c&v
     
  8. Offline

    fireblast709

    RawCode if you don't have anything useful to say, then leave
    Goblom why not have one listener that will forward the event to the best ability handler? (Based on, for example, held item)
     
    CaptainBern and desht like this.
  9. Offline

    desht


    Looking at your code, I wonder if you wouldn't do better to separate the Ability class out from your event listener class entirely. I like the MVC approach: model/view/controller, where the model (the backend logic, in this case your Ability class) is cleanly separated from the view (the classes which display data, e.g. changing the world somehow, or sending messages to players), and the controller (your event listeners, command handlers, etc.).

    Embedding your model inside your controller just leads to contortions which ultimately won't help with the maintainability of your code. In this case, it looks like your Ability is something that a player would have, so your event handlers would check the abilities for the player concerned, and dispatch the event to each ability (I'm assuming a Player can have more than one abilty here).

    I do this with Sensible Toolbox a lot; see https://github.com/desht/sensibleto...ensibletoolbox/listeners/GeneralListener.java (which dispatches events) and the abstract https://github.com/desht/sensibleto.../desht/sensibletoolbox/items/BaseSTBItem.java class (which defines the abstract methods to receive those dispatched events). Then I extend BaseSTBItem and implement the methods to receive the events that the listener passes on.
     
  10. Offline

    Smerfa

    maybe something like that?

    Code:java
    1. public abstract class Ability {
    2.  
    3. public AbilityType type; // you can use just "insteadof ProjectileAbility " when you need, but enum is better for me :P
    4.  
    5. public Ability(AbilityType type)
    6. {
    7. this.type = type;
    8. }
    9.  
    10.  
    11. public abstract String getName();
    12.  
    13. public abstract ItemStack getItem();
    14.  
    15. public abstract String getDescription();
    16. protected abstract void onEvent(Event event);
    17.  
    18. public static enum AbilityType
    19. {
    20. PROJECTILE, BOW_SHOOT, INTERACT;
    21. }
    22. }


    Code:java
    1. public abstract class ProjectileAbility extends Ability implements Listener {
    2. // add constructor
    3.  
    4.  
    5. @EventHandler
    6. public void onProjectileLaunch(ProjectileLaunchEvent event)
    7. {
    8. onEvent(event);
    9. }
    10. }


    Code:java
    1. public abstract class ShootBowAbility extends Ability implements Listener {
    2. // add constructor
    3.  
    4.  
    5. @EventHandler
    6. public void onShootBow(EntityShootBowEvent event)
    7. {
    8. onEvent(event);
    9. }
    10. }


    Code:java
    1. public abstract class InteractAbility extends Ability implements Listener {
    2. // add constructor
    3.  
    4.  
    5. @EventHandler
    6. public void onInteract(PlayerInteractEvent event)
    7. {
    8. onEvent(event);
    9. }
    10. }
     
  11. Offline

    Goblom

    I have already figured this out and gone ahead in development. (I apologize for not saying so earilier)

    I am using something along the lines of what desht said. Use a listener then dispatch events to the ability.
     
  12. Offline

    RawCode

  13. Offline

    fireblast709

    Just no. If you want to fight (reas as: discuss) about it, you can pm me if you want. Unlike you I like to keep threads clean. O btw, what has 'Type Erasure' to do with my post in the first place. The solution I provide does not even use generics.
     
    Rocoty and CaptainBern like this.
  14. Offline

    1Rogue

    One idea that comes to mind is using a ListenerFacade:

    Code:java
    1. public abstract class MyEvents {
    2.  
    3. protected final YourPlugin plugin;
    4.  
    5. public MyEvents(YourPlugin plugin) {
    6. this.plugin = plugin;
    7. }
    8.  
    9. public final String getName() {
    10. return this.getClass().getName();
    11. }
    12.  
    13. public abstract void onMove(PlayerMoveEvent event);
    14. public abstract void onInteract(PlayerInteractEvent event);
    15. //etc...
    16.  
    17. }


    Code:java
    1. public class ListenerFacade extends MyEvents implements Listener {
    2.  
    3. protected final YourPlugin plugin;
    4. private static Map<String, MyEvents> listeners = new HashMap<>();
    5.  
    6. public ListenerFacade(YourPlugin plugin) {
    7. this.plugin = plugin;
    8.  
    9. MyEvents[] facades = new MyEvents[] {
    10. new SomeListener(this.plugin),
    11. };
    12.  
    13. for (MyEvents ev : facades) {
    14. ListenerFacade.listeners.put(ev.getName(), ev);
    15. }
    16. }
    17.  
    18. @EventHandler
    19. public void onMove(PlayerMoveEvent event) {
    20. for (MyEvents ev : ListenerFacade.listeners) {
    21. ev.onMove(event);
    22. }
    23. }
    24.  
    25. //etc...
    26.  
    27. }


    Code:java
    1. public class SomeListener extends MyEvents {
    2.  
    3. public SomeListener(YourPlugin plugin) {
    4. super(plugin);
    5. }
    6.  
    7. @EventHandler
    8. public void onMove(PlayerMoveEvent event) {...}
    9.  
    10. }


    Though I don't really prefer it.
     
Thread Status:
Not open for further replies.

Share This Page