WIP on auras

Lots to do still

Normal buffing auras currently work for the most part
This commit is contained in:
Michael Cook (mackal) 2017-07-14 02:05:35 -04:00
parent be0374d197
commit 94038ebb75
22 changed files with 692 additions and 4 deletions

View File

@ -408,6 +408,7 @@ N(OP_ReloadUI),
N(OP_RemoveAllDoors),
N(OP_RemoveBlockedBuffs),
N(OP_RemoveNimbusEffect),
N(OP_RemoveTrap),
N(OP_Report),
N(OP_ReqClientSpawn),
N(OP_ReqNewZone),
@ -523,6 +524,7 @@ N(OP_TributeToggle),
N(OP_TributeUpdate),
N(OP_Untargetable),
N(OP_UpdateAA),
N(OP_UpdateAura),
N(OP_UpdateLeadershipAA),
N(OP_VetClaimReply),
N(OP_VetClaimRequest),

View File

@ -5332,6 +5332,24 @@ struct fling_struct {
/* 28 */
};
// used when action == 0
struct AuraCreate_Struct {
/* 00 */ uint32 action; // 0 = add, 1 = delete, 2 = reset
/* 04 */ uint32 type; // unsure -- normal auras show 1 clicky (ex. Circle of Power) show 0
/* 08 */ char aura_name[64];
/* 72 */ uint32 entity_id;
/* 76 */ uint32 icon;
/* 80 */
};
// used when action == 1
struct AuraDestory_Struct {
/* 00 */ uint32 action; // 0 = add, 1 = delete, 2 = reset
/* 04 */ uint32 entity_id;
/* 08 */
};
// I think we can assume it's just action for 2, client doesn't seem to do anything with the rest of the data in that case
// Restore structure packing to default
#pragma pack()

View File

@ -1674,6 +1674,7 @@ void SharedDatabase::LoadSpells(void *data, int max_spells) {
for (y = 0; y < 16; y++)
sp[tempid].deities[y]=atoi(row[126+y]);
sp[tempid].new_icon=atoi(row[144]);
sp[tempid].uninterruptable=atoi(row[146]) != 0;
sp[tempid].ResistDiff=atoi(row[147]);
sp[tempid].dot_stacking_exempt = atoi(row[148]) != 0;

View File

@ -562,9 +562,9 @@ typedef enum {
#define SE_LimitManaMin 348 // implemented
#define SE_ShieldEquipDmgMod 349 // implemented[AA] Increase melee base damage (indirectly increasing hate) when wearing a shield.
#define SE_ManaBurn 350 // implemented - Drains mana for damage/heal at a defined ratio up to a defined maximum amount of mana.
//#define SE_PersistentEffect 351 // *not implemented. creates a trap/totem that casts a spell (spell id + base1?) when anything comes near it. can probably make a beacon for this
//#define SE_IncreaseTrapCount 352 // *not implemented - looks to be some type of invulnerability? Test ITC (8755)
//#define SE_AdditionalAura 353 // *not implemented - allows use of more than 1 aura, aa effect
#define SE_PersistentEffect 351 // *not implemented. creates a trap/totem that casts a spell (spell id + base1?) when anything comes near it. can probably make a beacon for this
#define SE_IncreaseTrapCount 352 // *not implemented - looks to be some type of invulnerability? Test ITC (8755)
#define SE_AdditionalAura 353 // *not implemented - allows use of more than 1 aura, aa effect
//#define SE_DeactivateAllTraps 354 // *not implemented - looks to be some type of invulnerability? Test DAT (8757)
//#define SE_LearnTrap 355 // *not implemented - looks to be some type of invulnerability? Test LT (8758)
//#define SE_ChangeTriggerType 356 // not used
@ -757,7 +757,7 @@ struct SPDat_Spell_Struct
// -- DIETY_BERTOXXULOUS ... DIETY_VEESHAN
/* 142 */ //int8 npc_no_cast; // 142: between 0 & 100 -- NPC_NO_CAST
/* 143 */ //int ai_pt_bonus; // 143: always set to 0, client doesn't save this -- AI_PT_BONUS
/* 144 */ //int16 new_icon // Spell icon used by the client in uifiles/default/spells??.tga, both for spell gems & buff window. Looks to depreciate icon & memicon -- NEW_ICON
/* 144 */ int16 new_icon; // Spell icon used by the client in uifiles/default/spells??.tga, both for spell gems & buff window. Looks to depreciate icon & memicon -- NEW_ICON
/* 145 */ //int16 spellanim; // Doesn't look like it's the same as #doanim, so not sure what this is, particles I think -- SPELL_EFFECT_INDEX
/* 146 */ bool uninterruptable; // Looks like anything != 0 is uninterruptable. Values are mostly -1, 0, & 1 (Fetid Breath = 90?) -- NO_INTERRUPT
/* 147 */ int16 ResistDiff; // -- RESIST_MOD

View File

@ -675,3 +675,7 @@ OP_RAWOutOfSession=0x0000
# we need to document the differences between these packets to make identifying them easier
OP_Some3ByteHPUpdate=0x0000 # initial HP update for mobs
OP_InitialHPUpdate=0x0000
#aura related
OP_UpdateAura=0x1456
OP_RemoveTrap=0x71da

View File

@ -0,0 +1,13 @@
CREATE TABLE `auras` (
`type` INT(10) NOT NULL,
`npc_type` INT(10) NOT NULL,
`name` VARCHAR(64) NOT NULL,
`spell_id` INT(10) NOT NULL,
`distance` INT(10) NOT NULL DEFAULT 60,
`aura_type` INT(10) NOT NULL DEFAULT 1,
`spawn_type` INT(10) NOT NULL DEFAULT 0,
`movement` INT(10) NOT NULL DEFAULT 0,
`duration` INT(10) NOT NULL DEFAULT 5400,
`icon` INT(10) NOT NULL DEFAULT -1,
PRIMARY KEY(`type`)
)

View File

@ -5,6 +5,7 @@ SET(zone_sources
aa_ability.cpp
aggro.cpp
aggromanager.cpp
aura.cpp
attack.cpp
beacon.cpp
bonuses.cpp
@ -136,6 +137,7 @@ SET(zone_headers
aa.h
aa_ability.h
aggromanager.h
aura.h
basic_functions.h
beacon.h
bot.h

435
zone/aura.cpp Normal file
View File

@ -0,0 +1,435 @@
#include "../common/string_util.h"
#include "aura.h"
#include "client.h"
#include "string_ids.h"
#include "raids.h"
Aura::Aura(NPCType *type_data, Mob *owner, AuraRecord &record)
: NPC(type_data, 0, owner->GetPosition(), FlyMode3), spell_id(record.spell_id), distance(record.distance),
remove_timer(record.duration), movement_timer(100), process_timer(100)
{
GiveNPCTypeData(type_data); // we will delete this later on
m_owner = owner->GetID();
if (record.aura_type < static_cast<int>(AuraType::Max))
type = static_cast<AuraType>(record.aura_type);
else
type = AuraType::OnAllGroupMembers;
if (record.spawn_type < static_cast<int>(AuraSpawns::Max))
spawn_type = static_cast<AuraSpawns>(record.spawn_type);
else
spawn_type = AuraSpawns::GroupMembers;
if (record.movement < static_cast<int>(AuraMovement::Max))
movement_type = static_cast<AuraMovement>(record.movement);
else
movement_type = AuraMovement::Follow;
switch (type) {
case AuraType::OnAllFriendlies:
process_func = &Aura::ProcessOnAllFriendlies;
break;
case AuraType::OnAllGroupMembers:
process_func = &Aura::ProcessOnAllGroupMembers;
break;
case AuraType::OnGroupMembersPets:
process_func = &Aura::ProcessOnGroupMembersPets;
break;
case AuraType::Totem:
process_func = &Aura::ProcessTotem;
break;
case AuraType::EnterTrap:
process_func = &Aura::ProcessEnterTrap;
break;
case AuraType::ExitTrap:
process_func = &Aura::ProcessExitTrap;
break;
default:
process_func = nullptr;
}
}
void Aura::ProcessOnAllFriendlies(Mob *owner)
{
Shout("Stub 1");
}
void Aura::ProcessOnAllGroupMembers(Mob *owner)
{
if (!process_timer.Check())
return;
auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline
std::set<int> delayed_remove;
if (owner->IsRaidGrouped() && owner->IsClient()) { // currently raids are just client, but safety check
auto raid = owner->GetRaid();
if (raid == nullptr) { // well shit
Depop();
return;
}
auto group_id = raid->GetGroup(owner->CastToClient());
// some lambdas so the for loop is less horrible ...
auto verify_raid_client = [&raid, &group_id, this](Client *c) {
auto idx = raid->GetPlayerIndex(c);
if (c->GetID() == m_owner) {
return DistanceSquared(GetPosition(), c->GetPosition()) <= distance;
} else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) {
return false;
} else if (DistanceSquared(GetPosition(), c->GetPosition()) > distance) {
return false;
}
return true;
};
auto verify_raid_client_pet = [&raid, &group_id, this](Mob *m) {
auto idx = raid->GetPlayerIndex(m->GetOwner()->CastToClient());
if (m->GetOwner()->GetID() == m_owner) {
return DistanceSquared(GetPosition(), m->GetPosition()) <= distance;
} else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) {
return false;
} else if (DistanceSquared(GetPosition(), m->GetPosition()) > distance) {
return false;
}
return true;
};
auto verify_raid_client_swarm = [&raid, &group_id, this](NPC *n) {
auto owner = entity_list.GetMob(n->GetSwarmOwner());
if (owner == nullptr)
return false;
auto idx = raid->GetPlayerIndex(owner->CastToClient());
if (owner->GetID() == m_owner) {
return DistanceSquared(GetPosition(), n->GetPosition()) <= distance;
} else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) {
return false;
} else if (DistanceSquared(GetPosition(), n->GetPosition()) > distance) {
return false;
}
return true;
};
for (auto &e : mob_list) {
auto mob = e.second;
// step 1: check if we're already managing this NPC's buff
auto it = casted_on.find(mob->GetID());
if (it != casted_on.end()) {
// verify still good!
if (mob->IsClient()) {
if (!verify_raid_client(mob->CastToClient()))
delayed_remove.insert(mob->GetID());
} else if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner()) {
if (!verify_raid_client_pet(mob))
delayed_remove.insert(mob->GetID());
} else if (mob->IsNPC() && mob->IsPetOwnerClient()) {
auto npc = mob->CastToNPC();
if (!verify_raid_client_swarm(npc))
delayed_remove.insert(mob->GetID());
}
} else { // we're not on it!
if (mob->IsClient() && verify_raid_client(mob->CastToClient())) {
casted_on.insert(mob->GetID());
SpellFinished(spell_id, mob);
} else if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner() && verify_raid_client_pet(mob)) {
casted_on.insert(mob->GetID());
SpellFinished(spell_id, mob);
} else if (mob->IsNPC() && mob->IsPetOwnerClient()) {
auto npc = mob->CastToNPC();
if (verify_raid_client_swarm(npc)) {
casted_on.insert(mob->GetID());
SpellFinished(spell_id, mob);
}
}
}
}
} else if (owner->IsGrouped()) {
auto group = owner->GetGroup();
if (group == nullptr) { // uh oh
Depop();
return;
}
// lambdas to make for loop less ugly
auto verify_group_pet = [&group, this](Mob *m) {
auto owner = m->GetOwner();
if (owner != nullptr && group->IsGroupMember(owner) && DistanceSquared(GetPosition(), m->GetPosition()) <= distance)
return true;
return false;
};
auto verify_group_swarm = [&group, this](NPC *n) {
auto owner = entity_list.GetMob(n->GetSwarmOwner());
if (owner != nullptr && group->IsGroupMember(owner) && DistanceSquared(GetPosition(), n->GetPosition()) <= distance)
return true;
return false;
};
for (auto &e : mob_list) {
auto mob = e.second;
auto it = casted_on.find(mob->GetID());
if (it != casted_on.end()) { // make sure we're still valid
if (mob->IsPet()) {
if (!verify_group_pet(mob))
delayed_remove.insert(mob->GetID());
} else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo()) {
if (!verify_group_swarm(mob->CastToNPC()))
delayed_remove.insert(mob->GetID());
} else if (!group->IsGroupMember(mob) || DistanceSquared(GetPosition(), mob->GetPosition()) > distance) {
delayed_remove.insert(mob->GetID());
}
} else { // not on, check if we should be!
if (mob->IsPet() && verify_group_pet(mob)) {
casted_on.insert(mob->GetID());
SpellFinished(spell_id, mob);
} else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo() && verify_group_swarm(mob->CastToNPC())) {
casted_on.insert(mob->GetID());
SpellFinished(spell_id, mob);
} else if (group->IsGroupMember(mob) && DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) {
casted_on.insert(mob->GetID());
SpellFinished(spell_id, mob);
}
}
}
} else {
auto verify_solo = [&owner, this](Mob *m) {
if (m->IsPet() && m->GetOwnerID() == owner->GetID())
return true;
else if (m->IsNPC() && m->CastToNPC()->GetSwarmOwner() == owner->GetID())
return true;
else if (m->GetID() == owner->GetID())
return true;
else
return false;
};
for (auto &e : mob_list) {
auto mob = e.second;
auto it = casted_on.find(mob->GetID());
bool good = verify_solo(mob);
if (it != casted_on.end()) { // make sure still valid
if (!good || DistanceSquared(GetPosition(), mob->GetPosition()) > distance) {
delayed_remove.insert(mob->GetID());
}
} else if (good && DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) {
casted_on.insert(mob->GetID());
SpellFinished(spell_id, mob);
}
}
}
bool is_buff = IsBuffSpell(spell_id);
for (auto &e : delayed_remove) {
auto mob = entity_list.GetMob(e);
if (mob != nullptr && is_buff) // some auras cast instant spells so no need to remove
mob->BuffFadeBySpellIDAndCaster(spell_id, GetID());
casted_on.erase(e);
}
if (cast_timer.Enabled() || !cast_timer.Check())
return;
// TODO: some auras have to recast (DRU for example, non-buff too)
/* for (auto &e : casted_on) {
auto mob = entity_list.GetMob(e);
if (mob != nullptr && (!is_buff || !mob->IsAffectedByBuff(spell_id)))
}*/
}
void Aura::ProcessOnGroupMembersPets(Mob *owner)
{
Shout("Stub 3");
}
void Aura::ProcessTotem(Mob *owner)
{
Shout("Stub 4");
}
void Aura::ProcessEnterTrap(Mob *owner)
{
Shout("Stub 5");
}
void Aura::ProcessExitTrap(Mob *owner)
{
Shout("Stub 6");
}
bool Aura::Process()
{
// Aura::Depop clears buffs
if (p_depop)
return false;
auto owner = entity_list.GetMob(m_owner);
if (owner == nullptr) {
Depop();
return true;
}
if (movement_type == AuraMovement::Follow && GetPosition() != owner->GetPosition() && movement_timer.Check()) {
m_Position = owner->GetPosition();
SendPosUpdate();
}
// TODO: waypoints?
if (process_func)
process_func(*this, owner);
// TODO: quest calls
return true;
}
void Aura::Depop(bool unused)
{
// TODO: clean up buffs
p_depop = true;
}
void Mob::MakeAura(uint16 spell_id)
{
// TODO: verify room in AuraMgr
if (!IsValidSpell(spell_id))
return;
AuraRecord record;
if (!database.GetAuraEntry(spell_id, record)) {
Message(13, "Unable to find data for aura %s", spells[spell_id].name);
Log(Logs::General, Logs::Error, "Unable to find data for aura %d, check auras table.", spell_id);
return;
}
if (!IsValidSpell(record.spell_id)) {
Message(13, "Casted spell (%d) is not valid for aura %s", record.spell_id, spells[spell_id].name);
Log(Logs::General, Logs::Error, "Casted spell (%d) is not valid for aura %d, check auras table.",
record.spell_id, spell_id);
return;
}
if (record.aura_type > static_cast<int>(AuraType::Max)) {
return; // TODO: log
}
bool trap = false;
switch (static_cast<AuraType>(record.aura_type)) {
case AuraType::ExitTrap:
case AuraType::EnterTrap:
case AuraType::Totem:
trap = true;
break;
default:
trap = false;
break;
}
if (!CanSpawnAura(trap))
return;
const auto base = database.LoadNPCTypesData(record.npc_type);
if (base == nullptr) {
Message(13, "Unable to load NPC data for aura %s", spells[spell_id].teleport_zone);
Log(Logs::General, Logs::Error,
"Unable to load NPC data for aura %s (NPC ID %d), check auras and npc_types tables.",
spells[spell_id].teleport_zone, record.npc_type);
return;
}
auto npc_type = new NPCType;
memcpy(npc_type, base, sizeof(NPCType));
strn0cpy(npc_type->name, record.name, 64);
auto npc = new Aura(npc_type, this, record);
entity_list.AddNPC(npc, true, true);
if (trap)
AddTrap(npc, record);
else
AddAura(npc, record);
}
bool ZoneDatabase::GetAuraEntry(uint16 spell_id, AuraRecord &record)
{
auto query = StringFormat("SELECT npc_type, name, spell_id, distance, aura_type, spawn_type, movement, "
"duration, icon FROM auras WHERE type='%d'",
spell_id);
auto results = QueryDatabase(query);
if (!results.Success())
return false;
if (results.RowCount() != 1)
return false;
auto row = results.begin();
record.npc_type = atoi(row[0]);
strn0cpy(record.name, row[1], 64);
record.spell_id = atoi(row[2]);
record.distance = atoi(row[3]);
record.distance *= record.distance; // so we can avoid sqrt
record.aura_type = atoi(row[4]);
record.spawn_type = atoi(row[5]);
record.movement = atoi(row[6]);
record.duration = atoi(row[7]) * 1000; // DB is in seconds
record.icon = atoi(row[8]);
return true;
}
void Mob::AddAura(Aura *aura, AuraRecord &record)
{
// this is called only when it's safe
assert(aura != nullptr);
strn0cpy(aura_mgr.auras[aura_mgr.count].name, aura->GetCleanName(), 64);
aura_mgr.auras[aura_mgr.count].spawn_id = aura->GetID();
if (record.icon == -1)
aura_mgr.auras[aura_mgr.count].icon = spells[record.spell_id].new_icon;
else
aura_mgr.auras[aura_mgr.count].icon = record.icon;
if (IsClient()) {
auto outapp = new EQApplicationPacket(OP_UpdateAura, sizeof(AuraCreate_Struct));
auto aura_create = (AuraCreate_Struct *)outapp->pBuffer;
aura_create->action = 0;
aura_create->type = 1; // this can be 0 sometimes too
strn0cpy(aura_create->aura_name, aura_mgr.auras[aura_mgr.count].name, 64);
aura_create->entity_id = aura_mgr.auras[aura_mgr.count].spawn_id;
aura_create->icon = aura_mgr.auras[aura_mgr.count].icon;
CastToClient()->FastQueuePacket(&outapp);
}
// we can increment this now
aura_mgr.count++;
}
void Mob::AddTrap(Aura *aura, AuraRecord &record)
{
// this is called only when it's safe
assert(aura != nullptr);
strn0cpy(trap_mgr.auras[trap_mgr.count].name, aura->GetCleanName(), 64);
trap_mgr.auras[trap_mgr.count].spawn_id = aura->GetID();
if (record.icon == -1)
trap_mgr.auras[trap_mgr.count].icon = spells[record.spell_id].new_icon;
else
trap_mgr.auras[trap_mgr.count].icon = record.icon;
// doesn't send to client
trap_mgr.count++;
}
bool Mob::CanSpawnAura(bool trap)
{
if (trap && !HasFreeTrapSlots()) {
Message_StringID(MT_SpellFailure, NO_MORE_TRAPS);
return false;
} else if (!trap && !HasFreeAuraSlots()) {
Message_StringID(MT_SpellFailure, NO_MORE_AURAS);
return false;
}
return true;
}

76
zone/aura.h Normal file
View File

@ -0,0 +1,76 @@
#ifndef AURA_H
#define AURA_H
#include <functional>
#include <set>
#include "mob.h"
#include "npc.h"
#include "../common/types.h"
#include "../common/timer.h"
class Group;
class Raid;
class Mob;
struct NPCType;
enum class AuraType {
OnAllFriendlies, // AE PC/Pet basically (ex. Circle of Power)
OnAllGroupMembers, // Normal buffing aura (ex. Champion's Aura)
OnGroupMembersPets, // Hits just pets (ex. Rathe's Strength)
Totem, // Starts pulsing on a timer when an enemy enters (ex. Idol of Malos)
EnterTrap, // Casts once when an enemy enters (ex. Fire Rune)
ExitTrap, // Casts when they start to flee (ex. Poison Spikes Trap)
FullyScripted, // We just call script function not a predefined
Max
};
enum class AuraSpawns {
GroupMembers, // most auras use this
Everyone, // this is like traps and clickies who cast on everyone
Noone, // custom!
Max
};
enum class AuraMovement {
Follow, // follows caster
Stationary,
Pathing, // some sorted pathing TODO: implement
Max
};
class Aura : public NPC
{
public:
Aura(NPCType *type_data, Mob *owner, AuraRecord &record);
~Aura() { };
bool IsAura() const { return true; }
bool Process();
void Depop(bool unused = false);
void ProcessOnAllFriendlies(Mob *owner);
void ProcessOnAllGroupMembers(Mob *owner);
void ProcessOnGroupMembersPets(Mob *owner);
void ProcessTotem(Mob *owner);
void ProcessEnterTrap(Mob *owner);
void ProcessExitTrap(Mob *owner);
private:
int m_owner;
int spell_id; // spell we cast
int distance; // distance we remove
Timer remove_timer; // when we depop
Timer process_timer; // rate limit process calls
Timer cast_timer; // some auras pulse
Timer movement_timer; // rate limit movement updates
AuraType type;
AuraSpawns spawn_type;
AuraMovement movement_type;
std::function<void(Aura &, Mob *)> process_func;
std::set<int> casted_on; // we keep track of the other entities we've casted on
};
#endif /* !AURA_H */

View File

@ -1460,6 +1460,14 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
newbon->FeignedMinionChance = base1;
break;
case SE_AdditionalAura:
newbon->aura_slots += base1;
break;
case SE_IncreaseTrapCount:
newbon->trap_slots += base1;
break;
// to do
case SE_PetDiscipline:
break;
@ -3201,6 +3209,16 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
if (new_bonus->FeignedCastOnChance < effect_value)
new_bonus->FeignedCastOnChance = effect_value;
break;
case SE_AdditionalAura:
if (new_bonus->aura_slots < effect_value)
new_bonus->aura_slots = effect_value;
break;
case SE_IncreaseTrapCount:
if (new_bonus->trap_slots < effect_value)
new_bonus->trap_slots = effect_value;
break;
//Special custom cases for loading effects on to NPC from 'npc_spels_effects' table
if (IsAISpellEffect) {

View File

@ -383,6 +383,7 @@ void MapOpcodes()
ConnectedOpcodes[OP_TributeUpdate] = &Client::Handle_OP_TributeUpdate;
ConnectedOpcodes[OP_VetClaimRequest] = &Client::Handle_OP_VetClaimRequest;
ConnectedOpcodes[OP_VoiceMacroIn] = &Client::Handle_OP_VoiceMacroIn;
ConnectedOpcodes[OP_UpdateAura] = &Client::Handle_OP_UpdateAura;;
ConnectedOpcodes[OP_WearChange] = &Client::Handle_OP_WearChange;
ConnectedOpcodes[OP_WhoAllRequest] = &Client::Handle_OP_WhoAllRequest;
ConnectedOpcodes[OP_WorldUnknown001] = &Client::Handle_OP_Ignore;
@ -14360,6 +14361,29 @@ void Client::Handle_OP_VoiceMacroIn(const EQApplicationPacket *app)
}
void Client::Handle_OP_UpdateAura(const EQApplicationPacket *app)
{
if (app->size < 4) {
Log(Logs::General, Logs::None, "Size mismatch in OP_UpdateAura");
return;
}
auto action = *(uint32_t *)app->pBuffer; // action tells us the size
switch (action) {
case 2:
RemoveAllAuras();
break;
case 1: {
auto ads = (AuraDestory_Struct *)app->pBuffer;
RemoveAura(ads->entity_id);
break;
}
case 0: // client doesn't send this
break;
}
return;
}
void Client::Handle_OP_WearChange(const EQApplicationPacket *app)
{
if (app->size != sizeof(WearChange_Struct)) {

View File

@ -288,6 +288,7 @@
void Handle_OP_TributeNPC(const EQApplicationPacket *app);
void Handle_OP_TributeToggle(const EQApplicationPacket *app);
void Handle_OP_TributeUpdate(const EQApplicationPacket *app);
void Handle_OP_UpdateAura(const EQApplicationPacket *app);
void Handle_OP_VetClaimRequest(const EQApplicationPacket *app);
void Handle_OP_VoiceMacroIn(const EQApplicationPacket *app);
void Handle_OP_WearChange(const EQApplicationPacket *app);

View File

@ -105,6 +105,8 @@
#define PET_BUTTON_FOCUS 8
#define PET_BUTTON_SPELLHOLD 9
#define AURA_HARDCAP 2
typedef enum { //focus types
focusSpellHaste = 1,
focusSpellDuration,
@ -536,6 +538,8 @@ struct StatBonuses {
int16 FeignedCastOnChance; // Percent Value
bool PetCommands[PET_MAXCOMMANDS]; // SPA 267
int FeignedMinionChance; // SPA 281 base1 = chance, just like normal FD
int aura_slots;
int trap_slots;
};
typedef struct

View File

@ -80,6 +80,7 @@ public:
virtual bool IsBeacon() const { return false; }
virtual bool IsEncounter() const { return false; }
virtual bool IsBot() const { return false; }
virtual bool IsAura() const { return false; }
virtual bool Process() { return false; }
virtual bool Save() { return true; }

View File

@ -45,6 +45,8 @@ class EQApplicationPacket;
class Group;
class NPC;
class Raid;
class Aura;
struct AuraRecord;
struct NewSpawn_Struct;
struct PlayerPositionUpdateServer_Struct;
@ -85,6 +87,22 @@ public:
int params[MAX_SPECIAL_ATTACK_PARAMS];
};
struct AuraInfo {
char name[64];
int spawn_id;
int icon;
AuraInfo() : spawn_id(0), icon(0)
{
memset(name, 0, 64);
}
};
struct AuraMgr {
int count; // active auras
AuraInfo auras[AURA_HARDCAP];
AuraMgr() : count(0) { }
};
Mob(const char* in_name,
const char* in_lastname,
int32 in_cur_hp,
@ -308,6 +326,7 @@ public:
void BuffProcess();
virtual void DoBuffTic(const Buffs_Struct &buff, int slot, Mob* caster = nullptr);
void BuffFadeBySpellID(uint16 spell_id);
void BuffFadeBySpellIDAndCaster(uint16 spell_id, uint16 caster_id);
void BuffFadeByEffect(int effectid, int skipslot = -1);
void BuffFadeAll();
void BuffFadeNonPersistDeath();
@ -315,6 +334,7 @@ public:
void BuffFadeBySlot(int slot, bool iRecalcBonuses = true);
void BuffFadeDetrimentalByCaster(Mob *caster);
void BuffFadeBySitModifier();
bool IsAffectedByBuff(uint16 spell_id);
void BuffModifyDurationBySpellID(uint16 spell_id, int32 newDuration);
int AddBuff(Mob *caster, const uint16 spell_id, int duration = 0, int32 level_override = -1);
int CanBuffStack(uint16 spellid, uint8 caster_level, bool iFailIfOverwrite = false);
@ -604,6 +624,17 @@ public:
bool PlotPositionAroundTarget(Mob* target, float &x_dest, float &y_dest, float &z_dest,
bool lookForAftArc = true);
void MakeAura(uint16 spell_id);
inline int GetAuraSlots() { return 1 + aabonuses.aura_slots + itembonuses.aura_slots + spellbonuses.aura_slots; }
inline int GetTrapSlots() { return 1 + aabonuses.trap_slots + itembonuses.trap_slots + spellbonuses.trap_slots; }
inline bool HasFreeAuraSlots() { return aura_mgr.count < GetAuraSlots(); }
inline bool HasFreeTrapSlots() { return trap_mgr.count < GetTrapSlots(); }
void AddAura(Aura *aura, AuraRecord &record);
void AddTrap(Aura *aura, AuraRecord &record);
bool CanSpawnAura(bool trap);
void RemoveAura(int spawn_id) {}
void RemoveAllAuras() {}
//Procs
void TriggerDefensiveProcs(Mob *on, uint16 hand = EQEmu::inventory::slotPrimary, bool FromSkillProc = false, int damage = 0);
bool AddRangedProc(uint16 spell_id, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN);
@ -1465,6 +1496,9 @@ protected:
bool IsHorse;
AuraMgr aura_mgr;
AuraMgr trap_mgr;
private:
void _StopSong(); //this is not what you think it is
Mob* target;

View File

@ -90,6 +90,7 @@ class Client;
class Group;
class Raid;
class Spawn2;
class Aura;
namespace EQEmu
{
@ -425,6 +426,7 @@ protected:
NPCType* NPCTypedata_ours; //special case for npcs with uniquely created data.
friend class EntityList;
friend class Aura;
std::list<struct NPCFaction*> faction_list;
uint32 copper;
uint32 silver;

View File

@ -491,6 +491,14 @@ uint32 Raid::GetPlayerIndex(const char *name){
return 0; //should never get to here if we do everything else right, set it to 0 so we never crash things that rely on it.
}
uint32 Raid::GetPlayerIndex(Client *c)
{
for (int i = 0; i < MAX_RAID_MEMBERS; ++i)
if (c == members[i].member)
return i;
return 0xFFFFFFFF; // return sentinel value, make sure you check it unlike the above function
}
Client *Raid::GetClientByIndex(uint16 index)
{
if(index > MAX_RAID_MEMBERS)

View File

@ -142,6 +142,7 @@ public:
//keeps me from having to keep iterating through the list
//when I want lots of data from the same entry
uint32 GetPlayerIndex(const char *name);
uint32 GetPlayerIndex(Client *c);
//for perl interface
Client *GetClientByIndex(uint16 index);
const char *GetClientNameByIndex(uint8 index);

View File

@ -2791,6 +2791,10 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
break;
}
case SE_PersistentEffect:
MakeAura(spell_id);
break;
// Handled Elsewhere
case SE_ImmuneFleeing:
case SE_NegateSpellEffect:

View File

@ -4189,6 +4189,21 @@ void Mob::BuffFadeBySpellID(uint16 spell_id)
CalcBonuses();
}
void Mob::BuffFadeBySpellIDAndCaster(uint16 spell_id, uint16 caster_id)
{
bool recalc_bonus = false;
auto buff_count = GetMaxTotalSlots();
for (int i = 0; i < buff_count; ++i) {
if (buffs[i].spellid == spell_id && buffs[i].casterid == caster_id) {
BuffFadeBySlot(i, false);
recalc_bonus = true;
}
}
if (recalc_bonus)
CalcBonuses();
}
// removes buffs containing effectid, skipping skipslot
void Mob::BuffFadeByEffect(int effectid, int skipslot)
{
@ -4207,6 +4222,16 @@ void Mob::BuffFadeByEffect(int effectid, int skipslot)
CalcBonuses();
}
bool Mob::IsAffectedByBuff(uint16 spell_id)
{
int buff_count = GetMaxTotalSlots();
for (int i = 0; i < buff_count; ++i)
if (buffs[i].spellid == spell_id)
return true;
return false;
}
// checks if 'this' can be affected by spell_id from caster
// returns true if the spell should fail, false otherwise
bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster)

View File

@ -361,6 +361,7 @@
#define GAIN_GROUP_LEADERSHIP_EXP 8788 //
#define GAIN_RAID_LEADERSHIP_EXP 8789 //
#define BUFF_MINUTES_REMAINING 8799 //%1 (%2 minutes remaining)
#define NO_MORE_TRAPS 9002 //You have already placed your maximum number of traps.
#define FEAR_TOO_HIGH 9035 //Your target is too high of a level for your fear spell.
#define SLOW_MOSTLY_SUCCESSFUL 9029 //Your spell was mostly successful.
#define SLOW_PARTIALLY_SUCCESSFUL 9030 // Your spell was partially successful.
@ -375,6 +376,7 @@
#define SHAKE_OFF_STUN 9077 //You shake off the stun effect!
#define STRIKETHROUGH_STRING 9078 //You strike through your opponent's defenses!
#define SPELL_REFLECT 9082 //%1's spell has been reflected by %2.
#define NO_MORE_AURAS 9160 //You do not have sufficient focus to maintain that ability.
#define NEW_SPELLS_AVAIL 9149 //You have new spells available to you. Check the merchants near your guild master.
#define FD_CAST_ON_NO_BREAK 9174 //The strength of your will allows you to resume feigning death.
#define SNEAK_RESTRICT 9240 //You can not use this ability because you have not been hidden for long enough.

View File

@ -123,6 +123,18 @@ struct PetRecord {
uint32 equipmentset; // default equipment for the pet
};
struct AuraRecord {
uint32 npc_type;
char name[64]; // name shown in UI if shown and spawn name
int spell_id;
int distance;
int aura_type;
int spawn_type;
int movement;
int duration; // seconds some live for 90 mins (normal) others for 2 mins (traps)
int icon; // -1 will use the buffs NEW_ICON
};
// Actual pet info for a client.
struct PetInfo {
uint16 SpellID;
@ -404,6 +416,7 @@ public:
void AddLootDropToNPC(NPC* npc, uint32 lootdrop_id, ItemList* itemlist, uint8 droplimit, uint8 mindrop);
uint32 GetMaxNPCSpellsID();
uint32 GetMaxNPCSpellsEffectsID();
bool GetAuraEntry(uint16 spell_id, AuraRecord &record);
DBnpcspells_Struct* GetNPCSpells(uint32 iDBSpellsID);
DBnpcspellseffects_Struct* GetNPCSpellsEffects(uint32 iDBSpellsEffectsID);