#include "version.h"
#include "filesys.h"
#include "mapblock.h"
-#include "serverobject.h"
-#include "genericobject.h"
+#include "server/serveractiveobject.h"
#include "settings.h"
#include "profiler.h"
#include "log.h"
#include "mapgen/mg_biome.h"
#include "content_mapnode.h"
#include "content_nodemeta.h"
-#include "content_sao.h"
#include "content/mods.h"
#include "modchannels.h"
#include "serverlist.h"
#include "chatmessage.h"
#include "chat_interface.h"
#include "remoteplayer.h"
+#include "server/player_sao.h"
class ClientNotFoundException : public BaseException
{
{
BEGIN_DEBUG_EXCEPTION_HANDLER
+ /*
+ * The real business of the server happens on the ServerThread.
+ * How this works:
+ * AsyncRunStep() runs an actual server step as soon as enough time has
+ * passed (dedicated_server_loop keeps track of that).
+ * Receive() blocks at least(!) 30ms waiting for a packet (so this loop
+ * doesn't busy wait) and will process any remaining packets.
+ */
+
m_server->AsyncRunStep(true);
while (!stopRequested()) {
m_server->Receive();
- } catch (con::NoIncomingDataException &e) {
} catch (con::PeerNotFoundException &e) {
infostream<<"Server: PeerNotFoundException"<<std::endl;
} catch (ClientNotFoundException &e) {
for (auto &detached_inventory : m_detached_inventories) {
delete detached_inventory.second;
}
+
+ while (!m_unsent_map_edit_queue.empty()) {
+ delete m_unsent_map_edit_queue.front();
+ m_unsent_map_edit_queue.pop();
+ }
}
void Server::init()
std::vector<std::string> paths;
fs::GetRecursiveDirs(paths, g_settings->get("texture_path"));
fs::GetRecursiveDirs(paths, m_gamespec.path + DIR_DELIM + "textures");
- for (const std::string &path : paths)
- m_nodedef->applyTextureOverrides(path + DIR_DELIM + "override.txt");
+ for (const std::string &path : paths) {
+ TextureOverrideSource override_source(path + DIR_DELIM + "override.txt");
+ m_nodedef->applyTextureOverrides(override_source.getNodeTileOverrides());
+ m_itemdef->applyTextureOverrides(override_source.getItemTextureOverrides());
+ }
m_nodedef->setNodeRegistrationStatus(true);
m_clients.lock();
const RemoteClientMap &clients = m_clients.getClientList();
- ScopeProfiler sp(g_profiler, "Server: update visible objects");
-
- // Radius inside which objects are active
- static thread_local const s16 radius =
- g_settings->getS16("active_object_send_range_blocks") * MAP_BLOCKSIZE;
-
- // Radius inside which players are active
- static thread_local const bool is_transfer_limited =
- g_settings->exists("unlimited_player_transfer_distance") &&
- !g_settings->getBool("unlimited_player_transfer_distance");
- static thread_local const s16 player_transfer_dist =
- g_settings->getS16("player_transfer_distance") * MAP_BLOCKSIZE;
- s16 player_radius = player_transfer_dist;
- if (player_radius == 0 && is_transfer_limited)
- player_radius = radius;
+ ScopeProfiler sp(g_profiler, "Server: update objects within range");
for (const auto &client_it : clients) {
RemoteClient *client = client_it.second;
- // If definitions and textures have not been sent, don't
- // send objects either
if (client->getState() < CS_DefinitionsSent)
continue;
- RemotePlayer *player = m_env->getPlayer(client->peer_id);
- if (!player) {
- // This can happen if the client timeouts somehow
+ // This can happen if the client times out somehow
+ if (!m_env->getPlayer(client->peer_id))
continue;
- }
- PlayerSAO *playersao = player->getPlayerSAO();
+ PlayerSAO *playersao = getPlayerSAO(client->peer_id);
if (!playersao)
continue;
- s16 my_radius = MYMIN(radius, playersao->getWantedRange() * MAP_BLOCKSIZE);
- if (my_radius <= 0) my_radius = radius;
- //infostream << "Server: Active Radius " << my_radius << std::endl;
-
- std::queue<u16> removed_objects;
- std::queue<u16> added_objects;
- m_env->getRemovedActiveObjects(playersao, my_radius, player_radius,
- client->m_known_objects, removed_objects);
- m_env->getAddedActiveObjects(playersao, my_radius, player_radius,
- client->m_known_objects, added_objects);
-
- // Ignore if nothing happened
- if (removed_objects.empty() && added_objects.empty()) {
- continue;
- }
-
- std::string data_buffer;
-
- char buf[4];
-
- // Handle removed objects
- writeU16((u8*)buf, removed_objects.size());
- data_buffer.append(buf, 2);
- while (!removed_objects.empty()) {
- // Get object
- u16 id = removed_objects.front();
- ServerActiveObject* obj = m_env->getActiveObject(id);
-
- // Add to data buffer for sending
- writeU16((u8*)buf, id);
- data_buffer.append(buf, 2);
-
- // Remove from known objects
- client->m_known_objects.erase(id);
-
- if(obj && obj->m_known_by_count > 0)
- obj->m_known_by_count--;
- removed_objects.pop();
- }
-
- // Handle added objects
- writeU16((u8*)buf, added_objects.size());
- data_buffer.append(buf, 2);
- while (!added_objects.empty()) {
- // Get object
- u16 id = added_objects.front();
- ServerActiveObject* obj = m_env->getActiveObject(id);
-
- // Get object type
- u8 type = ACTIVEOBJECT_TYPE_INVALID;
- if (!obj)
- warningstream << FUNCTION_NAME << ": NULL object" << std::endl;
- else
- type = obj->getSendType();
-
- // Add to data buffer for sending
- writeU16((u8*)buf, id);
- data_buffer.append(buf, 2);
- writeU8((u8*)buf, type);
- data_buffer.append(buf, 1);
-
- if(obj)
- data_buffer.append(serializeLongString(
- obj->getClientInitializationData(client->net_proto_version)));
- else
- data_buffer.append(serializeLongString(""));
-
- // Add to known objects
- client->m_known_objects.insert(id);
-
- if(obj)
- obj->m_known_by_count++;
-
- added_objects.pop();
- }
-
- u32 pktSize = SendActiveObjectRemoveAdd(client->peer_id, data_buffer);
- verbosestream << "Server: Sent object remove/add: "
- << removed_objects.size() << " removed, "
- << added_objects.size() << " added, "
- << "packet size is " << pktSize << std::endl;
+ SendActiveObjectRemoveAdd(client, playersao);
}
m_clients.unlock();
+ // Save mod storages if modified
m_mod_storage_save_timer -= dtime;
if (m_mod_storage_save_timer <= 0.0f) {
- infostream << "Saving registered mod storages." << std::endl;
m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval");
+ int n = 0;
for (std::unordered_map<std::string, ModMetadata *>::const_iterator
it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) {
if (it->second->isModified()) {
it->second->save(getModStoragePath());
+ n++;
}
}
+ if (n > 0)
+ infostream << "Saved " << n << " modified mod storages." << std::endl;
}
}
// Route data to every client
for (const auto &client_it : clients) {
RemoteClient *client = client_it.second;
+ PlayerSAO *player = getPlayerSAO(client->peer_id);
std::string reliable_data;
std::string unreliable_data;
// Go through all objects in message buffer
for (const auto &buffered_message : buffered_messages) {
- // If object is not known by client, skip it
+ // If object does not exist or is not known by client, skip it
u16 id = buffered_message.first;
- if (client->m_known_objects.find(id) == client->m_known_objects.end())
+ ServerActiveObject *sao = m_env->getActiveObject(id);
+ if (!sao || client->m_known_objects.find(id) == client->m_known_objects.end())
continue;
// Get message list of object
std::vector<ActiveObjectMessage>* list = buffered_message.second;
// Go through every message
for (const ActiveObjectMessage &aom : *list) {
+ // Send position updates to players who do not see the attachment
+ if (aom.datastring[0] == AO_CMD_UPDATE_POSITION) {
+ if (sao->getId() == player->getId())
+ continue;
+
+ // Do not send position updates for attached players
+ // as long the parent is known to the client
+ ServerActiveObject *parent = sao->getParent();
+ if (parent && client->m_known_objects.find(parent->getId()) !=
+ client->m_known_objects.end())
+ continue;
+ }
// Compose the full new data with header
std::string new_data;
// Add object id
disable_single_change_sending ? 5 : 30);
break;
case MEET_BLOCK_NODE_METADATA_CHANGED: {
- verbosestream << "Server: MEET_BLOCK_NODE_METADATA_CHANGED" << std::endl;
prof.add("MEET_BLOCK_NODE_METADATA_CHANGED", 1);
if (!event->is_private_change) {
// Don't send the change yet. Collect them to eliminate dupes.
break;
}
case MEET_OTHER:
- infostream << "Server: MEET_OTHER" << std::endl;
prof.add("MEET_OTHER", 1);
for (const v3s16 &modified_block : event->modified_blocks) {
m_clients.markBlockposAsNotSent(modified_block);
void Server::Receive()
{
- session_t peer_id = 0;
- try {
- NetworkPacket pkt;
- m_con->Receive(&pkt);
- peer_id = pkt.getPeerId();
- ProcessData(&pkt);
- } catch (const con::InvalidIncomingDataException &e) {
- infostream << "Server::Receive(): InvalidIncomingDataException: what()="
- << e.what() << std::endl;
- } catch (const SerializationError &e) {
- infostream << "Server::Receive(): SerializationError: what()="
- << e.what() << std::endl;
- } catch (const ClientStateError &e) {
- errorstream << "ProcessData: peer=" << peer_id << e.what() << std::endl;
- DenyAccess_Legacy(peer_id, L"Your client sent something server didn't expect."
- L"Try reconnecting or updating your client");
- } catch (const con::PeerNotFoundException &e) {
- // Do nothing
+ NetworkPacket pkt;
+ session_t peer_id;
+ bool first = true;
+ for (;;) {
+ pkt.clear();
+ peer_id = 0;
+ try {
+ /*
+ In the first iteration *wait* for a packet, afterwards process
+ all packets that are immediately available (no waiting).
+ */
+ if (first) {
+ m_con->Receive(&pkt);
+ first = false;
+ } else {
+ if (!m_con->TryReceive(&pkt))
+ return;
+ }
+
+ peer_id = pkt.getPeerId();
+ ProcessData(&pkt);
+ } catch (const con::InvalidIncomingDataException &e) {
+ infostream << "Server::Receive(): InvalidIncomingDataException: what()="
+ << e.what() << std::endl;
+ } catch (const SerializationError &e) {
+ infostream << "Server::Receive(): SerializationError: what()="
+ << e.what() << std::endl;
+ } catch (const ClientStateError &e) {
+ errorstream << "ProcessData: peer=" << peer_id << " what()="
+ << e.what() << std::endl;
+ DenyAccess_Legacy(peer_id, L"Your client sent something server didn't expect."
+ L"Try reconnecting or updating your client");
+ } catch (const con::PeerNotFoundException &e) {
+ // Do nothing
+ } catch (const con::NoIncomingDataException &e) {
+ return;
+ }
}
}
SendPlayerInventoryFormspec(peer_id);
// Send inventory
- SendInventory(playersao);
+ SendInventory(playersao, false);
// Send HP or death screen
if (playersao->isDead())
// Send Breath
SendPlayerBreath(playersao);
- Address addr = getPeerAddress(player->getPeerId());
- std::string ip_str = addr.serializeString();
- actionstream<<player->getName() <<" [" << ip_str << "] joins game. " << std::endl;
/*
Print out action
*/
{
+ Address addr = getPeerAddress(player->getPeerId());
+ std::string ip_str = addr.serializeString();
const std::vector<std::string> &names = m_clients.getPlayerNames();
- actionstream << player->getName() << " joins game. List of players: ";
+ actionstream << player->getName() << " [" << ip_str << "] joins game. List of players: ";
for (const std::string &name : names) {
actionstream << name << " ";
return playersao;
}
-inline void Server::handleCommand(NetworkPacket* pkt)
+inline void Server::handleCommand(NetworkPacket *pkt)
{
- const ToServerCommandHandler& opHandle = toServerCommandTable[pkt->getCommand()];
+ const ToServerCommandHandler &opHandle = toServerCommandTable[pkt->getCommand()];
(this->*opHandle.handler)(pkt);
}
m_time_of_day_send_timer = 0;
}
-void Server::onMapEditEvent(MapEditEvent *event)
+void Server::onMapEditEvent(const MapEditEvent &event)
{
- if (m_ignore_map_edit_events_area.contains(event->getArea()))
+ if (m_ignore_map_edit_events_area.contains(event.getArea()))
return;
- MapEditEvent *e = event->clone();
- m_unsent_map_edit_queue.push(e);
+
+ m_unsent_map_edit_queue.push(new MapEditEvent(event));
}
Inventory* Server::getInventory(const InventoryLocation &loc)
return NULL;
}
-void Server::setInventoryModified(const InventoryLocation &loc, bool playerSend)
+void Server::setInventoryModified(const InventoryLocation &loc)
{
switch(loc.type){
case InventoryLocation::UNDEFINED:
break;
case InventoryLocation::PLAYER:
{
- if (!playerSend)
- return;
RemotePlayer *player = m_env->getPlayer(loc.name.c_str());
if (!player)
return;
- PlayerSAO *playersao = player->getPlayerSAO();
- if(!playersao)
- return;
-
- SendInventory(playersao);
+ player->setModified(true);
+ player->inventory.setModified(true);
+ // Updates are sent in ServerEnvironment::step()
}
break;
case InventoryLocation::NODEMETA:
MapEditEvent event;
event.type = MEET_BLOCK_NODE_METADATA_CHANGED;
event.p = loc.p;
- m_env->getMap().dispatchEvent(&event);
+ m_env->getMap().dispatchEvent(event);
}
break;
case InventoryLocation::DETACHED:
{
- sendDetachedInventory(loc.name,PEER_ID_INEXISTENT);
+ // Updates are sent in ServerEnvironment::step()
}
break;
default:
*major = client->getMajor();
*minor = client->getMinor();
*patch = client->getPatch();
- *vers_string = client->getPatch();
+ *vers_string = client->getFull();
m_clients.unlock();
Non-static send methods
*/
-void Server::SendInventory(PlayerSAO* playerSAO)
+void Server::SendInventory(PlayerSAO *sao, bool incremental)
{
- UpdateCrafting(playerSAO->getPlayer());
+ RemotePlayer *player = sao->getPlayer();
+
+ // Do not send new format to old clients
+ incremental &= player->protocol_version >= 38;
+
+ UpdateCrafting(player);
/*
Serialize it
*/
- NetworkPacket pkt(TOCLIENT_INVENTORY, 0, playerSAO->getPeerID());
+ NetworkPacket pkt(TOCLIENT_INVENTORY, 0, sao->getPeerID());
- std::ostringstream os;
- playerSAO->getInventory()->serialize(os);
-
- std::string s = os.str();
+ std::ostringstream os(std::ios::binary);
+ sao->getInventory()->serialize(os, incremental);
+ sao->getInventory()->setModified(false);
+ player->setModified(true);
+ const std::string &s = os.str();
pkt.putRawString(s.c_str(), s.size());
Send(&pkt);
}
void Server::SendShowFormspecMessage(session_t peer_id, const std::string &formspec,
const std::string &formname)
{
- NetworkPacket pkt(TOCLIENT_SHOW_FORMSPEC, 0 , peer_id);
+ NetworkPacket pkt(TOCLIENT_SHOW_FORMSPEC, 0, peer_id);
if (formspec.empty()){
//the client should close the formspec
//but make sure there wasn't another one open in meantime
pkt.putLongString("");
} else {
m_formspec_state_data[peer_id] = formname;
- pkt.putLongString(FORMSPEC_VERSION_STRING + formspec);
+ pkt.putLongString(formspec);
}
pkt << formname;
pkt << id << (u8) form->type << form->pos << form->name << form->scale
<< form->text << form->number << form->item << form->dir
- << form->align << form->offset << form->world_pos << form->size;
+ << form->align << form->offset << form->world_pos << form->size
+ << form->z_index;
Send(&pkt);
}
Send(&pkt);
}
-void Server::SendSetSky(session_t peer_id, const video::SColor &bgcolor,
- const std::string &type, const std::vector<std::string> ¶ms,
- bool &clouds)
+void Server::SendSetSky(session_t peer_id, const SkyboxParams ¶ms)
{
NetworkPacket pkt(TOCLIENT_SET_SKY, 0, peer_id);
- pkt << bgcolor << type << (u16) params.size();
- for (const std::string ¶m : params)
- pkt << param;
+ // Handle prior clients here
+ if (m_clients.getProtocolVersion(peer_id) < 39) {
+ pkt << params.bgcolor << params.type << (u16) params.textures.size();
+
+ for (const std::string& texture : params.textures)
+ pkt << texture;
+
+ pkt << params.clouds;
+ } else { // Handle current clients and future clients
+ pkt << params.bgcolor << params.type
+ << params.clouds << params.sun_tint
+ << params.moon_tint << params.tint_type;
+
+ if (params.type == "skybox") {
+ pkt << (u16) params.textures.size();
+ for (const std::string &texture : params.textures)
+ pkt << texture;
+ } else if (params.type == "regular") {
+ pkt << params.sky_color.day_sky << params.sky_color.day_horizon
+ << params.sky_color.dawn_sky << params.sky_color.dawn_horizon
+ << params.sky_color.night_sky << params.sky_color.night_horizon
+ << params.sky_color.indoors;
+ }
+ }
+
+ Send(&pkt);
+}
- pkt << clouds;
+void Server::SendSetSun(session_t peer_id, const SunParams ¶ms)
+{
+ NetworkPacket pkt(TOCLIENT_SET_SUN, 0, peer_id);
+ pkt << params.visible << params.texture
+ << params.tonemap << params.sunrise
+ << params.sunrise_visible << params.scale;
+
+ Send(&pkt);
+}
+void Server::SendSetMoon(session_t peer_id, const MoonParams ¶ms)
+{
+ NetworkPacket pkt(TOCLIENT_SET_MOON, 0, peer_id);
+
+ pkt << params.visible << params.texture
+ << params.tonemap << params.scale;
+
+ Send(&pkt);
+}
+void Server::SendSetStars(session_t peer_id, const StarParams ¶ms)
+{
+ NetworkPacket pkt(TOCLIENT_SET_STARS, 0, peer_id);
+
+ pkt << params.visible << params.count
+ << params.starcolor << params.scale;
Send(&pkt);
}
void Server::SendPlayerHP(session_t peer_id)
{
PlayerSAO *playersao = getPlayerSAO(peer_id);
- // In some rare case if the player is disconnected
- // while Lua call l_punch, for example, this can be NULL
- if (!playersao)
- return;
+ assert(playersao);
SendHP(peer_id, playersao->getHP());
m_script->player_event(playersao,"health_changed");
// Send to other clients
- std::string str = gob_cmd_punched(playersao->getHP());
- ActiveObjectMessage aom(playersao->getId(), true, str);
- playersao->m_messages_out.push(aom);
+ playersao->sendPunchCommand();
}
void Server::SendPlayerBreath(PlayerSAO *sao)
Send(&pkt);
}
+void Server::SendPlayerFov(session_t peer_id)
+{
+ NetworkPacket pkt(TOCLIENT_FOV, 4 + 1, peer_id);
+
+ PlayerFovSpec fov_spec = m_env->getPlayer(peer_id)->getFov();
+ pkt << fov_spec.fov << fov_spec.is_multiplier;
+
+ Send(&pkt);
+}
+
void Server::SendLocalPlayerAnimations(session_t peer_id, v2s32 animation_frames[4],
f32 animation_speed)
{
return;
NetworkPacket pkt(TOCLIENT_INVENTORY_FORMSPEC, 0, peer_id);
- pkt.putLongString(FORMSPEC_VERSION_STRING + player->inventory_formspec);
+ pkt.putLongString(player->inventory_formspec);
+
Send(&pkt);
}
return;
NetworkPacket pkt(TOCLIENT_FORMSPEC_PREPEND, 0, peer_id);
- pkt << FORMSPEC_VERSION_STRING + player->formspec_prepend;
+ pkt << player->formspec_prepend;
Send(&pkt);
}
-u32 Server::SendActiveObjectRemoveAdd(session_t peer_id, const std::string &datas)
+void Server::SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersao)
{
- NetworkPacket pkt(TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD, datas.size(), peer_id);
- pkt.putRawString(datas.c_str(), datas.size());
+ // Radius inside which objects are active
+ static thread_local const s16 radius =
+ g_settings->getS16("active_object_send_range_blocks") * MAP_BLOCKSIZE;
+
+ // Radius inside which players are active
+ static thread_local const bool is_transfer_limited =
+ g_settings->exists("unlimited_player_transfer_distance") &&
+ !g_settings->getBool("unlimited_player_transfer_distance");
+
+ static thread_local const s16 player_transfer_dist =
+ g_settings->getS16("player_transfer_distance") * MAP_BLOCKSIZE;
+
+ s16 player_radius = player_transfer_dist == 0 && is_transfer_limited ?
+ radius : player_transfer_dist;
+
+ s16 my_radius = MYMIN(radius, playersao->getWantedRange() * MAP_BLOCKSIZE);
+ if (my_radius <= 0)
+ my_radius = radius;
+
+ std::queue<u16> removed_objects, added_objects;
+ m_env->getRemovedActiveObjects(playersao, my_radius, player_radius,
+ client->m_known_objects, removed_objects);
+ m_env->getAddedActiveObjects(playersao, my_radius, player_radius,
+ client->m_known_objects, added_objects);
+
+ int removed_count = removed_objects.size();
+ int added_count = added_objects.size();
+
+ if (removed_objects.empty() && added_objects.empty())
+ return;
+
+ char buf[4];
+ std::string data;
+
+ // Handle removed objects
+ writeU16((u8*)buf, removed_objects.size());
+ data.append(buf, 2);
+ while (!removed_objects.empty()) {
+ // Get object
+ u16 id = removed_objects.front();
+ ServerActiveObject* obj = m_env->getActiveObject(id);
+
+ // Add to data buffer for sending
+ writeU16((u8*)buf, id);
+ data.append(buf, 2);
+
+ // Remove from known objects
+ client->m_known_objects.erase(id);
+
+ if (obj && obj->m_known_by_count > 0)
+ obj->m_known_by_count--;
+
+ removed_objects.pop();
+ }
+
+ // Handle added objects
+ writeU16((u8*)buf, added_objects.size());
+ data.append(buf, 2);
+ while (!added_objects.empty()) {
+ // Get object
+ u16 id = added_objects.front();
+ ServerActiveObject *obj = m_env->getActiveObject(id);
+ added_objects.pop();
+
+ if (!obj) {
+ warningstream << FUNCTION_NAME << ": NULL object id="
+ << (int)id << std::endl;
+ continue;
+ }
+
+ // Get object type
+ u8 type = obj->getSendType();
+
+ // Add to data buffer for sending
+ writeU16((u8*)buf, id);
+ data.append(buf, 2);
+ writeU8((u8*)buf, type);
+ data.append(buf, 1);
+
+ data.append(serializeLongString(
+ obj->getClientInitializationData(client->net_proto_version)));
+
+ // Add to known objects
+ client->m_known_objects.insert(id);
+
+ obj->m_known_by_count++;
+ }
+
+ NetworkPacket pkt(TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD, data.size(), client->peer_id);
+ pkt.putRawString(data.c_str(), data.size());
Send(&pkt);
- return pkt.getSize();
+
+ verbosestream << "Server::SendActiveObjectRemoveAdd: "
+ << removed_count << " removed, " << added_count << " added, "
+ << "packet size is " << pkt.getSize() << std::endl;
}
void Server::SendActiveObjectMessages(session_t peer_id, const std::string &datas,
Send(&pkt);
}
+inline s32 Server::nextSoundId()
+{
+ s32 ret = m_next_sound_id;
+ if (m_next_sound_id == INT32_MAX)
+ m_next_sound_id = 0; // signed overflow is undefined
+ else
+ m_next_sound_id++;
+ return ret;
+}
+
s32 Server::playSound(const SimpleSoundSpec &spec,
- const ServerSoundParams ¶ms)
+ const ServerSoundParams ¶ms, bool ephemeral)
{
// Find out initial position of sound
bool pos_exists = false;
// Filter destination clients
std::vector<session_t> dst_clients;
- if(!params.to_player.empty()) {
+ if (!params.to_player.empty()) {
RemotePlayer *player = m_env->getPlayer(params.to_player.c_str());
if(!player){
infostream<<"Server::playSound: Player \""<<params.to_player
RemotePlayer *player = m_env->getPlayer(client_id);
if (!player)
continue;
+ if (!params.exclude_player.empty() &&
+ params.exclude_player == player->getName())
+ continue;
PlayerSAO *sao = player->getPlayerSAO();
if (!sao)
return -1;
// Create the sound
- s32 id = m_next_sound_id++;
- // The sound will exist as a reference in m_playing_sounds
- m_playing_sounds[id] = ServerPlayingSound();
- ServerPlayingSound &psound = m_playing_sounds[id];
- psound.params = params;
- psound.spec = spec;
+ s32 id;
+ ServerPlayingSound *psound = nullptr;
+ if (ephemeral) {
+ id = -1; // old clients will still use this, so pick a reserved ID
+ } else {
+ id = nextSoundId();
+ // The sound will exist as a reference in m_playing_sounds
+ m_playing_sounds[id] = ServerPlayingSound();
+ psound = &m_playing_sounds[id];
+ psound->params = params;
+ psound->spec = spec;
+ }
float gain = params.gain * spec.gain;
NetworkPacket pkt(TOCLIENT_PLAY_SOUND, 0);
pkt << id << spec.name << gain
<< (u8) params.type << pos << params.object
- << params.loop << params.fade << params.pitch;
+ << params.loop << params.fade << params.pitch
+ << ephemeral;
- // Backwards compability
- bool play_sound = gain > 0;
+ bool as_reliable = !ephemeral;
for (const u16 dst_client : dst_clients) {
- if (play_sound || m_clients.getProtocolVersion(dst_client) >= 32) {
- psound.clients.insert(dst_client);
- m_clients.send(dst_client, 0, &pkt, true);
- }
+ if (psound)
+ psound->clients.insert(dst_client);
+ m_clients.send(dst_client, 0, &pkt, as_reliable);
}
return id;
}
void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_code)
{
- verbosestream << "Server: Announcing files to id(" << peer_id << ")"
- << std::endl;
-
// Make packet
NetworkPacket pkt(TOCLIENT_ANNOUNCE_MEDIA, 0, peer_id);
pkt << g_settings->get("remote_media");
Send(&pkt);
+
+ verbosestream << "Server: Announcing files to id(" << peer_id
+ << "): count=" << media_sent << " size=" << pkt.getSize() << std::endl;
}
struct SendableMedia
// Serialization & NetworkPacket isn't a love story
std::ostringstream os(std::ios_base::binary);
inv_it->second->serialize(os);
+ inv_it->second->setModified(false);
- std::string os_str = os.str();
+ const std::string &os_str = os.str();
pkt << static_cast<u16>(os_str.size()); // HACK: to keep compatibility with 5.0.0 clients
pkt.putRawString(os_str);
}
Send(&pkt);
}
-void Server::sendDetachedInventories(session_t peer_id)
+void Server::sendDetachedInventories(session_t peer_id, bool incremental)
{
for (const auto &detached_inventory : m_detached_inventories) {
const std::string &name = detached_inventory.first;
- //Inventory *inv = i->second;
+ if (incremental) {
+ Inventory *inv = detached_inventory.second;
+ if (!inv || !inv->checkModified())
+ continue;
+ }
+
sendDetachedInventory(name, peer_id);
}
}
void Server::DiePlayer(session_t peer_id, const PlayerHPChangeReason &reason)
{
PlayerSAO *playersao = getPlayerSAO(peer_id);
- // In some rare cases this can be NULL -- if the player is disconnected
- // when a Lua function modifies l_punch, for example
- if (!playersao)
- return;
+ assert(playersao);
infostream << "Server::DiePlayer(): Player "
<< playersao->getPlayer()->getName()
if (!clist || clist->getSize() == 0)
return;
+ if (!clist->checkModified())
+ return;
+
// Get a preview for crafting
ItemStack preview;
InventoryLocation loc;
SendEyeOffset(player->getPeerId(), first, third);
}
-void Server::setSky(RemotePlayer *player, const video::SColor &bgcolor,
- const std::string &type, const std::vector<std::string> ¶ms,
- bool &clouds)
+void Server::setSky(RemotePlayer *player, const SkyboxParams ¶ms)
+{
+ sanity_check(player);
+ player->setSky(params);
+ SendSetSky(player->getPeerId(), params);
+}
+
+void Server::setSun(RemotePlayer *player, const SunParams ¶ms)
+{
+ sanity_check(player);
+ player->setSun(params);
+ SendSetSun(player->getPeerId(), params);
+}
+
+void Server::setMoon(RemotePlayer *player, const MoonParams ¶ms)
+{
+ sanity_check(player);
+ player->setMoon(params);
+ SendSetMoon(player->getPeerId(), params);
+}
+
+void Server::setStars(RemotePlayer *player, const StarParams ¶ms)
{
sanity_check(player);
- player->setSky(bgcolor, type, params, clouds);
- SendSetSky(player->getPeerId(), bgcolor, type, params, clouds);
+ player->setStars(params);
+ SendSetStars(player->getPeerId(), params);
}
void Server::setClouds(RemotePlayer *player, const CloudParams ¶ms)
static thread_local const float profiler_print_interval =
g_settings->getFloat("profiler_print_interval");
+ /*
+ * The dedicated server loop only does time-keeping (in Server::step) and
+ * provides a way to main.cpp to kill the server externally (bool &kill).
+ */
+
for(;;) {
// This is kind of a hack but can be done like this
// because server.step() is very light