mirror of
https://gitlab.com/Scribble/gdlenhanced.git
synced 2026-08-17 14:23:04 -04:00
3688 lines
110 KiB
C++
3688 lines
110 KiB
C++
#include "StdAfx.h"
|
|
#include "SpellcastingManager.h"
|
|
#include "WeenieObject.h"
|
|
#include "World.h"
|
|
#include "SpellProjectile.h"
|
|
#include "WeenieFactory.h"
|
|
#include "InferredPortalData.h"
|
|
#include "WClassID.h"
|
|
#include "Container.h"
|
|
#include "Player.h"
|
|
#include "Client.h"
|
|
#include "Config.h"
|
|
#include "CombatFormulas.h"
|
|
#include "RandomRange.h"
|
|
#include "Random.h"
|
|
#include <random>
|
|
|
|
const float MAX_HEADING_TO_TARGET_FOR_CAST = 45.0f;
|
|
const float MAX_TURN_TIME_FOR_CAST = 9999.0f;
|
|
const float MAX_MOTION_TIME_FOR_CAST = 4.0f;
|
|
const float MAX_PROJECTILE_CAST_RANGE = 100.0;
|
|
const float CAST_UPDATE_RATE = 1.25f;
|
|
|
|
|
|
CSpellcastingManager::CSpellcastingManager(CWeenieObject *pWeenie)
|
|
{
|
|
m_pWeenie = pWeenie;
|
|
}
|
|
|
|
CSpellcastingManager::~CSpellcastingManager()
|
|
{
|
|
|
|
}
|
|
|
|
void CSpellcastingManager::EndCast(int error)
|
|
{
|
|
m_pWeenie->DoForcedStopCompletely();
|
|
m_pWeenie->NotifyUseDone(error);
|
|
m_bCasting = false;
|
|
m_SpellCastData = SpellCastData();
|
|
m_PendingMotions.clear();
|
|
m_bTurningToObject = false;
|
|
m_bTurned = false;
|
|
}
|
|
|
|
bool CSpellcastingManager::ResolveSpellBeingCasted()
|
|
{
|
|
if (!m_SpellCastData.spell_id)
|
|
return false;
|
|
|
|
CSpellTable *pSpellTable = MagicSystem::GetSpellTable();
|
|
if (!pSpellTable)
|
|
return false;
|
|
|
|
m_SpellCastData.spell = pSpellTable->GetSpellBase(m_SpellCastData.spell_id);
|
|
if (!m_SpellCastData.spell)
|
|
return false;
|
|
|
|
CSpellTableEx *pSpellTableEx = g_pPortalDataEx->GetSpellTableEx();
|
|
if (pSpellTableEx)
|
|
m_SpellCastData.spellEx = pSpellTableEx->GetSpellBase(m_SpellCastData.spell_id);
|
|
|
|
m_SpellCastData.spell_formula = m_SpellCastData.spell->InqSpellFormula();
|
|
m_SpellCastData.power_level_of_power_component = m_SpellCastData.spell_formula.GetPowerLevelOfPowerComponent();
|
|
m_SpellCastData.current_skill = DetermineSkillLevelForSpell();
|
|
m_SpellCastData.max_range = DetermineSpellRange();
|
|
|
|
if (m_SpellCastData.spell->_bitfield & SelfTargeted_SpellIndex)
|
|
{
|
|
m_SpellCastData.target_id = m_SpellCastData.source_id;
|
|
}
|
|
|
|
if (m_SpellCastData.equipped && m_SpellCastData.spell->InqTargetType() == ITEM_TYPE::TYPE_ITEM_ENCHANTABLE_TARGET)
|
|
{
|
|
m_SpellCastData.target_id = m_SpellCastData.caster_id;
|
|
}
|
|
|
|
// item enchantment
|
|
if (m_SpellCastData.equipped && (m_SpellCastData.spell->_school == 3) && (m_SpellCastData.spell->_bitfield & SelfTargeted_SpellIndex))
|
|
{
|
|
if (m_SpellCastData.spell->_category == 152 || m_SpellCastData.spell->_category == 154 || m_SpellCastData.spell->_category == 156 || m_SpellCastData.spell->_category == 158 || m_SpellCastData.spell->_category == 195 || m_SpellCastData.spell->_category == 695)
|
|
{
|
|
m_SpellCastData.target_id = m_SpellCastData.source_id;
|
|
}
|
|
else {
|
|
m_SpellCastData.target_id = m_SpellCastData.caster_id;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::string appendSpellText(std::string text, std::string newText, SpellComponentType componentType, SpellComponentType lastComponentType)
|
|
{
|
|
if (!newText.size())
|
|
return text;
|
|
|
|
switch (componentType)
|
|
{
|
|
default:
|
|
case SpellComponentType::Undef_SpellComponentType:
|
|
case SpellComponentType::Action_SpellComponentType:
|
|
case SpellComponentType::ConceptPrefix_SpellComponentType:
|
|
{
|
|
if (!text.empty())
|
|
text += " ";
|
|
text += newText;
|
|
break;
|
|
}
|
|
case SpellComponentType::ConceptSuffix_SpellComponentType:
|
|
{
|
|
switch (lastComponentType)
|
|
{
|
|
default:
|
|
case SpellComponentType::Undef_SpellComponentType:
|
|
case SpellComponentType::Action_SpellComponentType:
|
|
if (!text.empty())
|
|
text += " ";
|
|
text += newText;
|
|
break;
|
|
|
|
case SpellComponentType::ConceptPrefix_SpellComponentType:
|
|
case SpellComponentType::ConceptSuffix_SpellComponentType:
|
|
newText[0] = ::tolower(newText[0]);
|
|
text += newText;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
bool CSpellcastingManager::AddMotionsForSpell()
|
|
{
|
|
SpellComponentTable *pSpellComponents = MagicSystem::GetSpellComponentTable();
|
|
if (!pSpellComponents)
|
|
return false;
|
|
|
|
std::string spellWords;
|
|
SpellComponentType lastComponentType = Undef_SpellComponentType;
|
|
|
|
bool firstMotion = true;
|
|
|
|
for (DWORD i = 0; i < SPELLFORMULA_MAX_COMPS; i++)
|
|
{
|
|
DWORD comp = m_SpellCastData.spell_formula._comps[i];
|
|
if (!comp)
|
|
break;
|
|
|
|
const SpellComponentBase *pSpellComponent = pSpellComponents->InqSpellComponentBase(comp);
|
|
if (!pSpellComponent)
|
|
return false;
|
|
|
|
if (pSpellComponent->_category == Scarab_SpellComponentCategory && m_SpellCastData.spell->_bitfield & FastCast_SpellIndex)
|
|
continue;
|
|
|
|
DWORD gesture = pSpellComponent->_gesture;
|
|
if (gesture && (gesture & 0xFFFF) != 0)
|
|
{
|
|
if (gesture == 0x1000012F)
|
|
gesture = 0x13000132; // level 7's are wrong for some reason
|
|
|
|
// m_pWeenie->SendText(csprintf("Component \"%s\": %s %f %f 0x%08X (%u)", pSpellComponent->_name.c_str(), pSpellComponent->_text.c_str(), pSpellComponent->_time, pSpellComponent->_CDM, pSpellComponent->_gesture, pSpellComponent->_gesture & 0xFFFF), 1);
|
|
m_PendingMotions.push_back(SpellCastingMotion(gesture, 2.0f, firstMotion, firstMotion, pSpellComponent->_time));
|
|
firstMotion = false;
|
|
}
|
|
|
|
if (!pSpellComponent->_text.empty())
|
|
{
|
|
spellWords = appendSpellText(spellWords, pSpellComponent->_text, pSpellComponent->_type, lastComponentType);
|
|
lastComponentType = pSpellComponent->_type;
|
|
}
|
|
}
|
|
|
|
if (!spellWords.empty())
|
|
{
|
|
m_pWeenie->SpeakLocal(spellWords.c_str(), LTT_MAGIC_CASTING_CHANNEL);
|
|
}
|
|
|
|
m_SpellCastData.power_level_of_power_component = m_SpellCastData.spell_formula.GetPowerLevelOfPowerComponent();
|
|
return true;
|
|
}
|
|
|
|
void CSpellcastingManager::BeginCast()
|
|
{
|
|
m_bCasting = true;
|
|
m_PendingMotions.clear();
|
|
m_bTurningToObject = false;
|
|
m_bTurned = false;
|
|
|
|
AddMotionsForSpell();
|
|
|
|
BeginNextMotion();
|
|
}
|
|
|
|
CWeenieObject *CSpellcastingManager::GetCastTarget()
|
|
{
|
|
if (!m_bCasting || !m_SpellCastData.target_id)
|
|
return NULL;
|
|
|
|
return g_pWorld->FindObject(m_SpellCastData.target_id);
|
|
}
|
|
|
|
CWeenieObject *CSpellcastingManager::GetCastCaster()
|
|
{
|
|
// could be a contained item casting a spell on the player
|
|
return g_pWorld->FindObject(m_SpellCastData.caster_id);
|
|
}
|
|
|
|
CWeenieObject *CSpellcastingManager::GetCastSource()
|
|
{
|
|
return g_pWorld->FindObject(m_SpellCastData.source_id);
|
|
}
|
|
|
|
float CSpellcastingManager::HeadingToTarget()
|
|
{
|
|
if (!m_SpellCastData.target_id)
|
|
{
|
|
// Untargeted spell
|
|
return 0.0;
|
|
}
|
|
|
|
CWeenieObject *pTarget = GetCastTarget();
|
|
if (!pTarget)
|
|
{
|
|
// Don't know where the target is
|
|
EndCast(WERROR_OBJECT_GONE);
|
|
return 0.0;
|
|
}
|
|
|
|
if (pTarget->parent)
|
|
{
|
|
// Target has a parent (in inventory?)
|
|
// This needs additional logic
|
|
return 0.0;
|
|
}
|
|
|
|
// Return difference in heading.
|
|
float fHeadingToTarget = m_pWeenie->m_Position.heading_diff(pTarget->m_Position);
|
|
|
|
fHeadingToTarget = fabs(fHeadingToTarget);
|
|
|
|
if (fHeadingToTarget >= 180.0)
|
|
fHeadingToTarget = 360.0 - fHeadingToTarget;
|
|
|
|
return fHeadingToTarget;
|
|
}
|
|
|
|
bool CSpellcastingManager::MotionRequiresHeading()
|
|
{
|
|
if (!m_SpellCastData.target_id || m_SpellCastData.target_id == m_SpellCastData.caster_id || m_SpellCastData.target_id == m_SpellCastData.source_id)
|
|
return false;
|
|
|
|
if (CWeenieObject *target = g_pWorld->FindObject(m_SpellCastData.target_id))
|
|
{
|
|
if (CWeenieObject *owner = target->GetWorldTopLevelOwner())
|
|
{
|
|
if (owner->GetID() == m_SpellCastData.source_id)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (m_PendingMotions.empty())
|
|
return true; // last motion requires heading
|
|
|
|
return m_PendingMotions.begin()->requiresHeading;
|
|
}
|
|
|
|
void CSpellcastingManager::BeginNextMotion()
|
|
{
|
|
if (m_pWeenie->IsDead())
|
|
{
|
|
EndCast(0);
|
|
return;
|
|
}
|
|
|
|
bool bNeedsTurnToObject = false;
|
|
|
|
if (MotionRequiresHeading() && HeadingToTarget() > MAX_HEADING_TO_TARGET_FOR_CAST)
|
|
{
|
|
bNeedsTurnToObject = true;
|
|
}
|
|
|
|
if (!m_bCasting)
|
|
{
|
|
// Must have errored...
|
|
return;
|
|
}
|
|
|
|
if (bNeedsTurnToObject)
|
|
{
|
|
m_bTurningToObject = true;
|
|
|
|
MovementParameters params;
|
|
params.speed = 1.0f;
|
|
params.action_stamp = ++m_pWeenie->m_wAnimSequence;
|
|
params.modify_interpreted_state = 0;
|
|
m_pWeenie->last_move_was_autonomous = false;
|
|
|
|
m_pWeenie->cancel_moveto();
|
|
m_pWeenie->TurnToObject(m_SpellCastData.target_id, ¶ms);
|
|
|
|
m_SpellCastData.cast_timeout = Timer::cur_time + MAX_TURN_TIME_FOR_CAST;
|
|
}
|
|
else
|
|
{
|
|
m_bTurningToObject = false;
|
|
|
|
if (m_PendingMotions.empty())
|
|
{
|
|
int bitfield = m_pWeenie->m_SpellcastingManager->m_SpellCastData.spell->_bitfield;
|
|
if ((bitfield & Resistable_SpellIndex) && (bitfield & PKSensitive_SpellIndex) && (bitfield & FastCast_SpellIndex)) //streaks
|
|
{
|
|
if (m_pWeenie->m_Qualities.GetFloat(NEXT_SPELLCAST_TIMESTAMP_FLOAT, 0.0) <= Timer::cur_time)
|
|
{
|
|
int error = LaunchSpellEffect(false);
|
|
EndCast(error);
|
|
m_pWeenie->m_Qualities.SetFloat(NEXT_SPELLCAST_TIMESTAMP_FLOAT, Timer::cur_time + 2.0);
|
|
}
|
|
else
|
|
EndCast(WERROR_ACTIONS_LOCKED);
|
|
}
|
|
else //everything else
|
|
{
|
|
int error = LaunchSpellEffect(false);
|
|
EndCast(error);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_SpellCastData.cast_timeout = Timer::cur_time + MAX_MOTION_TIME_FOR_CAST;
|
|
|
|
m_pWeenie->StopCompletely(0);
|
|
|
|
SpellCastingMotion &cast_motion = *m_PendingMotions.begin();
|
|
|
|
MovementParameters params;
|
|
params.action_stamp = ++m_pWeenie->m_wAnimSequence;
|
|
params.speed = cast_motion.speed;
|
|
|
|
MovementStruct mvs;
|
|
mvs.type = MovementTypes::RawCommand;
|
|
mvs.motion = cast_motion.motion;
|
|
mvs.params = ¶ms;
|
|
|
|
m_pWeenie->last_move_was_autonomous = false;
|
|
|
|
int errorCode;
|
|
if (!(errorCode = m_pWeenie->PerformMovement(mvs)))
|
|
{
|
|
m_fNextCastTime = Timer::cur_time + (cast_motion.min_time * 0.5f);
|
|
m_pWeenie->_server_control_timestamp++;
|
|
m_pWeenie->Animation_Update();
|
|
}
|
|
else
|
|
{
|
|
// m_pWeenie->PerformMovement(mvs);
|
|
EndCast(WERROR_MAGIC_GENERAL_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Position CSpellcastingManager::GetSpellProjectileSpawnPosition(CSpellProjectile *pProjectile, CWeenieObject *pTarget, float *pDistToTarget, double dDir, bool bRing)
|
|
{
|
|
bool bArc = pProjectile->InqBoolQuality(GRAVITY_STATUS_BOOL, FALSE) ? true : false;
|
|
|
|
CWeenieObject *pSource = GetCastSource();
|
|
Position spawnPosition = pSource->m_Position.add_offset(Vector(0, 0, pSource->GetHeight() * (bArc ? 1.0 : (2.0 / 3.0))));
|
|
|
|
Vector targetOffset;
|
|
|
|
if (pTarget == pSource)
|
|
{
|
|
// rotate by dDir
|
|
double cs = cos(dDir);
|
|
double sn = sin(dDir);
|
|
|
|
double x = -1000 * sn;
|
|
double y = 1000 * cs;
|
|
|
|
float z = pSource->GetHeight() * (2.0 / 3.0);
|
|
if (bRing)
|
|
z *= 1.5;
|
|
|
|
targetOffset = spawnPosition.get_offset(pTarget->m_Position.add_offset(pTarget->m_Position.localtoglobalvec(Vector(x, y, z))));
|
|
}
|
|
else
|
|
{
|
|
targetOffset = spawnPosition.get_offset(pTarget->m_Position.add_offset(Vector(0, 0, pTarget->GetHeight() * (bArc ? (5.0 / 6.0) : (2.0 / 3.0)))));
|
|
//targetOffset = spawnPosition.get_offset(pTarget->m_Position.add_offset(Vector(0, 0, pTarget->GetHeight() * (bArc ? (5.0 / 6.0) : (0.5)))));
|
|
// rotate by dDir
|
|
double cs = cos(dDir);
|
|
double sn = sin(dDir);
|
|
|
|
targetOffset.x = targetOffset.x * cs - targetOffset.y * sn;
|
|
targetOffset.y = targetOffset.x * sn + targetOffset.y * cs;
|
|
}
|
|
|
|
Vector targetDir = targetOffset;
|
|
|
|
if (targetDir.normalize_check_small())
|
|
{
|
|
targetDir = spawnPosition.frame.get_vector_heading();
|
|
|
|
// spawnPosition.frame.m_origin += targetDir * minSpawnDist;
|
|
spawnPosition.frame.set_vector_heading(targetDir);
|
|
|
|
*pDistToTarget = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
double minSpawnDist = (pSource->GetRadius() + pProjectile->GetRadius()) + 0.1f;
|
|
|
|
if (bRing)
|
|
minSpawnDist += 0.5f; //this will influence ring projectile start position. 1.0 will cause small targets to be inside the spawn radius at point blank.
|
|
|
|
spawnPosition.frame.m_origin += targetDir * minSpawnDist;
|
|
spawnPosition.frame.set_vector_heading(targetDir);
|
|
|
|
*pDistToTarget = targetOffset.magnitude();
|
|
}
|
|
|
|
return spawnPosition;
|
|
}
|
|
|
|
Vector CSpellcastingManager::GetSpellProjectileSpawnVelocity(Position *pSpawnPosition, CWeenieObject *pTarget, float speed, bool tracked, bool gravity, Vector *pTargetDir, double dDir, bool bRing)
|
|
{
|
|
Vector targetOffset;
|
|
double targetDist;
|
|
|
|
CWeenieObject *pSource = GetCastSource();
|
|
if (pTarget == pSource)
|
|
{
|
|
// rotate by dDir
|
|
double cs = cos(dDir);
|
|
double sn = sin(dDir);
|
|
|
|
double x = -1000 * sn;
|
|
double y = 1000 * cs;
|
|
|
|
float z = pTarget->GetHeight() * (2.0 / 3.0);
|
|
if (bRing)
|
|
z *= 1.5;
|
|
|
|
targetOffset = pSpawnPosition->get_offset(pTarget->m_Position.add_offset(pTarget->m_Position.localtoglobalvec(Vector(x, y, z))));
|
|
tracked = false;
|
|
}
|
|
else
|
|
{
|
|
targetOffset = pSpawnPosition->get_offset(pTarget->m_Position.add_offset(Vector(0, 0, pTarget->GetHeight() * (2.0 / 3.0))));
|
|
|
|
// rotate by dDir
|
|
double cs = cos(dDir);
|
|
double sn = sin(dDir);
|
|
|
|
targetOffset.x = targetOffset.x * cs - targetOffset.y * sn;
|
|
targetOffset.y = targetOffset.x * sn + targetOffset.y * cs;
|
|
}
|
|
//targetOffset = pSpawnPosition->get_offset(pTarget->m_Position.add_offset(Vector(0, 0, pTarget->GetHeight() * 0.5f)));
|
|
|
|
targetDist = targetOffset.magnitude();
|
|
|
|
Vector v;
|
|
|
|
if (!tracked)
|
|
{
|
|
double t = targetDist / speed;
|
|
v = targetOffset / t;
|
|
|
|
if (gravity)
|
|
v.z += (9.8*t) / 2.0f;
|
|
|
|
Vector targetDir = v;
|
|
targetDir.normalize();
|
|
|
|
if (pTargetDir)
|
|
*pTargetDir = targetDir;
|
|
}
|
|
else
|
|
{
|
|
Vector P0 = targetOffset;
|
|
Vector P1(0, 0, 0);
|
|
|
|
float s0 = pTarget->get_velocity().magnitude();
|
|
Vector V0 = pTarget->get_velocity();
|
|
if (V0.normalize_check_small())
|
|
V0 = Vector(0, 0, 0);
|
|
|
|
float s1 = speed;
|
|
|
|
double a = (V0.x * V0.x) + (V0.y * V0.y) - (s1 * s1);
|
|
double b = 2 * ((P0.x * V0.x) + (P0.y * V0.y) - (P1.x * V0.x) - (P1.y * V0.y));
|
|
double c = (P0.x * P0.x) + (P0.y * P0.y) + (P1.x * P1.x) + (P1.y * P1.y) - (2 * P1.x * P0.x) - (2 * P1.y * P0.y);
|
|
|
|
double t1 = (-b + sqrt((b * b) - (4 * a * c))) / (2 * a);
|
|
double t2 = (-b - sqrt((b * b) - (4 * a * c))) / (2 * a);
|
|
|
|
if (t1 < 0)
|
|
t1 = FLT_MAX;
|
|
if (t2 < 0)
|
|
t2 = FLT_MAX;
|
|
|
|
double t = min(t1, t2);
|
|
if (t >= 100.0)
|
|
{
|
|
return GetSpellProjectileSpawnVelocity(pSpawnPosition, pTarget, speed, false, gravity, pTargetDir, dDir, bRing);
|
|
}
|
|
|
|
v.x = (P0.x + (t * s0 * V0.x)) / (t); // * s1);
|
|
v.y = (P0.y + (t * s0 * V0.y)) / (t); // * s1);
|
|
v.z = (P0.z + (t * s0 * V0.z)) / (t); // * s1);
|
|
|
|
if (gravity)
|
|
{
|
|
v.z += (9.8*t) / 2.0f;
|
|
}
|
|
|
|
if (pTargetDir)
|
|
{
|
|
Vector targetDir = v;
|
|
if (targetDir.normalize_check_small())
|
|
targetDir = Vector(0, 0, 0);
|
|
|
|
*pTargetDir = targetDir;
|
|
}
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
bool CSpellcastingManager::LaunchProjectileSpell(ProjectileSpellEx *meta)
|
|
{
|
|
if (!meta)
|
|
return false;
|
|
|
|
CWeenieObject *pTarget = GetCastTarget();
|
|
if (!pTarget || !pTarget->InValidCell())
|
|
{
|
|
// Don't know where the target is
|
|
return false;
|
|
}
|
|
|
|
int numX = (int)(meta->_dims.x + 0.5f);
|
|
int numY = (int)(meta->_dims.y + 0.5f);
|
|
int numZ = (int)(meta->_dims.z + 0.5f);
|
|
|
|
CWeenieObject *pSource = GetCastSource();
|
|
|
|
if (!pSource)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool isLifeProjectile = false;
|
|
double selfDrainedAmount = 0;
|
|
float selfDrainedDamageRatio = 0;
|
|
if (meta->AsLifeProjectileSpell())
|
|
{
|
|
isLifeProjectile = true;
|
|
ProjectileLifeSpellEx *lifeProjectile = meta->AsLifeProjectileSpell();
|
|
|
|
DAMAGE_TYPE damageType = static_cast<DAMAGE_TYPE>(lifeProjectile->_etype);
|
|
selfDrainedDamageRatio = lifeProjectile->_damage_ratio;
|
|
float drainPercentage = lifeProjectile->_drain_percentage;
|
|
|
|
switch (damageType)
|
|
{
|
|
case HEALTH_DAMAGE_TYPE:
|
|
{
|
|
int amount = round((float)pSource->GetHealth() * drainPercentage);
|
|
if (amount >= pSource->GetHealth())
|
|
{
|
|
selfDrainedAmount = pSource->GetHealth() - 1;
|
|
pSource->SetHealth(1);
|
|
}
|
|
else
|
|
selfDrainedAmount = abs(pSource->AdjustHealth(-amount));
|
|
break;
|
|
}
|
|
case STAMINA_DAMAGE_TYPE:
|
|
{
|
|
int amount = round((float)pSource->GetStamina() * drainPercentage);
|
|
selfDrainedAmount = abs(pSource->AdjustStamina(-amount));
|
|
break;
|
|
}
|
|
case MANA_DAMAGE_TYPE:
|
|
{
|
|
int amount = round((float)pSource->GetMana() * drainPercentage);
|
|
selfDrainedAmount = abs(pSource->AdjustMana(-amount));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bAngled = meta->_spreadAngle > 0 && numX > 1;
|
|
|
|
for (int x = 0; x < numX; x++)
|
|
{
|
|
for (int y = 0; y < numY; y++)
|
|
{
|
|
for (int z = 0; z < numZ; z++)
|
|
{
|
|
//DWORD damage = Random::GenUInt(meta->_baseIntensity, meta->_baseIntensity + meta->_variance);
|
|
|
|
int target = m_SpellCastData.target_id;
|
|
|
|
if (numX > 1 || numY > 1 || numZ > 1)
|
|
{
|
|
// volleys, blasts, rings aren't limited to one target
|
|
target = 0;
|
|
}
|
|
|
|
CSpellProjectile *pProjectile = new CSpellProjectile(m_SpellCastData, target);
|
|
|
|
// create the initial object
|
|
float distToTarget;
|
|
|
|
// spawn default object properties
|
|
g_pWeenieFactory->ApplyWeenieDefaults(pProjectile, meta->_wcid);
|
|
|
|
pProjectile->m_Qualities.SetInt(DAMAGE_TYPE_INT, meta->_etype);
|
|
|
|
// set spell id (is this even needed?)
|
|
pProjectile->SetSpellID(m_SpellCastData.spell_id);
|
|
pProjectile->SetInitialPosition(GetCastSource()->m_Position);
|
|
pProjectile->InitPhysicsObj();
|
|
|
|
pProjectile->m_PhysicsState |= MISSILE_PS | REPORT_COLLISIONS_PS;
|
|
pProjectile->m_PhysicsState &= ~IGNORE_COLLISIONS_PS;
|
|
|
|
double maxVelocity = 5.0;
|
|
pProjectile->m_Qualities.InqFloat(MAXIMUM_VELOCITY_FLOAT, maxVelocity);
|
|
|
|
bool bGravity = false;
|
|
if (pProjectile->InqBoolQuality(GRAVITY_STATUS_BOOL, FALSE))
|
|
{
|
|
pProjectile->m_PhysicsState |= GRAVITY_PS;
|
|
bGravity = true;
|
|
}
|
|
|
|
bool bTracking = !meta->_bNonTracking;
|
|
|
|
// angle at which to spawn from the caster
|
|
double theta = 0;
|
|
bool bRing = false;
|
|
if (bAngled)
|
|
{
|
|
double xRatio = x / (double) numX;
|
|
xRatio -= (numX - 1.0) / (double) (2*numX);
|
|
|
|
theta = DEG2RAD(xRatio * meta->_spreadAngle);
|
|
|
|
// if a ring we want it to start a little further out
|
|
if (meta->_spreadAngle > 180)
|
|
{
|
|
bRing = true;
|
|
}
|
|
}
|
|
|
|
Position projSpawnPos = GetSpellProjectileSpawnPosition(pProjectile, pTarget, &distToTarget, theta, bRing);
|
|
|
|
|
|
if (bRing)
|
|
{
|
|
// adjust for player casting position
|
|
Vector ringOffset = pSource->m_Position.localtoglobalvec(Vector(0, 0.1f, 0));
|
|
projSpawnPos = projSpawnPos.add_offset(ringOffset);
|
|
}
|
|
|
|
// overall offset
|
|
Vector createOffset = projSpawnPos.localtoglobalvec(meta->_createOffset);
|
|
projSpawnPos = projSpawnPos.add_offset(
|
|
Vector(
|
|
Random::GenFloat(-1.0, 1.0) * meta->_peturbation.x * meta->_padding.x,
|
|
Random::GenFloat(-1.0, 1.0) * meta->_peturbation.y * meta->_padding.y,
|
|
Random::GenFloat(-1.0, 1.0) * meta->_peturbation.z * meta->_padding.z));
|
|
projSpawnPos = projSpawnPos.add_offset(createOffset);
|
|
|
|
Vector spawnVelocity = GetSpellProjectileSpawnVelocity(&projSpawnPos, pTarget, maxVelocity, bTracking, bGravity, NULL, theta, bRing);
|
|
|
|
// individual offset
|
|
if (!bAngled)
|
|
{
|
|
Vector sizePerProjectile = meta->_padding; // * radius;
|
|
|
|
Vector projectileGroupOffset = Vector(x, y, z);
|
|
projectileGroupOffset.x *= sizePerProjectile.x;
|
|
projectileGroupOffset.y *= sizePerProjectile.y;
|
|
projectileGroupOffset.z *= sizePerProjectile.z;
|
|
|
|
projectileGroupOffset.x -= sizePerProjectile.x * ((meta->_dims.x - 1.0) / 2.0);
|
|
projectileGroupOffset.y -= sizePerProjectile.y * ((meta->_dims.y - 1.0) / 2.0);
|
|
projectileGroupOffset.z -= sizePerProjectile.z * ((meta->_dims.z - 1.0) / 2.0);
|
|
|
|
projectileGroupOffset = projSpawnPos.localtoglobalvec(projectileGroupOffset);
|
|
projSpawnPos = projSpawnPos.add_offset(projectileGroupOffset);
|
|
}
|
|
|
|
|
|
pProjectile->m_Position = projSpawnPos;
|
|
|
|
pProjectile->set_velocity(spawnVelocity, 0);
|
|
|
|
pProjectile->m_PhysicsState |= INELASTIC_PS | SCRIPTED_COLLISION_PS | LIGHTING_ON_PS;
|
|
|
|
// pProjectile->m_PhysicsState |= ETHEREAL_PS;
|
|
|
|
|
|
|
|
|
|
if (isLifeProjectile)
|
|
{
|
|
pProjectile->makeLifeProjectile(selfDrainedAmount, selfDrainedDamageRatio);
|
|
}
|
|
|
|
// insert the object into the world
|
|
if (g_pWorld->CreateEntity(pProjectile))
|
|
{
|
|
|
|
/*
|
|
LOG(Temp, Normal, "Projectile Offset @ %.3f %.3f %.3f %f\n",
|
|
createOffset.x + projectileGroupOffset.x,
|
|
createOffset.y + projectileGroupOffset.y,
|
|
createOffset.z + projectileGroupOffset.z, pProjectile->m_velocityVector.magnitude());
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// LOG(Temp, Normal, "-\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
void CSpellcastingManager::PerformCastParticleEffects()
|
|
{
|
|
if (!m_bCasting)
|
|
return;
|
|
|
|
// self effect
|
|
if (m_SpellCastData.spell->_caster_effect)
|
|
{
|
|
GetCastSource()->EmitEffect(m_SpellCastData.spell->_caster_effect, max(0.0, min(1.0, (m_SpellCastData.power_level_of_power_component - 1.0) / 7.0)));
|
|
}
|
|
|
|
// target effect
|
|
if (m_SpellCastData.spell->_target_effect)
|
|
{
|
|
if ((m_SpellCastData.spell->_category == 162 || m_SpellCastData.spell->_category == 164 || m_SpellCastData.spell->_category == 166 || m_SpellCastData.spell->_category == 168 ||
|
|
m_SpellCastData.spell->_category == 170 || m_SpellCastData.spell->_category == 172 || m_SpellCastData.spell->_category == 174 || m_SpellCastData.spell->_category == 160) &&
|
|
(m_SpellCastData.source_id == m_SpellCastData.target_id))
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (CWeenieObject *pTarget = GetCastTarget())
|
|
{
|
|
pTarget->EmitEffect(m_SpellCastData.spell->_target_effect, max(0.0, min(1.0, (m_SpellCastData.power_level_of_power_component - 1.0) / 7.0)));
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void CSpellcastingManager::PerformFellowCastParticleEffects(Fellowship *fellow)
|
|
{
|
|
if (!m_bCasting)
|
|
return;
|
|
|
|
// self effect
|
|
if (m_SpellCastData.spell->_caster_effect)
|
|
{
|
|
GetCastSource()->EmitEffect(m_SpellCastData.spell->_caster_effect, max(0.0, min(1.0, (m_SpellCastData.power_level_of_power_component - 1.0) / 7.0)));
|
|
}
|
|
|
|
// target effect
|
|
if (m_SpellCastData.spell->_target_effect)
|
|
{
|
|
for (auto &entry : fellow->_fellowship_table)
|
|
{
|
|
if (CWeenieObject *member = g_pWorld->FindPlayer(entry.first))
|
|
{
|
|
if (member)
|
|
member->EmitEffect(m_SpellCastData.spell->_target_effect, max(0.0, min(1.0, (m_SpellCastData.power_level_of_power_component - 1.0) / 7.0)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CSpellcastingManager::BeginPortalSend(const Position &targetPos)
|
|
{
|
|
if (CPlayerWeenie *player = m_pWeenie->AsPlayer())
|
|
{
|
|
if (!player->IsRecalling())
|
|
{
|
|
player->BeginRecall(targetPos);
|
|
}
|
|
}
|
|
else if (m_pWeenie->HasOwner())
|
|
{
|
|
if (CPlayerWeenie *gemtarget = m_pWeenie->GetWorldTopLevelOwner()->AsPlayer())
|
|
{
|
|
if (!gemtarget->IsRecalling())
|
|
{
|
|
gemtarget->BeginRecall(targetPos);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pWeenie->Movement_Teleport(targetPos, false);
|
|
}
|
|
}
|
|
|
|
int CSpellcastingManager::LaunchSpellEffect(bool bFizzled)
|
|
{
|
|
if (bFizzled)
|
|
return WERROR_NONE;
|
|
|
|
int targetError = CheckTargetValidity();
|
|
if (targetError && m_SpellCastData.range_check)
|
|
{
|
|
switch (targetError)
|
|
{
|
|
case WERROR_MISSILE_OUT_OF_RANGE:
|
|
{
|
|
CWeenieObject *pTarget = GetCastTarget();
|
|
if (pTarget)
|
|
pTarget->SendText(csprintf("%s tried to cast a spell on you, but was too far away!", m_pWeenie->GetName().c_str()), LTT_MAGIC);
|
|
|
|
m_pWeenie->SendText("That target is too far away!", LTT_MAGIC);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return targetError;
|
|
}
|
|
|
|
|
|
// cast source is assumed to be valid at this point
|
|
|
|
if (m_pWeenie->AsPlayer() && m_SpellCastData.uses_mana)
|
|
{
|
|
double chance = GetMagicSkillChance(m_SpellCastData.current_skill, m_SpellCastData.spell->_power);
|
|
if (chance < Random::RollDice(0.0, 1.0))
|
|
{
|
|
// fizzle
|
|
m_pWeenie->EmitEffect(PS_Fizzle, 0.542734265f);
|
|
m_pWeenie->AdjustMana(-5);
|
|
|
|
bFizzled = true;
|
|
}
|
|
else if (!bFizzled && m_pWeenie->m_Position.distance(m_SpellCastData.initial_cast_position) >= 6.0 && m_pWeenie->m_Qualities.GetInt(PLAYER_KILLER_STATUS_INT, 0) == PK_PKStatus)
|
|
{
|
|
// fizzle
|
|
m_pWeenie->EmitEffect(PS_Fizzle, 0.542734265f);
|
|
m_pWeenie->AdjustMana(-5);
|
|
m_pWeenie->SendText("Your movement disrupted spell casting!", LTT_MAGIC);
|
|
m_pWeenie->SendText("Your movement disrupted spell casting!", LTT_ERROR);
|
|
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
if (!m_UsedComponents.empty()) //we used components, so check if any need burning
|
|
{
|
|
//Each spell and each component type has a burn rate, where the lower level spells typically burn less.
|
|
//This rate is increased when fizzling. Your magic skill is not a factor except indirectly through fizzling.
|
|
|
|
SpellComponentTable *pSpellComponents = MagicSystem::GetSpellComponentTable();
|
|
float spellComponentLossMod = m_SpellCastData.spell->_component_loss;
|
|
if (bFizzled)
|
|
spellComponentLossMod *= 1.5; //made up value: 50% extra chance of burning components on fizzles.
|
|
|
|
if (pSpellComponents)
|
|
{
|
|
std::string componentsConsumedString = "";
|
|
for (std::map<DWORD, DWORD>::iterator iter = m_UsedComponents.begin(); iter != m_UsedComponents.end(); ++iter)
|
|
{
|
|
CWeenieObject *component = g_pWorld->FindObject(iter->first);
|
|
if (!component) // where did it go? force fizzle.
|
|
return WERROR_MAGIC_FIZZLE;
|
|
|
|
int compId = component->InqDIDQuality(SPELL_COMPONENT_DID, 0);
|
|
int spellPower = m_SpellCastData.spell->_power;
|
|
int currentSkill = m_SpellCastData.current_skill;
|
|
|
|
const SpellComponentBase *componentBase = pSpellComponents->InqSpellComponentBase(compId);
|
|
float burnChance = componentBase->_CDM * spellComponentLossMod;
|
|
burnChance *= min(1.0, (double)spellPower / (double)currentSkill);
|
|
if (Random::RollDice(0.0, 1.0) < burnChance)
|
|
{
|
|
for (int i = 0; i < getRandomNumber(1, iter->second, eRandomFormula::favorMid, 1.5, 0); ++i)
|
|
{
|
|
component->DecrementStackOrStructureNum();
|
|
if (componentsConsumedString.length() > 0)
|
|
componentsConsumedString.append(", ");
|
|
componentsConsumedString.append(componentBase->_name);
|
|
}
|
|
}
|
|
}
|
|
m_UsedComponents.clear();
|
|
|
|
if (componentsConsumedString.length() > 0)
|
|
m_pWeenie->SendText(csprintf("The spell consumed the following components: %s", componentsConsumedString.c_str()), LTT_MAGIC);
|
|
}
|
|
}
|
|
|
|
if (bFizzled)
|
|
return WERROR_MAGIC_FIZZLE;
|
|
|
|
m_pWeenie->AdjustMana(-GenerateManaCost());
|
|
}
|
|
else if ((m_pWeenie->AsMeleeWeapon() || m_pWeenie->AsMissileLauncher()) && m_SpellCastData.uses_mana)
|
|
{
|
|
// Drain mana from weapon on Cast on Strike
|
|
m_pWeenie->m_Qualities.SetInt(ITEM_CUR_MANA_INT, m_pWeenie->InqIntQuality(ITEM_CUR_MANA_INT, 0, 0) - m_SpellCastData.spell->_base_mana);
|
|
}
|
|
|
|
|
|
bool bSpellPerformed = false;
|
|
|
|
if (m_SpellCastData.spellEx && m_SpellCastData.spellEx->_meta_spell._spell)
|
|
{
|
|
switch (m_SpellCastData.spellEx->_meta_spell._sp_type)
|
|
{
|
|
case SpellType::Transfer_SpellType:
|
|
{
|
|
TransferSpellEx *meta = (TransferSpellEx *)m_SpellCastData.spellEx->_meta_spell._spell;
|
|
bSpellPerformed = DoTransferSpell(GetCastTarget(), meta);
|
|
break;
|
|
}
|
|
|
|
case SpellType::Boost_SpellType:
|
|
{
|
|
//bSpellPerformed = AdjustVital(GetCastTarget(), Random::GenInt(boostMin, boostMax), meta->_dt);
|
|
bSpellPerformed = AdjustVital(GetCastTarget());
|
|
break;
|
|
}
|
|
|
|
case SpellType::FellowBoost_SpellType:
|
|
{
|
|
FellowshipBoostSpellEx *meta = (FellowshipBoostSpellEx *)m_SpellCastData.spellEx->_meta_spell._spell;
|
|
|
|
CWeenieObject *target = GetCastTarget();
|
|
|
|
if (!target->HasFellowship())
|
|
break;
|
|
|
|
else
|
|
{
|
|
Fellowship *fellow = target->GetFellowship();
|
|
CWorldLandBlock *block = target->GetBlock();
|
|
for (auto &entry : fellow->_fellowship_table)
|
|
{
|
|
if (CWeenieObject *member = g_pWorld->FindPlayer(entry.first))
|
|
{
|
|
if (member->GetBlock() == block)
|
|
{
|
|
|
|
bSpellPerformed = AdjustVital(member);
|
|
}
|
|
}
|
|
}
|
|
PerformFellowCastParticleEffects(fellow);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SpellType::Dispel_SpellType:
|
|
{
|
|
DispelSpellEx *meta = (DispelSpellEx *)m_SpellCastData.spellEx->_meta_spell._spell;
|
|
|
|
int minNum = (int)((meta->_number * meta->_number_variance) + F_EPSILON);
|
|
int maxNum = (int)((meta->_number * (1.0 / meta->_number_variance)) + F_EPSILON);
|
|
|
|
int numToDispel = Random::GenInt(minNum, maxNum);
|
|
|
|
CWeenieObject *pTarget = GetCastTarget();
|
|
if (pTarget)
|
|
{
|
|
if (!(m_SpellCastData.spell->_bitfield & Beneficial_SpellIndex))
|
|
{
|
|
if (pTarget->GetWorldTopLevelOwner()->ImmuneToDamage(m_pWeenie))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
PackableListWithJson<DWORD> possibleToDispel;
|
|
|
|
if (pTarget->m_Qualities._enchantment_reg)
|
|
{
|
|
if (pTarget->m_Qualities._enchantment_reg->_add_list)
|
|
{
|
|
for (auto &entry : *pTarget->m_Qualities._enchantment_reg->_add_list)
|
|
{
|
|
if (entry._power_level > meta->_max_power)
|
|
continue;
|
|
if (entry._power_level < meta->_min_power)
|
|
continue;
|
|
if (entry._duration <= 0.0)
|
|
continue;
|
|
if ((entry._id & 0xFFFF) == 666) // vitae
|
|
continue;
|
|
|
|
if (CSpellTableEx *pSpellTableEx = g_pPortalDataEx->GetSpellTableEx())
|
|
{
|
|
if (const CSpellBaseEx *spellBaseEx = pSpellTableEx->GetSpellBase(entry._id & 0xFFFF))
|
|
{
|
|
if (meta->_school && meta->_school != spellBaseEx->_school)
|
|
continue;
|
|
|
|
switch (meta->_align)
|
|
{
|
|
case 0: // neutral
|
|
{
|
|
break;
|
|
}
|
|
case 1: // good only
|
|
{
|
|
if (!(spellBaseEx->_bitfield & Beneficial_SpellIndex))
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
case 2: // bad only
|
|
{
|
|
if (spellBaseEx->_bitfield & Beneficial_SpellIndex)
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
possibleToDispel.push_back(entry._id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pTarget->m_Qualities._enchantment_reg->_mult_list)
|
|
{
|
|
for (auto &entry : *pTarget->m_Qualities._enchantment_reg->_mult_list)
|
|
{
|
|
if (entry._power_level > meta->_max_power)
|
|
continue;
|
|
if (entry._power_level < meta->_min_power)
|
|
continue;
|
|
if (entry._duration <= 0.0)
|
|
continue;
|
|
if ((entry._id & 0xFFFF) == 666) // vitae
|
|
continue;
|
|
|
|
if (CSpellTableEx *pSpellTableEx = g_pPortalDataEx->GetSpellTableEx())
|
|
{
|
|
if (const CSpellBaseEx *spellBaseEx = pSpellTableEx->GetSpellBase(entry._id & 0xFFFF))
|
|
{
|
|
if (meta->_school && meta->_school != spellBaseEx->_school)
|
|
continue;
|
|
|
|
switch (meta->_align)
|
|
{
|
|
case 0: // neutral
|
|
{
|
|
break;
|
|
}
|
|
case 1: // good only
|
|
{
|
|
if (!(spellBaseEx->_bitfield & Beneficial_SpellIndex))
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
case 2: // bad only
|
|
{
|
|
if (spellBaseEx->_bitfield & Beneficial_SpellIndex)
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
possibleToDispel.push_back(entry._id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PackableListWithJson<DWORD> listToDispel;
|
|
|
|
if (meta->_number < 0)
|
|
{
|
|
// dispel all
|
|
listToDispel = possibleToDispel;
|
|
}
|
|
else
|
|
{
|
|
while (numToDispel > 0 && !possibleToDispel.empty())
|
|
{
|
|
std::list<DWORD>::iterator randomEntry = possibleToDispel.begin();
|
|
std::advance(randomEntry, Random::GenUInt(0, (DWORD)(possibleToDispel.size() - 1)));
|
|
|
|
listToDispel.push_back(*randomEntry);
|
|
possibleToDispel.erase(randomEntry);
|
|
|
|
numToDispel--;
|
|
}
|
|
}
|
|
|
|
std::string spellNames;
|
|
for (auto entry : listToDispel)
|
|
{
|
|
if (CSpellTableEx *pSpellTableEx = g_pPortalDataEx->GetSpellTableEx())
|
|
{
|
|
if (const CSpellBaseEx *spellBaseEx = pSpellTableEx->GetSpellBase(entry & 0xFFFF))
|
|
{
|
|
if (spellNames.empty())
|
|
{
|
|
spellNames = spellBaseEx->_name;
|
|
}
|
|
else
|
|
{
|
|
spellNames += ", ";
|
|
spellNames += spellBaseEx->_name;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// "You cast Incantation of Nullify All Magic Self on yourself and dispel: ..."
|
|
// "Tusker's Friend casts Nullify All Magic Other on you, but the dispel fails."
|
|
|
|
if (listToDispel.size() > 0)
|
|
{
|
|
if (pTarget == m_pWeenie)
|
|
{
|
|
m_pWeenie->SendText(csprintf("You cast %s on yourself and dispel: %s", m_SpellCastData.spell->_name.c_str(), spellNames.c_str()), LTT_MAGIC);
|
|
}
|
|
else
|
|
{
|
|
m_pWeenie->SendText(csprintf("You cast %s on %s and dispel: %s", m_SpellCastData.spell->_name.c_str(), pTarget->GetName().c_str(), spellNames.c_str()), LTT_MAGIC);
|
|
pTarget->SendText(csprintf("%s casts %s on you and dispels: %s", m_pWeenie->GetName().c_str(), m_SpellCastData.spell->_name.c_str(), spellNames.c_str()), LTT_MAGIC);
|
|
}
|
|
|
|
if (pTarget->m_Qualities._enchantment_reg)
|
|
{
|
|
pTarget->m_Qualities._enchantment_reg->RemoveEnchantments(&listToDispel);
|
|
|
|
BinaryWriter expireMessage;
|
|
expireMessage.Write<DWORD>(0x2C8);
|
|
listToDispel.Pack(&expireMessage);
|
|
pTarget->SendNetMessage(&expireMessage, PRIVATE_MSG, TRUE, FALSE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pTarget == m_pWeenie)
|
|
{
|
|
m_pWeenie->SendText(csprintf("You cast %s on yourself, but the dispel fails.", m_SpellCastData.spell->_name.c_str()), LTT_MAGIC);
|
|
}
|
|
else
|
|
{
|
|
m_pWeenie->SendText(csprintf("You cast %s on %s, but the dispel fails.", m_SpellCastData.spell->_name.c_str(), pTarget->GetName().c_str()), LTT_MAGIC);
|
|
pTarget->SendText(csprintf("%s casts %s on you, but the dispel fails.", m_pWeenie->GetName().c_str(), m_SpellCastData.spell->_name.c_str()), LTT_MAGIC);
|
|
}
|
|
}
|
|
|
|
bSpellPerformed = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SpellType::FellowDispel_SpellType:
|
|
{
|
|
FellowshipDispelSpellEx *meta = (FellowshipDispelSpellEx *)m_SpellCastData.spellEx->_meta_spell._spell;
|
|
|
|
int minNum = (int)((meta->_number * meta->_number_variance) + F_EPSILON);
|
|
int maxNum = (int)((meta->_number * (1.0 / meta->_number_variance)) + F_EPSILON);
|
|
|
|
int numToDispel = Random::GenInt(minNum, maxNum);
|
|
|
|
CWeenieObject *target = GetCastTarget();
|
|
|
|
if (!target->HasFellowship())
|
|
break;
|
|
|
|
else
|
|
{
|
|
Fellowship *fellow = target->GetFellowship();
|
|
CWorldLandBlock *block = target->GetBlock();
|
|
for (auto &entry : fellow->_fellowship_table)
|
|
{
|
|
if (CWeenieObject *member = g_pWorld->FindPlayer(entry.first))
|
|
{
|
|
if (member->GetBlock() == block)
|
|
{
|
|
|
|
if (member)
|
|
{
|
|
if (!(m_SpellCastData.spell->_bitfield & Beneficial_SpellIndex))
|
|
{
|
|
if (member->GetWorldTopLevelOwner()->ImmuneToDamage(m_pWeenie))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
PackableListWithJson<DWORD> possibleToDispel;
|
|
|
|
if (member->m_Qualities._enchantment_reg)
|
|
{
|
|
if (member->m_Qualities._enchantment_reg->_add_list)
|
|
{
|
|
for (auto &entry : *member->m_Qualities._enchantment_reg->_add_list)
|
|
{
|
|
if (entry._power_level > meta->_max_power)
|
|
continue;
|
|
if (entry._power_level < meta->_min_power)
|
|
continue;
|
|
if (entry._duration <= 0.0)
|
|
continue;
|
|
if ((entry._id & 0xFFFF) == 666) // vitae
|
|
continue;
|
|
|
|
if (CSpellTableEx *pSpellTableEx = g_pPortalDataEx->GetSpellTableEx())
|
|
{
|
|
if (const CSpellBaseEx *spellBaseEx = pSpellTableEx->GetSpellBase(entry._id & 0xFFFF))
|
|
{
|
|
if (meta->_school && meta->_school != spellBaseEx->_school)
|
|
continue;
|
|
|
|
switch (meta->_align)
|
|
{
|
|
case 0: // neutral
|
|
{
|
|
break;
|
|
}
|
|
case 1: // good only
|
|
{
|
|
if (!(spellBaseEx->_bitfield & Beneficial_SpellIndex))
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
case 2: // bad only
|
|
{
|
|
if (spellBaseEx->_bitfield & Beneficial_SpellIndex)
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
possibleToDispel.push_back(entry._id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (member->m_Qualities._enchantment_reg->_mult_list)
|
|
{
|
|
for (auto &entry : *member->m_Qualities._enchantment_reg->_mult_list)
|
|
{
|
|
if (entry._power_level > meta->_max_power)
|
|
continue;
|
|
if (entry._power_level < meta->_min_power)
|
|
continue;
|
|
if (entry._duration <= 0.0)
|
|
continue;
|
|
if ((entry._id & 0xFFFF) == 666) // vitae
|
|
continue;
|
|
|
|
if (CSpellTableEx *pSpellTableEx = g_pPortalDataEx->GetSpellTableEx())
|
|
{
|
|
if (const CSpellBaseEx *spellBaseEx = pSpellTableEx->GetSpellBase(entry._id & 0xFFFF))
|
|
{
|
|
if (meta->_school && meta->_school != spellBaseEx->_school)
|
|
continue;
|
|
|
|
switch (meta->_align)
|
|
{
|
|
case 0: // neutral
|
|
{
|
|
break;
|
|
}
|
|
case 1: // good only
|
|
{
|
|
if (!(spellBaseEx->_bitfield & Beneficial_SpellIndex))
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
case 2: // bad only
|
|
{
|
|
if (spellBaseEx->_bitfield & Beneficial_SpellIndex)
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
possibleToDispel.push_back(entry._id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PackableListWithJson<DWORD> listToDispel;
|
|
|
|
if (meta->_number < 0)
|
|
{
|
|
// dispel all
|
|
listToDispel = possibleToDispel;
|
|
}
|
|
else
|
|
{
|
|
while (numToDispel > 0 && !possibleToDispel.empty())
|
|
{
|
|
std::list<DWORD>::iterator randomEntry = possibleToDispel.begin();
|
|
std::advance(randomEntry, Random::GenUInt(0, (DWORD)(possibleToDispel.size() - 1)));
|
|
|
|
listToDispel.push_back(*randomEntry);
|
|
possibleToDispel.erase(randomEntry);
|
|
|
|
numToDispel--;
|
|
}
|
|
}
|
|
|
|
std::string spellNames;
|
|
for (auto entry : listToDispel)
|
|
{
|
|
if (CSpellTableEx *pSpellTableEx = g_pPortalDataEx->GetSpellTableEx())
|
|
{
|
|
if (const CSpellBaseEx *spellBaseEx = pSpellTableEx->GetSpellBase(entry & 0xFFFF))
|
|
{
|
|
if (spellNames.empty())
|
|
{
|
|
spellNames = spellBaseEx->_name;
|
|
}
|
|
else
|
|
{
|
|
spellNames += ", ";
|
|
spellNames += spellBaseEx->_name;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// "You cast Incantation of Nullify All Magic Self on yourself and dispel: ..."
|
|
// "Tusker's Friend casts Nullify All Magic Other on you, but the dispel fails."
|
|
|
|
if (listToDispel.size() > 0)
|
|
{
|
|
if (member == m_pWeenie)
|
|
{
|
|
m_pWeenie->SendText(csprintf("You cast %s on yourself and dispel: %s", m_SpellCastData.spell->_name.c_str(), spellNames.c_str()), LTT_MAGIC);
|
|
}
|
|
else
|
|
{
|
|
m_pWeenie->SendText(csprintf("You cast %s on %s and dispel: %s", m_SpellCastData.spell->_name.c_str(), member->GetName().c_str(), spellNames.c_str()), LTT_MAGIC);
|
|
member->SendText(csprintf("%s casts %s on you and dispels: %s", m_pWeenie->GetName().c_str(), m_SpellCastData.spell->_name.c_str(), spellNames.c_str()), LTT_MAGIC);
|
|
}
|
|
|
|
if (member->m_Qualities._enchantment_reg)
|
|
{
|
|
member->m_Qualities._enchantment_reg->RemoveEnchantments(&listToDispel);
|
|
|
|
BinaryWriter expireMessage;
|
|
expireMessage.Write<DWORD>(0x2C8);
|
|
listToDispel.Pack(&expireMessage);
|
|
member->SendNetMessage(&expireMessage, PRIVATE_MSG, TRUE, FALSE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (member == m_pWeenie)
|
|
{
|
|
m_pWeenie->SendText(csprintf("You cast %s on yourself, but the dispel fails.", m_SpellCastData.spell->_name.c_str()), LTT_MAGIC);
|
|
}
|
|
else
|
|
{
|
|
m_pWeenie->SendText(csprintf("You cast %s on %s, but the dispel fails.", m_SpellCastData.spell->_name.c_str(), member->GetName().c_str()), LTT_MAGIC);
|
|
member->SendText(csprintf("%s casts %s on you, but the dispel fails.", m_pWeenie->GetName().c_str(), m_SpellCastData.spell->_name.c_str()), LTT_MAGIC);
|
|
}
|
|
}
|
|
|
|
bSpellPerformed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
PerformFellowCastParticleEffects(fellow);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SpellType::PortalLink_SpellType:
|
|
{
|
|
//if (m_pWeenie->HasOwner())
|
|
//break;
|
|
|
|
PortalLinkSpellEx *meta = (PortalLinkSpellEx *)m_SpellCastData.spellEx->_meta_spell._spell;
|
|
|
|
CWeenieObject *pTarget = GetCastTarget();
|
|
if (pTarget)
|
|
{
|
|
// portal bitmask, 0x10 = cannot be summoned
|
|
// portal bitmask, 0x20 = cannot be linked/recalled
|
|
|
|
int minLevel = pTarget->InqIntQuality(MIN_LEVEL_INT, 0);
|
|
int maxLevel = pTarget->InqIntQuality(MAX_LEVEL_INT, 0);
|
|
DWORD origPortID = pTarget->InqDIDQuality(ORIGINAL_PORTAL_DID, 0);
|
|
|
|
int currentLevel = m_pWeenie->InqIntQuality(LEVEL_INT, 1);
|
|
|
|
if (minLevel && currentLevel < minLevel)
|
|
{
|
|
m_pWeenie->SendText("You are not powerful enough to tie to this portal yet.", LTT_MAGIC);
|
|
break;
|
|
}
|
|
else if (maxLevel && currentLevel > maxLevel)
|
|
{
|
|
m_pWeenie->SendText("You are too powerful to tie to this portal.", LTT_MAGIC);
|
|
break;
|
|
}
|
|
else if (origPortID == W_PORTALGATEWAY_CLASS) // Don't tie to portals with no original portal set.
|
|
{
|
|
m_pWeenie->SendText("This portal has no destination set.", LTT_MAGIC);
|
|
break;
|
|
}
|
|
|
|
switch (meta->_index)
|
|
{
|
|
case 1:
|
|
if (pTarget->m_Qualities.m_WeenieType == LifeStone_WeenieType)
|
|
{
|
|
m_pWeenie->m_Qualities.SetPosition(LINKED_LIFESTONE_POSITION, m_pWeenie->m_Position);
|
|
bSpellPerformed = true;
|
|
m_pWeenie->SendText("You have successfully linked with the life stone.", LTT_MAGIC);
|
|
}
|
|
else
|
|
{
|
|
m_pWeenie->SendText("You cannot link that.", LTT_MAGIC);
|
|
}
|
|
break;
|
|
|
|
case 2: // primary portal tie
|
|
if (pTarget->m_Qualities.m_WeenieType == Portal_WeenieType &&
|
|
!(pTarget->m_Qualities.GetInt(PORTAL_BITMASK_INT, 0) & 0x20))
|
|
{
|
|
if (origPortID > 0)
|
|
{
|
|
// This is a summoned portal with an original portal ID
|
|
m_pWeenie->m_Qualities.SetDataID(LINKED_PORTAL_ONE_DID, origPortID);
|
|
m_pWeenie->m_Qualities.SetPosition(LINKED_PORTAL_ONE_POSITION, pTarget->InqPositionQuality(DESTINATION_POSITION, Position()));
|
|
|
|
}
|
|
else
|
|
{
|
|
// This is not a summoned portal
|
|
m_pWeenie->m_Qualities.SetDataID(LINKED_PORTAL_ONE_DID, pTarget->m_Qualities.id);
|
|
m_pWeenie->m_Qualities.SetPosition(LINKED_PORTAL_ONE_POSITION, pTarget->InqPositionQuality(DESTINATION_POSITION, Position()));
|
|
}
|
|
bSpellPerformed = true;
|
|
m_pWeenie->SendText("You have successfully linked with the portal.", LTT_MAGIC);
|
|
}
|
|
else
|
|
{
|
|
m_pWeenie->SendText("You cannot link that portal.", LTT_MAGIC);
|
|
}
|
|
|
|
break;
|
|
|
|
case 3: // secondary portal tie
|
|
if (pTarget->m_Qualities.m_WeenieType == Portal_WeenieType &&
|
|
!(pTarget->m_Qualities.GetInt(PORTAL_BITMASK_INT, 0) & 0x20))
|
|
{
|
|
if (origPortID > 0)
|
|
{
|
|
// This is a summoned portal with an original portal ID
|
|
m_pWeenie->m_Qualities.SetDataID(LINKED_PORTAL_TWO_DID, origPortID);
|
|
m_pWeenie->m_Qualities.SetPosition(LINKED_PORTAL_TWO_POSITION, pTarget->InqPositionQuality(DESTINATION_POSITION, Position()));
|
|
|
|
}
|
|
else
|
|
{
|
|
// This is not a summoned portal
|
|
m_pWeenie->m_Qualities.SetDataID(LINKED_PORTAL_TWO_DID, pTarget->m_Qualities.id);
|
|
m_pWeenie->m_Qualities.SetPosition(LINKED_PORTAL_TWO_POSITION, pTarget->InqPositionQuality(DESTINATION_POSITION, Position()));
|
|
}
|
|
bSpellPerformed = true;
|
|
m_pWeenie->SendText("You have successfully linked with the portal.", LTT_MAGIC);
|
|
}
|
|
else
|
|
{
|
|
m_pWeenie->SendText("You cannot link that portal.", LTT_MAGIC);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (m_pWeenie->IsAdmin())
|
|
{
|
|
// lifestone tie = 1
|
|
// primary portal tie = 2
|
|
// secondary portal tie = 3
|
|
// m_pWeenie->SendText(csprintf("Index: %d", meta->_index), LTT_DEFAULT);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SpellType::PortalRecall_SpellType:
|
|
{
|
|
/*if (m_pWeenie->HasOwner()) //Needed to comment out for Rare gems.
|
|
break;*/
|
|
|
|
if (m_pWeenie->AsPlayer() && m_pWeenie->AsPlayer()->CheckPKActivity())
|
|
{
|
|
m_pWeenie->SendText("You have been involved in Player Killer combat too recently!", LTT_MAGIC);
|
|
break;
|
|
}
|
|
|
|
PortalRecallSpellEx *meta = (PortalRecallSpellEx *)m_SpellCastData.spellEx->_meta_spell._spell;
|
|
|
|
switch (meta->_index)
|
|
{
|
|
case 1: // lifestone sending
|
|
{
|
|
Position lifestone;
|
|
if (m_pWeenie->m_Qualities.InqPosition(SANCTUARY_POSITION, lifestone) && lifestone.objcell_id != 0)
|
|
{
|
|
BeginPortalSend(lifestone);
|
|
}
|
|
else
|
|
{
|
|
m_pWeenie->SendText("Your soul is not bound to a lifestone.", LTT_MAGIC);
|
|
}
|
|
|
|
bSpellPerformed = true;
|
|
break;
|
|
}
|
|
case 2: // lifestone recall
|
|
{
|
|
Position lifestone;
|
|
if (m_pWeenie->m_Qualities.InqPosition(LINKED_LIFESTONE_POSITION, lifestone) && lifestone.objcell_id != 0)
|
|
{
|
|
BeginPortalSend(lifestone);
|
|
}
|
|
else
|
|
{
|
|
m_pWeenie->SendText("You have no lifestone tied.", LTT_MAGIC);
|
|
}
|
|
|
|
bSpellPerformed = true;
|
|
break;
|
|
}
|
|
|
|
case 3: // portal recall
|
|
{
|
|
DWORD portalDID = 0;
|
|
|
|
if (m_pWeenie->m_Qualities.InqDataID(LAST_PORTAL_DID, portalDID) && portalDID != 0)
|
|
{
|
|
CWeenieDefaults *portalDefaults = NULL;
|
|
|
|
if (portalDID && !(1955 == portalDID))
|
|
{
|
|
portalDefaults = g_pWeenieFactory->GetWeenieDefaults(portalDID);
|
|
}
|
|
else if (portalDID && (1955 == portalDID))
|
|
{
|
|
portalDefaults = g_pWeenieFactory->GetWeenieDefaults(portalDID);
|
|
portalDefaults->m_Qualities.SetPosition(DESTINATION_POSITION, m_pWeenie->InqPositionQuality(LAST_PORTAL_POSITION, Position()));
|
|
}
|
|
|
|
if (portalDefaults)
|
|
{
|
|
int minLevel = 0;
|
|
int maxLevel = 0;
|
|
portalDefaults->m_Qualities.InqInt(MIN_LEVEL_INT, minLevel);
|
|
portalDefaults->m_Qualities.InqInt(MAX_LEVEL_INT, maxLevel);
|
|
|
|
int currentLevel = m_pWeenie->InqIntQuality(LEVEL_INT, 1);
|
|
|
|
if (minLevel && currentLevel < minLevel)
|
|
{
|
|
m_pWeenie->SendText("You are not powerful enough to recall this portal yet.", LTT_MAGIC);
|
|
break;
|
|
}
|
|
else if (maxLevel && currentLevel > maxLevel)
|
|
{
|
|
m_pWeenie->SendText("You are too powerful to recall this portal.", LTT_MAGIC);
|
|
break;
|
|
}
|
|
|
|
std::string restriction;
|
|
if (portalDefaults->m_Qualities.InqString(QUEST_RESTRICTION_STRING, restriction))
|
|
{
|
|
if (CPlayerWeenie *player = m_pWeenie->AsPlayer())
|
|
{
|
|
if (!player->InqQuest(restriction.c_str()))
|
|
{
|
|
m_pWeenie->SendText("You try to recall the portal but there is no effect.", LTT_MAGIC);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Position portalDest;
|
|
if (portalDefaults->m_Qualities.InqPosition(DESTINATION_POSITION, portalDest) && portalDest.objcell_id != 0)
|
|
{
|
|
BeginPortalSend(portalDest);
|
|
}
|
|
else
|
|
{
|
|
m_pWeenie->SendText("The portal you last used has no destination set.", LTT_MAGIC);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pWeenie->SendText("You have not used a recallable portal yet.", LTT_MAGIC);
|
|
}
|
|
|
|
bSpellPerformed = true;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case 4: // primary portal recall
|
|
{
|
|
DWORD portalDID = 0;
|
|
|
|
if (m_pWeenie->m_Qualities.InqDataID(LINKED_PORTAL_ONE_DID, portalDID) && portalDID != 0)
|
|
{
|
|
CWeenieDefaults *portalDefaults = NULL;
|
|
|
|
if (portalDID && !(1955 == portalDID))
|
|
{
|
|
portalDefaults = g_pWeenieFactory->GetWeenieDefaults(portalDID);
|
|
}
|
|
else if (portalDID && (1955 == portalDID))
|
|
{
|
|
portalDefaults = g_pWeenieFactory->GetWeenieDefaults(portalDID);
|
|
portalDefaults->m_Qualities.SetPosition(DESTINATION_POSITION, m_pWeenie->InqPositionQuality(LINKED_PORTAL_ONE_POSITION, Position()));
|
|
}
|
|
|
|
if (portalDefaults)
|
|
{
|
|
int minLevel = 0;
|
|
int maxLevel = 0;
|
|
portalDefaults->m_Qualities.InqInt(MIN_LEVEL_INT, minLevel);
|
|
portalDefaults->m_Qualities.InqInt(MAX_LEVEL_INT, maxLevel);
|
|
|
|
int currentLevel = m_pWeenie->InqIntQuality(LEVEL_INT, 1);
|
|
|
|
if (minLevel && currentLevel < minLevel)
|
|
{
|
|
m_pWeenie->SendText("You are not powerful enough to recall this portal yet.", LTT_MAGIC);
|
|
break;
|
|
}
|
|
else if (maxLevel && currentLevel > maxLevel)
|
|
{
|
|
m_pWeenie->SendText("You are too powerful to recall this portal.", LTT_MAGIC);
|
|
break;
|
|
}
|
|
|
|
std::string restriction;
|
|
if (portalDefaults->m_Qualities.InqString(QUEST_RESTRICTION_STRING, restriction))
|
|
{
|
|
if (CPlayerWeenie *player = m_pWeenie->AsPlayer())
|
|
{
|
|
if (!player->InqQuest(restriction.c_str()))
|
|
{
|
|
m_pWeenie->SendText("You try to recall the portal but there is no effect.", LTT_MAGIC);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Position portalDest;
|
|
if (portalDefaults->m_Qualities.InqPosition(DESTINATION_POSITION, portalDest) && portalDest.objcell_id != 0)
|
|
{
|
|
BeginPortalSend(portalDest);
|
|
}
|
|
else
|
|
{
|
|
m_pWeenie->SendText("The primary portal you have tied has no destination set.", LTT_MAGIC);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pWeenie->SendText("You have no primary portal tied.", LTT_MAGIC);
|
|
}
|
|
|
|
bSpellPerformed = true;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case 5: // secondary portal recall
|
|
{
|
|
DWORD portalDID = 0;
|
|
|
|
|
|
if (m_pWeenie->m_Qualities.InqDataID(LINKED_PORTAL_TWO_DID, portalDID) && portalDID != 0)
|
|
{
|
|
CWeenieDefaults *portalDefaults = NULL;
|
|
|
|
if (portalDID && !(1955 == portalDID))
|
|
{
|
|
portalDefaults = g_pWeenieFactory->GetWeenieDefaults(portalDID);
|
|
}
|
|
else if (portalDID && (1955 == portalDID))
|
|
{
|
|
portalDefaults = g_pWeenieFactory->GetWeenieDefaults(portalDID);
|
|
portalDefaults->m_Qualities.SetPosition(DESTINATION_POSITION, m_pWeenie->InqPositionQuality(LINKED_PORTAL_TWO_POSITION, Position()));
|
|
}
|
|
|
|
if (portalDefaults)
|
|
{
|
|
int minLevel = 0;
|
|
int maxLevel = 0;
|
|
portalDefaults->m_Qualities.InqInt(MIN_LEVEL_INT, minLevel);
|
|
portalDefaults->m_Qualities.InqInt(MAX_LEVEL_INT, maxLevel);
|
|
|
|
int currentLevel = m_pWeenie->InqIntQuality(LEVEL_INT, 1);
|
|
|
|
if (minLevel && currentLevel < minLevel)
|
|
{
|
|
m_pWeenie->SendText("You are not powerful enough to recall this portal yet.", LTT_MAGIC);
|
|
break;
|
|
}
|
|
else if (maxLevel && currentLevel > maxLevel)
|
|
{
|
|
m_pWeenie->SendText("You are too powerful to recall this portal.", LTT_MAGIC);
|
|
break;
|
|
}
|
|
|
|
std::string restriction;
|
|
if (portalDefaults->m_Qualities.InqString(QUEST_RESTRICTION_STRING, restriction))
|
|
{
|
|
if (CPlayerWeenie *player = m_pWeenie->AsPlayer())
|
|
{
|
|
if (!player->InqQuest(restriction.c_str()))
|
|
{
|
|
m_pWeenie->SendText("You try to recall the portal but there is no effect.", LTT_MAGIC);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Position portalDest;
|
|
if (portalDefaults->m_Qualities.InqPosition(DESTINATION_POSITION, portalDest) && portalDest.objcell_id != 0)
|
|
{
|
|
BeginPortalSend(portalDest);
|
|
}
|
|
else
|
|
{
|
|
m_pWeenie->SendText("The secondary portal you have tied has no destination set.", LTT_MAGIC);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pWeenie->SendText("You have no secondary portal tied.", LTT_MAGIC);
|
|
}
|
|
|
|
bSpellPerformed = true;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (m_pWeenie->IsAdmin())
|
|
{
|
|
// lifestone recall = 2
|
|
// portal recall = 3
|
|
// primary portal recall = 4
|
|
// secondary portal recall = 5
|
|
// m_pWeenie->SendText(csprintf("Index: %d", meta->_index), LTT_DEFAULT);
|
|
}
|
|
|
|
bSpellPerformed = true;
|
|
break;
|
|
}
|
|
|
|
case SpellType::Enchantment_SpellType:
|
|
{
|
|
EnchantmentSpellEx *meta = (EnchantmentSpellEx *)m_SpellCastData.spellEx->_meta_spell._spell;
|
|
|
|
Enchantment enchant;
|
|
int buffDuration = m_pWeenie->InqIntQuality(AUGMENTATION_INCREASED_SPELL_DURATION_INT, 0);
|
|
enchant._id = meta->_spell_id | ((DWORD)m_SpellCastData.serial << (DWORD)16);
|
|
enchant.m_SpellSetID = 0; // ???
|
|
enchant._spell_category = m_SpellCastData.spell->_category; // meta->_spellCategory;
|
|
enchant._power_level = m_SpellCastData.spell->_power;
|
|
enchant._start_time = Timer::cur_time;
|
|
enchant._duration = m_SpellCastData.equipped ? -1.0 : meta->_duration * (buffDuration*0.2 + 1);
|
|
enchant._caster = m_pWeenie->GetID();
|
|
enchant._degrade_modifier = meta->_degrade_modifier;
|
|
enchant._degrade_limit = meta->_degrade_limit;
|
|
enchant._last_time_degraded = -1.0;
|
|
enchant._smod = meta->_smod;
|
|
|
|
if (enchant._smod.type & Skill_EnchantmentType)
|
|
{
|
|
enchant._smod.key = (DWORD)SkillTable::OldToNewSkill((STypeSkill)enchant._smod.key);
|
|
}
|
|
|
|
|
|
std::list<CWeenieObject *> targets;
|
|
|
|
if (CWeenieObject *castTarget = GetCastTarget())
|
|
{
|
|
// NPKs should not be able to buff PKs
|
|
if (m_pWeenie && castTarget && m_pWeenie->AsPlayer() && castTarget->AsPlayer())
|
|
{
|
|
if (!m_pWeenie->IsPK() && castTarget->IsPK())
|
|
{
|
|
bSpellPerformed = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (m_SpellCastData.spell->InqTargetType() != ITEM_TYPE::TYPE_ITEM_ENCHANTABLE_TARGET)
|
|
{
|
|
targets.push_back(castTarget);
|
|
}
|
|
else
|
|
{
|
|
CContainerWeenie *container = castTarget->AsContainer();
|
|
if (container && !container->HasOwner())
|
|
{
|
|
for (auto wielded : container->m_Wielded)
|
|
{
|
|
if (wielded->GetItemType() & m_SpellCastData.spell->_non_component_target_type)
|
|
{
|
|
if (castTarget == m_pWeenie || wielded->parent || m_pWeenie->GetWorldTopLevelOwner() == castTarget->GetWorldTopLevelOwner()) // for other targets, only physically wielded allowed, TopLevelOwner for buff gems.
|
|
{
|
|
targets.push_back(wielded);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (castTarget->GetItemType() & m_SpellCastData.spell->_non_component_target_type)
|
|
{
|
|
if (castTarget == m_pWeenie || castTarget->parent || !castTarget->HasOwner() || castTarget->GetWorldTopLevelOwner() == m_pWeenie) // for other targets, only physically wielded allowed
|
|
{
|
|
targets.push_back(castTarget);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto target : targets)
|
|
{
|
|
// You cast Harlune's Blessing on yourself, refreshing Harlune's Blessing
|
|
// You cast Impenetrability III on Pathwarden Robe, surpassing Impenetrability II
|
|
|
|
CWeenieObject *topLevelOwner = target->GetWorldTopLevelOwner();
|
|
|
|
if (target->InqIntQuality(MAX_STACK_SIZE_INT, 1) > 1) //do not allow enchanting stackable items(ammunition)
|
|
{
|
|
m_pWeenie->SendText(csprintf("The %s can't be enchanted.", target->GetName().c_str()), LTT_MAGIC);
|
|
continue;
|
|
}
|
|
|
|
int text_option = 0; // 0 = normal cast text, 1 = refreshes text, 2 = surpassed by text, 3 = surpasses text
|
|
std::string existing_spell_name = "";
|
|
if (CEnchantmentRegistry* enchant_reg = target->m_Qualities._enchantment_reg)
|
|
{
|
|
if (Enchantment* highest_enchant = enchant_reg->GetHighestEnchantOfCategory(enchant._spell_category, enchant._smod.type)) // check if spell of this category already exists
|
|
{
|
|
if ((highest_enchant->_id & 0xFFFF) == enchant._id)
|
|
text_option = 1;
|
|
|
|
else
|
|
{
|
|
// if the current highest enchant is more powerful than the one being cast, use surpassed by text (2), otherwise use surpasses text (3)
|
|
text_option = highest_enchant->_power_level > enchant._power_level ? 2 : 3;
|
|
|
|
CSpellTable* st = MagicSystem::GetSpellTable();
|
|
existing_spell_name = st->GetSpellBase((highest_enchant->_id & 0xFFFF))->_name;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_pWeenie != target)
|
|
{
|
|
if (!(m_SpellCastData.spell->_bitfield & Beneficial_SpellIndex))
|
|
{
|
|
if (m_pWeenie && target && m_pWeenie->AsPlayer() && target->AsPlayer())
|
|
{
|
|
// Only Update PK Activity if both target and caster are PK, Prevents PKs from tagging NPKs for PK activity.
|
|
if (m_pWeenie->IsPK() && target->IsPK())
|
|
{
|
|
m_pWeenie->AsPlayer()->UpdatePKActivity();
|
|
target->AsPlayer()->UpdatePKActivity();
|
|
}
|
|
}
|
|
|
|
if (target->AsPlayer() && target->GetWorldTopLevelOwner()->ImmuneToDamage(m_pWeenie))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (m_SpellCastData.spell->_bitfield & Resistable_SpellIndex)
|
|
{
|
|
if (topLevelOwner->TryMagicResist(m_SpellCastData.current_skill))
|
|
{
|
|
topLevelOwner->EmitSound(Sound_ResistSpell, 1.0f, false);
|
|
topLevelOwner->SendText(csprintf("You resist the spell cast by %s", m_pWeenie->GetName().c_str()), LTT_MAGIC);
|
|
m_pWeenie->SendText(csprintf("%s resists your spell", target->GetName().c_str()), LTT_MAGIC);
|
|
topLevelOwner->OnResistSpell(m_pWeenie);
|
|
|
|
// Cast on Strike
|
|
if (m_pWeenie != m_pWeenie->GetWorldTopLevelOwner())
|
|
{
|
|
m_pWeenie->GetWorldTopLevelOwner()->SendText(csprintf("%s resists your spell", target->GetName().c_str()), LTT_MAGIC);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (int resistMagic = target->InqIntQuality(RESIST_MAGIC_INT, 0, FALSE))
|
|
{
|
|
if (resistMagic >= 9999 && topLevelOwner == m_pWeenie)
|
|
continue;
|
|
|
|
if (resistMagic >= 9999 || ::TryMagicResist(m_SpellCastData.current_skill, (DWORD)resistMagic))
|
|
{
|
|
target->EmitSound(Sound_ResistSpell, 1.0f, false);
|
|
|
|
if (m_pWeenie != topLevelOwner)
|
|
{
|
|
topLevelOwner->SendText(csprintf("%s resists the spell cast by %s", m_pWeenie->GetName().c_str(), m_pWeenie->GetName().c_str()), LTT_MAGIC);
|
|
}
|
|
|
|
m_pWeenie->SendText(csprintf("%s resists your spell", target->GetName().c_str()), LTT_MAGIC);
|
|
target->OnResistSpell(m_pWeenie);
|
|
continue;
|
|
}
|
|
}
|
|
if (int resistLifeMagic = target->InqIntQuality(LIFE_RESIST_RATING_INT, 0, FALSE))
|
|
{
|
|
if (resistLifeMagic >= 9999 || ::TryMagicResist(m_SpellCastData.current_skill, (DWORD)resistLifeMagic))
|
|
{
|
|
target->EmitSound(Sound_ResistSpell, 1.0f, false);
|
|
|
|
if (m_pWeenie != topLevelOwner)
|
|
{
|
|
topLevelOwner->SendText(csprintf("%s resists the spell cast by %s", m_pWeenie->GetName().c_str(), m_pWeenie->GetName().c_str()), LTT_MAGIC);
|
|
}
|
|
|
|
m_pWeenie->SendText(csprintf("%s resists your spell", target->GetName().c_str()), LTT_MAGIC);
|
|
target->OnResistSpell(m_pWeenie);
|
|
continue;
|
|
}
|
|
}
|
|
if (bool resistProjectileMagic = target->InqBoolQuality(NON_PROJECTILE_MAGIC_IMMUNE_BOOL, 0))
|
|
{
|
|
if (resistProjectileMagic == 1 || ::TryMagicResist(m_SpellCastData.current_skill, (DWORD)resistProjectileMagic))
|
|
{
|
|
target->EmitSound(Sound_ResistSpell, 1.0f, false);
|
|
|
|
if (m_pWeenie != topLevelOwner)
|
|
{
|
|
topLevelOwner->SendText(csprintf("%s resists the spell cast by %s", m_pWeenie->GetName().c_str(), m_pWeenie->GetName().c_str()), LTT_MAGIC);
|
|
}
|
|
|
|
m_pWeenie->SendText(csprintf("%s resists your spell", target->GetName().c_str()), LTT_MAGIC);
|
|
target->OnResistSpell(m_pWeenie);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
topLevelOwner->HandleAggro(m_pWeenie);
|
|
|
|
target->m_Qualities.UpdateEnchantment(&enchant);
|
|
target->NotifyEnchantmentUpdated(&enchant);
|
|
|
|
target->CheckVitalRanges();
|
|
const char* spell_name = m_SpellCastData.spell->_name.c_str();
|
|
if (m_pWeenie == target)
|
|
{
|
|
switch (text_option)
|
|
{
|
|
default:
|
|
case 0:
|
|
m_pWeenie->SendText(csprintf("You cast %s on yourself", spell_name), LTT_MAGIC);
|
|
break;
|
|
case 1:
|
|
m_pWeenie->SendText(csprintf("You cast %s on yourself, refreshing %s", spell_name, spell_name), LTT_MAGIC);
|
|
break;
|
|
case 2:
|
|
m_pWeenie->SendText(csprintf("You cast %s on yourself, but it is surpassed by %s", spell_name, existing_spell_name.c_str()), LTT_MAGIC);
|
|
break;
|
|
case 3:
|
|
m_pWeenie->SendText(csprintf("You cast %s on yourself, surpassing %s", spell_name, existing_spell_name.c_str()), LTT_MAGIC);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (text_option)
|
|
{
|
|
default:
|
|
case 0:
|
|
m_pWeenie->SendText(csprintf("You cast %s on %s", spell_name, target->GetName().c_str()), LTT_MAGIC);
|
|
if (m_pWeenie != topLevelOwner)
|
|
topLevelOwner->SendText(csprintf("%s cast %s on %s", m_pWeenie->GetName().c_str(), spell_name, target == topLevelOwner ? "you" : target->GetName().c_str()), LTT_MAGIC);
|
|
break;
|
|
case 1:
|
|
m_pWeenie->SendText(csprintf("You cast %s on %s, refreshing %s", spell_name, target->GetName().c_str(), spell_name), LTT_MAGIC);
|
|
if (m_pWeenie != topLevelOwner)
|
|
topLevelOwner->SendText(csprintf("%s cast %s on %s, refreshing %s", m_pWeenie->GetName().c_str(), spell_name, target == topLevelOwner ? "you" : target->GetName().c_str(), spell_name), LTT_MAGIC);
|
|
break;
|
|
case 2:
|
|
m_pWeenie->SendText(csprintf("You cast %s on %s, but it is surpassed by %s", spell_name, target->GetName().c_str(), existing_spell_name.c_str()), LTT_MAGIC);
|
|
if (m_pWeenie != topLevelOwner)
|
|
topLevelOwner->SendText(csprintf("%s cast %s on %s, but it is surpassed by %s", m_pWeenie->GetName().c_str(), spell_name, target == topLevelOwner ? "you" : target->GetName().c_str(), existing_spell_name.c_str()), LTT_MAGIC);
|
|
break;
|
|
case 3:
|
|
m_pWeenie->SendText(csprintf("You cast %s on %s, surpassing %s", spell_name, target->GetName().c_str(), existing_spell_name.c_str()), LTT_MAGIC);
|
|
if (m_pWeenie != topLevelOwner)
|
|
topLevelOwner->SendText(csprintf("%s cast %s on %s, surpassing %s", m_pWeenie->GetName().c_str(), spell_name, target == topLevelOwner ? "you" : target->GetName().c_str(), existing_spell_name.c_str()), LTT_MAGIC);
|
|
|
|
}
|
|
}
|
|
|
|
bSpellPerformed = true;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SpellType::FellowEnchantment_SpellType:
|
|
{
|
|
FellowshipEnchantmentSpellEx *meta = (FellowshipEnchantmentSpellEx *)m_SpellCastData.spellEx->_meta_spell._spell;
|
|
|
|
Enchantment enchant;
|
|
enchant._id = meta->_spell_id | ((DWORD)m_SpellCastData.serial << (DWORD)16);
|
|
enchant.m_SpellSetID = 0; // ???
|
|
enchant._spell_category = m_SpellCastData.spell->_category; // meta->_spellCategory;
|
|
enchant._power_level = m_SpellCastData.spell->_power;
|
|
enchant._start_time = Timer::cur_time;
|
|
enchant._duration = m_SpellCastData.equipped ? -1.0 : meta->_duration;
|
|
enchant._caster = m_pWeenie->GetID();
|
|
enchant._degrade_modifier = meta->_degrade_modifier;
|
|
enchant._degrade_limit = meta->_degrade_limit;
|
|
enchant._last_time_degraded = -1.0;
|
|
enchant._smod = meta->_smod;
|
|
|
|
CWeenieObject *target = GetCastTarget();
|
|
|
|
if (!target->HasFellowship())
|
|
break;
|
|
|
|
// NPKs should not be able to buff PKs
|
|
if (m_pWeenie && target && m_pWeenie->IsPK() && target->IsPK())
|
|
{
|
|
bSpellPerformed = false;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
Fellowship *fellow = target->GetFellowship();
|
|
CWorldLandBlock *block = target->GetBlock();
|
|
for (auto &entry : fellow->_fellowship_table)
|
|
{
|
|
if (CWeenieObject *member = g_pWorld->FindPlayer(entry.first))
|
|
{
|
|
if (member->GetBlock() == block)
|
|
{
|
|
|
|
if (enchant._smod.type & Skill_EnchantmentType)
|
|
{
|
|
enchant._smod.key = (DWORD)SkillTable::OldToNewSkill((STypeSkill)enchant._smod.key);
|
|
}
|
|
|
|
|
|
std::list<CWeenieObject *> targets;
|
|
|
|
if (member)
|
|
{
|
|
if (m_SpellCastData.spell->InqTargetType() != ITEM_TYPE::TYPE_ITEM_ENCHANTABLE_TARGET)
|
|
{
|
|
targets.push_back(member);
|
|
}
|
|
else
|
|
{
|
|
CContainerWeenie *container = member->AsContainer();
|
|
if (container && !container->HasOwner())
|
|
{
|
|
for (auto wielded : container->m_Wielded)
|
|
{
|
|
if (wielded->GetItemType() & m_SpellCastData.spell->_non_component_target_type)
|
|
{
|
|
if (member == m_pWeenie || wielded->parent) // for other targets, only physically wielded allowed
|
|
{
|
|
targets.push_back(wielded);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (member->GetItemType() & m_SpellCastData.spell->_non_component_target_type)
|
|
{
|
|
if (member == m_pWeenie || member->parent || !member->HasOwner() || member->GetWorldTopLevelOwner() == m_pWeenie) // for other targets, only physically wielded allowed
|
|
{
|
|
targets.push_back(member);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto target : targets)
|
|
{
|
|
// You cast Harlune's Blessing on yourself, refreshing Harlune's Blessing
|
|
// You cast Impenetrability III on Pathwarden Robe, surpassing Impenetrability II
|
|
|
|
CWeenieObject *topLevelOwner = target->GetWorldTopLevelOwner();
|
|
|
|
if (target->InqIntQuality(MAX_STACK_SIZE_INT, 1) > 1) //do not allow enchanting stackable items(ammunition)
|
|
{
|
|
m_pWeenie->SendText(csprintf("The %s can't be enchanted.", target->GetName().c_str()), LTT_MAGIC);
|
|
continue;
|
|
}
|
|
|
|
bool bAlreadyExisted = false;
|
|
if (target->m_Qualities._enchantment_reg && target->m_Qualities._enchantment_reg->IsEnchanted(enchant._id))
|
|
bAlreadyExisted = true;
|
|
|
|
if (m_pWeenie != target)
|
|
{
|
|
if (!(m_SpellCastData.spell->_bitfield & Beneficial_SpellIndex))
|
|
{
|
|
// Only Update PK Activity if both target and caster are PK, Prevents PKs from tagging NPKs for PK activity.
|
|
if (m_pWeenie->IsPK() && target->IsPK())
|
|
{
|
|
m_pWeenie->AsPlayer()->UpdatePKActivity();
|
|
target->AsPlayer()->UpdatePKActivity();
|
|
}
|
|
|
|
if (target->AsPlayer() && target->GetWorldTopLevelOwner()->ImmuneToDamage(m_pWeenie))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (m_SpellCastData.spell->_bitfield & Resistable_SpellIndex)
|
|
{
|
|
if (topLevelOwner->TryMagicResist(m_SpellCastData.current_skill))
|
|
{
|
|
topLevelOwner->EmitSound(Sound_ResistSpell, 1.0f, false);
|
|
topLevelOwner->SendText(csprintf("You resist the spell cast by %s", m_pWeenie->GetName().c_str()), LTT_MAGIC);
|
|
m_pWeenie->SendText(csprintf("%s resists your spell", target->GetName().c_str()), LTT_MAGIC);
|
|
topLevelOwner->OnResistSpell(m_pWeenie);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (int resistMagic = target->InqIntQuality(RESIST_MAGIC_INT, 0, FALSE))
|
|
{
|
|
if (resistMagic >= 9999 && topLevelOwner == m_pWeenie)
|
|
continue;
|
|
|
|
if (resistMagic >= 9999 || ::TryMagicResist(m_SpellCastData.current_skill, (DWORD)resistMagic))
|
|
{
|
|
target->EmitSound(Sound_ResistSpell, 1.0f, false);
|
|
|
|
if (m_pWeenie != topLevelOwner)
|
|
{
|
|
topLevelOwner->SendText(csprintf("%s resists the spell cast by %s", m_pWeenie->GetName().c_str(), m_pWeenie->GetName().c_str()), LTT_MAGIC);
|
|
}
|
|
|
|
m_pWeenie->SendText(csprintf("%s resists your spell", target->GetName().c_str()), LTT_MAGIC);
|
|
target->OnResistSpell(m_pWeenie);
|
|
continue;
|
|
}
|
|
}
|
|
if (int resistLifeMagic = target->InqIntQuality(LIFE_RESIST_RATING_INT, 0, FALSE))
|
|
{
|
|
if (resistLifeMagic >= 9999 || ::TryMagicResist(m_SpellCastData.current_skill, (DWORD)resistLifeMagic))
|
|
{
|
|
target->EmitSound(Sound_ResistSpell, 1.0f, false);
|
|
|
|
if (m_pWeenie != topLevelOwner)
|
|
{
|
|
topLevelOwner->SendText(csprintf("%s resists the spell cast by %s", m_pWeenie->GetName().c_str(), m_pWeenie->GetName().c_str()), LTT_MAGIC);
|
|
}
|
|
|
|
m_pWeenie->SendText(csprintf("%s resists your spell", target->GetName().c_str()), LTT_MAGIC);
|
|
target->OnResistSpell(m_pWeenie);
|
|
continue;
|
|
}
|
|
}
|
|
if (bool resistProjectileMagic = target->InqBoolQuality(NON_PROJECTILE_MAGIC_IMMUNE_BOOL, 0))
|
|
{
|
|
if (resistProjectileMagic == 1 || ::TryMagicResist(m_SpellCastData.current_skill, (DWORD)resistProjectileMagic))
|
|
{
|
|
target->EmitSound(Sound_ResistSpell, 1.0f, false);
|
|
|
|
if (m_pWeenie != topLevelOwner)
|
|
{
|
|
topLevelOwner->SendText(csprintf("%s resists the spell cast by %s", m_pWeenie->GetName().c_str(), m_pWeenie->GetName().c_str()), LTT_MAGIC);
|
|
}
|
|
|
|
m_pWeenie->SendText(csprintf("%s resists your spell", target->GetName().c_str()), LTT_MAGIC);
|
|
target->OnResistSpell(m_pWeenie);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
topLevelOwner->HandleAggro(m_pWeenie);
|
|
|
|
target->m_Qualities.UpdateEnchantment(&enchant);
|
|
target->NotifyEnchantmentUpdated(&enchant);
|
|
|
|
target->CheckVitalRanges();
|
|
|
|
if (m_pWeenie == target)
|
|
{
|
|
m_pWeenie->SendText(csprintf("You cast %s on yourself", m_SpellCastData.spell->_name.c_str()), LTT_MAGIC);
|
|
}
|
|
else
|
|
{
|
|
m_pWeenie->SendText(csprintf("You cast %s on %s", m_SpellCastData.spell->_name.c_str(), target->GetName().c_str()), LTT_MAGIC);
|
|
|
|
if (m_pWeenie != topLevelOwner)
|
|
{
|
|
if (target == topLevelOwner)
|
|
target->SendText(csprintf("%s cast %s on you", m_pWeenie->GetName().c_str(), m_SpellCastData.spell->_name.c_str()), LTT_MAGIC);
|
|
else
|
|
topLevelOwner->SendText(csprintf("%s cast %s on %s", m_pWeenie->GetName().c_str(), m_SpellCastData.spell->_name.c_str(), target->GetName().c_str()), LTT_MAGIC);
|
|
}
|
|
}
|
|
|
|
bSpellPerformed = true;
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
PerformFellowCastParticleEffects(fellow);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SpellType::PortalSummon_SpellType:
|
|
{
|
|
if (m_pWeenie && m_pWeenie->AsPlayer() && m_pWeenie->AsPlayer()->CheckPKActivity())
|
|
{
|
|
m_pWeenie->SendText("You have been involved in Player Killer combat too recently!", LTT_MAGIC);
|
|
break;
|
|
}
|
|
|
|
PortalSummonSpellEx *meta = (PortalSummonSpellEx *)m_SpellCastData.spellEx->_meta_spell._spell;
|
|
if (!meta && (meta->_link == 1 || meta->_link == 2))
|
|
break;
|
|
|
|
Position spawnPos;
|
|
if (!m_pWeenie->m_Qualities.InqPosition(PORTAL_SUMMON_LOC_POSITION, spawnPos))
|
|
{
|
|
spawnPos = GetCastSource()->m_Position;
|
|
spawnPos.frame.m_origin += spawnPos.localtoglobalvec(Vector(0, 7, 0));
|
|
}
|
|
|
|
DWORD portalDID = 0;
|
|
|
|
Position dummyPos;
|
|
bool bNoLink = false;
|
|
Position portalPos;
|
|
|
|
switch (meta->_link)
|
|
{
|
|
case 1:
|
|
portalDID = m_pWeenie->InqDIDQuality(LINKED_PORTAL_ONE_DID, 0);
|
|
portalPos = m_pWeenie->InqPositionQuality(LINKED_PORTAL_ONE_POSITION, Position());
|
|
|
|
if (!portalDID && !m_pWeenie->m_Qualities.InqPosition(LINKED_PORTAL_ONE_POSITION, dummyPos))
|
|
{
|
|
m_pWeenie->SendText("You do not have a primary portal tied.", LTT_MAGIC);
|
|
bNoLink = true;
|
|
}
|
|
|
|
break;
|
|
|
|
case 2:
|
|
portalDID = m_pWeenie->InqDIDQuality(LINKED_PORTAL_TWO_DID, 0);
|
|
portalPos = m_pWeenie->InqPositionQuality(LINKED_PORTAL_TWO_POSITION, Position());
|
|
|
|
if (!portalDID && !m_pWeenie->m_Qualities.InqPosition(LINKED_PORTAL_TWO_POSITION, dummyPos))
|
|
{
|
|
m_pWeenie->SendText("You do not have a secondary portal tied.", LTT_MAGIC);
|
|
bNoLink = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (bNoLink)
|
|
{
|
|
break;
|
|
}
|
|
|
|
CWeenieDefaults *portalDefaults = NULL;
|
|
|
|
bool canFlagForQuest = false;
|
|
if (portalDID)
|
|
{
|
|
portalDefaults = g_pWeenieFactory->GetWeenieDefaults(portalDID);
|
|
|
|
if (portalDefaults)
|
|
{
|
|
int minLevel = 0;
|
|
int maxLevel = 0;
|
|
portalDefaults->m_Qualities.InqInt(MIN_LEVEL_INT, minLevel);
|
|
portalDefaults->m_Qualities.InqInt(MAX_LEVEL_INT, maxLevel);
|
|
|
|
if (m_pWeenie->AsPlayer())
|
|
{
|
|
int currentLevel = m_pWeenie->InqIntQuality(LEVEL_INT, 1);
|
|
if (minLevel && currentLevel < minLevel)
|
|
{
|
|
m_pWeenie->SendText("You are not powerful enough to summon this portal yet.", LTT_MAGIC);
|
|
break;
|
|
}
|
|
else if (maxLevel && currentLevel > maxLevel)
|
|
{
|
|
m_pWeenie->SendText("You are too powerful to summon this portal.", LTT_MAGIC);
|
|
break;
|
|
}
|
|
|
|
if ((portalDefaults->m_Qualities.GetInt(PORTAL_BITMASK_INT, 0) & 0x10))
|
|
{
|
|
m_pWeenie->SendText("That portal may not be summoned.", LTT_MAGIC);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
canFlagForQuest = true;
|
|
}
|
|
}
|
|
|
|
CWeenieObject *weenie = g_pWeenieFactory->CreateWeenieByClassID(W_PORTALGATEWAY_CLASS, &spawnPos, false);
|
|
|
|
if (weenie)
|
|
{
|
|
weenie->_timeToRot = Timer::cur_time + meta->_portal_lifetime;
|
|
weenie->_beganRot = false;
|
|
weenie->m_Qualities.SetFloat(TIME_TO_ROT_FLOAT, weenie->_timeToRot);
|
|
|
|
bool bHasDestination = false;
|
|
|
|
if (portalDefaults)
|
|
{
|
|
weenie->CopyPositionStat(DESTINATION_POSITION, &portalDefaults->m_Qualities);
|
|
weenie->CopyIntStat(MIN_LEVEL_INT, &portalDefaults->m_Qualities);
|
|
weenie->CopyIntStat(MAX_LEVEL_INT, &portalDefaults->m_Qualities);
|
|
weenie->CopyIntStat(PORTAL_BITMASK_INT, &portalDefaults->m_Qualities);
|
|
weenie->CopyStringStat(QUEST_RESTRICTION_STRING, &portalDefaults->m_Qualities);
|
|
|
|
switch (meta->_link)
|
|
{
|
|
case 1:
|
|
weenie->m_Qualities.SetDataID(ORIGINAL_PORTAL_DID, m_pWeenie->InqDIDQuality(LINKED_PORTAL_ONE_DID, 0));
|
|
break;
|
|
|
|
case 2:
|
|
weenie->m_Qualities.SetDataID(ORIGINAL_PORTAL_DID, m_pWeenie->InqDIDQuality(LINKED_PORTAL_TWO_DID, 0));
|
|
break;
|
|
}
|
|
|
|
if (canFlagForQuest)
|
|
weenie->CopyStringStat(QUEST_STRING, &portalDefaults->m_Qualities);
|
|
if (portalDefaults->m_Qualities.GetBool(PORTAL_SHOW_DESTINATION_BOOL, 0))
|
|
{
|
|
weenie->CopyBoolStat(PORTAL_SHOW_DESTINATION_BOOL, &portalDefaults->m_Qualities);
|
|
weenie->CopyStringStat(APPRAISAL_PORTAL_DESTINATION_STRING, &portalDefaults->m_Qualities);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (meta->_link)
|
|
{
|
|
case 1:
|
|
weenie->m_Qualities.SetPosition(DESTINATION_POSITION, m_pWeenie->InqPositionQuality(LINKED_PORTAL_ONE_POSITION, Position()));
|
|
weenie->m_Qualities.SetDataID(ORIGINAL_PORTAL_DID, m_pWeenie->InqDIDQuality(LINKED_PORTAL_ONE_DID, 0));
|
|
break;
|
|
|
|
case 2:
|
|
weenie->m_Qualities.SetPosition(DESTINATION_POSITION, m_pWeenie->InqPositionQuality(LINKED_PORTAL_TWO_POSITION, Position()));
|
|
weenie->m_Qualities.SetDataID(ORIGINAL_PORTAL_DID, m_pWeenie->InqDIDQuality(LINKED_PORTAL_TWO_DID, 0));
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_pWorld->CreateEntity(weenie);
|
|
|
|
bSpellPerformed = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SpellType::PortalSending_SpellType:
|
|
{
|
|
PortalSendingSpellEx *meta = (PortalSendingSpellEx *)m_SpellCastData.spellEx->_meta_spell._spell;
|
|
|
|
CWeenieObject *target = GetCastTarget();
|
|
|
|
if (m_pWeenie->AsPlayer() && m_pWeenie->AsPlayer()->CheckPKActivity() || m_pWeenie->HasOwner() && target->AsPlayer() && target->AsPlayer()->CheckPKActivity())
|
|
{
|
|
m_pWeenie->SendText("You have been involved in Player Killer combat too recently!", LTT_MAGIC);
|
|
break;
|
|
}
|
|
|
|
if (target)
|
|
{
|
|
std::string spellName = m_SpellCastData.spellEx->_name; //need to set this now because movement_teleport will terminate m_SpellCastData.
|
|
PerformCastParticleEffects(); // perform particle effects early because teleporting will cancel it
|
|
target->Movement_Teleport(meta->_pos, false);
|
|
|
|
if ((m_pWeenie != target) && (m_pWeenie->m_Qualities.m_WeenieType != Gem_WeenieType))
|
|
target->SendText(csprintf("%s teleports you with %s.", m_pWeenie->GetName().c_str() , spellName.c_str()), LTT_MAGIC);
|
|
else
|
|
target->SendText("You have been teleported.", LTT_MAGIC);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SpellType::FellowPortalSending_SpellType:
|
|
{
|
|
FellowshipPortalSendingSpellEx *meta = (FellowshipPortalSendingSpellEx *)m_SpellCastData.spellEx->_meta_spell._spell;
|
|
|
|
CWeenieObject *target = GetCastTarget();
|
|
|
|
if (!target->HasFellowship())
|
|
break;
|
|
|
|
else
|
|
{
|
|
Fellowship *fellow = target->GetFellowship();
|
|
CWorldLandBlock *block = target->GetBlock();
|
|
for (auto &entry : fellow->_fellowship_table)
|
|
{
|
|
if (CWeenieObject *member = g_pWorld->FindPlayer(entry.first))
|
|
{
|
|
if (member->GetBlock() == block)
|
|
{
|
|
|
|
member->Movement_Teleport(meta->_pos, false);
|
|
member->SendText("You have been teleported.", LTT_MAGIC);
|
|
}
|
|
}
|
|
}
|
|
PerformFellowCastParticleEffects(fellow);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SpellType::LifeProjectile_SpellType:
|
|
|
|
case SpellType::Projectile_SpellType:
|
|
{
|
|
ProjectileSpellEx *meta = (ProjectileSpellEx *)m_SpellCastData.spellEx->_meta_spell._spell;
|
|
bSpellPerformed = LaunchProjectileSpell(meta);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bSpellPerformed)
|
|
{
|
|
PerformCastParticleEffects();
|
|
}
|
|
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
// You gain 37 points of health due to casting Drain Health Other I on Gelidite Lord
|
|
void CSpellcastingManager::TransferVitalPercent(CWeenieObject *target, float drainPercent, float infusePercent, STypeAttribute2nd attribute)
|
|
{
|
|
if (!target || target->IsDead())
|
|
return;
|
|
|
|
bool reversed = (m_SpellCastData.spell->_bitfield & Reversed_SpellIndex) ? true : false;
|
|
|
|
int targetAdjust;
|
|
int selfAdjust;
|
|
|
|
if (reversed)
|
|
{
|
|
// draining target, infusing self
|
|
targetAdjust = -(int)ceil(target->GetHealth() * drainPercent);
|
|
if (targetAdjust < -50)
|
|
targetAdjust = -50;
|
|
|
|
selfAdjust = (int)floor(-targetAdjust * infusePercent);
|
|
}
|
|
else
|
|
{
|
|
// draining self, infusing target
|
|
selfAdjust = -(int)ceil(m_pWeenie->GetHealth() * drainPercent);
|
|
targetAdjust = (int)floor(-selfAdjust * infusePercent);
|
|
}
|
|
|
|
if (targetAdjust < 0 && target->ImmuneToDamage(m_pWeenie))
|
|
{
|
|
// send notice they are immune?
|
|
return;
|
|
}
|
|
|
|
const char *vitalName;
|
|
switch (attribute)
|
|
{
|
|
case HEALTH_ATTRIBUTE_2ND:
|
|
{
|
|
selfAdjust = m_pWeenie->AdjustHealth(selfAdjust);
|
|
targetAdjust = target->AdjustHealth(targetAdjust);
|
|
vitalName = "health";
|
|
break;
|
|
}
|
|
case STAMINA_ATTRIBUTE_2ND:
|
|
{
|
|
selfAdjust = m_pWeenie->AdjustStamina(selfAdjust);
|
|
targetAdjust = target->AdjustStamina(targetAdjust);
|
|
vitalName = "stamina";
|
|
break;
|
|
}
|
|
case MANA_ATTRIBUTE_2ND:
|
|
{
|
|
selfAdjust = m_pWeenie->AdjustMana(selfAdjust);
|
|
targetAdjust = target->AdjustMana(targetAdjust);
|
|
vitalName = "mana";
|
|
break;
|
|
}
|
|
default:
|
|
return;
|
|
}
|
|
|
|
SendTransferVitalPercentText(target, reversed ? targetAdjust : selfAdjust, reversed ? selfAdjust : targetAdjust, reversed, vitalName);
|
|
|
|
if (target->IsDead())
|
|
{
|
|
if (m_pWeenie == target)
|
|
{
|
|
target->NotifyVictimEvent("You died!");
|
|
|
|
if (target->_IsPlayer())
|
|
{
|
|
target->NotifyDeathMessage(m_pWeenie->GetID(), csprintf("%s died!", target->GetName().c_str()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
target->NotifyVictimEvent(csprintf("You were killed by %s!", m_pWeenie->GetName().c_str()));
|
|
m_pWeenie->NotifyKillerEvent(csprintf("You killed %s!", target->GetName().c_str()));
|
|
|
|
if (target->_IsPlayer())
|
|
{
|
|
target->NotifyDeathMessage(m_pWeenie->GetID(), csprintf("%s killed %s!", m_pWeenie->GetName().c_str(), target->GetName().c_str()));
|
|
}
|
|
}
|
|
|
|
target->OnDeath(m_pWeenie->GetID());
|
|
}
|
|
|
|
if (m_pWeenie != target && m_pWeenie->IsDead())
|
|
{
|
|
m_pWeenie->NotifyVictimEvent("You died!");
|
|
|
|
if (m_pWeenie->_IsPlayer())
|
|
{
|
|
m_pWeenie->NotifyDeathMessage(m_pWeenie->GetID(), csprintf("%s died!", m_pWeenie->GetName().c_str()));
|
|
}
|
|
|
|
m_pWeenie->OnDeath(m_pWeenie->GetID());
|
|
}
|
|
}
|
|
|
|
void CSpellcastingManager::SendTransferVitalPercentText(CWeenieObject *target, int drained, int infused, bool reversed, const char *vitalName)
|
|
{
|
|
// You gain 37 points of health due to casting Drain Health Other I on Gelidite Lord
|
|
|
|
m_pWeenie->SendText(csprintf("You %s %d points of %s due to casting %s on %s",
|
|
reversed ? "gain" : "lose", reversed ? infused : -drained, vitalName, m_SpellCastData.spell->_name.c_str(), target->GetName().c_str()), LTT_MAGIC);
|
|
|
|
if (target != m_pWeenie)
|
|
{
|
|
target->SendText(csprintf("You %s %d points of %s due to %s casting %s on you",
|
|
!reversed ? "gain" : "lose", !reversed ? infused : -drained, vitalName, m_SpellCastData.spell->_name.c_str(), m_pWeenie->GetName().c_str()), LTT_MAGIC);
|
|
}
|
|
}
|
|
|
|
std::string GetAttribute2ndName(STypeAttribute2nd attribute2nd)
|
|
{
|
|
switch (attribute2nd)
|
|
{
|
|
case MAX_HEALTH_ATTRIBUTE_2ND:
|
|
return "maximum health";
|
|
case HEALTH_ATTRIBUTE_2ND:
|
|
return "health";
|
|
case MAX_STAMINA_ATTRIBUTE_2ND:
|
|
return "maximum stamina";
|
|
case STAMINA_ATTRIBUTE_2ND:
|
|
return "stamina";
|
|
case MAX_MANA_ATTRIBUTE_2ND:
|
|
return "maximum mana";
|
|
case MANA_ATTRIBUTE_2ND:
|
|
return "mana";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
bool CSpellcastingManager::DoTransferSpell(CWeenieObject *other, const TransferSpellEx *meta)
|
|
{
|
|
// Calculate source amount
|
|
CWeenieObject *source = NULL;
|
|
if (meta->_bitfield & TransferSpellEx::SourceSelf)
|
|
source = GetCastSource();
|
|
else if (meta->_bitfield & TransferSpellEx::SourceOther)
|
|
source = other;
|
|
|
|
CWeenieObject *dest = NULL;
|
|
if (meta->_bitfield & TransferSpellEx::DestinationSelf)
|
|
dest = GetCastSource();
|
|
else if (meta->_bitfield & TransferSpellEx::DestinationOther)
|
|
dest = other;
|
|
|
|
if (!source || !dest)
|
|
return false;
|
|
|
|
double drainResistMod = 1.0;
|
|
double boostResistMod = 1.0;
|
|
|
|
if (m_pWeenie != source)
|
|
{
|
|
// negative spell
|
|
if (source->ImmuneToDamage(m_pWeenie))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// NPKs should not be able to heal PKs
|
|
if (m_pWeenie && other && m_pWeenie->AsPlayer() && other->AsPlayer())
|
|
{
|
|
if (!m_pWeenie->IsPK() && other->IsPK())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// try to resist
|
|
if (m_SpellCastData.spell->_bitfield & Resistable_SpellIndex)
|
|
{
|
|
if (source->TryMagicResist(m_SpellCastData.current_skill))
|
|
{
|
|
source->EmitSound(Sound_ResistSpell, 1.0f, false);
|
|
source->SendText(csprintf("You resist the spell cast by %s", m_pWeenie->GetName().c_str()), LTT_MAGIC);
|
|
m_pWeenie->SendText(csprintf("%s resists your spell", source->GetName().c_str()), LTT_MAGIC);
|
|
source->OnResistSpell(m_pWeenie);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
source->HandleAggro(m_pWeenie);
|
|
|
|
switch (meta->_src)
|
|
{
|
|
case HEALTH_ATTRIBUTE_2ND:
|
|
source->m_Qualities.InqFloat(RESIST_HEALTH_DRAIN_FLOAT, drainResistMod);
|
|
break;
|
|
case STAMINA_ATTRIBUTE_2ND:
|
|
source->m_Qualities.InqFloat(RESIST_STAMINA_DRAIN_FLOAT, drainResistMod);
|
|
break;
|
|
case MANA_ATTRIBUTE_2ND:
|
|
source->m_Qualities.InqFloat(RESIST_MANA_DRAIN_FLOAT, drainResistMod);
|
|
break;
|
|
}
|
|
|
|
if (source->AsPlayer()) //only players have natural resistances.
|
|
{
|
|
//Some combination of strength and endurance allows one to have a level of "natural resistances" to the 7 damage types.This caps out at a 50 % resistance(the equivalent to level 5 life prots) to these damage types.This resistance is not additive to life protections : higher level life protections will overwrite these natural resistances, although life vulns will take these natural resistances into account, if the player does not have a higher level life protection cast upon them.
|
|
//For example, a player will not get a free protective bonus from natural resistances if they have both Prot 7 and Vuln 7 cast upon them.The Prot and Vuln will cancel each other out, and since the Prot has overwritten the natural resistances, there will be no resistance bonus.
|
|
//The abilities that Endurance or Endurance/Strength conveys are not increased by Strength or Endurance buffs.It is the raw Strength and/or Endurance scores that determine the various bonuses.
|
|
//drain resistances(same formula as natural resistances) allows one to partially resist drain health/stamina/mana and harm attacks, up to a maximum of roughly 50%.
|
|
|
|
//todo: natural resistances only change when base strength or endurance changes so we could potentially pre-calculate this somewhere else.
|
|
DWORD strength = 0;
|
|
DWORD endurance = 0;
|
|
source->m_Qualities.InqAttribute(STRENGTH_ATTRIBUTE, strength, true);
|
|
source->m_Qualities.InqAttribute(ENDURANCE_ATTRIBUTE, endurance, true);
|
|
float strAndEnd = (float)(strength + endurance);
|
|
float resistanceNatural;
|
|
if (strAndEnd <= 200) //formula deduced from values present in the client pdb.
|
|
resistanceNatural = 1.0f - ((0.05 * strAndEnd) / 100.f);
|
|
else
|
|
resistanceNatural = 1.0f - (((0.1666667 * strAndEnd) - 23.33333) / 100.f);
|
|
|
|
resistanceNatural = max(resistanceNatural, 0.5f);
|
|
|
|
if (resistanceNatural < drainResistMod)
|
|
drainResistMod = resistanceNatural;
|
|
}
|
|
}
|
|
|
|
if (m_pWeenie != dest)
|
|
{
|
|
switch (meta->_src)
|
|
{
|
|
case HEALTH_ATTRIBUTE_2ND:
|
|
dest->m_Qualities.InqFloat(RESIST_HEALTH_BOOST_FLOAT, boostResistMod);
|
|
break;
|
|
case STAMINA_ATTRIBUTE_2ND:
|
|
dest->m_Qualities.InqFloat(RESIST_STAMINA_DRAIN_FLOAT, boostResistMod);
|
|
break;
|
|
case MANA_ATTRIBUTE_2ND:
|
|
dest->m_Qualities.InqFloat(RESIST_MANA_DRAIN_FLOAT, boostResistMod);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
DWORD sourceStartValue = 0;
|
|
DWORD sourceMinValue = 0;
|
|
DWORD sourceMaxValue = 0;
|
|
source->m_Qualities.InqAttribute2nd(meta->_src, sourceStartValue, FALSE);
|
|
source->m_Qualities.InqAttribute2nd((STypeAttribute2nd)((int)meta->_src - 1), sourceMaxValue, FALSE);
|
|
|
|
DWORD destStartValue = 0;
|
|
DWORD destMinValue = 0;
|
|
DWORD destMaxValue = 0;
|
|
dest->m_Qualities.InqAttribute2nd(meta->_dest, destStartValue, FALSE);
|
|
dest->m_Qualities.InqAttribute2nd((STypeAttribute2nd)((int)meta->_dest - 1), destMaxValue, FALSE);
|
|
|
|
int sourceTakeAmount = (int)(sourceStartValue * meta->_proportion * drainResistMod);
|
|
if (meta->_transferCap && sourceTakeAmount > meta->_transferCap)
|
|
sourceTakeAmount = meta->_transferCap;
|
|
|
|
int destGiveAmount = (int)(sourceTakeAmount * (1.0 - meta->_lossPercent) * boostResistMod);
|
|
|
|
int destResultValue = destStartValue + destGiveAmount;
|
|
if (destResultValue > destMaxValue)
|
|
{
|
|
destResultValue = destMaxValue;
|
|
destGiveAmount = destMaxValue - destStartValue;
|
|
sourceTakeAmount = (int)(destGiveAmount / (1.0 - meta->_lossPercent));
|
|
}
|
|
int sourceResultValue = sourceStartValue - sourceTakeAmount;
|
|
|
|
if (sourceResultValue != sourceStartValue)
|
|
{
|
|
source->m_Qualities.SetAttribute2nd(meta->_src, sourceResultValue);
|
|
source->NotifyAttribute2ndStatUpdated(meta->_src);
|
|
}
|
|
|
|
if (destResultValue != destStartValue)
|
|
{
|
|
dest->m_Qualities.SetAttribute2nd(meta->_dest, destResultValue);
|
|
dest->NotifyAttribute2ndStatUpdated(meta->_dest);
|
|
}
|
|
|
|
if (source == dest)
|
|
{
|
|
// You cast Stamina to Mana Self III on yourself and lose 78 points of stamina and also gain 86 points of mana
|
|
if (m_pWeenie == source)
|
|
{
|
|
source->SendText(csprintf(
|
|
"You cast %s on yourself and lose %d points of %s and also gain %d points of %s",
|
|
m_SpellCastData.spell->_name.c_str(), sourceTakeAmount, GetAttribute2ndName(meta->_src).c_str(), destGiveAmount, GetAttribute2ndName(meta->_dest).c_str()
|
|
), LTT_MAGIC);
|
|
}
|
|
else
|
|
{
|
|
source->SendText(csprintf(
|
|
"You lose %d points of %s and also gain %d points of %s due to %s casting %s on you",
|
|
sourceTakeAmount, GetAttribute2ndName(meta->_src).c_str(), destGiveAmount, GetAttribute2ndName(meta->_dest).c_str(), m_pWeenie->GetName().c_str(), m_SpellCastData.spell->_name.c_str()
|
|
), LTT_MAGIC);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_pWeenie == source)
|
|
{
|
|
source->SendText(csprintf("You lose %d points of %s due to casting %s on %s",
|
|
sourceTakeAmount, GetAttribute2ndName(meta->_src).c_str(), m_SpellCastData.spell->_name.c_str(), dest->GetName().c_str()), LTT_MAGIC);
|
|
dest->SendText(csprintf("You gain %d points of %s due to %s casting %s on you",
|
|
destGiveAmount, GetAttribute2ndName(meta->_dest).c_str(), m_pWeenie->GetName().c_str(), m_SpellCastData.spell->_name.c_str()), LTT_MAGIC);
|
|
}
|
|
else if (m_pWeenie == dest)
|
|
{
|
|
dest->SendText(csprintf("You gain %d points of %s due to casting %s on %s",
|
|
destGiveAmount, GetAttribute2ndName(meta->_dest).c_str(), m_SpellCastData.spell->_name.c_str(), source->GetName().c_str()), LTT_MAGIC);
|
|
source->SendText(csprintf("You lose %d points of %s due to %s casting %s on you",
|
|
sourceTakeAmount, GetAttribute2ndName(meta->_src).c_str(), m_pWeenie->GetName().c_str(), m_SpellCastData.spell->_name.c_str()), LTT_MAGIC);
|
|
|
|
if (dest->AsPlayer())
|
|
{
|
|
// update the target's health on the casting player asap
|
|
if(source->AsPlayer())
|
|
((CPlayerWeenie*)source)->RefreshTargetHealth();
|
|
}
|
|
if (source->AsPlayer())
|
|
{
|
|
// update the target's health on the casting player asap
|
|
if(dest->AsPlayer())
|
|
((CPlayerWeenie*)dest)->RefreshTargetHealth();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// should never happen
|
|
}
|
|
}
|
|
|
|
if (meta->_src == HEALTH_ATTRIBUTE_2ND)
|
|
source->CheckDeath(m_pWeenie, DAMAGE_TYPE::HEALTH_DAMAGE_TYPE);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CSpellcastingManager::AdjustVital(CWeenieObject *target)
|
|
{
|
|
if (!target || target->IsDead())
|
|
return false;
|
|
|
|
// NPKs should not be able to heal PKs
|
|
if (m_pWeenie && target && m_pWeenie->AsPlayer() && target->AsPlayer())
|
|
{
|
|
if (!m_pWeenie->IsPK() && target->IsPK())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
BoostSpellEx *meta = (BoostSpellEx *)m_SpellCastData.spellEx->_meta_spell._spell;
|
|
|
|
bool isDamage = (meta->_boost < 0);
|
|
int boostMin = abs(meta->_boost);
|
|
int boostMax = abs(meta->_boost + meta->_boostVariance);
|
|
|
|
double preVarianceDamage = boostMax;
|
|
double damageOrHealAmount = Random::RollDice(boostMin, boostMax);
|
|
|
|
CWeenieObject *wand = g_pWorld->FindObject(m_SpellCastData.wand_id);
|
|
if (wand)
|
|
{
|
|
double elementalDamageMod = wand->InqDamageType() == meta->_dt ? wand->InqFloatQuality(ELEMENTAL_DAMAGE_MOD_FLOAT, 1.0) : 1.0;
|
|
if (m_pWeenie->AsPlayer() && target->AsPlayer()) //pvp
|
|
elementalDamageMod = ((elementalDamageMod - 1.0) / 2.0) + 1.0;
|
|
damageOrHealAmount *= elementalDamageMod;
|
|
}
|
|
|
|
// negative spell
|
|
if (isDamage)
|
|
{
|
|
if (m_pWeenie && target && m_pWeenie->AsPlayer() && target->AsPlayer())
|
|
{
|
|
if (m_pWeenie->IsPK() && target->IsPK())
|
|
{
|
|
m_pWeenie->AsPlayer()->UpdatePKActivity();
|
|
target->AsPlayer()->UpdatePKActivity();
|
|
}
|
|
}
|
|
|
|
// try to resist
|
|
if (m_SpellCastData.spell->_bitfield & Resistable_SpellIndex)
|
|
{
|
|
if (m_pWeenie != target)
|
|
{
|
|
if (target->TryMagicResist(m_SpellCastData.current_skill))
|
|
{
|
|
target->EmitSound(Sound_ResistSpell, 1.0f, false);
|
|
|
|
if (m_pWeenie)
|
|
{
|
|
target->SendText(csprintf("You resist the spell cast by %s", m_pWeenie->GetName().c_str()), LTT_MAGIC);
|
|
m_pWeenie->SendText(csprintf("%s resists your spell", target->GetName().c_str()), LTT_MAGIC);
|
|
target->OnResistSpell(m_pWeenie);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
DamageEventData dmgEvent;
|
|
dmgEvent.source = m_pWeenie;
|
|
dmgEvent.target = target;
|
|
dmgEvent.weapon = wand;
|
|
dmgEvent.damage_form = DF_MAGIC;
|
|
dmgEvent.damage_type = meta->_dt;
|
|
dmgEvent.hit_quadrant = DAMAGE_QUADRANT::DQ_UNDEF; //should spells have hit quadrants?
|
|
dmgEvent.attackSkill = m_SpellCastData.spell->InqSkillForSpell();
|
|
dmgEvent.attackSkillLevel = m_SpellCastData.current_skill;
|
|
dmgEvent.preVarianceDamage = preVarianceDamage;
|
|
dmgEvent.baseDamage = damageOrHealAmount;
|
|
|
|
dmgEvent.isProjectileSpell = false;
|
|
dmgEvent.spell_name = m_SpellCastData.spell->_name;
|
|
|
|
CalculateCriticalHitData(&dmgEvent, &m_SpellCastData);
|
|
dmgEvent.wasCrit = (Random::GenFloat(0.0, 1.0) < dmgEvent.critChance) ? true : false;
|
|
|
|
CalculateDamage(&dmgEvent, &m_SpellCastData);
|
|
|
|
m_pWeenie->TryToDealDamage(dmgEvent);
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
const char *vitalName;
|
|
|
|
switch (meta->_dt)
|
|
{
|
|
case HEALTH_DAMAGE_TYPE:
|
|
{
|
|
damageOrHealAmount = target->AdjustHealth(damageOrHealAmount);
|
|
vitalName = "health";
|
|
break;
|
|
}
|
|
case STAMINA_DAMAGE_TYPE:
|
|
{
|
|
damageOrHealAmount = target->AdjustStamina(damageOrHealAmount);
|
|
vitalName = "stamina";
|
|
break;
|
|
}
|
|
case MANA_DAMAGE_TYPE:
|
|
{
|
|
damageOrHealAmount = target->AdjustMana(damageOrHealAmount);
|
|
vitalName = "mana";
|
|
break;
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
SendAdjustVitalText(target, damageOrHealAmount, vitalName);
|
|
|
|
target->CheckDeath(GetCastSource(), meta->_dt);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void CSpellcastingManager::SendAdjustVitalText(CWeenieObject *target, int amount, const char *vitalName)
|
|
{
|
|
bool bRestore = true;
|
|
if (amount < 0 || (amount == 0 && !(m_SpellCastData.spell->_bitfield & Beneficial_SpellIndex)))
|
|
{
|
|
bRestore = false;
|
|
amount = -amount;
|
|
}
|
|
|
|
if (m_pWeenie != target)
|
|
{
|
|
m_pWeenie->SendText(csprintf("With %s you %s %d points of %s %s %s.",
|
|
m_SpellCastData.spell->_name.c_str(), bRestore ? "restore" : "drain", amount, vitalName, bRestore ? "to" : "from", target->GetName().c_str()), LTT_MAGIC);
|
|
|
|
target->SendText(csprintf("%s casts %s and %s %d points of your %s.",
|
|
m_pWeenie->GetName().c_str(), m_SpellCastData.spell->_name.c_str(), bRestore ? "restores" : "drains", amount, vitalName), LTT_MAGIC);
|
|
|
|
if (vitalName == "health")
|
|
{
|
|
if (m_pWeenie->AsPlayer())
|
|
{
|
|
// update the target's health on the casting player asap
|
|
((CPlayerWeenie*)m_pWeenie)->RefreshTargetHealth();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pWeenie->SendText(csprintf("You cast %s and %s %d points of your %s.",
|
|
m_SpellCastData.spell->_name.c_str(), bRestore ? "restore" : "drain", amount, vitalName), LTT_MAGIC);
|
|
}
|
|
}
|
|
|
|
int CSpellcastingManager::CreatureBeginCast(DWORD target_id, DWORD spell_id)
|
|
{
|
|
if (m_bCasting)
|
|
return WERROR_ACTIONS_LOCKED;
|
|
|
|
m_SpellCastData = SpellCastData();
|
|
m_SpellCastData.caster_id = m_pWeenie->GetID();
|
|
m_SpellCastData.source_id = m_pWeenie->GetTopLevelID();
|
|
m_SpellCastData.target_id = target_id;
|
|
m_SpellCastData.spell_id = spell_id;
|
|
m_SpellCastData.wand_id = m_pWeenie->GetWieldedCasterID();
|
|
|
|
m_SpellCastData.cast_timeout = Timer::cur_time + 10.0f;
|
|
m_SpellCastData.initial_cast_position = m_pWeenie->m_Position;
|
|
|
|
if (!ResolveSpellBeingCasted())
|
|
{
|
|
SERVER_INFO << "Player trying to cast unknown spell?";
|
|
return WERROR_MAGIC_GENERAL_FAILURE;
|
|
}
|
|
|
|
// if the spell uses mana and we use mana then check we've got enough
|
|
if (m_SpellCastData.uses_mana && m_pWeenie->InqBoolQuality(AI_USES_MANA_BOOL, 1) && m_pWeenie->GetMana() < m_SpellCastData.spell->_base_mana)
|
|
{
|
|
return WERROR_MAGIC_INSUFFICIENT_MANA;
|
|
}
|
|
|
|
// if we're at max hp, no need to cast self heals
|
|
if (m_SpellCastData.spell->_category == 67 && (m_pWeenie->GetHealth() == m_pWeenie->GetMaxHealth()))
|
|
{
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
m_bCasting = true;
|
|
m_PendingMotions.clear();
|
|
m_bTurningToObject = false;
|
|
|
|
m_PendingMotions.push_back(SpellCastingMotion(Motion_CastSpell, 2.0f, true, true, 2.0f));
|
|
m_SpellCastData.power_level_of_power_component = m_SpellCastData.spell_formula.GetPowerLevelOfPowerComponent();
|
|
|
|
// if we use our mana pool then adjust it for the spell cost
|
|
if (m_SpellCastData.uses_mana && m_pWeenie->InqBoolQuality(AI_USES_MANA_BOOL, 1))
|
|
{
|
|
m_pWeenie->AdjustMana(-(m_SpellCastData.spell->_base_mana)); //should this use GetManaCost to adjust for skill/mana c?
|
|
}
|
|
|
|
BeginNextMotion();
|
|
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
|
|
int CSpellcastingManager::CastSpellInstant(DWORD target_id, DWORD spell_id)
|
|
{
|
|
int error = WERROR_NONE;
|
|
|
|
// incase we are interrupting a cast
|
|
SpellCastData oldData = m_SpellCastData;
|
|
bool bOldCasting = m_bCasting;
|
|
|
|
m_bCasting = true;
|
|
m_SpellCastData = SpellCastData();
|
|
if (oldData.caster_id == 0)
|
|
m_SpellCastData.caster_id = m_pWeenie->GetID();
|
|
else
|
|
m_SpellCastData.caster_id = oldData.caster_id;
|
|
m_SpellCastData.source_id = m_pWeenie->GetTopLevelID();
|
|
m_SpellCastData.target_id = target_id;
|
|
m_SpellCastData.spell_id = spell_id;
|
|
m_SpellCastData.wand_id = m_pWeenie->GetWieldedCasterID();
|
|
m_SpellCastData.cast_timeout = Timer::cur_time + 10.0f;
|
|
m_SpellCastData.power_level_of_power_component = m_SpellCastData.spell_formula.GetPowerLevelOfPowerComponent();
|
|
m_SpellCastData.initial_cast_position = m_pWeenie->m_Position;
|
|
m_SpellCastData.uses_mana = false;
|
|
|
|
if (ResolveSpellBeingCasted())
|
|
{
|
|
LaunchSpellEffect(false);
|
|
}
|
|
else
|
|
{
|
|
error = WERROR_MAGIC_GENERAL_FAILURE;
|
|
}
|
|
|
|
m_SpellCastData = oldData;
|
|
m_bCasting = bOldCasting;
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
int CSpellcastingManager::CastSpellEquipped(DWORD target_id, DWORD spell_id, WORD serial)
|
|
{
|
|
int error = WERROR_NONE;
|
|
|
|
// incase we are interrupting a cast
|
|
SpellCastData oldData = m_SpellCastData;
|
|
bool bOldCasting = m_bCasting;
|
|
|
|
m_bCasting = true;
|
|
m_SpellCastData = SpellCastData();
|
|
m_SpellCastData.caster_id = m_pWeenie->GetID();
|
|
m_SpellCastData.source_id = m_pWeenie->GetTopLevelID();
|
|
m_SpellCastData.target_id = target_id;
|
|
m_SpellCastData.spell_id = spell_id;
|
|
m_SpellCastData.wand_id = m_pWeenie->GetWieldedCasterID();
|
|
m_SpellCastData.cast_timeout = Timer::cur_time + 10.0f;
|
|
m_SpellCastData.power_level_of_power_component = m_SpellCastData.spell_formula.GetPowerLevelOfPowerComponent();
|
|
m_SpellCastData.initial_cast_position = m_pWeenie->m_Position;
|
|
m_SpellCastData.uses_mana = false;
|
|
m_SpellCastData.equipped = true;
|
|
m_SpellCastData.serial = serial;
|
|
|
|
if (ResolveSpellBeingCasted())
|
|
{
|
|
LaunchSpellEffect(false);
|
|
}
|
|
else
|
|
{
|
|
error = WERROR_MAGIC_GENERAL_FAILURE;
|
|
}
|
|
|
|
m_SpellCastData = oldData;
|
|
m_bCasting = bOldCasting;
|
|
|
|
return error;
|
|
}
|
|
|
|
DWORD CSpellcastingManager::DetermineSkillLevelForSpell()
|
|
{
|
|
int spellcraft = 0;
|
|
CWeenieObject *source = g_pWorld->FindObject(m_SpellCastData.caster_id);
|
|
|
|
if (!source)
|
|
return 0;
|
|
|
|
if (source->m_Qualities.InqInt(ITEM_SPELLCRAFT_INT, spellcraft, FALSE, FALSE))
|
|
return (DWORD)spellcraft;
|
|
|
|
if (!m_SpellCastData.spell)
|
|
return 0;
|
|
|
|
DWORD skillLevel = 0;
|
|
STypeSkill skill = m_SpellCastData.spell->InqSkillForSpell();
|
|
|
|
if (skill)
|
|
{
|
|
m_pWeenie->InqSkill(skill, skillLevel, FALSE);
|
|
}
|
|
else
|
|
{
|
|
DWORD creatureEnch = 0, itemEnch = 0, life = 0, war = 0, voidMagic = 0;
|
|
m_pWeenie->InqSkill(CREATURE_ENCHANTMENT_SKILL, creatureEnch, FALSE);
|
|
m_pWeenie->InqSkill(ITEM_ENCHANTMENT_SKILL, itemEnch, FALSE);
|
|
m_pWeenie->InqSkill(LIFE_MAGIC_SKILL, life, FALSE);
|
|
m_pWeenie->InqSkill(WAR_MAGIC_SKILL, war, FALSE);
|
|
m_pWeenie->InqSkill(VOID_MAGIC_SKILL, voidMagic, FALSE);
|
|
|
|
DWORD highestSkill = 0;
|
|
|
|
highestSkill = max(highestSkill, creatureEnch);
|
|
highestSkill = max(highestSkill, itemEnch);
|
|
highestSkill = max(highestSkill, life);
|
|
highestSkill = max(highestSkill, war);
|
|
highestSkill = max(highestSkill, voidMagic);
|
|
|
|
skillLevel = highestSkill;
|
|
}
|
|
return skillLevel;
|
|
}
|
|
|
|
double CSpellcastingManager::DetermineSpellRange()
|
|
{
|
|
if (!m_SpellCastData.spell)
|
|
return 0.0;
|
|
|
|
double range = 0.0;
|
|
|
|
if (m_SpellCastData.spellEx)
|
|
range = m_SpellCastData.spellEx->_base_range_constant + m_SpellCastData.spellEx->_base_range_mod * m_SpellCastData.current_skill;
|
|
else
|
|
range = m_SpellCastData.spell->_base_range_constant + m_SpellCastData.spell->_base_range_mod * m_SpellCastData.current_skill;
|
|
|
|
const float RADAR_OUTDOOR_RADIUS = 75.0f;
|
|
if (range > RADAR_OUTDOOR_RADIUS)
|
|
range = RADAR_OUTDOOR_RADIUS;
|
|
|
|
return range;
|
|
}
|
|
|
|
int CSpellcastingManager::CheckTargetValidity()
|
|
{
|
|
if (!m_SpellCastData.spell)
|
|
return WERROR_MAGIC_GENERAL_FAILURE;
|
|
|
|
CWeenieObject *pCastSource = GetCastSource();
|
|
if (!pCastSource)
|
|
return WERROR_OBJECT_GONE;
|
|
|
|
CWeenieObject *pTarget = g_pWorld->FindWithinPVS(pCastSource, m_SpellCastData.target_id);
|
|
|
|
if (!(m_SpellCastData.spell->_bitfield & SelfTargeted_SpellIndex))
|
|
{
|
|
if (!pTarget)
|
|
return WERROR_OBJECT_GONE;
|
|
|
|
if (!pTarget->HasOwner() || !pCastSource->FindContainedItem(pTarget->GetID()))
|
|
{
|
|
if (pCastSource && m_SpellCastData.range_check)
|
|
{
|
|
if (pCastSource->DistanceTo(pTarget, true) > m_SpellCastData.max_range)
|
|
return WERROR_MISSILE_OUT_OF_RANGE;
|
|
}
|
|
}
|
|
}
|
|
|
|
int targetType = m_SpellCastData.spell->InqTargetType();
|
|
if (targetType != ITEM_TYPE::TYPE_UNDEF)
|
|
{
|
|
if (!pTarget)
|
|
return WERROR_OBJECT_GONE;
|
|
|
|
if (targetType == ITEM_TYPE::TYPE_ITEM_ENCHANTABLE_TARGET && !m_SpellCastData.equipped)
|
|
{
|
|
targetType |= TYPE_CREATURE;
|
|
}
|
|
|
|
if (!(pTarget->GetItemType() & targetType))
|
|
{
|
|
if (m_SpellCastData.equipped && (m_SpellCastData.spell->_school == 3) && (m_SpellCastData.spell->_bitfield & SelfTargeted_SpellIndex))
|
|
{
|
|
}
|
|
else
|
|
{
|
|
return WERROR_MAGIC_BAD_TARGET_TYPE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pTarget->IsCreature())
|
|
{
|
|
if (pTarget->IsVendor())
|
|
return WERROR_MAGIC_BAD_TARGET_TYPE;
|
|
}
|
|
|
|
if (!(m_SpellCastData.spell->_bitfield & (Beneficial_SpellIndex | NotResearchable_SpellIndex | SelfTargeted_SpellIndex))) //Not researchable bitmask to allow sending spells to function properly. SelfTargeted to allow portal summon gems to function.
|
|
{
|
|
if (!pTarget)
|
|
return WERROR_OBJECT_GONE;
|
|
|
|
if (pTarget->IsCreature() && !pTarget->IsAttackable() && (pTarget != m_pWeenie))//no harmful spells on unattackable players/admins/sentinels. Exclude self to cast AoE while unattackable.
|
|
{
|
|
if (pCastSource && m_SpellCastData.range_check)
|
|
{
|
|
if (pCastSource->DistanceTo(pTarget, true) > m_SpellCastData.max_range)
|
|
return WERROR_MISSILE_OUT_OF_RANGE;
|
|
}
|
|
return WERROR_PK_PROTECTED_TARGET;
|
|
}
|
|
}
|
|
|
|
if (m_SpellCastData.spell->_bitfield & Beneficial_SpellIndex)
|
|
{
|
|
if (!pTarget)
|
|
return WERROR_OBJECT_GONE;
|
|
|
|
if (pTarget->IsCreature() && !pTarget->AsPlayer() && m_pWeenie->AsPlayer())//no buffing mobs if we are a player.
|
|
{
|
|
if (pCastSource && m_SpellCastData.range_check)
|
|
{
|
|
if (pCastSource->DistanceTo(pTarget, true) > m_SpellCastData.max_range)
|
|
return WERROR_MISSILE_OUT_OF_RANGE;
|
|
}
|
|
return WERROR_MAGIC_BAD_TARGET_TYPE;
|
|
}
|
|
|
|
}
|
|
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
int CSpellcastingManager::GenerateManaCost()
|
|
{
|
|
DWORD manaConvSkill = m_pWeenie->GetEffectiveManaConversionSkill();
|
|
|
|
int spellLevel = 0;
|
|
int scarab = m_SpellCastData.spell_formula._comps[0];
|
|
switch (scarab)
|
|
{
|
|
case 110: spellLevel = 6; break;
|
|
case 112: spellLevel = 7; break;
|
|
case 193: spellLevel = 8; break;
|
|
default:
|
|
{
|
|
if (scarab <= 110 && scarab > 0)
|
|
{
|
|
spellLevel = scarab;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
spellLevel = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int difficulty = 50 + (25 * (spellLevel - 1));
|
|
|
|
return GetManaCost(m_SpellCastData.current_skill, difficulty, m_SpellCastData.spell->_base_mana, manaConvSkill);
|
|
}
|
|
|
|
int CSpellcastingManager::TryBeginCast(DWORD target_id, DWORD spell_id)
|
|
{
|
|
if (m_bCasting)
|
|
{
|
|
// m_pWeenie->SendText(csprintf("DEBUG: Actions Locked, Casting = true, Turning = %s", m_bTurningToObject ? "true" : "false"), LTT_ALL_CHANNELS);
|
|
return WERROR_ACTIONS_LOCKED;
|
|
}
|
|
if (m_pWeenie->IsInPortalSpace())
|
|
return WERROR_ACTIONS_LOCKED;
|
|
|
|
if (m_pWeenie->get_minterp()->interpreted_state.actions.size())
|
|
{
|
|
// m_pWeenie->SendText(csprintf("DEBUG: Actions Locked, Interp state has %d actions.", m_pWeenie->get_minterp()->interpreted_state.actions.size()), LTT_ALL_CHANNELS);
|
|
return WERROR_ACTIONS_LOCKED;
|
|
}
|
|
|
|
if (m_pWeenie->get_minterp()->interpreted_state.forward_command != 0x41000003)
|
|
{
|
|
// m_pWeenie->SendText(csprintf("DEBUG: Unprepared, Interp state forward command is 0x%08X, not idle.", m_pWeenie->get_minterp()->interpreted_state.forward_command), LTT_ALL_CHANNELS);
|
|
return WERROR_MAGIC_UNPREPARED;
|
|
}
|
|
|
|
if (!m_pWeenie->m_Qualities.IsSpellKnown(spell_id))
|
|
{
|
|
// Don't return the unlearned spell error for Cast on Strike
|
|
if (!(m_pWeenie->AsMeleeWeapon() || m_pWeenie->AsMissileLauncher()) && spell_id != m_pWeenie->InqDIDQuality(PROC_SPELL_DID, 0))
|
|
{
|
|
return WERROR_MAGIC_UNLEARNED_SPELL;
|
|
}
|
|
}
|
|
|
|
if (Timer::cur_time < m_fNextCastTime)
|
|
{
|
|
// m_pWeenie->SendText(csprintf("DEBUG: Actions Locked, Can't cast for %.3f more seconds.", Timer::cur_time - m_fNextCastTime), LTT_ALL_CHANNELS);
|
|
return WERROR_ACTIONS_LOCKED;
|
|
}
|
|
|
|
m_SpellCastData = SpellCastData(); // reset
|
|
m_SpellCastData.caster_id = m_pWeenie->GetID();
|
|
m_SpellCastData.source_id = m_pWeenie->GetTopLevelID();
|
|
m_SpellCastData.target_id = target_id;
|
|
m_SpellCastData.spell_id = spell_id;
|
|
m_SpellCastData.wand_id = m_pWeenie->GetWieldedCasterID();
|
|
m_SpellCastData.cast_timeout = Timer::cur_time + 10.0f;
|
|
m_SpellCastData.initial_cast_position = m_pWeenie->m_Position;
|
|
|
|
if (!ResolveSpellBeingCasted())
|
|
{
|
|
SERVER_INFO << "Player trying to cast unknown spell?";
|
|
return WERROR_MAGIC_GENERAL_FAILURE;
|
|
}
|
|
|
|
if (!m_SpellCastData.current_skill)
|
|
{
|
|
m_pWeenie->SendText("You are not trained in that skill!", LTT_MAGIC);
|
|
return WERROR_MAGIC_INVALID_SPELL_TYPE;
|
|
}
|
|
|
|
// m_pWeenie->SendText(csprintf("Casting %d", m_SpellCastData.spell_id), LTT_DEFAULT);
|
|
|
|
int targetError = CheckTargetValidity();
|
|
if (targetError)
|
|
{
|
|
CWeenieObject * pTarget = g_pWorld->FindObject(m_SpellCastData.target_id);
|
|
switch (targetError)
|
|
{
|
|
case WERROR_PK_PROTECTED_TARGET:
|
|
{
|
|
if (pTarget)
|
|
{
|
|
auto targetName = pTarget->GetName();
|
|
pTarget->SendText(csprintf("%s fails to affect you because you cannot be harmed!", m_pWeenie->GetName().c_str()), LTT_MAGIC);
|
|
m_pWeenie->SendText(csprintf("You fail to affect %s because %s cannot be harmed!", targetName.c_str(), targetName.c_str()), LTT_MAGIC);
|
|
}
|
|
return WERROR_NONE;
|
|
}
|
|
case WERROR_MAGIC_BAD_TARGET_TYPE:
|
|
{
|
|
if (pTarget)
|
|
{
|
|
m_pWeenie->EmitSound(Sound_UI_GeneralError, 1.0f, true);
|
|
m_pWeenie->NotifyWeenieErrorWithString(WERROR_MAGIC_BAD_TARGET_TYPE, pTarget->GetName().c_str());
|
|
}
|
|
return WERROR_NONE;
|
|
}
|
|
}
|
|
|
|
return targetError;
|
|
}
|
|
|
|
//Components
|
|
CContainerWeenie *caster = m_pWeenie->AsContainer();
|
|
if (caster != NULL && caster->InqBoolQuality(SPELL_COMPONENTS_REQUIRED_BOOL, FALSE) == TRUE)
|
|
{
|
|
bool foci;
|
|
|
|
if (g_pConfig->IsSpellFociEnabled())
|
|
{
|
|
switch (m_SpellCastData.spell->InqSkillForSpell())
|
|
{
|
|
case CREATURE_ENCHANTMENT_SKILL: foci = FindFociInContainer(caster, W_PACKCREATUREESSENCE_CLASS) || caster->InqIntQuality(AUGMENTATION_INFUSED_CREATURE_MAGIC_INT, 0); break;
|
|
case ITEM_ENCHANTMENT_SKILL: foci = FindFociInContainer(caster, W_PACKITEMESSENCE_CLASS) || caster->InqIntQuality(AUGMENTATION_INFUSED_ITEM_MAGIC_INT, 0); break;
|
|
case LIFE_MAGIC_SKILL: foci = FindFociInContainer(caster, W_PACKLIFEESSENCE_CLASS) || caster->InqIntQuality(AUGMENTATION_INFUSED_LIFE_MAGIC_INT, 0); break;
|
|
case WAR_MAGIC_SKILL: foci = FindFociInContainer(caster, W_PACKWARESSENCE_CLASS) || caster->InqIntQuality(AUGMENTATION_INFUSED_WAR_MAGIC_INT, 0); break;
|
|
case VOID_MAGIC_SKILL: foci = FindFociInContainer(caster, W_FOCIOFSHADOW_CLASS) || caster->InqIntQuality(AUGMENTATION_INFUSED_VOID_MAGIC_INT, 0); break;
|
|
}
|
|
}
|
|
|
|
SpellFormula randomizedComponents;
|
|
randomizedComponents.CopyFrom(m_SpellCastData.spell->InqSpellFormula());
|
|
|
|
CPlayerWeenie *player = m_pWeenie->AsPlayer();
|
|
if (player)
|
|
randomizedComponents.RandomizeForName(player->GetClient()->GetAccount(), m_SpellCastData.spell->_formula_version);
|
|
|
|
std::map<DWORD, DWORD> componentAmounts;
|
|
for each (DWORD componentId in randomizedComponents._comps)
|
|
{
|
|
if (componentId == 0)
|
|
continue;
|
|
|
|
if (foci != FALSE)
|
|
{
|
|
SpellComponentTable *pSpellComponents = MagicSystem::GetSpellComponentTable();
|
|
const SpellComponentBase *componentBase = pSpellComponents->InqSpellComponentBase(componentId);
|
|
switch (componentBase->_type)
|
|
{
|
|
case SpellComponentType::Power_SpellComponentType: //scarabs
|
|
break;
|
|
case SpellComponentType::Accent_SpellComponentType: //tapers
|
|
componentId = 188; //turn tapers into prismatic tapers.
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
}
|
|
|
|
componentAmounts[componentId]++;
|
|
}
|
|
if (foci != FALSE)
|
|
componentAmounts[188]++; //if using foci, add one more prismatic taper.
|
|
|
|
m_UsedComponents.clear(); // clear the list of left overs from previous interrupted spell attempts.
|
|
for (std::map<DWORD, DWORD>::iterator iter = componentAmounts.begin(); iter != componentAmounts.end(); ++iter)
|
|
{
|
|
DWORD compId = iter->first;
|
|
DWORD amount = iter->second;
|
|
|
|
std::map<DWORD, DWORD> components = FindComponentInContainer(caster, compId, amount);
|
|
|
|
if (!components.empty())
|
|
{
|
|
for (std::map<DWORD, DWORD>::iterator iter = components.begin(); iter != components.end(); ++iter)
|
|
m_UsedComponents[iter->first] += iter->second;
|
|
}
|
|
else
|
|
return WERROR_MAGIC_MISSING_COMPONENTS;
|
|
}
|
|
}
|
|
|
|
if (!(m_pWeenie->AsMeleeWeapon() || m_pWeenie->AsMissileLauncher()))
|
|
{
|
|
if (m_pWeenie->GetMana() < GenerateManaCost())
|
|
return WERROR_MAGIC_INSUFFICIENT_MANA;
|
|
}
|
|
else if (m_pWeenie->InqIntQuality(ITEM_CUR_MANA_INT, 0, 0) < GenerateManaCost())
|
|
return WERROR_MAGIC_INSUFFICIENT_MANA;
|
|
else
|
|
{
|
|
m_bCasting = true;
|
|
m_SpellCastData.current_skill = m_pWeenie->InqIntQuality(ITEM_SPELLCRAFT_INT, 0, 0);
|
|
int error = LaunchSpellEffect(FALSE);
|
|
EndCast(error);
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
if(m_pWeenie->AsPlayer())
|
|
m_pWeenie->AsPlayer()->CancelLifestoneProtection();
|
|
|
|
BeginCast();
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
std::map<DWORD, DWORD> CSpellcastingManager::FindComponentInContainer(CContainerWeenie *container, unsigned int componentId, int amountNeeded)
|
|
{
|
|
std::map<DWORD, DWORD> foundItems;
|
|
int amountLeftToFind = amountNeeded;
|
|
for (auto item : container->m_Items)
|
|
{
|
|
if (item->InqDIDQuality(SPELL_COMPONENT_DID, 0) == componentId)
|
|
{
|
|
int amount = item->InqIntQuality(STACK_SIZE_INT, 1);
|
|
if (amount > amountLeftToFind)
|
|
{
|
|
amount = amountLeftToFind;
|
|
amountLeftToFind = 0;
|
|
}
|
|
else
|
|
amountLeftToFind -= amount;
|
|
foundItems.emplace(item->GetID(), amount);
|
|
|
|
if (amountLeftToFind == 0)
|
|
return foundItems;
|
|
}
|
|
}
|
|
|
|
for (auto packSlot : container->m_Packs)
|
|
{
|
|
CContainerWeenie *pack = packSlot->AsContainer();
|
|
if (pack != NULL)
|
|
{
|
|
for (auto item : pack->m_Items)
|
|
{
|
|
if (item->InqDIDQuality(SPELL_COMPONENT_DID, 0) == componentId)
|
|
{
|
|
int amount = item->InqIntQuality(STACK_SIZE_INT, 1);
|
|
if (amount > amountLeftToFind)
|
|
{
|
|
amount = amountLeftToFind;
|
|
amountLeftToFind = 0;
|
|
}
|
|
else
|
|
amountLeftToFind -= amount;
|
|
foundItems.emplace(item->GetID(), amount);
|
|
|
|
if (amountLeftToFind == 0)
|
|
return foundItems;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//we didnt find everything, so empty our list and return
|
|
foundItems.clear();
|
|
return foundItems;
|
|
}
|
|
|
|
CWeenieObject *CSpellcastingManager::FindFociInContainer(CContainerWeenie *container, DWORD fociWcid)
|
|
{
|
|
for (auto pack : container->m_Packs)
|
|
{
|
|
if (pack->m_Qualities.id == fociWcid)
|
|
return pack;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void CSpellcastingManager::Update()
|
|
{
|
|
if (!m_bCasting)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (m_SpellCastData.cast_timeout <= Timer::cur_time)
|
|
{
|
|
EndCast(WERROR_MAGIC_GENERAL_FAILURE);
|
|
m_bCasting = false;
|
|
}
|
|
|
|
if (m_pWeenie->AsPlayer() && m_pWeenie->IsInPeaceMode())
|
|
{
|
|
// fizzle if you're no longer in combat mode.
|
|
m_pWeenie->EmitEffect(PS_Fizzle, 0.542734265f);
|
|
m_pWeenie->AdjustMana(-5);
|
|
|
|
int error = LaunchSpellEffect(TRUE);
|
|
EndCast(error);
|
|
}
|
|
|
|
if ((m_SpellCastData.next_update <= Timer::cur_time) && m_bTurned && m_pWeenie->m_Position.distance(m_SpellCastData.initial_cast_position) >= 6.0 && m_pWeenie->m_Qualities.GetInt(PLAYER_KILLER_STATUS_INT, 0) == PK_PKStatus)
|
|
{
|
|
// fizzle
|
|
m_pWeenie->EmitEffect(PS_Fizzle, 0.542734265f);
|
|
m_pWeenie->AdjustMana(-5);
|
|
m_pWeenie->SendText("Your movement disrupted spell casting!", LTT_MAGIC);
|
|
m_pWeenie->SendText("Your movement disrupted spell casting!", LTT_ERROR);
|
|
|
|
int error = LaunchSpellEffect(TRUE);
|
|
EndCast(error);
|
|
}
|
|
|
|
if (m_bTurningToObject)
|
|
{
|
|
if (m_pWeenie->movement_manager->moveto_manager->movement_type != MovementTypes::TurnToObject)
|
|
{
|
|
if (HeadingToTarget() <= MAX_HEADING_TO_TARGET_FOR_CAST)
|
|
{
|
|
// if (!m_pWeenie->get_minterp()->interpreted_state.turn_command)
|
|
{
|
|
BeginNextMotion();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((m_SpellCastData.next_update <= Timer::cur_time) && !m_pWeenie->get_minterp()->interpreted_state.turn_command)
|
|
{
|
|
MovementParameters params;
|
|
params.speed = 1.0f;
|
|
params.action_stamp = ++m_pWeenie->m_wAnimSequence;
|
|
m_pWeenie->last_move_was_autonomous = false;
|
|
m_pWeenie->TurnToObject(m_SpellCastData.target_id, ¶ms);
|
|
m_bTurned = true;
|
|
m_SpellCastData.next_update = Timer::cur_time + CAST_UPDATE_RATE;
|
|
//m_bTurningToObject = true;
|
|
//m_fCastTimeout = Timer::cur_time + MAX_TURN_TIME_FOR_CAST;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
CWeenieObject *pTarget = g_pWorld->FindWithinPVS(m_pWeenie, m_TargetID);
|
|
if (pTarget)
|
|
{
|
|
LOG(Temp, Normal, "%f (%f %f) %08X %f %f %f\n",
|
|
HeadingToTarget(),
|
|
m_pWeenie->m_Position.heading(pTarget->m_Position),
|
|
m_pWeenie->m_Position.frame.get_heading(),
|
|
m_pWeenie->get_minterp()->interpreted_state.turn_command,
|
|
m_pWeenie->movement_manager->moveto_manager->sought_position.frame.get_heading(),
|
|
m_pWeenie->get_heading(),
|
|
m_pWeenie->get_heading() - m_pWeenie->movement_manager->moveto_manager->sought_position.frame.get_heading());
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
|
|
void CSpellcastingManager::Cancel()
|
|
{
|
|
if (!m_bCasting)
|
|
{
|
|
return;
|
|
}
|
|
|
|
EndCast(WErrorType::WERROR_MAGIC_UNPREPARED);
|
|
m_bCasting = false;
|
|
}
|
|
|
|
void CSpellcastingManager::OnDeath(DWORD killer_id)
|
|
{
|
|
Cancel();
|
|
}
|
|
|
|
void CSpellcastingManager::HandleMotionDone(DWORD motion, BOOL success)
|
|
{
|
|
if (!m_bCasting)
|
|
return;
|
|
|
|
if (m_bTurningToObject)
|
|
return;
|
|
|
|
if (m_PendingMotions.empty() || m_PendingMotions.begin()->motion != motion)
|
|
return;
|
|
|
|
if (!success)
|
|
{
|
|
EndCast(WERROR_MAGIC_GENERAL_FAILURE);
|
|
return;
|
|
}
|
|
|
|
m_PendingMotions.pop_front();
|
|
BeginNextMotion();
|
|
}
|