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.