mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-21 22:41:29 +00:00
Merge pull request #304 from KayenEQ/Development
Implemented archery projectiles to do damage on impact.
This commit is contained in:
commit
a59cdc2c89
@ -1,10 +1,15 @@
|
|||||||
EQEMu Changelog (Started on Sept 24, 2003 15:50)
|
EQEMu Changelog (Started on Sept 24, 2003 15:50)
|
||||||
-------------------------------------------------------
|
-------------------------------------------------------
|
||||||
|
|
||||||
== 11/28/2014 ==
|
== 11/28/2014 ==
|
||||||
Trevius: Fixed a zone crash related to numhits for spells.
|
Trevius: Fixed a zone crash related to numhits for spells.
|
||||||
Trevius: Fixed a query related to group leaders logging in.
|
Trevius: Fixed a query related to group leaders logging in.
|
||||||
Trevius (Natedog): Fixed a world crash related to attempting to join an adventure with Mercenaries.
|
Trevius (Natedog): Fixed a world crash related to attempting to join an adventure with Mercenaries.
|
||||||
|
|
||||||
|
== 11/27/2014 ==
|
||||||
|
Kayen: Projectiles (ie Arrows) fired from archery will now do damage upon impact instead of instantly (consistent w/ live).
|
||||||
|
Optional SQL: utils/sql/git/optional/2014_11_27_ProjectileDmgOnImpact.sql
|
||||||
|
|
||||||
== 11/25/2014 ==
|
== 11/25/2014 ==
|
||||||
Trevius: Spells that modify model size are now limited to 2 size adjustments from the base size.
|
Trevius: Spells that modify model size are now limited to 2 size adjustments from the base size.
|
||||||
Trevius: Fix to prevent Mercenaries from being set as Group Leader.
|
Trevius: Fix to prevent Mercenaries from being set as Group Leader.
|
||||||
|
|||||||
@ -419,6 +419,7 @@ RULE_INT ( Combat, ArcheryBonusChance, 50)
|
|||||||
RULE_INT ( Combat, BerserkerFrenzyStart, 35)
|
RULE_INT ( Combat, BerserkerFrenzyStart, 35)
|
||||||
RULE_INT ( Combat, BerserkerFrenzyEnd, 45)
|
RULE_INT ( Combat, BerserkerFrenzyEnd, 45)
|
||||||
RULE_BOOL ( Combat, OneProcPerWeapon, true) //If enabled, One proc per weapon per round
|
RULE_BOOL ( Combat, OneProcPerWeapon, true) //If enabled, One proc per weapon per round
|
||||||
|
RULE_BOOL ( Combat, ProjectileDmgOnImpact, true) //If enabled, projectiles (ie arrows) will hit on impact, instead of instantly.
|
||||||
RULE_CATEGORY_END()
|
RULE_CATEGORY_END()
|
||||||
|
|
||||||
RULE_CATEGORY( NPC )
|
RULE_CATEGORY( NPC )
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Combat:ProjectileDmgOnImpact', 'true', 'If enabled, projectiles (ie arrows) will hit on impact, instead of instantly.');
|
||||||
@ -565,8 +565,7 @@ bool Client::Process() {
|
|||||||
viral_timer_counter = 0;
|
viral_timer_counter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(projectile_timer.Check())
|
ProjectileAttack();
|
||||||
SpellProjectileEffect();
|
|
||||||
|
|
||||||
if(spellbonuses.GravityEffect == 1) {
|
if(spellbonuses.GravityEffect == 1) {
|
||||||
if(gravity_timer.Check())
|
if(gravity_timer.Check())
|
||||||
|
|||||||
@ -459,6 +459,24 @@ struct Shielders_Struct {
|
|||||||
uint16 shielder_bonus;
|
uint16 shielder_bonus;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint16 increment;
|
||||||
|
uint16 hit_increment;
|
||||||
|
uint16 target_id;
|
||||||
|
int32 wpn_dmg;
|
||||||
|
float origin_x;
|
||||||
|
float origin_y;
|
||||||
|
float origin_z;
|
||||||
|
float tlast_x;
|
||||||
|
float tlast_y;
|
||||||
|
uint32 ranged_id;
|
||||||
|
uint32 ammo_id;
|
||||||
|
int ammo_slot;
|
||||||
|
uint8 skill;
|
||||||
|
float speed_mod;
|
||||||
|
} tProjatk;
|
||||||
|
|
||||||
//eventually turn this into a typedef and
|
//eventually turn this into a typedef and
|
||||||
//make DoAnim take it instead of int, to enforce its use.
|
//make DoAnim take it instead of int, to enforce its use.
|
||||||
enum { //type arguments to DoAnim
|
enum { //type arguments to DoAnim
|
||||||
|
|||||||
68
zone/mob.cpp
68
zone/mob.cpp
@ -279,13 +279,24 @@ Mob::Mob(const char* in_name,
|
|||||||
casting_spell_inventory_slot = 0;
|
casting_spell_inventory_slot = 0;
|
||||||
target = 0;
|
target = 0;
|
||||||
|
|
||||||
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_spell_id[i] = 0; }
|
ActiveProjectileATK = false;
|
||||||
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_target_id[i] = 0; }
|
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++)
|
||||||
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_increment[i] = 0; }
|
{
|
||||||
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_x[i] = 0; }
|
ProjectileAtk[i].increment = 0;
|
||||||
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_y[i] = 0; }
|
ProjectileAtk[i].hit_increment = 0;
|
||||||
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_z[i] = 0; }
|
ProjectileAtk[i].target_id = 0;
|
||||||
projectile_timer.Disable();
|
ProjectileAtk[i].wpn_dmg = 0;
|
||||||
|
ProjectileAtk[i].origin_x = 0.0f;
|
||||||
|
ProjectileAtk[i].origin_y = 0.0f;
|
||||||
|
ProjectileAtk[i].origin_z = 0.0f;
|
||||||
|
ProjectileAtk[i].tlast_x = 0.0f;
|
||||||
|
ProjectileAtk[i].tlast_y = 0.0f;
|
||||||
|
ProjectileAtk[i].ranged_id = 0;
|
||||||
|
ProjectileAtk[i].ammo_id = 0;
|
||||||
|
ProjectileAtk[i].ammo_slot = 0;
|
||||||
|
ProjectileAtk[i].skill = 0;
|
||||||
|
ProjectileAtk[i].speed_mod = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
memset(&itembonuses, 0, sizeof(StatBonuses));
|
memset(&itembonuses, 0, sizeof(StatBonuses));
|
||||||
memset(&spellbonuses, 0, sizeof(StatBonuses));
|
memset(&spellbonuses, 0, sizeof(StatBonuses));
|
||||||
@ -4235,49 +4246,6 @@ bool Mob::TryReflectSpell(uint32 spell_id)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mob::SpellProjectileEffect()
|
|
||||||
{
|
|
||||||
bool time_disable = false;
|
|
||||||
|
|
||||||
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) {
|
|
||||||
|
|
||||||
if (projectile_increment[i] == 0){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Mob* target = entity_list.GetMobID(projectile_target_id[i]);
|
|
||||||
|
|
||||||
float dist = 0;
|
|
||||||
|
|
||||||
if (target)
|
|
||||||
dist = target->CalculateDistance(projectile_x[i], projectile_y[i], projectile_z[i]);
|
|
||||||
|
|
||||||
int increment_end = 0;
|
|
||||||
increment_end = static_cast<int>(dist / 10) - 1; //This pretty accurately determines end time for speed for 1.5 and timer of 250 ms
|
|
||||||
|
|
||||||
if (increment_end <= projectile_increment[i]){
|
|
||||||
|
|
||||||
if (target && IsValidSpell(projectile_spell_id[i]))
|
|
||||||
SpellOnTarget(projectile_spell_id[i], target, false, true, spells[projectile_spell_id[i]].ResistDiff, true);
|
|
||||||
|
|
||||||
projectile_spell_id[i] = 0;
|
|
||||||
projectile_target_id[i] = 0;
|
|
||||||
projectile_x[i] = 0, projectile_y[i] = 0, projectile_z[i] = 0;
|
|
||||||
projectile_increment[i] = 0;
|
|
||||||
time_disable = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
projectile_increment[i]++;
|
|
||||||
time_disable = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (time_disable)
|
|
||||||
projectile_timer.Disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Mob::DoGravityEffect()
|
void Mob::DoGravityEffect()
|
||||||
{
|
{
|
||||||
Mob *caster = nullptr;
|
Mob *caster = nullptr;
|
||||||
|
|||||||
20
zone/mob.h
20
zone/mob.h
@ -237,8 +237,7 @@ public:
|
|||||||
uint16 CastingSpellID() const { return casting_spell_id; }
|
uint16 CastingSpellID() const { return casting_spell_id; }
|
||||||
bool DoCastingChecks();
|
bool DoCastingChecks();
|
||||||
bool TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier);
|
bool TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier);
|
||||||
void SpellProjectileEffect();
|
bool TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed = 1.5f);
|
||||||
bool TrySpellProjectile(Mob* spell_target, uint16 spell_id);
|
|
||||||
void ResourceTap(int32 damage, uint16 spell_id);
|
void ResourceTap(int32 damage, uint16 spell_id);
|
||||||
void TryTriggerThreshHold(int32 damage, int effect_id, Mob* attacker);
|
void TryTriggerThreshHold(int32 damage, int effect_id, Mob* attacker);
|
||||||
bool CheckSpellCategory(uint16 spell_id, int category_id, int effect_id);
|
bool CheckSpellCategory(uint16 spell_id, int category_id, int effect_id);
|
||||||
@ -729,9 +728,13 @@ public:
|
|||||||
int32 ReduceAllDamage(int32 damage);
|
int32 ReduceAllDamage(int32 damage);
|
||||||
|
|
||||||
virtual void DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, int32 min_damage = 1, int32 hate_override = -1, int ReuseTime = 10, bool HitChance=false, bool CanAvoid=true);
|
virtual void DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, int32 min_damage = 1, int32 hate_override = -1, int ReuseTime = 10, bool HitChance=false, bool CanAvoid=true);
|
||||||
virtual void DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon=nullptr, const Item_Struct* item=nullptr, uint16 weapon_damage=0, int16 chance_mod=0,int16 focus=0, int ReuseTime=0);
|
virtual void DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon=nullptr, const Item_Struct* AmmoItem=nullptr, uint16 weapon_damage=0, int16 chance_mod=0,int16 focus=0, int ReuseTime=0, uint32 range_id=0, int AmmoSlot=0, float speed = 4.0f);
|
||||||
virtual void DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes skillinuse, int16 chance_mod=0, int16 focus=0, bool CanRiposte=false, int ReuseTime=0);
|
virtual void DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes skillinuse, int16 chance_mod=0, int16 focus=0, bool CanRiposte=false, int ReuseTime=0);
|
||||||
virtual void DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon=nullptr, const ItemInst* Ammo=nullptr, uint16 weapon_damage=0, int16 chance_mod=0, int16 focus=0, int ReuseTime=0);
|
virtual void DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon=nullptr, const ItemInst* Ammo=nullptr, uint16 weapon_damage=0, int16 chance_mod=0, int16 focus=0, int ReuseTime=0, uint32 range_id=0, uint32 ammo_id=0, const Item_Struct *AmmoItem=nullptr, int AmmoSlot=0, float speed= 4.0f);
|
||||||
|
bool TryProjectileAttack(Mob* other, const Item_Struct *item, SkillUseTypes skillInUse, uint16 weapon_dmg, const ItemInst* RangeWeapon, const ItemInst* Ammo, int AmmoSlot, float speed);
|
||||||
|
void ProjectileAttack();
|
||||||
|
inline bool HasProjectileAttack() const { return ActiveProjectileATK; }
|
||||||
|
inline void SetProjectileAttack(bool value) { ActiveProjectileATK = value; }
|
||||||
bool CanDoSpecialAttack(Mob *other);
|
bool CanDoSpecialAttack(Mob *other);
|
||||||
bool Flurry(ExtraAttackOptions *opts);
|
bool Flurry(ExtraAttackOptions *opts);
|
||||||
bool Rampage(ExtraAttackOptions *opts);
|
bool Rampage(ExtraAttackOptions *opts);
|
||||||
@ -847,7 +850,7 @@ public:
|
|||||||
// HP Event
|
// HP Event
|
||||||
inline int GetNextHPEvent() const { return nexthpevent; }
|
inline int GetNextHPEvent() const { return nexthpevent; }
|
||||||
void SetNextHPEvent( int hpevent );
|
void SetNextHPEvent( int hpevent );
|
||||||
void SendItemAnimation(Mob *to, const Item_Struct *item, SkillUseTypes skillInUse);
|
void SendItemAnimation(Mob *to, const Item_Struct *item, SkillUseTypes skillInUse, float velocity= 4.0);
|
||||||
inline int& GetNextIncHPEvent() { return nextinchpevent; }
|
inline int& GetNextIncHPEvent() { return nextinchpevent; }
|
||||||
void SetNextIncHPEvent( int inchpevent );
|
void SetNextIncHPEvent( int inchpevent );
|
||||||
|
|
||||||
@ -1097,11 +1100,8 @@ protected:
|
|||||||
uint8 bardsong_slot;
|
uint8 bardsong_slot;
|
||||||
uint32 bardsong_target_id;
|
uint32 bardsong_target_id;
|
||||||
|
|
||||||
Timer projectile_timer;
|
bool ActiveProjectileATK;
|
||||||
uint32 projectile_spell_id[MAX_SPELL_PROJECTILE];
|
tProjatk ProjectileAtk[MAX_SPELL_PROJECTILE];
|
||||||
uint16 projectile_target_id[MAX_SPELL_PROJECTILE];
|
|
||||||
uint8 projectile_increment[MAX_SPELL_PROJECTILE];
|
|
||||||
float projectile_x[MAX_SPELL_PROJECTILE], projectile_y[MAX_SPELL_PROJECTILE], projectile_z[MAX_SPELL_PROJECTILE];
|
|
||||||
|
|
||||||
float rewind_x;
|
float rewind_x;
|
||||||
float rewind_y;
|
float rewind_y;
|
||||||
|
|||||||
@ -597,7 +597,6 @@ void Mob::AI_ShutDown() {
|
|||||||
tic_timer.Disable();
|
tic_timer.Disable();
|
||||||
mana_timer.Disable();
|
mana_timer.Disable();
|
||||||
spellend_timer.Disable();
|
spellend_timer.Disable();
|
||||||
projectile_timer.Disable();
|
|
||||||
rewind_timer.Disable();
|
rewind_timer.Disable();
|
||||||
bindwound_timer.Disable();
|
bindwound_timer.Disable();
|
||||||
stunned_timer.Disable();
|
stunned_timer.Disable();
|
||||||
|
|||||||
@ -670,8 +670,7 @@ bool NPC::Process()
|
|||||||
viral_timer_counter = 0;
|
viral_timer_counter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(projectile_timer.Check())
|
ProjectileAttack();
|
||||||
SpellProjectileEffect();
|
|
||||||
|
|
||||||
if(spellbonuses.GravityEffect == 1) {
|
if(spellbonuses.GravityEffect == 1) {
|
||||||
if(gravity_timer.Check())
|
if(gravity_timer.Check())
|
||||||
|
|||||||
@ -198,7 +198,7 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) {
|
|||||||
SetAttackTimer();
|
SetAttackTimer();
|
||||||
ThrowingAttack(GetTarget());
|
ThrowingAttack(GetTarget());
|
||||||
if (CheckDoubleRangedAttack())
|
if (CheckDoubleRangedAttack())
|
||||||
RangedAttack(GetTarget(), true);
|
ThrowingAttack(GetTarget(), true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//ranged attack (archery)
|
//ranged attack (archery)
|
||||||
@ -596,7 +596,6 @@ void Mob::RogueBackstab(Mob* other, bool min_damage, int ReuseTime)
|
|||||||
for (int i = 0; i < EmuConstants::ITEM_COMMON_SIZE; ++i)
|
for (int i = 0; i < EmuConstants::ITEM_COMMON_SIZE; ++i)
|
||||||
{
|
{
|
||||||
ItemInst *aug = wpn->GetAugment(i);
|
ItemInst *aug = wpn->GetAugment(i);
|
||||||
if(aug)
|
|
||||||
{
|
{
|
||||||
backstab_dmg += aug->GetItem()->BackstabDmg;
|
backstab_dmg += aug->GetItem()->BackstabDmg;
|
||||||
}
|
}
|
||||||
@ -788,8 +787,8 @@ void Client::RangedAttack(Mob* other, bool CanDoubleAttack) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SendItemAnimation(GetTarget(), AmmoItem, SkillArchery);
|
//Shoots projectile and/or applies the archery damage
|
||||||
DoArcheryAttackDmg(GetTarget(), RangeWeapon, Ammo);
|
DoArcheryAttackDmg(GetTarget(), RangeWeapon, Ammo,0,0,0,0,0,0, AmmoItem, ammo_slot);
|
||||||
|
|
||||||
//EndlessQuiver AA base1 = 100% Chance to avoid consumption arrow.
|
//EndlessQuiver AA base1 = 100% Chance to avoid consumption arrow.
|
||||||
int ChanceAvoidConsume = aabonuses.ConsumeProjectile + itembonuses.ConsumeProjectile + spellbonuses.ConsumeProjectile;
|
int ChanceAvoidConsume = aabonuses.ConsumeProjectile + itembonuses.ConsumeProjectile + spellbonuses.ConsumeProjectile;
|
||||||
@ -805,147 +804,204 @@ void Client::RangedAttack(Mob* other, bool CanDoubleAttack) {
|
|||||||
CommonBreakInvisible();
|
CommonBreakInvisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const ItemInst* Ammo, uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime) {
|
void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const ItemInst* Ammo, uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime,
|
||||||
if (!CanDoSpecialAttack(other))
|
uint32 range_id, uint32 ammo_id, const Item_Struct *AmmoItem, int AmmoSlot, float speed) {
|
||||||
|
|
||||||
|
if ((other == nullptr ||
|
||||||
|
((IsClient() && CastToClient()->dead) ||
|
||||||
|
(other->IsClient() && other->CastToClient()->dead)) ||
|
||||||
|
HasDied() ||
|
||||||
|
(!IsAttackAllowed(other)) ||
|
||||||
|
(other->GetInvul() ||
|
||||||
|
other->GetSpecialAbility(IMMUNE_MELEE))))
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!other->CheckHitChance(this, SkillArchery, MainPrimary, chance_mod)) {
|
const ItemInst* _RangeWeapon = nullptr;
|
||||||
|
const ItemInst* _Ammo = nullptr;
|
||||||
|
const Item_Struct* ammo_lost = nullptr;
|
||||||
|
|
||||||
|
/*
|
||||||
|
If LaunchProjectile is false this function will do archery damage on target,
|
||||||
|
otherwise it will shoot the projectile at the target, once the projectile hits target
|
||||||
|
this function is then run again to do the damage portion
|
||||||
|
*/
|
||||||
|
bool LaunchProjectile = false;
|
||||||
|
bool ProjectileMiss = false;
|
||||||
|
|
||||||
|
if (RuleB(Combat, ProjectileDmgOnImpact)){
|
||||||
|
|
||||||
|
if (AmmoItem)
|
||||||
|
LaunchProjectile = true;
|
||||||
|
else{
|
||||||
|
/*
|
||||||
|
Item sync check on projectile landing.
|
||||||
|
Weapon damage is already calculated so this only affects procs!
|
||||||
|
Ammo proc check will use database to find proc if you used up your last ammo.
|
||||||
|
If you change range item mid projectile flight, you loose your chance to proc from bow (Deal with it!).
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!RangeWeapon && !Ammo && range_id && ammo_id){
|
||||||
|
|
||||||
|
if (weapon_damage == 0)
|
||||||
|
ProjectileMiss = true; //This indicates that MISS was originally calculated.
|
||||||
|
|
||||||
|
if (IsClient()){
|
||||||
|
|
||||||
|
_RangeWeapon = CastToClient()->m_inv[MainRange];
|
||||||
|
if (_RangeWeapon && !_RangeWeapon->GetItem() && _RangeWeapon->GetItem()->ID == range_id)
|
||||||
|
RangeWeapon = _RangeWeapon;
|
||||||
|
|
||||||
|
_Ammo = CastToClient()->m_inv[AmmoSlot];
|
||||||
|
if (_Ammo && _Ammo->GetItem() && _Ammo->GetItem()->ID == ammo_id)
|
||||||
|
Ammo = _Ammo;
|
||||||
|
else
|
||||||
|
ammo_lost = database.GetItem(ammo_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (AmmoItem)
|
||||||
|
SendItemAnimation(other, AmmoItem, SkillArchery);
|
||||||
|
|
||||||
|
if (ProjectileMiss || !other->CheckHitChance(this, SkillArchery, MainPrimary, chance_mod)) {
|
||||||
mlog(COMBAT__RANGED, "Ranged attack missed %s.", other->GetName());
|
mlog(COMBAT__RANGED, "Ranged attack missed %s.", other->GetName());
|
||||||
other->Damage(this, 0, SPELL_UNKNOWN, SkillArchery);
|
|
||||||
|
if (LaunchProjectile){
|
||||||
|
TryProjectileAttack(other, AmmoItem, SkillArchery, 0, RangeWeapon, Ammo, AmmoSlot, speed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
other->Damage(this, 0, SPELL_UNKNOWN, SkillArchery);
|
||||||
} else {
|
} else {
|
||||||
mlog(COMBAT__RANGED, "Ranged attack hit %s.", other->GetName());
|
mlog(COMBAT__RANGED, "Ranged attack hit %s.", other->GetName());
|
||||||
|
|
||||||
|
bool HeadShot = false;
|
||||||
|
uint32 HeadShot_Dmg = TryHeadShot(other, SkillArchery);
|
||||||
|
if (HeadShot_Dmg)
|
||||||
|
HeadShot = true;
|
||||||
|
|
||||||
bool HeadShot = false;
|
int32 hate = 0;
|
||||||
uint32 HeadShot_Dmg = TryHeadShot(other, SkillArchery);
|
int32 TotalDmg = 0;
|
||||||
if (HeadShot_Dmg)
|
int16 WDmg = 0;
|
||||||
HeadShot = true;
|
int16 ADmg = 0;
|
||||||
|
if (!weapon_damage){
|
||||||
|
WDmg = GetWeaponDamage(other, RangeWeapon);
|
||||||
|
ADmg = GetWeaponDamage(other, Ammo);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
WDmg = weapon_damage;
|
||||||
|
|
||||||
int32 TotalDmg = 0;
|
if (LaunchProjectile){//1: Shoot the Projectile once we calculate weapon damage.
|
||||||
int16 WDmg = 0;
|
TryProjectileAttack(other, AmmoItem, SkillArchery, WDmg, RangeWeapon, Ammo, AmmoSlot, speed);
|
||||||
int16 ADmg = 0;
|
return;
|
||||||
if (!weapon_damage){
|
}
|
||||||
WDmg = GetWeaponDamage(other, RangeWeapon);
|
|
||||||
ADmg = GetWeaponDamage(other, Ammo);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
WDmg = weapon_damage;
|
|
||||||
|
|
||||||
if (focus) //From FcBaseEffects
|
if (focus) //From FcBaseEffects
|
||||||
WDmg += WDmg*focus/100;
|
WDmg += WDmg*focus/100;
|
||||||
|
|
||||||
if((WDmg > 0) || (ADmg > 0)) {
|
if((WDmg > 0) || (ADmg > 0)) {
|
||||||
if(WDmg < 0)
|
if(WDmg < 0)
|
||||||
WDmg = 0;
|
WDmg = 0;
|
||||||
if(ADmg < 0)
|
if(ADmg < 0)
|
||||||
ADmg = 0;
|
ADmg = 0;
|
||||||
uint32 MaxDmg = (RuleR(Combat, ArcheryBaseDamageBonus)*(WDmg+ADmg)*GetDamageTable(SkillArchery)) / 100;
|
uint32 MaxDmg = (RuleR(Combat, ArcheryBaseDamageBonus)*(WDmg+ADmg)*GetDamageTable(SkillArchery)) / 100;
|
||||||
int32 hate = ((WDmg+ADmg));
|
hate = ((WDmg+ADmg));
|
||||||
|
|
||||||
if (HeadShot)
|
|
||||||
MaxDmg = HeadShot_Dmg;
|
|
||||||
|
|
||||||
uint16 bonusArcheryDamageModifier = aabonuses.ArcheryDamageModifier + itembonuses.ArcheryDamageModifier + spellbonuses.ArcheryDamageModifier;
|
|
||||||
|
|
||||||
MaxDmg += MaxDmg*bonusArcheryDamageModifier / 100;
|
|
||||||
|
|
||||||
mlog(COMBAT__RANGED, "Bow DMG %d, Arrow DMG %d, Max Damage %d.", WDmg, ADmg, MaxDmg);
|
|
||||||
|
|
||||||
bool dobonus = false;
|
|
||||||
if(GetClass() == RANGER && GetLevel() > 50)
|
|
||||||
{
|
|
||||||
int bonuschance = RuleI(Combat, ArcheryBonusChance);
|
|
||||||
|
|
||||||
bonuschance = mod_archery_bonus_chance(bonuschance, RangeWeapon);
|
|
||||||
|
|
||||||
if( !RuleB(Combat, UseArcheryBonusRoll) || (MakeRandomInt(1, 100) < bonuschance) )
|
|
||||||
{
|
|
||||||
if(RuleB(Combat, ArcheryBonusRequiresStationary))
|
|
||||||
{
|
|
||||||
if(other->IsNPC() && !other->IsMoving() && !other->IsRooted())
|
|
||||||
{
|
|
||||||
dobonus = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dobonus = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(dobonus)
|
|
||||||
{
|
|
||||||
MaxDmg *= 2;
|
|
||||||
hate *= 2;
|
|
||||||
MaxDmg = mod_archery_bonus_damage(MaxDmg, RangeWeapon);
|
|
||||||
|
|
||||||
mlog(COMBAT__RANGED, "Ranger. Double damage success roll, doubling damage to %d", MaxDmg);
|
|
||||||
Message_StringID(MT_CritMelee, BOW_DOUBLE_DAMAGE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MaxDmg == 0)
|
|
||||||
MaxDmg = 1;
|
|
||||||
|
|
||||||
if(RuleB(Combat, UseIntervalAC))
|
|
||||||
TotalDmg = MaxDmg;
|
|
||||||
else
|
|
||||||
TotalDmg = MakeRandomInt(1, MaxDmg);
|
|
||||||
|
|
||||||
int minDmg = 1;
|
|
||||||
if(GetLevel() > 25){
|
|
||||||
//twice, for ammo and weapon
|
|
||||||
TotalDmg += (2*((GetLevel()-25)/3));
|
|
||||||
minDmg += (2*((GetLevel()-25)/3));
|
|
||||||
minDmg += minDmg * GetMeleeMinDamageMod_SE(SkillArchery) / 100;
|
|
||||||
hate += (2*((GetLevel()-25)/3));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!HeadShot)
|
|
||||||
other->AvoidDamage(this, TotalDmg, false);
|
|
||||||
|
|
||||||
other->MeleeMitigation(this, TotalDmg, minDmg);
|
|
||||||
if(TotalDmg > 0)
|
|
||||||
{
|
|
||||||
ApplyMeleeDamageBonus(SkillArchery, TotalDmg);
|
|
||||||
TotalDmg += other->GetFcDamageAmtIncoming(this, 0, true, SkillArchery);
|
|
||||||
TotalDmg += (itembonuses.HeroicDEX / 10) + (TotalDmg * other->GetSkillDmgTaken(SkillArchery) / 100) + GetSkillDmgAmt(SkillArchery);
|
|
||||||
|
|
||||||
TotalDmg = mod_archery_damage(TotalDmg, dobonus, RangeWeapon);
|
|
||||||
|
|
||||||
TryCriticalHit(other, SkillArchery, TotalDmg);
|
|
||||||
other->AddToHateList(this, hate, 0, false);
|
|
||||||
CheckNumHitsRemaining(NUMHIT_OutgoingHitSuccess);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
TotalDmg = -5;
|
|
||||||
|
|
||||||
if (HeadShot)
|
if (HeadShot)
|
||||||
entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FATAL_BOW_SHOT, GetName());
|
MaxDmg = HeadShot_Dmg;
|
||||||
|
|
||||||
other->Damage(this, TotalDmg, SPELL_UNKNOWN, SkillArchery);
|
uint16 bonusArcheryDamageModifier = aabonuses.ArcheryDamageModifier + itembonuses.ArcheryDamageModifier + spellbonuses.ArcheryDamageModifier;
|
||||||
|
|
||||||
if (TotalDmg > 0 && HasSkillProcSuccess() && GetTarget() && other && !other->HasDied()){
|
MaxDmg += MaxDmg*bonusArcheryDamageModifier / 100;
|
||||||
if (ReuseTime)
|
|
||||||
TrySkillProc(other, SkillArchery, ReuseTime);
|
mlog(COMBAT__RANGED, "Bow DMG %d, Arrow DMG %d, Max Damage %d.", WDmg, ADmg, MaxDmg);
|
||||||
else
|
|
||||||
TrySkillProc(other, SkillArchery, 0, true, MainRange);
|
bool dobonus = false;
|
||||||
|
if(GetClass() == RANGER && GetLevel() > 50){
|
||||||
|
|
||||||
|
int bonuschance = RuleI(Combat, ArcheryBonusChance);
|
||||||
|
bonuschance = mod_archery_bonus_chance(bonuschance, RangeWeapon);
|
||||||
|
|
||||||
|
if( !RuleB(Combat, UseArcheryBonusRoll) || (MakeRandomInt(1, 100) < bonuschance)){
|
||||||
|
if(RuleB(Combat, ArcheryBonusRequiresStationary)){
|
||||||
|
if(other->IsNPC() && !other->IsMoving() && !other->IsRooted())
|
||||||
|
dobonus = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
dobonus = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dobonus){
|
||||||
|
MaxDmg *= 2;
|
||||||
|
hate *= 2;
|
||||||
|
MaxDmg = mod_archery_bonus_damage(MaxDmg, RangeWeapon);
|
||||||
|
|
||||||
|
mlog(COMBAT__RANGED, "Ranger. Double damage success roll, doubling damage to %d", MaxDmg);
|
||||||
|
Message_StringID(MT_CritMelee, BOW_DOUBLE_DAMAGE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (MaxDmg == 0)
|
||||||
|
MaxDmg = 1;
|
||||||
|
|
||||||
|
if(RuleB(Combat, UseIntervalAC))
|
||||||
|
TotalDmg = MaxDmg;
|
||||||
|
else
|
||||||
|
TotalDmg = MakeRandomInt(1, MaxDmg);
|
||||||
|
|
||||||
|
int minDmg = 1;
|
||||||
|
if(GetLevel() > 25){
|
||||||
|
//twice, for ammo and weapon
|
||||||
|
TotalDmg += (2*((GetLevel()-25)/3));
|
||||||
|
minDmg += (2*((GetLevel()-25)/3));
|
||||||
|
minDmg += minDmg * GetMeleeMinDamageMod_SE(SkillArchery) / 100;
|
||||||
|
hate += (2*((GetLevel()-25)/3));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!HeadShot)
|
||||||
|
other->AvoidDamage(this, TotalDmg, false);
|
||||||
|
|
||||||
|
other->MeleeMitigation(this, TotalDmg, minDmg);
|
||||||
|
if(TotalDmg > 0){
|
||||||
|
CommonOutgoingHitSuccess(other, TotalDmg, SkillArchery);
|
||||||
|
TotalDmg = mod_archery_damage(TotalDmg, dobonus, RangeWeapon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
TotalDmg = -5;
|
||||||
|
|
||||||
|
if (HeadShot)
|
||||||
|
entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FATAL_BOW_SHOT, GetName());
|
||||||
|
|
||||||
|
other->AddToHateList(this, hate, 0, false);
|
||||||
|
other->Damage(this, TotalDmg, SPELL_UNKNOWN, SkillArchery);
|
||||||
|
|
||||||
|
//Skill Proc Success
|
||||||
|
if (TotalDmg > 0 && HasSkillProcSuccess() && other && !other->HasDied()){
|
||||||
|
if (ReuseTime)
|
||||||
|
TrySkillProc(other, SkillArchery, ReuseTime);
|
||||||
|
else
|
||||||
|
TrySkillProc(other, SkillArchery, 0, true, MainRange);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//try proc on hits and misses
|
if (LaunchProjectile)
|
||||||
if((RangeWeapon != nullptr) && GetTarget() && other && !other->HasDied()){
|
return;//Shouldn't reach this point, but just in case.
|
||||||
|
|
||||||
|
//Weapon Proc
|
||||||
|
if(!RangeWeapon && other && !other->HasDied())
|
||||||
TryWeaponProc(RangeWeapon, other, MainRange);
|
TryWeaponProc(RangeWeapon, other, MainRange);
|
||||||
}
|
|
||||||
|
|
||||||
//Arrow procs because why not?
|
//Ammo Proc
|
||||||
if((Ammo != NULL) && GetTarget() && other && !other->HasDied())
|
if (ammo_lost)
|
||||||
{
|
TryWeaponProc(nullptr, ammo_lost, other, MainRange);
|
||||||
TryWeaponProc(Ammo, other, MainRange);
|
else if(Ammo && other && !other->HasDied())
|
||||||
}
|
TryWeaponProc(Ammo, other, MainRange);
|
||||||
|
|
||||||
if (HasSkillProcs() && GetTarget() && other && !other->HasDied()){
|
//Skill Proc
|
||||||
|
if (HasSkillProcs() && other && !other->HasDied()){
|
||||||
if (ReuseTime)
|
if (ReuseTime)
|
||||||
TrySkillProc(other, SkillArchery, ReuseTime);
|
TrySkillProc(other, SkillArchery, ReuseTime);
|
||||||
else
|
else
|
||||||
@ -953,6 +1009,121 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Mob::TryProjectileAttack(Mob* other, const Item_Struct *item, SkillUseTypes skillInUse, uint16 weapon_dmg, const ItemInst* RangeWeapon, const ItemInst* Ammo, int AmmoSlot, float speed){
|
||||||
|
|
||||||
|
if (!other)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int slot = -1;
|
||||||
|
|
||||||
|
//Make sure there is an avialable slot.
|
||||||
|
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) {
|
||||||
|
if (ProjectileAtk[i].target_id == 0){
|
||||||
|
slot = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slot < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
float speed_mod = speed * 0.45f;
|
||||||
|
|
||||||
|
float distance = other->CalculateDistance(GetX(), GetY(), GetZ());
|
||||||
|
float hit = 60.0f + (distance / speed_mod); //Calcuation: 60 = Animation Lag, 1.8 = Speed modifier for speed of (4)
|
||||||
|
|
||||||
|
ProjectileAtk[slot].increment = 1;
|
||||||
|
ProjectileAtk[slot].hit_increment = static_cast<uint16>(hit); //This projected hit time if target does NOT MOVE
|
||||||
|
ProjectileAtk[slot].target_id = other->GetID();
|
||||||
|
ProjectileAtk[slot].wpn_dmg = weapon_dmg;
|
||||||
|
ProjectileAtk[slot].origin_x = GetX();
|
||||||
|
ProjectileAtk[slot].origin_y = GetY();
|
||||||
|
ProjectileAtk[slot].origin_z = GetZ();
|
||||||
|
|
||||||
|
if (RangeWeapon && RangeWeapon->GetItem())
|
||||||
|
ProjectileAtk[slot].ranged_id = RangeWeapon->GetItem()->ID;
|
||||||
|
|
||||||
|
if (Ammo && Ammo->GetItem())
|
||||||
|
ProjectileAtk[slot].ammo_id = Ammo->GetItem()->ID;
|
||||||
|
|
||||||
|
ProjectileAtk[slot].ammo_slot = 0;
|
||||||
|
ProjectileAtk[slot].skill = skillInUse;
|
||||||
|
ProjectileAtk[slot].speed_mod = speed_mod;
|
||||||
|
|
||||||
|
SetProjectileAttack(true);
|
||||||
|
|
||||||
|
if(item)
|
||||||
|
SendItemAnimation(other, item, skillInUse, speed);
|
||||||
|
else if (IsNPC())
|
||||||
|
ProjectileAnimation(other, 0,false,speed,0,0,0,CastToNPC()->GetAmmoIDfile(),skillInUse);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Mob::ProjectileAttack()
|
||||||
|
{
|
||||||
|
if (!HasProjectileAttack())
|
||||||
|
return;;
|
||||||
|
|
||||||
|
Mob* target = nullptr;
|
||||||
|
bool disable = true;
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) {
|
||||||
|
|
||||||
|
if (ProjectileAtk[i].increment == 0){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
disable = false;
|
||||||
|
Mob* target = entity_list.GetMobID(ProjectileAtk[i].target_id);
|
||||||
|
|
||||||
|
if (target && target->IsMoving()){ //Only recalculate hit increment if target moving
|
||||||
|
//Due to frequency that we need to check increment the targets position variables may not be updated even if moving. Do a simple check before calculating distance.
|
||||||
|
if (ProjectileAtk[i].tlast_x != target->GetX() || ProjectileAtk[i].tlast_y != target->GetY()){
|
||||||
|
ProjectileAtk[i].tlast_x = target->GetX();
|
||||||
|
ProjectileAtk[i].tlast_y = target->GetY();
|
||||||
|
float distance = target->CalculateDistance(ProjectileAtk[i].origin_x, ProjectileAtk[i].origin_y, ProjectileAtk[i].origin_z);
|
||||||
|
float hit = 60.0f + (distance / ProjectileAtk[i].speed_mod); //Calcuation: 60 = Animation Lag, 1.8 = Speed modifier for speed of (4)
|
||||||
|
ProjectileAtk[i].hit_increment = static_cast<uint16>(hit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ProjectileAtk[i].hit_increment <= ProjectileAtk[i].increment){
|
||||||
|
|
||||||
|
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);
|
||||||
|
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].target_id = 0;
|
||||||
|
ProjectileAtk[i].wpn_dmg = 0;
|
||||||
|
ProjectileAtk[i].origin_x = 0.0f;
|
||||||
|
ProjectileAtk[i].origin_y = 0.0f;
|
||||||
|
ProjectileAtk[i].origin_z = 0.0f;
|
||||||
|
ProjectileAtk[i].tlast_x = 0.0f;
|
||||||
|
ProjectileAtk[i].tlast_y = 0.0f;
|
||||||
|
ProjectileAtk[i].ranged_id = 0;
|
||||||
|
ProjectileAtk[i].ammo_id = 0;
|
||||||
|
ProjectileAtk[i].ammo_slot = 0;
|
||||||
|
ProjectileAtk[i].skill = 0;
|
||||||
|
ProjectileAtk[i].speed_mod = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
ProjectileAtk[i].increment++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disable)
|
||||||
|
SetProjectileAttack(false);
|
||||||
|
}
|
||||||
|
|
||||||
void NPC::RangedAttack(Mob* other)
|
void NPC::RangedAttack(Mob* other)
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -1180,8 +1351,6 @@ void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51
|
|||||||
(GetAppearance() == eaDead)){
|
(GetAppearance() == eaDead)){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//send item animation, also does the throw animation
|
|
||||||
SendItemAnimation(GetTarget(), item, SkillThrowing);
|
|
||||||
|
|
||||||
DoThrowingAttackDmg(GetTarget(), RangeWeapon, item);
|
DoThrowingAttackDmg(GetTarget(), RangeWeapon, item);
|
||||||
|
|
||||||
@ -1191,21 +1360,78 @@ void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51
|
|||||||
CommonBreakInvisible();
|
CommonBreakInvisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mob::DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item_Struct* item, uint16 weapon_damage, int16 chance_mod,int16 focus, int ReuseTime)
|
void Mob::DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item_Struct* AmmoItem, uint16 weapon_damage, int16 chance_mod,int16 focus, int ReuseTime, uint32 range_id, int AmmoSlot, float speed)
|
||||||
{
|
{
|
||||||
if (!CanDoSpecialAttack(other))
|
if ((other == nullptr ||
|
||||||
|
((IsClient() && CastToClient()->dead) ||
|
||||||
|
(other->IsClient() && other->CastToClient()->dead)) ||
|
||||||
|
HasDied() ||
|
||||||
|
(!IsAttackAllowed(other)) ||
|
||||||
|
(other->GetInvul() ||
|
||||||
|
other->GetSpecialAbility(IMMUNE_MELEE))))
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!other->CheckHitChance(this, SkillThrowing, MainPrimary, chance_mod)){
|
const ItemInst* _RangeWeapon = nullptr;
|
||||||
|
const Item_Struct* ammo_lost = nullptr;
|
||||||
|
|
||||||
|
/*
|
||||||
|
If LaunchProjectile is false this function will do archery damage on target,
|
||||||
|
otherwise it will shoot the projectile at the target, once the projectile hits target
|
||||||
|
this function is then run again to do the damage portion
|
||||||
|
*/
|
||||||
|
bool LaunchProjectile = false;
|
||||||
|
bool ProjectileMiss = false;
|
||||||
|
|
||||||
|
if (RuleB(Combat, ProjectileDmgOnImpact)){
|
||||||
|
|
||||||
|
if (AmmoItem)
|
||||||
|
LaunchProjectile = true;
|
||||||
|
else{
|
||||||
|
if (!RangeWeapon && range_id){
|
||||||
|
|
||||||
|
if (weapon_damage == 0)
|
||||||
|
ProjectileMiss = true; //This indicates that MISS was originally calculated.
|
||||||
|
|
||||||
|
if (IsClient()){
|
||||||
|
|
||||||
|
_RangeWeapon = CastToClient()->m_inv[AmmoSlot];
|
||||||
|
if (_RangeWeapon && _RangeWeapon->GetItem() && _RangeWeapon->GetItem()->ID != range_id)
|
||||||
|
RangeWeapon = _RangeWeapon;
|
||||||
|
else
|
||||||
|
ammo_lost = database.GetItem(range_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (AmmoItem)
|
||||||
|
SendItemAnimation(other, AmmoItem, SkillThrowing);
|
||||||
|
|
||||||
|
if (ProjectileMiss || !other->CheckHitChance(this, SkillThrowing, MainPrimary, chance_mod)){
|
||||||
mlog(COMBAT__RANGED, "Ranged attack missed %s.", other->GetName());
|
mlog(COMBAT__RANGED, "Ranged attack missed %s.", other->GetName());
|
||||||
other->Damage(this, 0, SPELL_UNKNOWN, SkillThrowing);
|
if (LaunchProjectile){
|
||||||
|
TryProjectileAttack(other, AmmoItem, SkillThrowing, 0, RangeWeapon, nullptr, AmmoSlot, speed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
other->Damage(this, 0, SPELL_UNKNOWN, SkillThrowing);
|
||||||
} else {
|
} else {
|
||||||
mlog(COMBAT__RANGED, "Throwing attack hit %s.", other->GetName());
|
mlog(COMBAT__RANGED, "Throwing attack hit %s.", other->GetName());
|
||||||
|
|
||||||
int16 WDmg = 0;
|
int16 WDmg = 0;
|
||||||
|
|
||||||
if (!weapon_damage && item != nullptr)
|
if (!weapon_damage){
|
||||||
WDmg = GetWeaponDamage(other, item);
|
if (IsClient() && RangeWeapon)
|
||||||
|
WDmg = GetWeaponDamage(other, RangeWeapon);
|
||||||
|
else if (AmmoItem)
|
||||||
|
WDmg = GetWeaponDamage(other, AmmoItem);
|
||||||
|
|
||||||
|
if (LaunchProjectile){
|
||||||
|
TryProjectileAttack(other, AmmoItem, SkillThrowing, WDmg, RangeWeapon, nullptr, AmmoSlot, speed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
WDmg = weapon_damage;
|
WDmg = weapon_damage;
|
||||||
|
|
||||||
@ -1242,7 +1468,7 @@ void Mob::DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Ite
|
|||||||
other->AddToHateList(this, 2*WDmg, 0, false);
|
other->AddToHateList(this, 2*WDmg, 0, false);
|
||||||
other->Damage(this, TotalDmg, SPELL_UNKNOWN, SkillThrowing);
|
other->Damage(this, TotalDmg, SPELL_UNKNOWN, SkillThrowing);
|
||||||
|
|
||||||
if (TotalDmg > 0 && HasSkillProcSuccess() && GetTarget() && other && !other->HasDied()){
|
if (TotalDmg > 0 && HasSkillProcSuccess() && other && !other->HasDied()){
|
||||||
if (ReuseTime)
|
if (ReuseTime)
|
||||||
TrySkillProc(other, SkillThrowing, ReuseTime);
|
TrySkillProc(other, SkillThrowing, ReuseTime);
|
||||||
else
|
else
|
||||||
@ -1250,19 +1476,24 @@ void Mob::DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Ite
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if((RangeWeapon != nullptr) && GetTarget() && other && (other->GetHP() > -10))
|
if (LaunchProjectile)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//Throwing item Proc
|
||||||
|
if (ammo_lost)
|
||||||
|
TryWeaponProc(nullptr, ammo_lost, other, MainRange);
|
||||||
|
else if(RangeWeapon && other && !other->HasDied())
|
||||||
TryWeaponProc(RangeWeapon, other, MainRange);
|
TryWeaponProc(RangeWeapon, other, MainRange);
|
||||||
|
|
||||||
if (HasSkillProcs() && GetTarget() && other && !other->HasDied()){
|
if (HasSkillProcs() && other && !other->HasDied()){
|
||||||
if (ReuseTime)
|
if (ReuseTime)
|
||||||
TrySkillProc(other, SkillThrowing, ReuseTime);
|
TrySkillProc(other, SkillThrowing, ReuseTime);
|
||||||
else
|
else
|
||||||
TrySkillProc(other, SkillThrowing, 0, false, MainRange);
|
TrySkillProc(other, SkillThrowing, 0, false, MainRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mob::SendItemAnimation(Mob *to, const Item_Struct *item, SkillUseTypes skillInUse) {
|
void Mob::SendItemAnimation(Mob *to, const Item_Struct *item, SkillUseTypes skillInUse, float velocity) {
|
||||||
EQApplicationPacket *outapp = new EQApplicationPacket(OP_SomeItemPacketMaybe, sizeof(Arrow_Struct));
|
EQApplicationPacket *outapp = new EQApplicationPacket(OP_SomeItemPacketMaybe, sizeof(Arrow_Struct));
|
||||||
Arrow_Struct *as = (Arrow_Struct *) outapp->pBuffer;
|
Arrow_Struct *as = (Arrow_Struct *) outapp->pBuffer;
|
||||||
as->type = 1;
|
as->type = 1;
|
||||||
@ -1290,7 +1521,7 @@ void Mob::SendItemAnimation(Mob *to, const Item_Struct *item, SkillUseTypes skil
|
|||||||
|
|
||||||
Arc causes the object to form an arc in motion. A value too high will
|
Arc causes the object to form an arc in motion. A value too high will
|
||||||
*/
|
*/
|
||||||
as->velocity = 4.0;
|
as->velocity = velocity;
|
||||||
|
|
||||||
//these angle and tilt used together seem to make the arrow/knife throw as straight as I can make it
|
//these angle and tilt used together seem to make the arrow/knife throw as straight as I can make it
|
||||||
|
|
||||||
|
|||||||
@ -6400,17 +6400,16 @@ bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDama
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id){
|
bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){
|
||||||
|
|
||||||
/*For mage 'Bolt' line and other various spells.
|
/*For mage 'Bolt' line and other various spells.
|
||||||
-This is mostly accurate for how the modern clients handle this effect.
|
-This is mostly accurate for how the modern clients handle this effect.
|
||||||
-It was changed at some point to use an actual projectile as done here (opposed to a particle effect in classic)
|
-It was changed at some point to use an actual projectile as done here (opposed to a particle effect in classic)
|
||||||
-The projectile graphic appears to be that of 'Ball of Sunlight' ID 80648 and will be visible to anyone in SoF+
|
|
||||||
-There is no LOS check to prevent a bolt from being cast. If you don't have LOS your bolt simply goes into whatever barrier
|
-There is no LOS check to prevent a bolt from being cast. If you don't have LOS your bolt simply goes into whatever barrier
|
||||||
and you lose your mana. If there is LOS the bolt will lock onto your target and the damage is applied when it hits the target.
|
and you lose your mana. If there is LOS the bolt will lock onto your target and the damage is applied when it hits the target.
|
||||||
-If your target moves the bolt moves with it in any direction or angle (consistent with other projectiles).
|
-If your target moves the bolt moves with it in any direction or angle (consistent with other projectiles).
|
||||||
-The way this is written once a bolt is cast a timer checks the distance from the initial cast to the target repeatedly
|
-The way this is written once a bolt is cast a the distance from the initial cast to the target repeatedly
|
||||||
and calculates at what predicted time the bolt should hit that target in client_process (therefore accounting for any target movement).
|
check and if target is moving recalculates at what predicted time the bolt should hit that target in client_process
|
||||||
When bolt hits its predicted point the damage is then done to target.
|
When bolt hits its predicted point the damage is then done to target.
|
||||||
Note: Projectile speed of 1 takes 3 seconds to go 100 distance units. Calculations are based on this constant.
|
Note: Projectile speed of 1 takes 3 seconds to go 100 distance units. Calculations are based on this constant.
|
||||||
Live Bolt speed: Projectile speed of X takes 5 seconds to go 300 distance units.
|
Live Bolt speed: Projectile speed of X takes 5 seconds to go 300 distance units.
|
||||||
@ -6422,31 +6421,41 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id){
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
uint8 anim = spells[spell_id].CastingAnim;
|
uint8 anim = spells[spell_id].CastingAnim;
|
||||||
int bolt_id = -1;
|
int slot = -1;
|
||||||
|
|
||||||
//Make sure there is an avialable bolt to be cast.
|
//Make sure there is an avialable bolt to be cast.
|
||||||
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) {
|
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) {
|
||||||
if (projectile_spell_id[i] == 0){
|
if (ProjectileAtk[i].target_id == 0){
|
||||||
bolt_id = i;
|
slot = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bolt_id < 0)
|
if (slot < 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (CheckLosFN(spell_target)) {
|
if (CheckLosFN(spell_target)) {
|
||||||
|
|
||||||
projectile_spell_id[bolt_id] = spell_id;
|
float speed_mod = speed * 0.45f; //Constant for adjusting speeds to match calculated impact time.
|
||||||
projectile_target_id[bolt_id] = spell_target->GetID();
|
float distance = spell_target->CalculateDistance(GetX(), GetY(), GetZ());
|
||||||
projectile_x[bolt_id] = GetX(), projectile_y[bolt_id] = GetY(), projectile_z[bolt_id] = GetZ();
|
float hit = 60.0f + (distance / speed_mod);
|
||||||
projectile_increment[bolt_id] = 1;
|
|
||||||
projectile_timer.Start(250);
|
ProjectileAtk[slot].increment = 1;
|
||||||
|
ProjectileAtk[slot].hit_increment = static_cast<uint16>(hit); //This projected hit time if target does NOT MOVE
|
||||||
|
ProjectileAtk[slot].target_id = spell_target->GetID();
|
||||||
|
ProjectileAtk[slot].wpn_dmg = spell_id; //Store spell_id in weapon damage field
|
||||||
|
ProjectileAtk[slot].origin_x = GetX();
|
||||||
|
ProjectileAtk[slot].origin_y = GetY();
|
||||||
|
ProjectileAtk[slot].origin_z = GetZ();
|
||||||
|
ProjectileAtk[slot].skill = SkillConjuration;
|
||||||
|
ProjectileAtk[slot].speed_mod = speed_mod;
|
||||||
|
|
||||||
|
SetProjectileAttack(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
//This will use the correct graphic as defined in the player_1 field of spells_new table. Found in UF+ spell files.
|
//This will use the correct graphic as defined in the player_1 field of spells_new table. Found in UF+ spell files.
|
||||||
if (RuleB(Spells, UseLiveSpellProjectileGFX)) {
|
if (RuleB(Spells, UseLiveSpellProjectileGFX)) {
|
||||||
ProjectileAnimation(spell_target,0, false, 1.5,0,0,0, spells[spell_id].player_1);
|
ProjectileAnimation(spell_target,0, false, speed,0,0,0, spells[spell_id].player_1);
|
||||||
}
|
}
|
||||||
|
|
||||||
//This allows limited support for server using older spell files that do not contain data for bolt graphics.
|
//This allows limited support for server using older spell files that do not contain data for bolt graphics.
|
||||||
@ -6456,19 +6465,17 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id){
|
|||||||
|
|
||||||
if (IsClient()){
|
if (IsClient()){
|
||||||
if (CastToClient()->GetClientVersionBit() <= 4) //Titanium needs alternate graphic.
|
if (CastToClient()->GetClientVersionBit() <= 4) //Titanium needs alternate graphic.
|
||||||
ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_Titanium)), false, 1.5);
|
ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_Titanium)), false, speed);
|
||||||
else
|
else
|
||||||
ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_SOF)), false, 1.5);
|
ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_SOF)), false, speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_NPC)), false, 1.5);
|
ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_NPC)), false, speed);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Default to an arrow if not using a mage bolt (Use up to date spell file and enable above rules for best results)
|
//Default to an arrow if not using a mage bolt (Use up to date spell file and enable above rules for best results)
|
||||||
else
|
else
|
||||||
ProjectileAnimation(spell_target,0, 1, 1.5);
|
ProjectileAnimation(spell_target,0, 1, speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spells[spell_id].CastingAnim == 64)
|
if (spells[spell_id].CastingAnim == 64)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user