3611 lines
106 KiB
C++
3611 lines
106 KiB
C++
#include "stdafx.h"
|
||
#include "Map.h"
|
||
#include "../shared/KOSocketMgr.h"
|
||
#include "MagicProcess.h"
|
||
#include "MagicInstance.h"
|
||
#include <boost\foreach.hpp>
|
||
|
||
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<Unit *> casted_member;
|
||
|
||
if (!bIsRecastingSavedMagic
|
||
&& sTargetID >= 0
|
||
&& (pSkillTarget && pSkillTarget->HasSavedMagic(nSkillID)))
|
||
return false;
|
||
|
||
if (sTargetID == -1)
|
||
{
|
||
std::vector<uint16> 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<Unit *> casted_member;
|
||
|
||
// If the target's a group of people...
|
||
if (sTargetID == -1)
|
||
{
|
||
std::vector<uint16> 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<Unit *> 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<uint16> 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<CUser *> 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<Unit *> casted_member;
|
||
|
||
int damage = pType->sDamage;
|
||
|
||
if (sTargetID == -1)
|
||
{
|
||
std::vector<uint16> 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<CUser *> 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);
|
||
}
|