- struct CMDrops
- {
- UINT id;
- UINT zuly;
- UINT zulyprob;
- vector<CDropInfo*> Drops;
- UINT level_min;//for map drops
- UINT level_max;//for map drops
- UINT level_boss;//for map drops
- UINT probmax;
disclaimer
Parts of this code is experimental but the heart of it has been working for almost a year now.
Also the drops table in the database is incomplete and you will most likely want to configure it your own way. If someone uses this code and sets up their own drop table in the database then maybe they could share it with us all. Switching drops lists is SOOOO easy in this system.
The code includes some changes to a few of the basic structures in the server, including the config files, startup and GM commands.
It has been a long time since i initially developed this mod so I am attempting to get every single internal change. I will try my best to get them all and I appologize if I miss some. Just let me know if something doesn't work and i will revise it.
Here goes.......
open Datatypes.h
find
After, add
- UINT prob;
Still in datatypes.h, find
- struct CMDrops
- {
- UINT id;
- UINT zuly;
- UINT zulyprob;
- vector<CDropInfo*> Drops;
- UINT level_min;//for map drops
- UINT level_max;//for map drops
- UINT level_boss;//for map drops
- UINT probmax;
after, add
- UINT prob;
- UINT map;
- UINT mob;
- UINT itemtype;
- UINT itemnum;
- UINT alt[8];
open WorldServer.h
find
- vector<CMDrops*> MDropList; // Drops List
After, add
- vector<CMDrops*> SkillbookList; // Skillbook drop list
still in WorldServer.h
find
- bool LoadDropsData( );
after, add
- bool LoadPYDropsData( );
- bool LoadSkillBookDropsData( );
still in WorldServer.h
find
- CDrop* GetDrop( CMonster* thismon );
after, add
- CDrop* GetPYDrop( CMonster* thismon, UINT droptype );
open WorldServer.cpp
find
- LoadDropsData( );
replace with
- // LoadDropsData( );
- // new drops routine load
- LoadPYDropsData( );
- LoadSkillBookDropsData( );
- // end of new drops data
This disables but does not remove the old drop system so that it is easy to switch back
still in Worldserver.cpp
find
- Config.PlayerDmg = ConfigGetInt ( file, "playerdmg", 120);
After, add
- Config.BlueChance = ConfigGetInt ( file, "bluechance", 5);
- Config.StatChance = ConfigGetInt ( file, "statchance", 5);
- Config.SlotChance = ConfigGetInt ( file, "slotchance", 5);
- Config.RefineChance = ConfigGetInt ( file, "refinechance", 5);
open Sockets.h
find
- int MonsterDmg;
after, add
- int BlueChance;
- int StatChance;
- int SlotChance;
- int RefineChance;
Sets up the variables to control the various config chances
open Startup.cpp
find (end of existing drops load code)
- MDropList.push_back( newdrop );
- }
- fclose(fh);
- return true;
- }
after, add
- bool CWorldServer::LoadPYDropsData( )
- {
- MDropList.clear();
- MYSQL_ROW row;
- MYSQL_RES *result = DB->QStore("SELECT id,type,min_level,max_level,prob,mob,map,alt FROM item_drops");
- if(result==NULL)
- {
- DB->QFree( );
- return false;
- }
- while(row = mysql_fetch_row(result))
- {
- CMDrops* newdrop = new (nothrow) CMDrops;
- assert(newdrop);
- newdrop->itemnum = atoi(row[0]);
- newdrop->itemtype = atoi(row[1]);
- newdrop->level_min = atoi(row[2]);
- newdrop->level_max = atoi(row[3]);
- newdrop->prob = atoi(row[4]);
- newdrop->mob = atoi(row[5]);
- newdrop->map = atoi(row[6]);
- char *tmp;
- if((tmp = strtok( row[7] , "|"))==NULL)
- newdrop->alt[0]=0;
- else
- newdrop->alt[0]=atoi(tmp);
- for(unsigned int i=1;i<8; i++)
- {
- if((tmp = strtok( NULL , "|"))==NULL)
- newdrop->alt[i]=0;
- else
- newdrop->alt[i]=atoi(tmp);
- }
- MDropList.push_back( newdrop );
- }
- DB->QFree( );
- Log( MSG_INFO, "PYDrops loaded" );
- return true;
- }
- bool CWorldServer::LoadSkillBookDropsData( )
- {
- //LogSkillbook data load
- MYSQL_ROW row;
- MYSQL_RES *result = DB->QStore("SELECT id,min,max,prob FROM list_skillbooks");
- if(result==NULL)
- {
- DB->QFree( );
- return false;
- }
- int c = 0;
- while(row = mysql_fetch_row(result))
- {
- c++;
- CMDrops* newdrop = new (nothrow) CMDrops;
- assert(newdrop);
- newdrop->itemnum = atoi(row[0]);
- newdrop->itemtype = 10;
- newdrop->level_min = atoi(row[1]);
- newdrop->level_max = atoi(row[2]);
- newdrop->prob = atoi(row[3]);
- SkillbookList.push_back( newdrop );
- }
- DB->QFree( );
- Log( MSG_INFO, "Skillbook Drops loaded" );
- return true;
- }
open Monsterfunctions.cpp
find
- CDrop* CMonster::GetDrop( )
- {
- GServer->GetDrop( this );
- }
replace with
- CDrop* CMonster::GetDrop( )
- {
- //added a new functionality to PYGetDrop.
- //now requires an input value for droptype. A droptype of 1 is a normal drop while a droptype of 2 can be sent to generate a drop while the monster is still alive
- //Code for these 'side drops' is still under development and will follow soon
- GServer->PYGetDrop( this, 1 );
- }
and finally the drop code itself.
open ServerFunctions.cpp
find
- }
- newdrop->item.gem = 0;
- return newdrop;
- }
The end of the existing drops code.
After, add
- // Build Drop the PY way
- CDrop* CWorldServer::GetPYDrop( CMonster* thismon, UINT droptype )
- { //if droptype = 1 then it is a normal drop. if it is 2 then it is a potential side drop.
- if(droptype == 2) // monster is still alive
- {
- // kicks it straight back if the monster is not dead
- if(thismon->thisnpc->side != 0) //perhaps we get a side drop??
- {
- if(GServer->RandNumber(0,100) < thismon->thisnpc->sidechance)
- {
- droptype = thismon->thisnpc->side;
- }
- else
- {
- return NULL; //No drop this time
- }
- }
- else
- {
- return NULL; //No drop this time
- }
- }
- CDrop* newdrop = new (nothrow) CDrop;
- if(newdrop==NULL)
- {
- Log(MSG_WARNING, "Error allocing memory [getdrop]" );
- return NULL;
- }
- newdrop->clientid = GetNewClientID( );
- newdrop->posMap = thismon->Position->Map;
- newdrop->pos = RandInCircle( thismon->Position->current, 3 );
- newdrop->droptime = time(NULL);
- newdrop->owner = thismon->MonsterDrop->firsthit;
- newdrop->thisparty = thismon->thisparty;
- ClearItem(newdrop->item);
- CPlayer* thisclient = GServer->GetClientByCID(thismon->MonsterDrop->firsthit);
- // code to modify drop chance for different levels
- //float charm = 0;
- float charm = (float)thisclient->Attr->Cha / 5;
- float leveldif = (float)thismon->thisnpc->level - (float)thisclient->Stats->Level;
- float droprate = (float)GServer->Config.DROP_RATE + charm; //basic server rate + extra for player charm
- float dropchance = (droprate + (droprate * 0.01 * leveldif));
- if(dropchance < 10) dropchance = 10; //always a small chance of a drop even when the mob is more than 20 levels beneath your own
- if(thismon->thisnpc->level == 1)
- dropchance = 80;
- if (GServer->RandNumber(0, 100)> dropchance)
- return NULL; // no drop here. not this time anyway.
- CItemType prob[MDropList.size()];
- bool isdrop = false;
- int n = 0;
- int test = 0;
- long int probmax = 0;
- int itemnumber[MDropList.size()];
- int itemtype[MDropList.size()];
- int probability[MDropList.size()];
- int alternate[MDropList.size()][8];
- if( thismon->IsGhost())
- {
- // Stuff to do if the mob is a ghost of any type
- int selection = 1 + rand()%10;
- if( selection <= 3 ) //MP water
- {
- newdrop->type = 2;
- itemnumber[n] = 399;
- itemtype[n] = 12;
- probability[n] = 10;
- probmax =10;
- n++;
- }
- else if( selection <=6 ) //HP water
- {
- newdrop->type = 2;
- itemnumber[n] = 400;
- itemtype[n] = 12;
- probability[n] = 10;
- probmax =10;
- n++;
- }
- else //skillbooks
- {
- for(int i=0; i<SkillbookList.size( ); i++)
- {
- newdrop->type = 2;
- CMDrops* thisdrop = GServer->SkillbookList.at(i);
- if(thisdrop->level_min <= thismon->thisnpc->level && thisdrop->level_max >= thismon->thisnpc->level)
- {
- itemnumber[n] = thisdrop->itemnum;
- itemtype[n] = thisdrop->itemtype;
- probability[n] = thisdrop->prob;
- probmax += thisdrop->prob;
- n++;
- }
- }
- }
- }
- else
- {
- int randv = RandNumber( 1, 100);
- if(randv <= 30)//30% zuly drop instead of item drop
- {
- newdrop->type = 1; //Drop Zuly
- newdrop->amount = thismon->thisnpc->level * 5 * Config.ZULY_RATE + RandNumber( 1, 10 );
- return newdrop;
- }
- // Stuff to do if the mob isn't a ghost
- int randomdrop = GServer->RandNumber(1, 100);
- //enable the next line for debug purposes if you want to confirm a drop is working.
- //Log(MSG_INFO, "Mob type %i. Map = %i. Level = %i", thismon->montype, thismon->Position->Map,thismon->thisnpc->level);
- for(int i=0; i<MDropList.size( ); i++)
- {
- isdrop=false;
- CMDrops* thisdrop = GServer->MDropList.at(i);
- if(thisdrop->mob == thismon->montype)
- {
- //Mob drop possible.
- test = GServer->RandNumber(1, 1000);
- if(test < thisdrop->prob)
- {
- isdrop = true;
- //item will be added to the short list
- }
- }
- if(thisdrop->map == thismon->Position->Map)
- {
- //Map drop possible.
- test = GServer->RandNumber(1, 1000);
- if(thismon->thisnpc->level == 1)
- test = GServer->RandNumber(1, 10000); // make it less likely to get map drops from event mobs
- if(test < thisdrop->prob)
- {
- isdrop = true;
- //item will be added to the short list
- }
- }
- if(thismon->thisnpc->level >= thisdrop->level_min && thismon->thisnpc->level <= thisdrop->level_max)
- {
- //Level drop possible
- test = GServer->RandNumber(1, 1000);
- if(test < thisdrop->prob)
- {
- isdrop = true;
- //item will be added to the short list
- }
- }
- if(isdrop == true) //Add item to the short list
- {
- if(droptype != 1) //side drops only. Skip if the item is not a match for side type
- {
- if(itemtype[n] != droptype)continue;
- }
- //droptype 1 is a regular drop
- itemnumber[n] = thisdrop->itemnum;
- itemtype[n] = thisdrop->itemtype;
- //probability[n] = thisdrop->prob;
- alternate[n][0] = 0;
- for(int i=1;i<8;i++)
- {
- alternate[n][i] = thisdrop->alt[i];
- }
- n++;
- }
- }
- }
- int newn = n;
- if(n == 0)
- return NULL;
- int maxitems = n;
- //maxitems is the number of items in the shortlist
- // randomize the item from the shortlist. items get equal chance
- n = GServer->RandNumber(0, maxitems);
- newdrop->item.itemnum = itemnumber[n];
- newdrop->item.itemtype = itemtype[n];
- newdrop->type = 2;
- newdrop->item.lifespan = 10 + rand()%80;
- float dmod = 0; //random number from 0 to 100 made up of 4 sub numbers to keep
- //the average value near to 50
- for(int i=0; i<4; i++)
- {
- float r1 = rand()%20;
- dmod += r1;
- }
- newdrop->item.durability = 10 + (int)dmod;
- if( newdrop->item.itemtype == 8 || newdrop->item.itemtype == 9 )
- {
- //This probability is now configurable from WorldServer.conf
- int psocked = rand()%101; //Probability of finding a socketed item
- if( psocked < Config.SlotChance) //default should be around 5% needs to be rare
- {
- newdrop->item.socketed = true;
- }
- else
- {
- newdrop->item.socketed = false;
- }
- }
- else
- {
- newdrop->item.socketed = false;
- }
- newdrop->item.appraised = false;
- newdrop->item.stats = 0;
- newdrop->item.count = 1;
- //chamod = a modifier based on the character's CHA stat. Increases the number of drops
- int chamod = (int)floor(thisclient->Attr->Cha / 20);
- if(chamod <0) chamod = 0;
- int basedrop = 6 + chamod; //Base number of items to be dropped. add CHA to increase this.
- if( newdrop->item.itemtype == 10 || newdrop->item.itemtype == 12 )
- {
- newdrop->item.count = RandNumber(0, basedrop);
- if(thismon->thisnpc->level == 1 && newdrop->item.count > 6) newdrop->item.count = 6; //limit the drop rate of items from level 1 event mobs
- if(newdrop->item.count==0)
- newdrop->item.count = 1;
- if(newdrop->item.itemtype == 10)
- {
- if(newdrop->item.itemnum >=441 && newdrop->item.itemnum <= 888)// skillbooks
- newdrop->item.count = 1; // just one skill book per drop
- }
- if(newdrop->item.itemtype == 12)
- {
- if(newdrop->item.itemnum > 300 && newdrop->item.itemnum < 360) //bullets get a lot higher count.
- {
- newdrop->item.count *= 10;
- newdrop->item.count += 10;
- }
- }
- }
- else if( newdrop->item.itemtype >1 && newdrop->item.itemtype !=7 && newdrop->item.itemtype < 10)
- {
- // check to see if the item will be refined
- int prefine = rand()%100; //Probability of finding a refined item
- if(prefine < Config.RefineChance) // default = 5%
- {
- int refinelevel = rand()%101; //which level of refine do we actually get
- if( refinelevel < 5) //default 5%
- newdrop->item.refine = 3 * 16;
- else if( refinelevel < 11 ) //10%
- newdrop->item.refine = 2 * 16;
- else // 90%
- newdrop->item.refine = 16;
- }
- else //99%
- newdrop->item.refine = 0;
- // will the item be a blue?
- int blue = 0;
- int bluechance1 = RandNumber( 1, 100);
- int bluechance2 = Config.BlueChance + chamod;
- // will the items get stats? All blue items will.
- int pstats = rand()%101; //Probability of the item having stats. default = 5%
- //This probability is now configurable from WorldServer.conf. CHA also has an effect
- if(bluechance1 < bluechance2) // some percentage of drops will be specials or blues whenever one is available.
- {
- Log( MSG_INFO, "Selected a blue item");
- int p = 1;
- while(alternate[n][p] != 0 && p < 8)
- {
- p++;
- }
- if(p > 1) // blues available for this item
- {
- p--;
- int bluenum = RandNumber( 1, p);
- newdrop->item.itemnum = alternate[n][bluenum];
- pstats = 1; //make sure we get stats for this item
- }
- /*else
- {
- //Sorry blue item not available for this item
- }*/
- }
- //Uniques count as blues.
- if(newdrop->item.itemnum > 900)pstats = 1; //make sure we get stats for this unique item
- if( pstats < Config.StatChance) // default 5%
- newdrop->item.stats = rand()%300;
- }
- newdrop->item.gem = 0;
- return newdrop;
- }
Thanks to Lmame who spotted that i did not include this main code the first time.
I'm pretty sure these are all the actual code changes. If you have any issues with this then please let me know. The most likely thing i may have possibly left out is registering a variable or structure so if you get a compile error saying that a structure has no such datatype or something like that then this is what has happened.
Now for the other stuff
Open worldserver.conf in a suitable text editor.
find
- monsterdmg=100
After add
- bluechance=5
- statchance=5
- slotchance=5
- refinechance=5
Note: the values here are percentage chances of the dropped item being a 'blue', having refines, stats etc. added to it. 5% may be too low for some of you so feel free to configure them however you like.
Databases.
This drops database has the correct structure but the values are out of date. Specifically, some of the drop chances in the 'prob' field are greater than 1000. This will not cause any problems. It is just that any value greater than 1000 is treated as being 1000 by the drops code.
For example if you have an item with a 'prob' of 1000 and another with 150,000 they will both be read as 1000 by the server and therefore will both have a 100% chance of being added to the item drop shortlist. (more explanation of operation of this mod will follow)
The skillbooks database table contains ALL EVO skillbooks. they are segregated by player level at which they can be used. They will then be dropped by ghosts which are within a range of +/- 15 levels or so of that level. Some server owners may want to delete common skill books from this list and just leave the rare ones. It's your choice. For use with OsiRose the entire skill list will need to be recompiled as they are all completely different.
I also have a little utility built in Microsoft Excel which may help you to design and build your own custom drops table. (provided you have Excel that is)
This contains a lookup table that makes finding the item type and id really easy.
Using the 'hidden drops' tab, you can build a table of item drops that directly corresponds to the SQL drops list.
if you enter an item id and item type, the item name will be displayed automatically. The same works for the monster id and map id.
Once you have a suitable drops list, just copy columns a through i (excluding the header row) into a blank workbook and save it as a CSV. From there you can import it directly into your database.
Easy isn't it?
Just a few little notes on how to use the drops system to your best advantage.
When a monster is killed, the entire drops list is parsed inside the server. ANY item which could potentially be dropped from this mob is identified and a random number is generated (1 to 1000) If this number is smaller than the item's 'prob' value then the item is added to a shortlist of potential drops.
If you set the item's 'prob' to 1000 (or greater) then you absolutely guarantee that the item will be added to the short list.
Once all the drops have been parsed, the server then takes a look to see how many items exist inside the shortlist. Let's say for example 10 items made the grade and were included.
The server now generates a random number that corresponds to one of those 10 items. Each has an equal chance.
The final choice is returned to the program and will be dropped.
- If you have an item that you want to always be added to short list (eg fruit) make its 'prob' value 1000
- don't make too many items with high 'prob' values that are able to fall from any particular mob. The shortlist can handle any number of items but you will just end up diluting the chance of any particular item dropping if the shortlist contains too much stuff.
- My item_drops database works largely by level range drops as it is currently set up. You might want to change this to make items drop by mob. Please feel free to experiment and configure it however you like. If you want a mini Jelly bean to drop a Turak Axe then that is your choice. It may suck but its still your choice.
- You will note that in my drops code, your actual drop chance from any specific mob is largely dependent on the level difference between you and the mob. If the mob is exactly your level (Yellow name) then your chance of getting something will be 80%. If the thing is red or purple then it will be 100% but if it is lower than you then the chance rapidly drops away down to a minimum level of 10% at 20 levels below you. Yes this means that a level 200+ character will still get the occasional drop from a mini-jelly bean. If you think this is a crap idea just find this line in the new GetPYdrops function and comment it out
- if(dropchance < 10) dropchance = 10; //always a small chance of a drop even when the mob is more than 20 levels beneath your own
- The number of items that drop in a useitem or mat drop is proportional to your CHA stat. More CHA = more drops (not drop chance, just drop amount). I could potentially alter the drop chance too if requested. just never got around to it. I like making character stats actually DO something in the game
Well that's about it. Let me know if you have problems or if you would like modifications added.
I will also be releasing a revised and more up to date drops database shortly but in the meantime please fell free to design your own custom drop list.
Enjoy
Code and design by PurpleYouko, Kuro-Tejina.com and SpiritFox Productions