diff --git a/modules/custom/cpp/ah_announcement.cpp b/modules/custom/cpp/ah_announcement.cpp index 0cfd2981281..f42b4405b31 100644 --- a/modules/custom/cpp/ah_announcement.cpp +++ b/modules/custom/cpp/ah_announcement.cpp @@ -162,8 +162,7 @@ class AHAnnouncementModule : public CPPModule name[0] = std::toupper(name[0]); // Send message to seller! - message::send(sellerId, std::make_unique(PChar, MESSAGE_SYSTEM_3, - fmt::format("Your '{}' has sold to {} for {} gil!", name, PChar->name, price).c_str(), "")); + // message::send(sellerId, std::make_unique(PChar, MESSAGE_SYSTEM_3, fmt::format("Your '{}' has sold to {} for {} gil!", name, PChar->name, price).c_str(), "")); } } } diff --git a/src/common/cbasetypes.h b/src/common/cbasetypes.h index f92451ff7af..a0ccce29186 100644 --- a/src/common/cbasetypes.h +++ b/src/common/cbasetypes.h @@ -6,7 +6,9 @@ #include #include #include +#include #include +#include #include "logging.h" #include "macros.h" diff --git a/src/common/ipc.h b/src/common/ipc.h index 38a6d1622cd..c311609d557 100644 --- a/src/common/ipc.h +++ b/src/common/ipc.h @@ -28,6 +28,27 @@ #include #include +#include "cbasetypes.h" + +// +// Forward declarations (before including ipc headers) +// + +namespace ipc +{ + template + auto toBytes(const T& object) -> std::vector; + + template + auto toBytesWithHeader(const T& object) -> std::vector; + + template + auto fromBytes(const std::span message) -> std::optional; + + template + auto fromBytesWithHeader(const std::span message) -> std::optional; +} // namespace ipc + #include "ipc_structs.h" #include "ipc_stubs.h" @@ -35,22 +56,27 @@ namespace ipc { - // Forward declarations - enum class MessageType : uint8_t; + // + // Helpers + // template - MessageType getEnumType(); + auto toBytes(const T& object) -> std::vector + { + auto bytes = std::vector(); + alpaca::serialize(object, bytes); + return bytes; + } - // Helpers template - auto toBytes(const T& object) -> std::vector + auto toBytesWithHeader(const T& object) -> std::vector { - auto bytes = std::vector(); + auto bytes = std::vector(); auto bytes_written = alpaca::serialize(object, bytes); - const auto type = static_cast(getEnumType()); + const auto type = static_cast(getEnumType()); - std::vector message(1 + bytes_written); + std::vector message(1 + bytes_written); message[0] = type; std::memcpy(message.data() + 1, bytes.data(), bytes_written); @@ -58,7 +84,25 @@ namespace ipc } template - auto fromBytes(const std::vector& message) -> std::optional + auto fromBytes(const std::span message) -> std::optional + { + if (message.empty()) + { + return std::nullopt; + } + + auto ec = std::error_code{}; + auto object = alpaca::deserialize(message, ec); + if (ec) + { + return std::nullopt; + } + + return std::move(object); + } + + template + auto fromBytesWithHeader(const std::span message) -> std::optional { if (message.empty()) { @@ -82,12 +126,4 @@ namespace ipc return std::move(object); } - - // Message handler implementation - class IPCMessageHandler : public IIPCMessageHandler - { - private: - // Don't handle this message type if we get it - void handleMessage_SomeData(const SomeData& message) override = default; - }; } // namespace ipc diff --git a/src/common/ipc_structs.h b/src/common/ipc_structs.h index 02caa83c179..12c54d7c383 100644 --- a/src/common/ipc_structs.h +++ b/src/common/ipc_structs.h @@ -21,6 +21,11 @@ #pragma once +#include "cbasetypes.h" + +#include "common/regional_event.h" +#include "map/packets/message_standard.h" + #include #include #include @@ -28,8 +33,215 @@ namespace ipc { - struct SomeData // Example struct. Remove this once we have real structs. + struct EmptyStruct + { + }; + + struct CharLogin + { + uint32 accountId; + uint32 charId; + }; + + struct CharVarUpdate + { + uint32 charId; + int32 value; + uint32 expiry; + std::string varName; + }; + + struct ChatMessageTell + { + uint32 senderId; + std::string senderName; + std::string recipientName; + std::string message; + uint16 zoneId; + uint8 gmLevel; + }; + + struct ChatMessageParty + { + uint32 partyId; + uint32 senderId; + std::string senderName; + std::string message; + uint16 zoneId; + uint8 gmLevel; + }; + + struct ChatMessageAlliance + { + uint32 allianceId; + uint32 senderId; + std::string senderName; + std::string message; + uint16 zoneId; + uint8 gmLevel; + }; + + struct ChatMessageLinkshell + { + uint32 linkshellId; + uint32 senderId; + std::string senderName; + std::string message; + uint16 zoneId; + uint8 gmLevel; + }; + + struct ChatMessageUnity + { + uint32 unityLeaderId; + uint32 senderId; + std::string senderName; + std::string message; + uint16 zoneId; + uint8 gmLevel; + }; + + struct ChatMessageYell + { + uint32 senderId; + std::string senderName; + std::string message; + uint16 zoneId; + uint8 gmLevel; + }; + + struct ChatMessageServerMessage + { + uint32 senderId; + std::string senderName; + std::string message; + uint16 zoneId; + uint8 gmLevel; + }; + + struct PartyInvite + { + uint32 inviteeId; + uint16 inviteeTargId; + uint32 inviterId; + uint16 inviterTargId; + std::string inviterName; + uint8 inviteType; + }; + + struct PartyInviteResponse + { + uint32 inviteeId; + uint16 inviteeTargId; + uint32 inviterId; + uint16 inviterTargId; + std::string inviterName; + uint8 inviteAnswer; + }; + + struct PartyReload + { + uint32 partyId; + }; + + struct PartyDisband + { + uint32 partyId; + }; + + struct AllianceReload + { + uint32 allianceId; + }; + + struct AllianceDissolve + { + uint32 allianceId; + }; + + struct PlayerKick + { + uint32 charId; + }; + + struct MessageStandard + { + uint32 charId; + MsgStd message; + }; + + struct MessageSystem + { + uint32 charId; + MsgStd message; + }; + + struct LinkshellRankChange + { + uint32 charId; + std::string linkshellName; + uint32 linkshellId; + uint8 permission; + }; + + struct LinkshellRemove + { + uint32 charId; + std::string linkshellName; + uint32 linkshellId; + uint8 linkshellType; + }; + + struct LinkshellSetMessage + { + uint32 linkshellId; + std::string poster; + std::string message; + }; + + struct LuaFunction + { + uint16 zoneId; + std::string funcString; + }; + + struct KillSession + { + uint32 charId; + }; + + struct RegionalEvent + { + RegionalEventType type; + uint8 subType; + std::vector payload; + }; + + struct GMSendToZone + { + uint32 targetId; + uint32 requesterId; + + uint16 zoneId; + float x; + float y; + float z; + uint8 rot; + uint32 moghouseID; + }; + + struct GMSendToEntity + { + uint16 zoneId; + }; + + struct RPCSend + { + uint16 zoneId; + }; + + struct RPCRecv { - std::vector messages; + uint16 zoneId; }; } // namespace ipc diff --git a/src/common/ipp.h b/src/common/ipp.h new file mode 100644 index 00000000000..5f9854fe023 --- /dev/null +++ b/src/common/ipp.h @@ -0,0 +1,91 @@ +/* +=========================================================================== + + Copyright (c) 2025 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#pragma once + +#include "cbasetypes.h" +#include "logging.h" +#include "socket.h" + +#include +#include + +class IPP final +{ +public: + IPP() + { + } + + explicit IPP(const uint32 ip, const uint16 port) + : ip_(ip) + , port_(port) + { + } + + explicit IPP(const uint64& ipp) + : IPP(static_cast(ipp), static_cast(ipp >> 32)) + { + } + + explicit IPP(const zmq::message_t& message) + : IPP(*reinterpret_cast(message.data())) + { + } + + auto toString() const -> std::string + { + in_addr inaddr{}; + inaddr.s_addr = ip_; + + char address[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &inaddr, address, INET_ADDRSTRLEN); + + // TODO: Add a designator for if this address is connect, map, search, or world, etc. + + // This is internal, so we can trust it. + return fmt::format("{}:{}", asStringFromUntrustedSource(address), port_); + } + + auto getIPP() const -> uint64 + { + return static_cast(ip_) | (static_cast(port_) << 32); + } + + auto toZMQMessage() const -> zmq::message_t + { + const auto ipp = getIPP(); + return zmq::message_t(&ipp, sizeof(ipp)); + } + + // + // Operators for use with STL containers + // + + auto operator<(const IPP& other) const -> bool + { + return ip_ < other.ip_ || (ip_ == other.ip_ && port_ < other.port_); + } + +private: + uint32 ip_{}; + uint16 port_{}; +}; diff --git a/src/common/mmo.h b/src/common/mmo.h index eea582857ab..6006c0f2f2a 100644 --- a/src/common/mmo.h +++ b/src/common/mmo.h @@ -34,145 +34,6 @@ #define FFXI_HEADER_SIZE 0x1C // common packet header size -enum MSGSERVTYPE : uint8 -{ - MSG_LOGIN, - MSG_CHAT_TELL, - MSG_CHAT_PARTY, - MSG_CHAT_ALLIANCE, - MSG_CHAT_LINKSHELL, - MSG_CHAT_UNITY, - MSG_CHAT_YELL, - MSG_CHAT_SERVMES, - MSG_PT_INVITE, - MSG_PT_INV_RES, - MSG_PT_RELOAD, - MSG_PT_DISBAND, - MSG_ALLIANCE_RELOAD, - MSG_ALLIANCE_DISSOLVE, - MSG_PLAYER_KICK, - MSG_DIRECT, - MSG_LINKSHELL_RANK_CHANGE, - MSG_LINKSHELL_REMOVE, - MSG_LUA_FUNCTION, - MSG_CHARVAR_UPDATE, - - // Login/session related - MSG_KILL_SESSION, // Kill session on processes that receive this. Intended to delete sessions ahead of map cleanup - - // conquest, besieged, campaign.. - MSG_WORLD2MAP_REGIONAL_EVENT, - MSG_MAP2WORLD_REGIONAL_EVENT, - - // gm commands - MSG_SEND_TO_ZONE, - MSG_SEND_TO_ENTITY, - - // rpc - MSG_RPC_SEND, // sent by sender -> reciever - MSG_RPC_RECV, // sent by reciever -> sender -}; -DECLARE_FORMAT_AS_UNDERLYING(MSGSERVTYPE); - -enum REGIONALMSGTYPE : uint8 -{ - REGIONAL_EVT_MSG_CONQUEST, - REGIONAL_EVT_MSG_BESIEGED, - REGIONAL_EVT_MSG_CAMPAIGN, - REGIONAL_EVT_MSG_COLONIZATION, -}; -DECLARE_FORMAT_AS_UNDERLYING(REGIONALMSGTYPE); - -enum CONQUESTMSGTYPE : uint8 -{ - // WORLD --------> MAP - - // World map broadcasts weekly update started to all zones - CONQUEST_WORLD2MAP_WEEKLY_UPDATE_START, - // World map broadcasts that update is done, with the respective tally - CONQUEST_WORLD2MAP_WEEKLY_UPDATE_END, - // World map broadcasts influence point updates to all zones. - // Used for periodic updates or initialization. - CONQUEST_WORLD2MAP_INFLUENCE_POINTS, - // World map broadcasts region control data to all zones. - // Used for initialization. - CONQUEST_WORLD2MAP_REGION_CONTROL, - - // MAP ----------> WORLD - - // A GM Triggers a weekly update. From one zone to world. - // World should send CONQUEST_WORLD2MAP_WEEKLY_UPDATE_START and - // CONQUEST_WORLD2MAP_WEEKLY_UPDATE_END when done - CONQUEST_MAP2WORLD_GM_WEEKLY_UPDATE, - // A GM requests houry conquest data (just influence points). - // World server should respond with CONQUEST_WORLD2MAP_INFLUENCE_POINTS triggering a zone update - CONQUEST_MAP2WORLD_GM_CONQUEST_UPDATE, - // Influence point update from any zone to world. - CONQUEST_MAP2WORLD_ADD_INFLUENCE_POINTS, -}; -DECLARE_FORMAT_AS_UNDERLYING(CONQUESTMSGTYPE); - -constexpr auto msgTypeToStr = [](uint8 msgtype) -{ - switch (msgtype) - { - case MSG_LOGIN: - return "MSG_LOGIN"; - case MSG_CHAT_TELL: - return "MSG_CHAT_TELL"; - case MSG_CHAT_PARTY: - return "MSG_CHAT_PARTY"; - case MSG_CHAT_ALLIANCE: - return "MSG_CHAT_ALLIANCE"; - case MSG_CHAT_LINKSHELL: - return "MSG_CHAT_LINKSHELL"; - case MSG_CHAT_UNITY: - return "MSG_CHAT_UNITY"; - case MSG_CHAT_YELL: - return "MSG_CHAT_YELL"; - case MSG_CHAT_SERVMES: - return "MSG_CHAT_SERVMES"; - case MSG_PT_INVITE: - return "MSG_PT_INVITE"; - case MSG_PT_INV_RES: - return "MSG_PT_INV_RES"; - case MSG_PT_RELOAD: - return "MSG_PT_RELOAD"; - case MSG_PT_DISBAND: - return "MSG_PT_DISBAND"; - case MSG_ALLIANCE_RELOAD: - return "MSG_ALLIANCE_RELOAD"; - case MSG_ALLIANCE_DISSOLVE: - return "MSG_ALLIANCE_DISSOLVE"; - case MSG_PLAYER_KICK: - return "MSG_PLAYER_KICK"; - case MSG_DIRECT: - return "MSG_DIRECT"; - case MSG_LINKSHELL_RANK_CHANGE: - return "MSG_LINKSHELL_RANK_CHANGE"; - case MSG_LINKSHELL_REMOVE: - return "MSG_LINKSHELL_REMOVE"; - case MSG_LUA_FUNCTION: - return "MSG_LUA_FUNCTION"; - case MSG_CHARVAR_UPDATE: - return "MSG_CHARVAR_UPDATE"; - case MSG_SEND_TO_ZONE: - return "MSG_SEND_TO_ZONE"; - case MSG_SEND_TO_ENTITY: - return "MSG_SEND_TO_ENTITY"; - case MSG_RPC_SEND: - return "MSG_RPC_SEND"; - case MSG_RPC_RECV: - return "MSG_RPC_RECV"; - case MSG_WORLD2MAP_REGIONAL_EVENT: - return "MSG_WORLD2MAP_REGIONAL_EVENT"; - case MSG_KILL_SESSION: - return "MSG_KILL_SESSION"; - default: - return "Unknown"; - }; -}; - // For filters1_t, filters2_t and SAVE_CONF: // See https://github.com/atom0s/XiPackets/tree/main/world/server/0x00B4 struct filters1_t @@ -662,7 +523,6 @@ class char_mini m_zone = 0; m_nation = 0; }; - ~char_mini(){}; }; // https://github.com/atom0s/XiPackets/tree/main/world/client/0x000A diff --git a/src/common/regional_event.h b/src/common/regional_event.h new file mode 100644 index 00000000000..b3b4c8a3d8c --- /dev/null +++ b/src/common/regional_event.h @@ -0,0 +1,98 @@ +/* +=========================================================================== + + Copyright (c) 2025 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#pragma once + +#include "cbasetypes.h" + +#include "map/conquest_data.h" + +enum RegionalEventType : uint8 +{ + Conquest = 1, + Besieged = 2, + Campaign = 3, + Colonization = 4, +}; +DECLARE_FORMAT_AS_UNDERLYING(RegionalEventType); + +struct RegionalEvent +{ + // RegionalEventType + uint8 type; + + // ConquestMessage, etc. + uint8 subType; + + // Payload built with ipc::toBytes + std::vector payload; +}; + +enum ConquestMessage : uint8 +{ + // World map broadcasts weekly update started to all zones + W2M_WeeklyUpdateStart, + + // World map broadcasts that update is done, with the respective tally + W2M_WeeklyUpdateEnd, + + // World map broadcasts influence point updates to all zones. + // Used for periodic updates or initialization. + W2M_BroadcastInfluencePoints, + + // World map broadcasts region control data to all zones. + // Used for initialization. + W2M_BroadcastRegionControls, + + // A GM Triggers a weekly update. From one zone to world. + // World should send CONQUEST_WORLD2MAP_WEEKLY_UPDATE_START and + // CONQUEST_WORLD2MAP_WEEKLY_UPDATE_END when done + M2W_GM_WeeklyUpdate, + + // A GM requests houry conquest data (just influence points). + // World server should respond with CONQUEST_WORLD2MAP_INFLUENCE_POINTS triggering a zone update + M2W_GM_ConquestUpdate, + + // Influence point update from any zone to world. + M2W_AddInfluencePoints, +}; +DECLARE_FORMAT_AS_UNDERLYING(ConquestMessage); + +// W2M_BroadcastInfluencePoints +struct ConquestInfluenceUpdate +{ + uint8 shouldUpdateZones; + std::vector influences; +}; + +// W2M_BroadcastRegionControls +struct ConquestRegionControlUpdate +{ + std::vector regionControls; +}; + +// M2W_AddInfluencePoints +struct ConquestAddInfluencePoints +{ + int32 points; + uint32 nation; + uint8 region; +}; diff --git a/src/common/zmq_dealer_wrapper.h b/src/common/zmq_dealer_wrapper.h new file mode 100644 index 00000000000..5051cf204b5 --- /dev/null +++ b/src/common/zmq_dealer_wrapper.h @@ -0,0 +1,140 @@ +/* +=========================================================================== + + Copyright (c) 2025 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#pragma once + +#include "common/ipp.h" + +#include +#include + +#include +#include +#include +#include + +class ZMQDealerWrapper final +{ + class ZMQWorker final + { + public: + ZMQWorker(std::atomic& requestExit, + moodycamel::ConcurrentQueue& incomingQueue, + moodycamel::ConcurrentQueue& outgoingQueue, + const std::string& endpoint, + uint64_t routingId) + : requestExit_(requestExit) + , incomingQueue_(incomingQueue) + , outgoingQueue_(outgoingQueue) + , zmqContext_(1) + , zmqSocket_(zmqContext_, zmq::socket_type::dealer) + { + zmqSocket_.set(zmq::sockopt::routing_id, zmq::const_buffer(&routingId, sizeof(uint64))); + zmqSocket_.set(zmq::sockopt::rcvtimeo, 200); + + try + { + zmqSocket_.connect(endpoint); + } + catch (zmq::error_t& err) + { + throw std::runtime_error(fmt::format("Unable to bind chat socket: {}", err.what())); + } + + listen(); + } + + ~ZMQWorker() + { + zmqSocket_.close(); + zmqContext_.close(); + } + + private: + void listen() + { + while (!requestExit_) + { + TracyZoneScoped; + + zmq::message_t msg; + try + { + if (!zmqSocket_.recv(msg, zmq::recv_flags::none)) + { + while (outgoingQueue_.try_dequeue(msg)) + { + zmqSocket_.send(msg, zmq::send_flags::none); + } + continue; + } + + incomingQueue_.enqueue(std::move(msg)); + } + catch (zmq::error_t& e) + { + // Context was terminated (ETERM = 156384765) + // Exit loop + if (e.num() == 156384765) + { + return; + } + + ShowError(fmt::format("Message: {}", e.what())); + continue; + } + } + } + + private: + std::atomic& requestExit_; + + moodycamel::ConcurrentQueue& incomingQueue_; + moodycamel::ConcurrentQueue& outgoingQueue_; + + zmq::context_t zmqContext_; + zmq::socket_t zmqSocket_; + }; + +public: + ZMQDealerWrapper(const std::string& endpoint, uint64 routingId) + : requestExit_(false) + , thread_( + [this, endpoint, routingId]() + { + ZMQWorker worker(requestExit_, incomingQueue_, outgoingQueue_, endpoint, routingId); + }) + { + } + + ~ZMQDealerWrapper() + { + requestExit_ = true; + thread_.join(); + } + + moodycamel::ConcurrentQueue incomingQueue_; + moodycamel::ConcurrentQueue outgoingQueue_; + +private: + std::atomic requestExit_; + nonstd::jthread thread_; +}; diff --git a/src/common/zmq_router_wrapper.h b/src/common/zmq_router_wrapper.h new file mode 100644 index 00000000000..de633534e09 --- /dev/null +++ b/src/common/zmq_router_wrapper.h @@ -0,0 +1,162 @@ +/* +=========================================================================== + + Copyright (c) 2025 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#pragma once + +#include "common/ipp.h" + +#include +#include + +#include +#include +#include +#include + +struct HandleableMessage +{ + IPP ipp; + std::vector payload; +}; + +class ZMQRouterWrapper final +{ + class ZMQWorker final + { + public: + ZMQWorker(std::atomic& requestExit, + moodycamel::ConcurrentQueue& incomingQueue, + moodycamel::ConcurrentQueue& outgoingQueue, + const std::string& endpoint) + : requestExit_(requestExit) + , incomingQueue_(incomingQueue) + , outgoingQueue_(outgoingQueue) + , zmqContext_(1) + , zmqSocket_(zmqContext_, zmq::socket_type::router) + { + zmqSocket_.set(zmq::sockopt::rcvtimeo, 200); + + ShowInfo("Starting ZMQ Server on %s", endpoint.c_str()); + + try + { + zmqSocket_.bind(endpoint); + } + catch (zmq::error_t& err) + { + throw std::runtime_error(fmt::format("Unable to bind chat socket: {}", err.what())); + } + + listen(); + } + + ~ZMQWorker() + { + zmqSocket_.close(); + zmqContext_.close(); + } + + private: + void listen() + { + while (!requestExit_) + { + TracyZoneScoped; + + // Since we are a zmq::socket_type::router, we expect a multipart message: + // [routing id (IPP), message] + std::array msgs; + try + { + if (!zmq::recv_multipart_n(zmqSocket_, msgs.data(), msgs.size(), zmq::recv_flags::none)) + { + HandleableMessage msg; + while (outgoingQueue_.try_dequeue(msg)) + { + // We send the same way as we receive: [routing id (IPP), message] + msgs[0] = msg.ipp.toZMQMessage(); + msgs[1] = zmq::message_t(msg.payload); + zmq::send_multipart(zmqSocket_, msgs, zmq::send_flags::none); + } + continue; + } + } + catch (zmq::error_t& e) + { + // Context was terminated + // Exit loop + if (e.num() == 156384765) // ETERM + { + return; + } + + ShowError(fmt::format("Message: {}", e.what())); + continue; + } + + // Handle incoming message + auto& from = msgs[0]; + auto& data = msgs[1]; + + // TODO: Reduce copies here + + auto ipp = IPP(from); + auto payload = std::vector(data.data(), data.data() + data.size()); + + incomingQueue_.enqueue(HandleableMessage{ ipp, payload }); + } + } + + private: + std::atomic& requestExit_; + + moodycamel::ConcurrentQueue& incomingQueue_; + moodycamel::ConcurrentQueue& outgoingQueue_; + + zmq::context_t zmqContext_; + zmq::socket_t zmqSocket_; + }; + +public: + ZMQRouterWrapper(const std::string& endpoint) + : requestExit_(false) + , thread_( + [this, endpoint]() + { + TracySetThreadName("Message Server (ZMQ)"); + ZMQWorker worker(requestExit_, incomingQueue_, outgoingQueue_, endpoint); + }) + { + } + + ~ZMQRouterWrapper() + { + requestExit_ = true; + thread_.join(); + } + + moodycamel::ConcurrentQueue incomingQueue_; + moodycamel::ConcurrentQueue outgoingQueue_; + +private: + std::atomic requestExit_; + nonstd::jthread thread_; +}; diff --git a/src/login/auth_session.cpp b/src/login/auth_session.cpp index 0b78e233767..dac01c629eb 100644 --- a/src/login/auth_session.cpp +++ b/src/login/auth_session.cpp @@ -21,6 +21,7 @@ #include "auth_session.h" +#include "common/ipc.h" #include "common/socket.h" // for ref #include "common/utils.h" @@ -224,23 +225,25 @@ void auth_session::read_func() { while (rset->next()) { - /* - uint32 charid = rset->get("charid"); - uint64 ip = rset->get("ip"); - uint64 port = rset->get("port"); - - ip |= (port << 32); - - zmq::message_t chardata(sizeof(charid)); - ref((uint8*)chardata.data(), 0) = charid; - zmq::message_t empty(0); + // NOTE: This only fires if there is already a session, so `CharLogin` + // : could be named better. Could this be used to eject existing sessions? // TODO: MSG_LOGIN is a no-op in message_server.cpp, // : so sending this does nothing? // : But in the client (message.cpp), it _could_ // : be used to clear out lingering PChar data. - queue_message(ipp, MSG_LOGIN, &chardata, &empty); - */ + + const auto charId = rset->get("charid"); + + // TODO: Make a thin wrapper around ZMQDealerWrapper to make it easier + // : to send messages to the world server from connect and search. + + const auto payload = ipc::toBytesWithHeader(ipc::CharLogin{ + .accountId = accountID, + .charId = charId, + }); + + zmqDealerWrapper_.outgoingQueue_.enqueue(zmq::message_t(payload.data(), payload.size())); } } @@ -248,7 +251,6 @@ void auth_session::read_func() // Not a real problem because the account is locked out when a character is logged in. /* - const auto rset = db::preparedStmt("SELECT charid \ FROM accounts_sessions \ WHERE accid = ? LIMIT 1";, accountID); @@ -268,7 +270,8 @@ void auth_session::read_func() ref(data_, 0) = LOGIN_ERROR_ALREADY_LOGGED_IN; do_write(1); return; - }*/ + } + */ // Success std::memset(data_, 0, 49); diff --git a/src/login/auth_session.h b/src/login/auth_session.h index e6c55f1460d..39c4b0068c1 100644 --- a/src/login/auth_session.h +++ b/src/login/auth_session.h @@ -28,6 +28,8 @@ #include "handler_session.h" #include "login_helpers.h" +#include "common/zmq_dealer_wrapper.h" + // TODO: Move to enum #define LOGIN_ATTEMPT 0x10 #define LOGIN_CREATE 0x20 @@ -94,8 +96,9 @@ DECLARE_FORMAT_AS_UNDERLYING(ACCOUNT_PRIVILEGE_CODE); class auth_session : public handler_session { public: - auth_session(asio::ssl::stream socket) + auth_session(asio::ssl::stream socket, ZMQDealerWrapper& zmqDealerWrapper) : handler_session(std::move(socket)) + , zmqDealerWrapper_(zmqDealerWrapper) { DebugSockets(fmt::format("auth_session from {}", ipAddress)); } @@ -120,4 +123,7 @@ class auth_session : public handler_session } void do_write(std::size_t length); + +private: + ZMQDealerWrapper& zmqDealerWrapper_; }; diff --git a/src/login/connect_server.cpp b/src/login/connect_server.cpp index 4274b3e0042..1e6df4fa8ca 100644 --- a/src/login/connect_server.cpp +++ b/src/login/connect_server.cpp @@ -21,8 +21,32 @@ #include "connect_server.h" +namespace +{ + auto getZMQEndpointString() -> std::string + { + return fmt::format("tcp://{}:{}", settings::get("network.ZMQ_IP"), settings::get("network.ZMQ_PORT")); + } + + auto getZMQRoutingId() -> uint64 + { + // We will only ever have a single login server, so we can use different logic for the routing id + + const auto ip = settings::get("network.LOGIN_AUTH_IP"); + const uint64 port = settings::get("network.LOGIN_AUTH_PORT"); + + uint64 ipp{}; + inet_pton(AF_INET, ip.c_str(), &ipp); + + ipp |= (port << 32); + + return ipp; + } +} // namespace + ConnectServer::ConnectServer(int argc, char** argv) : Application("connect", argc, argv) +, zmqDealerWrapper_(getZMQEndpointString(), getZMQRoutingId()) { asio::io_context io_context; @@ -75,9 +99,9 @@ ConnectServer::ConnectServer(int argc, char** argv) ShowInfo("creating ports"); // Handler creates session of type T for specific port on connection. - handler auth(io_context, settings::get("network.LOGIN_AUTH_PORT")); - handler view(io_context, settings::get("network.LOGIN_VIEW_PORT")); - handler data(io_context, settings::get("network.LOGIN_DATA_PORT")); + handler auth(io_context, settings::get("network.LOGIN_AUTH_PORT"), zmqDealerWrapper_); + handler view(io_context, settings::get("network.LOGIN_VIEW_PORT"), zmqDealerWrapper_); + handler data(io_context, settings::get("network.LOGIN_DATA_PORT"), zmqDealerWrapper_); asio::steady_timer cleanup_callback(io_context, std::chrono::minutes(15)); cleanup_callback.async_wait(std::bind(&ConnectServer::periodicCleanup, this, std::placeholders::_1, &cleanup_callback)); diff --git a/src/login/connect_server.h b/src/login/connect_server.h index 3939726f019..afa7f472f36 100644 --- a/src/login/connect_server.h +++ b/src/login/connect_server.h @@ -29,6 +29,8 @@ #include "common/settings.h" #include "common/utils.h" #include "common/xirand.h" +#include "common/zmq_dealer_wrapper.h" + #include "map/packets/basic.h" #include @@ -85,4 +87,7 @@ class ConnectServer final : public Application // When this happens, the data/view socket are never opened and will never be cleaned up normally. // Auth is closed before any other sessions are open, so the data/view cleanups aren't sufficient void periodicCleanup(const asio::error_code& error, asio::steady_timer* timer); + +private: + ZMQDealerWrapper zmqDealerWrapper_; }; diff --git a/src/login/handler.h b/src/login/handler.h index 0dfe529f531..4891d29f03d 100644 --- a/src/login/handler.h +++ b/src/login/handler.h @@ -29,13 +29,16 @@ #include "data_session.h" #include "view_session.h" +#include "common/zmq_dealer_wrapper.h" + template class handler { public: - handler(asio::io_context& io_context, unsigned int port) + handler(asio::io_context& io_context, unsigned int port, ZMQDealerWrapper& zmqDealerWrapper) : acceptor_(io_context, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)) , sslContext_(asio::ssl::context::tls_server) + , zmqDealerWrapper_(zmqDealerWrapper) { acceptor_.set_option(asio::socket_base::reuse_address(true)); @@ -58,7 +61,7 @@ class handler { if constexpr (std::is_same_v) { - auto auth_handler = std::make_shared(asio::ssl::stream(std::move(socket), sslContext_)); + auto auth_handler = std::make_shared(asio::ssl::stream(std::move(socket), sslContext_), zmqDealerWrapper_); auth_handler->start(); } else if constexpr (std::is_same_v) @@ -84,4 +87,6 @@ class handler asio::ip::tcp::acceptor acceptor_; asio::ssl::context sslContext_; + + ZMQDealerWrapper& zmqDealerWrapper_; }; diff --git a/src/map/alliance.cpp b/src/map/alliance.cpp index 448fee88c5a..bb2c48f41ed 100644 --- a/src/map/alliance.cpp +++ b/src/map/alliance.cpp @@ -27,8 +27,8 @@ #include "conquest_system.h" #include "entities/battleentity.h" +#include "ipc_client.h" #include "map.h" -#include "message.h" #include "party.h" #include "treasure_pool.h" #include "utils/charutils.h" @@ -78,9 +78,10 @@ void CAlliance::dissolveAlliance(bool playerInitiated) { // sql->Query("UPDATE accounts_parties SET allianceid = 0, partyflag = partyflag & ~%d WHERE allianceid = %u", ALLIANCE_LEADER | PARTY_SECOND // | PARTY_THIRD, m_AllianceID); - uint8 data[4]{}; - ref(data, 0) = m_AllianceID; - message::send(MSG_ALLIANCE_DISSOLVE, data, sizeof(data), nullptr); + + message::send(ipc::AllianceDissolve{ + .allianceId = m_AllianceID, + }); } else { @@ -167,13 +168,14 @@ void CAlliance::removeParty(CParty* party) ALLIANCE_LEADER | PARTY_SECOND | PARTY_THIRD, party->GetPartyID()); // notify alliance - uint8 data[4]{}; - ref(data, 0) = m_AllianceID; - message::send(MSG_ALLIANCE_RELOAD, data, sizeof(data), nullptr); + message::send(ipc::AllianceReload{ + .allianceId = m_AllianceID, + }); // notify leaving party - ref(data, 0) = party->GetPartyID(); - message::send(MSG_PT_RELOAD, data, sizeof(data), nullptr); + message::send(ipc::PartyReload{ + .partyId = party->GetPartyID(), + }); } void CAlliance::delParty(CParty* party) @@ -267,9 +269,9 @@ void CAlliance::addParty(CParty* party) party->SetPartyNumber(newparty); - uint8 data[4]{}; - ref(data, 0) = m_AllianceID; - message::send(MSG_ALLIANCE_RELOAD, data, sizeof(data), nullptr); + message::send(ipc::AllianceReload{ + .allianceId = m_AllianceID, + }); } void CAlliance::addParty(uint32 partyid) const @@ -294,9 +296,9 @@ void CAlliance::addParty(uint32 partyid) const _sql->Query("UPDATE accounts_parties SET allianceid = %u, partyflag = partyflag | %d WHERE partyid = %u", m_AllianceID, newparty, partyid); - uint8 data[4]{}; - ref(data, 0) = m_AllianceID; - message::send(MSG_ALLIANCE_RELOAD, data, sizeof(data), nullptr); + message::send(ipc::AllianceReload{ + .allianceId = m_AllianceID, + }); } void CAlliance::pushParty(CParty* PParty, uint8 number) diff --git a/src/map/conquest_system.cpp b/src/map/conquest_system.cpp index 568d31d489b..a56f4fda6c1 100644 --- a/src/map/conquest_system.cpp +++ b/src/map/conquest_system.cpp @@ -24,7 +24,7 @@ #include "common/mmo.h" #include "common/vana_time.h" #include "entities/charentity.h" -#include "message.h" +#include "ipc_client.h" #include "utils/charutils.h" #include "utils/zoneutils.h" @@ -47,75 +47,37 @@ namespace conquest return conquestData; } - void HandleZMQMessage(uint8* data) + void HandleZMQMessage(uint8 subType, const std::span data) { - uint8 subtype = ref(data, 1); - switch (subtype) + switch (static_cast(subType)) { - case CONQUEST_WORLD2MAP_WEEKLY_UPDATE_START: + case W2M_WeeklyUpdateStart: { HandleWeeklyTallyStart(); break; } - case CONQUEST_WORLD2MAP_WEEKLY_UPDATE_END: + case W2M_WeeklyUpdateEnd: { - const std::size_t headerLength = 2 * sizeof(uint8) + sizeof(std::size_t); - const std::size_t size = ref(data, 2); - - std::vector regionControls = std::vector(size); - for (std::size_t i = 0; i < size; i++) + if (const auto object = ipc::fromBytes(data)) { - const std::size_t start = headerLength + i * sizeof(region_control_t); - - region_control_t regionControl{}; - regionControl.current = ref(data, start); - regionControl.prev = ref(data, start + 1); - - regionControls[i] = regionControl; + HandleWeeklyTallyEnd((*object).regionControls); } - - HandleWeeklyTallyEnd(regionControls); break; } - case CONQUEST_WORLD2MAP_INFLUENCE_POINTS: + case W2M_BroadcastInfluencePoints: { - const std::size_t headerLength = 2 * sizeof(uint8) + sizeof(bool) + sizeof(std::size_t); - bool shouldUpdateZones = ref(data, 2); - const std::size_t size = ref(data, 3); - std::vector influencePoints = std::vector(size); - for (std::size_t i = 0; i < size; i++) + if (const auto object = ipc::fromBytes(data)) { - const std::size_t start = headerLength + i * sizeof(influence_t); - - influence_t influence{}; - influence.sandoria_influence = ref(data, start); - influence.bastok_influence = ref(data, start + 2); - influence.windurst_influence = ref(data, start + 4); - influence.beastmen_influence = ref(data, start + 6); - - influencePoints[i] = influence; + HandleInfluenceUpdate((*object).influences, (*object).shouldUpdateZones); } - - HandleInfluenceUpdate(influencePoints, shouldUpdateZones); break; } - case CONQUEST_WORLD2MAP_REGION_CONTROL: + case W2M_BroadcastRegionControls: { - const std::size_t headerLength = 2 * sizeof(uint8) + sizeof(std::size_t); - const std::size_t size = ref(data, 2); - - std::vector regionControls; - for (std::size_t i = 0; i < size; i++) + if (const auto object = ipc::fromBytes(data)) { - const std::size_t start = headerLength + i * sizeof(region_control_t); - - region_control_t regionControl{}; - regionControl.current = ref(data, start); - regionControl.prev = ref(data, start + 1); - regionControls.emplace_back(regionControl); + GetConquestData()->updateRegionControls((*object).regionControls); } - - GetConquestData()->updateRegionControls(regionControls); break; } } @@ -126,27 +88,21 @@ namespace conquest // Send update message to world server // Note that we do not update local cache, as it would potentially become out of sync from // world server due to other map updates anyway. We wait for eventual consistency. - const std::size_t headerLength = 2 * sizeof(uint8); - const std::size_t dataLength = headerLength + sizeof(int32) + sizeof(uint32) + sizeof(uint8); - uint8 data[dataLength]{}; - - // Regional event type + conquest msg type - ref((uint8*)data, 0) = REGIONAL_EVT_MSG_CONQUEST; - ref((uint8*)data, 1) = CONQUEST_MAP2WORLD_ADD_INFLUENCE_POINTS; - - // Payload - ref(data, 2) = points; - ref(data, 6) = nation; - ref(data, 10) = (uint8)region; - // Do send the message - message::send(MSG_MAP2WORLD_REGIONAL_EVENT, data, dataLength); + message::send(ipc::RegionalEvent{ + .type = RegionalEventType::Conquest, + .subType = ConquestMessage::M2W_AddInfluencePoints, + .payload = ipc::toBytes(ConquestAddInfluencePoints{ + .points = points, + .nation = nation, + .region = static_cast(region), + }), + }); } /************************************************************************ - * GainInfluencePoints * + * GainInfluencePoints * * +1 point for nation * - * * ************************************************************************/ void GainInfluencePoints(CCharEntity* PChar, uint32 points) @@ -361,38 +317,28 @@ namespace conquest } /************************************************************************ - * UpdateConquestGM * - * Update region control * - * just used by GM command * + * UpdateConquestGM * + * Update region control * + * just used by GM command * ************************************************************************/ void UpdateConquestGM(ConquestUpdate type) { if (type == Conquest_Tally_Start) { - // Notify world server that we want to force a weekly update - const std::size_t dataLen = 2 * sizeof(uint8); - uint8 data[dataLen]{}; - - // Create ZMQ message with header and no other payload - ref((uint8*)data, 0) = REGIONAL_EVT_MSG_CONQUEST; - ref((uint8*)data, 1) = CONQUEST_MAP2WORLD_GM_WEEKLY_UPDATE; - - // Send to world - message::send(MSG_MAP2WORLD_REGIONAL_EVENT, data, dataLen); + message::send(ipc::RegionalEvent{ + .type = RegionalEventType::Conquest, + .subType = ConquestMessage::M2W_GM_WeeklyUpdate, + // No payload + }); } else if (type == Conquest_Update) { - // Fetch most recent data from world server - const std::size_t dataLen = 2 * sizeof(uint8); - uint8 data[dataLen]{}; - - // Create ZMQ message with header and no payload - ref((uint8*)data, 0) = REGIONAL_EVT_MSG_CONQUEST; - ref((uint8*)data, 1) = CONQUEST_MAP2WORLD_GM_CONQUEST_UPDATE; - - // Send to world - message::send(MSG_MAP2WORLD_REGIONAL_EVENT, data, dataLen); + message::send(ipc::RegionalEvent{ + .type = RegionalEventType::Conquest, + .subType = ConquestMessage::M2W_GM_WeeklyUpdate, + // No payload + }); } else if (type == Conquest_Tally_End) { @@ -424,7 +370,8 @@ namespace conquest // Cities do not have owner or influence uint8 influence = 0; uint8 owner = 0; - if (regionId <= REGION_TYPE::TAVNAZIA) { + if (regionId <= REGION_TYPE::TAVNAZIA) + { influence = conquest::GetInfluenceGraphics(PZone->GetRegionID()); owner = conquest::GetRegionOwner(PZone->GetRegionID()); } @@ -467,7 +414,8 @@ namespace conquest // Cities do not have owner or influence uint8 influence = 0; uint8 owner = 0; - if (regionId <= REGION_TYPE::TAVNAZIA) { + if (regionId <= REGION_TYPE::TAVNAZIA) + { influence = conquest::GetInfluenceGraphics(PZone->GetRegionID()); owner = conquest::GetRegionOwner(PZone->GetRegionID()); } @@ -513,7 +461,8 @@ namespace conquest // Cities do not have owner or influence uint8 influence = 0; uint8 owner = 0; - if (regionId <= REGION_TYPE::TAVNAZIA) { + if (regionId <= REGION_TYPE::TAVNAZIA) + { influence = conquest::GetInfluenceGraphics(PZone->GetRegionID()); owner = conquest::GetRegionOwner(PZone->GetRegionID()); } diff --git a/src/map/conquest_system.h b/src/map/conquest_system.h index 0392672d79c..c2f9e763747 100644 --- a/src/map/conquest_system.h +++ b/src/map/conquest_system.h @@ -22,6 +22,7 @@ #pragma once #include "common/cbasetypes.h" +#include "common/regional_event.h" #include "conquest_data.h" #include "zone.h" @@ -30,7 +31,7 @@ enum ConquestUpdate : uint8 { Conquest_Tally_Start = 0, Conquest_Tally_End = 1, - Conquest_Update = 2 + Conquest_Update = 2, }; class CCharEntity; @@ -44,7 +45,7 @@ namespace conquest { std::shared_ptr GetConquestData(); // Cached data with influences / region controls - void HandleZMQMessage(uint8* data); + void HandleZMQMessage(uint8 subType, const std::span data); void UpdateConquestGM(ConquestUpdate type); // Update conquest system by GM (modify in the DB and use @updateconquest) void GainInfluencePoints(CCharEntity* PChar, uint32 points); // Gain influence for player's nation (+1) diff --git a/src/map/entities/charentity.cpp b/src/map/entities/charentity.cpp index 018bc813acb..2ae6480f9da 100644 --- a/src/map/entities/charentity.cpp +++ b/src/map/entities/charentity.cpp @@ -66,6 +66,7 @@ #include "char_recast_container.h" #include "charentity.h" #include "conquest_system.h" +#include "ipc_client.h" #include "item_container.h" #include "items/item_furnishing.h" #include "items/item_usable.h" @@ -73,7 +74,6 @@ #include "job_points.h" #include "latent_effect_container.h" #include "linkshell.h" -#include "message.h" #include "mob_modifier.h" #include "mobskill.h" #include "modifier.h" @@ -333,17 +333,17 @@ CCharEntity::~CCharEntity() if (PParty && loc.destination != 0 && m_moghouseID == 0) { - uint8 data[4]{}; - if (PParty->m_PAlliance) { - ref(data, 0) = PParty->m_PAlliance->m_AllianceID; - message::send(MSG_ALLIANCE_RELOAD, data, sizeof(data), nullptr); + message::send(ipc::AllianceReload{ + .allianceId = PParty->m_PAlliance->m_AllianceID, + }); } else { - ref(data, 0) = PParty->GetPartyID(); - message::send(MSG_PT_RELOAD, data, sizeof(data), nullptr); + message::send(ipc::PartyReload{ + .partyId = PParty->GetPartyID(), + }); } } diff --git a/src/map/ipc_client.cpp b/src/map/ipc_client.cpp index f97c1f9eb6a..bbdb8468ea9 100644 --- a/src/map/ipc_client.cpp +++ b/src/map/ipc_client.cpp @@ -2,6 +2,7 @@ =========================================================================== Copyright (c) 2010-2015 Darkstar Dev Teams + Copyright (c) 2025 LandSandBoat Dev Teams This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,11 +20,13 @@ =========================================================================== */ +#include "ipc_client.h" + +#include "common/ipp.h" + #include #include -#include "message.h" - #include "alliance.h" #include "conquest_system.h" #include "linkshell.h" @@ -35,1029 +38,875 @@ #include "lua/luautils.h" +#include "packets/chat_message.h" #include "packets/message_standard.h" #include "packets/message_system.h" #include "packets/party_invite.h" #include "packets/server_ip.h" #include "items/item_linkshell.h" + #include "utils/charutils.h" #include "utils/jailutils.h" #include "utils/serverutils.h" #include "utils/zoneutils.h" -namespace message +namespace { - zmq::context_t zContext; - std::unique_ptr zSocket; - - moodycamel::ConcurrentQueue outgoing_queue; - moodycamel::ConcurrentQueue incoming_queue; - - std::unordered_map replyMap; - - void send_queue() + auto getZMQEndpointString() -> std::string { - TracyZoneScoped; - - chat_message_t msg; - while (outgoing_queue.try_dequeue(msg)) - { - try - { - zSocket->send(msg.type, zmq::send_flags::sndmore); - zSocket->send(msg.data, zmq::send_flags::sndmore); - zSocket->send(msg.packet, zmq::send_flags::none); - } - catch (std::exception& e) - { - ShowError("Message: %s", e.what()); - } - } + return fmt::format("tcp://{}:{}", settings::get("network.ZMQ_IP"), settings::get("network.ZMQ_PORT")); } - void parse(chat_message_t& message) + auto getZMQRoutingId() -> uint64 { - TracyZoneScoped; + // Original IPP/RoutingId logic - auto type = (MSGSERVTYPE)ref((uint8*)message.type.data(), 0); - auto& extra = message.data; - auto& packet = message.packet; - - TracyZoneCString(msgTypeToStr(type)); - - ShowDebug("Message: Received message %s (%d) from message server", - msgTypeToStr(type), static_cast(type)); + uint64 ipp = map_ip.s_addr; + uint64 port = map_port; - switch (type) + // if no ip/port were supplied, set to 1 (0 is not valid for an identity) + if (map_ip.s_addr == 0 && map_port == 0) { - case MSG_LOGIN: + const auto rset = db::preparedStmt("SELECT zoneip, zoneport FROM zone_settings GROUP BY zoneip, zoneport ORDER BY COUNT(*) DESC"); + if (rset && rset->rowsCount() && rset->next()) { - CCharEntity* PChar = zoneutils::GetChar(ref((uint8*)extra.data(), 0)); + const auto zoneip = rset->get("zoneip"); + const auto zoneport = rset->get("zoneport"); - if (!PChar) - { - _sql->Query("DELETE FROM accounts_sessions WHERE charid = %d", ref((uint8*)extra.data(), 0)); - } - else - { - // TODO: disconnect the client, but leave the character in the disconnecting state - // PChar->status = STATUS_SHUTDOWN; - // won't save their position (since this is the wrong thread) but not a huge deal - // PChar->pushPacket(PChar, 1, 0); - } - break; + inet_pton(AF_INET, zoneip.c_str(), &ipp); + port = zoneport; } - case MSG_CHAT_TELL: - { - char characterName[PacketNameLength] = {}; - std::memcpy(&characterName, reinterpret_cast(extra.data()) + 4, PacketNameLength - 1); + } - CCharEntity* PChar = zoneutils::GetCharByName(characterName); - if (PChar && PChar->status != STATUS_TYPE::DISAPPEAR && !jailutils::InPrison(PChar)) - { - std::unique_ptr newPacket = std::make_unique(); - std::memcpy(*newPacket, packet.data(), std::min(packet.size(), PACKET_SIZE)); - auto gm_sent = newPacket->ref(0x05); - if (settings::get("map.BLOCK_TELL_TO_HIDDEN_GM") && PChar->m_isGMHidden && !gm_sent) - { - send(MSG_DIRECT, extra.data(), sizeof(uint32), std::make_unique(PChar, 0, 0, MsgStd::TellNotReceivedOffline)); - } - else if (PChar->isAway() && !gm_sent) - { - send(MSG_DIRECT, extra.data(), sizeof(uint32), std::make_unique(PChar, 0, 0, MsgStd::TellNotReceivedAway)); - } - else - { - PChar->pushPacket(std::move(newPacket)); - } - } - else - { - send(MSG_DIRECT, extra.data(), sizeof(uint32), std::make_unique(PChar, 0, 0, MsgStd::TellNotReceivedOffline)); - } - break; - } - case MSG_CHAT_PARTY: - { - uint32 partyid = ref((uint8*)extra.data(), 0); - uint32 senderid = ref((uint8*)extra.data(), 4); - CParty* PParty = nullptr; + ipp |= (port << 32); - // TODO: when Party/Alliance gets a rewrite, make a zoneutils::ForEachParty or some other accessor to reduce the amount of iterations significantly. - // clang-format off - zoneutils::ForEachZone([partyid, &PParty](CZone* PZone) - { - PZone->ForEachChar([partyid, &PParty](CCharEntity* PChar) - { - if (PChar->PParty && PChar->PParty->GetPartyID() == partyid) - { - PParty = PChar->PParty; - return; - } - }); + return ipp; + } +} // namespace - if (PParty) - { - return; - } - }); - // clang-format on +// TODO: Don't do this +std::unique_ptr ipcClient_; - if (PParty) - { - auto newPacket = std::make_unique(); - std::memcpy(*newPacket, packet.data(), std::min(packet.size(), PACKET_SIZE)); - PParty->PushPacket(senderid, 0, newPacket); - } - break; - } - case MSG_CHAT_ALLIANCE: - { - uint32 allianceid = ref((uint8*)extra.data(), 0); - uint32 senderid = ref((uint8*)extra.data(), 4); - CAlliance* PAlliance = nullptr; +void message::init() +{ + ipcClient_ = std::make_unique(); +} - // TODO: when Party/Alliance gets a rewrite, make a zoneutils::ForEachParty or some other accessor to reduce the amount of iterations significantly. - // clang-format off - zoneutils::ForEachZone([allianceid, &PAlliance](CZone* PZone) - { - PZone->ForEachChar([allianceid, &PAlliance](CCharEntity* PChar) - { - if (PChar->PParty && PChar->PParty && PChar->PParty->m_PAlliance && PChar->PParty->m_PAlliance->m_AllianceID == allianceid) - { - PAlliance = PChar->PParty->m_PAlliance; - return; - } - }); +void message::handle_incoming() +{ + ipcClient_->handleIncomingMessages(); +} - if (PAlliance) - { - return; - } - }); - // clang-format on +IPCClient::IPCClient() +: zmqDealerWrapper_(getZMQEndpointString(), getZMQRoutingId()) +{ + TracyZoneScoped; +} - if (PAlliance) - { - for (auto& currentParty : PAlliance->partyList) - { - auto newPacket = std::make_unique(); - std::memcpy(*newPacket, packet.data(), std::min(packet.size(), PACKET_SIZE)); - currentParty->PushPacket(senderid, 0, newPacket); - } - } - break; - } - case MSG_CHAT_LINKSHELL: - { - uint32 linkshellID = ref((uint8*)extra.data(), 0); - CLinkshell* PLinkshell = linkshell::GetLinkshell(linkshellID); - if (PLinkshell) - { - auto newPacket = std::make_unique(); - std::memcpy(*newPacket, packet.data(), std::min(packet.size(), PACKET_SIZE)); - PLinkshell->PushPacket(ref((uint8*)extra.data(), 4), newPacket); - } - break; - } - case MSG_CHAT_UNITY: - { - uint32 leader = ref((uint8*)extra.data(), 0); - CUnityChat* PUnityChat = unitychat::GetUnityChat(leader); - if (PUnityChat) - { - auto newPacket = std::make_unique(); - std::memcpy(*newPacket, packet.data(), std::min(packet.size(), PACKET_SIZE)); - PUnityChat->PushPacket(ref((uint8*)extra.data(), 4), newPacket); - } - break; - } - case MSG_CHAT_YELL: - { - // clang-format off - zoneutils::ForEachZone([&packet, &extra](CZone* PZone) - { - if (PZone->CanUseMisc(MISC_YELL)) - { - PZone->ForEachChar([&packet, &extra](CCharEntity* PChar) - { - // don't push to sender - if (PChar->id != ref((uint8*)extra.data(), 0)) - { - auto newPacket = std::make_unique(); - std::memcpy(*newPacket, packet.data(), std::min(packet.size(), PACKET_SIZE)); - PChar->pushPacket(std::move(newPacket)); - } - }); - } - }); - // clang-format on - break; - } - case MSG_CHAT_SERVMES: - { - // clang-format off - zoneutils::ForEachZone([&packet](CZone* PZone) - { - PZone->ForEachChar([&packet](CCharEntity* PChar) - { - auto newPacket = std::make_unique(); - std::memcpy(*newPacket, packet.data(), std::min(packet.size(), PACKET_SIZE)); - PChar->pushPacket(std::move(newPacket)); - }); - }); - // clang-format on - break; - } - case MSG_PT_INVITE: - { - uint32 id = ref((uint8*)extra.data(), 0); - // uint16 targid = ref((uint8*)extra.data(), 4); - uint8 inviteType = ref((uint8*)packet.data(), 0x0B); - CCharEntity* PInvitee = zoneutils::GetChar(id); +void IPCClient::handleIncomingMessages() +{ + TracyZoneScoped; - if (PInvitee) - { - // make sure invitee isn't dead or in jail, they aren't a party member and don't already have an invite pending, and your party is not full - if (PInvitee->isDead() || jailutils::InPrison(PInvitee) || PInvitee->InvitePending.id != 0 || - (PInvitee->PParty && inviteType == INVITE_PARTY) || - (inviteType == INVITE_ALLIANCE && (!PInvitee->PParty || PInvitee->PParty->GetLeader() != PInvitee || (PInvitee->PParty && PInvitee->PParty->m_PAlliance)))) - { - ref((uint8*)extra.data(), 0) = ref((uint8*)extra.data(), 6); - send(MSG_DIRECT, extra.data(), sizeof(uint32), std::make_unique(PInvitee, 0, 0, MsgStd::CannotInvite)); - return; - } - // check /blockaid - if (PInvitee->getBlockingAid()) - { - ref((uint8*)extra.data(), 0) = ref((uint8*)extra.data(), 6); - // Target is blocking assistance - send(MSG_DIRECT, extra.data(), sizeof(uint32), std::make_unique(0, 0, MsgStd::TargetIsCurrentlyBlocking)); - // Interaction was blocked - PInvitee->pushPacket(0, 0, MsgStd::BlockedByBlockaid); - // You cannot invite that person at this time. - send(MSG_DIRECT, extra.data(), sizeof(uint32), std::make_unique(PInvitee, 0, 0, MsgStd::CannotInvite)); - break; - } - if (PInvitee->StatusEffectContainer->HasStatusEffect(EFFECT_LEVEL_SYNC)) - { - ref((uint8*)extra.data(), 0) = ref((uint8*)extra.data(), 6); - send(MSG_DIRECT, extra.data(), sizeof(uint32), std::make_unique(PInvitee, 0, 0, MsgStd::CannotInviteLevelSync)); - return; - } + // TODO: Can we stop more messages appearing on the queue while we're processing? + zmq::message_t out; + while (zmqDealerWrapper_.incomingQueue_.try_dequeue(out)) + { + const auto firstByte = out.data()[0]; + const auto msgType = ipc::toString(static_cast(firstByte)); - PInvitee->InvitePending.id = ref((uint8*)extra.data(), 6); - PInvitee->InvitePending.targid = ref((uint8*)extra.data(), 10); + // TODO: Make an IPP for the world server, so we can use it here + ShowDebugFmt("Incoming {} message", msgType); - auto newPacket = std::make_unique(); - std::memcpy(*newPacket, packet.data(), std::min(packet.size(), PACKET_SIZE)); - PInvitee->pushPacket(std::move(newPacket)); - } - break; - } - case MSG_PT_INV_RES: - { - uint32 inviterId = ref((uint8*)extra.data(), 0); - // uint16 inviterTargid = ref((uint8*)extra.data(), 4); - uint32 inviteeId = ref((uint8*)extra.data(), 6); - // uint16 inviteeTargid = ref((uint8*)extra.data(), 10); - uint8 inviteAnswer = ref((uint8*)extra.data(), 12); - CCharEntity* PInviter = zoneutils::GetChar(inviterId); - - if (PInviter) - { - if (inviteAnswer == 0) - { - PInviter->pushPacket(PInviter, 0, 0, MsgStd::InvitationDeclined); - } - else - { - // both party leaders? - int ret = _sql->Query("SELECT * FROM accounts_parties WHERE partyid <> 0 AND \ - ((charid = %u OR charid = %u) AND partyflag & %u)", - inviterId, inviteeId, PARTY_LEADER); - if (ret != SQL_ERROR && _sql->NumRows() == 2) - { - if (PInviter->PParty) - { - if (PInviter->PParty->m_PAlliance) - { - ret = _sql->Query("SELECT * FROM accounts_parties WHERE allianceid <> 0 AND \ - allianceid = (SELECT allianceid FROM accounts_parties where \ - charid = %u) GROUP BY partyid", - inviterId); - if (ret != SQL_ERROR && _sql->NumRows() > 0 && _sql->NumRows() < 3) - { - PInviter->PParty->m_PAlliance->addParty(inviteeId); - } - else - { - send(MSG_DIRECT, (uint8*)extra.data() + 6, sizeof(uint32), - std::make_unique(PInviter, 0, 0, MsgStd::CannotBeProcessed)); - } - } - else if (PInviter->PParty) - { - // make new alliance - CAlliance* PAlliance = new CAlliance(PInviter); - PAlliance->addParty(inviteeId); - } - } - else // Somehow, the inviter didn't have a party despite the database thinking they did. - { - send(MSG_DIRECT, (uint8*)extra.data() + 6, sizeof(uint32), - std::make_unique(PInviter, 0, 0, MsgStd::CannotBeProcessed)); - } - } - else - { - if (PInviter->PParty == nullptr) - { - PInviter->PParty = new CParty(PInviter); - } - if (PInviter->PParty && PInviter->PParty->GetLeader() == PInviter) - { - ret = _sql->Query("SELECT * FROM accounts_parties WHERE partyid <> 0 AND charid = %u", inviteeId); - if (ret != SQL_ERROR && _sql->NumRows() == 0) - { - PInviter->PParty->AddMember(inviteeId); - } - } - } - } - } - break; - } - case MSG_PT_RELOAD: - { - uint32 partyid = ref((uint8*)extra.data(), 0); + ipc::IIPCMessageHandler::handleMessage(IPP(), { static_cast(out.data()), out.size() }); + } +} - int ret = _sql->Query("SELECT charid FROM accounts_parties WHERE partyid = %u", partyid); - if (ret != SQL_ERROR && _sql->NumRows() != 0) - { - while (_sql->NextRow() == SQL_SUCCESS) - { - CCharEntity* PChar = zoneutils::GetChar(_sql->GetUIntData(0)); - if (PChar) - { - PChar->ReloadPartyInc(); - } - } - } +void IPCClient::handleMessage_EmptyStruct(const IPP& ipp, const ipc::EmptyStruct& message) +{ + TracyZoneScoped; - break; - } - case MSG_PT_DISBAND: - { - uint32 partyid = ref((uint8*)extra.data(), 0); - CParty* PParty = nullptr; + ShowDebugFmt("Received EmptyStruct message from {}", ipp.toString()); +} - // TODO: when Party/Alliance gets a rewrite, make a zoneutils::ForEachParty or some other accessor to reduce the amount of iterations significantly. - // clang-format off - zoneutils::ForEachZone([partyid, &PParty](CZone* PZone) - { - PZone->ForEachChar([partyid, &PParty](CCharEntity* PChar) - { - if (PChar->PParty && PChar->PParty->GetPartyID() == partyid) - { - PParty = PChar->PParty; - return; - } - }); +void IPCClient::handleMessage_CharLogin(const IPP& ipp, const ipc::CharLogin& message) +{ + TracyZoneScoped; - if (PParty) - { - return; - } - }); - // clang-format on + CCharEntity* PChar = zoneutils::GetChar(message.charId); + if (!PChar) + { + db::preparedStmt("DELETE FROM accounts_sessions WHERE charid = ?", message.charId); + } + else + { + // TODO: disconnect the client, but leave the character in the disconnecting state + // PChar->status = STATUS_SHUTDOWN; + // won't save their position but not a huge deal + // PChar->pushPacket(PChar, 1, 0); + } +} - if (PParty) - { - PParty->DisbandParty(false); - } +void IPCClient::handleMessage_CharVarUpdate(const IPP& ipp, const ipc::CharVarUpdate& message) +{ + TracyZoneScoped; - break; - } - case MSG_ALLIANCE_RELOAD: - { - uint32 allianceid = ref((uint8*)extra.data(), 0); + if (auto* PChar = zoneutils::GetChar(message.charId)) + { + ShowDebugFmt("Received CharVarUpdate message from {}, char {}: {} = {} (expiry {})", ipp.toString(), message.charId, message.varName, message.value, message.expiry); + PChar->updateCharVarCache(message.varName, message.value, message.expiry); + } +} - int ret = _sql->Query("SELECT charid FROM accounts_parties WHERE allianceid = %u", allianceid); - if (ret != SQL_ERROR && _sql->NumRows() != 0) - { - while (_sql->NextRow() == SQL_SUCCESS) - { - CCharEntity* PChar = zoneutils::GetChar(_sql->GetUIntData(0)); - if (PChar) - { - PChar->ReloadPartyInc(); - } - } - } +void IPCClient::handleMessage_ChatMessageTell(const IPP& ipp, const ipc::ChatMessageTell& message) +{ + TracyZoneScoped; - break; - } - case MSG_ALLIANCE_DISSOLVE: - { - uint32 allianceid = ref((uint8*)extra.data(), 0); - CAlliance* PAlliance = nullptr; + ShowDebugFmt("Received MessageTell message from {}, char {} to char {}: {}", ipp.toString(), message.senderName, message.recipientName, message.message); - // TODO: when Party/Alliance gets a rewrite, make a zoneutils::ForEachAlliance or some other accessor to reduce the amount of iterations significantly. - // clang-format off - zoneutils::ForEachZone([allianceid, &PAlliance](CZone* PZone) - { - PZone->ForEachChar([allianceid, &PAlliance](CCharEntity* PChar) - { - if (PChar->PParty && PChar->PParty->m_PAlliance && PChar->PParty->m_PAlliance->m_AllianceID == allianceid) - { - PAlliance = PChar->PParty->m_PAlliance; - return; - } - }); + CCharEntity* PChar = zoneutils::GetCharByName(message.recipientName); + if (PChar && PChar->status != STATUS_TYPE::DISAPPEAR && !jailutils::InPrison(PChar)) + { + const auto gmSent = message.gmLevel > 0; - if (PAlliance) - { - return; - } - }); - // clang-format on + if (settings::get("map.BLOCK_TELL_TO_HIDDEN_GM") && PChar->m_isGMHidden && !gmSent) + { + message::send(ipc::MessageStandard{ + .charId = PChar->id, + .message = MsgStd::TellNotReceivedOffline, + }); + } + else if (PChar->isAway() && !gmSent) + { + message::send(ipc::MessageStandard{ + .charId = PChar->id, + .message = MsgStd::TellNotReceivedAway, + }); + } + else + { + PChar->pushPacket(std::make_unique(PChar, MESSAGE_TELL, message.message, message.recipientName)); + } + } + else + { + message::send(ipc::MessageStandard{ + .charId = PChar->id, + .message = MsgStd::TellNotReceivedOffline, + }); + } +} - if (PAlliance) - { - PAlliance->dissolveAlliance(false); - } +void IPCClient::handleMessage_ChatMessageParty(const IPP& ipp, const ipc::ChatMessageParty& message) +{ + TracyZoneScoped; - break; - } - case MSG_PLAYER_KICK: - { - uint32 charid = ref((uint8*)extra.data(), 0); + CParty* PParty = nullptr; - // player was kicked and is no longer in alliance/party db -- they need a direct update. - CCharEntity* PChar = zoneutils::GetChar(charid); - if (PChar) - { - PChar->ReloadPartyInc(); - } - break; - } - case MSG_DIRECT: - { - CCharEntity* PChar = zoneutils::GetChar(ref((uint8*)extra.data(), 0)); - if (PChar) - { - auto newPacket = std::make_unique(); - std::memcpy(*newPacket, packet.data(), std::min(packet.size(), PACKET_SIZE)); - PChar->pushPacket(std::move(newPacket)); - } - break; - } - case MSG_LINKSHELL_RANK_CHANGE: - { - CLinkshell* PLinkshell = linkshell::GetLinkshell(ref((uint8*)extra.data(), 24)); + const auto partyid = message.partyId; - if (PLinkshell) - { - char memberName[PacketNameLength] = {}; - std::memcpy(&memberName, reinterpret_cast(extra.data()) + 4, PacketNameLength - 1); - PLinkshell->ChangeMemberRank(memberName, ref((uint8*)extra.data(), 28)); - } - break; - } - case MSG_LINKSHELL_REMOVE: - { - char memberName[PacketNameLength] = {}; - std::memcpy(&memberName, reinterpret_cast(extra.data()) + 4, PacketNameLength - 1); - CCharEntity* PChar = zoneutils::GetCharByName(memberName); + // TODO: When Party/Alliance gets a rewrite, make a zoneutils::ForEachParty or some other accessor to reduce the amount of iterations significantly. - if (PChar && PChar->PLinkshell1 && PChar->PLinkshell1->getID() == ref((uint8*)extra.data(), 24)) - { - uint8 kickerRank = ref((uint8*)extra.data(), 28); - CItemLinkshell* targetLS = (CItemLinkshell*)PChar->getEquip(SLOT_LINK1); - if (targetLS && (kickerRank == LSTYPE_LINKSHELL || (kickerRank == LSTYPE_PEARLSACK && targetLS->GetLSType() == LSTYPE_LINKPEARL))) - { - PChar->PLinkshell1->RemoveMemberByName(memberName, - (targetLS->GetLSType() == (uint8)LSTYPE_LINKSHELL ? (uint8)LSTYPE_PEARLSACK : kickerRank)); - } - } - else if (PChar && PChar->PLinkshell2 && PChar->PLinkshell2->getID() == ref((uint8*)extra.data(), 24)) - { - uint8 kickerRank = ref((uint8*)extra.data(), 28); - CItemLinkshell* targetLS = (CItemLinkshell*)PChar->getEquip(SLOT_LINK2); - if (targetLS && (kickerRank == LSTYPE_LINKSHELL || (kickerRank == LSTYPE_PEARLSACK && targetLS->GetLSType() == LSTYPE_LINKPEARL))) - { - PChar->PLinkshell2->RemoveMemberByName(memberName, kickerRank); - } - } - break; - } - case MSG_SEND_TO_ZONE: + // clang-format off + zoneutils::ForEachZone([partyid, &PParty](CZone* PZone) + { + PZone->ForEachChar([partyid, &PParty](CCharEntity* PChar) + { + if (PChar->PParty && PChar->PParty->GetPartyID() == partyid) { - CCharEntity* PChar = zoneutils::GetChar(ref((uint8*)extra.data(), 0)); - - if (PChar && PChar->loc.zone) - { - uint32 requester = ref((uint8*)extra.data(), 4); - - if (requester != 0) - { - char buf[30]; - std::memset(&buf[0], 0, sizeof(buf)); - - ref(&buf, 0) = requester; - ref(&buf, 8) = PChar->getZone(); - ref(&buf, 10) = PChar->loc.p.x; - ref(&buf, 14) = PChar->loc.p.y; - ref(&buf, 18) = PChar->loc.p.z; - ref(&buf, 22) = PChar->loc.p.rotation; - ref(&buf, 23) = PChar->m_moghouseID; - - message::send(MSG_SEND_TO_ZONE, &buf, sizeof(buf), nullptr); - break; - } - - uint16 zoneId = ref((uint8*)extra.data(), 8); - float x = ref((uint8*)extra.data(), 10); - float y = ref((uint8*)extra.data(), 14); - float z = ref((uint8*)extra.data(), 18); - uint8 rot = ref((uint8*)extra.data(), 22); - uint32 moghouseID = ref((uint8*)extra.data(), 23); - - PChar->updatemask = 0; - - PChar->m_moghouseID = 0; - - PChar->loc.p.x = x; - PChar->loc.p.y = y; - PChar->loc.p.z = z; - PChar->loc.p.rotation = rot; - PChar->loc.destination = zoneId; - PChar->m_moghouseID = moghouseID; - PChar->loc.boundary = 0; - PChar->status = STATUS_TYPE::DISAPPEAR; - PChar->animation = ANIMATION_NONE; - PChar->clearPacketList(); - - charutils::SendToZone(PChar, 2, zoneutils::GetZoneIPP(zoneId)); - } - break; + PParty = PChar->PParty; + return; } - case MSG_SEND_TO_ENTITY: - { - // Need to check which server we're on so we don't get null pointers - bool toTargetServer = ref((uint8*)extra.data(), 0); - bool spawnedOnly = ref((uint8*)extra.data(), 1); - - if (toTargetServer) // This is going to the target's game server - { - CBaseEntity* Entity = zoneutils::GetEntity(ref((uint8*)extra.data(), 6)); + }); + if (PParty) + { + return; + } + }); + if (PParty) + { + PParty->PushPacket(message.senderId, 0, std::make_unique(message.senderName, message.zoneId, MESSAGE_PARTY, message.message, message.gmLevel)); + } + // clang-format on +} - if (Entity && Entity->loc.zone) - { - char buf[22]; - std::memset(&buf[0], 0, sizeof(buf)); +void IPCClient::handleMessage_ChatMessageAlliance(const IPP& ipp, const ipc::ChatMessageAlliance& message) +{ + TracyZoneScoped; - uint16 targetZone = ref((uint8*)extra.data(), 2); - uint16 playerZone = ref((uint8*)extra.data(), 4); - uint16 playerID = ref((uint8*)extra.data(), 10); + CAlliance* PAlliance = nullptr; - float X = Entity->GetXPos(); - float Y = Entity->GetYPos(); - float Z = Entity->GetZPos(); - uint8 R = Entity->GetRotPos(); + const auto allianceid = message.allianceId; - ref(&buf, 1) = true; // Found, so initiate warp back on the requesting server + // TODO: When Party/Alliance gets a rewrite, make a zoneutils::ForEachParty or some other accessor to reduce the amount of iterations significantly. - if (Entity->status == STATUS_TYPE::DISAPPEAR) - { - if (spawnedOnly) - { - ref(&buf, 1) = false; // Spawned only, so do not initiate warp - } - else - { - // If entity not spawned, go to default location as listed in database - const char* query = "SELECT pos_x, pos_y, pos_z FROM mob_spawn_points WHERE mobid = %u"; - auto fetch = _sql->Query(query, Entity->id); - - if (fetch != SQL_ERROR && _sql->NumRows() != 0) - { - while (_sql->NextRow() == SQL_SUCCESS) - { - X = _sql->GetFloatData(0); - Y = _sql->GetFloatData(1); - Z = _sql->GetFloatData(2); - } - } - } - } + // clang-format off + zoneutils::ForEachZone([allianceid, &PAlliance](CZone* PZone) + { + PZone->ForEachChar([allianceid, &PAlliance](CCharEntity* PChar) + { + if (PChar->PParty && PChar->PParty && PChar->PParty->m_PAlliance && PChar->PParty->m_PAlliance->m_AllianceID == allianceid) + { + PAlliance = PChar->PParty->m_PAlliance; + return; + } + }); + if (PAlliance) + { + return; + } + }); + if (PAlliance) + { + for (const auto& currentParty : PAlliance->partyList) + { + currentParty->PushPacket(message.senderId, 0, std::make_unique(message.senderName, message.zoneId, MESSAGE_PARTY, message.message, message.gmLevel)); + } + } + // clang-format on +} - ref(&buf, 0) = false; - ref(&buf, 2) = playerZone; - ref(&buf, 4) = playerID; - ref(&buf, 6) = X; - ref(&buf, 10) = Y; - ref(&buf, 14) = Z; - ref(&buf, 18) = R; - ref(&buf, 20) = targetZone; - - message::send(MSG_SEND_TO_ENTITY, &buf, sizeof(buf), nullptr); - break; - } - } - else // This is going to the player's game server - { - CCharEntity* PChar = zoneutils::GetChar(ref((uint8*)extra.data(), 4)); +void IPCClient::handleMessage_ChatMessageLinkshell(const IPP& ipp, const ipc::ChatMessageLinkshell& message) +{ + TracyZoneScoped; - if (PChar && PChar->loc.zone) - { - if (ref((uint8*)extra.data(), 1)) - { - PChar->loc.p.x = ref((uint8*)extra.data(), 6); - PChar->loc.p.y = ref((uint8*)extra.data(), 10); - PChar->loc.p.z = ref((uint8*)extra.data(), 14); - PChar->loc.p.rotation = ref((uint8*)extra.data(), 18); - PChar->loc.destination = ref((uint8*)extra.data(), 20); + if (CLinkshell* PLinkshell = linkshell::GetLinkshell(message.linkshellId)) + { + // TODO: Linkshell 1 vs 2? + PLinkshell->PushPacket(message.senderId, std::make_unique(message.senderName, message.zoneId, MESSAGE_LINKSHELL, message.message, message.gmLevel)); + } +} - PChar->m_moghouseID = 0; - PChar->loc.boundary = 0; - PChar->updatemask = 0; +void IPCClient::handleMessage_ChatMessageUnity(const IPP& ipp, const ipc::ChatMessageUnity& message) +{ + TracyZoneScoped; - PChar->status = STATUS_TYPE::DISAPPEAR; - PChar->animation = ANIMATION_NONE; + if (CUnityChat* PUnityChat = unitychat::GetUnityChat(message.unityLeaderId)) + { + PUnityChat->PushPacket(message.senderId, std::make_unique(message.senderName, message.zoneId, MESSAGE_UNITY, message.message, message.gmLevel)); + } +} - PChar->clearPacketList(); +void IPCClient::handleMessage_ChatMessageYell(const IPP& ipp, const ipc::ChatMessageYell& message) +{ + TracyZoneScoped; - charutils::SendToZone(PChar, 2, zoneutils::GetZoneIPP(PChar->loc.destination)); - } - } - } - break; - } - case MSG_LUA_FUNCTION: + // clang-format off + zoneutils::ForEachZone([&](CZone* PZone) + { + if (PZone->CanUseMisc(MISC_YELL)) + { + PZone->ForEachChar([&](CCharEntity* PChar) { - auto str = std::string((const char*)extra.data() + 4); - auto result = lua.safe_script(str); - if (!result.valid()) + // Don't push to sender + if (PChar->id != message.senderId) { - sol::error err = result; - ShowError("MSG_LUA_FUNCTION: error: %s: %s", err.what(), str.c_str()); + PChar->pushPacket(std::make_unique(message.senderName, message.zoneId, MESSAGE_YELL, message.message, message.gmLevel)); } - break; - } - case MSG_CHARVAR_UPDATE: - { - uint8* data = (uint8*)extra.data(); - uint32 charId = ref(data, 0); - int32 value = ref(data, 4); - uint32 expiry = ref(data, 8); + }); + } + }); + // clang-format on +} - ShowDebug(fmt::format("Received message to update var for {}", charId)); +void IPCClient::handleMessage_ChatMessageServerMessage(const IPP& ipp, const ipc::ChatMessageServerMessage& message) +{ + TracyZoneScoped; - uint8 varNameSize = ref(data, 12); - auto varName = std::string(data + 13, data + 13 + varNameSize); + // clang-format off + zoneutils::ForEachZone([&](CZone* PZone) + { + PZone->ForEachChar([&](CCharEntity* PChar) + { + PChar->pushPacket(std::make_unique(message.senderName, message.zoneId, MESSAGE_SYSTEM_1, message.message, message.gmLevel)); + }); + }); + // clang-format on +} - if (auto player = zoneutils::GetChar(charId)) - { - ShowDebug(fmt::format("Updating charvar for {} ({}): {} = {}", player->getName(), charId, varName, value)); - player->updateCharVarCache(varName, value, expiry); - } - break; - } - case MSG_RPC_SEND: - { - // Extract data - uint8* data = (uint8*)extra.data(); - uint16 sendZone = ref(data, 2); // here - uint16 recvZone = ref(data, 4); // origin - uint64_t slotKey = ref(data, 6); - uint16 strSize = ref(data, 14); - auto sendStr = std::string(data + 16, data + 16 + strSize); - - // Execute Lua & collect payload - std::string payload = ""; - auto result = lua.safe_script(sendStr); - if (result.valid() && result.return_count()) - { - payload = result.get(0); - } +void IPCClient::handleMessage_PartyInvite(const IPP& ipp, const ipc::PartyInvite& message) +{ + TracyZoneScoped; - // Reply w/ payload - std::vector packetData(16 + payload.size() + 1); + if (CCharEntity* PInvitee = zoneutils::GetChar(message.inviteeId)) + { + // make sure invitee isn't dead or in jail, they aren't a party member and don't already have an invite pending, and your party is not full + if (PInvitee->isDead() || + jailutils::InPrison(PInvitee) || + PInvitee->InvitePending.id != 0 || + (PInvitee->PParty && message.inviteType == INVITE_PARTY) || + (message.inviteType == INVITE_ALLIANCE && (!PInvitee->PParty || PInvitee->PParty->GetLeader() != PInvitee || (PInvitee->PParty && PInvitee->PParty->m_PAlliance)))) + { + message::send(ipc::MessageStandard{ + .charId = message.inviteeId, + .message = MsgStd::CannotInvite, + }); - ref(packetData.data(), 2) = recvZone; // origin - ref(packetData.data(), 4) = sendZone; // here - ref(packetData.data(), 6) = slotKey; + return; + } - ref(packetData.data(), 14) = (uint16)payload.size(); - std::memcpy(packetData.data() + 16, payload.data(), payload.size()); + if (PInvitee->getBlockingAid()) + { + // Target is blocking assistance + message::send(ipc::MessageSystem{ + .charId = message.inviterId, + .message = MsgStd::TargetIsCurrentlyBlocking, + }); + + // Interaction was blocked + PInvitee->pushPacket(0, 0, MsgStd::BlockedByBlockaid); + + // You cannot invite that person at this time. + message::send(ipc::MessageStandard{ + .charId = message.inviteeId, + .message = MsgStd::CannotInvite, + }); + + return; + } - packetData[packetData.size() - 1] = '\0'; + if (PInvitee->StatusEffectContainer->HasStatusEffect(EFFECT_LEVEL_SYNC)) + { + message::send(ipc::MessageStandard{ + .charId = message.inviteeId, + .message = MsgStd::CannotInviteLevelSync, + }); - message::send(MSG_RPC_RECV, packetData.data(), packetData.size()); + return; + } - break; - } - case MSG_RPC_RECV: - { - uint8* data = (uint8*)extra.data(); - // No need for any of the zone id data now - uint64_t slotKey = ref(data, 6); - uint16 strSize = ref(data, 14); - auto payload = std::string(data + 16, data + 16 + strSize); - - auto maybeEntry = replyMap.find(slotKey); - if (maybeEntry != replyMap.end()) - { - auto& recvFunc = maybeEntry->second; - auto result = recvFunc(payload); - if (!result.valid()) - { - sol::error err = result; - ShowError("message::parse::MSG_RPC_RECV: %s", err.what()); - } - replyMap.erase(slotKey); - } + PInvitee->InvitePending.id = message.inviterId; + PInvitee->InvitePending.targid = message.inviterTargId; - break; - } - case MSG_WORLD2MAP_REGIONAL_EVENT: - { - // Extract data - uint8* data = (uint8*)extra.data(); - uint8 eventType = ref(data, 0); - // uint8 subtype = ref(data, 1); + PInvitee->pushPacket(std::make_unique(message.inviterId, message.inviterTargId, message.inviterName, INVITE_PARTY)); + } +} + +void IPCClient::handleMessage_PartyInviteResponse(const IPP& ipp, const ipc::PartyInviteResponse& message) +{ + TracyZoneScoped; - // Handle each event type and subtype. - switch (eventType) + CCharEntity* PInviter = zoneutils::GetChar(message.inviterId); + if (PInviter) + { + if (message.inviteAnswer == 0) + { + PInviter->pushPacket(PInviter, 0, 0, MsgStd::InvitationDeclined); + } + else + { + // both party leaders? + const auto rset = db::preparedStmt("SELECT * FROM accounts_parties WHERE partyid <> 0 AND " + "((charid = ? OR charid = ?) AND partyflag & ?)", + message.inviterId, message.inviteeId, PARTY_LEADER); + if (rset && rset->rowsCount() == 2) + { + if (PInviter->PParty) { - case REGIONAL_EVT_MSG_CONQUEST: + if (PInviter->PParty->m_PAlliance) { - conquest::HandleZMQMessage(data); - break; - } - /* - case REGIONAL_EVT_MSG_BESIEGED: - { - // TODO: Handle besieged message - break; - } - case REGIONAL_EVT_MSG_CAMPAIGN: - { - // TODO: Handle besieged message - break; - } - case REGIONAL_EVT_MSG_COLONIZATION: - { - // TODO: Handle besieged message - break; + const auto rset2 = db::preparedStmt("SELECT * FROM accounts_parties WHERE allianceid <> 0 AND " + "allianceid = (SELECT allianceid FROM accounts_parties where " + "charid = ?) GROUP BY partyid", + message.inviterId); + if (rset2 && rset2->rowsCount() > 0 && rset2->rowsCount() < 3) + { + PInviter->PParty->m_PAlliance->addParty(message.inviteeId); + } + else + { + message::send(ipc::MessageStandard{ + .charId = message.inviterId, + .message = MsgStd::CannotBeProcessed, + }); + } } - */ - default: + else if (PInviter->PParty) { - ShowWarning("Message: unhandled regional event type %d", eventType); + // make new alliance + CAlliance* PAlliance = new CAlliance(PInviter); + PAlliance->addParty(message.inviteeId); } } - - break; + else // Somehow, the inviter didn't have a party despite the database thinking they did. + { + message::send(ipc::MessageStandard{ + .charId = message.inviterId, + .message = MsgStd::CannotBeProcessed, + }); + } } - case MSG_KILL_SESSION: + else { - uint32 charID = ref((uint8*)extra.data(), 0); - - map_session_data_t* sessionToDelete = nullptr; - for (auto mapSession : map_session_list) + if (PInviter->PParty == nullptr) { - auto session = mapSession.second; - if (session->charID == charID) - { - sessionToDelete = session; - break; - } + PInviter->PParty = new CParty(PInviter); } - if (sessionToDelete && sessionToDelete->blowfish.status == BLOWFISH_PENDING_ZONE) + if (PInviter->PParty && PInviter->PParty->GetLeader() == PInviter) { - DebugSockets(fmt::format("Closing session of charid {} on request of other process", charID)); - map_close_session(server_clock::now(), sessionToDelete); + const auto rset2 = db::preparedStmt("SELECT * FROM accounts_parties WHERE partyid <> 0 AND charid = ?", message.inviteeId); + if (rset2 && rset2->rowsCount() == 0) + { + PInviter->PParty->AddMember(message.inviteeId); + } } - break; } - default: + } + } +} + +void IPCClient::handleMessage_PartyReload(const IPP& ipp, const ipc::PartyReload& message) +{ + TracyZoneScoped; + + const auto rset = db::preparedStmt("SELECT charid FROM accounts_parties WHERE partyid = ?", message.partyId); + if (rset && rset->rowsCount()) + { + while (rset->next()) + { + const auto charid = rset->get("charid"); + if (CCharEntity* PChar = zoneutils::GetChar(charid)) { - ShowWarning("Message: unhandled message type %d", type); + PChar->ReloadPartyInc(); } } } +} - void send_charvar_update(uint32 charId, std::string const& varName, uint32 value, uint32 expiry) - { - const uint32 size = sizeof(uint32) + sizeof(uint32) + sizeof(uint32) + sizeof(uint8) + 256; - char buf[size] = {}; - std::memset(&buf[0], 0, size); +void IPCClient::handleMessage_PartyDisband(const IPP& ipp, const ipc::PartyDisband& message) +{ + TracyZoneScoped; - ref(buf, 0) = charId; - ref(buf, 4) = value; - ref(buf, 8) = expiry; - ref(buf, 12) = static_cast(std::min(varName.size(), 255)); - std::memcpy(buf + 13, varName.c_str(), varName.size()); + CParty* PParty = nullptr; - message::send(MSG_CHARVAR_UPDATE, buf, size, nullptr); - } + const auto partyid = message.partyId; - void handle_incoming() - { - TracyZoneScoped; + // TODO: When Party/Alliance gets a rewrite, make a zoneutils::ForEachParty or some other accessor to reduce the amount of iterations significantly. - chat_message_t message; - while (incoming_queue.try_dequeue(message)) + // clang-format off + zoneutils::ForEachZone([partyid, &PParty](CZone* PZone) + { + PZone->ForEachChar([partyid, &PParty](CCharEntity* PChar) { - parse(message); + if (PChar->PParty && PChar->PParty->GetPartyID() == partyid) + { + PParty = PChar->PParty; + return; + } + }); + if (PParty) + { + return; } + }); + if (PParty) + { + PParty->DisbandParty(false); } + // clang-format on +} + +void IPCClient::handleMessage_AllianceReload(const IPP& ipp, const ipc::AllianceReload& message) +{ + TracyZoneScoped; - void listen() + const auto rset = db::preparedStmt("SELECT charid FROM accounts_parties WHERE allianceid = ?", message.allianceId); + if (rset && rset->rowsCount()) { - TracySetThreadName("ZMQ Thread"); - while (gRunFlag) + while (rset->next()) { - if (!zSocket) + const auto charid = rset->get("charid"); + if (CCharEntity* PChar = zoneutils::GetChar(charid)) { - return; + PChar->ReloadPartyInc(); } + } + } +} - try - { - chat_message_t message; - if (!zSocket->recv(message.type, zmq::recv_flags::none)) - { - TracyZoneScoped; - send_queue(); - continue; - } +void IPCClient::handleMessage_AllianceDissolve(const IPP& ipp, const ipc::AllianceDissolve& message) +{ + TracyZoneScoped; - TracyZoneScoped; - int more = zSocket->get(zmq::sockopt::rcvmore); - if (more) - { - std::ignore = zSocket->recv(message.data); - more = zSocket->get(zmq::sockopt::rcvmore); - if (more) - { - std::ignore = zSocket->recv(message.packet); - } - } + CAlliance* PAlliance = nullptr; - incoming_queue.enqueue(std::move(message)); - } - catch (zmq::error_t& e) + const auto allianceid = message.allianceId; + + // TODO: When Party/Alliance gets a rewrite, make a zoneutils::ForEachAlliance or some other accessor to reduce the amount of iterations significantly. + + // clang-format off + zoneutils::ForEachZone([allianceid, &PAlliance](CZone* PZone) + { + PZone->ForEachChar([allianceid, &PAlliance](CCharEntity* PChar) + { + if (PChar->PParty && PChar->PParty->m_PAlliance && PChar->PParty->m_PAlliance->m_AllianceID == allianceid) { - // Context was terminated (ETERM = 156384765) - // Exit loop - if (!zSocket || e.num() == 156384765) - { - return; - } - ShowError("Message: %s\n", e.what()); - continue; + PAlliance = PChar->PParty->m_PAlliance; + return; } + }); + if (PAlliance) + { + return; } + }); + if (PAlliance) + { + PAlliance->dissolveAlliance(false); } + // clang-format on +} + +void IPCClient::handleMessage_PlayerKick(const IPP& ipp, const ipc::PlayerKick& message) +{ + TracyZoneScoped; - void init() + // player was kicked and is no longer in alliance/party db -- they need a direct update. + if (CCharEntity* PChar = zoneutils::GetChar(message.charId)) { - init(settings::get("network.ZMQ_IP").c_str(), settings::get("network.ZMQ_PORT")); + PChar->ReloadPartyInc(); } +} - void init(const char* chatIp, uint16 chatPort) +void IPCClient::handleMessage_MessageStandard(const IPP& ipp, const ipc::MessageStandard& message) +{ + TracyZoneScoped; + + if (CCharEntity* PChar = zoneutils::GetChar(message.charId)) { - TracyZoneScoped; + PChar->pushPacket(std::make_unique(PChar, 0, 0, message.message)); + } +} - zContext = zmq::context_t(1); - zSocket = std::make_unique(zContext, zmq::socket_type::dealer); +void IPCClient::handleMessage_MessageSystem(const IPP& ipp, const ipc::MessageSystem& message) +{ + TracyZoneScoped; - uint64 ipp = map_ip.s_addr; - uint64 port = map_port; + if (CCharEntity* PChar = zoneutils::GetChar(message.charId)) + { + PChar->pushPacket(std::make_unique(PChar, 0, 0, message.message)); + } +} - // if no ip/port were supplied, set to 1 (0 is not valid for an identity) - if (map_ip.s_addr == 0 && map_port == 0) - { - int ret = _sql->Query("SELECT zoneip, zoneport FROM zone_settings GROUP BY zoneip, zoneport ORDER BY COUNT(*) DESC"); - if (ret != SQL_ERROR && _sql->NumRows() > 0 && _sql->NextRow() == SQL_SUCCESS) - { - inet_pton(AF_INET, (const char*)_sql->GetData(0), &ipp); - port = _sql->GetUIntData(1); - } - } +void IPCClient::handleMessage_LinkshellRankChange(const IPP& ipp, const ipc::LinkshellRankChange& message) +{ + TracyZoneScoped; - ipp |= (port << 32); + if (CLinkshell* PLinkshell = linkshell::GetLinkshell(message.linkshellId)) + { + PLinkshell->ChangeMemberRank(message.linkshellName, message.permission); + } +} - zSocket->set(zmq::sockopt::routing_id, zmq::const_buffer(&ipp, sizeof(ipp))); - zSocket->set(zmq::sockopt::rcvtimeo, 500); +void IPCClient::handleMessage_LinkshellRemove(const IPP& ipp, const ipc::LinkshellRemove& message) +{ + TracyZoneScoped; - std::string server = "tcp://"; - server.append(chatIp); - server.append(":"); - server.append(std::to_string(chatPort)); + /* + CCharEntity* PChar = zoneutils::GetCharByName(message.linkshellName); // TODO: Should this be memberName? - try + if (PChar && PChar->PLinkshell1 && PChar->PLinkshell1->getID() == message.linkshellId) + { + uint8 kickerRank = ref((uint8*)extra.data(), 28); + CItemLinkshell* targetLS = (CItemLinkshell*)PChar->getEquip(SLOT_LINK1); + if (targetLS && (kickerRank == LSTYPE_LINKSHELL || (kickerRank == LSTYPE_PEARLSACK && targetLS->GetLSType() == LSTYPE_LINKPEARL))) { - zSocket->connect(server.c_str()); + PChar->PLinkshell1->RemoveMemberByName(memberName, + (targetLS->GetLSType() == (uint8)LSTYPE_LINKSHELL ? (uint8)LSTYPE_PEARLSACK : kickerRank)); } - catch (zmq::error_t& err) + } + else if (PChar && PChar->PLinkshell2 && PChar->PLinkshell2->getID() == ref((uint8*)extra.data(), 24)) + { + uint8 kickerRank = ref((uint8*)extra.data(), 28); + CItemLinkshell* targetLS = (CItemLinkshell*)PChar->getEquip(SLOT_LINK2); + if (targetLS && (kickerRank == LSTYPE_LINKSHELL || (kickerRank == LSTYPE_PEARLSACK && targetLS->GetLSType() == LSTYPE_LINKPEARL))) { - ShowCritical("Message: Unable to connect chat socket: %s", err.what()); + PChar->PLinkshell2->RemoveMemberByName(memberName, kickerRank); } } + */ +} + +void IPCClient::handleMessage_LinkshellSetMessage(const IPP& ipp, const ipc::LinkshellSetMessage& message) +{ + TracyZoneScoped; - void close() + // TODO: This was originally piggybacking on the Linkshell chat messages, so we need to reimplement this + // : in here now + + ShowWarning("Not implemented"); +} + +void IPCClient::handleMessage_LuaFunction(const IPP& ipp, const ipc::LuaFunction& message) +{ + TracyZoneScoped; + + auto result = lua.safe_script(message.funcString); + if (!result.valid()) { - TracyZoneScoped; + sol::error err = result; + ShowError("MSG_LUA_FUNCTION: error: %s: %s", err.what(), message.funcString.c_str()); + } +} + +void IPCClient::handleMessage_KillSession(const IPP& ipp, const ipc::KillSession& message) +{ + TracyZoneScoped; + + map_session_data_t* sessionToDelete = nullptr; - if (zSocket) + for (const auto [_, session] : map_session_list) + { + if (session->charID == message.charId) { - zSocket->close(); - zSocket = nullptr; + sessionToDelete = session; + break; } - zContext.close(); } - void send(MSGSERVTYPE type, void* data, size_t datalen, const std::unique_ptr& packet) + if (sessionToDelete && sessionToDelete->blowfish.status == BLOWFISH_PENDING_ZONE) { - TracyZoneScoped; + DebugSockets(fmt::format("Closing session of charid {} on request of other process", message.charId)); + map_close_session(server_clock::now(), sessionToDelete); + } +} - chat_message_t msg; - msg.type = zmq::message_t(sizeof(MSGSERVTYPE)); +void IPCClient::handleMessage_RegionalEvent(const IPP& ipp, const ipc::RegionalEvent& message) +{ + TracyZoneScoped; - ref((uint8*)msg.type.data(), 0) = type; + switch (message.type) + { + case RegionalEventType::Conquest: + conquest::HandleZMQMessage(message.subType, { message.payload.data(), message.payload.size() }); + break; + default: + ShowWarningFmt("Received unknown regional event type {} from {}", message.type, ipp.toString()); + break; + } +} - msg.data = zmq::message_t(datalen); - if (datalen > 0) - { - std::memcpy(msg.data.data(), data, datalen); - } +void IPCClient::handleMessage_GMSendToZone(const IPP& ipp, const ipc::GMSendToZone& message) +{ + TracyZoneScoped; - if (packet) - { - // clang-format off - msg.packet = zmq::message_t(*packet, packet->getSize()); - } - else + // TODO: + /* + CCharEntity* PChar = zoneutils::GetChar(message.targetId); + if (PChar && PChar->loc.zone) + { + uint32 requester = ref(message.requesterId); + if (requester != 0) { - msg.packet = zmq::message_t(0); + char buf[30]; + std::memset(&buf[0], 0, sizeof(buf)); + + ref(&buf, 0) = requester; + ref(&buf, 8) = PChar->getZone(); + ref(&buf, 10) = PChar->loc.p.x; + ref(&buf, 14) = PChar->loc.p.y; + ref(&buf, 18) = PChar->loc.p.z; + ref(&buf, 22) = PChar->loc.p.rotation; + ref(&buf, 23) = PChar->m_moghouseID; + + message::send(MSG_SEND_TO_ZONE, &buf, sizeof(buf), nullptr); + break; } - outgoing_queue.enqueue(msg); + uint16 zoneId = ref((uint8*)extra.data(), 8); + float x = ref((uint8*)extra.data(), 10); + float y = ref((uint8*)extra.data(), 14); + float z = ref((uint8*)extra.data(), 18); + uint8 rot = ref((uint8*)extra.data(), 22); + uint32 moghouseID = ref((uint8*)extra.data(), 23); + + PChar->updatemask = 0; + + PChar->m_moghouseID = 0; + + PChar->loc.p.x = x; + PChar->loc.p.y = y; + PChar->loc.p.z = z; + PChar->loc.p.rotation = rot; + PChar->loc.destination = zoneId; + PChar->m_moghouseID = moghouseID; + PChar->loc.boundary = 0; + PChar->status = STATUS_TYPE::DISAPPEAR; + PChar->animation = ANIMATION_NONE; + PChar->clearPacketList(); + + charutils::SendToZone(PChar, 2, zoneutils::GetZoneIPP(zoneId)); } + */ +} + +void IPCClient::handleMessage_GMSendToEntity(const IPP& ipp, const ipc::GMSendToEntity& message) +{ + TracyZoneScoped; + + // TODO: + /* + // Need to check which server we're on so we don't get null pointers + bool toTargetServer = ref((uint8*)extra.data(), 0); + bool spawnedOnly = ref((uint8*)extra.data(), 1); - void send(uint16 zone, std::string const& luaFunc) + if (toTargetServer) // This is going to the target's game server { - TracyZoneScoped; + CBaseEntity* Entity = zoneutils::GetEntity(ref((uint8*)extra.data(), 6)); - std::vector packetData(4 + luaFunc.size() + 1); - ref(packetData.data(), 2) = zone; + if (Entity && Entity->loc.zone) + { + char buf[22]; + std::memset(&buf[0], 0, sizeof(buf)); - std::memcpy(packetData.data() + 4, luaFunc.data(), luaFunc.size()); + uint16 targetZone = ref((uint8*)extra.data(), 2); + uint16 playerZone = ref((uint8*)extra.data(), 4); + uint16 playerID = ref((uint8*)extra.data(), 10); - packetData[packetData.size() - 1] = '\0'; + float X = Entity->GetXPos(); + float Y = Entity->GetYPos(); + float Z = Entity->GetZPos(); + uint8 R = Entity->GetRotPos(); - message::send(MSG_LUA_FUNCTION, packetData.data(), packetData.size()); - } + ref(&buf, 1) = true; // Found, so initiate warp back on the requesting server - void send(uint32 playerId, const std::unique_ptr& packet) - { - TracyZoneScoped; + if (Entity->status == STATUS_TYPE::DISAPPEAR) + { + if (spawnedOnly) + { + ref(&buf, 1) = false; // Spawned only, so do not initiate warp + } + else + { + // If entity not spawned, go to default location as listed in database + const char* query = "SELECT pos_x, pos_y, pos_z FROM mob_spawn_points WHERE mobid = %u"; + auto fetch = _sql->Query(query, Entity->id); - std::array packetData{}; - ref(packetData.data(), 0) = playerId; - message::send(MSG_DIRECT, packetData.data(), packetData.size(), packet); - } + if (fetch != SQL_ERROR && _sql->NumRows() != 0) + { + while (_sql->NextRow() == SQL_SUCCESS) + { + X = _sql->GetFloatData(0); + Y = _sql->GetFloatData(1); + Z = _sql->GetFloatData(2); + } + } + } + } - void send(std::string const& playerName, const std::unique_ptr& packet) + ref(&buf, 0) = false; + ref(&buf, 2) = playerZone; + ref(&buf, 4) = playerID; + ref(&buf, 6) = X; + ref(&buf, 10) = Y; + ref(&buf, 14) = Z; + ref(&buf, 18) = R; + ref(&buf, 20) = targetZone; + + message::send(MSG_SEND_TO_ENTITY, &buf, sizeof(buf), nullptr); + break; + } + } + else // This is going to the player's game server { - TracyZoneScoped; + CCharEntity* PChar = zoneutils::GetChar(ref((uint8*)extra.data(), 4)); - send(charutils::getCharIdFromName(playerName), packet); + if (PChar && PChar->loc.zone) + { + if (ref((uint8*)extra.data(), 1)) + { + PChar->loc.p.x = ref((uint8*)extra.data(), 6); + PChar->loc.p.y = ref((uint8*)extra.data(), 10); + PChar->loc.p.z = ref((uint8*)extra.data(), 14); + PChar->loc.p.rotation = ref((uint8*)extra.data(), 18); + PChar->loc.destination = ref((uint8*)extra.data(), 20); + + PChar->m_moghouseID = 0; + PChar->loc.boundary = 0; + PChar->updatemask = 0; + + PChar->status = STATUS_TYPE::DISAPPEAR; + PChar->animation = ANIMATION_NONE; + + PChar->clearPacketList(); + + charutils::SendToZone(PChar, 2, zoneutils::GetZoneIPP(PChar->loc.destination)); + } + } } + */ +} - void rpc_send(uint16 sendZone, uint16 recvZone, std::string const& sendStr, sol::function recvFunc) +void IPCClient::handleMessage_RPCSend(const IPP& ipp, const ipc::RPCSend& message) +{ + TracyZoneScoped; + + // TODO: + /* + // Extract data + uint8* data = (uint8*)extra.data(); + uint16 sendZone = ref(data, 2); // here + uint16 recvZone = ref(data, 4); // origin + uint64_t slotKey = ref(data, 6); + uint16 strSize = ref(data, 14); + auto sendStr = std::string(data + 16, data + 16 + strSize); + + // Execute Lua & collect payload + std::string payload = ""; + auto result = lua.safe_script(sendStr); + if (result.valid() && result.return_count()) { - uint64_t slotKey = std::chrono::duration_cast(hires_clock::now().time_since_epoch()).count(); - replyMap[slotKey] = std::move(recvFunc); + payload = result.get(0); + } + + // Reply w/ payload + std::vector packetData(16 + payload.size() + 1); + + ref(packetData.data(), 2) = recvZone; // origin + ref(packetData.data(), 4) = sendZone; // here + ref(packetData.data(), 6) = slotKey; - std::vector packetData(16 + sendStr.size() + 1); + ref(packetData.data(), 14) = (uint16)payload.size(); + std::memcpy(packetData.data() + 16, payload.data(), payload.size()); - ref(packetData.data(), 2) = recvZone; // destination - ref(packetData.data(), 4) = sendZone; // origin - ref(packetData.data(), 6) = slotKey; + packetData[packetData.size() - 1] = '\0'; + + message::send(MSG_RPC_RECV, packetData.data(), packetData.size()); + */ +} + +void IPCClient::handleMessage_RPCRecv(const IPP& ipp, const ipc::RPCRecv& message) +{ + TracyZoneScoped; - ref(packetData.data(), 14) = (uint16)sendStr.size(); - std::memcpy(packetData.data() + 16, sendStr.data(), sendStr.size()); + // TODO: + /* + uint8* data = (uint8*)extra.data(); - packetData[packetData.size() - 1] = '\0'; + // No need for any of the zone id data now + uint64_t slotKey = ref(data, 6); + uint16 strSize = ref(data, 14); + auto payload = std::string(data + 16, data + 16 + strSize); - message::send(MSG_RPC_SEND, packetData.data(), packetData.size()); + auto maybeEntry = replyMap.find(slotKey); + if (maybeEntry != replyMap.end()) + { + auto& recvFunc = maybeEntry->second; + auto result = recvFunc(payload); + if (!result.valid()) + { + sol::error err = result; + ShowError("message::parse::MSG_RPC_RECV: %s", err.what()); + } + replyMap.erase(slotKey); } -}; // namespace message + */ +} + +void IPCClient::handleUnknownMessage(const IPP& ipp, const std::span message) +{ + TracyZoneScoped; + + ShowWarningFmt("Received unknown message from {} with code {} and size {}", ipp.toString(), message[0], message.size()); +} diff --git a/src/map/ipc_client.h b/src/map/ipc_client.h index fd8ce25521d..56951210782 100644 --- a/src/map/ipc_client.h +++ b/src/map/ipc_client.h @@ -2,6 +2,7 @@ =========================================================================== Copyright (c) 2010-2015 Darkstar Dev Teams + Copyright (c) 2025 LandSandBoat Dev Teams This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,74 +20,102 @@ =========================================================================== */ -#ifndef _MESSAGE_H_ -#define _MESSAGE_H_ +#pragma once #include "common/cbasetypes.h" +#include "common/ipc.h" #include "common/lua.h" #include "common/mmo.h" #include "common/socket.h" #include "common/sql.h" +#include "common/zmq_dealer_wrapper.h" +#include #include +#include -class CBasicPacket; +class IPCClient final : public ipc::IIPCMessageHandler +{ +public: + IPCClient(); + + void handleIncomingMessages(); + + template + void sendMessage(T&& message); + + // + // ipc::IIPCMessageHandler + // + + void handleMessage_EmptyStruct(const IPP& ipp, const ipc::EmptyStruct& message) override; + void handleMessage_CharLogin(const IPP& ipp, const ipc::CharLogin& message) override; + void handleMessage_CharVarUpdate(const IPP& ipp, const ipc::CharVarUpdate& message) override; + void handleMessage_ChatMessageTell(const IPP& ipp, const ipc::ChatMessageTell& message) override; + void handleMessage_ChatMessageParty(const IPP& ipp, const ipc::ChatMessageParty& message) override; + void handleMessage_ChatMessageAlliance(const IPP& ipp, const ipc::ChatMessageAlliance& message) override; + void handleMessage_ChatMessageLinkshell(const IPP& ipp, const ipc::ChatMessageLinkshell& message) override; + void handleMessage_ChatMessageUnity(const IPP& ipp, const ipc::ChatMessageUnity& message) override; + void handleMessage_ChatMessageYell(const IPP& ipp, const ipc::ChatMessageYell& message) override; + void handleMessage_ChatMessageServerMessage(const IPP& ipp, const ipc::ChatMessageServerMessage& message) override; + void handleMessage_PartyInvite(const IPP& ipp, const ipc::PartyInvite& message) override; + void handleMessage_PartyInviteResponse(const IPP& ipp, const ipc::PartyInviteResponse& message) override; + void handleMessage_PartyReload(const IPP& ipp, const ipc::PartyReload& message) override; + void handleMessage_PartyDisband(const IPP& ipp, const ipc::PartyDisband& message) override; + void handleMessage_AllianceReload(const IPP& ipp, const ipc::AllianceReload& message) override; + void handleMessage_AllianceDissolve(const IPP& ipp, const ipc::AllianceDissolve& message) override; + void handleMessage_PlayerKick(const IPP& ipp, const ipc::PlayerKick& message) override; + void handleMessage_MessageStandard(const IPP& ipp, const ipc::MessageStandard& message) override; + void handleMessage_MessageSystem(const IPP& ipp, const ipc::MessageSystem& message) override; + void handleMessage_LinkshellRankChange(const IPP& ipp, const ipc::LinkshellRankChange& message) override; + void handleMessage_LinkshellRemove(const IPP& ipp, const ipc::LinkshellRemove& message) override; + void handleMessage_LinkshellSetMessage(const IPP& ipp, const ipc::LinkshellSetMessage& message) override; + void handleMessage_LuaFunction(const IPP& ipp, const ipc::LuaFunction& message) override; + void handleMessage_KillSession(const IPP& ipp, const ipc::KillSession& message) override; + void handleMessage_RegionalEvent(const IPP& ipp, const ipc::RegionalEvent& message) override; + void handleMessage_GMSendToZone(const IPP& ipp, const ipc::GMSendToZone& message) override; + void handleMessage_GMSendToEntity(const IPP& ipp, const ipc::GMSendToEntity& message) override; + void handleMessage_RPCSend(const IPP& ipp, const ipc::RPCSend& message) override; + void handleMessage_RPCRecv(const IPP& ipp, const ipc::RPCRecv& message) override; + + void handleUnknownMessage(const IPP& ipp, const std::span message) override; + +private: + ZMQDealerWrapper zmqDealerWrapper_; +}; -struct chat_message_t +// +// Inline implementations +// + +template +void IPCClient::sendMessage(T&& message) { - zmq::message_t type; - zmq::message_t data; - zmq::message_t packet; + TracyZoneScoped; - chat_message_t() noexcept - { - } + // TODO: IPP for World Server + ShowDebugFmt("Sending message: {}", ipc::toString(ipc::getEnumType())); - chat_message_t& operator=(chat_message_t&& other) noexcept - { - this->type.move(other.type); - this->packet.move(other.packet); - this->data.move(other.data); - return *this; - } + const auto bytes = ipc::toBytesWithHeader(message); + zmqDealerWrapper_.outgoingQueue_.enqueue(zmq::message_t(bytes)); +} - chat_message_t(chat_message_t const& other) noexcept - { - // NOTE: Normally we wouldn't want to use const_cast, but ZMQ has - // deprecated their copy function that uses const&. - this->type.copy(*const_cast(&other.type)); - this->packet.copy(*const_cast(&other.packet)); - this->data.copy(*const_cast(&other.data)); - } +// +// Convenience namespace +// - chat_message_t(chat_message_t&& other) noexcept - : type(std::move(other.type)) - , data(std::move(other.data)) - , packet(std::move(other.packet)) - { - } -}; +// TODO: Don't do this +extern std::unique_ptr ipcClient_; namespace message { - // For use on the main thread - // NOTE: All SQL operations happen on the main thread void init(); - void init(const char* chatIp, uint16 chatPort); + + template + void send(T&& message) + { + ipcClient_->sendMessage(message); + } + void handle_incoming(); - void send(MSGSERVTYPE type, void* data, size_t datalen, const std::unique_ptr& packet = nullptr); - void send(uint16 zone, std::string const& luaFunc); - void send(uint32 playerId, const std::unique_ptr& packet); - void send(std::string const& playerName, const std::unique_ptr& packet); - void send_charvar_update(uint32 charId, std::string const& varName, uint32 value, uint32 expiry); - void rpc_send(uint16 sendZone, uint16 recvZone, std::string const& sendStr, sol::function recvFunc); - - void close(); - - // For use on the zmq thread - // NOTE: No SQL operations happen in here - void listen(); - void send_queue(); -}; // namespace message - -#endif +} // namespace message diff --git a/src/map/linkshell.cpp b/src/map/linkshell.cpp index 832107060aa..2487347f9aa 100644 --- a/src/map/linkshell.cpp +++ b/src/map/linkshell.cpp @@ -33,11 +33,11 @@ #include "packets/message_system.h" #include "conquest_system.h" +#include "ipc_client.h" #include "item_container.h" #include "items/item_linkshell.h" #include "linkshell.h" #include "map.h" -#include "message.h" #include "packets/linkshell_message.h" #include "utils/charutils.h" #include "utils/itemutils.h" @@ -91,13 +91,13 @@ void CLinkshell::setMessage(const std::string& message, const std::string& poste return; } - int8 packetData[8]{}; - ref(packetData, 0) = m_id; - ref(packetData, 4) = 0; if (message.size() != 0) { - message::send(MSG_CHAT_LINKSHELL, packetData, sizeof(packetData), - std::make_unique(poster, message, m_name, std::numeric_limits::min(), true)); + message::send(ipc::LinkshellSetMessage{ + .linkshellId = m_id, + .poster = poster, + .message = message, + }); } } diff --git a/src/map/lua/lua_baseentity.cpp b/src/map/lua/lua_baseentity.cpp index de89251e1d7..0ccfff4abfc 100644 --- a/src/map/lua/lua_baseentity.cpp +++ b/src/map/lua/lua_baseentity.cpp @@ -44,11 +44,11 @@ #include "fishingcontest.h" #include "guild.h" #include "instance.h" +#include "ipc_client.h" #include "item_container.h" #include "latent_effect_container.h" #include "linkshell.h" #include "map.h" -#include "message.h" #include "mob_modifier.h" #include "mob_spell_container.h" #include "mobskill.h" @@ -346,7 +346,13 @@ void CLuaBaseEntity::printToArea(std::string const& message, sol::object const& if (messageRange == MESSAGE_AREA_SYSTEM) { - message::send(MSG_CHAT_SERVMES, nullptr, 0, std::make_unique(PChar, messageLook, message, name)); + // TODO: Support messageLook + + message::send(ipc::ChatMessageServerMessage{ + .senderId = PChar->id, + .senderName = name, + .message = message, + }); } else if (messageRange == MESSAGE_AREA_SAY) { @@ -360,37 +366,60 @@ void CLuaBaseEntity::printToArea(std::string const& message, sol::object const& } else if (messageRange == MESSAGE_AREA_PARTY) { - int8 packetData[8]{}; if (PChar->PParty->m_PAlliance) { - ref(packetData, 0) = PChar->PParty->m_PAlliance->m_AllianceID; - ref(packetData, 4) = 0; // No ID so that the PChar sees the message too - message::send(MSG_CHAT_ALLIANCE, packetData, sizeof(packetData), std::make_unique(PChar, messageLook, message, name)); + // TODO: Support messageLook + + message::send(ipc::ChatMessageAlliance{ + .allianceId = PChar->PParty->m_PAlliance->m_AllianceID, + .senderId = PChar->id, + .senderName = name, + .message = message, + }); } else if (PChar->PParty) { - ref(packetData, 0) = PChar->PParty->GetPartyID(); - ref(packetData, 4) = 0; // No ID so that the PChar sees the message too - message::send(MSG_CHAT_PARTY, packetData, sizeof(packetData), std::make_unique(PChar, messageLook, message, name)); + // TODO: Support messageLook + + message::send(ipc::ChatMessageParty{ + .partyId = PChar->PParty->GetPartyID(), + .senderId = PChar->id, + .senderName = name, + .message = message, + }); } } else if (messageRange == MESSAGE_AREA_YELL) { - message::send(MSG_CHAT_YELL, nullptr, 0, std::make_unique(PChar, messageLook, message, name)); + // TODO: Support messageLook + + message::send(ipc::ChatMessageYell{ + .senderId = PChar->id, + .senderName = name, + .message = message, + }); + PChar->loc.zone->PushPacket(PChar, CHAR_INRANGE_SELF, std::make_unique(PChar, messageLook, message, name)); } else if (messageRange == MESSAGE_AREA_UNITY) { - message::send(MSG_CHAT_UNITY, nullptr, 0, std::make_unique(PChar, messageLook, message, name)); + // TODO: Support messageLook + + message::send(ipc::ChatMessageUnity{ + .unityLeaderId = PChar->id, + .senderId = PChar->id, + .senderName = name, + .message = message, + }); + PChar->loc.zone->PushPacket(PChar, CHAR_INRANGE_SELF, std::make_unique(PChar, messageLook, message, name)); } else { ShowError("CLuaBaseEntity::printToArea : invalid message area/messageRange value %u given by script.", messageRange); } - /* - Todo: Assist channels - */ + + // TODO: Assist channels } /************************************************************************ @@ -3722,7 +3751,7 @@ void CLuaBaseEntity::goToEntity(uint32 targetID, sol::object const& option) ref(&buf, 6) = targetID; ref(&buf, 10) = playerID; - message::send(MSG_SEND_TO_ENTITY, &buf[0], sizeof(buf), nullptr); + // message::send(MSG_SEND_TO_ENTITY, &buf[0], sizeof(buf), nullptr); } /************************************************************************ @@ -3733,22 +3762,18 @@ void CLuaBaseEntity::goToEntity(uint32 targetID, sol::object const& option) bool CLuaBaseEntity::gotoPlayer(std::string const& playerName) { - bool found = false; - uint32 charid = charutils::getCharIdFromName(playerName); if (charid != 0) { - char buf[30]; - std::memset(&buf[0], 0, sizeof(buf)); - - ref(&buf, 0) = charid; // target char id - ref(&buf, 4) = m_PBaseEntity->id; // warping to target char, their server will send us a zoning message with their pos + message::send(ipc::GMSendToZone{ + .targetId = charid, // target char id + .requesterId = m_PBaseEntity->id, // warping to target char, their server will send us a zoning message with their pos + }); - message::send(MSG_SEND_TO_ZONE, &buf[0], sizeof(buf), nullptr); - found = true; + return true; } - return found; + return false; } /************************************************************************ @@ -3781,7 +3806,7 @@ bool CLuaBaseEntity::bringPlayer(std::string const& playerName) ref(&buf, 23) = static_cast(m_PBaseEntity)->m_moghouseID; } - message::send(MSG_SEND_TO_ZONE, &buf[0], sizeof(buf), nullptr); + // message::send(MSG_SEND_TO_ZONE, &buf[0], sizeof(buf), nullptr); found = true; } diff --git a/src/map/lua/luautils.cpp b/src/map/lua/luautils.cpp index 7cabd6b3260..bd11cc2edb6 100644 --- a/src/map/lua/luautils.cpp +++ b/src/map/lua/luautils.cpp @@ -22,6 +22,7 @@ #include "luautils.h" #include "common/filewatcher.h" +#include "common/ipc.h" #include "common/logging.h" #include "common/utils.h" #include "common/vana_time.h" @@ -72,9 +73,9 @@ #include "entities/mobentity.h" #include "fishingcontest.h" #include "instance.h" +#include "ipc_client.h" #include "items/item_puppet.h" #include "map.h" -#include "message.h" #include "mobskill.h" #include "monstrosity.h" #include "packets/action.h" @@ -1388,7 +1389,10 @@ namespace luautils void SendLuaFuncStringToZone(uint16 zoneId, std::string const& str) { - message::send(zoneId, str); + message::send(ipc::LuaFunction{ + .zoneId = zoneId, + .funcString = str, + }); } uint32 VanadielTime() diff --git a/src/map/map.cpp b/src/map/map.cpp index 32ff05c52d8..615da516afb 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -35,10 +35,10 @@ #include "ability.h" #include "daily_system.h" +#include "ipc_client.h" #include "job_points.h" #include "latent_effect_container.h" #include "linkshell.h" -#include "message.h" #include "mob_spell_list.h" #include "monstrosity.h" #include "packet_guard.h" @@ -266,7 +266,6 @@ int32 do_init(int32 argc, char** argv) ShowInfo("do_init: starting ZMQ thread"); message::init(); - messageThread = nonstd::jthread(message::listen); ShowInfo("do_init: loading items"); itemutils::Initialize(); @@ -403,8 +402,7 @@ int32 do_init(int32 argc, char** argv) } fmt::print("Promoting {} to GM level {}\n", PChar->name, level); - PChar->pushPacket(PChar, MESSAGE_SYSTEM_3, - fmt::format("You have been set to GM level {}.", level), ""); + PChar->pushPacket(PChar, MESSAGE_SYSTEM_3, fmt::format("You have been set to GM level {}.", level)); }); gConsoleService->RegisterCommand("reload_settings", "Reload settings files.", @@ -466,7 +464,6 @@ void do_final(int code) trustutils::FreeTrustList(); zoneutils::FreeZoneList(); - message::close(); messageThread.join(); CTaskMgr::delInstance(); @@ -789,22 +786,22 @@ int32 recv_parse(int8* buff, size_t* buffsize, sockaddr_in* from, map_session_da if (map_session_data->PChar == nullptr) { - uint32 CharID = ref(buff, FFXI_HEADER_SIZE + 0x0C); - uint16 LangID = ref(buff, FFXI_HEADER_SIZE + 0x58); + uint32 charID = ref(buff, FFXI_HEADER_SIZE + 0x0C); + uint16 langID = ref(buff, FFXI_HEADER_SIZE + 0x58); - std::ignore = LangID; + std::ignore = langID; - auto rset = db::preparedStmt("SELECT charid FROM chars WHERE charid = (?) LIMIT 1", CharID); + auto rset = db::preparedStmt("SELECT charid FROM chars WHERE charid = ? LIMIT 1", charID); if (!rset || rset->rowsCount() == 0 || !rset->next()) { - ShowError("recv_parse: Cannot load charid %u", CharID); + ShowError("recv_parse: Cannot load charid %u", charID); return -1; } - rset = db::preparedStmt("SELECT session_key FROM accounts_sessions WHERE charid = (?) LIMIT 1", CharID); + rset = db::preparedStmt("SELECT session_key FROM accounts_sessions WHERE charid = ? LIMIT 1", charID); if (!rset || rset->rowsCount() == 0 || !rset->next()) { - ShowError("recv_parse: Cannot load session_key for charid %u", CharID); + ShowError("recv_parse: Cannot load session_key for charid %u", charID); } else { @@ -813,16 +810,15 @@ int32 recv_parse(int8* buff, size_t* buffsize, sockaddr_in* from, map_session_da map_session_data->initBlowfish(); } - map_session_data->PChar = charutils::LoadChar(CharID); - map_session_data->charID = CharID; + map_session_data->PChar = charutils::LoadChar(charID); + map_session_data->charID = charID; // If we're a new char on a new instance and prevzone != zone if (map_session_data->blowfish.status == BLOWFISH_WAITING && map_session_data->PChar->loc.destination != map_session_data->PChar->loc.prevzone) { - uint8 data[4]{}; - ref(data, 0) = CharID; - - message::send(MSG_KILL_SESSION, data, sizeof(data), nullptr); + message::send(ipc::KillSession{ + .charId = charID, + }); } } diff --git a/src/map/packet_system.cpp b/src/map/packet_system.cpp index 26b330ca6cd..2a2e691a6aa 100644 --- a/src/map/packet_system.cpp +++ b/src/map/packet_system.cpp @@ -39,11 +39,11 @@ #include "conquest_system.h" #include "enmity_container.h" #include "fishingcontest.h" +#include "ipc_client.h" #include "item_container.h" #include "latent_effect_container.h" #include "linkshell.h" #include "map.h" -#include "message.h" #include "mob_modifier.h" #include "monstrosity.h" #include "notoriety_container.h" @@ -3227,8 +3227,10 @@ void SmallPacket0x066(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x06E(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + uint32 charid = data.ref(0x04); uint16 targid = data.ref(0x08); + // cannot invite yourself if (PChar->id == charid) { @@ -3244,7 +3246,8 @@ void SmallPacket0x06E(map_session_data_t* const PSession, CCharEntity* const PCh switch (data.ref(0x0A)) { - case 0: // party - must by party leader or solo + case INVITE_PARTY: // party - must by party leader or solo + { if (PChar->PParty == nullptr || PChar->PParty->GetLeader() == PChar) { if (PChar->PParty && PChar->PParty->IsFull()) @@ -3252,7 +3255,9 @@ void SmallPacket0x06E(map_session_data_t* const PSession, CCharEntity* const PCh PChar->pushPacket(PChar, 0, 0, MsgStd::CannotInvite); break; } + CCharEntity* PInvitee = nullptr; + if (targid != 0) { CBaseEntity* PEntity = PChar->GetEntity(targid, TYPE_PC); @@ -3265,9 +3270,11 @@ void SmallPacket0x06E(map_session_data_t* const PSession, CCharEntity* const PCh { PInvitee = zoneutils::GetChar(charid); } + if (PInvitee) { ShowDebug("%s sent party invite to %s", PChar->getName(), PInvitee->getName()); + // make sure invitee isn't dead or in jail, they aren't a party member and don't already have an invite pending, and your party is not full if (PInvitee->isDead() || jailutils::InPrison(PInvitee) || PInvitee->InvitePending.id != 0 || PInvitee->PParty != nullptr) { @@ -3275,6 +3282,7 @@ void SmallPacket0x06E(map_session_data_t* const PSession, CCharEntity* const PCh PChar->pushPacket(PChar, 0, 0, MsgStd::CannotInvite); break; } + // check /blockaid if (PInvitee->getBlockingAid()) { @@ -3287,6 +3295,7 @@ void SmallPacket0x06E(map_session_data_t* const PSession, CCharEntity* const PCh PChar->pushPacket(0, 0, MsgStd::CannotInvite); break; } + if (PInvitee->StatusEffectContainer->HasStatusEffect(EFFECT_LEVEL_SYNC)) { ShowDebug("%s has level sync, unable to send invite", PInvitee->getName()); @@ -3296,8 +3305,11 @@ void SmallPacket0x06E(map_session_data_t* const PSession, CCharEntity* const PCh PInvitee->InvitePending.id = PChar->id; PInvitee->InvitePending.targid = PChar->targid; - PInvitee->pushPacket(charid, targid, PChar, INVITE_PARTY); + + PInvitee->pushPacket(charid, targid, PChar->getName(), INVITE_PARTY); + ShowDebug("Sent party invite packet to %s", PInvitee->getName()); + if (PChar->PParty && PChar->PParty->GetSyncTarget()) { PInvitee->pushPacket(PInvitee, 0, 0, MsgStd::LevelSyncWarning); @@ -3306,13 +3318,16 @@ void SmallPacket0x06E(map_session_data_t* const PSession, CCharEntity* const PCh else { ShowDebug("Building invite packet to send to lobby server from %s to (%d)", PChar->getName(), charid); + // on another server (hopefully) - uint8 packetData[12]{}; - ref(packetData, 0) = charid; - ref(packetData, 4) = targid; - ref(packetData, 6) = PChar->id; - ref(packetData, 10) = PChar->targid; - message::send(MSG_PT_INVITE, packetData, sizeof(packetData), std::make_unique(charid, targid, PChar, INVITE_PARTY)); + message::send(ipc::PartyInvite{ + .inviteeId = charid, + .inviteeTargId = targid, + .inviterId = PChar->id, + .inviterTargId = PChar->targid, + .inviterName = PChar->getName(), + .inviteType = INVITE_PARTY, + }); ShowDebug("Sent invite packet to lobby server from %s to (%d)", PChar->getName(), charid); } @@ -3322,14 +3337,16 @@ void SmallPacket0x06E(map_session_data_t* const PSession, CCharEntity* const PCh ShowDebug("%s is not party leader, cannot send invite", PChar->getName()); PChar->pushPacket(PChar, 0, 0, MsgStd::NotPartyLeader); } - break; - - case 5: // alliance - must be unallied party leader or alliance leader of a non-full alliance + } + break; + case INVITE_ALLIANCE: // alliance - must be unallied party leader or alliance leader of a non-full alliance + { if (PChar->PParty && PChar->PParty->GetLeader() == PChar && (PChar->PParty->m_PAlliance == nullptr || (PChar->PParty->m_PAlliance->getMainParty() == PChar->PParty && !PChar->PParty->m_PAlliance->isFull()))) { CCharEntity* PInvitee = nullptr; + if (targid != 0) { CBaseEntity* PEntity = PChar->GetEntity(targid, TYPE_PC); @@ -3346,6 +3363,7 @@ void SmallPacket0x06E(map_session_data_t* const PSession, CCharEntity* const PCh if (PInvitee) { ShowDebug("%s sent alliance invite to %s", PChar->getName(), PInvitee->getName()); + // check /blockaid if (PInvitee->getBlockingAid()) { @@ -3358,6 +3376,7 @@ void SmallPacket0x06E(map_session_data_t* const PSession, CCharEntity* const PCh PChar->pushPacket(0, 0, MsgStd::CannotInvite); break; } + // make sure intvitee isn't dead or in jail, they are an unallied party leader and don't already have an invite pending if (PInvitee->isDead() || jailutils::InPrison(PInvitee) || PInvitee->InvitePending.id != 0 || PInvitee->PParty == nullptr || PInvitee->PParty->GetLeader() != PInvitee || PInvitee->PParty->m_PAlliance) @@ -3366,6 +3385,7 @@ void SmallPacket0x06E(map_session_data_t* const PSession, CCharEntity* const PCh PChar->pushPacket(PChar, 0, 0, MsgStd::CannotInvite); break; } + if (PInvitee->StatusEffectContainer->HasStatusEffect(EFFECT_LEVEL_SYNC)) { ShowDebug("%s has level sync, unable to send invite", PInvitee->getName()); @@ -3375,7 +3395,9 @@ void SmallPacket0x06E(map_session_data_t* const PSession, CCharEntity* const PCh PInvitee->InvitePending.id = PChar->id; PInvitee->InvitePending.targid = PChar->targid; - PInvitee->pushPacket(charid, targid, PChar, INVITE_ALLIANCE); + + PInvitee->pushPacket(charid, targid, PChar->getName(), INVITE_ALLIANCE); + ShowDebug("Sent party invite packet to %s", PInvitee->getName()); } else @@ -3383,21 +3405,25 @@ void SmallPacket0x06E(map_session_data_t* const PSession, CCharEntity* const PCh ShowDebug("(Alliance) Building invite packet to send to lobby server from %s to (%d)", PChar->getName(), charid); // on another server (hopefully) - uint8 packetData[12]{}; - ref(packetData, 0) = charid; - ref(packetData, 4) = targid; - ref(packetData, 6) = PChar->id; - ref(packetData, 10) = PChar->targid; - message::send(MSG_PT_INVITE, packetData, sizeof(packetData), std::make_unique(charid, targid, PChar, INVITE_ALLIANCE)); + message::send(ipc::PartyInvite{ + .inviteeId = charid, + .inviteeTargId = targid, + .inviterId = PChar->id, + .inviterTargId = PChar->targid, + .inviterName = PChar->getName(), + .inviteType = INVITE_ALLIANCE, + }); ShowDebug("(Alliance) Sent invite packet to lobby server from %s to (%d)", PChar->getName(), charid); } } - break; - + } + break; default: + { ShowError("SmallPacket0x06E : unknown byte <%.2X>", data.ref(0x0A)); - break; + } + break; } } @@ -3410,55 +3436,67 @@ void SmallPacket0x06E(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x06F(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + if (PChar->PParty) { switch (data.ref(0x04)) { - case 0: // party - anyone may remove themself from party regardless of leadership or alliance + case INVITE_PARTY: // party - anyone may remove themself from party regardless of leadership or alliance + { if (PChar->PParty->m_PAlliance && PChar->PParty->HasOnlyOneMember()) // single member alliance parties must be removed from alliance before disband { ShowDebug("%s party size is one", PChar->getName()); + if (PChar->PParty->m_PAlliance->hasOnlyOneParty()) // if there is only 1 party then dissolve alliance { ShowDebug("%s alliance size is one party", PChar->getName()); + PChar->PParty->m_PAlliance->dissolveAlliance(); ShowDebug("%s alliance is dissolved", PChar->getName()); } else { ShowDebug("Removing %s party from alliance", PChar->getName()); + PChar->PParty->m_PAlliance->removeParty(PChar->PParty); ShowDebug("%s party is removed from alliance", PChar->getName()); } } ShowDebug("Removing %s from party", PChar->getName()); + PChar->PParty->RemoveMember(PChar); ShowDebug("%s is removed from party", PChar->getName()); - break; - case 5: // alliance - any party leader in alliance may remove their party + } + break; + case INVITE_ALLIANCE: // alliance - any party leader in alliance may remove their party + { if (PChar->PParty->m_PAlliance && PChar->PParty->GetLeader() == PChar) { ShowDebug("%s is leader of a party in an alliance", PChar->getName()); if (PChar->PParty->m_PAlliance->hasOnlyOneParty()) // if there is only 1 party then dissolve alliance { ShowDebug("One party in alliance, %s wants to dissolve the alliance", PChar->getName()); + PChar->PParty->m_PAlliance->dissolveAlliance(); ShowDebug("%s has dissolved the alliance", PChar->getName()); } else { ShowDebug("%s wants to remove their party from the alliance", PChar->getName()); + PChar->PParty->m_PAlliance->removeParty(PChar->PParty); ShowDebug("%s party is removed from the alliance", PChar->getName()); } } - break; - + } + break; default: + { ShowError("SmallPacket0x06F : unknown byte <%.2X>", data.ref(0x04)); - break; + } + break; } } } @@ -3472,6 +3510,7 @@ void SmallPacket0x06F(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x070(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + if (PChar->PParty && PChar->PParty->GetLeader() == PChar) { switch (data.ref(0x04)) @@ -3510,6 +3549,7 @@ void SmallPacket0x070(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x071(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + switch (data.ref(0x0A)) { case 0: // party - party leader may remove member of his own party @@ -3556,21 +3596,23 @@ void SmallPacket0x071(map_session_data_t* const PSession, CCharEntity* const PCh { ShowDebug("%s has removed %s from party", PChar->getName(), victimName); - uint8 reloadData[4]{}; if (PChar->PParty && PChar->PParty->m_PAlliance) { - ref(reloadData, 0) = PChar->PParty->m_PAlliance->m_AllianceID; - message::send(MSG_ALLIANCE_RELOAD, reloadData, sizeof(reloadData), nullptr); + message::send(ipc::AllianceReload{ + .allianceId = PChar->PParty->m_PAlliance->m_AllianceID, + }); } else // No alliance, notify party. { - ref(reloadData, 0) = PChar->PParty->GetPartyID(); - message::send(MSG_PT_RELOAD, reloadData, sizeof(reloadData), nullptr); + message::send(ipc::PartyReload{ + .partyId = PChar->PParty->GetPartyID(), + }); } // Notify the player they were just kicked -- they are no longer in the DB and party/alliance reloads won't notify them. - ref(reloadData, 0) = victimId; - message::send(MSG_PLAYER_KICK, reloadData, sizeof(reloadData), nullptr); + message::send(ipc::PlayerKick{ + .charId = victimId, + }); } } } @@ -3585,12 +3627,12 @@ void SmallPacket0x071(map_session_data_t* const PSession, CCharEntity* const PCh { const auto linkshellName = escapeString(asStringFromUntrustedSource(data[0x0C], 20)); - int8 packetData[29]{}; - ref(packetData, 0) = PChar->id; - std::memcpy(packetData + 0x04, linkshellName.data(), 20); - ref(packetData, 24) = PChar->PLinkshell1->getID(); - ref(packetData, 28) = PItemLinkshell->GetLSType(); - message::send(MSG_LINKSHELL_REMOVE, packetData, sizeof(packetData), nullptr); + message::send(ipc::LinkshellRemove{ + .charId = PChar->id, + .linkshellName = linkshellName, + .linkshellId = PChar->PLinkshell1->getID(), + .linkshellType = PItemLinkshell->GetLSType(), + }); } } break; @@ -3602,12 +3644,12 @@ void SmallPacket0x071(map_session_data_t* const PSession, CCharEntity* const PCh { const auto linkshellName = escapeString(asStringFromUntrustedSource(data[0x0C], 20)); - int8 packetData[29]{}; - ref(packetData, 0) = PChar->id; - std::memcpy(packetData + 0x04, linkshellName.data(), 20); - ref(packetData, 24) = PChar->PLinkshell2->getID(); - ref(packetData, 28) = PItemLinkshell->GetLSType(); - message::send(MSG_LINKSHELL_REMOVE, packetData, sizeof(packetData), nullptr); + message::send(ipc::LinkshellRemove{ + .charId = PChar->id, + .linkshellName = linkshellName, + .linkshellId = PChar->PLinkshell2->getID(), + .linkshellType = PItemLinkshell->GetLSType(), + }); } } break; @@ -3664,13 +3706,14 @@ void SmallPacket0x071(map_session_data_t* const PSession, CCharEntity* const PCh ShowDebug("%s has removed %s party from alliance", PChar->getName(), victimName); // notify party they were removed - uint8 removeData[4]{}; - ref(removeData, 0) = partyid; - message::send(MSG_PT_RELOAD, removeData, sizeof(removeData), nullptr); + message::send(ipc::PartyReload{ + .partyId = partyid, + }); // notify alliance a party was removed - ref(removeData, 0) = allianceID; - message::send(MSG_ALLIANCE_RELOAD, removeData, sizeof(removeData), nullptr); + message::send(ipc::AllianceReload{ + .allianceId = allianceID, + }); } } } @@ -3793,19 +3836,20 @@ void SmallPacket0x074(map_session_data_t* const PSession, CCharEntity* const PCh { ShowDebug("(Party) Building invite packet to send to lobby server for %s", PChar->getName()); - uint8 packetData[13]{}; - ref(packetData, 0) = PChar->InvitePending.id; - ref(packetData, 4) = PChar->InvitePending.targid; - ref(packetData, 6) = PChar->id; - ref(packetData, 10) = PChar->targid; - ref(packetData, 12) = InviteAnswer; - PChar->InvitePending.clean(); - message::send(MSG_PT_INV_RES, packetData, sizeof(packetData), nullptr); + message::send(ipc::PartyInviteResponse{ + .inviteeId = PChar->InvitePending.id, + .inviteeTargId = PChar->InvitePending.targid, + .inviterId = PChar->id, + .inviterTargId = PChar->targid, + .inviterName = PChar->getName(), + .inviteAnswer = InviteAnswer, + }); ShowDebug("(Party) Sent invite packet to send to lobby server for %s", PChar->getName()); } + PChar->InvitePending.clean(); } @@ -3861,12 +3905,12 @@ void SmallPacket0x077(map_session_data_t* const PSession, CCharEntity* const PCh const auto linkshellName = escapeString(asStringFromUntrustedSource(data[0x04], 20)); const auto permission = data.ref(0x15); - int8 packetData[29]{}; - ref(packetData, 0) = PChar->id; - std::memcpy(packetData + 0x04, linkshellName.data(), 20); - ref(packetData, 24) = PChar->PLinkshell1->getID(); - ref(packetData, 28) = permission; - message::send(MSG_LINKSHELL_RANK_CHANGE, packetData, sizeof(packetData), nullptr); + message::send(ipc::LinkshellRankChange{ + .charId = PChar->id, + .linkshellName = linkshellName, + .linkshellId = PChar->PLinkshell1->getID(), + .permission = permission, + }); } } break; @@ -3877,12 +3921,12 @@ void SmallPacket0x077(map_session_data_t* const PSession, CCharEntity* const PCh const auto linkshellName = escapeString(asStringFromUntrustedSource(data[0x04], 20)); const auto permission = data.ref(0x15); - int8 packetData[29]{}; - ref(packetData, 0) = PChar->id; - std::memcpy(packetData + 0x04, linkshellName.data(), 20); - ref(packetData, 24) = PChar->PLinkshell2->getID(); - ref(packetData, 28) = permission; - message::send(MSG_LINKSHELL_RANK_CHANGE, packetData, sizeof(packetData), nullptr); + message::send(ipc::LinkshellRankChange{ + .charId = PChar->id, + .linkshellName = linkshellName, + .linkshellId = PChar->PLinkshell2->getID(), + .permission = permission, + }); } } break; @@ -3896,9 +3940,9 @@ void SmallPacket0x077(map_session_data_t* const PSession, CCharEntity* const PCh ShowDebug(fmt::format("(Alliance) Changing leader to {}", memberName)); PChar->PParty->m_PAlliance->assignAllianceLeader(memberName); - uint8 allianceData[4]{}; - ref(allianceData, 0) = PChar->PParty->m_PAlliance->m_AllianceID; - message::send(MSG_ALLIANCE_RELOAD, allianceData, sizeof(allianceData), nullptr); + message::send(ipc::AllianceReload{ + .allianceId = PChar->PParty->m_PAlliance->m_AllianceID, + }); } } break; @@ -3918,6 +3962,7 @@ void SmallPacket0x077(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x078(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + PChar->pushPacket(PChar); } @@ -3930,6 +3975,7 @@ void SmallPacket0x078(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x083(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + uint8 quantity = data.ref(0x04); uint8 shopSlotID = data.ref(0x0A); @@ -3984,6 +4030,7 @@ void SmallPacket0x083(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x084(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + if (PChar->animation != ANIMATION_SYNTH && (PChar->CraftContainer && PChar->CraftContainer->getItemsCount() == 0)) { uint32 quantity = data.ref(0x04); @@ -4089,6 +4136,7 @@ void SmallPacket0x085(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x096(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + if (jailutils::InPrison(PChar)) { // Prevent crafting in prison @@ -4188,6 +4236,8 @@ void SmallPacket0x096(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x09B(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { + TracyZoneScoped; + // ShowInfo("SmallPacket0x09B"); // NOTE: Can trigger with !cs 335 from Chocobo Circuit @@ -4350,6 +4400,7 @@ void SmallPacket0x09B(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0AA(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + uint16 itemID = data.ref(0x04); uint8 quantity = data.ref(0x07); @@ -4403,6 +4454,7 @@ void SmallPacket0x0AA(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0A2(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + uint16 diceroll = xirand::GetRandomNumber(1000); PChar->loc.zone->PushPacket(PChar, CHAR_INRANGE_SELF, std::make_unique(PChar, diceroll, MsgStd::DiceRoll)); @@ -4417,6 +4469,7 @@ void SmallPacket0x0A2(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0AB(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + if (PChar->PGuildShop != nullptr) { PChar->pushPacket(PChar, PChar->PGuildShop); @@ -4432,6 +4485,7 @@ void SmallPacket0x0AB(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0AC(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + if (PChar->animation != ANIMATION_SYNTH && (PChar->CraftContainer && PChar->CraftContainer->getItemsCount() == 0)) { if (PChar->PGuildShop != nullptr) @@ -4476,6 +4530,7 @@ void SmallPacket0x0AC(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0AD(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + if (PChar->PGuildShop != nullptr) { PChar->pushPacket(PChar, PChar->PGuildShop); @@ -4491,21 +4546,32 @@ void SmallPacket0x0AD(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0B5(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; - char message[256] = {}; + + char msgBuffer[256] = {}; uint8 messagePosition = 0x07; - std::memcpy(&message, data[messagePosition], std::min(data.getSize() - messagePosition, sizeof(message))); + std::memcpy(&msgBuffer, data[messagePosition], std::min(data.getSize() - messagePosition, sizeof(msgBuffer))); - if (data.ref(0x06) == '!' && !jailutils::InPrison(PChar) && (CCommandHandler::call(lua, PChar, message) == 0 || PChar->m_GMlevel > 0)) + if (data.ref(0x06) == '!' && !jailutils::InPrison(PChar) && (CCommandHandler::call(lua, PChar, msgBuffer) == 0 || PChar->m_GMlevel > 0)) { // this makes sure a command isn't sent to chat } else if (data.ref(0x06) == '#' && PChar->m_GMlevel > 0) { - message::send(MSG_CHAT_SERVMES, nullptr, 0, std::make_unique(PChar, MESSAGE_SYSTEM_1, (const char*)data[0x07])); + const auto rawMessage = asStringFromUntrustedSource(data[0x07]); + + message::send(ipc::ChatMessageServerMessage{ + .senderId = PChar->id, + .senderName = PChar->getName(), + .message = rawMessage, + .zoneId = PChar->getZone(), + .gmLevel = PChar->m_GMlevel, + }); } else { + const auto rawMessage = asStringFromUntrustedSource(data[0x06]); + if (jailutils::InPrison(PChar)) { if (data.ref(0x04) == MESSAGE_SAY) @@ -4513,9 +4579,9 @@ void SmallPacket0x0B5(map_session_data_t* const PSession, CCharEntity* const PCh if (settings::get("map.AUDIT_CHAT") && settings::get("map.AUDIT_SAY")) { // clang-format off - // NOTE: We capture rawMessage as a std::string because if we cast data[0x06] into a const char*, the underlying data might + // NOTE: We capture rawMessage as a copy because if we cast data[0x06] into a const char*, the underlying data might // : be gone by the time we action this lambda on the worker thread. - Async::getInstance()->submit([name = PChar->getName(), zoneId = PChar->getZone(), rawMessage = std::string((const char*)data[0x06])]() + Async::getInstance()->submit([name = PChar->getName(), zoneId = PChar->getZone(), rawMessage]() { const auto query = "INSERT INTO audit_chat (speaker, type, zoneid, message, datetime) VALUES(?, 'SAY', ?, ?, current_timestamp())"; if (!db::preparedStmt(query, name, zoneId, rawMessage)) @@ -4525,7 +4591,7 @@ void SmallPacket0x0B5(map_session_data_t* const PSession, CCharEntity* const PCh }); // clang-format on } - PChar->loc.zone->PushPacket(PChar, CHAR_INRANGE, std::make_unique(PChar, MESSAGE_SAY, (const char*)data[0x06])); + PChar->loc.zone->PushPacket(PChar, CHAR_INRANGE, std::make_unique(PChar, MESSAGE_SAY, rawMessage)); } else { @@ -4541,9 +4607,9 @@ void SmallPacket0x0B5(map_session_data_t* const PSession, CCharEntity* const PCh if (settings::get("map.AUDIT_CHAT") && settings::get("map.AUDIT_SAY")) { // clang-format off - // NOTE: We capture rawMessage as a std::string because if we cast data[0x06] into a const char*, the underlying data might + // NOTE: We capture rawMessage as a copy because if we cast data[0x06] into a const char*, the underlying data might // : be gone by the time we action this lambda on the worker thread. - Async::getInstance()->submit([name = PChar->getName(), zoneId = PChar->getZone(), rawMessage = std::string((const char*)data[0x06])]() + Async::getInstance()->submit([name = PChar->getName(), zoneId = PChar->getZone(), rawMessage]() { const auto query = "INSERT INTO audit_chat (speaker, type, zoneid, message, datetime) VALUES(?, 'SAY', ?, ?, current_timestamp())"; if (!db::preparedStmt(query, name, zoneId, rawMessage)) @@ -4553,12 +4619,12 @@ void SmallPacket0x0B5(map_session_data_t* const PSession, CCharEntity* const PCh }); // clang-format on } - PChar->loc.zone->PushPacket(PChar, CHAR_INRANGE, std::make_unique(PChar, MESSAGE_SAY, (const char*)data[0x06])); + PChar->loc.zone->PushPacket(PChar, CHAR_INRANGE, std::make_unique(PChar, MESSAGE_SAY, rawMessage)); } break; case MESSAGE_EMOTION: { - PChar->loc.zone->PushPacket(PChar, CHAR_INRANGE, std::make_unique(PChar, MESSAGE_EMOTION, (const char*)data[0x06])); + PChar->loc.zone->PushPacket(PChar, CHAR_INRANGE, std::make_unique(PChar, MESSAGE_EMOTION, rawMessage)); } break; case MESSAGE_SHOUT: @@ -4566,9 +4632,9 @@ void SmallPacket0x0B5(map_session_data_t* const PSession, CCharEntity* const PCh if (settings::get("map.AUDIT_CHAT") && settings::get("map.AUDIT_SHOUT")) { // clang-format off - // NOTE: We capture rawMessage as a std::string because if we cast data[0x06] into a const char*, the underlying data might + // NOTE: We capture rawMessage as a copy because if we cast data[0x06] into a const char*, the underlying data might // : be gone by the time we action this lambda on the worker thread. - Async::getInstance()->submit([name = PChar->getName(), zoneId = PChar->getZone(), rawMessage = std::string((const char*)data[0x06])]() + Async::getInstance()->submit([name = PChar->getName(), zoneId = PChar->getZone(), rawMessage]() { const auto query = "INSERT INTO audit_chat (speaker, type, zoneid, message, datetime) VALUES(?, 'SHOUT', ?, ?, current_timestamp())"; if (!db::preparedStmt(query, name, zoneId, rawMessage)) @@ -4578,18 +4644,21 @@ void SmallPacket0x0B5(map_session_data_t* const PSession, CCharEntity* const PCh }); // clang-format on } - PChar->loc.zone->PushPacket(PChar, CHAR_INSHOUT, std::make_unique(PChar, MESSAGE_SHOUT, (const char*)data[0x06])); + PChar->loc.zone->PushPacket(PChar, CHAR_INSHOUT, std::make_unique(PChar, MESSAGE_SHOUT, rawMessage)); } break; case MESSAGE_LINKSHELL: { if (PChar->PLinkshell1 != nullptr) { - int8 packetData[8]{}; - ref(packetData, 0) = PChar->PLinkshell1->getID(); - ref(packetData, 4) = PChar->id; - message::send(MSG_CHAT_LINKSHELL, packetData, sizeof(packetData), - std::make_unique(PChar, MESSAGE_LINKSHELL, (const char*)data[0x06])); + message::send(ipc::ChatMessageLinkshell{ + .linkshellId = PChar->PLinkshell1->getID(), + .senderId = PChar->id, + .senderName = PChar->getName(), + .message = rawMessage, + .zoneId = PChar->getZone(), + .gmLevel = PChar->m_GMlevel, + }); if (settings::get("map.AUDIT_CHAT") && settings::get("map.AUDIT_LINKSHELL")) { @@ -4597,9 +4666,9 @@ void SmallPacket0x0B5(map_session_data_t* const PSession, CCharEntity* const PCh DecodeStringLinkshell(PChar->PLinkshell1->getName(), decodedLinkshellName); // clang-format off - // NOTE: We capture rawMessage as a std::string because if we cast data[0x06] into a const char*, the underlying data might + // NOTE: We capture rawMessage as a copy because if we cast data[0x06] into a const char*, the underlying data might // : be gone by the time we action this lambda on the worker thread. - Async::getInstance()->submit([name = PChar->getName(), zoneId = PChar->getZone(), rawMessage = std::string((const char*)data[0x06]), decodedLinkshellName]() + Async::getInstance()->submit([name = PChar->getName(), zoneId = PChar->getZone(), rawMessage, decodedLinkshellName]() { const auto query = "INSERT INTO audit_chat (speaker, type, lsName, zoneid, message, datetime) VALUES(?, 'LINKSHELL', ?, ?, ?, current_timestamp())"; if (!db::preparedStmt(query, name, decodedLinkshellName, zoneId, rawMessage)) @@ -4616,11 +4685,14 @@ void SmallPacket0x0B5(map_session_data_t* const PSession, CCharEntity* const PCh { if (PChar->PLinkshell2 != nullptr) { - int8 packetData[8]{}; - ref(packetData, 0) = PChar->PLinkshell2->getID(); - ref(packetData, 4) = PChar->id; - message::send(MSG_CHAT_LINKSHELL, packetData, sizeof(packetData), - std::make_unique(PChar, MESSAGE_LINKSHELL, (const char*)data[0x06])); + message::send(ipc::ChatMessageLinkshell{ + .linkshellId = PChar->PLinkshell2->getID(), + .senderId = PChar->id, + .senderName = PChar->getName(), + .message = rawMessage, + .zoneId = PChar->getZone(), + .gmLevel = PChar->m_GMlevel, + }); if (settings::get("map.AUDIT_CHAT") && settings::get("map.AUDIT_LINKSHELL")) { @@ -4628,9 +4700,9 @@ void SmallPacket0x0B5(map_session_data_t* const PSession, CCharEntity* const PCh DecodeStringLinkshell(PChar->PLinkshell2->getName(), decodedLinkshellName); // clang-format off - // NOTE: We capture rawMessage as a std::string because if we cast data[0x06] into a const char*, the underlying data might + // NOTE: We capture rawMessage as a copy because if we cast data[0x06] into a const char*, the underlying data might // : be gone by the time we action this lambda on the worker thread. - Async::getInstance()->submit([name = PChar->getName(), zoneId = PChar->getZone(), rawMessage = std::string((const char*)data[0x06]), decodedLinkshellName]() + Async::getInstance()->submit([name = PChar->getName(), zoneId = PChar->getZone(), rawMessage, decodedLinkshellName]() { const auto query = "INSERT INTO audit_chat (speaker, type, lsName, zoneid, message, datetime) VALUES(?, 'LINKSHELL', ?, ?, ?, current_timestamp())"; if (!db::preparedStmt(query, name, decodedLinkshellName, zoneId, rawMessage)) @@ -4647,26 +4719,35 @@ void SmallPacket0x0B5(map_session_data_t* const PSession, CCharEntity* const PCh { if (PChar->PParty != nullptr) { - int8 packetData[8]{}; if (PChar->PParty->m_PAlliance) { - ref(packetData, 0) = PChar->PParty->m_PAlliance->m_AllianceID; - ref(packetData, 4) = PChar->id; - message::send(MSG_CHAT_ALLIANCE, packetData, sizeof(packetData), std::make_unique(PChar, MESSAGE_PARTY, (const char*)data[0x06])); + message::send(ipc::ChatMessageAlliance{ + .allianceId = PChar->PParty->m_PAlliance->m_AllianceID, + .senderId = PChar->id, + .senderName = PChar->getName(), + .message = rawMessage, + .zoneId = PChar->getZone(), + .gmLevel = PChar->m_GMlevel, + }); } else { - ref(packetData, 0) = PChar->PParty->GetPartyID(); - ref(packetData, 4) = PChar->id; - message::send(MSG_CHAT_PARTY, packetData, sizeof(packetData), std::make_unique(PChar, MESSAGE_PARTY, (const char*)data[0x06])); + message::send(ipc::ChatMessageParty{ + .partyId = PChar->PParty->GetPartyID(), + .senderId = PChar->id, + .senderName = PChar->getName(), + .message = rawMessage, + .zoneId = PChar->getZone(), + .gmLevel = PChar->m_GMlevel, + }); } if (settings::get("map.AUDIT_CHAT") && settings::get("map.AUDIT_PARTY")) { // clang-format off - // NOTE: We capture rawMessage as a std::string because if we cast data[0x06] into a const char*, the underlying data might + // NOTE: We capture rawMessage as a copy because if we cast data[0x06] into a const char*, the underlying data might // : be gone by the time we action this lambda on the worker thread. - Async::getInstance()->submit([name = PChar->getName(), zoneId = PChar->getZone(), rawMessage = std::string((const char*)data[0x06])]() + Async::getInstance()->submit([name = PChar->getName(), zoneId = PChar->getZone(), rawMessage]() { const auto query = "INSERT INTO audit_chat (speaker, type, zoneid, message, datetime) VALUES(?, 'PARTY', ?, ?, current_timestamp())"; if (!db::preparedStmt(query, name, zoneId, rawMessage)) @@ -4696,17 +4777,20 @@ void SmallPacket0x0B5(map_session_data_t* const PSession, CCharEntity* const PCh // CharVar will self-expire and set to zero after the cooldown period PChar->setCharVar("[YELL]Cooldown", 1, CVanaTime::getInstance()->getSysTime() + yellCooldownTime); - int8 packetData[4]{}; - ref(packetData, 0) = PChar->id; - - message::send(MSG_CHAT_YELL, packetData, sizeof(packetData), std::make_unique(PChar, MESSAGE_YELL, (const char*)data[0x06])); + message::send(ipc::ChatMessageYell{ + .senderId = PChar->id, + .senderName = PChar->getName(), + .message = rawMessage, + .zoneId = PChar->getZone(), + .gmLevel = PChar->m_GMlevel, + }); if (settings::get("map.AUDIT_CHAT") && settings::get("map.AUDIT_YELL")) { // clang-format off - // NOTE: We capture rawMessage as a std::string because if we cast data[0x06] into a const char*, the underlying data might + // NOTE: We capture rawMessage as a copy because if we cast data[0x06] into a const char*, the underlying data might // : be gone by the time we action this lambda on the worker thread. - Async::getInstance()->submit([name = PChar->getName(), zoneId = PChar->getZone(), rawMessage = std::string((const char*)data[0x06])]() + Async::getInstance()->submit([name = PChar->getName(), zoneId = PChar->getZone(), rawMessage]() { const auto query = "INSERT INTO audit_chat (speaker, type, zoneid, message, datetime) VALUES(?, 'YELL', ?, ?, current_timestamp())"; if (!db::preparedStmt(query, name, zoneId, rawMessage)) @@ -4732,20 +4816,23 @@ void SmallPacket0x0B5(map_session_data_t* const PSession, CCharEntity* const PCh { if (PChar->PUnityChat != nullptr) { - int8 packetData[8]{}; - ref(packetData, 0) = PChar->PUnityChat->getLeader(); - ref(packetData, 4) = PChar->id; - message::send(MSG_CHAT_UNITY, packetData, sizeof(packetData), - std::make_unique(PChar, MESSAGE_UNITY, (const char*)data[0x06])); + message::send(ipc::ChatMessageUnity{ + .unityLeaderId = PChar->PUnityChat->getLeader(), + .senderId = PChar->id, + .senderName = PChar->getName(), + .message = rawMessage, + .zoneId = PChar->getZone(), + .gmLevel = PChar->m_GMlevel, + }); - roeutils::event(ROE_EVENT::ROE_UNITY_CHAT, PChar, RoeDatagram("unityMessage", (const char*)data[0x06])); + roeutils::event(ROE_EVENT::ROE_UNITY_CHAT, PChar, RoeDatagram("unityMessage", rawMessage)); if (settings::get("map.AUDIT_CHAT") && settings::get("map.AUDIT_UNITY")) { // clang-format off - // NOTE: We capture rawMessage as a std::string because if we cast data[0x06] into a const char*, the underlying data might + // NOTE: We capture rawMessage as a copy because if we cast data[0x06] into a const char*, the underlying data might // : be gone by the time we action this lambda on the worker thread. - Async::getInstance()->submit([name = PChar->getName(), zoneId = PChar->getZone(), unityLeader = PChar->PUnityChat->getLeader(), rawMessage = std::string((const char*)data[0x06])]() + Async::getInstance()->submit([name = PChar->getName(), zoneId = PChar->getZone(), unityLeader = PChar->PUnityChat->getLeader(), rawMessage]() { const auto query = "INSERT INTO audit_chat (speaker, type, zoneid, unity, message, datetime) VALUES(?, 'UNITY', ?, ?, ?, current_timestamp())"; if (!db::preparedStmt(query, name, zoneId, unityLeader, rawMessage)) @@ -4759,6 +4846,7 @@ void SmallPacket0x0B5(map_session_data_t* const PSession, CCharEntity* const PCh } break; } + PChar->m_charHistory.chatsSent++; } } @@ -4794,16 +4882,19 @@ void SmallPacket0x0B6(map_session_data_t* const PSession, CCharEntity* const PCh return; } - int8 packetData[64]{}; - strncpy((char*)packetData + 4, recipientName.c_str(), recipientName.length() + 1); - ref(packetData, 0) = PChar->id; - - message::send(MSG_CHAT_TELL, packetData, recipientName.length() + 5, std::make_unique(PChar, MESSAGE_TELL, message)); + message::send(ipc::ChatMessageTell{ + .senderId = PChar->id, + .senderName = PChar->getName(), + .recipientName = recipientName, + .message = message, + .zoneId = PChar->getZone(), + .gmLevel = PChar->m_GMlevel, + }); if (settings::get("map.AUDIT_CHAT") && settings::get("map.AUDIT_TELL")) { // clang-format off - // NOTE: We capture rawMessage as a std::string because if we cast data[0x06] into a const char*, the underlying data might + // NOTE: We capture rawMessage as a copy because if we cast data[0x06] into a const char*, the underlying data might // : be gone by the time we action this lambda on the worker thread. Async::getInstance()->submit([name = PChar->getName(), zoneId = PChar->getZone(), recipient = recipientName, message]() { @@ -4826,6 +4917,7 @@ void SmallPacket0x0B6(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0BE(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + uint8 operation = data.ref(0x05); switch (data.ref(0x04)) @@ -4904,6 +4996,7 @@ void SmallPacket0x0BE(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0BF(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + if (PChar->m_moghouseID) { JOBPOINT_TYPE jpType = static_cast(data.ref(0x04)); @@ -4926,6 +5019,7 @@ void SmallPacket0x0BF(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0C0(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + if (charutils::hasKeyItem(PChar, 2544)) { // Only send Job Points Packet if the player has unlocked them @@ -4942,6 +5036,7 @@ void SmallPacket0x0C0(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0C3(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + uint8 lsNum = data.ref(0x05); CItemLinkshell* PItemLinkshell = (CItemLinkshell*)PChar->getEquip(SLOT_LINK1); if (lsNum == 2) @@ -4972,6 +5067,7 @@ void SmallPacket0x0C3(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0C4(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + uint8 SlotID = data.ref(0x06); uint8 LocationID = data.ref(0x07); uint8 action = data.ref(0x08); @@ -5202,6 +5298,7 @@ void SmallPacket0x0CB(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0D2(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + // clang-format off PChar->ForAlliance([PChar](CBattleEntity* PPartyMember) { @@ -5223,6 +5320,7 @@ void SmallPacket0x0D2(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0D3(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + PChar->m_charHistory.gmCalls++; } @@ -5324,6 +5422,7 @@ struct GP_CLI_CONFIG void SmallPacket0x0DC(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + auto configUpdateData = data.as(); bool value = configUpdateData->SetFlg == 1; // 1 == on, 2 == off. What? @@ -5429,6 +5528,7 @@ void SmallPacket0x0DC(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0DD(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + uint32 id = data.ref(0x04); uint16 targid = data.ref(0x08); uint8 type = data.ref(0x0C); @@ -5600,6 +5700,7 @@ void SmallPacket0x0DD(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0DE(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + PChar->bazaar.message.clear(); PChar->bazaar.message.insert(0, (const char*)data[4], 120); // Maximum bazaar message limit: 120 characters @@ -5620,6 +5721,7 @@ void SmallPacket0x0DE(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0E0(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + char message[256]; _sql->EscapeString(message, (const char*)data[0x04]); @@ -5650,6 +5752,7 @@ void SmallPacket0x0E0(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0E1(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + uint8 slot = data.ref(0x07); if (slot == PChar->equip[SLOT_LINK1] && PChar->PLinkshell1) { @@ -5670,6 +5773,7 @@ void SmallPacket0x0E1(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0E2(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + CItemLinkshell* PItemLinkshell = (CItemLinkshell*)PChar->getEquip(SLOT_LINK1); if (PChar->PLinkshell1 != nullptr && (PItemLinkshell != nullptr && PItemLinkshell->isType(ITEM_LINKSHELL))) @@ -5722,6 +5826,7 @@ void SmallPacket0x0E2(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0E7(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + if (PChar->status != STATUS_TYPE::NORMAL) { return; @@ -5771,6 +5876,7 @@ void SmallPacket0x0E7(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0E8(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + if (PChar->status != STATUS_TYPE::NORMAL) { return; @@ -5866,6 +5972,7 @@ void SmallPacket0x0EA(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0EB(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + if (!PChar->isNpcLocked()) { return; @@ -5883,6 +5990,7 @@ void SmallPacket0x0EB(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0F1(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + uint16 IconID = data.ref(0x04); if (IconID) @@ -5900,6 +6008,7 @@ void SmallPacket0x0F1(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0F2(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + PChar->loc.boundary = data.ref(0x06); charutils::SaveCharPosition(PChar); @@ -5915,6 +6024,7 @@ void SmallPacket0x0F4(map_session_data_t* const PSession, CCharEntity* const PCh { TracyZoneScoped; TracyZoneCString("Wide Scan"); + PChar->loc.zone->WideScan(PChar, charutils::getWideScanRange(PChar)); } @@ -5927,6 +6037,7 @@ void SmallPacket0x0F4(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0F5(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + uint16 TargID = data.ref(0x04); CBaseEntity* target = PChar->GetEntity(TargID, TYPE_MOB | TYPE_NPC); @@ -5959,6 +6070,7 @@ void SmallPacket0x0F5(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0F6(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + PChar->WideScanTarget = std::nullopt; } @@ -6159,6 +6271,7 @@ void SmallPacket0x0FA(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0FB(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + uint16 ItemID = data.ref(0x04); if (ItemID == 0) @@ -6262,6 +6375,7 @@ void SmallPacket0x0FB(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0FC(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + uint16 potItemID = data.ref(0x04); uint16 itemID = data.ref(0x06); @@ -6360,6 +6474,7 @@ void SmallPacket0x0FC(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0FD(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + uint16 itemID = data.ref(0x04); if (itemID == 0) { @@ -6433,6 +6548,7 @@ void SmallPacket0x0FD(map_session_data_t* const PSession, CCharEntity* const PCh void SmallPacket0x0FE(map_session_data_t* const PSession, CCharEntity* const PChar, CBasicPacket& data) { TracyZoneScoped; + uint16 ItemID = data.ref(0x04); if (ItemID == 0) { diff --git a/src/map/packets/chat_message.cpp b/src/map/packets/chat_message.cpp index 48d0bc2e8b3..b8c2ff7f9de 100644 --- a/src/map/packets/chat_message.cpp +++ b/src/map/packets/chat_message.cpp @@ -54,3 +54,33 @@ CChatMessagePacket::CChatMessagePacket(CCharEntity* PChar, CHAT_MESSAGE_TYPE Mes std::memcpy(buffer_.data() + 0x08, &name[0], std::min(name.size(), (size_t)0xF)); std::memcpy(buffer_.data() + 0x17, &message[0], buffSize); } + +CChatMessagePacket::CChatMessagePacket(const std::string& name, uint16 zone, CHAT_MESSAGE_TYPE MessageType, const std::string& message, uint8 gmLevel) +{ + // there seems to be some sort of variable cap on the length of the packet, which I cannot determine + // (it changed when zoning, but not when zoning back) + // if you'd like to try and figure out what the cap is based on, the client side max message length is also + // variable in the same way, and is probably so under the same circumstances + // until that can be found, we'll just use the max length + auto buffSize = std::min(message.size(), 236); + + // Build the packet.. + // CBasicPacket::id(id); + this->setType(0x17); + + // 12 (base length / 2) + ((buffSize in chunks of 4) / 2) + // this->size = 12 + ((buffSize / 4) + 1) * 2; + this->setSize(0x104); + + ref(0x04) = MessageType; + + if (gmLevel >= 3 && name.empty()) + { + ref(0x05) = 0x01; + } + + ref(0x06) = zone; + + std::memcpy(buffer_.data() + 0x08, &name[0], std::min(name.size(), (size_t)0xF)); + std::memcpy(buffer_.data() + 0x17, &message[0], buffSize); +} diff --git a/src/map/packets/chat_message.h b/src/map/packets/chat_message.h index b3ae2f5a701..38338414b45 100644 --- a/src/map/packets/chat_message.h +++ b/src/map/packets/chat_message.h @@ -27,7 +27,7 @@ #include "basic.h" // Extracted from client. -enum CHAT_MESSAGE_TYPE +enum CHAT_MESSAGE_TYPE : uint8 { MESSAGE_SAY = 0, MESSAGE_SHOUT = 1, @@ -84,6 +84,7 @@ class CChatMessagePacket : public CBasicPacket public: static const uint16 id{ 0x17 }; CChatMessagePacket(CCharEntity* PChar, CHAT_MESSAGE_TYPE MessageType, std::string const& message, std::string const& sender = std::string()); + CChatMessagePacket(const std::string& name, uint16 zone, CHAT_MESSAGE_TYPE MessageType, const std::string& message, uint8 gmLevel = 0); }; #endif diff --git a/src/map/packets/message_standard.h b/src/map/packets/message_standard.h index c5f1196c255..0e1ea45eb31 100644 --- a/src/map/packets/message_standard.h +++ b/src/map/packets/message_standard.h @@ -29,11 +29,8 @@ // Valid MessageIDs for both standard and SYSTEM type messages // Found in ROM/27/76.dat or 1-27-76.xml if using mass extractor // Todo: move msg enums to common location out of packet headers -enum class MsgStd +enum class MsgStd : uint16 { - // Used as a sentinel value. This should not be used as part of a packet. - Unknown = -1, - // Keep message IDs in order OR ELSE UNSPECIFIED CONSQUENSES SHALL ENSUE CouldNotEnter = 2, // You could not enter the next area. [0,1,3,4, all same message] diff --git a/src/map/packets/party_invite.cpp b/src/map/packets/party_invite.cpp index b4addca5a2a..387fcf9ad4f 100644 --- a/src/map/packets/party_invite.cpp +++ b/src/map/packets/party_invite.cpp @@ -27,7 +27,7 @@ #include "entities/charentity.h" -CPartyInvitePacket::CPartyInvitePacket(uint32 id, uint16 targid, CCharEntity* PInviter, INVITETYPE InviteType) +CPartyInvitePacket::CPartyInvitePacket(uint32 id, uint16 targid, const std::string& inviterName, INVITETYPE InviteType) { this->setType(0xDC); this->setSize(0x20); @@ -37,5 +37,5 @@ CPartyInvitePacket::CPartyInvitePacket(uint32 id, uint16 targid, CCharEntity* PI ref(0x0B) = InviteType; - std::memcpy(buffer_.data() + 0x0C, PInviter->getName().c_str(), PInviter->getName().size()); + std::memcpy(buffer_.data() + 0x0C, inviterName.c_str(), inviterName.size()); } diff --git a/src/map/packets/party_invite.h b/src/map/packets/party_invite.h index 007be2b4bd1..7ba00b0d60f 100644 --- a/src/map/packets/party_invite.h +++ b/src/map/packets/party_invite.h @@ -29,7 +29,7 @@ enum INVITETYPE { INVITE_PARTY = 0, - INVITE_ALLIANCE = 5 + INVITE_ALLIANCE = 5, }; class CCharEntity; @@ -37,7 +37,7 @@ class CCharEntity; class CPartyInvitePacket : public CBasicPacket { public: - CPartyInvitePacket(uint32 id, uint16 targid, CCharEntity* PInviter, INVITETYPE InviteType); + CPartyInvitePacket(uint32 id, uint16 targid, const std::string& inviterName, INVITETYPE InviteType); }; #endif diff --git a/src/map/party.cpp b/src/map/party.cpp index 18751fa26e6..bd8408c001f 100644 --- a/src/map/party.cpp +++ b/src/map/party.cpp @@ -24,10 +24,10 @@ #include "alliance.h" #include "entities/battleentity.h" +#include "ipc_client.h" #include "job_points.h" #include "latent_effect_container.h" #include "map.h" -#include "message.h" #include "party.h" #include "status_effect_container.h" #include "treasure_pool.h" @@ -157,12 +157,12 @@ void CParty::DisbandParty(bool playerInitiated) _sql->Query("DELETE FROM accounts_parties WHERE charid = %u", PChar->id); } - // make sure chat server isn't notified of a disband if this came from the chat server already + // make sure message server isn't notified of a disband if this came from the message server already if (playerInitiated) { - uint8 data[4]{}; - ref(data, 0) = m_PartyID; - message::send(MSG_PT_DISBAND, data, sizeof(data), nullptr); + message::send(ipc::PartyDisband{ + .partyId = m_PartyID, + }); } } else if (m_PartyType == PARTY_MOBS) @@ -214,16 +214,17 @@ void CParty::AssignPartyRole(const std::string& MemberName, uint8 role) break; } - uint8 data[4]{}; if (m_PAlliance) { - ref(data, 0) = m_PAlliance->m_AllianceID; - message::send(MSG_ALLIANCE_RELOAD, data, sizeof(data), nullptr); + message::send(ipc::AllianceReload{ + .allianceId = m_PAlliance->m_AllianceID, + }); } else { - ref(data, 0) = m_PartyID; - message::send(MSG_PT_RELOAD, data, sizeof(data), nullptr); + message::send(ipc::PartyReload{ + .partyId = m_PartyID, + }); } } @@ -350,16 +351,17 @@ void CParty::RemoveMember(CBattleEntity* PEntity) _sql->Query("DELETE FROM accounts_parties WHERE charid = %u", PChar->id); - uint8 data[4]{}; if (m_PAlliance) { - ref(data, 0) = m_PAlliance->m_AllianceID; - message::send(MSG_ALLIANCE_RELOAD, data, sizeof(data), nullptr); + message::send(ipc::AllianceReload{ + .allianceId = m_PAlliance->m_AllianceID, + }); } else { - ref(data, 0) = m_PartyID; - message::send(MSG_PT_RELOAD, data, sizeof(data), nullptr); + message::send(ipc::PartyReload{ + .partyId = m_PartyID, + }); } if (PChar->PTreasurePool != nullptr && PChar->PTreasurePool->GetPoolType() != TREASUREPOOL_ZONE) @@ -613,16 +615,18 @@ void CParty::AddMember(CBattleEntity* PEntity) _sql->Query("INSERT INTO accounts_parties (charid, partyid, allianceid, partyflag) VALUES (%u, %u, %u, %u)", PChar->id, m_PartyID, allianceid, GetMemberFlags(PChar)); - uint8 data[4]{}; + if (m_PAlliance) { - ref(data, 0) = m_PAlliance->m_AllianceID; - message::send(MSG_ALLIANCE_RELOAD, data, sizeof(data), nullptr); + message::send(ipc::AllianceReload{ + .allianceId = m_PAlliance->m_AllianceID, + }); } else { - ref(data, 0) = m_PartyID; - message::send(MSG_PT_RELOAD, data, sizeof(data), nullptr); + message::send(ipc::PartyReload{ + .partyId = m_PartyID, + }); } ReloadTreasurePool(PChar); @@ -687,16 +691,18 @@ void CParty::AddMember(uint32 id) } _sql->Query("INSERT INTO accounts_parties (charid, partyid, allianceid, partyflag) VALUES (%u, %u, %u, %u)", id, m_PartyID, allianceid, Flags); - uint8 data[4]{}; + if (m_PAlliance) { - ref(data, 0) = m_PAlliance->m_AllianceID; - message::send(MSG_ALLIANCE_RELOAD, data, sizeof(data), nullptr); + message::send(ipc::AllianceReload{ + .allianceId = m_PAlliance->m_AllianceID, + }); } else { - ref(data, 0) = m_PartyID; - message::send(MSG_PT_RELOAD, data, sizeof(data), nullptr); + message::send(ipc::PartyReload{ + .partyId = m_PartyID, + }); } /*if (PChar->nameflags.flags & FLAG_INVITE) diff --git a/src/map/unitychat.cpp b/src/map/unitychat.cpp index 16a2bdc2de5..be39daf33ff 100644 --- a/src/map/unitychat.cpp +++ b/src/map/unitychat.cpp @@ -19,8 +19,8 @@ #include #include "entities/charentity.h" +#include "ipc_client.h" #include "map.h" -#include "message.h" #include "unitychat.h" #include "utils/jailutils.h" diff --git a/src/map/utils/charutils.cpp b/src/map/utils/charutils.cpp index 43d54c19037..286c58a27a2 100644 --- a/src/map/utils/charutils.cpp +++ b/src/map/utils/charutils.cpp @@ -77,11 +77,11 @@ #include "alliance.h" #include "conquest_system.h" #include "grades.h" +#include "ipc_client.h" #include "item_container.h" #include "latent_effect_container.h" #include "linkshell.h" #include "map.h" -#include "message.h" #include "mob_modifier.h" #include "recast_container.h" #include "roe.h" @@ -6659,7 +6659,13 @@ namespace charutils } PersistCharVar(charId, var, value, expiry); - message::send_charvar_update(charId, var, value, expiry); + + message::send(ipc::CharVarUpdate{ + .charId = charId, + .value = value, + .expiry = expiry, + .varName = var, + }); } void SetCharVar(CCharEntity* PChar, std::string const& var, int32 value, uint32 expiry /* = 0 */) diff --git a/src/map/zone.cpp b/src/map/zone.cpp index 17c693b7081..d925419a8c2 100644 --- a/src/map/zone.cpp +++ b/src/map/zone.cpp @@ -20,7 +20,7 @@ */ // TODO: -// It is necessary to divide the Czone class into basic and heirs. Already painted: Standard, Rezident, Instance and Dinamis +// It is necessary to divide the CZone class into basic and heirs. Already painted: Standard, Resident, Instance and Dynamis // Each of these zones has special behavior #include "zone.h" @@ -36,10 +36,10 @@ #include "battlefield.h" #include "common/vana_time.h" #include "enmity_container.h" +#include "ipc_client.h" #include "latent_effect_container.h" #include "linkshell.h" #include "map.h" -#include "message.h" #include "monstrosity.h" #include "notoriety_container.h" #include "party.h" @@ -109,6 +109,8 @@ CZone::CZone(ZONEID ZoneID, REGION_TYPE RegionID, CONTINENT_TYPE ContinentID, ui CZone::~CZone() { + TracyZoneScoped; + destroy(m_TreasurePool); destroy(m_zoneEntities); destroy(m_BattlefieldHandler); @@ -320,6 +322,7 @@ zoneLine_t* CZone::GetZoneLine(uint32 zoneLineID) void CZone::LoadZoneLines() { TracyZoneScoped; + static const char fmtQuery[] = "SELECT zoneline, tozone, tox, toy, toz, rotation FROM zonelines WHERE fromzone = %u"; int32 ret = _sql->Query(fmtQuery, m_zoneID); @@ -359,6 +362,7 @@ void CZone::LoadZoneLines() void CZone::LoadZoneWeather() { TracyZoneScoped; + static const char* Query = "SELECT weather FROM zone_weather WHERE zone = %u"; int32 ret = _sql->Query(Query, m_zoneID); @@ -386,6 +390,7 @@ void CZone::LoadZoneWeather() void CZone::LoadZoneSettings() { TracyZoneScoped; + static const char* Query = "SELECT " "zone.name," "zone.zoneip," @@ -441,6 +446,7 @@ void CZone::LoadZoneSettings() void CZone::LoadNavMesh() { TracyZoneScoped; + if (m_navMesh == nullptr) { m_navMesh = new CNavMesh((uint16)GetID()); @@ -459,6 +465,8 @@ void CZone::LoadNavMesh() void CZone::LoadZoneLos() { + TracyZoneScoped; + if (GetTypeMask() & ZONE_TYPE::CITY || (m_miscMask & MISC_LOS_OFF)) { // Skip cities and zones with line of sight turned off @@ -474,56 +482,26 @@ void CZone::LoadZoneLos() lineOfSight = ZoneLos::Load((uint16)GetID(), fmt::sprintf("losmeshes/%s.obj", getName())); } -/************************************************************************ - * * - * Add a MOB to the zone * - * * - ************************************************************************/ - void CZone::InsertMOB(CBaseEntity* PMob) { m_zoneEntities->InsertMOB(PMob); } -/************************************************************************ - * * - * Add an NPC to the zone * - * * - ************************************************************************/ - void CZone::InsertNPC(CBaseEntity* PNpc) { m_zoneEntities->InsertNPC(PNpc); } -/************************************************************************ - * * - * Add a PET to the zone (free targid 0x700-0x7FF) * - * * - ************************************************************************/ - void CZone::InsertPET(CBaseEntity* PPet) { m_zoneEntities->InsertPET(PPet); } -/************************************************************************ - * * - * Add a trust to the zone * - * * - ************************************************************************/ - void CZone::InsertTRUST(CBaseEntity* PTrust) { m_zoneEntities->InsertTRUST(PTrust); } -/************************************************************************ - * * - * Add a trigger area to the zone * - * * - ************************************************************************/ - void CZone::InsertTriggerArea(std::unique_ptr&& triggerArea) { if (triggerArea != nullptr) @@ -542,15 +520,10 @@ void CZone::InsertTriggerArea(std::unique_ptr&& triggerArea) void CZone::FindPartyForMob(CBaseEntity* PEntity) { TracyZoneScoped; + m_zoneEntities->FindPartyForMob(PEntity); } -/************************************************************************ - * * - * The ship/boat is leaving, necessary to collect passengers * - * * - ************************************************************************/ - void CZone::TransportDepart(uint16 boundary, uint16 zone) { m_zoneEntities->TransportDepart(boundary, zone); @@ -558,6 +531,8 @@ void CZone::TransportDepart(uint16 boundary, uint16 zone) void CZone::updateCharLevelRestriction(CCharEntity* PChar) { + TracyZoneScoped; + if (PChar->StatusEffectContainer->HasStatusEffect(EFFECT_LEVEL_RESTRICTION)) { // If the level restriction is already the same then no need to change it @@ -588,6 +563,7 @@ void CZone::updateCharLevelRestriction(CCharEntity* PChar) void CZone::SetWeather(WEATHER weather) { TracyZoneScoped; + if (weather >= MAX_WEATHER_ID) { ShowWarning("Weather value (%d) exceeds MAX_WEATHER_ID.", weather); @@ -702,6 +678,7 @@ void CZone::UpdateWeather() void CZone::DecreaseZoneCounter(CCharEntity* PChar) { TracyZoneScoped; + m_zoneEntities->DecreaseZoneCounter(PChar); if (m_zoneEntities->CharListEmpty()) @@ -754,27 +731,11 @@ void CZone::IncreaseZoneCounter(CCharEntity* PChar) CharZoneIn(PChar); } -/************************************************************************ - * * - * Check the visibility of monsters by a character. It is better to * - * keep the distance in global variable (server settings). It is in * - * this function that the aggression of monsters is checked so that * - * distance is not calculated several times (e.g. in ZoneServer) * - * * - ************************************************************************/ - void CZone::SpawnMOBs(CCharEntity* PChar) { m_zoneEntities->SpawnMOBs(PChar); } -/************************************************************************ - * * - * Check the visibility of pets by a character. For the adding of pets * - * use UPDATE instead of SPAWN. SPAWN is only used when calling. * - * * - ************************************************************************/ - void CZone::SpawnPETs(CCharEntity* PChar) { m_zoneEntities->SpawnPETs(PChar); @@ -785,48 +746,21 @@ void CZone::SpawnTRUSTs(CCharEntity* PChar) m_zoneEntities->SpawnTRUSTs(PChar); } -/************************************************************************ - * * - * Check the visibility of NPCs by a character. * - * * - ************************************************************************/ - void CZone::SpawnNPCs(CCharEntity* PChar) { m_zoneEntities->SpawnNPCs(PChar); } -/************************************************************************ - * * - * Check the visibility of other characters by a character. The point * - * of this action is that the characters update themselves and are * - * added to the lists of other characters. Originally, the list size * - * was limited to/changed to within 25-50 visible characters. * - * * - ************************************************************************/ - void CZone::SpawnPCs(CCharEntity* PChar) { m_zoneEntities->SpawnPCs(PChar); } -/************************************************************************ - * * - * Displaying Moogle in MogHouse, etc. * - * * - ************************************************************************/ - void CZone::SpawnConditionalNPCs(CCharEntity* PChar) { m_zoneEntities->SpawnConditionalNPCs(PChar); } -/************************************************************************ - * * - * Displaying ships/boats in the zone (not stored in the main list). * - * * - ************************************************************************/ - void CZone::SpawnTransport(CCharEntity* PChar) { m_zoneEntities->SpawnTransport(PChar); @@ -846,6 +780,7 @@ CBaseEntity* CZone::GetEntity(uint16 targid, uint8 filter) void CZone::TOTDChange(TIMETYPE TOTD) { TracyZoneScoped; + m_zoneEntities->TOTDChange(TOTD); luautils::OnTOTDChange(m_zoneID, TOTD); @@ -853,6 +788,8 @@ void CZone::TOTDChange(TIMETYPE TOTD) void CZone::SavePlayTime() { + TracyZoneScoped; + m_zoneEntities->SavePlayTime(); } @@ -869,18 +806,21 @@ CCharEntity* CZone::GetCharByID(uint32 id) void CZone::PushPacket(CBaseEntity* PEntity, GLOBAL_MESSAGE_TYPE message_type, const std::unique_ptr& packet) { TracyZoneScoped; + m_zoneEntities->PushPacket(PEntity, message_type, packet); } void CZone::UpdateEntityPacket(CBaseEntity* PEntity, ENTITYUPDATE type, uint8 updatemask, bool alwaysInclude) { TracyZoneScoped; + m_zoneEntities->UpdateEntityPacket(PEntity, type, updatemask, alwaysInclude); } void CZone::WideScan(CCharEntity* PChar, uint16 radius) { TracyZoneScoped; + m_zoneEntities->WideScan(PChar, radius); } @@ -1210,7 +1150,6 @@ bool CZone::IsZoneActive() const CZoneEntities* CZone::GetZoneEntities() { - TracyZoneScoped; return m_zoneEntities; } diff --git a/src/world/besieged_system.h b/src/world/besieged_system.h index 80f1b077d86..814614f64de 100644 --- a/src/world/besieged_system.h +++ b/src/world/besieged_system.h @@ -23,14 +23,23 @@ #include "message_handler.h" +#include "world_server.h" + class BesiegedSystem : public IMessageHandler { public: - BesiegedSystem() = default; + BesiegedSystem(WorldServer& worldServer) + : worldServer_(worldServer) + { + } + ~BesiegedSystem() = default; - bool handleMessage(HandleableMessage&& message) override + bool handleMessage(uint8 messageType, HandleableMessage&& message) override { return false; } + +private: + WorldServer& worldServer_; }; diff --git a/src/world/campaign_system.h b/src/world/campaign_system.h index 10d6a76bb02..24bb05fdf2a 100644 --- a/src/world/campaign_system.h +++ b/src/world/campaign_system.h @@ -23,14 +23,23 @@ #include "message_handler.h" +#include "world_server.h" + class CampaignSystem : public IMessageHandler { public: - CampaignSystem() = default; + CampaignSystem(WorldServer& worldServer) + : worldServer_(worldServer) + { + } + ~CampaignSystem() = default; - bool handleMessage(HandleableMessage&& message) override + bool handleMessage(uint8 messageType, HandleableMessage&& message) override { return false; } + +private: + WorldServer& worldServer_; }; diff --git a/src/world/colonization_system.h b/src/world/colonization_system.h index 1b5988f6d7b..004c44f4d86 100644 --- a/src/world/colonization_system.h +++ b/src/world/colonization_system.h @@ -23,14 +23,23 @@ #include "message_handler.h" +#include "world_server.h" + class ColonizationSystem : public IMessageHandler { public: - ColonizationSystem() = default; + ColonizationSystem(WorldServer& worldServer) + : worldServer_(worldServer) + { + } + ~ColonizationSystem() = default; - bool handleMessage(HandleableMessage&& message) override + bool handleMessage(uint8 messageType, HandleableMessage&& message) override { return false; } + +private: + WorldServer& worldServer_; }; diff --git a/src/world/conquest_system.cpp b/src/world/conquest_system.cpp index 7688b049e38..49b12fc4d15 100644 --- a/src/world/conquest_system.cpp +++ b/src/world/conquest_system.cpp @@ -21,26 +21,34 @@ #include "conquest_system.h" -#include "message_server.h" +#include "ipc_server.h" #include "common/database.h" -ConquestSystem::ConquestSystem() +ConquestSystem::ConquestSystem(WorldServer& worldServer) +: worldServer_(worldServer) { } -bool ConquestSystem::handleMessage(HandleableMessage&& message) +bool ConquestSystem::handleMessage(uint8 messageType, HandleableMessage&& message) { - const uint8 conquestMsgType = message.payload[1]; + const auto conquestMsgType = static_cast(messageType); switch (conquestMsgType) { - case CONQUESTMSGTYPE::CONQUEST_MAP2WORLD_GM_WEEKLY_UPDATE: + case ConquestMessage::M2W_GM_WeeklyUpdate: { updateWeekConquest(); return true; } break; - case CONQUESTMSGTYPE::CONQUEST_MAP2WORLD_ADD_INFLUENCE_POINTS: + case ConquestMessage::M2W_GM_ConquestUpdate: + { + // Send influence data to the requesting map server + sendInfluencesMsg(true, message.ipp.getIPP()); + return true; + } + break; + case ConquestMessage::M2W_AddInfluencePoints: { int32 points = 0; uint32 nation = 0; @@ -56,23 +64,11 @@ bool ConquestSystem::handleMessage(HandleableMessage&& message) return true; } break; - case CONQUESTMSGTYPE::CONQUEST_MAP2WORLD_GM_CONQUEST_UPDATE: - { - // Convert from_addr to ip + port - uint64 ipp = message.from_addr.s_addr; - ipp |= (((uint64)message.from_port) << 32); - - // Send influence data to the requesting map server - sendInfluencesMsg(true, ipp); - return true; - } - break; default: { - ShowDebug(fmt::format("Message: unknown conquest type received: {} from {}:{}", - static_cast(conquestMsgType), - message.from_addr.s_addr, - message.from_port)); + ShowDebug(fmt::format("Message: unknown conquest type message received: {} from {}", + conquestMsgType, + message.ipp.toString())); } break; } @@ -83,59 +79,32 @@ bool ConquestSystem::handleMessage(HandleableMessage&& message) void ConquestSystem::sendTallyStartMsg() { // 1- Send message to all zones. We are starting update. - const std::size_t dataLen = 2 * sizeof(uint8); - uint8 data[2 * sizeof(uint8) + sizeof(uint32)]{}; - - // Create ZMQ message with header and no other payload - ref((uint8*)data, 0) = REGIONAL_EVT_MSG_CONQUEST; - ref((uint8*)data, 1) = CONQUEST_WORLD2MAP_WEEKLY_UPDATE_START; - // Send to map - zmq::message_t dataMsg = zmq::message_t(dataLen); - memcpy(dataMsg.data(), data, dataLen); - queue_message_broadcast(MSG_WORLD2MAP_REGIONAL_EVENT, &dataMsg); + // queue_message_broadcast(MSG_WORLD2MAP_REGIONAL_EVENT, &dataMsg); + worldServer_.ipcServer_->sendMessage(IPP(), + ipc::RegionalEvent{ + .type = RegionalEventType::Conquest, + .subType = ConquestMessage::W2M_WeeklyUpdateStart, + // No payload + }); } void ConquestSystem::sendInfluencesMsg(bool shouldUpdateZones, uint64 ipp) { - auto influences = getRegionalInfluences(); - - // Base length is the type + subtype + influence size - const std::size_t headerLength = 2 * sizeof(uint8) + sizeof(std::size_t) + sizeof(bool); - const std::size_t dataLen = headerLength + sizeof(influence_t) * influences.size(); - const uint8* data = new uint8[dataLen]; - - // Regional event type + conquest msg type - ref((uint8*)data, 0) = REGIONAL_EVT_MSG_CONQUEST; - ref((uint8*)data, 1) = CONQUEST_WORLD2MAP_INFLUENCE_POINTS; - ref((uint8*)data, 2) = shouldUpdateZones; - - // Influences controls array - ref((uint8*)data, 3) = influences.size(); - for (std::size_t i = 0; i < influences.size(); i++) - { - // Everything is offset by i*size of region control struct + headerLength - const std::size_t start = headerLength + i * sizeof(influence_t); - ref((uint8*)data, start) = influences[i].sandoria_influence; - ref((uint8*)data, start + 2) = influences[i].bastok_influence; - ref((uint8*)data, start + 4) = influences[i].windurst_influence; - ref((uint8*)data, start + 6) = influences[i].beastmen_influence; - } - - // 3- Create ZMQ Message and queue it - zmq::message_t dataMsg = zmq::message_t(dataLen); - memcpy(dataMsg.data(), data, dataLen); - if (ipp == 0xFFFF) - { - queue_message_broadcast(MSG_WORLD2MAP_REGIONAL_EVENT, &dataMsg); - } - else - { - queue_message(ipp, MSG_WORLD2MAP_REGIONAL_EVENT, &dataMsg); - } + const auto influences = getRegionalInfluences(); + + worldServer_.ipcServer_->sendMessage(IPP(ipp), + ipc::RegionalEvent{ + .type = RegionalEventType::Conquest, + .subType = ConquestMessage::W2M_BroadcastInfluencePoints, + .payload = ipc::toBytes(ConquestInfluenceUpdate{ + .shouldUpdateZones = shouldUpdateZones, + .influences = influences, + }), + }); } -void ConquestSystem::sendRegionControlsMsg(CONQUESTMSGTYPE msgType, uint64 ipp) +void ConquestSystem::sendRegionControlsMsg(ConquestMessage msgType, uint64 ipp) { // 2- Serialize regional controls with the following schema: // - REGIONALMSGTYPE @@ -144,7 +113,7 @@ void ConquestSystem::sendRegionControlsMsg(CONQUESTMSGTYPE msgType, uint64 ipp) // - For N elements we have: // - current control (uint8) // - prev control (uint8) - auto regionControls = getRegionControls(); + const auto regionControls = getRegionControls(); // Header length is the type + subtype + region control size + size of the size_t const std::size_t headerLength = 2 * sizeof(uint8) + sizeof(std::size_t); @@ -152,7 +121,7 @@ void ConquestSystem::sendRegionControlsMsg(CONQUESTMSGTYPE msgType, uint64 ipp) const uint8* data = new uint8[dataLen]; // Regional event type + conquest msg type - ref((uint8*)data, 0) = REGIONAL_EVT_MSG_CONQUEST; + ref((uint8*)data, 0) = RegionalEventType::Conquest; ref((uint8*)data, 1) = msgType; // Region controls array @@ -171,11 +140,11 @@ void ConquestSystem::sendRegionControlsMsg(CONQUESTMSGTYPE msgType, uint64 ipp) if (ipp == 0xFFFF) { - queue_message_broadcast(MSG_WORLD2MAP_REGIONAL_EVENT, &dataMsg); + // queue_message_broadcast(MSG_WORLD2MAP_REGIONAL_EVENT, &dataMsg); } else { - queue_message(ipp, MSG_WORLD2MAP_REGIONAL_EVENT, &dataMsg); + // queue_message(ipp, MSG_WORLD2MAP_REGIONAL_EVENT, &dataMsg); } } @@ -251,7 +220,7 @@ void ConquestSystem::updateWeekConquest() } // 3- Send tally end Msg - sendRegionControlsMsg(CONQUEST_WORLD2MAP_WEEKLY_UPDATE_END); + sendRegionControlsMsg(ConquestMessage::W2M_WeeklyUpdateEnd); } void ConquestSystem::updateHourlyConquest() @@ -266,9 +235,7 @@ void ConquestSystem::updateVanaHourlyConquest() auto ConquestSystem::getRegionalInfluences() -> std::vector const { - auto query = "SELECT sandoria_influence, bastok_influence, windurst_influence, beastmen_influence FROM conquest_system"; - - auto rset = db::preparedStmt(query); + const auto rset = db::preparedStmt("SELECT sandoria_influence, bastok_influence, windurst_influence, beastmen_influence FROM conquest_system"); std::vector influences; if (rset && rset->rowsCount()) @@ -289,9 +256,7 @@ auto ConquestSystem::getRegionalInfluences() -> std::vector const auto ConquestSystem::getRegionControls() -> std::vector const { - auto query = "SELECT region_control, region_control_prev FROM conquest_system"; - - auto rset = db::preparedStmt(query); + const auto rset = db::preparedStmt("SELECT region_control, region_control_prev FROM conquest_system"); std::vector controllers; if (rset && rset->rowsCount()) diff --git a/src/world/conquest_system.h b/src/world/conquest_system.h index 13d521d73ee..0d5abbaf203 100644 --- a/src/world/conquest_system.h +++ b/src/world/conquest_system.h @@ -21,24 +21,31 @@ #pragma once +#include "message_handler.h" + +#include "world_server.h" + +#include "common/regional_event.h" + #include "map/conquest_system.h" #include "map/zone.h" -#include "message_handler.h" -/** - * Conquest System on the world server. - * This class handles all the DB updates as a response to map server updates. - */ +class IPCServer; + +// +// Conquest System on the world server. +// This class handles all the DB updates as a response to map server updates. +// class ConquestSystem : public IMessageHandler { public: - ConquestSystem(); + ConquestSystem(WorldServer& worldServer); ~ConquestSystem() override = default; /** * IMessageHandler implementation. Used to handle messages from message_server. */ - bool handleMessage(HandleableMessage&& message) override; + bool handleMessage(uint8 messageType, HandleableMessage&& message) override; /** * Called weekly, updates conquest data and sends regional control information @@ -66,5 +73,7 @@ class ConquestSystem : public IMessageHandler void sendTallyStartMsg(); void sendInfluencesMsg(bool shouldUpdateZones, uint64 ipp = 0xFFFF); - void sendRegionControlsMsg(CONQUESTMSGTYPE msgType, uint64 ipp = 0xFFFF); + void sendRegionControlsMsg(ConquestMessage msgType, uint64 ipp = 0xFFFF); + + WorldServer& worldServer_; }; diff --git a/src/world/ipc_server.cpp b/src/world/ipc_server.cpp index a696120fc04..fde7df2b3cb 100644 --- a/src/world/ipc_server.cpp +++ b/src/world/ipc_server.cpp @@ -2,6 +2,7 @@ =========================================================================== Copyright (c) 2010-2015 Darkstar Dev Teams + Copyright (c) 2025 LandSandBoat Dev Teams This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,6 +20,14 @@ =========================================================================== */ +#include "ipc_server.h" + +#include "besieged_system.h" +#include "campaign_system.h" +#include "colonization_system.h" +#include "conquest_system.h" +#include "world_server.h" + #include #include #include @@ -26,424 +35,529 @@ #include "common/database.h" #include "common/logging.h" -#include "conquest_system.h" -#include "message_server.h" +#include "common/regional_event.h" -struct chat_message_t +namespace { - uint64 dest = 0; - MSGSERVTYPE type{}; - zmq::message_t data{}; - zmq::message_t packet{}; -}; + auto getZMQEndpointString() -> std::string + { + return fmt::format("tcp://{}:{}", settings::get("network.ZMQ_IP"), settings::get("network.ZMQ_PORT")); + } +} // namespace -struct zone_settings_t +IPCServer::IPCServer(WorldServer& worldServer) +: worldServer_(worldServer) +, zmqRouterWrapper_(getZMQEndpointString()) { - uint16 zoneid = 0; - uint64 ipp = 0; - uint32 misc = 0; -}; + TracyZoneScoped; +} -namespace +auto IPCServer::getIPPForCharId(uint32 charId) -> std::optional { - zmq::context_t zContext; - std::unique_ptr zSocket; - - moodycamel::ConcurrentQueue outgoing_queue; - moodycamel::ConcurrentQueue external_processing_queue; - - std::unordered_map zoneSettingsMap; - std::vector mapEndpoints; - std::vector yellMapEndpoints; -} // namespace + TracyZoneScoped; -void queue_message(uint64 ipp, MSGSERVTYPE type, zmq::message_t* extra, zmq::message_t* packet) -{ - chat_message_t msg; - msg.dest = ipp; - msg.type = type; + // TODO: We know when chars move, we could be caching this information - msg.data.copy(*extra); - if (packet != nullptr) + const auto rset = db::preparedStmt("SELECT server_addr, server_port FROM accounts_sessions WHERE charid = ? LIMIT 1", charId); + if (rset && rset->rowsCount() && rset->next()) { - msg.packet.copy(*packet); + const auto ip = rset->get("server_addr"); + const auto port = rset->get("server_port"); + return IPP(ip, port); } - outgoing_queue.enqueue(std::move(msg)); + return std::nullopt; } -void queue_message_broadcast(MSGSERVTYPE type, zmq::message_t* extra, zmq::message_t* packet) +auto IPCServer::getIPPForCharName(const std::string& charName) -> std::optional { - for (const auto& ipp : mapEndpoints) + TracyZoneScoped; + + // TODO: We know when chars move, we could be caching this information + + const auto rset = db::preparedStmt("SELECT server_addr, server_port FROM accounts_sessions LEFT JOIN chars ON " + "accounts_sessions.charid = chars.charid WHERE charname = ? LIMIT 1", + charName); + if (rset && rset->rowsCount() && rset->next()) { - queue_message(ipp, type, extra, packet); + const auto ip = rset->get("server_addr"); + const auto port = rset->get("server_port"); + return IPP(ip, port); } -} -auto pop_external_processing_message() -> std::optional -{ - HandleableMessage out; - return external_processing_queue.try_dequeue(out) ? std::optional(out) : std::nullopt; + return std::nullopt; } -void message_server_send(uint64 ipp, MSGSERVTYPE type, zmq::message_t* extra, zmq::message_t* packet) +auto IPCServer::getIPPForZoneId(uint16 zoneId) -> std::optional { - try - { - std::array msgs; - msgs[0] = zmq::message_t(&ipp, sizeof(ipp)); - msgs[1] = zmq::message_t(&type, sizeof(type)); - msgs[2].copy(*extra); - msgs[3].copy(*packet); - zmq::send_multipart(*zSocket, msgs); - } - catch (zmq::error_t& e) + TracyZoneScoped; + + if (const auto it = zoneSettings_.zoneSettingsMap_.find(zoneId); it != zoneSettings_.zoneSettingsMap_.end()) { - ShowError(fmt::format("Message: {}", e.what())); + return it->second.ipp; } + + return std::nullopt; } -std::string ipp_to_string(uint64 ipp) +auto IPCServer::getIPPsForParty(uint32 partyId) -> std::vector { - // Ip / port pair is stored as uint32 port MSB and uint32 Ip LSB - uint32 port = (uint32)(ipp >> 32); - uint32 ip = (uint32)(ipp); - in_addr target{}; - target.s_addr = ip; - char target_address[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, &target, target_address, INET_ADDRSTRLEN); + TracyZoneScoped; - // This is internal, so we can trust it. - return fmt::format("{}:{}", asStringFromUntrustedSource(target_address), port); -} + // TODO: We know when chars move, we could be caching this infor -void message_server_parse(MSGSERVTYPE type, zmq::message_t* extra, zmq::message_t* packet, zmq::message_t* from) -{ - in_addr from_ip{}; - uint16 from_port = 0; - char from_address[INET_ADDRSTRLEN]; + // TODO: Simplify query now that there's alliance versions? + const auto query = "SELECT server_addr, server_port, MIN(charid) FROM accounts_sessions JOIN accounts_parties USING (charid) " + "WHERE IF (allianceid <> 0, allianceid = (SELECT MAX(allianceid) FROM accounts_parties WHERE partyid = ?), " + "partyid = ?) GROUP BY server_addr, server_port"; - if (from) + const auto rset = db::preparedStmt(query, partyId, partyId); + if (rset && rset->rowsCount()) { - from_ip.s_addr = ref((uint8*)from->data(), 0); - from_port = ref((uint8*)from->data(), 4); - inet_ntop(AF_INET, &from_ip, from_address, INET_ADDRSTRLEN); + std::vector ippList; + while (rset->next()) + { + const auto ip = rset->get("server_addr"); + const auto port = rset->get("server_port"); + ippList.emplace_back(ip, port); + } + + return ippList; } - auto forward_message = [&](std::unique_ptr&& rset) + return {}; +} + +auto IPCServer::getIPPsForAlliance(uint32 allianceId) -> std::vector +{ + TracyZoneScoped; + + // TODO: We know when chars move, we could be caching this infor + + const auto query = "SELECT server_addr, server_port, MIN(charid) FROM accounts_sessions JOIN accounts_parties USING (charid) " + "WHERE allianceid = ? " + "GROUP BY server_addr, server_port"; + + const auto rset = db::preparedStmt(query, allianceId); + if (rset && rset->rowsCount()) { - // This is only used for cases where SQL is used to get the IPs (not cached). - // E.g: When we get ips for a specific account_session. - // Consider moving this logic to a helper method and call it from each case instead. - if (!rset) + std::vector ippList; + while (rset->next()) { - return; + const auto ip = rset->get("server_addr"); + const auto port = rset->get("server_port"); + ippList.emplace_back(ip, port); } - // This is internal, so we can trust it. - ShowDebug(fmt::format("Message: Received message {} ({}) from {}:{}", - msgTypeToStr(type), static_cast(type), asStringFromUntrustedSource(from_address), from_port)); + return ippList; + } - while (rset->next()) - { - uint64 ip = rset->get("server_addr"); - uint64 port = rset->get("server_port"); - uint64 ipp = ip | (port << 32); - std::string ipString = ipp_to_string(ipp); + return {}; +} - ShowDebug(fmt::format("Message: -> rerouting to {}", ipString)); - message_server_send(ipp, type, extra, packet); - } - }; +auto IPCServer::getIPPsForLinkshell(uint32 linkshellId) -> std::vector +{ + TracyZoneScoped; + + // TODO: We know when chars move, we could be caching this infor + + const auto query = "SELECT server_addr, server_port FROM accounts_sessions " + "WHERE linkshellid1 = ? OR linkshellid2 = ? GROUP BY server_addr, server_port"; - switch (type) + const auto rset = db::preparedStmt(query, linkshellId, linkshellId); + if (rset && rset->rowsCount()) { - case MSG_CHAT_TELL: - case MSG_LINKSHELL_RANK_CHANGE: - case MSG_LINKSHELL_REMOVE: - case MSG_CHARVAR_UPDATE: + std::vector ippList; + while (rset->next()) { - const char* query = "SELECT server_addr, server_port FROM accounts_sessions LEFT JOIN chars ON " - "accounts_sessions.charid = chars.charid WHERE charname = ? LIMIT 1"; - - // This is going straight into a prepared statement, so we can trust it. - auto rset = db::preparedStmt(query, asStringFromUntrustedSource((uint8*)extra->data() + 4)); - if (rset) - { - forward_message(std::move(rset)); - } - else - { - query = "SELECT server_addr, server_port FROM accounts_sessions WHERE charid = ? LIMIT 1"; - - uint32 charId = ref((uint8*)extra->data(), 0); - forward_message(db::preparedStmt(query, charId)); - } - break; + const auto ip = rset->get("server_addr"); + const auto port = rset->get("server_port"); + ippList.emplace_back(ip, port); } - case MSG_CHAT_PARTY: - case MSG_PT_RELOAD: - case MSG_PT_DISBAND: - { - // TODO: simplify query now that there's alliance versions? - const char* query = "SELECT server_addr, server_port, MIN(charid) FROM accounts_sessions JOIN accounts_parties USING (charid) " - "WHERE IF (allianceid <> 0, allianceid = (SELECT MAX(allianceid) FROM accounts_parties WHERE partyid = ?), " - "partyid = ?) GROUP BY server_addr, server_port"; - uint32 partyid = ref((uint8*)extra->data(), 0); - forward_message(db::preparedStmt(query, partyid, partyid)); - break; - } - case MSG_CHAT_ALLIANCE: - case MSG_ALLIANCE_RELOAD: - case MSG_ALLIANCE_DISSOLVE: - { - const char* query = "SELECT server_addr, server_port, MIN(charid) FROM accounts_sessions JOIN accounts_parties USING (charid) " - "WHERE allianceid = ? " - "GROUP BY server_addr, server_port"; + return ippList; + } - uint32 allianceid = ref((uint8*)extra->data(), 0); - forward_message(db::preparedStmt(query, allianceid)); - break; - } - case MSG_CHAT_LINKSHELL: - { - const char* query = "SELECT server_addr, server_port FROM accounts_sessions " - "WHERE linkshellid1 = ? OR linkshellid2 = ? GROUP BY server_addr, server_port"; + return {}; +} - uint32 linkshellId = ref((uint8*)extra->data(), 0); - forward_message(db::preparedStmt(query, linkshellId, linkshellId)); - break; - } - case MSG_CHAT_UNITY: - { - const char* query = "SELECT server_addr, server_port FROM accounts_sessions " - "WHERE unitychat = ? GROUP BY server_addr, server_port"; +auto IPCServer::getIPPsForUnity(uint32 unityId) -> std::vector +{ + TracyZoneScoped; - uint32 unityId = ref((uint8*)extra->data(), 0); - forward_message(db::preparedStmt(query, unityId)); - break; - } - case MSG_CHAT_YELL: - { - for (const auto& ipp : yellMapEndpoints) - { - ShowDebug(fmt::format("Message: -> rerouting to {}", ipp_to_string(ipp))); - message_server_send(ipp, type, extra, packet); - } - break; - } - case MSG_CHAT_SERVMES: - { - for (const auto& ipp : mapEndpoints) - { - ShowDebug(fmt::format("Message: -> rerouting to {}", ipp_to_string(ipp))); - message_server_send(ipp, type, extra, packet); - } - break; - } - case MSG_PT_INVITE: - case MSG_PT_INV_RES: - case MSG_PLAYER_KICK: - case MSG_DIRECT: - case MSG_SEND_TO_ZONE: - { - const char* query = "SELECT server_addr, server_port FROM accounts_sessions WHERE charid = ?"; + // TODO: We know when chars move, we could be caching this infor - uint32 charId = ref((uint8*)extra->data(), 0); - forward_message(db::preparedStmt(query, charId)); - break; - } - case MSG_SEND_TO_ENTITY: - case MSG_LUA_FUNCTION: - case MSG_RPC_SEND: - case MSG_RPC_RECV: - { - uint16 zoneId = ref((uint8*)extra->data(), 2); - try - { - auto zoneSettings = zoneSettingsMap.at(zoneId); - ShowDebug(fmt::format("Message: -> rerouting to {}", ipp_to_string(zoneSettings.ipp))); - message_server_send(zoneSettings.ipp, type, extra, packet); - } - catch (const std::out_of_range&) - { - ShowError("Requested ZoneId not in cache: %d", zoneId); - } + const auto query = "SELECT server_addr, server_port FROM accounts_sessions " + "WHERE unitychat = ? GROUP BY server_addr, server_port"; - break; - } - case MSG_LOGIN: + const auto rset = db::preparedStmt(query, unityId); + if (rset && rset->rowsCount()) + { + std::vector ippList; + while (rset->next()) { - // no op - break; + const auto ip = rset->get("server_addr"); + const auto port = rset->get("server_port"); + ippList.emplace_back(ip, port); } - case MSG_KILL_SESSION: - { - uint32 charid = ref((uint8*)extra->data(), 0); - const char* query = "SELECT pos_prevzone, pos_zone from chars where charid = ? LIMIT 1"; - auto rset = db::preparedStmt(query, charid); - - // Get zone ID from query and try to send to _just_ the previous zone - if (rset && rset->rowsCount() && rset->next()) - { - uint32 prevZoneID = rset->get("pos_prevzone"); - uint32 nextZoneID = rset->get("pos_zone"); - - if (prevZoneID != nextZoneID) - { - auto zoneSettings = zoneSettingsMap.at(prevZoneID); - - ShowDebug(fmt::format("Message: -> rerouting to {}", ipp_to_string(zoneSettings.ipp))); - message_server_send(zoneSettings.ipp, type, extra, packet); - } - } - else - { - for (const auto& ipp : mapEndpoints) - { - ShowDebug(fmt::format("Message: -> rerouting to {}", ipp_to_string(ipp))); - message_server_send(ipp, type, extra, packet); - } - } - break; - } - case MSG_MAP2WORLD_REGIONAL_EVENT: - { - uint8* data = (uint8*)extra->data(); + return ippList; + } - // Create a copy - std::vector bytes(data, data + extra->size()); + return {}; +} - external_processing_queue.enqueue(HandleableMessage{ bytes, from_ip, from_port }); - break; - } - default: - { - // This is internal, so we can trust it. - ShowDebug(fmt::format("Message: unknown type received: {} from {}:{}", static_cast(type), asStringFromUntrustedSource(from_address), from_port)); - break; - } +void IPCServer::rerouteMessageToCharId(uint32 charId, const auto& message) +{ + TracyZoneScoped; + + if (const auto maybeCharIPP = getIPPForCharId(charId)) + { + const auto charIPP = *maybeCharIPP; + ShowDebugFmt("Message: -> rerouting to char<{}> on {}", charId, charIPP.toString()); + sendMessage(charIPP, std::move(message)); } } -void message_server_listen(bool const& requestExit) +void IPCServer::rerouteMessageToCharName(const std::string& charName, const auto& message) { - while (!requestExit) + TracyZoneScoped; + + if (const auto maybeCharIPP = getIPPForCharName(charName)) { - std::array msgs; - try - { - const auto ret = zmq::recv_multipart_n(*zSocket, msgs.data(), msgs.size()); - if (!ret) - { - chat_message_t msg; - while (outgoing_queue.try_dequeue(msg)) - { - message_server_send(msg.dest, msg.type, &msg.data, &msg.packet); - } - continue; - } - } - catch (zmq::error_t& e) - { - // Context was terminated - // Exit loop - if (!zSocket || e.num() == 156384765) // ETERM - { - return; - } - - ShowError(fmt::format("Message: {}", e.what())); - continue; - } + const auto charIPP = *maybeCharIPP; + ShowDebugFmt("Message: -> rerouting to char<{}> on {}", charName, charIPP.toString()); + sendMessage(charIPP, std::move(message)); + } +} - // 0: zmq::message_t from - // 1: zmq::message_t type - // 2: zmq::message_t extra - // 3: zmq::message_t packet - message_server_parse((MSGSERVTYPE)ref((uint8*)msgs[1].data(), 0), &msgs[2], &msgs[3], &msgs[0]); +void IPCServer::rerouteMessageToZoneId(uint16 zoneId, const auto& message) +{ + TracyZoneScoped; + + if (const auto maybeZoneIPP = getIPPForZoneId(zoneId)) + { + const auto zoneIPP = *maybeZoneIPP; + ShowDebugFmt("Message: -> rerouting to zone<{}> on {}", zoneId, zoneIPP.toString()); + sendMessage(zoneIPP, std::move(message)); } } -void cache_zone_settings() +void IPCServer::rerouteMessageToPartyMembers(uint32 partyId, const auto& message) { - auto rset = db::preparedStmt("SELECT zoneid, zoneip, zoneport, misc FROM zone_settings"); - if (!rset) + TracyZoneScoped; + + for (const auto& ipp : getIPPsForParty(partyId)) { - ShowCritical("Error loading zone settings from DB"); - throw std::runtime_error("Message Server: Failed to load zone settings from database"); + sendMessage(ipp, message); } +} + +void IPCServer::rerouteMessageToAllianceMembers(uint32 allianceId, const auto& message) +{ + TracyZoneScoped; - // Keep track of the zones, as well as a list of unique ip / port combinations. - std::set mapEndpointSet; - std::set yellMapEndpointSet; - zoneSettingsMap = std::unordered_map(); - while (rset->next()) + for (const auto& ipp : getIPPsForAlliance(allianceId)) { - uint64 ip = 0; - inet_pton(AF_INET, rset->get("zoneip").c_str(), &ip); - uint64 port = rset->get("zoneport"); + sendMessage(ipp, message); + } +} - zone_settings_t zone_settings{}; - zone_settings.zoneid = rset->get("zoneid"); - zone_settings.ipp = ip | (port << 32); - zone_settings.misc = rset->get("misc"); +void IPCServer::rerouteMessageToLinkshellMembers(uint32 linkshellId, const auto& message) +{ + TracyZoneScoped; - mapEndpointSet.insert(zone_settings.ipp); + for (const auto& ipp : getIPPsForLinkshell(linkshellId)) + { + sendMessage(ipp, message); + } +} - if (zone_settings.misc & ZONEMISC::MISC_YELL) - { - yellMapEndpointSet.insert(zone_settings.ipp); - } +void IPCServer::rerouteMessageToUnityMembers(uint32 unityId, const auto& message) +{ + TracyZoneScoped; - zoneSettingsMap[zone_settings.zoneid] = zone_settings; + for (const auto& ipp : getIPPsForUnity(unityId)) + { + sendMessage(ipp, message); } +} + +void IPCServer::handleIncomingMessages() +{ + TracyZoneScoped; - // Now copy the zone ipp sets to vectors for easier iteration - std::copy(mapEndpointSet.begin(), mapEndpointSet.end(), std::back_inserter(mapEndpoints)); - std::copy(yellMapEndpointSet.begin(), yellMapEndpointSet.end(), std::back_inserter(yellMapEndpoints)); + // TODO: Can we stop more messages appearing on the queue while we're processing? + HandleableMessage out; + while (zmqRouterWrapper_.incomingQueue_.try_dequeue(out)) + { + const auto firstByte = out.payload[0]; + const auto msgType = ipc::toString(static_cast(firstByte)); + + ShowDebugFmt("Incoming {} message from {}", msgType, out.ipp.toString()); + + ipc::IIPCMessageHandler::handleMessage(out.ipp, { out.payload.data(), out.payload.size() }); + } } -void message_server_init(bool const& requestExit) +void IPCServer::handleMessage_EmptyStruct(const IPP& ipp, const ipc::EmptyStruct& message) { - TracySetThreadName("Message Server (ZMQ)"); + TracyZoneScoped; - cache_zone_settings(); + ShowInfoFmt("Received EmptyStruct message from {}", ipp.toString()); +} - // Zmql - zContext = zmq::context_t(1); - zSocket = std::make_unique(zContext, zmq::socket_type::router); +void IPCServer::handleMessage_CharLogin(const IPP& ipp, const ipc::CharLogin& message) +{ + TracyZoneScoped; - zSocket->set(zmq::sockopt::rcvtimeo, 500); + ShowDebugFmt("Received CharLogin message from {} for account {} char {}", ipp.toString(), message.accountId, message.charId); - auto server = fmt::format("tcp://{}:{}", - settings::get("network.ZMQ_IP"), - settings::get("network.ZMQ_PORT")); + // NOTE: Originally a NO-OP +} + +void IPCServer::handleMessage_CharVarUpdate(const IPP& ipp, const ipc::CharVarUpdate& message) +{ + TracyZoneScoped; + + rerouteMessageToCharId(message.charId, message); +} + +void IPCServer::handleMessage_ChatMessageTell(const IPP& ipp, const ipc::ChatMessageTell& message) +{ + TracyZoneScoped; + + rerouteMessageToCharName(message.recipientName, message); +} + +void IPCServer::handleMessage_ChatMessageParty(const IPP& ipp, const ipc::ChatMessageParty& message) +{ + TracyZoneScoped; + + rerouteMessageToPartyMembers(message.partyId, message); +} + +void IPCServer::handleMessage_ChatMessageAlliance(const IPP& ipp, const ipc::ChatMessageAlliance& message) +{ + TracyZoneScoped; + + rerouteMessageToAllianceMembers(message.allianceId, message); +} + +void IPCServer::handleMessage_ChatMessageLinkshell(const IPP& ipp, const ipc::ChatMessageLinkshell& message) +{ + TracyZoneScoped; + + rerouteMessageToLinkshellMembers(message.linkshellId, message); +} + +void IPCServer::handleMessage_ChatMessageUnity(const IPP& ipp, const ipc::ChatMessageUnity& message) +{ + TracyZoneScoped; + + rerouteMessageToUnityMembers(message.unityLeaderId, message); +} + +void IPCServer::handleMessage_ChatMessageYell(const IPP& ipp, const ipc::ChatMessageYell& message) +{ + TracyZoneScoped; + + for (const auto& ipp : zoneSettings_.yellMapEndpoints_) + { + sendMessage(ipp, message); + } +} - ShowInfo("Starting ZMQ Server on %s", server.c_str()); +void IPCServer::handleMessage_ChatMessageServerMessage(const IPP& ipp, const ipc::ChatMessageServerMessage& message) +{ + TracyZoneScoped; - try + for (const auto& ipp : zoneSettings_.mapEndpoints_) { - zSocket->bind(server.c_str()); + sendMessage(ipp, message); } - catch (zmq::error_t& err) +} + +void IPCServer::handleMessage_PartyInvite(const IPP& ipp, const ipc::PartyInvite& message) +{ + TracyZoneScoped; + + rerouteMessageToCharId(message.inviteeId, message); +} + +void IPCServer::handleMessage_PartyInviteResponse(const IPP& ipp, const ipc::PartyInviteResponse& message) +{ + TracyZoneScoped; + + rerouteMessageToCharId(message.inviterId, message); +} + +void IPCServer::handleMessage_PartyReload(const IPP& ipp, const ipc::PartyReload& message) +{ + TracyZoneScoped; + + rerouteMessageToPartyMembers(message.partyId, message); +} + +void IPCServer::handleMessage_PartyDisband(const IPP& ipp, const ipc::PartyDisband& message) +{ + TracyZoneScoped; + + rerouteMessageToPartyMembers(message.partyId, message); +} + +void IPCServer::handleMessage_AllianceReload(const IPP& ipp, const ipc::AllianceReload& message) +{ + TracyZoneScoped; + + rerouteMessageToAllianceMembers(message.allianceId, message); +} + +void IPCServer::handleMessage_AllianceDissolve(const IPP& ipp, const ipc::AllianceDissolve& message) +{ + TracyZoneScoped; + + rerouteMessageToAllianceMembers(message.allianceId, message); +} + +void IPCServer::handleMessage_PlayerKick(const IPP& ipp, const ipc::PlayerKick& message) +{ + TracyZoneScoped; + + rerouteMessageToCharId(message.charId, message); +} + +void IPCServer::handleMessage_MessageStandard(const IPP& ipp, const ipc::MessageStandard& message) +{ + TracyZoneScoped; + + rerouteMessageToCharId(message.charId, message); +} + +void IPCServer::handleMessage_MessageSystem(const IPP& ipp, const ipc::MessageSystem& message) +{ + TracyZoneScoped; + + rerouteMessageToCharId(message.charId, message); +} + +void IPCServer::handleMessage_LinkshellRankChange(const IPP& ipp, const ipc::LinkshellRankChange& message) +{ + TracyZoneScoped; + + rerouteMessageToCharId(message.charId, message); +} + +void IPCServer::handleMessage_LinkshellRemove(const IPP& ipp, const ipc::LinkshellRemove& message) +{ + TracyZoneScoped; + + rerouteMessageToCharId(message.charId, message); +} + +void IPCServer::handleMessage_LinkshellSetMessage(const IPP& ipp, const ipc::LinkshellSetMessage& message) +{ + TracyZoneScoped; + + rerouteMessageToLinkshellMembers(message.linkshellId, message); +} + +void IPCServer::handleMessage_LuaFunction(const IPP& ipp, const ipc::LuaFunction& message) +{ + TracyZoneScoped; + + rerouteMessageToZoneId(message.zoneId, message); +} + +void IPCServer::handleMessage_KillSession(const IPP& ipp, const ipc::KillSession& message) +{ + TracyZoneScoped; + + const auto rset = db::preparedStmt("SELECT pos_prevzone, pos_zone from chars where charid = ? LIMIT 1", message.charId); + + // Get zone ID from query and try to send to _just_ the previous zone + if (rset && rset->rowsCount() && rset->next()) { - ShowCritical(fmt::format("Unable to bind chat socket: {}", err.what())); + const auto prevZoneID = rset->get("pos_prevzone"); + const auto nextZoneID = rset->get("pos_zone"); + + if (prevZoneID != nextZoneID) + { + const auto zoneSettings = zoneSettings_.zoneSettingsMap_.at(prevZoneID); + + ShowDebug(fmt::format("Message: -> rerouting to {}", zoneSettings.ipp.toString())); + + sendMessage(zoneSettings.ipp, message); + } } + else // Otherwise, send to all zones + { + // TODO: Is this insane, do we need to send this to _every_ zone? + for (const auto& ipp : zoneSettings_.mapEndpoints_) + { + ShowDebug(fmt::format("Message: -> rerouting to {}", ipp.toString())); - ShowInfo("ZMQ Server listening..."); - message_server_listen(requestExit); + sendMessage(ipp, message); + } + } } -void message_server_close() +void IPCServer::handleMessage_RegionalEvent(const IPP& ipp, const ipc::RegionalEvent& message) { - if (zSocket) + TracyZoneScoped; + + // Dispatch to relevant system + switch (message.type) { - zSocket->close(); - zSocket = nullptr; + case RegionalEventType::Conquest: + worldServer_.conquestSystem_->handleMessage(message.subType, { ipp, message.payload }); + break; + case RegionalEventType::Besieged: + worldServer_.besiegedSystem_->handleMessage(message.subType, { ipp, message.payload }); + break; + case RegionalEventType::Campaign: + worldServer_.campaignSystem_->handleMessage(message.subType, { ipp, message.payload }); + break; + case RegionalEventType::Colonization: + worldServer_.colonizationSystem_->handleMessage(message.subType, { ipp, message.payload }); + break; + default: + ShowWarning("Received unknown RegionalEvent type {}", static_cast(message.type)); + break; } +} + +void IPCServer::handleMessage_GMSendToZone(const IPP& ipp, const ipc::GMSendToZone& message) +{ + TracyZoneScoped; + + rerouteMessageToCharId(message.targetId, message); +} + +void IPCServer::handleMessage_GMSendToEntity(const IPP& ipp, const ipc::GMSendToEntity& message) +{ + TracyZoneScoped; + + rerouteMessageToZoneId(message.zoneId, message); +} + +void IPCServer::handleMessage_RPCSend(const IPP& ipp, const ipc::RPCSend& message) +{ + TracyZoneScoped; + + rerouteMessageToZoneId(message.zoneId, message); +} + +void IPCServer::handleMessage_RPCRecv(const IPP& ipp, const ipc::RPCRecv& message) +{ + TracyZoneScoped; + + rerouteMessageToZoneId(message.zoneId, message); +} + +void IPCServer::handleUnknownMessage(const IPP& ipp, const std::span message) +{ + TracyZoneScoped; - zContext.close(); + ShowWarningFmt("Received unknown message from {} with code {} and size {}", ipp.toString(), message[0], message.size()); } diff --git a/src/world/ipc_server.h b/src/world/ipc_server.h index 27468e632c7..b1430ade62b 100644 --- a/src/world/ipc_server.h +++ b/src/world/ipc_server.h @@ -2,6 +2,7 @@ =========================================================================== Copyright (c) 2010-2015 Darkstar Dev Teams + Copyright (c) 2025 LandSandBoat Dev Teams This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,35 +22,104 @@ #pragma once +#include "common/ipc.h" +#include "common/ipp.h" #include "common/mmo.h" #include "common/socket.h" +#include "common/zmq_dealer_wrapper.h" + #include "conquest_system.h" #include "message_handler.h" +#include "world_server.h" +#include "zone_settings.h" #include #include #include -void queue_message(uint64 ipp, MSGSERVTYPE type, zmq::message_t* extra, zmq::message_t* packet = nullptr); -void queue_message_broadcast(MSGSERVTYPE type, zmq::message_t* extra, zmq::message_t* packet = nullptr); +class IPCServer final : public ipc::IIPCMessageHandler +{ +public: + IPCServer(WorldServer& worldServer); + ~IPCServer() override = default; + + void handleIncomingMessages(); -auto pop_external_processing_message() -> std::optional; + template + void sendMessage(const IPP& ipp, T&& message); -void message_server_init(bool const& requestExit); -void message_server_close(); + auto getIPPForCharId(uint32 charId) -> std::optional; + auto getIPPForCharName(const std::string& charName) -> std::optional; + auto getIPPForZoneId(uint16 zoneId) -> std::optional; + auto getIPPsForParty(uint32 partyId) -> std::vector; + auto getIPPsForAlliance(uint32 allianceId) -> std::vector; + auto getIPPsForLinkshell(uint32 linkshellId) -> std::vector; + auto getIPPsForUnity(uint32 unityId) -> std::vector; -struct message_server_wrapper_t -{ - message_server_wrapper_t(std::atomic_bool const& requestExit) - : m_thread(std::make_unique(std::bind(message_server_init, std::ref(requestExit)))) - { - } + void rerouteMessageToCharId(uint32 charId, const auto& message); + void rerouteMessageToCharName(const std::string& charName, const auto& message); + void rerouteMessageToZoneId(uint16 zoneId, const auto& message); + void rerouteMessageToPartyMembers(uint32 partyId, const auto& message); + void rerouteMessageToAllianceMembers(uint32 allianceId, const auto& message); + void rerouteMessageToLinkshellMembers(uint32 linkshellId, const auto& message); + void rerouteMessageToUnityMembers(uint32 unityId, const auto& message); - ~message_server_wrapper_t() - { - message_server_close(); - } + // + // ipc::IIPCMessageHandler + // + + void handleMessage_EmptyStruct(const IPP& ipp, const ipc::EmptyStruct& message) override; + void handleMessage_CharLogin(const IPP& ipp, const ipc::CharLogin& message) override; + void handleMessage_CharVarUpdate(const IPP& ipp, const ipc::CharVarUpdate& message) override; + void handleMessage_ChatMessageTell(const IPP& ipp, const ipc::ChatMessageTell& message) override; + void handleMessage_ChatMessageParty(const IPP& ipp, const ipc::ChatMessageParty& message) override; + void handleMessage_ChatMessageAlliance(const IPP& ipp, const ipc::ChatMessageAlliance& message) override; + void handleMessage_ChatMessageLinkshell(const IPP& ipp, const ipc::ChatMessageLinkshell& message) override; + void handleMessage_ChatMessageUnity(const IPP& ipp, const ipc::ChatMessageUnity& message) override; + void handleMessage_ChatMessageYell(const IPP& ipp, const ipc::ChatMessageYell& message) override; + void handleMessage_ChatMessageServerMessage(const IPP& ipp, const ipc::ChatMessageServerMessage& message) override; + void handleMessage_PartyInvite(const IPP& ipp, const ipc::PartyInvite& message) override; + void handleMessage_PartyInviteResponse(const IPP& ipp, const ipc::PartyInviteResponse& message) override; + void handleMessage_PartyReload(const IPP& ipp, const ipc::PartyReload& message) override; + void handleMessage_PartyDisband(const IPP& ipp, const ipc::PartyDisband& message) override; + void handleMessage_AllianceReload(const IPP& ipp, const ipc::AllianceReload& message) override; + void handleMessage_AllianceDissolve(const IPP& ipp, const ipc::AllianceDissolve& message) override; + void handleMessage_PlayerKick(const IPP& ipp, const ipc::PlayerKick& message) override; + void handleMessage_MessageStandard(const IPP& ipp, const ipc::MessageStandard& message) override; + void handleMessage_MessageSystem(const IPP& ipp, const ipc::MessageSystem& message) override; + void handleMessage_LinkshellRankChange(const IPP& ipp, const ipc::LinkshellRankChange& message) override; + void handleMessage_LinkshellRemove(const IPP& ipp, const ipc::LinkshellRemove& message) override; + void handleMessage_LinkshellSetMessage(const IPP& ipp, const ipc::LinkshellSetMessage& message) override; + void handleMessage_LuaFunction(const IPP& ipp, const ipc::LuaFunction& message) override; + void handleMessage_KillSession(const IPP& ipp, const ipc::KillSession& message) override; + void handleMessage_RegionalEvent(const IPP& ipp, const ipc::RegionalEvent& message) override; + void handleMessage_GMSendToZone(const IPP& ipp, const ipc::GMSendToZone& message) override; + void handleMessage_GMSendToEntity(const IPP& ipp, const ipc::GMSendToEntity& message) override; + void handleMessage_RPCSend(const IPP& ipp, const ipc::RPCSend& message) override; + void handleMessage_RPCRecv(const IPP& ipp, const ipc::RPCRecv& message) override; + + void handleUnknownMessage(const IPP& ipp, const std::span message) override; private: - std::unique_ptr m_thread; + WorldServer& worldServer_; + + ZoneSettings zoneSettings_; + ZMQRouterWrapper zmqRouterWrapper_; }; + +// +// Inline implementations +// + +template +void IPCServer::sendMessage(const IPP& ipp, T&& message) +{ + TracyZoneScoped; + + ShowDebugFmt("Sending {} message to {}", ipc::toString(ipc::getEnumType()), ipp.toString()); + + const auto bytes = ipc::toBytesWithHeader(message); + HandleableMessage out{ ipp, std::vector{ bytes.begin(), bytes.end() } }; + + zmqRouterWrapper_.outgoingQueue_.enqueue(std::move(out)); +} diff --git a/src/world/message_handler.h b/src/world/message_handler.h index 436f410dd35..dc5ad3e6feb 100644 --- a/src/world/message_handler.h +++ b/src/world/message_handler.h @@ -21,15 +21,10 @@ #pragma once +#include "common/ipp.h" #include "common/mmo.h" #include "common/socket.h" - -struct HandleableMessage -{ - std::vector payload; - in_addr from_addr; - uint16 from_port; -}; +#include "common/zmq_router_wrapper.h" class IMessageHandler { @@ -38,5 +33,5 @@ class IMessageHandler { } - virtual bool handleMessage(HandleableMessage&& message) = 0; + virtual bool handleMessage(uint8 messageType, HandleableMessage&& message) = 0; }; diff --git a/src/world/time_server.cpp b/src/world/time_server.cpp index 62a0ab60cff..fa59462af6c 100644 --- a/src/world/time_server.cpp +++ b/src/world/time_server.cpp @@ -25,12 +25,15 @@ #include "common/taskmgr.h" #include "common/tracy.h" #include "common/vana_time.h" + +#include "conquest_system.h" #include "daily_tally.h" #include "world_server.h" int32 time_server(time_point tick, CTaskMgr::CTask* PTask) { TracyZoneScoped; + TIMETYPE VanadielTOTD = CVanaTime::getInstance()->SyncTime(); WorldServer* worldServer = std::any_cast(PTask->m_data); @@ -41,7 +44,7 @@ int32 time_server(time_point tick, CTaskMgr::CTask* PTask) { if (tick > (lastConquestTally + 1h)) { - worldServer->conquestSystem->updateWeekConquest(); + worldServer->conquestSystem_->updateWeekConquest(); lastConquestTally = tick; } } @@ -50,7 +53,7 @@ int32 time_server(time_point tick, CTaskMgr::CTask* PTask) { if (tick > (lastConquestUpdate + 1h)) { - worldServer->conquestSystem->updateHourlyConquest(); + worldServer->conquestSystem_->updateHourlyConquest(); lastConquestUpdate = tick; } } @@ -61,7 +64,7 @@ int32 time_server(time_point tick, CTaskMgr::CTask* PTask) { if (tick > (lastVHourlyUpdate + 4800ms)) { - worldServer->conquestSystem->updateVanaHourlyConquest(); + worldServer->conquestSystem_->updateVanaHourlyConquest(); lastVHourlyUpdate = tick; } } diff --git a/src/world/world_server.cpp b/src/world/world_server.cpp index 978986b82c0..4cd8577be78 100644 --- a/src/world/world_server.cpp +++ b/src/world/world_server.cpp @@ -23,70 +23,38 @@ #include "common/application.h" #include "common/logging.h" + +#include "besieged_system.h" +#include "campaign_system.h" +#include "colonization_system.h" +#include "conquest_system.h" +#include "http_server.h" +#include "ipc_server.h" #include "time_server.h" -int32 forward_queued_messages_to_handlers(time_point tick, CTaskMgr::CTask* PTask) +int32 pump_queues(time_point tick, CTaskMgr::CTask* PTask) { TracyZoneScoped; - WorldServer* worldServer = std::any_cast(PTask->m_data); - - while (std::optional maybeMessage = pop_external_processing_message()) - { - HandleableMessage message = *maybeMessage; - auto subType = static_cast(ref(message.payload.data(), 0)); - IMessageHandler* handler = nullptr; - switch (subType) - { - case REGIONAL_EVT_MSG_CONQUEST: - { - handler = worldServer->conquestSystem.get(); - } - break; - case REGIONAL_EVT_MSG_BESIEGED: - { - handler = worldServer->besiegedSystem.get(); - } - break; - case REGIONAL_EVT_MSG_CAMPAIGN: - { - handler = worldServer->campaignSystem.get(); - } - break; - case REGIONAL_EVT_MSG_COLONIZATION: - { - handler = worldServer->colonizationSystem.get(); - } - break; - default: - { - ShowError(fmt::format("Unknown IMessageHandler type requested: {}", subType)); - } - break; - } - if (handler) - { - handler->handleMessage(std::move(message)); - } - } + std::any_cast(PTask->m_data)->ipcServer_->handleIncomingMessages(); return 0; } WorldServer::WorldServer(int argc, char** argv) : Application("world", argc, argv) -, httpServer(std::make_unique()) -, messageServer(std::make_unique(std::ref(m_RequestExit))) -, conquestSystem(std::make_unique()) -, besiegedSystem(std::make_unique()) -, campaignSystem(std::make_unique()) -, colonizationSystem(std::make_unique()) +, ipcServer_(std::make_unique(*this)) +, conquestSystem_(std::make_unique(*this)) +, besiegedSystem_(std::make_unique(*this)) +, campaignSystem_(std::make_unique(*this)) +, colonizationSystem_(std::make_unique(*this)) +, httpServer_(std::make_unique()) { // Tasks CTaskMgr::getInstance()->AddTask("time_server", server_clock::now(), this, CTaskMgr::TASK_INTERVAL, 2400ms, time_server); // TODO: Make this more reactive than a polling job - CTaskMgr::getInstance()->AddTask("forward_queued_messages_to_handlers", server_clock::now(), this, CTaskMgr::TASK_INTERVAL, 250ms, forward_queued_messages_to_handlers); + CTaskMgr::getInstance()->AddTask("pump_queues", server_clock::now(), this, CTaskMgr::TASK_INTERVAL, 250ms, pump_queues); } WorldServer::~WorldServer() = default; diff --git a/src/world/world_server.h b/src/world/world_server.h index dac9f9fef83..bd652e9c15e 100644 --- a/src/world/world_server.h +++ b/src/world/world_server.h @@ -24,12 +24,18 @@ #include "common/application.h" #include "common/taskmgr.h" -#include "besieged_system.h" -#include "campaign_system.h" -#include "colonization_system.h" -#include "conquest_system.h" #include "http_server.h" -#include "message_server.h" + +// +// Forward declarations +// + +class IPCServer; +class PartySystem; +class ConquestSystem; +class BesiegedSystem; +class CampaignSystem; +class ColonizationSystem; class WorldServer final : public Application { @@ -39,11 +45,14 @@ class WorldServer final : public Application void Tick() override; - std::unique_ptr httpServer; - std::unique_ptr messageServer; + std::unique_ptr ipcServer_; + + // TODO: PartySystem + + std::unique_ptr conquestSystem_; + std::unique_ptr besiegedSystem_; + std::unique_ptr campaignSystem_; + std::unique_ptr colonizationSystem_; - std::unique_ptr conquestSystem; - std::unique_ptr besiegedSystem; - std::unique_ptr campaignSystem; - std::unique_ptr colonizationSystem; + std::unique_ptr httpServer_; }; diff --git a/src/world/zone_settings.h b/src/world/zone_settings.h new file mode 100644 index 00000000000..867ac33f875 --- /dev/null +++ b/src/world/zone_settings.h @@ -0,0 +1,87 @@ +/* +=========================================================================== + + Copyright (c) 2025 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#pragma once + +#include "common/cbasetypes.h" +#include "common/database.h" +#include "common/ipp.h" +#include "common/logging.h" + +#include +#include +#include + +class ZoneSettings final +{ +private: + struct ZoneSettingsEntry final + { + uint16 zoneid{}; + IPP ipp{}; + uint32 misc{}; + }; + +public: + ZoneSettings() + { + const auto rset = db::preparedStmt("SELECT zoneid, zoneip, zoneport, misc FROM zone_settings"); + if (!rset) + { + ShowCritical("Error loading zone settings from DB"); + throw std::runtime_error("Message Server: Failed to load zone settings from database"); + } + + // Keep track of the zones, as well as a list of unique ip / port combinations. + std::set mapEndpointSet; + std::set yellMapEndpointSet; + + while (rset->next()) + { + uint64 ip = 0; + inet_pton(AF_INET, rset->get("zoneip").c_str(), &ip); + uint64 port = rset->get("zoneport"); + + ZoneSettingsEntry zone_settings{}; + zone_settings.zoneid = rset->get("zoneid"); + zone_settings.ipp = IPP(ip, port); + zone_settings.misc = rset->get("misc"); + + mapEndpointSet.insert(zone_settings.ipp); + + if (zone_settings.misc & ZONEMISC::MISC_YELL) + { + yellMapEndpointSet.insert(zone_settings.ipp); + } + + zoneSettingsMap_[zone_settings.zoneid] = zone_settings; + } + + std::copy(mapEndpointSet.begin(), mapEndpointSet.end(), std::back_inserter(mapEndpoints_)); + std::copy(yellMapEndpointSet.begin(), yellMapEndpointSet.end(), std::back_inserter(yellMapEndpoints_)); + } + + // TODO: Properly encapsulate this + // private: + std::unordered_map zoneSettingsMap_; + std::vector mapEndpoints_; + std::vector yellMapEndpoints_; +}; diff --git a/tools/announce.py b/tools/announce.py index d625d772aa1..80feca8dde9 100644 --- a/tools/announce.py +++ b/tools/announce.py @@ -10,6 +10,7 @@ # ############################# +import socket import sys import zmq import struct @@ -22,6 +23,18 @@ context = zmq.Context() sock = context.socket(zmq.DEALER) + +ip_str = "127.0.0.1" +port = 54003 + +ip_bytes = socket.inet_aton(ip_str) +(ip_int,) = struct.unpack("!I", ip_bytes) +ipp = ip_int | (port << 32) +ipp_bytes = struct.pack("!Q", ipp) + +print(f"Connecting to {ip_str}:{port} ({ipp})") + +sock.setsockopt(zmq.ROUTING_ID, ipp_bytes) sock.connect("tcp://127.0.0.1:54003") @@ -59,9 +72,7 @@ def build_chat_packet(msg_type, gm_flag, zone, sender, msg): def send_server_message(msg): print(f"Sending '{msg}'") buffer = build_chat_packet(MSG_CHAT_SERVMES, 1, 0, "", msg) - sock.send_multipart( - [struct.pack("!B", MSG_CHAT_SERVMES), b"\0", buffer], zmq.NOBLOCK - ) + sock.send_multipart([buffer], zmq.NOBLOCK) def main(): diff --git a/tools/generate_ipc_stubs.py b/tools/generate_ipc_stubs.py index 497b65b0c4d..fd5ff154f82 100644 --- a/tools/generate_ipc_stubs.py +++ b/tools/generate_ipc_stubs.py @@ -10,9 +10,52 @@ import sys +# TODO: Make this generic so you can specify an output filename, namespace name, interface name, enum name, etc. + + # Define the struct names that will be used to generate build/generated/ipc_stubs.h IPC_STRUCT_NAMES = [ - "SomeData", # Example struct. Remove this once we have real structs. + "EmptyStruct", + + "CharLogin", + "CharVarUpdate", + + "ChatMessageTell", + "ChatMessageParty", + "ChatMessageAlliance", + "ChatMessageLinkshell", + "ChatMessageUnity", + "ChatMessageYell", + "ChatMessageServerMessage", + + "PartyInvite", + "PartyInviteResponse", + "PartyReload", + "PartyDisband", + + "AllianceReload", + "AllianceDissolve", + + "PlayerKick", + + "MessageStandard", + "MessageSystem", + + "LinkshellRankChange", + "LinkshellRemove", + "LinkshellSetMessage", + + "LuaFunction", + + "KillSession", + + "RegionalEvent", + + "GMSendToZone", + "GMSendToEntity", + + "RPCSend", + "RPCRecv", ] @@ -24,122 +67,124 @@ def generate_ipc_stubs(output_dir, struct_names): ipc_stub_file = os.path.join(output_path, "ipc_stubs.h") with open(ipc_stub_file, "w") as f: - f.write("// This file is auto-generated by tools/generate_ipc_stubs.py\n") - f.write("// Do not modify this file directly.\n\n") - f.write("#pragma once\n\n") - f.write("#include \n") - f.write("#include \n\n") - f.write("#include \"ipc.h\"\n\n") - f.write("#include \"logging.h\"\n\n") - f.write("namespace ipc\n{\n\n") + f.write(f"// This file is auto-generated by tools/generate_ipc_stubs.py\n") + f.write(f"// Do not modify this file directly.\n\n") + f.write(f"#pragma once\n\n") + f.write(f"#include \n") + f.write(f"#include \n\n") + f.write(f"#include \n\n") + f.write(f"#include \"common/ipc_structs.h\"\n") + f.write(f"#include \"common/ipp.h\"\n\n") + f.write(f"#include \"common/logging.h\"\n\n") + f.write(f"namespace ipc\n{{\n\n") generate_message_type_enum(f, struct_names) - generate_forward_declarations(f, struct_names) generate_struct_completion_traits(f) generate_get_enum_type_function(f, struct_names) generate_enum_to_string_function(f, struct_names) generate_message_handler_interface(f, struct_names) - f.write("} // namespace ipc\n") + f.write(f"}} // namespace ipc\n") def generate_message_type_enum(file, struct_names): max_name_length = max(len(name) for name in struct_names) - file.write("enum class MessageType : uint8_t\n{\n") - - for idx, name in enumerate(struct_names): - file.write(f" {name.ljust(max_name_length)} = {idx},\n") - - file.write("};\n\n") - + file.write(f"enum class MessageType : uint8_t\n{{\n") -def generate_forward_declarations(file, struct_names): - file.write("// You must remember to implement each of these structs.\n\n") + file.write(f" // 0 is reserved for unknown messages\n\n") - for name in struct_names: - file.write(f"struct {name};\n") + for idx, name in enumerate(struct_names, 1): + file.write(f" {name.ljust(max_name_length)} = {idx},\n") - file.write("\n") + file.write(f"}};\n\n") def generate_struct_completion_traits(file): - file.write("template\n") + file.write("template \n") file.write("struct is_struct_complete : std::false_type {};\n\n") - file.write("template\n") - file.write("struct is_struct_complete> : std::true_type {};\n\n") + file.write(f"template \n") + file.write(f"struct is_struct_complete> : std::true_type {{}};\n\n") def generate_get_enum_type_function(file, struct_names): - file.write("template\n") - file.write("auto getEnumType() -> MessageType\n") - file.write("{\n") - file.write(" static_assert(false, \"You should never reach this point.\");\n") - file.write("}\n\n") + file.write(f"template \n") + file.write(f"constexpr auto getEnumType() -> MessageType\n") + file.write(f"{{\n") + file.write(f" static_assert(is_struct_complete::value, \"You must fully define this struct in common/ipc_structs.h.\");\n\n") + file.write(f" using UnderlyingT = std::decay_t;\n\n") + counter = 0 for name in struct_names: - file.write(f"template<>\n") - file.write(f"auto getEnumType<{name}>() -> MessageType\n") - file.write("{\n") - file.write(f" static_assert(is_struct_complete<{name}>::value, \"You must fully define this struct in common/ipc_structs.h.\");\n") - file.write(f" return MessageType::{name};\n") - file.write("}\n\n") + if counter == 0: + file.write(f" if constexpr (std::is_same_v)\n") + else: + file.write(f" else if constexpr (std::is_same_v)\n") + + file.write(f" {{\n") + file.write(f" return MessageType::{name};\n") + file.write(f" }}\n") + + counter += 1 + + file.write(f"}}\n\n") def generate_enum_to_string_function(file, struct_names): - file.write("auto toString(MessageType type) -> std::string\n") - file.write("{\n") - file.write(" switch (type)\n") - file.write(" {\n") + file.write(f"constexpr auto toString(MessageType type) -> std::string\n") + file.write(f"{{\n") + file.write(f" switch (type)\n") + file.write(f" {{\n") for name in struct_names: file.write(f" case MessageType::{name}:\n") file.write(f" return \"{name}\";\n") - file.write(" default:\n") - file.write(" return \"Unknown\";\n") - file.write(" }\n") - file.write("}\n\n") + file.write(f" default:\n") + file.write(f" return \"Unknown\";\n") + file.write(f" }}\n") + file.write(f"}}\n\n") def generate_message_handler_interface(file, struct_names): - file.write("template\n") - file.write("auto fromBytes(const std::vector& message) -> std::optional;\n\n") - file.write("class IIPCMessageHandler\n") - file.write("{\n") - file.write("public:\n") - file.write(" virtual ~IIPCMessageHandler() = default;\n\n") - - file.write(" void handleMessage(const std::vector& message)\n") - file.write(" {\n") - file.write(" const auto messageType = static_cast(message[0]);\n") - file.write(" switch (messageType)\n") - file.write(" {\n") + file.write(f"class IIPCMessageHandler\n") + file.write(f"{{\n") + file.write(f"public:\n") + file.write(f" virtual ~IIPCMessageHandler() = default;\n\n") + + file.write(f" void handleMessage(const IPP& ipp, const std::span message)\n") + file.write(f" {{\n") + file.write(f" const auto messageType = static_cast(message[0]);\n") + file.write(f" switch (messageType)\n") + file.write(f" {{\n") for name in struct_names: file.write(f" case MessageType::{name}:\n") - file.write(" {\n") - file.write(f" const auto object = ipc::fromBytes<{name}>(message);\n") - file.write(" if (!object.has_value())\n") - file.write(" {\n") + file.write(f" {{\n") + file.write(f" const auto object = ipc::fromBytesWithHeader<{name}>(message);\n") + file.write(f" if (!object.has_value())\n") + file.write(f" {{\n") file.write(f" ShowError(\"Failed to deserialize {name} message.\");\n") - file.write(" break;\n") - file.write(" }\n") - file.write(f" handleMessage_{name}(*object);\n") - file.write(" }\n") + file.write(f" break;\n") + file.write(f" }}\n") + file.write(f" handleMessage_{name}(ipp, *object);\n") + file.write(f" }}\n") file.write(f" break;\n") - file.write(" default:\n") - file.write(" break;\n") - file.write(" }\n") - file.write(" }\n\n") - file.write("protected:\n") + file.write(f" default:\n") + file.write(f" handleUnknownMessage(ipp, message);\n") + file.write(f" break;\n") + file.write(f" }}\n") + file.write(f" }}\n\n") + file.write(f"protected:\n") for name in struct_names: - file.write(f" virtual void handleMessage_{name}(const {name}& message) = 0;\n") + file.write(f" virtual void handleMessage_{name}(const IPP& ipp, const ipc::{name}& message) = 0;\n") + + file.write(f"\n virtual void handleUnknownMessage(const IPP& ipp, const std::span message) = 0;\n") - file.write("};\n\n") + file.write(f"}};\n\n") if __name__ == "__main__":