Solved Sleeping without time change

Discussion in 'Spigot Plugin Development' started by OffLuffy, Nov 11, 2016.

  1. I'd like to write a plugin that adjusts the speed that night progresses depending on the number of players sleeping. I've done most of the code for this, but I've run into a small road block. The vanilla time-change mechanics are still in-tact, and I haven't found a way to prevent it. I was wondering if anyone has found a non NMS or packet altering method of preventing sleeping from changing the time while still allowing the player to remain in bed.

    It doesn't appear there is any simple API-provided way to do so, but I figured some of the more cunning developers may have found some trick of scheduling and loopholes to achieve this effect.
     
  2. Why not with packets?

    Then you could actually cancel the right clicks on beds and just make it look like they are sleeping with PacketPlayOutBed. Then you use a repeated task to add time to the corrent time of the world (World#getTime and World#setTime) depending on how many players are in bed at the moment. You should maybe send titles or messages to those players so they know how many are sleeping and how fast the time is changing.
     
    • Optimistic Optimistic x 1
  3. Only because I haven't worked with them much at all, and generally prefer the simpler API-driven methods, breaks less often and such.

    It leaves me wondering stuff like:
    • How can I monitor when they wake back up or will it keep the player in bed until I send another packet to wake them?
    • Assuming the player can cancel the sleep, how do I monitor that?
    • Will it require sending bed packets in a repeating task to keep them in bed?
    • Will I need to send the same packet or another packet to everyone for them to see that a player is sleeping?
    • Will the packet-modified sleep let them sleep past morning?

    If you have an example of how the packet can be utilized, I'd appreciate it. I see some instances here and there, but they seem like a mess. Chances are I'll try to contain NMS code to my underlying lib plugin so I don't have to figure out which plugins break with NMS updates. Can probably live with that.

    The rest of the plugin is pretty much done, although I've been unable to test much of it, given the vanilla mechanics some-what interrupt that. The idea currently is that there is a minimum night length specified via config, the plugin calculates how many ticks-per-tick need to pass to achieve this speed (-1 to account for the vanilla time passing), then scales that speed-in-ticks value based on the amount of total sleepers / non-ignored wakers. Some potential for divide by zero circumstances there, so special case catching and such. Each sleeper triggers a broadcast, as you mentioned, although I haven't thought about a title yet. May become somewhat invasive to players whom don't want to sleep. Suppose I'll need to do something about spamming as well after the fact.
     
  4. So I've given ProtocolLib a shot, but it doesn't seem to be functioning as intended. At least not with the bed packets. Currently, it sends the packets, but they do nothing.

    Instead, I thought I'd be a little sneaky and let the player enter the bed, trigger a custom sleep event in which they'd be stored in a sleeping list, and immediately wake them up and block the packet being sent for the wake animation so that the client would still appear to be sleeping. I'd cancel the move event for sleeping players as well to prevent sleep walking.
    The problem here is that I also know of no way to interrupt sleeping.

    Additionally, I'd listen to packets received for the wake animation and trigger a custom wake event. I suspect I may run into a problem as I'm not sure if the client will send a wake animation if the server interrupts their sleep (when it turns day or if the bed is removed mid-sleep).

    So that being said, does anyone know of how to interrupt a sleeping player? I can't just cancel the enter bed event as it doesn't show the client that they've went to sleep. I can't cancel the event in a delayed task it seems either, as I'm assuming any follow-up activity would've executed prior to cancelling it. Just seems quite wrong. I've tried teleporting the player to themselves (thinking I could also intercept the packet for that), but that also doesn't eject them from the bed.
     
  5. @Nikl
    Nah, that will desync the player from the server. For the server the player is NOT sleeping

    @OffLuffy
    If yo do not care to use some NMS, you can set the sleepTicks of the player to neve reach it's max. This way the server will NOT treat this player as sleeping and therefore doesn't advance the time. Have a look here. I made a plugin a while ago which does this, it is linked in the same thread, but here is the link to the post.
    Here is the relevant code:
    Code (Text):
        /**
         * Make the player continue his sleep
         */
        void forceContinueSleeping() {
            if (isPlayerInvalidAndCancelIfYes()) {
                return;
            }
            Player player = getPlayer();

            Object nmsPlayer = ReflectionUtil.invokeMethod(player, "getHandle", new Class[0]);

            ReflectionUtil.setInstanceField(nmsPlayer, "sleepTicks", 98);
        }
     
    You will need to call that every tick. After the sleepTicks reach 100, the player is counted as "deeplyAsleep". If all players are that, the time will be set to daytime again.

    Letting the sleepticks reach 100 may be enough to wake up the player. If not, WolrServer calls this method to wake up the player:
    Code (Text):
    if(entityhuman.isSleeping()) {
        entityhuman.a(false, false, true);
    }
    That will hopefully give you enough insight, so that you can figure out a solution that works for what you try to accomplish.
     
    • Useful Useful x 1
  6. Thanks -- I've been looking around for damn near anything to do this, and it just wasn't happening. I was assuming there was a sleepTicks method, but of course the API doesn't allow setting, just getting.

    To conserve some resources, would it be just as well to instead set it only every 10-25 ticks? Not that I think it'd make a huge difference, but might as well if it's just as functional.

    Doubt it'll make too much difference, but I'm using DarkBlade12's ReflectionUtils, which doesn't have #setInstanceField(), but it does have a #setValue(), an overload has similar arguments, but has an additional 'declared' boolean. Not terribly sure if sleepTicks is declared or not, but guess it's a 50/50 chance of working (assuming it is in fact a similar function).
     
    #6 OffLuffy, Nov 19, 2016
    Last edited: Nov 19, 2016
  7. Thanks @IAlIstannen , looks like it works perfectly. Pending resolving a math error now, I'll likely release a new plugin here shortly. :D

    Since I only need to prevent time change, I've just set sleepTicks to 0 every 10 ticks, works quite well.
     
  8. @OffLuffy
    Code (Text):
    public int sleepTicks;
    In EntityHuman.
    This means it is a declared Field of EntityHuman (declared in this class), but not of EntityPlayer. Setting the value to false should be all right.
    You only really need declared Fields if the field is private, as getField(name) doesn't find these.
    Here is a cheat sheet.

    It is not part of the bukkit api, it is part of mojangs server (NMS), which is why you can not obtain or set it easily and without reflection.

    If you only set it every 10-25 ticks, you must set it to a smaller value, else it will be past 99 when you next set it. But yes, that should work too.

    EDIT: Ninjad... :p Anything I can help with? :p
    Yea, that is a way too ;)
     
    • Informative Informative x 1
  9. Nah, it was just a function I had to remap a value in a range to a new range. But I had derp'd and flopped up the order I passed in arguments to the function, so was giving me some odd, very small value. Fixed that, so it looks about ready. Just polishing and modifying messages and such. Haven't thought of how to prevent spamming, so I may not use chat messages, but just titles while sleeping. When I get it finished, I'll separate it from my library plugin so I can release it without dependencies.

    Thanks!
     
    • Like Like x 1
  10. @OffLuffy
    Looks great!

    I have a few suggestions though:
    1. You do not need vault to simply check for permissions. Permissible.hasPermission is fine
    2. Dependency injection or singelton. Do not make all methods and variables static in the main. Pass an instance of it around or provide a getInstance() method, returning an instance. The former is called "Dependency Injection", the latter "Singleton design pattern"
    3. You can define a command permission in the plugin.yml. This way it will not even show up when tab completing:
      Code (Text):
      carbonsleepreload:
          description: Reloads config values
          permission: "realod"
    4. You import Spigot v1.11, thus requiring this version. This means you can just use "Player.sendTitle". It is deprecated, so there might be a newer version of it, but it surely is more stable than using NMS directly ;)
      This also means you can drop your entire TitleUtil, FormattedMessage and MessagePart classes. I believe TextComponent is part of Spigot too, which is the same as your FormattedMessage
    5. This also removes the need to use any NMS, which means it will work for way more versions!
    6. Your whole MiscUtil is never really used, apart from the clamp method.
    7. Set the "inst" variable to "null" in onDisable. This prevents some memory leaks which may occur due to the insane way bukkit handles reloads.
    8. "Wakey, Wakey rise and shine" is much cooler than "Good morning"

    Apart from that it looks like a solid plugin, which certainly adds some charm to the game. Great idea!
     
    #11 IAlIstannen, Nov 19, 2016
    Last edited: Nov 19, 2016
    • Like Like x 1
  11. Definitely will look into those suggestions. I've been told previously singleton static stuff is a bad idea, so I probably do need to find a work-around for it. Most of the suggestions here I think is a byproduct of having unlinked it from my library plugin, especially the utility classes, which were mostly copy-pasta'd in and retain code geared more towards other projects.

    1. True. I'm only checking one perm, but I've been in the habit of using Vault for a while now. If some other features are added in later it may be worth-while, but for now, perhaps better not to bother.
    2. Gotcha. Love encapsulation, but did a lazy.
    3. Will look into that, never bothered with tab-completion, not a bad idea.
    4. I wasn't aware Spigot had title support now :D
    6. I used repVars() in the title utilities class. Although it was pretty much a copy-pasta from the lib plugin. Will likely remove it when I sort out the title suggestion as well.
    7. Will likely look into #2 and perhaps do away with this entirely
    8. Indeed. I intend to make this configurable as well. Perhaps a bit nicer to the non-English peeps out there as well.

    All in all, it was a rather rushed idea. Sped along with the release of 1.11 and needing to update my server as well.
     
  12. @OffLuffy
    Singelton is not that bad :p
    The clean way is dependency injection, which I probably mentioned.

    1. You only need vaults for groups and setting permissions, which you will probably not need. But yea, later on it may come in handy :p
    4. Yea, I compiled against 1.7.10 until quite recently. It was a shock xD
    6. Your title is not (yet) configurable, so you can probably sacrifice it. But yea, I am in the habit of copy-pasting stuff too xD
    Have a nice day and good luck with improving this ;)
     
    • Friendly Friendly x 1
  13. Think I've gotten to all the suggestions. The TextComponent was a little tricky. It's not NMS, so I suppose it's more version independent, but it's also not exactly part of the API I think @[email protected] It's preferable at any rate. The Player#sendTitle() doesn't exactly support the TextComponent, but you can use TextComponent#getLegacyText(), and that maintains the styling, although getText(), toString(), and getPlainText() are a bit useless to that end. The sendTitle() is deprecated and currently without a replacement as far as I've found, but I suppose it'll work until a replacement is added.

    Also think I managed to snipe out the unnecessarily static data and put in all this fancy dependency injection. Not so bad with only like 2-3 classes that needed it. I don't think there are any other static variables to null out at least. The memory leak possibility is nice to know as well.

    Title is also now configurable, but only variable at any rate. I'll rig up a new, more applicable variable function later if necessary.
     
  14. @OffLuffy
    I believe you do not need the ChatComponent at all for the title. Just use [ChatColor.RED + "Hello" + ChatColor.GREEN + "You"] like you would do with a normal chat message. (Which is what toLegacyText does)

    Dependency injection is a pain, but at least it makes testing and changing easier. Good on you! ;)
    (Small nagging, you can make the plugin instance field in the injected classes private ;))

    The configurable subtitle is nice too!

    I think that was all I could think of, looks a lot nicer now. Really a cool plugin! :)

    Have a nice day!
     
  15. That is true. I looked at the github to see what legacy text did, and if there was some other work around, and it does pretty much just convert it to use the section symbol. Didn't occur to me to actually remove the ChatComponent overhead though. Could even make the color configurable to that end with the usual translateAlternateColorCode('&') bit.

    I've added the private modifier, although previously I assume they were private by lack of modifier.

    Seems at least one user has mentioned that this version without NMS no longer worked on Spigot 1.11, albeit I can't imagine why unless they weren't actually using Spigot. By removing the TextComponent, it would seem that all non-API imports are removed, even letting me depend on just the good ol' API jar instead of the server jar.

    You've been quite a useful fellow XD
     
    • Friendly Friendly x 1
  16. @OffLuffy
    True :)
    Makes some users happy hopefully :p

    Sounds logical but isn't the case actually. This is called "package private" in fancy terms and means that every class in the same package can see it. Have a look here.

    I do hope it was because of the Spigot API as you said, else I will take the blame :D
    Just using the API is a quite refreshing feeling and hopefully future-proofs things ;)


    Thank you!
    It was a joy writing with you :)

    If you have any other questions you feel I may be able to answer, send me a message ;)

    But now enjoy your day!
     
    • Informative Informative x 1