From 8951ad90810146d3ae5d3075108ec35edb2f4b52 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sun, 17 May 2026 10:03:02 +0200 Subject: [PATCH 01/20] fix(object): Prevent crash if Object::m_drawable is a nullptr on save load (#2712) --- Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp | 6 ++---- .../Code/GameEngine/Source/GameLogic/Object/Object.cpp | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp index edda6839d12..85bd61efa71 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -3608,12 +3608,10 @@ void Object::xfer( Xfer *xfer ) Drawable *draw = getDrawable(); DrawableID drawableID = draw ? draw->getID() : INVALID_DRAWABLE_ID; xfer->xferDrawableID( &drawableID ); - if( xfer->getXferMode() == XFER_LOAD ) + if (draw && xfer->getXferMode() == XFER_LOAD) { - // change the ID of the drawable attached to be the same ID as it was when it was saved - draw->setID( drawableID ); - + draw->setID(drawableID); } // internal name diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 0a03557b1b3..a8029b4ec8d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -4118,12 +4118,10 @@ void Object::xfer( Xfer *xfer ) Drawable *draw = getDrawable(); DrawableID drawableID = draw ? draw->getID() : INVALID_DRAWABLE_ID; xfer->xferDrawableID( &drawableID ); - if( xfer->getXferMode() == XFER_LOAD ) + if (draw && xfer->getXferMode() == XFER_LOAD) { - // change the ID of the drawable attached to be the same ID as it was when it was saved - draw->setID( drawableID ); - + draw->setID(drawableID); } // internal name From 2219f63c8b3713841f1a618bbbc4f3798bb4ec53 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 17 May 2026 11:37:55 +0200 Subject: [PATCH 02/20] refactor(netpacket): Simplify NetPacket functions for packet buffer reads (#2463) --- .../Include/GameNetwork/NetCommandList.h | 1 + .../Include/GameNetwork/NetCommandMsg.h | 65 +- .../Include/GameNetwork/NetPacket.h | 35 +- .../Include/GameNetwork/NetPacketStructs.h | 131 +- .../Source/GameNetwork/ConnectionManager.cpp | 17 +- .../Source/GameNetwork/NetCommandList.cpp | 13 +- .../Source/GameNetwork/NetCommandMsg.cpp | 15 +- .../Source/GameNetwork/NetCommandRef.cpp | 8 +- .../Source/GameNetwork/NetPacket.cpp | 1072 ++--------------- .../Source/GameNetwork/NetPacketStructs.cpp | 527 +++++++- 10 files changed, 828 insertions(+), 1056 deletions(-) diff --git a/Core/GameEngine/Include/GameNetwork/NetCommandList.h b/Core/GameEngine/Include/GameNetwork/NetCommandList.h index 3e042d92123..cb7f262415a 100644 --- a/Core/GameEngine/Include/GameNetwork/NetCommandList.h +++ b/Core/GameEngine/Include/GameNetwork/NetCommandList.h @@ -50,6 +50,7 @@ class NetCommandList : public MemoryPoolObject void init(); ///< Initialize the list void reset(); ///< Reset the list to the initial state. NetCommandRef * addMessage(NetCommandMsg *cmdMsg); ///< Add message to the list in its properly ordered place. + NetCommandRef * addMessage(NetCommandRef *&msg); ///< Add message to the list in its properly ordered place. Bool isEqualCommandMsg(NetCommandMsg *msg1, NetCommandMsg *msg2); NetCommandRef * getFirstMessage(); ///< Get the first message on the list. NetCommandRef * findMessage(NetCommandMsg *msg); ///< Find and return a reference to the given message if one exists. diff --git a/Core/GameEngine/Include/GameNetwork/NetCommandMsg.h b/Core/GameEngine/Include/GameNetwork/NetCommandMsg.h index caf2302bc9b..87c48572837 100644 --- a/Core/GameEngine/Include/GameNetwork/NetCommandMsg.h +++ b/Core/GameEngine/Include/GameNetwork/NetCommandMsg.h @@ -35,6 +35,61 @@ class NetCommandRef; +//----------------------------------------------------------------------------- +class NetCommandDataChunk +{ + NetCommandDataChunk(const NetCommandDataChunk&) CPP_11(= delete); + void operator=(const NetCommandDataChunk&) CPP_11(= delete); + +public: + NetCommandDataChunk(Byte *data, UnsignedInt size) + : m_data(reinterpret_cast(data)) + , m_size(size) + {} + + NetCommandDataChunk(UnsignedByte *data, UnsignedInt size) + : m_data(data) + , m_size(size) + {} + + NetCommandDataChunk(UnsignedInt size) + : m_data(NEW UnsignedByte[size]) + , m_size(size) + {} + + ~NetCommandDataChunk() + { + delete[] m_data; + } + + const UnsignedByte *data() const + { + return m_data; + } + + UnsignedByte *data() + { + return m_data; + } + + UnsignedInt size() const + { + return m_size; + } + + UnsignedByte *release() + { + UnsignedByte *ret = m_data; + m_data = nullptr; + m_size = 0; + return ret; + } + +private: + UnsignedByte *m_data; + UnsignedInt m_size; +}; + //----------------------------------------------------------------------------- class NetCommandMsg : public MemoryPoolObject { @@ -60,6 +115,7 @@ class NetCommandMsg : public MemoryPoolObject virtual size_t getSizeForSmallNetPacket(const Select* select = nullptr) const = 0; virtual size_t copyBytesForSmallNetPacket(UnsignedByte* buffer, const NetCommandRef& ref, const Select* select = nullptr) const = 0; virtual Select getSmallNetPacketSelect() const = 0; + virtual size_t readMessageData(NetCommandRef& ref, NetPacketBuf buf) const = 0; void attach(); void detach(); @@ -96,6 +152,11 @@ class NetCommandMsgT : public NetCommandMsg { return SmallNetPacketType::copyBytes(buffer, ref, select); } + + virtual size_t readMessageData(NetCommandRef& ref, NetPacketBuf buf) const override + { + return SmallNetPacketType::CommandData::readMessage(ref, buf); + } }; //----------------------------------------------------------------------------- @@ -440,7 +501,7 @@ class NetWrapperCommandMsg : public NetCommandMsgT +size_t readObject(T &value, NetPacketBuf src) +{ + const size_t readLen = min(sizeof(value), src.size()); + memcpy(&value, src.data(), readLen); + return readLen; +} + +inline size_t readBytes(UnsignedByte *dest, size_t destLen, NetPacketBuf src) +{ + const size_t readLen = min(destLen, src.size()); + memcpy(dest, src.data(), readLen); + return readLen; +} + +inline size_t readStringWithoutNull(UnicodeString &str, size_t maxStrLen, NetPacketBuf src) +{ + const size_t strLen = min(maxStrLen, src.size() / sizeof(WideChar)); + const size_t cpyLen = strLen * sizeof(WideChar); + + if (strLen > 0) + { + WideChar *strBuf = str.getBufferForRead(strLen); + memcpy(strBuf, src.data(), cpyLen); + strBuf[strLen] = 0; + } + return cpyLen; +} + +inline size_t readStringWithNull(AsciiString &str, size_t maxStrLen, NetPacketBuf src) +{ + const size_t realStrLen = strnlen(reinterpret_cast(src.data()), src.size()); + const size_t usedStrLen = min(realStrLen, maxStrLen); + const size_t realCpyLen = realStrLen * sizeof(char); + const size_t usedCpyLen = usedStrLen * sizeof(char); + + if (usedStrLen > 0) + { + char *strBuf = str.getBufferForRead(usedStrLen); + memcpy(strBuf, src.data(), usedCpyLen); + strBuf[usedStrLen] = 0; + } + return realCpyLen + sizeof(char); +} + template size_t writePrimitive(UnsignedByte *dest, T value) { @@ -70,19 +155,19 @@ size_t writePrimitive(UnsignedByte *dest, T value) } template -size_t writeObject(UnsignedByte *dest, const T& value) +size_t writeObject(UnsignedByte *dest, const T &value) { memcpy(dest, &value, sizeof(value)); return sizeof(value); } -inline size_t writeBytes(UnsignedByte *dest, const UnsignedByte* src, size_t len) +inline size_t writeBytes(UnsignedByte *dest, const UnsignedByte *src, size_t len) { memcpy(dest, src, len); return len; } -inline size_t writeStringWithoutNull(UnsignedByte *dest, const UnicodeString& value, size_t maxLen) +inline size_t writeStringWithoutNull(UnsignedByte *dest, const UnicodeString &value, size_t maxLen) { const size_t copyLen = std::min(value.getLength(), maxLen); const size_t copyBytes = copyLen * sizeof(WideChar); @@ -90,7 +175,7 @@ inline size_t writeStringWithoutNull(UnsignedByte *dest, const UnicodeString& va return copyBytes; } -inline size_t writeStringWithNull(UnsignedByte *dest, const AsciiString& value) +inline size_t writeStringWithNull(UnsignedByte *dest, const AsciiString &value) { memcpy(dest, value.str(), value.getByteCount() + 1); return static_cast(value.getByteCount() + 1); @@ -125,48 +210,48 @@ namespace NetPacketFieldTypes struct NetPacketCommandTypeField { NetPacketCommandTypeField() : fieldType(NetPacketFieldTypes::CommandType) {} - char fieldType; + const NetPacketFieldType fieldType; UnsignedByte commandType; }; struct NetPacketRelayField { NetPacketRelayField() : fieldType(NetPacketFieldTypes::Relay) {} - char fieldType; + const NetPacketFieldType fieldType; UnsignedByte relay; }; struct NetPacketFrameField { NetPacketFrameField() : fieldType(NetPacketFieldTypes::Frame) {} - char fieldType; + const NetPacketFieldType fieldType; UnsignedInt frame; }; struct NetPacketPlayerIdField { NetPacketPlayerIdField() : fieldType(NetPacketFieldTypes::PlayerId) {} - char fieldType; + const NetPacketFieldType fieldType; UnsignedByte playerId; }; struct NetPacketCommandIdField { NetPacketCommandIdField() : fieldType(NetPacketFieldTypes::CommandId) {} - char fieldType; + const NetPacketFieldType fieldType; UnsignedShort commandId; }; struct NetPacketDataField { NetPacketDataField() : fieldType(NetPacketFieldTypes::Data) {} - char fieldType; + const NetPacketFieldType fieldType; }; struct NetPacketRepeatField { NetPacketRepeatField() : fieldType(NetPacketFieldTypes::Repeat) {} - char fieldType; + const NetPacketFieldType fieldType; }; //////////////////////////////////////////////////////////////////////////////// @@ -222,6 +307,9 @@ struct SmallNetPacketCommandBase static size_t getSize(const SmallNetPacketCommandBaseSelect *select = nullptr); static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref, const SmallNetPacketCommandBaseSelect *select = nullptr); + static size_t readMessage(NetCommandRef *&ref, CommandBase &base, NetPacketBuf buf); +private: + static NetCommandMsg *constructNetCommandMsg(const CommandBase &base); }; //////////////////////////////////////////////////////////////////////////////// @@ -286,6 +374,7 @@ struct NetPacketNoData static size_t getSize(const NetCommandMsg &) { return 0; } static size_t copyBytes(UnsignedByte *, const NetCommandRef &) { return 0; } + static size_t readMessage(NetCommandRef &, NetPacketBuf) { return 0; } }; //////////////////////////////////////////////////////////////////////////////// @@ -304,6 +393,7 @@ struct NetPacketAckCommandData static size_t getSize(const NetCommandMsg &msg) { return sizeof(FixedData); } static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref); + static size_t readMessage(NetCommandRef &ref, NetPacketBuf buf); }; struct NetPacketAckCommandBase @@ -339,6 +429,7 @@ struct NetPacketFrameCommandData static size_t getSize(const NetCommandMsg &msg) { return sizeof(FixedData); } static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref); + static size_t readMessage(NetCommandRef &ref, NetPacketBuf buf); }; struct NetPacketFrameCommandBase @@ -374,6 +465,7 @@ struct NetPacketPlayerLeaveCommandData static size_t getSize(const NetCommandMsg &msg) { return sizeof(FixedData); } static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref); + static size_t readMessage(NetCommandRef &ref, NetPacketBuf buf); }; struct NetPacketPlayerLeaveCommandBase @@ -410,6 +502,7 @@ struct NetPacketRunAheadMetricsCommandData static size_t getSize(const NetCommandMsg &msg) { return sizeof(FixedData); } static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref); + static size_t readMessage(NetCommandRef &ref, NetPacketBuf buf); }; struct NetPacketRunAheadMetricsCommandBase @@ -446,6 +539,7 @@ struct NetPacketRunAheadCommandData static size_t getSize(const NetCommandMsg &msg) { return sizeof(FixedData); } static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref); + static size_t readMessage(NetCommandRef &ref, NetPacketBuf buf); }; struct NetPacketRunAheadCommandBase @@ -481,6 +575,7 @@ struct NetPacketDestroyPlayerCommandData static size_t getSize(const NetCommandMsg &msg) { return sizeof(FixedData); } static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref); + static size_t readMessage(NetCommandRef &ref, NetPacketBuf buf); }; struct NetPacketDestroyPlayerCommandBase @@ -521,6 +616,7 @@ struct NetPacketKeepAliveCommandBase static size_t getSize() { return sizeof(CommandBase); } static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref); + static size_t readMessage(NetCommandRef &ref, NetPacketBuf buf); }; //////////////////////////////////////////////////////////////////////////////// @@ -560,6 +656,7 @@ struct NetPacketDisconnectPlayerCommandData static size_t getSize(const NetCommandMsg &msg) { return sizeof(FixedData); } static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref); + static size_t readMessage(NetCommandRef &ref, NetPacketBuf buf); }; struct NetPacketDisconnectPlayerCommandBase @@ -639,6 +736,7 @@ struct NetPacketDisconnectVoteCommandData static size_t getSize(const NetCommandMsg &msg) { return sizeof(FixedData); } static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref); + static size_t readMessage(NetCommandRef &ref, NetPacketBuf buf); }; struct NetPacketDisconnectVoteCommandBase @@ -669,6 +767,7 @@ struct NetPacketChatCommandData static size_t getSize(const NetCommandMsg &msg); static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref); + static size_t readMessage(NetCommandRef &ref, NetPacketBuf buf); }; struct NetPacketChatCommandBase @@ -699,6 +798,7 @@ struct NetPacketDisconnectChatCommandData static size_t getSize(const NetCommandMsg &msg); static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref); + static size_t readMessage(NetCommandRef &ref, NetPacketBuf buf); }; struct NetPacketDisconnectChatCommandBase @@ -729,6 +829,7 @@ struct NetPacketGameCommandData static size_t getSize(const NetCommandMsg &msg); static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref); + static size_t readMessage(NetCommandRef &ref, NetPacketBuf buf); }; struct NetPacketGameCommandBase @@ -769,6 +870,7 @@ struct NetPacketWrapperCommandData static size_t getSize(const NetCommandMsg &msg); static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref); + static size_t readMessage(NetCommandRef &ref, NetPacketBuf buf); }; struct NetPacketWrapperCommandBase @@ -798,6 +900,7 @@ struct NetPacketFileCommandData static size_t getSize(const NetCommandMsg &msg); static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref); + static size_t readMessage(NetCommandRef &ref, NetPacketBuf buf); }; struct NetPacketFileCommandBase @@ -828,6 +931,7 @@ struct NetPacketFileAnnounceCommandData static size_t getSize(const NetCommandMsg &msg); static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref); + static size_t readMessage(NetCommandRef &ref, NetPacketBuf buf); }; struct NetPacketFileAnnounceCommandBase @@ -864,6 +968,7 @@ struct NetPacketFileProgressCommandData static size_t getSize(const NetCommandMsg &msg) { return sizeof(FixedData); } static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref); + static size_t readMessage(NetCommandRef &ref, NetPacketBuf buf); }; struct NetPacketFileProgressCommandBase @@ -899,6 +1004,7 @@ struct NetPacketProgressCommandData static size_t getSize(const NetCommandMsg &msg) { return sizeof(FixedData); } static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref); + static size_t readMessage(NetCommandRef &ref, NetPacketBuf buf); }; struct NetPacketProgressCommandBase @@ -978,6 +1084,7 @@ struct NetPacketDisconnectFrameCommandData static size_t getSize(const NetCommandMsg &msg) { return sizeof(FixedData); } static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref); + static size_t readMessage(NetCommandRef &ref, NetPacketBuf buf); }; struct NetPacketDisconnectFrameCommandBase @@ -1013,6 +1120,7 @@ struct NetPacketDisconnectScreenOffCommandData static size_t getSize(const NetCommandMsg &msg) { return sizeof(FixedData); } static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref); + static size_t readMessage(NetCommandRef &ref, NetPacketBuf buf); }; struct NetPacketDisconnectScreenOffCommandBase @@ -1048,6 +1156,7 @@ struct NetPacketFrameResendRequestCommandData static size_t getSize(const NetCommandMsg &msg) { return sizeof(FixedData); } static size_t copyBytes(UnsignedByte *buffer, const NetCommandRef &ref); + static size_t readMessage(NetCommandRef &ref, NetPacketBuf buf); }; struct NetPacketFrameResendRequestCommandBase diff --git a/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp b/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp index 6aed5ac39dc..9ea8927d696 100644 --- a/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp +++ b/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp @@ -836,7 +836,7 @@ void ConnectionManager::processFile(NetFileCommandMsg *msg) // uncompress Targas #ifdef COMPRESS_TARGAS Bool deleteBuf = FALSE; - if (msg->getFilename().endsWith(".tga") && CompressionManager::isDataCompressed(buf, len)) + if (msg->getPortableFilename().endsWith(".tga") && CompressionManager::isDataCompressed(buf, len)) { Int uncompLen = CompressionManager::getUncompressedSize(buf, len); UnsignedByte *uncompBuffer = NEW UnsignedByte[uncompLen]; @@ -2324,6 +2324,7 @@ void ConnectionManager::sendFile(AsciiString path, UnsignedByte playerMask, Unsi Int len = theFile->size(); char *buf = theFile->readEntireAndClose(); + NetCommandDataChunk rawDataChunk(buf, len); // compress Targas #ifdef COMPRESS_TARGAS @@ -2339,35 +2340,31 @@ void ConnectionManager::sendFile(AsciiString path, UnsignedByte playerMask, Unsi delete[] compressedBuf; compressedBuf = nullptr; } + + NetCommandDataChunk compressedDataChunk(compressedBuf, compressedSize); #endif // COMPRESS_TARGAS NetFileCommandMsg *fileMsg = newInstance(NetFileCommandMsg); fileMsg->setPlayerID(m_localSlot); fileMsg->setID(commandID); fileMsg->setRealFilename(path); + #ifdef COMPRESS_TARGAS if (compressedBuf) { DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("Compressed '%s' from %d to %d (%g%%) before transfer", path.str(), len, compressedSize, (Real)compressedSize/(Real)len*100.0f)); - fileMsg->setFileData((unsigned char *)compressedBuf, compressedSize); + fileMsg->setFileData(compressedDataChunk); } else #endif // COMPRESS_TARGAS { - fileMsg->setFileData((unsigned char *)buf, len); + fileMsg->setFileData(rawDataChunk); } DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("ConnectionManager::sendFile() - creating file message with ID of %d for '%s' going to %X from %d, size of %d", fileMsg->getID(), fileMsg->getRealFilename().str(), playerMask, fileMsg->getPlayerID(), fileMsg->getFileLength())); - delete[] buf; - buf = nullptr; -#ifdef COMPRESS_TARGAS - delete[] compressedBuf; - compressedBuf = nullptr; -#endif // COMPRESS_TARGAS - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("Sending file: '%s', len %d, to %X", path.str(), len, playerMask)); sendLocalCommand(fileMsg, playerMask); diff --git a/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp b/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp index b6a834c987b..579b8578c72 100644 --- a/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp @@ -128,13 +128,19 @@ void NetCommandList::reset() { */ NetCommandRef * NetCommandList::addMessage(NetCommandMsg *cmdMsg) { if (cmdMsg == nullptr) { - DEBUG_ASSERTCRASH(cmdMsg != nullptr, ("NetCommandList::addMessage - command message was null")); + DEBUG_CRASH(("NetCommandList::addMessage - command message was null")); return nullptr; } -// UnsignedInt id = cmdMsg->getID(); - NetCommandRef *msg = NEW_NETCOMMANDREF(cmdMsg); + return addMessage(msg); +} + +NetCommandRef * NetCommandList::addMessage(NetCommandRef *&msg) { + if (msg == nullptr) { + DEBUG_CRASH(("NetCommandList::addMessage - command ref was null")); + return nullptr; + } if (m_first == nullptr) { // this is the first node, so we don't have to worry about ordering it. @@ -304,6 +310,7 @@ NetCommandRef * NetCommandList::addMessage(NetCommandMsg *cmdMsg) { // This command is already in the list, don't duplicate it. deleteInstance(msg); + msg = nullptr; return nullptr; } diff --git a/Core/GameEngine/Source/GameNetwork/NetCommandMsg.cpp b/Core/GameEngine/Source/GameNetwork/NetCommandMsg.cpp index dd652a6cba3..4be0ff253e7 100644 --- a/Core/GameEngine/Source/GameNetwork/NetCommandMsg.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetCommandMsg.cpp @@ -937,12 +937,11 @@ UnsignedByte * NetWrapperCommandMsg::getData() { return m_data; } -void NetWrapperCommandMsg::setData(UnsignedByte *data, UnsignedInt dataLength) +void NetWrapperCommandMsg::setData(NetCommandDataChunk &dataChunk) { delete[] m_data; - m_data = NEW UnsignedByte[dataLength]; // pool[]ify - memcpy(m_data, data, dataLength); - m_dataLength = dataLength; + m_dataLength = dataChunk.size(); + m_data = dataChunk.release(); } UnsignedInt NetWrapperCommandMsg::getDataLength() const { @@ -1036,11 +1035,11 @@ UnsignedByte * NetFileCommandMsg::getFileData() { return m_data; } -void NetFileCommandMsg::setFileData(UnsignedByte *data, UnsignedInt dataLength) +void NetFileCommandMsg::setFileData(NetCommandDataChunk &dataChunk) { - m_dataLength = dataLength; - m_data = NEW UnsignedByte[dataLength]; // pool[]ify - memcpy(m_data, data, dataLength); + delete[] m_data; + m_dataLength = dataChunk.size(); + m_data = dataChunk.release(); } NetCommandMsg::Select NetFileCommandMsg::getSmallNetPacketSelect() const { diff --git a/Core/GameEngine/Source/GameNetwork/NetCommandRef.cpp b/Core/GameEngine/Source/GameNetwork/NetCommandRef.cpp index 470a387ff77..ba59cab5694 100644 --- a/Core/GameEngine/Source/GameNetwork/NetCommandRef.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetCommandRef.cpp @@ -41,9 +41,13 @@ NetCommandRef::NetCommandRef(NetCommandMsg *msg) #endif { m_msg = msg; + if (m_msg != nullptr) + { + m_msg->attach(); + } m_next = nullptr; m_prev = nullptr; - m_msg->attach(); + m_relay = 0; m_timeLastSent = -1; #ifdef DEBUG_NETCOMMANDREF @@ -61,7 +65,7 @@ NetCommandRef::~NetCommandRef() { m_msg->detach(); } - DEBUG_ASSERTCRASH(m_next == nullptr, ("NetCommandRef::~NetCommandRef - m_next != nullptr")); + DEBUG_ASSERTCRASH(m_next == nullptr, ("NetCommandRef::~NetCommandRef - m_next != nullptr")); DEBUG_ASSERTCRASH(m_prev == nullptr, ("NetCommandRef::~NetCommandRef - m_prev != nullptr")); #ifdef DEBUG_NETCOMMANDREF diff --git a/Core/GameEngine/Source/GameNetwork/NetPacket.cpp b/Core/GameEngine/Source/GameNetwork/NetPacket.cpp index 2c59457c84c..c04446d7e51 100644 --- a/Core/GameEngine/Source/GameNetwork/NetPacket.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetPacket.cpp @@ -34,160 +34,38 @@ #include "GameNetwork/NetPacketStructs.h" -// This function assumes that all of the fields are either of default value or are -// present in the raw data. -NetCommandRef * NetPacket::ConstructNetCommandMsgFromRawData(UnsignedByte *data, UnsignedShort dataLength) { - NetCommandType commandType = NETCOMMANDTYPE_GAMECOMMAND; - UnsignedByte commandTypeByte = static_cast(commandType); - UnsignedShort commandID = 0; - UnsignedInt frame = 0; - UnsignedByte playerID = 0; - UnsignedByte relay = 0; - - Int offset = 0; - NetCommandRef *ref = nullptr; - NetCommandMsg *msg = nullptr; - - while (offset < (Int)dataLength) { - - switch (data[offset]) { - - case NetPacketFieldTypes::CommandType: - ++offset; - memcpy(&commandTypeByte, data + offset, sizeof(commandTypeByte)); - offset += sizeof(commandTypeByte); - commandType = static_cast(commandTypeByte); - break; - - case NetPacketFieldTypes::Relay: - ++offset; - memcpy(&relay, data + offset, sizeof(relay)); - offset += sizeof(relay); - break; - - case NetPacketFieldTypes::Frame: - ++offset; - memcpy(&frame, data + offset, sizeof(frame)); - offset += sizeof(frame); - break; - - case NetPacketFieldTypes::PlayerId: - ++offset; - memcpy(&playerID, data + offset, sizeof(playerID)); - offset += sizeof(playerID); - break; - - case NetPacketFieldTypes::CommandId: - ++offset; - memcpy(&commandID, data + offset, sizeof(commandID)); - offset += sizeof(commandID); - break; - - case NetPacketFieldTypes::Data: - ++offset; - - switch (commandType) { - - case NETCOMMANDTYPE_GAMECOMMAND: - msg = readGameMessage(data, offset); - break; - case NETCOMMANDTYPE_ACKBOTH: - msg = readAckBothMessage(data, offset); - break; - case NETCOMMANDTYPE_ACKSTAGE1: - msg = readAckStage1Message(data, offset); - break; - case NETCOMMANDTYPE_ACKSTAGE2: - msg = readAckStage2Message(data, offset); - break; - case NETCOMMANDTYPE_FRAMEINFO: - msg = readFrameMessage(data, offset); - break; - case NETCOMMANDTYPE_PLAYERLEAVE: - msg = readPlayerLeaveMessage(data, offset); - break; - case NETCOMMANDTYPE_RUNAHEADMETRICS: - msg = readRunAheadMetricsMessage(data, offset); - break; - case NETCOMMANDTYPE_RUNAHEAD: - msg = readRunAheadMessage(data, offset); - break; - case NETCOMMANDTYPE_DESTROYPLAYER: - msg = readDestroyPlayerMessage(data, offset); - break; - case NETCOMMANDTYPE_KEEPALIVE: - msg = readKeepAliveMessage(data, offset); - break; - case NETCOMMANDTYPE_DISCONNECTKEEPALIVE: - msg = readDisconnectKeepAliveMessage(data, offset); - break; - case NETCOMMANDTYPE_DISCONNECTPLAYER: - msg = readDisconnectPlayerMessage(data, offset); - break; - case NETCOMMANDTYPE_PACKETROUTERQUERY: - msg = readPacketRouterQueryMessage(data, offset); - break; - case NETCOMMANDTYPE_PACKETROUTERACK: - msg = readPacketRouterAckMessage(data, offset); - break; - case NETCOMMANDTYPE_DISCONNECTCHAT: - msg = readDisconnectChatMessage(data, offset); - break; - case NETCOMMANDTYPE_DISCONNECTVOTE: - msg = readDisconnectVoteMessage(data, offset); - break; - case NETCOMMANDTYPE_CHAT: - msg = readChatMessage(data, offset); - break; - case NETCOMMANDTYPE_PROGRESS: - msg = readProgressMessage(data, offset); - break; - case NETCOMMANDTYPE_LOADCOMPLETE: - msg = readLoadCompleteMessage(data, offset); - break; - case NETCOMMANDTYPE_TIMEOUTSTART: - msg = readTimeOutGameStartMessage(data, offset); - break; - case NETCOMMANDTYPE_WRAPPER: - msg = readWrapperMessage(data, offset); - break; - case NETCOMMANDTYPE_FILE: - msg = readFileMessage(data, offset); - break; - case NETCOMMANDTYPE_FILEANNOUNCE: - msg = readFileAnnounceMessage(data, offset); - break; - case NETCOMMANDTYPE_FILEPROGRESS: - msg = readFileProgressMessage(data, offset); - break; - case NETCOMMANDTYPE_DISCONNECTFRAME: - msg = readDisconnectFrameMessage(data, offset); - break; - case NETCOMMANDTYPE_DISCONNECTSCREENOFF: - msg = readDisconnectScreenOffMessage(data, offset); - break; - case NETCOMMANDTYPE_FRAMERESENDREQUEST: - msg = readFrameResendRequestMessage(data, offset); - break; - - } - - msg->setExecutionFrame(frame); - msg->setID(commandID); - msg->setPlayerID(playerID); - msg->setNetCommandType(commandType); - - ref = NEW_NETCOMMANDREF(msg); - - ref->setRelay(relay); +static size_t constructNetCommandRef(NetCommandRef *&ref, SmallNetPacketCommandBase::CommandBase &base, NetPacketBuf buf) +{ + size_t size = SmallNetPacketCommandBase::readMessage(ref, base, buf); - msg->detach(); - msg = nullptr; + if (ref != nullptr) + { + DEBUG_ASSERTCRASH(ref->getCommand() != nullptr, ("constructNetCommandRef: ref->getCommand() is null")); + size += ref->getCommand()->readMessageData(*ref, buf.offset(size)); + } - return ref; + return size; +} - } +// This function assumes that all of the fields are either of default value or are +// present in the raw data. +NetCommandRef *NetPacket::ConstructNetCommandMsgFromRawData(const UnsignedByte *data, UnsignedInt dataLength) { + SmallNetPacketCommandBase::CommandBase commandBase; + commandBase.commandType.commandType = static_cast(NETCOMMANDTYPE_GAMECOMMAND); + commandBase.relay.relay = 0; + commandBase.frame.frame = 0; + commandBase.playerId.playerId = 0; + commandBase.commandId.commandId = 0; + + NetPacketBuf buf(data, dataLength); + NetCommandRef *ref = nullptr; + constructNetCommandRef(ref, commandBase, buf); + if (ref == nullptr) + { + DEBUG_CRASH(("Unrecognized packet entry, ignoring.")); + DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::ConstructNetCommandMsgFromRawData - Unrecognized packet")); + dumpPacketToLog(data, dataLength); } return ref; @@ -230,10 +108,12 @@ NetPacketList NetPacket::ConstructBigCommandPacketList(NetCommandRef *ref) { while (currentChunk < numChunks) { NetPacket *packet = newInstance(NetPacket); - UnsignedShort dataSizeThisPacket = commandSizePerPacket; + UnsignedInt dataSizeThisPacket = commandSizePerPacket; if ((bufferSize - bigPacketCurrentOffset) < dataSizeThisPacket) { dataSizeThisPacket = bufferSize - bigPacketCurrentOffset; } + NetCommandDataChunk bigPacket(dataSizeThisPacket); + memcpy(bigPacket.data(), bigPacketData + bigPacketCurrentOffset, bigPacket.size()); if (DoesCommandRequireACommandID(wrapperMsg->getNetCommandType())) { wrapperMsg->setID(GenerateNextCommandID()); @@ -244,13 +124,13 @@ NetPacketList NetPacket::ConstructBigCommandPacketList(NetCommandRef *ref) { wrapperMsg->setChunkNumber(currentChunk); wrapperMsg->setNumChunks(numChunks); wrapperMsg->setDataOffset(bigPacketCurrentOffset); - wrapperMsg->setData(bigPacketData + bigPacketCurrentOffset, dataSizeThisPacket); + wrapperMsg->setData(bigPacket); wrapperMsg->setTotalDataLength(bufferSize); wrapperMsg->setWrappedCommandID(msg->getID()); bigPacketCurrentOffset += dataSizeThisPacket; - NetCommandRef * ref = NEW_NETCOMMANDREF(wrapperMsg); + NetCommandRef *ref = NEW_NETCOMMANDREF(wrapperMsg); ref->setRelay(ref->getRelay()); if (packet->addCommand(ref) == FALSE) { @@ -527,204 +407,58 @@ Bool NetPacket::isAckStage2Repeat(NetCommandRef *msg) { */ NetCommandList * NetPacket::getCommandList() { NetCommandList *retval = newInstance(NetCommandList); -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::getCommandList, packet length = %d", m_packetLen)); retval->init(); // These need to be the same as the default values for m_lastPlayerID, m_lastFrame, etc. - UnsignedByte playerID = 0; - UnsignedInt frame = 0; - UnsignedShort commandID = 1; // The first command is going to be - UnsignedByte commandType = 0; - UnsignedByte relay = 0; + SmallNetPacketCommandBase::CommandBase commandBase; + commandBase.commandType.commandType = 0; + commandBase.relay.relay = 0; + commandBase.frame.frame = 0; + commandBase.playerId.playerId = 0; + commandBase.commandId.commandId = 1; // The first command is going to be + NetCommandRef *lastCommand = nullptr; Int i = 0; - while (i < m_packetLen) { - - switch(m_packet[i]) { - - case NetPacketFieldTypes::CommandType: - ++i; - memcpy(&commandType, m_packet + i, sizeof(UnsignedByte)); - i += sizeof(UnsignedByte); - break; - case NetPacketFieldTypes::Frame: - ++i; - memcpy(&frame, m_packet + i, sizeof(UnsignedInt)); - i += sizeof(UnsignedInt); - break; - case NetPacketFieldTypes::PlayerId: - ++i; - memcpy(&playerID, m_packet + i, sizeof(UnsignedByte)); - i += sizeof(UnsignedByte); - break; - case NetPacketFieldTypes::Relay: - ++i; - memcpy(&relay, m_packet + i, sizeof(UnsignedByte)); - i += sizeof(UnsignedByte); - break; - case NetPacketFieldTypes::CommandId: - ++i; - memcpy(&commandID, m_packet + i, sizeof(UnsignedShort)); - i += sizeof(UnsignedShort); - break; - case NetPacketFieldTypes::Data: { - ++i; + NetPacketBuf buf(m_packet, m_packetLen); - NetCommandMsg *msg = nullptr; + while (i < buf.size()) + { + const Bool isRepeat = m_packet[i] == NetPacketFieldTypes::Repeat; - //DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::getCommandList() - command of type %d(%s)", commandType, GetNetCommandTypeAsString((NetCommandType)commandType))); + if (!isRepeat) + { + NetCommandRef *ref = nullptr; + i += constructNetCommandRef(ref, commandBase, buf.offset(i)); - switch((NetCommandType)commandType) + if (ref == nullptr) { - case NETCOMMANDTYPE_GAMECOMMAND: - msg = readGameMessage(m_packet, i); - //DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read game command from player %d for frame %d", playerID, frame)); - break; - case NETCOMMANDTYPE_ACKBOTH: - msg = readAckBothMessage(m_packet, i); - break; - case NETCOMMANDTYPE_ACKSTAGE1: - msg = readAckStage1Message(m_packet, i); - break; - case NETCOMMANDTYPE_ACKSTAGE2: - msg = readAckStage2Message(m_packet, i); - break; - case NETCOMMANDTYPE_FRAMEINFO: - msg = readFrameMessage(m_packet, i); - // frameinfodebug - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read frame %d from player %d, command count = %d, relay = 0x%X", frame, playerID, ((NetFrameCommandMsg *)msg)->getCommandCount(), relay)); - break; - case NETCOMMANDTYPE_PLAYERLEAVE: - msg = readPlayerLeaveMessage(m_packet, i); -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read player leave message from player %d for execution on frame %d", playerID, frame)); - break; - case NETCOMMANDTYPE_RUNAHEADMETRICS: - msg = readRunAheadMetricsMessage(m_packet, i); - break; - case NETCOMMANDTYPE_RUNAHEAD: - msg = readRunAheadMessage(m_packet, i); -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read run ahead message from player %d for execution on frame %d", playerID, frame)); - break; - case NETCOMMANDTYPE_DESTROYPLAYER: - msg = readDestroyPlayerMessage(m_packet, i); -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read CRC info message from player %d for execution on frame %d", playerID, frame)); - break; - case NETCOMMANDTYPE_KEEPALIVE: - msg = readKeepAliveMessage(m_packet, i); -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read keep alive message from player %d", playerID)); - break; - case NETCOMMANDTYPE_DISCONNECTKEEPALIVE: - msg = readDisconnectKeepAliveMessage(m_packet, i); -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read keep alive message from player %d", playerID)); - break; - case NETCOMMANDTYPE_DISCONNECTPLAYER: - msg = readDisconnectPlayerMessage(m_packet, i); -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read disconnect player message from player %d", playerID)); - break; - case NETCOMMANDTYPE_PACKETROUTERQUERY: - msg = readPacketRouterQueryMessage(m_packet, i); -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read packet router query message from player %d", playerID)); - break; - case NETCOMMANDTYPE_PACKETROUTERACK: - msg = readPacketRouterAckMessage(m_packet, i); -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read packet router ack message from player %d", playerID)); - break; - case NETCOMMANDTYPE_DISCONNECTCHAT: - msg = readDisconnectChatMessage(m_packet, i); -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read disconnect chat message from player %d", playerID)); - break; - case NETCOMMANDTYPE_DISCONNECTVOTE: - msg = readDisconnectVoteMessage(m_packet, i); -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read disconnect vote message from player %d", playerID)); - break; - case NETCOMMANDTYPE_CHAT: - msg = readChatMessage(m_packet, i); -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read chat message from player %d", playerID)); - break; - case NETCOMMANDTYPE_PROGRESS: - msg = readProgressMessage(m_packet, i); -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read Progress message from player %d", playerID)); - break; - case NETCOMMANDTYPE_LOADCOMPLETE: - msg = readLoadCompleteMessage(m_packet, i); -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read LoadComplete message from player %d", playerID)); - break; - case NETCOMMANDTYPE_TIMEOUTSTART: - msg = readTimeOutGameStartMessage(m_packet, i); -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read TimeOutGameStart message from player %d", playerID)); - break; - case NETCOMMANDTYPE_WRAPPER: - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read Wrapper message from player %d", playerID)); - msg = readWrapperMessage(m_packet, i); - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("Done reading Wrapper message from player %d - wrapped command was %d", playerID, - ((NetWrapperCommandMsg *)msg)->getWrappedCommandID())); - break; - case NETCOMMANDTYPE_FILE: - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read file message from player %d", playerID)); - msg = readFileMessage(m_packet, i); - break; - case NETCOMMANDTYPE_FILEANNOUNCE: - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read file announce message from player %d", playerID)); - msg = readFileAnnounceMessage(m_packet, i); - break; - case NETCOMMANDTYPE_FILEPROGRESS: - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read file progress message from player %d", playerID)); - msg = readFileProgressMessage(m_packet, i); - break; - case NETCOMMANDTYPE_DISCONNECTFRAME: - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read disconnect frame message from player %d", playerID)); - msg = readDisconnectFrameMessage(m_packet, i); - break; - case NETCOMMANDTYPE_DISCONNECTSCREENOFF: - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read disconnect screen off message from player %d", playerID)); - msg = readDisconnectScreenOffMessage(m_packet, i); - break; - case NETCOMMANDTYPE_FRAMERESENDREQUEST: - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("read frame resend request message from player %d", playerID)); - msg = readFrameResendRequestMessage(m_packet, i); - break; - } - - if (msg == nullptr) { - DEBUG_CRASH(("Didn't read a message from the packet. Things are about to go wrong.")); + // we don't recognize this command, but we have to increment i so we don't fall into an infinite loop. + DEBUG_CRASH(("Unrecognized packet entry, ignoring.")); + DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::getCommandList - Unrecognized packet entry at index %d", i)); + dumpPacketToLog(m_packet, m_packetLen); continue; } - // set the info - msg->setExecutionFrame(frame); - msg->setPlayerID(playerID); - msg->setNetCommandType((NetCommandType)commandType); - msg->setID(commandID); - -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("frame = %d, player = %d, command type = %d, id = %d", frame, playerID, commandType, commandID)); - // increment to the next command ID. - if (DoesCommandRequireACommandID((NetCommandType)commandType)) { - ++commandID; + if (DoesCommandRequireACommandID((NetCommandType)commandBase.commandType.commandType)) { + ++commandBase.commandId.commandId; } + NetCommandMsg *msg = ref->getCommand(); + msg->attach(); + // add the message to the list. - NetCommandRef *ref = retval->addMessage(msg); - if (ref != nullptr) { - ref->setRelay(relay); - } else { - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::getCommandList - failed to set relay for message %d", msg->getID())); - } + retval->addMessage(ref); deleteInstance(lastCommand); - lastCommand = newInstance(NetCommandRef)(msg); - - msg->detach(); // Need to detach from new NetCommandMsg created by the "readXMessage" above. - - // since the message is part of the list now, we don't have to keep track of it. So we'll just set it to null. - msg = nullptr; - break; + lastCommand = NEW_NETCOMMANDREF(msg); + msg->detach(); } + else + { + i += NetPacketRepeatCommand::getSize(); - case 'Z': { - - ++i; // Repeat the last command, doing some funky cool byte-saving stuff if (lastCommand == nullptr) { DEBUG_CRASH(("Got a repeat command with no command to repeat.")); @@ -732,703 +466,69 @@ NetCommandList * NetPacket::getCommandList() { NetCommandMsg *msg = nullptr; - switch(commandType) { - + switch (commandBase.commandType.commandType) + { case NETCOMMANDTYPE_ACKSTAGE1: { msg = newInstance(NetAckStage1CommandMsg)(); - NetAckStage1CommandMsg* laststageone = (NetAckStage1CommandMsg*)(lastCommand->getCommand()); + NetAckStage1CommandMsg* laststageone = static_cast(lastCommand->getCommand()); ((NetAckStage1CommandMsg*)msg)->setCommandID(laststageone->getCommandID() + 1); ((NetAckStage1CommandMsg*)msg)->setOriginalPlayerID(laststageone->getOriginalPlayerID()); break; } case NETCOMMANDTYPE_ACKSTAGE2: { msg = newInstance(NetAckStage2CommandMsg)(); - NetAckStage2CommandMsg* laststagetwo = (NetAckStage2CommandMsg*)(lastCommand->getCommand()); + NetAckStage2CommandMsg* laststagetwo = static_cast(lastCommand->getCommand()); ((NetAckStage2CommandMsg*)msg)->setCommandID(laststagetwo->getCommandID() + 1); ((NetAckStage2CommandMsg*)msg)->setOriginalPlayerID(laststagetwo->getOriginalPlayerID()); break; } case NETCOMMANDTYPE_ACKBOTH: { msg = newInstance(NetAckBothCommandMsg)(); - NetAckBothCommandMsg* lastboth = (NetAckBothCommandMsg*)(lastCommand->getCommand()); + NetAckBothCommandMsg* lastboth = static_cast(lastCommand->getCommand()); ((NetAckBothCommandMsg*)msg)->setCommandID(lastboth->getCommandID() + 1); ((NetAckBothCommandMsg*)msg)->setOriginalPlayerID(lastboth->getOriginalPlayerID()); break; } case NETCOMMANDTYPE_FRAMEINFO: { msg = newInstance(NetFrameCommandMsg)(); - ++frame; // this is set below. + ++commandBase.frame.frame; // this is set below. ((NetFrameCommandMsg*)msg)->setCommandCount(0); - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("Read a repeated frame command, frame = %d, player = %d, commandID = %d", frame, playerID, commandID)); + DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("Read a repeated frame command, frame = %d, playerId = %d, commandId = %d", + commandBase.frame.frame, commandBase.playerId.playerId, commandBase.commandId.commandId)); break; } default: DEBUG_CRASH(("Trying to repeat a command that shouldn't be repeated.")); continue; - } - msg->setExecutionFrame(frame); - msg->setPlayerID(playerID); - msg->setNetCommandType((NetCommandType)commandType); - msg->setID(commandID); - -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("frame = %d, player = %d, command type = %d, id = %d", frame, playerID, commandType, commandID)); + msg->setExecutionFrame(commandBase.frame.frame); + msg->setPlayerID(commandBase.playerId.playerId); + msg->setNetCommandType((NetCommandType)commandBase.commandType.commandType); + msg->setID(commandBase.commandId.commandId); // increment to the next command ID. - if (DoesCommandRequireACommandID((NetCommandType)commandType)) { - ++commandID; + if (DoesCommandRequireACommandID((NetCommandType)commandBase.commandType.commandType)) { + ++commandBase.commandId.commandId; } // add the message to the list. NetCommandRef *ref = retval->addMessage(msg); if (ref != nullptr) { - ref->setRelay(relay); + ref->setRelay(commandBase.relay.relay); } deleteInstance(lastCommand); -// lastCommand = newInstance(NetCommandRef)(msg); lastCommand = NEW_NETCOMMANDREF(msg); - - msg->detach(); // Need to detach from new NetCommandMsg created by the "readXMessage" above. - - // since the message is part of the list now, we don't have to keep track of it. So we'll just set it to null. - msg = nullptr; - break; - } - - default: - // we don't recognize this command, but we have to increment i so we don't fall into an infinite loop. - DEBUG_CRASH(("Unrecognized packet entry, ignoring.")); - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::getCommandList - Unrecognized packet entry at index %d", i)); - dumpPacketToLog(); - ++i; - break; - + msg->detach(); } - } deleteInstance(lastCommand); - lastCommand = nullptr; return retval; } -/** - * Reads the data portion of a game message from the given position in the packet. - */ -NetCommandMsg * NetPacket::readGameMessage(UnsignedByte *data, Int &i) -{ - NetGameCommandMsg *msg = newInstance(NetGameCommandMsg); - -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::readGameMessage")); - - // Get the GameMessage command type. - GameMessage::Type newType; - memcpy(&newType, data + i, sizeof(GameMessage::Type)); - i += sizeof(GameMessage::Type); - msg->setGameMessageType(newType); - - // Get the number of argument types - UnsignedByte numArgTypes = 0; - memcpy(&numArgTypes, data + i, sizeof(numArgTypes)); - i += sizeof(numArgTypes); - - // Get the types and the number of arguments of those types. - Int totalArgCount = 0; - GameMessageParser *parser = newInstance(GameMessageParser)(); - Int j = 0; - for (; j < numArgTypes; ++j) { - UnsignedByte type = (UnsignedByte)ARGUMENTDATATYPE_UNKNOWN; - memcpy(&type, data + i, sizeof(type)); - i += sizeof(type); - - UnsignedByte argCount = 0; - memcpy(&argCount, data + i, sizeof(argCount)); - i += sizeof(argCount); - - parser->addArgType((GameMessageArgumentDataType)type, argCount); - totalArgCount += argCount; - } - - GameMessageParserArgumentType *parserArgType = parser->getFirstArgumentType(); - GameMessageArgumentDataType lasttype = ARGUMENTDATATYPE_UNKNOWN; - Int argsLeftForType = 0; - if (parserArgType != nullptr) { - lasttype = parserArgType->getType(); - argsLeftForType = parserArgType->getArgCount(); - } - for (j = 0; j < totalArgCount; ++j) { - readGameMessageArgumentFromPacket(lasttype, msg, data, i); - - --argsLeftForType; - if (argsLeftForType == 0) { - DEBUG_ASSERTCRASH(parserArgType != nullptr, ("parserArgType was null when it shouldn't have been.")); - if (parserArgType == nullptr) { - return nullptr; - } - - parserArgType = parserArgType->getNext(); - // parserArgType is allowed to be null here - if (parserArgType != nullptr) { - argsLeftForType = parserArgType->getArgCount(); - lasttype = parserArgType->getType(); - } - } - } - - deleteInstance(parser); - parser = nullptr; - - return (NetCommandMsg *)msg; -} - -void NetPacket::readGameMessageArgumentFromPacket(GameMessageArgumentDataType type, NetGameCommandMsg *msg, UnsignedByte *data, Int &i) { - - GameMessageArgumentType arg; - - switch (type) { - - case ARGUMENTDATATYPE_INTEGER: - Int theint; - memcpy(&theint, data + i, sizeof(theint)); - i += sizeof(theint); - arg.integer = theint; - msg->addArgument(type, arg); - break; - - case ARGUMENTDATATYPE_REAL: - Real thereal; - memcpy(&thereal, data + i, sizeof(thereal)); - i += sizeof(thereal); - arg.real = thereal; - msg->addArgument(type, arg); - break; - - case ARGUMENTDATATYPE_BOOLEAN: - Bool thebool; - memcpy(&thebool, data + i, sizeof(thebool)); - i += sizeof(thebool); - arg.boolean = thebool; - msg->addArgument(type, arg); - break; - - case ARGUMENTDATATYPE_OBJECTID: - ObjectID theobjectid; - memcpy(&theobjectid, data + i, sizeof(theobjectid)); - i += sizeof(theobjectid); - arg.objectID = theobjectid; - msg->addArgument(type, arg); - break; - - case ARGUMENTDATATYPE_DRAWABLEID: - DrawableID thedrawableid; - memcpy(&thedrawableid, data + i, sizeof(thedrawableid)); - i += sizeof(thedrawableid); - arg.drawableID = thedrawableid; - msg->addArgument(type, arg); - break; - - case ARGUMENTDATATYPE_TEAMID: - UnsignedInt theunsignedint; - memcpy(&theunsignedint, data + i, sizeof(theunsignedint)); - i += sizeof(theunsignedint); - arg.teamID = theunsignedint; - msg->addArgument(type, arg); - break; - - case ARGUMENTDATATYPE_LOCATION: - Coord3D coord; - memcpy(&coord, data + i, sizeof(coord)); - i += sizeof(coord); - arg.location = coord; - msg->addArgument(type, arg); - break; - - case ARGUMENTDATATYPE_PIXEL: - ICoord2D pixel; - memcpy(&pixel, data + i, sizeof(pixel)); - i += sizeof(pixel); - arg.pixel = pixel; - msg->addArgument(type, arg); - break; - - case ARGUMENTDATATYPE_PIXELREGION: - IRegion2D reg; - memcpy(®, data + i, sizeof(reg)); - i += sizeof(reg); - arg.pixelRegion = reg; - msg->addArgument(type, arg); - break; - - case ARGUMENTDATATYPE_TIMESTAMP: - UnsignedInt stamp; - memcpy(&stamp, data + i, sizeof(stamp)); - i += sizeof(stamp); - arg.timestamp = stamp; - msg->addArgument(type, arg); - break; - - case ARGUMENTDATATYPE_WIDECHAR: - WideChar c; - memcpy(&c, data + i, sizeof(c)); - i += sizeof(c); - arg.wChar = c; - msg->addArgument(type, arg); - break; - - } - -} - -/** - * Reads the data portion of the ack message at this position in the packet. - */ -NetCommandMsg * NetPacket::readAckBothMessage(UnsignedByte *data, Int &i) { - NetAckBothCommandMsg *msg = newInstance(NetAckBothCommandMsg); - - //DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::readAckMessage, ")); - UnsignedShort cmdID = 0; - - memcpy(&cmdID, data + i, sizeof(UnsignedShort)); - i += sizeof(UnsignedShort); - msg->setCommandID(cmdID); - //DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("commandID = %d, ", cmdID)); - - UnsignedByte origPlayerID = 0; - memcpy(&origPlayerID, data + i, sizeof(UnsignedByte)); - i += sizeof(UnsignedByte); - msg->setOriginalPlayerID(origPlayerID); - //DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("original player id = %d", origPlayerID)); - - return msg; -} - -/** - * Reads the data portion of the ack message at this position in the packet. - */ -NetCommandMsg * NetPacket::readAckStage1Message(UnsignedByte *data, Int &i) { - NetAckStage1CommandMsg *msg = newInstance(NetAckStage1CommandMsg); - -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::readAckMessage, ")); - UnsignedShort cmdID = 0; - - memcpy(&cmdID, data + i, sizeof(UnsignedShort)); - i += sizeof(UnsignedShort); - msg->setCommandID(cmdID); -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("commandID = %d, ", cmdID)); - - UnsignedByte origPlayerID = 0; - memcpy(&origPlayerID, data + i, sizeof(UnsignedByte)); - i += sizeof(UnsignedByte); - msg->setOriginalPlayerID(origPlayerID); -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("original player id = %d", origPlayerID)); - - return msg; -} - -/** - * Reads the data portion of the ack message at this position in the packet. - */ -NetCommandMsg * NetPacket::readAckStage2Message(UnsignedByte *data, Int &i) { - NetAckStage2CommandMsg *msg = newInstance(NetAckStage2CommandMsg); - -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::readAckMessage, ")); - UnsignedShort cmdID = 0; - - memcpy(&cmdID, data + i, sizeof(UnsignedShort)); - i += sizeof(UnsignedShort); - msg->setCommandID(cmdID); -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("commandID = %d, ", cmdID)); - - UnsignedByte origPlayerID = 0; - memcpy(&origPlayerID, data + i, sizeof(UnsignedByte)); - i += sizeof(UnsignedByte); - msg->setOriginalPlayerID(origPlayerID); -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("original player id = %d", origPlayerID)); - - return msg; -} - -/** - * Reads the data portion of the frame message at this position in the packet. - */ -NetCommandMsg * NetPacket::readFrameMessage(UnsignedByte *data, Int &i) { - NetFrameCommandMsg *msg = newInstance(NetFrameCommandMsg); - -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::readFrameMessage, ")); - UnsignedShort cmdCount = 0; - - memcpy(&cmdCount, data + i, sizeof(UnsignedShort)); - i += sizeof(UnsignedShort); - msg->setCommandCount(cmdCount); -// DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("command count = %d, ", cmdCount)); - - return msg; -} - -/** - * Reads the player leave message at this position in the packet. - */ -NetCommandMsg * NetPacket::readPlayerLeaveMessage(UnsignedByte *data, Int &i) { - NetPlayerLeaveCommandMsg *msg = newInstance(NetPlayerLeaveCommandMsg); - - UnsignedByte leavingPlayerID = 0; - - memcpy(&leavingPlayerID, data + i, sizeof(UnsignedByte)); - i += sizeof(UnsignedByte); - msg->setLeavingPlayerID(leavingPlayerID); - - return msg; -} - -/** - * Reads the run ahead metrics message at this position in the packet. - */ -NetCommandMsg * NetPacket::readRunAheadMetricsMessage(UnsignedByte *data, Int &i) { - NetRunAheadMetricsCommandMsg *msg = newInstance(NetRunAheadMetricsCommandMsg); - - Real averageLatency = (Real)0.2; - UnsignedShort averageFps = 30; - - memcpy(&averageLatency, data + i, sizeof(Real)); - i += sizeof(Real); - msg->setAverageLatency(averageLatency); - - memcpy(&averageFps, data + i, sizeof(UnsignedShort)); - i += sizeof(UnsignedShort); - msg->setAverageFps((Int)averageFps); - return msg; -} - -/** - * Reads the run ahead message at this position in the packet. - */ -NetCommandMsg * NetPacket::readRunAheadMessage(UnsignedByte *data, Int &i) { - NetRunAheadCommandMsg *msg = newInstance(NetRunAheadCommandMsg); - - UnsignedShort newRunAhead = 20; - memcpy(&newRunAhead, data + i, sizeof(UnsignedShort)); - i += sizeof(UnsignedShort); - msg->setRunAhead(newRunAhead); - - UnsignedByte newFrameRate = 30; - memcpy(&newFrameRate, data + i, sizeof(UnsignedByte)); - i += sizeof(UnsignedByte); - msg->setFrameRate(newFrameRate); - - return msg; -} - -/** - * Reads the CRC info message at this position in the packet. - */ -NetCommandMsg * NetPacket::readDestroyPlayerMessage(UnsignedByte *data, Int &i) { - NetDestroyPlayerCommandMsg *msg = newInstance(NetDestroyPlayerCommandMsg); - - UnsignedInt newVal = 0; - memcpy(&newVal, data + i, sizeof(UnsignedInt)); - i += sizeof(UnsignedInt); - msg->setPlayerIndex(newVal); - //DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("Saw CRC of 0x%8.8X", newCRC)); - - return msg; -} - -/** - * Reads the keep alive data, of which there is none. - */ -NetCommandMsg * NetPacket::readKeepAliveMessage(UnsignedByte *data, Int &i) { - NetKeepAliveCommandMsg *msg = newInstance(NetKeepAliveCommandMsg); - - return msg; -} - -/** - * Reads the disconnect keep alive data, of which there is none. - */ -NetCommandMsg * NetPacket::readDisconnectKeepAliveMessage(UnsignedByte *data, Int &i) { - NetDisconnectKeepAliveCommandMsg *msg = newInstance(NetDisconnectKeepAliveCommandMsg); - - return msg; -} - -/** - * Reads the disconnect player data. Which is the slot number of the player being disconnected. - */ -NetCommandMsg * NetPacket::readDisconnectPlayerMessage(UnsignedByte *data, Int &i) { - NetDisconnectPlayerCommandMsg *msg = newInstance(NetDisconnectPlayerCommandMsg); - - UnsignedByte slot = 0; - memcpy(&slot, data + i, sizeof(slot)); - i += sizeof(slot); - msg->setDisconnectSlot(slot); - - UnsignedInt disconnectFrame = 0; - memcpy(&disconnectFrame, data + i, sizeof(disconnectFrame)); - i += sizeof(disconnectFrame); - msg->setDisconnectFrame(disconnectFrame); - - return msg; -} - -/** - * Reads the packet router query data, of which there is none. - */ -NetCommandMsg * NetPacket::readPacketRouterQueryMessage(UnsignedByte *data, Int &i) { - NetPacketRouterQueryCommandMsg *msg = newInstance(NetPacketRouterQueryCommandMsg); - - return msg; -} - -/** - * Reads the packet router ack data, of which there is none. - */ -NetCommandMsg * NetPacket::readPacketRouterAckMessage(UnsignedByte *data, Int &i) { - NetPacketRouterAckCommandMsg *msg = newInstance(NetPacketRouterAckCommandMsg); - - return msg; -} - -/** - * Reads the disconnect chat data, which is just the string. - */ -NetCommandMsg * NetPacket::readDisconnectChatMessage(UnsignedByte *data, Int &i) { - NetDisconnectChatCommandMsg *msg = newInstance(NetDisconnectChatCommandMsg); - - WideChar text[256]; - UnsignedByte length; - memcpy(&length, data + i, sizeof(UnsignedByte)); - ++i; - memcpy(text, data + i, length * sizeof(WideChar)); - i += length * sizeof(WideChar); - text[length] = 0; - - UnicodeString unitext; - unitext.set(text); - - //DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::readDisconnectChatMessage - read message, message is %ls", unitext.str())); - - msg->setText(unitext); - return msg; -} - -/** - * Reads the chat data, which is just the string. - */ -NetCommandMsg * NetPacket::readChatMessage(UnsignedByte *data, Int &i) { - NetChatCommandMsg *msg = newInstance(NetChatCommandMsg); - - WideChar text[256]; - UnsignedByte length; - Int playerMask; - memcpy(&length, data + i, sizeof(UnsignedByte)); - ++i; - memcpy(text, data + i, length * sizeof(WideChar)); - i += length * sizeof(WideChar); - text[length] = 0; - memcpy(&playerMask, data + i, sizeof(Int)); - i += sizeof(Int); - - - UnicodeString unitext; - unitext.set(text); - - //DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::readChatMessage - read message, message is %ls", unitext.str())); - - msg->setText(unitext); - msg->setPlayerMask(playerMask); - return msg; -} - -/** - * Reads the disconnect vote data. Which is the slot number of the player being disconnected. - */ -NetCommandMsg * NetPacket::readDisconnectVoteMessage(UnsignedByte *data, Int &i) { - NetDisconnectVoteCommandMsg *msg = newInstance(NetDisconnectVoteCommandMsg); - - UnsignedByte slot = 0; - memcpy(&slot, data + i, sizeof(slot)); - i += sizeof(slot); - msg->setSlot(slot); - - UnsignedInt voteFrame = 0; - memcpy(&voteFrame, data + i, sizeof(voteFrame)); - i += sizeof(voteFrame); - msg->setVoteFrame(voteFrame); - - return msg; -} - -/** - * Reads the Progress data. Which is the slot number of the player being disconnected. - */ -NetCommandMsg * NetPacket::readProgressMessage(UnsignedByte *data, Int &i) { - NetProgressCommandMsg *msg = newInstance(NetProgressCommandMsg); - - UnsignedByte percentage = 0; - memcpy(&percentage, data + i, sizeof(UnsignedByte)); - i += sizeof(UnsignedByte); - msg->setPercentage(percentage); - - return msg; -} - -NetCommandMsg * NetPacket::readLoadCompleteMessage(UnsignedByte *data, Int &i) { - NetLoadCompleteCommandMsg *msg = newInstance(NetLoadCompleteCommandMsg); - return msg; -} - -NetCommandMsg * NetPacket::readTimeOutGameStartMessage(UnsignedByte *data, Int &i) { - NetTimeOutGameStartCommandMsg *msg = newInstance(NetTimeOutGameStartCommandMsg); - return msg; -} - -NetCommandMsg * NetPacket::readWrapperMessage(UnsignedByte *data, Int &i) { - NetWrapperCommandMsg *msg = newInstance(NetWrapperCommandMsg); - - // get the wrapped command ID - UnsignedShort wrappedCommandID = 0; - memcpy(&wrappedCommandID, data + i, sizeof(wrappedCommandID)); - msg->setWrappedCommandID(wrappedCommandID); - i += sizeof(wrappedCommandID); - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::readWrapperMessage - wrapped command ID == %d", wrappedCommandID)); - - // get the chunk number. - UnsignedInt chunkNumber = 0; - memcpy(&chunkNumber, data + i, sizeof(chunkNumber)); - msg->setChunkNumber(chunkNumber); - i += sizeof(chunkNumber); - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::readWrapperMessage - chunk number = %d", chunkNumber)); - - // get the number of chunks - UnsignedInt numChunks = 0; - memcpy(&numChunks, data + i, sizeof(numChunks)); - msg->setNumChunks(numChunks); - i += sizeof(numChunks); - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::readWrapperMessage - number of chunks = %d", numChunks)); - - // get the total data length - UnsignedInt totalDataLength = 0; - memcpy(&totalDataLength, data + i, sizeof(totalDataLength)); - msg->setTotalDataLength(totalDataLength); - i += sizeof(totalDataLength); - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::readWrapperMessage - total data length = %d", totalDataLength)); - - // get the data length for this chunk - UnsignedInt dataLength = 0; - memcpy(&dataLength, data + i, sizeof(dataLength)); - i += sizeof(dataLength); - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::readWrapperMessage - data length = %d", dataLength)); - - UnsignedInt dataOffset = 0; - memcpy(&dataOffset, data + i, sizeof(dataOffset)); - msg->setDataOffset(dataOffset); - i += sizeof(dataOffset); - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::readWrapperMessage - data offset = %d", dataOffset)); - - msg->setData(data + i, dataLength); - i += dataLength; - - return msg; -} - -NetCommandMsg * NetPacket::readFileMessage(UnsignedByte *data, Int &i) { - NetFileCommandMsg *msg = newInstance(NetFileCommandMsg); - char filename[_MAX_PATH]; - - // TheSuperHackers @security Mauller/Jbremer/SkyAero 11/12/2025 Prevent buffer overflow when copying filepath string - i += strlcpy(filename, reinterpret_cast(data + i), ARRAY_SIZE(filename)); - ++i; //Increment for null terminator - msg->setPortableFilename(AsciiString(filename)); // it's transferred as a portable filename - - UnsignedInt dataLength = 0; - memcpy(&dataLength, data + i, sizeof(dataLength)); - i += sizeof(dataLength); - - UnsignedByte *buf = NEW UnsignedByte[dataLength]; - memcpy(buf, data + i, dataLength); - i += dataLength; - - msg->setFileData(buf, dataLength); - - return msg; -} - -NetCommandMsg * NetPacket::readFileAnnounceMessage(UnsignedByte *data, Int &i) { - NetFileAnnounceCommandMsg *msg = newInstance(NetFileAnnounceCommandMsg); - char filename[_MAX_PATH]; - - // TheSuperHackers @security Mauller/Jbremer/SkyAero 11/12/2025 Prevent buffer overflow when copying filepath string - i += strlcpy(filename, reinterpret_cast(data + i), ARRAY_SIZE(filename)); - ++i; //Increment for null terminator - msg->setPortableFilename(AsciiString(filename)); // it's transferred as a portable filename - - UnsignedShort fileID = 0; - memcpy(&fileID, data + i, sizeof(fileID)); - i += sizeof(fileID); - msg->setFileID(fileID); - - UnsignedByte playerMask = 0; - memcpy(&playerMask, data + i, sizeof(playerMask)); - i += sizeof(playerMask); - msg->setPlayerMask(playerMask); - - return msg; -} - -NetCommandMsg * NetPacket::readFileProgressMessage(UnsignedByte *data, Int &i) { - NetFileProgressCommandMsg *msg = newInstance(NetFileProgressCommandMsg); - - UnsignedShort fileID = 0; - memcpy(&fileID, data + i, sizeof(fileID)); - i += sizeof(fileID); - msg->setFileID(fileID); - - Int progress = 0; - memcpy(&progress, data + i, sizeof(progress)); - i += sizeof(progress); - msg->setProgress(progress); - - return msg; -} - -NetCommandMsg * NetPacket::readDisconnectFrameMessage(UnsignedByte *data, Int &i) { - NetDisconnectFrameCommandMsg *msg = newInstance(NetDisconnectFrameCommandMsg); - - UnsignedInt disconnectFrame = 0; - memcpy(&disconnectFrame, data + i, sizeof(disconnectFrame)); - i += sizeof(disconnectFrame); - msg->setDisconnectFrame(disconnectFrame); - - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::readDisconnectFrameMessage - read disconnect frame for frame %d", disconnectFrame)); - - return msg; -} - -NetCommandMsg * NetPacket::readDisconnectScreenOffMessage(UnsignedByte *data, Int &i) { - NetDisconnectScreenOffCommandMsg *msg = newInstance(NetDisconnectScreenOffCommandMsg); - - UnsignedInt newFrame = 0; - memcpy(&newFrame, data + i, sizeof(newFrame)); - i += sizeof(newFrame); - msg->setNewFrame(newFrame); - - return msg; -} - -NetCommandMsg * NetPacket::readFrameResendRequestMessage(UnsignedByte *data, Int &i) { - NetFrameResendRequestCommandMsg *msg = newInstance(NetFrameResendRequestCommandMsg); - - UnsignedInt frameToResend = 0; - memcpy(&frameToResend, data + i, sizeof(frameToResend)); - i += sizeof(frameToResend); - msg->setFrameToResend(frameToResend); - - return msg; -} - /** * Returns the number of commands in this packet. Only valid if the packet is locally constructed. */ @@ -1467,16 +567,16 @@ Int NetPacket::getLength() { /** * Dumps the packet to the debug log file */ -void NetPacket::dumpPacketToLog() { - DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::dumpPacketToLog() - packet is %d bytes", m_packetLen)); - Int numLines = m_packetLen / 8; - if ((m_packetLen % 8) != 0) { +void NetPacket::dumpPacketToLog(const UnsignedByte *packet, Int packetLen) { + DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("NetPacket::dumpPacketToLog() - packet is %d bytes", packetLen)); + Int numLines = packetLen / 8; + if ((packetLen % 8) != 0) { ++numLines; } for (Int dumpindex = 0; dumpindex < numLines; ++dumpindex) { DEBUG_LOG_LEVEL_RAW(DEBUG_LEVEL_NET, ("\t%d\t", dumpindex*8)); - for (Int dumpindex2 = 0; (dumpindex2 < 8) && ((dumpindex*8 + dumpindex2) < m_packetLen); ++dumpindex2) { - DEBUG_LOG_LEVEL_RAW(DEBUG_LEVEL_NET, ("%02x '%c' ", m_packet[dumpindex*8 + dumpindex2], m_packet[dumpindex*8 + dumpindex2])); + for (Int dumpindex2 = 0; (dumpindex2 < 8) && ((dumpindex*8 + dumpindex2) < packetLen); ++dumpindex2) { + DEBUG_LOG_LEVEL_RAW(DEBUG_LEVEL_NET, ("%02x '%c' ", packet[dumpindex*8 + dumpindex2], packet[dumpindex*8 + dumpindex2])); } DEBUG_LOG_LEVEL_RAW(DEBUG_LEVEL_NET, ("\n")); } diff --git a/Core/GameEngine/Source/GameNetwork/NetPacketStructs.cpp b/Core/GameEngine/Source/GameNetwork/NetPacketStructs.cpp index 5f42e3388c3..500fcdbee88 100644 --- a/Core/GameEngine/Source/GameNetwork/NetPacketStructs.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetPacketStructs.cpp @@ -108,6 +108,155 @@ size_t SmallNetPacketCommandBase::copyBytes(UnsignedByte *buffer, const NetComma return size; } +size_t SmallNetPacketCommandBase::readMessage(NetCommandRef *&ref, CommandBase &base, NetPacketBuf buf) +{ + size_t size = 0; + + while (size < buf.size()) + { + switch (buf[size]) + { + case NetPacketFieldTypes::CommandType: + size += network::readObject(base.commandType, buf.offset(size)); + break; + case NetPacketFieldTypes::Relay: + size += network::readObject(base.relay, buf.offset(size)); + break; + case NetPacketFieldTypes::Frame: + size += network::readObject(base.frame, buf.offset(size)); + break; + case NetPacketFieldTypes::PlayerId: + size += network::readObject(base.playerId, buf.offset(size)); + break; + case NetPacketFieldTypes::CommandId: + size += network::readObject(base.commandId, buf.offset(size)); + break; + case NetPacketFieldTypes::Data: + { + size += network::readObject(base.dataHeader, buf.offset(size)); + // The data field marks the end of the command base. + if (NetCommandMsg* msg = constructNetCommandMsg(base)) + { + ref = NEW_NETCOMMANDREF(msg); + ref->setRelay(base.relay.relay); + msg->detach(); + } + return size; + } + case NetPacketFieldTypes::Repeat: + default: + DEBUG_CRASH(("SmallNetPacketCommandBase::readBytes: Unexpected field type '%c' encountered.", buf[size])); + return size + 1; + } + } + + return size; +} + +NetCommandMsg *SmallNetPacketCommandBase::constructNetCommandMsg(const CommandBase &base) +{ + NetCommandMsg *msg = nullptr; + NetCommandType commandType = static_cast(base.commandType.commandType); + + switch (commandType) + { + case NETCOMMANDTYPE_GAMECOMMAND: + msg = newInstance(NetGameCommandMsg); + break; + case NETCOMMANDTYPE_ACKBOTH: + msg = newInstance(NetAckBothCommandMsg); + break; + case NETCOMMANDTYPE_ACKSTAGE1: + msg = newInstance(NetAckStage1CommandMsg); + break; + case NETCOMMANDTYPE_ACKSTAGE2: + msg = newInstance(NetAckStage2CommandMsg); + break; + case NETCOMMANDTYPE_FRAMEINFO: + msg = newInstance(NetFrameCommandMsg); + break; + case NETCOMMANDTYPE_PLAYERLEAVE: + msg = newInstance(NetPlayerLeaveCommandMsg); + break; + case NETCOMMANDTYPE_RUNAHEADMETRICS: + msg = newInstance(NetRunAheadMetricsCommandMsg); + break; + case NETCOMMANDTYPE_RUNAHEAD: + msg = newInstance(NetRunAheadCommandMsg); + break; + case NETCOMMANDTYPE_DESTROYPLAYER: + msg = newInstance(NetDestroyPlayerCommandMsg); + break; + case NETCOMMANDTYPE_KEEPALIVE: + msg = newInstance(NetKeepAliveCommandMsg); + break; + case NETCOMMANDTYPE_DISCONNECTKEEPALIVE: + msg = newInstance(NetDisconnectKeepAliveCommandMsg); + break; + case NETCOMMANDTYPE_DISCONNECTPLAYER: + msg = newInstance(NetDisconnectPlayerCommandMsg); + break; + case NETCOMMANDTYPE_PACKETROUTERQUERY: + msg = newInstance(NetPacketRouterQueryCommandMsg); + break; + case NETCOMMANDTYPE_PACKETROUTERACK: + msg = newInstance(NetPacketRouterAckCommandMsg); + break; + case NETCOMMANDTYPE_DISCONNECTCHAT: + msg = newInstance(NetDisconnectChatCommandMsg); + break; + case NETCOMMANDTYPE_DISCONNECTVOTE: + msg = newInstance(NetDisconnectVoteCommandMsg); + break; + case NETCOMMANDTYPE_CHAT: + msg = newInstance(NetChatCommandMsg); + break; + case NETCOMMANDTYPE_PROGRESS: + msg = newInstance(NetProgressCommandMsg); + break; + case NETCOMMANDTYPE_LOADCOMPLETE: + msg = newInstance(NetLoadCompleteCommandMsg); + break; + case NETCOMMANDTYPE_TIMEOUTSTART: + msg = newInstance(NetTimeOutGameStartCommandMsg); + break; + case NETCOMMANDTYPE_WRAPPER: + msg = newInstance(NetWrapperCommandMsg); + break; + case NETCOMMANDTYPE_FILE: + msg = newInstance(NetFileCommandMsg); + break; + case NETCOMMANDTYPE_FILEANNOUNCE: + msg = newInstance(NetFileAnnounceCommandMsg); + break; + case NETCOMMANDTYPE_FILEPROGRESS: + msg = newInstance(NetFileProgressCommandMsg); + break; + case NETCOMMANDTYPE_DISCONNECTFRAME: + msg = newInstance(NetDisconnectFrameCommandMsg); + break; + case NETCOMMANDTYPE_DISCONNECTSCREENOFF: + msg = newInstance(NetDisconnectScreenOffCommandMsg); + break; + case NETCOMMANDTYPE_FRAMERESENDREQUEST: + msg = newInstance(NetFrameResendRequestCommandMsg); + break; + default: + DEBUG_CRASH(("SmallNetPacketCommandBase::constructNetCommandMsg: Unexpected command type '%d' encountered.", commandType)); + return nullptr; + } + + DEBUG_ASSERTCRASH(commandType == msg->getNetCommandType(), + ("SmallNetPacketCommandBase::constructNetCommandMsg: Read command type '%d' does not match created command '%d'.", commandType, msg->getNetCommandType())); + + msg->setNetCommandType(static_cast(base.commandType.commandType)); + msg->setExecutionFrame(base.frame.frame); + msg->setPlayerID(base.playerId.playerId); + msg->setID(base.commandId.commandId); + + return msg; +} + //////////////////////////////////////////////////////////////////////////////// // NetPacketAckCommand //////////////////////////////////////////////////////////////////////////////// @@ -122,6 +271,20 @@ size_t NetPacketAckCommandData::copyBytes(UnsignedByte *buffer, const NetCommand return network::writeObject(buffer, data); } +size_t NetPacketAckCommandData::readMessage(NetCommandRef &ref, NetPacketBuf buf) +{ + CommandMsg *cmdMsg = static_cast(ref.getCommand()); + FixedData data; + data.commandId = 0; + data.originalPlayerId = 0; + + size_t size = network::readObject(data, buf); + cmdMsg->setCommandID(data.commandId); + cmdMsg->setOriginalPlayerID(data.originalPlayerId); + + return size; +} + size_t NetPacketAckCommandBase::copyBytes(UnsignedByte *buffer, const NetCommandRef &ref) { const NetCommandMsg *msg = ref.getCommand(); @@ -148,6 +311,18 @@ size_t NetPacketFrameCommandData::copyBytes(UnsignedByte *buffer, const NetComma return network::writeObject(buffer, data); } +size_t NetPacketFrameCommandData::readMessage(NetCommandRef &ref, NetPacketBuf buf) +{ + CommandMsg *cmdMsg = static_cast(ref.getCommand()); + FixedData data; + data.commandCount = 0; + + size_t size = network::readObject(data, buf); + cmdMsg->setCommandCount(data.commandCount); + + return size; +} + size_t NetPacketFrameCommandBase::copyBytes(UnsignedByte *buffer, const NetCommandRef &ref) { const NetCommandMsg *msg = ref.getCommand(); @@ -174,6 +349,18 @@ size_t NetPacketPlayerLeaveCommandData::copyBytes(UnsignedByte *buffer, const Ne return network::writeObject(buffer, data); } +size_t NetPacketPlayerLeaveCommandData::readMessage(NetCommandRef &ref, NetPacketBuf buf) +{ + CommandMsg *cmdMsg = static_cast(ref.getCommand()); + FixedData data; + data.leavingPlayerId = 0; + + size_t size = network::readObject(data, buf); + cmdMsg->setLeavingPlayerID(data.leavingPlayerId); + + return size; +} + size_t NetPacketPlayerLeaveCommandBase::copyBytes(UnsignedByte *buffer, const NetCommandRef &ref) { const NetCommandMsg *msg = ref.getCommand(); @@ -201,6 +388,20 @@ size_t NetPacketRunAheadMetricsCommandData::copyBytes(UnsignedByte *buffer, cons return network::writeObject(buffer, data); } +size_t NetPacketRunAheadMetricsCommandData::readMessage(NetCommandRef &ref, NetPacketBuf buf) +{ + CommandMsg *cmdMsg = static_cast(ref.getCommand()); + FixedData data; + data.averageLatency = 0.2f; + data.averageFps = 30; + + size_t size = network::readObject(data, buf); + cmdMsg->setAverageLatency(data.averageLatency); + cmdMsg->setAverageFps(data.averageFps); + + return size; +} + size_t NetPacketRunAheadMetricsCommandBase::copyBytes(UnsignedByte *buffer, const NetCommandRef &ref) { const NetCommandMsg *msg = ref.getCommand(); @@ -228,6 +429,20 @@ size_t NetPacketRunAheadCommandData::copyBytes(UnsignedByte *buffer, const NetCo return network::writeObject(buffer, data); } +size_t NetPacketRunAheadCommandData::readMessage(NetCommandRef &ref, NetPacketBuf buf) +{ + CommandMsg *cmdMsg = static_cast(ref.getCommand()); + FixedData data; + data.runAhead = 20; + data.frameRate = 30; + + size_t size = network::readObject(data, buf); + cmdMsg->setRunAhead(data.runAhead); + cmdMsg->setFrameRate(data.frameRate); + + return size; +} + size_t NetPacketRunAheadCommandBase::copyBytes(UnsignedByte *buffer, const NetCommandRef &ref) { const NetCommandMsg *msg = ref.getCommand(); @@ -254,6 +469,18 @@ size_t NetPacketDestroyPlayerCommandData::copyBytes(UnsignedByte *buffer, const return network::writeObject(buffer, data); } +size_t NetPacketDestroyPlayerCommandData::readMessage(NetCommandRef &ref, NetPacketBuf buf) +{ + CommandMsg *cmdMsg = static_cast(ref.getCommand()); + FixedData data; + data.playerIndex = 0; + + size_t size = network::readObject(data, buf); + cmdMsg->setPlayerIndex(data.playerIndex); + + return size; +} + size_t NetPacketDestroyPlayerCommandBase::copyBytes(UnsignedByte *buffer, const NetCommandRef &ref) { const NetCommandMsg *msg = ref.getCommand(); @@ -315,6 +542,20 @@ size_t NetPacketDisconnectPlayerCommandData::copyBytes(UnsignedByte *buffer, con return network::writeObject(buffer, data); } +size_t NetPacketDisconnectPlayerCommandData::readMessage(NetCommandRef &ref, NetPacketBuf buf) +{ + CommandMsg *cmdMsg = static_cast(ref.getCommand()); + FixedData data; + data.disconnectSlot = 0; + data.disconnectFrame = 0; + + size_t size = network::readObject(data, buf); + cmdMsg->setDisconnectSlot(data.disconnectSlot); + cmdMsg->setDisconnectFrame(data.disconnectFrame); + + return size; +} + size_t NetPacketDisconnectPlayerCommandBase::copyBytes(UnsignedByte *buffer, const NetCommandRef &ref) { const NetCommandMsg *msg = ref.getCommand(); @@ -376,6 +617,20 @@ size_t NetPacketDisconnectVoteCommandData::copyBytes(UnsignedByte *buffer, const return network::writeObject(buffer, data); } +size_t NetPacketDisconnectVoteCommandData::readMessage(NetCommandRef &ref, NetPacketBuf buf) +{ + CommandMsg *cmdMsg = static_cast(ref.getCommand()); + FixedData data; + data.slot = 0; + data.voteFrame = 0; + + size_t size = network::readObject(data, buf); + cmdMsg->setSlot(data.slot); + cmdMsg->setVoteFrame(data.voteFrame); + + return size; +} + size_t NetPacketDisconnectVoteCommandBase::copyBytes(UnsignedByte *buffer, const NetCommandRef &ref) { const NetCommandMsg *msg = ref.getCommand(); @@ -408,7 +663,7 @@ size_t NetPacketChatCommandData::getSize(const NetCommandMsg &msg) size_t NetPacketChatCommandData::copyBytes(UnsignedByte *buffer, const NetCommandRef &ref) { const CommandMsg *cmdMsg = static_cast(ref.getCommand()); - const Int textLength = std::min(cmdMsg->getText().getLength(), 255); + const size_t textLength = std::min(cmdMsg->getText().getLength(), 255); size_t size = 0; size += network::writePrimitive(buffer + size, (UnsignedByte)textLength); @@ -417,6 +672,24 @@ size_t NetPacketChatCommandData::copyBytes(UnsignedByte *buffer, const NetComman return size; } +size_t NetPacketChatCommandData::readMessage(NetCommandRef &ref, NetPacketBuf buf) +{ + CommandMsg *cmdMsg = static_cast(ref.getCommand()); + UnsignedByte textLength = 0; + UnicodeString unitext; + Int playerMask = 0; + + size_t size = 0; + size += network::readObject(textLength, buf.offset(size)); + size += network::readStringWithoutNull(unitext, textLength, buf.offset(size)); + size += network::readObject(playerMask, buf.offset(size)); + + cmdMsg->setText(unitext); + cmdMsg->setPlayerMask(playerMask); + + return size; +} + size_t NetPacketChatCommandBase::copyBytes(UnsignedByte *buffer, const NetCommandRef &ref) { const NetCommandMsg *msg = ref.getCommand(); @@ -456,6 +729,21 @@ size_t NetPacketDisconnectChatCommandData::copyBytes(UnsignedByte *buffer, const return size; } +size_t NetPacketDisconnectChatCommandData::readMessage(NetCommandRef &ref, NetPacketBuf buf) +{ + CommandMsg *cmdMsg = static_cast(ref.getCommand()); + UnsignedByte textLength = 0; + UnicodeString unitext; + + size_t size = 0; + size += network::readObject(textLength, buf.offset(size)); + size += network::readStringWithoutNull(unitext, textLength, buf.offset(size)); + + cmdMsg->setText(unitext); + + return size; +} + size_t NetPacketDisconnectChatCommandBase::copyBytes(UnsignedByte *buffer, const NetCommandRef &ref) { const NetCommandMsg *msg = ref.getCommand(); @@ -604,6 +892,116 @@ size_t NetPacketGameCommandData::copyBytes(UnsignedByte *buffer, const NetComman return size; } +size_t NetPacketGameCommandData::readMessage(NetCommandRef &ref, NetPacketBuf buf) +{ + CommandMsg *cmdMsg = static_cast(ref.getCommand()); + GameMessageParser *parser = newInstance(GameMessageParser)(); + Int newType = 0; + UnsignedByte numArgTypes = 0; + + size_t size = 0; + size += network::readObject(newType, buf.offset(size)); + size += network::readObject(numArgTypes, buf.offset(size)); + + cmdMsg->setGameMessageType(static_cast(newType)); + + Int totalArgCount = 0; + Int argIndex = 0; + + for (; argIndex < (Int)numArgTypes; ++argIndex) + { + UnsignedByte type = (UnsignedByte)ARGUMENTDATATYPE_UNKNOWN; + UnsignedByte argCount = 0; + + size += network::readObject(type, buf.offset(size)); + size += network::readObject(argCount, buf.offset(size)); + + parser->addArgType(static_cast(type), argCount); + totalArgCount += argCount; + } + + GameMessageParserArgumentType *parserArgType = parser->getFirstArgumentType(); + GameMessageArgumentDataType lastType = ARGUMENTDATATYPE_UNKNOWN; + Int argsLeftForType = 0; + + if (parserArgType != nullptr) + { + lastType = parserArgType->getType(); + argsLeftForType = parserArgType->getArgCount(); + } + + for (argIndex = 0; argIndex < totalArgCount; ++argIndex) + { + GameMessageArgumentType arg; + const size_t sizeBefore = size; + + switch (lastType) + { + case ARGUMENTDATATYPE_INTEGER: + size += network::readObject(arg.integer, buf.offset(size)); + break; + case ARGUMENTDATATYPE_REAL: + size += network::readObject(arg.real, buf.offset(size)); + break; + case ARGUMENTDATATYPE_BOOLEAN: + size += network::readObject(arg.boolean, buf.offset(size)); + break; + case ARGUMENTDATATYPE_OBJECTID: + size += network::readObject(arg.objectID, buf.offset(size)); + break; + case ARGUMENTDATATYPE_DRAWABLEID: + size += network::readObject(arg.drawableID, buf.offset(size)); + break; + case ARGUMENTDATATYPE_TEAMID: + size += network::readObject(arg.teamID, buf.offset(size)); + break; + case ARGUMENTDATATYPE_LOCATION: + size += network::readObject(arg.location, buf.offset(size)); + break; + case ARGUMENTDATATYPE_PIXEL: + size += network::readObject(arg.pixel, buf.offset(size)); + break; + case ARGUMENTDATATYPE_PIXELREGION: + size += network::readObject(arg.pixelRegion, buf.offset(size)); + break; + case ARGUMENTDATATYPE_TIMESTAMP: + size += network::readObject(arg.timestamp, buf.offset(size)); + break; + case ARGUMENTDATATYPE_WIDECHAR: + size += network::readObject(arg.wChar, buf.offset(size)); + break; + } + + if (size > sizeBefore) + { + cmdMsg->addArgument(lastType, arg); + } + + --argsLeftForType; + + if (argsLeftForType == 0) + { + if (parserArgType == nullptr) + { + DEBUG_CRASH(("parserArgType was null when it shouldn't have been.")); + break; + } + + parserArgType = parserArgType->getNext(); + // parserArgType is allowed to be null here + if (parserArgType != nullptr) + { + argsLeftForType = parserArgType->getArgCount(); + lastType = parserArgType->getType(); + } + } + } + + deleteInstance(parser); + + return size; +} + size_t NetPacketGameCommandBase::copyBytes(UnsignedByte *buffer, const NetCommandRef &ref) { const NetCommandMsg *msg = ref.getCommand(); @@ -648,6 +1046,33 @@ size_t NetPacketWrapperCommandData::copyBytes(UnsignedByte *buffer, const NetCom return size; } +size_t NetPacketWrapperCommandData::readMessage(NetCommandRef &ref, NetPacketBuf buf) +{ + CommandMsg *cmdMsg = static_cast(ref.getCommand()); + FixedData data; + data.wrappedCommandId = 0; + data.chunkNumber = 0; + data.numChunks = 0; + data.totalDataLength = 0; + data.dataLength = 0; + data.dataOffset = 0; + + size_t size = 0; + size += network::readObject(data, buf.offset(size)); + + NetCommandDataChunk dataChunk(data.dataLength); + size += network::readBytes(dataChunk.data(), dataChunk.size(), buf.offset(size)); + + cmdMsg->setWrappedCommandID(data.wrappedCommandId); + cmdMsg->setChunkNumber(data.chunkNumber); + cmdMsg->setNumChunks(data.numChunks); + cmdMsg->setTotalDataLength(data.totalDataLength); + cmdMsg->setDataOffset(data.dataOffset); + cmdMsg->setData(dataChunk); + + return size; +} + size_t NetPacketWrapperCommandBase::copyBytes(UnsignedByte *buffer, const NetCommandRef &ref) { const NetCommandMsg *msg = ref.getCommand(); @@ -687,6 +1112,25 @@ size_t NetPacketFileCommandData::copyBytes(UnsignedByte *buffer, const NetComman return size; } +size_t NetPacketFileCommandData::readMessage(NetCommandRef &ref, NetPacketBuf buf) +{ + CommandMsg *cmdMsg = static_cast(ref.getCommand()); + AsciiString filename; + UnsignedInt dataLength = 0; + + size_t size = 0; + size += network::readStringWithNull(filename, _MAX_PATH, buf.offset(size)); + size += network::readObject(dataLength, buf.offset(size)); + + NetCommandDataChunk dataChunk(dataLength); + size += network::readBytes(dataChunk.data(), dataChunk.size(), buf.offset(size)); + + cmdMsg->setPortableFilename(filename); + cmdMsg->setFileData(dataChunk); + + return size; +} + size_t NetPacketFileCommandBase::copyBytes(UnsignedByte *buffer, const NetCommandRef &ref) { const NetCommandMsg *msg = ref.getCommand(); @@ -726,6 +1170,25 @@ size_t NetPacketFileAnnounceCommandData::copyBytes(UnsignedByte *buffer, const N return size; } +size_t NetPacketFileAnnounceCommandData::readMessage(NetCommandRef &ref, NetPacketBuf buf) +{ + CommandMsg *cmdMsg = static_cast(ref.getCommand()); + AsciiString filename; + UnsignedShort fileID = 0; + UnsignedByte playerMask = 0; + + size_t size = 0; + size += network::readStringWithNull(filename, _MAX_PATH, buf.offset(size)); + size += network::readObject(fileID, buf.offset(size)); + size += network::readObject(playerMask, buf.offset(size)); + + cmdMsg->setPortableFilename(filename); + cmdMsg->setFileID(fileID); + cmdMsg->setPlayerMask(playerMask); + + return size; +} + size_t NetPacketFileAnnounceCommandBase::copyBytes(UnsignedByte *buffer, const NetCommandRef &ref) { const NetCommandMsg *msg = ref.getCommand(); @@ -753,6 +1216,20 @@ size_t NetPacketFileProgressCommandData::copyBytes(UnsignedByte *buffer, const N return network::writeObject(buffer, data); } +size_t NetPacketFileProgressCommandData::readMessage(NetCommandRef &ref, NetPacketBuf buf) +{ + CommandMsg *cmdMsg = static_cast(ref.getCommand()); + FixedData data; + data.fileId = 0; + data.progress = 0; + + size_t size = network::readObject(data, buf); + cmdMsg->setFileID(data.fileId); + cmdMsg->setProgress(data.progress); + + return size; +} + size_t NetPacketFileProgressCommandBase::copyBytes(UnsignedByte *buffer, const NetCommandRef &ref) { const NetCommandMsg *msg = ref.getCommand(); @@ -779,6 +1256,18 @@ size_t NetPacketProgressCommandData::copyBytes(UnsignedByte *buffer, const NetCo return network::writeObject(buffer, data); } +size_t NetPacketProgressCommandData::readMessage(NetCommandRef &ref, NetPacketBuf buf) +{ + CommandMsg *cmdMsg = static_cast(ref.getCommand()); + FixedData data; + data.percentage = 0; + + size_t size = network::readObject(data, buf); + cmdMsg->setPercentage(data.percentage); + + return size; +} + size_t NetPacketProgressCommandBase::copyBytes(UnsignedByte *buffer, const NetCommandRef &ref) { const NetCommandMsg *msg = ref.getCommand(); @@ -839,6 +1328,18 @@ size_t NetPacketDisconnectFrameCommandData::copyBytes(UnsignedByte *buffer, cons return network::writeObject(buffer, data); } +size_t NetPacketDisconnectFrameCommandData::readMessage(NetCommandRef &ref, NetPacketBuf buf) +{ + CommandMsg *cmdMsg = static_cast(ref.getCommand()); + FixedData data; + data.disconnectFrame = 0; + + size_t size = network::readObject(data, buf); + cmdMsg->setDisconnectFrame(data.disconnectFrame); + + return size; +} + size_t NetPacketDisconnectFrameCommandBase::copyBytes(UnsignedByte *buffer, const NetCommandRef &ref) { const NetCommandMsg *msg = ref.getCommand(); @@ -865,6 +1366,18 @@ size_t NetPacketDisconnectScreenOffCommandData::copyBytes(UnsignedByte *buffer, return network::writeObject(buffer, data); } +size_t NetPacketDisconnectScreenOffCommandData::readMessage(NetCommandRef &ref, NetPacketBuf buf) +{ + CommandMsg *cmdMsg = static_cast(ref.getCommand()); + FixedData data; + data.newFrame = 0; + + size_t size = network::readObject(data, buf); + cmdMsg->setNewFrame(data.newFrame); + + return size; +} + size_t NetPacketDisconnectScreenOffCommandBase::copyBytes(UnsignedByte *buffer, const NetCommandRef &ref) { const NetCommandMsg *msg = ref.getCommand(); @@ -891,6 +1404,18 @@ size_t NetPacketFrameResendRequestCommandData::copyBytes(UnsignedByte *buffer, c return network::writeObject(buffer, data); } +size_t NetPacketFrameResendRequestCommandData::readMessage(NetCommandRef &ref, NetPacketBuf buf) +{ + CommandMsg *cmdMsg = static_cast(ref.getCommand()); + FixedData data; + data.frameToResend = 0; + + size_t size = network::readObject(data, buf); + cmdMsg->setFrameToResend(data.frameToResend); + + return size; +} + size_t NetPacketFrameResendRequestCommandBase::copyBytes(UnsignedByte *buffer, const NetCommandRef &ref) { const NetCommandMsg *msg = ref.getCommand(); From 7dc2632ea3cd5b13fcd44df2af7c6033e4eb2b35 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Tue, 19 May 2026 21:53:35 +0200 Subject: [PATCH 03/20] bugfix: Restore retail compatibility after retail behavior flags change (#2727) --- Generals/Code/GameEngine/Include/Common/TunnelTracker.h | 2 +- Generals/Code/GameEngine/Source/Common/RTS/Player.cpp | 2 +- Generals/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp | 4 ++-- .../Source/GameLogic/Object/Behavior/PoisonedBehavior.cpp | 2 +- .../GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp | 2 +- .../GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp | 2 +- .../Source/GameLogic/Object/Contain/TunnelContain.cpp | 2 +- .../GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp | 2 +- GeneralsMD/Code/GameEngine/Include/Common/TunnelTracker.h | 2 +- GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp | 2 +- .../Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp | 4 ++-- .../Source/GameLogic/Object/Behavior/PoisonedBehavior.cpp | 2 +- .../GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp | 2 +- .../GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp | 2 +- .../Source/GameLogic/Object/Contain/TunnelContain.cpp | 2 +- .../GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp | 2 +- .../Source/GameLogic/Object/Update/StealthUpdate.cpp | 2 +- 17 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Generals/Code/GameEngine/Include/Common/TunnelTracker.h b/Generals/Code/GameEngine/Include/Common/TunnelTracker.h index b8d19cb7197..0762faee90c 100644 --- a/Generals/Code/GameEngine/Include/Common/TunnelTracker.h +++ b/Generals/Code/GameEngine/Include/Common/TunnelTracker.h @@ -60,7 +60,7 @@ class TunnelTracker : public MemoryPoolObject, static void destroyObject( Object *obj, void *userData ); ///< Callback for Iterate Contained system static void healObject( Object *obj, void *frames ); ///< Callback for Iterate Contained system -#if PRESERVE_TUNNEL_HEAL_STACKING || RETAIL_COMPATIBLE_CRC +#if RETAIL_COMPATIBLE_CRC || PRESERVE_TUNNEL_HEAL_STACKING void healObjects(Real frames); ///< heal all objects within the tunnel #else void healObjects(); ///< heal all objects within the tunnel diff --git a/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp b/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp index 7bc48908c7a..590e7eebca3 100644 --- a/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -678,7 +678,7 @@ void Player::update() } } -#if !PRESERVE_TUNNEL_HEAL_STACKING && !RETAIL_COMPATIBLE_CRC +#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_TUNNEL_HEAL_STACKING) // TheSuperHackers @bugfix Stubbjax 26/09/2025 The Tunnel System now heals // all units once per frame instead of once per frame per Tunnel Network. TunnelTracker* tunnelSystem = getTunnelSystem(); diff --git a/Generals/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp b/Generals/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp index 385d1bf20c7..91fc49b7f13 100644 --- a/Generals/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp +++ b/Generals/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp @@ -277,7 +277,7 @@ void TunnelTracker::destroyObject( Object *obj, void * ) // ------------------------------------------------------------------------ // heal all the objects within the tunnel system using the iterateContained function -#if PRESERVE_TUNNEL_HEAL_STACKING || RETAIL_COMPATIBLE_CRC +#if RETAIL_COMPATIBLE_CRC || PRESERVE_TUNNEL_HEAL_STACKING void TunnelTracker::healObjects(Real frames) { iterateContained(healObject, &frames, FALSE); @@ -301,7 +301,7 @@ void TunnelTracker::healObject( Object *obj, void *frames) { //get the number of frames to heal -#if PRESERVE_TUNNEL_HEAL_STACKING || RETAIL_COMPATIBLE_CRC +#if RETAIL_COMPATIBLE_CRC || PRESERVE_TUNNEL_HEAL_STACKING Real *framesForFullHeal = (Real*)frames; #else UnsignedInt* framesForFullHeal = (UnsignedInt*)frames; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/PoisonedBehavior.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/PoisonedBehavior.cpp index a11b49f5792..5483e20dd84 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/PoisonedBehavior.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/PoisonedBehavior.cpp @@ -158,7 +158,7 @@ void PoisonedBehavior::startPoisonedEffects( const DamageInfo *damageInfo ) // We are going to take the damage dealt by the original poisoner every so often for a while. m_poisonDamageAmount = damageInfo->out.m_actualDamageDealt; -#if !RETAIL_COMPATIBLE_CRC && !PRESERVE_NO_XP_FROM_POISON_KILLS +#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_NO_XP_FROM_POISON_KILLS) // TheSuperHackers @bugfix Stubbjax 03/09/2025 Allow poison damage to award xp to the poison source. m_poisonSource = damageInfo->in.m_sourceID; #endif diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp index ed8e846cfe1..bea29c00a96 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp @@ -593,7 +593,7 @@ void ActiveBody::attemptHealing( DamageInfo *damageInfo ) //(object pointer loses scope as soon as atteptdamage's caller ends) m_lastDamageInfo = *damageInfo; m_lastDamageCleared = false; -#if PRESERVE_STRUCTURE_STEALTH_DURING_REPAIR +#if RETAIL_COMPATIBLE_CRC || PRESERVE_STRUCTURE_STEALTH_DURING_REPAIR m_lastDamageTimestamp = TheGameLogic->getFrame(); #endif m_lastHealingTimestamp = TheGameLogic->getFrame(); diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp index 0856934e280..fbf487ec78d 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp @@ -50,7 +50,7 @@ CrateCollideModuleData::CrateCollideModuleData() m_executeAnimationFades = TRUE; m_isBuildingPickup = FALSE; m_isHumanOnlyPickup = FALSE; - m_allowMultiPickup = (PRESERVE_MULTI_CRATE_PICKUP != 0); + m_allowMultiPickup = (RETAIL_COMPATIBLE_CRC != 0 || PRESERVE_MULTI_CRATE_PICKUP != 0); m_executeFX = nullptr; m_pickupScience = SCIENCE_INVALID; m_executionAnimationTemplate = AsciiString::TheEmptyString; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp index 82ac87c314a..ed9c1983da5 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp @@ -437,7 +437,7 @@ UpdateSleepTime TunnelContain::update() if (controllingPlayer) { TunnelTracker *tunnelSystem = controllingPlayer->getTunnelSystem(); -#if PRESERVE_TUNNEL_HEAL_STACKING || RETAIL_COMPATIBLE_CRC +#if RETAIL_COMPATIBLE_CRC || PRESERVE_TUNNEL_HEAL_STACKING if (tunnelSystem) { const TunnelContainModuleData* modData = getTunnelContainModuleData(); diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp index 0e802efaadb..003c55fde46 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp @@ -1340,7 +1340,7 @@ class GenericObjectCreationNugget : public ObjectCreationNugget } } -#if !RETAIL_COMPATIBLE_CRC && !PRESERVE_NO_XP_FROM_OCL_KILLS +#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_NO_XP_FROM_OCL_KILLS) ObjectID sinkID = sourceObj->getExperienceTracker()->getExperienceSink(); firstObject->getExperienceTracker()->setExperienceSink(sinkID != INVALID_ID ? sinkID : sourceObj->getID()); #endif diff --git a/GeneralsMD/Code/GameEngine/Include/Common/TunnelTracker.h b/GeneralsMD/Code/GameEngine/Include/Common/TunnelTracker.h index b1e3f5bf492..b044ebc00cd 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/TunnelTracker.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/TunnelTracker.h @@ -60,7 +60,7 @@ class TunnelTracker : public MemoryPoolObject, static void destroyObject( Object *obj, void *userData ); ///< Callback for Iterate Contained system static void healObject( Object *obj, void *frames ); ///< Callback for Iterate Contained system -#if PRESERVE_TUNNEL_HEAL_STACKING || RETAIL_COMPATIBLE_CRC +#if RETAIL_COMPATIBLE_CRC || PRESERVE_TUNNEL_HEAL_STACKING void healObjects(Real frames); ///< heal all objects within the tunnel #else void healObjects(); ///< heal all objects within the tunnel diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp index a9a53e3a006..cfa31bcc99c 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -716,7 +716,7 @@ void Player::update() } } -#if !PRESERVE_TUNNEL_HEAL_STACKING && !RETAIL_COMPATIBLE_CRC +#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_TUNNEL_HEAL_STACKING) // TheSuperHackers @bugfix Stubbjax 26/09/2025 The Tunnel System now heals // all units once per frame instead of once per frame per Tunnel Network. TunnelTracker* tunnelSystem = getTunnelSystem(); diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp index 5e1de31615e..114c70088f3 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp @@ -278,7 +278,7 @@ void TunnelTracker::destroyObject( Object *obj, void * ) // ------------------------------------------------------------------------ // heal all the objects within the tunnel system using the iterateContained function -#if PRESERVE_TUNNEL_HEAL_STACKING || RETAIL_COMPATIBLE_CRC +#if RETAIL_COMPATIBLE_CRC || PRESERVE_TUNNEL_HEAL_STACKING void TunnelTracker::healObjects(Real frames) { iterateContained(healObject, &frames, FALSE); @@ -302,7 +302,7 @@ void TunnelTracker::healObject( Object *obj, void *frames) { //get the number of frames to heal -#if PRESERVE_TUNNEL_HEAL_STACKING || RETAIL_COMPATIBLE_CRC +#if RETAIL_COMPATIBLE_CRC || PRESERVE_TUNNEL_HEAL_STACKING Real *framesForFullHeal = (Real*)frames; #else UnsignedInt* framesForFullHeal = (UnsignedInt*)frames; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/PoisonedBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/PoisonedBehavior.cpp index c593952333a..1cadfe55df4 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/PoisonedBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/PoisonedBehavior.cpp @@ -159,7 +159,7 @@ void PoisonedBehavior::startPoisonedEffects( const DamageInfo *damageInfo ) // We are going to take the damage dealt by the original poisoner every so often for a while. m_poisonDamageAmount = damageInfo->out.m_actualDamageDealt; -#if !RETAIL_COMPATIBLE_CRC && !PRESERVE_NO_XP_FROM_POISON_KILLS +#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_NO_XP_FROM_POISON_KILLS) // TheSuperHackers @bugfix Stubbjax 03/09/2025 Allow poison damage to award xp to the poison source. m_poisonSource = damageInfo->in.m_sourceID; #endif diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp index 550d5bba1e1..21ce7bd81ae 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp @@ -842,7 +842,7 @@ void ActiveBody::attemptHealing( DamageInfo *damageInfo ) //(object pointer loses scope as soon as atteptdamage's caller ends) m_lastDamageInfo = *damageInfo; m_lastDamageCleared = false; -#if PRESERVE_STRUCTURE_STEALTH_DURING_REPAIR +#if RETAIL_COMPATIBLE_CRC || PRESERVE_STRUCTURE_STEALTH_DURING_REPAIR m_lastDamageTimestamp = TheGameLogic->getFrame(); #endif m_lastHealingTimestamp = TheGameLogic->getFrame(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp index 01decec3f0a..e9ae3d22976 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp @@ -53,7 +53,7 @@ CrateCollideModuleData::CrateCollideModuleData() m_executeAnimationFades = TRUE; m_isBuildingPickup = FALSE; m_isHumanOnlyPickup = FALSE; - m_allowMultiPickup = (PRESERVE_MULTI_CRATE_PICKUP != 0); + m_allowMultiPickup = (RETAIL_COMPATIBLE_CRC != 0 || PRESERVE_MULTI_CRATE_PICKUP != 0); m_executeFX = nullptr; m_pickupScience = SCIENCE_INVALID; m_executionAnimationTemplate = AsciiString::TheEmptyString; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp index aa6a5f1aa5d..b8b3115beea 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TunnelContain.cpp @@ -549,7 +549,7 @@ UpdateSleepTime TunnelContain::update() if (controllingPlayer) { TunnelTracker *tunnelSystem = controllingPlayer->getTunnelSystem(); -#if PRESERVE_TUNNEL_HEAL_STACKING || RETAIL_COMPATIBLE_CRC +#if RETAIL_COMPATIBLE_CRC || PRESERVE_TUNNEL_HEAL_STACKING if (tunnelSystem) { const TunnelContainModuleData* modData = getTunnelContainModuleData(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp index 892c1f120ab..b77ede9b68e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp @@ -1428,7 +1428,7 @@ class GenericObjectCreationNugget : public ObjectCreationNugget } } -#if !RETAIL_COMPATIBLE_CRC && !PRESERVE_NO_XP_FROM_OCL_KILLS +#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_NO_XP_FROM_OCL_KILLS) ObjectID sinkID = sourceObj->getExperienceTracker()->getExperienceSink(); firstObject->getExperienceTracker()->setExperienceSink(sinkID != INVALID_ID ? sinkID : sourceObj->getID()); #endif diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp index d5322eea49f..f893bccec3d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp @@ -312,7 +312,7 @@ Bool StealthUpdate::allowedToStealth( Object *stealthOwner ) const if( flags & STEALTH_NOT_WHILE_TAKING_DAMAGE && self->getBodyModule()->getLastDamageTimestamp() >= now - 1 ) { -#if PRESERVE_STRUCTURE_STEALTH_DURING_REPAIR +#if RETAIL_COMPATIBLE_CRC || PRESERVE_STRUCTURE_STEALTH_DURING_REPAIR //Only if it's not healing damage. if( self->getBodyModule()->getLastDamageInfo()->in.m_damageType != DAMAGE_HEALING ) #endif From 5ef79850befb37c3c5edc282f73446d8226d84e1 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Tue, 19 May 2026 23:57:58 +0200 Subject: [PATCH 04/20] fix(gameengine): Remove music tracks as prerequisite to initialize the game engine (#2737) --- Core/GameEngine/Include/Common/GameAudio.h | 2 -- .../Source/Common/Audio/GameAudio.cpp | 27 ------------------- .../GameEngine/Source/Common/GameEngine.cpp | 2 -- .../Tools/WorldBuilder/src/WorldBuilder.cpp | 3 --- .../GameEngine/Source/Common/GameEngine.cpp | 2 -- .../Tools/WorldBuilder/src/WorldBuilder.cpp | 3 --- 6 files changed, 39 deletions(-) diff --git a/Core/GameEngine/Include/Common/GameAudio.h b/Core/GameEngine/Include/Common/GameAudio.h index dae9d7cd132..5c83fd91681 100644 --- a/Core/GameEngine/Include/Common/GameAudio.h +++ b/Core/GameEngine/Include/Common/GameAudio.h @@ -296,8 +296,6 @@ class AudioManager : public SubsystemInterface // For the file cache to know when to remove files. virtual void closeAnySamplesUsingFile( const void *fileToClose ) = 0; - virtual Bool isMusicAlreadyLoaded() const; - Bool getDisallowSpeech() const { return m_disallowSpeech; } void setDisallowSpeech( Bool disallowSpeech ) { m_disallowSpeech = disallowSpeech; } diff --git a/Core/GameEngine/Source/Common/Audio/GameAudio.cpp b/Core/GameEngine/Source/Common/Audio/GameAudio.cpp index c1df413bb7a..41be35aeeed 100644 --- a/Core/GameEngine/Source/Common/Audio/GameAudio.cpp +++ b/Core/GameEngine/Source/Common/Audio/GameAudio.cpp @@ -944,33 +944,6 @@ Real AudioManager::getAudioLengthMS( const AudioEventRTS *event ) getFileLengthMS(tmpEvent.getDecayFilename()); } -//------------------------------------------------------------------------------------------------- -Bool AudioManager::isMusicAlreadyLoaded() const -{ - const AudioEventInfo *musicToLoad = nullptr; - AudioEventInfoHash::const_iterator it; - for (it = m_allAudioEventInfo.begin(); it != m_allAudioEventInfo.end(); ++it) { - if (it->second) { - const AudioEventInfo *aet = it->second; - if (aet->m_soundType == AT_Music) { - musicToLoad = aet; - } - } - } - - if (!musicToLoad) { - return FALSE; - } - - AudioEventRTS aud; - aud.setAudioEventInfo(musicToLoad); - aud.generateFilename(); - - AsciiString astr = aud.getFilename(); - - return (TheFileSystem->doesFileExist(astr.str())); -} - //------------------------------------------------------------------------------------------------- void AudioManager::findAllAudioEventsOfType( AudioType audioType, std::vector& allEvents ) { diff --git a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp index ce2fdf16bac..0350d007b23 100644 --- a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp @@ -441,8 +441,6 @@ void GameEngine::init() initSubsystem(TheGlobalLanguageData,"TheGlobalLanguageData",MSGNEW("GameEngineSubsystem") GlobalLanguage, nullptr); // must be before the game text TheGlobalLanguageData->parseCustomDefinition(); initSubsystem(TheAudio,"TheAudio", createAudioManager(TheGlobalData->m_headless), nullptr); - if (!TheAudio->isMusicAlreadyLoaded()) - setQuitting(TRUE); #if RTS_ZEROHOUR && RETAIL_COMPATIBLE_CRC TheNameKeyGenerator->syncNameKeyID(); diff --git a/Generals/Code/Tools/WorldBuilder/src/WorldBuilder.cpp b/Generals/Code/Tools/WorldBuilder/src/WorldBuilder.cpp index 71d6c44b1f3..b71492898b9 100644 --- a/Generals/Code/Tools/WorldBuilder/src/WorldBuilder.cpp +++ b/Generals/Code/Tools/WorldBuilder/src/WorldBuilder.cpp @@ -367,9 +367,6 @@ BOOL CWorldBuilderApp::InitInstance() initSubsystem(TheScriptEngine, (ScriptEngine*)(new ScriptEngine())); initSubsystem(TheAudio, (AudioManager*)new MilesAudioManager()); - if (!TheAudio->isMusicAlreadyLoaded()) - return FALSE; - initSubsystem(TheVideoPlayer, (VideoPlayerInterface*)(new VideoPlayer())); initSubsystem(TheModuleFactory, (ModuleFactory*)(new W3DModuleFactory())); initSubsystem(TheSidesList, new SidesList()); diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp index a34487538d8..ad861da8d90 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp @@ -526,8 +526,6 @@ void GameEngine::init() DEBUG_LOG(("%s", Buf));//////////////////////////////////////////////////////////////////////////// #endif///////////////////////////////////////////////////////////////////////////////////////////// initSubsystem(TheAudio,"TheAudio", createAudioManager(TheGlobalData->m_headless), nullptr); - if (!TheAudio->isMusicAlreadyLoaded()) - setQuitting(TRUE); #if RTS_ZEROHOUR && RETAIL_COMPATIBLE_CRC TheNameKeyGenerator->syncNameKeyID(); diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp index 7efd924e2b1..7f49898e497 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp @@ -384,9 +384,6 @@ BOOL CWorldBuilderApp::InitInstance() ini.loadFileDirectory( "Data\\Scripts\\Scripts", INI_LOAD_OVERWRITE, nullptr ); initSubsystem(TheAudio, (AudioManager*)new MilesAudioManager()); - if (!TheAudio->isMusicAlreadyLoaded()) - return FALSE; - initSubsystem(TheVideoPlayer, (VideoPlayerInterface*)(new VideoPlayer())); initSubsystem(TheModuleFactory, (ModuleFactory*)(new W3DModuleFactory())); initSubsystem(TheSidesList, new SidesList()); From 159447416ede917ff2eac8f12979e82df191b23d Mon Sep 17 00:00:00 2001 From: Stubbjax <11547761+Stubbjax@users.noreply.github.com> Date: Wed, 20 May 2026 17:00:13 +1000 Subject: [PATCH 05/20] bugfix(physics): Prevent dead units from repeatedly dealing crash damage on collision with non-buildings (#2204) --- .../GameLogic/Object/Update/PhysicsUpdate.cpp | 21 +++++++++++++++++++ .../GameLogic/Object/Update/PhysicsUpdate.cpp | 21 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index a56790b941f..13b1fe8c344 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -32,8 +32,10 @@ #define NO_DEBUG_CRC #include "Common/PerfTimer.h" +#include "Common/Player.h" #include "Common/ThingTemplate.h" #include "Common/Xfer.h" +#include "GameClient/FXList.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Module/AIUpdate.h" #include "GameLogic/Module/BodyModule.h" @@ -1262,7 +1264,26 @@ void PhysicsBehavior::onCollide( Object *other, const Coord3D *loc, const Coord3 // fall into a nonbuilding -- whatever. if we're a vehicle, quietly do a little damage. if (obj->isKindOf(KINDOF_VEHICLE)) { +#if RETAIL_COMPATIBLE_CRC TheWeaponStore->createAndFireTempWeapon(getPhysicsBehaviorModuleData()->m_vehicleCrashesIntoNonBuildingWeaponTemplate, obj, obj->getPosition()); +#else + // TheSuperHackers @bugfix Stubbjax 19/04/2026 Prevent non-building collisions from repeatedly dealing collateral damage to other objects. + const WeaponTemplate* weaponTemplate = getPhysicsBehaviorModuleData()->m_vehicleCrashesIntoNonBuildingWeaponTemplate; + if (weaponTemplate != nullptr) + { + WeaponBonus nullBonus; + + DamageInfo damageInfo; + damageInfo.in.m_damageType = weaponTemplate->getDamageType(); + damageInfo.in.m_deathType = weaponTemplate->getDeathType(); + damageInfo.in.m_sourceID = obj->getID(); + damageInfo.in.m_sourcePlayerMask = obj->getControllingPlayer() ? obj->getControllingPlayer()->getPlayerMask() : 0; + damageInfo.in.m_amount = weaponTemplate->getPrimaryDamage(nullBonus); + + other->attemptDamage(&damageInfo); + FXList::doFXObj(weaponTemplate->getFireFX(obj->getVeterancyLevel()), obj); + } +#endif } } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index ccc57226348..10b09f534cb 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -32,8 +32,10 @@ #define NO_DEBUG_CRC #include "Common/PerfTimer.h" +#include "Common/Player.h" #include "Common/ThingTemplate.h" #include "Common/Xfer.h" +#include "GameClient/FXList.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Module/AIUpdate.h" #include "GameLogic/Module/BodyModule.h" @@ -1387,7 +1389,26 @@ void PhysicsBehavior::onCollide( Object *other, const Coord3D *loc, const Coord3 // fall into a nonbuilding -- whatever. if we're a vehicle, quietly do a little damage. if (obj->isKindOf(KINDOF_VEHICLE)) { +#if RETAIL_COMPATIBLE_CRC TheWeaponStore->createAndFireTempWeapon(getPhysicsBehaviorModuleData()->m_vehicleCrashesIntoNonBuildingWeaponTemplate, obj, obj->getPosition()); +#else + // TheSuperHackers @bugfix Stubbjax 19/04/2026 Prevent non-building collisions from repeatedly dealing collateral damage to other objects. + const WeaponTemplate* weaponTemplate = getPhysicsBehaviorModuleData()->m_vehicleCrashesIntoNonBuildingWeaponTemplate; + if (weaponTemplate != nullptr) + { + WeaponBonus nullBonus; + + DamageInfo damageInfo; + damageInfo.in.m_damageType = weaponTemplate->getDamageType(); + damageInfo.in.m_deathType = weaponTemplate->getDeathType(); + damageInfo.in.m_sourceID = obj->getID(); + damageInfo.in.m_sourcePlayerMask = obj->getControllingPlayer() ? obj->getControllingPlayer()->getPlayerMask() : 0; + damageInfo.in.m_amount = weaponTemplate->getPrimaryDamage(nullBonus); + + other->attemptDamage(&damageInfo); + FXList::doFXObj(weaponTemplate->getFireFX(obj->getVeterancyLevel()), obj); + } +#endif } } } From f8476f04e5396bd0510a9f23ed043f3879b14926 Mon Sep 17 00:00:00 2001 From: Stubbjax <11547761+Stubbjax@users.noreply.github.com> Date: Wed, 20 May 2026 17:00:57 +1000 Subject: [PATCH 06/20] bugfix(physics): Prevent building crash damage from dealing damage to other objects (#2723) --- .../GameLogic/Object/Update/PhysicsUpdate.cpp | 19 +++++++++++++++++++ .../GameLogic/Object/Update/PhysicsUpdate.cpp | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index 13b1fe8c344..3538f7742d0 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -1254,7 +1254,26 @@ void PhysicsBehavior::onCollide( Object *other, const Coord3D *loc, const Coord3 // fall into a building. if a vehicle, blow up. then destroy ourself (not die), regardless. if (obj->isKindOf(KINDOF_VEHICLE)) { +#if RETAIL_COMPATIBLE_CRC TheWeaponStore->createAndFireTempWeapon(getPhysicsBehaviorModuleData()->m_vehicleCrashesIntoBuildingWeaponTemplate, obj, obj->getPosition()); +#else + // TheSuperHackers @bugfix Stubbjax 17/05/2026 Prevent building collisions from dealing collateral damage to other objects. + const WeaponTemplate* weaponTemplate = getPhysicsBehaviorModuleData()->m_vehicleCrashesIntoBuildingWeaponTemplate; + if (weaponTemplate != nullptr) + { + WeaponBonus nullBonus; + + DamageInfo damageInfo; + damageInfo.in.m_damageType = weaponTemplate->getDamageType(); + damageInfo.in.m_deathType = weaponTemplate->getDeathType(); + damageInfo.in.m_sourceID = obj->getID(); + damageInfo.in.m_sourcePlayerMask = obj->getControllingPlayer() ? obj->getControllingPlayer()->getPlayerMask() : 0; + damageInfo.in.m_amount = weaponTemplate->getPrimaryDamage(nullBonus); + + other->attemptDamage(&damageInfo); + FXList::doFXObj(weaponTemplate->getFireFX(obj->getVeterancyLevel()), obj); + } +#endif } TheGameLogic->destroyObject(obj); return; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index 10b09f534cb..fcef6cd591d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -1379,7 +1379,26 @@ void PhysicsBehavior::onCollide( Object *other, const Coord3D *loc, const Coord3 // fall into a building. if a vehicle, blow up. then destroy ourself (not die), regardless. if (obj->isKindOf(KINDOF_VEHICLE)) { +#if RETAIL_COMPATIBLE_CRC TheWeaponStore->createAndFireTempWeapon(getPhysicsBehaviorModuleData()->m_vehicleCrashesIntoBuildingWeaponTemplate, obj, obj->getPosition()); +#else + // TheSuperHackers @bugfix Stubbjax 17/05/2026 Prevent building collisions from dealing collateral damage to other objects. + const WeaponTemplate* weaponTemplate = getPhysicsBehaviorModuleData()->m_vehicleCrashesIntoBuildingWeaponTemplate; + if (weaponTemplate != nullptr) + { + WeaponBonus nullBonus; + + DamageInfo damageInfo; + damageInfo.in.m_damageType = weaponTemplate->getDamageType(); + damageInfo.in.m_deathType = weaponTemplate->getDeathType(); + damageInfo.in.m_sourceID = obj->getID(); + damageInfo.in.m_sourcePlayerMask = obj->getControllingPlayer() ? obj->getControllingPlayer()->getPlayerMask() : 0; + damageInfo.in.m_amount = weaponTemplate->getPrimaryDamage(nullBonus); + + other->attemptDamage(&damageInfo); + FXList::doFXObj(weaponTemplate->getFireFX(obj->getVeterancyLevel()), obj); + } +#endif } TheGameLogic->destroyObject(obj); return; From 5a4187cea27a3f0bd4b8fd20ba20614e83bf7d73 Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Wed, 20 May 2026 13:16:24 +0200 Subject: [PATCH 07/20] fix(network): Verify accepted type of incoming game messages (#2708) --- .../Include/GameNetwork/NetCommandMsg.h | 1 + .../Source/GameNetwork/NetCommandMsg.cpp | 4 +++ .../GameEngine/Source/GameNetwork/Network.cpp | 29 +++++++++++-------- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/Core/GameEngine/Include/GameNetwork/NetCommandMsg.h b/Core/GameEngine/Include/GameNetwork/NetCommandMsg.h index 87c48572837..badd16826e6 100644 --- a/Core/GameEngine/Include/GameNetwork/NetCommandMsg.h +++ b/Core/GameEngine/Include/GameNetwork/NetCommandMsg.h @@ -174,6 +174,7 @@ class NetGameCommandMsg : public NetCommandMsgTgetType() > GameMessage::MSG_BEGIN_NETWORK_MESSAGES) && (msg->getType() < GameMessage::MSG_END_NETWORK_MESSAGES))) { - return TRUE; - } - return FALSE; +Bool Network::isMessageTypeWithinNetworkRange(GameMessage::Type type) { + return type > GameMessage::MSG_BEGIN_NETWORK_MESSAGES && type < GameMessage::MSG_END_NETWORK_MESSAGES; } /** @@ -464,7 +458,7 @@ void Network::GetCommandsFromCommandList() { GameMessage *next = nullptr; while (msg != nullptr) { next = msg->next(); - if (isTransferCommand(msg)) { // Is this something we should be sending to the other players? + if (isMessageTypeWithinNetworkRange(msg->getType())) { // Is this something we should be sending to the other players? if (m_localStatus == NETLOCALSTATUS_INGAME) { m_conMgr->sendLocalGameMessage(msg, getExecutionFrame()); } @@ -586,8 +580,19 @@ void Network::RelayCommandsToCommandList(UnsignedInt frame) { while (msg != nullptr) { NetCommandType cmdType = msg->getCommand()->getNetCommandType(); if (cmdType == NETCOMMANDTYPE_GAMECOMMAND) { - //DEBUG_LOG(("Network::RelayCommandsToCommandList - appending command %d of type %s to command list on frame %d", msg->getCommand()->getID(), ((NetGameCommandMsg *)msg->getCommand())->constructGameMessage()->getCommandAsString(), TheGameLogic->getFrame())); - TheCommandList->appendMessage(((NetGameCommandMsg *)msg->getCommand())->constructGameMessage()); + NetGameCommandMsg* gmsg = static_cast(msg->getCommand()); +#if RETAIL_COMPATIBLE_CRC + TheCommandList->appendMessage(gmsg->constructGameMessage()); +#else + // TheSuperHackers @fix stephanmeesters 14/05/2026 Verify accepted type of incoming game messages + if (isMessageTypeWithinNetworkRange(gmsg->getGameMessageType())) { + //DEBUG_LOG(("Network::RelayCommandsToCommandList - appending command %d of type %s to command list on frame %d", msg->getCommand()->getID(), gmsg->getCommandAsString(), TheGameLogic->getFrame())); + TheCommandList->appendMessage(gmsg->constructGameMessage()); + } else { + DEBUG_LOG(("Network::RelayCommandsToCommandList - rejecting game message from player %d of type %s, which is not a network type.", + gmsg->getPlayerID(), GameMessage::getCommandTypeAsString(gmsg->getGameMessageType()))); + } +#endif } else { processFrameSynchronizedNetCommand(msg); } From 7cb3c11e77bdc41f084f99b60310863feea790a9 Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Wed, 20 May 2026 13:17:01 +0200 Subject: [PATCH 08/20] fix(particlesys): Add or simplify null-checks to createParticleSystem calls (#2724) --- .../Drawable/Update/BeaconClientUpdate.cpp | 11 ++-- .../Source/GameClient/System/ParticleSys.cpp | 8 +-- .../Drawable/Draw/W3DTankTruckDraw.cpp | 5 +- .../GameClient/Drawable/Draw/W3DTruckDraw.cpp | 5 +- .../Object/Behavior/AutoHealBehavior.cpp | 9 +-- .../GameLogic/Object/ObjectCreationList.cpp | 4 +- .../Update/HelicopterSlowDeathUpdate.cpp | 40 ++++++------- .../GameLogic/Object/Update/LaserUpdate.cpp | 18 ++---- .../Update/ParticleUplinkCannonUpdate.cpp | 23 +++----- .../GameLogic/Object/Update/SlavedUpdate.cpp | 39 ++++++------- .../Object/Update/StealthDetectorUpdate.cpp | 56 ++++++++----------- .../GameLogic/ScriptEngine/ScriptEngine.cpp | 15 ++--- .../Object/Behavior/AutoHealBehavior.cpp | 9 +-- .../GameLogic/Object/ObjectCreationList.cpp | 4 +- .../Update/AIUpdate/ChinookAIUpdate.cpp | 14 ++--- .../GameLogic/Object/Update/EMPUpdate.cpp | 9 ++- .../Update/HelicopterSlowDeathUpdate.cpp | 40 ++++++------- .../GameLogic/Object/Update/LaserUpdate.cpp | 18 ++---- .../Update/ParticleUplinkCannonUpdate.cpp | 23 +++----- .../GameLogic/Object/Update/SlavedUpdate.cpp | 39 ++++++------- .../Object/Update/StealthDetectorUpdate.cpp | 53 ++++++++---------- .../GameLogic/ScriptEngine/ScriptEngine.cpp | 15 ++--- 22 files changed, 188 insertions(+), 269 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/Drawable/Update/BeaconClientUpdate.cpp b/Core/GameEngine/Source/GameClient/Drawable/Update/BeaconClientUpdate.cpp index c76e3dc3528..63071f8340c 100644 --- a/Core/GameEngine/Source/GameClient/Drawable/Update/BeaconClientUpdate.cpp +++ b/Core/GameEngine/Source/GameClient/Drawable/Update/BeaconClientUpdate.cpp @@ -108,14 +108,11 @@ static ParticleSystem* createParticleSystem( Drawable *draw ) templateName.format("BeaconSmokeFFFFFF"); const ParticleSystemTemplate *failsafeTemplate = TheParticleSystemManager->findTemplate( templateName ); DEBUG_ASSERTCRASH(failsafeTemplate, ("Doh, this is bad \n I Could not even find the white particle system to make a failsafe system out of.")); - if (failsafeTemplate) + system = TheParticleSystemManager->createParticleSystem( failsafeTemplate ); + if (system) { - system = TheParticleSystemManager->createParticleSystem( failsafeTemplate ); - if (system) - { - system->attachToDrawable( draw ); - system->tintAllColors( obj->getIndicatorColor() ); - } + system->attachToDrawable( draw ); + system->tintAllColors( obj->getIndicatorColor() ); } } } diff --git a/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp b/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp index b516a335000..da316af3bdb 100644 --- a/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp +++ b/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp @@ -2061,9 +2061,9 @@ Bool ParticleSystem::update( Int localPlayerIndex ) if (m_attachedSystemName.isEmpty() == false) { const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate( m_attachedSystemName ); - if (tmp) + ParticleSystem *sys = TheParticleSystemManager->createParticleSystem( tmp, TRUE ); + if (sys) { - ParticleSystem *sys = TheParticleSystemManager->createParticleSystem( tmp, TRUE ); sys->setControlParticle( p ); p->controlParticleSystem( sys ); } @@ -2867,9 +2867,7 @@ ParticleSystem *ParticleSystemTemplate::createSlaveSystem( Bool createSlaves ) c if (m_slaveTemplate == nullptr && m_slaveSystemName.isEmpty() == false) m_slaveTemplate = TheParticleSystemManager->findTemplate( m_slaveSystemName ); - ParticleSystem *slave = nullptr; - if (m_slaveTemplate) - slave = TheParticleSystemManager->createParticleSystem( m_slaveTemplate, createSlaves ); + ParticleSystem *slave = TheParticleSystemManager->createParticleSystem( m_slaveTemplate, createSlaves ); return slave; } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp index 5638cd1b3fb..8d936948ed6 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp @@ -247,9 +247,10 @@ void W3DTankTruckDraw::createWheelEmitters() { if (m_truckEffectIDs[i] == INVALID_PARTICLE_SYSTEM_ID) { - if (const ParticleSystemTemplate *sysTemplate = TheParticleSystemManager->findTemplate(*effectNames[i])) + const ParticleSystemTemplate *sysTemplate = TheParticleSystemManager->findTemplate(*effectNames[i]); + ParticleSystem *particleSys = TheParticleSystemManager->createParticleSystem( sysTemplate ); + if (particleSys) { - ParticleSystem *particleSys = TheParticleSystemManager->createParticleSystem( sysTemplate ); particleSys->attachToObject(getDrawable()->getObject()); // important: mark it as do-not-save, since we'll just re-create it when we reload. particleSys->setSaveable(FALSE); diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTruckDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTruckDraw.cpp index afcb3d11110..4e4f59ef93d 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTruckDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTruckDraw.cpp @@ -170,9 +170,10 @@ void W3DTruckDraw::createWheelEmitters() { if (m_truckEffectIDs[i] == INVALID_PARTICLE_SYSTEM_ID) { - if (const ParticleSystemTemplate *sysTemplate = TheParticleSystemManager->findTemplate(*effectNames[i])) + const ParticleSystemTemplate *sysTemplate = TheParticleSystemManager->findTemplate(*effectNames[i]); + ParticleSystem *particleSys = TheParticleSystemManager->createParticleSystem( sysTemplate ); + if (particleSys) { - ParticleSystem *particleSys = TheParticleSystemManager->createParticleSystem( sysTemplate ); particleSys->attachToObject(getDrawable()->getObject()); // important: mark it as do-not-save, since we'll just re-create it when we reload. particleSys->setSaveable(FALSE); diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp index 2ba064bcf06..2fb184d1234 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp @@ -279,13 +279,10 @@ void AutoHealBehavior::pulseHealObject( Object *obj ) obj->attemptHealingFromSoleBenefactor( data->m_healingAmount, getObject(), data->m_healingDelay ); - if( data->m_unitHealPulseParticleSystemTmpl ) + ParticleSystem *system = TheParticleSystemManager->createParticleSystem( data->m_unitHealPulseParticleSystemTmpl ); + if( system ) { - ParticleSystem *system = TheParticleSystemManager->createParticleSystem( data->m_unitHealPulseParticleSystemTmpl ); - if( system ) - { - system->setPosition( obj->getPosition() ); - } + system->setPosition( obj->getPosition() ); } m_soonestHealFrame = TheGameLogic->getFrame() + data->m_healingDelay;// In case onDamage tries to wake us up early diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp index 003c55fde46..100c5da9982 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp @@ -959,9 +959,9 @@ class GenericObjectCreationNugget : public ObjectCreationNugget if (!m_particleSysName.isEmpty()) { const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate(m_particleSysName); - if (tmp) + ParticleSystem *sys = TheParticleSystemManager->createParticleSystem(tmp); + if (sys) { - ParticleSystem *sys = TheParticleSystemManager->createParticleSystem(tmp); sys->attachToObject(obj); } } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/HelicopterSlowDeathUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/HelicopterSlowDeathUpdate.cpp index b8c2be438f5..78210364329 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/HelicopterSlowDeathUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/HelicopterSlowDeathUpdate.cpp @@ -229,40 +229,36 @@ void HelicopterSlowDeathBehavior::beginSlowDeath( const DamageInfo *damageInfo ) locomotor->setMaxBraking( modData->m_maxBraking ); // attach particle system to bone if present - if( modData->m_attachParticleSystem ) + ParticleSystem *pSys = TheParticleSystemManager->createParticleSystem( modData->m_attachParticleSystem ); + if( pSys ) { - ParticleSystem *pSys = TheParticleSystemManager->createParticleSystem( modData->m_attachParticleSystem ); - if( pSys ) + + // where do the offset attachment to + if( modData->m_attachParticleBone.isEmpty() == FALSE ) { + Drawable *draw = getObject()->getDrawable(); - // where do the offset attachment to - if( modData->m_attachParticleBone.isEmpty() == FALSE ) + if( draw ) { - Drawable *draw = getObject()->getDrawable(); + Coord3D pos; - if( draw ) - { - Coord3D pos; - - if( draw->getPristineBonePositions( modData->m_attachParticleBone.str(), 0, &pos, nullptr, 1 ) ) - pSys->setPosition( &pos ); - - } + if( draw->getPristineBonePositions( modData->m_attachParticleBone.str(), 0, &pos, nullptr, 1 ) ) + pSys->setPosition( &pos ); } - else - { - // use location coord specified ... it will be zero if not given which is center of obj anyway - pSys->setPosition( &modData->m_attachParticleLoc ); - - } + } + else + { - // attach the particle system to the object - pSys->attachToObject( getObject() ); + // use location coord specified ... it will be zero if not given which is center of obj anyway + pSys->setPosition( &modData->m_attachParticleLoc ); } + // attach the particle system to the object + pSys->attachToObject( getObject() ); + } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/LaserUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/LaserUpdate.cpp index fb1276731fd..13083d4cc31 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/LaserUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/LaserUpdate.cpp @@ -258,13 +258,10 @@ void LaserUpdate::initLaser( const Object *parent, const Coord3D *startPos, cons if( data->m_particleSystemName.isNotEmpty() ) { const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate( data->m_particleSystemName ); - if( tmp ) + system = TheParticleSystemManager->createParticleSystem( tmp ); + if( system ) { - system = TheParticleSystemManager->createParticleSystem( tmp ); - if( system ) - { - m_particleSystemID = system->getSystemID(); - } + m_particleSystemID = system->getSystemID(); } } @@ -272,13 +269,10 @@ void LaserUpdate::initLaser( const Object *parent, const Coord3D *startPos, cons if( data->m_targetParticleSystemName.isNotEmpty() ) { const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate( data->m_targetParticleSystemName ); - if( tmp ) + system = TheParticleSystemManager->createParticleSystem( tmp ); + if( system ) { - system = TheParticleSystemManager->createParticleSystem( tmp ); - if( system ) - { - m_targetParticleSystemID = system->getSystemID(); - } + m_targetParticleSystemID = system->getSystemID(); } } } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp index 980a9c525af..7441ff504c2 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp @@ -849,15 +849,11 @@ void ParticleUplinkCannonUpdate::createConnectorFlare( IntensityTypes intensity if( str.isNotEmpty() ) { const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate( str ); - ParticleSystem *system; - if( tmp ) + ParticleSystem *system = TheParticleSystemManager->createParticleSystem( tmp ); + if( system ) { - system = TheParticleSystemManager->createParticleSystem( tmp ); - if( system ) - { - m_connectorSystemID = system->getSystemID(); - system->setPosition( &m_connectorNodePosition ); - } + m_connectorSystemID = system->getSystemID(); + system->setPosition( &m_connectorNodePosition ); } } } @@ -885,14 +881,11 @@ void ParticleUplinkCannonUpdate::createLaserBaseFlare( IntensityTypes intensity if( str.isNotEmpty() ) { const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate( str ); - if( tmp ) + ParticleSystem *system = TheParticleSystemManager->createParticleSystem( tmp ); + if( system ) { - ParticleSystem *system = TheParticleSystemManager->createParticleSystem( tmp ); - if( system ) - { - m_laserBaseSystemID = system->getSystemID(); - system->setPosition( &m_laserOriginPosition ); - } + m_laserBaseSystemID = system->getSystemID(); + system->setPosition( &m_laserOriginPosition ); } } } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/SlavedUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/SlavedUpdate.cpp index 6ffef8dc861..7441f62a78d 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/SlavedUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/SlavedUpdate.cpp @@ -616,30 +616,27 @@ void SlavedUpdate::setRepairState( RepairStates repairState ) if( !data->m_weldingSysName.isEmpty() ) { const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate( data->m_weldingSysName ); - if( tmp ) + ParticleSystem *weldingSys = TheParticleSystemManager->createParticleSystem(tmp); + if( weldingSys ) { - ParticleSystem *weldingSys = TheParticleSystemManager->createParticleSystem(tmp); - if( weldingSys ) + Coord3D pos; + //Get the bone position + if( draw->getPristineBonePositions( data->m_weldingFXBone.str(), 0, &pos, nullptr, 1 ) ) { - Coord3D pos; - //Get the bone position - if( draw->getPristineBonePositions( data->m_weldingFXBone.str(), 0, &pos, nullptr, 1 ) ) - { - pos.add( obj->getPosition() ); - } - else - { - pos.set( obj->getPosition() ); - } - - weldingSys->setPosition( &pos ); - Real time = (Real)(m_framesToWait * LOGICFRAMES_PER_SECOND); - weldingSys->setLifetimeRange( time, time ); - - AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_repairSparks; - soundToPlay.setPosition( &pos ); - TheAudio->addAudioEvent( &soundToPlay ); + pos.add( obj->getPosition() ); } + else + { + pos.set( obj->getPosition() ); + } + + weldingSys->setPosition( &pos ); + Real time = (Real)(m_framesToWait * LOGICFRAMES_PER_SECOND); + weldingSys->setLifetimeRange( time, time ); + + AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_repairSparks; + soundToPlay.setPosition( &pos ); + TheAudio->addAudioEvent( &soundToPlay ); } } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/StealthDetectorUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/StealthDetectorUpdate.cpp index d96c5a8a32f..4f8fbbdef21 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/StealthDetectorUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/StealthDetectorUpdate.cpp @@ -290,22 +290,16 @@ UpdateSleepTime StealthDetectorUpdate::update() theirDraw->setHeatVisionOpacity( 1.0f ); } - if (data->m_IRGridParticleSysTmpl) + const ParticleSystemTemplate *gridTemplate = data->m_IRGridParticleSysTmpl; + ParticleSystem *sys = TheParticleSystemManager->createParticleSystem( gridTemplate );//GRID + if (sys) { - const ParticleSystemTemplate *gridTemplate = data->m_IRGridParticleSysTmpl; - if (gridTemplate) - { - ParticleSystem *sys = TheParticleSystemManager->createParticleSystem( gridTemplate );//GRID - if (sys) - { - Coord3D gridPosition = *them->getPosition(); - gridPosition.z = self->getPosition()->z + 17; - gridPosition.x -= ((Int)gridPosition.x)%12; - gridPosition.y -= ((Int)gridPosition.y)%12; + Coord3D gridPosition = *them->getPosition(); + gridPosition.z = self->getPosition()->z + 17; + gridPosition.x -= ((Int)gridPosition.x)%12; + gridPosition.y -= ((Int)gridPosition.y)%12; - sys->setPosition( &gridPosition ); - } - } + sys->setPosition( &gridPosition ); } } @@ -352,34 +346,28 @@ UpdateSleepTime StealthDetectorUpdate::update() else pingTemplate = data->m_IRParticleSysTmpl; - if (pingTemplate) + ParticleSystem *sys = TheParticleSystemManager->createParticleSystem( pingTemplate ); + if (sys) { - ParticleSystem *sys = TheParticleSystemManager->createParticleSystem( pingTemplate ); - if (sys) - { - if (myDraw) - sys->attachToDrawable( myDraw ); - else - sys->attachToObject( self ); + if (myDraw) + sys->attachToDrawable( myDraw ); + else + sys->attachToObject( self ); - sys->setPosition( &bonePosition ); - } + sys->setPosition( &bonePosition ); } const ParticleSystemTemplate *beaconTemplate = data->m_IRBeaconParticleSysTmpl; - if (beaconTemplate) + sys = TheParticleSystemManager->createParticleSystem( beaconTemplate );//BEACON + if (sys) { - ParticleSystem *sys = TheParticleSystemManager->createParticleSystem( beaconTemplate );//BEACON - if (sys) - { - if (myDraw) - sys->attachToDrawable( myDraw ); - else - sys->attachToObject( self ); + if (myDraw) + sys->attachToDrawable( myDraw ); + else + sys->attachToObject( self ); - sys->setPosition( &bonePosition ); + sys->setPosition( &bonePosition ); - } } AudioEventRTS IRPingSound; diff --git a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp index 7775245e531..1622c63b0d2 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp @@ -9442,15 +9442,12 @@ static void _updateAndSetCurrentSystem() // to be a tiny memory overwrite, now it is a crash since destroy() now has a function call. ParticleSystemTemplate *parentTemp = TheParticleSystemManager->findParentTemplate(pTemp->getName(), 0); - if (parentTemp) { - ParticleSystem *parentSystem = nullptr; - parentSystem = TheParticleSystemManager->createParticleSystem(parentTemp); - - if (parentSystem) { - ParticleSystem::mergeRelatedParticleSystems(parentSystem, st_particleSystem, true); - parentSystem->stop(); - parentSystem->destroy(); - } + ParticleSystem *parentSystem = TheParticleSystemManager->createParticleSystem(parentTemp); + + if (parentSystem) { + ParticleSystem::mergeRelatedParticleSystems(parentSystem, st_particleSystem, true); + parentSystem->stop(); + parentSystem->destroy(); } Coord3D pos; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp index ce73fb30c47..16607a97a1a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp @@ -294,13 +294,10 @@ void AutoHealBehavior::pulseHealObject( Object *obj ) obj->attemptHealingFromSoleBenefactor( data->m_healingAmount, getObject(), data->m_healingDelay ); - if( data->m_unitHealPulseParticleSystemTmpl ) + ParticleSystem *system = TheParticleSystemManager->createParticleSystem( data->m_unitHealPulseParticleSystemTmpl ); + if( system ) { - ParticleSystem *system = TheParticleSystemManager->createParticleSystem( data->m_unitHealPulseParticleSystemTmpl ); - if( system ) - { - system->setPosition( obj->getPosition() ); - } + system->setPosition( obj->getPosition() ); } m_soonestHealFrame = TheGameLogic->getFrame() + data->m_healingDelay;// In case onDamage tries to wake us up early diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp index b77ede9b68e..7a39fcddd47 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp @@ -974,9 +974,9 @@ class GenericObjectCreationNugget : public ObjectCreationNugget if (!m_particleSysName.isEmpty()) { const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate(m_particleSysName); - if (tmp) + ParticleSystem *sys = TheParticleSystemManager->createParticleSystem(tmp); + if (sys) { - ParticleSystem *sys = TheParticleSystemManager->createParticleSystem(tmp); sys->attachToObject(obj); } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/ChinookAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/ChinookAIUpdate.cpp index 8ce9bff6ba1..48146964e19 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/ChinookAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/ChinookAIUpdate.cpp @@ -1199,15 +1199,11 @@ UpdateSleepTime ChinookAIUpdate::update() if ( GameClientRandomValueReal( 0.0f, chopperElevation ) < 5.0f ) { const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate( getChinookAIUpdateModuleData()->m_rotorWashParticleSystem ); - ParticleSystem *system; - if( tmp ) - { - system = TheParticleSystemManager->createParticleSystem( tmp ); - if( system ) - { - system->setPosition( &pos ); - } - } + ParticleSystem *system = TheParticleSystemManager->createParticleSystem( tmp ); + if( system ) + { + system->setPosition( &pos ); + } } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp index 12718e114b1..aba51b4c2ee 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp @@ -449,12 +449,11 @@ UpdateSleepTime LeafletDropBehavior::update() // start shoveling out those leaflets, boys. const LeafletDropBehaviorModuleData *data = getLeafletDropBehaviorModuleData(); const ParticleSystemTemplate *tmp = data->m_leafletFXParticleSystem; - if (tmp) + ParticleSystem *sys = TheParticleSystemManager->createParticleSystem(tmp); + if (sys) { - ParticleSystem *sys = TheParticleSystemManager->createParticleSystem(tmp); - if (sys) - sys->attachToObject(getObject()); - } + sys->attachToObject(getObject()); + } m_fxFired = TRUE; // hey, at least we tried. } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/HelicopterSlowDeathUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/HelicopterSlowDeathUpdate.cpp index e3a86fb24b4..0569bca3e84 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/HelicopterSlowDeathUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/HelicopterSlowDeathUpdate.cpp @@ -229,40 +229,36 @@ void HelicopterSlowDeathBehavior::beginSlowDeath( const DamageInfo *damageInfo ) locomotor->setMaxBraking( modData->m_maxBraking ); // attach particle system to bone if present - if( modData->m_attachParticleSystem ) + ParticleSystem *pSys = TheParticleSystemManager->createParticleSystem( modData->m_attachParticleSystem ); + if( pSys ) { - ParticleSystem *pSys = TheParticleSystemManager->createParticleSystem( modData->m_attachParticleSystem ); - if( pSys ) + + // where do the offset attachment to + if( modData->m_attachParticleBone.isEmpty() == FALSE ) { + Drawable *draw = getObject()->getDrawable(); - // where do the offset attachment to - if( modData->m_attachParticleBone.isEmpty() == FALSE ) + if( draw ) { - Drawable *draw = getObject()->getDrawable(); + Coord3D pos; - if( draw ) - { - Coord3D pos; - - if( draw->getPristineBonePositions( modData->m_attachParticleBone.str(), 0, &pos, nullptr, 1 ) ) - pSys->setPosition( &pos ); - - } + if( draw->getPristineBonePositions( modData->m_attachParticleBone.str(), 0, &pos, nullptr, 1 ) ) + pSys->setPosition( &pos ); } - else - { - // use location coord specified ... it will be zero if not given which is center of obj anyway - pSys->setPosition( &modData->m_attachParticleLoc ); - - } + } + else + { - // attach the particle system to the object - pSys->attachToObject( getObject() ); + // use location coord specified ... it will be zero if not given which is center of obj anyway + pSys->setPosition( &modData->m_attachParticleLoc ); } + // attach the particle system to the object + pSys->attachToObject( getObject() ); + } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LaserUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LaserUpdate.cpp index 613d46a2146..87697c9e546 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LaserUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LaserUpdate.cpp @@ -357,13 +357,10 @@ void LaserUpdate::initLaser( const Object *parent, const Object *target, const C if( data->m_particleSystemName.isNotEmpty() ) { const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate( data->m_particleSystemName ); - if( tmp ) + system = TheParticleSystemManager->createParticleSystem( tmp ); + if( system ) { - system = TheParticleSystemManager->createParticleSystem( tmp ); - if( system ) - { - m_particleSystemID = system->getSystemID(); - } + m_particleSystemID = system->getSystemID(); } } @@ -371,13 +368,10 @@ void LaserUpdate::initLaser( const Object *parent, const Object *target, const C if( data->m_targetParticleSystemName.isNotEmpty() ) { const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate( data->m_targetParticleSystemName ); - if( tmp ) + system = TheParticleSystemManager->createParticleSystem( tmp ); + if( system ) { - system = TheParticleSystemManager->createParticleSystem( tmp ); - if( system ) - { - m_targetParticleSystemID = system->getSystemID(); - } + m_targetParticleSystemID = system->getSystemID(); } } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp index 9b9f5275b38..8819f34d815 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp @@ -934,15 +934,11 @@ void ParticleUplinkCannonUpdate::createConnectorFlare( IntensityTypes intensity if( str.isNotEmpty() ) { const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate( str ); - ParticleSystem *system; - if( tmp ) + ParticleSystem *system = TheParticleSystemManager->createParticleSystem( tmp ); + if( system ) { - system = TheParticleSystemManager->createParticleSystem( tmp ); - if( system ) - { - m_connectorSystemID = system->getSystemID(); - system->setPosition( &m_connectorNodePosition ); - } + m_connectorSystemID = system->getSystemID(); + system->setPosition( &m_connectorNodePosition ); } } } @@ -970,14 +966,11 @@ void ParticleUplinkCannonUpdate::createLaserBaseFlare( IntensityTypes intensity if( str.isNotEmpty() ) { const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate( str ); - if( tmp ) + ParticleSystem *system = TheParticleSystemManager->createParticleSystem( tmp ); + if( system ) { - ParticleSystem *system = TheParticleSystemManager->createParticleSystem( tmp ); - if( system ) - { - m_laserBaseSystemID = system->getSystemID(); - system->setPosition( &m_laserOriginPosition ); - } + m_laserBaseSystemID = system->getSystemID(); + system->setPosition( &m_laserOriginPosition ); } } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SlavedUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SlavedUpdate.cpp index 27303efcc5b..4e50e5d7985 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SlavedUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SlavedUpdate.cpp @@ -621,30 +621,27 @@ void SlavedUpdate::setRepairState( RepairStates repairState ) if( !data->m_weldingSysName.isEmpty() ) { const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate( data->m_weldingSysName ); - if( tmp ) + ParticleSystem *weldingSys = TheParticleSystemManager->createParticleSystem(tmp); + if( weldingSys ) { - ParticleSystem *weldingSys = TheParticleSystemManager->createParticleSystem(tmp); - if( weldingSys ) + Coord3D pos; + //Get the bone position + if( draw->getPristineBonePositions( data->m_weldingFXBone.str(), 0, &pos, nullptr, 1 ) ) { - Coord3D pos; - //Get the bone position - if( draw->getPristineBonePositions( data->m_weldingFXBone.str(), 0, &pos, nullptr, 1 ) ) - { - pos.add( obj->getPosition() ); - } - else - { - pos.set( obj->getPosition() ); - } - - weldingSys->setPosition( &pos ); - Real time = (Real)(m_framesToWait * LOGICFRAMES_PER_SECOND); - weldingSys->setLifetimeRange( time, time ); - - AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_repairSparks; - soundToPlay.setPosition( &pos ); - TheAudio->addAudioEvent( &soundToPlay ); + pos.add( obj->getPosition() ); } + else + { + pos.set( obj->getPosition() ); + } + + weldingSys->setPosition( &pos ); + Real time = (Real)(m_framesToWait * LOGICFRAMES_PER_SECOND); + weldingSys->setLifetimeRange( time, time ); + + AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_repairSparks; + soundToPlay.setPosition( &pos ); + TheAudio->addAudioEvent( &soundToPlay ); } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthDetectorUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthDetectorUpdate.cpp index 7be96b7a61f..746730dc27d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthDetectorUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthDetectorUpdate.cpp @@ -312,18 +312,15 @@ UpdateSleepTime StealthDetectorUpdate::update() if (data->m_IRGridParticleSysTmpl) { const ParticleSystemTemplate *gridTemplate = data->m_IRGridParticleSysTmpl; - if (gridTemplate) + ParticleSystem *sys = TheParticleSystemManager->createParticleSystem( gridTemplate );//GRID + if (sys) { - ParticleSystem *sys = TheParticleSystemManager->createParticleSystem( gridTemplate );//GRID - if (sys) - { - Coord3D gridPosition = *them->getPosition(); - gridPosition.z = self->getPosition()->z + 17; - gridPosition.x -= ((Int)gridPosition.x)%12; - gridPosition.y -= ((Int)gridPosition.y)%12; + Coord3D gridPosition = *them->getPosition(); + gridPosition.z = self->getPosition()->z + 17; + gridPosition.x -= ((Int)gridPosition.x)%12; + gridPosition.y -= ((Int)gridPosition.y)%12; - sys->setPosition( &gridPosition ); - } + sys->setPosition( &gridPosition ); } } @@ -373,34 +370,28 @@ UpdateSleepTime StealthDetectorUpdate::update() else pingTemplate = data->m_IRParticleSysTmpl; - if (pingTemplate) + ParticleSystem *sys = TheParticleSystemManager->createParticleSystem( pingTemplate ); + if (sys) { - ParticleSystem *sys = TheParticleSystemManager->createParticleSystem( pingTemplate ); - if (sys) - { - if (myDraw) - sys->attachToDrawable( myDraw ); - else - sys->attachToObject( self ); - - sys->setPosition( &bonePosition ); - } + if (myDraw) + sys->attachToDrawable( myDraw ); + else + sys->attachToObject( self ); + + sys->setPosition( &bonePosition ); } const ParticleSystemTemplate *beaconTemplate = data->m_IRBeaconParticleSysTmpl; - if (beaconTemplate) + sys = TheParticleSystemManager->createParticleSystem( beaconTemplate );//BEACON + if (sys) { - ParticleSystem *sys = TheParticleSystemManager->createParticleSystem( beaconTemplate );//BEACON - if (sys) - { - if (myDraw) - sys->attachToDrawable( myDraw ); - else - sys->attachToObject( self ); + if (myDraw) + sys->attachToDrawable( myDraw ); + else + sys->attachToObject( self ); - sys->setPosition( &bonePosition ); + sys->setPosition( &bonePosition ); - } } AudioEventRTS IRPingSound; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp index 7c9c612245c..d10dff653a1 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp @@ -10145,15 +10145,12 @@ static void _updateAndSetCurrentSystem() // to be a tiny memory overwrite, now it is a crash since destroy() now has a function call. ParticleSystemTemplate *parentTemp = TheParticleSystemManager->findParentTemplate(pTemp->getName(), 0); - if (parentTemp) { - ParticleSystem *parentSystem = nullptr; - parentSystem = TheParticleSystemManager->createParticleSystem(parentTemp); - - if (parentSystem) { - ParticleSystem::mergeRelatedParticleSystems(parentSystem, st_particleSystem, true); - parentSystem->stop(); - parentSystem->destroy(); - } + ParticleSystem *parentSystem = TheParticleSystemManager->createParticleSystem(parentTemp); + + if (parentSystem) { + ParticleSystem::mergeRelatedParticleSystems(parentSystem, st_particleSystem, true); + parentSystem->stop(); + parentSystem->destroy(); } Coord3D pos; From f7ecdb4cc400f1c966b74720b42ba5abfca1b12f Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Wed, 20 May 2026 13:52:51 +0200 Subject: [PATCH 09/20] refactor(network): Use switch cases in utility functions for network commands (#2725) --- .../Include/GameNetwork/networkutil.h | 4 +- .../Source/GameNetwork/NetworkUtil.cpp | 209 +++++++----------- 2 files changed, 88 insertions(+), 125 deletions(-) diff --git a/Core/GameEngine/Include/GameNetwork/networkutil.h b/Core/GameEngine/Include/GameNetwork/networkutil.h index 944e2ce0a60..722c59fd7ff 100644 --- a/Core/GameEngine/Include/GameNetwork/networkutil.h +++ b/Core/GameEngine/Include/GameNetwork/networkutil.h @@ -31,8 +31,8 @@ UnsignedInt AssembleIp(UnsignedByte a, UnsignedByte b, UnsignedByte c, UnsignedB UnsignedInt ResolveIP(AsciiString host); UnsignedShort GenerateNextCommandID(); Bool DoesCommandRequireACommandID(NetCommandType type); -Bool CommandRequiresAck(NetCommandMsg *msg); -Bool CommandRequiresDirectSend(NetCommandMsg *msg); +Bool CommandRequiresAck(const NetCommandMsg *msg); +Bool CommandRequiresDirectSend(const NetCommandMsg *msg); Bool IsCommandSynchronized(NetCommandType type); const char* GetNetCommandTypeAsString(NetCommandType type); diff --git a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp index 174599cc97c..77394c1e05f 100644 --- a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp @@ -119,70 +119,53 @@ UnsignedShort GenerateNextCommandID() { /** * Returns true if this type of command requires a unique command ID. */ -Bool DoesCommandRequireACommandID(NetCommandType type) { - if ((type == NETCOMMANDTYPE_GAMECOMMAND) || - (type == NETCOMMANDTYPE_FRAMEINFO) || - (type == NETCOMMANDTYPE_PLAYERLEAVE) || - (type == NETCOMMANDTYPE_DESTROYPLAYER) || - (type == NETCOMMANDTYPE_RUNAHEADMETRICS) || - (type == NETCOMMANDTYPE_RUNAHEAD) || - (type == NETCOMMANDTYPE_CHAT) || - (type == NETCOMMANDTYPE_DISCONNECTVOTE) || - (type == NETCOMMANDTYPE_LOADCOMPLETE) || - (type == NETCOMMANDTYPE_TIMEOUTSTART) || - (type == NETCOMMANDTYPE_WRAPPER) || - (type == NETCOMMANDTYPE_FILE) || - (type == NETCOMMANDTYPE_FILEANNOUNCE) || - (type == NETCOMMANDTYPE_FILEPROGRESS) || - (type == NETCOMMANDTYPE_DISCONNECTPLAYER) || - (type == NETCOMMANDTYPE_DISCONNECTFRAME) || - (type == NETCOMMANDTYPE_DISCONNECTSCREENOFF) || - (type == NETCOMMANDTYPE_FRAMERESENDREQUEST)) - { +Bool DoesCommandRequireACommandID(NetCommandType type) +{ + switch (type) { + case NETCOMMANDTYPE_FRAMEINFO: + case NETCOMMANDTYPE_GAMECOMMAND: + case NETCOMMANDTYPE_PLAYERLEAVE: + case NETCOMMANDTYPE_RUNAHEADMETRICS: + case NETCOMMANDTYPE_RUNAHEAD: + case NETCOMMANDTYPE_DESTROYPLAYER: + case NETCOMMANDTYPE_CHAT: + case NETCOMMANDTYPE_LOADCOMPLETE: + case NETCOMMANDTYPE_TIMEOUTSTART: + case NETCOMMANDTYPE_WRAPPER: + case NETCOMMANDTYPE_FILE: + case NETCOMMANDTYPE_FILEANNOUNCE: + case NETCOMMANDTYPE_FILEPROGRESS: + case NETCOMMANDTYPE_FRAMERESENDREQUEST: + case NETCOMMANDTYPE_DISCONNECTPLAYER: + case NETCOMMANDTYPE_DISCONNECTVOTE: + case NETCOMMANDTYPE_DISCONNECTFRAME: + case NETCOMMANDTYPE_DISCONNECTSCREENOFF: return TRUE; + default: + return FALSE; } - return FALSE; } /** * Returns true if this type of network command requires an ack. */ -Bool CommandRequiresAck(NetCommandMsg *msg) { - if ((msg->getNetCommandType() == NETCOMMANDTYPE_GAMECOMMAND) || - (msg->getNetCommandType() == NETCOMMANDTYPE_FRAMEINFO) || - (msg->getNetCommandType() == NETCOMMANDTYPE_PLAYERLEAVE) || - (msg->getNetCommandType() == NETCOMMANDTYPE_DESTROYPLAYER) || - (msg->getNetCommandType() == NETCOMMANDTYPE_RUNAHEADMETRICS) || - (msg->getNetCommandType() == NETCOMMANDTYPE_RUNAHEAD) || - (msg->getNetCommandType() == NETCOMMANDTYPE_CHAT) || - (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTVOTE) || - (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTPLAYER) || - (msg->getNetCommandType() == NETCOMMANDTYPE_LOADCOMPLETE) || - (msg->getNetCommandType() == NETCOMMANDTYPE_TIMEOUTSTART) || - (msg->getNetCommandType() == NETCOMMANDTYPE_WRAPPER) || - (msg->getNetCommandType() == NETCOMMANDTYPE_FILE) || - (msg->getNetCommandType() == NETCOMMANDTYPE_FILEANNOUNCE) || - (msg->getNetCommandType() == NETCOMMANDTYPE_FILEPROGRESS) || - (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTPLAYER) || - (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTFRAME) || - (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTSCREENOFF) || - (msg->getNetCommandType() == NETCOMMANDTYPE_FRAMERESENDREQUEST)) - { - return TRUE; - } - return FALSE; +Bool CommandRequiresAck(const NetCommandMsg* msg) +{ + return DoesCommandRequireACommandID(msg->getNetCommandType()); } -Bool IsCommandSynchronized(NetCommandType type) { - if ((type == NETCOMMANDTYPE_GAMECOMMAND) || - (type == NETCOMMANDTYPE_FRAMEINFO) || - (type == NETCOMMANDTYPE_PLAYERLEAVE) || - (type == NETCOMMANDTYPE_DESTROYPLAYER) || - (type == NETCOMMANDTYPE_RUNAHEAD)) - { +Bool IsCommandSynchronized(NetCommandType type) +{ + switch (type) { + case NETCOMMANDTYPE_FRAMEINFO: + case NETCOMMANDTYPE_GAMECOMMAND: + case NETCOMMANDTYPE_PLAYERLEAVE: + case NETCOMMANDTYPE_RUNAHEAD: + case NETCOMMANDTYPE_DESTROYPLAYER: return TRUE; + default: + return FALSE; } - return FALSE; } /** @@ -190,86 +173,66 @@ Bool IsCommandSynchronized(NetCommandType type) { * rather than going through the packet router. This should really only be used by commands * used on the disconnect screen. */ -Bool CommandRequiresDirectSend(NetCommandMsg *msg) { - if ((msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTVOTE) || - (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTPLAYER) || - (msg->getNetCommandType() == NETCOMMANDTYPE_LOADCOMPLETE) || - (msg->getNetCommandType() == NETCOMMANDTYPE_TIMEOUTSTART) || - (msg->getNetCommandType() == NETCOMMANDTYPE_FILE) || - (msg->getNetCommandType() == NETCOMMANDTYPE_FILEANNOUNCE) || - (msg->getNetCommandType() == NETCOMMANDTYPE_FILEPROGRESS) || - (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTFRAME) || - (msg->getNetCommandType() == NETCOMMANDTYPE_DISCONNECTSCREENOFF) || - (msg->getNetCommandType() == NETCOMMANDTYPE_FRAMERESENDREQUEST)) { - return TRUE; - } - return FALSE; -} - -const char* GetNetCommandTypeAsString(NetCommandType type) { - - switch (type) { - case NETCOMMANDTYPE_ACKBOTH: - return "NETCOMMANDTYPE_ACKBOTH"; - case NETCOMMANDTYPE_ACKSTAGE1: - return "NETCOMMANDTYPE_ACKSTAGE1"; - case NETCOMMANDTYPE_ACKSTAGE2: - return "NETCOMMANDTYPE_ACKSTAGE2"; - case NETCOMMANDTYPE_FRAMEINFO: - return "NETCOMMANDTYPE_FRAMEINFO"; - case NETCOMMANDTYPE_GAMECOMMAND: - return "NETCOMMANDTYPE_GAMECOMMAND"; - case NETCOMMANDTYPE_PLAYERLEAVE: - return "NETCOMMANDTYPE_PLAYERLEAVE"; - case NETCOMMANDTYPE_RUNAHEADMETRICS: - return "NETCOMMANDTYPE_RUNAHEADMETRICS"; - case NETCOMMANDTYPE_RUNAHEAD: - return "NETCOMMANDTYPE_RUNAHEAD"; - case NETCOMMANDTYPE_DESTROYPLAYER: - return "NETCOMMANDTYPE_DESTROYPLAYER"; - case NETCOMMANDTYPE_KEEPALIVE: - return "NETCOMMANDTYPE_KEEPALIVE"; - case NETCOMMANDTYPE_DISCONNECTCHAT: - return "NETCOMMANDTYPE_DISCONNECTCHAT"; - case NETCOMMANDTYPE_CHAT: - return "NETCOMMANDTYPE_CHAT"; - case NETCOMMANDTYPE_MANGLERQUERY: - return "NETCOMMANDTYPE_MANGLERQUERY"; - case NETCOMMANDTYPE_MANGLERRESPONSE: - return "NETCOMMANDTYPE_MANGLERRESPONSE"; - case NETCOMMANDTYPE_PROGRESS: - return "NETCOMMANDTYPE_PROGRESS"; +Bool CommandRequiresDirectSend(const NetCommandMsg* msg) +{ + switch (msg->getNetCommandType()) { case NETCOMMANDTYPE_LOADCOMPLETE: - return "NETCOMMANDTYPE_LOADCOMPLETE"; case NETCOMMANDTYPE_TIMEOUTSTART: - return "NETCOMMANDTYPE_TIMEOUTSTART"; - case NETCOMMANDTYPE_WRAPPER: - return "NETCOMMANDTYPE_WRAPPER"; case NETCOMMANDTYPE_FILE: - return "NETCOMMANDTYPE_FILE"; case NETCOMMANDTYPE_FILEANNOUNCE: - return "NETCOMMANDTYPE_FILEANNOUNCE"; case NETCOMMANDTYPE_FILEPROGRESS: - return "NETCOMMANDTYPE_FILEPROGRESS"; - case NETCOMMANDTYPE_DISCONNECTKEEPALIVE: - return "NETCOMMANDTYPE_DISCONNECTKEEPALIVE"; + case NETCOMMANDTYPE_FRAMERESENDREQUEST: case NETCOMMANDTYPE_DISCONNECTPLAYER: - return "NETCOMMANDTYPE_DISCONNECTPLAYER"; - case NETCOMMANDTYPE_PACKETROUTERQUERY: - return "NETCOMMANDTYPE_PACKETROUTERQUERY"; - case NETCOMMANDTYPE_PACKETROUTERACK: - return "NETCOMMANDTYPE_PACKETROUTERACK"; case NETCOMMANDTYPE_DISCONNECTVOTE: - return "NETCOMMANDTYPE_DISCONNECTVOTE"; case NETCOMMANDTYPE_DISCONNECTFRAME: - return "NETCOMMANDTYPE_DISCONNECTFRAME"; case NETCOMMANDTYPE_DISCONNECTSCREENOFF: - return "NETCOMMANDTYPE_DISCONNECTSCREENOFF"; - case NETCOMMANDTYPE_FRAMERESENDREQUEST: - return "NETCOMMANDTYPE_FRAMERESENDREQUEST"; + return TRUE; + default: + return FALSE; + } +} + +const char* GetNetCommandTypeAsString(NetCommandType type) +{ +#define CASE_LABEL(x) case x: return #x; + + switch (type) { + CASE_LABEL(NETCOMMANDTYPE_UNKNOWN) + CASE_LABEL(NETCOMMANDTYPE_ACKBOTH) + CASE_LABEL(NETCOMMANDTYPE_ACKSTAGE1) + CASE_LABEL(NETCOMMANDTYPE_ACKSTAGE2) + CASE_LABEL(NETCOMMANDTYPE_FRAMEINFO) + CASE_LABEL(NETCOMMANDTYPE_GAMECOMMAND) + CASE_LABEL(NETCOMMANDTYPE_PLAYERLEAVE) + CASE_LABEL(NETCOMMANDTYPE_RUNAHEADMETRICS) + CASE_LABEL(NETCOMMANDTYPE_RUNAHEAD) + CASE_LABEL(NETCOMMANDTYPE_DESTROYPLAYER) + CASE_LABEL(NETCOMMANDTYPE_KEEPALIVE) + CASE_LABEL(NETCOMMANDTYPE_DISCONNECTCHAT) + CASE_LABEL(NETCOMMANDTYPE_CHAT) + CASE_LABEL(NETCOMMANDTYPE_MANGLERQUERY) + CASE_LABEL(NETCOMMANDTYPE_MANGLERRESPONSE) + CASE_LABEL(NETCOMMANDTYPE_PROGRESS) + CASE_LABEL(NETCOMMANDTYPE_LOADCOMPLETE) + CASE_LABEL(NETCOMMANDTYPE_TIMEOUTSTART) + CASE_LABEL(NETCOMMANDTYPE_WRAPPER) + CASE_LABEL(NETCOMMANDTYPE_FILE) + CASE_LABEL(NETCOMMANDTYPE_FILEANNOUNCE) + CASE_LABEL(NETCOMMANDTYPE_FILEPROGRESS) + CASE_LABEL(NETCOMMANDTYPE_FRAMERESENDREQUEST) + CASE_LABEL(NETCOMMANDTYPE_DISCONNECTSTART) + CASE_LABEL(NETCOMMANDTYPE_DISCONNECTKEEPALIVE) + CASE_LABEL(NETCOMMANDTYPE_DISCONNECTPLAYER) + CASE_LABEL(NETCOMMANDTYPE_PACKETROUTERQUERY) + CASE_LABEL(NETCOMMANDTYPE_PACKETROUTERACK) + CASE_LABEL(NETCOMMANDTYPE_DISCONNECTVOTE) + CASE_LABEL(NETCOMMANDTYPE_DISCONNECTFRAME) + CASE_LABEL(NETCOMMANDTYPE_DISCONNECTSCREENOFF) + CASE_LABEL(NETCOMMANDTYPE_DISCONNECTEND) default: - DEBUG_CRASH(("Unknown NetCommandType in GetNetCommandTypeAsString")); - return "UNKNOWN"; + DEBUG_CRASH(("Unhandled NetCommandType in GetNetCommandTypeAsString")); + return ""; } +#undef CASE_LABEL } From c1bce4f5dcf13a3b9e4edd5bcda42b2d6ec64736 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Wed, 20 May 2026 13:54:29 +0200 Subject: [PATCH 10/20] perf(network): Replace extern global network variables with const ones (#2726) --- .../Include/GameNetwork/NetworkDefs.h | 38 ++++++++++--------- .../Source/GameNetwork/NetworkUtil.cpp | 8 ---- .../GameEngine/Source/Common/CommandLine.cpp | 12 ------ .../GameEngine/Source/Common/CommandLine.cpp | 12 ------ 4 files changed, 20 insertions(+), 50 deletions(-) diff --git a/Core/GameEngine/Include/GameNetwork/NetworkDefs.h b/Core/GameEngine/Include/GameNetwork/NetworkDefs.h index 8976cc0a040..274a5d6a346 100644 --- a/Core/GameEngine/Include/GameNetwork/NetworkDefs.h +++ b/Core/GameEngine/Include/GameNetwork/NetworkDefs.h @@ -27,19 +27,21 @@ #include "Lib/BaseType.h" #include "Common/MessageStream.h" -static const Int WOL_NAME_LEN = 64; +static constexpr const Int WOL_NAME_LEN = 64; /// Max number of commands per frame -static const Int MAX_COMMANDS = 256; +static constexpr const Int MAX_COMMANDS = 256; -extern Int MIN_LOGIC_FRAMES; -extern Int MAX_FRAMES_AHEAD; -extern Int MIN_RUNAHEAD; +// TheSuperHackers @tweak Mauller 26/08/2025 reduce the minimum runahead from 10 +// This lets network games run at latencies down to 133ms when the network conditions allow +static constexpr const Int MIN_LOGIC_FRAMES = 5; +static constexpr const Int MAX_FRAMES_AHEAD = 128; +static constexpr const Int MIN_RUNAHEAD = 4; // FRAME_DATA_LENGTH needs to be MAX_FRAMES_AHEAD+1 because a player on a different // computer can send commands for a frame that is one beyond twice the max runahead. -extern Int FRAME_DATA_LENGTH; -extern Int FRAMES_TO_KEEP; +static constexpr const Int FRAME_DATA_LENGTH = (MAX_FRAMES_AHEAD + 1) * 2; +static constexpr const Int FRAMES_TO_KEEP = (MAX_FRAMES_AHEAD / 2) + 1; // This is the connection numbering: 1-8 are for players enum ConnectionNumbers CPP_11(: Int) @@ -85,7 +87,7 @@ static constexpr const Int MAX_MESSAGES = 256; * Command packet - contains frame #, total # of commands, and each command. This is what gets sent * to each player every frame */ -static const Int numCommandsPerCommandPacket = (MAX_NETWORK_MESSAGE_LEN - sizeof(UnsignedInt) - sizeof(UnsignedShort))/sizeof(GameMessage); +static constexpr const Int numCommandsPerCommandPacket = (MAX_NETWORK_MESSAGE_LEN - sizeof(UnsignedInt) - sizeof(UnsignedShort))/sizeof(GameMessage); #pragma pack(push, 1) struct CommandPacket { @@ -193,38 +195,38 @@ enum PlayerLeaveCode CPP_11(: Int) { }; // Magic number for identifying a Generals packet. -static const UnsignedShort GENERALS_MAGIC_NUMBER = 0xF00D; +static constexpr const UnsignedShort GENERALS_MAGIC_NUMBER = 0xF00D; // The number of fps history entries. -//static const Int NETWORK_FPS_HISTORY_LENGTH = 30; +//static constexpr const Int NETWORK_FPS_HISTORY_LENGTH = 30; // The number of ping history entries. -//static const Int NETWORK_LATENCY_HISTORY_LENGTH = 200; +//static constexpr const Int NETWORK_LATENCY_HISTORY_LENGTH = 200; // The number of miliseconds between run ahead metrics things -//static const Int NETWORK_RUN_AHEAD_METRICS_TIME = 5000; +//static constexpr const Int NETWORK_RUN_AHEAD_METRICS_TIME = 5000; // The number of cushion values to keep. -//static const Int NETWORK_CUSHION_HISTORY_LENGTH = 10; +//static constexpr const Int NETWORK_CUSHION_HISTORY_LENGTH = 10; // The amount of slack in the run ahead value. This is the percentage of the calculated run ahead that is added. -//static const Int NETWORK_RUN_AHEAD_SLACK = 20; +//static constexpr const Int NETWORK_RUN_AHEAD_SLACK = 20; // The number of seconds between when the connections to each player send a keep-alive packet. // This should be less than 30 just to keep firewall ports open. -//static const Int NETWORK_KEEPALIVE_DELAY = 20; +//static constexpr const Int NETWORK_KEEPALIVE_DELAY = 20; // The number of milliseconds between when the game gets stuck on a frame for a network stall and // and when the disconnect dialog comes up. -//static const Int NETWORK_DISCONNECT_TIME = 5000; +//static constexpr const Int NETWORK_DISCONNECT_TIME = 5000; // The number of miliseconds between when a player's last disconnect keep alive command // was received and when they are considered disconnected from the game. -//static const Int NETWORK_PLAYER_TIMEOUT_TIME = 60000; +//static constexpr const Int NETWORK_PLAYER_TIMEOUT_TIME = 60000; // The base port number used for the transport socket. A players slot number is added to this // value to get their actual port number. -static const Int NETWORK_BASE_PORT_NUMBER = 8088; +static constexpr const Int NETWORK_BASE_PORT_NUMBER = 8088; // the singleton class NetworkInterface; diff --git a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp index 77394c1e05f..d48280759b6 100644 --- a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp @@ -27,14 +27,6 @@ #include "GameNetwork/networkutil.h" -// TheSuperHackers @tweak Mauller 26/08/2025 reduce the minimum runahead from 10 -// This lets network games run at latencies down to 133ms when the network conditions allow -Int MIN_LOGIC_FRAMES = 5; -Int MAX_FRAMES_AHEAD = 128; -Int MIN_RUNAHEAD = 4; -Int FRAME_DATA_LENGTH = (MAX_FRAMES_AHEAD+1)*2; -Int FRAMES_TO_KEEP = (MAX_FRAMES_AHEAD/2) + 1; - #ifdef DEBUG_LOGGING void dumpBufferToLog(const void *vBuf, Int len, const char *fname, Int line) diff --git a/Generals/Code/GameEngine/Source/Common/CommandLine.cpp b/Generals/Code/GameEngine/Source/Common/CommandLine.cpp index cd2284943a5..543576d78fc 100644 --- a/Generals/Code/GameEngine/Source/Common/CommandLine.cpp +++ b/Generals/Code/GameEngine/Source/Common/CommandLine.cpp @@ -868,17 +868,6 @@ Int parseSelectAll( char *args[], int num ) return 1; } - -Int parseRunAhead( char *args[], Int num ) -{ - if (num > 2) - { - MIN_RUNAHEAD = atoi(args[1]); - MAX_FRAMES_AHEAD = atoi(args[2]); - FRAME_DATA_LENGTH = (MAX_FRAMES_AHEAD + 1)*2; - } - return 3; -} #endif @@ -1279,7 +1268,6 @@ static CommandLineParam paramsForEngineInit[] = { "-logToCon", parseLogToConsole }, { "-vTune", parseVTune }, { "-selectTheUnselectable", parseSelectAll }, - { "-RunAhead", parseRunAhead }, #if ENABLE_CONFIGURABLE_SHROUD { "-noshroud", parseNoShroud }, #endif diff --git a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp index cd2284943a5..543576d78fc 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp @@ -868,17 +868,6 @@ Int parseSelectAll( char *args[], int num ) return 1; } - -Int parseRunAhead( char *args[], Int num ) -{ - if (num > 2) - { - MIN_RUNAHEAD = atoi(args[1]); - MAX_FRAMES_AHEAD = atoi(args[2]); - FRAME_DATA_LENGTH = (MAX_FRAMES_AHEAD + 1)*2; - } - return 3; -} #endif @@ -1279,7 +1268,6 @@ static CommandLineParam paramsForEngineInit[] = { "-logToCon", parseLogToConsole }, { "-vTune", parseVTune }, { "-selectTheUnselectable", parseSelectAll }, - { "-RunAhead", parseRunAhead }, #if ENABLE_CONFIGURABLE_SHROUD { "-noshroud", parseNoShroud }, #endif From 5852010ea3e71553246173e6e14b7ae1da78be8b Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Wed, 20 May 2026 14:55:52 +0200 Subject: [PATCH 11/20] bugfix(aigroup): Prevent game crash when a player is selected in Replay playback (2) (#2711) --- .../Source/GameLogic/System/GameLogicDispatch.cpp | 7 +++---- Generals/Code/GameEngine/Include/GameLogic/AI.h | 1 + Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp | 5 +++++ GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h | 1 + GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp | 5 +++++ 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index c002ff8f239..fa9d2292f7e 100644 --- a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -2120,10 +2120,9 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) } #if RETAIL_COMPATIBLE_AIGROUP - // TheSuperHackers @bugfix xezon 28/06/2025 This hack avoids crashing when players are selected during Replay playback. - // It can read data from an already deleted AIGroup and return this function when its member size is 0, signifying that - // it is indeed deleted. - if (currentlySelectedGroup && currentlySelectedGroup->getCount() == 0) + // TheSuperHackers @bugfix xezon/Caball009 14/05/2026 This fix avoids crashing when players are selected during Replay playback. + // The current AI group may have been destroyed, and its memory deallocated, in which case it shouldn't be used. + if (currentlySelectedGroup && !TheAI->doesGroupExist(currentlySelectedGroup)) return; #endif diff --git a/Generals/Code/GameEngine/Include/GameLogic/AI.h b/Generals/Code/GameEngine/Include/GameLogic/AI.h index ff4dc881042..ec72bc9f6d4 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/AI.h +++ b/Generals/Code/GameEngine/Include/GameLogic/AI.h @@ -273,6 +273,7 @@ class AI : public SubsystemInterface, public Snapshot AIGroupPtr createGroup(); ///< instantiate a new AI Group void destroyGroup( AIGroup *group ); ///< destroy the given AI Group AIGroup *findGroup( UnsignedInt id ); ///< return the AI Group with the given ID + Bool doesGroupExist(AIGroup* group) const; ///< return whether the given AI Group exists, i.e. is part of the group list // Formation info enum FormationID getNextFormationID(); diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp index fb576b640fd..f3b2f362db5 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp @@ -494,6 +494,11 @@ AIGroup *AI::findGroup( UnsignedInt id ) return nullptr; } +Bool AI::doesGroupExist(AIGroup* group) const +{ + return std::find(m_groupList.begin(), m_groupList.end(), group) != m_groupList.end(); +} + //-------------------------------------------------------------------------------------------------------- /** * Get the next formation id. diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h index 90c3dd32785..3b46117adee 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h @@ -279,6 +279,7 @@ class AI : public SubsystemInterface, public Snapshot AIGroupPtr createGroup(); ///< instantiate a new AI Group void destroyGroup( AIGroup *group ); ///< destroy the given AI Group AIGroup *findGroup( UnsignedInt id ); ///< return the AI Group with the given ID + Bool doesGroupExist(AIGroup* group) const; ///< return whether the given AI Group exists, i.e. is part of the group list // Formation info enum FormationID getNextFormationID(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp index 9959cb42546..ff0d155d9f2 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp @@ -497,6 +497,11 @@ AIGroup *AI::findGroup( UnsignedInt id ) return nullptr; } +Bool AI::doesGroupExist(AIGroup* group) const +{ + return std::find(m_groupList.begin(), m_groupList.end(), group) != m_groupList.end(); +} + //-------------------------------------------------------------------------------------------------------- /** * Get the next formation id. From 842c7a53261c8e14772cd94ea678f6cc446637d8 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Wed, 20 May 2026 19:59:48 +0200 Subject: [PATCH 12/20] fix(recorder): Fix memory leak of RecorderClass::m_crcInfo (#2713) --- .../Code/GameEngine/Include/Common/Recorder.h | 36 ++++- .../GameEngine/Source/Common/Recorder.cpp | 139 +++++++----------- .../Code/GameEngine/Include/Common/Recorder.h | 36 ++++- .../GameEngine/Source/Common/Recorder.cpp | 139 +++++++----------- 4 files changed, 168 insertions(+), 182 deletions(-) diff --git a/Generals/Code/GameEngine/Include/Common/Recorder.h b/Generals/Code/GameEngine/Include/Common/Recorder.h index 47a1a3429e0..cb96ac8ccba 100644 --- a/Generals/Code/GameEngine/Include/Common/Recorder.h +++ b/Generals/Code/GameEngine/Include/Common/Recorder.h @@ -53,13 +53,39 @@ enum RecorderModeType CPP_11(: Int) { RECORDERMODETYPE_NONE // this is a valid state to be in on the shell map, or in saved games }; -class CRCInfo; +class RecorderClass : public SubsystemInterface +{ +protected: + // TheSuperHackers @info helmutbuhler 03/04/2025 CRC overview: + // Each peer periodically computes a CRC from its local game state and broadcasts it to all peers, including itself, + // to verify synchronization. CRC messages are received a few frames later in network games to avoid stalling every + // frame while waiting for all peers. This works because all peers compare the same received CRCs on the same frame. + // + // Replays are different: recorded CRC messages appear on the frame they were originally received, so directly + // comparing them against the current local state would mismatch. To handle this, local CRCs must be queued until the + // corresponding replay CRC messages arrive. This class implements that queue. + class CRCInfo + { + public: + CRCInfo(); + CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer); + void addCRC(UnsignedInt val); + UnsignedInt readCRC(); + int GetQueueSize() const { return m_data.size(); } + UnsignedInt getLocalPlayer() const { return m_localPlayer; } + void setSawCRCMismatch() { m_sawCRCMismatch = TRUE; } + Bool sawCRCMismatch() const { return m_sawCRCMismatch; } + + protected: + Bool m_sawCRCMismatch; + Bool m_skippedOne; + UnsignedInt m_localPlayer; + std::list m_data; + }; -class RecorderClass : public SubsystemInterface { public: struct ReplayHeader; -public: RecorderClass(); ///< Constructor. virtual ~RecorderClass() override; ///< Destructor. @@ -86,9 +112,6 @@ class RecorderClass : public SubsystemInterface { public: void handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback); -protected: - CRCInfo *m_crcInfo; -public: // read in info relating to a replay, conditionally setting up m_file for playback struct ReplayHeader @@ -158,6 +181,7 @@ class RecorderClass : public SubsystemInterface { CullBadCommandsResult cullBadCommands(); ///< prevent the user from giving mouse commands that he shouldn't be able to do during playback. + CRCInfo m_crcInfo; File* m_file; AsciiString m_fileName; Int m_currentFilePosition; diff --git a/Generals/Code/GameEngine/Source/Common/Recorder.cpp b/Generals/Code/GameEngine/Source/Common/Recorder.cpp index 1e4fdd83238..f399d9f68aa 100644 --- a/Generals/Code/GameEngine/Source/Common/Recorder.cpp +++ b/Generals/Code/GameEngine/Source/Common/Recorder.cpp @@ -100,6 +100,50 @@ static FILE* openStatsLogFile() } #endif +RecorderClass::CRCInfo::CRCInfo() : + m_sawCRCMismatch(FALSE), + m_skippedOne(FALSE), + m_localPlayer(0) +{} + +RecorderClass::CRCInfo::CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer) +{ + m_sawCRCMismatch = FALSE; + m_skippedOne = !isMultiplayer; + m_localPlayer = localPlayer; +} + +void RecorderClass::CRCInfo::addCRC(UnsignedInt val) +{ + // TheSuperHackers @fix helmutbuhler 03/04/2025 + // In Multiplayer, the first MSG_LOGIC_CRC message somehow doesn't make it through the network. + // Perhaps this happens because the network is not yet set up on frame 0. + // So we also don't queue up the first local crc message, otherwise the crc + // messages wouldn't match up anymore and we'd desync immediately during playback. + if (!m_skippedOne) + { + m_skippedOne = TRUE; + return; + } + + m_data.push_back(val); + //DEBUG_LOG(("CRCInfo::addCRC() - crc %8.8X pushes list to %d entries (full=%d)", val, m_data.size(), !m_data.empty())); +} + +UnsignedInt RecorderClass::CRCInfo::readCRC() +{ + if (m_data.empty()) + { + DEBUG_LOG(("CRCInfo::readCRC() - bailing, full=0, size=%d", m_data.size())); + return 0; + } + + UnsignedInt val = m_data.front(); + m_data.pop_front(); + //DEBUG_LOG(("CRCInfo::readCRC() - returning %8.8X, full=%d, size=%d", val, !m_data.empty(), m_data.size())); + return val; +} + void RecorderClass::logGameStart(AsciiString options) { if (!m_file) @@ -933,84 +977,9 @@ AsciiString RecorderClass::getCurrentReplayFilename() return AsciiString::TheEmptyString; } -// TheSuperHackers @info helmutbuhler 03/04/2025 -// Some info about CRC: -// In each game, each peer periodically calculates a CRC from the local gamestate and sends that -// in a message to all peers (including itself) so that everyone can check that the crc is synchronous. -// In a network game, there is a delay between sending the CRC message and receiving it. This is -// necessary because if you were to wait each frame for all messages from all peers, things would go -// horribly slow. -// But this delay is not a problem for CRC checking because everyone receives the CRC in the same frame -// and every peer just makes sure all the received CRCs are equal. -// While playing replays, this is a problem however: The CRC messages in the replays appear on the frame -// they were received, which can be a few frames delayed if it was a network game. And if we were to -// compare those with the local gamestate, they wouldn't sync up. -// So, in order to fix this, we need to queue up our local CRCs, -// so that we can check it with the crc messages that come later. -// This class is basically that queue. -class CRCInfo -{ -public: - CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer); - void addCRC(UnsignedInt val); - UnsignedInt readCRC(); - - int GetQueueSize() const { return m_data.size(); } - - UnsignedInt getLocalPlayer() { return m_localPlayer; } - - void setSawCRCMismatch() { m_sawCRCMismatch = TRUE; } - Bool sawCRCMismatch() const { return m_sawCRCMismatch; } - -protected: - - Bool m_sawCRCMismatch; - Bool m_skippedOne; - std::list m_data; - UnsignedInt m_localPlayer; -}; - -CRCInfo::CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer) -{ - m_localPlayer = localPlayer; - m_skippedOne = !isMultiplayer; - m_sawCRCMismatch = FALSE; -} - -void CRCInfo::addCRC(UnsignedInt val) -{ - // TheSuperHackers @fix helmutbuhler 03/04/2025 - // In Multiplayer, the first MSG_LOGIC_CRC message somehow doesn't make it through the network. - // Perhaps this happens because the network is not yet set up on frame 0. - // So we also don't queue up the first local crc message, otherwise the crc - // messages wouldn't match up anymore and we'd desync immediately during playback. - if (!m_skippedOne) - { - m_skippedOne = TRUE; - return; - } - - m_data.push_back(val); - //DEBUG_LOG(("CRCInfo::addCRC() - crc %8.8X pushes list to %d entries (full=%d)", val, m_data.size(), !m_data.empty())); -} - -UnsignedInt CRCInfo::readCRC() -{ - if (m_data.empty()) - { - DEBUG_LOG(("CRCInfo::readCRC() - bailing, full=0, size=%d", m_data.size())); - return 0; - } - - UnsignedInt val = m_data.front(); - m_data.pop_front(); - //DEBUG_LOG(("CRCInfo::readCRC() - returning %8.8X, full=%d, size=%d", val, !m_data.empty(), m_data.size())); - return val; -} - Bool RecorderClass::sawCRCMismatch() const { - return m_crcInfo->sawCRCMismatch(); + return m_crcInfo.sawCRCMismatch(); } void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback) @@ -1018,11 +987,11 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f if (fromPlayback) { //DEBUG_LOG(("RecorderClass::handleCRCMessage() - Adding CRC of %X from %d to m_crcInfo", newCRC, playerIndex)); - m_crcInfo->addCRC(newCRC); + m_crcInfo.addCRC(newCRC); return; } - Int localPlayerIndex = m_crcInfo->getLocalPlayer(); + Int localPlayerIndex = m_crcInfo.getLocalPlayer(); Bool samePlayer = FALSE; AsciiString playerName; playerName.format("player%d", localPlayerIndex); @@ -1031,10 +1000,10 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f samePlayer = TRUE; if (samePlayer || (localPlayerIndex < 0)) { - UnsignedInt playbackCRC = m_crcInfo->readCRC(); + UnsignedInt playbackCRC = m_crcInfo.readCRC(); //DEBUG_LOG(("RecorderClass::handleCRCMessage() - Comparing CRCs of InGame:%8.8X Replay:%8.8X Frame:%d from Player %d", - // playbackCRC, newCRC, TheGameLogic->getFrame()-m_crcInfo->GetQueueSize()-1, playerIndex)); - if (TheGameLogic->getFrame() > 0 && newCRC != playbackCRC && !m_crcInfo->sawCRCMismatch()) + // playbackCRC, newCRC, TheGameLogic->getFrame()-m_crcInfo.GetQueueSize()-1, playerIndex)); + if (TheGameLogic->getFrame() > 0 && newCRC != playbackCRC && !m_crcInfo.sawCRCMismatch()) { // Since we don't seem to have any *visible* desyncs when replaying games, but get this warning // virtually every replay, the assumption is our CRC checking is faulty. Since we're at the @@ -1048,7 +1017,7 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f // TheSuperHackers @info helmutbuhler 03/04/2025 // Note: We subtract the queue size from the frame number. This way we calculate the correct frame // the mismatch first happened in case the NetCRCInterval is set to 1 during the game. - const UnsignedInt mismatchFrame = TheGameLogic->getFrame() - m_crcInfo->GetQueueSize() - 1; + const UnsignedInt mismatchFrame = TheGameLogic->getFrame() - m_crcInfo.GetQueueSize() - 1; // Now also prints a UI message for it. const UnicodeString mismatchDetailsStr = TheGameText->FETCH_OR_SUBSTITUTE("GUI:CRCMismatchDetails", L"InGame:%8.8X Replay:%8.8X Frame:%d"); @@ -1070,7 +1039,7 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f TheGameLogic->setGamePaused(pause, pauseMusic, pauseInput); // Mark this mismatch as seen when we had the chance to pause once. - m_crcInfo->setSawCRCMismatch(); + m_crcInfo.setSawCRCMismatch(); } } return; @@ -1192,9 +1161,9 @@ Bool RecorderClass::playbackFile(AsciiString filename) #endif Bool isMultiplayer = m_gameInfo.getSlot(header.localPlayerIndex)->getIP() != 0; - m_crcInfo = NEW CRCInfo(header.localPlayerIndex, isMultiplayer); + m_crcInfo = CRCInfo(header.localPlayerIndex, isMultiplayer); REPLAY_CRC_INTERVAL = m_gameInfo.getCRCInterval(); - DEBUG_LOG(("Player index is %d, replay CRC interval is %d", m_crcInfo->getLocalPlayer(), REPLAY_CRC_INTERVAL)); + DEBUG_LOG(("Player index is %d, replay CRC interval is %d", m_crcInfo.getLocalPlayer(), REPLAY_CRC_INTERVAL)); Int difficulty = 0; m_file->read(&difficulty, sizeof(difficulty)); diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h index 142a04ececd..fba73ad5ec3 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h @@ -53,13 +53,39 @@ enum RecorderModeType CPP_11(: Int) { RECORDERMODETYPE_NONE // this is a valid state to be in on the shell map, or in saved games }; -class CRCInfo; +class RecorderClass : public SubsystemInterface +{ +protected: + // TheSuperHackers @info helmutbuhler 03/04/2025 CRC overview: + // Each peer periodically computes a CRC from its local game state and broadcasts it to all peers, including itself, + // to verify synchronization. CRC messages are received a few frames later in network games to avoid stalling every + // frame while waiting for all peers. This works because all peers compare the same received CRCs on the same frame. + // + // Replays are different: recorded CRC messages appear on the frame they were originally received, so directly + // comparing them against the current local state would mismatch. To handle this, local CRCs must be queued until the + // corresponding replay CRC messages arrive. This class implements that queue. + class CRCInfo + { + public: + CRCInfo(); + CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer); + void addCRC(UnsignedInt val); + UnsignedInt readCRC(); + int GetQueueSize() const { return m_data.size(); } + UnsignedInt getLocalPlayer() const { return m_localPlayer; } + void setSawCRCMismatch() { m_sawCRCMismatch = TRUE; } + Bool sawCRCMismatch() const { return m_sawCRCMismatch; } + + protected: + Bool m_sawCRCMismatch; + Bool m_skippedOne; + UnsignedInt m_localPlayer; + std::list m_data; + }; -class RecorderClass : public SubsystemInterface { public: struct ReplayHeader; -public: RecorderClass(); ///< Constructor. virtual ~RecorderClass() override; ///< Destructor. @@ -86,9 +112,6 @@ class RecorderClass : public SubsystemInterface { public: void handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback); -protected: - CRCInfo *m_crcInfo; -public: // read in info relating to a replay, conditionally setting up m_file for playback struct ReplayHeader @@ -158,6 +181,7 @@ class RecorderClass : public SubsystemInterface { CullBadCommandsResult cullBadCommands(); ///< prevent the user from giving mouse commands that he shouldn't be able to do during playback. + CRCInfo m_crcInfo; File* m_file; AsciiString m_fileName; Int m_currentFilePosition; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp index 7e38c456b89..ad1783583b2 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp @@ -100,6 +100,50 @@ static FILE* openStatsLogFile() } #endif +RecorderClass::CRCInfo::CRCInfo() : + m_sawCRCMismatch(FALSE), + m_skippedOne(FALSE), + m_localPlayer(0) +{} + +RecorderClass::CRCInfo::CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer) +{ + m_sawCRCMismatch = FALSE; + m_skippedOne = !isMultiplayer; + m_localPlayer = localPlayer; +} + +void RecorderClass::CRCInfo::addCRC(UnsignedInt val) +{ + // TheSuperHackers @fix helmutbuhler 03/04/2025 + // In Multiplayer, the first MSG_LOGIC_CRC message somehow doesn't make it through the network. + // Perhaps this happens because the network is not yet set up on frame 0. + // So we also don't queue up the first local crc message, otherwise the crc + // messages wouldn't match up anymore and we'd desync immediately during playback. + if (!m_skippedOne) + { + m_skippedOne = TRUE; + return; + } + + m_data.push_back(val); + //DEBUG_LOG(("CRCInfo::addCRC() - crc %8.8X pushes list to %d entries (full=%d)", val, m_data.size(), !m_data.empty())); +} + +UnsignedInt RecorderClass::CRCInfo::readCRC() +{ + if (m_data.empty()) + { + DEBUG_LOG(("CRCInfo::readCRC() - bailing, full=0, size=%d", m_data.size())); + return 0; + } + + UnsignedInt val = m_data.front(); + m_data.pop_front(); + //DEBUG_LOG(("CRCInfo::readCRC() - returning %8.8X, full=%d, size=%d", val, !m_data.empty(), m_data.size())); + return val; +} + void RecorderClass::logGameStart(AsciiString options) { if (!m_file) @@ -935,84 +979,9 @@ AsciiString RecorderClass::getCurrentReplayFilename() return AsciiString::TheEmptyString; } -// TheSuperHackers @info helmutbuhler 03/04/2025 -// Some info about CRC: -// In each game, each peer periodically calculates a CRC from the local gamestate and sends that -// in a message to all peers (including itself) so that everyone can check that the crc is synchronous. -// In a network game, there is a delay between sending the CRC message and receiving it. This is -// necessary because if you were to wait each frame for all messages from all peers, things would go -// horribly slow. -// But this delay is not a problem for CRC checking because everyone receives the CRC in the same frame -// and every peer just makes sure all the received CRCs are equal. -// While playing replays, this is a problem however: The CRC messages in the replays appear on the frame -// they were received, which can be a few frames delayed if it was a network game. And if we were to -// compare those with the local gamestate, they wouldn't sync up. -// So, in order to fix this, we need to queue up our local CRCs, -// so that we can check it with the crc messages that come later. -// This class is basically that queue. -class CRCInfo -{ -public: - CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer); - void addCRC(UnsignedInt val); - UnsignedInt readCRC(); - - int GetQueueSize() const { return m_data.size(); } - - UnsignedInt getLocalPlayer() { return m_localPlayer; } - - void setSawCRCMismatch() { m_sawCRCMismatch = TRUE; } - Bool sawCRCMismatch() const { return m_sawCRCMismatch; } - -protected: - - Bool m_sawCRCMismatch; - Bool m_skippedOne; - std::list m_data; - UnsignedInt m_localPlayer; -}; - -CRCInfo::CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer) -{ - m_localPlayer = localPlayer; - m_skippedOne = !isMultiplayer; - m_sawCRCMismatch = FALSE; -} - -void CRCInfo::addCRC(UnsignedInt val) -{ - // TheSuperHackers @fix helmutbuhler 03/04/2025 - // In Multiplayer, the first MSG_LOGIC_CRC message somehow doesn't make it through the network. - // Perhaps this happens because the network is not yet set up on frame 0. - // So we also don't queue up the first local crc message, otherwise the crc - // messages wouldn't match up anymore and we'd desync immediately during playback. - if (!m_skippedOne) - { - m_skippedOne = TRUE; - return; - } - - m_data.push_back(val); - //DEBUG_LOG(("CRCInfo::addCRC() - crc %8.8X pushes list to %d entries (full=%d)", val, m_data.size(), !m_data.empty())); -} - -UnsignedInt CRCInfo::readCRC() -{ - if (m_data.empty()) - { - DEBUG_LOG(("CRCInfo::readCRC() - bailing, full=0, size=%d", m_data.size())); - return 0; - } - - UnsignedInt val = m_data.front(); - m_data.pop_front(); - //DEBUG_LOG(("CRCInfo::readCRC() - returning %8.8X, full=%d, size=%d", val, !m_data.empty(), m_data.size())); - return val; -} - Bool RecorderClass::sawCRCMismatch() const { - return m_crcInfo->sawCRCMismatch(); + return m_crcInfo.sawCRCMismatch(); } void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback) @@ -1020,11 +989,11 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f if (fromPlayback) { //DEBUG_LOG(("RecorderClass::handleCRCMessage() - Adding CRC of %X from %d to m_crcInfo", newCRC, playerIndex)); - m_crcInfo->addCRC(newCRC); + m_crcInfo.addCRC(newCRC); return; } - Int localPlayerIndex = m_crcInfo->getLocalPlayer(); + Int localPlayerIndex = m_crcInfo.getLocalPlayer(); Bool samePlayer = FALSE; AsciiString playerName; playerName.format("player%d", localPlayerIndex); @@ -1033,10 +1002,10 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f samePlayer = TRUE; if (samePlayer || (localPlayerIndex < 0)) { - UnsignedInt playbackCRC = m_crcInfo->readCRC(); + UnsignedInt playbackCRC = m_crcInfo.readCRC(); //DEBUG_LOG(("RecorderClass::handleCRCMessage() - Comparing CRCs of InGame:%8.8X Replay:%8.8X Frame:%d from Player %d", - // playbackCRC, newCRC, TheGameLogic->getFrame()-m_crcInfo->GetQueueSize()-1, playerIndex)); - if (TheGameLogic->getFrame() > 0 && newCRC != playbackCRC && !m_crcInfo->sawCRCMismatch()) + // playbackCRC, newCRC, TheGameLogic->getFrame()-m_crcInfo.GetQueueSize()-1, playerIndex)); + if (TheGameLogic->getFrame() > 0 && newCRC != playbackCRC && !m_crcInfo.sawCRCMismatch()) { //Kris: Patch 1.01 November 10, 2003 (integrated changes from Matt Campbell) // Since we don't seem to have any *visible* desyncs when replaying games, but get this warning @@ -1051,7 +1020,7 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f // TheSuperHackers @info helmutbuhler 03/04/2025 // Note: We subtract the queue size from the frame number. This way we calculate the correct frame // the mismatch first happened in case the NetCRCInterval is set to 1 during the game. - const UnsignedInt mismatchFrame = TheGameLogic->getFrame() - m_crcInfo->GetQueueSize() - 1; + const UnsignedInt mismatchFrame = TheGameLogic->getFrame() - m_crcInfo.GetQueueSize() - 1; // Now also prints a UI message for it. const UnicodeString mismatchDetailsStr = TheGameText->FETCH_OR_SUBSTITUTE("GUI:CRCMismatchDetails", L"InGame:%8.8X Replay:%8.8X Frame:%d"); @@ -1073,7 +1042,7 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f TheGameLogic->setGamePaused(pause, pauseMusic, pauseInput); // Mark this mismatch as seen when we had the chance to pause once. - m_crcInfo->setSawCRCMismatch(); + m_crcInfo.setSawCRCMismatch(); } } return; @@ -1195,9 +1164,9 @@ Bool RecorderClass::playbackFile(AsciiString filename) #endif Bool isMultiplayer = m_gameInfo.getSlot(header.localPlayerIndex)->getIP() != 0; - m_crcInfo = NEW CRCInfo(header.localPlayerIndex, isMultiplayer); + m_crcInfo = CRCInfo(header.localPlayerIndex, isMultiplayer); REPLAY_CRC_INTERVAL = m_gameInfo.getCRCInterval(); - DEBUG_LOG(("Player index is %d, replay CRC interval is %d", m_crcInfo->getLocalPlayer(), REPLAY_CRC_INTERVAL)); + DEBUG_LOG(("Player index is %d, replay CRC interval is %d", m_crcInfo.getLocalPlayer(), REPLAY_CRC_INTERVAL)); Int difficulty = 0; m_file->read(&difficulty, sizeof(difficulty)); From bf8d5be0294fc71b601444ff9446c12f4720022f Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Thu, 21 May 2026 21:13:20 +0200 Subject: [PATCH 13/20] bugfix(gameengine): Fix logic in GameEngine::canUpdateRegularGameLogic() (#2707) --- .../Code/GameEngine/Include/Common/GameEngine.h | 4 ++-- .../Code/GameEngine/Source/Common/GameEngine.cpp | 15 +++++++-------- .../Code/GameEngine/Include/Common/GameEngine.h | 4 ++-- .../Code/GameEngine/Source/Common/GameEngine.cpp | 15 +++++++-------- 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/Generals/Code/GameEngine/Include/Common/GameEngine.h b/Generals/Code/GameEngine/Include/Common/GameEngine.h index 67a45eb5f2e..8dfb25151ac 100644 --- a/Generals/Code/GameEngine/Include/Common/GameEngine.h +++ b/Generals/Code/GameEngine/Include/Common/GameEngine.h @@ -80,9 +80,9 @@ class GameEngine : public SubsystemInterface virtual void resetSubsystems(); - Bool canUpdateGameLogic(); + Bool canUpdateGameLogic(UnsignedInt logicTimeQueryFlags); Bool canUpdateNetworkGameLogic(); - Bool canUpdateRegularGameLogic(); + Bool canUpdateRegularGameLogic(UnsignedInt logicTimeQueryFlags); virtual FileSystem *createFileSystem(); ///< Factory for FileSystem classes virtual LocalFileSystem *createLocalFileSystem() = 0; ///< Factory for LocalFileSystem classes diff --git a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp index 0350d007b23..135766458f4 100644 --- a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp @@ -649,7 +649,7 @@ void GameEngine::resetSubsystems() } /// ----------------------------------------------------------------------------------------------- -Bool GameEngine::canUpdateGameLogic() +Bool GameEngine::canUpdateGameLogic(UnsignedInt logicTimeQueryFlags) { // Must be first. TheGameLogic->preUpdate(); @@ -663,7 +663,7 @@ Bool GameEngine::canUpdateGameLogic() } else { - return canUpdateRegularGameLogic(); + return canUpdateRegularGameLogic(logicTimeQueryFlags); } } @@ -684,11 +684,10 @@ Bool GameEngine::canUpdateNetworkGameLogic() } /// ----------------------------------------------------------------------------------------------- -Bool GameEngine::canUpdateRegularGameLogic() +Bool GameEngine::canUpdateRegularGameLogic(UnsignedInt logicTimeQueryFlags) { - const Bool enabled = TheFramePacer->isLogicTimeScaleEnabled(); - const Int logicTimeScaleFps = TheFramePacer->getLogicTimeScaleFps(); - const Int maxRenderFps = TheFramePacer->getFramesPerSecondLimit(); + const Int logicTimeScaleFps = TheFramePacer->getActualLogicTimeScaleFps(logicTimeQueryFlags); + const Int maxRenderFps = TheFramePacer->getActualFramesPerSecondLimit(); #if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE) const Bool useFastMode = TheGlobalData->m_TiVOFastMode; @@ -696,7 +695,7 @@ Bool GameEngine::canUpdateRegularGameLogic() const Bool useFastMode = TheGlobalData->m_TiVOFastMode && TheGameLogic->isInReplayGame(); #endif - if (useFastMode || !enabled || logicTimeScaleFps >= maxRenderFps) + if (useFastMode || logicTimeScaleFps >= maxRenderFps) { // Logic time scale is uncapped or larger equal Render FPS. Update straight away. return true; @@ -746,7 +745,7 @@ void GameEngine::update() } } - const Bool canUpdate = canUpdateGameLogic(); + const Bool canUpdate = canUpdateGameLogic(FramePacer::IgnoreFrozenTime | FramePacer::IgnoreHaltedGame); const Bool canUpdateLogic = canUpdate && !TheFramePacer->isGameHalted() && !TheFramePacer->isTimeFrozen(); const Bool canUpdateScript = canUpdate && !TheFramePacer->isGameHalted(); diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h b/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h index 20e7188b3e6..bcf91c3643f 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h @@ -79,9 +79,9 @@ class GameEngine : public SubsystemInterface virtual void resetSubsystems(); - Bool canUpdateGameLogic(); + Bool canUpdateGameLogic(UnsignedInt logicTimeQueryFlags); Bool canUpdateNetworkGameLogic(); - Bool canUpdateRegularGameLogic(); + Bool canUpdateRegularGameLogic(UnsignedInt logicTimeQueryFlags); virtual FileSystem *createFileSystem(); ///< Factory for FileSystem classes virtual LocalFileSystem *createLocalFileSystem() = 0; ///< Factory for LocalFileSystem classes diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp index ad861da8d90..92743bf0f55 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp @@ -813,7 +813,7 @@ void GameEngine::resetSubsystems() } /// ----------------------------------------------------------------------------------------------- -Bool GameEngine::canUpdateGameLogic() +Bool GameEngine::canUpdateGameLogic(UnsignedInt logicTimeQueryFlags) { // Must be first. TheGameLogic->preUpdate(); @@ -827,7 +827,7 @@ Bool GameEngine::canUpdateGameLogic() } else { - return canUpdateRegularGameLogic(); + return canUpdateRegularGameLogic(logicTimeQueryFlags); } } @@ -848,11 +848,10 @@ Bool GameEngine::canUpdateNetworkGameLogic() } /// ----------------------------------------------------------------------------------------------- -Bool GameEngine::canUpdateRegularGameLogic() +Bool GameEngine::canUpdateRegularGameLogic(UnsignedInt logicTimeQueryFlags) { - const Bool enabled = TheFramePacer->isLogicTimeScaleEnabled(); - const Int logicTimeScaleFps = TheFramePacer->getLogicTimeScaleFps(); - const Int maxRenderFps = TheFramePacer->getFramesPerSecondLimit(); + const Int logicTimeScaleFps = TheFramePacer->getActualLogicTimeScaleFps(logicTimeQueryFlags); + const Int maxRenderFps = TheFramePacer->getActualFramesPerSecondLimit(); #if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE) const Bool useFastMode = TheGlobalData->m_TiVOFastMode; @@ -860,7 +859,7 @@ Bool GameEngine::canUpdateRegularGameLogic() const Bool useFastMode = TheGlobalData->m_TiVOFastMode && TheGameLogic->isInReplayGame(); #endif - if (useFastMode || !enabled || logicTimeScaleFps >= maxRenderFps) + if (useFastMode || logicTimeScaleFps >= maxRenderFps) { // Logic time scale is uncapped or larger equal Render FPS. Update straight away. return true; @@ -910,7 +909,7 @@ void GameEngine::update() } } - const Bool canUpdate = canUpdateGameLogic(); + const Bool canUpdate = canUpdateGameLogic(FramePacer::IgnoreFrozenTime | FramePacer::IgnoreHaltedGame); const Bool canUpdateLogic = canUpdate && !TheFramePacer->isGameHalted() && !TheFramePacer->isTimeFrozen(); const Bool canUpdateScript = canUpdate && !TheFramePacer->isGameHalted(); From 9a402fbe23c5a9196f48c720ef5a1d661a385f87 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Mon, 25 May 2026 12:50:55 +0200 Subject: [PATCH 14/20] fix(particlesys): Decouple particle systems from logic crc (#2742) --- Core/GameEngine/Include/Common/RandomValue.h | 20 ++++++++++++++++++ Core/GameEngine/Source/Common/RandomValue.cpp | 21 +++++++++++++++++++ .../Code/GameEngine/Include/Common/Geometry.h | 6 +++--- .../Source/Common/System/Geometry.cpp | 14 ++++++------- .../Object/Damage/TransitionDamageFX.cpp | 11 +++++++--- .../GameLogic/Object/Update/EMPUpdate.cpp | 16 +++++++++++--- .../Object/Update/SpecialAbilityUpdate.cpp | 11 +++++++++- .../Code/GameEngine/Include/Common/Geometry.h | 6 +++--- .../Source/Common/System/Geometry.cpp | 14 ++++++------- .../Object/Damage/TransitionDamageFX.cpp | 11 +++++++--- .../GameLogic/Object/Update/EMPUpdate.cpp | 16 +++++++++++--- .../Object/Update/SpecialAbilityUpdate.cpp | 11 +++++++++- 12 files changed, 123 insertions(+), 34 deletions(-) diff --git a/Core/GameEngine/Include/Common/RandomValue.h b/Core/GameEngine/Include/Common/RandomValue.h index 8cc97d5d844..572a3b713e5 100644 --- a/Core/GameEngine/Include/Common/RandomValue.h +++ b/Core/GameEngine/Include/Common/RandomValue.h @@ -35,4 +35,24 @@ extern void InitRandom( UnsignedInt seed ); extern UnsignedInt GetGameLogicRandomSeed(); ///< Get the seed (used for replays) extern UnsignedInt GetGameLogicRandomSeedCRC();///< Get the seed (used for CRCs) +struct RandomValueClass +{ + virtual Int GetRandomValueInt( Int lo, Int hi, const char *file, Int line ) const = 0; + virtual Real GetRandomValueReal( Real lo, Real hi, const char *file, Int line ) const = 0; +}; +struct LogicRandomValueClass final : RandomValueClass +{ + virtual Int GetRandomValueInt( Int lo, Int hi, const char *file, Int line ) const override; + virtual Real GetRandomValueReal( Real lo, Real hi, const char *file, Int line ) const override; +}; +struct ClientRandomValueClass final : RandomValueClass +{ + virtual Int GetRandomValueInt( Int lo, Int hi, const char *file, Int line ) const override; + virtual Real GetRandomValueReal( Real lo, Real hi, const char *file, Int line ) const override; +}; + +// use these macros to access the random value functions +#define RandomValueInt(randomValueClass, lo, hi) randomValueClass.GetRandomValueInt( lo, hi, __FILE__, __LINE__ ) +#define RandomValueReal(randomValueClass, lo, hi) randomValueClass.GetRandomValueReal( lo, hi, __FILE__, __LINE__ ) + //-------------------------------------------------------------------------------------------------------------- diff --git a/Core/GameEngine/Source/Common/RandomValue.cpp b/Core/GameEngine/Source/Common/RandomValue.cpp index d5317a52852..6e52d4f8f91 100644 --- a/Core/GameEngine/Source/Common/RandomValue.cpp +++ b/Core/GameEngine/Source/Common/RandomValue.cpp @@ -465,3 +465,24 @@ Real GameLogicRandomVariable::getValue() const return 0.0f; } } + + +Int LogicRandomValueClass::GetRandomValueInt( Int lo, Int hi, const char *file, Int line ) const +{ + return GetGameLogicRandomValue(lo, hi, file, line); +} + +Real LogicRandomValueClass::GetRandomValueReal( Real lo, Real hi, const char *file, Int line ) const +{ + return GetGameLogicRandomValueReal(lo, hi, file, line); +} + +Int ClientRandomValueClass::GetRandomValueInt( Int lo, Int hi, const char *file, Int line ) const +{ + return GetGameClientRandomValue(lo, hi, file, line); +} + +Real ClientRandomValueClass::GetRandomValueReal( Real lo, Real hi, const char *file, Int line ) const +{ + return GetGameClientRandomValueReal(lo, hi, file, line); +} diff --git a/Generals/Code/GameEngine/Include/Common/Geometry.h b/Generals/Code/GameEngine/Include/Common/Geometry.h index 88dcd9555d2..e743afa1672 100644 --- a/Generals/Code/GameEngine/Include/Common/Geometry.h +++ b/Generals/Code/GameEngine/Include/Common/Geometry.h @@ -31,6 +31,7 @@ #include "Lib/BaseType.h" #include "Common/AsciiString.h" +#include "Common/RandomValue.h" #include "Common/Snapshot.h" class INI; @@ -171,9 +172,8 @@ class GeometryInfo : public Snapshot /// get the 2d bounding box void get2DBounds(const Coord3D& geomCenter, Real angle, Region2D& bounds ) const; - /// note that the pt is generated using game logic random, not game client random! - void makeRandomOffsetWithinFootprint(Coord3D& pt) const; - void makeRandomOffsetOnPerimeter(Coord3D& pt) const; //Chooses a random point on the extent border. + void makeRandomOffsetWithinFootprint(Coord3D& pt, const RandomValueClass& random = LogicRandomValueClass()) const; + void makeRandomOffsetOnPerimeter(Coord3D& pt) const; ///< Chooses a random point on the extent border. Uses game logic random! void clipPointToFootprint(const Coord3D& geomCenter, Coord3D& ptToClip) const; diff --git a/Generals/Code/GameEngine/Source/Common/System/Geometry.cpp b/Generals/Code/GameEngine/Source/Common/System/Geometry.cpp index f33da731b42..2e4c35a2421 100644 --- a/Generals/Code/GameEngine/Source/Common/System/Geometry.cpp +++ b/Generals/Code/GameEngine/Source/Common/System/Geometry.cpp @@ -376,7 +376,7 @@ Bool GeometryInfo::isPointInFootprint(const Coord3D& geomCenter, const Coord3D& } //============================================================================= -void GeometryInfo::makeRandomOffsetWithinFootprint(Coord3D& pt) const +void GeometryInfo::makeRandomOffsetWithinFootprint(Coord3D& pt, const RandomValueClass& random) const { switch(m_type) { @@ -390,14 +390,14 @@ void GeometryInfo::makeRandomOffsetWithinFootprint(Coord3D& pt) const Real distSqr; do { - pt.x = GameLogicRandomValueReal(-m_majorRadius, m_majorRadius); - pt.y = GameLogicRandomValueReal(-m_majorRadius, m_majorRadius); + pt.x = RandomValueReal(random, -m_majorRadius, m_majorRadius); + pt.y = RandomValueReal(random, -m_majorRadius, m_majorRadius); pt.z = 0.0f; distSqr = sqr(pt.x) + sqr(pt.y); } while (distSqr > maxDistSqr); #else - Real radius = GameLogicRandomValueReal(0.0f, m_boundingCircleRadius); - Real angle = GameLogicRandomValueReal(-PI, PI); + Real radius = RandomValueReal(random, 0.0f, m_boundingCircleRadius); + Real angle = RandomValueReal(random, -PI, PI); pt.x = radius * Cos(angle); pt.y = radius * Sin(angle); pt.z = 0.0f; @@ -407,8 +407,8 @@ void GeometryInfo::makeRandomOffsetWithinFootprint(Coord3D& pt) const case GEOMETRY_BOX: { - pt.x = GameLogicRandomValueReal(-m_majorRadius, m_majorRadius); - pt.y = GameLogicRandomValueReal(-m_minorRadius, m_minorRadius); + pt.x = RandomValueReal(random, -m_majorRadius, m_majorRadius); + pt.y = RandomValueReal(random, -m_minorRadius, m_minorRadius); pt.z = 0.0f; break; } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp index a78a1b4ddd5..c9837afe55f 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp @@ -253,7 +253,7 @@ void TransitionDamageFX::onDelete() /** Given an FXLoc info struct, return the effect position that we are supposed to use. * The position is local to to the object */ //------------------------------------------------------------------------------------------------- -static Coord3D getLocalEffectPos( const FXLocInfo *locInfo, Drawable *draw ) +static Coord3D getLocalEffectPos( const FXLocInfo *locInfo, Drawable *draw, const RandomValueClass &random = LogicRandomValueClass() ) { DEBUG_ASSERTCRASH( locInfo, ("getLocalEffectPos: locInfo is null") ); @@ -290,7 +290,7 @@ static Coord3D getLocalEffectPos( const FXLocInfo *locInfo, Drawable *draw ) return locInfo->loc; // pick one of the bone positions - Int pick = GameLogicRandomValue( 0, boneCount - 1 ); + Int pick = RandomValueInt( random, 0, boneCount - 1 ); return positions[ pick ]; } @@ -387,6 +387,11 @@ void TransitionDamageFX::onBodyDamageStateChange( const DamageInfo* damageInfo, if( lastDamageInfo == nullptr || getDamageTypeFlag( modData->m_damageParticleTypes, lastDamageInfo->in.m_damageType ) ) { +#if RETAIL_COMPATIBLE_CRC + // TheSuperHackers @fix The particle system is now decoupled from the logic crc + // and the side effects on the logic random seed values are preserved for retail compatibility. + getLocalEffectPos( &modData->m_particleSystem[ newState ][ i ].locInfo, draw, LogicRandomValueClass() ); +#endif // create a new particle system based on the template provided ParticleSystem* pSystem = TheParticleSystemManager->createParticleSystem( pSystemT ); @@ -394,7 +399,7 @@ void TransitionDamageFX::onBodyDamageStateChange( const DamageInfo* damageInfo, { // get the what is the position we're going to played the effect at - pos = getLocalEffectPos( &modData->m_particleSystem[ newState ][ i ].locInfo, draw ); + pos = getLocalEffectPos( &modData->m_particleSystem[ newState ][ i ].locInfo, draw, ClientRandomValueClass() ); // // set position on system given any bone position provided, the bone position is diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp index e7bd1b54f78..ca7736e5c86 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp @@ -235,14 +235,24 @@ void EMPUpdate::doDisableAttack() for (UnsignedInt e = 0 ; e < emitterCount; ++e) { +#if RETAIL_COMPATIBLE_CRC + // TheSuperHackers @fix The particle system is now decoupled from the logic crc + // and the side effects on the logic random seed values are preserved for retail compatibility. + { + Coord3D offs = {0,0,0}; + curVictim->getGeometryInfo().makeRandomOffsetWithinFootprint( offs, LogicRandomValueClass() ); + GameLogicRandomValue(3, victimHeight); + GameLogicRandomValue(1, 100); + } +#endif ParticleSystem *sys = TheParticleSystemManager->createParticleSystem(tmp); if (sys) { Coord3D offs = {0,0,0}; - curVictim->getGeometryInfo().makeRandomOffsetWithinFootprint( offs ); - offs.z = GameLogicRandomValue(3, victimHeight); + curVictim->getGeometryInfo().makeRandomOffsetWithinFootprint( offs, ClientRandomValueClass() ); + offs.z = GameClientRandomValue(3, victimHeight); //This puts all the sparks within a quadrahemicycloid (rectangular dome) volume //The same shape as a four cornered camping dome tent, for those with less Greek @@ -259,7 +269,7 @@ void EMPUpdate::doDisableAttack() sys->attachToObject(curVictim); sys->setPosition( &offs ); sys->setSystemLifetime(MAX(0, data->m_disabledDuration - 30)); - sys->setInitialDelay(GameLogicRandomValue(1,100)); + sys->setInitialDelay(GameClientRandomValue(1,100)); } } } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp index 453331703eb..95c568a029c 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp @@ -1263,11 +1263,20 @@ void SpecialAbilityUpdate::triggerAbilityEffect() const ParticleSystemTemplate *tmp = data->m_disableFXParticleSystem; if (tmp) { +#if RETAIL_COMPATIBLE_CRC + // TheSuperHackers @fix The particle system is now decoupled from the logic crc + // and the side effects on the logic random seed values are preserved for retail compatibility. + { + Coord3D offs = {0,0,0}; + target->getGeometryInfo().makeRandomOffsetWithinFootprint( offs, LogicRandomValueClass() ); + } +#endif + ParticleSystem *sys = TheParticleSystemManager->createParticleSystem(tmp); if (sys) { Coord3D offs = {0,0,0}; - target->getGeometryInfo().makeRandomOffsetWithinFootprint( offs ); + target->getGeometryInfo().makeRandomOffsetWithinFootprint( offs, ClientRandomValueClass() ); sys->attachToObject(target); sys->setPosition( &offs ); diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Geometry.h b/GeneralsMD/Code/GameEngine/Include/Common/Geometry.h index 221ca370ce0..907b1ae72cb 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Geometry.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Geometry.h @@ -31,6 +31,7 @@ #include "Lib/BaseType.h" #include "Common/AsciiString.h" +#include "Common/RandomValue.h" #include "Common/Snapshot.h" class INI; @@ -171,9 +172,8 @@ class GeometryInfo : public Snapshot /// get the 2d bounding box void get2DBounds(const Coord3D& geomCenter, Real angle, Region2D& bounds ) const; - /// note that the pt is generated using game logic random, not game client random! - void makeRandomOffsetWithinFootprint(Coord3D& pt) const; - void makeRandomOffsetOnPerimeter(Coord3D& pt) const; //Chooses a random point on the extent border. + void makeRandomOffsetWithinFootprint(Coord3D& pt, const RandomValueClass& random = LogicRandomValueClass()) const; + void makeRandomOffsetOnPerimeter(Coord3D& pt) const; ///< Chooses a random point on the extent border. Uses game logic random! void clipPointToFootprint(const Coord3D& geomCenter, Coord3D& ptToClip) const; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/Geometry.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/Geometry.cpp index b69c01de560..1f927ae2615 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/Geometry.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/Geometry.cpp @@ -376,7 +376,7 @@ Bool GeometryInfo::isPointInFootprint(const Coord3D& geomCenter, const Coord3D& } //============================================================================= -void GeometryInfo::makeRandomOffsetWithinFootprint(Coord3D& pt) const +void GeometryInfo::makeRandomOffsetWithinFootprint(Coord3D& pt, const RandomValueClass& random) const { switch(m_type) { @@ -390,14 +390,14 @@ void GeometryInfo::makeRandomOffsetWithinFootprint(Coord3D& pt) const Real distSqr; do { - pt.x = GameLogicRandomValueReal(-m_majorRadius, m_majorRadius); - pt.y = GameLogicRandomValueReal(-m_majorRadius, m_majorRadius); + pt.x = RandomValueReal(random, -m_majorRadius, m_majorRadius); + pt.y = RandomValueReal(random, -m_majorRadius, m_majorRadius); pt.z = 0.0f; distSqr = sqr(pt.x) + sqr(pt.y); } while (distSqr > maxDistSqr); #else - Real radius = GameLogicRandomValueReal(0.0f, m_boundingCircleRadius); - Real angle = GameLogicRandomValueReal(-PI, PI); + Real radius = RandomValueReal(random, 0.0f, m_boundingCircleRadius); + Real angle = RandomValueReal(random, -PI, PI); pt.x = radius * Cos(angle); pt.y = radius * Sin(angle); pt.z = 0.0f; @@ -407,8 +407,8 @@ void GeometryInfo::makeRandomOffsetWithinFootprint(Coord3D& pt) const case GEOMETRY_BOX: { - pt.x = GameLogicRandomValueReal(-m_majorRadius, m_majorRadius); - pt.y = GameLogicRandomValueReal(-m_minorRadius, m_minorRadius); + pt.x = RandomValueReal(random, -m_majorRadius, m_majorRadius); + pt.y = RandomValueReal(random, -m_minorRadius, m_minorRadius); pt.z = 0.0f; break; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp index 9e201c1508f..ce57bd0a2f6 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp @@ -253,7 +253,7 @@ void TransitionDamageFX::onDelete() /** Given an FXLoc info struct, return the effect position that we are supposed to use. * The position is local to to the object */ //------------------------------------------------------------------------------------------------- -static Coord3D getLocalEffectPos( const FXLocInfo *locInfo, Drawable *draw ) +static Coord3D getLocalEffectPos( const FXLocInfo *locInfo, Drawable *draw, const RandomValueClass &random = LogicRandomValueClass() ) { DEBUG_ASSERTCRASH( locInfo, ("getLocalEffectPos: locInfo is null") ); @@ -290,7 +290,7 @@ static Coord3D getLocalEffectPos( const FXLocInfo *locInfo, Drawable *draw ) return locInfo->loc; // pick one of the bone positions - Int pick = GameLogicRandomValue( 0, boneCount - 1 ); + Int pick = RandomValueInt( random, 0, boneCount - 1 ); return positions[ pick ]; } @@ -387,6 +387,11 @@ void TransitionDamageFX::onBodyDamageStateChange( const DamageInfo* damageInfo, if( lastDamageInfo == nullptr || getDamageTypeFlag( modData->m_damageParticleTypes, lastDamageInfo->in.m_damageType ) ) { +#if RETAIL_COMPATIBLE_CRC + // TheSuperHackers @fix The particle system is now decoupled from the logic crc + // and the side effects on the logic random seed values are preserved for retail compatibility. + getLocalEffectPos( &modData->m_particleSystem[ newState ][ i ].locInfo, draw, LogicRandomValueClass() ); +#endif // create a new particle system based on the template provided ParticleSystem* pSystem = TheParticleSystemManager->createParticleSystem( pSystemT ); @@ -394,7 +399,7 @@ void TransitionDamageFX::onBodyDamageStateChange( const DamageInfo* damageInfo, { // get the what is the position we're going to played the effect at - pos = getLocalEffectPos( &modData->m_particleSystem[ newState ][ i ].locInfo, draw ); + pos = getLocalEffectPos( &modData->m_particleSystem[ newState ][ i ].locInfo, draw, ClientRandomValueClass() ); // // set position on system given any bone position provided, the bone position is diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp index aba51b4c2ee..d07e08795d1 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp @@ -304,14 +304,24 @@ void EMPUpdate::doDisableAttack() for (UnsignedInt e = 0 ; e < emitterCount; ++e) { +#if RETAIL_COMPATIBLE_CRC + // TheSuperHackers @fix The particle system is now decoupled from the logic crc + // and the side effects on the logic random seed values are preserved for retail compatibility. + { + Coord3D offs = {0,0,0}; + curVictim->getGeometryInfo().makeRandomOffsetWithinFootprint( offs, LogicRandomValueClass() ); + GameLogicRandomValue(3, victimHeight); + GameLogicRandomValue(1, 100); + } +#endif ParticleSystem *sys = TheParticleSystemManager->createParticleSystem(tmp); if (sys) { Coord3D offs = {0,0,0}; - curVictim->getGeometryInfo().makeRandomOffsetWithinFootprint( offs ); - offs.z = GameLogicRandomValue(3, victimHeight); + curVictim->getGeometryInfo().makeRandomOffsetWithinFootprint( offs, ClientRandomValueClass() ); + offs.z = GameClientRandomValue(3, victimHeight); //This puts all the sparks within a quadrahemicycloid (rectangular dome) volume //The same shape as a four cornered camping dome tent, for those with less Greek @@ -328,7 +338,7 @@ void EMPUpdate::doDisableAttack() sys->attachToObject(curVictim); sys->setPosition( &offs ); sys->setSystemLifetime(MAX(0, data->m_disabledDuration - 30)); - sys->setInitialDelay(GameLogicRandomValue(1,100)); + sys->setInitialDelay(GameClientRandomValue(1,100)); } } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp index fbbc907f31b..ba596b32152 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp @@ -1400,11 +1400,20 @@ void SpecialAbilityUpdate::triggerAbilityEffect() const ParticleSystemTemplate *tmp = data->m_disableFXParticleSystem; if (tmp) { +#if RETAIL_COMPATIBLE_CRC + // TheSuperHackers @fix The particle system is now decoupled from the logic crc + // and the side effects on the logic random seed values are preserved for retail compatibility. + { + Coord3D offs = {0,0,0}; + target->getGeometryInfo().makeRandomOffsetWithinFootprint( offs, LogicRandomValueClass() ); + } +#endif + ParticleSystem *sys = TheParticleSystemManager->createParticleSystem(tmp); if (sys) { Coord3D offs = {0,0,0}; - target->getGeometryInfo().makeRandomOffsetWithinFootprint( offs ); + target->getGeometryInfo().makeRandomOffsetWithinFootprint( offs, ClientRandomValueClass() ); sys->attachToObject(target); sys->setPosition( &offs ); From 92df9775166087c72fc4a05dbcb09471380dbbb4 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Mon, 25 May 2026 18:11:47 +0200 Subject: [PATCH 15/20] fix(particlesys): Simplify ParticleSystemManagerDummy setup (#2740) --- .../Include/GameClient/ParticleSys.h | 18 ++++++++++++++++-- .../Source/Common/ReplaySimulation.cpp | 2 -- .../Drawable/Update/BeaconClientUpdate.cpp | 6 ++---- Core/GameEngine/Source/GameClient/FXList.cpp | 2 +- .../GameEngine/Include/GameClient/GameClient.h | 2 -- .../Source/GameClient/GameClient.cpp | 9 --------- .../GameEngine/Include/GameClient/GameClient.h | 2 -- .../Source/GameClient/GameClient.cpp | 9 --------- 8 files changed, 19 insertions(+), 31 deletions(-) diff --git a/Core/GameEngine/Include/GameClient/ParticleSys.h b/Core/GameEngine/Include/GameClient/ParticleSys.h index 5054cd2b8f8..e65d8773675 100644 --- a/Core/GameEngine/Include/GameClient/ParticleSys.h +++ b/Core/GameEngine/Include/GameClient/ParticleSys.h @@ -753,6 +753,8 @@ class ParticleSystemManager : public SubsystemInterface, virtual void reset() override; ///< reset the manager and all particle systems virtual void update() override; ///< update all particle systems + virtual Bool isDummy() const { return false; } + virtual Int getOnScreenParticleCount() = 0; ///< returns the number of particles on screen virtual void setOnScreenParticleCount(int count); @@ -761,8 +763,7 @@ class ParticleSystemManager : public SubsystemInterface, ParticleSystemTemplate *newTemplate( const AsciiString &name ); /// given a template, instantiate a particle system - ParticleSystem *createParticleSystem( const ParticleSystemTemplate *sysTemplate, - Bool createSlaves = TRUE ); + ParticleSystem *createParticleSystem( const ParticleSystemTemplate *sysTemplate, Bool createSlaves = TRUE ); /** given a template, instantiate a particle system. if attachTo is not null, attach the particle system to the given object. @@ -835,11 +836,24 @@ class ParticleSystemManager : public SubsystemInterface, ParticleSystemIDMap m_systemMap; ///< a hash map of all particle systems }; + // TheSuperHackers @feature bobtista 31/01/2026 // ParticleSystemManager that does nothing. Used for Headless Mode. +// Generally does not load particle system templates. Certainly does not create particle systems. class ParticleSystemManagerDummy : public ParticleSystemManager { public: +#if RETAIL_COMPATIBLE_CRC + // Must not overload init to keep loading the particle system templates, + // which are unfortunately needed to preserve the correct logic crc. +#else + virtual void init() override {} + virtual void reset() override {} +#endif + virtual void update() override {} + + virtual Bool isDummy() const override { return true; } + virtual Int getOnScreenParticleCount() override { return 0; } virtual void doParticles(RenderInfoClass &rinfo) override {} virtual void queueParticleRender() override {} diff --git a/Core/GameEngine/Source/Common/ReplaySimulation.cpp b/Core/GameEngine/Source/Common/ReplaySimulation.cpp index 7d18b5cb58f..e6871769a94 100644 --- a/Core/GameEngine/Source/Common/ReplaySimulation.cpp +++ b/Core/GameEngine/Source/Common/ReplaySimulation.cpp @@ -86,8 +86,6 @@ int ReplaySimulation::simulateReplaysInThisProcess(const std::vectorgetPlaybackFrameCount() / LOGICFRAMES_PER_SECOND; while (TheRecorder->isPlaybackInProgress()) { - TheGameClient->updateHeadless(); - const int progressFrameInterval = 10*60*LOGICFRAMES_PER_SECOND; if (TheGameLogic->getFrame() != 0 && TheGameLogic->getFrame() % progressFrameInterval == 0) { diff --git a/Core/GameEngine/Source/GameClient/Drawable/Update/BeaconClientUpdate.cpp b/Core/GameEngine/Source/GameClient/Drawable/Update/BeaconClientUpdate.cpp index 63071f8340c..77a594ab1df 100644 --- a/Core/GameEngine/Source/GameClient/Drawable/Update/BeaconClientUpdate.cpp +++ b/Core/GameEngine/Source/GameClient/Drawable/Update/BeaconClientUpdate.cpp @@ -94,9 +94,7 @@ static ParticleSystem* createParticleSystem( Drawable *draw ) AsciiString templateName; templateName.format("BeaconSmoke%6.6X", (0xffffff & obj->getIndicatorColor())); const ParticleSystemTemplate *particleTemplate = TheParticleSystemManager->findTemplate( templateName ); - - DEBUG_ASSERTCRASH(particleTemplate, ("Could not find particle system %s", templateName.str())); - + DEBUG_ASSERTCRASH(TheParticleSystemManager->isDummy() || particleTemplate, ("Could not find particle system %s", templateName.str())); if (particleTemplate) { system = TheParticleSystemManager->createParticleSystem( particleTemplate ); @@ -107,7 +105,7 @@ static ParticleSystem* createParticleSystem( Drawable *draw ) {// THis this will whip up a new particle system to match the house color provided templateName.format("BeaconSmokeFFFFFF"); const ParticleSystemTemplate *failsafeTemplate = TheParticleSystemManager->findTemplate( templateName ); - DEBUG_ASSERTCRASH(failsafeTemplate, ("Doh, this is bad \n I Could not even find the white particle system to make a failsafe system out of.")); + DEBUG_ASSERTCRASH(TheParticleSystemManager->isDummy() || failsafeTemplate, ("Doh, this is bad \n I Could not even find the white particle system to make a failsafe system out of.")); system = TheParticleSystemManager->createParticleSystem( failsafeTemplate ); if (system) { diff --git a/Core/GameEngine/Source/GameClient/FXList.cpp b/Core/GameEngine/Source/GameClient/FXList.cpp index 50ab4135ae0..a9f630638fd 100644 --- a/Core/GameEngine/Source/GameClient/FXList.cpp +++ b/Core/GameEngine/Source/GameClient/FXList.cpp @@ -600,7 +600,7 @@ class ParticleSystemFXNugget : public FXNugget } const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate(m_name); - DEBUG_ASSERTCRASH(tmp, ("ParticleSystem %s not found",m_name.str())); + DEBUG_ASSERTCRASH(TheParticleSystemManager->isDummy() || tmp, ("ParticleSystem %s not found",m_name.str())); if (tmp) { for (Int i = 0; i < m_count; i++ ) diff --git a/Generals/Code/GameEngine/Include/GameClient/GameClient.h b/Generals/Code/GameEngine/Include/GameClient/GameClient.h index 7d42104edb4..7183371d887 100644 --- a/Generals/Code/GameEngine/Include/GameClient/GameClient.h +++ b/Generals/Code/GameEngine/Include/GameClient/GameClient.h @@ -94,8 +94,6 @@ class GameClient : public SubsystemInterface, void step(); ///< Do one fixed time step - void updateHeadless(); - void addDrawableToLookupTable( Drawable *draw ); ///< add drawable ID to hash lookup table void removeDrawableFromLookupTable( Drawable *draw ); ///< remove drawable ID from hash lookup table diff --git a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp index b9678a641ef..e2d293df015 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -750,15 +750,6 @@ void GameClient::step() TheDisplay->step(); } -void GameClient::updateHeadless() -{ - // TheSuperHackers @info helmutbuhler 03/05/2025 bobtista 02/02/2026 - // Update particles to prevent accumulation in headless mode. Particles are generated - // during GameLogic and only cleaned up during rendering. update() lets particles finish - // their lifecycle naturally instead of abruptly removing them with reset(). - TheParticleSystemManager->update(); -} - Bool GameClient::isMovieAbortRequested() { if (TheGameEngine) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h index ea8e3a59c5d..3650be5ee24 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h @@ -98,8 +98,6 @@ class GameClient : public SubsystemInterface, void step(); ///< Do one fixed time step - void updateHeadless(); - void addDrawableToLookupTable( Drawable *draw ); ///< add drawable ID to hash lookup table void removeDrawableFromLookupTable( Drawable *draw ); ///< remove drawable ID from hash lookup table diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index cb91389555b..d1c956e533a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -787,15 +787,6 @@ void GameClient::step() TheDisplay->step(); } -void GameClient::updateHeadless() -{ - // TheSuperHackers @info helmutbuhler 03/05/2025 bobtista 02/02/2026 - // Update particles to prevent accumulation in headless mode. Particles are generated - // during GameLogic and only cleaned up during rendering. update() lets particles finish - // their lifecycle naturally instead of abruptly removing them with reset(). - TheParticleSystemManager->update(); -} - Bool GameClient::isMovieAbortRequested() { if (TheGameEngine) From adcf69ed18b37bdeaaedde1f5a33010c1186e4b0 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Thu, 28 May 2026 21:09:25 +0200 Subject: [PATCH 16/20] fix(memory): Fix audio event related memory leaks when pausing the game (#2731) --- Core/GameEngine/Include/Common/AudioRequest.h | 2 ++ .../Source/Common/Audio/AudioRequest.cpp | 15 +++++++++++++++ .../Include/MilesAudioDevice/MilesAudioManager.h | 2 +- .../Source/MilesAudioDevice/MilesAudioManager.cpp | 14 +++++++++----- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/Core/GameEngine/Include/Common/AudioRequest.h b/Core/GameEngine/Include/Common/AudioRequest.h index a0321fdb481..3174fe85a8a 100644 --- a/Core/GameEngine/Include/Common/AudioRequest.h +++ b/Core/GameEngine/Include/Common/AudioRequest.h @@ -45,6 +45,8 @@ struct AudioRequest : public MemoryPoolObject MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( AudioRequest, "AudioRequest" ) public: + AudioEventRTS* releasePendingEvent(); + RequestType m_request; union { diff --git a/Core/GameEngine/Source/Common/Audio/AudioRequest.cpp b/Core/GameEngine/Source/Common/Audio/AudioRequest.cpp index a3235e1b69a..36876f35449 100644 --- a/Core/GameEngine/Source/Common/Audio/AudioRequest.cpp +++ b/Core/GameEngine/Source/Common/Audio/AudioRequest.cpp @@ -30,5 +30,20 @@ AudioRequest::~AudioRequest() { + if (m_usePendingEvent) + { + delete m_pendingEvent; + } +} +AudioEventRTS* AudioRequest::releasePendingEvent() +{ + if (m_usePendingEvent) + { + m_usePendingEvent = false; + AudioEventRTS* event = m_pendingEvent; + m_pendingEvent = nullptr; + return event; + } + return nullptr; } diff --git a/Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h b/Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h index 71707bd3054..2f4b2d7f66b 100644 --- a/Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h +++ b/Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h @@ -251,7 +251,7 @@ class MilesAudioManager : public AudioManager void initSamplePools(); void processRequest( AudioRequest *req ); - void playAudioEvent( AudioEventRTS *event ); + void playAudioEvent( AudioRequest* req ); void stopAudioEvent( AudioHandle handle ); void pauseAudioEvent( AudioHandle handle ); diff --git a/Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp b/Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp index 3a541494959..d76113e8342 100644 --- a/Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp +++ b/Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp @@ -652,8 +652,12 @@ void MilesAudioManager::pauseAmbient( Bool shouldPause ) } //------------------------------------------------------------------------------------------------- -void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) +void MilesAudioManager::playAudioEvent( AudioRequest* req ) { + DEBUG_ASSERTCRASH(req->m_usePendingEvent && req->m_pendingEvent, ("audio request was expected to contain a valid audio event")); + + AudioEventRTS* event = req->m_pendingEvent; + #ifdef INTENSIVE_AUDIO_DEBUG DEBUG_LOG(("MILES (%d) - Processing play request: %d (%s)", TheGameLogic->getFrame(), event->getPlayingHandle(), event->getEventName().str())); #endif @@ -709,7 +713,7 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) } // Put this on here, so that the audio event RTS will be cleaned up regardless. - audio->m_audioEventRTS = event; + audio->m_audioEventRTS = event = req->releasePendingEvent(); audio->m_stream = stream; audio->m_type = PAT_Stream; @@ -778,7 +782,7 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) sample3D = nullptr; } // Push it onto the list of playing things - audio->m_audioEventRTS = event; + audio->m_audioEventRTS = event = req->releasePendingEvent(); audio->m_3DSample = sample3D; audio->m_file = nullptr; audio->m_type = PAT_3DSample; @@ -849,7 +853,7 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) } // Push it onto the list of playing things - audio->m_audioEventRTS = event; + audio->m_audioEventRTS = event = req->releasePendingEvent(); audio->m_sample = sample; audio->m_file = nullptr; audio->m_type = PAT_Sample; @@ -2930,7 +2934,7 @@ void MilesAudioManager::processRequest( AudioRequest *req ) { case AR_Play: { - playAudioEvent(req->m_pendingEvent); + playAudioEvent(req); break; } case AR_Pause: From cc4a683fd32282a7b4ebaa1d97891412b5aa77ed Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Thu, 28 May 2026 21:10:08 +0200 Subject: [PATCH 17/20] bugfix(water): Fix river visuals in black shroud (#2749) --- .../Source/W3DDevice/GameClient/Water/W3DWater.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp index 564cc008e42..f3f33aac10e 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp @@ -179,7 +179,7 @@ static Int getRiverVertexDiffuse(W3DShroud *shroud, Real x, Real y, Real shadeR, (Int)(shadeR * shroudScale), (Int)(shadeG * shroudScale), (Int)(shadeB * shroudScale), - (diffuse >> 24) & 0xff); + ((diffuse >> 24) & 0xff) * shroudScale); } void doSkyBoxSet(Bool startDraw) @@ -926,9 +926,11 @@ void WaterRenderObjClass::ReAcquireResources() tex t1 \n\ tex t2 \n\ tex t3\n\ - mul r0,v0,t0 ; blend vertex color into t0. \n\ + mul r0.rgb, v0, t0 ; blend vertex color into t0. \n\ + mov r0.a, t0 ; keep vertex alpha from fading the base water. \n\ mul r1, t1, t2 ; mul\n\ - add r0.rgb, r0, t3\n\ + add r1.rgb, r1, t3\n\ + mul r1.rgb, r1, v0.a\n\ +mul r0.a, r0, t3\n\ add r0.rgb, r0, r1\n"; hr = D3DXAssembleShader( shader, strlen(shader), 0, nullptr, &compiledShader, nullptr); From 8a06f3536a901041b27c40eecfc7445965950402 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 30 May 2026 12:34:09 +0200 Subject: [PATCH 18/20] unify(message): Merge MessageStream, MetaEvent code (#2756) --- .../GameEngine/Include/Common/MessageStream.h | 35 ++++++++++- .../Source/Common/MessageStream.cpp | 34 +++++++++- .../GameClient/MessageStream/MetaEvent.cpp | 62 ++++++++++++++++++- .../GameEngine/Include/Common/MessageStream.h | 2 - .../GameClient/MessageStream/MetaEvent.cpp | 1 - 5 files changed, 125 insertions(+), 9 deletions(-) diff --git a/Generals/Code/GameEngine/Include/Common/MessageStream.h b/Generals/Code/GameEngine/Include/Common/MessageStream.h index a8ddb0f368d..32d15c8fb07 100644 --- a/Generals/Code/GameEngine/Include/Common/MessageStream.h +++ b/Generals/Code/GameEngine/Include/Common/MessageStream.h @@ -272,6 +272,7 @@ class GameMessage : public MemoryPoolObject MSG_META_BEGIN_CAMERA_ZOOM_OUT, MSG_META_END_CAMERA_ZOOM_OUT, MSG_META_CAMERA_RESET, + MSG_META_TOGGLE_CAMERA_TRACKING_DRAWABLE, MSG_META_TOGGLE_FAST_FORWARD_REPLAY, ///< Toggle the fast forward feature MSG_META_TOGGLE_PAUSE, ///< TheSuperHackers @feature Toggle game pause MSG_META_TOGGLE_PAUSE_ALT, ///< TheSuperHackers @feature Toggle game pause (alternative mapping) @@ -280,6 +281,30 @@ class GameMessage : public MemoryPoolObject MSG_META_DEMO_INSTANT_QUIT, ///< bail out of game immediately +#if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE)//may be defined in GameCommon.h + MSG_CHEAT_RUNSCRIPT1, ///< run script named "KEY_F1" + MSG_CHEAT_RUNSCRIPT2, ///< run script named "KEY_F2" + MSG_CHEAT_RUNSCRIPT3, ///< run script named "KEY_F3" + MSG_CHEAT_RUNSCRIPT4, ///< run script named "KEY_F4" + MSG_CHEAT_RUNSCRIPT5, ///< run script named "KEY_F5" + MSG_CHEAT_RUNSCRIPT6, ///< run script named "KEY_F6" + MSG_CHEAT_RUNSCRIPT7, ///< run script named "KEY_F7" + MSG_CHEAT_RUNSCRIPT8, ///< run script named "KEY_F8" + MSG_CHEAT_RUNSCRIPT9, ///< run script named "KEY_F9" + MSG_CHEAT_TOGGLE_SPECIAL_POWER_DELAYS, ///< Toggle special power delays on/off + MSG_CHEAT_SWITCH_TEAMS, ///< switch local control to another team + MSG_CHEAT_KILL_SELECTION, ///< kill the selected units (yeah!) + MSG_CHEAT_TOGGLE_HAND_OF_GOD_MODE, ///< do 100% damage to the selected units (w00t!) + MSG_CHEAT_INSTANT_BUILD, ///< All building is with a timer of 1 + MSG_CHEAT_DESHROUD, ///< de-shroud the world for the local player + MSG_CHEAT_ADD_CASH, ///< adds 10000 cash to the player + MSG_CHEAT_GIVE_ALL_SCIENCES, ///< grant all grantable sciences + MSG_CHEAT_GIVE_SCIENCEPURCHASEPOINTS, ///< give yourself an SPP (but no rank change) + MSG_CHEAT_SHOW_HEALTH, ///< show object health + MSG_CHEAT_TOGGLE_MESSAGE_TEXT, ///< hides/shows the onscreen messages + +#endif + // META items that are really for debug/demo/development use only... // They do not get built into RELEASE builds. #if defined(RTS_DEBUG) @@ -339,10 +364,12 @@ class GameMessage : public MemoryPoolObject MSG_META_DEMO_ENSHROUD, ///< re-shroud the world for the local player MSG_META_DEMO_DESHROUD, ///< de-shroud the world for the local player MSG_META_DEBUG_SHOW_EXTENTS, ///< show object extents - MSG_META_DEBUG_SHOW_HEALTH, ///< show object health + MSG_META_DEBUG_SHOW_AUDIO_LOCATIONS, ///< show audio objects and radii + MSG_META_DEBUG_SHOW_HEALTH, ///< show object health MSG_META_DEBUG_GIVE_VETERANCY, ///< give a veterancy level to selected objects MSG_META_DEBUG_TAKE_VETERANCY, ///< take a veterancy level from selected objects MSG_META_DEMO_TOGGLE_AI_DEBUG, ///< show/hide the ai debug stats + MSG_META_DEMO_TOGGLE_SUPPLY_CENTER_PLACEMENT, ///getFrame() + +#define dont_DUMP_ALL_KEYS_TO_LOG + + +#ifdef DUMP_ALL_KEYS_TO_LOG +#include "GameClient/Keyboard.h" +#endif + MetaMap *TheMetaMap = nullptr; @@ -174,6 +182,7 @@ static const LookupListRec GameMessageMetaTypeNames[] = { "BEGIN_CAMERA_ZOOM_OUT", GameMessage::MSG_META_BEGIN_CAMERA_ZOOM_OUT }, { "END_CAMERA_ZOOM_OUT", GameMessage::MSG_META_END_CAMERA_ZOOM_OUT }, { "CAMERA_RESET", GameMessage::MSG_META_CAMERA_RESET }, + { "TOGGLE_CAMERA_TRACKING_DRAWABLE", GameMessage::MSG_META_TOGGLE_CAMERA_TRACKING_DRAWABLE }, { "TOGGLE_FAST_FORWARD_REPLAY", GameMessage::MSG_META_TOGGLE_FAST_FORWARD_REPLAY }, { "TOGGLE_PAUSE", GameMessage::MSG_META_TOGGLE_PAUSE }, { "TOGGLE_PAUSE_ALT", GameMessage::MSG_META_TOGGLE_PAUSE_ALT }, @@ -181,8 +190,33 @@ static const LookupListRec GameMessageMetaTypeNames[] = { "STEP_FRAME_ALT", GameMessage::MSG_META_STEP_FRAME_ALT }, { "DEMO_INSTANT_QUIT", GameMessage::MSG_META_DEMO_INSTANT_QUIT }, +#if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE)//may be defined in GameCommon.h + { "CHEAT_RUNSCRIPT1", GameMessage::MSG_CHEAT_RUNSCRIPT1 }, + { "CHEAT_RUNSCRIPT2", GameMessage::MSG_CHEAT_RUNSCRIPT2 }, + { "CHEAT_RUNSCRIPT3", GameMessage::MSG_CHEAT_RUNSCRIPT3 }, + { "CHEAT_RUNSCRIPT4", GameMessage::MSG_CHEAT_RUNSCRIPT4 }, + { "CHEAT_RUNSCRIPT5", GameMessage::MSG_CHEAT_RUNSCRIPT5 }, + { "CHEAT_RUNSCRIPT6", GameMessage::MSG_CHEAT_RUNSCRIPT6 }, + { "CHEAT_RUNSCRIPT7", GameMessage::MSG_CHEAT_RUNSCRIPT7 }, + { "CHEAT_RUNSCRIPT8", GameMessage::MSG_CHEAT_RUNSCRIPT8 }, + { "CHEAT_RUNSCRIPT9", GameMessage::MSG_CHEAT_RUNSCRIPT9 }, + { "CHEAT_TOGGLE_SPECIAL_POWER_DELAYS", GameMessage::MSG_CHEAT_TOGGLE_SPECIAL_POWER_DELAYS }, + { "CHEAT_SWITCH_TEAMS", GameMessage::MSG_CHEAT_SWITCH_TEAMS }, + { "CHEAT_KILL_SELECTION", GameMessage::MSG_CHEAT_KILL_SELECTION }, + { "CHEAT_TOGGLE_HAND_OF_GOD_MODE", GameMessage::MSG_CHEAT_TOGGLE_HAND_OF_GOD_MODE }, + { "CHEAT_INSTANT_BUILD", GameMessage::MSG_CHEAT_INSTANT_BUILD }, + { "CHEAT_DESHROUD", GameMessage::MSG_CHEAT_DESHROUD }, + { "CHEAT_ADD_CASH", GameMessage::MSG_CHEAT_ADD_CASH }, + { "CHEAT_GIVE_ALL_SCIENCES", GameMessage::MSG_CHEAT_GIVE_ALL_SCIENCES }, + { "CHEAT_GIVE_SCIENCEPURCHASEPOINTS", GameMessage::MSG_CHEAT_GIVE_SCIENCEPURCHASEPOINTS }, + { "CHEAT_SHOW_HEALTH", GameMessage::MSG_CHEAT_SHOW_HEALTH }, + { "CHEAT_TOGGLE_MESSAGE_TEXT", GameMessage::MSG_CHEAT_TOGGLE_MESSAGE_TEXT }, + +#endif + #if defined(RTS_DEBUG) { "HELP", GameMessage::MSG_META_HELP }, + { "DEMO_TOGGLE_BEHIND_BUILDINGS", GameMessage::MSG_META_DEMO_TOGGLE_BEHIND_BUILDINGS }, { "DEMO_LOD_DECREASE", GameMessage::MSG_META_DEMO_LOD_DECREASE }, { "DEMO_LOD_INCREASE", GameMessage::MSG_META_DEMO_LOD_INCREASE }, @@ -250,7 +284,8 @@ static const LookupListRec GameMessageMetaTypeNames[] = { "DEMO_TOGGLE_GREEN_VIEW", GameMessage::MSG_META_DEMO_TOGGLE_GREEN_VIEW }, { "DEMO_TOGGLE_MOTION_BLUR_ZOOM", GameMessage::MSG_META_DEMO_TOGGLE_MOTION_BLUR_ZOOM }, { "DEMO_SHOW_EXTENTS", GameMessage::MSG_META_DEBUG_SHOW_EXTENTS }, - { "DEMO_SHOW_HEALTH", GameMessage::MSG_META_DEBUG_SHOW_HEALTH }, + { "DEMO_SHOW_AUDIO_LOCATIONS", GameMessage::MSG_META_DEBUG_SHOW_AUDIO_LOCATIONS }, + { "DEMO_SHOW_HEALTH", GameMessage::MSG_META_DEBUG_SHOW_HEALTH }, { "DEMO_GIVE_VETERANCY", GameMessage::MSG_META_DEBUG_GIVE_VETERANCY }, { "DEMO_TAKE_VETERANCY", GameMessage::MSG_META_DEBUG_TAKE_VETERANCY }, { "DEMO_BATTLE_CRY", GameMessage::MSG_META_DEMO_BATTLE_CRY }, @@ -266,6 +301,7 @@ static const LookupListRec GameMessageMetaTypeNames[] = { "DEMO_DESHROUD", GameMessage::MSG_META_DEMO_DESHROUD }, { "DEMO_ENSHROUD", GameMessage::MSG_META_DEMO_ENSHROUD }, { "DEMO_TOGGLE_AI_DEBUG", GameMessage::MSG_META_DEMO_TOGGLE_AI_DEBUG }, + { "DEMO_TOGGLE_SUPPLY_CENTER_PLACEMENT", GameMessage::MSG_META_DEMO_TOGGLE_SUPPLY_CENTER_PLACEMENT }, { "DEMO_TOGGLE_NO_DRAW", GameMessage::MSG_NO_DRAW }, { "DEMO_CYCLE_LOD_LEVEL", GameMessage::MSG_META_DEMO_CYCLE_LOD_LEVEL }, { "DEMO_DUMP_ASSETS", GameMessage::MSG_META_DEBUG_DUMP_ASSETS}, @@ -302,6 +338,9 @@ static const LookupListRec GameMessageMetaTypeNames[] = { "DEBUG_DUMP_ALL_PLAYER_OBJECTS", GameMessage::MSG_META_DEBUG_DUMP_ALL_PLAYER_OBJECTS }, { "DEMO_WIN", GameMessage::MSG_META_DEBUG_WIN }, { "DEMO_TOGGLE_DEBUG_STATS", GameMessage::MSG_META_DEMO_TOGGLE_DEBUG_STATS }, + { "DEBUG_OBJECT_ID_PERFORMANCE", GameMessage::MSG_META_DEBUG_OBJECT_ID_PERFORMANCE }, + { "DEBUG_DRAWABLE_ID_PERFORMANCE", GameMessage::MSG_META_DEBUG_DRAWABLE_ID_PERFORMANCE }, + { "DEBUG_SLEEPY_UPDATE_PERFORMANCE", GameMessage::MSG_META_DEBUG_SLEEPY_UPDATE_PERFORMANCE }, #endif // defined(RTS_DEBUG) @@ -499,9 +538,28 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa } } + + if (t == GameMessage::MSG_RAW_KEY_DOWN) + { m_lastKeyDown = key; - m_lastModState = newModState; + + +#ifdef DUMP_ALL_KEYS_TO_LOG + + WideChar Wkey = TheKeyboard->getPrintableKey(key, 0); + UnicodeString uKey; + uKey.set(&Wkey); + AsciiString aKey; + aKey.translate(uKey); + DEBUG_LOG(("^%s ", aKey.str())); +#endif + + } + + + + m_lastModState = newModState; } diff --git a/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h b/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h index 0a08d98486d..fc7945a3156 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h @@ -614,8 +614,6 @@ class GameMessage : public MemoryPoolObject MSG_DEBUG_KILL_OBJECT, #endif - - //********************************************************************************************************* MSG_END_NETWORK_MESSAGES = 1999, ///< MARKER TO DELINEATE MESSAGES THAT GO OVER THE NETWORK //********************************************************************************************************* diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp index 5f7d773a129..731e6eafcba 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp @@ -465,7 +465,6 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa newModState |= ALT; } - for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next) { if (!isMessageUsable(map->m_usableIn)) From d7f72032420fbf8ab641f527ac2e008cd21e215e Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 30 May 2026 13:09:23 +0200 Subject: [PATCH 19/20] fix(metaevent): Ignore order in which modifier keys are released to trigger meta events (#2577) --- Core/Libraries/Include/Lib/BaseType.h | 1 + .../GameEngine/Include/GameClient/MetaEvent.h | 81 ++++++++++++- .../GameClient/MessageStream/MetaEvent.cpp | 110 ++++++++++++------ .../GameEngine/Include/GameClient/MetaEvent.h | 81 ++++++++++++- .../GameClient/MessageStream/MetaEvent.cpp | 110 ++++++++++++------ 5 files changed, 311 insertions(+), 72 deletions(-) diff --git a/Core/Libraries/Include/Lib/BaseType.h b/Core/Libraries/Include/Lib/BaseType.h index cbe51e3e995..2cb718c9e27 100644 --- a/Core/Libraries/Include/Lib/BaseType.h +++ b/Core/Libraries/Include/Lib/BaseType.h @@ -139,6 +139,7 @@ inline Real deg2rad(Real rad) { return rad * (PI/180); } //----------------------------------------------------------------------------- // TheSuperHackers @build xezon 17/03/2025 Renames BitTest to BitIsSet to prevent conflict with BitTest macro from winnt.h #define BitIsSet( x, i ) ( ( (x) & (i) ) != 0 ) +#define BitsAreSet( x, i ) ( ( (x) & (i) ) == (i) ) #define BitSet( x, i ) ( (x) |= (i) ) #define BitClear( x, i ) ( (x ) &= ~(i) ) #define BitToggle( x, i ) ( (x) ^= (i) ) diff --git a/Generals/Code/GameEngine/Include/GameClient/MetaEvent.h b/Generals/Code/GameEngine/Include/GameClient/MetaEvent.h index 28ecc024578..fd9dc1efca0 100644 --- a/Generals/Code/GameEngine/Include/GameClient/MetaEvent.h +++ b/Generals/Code/GameEngine/Include/GameClient/MetaEvent.h @@ -354,9 +354,84 @@ EMPTY_DTOR(MetaMapRec) class MetaEventTranslator : public GameMessageTranslator { private: - - Int m_lastKeyDown; // really a MappableKeyType - Int m_lastModState; // really a MappableKeyModState + struct KeyDownInfo + { + KeyDownInfo() : m_modStateBits(0) {} + + static UnsignedInt getMaxKeyModStateCount() + { + return 7; + } + + static MappableKeyModState toKeyModState(UnsignedInt index) + { + switch (index) + { + case 0: return CTRL; + case 1: return ALT; + case 2: return SHIFT; + case 3: return CTRL_ALT; + case 4: return SHIFT_CTRL; + case 5: return SHIFT_ALT; + case 6: return SHIFT_ALT_CTRL; + } + return NONE; + } + + static UnsignedInt toIndex(MappableKeyModState modState) + { + switch (modState) + { + case CTRL: return 0; + case ALT: return 1; + case SHIFT: return 2; + case CTRL_ALT: return 3; + case SHIFT_CTRL: return 4; + case SHIFT_ALT: return 5; + case SHIFT_ALT_CTRL: return 6; + } + return 7; + } + + Bool isKeyDown() const + { + return m_modStateBits != 0; + } + + MappableKeyModState getKeyModState(UnsignedInt index) + { + if (BitIsSet(m_modStateBits, 1 << index)) + { + return toKeyModState(index); + } + return NONE; + } + + void clearKeyModState(UnsignedInt index) + { + BitClear(m_modStateBits, 1 << index); + } + + Bool hasKeyModState(MappableKeyModState modState) const + { + return BitIsSet(m_modStateBits, 1 << toIndex(modState)); + } + + void setKeyModState(MappableKeyModState modState) + { + BitSet(m_modStateBits, 1 << toIndex(modState)); + } + + void clearKeyModState(MappableKeyModState modState) + { + BitClear(m_modStateBits, 1 << toIndex(modState)); + } + + private: + UnsignedByte m_modStateBits; ///< Fits all combinations of CTRL+ALT+SHIFT, storing 1 bit for each + }; + + KeyDownInfo m_keyDownInfos[KEY_COUNT]; enum { NUM_MOUSE_BUTTONS = 3 }; ICoord2D m_mouseDownPosition[NUM_MOUSE_BUTTONS]; diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp index 34ee4ab84f8..1db57262125 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp @@ -379,15 +379,11 @@ static const FieldParse TheMetaMapFieldParseTable[] = // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// //------------------------------------------------------------------------------------------------- -MetaEventTranslator::MetaEventTranslator() : - m_lastKeyDown(MK_NONE), - m_lastModState(0) +MetaEventTranslator::MetaEventTranslator() { for (Int i = 0; i < NUM_MOUSE_BUTTONS; ++i) { m_nextUpShouldCreateDoubleClick[i] = FALSE; } - - } //------------------------------------------------------------------------------------------------- @@ -443,8 +439,20 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa if (t == GameMessage::MSG_RAW_KEY_DOWN || t == GameMessage::MSG_RAW_KEY_UP) { - MappableKeyType key = (MappableKeyType)msg->getArgument(0)->integer; - Int keyState = msg->getArgument(1)->integer; + const Int systemKey = msg->getArgument(0)->integer; + const Int keyState = msg->getArgument(1)->integer; + + MappableKeyType key = (MappableKeyType)systemKey; + switch (systemKey) + { + case KEY_LCTRL: + case KEY_RCTRL: + case KEY_LSHIFT: + case KEY_RSHIFT: + case KEY_LALT: + case KEY_RALT: + key = MK_NONE; + } // for our purposes here, we don't care to distinguish between right and left keys, // so just fudge a little to simplify things. @@ -465,28 +473,58 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa newModState |= ALT; } - for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next) + const Bool modStateRemoved = (key == MK_NONE) && (t == GameMessage::MSG_RAW_KEY_UP); + + if (modStateRemoved) { - if (!isMessageUsable(map->m_usableIn)) - continue; + // TheSuperHackers @fix The key handler now ignores the order in which modifier keys are released. + // This avoids frustrating experiences where a wrong button release order would skip an important key event. - // check for the special case of mods-only-changed. - if ( - map->m_key == MK_NONE && - newModState != m_lastModState && - ( - (map->m_transition == UP && map->m_modState == m_lastModState) || - (map->m_transition == DOWN && map->m_modState == newModState) - ) - ) + for (Int keyDownIndex = 0; keyDownIndex < ARRAY_SIZE(m_keyDownInfos); ++keyDownIndex) { - //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() Mods-only change: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); - /*GameMessage *metaMsg =*/ TheMessageStream->appendMessage(map->m_meta); - disp = DESTROY_MESSAGE; - break; + const MappableKeyType keyDown = (MappableKeyType)keyDownIndex; + KeyDownInfo &keyDownInfo = m_keyDownInfos[keyDownIndex]; + + if (!keyDownInfo.isKeyDown()) + continue; + + for (UnsignedInt modStateIndex = 0; modStateIndex < KeyDownInfo::getMaxKeyModStateCount(); ++modStateIndex) + { + const MappableKeyModState keyDownModState = keyDownInfo.getKeyModState(modStateIndex); + + if (keyDownModState == NONE) + continue; + + if (BitsAreSet(newModState, keyDownModState)) + continue; + + // Forget that this key and mod state are pressed. + keyDownInfo.clearKeyModState(modStateIndex); + + for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next) + { + if (!isMessageUsable(map->m_usableIn)) + continue; + + if (!(map->m_key == keyDown && map->m_modState == keyDownModState && map->m_transition == UP)) + continue; + + TheMessageStream->appendMessage(map->m_meta); + disp = DESTROY_MESSAGE; + } + } } + } + else + { + // TheSuperHackers @info The regular key handler only triggers events when the mapped key is pressed, + // not when the modifier (CTRL, ALT, SHIFT) is pressed, unless the key is MK_NONE. + + for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next) + { + if (!isMessageUsable(map->m_usableIn)) + continue; - // ok, now check for "normal" key transitions. if ( map->m_key == key && map->m_modState == newModState && @@ -497,7 +535,6 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa ) ) { - if( keyState & KEY_STATE_AUTOREPEAT ) { // if it's an autorepeat of a "known" key, don't generate the meta-event, @@ -538,13 +575,8 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa } } - - if (t == GameMessage::MSG_RAW_KEY_DOWN) { - m_lastKeyDown = key; - - #ifdef DUMP_ALL_KEYS_TO_LOG WideChar Wkey = TheKeyboard->getPrintableKey(key, 0); @@ -554,12 +586,24 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa aKey.translate(uKey); DEBUG_LOG(("^%s ", aKey.str())); #endif - + if (newModState != NONE) + { + // Remember that this key and mod state are pressed. + m_keyDownInfos[key].setKeyModState((MappableKeyModState)newModState); + } } + else + { + if (newModState != NONE) + { + DEBUG_ASSERTCRASH(key != MK_NONE, ("Key is expected to be not MK_NONE")); + // Forget that this key and mod state are pressed. + m_keyDownInfos[key].clearKeyModState((MappableKeyModState)newModState); + } + } - - m_lastModState = newModState; + } } diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h b/GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h index 1bfc1a9ea2b..92d45caffd0 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h @@ -354,9 +354,84 @@ EMPTY_DTOR(MetaMapRec) class MetaEventTranslator : public GameMessageTranslator { private: - - Int m_lastKeyDown; // really a MappableKeyType - Int m_lastModState; // really a MappableKeyModState + struct KeyDownInfo + { + KeyDownInfo() : m_modStateBits(0) {} + + static UnsignedInt getMaxKeyModStateCount() + { + return 7; + } + + static MappableKeyModState toKeyModState(UnsignedInt index) + { + switch (index) + { + case 0: return CTRL; + case 1: return ALT; + case 2: return SHIFT; + case 3: return CTRL_ALT; + case 4: return SHIFT_CTRL; + case 5: return SHIFT_ALT; + case 6: return SHIFT_ALT_CTRL; + } + return NONE; + } + + static UnsignedInt toIndex(MappableKeyModState modState) + { + switch (modState) + { + case CTRL: return 0; + case ALT: return 1; + case SHIFT: return 2; + case CTRL_ALT: return 3; + case SHIFT_CTRL: return 4; + case SHIFT_ALT: return 5; + case SHIFT_ALT_CTRL: return 6; + } + return 7; + } + + Bool isKeyDown() const + { + return m_modStateBits != 0; + } + + MappableKeyModState getKeyModState(UnsignedInt index) + { + if (BitIsSet(m_modStateBits, 1 << index)) + { + return toKeyModState(index); + } + return NONE; + } + + void clearKeyModState(UnsignedInt index) + { + BitClear(m_modStateBits, 1 << index); + } + + Bool hasKeyModState(MappableKeyModState modState) const + { + return BitIsSet(m_modStateBits, 1 << toIndex(modState)); + } + + void setKeyModState(MappableKeyModState modState) + { + BitSet(m_modStateBits, 1 << toIndex(modState)); + } + + void clearKeyModState(MappableKeyModState modState) + { + BitClear(m_modStateBits, 1 << toIndex(modState)); + } + + private: + UnsignedByte m_modStateBits; ///< Fits all combinations of CTRL+ALT+SHIFT, storing 1 bit for each + }; + + KeyDownInfo m_keyDownInfos[KEY_COUNT]; enum { NUM_MOUSE_BUTTONS = 3 }; ICoord2D m_mouseDownPosition[NUM_MOUSE_BUTTONS]; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp index 731e6eafcba..c0c9b5d45f8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp @@ -379,15 +379,11 @@ static const FieldParse TheMetaMapFieldParseTable[] = // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// //------------------------------------------------------------------------------------------------- -MetaEventTranslator::MetaEventTranslator() : - m_lastKeyDown(MK_NONE), - m_lastModState(0) +MetaEventTranslator::MetaEventTranslator() { for (Int i = 0; i < NUM_MOUSE_BUTTONS; ++i) { m_nextUpShouldCreateDoubleClick[i] = FALSE; } - - } //------------------------------------------------------------------------------------------------- @@ -443,8 +439,20 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa if (t == GameMessage::MSG_RAW_KEY_DOWN || t == GameMessage::MSG_RAW_KEY_UP) { - MappableKeyType key = (MappableKeyType)msg->getArgument(0)->integer; - Int keyState = msg->getArgument(1)->integer; + const Int systemKey = msg->getArgument(0)->integer; + const Int keyState = msg->getArgument(1)->integer; + + MappableKeyType key = (MappableKeyType)systemKey; + switch (systemKey) + { + case KEY_LCTRL: + case KEY_RCTRL: + case KEY_LSHIFT: + case KEY_RSHIFT: + case KEY_LALT: + case KEY_RALT: + key = MK_NONE; + } // for our purposes here, we don't care to distinguish between right and left keys, // so just fudge a little to simplify things. @@ -465,28 +473,58 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa newModState |= ALT; } - for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next) + const Bool modStateRemoved = (key == MK_NONE) && (t == GameMessage::MSG_RAW_KEY_UP); + + if (modStateRemoved) { - if (!isMessageUsable(map->m_usableIn)) - continue; + // TheSuperHackers @fix The key handler now ignores the order in which modifier keys are released. + // This avoids frustrating experiences where a wrong button release order would skip an important key event. - // check for the special case of mods-only-changed. - if ( - map->m_key == MK_NONE && - newModState != m_lastModState && - ( - (map->m_transition == UP && map->m_modState == m_lastModState) || - (map->m_transition == DOWN && map->m_modState == newModState) - ) - ) + for (Int keyDownIndex = 0; keyDownIndex < ARRAY_SIZE(m_keyDownInfos); ++keyDownIndex) { - //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() Mods-only change: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); - /*GameMessage *metaMsg =*/ TheMessageStream->appendMessage(map->m_meta); - disp = DESTROY_MESSAGE; - break; + const MappableKeyType keyDown = (MappableKeyType)keyDownIndex; + KeyDownInfo &keyDownInfo = m_keyDownInfos[keyDownIndex]; + + if (!keyDownInfo.isKeyDown()) + continue; + + for (UnsignedInt modStateIndex = 0; modStateIndex < KeyDownInfo::getMaxKeyModStateCount(); ++modStateIndex) + { + const MappableKeyModState keyDownModState = keyDownInfo.getKeyModState(modStateIndex); + + if (keyDownModState == NONE) + continue; + + if (BitsAreSet(newModState, keyDownModState)) + continue; + + // Forget that this key and mod state are pressed. + keyDownInfo.clearKeyModState(modStateIndex); + + for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next) + { + if (!isMessageUsable(map->m_usableIn)) + continue; + + if (!(map->m_key == keyDown && map->m_modState == keyDownModState && map->m_transition == UP)) + continue; + + TheMessageStream->appendMessage(map->m_meta); + disp = DESTROY_MESSAGE; + } + } } + } + else + { + // TheSuperHackers @info The regular key handler only triggers events when the mapped key is pressed, + // not when the modifier (CTRL, ALT, SHIFT) is pressed, unless the key is MK_NONE. + + for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next) + { + if (!isMessageUsable(map->m_usableIn)) + continue; - // ok, now check for "normal" key transitions. if ( map->m_key == key && map->m_modState == newModState && @@ -497,7 +535,6 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa ) ) { - if( keyState & KEY_STATE_AUTOREPEAT ) { // if it's an autorepeat of a "known" key, don't generate the meta-event, @@ -538,13 +575,8 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa } } - - if (t == GameMessage::MSG_RAW_KEY_DOWN) { - m_lastKeyDown = key; - - #ifdef DUMP_ALL_KEYS_TO_LOG WideChar Wkey = TheKeyboard->getPrintableKey(key, 0); @@ -554,12 +586,24 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa aKey.translate(uKey); DEBUG_LOG(("^%s ", aKey.str())); #endif - + if (newModState != NONE) + { + // Remember that this key and mod state are pressed. + m_keyDownInfos[key].setKeyModState((MappableKeyModState)newModState); + } } + else + { + if (newModState != NONE) + { + DEBUG_ASSERTCRASH(key != MK_NONE, ("Key is expected to be not MK_NONE")); + // Forget that this key and mod state are pressed. + m_keyDownInfos[key].clearKeyModState((MappableKeyModState)newModState); + } + } - - m_lastModState = newModState; + } } From ed7e96f8fe1215ec3ebe4b21ed4ab518275e9f71 Mon Sep 17 00:00:00 2001 From: Joseph Ameer <77818519+Mr-Sheerlock@users.noreply.github.com> Date: Sun, 31 May 2026 10:39:00 +0300 Subject: [PATCH 20/20] unify(shroud): Merge W3DShroud code from Zero Hour and implicitly fix black terrain for one frame on far camera jumps (#2757) --- .../Source/W3DDevice/GameClient/W3DShroud.cpp | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp index abead507fd4..26a5a2f32da 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp @@ -120,19 +120,24 @@ void W3DShroud::init(WorldHeightMap *pMap, Real worldCellSizeX, Real worldCellSi //Precompute a bounding box for entire shroud layer if (pMap) { - m_numCellsX = REAL_TO_INT_CEIL((Real)(pMap->getXExtent() - 1 - pMap->getBorderSize()*2)*MAP_XY_FACTOR/m_cellWidth); - m_numCellsY = REAL_TO_INT_CEIL((Real)(pMap->getYExtent() - 1 - pMap->getBorderSize()*2)*MAP_XY_FACTOR/m_cellHeight); + m_numCellsX = REAL_TO_INT_CEIL((Real)(pMap->getXExtent() - 1 - pMap->getBorderSizeInline()*2)*MAP_XY_FACTOR/m_cellWidth); + m_numCellsY = REAL_TO_INT_CEIL((Real)(pMap->getYExtent() - 1 - pMap->getBorderSizeInline()*2)*MAP_XY_FACTOR/m_cellHeight); //Maximum visible cells will depend on maximum drawable terrain size plus 1 for partial cells (since //shroud cells are larger than terrain cells). dstTextureWidth=m_numMaxVisibleCellsX=REAL_TO_INT_FLOOR((Real)(pMap->getDrawWidth()-1)*MAP_XY_FACTOR/m_cellWidth)+1; dstTextureHeight=m_numMaxVisibleCellsY=REAL_TO_INT_FLOOR((Real)(pMap->getDrawHeight()-1)*MAP_XY_FACTOR/m_cellHeight)+1; + + dstTextureWidth = m_numCellsX; + dstTextureHeight = m_numCellsY; + dstTextureWidth += 2; //enlarge by 2 pixels so we can have a border color all the way around. unsigned int depth = 1; dstTextureHeight += 2; //enlarge by 2 pixels so we can have border color all the way around. TextureLoader::Validate_Texture_Size((unsigned int &)dstTextureWidth,(unsigned int &)dstTextureHeight, depth); } + UnsignedInt srcWidth,srcHeight; srcWidth=m_numCellsX; @@ -225,7 +230,7 @@ void W3DShroud::ReleaseResources() Bool W3DShroud::ReAcquireResources() { if (!m_dstTextureWidth) - return TRUE; //nothing to reaquire since shroud was never initialized with valid data + return TRUE; //nothing to reacquire since shroud was never initialized with valid data DEBUG_ASSERTCRASH( m_pDstTexture == nullptr, ("ReAcquire of existing shroud texture")); @@ -613,15 +618,24 @@ void W3DShroud::render(CameraClass *cam) WorldHeightMap *hm=TheTerrainRenderObject->getMap(); - Int visStartX=REAL_TO_INT_FLOOR((Real)(hm->getDrawOrgX()-hm->getBorderSize())*MAP_XY_FACTOR/m_cellWidth); //start of rendered heightmap rectangle + Int visStartX=REAL_TO_INT_FLOOR((Real)(hm->getDrawOrgX()-hm->getBorderSizeInline())*MAP_XY_FACTOR/m_cellWidth); //start of rendered heightmap rectangle if (visStartX < 0) visStartX = 0; //no shroud is applied in border area so it always starts at > 0 - Int visStartY=REAL_TO_INT_FLOOR((Real)(hm->getDrawOrgY()-hm->getBorderSize())*MAP_XY_FACTOR/m_cellHeight); + Int visStartY=REAL_TO_INT_FLOOR((Real)(hm->getDrawOrgY()-hm->getBorderSizeInline())*MAP_XY_FACTOR/m_cellHeight); if (visStartY < 0) visStartY = 0; //no shroud is applied in border area so it always starts at > 0 + + // Do it all [3/11/2003] + visStartX = 0; + visStartY = 0; + Int visEndX=visStartX+REAL_TO_INT_FLOOR((Real)(hm->getDrawWidth()-1)*MAP_XY_FACTOR/m_cellWidth)+1; //size of rendered heightmap rectangle Int visEndY=visStartY+REAL_TO_INT_FLOOR((Real)(hm->getDrawHeight()-1)*MAP_XY_FACTOR/m_cellHeight)+1; + // Do it all [3/11/2003] + visEndX = m_numCellsX; + visEndY = m_numCellsY; + if (visEndX > m_numCellsX) { visStartX -= visEndX - m_numCellsX; //shift visible rectangle to fall within terrain bounds