diff --git a/src/d_net.cpp b/src/d_net.cpp index c98f2b1e9d..0e9473d59f 100644 --- a/src/d_net.cpp +++ b/src/d_net.cpp @@ -2705,6 +2705,21 @@ void Net_DoCommand (int type, uint8_t **stream, int player) case DEM_ENDSCREENJOB: EndScreenJob(); break; + + case DEM_ZSC_CMD: + { + int cmd = ReadLong(stream); + unsigned int size = ReadWord(stream); + + TArray buffer = {}; + buffer.Grow(size); + for (unsigned int i = 0u; i < size; ++i) + buffer.Push(ReadByte(stream)); + + FNetworkCommand netCmd = { player, cmd, buffer }; + primaryLevel->localEventManager->NetCommand(netCmd); + } + break; default: I_Error ("Unknown net command: %d", type); @@ -2763,6 +2778,10 @@ void Net_SkipCommand (int type, uint8_t **stream) skip = strlen((char *)(*stream)) + 15; break; + case DEM_ZSC_CMD: + skip = 6 + (((*stream)[4] << 8) | (*stream)[5]); + break; + case DEM_SUMMON2: case DEM_SUMMONFRIEND2: case DEM_SUMMONFOE2: diff --git a/src/d_protocol.h b/src/d_protocol.h index 2c36a1874f..5899dd8ac7 100644 --- a/src/d_protocol.h +++ b/src/d_protocol.h @@ -163,6 +163,7 @@ enum EDemoCommand DEM_MDK, // 71 String: Damage type DEM_SETINV, // 72 SetInventory DEM_ENDSCREENJOB, + DEM_ZSC_CMD, // 74 Long: Command id, Word: Byte size of command }; // The following are implemented by cht_DoCheat in m_cheat.cpp diff --git a/src/events.cpp b/src/events.cpp index 3fe1cef311..5ce46fc05c 100755 --- a/src/events.cpp +++ b/src/events.cpp @@ -45,6 +45,51 @@ EventManager staticEventManager; +static int ListGetInt(VMVa_List& tags) +{ + if (tags.curindex < tags.numargs) + { + if (tags.reginfo[tags.curindex] == REGT_FLOAT) + return static_cast(tags.args[tags.curindex++].f); + + if (tags.reginfo[tags.curindex] == REGT_INT) + return tags.args[tags.curindex++].i; + + ThrowAbortException(X_OTHER, "Invalid parameter in network command function, int expected"); + } + + return TAG_DONE; +} + +static double ListGetDouble(VMVa_List& tags) +{ + if (tags.curindex < tags.numargs) + { + if (tags.reginfo[tags.curindex] == REGT_FLOAT) + return tags.args[tags.curindex++].f; + + if (tags.reginfo[tags.curindex] == REGT_INT) + return tags.args[tags.curindex++].i; + + ThrowAbortException(X_OTHER, "Invalid parameter in network command function, float expected"); + } + + return TAG_DONE; +} + +static const FString* ListGetString(VMVa_List& tags) +{ + if (tags.curindex < tags.numargs) + { + if (tags.reginfo[tags.curindex] == REGT_STRING) + return &tags.args[tags.curindex++].s(); + + ThrowAbortException(X_OTHER, "Invalid parameter in network command function, string expected"); + } + + return nullptr; +} + void EventManager::CallOnRegister() { for (DStaticEventHandler* handler = FirstEventHandler; handler; handler = handler->next) @@ -176,6 +221,92 @@ bool EventManager::SendNetworkEvent(FString name, int arg1, int arg2, int arg3, return true; } +bool EventManager::SendNetworkCommand(int cmd, VMVa_List args) +{ + if (gamestate != GS_LEVEL && gamestate != GS_TITLELEVEL) + return false; + + // Calculate the size of the message so we know where it ends. + unsigned int bytes = 0u; + int tag = ListGetInt(args); + while (tag != TAG_DONE) + { + switch (tag) + { + case NET_BYTE: + ++bytes; + break; + + case NET_WORD: + bytes += 2u; + break; + + case NET_LONG: + case NET_FLOAT: + bytes += 4u; + break; + + case NET_STRING: + ++bytes; // Strings will always consume at least one byte. + const FString* str = ListGetString(args); + if (str != nullptr) + bytes += str->Len(); + break; + } + + if (tag != NET_STRING) + ++args.curindex; + + tag = ListGetInt(args); + } + + Net_WriteByte(DEM_ZSC_CMD); + Net_WriteLong(cmd); // Using a full int here to allow better prevention of overlapping command ids. + Net_WriteWord(bytes); + + constexpr char Default[] = ""; + + args.curindex = 0; + tag = ListGetInt(args); + while (tag != TAG_DONE) + { + switch (tag) + { + default: + ++args.curindex; + break; + + case NET_BYTE: + Net_WriteByte(ListGetInt(args)); + break; + + case NET_WORD: + Net_WriteWord(ListGetInt(args)); + break; + + case NET_LONG: + Net_WriteLong(ListGetInt(args)); + break; + + case NET_FLOAT: + Net_WriteFloat(ListGetDouble(args)); + break; + + case NET_STRING: + const FString* str = ListGetString(args); + if (str != nullptr) + Net_WriteString(str->GetChars()); + else + Net_WriteString(Default); // Still have to send something here to be read correctly. + break; + } + + tag = ListGetInt(args); + } + + return true; +} + bool EventManager::CheckHandler(DStaticEventHandler* handler) { for (DStaticEventHandler* lhandler = FirstEventHandler; lhandler; lhandler = lhandler->next) @@ -524,6 +655,14 @@ bool EventManager::Responder(const event_t* ev) return false; } +void EventManager::NetCommand(FNetworkCommand& cmd) +{ + if (ShouldCallStatic(false)) staticEventManager.NetCommand(cmd); + + for (DStaticEventHandler* handler = FirstEventHandler; handler; handler = handler->next) + handler->NetCommandProcess(cmd); +} + void EventManager::Console(int player, FString name, int arg1, int arg2, int arg3, bool manual, bool ui) { if (ShouldCallStatic(false)) staticEventManager.Console(player, name, arg1, arg2, arg3, manual, ui); @@ -673,6 +812,49 @@ DEFINE_FIELD_X(ReplacedEvent, FReplacedEvent, Replacee) DEFINE_FIELD_X(ReplacedEvent, FReplacedEvent, Replacement) DEFINE_FIELD_X(ReplacedEvent, FReplacedEvent, IsFinal) +DEFINE_FIELD_X(NetworkCommand, FNetworkCommand, Player) +DEFINE_FIELD_X(NetworkCommand, FNetworkCommand, Command) + +DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadByte) +{ + PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); + + ACTION_RETURN_INT(self->ReadByte()); +} + +DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadWord) +{ + PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); + + ACTION_RETURN_INT(self->ReadWord()); +} + +DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadLong) +{ + PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); + + ACTION_RETURN_INT(self->ReadLong()); +} + +DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadFloat) +{ + PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); + + ACTION_RETURN_FLOAT(self->ReadFloat()); +} + +DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadString) +{ + PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); + + FString res = {}; + auto str = self->ReadString(); + if (str != nullptr) + res = str; + + ACTION_RETURN_STRING(res); +} + DEFINE_ACTION_FUNCTION(DStaticEventHandler, SetOrder) { PARAM_SELF_PROLOGUE(DStaticEventHandler); @@ -682,6 +864,16 @@ DEFINE_ACTION_FUNCTION(DStaticEventHandler, SetOrder) return 0; } +DEFINE_ACTION_FUNCTION(DEventHandler, SendNetworkCommand) +{ + PARAM_PROLOGUE; + PARAM_INT(cmd); + PARAM_VA_POINTER(va_reginfo); + + VMVa_List args = { param + 1, 0, numparam - 2, va_reginfo + 1 }; + ACTION_RETURN_BOOL(currentVMLevel->localEventManager->SendNetworkCommand(cmd, args)); +} + DEFINE_ACTION_FUNCTION(DEventHandler, SendNetworkEvent) { PARAM_PROLOGUE; @@ -1178,6 +1370,20 @@ void DStaticEventHandler::PostUiTick() } } +void DStaticEventHandler::NetCommandProcess(FNetworkCommand& cmd) +{ + IFVIRTUAL(DStaticEventHandler, NetworkCommandProcess) + { + if (isEmpty(func)) + return; + + VMValue params[] = { this, &cmd }; + VMCall(func, params, 2, nullptr, 0); + + cmd.Reset(); + } +} + void DStaticEventHandler::ConsoleProcess(int player, FString name, int arg1, int arg2, int arg3, bool manual, bool ui) { if (player < 0) diff --git a/src/events.h b/src/events.h index c97908b377..b5d5026384 100755 --- a/src/events.h +++ b/src/events.h @@ -18,6 +18,119 @@ enum class EventHandlerType PerMap }; +enum ENetCmd +{ + NET_BYTE = 1, + NET_WORD, + NET_LONG, + NET_FLOAT, + NET_STRING, +}; + +struct FNetworkCommand +{ +private: + size_t _index = 0; + TArray _stream = {}; + + inline bool IsValid() const + { + return _index < _stream.Size(); + } + +public: + int Player = 0; + int Command = 0; + + FNetworkCommand(const int player, const int command, TArray& stream) : Player(player), Command(command) + { + _stream.Swap(stream); + } + + inline void Reset() + { + _index = 0; + } + + int ReadByte() + { + if (!IsValid()) + return 0; + + return _stream[_index++]; + } + + // If a value has to cut off early, just treat the previous value as the full one. + int ReadWord() + { + if (!IsValid()) + return 0; + + int value = _stream[_index++]; + if (IsValid()) + value = (value << 8) | _stream[_index++]; + + return value; + } + + int ReadLong() + { + if (!IsValid()) + return 0; + + int value = _stream[_index++]; + if (IsValid()) + { + value = (value << 8) | _stream[_index++]; + if (IsValid()) + { + value = (value << 8) | _stream[_index++]; + if (IsValid()) + value = (value << 8) | _stream[_index++]; + } + } + + return value; + } + + // Floats without their first 9 bits are pretty meaningless so those are done first. + double ReadFloat() + { + if (!IsValid()) + return 0.0; + + int value = _stream[_index++] << 24; + if (IsValid()) + { + value |= _stream[_index++] << 16; + if (IsValid()) + { + value |= _stream[_index++] << 8; + if (IsValid()) + value |= _stream[_index++]; + } + } + + union + { + int i; + float f; + } floatCaster; + floatCaster.i = value; + return floatCaster.f; + } + + const char* ReadString() + { + if (!IsValid()) + return nullptr; + + const char* str = reinterpret_cast(&_stream[_index]); + _index += strlen(str) + 1; + return str; + } +}; + // ============================================== // // EventHandler - base class @@ -113,6 +226,7 @@ public: // void ConsoleProcess(int player, FString name, int arg1, int arg2, int arg3, bool manual, bool ui); + void NetCommandProcess(FNetworkCommand& cmd); // void CheckReplacement(PClassActor* replacee, PClassActor** replacement, bool* final); @@ -285,6 +399,8 @@ struct EventManager bool Responder(const event_t* ev); // splits events into InputProcess and UiProcess // this executes on console/net events. void Console(int player, FString name, int arg1, int arg2, int arg3, bool manual, bool ui); + // This reads from ZScript network commands. + void NetCommand(FNetworkCommand& cmd); // called when looking up the replacement for an actor class bool CheckReplacement(PClassActor* replacee, PClassActor** replacement); @@ -296,6 +412,8 @@ struct EventManager // send networked event. unified function. bool SendNetworkEvent(FString name, int arg1, int arg2, int arg3, bool manual); + // Send a custom network command from ZScript. + bool SendNetworkCommand(int cmd, VMVa_List args); // check if there is anything that should receive GUI events bool CheckUiProcessors(); diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index e81961a6a9..49fc9bc5ef 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -57,6 +57,7 @@ #include "a_dynlight.h" #include "types.h" #include "dictionary.h" +#include "events.h" static TArray properties; static TArray AFTable; @@ -807,6 +808,10 @@ void InitThingdef() frp->Size = sizeof(FRailParams); frp->Align = alignof(FRailParams); + auto netcmdstruct = NewStruct("NetworkCommand", nullptr, true); + netcmdstruct->Size = sizeof(FNetworkCommand); + netcmdstruct->Align = alignof(FNetworkCommand); + auto fltd = NewStruct("FLineTraceData", nullptr); fltd->Size = sizeof(FLineTraceData); fltd->Align = alignof(FLineTraceData); diff --git a/wadsrc/static/zscript/events.zs b/wadsrc/static/zscript/events.zs index c3c10129ca..f848148891 100644 --- a/wadsrc/static/zscript/events.zs +++ b/wadsrc/static/zscript/events.zs @@ -1,3 +1,23 @@ +enum ENetCmd +{ + NET_BYTE = 1, + NET_WORD, + NET_LONG, + NET_FLOAT, + NET_STRING, +} + +struct NetworkCommand native play version("4.12") +{ + native readonly int Player; + native readonly int Command; + + native int ReadByte(); + native int ReadWord(); + native int ReadLong(); + native double ReadFloat(); + native string ReadString(); +} struct RenderEvent native ui version("2.4") { @@ -127,6 +147,7 @@ class StaticEventHandler : Object native play version("2.4") virtual ui void ConsoleProcess(ConsoleEvent e) {} virtual ui void InterfaceProcess(ConsoleEvent e) {} virtual void NetworkProcess(ConsoleEvent e) {} + version("4.12") virtual void NetworkCommandProcess(NetworkCommand cmd) {} // virtual void CheckReplacement(ReplaceEvent e) {} @@ -150,5 +171,6 @@ class EventHandler : StaticEventHandler native version("2.4") { clearscope static native StaticEventHandler Find(class type); clearscope static native void SendNetworkEvent(String name, int arg1 = 0, int arg2 = 0, int arg3 = 0); + version("4.12") clearscope static native vararg bool SendNetworkCommand(int cmd, ...); clearscope static native void SendInterfaceEvent(int playerNum, string name, int arg1 = 0, int arg2 = 0, int arg3 = 0); }