by date
Teh Ph4t L3wtz
Teh Ph4t L3wtz
| Name: | Devon Winter | |
|---|---|---|
| Date Posted: | Oct 30, 2007 | |
| Rating: | Not Rated | |
| Public: | YES | |
| Comments: | YES | |
| RSS Feed: | or Subscribe with . | |
| Profile Page: | View profile page for Devon Winter |
Blog post
So when last we left off, our hero could finally loot the corpses of her hard-earned kills. I figured this week I'd spend some time talking about that system. Like any system, in concept it sounds really simple. In practice of course, the implementation is anything but. So here we go.
What this isn't
Sadly, this is not a turnkey drop-in loot resource, complete with code. The reason for this is not out of any lack of desire to share, but only that my limited time doesn't allow for me to go through and remove all the dependencies this system has on the systems I've already built. With over a year's worth of work already in place in my engine, to make this a stand alone resource I'd have to either stub out or provide all the underlying systems, and I just don't have the time to go through and fix up those dependencies.
What I will do though is go through the entire system at a high level, talk a bit about what I put where and why I put it there, and hopefully if you're doing something similar, you might find some kernel of usefulness buried within. Or perhaps just an entertaining read of a mildly delusional programmer. :P
The Parameters
So what I'm shooting for is a first pass implementaion of WoW's looting system (and LOTRO's.. and EQ2'S.. etc). That is, objects don't drop on the ground ala GW or Diablo. You kill a corpse, and it gets a sparkly saying you should loot it. You loot it, and it gives you some money, and displays a window with the other things on the corpse. You take those things by clicking on them, and for fun I added a "Take All" button, so clicking on that will give you everything in the panel at once. As I don't have the notion of groups yet, then I wasn't going to mess around with any group looting rules. Everything is free for all, and it's first come first serve. In fact, you can even loot something while someone else is looking at the loot panel. Obviously, this isn't final design, but it was enough for my purposes.
Setting up the data
So the thing I realized I needed was a loot table. This determines who has what loot, and the chances of them dropping it. Now I've already built a tool that allows me to take excel spreadsheet data, save it in a comma delimited format, and generate .cs files that contain datablock definitions for the data defined in the spreadsheet. Not the most elegant way of setting up data in the game, but at the time I hadn't bothered to code up an XML parser, so this was more expedient. Turns out it works pretty well. Here's the structure definition for the loot table record:
Now my game's set in the future, and as we all know, money in the future looses all demonination and just because credits. baseCredit and creditSpread work together to determine how much money to randomly drop. Basically you'll get baseCredit +/- some random amount between 0 and creditSpread/2. A loot entry defines a drop rate (from 0 to 1), and a pointer to our item definition for the thing to drop. This is a very simple drop system, and I wrote up some notes for a much more complicated system, and then saved it for future reference. Again, K.I.S.S. I created some examples of these for the two mobs I currently have in the game, and the player datablock gets a pointer to one of these records as a the "Loot" member.
I wasn't done with data yet, as I still needed a bit more. We know knew where to go to figure out what loot to put on the player, but the generated loot needs to be stored somewhere. I created two new members to go on the player structure (not the player datablock). The first is a simple bool -- isLootable. Basically, this gets set to true when a mob has died and loot has been generated for him, and is true as long as loot remains on him. The moment all loot is removed from him, this gets set to false. Second, a new record called "generatedLoot" was added. This holds what you think it would, namely being the amount of money generated, and an array of item references. It could hold quantities as well, for each of those items, but I decided that each item in the loot table would only ever generate one of those items. That's the beauty of being both designer and programmer, I can make those kinds of decisions. :) Both of these pieces of data (the isLootable flag and the generatedLoot structure) are tied to a "lootModified" flag on the player, and replicated to the clients by way of the pack/unpack update routines.
The Process
So how does it work? Well something like this. If at any point a player becomes disabled, (ie., dead), we check their loot table pointer to see if it 's non null. If it is, then we call a generateLoot function that looks up the loot table entry, rolls the dice, and stuffs the generated money and items, if present, into the generatedLoot structure. The isLootable flag is set to true, and the "lootModified" flag set so these updates get replicated to the clients.
At this point, I had to go off and create a sparkly particle effect. Ugh. Art. I suck so much at art. Still, after some wrangling I got something that didn't make me want to cringe. SO.. more data added to the player datablock -- a pointer to the sparkly particle effect. I stuffed it in there right between footpuffs and splash effects.
So.. back to process. On the client, in advanceTime, I now update loot particles. If the "isLootable" flag is set (having been replicated to us from the server), and we do not have a particle system up yet, AND we are not playing an animation (cause see.. I don't want the sparkly to show up until I've finished the death anim), then we generate the loot emitter, register it, and start it. If the loot emitter's already generated, we just emit some particles. And if the flag is not set, but the emitter is still running, we schedule it for termination. Wee!
The player sees the loot sparkly, jumps for joy, and runs up to r-click on the corpse. When they do, I send the command up to the server that says "work on this right here" with the mouse coords. I haven't yet made the enhancements suggested in last week's blog, but I almost certainly will. For now though it still sends the work command with the mouse coords. The server says "hey.. corpse here.. lootable?" If so, then it generates a notification message to all interested clients that client Jimmy is looting the mob. It also takes this opportunity to decrease the generated loot by the amount of money on the mob, and increase the player's money (oh yeah, I forgot, each player has a new S32 for their current credit amount) by said amount. Note -- even though I send the amount of money taken along with the notification, the amount is transmitted only for the purpose of displaying the chat message. The client should *not* update it's own money amount, because the server already did that, and the updated money amount will be sent via replication. Finally, I also tell the specific client that requested the loot to play his loot animation.
So on the client, I get the begin_loot notification, and if it was my client, I display the loot panel and chat the "Player jimmy has looted some money" message. If it wasn't my client that's 'begin_looted'ed, I just chat the message, so everyone knows how much money you got off that corpse!
Brief pause here as I go code up the UI panel for displaying loot. See note above about previously generated systems. This wasn't hard because it was almost identical in function and form to the inventory panel I had already created. Back to the action..
So now my loot panel is displayed, and it was informed about which mob it was displaying loot for. Any time the generatedLoot is modified, in unpackUpdate, I refresh any panels that are open. So this means that even if I'm looking at a panel, and someone else takes something from the mob, the panel will reflect that information. Sweet huh? When the user clicks on an item in the panel, the panel generates a command to the server to "Take Item Blah from Mob Blahblah". The server verifies the item still exists, and if so, it removes the item from the mob's generatedLoot structure, and grants the player a single instance of said item. This goes through the player's already written "grantItem" routine, so items are auto-stacked inside their inventory (another previously written system). Again, a notification is sent to all clients of the item taken, purely for chat purposes. The updating of the panel and the player's inventory is left up to the server's replication. If at any time when the server moves the last item out of generatedLoot, and there's no money left, the "isLootable" flag is set to off. Clientside, when I refreh UI panels, if at any time the isLootable flag changes, if it's set to off I close the loot UI panel. So when the last item is taken the panel closes automagically.
Finally, I added a "take all" button, which generates a separate command to the server, and the server just iterates through all the generatedLoot on the mob, sending out notifications for each item taken.
Okay! That's pretty much it. A long read, yes, but that's pretty much top to bottom. I promised some pictures, or something, so here's a poor quality video of the looting in action. Sorry for the poor quality, I didn't realize photobucket would recompress what I had already compressed! I promise future videos to be in better quality.
Simple Loot Demo
What this isn't
Sadly, this is not a turnkey drop-in loot resource, complete with code. The reason for this is not out of any lack of desire to share, but only that my limited time doesn't allow for me to go through and remove all the dependencies this system has on the systems I've already built. With over a year's worth of work already in place in my engine, to make this a stand alone resource I'd have to either stub out or provide all the underlying systems, and I just don't have the time to go through and fix up those dependencies.
What I will do though is go through the entire system at a high level, talk a bit about what I put where and why I put it there, and hopefully if you're doing something similar, you might find some kernel of usefulness buried within. Or perhaps just an entertaining read of a mildly delusional programmer. :P
The Parameters
So what I'm shooting for is a first pass implementaion of WoW's looting system (and LOTRO's.. and EQ2'S.. etc). That is, objects don't drop on the ground ala GW or Diablo. You kill a corpse, and it gets a sparkly saying you should loot it. You loot it, and it gives you some money, and displays a window with the other things on the corpse. You take those things by clicking on them, and for fun I added a "Take All" button, so clicking on that will give you everything in the panel at once. As I don't have the notion of groups yet, then I wasn't going to mess around with any group looting rules. Everything is free for all, and it's first come first serve. In fact, you can even loot something while someone else is looking at the loot panel. Obviously, this isn't final design, but it was enough for my purposes.
Setting up the data
So the thing I realized I needed was a loot table. This determines who has what loot, and the chances of them dropping it. Now I've already built a tool that allows me to take excel spreadsheet data, save it in a comma delimited format, and generate .cs files that contain datablock definitions for the data defined in the spreadsheet. Not the most elegant way of setting up data in the game, but at the time I hadn't bothered to code up an XML parser, so this was more expedient. Turns out it works pretty well. Here's the structure definition for the loot table record:
struct lootentry
{
F32 rate;
GameBaseData *item;
};
typedef struct lootentry LootEntryType;
S8 baseCredit;
S8 creditSpread;
LootEntryType lootEntries[cMaxLootEntries];
Now my game's set in the future, and as we all know, money in the future looses all demonination and just because credits. baseCredit and creditSpread work together to determine how much money to randomly drop. Basically you'll get baseCredit +/- some random amount between 0 and creditSpread/2. A loot entry defines a drop rate (from 0 to 1), and a pointer to our item definition for the thing to drop. This is a very simple drop system, and I wrote up some notes for a much more complicated system, and then saved it for future reference. Again, K.I.S.S. I created some examples of these for the two mobs I currently have in the game, and the player datablock gets a pointer to one of these records as a the "Loot" member.
I wasn't done with data yet, as I still needed a bit more. We know knew where to go to figure out what loot to put on the player, but the generated loot needs to be stored somewhere. I created two new members to go on the player structure (not the player datablock). The first is a simple bool -- isLootable. Basically, this gets set to true when a mob has died and loot has been generated for him, and is true as long as loot remains on him. The moment all loot is removed from him, this gets set to false. Second, a new record called "generatedLoot" was added. This holds what you think it would, namely being the amount of money generated, and an array of item references. It could hold quantities as well, for each of those items, but I decided that each item in the loot table would only ever generate one of those items. That's the beauty of being both designer and programmer, I can make those kinds of decisions. :) Both of these pieces of data (the isLootable flag and the generatedLoot structure) are tied to a "lootModified" flag on the player, and replicated to the clients by way of the pack/unpack update routines.
The Process
So how does it work? Well something like this. If at any point a player becomes disabled, (ie., dead), we check their loot table pointer to see if it 's non null. If it is, then we call a generateLoot function that looks up the loot table entry, rolls the dice, and stuffs the generated money and items, if present, into the generatedLoot structure. The isLootable flag is set to true, and the "lootModified" flag set so these updates get replicated to the clients.
At this point, I had to go off and create a sparkly particle effect. Ugh. Art. I suck so much at art. Still, after some wrangling I got something that didn't make me want to cringe. SO.. more data added to the player datablock -- a pointer to the sparkly particle effect. I stuffed it in there right between footpuffs and splash effects.
So.. back to process. On the client, in advanceTime, I now update loot particles. If the "isLootable" flag is set (having been replicated to us from the server), and we do not have a particle system up yet, AND we are not playing an animation (cause see.. I don't want the sparkly to show up until I've finished the death anim), then we generate the loot emitter, register it, and start it. If the loot emitter's already generated, we just emit some particles. And if the flag is not set, but the emitter is still running, we schedule it for termination. Wee!
The player sees the loot sparkly, jumps for joy, and runs up to r-click on the corpse. When they do, I send the command up to the server that says "work on this right here" with the mouse coords. I haven't yet made the enhancements suggested in last week's blog, but I almost certainly will. For now though it still sends the work command with the mouse coords. The server says "hey.. corpse here.. lootable?" If so, then it generates a notification message to all interested clients that client Jimmy is looting the mob. It also takes this opportunity to decrease the generated loot by the amount of money on the mob, and increase the player's money (oh yeah, I forgot, each player has a new S32 for their current credit amount) by said amount. Note -- even though I send the amount of money taken along with the notification, the amount is transmitted only for the purpose of displaying the chat message. The client should *not* update it's own money amount, because the server already did that, and the updated money amount will be sent via replication. Finally, I also tell the specific client that requested the loot to play his loot animation.
So on the client, I get the begin_loot notification, and if it was my client, I display the loot panel and chat the "Player jimmy has looted some money" message. If it wasn't my client that's 'begin_looted'ed, I just chat the message, so everyone knows how much money you got off that corpse!
Brief pause here as I go code up the UI panel for displaying loot. See note above about previously generated systems. This wasn't hard because it was almost identical in function and form to the inventory panel I had already created. Back to the action..
So now my loot panel is displayed, and it was informed about which mob it was displaying loot for. Any time the generatedLoot is modified, in unpackUpdate, I refresh any panels that are open. So this means that even if I'm looking at a panel, and someone else takes something from the mob, the panel will reflect that information. Sweet huh? When the user clicks on an item in the panel, the panel generates a command to the server to "Take Item Blah from Mob Blahblah". The server verifies the item still exists, and if so, it removes the item from the mob's generatedLoot structure, and grants the player a single instance of said item. This goes through the player's already written "grantItem" routine, so items are auto-stacked inside their inventory (another previously written system). Again, a notification is sent to all clients of the item taken, purely for chat purposes. The updating of the panel and the player's inventory is left up to the server's replication. If at any time when the server moves the last item out of generatedLoot, and there's no money left, the "isLootable" flag is set to off. Clientside, when I refreh UI panels, if at any time the isLootable flag changes, if it's set to off I close the loot UI panel. So when the last item is taken the panel closes automagically.
Finally, I added a "take all" button, which generates a separate command to the server, and the server just iterates through all the generatedLoot on the mob, sending out notifications for each item taken.
Okay! That's pretty much it. A long read, yes, but that's pretty much top to bottom. I promised some pictures, or something, so here's a poor quality video of the looting in action. Sorry for the poor quality, I didn't realize photobucket would recompress what I had already compressed! I promise future videos to be in better quality.
Simple Loot Demo
Recent Blog Posts
| List: | 07/08/08 - On Cursors and Code, and Mickey Mouse 06/20/08 - Upgrading to TGEA - Twice 06/04/08 - Truespace and Torque - Part II 06/04/08 - Truespace and Torque - A Study in Four Year Old Technology 03/03/08 - Mission Complete! 11/15/07 - Fixing the Zombie Shuffle 11/10/07 - Hellgate crits production for 1300 hp's. Production dies. 10/30/07 - Teh Ph4t L3wtz |
|---|
Submit your own resources!| Brian Wilson (Oct 30, 2007 at 15:26 GMT) |
You must be a member and be logged in to either append comments or rate this resource.


Not Rated


