TalkPHP
 
 
Account Login
Latest Articles
» The basic usage of PHPTAL, a XML/XHTML template library for PHP
» Vulnerable methods and the areas they are commonly trusted in.
» Simple way to protect a form from bot
» The Basics On: How Session Stealing Works
» How to keep your forms from double posting data
Advertisement
Associates
Associates
techtuts Darkmindz
CSS Tutorials Tutorialsphere.com - Free Online Tutorials
Boston PHP SurfnLearn
Reply
 
LinkBack Thread Tools Display Modes
Old 06-12-2008, 07:25 AM   #1 (permalink)
The Wanderer
 
Join Date: Jun 2008
Posts: 8
Thanks: 1
TexasMd91 is on a distinguished road
Default What is the best way to structure this database?

I am helping a friend building a site for a MMO to come up with drop rates or rare items from killing monsters. I want this database to be fully dynamic.

There needs to be a way to store monsters, and items.
How I currently have it is a table called 'monsters' with columns 'id' and 'name'. I then have a table called 'items' with columns 'id' and 'name'. These 2 databases will store all the Possible Monsters that people can kill, and every drop they could receive.

Now there needs to be a table for the logs. I use a table named 'drops' with columns 'id', 'monster_id', and 'item_id'. Only problem is that monsters can drop more then 1 item at a time. So how can I arrange it?

I was thinking of making item_id a string with it being something like 1-19|2-1|91-90| (first number being the quantity, second being the item_id, and then splitting the array 2 times to find out how much of each drop). Is there an easier way to do this?

Last edited by TexasMd91 : 07-02-2008 at 06:28 AM.
TexasMd91 is offline  
Reply With Quote
Old 06-12-2008, 10:13 AM   #2 (permalink)
Jim
The Addict
 
Jim's Avatar
 
Join Date: Nov 2007
Location: the Netherlands
Posts: 224
Thanks: 2
Jim is on a distinguished road
Default

Well, what you can do, is make a kill table. Every kill of a monster will be stored in that table. With simply the user_id and monster_id. Then you can make a drop table which coresponds to the kill table: kill_id, drop_type, quanity, and just store every droptype in a new record.

This might not be the best option since i don't know if you want to store the kills, but maybe it helps you in a way.


(I don't get it btw, why store the drops in a database? Can't you just randomize the drop with each kill and automatically store the drops in the inventory of the users?)
Send a message via MSN to Jim Send a message via Skype™ to Jim
Jim is offline  
Reply With Quote
Old 06-12-2008, 09:22 PM   #3 (permalink)
The Wanderer
 
Join Date: Jun 2008
Posts: 8
Thanks: 1
TexasMd91 is on a distinguished road
Default

Quote:
Originally Posted by Jim View Post
Well, what you can do, is make a kill table. Every kill of a monster will be stored in that table. With simply the user_id and monster_id. Then you can make a drop table which coresponds to the kill table: kill_id, drop_type, quanity, and just store every droptype in a new record.

This might not be the best option since i don't know if you want to store the kills, but maybe it helps you in a way.


(I don't get it btw, why store the drops in a database? Can't you just randomize the drop with each kill and automatically store the drops in the inventory of the users?)
I am not the one creating the MMO, this is just a site for statistics to see how rare items are.

and that idea is pretty good, I think I will do it that way unless some one else has a better suggestion.
TexasMd91 is offline  
Reply With Quote
Old 06-13-2008, 05:53 AM   #4 (permalink)
The Contributor
 
Join Date: Nov 2007
Location: California
Posts: 56
Thanks: 0
dschreck is on a distinguished road
Default

I'd do it something like this:

Code:
CREATE TABLE monsters (
 id INT NOT NULL AUTO_INCREMENT,
 name VARCHAR(120) NOT NULL,
 level INT NOT NULL DEFAULT 0,
 PRIMARY KEY(id),
 INDEX level (level)
);

CREATE TABLE drops (
 id INT NOT NULL AUTO_INCREMENT,
 monster_id INT NOT NULL,
 item_id INT NOT NULL,
 PRIMARY KEY(id),
 index monster_item (item_id, monster_id)
);

CREATE TABLE items (
 id INT NOT NULL AUTO_INCREMENT,
 name VARCHAR(120) NOT NULL,
 min_level INT NOT NULL DEFAULT 0,
 max_level INT NOT NULL DEFAULT 99,
 PRIMARY KEY(id)
);


CREATE TABLE found_items (
 id INT NOT NULL AUTO_INCREMENT,
 user_id INT NOT NULL,
 drop_id INT NOT NULL,
 PRIMARY KEY(id),
 INDEX user_drop (user_id, drop_id)
);
I added the item levels because I just felt like it ;p

The idea is that the monsters has a 1 to many relationship with drops (1:n). And the relationship between items and drops is many to many (n:n). Ie: Many items can be dropped by many monsters. So your drops table is essentially just a lookup table. Now, you store the drops in the found_items table, where one drop is stored in one row of the found_items (1:1).

This is of course done in MySQL :p
__________________
My Blog: http://www.iwilldomybest.com/
dschreck is offline  
Reply With Quote
Old 06-13-2008, 07:51 AM   #5 (permalink)
The Wanderer
 
Join Date: Jun 2008
Posts: 8
Thanks: 1
TexasMd91 is on a distinguished road
Default

dschreck, that is exactly how I first created it, only problem is that monsters can drop more then one item at a time, also that certain item can be in more then one quantity. Just making 2 rows doesn't work because then I can't count the rows to see how many times it has been killed.

Example:
Code:
Ork dropped 1 bone, 6 gold, 1 ork skin.
Ork dropped 1 bone, 3 gold.
EDIT: I could keep in the monster table how many times its been killed and increase it every time.
TexasMd91 is offline  
Reply With Quote
Old 06-13-2008, 05:03 PM   #6 (permalink)
The Contributor
 
Join Date: Nov 2007
Location: California
Posts: 56
Thanks: 0
dschreck is on a distinguished road
Default

Quote:
Originally Posted by TexasMd91 View Post
dschreck, that is exactly how I first created it, only problem is that monsters can drop more then one item at a time, also that certain item can be in more then one quantity. Just making 2 rows doesn't work because then I can't count the rows to see how many times it has been killed.
You insert into the lookup table multiple times....

IE:

Orc Drops 1 Hide, 1 Weapon

INSERT INTO drops VALUES (monster_id_of_orc_goes_here, item_id_of_hide_goes_here);
INSERT INTO drops VALUES (monster_id_of_orc_goes_here, item_id_of_weapon_goes_here);

Not sure what you'd do with Gold... but that would probably go into their items...

Another thing to remember, is that with an MMO you really only need to record when someone picks up an item, and keeps it in their inventory.

But if you're just trying to record what items drop off of what monster, the drops look up table should work fine.
__________________
My Blog: http://www.iwilldomybest.com/
dschreck is offline  
Reply With Quote
Old 06-13-2008, 08:07 PM   #7 (permalink)
The Wanderer
 
Join Date: Jun 2008
Posts: 8
Thanks: 1
TexasMd91 is on a distinguished road
Default

Quote:
Originally Posted by dschreck View Post
You insert into the lookup table multiple times....

IE:

Orc Drops 1 Hide, 1 Weapon

INSERT INTO drops VALUES (monster_id_of_orc_goes_here, item_id_of_hide_goes_here);
INSERT INTO drops VALUES (monster_id_of_orc_goes_here, item_id_of_weapon_goes_here);
But then it would register that is 2 kills when really the player only killed it once and got both items in 1 go.

I ended up creating a monDropID which is used to say which drop it is, so if the user gets 2 drops at once then the monDropID will be the same as the first for the second drop.

Quote:
Another thing to remember, is that with an MMO you really only need to record when someone picks up an item, and keeps it in their inventory.
I am creating a website for users to log in what drops they get (I created a program in c++ which is pretty much a logger where the user can easily insert their drop information.) The statistics will then be used to show how different variables matter. (IE: if the user is wearing a special item which increases drop ratio).
TexasMd91 is offline  
Reply With Quote
Old 07-02-2008, 05:39 AM   #8 (permalink)
The Wanderer
 
Rizza's Avatar
 
Join Date: Dec 2007
Location: Orlando, FL
Posts: 23
Thanks: 0
Rizza is on a distinguished road
Default

Forgive me for how exhaustive this explanation is... but I don't want to just give you an answer and not explain the reasoning behind it. Beware for those who enter this post, I'm going to be writing a lot.

Here's my suggestion; but first my assumption: You're using MySQL of some flavor, probably 4.

If you want to track monster-to-item statistics, you don't need to insert into the database every time someone kills a monster. Likewise, you would not want to insert multiple rows for each item quantity. Doing either is a surefire way to quickly bloat your database and surely lag your server.

MySQL is developed with a few optimizations in mind you should take into consideration while developing a plan for interacting with it. While it is fast, its optimized for performance in SELECT queries, not UPDATEs and certainly not INSERTs.

Lets assume you have 10 users on the site and each person is killing 2 monsters every minute with each monster dropping 3 items (5 gold, 1 sword and a stylish ring). If we were to insert a row for each monster-to-item record, we would be making 140 INSERT queries every minute, assuming we went with the one row-per item/monster relationship model previously mentioned. That's disastrous, just think about 100 users. Additionally, as you pointed out, we would have a monster kill count integrity issue by inserting new rows for each relationship. So lets think of a different way to do this...

We need to prioritize what we're looking to actually track here: It's the rarity of items and percentage chance that a player will receive A or B item while killing X or Y monster.

That calls for 3 tables. "monsters", "items" and "items_monsters"; where items_monsters represents a one-to-one relationship in the table. So for example: 100 items, 100 monsters, potentially 10000 items_monsters rows; something MySQL can easily handle if we're using integers for our record IDs.

Why are integers so important? It all boils down to how MySQL searches through it's rows. When using a SELECT query, the WHERE clause will search non-integer values on a bit-by-bit basis rather than the string as a whole (indexes excluded, but not always). This means, if you were to make an id "1-19", it would then do something like this:

MySQL: "Give me all of the records with an ID of 1*."
MySQL: "Give me all of the records with an ID of 1-*."
MySQL: "Give me all of the records with an ID of 1-1*."
MySQL: "Give me all of the records with an ID of 1-19."
MySQL: "Okay, PHP, here you go, I found your row(s)!"

Whereas with integers it looks at the value as a whole, so a record ID of 879 will result in:

MySQL: "Give me all of the records with an ID of 879."
MySQL: "Okay, PHP, here you go, I found your row(s)!"

Now you know, and knowledge is power. Lets get onto the schema.

Code:
CREATE TABLE `monsters` (
  `id` int(10) unsigned auto_increment,
  `name` varchar(32) null,
  `killed` int(10) unsigned default 0,
  PRIMARY KEY (`id`),
  UNIQUE KEY (`name`)
) ENGINE=InnoDB;

CREATE TABLE `items` (
  `id` int(10) unsigned auto_increment,
  `name` varchar(32) null,
  `dropped` int(10) unsigned default 0,
  PRIMARY KEY (`id`)
  UNIQUE KEY (`name`)
) ENGINE=InnoDB;

CREATE TABLE `items_monsters` (
  `items_id` int(10) unsigned default 0,
  `monsters_id` int(10) unsigned default 0,
  `count` int(10) unsigned default 0,
  PRIMARY KEY `item_monster` (`item_id`, `monster_id`)
) ENGINE=InnoDB;

ALTER TABLE `items_monsters`
  ADD CONSTRAINT `items_monsters_ibfk_1` FOREIGN KEY (`items_id`) REFERENCES `items` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
  ADD CONSTRAINT `items_monsters_ibfk_2` FOREIGN KEY (`monsters_id`) REFERENCES `monsters` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
First thing is first, as an added bonus I figured I'd toss in the InnoDB Storage Engine which allows for much greater control over the integrity of relational data, the ADD CONSTRAINT statements mean that if a monster or item is updated or deleted, it will recursively update or delete any entries inside of the "items_monsters" table. Good thing to have, no reason keeping relational data if one part of the relationship is deleted. Let MySQL do that work; PHP should pay no mind.

Secondly, UNIQUE KEY on the column "name" in the tables "items" and "monsters". It'd be a shame if we had two separate entries for "Staff of Legendary Destruction". That's just an extra measure of precaution.

Also, having the "unsigned" attribute on integers is great for primary keys and counts since they'll only be going up. Integer values, by standard, support negative and positive numbers. There won't be any negative number record ID's, so unsigned makes that datatype positive integers only. Good stuff.

Finally, explanation of why I personally like this more (not to say it can't be improved upon, because it can... just wait).

When items are dropped, you can check to see if the relationship already exists. If it doesn't, you make an INSERT for that relationship, otherwise you UPDATE a pre-existing row. Eventually as your database fills up with information, you'll be making less costly INSERT queries.

When it comes to your SELECT queries, yes, you will have to make some more complex queries with this set up, but you at least won't be going through hundreds of thousands of rows to get your results. Its the lesser of two evils, in my opinion. Something like this...

Code:
SELECT (`items_monsters`.`count` / `monsters`.`killed`) AS `ratio` FROM `items_monsters`
  LEFT JOIN `monsters` ON `items_monsters`.`monsters_id` = `monsters`.`id`
WHERE `items_monsters`.`item_id` = 32;
One of the more simple queries, will get the drop ratio for a particular item for all monsters that have dropped that item. The stopping point on this implementation is it becomes difficult when you're trying to get drop ratios of all monsters, ie: how likely is it that I'll get some item out of all the monsters ever killed? Some homework for you.

Hope this helped! Sorry if I explained too much (or not enough on the homework <3 ) of what you already knew. Just kinda figured someone might stumble upon it and find some useful info! :)

Last edited by Rizza : 07-02-2008 at 06:20 AM.
Rizza is offline  
Reply With Quote
The Following User Says Thank You to Rizza For This Useful Post:
TexasMd91 (07-02-2008)
Old 07-02-2008, 06:27 AM   #9 (permalink)
The Wanderer
 
Join Date: Jun 2008
Posts: 8
Thanks: 1
TexasMd91 is on a distinguished road
Default

I love that you didn't just post the code, you explained every single step and why you did these different things. It was one of the most interesting reads in this forum :P

I never even heard of using a 'CONSTRAINT' or a mysql engine. You learn things every day...

Last edited by TexasMd91 : 07-03-2008 at 06:02 AM.
TexasMd91 is offline  
Reply With Quote
Old 07-02-2008, 12:20 PM   #10 (permalink)
The Gregarious
Upcoming Programmer Inquisitive 
 
Join Date: Sep 2007
Posts: 655
Thanks: 82
Tanax is on a distinguished road
Default

Yea, I would go with adding something like a "quantity"-column on the drops table..
Tanax is offline  
Reply With Quote
Reply



Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are On


All times are GMT. The time now is 06:53 PM.

 
     

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2008, Jelsoft Enterprises Ltd.
Search Engine Optimization by vBSEO 3.1.0