Jump to content

TheAdmin33

Member
  • Posts

    6
  • Joined

  • Last visited

  • Feedback

    0%

About TheAdmin33

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

TheAdmin33's Achievements

Enthusiast

Enthusiast (6/16)

  • Conversation Starter
  • One Year In
  • Dedicated
  • One Month Later
  • Week One Done

Recent Badges

38

Reputation

  1. I implemented a pet system. To calculate the pet's buffs, I'm retrieving the DEF and STR values using GetPoint(). However, the retrieved values do not match the values displayed in the character (C) window. The reason is that the buff function uses GetPoint(POINT_DEF_GRADE) and GetPoint(POINT_ATT_GRADE). This is also how many existing public pet systems are implemented. Instead, you could implement the same calculation logic used in CPythonPlayer::__GetTotalAtk from the client source on the server side. This would allow the server to calculate the actual STR (Attack) value correctly and use it when applying pet buffs, ensuring the values match those shown in the character window. Anyone who wants to use it can adapt it to their own pet system. void CNewPet::GiveBuff() { if (!m_pkChar || !m_petItem) return; m_pkOwner->RemoveAffect(EPetConstants::PET_AFFECT_TYPE); // ---- ATTACK (like client) ---- LPITEM weapon = m_pkOwner->GetWear(WEAR_WEAPON); int weaponMin = 0, weaponMax = 0, weaponBonus = 0; if (weapon && weapon->GetType() == ITEM_WEAPON) { weaponMin = weapon->GetValue(3); weaponMax = weapon->GetValue(4); weaponBonus = weapon->GetValue(5); } int raceStat = 0; switch (m_pkOwner->GetJob()) { case JOB_WARRIOR: case JOB_SURA: raceStat = m_pkOwner->GetPoint(POINT_ST); break; case JOB_ASSASSIN: raceStat = m_pkOwner->GetPoint(POINT_DX); break; case JOB_SHAMAN: raceStat = m_pkOwner->GetPoint(POINT_IQ); break; #ifdef ENABLE_WOLFMAN_CHARACTER case JOB_WOLFMAN: raceStat = m_pkOwner->GetPoint(POINT_DX); break; #endif default: raceStat = m_pkOwner->GetPoint(POINT_ST); break; } int levelAtk = m_pkOwner->GetLevel() * 2; int statAtk = (4 * m_pkOwner->GetPoint(POINT_ST) + 2 * raceStat) / 3; int hitRateSrc = (m_pkOwner->GetPoint(POINT_DX) * 4 + m_pkOwner->GetLevel() * 2) / 6; int hitRate = 100 * (MIN(90, hitRateSrc) + 210) / 300; int weaponAtk = (((weaponMin + weaponMax) / 2) + weaponBonus) * 2; int attValue = levelAtk + (statAtk + weaponAtk) * hitRate / 100; attValue += m_pkOwner->GetPoint(POINT_ATT_GRADE_BONUS); // ----------------------------------------------- int baseValues[3] = { m_pkOwner->GetMaxHP(), m_pkOwner->GetPoint(POINT_CLIENT_DEF_GRADE), attValue }; // m_pkOwner->ChatPacket(CHAT_TYPE_INFO, "MaxHP:%d ,Def:%d, Str:%d", baseValues[0], baseValues[1], baseValues[2]); // m_pkOwner->ChatPacket(CHAT_TYPE_INFO, "MaxHP:%d ,Def:%d, Str:%d", m_pkOwner->GetMaxHP(), m_pkOwner->GetPoint(POINT_DEF_GRADE), m_pkOwner->GetPoint(POINT_ATT_GRADE)); for (int i = 0; i < 3; ++i) { m_pkOwner->AddAffect( EPetConstants::PET_AFFECT_TYPE, aApplyInfo[petBonusType[i]].bPointType, static_cast<int>(baseValues[i] * m_bonus[i] / 1000), 0, INFINITE_AFFECT_DURATION, 0, false ); } }
  2. The average damage and skill damage ratios are calculated in this function. I’ve updated the code to make it more up to date. You don’t have to implement this update; it’s just something I did out of boredom and wanted to share. It’s up to you whether you add it or not. item_addon.cpp; #include "stdafx.h" #include "constants.h" #include "utils.h" #include "item.h" #include "item_addon.h" #include <random> #include <cmath> CItemAddonManager::CItemAddonManager() { } CItemAddonManager::~CItemAddonManager() { } void CItemAddonManager::ApplyAddonTo(int iAddonType, LPITEM pItem) { if (!pItem) { sys_err("ApplyAddonTo: item pointer null"); return; } std::random_device rd; std::mt19937 gen(rd()); std::normal_distribution<> gauss_dist(0.0, 5.0); int iSkillBonus = MINMAX(-30, static_cast<int>(std::round(gauss_dist(gen))), 30); int iNormalHitBonus = 0; if (std::abs(iSkillBonus) <= 20) { std::uniform_int_distribution<> dist_8_8(-8, 8); std::uniform_int_distribution<> dist_1_4(1, 4); iNormalHitBonus = -2 * iSkillBonus + std::abs(dist_8_8(gen) + dist_8_8(gen)) + dist_1_4(gen); } else { std::uniform_int_distribution<> dist_1_5(1, 5); iNormalHitBonus = -2 * iSkillBonus + dist_1_5(gen); } pItem->RemoveAttributeType(APPLY_SKILL_DAMAGE_BONUS); pItem->RemoveAttributeType(APPLY_NORMAL_HIT_DAMAGE_BONUS); pItem->AddAttribute(APPLY_NORMAL_HIT_DAMAGE_BONUS, iNormalHitBonus); pItem->AddAttribute(APPLY_SKILL_DAMAGE_BONUS, iSkillBonus); }
  3. GMs can use the /i command to create items. Normally, when you use the command /i 27992, it produces 1 White Pearl, and using /i 27992 10 produces 10 White Pearls. With this update, using /i 27992 10 10 will produce 10 stacks of 10 White Pearls each (i.e., 100 in total). For non-stackable items, an example command is: /i 19 0 10 (creates 10 units of +9 swords). ACMD(do_item) { char arg1[256], arg2[256], arg3[256]; one_argument(two_arguments(argument, arg1, sizeof(arg1), arg2, sizeof(arg2)), arg3, sizeof(arg3)); DWORD dwVnum = 0; int iCount = 1; int loop_count = 1; if (!*arg1) { ch->ChatPacket(CHAT_TYPE_INFO, "Usage: item <item vnum>"); return; } if (isnhdigit(*arg1)) str_to_number(dwVnum, arg1); else { if (!ITEM_MANAGER::instance().GetVnum(arg1, dwVnum)) { ch->ChatPacket(CHAT_TYPE_INFO, "#%u item not exist by that vnum.", dwVnum); return; } } if (*arg2) { str_to_number(iCount, arg2); iCount = MINMAX(1, iCount, ITEM_MAX_COUNT); } if (*arg3) { str_to_number(loop_count, arg3); loop_count = MINMAX(1, loop_count, 20); // I didn't create a constant like ITEM_MAX_COUNT; you can modify this. Note: 20 is the limit, do not increase it too much. } for (int i = 0; i < loop_count; ++i) { LPITEM item = ITEM_MANAGER::instance().CreateItem(dwVnum, iCount, 0, true); if (!item) { ch->ChatPacket(CHAT_TYPE_INFO, "#%u item not exist by that vnum.", dwVnum); break; // if the item doesn't exist, exit the loop } if (item->IsDragonSoul()) { int iEmptyPos = ch->GetEmptyDragonSoulInventory(item); if (iEmptyPos != -1) { item->AddToCharacter(ch, TItemPos(DRAGON_SOUL_INVENTORY, iEmptyPos)); LogManager::instance().ItemLog(ch, item, "GM", item->GetName()); } else { M2_DESTROY_ITEM(item); if (!ch->DragonSoul_IsQualified()) { ch->ChatPacket(CHAT_TYPE_INFO, "Inventory is not activated."); } else { ch->ChatPacket(CHAT_TYPE_INFO, "Not enough inventory space."); } break; // if there is no space in inventory, exit the loop } } else { int iEmptyPos = ch->GetEmptyInventory(item->GetSize()); if (iEmptyPos != -1) { item->AddToCharacter(ch, TItemPos(INVENTORY, iEmptyPos)); LogManager::instance().ItemLog(ch, item, "GM", item->GetName()); } else { M2_DESTROY_ITEM(item); ch->ChatPacket(CHAT_TYPE_INFO, "Not enough inventory space."); break; // if there is no space in inventory, exit the loop } } } }
      • 2
      • Metin2 Dev
  4. You're right, I fixed it. Download the file again.
  5. In Metin2, the attributes granted by the gems socketed into your accessories are calculated according to the rates defined in the aiAccessorySocketEffectivePct array within the constants.cpp file. const int aiAccessorySocketEffectivePct[ITEM_ACCESSORY_SOCKET_MAX_NUM + 1] = { 0, 10, 20, 40 }; Well, that's true in theory, but in practice, it doesn't quite work like that. This fix will help resolve the issue. Before; After; Fix; [Hidden Content] [Hidden Content]
×
×
  • 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.