/* * Sv_New_f * * Sends the first message from the server to a connected client. * This will be sent on the initial connection and upon each server load. */ static void Sv_New_f(void) { int player_num; Com_Debug("New() from %s\n", Sv_NetaddrToString(sv_client)); if (sv_client->state != SV_CLIENT_CONNECTED) { Com_Warn("Sv_New_f: %s already spawned\n", Sv_NetaddrToString(sv_client)); return; } // demo servers will send the demo file's server info packet if (sv.state == SV_ACTIVE_DEMO) { return; } // send the server data Msg_WriteByte(&sv_client->netchan.message, SV_CMD_SERVER_DATA); Msg_WriteLong(&sv_client->netchan.message, PROTOCOL); Msg_WriteLong(&sv_client->netchan.message, svs.spawn_count); Msg_WriteLong(&sv_client->netchan.message, svs.frame_rate); Msg_WriteByte(&sv_client->netchan.message, 0); Msg_WriteString(&sv_client->netchan.message, Cvar_GetString("game")); player_num = sv_client - svs.clients; Msg_WriteShort(&sv_client->netchan.message, player_num); // send full level name Msg_WriteString(&sv_client->netchan.message, sv.config_strings[CS_NAME]); // begin fetching config_strings Msg_WriteByte(&sv_client->netchan.message, SV_CMD_CBUF_TEXT); Msg_WriteString(&sv_client->netchan.message, va("config_strings %i 0\n", svs.spawn_count)); }
/* * Sv_ConfigStrings_f */ static void Sv_ConfigStrings_f(void) { unsigned int start; Com_Debug("ConfigStrings() from %s\n", Sv_NetaddrToString(sv_client)); if (sv_client->state != SV_CLIENT_CONNECTED) { Com_Warn("Sv_ConfigStrings_f: %s already spawned\n", Sv_NetaddrToString(sv_client)); return; } // handle the case of a level changing while a client was connecting if (strtoul(Cmd_Argv(1), NULL, 0) != svs.spawn_count) { Com_Debug("Sv_ConfigStrings_f: Stale spawn count from %s\n", Sv_NetaddrToString(sv_client)); Sv_New_f(); return; } start = strtoul(Cmd_Argv(2), NULL, 0); if (start >= MAX_CONFIG_STRINGS) { // catch bad offsets Com_Warn("Sv_ConfigStrings_f: Bad config_string offset from %s\n", Sv_NetaddrToString(sv_client)); Sv_KickClient(sv_client, NULL); return; } // write a packet full of data while (sv_client->netchan.message.size < MAX_MSG_SIZE / 2 && start < MAX_CONFIG_STRINGS) { if (sv.config_strings[start][0]) { Msg_WriteByte(&sv_client->netchan.message, SV_CMD_CONFIG_STRING); Msg_WriteShort(&sv_client->netchan.message, start); Msg_WriteString(&sv_client->netchan.message, sv.config_strings[start]); } start++; } // send next command if (start == MAX_CONFIG_STRINGS) { Msg_WriteByte(&sv_client->netchan.message, SV_CMD_CBUF_TEXT); Msg_WriteString(&sv_client->netchan.message, va("baselines %i 0\n", svs.spawn_count)); } else { Msg_WriteByte(&sv_client->netchan.message, SV_CMD_CBUF_TEXT); Msg_WriteString(&sv_client->netchan.message, va("config_strings %i %i\n", svs.spawn_count, start)); } }
/* * Sv_Baselines_f */ static void Sv_Baselines_f(void) { unsigned int start; entity_state_t nullstate; entity_state_t *base; Com_Debug("Baselines() from %s\n", Sv_NetaddrToString(sv_client)); if (sv_client->state != SV_CLIENT_CONNECTED) { Com_Warn("Sv_Baselines_f: %s already spawned\n", Sv_NetaddrToString(sv_client)); return; } // handle the case of a level changing while a client was connecting if (strtoul(Cmd_Argv(1), NULL, 0) != svs.spawn_count) { Com_Debug("Sv_Baselines_f: Stale spawn count from %s\n", Sv_NetaddrToString(sv_client)); Sv_New_f(); return; } start = strtoul(Cmd_Argv(2), NULL, 0); memset(&nullstate, 0, sizeof(nullstate)); // write a packet full of data while (sv_client->netchan.message.size < MAX_MSG_SIZE / 2 && start < MAX_EDICTS) { base = &sv.baselines[start]; if (base->model1 || base->sound || base->effects) { Msg_WriteByte(&sv_client->netchan.message, SV_CMD_ENTITY_BASELINE); Msg_WriteDeltaEntity(&nullstate, base, &sv_client->netchan.message, true, true); } start++; } // send next command if (start == MAX_EDICTS) { Msg_WriteByte(&sv_client->netchan.message, SV_CMD_CBUF_TEXT); Msg_WriteString(&sv_client->netchan.message, va("precache %i\n", svs.spawn_count)); } else { Msg_WriteByte(&sv_client->netchan.message, SV_CMD_CBUF_TEXT); Msg_WriteString(&sv_client->netchan.message, va("baselines %i %i\n", svs.spawn_count, start)); } }
/* * @brief Searches sv.config_strings from the specified start, searching for the * desired name. If not found, the name can be optionally created and sent to * all connected clients. This allows the game to lazily load assets. */ static uint16_t Sv_FindIndex(const char *name, uint16_t start, uint16_t max, _Bool create) { uint16_t i; if (!name || !name[0]) return 0; for (i = 0; i < max && sv.config_strings[start + i][0]; i++) if (!g_strcmp0(sv.config_strings[start + i], name)) return i; if (!create) return 0; if (i == max) { Com_Warn("Max index for %s reached\n", name); return 0; } g_strlcpy(sv.config_strings[start + i], name, sizeof(sv.config_strings[i])); if (sv.state != SV_LOADING) { // send the update to everyone Sb_Clear(&sv.multicast); Msg_WriteChar(&sv.multicast, SV_CMD_CONFIG_STRING); Msg_WriteShort(&sv.multicast, start + i); Msg_WriteString(&sv.multicast, name); Sv_Multicast(vec3_origin, MULTICAST_ALL_R); } return i; }
/* * @brief Sends the shutdown message message to all connected clients. The message * is sent immediately, because the server could completely terminate after * returning from this function. */ static void Sv_ShutdownMessage(const char *msg, _Bool reconnect) { sv_client_t * cl; int32_t i; if (!svs.initialized) return; Sb_Clear(&net_message); if (msg) { // send message Msg_WriteByte(&net_message, SV_CMD_PRINT); Msg_WriteByte(&net_message, PRINT_HIGH); Msg_WriteString(&net_message, msg); } if (reconnect) // send reconnect Msg_WriteByte(&net_message, SV_CMD_RECONNECT); else // or just disconnect Msg_WriteByte(&net_message, SV_CMD_DISCONNECT); for (i = 0, cl = svs.clients; i < sv_max_clients->integer; i++, cl++) if (cl->state >= SV_CLIENT_CONNECTED) Netchan_Transmit(&cl->netchan, net_message.size, net_message.data); }
/* * Cl_WriteDemoHeader * * Writes server_data, config_strings, and baselines once a non-delta * compressed frame arrives from the server. */ static void Cl_WriteDemoHeader(void) { byte buffer[MAX_MSG_SIZE]; size_buf_t msg; int i; int len; entity_state_t null_state; // write out messages to hold the startup information Sb_Init(&msg, buffer, sizeof(buffer)); // write the server data Msg_WriteByte(&msg, SV_CMD_SERVER_DATA); Msg_WriteLong(&msg, PROTOCOL); Msg_WriteLong(&msg, cl.server_count); Msg_WriteLong(&msg, cl.server_frame_rate); Msg_WriteByte(&msg, 1); // demo_server byte Msg_WriteString(&msg, Cvar_GetString("game")); Msg_WriteShort(&msg, cl.player_num); Msg_WriteString(&msg, cl.config_strings[CS_NAME]); // and config_strings for (i = 0; i < MAX_CONFIG_STRINGS; i++) { if (*cl.config_strings[i] != '\0') { if (msg.size + strlen(cl.config_strings[i]) + 32 > msg.max_size) { // write it out len = LittleLong(msg.size); Fs_Write(&len, 4, 1, cls.demo_file); Fs_Write(msg.data, msg.size, 1, cls.demo_file); msg.size = 0; } Msg_WriteByte(&msg, SV_CMD_CONFIG_STRING); Msg_WriteShort(&msg, i); Msg_WriteString(&msg, cl.config_strings[i]); } } // and baselines for (i = 0; i < MAX_EDICTS; i++) { entity_state_t *ent = &cl.entities[i].baseline; if (!ent->number) continue; if (msg.size + 64 > msg.max_size) { // write it out len = LittleLong(msg.size); Fs_Write(&len, 4, 1, cls.demo_file); Fs_Write(msg.data, msg.size, 1, cls.demo_file); msg.size = 0; } memset(&null_state, 0, sizeof(null_state)); Msg_WriteByte(&msg, SV_CMD_ENTITY_BASELINE); Msg_WriteDeltaEntity(&null_state, &cl.entities[i].baseline, &msg, true, true); } Msg_WriteByte(&msg, SV_CMD_CBUF_TEXT); Msg_WriteString(&msg, "precache 0\n"); // write it to the demo file len = LittleLong(msg.size); Fs_Write(&len, 4, 1, cls.demo_file); Fs_Write(msg.data, msg.size, 1, cls.demo_file); Com_Print("Recording to %s.\n", cls.demo_path); // the rest of the demo file will be individual frames }