mirror of
https://github.com/JGRennison/OpenTTD-patches.git
synced 2024-11-04 06:00:15 +00:00
903c3aa63d
- Code has been added to the saveload code to honour the SLF_SAVE_NO and SLF_NETWORK_NO flags. SLF_NETWORK_NO just reads in the the bytestream and then discards it because that setting is not synchronised. For this the function SlSkipBytes() has been reinstated - SAVEGAME_VERSION has been changed from a constant ENUM to a constant integer. This was done for the configuration-code to be able to tell which version of a CONDVAR type to handle. As said before, because settings can be saved to the savegame, they will become conditional at some point. The configuration code always has to read the 'most recent' version. - GameOptions are saved through the new structure. It is fully compatible with any old savegame...however it is better. Because of the move to this new format we can instruct the loader to skip certain variables. Autosave for example isn't synchronised anymore (in the network). The same goes for currency and kilometers :D. That is the only functionality change this patch is supposed to have if I have written it correctly. - NOTE! Patches are still not saved so for Multiplayer to work network_client.c and network_server.c needed slight modifications.
1580 lines
46 KiB
C
1580 lines
46 KiB
C
/* $Id$ */
|
|
|
|
#include "stdafx.h"
|
|
#include "debug.h"
|
|
#include "string.h"
|
|
#include "strings.h"
|
|
#include "network_data.h"
|
|
#include "train.h"
|
|
|
|
#ifdef ENABLE_NETWORK
|
|
|
|
#include "table/strings.h"
|
|
#include "functions.h"
|
|
#include "network_server.h"
|
|
#include "network_udp.h"
|
|
#include "console.h"
|
|
#include "command.h"
|
|
#include "gfx.h"
|
|
#include "saveload.h"
|
|
#include "vehicle.h"
|
|
#include "station.h"
|
|
#include "settings.h"
|
|
#include "variables.h"
|
|
|
|
// This file handles all the server-commands
|
|
|
|
static void NetworkHandleCommandQueue(NetworkClientState* cs);
|
|
static void NetworkSendPatchSettings(NetworkClientState* cs);
|
|
|
|
void NetworkPopulateCompanyInfo(void);
|
|
|
|
// Is the network enabled?
|
|
|
|
// **********
|
|
// Sending functions
|
|
// DEF_SERVER_SEND_COMMAND has parameter: NetworkClientState *cs
|
|
// **********
|
|
|
|
DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_CLIENT_INFO)(NetworkClientState *cs, NetworkClientInfo *ci)
|
|
{
|
|
//
|
|
// Packet: SERVER_CLIENT_INFO
|
|
// Function: Sends info about a client
|
|
// Data:
|
|
// uint16: The index of the client (always unique on a server. 1 = server)
|
|
// uint8: As which player the client is playing
|
|
// String: The name of the client
|
|
// String: The unique id of the client
|
|
//
|
|
|
|
Packet *p;
|
|
|
|
if (ci->client_index != NETWORK_EMPTY_INDEX) {
|
|
p = NetworkSend_Init(PACKET_SERVER_CLIENT_INFO);
|
|
NetworkSend_uint16(p, ci->client_index);
|
|
NetworkSend_uint8 (p, ci->client_playas);
|
|
NetworkSend_string(p, ci->client_name);
|
|
NetworkSend_string(p, ci->unique_id);
|
|
|
|
NetworkSend_Packet(p, cs);
|
|
}
|
|
}
|
|
|
|
DEF_SERVER_SEND_COMMAND(PACKET_SERVER_COMPANY_INFO)
|
|
{
|
|
//
|
|
// Packet: SERVER_COMPANY_INFO
|
|
// Function: Sends info about the companies
|
|
// Data:
|
|
//
|
|
|
|
int i;
|
|
|
|
Player *player;
|
|
Packet *p;
|
|
|
|
byte active = ActivePlayerCount();
|
|
|
|
if (active == 0) {
|
|
Packet *p = NetworkSend_Init(PACKET_SERVER_COMPANY_INFO);
|
|
|
|
NetworkSend_uint8 (p, NETWORK_COMPANY_INFO_VERSION);
|
|
NetworkSend_uint8 (p, active);
|
|
|
|
NetworkSend_Packet(p, cs);
|
|
return;
|
|
}
|
|
|
|
NetworkPopulateCompanyInfo();
|
|
|
|
FOR_ALL_PLAYERS(player) {
|
|
if (!player->is_active)
|
|
continue;
|
|
|
|
p = NetworkSend_Init(PACKET_SERVER_COMPANY_INFO);
|
|
|
|
NetworkSend_uint8 (p, NETWORK_COMPANY_INFO_VERSION);
|
|
NetworkSend_uint8 (p, active);
|
|
NetworkSend_uint8 (p, player->index);
|
|
|
|
NetworkSend_string(p, _network_player_info[player->index].company_name);
|
|
NetworkSend_uint8 (p, _network_player_info[player->index].inaugurated_year);
|
|
NetworkSend_uint64(p, _network_player_info[player->index].company_value);
|
|
NetworkSend_uint64(p, _network_player_info[player->index].money);
|
|
NetworkSend_uint64(p, _network_player_info[player->index].income);
|
|
NetworkSend_uint16(p, _network_player_info[player->index].performance);
|
|
|
|
/* Send 1 if there is a passord for the company else send 0 */
|
|
if (_network_player_info[player->index].password[0] != '\0') {
|
|
NetworkSend_uint8 (p, 1);
|
|
} else {
|
|
NetworkSend_uint8 (p, 0);
|
|
}
|
|
|
|
for (i = 0; i < NETWORK_VEHICLE_TYPES; i++)
|
|
NetworkSend_uint16(p, _network_player_info[player->index].num_vehicle[i]);
|
|
|
|
for (i = 0; i < NETWORK_STATION_TYPES; i++)
|
|
NetworkSend_uint16(p, _network_player_info[player->index].num_station[i]);
|
|
|
|
if (_network_player_info[player->index].players[0] == '\0')
|
|
NetworkSend_string(p, "<none>");
|
|
else
|
|
NetworkSend_string(p, _network_player_info[player->index].players);
|
|
|
|
NetworkSend_Packet(p, cs);
|
|
}
|
|
|
|
p = NetworkSend_Init(PACKET_SERVER_COMPANY_INFO);
|
|
|
|
NetworkSend_uint8 (p, NETWORK_COMPANY_INFO_VERSION);
|
|
NetworkSend_uint8 (p, 0);
|
|
|
|
NetworkSend_Packet(p, cs);
|
|
}
|
|
|
|
DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_ERROR)(NetworkClientState *cs, NetworkErrorCode error)
|
|
{
|
|
//
|
|
// Packet: SERVER_ERROR
|
|
// Function: The client made an error
|
|
// Data:
|
|
// uint8: ErrorID (see network_data.h, NetworkErrorCode)
|
|
//
|
|
|
|
NetworkClientState *new_cs;
|
|
char str[100];
|
|
char client_name[NETWORK_CLIENT_NAME_LENGTH];
|
|
|
|
Packet *p = NetworkSend_Init(PACKET_SERVER_ERROR);
|
|
NetworkSend_uint8(p, error);
|
|
NetworkSend_Packet(p, cs);
|
|
|
|
GetString(str, STR_NETWORK_ERR_CLIENT_GENERAL + error);
|
|
|
|
// Only send when the current client was in game
|
|
if (cs->status > STATUS_AUTH) {
|
|
NetworkGetClientName(client_name, sizeof(client_name), cs);
|
|
|
|
DEBUG(net, 2) ("[NET] '%s' made an error and has been disconnected. Reason: %s", client_name, str);
|
|
|
|
NetworkTextMessage(NETWORK_ACTION_LEAVE, 1, false, client_name, "%s", str);
|
|
|
|
FOR_ALL_CLIENTS(new_cs) {
|
|
if (new_cs->status > STATUS_AUTH && new_cs != cs) {
|
|
// Some errors we filter to a more general error. Clients don't have to know the real
|
|
// reason a joining failed.
|
|
if (error == NETWORK_ERROR_NOT_AUTHORIZED || error == NETWORK_ERROR_NOT_EXPECTED || error == NETWORK_ERROR_WRONG_REVISION)
|
|
error = NETWORK_ERROR_ILLEGAL_PACKET;
|
|
|
|
SEND_COMMAND(PACKET_SERVER_ERROR_QUIT)(new_cs, cs->index, error);
|
|
}
|
|
}
|
|
} else {
|
|
DEBUG(net, 2) ("[NET] Client %d made an error and has been disconnected. Reason: %s", cs->index, str);
|
|
}
|
|
|
|
cs->quited = true;
|
|
|
|
// Make sure the data get's there before we close the connection
|
|
NetworkSend_Packets(cs);
|
|
|
|
// The client made a mistake, so drop his connection now!
|
|
NetworkCloseClient(cs);
|
|
}
|
|
|
|
DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_NEED_PASSWORD)(NetworkClientState *cs, NetworkPasswordType type)
|
|
{
|
|
//
|
|
// Packet: SERVER_NEED_PASSWORD
|
|
// Function: Indication to the client that the server needs a password
|
|
// Data:
|
|
// uint8: Type of password
|
|
//
|
|
|
|
Packet *p = NetworkSend_Init(PACKET_SERVER_NEED_PASSWORD);
|
|
NetworkSend_uint8(p, type);
|
|
NetworkSend_Packet(p, cs);
|
|
}
|
|
|
|
DEF_SERVER_SEND_COMMAND(PACKET_SERVER_WELCOME)
|
|
{
|
|
//
|
|
// Packet: SERVER_WELCOME
|
|
// Function: The client is joined and ready to receive his map
|
|
// Data:
|
|
// uint16: Own ClientID
|
|
//
|
|
|
|
Packet *p;
|
|
const NetworkClientState *new_cs;
|
|
|
|
// Invalid packet when status is AUTH or higher
|
|
if (cs->status >= STATUS_AUTH) return;
|
|
|
|
cs->status = STATUS_AUTH;
|
|
_network_game_info.clients_on++;
|
|
|
|
p = NetworkSend_Init(PACKET_SERVER_WELCOME);
|
|
NetworkSend_uint16(p, cs->index);
|
|
NetworkSend_Packet(p, cs);
|
|
|
|
// Transmit info about all the active clients
|
|
FOR_ALL_CLIENTS(new_cs) {
|
|
if (new_cs != cs && new_cs->status > STATUS_AUTH)
|
|
SEND_COMMAND(PACKET_SERVER_CLIENT_INFO)(cs, DEREF_CLIENT_INFO(new_cs));
|
|
}
|
|
// Also send the info of the server
|
|
SEND_COMMAND(PACKET_SERVER_CLIENT_INFO)(cs, NetworkFindClientInfoFromIndex(NETWORK_SERVER_INDEX));
|
|
}
|
|
|
|
DEF_SERVER_SEND_COMMAND(PACKET_SERVER_WAIT)
|
|
{
|
|
//
|
|
// Packet: PACKET_SERVER_WAIT
|
|
// Function: The client can not receive the map at the moment because
|
|
// someone else is already receiving the map
|
|
// Data:
|
|
// uint8: Clients awaiting map
|
|
//
|
|
int waiting = 0;
|
|
NetworkClientState *new_cs;
|
|
Packet *p;
|
|
|
|
// Count how many players are waiting in the queue
|
|
FOR_ALL_CLIENTS(new_cs) {
|
|
if (new_cs->status == STATUS_MAP_WAIT)
|
|
waiting++;
|
|
}
|
|
|
|
p = NetworkSend_Init(PACKET_SERVER_WAIT);
|
|
NetworkSend_uint8(p, waiting);
|
|
NetworkSend_Packet(p, cs);
|
|
}
|
|
|
|
// This sends the map to the client
|
|
DEF_SERVER_SEND_COMMAND(PACKET_SERVER_MAP)
|
|
{
|
|
//
|
|
// Packet: SERVER_MAP
|
|
// Function: Sends the map to the client, or a part of it (it is splitted in
|
|
// a lot of multiple packets)
|
|
// Data:
|
|
// uint8: packet-type (MAP_PACKET_START, MAP_PACKET_NORMAL and MAP_PACKET_END)
|
|
// if MAP_PACKET_START:
|
|
// uint32: The current FrameCounter
|
|
// if MAP_PACKET_NORMAL:
|
|
// piece of the map (till max-size of packet)
|
|
// if MAP_PACKET_END:
|
|
// uint32: seed0 of player
|
|
// uint32: seed1 of player
|
|
// last 2 are repeated MAX_PLAYERS time
|
|
//
|
|
|
|
char filename[256];
|
|
static FILE *file_pointer;
|
|
static uint sent_packets; // How many packets we did send succecfully last time
|
|
|
|
if (cs->status < STATUS_AUTH) {
|
|
// Illegal call, return error and ignore the packet
|
|
SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NOT_AUTHORIZED);
|
|
return;
|
|
}
|
|
if (cs->status == STATUS_AUTH) {
|
|
Packet *p;
|
|
|
|
// Make a dump of the current game
|
|
sprintf(filename, "%s%snetwork_server.tmp", _path.autosave_dir, PATHSEP);
|
|
if (SaveOrLoad(filename, SL_SAVE) != SL_OK) error("network savedump failed");
|
|
|
|
file_pointer = fopen(filename, "rb");
|
|
fseek(file_pointer, 0, SEEK_END);
|
|
|
|
// Now send the _frame_counter and how many packets are coming
|
|
p = NetworkSend_Init(PACKET_SERVER_MAP);
|
|
NetworkSend_uint8(p, MAP_PACKET_START);
|
|
NetworkSend_uint32(p, _frame_counter);
|
|
NetworkSend_uint32(p, ftell(file_pointer));
|
|
NetworkSend_Packet(p, cs);
|
|
|
|
fseek(file_pointer, 0, SEEK_SET);
|
|
|
|
sent_packets = 4; // We start with trying 4 packets
|
|
|
|
cs->status = STATUS_MAP;
|
|
/* Mark the start of download */
|
|
cs->last_frame = _frame_counter;
|
|
cs->last_frame_server = _frame_counter;
|
|
}
|
|
|
|
if (cs->status == STATUS_MAP) {
|
|
uint i;
|
|
int res;
|
|
for (i = 0; i < sent_packets; i++) {
|
|
Packet *p = NetworkSend_Init(PACKET_SERVER_MAP);
|
|
NetworkSend_uint8(p, MAP_PACKET_NORMAL);
|
|
res = fread(p->buffer + p->size, 1, SEND_MTU - p->size, file_pointer);
|
|
if (ferror(file_pointer)) {
|
|
error("Error reading temporary network savegame!");
|
|
}
|
|
p->size += res;
|
|
NetworkSend_Packet(p, cs);
|
|
if (feof(file_pointer)) {
|
|
// Done reading!
|
|
Packet *p;
|
|
|
|
// XXX - Delete this when patch-settings are saved in-game
|
|
NetworkSendPatchSettings(cs);
|
|
|
|
p = NetworkSend_Init(PACKET_SERVER_MAP);
|
|
NetworkSend_uint8(p, MAP_PACKET_END);
|
|
NetworkSend_Packet(p, cs);
|
|
|
|
// Set the status to DONE_MAP, no we will wait for the client
|
|
// to send it is ready (maybe that happens like never ;))
|
|
cs->status = STATUS_DONE_MAP;
|
|
fclose(file_pointer);
|
|
|
|
{
|
|
NetworkClientState *new_cs;
|
|
bool new_map_client = false;
|
|
// Check if there is a client waiting for receiving the map
|
|
// and start sending him the map
|
|
FOR_ALL_CLIENTS(new_cs) {
|
|
if (new_cs->status == STATUS_MAP_WAIT) {
|
|
// Check if we already have a new client to send the map to
|
|
if (!new_map_client) {
|
|
// If not, this client will get the map
|
|
new_cs->status = STATUS_AUTH;
|
|
new_map_client = true;
|
|
SEND_COMMAND(PACKET_SERVER_MAP)(new_cs);
|
|
} else {
|
|
// Else, send the other clients how many clients are in front of them
|
|
SEND_COMMAND(PACKET_SERVER_WAIT)(new_cs);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// There is no more data, so break the for
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Send all packets (forced) and check if we have send it all
|
|
NetworkSend_Packets(cs);
|
|
if (cs->packet_queue == NULL) {
|
|
// All are sent, increase the sent_packets
|
|
sent_packets *= 2;
|
|
} else {
|
|
// Not everything is sent, decrease the sent_packets
|
|
if (sent_packets > 1) sent_packets /= 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_JOIN)(NetworkClientState *cs, uint16 client_index)
|
|
{
|
|
//
|
|
// Packet: SERVER_JOIN
|
|
// Function: A client is joined (all active clients receive this after a
|
|
// PACKET_CLIENT_MAP_OK) Mostly what directly follows is a
|
|
// PACKET_SERVER_CLIENT_INFO
|
|
// Data:
|
|
// uint16: Client-Index
|
|
//
|
|
|
|
Packet *p = NetworkSend_Init(PACKET_SERVER_JOIN);
|
|
|
|
NetworkSend_uint16(p, client_index);
|
|
|
|
NetworkSend_Packet(p, cs);
|
|
}
|
|
|
|
|
|
DEF_SERVER_SEND_COMMAND(PACKET_SERVER_FRAME)
|
|
{
|
|
//
|
|
// Packet: SERVER_FRAME
|
|
// Function: Sends the current frame-counter to the client
|
|
// Data:
|
|
// uint32: Frame Counter
|
|
// uint32: Frame Counter Max (how far may the client walk before the server?)
|
|
// [uint32: general-seed-1]
|
|
// [uint32: general-seed-2]
|
|
// (last two depends on compile-settings, and are not default settings)
|
|
//
|
|
|
|
Packet *p = NetworkSend_Init(PACKET_SERVER_FRAME);
|
|
NetworkSend_uint32(p, _frame_counter);
|
|
NetworkSend_uint32(p, _frame_counter_max);
|
|
#ifdef ENABLE_NETWORK_SYNC_EVERY_FRAME
|
|
NetworkSend_uint32(p, _sync_seed_1);
|
|
#ifdef NETWORK_SEND_DOUBLE_SEED
|
|
NetworkSend_uint32(p, _sync_seed_2);
|
|
#endif
|
|
#endif
|
|
NetworkSend_Packet(p, cs);
|
|
}
|
|
|
|
DEF_SERVER_SEND_COMMAND(PACKET_SERVER_SYNC)
|
|
{
|
|
//
|
|
// Packet: SERVER_SYNC
|
|
// Function: Sends a sync-check to the client
|
|
// Data:
|
|
// uint32: Frame Counter
|
|
// uint32: General-seed-1
|
|
// [uint32: general-seed-2]
|
|
// (last one depends on compile-settings, and are not default settings)
|
|
//
|
|
|
|
Packet *p = NetworkSend_Init(PACKET_SERVER_SYNC);
|
|
NetworkSend_uint32(p, _frame_counter);
|
|
NetworkSend_uint32(p, _sync_seed_1);
|
|
|
|
#ifdef NETWORK_SEND_DOUBLE_SEED
|
|
NetworkSend_uint32(p, _sync_seed_2);
|
|
#endif
|
|
NetworkSend_Packet(p, cs);
|
|
}
|
|
|
|
DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_COMMAND)(NetworkClientState *cs, CommandPacket *cp)
|
|
{
|
|
//
|
|
// Packet: SERVER_COMMAND
|
|
// Function: Sends a DoCommand to the client
|
|
// Data:
|
|
// uint8: PlayerID (0..MAX_PLAYERS-1)
|
|
// uint32: CommandID (see command.h)
|
|
// uint32: P1 (free variables used in DoCommand)
|
|
// uint32: P2
|
|
// uint32: Tile
|
|
// string: text
|
|
// uint8: CallBackID (see callback_table.c)
|
|
// uint32: Frame of execution
|
|
//
|
|
|
|
Packet *p = NetworkSend_Init(PACKET_SERVER_COMMAND);
|
|
|
|
NetworkSend_uint8(p, cp->player);
|
|
NetworkSend_uint32(p, cp->cmd);
|
|
NetworkSend_uint32(p, cp->p1);
|
|
NetworkSend_uint32(p, cp->p2);
|
|
NetworkSend_uint32(p, cp->tile);
|
|
NetworkSend_string(p, cp->text);
|
|
NetworkSend_uint8(p, cp->callback);
|
|
NetworkSend_uint32(p, cp->frame);
|
|
|
|
NetworkSend_Packet(p, cs);
|
|
}
|
|
|
|
DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_CHAT)(NetworkClientState *cs, NetworkAction action, uint16 client_index, bool self_send, const char *msg)
|
|
{
|
|
//
|
|
// Packet: SERVER_CHAT
|
|
// Function: Sends a chat-packet to the client
|
|
// Data:
|
|
// uint8: ActionID (see network_data.h, NetworkAction)
|
|
// uint16: Client-index
|
|
// String: Message (max MAX_TEXT_MSG_LEN)
|
|
//
|
|
|
|
Packet *p = NetworkSend_Init(PACKET_SERVER_CHAT);
|
|
|
|
NetworkSend_uint8(p, action);
|
|
NetworkSend_uint16(p, client_index);
|
|
NetworkSend_uint8(p, self_send);
|
|
NetworkSend_string(p, msg);
|
|
|
|
NetworkSend_Packet(p, cs);
|
|
}
|
|
|
|
DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_ERROR_QUIT)(NetworkClientState *cs, uint16 client_index, NetworkErrorCode errorno)
|
|
{
|
|
//
|
|
// Packet: SERVER_ERROR_QUIT
|
|
// Function: One of the clients made an error and is quiting the game
|
|
// This packet informs the other clients of that.
|
|
// Data:
|
|
// uint16: Client-index
|
|
// uint8: ErrorID (see network_data.h, NetworkErrorCode)
|
|
//
|
|
|
|
Packet *p = NetworkSend_Init(PACKET_SERVER_ERROR_QUIT);
|
|
|
|
NetworkSend_uint16(p, client_index);
|
|
NetworkSend_uint8(p, errorno);
|
|
|
|
NetworkSend_Packet(p, cs);
|
|
}
|
|
|
|
DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_QUIT)(NetworkClientState *cs, uint16 client_index, const char *leavemsg)
|
|
{
|
|
//
|
|
// Packet: SERVER_ERROR_QUIT
|
|
// Function: A client left the game, and this packets informs the other clients
|
|
// of that.
|
|
// Data:
|
|
// uint16: Client-index
|
|
// String: leave-message
|
|
//
|
|
|
|
Packet *p = NetworkSend_Init(PACKET_SERVER_QUIT);
|
|
|
|
NetworkSend_uint16(p, client_index);
|
|
NetworkSend_string(p, leavemsg);
|
|
|
|
NetworkSend_Packet(p, cs);
|
|
}
|
|
|
|
DEF_SERVER_SEND_COMMAND(PACKET_SERVER_SHUTDOWN)
|
|
{
|
|
//
|
|
// Packet: SERVER_SHUTDOWN
|
|
// Function: Let the clients know that the server is closing
|
|
// Data:
|
|
// <none>
|
|
//
|
|
|
|
Packet *p = NetworkSend_Init(PACKET_SERVER_SHUTDOWN);
|
|
NetworkSend_Packet(p, cs);
|
|
}
|
|
|
|
DEF_SERVER_SEND_COMMAND(PACKET_SERVER_NEWGAME)
|
|
{
|
|
//
|
|
// Packet: PACKET_SERVER_NEWGAME
|
|
// Function: Let the clients know that the server is loading a new map
|
|
// Data:
|
|
// <none>
|
|
//
|
|
|
|
Packet *p = NetworkSend_Init(PACKET_SERVER_NEWGAME);
|
|
NetworkSend_Packet(p, cs);
|
|
}
|
|
|
|
DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_RCON)(NetworkClientState *cs, uint16 color, const char *command)
|
|
{
|
|
Packet *p = NetworkSend_Init(PACKET_SERVER_RCON);
|
|
|
|
NetworkSend_uint16(p, color);
|
|
NetworkSend_string(p, command);
|
|
NetworkSend_Packet(p, cs);
|
|
}
|
|
|
|
// **********
|
|
// Receiving functions
|
|
// DEF_SERVER_RECEIVE_COMMAND has parameter: NetworkClientState *cs, Packet *p
|
|
// **********
|
|
|
|
DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_COMPANY_INFO)
|
|
{
|
|
SEND_COMMAND(PACKET_SERVER_COMPANY_INFO)(cs);
|
|
}
|
|
|
|
DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_JOIN)
|
|
{
|
|
char name[NETWORK_NAME_LENGTH];
|
|
char unique_id[NETWORK_NAME_LENGTH];
|
|
NetworkClientInfo *ci;
|
|
byte playas;
|
|
NetworkLanguage client_lang;
|
|
char client_revision[NETWORK_REVISION_LENGTH];
|
|
|
|
NetworkRecv_string(cs, p, client_revision, sizeof(client_revision));
|
|
|
|
#if defined(WITH_REV) || defined(WITH_REV_HACK)
|
|
// Check if the client has revision control enabled
|
|
if (strncmp(NOREV_STRING, client_revision, sizeof(client_revision)) != 0) {
|
|
if (strncmp(_network_game_info.server_revision, client_revision, sizeof(_network_game_info.server_revision) - 1) != 0) {
|
|
// Different revisions!!
|
|
SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_WRONG_REVISION);
|
|
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
NetworkRecv_string(cs, p, name, sizeof(name));
|
|
playas = NetworkRecv_uint8(cs, p);
|
|
client_lang = NetworkRecv_uint8(cs, p);
|
|
NetworkRecv_string(cs, p, unique_id, sizeof(unique_id));
|
|
|
|
if (cs->quited) return;
|
|
|
|
// join another company does not affect these values
|
|
switch (playas) {
|
|
case 0: /* New company */
|
|
if (ActivePlayerCount() >= _network_game_info.companies_max) {
|
|
SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_FULL);
|
|
return;
|
|
}
|
|
break;
|
|
case OWNER_SPECTATOR: /* Spectator */
|
|
if (NetworkSpectatorCount() >= _network_game_info.spectators_max) {
|
|
SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_FULL);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// We need a valid name.. make it Player
|
|
if (name[0] == '\0') snprintf(name, sizeof(name), "Player");
|
|
|
|
if (!NetworkFindName(name)) { // Change name if duplicate
|
|
// We could not create a name for this player
|
|
SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NAME_IN_USE);
|
|
return;
|
|
}
|
|
|
|
ci = DEREF_CLIENT_INFO(cs);
|
|
|
|
snprintf(ci->client_name, sizeof(ci->client_name), "%s", name);
|
|
snprintf(ci->unique_id, sizeof(ci->unique_id), "%s", unique_id);
|
|
ci->client_playas = playas;
|
|
ci->client_lang = client_lang;
|
|
|
|
// We now want a password from the client
|
|
// else we do not allow him in!
|
|
if (_network_game_info.use_password)
|
|
SEND_COMMAND(PACKET_SERVER_NEED_PASSWORD)(cs, NETWORK_GAME_PASSWORD);
|
|
else {
|
|
if (ci->client_playas > 0 && ci->client_playas <= MAX_PLAYERS && _network_player_info[ci->client_playas - 1].password[0] != '\0') {
|
|
SEND_COMMAND(PACKET_SERVER_NEED_PASSWORD)(cs, NETWORK_COMPANY_PASSWORD);
|
|
}
|
|
else {
|
|
SEND_COMMAND(PACKET_SERVER_WELCOME)(cs);
|
|
}
|
|
}
|
|
|
|
/* Make sure companies to who people try to join are not autocleaned */
|
|
if (playas >= 1 && playas <= MAX_PLAYERS)
|
|
_network_player_info[playas-1].months_empty = 0;
|
|
}
|
|
|
|
DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_PASSWORD)
|
|
{
|
|
NetworkPasswordType type;
|
|
char password[NETWORK_PASSWORD_LENGTH];
|
|
NetworkClientInfo *ci;
|
|
|
|
type = NetworkRecv_uint8(cs, p);
|
|
NetworkRecv_string(cs, p, password, sizeof(password));
|
|
|
|
if (cs->status == STATUS_INACTIVE && type == NETWORK_GAME_PASSWORD) {
|
|
// Check game-password
|
|
if (strncmp(password, _network_game_info.server_password, sizeof(password)) != 0) {
|
|
// Password is invalid
|
|
SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_WRONG_PASSWORD);
|
|
return;
|
|
}
|
|
|
|
ci = DEREF_CLIENT_INFO(cs);
|
|
|
|
if (ci->client_playas <= MAX_PLAYERS && _network_player_info[ci->client_playas - 1].password[0] != '\0') {
|
|
SEND_COMMAND(PACKET_SERVER_NEED_PASSWORD)(cs, NETWORK_COMPANY_PASSWORD);
|
|
return;
|
|
}
|
|
|
|
// Valid password, allow user
|
|
SEND_COMMAND(PACKET_SERVER_WELCOME)(cs);
|
|
return;
|
|
} else if (cs->status == STATUS_INACTIVE && type == NETWORK_COMPANY_PASSWORD) {
|
|
ci = DEREF_CLIENT_INFO(cs);
|
|
|
|
if (strncmp(password, _network_player_info[ci->client_playas - 1].password, sizeof(password)) != 0) {
|
|
// Password is invalid
|
|
SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_WRONG_PASSWORD);
|
|
return;
|
|
}
|
|
|
|
SEND_COMMAND(PACKET_SERVER_WELCOME)(cs);
|
|
return;
|
|
}
|
|
|
|
|
|
SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NOT_EXPECTED);
|
|
return;
|
|
}
|
|
|
|
DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_GETMAP)
|
|
{
|
|
NetworkClientState *new_cs;
|
|
|
|
// The client was never joined.. so this is impossible, right?
|
|
// Ignore the packet, give the client a warning, and close his connection
|
|
if (cs->status < STATUS_AUTH || cs->quited) {
|
|
SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NOT_AUTHORIZED);
|
|
return;
|
|
}
|
|
|
|
// Check if someone else is receiving the map
|
|
FOR_ALL_CLIENTS(new_cs) {
|
|
if (new_cs->status == STATUS_MAP) {
|
|
// Tell the new client to wait
|
|
cs->status = STATUS_MAP_WAIT;
|
|
SEND_COMMAND(PACKET_SERVER_WAIT)(cs);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// We receive a request to upload the map.. give it to the client!
|
|
SEND_COMMAND(PACKET_SERVER_MAP)(cs);
|
|
}
|
|
|
|
DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_MAP_OK)
|
|
{
|
|
// Client has the map, now start syncing
|
|
if (cs->status == STATUS_DONE_MAP && !cs->quited) {
|
|
char client_name[NETWORK_CLIENT_NAME_LENGTH];
|
|
NetworkClientState *new_cs;
|
|
|
|
NetworkGetClientName(client_name, sizeof(client_name), cs);
|
|
|
|
NetworkTextMessage(NETWORK_ACTION_JOIN, 1, false, client_name, "");
|
|
|
|
// Mark the client as pre-active, and wait for an ACK
|
|
// so we know he is done loading and in sync with us
|
|
cs->status = STATUS_PRE_ACTIVE;
|
|
NetworkHandleCommandQueue(cs);
|
|
SEND_COMMAND(PACKET_SERVER_FRAME)(cs);
|
|
SEND_COMMAND(PACKET_SERVER_SYNC)(cs);
|
|
|
|
// This is the frame the client receives
|
|
// we need it later on to make sure the client is not too slow
|
|
cs->last_frame = _frame_counter;
|
|
cs->last_frame_server = _frame_counter;
|
|
|
|
FOR_ALL_CLIENTS(new_cs) {
|
|
if (new_cs->status > STATUS_AUTH) {
|
|
SEND_COMMAND(PACKET_SERVER_CLIENT_INFO)(new_cs, DEREF_CLIENT_INFO(cs));
|
|
SEND_COMMAND(PACKET_SERVER_JOIN)(new_cs, cs->index);
|
|
}
|
|
}
|
|
|
|
if (_network_pause_on_join) {
|
|
/* Now pause the game till the client is in sync */
|
|
DoCommandP(0, 1, 0, NULL, CMD_PAUSE);
|
|
|
|
NetworkServer_HandleChat(NETWORK_ACTION_CHAT, DESTTYPE_BROADCAST, 0, "Game paused (incoming client)", NETWORK_SERVER_INDEX);
|
|
}
|
|
} else {
|
|
// Wrong status for this packet, give a warning to client, and close connection
|
|
SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NOT_EXPECTED);
|
|
}
|
|
}
|
|
|
|
/** Enforce the command flags.
|
|
* Eg a server-only command can only be executed by a server, etc.
|
|
* @param *cp the commandpacket that is going to be checked
|
|
* @param *ci client information for debugging output to console
|
|
*/
|
|
static bool CheckCommandFlags(const CommandPacket *cp, const NetworkClientInfo *ci)
|
|
{
|
|
byte flags = GetCommandFlags(cp->cmd);
|
|
|
|
if (flags & CMD_SERVER && ci->client_index != NETWORK_SERVER_INDEX) {
|
|
IConsolePrintF(_icolour_err, "WARNING: server only command from player %d (IP: %s), kicking...", ci->client_playas, GetPlayerIP(ci));
|
|
return false;
|
|
}
|
|
|
|
if (flags & CMD_OFFLINE) {
|
|
IConsolePrintF(_icolour_err, "WARNING: offline only command from player %d (IP: %s), kicking...", ci->client_playas, GetPlayerIP(ci));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/** The client has done a command and wants us to handle it
|
|
* @param *cs the connected client that has sent the command
|
|
* @param *p the packet in which the command was sent
|
|
*/
|
|
DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_COMMAND)
|
|
{
|
|
NetworkClientState *new_cs;
|
|
const NetworkClientInfo *ci;
|
|
byte callback;
|
|
|
|
CommandPacket *cp = malloc(sizeof(CommandPacket));
|
|
|
|
// The client was never joined.. so this is impossible, right?
|
|
// Ignore the packet, give the client a warning, and close his connection
|
|
if (cs->status < STATUS_DONE_MAP || cs->quited) {
|
|
SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NOT_EXPECTED);
|
|
return;
|
|
}
|
|
|
|
cp->player = NetworkRecv_uint8(cs, p);
|
|
cp->cmd = NetworkRecv_uint32(cs, p);
|
|
cp->p1 = NetworkRecv_uint32(cs, p);
|
|
cp->p2 = NetworkRecv_uint32(cs, p);
|
|
cp->tile = NetworkRecv_uint32(cs, p);
|
|
NetworkRecv_string(cs, p, cp->text, lengthof(cp->text));
|
|
|
|
callback = NetworkRecv_uint8(cs, p);
|
|
|
|
if (cs->quited) return;
|
|
|
|
ci = DEREF_CLIENT_INFO(cs);
|
|
|
|
/* Check if cp->cmd is valid */
|
|
if (!IsValidCommand(cp->cmd)) {
|
|
IConsolePrintF(_icolour_err, "WARNING: invalid command from player %d (IP: %s).", ci->client_playas, GetPlayerIP(ci));
|
|
SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NOT_EXPECTED);
|
|
return;
|
|
}
|
|
|
|
if (!CheckCommandFlags(cp, ci)) {
|
|
SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_KICKED);
|
|
return;
|
|
}
|
|
|
|
/** Only CMD_PLAYER_CTRL is always allowed, for the rest, playas needs
|
|
* to match the player in the packet. If it doesn't, the client has done
|
|
* something pretty naughty (or a bug), and will be kicked
|
|
*/
|
|
if (!(cp->cmd == CMD_PLAYER_CTRL && cp->p1 == 0) && ci->client_playas - 1 != cp->player) {
|
|
IConsolePrintF(_icolour_err, "WARNING: player %d (IP: %s) tried to execute a command as player %d, kicking...",
|
|
ci->client_playas - 1, GetPlayerIP(ci), cp->player);
|
|
SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_PLAYER_MISMATCH);
|
|
return;
|
|
}
|
|
|
|
/** @todo CMD_PLAYER_CTRL with p1 = 0 announces a new player to the server. To give the
|
|
* player the correct ID, the server injects p2 and executes the command. Any other p1
|
|
* is prohibited. Pretty ugly and should be redone together with its function.
|
|
* @see CmdPlayerCtrl() players.c:655
|
|
*/
|
|
if (cp->cmd == CMD_PLAYER_CTRL) {
|
|
if (cp->p1 != 0) {
|
|
SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_CHEATER);
|
|
return;
|
|
}
|
|
|
|
// XXX - UGLY! p2 is mis-used to get the client-id in CmdPlayerCtrl
|
|
cp->p2 = cs - _clients;
|
|
}
|
|
|
|
// The frame can be executed in the same frame as the next frame-packet
|
|
// That frame just before that frame is saved in _frame_counter_max
|
|
cp->frame = _frame_counter_max + 1;
|
|
cp->next = NULL;
|
|
|
|
// Queue the command for the clients (are send at the end of the frame
|
|
// if they can handle it ;))
|
|
FOR_ALL_CLIENTS(new_cs) {
|
|
if (new_cs->status > STATUS_AUTH) {
|
|
// Callbacks are only send back to the client who sent them in the
|
|
// first place. This filters that out.
|
|
cp->callback = (new_cs != cs) ? 0 : callback;
|
|
NetworkAddCommandQueue(new_cs, cp);
|
|
}
|
|
}
|
|
|
|
cp->callback = 0;
|
|
// Queue the command on the server
|
|
if (_local_command_queue == NULL) {
|
|
_local_command_queue = cp;
|
|
} else {
|
|
// Find last packet
|
|
CommandPacket *c = _local_command_queue;
|
|
while (c->next != NULL) c = c->next;
|
|
c->next = cp;
|
|
}
|
|
}
|
|
|
|
DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_ERROR)
|
|
{
|
|
// This packets means a client noticed an error and is reporting this
|
|
// to us. Display the error and report it to the other clients
|
|
NetworkClientState *new_cs;
|
|
byte errorno = NetworkRecv_uint8(cs, p);
|
|
char str[100];
|
|
char client_name[NETWORK_CLIENT_NAME_LENGTH];
|
|
|
|
// The client was never joined.. thank the client for the packet, but ignore it
|
|
if (cs->status < STATUS_DONE_MAP || cs->quited) {
|
|
cs->quited = true;
|
|
return;
|
|
}
|
|
|
|
NetworkGetClientName(client_name, sizeof(client_name), cs);
|
|
|
|
GetString(str, STR_NETWORK_ERR_CLIENT_GENERAL + errorno);
|
|
|
|
DEBUG(net, 2)("[NET] %s reported an error and is closing his connection (%s)", client_name, str);
|
|
|
|
NetworkTextMessage(NETWORK_ACTION_LEAVE, 1, false, client_name, "%s", str);
|
|
|
|
FOR_ALL_CLIENTS(new_cs) {
|
|
if (new_cs->status > STATUS_AUTH) {
|
|
SEND_COMMAND(PACKET_SERVER_ERROR_QUIT)(new_cs, cs->index, errorno);
|
|
}
|
|
}
|
|
|
|
cs->quited = true;
|
|
}
|
|
|
|
DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_QUIT)
|
|
{
|
|
// The client wants to leave. Display this and report it to the other
|
|
// clients.
|
|
NetworkClientState *new_cs;
|
|
char str[100];
|
|
char client_name[NETWORK_CLIENT_NAME_LENGTH];
|
|
|
|
// The client was never joined.. thank the client for the packet, but ignore it
|
|
if (cs->status < STATUS_DONE_MAP || cs->quited) {
|
|
cs->quited = true;
|
|
return;
|
|
}
|
|
|
|
NetworkRecv_string(cs, p, str, lengthof(str));
|
|
|
|
NetworkGetClientName(client_name, sizeof(client_name), cs);
|
|
|
|
NetworkTextMessage(NETWORK_ACTION_LEAVE, 1, false, client_name, "%s", str);
|
|
|
|
FOR_ALL_CLIENTS(new_cs) {
|
|
if (new_cs->status > STATUS_AUTH) {
|
|
SEND_COMMAND(PACKET_SERVER_QUIT)(new_cs, cs->index, str);
|
|
}
|
|
}
|
|
|
|
cs->quited = true;
|
|
}
|
|
|
|
DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_ACK)
|
|
{
|
|
uint32 frame = NetworkRecv_uint32(cs, p);
|
|
|
|
/* The client is trying to catch up with the server */
|
|
if (cs->status == STATUS_PRE_ACTIVE) {
|
|
/* The client is not yet catched up? */
|
|
if (frame + DAY_TICKS < _frame_counter)
|
|
return;
|
|
|
|
/* Now he is! Unpause the game */
|
|
cs->status = STATUS_ACTIVE;
|
|
|
|
if (_network_pause_on_join) {
|
|
DoCommandP(0, 0, 0, NULL, CMD_PAUSE);
|
|
NetworkServer_HandleChat(NETWORK_ACTION_CHAT, DESTTYPE_BROADCAST, 0, "Game unpaused", NETWORK_SERVER_INDEX);
|
|
}
|
|
}
|
|
|
|
// The client received the frame, make note of it
|
|
cs->last_frame = frame;
|
|
// With those 2 values we can calculate the lag realtime
|
|
cs->last_frame_server = _frame_counter;
|
|
}
|
|
|
|
|
|
|
|
void NetworkServer_HandleChat(NetworkAction action, DestType desttype, int dest, const char *msg, uint16 from_index)
|
|
{
|
|
NetworkClientState *cs;
|
|
NetworkClientInfo *ci, *ci_own, *ci_to;
|
|
|
|
switch (desttype) {
|
|
case DESTTYPE_CLIENT:
|
|
/* Are we sending to the server? */
|
|
if (dest == NETWORK_SERVER_INDEX) {
|
|
ci = NetworkFindClientInfoFromIndex(from_index);
|
|
/* Display the text locally, and that is it */
|
|
if (ci != NULL)
|
|
NetworkTextMessage(action, GetDrawStringPlayerColor(ci->client_playas-1), false, ci->client_name, "%s", msg);
|
|
} else {
|
|
/* Else find the client to send the message to */
|
|
FOR_ALL_CLIENTS(cs) {
|
|
if (cs->index == dest) {
|
|
SEND_COMMAND(PACKET_SERVER_CHAT)(cs, action, from_index, false, msg);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Display the message locally (so you know you have sent it)
|
|
if (from_index != dest) {
|
|
if (from_index == NETWORK_SERVER_INDEX) {
|
|
ci = NetworkFindClientInfoFromIndex(from_index);
|
|
ci_to = NetworkFindClientInfoFromIndex(dest);
|
|
if (ci != NULL && ci_to != NULL)
|
|
NetworkTextMessage(action, GetDrawStringPlayerColor(ci->client_playas-1), true, ci_to->client_name, "%s", msg);
|
|
} else {
|
|
FOR_ALL_CLIENTS(cs) {
|
|
if (cs->index == from_index) {
|
|
SEND_COMMAND(PACKET_SERVER_CHAT)(cs, action, dest, true, msg);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case DESTTYPE_PLAYER: {
|
|
bool show_local = true; // If this is false, the message is already displayed
|
|
// on the client who did sent it.
|
|
/* Find all clients that belong to this player */
|
|
ci_to = NULL;
|
|
FOR_ALL_CLIENTS(cs) {
|
|
ci = DEREF_CLIENT_INFO(cs);
|
|
if (ci->client_playas == dest) {
|
|
SEND_COMMAND(PACKET_SERVER_CHAT)(cs, action, from_index, false, msg);
|
|
if (cs->index == from_index) {
|
|
show_local = false;
|
|
}
|
|
ci_to = ci; // Remember a client that is in the company for company-name
|
|
}
|
|
}
|
|
|
|
ci = NetworkFindClientInfoFromIndex(from_index);
|
|
ci_own = NetworkFindClientInfoFromIndex(NETWORK_SERVER_INDEX);
|
|
if (ci != NULL && ci_own != NULL && ci_own->client_playas == dest) {
|
|
NetworkTextMessage(action, GetDrawStringPlayerColor(ci->client_playas-1), false, ci->client_name, "%s", msg);
|
|
if (from_index == NETWORK_SERVER_INDEX)
|
|
show_local = false;
|
|
ci_to = ci_own;
|
|
}
|
|
|
|
/* There is no such player */
|
|
if (ci_to == NULL) break;
|
|
|
|
// Display the message locally (so you know you have sent it)
|
|
if (ci != NULL && show_local) {
|
|
if (from_index == NETWORK_SERVER_INDEX) {
|
|
char name[NETWORK_NAME_LENGTH];
|
|
GetString(name, GetPlayer(ci_to->client_playas-1)->name_1);
|
|
NetworkTextMessage(action, GetDrawStringPlayerColor(ci_own->client_playas-1), true, name, "%s", msg);
|
|
} else {
|
|
FOR_ALL_CLIENTS(cs) {
|
|
if (cs->index == from_index) {
|
|
SEND_COMMAND(PACKET_SERVER_CHAT)(cs, action, ci_to->client_index, true, msg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
DEBUG(net, 0)("[NET][Server] Received unknown destination type %d. Doing broadcast instead.");
|
|
/* fall-through to next case */
|
|
case DESTTYPE_BROADCAST:
|
|
FOR_ALL_CLIENTS(cs) {
|
|
SEND_COMMAND(PACKET_SERVER_CHAT)(cs, action, from_index, false, msg);
|
|
}
|
|
ci = NetworkFindClientInfoFromIndex(from_index);
|
|
if (ci != NULL)
|
|
NetworkTextMessage(action, GetDrawStringPlayerColor(ci->client_playas-1), false, ci->client_name, "%s", msg);
|
|
break;
|
|
}
|
|
}
|
|
|
|
DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_CHAT)
|
|
{
|
|
NetworkAction action = NetworkRecv_uint8(cs, p);
|
|
DestType desttype = NetworkRecv_uint8(cs, p);
|
|
int dest = NetworkRecv_uint8(cs, p);
|
|
char msg[MAX_TEXT_MSG_LEN];
|
|
|
|
NetworkRecv_string(cs, p, msg, MAX_TEXT_MSG_LEN);
|
|
|
|
NetworkServer_HandleChat(action, desttype, dest, msg, cs->index);
|
|
}
|
|
|
|
DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_SET_PASSWORD)
|
|
{
|
|
char password[NETWORK_PASSWORD_LENGTH];
|
|
NetworkClientInfo *ci;
|
|
|
|
NetworkRecv_string(cs, p, password, sizeof(password));
|
|
ci = DEREF_CLIENT_INFO(cs);
|
|
|
|
if (ci->client_playas <= MAX_PLAYERS) {
|
|
ttd_strlcpy(_network_player_info[ci->client_playas - 1].password, password, sizeof(_network_player_info[ci->client_playas - 1].password));
|
|
}
|
|
}
|
|
|
|
DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_SET_NAME)
|
|
{
|
|
char client_name[NETWORK_CLIENT_NAME_LENGTH];
|
|
NetworkClientInfo *ci;
|
|
|
|
NetworkRecv_string(cs, p, client_name, sizeof(client_name));
|
|
ci = DEREF_CLIENT_INFO(cs);
|
|
|
|
if (cs->quited)
|
|
return;
|
|
|
|
if (ci != NULL) {
|
|
// Display change
|
|
if (NetworkFindName(client_name)) {
|
|
NetworkTextMessage(NETWORK_ACTION_NAME_CHANGE, 1, false, ci->client_name, "%s", client_name);
|
|
ttd_strlcpy(ci->client_name, client_name, sizeof(ci->client_name));
|
|
NetworkUpdateClientInfo(ci->client_index);
|
|
}
|
|
}
|
|
}
|
|
|
|
DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_RCON)
|
|
{
|
|
char pass[NETWORK_PASSWORD_LENGTH];
|
|
char command[NETWORK_RCONCOMMAND_LENGTH];
|
|
|
|
if (_network_game_info.rcon_password[0] == '\0')
|
|
return;
|
|
|
|
NetworkRecv_string(cs, p, pass, sizeof(pass));
|
|
NetworkRecv_string(cs, p, command, sizeof(command));
|
|
|
|
if (strncmp(pass, _network_game_info.rcon_password, sizeof(pass)) != 0) {
|
|
DEBUG(net, 0)("[RCon] Wrong password from client-id %d", cs->index);
|
|
return;
|
|
}
|
|
|
|
DEBUG(net, 0)("[RCon] Client-id %d executed: %s", cs->index, command);
|
|
|
|
_redirect_console_to_client = cs->index;
|
|
IConsoleCmdExec(command);
|
|
_redirect_console_to_client = 0;
|
|
return;
|
|
}
|
|
|
|
// The layout for the receive-functions by the server
|
|
typedef void NetworkServerPacket(NetworkClientState *cs, Packet *p);
|
|
|
|
|
|
// This array matches PacketType. At an incoming
|
|
// packet it is matches against this array
|
|
// and that way the right function to handle that
|
|
// packet is found.
|
|
static NetworkServerPacket* const _network_server_packet[] = {
|
|
NULL, /*PACKET_SERVER_FULL,*/
|
|
NULL, /*PACKET_SERVER_BANNED,*/
|
|
RECEIVE_COMMAND(PACKET_CLIENT_JOIN),
|
|
NULL, /*PACKET_SERVER_ERROR,*/
|
|
RECEIVE_COMMAND(PACKET_CLIENT_COMPANY_INFO),
|
|
NULL, /*PACKET_SERVER_COMPANY_INFO,*/
|
|
NULL, /*PACKET_SERVER_CLIENT_INFO,*/
|
|
NULL, /*PACKET_SERVER_NEED_PASSWORD,*/
|
|
RECEIVE_COMMAND(PACKET_CLIENT_PASSWORD),
|
|
NULL, /*PACKET_SERVER_WELCOME,*/
|
|
RECEIVE_COMMAND(PACKET_CLIENT_GETMAP),
|
|
NULL, /*PACKET_SERVER_WAIT,*/
|
|
NULL, /*PACKET_SERVER_MAP,*/
|
|
RECEIVE_COMMAND(PACKET_CLIENT_MAP_OK),
|
|
NULL, /*PACKET_SERVER_JOIN,*/
|
|
NULL, /*PACKET_SERVER_FRAME,*/
|
|
NULL, /*PACKET_SERVER_SYNC,*/
|
|
RECEIVE_COMMAND(PACKET_CLIENT_ACK),
|
|
RECEIVE_COMMAND(PACKET_CLIENT_COMMAND),
|
|
NULL, /*PACKET_SERVER_COMMAND,*/
|
|
RECEIVE_COMMAND(PACKET_CLIENT_CHAT),
|
|
NULL, /*PACKET_SERVER_CHAT,*/
|
|
RECEIVE_COMMAND(PACKET_CLIENT_SET_PASSWORD),
|
|
RECEIVE_COMMAND(PACKET_CLIENT_SET_NAME),
|
|
RECEIVE_COMMAND(PACKET_CLIENT_QUIT),
|
|
RECEIVE_COMMAND(PACKET_CLIENT_ERROR),
|
|
NULL, /*PACKET_SERVER_QUIT,*/
|
|
NULL, /*PACKET_SERVER_ERROR_QUIT,*/
|
|
NULL, /*PACKET_SERVER_SHUTDOWN,*/
|
|
NULL, /*PACKET_SERVER_NEWGAME,*/
|
|
NULL, /*PACKET_SERVER_RCON,*/
|
|
RECEIVE_COMMAND(PACKET_CLIENT_RCON),
|
|
};
|
|
|
|
// If this fails, check the array above with network_data.h
|
|
assert_compile(lengthof(_network_server_packet) == PACKET_END);
|
|
|
|
|
|
extern const SettingDesc _patch_settings[];
|
|
|
|
// This is a TEMPORARY solution to get the patch-settings
|
|
// to the client. When the patch-settings are saved in the savegame
|
|
// this should be removed!!
|
|
static void NetworkSendPatchSettings(NetworkClientState* cs)
|
|
{
|
|
const SettingDesc *item;
|
|
Packet *p = NetworkSend_Init(PACKET_SERVER_MAP);
|
|
NetworkSend_uint8(p, MAP_PACKET_PATCH);
|
|
// Now send all the patch-settings in a pretty order..
|
|
|
|
item = _patch_settings;
|
|
|
|
for (; item->save.cmd != SL_END; item++) {
|
|
const void *var = ini_get_variable(&item->save, &_patches);
|
|
switch (GetVarMemType(item->save.conv)) {
|
|
case SLE_VAR_BL:
|
|
case SLE_VAR_I8:
|
|
case SLE_VAR_U8:
|
|
NetworkSend_uint8(p, *(uint8 *)var);
|
|
break;
|
|
case SLE_VAR_I16:
|
|
case SLE_VAR_U16:
|
|
NetworkSend_uint16(p, *(uint16 *)var);
|
|
break;
|
|
case SLE_VAR_I32:
|
|
case SLE_VAR_U32:
|
|
NetworkSend_uint32(p, *(uint32 *)var);
|
|
break;
|
|
}
|
|
}
|
|
|
|
NetworkSend_Packet(p, cs);
|
|
}
|
|
|
|
// This update the company_info-stuff
|
|
void NetworkPopulateCompanyInfo(void)
|
|
{
|
|
char password[NETWORK_PASSWORD_LENGTH];
|
|
Player *p;
|
|
Vehicle *v;
|
|
Station *s;
|
|
NetworkClientState *cs;
|
|
NetworkClientInfo *ci;
|
|
int i;
|
|
uint16 months_empty;
|
|
|
|
FOR_ALL_PLAYERS(p) {
|
|
if (!p->is_active) {
|
|
memset(&_network_player_info[p->index], 0, sizeof(NetworkPlayerInfo));
|
|
continue;
|
|
}
|
|
|
|
// Clean the info but not the password
|
|
ttd_strlcpy(password, _network_player_info[p->index].password, sizeof(password));
|
|
months_empty = _network_player_info[p->index].months_empty;
|
|
memset(&_network_player_info[p->index], 0, sizeof(NetworkPlayerInfo));
|
|
_network_player_info[p->index].months_empty = months_empty;
|
|
ttd_strlcpy(_network_player_info[p->index].password, password, sizeof(_network_player_info[p->index].password));
|
|
|
|
// Grap the company name
|
|
SetDParam(0, p->name_1);
|
|
SetDParam(1, p->name_2);
|
|
GetString(_network_player_info[p->index].company_name, STR_JUST_STRING);
|
|
|
|
// Check the income
|
|
if (_cur_year - 1 == p->inaugurated_year)
|
|
// The player is here just 1 year, so display [2], else display[1]
|
|
for (i = 0; i < 13; i++)
|
|
_network_player_info[p->index].income -= p->yearly_expenses[2][i];
|
|
else
|
|
for (i = 0; i < 13; i++)
|
|
_network_player_info[p->index].income -= p->yearly_expenses[1][i];
|
|
|
|
// Set some general stuff
|
|
_network_player_info[p->index].inaugurated_year = p->inaugurated_year;
|
|
_network_player_info[p->index].company_value = p->old_economy[0].company_value;
|
|
_network_player_info[p->index].money = p->money64;
|
|
_network_player_info[p->index].performance = p->old_economy[0].performance_history;
|
|
}
|
|
|
|
// Go through all vehicles and count the type of vehicles
|
|
FOR_ALL_VEHICLES(v) {
|
|
if (v->owner < MAX_PLAYERS)
|
|
switch (v->type) {
|
|
case VEH_Train:
|
|
if (IsFrontEngine(v))
|
|
_network_player_info[v->owner].num_vehicle[0]++;
|
|
break;
|
|
case VEH_Road:
|
|
if (v->cargo_type != CT_PASSENGERS)
|
|
_network_player_info[v->owner].num_vehicle[1]++;
|
|
else
|
|
_network_player_info[v->owner].num_vehicle[2]++;
|
|
break;
|
|
case VEH_Aircraft:
|
|
if (v->subtype <= 2)
|
|
_network_player_info[v->owner].num_vehicle[3]++;
|
|
break;
|
|
case VEH_Ship:
|
|
_network_player_info[v->owner].num_vehicle[4]++;
|
|
break;
|
|
case VEH_Special:
|
|
case VEH_Disaster:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Go through all stations and count the types of stations
|
|
FOR_ALL_STATIONS(s) {
|
|
if (s->owner < MAX_PLAYERS) {
|
|
if ((s->facilities & FACIL_TRAIN))
|
|
_network_player_info[s->owner].num_station[0]++;
|
|
if ((s->facilities & FACIL_TRUCK_STOP))
|
|
_network_player_info[s->owner].num_station[1]++;
|
|
if ((s->facilities & FACIL_BUS_STOP))
|
|
_network_player_info[s->owner].num_station[2]++;
|
|
if ((s->facilities & FACIL_AIRPORT))
|
|
_network_player_info[s->owner].num_station[3]++;
|
|
if ((s->facilities & FACIL_DOCK))
|
|
_network_player_info[s->owner].num_station[4]++;
|
|
}
|
|
}
|
|
|
|
ci = NetworkFindClientInfoFromIndex(NETWORK_SERVER_INDEX);
|
|
// Register local player (if not dedicated)
|
|
if (ci != NULL && ci->client_playas > 0 && ci->client_playas <= MAX_PLAYERS)
|
|
ttd_strlcpy(_network_player_info[ci->client_playas-1].players, ci->client_name, sizeof(_network_player_info[ci->client_playas-1].players));
|
|
|
|
FOR_ALL_CLIENTS(cs) {
|
|
char client_name[NETWORK_CLIENT_NAME_LENGTH];
|
|
|
|
NetworkGetClientName(client_name, sizeof(client_name), cs);
|
|
|
|
ci = DEREF_CLIENT_INFO(cs);
|
|
if (ci != NULL && ci->client_playas > 0 && ci->client_playas <= MAX_PLAYERS) {
|
|
if (strlen(_network_player_info[ci->client_playas-1].players) != 0)
|
|
ttd_strlcat(_network_player_info[ci->client_playas - 1].players, ", ", lengthof(_network_player_info[ci->client_playas - 1].players));
|
|
|
|
ttd_strlcat(_network_player_info[ci->client_playas - 1].players, client_name, lengthof(_network_player_info[ci->client_playas - 1].players));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Send a packet to all clients with updated info about this client_index
|
|
void NetworkUpdateClientInfo(uint16 client_index)
|
|
{
|
|
NetworkClientState *cs;
|
|
NetworkClientInfo *ci;
|
|
|
|
ci = NetworkFindClientInfoFromIndex(client_index);
|
|
|
|
if (ci == NULL)
|
|
return;
|
|
|
|
FOR_ALL_CLIENTS(cs) {
|
|
SEND_COMMAND(PACKET_SERVER_CLIENT_INFO)(cs, ci);
|
|
}
|
|
}
|
|
|
|
extern void SwitchMode(int new_mode);
|
|
|
|
/* Check if we want to restart the map */
|
|
static void NetworkCheckRestartMap(void)
|
|
{
|
|
if (_network_restart_game_date != 0 && _cur_year + MAX_YEAR_BEGIN_REAL >= _network_restart_game_date) {
|
|
DEBUG(net, 0)("Auto-restarting map. Year %d reached.", _cur_year + MAX_YEAR_BEGIN_REAL);
|
|
|
|
_random_seeds[0][0] = Random();
|
|
_random_seeds[0][1] = InteractiveRandom();
|
|
|
|
SwitchMode(SM_NEWGAME);
|
|
}
|
|
}
|
|
|
|
/* Check if the server has autoclean_companies activated
|
|
Two things happen:
|
|
1) If a company is not protected, it is closed after 1 year (for example)
|
|
2) If a company is protected, protection is disabled after 3 years (for example)
|
|
(and item 1. happens a year later) */
|
|
static void NetworkAutoCleanCompanies(void)
|
|
{
|
|
NetworkClientState *cs;
|
|
NetworkClientInfo *ci;
|
|
Player *p;
|
|
bool clients_in_company[MAX_PLAYERS];
|
|
|
|
if (!_network_autoclean_companies)
|
|
return;
|
|
|
|
memset(clients_in_company, 0, sizeof(clients_in_company));
|
|
|
|
/* Detect the active companies */
|
|
FOR_ALL_CLIENTS(cs) {
|
|
ci = DEREF_CLIENT_INFO(cs);
|
|
if (ci->client_playas >= 1 && ci->client_playas <= MAX_PLAYERS) {
|
|
clients_in_company[ci->client_playas-1] = true;
|
|
}
|
|
}
|
|
if (!_network_dedicated) {
|
|
ci = NetworkFindClientInfoFromIndex(NETWORK_SERVER_INDEX);
|
|
if (ci->client_playas >= 1 && ci->client_playas <= MAX_PLAYERS) {
|
|
clients_in_company[ci->client_playas-1] = true;
|
|
}
|
|
}
|
|
|
|
/* Go through all the comapnies */
|
|
FOR_ALL_PLAYERS(p) {
|
|
/* Skip the non-active once */
|
|
if (!p->is_active || p->is_ai)
|
|
continue;
|
|
|
|
if (!clients_in_company[p->index]) {
|
|
/* The company is empty for one month more */
|
|
_network_player_info[p->index].months_empty++;
|
|
|
|
/* Is the company empty for autoclean_unprotected-months, and is there no protection? */
|
|
if (_network_player_info[p->index].months_empty > _network_autoclean_unprotected && _network_player_info[p->index].password[0] == '\0') {
|
|
/* Shut the company down */
|
|
DoCommandP(0, 2, p->index, NULL, CMD_PLAYER_CTRL);
|
|
IConsolePrintF(_icolour_def, "Auto-cleaned company #%d", p->index+1);
|
|
}
|
|
/* Is the compnay empty for autoclean_protected-months, and there is a protection? */
|
|
if (_network_player_info[p->index].months_empty > _network_autoclean_protected && _network_player_info[p->index].password[0] != '\0') {
|
|
/* Unprotect the company */
|
|
_network_player_info[p->index].password[0] = '\0';
|
|
IConsolePrintF(_icolour_def, "Auto-removed protection from company #%d", p->index+1);
|
|
_network_player_info[p->index].months_empty = 0;
|
|
}
|
|
} else {
|
|
/* It is not empty, reset the date */
|
|
_network_player_info[p->index].months_empty = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// This function changes new_name to a name that is unique (by adding #1 ...)
|
|
// and it returns true if that succeeded.
|
|
bool NetworkFindName(char new_name[NETWORK_CLIENT_NAME_LENGTH])
|
|
{
|
|
NetworkClientState *new_cs;
|
|
NetworkClientInfo *ci;
|
|
bool found_name = false;
|
|
byte number = 0;
|
|
char original_name[NETWORK_CLIENT_NAME_LENGTH];
|
|
|
|
// We use NETWORK_NAME_LENGTH in here, because new_name is really a pointer
|
|
ttd_strlcpy(original_name, new_name, NETWORK_CLIENT_NAME_LENGTH);
|
|
|
|
while (!found_name) {
|
|
found_name = true;
|
|
FOR_ALL_CLIENTS(new_cs) {
|
|
ci = DEREF_CLIENT_INFO(new_cs);
|
|
if (strncmp(ci->client_name, new_name, NETWORK_CLIENT_NAME_LENGTH) == 0) {
|
|
// Name already in use
|
|
found_name = false;
|
|
break;
|
|
}
|
|
}
|
|
// Check if it is the same as the server-name
|
|
ci = NetworkFindClientInfoFromIndex(NETWORK_SERVER_INDEX);
|
|
if (ci != NULL) {
|
|
if (strncmp(ci->client_name, new_name, NETWORK_CLIENT_NAME_LENGTH) == 0) {
|
|
// Name already in use
|
|
found_name = false;
|
|
}
|
|
}
|
|
|
|
if (!found_name) {
|
|
// Try a new name (<name> #1, <name> #2, and so on)
|
|
|
|
// Stop if we tried for more than 50 times..
|
|
if (number++ > 50) break;
|
|
snprintf(new_name, NETWORK_CLIENT_NAME_LENGTH, "%s #%d", original_name, number);
|
|
}
|
|
}
|
|
|
|
return found_name;
|
|
}
|
|
|
|
// Reads a packet from the stream
|
|
bool NetworkServer_ReadPackets(NetworkClientState *cs)
|
|
{
|
|
Packet *p;
|
|
NetworkRecvStatus res;
|
|
while ((p = NetworkRecv_Packet(cs, &res)) != NULL) {
|
|
byte type = NetworkRecv_uint8(cs, p);
|
|
if (type < PACKET_END && _network_server_packet[type] != NULL && !cs->quited)
|
|
_network_server_packet[type](cs, p);
|
|
else
|
|
DEBUG(net, 0)("[NET][Server] Received invalid packet type %d", type);
|
|
free(p);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Handle the local command-queue
|
|
static void NetworkHandleCommandQueue(NetworkClientState* cs)
|
|
{
|
|
CommandPacket *cp;
|
|
|
|
while ( (cp = cs->command_queue) != NULL) {
|
|
SEND_COMMAND(PACKET_SERVER_COMMAND)(cs, cp);
|
|
|
|
cs->command_queue = cp->next;
|
|
free(cp);
|
|
}
|
|
}
|
|
|
|
// This is called every tick if this is a _network_server
|
|
void NetworkServer_Tick(bool send_frame)
|
|
{
|
|
NetworkClientState *cs;
|
|
#ifndef ENABLE_NETWORK_SYNC_EVERY_FRAME
|
|
bool send_sync = false;
|
|
#endif
|
|
|
|
#ifndef ENABLE_NETWORK_SYNC_EVERY_FRAME
|
|
if (_frame_counter >= _last_sync_frame + _network_sync_freq) {
|
|
_last_sync_frame = _frame_counter;
|
|
send_sync = true;
|
|
}
|
|
#endif
|
|
|
|
// Now we are done with the frame, inform the clients that they can
|
|
// do their frame!
|
|
FOR_ALL_CLIENTS(cs) {
|
|
// Check if the speed of the client is what we can expect from a client
|
|
if (cs->status == STATUS_ACTIVE) {
|
|
// 1 lag-point per day
|
|
int lag = NetworkCalculateLag(cs) / DAY_TICKS;
|
|
if (lag > 0) {
|
|
if (lag > 3) {
|
|
// Client did still not report in after 4 game-day, drop him
|
|
// (that is, the 3 of above, + 1 before any lag is counted)
|
|
IConsolePrintF(_icolour_err,"Client #%d is dropped because the client did not respond for more than 4 game-days", cs->index);
|
|
NetworkCloseClient(cs);
|
|
continue;
|
|
}
|
|
|
|
// Report once per time we detect the lag
|
|
if (cs->lag_test == 0) {
|
|
IConsolePrintF(_icolour_warn,"[%d] Client #%d is slow, try increasing *net_frame_freq to a higher value!", _frame_counter, cs->index);
|
|
cs->lag_test = 1;
|
|
}
|
|
} else {
|
|
cs->lag_test = 0;
|
|
}
|
|
} else if (cs->status == STATUS_PRE_ACTIVE) {
|
|
int lag = NetworkCalculateLag(cs);
|
|
if (lag > _network_max_join_time) {
|
|
IConsolePrintF(_icolour_err,"Client #%d is dropped because it took longer than %d ticks for him to join", cs->index, _network_max_join_time);
|
|
NetworkCloseClient(cs);
|
|
}
|
|
}
|
|
|
|
if (cs->status >= STATUS_PRE_ACTIVE) {
|
|
// Check if we can send command, and if we have anything in the queue
|
|
NetworkHandleCommandQueue(cs);
|
|
|
|
// Send an updated _frame_counter_max to the client
|
|
if (send_frame)
|
|
SEND_COMMAND(PACKET_SERVER_FRAME)(cs);
|
|
|
|
#ifndef ENABLE_NETWORK_SYNC_EVERY_FRAME
|
|
// Send a sync-check packet
|
|
if (send_sync)
|
|
SEND_COMMAND(PACKET_SERVER_SYNC)(cs);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* See if we need to advertise */
|
|
NetworkUDPAdvertise();
|
|
}
|
|
|
|
void NetworkServerYearlyLoop(void)
|
|
{
|
|
NetworkCheckRestartMap();
|
|
}
|
|
|
|
void NetworkServerMonthlyLoop(void)
|
|
{
|
|
NetworkAutoCleanCompanies();
|
|
}
|
|
|
|
#endif /* ENABLE_NETWORK */
|