Jump to content

Common Drop Item Renewal(Make level-based drops in the easiest way)


Recommended Posts

Let's assume common_drop_item.txt is almost never used by anyone, but for those who are thinking about using it, here's some (potential) bugs I found inside.

Those may or may be not bugs, it depends on what use you want to make for common_drop_item.

I think the most useful use would be to set drops easily depending on mob and users' levels, without making it mob-by-mob(mob_drop_item.txt), for those who wish to rewrite all the drops from scratch.

It would be a good replace of settings drops via LUA(which may cause performance issues, and also you have to deal also with empire and user's drop rates) too.

 

The bugs I found are:

1) Min and max_level are only based on the user's level. No matter which mob level it is(e.g. min_level=30, max_level=100: if you kill a wolf(level 3)  you will get the item anyway).

2) Consequence of (1), if player level is above or below 15 levels(standard level limit for dropping items) than mob's, the item will be dropped anyway.

3) The "count" was declared but never applied, only one item used to be dropped.

And so on, thus I decided to rewrite the entire function and make it useful.

 

Renewal Features:

1) If mob level is below or above than for example 15 levels than the common_drop_item, the item won't be dropped.

2) If mob level is above or below than certain levels from user, the item won't be dropped.

3) Just like standard drops, there will be applied handicaps on drop rate %, based on user and mob levels(There is a mid-level between start and end, the farthest difference between this and user or mob level is, the less drop rate % will be)

4) Drops can be also based on mob ranks(like common drop item's concern), and the ranks can be indicated.

5) You can choose in which map the item can be dropped(0=any).

6) Multiple items can be set in the same settings.

Other features can be found in the function.

 

Understanding structure:

The structure is similar to mob_drop_item's one, but with new(and different) variables, which are:

"map": the map index, default 0

"lv_start" and "lv_end": min and max (user)level, default 1-999

"min_rank": and "max_rank":  min and max (mob)Rank, starting from 0, which is the 1st rank, default 0-3

"rank_gain": if there are more ranks, each rank will have more % chance to drop the item, based on the previous rank's percent, default 20

"level_diff": the minimum level difference between user and mobs' levels, and also the minimum level difference between mob and lv_start or lv_end, default 15

For item declaration, the syntax is the same as mob_drop_item, with 3 chars:
ID  VNUM  COUNT  PERCENT

 

Note:

This system has not been tested in real servers, and it may have some bugs or can be optimized, so please analyze it if you are planning to user it in real servers.

If you find any bug or you have any advice, please share it here.

 

Replace inside ITEM_MANAGER::CreateDropItem function in item_manager.cpp

Spoiler



bool ITEM_MANAGER::CreateDropItem(LPCHARACTER pkChr, LPCHARACTER pkKiller, std::vector<LPITEM> &vec_item)
{
	int iLevel = pkKiller->GetLevel();

	int iDeltaPercent, iRandRange;
	if (!GetDropPct(pkChr, pkKiller, iDeltaPercent, iRandRange))
		return false;

	BYTE bRank = pkChr->GetMobRank();
	LPITEM item = NULL;

	int victim_level = pkChr->GetLevel();

	int level_diff = victim_level - iLevel;

	int start_search = 2;

	int drop_count = 1;

	// Common Drop Items
	std::vector<CItemDropInfo> it = g_vec_pkCommonDropItem[bRank];

	size_t itSize = it.size();

	if (pkKiller->IsEquipUniqueGroup(UNIQUE_GROUP_DOUBLE_ITEM))
		start_search *= 2;

	if(start_search >= number(start_search,10)) drop_count = 2;

	for(int i = 0; i < drop_count; i++){

		int itChoose = number(0, itSize()-1);

		const CItemDropInfo &c_rInfo = it[itChoose];

		int common_level_diff = c_rInfo.m_iLevelDiff;

		if (iLevel < c_rInfo.m_iLevelStart || iLevel > c_rInfo.m_iLevelEnd)
		{
			//sys_log(0,"You cannot drop because of level : your %i, min %i, max %i", iLevel, c_rInfo.m_iLevelStart, c_rInfo.m_iLevelEnd);
			continue;
		}
		else if (level_diff > common_level_diff || level_diff < -common_level_diff)
		{
			//sys_log(0,"You cannot drop because of your&mob level differences : yourlvdiff %i, required %i", level_diff, common_level_diff);
			continue;
		}
		else if (victim_level < c_rInfo.m_iLevelStart - common_level_diff || victim_level > c_rInfo.m_iLevelEnd + common_level_diff)
		{
			//sys_log(0,"You cannot drop because of mob level differences : moblv %i, min %i, max %i", victim_level, c_rInfo.m_iLevelStart-common_level_diff, c_rInfo.m_iLevelEnd-common_level_diff);
			continue;
		}
		else if (c_rInfo.m_MapIndex != 0 && pkKiller->GetMapIndex() != c_rInfo.m_MapIndex)
		{
			//sys_log(0,"You cannot drop, map_index=%i vs %i", pkKiller->GetMapIndex(), c_rInfo.m_MapIndex);
			continue;
		}

		int mid_level = round((c_rInfo.m_iLevelEnd + c_rInfo.m_iLevelStart) / 2);
		int percent_handicap = 0;

		percent_handicap += round((mid_level - victim_level) * 1.3); //decrease percent if mob level <> mid level, if difference <> 76, percent = 100
		if (percent_handicap < 0)
			percent_handicap = -percent_handicap;

		percent_handicap += round((mid_level - iLevel) * 2); //decrease percent if player level <> mid level, if difference is <> 50, percent = 100
		if (percent_handicap < 0)
			percent_handicap = -percent_handicap;

		percent_handicap += round((victim_level - iLevel) * 2.6); //decrease percent if mob level <> player level, if difference is <> 15, percent = 100

		//worst case: approx. -90% if all handicaps <> 15, optimal case: mob level = player level = mid level

		if (percent_handicap < 0)
			percent_handicap = -percent_handicap;
		else if (percent_handicap > 100)
			percent_handicap = 100; //item is not dropped.

		else if (percent_handicap > 0 && percent_handicap < 1)
			percent_handicap = 1; //optional fallback, will not appen anyway because of round.

		int percent_total = round(c_rInfo.m_iPercent - ((c_rInfo.m_iPercent * percent_handicap) / 100));

		int iPercent = percent_total;

		iPercent = iPercent * (100 + CPrivManager::instance().GetPriv(pkKiller, PRIV_ITEM_DROP)) / 100;

		/*
		std::random_device os_seed;
		const uint_least32_t seed = os_seed();
		std::mt19937 generator(seed);
		std::uniform_int_distribution< uint_least32_t > distribute(1, c_rInfo.m_MaxRange);
		*/

		//sys_log(0, "CreateDropItem %d ~ %d %d(%d)", c_rInfo.m_iLevelStart, c_rInfo.m_iLevelEnd, c_rInfo.m_dwVnum, iPercent, c_rInfo.m_iPercent);

		if (iPercent >= number(1, c_rInfo.m_MaxRange))
		{
			TItemTable *table = GetTable(c_rInfo.m_dwVnum);

			//sys_log(0,"Item Dropped: Current Percent: %i, MaxRange %i, percent_total %i", iPercent, c_rInfo.m_MaxRange, percent_total);

			if (!table)
			{
				//sys_err("No table for %d", c_rInfo.m_dwVnum);
				continue;
			}

			item = NULL;

			if (table->bType == ITEM_POLYMORPH)
			{
				if (c_rInfo.m_dwVnum == pkChr->GetPolymorphItemVnum())
				{
					item = CreateItem(c_rInfo.m_dwVnum, 1, 0, true);

					if (item)
						item->SetSocket(0, pkChr->GetRaceNum());
				}
			}
			else
				item = CreateItem(c_rInfo.m_dwVnum, c_rInfo.m_iCount, 0, true);

			if (item)
				vec_item.push_back(item);
		}
	}
	// Drop Item Group
	{
		itertype(m_map_pkDropItemGroup) it;
		it = m_map_pkDropItemGroup.find(pkChr->GetRaceNum());

		if (it != m_map_pkDropItemGroup.end())
		{
			typeof(it->second->GetVector()) v = it->second->GetVector();

			for (DWORD i = 0; i < v.size(); ++i)
			{
				int iPercent = (v[i].dwPct * iDeltaPercent) / 100;

				if (iPercent >= number(1, iRandRange))
				{
					item = CreateItem(v[i].dwVnum, v[i].iCount, 0, true);

					if (item)
					{
						if (item->GetType() == ITEM_POLYMORPH)
						{
							if (item->GetVnum() == pkChr->GetPolymorphItemVnum())
							{
								item->SetSocket(0, pkChr->GetRaceNum());
							}
						}

						vec_item.push_back(item);
					}
				}
			}
		}
	}

	// MobDropItem Group
	{
		itertype(m_map_pkMobItemGroup) it;
		it = m_map_pkMobItemGroup.find(pkChr->GetRaceNum());

		if (it != m_map_pkMobItemGroup.end())
		{
			CMobItemGroup *pGroup = it->second;

			// MOB_DROP_ITEM_BUG_FIX
			// 20050805.myevan.MobDropItem �� �������� ���� ��� CMobItemGroup::GetOne() ���ٽ� ���� �߻� ����
			if (pGroup && !pGroup->IsEmpty())
			{
				int iPercent = 40000 * iDeltaPercent / pGroup->GetKillPerDrop();
				if (iPercent >= number(1, iRandRange))
				{
					const CMobItemGroup::SMobItemGroupInfo &info = pGroup->GetOne();
					item = CreateItem(info.dwItemVnum, info.iCount, 0, true, info.iRarePct);

					if (item)
						vec_item.push_back(item);
				}
			}
			// END_OF_MOB_DROP_ITEM_BUG_FIX
		}
	}

	// Level Item Group
	{
		itertype(m_map_pkLevelItemGroup) it;
		it = m_map_pkLevelItemGroup.find(pkChr->GetRaceNum());

		if (it != m_map_pkLevelItemGroup.end())
		{
			if (it->second->GetLevelLimit() <= (DWORD)iLevel)
			{
				typeof(it->second->GetVector()) v = it->second->GetVector();

				for (DWORD i = 0; i < v.size(); i++)
				{
					if (v[i].dwPct >= (DWORD)number(1, 1000000 /*iRandRange*/))
					{
						DWORD dwVnum = v[i].dwVNum;
						item = CreateItem(dwVnum, v[i].iCount, 0, true);
						if (item)
							vec_item.push_back(item);
					}
				}
			}
		}
	}

	// BuyerTheitGloves Item Group
	{
		// by mhh �ϴ� �ӽ÷� �Ϻ��� �Ϲ� drop �� �����ϰ� ����
		if (pkKiller->GetPremiumRemainSeconds(PREMIUM_ITEM) > 0 ||
			pkKiller->IsEquipUniqueGroup(UNIQUE_GROUP_DOUBLE_ITEM))
		{
			itertype(m_map_pkGloveItemGroup) it;
			it = m_map_pkGloveItemGroup.find(pkChr->GetRaceNum());

			if (it != m_map_pkGloveItemGroup.end())
			{
				typeof(it->second->GetVector()) v = it->second->GetVector();

				for (DWORD i = 0; i < v.size(); ++i)
				{
					int iPercent = (v[i].dwPct * iDeltaPercent) / 100;

					if (iPercent >= number(1, iRandRange))
					{
						DWORD dwVnum = v[i].dwVnum;
						item = CreateItem(dwVnum, v[i].iCount, 0, true);
						if (item)
							vec_item.push_back(item);
					}
				}
			}
		}
	}

	// ����
	if (pkChr->GetMobDropItemVnum())
	{
		itertype(m_map_dwEtcItemDropProb) it = m_map_dwEtcItemDropProb.find(pkChr->GetMobDropItemVnum());

		if (it != m_map_dwEtcItemDropProb.end())
		{
			int iPercent = (it->second * iDeltaPercent) / 100;

			if (iPercent >= number(1, iRandRange))
			{
				item = CreateItem(pkChr->GetMobDropItemVnum(), 1, 0, true);
				if (item)
					vec_item.push_back(item);
			}
		}
	}

	if (pkChr->IsStone())
	{
		if (pkChr->GetDropMetinStoneVnum())
		{
			int iPercent = (pkChr->GetDropMetinStonePct() * iDeltaPercent) * 400;

			if (iPercent >= number(1, iRandRange))
			{
				item = CreateItem(pkChr->GetDropMetinStoneVnum(), 1, 0, true);
				if (item)
					vec_item.push_back(item);
			}
		}
	}

	if (pkKiller->IsHorseRiding() &&
		GetDropPerKillPct(1000, 1000000, iDeltaPercent, "horse_skill_book_drop") >= number(1, iRandRange))
	{
		sys_log(0, "EVENT HORSE_SKILL_BOOK_DROP");

		if ((item = CreateItem(ITEM_HORSE_SKILL_TRAIN_BOOK, 1, 0, true)))
			vec_item.push_back(item);
	}

	if (GetDropPerKillPct(100, 1000, iDeltaPercent, "lotto_drop") >= number(1, iRandRange))
	{
		DWORD *pdw = M2_NEW DWORD[3];

		pdw[0] = 50001;
		pdw[1] = 1;
		pdw[2] = quest::CQuestManager::instance().GetEventFlag("lotto_round");

		// ����� ���� ������ �����Ѵ�
		DBManager::instance().ReturnQuery(QID_LOTTO, pkKiller->GetPlayerID(), pdw,
										  "INSERT INTO lotto_list VALUES(0, 'server%s', %u, NOW())",
										  get_table_postfix(), pkKiller->GetPlayerID());
	}

	//
	// ����� ��� ������
	//
	CreateQuestDropItem(pkChr, pkKiller, vec_item, iDeltaPercent, iRandRange);

	for (itertype(vec_item) it = vec_item.begin(); it != vec_item.end(); ++it)
	{
		LPITEM item = *it;
		DBManager::instance().SendMoneyLog(MONEY_LOG_DROP, item->GetVnum(), item->GetCount());
	}

	return vec_item.size();
}

 

 

Replace entire function inside item_manager_read_tables.cpp

Spoiler





bool ITEM_MANAGER::ReadCommonDropItemFile(const char* c_pszFileName){
	CTextFileLoader loader;

	if (!loader.Load(c_pszFileName)){
		return false;
	}

	for (DWORD i = 0; i < loader.GetChildNodeCount(); ++i)
	{
		std::string stName("");

		loader.GetCurrentNodeName(&stName);

		loader.SetChildNode(i);

		int mapIndex=0;
		int level_start = 1;
		int level_end = 200;
		int min_rank = 0; //Grade 1
		int max_rank = 3; //Grade 4
		int rank_gain = 20;
		int level_diff = 15;//Standard level difference

		loader.GetTokenInteger("map", &mapIndex);
		loader.GetTokenInteger("lv_start", &level_start);
		loader.GetTokenInteger("lv_end", &level_end);
		loader.GetTokenInteger("min_rank", &min_rank);
		loader.GetTokenInteger("max_rank", &max_rank);
		loader.GetTokenInteger("rank_gain", &rank_gain);
		loader.GetTokenInteger("level_diff", &level_diff);
		TTokenVector* pTok;

		for (int k = 1; k < 256; ++k)
		{

			if (loader.GetTokenVector(std::to_string(k), &pTok))
				{
					std::string& name = pTok->at(0);
					DWORD Vnum = 0;

					if (!GetVnumByOriginalName(name.c_str(), Vnum))
					{
						str_to_number(Vnum, name.c_str());
						if (!ITEM_MANAGER::instance().GetTable(Vnum))
						{
							sys_err("ReadCommonItemGroup : there is no item %s : node %s : vnum %d", name.c_str(), stName.c_str(), Vnum);
							return false;
						}
					}

					int iCount = 0;
					int max_range = 100;
					str_to_number(iCount, pTok->at(1).c_str());

					const char* iPercent = pTok->at(2).c_str();

					std::string strPercent(iPercent);
					
					std::string integer_part = "";
					std::string decimal_part = "";

					int total_percent = 0;

					if (strPercent.find(".") != std::string::npos) {
						integer_part = strPercent.substr(0, strPercent.find("."));
						decimal_part = strPercent.substr(strPercent.find(".") + 1);

						if (!decimal_part.empty()) {
							if (integer_part == "0") { //we want only 0.x+ fractions
								max_range = max_range * 10;
									bool search = true;
									while (search) {
										if (decimal_part.rfind("0", 0) == 0) {
											if (decimal_part.substr(0, 1) != "0") { search = false;break; }
											else if (decimal_part.substr(0, 1).empty()) { search = false; break; }
											max_range = max_range*10;
											decimal_part.replace(0, 1, "");
										}
										else { search = false; break; }

									}
								
								if (decimal_part.find("0") != std::string::npos) {
									decimal_part.replace(decimal_part.find("0"), decimal_part.length() - decimal_part.find("0") + 1, "");
								}

								if (decimal_part.length() >= 2) {
									decimal_part.replace(2, decimal_part.length() - 2, "");
									max_range = max_range * 10;
								}

								total_percent = stoi(decimal_part);

							}
						}
					}
					else { total_percent = stoi(strPercent); }
					
					for(int j = min_rank; j <= max_rank; j++){
						if(j > min_rank) total_percent = total_percent + ( (total_percent * rank_gain) / 100);
						g_vec_pkCommonDropItem[j].push_back(CItemDropInfo(mapIndex, level_start, level_end, Vnum, iCount, total_percent, level_diff, max_range));
						
						/*
						sys_log(0,"Added drop from CommonItemGroup: Map Index %i, LevelStart %i, LevelEnd %i, MobRank %i, LevelDifference %i, ItemVnum %i, ItemCount %i, DropPercent %i, MaxRange %i",
						mapIndex, level_start, level_end, j, level_diff, Vnum, iCount, total_percent, max_range);
						*/
						
					}
				}
		}
		loader.SetParentNode();
	}
	return true;

}

 

 

 

Replace this class inside item_manager_private_types.h and item_manager.cpp

Spoiler

class CItemDropInfo

{

public:

    CItemDropInfo(int mapIndex, int iLevelStart, int iLevelEnd, DWORD dwVnum, int iCount, int iPercent, int iLevelDiff, int max_range) :

        m_MapIndex(mapIndex), m_iLevelStart(iLevelStart), m_iLevelEnd(iLevelEnd), m_dwVnum(dwVnum), m_iCount(iCount), m_iPercent(iPercent), m_iLevelDiff(iLevelDiff),

        m_MaxRange(max_range)

    {

    }

 

    int m_iLevelStart;

    int m_iLevelEnd;

    int m_iPercent; // 1 ~ 1000

    int m_iCount;

    DWORD   m_dwVnum;

    int m_MapIndex;

    int m_iLevelDiff;

    int m_MaxRange;

 

    friend bool operator < (const CItemDropInfo& l, const CItemDropInfo& r)

    {

        return l.m_iLevelEnd < r.m_iLevelEnd;

    }

};

 

Example Usage(Sword+9  at 0.5%):

Spoiler





Group Test
{
	map 0
	lv_start    1
	lv_end  18
	min_rank	0
	max_rank	3
	rank_gain	15
	level_diff	15
	1   19   1   0.5
}

 

 

Note:

If you wish to apply fairness between q.ty of items->percents, you can use one of these logics:

1) multiply iPercent by i (inside item_manager.cpp)

2) just multiply manually item percents by count of items inside common_drop_item.txt(e.g. first item(1) percent = 1, item_count=30 -> first_item(1) percent = first_item(1) percent*item_count)

 

Edited by TheDragster
Bug fixes
  • Metin2 Dev 1
  • Love 6
  • Good 1
  • Think 1
Link to post
Posted (edited)

Update:
-System now allows of setting fractions Percent(0.x, 0.0x, ....) instead of using big numbers.

-Drop Percents depends also on Empire Drop Rate(%) and Exp Rings

Edited by TheDragster
Link to post
Posted (edited)

UPDATE:

-Optimized percent handicaps: now also based on difference between mob and user levels.

Now the strategy to generate random numbers is the "real"(hardware based) by default.

-Replaced whole logic inside the function, now a random percent(item) inside the ones indicated will be picked.
This way percents are real, and there is is also an hugeness of hardware resources saved, because While cycle, which used  to iterate all items, has been removed now.

-Double_Item_drop Items(like thief gloves) now act like they should(there's a chance to make a "double pick" from the available items).

 

There's also a note for those who wish to apply fairness between items drop rates.

Edited by TheDragster
Link to post

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...

Important Information

Terms of Use / Privacy Policy / Guidelines / We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.