#include "stdafx.h" #include "Map.h" #include "../shared/KOSocketMgr.h" #include "MagicProcess.h" #include "MagicInstance.h" #include using std::string; using std::vector; #define SPECIAL_MAGIC_HAMMER_SKILL_1 490215 #define SPECIAL_MAGIC_HAMMER_SKILL_2 490216 #define SPECIAL_MAGIC_HAMMER_SKILL_3 490217 void MagicInstance::Run() { SkillUseResult result; if (pSkill == nullptr) pSkill = g_pMain->m_MagictableArray.GetData(nSkillID); if (pSkillCaster == nullptr) pSkillCaster = g_pMain->GetUnitPtr(sCasterID); if (sTargetID != -1 && pSkillTarget == nullptr) pSkillTarget = g_pMain->GetUnitPtr(sTargetID); if(pSkillTarget != nullptr) { if(pSkillTarget->isNPC()) { if(TO_NPC(pSkillTarget)->isMonster() && (TO_NPC(pSkillTarget)->GetType() == NPC_TREE || TO_NPC(pSkillTarget)->GetType() == NPC_FOSSIL)) { SendSkillFailed(); return; } } } if (pSkill == nullptr || pSkillCaster == nullptr || CheckSkillPrerequisites() == SkillUseFail || (result = UserCanCast()) == SkillUseFail) { SendSkillFailed(); return; } // If the skill's already been handled (e.g. death taunts), // we don't need to do anything further. if (result == SkillUseHandled) return; bool bInitialResult; switch (bOpcode) { case MAGIC_CASTING: case MAGIC_FAIL: if (pSkillCaster->isPlayer() && pSkill->bCastTime > 0) { CUser *pCaster = TO_USER(pSkillCaster); Guard lock(pCaster->_unitlock); if (pCaster->m_CastingCoolDownList.find(nSkillID) != pCaster->m_CastingCoolDownList.end()) { pCaster->m_CastingCoolDownList.erase(nSkillID); pCaster->m_CastingCoolDownList.insert(std::make_pair(nSkillID, getMSTime())); }else pCaster->m_CastingCoolDownList.insert(std::make_pair(nSkillID, getMSTime())); } SendSkill(bOpcode == MAGIC_CASTING); // only send casting packets to the region, not fail packets. break; case MAGIC_FLYING: { // Handle arrow & mana checking/removals. if (pSkillCaster->isPlayer()) { CUser * pCaster = TO_USER(pSkillCaster); _MAGIC_TYPE2 * pType = g_pMain->m_Magictype2Array.GetData(nSkillID); // NOTE: Not all skills that use MAGIC_FLYING are type 2 skills. // Some type 3 skills use it (such as "Vampiric Fire"). // For these we should really apply the same flying logic, but for now we'll just ignore it. if (pType != nullptr) { // Throwing knives are differentiated by the fact "NeedArrow" is set to 0. // We still need to check for & take 1 throwing knife in this case however. uint8 bCount = pType->bNeedArrow; if (!bCount) bCount = 1; if (pType == nullptr // The user does not have enough arrows! We should point them in the right direction. ;) || (!pCaster->CheckExistItem(pSkill->iUseItem, bCount)) // Ensure user has enough mana for this skill || pSkill->sMsp > pSkillCaster->GetMana()) { SendSkillFailed(); return; } // Add all flying arrow instances to the user's list for hit detection Guard lock(pCaster->_unitlock); for (size_t i = 0; i < bCount; i++) pCaster->m_flyingArrows.push_back(Arrow(pType->iNum, UNIXTIME)); // Remove the arrows pCaster->RobItem(pSkill->iUseItem, bCount); } // for non-type 2 skills, ensure we check the user's mana. else if (pSkill->sMsp > pSkillCaster->GetMana()) { SendSkillFailed(); return; } // Take the required mana for this skill if (!pCaster -> isBlinking() && pCaster->isRogue() && !pCaster->isDevil()) pCaster->MSpChange(-(pSkill->sMsp)); } SendSkill(true); // send this to the region } break; case MAGIC_EFFECTING: // Hacky check for a transformation item (Disguise Totem, Disguise Scroll) // These apply when first type's set to 0, second type's set and obviously, there's a consumable item. // Need to find a better way of handling this. if (!bIsRecastingSavedMagic && (pSkill->bType[0] == 0 && pSkill->bType[1] != 0 && pSkill->iUseItem != 0 && (pSkillCaster->isPlayer() && TO_USER(pSkillCaster)->CheckExistItem(pSkill->iUseItem))) && !TO_USER(pSkillCaster)->isInPKZone()) { SendTransformationList(); return; } bInitialResult = ExecuteSkill(pSkill->bType[0]); if (bInitialResult) { if (pSkillCaster->isPlayer()) { CUser *pCaster = TO_USER(pSkillCaster); if (!pSkillCaster->hasBuff(BUFF_TYPE_INSTANT_MAGIC)) { Guard lock(pCaster->_unitlock); pCaster->m_CoolDownList.insert(std::make_pair(nSkillID, UNIXTIME)); if (pSkill->bType[0] != 0) { if(pSkill->bType[0] == 3 && pSkill->nBeforeAction == int32(-1)) pCaster->m_MagicTypeCooldownList.insert(std::make_pair(10, UNIXTIME)); else pCaster->m_MagicTypeCooldownList.insert(std::make_pair(pSkill->bType[0], UNIXTIME)); } if (pSkill->bType[1] != 0) pCaster->m_MagicTypeCooldownList.insert(std::make_pair(pSkill->bType[1], UNIXTIME)); } } ExecuteSkill(pSkill->bType[1]); if (pSkill->bType[0] != 2) ConsumeItem(); } break; case MAGIC_CANCEL: case MAGIC_CANCEL2: Type3Cancel(); //Damage over Time skills. Type4Cancel(); //Buffs Type6Cancel(); //Transformations Type9Cancel(); //Stealth, lupine etc. break; case MAGIC_TYPE4_EXTEND: Type4Extend(); break; } } SkillUseResult MagicInstance::UserCanCast() { if (pSkill == nullptr) return SkillUseFail; if(pSkillCaster->isBlinking() && !bIsRecastingSavedMagic ) return SkillUseFail; // We don't need to check anything as we're just canceling our buffs. if (bOpcode == MAGIC_CANCEL || bOpcode == MAGIC_CANCEL2 // Also don't need to check anything if we're forwarding a fail packet. || bOpcode == MAGIC_FAIL // Or are reapplying persistent buffs. || bIsRecastingSavedMagic) return SkillUseOK; // Ensure the caster can use skills (i.e. they're not incapacitated, or have skills blocked, etc). // Additionally, unless it's resurrection-related, dead players cannot use skills. if (!pSkillCaster->canUseSkills() || (pSkillCaster->isDead() && pSkill->bType[0] != 5)) return SkillUseFail; // If we're using an AOE, and the target is specified... something's not right. if ((pSkill->bMoral >= MORAL_AREA_ENEMY && pSkill->bMoral <= MORAL_SELF_AREA) && sTargetID != -1) { // Items that proc skills require the target ID to be fixed up. // There's no other means of detecting who to target. if (!bIsItemProc) return SkillUseFail; sTargetID = -1; } // NPCs do not need most of these checks. if (pSkillCaster->isPlayer()) { if ((pSkill->sSkill != 0 && (TO_USER(pSkillCaster)->m_sClass != (pSkill->sSkill / 10) || pSkillCaster->GetLevel() < pSkill->sSkillLevel)) && pSkillCaster->GetZoneID() != ZONE_CHAOS_DUNGEON) return SkillUseFail; if (pSkillCaster->GetZoneID() == ZONE_CHAOS_DUNGEON && !g_pMain->pTempleEvent.isAttackable) return SkillUseFail; if (pSkillCaster->GetZoneID() == ZONE_BORDER_DEFENSE_WAR && !g_pMain->pTempleEvent.isAttackable) return SkillUseFail; if (pSkillCaster->GetZoneID() == ZONE_JURAD_MOUNTAIN && !g_pMain->pTempleEvent.isAttackable) return SkillUseFail; if (TO_USER(pSkillCaster)->isTrading() || TO_USER(pSkillCaster)->isMerchanting() || TO_USER(pSkillCaster)->isSellingMerchant() || TO_USER(pSkillCaster)->m_bMerchantStatex || TO_USER(pSkillCaster)->isBuyingMerchant() || TO_USER(pSkillCaster)->isStoreOpen() || TO_USER(pSkillCaster)->isMining()) return SkillUseFail; if ((pSkillCaster->GetMana() - pSkill->sMsp) < 0 && (((pSkill->bType[0] == 2 || pSkill->bType[0] == 3) && pSkill->bFlyingEffect == 0) || (pSkill->bType[0] != 2 || pSkill->bType[0] == 3))) return SkillUseFail; // If we're in a snow war, we're only ever allowed to use the snowball skill. if (pSkillCaster->GetZoneID() == ZONE_SNOW_BATTLE && g_pMain->m_byBattleOpen == SNOW_BATTLE && nSkillID != SNOW_EVENT_SKILL) return SkillUseFail; // Handle death taunts (i.e. pressing the spacebar on a corpse). // NOTE: These skills don't really have any other generic means of identification. if (pSkillTarget != nullptr && pSkill->bMoral == MORAL_ENEMY && pSkill->bType[0] == 3 && pSkill->bType[1] == 0 // Target player must be a corpse. && pSkillTarget->isDead()) { _MAGIC_TYPE3 * pType3 = g_pMain->m_Magictype3Array.GetData(pSkill->iNum); if (pType3 == nullptr) return SkillUseFail; // Skill mustn't do any damage or such. if (pType3->bDirectType == 0 && pType3->sFirstDamage == 0 && pType3->sTimeDamage == 0) { // We also need to tweak the packet being sent. bOpcode = MAGIC_EFFECTING; sData[1] = 1; SendSkill(); return SkillUseHandled; } } _MAGIC_TYPE5 * pType5; uint8 bType = 0; uint16 sNeedStone = 0; if (pSkill->bType[0] == 5) { pType5 = g_pMain->m_Magictype5Array.GetData(pSkill->iNum); if (pType5) { bType = pType5->bType; sNeedStone = pType5->sNeedStone; } } // Archer & transformation skills will handle item checking themselves if ((pSkill->bType[0] != 2 && pSkill->bType[0] != 6) // The user does not meet the item's requirements or does not have any of said item. && (pSkill->iUseItem != 0 && !TO_USER(bType == RESURRECTION ? pSkillTarget : pSkillCaster)->CanUseItem(pSkill->iUseItem, (bType == RESURRECTION ? sNeedStone : 1)))) return SkillUseFail; // Some skills also require class-specific stones which are taken instead of UseItem. // In this case, UseItem is considered a required item and not consumed on skill use. if (pSkill->nBeforeAction >= ClassWarrior && pSkill->nBeforeAction <= ClassPorutu) nConsumeItem = CLASS_STONE_BASE_ID + (pSkill->nBeforeAction * 1000); else nConsumeItem = pSkill->iUseItem; if ((pSkill->bType[0] != 2 && pSkill->bType[0] != 6) // The user does not meet the item's requirements or does not have any of said item. && (pSkill->iUseItem != 0 && !TO_USER(pSkillCaster)->CanUseItem(pSkill->iUseItem)) && bType != RESURRECTION) return SkillUseFail; // We cannot use CSW transformations outside of Delos (or when CSW is not enabled.) if (pSkill->bType[0] == 6 && (nSkillID / 10000) == 45 && pSkillCaster->GetZoneID() != ZONE_DELOS) return SkillUseFail; #if !defined(DEBUG) // For skills that are unlocked via quests, ensure the user has actually // completed the quest... // NOTE: GMs are excluded. if (!TO_USER(pSkillCaster)->isGM() && pSkill->sEtc != 0 && !TO_USER(pSkillCaster)->V3_CheckExistEvent(pSkill->sEtc, 2)) return SkillUseFail; #endif if (pSkill->bType[0] < 4 && pSkillTarget != nullptr && !pSkillCaster->isInAttackRange(pSkillTarget, pSkill)) return SkillUseFail; } if ((bOpcode == MAGIC_EFFECTING || bOpcode == MAGIC_CASTING) && !IsAvailable()) return SkillUseFail; // Instant casting affects the next cast skill only, and is then removed. if (bOpcode == MAGIC_EFFECTING && pSkillCaster->canInstantCast()) bInstantCast = true; // In case we made it to here, we can cast! Hurray! return SkillUseOK; } SkillUseResult MagicInstance::CheckSkillPrerequisites() { if (pSkill == nullptr) return SkillUseFail; if (bOpcode != MAGIC_FLYING && bOpcode != MAGIC_EFFECTING) { if (bOpcode == MAGIC_CASTING && pSkillTarget && (pSkill->sRange > 0 && pSkill->sUseStanding == 1 && (pSkillCaster->GetDistanceSqrt(pSkillTarget) > float(pSkill->sRange * 2)))) // Skill Range Check for Casting return SkillUseFail; else return SkillUseOK; } if (pSkillCaster == nullptr) { TRACE("#### CheckSkillPrerequisites : pSkillCaster == nullptr ####\n"); return SkillUseFail; } if (nSkillID > 299999 && nSkillID < 399999) if (pSkillCaster->isPlayer()) return SkillUseFail; if (pSkillCaster->isPlayer()) { CUser *pCaster = TO_USER(pSkillCaster); if (pCaster) { if (pCaster->GetZoneID() == ZONE_PRISON) return SkillUseFail; if (pSkill->sUseStanding == 1 && pCaster->m_sSpeed != 0) // Hacking prevention! return SkillUseFail; if ((pSkill->bType[0] == 3 || pSkill->bType[1] == 3) && pSkill->bFlyingEffect != -1) { _MAGIC_TYPE3 * pType = g_pMain->m_Magictype3Array.GetData(nSkillID); if (pType == nullptr) TRACE("[%s] Used skill %d but it does not exist in MagicType3.\n", pSkillCaster->GetName().c_str(), nSkillID); else if (pType->bAttribute == AttributeNone) { if (pSkill->bType[0] != 0) pCaster->m_MagicTypeCooldownList.erase(pSkill->bType[0]); if (pSkill->bType[1] != 0) pCaster->m_MagicTypeCooldownList.erase(pSkill->bType[1]); } } // Skil Range, Safety Area, Temple Zones and Event Room Checks... if (pSkillTarget != nullptr) { float A = pSkillCaster->GetDistanceSqrt(pSkillTarget); if ((pSkill->sRange > 0 && pSkill->sUseStanding == 0 && (pSkillCaster->GetDistanceSqrt(pSkillTarget) > float(pSkill->sRange * 2))) // Skill Range Check || (pSkillTarget->isPlayer() && pSkillCaster != pSkillTarget && (TO_USER(pSkillTarget)->isInSafetyArea() && nSkillID < 300000 && (pSkillCaster->GetZoneID() == ZONE_DELOS || pSkillCaster->GetZoneID() == ZONE_ARENA))) // Disable Skill in Safety Area || (pCaster->isInTempleEventZone() && !pCaster->isSameEventRoom(pSkillTarget))) // If EventRoom is not same disable Skill. return SkillUseFail; } Guard lock2(pCaster->_unitlock); // Skill Cooldown Checks... if(bOpcode != MAGIC_FLYING) { if (pCaster->m_CastingCoolDownList.find(nSkillID) != pCaster->m_CastingCoolDownList.end() && pSkill->bCastTime > 0) { SkillCooldownList::iterator itr = pCaster->m_CastingCoolDownList.find(nSkillID); int64 DiffrentMilliseconds = getMSTime() - itr->second; if (DiffrentMilliseconds > 0 && DiffrentMilliseconds < int32(pSkill->bCastTime * 10) && pSkill->bType[0] != 9 && !bIsRecastingSavedMagic) return SkillUseFail; else pCaster->m_CastingCoolDownList.erase(nSkillID); } } if(pSkillTarget == nullptr && pSkillCaster != nullptr) { if(pSkill->bMoral == MORAL_AREA_ALL || pSkill->bMoral == MORAL_AREA_ENEMY || pSkill->bMoral == MORAL_AREA_FRIEND || pSkill->bMoral == MORAL_SELF_AREA) { if(!pSkillCaster->isInRangeSlow(this->sData[0], this->sData[2], (float) (pSkill->sRange + 2))) return SkillUseFail; } } // Skill Cooldown Checks... if (bOpcode != MAGIC_TYPE4_EXTEND && pCaster->m_CoolDownList.find(nSkillID) != pCaster->m_CoolDownList.end()) { SkillCooldownList::iterator itr = pCaster->m_CoolDownList.find(nSkillID); int64 DiffrentMilliseconds = (int64(UNIXTIME) * 1000) - (int64(itr->second) * 1000); if (DiffrentMilliseconds > 0 && DiffrentMilliseconds < int32(pSkill->sReCastTime * 100) && pSkill->bType[0] != 9 && !bIsRecastingSavedMagic) return SkillUseFail; else pCaster->m_CoolDownList.erase(nSkillID); } if (pCaster->isRogue() && pSkill->bType[0] == 2 && pSkill -> iUseItem != 370007000) { _ITEM_TABLE * pLeftHand = TO_USER(pSkillCaster)->GetItemPrototype(LEFTHAND); _ITEM_TABLE * pRightHand = TO_USER(pSkillCaster)->GetItemPrototype(RIGHTHAND); if ((pLeftHand == nullptr && pRightHand == nullptr) || ((pLeftHand && !pLeftHand->isBow()) || (pRightHand && !pRightHand->isBow()))) // Hacking prevention! return SkillUseFail; if (bOpcode == MAGIC_EFFECTING) return SkillUseOK; } uint8 myType = pSkill->bType[0]; if(myType == 3 && pSkill->nBeforeAction == -1) myType = 10; // Same time skill checks... MagicTypeCooldownList::iterator itr; if (myType != 0) { if ((myType == 1 || myType == 2 || myType == 3 || myType == 4 || myType == 5 || myType == 6 || myType == 7 || myType == 10) && (nSkillID < 400000 || myType == 10) && pCaster->m_MagicTypeCooldownList.find(myType) != pCaster->m_MagicTypeCooldownList.end()) { itr = pCaster->m_MagicTypeCooldownList.find(myType); if ((float(UNIXTIME - itr->second) < PLAYER_SKILL_REQUEST_INTERVAL)) return SkillUseFail; else pCaster->m_MagicTypeCooldownList.erase(myType); } } if (pSkill->bType[1] != 0) { if ((pSkill->bType[1] == 1 || pSkill->bType[1] == 2 || pSkill->bType[1] == 3 || pSkill->bType[1] == 4 || pSkill->bType[1] == 5 || pSkill->bType[1] == 6 || pSkill->bType[1] == 7) && nSkillID < 400000 && pCaster->m_MagicTypeCooldownList.find(pSkill->bType[1]) != pCaster->m_MagicTypeCooldownList.end()) { itr = pCaster->m_MagicTypeCooldownList.find(pSkill->bType[1]); if ((float(UNIXTIME - itr->second) < PLAYER_SKILL_REQUEST_INTERVAL)) return SkillUseFail; else pCaster->m_MagicTypeCooldownList.erase(pSkill->bType[1]); } } } } if (pSkillTarget != nullptr) { if (pSkillTarget->isNPC() && !pSkillCaster->isAttackable(pSkillTarget)) return SkillUseFail; else { if (TO_USER(pSkillTarget)->hasBuff(BUFF_TYPE_FREEZE)) // Effect Sorunu ve Blink Sorunu Var... return SkillUseFail; } } return SkillUseOK; } /** * @brief Checks primary type 3 (DOT/HOT) skill prerequisites before executing the skill. * * @return true if it succeeds, false if it fails. */ bool MagicInstance::CheckType3Prerequisites() { _MAGIC_TYPE3 * pType = g_pMain->m_Magictype3Array.GetData(nSkillID); if (pType == nullptr) return false; // Handle AOE prerequisites if (sTargetID == -1) { // No need to handle any prerequisite logic for NPCs/mobs casting AOEs. if (!pSkillCaster->isPlayer()) return true; // Player can attack other players in the safety area. /*if (TO_USER(pSkillCaster)->isInSafetyArea()) return false;*/ if (pSkill->bMoral == MORAL_PARTY_ALL && pType->sTimeDamage > 0) { // Players may not cast group healing spells whilst transformed // into a monster (skills with IDs of 45###). if (TO_USER(pSkillCaster)->isMonsterTransformation()) return false; // Official behaviour means we cannot cast a group healing spell // if we currently have an active restoration spells on us. // This behaviour seems fairly illogical, but it's how it works. for (int i = 0; i < MAX_TYPE3_REPEAT; i++) { if (pSkillCaster->m_durationalSkills[i].m_sHPAmount > 0) return false; } } // No other reason to reject AOE spells. return true; } // Handle prerequisites for skills cast on NPCs. else if (sTargetID >= NPC_BAND) { if (pSkillTarget == nullptr) return false; // Unless the zone is Delos, or it's a healing skill, we can continue on our merry way. if (pSkillCaster->GetZoneID() != 30 || (pType->sFirstDamage <= 0 && pType->sTimeDamage <= 0)) return true; // We cannot heal gates! That would be bad, very bad. //if (TO_NPC(pSkillTarget)->GetType() == NPC_GATE) // note: official only checks byType 50 //return false; // Otherwise, officially there's no reason we can't heal NPCs (more specific logic later). return true; } // Handle prerequisites for skills cast on players. else { // We only care about friendly non-AOE spells. if (pSkill->bMoral > MORAL_PARTY) return true; if (pSkillTarget == nullptr || !pSkillTarget->isPlayer() || pSkillTarget->isDead()) return false; // If the spell is a healing/restoration spell... if (pType->sTimeDamage >= 0) { // Official behaviour means we cannot cast a restoration spell // if the target currently has an active restoration spell on them. /*for (int i = 0; i < MAX_TYPE3_REPEAT; i++) { if (pSkillTarget->m_durationalSkills[i].m_sHPAmount > 0 && pSkillCaster->m_durationalSkills[i].m_bHPInterval != 4) return false; }*/ } // It appears that the server should reject any attacks or heals // on players that have transformed into monsters. if (TO_USER(pSkillTarget)->isSiegeTransformation() && !pSkillCaster->CanAttack(pSkillTarget)) return false; return true; } } /** * @brief Checks primary type 4 (buff/debuff) skill prerequisites before executing the skill. * * @return true if it succeeds, false if it fails. */ bool MagicInstance::CheckType4Prerequisites() { _MAGIC_TYPE4 * pType = g_pMain->m_Magictype4Array.GetData(nSkillID); // Certain transformation (type 6) skills state they have an associated // type 4 skill but do not have any entry in the table. Consider these OK. if (pType == nullptr) return (pSkill->bType[0] == 6); if (sTargetID < 0 || sTargetID >= MAX_USER) { if (sTargetID < NPC_BAND // i.e. it's an AOE || pType->bBuffType != BUFF_TYPE_HP_MP // doesn't matter who we heal. || pType->sMaxHPPct != 99) // e.g. skills like Dispel Magic / Sweet Kiss return true; return false; } // At this point, it can only be a user -- so ensure the ID was invalid (this was looked up earlier). if (pSkillTarget == nullptr || !pSkillTarget->isPlayer()) return false; if (TO_USER(pSkillTarget)->isTransformed()) { // Can't use buff scrolls/pots when transformed into anything but NPCs. if (!TO_USER(pSkillTarget)->isNPCTransformation() && (nSkillID >= 500000 // Can't use bezoars etc when transformed // (this should really be a whitelist, but this is blocked explicitly in 1.298) || pType->bBuffType == BUFF_TYPE_SIZE)) { SendSkillFailed(); return false; } } // If the specified target already has this buff (debuffs are required to reset) // we should fail this skill. // NOTE: AOE buffs are exempt. if (pType->isBuff()) { Guard lock(pSkillTarget->_unitlock); if (pSkillTarget->m_buffMap.find(pType->bBuffType) != pSkillTarget->m_buffMap.end()) return false; } // TODO: Allow for siege-weapon only buffs (e.g. "Physical Attack Scroll") return true; } bool MagicInstance::CheckType6Prerequisites() { if (!pSkillCaster->isPlayer()) return true; _MAGIC_TYPE6 * pType = g_pMain->m_Magictype6Array.GetData(nSkillID); if (pType == nullptr) return false; CUser * pCaster = TO_USER(pSkillCaster); switch (pType->bUserSkillUse) { // For monster transformations (TransformationSkillUseMonster), nBeforeAction is set to the item // used for showing the transformation list & UseItem is the consumable item. case TransformationSkillUseMonster: // Ensure they have the item for showing the transformation list if ((!pCaster->CanUseItem(pSkill->nBeforeAction) // Ensure they have the required item for the skill. || !pCaster->CanUseItem(pSkill->iUseItem)) && !pCaster->CheckExistItem(381001000)) return false; break; // For all other transformations, all we care about is UseItem (BeforeAction is set to 0 in these cases). default: // Ensure they have the item for showing the transformation list if (!pCaster->CanUseItem(pSkill->iUseItem)) return false; break; } // Perform class check, if applicable. bool bAllowedClass = (pType->sClass == 0); if (bAllowedClass) return true; // NOTE: sClass is a 4 digit number (e.g. 1111) with a digit per class // in the order warrior/rogue/mage/priest with '1' representing 'allowed' & // anything else as forbidden. switch (pCaster->GetBaseClassType()) { case ClassWarrior: bAllowedClass = ((pType->sClass / 1000)) == 1; break; case ClassRogue: bAllowedClass = ((pType->sClass % 1000) / 100) == 1; break; case ClassMage: bAllowedClass = (((pType->sClass % 1000) % 100) / 10) == 1; break; case ClassPriest: bAllowedClass = (((pType->sClass % 1000) % 100) % 10) == 1; break; case ClassPorutu: bAllowedClass = (((pType->sClass % 1000) % 100) % 10) == 1; break; } return bAllowedClass; } bool MagicInstance::ExecuteSkill(uint8 bType) { if (bType == 0 || (pSkillCaster->isBlinking() && bType != 4 && pSkill->iNum < 300000)) return false; // Implement player-specific logic before skills are executed. if (pSkillCaster->isPlayer()) { // If a player is stealthed, and they are casting a type 1/2/3/7 skill // it is classed as an attack, so they should be unstealthed. if ((bType >= 1 && bType <= 3) || (bType == 7)) TO_USER(pSkillCaster)->RemoveStealth(); if(TO_USER(pSkillCaster)->isKurianPortu() && pSkill->bItemGroup >= 0 && pSkill->bItemGroup <= 90 && pSkill->iUseItem == 0) { TO_USER(pSkillCaster)->ySpChange( -pSkill->bItemGroup ); } } switch (bType) { case 1: return ExecuteType1(); case 2: return ExecuteType2(); case 3: return ExecuteType3(); case 4: return ExecuteType4(); case 5: return ExecuteType5(); case 6: return ExecuteType6(); case 7: return ExecuteType7(); case 8: return ExecuteType8(); case 9: return ExecuteType9(); } return false; } void MagicInstance::SendTransformationList() { if (!pSkillCaster->isPlayer()) return; Packet result(WIZ_MAGIC_PROCESS, uint8(MAGIC_TRANSFORM_LIST)); result << nSkillID; TO_USER(pSkillCaster)->Send(&result); } /** * @brief Sends the skill failed packet to the caster. * * @param sTargetID Target ID to override with, as some use cases * require. */ void MagicInstance::SendSkillFailed(int16 sTargetID) { if (pSkillCaster == nullptr) return; Packet result; sData[3] = (bOpcode == MAGIC_CASTING ? SKILLMAGIC_FAIL_CASTING : SKILLMAGIC_FAIL_NOEFFECT); BuildSkillPacket(result, sCasterID, sTargetID == -1 ? this->sTargetID : sTargetID, MAGIC_FAIL, nSkillID, sData); // No need to proceed if we're not sending fail packets. if (!bSendFail || !pSkillCaster->isPlayer()) return; TO_USER(pSkillCaster)->Send(&result); } void MagicInstance::SendSkillNotEffect() { if (pSkillCaster == nullptr) return; Packet result; sData[3] = (bOpcode == MAGIC_CASTING ? SKILLMAGIC_FAIL_CASTING : SKILLMAGIC_FAIL_NOEFFECT); BuildSkillPacket(result, sCasterID, sTargetID, MAGIC_EFFECTING, nSkillID, sData); // No need to proceed if we're not sending fail packets. if (!bSendFail || !pSkillCaster->isPlayer()) return; TO_USER(pSkillCaster)->Send(&result); } /** * @brief Builds skill packet using the specified data. * * @param result Buffer to store the packet in. * @param sSkillCaster Skill caster's ID. * @param sSkillTarget Skill target's ID. * @param opcode Skill system opcode. * @param nSkillID Identifier for the skill. * @param sData Array of additional misc skill data. */ void MagicInstance::BuildSkillPacket(Packet & result, int16 sSkillCaster, int16 sSkillTarget, int8 opcode, uint32 nSkillID, int16 sData[7]) { // On skill failure, flag the skill as failed. if (opcode == MAGIC_FAIL) { bSkillSuccessful = false; // No need to proceed if we're not sending fail packets. if (!bSendFail) return; } result.Initialize(WIZ_MAGIC_PROCESS); result << opcode << nSkillID << sSkillCaster << sSkillTarget << sData[0] << sData[1] << sData[2] << sData[3] << sData[4] << sData[5] << sData[6]; } /** * @brief Builds and sends skill packet using the specified data to pUnit. * * @param pUnit The unit to send the packet to. If an NPC is specified, * bSendToRegion is assumed true. * @param bSendToRegion true to send the packet to pUser's region. * @param sSkillCaster Skill caster's ID. * @param sSkillTarget Skill target's ID. * @param opcode Skill system opcode. * @param nSkillID Identifier for the skill. * @param sData Array of additional misc skill data. */ void MagicInstance::BuildAndSendSkillPacket(Unit * pUnit, bool bSendToRegion, int16 sSkillCaster, int16 sSkillTarget, int8 opcode, uint32 nSkillID, int16 sData[7]) { Packet result; BuildSkillPacket(result, sSkillCaster, sSkillTarget, opcode, nSkillID, sData); // No need to proceed if we're not sending fail packets. if (opcode == MAGIC_FAIL && !bSendFail) return; if (bSendToRegion || !pUnit->isPlayer()) pUnit->SendToRegion(&result); else TO_USER(pUnit)->Send(&result); } /** * @brief Sends the skill data in the current context to pUnit. * If pUnit is nullptr, it will assume the caster. * * @param bSendToRegion true to send the packet to pUnit's region. * If pUnit is an NPC, this is assumed true. * @param pUser The unit to send the packet to. * If pUnit is an NPC, it will send to pUnit's region regardless. */ void MagicInstance::SendSkill(bool bSendToRegion, Unit * pUnit) { // If pUnit is nullptr, it will assume the caster. if (pUnit == nullptr) pUnit = pSkillCaster; // Build the skill packet using the skill data in the current context. BuildAndSendSkillPacket(pUnit, bSendToRegion, this->sCasterID, this->sTargetID, this->bOpcode, this->nSkillID, this->sData); } bool MagicInstance::IsAvailable() { CUser* pParty = nullptr; int modulator = 0, Class = 0, skill_mod = 0; if (pSkill == nullptr || (pSkillCaster->GetZoneID() == ZONE_CHAOS_DUNGEON && !g_pMain->pTempleEvent.isAttackable)) goto fail_return; if (pSkill == nullptr || (pSkillCaster->GetZoneID() == ZONE_BORDER_DEFENSE_WAR && !g_pMain->pTempleEvent.isAttackable)) goto fail_return; if (pSkill == nullptr || (pSkillCaster->GetZoneID() == ZONE_JURAD_MOUNTAIN && !g_pMain->pTempleEvent.isAttackable)) goto fail_return; switch (pSkill->bMoral) { // Enforce self-casting skills *unless* we're an NPC. // Quest NPCs, naturally, use skills set as self-buffs. case MORAL_SELF: if (pSkillCaster->isPlayer() && pSkillCaster != pSkillTarget) goto fail_return; break; case MORAL_FRIEND_WITHME: if (pSkillTarget != pSkillCaster && pSkillCaster->isHostileTo(pSkillTarget)) pSkillTarget = pSkillCaster; break; case MORAL_FRIEND_EXCEPTME: if (pSkillCaster == pSkillTarget || pSkillCaster->isHostileTo(pSkillTarget)) goto fail_return; break; case MORAL_PARTY: { // NPCs can't *be* in parties. if (pSkillCaster->isNPC() || (pSkillTarget != nullptr && pSkillTarget->isNPC())) goto fail_return; // We're definitely a user, so... CUser *pCaster = TO_USER(pSkillCaster); // If the caster's not in a party, make sure the target's not someone other than themselves. if ((!pCaster->isInParty() && pSkillCaster != pSkillTarget) // Also ensure that if there is a target, they're in the same party. || (pSkillTarget != nullptr && TO_USER(pSkillTarget)->GetPartyID() != pCaster->GetPartyID())) goto fail_return; } break; case MORAL_NPC: if (pSkillTarget == nullptr || !pSkillTarget->isNPC() || pSkillCaster->isHostileTo(pSkillTarget) && pSkill->iUseItem != 379105000) goto fail_return; break; case MORAL_ENEMY: // Allow for archery skills with no defined targets (e.g. an arrow from 'multiple shot' not hitting any targets). // These should be ignored, not fail. if (pSkillTarget != nullptr && !pSkillCaster->isHostileTo(pSkillTarget)) goto fail_return; break; case MORAL_CORPSE_FRIEND: // We need to revive *something*. if (pSkillTarget == nullptr // Are we allowed to revive this person? || pSkillCaster->isHostileTo(pSkillTarget) // We cannot revive ourselves. || pSkillCaster == pSkillTarget // We can't revive living targets. || pSkillTarget->isAlive()) goto fail_return; break; case MORAL_CLAN: { // NPCs cannot be in clans. if (pSkillCaster->isNPC() || (pSkillTarget != nullptr && pSkillTarget->isNPC())) goto fail_return; // We're definitely a user, so.... CUser * pCaster = TO_USER(pSkillCaster); // If the caster's not in a clan, make sure the target's not someone other than themselves. if ((!pCaster->isInClan() && pSkillCaster != pSkillTarget) // If we're targeting someone, that target must be in our clan. || (pSkillTarget != nullptr && TO_USER(pSkillTarget)->GetClanID() != pCaster->GetClanID())) goto fail_return; } break; case MORAL_SIEGE_WEAPON: if (pSkillCaster->isPlayer() || !TO_USER(pSkillCaster)->isSiegeTransformation()) goto fail_return; break; } // Check skill prerequisites for (int i = 0; i < 2; i++) { switch (pSkill->bType[i]) { case 3: if (!CheckType3Prerequisites()) return false; break; case 4: if (!CheckType4Prerequisites()) return false; break; case 6: if (!CheckType6Prerequisites()) return false; else { if (TO_USER(pSkillCaster)->CheckExistItem(381001000)) TO_USER(pSkillCaster)->RobItem(381001000); } break; } } // This only applies to users casting skills. NPCs are fine and dandy (we can trust THEM at least). if (pSkillCaster->isPlayer()) { if(pSkill->iNum == 490145)//EXP Flash { if(TO_USER(pSkillCaster)->PremiumID == 11) { if(TO_USER(pSkillCaster)->m_FlashExpBonus > 79 || TO_USER(pSkillCaster)->GetLevel() > 79) return false; } else { if(TO_USER(pSkillCaster)->m_FlashExpBonus > 39) return false; } } if(pSkill->iNum == 490146)// DC Flash { if(TO_USER(pSkillCaster)->PremiumID == 10) { if(TO_USER(pSkillCaster)->m_FlashDcBonus > 99) return false; } else { return false; } } if(pSkill->iNum == 490147)//WAR Flash { if(TO_USER(pSkillCaster)->PremiumID == 12) { if(TO_USER(pSkillCaster)->m_FlashWarBonus > 99) return false; } else { return false; } } if (pSkill->bType[0] == 3) { _MAGIC_TYPE3 * pType3 = g_pMain->m_Magictype3Array.GetData(pSkill->iNum); _ITEM_TABLE * pTable; if (pType3 == nullptr) goto fail_return; // Allow for skills that block potion use. if (!pSkillCaster->canUsePotions() && pType3->bDirectType == 1 // affects target's HP (magic numbers! yay!) && pType3->sFirstDamage > 0 // healing only && pSkill->iUseItem != 0 // requiring an item (i.e. pots) // To avoid conflicting with priest skills that require items (e.g. "Laying of hands") // we need to lookup the item itself for the information we need to ignore it. && (pTable = g_pMain->GetItemPtr(pSkill->iUseItem)) != nullptr // Item-required healing skills are class-specific. // We DO NOT want to block these skills. && pTable->m_bClass == 0) goto fail_return; } modulator = pSkill->sSkill % 10; // Hacking prevention! if( modulator != 0 ) { Class = pSkill->sSkill / 10; if(Class != TO_USER(pSkillCaster)->GetClass()) goto fail_return; if((pSkill->sSkillLevel > TO_USER(pSkillCaster)->m_bstrSkill[modulator]) && TO_USER(pSkillCaster)->GetFame() != COMMAND_CAPTAIN) goto fail_return; } else if (pSkill->sSkillLevel > pSkillCaster->GetLevel()) goto fail_return; if (pSkill->bType[0] == 1) { // Weapons verification in case of COMBO attack (another hacking prevention). if (pSkill->sSkill == 1055 || pSkill->sSkill == 2055) // Weapons verification in case of dual wielding attacks ! { if (TO_USER(pSkillCaster)->isWeaponsDisabled()) return false; _ITEM_TABLE *pLeftHand = TO_USER(pSkillCaster)->GetItemPrototype(LEFTHAND); _ITEM_TABLE *pRightHand = TO_USER(pSkillCaster)->GetItemPrototype(RIGHTHAND); if ((pLeftHand != nullptr && !pLeftHand->isSword() && !pLeftHand->isAxe() && !pLeftHand->isMace() && !pLeftHand->isSpear()) || (pRightHand != nullptr && !pRightHand->isSword() && !pRightHand->isAxe() && !pRightHand->isMace() && !pRightHand->isSpear())) return false; } else if (pSkill->sSkill == 1056 || pSkill->sSkill == 2056) { // Weapons verification in case of 2 handed attacks ! if (TO_USER(pSkillCaster)->isWeaponsDisabled()) return false; _ITEM_TABLE *pRightHand = TO_USER(pSkillCaster)->GetItemPrototype(RIGHTHAND); if (TO_USER(pSkillCaster)->GetItem(LEFTHAND)->nNum != 0 || (pRightHand != nullptr && !pRightHand->isSword() && !pRightHand->isAxe() && !pRightHand->isMace() && !pRightHand->isSpear())) return false; } } // Handle MP/HP/item loss. if (bOpcode == MAGIC_EFFECTING) { int total_hit = pSkillCaster->m_sTotalHit; if ((pSkill->bType[0] == 2 || pSkill->bType[0] == 3) && pSkill->bFlyingEffect != 0) // Type 2 related... return true; // Do not reduce MP/SP when flying effect is not 0. if (pSkill->sMsp > pSkillCaster->GetMana()) goto fail_return; // If the PLAYER uses an item to cast a spell. if (pSkillCaster->isPlayer() && (pSkill->bType[0] == 3 || pSkill->bType[0] == 4)) { if (pSkill->iUseItem != 0) { _ITEM_TABLE* pItem = nullptr; // This checks if such an item exists. pItem = g_pMain->GetItemPtr(pSkill->iUseItem); if (!pItem) return false; if ((pItem->m_bClass != 0 && !TO_USER(pSkillCaster)->JobGroupCheck(pItem->m_bClass)) || (pItem->m_bReqLevel != 0 && TO_USER(pSkillCaster)->GetLevel() < pItem->m_bReqLevel)) return false; } } if (pSkillCaster->isDevil()) { pSkillCaster->MSpChange(-(pSkill->sMsp / 2)); } else if (pSkill->bType[0] != 4 || (pSkill->bType[0] == 4 && sTargetID == -1) && !pSkillCaster -> isBlinking()) { pSkillCaster->MSpChange(-(pSkill->sMsp)); } // Skills that require HP rather than MP. if (pSkill->sHP > 0 && pSkill->sMsp == 0 && pSkill->sHP < 10000) // Hack (used officially) to allow for skills like "Sacrifice" { if (pSkill->sHP > pSkillCaster->GetHealth()) goto fail_return; pSkillCaster->HpChange(-pSkill->sHP); } // Support skills like "Sacrifice", that sacrifice your HP for another's. if (pSkill->sHP >= 10000) { // Can't cast this on ourself. if (pSkillCaster == pSkillTarget) return false; // Take 10,000 HP from the caster (note: DB is set to 10,0001 but server will always take 10,000...) pSkillCaster->HpChange(-10000); } } } return true; fail_return: SendSkillFailed(); return false; } bool MagicInstance::ExecuteType1() { if (pSkill == nullptr) return false; _MAGIC_TYPE1* pType = g_pMain->m_Magictype1Array.GetData(nSkillID); if (pType == nullptr) return false; int damage = 0; bool bResult = false; vector casted_member; if (!bIsRecastingSavedMagic && sTargetID >= 0 && (pSkillTarget && pSkillTarget->HasSavedMagic(nSkillID))) return false; if (sTargetID == -1) { std::vector unitList; g_pMain->GetUnitListFromSurroundingRegions(pSkillCaster, &unitList); foreach (itr, unitList) { Unit * pTarget = g_pMain->GetUnitPtr(*itr); if(pTarget == nullptr) continue; if (TO_USER(pSkillCaster)->isInArena() && pSkillCaster == pTarget) continue; if (pTarget->isPlayer()) { if (TO_USER(pTarget)->isGM()) continue; if (pSkillCaster->GetNation() != pTarget->GetNation() || pTarget->GetZoneID() == ZONE_ARENA) TO_USER(pTarget)->RemoveStealth(); } if (pSkillCaster != pTarget && !pTarget->isDead() && !pTarget->isBlinking() && pTarget->isAttackable() && CMagicProcess::UserRegionCheck(pSkillCaster, pTarget, pSkill,pSkill->sRange, sData[0], sData[2])) casted_member.push_back(pTarget); if (pSkill->sRange > 0 && (pSkillCaster->GetDistanceSqrt(pTarget) >= (float)pSkill->sRange *2)) continue; damage = pSkillCaster->GetDamage(pTarget, pSkill); pTarget->HpChange(-damage, pSkillCaster); } // If you managed to not hit anything with your AoE, you're still gonna have a cooldown (You should l2aim) if (casted_member.empty() || (sTargetID == -1 && casted_member.empty())) { SendSkill(); return true; } bResult = 1; } if (pSkillTarget != nullptr && !pSkillTarget->isDead()) { bResult = 1; uint16 sAdditionalDamage = pType->sAddDamage; // Give increased damage in war zones (as per official 1.298~1.325) // This may need to be revised to use the last nation to win the war, or more accurately, // the nation that last won the war 3 times in a row (whether official behaves this way now is unknown). if (pSkillCaster->isPlayer() && pSkillTarget->isPlayer()) { if (pSkillCaster->GetMap()->isWarZone()) sAdditionalDamage /= 2; else sAdditionalDamage /= 3; } damage = pSkillCaster->GetDamage(pSkillTarget, pSkill); // Only add additional damage if the target's not currently blocking it. // NOTE: Not sure whether to count this as physical or magic damage. // Using physical damage, due to the nature of these skills. if (!pSkillTarget->m_bBlockPhysical) damage += sAdditionalDamage; if (pSkillCaster->GetZoneID() == ZONE_CHAOS_DUNGEON) damage = pType->sAddDamage / 10; pSkillTarget->HpChange(-damage, pSkillCaster); if (pSkillTarget->m_bReflectArmorType != 0 && pSkillCaster != pSkillTarget) ReflectDamage(damage, pSkillTarget); } // yeni koydum if (pSkillCaster->isPlayer()) TO_USER(pSkillCaster)->ItemWoreOut(ATTACK, damage); // /This should only be sent once. I don't think there's reason to enforce this, as no skills behave otherwise sData[3] = (damage == 0 ? SKILLMAGIC_FAIL_ATTACKZERO : 0); // Send the skill data in the current context to the caster's region if (pSkill->iNum !=106520 && pSkill->iNum !=106802 && pSkill->iNum !=206520 && pSkill->iNum !=105520 && pSkill->iNum !=205520 && pSkill->iNum !=206802 && pSkill->iNum !=106820 && pSkill->iNum !=206820) SendSkill(); return bResult; } bool MagicInstance::ExecuteType2() { /* NOTE: Archery skills work differently to most other skills. When an archery skill is used, the client sends MAGIC_FLYING (instead of MAGIC_CASTING) to show the arrows flying in the air to their targets. The client chooses the target(s) to be hit by the arrows. When an arrow hits a target, it will send MAGIC_EFFECTING which triggers this handler. An arrow sent may not necessarily hit a target. As such, for archery skills that use multiple arrows, not all n arrows will necessarily hit their target, and thus they will not necessarily call this handler all n times. What this means is, we must remove all n arrows from the user in MAGIC_FLYING, otherwise it is not guaranteed all arrows will be hit and thus removed. (and we can't just go and take all n items each time an arrow hits, that could potentially mean 25 arrows are removed [5 each hit] when using "Arrow Shower"!) However, via the use of hacks, this MAGIC_FLYING step can be skipped -- so we must also check to ensure that there arrows are indeed flying, to prevent users from spamming the skill without using arrows. */ if (pSkill == nullptr) return false; _MAGIC_TYPE2 *pType = g_pMain->m_Magictype2Array.GetData(nSkillID); if (pType == nullptr) return false; int damage = 0; bool bResult = false; float total_range = 0.0f; // These variables are used for range verification! int sx, sz, tx, tz; int range = 0; // If we need arrows, then we require a bow. // This check is needed to allow for throwing knives (the sole exception at this time. // In this case, 'NeedArrow' is set to 0 -- we do not need a bow to use throwing knives, obviously. if (pType->bNeedArrow > 0) { _ITEM_TABLE * pTable = nullptr; if (pSkillCaster->isPlayer()) { if (TO_USER(pSkillCaster)->isWeaponsDisabled()) return false; // Not wearing a left-handed bow pTable = TO_USER(pSkillCaster)->GetItemPrototype(LEFTHAND); if (pTable == nullptr || !pTable->isBow()) { pTable = TO_USER(pSkillCaster)->GetItemPrototype(RIGHTHAND); // Not wearing a right-handed (2h) bow either! if (pTable == nullptr || !pTable->isBow()) return false; } } else { // TODO: Verify this. It's more a placeholder than anything. pTable = g_pMain->GetItemPtr(TO_NPC(pSkillCaster)->m_iWeapon_1); if (pTable == nullptr) return false; } // For arrow skills, we require a bow & its range. range = pTable->m_sRange; } else { // For non-arrow skills (i.e. throwing knives) we should use the skill's range. range = pSkill->sRange; } // is this checked already? if (pSkillTarget == nullptr || pSkillTarget->isDead()) goto packet_send; total_range = pow(((pType->sAddRange * range) / 100.0f), 2.0f) ; // Range verification procedure. sx = (int)pSkillCaster->GetX(); tx = (int)pSkillTarget->GetX(); sz = (int)pSkillCaster->GetZ(); tz = (int)pSkillTarget->GetZ(); if ((pow((float)(sx - tx), 2.0f) + pow((float)(sz - tz), 2.0f)) > total_range) // Target is out of range, exit. goto packet_send; if (pSkillCaster->isPlayer()) { CUser * pUser = TO_USER(pSkillCaster); Guard lock(pUser->_unitlock); // No arrows currently flying. if (pUser->m_flyingArrows.empty()) goto packet_send; ArrowList::iterator arrowItr; bool bFoundArrow = false; for (auto itr = pUser->m_flyingArrows.begin(); itr != pUser->m_flyingArrows.end();) { if (UNIXTIME >= itr->tFlyingTime + ARROW_EXPIRATION_TIME) { itr = pUser->m_flyingArrows.erase(itr); } else { if (itr->nSkillID == nSkillID) { arrowItr = itr; /* don't break out here to ensure we remove all expired arrows */ bFoundArrow = true; } ++itr; } } // No flying arrow matching this skill's criteria was found. // User's probably cheating. if (!bFoundArrow) goto packet_send; // Remove this instance's arrow now that we've found it. pUser->m_flyingArrows.erase(arrowItr); } damage = pSkillCaster->GetDamage(pSkillTarget, pSkill); // Get damage points of enemy. pSkillTarget->HpChange(-damage, pSkillCaster); // Reduce target health point. if (pSkillTarget->m_bReflectArmorType != 0 && pSkillCaster != pSkillTarget) ReflectDamage(damage, pSkillTarget); bResult = true; packet_send: // This should only be sent once. I don't think there's reason to enforce this, as no skills behave otherwise sData[3] = (damage == 0 ? SKILLMAGIC_FAIL_ATTACKZERO : 0); // Send the skill data in the current context to the caster's region if (pSkill->iNum != 208562 && pSkill->iNum != 108562 && pSkill->iNum != 208566 && pSkill->iNum != 108566) SendSkill(); return bResult; } // Applied when a magical attack, healing, and mana restoration is done. bool MagicInstance::ExecuteType3() { if (pSkill == nullptr) return false; _MAGIC_TYPE3* pType = g_pMain->m_Magictype3Array.GetData(nSkillID); if (pType == nullptr) return false; int damage = 0, duration_damage = 0; vector casted_member; // If the target's a group of people... if (sTargetID == -1) { std::vector unitList; g_pMain->GetUnitListFromSurroundingRegions(pSkillCaster, &unitList); if(pType->sFirstDamage > 0 || pType->sTimeDamage > 0) casted_member.push_back(pSkillCaster); foreach (itr, unitList) { Unit * pTarget = g_pMain->GetUnitPtr(*itr); if(pTarget == nullptr) continue; if (pTarget->isNPC() && pTarget->GetNation() == pSkillCaster->GetNation() && pType->sFirstDamage < 0 && !TO_NPC(pTarget)->isMonster()) continue; if(pTarget->GetEventRoom() != pSkillCaster->GetEventRoom()) continue; if (pSkillCaster != pTarget && !pTarget->isDead() && pTarget->isAttackable() && CMagicProcess::UserRegionCheck(pSkillCaster, pTarget, pSkill, pType->bRadius, sData[0], sData[2])) casted_member.push_back(pTarget); } // If you managed to not hit anything with your AoE, you're still gonna have a cooldown (You should l2aim) if (casted_member.empty() || (sTargetID == -1 && casted_member.empty())) { SendSkill(); return true; } } else { // If the target was a single unit. if (pSkillTarget == nullptr || pSkillTarget->isDead() || (pSkillTarget->isPlayer() && (TO_USER(pSkillTarget)->isBlinking() || TO_USER(pSkillTarget)->isInSafetyArea()))) return false; if (pSkill->sRange > 0 && (pSkillCaster->GetDistanceSqrt(pSkillTarget) > float(pSkill->sRange * 2))) return false; casted_member.push_back(pSkillTarget); } // Anger explosion requires the caster be a player // and a full anger gauge (which will be reset after use). if (pType->bDirectType == 18) { // Only players can cast this skill. if (!pSkillCaster->isPlayer() || !TO_USER(pSkillCaster)->hasFullAngerGauge()) return false; // Reset the anger gauge TO_USER(pSkillCaster)->UpdateAngerGauge(0); } sData[1] = 1; foreach (itr, casted_member) { Unit * pTarget = *itr; // it's checked above, not much need to check it again if(pTarget == nullptr) continue; // If you are casting an attack spell. if ((pType->sFirstDamage < 0) && (pType->bDirectType == 1 || pType->bDirectType == 8) && (nSkillID < 400000) && (pType->bDirectType != 11 && pType->bDirectType != 13)) damage = GetMagicDamage(pTarget, pType->sFirstDamage, pType->bAttribute); else damage = pType->sFirstDamage; // Allow for complete magic damage blocks. if (damage < 0 && pTarget->m_bBlockMagic) continue; if (pSkillCaster->isPlayer()) { if (pSkillCaster->GetZoneID() == ZONE_SNOW_BATTLE && g_pMain->m_byBattleOpen == SNOW_BATTLE) damage = -10; } bool bSendLightningStunSkill = true; if (pType->bDirectType < 2 && nSkillID < 400000 && pType->bAttribute == AttributeLightning || pType->bAttribute == AttributeIce && pTarget->isPlayer()) { // Success rate... if (pSkill->bSuccessRate < 100 && pSkill->bSuccessRate <= myrand(0, 100)) bSendLightningStunSkill = false; // Calculate user ressistance... else { uint16 nMaxRessitance = 250; // Monster Lighting Fix uint16 nTargetResistance = pType->bAttribute == AttributeIce ? pTarget->m_sColdR : pTarget->m_sLightningR; if (nTargetResistance > nMaxRessitance) nMaxRessitance = nTargetResistance; if (nTargetResistance >= myrand(0, nMaxRessitance - nTargetResistance) || myrand(0,3)) bSendLightningStunSkill = false; } if (nSkillID == 115810 || nSkillID == 215810) bSendLightningStunSkill = true; } // Non-durational spells. if (pType->bDuration == 0) { switch (pType->bDirectType) { // Affects target's HP case 1: // Disable to heal or minor NPC. if ((pTarget->isNPC() && pType->sTimeDamage > 0) || (pTarget->isNPC() && pType->sFirstDamage > 0)) return false; // "Critical Point" buff gives a chance to double HP from pots or the rogue skill "Minor heal". if (damage > 0 && pSkillCaster->hasBuff(BUFF_TYPE_DAMAGE_DOUBLE) && CheckPercent(500)) damage *= 2; /*if (damage > 0 && pTarget->isDevil()) //Created by Terry { damage *= 2; } Kurian HP POT x2 */ pTarget->HpChangeMagic(damage, pSkillCaster, (AttributeType) pType->bAttribute); if (pTarget->m_bReflectArmorType != 0 && pTarget != pSkillCaster && damage < 0) ReflectDamage(damage, pTarget); break; case 2: if (!pTarget->isDead() && pTarget->isPlayer()) pTarget->MSpChange(pType->sFirstDamage); else if (!pTarget->isDead()) pTarget->HpChange(pType->sFirstDamage,pSkillCaster); break; case 3: pTarget->MSpChange(damage); break; // "Magic Hammer" repairs equipped items. case 4: if (pTarget->isPlayer()) { if (damage > 0) { // Power Up Store ( Talia, Armor Destruction ve Tamamını ) if (pSkill->iNum == SPECIAL_MAGIC_HAMMER_SKILL_1 || pSkill->iNum == SPECIAL_MAGIC_HAMMER_SKILL_2 || pSkill->iNum == SPECIAL_MAGIC_HAMMER_SKILL_3) TO_USER(pTarget)->ItemWoreOut(ACID_ALL, damage); else TO_USER(pTarget)->ItemWoreOut(REPAIR_ALL, damage); } else TO_USER(pTarget)->ItemWoreOut(DEFENCE, -damage); } break; // Increases/decreases target's HP by a percentage case 5: if (pType->sFirstDamage < 100) damage = (pType->sFirstDamage * pTarget->GetHealth()) / -100; else damage = (pTarget->GetMaxHealth() * (pType->sFirstDamage - 100)) / 100; pTarget->HpChangeMagic(damage, pSkillCaster); break; // Caster absorbs damage based on percentage of target's HP. Players only. case 8: if (pType->sFirstDamage > 0) { if (pType->sFirstDamage < 100) damage = (pTarget->GetHealth() * 100) / pType->sFirstDamage; else damage = (pTarget->GetMaxHealth() - 100 * 100) / pType->sFirstDamage; } if (!pTarget->isDead() && pTarget->isPlayer()) { pTarget->HpChangeMagic(damage, pSkillCaster); pSkillCaster->HpChangeMagic(-(damage)); } else pTarget->HpChange(damage,pSkillCaster); break; // Caster absorbs damage based on percentage of target's max HP case 9: if (pType->sFirstDamage < 100) damage = (pType->sFirstDamage * pTarget->GetHealth()) / -100; else damage = (pTarget->GetMaxHealth() * (pType->sFirstDamage - 100)) / 100; pTarget->HpChangeMagic(damage, pSkillCaster); if (pTarget->isPlayer()) pSkillCaster->HpChangeMagic(-(damage)); break; // Inflicts true damage (i.e. disregards Ac/resistances/buffs, etc). case 11: pTarget->HpChange(damage, pSkillCaster); break; // Used by "Destination scroll" (whatever that is) case 12: continue; // Chance (how often?) to reduce the opponent's armor and weapon durability by sFirstDamage case 13: if (pTarget->isPlayer() && CheckPercent(500)) // using 50% for now. { TO_USER(pTarget)->ItemWoreOut(ATTACK, damage); TO_USER(pTarget)->ItemWoreOut(DEFENCE, damage); } break; // Drains target's MP, gives half of it to the caster as HP. Players only. // NOTE: Non-durational form (as in 1.8xx). This was made durational later (database configured). case 16: if (pTarget->isPlayer()) { pTarget->MSpChange(pType->sFirstDamage); pSkillCaster->HpChangeMagic(-(pType->sFirstDamage) / 2); } break; case 17: if (!pTarget->isNPC() && !pTarget->isDead() && pSkillCaster->GetZoneID() == ZONE_DELOS && !pSkillCaster->isDead()) { pTarget->HpChangeMagic(pType->sFirstDamage,pSkillCaster, (AttributeType) pType->bAttribute); } break; case 19: // Chaos Dungeon Skills if (pTarget->isPlayer()) { pTarget->HpChangeMagic(damage / 10, pSkillCaster, (AttributeType) pType->bAttribute); if (pTarget != pSkillCaster) ReflectDamage(damage, pTarget); } break; // Stat Scroll - MagicNum = 501011 case 255: if (TO_USER(pSkillCaster)->isPlayer()) { } break; } } // Durational spells! Durational spells only involve HP. else if (pType->bDuration != 0) { if (pType->bDirectType == 18) damage = -(int)(pSkillCaster->GetLevel() * 12.5); if (damage != 0) // In case there was first damage...... pTarget->HpChangeMagic(damage, pSkillCaster); // Initial damage!!! if (pTarget == nullptr) return false; if (pTarget->isAlive()) { // HP booster (should this actually just be using sFirstDamage as the percent of max HP, i.e. 105 being 5% of max HP each increment?) if (!pTarget->isNPC()) { if (pType->bDirectType == 14) duration_damage = (int)(pSkillCaster->GetLevel() * (10 + pSkillCaster->GetLevel() / 2.6)) + 3; else if (pType->bDirectType == 19) duration_damage = (pType->sTimeDamage / 10); else if (pType->sTimeDamage < 0 && pType->bAttribute != 4) duration_damage = GetMagicDamage(pTarget, pType->sTimeDamage, pType->bAttribute); else duration_damage = pType->sTimeDamage; // Allow for complete magic damage blocks. if (duration_damage < 0 && pTarget->m_bBlockMagic) continue; if (pType->bDirectType == 18) duration_damage = -(int)((pSkillCaster->GetLevel() * 12.5) * (pType->bDuration / 2)); // Setup DOT (damage over time) for (int k = 0; k < MAX_TYPE3_REPEAT; k++) { Unit::MagicType3 * pEffect = &pTarget->m_durationalSkills[k]; if(pEffect == nullptr) continue; if (pEffect->m_byUsed) continue; pEffect->m_byUsed = true; pEffect->m_tHPLastTime = 0; if (pType->bDirectType == 14) // HP Booster pot basamama sorunu pEffect->m_bHPInterval = 4; // interval of 4s between each damage loss/HP gain else pEffect->m_bHPInterval = 2; // interval of 2s between each damage loss/HP gain // number of ticks required at a rate of 2s per tick over the total duration of the skill float tickCount = (float)pType->bDuration / (float)pEffect->m_bHPInterval; // amount of HP to be given/taken every tick at a rate of 2s per tick pEffect->m_sHPAmount = (int16)(duration_damage / tickCount); pEffect->m_bTickCount = 0; pEffect->m_bTickLimit = (uint8) tickCount; pEffect->m_sSourceID = sCasterID; pEffect->m_byAttribute = pType->bAttribute; pEffect->m_sTo = false; break; } pTarget->m_bType3Flag = true; }else{ if (pType->sTimeDamage < 0 && pType->bAttribute != 4) duration_damage = GetMagicDamage(pTarget, pType->sTimeDamage, pType->bAttribute); else duration_damage = pType->sTimeDamage; for (int k = 0; k < MAX_TYPE3_REPEAT; k++) { Unit::MagicType3 * pEffect = &pSkillCaster->m_durationalSkills[k]; if(pEffect == nullptr) continue; if (pEffect->m_byUsed) continue; pEffect->m_byUsed = true; pEffect->m_tHPLastTime = 0; pEffect->m_bHPInterval = 2; // interval of 2s between each damage loss/HP gain // number of ticks required at a rate of 2s per tick over the total duration of the skill float tickCount = (float)pType->bDuration / (float)pEffect->m_bHPInterval; // amount of HP to be given/taken every tick at a rate of 2s per tick pEffect->m_sHPAmount = (int16)(duration_damage / tickCount); pEffect->m_bTickCount = 0; pEffect->m_bTickLimit = (uint8) tickCount; pEffect->m_sSourceID = pTarget->GetID(); pEffect->m_byAttribute = pType->bAttribute; pEffect->m_sTo = true; break; } pSkillCaster->m_bType3Flag = true; } } // Send the status updates (i.e. DOT or position indicators) to the party/user if (pTarget->isPlayer() // Ignore healing spells, not sure if this will break any skills. && pType->sTimeDamage < 0) { if (bSendLightningStunSkill) TO_USER(pTarget)->SendUserStatusUpdate(pType->bAttribute == POISON_R ? USER_STATUS_POISON : USER_STATUS_DOT, USER_STATUS_INFLICT); } } if (!bSendLightningStunSkill) { if (pSkillCaster->isNPC()) { sData[1] = 0; BuildAndSendSkillPacket(pSkillCaster, true, sCasterID, pTarget->GetID(), bOpcode, nSkillID, sData); TO_USER(pSkillCaster)->ShowEffect(nSkillID); } else { if (pSkill->bMoral == MORAL_ENEMY) { sData[1] = 0; BuildAndSendSkillPacket(pSkillCaster, true, sCasterID, pTarget->GetID(), bOpcode, nSkillID, sData); TO_USER(pSkillCaster)->ShowEffect(nSkillID); } else if (pSkill->bMoral != MORAL_ENEMY) { sData[1] = 0; BuildAndSendSkillPacket(pSkillCaster, true, sCasterID, pTarget->GetID(), bOpcode, nSkillID, sData); TO_USER(pSkillCaster)->ShowEffect(nSkillID); } } } else { if (bSendLightningStunSkill && LightStunSkills() && pTarget->isPlayer() || bSendLightningStunSkill && ColdSkills() && pTarget->isPlayer()) { MagicInstance instance; nSkillID += 80000; instance.nSkillID = nSkillID; if (LightStunSkillsNot() && pTarget->isPlayer()) { TO_USER(pTarget)->SendUserStatusUpdate(USER_STATUS_POISON, USER_STATUS_INFLICT); ExecuteType4(); } if(ColdSkillsNot() && pTarget->isPlayer()) { if (pTarget->isPlayer() && ColdSkillsNot()) { TO_USER(pTarget)->SendUserStatusUpdate(USER_STATUS_SPEED, USER_STATUS_INFLICT); ExecuteType4(); } _MAGIC_TYPE4* pType2 = g_pMain->m_Magictype4Array.GetData(nSkillID); if (pType2 == nullptr) return false; uint8 nTargetSpeedAmount = pType2->bSpeed; uint8 bResult = 1; uint16 sDuration = pType2->sDuration; Unit *pTmp = (pSkillCaster->isPlayer() ? pSkillCaster : pTarget); int16 sDataCopy[] = { sData[0], bResult, sData[2], sDuration, sData[4], pType2->bSpeed, sData[6] }; sDataCopy[5] = nTargetSpeedAmount; BuildAndSendSkillPacket(pTmp, true, sCasterID, pTarget->GetID(), bOpcode, nSkillID, sDataCopy); } else BuildAndSendSkillPacket(pSkillCaster, true, sCasterID, pTarget->GetID(), bOpcode, nSkillID, sData); TO_USER(pSkillCaster)->ShowEffect(nSkillID); } else // Send the skill data in the current context to the caster's region, with the target explicitly set. // In the case of AOEs, this will mean the AOE will show the AOE's effect on the user (not show the AOE itself again). if (pSkill->bType[1] == 0 || pSkill->bType[1] == 3) BuildAndSendSkillPacket(pSkillCaster, true, sCasterID, pTarget->GetID(), bOpcode, nSkillID, sData); } // Tell the AI server we're healing someone (so that they can choose to pick on the healer!) if (pType->bDirectType == 1 && damage > 0 && sCasterID != sTargetID) { Packet result(AG_HEAL_MAGIC); result << sCasterID; g_pMain->Send_AIServer(&result); } } // Allow for AOE effects. if (sTargetID == -1 && pSkill->bType[0] == 3) SendSkill(); return true; } bool MagicInstance::ExecuteType4() { if (pSkill == nullptr) return false; _MAGIC_TYPE4* pType = g_pMain->m_Magictype4Array.GetData(nSkillID); if (pType == nullptr) return false; int damage = 0; vector casted_member; if (!bIsRecastingSavedMagic && sTargetID >= 0 && (pSkillTarget && pSkillTarget->HasSavedMagic(nSkillID))) return false; if (sTargetID >= 0 && pType->bBuffType == BUFF_TYPE_FREEZE && (pSkillTarget && pSkillTarget->isNPC())) return false; if (sTargetID == -1) { std::vector unitList; g_pMain->GetUnitListFromSurroundingRegions(pSkillCaster, &unitList); foreach (itr, unitList) { Unit * pTarget = g_pMain->GetUnitPtr(*itr); if(pTarget == nullptr) continue; if ((TO_USER(pSkillCaster)->isInArena() ||TO_USER(pSkillCaster)->isInPartyArena()) && pSkillCaster == pTarget) continue; if (pTarget->isPlayer()) { if (TO_USER(pTarget)->isGM()) continue; } if (!pTarget->isDead() && !pTarget->isBlinking() && pTarget->isAttackable() && CMagicProcess::UserRegionCheck(pSkillCaster, pTarget, pSkill, pType->bRadius, sData[0], sData[2])){ casted_member.push_back(pTarget); if (pTarget->isPlayer() && (pSkillCaster->GetNation() != pTarget->GetNation() || pTarget->GetZoneID() == ZONE_ARENA)) TO_USER(pTarget)->RemoveStealth(); } } if(pSkill->bType[0] == 4) SendSkill(); if (casted_member.empty()) { if (pSkillCaster->isPlayer()) { if (pSkill->bMoral == MORAL_PARTY_ALL) casted_member.push_back(pSkillCaster); else { return false; } } } } else { // If the target was another single unit. if (pSkillTarget == nullptr || pSkillTarget->isDead() || (pSkillTarget->isBlinking() && !bIsRecastingSavedMagic)) return false; casted_member.push_back(pSkillTarget); } foreach (itr, casted_member) { Unit * pTarget = *itr; if(pTarget == nullptr) continue; if (sTargetID != -1 && pSkill->sRange > 0 && (pSkillCaster->GetDistanceSqrt(pTarget) > float(pSkill->sRange * 2)) && pType->bBuffType != BUFF_TYPE_HP_MP) continue; uint8 bResult = 1; _BUFF_TYPE4_INFO pBuffInfo; bool bAllowCastOnSelf = false; uint16 sDuration = pType->sDuration; // Speed Skills bool mSendSpeed = true; uint8 nTargetSpeedAmount = pType->bSpeed; // A handful of skills (Krowaz, currently) should use the caster as the target. // As such, we should correct this before any other buff/debuff logic is handled. switch (pType->bBuffType) { case BUFF_TYPE_UNDEAD: case BUFF_TYPE_UNSIGHT: case BUFF_TYPE_BLOCK_PHYSICAL_DAMAGE: case BUFF_TYPE_BLOCK_MAGICAL_DAMAGE: if (!pSkillCaster->isPlayer() && pSkillCaster->GetZoneID() != ZONE_CHAOS_DUNGEON) continue; pTarget = pSkillCaster; bAllowCastOnSelf = true; break; } bool bBlockingDebuffs = pTarget->m_bBlockCurses; // Skill description: Blocks all curses and has a chance to reflect the curse back onto the caster. // NOTE: the exact rate is undefined, so we'll have to guess and improve later. if (pType->isDebuff() && pTarget->m_bReflectCurses) { const short reflectChance = 25; // % chance to reflect. if (CheckPercent(reflectChance * 10)) { pTarget = pSkillCaster; // skill has been reflected, the target is now the caster. bBlockingDebuffs = (pTarget->m_bBlockCurses || pTarget->m_bReflectCurses); bAllowCastOnSelf = true; } // Didn't reflect, so we'll just block instead. else { bBlockingDebuffs = true; } } Type4BuffMap::iterator buffItr = pTarget->m_buffMap.find(pType->bBuffType); // Identify whether or not a skill (buff/debuff) with this buff type was already cast on the player. // NOTE: Buffs will already be cast on a user when trying to recast. // We should not error out in this case. bool bSkillTypeAlreadyOnTarget = (!bIsRecastingSavedMagic && buffItr != pTarget->m_buffMap.end()); // Debuffs 'stack', in that the expiry time is reset each time. // Debuffs also take precedence over buffs of the same nature, so we should ensure they get removed // rather than just stacking the modifiers, as the client only supports one (de)buff of that type active. if (bSkillTypeAlreadyOnTarget && pType->isDebuff()) { CMagicProcess::RemoveType4Buff(pType->bBuffType, pTarget, false); bSkillTypeAlreadyOnTarget = false; } // If this skill is a debuff, and we are in the crossfire, // we should not bother debuffing ourselves (that would be bad!) // Note that we should allow us if there's an explicit override (i.e. with Krowaz self-debuffs) if (!bAllowCastOnSelf && pType->isDebuff() && pTarget == pSkillCaster) continue; // If the user already has this buff type cast on them (debuffs should just reset the duration) if ((bSkillTypeAlreadyOnTarget && pType->isBuff()) // or it's a curse (debuff), and we're blocking them || (pType->isDebuff() && bBlockingDebuffs) // or we couldn't grant the (de)buff... || !CMagicProcess::GrantType4Buff(pSkill, pType, pSkillCaster, pTarget, bIsRecastingSavedMagic)) { if (sTargetID != -1 // only error out when buffing a target, otherwise we break the mass-(de)buff. // Only buffs should error here, unless it's a debuff & the user's blocking it. && (pType->isBuff() || (pType->isDebuff() && bBlockingDebuffs))) { bResult = 0; goto fail_return; } // Debuffs of any kind, or area buffs should be ignored and carry on. // Usually - debuffs specifically - they correspond with attack skills which should // not be reset on fail. continue; } // Only players can store persistent skills. if (nSkillID > 500000 && pTarget->isPlayer()) { // Persisting effects will already exist in the map if we're recasting it. if (!bIsRecastingSavedMagic) pTarget->InsertSavedMagic(nSkillID, pType->sDuration); else sDuration = pTarget->GetSavedMagicDuration(nSkillID); } if (pSkillCaster->isPlayer() && (sTargetID != -1 && pSkill->bType[0] == 4) && !pSkillCaster->isBlinking()) pSkillCaster->MSpChange( -(pSkill->sMsp) ); // We do not want to reinsert debuffs into the map (which had their expiry times reset above). if (!bSkillTypeAlreadyOnTarget) { pBuffInfo.m_nSkillID = nSkillID; pBuffInfo.m_bBuffType = pType->bBuffType; pBuffInfo.m_bIsBuff = pType->bIsBuff; pBuffInfo.m_bDurationExtended = false; pBuffInfo.m_tEndTime = UNIXTIME + sDuration; // Add the buff into the buff map. pTarget->AddType4Buff(pType->bBuffType, pBuffInfo); } // Speed skill posibility... if (pTarget -> isPlayer() || pTarget->isNPC()) { if ((pSkill->bMoral == MORAL_ENEMY || pSkill->bMoral == MORAL_AREA_ENEMY) && (pType->bBuffType == BUFF_TYPE_SPEED2 || pType->bBuffType == BUFF_TYPE_SPEED || pType->bBuffType == BUFF_TYPE_STUN)) { // Success rate...by Terry if (pSkill->bSuccessRate < 100 && pSkill->bSuccessRate <= myrand(0, 100)) mSendSpeed = false; else { // Calculate user ressistance... uint16 nMaxRessitance = 250; uint16 nTargetResistance = pType->bBuffType == BUFF_TYPE_SPEED2 ? pTarget->m_sColdR : pTarget->m_sLightningR; if (nTargetResistance > nMaxRessitance) nMaxRessitance = nTargetResistance; if (nTargetResistance >= myrand(0, nMaxRessitance - nTargetResistance) || myrand(0,3)) mSendSpeed = false; } if (mSendSpeed) { _MAGIC_TYPE4 * pTypeTarget; Guard lock(pTarget->m_buffLock); auto itr = pTarget->m_buffMap.find(BUFF_TYPE_SPEED || BUFF_TYPE_SPEED2); if (itr != pTarget->m_buffMap.end() && (pTypeTarget = g_pMain->m_Magictype4Array.GetData(itr->second.m_nSkillID))) nTargetSpeedAmount = pTypeTarget->bSpeed; } if (!mSendSpeed) goto fail_return; } } // Update character stats. if (pTarget->isPlayer()) { TO_USER(pTarget)->SetUserAbility(); TO_USER(pTarget)->Send2AI_UserUpdateInfo(); if (pType->isBuff() && pType->bBuffType == BUFF_TYPE_HP_MP) TO_USER(pTarget)->HpChange(pTarget->m_sMaxHPAmount); } /*else { TO_NPC(pTarget)->Send2AI_NpcUpdateInfo(); if (pType->isBuff() && pType->bBuffType == BUFF_TYPE_HP_MP) TO_NPC(pTarget)->HpChange(pTarget->m_sMaxHPAmount); }*/ fail_return: if (pSkill->bType[1] == 0 || pSkill->bType[1] == 4) { Unit *pTmp = (pSkillCaster->isPlayer() ? pSkillCaster : pTarget); int16 sDataCopy[] = { sData[0], bResult, sData[2], sDuration, sData[4], pType->bSpeed, sData[6] }; if (!mSendSpeed) { if (pSkillCaster->isNPC()) { sDataCopy[1] = 0; BuildAndSendSkillPacket(pSkillCaster, true, sCasterID, pTarget->GetID(), bOpcode, nSkillID, sDataCopy); TO_USER(pSkillCaster)->ShowEffect(nSkillID); } else { if (pSkill->bMoral == MORAL_ENEMY) { sDataCopy[1] = 0; BuildAndSendSkillPacket(pSkillCaster, true, sCasterID, pTarget->GetID(), bOpcode, nSkillID, sDataCopy); TO_USER(pSkillCaster)->ShowEffect(nSkillID); } else if (pSkill->bMoral != MORAL_ENEMY) { sDataCopy[1] = 0; BuildAndSendSkillPacket(pSkillCaster, true, sCasterID, pTarget->GetID(), bOpcode, nSkillID, sDataCopy); TO_USER(pSkillCaster)->ShowEffect(nSkillID); } } } else BuildAndSendSkillPacket(pTmp, true, sCasterID, pTarget->GetID(), bOpcode, nSkillID, sDataCopy); if (mSendSpeed && (pSkill->bMoral >= MORAL_ENEMY || pSkill->bMoral >= MORAL_AREA_ENEMY) && pTarget->isPlayer()) { UserStatus status = USER_STATUS_POISON; if (pType->bBuffType == BUFF_TYPE_SPEED || pType->bBuffType == BUFF_TYPE_SPEED2) status = USER_STATUS_SPEED; TO_USER(pTarget)->SendUserStatusUpdate(status, USER_STATUS_INFLICT); } if (pType->bBuffType == BUFF_TYPE_DECREASE_RESIST || pType->bBuffType == BUFF_TYPE_DISABLE_TARGETING || pType->bBuffType == BUFF_TYPE_AC // for Torment by Terry. && sTargetID != -1) SendSkill(); } if (bResult == 0 && pSkillCaster->isPlayer()) SendSkillFailed((*itr)->GetID()); } return true; } bool MagicInstance::ExecuteType5() { // Disallow anyone that isn't a player. if (!pSkillCaster->isPlayer() || pSkill == nullptr) return false; _MAGIC_TYPE5* pType = g_pMain->m_Magictype5Array.GetData(nSkillID); if (pType == nullptr) return false; vector casted_member; // Targeting a group of people (e.g. party) if (sTargetID == -1) { SessionMap sessMap = g_pMain->m_socketMgr.GetActiveSessionMap(); BOOST_FOREACH (auto itr, sessMap) { CUser * pTUser = TO_USER(itr.second); if (!pTUser->isInGame()) continue; // If the target's dead, only allow resurrection/self-resurrection spells. if (pTUser->isDead() && (pType->bType != RESURRECTION && pType->bType != RESURRECTION_SELF)) continue; // If the target's alive, we don't need to resurrect them. if (!pTUser->isDead() && (pType->bType == RESURRECTION || pType->bType == RESURRECTION_SELF)) continue; // Ensure the target's applicable for this skill. if (CMagicProcess::UserRegionCheck(pSkillCaster, pTUser, pSkill, pSkill->sRange, sData[0], sData[2])) casted_member.push_back(pTUser); } } // Targeting a single person else { if (pSkillTarget == nullptr || !pSkillTarget->isPlayer()) return false; // If the target's dead, only allow resurrection/self-resurrection spells. if (pSkillTarget->isDead() && (pType->bType != RESURRECTION && pType->bType != RESURRECTION_SELF)) return false; // If the target's alive, we don't need to resurrect them. if (!pSkillTarget->isDead() && (pType->bType == RESURRECTION || pType->bType == RESURRECTION_SELF)) return false; casted_member.push_back(TO_USER(pSkillTarget)); } foreach (itr, casted_member) { Type4BuffMap::iterator buffIterator; CUser * pTUser = (*itr); if(pTUser == nullptr) continue; int skillCount = 0; bool bRemoveDOT = false; switch (pType->bType) { // Remove all DOT skills case REMOVE_TYPE3: for (int i = 0; i < MAX_TYPE3_REPEAT; i++) { Unit::MagicType3 * pEffect = &pTUser->m_durationalSkills[i]; if(pEffect == nullptr) continue; if (!pEffect->m_byUsed) continue; // Ignore healing-over-time skills if (pEffect->m_sHPAmount >= 0) { skillCount++; continue; } pEffect->Reset(); // TODO: Wrap this up (ugh, I feel so dirty) Packet result(WIZ_MAGIC_PROCESS, uint8(MAGIC_DURATION_EXPIRED)); result << uint8(200); // removes DOT skill pTUser->Send(&result); bRemoveDOT = true; } if (skillCount == 0) { pTUser->m_bType3Flag = false; if (bRemoveDOT) pTUser->SendUserStatusUpdate(USER_STATUS_DOT, USER_STATUS_CURE); } break; case REMOVE_TYPE4: // Remove type 4 debuffs { Type4BuffMap buffMap = pTUser->m_buffMap; // copy the map so we can't break it while looping foreach (itr, buffMap) { if (itr->second.isDebuff()) { CMagicProcess::RemoveType4Buff(itr->first, pTUser); if (pTUser->isLockableScroll(itr->second.m_bBuffType)) pTUser->RecastLockableScrolls(itr->second.m_bBuffType); } } // NOTE: This originally checked to see if there were any active debuffs. // As this seems pointless (as we're removing all of them), it was removed // however leaving this note in, in case this behaviour in certain conditions // is required. pTUser->SendUserStatusUpdate(USER_STATUS_POISON, USER_STATUS_CURE); } break; case RESURRECTION_SELF: if (pSkillCaster != pTUser || pTUser->m_iLostExp == 0) continue; pTUser->Regene(1,nSkillID); break; case RESURRECTION: if (pTUser->CheckExistItem(pSkill->iUseItem, pType->sNeedStone)) { if (pTUser->RobItem(pSkill->iUseItem, pType->sNeedStone)) { TO_USER(pSkillCaster)->GiveItem(pSkill->iUseItem, (pType->sNeedStone / 2) + 1); pTUser->Regene(1,nSkillID); } } break; case REMOVE_BLESS: { if (CMagicProcess::RemoveType4Buff(BUFF_TYPE_HP_MP, pTUser)) { if (!pTUser->isDebuffed()) pTUser->SendUserStatusUpdate(USER_STATUS_POISON, USER_STATUS_CURE); } } break; } if (pSkill->bType[1] == 0 || pSkill->bType[1] == 5) { // Send the packet to the caster. sData[1] = 1; BuildAndSendSkillPacket(pSkillCaster, true, sCasterID, (*itr)->GetID(), bOpcode, nSkillID, sData); } } return true; } bool MagicInstance::ExecuteType6() { if (pSkill == nullptr || !pSkillCaster->isPlayer()) return false; _MAGIC_TYPE6 * pType = g_pMain->m_Magictype6Array.GetData(nSkillID); CUser * pCaster = TO_USER(pSkillCaster); uint16 sDuration = 0; if (pType == nullptr // Allow NPC transformations in PVP zones || (pType->bUserSkillUse != TransformationSkillUseNPC // All PVP zones. && pSkillCaster->GetMap()->canAttackOtherNation() // Exclude home zones (which can be invaded). && (pSkillCaster->GetZoneID() > ELMORAD && !(pSkillCaster->GetZoneID() == ZONE_KARUS_ESLANT || pSkillCaster->GetZoneID() == ZONE_ELMORAD_ESLANT))) || (!bIsRecastingSavedMagic && pCaster->isTransformed()) // All buffs must be removed before using transformation skills || (pType->bUserSkillUse != TransformationSkillUseNPC && pSkillCaster->isBuffed(true)) // Transformation nation. || (pType->bNation != 0 && pType->bNation != pCaster->GetNation())) { // If we're recasting it, then it's either already cast on us (i.e. we're changing zones) // or we're relogging. We need to remove it now, as the client doesn't see us using it. if (bIsRecastingSavedMagic) Type6Cancel(true); return false; } // We can ignore all these checks if we're just recasting on relog. if (!bIsRecastingSavedMagic) { if (pSkillTarget->HasSavedMagic(nSkillID)) return false; // Monster transformations require a transformation list. if (pType->bUserSkillUse == TransformationMonster) pCaster->RobItem(pSkill->nBeforeAction); // User's casting a new skill. Use the full duration. sDuration = pType->sDuration; } else { // Server's recasting the skill (kept on relog, zone change, etc.) int16 tmp = pSkillCaster->GetSavedMagicDuration(nSkillID); // Has it expired (or not been set?) -- just in case. if (tmp <= 0) return false; // it's positive, so no harm here in casting. sDuration = tmp; } switch (pType->bUserSkillUse) { case TransformationSkillUseMonster: pCaster->m_transformationType = TransformationMonster; break; case TransformationSkillUseNPC: pCaster->m_transformationType = TransformationNPC; break; case TransformationSkillUseSiege: case TransformationSkillUseSiege2: pCaster->m_transformationType = TransformationSiege; break; default: // anything return false; } Packet result(AG_USER_TRANS_CHANGE); result << pCaster->GetID() << uint8(pCaster->m_transformationType); g_pMain->Send_AIServer(&result); // TODO : Save duration, and obviously end pCaster->m_sTransformID = pType->sTransformID; pCaster->m_tTransformationStartTime = UNIXTIME; pCaster->m_sTransformationDuration = sDuration; pSkillCaster->StateChangeServerDirect(3, nSkillID); // TODO : Give the users ALL TEH BONUSES!! sData[1] = 1; sData[3] = sDuration; SendSkill(); pSkillCaster->InsertSavedMagic(nSkillID, sDuration); return true; } bool MagicInstance::ExecuteType7() { if (pSkill == nullptr) return false; _MAGIC_TYPE7* pType = g_pMain->m_Magictype7Array.GetData(nSkillID); if (pType == nullptr) return false; vector casted_member; int damage = pType->sDamage; if (sTargetID == -1) { std::vector unitList; g_pMain->GetUnitListFromSurroundingRegions(pSkillCaster, &unitList); if(pType->sDamage > 0) casted_member.push_back(pSkillCaster); foreach (itr, unitList) { Unit * pTarget = g_pMain->GetUnitPtr(*itr); if(pTarget == nullptr) continue; if (pTarget->isNPC() && pTarget->GetNation() == pSkillCaster->GetNation() && !TO_NPC(pTarget)->isMonster()) continue; if (pSkillCaster != pTarget && !pTarget->isDead() && !pTarget->isBlinking() && pTarget->isAttackable() && CMagicProcess::UserRegionCheck(pSkillCaster, pTarget, pSkill, pType->bRadius, sData[0], sData[2])) casted_member.push_back(pTarget); } if (casted_member.empty() || (sTargetID == -1 && casted_member.empty())) return false; } else { if (pSkillTarget != nullptr && !pSkillTarget->isDead()) { if (pType->bTargetChange == 1) { if (damage < 0) return false; pSkillTarget->HpChange(-damage, pSkillCaster); return true; } } } foreach (itr, casted_member) { Unit * pTarget = *itr; if(pTarget == nullptr) continue; if (pType->bTargetChange == 1) { if (damage < 0) continue; pTarget->HpChange(-damage, pSkillCaster); } } return false; } // Warp, resurrection, and summon spells. bool MagicInstance::ExecuteType8() { if (pSkill == nullptr) return false; _MAGIC_TYPE8* pType = g_pMain->m_Magictype8Array.GetData(nSkillID); if (pType == nullptr) return false; vector casted_member; if (sTargetID == -1) { // TODO: Localise this loop to make it not suck (the life out of the server). SessionMap sessMap = g_pMain->m_socketMgr.GetActiveSessionMap(); BOOST_FOREACH (auto itr, sessMap) { CUser* pTUser = TO_USER(itr.second); if (CMagicProcess::UserRegionCheck(pSkillCaster, pTUser, pSkill, pType->sRadius, sData[0], sData[2])) casted_member.push_back(pTUser); } if (casted_member.empty()) return false; } else { // If the target was another single player. CUser* pTUser = g_pMain->GetUserPtr(sTargetID); if (pTUser == nullptr) return false; casted_member.push_back(pTUser); } foreach (itr, casted_member) { CUser* pTUser = *itr; if(pTUser == nullptr) continue; uint8 bResult = 0; _OBJECT_EVENT* pEvent = nullptr; if (pType->bWarpType != 11) { // Warp or summon related: targets CANNOT be dead. if (pTUser->isDead() // Players can have their teleports blocked by skills. || !pTUser->canTeleport()) goto packet_send; } // Resurrection related: we're reviving DEAD targets. else if (!pTUser->isDead()) goto packet_send; // Is target already warping? if (pTUser->m_bWarp) goto packet_send; switch(pType->bWarpType) { case 0: // For Kurian Rush by Terry { CUser * pCaster = TO_USER(pSkillCaster); if (KurianStuns() && (*itr)->isPlayer()) { MagicInstance instance; nSkillID += 80000; instance.nSkillID = nSkillID; if (KurianStunsNot() && (*itr)->isPlayer()) { (*itr)->SendUserStatusUpdate(USER_STATUS_POISON, USER_STATUS_INFLICT); ExecuteType4(); _MAGIC_TYPE4* pType2 = g_pMain->m_Magictype4Array.GetData(nSkillID); nSkillID -= 80000; if (pType2 == nullptr) return false; uint8 bResult = 1; uint16 sDuration = pType2->sDuration; uint8 nTargetSpeedAmount = pType2->bSpeed; Unit *pTmp = (pSkillCaster->isPlayer() ? pSkillCaster : *itr); int16 sDataCopy[] = { sData[0], bResult, sData[2], sDuration, sData[4], pType2->bSpeed, sData[6] }; } } } break; case 1: // Send source to resurrection point. // Disable gate / escape etc for forgetten temple and pvp zones... if (pTUser->GetZoneID() > ZONE_BIFROST && (nSkillID == 109035 || nSkillID == 110035 || nSkillID == 209035 || nSkillID == 210035)) { SendSkillFailed(); return false; } // Send the packet to the target. sData[1] = 1; BuildAndSendSkillPacket(*itr, true, sCasterID, (*itr)->GetID(), bOpcode, nSkillID, sData); if (pTUser->GetMap() == nullptr) continue; pEvent = pTUser->GetMap()->GetObjectEvent(pTUser->m_sBind); if (pEvent != nullptr) pTUser->Warp(uint16(pEvent->fPosX * 10), uint16(pEvent->fPosZ * 10)); else if (pTUser->GetZoneID() <= ELMORAD || pTUser->GetMap()->isWarZone() || pTUser->GetMap()->canAttackOtherNation()) { _START_POSITION * pStartPosition = g_pMain->m_StartPositionArray.GetData(pTUser->GetZoneID()); if (pStartPosition) pTUser->Warp((uint16)((pTUser->GetNation() == KARUS ? pStartPosition->sKarusX : pStartPosition->sElmoradX) + myrand(0, pStartPosition->bRangeX)) * 10,(uint16)((pTUser->GetNation() == KARUS ? pStartPosition->sKarusZ : pStartPosition->sElmoradZ) + myrand(0, pStartPosition->bRangeZ)) * 10); else return false; } else if (pTUser->GetZoneID() == ZONE_JURAD_MOUNTAIN) { uint16 KillCount1, KillCount2, KillCount3; float x = 0.0f, z = 0.0f; KillCount1 = pTUser->GetNation() == KARUS ? g_pMain->pTempleEvent.KarusDeathRoom1[pTUser->GetEventRoom()] : g_pMain->pTempleEvent.ElmoDeathRoom1[pTUser->GetEventRoom()]; KillCount2 = pTUser->GetNation() == KARUS ? g_pMain->pTempleEvent.KarusDeathRoom2[pTUser->GetEventRoom()] : g_pMain->pTempleEvent.ElmoDeathRoom2[pTUser->GetEventRoom()]; KillCount3 = pTUser->GetNation() == KARUS ? g_pMain->pTempleEvent.KarusDeathRoom3[pTUser->GetEventRoom()] : g_pMain->pTempleEvent.ElmoDeathRoom3[pTUser->GetEventRoom()]; if (KillCount1 > 3 && KillCount2 < 4) { if (pTUser->GetNation() == KARUS) { x = (float) (223 + (myrand(-1,1))); z = (float) (672 + (myrand(-1,1))); }else { x = (float) (800 + (myrand(-1,1))); z = (float) (343 + (myrand(-1,1))); } }else if(KillCount2 > 3 && KillCount3 < 4) { if (pTUser->GetNation() == KARUS) { x = (float) (340 + (myrand(-1,1))); z = (float) (847 + (myrand(-1,1))); }else { x = (float) (690 + (myrand(-1,1))); z = (float) (172 + (myrand(-1,1))); } }else if(KillCount3 > 3) { if (pTUser->GetNation() == KARUS) { x = (float) (512 + (myrand(-1,1))); z = (float) (736 + (myrand(-1,1))); }else { x = (float) (512 + (myrand(-1,1))); z = (float) (282 + (myrand(-1,1))); } }else { short sx,sz; pTUser->GetStartPosition(sx, sz); x = sx; z = sz; } pTUser->Warp(uint16(x * 10), uint16(z * 10)); } else { _START_POSITION * pStartPosition = g_pMain->m_StartPositionArray.GetData(pTUser->GetZoneID()); if (pStartPosition) pTUser->Warp((uint16)((pTUser->GetNation() == KARUS ? pStartPosition->sKarusX : pStartPosition->sElmoradX) + myrand(0, pStartPosition->bRangeX)) * 10,(uint16)((pTUser->GetNation() == KARUS ? pStartPosition->sKarusZ : pStartPosition->sElmoradZ) + myrand(0, pStartPosition->bRangeZ)) * 10); else return false; } break; case 2: // Send target to teleport point WITHIN the zone. // LATER!!! break; case 3: // Send target to teleport point OUTSIDE the zone. // LATER!!! break; case 5: // Send target to a hidden zone. // LATER!!! break; case 11: // Resurrect a dead player. { // Send the packet to the target. sData[1] = 1; BuildAndSendSkillPacket(*itr, true, sCasterID, (*itr)->GetID(), bOpcode, nSkillID, sData); pTUser->m_bResHpType = USER_STANDING; // Target status is officially alive now. pTUser->HpChange(pTUser->m_iMaxHp, pSkillCaster); // Refill HP. pTUser->ExpChange( pType->sExpRecover/100 ,true); // Increase target experience. Packet result(AG_USER_REGENE); result << uint16((*itr)->GetSocketID()) << uint16(pTUser->GetHealth()); g_pMain->Send_AIServer(&result); } break; case 12: // Summon a target within the zone. // Disable telepert for forgetten temple... if(pTUser->GetZoneID() == ZONE_FORGOTTEN_TEMPLE || (pTUser->GetZoneID() > ZONE_BIFROST && (nSkillID == 490042 || nSkillID == 490050))) { SendSkillFailed(); return false; } // Cannot teleport users from other zones. if (pSkillCaster->GetZoneID() != pTUser->GetZoneID() // Cannot teleport ourselves. || pSkillCaster == pTUser) goto packet_send; // Send the packet to the target. sData[1] = 1; BuildAndSendSkillPacket(*itr, true, sCasterID, (*itr)->GetID(), bOpcode, nSkillID, sData); pTUser->Warp(pSkillCaster->GetSPosX(), pSkillCaster->GetSPosZ()); break; case 13: // Summon a target outside the zone. if (pSkillCaster->GetZoneID() == pTUser->GetZoneID()) // Different zone? goto packet_send; // Send the packet to the target. sData[1] = 1; BuildAndSendSkillPacket(*itr, true, sCasterID, (*itr)->GetID(), bOpcode, nSkillID, sData); pTUser->ZoneChange(pSkillCaster->GetZoneID(), pSkillCaster->GetX(), pSkillCaster->GetZ()) ; pTUser->UserInOut(INOUT_RESPAWN); break; case 20: // Teleport the source (radius) meters forward { // Calculate difference between where user is now and where they were previously // to figure out an orientation. // Should really use m_sDirection, but not sure what the value is exactly. float warp_x = pTUser->GetX() - pTUser->m_oldx, warp_z = pTUser->GetZ() - pTUser->m_oldz; // Unable to work out orientation, so we'll just fail (won't be necessary with m_sDirection). float distance = sqrtf(warp_x*warp_x + warp_z*warp_z); if (distance == 0.0f) goto packet_send; warp_x /= distance; warp_z /= distance; warp_x *= pType->sRadius; warp_z *= pType->sRadius; warp_x += pTUser->m_oldx; warp_z += pTUser->m_oldz; sData[1] = 1; BuildAndSendSkillPacket(*itr, true, sCasterID, (*itr)->GetID(), bOpcode, nSkillID, sData); pTUser->Warp(uint16(warp_x * 10), uint16(warp_z * 10)); } break; case 21: // Summon a monster within a zone with monster staff. { MonsterSummonList * pMonsterSummonList = g_pMain->m_MonsterSummonList.GetData(0); if (pMonsterSummonList) { int nRandom = myrand(0, 9999); int TotalMonsterRate = 0; std::vector<_MONSTER_SUMMON_LIST>::iterator RandomMonsterRate[10000]; memset(RandomMonsterRate, 0, sizeof(RandomMonsterRate)); for (std::vector<_MONSTER_SUMMON_LIST>::iterator itr = pMonsterSummonList->begin(); itr != pMonsterSummonList->end(); ++itr) { for(int ad=0; ad < itr->sProbability; ad++) RandomMonsterRate[TotalMonsterRate + ad] = itr; TotalMonsterRate += itr->sProbability; } std::vector<_MONSTER_SUMMON_LIST>::iterator itr = RandomMonsterRate[nRandom]; g_pMain->SpawnEventNpc(itr->sSid,true,pSkillCaster->GetZoneID(),pSkillCaster->GetX(),pSkillCaster->GetY(),pSkillCaster->GetZ(),1,10); break; } } break; // This is used by Wild Advent (70 rogue skill) and Descent, teleport the user to the target user (Moral check to distinguish between the 2 skills) case 25: float dest_x, dest_z = 0.0f; // If we're not even in the same zone, I can't teleport to you! if (pTUser->GetZoneID() != pSkillCaster->GetZoneID() || (pSkill->bMoral < MORAL_ENEMY && pSkillCaster->isHostileTo(pTUser)) || (pSkill->iNum > 500000 && pSkillCaster->GetZoneID() > ZONE_MORADONM2)) return false; dest_x = pTUser->GetX(); dest_z = pTUser->GetZ(); if (pSkillCaster->isPlayer() && (pSkillCaster->GetDistanceSqrt(pSkillTarget) <= (float)pType->sRadius)) TO_USER(pSkillCaster)->Warp(uint16(dest_x * 10), uint16(dest_z * 10)); else SendSkillFailed(); break; } bResult = 1; packet_send: // Send the packet to the caster. sData[1] = bResult; BuildAndSendSkillPacket(pSkillCaster, true, sCasterID, (*itr)->GetID(), bOpcode, nSkillID, sData); } return true; } bool MagicInstance::ExecuteType9() { if (pSkill == nullptr // Only players can use these skills. || !pSkillCaster->isPlayer()) return false; _MAGIC_TYPE9* pType = g_pMain->m_Magictype9Array.GetData(nSkillID); if (pType == nullptr) return false; CUser * pCaster = TO_USER(pSkillCaster); if(pCaster == nullptr) return false; Guard lock(pCaster->_unitlock); Type9BuffMap & buffMap = pCaster->m_type9BuffMap; // Ensure this type of skill isn't already in use. if (buffMap.find(pType->bStateChange) != buffMap.end()) { sData[1] = 0; SendSkillFailed(); return false; } sData[1] = 1; if (pType->bStateChange <= 2 && pCaster->canStealth() && pCaster->GetZoneID() != ZONE_FORGOTTEN_TEMPLE) { // Cannot stealth when already stealthed. // This prevents us from using both types 1 and 2 (i.e. dispel on move & dispel on attack) // at the same time. if (pCaster->m_bInvisibilityType != INVIS_NONE) { sData[1] = 0; SendSkillFailed(); return false; } // Invisibility perk does NOT apply when using these skills on monsters. if (pSkillTarget->isPlayer()) { pCaster->StateChangeServerDirect(7, pType->bStateChange); // Update the client to be invisible buffMap.insert(std::make_pair(pType->bStateChange, _BUFF_TYPE9_INFO(nSkillID, UNIXTIME + pType->sDuration))); } // Update all players nearby to tell them we're now invisible. SendSkill(); } else if (pType->bStateChange >= 3 && pType->bStateChange <= 4) { Packet result(WIZ_STEALTH, uint8(1)); result << pType->sRadius; // If the player's in a party, apply this skill to all members of the party. if (pCaster->isInParty() && pType->bStateChange == 4) { _PARTY_GROUP * pParty = g_pMain->GetPartyPtr(pCaster->GetPartyID()); if (pParty == nullptr) return false; for (int i = 0; i < MAX_PARTY_USERS; i++) { CUser *pUser = g_pMain->GetUserPtr(pParty->uid[i]); if (pUser == nullptr) continue; // If this user already has this skill active, we don't need to reapply it. if (pUser->m_type9BuffMap.find(pType->bStateChange) != pUser->m_type9BuffMap.end()) continue; pUser->m_type9BuffMap.insert(std::make_pair(pType->bStateChange, _BUFF_TYPE9_INFO(nSkillID, UNIXTIME + pType->sDuration))); pUser->Send(&result); // Ensure every user in the party is given the skill icon in the corner of the screen. BuildAndSendSkillPacket(pUser, false, sCasterID, pUser->GetID(), bOpcode, nSkillID, sData); } } else // not in a party, so just apply this skill to us. { buffMap.insert(std::make_pair(pType->bStateChange, _BUFF_TYPE9_INFO(nSkillID, UNIXTIME + pType->sDuration))); pCaster->Send(&result); // Ensure we are given the skill icon in the corner of the screen. SendSkill(false); // only update us, as only we need to know that we can see invisible players. } } else if(pType->bStateChange == 8)//pat summon { _ITEM_TABLE * pItemData = nullptr; if(pCaster == nullptr || (pItemData = pCaster->GetItemPrototype(SHOULDER)) == nullptr || !pItemData->isPet()) return false; _ITEM_DATA *pItem = nullptr; if ( (pItem = pCaster->GetItem(SHOULDER)) == nullptr || pItem->nNum != pItemData->Getnum()) return false; CPet *newPet = g_pMain->GetPetPtr(pItem->nSerialNum); if(newPet == nullptr ) return false; if(newPet->m_pNpc != nullptr) { if(newPet->m_pNpc->isAlive()) { Packet result(WIZ_PET); result << uint8(1) << uint8(5) << uint8(1) << uint16(-2); pCaster->Send(&result); SendSkillFailed(); return false; } } if((pCaster->isInPKZone() || pCaster->isInTempleEventZone()) || (pCaster->GetMap() != nullptr && pCaster->GetMap()->isWarZone())) { Packet result(WIZ_PET); result << uint8(1) << uint8(5) << uint8(1) << uint16(-3); pCaster->Send(&result); SendSkillFailed(); return false; } SendSkill(); pCaster->PetSkill(pItem->nSerialNum); sData[4] = newPet->GetID(); SendSkill(); pCaster->isSummonPet = true; } else if (pType->bStateChange == 9) { if (pCaster->GetZoneID() == ZONE_MORADON || pSkillCaster->GetZoneID() == ZONE_MORADONM2) return 0; buffMap.insert(std::make_pair(pType->bStateChange, _BUFF_TYPE9_INFO(nSkillID, UNIXTIME + pType->sDuration))); g_pMain->SpawnEventNpc(pType->sMonsterNum,false,pCaster->GetZoneID(),pCaster->GetX(),pCaster->GetY(),pCaster->GetZ(),1,2, pType->sDuration, /*pCaster->GetZoneID() == ZONE_MORADON ? 3 :*/ pCaster->GetNation(), pCaster->GetSocketID()); SendSkill(); } return true; } short MagicInstance::GetMagicDamage(Unit *pTarget, int total_hit, int attribute) { short damage = 0, temp_hit = 0, righthand_damage = 0, attribute_damage = 0; int random = 0, total_r = 0 ; uint8 result; if (pTarget == nullptr || pSkillCaster == nullptr) return 0; if(pTarget->isDead() || pSkillCaster->isDead()) return 0; // Trigger item procs if (pTarget->isPlayer() && pSkillCaster->isPlayer()) { pSkillCaster->OnAttack(pTarget, AttackTypeMagic); pTarget->OnDefend(pSkillCaster, AttackTypeMagic); } if (pTarget->m_bBlockMagic) return 0; int16 sMagicAmount = 0; if (pSkillCaster->isNPC()) { result = pSkillCaster->GetHitRate(pTarget->m_fTotalHitrate / pSkillCaster->m_fTotalEvasionrate); } else { CUser *pUser = TO_USER(pSkillCaster); uint8 bCha = pUser->isMage() ? pUser->GetStat(STAT_CHA) : 60; if (bCha > 86 && pUser->isMage()) sMagicAmount = bCha - 86; sMagicAmount += pUser->m_sMagicAttackAmount; total_hit = total_hit * bCha / 186; result = SUCCESS; } if (result != FAIL) { // In case of SUCCESS.... switch (attribute) { case FIRE_R: total_r = (pTarget->m_sFireR + pTarget->m_bAddFireR) * pTarget->m_bPctFireR / 100; break; case COLD_R: total_r = (pTarget->m_sColdR + pTarget->m_bAddColdR) * pTarget->m_bPctColdR / 100; break; case LIGHTNING_R: total_r = (pTarget->m_sLightningR + pTarget->m_bAddLightningR) * pTarget->m_bPctLightningR / 100; break; case MAGIC_R: total_r = (pTarget->m_sMagicR + pTarget->m_bAddMagicR) * pTarget->m_bPctMagicR / 100; break; case DISEASE_R: total_r = (pTarget->m_sDiseaseR + pTarget->m_bAddDiseaseR) * pTarget->m_bPctDiseaseR / 100; break; case POISON_R: total_r = (pTarget->m_sPoisonR + pTarget->m_bAddPoisonR) * pTarget->m_bPctPoisonR / 100; break; } total_r += pTarget->m_bResistanceBonus; Guard lock(pSkillCaster->_unitlock); if (pSkillCaster->isPlayer()) { CUser *pUser = TO_USER(pSkillCaster); // double the staff's damage when using a skill of the same attribute as the staff _ITEM_TABLE *pRightHand = pUser->GetItemPrototype(RIGHTHAND); if (!pUser->isWeaponsDisabled() && pRightHand != nullptr && pRightHand->isStaff() && pUser->GetItemPrototype(LEFTHAND) == nullptr) { righthand_damage = pRightHand->m_sDamage + pUser->m_bAddWeaponDamage; auto itr = pSkillCaster->m_equippedItemBonuses.find(RIGHTHAND); if (itr != pSkillCaster->m_equippedItemBonuses.end()) { auto bonusItr = itr->second.find(attribute); if (bonusItr != itr->second.end()) attribute_damage *= 2; } } else { righthand_damage = 0; } // Add on any elemental skill damage foreach (itr, pSkillCaster->m_equippedItemBonuses) { uint8 bSlot = itr->first; foreach (bonusItr, itr->second) { uint8 bType = bonusItr->first; int16 sAmount = bonusItr->second; int16 sTempResist = 0; switch (bType) { case ITEM_TYPE_FIRE: sTempResist = (pTarget->m_sFireR + pTarget->m_bAddFireR) * pTarget->m_bPctFireR / 100; break; case ITEM_TYPE_COLD: sTempResist = (pTarget->m_sColdR + pTarget->m_bAddColdR) * pTarget->m_bPctColdR / 100; break; case ITEM_TYPE_LIGHTNING: sTempResist = (pTarget->m_sLightningR + pTarget->m_bAddLightningR) * pTarget->m_bPctLightningR / 100; break; case ITEM_TYPE_POISON: sTempResist = (pTarget->m_sPoisonR + pTarget->m_bAddPoisonR) * pTarget->m_bPctPoisonR / 100; break; } sTempResist += pTarget->m_bResistanceBonus; if (bType >= ITEM_TYPE_FIRE && bType <= ITEM_TYPE_POISON) { if (sTempResist > 200) sTempResist = 200; // add attribute damage amount to right-hand damage instead of attribute // so it doesn't bother taking into account caster level (as it would with the staff attributes). righthand_damage += sAmount - sAmount * sTempResist / 200; } } } } damage = (230 * total_hit / (total_r + 250)); random = myrand(0, damage); damage = (short)(random * 0.3f + (damage * 0.85f)) - sMagicAmount; if (pSkillCaster->isNPC()) damage -= ((3 * righthand_damage) + (3 * attribute_damage)); else if (attribute != MAGIC_R) // Only if the staff has an attribute. damage -= (short)(((righthand_damage * 0.8f) + (righthand_damage * pSkillCaster->GetLevel()) / 60) + ((attribute_damage * 0.8f) + (attribute_damage * pSkillCaster->GetLevel()) / 30)); if (pTarget->m_bMagicDamageReduction < 100) damage = damage * pTarget->m_bMagicDamageReduction / 100; } // Apply boost for skills matching weather type. // This isn't actually used officially, but I think it's neat... GetWeatherDamage(damage, attribute); if (pTarget->isPlayer()) damage /= 3; // Implement damage cap. if (damage > MAX_DAMAGE) damage = MAX_DAMAGE; if(pTarget->GetID() > NPC_BAND) { switch(TO_NPC(pTarget)->GetType()) { case NPC_FOSSIL: damage = 0; break; case NPC_TREE: damage = 0; break; } } return damage; } int32 MagicInstance::GetWeatherDamage(int32 damage, int attribute) { // Give a 10% damage output boost based on weather (and skill's elemental attribute) if ((g_pMain->m_byWeather == WEATHER_FINE && attribute == AttributeFire) || (g_pMain->m_byWeather == WEATHER_RAIN && attribute == AttributeLightning) || (g_pMain->m_byWeather == WEATHER_SNOW && attribute == AttributeIce)) damage = (damage * 110) / 100; return damage; } void MagicInstance::Type6Cancel(bool bForceRemoval) { if (g_pMain->m_Magictype6Array.GetData(nSkillID) == nullptr) return; // NPCs cannot transform. if (!pSkillCaster->isPlayer() // Are we transformed? Note: if we're relogging, and we need to remove it, we should ignore this check. || (!bForceRemoval && !TO_USER(pSkillCaster)->isTransformed())) return; CUser * pUser = TO_USER(pSkillCaster); Packet result(WIZ_MAGIC_PROCESS, uint8(MAGIC_CANCEL_TRANSFORMATION)); // TODO: Reset stat changes, recalculate stats. pUser->m_transformationType = TransformationNone; Packet result2(AG_USER_TRANS_CHANGE); result2 << pUser->GetID() << uint8(pUser->m_transformationType); g_pMain->Send_AIServer(&result2); pUser->Send(&result); pUser->RemoveSavedMagic(pUser->m_bAbnormalType); pUser->StateChangeServerDirect(3, ABNORMAL_NORMAL); } void MagicInstance::Type9Cancel(bool bRemoveFromMap) { if (pSkillCaster == nullptr || !pSkillCaster->isPlayer()) return; _MAGIC_TYPE9 * pType = g_pMain->m_Magictype9Array.GetData(nSkillID); if (pType == nullptr) return; uint8 bResponse = 0; CUser * pCaster = TO_USER(pSkillCaster); Guard lock(pCaster->_unitlock); Type9BuffMap & buffMap = pCaster->m_type9BuffMap; // If this skill isn't already applied, there's no reason to remove it. if (buffMap.find(pType->bStateChange) == buffMap.end() && pType->bStateChange != 8) return; else if(pType->bStateChange == 8 && buffMap.find(pType->bStateChange) == buffMap.end()) buffMap.insert(std::make_pair(8, _BUFF_TYPE9_INFO(500117, UNIXTIME + 5))); // Remove the buff from the map if (bRemoveFromMap) buffMap.erase(pType->bStateChange); // Stealths if (pType->bStateChange <= 2 || (pType->bStateChange >= 5 && pType->bStateChange < 7)) { pCaster->StateChangeServerDirect(7, INVIS_NONE); bResponse = 91; } // Lupine etc. else if (pType->bStateChange >= 3 && pType->bStateChange <= 4) { pCaster->InitializeStealth(); bResponse = 92; } // Guardian pet related else if (pType->bStateChange == 7) { Packet pet(WIZ_PET, uint8(1)); pet << uint16(1) << uint16(6); pCaster->Send(&pet); bResponse = 93; } else if(pType->bStateChange == 8)//pat summon { if(!pCaster->isSummonPet) { SendSkillFailed(); return; } _ITEM_TABLE * pItemData = nullptr; if(pCaster == nullptr || (pItemData = pCaster->GetItemPrototype(SHOULDER)) == nullptr || !pItemData->isPet()) return; _ITEM_DATA *pItem = nullptr; if ((pItem = pCaster->GetItem(SHOULDER)) == nullptr || pItem->nNum != pItemData->Getnum()) return; CPet *newPet = g_pMain->GetPetPtr(pItem->nSerialNum); if(newPet == nullptr || newPet->m_pNpc == nullptr) return; if(newPet->m_pNpc != nullptr) { if(newPet->m_pNpc->isAlive()) newPet->Dead(); bResponse = 93; pCaster->isSummonPet = false; } } else if (pType->bStateChange == 9) { g_pMain->KillNpc(pCaster->GetSocketID()); } Packet result(WIZ_MAGIC_PROCESS, uint8(MAGIC_DURATION_EXPIRED)); result << bResponse; pCaster->Send(&result); } void MagicInstance::Type4Cancel() { if (pSkill == nullptr || pSkillTarget != pSkillCaster) return; _MAGIC_TYPE4* pType = g_pMain->m_Magictype4Array.GetData(nSkillID); if (pType == nullptr || pType->isDebuff()) return; if (nSkillID > 500000 && TO_USER(pSkillCaster)->isLockableScroll(pType->bBuffType) && pSkillCaster->hasDebuff(pType->bBuffType)) return; if (!CMagicProcess::RemoveType4Buff(pType->bBuffType, TO_USER(pSkillCaster))) return; TO_USER(pSkillCaster)->RemoveSavedMagic(nSkillID); } void MagicInstance::Type3Cancel() { if (pSkill == nullptr || pSkillCaster != pSkillTarget) return; // Should this take only the specified skill? I'm thinking so. _MAGIC_TYPE3* pType = g_pMain->m_Magictype3Array.GetData(nSkillID); if (pType == nullptr) return; for (int i = 0; i < MAX_TYPE3_REPEAT; i++) { Unit::MagicType3 * pEffect = &pSkillCaster->m_durationalSkills[i]; if(pEffect == nullptr) continue; if (!pEffect->m_byUsed // we can only cancel healing-over-time skills // damage-over-time skills must remain. || pEffect->m_sHPAmount <= 0) continue; pEffect->Reset(); break; // we can only have one healing-over-time skill active. // since we've found it, no need to loop through the rest of the array. } if (pSkillCaster->isPlayer()) { Packet result(WIZ_MAGIC_PROCESS, uint8(MAGIC_DURATION_EXPIRED)); result << uint8(100); // remove the healing-over-time skill. TO_USER(pSkillCaster)->Send(&result); } int buff_test = 0; for (int j = 0; j < MAX_TYPE3_REPEAT; j++) { if (pSkillCaster->m_durationalSkills[j].m_byUsed) { buff_test++; break; } } if (buff_test == 0) pSkillCaster->m_bType3Flag = false; if (pSkillCaster->isPlayer() && !pSkillCaster->m_bType3Flag) TO_USER(pSkillCaster)->SendUserStatusUpdate(USER_STATUS_DOT, USER_STATUS_CURE); } void MagicInstance::Type4Extend() { if (pSkill == nullptr // Only players can extend buffs. || !pSkillCaster->isPlayer() // Can't use on special items. || nSkillID >= 500000) return; _MAGIC_TYPE4 *pType = g_pMain->m_Magictype4Array.GetData(nSkillID); if (pType == nullptr || pType->isDebuff()) return; Guard lock(pSkillTarget->_unitlock); Type4BuffMap::iterator itr = pSkillTarget->m_buffMap.find(pType->bBuffType); // Can't extend a buff that hasn't been started. if (itr == pSkillCaster->m_buffMap.end() // Can't extend a buff that's already been extended. || itr->second.m_bDurationExtended) return; // Require the "Duration Item" for buff duration extension. // The things we must do to future-proof things... bool bItemFound = false; for (int i = SLOT_MAX; i < INVENTORY_TOTAL; i++) { _ITEM_DATA * pItem = nullptr; _ITEM_TABLE * pTable = TO_USER(pSkillCaster)->GetItemPrototype(i, pItem); if (pTable == nullptr || pTable->m_bKind != 255 || pTable->m_iEffect1 == 0) continue; _MAGIC_TABLE * pEffect = g_pMain->m_MagictableArray.GetData(pTable->m_iEffect1); if (pEffect == nullptr || pEffect->bMoral != MORAL_EXTEND_DURATION || !TO_USER(pSkillCaster)->RobItem(i, pTable)) continue; bItemFound = true; break; } // No "Duration Item" was found. if (!bItemFound) return; // Extend the duration of a buff. itr->second.m_tEndTime += pType->sDuration; // Mark the buff as extended (we cannot extend it more than once). itr->second.m_bDurationExtended = true; Packet result(WIZ_MAGIC_PROCESS, uint8(MAGIC_TYPE4_EXTEND)); result << uint32(nSkillID); TO_USER(pSkillTarget)->Send(&result); } void MagicInstance::ReflectDamage(int32 damage, Unit * pTarget) { if (pSkillCaster == nullptr || pTarget == nullptr) return; if (damage < 0) damage *= -1; int16 total_resistance_caster = 0; int32 reflect_damage = 0; switch (pTarget->m_bReflectArmorType) { case FIRE_DAMAGE: total_resistance_caster = (pSkillCaster->m_sFireR + pSkillCaster->m_bAddFireR) * pTarget->m_bPctFireR / 100; reflect_damage = ((230 * damage) / (total_resistance_caster + 250)) / 100 * 35; pSkillCaster->HpChange(-damage, pTarget); break; case ICE_DAMAGE: total_resistance_caster = (pSkillCaster->m_sColdR + pSkillCaster->m_bAddColdR) * pTarget->m_bPctColdR / 100; reflect_damage = ((230 * damage) / (total_resistance_caster + 250)) / 100 * 35; pSkillCaster->HpChange(-damage, pTarget); break; case LIGHTNING_DAMAGE: total_resistance_caster = (pSkillCaster->m_sLightningR + pSkillCaster->m_bAddLightningR) * pTarget->m_bPctLightningR / 100; reflect_damage = ((230 * damage) / (total_resistance_caster + 250)) / 100 * 35; pSkillCaster->HpChange(-damage, pTarget); break; } CMagicProcess::RemoveType4Buff(BUFF_TYPE_MAGE_ARMOR,pTarget,true); } void MagicInstance::ConsumeItem() { if (nConsumeItem != 0 && pSkillCaster->isPlayer()) if( nConsumeItem == 370001000 || nConsumeItem == 370002000 || nConsumeItem == 370003000 || nConsumeItem == 379069000 || nConsumeItem == 379070000 || nConsumeItem == 379063000 || nConsumeItem == 379064000 || nConsumeItem == 379065000 || nConsumeItem == 379066000 ) TO_USER(pSkillCaster)->RobItem(0); else TO_USER(pSkillCaster)->RobItem(nConsumeItem); if (bInstantCast) CMagicProcess::RemoveType4Buff(BUFF_TYPE_INSTANT_MAGIC, pSkillCaster); }