diff --git a/common/spdat.h b/common/spdat.h index 4016821b4..1841ae624 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -188,7 +188,7 @@ #define MAX_SYMPATHETIC_PROCS 10 // Number of sympathetic procs a client can have (This is arbitrary) #define MAX_FOCUS_PROC_LIMIT_TIMERS 20 //Number of focus recast timers that can be going at same time (This is arbitrary) #define MAX_PROC_LIMIT_TIMERS 8 //Number of proc delay timers that can be going at same time, different proc types get their own timer array. (This is arbitrary) - +#define MAX_APPEARANCE_EFFECTS 20 //Up to 20 Appearance Effects can be saved to a mobs appearance effect array, these will be sent to other clients when they enter a zone (This is arbitrary) const int Z_AGGRO=10; diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index 12034cbe9..1e1f548ba 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -294,6 +294,7 @@ SET(gm_commands gm_commands/aggrozone.cpp gm_commands/ai.cpp gm_commands/appearance.cpp + gm_commands/appearanceeffects.cpp gm_commands/attack.cpp gm_commands/augmentitem.cpp gm_commands/ban.cpp diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 526be7058..59b2b76e1 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -745,6 +745,8 @@ void Client::CompleteConnect() entity_list.SendUntargetable(this); + entity_list.SendAppearanceEffects(this); + int x; for (x = EQ::textures::textureBegin; x <= EQ::textures::LastTexture; x++) { SendWearChange(x); diff --git a/zone/command.cpp b/zone/command.cpp index fb6b32c75..166f2cc81 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -120,6 +120,7 @@ int command_init(void) command_add("aggrozone", "[aggro] - Aggro every mob in the zone with X aggro. Default is 0. Not recommend if you're not invulnerable.", AccountStatus::GMAdmin, command_aggrozone) || command_add("ai", "[factionid/spellslist/con/guard/roambox/stop/start] - Modify AI on NPC target", AccountStatus::GMAdmin, command_ai) || command_add("appearance", "[type] [value] - Send an appearance packet for you or your target", AccountStatus::GMLeadAdmin, command_appearance) || + command_add("appearanceeffects", "- [view] [set] [remove] appearance effects.", AccountStatus::GMAdmin, command_appearanceeffects) || command_add("apply_shared_memory", "[shared_memory_name] - Tells every zone and world to apply a specific shared memory segment by name.", AccountStatus::GMImpossible, command_apply_shared_memory) || command_add("attack", "[targetname] - Make your NPC target attack targetname", AccountStatus::GMLeadAdmin, command_attack) || command_add("augmentitem", "Force augments an item. Must have the augment item window open.", AccountStatus::GMImpossible, command_augmentitem) || diff --git a/zone/command.h b/zone/command.h index a4c758a16..177b53cb4 100644 --- a/zone/command.h +++ b/zone/command.h @@ -34,6 +34,7 @@ void command_aggro(Client *c, const Seperator *sep); void command_aggrozone(Client *c, const Seperator *sep); void command_ai(Client *c, const Seperator *sep); void command_appearance(Client *c, const Seperator *sep); +void command_appearanceeffects(Client *c, const Seperator *sep); void command_apply_shared_memory(Client *c, const Seperator *sep); void command_attack(Client *c, const Seperator *sep); void command_augmentitem(Client *c, const Seperator *sep); diff --git a/zone/entity.cpp b/zone/entity.cpp index aad8a3e82..d5b22c2fe 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -4582,6 +4582,26 @@ void EntityList::SendUntargetable(Client *c) } } +void EntityList::SendAppearanceEffects(Client *c) +{ + if (!c) + return; + + auto it = mob_list.begin(); + while (it != mob_list.end()) { + Mob *cur = it->second; + + if (cur) { + if (cur == c) { + ++it; + continue; + } + cur->SendSavedAppearenceEffects(c); + } + ++it; + } +} + void EntityList::ZoneWho(Client *c, Who_All_Struct *Who) { // This is only called for SoF clients, as regular /who is now handled server-side for that client. diff --git a/zone/entity.h b/zone/entity.h index c264237c9..9c437ae09 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -380,6 +380,7 @@ public: void SendZoneAppearance(Client *c); void SendNimbusEffects(Client *c); void SendUntargetable(Client *c); + void SendAppearanceEffects(Client *c); void DuelMessage(Mob* winner, Mob* loser, bool flee); void QuestJournalledSayClose(Mob *sender, float dist, const char* mobname, const char* message, Journal::Options &opts); void GroupMessage(uint32 gid, const char *from, const char *message); diff --git a/zone/gm_commands/appearanceeffects.cpp b/zone/gm_commands/appearanceeffects.cpp new file mode 100644 index 000000000..8a9a5eb44 --- /dev/null +++ b/zone/gm_commands/appearanceeffects.cpp @@ -0,0 +1,44 @@ +#include "../client.h" + +void command_appearanceeffects(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == '\0' || !strcasecmp(sep->arg[1], "help")) { + c->Message(Chat::White, "Syntax: #appearanceeffects [subcommand]."); + c->Message(Chat::White, "[view] Display all appearance effects saved to your target. #appearanceffects view"); + c->Message(Chat::White, "[set] Set an appearance effects saved to your target. #appearanceffects set [app_effectid] [slotid]"); + c->Message(Chat::White, "[remove] Remove all appearance effects saved to your target. #appearanceffects remove"); + } + + if (!strcasecmp(sep->arg[1], "view")) { + Mob* m_target = c->GetTarget(); + if (m_target) { + m_target->GetAppearenceEffects(); + } + return; + } + + + if (!strcasecmp(sep->arg[1], "set")) { + int32 app_effectid = atof(sep->arg[2]); + int32 slot = atoi(sep->arg[3]); + + Mob* m_target = c->GetTarget(); + if (m_target) { + m_target->SendAppearanceEffect(app_effectid, 0, 0, 0, 0, nullptr, slot, 0, 0, 0, 0, 0, 0, 0, 0, 0); + c->Message(Chat::White, "Appearance Effect ID %i for slot %i has been set.", app_effectid, slot); + } + } + + if (!strcasecmp(sep->arg[1], "remove")) { + Mob* m_target = c->GetTarget(); + if (m_target) { + m_target->SendIllusionPacket(m_target->GetRace(), m_target->GetGender(), m_target->GetTexture(), m_target->GetHelmTexture(), + m_target->GetHairColor(), m_target->GetBeardColor(), m_target->GetEyeColor1(), m_target->GetEyeColor2(), + m_target->GetHairStyle(), m_target->GetLuclinFace(), m_target->GetBeard(), 0xFF, + m_target->GetDrakkinHeritage(), m_target->GetDrakkinTattoo(), m_target->GetDrakkinDetails(), m_target->GetSize(), false); + m_target->ClearAppearenceEffects(); + c->Message(Chat::White, "All Appearance Effects have been removed."); + } + return; + } +} diff --git a/zone/mob.cpp b/zone/mob.cpp index dc59d444c..2274b34a4 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -480,6 +480,11 @@ Mob::Mob( Vulnerability_Mod[i] = 0; } + for (int i = 0; i < MAX_APPEARANCE_EFFECTS; i++) { + appearance_effects_id[i] = 0; + appearance_effects_slot[i] = 0; + } + emoteid = 0; endur_upkeep = false; degenerating_effects = false; @@ -2385,7 +2390,8 @@ void Mob::SendIllusionPacket( uint32 in_drakkin_heritage, uint32 in_drakkin_tattoo, uint32 in_drakkin_details, - float in_size + float in_size, + bool send_appearance_effects ) { uint8 new_texture = in_texture; @@ -2494,6 +2500,10 @@ void Mob::SendIllusionPacket( /* Refresh armor and tints after send illusion packet */ SendArmorAppearance(); + if (send_appearance_effects) { + SendSavedAppearenceEffects(nullptr); + } + LogSpells( "Illusion: Race [{}] Gender [{}] Texture [{}] HelmTexture [{}] HairColor [{}] BeardColor [{}] EyeColor1 [{}] EyeColor2 [{}] HairStyle [{}] Face [{}] DrakkinHeritage [{}] DrakkinTattoo [{}] DrakkinDetails [{}] Size [{}]", race, @@ -2869,7 +2879,7 @@ void Mob::SendAppearanceEffect(uint32 parm1, uint32 parm2, uint32 parm3, uint32 6 = right foot 9 = Face - value#ground = 1, will place the effect on ground, of corresponding slot is set to 0 effect is permanenant, if > 0 will fade if mob death/despawn. + value#ground = 1, will place the effect on ground, this is permanenant */ //higher values can crash client @@ -2892,6 +2902,22 @@ void Mob::SendAppearanceEffect(uint32 parm1, uint32 parm2, uint32 parm3, uint32 value5slot = 1; } + if (!value1ground && parm1) { + SetAppearenceEffects(value1slot, parm1); + } + if (!value2ground && parm2) { + SetAppearenceEffects(value2slot, parm2); + } + if (!value3ground && parm3) { + SetAppearenceEffects(value3slot, parm3); + } + if (!value4ground && parm4) { + SetAppearenceEffects(value4slot, parm4); + } + if (!value5ground && parm5) { + SetAppearenceEffects(value5slot, parm5); + } + LevelAppearance_Struct* la = (LevelAppearance_Struct*)outapp->pBuffer; la->spawn_id = GetID(); la->parm1 = parm1; @@ -2920,6 +2946,62 @@ void Mob::SendAppearanceEffect(uint32 parm1, uint32 parm2, uint32 parm3, uint32 safe_delete(outapp); } +void Mob::SetAppearenceEffects(int32 slot, int32 value) +{ + for (int i = 0; i < MAX_APPEARANCE_EFFECTS; i++) { + if (!appearance_effects_id[i]) { + appearance_effects_id[i] = value; + appearance_effects_slot[i] = slot; + return; + } + } +} + +void Mob::GetAppearenceEffects() +{ + //used with GM command + if (!appearance_effects_id[0]) { + Message(Chat::Red, "No Appearance Effects exist on this mob"); + return; + } + + for (int i = 0; i < MAX_APPEARANCE_EFFECTS; i++) { + Message(Chat::Red, "ID: %i :: App Effect ID %i :: Slot %i", i, appearance_effects_id[i], appearance_effects_slot[i]); + } +} + +void Mob::ClearAppearenceEffects() +{ + for (int i = 0; i < MAX_APPEARANCE_EFFECTS; i++) { + appearance_effects_id[i] = 0; + appearance_effects_slot[i] = 0; + } +} + +void Mob::SendSavedAppearenceEffects(Client *receiver = nullptr) +{ + if (!appearance_effects_id[0]) { + return; + } + + if (appearance_effects_id[0]) { + SendAppearanceEffect(appearance_effects_id[0], appearance_effects_id[1], appearance_effects_id[2], appearance_effects_id[3], appearance_effects_id[4], receiver, + appearance_effects_slot[0], 0, appearance_effects_slot[1], 0, appearance_effects_slot[2], 0, appearance_effects_slot[3], 0, appearance_effects_slot[4], 0); + } + if (appearance_effects_id[5]) { + SendAppearanceEffect(appearance_effects_id[5], appearance_effects_id[6], appearance_effects_id[7], appearance_effects_id[8], appearance_effects_id[9], receiver, + appearance_effects_slot[5], 0, appearance_effects_slot[6], 0, appearance_effects_slot[7], 0, appearance_effects_slot[8], 0, appearance_effects_slot[9], 0); + } + if (appearance_effects_id[10]) { + SendAppearanceEffect(appearance_effects_id[10], appearance_effects_id[11], appearance_effects_id[12], appearance_effects_id[13], appearance_effects_id[14], receiver, + appearance_effects_slot[10], 0, appearance_effects_slot[11], 0, appearance_effects_slot[12], 0, appearance_effects_slot[13], 0, appearance_effects_slot[14], 0); + } + if (appearance_effects_id[15]) { + SendAppearanceEffect(appearance_effects_id[15], appearance_effects_id[16], appearance_effects_id[17], appearance_effects_id[18], appearance_effects_id[19], receiver, + appearance_effects_slot[15], 0, appearance_effects_slot[16], 0, appearance_effects_slot[17], 0, appearance_effects_slot[18], 0, appearance_effects_slot[19], 0); + } +} + void Mob::SendTargetable(bool on, Client *specific_target) { auto outapp = new EQApplicationPacket(OP_Untargetable, sizeof(Untargetable_Struct)); Untargetable_Struct *ut = (Untargetable_Struct*)outapp->pBuffer; diff --git a/zone/mob.h b/zone/mob.h index 48c46db37..c1fe57a89 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -425,6 +425,10 @@ public: inline bool HasEndurUpkeep() const { return endur_upkeep; } inline void SetEndurUpkeep(bool val) { endur_upkeep = val; } bool HasBuffWithSpellGroup(int spell_group); + void SetAppearenceEffects(int32 slot, int32 value); + void GetAppearenceEffects(); + void ClearAppearenceEffects(); + void SendSavedAppearenceEffects(Client *receiver); //Basic Stats/Inventory virtual void SetLevel(uint8 in_level, bool command = false) { level = in_level; } @@ -788,7 +792,7 @@ public: uint8 in_haircolor = 0xFF, uint8 in_beardcolor = 0xFF, uint8 in_eyecolor1 = 0xFF, uint8 in_eyecolor2 = 0xFF, uint8 in_hairstyle = 0xFF, uint8 in_luclinface = 0xFF, uint8 in_beard = 0xFF, uint8 in_aa_title = 0xFF, uint32 in_drakkin_heritage = 0xFFFFFFFF, uint32 in_drakkin_tattoo = 0xFFFFFFFF, - uint32 in_drakkin_details = 0xFFFFFFFF, float in_size = -1.0f); + uint32 in_drakkin_details = 0xFFFFFFFF, float in_size = -1.0f, bool send_appearance_effects = true); bool RandomizeFeatures(bool send_illusion = true, bool set_variables = true); virtual void Stun(int duration); virtual void UnStun(); @@ -1508,6 +1512,9 @@ protected: Timer def_proclimit_timer[MAX_PROC_LIMIT_TIMERS]; //SPA 512 int32 def_proclimit_spellid[MAX_PROC_LIMIT_TIMERS]; //SPA 512 + int32 appearance_effects_id[MAX_APPEARANCE_EFFECTS]; + int32 appearance_effects_slot[MAX_APPEARANCE_EFFECTS]; + Timer shield_timer; uint32 m_shield_target_id; uint32 m_shielder_id; diff --git a/zone/perl_mob.cpp b/zone/perl_mob.cpp index 1c9e399a2..28b8be62d 100644 --- a/zone/perl_mob.cpp +++ b/zone/perl_mob.cpp @@ -4809,31 +4809,22 @@ XS(XS_Mob_SendAppearanceEffectActor) { XS(XS_Mob_SendAppearanceEffectGround); /* prototype to pass -Wmissing-prototypes */ XS(XS_Mob_SendAppearanceEffectGround) { dXSARGS; - if (items < 3 || items > 12) - Perl_croak(aTHX_ "Usage: Mob::SendAppearanceEffectGround(THIS, int32 effect1, uint32 slot1 = 1, [int32 effect2 = 0], [uint32 slot2 = 1], [int32 effect3 = 0], [uint32 slot3 = 1], [int32 effect4 = 0], [uint32 slot4 = 1], [int32 effect5 = 0], [uint32 slot5 = 1], [Client* single_client_to_send_to = null])"); // @categories Script Utility + if (items < 3 || items > 8) + Perl_croak(aTHX_ "Usage: Mob::SendAppearanceEffectGround(THIS, int32 effect1, [int32 effect2 = 0], [int32 effect3 = 0], [int32 effect4 = 0], [int32 effect5 = 0], [Client* single_client_to_send_to = null])"); // @categories Script Utility { Mob *THIS; int32 parm1 = (int32)SvIV(ST(1)); - uint32 value1slot = (uint32)SvIV(ST(2)); int32 parm2 = 0; - uint32 value2slot = 1; int32 parm3 = 0; - uint32 value3slot = 1; int32 parm4 = 0; - uint32 value4slot = 1; int32 parm5 = 0; - uint32 value5slot = 1; Client *client = nullptr; VALIDATE_THIS_IS_MOB; - if (items > 3) { parm2 = (int32)SvIV(ST(3)); } - if (items > 4) { value2slot = (uint32)SvIV(ST(4)); } - if (items > 5) { parm3 = (int32)SvIV(ST(5)); } - if (items > 6) { value3slot = (uint32)SvIV(ST(6)); } - if (items > 7) { parm4 = (int32)SvIV(ST(7)); } - if (items > 8) { value4slot = (uint32)SvIV(ST(8)); } - if (items > 9) { parm5 = (int32)SvIV(ST(9)); } - if (items > 10) { value5slot = (uint32)SvIV(ST(10)); } - if (items > 11) { + if (items > 3) { parm2 = (int32)SvIV(ST(2)); } + if (items > 4) { parm3 = (int32)SvIV(ST(3)); } + if (items > 5) { parm4 = (int32)SvIV(ST(4)); } + if (items > 6) { parm5 = (int32)SvIV(ST(5)); } + if (items > 7) { if (sv_derived_from(ST(6), "Client")) { IV tmp = SvIV((SV *)SvRV(ST(11))); client = INT2PTR(Client *, tmp); @@ -4844,8 +4835,8 @@ XS(XS_Mob_SendAppearanceEffectGround) { Perl_croak(aTHX_ "client is nullptr, avoiding crash."); } - THIS->SendAppearanceEffect(parm1, parm2, parm3, parm4, parm5, client, value1slot, 1, value2slot, 1, value3slot, 1, - value4slot, 1, value5slot, 1); + THIS->SendAppearanceEffect(parm1, parm2, parm3, parm4, parm5, client, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1); } XSRETURN_EMPTY; } @@ -4858,10 +4849,11 @@ XS(XS_Mob_RemoveAllAppearanceEffects) { { Mob *THIS; VALIDATE_THIS_IS_MOB; - THIS->SendIllusionPacket(THIS->GetRace(), THIS->GetGender(), THIS->GetTexture(), THIS->GetHelmTexture(), - THIS->GetHairColor(), THIS->GetBeardColor(), THIS->GetEyeColor1(), THIS->GetEyeColor2(), - THIS->GetHairStyle(), THIS->GetLuclinFace(), THIS->GetBeard(), 0xFF, - THIS->GetDrakkinHeritage(), THIS->GetDrakkinTattoo(), THIS->GetDrakkinDetails(), THIS->GetSize()); + THIS->SendIllusionPacket(THIS->GetRace(), THIS->GetGender(), THIS->GetTexture(), THIS->GetHelmTexture(), + THIS->GetHairColor(), THIS->GetBeardColor(), THIS->GetEyeColor1(), THIS->GetEyeColor2(), + THIS->GetHairStyle(), THIS->GetLuclinFace(), THIS->GetBeard(), 0xFF, + THIS->GetDrakkinHeritage(), THIS->GetDrakkinTattoo(), THIS->GetDrakkinDetails(), THIS->GetSize(), false); + THIS->ClearAppearenceEffects(); } XSRETURN_EMPTY; }