View previous topic :: View next topic |
Author |
Message |
Kato
Joined: 05 Mar 2010 Posts: 47
|
Posted: Wed May 18, 2011 4:05 Post subject: Database driven loot system? |
|
|
Hi all,
Anyone knows if it would make sense to build a loot system relying on an object table(MySQL) rather than using CreateItemOnObject() or CopyItem()?
Currently my loot system retrieves items from hidden chests and uses CopyItem() to create the item in object's inventory(just as many loot systems do). It works fine, but requires one chest for each item type(gems, weapons etc...), and the more items I add to the chests, the more ressources the system uses, since for each stored item(the module is growing bigger and bigger with each added item), an object variable + an int variable for indexing are stored on the chest for later retrieval.
I know that CopyItem() is faster than CreateItemOnObject(), but what about retrieving the object(randomly) from a table(BLOB type, if I recall) with MySQL? Would it be fast enough to prevent any lag?
Thanks! |
|
Back to top |
|
|
virusman
Joined: 30 Jan 2005 Posts: 1020 Location: Russia
|
Posted: Wed May 18, 2011 10:16 Post subject: |
|
|
It won't be any faster than CopyItem(); the main advantage of DB is flexibility. In terms of performance, it will be the same or a bit slower than chests. _________________ In Soviet Russia, NWN plays you! |
|
Back to top |
|
|
Kato
Joined: 05 Mar 2010 Posts: 47
|
Posted: Wed May 18, 2011 19:04 Post subject: |
|
|
Thank you, virusman. When you say a bit slower, do you mean that it would be feasible nonetheless? I'm asking this because my goal is to get rid of those chests and items and variables, relying solely on the database for my loot system. Now, I know that some builders prefer to store the resref instead of the whole object, yet this implies using CreateItemOnObject().
So, wich option would create the less lag? Retrieving the resref + CreateItemOnObject() or simply retrieving the whole object from the DB, knowing that the items are created in the OnDeath event of monsters and the OnOpen event of some placeables?
Thank you very much for taking the time to answer this |
|
Back to top |
|
|
virusman
Joined: 30 Jan 2005 Posts: 1020 Location: Russia
|
Posted: Wed May 18, 2011 19:52 Post subject: |
|
|
Database latency is probably higher than object->GFF conversion, so I'd still use chests for caching (populate with items from database on module load, then reload cache when DB contents are changed).
The main question here is how flexible you want it to be. There is no point in switching from in-game to DB objects solely for performance. _________________ In Soviet Russia, NWN plays you! |
|
Back to top |
|
|
Kato
Joined: 05 Mar 2010 Posts: 47
|
Posted: Wed May 18, 2011 22:10 Post subject: |
|
|
Thank you, I'll stick to the chests for now, yet I still wonder what will happen when those will hold 500+ items... |
|
Back to top |
|
|
Skywing
Joined: 03 Jan 2008 Posts: 321
|
Posted: Thu May 19, 2011 9:27 Post subject: |
|
|
Note that both the item creation routines and the database deserialization support involve unpacking an object from its saved GFF form - so you pay the cost for that work either way. As virusman notes, with the database, you just have the additional work of pulling things from MySQL. |
|
Back to top |
|
|
eeriegeek
Joined: 07 Jan 2008 Posts: 59
|
Posted: Fri Jun 10, 2011 0:08 Post subject: |
|
|
I am currently generating all treasure drops via database look-ups on my (always under development) PW. There has not been a problem with performance with 6500 (2200 items in 3 grouping systems) items in the table, but the PW is admittedly lightly loaded. You have to be careful how you design your random look-ups so you don't end up doing full table scans for each drop. |
|
Back to top |
|
|
Kato
Joined: 05 Mar 2010 Posts: 47
|
Posted: Fri Jun 10, 2011 2:31 Post subject: |
|
|
Indeed, thank you for this precison. |
|
Back to top |
|
|
elven
Joined: 28 Jul 2006 Posts: 259 Location: Germany
|
Posted: Mon Jun 13, 2011 12:03 Post subject: |
|
|
Querying a good database doesn't really involve that much overhead, latency-wise. Most things will be in RAM and the worst to happen is some indirection and hitting the query planner. The overhead for de/serializing gff data will be much much bigger than doing a simple query on a properly indexed table. Either way, it doesn't matter - it's fast enough in any case even under heavy load.
I as well run a loot system based on wildcard-matched resrefs, with random chance for various items and lots of flexibility. No problems there, and it really cut down on maintenance regarding utc templates. |
|
Back to top |
|
|
Kato
Joined: 05 Mar 2010 Posts: 47
|
Posted: Tue Jun 14, 2011 20:46 Post subject: |
|
|
Thank you for the infos, Elven. By the way, would you agree to give me some clues regarding the formula used to randomly retrieve the "weighted" items, please? (My table would hold the fields "resref", "category", "weight" and "rarity", the last one being used to update weights upon adding/removing items from any category)
Thank you!
Kato |
|
Back to top |
|
|
elven
Joined: 28 Jul 2006 Posts: 259 Location: Germany
|
Posted: Tue Jun 14, 2011 21:00 Post subject: |
|
|
Code: |
p=# \d loot_aggregator
View "public.loot_aggregator"
Column | Type | Modifiers
-------------+-------------------+-----------
loot | text |
replace | integer |
racial_type | integer |
resref | character varying |
tag | character varying |
name | character varying |
uniq | boolean |
log | boolean |
View definition:
SELECT loot_chains.loot, loot_chains.replace, loot_chains.racial_type, loot_chains.resref, loot_chains.tag, loot_chains.name, loot_chains.uniq, loot_chains.log
FROM loot_chains
WHERE (random() - loot_chains.chance) <= 0::double precision
ORDER BY loot_chains.racial_type, loot_chains.tag, loot_chains.resref, loot_chains.name, loot_chains."order";
p=# select * from loot_chains limit 2;
id | racial_type | resref | tag | name | lvar | order | replace | loot | chance | log | uniq | last_update_by
----+-------------+-------------+-----+------+------+-------+---------+----------------+--------+-----+------+----------------
35 | 12 | fk_krieger | % | % | % | 1 | 0 | kurzschwert001 | 0.1 | f | f |
41 | 12 | fk_schuetze | % | % | % | 1 | 0 | kurzbogen | 0.15 | f | f |
(2 rows)
p=# select * from loot_aggregator where 'fk_krieger' like resref limit 2;
loot | replace | racial_type | resref | tag | name | uniq | log
--------------+---------+-------------+--------+-----+------+------+-----
food_raw_002 | 0 | 8 | % | % | % | f | f
goblinkopf | 0 | 12 | % | % | % | f | f
(2 rows)
|
Code: |
void GenerateNPCTreasure(object oNPC = OBJECT_SELF) {
_DEBUG("lootchain", 1, "getfor: " + GetName(oNPC) + " -> " + GetTag(oNPC) + ":" + GetResRef(oNPC));
object oPlayer = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, oNPC);
// First, clean the inventory.
if (gvGetInt("treasure_clean_npcs")) {
object o = GetFirstItemInInventory(oNPC);
while (GetIsObjectValid(o)) {
if (!GetPlotFlag(o)) {
DestroyObject(o);
}
o = GetNextItemInInventory(oNPC);
}
}
// Select all loot chains.
pQ(
"select replace, loot, uniq, log from loot_aggregator where " +
"(racial_type = -1 or racial_type = " + pSi(GetRacialType(oNPC)) + ") and " +
pSs(GetResRef(oNPC)) + " like resref and " +
pSs(GetTag(oNPC)) + " like tag and " +
pSs(GetName(oNPC)) + " like name;"
);
string loot;
int replace, uniq, log;
string tocreate = "";
while (pF()) {
replace = pGi(1);
loot = pGs(2);
uniq = pGb(3);
log = pGb(4);
// it is unique. Do not spawn, do not do the replacing, because the player already has one.
if (uniq && GetIsObjectValid(GetItemResRefPossessedBy(oPlayer, loot)))
continue;
_DEBUG("lootchain", 1, "result[]: " + loot + " << " + itoa(replace) + " -> '" + tocreate + "'");
// Skim off replaced loots before this one gets added.
while (replace > 0) {
int idx = strpos(tocreate, "#");
if (-1 == idx)
break;
_DEBUG("lootchain", 1, "replace <" + itoa(replace) + ">: " + substr(tocreate, 0, idx));
tocreate = substr(tocreate, idx + 1, strlen(tocreate));
_DEBUG("lootchain", 1, "isnow: " + tocreate);
replace--;
}
if (log) {
audit("loot", oPlayer, audit_fields("resref", loot), "loot_chains");
WriteTimestampedLogEntry("lootchain: Spawning '" + loot + "' for " + GetName(oPlayer));
}
tocreate = loot + "#" + tocreate;
}
// Skim off the last hash that gets invariably added.
tocreate = substr(tocreate, 0, strlen(tocreate) - 1);
_DEBUG("lootchain", 1, "isnow at end: " + tocreate);
DelayCommand(1.0f, CreateChainedOnObjectByResRefString(tocreate, oNPC));
}
|
|
|
Back to top |
|
|
Kato
Joined: 05 Mar 2010 Posts: 47
|
Posted: Tue Jun 14, 2011 23:05 Post subject: |
|
|
Thank you very much!!! |
|
Back to top |
|
|
Baaleos
Joined: 02 Sep 2007 Posts: 830
|
Posted: Thu Jun 16, 2011 16:28 Post subject: I wouldnt |
|
|
I wouldnt replace the chest system completely, at least, not at first. I instead chose to supliment it.
http://rhun.wikia.com/wiki/Development_:_Dynamic_Loot_Generator
This is a walk through of how our Dynamic Loot generator works.
It works on the simple premise of
1 - Select a random item type/resref from the stored item details database table.
( select * from item_templates order by rand() limit 1 )
This gets us the resref of the template.
We create it.
We can then get its item type, and then we can further query the database for the item properties we desire for that item type.
eg - Select the itemproperty int value for Damage Bonus 2d12 Divine for item type Long Sword etc....
Our system works well, and doesnt have noticable lag.
Note - The sql Queries are blocking, so its important to remember that the further away your db is, eg- off site, then the longer the lag/delay between query start, and query end will be. |
|
Back to top |
|
|
Kato
Joined: 05 Mar 2010 Posts: 47
|
Posted: Thu Jun 16, 2011 20:26 Post subject: |
|
|
Thank you, Baaleos. Since you mention retrieving resrefs with RAND(), I seize the opportunity to share/evaluate a formula/query I found after a lot of researches:
SELECT resref FROM items WHERE category = (SELECT category FROM items ORDER BY -LOG(1.0 - RAND()) / avail limit 1) ORDER BY -LOG(1.0 - RAND()) / weight LIMIT 1
Now the goal is to retrieve a random weighted item resref from a random weighted category. The higher the weight, the higher the chance for the resref to be picked. Same for category, this time the "weight" being the field "avail".
The query is made for a table with fields: resref, category, weight, avail. For weight and avail, I currently use values between 1 and 99. The query works, and it takes 0,0472 seconds to execute, I don't know if it's considered fast or not...
However, for categories, the subquery is not flawless, it will tend to pick categories containing the most resrefs more often. Any idea on how to improve the subquery? (or the whole query if it's wrong)
Thank you!
Kato |
|
Back to top |
|
|
Kato
Joined: 05 Mar 2010 Posts: 47
|
Posted: Fri Jun 17, 2011 6:03 Post subject: |
|
|
I should be ok, thank you. A MySQL expert advised me to create 2 tables, simplifying the queries a lot. Thank you all for reading and answering this. |
|
Back to top |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|