Moriash Moreau: My Second Life
Monday, July 31, 2006
Well, I broke down and added another monster to the menagerie. I'm going to have to be careful not to overload the system with too many different types. Otherwise, the players are going to spend their whole game shooting at blank gray panels while the multitudinous monster images download. But, I needed at least one more fiend to fill out the medium difficulty (2 point) monsters, so I went ahead and snuck in one more.
This time, it's a Banshee. (Thanks go out to Chrestomanci for the idea.) It's not terribly strong or fast, but its wail can be devastating at close range. Fortunately, after the groundwork on the Ankle Biter and the Old Soldier, this one came together fairly easily. Basically, it has the Soldier's movement routines (close to an arbitrary radius and orbit), with somewhat modified firing routines. Instead of using a physical carrier for the damage (i.e. a bullet), it uses the chat channel damage routines. As you may remember, when a monster "punches," it actually sends out a damage amount and the key of the recipient. So it was easy to generate a similar damage call with a longer range sensor.
As for damage, it reduces by range. I suppose it would have been more physically correct to drop damage off by inverse-square law, but in practice this left everything but point-blank screams at one point of damage. So, I settled for a simpler linear reduction. On average, screams do two to three points of damage.
The avatar isn't anything too fancy. Basically, it's a female avatar (yes, I now have token female representation in the ghoul parade) with pitch black skin, black robe, and glowing red eyes. Simple, but it seems to work. The blue aura was originally a screw-up in my color mask settings. I inadvertently left a bit of my makeshift Chroma Key stage around the edges in the first image attempt, and didn't notice it until after I had uploaded. But the effect added a little interest to the plain black silhouette, so I decided to enhance it instead of fixing it in the second version.
The picture above catches a Banshee in mid-scream. Unfortunately, I was experiencing a bit of lag at the time the picture was taken, so the particle effects didn't fully develop. Still, you get the idea: a cone of concentric rings expanding outward from the Banshee's mouth. The sound effects are, as you might expect, an assortment of screams. I went ahead and cut the volume on them a bit, mostly because they got on my nerves during development. Still, the incessant screeching provides plenty of incentive to mow them down with a volley or two of monster gun fire.
Friday, July 28, 2006
Ladies and gentlemen, FICs and furries, goths of all ages, I'm proud to announce the official beta release of the Garage of Doom! Just click on the Garden of Mo link up there on the right, ride the transport stool labeled "Garage of DOOM," and give it a go!
The basics should be pretty self-explanatory, and I've written some documentation, as well. Just touch the "IN CASE OF MONSTERS BREAK GLASS" box in the security room to get your HUD, gun, and docs. Then, once you've skimmed the instructions (they're not too long), don your HUD and gun, touch the double doors, and you're in. Have fun!
And when you're done, please let me know what you think. I've installed a simple answering machine in the security room. If you have any comments or suggestions, touch it and speak your mind. It'll record everything it hears for three minutes and send me the transcript.
Aside from any bugs you might notice, I'm mostly interested in your thoughts on the general game balance and playability. Just so I can get a feel for how to scale your comments, you might mention your previous experience with other First Person Shooters. I'm looking for things like:
- Monsters that are too hard or too easy, and why.
- General game difficulty and progression from easy to hard.
- Appearance rate and general power level of the various power-ups.
- Interface issues- intuitive enough, easy enough to use?
- And so on.
I'm happy to listen to any suggestions and ideas for future improvements to the game you might have, too. Just keep in mind the scope of the project. I'd love to have fully articulated 3-D animated prim models of the fiends, running through a multi-level, sim-spanning parking complex and shopping mall... But I only have about a 16th of a sim's resources to play with.
Oh, and I know it needs cars, thanks. Working on it.
I came home late last Wednesday night and, against my better judgment, logged into SL to put in some time on the Garage of DOOM. This was a mistake, at least from a practical RL standpoint, but I knew that going in. I was not a happy camper Thursday morning. And employers are seldom amused when you show up to meetings with keyboard-shaped sleep marks embedded in your cheek.
But, in terms of SL and the GoD, it was a very productive session. I love late nights like that, where all of the background processes that have been percolating in the back of my mind suddenly reach readout all at once. I live for those hours long domino trains of minor epiphanies. Okay, I don't live for them. But they're a hell of a lot of fun.
This particular little string of brainwaves started with the monster rezzing subsystem. As you may or may not remember (if you've had nothing better to do than read past weblog posts on this project), the monster distribution system is composed of a central server and a series of remote rezzers. The player's HUD calls out for a monster, the server hears it, and the player's key is sent to the rezzers. Then the rezzers are sent monsters to be rezzed and programmed with said player's key. The net result is some types of monsters come into existence knowing who they should pursue.
That's the way it used to work. Then a light bulb flickered on above my head, and I realized that a player has no way of telling if a monster was called by his HUD or by another player. And that's assuming it's a multi-player game to begin with. It doesn't matter at all if there's only one player on the field!
Each player's HUD calls for monsters every 6 seconds or so. Said call can result in one to three monsters, depending on how close the total monster count is to the maximum monster population built into the server. There is no outward sign to indicate whether Bob's HUD or Fred's HUD sent the call for a monster. All the players know is that more monsters have appeared. So why bother designating an initial target by remote? Just let the monsters who care about such things decide for themselves!
That chopped out a considerable amount of communications traffic and processing, and made the monster rezzing process a fair bit simpler. Bob's HUD calls for a monster. The monster server decides how many monsters to rez, and sends the monsters to randomly selected remote rezzers. The rezzer detects a change in inventory, and rezzes the monster that just arrived. Less pre-processing, fewer interim steps, and no extraneous hidden comm chatter required.
Once the monster is rezzed, it waits for the next roll call of active players to be sent out by the central server, then scans for appropriate targets. Then it goes after the nearest player. The only difference is that some monsters (such as Vampires, Rippers, and Wraiths) remember this first target selection, and goes after that player exclusively until one or the other is dead. Less decisive foes (Skeletons, Mummies, Zombies) continually check to see which player is closest. Either way, they function just fine without additional pre-programming from the central server.
This also sorted out one of the most maddening intermittent bugs in the game. For some reason I was never able to determine, an unknown transient coincidence of game events would sometimes cause the monster to hang while waiting for the initial programming mentioned above. Normally , the monster would come into play listening on a predetermined, random communications channel. If everything went well (something like 98% of the time) the rezzer would send the summoning player's key on this channel, and the monster would stop listening (thus removing a now-unnecessary llListen) and leap into action. (The channel had to be reselected each new rez, because multiple rezzers are in chat range of each other, and monsters could end up programmed by an adjacent message.) But, for some reason I was never able to fathom, every once in a while the monster missed this call-to-arms, or possibly shut down its programming channel prematurely, and just stand there staring at the walls. Not only did the bug make the monster a sitting duck, it tied up the rezzer (which checks for obstructions before rezzing) until the player got around to dispatching it. Not good.
Now, thought, the monster comes into play ready for action. It immediately starts scanning for nearby avatars, and checks their keys against the active player roll call list when it arrives (6 seconds between roll calls). And since the roll call list is continually updated, if said monster should for some reason miss the first one (lag effects, processing/setup required on initial rez), the worst thing that can happen is it stands still for a little longer before it starts stalking its prey. All in all, Wednesday's work was a major boon for the efficiency and stability of the game as a while.
And then, Thursday, I spent over an hour tracking down a stupid list processing bug in the high score server, before realizing that the entire subroutine I was debugging was more-or-less superfluous. Some days ya' got it, some days ya' don't.
Monday, July 24, 2006
I've been kind of taking it easy in SL recently. I started to see the early signs of burnout, and figured now would be as good a time as any to catch up on TiVo instead of squinting at my monitor.
Unfortunately, this meant that I missed all of the Relay for Life festivities. I'm a bit irked about that, as I'd planned on getting in on some aspect of it this time. As it is, I had to settle for lighting a dozen or so luminieres along the race course late last night. Given that my mother has had cancer three times in the last three decades (thyroid cancer when I was a kid, breast cancer my freshman year at college, uterine cancer earlier this year- the latter required no chemo or radiation after the surgery, thank God), that feels awfully piddly. I'm going to have to start putting aside some real money for the cause, and pronto. Throwing what amounts to fifty or sixty bucks US$ in at the last second feels pretty hollow and chintzy for such a personally important cause.
And that's really more than I intended to ever say about my personal life here.
In any case, I did finally get around to building the high score system for the GoD. As I mentioned way back whenever, that's just about the last piece of the puzzle. Mechanically, the Garage of DOOM is done. From here on, it's testing, balancing, tweaking, and decorating. Here's the obligatory screenshot.
Unfortunately, I'm still left with llSetText for the readout. This is unfortunate, but I can't afford the prims for an XyText display. Still, I don't think it looks too cheesy.
The names for the dummy scores are famous monster slayers from various sources. I'm sure you recognize most of them, but if not, here are the references. Not really relevant to anything, but it amused me, anyway. I was a little disappointed to find out that even IMDB doesn't list the last name of the protagonist in Shaun of the Dead. Otherwise, he'd be on the list, too.
Mechanically, the scoring system is somewhat convoluted. The main problem is that the garage is not a permanent structure. So, I had to store and process the scores
off-site, at ground level. Given that this places the score server over a kilometer away from the garage, this was a bit of a hurdle. In order to work around the distances involved, I had to resort to physical carriers to bring the information from place to place.
I suppose I could have futzed around with in-game e-mail, but I believe this method is both faster and more reliable in the long run. An out-of-game solution, like a website accessed through the new HTTP script calls, would probably have been more efficient. But, frankly, that's more work than I want to put into this minor aspect of the game. (Especially given my lack of experience in real-world web coding.) I seriously considered omitting the high scores system from the game altogether. It adds complication, and at its core it encourages people to cheat. (And I don't doubt there are many ways to cheat using outside scripts and objects. It's unavoidable.) Or, rather, it encourages people who are inclined to cheat to do so, anyway. There is little incentive to cheat at a game unless there is some outward sign of your feigned prowess. Otherwise, it would be as pointless as cheating at Solitaire.
In any case, I settled on using physical carriers to bring the scores back and forth. Each player death now launches a chain of events as follows:
- Take the final score and add the time bonus (one point per 10 seconds).
- Notify the player of his final score, and give him instructions on how to exit the garage.
- Rez a single-use teleporter device above the player. Said device either revives the player in a random location in the garage (within the first 10 seconds after death) or takes him back to the security office/entry area (after 10 seconds have passed).
- Rez an invisible "rocket" to carry the final score down to the score server.
- The rocket travels down to the score server and announces the latest score.
- The score server adds the score and the associated player to the scores list, then sorts it in order from highest to lowest. The top five scores are kept, while the rest are discarded. Two lists are kept: a daily best (which resets at midnight PST) and an all-time best (which is reset by yours truly, tentatively every month).
- The score server rezzes another rocket, and feeds it the current high score lists.
- The score rocket carries the scores lists back to the security room and announces them to the display objects (above).
- The scores list is parsed and displayed.
And there you go! A high scores system, in just about the most convoluted way imaginable. The security desk launches a dummy rocket on rez, as well, in order to grab the current scores lists when a new garage is created. All told, the update process takes less than 10 seconds, barring unforeseen circumstances (such as lag or physics failures). Not too bad, really.
After much debate, I also added a continue feature (as mentioned above). The exit teleporter device now comes equipped to reset the player. His score, timer, and Holy Hand Grenade counts are reset (to zero, zero, and one, respectively). But the monster count kept by the server is not reset. And, since the currently difficulty level is tied to the total monsters rezzed in a game session, this means the player can continue fighting against the more difficult monsters.
I was of two minds about this one. On the one hand, I hate to give away all the game's secrets for free. With indefinite continues, the player can keep going until he sees everything the game has to offer, no skill required.
On the other hand, I've found that I often don't even reach the hardest monsters in a single game, unless I'm really on the ball. I expect quite a few people will lose interest before they realize that there's more to the game than they'd see in the span of a single player life. The instruction manual (assuming it's read, and assuming I ever get around to writing it!) will solve this to some degree, as it'll have a complete catalogue of monsters and their relative difficulties. So he or she will know that there is more to the game than what he saw on the first outing.
I suppose it's more ego than anything else that's pushing me toward the continues. I'd hate for everyone not to have the chance to see my fancy Hard difficulty baddies, merely because they're not skilled enough to progress that far. I suppose I'll need to find a middle ground, and limit the player to only two or three continued sessions. I just hate to add that additional complication to the already bloating player life tracking code. Even after splitting into multiple scripts, the HUD is getting cumbersome again.
Oh well, I guess we'll see. Getting close to the official open beta period! Oy.
Sunday, July 16, 2006
Feted Inner Bear
Well, tonight I made a bear. Cute little fella, isn't he? And a pretty good likeness, to boot.
I'm not really certain what spawned this. I logged in this evening (Sunday), and did a little putzing around and assorted IMing. (No notable progress on the Garage this weekend. Took a little time off from SL.) Toward the end of an IM session, Laura asked me if I had ever made a bear. Not really knowing what it was that she wanted, I answered that all I had was a few modifications of the bear Chrestomanci had made for Plywood (Mama Bear in her various incarnations). In addition to various poses and modifications used in the strip proper, this includes a tiny av version (something like two feet tall) and a Mega Bear (about 18 meters tall). At this point, I was told that I needed to come make a bear.
Evidently, there was a mass bear-making festival at some point earlier this weekend, as the snapshot I was given indicated. Said picture showed a half dozen people (only two of whom I recognized) posing with their ursine dopplegangers. Sounds like it would have been a nifty event, although I expect I would have ended up editing in complete silence, yet again. I've yet to master the art of chatting while prim-slinging.
I am, of course, aware of the Linden Bears. Some (most? all?) Lindens have bear versions of themselves, given out either to select people or to anyone who asks. I don't actually know any Lindens well enough to have actually seen one, however. (Hamlet was the only Linden I could even claim passing acquaintance with and, of course, he's gone freelance now.) Given that the original nekkid bear template was crafted by Aimee Weber, and that said item was actually named "Feted Inner Bear," I assume some of the SL luminaries decided to adopt the practice as well. Again, I wouldn't know. One day, I'm going to have to make some feeble attempt at socializing with the SL community at large.
Nah, that's just crazy talk!
Friday, July 14, 2006
Well, finished the Rover last night. I think this is the last monster I'll be adding for a while. I guess the Mall Zombie will be shelved for now. I hope the distaff contingent of the SL community won't be too offended by the lack of female representation in my cast of horrors. If it helps, feel free to imagine the Rippers, Jack o' Lanterns, Poltergeists, and Rovers as whichever gender you prefer. I don't think they'll mind. At least, no more than they mind being shot at. I've found that gender identity issues take a back seat when the lead is flying. Even so, I won't object if you want to play while dressed up as Lara Croft or Jill Valentine to balance the scales. Just send screenshots.
Uh, yeah. I'm moving on before I dig myself any deeper.
So, here's a Rover, in all it's single-prim glory. I suppose there wasn't much point in including a screenshot of a slightly shiny, but otherwise unadorned, white sphere. But I've got to be consistent here!
As usual, it turned out to be more complex than expected. In this case, it was a relatively easy fix, though. Currently, the remote monster rezzers create monsters with their roots at a standard half meter from the floor. This has worked fine, 'til now. As you can see on the Mummy in the background, and more clearly in previous pictures, the base of a typical walking monster is a long, flattish semi-sphere. This is the root prim of the monster, and houses the lion's share of the movement and control scripts. The remaining prims (the flattened gray cylinder and the flat pane with the monster art) are mostly for show, and to provide a reasonably sized target to shoot at.
In this case, however, the baddy is a giant (1.75 meter diameter) sphere. If rezzed normally, it would end up wedged partway into the floor. Bad Things (tm) happen when you attempt to force a physical object into another solid object. I was loathe to reprogram my rezzing scheme to deal with variable rez heights at this stage of the game, so I settled for a one-shot hack for the Rovers.
Initially, the Rovers appear as 0.5m diameter spheres. Immediately after rezzing, they turn themselves physical and lift to a safe distance (one meter) from the floor. Then, they turn themselves non-physical again, and inflate to their intended size. Finally, they turn physical once again, and are ready to bounce into action. Irritating and cumbersome, but it seems to work.
You may wonder about the swap to non-physical status prior to inflation. It kind of threw me for a loop until I figured it out, too. llSetScale, the LSL command for changing prim size, will not work on physical objects. This is because the resulting changes in mass (for example, the Rover becomes about 42 times more massive in this transaction) would cause all sorts of problems with the physics engine. So, we're left with silliness like the above to work around this limitation.
Mechanically, the Rovers are a departure from previous designs. The main difference is they use llApplyImpulse for motive power, instead of the more controlled llMoveToTarget used by all previous monsters. Every scan cycle, they nudge themselves in the proper direction on the X-Y plan to approach the target. Then, every time they collide with the floor, an impulse is applied directly upward. The net result is a reasonably good approximation of a guided rubber ball.
For special effects purposes, I resorted to using drum sounds. Each bounce, the movement script picks one of three bass drum thuds at random. Each instance of damage results in one of three sharper snare drum beats, with an accompanying small burst of white particles. Upon death, the Rover bursts into a larger cloud of white, and disappears with a "pop!" Fortunately, I happened to have a variety of popping sounds from a previous project. And on attack, the Rover briefly flashes red and plays one of the Skeleton's attack roars (I'm not above recycling sound effects when one fits). None of this is Prisoner cannon, mind you, but it does add some interest to what would otherwise be a plain white ball.
Speaking of, I've been somewhat disappointed by the lack of recognition of the iconic Rover from the classic BBC television series, The Prisoner. So far, I am zero for four in recognition. But I have gotten positive responses on general creepiness, so I guess it'll work either way.
Monday, July 10, 2006
Monsters Old and New
Another weekend down, another session of assorted tweaks and improvements.
This time, I focused on improvements and additions to the monsters themselves, and to their dispensing patterns. The latter is mostly a tweaking exercise, nothing too interesting. It's just a matter of pushing around my threshold numbers for easy, medium, and hard monsters, and working up the range for the random numbers. Right now, my scheme is to pick a random number from 0 to 40, then add +1 to it for every 5 monsters that have been summoned. If the random number comes out less than 40, summon an easy. Less than 55, summon a medium. Otherwise, go with a hard monster. As you can see, all of these numbers can be changed to vary the types of monsters that appear as the game progresses. Ideally, I'd like a barrage of easy monsters for the first minute or two, then a gradual increase in medium monsters for another few minutes, finally fading into hard monsters after seven or eight minutes. (Monster rate is something like two every 6 seconds, on average.) This is all pretty fuzzy, though. I've arbitrarily picked five minutes as a typical game duration for a moderately skilled player. We'll see how that works out.
As for the monsters themselves, that's mostly been a matter of changing their movement patterns. I have modified the Coffee Zombie such that it moves in a random, hyperactive jittering pattern. This seems fitting, and makes it quite challenging to deal with, especially at close ranges. It doesn't seem to be as effective at dealing damage to a stationary target, but it is quite effective at blocking escape and preventing players from "threading the needle" between two monsters.
Similarly, I've added a sinusoidal component to the Wraith's movement. The sidestep direction is based on its current facing. So, when the player is directly in front of it, it moves in a side-to-side motion as it approaches. However, the movement when a player runs past it is unexpected. Since the creature takes a moment to turn to face the rapidly fleeing player, it has a 50-50 chance of sidling into him as he passes (if the sinusoidal offset is currently in that direction). Then, as the Wraith turns, the path becomes a smooth arc before settling back into the side-to-side motion again. The net result is very hard to anticipate, and can be lethal for the player who passes too close. This behavior also forms nifty synchronized ballet when they're in large groups.
I've opted to leave the behavior the same for the other walking monsters (Ankle Biter, Newbie Zombie, Vampire, Skeleton). The straight-line behavior still fills some gaps that the varying movement types might miss. And too much oddball movements makes the whole mob appear random and purpose-less, especially when coupled with the collision avoidance routines talked about before. That, and I'm out of ideas for movement types.
Well, almost. I was able to whip together a first pass at a Rover from "The Prisoner." It's simply a big, white ball that bounces toward the target. But I'm having a little trouble keeping it in the arena. It may simply be a matter of excessive speed, but I kept finding the prototypes bouncing around on the roof. And the over-exuberant collisions would often send the player back several steps. I hope that a general reduction in speed will fix both issues.
I've also introduced a couple more monster types. On the left is the Mummy. The avatar was made by Tod69 Talamasca of Living Dead Skins. It's probably a good thing I didn't discover Living Dead earlier, or the zombies would be considerably more gruesome than they are now. Tod69's zombies are stomach churning, with exposed muscle and bone, not to mention considerable signs of rot, decay, and gangrene. Quite realistic (if such a thing can be said for a zombie), but I think I prefer the green-skinned, but otherwise whole, zombies. It's more in keeping with the tongue-in-cheek nature of the game. I'm going to have to give some serious though to whether the mummy is too gruesome. I may have to re-cast him.
The Mummy is another easy, straightforward walker. It uses the same mechanics as the Skeleton. The main difference is it comes into play with existing damage. So the difficulty is somewhat variable. Not a massive change, but it at least gives a little variety.
On the right is the Old Soldier, a Hard difficulty monster. The outfit is a mix of three or four different store-bought and freebie army uniforms. I opted to give him a little more dignity than the other zombies, and used the more world-weary sounding moans from my ever-growing zombie sounds collection.
I'm still not 100% happy with his behavior. Offensively, he periodically stops to fire a short burst of two to four bullets at his chosen target. This seems to work well enough, although calculation delays hurt his accuracy somewhat. I'm still streamlining this a bit, in hopes of making him more effective at leading the target. Movement-wise, he is a little odd. He moves into range (between 8 and 16 meters), then sidesteps until he moves out of range again. The net result is a saw-toothed orbit around the target player. When he collides with an obstruction (such as a wall or another monster), he reverses his sidestep direction. This all works fine, and is difficult to track. But it lacks a certain dignity. He seems to spend half his time wobbling or bouncing as he suddenly changes directions. Not really satisfactory. Unfortunately, the only solution I can find is to slow him down. This may be appropriate, I suppose, but it also makes him easier to hit. Needs work.
The Shape of Things to Come, Update
As I mentioned in an earlier post, my ocean view is about to disappear. The new sims to my east appeared and disappeared like a virtual fata morgana for a few days, continuously changing in shape and elevation, until they disappeared beneath the waves altogether a few weeks ago. Nobody, including the Lindens I was able to reach, seemed to know what they were or where they went.
Well, this morning, my Inbox presented me with the answer. (You are subscribed to the Announcements Forum mailing list, right?) Per Cyn Linden's post, Mainland Shoreline Reconciliation, LL is going to be smoothing out the knife-edge end of the world with a batch of new sims. Some will be open spaces, others will be sold off as regular land.
Of course, I'm rooting for a nice water sim. But, given my elevation (Eastern Louise is a couple dozen meters above the waterline), I'd say that's pretty unlikely. As you can see from this map snippet, Louise is in the middle third of the coastline. Unless the Land Management group has plans for a lake, it seems unlikely that they'd bring my adjacent edge to so abrupt an end. A tree-bedecked void sim would also be nice but, again, probably unlikely. My guess is there will be at least two more sims to the East of me, sloping downward to a more natural coastline, with at least one of those being a normal for-sale sim.
I'm not altogether sure how to feel about this. I regret losing the ocean view, of course, but I am pleased to have had it for as long as I did. I enjoyed it for over a year (over six years in SL time, and longer than the vast majority of the currently active SL population has been alive), and it was clear that this perfectly straight edge was destined for expansion. Naturally, I'm glad to hear about the expansion of the Grid. It's a sign of the continuing health of SL. Maybe it'll bring land prices down a bit, in addition to providing some more First Land, both of which are good for the newer players. I'm a bit tempted to rally behind the NIMBY flag with regards to the First Land. But only a bit. First Land tenants generally don't last long enough to get upset about. They either leave the game or move on to larger plots elsewhere. I suppose a long-view perspective is one of the benefits of homesteading in one location for as long as I have.
Ultimately, all I can do is cross my fingers and hope for a little bit of considerate, aesthetically concerned building by the new neighbors. Damn. I almost managed to type that with a straight face.
Addendum, 7:02pm, 7/10/06
Well! That was fast. There is now an approximately 2x10 sim strip off the east cost. Looks good. So far, it's bereft of trees, but there are some nice waterways. Too soon to tell if the one off my east coast has sufficient water to be considered a void sim. We'll see.
Thursday, July 06, 2006
Sunday evening, a few friends helped me do some load testing on the Garage. At peak, we had five players: Dominic White, Sophia Weary, myself, Laura Ingersoll, and Chrestomanci Bard (listed in order from greatest to least experience with first person shooters). This turned out to be a better test than I thought, because not only did it test the mechanics of the game, it gave me a good feel for the relative difficulty level of the current version. As I've mentioned before, this has been one of the harder aspects to pin down, because I have been tinkering with the game for weeks now, and cannot tell how new players will perceive it.
Results varied widely. Dominic made the best showing by far, beating my best survival time (the sole scoring system was by time, but see below), with a solo time of 12 minutes, 59 seconds. This beat my previous best time by over two minutes, and Dominic indicated that he could have done better if I'd explained the power-ups better. (I neglected to mention that Berserker Packs grant invulnerability in addition to insta-kill punches). Sophia turned in times in excess of 10 minutes, while Laura and I seemed to last five or six minutes at a go. (Of course, I was a little distracted.) On the other end of the spectrum, Chres (the one with the least experience with FPS's) survived for under two minutes. Group games were harder to gauge, although it was apparent that the monster rez rate was too low initially. But that was easily fixed on the fly.
Overall, I'm pleased with these results. My tentative intent was for a typical game to last around five minutes. I was a bit concerned with the long game. It seems like, in the end, the high-end players only lose due to mis-steps and lag. I'm going to have to do some work to make the game more difficult as things progress.
Fortunately, the infrastructure is already in place. The monster server already tracks the number of monsters rezzed, so it's a simple matter to tie into that system to track game progress. At present, monsters are rezzed completely at random. I think the best way to address the difficulty-over-time issue is to divide them into easy, medium, and hard modes. Initially, the game will skew towards easy and medium monsters. But, as time progresses, it will shift to a larger percentage of hard ones. It's a little more math in an already processing-intensive aspect of the game, but I think it'll be worth it.
One of the biggest complaints was about the walking monsters. Their movement is too predictable. They move in a straight line to the intended target, give or take some side-stepping (see the last post). I can't argue that. And it's far too easy to literally run circles around them. While I intend to keep some basic walkers (zombies aren't known for their cat-like reflexes and unpredictable movements), I need to have at least some of them move in a different fashion. It would be useful for the Ankle Biters to occasionally move sideways, such that they more often have a clear shot (as opposed to spending most of the time shooting fellow monsters in the back). I suppose the Wraith, at least, could follow some form of mathematical curve, like the Poltergeists. But that's harder to implement for a physical monster that can run into other obstructions. Perhaps I could make the Coffee Zombie walk a drunken, jittery path, in any case. Maybe that's really Irish coffee he's so carefully protecting.
I also haven't really looked into ballistic movement. Right now, all of the movement is accomplished by use of llMoveToTarget. But there are other methods. It might be interesting to replace this with a series of impulses in the general direction of the players. Combine that with the existing avoidance scheme (also replaced with an impulse), and a monster could bounce menacingly all over the arena. Perhaps I should look into sound effects from that improbable guard dog, Rover, from The Prisoner.
The flying monsters seemed to be about right, though. The Poltergeists, while predictable, were still difficult to track and kill while on the run. The speed and (unintentional but serendipitous) erratic elevation changes of the Rippers at close range (their elevation is based on distance from target, so constantly varies while the target is in motion) served to make them quite challenging. And the exploding Jack o' Lanterns, while simple and direct, proved devastating for the unwary.
Aside from the difficulty of the monsters, the most common suggestion was the addition of a real scoring system. The survival-time based system is just too easy to game. High scores could be achieved simply by running in circles around a clump of slow monsters while shooting the fast ones down, until there is nothing left but the maximum allowable number of plodding and easy-to-dodge zombies. And that's just normal gameplay. There's always the matter of cheating by rezzing obstructions, using high-altitude flight assists to paste yourself to the ceiling, sit-teleporting to the roof, etc. (And that's not a suggestion list, by the way.) A scoring system requiring the player to actively participate, by mowing down the baddies, would nullify most of those potential cheats. And (as Dom and Soph pointed out) it would also encourage players to cycle through the easy (and thus low-scoring) monsters, in hopes of getting higher scoring bad guys to shoot.
Thus the new HUD at the right. As you can see, I've added a five digit score counter. Now, when a monster dies, it shouts out a score (currently between 1 and 3 points, depending on the difficulty of the monster) and the key of the player who should receive the points. As a nod to the previous scoring system (and my general unwillingness to throw away the clock idea altogether), a bonus is added for survival time (currently +1 point per 10 seconds). It's not a high scoring game, but this is mostly for practical reasons. Pinball scores would require a HUD that stretched halfway across the screen!
I'm in a bit of a quandry about how scores should be assigned in multiplayer games, though. According to Sophia and Dominic, multiplayer FPS's and MMORPG's traditionally assign points to the player that lands the killing blow, even if another player did 99% of the work. The noteable exception is Everquest (which I played for years), which assigned (experience) points based on who did the most damage. In both systems, kill-stealing (deliberately behaving in such a way that you take undeserved points) is frowned upon.
I can't decide which system would better encourage cooperative play. On the one hand, it seems like the most-damage-dealt system would be the fairest. This gives credit to the person who spent the most time and effort dispatching the given monster. On the other, I can see players coming to resent help from others, because there is a chance that said helper will land one more bullet and take the kill. The last-hit method can be read the same way. There is the potential for another player to come along and sneak in the last shot in a hard-fought skirmish, assuming he wasn't too busy dealing with his own problems. But it seems like that would be a 50-50 proposition- who lands the last shot when both are firing at 10 rounds a second? I don't know if this coinflip would lessen attempted kill stealing, or increase it. In either case, I'm left with the ugly interpretation of "helping" as "kill stealing." I guess it's all going to come down to the players to develop their own conventions and rules of thumb.
Right now, I have the last-hit-wins method programmed into the game. This required adding a scoring system to the HUDs, as well as a hit-tracking system to the monsters. Right now, the tracking system notes the key of the owner of the last bullet that lands, or the last chat-driven damage command. As mentioned above, the monster simply has to shout this key, along with its score, and the HUD will check to see if the key matches its owner. So far, it seems to work reasonably well.
Last night, I came up with a most-hits-wins version. At this point, I think I'll stick with last-hit-wins, at least until I see how prevalent kill stealing turns out to be. The last-hit method may work better from a sim resources perspective, as well. The most-hits method requires a fair bit of list sorting and processing on death. In this method, the monster adds the appropriate player key to the end of a list each time it is hit. There is no processing done during play (things just move too fast), so on death the score keeper script is left with an unsorted list of keys. It sorts this list alphabetically (using llSortList), in order to group the different keys together. Then, the script runs through this list, tallies the number of times each key appears, and adds that tally (along with its associated key) to the list. Once that's complete, the tally list is sorted in order from highest score to lowest, and the highest count gets the points.
It occurs to me that I could use the most-hits-wins system to allocate scores based on percentage of damage. Maybe take the whole tally, and apply a score to each player based on his percentage of total hits. The llRound function is perfect for this, as it rounds to the nearest whole integer. The more I think about that, the more I like it. For example, say a monster is worth 3 points. Player A shoots it 5 times. Player B shoots it 6 times. Player C shoots it once. This makes a total of 12 hits. So:
- Player A gets 1 point: 3 * 5 / 12 = 1.25, which rounds to 1 point.
- Player B gets 2 points: 3 * 6 / 12 = 1.5, which rounds to 2 points.
- Player C gets 0 points: 3 * 1 / 12 = 0.25, which rounds to 0 points.
Yes, this means that, in some instances, low scoring monsters could potentially not yield any points when three or more players attack them at once (since a single player would need to account for 51% of the hits in order to get a point). And, further, ties could result in more points being assigned than the monster should theoretically yield (a one point monster giving out a total of two points, for example, or a three pointer yielding four points spread over three players). I don't see a problem with this. Or, at least, no problem worth adding even more complication. I think we can deal with scores being off by a one or two percent over the course of a typical game.
I suppose there could still be a lesser form of kill-stealing, since a single-player could still score a full score, verses the partial score available from a team effort. But I think this is much more in line with the cooperative play philosophy. Yes, one player could get more points from a solo kill, but it'd also take him twice as long to do the job.
I think I'm going to have to give this method a shot.
Addendum, 7-6-06, Evening
Well, the proportionate scoring method works, but it's too slow for pitched multiplayer battles. Turns out that too many if-thens were required to filter out extraneous traffic on the damage channels, and the script couldn't keep up with the influx of data from a heavy fusillade of machine gun fire. The most-hits-wins method turned out to have similar issues. I expect that I could do some additional streamlining and reprogramming, but I think I'll just let this be the deciding factor and stick with the last-hit-wins method. That requires the least processing by far, and it therefore appears to be the quickest. (I actually had to add a pause to it, in order to allow the various death sequences to play out before deleting the monster.) I did learn a bit more about list manipulation, however, so the time wasn't a complete waste.
I also managed to revamp the monster rezzer to slowly skew monster distribution toward harder monsters as the game progresses. Now I just need to have more "hard" and "easy" category monsters to fill it out.
Wednesday, July 05, 2006
Garage Weekend Update
The four day Fourth of July weekend was a pretty productive one for me. At least, it was if you consider working on the Garage of DOOM "productive." But I'll put that question aside for now and get on with the update.
Much of my time was spent trying to separate multipurpose scripts into multiple single-function scripts. This is a process somewhat on par with trying to untangle a bowl of wet spaghetti using a single chopstick. But the task proved to be necessary in order to more efficiently clear backlogs of chat traffic from one script to another. I found that, when things are moving fast and a number of things are happening at once, some of my combined scripts would tend to bog down as they tried to deal with the constant stream of incoming data. It irks me that a script can choke on a mere five or six incoming chat commands per second (Oh Mono, where art thou?), put there's no help for it.
I'll give you an example. The central server (conveniently located inside a manhole cover in the center of the garage) serves several functions. It listens for monster calls from player HUDs, and selects monsters and power-ups to send to the remote monster dispensers for rezzing. It tracks and counts players by sending out a signal for a roll call, listening for a "here" response from all currently active players, compiling a list of active player keys, and sending this list to the monsters. It performs a similar roll call function with the monsters, and limits the maximum monster count to a predetermined number. And there are a number of one-shot and ancillary functions that must be taken care of during the game, as well. Initially, I naively put all these functions in one large script. But, when there were two or three players and a dozen monsters all talking back and forth with the server every second or two, it tended to lag a bit behind.
So, each of the functions was split into small parts, some of them only 20 lines long. Similar things were done with the player HUD, to deal with power-ups, health tracking, time tracking, collision tracking, and so on. (Fortunately, I had the good sense to use modular parts for the nine-and-counting monsters from the beginning.) And then came the test, check, and debug process required to pass the required information back and forth between them all. Long and tedious. But everything seems to work considerably faster and more reliably now.
I'm a little concerned about script count, though. I've been doing everything I can to filter out extraneous chats (such as limiting listens to specific keywords, as opposed to using if-thens to check for them later, and using the various link-here, link-there, link-everywhere-but-here link messaging directions to avoid processing extraneous internal communications) and make each individual script more efficient and less laggy. But I fear the net result is still more overall sim load. I'm going to be watching that very carefully during my load tests. I'd hate to throw away all this work, but I don't own the sim, and I don't have the right to consume significantly more than my share of the resources. Fortunately, the garage is not a constant load. It deletes itself after 10 minutes of inactivity, and is only under load for a few minutes at a time (the length of a typical game- which I'm trying my darnedest to make shorter, for a variety of reasons). I hope that's enough. In the meantime, it's streamline, conserve, and reduce at every turn. It officially works, now it just has to work smarter.
Once I completed the modularization of the various parts, I had the brilliant idea of making them all interconnected again. Well, that wasn't my original intent, but it sure seemed to work out that way. See, I decided to add a new type of power-up: the Freeze Pack. When activated, the Freezer stops all currently active monsters in their tracks, and stops new monsters from appearing, for a 30 second period. Simple, right?
Yeah, I thought so, too. Those of you who managed to stay awake through previous posts may remember a passing reference to an aborted invisibility power-up. Basically, this is effectively the same thing, on a mass scale. At its heart, the Freeze effect directs the monster server to feed empty player lists to the monsters. After that, the monsters can't find any valid targets, and they just coast to a halt until they hear otherwise. Then, after the Freeze period has elapsed, a populated player list is sent, and they leap into action again.
At least, that's how I hoped it would work. But there were a massive number of little hidden consequences to flipping the player list on and off. First, the monsters are more-or-less autonomous. They do their own thing, under the direction of the player list and their own programming. So, if the bogus list isn't heard (due to lag effects, or rezzing at exactly the wrong time), they'll keep pounding on the hapless player. And even the monsters that did hear the command took a few seconds to react. And, during this time, they'd keep following and pounding on the player. Not terribly satisfactory.
Before it was complete, the Freeze power-up had some variable or toggle in just about every major component of the game. And it required flipping the monsters back and forth between physical and non-physical, in order to get them to stop in their tracks (as opposed to sort of meandering to a stop a few meters later). This is another perfect example of a small change with huge unanticipated consequences.
In any case, after half a day's work, I now have one more power-up. It's a spinning ice cube, and it makes a wave of blue-white particles and the sound of a burst of arctic wind when it's activated. Umm, yeah. I don't think I'll be making any more power-ups for a while.
Beyond that, most of the weekend was spent with minor tinkering and enhancement on the monsters. Most of this was with an eye for making them tougher, faster, and smarter. They're getting there. The walking undead still tend to follow the players in a straight line, and are a bit too easy to coax into an ineffectual clump. But they're getting better.
In the middle of all that, I came up with a rather nifty (if I do say so myself) avoidance algorithm to prevent the creatures from clumping together quite so badly. On collision, they calculate the direction vector away from the obstruction, and move back a step in the proper direction. In a group, this means that the monsters tend to briefly bumble about, then spread themselves out nicely. It's a small thing, but it's made a huge difference.
And it's opened the way for one of the most often requested features for the garage: cars! Now that the monsters know how to sidestep, as opposed to either repeatedly slamming into or attempting to climb over obstructions, I can afford to carefully add a car or two for cover. I'll have to see what kind of impact this has on the game. For theme purposes, it would add quite a bit to the game to have a couple of old junkers in the garage. But I don't know if the obstruction will cause too many problems, or make it too easy to run and hide.
And I seem to have run out of time for this update. More to come.
Plywood No More
I'll probably have more to say on this at some later time, but in short: we're no longer making Plywood. I won't swear that we won't get back to it, someday. But I wouldn't hold my breath.
There are a few reasons why. First, real life kept getting in the way. Even a comic made with screenshots takes a few hours to generate, when you figure in setup time, posing, script writing, lettering, and piecing it all together. I can't imagine how real webcomic artists manage to do all that in addition to creating the art by hand. This little sideline has greatly increased my respect for comic artists that create their work day after day, serveral times a week.
The rest are more personal. I'm only speaking for myself here. After doing this for a while, I had to stop and look at my motivations. In the beginning, I started out doing this for fun, and for the satisfaction of entertaining my readers. Good goals, low expectations. But, as time went on, my expectations changed. And I'm not proud of that. Working for fame and acclaim is a mugs game, folks. I spent all my time thinking, someday, I'd be famous (at least in SL) for my work, and resenting the movers, shakers, and FIC makers for not giving me what I thought was my due. Moments of recognition were fleeting, and 95% of the people I met answered with "What's Plywood?" followed by polite nods and forced smiles. That is quite embarrassing, believe me.
Don't get me wrong, there have been a few people who have been very supportive and complimentary. And I appreciate it! It makes me feel like an ungrateful jerk that, deep down, that wasn't enough for me. It should have been, and it would have been, at the beginning. I'm just tired of expecting more, and irrationally resenting the SL community at large when I don't get it. It's petty, it's unreasonable, and it's no way to enjoy making a comic. I guess the long and short of it is I took the time to examine my motives, and I didn't like what I saw. And, frankly, it's hard to be funny when you resent the results, or the lack thereof.
I guess that's my advice as a Second Life webcomic creator: if you're going to do it, do it for fun. Do it because you enjoy making it, and because you like the thought of a small, mostly-anonymous audience smiling at it before they move on with their own lives and Second Lives. Don't go into it expecting fame. Don't expect acclaim. Don't expect praise. Don't expect this to be your in-road into the FIC, or whatever other clique you think you want to run with. And for the love of Pete, don't do it expecting to make real money at it! You'll be disappointed, I guarantee. And your work will suffer. This is a hobby. It's not a job, and it's not a stepping stone.
(Incidentally, expecting to make money at this is the fastest way I can think of to a bitter, and short lived, series. In our case, we ended up losing money on our one and only in-game compilation sale. That was discouraging, to say the least. It's tempting to measure your success and the esteem of your fan base on the strength of sales figures. Don't do it. Think hard about why you want to sell an in-game copy of your work, whatever it might be. If it's because you expect it to be a measure of your work's worth, think again. If you expect to make money, well, wake up. Not going to happen.)
So, that's that. I'm now "that guy that used to make that webcomic, what was it called again?" I'm working hard to get to the point where I feel proud and happy about the body of work we produced, and stop feeling like a quitter, a has-been, and a wanna-be. I'm not there yet. I'm still at the anger stage of the grieving process, directed mostly at myself, but I can at least comprehend the possibility of acceptance. We'll see.
As I said, I'm sure I'll have more speculations and ruminations on the whole Second Life webcomic thing. I'm not above milking a dead project to increase my post count. But, in the meantime, I'm going to try and find an identity for myself beyond "creator of Plywood."
Say, did I tell you about this game I'm making?
Y'know, if you haven't performed the rite of AFK Molestation on your friends and cohorts, you're missing out. All it takes is a little rapid-fire building skill and a quick screenshot finger. Or two fingers. Why, oh why, did they change screenies from "`" to "CTRL-`"? I've missed far too many good candid shots trying to remember that. But I digress.
This one was Sophia Weary's idea, and it's elegant in its simplicity. Walk away from your computer, and come back to find that everyone has mutated into furries... Including you! Aiiiiyeeee! (Take that, spell checker.) We used the heads from standard Linden library furries. What are those, anyway? Meerkats?
It helps if everyone is sitting in pose-equipped chairs, which keep the av's head still even if he's AFK. Otherwise, you'd need to do some fairly tricky estimation work to assure that the victim's head snaps back up into place when he returns. The key to this one is to refuse to admit anything is amiss. Furry heads? What furry heads?
That's Chrestomanci Bard (Victim #1) on the left, and Laura Ingersoll (Victim #2) on the right. Sophia is at top, and that's my back to the camera at bottom. Dominic White, the only full-time furry in our little kaffeeklatch, was absent. (I don't know if he's a furry by philosophy, but he does cut a dashing figure in wolf head and flowing cape.) That's his picture on the poster in the back.
Chrestomanci returned from her phone call, possibly summoned by the sound of surreptitious giggling and the click-whirr of screenshots. Laura was still MIA. Since Chres is the owner of the wicker chairs, this opened the door for even more elaborate mischief. Enter the AFK Zone, courtesy of Sophia, again. She has a scary gift for this.
The chair, and its occupant, was relocated to 500 meters above ground. Between the three of us, we managed to construct a black sphere with moving starfield, a whole slew of particle effects, and a spinning noise maker with spooky woo-woo-woo sounds. (The latter was mine. One side effect of the Garage of DOOM is that I've now got a whole library of spooky/monstrous sound bites.) This pic was taken from the outside. From the inside, all that could be seen was starry blackness and particulate fireworks. There is no escape from the AFK Zone!
We all donned disguises and waited. Chres is the semi-transparent blob to the lower left of the chair, just below the large white particle. She's wearing a semi-invisi-primmed ghost costume from a couple Halloweens back. Sophia is crouched inside the black box at lower right. I'm the flying black silhouette at middle right. Unfortunately, the ghost of Rod Sirling doesn't show up on film.
This isn't relevant, but it was kind of a nifty picture. That's Laura at left and Chres at right, during the course of our semi-weekly avatar showoff fest. This time, it was on the roof of Chres's Afton house (mostly because that's where we fell when the AFK Zone was dismantled). I think I'm the only one that doesn't have a custom furry avatar. Well, aside from a 20 meter tall teddy bear, but I don't know if that counts. It's more a mega-plushy than a furry.
In any case, I just liked the story built into this picture. The actual dialogue consisted of Laura expressing envy and dismay over Chres's superior tail design, and Chres responding (in character) "Well, I'm just foxier!" The expressions on the prim-furry heads are ideal.
Ah, I miss these moments. I'm going to have to make a point to spend more time outside of Louise, and less time with my head buried in a script window.