Object interaction event and interaction with custom objects

  • I know the rule says "one post, one topic" but these two points are rather tightly related.


    1) a PlayerInteractsEvent to be fired when the player interacts with some object ([F] key). I could not find it even in the LUA API. Among other things, it could be useful for controlling access to objects like workbenches, furnaces, etc. in protected areas (it may subsume the current LUA event "PlayerObjectStatusChange").


    2) a World3DModel::SetInteracts(boolean) method to activate the possibility to interact with a custom 3D model. Interaction should not do anything specific on the model itself, 'simply' show the hammer cursor and trigger the above event on [F] pressed. Plug-ins could process the event to implement specific functionalities.

  • It's fine to create a single topic for these two points :)


    a World3DModel::SetInteracts(boolean) method to activate the possibility to interact with a custom 3D model

    Something similar was actually planned, it will be added soon ;)


    a PlayerInteractsEvent to be fired when the player interacts with some object ([F] key)

    A "PlayerInteractEvent" will be introduced at least for the custom models. Not sure about regular objects, right now opening up the crafting menu at a workbench is handled clientside. It would be possible to trigger such an event, but it would not be possible to cancel the event (i.e. prevent the player from accessing the workbench) :|
    But if it is really required we could change this behaviour.

  • Something similar was actually planned, it will be added soon ;)

    Great! Looking forward for the next instalment!

    A "PlayerInteractEvent" will be introduced at least for the custom models. Not sure about regular objects, right now opening up the crafting menu at a workbench is handled clientside. It would be possible to trigger such an event, but it would not be possible to cancel the event (i.e. prevent the player from accessing the workbench) :| But if it is really required we could change this behaviour.

    Which raises another question: when in the work-flow event handlers are called? Even assuming somewhere along the flow there is difference between events started server-side (e.g., an NPC spawn) and events started client-side (e.g.: opening a door), are the handler called before the action is carried out, after it or independently (i.e. asynchronously / in different threads)?


    Currently, the client checks with the server after [F] is pressed on a door and before actually opening it (otherwise, it would not be possible to block door opening); OTOH, the handlers for the quick slot change event are called much after the change happened (one or two seconds after the change with client and server running on the same machine!).


    I understand the delays that always checking with the server would introduce, but this already happens in several cases, even for events more frequent than opening a workbench, like entering a chunk. One could believe that the behaviour should be made uniform as much as possible and the cases where this is impossible (or would be highly counter-productive) listed explicitly in the docs.


    Anyway, I think the major reason for handling in a script an interaction-with-object event is to prevent it from being carried out; if it is not possible to block it, having the handler looses much of its importance.

  • when in the work-flow event handlers are called? Even assuming somewhere along the flow there is difference between events started server-side (e.g., an NPC spawn) and events started client-side (e.g.: opening a door), are the handler called before the action is carried out, after it or independently (i.e. asynchronously / in different threads)?

    Only serverside events are taken into account. Some clientside interactions like opening the crafting menu, for example, have no communication with the server. Usually the communication only happens when the player actually wants to change something in the world (open a door), or does something that the server has to check (craft an item), or does anything that has an visual effect for other players (change the item in your hands).


    Usually the API events are called before the actual action, so it's possible to cancel the event. Sometimes events can't be cancelled: Some events can't be cancelled for logical reasons (obviously cancelling the "PlayerDisconnectEvent" is not possible), some other events can't be cancelled since they event is called after the actual action (e.g. the new "PlayerKeyEvent").


    The old PlayerQuickslotChange event in the Lua API was an exception: The player does not send a notification to the server when he changes his quickslot (i.e. scrolls with the mouse), otherwise a player could spam the server with packets by torturing his mouse wheel.
    Actually the quickslot is updated with every player sync packet, and if the server sees a difference to the previous state, the event is triggered (of course no chance to cancel it). In this case the event just serves as a "notification".
    If the event would be cancellable (i.e. the player sends a message to the server when he changes the quickslot), the player actually has to wait for the response of the server. This delays the scrolling (e.g. player scrolls but the game only reacts after 0.5 seconds)...
    Is this event needed? Since it steps out of line I would remove it eventually (unless someone needs it).


    Right now every event offers the setCancelled(boolean); method, but I guess it's more convenient to have an interface which is only implemented by certain events (by all events that are actually cancellable). This way you can see if the event it cancellable or not ;)

  • Only serverside events are taken into account. [...]

    Understood.

    Quote

    Some events can't be cancelled for logical reasons (obviously cancelling the "PlayerDisconnectEvent" is not possible), some other events can't be cancelled since they event is called after the actual action (e.g. the new "PlayerKeyEvent").

    Agreed of course on "PlayerDisconnectEvent" ;) . Not so sure about the "PlayerKeyEvent", though. Of course, it may happen only after the key is pressed, as all events are triggered after the event took place, but what about being handled before the consequent action is carried out?


    A practical example could be an elevator plug-in I'm porting from my existing LUA script: I would like to use the number keys [0]-[9] to select the destination floor (at least for the first 10 floors) but, after processing the key, if the user is found to be in an elevator, I would like to block the usual behaviour of keys [1]-[5] (quick slot change), cancelling the event. So, after the key press event itself obviously, but before the key press is processed by the system.


    Quote

    The old PlayerQuickslotChange event in the Lua API was an exception[...].

    Understood. My example above notwithstanding, this event in itself seem rather secondary...


    Quote

    Right now every event offers the setCancelled(boolean); method, but I guess it's more convenient to have an interface which is only implemented by certain events (by all events that are actually cancellable). This way you can see if the event it cancellable or not ;)

    Yes, this makes a lot of sense. Thanks for your reply, enlightening as they always are! :thumbsup:

  • Not so sure about the "PlayerKeyEvent", though. Of course, it may happen only after the key is pressed, as all events are triggered after the event took place, but what about being handled before the consequent action is carried out?

    I know what you mean, but the "PlayerKeyEvent" is an API exclusive event, so nothing happens after this event was called. Unfortunately it is not possible to "cancel" any clientside processing of the keys. Since the API still works serverside, it would require the client to send all key inputs to the server first, and wait for the response. The result would be a very clunky and irresponsive behaviour of the game (for example, every movement key would have a delay depending on your ping, but even a 0.5 seconds delay would make the game more or less "unplayable") :S


    This "player has to send a packet to the server first and wait for the response before processing the input" problem can be avoided once clientside plugins are possible. However, this has a very low priority at the moment, and we're not even sure if we really implement something like this in the long run (since clientside plugins are executed on the client, a malicious plugin could cause a lot of harm)...


    A practical example could be an elevator plug-in I'm porting from my existing LUA script: I would like to use the number keys [0]-[9] to select the destination floor (at least for the first 10 floors) but, after processing the key, if the user is found to be in an elevator, I would like to block the usual behaviour of keys [1]-[5] (quick slot change)

    Probably it makes sense to have a method to "block" certain key inputs. Of course the "PlayerKeyEvent" would still be called, but the default clientside key inputs won't be processed anymore. Maybe something like player.disableClientsideKeys(KeyInput.KEY_1, KeyInput.KEY_2, KeyInput.KEY_3, KeyInput.KEY_4, KeyInput.KEY_5);.
    This approach unfortunately does not take possible clientside changes of the keybindings into account, so maybe it could use the keybinding from the config.properties file, i.e. something like: player.disableClientsideInput("input_1", "input_2", "input_3", "input_4", "input_5");
    Need some time to think about it :D


    More specifically your elevator plugin could block/disable the clientside input (for keys 1-5) when stepping into the elevator (and just listen for the PlayerKeyEvent, which will still be processed), and re-enable the input when the player leaves the elevator (or even while the elevator is moving).


    PS: When it comes to the elevator plugin, I highly recommend to have a look at the player.moveTo() method (a similar method will be added for world elements, like models btw) ;)

  • Your explanations make much sense, thanks.


    However, dealing with keystrokes in some specific / special way from within plug-ins might be rather important and it seems reasonable to get as right as possible since the beginning. One aspect coming to my mind is interaction with GuiElements (a generalisation of my elevator example). Other may likely exist.


    GuiElements might be a primary way to display choices and actions to the player and collect selections from him, replacing the chat command system, which was a God sent so far, but it is a bit clumsy and requires the player to know what to type. As normally there is no mouse cursor in RW, selection in a GUIPanel can only be carried out with keystrokes, which however, when directed to the GUIPanel, should not trigger the 'usual' action (selecting a "{S}ave" button in the panel with {S} should not move backward).


    I do not have a solution yet, your suggestion about player.disableClientsideKeys() seems on the right track, and other solutions may well exist. For instance, a GuiPanel or a GuiLabel may declare its own short-cut(s) 'automatically' disabling any 'normal' function that keystroke(s) may have (if they have none, then there is nothing to care about!).



    Customised key bindings does not seem to me an issue: the greater problem I see is to disable -- at certain times -- the normal function of a given key, if any; but in practice it should be disabled unconditionally, regardless it currently has a binding or not, or it had a default binding which has been changed to another key, or it had NOT a default binding and now has a custom binding.


    If a plug-in needs the player to perform a certain action (say, to achieve some goal or quest), it would not bother about key bindings at all, as it should try to detect that the goal has been reached, rather than monitor a certain sequence of key strokes, which would be very aleatory at best (what about the [MOVE_FORWARD] being kept pressed for 5 secs? How many individual [MOVE_FORWARD] key presses this implies?). So, key bindings should not be very relevant in such a case too.

  • However, dealing with keystrokes in some specific / special way from within plug-ins might be rather important and it seems reasonable to get as right as possible since the beginning

    In general the only options are indeed either clientside scripts, or the client has to ask the server first every time it wants to process a key input. While the first one isn't an option right now, the second one is unfortunately never an option... :(


    GuiElements might be a primary way to display choices and actions to the player and collect selections from him, replacing the chat command system, which was a God sent so far, but it is a bit clumsy and requires the player to know what to type. As normally there is no mouse cursor in RW, selection in a GUIPanel can only be carried out with keystrokes, which however, when directed to the GUIPanel, should not trigger the 'usual' action (selecting a "{S}ave" button in the panel with {S} should not move backward).

    Right now you can't create "modal" gui elements (so any panel, label or image you create will simply be attached to the hud, without an option to interact with it). However, it's our intention to create something like a "modal window" element, once it's displayed on the player's screen, he is forced to interact with it. At the same time no other keys will work (with the exception of the ESC key), but the PlayerKeyEvent will still be triggered (and since the API has full control over the window, you could listen to any key you want, and close the windows whenever you want).
    Of course it will also be possible to enable the mouse cursor then, so it's up to you to decide if the player has to interact with the window by pressing certain keys, or if he can actually press buttons or any elements on the window.
    Modal windows will be implemented in the medium run, they're not too far away, but right now we need to concentrate on other things first ^^


    Customised key bindings does not seem to me an issue

    Yeah, my mistake, of course you define a certain key in your plugin (e.g. key E or Enter or whatever) and it doesn't matter what keybindings the player has, you simply want to disable key E or Enter etc, no matter if it's used for something else or not.


    what about the [MOVE_FORWARD] being kept pressed for 5 secs? How many individual [MOVE_FORWARD] key presses this implies?

    If the API listens for the "move forward" key (it can't listen for certain keybindings, just for explicit keys, so in this case probably the W key), the PlayerKeyEvent will be called twice: the first time the isPressed() function returns true, the second time (after 5 seconds) it returns false ;)

  • I am worried! Do you never sleep? Please take care of yourself, for the sake of yourself, of your beloved... and of this community: we need you! And in good shape! ^^


    Modal GUI elements will certainly be useful, but have their own complexities and, reasonably, they are not a top priority now. So, they will come when (and if) they will come.


    But not all plug-in GUI interaction with users needs to be modal; in fact, most of the applications I have in mind would work equally well (some even better) with modeless "dialogue boxes" (well, kind of...).


    Possibly I didn't express myself clearly, but I understand and agree with you that a full client-server-client round trip for each key stroke would make the game unplayable.


    This is why something along the lines of your player.disableClientsideKeys() suggestion could do the job, relatively simply and elegantly: just one server-client message when displaying the "dialogue box" and one when hiding it. For the rest, key strokes would still be processed locally, with the added (local!) check if a particular key is currently disabled or not, which shouldn't involve a big overhead.



    Then, whether the disabling is achieved via a specific Player method or via a specific GuiElement property or via whatever other mean are implementation details whose overall convenience is better decided by whom has the full picture of the whole application.



    Thanks!

  • I am worried! Do you never sleep?

    Hehe, sleep is overrated :D But thanks for caring about me :saint:


    But not all plug-in GUI interaction with users needs to be modal; in fact, most of the applications I have in mind would work equally well (some even better) with modeless "dialogue boxes" (well, kind of...).

    At least if mouse input is required a modal element would be useful. So the player can't mess up the gui by opening up the inventory at the same time etc.^^ But of course it's also possible (basically that's already possible, with the exception that you can't disable keys yet) to just have a panel which displays some options or instructions (i.e. "Press E to enable the elevator"). In this case it would be up to the plugin to decide when a key input actually has an effect (i.e. only when standing at a certain position).

  • But of course it's also possible (basically that's already possible, with the exception that you can't disable keys yet) to just have a panel which displays some options or instructions.

    Exactly! That "exception" is (was?) the only missing detail of the picture. This is why I am insisting so much on it.. :S


    I noticed that the new API version as new Player.is/setListenForKeyInput methods: I can speculate that, normally, listening is off and key strokes can only be examined after the fact and not cancelled; while, by turning listening on, once key strokes are forwarded to the server, they can be cancelled at the expenses of a slower reaction time. Near?

  • I can speculate that, normally, listening is off and key strokes can only be examined after the fact and not cancelled; while, by turning listening on, once key strokes are forwarded to the server, they can be cancelled at the expenses of a slower reaction time. Near?

    It just determines if key inputs (only keys which were registered by calling Player.registerKeys(int... keys)) should be forwarded to the server or not. This way you can register all keys your plugin needs on startup, and enable/disable listening only when needed (in order to save some traffic, so the player does not send any unnecessary key input packets to the server).
    Having the ability to cancel key inputs (i.e. as mentioned before, each input has to be verified by the server first) is unfortunately no option. Of course one could say "let the plugin decided", but even on a fast server with a good ping you get a noticeable delay (especially the input the player expects a direct response, like movement, scrolling etc). In the end, players will blame us and complain about "all my input is delayed" =O
    The most performant option is probably indeed an Player.disableClientsideKeys() method^^

  • Yes, I understood the delays involved by the client-server-client round trip.


    But in fact such a round trip is mostly unnecessary: if a plug-in needs to monitor a certain set of keys for its own purposes, it is practically certain that those keys should not carry out the in-game actions bound to them.


    Is this what you mean by Player.disableClientSideKeys() ?


    So, there would be 3 methods:
    1) Player.registerKeys(....) to list the relevant keys
    2) Player.setListenForKeyInput(true|false) to activate|deactivate forwarding of those keys to the server
    3) Player.disableClientSideKeys(true|false?) to disable|enable processing of those keys.


    I wonder if 3) could be already implicit in 2), though (or even both 3) and 2) implicit in 1)? ).


    This would both avoid any unnecessary forwarding (less traffic) and make sure that only for registered keys there would be a delay due to the round trip, which is unavoidable, as the server-side plug-in needs to be notified of those key presses and perform any consequent action (better performance).


    Would this be a reasonable middle ground?

  • if a plug-in needs to monitor a certain set of keys for its own purposes, it is practically certain that those keys should not carry out the in-game actions bound to them.

    No doubt, but it's important to have an efficient way to do that without too many disadvantages (or uncomfortable behaviour for the player).


    Is this what you mean by Player.disableClientSideKeys() ?

    Yes, exactly. For example, if you call player.disableClientSideKeys(KeyInput.KEY_W, KeyInput.KEY_A, KeyInput.KEY_S, KeyInput.KEY_D), the W, A, S and D keys will be disabled on the clientside (probably most people use these keys for player movement, so in this case, the player would be unable to move his character anymore). Or if you call player.disableClientSideKeys(KeyInput.KEY_I), the player can't press I anymore (or more precisely, it has no effect, e.g. if the player uses this key to bring up his inventory, he can't do that anymore). This state lasts until you call player.enableClientsideKeys() or something like that.
    However, the "PlayerKeyEvent" in the API will always be called, there is no way to disable that (with the exception of unregistering keys, or by setting the "listenForKeyInput" to false).


    This would both avoid any unnecessary forwarding (less traffic)

    I guess this way we can already expect the least amount of traffic. Calling player.setListenForKeyInput() is very "cheap" (just a few bytes the server has to send to the client), however, it's not necessary at all. It is only useful if your plugin registers a lot of keys (or some keys which are frequently pressed, like WASD etc), but only need them in certain situations - this way you can enable or disable the "listenForKeyInput" when needed, so the player does not always spam the server with his key inputs (every single key input is more "expensive" [still quite cheap] than a "setListenForKeyInput" packet) ;)

Participate now!

Don’t have an account yet? Create a new account now and be part of our community!