diff --git a/utils/sql/git/required/2014_10_16_special_abilities_null.sql b/utils/sql/git/required/2014_10_16_special_abilities_null.sql new file mode 100644 index 000000000..8f92a114c --- /dev/null +++ b/utils/sql/git/required/2014_10_16_special_abilities_null.sql @@ -0,0 +1,4 @@ +ALTER TABLE `merc_stats` MODIFY COLUMN `special_abilities` text CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL AFTER `attack_speed`; + +ALTER TABLE `npc_types` MODIFY COLUMN `special_abilities` text CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL AFTER `npcspecialattks`; + diff --git a/zone/attack.cpp b/zone/attack.cpp index 0b8cf07f1..3d05bad70 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -1288,15 +1288,18 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b mlog(COMBAT__DAMAGE, "Damage calculated to %d (min %d, max %d, str %d, skill %d, DMG %d, lv %d)", damage, min_hit, max_hit, GetSTR(), GetSkill(skillinuse), weapon_damage, mylevel); + int hit_chance_bonus = 0; + if(opts) { damage *= opts->damage_percent; damage += opts->damage_flat; hate *= opts->hate_percent; hate += opts->hate_flat; + hit_chance_bonus += opts->hate_flat; } //check to see if we hit.. - if(!other->CheckHitChance(this, skillinuse, Hand)) { + if(!other->CheckHitChance(this, skillinuse, Hand, hit_chance_bonus)) { mlog(COMBAT__ATTACKS, "Attack missed. Damage set to 0."); damage = 0; } else { //we hit, try to avoid it @@ -1890,14 +1893,18 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool other->AddToHateList(this, hate); } else { + + int hit_chance_bonus = 0; + if(opts) { damage *= opts->damage_percent; damage += opts->damage_flat; hate *= opts->hate_percent; hate += opts->hate_flat; + hit_chance_bonus += opts->hit_chance; } - if(!other->CheckHitChance(this, skillinuse, Hand)) { + if(!other->CheckHitChance(this, skillinuse, Hand, hit_chance_bonus)) { damage = 0; //miss } else { //hit, check for damage avoidance other->AvoidDamage(this, damage); diff --git a/zone/common.h b/zone/common.h index fb56b3f01..c5a55d670 100644 --- a/zone/common.h +++ b/zone/common.h @@ -563,7 +563,7 @@ struct ExtraAttackOptions { : damage_percent(1.0f), damage_flat(0), armor_pen_percent(0.0f), armor_pen_flat(0), crit_percent(1.0f), crit_flat(0.0f), - hate_percent(1.0f), hate_flat(0) + hate_percent(1.0f), hate_flat(0), hit_chance(0) { } float damage_percent; @@ -574,6 +574,7 @@ struct ExtraAttackOptions { float crit_flat; float hate_percent; int hate_flat; + int hit_chance; }; #endif diff --git a/zone/effects.cpp b/zone/effects.cpp index 7822d2e5e..3d8138100 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -683,8 +683,18 @@ bool Client::UseDiscipline(uint32 spell_id, uint32 target) { { uint32 reduced_recast = spell.recast_time / 1000; reduced_recast -= CastToClient()->GetFocusEffect(focusReduceRecastTime, spell_id); - if(reduced_recast < 0) + if(reduced_recast <= 0){ reduced_recast = 0; + if (GetPTimers().Enabled((uint32)DiscTimer)) + GetPTimers().Clear(&database, (uint32)DiscTimer); + } + + if (reduced_recast > 0) + CastSpell(spell_id, target, DISCIPLINE_SPELL_SLOT, -1, -1, 0, -1, (uint32)DiscTimer, reduced_recast); + else{ + CastSpell(spell_id, target, DISCIPLINE_SPELL_SLOT); + return true; + } CastSpell(spell_id, target, DISCIPLINE_SPELL_SLOT, -1, -1, 0, -1, (uint32)DiscTimer, reduced_recast); if(spells[spell_id].EndurTimerIndex < MAX_DISCIPLINE_TIMERS) diff --git a/zone/entity.cpp b/zone/entity.cpp index 0ce530419..d5354386f 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -4612,42 +4612,49 @@ Client *EntityList::FindCorpseDragger(uint16 CorpseID) return nullptr; } -Mob *EntityList::GetTargetForVirus(Mob *spreader) +Mob *EntityList::GetTargetForVirus(Mob *spreader, int range) { int max_spread_range = RuleI(Spells, VirusSpreadDistance); + if (range) + max_spread_range = range; + std::vector TargetsInRange; auto it = mob_list.begin(); while (it != mob_list.end()) { + Mob *cur = it->second; // Make sure the target is in range, has los and is not the mob doing the spreading - if ((it->second->GetID() != spreader->GetID()) && - (it->second->CalculateDistance(spreader->GetX(), spreader->GetY(), + if ((cur->GetID() != spreader->GetID()) && + (cur->CalculateDistance(spreader->GetX(), spreader->GetY(), spreader->GetZ()) <= max_spread_range) && - (spreader->CheckLosFN(it->second))) { + (spreader->CheckLosFN(cur))) { // If the spreader is an npc it can only spread to other npc controlled mobs - if (spreader->IsNPC() && !spreader->IsPet() && it->second->IsNPC()) { - TargetsInRange.push_back(it->second); + if (spreader->IsNPC() && !spreader->IsPet() && !spreader->CastToNPC()->GetSwarmOwner() && cur->IsNPC()) { + TargetsInRange.push_back(cur); } // If the spreader is an npc controlled pet it can spread to any other npc or an npc controlled pet else if (spreader->IsNPC() && spreader->IsPet() && spreader->GetOwner()->IsNPC()) { - if (it->second->IsNPC() && !it->second->IsPet()) { - TargetsInRange.push_back(it->second); - } else if (it->second->IsNPC() && it->second->IsPet() && it->second->GetOwner()->IsNPC()) { - TargetsInRange.push_back(it->second); + if (cur->IsNPC() && !cur->IsPet()) { + TargetsInRange.push_back(cur); + } else if (cur->IsNPC() && cur->IsPet() && cur->GetOwner()->IsNPC()) { + TargetsInRange.push_back(cur); + } + else if (cur->IsNPC() && cur->CastToNPC()->GetSwarmOwner() && cur->GetOwner()->IsNPC()) { + TargetsInRange.push_back(cur); } } // if the spreader is anything else(bot, pet, etc) then it should spread to everything but non client controlled npcs - else if (!spreader->IsNPC() && !it->second->IsNPC()) { - TargetsInRange.push_back(it->second); + else if (!spreader->IsNPC() && !cur->IsNPC()) { + TargetsInRange.push_back(cur); } // if its a pet we need to determine appropriate targets(pet to client, pet to pet, pet to bot, etc) - else if (spreader->IsNPC() && spreader->IsPet() && !spreader->GetOwner()->IsNPC()) { - if (!it->second->IsNPC()) { - TargetsInRange.push_back(it->second); + else if (spreader->IsNPC() && (spreader->IsPet() || spreader->CastToNPC()->GetSwarmOwner()) && !spreader->GetOwner()->IsNPC()) { + if (!cur->IsNPC()) { + TargetsInRange.push_back(cur); } - else if (it->second->IsNPC() && it->second->IsPet() && !it->second->GetOwner()->IsNPC()) { - TargetsInRange.push_back(it->second); + else if (cur->IsNPC() && (cur->IsPet() || cur->CastToNPC()->GetSwarmOwner()) && !cur->GetOwner()->IsNPC()) { + TargetsInRange.push_back(cur); } } } diff --git a/zone/entity.h b/zone/entity.h index 5776ed936..ad5de09bf 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -134,7 +134,7 @@ public: Mob *GetMob(const char* name); Mob *GetMobByNpcTypeID(uint32 get_id); bool IsMobSpawnedByNpcTypeID(uint32 get_id); - Mob *GetTargetForVirus(Mob* spreader); + Mob *GetTargetForVirus(Mob* spreader, int range); inline NPC *GetNPCByID(uint16 id) { return npc_list.count(id) ? npc_list.at(id) : nullptr; } NPC *GetNPCByNPCTypeID(uint32 npc_id); diff --git a/zone/mob.cpp b/zone/mob.cpp index 8d53d50ef..9fb113392 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -4349,7 +4349,7 @@ void Mob::SpreadVirus(uint16 spell_id, uint16 casterID) // Only spread in zones without perm buffs if(!zone->BuffTimersSuspended()) { for(int i = 0; i < num_targs; i++) { - target = entity_list.GetTargetForVirus(this); + target = entity_list.GetTargetForVirus(this, spells[spell_id].viral_range); if(target) { // Only spreads to the uninfected if(!target->FindBuff(spell_id)) { diff --git a/zone/spells.cpp b/zone/spells.cpp index 50eb9479f..544da35a0 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -307,6 +307,10 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, uint16 slot, sprintf(temp, "%d", spell_id); parse->EventNPC(EVENT_CAST_BEGIN, CastToNPC(), nullptr, temp, 0); } + + //To prevent NPC ghosting when spells are cast from scripts + if (IsNPC() && IsMoving() && cast_time > 0) + SendPosition(); if(resist_adjust) { @@ -2191,7 +2195,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16 CastToClient()->GetPTimers().Start(casting_spell_timer, casting_spell_timer_duration); mlog(SPELLS__CASTING, "Spell %d: Setting custom reuse timer %d to %d", spell_id, casting_spell_timer, casting_spell_timer_duration); } - else if(spells[spell_id].recast_time > 1000) { + else if(spells[spell_id].recast_time > 1000 && !spells[spell_id].IsDisciplineBuff) { int recast = spells[spell_id].recast_time/1000; if (spell_id == SPELL_LAY_ON_HANDS) //lay on hands { diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index e83edf2b9..2fb79029b 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -1816,7 +1816,12 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) { tmpNPCType->min_dmg = atoi(row[31]); tmpNPCType->max_dmg = atoi(row[32]); tmpNPCType->attack_count = atoi(row[33]); - strn0cpy(tmpNPCType->special_abilities, row[34], 512); + + if (row[34] != nullptr) + strn0cpy(tmpNPCType->special_abilities, row[34], 512); + else + tmpNPCType->special_abilities[0] = '\0'; + tmpNPCType->npc_spells_id = atoi(row[35]); tmpNPCType->npc_spells_effects_id = atoi(row[36]); tmpNPCType->d_meele_texture1 = atoi(row[37]); @@ -2018,7 +2023,11 @@ const NPCType* ZoneDatabase::GetMercType(uint32 id, uint16 raceid, uint32 client tmpNPCType->min_dmg = atoi(row[24]); tmpNPCType->max_dmg = atoi(row[25]); tmpNPCType->attack_count = atoi(row[26]); - strn0cpy(tmpNPCType->special_abilities, row[27], 512); + + if (row[27] != nullptr) + strn0cpy(tmpNPCType->special_abilities, row[27], 512); + else + tmpNPCType->special_abilities[0] = '\0'; tmpNPCType->d_meele_texture1 = atoi(row[28]); tmpNPCType->d_meele_texture2 = atoi(row[29]);