This tutorial was created by Royce Pipkins and demonstrates
a Beta 4 minimum netplay application. Download a copy of the source code associated with this file by clicking
here.
//Make sure you create your project as a "Win32 Application" NOT a
//"Win32 Console Application"
//don't forget to set the working directory for the project to the base project
//directory. It's also very help to set the output directories to the base project
//directory.
//don't forget to copy all the dll's, the lib, include, and levels directoies
//from the SDK base code into your proj directory
//add lib\genesis.lib and lib\genesisd.lib to the release and debug link lists
//respectivly.
//add winmm.lib to both link lists
//change /ML to /MT in the C/C++ project options for Release.
//change /MLd to /MTd in the C/C++ project options for Debug.
//run the exe from the project base directory.
//NOTE: the /MT and /MTd bits are diffrent from what I said in the Beta 4 MinApp //code but some comments I got from Dan of the ROC crew and a post by an Eclipse //person leads me to belive code generation should really be set to multi-threaded.
//As a test: copy your exe to the SDK base directory. (The same directory GTest.exe //is in) Run it from there. If it works in the SDK directory but not in your project //directory you have not copied all the nessecary files to your project directory.
#include <windows.h> #include "include\Genesis.h" #include <malloc.h> #include <string.h> #include <Time.h> #include <mmsystem.h>
//PROTOTYPES static HWND CreateMainWindow(HANDLE hInstance, char *AppName, int32 Width, int32 Height); LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam); void MoveCamera(void); void TextPump(void); void AddDisplayString(const char* string); void TextEntry(void); void StartServer(void); void StopNetPlay(void); void ClientConnect(const char * IP); void ClientCycle(void); void ServerCycle(void); void SendChatToServer(const char* chat_str); void SendChatToClient(geCSNetMgr_NetClient *client, const char *chat_str); void RestorwIfMined(void);
//USER MESSAGE SUBTYPES #define SUBTYPE_CHAT 0
//GLOBAL VARIABLES HWND mainwindowhandle; int focus = 1; geEngine *Engine = NULL; geDriver_System *DrvSys = NULL; geDriver *Driver = NULL; geDriver_Mode *Mode = NULL; geWorld *World = NULL; geCamera *Camera = NULL; geRect Rect; char *modename = NULL, *drvname = NULL; MSG Msg; geXForm3d Xform; int run, Width, Height;
geBoolean LeftMouse = GE_FALSE, RightMouse = GE_FALSE,
MoveForward = GE_FALSE, MoveBackward = GE_FALSE,
WantToQuit = GE_FALSE, TextEntryMode = GE_FALSE,
ChatMode = GE_FALSE, IPEntryMode = GE_FALSE,
NameEntryMode = GE_FALSE;
int WWidth = 640, WHeight = 480;
geVec3d CamAngle = {0, 0, 0}, CamLocation = {-1156, 400, 1215};
geVec3d Mins = {-12.5, -12.5, -12.5},
Maxs = {12.5, 12.5, 12.5};
char* display[15] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,NULL, NULL, NULL, NULL, NULL};
char textbuffer[1024]; int textbufpos = 0; geCSNetMgr *NetMgr = NULL; geCSNetMgr_NetSession *NetSess = NULL; geBoolean ServerMode = GE_FALSE, ClientMode = GE_FALSE; geCSNetMgr_NetClient *clients[64]; char thisClientName[32]; char TempName[1024]; geVFile *MainFS, *map;
//////////////////////////////////////////////////////////////////////// // WinMain // ////////////////////////////////////////////////////////////////////////
int WINAPI WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{ //start main window mainwindowhandle = CreateMainWindow(hInstance, "Small Netplay Application", WWidth, WHeight);
//start genesis Engine = geEngine_Create(mainwindowhandle, "MinApp", ".");
if (!Engine) {
MessageBox(NULL,"No Engine", "Error",MB_OK);
_exit(-1);
} geEngine_EnableFrameRateCounter(Engine, GE_FALSE);
//get driver container
DrvSys = geEngine_GetDriverSystem(Engine);
if (!DrvSys) {
MessageBox(NULL,"No DrvSys", "Error",MB_OK);
_exit(-1);
}
//pick driver
Driver = geDriver_SystemGetNextDriver(DrvSys, NULL);
if (!Driver) MessageBox(NULL,"No Driver","Error",MB_OK);
while(1) {
geDriver_GetName(Driver, &drvname);
if (drvname[0] == 'S') break;
Driver = geDriver_SystemGetNextDriver(DrvSys, Driver);
if (!Driver) _exit(-1);
}
//pick screen resolution
Mode = geDriver_GetNextMode(Driver, NULL);
while(1) {
if (!Mode) MessageBox(NULL,"No Mode","Error",MB_OK);
geDriver_ModeGetWidthHeight(Mode, &Width, &Height);
if (Width == -1/*WWidth*/ && Height == -1/*WHeight*/) break;
Mode = geDriver_GetNextMode(Driver, Mode);
}
//tell Genesis the use our selected video Driver in our selected video mode
if (!geEngine_SetDriverAndMode(Engine, Driver, Mode)) {
MessageBox(NULL,"Set Driver/Mode failed","Error",MB_OK);
_exit(-1);
} ///*********NEW FOR BETA 4!! IMPORTANT************
//Call this here and BeginFrame will fail.
//geEngine_SetGamma(Engine, 2.0f);
GetCurrentDirectory(sizeof(TempName), TempName);
MainFS = geVFile_OpenNewSystem(NULL,
GE_VFILE_TYPE_DOS,
TempName,
NULL,
GE_VFILE_OPEN_READONLY | GE_VFILE_OPEN_DIRECTORY);map = geVFile_Open(MainFS, "levels\\genvs.bsp", GE_VFILE_OPEN_READONLY);
//create a world object from a BSP file. World = geWorld_Create(map);
if (!World) {
MessageBox(NULL,"No World","Error",MB_OK);
_exit(-1);
}
///*********NEW FOR BETA 4!! IMPORTANT************
//geEngine now has a member that is an array of World pointers!!!
//somebody should try a multiple worlds demo :)
if (!geEngine_AddWorld(Engine, World)) {
MessageBox(NULL,"Failed to add World","Error",MB_OK);
_exit(-1);
}//Camera rect Rect.Left = 0; Rect.Right = WWidth - 1; Rect.Top = 0; Rect.Bottom = (WHeight - 1) - 60;
//create a camera object with an FOV of 2 (clueless), //and Rect window rendering area Camera = geCamera_Create(2.0f, &Rect);
if (!Camera) {
MessageBox(NULL,"No Camera","Error",MB_OK);
_exit(-1);
}
//initialize camera geXForm3d_SetIdentity(&Xform); geXForm3d_RotateZ(&Xform, (geFloat)0.0); geXForm3d_RotateX(&Xform, (geFloat)0.0); geXForm3d_RotateY(&Xform, (geFloat)0.0); geXForm3d_Translate(&Xform, -1156, 400, 1215); geCamera_SetXForm(Camera, &Xform);
//init the text buffer textbuffer[0] = 0; textbuffer[1] = 0;
//Reposition the window once more SetWindowPos(mainwindowhandle, HWND_TOP,100,100,WWidth+10,WHeight+10,SWP_NOCOPYBITS | SWP_NOZORDER); ShowCursor(FALSE);
//main application loop
run = 1;
while (run) {
if (WantToQuit) {
PostMessage(mainwindowhandle, WM_QUIT, 0, 0);
ShowCursor(TRUE);
} if (focus) MoveCamera();
TextEntry();
TextPump();
if (ServerMode) ServerCycle();
if (ClientMode || ServerMode) ClientCycle();
//Begin the engine frame, Engine, and Camera better be valid pointers
//or its gonna hang.
///*********NEW FOR BETA 4!! IMPORTANT************
//World was dropped from the arg list.
if (GE_FALSE == geEngine_BeginFrame(Engine, Camera, GE_TRUE)) {
MessageBox(mainwindowhandle, "BeginFrame failed", "Error", MB_OK);
break;
}
//render to offscreen buffer
if (GE_FALSE == geEngine_RenderWorld(Engine, World, Camera, 0.0f)) {
MessageBox(mainwindowhandle, "RenderWorld failed", "Error", MB_OK);
break;
} //end the frame, copy offscreen buffer to onscreen window
if (GE_FALSE == geEngine_EndFrame(Engine)) {
MessageBox(mainwindowhandle, "Endframe failed", "Error", MB_OK);
break;
}
while (PeekMessage( &Msg, NULL, 0, 0, PM_NOREMOVE))
{
if (!GetMessage(&Msg, NULL, 0, 0 ))
{
run = 0;
break;
} TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
//shut it all down and quit geCamera_Destroy(&Camera);
geWorld_Free(World);
geEngine_ShutdownDriver(Engine);
geEngine_Free(Engine);
return (0); }
//=====================================================================================
// CreateMainWindow
//=====================================================================================
static HWND CreateMainWindow(HANDLE hInstance, char *AppName, int32 Width, int32 Height)
{
WNDCLASS wc;
HWND hWnd; RECT WindowRect; // // Set up and register application window class //
wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = GetStockObject(BLACK_BRUSH); wc.lpszMenuName = (const char*)NULL; wc.lpszClassName = AppName;
RegisterClass(&wc);
// // Create application's main window //
hWnd = CreateWindowEx( 0, AppName, AppName, 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
MessageBox(0, "Could not create window.", "** ERROR **", MB_OK);
_exit(1);
} UpdateWindow(hWnd); SetFocus(hWnd);
SetWindowLong(hWnd,
GWL_STYLE,
GetWindowLong(hWnd, GWL_STYLE) & ~WS_POPUP); SetWindowLong(hWnd,
GWL_STYLE,
GetWindowLong(hWnd, GWL_STYLE) | (WS_OVERLAPPED |
WS_CAPTION |
WS_SYSMENU |
WS_MINIMIZEBOX)); SetWindowLong(hWnd,
GWL_STYLE,
GetWindowLong(hWnd, GWL_STYLE) | WS_THICKFRAME |
WS_MAXIMIZEBOX); SetWindowLong(hWnd,
GWL_EXSTYLE,
GetWindowLong(hWnd, GWL_EXSTYLE) ); WindowRect.left = 10; WindowRect.top = 10; WindowRect.right = 10+Width; WindowRect.bottom = 10+Height;
SetWindowPos(hWnd,
HWND_TOP,
100,
100,
Width+10,
Height+10,
SWP_NOCOPYBITS); // // Make window visible // ShowWindow(hWnd, SW_SHOWNORMAL);
return hWnd;
}
//////////////////////////////////////////////////////////////////////// // WndProc - processes all WM messages // ////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage,
WPARAM wParam, LPARAM lParam)
{ char key;
switch(iMessage)
{
case WM_RBUTTONDOWN: RightMouse = GE_TRUE; break;
case WM_LBUTTONUP: LeftMouse = GE_FALSE; break; case WM_RBUTTONUP: RightMouse = GE_FALSE; break; case WM_LBUTTONDOWN: LeftMouse = GE_TRUE; break;
case WM_KEYDOWN: //alpha VK's are same as ascii capitals
if (!TextEntryMode) {
key = (char)wParam;
switch (key) {
case 'L':
WantToQuit = GE_TRUE;
break;
case 'A':
MoveForward = GE_TRUE;
MoveBackward = GE_FALSE;
break;
case 'Z':
MoveBackward = GE_TRUE;
MoveForward = GE_FALSE;
break;
case 'T':
TextEntryMode = GE_TRUE;
ChatMode = GE_TRUE;
AddDisplayString(">>> Type a chat message.");
break;
case 'S':
if (!ClientMode) {
if (!ServerMode) {
StartServer();
}
else AddDisplayString (">>> You are already a server.");
}
else AddDisplayString(">>> You are a client. You can't start a server now.");
break;
case 'D':
StopNetPlay();
break;
case 'C':
if (!ServerMode) {
if (!ClientMode) {
TextEntryMode = GE_TRUE;
IPEntryMode = GE_TRUE;
AddDisplayString(">>> Enter IP Address of Server");
}
else AddDisplayString(">>> You are already a client.");
}
else AddDisplayString(">>> You are a server. You can't connect to another server.");
break;
case 'N':
if (!ServerMode && !ClientMode) {
NameEntryMode = GE_TRUE;
TextEntryMode = GE_TRUE;
AddDisplayString(">>> Enter your Name. (31 chars max)");
}
else {
AddDisplayString(">>> You can only set your name BEFORE you activate NetPlay.");
AddDisplayString(">>> Use the 'D' key to shut down NetPlay");
}
break;
case 'H':
AddDisplayString(">>> HELP MENU");
AddDisplayString(">>> A - Move Forward");
AddDisplayString(">>> Z - Move Backward");
AddDisplayString(">>> C - Connect to Server");
AddDisplayString(">>> S - Become a Server");
AddDisplayString(">>> D - Shutdown Netplay");
AddDisplayString(">>> N - Name Yourself");
AddDisplayString(">>> T - Send a Chat Message");
AddDisplayString(">>> NOTE: You won't find each other in the world.");
AddDisplayString(">>> Chat only for now.");
break;
case 'V':
AddDisplayString("MinApp Ver. 0.06");
break;
}
}
break; case WM_KEYUP:
if (!TextEntryMode) {
key = (char)wParam;
switch (key) {
case 'A':
MoveForward = GE_FALSE;
break;
case 'Z':
MoveBackward = GE_FALSE;
break;
}
}
break; case WM_CHAR:
if (TextEntryMode) {
//gather strings here
if (wParam == VK_RETURN) {
TextEntryMode = GE_FALSE;
textbuffer[textbufpos] = 0;
textbufpos++;
}
else if (wParam == VK_BACK) {
if (textbufpos) textbufpos--;
textbuffer[textbufpos] = 0;
}
else {
textbuffer[textbufpos] = (char)wParam;
if (textbufpos < 1023) textbufpos++;
textbuffer[textbufpos] = 0;
}
}
break;
case WM_KILLFOCUS:
ShowCursor(TRUE);
focus = 0;
break;
case WM_SETFOCUS:
ShowCursor(FALSE);
focus = 1;
break; default: return DefWindowProc(hWnd, iMessage, wParam, lParam); } return 0; }
void RestoreIfMined(void) {
ShowWindow(mainwindowhandle, SW_RESTORE);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MoveCamera - processes mouse and movement data that was gathered by WndProc //
///////////////////////////////////////////////////////////////////////////////////////////////////////////
void MoveCamera(void)
{
POINT pos;
geXForm3d delta; //Camera space transform
geFloat z_trans = 0.0; //Camera space
geVec3d zerovector = {0,0,0};
geVec3d CamAngle_Bak, CamLoc_Bak;
GE_Collision Collision_data;
CamAngle_Bak.X = CamAngle.X; CamAngle_Bak.Y = CamAngle.Y; CamAngle_Bak.Z = CamAngle.Z;
CamLoc_Bak.X = CamLocation.X; CamLoc_Bak.Y = CamLocation.Y; CamLoc_Bak.Z = CamLocation.Z;
GetCursorPos(&pos); ScreenToClient(mainwindowhandle, &pos);
geXForm3d_SetIdentity(&Xform); geXForm3d_SetIdentity(&delta);
CamAngle.Y += (float)((pos.x - WWidth/2) * (-0.001745)); CamAngle.X += (float)((pos.y - WHeight/2) * (-0.001745));
geXForm3d_RotateX(&Xform, CamAngle.X); geXForm3d_RotateY(&Xform, CamAngle.Y); geXForm3d_Translate(&Xform, CamLocation.X, CamLocation.Y, CamLocation.Z);
if (MoveForward) z_trans = -25.0; if (MoveBackward) z_trans = 25.0; geXForm3d_Translate(&delta, 0, 0, z_trans);
geXForm3d_Multiply(&Xform, &delta, &Xform);
geXForm3d_Transform(&Xform, &zerovector, &CamLocation);
if (geWorld_Collision(World, &Mins, &Maxs, &CamLocation, &CamLoc_Bak, GE_COLLIDE_ALL, 0, NULL, NULL, &Collision_data)) {
CamLocation.X = CamLoc_Bak.X;
CamLocation.Y = CamLoc_Bak.Y;
CamLocation.Z = CamLoc_Bak.Z;
CamAngle.X = CamAngle_Bak.X;
CamAngle.Y = CamAngle_Bak.Y;
CamAngle.Z = CamAngle_Bak.Z;
}
else geCamera_SetXForm(Camera, &Xform); pos.x = WWidth/2; pos.y = WHeight/2; ClientToScreen(mainwindowhandle, &pos); SetCursorPos(pos.x, pos.y); }
///////////////////////////////////////////////////////////////////////////////////////
// Text Pump //
///////////////////////////////////////////////////////////////////////////////////////
void TextPump(void) {
static int IsEmpty = 1;
static DWORD d_start_t;
DWORD current_t;
int i; if (IsEmpty) {
if (display[0] == NULL) return;
else {
IsEmpty = 0;
d_start_t = GetTickCount();
}
} if (!IsEmpty) { //check the time first and maybe roll the display
current_t = GetTickCount();
if ((current_t - d_start_t) % 15000 < 1000 &&
(current_t - d_start_t) > 1000) {
d_start_t = current_t;
free(display[0]);
for (i = 0; i < 14; i++) {
display[i] = display[i+1];
}
display[14] = NULL;
} //if we just emptied out the display, reset IsEmpty and return
if (display[0] == NULL) {
IsEmpty = 1;
return;
} //output the display
for (i = 0; i < 15; i++) {
if (display[i] == NULL) break;
geEngine_Printf(Engine, 10, 14 * i, display[i]);
}
}
}
//will make a copy of string
void AddDisplayString(const char* string) {
int i; char *internalcopy = NULL;
internalcopy = malloc(strlen(string) + 2); strcpy(internalcopy, string);
if (display[14] == NULL) {
for (i = 0; i < 15; i++) {
if (display[i] == NULL) {
display[i] = internalcopy;
break;
}
}
}
else {
free(display[0]);
for (i = 0; i < 14; i++) {
display[i] = display[i+1];
}
display[14] = internalcopy;
}
}
void TextEntry(void) {
static geBoolean LastCall = GE_FALSE;
char sys_msgbuf[1024];
if (TextEntryMode) {
geEngine_Printf(Engine, 100, 15 * 25, textbuffer + 1);
}
else {
if (LastCall) {
if (ChatMode) {
if (ServerMode) {
SendChatToServer(textbuffer + 1);
}
if (ClientMode) {
SendChatToServer(textbuffer + 1);
//AddDisplayString(textbuffer + 1); clients will get it echoed back anyway
}
ChatMode = GE_FALSE;
}
if (IPEntryMode) {
ClientConnect(textbuffer + 1);
IPEntryMode = GE_FALSE;
sprintf(sys_msgbuf,"Trying to connect to %s.", textbuffer + 1);
AddDisplayString(sys_msgbuf);
}
if (NameEntryMode) {
strncpy(thisClientName, textbuffer + 1, 32);
thisClientName[31] - 0;
NameEntryMode = GE_FALSE;
sprintf(sys_msgbuf,"Your name is now %s.", textbuffer + 1);
AddDisplayString(sys_msgbuf);
}
textbufpos = 0;
textbuffer[0] = 0;
textbuffer[1] = 0;
}
}
LastCall = TextEntryMode;
}
/////////////////////////////////////////////////////////////////////////////////////// // NetPlay Code // ///////////////////////////////////////////////////////////////////////////////////////
void StartServer(void) { if (!ServerMode && !ClientMode) {
NetMgr = geCSNetMgr_Create();
if(!geCSNetMgr_StartSession(NetMgr, "Small NetPlay Server", thisClientName)) {
AddDisplayString("Start Server command failed");
return;
}
ServerMode = GE_TRUE;
AddDisplayString("Server Started");
}
} void StopNetPlay(void) {
if (ServerMode) geCSNetMgr_StopSession(NetMgr);
geCSNetMgr_Destroy(&NetMgr);
AddDisplayString("NetPlay Shutdown.");
ServerMode = GE_FALSE;
ClientMode = GE_FALSE;
}
void ClientConnect(const char * IP) {
geCSNetMgr_NetSession *Session = NULL; int32 Session_count = 0;
NetMgr = geCSNetMgr_Create();
if (!geCSNetMgr_FindSession(NetMgr, IP, &Session, &Session_count)) {
AddDisplayString("Did not find a NetPlay Session on the server.");
StopNetPlay();
return;
}
if (!geCSNetMgr_JoinSession(NetMgr, thisClientName, &Session[0])) {
AddDisplayString("Found a session on the server, but cannot join it.");
StopNetPlay();
return;
}
AddDisplayString("Client is connected to server!!");
ClientMode = GE_TRUE;
} void ClientCycle(void) {
geCSNetMgr_NetMsgType MsgType;
int32 Size = 0;
void *msg_data = NULL;
char chat_str[1024];
while (geCSNetMgr_ReceiveFromServer(NetMgr, &MsgType, &Size, (uint8 **)(&msg_data))) {
if (MsgType == NET_MSG_USER && Size > 1) {
switch(*((uint8 *)msg_data)) {
case SUBTYPE_CHAT:
strncpy(chat_str, (char *)(msg_data) + 1, Size - 1);
AddDisplayString(chat_str);
break;
}
}
}
} void SendChatToServer(const char* chat_str) {
static char send_buf[2048];
int bufferlen = 0;
send_buf[0] = SUBTYPE_CHAT;
strcpy(send_buf + 1, chat_str); bufferlen = strlen(chat_str) + 2;
if (!geCSNetMgr_SendToServer(NetMgr, GE_FALSE, send_buf, bufferlen))
AddDisplayString("SendToServer failed.");
} void ServerCycle(void) {
uint32 MsgType;
geCSNetMgr_NetID cId;
int32 Size = 0;
uint8 *msg_data = NULL;
char chat_str[1024];
char *msg_copy = NULL;
int i;
while (geCSNetMgr_ReceiveFromClient(NetMgr, &MsgType, &cId, &Size, &msg_data)) { //make a copy of the buffer immeadiatly. It may become invalid later.
msg_copy = malloc(Size + 1);
memcpy(msg_copy, msg_data, Size); if (MsgType == NET_MSG_USER && Size > 1) {
switch(*((uint8 *)msg_copy)) {
case SUBTYPE_CHAT:
for (i = 0; i < 64; i++) {
if (clients[i] != NULL) {
if (clients[i]->Id == cId) break;
}
}
// for the moment the To parameter is ignored
// any xmit to any client, goes to all clients
// this will almost certainly change.
if (clients[i] != NULL) {
sprintf(chat_str, "%s> %s", clients[i]->Name, (char *)msg_copy + 1);
SendChatToClient(clients[i], chat_str);
} //AddDisplayString(chat_str);
//The server now runs ClientCycle
//and gets back it own chat messages thru there. break;
}
} if (MsgType == NET_MSG_CREATE_CLIENT) {
AddDisplayString("A Client has Joined!!");
RestoreIfMined();
for (i = 0; i < 64; i++) {
if (clients[i] == NULL) {
clients[i] = malloc(sizeof(geCSNetMgr_NetClient));
memcpy(clients[i], msg_copy, sizeof(geCSNetMgr_NetClient));
break;
}
}
} if (MsgType == NET_MSG_DESTROY_CLIENT) {
AddDisplayString("A Client has left.");
for (i = 0; i < 64; i++) {
if (clients[i] != NULL) {
if (clients[i]->Id == ((geCSNetMgr_NetClient *)(msg_copy))->Id) {
free(clients[i]);
clients[i] = NULL;
}
}
}
} } }
void SendChatToClient(geCSNetMgr_NetClient *client, const char *chat_str) {
static char send_buf[2048];
int bufferlen = 0;
send_buf[0] = SUBTYPE_CHAT;
strcpy(send_buf + 1, chat_str); bufferlen = strlen(chat_str) + 2;
geCSNetMgr_SendToClient(NetMgr, GE_FALSE, client->Id, send_buf, bufferlen);