mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 21:01:29 +00:00
WIP on auras
Lots to do still Normal buffing auras currently work for the most part
This commit is contained in:
parent
be0374d197
commit
94038ebb75
@ -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),
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
13
utils/sql/git/required/2017_07_xx_aura.sql
Normal file
13
utils/sql/git/required/2017_07_xx_aura.sql
Normal 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`)
|
||||
)
|
||||
@ -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
435
zone/aura.cpp
Normal 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
76
zone/aura.h
Normal 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 */
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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; }
|
||||
|
||||
34
zone/mob.h
34
zone/mob.h
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user