mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-16 01:01:30 +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)
|
||||
-------------------------------------------------------
|
||||
|
||||
== 11/28/2014 ==
|
||||
Trevius: Fixed a zone crash related to numhits for spells.
|
||||
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.
|
||||
|
||||
== 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 ==
|
||||
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.
|
||||
|
||||
@ -419,6 +419,7 @@ RULE_INT ( Combat, ArcheryBonusChance, 50)
|
||||
RULE_INT ( Combat, BerserkerFrenzyStart, 35)
|
||||
RULE_INT ( Combat, BerserkerFrenzyEnd, 45)
|
||||
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( 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;
|
||||
}
|
||||
|
||||
if(projectile_timer.Check())
|
||||
SpellProjectileEffect();
|
||||
ProjectileAttack();
|
||||
|
||||
if(spellbonuses.GravityEffect == 1) {
|
||||
if(gravity_timer.Check())
|
||||
|
||||
@ -459,6 +459,24 @@ struct Shielders_Struct {
|
||||
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
|
||||
//make DoAnim take it instead of int, to enforce its use.
|
||||
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;
|
||||
target = 0;
|
||||
|
||||
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_spell_id[i] = 0; }
|
||||
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_target_id[i] = 0; }
|
||||
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; }
|
||||
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_y[i] = 0; }
|
||||
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_z[i] = 0; }
|
||||
projectile_timer.Disable();
|
||||
ActiveProjectileATK = false;
|
||||
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++)
|
||||
{
|
||||
ProjectileAtk[i].increment = 0;
|
||||
ProjectileAtk[i].hit_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;
|
||||
}
|
||||
|
||||
memset(&itembonuses, 0, sizeof(StatBonuses));
|
||||
memset(&spellbonuses, 0, sizeof(StatBonuses));
|
||||
@ -4235,49 +4246,6 @@ bool Mob::TryReflectSpell(uint32 spell_id)
|
||||
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()
|
||||
{
|
||||
Mob *caster = nullptr;
|
||||
|
||||
20
zone/mob.h
20
zone/mob.h
@ -237,8 +237,7 @@ public:
|
||||
uint16 CastingSpellID() const { return casting_spell_id; }
|
||||
bool DoCastingChecks();
|
||||
bool TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier);
|
||||
void SpellProjectileEffect();
|
||||
bool TrySpellProjectile(Mob* spell_target, uint16 spell_id);
|
||||
bool TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed = 1.5f);
|
||||
void ResourceTap(int32 damage, uint16 spell_id);
|
||||
void TryTriggerThreshHold(int32 damage, int effect_id, Mob* attacker);
|
||||
bool CheckSpellCategory(uint16 spell_id, int category_id, int effect_id);
|
||||
@ -729,9 +728,13 @@ public:
|
||||
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 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 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 Flurry(ExtraAttackOptions *opts);
|
||||
bool Rampage(ExtraAttackOptions *opts);
|
||||
@ -847,7 +850,7 @@ public:
|
||||
// HP Event
|
||||
inline int GetNextHPEvent() const { return nexthpevent; }
|
||||
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; }
|
||||
void SetNextIncHPEvent( int inchpevent );
|
||||
|
||||
@ -1097,11 +1100,8 @@ protected:
|
||||
uint8 bardsong_slot;
|
||||
uint32 bardsong_target_id;
|
||||
|
||||
Timer projectile_timer;
|
||||
uint32 projectile_spell_id[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];
|
||||
bool ActiveProjectileATK;
|
||||
tProjatk ProjectileAtk[MAX_SPELL_PROJECTILE];
|
||||
|
||||
float rewind_x;
|
||||
float rewind_y;
|
||||
|
||||
@ -597,7 +597,6 @@ void Mob::AI_ShutDown() {
|
||||
tic_timer.Disable();
|
||||
mana_timer.Disable();
|
||||
spellend_timer.Disable();
|
||||
projectile_timer.Disable();
|
||||
rewind_timer.Disable();
|
||||
bindwound_timer.Disable();
|
||||
stunned_timer.Disable();
|
||||
|
||||
@ -670,8 +670,7 @@ bool NPC::Process()
|
||||
viral_timer_counter = 0;
|
||||
}
|
||||
|
||||
if(projectile_timer.Check())
|
||||
SpellProjectileEffect();
|
||||
ProjectileAttack();
|
||||
|
||||
if(spellbonuses.GravityEffect == 1) {
|
||||
if(gravity_timer.Check())
|
||||
|
||||
@ -198,7 +198,7 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) {
|
||||
SetAttackTimer();
|
||||
ThrowingAttack(GetTarget());
|
||||
if (CheckDoubleRangedAttack())
|
||||
RangedAttack(GetTarget(), true);
|
||||
ThrowingAttack(GetTarget(), true);
|
||||
return;
|
||||
}
|
||||
//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)
|
||||
{
|
||||
ItemInst *aug = wpn->GetAugment(i);
|
||||
if(aug)
|
||||
{
|
||||
backstab_dmg += aug->GetItem()->BackstabDmg;
|
||||
}
|
||||
@ -788,8 +787,8 @@ void Client::RangedAttack(Mob* other, bool CanDoubleAttack) {
|
||||
return;
|
||||
}
|
||||
|
||||
SendItemAnimation(GetTarget(), AmmoItem, SkillArchery);
|
||||
DoArcheryAttackDmg(GetTarget(), RangeWeapon, Ammo);
|
||||
//Shoots projectile and/or applies the archery damage
|
||||
DoArcheryAttackDmg(GetTarget(), RangeWeapon, Ammo,0,0,0,0,0,0, AmmoItem, ammo_slot);
|
||||
|
||||
//EndlessQuiver AA base1 = 100% Chance to avoid consumption arrow.
|
||||
int ChanceAvoidConsume = aabonuses.ConsumeProjectile + itembonuses.ConsumeProjectile + spellbonuses.ConsumeProjectile;
|
||||
@ -805,147 +804,204 @@ void Client::RangedAttack(Mob* other, bool CanDoubleAttack) {
|
||||
CommonBreakInvisible();
|
||||
}
|
||||
|
||||
void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const ItemInst* Ammo, uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime) {
|
||||
if (!CanDoSpecialAttack(other))
|
||||
void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const ItemInst* Ammo, uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime,
|
||||
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;
|
||||
}
|
||||
|
||||
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());
|
||||
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 {
|
||||
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;
|
||||
uint32 HeadShot_Dmg = TryHeadShot(other, SkillArchery);
|
||||
if (HeadShot_Dmg)
|
||||
HeadShot = true;
|
||||
int32 hate = 0;
|
||||
int32 TotalDmg = 0;
|
||||
int16 WDmg = 0;
|
||||
int16 ADmg = 0;
|
||||
if (!weapon_damage){
|
||||
WDmg = GetWeaponDamage(other, RangeWeapon);
|
||||
ADmg = GetWeaponDamage(other, Ammo);
|
||||
}
|
||||
else
|
||||
WDmg = weapon_damage;
|
||||
|
||||
int32 TotalDmg = 0;
|
||||
int16 WDmg = 0;
|
||||
int16 ADmg = 0;
|
||||
if (!weapon_damage){
|
||||
WDmg = GetWeaponDamage(other, RangeWeapon);
|
||||
ADmg = GetWeaponDamage(other, Ammo);
|
||||
}
|
||||
else
|
||||
WDmg = weapon_damage;
|
||||
if (LaunchProjectile){//1: Shoot the Projectile once we calculate weapon damage.
|
||||
TryProjectileAttack(other, AmmoItem, SkillArchery, WDmg, RangeWeapon, Ammo, AmmoSlot, speed);
|
||||
return;
|
||||
}
|
||||
|
||||
if (focus) //From FcBaseEffects
|
||||
WDmg += WDmg*focus/100;
|
||||
if (focus) //From FcBaseEffects
|
||||
WDmg += WDmg*focus/100;
|
||||
|
||||
if((WDmg > 0) || (ADmg > 0)) {
|
||||
if(WDmg < 0)
|
||||
WDmg = 0;
|
||||
if(ADmg < 0)
|
||||
ADmg = 0;
|
||||
uint32 MaxDmg = (RuleR(Combat, ArcheryBaseDamageBonus)*(WDmg+ADmg)*GetDamageTable(SkillArchery)) / 100;
|
||||
int32 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((WDmg > 0) || (ADmg > 0)) {
|
||||
if(WDmg < 0)
|
||||
WDmg = 0;
|
||||
if(ADmg < 0)
|
||||
ADmg = 0;
|
||||
uint32 MaxDmg = (RuleR(Combat, ArcheryBaseDamageBonus)*(WDmg+ADmg)*GetDamageTable(SkillArchery)) / 100;
|
||||
hate = ((WDmg+ADmg));
|
||||
|
||||
if (HeadShot)
|
||||
entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FATAL_BOW_SHOT, GetName());
|
||||
|
||||
other->Damage(this, TotalDmg, SPELL_UNKNOWN, SkillArchery);
|
||||
|
||||
if (TotalDmg > 0 && HasSkillProcSuccess() && GetTarget() && other && !other->HasDied()){
|
||||
if (ReuseTime)
|
||||
TrySkillProc(other, SkillArchery, ReuseTime);
|
||||
else
|
||||
TrySkillProc(other, SkillArchery, 0, true, MainRange);
|
||||
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){
|
||||
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((RangeWeapon != nullptr) && GetTarget() && other && !other->HasDied()){
|
||||
if (LaunchProjectile)
|
||||
return;//Shouldn't reach this point, but just in case.
|
||||
|
||||
//Weapon Proc
|
||||
if(!RangeWeapon && other && !other->HasDied())
|
||||
TryWeaponProc(RangeWeapon, other, MainRange);
|
||||
}
|
||||
|
||||
//Arrow procs because why not?
|
||||
if((Ammo != NULL) && GetTarget() && other && !other->HasDied())
|
||||
{
|
||||
TryWeaponProc(Ammo, other, MainRange);
|
||||
}
|
||||
//Ammo Proc
|
||||
if (ammo_lost)
|
||||
TryWeaponProc(nullptr, ammo_lost, 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)
|
||||
TrySkillProc(other, SkillArchery, ReuseTime);
|
||||
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)
|
||||
{
|
||||
|
||||
@ -1180,8 +1351,6 @@ void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51
|
||||
(GetAppearance() == eaDead)){
|
||||
return;
|
||||
}
|
||||
//send item animation, also does the throw animation
|
||||
SendItemAnimation(GetTarget(), item, SkillThrowing);
|
||||
|
||||
DoThrowingAttackDmg(GetTarget(), RangeWeapon, item);
|
||||
|
||||
@ -1191,21 +1360,78 @@ void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51
|
||||
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;
|
||||
}
|
||||
|
||||
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());
|
||||
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 {
|
||||
mlog(COMBAT__RANGED, "Throwing attack hit %s.", other->GetName());
|
||||
|
||||
int16 WDmg = 0;
|
||||
|
||||
if (!weapon_damage && item != nullptr)
|
||||
WDmg = GetWeaponDamage(other, item);
|
||||
if (!weapon_damage){
|
||||
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
|
||||
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->Damage(this, TotalDmg, SPELL_UNKNOWN, SkillThrowing);
|
||||
|
||||
if (TotalDmg > 0 && HasSkillProcSuccess() && GetTarget() && other && !other->HasDied()){
|
||||
if (TotalDmg > 0 && HasSkillProcSuccess() && other && !other->HasDied()){
|
||||
if (ReuseTime)
|
||||
TrySkillProc(other, SkillThrowing, ReuseTime);
|
||||
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);
|
||||
|
||||
if (HasSkillProcs() && GetTarget() && other && !other->HasDied()){
|
||||
if (HasSkillProcs() && other && !other->HasDied()){
|
||||
if (ReuseTime)
|
||||
TrySkillProc(other, SkillThrowing, ReuseTime);
|
||||
else
|
||||
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));
|
||||
Arrow_Struct *as = (Arrow_Struct *) outapp->pBuffer;
|
||||
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
|
||||
*/
|
||||
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
|
||||
|
||||
|
||||
@ -6400,17 +6400,16 @@ bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDama
|
||||
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.
|
||||
-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)
|
||||
-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
|
||||
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).
|
||||
-The way this is written once a bolt is cast a timer checks 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).
|
||||
-The way this is written once a bolt is cast a the distance from the initial cast to the target repeatedly
|
||||
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.
|
||||
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.
|
||||
@ -6422,31 +6421,41 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id){
|
||||
return false;
|
||||
|
||||
uint8 anim = spells[spell_id].CastingAnim;
|
||||
int bolt_id = -1;
|
||||
int slot = -1;
|
||||
|
||||
//Make sure there is an avialable bolt to be cast.
|
||||
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) {
|
||||
if (projectile_spell_id[i] == 0){
|
||||
bolt_id = i;
|
||||
if (ProjectileAtk[i].target_id == 0){
|
||||
slot = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bolt_id < 0)
|
||||
if (slot < 0)
|
||||
return false;
|
||||
|
||||
|
||||
if (CheckLosFN(spell_target)) {
|
||||
|
||||
projectile_spell_id[bolt_id] = spell_id;
|
||||
projectile_target_id[bolt_id] = spell_target->GetID();
|
||||
projectile_x[bolt_id] = GetX(), projectile_y[bolt_id] = GetY(), projectile_z[bolt_id] = GetZ();
|
||||
projectile_increment[bolt_id] = 1;
|
||||
projectile_timer.Start(250);
|
||||
float speed_mod = speed * 0.45f; //Constant for adjusting speeds to match calculated impact time.
|
||||
float distance = spell_target->CalculateDistance(GetX(), GetY(), GetZ());
|
||||
float hit = 60.0f + (distance / speed_mod);
|
||||
|
||||
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.
|
||||
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.
|
||||
@ -6456,19 +6465,17 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id){
|
||||
|
||||
if (IsClient()){
|
||||
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
|
||||
ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_SOF)), false, 1.5);
|
||||
ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_SOF)), false, speed);
|
||||
}
|
||||
|
||||
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)
|
||||
else
|
||||
ProjectileAnimation(spell_target,0, 1, 1.5);
|
||||
ProjectileAnimation(spell_target,0, 1, speed);
|
||||
}
|
||||
|
||||
if (spells[spell_id].CastingAnim == 64)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user