Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@@ -37,6 +37,8 @@
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
#include "table/strings.h"
|
||||
|
||||
#include "../citymania/cm_client_list_gui.hpp"
|
||||
|
||||
#include "../safeguards.h"
|
||||
@@ -44,14 +46,12 @@
|
||||
|
||||
/* This file handles all the server-commands */
|
||||
|
||||
DECLARE_POSTFIX_INCREMENT(ClientID)
|
||||
DECLARE_INCREMENT_DECREMENT_OPERATORS(ClientID)
|
||||
/** The identifier counter for new clients (is never decreased) */
|
||||
static ClientID _network_client_id = CLIENT_ID_FIRST;
|
||||
|
||||
/** Make very sure the preconditions given in network_type.h are actually followed */
|
||||
static_assert(MAX_CLIENT_SLOTS > MAX_CLIENTS);
|
||||
/** Yes... */
|
||||
static_assert(NetworkClientSocketPool::MAX_SIZE == MAX_CLIENT_SLOTS);
|
||||
static_assert(NetworkClientSocketPool::MAX_SIZE > MAX_CLIENTS);
|
||||
|
||||
/** The pool with clients. */
|
||||
NetworkClientSocketPool _networkclientsocket_pool("NetworkClientSocket");
|
||||
@@ -87,7 +87,7 @@ struct PacketWriter : SaveFilter {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(this->mutex);
|
||||
|
||||
if (this->cs != nullptr) this->exit_sig.wait(lock);
|
||||
while (this->cs != nullptr) this->exit_sig.wait(lock);
|
||||
|
||||
/* This must all wait until the Destroy function is called. */
|
||||
|
||||
@@ -192,7 +192,6 @@ struct PacketWriter : SaveFilter {
|
||||
*/
|
||||
ServerNetworkGameSocketHandler::ServerNetworkGameSocketHandler(SOCKET s) : NetworkGameSocketHandler(s)
|
||||
{
|
||||
this->status = STATUS_INACTIVE;
|
||||
this->client_id = _network_client_id++;
|
||||
this->receive_limit = _settings_client.network.bytes_per_frame_burst;
|
||||
|
||||
@@ -261,7 +260,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::CloseConnection(NetworkRecvSta
|
||||
}
|
||||
}
|
||||
|
||||
/* If we were transfering a map to this client, stop the savegame creation
|
||||
/* If we were transferring a map to this client, stop the savegame creation
|
||||
* process and queue the next client to receive the map. */
|
||||
if (this->status == STATUS_MAP) {
|
||||
/* Ensure the saving of the game is stopped too. */
|
||||
@@ -414,22 +413,18 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendNewGRFCheck()
|
||||
Debug(net, 9, "client[{}] status = NEWGRFS_CHECK", this->client_id);
|
||||
this->status = STATUS_NEWGRFS_CHECK;
|
||||
|
||||
if (_grfconfig == nullptr) {
|
||||
if (_grfconfig.empty()) {
|
||||
/* There are no NewGRFs, so they're welcome. */
|
||||
return this->SendWelcome();
|
||||
}
|
||||
|
||||
auto p = std::make_unique<Packet>(this, PACKET_SERVER_CHECK_NEWGRFS, TCP_MTU);
|
||||
const GRFConfig *c;
|
||||
uint grf_count = 0;
|
||||
|
||||
for (c = _grfconfig; c != nullptr; c = c->next) {
|
||||
if (!HasBit(c->flags, GCF_STATIC)) grf_count++;
|
||||
}
|
||||
|
||||
uint grf_count = std::ranges::count_if(_grfconfig, [](const auto &c){ return !c->flags.Test(GRFConfigFlag::Static); });
|
||||
p->Send_uint8 (grf_count);
|
||||
for (c = _grfconfig; c != nullptr; c = c->next) {
|
||||
if (!HasBit(c->flags, GCF_STATIC)) SerializeGRFIdentifier(*p, c->ident);
|
||||
|
||||
for (const auto &c : _grfconfig) {
|
||||
if (!c->flags.Test(GRFConfigFlag::Static)) SerializeGRFIdentifier(*p, c->ident);
|
||||
}
|
||||
|
||||
this->SendPacket(std::move(p));
|
||||
@@ -706,7 +701,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendChat(NetworkAction action,
|
||||
* Send a chat message from external source.
|
||||
* @param source Name of the source this message came from.
|
||||
* @param colour TextColour to use for the message.
|
||||
* @param user Name of the user who sent the messsage.
|
||||
* @param user Name of the user who sent the message.
|
||||
* @param msg The actual message.
|
||||
*/
|
||||
NetworkRecvStatus ServerNetworkGameSocketHandler::SendExternalChat(const std::string &source, TextColour colour, const std::string &user, const std::string &msg)
|
||||
@@ -897,13 +892,13 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_IDENTIFY(Packet
|
||||
Debug(sl, 2, "Using saveload preset '%s:%d' for client %d (mask %d)", this->cm_preset.format->name, (int)this->cm_preset.compression_level, (int)this->client_id, (int)savegame_formats);
|
||||
|
||||
/* join another company does not affect these values */
|
||||
switch (playas) {
|
||||
case COMPANY_NEW_COMPANY: // New company
|
||||
switch (playas.base()) {
|
||||
case COMPANY_NEW_COMPANY.base(): // New company
|
||||
if (Company::GetNumItems() >= _settings_client.network.max_companies) {
|
||||
return this->SendError(NETWORK_ERROR_FULL);
|
||||
}
|
||||
break;
|
||||
case COMPANY_SPECTATOR: // Spectator
|
||||
case COMPANY_SPECTATOR.base(): // Spectator
|
||||
break;
|
||||
default: // Join another company (companies 1..MAX_COMPANIES (index 0..(MAX_COMPANIES-1)))
|
||||
if (!Company::IsValidHumanID(playas)) {
|
||||
@@ -933,10 +928,10 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_IDENTIFY(Packet
|
||||
NetworkClientInfo *ci = new NetworkClientInfo(this->client_id);
|
||||
this->SetInfo(ci);
|
||||
ci->join_date = TimerGameEconomy::date;
|
||||
ci->client_name = client_name;
|
||||
ci->client_name = std::move(client_name);
|
||||
ci->client_playas = playas;
|
||||
ci->public_key = this->peer_public_key;
|
||||
Debug(desync, 1, "client: {:08x}; {:02x}; {:02x}; {:02x}", TimerGameEconomy::date, TimerGameEconomy::date_fract, (int)ci->client_playas, (int)ci->index);
|
||||
Debug(desync, 1, "client: {:08x}; {:02x}; {:02x}; {:02x}", TimerGameEconomy::date, TimerGameEconomy::date_fract, ci->client_playas, ci->index);
|
||||
|
||||
/* Make sure companies to which people try to join are not autocleaned */
|
||||
Company *c = Company::GetIfValid(playas);
|
||||
@@ -948,9 +943,9 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_IDENTIFY(Packet
|
||||
static NetworkErrorCode GetErrorForAuthenticationMethod(NetworkAuthenticationMethod method)
|
||||
{
|
||||
switch (method) {
|
||||
case NETWORK_AUTH_METHOD_X25519_PAKE:
|
||||
case NetworkAuthenticationMethod::X25519_PAKE:
|
||||
return NETWORK_ERROR_WRONG_PASSWORD;
|
||||
case NETWORK_AUTH_METHOD_X25519_AUTHORIZED_KEY:
|
||||
case NetworkAuthenticationMethod::X25519_AuthorizedKey:
|
||||
return NETWORK_ERROR_NOT_ON_ALLOW_LIST;
|
||||
|
||||
default:
|
||||
@@ -968,13 +963,13 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_AUTH_RESPONSE(P
|
||||
|
||||
auto authentication_method = this->authentication_handler->GetAuthenticationMethod();
|
||||
switch (this->authentication_handler->ReceiveResponse(p)) {
|
||||
case NetworkAuthenticationServerHandler::AUTHENTICATED:
|
||||
case NetworkAuthenticationServerHandler::ResponseResult::Authenticated:
|
||||
break;
|
||||
|
||||
case NetworkAuthenticationServerHandler::RETRY_NEXT_METHOD:
|
||||
case NetworkAuthenticationServerHandler::ResponseResult::RetryNextMethod:
|
||||
return this->SendAuthRequest();
|
||||
|
||||
case NetworkAuthenticationServerHandler::NOT_AUTHENTICATED:
|
||||
case NetworkAuthenticationServerHandler::ResponseResult::NotAuthenticated:
|
||||
default:
|
||||
return this->SendError(GetErrorForAuthenticationMethod(authentication_method));
|
||||
}
|
||||
@@ -1095,12 +1090,12 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_COMMAND(Packet
|
||||
}
|
||||
|
||||
|
||||
if ((GetCommandFlags(cp.cmd) & CMD_SERVER) && ci->client_id != CLIENT_ID_SERVER) {
|
||||
if (GetCommandFlags(cp.cmd).Test(CommandFlag::Server) && ci->client_id != CLIENT_ID_SERVER) {
|
||||
IConsolePrint(CC_WARNING, "Kicking client #{} (IP: {}) due to calling a server only command {}.", ci->client_id, this->GetClientIP(), cp.cmd);
|
||||
return this->SendError(NETWORK_ERROR_KICKED);
|
||||
}
|
||||
|
||||
if ((GetCommandFlags(cp.cmd) & CMD_SPECTATOR) == 0 && !Company::IsValidID(cp.company) && ci->client_id != CLIENT_ID_SERVER) {
|
||||
if (!GetCommandFlags(cp.cmd).Test(CommandFlag::Spectator) && !Company::IsValidID(cp.company) && ci->client_id != CLIENT_ID_SERVER) {
|
||||
IConsolePrint(CC_WARNING, "Kicking client #{} (IP: {}) due to calling a non-spectator command {}.", ci->client_id, this->GetClientIP(), cp.cmd);
|
||||
return this->SendError(NETWORK_ERROR_KICKED);
|
||||
}
|
||||
@@ -1147,9 +1142,9 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_COMMAND(Packet
|
||||
if (!found) return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
|
||||
if (GetCommandFlags(cp.cmd) & CMD_CLIENT_ID) NetworkReplaceCommandClientId(cp, this->client_id);
|
||||
if (GetCommandFlags(cp.cmd).Test(CommandFlag::ClientID)) NetworkReplaceCommandClientId(cp, this->client_id);
|
||||
|
||||
this->incoming_queue.push_back(cp);
|
||||
this->incoming_queue.push_back(std::move(cp));
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
|
||||
@@ -1346,8 +1341,7 @@ void NetworkServerSendChat(NetworkAction action, DestType desttype, int dest, co
|
||||
if (ci != nullptr && show_local) {
|
||||
if (from_id == CLIENT_ID_SERVER) {
|
||||
StringID str = Company::IsValidID(ci_to->client_playas) ? STR_COMPANY_NAME : STR_NETWORK_SPECTATORS;
|
||||
SetDParam(0, ci_to->client_playas);
|
||||
std::string name = GetString(str);
|
||||
std::string name = GetString(str, ci_to->client_playas);
|
||||
NetworkTextMessage(action, GetDrawStringCompanyColour(ci_own->client_playas), true, name, msg, data);
|
||||
} else {
|
||||
for (NetworkClientSocket *cs : NetworkClientSocket::Iterate()) {
|
||||
@@ -1372,7 +1366,7 @@ void NetworkServerSendChat(NetworkAction action, DestType desttype, int dest, co
|
||||
|
||||
ci = NetworkClientInfo::GetByClientID(from_id);
|
||||
if (ci != nullptr) {
|
||||
NetworkTextMessage(action, GetDrawStringCompanyColour(ci->client_playas), false, ci->client_name, msg, data, "");
|
||||
NetworkTextMessage(action, GetDrawStringCompanyColour(ci->client_playas), false, ci->client_name, msg, data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1382,7 +1376,7 @@ void NetworkServerSendChat(NetworkAction action, DestType desttype, int dest, co
|
||||
* Send a chat message from external source.
|
||||
* @param source Name of the source this message came from.
|
||||
* @param colour TextColour to use for the message.
|
||||
* @param user Name of the user who sent the messsage.
|
||||
* @param user Name of the user who sent the message.
|
||||
* @param msg The actual message.
|
||||
*/
|
||||
void NetworkServerSendExternalChat(const std::string &source, TextColour colour, const std::string &user, const std::string &msg)
|
||||
@@ -1390,7 +1384,7 @@ void NetworkServerSendExternalChat(const std::string &source, TextColour colour,
|
||||
for (NetworkClientSocket *cs : NetworkClientSocket::Iterate()) {
|
||||
if (cs->status >= ServerNetworkGameSocketHandler::STATUS_AUTHORIZED) cs->SendExternalChat(source, colour, user, msg);
|
||||
}
|
||||
NetworkTextMessage(NETWORK_ACTION_EXTERNAL_CHAT, colour, false, user, msg, 0, source);
|
||||
NetworkTextMessage(NETWORK_ACTION_EXTERNAL_CHAT, colour, false, user, msg, source);
|
||||
}
|
||||
|
||||
NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_CHAT(Packet &p)
|
||||
@@ -1450,7 +1444,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_SET_NAME(Packet
|
||||
/* Display change */
|
||||
if (NetworkMakeClientNameUnique(client_name)) {
|
||||
NetworkTextMessage(NETWORK_ACTION_NAME_CHANGE, CC_DEFAULT, false, ci->client_name, client_name);
|
||||
ci->client_name = client_name;
|
||||
ci->client_name = std::move(client_name);
|
||||
NetworkUpdateClientInfo(ci->client_id);
|
||||
}
|
||||
}
|
||||
@@ -1505,12 +1499,11 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_MOVE(Packet &p)
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the company stats.
|
||||
* @param stats the stats to update
|
||||
* Get the company stats.
|
||||
*/
|
||||
void NetworkPopulateCompanyStats(NetworkCompanyStats *stats)
|
||||
NetworkCompanyStatsArray NetworkGetCompanyStats()
|
||||
{
|
||||
memset(stats, 0, sizeof(*stats) * MAX_COMPANIES);
|
||||
NetworkCompanyStatsArray stats = {};
|
||||
|
||||
/* Go through all vehicles and count the type of vehicles */
|
||||
for (const Vehicle *v : Vehicle::Iterate()) {
|
||||
@@ -1531,13 +1524,15 @@ void NetworkPopulateCompanyStats(NetworkCompanyStats *stats)
|
||||
if (Company::IsValidID(s->owner)) {
|
||||
NetworkCompanyStats *npi = &stats[s->owner];
|
||||
|
||||
if (s->facilities & FACIL_TRAIN) npi->num_station[NETWORK_VEH_TRAIN]++;
|
||||
if (s->facilities & FACIL_TRUCK_STOP) npi->num_station[NETWORK_VEH_LORRY]++;
|
||||
if (s->facilities & FACIL_BUS_STOP) npi->num_station[NETWORK_VEH_BUS]++;
|
||||
if (s->facilities & FACIL_AIRPORT) npi->num_station[NETWORK_VEH_PLANE]++;
|
||||
if (s->facilities & FACIL_DOCK) npi->num_station[NETWORK_VEH_SHIP]++;
|
||||
if (s->facilities.Test(StationFacility::Train)) npi->num_station[NETWORK_VEH_TRAIN]++;
|
||||
if (s->facilities.Test(StationFacility::TruckStop)) npi->num_station[NETWORK_VEH_LORRY]++;
|
||||
if (s->facilities.Test(StationFacility::BusStop)) npi->num_station[NETWORK_VEH_BUS]++;
|
||||
if (s->facilities.Test(StationFacility::Airport)) npi->num_station[NETWORK_VEH_PLANE]++;
|
||||
if (s->facilities.Test(StationFacility::Dock)) npi->num_station[NETWORK_VEH_SHIP]++;
|
||||
}
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1550,7 +1545,7 @@ void NetworkUpdateClientInfo(ClientID client_id)
|
||||
|
||||
if (ci == nullptr) return;
|
||||
|
||||
Debug(desync, 1, "client: {:08x}; {:02x}; {:02x}; {:04x}", TimerGameEconomy::date, TimerGameEconomy::date_fract, (int)ci->client_playas, client_id);
|
||||
Debug(desync, 1, "client: {:08x}; {:02x}; {:02x}; {:04x}", TimerGameEconomy::date, TimerGameEconomy::date_fract, ci->client_playas, client_id);
|
||||
|
||||
for (NetworkClientSocket *cs : NetworkClientSocket::Iterate()) {
|
||||
if (cs->status >= ServerNetworkGameSocketHandler::STATUS_AUTHORIZED) {
|
||||
@@ -1568,25 +1563,25 @@ void NetworkUpdateClientInfo(ClientID client_id)
|
||||
*/
|
||||
static void NetworkAutoCleanCompanies()
|
||||
{
|
||||
CompanyMask has_clients = 0;
|
||||
CompanyMask has_vehicles = 0;
|
||||
CompanyMask has_clients{};
|
||||
CompanyMask has_vehicles{};
|
||||
|
||||
if (!_settings_client.network.autoclean_companies) return;
|
||||
|
||||
/* Detect the active companies */
|
||||
for (const NetworkClientInfo *ci : NetworkClientInfo::Iterate()) {
|
||||
if (Company::IsValidID(ci->client_playas)) SetBit(has_clients, ci->client_playas);
|
||||
if (Company::IsValidID(ci->client_playas)) has_clients.Set(ci->client_playas);
|
||||
}
|
||||
|
||||
if (!_network_dedicated) {
|
||||
const NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(CLIENT_ID_SERVER);
|
||||
assert(ci != nullptr);
|
||||
if (Company::IsValidID(ci->client_playas)) SetBit(has_clients, ci->client_playas);
|
||||
if (Company::IsValidID(ci->client_playas)) has_clients.Set(ci->client_playas);
|
||||
}
|
||||
|
||||
if (_settings_client.network.autoclean_novehicles != 0) {
|
||||
for (const Company *c : Company::Iterate()) {
|
||||
if (std::any_of(std::begin(c->group_all), std::end(c->group_all), [](const GroupStatistics &gs) { return gs.num_vehicle != 0; })) SetBit(has_vehicles, c->index);
|
||||
if (std::any_of(std::begin(c->group_all), std::end(c->group_all), [](const GroupStatistics &gs) { return gs.num_vehicle != 0; })) has_vehicles.Set(c->index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1595,7 +1590,7 @@ static void NetworkAutoCleanCompanies()
|
||||
/* Skip the non-active once */
|
||||
if (c->is_ai) continue;
|
||||
|
||||
if (!HasBit(has_clients, c->index)) {
|
||||
if (!has_clients.Test(c->index)) {
|
||||
/* The company is empty for one month more */
|
||||
if (c->months_empty != std::numeric_limits<decltype(c->months_empty)>::max()) c->months_empty++;
|
||||
|
||||
@@ -1606,7 +1601,7 @@ static void NetworkAutoCleanCompanies()
|
||||
IConsolePrint(CC_INFO, "Auto-cleaned company #{}.", c->index + 1);
|
||||
}
|
||||
/* Is the company empty for autoclean_novehicles-months, and has no vehicles? */
|
||||
if (_settings_client.network.autoclean_novehicles != 0 && c->months_empty > _settings_client.network.autoclean_novehicles && !HasBit(has_vehicles, c->index)) {
|
||||
if (_settings_client.network.autoclean_novehicles != 0 && c->months_empty > _settings_client.network.autoclean_novehicles && !has_vehicles.Test(c->index)) {
|
||||
/* Shut the company down */
|
||||
Command<CMD_COMPANY_CTRL>::Post(CCA_DELETE, c->index, CRR_AUTOCLEAN, INVALID_CLIENT_ID);
|
||||
IConsolePrint(CC_INFO, "Auto-cleaned company #{} with no vehicles.", c->index + 1);
|
||||
@@ -1893,7 +1888,7 @@ static IntervalTimer<TimerGameEconomy> _economy_network_yearly({TimerGameEconomy
|
||||
{
|
||||
if (!_network_server) return;
|
||||
|
||||
NetworkAdminUpdate(ADMIN_FREQUENCY_ANUALLY);
|
||||
NetworkAdminUpdate(AdminUpdateFrequency::Annually);
|
||||
});
|
||||
|
||||
/** Quarterly "callback". Called whenever the economy quarter changes. */
|
||||
@@ -1902,7 +1897,7 @@ static IntervalTimer<TimerGameEconomy> _network_quarterly({TimerGameEconomy::QUA
|
||||
if (!_network_server) return;
|
||||
|
||||
NetworkAutoCleanCompanies();
|
||||
NetworkAdminUpdate(ADMIN_FREQUENCY_QUARTERLY);
|
||||
NetworkAdminUpdate(AdminUpdateFrequency::Quarterly);
|
||||
});
|
||||
|
||||
/** Economy monthly "callback". Called whenever the economy month changes. */
|
||||
@@ -1911,7 +1906,7 @@ static IntervalTimer<TimerGameEconomy> _network_monthly({TimerGameEconomy::MONTH
|
||||
if (!_network_server) return;
|
||||
|
||||
NetworkAutoCleanCompanies();
|
||||
NetworkAdminUpdate(ADMIN_FREQUENCY_MONTHLY);
|
||||
NetworkAdminUpdate(AdminUpdateFrequency::Monthly);
|
||||
});
|
||||
|
||||
/** Economy weekly "callback". Called whenever the economy week changes. */
|
||||
@@ -1919,7 +1914,7 @@ static IntervalTimer<TimerGameEconomy> _network_weekly({TimerGameEconomy::WEEK,
|
||||
{
|
||||
if (!_network_server) return;
|
||||
|
||||
NetworkAdminUpdate(ADMIN_FREQUENCY_WEEKLY);
|
||||
NetworkAdminUpdate(AdminUpdateFrequency::Weekly);
|
||||
});
|
||||
|
||||
/** Daily "callback". Called whenever the economy date changes. */
|
||||
@@ -1927,7 +1922,7 @@ static IntervalTimer<TimerGameEconomy> _economy_network_daily({TimerGameEconomy:
|
||||
{
|
||||
if (!_network_server) return;
|
||||
|
||||
NetworkAdminUpdate(ADMIN_FREQUENCY_DAILY);
|
||||
NetworkAdminUpdate(AdminUpdateFrequency::Daily);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -1945,7 +1940,7 @@ void NetworkServerShowStatusToConsole()
|
||||
static const char * const stat_str[] = {
|
||||
"inactive",
|
||||
"authorizing",
|
||||
"identifing client",
|
||||
"identifying client",
|
||||
"checking NewGRFs",
|
||||
"authorized",
|
||||
"waiting",
|
||||
@@ -2014,11 +2009,17 @@ void NetworkServerDoMove(ClientID client_id, CompanyID company_id)
|
||||
cs->SendMove(client_id, company_id);
|
||||
}
|
||||
|
||||
/* announce the client's move */
|
||||
/* Announce the client's move. */
|
||||
NetworkUpdateClientInfo(client_id);
|
||||
|
||||
NetworkAction action = (company_id == COMPANY_SPECTATOR) ? NETWORK_ACTION_COMPANY_SPECTATOR : NETWORK_ACTION_COMPANY_JOIN;
|
||||
NetworkServerSendChat(action, DESTTYPE_BROADCAST, 0, "", client_id, company_id + 1);
|
||||
if (company_id == COMPANY_SPECTATOR) {
|
||||
/* The client has joined spectators. */
|
||||
NetworkServerSendChat(NETWORK_ACTION_COMPANY_SPECTATOR, DESTTYPE_BROADCAST, 0, "", client_id);
|
||||
} else {
|
||||
/* The client has joined another company. */
|
||||
std::string company_name = GetString(STR_COMPANY_NAME, company_id);
|
||||
NetworkServerSendChat(NETWORK_ACTION_COMPANY_JOIN, DESTTYPE_BROADCAST, 0, company_name, client_id);
|
||||
}
|
||||
|
||||
citymania::SetClientListDirty();
|
||||
InvalidateWindowData(WC_CLIENT_LIST, 0);
|
||||
|
||||
Reference in New Issue
Block a user