Merge remote-tracking branch 'upstream/master' into spaupdate03

This commit is contained in:
KayenEQ
2021-07-30 13:51:16 -04:00
28 changed files with 591 additions and 84 deletions
+33 -6
View File
@@ -5483,13 +5483,40 @@ void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell)
CheckIncreaseSkill(EQ::skills::SkillDoubleAttack, target, -10);
if (CheckDoubleAttack()) {
Attack(target, hand, false, false, IsFromSpell);
// Modern AA description: Increases your chance of ... performing one additional hit with a 2-handed weapon when double attacking by 2%.
if (hand == EQ::invslot::slotPrimary) {
auto extraattackchance = aabonuses.ExtraAttackChance + spellbonuses.ExtraAttackChance +
itembonuses.ExtraAttackChance;
if (extraattackchance && HasTwoHanderEquipped() && zone->random.Roll(extraattackchance))
Attack(target, hand, false, false, IsFromSpell);
if (HasTwoHanderEquipped()) {
auto extraattackchance = aabonuses.ExtraAttackChance[0] + spellbonuses.ExtraAttackChance[0] +
itembonuses.ExtraAttackChance[0];
if (extraattackchance && zone->random.Roll(extraattackchance)) {
auto extraattackamt = std::max({ aabonuses.ExtraAttackChance[1], spellbonuses.ExtraAttackChance[1], itembonuses.ExtraAttackChance[1] });
for (int i = 0; i < extraattackamt; i++) {
Attack(target, hand, false, false, IsFromSpell);
}
}
}
else {
auto extraattackchance_primary = aabonuses.ExtraAttackChancePrimary[0] + spellbonuses.ExtraAttackChancePrimary[0] +
itembonuses.ExtraAttackChancePrimary[0];
if (extraattackchance_primary && zone->random.Roll(extraattackchance_primary)) {
auto extraattackamt_primary = std::max({ aabonuses.ExtraAttackChancePrimary[1], spellbonuses.ExtraAttackChancePrimary[1], itembonuses.ExtraAttackChancePrimary[1] });
for (int i = 0; i < extraattackamt_primary; i++) {
Attack(target, hand, false, false, IsFromSpell);
}
}
}
}
if (hand == EQ::invslot::slotSecondary) {
auto extraattackchance_secondary = aabonuses.ExtraAttackChanceSecondary[0] + spellbonuses.ExtraAttackChanceSecondary[0] +
itembonuses.ExtraAttackChanceSecondary[0];
if (extraattackchance_secondary && zone->random.Roll(extraattackchance_secondary)) {
auto extraattackamt_secondary = std::max({ aabonuses.ExtraAttackChanceSecondary[1], spellbonuses.ExtraAttackChanceSecondary[1], itembonuses.ExtraAttackChanceSecondary[1] });
for (int i = 0; i < extraattackamt_secondary; i++) {
Attack(target, hand, false, false, IsFromSpell);
}
}
}
// you can only triple from the main hand
+122 -9
View File
@@ -860,9 +860,6 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
case SE_MaxBindWound:
newbon->MaxBindWound += base1;
break;
case SE_ExtraAttackChance:
newbon->ExtraAttackChance += base1;
break;
case SE_SeeInvis:
newbon->SeeInvis = base1;
break;
@@ -984,7 +981,6 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
case SE_BlockBehind:
newbon->BlockBehind += base1;
break;
case SE_StrikeThrough:
case SE_StrikeThrough2:
newbon->StrikeThrough += base1;
@@ -1177,6 +1173,17 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
break;
}
case SE_Skill_Base_Damage_Mod: {
// Bad data or unsupported new skill
if (base2 > EQ::skills::HIGHEST_SKILL)
break;
if (base2 == ALL_SKILLS)
newbon->DamageModifier3[EQ::skills::HIGHEST_SKILL + 1] += base1;
else
newbon->DamageModifier3[base2] += base1;
break;
}
case SE_SlayUndead: {
if (newbon->SlayUndead[1] < base1)
newbon->SlayUndead[0] = base1; // Rate
@@ -1561,6 +1568,34 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
newbon->Pet_Add_Atk += base1;
break;
case SE_ExtraAttackChance:
{
if (newbon->ExtraAttackChance[0] < base1) {
newbon->ExtraAttackChance[0] = base1;
newbon->ExtraAttackChance[1] = base2 ? base2 : 1;
}
break;
}
case SE_AddExtraAttackPct_1h_Primary:
{
if (newbon->ExtraAttackChancePrimary[0] < base1) {
newbon->ExtraAttackChancePrimary[0] = base1;
newbon->ExtraAttackChancePrimary[1] = base2 ? base2 : 1;
}
break;
}
case SE_AddExtraAttackPct_1h_Secondary:
{
if (newbon->ExtraAttackChanceSecondary[0] < base1) {
newbon->ExtraAttackChanceSecondary[0] = base1;
newbon->ExtraAttackChanceSecondary[1] = base2 ? base2 : 1;
}
break;
}
// to do
case SE_PetDiscipline:
break;
@@ -1715,7 +1750,9 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
case SE_ChangeFrenzyRad:
{
// redundant to have level check here
if (max != 0 && GetLevel() > max)
break;
if(new_bonus->AggroRange == -1 || effect_value < new_bonus->AggroRange)
{
new_bonus->AggroRange = static_cast<float>(effect_value);
@@ -1725,6 +1762,8 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
case SE_Harmony:
{
if (max != 0 && GetLevel() > max)
break;
// Harmony effect as buff - kinda tricky
// harmony could stack with a lull spell, which has better aggro range
// take the one with less range in any case
@@ -2323,6 +2362,19 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
break;
}
case SE_Skill_Base_Damage_Mod:
{
// Bad data or unsupported new skill
if (base2 > EQ::skills::HIGHEST_SKILL)
break;
int skill = base2 == ALL_SKILLS ? EQ::skills::HIGHEST_SKILL + 1 : base2;
if (effect_value < 0 && new_bonus->DamageModifier3[skill] > effect_value)
new_bonus->DamageModifier3[skill] = effect_value;
else if (effect_value > 0 && new_bonus->DamageModifier3[skill] < effect_value)
new_bonus->DamageModifier3[skill] = effect_value;
break;
}
case SE_MinDamageModifier:
{
// Bad data or unsupported new skill
@@ -2358,8 +2410,45 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
}
case SE_ExtraAttackChance:
new_bonus->ExtraAttackChance += effect_value;
{
if (AdditiveWornBonus) {
new_bonus->ExtraAttackChance[0] += effect_value;
new_bonus->ExtraAttackChance[1] = base2 ? base2 : 1;
}
if (new_bonus->ExtraAttackChance[0] < effect_value) {
new_bonus->ExtraAttackChance[0] = effect_value;
new_bonus->ExtraAttackChance[1] = base2 ? base2 : 1;
}
break;
}
case SE_AddExtraAttackPct_1h_Primary:
{
if (AdditiveWornBonus) {
new_bonus->ExtraAttackChancePrimary[0] += effect_value;
new_bonus->ExtraAttackChancePrimary[1] = base2 ? base2 : 1;
}
if (new_bonus->ExtraAttackChancePrimary[0] < effect_value) {
new_bonus->ExtraAttackChancePrimary[0] = effect_value;
new_bonus->ExtraAttackChancePrimary[1] = base2 ? base2 : 1;
}
break;
}
case SE_AddExtraAttackPct_1h_Secondary:
{
if (AdditiveWornBonus) {
new_bonus->ExtraAttackChanceSecondary[0] += effect_value;
new_bonus->ExtraAttackChanceSecondary[1] = base2 ? base2 : 1;
}
if (new_bonus->ExtraAttackChanceSecondary[0] < effect_value) {
new_bonus->ExtraAttackChanceSecondary[0] = effect_value;
new_bonus->ExtraAttackChanceSecondary[1] = base2 ? base2 : 1;
}
break;
}
case SE_PercentXPIncrease:
{
@@ -4252,6 +4341,18 @@ void Mob::NegateSpellsBonuses(uint16 spell_id)
break;
}
case SE_Skill_Base_Damage_Mod:
{
for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++)
{
spellbonuses.DamageModifier3[e] = effect_value;
aabonuses.DamageModifier3[e] = effect_value;
itembonuses.DamageModifier3[e] = effect_value;
}
break;
}
case SE_MinDamageModifier:
{
for (int e = 0; e < EQ::skills::HIGHEST_SKILL + 1; e++)
@@ -4276,9 +4377,21 @@ void Mob::NegateSpellsBonuses(uint16 spell_id)
break;
case SE_ExtraAttackChance:
spellbonuses.ExtraAttackChance = effect_value;
aabonuses.ExtraAttackChance = effect_value;
itembonuses.ExtraAttackChance = effect_value;
spellbonuses.ExtraAttackChance[0] = effect_value;
aabonuses.ExtraAttackChance[0] = effect_value;
itembonuses.ExtraAttackChance[0] = effect_value;
break;
case SE_AddExtraAttackPct_1h_Primary:
spellbonuses.ExtraAttackChancePrimary[0] = effect_value;
aabonuses.ExtraAttackChancePrimary[0] = effect_value;
itembonuses.ExtraAttackChancePrimary[0] = effect_value;
break;
case SE_AddExtraAttackPct_1h_Secondary:
spellbonuses.ExtraAttackChanceSecondary[0] = effect_value;
aabonuses.ExtraAttackChanceSecondary[0] = effect_value;
itembonuses.ExtraAttackChanceSecondary[0] = effect_value;
break;
case SE_PercentXPIncrease:
+3 -1
View File
@@ -3272,7 +3272,9 @@ void Bot::AI_Process()
}
TEST_COMBATANTS();
int32 ExtraAttackChanceBonus = (spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance + aabonuses.ExtraAttackChance);
auto ExtraAttackChanceBonus =
(spellbonuses.ExtraAttackChance[0] + itembonuses.ExtraAttackChance[0] +
aabonuses.ExtraAttackChance[0]);
if (ExtraAttackChanceBonus) {
if (p_item && p_item->GetItem()->IsType2HWeapon()) {
+5 -1
View File
@@ -10130,7 +10130,6 @@ void Client::SetAFK(uint8 afk_flag) {
safe_delete(outapp);
}
void Client::SendToInstance(std::string instance_type, std::string zone_short_name, uint32 instance_version, float x, float y, float z, float heading, std::string instance_identifier, uint32 duration) {
uint32 zone_id = ZoneID(zone_short_name);
std::string current_instance_type = str_tolower(instance_type);
@@ -10241,3 +10240,8 @@ void Client::RemoveItem(uint32 item_id, uint32 quantity)
}
}
}
void Client::SetGMStatus(int newStatus) {
if (this->Admin() != newStatus)
database.UpdateGMStatus(this->AccountID(), newStatus);
}
+1
View File
@@ -1523,6 +1523,7 @@ public:
void LoadAccountFlags();
void SetAccountFlag(std::string flag, std::string val);
std::string GetAccountFlag(std::string flag);
void SetGMStatus(int newStatus);
float GetDamageMultiplier(EQ::skills::SkillType how_long_has_this_been_missing);
void Consume(const EQ::ItemData *item, uint8 type, int16 slot, bool auto_consume);
void PlayMP3(const char* fname);
+1 -1
View File
@@ -12997,7 +12997,7 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app)
int16 charges = 0;
if (item->Stackable || tmpmer_used)
charges = mp->quantity;
else if ( item->MaxCharges > 1)
else if ( item->MaxCharges >= 1)
charges = item->MaxCharges;
EQ::ItemInstance* inst = database.CreateItem(item, charges);
-2
View File
@@ -75,8 +75,6 @@
#include "mob_movement_manager.h"
#include "npc_scale_manager.h"
#include "../common/content/world_content_service.h"
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "../common/http/httplib.h"
extern QueryServ* QServ;
+4 -1
View File
@@ -445,10 +445,13 @@ struct StatBonuses {
int32 HitChanceEffect[EQ::skills::HIGHEST_SKILL + 2]; //Spell effect Chance to Hit, straight percent increase
int32 DamageModifier[EQ::skills::HIGHEST_SKILL + 2]; //i
int32 DamageModifier2[EQ::skills::HIGHEST_SKILL + 2]; //i
int32 DamageModifier3[EQ::skills::HIGHEST_SKILL + 2]; //i
int32 MinDamageModifier[EQ::skills::HIGHEST_SKILL + 2]; //i
int32 ProcChance; // ProcChance/10 == % increase i = CombatEffects
int32 ProcChanceSPA; // ProcChance from spell effects
int32 ExtraAttackChance;
int32 ExtraAttackChance[2]; // base chance(w/ 2H weapon)=0, amt of extra attacks=1
int32 ExtraAttackChancePrimary[2]; // base chance=0, , amt of extra attacks=1
int32 ExtraAttackChanceSecondary[2]; // base chance=0, , amt of extra attacks=1
int32 DoTShielding;
int32 DivineSaveChance[2]; // Second Chance (base1 = chance, base2 = spell on trigger)
uint32 DeathSave[4]; // Death Pact [0](value = 1 partial 2 = full) [1]=slot [2]=LvLimit [3]=HealAmt
+7 -1
View File
@@ -2158,6 +2158,11 @@ void Lua_Client::RemoveItem(uint32 item_id, uint32 quantity) {
return self->RemoveItem(item_id, quantity);
}
void Lua_Client::SetGMStatus(uint32 newStatus) {
Lua_Safe_Call_Void();
return self->SetGMStatus(newStatus);
}
luabind::scope lua_register_client() {
return luabind::class_<Lua_Client, Lua_Mob>("Client")
.def(luabind::constructor<>())
@@ -2525,7 +2530,8 @@ luabind::scope lua_register_client() {
.def("SendToInstance", (void(Lua_Client::*)(std::string,std::string,uint32,float,float,float,float,std::string,uint32))&Lua_Client::SendToInstance)
.def("CountItem", (int(Lua_Client::*)(uint32))&Lua_Client::CountItem)
.def("RemoveItem", (void(Lua_Client::*)(uint32))&Lua_Client::RemoveItem)
.def("RemoveItem", (void(Lua_Client::*)(uint32,uint32))&Lua_Client::RemoveItem);
.def("RemoveItem", (void(Lua_Client::*)(uint32,uint32))&Lua_Client::RemoveItem)
.def("SetGMStatus", (void(Lua_Client::*)(int32))& Lua_Client::SetGMStatus);
}
luabind::scope lua_register_inventory_where() {
+1
View File
@@ -370,6 +370,7 @@ public:
int CountItem(uint32 item_id);
void RemoveItem(uint32 item_id);
void RemoveItem(uint32 item_id, uint32 quantity);
void SetGMStatus(uint32 newStatus);
void SetPrimaryWeaponOrnamentation(uint32 model_id);
void SetSecondaryWeaponOrnamentation(uint32 model_id);
+7 -1
View File
@@ -474,6 +474,11 @@ int Lua_Spell::GetDamageShieldType() {
return self->DamageShieldType;
}
int Lua_Spell::GetRank() {
Lua_Safe_Call_Int();
return self->rank;
}
luabind::scope lua_register_spell() {
return luabind::class_<Lua_Spell>("Spell")
.def(luabind::constructor<>())
@@ -561,7 +566,8 @@ luabind::scope lua_register_spell() {
.def("MaxDist", &Lua_Spell::GetMaxDist)
.def("MaxDistMod", &Lua_Spell::GetMaxDistMod)
.def("MinRange", &Lua_Spell::GetMinRange)
.def("DamageShieldType", &Lua_Spell::GetDamageShieldType);
.def("DamageShieldType", &Lua_Spell::GetDamageShieldType)
.def("Rank", &Lua_Spell::GetRank);
}
#endif
+1
View File
@@ -107,6 +107,7 @@ public:
float GetMaxDistMod();
float GetMinRange();
int GetDamageShieldType();
int GetRank();
};
#endif
+1 -1
View File
@@ -572,7 +572,7 @@ int32 Lua_StatBonuses::GetProcChanceSPA() const {
int32 Lua_StatBonuses::GetExtraAttackChance() const {
Lua_Safe_Call_Int();
return self->ExtraAttackChance;
return self->ExtraAttackChance[0];
}
int32 Lua_StatBonuses::GetDoTShielding() const {
+1 -1
View File
@@ -1624,7 +1624,7 @@ void Merc::AI_Process() {
}
}
int16 ExtraAttackChanceBonus = spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance + aabonuses.ExtraAttackChance;
int16 ExtraAttackChanceBonus = spellbonuses.ExtraAttackChance[0] + itembonuses.ExtraAttackChance[0] + aabonuses.ExtraAttackChance[0];
if (GetTarget() && ExtraAttackChanceBonus) {
if(zone->random.Roll(ExtraAttackChanceBonus))
+3 -3
View File
@@ -3708,9 +3708,6 @@ void Mob::TryTwincast(Mob *caster, Mob *target, uint32 spell_id)
if(!IsValidSpell(spell_id))
return;
if (IsEffectInSpell(spell_id, SE_TwinCastBlocker))
return;
if(IsClient())
{
int32 focus = CastToClient()->GetFocusEffect(focusTwincast, spell_id);
@@ -4862,6 +4859,9 @@ int16 Mob::GetMeleeDamageMod_SE(uint16 skill)
dmg_mod += itembonuses.DamageModifier2[EQ::skills::HIGHEST_SKILL + 1] + spellbonuses.DamageModifier2[EQ::skills::HIGHEST_SKILL + 1] + aabonuses.DamageModifier2[EQ::skills::HIGHEST_SKILL + 1] +
itembonuses.DamageModifier2[skill] + spellbonuses.DamageModifier2[skill] + aabonuses.DamageModifier2[skill];
dmg_mod += itembonuses.DamageModifier3[EQ::skills::HIGHEST_SKILL + 1] + spellbonuses.DamageModifier3[EQ::skills::HIGHEST_SKILL + 1] + aabonuses.DamageModifier3[EQ::skills::HIGHEST_SKILL + 1] +
itembonuses.DamageModifier3[skill] + spellbonuses.DamageModifier3[skill] + aabonuses.DamageModifier3[skill];
if(dmg_mod < -100)
dmg_mod = -100;
+1
View File
@@ -837,6 +837,7 @@ public:
inline int16 GetSpellPowerDistanceMod() const { return SpellPowerDistanceMod; };
inline void SetSpellPowerDistanceMod(int16 value) { SpellPowerDistanceMod = value; };
int32 GetSpellStat(uint32 spell_id, const char *identifier, uint8 slot = 0);
bool HarmonySpellLevelCheck(int32 spell_id, Mob* target = nullptr);
void CastSpellOnLand(Mob* caster, uint32 spell_id);
void FocusProcLimitProcess();
+16
View File
@@ -3413,6 +3413,21 @@ XS(XS_Client_ReadBook) {
XSRETURN_EMPTY;
}
XS(XS_Client_SetGMStatus); /* prototype to pass -Wmissing-prototypes */
XS(XS_Client_SetGMStatus) {
dXSARGS;
if (items != 2)
Perl_croak(aTHX_ "Usage: Client::SetGMStatus(THIS, int newStatus)"); // @categories Script Utility
{
Client *THIS;
int newStatus = (int)SvIV(ST(1));
VALIDATE_THIS_IS_CLIENT;
THIS->SetGMStatus(newStatus);
THIS->UpdateAdmin(true);
}
XSRETURN_EMPTY;
}
XS(XS_Client_UpdateGroupAAs); /* prototype to pass -Wmissing-prototypes */
XS(XS_Client_UpdateGroupAAs) {
dXSARGS;
@@ -5706,6 +5721,7 @@ XS(boot_Client) {
newXSproto(strcpy(buf, "UntrainDisc"), XS_Client_UntrainDisc, file, "$$;$");
newXSproto(strcpy(buf, "UntrainDiscAll"), XS_Client_UntrainDiscAll, file, "$;$");
newXSproto(strcpy(buf, "UpdateAdmin"), XS_Client_UpdateAdmin, file, "$;$");
newXSproto(strcpy(buf, "SetGMStatus"), XS_Client_SetGMStatus, file, "$$");
newXSproto(strcpy(buf, "UpdateGroupAAs"), XS_Client_UpdateGroupAAs, file, "$$$");
newXSproto(strcpy(buf, "UpdateLDoNPoints"), XS_Client_UpdateLDoNPoints, file, "$$$");
newXSproto(strcpy(buf, "UpdateTaskActivity"), XS_Client_UpdateTaskActivity, file, "$$$$;$");
+53 -18
View File
@@ -929,11 +929,31 @@ bool Mob::TryProjectileAttack(Mob *other, const EQ::ItemData *item, EQ::skills::
if (slot < 0)
return false;
float speed_mod = speed;
float distance_mod = 0.0f;
float distance = other->CalculateDistance(GetX(), GetY(), GetZ());
float hit =
1200.0f + (10 * distance / speed_mod); // Calcuation: 60 = Animation Lag, 1.8 = Speed modifier for speed of (4)
/*
New Distance Mod constant (7/25/21 update), modifier is needed to adjust slower speeds to have correct impact times at short distances.
We use archery 4.0 speed as a baseline for the forumla. At speed 1.5 at 50 pct distance mod is needed, where as speed 4.0 there is no modifer.
Therefore, we derive out our modifer as follows. distance_mod = (speed - 4) * ((50 - 0)/(1.5-4)). The ratio there is -20.0f. distance_mod = (speed - 4) * -20.0f
For distances >125 we use different modifier, this was all meticulously tested by eye to get the best possible outcome for projectile impact times. Not perfect though.
*/
if (distance <= 125.0f) {
if (speed != 4.0f) { //Standard functions will always be 4.0f for archery.
distance_mod = (speed - 4.0f) * -20.0f;
distance += distance * distance_mod / 100.0f;
}
}
else if (distance > 125.0f && distance <= 200.0f)
distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length
else if (distance > 200.0f) {
distance = distance * 1.30f; //Add 30% to base distance if over 200 range to tighten up hit timing.
distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length
}
float hit = 1200.0f + (10 * distance / speed);
ProjectileAtk[slot].increment = 1;
ProjectileAtk[slot].hit_increment = static_cast<uint16>(hit); // This projected hit time if target does NOT MOVE
@@ -951,14 +971,12 @@ bool Mob::TryProjectileAttack(Mob *other, const EQ::ItemData *item, EQ::skills::
ProjectileAtk[slot].ammo_slot = 0;
ProjectileAtk[slot].skill = skillInUse;
ProjectileAtk[slot].speed_mod = speed_mod;
ProjectileAtk[slot].speed_mod = speed;
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;
}
@@ -978,23 +996,40 @@ void Mob::ProjectileAttack()
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 (target && target->IsMoving()) {
/*
Only recalculate hit increment if target is 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 = 1200.0f + (10 * distance / ProjectileAtk[i].speed_mod); // Calcuation: 60 =
// Animation Lag, 1.8 =
// Speed modifier for speed
// of (4)
//Recalculate from the original location the projectile was fired in relation to the current targets location.
float distance = target->CalculateDistance(ProjectileAtk[i].origin_x, ProjectileAtk[i].origin_y, ProjectileAtk[i].origin_z);
float distance_mod = 0.0f;
if (distance <= 125.0f) {
distance_mod = (ProjectileAtk[i].speed_mod - 4.0f) * -20.0f;
distance += distance * distance_mod / 100.0f;
}
else if (distance > 125.0f && distance <= 200.0f)
distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length
else if (distance > 200.0f) {
distance = distance * 1.30f; //Add 30% to base distance if over 200 range to tighten up hit timing.
distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length
}
float hit = 1200.0f + (10 * distance / ProjectileAtk[i].speed_mod);
ProjectileAtk[i].hit_increment = static_cast<uint16>(hit);
}
}
// We hit I guess?
// Check if we hit.
if (ProjectileAtk[i].hit_increment <= ProjectileAtk[i].increment) {
if (target) {
if (IsNPC()) {
@@ -1496,7 +1531,7 @@ void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, f
speed = 4.0;
}
if(!angle) {
angle = CalculateHeadingToTarget(to->GetX(), to->GetY()) * 2;
angle = CalculateHeadingToTarget(to->GetX(), to->GetY());
}
if(!tilt) {
tilt = 125;
+60 -15
View File
@@ -3226,6 +3226,10 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
case SE_Ff_ReuseTimeMax:
case SE_Ff_Value_Min:
case SE_Ff_Value_Max:
case SE_AddExtraAttackPct_1h_Primary:
case SE_AddExtraAttackPct_1h_Secondary:
case SE_Skill_Base_Damage_Mod:
{
break;
}
@@ -4890,7 +4894,7 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id)
break;
case SE_FcTwincast:
if (type == focusTwincast)
if (type == focusTwincast && !IsEffectInSpell(spell_id, SE_TwinCastBlocker))
value = base1;
break;
@@ -5531,7 +5535,7 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo
break;
case SE_FcTwincast:
if (type == focusTwincast)
if (type == focusTwincast && !IsEffectInSpell(spell_id, SE_TwinCastBlocker))
value = focus_spell.base[i];
break;
@@ -7144,7 +7148,7 @@ bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDama
return false;
}
bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){
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.
@@ -7169,7 +7173,7 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){
//Make sure there is an avialable bolt to be cast.
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) {
if (ProjectileAtk[i].target_id == 0){
if (ProjectileAtk[i].target_id == 0) {
slot = i;
break;
}
@@ -7178,11 +7182,35 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){
if (slot < 0)
return false;
float arc = 0.0f;
float distance_mod = 0.0f;
if (CheckLosFN(spell_target)) {
float speed_mod = speed; //Constant for adjusting speeds to match calculated impact time.
float distance = spell_target->CalculateDistance(GetX(), GetY(), GetZ());
float hit = 1200.0f + (10 * distance / speed_mod);
/*
New Distance Mod constant (7/25/21 update), modifier is needed to adjust slower speeds to have correct impact times at short distances.
We use archery 4.0 speed as a baseline for the forumla. At speed 1.5 at 50 pct distance mod is needed, where as speed 4.0 there is no modifer.
Therefore, we derive out our modifer as follows. distance_mod = (speed - 4) * ((50 - 0)/(1.5-4)). The ratio there is -20.0f. distance_mod = (speed - 4) * -20.0f
For distances >125 we use different modifier, this was all meticulously tested by eye to get the best possible outcome for projectile impact times. Not perfect though.
*/
if (distance <= 125.0f) {
distance_mod = (speed - 4.0f) * -20.0f;
distance += distance * distance_mod / 100.0f;
}
else if (distance > 125.0f && distance <= 200.0f)
distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length
else if (distance > 200.0f) {
arc = 50.0f - ((distance - 200.0f) * 0.266f); //Arc angle gets drastically larger if >200 distance, lets lower it down gradually for better effect.
arc = std::max(arc, 20.0f); //No lower than 20 arc
distance = distance * 1.30f; //Add 30% to base distance if over 200 range to tighten up hit timing.
distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length
}
float hit = 1200.0f + (10 * distance / speed);
ProjectileAtk[slot].increment = 1;
ProjectileAtk[slot].hit_increment = static_cast<uint16>(hit); //This projected hit time if target does NOT MOVE
@@ -7192,34 +7220,33 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){
ProjectileAtk[slot].origin_y = GetY();
ProjectileAtk[slot].origin_z = GetZ();
ProjectileAtk[slot].skill = EQ::skills::SkillConjuration;
ProjectileAtk[slot].speed_mod = speed_mod;
ProjectileAtk[slot].speed_mod = speed;
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, speed,0,0,0, spells[spell_id].player_1);
ProjectileAnimation(spell_target, 0, false, speed, 0.0f, 0.0f, arc, spells[spell_id].player_1);
}
//This allows limited support for server using older spell files that do not contain data for bolt graphics.
else {
//Only use fire graphic for fire spells.
if (spells[spell_id].resisttype == RESIST_FIRE) {
if (IsClient()){
if (IsClient()) {
if (CastToClient()->ClientVersionBit() <= 4) //Titanium needs alternate graphic.
ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_Titanium)), false, speed);
ProjectileAnimation(spell_target, (RuleI(Spells, FRProjectileItem_Titanium)), false, speed, 0.0f, 0.0f, arc);
else
ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_SOF)), false, speed);
}
ProjectileAnimation(spell_target, (RuleI(Spells, FRProjectileItem_SOF)), false, speed, 0.0f, 0.0f, arc);
}
else
ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_NPC)), false, speed);
ProjectileAnimation(spell_target, (RuleI(Spells, FRProjectileItem_NPC)), false, speed, 0.0f, 0.0f, arc);
}
//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, speed);
ProjectileAnimation(spell_target, 0, 1, speed, 0.0f, 0.0f, arc);
}
if (spells[spell_id].CastingAnim == 64)
@@ -7564,3 +7591,21 @@ void Client::BreakFeignDeathWhenCastOn(bool IsResisted)
MessageString(Chat::SpellFailure,FD_CAST_ON);
}
}
bool Mob::HarmonySpellLevelCheck(int32 spell_id, Mob *target)
{
//'this' = caster of spell
if (!target) {
return false;
}
for (int i = 0; i < EFFECT_COUNT; i++) {
// not important to check limit on SE_Lull as it doesnt have one and if the other components won't land, then SE_Lull wont either
if (spells[spell_id].effectid[i] == SE_ChangeFrenzyRad || spells[spell_id].effectid[i] == SE_Harmony) {
if ((spells[spell_id].max[i] != 0 && target->GetLevel() > spells[spell_id].max[i]) || target->GetSpecialAbility(IMMUNE_PACIFY)) {
return false;
}
}
}
return true;
}
+9 -11
View File
@@ -224,17 +224,9 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot,
if (spellbonuses.NegateIfCombat)
BuffFadeByEffect(SE_NegateIfCombat);
if(IsClient() && GetTarget() && IsHarmonySpell(spell_id))
{
for(int i = 0; i < EFFECT_COUNT; i++) {
// not important to check limit on SE_Lull as it doesnt have one and if the other components won't land, then SE_Lull wont either
if (spells[spell_id].effectid[i] == SE_ChangeFrenzyRad || spells[spell_id].effectid[i] == SE_Harmony) {
if((spells[spell_id].max[i] != 0 && GetTarget()->GetLevel() > spells[spell_id].max[i]) || GetTarget()->GetSpecialAbility(IMMUNE_PACIFY)) {
InterruptSpell(CANNOT_AFFECT_NPC, 0x121, spell_id);
return(false);
}
}
}
if (IsClient() && IsHarmonySpell(spell_id) && !HarmonySpellLevelCheck(spell_id, entity_list.GetMobID(target_id))) {
InterruptSpell(SPELL_NO_EFFECT, 0x121, spell_id);
return false;
}
if (HasActiveSong() && IsBardSong(spell_id)) {
@@ -3789,6 +3781,12 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r
return false;
}
}
//Need this to account for special AOE cases.
if (IsClient() && IsHarmonySpell(spell_id) && !HarmonySpellLevelCheck(spell_id, spelltar)) {
MessageString(Chat::SpellFailure, SPELL_NO_EFFECT);
return false;
}
// Block next spell effect should be used up first(since its blocking the next spell)
if(CanBlockSpell()) {
int buff_count = GetMaxTotalSlots();
+18
View File
@@ -4977,3 +4977,21 @@ void ZoneDatabase::SetEXPModifier(uint32 character_id, uint32 zone_id, double ex
);
database.QueryDatabase(query);
}
void ZoneDatabase::UpdateGMStatus(uint32 accID, int newStatus)
{
if (accID) {
std::string query = fmt::format(
SQL(
UPDATE
`account`
SET `status` = {}
WHERE
`id` = {}
),
newStatus,
accID
);
database.QueryDatabase(query);
}
}
+3
View File
@@ -289,6 +289,9 @@ public:
void DeleteBuyLines(uint32 CharID);
void UpdateBuyLine(uint32 CharID, uint32 BuySlot, uint32 Quantity);
void UpdateGMStatus(uint32 accID, int newStatus);
/**
************************************************
* Character