garbagemule / MobArena

MobArena plugin for Minecraft
GNU General Public License v3.0
195 stars 150 forks source link

Console commands on some Arena Events #678

Open Maroon28 opened 3 years ago

Maroon28 commented 3 years ago

Feature request

Short description

This is an expansion to issue #598 , with more features to properly flesh out this idea. To keep it simple just send a console command whenever certain events happen. Some basic ones could be: playerJoin, playerLeave, playerDeath, arenaStart, and arenaEnd. This allows us to have more flexibility by integrating some aspects of MobArena with other plugins without needing to use a third-party software, such as skript.

Implementation details

It'll be a simple node for each arena, which allows multiple commands that the plugin can loop through until they're all executed. The commands should be executed as a final step, in order not to mess up with any of MobArena's internal steps. Something like this, for more clarification:

  1. A cancellable event is emitted via Bukkit's event bus. If cancelled, MobArena aborts the player join.
  2. If the arena has an entry fee, the fee is removed from the player's holdings (balance, inventory, etc.).
  3. All of the player's nearby pets are told to sit.
  4. The player is teleported to the lobby (possible world change).
  5. The player's gamemode is set to Survival (possible gamemode change).
  6. The player's inventory is saved, then cleared.
  7. All of the player's current potion effects are stored, then cleared.
  8. The player's "flying" and "allowFlight" flags are set to false.
  9. The player's health, hunger, and experience are all saved, then "reset".
  10. The player's local time is changed according to the arena settings.
  11. If a default class is set, the player "selects" that class.
  12. And for the new step, Execute any defined playerJoin commands.
    
    arenas:
    default:
    settings:
     player-join-commands:
     - give <player> diamond 1
     - eco give <player> 100
     player-leave-commands: #This could also execute if the arena ends
     - ban <player>
     player-death-commands:
     - eco set <player> 0
     arena-end-commands:
     - give <player> diamond 10 #<player> here references all players that lasted to the end. Another placeholder could be used.


Using the above configuration, the player will get a diamond and $100 on join. He will be banned upon leaving the arena (quite harsh, I know.), and will have his balance set to 0 if he dies. If the arena successfully ends, everyone who lived to the end in the arena gets 10 diamonds. 

Some more details on how each event should behave:

playerJoin: Runs whenever a player joins the arena 
playerLeave: Runs when a player leaves the arena on purpose, either using /ma leave, or by disconnecting. 
playerDeath: Runs when a player gets killed by a monster in the arena
arenaEnd: Runs when the arena ends for whatever reason. Either all players disconnect, the final player dies, etc
arenaStart: Runs when all the arena players get ready, and mobs start spawning

This is quite a rough idea, so feel free to change anything around until it fits in!

**Additional info**
<!-- Is there anything else we need to know? -->
N/A
garbagemule commented 3 years ago

Let's consolidate everything into one issue to just keep track of stuff in one place. I've closed #598 and we'll continue here. I think it's worth reiterating the "order of events" challenge to really drive the point home and cover all bases (only one is covered here currently), and also to mention current workarounds for people looking to do something about this here and now.

Procedure Ordering

I hinted at this in the old issue, but there's quite a bit to consider with something like this because of the order of events. For the very specific case of the player join procedure referenced here, I think it makes sense to run commands after the procedure, because that's where the "state" of the player should be stable and unchanging until something else happens (like picking a class or leaving the lobby). For the other events, I'm not sure that after is the right place:

One way to approach this issue is to introduce hooks both before and after each of these procedures and let people choose for themselves. It does muddy the waters a bit, as all flexibility tends to do, so "do we need it?" is probably a good question to have answered before making a giant project out of it all.

For how, we can restort to...

Workarounds

In #598, I alluded to two different workarounds; world/region triggers with external plugins and MobArena's Things framework.

Plugins like WorldGuard have, to my knowledge, some hooks that allow for running commands or "doing stuff" when players enter and leave regions. By placing a region around the lobby, it should be possible to "ghetto detect" the player join event. Similarly, placing a region around the entire arena (lobby, arena floor, spectator area) should make it possible to "ghetto detect" player leave. I don't have any specifics to share, but if anyone does, we could write them down here or maybe add a wiki page about it.

MobArena's own Things framework has Command Things, which can be used to "give" commands to players when they select a class. This is obviously very limited compared to region triggers, but it might be enough for some setups (for now).

Another much more powerful workaround is using a plugin script engine such as Skript or Denizen to import and listen to MobArena's events and run commands based on those. They all fall in the "before" category, as some of them can be cancelled to prevent that event from actually occurring. I haven't toyed with this stuff myself, but a couple of folks have had success with it, so it definitely is possible.

Of course, workarounds are likely not as effective or predictable as...

Native Support

In #598, I pointed out that I don't see this happening before the Sessions Rework. This is mostly because the Sessions Rework is arguably more important than other session-related features, so taking effort away from that and spending it on stuff that has to be rewritten anyway is a bit of a waste of time. Progress on it is slow and tends to stagnate, which means a lot of other requests will feel a lot more urgent, even though they aren't as important. The more time we spend on seemingly urgent stuff, the longer it will take for the important stuff to get done.

I take my previous statement back. I think we may be able to do this properly before the Sessions Rework. One of the goals of the Sessions Rework is to make things more flexible and more extensible. Hooking into internal events and procedures is very much an extensibility thing (even if it is implemented natively). If we "get it wrong" now, it means we have to suffer the backwards compatibility issues that come with modernizing the code base, but if we "get it right", chances are backwards compatibility becomes a non-issue, and maybe we can even salvage what we do here and reuse it. That's why I think it's important that we don't rush it. I hope that makes sense :)

Maroon28 commented 3 years ago

Procedures

For the procedure ordering, I do agree that most of these cases should be run before most of the events take place. But we need to take in consideration what we will use these new modules for before deciding. If I had to guess, it'll likely be used to either penalise or reward players. If we're rewarding with a virtual item (money, or points, or w/e for example), sure, it shouldn't matter too much when you run the commands. However, we still need to consider the possibility of rewarding physical items

Take this, for example. What if you want to give a crate key to a player after dying? Running the commands before would be problematic, since the MobArena clears your arena items, and restores back the pre-arena items. Meaning, you'd likely get the key while you're in the arena, then proceed to lose it when the arena gives you back the old items. I think what would make the most sense in this case is to run the commands after the inventory clear and restore steps. This mainly applies to On Leave and On Death

For the On Start, perhaps we can branch it out a bit? To be more specific, we can make it split into On Lobby Start and On Arena Start. The lobby start commands run when the first player joins the lobby and the countdown starts. I personally don't see any good use for this, the only thing I can think of would be some sort of notification that the arena is being queued now. So imo, we can just skip this and do the On Arena Start, which essentially runs after the players are teleported to their arena warps.

As for On End, I'd also suggest we branch this one to Before End and After End, which are self-explanatory. One runs before rolling back the arena, and the other runs after.

Workarounds

I dug around a bit in the recommended workarounds, and I seem to have gotten somewhere with worldguard regions. We should be able to recreate some of these event hooks with some really hacky worldguard flags. First, download and install worldguard and WorldGuardExtraFlags(https://www.spigotmc.org/resources/4823/).

Here's a small guide to replicate some of the events:

On Join: Define a worldguard region around your lobby area (or the entire arena), do /rg flags while in the region, scroll to page 6, and set your console-command-on-entry. This will mimic the event just fine, as long as you make sure that they can only enter it once (which is when the arena starts). This is to prevent your players running out of the region, then walking back in and triggering the command.

On Leave: Same as above, just set the console-command-on-exit flag instead! Make sure you also encompass the entire arena!

On Death: This one is a little tricky, you have to make a sub-region where your fighting happens, and then set the spectator spawn slightly above the sub-region border. Now just set your console-command-on-exit flag and that should work fine. An issue here would be if you don't have spectator mode on. That means it will run both On Leave and On Death. If you do /ma leave, it will also run both commands, and sadly there isn't much to do about that.

On Start: Simply make a region where your player spawnpoint is, and make sure they can't get to it again, to avoid running and going back in. This again could be a little tricky if your player spawn is in the middle of your fighting ground

On End: This will also sadly blur in with the On Death and On Leave events, and there's not much we can do about that.

garbagemule commented 3 years ago

The following is a breakdown of the procedures for each of the mentioned events. As evident from the wall of text, there's a lot of stuff going on, but that doesn't mean we can't find a good spot (or spots) for commands to be executed.

Player join

The join procedure basically stores the player state, moves them to the lobby, and "resets" everything in various ways.

  1. ArenaPlayerJoinEvent (cancellable) emitted.
  2. Optional entry fee collected.
  3. Pets told to sit.
  4. Player teleported to lobby (possible world change).
  5. Game mode set to survival (possible game mode change).
  6. Inventory saved, then cleared.
  7. Potion effects saved, then cleared.
  8. Fly flags set to false.
  9. Health saved, then filled.
  10. Hunger saved, then filled.
  11. Experience saved, then cleared.
  12. Optional local time change.
  13. Player marked as "part of the arena" (and the lobby).
  14. Optional timer start if they aren't already started.
  15. Optional default class is assigned.

Player death

Some "clearing" happens on player death, but most of the "logistics" can't happen during the death screen and so has to wait for the respawn.

  1. Dropped items are cleared.
  2. Dropped experience is cleared.
  3. If keep-exp: true, current experience is added as an Experience Thing reward.
  4. ArenaPlayerDeathEvent (not cancellable) emitted.
  5. Player marked as no longer "part of the arena".
  6. Player unmounted.
  7. Inventory cleared.
  8. The Arena end procedure is tried (fails if more players are alive).

Player respawn

When a player respawns, they have to be "reset" and either sent to the spectator area or "kicked".

  1. Respawn location set to spectator warp.
  2. Class-specific permissions removed.
  3. Potion effects removed.
  4. If spectate-on-death: false, the Player leave procedure is executed in the next tick.

Player leave

The leave procedure is basically the inverse of the join procedure, as it uses the same "steps" list, but in reverse. However, it does a lot of "cleanup" from class selection and such as well.

  1. ArenaPlayerLeaveEvent (cancellable) emitted.
  2. Player marked as no longer "part of the arena".
  3. Player unmounted.
  4. Inventory is cleared.
  5. Class-specific permissions removed.
  6. Potion effects removed.
  7. If in lobby, class limits adjusted.
  8. Local time restored.
  9. Experience restored.
  10. Hunger restored.
  11. Health restored.
  12. Fly flags restored.
  13. Potion effects restored.
  14. Inventory restored.
  15. Rewards granted.
  16. Game mode restored.
  17. Player teleported to join location.
  18. Pets told to not sit.
  19. If exit warp is set, player teleported to exit warp.
  20. If in lobby, entry fee refunded.
  21. The Arena end procedure is tried (fails if more players are alive).

Note that the order of the teleporting and the game mode changing is pretty important, as multi-world inventory plugins tend to "do stuff" on those events.

Arena start

A lot of stuff happens when the arena starts, and like with the join/leave process, the order does matter quite a bit, even though it might seem a little arbitrary.

  1. Auto start timer stopped (if running).
  2. ArenaStartEvent (cancellable) emitted.
  3. Snapshot of registered containers taken.
  4. All "lobby players" become "arena players".
  5. Players who chose "random" are assigned random classes.
  6. Scoreboard initialized.
  7. For each player:
    1. Player teleported to arena warp.
    2. Class-specific permissions added.
    3. Player stats reset.
    4. Optional class fee collected.
    5. Player added to scoreboard.
  8. Wave spawner started.
  9. Sheep bouncer started.
  10. Arena state set to "running".
  11. Class pets spawned.
  12. Class mounts spawned.
  13. Class limits reset.
  14. Reward manager reset.
  15. Leaderboards initialized and started.
  16. Optional arena start announcement broadcast.

Arena end

When the arena ends, players will have already left or died (players are "kicked" on the final wave, if set), so the cleanup is mostly non-player stuff.

  1. ArenaEndEvent (cancellable) emitted.
  2. Arena is disabled.
  3. Arena state set to "not running".
  4. Leaderboards stopped and updated one last time.
  5. Wave spawner stopped.
  6. Sheep bouncer stopped.
  7. Optional arena end announcement broadcast.
  8. Monsters removed.
  9. Player-placed blocks removed.
  10. "Residual entities" (dropped items, arrows, experience orbs, etc.) removed.
  11. If soft restore enabled, region is restored.
  12. Snapshot of registered containers restored.
  13. Arena is re-enabled.
garbagemule commented 3 years ago

Take this, for example. What if you want to give a crate key to a player after dying? Running the commands before would be problematic, since the MobArena clears your arena items, and restores back the pre-arena items. Meaning, you'd likely get the key while you're in the arena, then proceed to lose it when the arena gives you back the old items. I think what would make the most sense in this case is to run the commands after the inventory clear and restore steps. This mainly applies to On Leave and On Death

This is perhaps not the greatest example, since rewards should be added as actual rewards to hook into the reward granting portion of the leave procedure. This can be done with MobArena's /ma addreward command, but it would have to happen quite early in the leave/death procedures, because you can only add rewards to players that are "part of" an arena that is also in the "running" state.

I guess it's just two different ways to solve the same problem, which is fine. The more I think about it, the more I think we need to have both before- and after-hooks on most of these events, but where exactly is still unclear. It would be good with concrete examples showing the necessity of each hook, just to have something to work towards.

Maroon28 commented 3 years ago

We've already checked off the player join command location. It should be after the entire procedure ended, I.E step 16. I don't see a need for a before hook, but if we really want to, we can put it as step 5 or 6. Again, this isn't needed at all, and I can't think of any good uses to have a before hook.

PlayerDeath: This one could be executed after step 5, I.E when the player is no longer part of the arena. It can also fit after step 7? Not too sure about this one.

PlayerRespawn: This can have two hooks, maybe? One executes after step 3, the other executes after step 4. Reasoning behind this is if you want to give something to the spectators, you'd use the first hook. But then again if the second hook executes as a new step 5, it might mess with the leave procedure, so perhaps we could only have one hook that happens after step 3, and the rest can be part of the PlayerLeave procedure. As for examples on what items you could give to spectators, probably items to open GUIS that either let you exit the arena, join the next running arena, get a list of arenas etc.

PlayerLeave: This one's a little tricky. I'm not a 100% sure myself, but I'd say somewhere along the lines of step 18? Like, straight after the players are teleported to the join location (or the exit warp), since that's when the player's state is the most stable.

ArenaStart: I think this one can be after step 7. Or maybe even part of step 7. Maybe right after they're teleported to the arena warp? So after step i. We can add another hook that runs after step 16, too.

ArenaEnd: This one can happen after step 7 (again), or step 8/9. If we want to add another hook, then I think it should be after step 12 or 13

Maroon28 commented 2 years ago

So! I was digging around recently and I found another plugin that can help accomplish exactly this sort of stuff. You can basically listen to events from any other plugin, extract any 'local' placeholders from it (using the event directly!), then execute any commands needed for any use cases. So, no more hacky worldguard regions are needed or that much coding experience!

The plugin is ConditionalEvents, and you can use the Custom Events guide to make the mobarena ones. Of course this isn't a final solution and it would still probably be better to have stuff like that natively supported inside MobArena, but I figured I'd update the issue here for any future viewers looking to do something similar! Here's a quick example that I made and tested and it works perfectly fine:

event1:
    type: custom
    custom_event_data:
      event: com.garbagemule.MobArena.events.ArenaPlayerJoinEvent
      player_variable: getPlayer()
      variables_to_capture:
      - '%arena_name%;getArena().getSlug()'
    actions:
      default:
      - 'message: &cThis is a test message! You joined %arena_name%'
      - 'playsound: %player%;NOTE_PLING;10;0.1'

edit: Added an example