diff --git a/common/base_packet.h b/common/base_packet.h index 4f47c919a..3b92e3e90 100644 --- a/common/base_packet.h +++ b/common/base_packet.h @@ -75,6 +75,7 @@ public: uint32 ReadUInt32() { uint32 value = *(uint32 *)(pBuffer + _rpos); _rpos += sizeof(uint32); return value; } uint32 ReadUInt32(uint32 Offset) const { uint32 value = *(uint32 *)(pBuffer + Offset); return value; } void ReadString(char *str) { uint32 len = static_cast(strlen((char *)(pBuffer + _rpos))) + 1; memcpy(str, pBuffer + _rpos, len); _rpos += len; } + void ReadString(std::string &str) { str = reinterpret_cast(pBuffer + _rpos); _rpos += str.length() + 1; } void ReadString(char *str, uint32 Offset, uint32 MaxLength) const; uint32 GetWritePosition() { return _wpos; } diff --git a/common/eq_constants.h b/common/eq_constants.h index faa0ab247..657db450e 100644 --- a/common/eq_constants.h +++ b/common/eq_constants.h @@ -87,6 +87,7 @@ typedef enum { _eaMaxAppearance } EmuAppearance; +#define MT_NPCQuestSay 10 // msg_type's for custom usercolors #define MT_Say 256 #define MT_Tell 257 diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index c80fdcc02..fb9260857 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -1188,6 +1188,20 @@ struct SpecialMesg_Struct /*24*/ char message[1]; // What is being said? }; +struct SpecialMesgHeader_Struct +{ +/*00*/ char SpeakMode; // 2 shouts, 4 %1 %2, 3 %2, 5 tells group, 0 copy, default says +/*01*/ char JournalMode; // 1 and 2 go to journal +/*02*/ char language; +/*03*/ uint32 msg_type; // Color of text (see MT_*** below) +/*07*/ uint32 target_spawn_id; // Who is it being said to? +/*11*/ // speaker's name +/*xx*/ // unknown, location, client doesn't care +/*xx*/ // unknown +/*xx*/ // unknown +/*xx*/ // message +}; + /* ** When somebody changes what they're wearing or give a pet a weapon (model changes) ** Length: 19 Bytes diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index d7fa21c41..322e187e4 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -3199,43 +3199,35 @@ namespace RoF EQApplicationPacket *in = *p; *p = nullptr; - SpecialMesg_Struct *emu = (SpecialMesg_Struct *)in->pBuffer; + SerializeBuffer buf(in->size); + buf.WriteInt8(in->ReadUInt8()); // speak mode + buf.WriteInt8(in->ReadUInt8()); // journal mode + buf.WriteInt8(in->ReadUInt8()); // language + buf.WriteInt32(in->ReadUInt32()); // message type + buf.WriteInt32(in->ReadUInt32()); // target spawn id - unsigned char *__emu_buffer = in->pBuffer; - // break strlen optimizations! - char *message = emu->sayer; - auto sayer_length = std::char_traits::length(message); - message += sayer_length + 1 + 12; // skip over sayer name, null term, and 3 floats + std::string name; + in->ReadString(name); // NPC names max out at 63 chars - std::string old_message = message; + buf.WriteString(name); + + buf.WriteInt32(in->ReadUInt32()); // loc + buf.WriteInt32(in->ReadUInt32()); + buf.WriteInt32(in->ReadUInt32()); + + std::string old_message; std::string new_message; + in->ReadString(old_message); + ServerToRoFSayLink(new_message, old_message); - //in->size = 3 + 4 + 4 + strlen(emu->sayer) + 1 + 12 + new_message.length() + 1; - in->size = sayer_length + new_message.length() + 25; - in->pBuffer = new unsigned char[in->size]; + buf.WriteString(new_message); - char *OutBuffer = (char *)in->pBuffer; + auto outapp = new EQApplicationPacket(OP_SpecialMesg, buf); - VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, emu->header[0]); - VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, emu->header[1]); - VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, emu->header[2]); - - VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->msg_type); - VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->target_spawn_id); - - VARSTRUCT_ENCODE_STRING(OutBuffer, emu->sayer); - - // TODO: figure this shit out - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0.0f); - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0.0f); - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0.0f); - - VARSTRUCT_ENCODE_STRING(OutBuffer, new_message.c_str()); - - delete[] __emu_buffer; - dest->FastQueuePacket(&in, ack_req); + dest->FastQueuePacket(&outapp, ack_req); + delete in; } ENCODE(OP_Stun) diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 66e699e2a..a6cf7a134 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -3266,43 +3266,35 @@ namespace RoF2 EQApplicationPacket *in = *p; *p = nullptr; - SpecialMesg_Struct *emu = (SpecialMesg_Struct *)in->pBuffer; + SerializeBuffer buf(in->size); + buf.WriteInt8(in->ReadUInt8()); // speak mode + buf.WriteInt8(in->ReadUInt8()); // journal mode + buf.WriteInt8(in->ReadUInt8()); // language + buf.WriteInt32(in->ReadUInt32()); // message type + buf.WriteInt32(in->ReadUInt32()); // target spawn id - unsigned char *__emu_buffer = in->pBuffer; - // break strlen optimizations! - char *message = emu->sayer; - auto sayer_length = std::char_traits::length(message); - message += sayer_length + 1 + 12; // skip over sayer name, null term, and 3 floats + std::string name; + in->ReadString(name); // NPC names max out at 63 chars - std::string old_message = message; + buf.WriteString(name); + + buf.WriteInt32(in->ReadUInt32()); // loc + buf.WriteInt32(in->ReadUInt32()); + buf.WriteInt32(in->ReadUInt32()); + + std::string old_message; std::string new_message; + in->ReadString(old_message); + ServerToRoF2SayLink(new_message, old_message); - //in->size = 3 + 4 + 4 + strlen(emu->sayer) + 1 + 12 + new_message.length() + 1; - in->size = sayer_length + new_message.length() + 25; - in->pBuffer = new unsigned char[in->size]; + buf.WriteString(new_message); - char *OutBuffer = (char *)in->pBuffer; + auto outapp = new EQApplicationPacket(OP_SpecialMesg, buf); - VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, emu->header[0]); - VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, emu->header[1]); - VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, emu->header[2]); - - VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->msg_type); - VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->target_spawn_id); - - VARSTRUCT_ENCODE_STRING(OutBuffer, emu->sayer); - - // TODO: figure this shit out - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0.0f); - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0.0f); - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0.0f); - - VARSTRUCT_ENCODE_STRING(OutBuffer, new_message.c_str()); - - delete[] __emu_buffer; - dest->FastQueuePacket(&in, ack_req); + dest->FastQueuePacket(&outapp, ack_req); + delete in; } ENCODE(OP_Stun) diff --git a/common/patches/sod.cpp b/common/patches/sod.cpp index 2c9d70edf..11aae5cc9 100644 --- a/common/patches/sod.cpp +++ b/common/patches/sod.cpp @@ -2069,43 +2069,35 @@ namespace SoD EQApplicationPacket *in = *p; *p = nullptr; - SpecialMesg_Struct *emu = (SpecialMesg_Struct *)in->pBuffer; + SerializeBuffer buf(in->size); + buf.WriteInt8(in->ReadUInt8()); // speak mode + buf.WriteInt8(in->ReadUInt8()); // journal mode + buf.WriteInt8(in->ReadUInt8()); // language + buf.WriteInt32(in->ReadUInt32()); // message type + buf.WriteInt32(in->ReadUInt32()); // target spawn id - unsigned char *__emu_buffer = in->pBuffer; - // break strlen optimizations! - char *message = emu->sayer; - auto sayer_length = std::char_traits::length(message); - message += sayer_length + 1 + 12; // skip over sayer name, null term, and 3 floats + std::string name; + in->ReadString(name); // NPC names max out at 63 chars - std::string old_message = message; + buf.WriteString(name); + + buf.WriteInt32(in->ReadUInt32()); // loc + buf.WriteInt32(in->ReadUInt32()); + buf.WriteInt32(in->ReadUInt32()); + + std::string old_message; std::string new_message; + in->ReadString(old_message); + ServerToSoDSayLink(new_message, old_message); - //in->size = 3 + 4 + 4 + strlen(emu->sayer) + 1 + 12 + new_message.length() + 1; - in->size = sayer_length + new_message.length() + 25; - in->pBuffer = new unsigned char[in->size]; + buf.WriteString(new_message); - char *OutBuffer = (char *)in->pBuffer; + auto outapp = new EQApplicationPacket(OP_SpecialMesg, buf); - VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, emu->header[0]); - VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, emu->header[1]); - VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, emu->header[2]); - - VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->msg_type); - VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->target_spawn_id); - - VARSTRUCT_ENCODE_STRING(OutBuffer, emu->sayer); - - // TODO: figure this shit out - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0.0f); - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0.0f); - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0.0f); - - VARSTRUCT_ENCODE_STRING(OutBuffer, new_message.c_str()); - - delete[] __emu_buffer; - dest->FastQueuePacket(&in, ack_req); + dest->FastQueuePacket(&outapp, ack_req); + delete in; } ENCODE(OP_Stun) diff --git a/common/patches/sof.cpp b/common/patches/sof.cpp index 0b3043c70..7569b4c5d 100644 --- a/common/patches/sof.cpp +++ b/common/patches/sof.cpp @@ -1720,43 +1720,35 @@ namespace SoF EQApplicationPacket *in = *p; *p = nullptr; - SpecialMesg_Struct *emu = (SpecialMesg_Struct *)in->pBuffer; + SerializeBuffer buf(in->size); + buf.WriteInt8(in->ReadUInt8()); // speak mode + buf.WriteInt8(in->ReadUInt8()); // journal mode + buf.WriteInt8(in->ReadUInt8()); // language + buf.WriteInt32(in->ReadUInt32()); // message type + buf.WriteInt32(in->ReadUInt32()); // target spawn id - unsigned char *__emu_buffer = in->pBuffer; - // break strlen optimizations! - char *message = emu->sayer; - auto sayer_length = std::char_traits::length(message); - message += sayer_length + 1 + 12; // skip over sayer name, null term, and 3 floats + std::string name; + in->ReadString(name); - std::string old_message = message; + buf.WriteString(name); + + buf.WriteInt32(in->ReadUInt32()); // loc + buf.WriteInt32(in->ReadUInt32()); + buf.WriteInt32(in->ReadUInt32()); + + std::string old_message; std::string new_message; + in->ReadString(old_message); + ServerToSoFSayLink(new_message, old_message); - //in->size = 3 + 4 + 4 + strlen(emu->sayer) + 1 + 12 + new_message.length() + 1; - in->size = sayer_length + new_message.length() + 25; - in->pBuffer = new unsigned char[in->size]; + buf.WriteString(new_message); - char *OutBuffer = (char *)in->pBuffer; + auto outapp = new EQApplicationPacket(OP_SpecialMesg, buf); - VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, emu->header[0]); - VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, emu->header[1]); - VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, emu->header[2]); - - VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->msg_type); - VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->target_spawn_id); - - VARSTRUCT_ENCODE_STRING(OutBuffer, emu->sayer); - - // TODO: figure this shit out - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0.0f); - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0.0f); - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0.0f); - - VARSTRUCT_ENCODE_STRING(OutBuffer, new_message.c_str()); - - delete[] __emu_buffer; - dest->FastQueuePacket(&in, ack_req); + dest->FastQueuePacket(&outapp, ack_req); + delete in; } ENCODE(OP_Stun) diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index c4bef5bea..d8a115a98 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -1420,43 +1420,35 @@ namespace Titanium EQApplicationPacket *in = *p; *p = nullptr; - SpecialMesg_Struct *emu = (SpecialMesg_Struct *)in->pBuffer; + SerializeBuffer buf(in->size); + buf.WriteInt8(in->ReadUInt8()); // speak mode + buf.WriteInt8(in->ReadUInt8()); // journal mode + buf.WriteInt8(in->ReadUInt8()); // language + buf.WriteInt32(in->ReadUInt32()); // message type + buf.WriteInt32(in->ReadUInt32()); // target spawn id - unsigned char *__emu_buffer = in->pBuffer; - // break strlen optimizations! - char *message = emu->sayer; - auto sayer_length = std::char_traits::length(message); - message += sayer_length + 1 + 12; // skip over sayer name, null term, and 3 floats + std::string name; + in->ReadString(name); // NPC names max out at 63 chars - std::string old_message = message; + buf.WriteString(name); + + buf.WriteInt32(in->ReadUInt32()); // loc + buf.WriteInt32(in->ReadUInt32()); + buf.WriteInt32(in->ReadUInt32()); + + std::string old_message; std::string new_message; + in->ReadString(old_message); + ServerToTitaniumSayLink(new_message, old_message); - //in->size = 3 + 4 + 4 + strlen(emu->sayer) + 1 + 12 + new_message.length() + 1; - in->size = sayer_length + new_message.length() + 25; - in->pBuffer = new unsigned char[in->size]; + buf.WriteString(new_message); - char *OutBuffer = (char *)in->pBuffer; + auto outapp = new EQApplicationPacket(OP_SpecialMesg, buf); - VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, emu->header[0]); - VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, emu->header[1]); - VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, emu->header[2]); - - VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->msg_type); - VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->target_spawn_id); - - VARSTRUCT_ENCODE_STRING(OutBuffer, emu->sayer); - - // TODO: figure this shit out - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0.0f); - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0.0f); - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0.0f); - - VARSTRUCT_ENCODE_STRING(OutBuffer, new_message.c_str()); - - delete[] __emu_buffer; - dest->FastQueuePacket(&in, ack_req); + dest->FastQueuePacket(&outapp, ack_req); + delete in; } ENCODE(OP_TaskDescription) diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index 7db0cfb92..b644c9554 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -2369,43 +2369,35 @@ namespace UF EQApplicationPacket *in = *p; *p = nullptr; - SpecialMesg_Struct *emu = (SpecialMesg_Struct *)in->pBuffer; + SerializeBuffer buf(in->size); + buf.WriteInt8(in->ReadUInt8()); // speak mode + buf.WriteInt8(in->ReadUInt8()); // journal mode + buf.WriteInt8(in->ReadUInt8()); // language + buf.WriteInt32(in->ReadUInt32()); // message type + buf.WriteInt32(in->ReadUInt32()); // target spawn id - unsigned char *__emu_buffer = in->pBuffer; - // break strlen optimizations! - char *message = emu->sayer; - auto sayer_length = std::char_traits::length(message); - message += sayer_length + 1 + 12; // skip over sayer name, null term, and 3 floats + std::string name; + in->ReadString(name); // NPC names max out at 63 chars - std::string old_message = message; + buf.WriteString(name); + + buf.WriteInt32(in->ReadUInt32()); // loc + buf.WriteInt32(in->ReadUInt32()); + buf.WriteInt32(in->ReadUInt32()); + + std::string old_message; std::string new_message; + in->ReadString(old_message); + ServerToUFSayLink(new_message, old_message); - //in->size = 3 + 4 + 4 + strlen(emu->sayer) + 1 + 12 + new_message.length() + 1; - in->size = sayer_length + new_message.length() + 25; - in->pBuffer = new unsigned char[in->size]; + buf.WriteString(new_message); - char *OutBuffer = (char *)in->pBuffer; + auto outapp = new EQApplicationPacket(OP_SpecialMesg, buf); - VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, emu->header[0]); - VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, emu->header[1]); - VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, emu->header[2]); - - VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->msg_type); - VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->target_spawn_id); - - VARSTRUCT_ENCODE_STRING(OutBuffer, emu->sayer); - - // TODO: figure this shit out - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0.0f); - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0.0f); - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0.0f); - - VARSTRUCT_ENCODE_STRING(OutBuffer, new_message.c_str()); - - delete[] __emu_buffer; - dest->FastQueuePacket(&in, ack_req); + dest->FastQueuePacket(&outapp, ack_req); + delete in; } ENCODE(OP_Stun) diff --git a/zone/client.cpp b/zone/client.cpp index fd2df48bc..e90dafa8a 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -1277,21 +1277,19 @@ void Client::Message(uint32 type, const char* message, ...) { vsnprintf(buffer, 4096, message, argptr); va_end(argptr); - size_t len = strlen(buffer); + SerializeBuffer buf(sizeof(SpecialMesgHeader_Struct) + 12 + 64 + 64); + buf.WriteInt8(static_cast(Journal::SpeakMode::Raw)); + buf.WriteInt8(static_cast(Journal::Mode::None)); + buf.WriteInt8(0); // language + buf.WriteUInt32(type); + buf.WriteUInt32(0); // target spawn ID used for journal filtering, ignored here + buf.WriteString(""); // send name, not applicable here + buf.WriteInt32(0); // location, client seems to ignore + buf.WriteInt32(0); + buf.WriteInt32(0); + buf.WriteString(buffer); - //client dosent like our packet all the time unless - //we make it really big, then it seems to not care that - //our header is malformed. - //len = 4096 - sizeof(SpecialMesg_Struct); - - uint32 len_packet = sizeof(SpecialMesg_Struct)+len; - auto app = new EQApplicationPacket(OP_SpecialMesg, len_packet); - SpecialMesg_Struct* sm=(SpecialMesg_Struct*)app->pBuffer; - sm->header[0] = 0x00; // Header used for #emote style messages.. - sm->header[1] = 0x00; // Play around with these to see other types - sm->header[2] = 0x00; - sm->msg_type = type; - memcpy(sm->message, buffer, len+1); + auto app = new EQApplicationPacket(OP_SpecialMesg, buf); FastQueuePacket(&app); @@ -1308,67 +1306,25 @@ void Client::FilteredMessage(Mob *sender, uint32 type, eqFilterType filter, cons vsnprintf(buffer, 4096, message, argptr); va_end(argptr); - size_t len = strlen(buffer); + SerializeBuffer buf(sizeof(SpecialMesgHeader_Struct) + 12 + 64 + 64); + buf.WriteInt8(static_cast(Journal::SpeakMode::Raw)); + buf.WriteInt8(static_cast(Journal::Mode::None)); + buf.WriteInt8(0); // language + buf.WriteUInt32(type); + buf.WriteUInt32(0); // target spawn ID used for journal filtering, ignored here + buf.WriteString(""); // send name, not applicable here + buf.WriteInt32(0); // location, client seems to ignore + buf.WriteInt32(0); + buf.WriteInt32(0); + buf.WriteString(buffer); - //client dosent like our packet all the time unless - //we make it really big, then it seems to not care that - //our header is malformed. - //len = 4096 - sizeof(SpecialMesg_Struct); - - uint32 len_packet = sizeof(SpecialMesg_Struct) + len; - auto app = new EQApplicationPacket(OP_SpecialMesg, len_packet); - SpecialMesg_Struct* sm = (SpecialMesg_Struct*)app->pBuffer; - sm->header[0] = 0x00; // Header used for #emote style messages.. - sm->header[1] = 0x00; // Play around with these to see other types - sm->header[2] = 0x00; - sm->msg_type = type; - memcpy(sm->message, buffer, len + 1); + auto app = new EQApplicationPacket(OP_SpecialMesg, buf); FastQueuePacket(&app); safe_delete_array(buffer); } -void Client::QuestJournalledMessage(const char *npcname, const char* message) { - - // npcnames longer than 60 characters crash the client when they log back in - const int MaxNPCNameLength = 60; - // I assume there is an upper safe limit on the message length. Don't know what it is, but 4000 doesn't crash - // the client. - const int MaxMessageLength = 4000; - - char OutNPCName[MaxNPCNameLength+1]; - char OutMessage[MaxMessageLength+1]; - - // Apparently Visual C++ snprintf is not C99 compliant and doesn't put the null terminator - // in if the formatted string >= the maximum length, so we put it in. - // - snprintf(OutNPCName, MaxNPCNameLength, "%s", npcname); OutNPCName[MaxNPCNameLength]='\0'; - snprintf(OutMessage, MaxMessageLength, "%s", message); OutMessage[MaxMessageLength]='\0'; - - uint32 len_packet = sizeof(SpecialMesg_Struct) + strlen(OutNPCName) + strlen(OutMessage); - auto app = new EQApplicationPacket(OP_SpecialMesg, len_packet); - SpecialMesg_Struct* sm=(SpecialMesg_Struct*)app->pBuffer; - - sm->header[0] = 0; - sm->header[1] = 2; - sm->header[2] = 0; - sm->msg_type = 0x0a; - sm->target_spawn_id = GetID(); - - char *dest = &sm->sayer[0]; - - memcpy(dest, OutNPCName, strlen(OutNPCName) + 1); - - dest = dest + strlen(OutNPCName) + 13; - - memcpy(dest, OutMessage, strlen(OutMessage) + 1); - - QueuePacket(app); - - safe_delete(app); -} - void Client::SetMaxHP() { if(dead) return; diff --git a/zone/client.h b/zone/client.h index a44e83087..0272fb21b 100644 --- a/zone/client.h +++ b/zone/client.h @@ -341,7 +341,6 @@ public: void ChannelMessageSend(const char* from, const char* to, uint8 chan_num, uint8 language, uint8 lang_skill, const char* message, ...); void Message(uint32 type, const char* message, ...); void FilteredMessage(Mob *sender, uint32 type, eqFilterType filter, const char* message, ...); - void QuestJournalledMessage(const char *npcname, const char* message); void VoiceMacroReceived(uint32 Type, char *Target, uint32 MacroNumber); void SendSound(); void LearnRecipe(uint32 recipeID); diff --git a/zone/common.h b/zone/common.h index 62f1d9771..0ffa67468 100644 --- a/zone/common.h +++ b/zone/common.h @@ -272,6 +272,31 @@ enum class LootRequestType : uint8 { AllowedPVPDefined, }; +namespace Journal { + enum class SpeakMode : uint8 { + Raw = 0, // this just uses the raw message + Say = 1, // prints with "%1 says,%2 '%3'" if in another language else "%1 says '%2'" + Shout = 2, // prints with "%1 shouts,%2 '%3'" if in another language else "%1 shouts '%2'" + EmoteAlt = 3, // prints "%2", this should just be the same as raw ... + Emote = 4, // prints "%1 %2" if message doesn't start with "\" or "@", else "%1%2" + Group = 5 // prints "%1 tells the group,%2 '%3'" + }; + + enum class Mode : uint8 { + None = 0, + Log1 = 1, // 1 and 2 log to journal + Log2 = 2, // our current code uses 2 + }; + + struct Options { + SpeakMode speak_mode; + Mode journal_mode; + int8 language; + uint32 message_type; + uint32 target_spawn_id; // who the message is talking to (limits journaling) + }; +}; + //this is our internal representation of the BUFF struct, can put whatever we want in it struct Buffs_Struct { uint16 spellid; diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 154e28566..bdb190e9c 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -155,12 +155,31 @@ XS(XS__say); // prototype to pass -Wmissing-prototypes XS(XS__say) { dXSARGS; - if (items == 1) - quest_manager.say(SvPV_nolen(ST(0))); - else if (items == 2) - quest_manager.say(SvPV_nolen(ST(0)), (int) SvIV(ST(1))); - else - Perl_croak(aTHX_ "Usage: quest::say(string message, int language_id])"); + Journal::Options opts; + // we currently default to these + opts.speak_mode = Journal::SpeakMode::Say; + opts.journal_mode = Journal::Mode::Log2; + opts.language = 0; + opts.message_type = MT_NPCQuestSay; + if (items == 0 || items > 5) { + Perl_croak(aTHX_ "Usage: quest::say(string message, [int language_id], [int message_type], [int speak_mode], [int journal_mode])"); + } else if (items == 2) { + opts.language = (int)SvIV(ST(1)); + } else if (items == 3) { + opts.language = (int)SvIV(ST(1)); + opts.message_type = (int)SvIV(ST(2)); + } else if (items == 4) { + opts.language = (int)SvIV(ST(1)); + opts.message_type = (int)SvIV(ST(2)); + opts.speak_mode = (Journal::SpeakMode)SvIV(ST(3)); + } else if (items == 5) { + opts.language = (int)SvIV(ST(1)); + opts.message_type = (int)SvIV(ST(2)); + opts.speak_mode = (Journal::SpeakMode)SvIV(ST(3)); + opts.journal_mode = (Journal::Mode)SvIV(ST(4)); + } + + quest_manager.say(SvPV_nolen(ST(0)), opts); XSRETURN_EMPTY; } diff --git a/zone/entity.cpp b/zone/entity.cpp index 0ed696327..f7fea532b 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -3733,24 +3733,26 @@ bool Entity::CheckCoordLosNoZLeaps(float cur_x, float cur_y, float cur_z, } void EntityList::QuestJournalledSayClose(Mob *sender, Client *QuestInitiator, - float dist, const char* mobname, const char* message) + float dist, const char* mobname, const char* message, Journal::Options &opts) { - Client *c = nullptr; - float dist2 = dist * dist; + SerializeBuffer buf(sizeof(SpecialMesgHeader_Struct) + 12 + 64 + 64); - // Send the message to the quest initiator such that the client will enter it into the NPC Quest Journal - if (QuestInitiator) { - auto buf = new char[strlen(mobname) + strlen(message) + 10]; - sprintf(buf, "%s says, '%s'", mobname, message); - QuestInitiator->QuestJournalledMessage(mobname, buf); - safe_delete_array(buf); - } - // Use the old method for all other nearby clients - for (auto it = client_list.begin(); it != client_list.end(); ++it) { - c = it->second; - if(c && (c != QuestInitiator) && DistanceSquared(c->GetPosition(), sender->GetPosition()) <= dist2) - c->Message_StringID(10, GENERIC_SAY, mobname, message); - } + buf.WriteInt8(static_cast(opts.speak_mode)); + buf.WriteInt8(static_cast(opts.journal_mode)); + buf.WriteInt8(opts.language); + buf.WriteInt32(opts.message_type); + buf.WriteInt32(opts.target_spawn_id); + buf.WriteString(mobname); + buf.WriteInt32(0); // location, client doesn't seem to do anything with this + buf.WriteInt32(0); + buf.WriteInt32(0); + buf.WriteString(message); + + auto outapp = new EQApplicationPacket(OP_SpecialMesg, buf); + + // client only bothers logging if target spawn ID matches, safe to send to everyone + QueueCloseClients(sender, outapp, false, dist); + delete outapp; } Corpse *EntityList::GetClosestCorpse(Mob *sender, const char *Name) diff --git a/zone/entity.h b/zone/entity.h index 084cf6f01..c50575b5b 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -29,6 +29,7 @@ #include "position.h" #include "zonedump.h" +#include "common.h" class Encounter; class Beacon; @@ -337,7 +338,7 @@ public: void SendNimbusEffects(Client *c); void SendUntargetable(Client *c); void DuelMessage(Mob* winner, Mob* loser, bool flee); - void QuestJournalledSayClose(Mob *sender, Client *QuestIntiator, float dist, const char* mobname, const char* message); + void QuestJournalledSayClose(Mob *sender, Client *QuestIntiator, float dist, const char* mobname, const char* message, Journal::Options &opts); void GroupMessage(uint32 gid, const char *from, const char *message); void ExpeditionWarning(uint32 minutes_left); diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index 9dd1d8c9b..ef9180b68 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -36,6 +36,8 @@ struct BodyTypes { }; struct Filters { }; struct MessageTypes { }; struct Rule { }; +struct Journal_SpeakMode { }; +struct Journal_Mode { }; struct lua_registered_event { std::string encounter_name; @@ -2232,6 +2234,7 @@ luabind::scope lua_register_message_types() { return luabind::class_("MT") .enum_("constants") [ + luabind::value("NPCQuestSay", MT_NPCQuestSay), luabind::value("Say", MT_Say), luabind::value("Tell", MT_Tell), luabind::value("Group", MT_Group), @@ -2362,4 +2365,27 @@ luabind::scope lua_register_ruleb() { ]; } +luabind::scope lua_register_journal_speakmode() { + return luabind::class_("SpeakMode") + .enum_("constants") + [ + luabind::value("Raw", static_cast(Journal::SpeakMode::Raw)), + luabind::value("Say", static_cast(Journal::SpeakMode::Say)), + luabind::value("Shout", static_cast(Journal::SpeakMode::Shout)), + luabind::value("EmoteAlt", static_cast(Journal::SpeakMode::EmoteAlt)), + luabind::value("Emote", static_cast(Journal::SpeakMode::Emote)), + luabind::value("Group", static_cast(Journal::SpeakMode::Group)) + ]; +} + +luabind::scope lua_register_journal_mode() { + return luabind::class_("JournalMode") + .enum_("constants") + [ + luabind::value("None", static_cast(Journal::Mode::None)), + luabind::value("Log1", static_cast(Journal::Mode::Log1)), + luabind::value("Log2", static_cast(Journal::Mode::Log2)) + ]; +} + #endif diff --git a/zone/lua_general.h b/zone/lua_general.h index 4c8b7f8d2..d5b741a76 100644 --- a/zone/lua_general.h +++ b/zone/lua_general.h @@ -19,6 +19,8 @@ luabind::scope lua_register_rules_const(); luabind::scope lua_register_rulei(); luabind::scope lua_register_ruler(); luabind::scope lua_register_ruleb(); +luabind::scope lua_register_journal_speakmode(); +luabind::scope lua_register_journal_mode(); #endif #endif diff --git a/zone/lua_mob.cpp b/zone/lua_mob.cpp index 906684dfe..6f7e435f3 100644 --- a/zone/lua_mob.cpp +++ b/zone/lua_mob.cpp @@ -760,7 +760,65 @@ void Lua_Mob::Say(const char* message, int language) { void Lua_Mob::QuestSay(Lua_Client client, const char *message) { Lua_Safe_Call_Void(); - self->QuestJournalledSay(client, message); + Journal::Options journal_opts; + journal_opts.speak_mode = Journal::SpeakMode::Say; + journal_opts.journal_mode = RuleB(NPC, EnableNPCQuestJournal) ? Journal::Mode::Log2 : Journal::Mode::None; + journal_opts.language = 0; + journal_opts.message_type = MT_NPCQuestSay; + journal_opts.target_spawn_id = 0; + self->QuestJournalledSay(client, message, journal_opts); +} + +void Lua_Mob::QuestSay(Lua_Client client, const char *message, luabind::adl::object opts) { + Lua_Safe_Call_Void(); + + Journal::Options journal_opts; + // defaults + journal_opts.speak_mode = Journal::SpeakMode::Say; + journal_opts.journal_mode = Journal::Mode::Log2; + journal_opts.language = 0; + journal_opts.message_type = MT_NPCQuestSay; + journal_opts.target_spawn_id = 0; + + if (luabind::type(opts) == LUA_TTABLE) { + auto cur = opts["speak_mode"]; + if (luabind::type(cur) != LUA_TNIL) { + try { + journal_opts.speak_mode = static_cast(luabind::object_cast(cur)); + } catch (luabind::cast_failed) { + } + } + + cur = opts["journal_mode"]; + if (luabind::type(cur) != LUA_TNIL) { + try { + journal_opts.journal_mode = static_cast(luabind::object_cast(cur)); + } catch (luabind::cast_failed) { + } + } + + cur = opts["language"]; + if (luabind::type(cur) != LUA_TNIL) { + try { + journal_opts.language = luabind::object_cast(cur); + } catch (luabind::cast_failed) { + } + } + + cur = opts["message_type"]; + if (luabind::type(cur) != LUA_TNIL) { + try { + journal_opts.message_type = luabind::object_cast(cur); + } catch (luabind::cast_failed) { + } + } + } + + // if rule disables it, we override provided + if (!RuleB(NPC, EnableNPCQuestJournal)) + journal_opts.journal_mode = Journal::Mode::None; + + self->QuestJournalledSay(client, message, journal_opts); } void Lua_Mob::Shout(const char *message) { @@ -2331,7 +2389,8 @@ luabind::scope lua_register_mob() { .def("Message_StringID", &Lua_Mob::Message_StringID) .def("Say", (void(Lua_Mob::*)(const char*))& Lua_Mob::Say) .def("Say", (void(Lua_Mob::*)(const char*, int))& Lua_Mob::Say) - .def("QuestSay", &Lua_Mob::QuestSay) + .def("QuestSay", (void(Lua_Mob::*)(Lua_Client,const char *))&Lua_Mob::QuestSay) + .def("QuestSay", (void(Lua_Mob::*)(Lua_Client,const char *,luabind::adl::object))&Lua_Mob::QuestSay) .def("Shout", (void(Lua_Mob::*)(const char*))& Lua_Mob::Shout) .def("Shout", (void(Lua_Mob::*)(const char*, int))& Lua_Mob::Shout) .def("Emote", &Lua_Mob::Emote) diff --git a/zone/lua_mob.h b/zone/lua_mob.h index be8eb388f..1270f2121 100644 --- a/zone/lua_mob.h +++ b/zone/lua_mob.h @@ -170,6 +170,7 @@ public: void Say(const char *message); void Say(const char* message, int language); void QuestSay(Lua_Client client, const char *message); + void QuestSay(Lua_Client client, const char *message, luabind::adl::object opts); void Shout(const char *message); void Shout(const char* message, int language); void Emote(const char *message); diff --git a/zone/lua_parser.cpp b/zone/lua_parser.cpp index dda4b2d23..2c7cfe53e 100644 --- a/zone/lua_parser.cpp +++ b/zone/lua_parser.cpp @@ -1102,7 +1102,9 @@ void LuaParser::MapFunctions(lua_State *L) { lua_register_rules_const(), lua_register_rulei(), lua_register_ruler(), - lua_register_ruleb() + lua_register_ruleb(), + lua_register_journal_speakmode(), + lua_register_journal_mode() ]; } catch(std::exception &ex) { diff --git a/zone/mob.cpp b/zone/mob.cpp index b068905a9..ca844d459 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -2911,9 +2911,13 @@ void Mob::Emote(const char *format, ...) GENERIC_EMOTE, GetCleanName(), buf); } -void Mob::QuestJournalledSay(Client *QuestInitiator, const char *str) +void Mob::QuestJournalledSay(Client *QuestInitiator, const char *str, Journal::Options &opts) { - entity_list.QuestJournalledSayClose(this, QuestInitiator, 200, GetCleanName(), str); + // just in case + if (opts.target_spawn_id == 0 && QuestInitiator) + opts.target_spawn_id = QuestInitiator->GetID(); + + entity_list.QuestJournalledSayClose(this, QuestInitiator, 200, GetCleanName(), str, opts); } const char *Mob::GetCleanName() diff --git a/zone/mob.h b/zone/mob.h index ea71017c7..d2ebe3934 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -744,7 +744,7 @@ public: const char *message6 = 0, const char *message7 = 0, const char *message8 = 0, const char *message9 = 0); void Shout(const char *format, ...); void Emote(const char *format, ...); - void QuestJournalledSay(Client *QuestInitiator, const char *str); + void QuestJournalledSay(Client *QuestInitiator, const char *str, Journal::Options &opts); int32 GetItemStat(uint32 itemid, const char *identifier); int16 CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, bool best_focus=false); diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 42cb39503..79f5af8a5 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -154,30 +154,19 @@ void QuestManager::echo(int colour, const char *str) { entity_list.MessageClose(initiator, false, 200, colour, str); } -void QuestManager::say(const char *str) { +void QuestManager::say(const char *str, Journal::Options &opts) { QuestManagerCurrentQuestVars(); if (!owner) { Log(Logs::General, Logs::Quests, "QuestManager::say called with nullptr owner. Probably syntax error in quest file."); return; } else { - if(RuleB(NPC, EnableNPCQuestJournal) && initiator) { - owner->QuestJournalledSay(initiator, str); + if (!RuleB(NPC, EnableNPCQuestJournal)) + opts.journal_mode = Journal::Mode::None; + if (initiator) { + opts.target_spawn_id = initiator->GetID(); + owner->QuestJournalledSay(initiator, str, opts); } - else { - owner->Say(str); - } - } -} - -void QuestManager::say(const char *str, uint8 language) { - QuestManagerCurrentQuestVars(); - if (!owner) { - Log(Logs::General, Logs::Quests, "QuestManager::say called with nullptr owner. Probably syntax error in quest file."); - return; - } - else { - entity_list.ChannelMessage(owner, 8, language, str); } } diff --git a/zone/questmgr.h b/zone/questmgr.h index 6517872d8..1b9ea9c4d 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -62,8 +62,7 @@ public: //quest functions void echo(int colour, const char *str); - void say(const char *str); - void say(const char *str, uint8 language); + void say(const char *str, Journal::Options &opts); void me(const char *str); void summonitem(uint32 itemid, int16 charges = -1); void write(const char *file, const char *str);