Added SendNetworkCommand

Allows for a custom message to be sent over the network without the need for SendNetworkEvent. This includes all the possible valid types of byte, word, long, float, and string.
This commit is contained in:
Boondorl 2024-01-04 03:07:17 -05:00 committed by Christoph Oelckers
parent c62ecc44e2
commit 9565c94cd2
6 changed files with 371 additions and 0 deletions

View file

@ -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<uint8_t> 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:

View file

@ -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

View file

@ -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<int>(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)

View file

@ -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<uint8_t> _stream = {};
inline bool IsValid() const
{
return _index < _stream.Size();
}
public:
int Player = 0;
int Command = 0;
FNetworkCommand(const int player, const int command, TArray<uint8_t>& 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<const char*>(&_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();

View file

@ -57,6 +57,7 @@
#include "a_dynlight.h"
#include "types.h"
#include "dictionary.h"
#include "events.h"
static TArray<FPropertyInfo*> properties;
static TArray<AFuncDesc> 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);

View file

@ -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<StaticEventHandler> 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);
}