mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-13 18:51:29 +00:00
Merge pull request #319 from KayenEQ/Development
NPC ranged attack projectiles will now do damage on impact.
This commit is contained in:
commit
4f242f01c1
@ -155,6 +155,7 @@ public:
|
|||||||
virtual void RangedAttack(Mob* other);
|
virtual void RangedAttack(Mob* other);
|
||||||
virtual void ThrowingAttack(Mob* other) { }
|
virtual void ThrowingAttack(Mob* other) { }
|
||||||
int32 GetNumberOfAttacks() const { return attack_count; }
|
int32 GetNumberOfAttacks() const { return attack_count; }
|
||||||
|
void DoRangedAttackDmg(Mob* other, bool Launch=true, int16 damage_mod=0, int16 chance_mod=0, SkillUseTypes skill=SkillArchery, float speed=4.0f, const char *IDFile = nullptr);
|
||||||
|
|
||||||
bool DatabaseCastAccepted(int spell_id);
|
bool DatabaseCastAccepted(int spell_id);
|
||||||
bool IsFactionListAlly(uint32 other_faction);
|
bool IsFactionListAlly(uint32 other_faction);
|
||||||
|
|||||||
@ -1062,8 +1062,8 @@ bool Mob::TryProjectileAttack(Mob* other, const Item_Struct *item, SkillUseTypes
|
|||||||
|
|
||||||
if(item)
|
if(item)
|
||||||
SendItemAnimation(other, item, skillInUse, speed);
|
SendItemAnimation(other, item, skillInUse, speed);
|
||||||
else if (IsNPC())
|
//else if (IsNPC())
|
||||||
ProjectileAnimation(other, 0,false,speed,0,0,0,CastToNPC()->GetAmmoIDfile(),skillInUse);
|
//ProjectileAnimation(other, 0,false,speed,0,0,0,CastToNPC()->GetAmmoIDfile(),skillInUse);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1100,12 +1100,19 @@ void Mob::ProjectileAttack()
|
|||||||
if (ProjectileAtk[i].hit_increment <= ProjectileAtk[i].increment){
|
if (ProjectileAtk[i].hit_increment <= ProjectileAtk[i].increment){
|
||||||
|
|
||||||
if (target){
|
if (target){
|
||||||
if (ProjectileAtk[i].skill == SkillArchery)
|
|
||||||
DoArcheryAttackDmg(target, nullptr, nullptr,ProjectileAtk[i].wpn_dmg,0,0,0,ProjectileAtk[i].ranged_id, ProjectileAtk[i].ammo_id, nullptr, ProjectileAtk[i].ammo_slot);
|
if (IsNPC())
|
||||||
else if (ProjectileAtk[i].skill == SkillThrowing)
|
CastToNPC()->DoRangedAttackDmg(target, false, ProjectileAtk[i].wpn_dmg,0, static_cast<SkillUseTypes>(ProjectileAtk[i].skill));
|
||||||
DoThrowingAttackDmg(target, nullptr, nullptr,ProjectileAtk[i].wpn_dmg,0,0,0, ProjectileAtk[i].ranged_id, ProjectileAtk[i].ammo_slot);
|
|
||||||
else if (ProjectileAtk[i].skill == SkillConjuration && IsValidSpell(ProjectileAtk[i].wpn_dmg))
|
else
|
||||||
SpellOnTarget(ProjectileAtk[i].wpn_dmg, target, false, true, spells[ProjectileAtk[i].wpn_dmg].ResistDiff, true);
|
{
|
||||||
|
if (ProjectileAtk[i].skill == SkillArchery)
|
||||||
|
DoArcheryAttackDmg(target, nullptr, nullptr,ProjectileAtk[i].wpn_dmg,0,0,0,ProjectileAtk[i].ranged_id, ProjectileAtk[i].ammo_id, nullptr, ProjectileAtk[i].ammo_slot);
|
||||||
|
else if (ProjectileAtk[i].skill == SkillThrowing)
|
||||||
|
DoThrowingAttackDmg(target, nullptr, nullptr,ProjectileAtk[i].wpn_dmg,0,0,0, ProjectileAtk[i].ranged_id, ProjectileAtk[i].ammo_slot);
|
||||||
|
else if (ProjectileAtk[i].skill == SkillConjuration && IsValidSpell(ProjectileAtk[i].wpn_dmg))
|
||||||
|
SpellOnTarget(ProjectileAtk[i].wpn_dmg, target, false, true, spells[ProjectileAtk[i].wpn_dmg].ResistDiff, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectileAtk[i].increment = 0;
|
ProjectileAtk[i].increment = 0;
|
||||||
@ -1190,14 +1197,8 @@ void NPC::RangedAttack(Mob* other)
|
|||||||
attacks = attacks > 0 ? attacks : 1;
|
attacks = attacks > 0 ? attacks : 1;
|
||||||
for(int i = 0; i < attacks; ++i) {
|
for(int i = 0; i < attacks; ++i) {
|
||||||
|
|
||||||
//if we have SPECATK_RANGED_ATK set then we range attack without weapon or ammo
|
|
||||||
const Item_Struct* weapon = nullptr;
|
|
||||||
const Item_Struct* ammo = nullptr;
|
|
||||||
if(!GetSpecialAbility(SPECATK_RANGED_ATK))
|
if(!GetSpecialAbility(SPECATK_RANGED_ATK))
|
||||||
{
|
|
||||||
//find our bow and ammo return if we can't find them...
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
int sa_min_range = GetSpecialAbilityParam(SPECATK_RANGED_ATK, 4); //Min Range of NPC attack
|
int sa_min_range = GetSpecialAbilityParam(SPECATK_RANGED_ATK, 4); //Min Range of NPC attack
|
||||||
int sa_max_range = GetSpecialAbilityParam(SPECATK_RANGED_ATK, 1); //Max Range of NPC attack
|
int sa_max_range = GetSpecialAbilityParam(SPECATK_RANGED_ATK, 1); //Max Range of NPC attack
|
||||||
@ -1211,17 +1212,12 @@ void NPC::RangedAttack(Mob* other)
|
|||||||
if (sa_min_range)
|
if (sa_min_range)
|
||||||
min_range = static_cast<float>(sa_min_range);
|
min_range = static_cast<float>(sa_min_range);
|
||||||
|
|
||||||
mlog(COMBAT__RANGED, "Calculated bow range to be %.1f", max_range);
|
|
||||||
max_range *= max_range;
|
max_range *= max_range;
|
||||||
if(DistNoRootNoZ(*other) > max_range) {
|
if(DistNoRoot(*other) > max_range)
|
||||||
mlog(COMBAT__RANGED, "Ranged attack out of range...%.2f vs %.2f", DistNoRootNoZ(*other), max_range);
|
|
||||||
//target is out of range, client does a message
|
|
||||||
return;
|
return;
|
||||||
}
|
else if(DistNoRoot(*other) < (min_range * min_range))
|
||||||
else if(DistNoRootNoZ(*other) < (min_range * min_range))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
||||||
if(!other || !IsAttackAllowed(other) ||
|
if(!other || !IsAttackAllowed(other) ||
|
||||||
IsCasting() ||
|
IsCasting() ||
|
||||||
DivineAura() ||
|
DivineAura() ||
|
||||||
@ -1232,74 +1228,100 @@ void NPC::RangedAttack(Mob* other)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SkillUseTypes skillinuse = SkillArchery;
|
|
||||||
skillinuse = static_cast<SkillUseTypes>(GetRangedSkill());
|
|
||||||
|
|
||||||
if(!ammo && !GetAmmoIDfile())
|
|
||||||
ammo = database.GetItem(8005);
|
|
||||||
|
|
||||||
if(ammo)
|
|
||||||
SendItemAnimation(other, ammo, SkillArchery);
|
|
||||||
else
|
|
||||||
ProjectileAnimation(other, 0,false,0,0,0,0,GetAmmoIDfile(),skillinuse);
|
|
||||||
|
|
||||||
FaceTarget(other);
|
FaceTarget(other);
|
||||||
|
|
||||||
if (!other->CheckHitChance(this, skillinuse, MainRange, GetSpecialAbilityParam(SPECATK_RANGED_ATK, 2)))
|
DoRangedAttackDmg(other);
|
||||||
{
|
|
||||||
mlog(COMBAT__RANGED, "Ranged attack missed %s.", other->GetName());
|
|
||||||
other->Damage(this, 0, SPELL_UNKNOWN, skillinuse);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int16 WDmg = GetWeaponDamage(other, weapon);
|
|
||||||
int16 ADmg = GetWeaponDamage(other, ammo);
|
|
||||||
int32 TotalDmg = 0;
|
|
||||||
if(WDmg > 0 || ADmg > 0)
|
|
||||||
{
|
|
||||||
mlog(COMBAT__RANGED, "Ranged attack hit %s.", other->GetName());
|
|
||||||
|
|
||||||
int32 MaxDmg = max_dmg * RuleR(Combat, ArcheryNPCMultiplier); // should add a field to npc_types
|
|
||||||
int32 MinDmg = min_dmg * RuleR(Combat, ArcheryNPCMultiplier);
|
|
||||||
|
|
||||||
if(RuleB(Combat, UseIntervalAC))
|
|
||||||
TotalDmg = MaxDmg;
|
|
||||||
else
|
|
||||||
TotalDmg = zone->random.Int(MinDmg, MaxDmg);
|
|
||||||
|
|
||||||
TotalDmg += TotalDmg * GetSpecialAbilityParam(SPECATK_RANGED_ATK, 3) / 100; //Damage modifier
|
|
||||||
|
|
||||||
other->AvoidDamage(this, TotalDmg, false);
|
|
||||||
other->MeleeMitigation(this, TotalDmg, MinDmg);
|
|
||||||
if (TotalDmg > 0)
|
|
||||||
CommonOutgoingHitSuccess(other, TotalDmg, skillinuse);
|
|
||||||
}
|
|
||||||
|
|
||||||
else
|
|
||||||
TotalDmg = -5;
|
|
||||||
|
|
||||||
if (TotalDmg > 0)
|
|
||||||
other->AddToHateList(this, TotalDmg, 0, false);
|
|
||||||
else
|
|
||||||
other->AddToHateList(this, 0, 0, false);
|
|
||||||
|
|
||||||
other->Damage(this, TotalDmg, SPELL_UNKNOWN, skillinuse);
|
|
||||||
|
|
||||||
if (TotalDmg > 0 && HasSkillProcSuccess() && GetTarget() && !other->HasDied())
|
|
||||||
TrySkillProc(other, skillinuse, 0, true, MainRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
//try proc on hits and misses
|
|
||||||
if(other && !other->HasDied())
|
|
||||||
TrySpellProc(nullptr, (const Item_Struct*)nullptr, other, MainRange);
|
|
||||||
|
|
||||||
if (HasSkillProcs() && other && !other->HasDied())
|
|
||||||
TrySkillProc(other, skillinuse, 0, false, MainRange);
|
|
||||||
|
|
||||||
CommonBreakInvisible();
|
CommonBreakInvisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 chance_mod, SkillUseTypes skill, float speed, const char *IDFile) {
|
||||||
|
|
||||||
|
if ((other == nullptr ||
|
||||||
|
(other->HasDied())) ||
|
||||||
|
HasDied() ||
|
||||||
|
(!IsAttackAllowed(other)) ||
|
||||||
|
(other->GetInvul() ||
|
||||||
|
other->GetSpecialAbility(IMMUNE_MELEE)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SkillUseTypes skillInUse = static_cast<SkillUseTypes>(GetRangedSkill());
|
||||||
|
|
||||||
|
if (skill != skillInUse)
|
||||||
|
skillInUse = skill;
|
||||||
|
|
||||||
|
if (Launch)
|
||||||
|
{
|
||||||
|
const char *ammo = "IT10";
|
||||||
|
|
||||||
|
if (IDFile != nullptr)
|
||||||
|
ammo = IDFile;
|
||||||
|
else if (GetAmmoIDfile())
|
||||||
|
ammo = GetAmmoIDfile();
|
||||||
|
|
||||||
|
ProjectileAnimation(other, 0,false,speed,0,0,0,ammo,skillInUse);
|
||||||
|
|
||||||
|
if (RuleB(Combat, ProjectileDmgOnImpact))
|
||||||
|
{
|
||||||
|
TryProjectileAttack(other, nullptr, skillInUse, damage_mod, nullptr, nullptr, 0, speed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!chance_mod)
|
||||||
|
chance_mod = GetSpecialAbilityParam(SPECATK_RANGED_ATK, 2);
|
||||||
|
|
||||||
|
if (!other->CheckHitChance(this, skillInUse, MainRange, chance_mod))
|
||||||
|
{
|
||||||
|
other->Damage(this, 0, SPELL_UNKNOWN, skillInUse);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int32 TotalDmg = 0;
|
||||||
|
int32 MaxDmg = max_dmg * RuleR(Combat, ArcheryNPCMultiplier); // should add a field to npc_types
|
||||||
|
int32 MinDmg = min_dmg * RuleR(Combat, ArcheryNPCMultiplier);
|
||||||
|
|
||||||
|
if(RuleB(Combat, UseIntervalAC))
|
||||||
|
TotalDmg = MaxDmg;
|
||||||
|
else
|
||||||
|
TotalDmg = zone->random.Int(MinDmg, MaxDmg);
|
||||||
|
|
||||||
|
|
||||||
|
if (!damage_mod)
|
||||||
|
damage_mod = GetSpecialAbilityParam(SPECATK_RANGED_ATK, 3);//Damage modifier
|
||||||
|
|
||||||
|
TotalDmg += TotalDmg * damage_mod / 100;
|
||||||
|
|
||||||
|
other->AvoidDamage(this, TotalDmg, false);
|
||||||
|
other->MeleeMitigation(this, TotalDmg, MinDmg);
|
||||||
|
|
||||||
|
if (TotalDmg > 0)
|
||||||
|
CommonOutgoingHitSuccess(other, TotalDmg, skillInUse);
|
||||||
|
else
|
||||||
|
TotalDmg = -5;
|
||||||
|
|
||||||
|
if (TotalDmg > 0)
|
||||||
|
other->AddToHateList(this, TotalDmg, 0, false);
|
||||||
|
else
|
||||||
|
other->AddToHateList(this, 0, 0, false);
|
||||||
|
|
||||||
|
other->Damage(this, TotalDmg, SPELL_UNKNOWN, skillInUse);
|
||||||
|
|
||||||
|
if (TotalDmg > 0 && HasSkillProcSuccess() && !other->HasDied())
|
||||||
|
TrySkillProc(other, skillInUse, 0, true, MainRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
//try proc on hits and misses
|
||||||
|
if(other && !other->HasDied())
|
||||||
|
TrySpellProc(nullptr, (const Item_Struct*)nullptr, other, MainRange);
|
||||||
|
|
||||||
|
if (HasSkillProcs() && other && !other->HasDied())
|
||||||
|
TrySkillProc(other, skillInUse, 0, false, MainRange);
|
||||||
|
}
|
||||||
|
|
||||||
uint16 Mob::GetThrownDamage(int16 wDmg, int32& TotalDmg, int& minDmg) {
|
uint16 Mob::GetThrownDamage(int16 wDmg, int32& TotalDmg, int& minDmg) {
|
||||||
uint16 MaxDmg = (((2 * wDmg) * GetDamageTable(SkillThrowing)) / 100);
|
uint16 MaxDmg = (((2 * wDmg) * GetDamageTable(SkillThrowing)) / 100);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user