/** * @sa gi.AddEvent * @param[in] mask The player bitmask to send the events to. Use @c PM_ALL to send to every connected player. */ static void SV_AddEvent (unsigned int mask, int eType, int entnum) { pending_event_t *p = &sv->pendingEvent; const int rawType = eType &~ EVENT_INSTANTLY; if (rawType >= EV_NUM_EVENTS || rawType < 0) Com_Error(ERR_DROP, "SV_AddEvent: invalid event %i", rawType); const char *eventName = eventNames[rawType].name; Com_DPrintf(DEBUG_EVENTSYS, "Event type: %s (%i - %i) (mask %s) (entnum: %i)\n", eventName, rawType, eType, Com_UnsignedIntToBinary(mask), entnum); /* finish the last event */ if (p->pending) SV_EndEvents(); /* start the new event */ p->pending = true; p->playerMask = mask; p->type = eType; p->entnum = entnum; p->buf = new dbuffer(); /* write header */ NET_WriteByte(p->buf, svc_event); NET_WriteByte(p->buf, eType); if (entnum != -1) NET_WriteShort(p->buf, entnum); }
/** * @brief Responds with teaminfo such as free team num * @sa CL_ParseTeamInfoMessage */ static void SVC_TeamInfo (struct net_stream *s) { client_t *cl; dbuffer msg; char infoGlobal[MAX_INFO_STRING] = ""; NET_WriteByte(&msg, clc_oob); NET_WriteRawString(&msg, "teaminfo\n"); Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_teamplay", Cvar_GetString("sv_teamplay")); Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_maxteams", Cvar_GetString("sv_maxteams")); Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_maxplayersperteam", Cvar_GetString("sv_maxplayersperteam")); NET_WriteString(&msg, infoGlobal); cl = NULL; while ((cl = SV_GetNextClient(cl)) != NULL) { if (cl->state >= cs_connected) { char infoPlayer[MAX_INFO_STRING] = ""; /* show players that already have a team with their teamnum */ int teamId = svs.ge->ClientGetTeamNum(cl->player); if (!teamId) teamId = TEAM_NO_ACTIVE; Info_SetValueForKeyAsInteger(infoPlayer, sizeof(infoPlayer), "cl_team", teamId); Info_SetValueForKeyAsInteger(infoPlayer, sizeof(infoPlayer), "cl_ready", svs.ge->ClientIsReady(cl->player)); Info_SetValueForKey(infoPlayer, sizeof(infoPlayer), "cl_name", cl->name); NET_WriteString(&msg, infoPlayer); } } NET_WriteByte(&msg, 0); NET_WriteMsg(s, msg); }
/** * @brief * @note Called after precache was sent from the server * @sa SV_Configstrings_f * @sa CL_Precache_f */ void CL_RequestNextDownload (void) { if (cls.state != ca_connected) { Com_Printf("CL_RequestNextDownload: Not connected (%i)\n", cls.state); return; } /* Use the map data from the server */ cl.mapTiles = SV_GetMapTiles(); cl.mapData = SV_GetMapData(); /* as a multiplayer client we have to load the map here and * check the compatibility with the server */ if (!Com_ServerState() && !CL_CanMultiplayerStart()) return; CL_ViewLoadMedia(); dbuffer msg(7); /* send begin */ /* this will activate the render process (see client state ca_active) */ NET_WriteByte(&msg, clc_stringcmd); /* see CL_StartGame */ NET_WriteString(&msg, NET_STATE_BEGIN "\n"); NET_WriteMsg(cls.netStream, msg); cls.waitingForStart = CL_Milliseconds(); S_MumbleLink(); }
/** * @brief Called during startup of mission to send team info */ void GAME_SpawnSoldiers (void) { const cgame_export_t *list = GAME_GetCurrentType(); qboolean spawnStatus; /* this callback is responsible to set up the cl.chrList */ if (list && list->Spawn) spawnStatus = list->Spawn(); else spawnStatus = GAME_Spawn(); if (spawnStatus && cl.chrList.num > 0) { struct dbuffer *msg; /* send team info */ msg = new_dbuffer(); GAME_SendCurrentTeamSpawningInfo(msg, &cl.chrList); NET_WriteMsg(cls.netStream, msg); msg = new_dbuffer(); NET_WriteByte(msg, clc_stringcmd); NET_WriteString(msg, "spawn\n"); NET_WriteMsg(cls.netStream, msg); GAME_InitializeBattlescape(&cl.chrList); } }
/** * @brief Responds with all the info that the server browser can see * @sa SV_StatusString */ static void SVC_Status (struct net_stream* s) { if (SVC_RateLimitAddress(*s)) { Com_DPrintf(DEBUG_SERVER, "SVC_Status: rate limit from %s exceeded, dropping request\n", NET_StreamToString(s)); return; } /* Allow getstatus to be DoSed relatively easily, but prevent excess outbound bandwidth usage when being flooded inbound */ if (SVC_RateLimit(&outboundLeakyBucket, 10, 100)) { Com_DPrintf(DEBUG_SERVER, "SVC_Status: rate limit exceeded, dropping request\n"); return; } dbuffer msg; NET_WriteByte(&msg, svc_oob); NET_WriteRawString(&msg, SV_CMD_PRINT "\n"); char info[MAX_INFO_STRING]; NET_WriteRawString(&msg, Cvar_Serverinfo(info, sizeof(info))); NET_WriteRawString(&msg, "\n"); client_t* cl = nullptr; while ((cl = SV_GetNextClient(cl)) != nullptr) { if (cl->state <= cs_free) continue; char player[1024]; Com_sprintf(player, sizeof(player), "%i \"%s\"\n", svs.ge->ClientGetTeamNum(*cl->player), cl->name); NET_WriteRawString(&msg, player); } NET_WriteMsg(s, msg); }
/** * @brief Called when the player is totally leaving the server, either willingly * or unwillingly. This is NOT called if the entire server is quitting * or crashing. */ void SV_DropClient (client_t * drop, const char *message) { /* add the disconnect */ dbuffer msg(2 + strlen(message)); NET_WriteByte(&msg, svc_disconnect); NET_WriteString(&msg, message); NET_WriteMsg(drop->stream, msg); SV_BroadcastPrintf(PRINT_CHAT, "%s was dropped from the server - reason: %s\n", drop->name, message); if (drop->state == cs_spawned || drop->state == cs_spawning) { /* call the prog function for removing a client */ /* this will remove the body, among other things */ const ScopedMutex scopedMutex(svs.serverMutex); svs.ge->ClientDisconnect(drop->player); } NET_StreamFinished(drop->stream); drop->stream = NULL; drop->player->inuse = false; SV_SetClientState(drop, cs_free); drop->name[0] = 0; if (svs.abandon) { int count = 0; client_t *cl = NULL; while ((cl = SV_GetNextClient(cl)) != NULL) if (cl->state >= cs_connected) count++; if (count == 0) svs.killserver = true; } }
/** * @brief Search the index in the config strings relative to a given start * @param name The value of the config string to search the index for * @param start The relative start point for the search * @param max The max. searched entries in the config string before giving up * @param create if @c true the value will get written into the config strings (appended) * @return @c 0 if not found */ static unsigned int SV_FindIndex (const char *name, int start, int max, qboolean create) { int i; if (!name || !name[0]) return 0; for (i = 1; i < max && SV_GetConfigString(start + i)[0] != '\0'; i++) { const char *configString = SV_GetConfigString(start + i); if (Q_streq(configString, name)) return i; } if (!create) return 0; if (i == max) Com_Error(ERR_DROP, "*Index: overflow '%s' start: %i, max: %i", name, start, max); SV_SetConfigString(start + i, name); if (Com_ServerState() != ss_loading) { /* send the update to everyone */ struct dbuffer *msg = new_dbuffer(); NET_WriteByte(msg, svc_configstring); NET_WriteShort(msg, start + i); NET_WriteString(msg, name); SV_Multicast(~0, msg); } return i; }
/** * @brief Sets the @c cls.state to @c ca_disconnected and informs the server * @sa CL_Drop * @note Goes from a connected state to disconnected state * Sends a disconnect message to the server * This is also called on @c Com_Error, so it shouldn't cause any errors */ void CL_Disconnect (void) { if (cls.state < ca_connecting) return; Com_Printf("Disconnecting...\n"); /* send a disconnect message to the server */ if (!Com_ServerState()) { dbuffer msg; NET_WriteByte(&msg, clc_stringcmd); NET_WriteString(&msg, NET_STATE_DISCONNECT "\n"); NET_WriteMsg(cls.netStream, msg); /* make sure, that this is send */ NET_Wait(0); } NET_StreamFinished(cls.netStream); cls.netStream = nullptr; CL_ClearState(); S_Stop(); R_ShutdownModels(false); R_FreeWorldImages(); CL_SetClientState(ca_disconnected); CL_ClearBattlescapeEvents(); GAME_EndBattlescape(); }
/** * @brief Stores a team-list (chr-list) info to buffer (which might be a network buffer, too). * @sa G_ClientTeamInfo * @sa MP_SaveTeamMultiplayerInfo * @note Called in CL_Precache_f to send the team info to server */ static void GAME_SendCurrentTeamSpawningInfo (struct dbuffer * buf, chrList_t *team) { int i; /* header */ NET_WriteByte(buf, clc_teaminfo); NET_WriteByte(buf, team->num); Com_DPrintf(DEBUG_CLIENT, "GAME_SendCurrentTeamSpawningInfo: Upload information about %i soldiers to server\n", team->num); for (i = 0; i < team->num; i++) { character_t *chr = team->chr[i]; GAME_NetSendCharacter(buf, chr); CL_NetSendInventory(buf, &chr->i); } }
/** * @brief Sends text across to be displayed if the level passes */ void SV_ClientPrintf (client_t *cl, int level, const char *fmt, ...) { if (level > cl->messagelevel) return; dbuffer msg; NET_WriteByte(&msg, svc_print); NET_WriteByte(&msg, level); va_list argptr; va_start(argptr, fmt); char str[MAX_SVC_PRINT]; NET_VPrintf(&msg, fmt, argptr, str, sizeof(str)); va_end(argptr); NET_WriteMsg(cl->stream, msg); }
/** * @brief Use this if the value might change and you need the position in the buffer * @note If someone aborts or adds an event, this pointer is of course no longer valid */ static byte* SV_WriteDummyByte (byte c) { pending_event_t *p = &sv->pendingEvent; byte *pos = (byte*) p->buf->end; NET_WriteByte(p->buf, c); assert(pos != NULL); return pos; }
/** * @note EV_ACTOR_SHOOT is using WriteDir for writing the normal, but ReadByte * for reading it - keep that in mind when you change something here */ void NET_WriteDir (dbuffer* buf, const vec3_t dir) { if (!dir) { NET_WriteByte(buf, 0); return; } float bestd = 0.0f; int best = 0; const size_t bytedirsLength = lengthof(bytedirs); for (int i = 0; i < bytedirsLength; i++) { const float d = DotProduct(dir, bytedirs[i]); if (d > bestd) { bestd = d; best = i; } } NET_WriteByte(buf, best); }
static void testDBufferNetHandling (void) { dbuffer buf; NET_WriteByte(&buf, 'b'); CU_ASSERT_EQUAL(1, buf.length()); NET_WriteShort(&buf, 128); CU_ASSERT_EQUAL(3, buf.length()); NET_WriteLong(&buf, 128); CU_ASSERT_EQUAL(7, buf.length()); }
/** * @sa gi.AddEvent * @param[in] mask The player bitmask to send the events to. Use @c PM_ALL to send to every connected player. */ static void SV_AddEvent (unsigned int mask, int eType) { pending_event_t *p = &sv->pendingEvent; Com_DPrintf(DEBUG_EVENTSYS, "Event type: %i (mask %i)\n", eType, mask); /* finish the last event */ if (p->pending) SV_EndEvents(); /* start the new event */ p->pending = qtrue; p->playerMask = mask; p->type = eType; p->buf = new_dbuffer(); /* write header */ NET_WriteByte(p->buf, svc_event); NET_WriteByte(p->buf, eType); }
/** * @brief Sends text across to be displayed if the level passes */ void SV_ClientPrintf (client_t * cl, int level, const char *fmt, ...) { va_list argptr; struct dbuffer *msg; char str[1024]; if (level > cl->messagelevel) return; msg = new_dbuffer(); NET_WriteByte(msg, svc_print); NET_WriteByte(msg, level); va_start(argptr, fmt); NET_VPrintf(msg, fmt, argptr, str, sizeof(str)); va_end(argptr); NET_WriteMsg(cl->stream, msg); }
static void testEvents (void) { const event_t events[] = {EV_RESET, EV_START, EV_ENDROUND, EV_ENDROUNDANNOUNCE}; for (int i = 0; i < lengthof(events); i++) { dbuffer buf; NET_WriteByte(&buf, events[i]); CL_ParseEvent(&buf); } CU_ASSERT_EQUAL(CL_ClearBattlescapeEvents(), lengthof(events)); }
/** * @brief Sends text to all active clients */ void SV_BroadcastPrintf (int level, const char *fmt, ...) { va_list argptr; struct dbuffer *msg; client_t *cl; char str[1024]; msg = new_dbuffer(); NET_WriteByte(msg, svc_print); NET_WriteByte(msg, level); va_start(argptr, fmt); NET_VPrintf(msg, fmt, argptr, str, sizeof(str)); va_end(argptr); /* echo to console */ if (sv_dedicated->integer) { char copy[1024]; int i; const int length = sizeof(copy) - 1; va_start(argptr, fmt); Q_vsnprintf(copy, sizeof(copy), fmt, argptr); va_end(argptr); /* mask off high bits */ for (i = 0; i < length && copy[i]; i++) copy[i] = copy[i] & 127; copy[i] = '\0'; Com_Printf("%s", copy); } cl = NULL; while ((cl = SV_GetNextClient(cl)) != NULL) { if (level > cl->messagelevel) continue; if (cl->state < cs_connected) continue; NET_WriteConstMsg(cl->stream, msg); } free_dbuffer(msg); }
/** * @brief Responds with teaminfo such as free team num * @sa CL_ParseTeamInfoMessage */ static void SVC_TeamInfo (struct net_stream* s) { if (SVC_RateLimitAddress(*s)) { Com_DPrintf(DEBUG_SERVER, "SVC_TeamInfo: rate limit from %s exceeded, dropping request\n", NET_StreamToString(s)); return; } /* Allow getinfo to be DoSed relatively easily, but prevent excess outbound bandwidth usage when being flooded inbound */ if (SVC_RateLimit(&outboundLeakyBucket)) { Com_DPrintf(DEBUG_SERVER, "SVC_TeamInfo: rate limit exceeded, dropping request\n"); return; } char infoGlobal[MAX_INFO_STRING] = ""; Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_teamplay", Cvar_GetString("sv_teamplay")); Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_maxteams", Cvar_GetString("sv_maxteams")); Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_maxplayersperteam", Cvar_GetString("sv_maxplayersperteam")); dbuffer msg; NET_WriteByte(&msg, svc_oob); NET_WriteRawString(&msg, "teaminfo\n"); NET_WriteString(&msg, infoGlobal); client_t* cl = nullptr; while ((cl = SV_GetNextClient(cl)) != nullptr) { if (cl->state < cs_connected) continue; char infoPlayer[MAX_INFO_STRING] = ""; /* show players that already have a team with their teamnum */ int teamId = svs.ge->ClientGetTeamNum(*cl->player); if (!teamId) teamId = TEAM_NO_ACTIVE; Info_SetValueForKeyAsInteger(infoPlayer, sizeof(infoPlayer), "cl_team", teamId); Info_SetValueForKeyAsInteger(infoPlayer, sizeof(infoPlayer), "cl_ready", svs.ge->ClientIsReady(cl->player)); Info_SetValueForKey(infoPlayer, sizeof(infoPlayer), "cl_name", cl->name); NET_WriteString(&msg, infoPlayer); } NET_WriteByte(&msg, 0); NET_WriteMsg(s, msg); }
static void testDBufferNetHandling (void) { dbuffer* buf = new_dbuffer(); NET_WriteByte(buf, 'b'); CU_ASSERT_EQUAL(1, dbuffer_len(buf)); NET_WriteShort(buf, 128); CU_ASSERT_EQUAL(3, dbuffer_len(buf)); NET_WriteLong(buf, 128); CU_ASSERT_EQUAL(7, dbuffer_len(buf)); free_dbuffer(buf); }
/** * @brief Writes to buffer according to format; version without syntactic sugar * for variable arguments, to call it from other functions with variable arguments * @note short and char are promoted to int when passed to variadic functions! */ void NET_vWriteFormat (dbuffer* buf, const char* format, va_list ap) { while (*format) { const char typeID = *format++; switch (typeID) { case 'c': NET_WriteChar(buf, va_arg(ap, int)); break; case 'b': NET_WriteByte(buf, va_arg(ap, int)); break; case 's': NET_WriteShort(buf, va_arg(ap, int)); break; case 'l': NET_WriteLong(buf, va_arg(ap, int)); break; case 'p': NET_WritePos(buf, va_arg(ap, float*)); break; case 'g': NET_WriteGPos(buf, va_arg(ap, byte*)); break; case 'd': NET_WriteDir(buf, va_arg(ap, float*)); break; case 'a': /* NOTE: float is promoted to double through ... */ NET_WriteAngle(buf, va_arg(ap, double)); break; case '!': break; case '&': NET_WriteString(buf, va_arg(ap, char*)); break; case '*': { const int n = va_arg(ap, int); const byte* p = va_arg(ap, byte*); NET_WriteShort(buf, n); for (int i = 0; i < n; i++) NET_WriteByte(buf, *p++); break; } default: Com_Error(ERR_DROP, "WriteFormat: Unknown type!"); } } /* Too many arguments for the given format; too few cause crash above */ #ifdef PARANOID if (!ap) Com_Error(ERR_DROP, "WriteFormat: Too many arguments!"); #endif }
/** * @sa gi.EndEvents */ static void SV_EndEvents (void) { pending_event_t *p = &sv->pendingEvent; if (!p->pending) return; NET_WriteByte(p->buf, EV_NULL); SV_Multicast(p->playerMask, p->buf); p->pending = qfalse; /* freed in SV_Multicast */ p->buf = NULL; }
/** * @brief Used by SV_Shutdown to send a final message to all * connected clients before the server goes down. * @sa SV_Shutdown */ static void SV_FinalMessage (const char *message, bool reconnect) { client_t *cl; dbuffer msg(2 + strlen(message)); if (reconnect) NET_WriteByte(&msg, svc_reconnect); else NET_WriteByte(&msg, svc_disconnect); NET_WriteString(&msg, message); cl = NULL; while ((cl = SV_GetNextClient(cl)) != NULL) if (cl->state >= cs_connected) { NET_WriteConstMsg(cl->stream, msg); NET_StreamFinished(cl->stream); cl->stream = NULL; } /* make sure, that this is send */ NET_Wait(0); }
/** * @sa gi.EndEvents */ static void SV_EndEvents (void) { pending_event_t *p = &sv->pendingEvent; if (!p->pending) return; NET_WriteByte(p->buf, EV_NULL); SV_Multicast(p->playerMask, *p->buf); p->pending = false; delete p->buf; p->buf = NULL; }
/** * @sa SV_BroadcastCommand */ void SV_ClientCommand (client_t *client, const char *fmt, ...) { va_list ap; char str[MAX_SVC_STUFFTEXT]; dbuffer msg; NET_WriteByte(&msg, svc_stufftext); va_start(ap, fmt); NET_VPrintf(&msg, fmt, ap, str, sizeof(str)); va_end(ap); NET_WriteMsg(client->stream, msg); }
/** * @sa SV_BroadcastCommand */ void SV_ClientCommand (client_t *client, const char *fmt, ...) { va_list ap; char str[1024]; struct dbuffer *msg = new_dbuffer(); NET_WriteByte(msg, svc_stufftext); va_start(ap, fmt); NET_VPrintf(msg, fmt, ap, str, sizeof(str)); va_end(ap); NET_WriteMsg(client->stream, msg); }
/** * @brief Send the userinfo to the server (and to all other clients) * when they changed (CVAR_USERINFO) * @sa CL_Connect */ static void CL_SendChangedUserinfos (void) { /* send a userinfo update if needed */ if (cls.state < ca_connected) return; if (!Com_IsUserinfoModified()) return; char info[MAX_INFO_STRING]; const char* userInfo = Cvar_Userinfo(info, sizeof(info)); dbuffer msg(strlen(userInfo) + 2); NET_WriteByte(&msg, clc_userinfo); NET_WriteString(&msg, userInfo); NET_WriteMsg(cls.netStream, msg); Com_SetUserinfoModified(false); }
static void CL_ForwardToServer_f (void) { if (cls.state != ca_connected && cls.state != ca_active) { Com_Printf("Can't \"%s\", not connected\n", Cmd_Argv(0)); return; } /* don't forward the first argument */ if (Cmd_Argc() > 1) { const int len = strlen(Cmd_Args()) + 1; dbuffer msg(len + 1); NET_WriteByte(&msg, clc_stringcmd); msg.add(Cmd_Args(), len); NET_WriteMsg(cls.netStream, msg); } }
/** * @brief Finishes the current turn of the player in battlescape and starts the turn for the next team. */ static void CL_NextRound_f (void) { /* can't end round if we are not in battlescape */ if (!CL_BattlescapeRunning()) return; /* can't end round if we're not active */ if (!cls.isOurRound()) { HUD_DisplayMessage(_("It is not your turn!")); return; } /* send endround */ dbuffer msg; NET_WriteByte(&msg, clc_endround); NET_WriteMsg(cls.netStream, msg); }
/** * @brief Finishes the current round of the player in battlescape and starts the round for the next team. */ static void CL_NextRound_f (void) { struct dbuffer *msg; /* can't end round if we are not in battlescape */ if (!CL_BattlescapeRunning()) return; /* can't end round if we're not active */ if (cls.team != cl.actTeam) { HUD_DisplayMessage(_("This isn't your round")); return; } /* send endround */ msg = new_dbuffer(); NET_WriteByte(msg, clc_endround); NET_WriteMsg(cls.netStream, msg); }
/** * @brief Send the character information to the server that is needed to spawn the soldiers of the player. * @param[out] buf The net channel buffer to write the character data into. * @param[in] chr The character to get the data from. */ static void GAME_NetSendCharacter (struct dbuffer * buf, const character_t *chr) { int j; if (!chr) Com_Error(ERR_DROP, "No character given"); if (chr->fieldSize != ACTOR_SIZE_2x2 && chr->fieldSize != ACTOR_SIZE_NORMAL) Com_Error(ERR_DROP, "Invalid character size given for character '%s': %i", chr->name, chr->fieldSize); if (chr->teamDef == NULL) Com_Error(ERR_DROP, "Character with no teamdef set (%s)", chr->name); NET_WriteByte(buf, chr->fieldSize); NET_WriteShort(buf, chr->ucn); NET_WriteString(buf, chr->name); /* model */ NET_WriteString(buf, chr->path); NET_WriteString(buf, chr->body); NET_WriteString(buf, chr->head); NET_WriteByte(buf, chr->skin); NET_WriteShort(buf, chr->HP); NET_WriteShort(buf, chr->maxHP); NET_WriteByte(buf, chr->teamDef->idx); NET_WriteByte(buf, chr->gender); NET_WriteByte(buf, chr->STUN); NET_WriteByte(buf, chr->morale); for (j = 0; j < SKILL_NUM_TYPES + 1; j++) NET_WriteLong(buf, chr->score.experience[j]); for (j = 0; j < SKILL_NUM_TYPES; j++) NET_WriteByte(buf, chr->score.skills[j]); for (j = 0; j < SKILL_NUM_TYPES + 1; j++) NET_WriteByte(buf, chr->score.initialSkills[j]); for (j = 0; j < KILLED_NUM_TYPES; j++) NET_WriteShort(buf, chr->score.kills[j]); for (j = 0; j < KILLED_NUM_TYPES; j++) NET_WriteShort(buf, chr->score.stuns[j]); NET_WriteShort(buf, chr->score.assignedMissions); }