[Spells] SPA 299 Wake the Dead updates and crash fixes. SPA 306 Army of Dead implemented. (#1929)

* start

* wtd fix v1

* Update aa.cpp

* rework done, army of dead supported

* debugs

* Update aa.cpp

* Update spdat.h
This commit is contained in:
KayenEQ 2022-01-16 14:55:51 -05:00 committed by GitHub
parent 5f482a9b30
commit 5ebbbf647b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 259 additions and 138 deletions

View File

@ -1017,14 +1017,14 @@ typedef enum {
#define SE_FcSpellVulnerability 296 // implemented, @Fc, On Target, spell damage taken mod pct, base: min pct, limit: max pct
#define SE_FcDamageAmtIncoming 297 // implemetned, @Fc, On Target, damage taken flat amt, base: amt
#define SE_ChangeHeight 298 // implemented
#define SE_WakeTheDead 299 // implemented
#define SE_WakeTheDead 299 // implemented, @Pets, summon one temporary pet from nearby corpses that last a set duration, base: none, limit: none, max: duration (seconds). Note: max range of corpse is 250.
#define SE_Doppelganger 300 // implemented
#define SE_ArcheryDamageModifier 301 // implemented[AA] - increase archery damage by percent
#define SE_FcDamagePctCrit 302 // implemented, @Fc, On Caster, spell damage mod pct, base: min pct, limit: max pct, Note: applied after critical hits has been calculated.
#define SE_FcDamageAmtCrit 303 // implemented, @Fc, On Caster, spell damage mod flat amt, base: amt
#define SE_OffhandRiposteFail 304 // implemented as bonus - enemy cannot riposte offhand attacks
#define SE_MitigateDamageShield 305 // implemented - off hand attacks only (Shielding Resistance)
//#define SE_ArmyOfTheDead 306 // *not implemented NecroAA - This ability calls up to five shades of nearby corpses back to life to serve the necromancer. The soulless abominations will mindlessly fight the target until called back to the afterlife some time later. The first rank summons up to three shades that serve for 60 seconds, and each additional rank adds one more possible shade and increases their duration by 15 seconds
#define SE_ArmyOfTheDead 306 // implemented, @Pets, summon multiple temporary pets from nearby corpses that last a set duration, base: amount of corpses that a pet can summon from, limit: none, max: duration (seconds). Note: max range of corpse is 250.
//#define SE_Appraisal 307 // *not implemented Rogue AA - This ability allows you to estimate the selling price of an item you are holding on your cursor.
#define SE_ZoneSuspendMinion 308 // implemented, @Pet, allow suspended pets to be resummoned upon zoning, base: 1, limit: none, max: none, Calc: Bool
#define SE_GateCastersBindpoint 309 // implemented - Gate to casters bind point

View File

@ -213,7 +213,7 @@ void Mob::TypesTemporaryPets(uint32 typesid, Mob *targ, const char *name_overrid
glm::vec2(5, 5), glm::vec2(-5, 5), glm::vec2(5, -5), glm::vec2(-5, -5),
glm::vec2(10, 10), glm::vec2(-10, 10), glm::vec2(10, -10), glm::vec2(-10, -10),
glm::vec2(8, 8), glm::vec2(-8, 8), glm::vec2(8, -8), glm::vec2(-8, -8)
};;
};
while(summon_count > 0) {
int pet_duration = pet.duration;
@ -273,63 +273,94 @@ void Mob::TypesTemporaryPets(uint32 typesid, Mob *targ, const char *name_overrid
delete made_npc;
}
void Mob::WakeTheDead(uint16 spell_id, Mob *target, uint32 duration)
{
Corpse *CorpseToUse = nullptr;
CorpseToUse = entity_list.GetClosestCorpse(this, nullptr);
void Mob::WakeTheDead(uint16 spell_id, Corpse *corpse_to_use, Mob *target, uint32 duration) {
if(!CorpseToUse)
/*
SPA 299 Wake The Dead, 'animateDead' should be temp pet, always spawns 1 pet from corpse, max value is duration
SPA 306 Wake The Dead, 'animateDead#' should be temp pet, base is amount of pets from indivual corpses, max value is duration
Max range for closet corpse is 250 units.
TODO: Should use temp pets
*/
if (!corpse_to_use) {
return;
}
//assuming we have pets in our table; we take the first pet as a base type.
const NPCType *base_type = content_db.LoadNPCTypesData(500);
auto make_npc = new NPCType;
memcpy(make_npc, base_type, sizeof(NPCType));
/* TODO: Does WTD use pet focus?
int act_power = 0;
//combat stats
make_npc->AC = ((GetLevel() * 7) + 550);
make_npc->ATK = GetLevel();
make_npc->max_dmg = (GetLevel() * 4) + 2;
make_npc->min_dmg = 1;
if (IsClient()) {
act_power = CastToClient()->GetFocusEffect(focusPetPower, spell_id);
act_power = CastToClient()->mod_pet_power(act_power, spell_id);
}
*/
//base stats
make_npc->current_hp = (GetLevel() * 55);
make_npc->max_hp = (GetLevel() * 55);
make_npc->STR = 85 + (GetLevel() * 3);
make_npc->STA = 85 + (GetLevel() * 3);
make_npc->DEX = 85 + (GetLevel() * 3);
make_npc->AGI = 85 + (GetLevel() * 3);
make_npc->INT = 85 + (GetLevel() * 3);
make_npc->WIS = 85 + (GetLevel() * 3);
make_npc->CHA = 85 + (GetLevel() * 3);
make_npc->MR = 25;
make_npc->FR = 25;
make_npc->CR = 25;
make_npc->DR = 25;
make_npc->PR = 25;
SwarmPet_Struct pet;
pet.count = 1;
pet.duration = 1;
//pet.duration += GetFocusEffect(focusSwarmPetDuration, spell_id) / 1000; //TODO: Does WTD use pet focus?
pet.npc_id = WAKE_THE_DEAD_NPCTYPEID;
NPCType *made_npc = nullptr;
const NPCType *npc_type = content_db.LoadNPCTypesData(WAKE_THE_DEAD_NPCTYPEID);
if (npc_type == nullptr) {
//log write
LogError("Unknown npc type for 'Wake the Dead' swarm pet spell id: [{}]", spell_id);
Message(0, "Unable to find pet!");
return;
}
made_npc = new NPCType;
memcpy(made_npc, npc_type, sizeof(NPCType));
//level class and gender
make_npc->level = GetLevel();
make_npc->class_ = CorpseToUse->class_;
make_npc->race = CorpseToUse->race;
make_npc->gender = CorpseToUse->gender;
make_npc->loottable_id = 0;
//name
char NewName[64];
sprintf(NewName, "%s`s Animated Corpse", GetCleanName());
strcpy(make_npc->name, NewName);
strcpy(made_npc->name, NewName);
npc_type = made_npc;
//combat stats
made_npc->AC = ((GetLevel() * 7) + 550);
made_npc->ATK = GetLevel();
made_npc->max_dmg = (GetLevel() * 4) + 2;
made_npc->min_dmg = 1;
//base stats
made_npc->current_hp = (GetLevel() * 55);
made_npc->max_hp = (GetLevel() * 55);
made_npc->STR = 85 + (GetLevel() * 3);
made_npc->STA = 85 + (GetLevel() * 3);
made_npc->DEX = 85 + (GetLevel() * 3);
made_npc->AGI = 85 + (GetLevel() * 3);
made_npc->INT = 85 + (GetLevel() * 3);
made_npc->WIS = 85 + (GetLevel() * 3);
made_npc->CHA = 85 + (GetLevel() * 3);
made_npc->MR = 25;
made_npc->FR = 25;
made_npc->CR = 25;
made_npc->DR = 25;
made_npc->PR = 25;
//level class and gender
made_npc->level = GetLevel();
made_npc->class_ = corpse_to_use->class_;
made_npc->race = corpse_to_use->race;
made_npc->gender = corpse_to_use->gender;
made_npc->loottable_id = 0;
//appearance
make_npc->beard = CorpseToUse->beard;
make_npc->beardcolor = CorpseToUse->beardcolor;
make_npc->eyecolor1 = CorpseToUse->eyecolor1;
make_npc->eyecolor2 = CorpseToUse->eyecolor2;
make_npc->haircolor = CorpseToUse->haircolor;
make_npc->hairstyle = CorpseToUse->hairstyle;
make_npc->helmtexture = CorpseToUse->helmtexture;
make_npc->luclinface = CorpseToUse->luclinface;
make_npc->size = CorpseToUse->size;
make_npc->texture = CorpseToUse->texture;
made_npc->beard = corpse_to_use->beard;
made_npc->beardcolor = corpse_to_use->beardcolor;
made_npc->eyecolor1 = corpse_to_use->eyecolor1;
made_npc->eyecolor2 = corpse_to_use->eyecolor2;
made_npc->haircolor = corpse_to_use->haircolor;
made_npc->hairstyle = corpse_to_use->hairstyle;
made_npc->helmtexture = corpse_to_use->helmtexture;
made_npc->luclinface = corpse_to_use->luclinface;
made_npc->size = corpse_to_use->size;
made_npc->texture = corpse_to_use->texture;
//cast stuff.. based off of PEQ's if you want to change
//it you'll have to mod this code, but most likely
@ -337,130 +368,144 @@ void Mob::WakeTheDead(uint16 spell_id, Mob *target, uint32 duration)
//part of their spell list; can't think of any smooth
//way to do this
//some basic combat mods here too since it's convienent
switch(CorpseToUse->class_)
switch (corpse_to_use->class_)
{
case CLERIC:
make_npc->npc_spells_id = 1;
made_npc->npc_spells_id = 1;
break;
case WIZARD:
make_npc->npc_spells_id = 2;
made_npc->npc_spells_id = 2;
break;
case NECROMANCER:
make_npc->npc_spells_id = 3;
made_npc->npc_spells_id = 3;
break;
case MAGICIAN:
make_npc->npc_spells_id = 4;
made_npc->npc_spells_id = 4;
break;
case ENCHANTER:
make_npc->npc_spells_id = 5;
made_npc->npc_spells_id = 5;
break;
case SHAMAN:
make_npc->npc_spells_id = 6;
made_npc->npc_spells_id = 6;
break;
case DRUID:
make_npc->npc_spells_id = 7;
made_npc->npc_spells_id = 7;
break;
case PALADIN:
//SPECATK_TRIPLE
strcpy(make_npc->special_abilities, "6,1");
make_npc->current_hp = make_npc->current_hp * 150 / 100;
make_npc->max_hp = make_npc->max_hp * 150 / 100;
make_npc->npc_spells_id = 8;
strcpy(made_npc->special_abilities, "6,1");
made_npc->current_hp = made_npc->current_hp * 150 / 100;
made_npc->max_hp = made_npc->max_hp * 150 / 100;
made_npc->npc_spells_id = 8;
break;
case SHADOWKNIGHT:
strcpy(make_npc->special_abilities, "6,1");
make_npc->current_hp = make_npc->current_hp * 150 / 100;
make_npc->max_hp = make_npc->max_hp * 150 / 100;
make_npc->npc_spells_id = 9;
strcpy(made_npc->special_abilities, "6,1");
made_npc->current_hp = made_npc->current_hp * 150 / 100;
made_npc->max_hp = made_npc->max_hp * 150 / 100;
made_npc->npc_spells_id = 9;
break;
case RANGER:
strcpy(make_npc->special_abilities, "7,1");
make_npc->current_hp = make_npc->current_hp * 135 / 100;
make_npc->max_hp = make_npc->max_hp * 135 / 100;
make_npc->npc_spells_id = 10;
strcpy(made_npc->special_abilities, "7,1");
made_npc->current_hp = made_npc->current_hp * 135 / 100;
made_npc->max_hp = made_npc->max_hp * 135 / 100;
made_npc->npc_spells_id = 10;
break;
case BARD:
strcpy(make_npc->special_abilities, "6,1");
make_npc->current_hp = make_npc->current_hp * 110 / 100;
make_npc->max_hp = make_npc->max_hp * 110 / 100;
make_npc->npc_spells_id = 11;
strcpy(made_npc->special_abilities, "6,1");
made_npc->current_hp = made_npc->current_hp * 110 / 100;
made_npc->max_hp = made_npc->max_hp * 110 / 100;
made_npc->npc_spells_id = 11;
break;
case BEASTLORD:
strcpy(make_npc->special_abilities, "7,1");
make_npc->current_hp = make_npc->current_hp * 110 / 100;
make_npc->max_hp = make_npc->max_hp * 110 / 100;
make_npc->npc_spells_id = 12;
strcpy(made_npc->special_abilities, "7,1");
made_npc->current_hp = made_npc->current_hp * 110 / 100;
made_npc->max_hp = made_npc->max_hp * 110 / 100;
made_npc->npc_spells_id = 12;
break;
case ROGUE:
strcpy(make_npc->special_abilities, "7,1");
make_npc->max_dmg = make_npc->max_dmg * 150 /100;
make_npc->current_hp = make_npc->current_hp * 110 / 100;
make_npc->max_hp = make_npc->max_hp * 110 / 100;
strcpy(made_npc->special_abilities, "7,1");
made_npc->max_dmg = made_npc->max_dmg * 150 / 100;
made_npc->current_hp = made_npc->current_hp * 110 / 100;
made_npc->max_hp = made_npc->max_hp * 110 / 100;
break;
case MONK:
strcpy(make_npc->special_abilities, "7,1");
make_npc->max_dmg = make_npc->max_dmg * 150 /100;
make_npc->current_hp = make_npc->current_hp * 135 / 100;
make_npc->max_hp = make_npc->max_hp * 135 / 100;
strcpy(made_npc->special_abilities, "7,1");
made_npc->max_dmg = made_npc->max_dmg * 150 / 100;
made_npc->current_hp = made_npc->current_hp * 135 / 100;
made_npc->max_hp = made_npc->max_hp * 135 / 100;
break;
case WARRIOR:
case BERSERKER:
strcpy(make_npc->special_abilities, "7,1");
make_npc->max_dmg = make_npc->max_dmg * 150 /100;
make_npc->current_hp = make_npc->current_hp * 175 / 100;
make_npc->max_hp = make_npc->max_hp * 175 / 100;
strcpy(made_npc->special_abilities, "7,1");
made_npc->max_dmg = made_npc->max_dmg * 150 / 100;
made_npc->current_hp = made_npc->current_hp * 175 / 100;
made_npc->max_hp = made_npc->max_hp * 175 / 100;
break;
default:
make_npc->npc_spells_id = 0;
made_npc->npc_spells_id = 0;
break;
}
make_npc->loottable_id = 0;
make_npc->merchanttype = 0;
make_npc->d_melee_texture1 = 0;
make_npc->d_melee_texture2 = 0;
made_npc->loottable_id = 0;
made_npc->merchanttype = 0;
made_npc->d_melee_texture1 = 0;
made_npc->d_melee_texture2 = 0;
auto npca = new NPC(make_npc, 0, GetPosition(), GravityBehavior::Water);
if(!npca->GetSwarmInfo()){
auto nSI = new SwarmPet;
npca->SetSwarmInfo(nSI);
npca->GetSwarmInfo()->duration = new Timer(duration*1000);
}
else{
npca->GetSwarmInfo()->duration->Start(duration*1000);
}
int summon_count = 0;
summon_count = pet.count;
npca->StartSwarmTimer(duration * 1000);
npca->GetSwarmInfo()->owner_id = GetID();
NPC* swarm_pet_npc = nullptr;
//TODO: potenitally add support for multiple pets per corpse
while (summon_count > 0) {
int pet_duration = duration;
//give the pet somebody to "love"
if(target != nullptr){
npca->AddToHateList(target, 100000);
npca->GetSwarmInfo()->target = target->GetID();
}
//gear stuff, need to make sure there's
//no situation where this stuff can be duped
for (int x = EQ::invslot::EQUIPMENT_BEGIN; x <= EQ::invslot::EQUIPMENT_END; x++)
{
uint32 sitem = 0;
sitem = CorpseToUse->GetWornItem(x);
if(sitem){
const EQ::ItemData * itm = database.GetItem(sitem);
npca->AddLootDrop(itm, &npca->itemlist, NPC::NewLootDropEntry(), true);
NPCType *npc_dup = nullptr;
if (made_npc != nullptr) {
npc_dup = new NPCType;
memcpy(npc_dup, made_npc, sizeof(NPCType));
}
swarm_pet_npc = new NPC(
(npc_dup != nullptr) ? npc_dup : npc_type,
0, corpse_to_use->GetPosition(),GravityBehavior::Water);
swarm_pet_npc->SetFollowID(GetID());
if (!swarm_pet_npc->GetSwarmInfo()) {
auto nSI = new SwarmPet;
swarm_pet_npc->SetSwarmInfo(nSI);
swarm_pet_npc->GetSwarmInfo()->duration = new Timer(pet_duration * 1000);
}
else {
swarm_pet_npc->GetSwarmInfo()->duration->Start(pet_duration * 1000);
}
swarm_pet_npc->StartSwarmTimer(pet_duration * 1000);
//removing this prevents the pet from attacking
swarm_pet_npc->GetSwarmInfo()->owner_id = GetID();
//give the pets somebody to "love"
if (target != nullptr) {
swarm_pet_npc->AddToHateList(target, 10000, 1000);
swarm_pet_npc->GetSwarmInfo()->target = 0;
}
//we allocated a new NPC type object, give the NPC ownership of that memory
if (npc_dup != nullptr)
swarm_pet_npc->GiveNPCTypeData(npc_dup);
entity_list.AddNPC(swarm_pet_npc, true, true);
summon_count--;
}
//we allocated a new NPC type object, give the NPC ownership of that memory
if(make_npc != nullptr)
npca->GiveNPCTypeData(make_npc);
entity_list.AddNPC(npca, true, true);
//the target of these swarm pets will take offense to being cast on...
if(target != nullptr)
if (target != nullptr)
target->AddToHateList(this, 1, 0);
// The other pointers we make are handled elsewhere.
delete made_npc;
}
void Client::ResetAA() {

View File

@ -2,6 +2,7 @@
#define AA_H
#define MAX_SWARM_PETS 12 //this can change as long as you make more coords (swarm_pet_x/swarm_pet_y)
#define WAKE_THE_DEAD_NPCTYPEID 500 //We use first pet in pets table as a template
typedef enum {
aaActionNone = 0,

View File

@ -2915,8 +2915,9 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b
}
}
if (other->GetTempPetCount())
if (other->GetTempPetCount()) {
entity_list.AddTempPetsToHateList(other, this, bFrenzy);
}
if (!wasengaged) {
if (IsNPC() && other->IsClient() && other->CastToClient())

View File

@ -4329,6 +4329,56 @@ Corpse *EntityList::GetClosestCorpse(Mob *sender, const char *Name)
return ClosestCorpse;
}
void EntityList::TryWakeTheDead(Mob *sender, Mob *target, int32 spell_id, uint32 max_distance, uint32 duration, uint32 amount_pets)
{
if (!sender) {
return;
}
std::vector<int> used_corpse_list;
for (int i = 0; i < amount_pets; i++)
{
uint32 CurrentDistance, ClosestDistance = 4294967295u;
Corpse *CurrentCorpse, *ClosestCorpse = nullptr;
auto it = corpse_list.begin();
while (it != corpse_list.end()) {
CurrentCorpse = it->second;
++it;
bool corpse_already_used = false;
for (auto itr = used_corpse_list.begin(); itr != used_corpse_list.end(); ++itr) {
if ((*itr) && (*itr) == CurrentCorpse->GetID()) {
corpse_already_used = true;
continue;
}
}
if (corpse_already_used) {
continue;
}
CurrentDistance = static_cast<uint32>(sender->CalculateDistance(CurrentCorpse->GetX(), CurrentCorpse->GetY(), CurrentCorpse->GetZ()));
if (max_distance && CurrentDistance > max_distance) {
continue;
}
if (CurrentDistance < ClosestDistance) {
ClosestDistance = CurrentDistance;
ClosestCorpse = CurrentCorpse;
}
}
if (ClosestCorpse) {
sender->WakeTheDead(spell_id, ClosestCorpse, target, duration);
used_corpse_list.push_back(ClosestCorpse->GetID());
}
}
}
void EntityList::ForceGroupUpdate(uint32 gid)
{
auto it = client_list.begin();

View File

@ -478,6 +478,7 @@ public:
uint32 CheckNPCsClose(Mob *center);
Corpse* GetClosestCorpse(Mob* sender, const char *Name);
void TryWakeTheDead(Mob* sender, Mob* target, int32 spell_id, uint32 max_distance, uint32 duration, uint32 amount_pets);
NPC* GetClosestBanker(Mob* sender, uint32 &distance);
void CameraEffect(uint32 duration, uint32 intensity);
Mob* GetClosestMobByBodyType(Mob* sender, bodyType BodyType, bool skip_client_pets=false);

View File

@ -800,7 +800,7 @@ public:
inline void Amnesia(bool newval) { amnesiad = newval; }
void TemporaryPets(uint16 spell_id, Mob *target, const char *name_override = nullptr, uint32 duration_override = 0, bool followme=true, bool sticktarg=false, uint16 *controlled_pet_id = nullptr);
void TypesTemporaryPets(uint32 typesid, Mob *target, const char *name_override = nullptr, uint32 duration_override = 0, bool followme=true, bool sticktarg=false);
void WakeTheDead(uint16 spell_id, Mob *target, uint32 duration);
void WakeTheDead(uint16 spell_id, Corpse *corpse_to_use, Mob *target, uint32 duration);
void Spin();
void Kill();
bool PassCharismaCheck(Mob* caster, uint16 spell_id);

View File

@ -3082,25 +3082,23 @@ void NPC::ClearLastName()
void NPC::DepopSwarmPets()
{
if (GetSwarmInfo()) {
if (GetSwarmInfo()->duration->Check(false)){
Mob* owner = entity_list.GetMobID(GetSwarmInfo()->owner_id);
if (owner)
if (owner) {
owner->SetTempPetCount(owner->GetTempPetCount() - 1);
}
Depop();
return;
}
//This is only used for optional quest or rule derived behavior now if you force a temp pet on a specific target.
if (GetSwarmInfo()->target) {
Mob *targMob = entity_list.GetMob(GetSwarmInfo()->target);
if(!targMob || (targMob && targMob->IsCorpse())){
Mob* owner = entity_list.GetMobID(GetSwarmInfo()->owner_id);
if (owner)
if (owner) {
owner->SetTempPetCount(owner->GetTempPetCount() - 1);
}
Depop();
return;
}

View File

@ -2295,10 +2295,35 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
//meh dupe issue with npc casting this
if(caster && caster->IsClient()){
int dur = spells[spell_id].max_value[i];
if (!dur)
if (!dur) {
dur = 60;
}
caster->WakeTheDead(spell_id, caster->GetTarget(), dur);
Mob* m_target = caster->GetTarget();
if (m_target) {
entity_list.TryWakeTheDead(caster, m_target, spell_id, 250, dur, 1);
}
}
break;
}
case SE_ArmyOfTheDead:
{
if (caster && caster->IsClient()) {
int dur = spells[spell_id].max_value[i];
if (!dur) {
dur = 60;
}
int amount = spells[spell_id].base_value[i];
if (!amount) {
amount = 1;
}
Mob* m_target = caster->GetTarget();
if (m_target) {
entity_list.TryWakeTheDead(caster, m_target, spell_id, 250, dur, amount);
}
}
break;
}