Thursday, June 4, 2009

User inputs

I had mentioned previously that players have two inputs to their computer, and thus to Metaplace -- the keyboard and the mouse. Fellow beta tester KStarfire pointed out that I missed the joystick/gamepad. Does Flash support a joystick? I'm not sure, but I'll concede the point to him.

Metaplace uses a single method, the MakeInput() function, to define all of the inputs that a world understands. These are parsed at compile time to ensure they're valid, and passed to the client as a bunch of tags to tell it which inputs it should bother to send to the server. Here's its definition:

MakeInput(input description, input code, input event, input modifier, command string)

Where input description is something like "press I to open the inventory", the input code is "i", the input event is "down", the input modifier is "control" and the command string is "inventory".

Keyboard

Whether you're going old-school RPG with arrow keys or WASD movement, or just need a few keys to support inventory and status windows, the keyboard is a must. MakeInput() lets us define an event - that is, send a Command - upon a specific key press (up or down) with a modifier (shift, alt, none) for every need we have. Some of the keys have bigger "codes" than the letter they represent, such as "left" for the left-arrow key. This means we can do one-step-at-a-time walking, by just listening for "down" events for our arrow keys, or we can do walk-until-I-let-go walking, where we start walking on "down" event, and stop on "up". This sounds like everything we could want, but there are some limitations.

There are a bunch of keys that aren't supported. I won't spell them out here, but there's a bug filed (by me) in the Metaplace forums for certain punctuation keys that you just aren't able to listen for. This is problematic for me in my Ultima Online world, where I want to emulate the speech system of UO, where there's no chat box in which you click to start typing. This means that my current implementation (which you can try out) lets you type letters, numbers and a space, but none of your punctuation comes through, which makes you type like a ... well, like most of the people on the internet.

My UO world also points out another issue: to get this working, I had to make 26 lines of MakeInput() to handle each letter; another 26 for when I press shift (to get a capital version); 10 more for the digits; space, enter... that's 64 right there. If the punctuation was working, I'd have even more. And what if I wanted to catch every keypress possible? UO allowed you to map keypresses to macros, such as "control-e" for the allnames macro (which happens to work in my UO world, if you want to try it.) This means four modifiers (none, shift, control, alt), two events (down and up), and ... 100+ keys? That's 800 MakeInput() lines! In my UO world, I only have half of them (I wasn't interested in key-up events). Rest assured that I wrote a program to write those all out for me. The problem that this points out: there's no way to say "any" for the "input code", to say "for any keypress, send this command" or "for any shift-keyrelease, send this command".

Another problem that arises is that a given code/event/modifier combination can only have one possible Command that it will send -- you can't have "i" both bring up your inventory and cast the Invisibility spell. If you define a MakeInput() more than once for the same combination, it used to be the case that the client used a random one. Now it looks like the last-defined one -- the one in the script loaded last -- is the one that wins.

For a user creating their world completely from scratch, this shouldn't be a problem - they know what they want each key to do, and aren't likely to re-define the same combination again for another purpose (though I'll come back to this). The problem is more likely to appear when the Marketplace becomes involved, when a world-builder buys off-the-shelf modules that define MakeInput()s. As more and more content becomes available, the chances of these modules colliding is going to grow.

This has already occurred. KStarfire had a world where the spacebar was the "throw" command; after he had created that functionality, the avatar module -- probably the most ubiquitous module in Metaplace -- added a jump action to the avatars. And what key did they choose for that? That's right, the spacebar.

KStarfire, at that point, had two choices: either edit the avatar module, change the MakeInput() line where the jump was defined, and have to do this every time the module updated; or change his own code. But what if the "throw" command was one that he had purchased, instead of written himself? Another solution would be to allow the keys for every module to be defined by the user or world-builder, to have the MakeInput() commands pull their values from a user or template property. Unfortunately, the MakeInput() command doesn't allow this (future blog post).

So is there a solution? I think so. I think that a module needs to be developed that defines every single combination in MakeInput() and sends a single command from the client to the server when it happens. This Command would then fire a Trigger to the the user object, and at that point, any interested parties could listen for the Trigger, and based on configuration, decide if that matters to them. So the avatar system could allow the user, or world admin, to say that "control-spacebar is jump" and "spacebar is throw", and each module would handle things the way they should. Additionally, modules could, if they wished, share the same keypress.

I believe in this solution so much, in fact, that I've implemented it. Also, I was able to work around the limited environment of the Define Commands() block to come up with this little gem to save myself from typing an enormous list of MakeInput()s:


keys={"numpad0","numpad1","numpad2","numpad3","numpad4","numpad5","numpad6","numpad7","numpad8","numpad9",
"f1","f2","f3","f4","f5","f6","f7","f8","f9","f10","f11","f12",
"backspace","tab","return","pause","capslock","escape","space","pageup","pagedown",
"end","home","left","up","right","down","print","printscrn","insert","delete","help","numlock","scroll",
'-','=','\\','.','/','0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i',
'j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
"shift","control","alt","lshift","rshift","lcontrol","rcontrol"}
states={"down","up"}
modifiers={"none","shift","control","alt"}

for x=1,#keys do
key=keys[x]
for y=1,#states do
state=states[y]
for z=1,#modifiers do
modifier=modifiers[z]
MakeInput('keypress '..modifier..'-'..key..' '..state,key,state,modifier,(state=='up' and 'un' or '')..'key '..modifier..' '..key)
end
end
end


This generates every possible MakeInput() that Metaplace supports right now (with all of the missing punctation). It fires a Trigger key(modifier,code) for key down, and unkey(modifier,code) for key up. Now I can have code such as


Trigger key(modifier,code)
if code==self.jumpkey and code==self.jumpmodifier then
SendTo(self,"jump")
end
end


or even have a way to encode the modifier and code into a single value so there aren't two values to store and compare, and have a comparison function:


Trigger key(modifier,code)
if self.jumpkey==keypair(modifier,code) then
SendTo(self,"jump")
end
end


I really think that this approach is going to be inevitable, because the colliding modules is going to happen more and more, and the need for customizable keypresses, either to prevent collisions or to allow flexible worldbuilding (not to mention, to be able to support key macros) is going to be in demand. And, as I hinted at before, we might want to change the meaning of a key halfway through gameplay; for instance, I might want "i" to open the inventory in a non-combat mode, but when I'm in combat mode, "i" might cast the Invisibility spell. This could be one Trigger that handles the keypress, that understands both modes, or two separate Triggers (in different modules) that each handle a specific case, unaware (and uncaring) that the other exists.

I have one last thing that I'd like to see support for -- held-down keys generating repeated key-presses, much as we're used to seeing on computers when we hold down a key in our word processor. Some world might need this functionality (such as my UO speech), but others probably won't; once I add this as an option, I'll publish the above code as a small module for universal key handling.

One last thing about the keyboard: even with the Flash client in focus, some browsers out there rudely capture keypresses instead of passing them on to Metaplace. Specifically, the "control-c" in UO is meant to refresh your mouse cursor (there's a bug there I have yet to file), but in Internet Explorer 8, this brings up a menu or something. I've seen this on some of the other browsers, with certain keypresses, whether it toggles bookmarks or who-knows-what. This is an unfortunate issue that world- and module-builders will have to keep in mind -- the browser compatibility issues reach further than the web page!

Mouse

The mouse is a must in today's computing, giving us near-instant pointing and selection. Very few worlds are likely to get by without its use, largely in part to the graphical nature of the worlds (plain worlds such as Plain Old Chat.)

Unlike the keyboard, there are only a few different input codes available for the mouse:


  • mouse-terrain -- clicking on the ground (on a tile)
  • mouse-object -- clicking on an object


"click" and "double-click" are the only two events supported. Embarrassingly, I've never tried the "double-click" event, since the only one of my worlds that needs it (UO) was created before this event was added, and I ended up writing my own (in a similar manner to how I'll write the repeating key code). All of the modifiers are supported (none, shift, control, alt).

There are lots of things missing here. There's no way to distinguish where the mouse-click went down, and where it was released (I believe the location of release is what is sent to the server). This is one that I think could add some extensibility to a world's interface, and wouldn't increase the traffic at all.

Having the mouse location, either while holding down the mouse button or not, would of course be valuable. This would let us have draggind-and-dropping, of handling mouse-over/hover (future blog post) very easily, and to have the mouse cursor itself act as an agent in the game, as the avatar itself. We're told that the traffic between the client and server would be too large to allow this feature. I can certainly see that there *could* be a lot of traffic, depending on how often you sent the mouse data. But I propose that this is a setting we should be able to send to the client,

SetMouseLocationUpdateTime()

as well as provide a way to enable and disable the flow of this data, so worlds that need it can request it:

StartMouseLocationUpdates()
StopMouseLocationUpdates()

And of course, the client could have a hardcoded maximum of tags being sent in.


And last but not least, there's the fact that all mouse events currently represent the left-mouse-button only. No right click (nor middle click, nor sideclick?). This is a Flash problem, as I mentioned in the post on movement, but indeed a problem. As I had said, Ultima Online used right-mouse-hold (or right-mouse-doubleclick) for movement, and everyone associates right-clicking with a context menu. Other clients (a future blog post) could implement this support, of course, but I think it unlikely that we're going to see support in the Flash client. And, of course, even if other clients COULD support it, there's no way for the world to state that it's interested, because it's not an option in MakeInput().

So for now, workarounds need to be made. Luckily (perhaps oddly), double-left-click on terrain had no meaning in Ultima Online, so I've used that as my walk-to command (I use single-left-click as a "look" command.) As for workarounds for other missing things, I use ctrl-left-click as pickup/drop (instead of drag-and-drop). Though I also have a little hack for this, too, which I'll mention ... in a future post. Shift-left-click is, I believe, used to bring up the Behavior Tool, which acts in place of a right-click for a context menu, and the current avatar behaviour seems to go with single-left-click for bringing up a context menu on other avatars (for meeping, sending friend requests, offering gameplay, etc.)

I can also see a need for a mouse equivalent to the "every key" module I mentioned above, letting world owners define the mouse meaning they desire to various actions: if I want single click to be "look", and double click to be use, I should be able to set that instead of be forced to use whatever the module-writer decided.


Overall, I'm a bit disappointed with the mouse support, because it's really the primary interface for many. The modifiers can help us for now, but I really hope that, once all of the bigger pieces of Metaplace get done, the mouse support can be revisited.

Joystick?

Is this so far-fetched? Not really; I have no idea if Flash can support joysticks, or gamepads, but custom clients sure can, but we still hit the problem of having no support for them in the MakeInput() call. And this brings up something that has stuck in craw for a bit: the special nature of MakeInput().

I had planned on talking about this in a future post, but I really don't understand why MakeInput() must be in the Define Commands() block. Or at least, why it must ONLY be in that block. I can see a desire to precompile this, to ensure that the definition is done correctly. Or can I? Every other API call checks this at runtime, not at compile time, and lets the world owner know in the Debug or Log window, and lets the user know by just not working. Why can't MakeInput do the same thing? Why can't I call MakeInput() later in my script, after asking the user to define some keys, of after the user's preferences have loaded? Why can't we call DeleteInput() -- there's a tag ([W_DELINPUT]) that exists for when a script is unloaded, which removes a defined input, so doing so after the world is running, and a user is connected, is certainly possible. Why can't we add inputs after we're up and running?

Maybe there's a good reason, maybe it goes against the design of the system, or maybe this was just oversight. Some day I hope to know! Until then, though, I hope my module will be a viable workaround, or that the collision of inputs doesn't become too big of a problem, too soon.

6 comments:

  1. Alo Crwth! This is Jellee :)

    A lot of what you spoke about here is something I've pondered about Metaplace from the start. The interface does indeed have a lot of limitations. Some of those are due to the Flash interface (although there must be some way of disabling the Flash right-click menu, surely?) I know right-click functionality can be added to a web-page from Javascript using this code, not sure how to implement that and then have the flash client support right-click though (I'll leave that to you clever folk :D):

    // Right click functionality
    function doSomething(e) {
    var rightclick;
    if (!e) var e = window.event;
    if (e.which)
    {
    rightclick = (e.which == 3);
    }
    else if (e.button)
    {
    rightclick = (e.button == 2);
    }
    if(rightclick)
    {
    // Prevent right-click browser menu
    e.preventDefault();
    e.stopPropagation();

    // INSERT RIGHT CLICK FUNCTIONALITY HERE

    // END RIGHT CLICK FUNCTIONALITY
    return false;
    }
    else
    {
    return false;
    }
    }

    Anyway, the mouse could really have some welcome inclusions. For example, I'm looking at making a space invaders-type world, which would really work well with a mouse-move control system, where moving the mouse to the left or right moves the spaceship without you needing to click anywhere. This could be implemented as you say by having the engine pick up the position of the mouse, but it could work kinda like Flow does, where it only takes the position of the mouse after a given interval (which I think you alluded to above). If you haven't seen Flow, you can see it here: http://intihuatani.usc.edu/cloud/flowing/

    I think this would be sufficient, and it would be a lot less server-intensive as well.

    ReplyDelete
  2. I was thinking about what you said about having a key do a different thing depending on the player's situation (the 'i' key when in non-combat mode was your example I think).

    Some way of recording the current game state would make this possible. You could have a 'non-combat' state and then have a trigger call in the relevant command for each of the functions that will fire with that key. Then a simple line of code that checked the current game state before continuing added to that trigger. Perhaps that's a bit inefficient, but that's the gist anyway.

    ReplyDelete
  3. This solution - having the Command check the state - means that the single Command implementation would have to know about both possibilities; in the case of people collaborating to develop modules for an RPG system, one person might write a spellcasting module, the other the inventory system, but both might end up using "i" as a key, and their two modules won't be able to nicely share the MakeInput()'s Command.

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. Sorry for the delete - don't know how to edit :S

    The solution I have is essentially the same as yours, except the state of the game is stored in its own separate module. Then the trigger calls all of the modules that would use that combination of keypresses and modifiers.

    So instead of SendTo(self, "jump"), you would have:
    SendTo(self, "jump")
    SendTo(self, "open_inventory")
    SendTo(self, "cast_spell")

    and then in each of the triggers have:

    Trigger jump()
    if(game.state == "running")
    DO CODE
    end
    end

    Trigger open_inventory()
    if(game.state == "standing")
    DO CODE
    end
    end

    Trigger cast_spell()
    if(game.state == "combat")
    DO CODE
    end
    end

    ReplyDelete
  6. Yes, this works fine if the multiple scripts are authored by the same person (as in your case) or by a group of people who agree on a convention (as we're trying to do http://alpha.metaplace.com/forums/topics/listing/52 <-- here), but it doesn't fit with the free-form everyone-for-themselves nature of the Metaplace Marketplace right now.

    I think we're just fortunate that most modules on the Marketplace aren't Input-based (they're art assets, or behaviours) and that those modules that DO define Inputs are mostly ones written by the Metaplace Content Team. But since these don't suit everyone, others will eventually come, and inevitably collide.

    ReplyDelete