Showing posts with label workarounds. Show all posts
Showing posts with label workarounds. Show all posts

Monday, June 29, 2009

OutputToUser()

Back from vacation, and it looks like our last server release has lots of goodies in it. One of the best ones, in my opinion, is "Added support for per-user camera control from script." It's the "per-user" part that makes me happy. I have mentioned before the loss of the OutputToUser() function, which was the ultimate way to do per-user effects, so anything that adds them back is welcome indeed.

MetaMarkup

The Metaplace servers, and specifically, a Metaplace world, communicates with a user's client by way of MetaMarkup tags, also occasionally called "game markup language." These are plain-text messages such as these samples pulled from my time in the PlainOldChat world:

[O_HERE]|10013|0:308|5|4|0|0|Crwth|0:1|0| |0
[P_ZOOM]|1.000000
[W_SPRITE]|24080:135|0.375|0.140625|255|255|255|http://assets.metaplace.com/worlds/0/24/24079/assets/images/dwnbtnpress.png|dwnbtnpresspng_|2|0|.|4|0|0
[S_CONFIRM]|Sprite data fetched successfully.
[UI_CAPABILITY]|2165|drag
[INV_GOLD]|241|13829|fc239cc9bf4ef7e148aae958c258bbb4|25|284503

The first part in the [brackets] defines the type of the tag, and the rest of the data, separated by |pipes| make up the parameters of the tag. If you're curious, you can visit any Metaplace world in which you're an administrator and go to Advanced|Debug and click on the "log" tab; perhaps uncheck the "commands" box as well. Everything that the client needs to know -- appearance/disappearance of objects, movement of objects, UI popping in and out -- comes through here.

As a world-builder, we have control over most of what comes through by using the API in scripts attached to objects, or by just building our world with the tools. Thus, calling CreateObject() will make a [O_HERE] tag appear (to most people - see below), and anything UI-based will give you one or more [UI_ ...] type tags.

OutputToUser() let you "hand-craft" these tags, such as

OutputToUser(self,"[P_ZOOM]|"..self.myzoom)

or

for _,user in ipairs(GetUsersInPlace()) do
if user.cansee then
OutputToUser(user,"[O_HERE]|"..getHereParams(self))
end
end

Why is this useful? Right now, the Metaplace API and system is setup mainly to support the idea of a shared-world view. If you go to Metaplace Central, everyone gets to see the same tiles on the ground, the same stationary objects, and see the same look for everyone they encounter. But what if this isn't what you want? Two types of games that easily come to mind, where players should have different views, are RPGs (Role Playing Games) and RTSes (Real Time Strategy). Both of these can have requirements that certain players have different/extra knowledge about the game world than others. With OutputToUser(), you could, with some work, code this up any way you wanted. Now, you're at the mercy of what the API permits.

UI

So what DOES the API permit, on a per-user basis? Well from the beginning, UI has always been able to be done per-user, which I've mentioned previously. This makes sense, as support for things such as pop-up dialogs are important pretty much anywhere, and just because I'm being asked "are you sure?" doesn't mean that everyone else should also see that message. So from day one (at least, my day one in beta) we've had per-user UI, so there was no need to use OutputToUser() for it. Right?

Perhaps, but I can actually think of reasons you might want to hand-craft the [UI_...] tags; sometimes it's easier to have strings premade which have drop-in values (using string formatting) or to send a variable amount of UI commands based on computations and iteration through tables instead of a bunch of conditional code. Fellow tester LunarRaid, as I recall, was doing something fancy with OutputToUser() and changing the art of UI elements.

Place Settings

There are a bunch of MetaMarkup tags that tell the user's client about the Place they're in, such as the location of the camera, the zoom level, and the type of View (top-down, isomorphic, etc.) Some of these, such as the View, probably make sense as a shared, universal settings for all users (though, I did hand-craft [P_VIEW] tags in a world to allow visitors to change the View -- for themselves alone -- to see how certain code behaved in different Views). Others, though, might have legitimate need to be different between users.

I mentioned in a previous post that certain calculations depended on knowing what the zoom level was of a Place, and thus it was strongly suggested that the zoom was locked, preventing the user from scrolling with the mouse wheel. But if you could set the zoom level, per user, you could not only override any mouse wheel zooming by forcing the zoom every second, half-second or quarter-second, but you could also provide a little zoom bar with which the player can legitimately change their zoom level in a way that code that depends on it will know (zooming with the mouse wheel is all client-side, so nothing gets sent to the server, and thus scripts can't know that it has been done.)

Playing with the camera can also provide some interesting effects: right now, the camera is usually locked to a user's avatar, or locked to a given location in the world. While we've had a MoveCamera() function for a long time, to change that location-based camera position, we never had the flexibility to change the camera behaviour between the two on a per-user basis. (Locking the camera to the user, and hiding the user, allows for interesting effects such as my follow camera experiment, based on a discussion with LunarRaid; he recently requested some new functionality that I would also like, as can be seen by the jerkiness here.)

One concept that can play a big part in RTSes is the "fog of war", where the map is unknown to you until you've been there, and even afterwards, parts of the map that you don't currently see can change - often, RTSes would have these "old" areas greyed out, with the scene as last seen. Of course, "what you see" changes from each player's point-of-view, and this includes the tiles themselves; my world BITN (currently suffering from an art issue) was a testbed for such tricks, where the world was dark except for a few lightsources or the special ability of the user to see in the dark, and thus tiles were revealed based on user-specific data.


This last server release has given us some of the functionality that I used to have relating to Place settings, but there are still some calls that we could use...

Objects

Along with the selective viewing of tiles in BITN was also the idea of selective views of objects. In fantasy RPGs, you might have spells such as Invisibility, Illusion or Polymorph, which change your outward appearance. These on their own are easy enough to implement, by changing the player's sprite. But such games might also have the idea of different "vision" types (perhaps granted by other spells, perhaps innate to the player's character's race), such as See Invisibility, See Illusion, or True Sight (which might see through all of these tricks).

Changing the player sprite is a good solution for Invisibility or Illusion if everyone is affected (although it could even be argued that the player of the invisible or illusionary character might want to see their true form), but as soon as we have different players who should see different things, the sprite-change method isn't suitable. When we were able to handcraft MetaMarkup, we could send different [O_SPRITE] tags to different users, depending on what they should see. This is exactly what BITN did, where I had all of the above spells and visions; if someone had cast illusion on their usual rogue form, they would appear as a fighter to the commonfolk, but anyone who had either See Illusion or True Sight would see the character as the rogue he or she really was.

So seeing an object differently can be useful. How about location? In the fog-of-war idea, the greyed out "old information" might show that there were some enemy units there, but they have since moved on since you last looked; on your screen, those objects should still be there, but for that enemy player, he sees them for where they truly are. Right now, you can't do this - as soon as you move an object, it moves -- there are no selective [O_HERE] tags based on whether or not you should have accurate knowledge of an object's location.

(I should point out that we do have a SetUserVisibility() function, which lets you set how far from a user objects can be seen - when objects leave this radius, either because they or the user moves, [O_GONE] tags are sent, even though some other user might still see them. It's a limited version of per-user visibility, which does solve some problems, but it only allows "see it or don't", not "see it here or see it there". There's also the gmVisible setting on templates, which set whether objects of this type can be seen by only administrators of a world, or by everyone -- again, it has it's uses (my camera marker module uses this functionality), but isn't a game play tool, just a game design tool.)

Look and location are just two examples of object settings that you might want to have different per-user; you can browse through the MetaMarkup page and look at each tag, perhaps coming up with all sorts of interesting game mechanics that could be implemented if only you could hand-craft how these tags were being sent to different users (just looking at the page now, I thought of having "x-ray specs" where you could have another player's avatar's clothing vanish, but only if you have the x-ray specs -- oo la la!)

The Solution

Of course, the easiest "solution" would be to just give back OutputToUser(). From what I can gather, the reason it was removed was to prevent malicious use; the last example MetaMarkup tag I showed above, [INV_GOLD], represents something "meta" from the gameworld you're in, something at the Metaplace level instead of the world level. Perhaps forging these, making people think that they got gold that they shouldn't, is the issue? Regardless of the reason, we've lost it.

So far, we've been getting new API calls to replace some of the most-often cited functionality of OutputToUser(). The per-user zoom is definitely a good one; we also recently got AddEffectForUser() added to the mix. And I can hope that, as long as I keep pestering/asking for the other users, we'll see the API calls appear.

A different solution, though, would be to still allow hand-crafting of tags, but perhaps only certain ones -- allow something like

OutputToUser(user,tag,params)

where I would call

OutputToUser(self,"O_HERE","10013|0:308|5|4|0|0|Crwth|0:1|0| |0")

and the function can decide if "O_HERE" is one I'm allowed to hand-craft. This would prevent me from faking INV_ tags, if that's the concern. Without knowing the full range of concerns regarding the old OutputToUser(), I don't know if this new one would be feasible. It would, however, be a one-stop solution to all of the other useful features lost and now pending API additions. Even if the goal is to have API functions for all of the imaginable per-user needs, something like this might be a nice temporary fix?

Monday, June 15, 2009

Happy accidents

One of the interesting things about working with Metaplace is that, with all of the different API calls and graphic effects, you can do all sorts of things -- some planned, some not!

For instance, the module that I just published today provided something like a "circle of transparency", which, from Ultima Online, would make objects that blocked your view of yourself transparent, so you could still see things near you on the ground, or that are attacking you, or just to see yourself for whatever reason. This is a client-side effect, where the client just clears out a circle of user-specified radius based on the objects it renders. Of course, I don't have access to the Metaplace client, nor does it have support for this, so I had to write a workaround, as I usually do.

An actual circle was out of the question (or is it? Hrm...), so instead, I went for a method of making any object whose sprite overlapped with the user's become invisible. This requires a lot of the screenspace/worldspace calculations that I've talked about. My original plan was to just make the sprite invisible to the user by changing the sprite, or through some sort of effect. As I was working on it, testing the algorithm for determining sprite overlap, I thought, "I'll just have it show a glow effect on the objects that it wants to turn transparent". This would be a temporary effect, just to help me visually see what the code wanted to hide. The effect didn't seem to be appearing on the objects it should, so I started messing around with it, changing values and effect types to see if the glow was perhaps broken. Once I figured out the issue (and I unfortunately cannot remember what it was), I had the effect set to the "bevel" effect, and had the object hiding itself.

Well, let me tell you, this effect was even better than I had planned. I had *planned* to just have the object vanish! But with the bevel effect, you can see through it (since I turned on the "hideObject" setting) but still get a sense of what it is you're seeing through, because the edges still have the bevel effect applied. Fantastic! If you want to see the effect, go take a stroll around my rotateplace world, stepping near the trees.

This lucky stumbling on a solution was a bit surprising, since I had previously made a demo world where you can play with all of the effects (I'll talk about them in a future post). But even after having all of that hands-on experience with bevelling and effects in general, I never thought of using it as a transparency system that including the "cloaking border", as fellow-tester Dalian described it.

This is what made me think of writing this post: there are all sorts of things that you can do in Metaplace, whether it's modules people have written to single lines of code, that you're able to put to uses that no one likely thought of. Once in a while, I've looked at the latest release notes, read about the latest functionality that was introduced, and said, "hey, not only can it do this, but you could use it to do THAT, too." But generally, I read about new functionality and think either "ahh, they finally added that feature we've been asking for", knowing what the original request was for, or, "hrm, they added this feature out of the blue; I wonder what the Metaplace content folks needed it for," and come up with a reasonable idea. But who knows what kind of interesting uses are still undiscovered, because we read about these features and just recognize the "intended" use?

And that brings up the other side: having a need for some feature, effect or functionality, and not having it... what do you do? Well, I kinda pride myself as the King of Workarounds, and I can usually find a way to do pretty much anything needed in Metaplace, even if there's nothing close to an obvious way to do it. The workaround may be ugly, may be laggy, may be considered illegal in thirteen states, but I can usually find one. And I think that, too, speaks to the beauty, the flexibility of the Metaplace platform: even if you can't do something, you can still do it.

Edit: I just realized that this post probably had me sounding like a huge Metaplace fanboy -- I didn't say anything bad! How's this: the best tool we had for workarounds, the OutputToUser() function, was taken from us. So there!