void *ServerThread::run()
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
BEGIN_DEBUG_EXCEPTION_HANDLER
m_server->AsyncRunStep(true);
}
}
- END_DEBUG_EXCEPTION_HANDLER(errorstream)
+ END_DEBUG_EXCEPTION_HANDLER
return NULL;
}
const std::string &path_world,
const SubgameSpec &gamespec,
bool simple_singleplayer_mode,
- bool ipv6
+ bool ipv6,
+ ChatInterface *iface
):
m_path_world(path_world),
m_gamespec(gamespec),
m_clients(&m_con),
m_shutdown_requested(false),
m_shutdown_ask_reconnect(false),
+ m_admin_chat(iface),
m_ignore_map_edit_events(false),
m_ignore_map_edit_events_peer_id(0),
m_next_sound_id(0)
m_script = new GameScripting(this);
std::string script_path = getBuiltinLuaPath() + DIR_DELIM "init.lua";
- std::string error_msg;
- if (!m_script->loadMod(script_path, BUILTIN_MOD_NAME, &error_msg))
- throw ModError("Failed to load and run " + script_path
- + "\nError from Lua:\n" + error_msg);
+ m_script->loadMod(script_path, BUILTIN_MOD_NAME);
// Print mods
infostream << "Server: Loading mods: ";
}
infostream << std::endl;
// Load and run "mod" scripts
- for (std::vector<ModSpec>::iterator i = m_mods.begin();
- i != m_mods.end(); ++i) {
- const ModSpec &mod = *i;
+ for (std::vector<ModSpec>::iterator it = m_mods.begin();
+ it != m_mods.end(); ++it) {
+ const ModSpec &mod = *it;
if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) {
- std::ostringstream err;
- err << "Error loading mod \"" << mod.name
- << "\": mod_name does not follow naming conventions: "
- << "Only chararacters [a-z0-9_] are allowed." << std::endl;
- errorstream << err.str().c_str();
- throw ModError(err.str());
+ throw ModError("Error loading mod \"" + mod.name +
+ "\": Mod name does not follow naming conventions: "
+ "Only chararacters [a-z0-9_] are allowed.");
}
- std::string script_path = mod.path + DIR_DELIM "init.lua";
+ std::string script_path = mod.path + DIR_DELIM + "init.lua";
infostream << " [" << padStringRight(mod.name, 12) << "] [\""
<< script_path << "\"]" << std::endl;
- if (!m_script->loadMod(script_path, mod.name, &error_msg)) {
- errorstream << "Server: Failed to load and run "
- << script_path << std::endl;
- throw ModError("Failed to load and run " + script_path
- + "\nError from Lua:\n" + error_msg);
- }
+ m_script->loadMod(script_path, mod.name);
}
// Read Textures and calculate sha1 sums
void Server::start(Address bind_addr)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
m_bind_addr = bind_addr;
void Server::stop()
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
infostream<<"Server: Stopping and waiting threads"<<std::endl;
void Server::step(float dtime)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
// Limit a bit
- if(dtime > 2.0)
+ if (dtime > 2.0)
dtime = 2.0;
{
MutexAutoLock lock(m_step_dtime_mutex);
}
// Throw if fatal error occurred in thread
std::string async_err = m_async_fatal_error.get();
- if(async_err != "") {
- if (m_simple_singleplayer_mode) {
- throw ServerError(async_err);
- }
- else {
+ if (!async_err.empty()) {
+ if (!m_simple_singleplayer_mode) {
m_env->kickAllPlayers(SERVER_ACCESSDENIED_CRASH,
g_settings->get("kick_msg_crash"),
g_settings->getBool("ask_reconnect_on_crash"));
- errorstream << "UNRECOVERABLE error occurred. Stopping server. "
- << "Please fix the following error:" << std::endl
- << async_err << std::endl;
- FATAL_ERROR(async_err.c_str());
}
+ throw ServerError(async_err);
}
}
void Server::AsyncRunStep(bool initial_step)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
g_profiler->add("Server::AsyncRunStep (num)", 1);
U32_MAX);
}
+ /*
+ Listen to the admin chat, if available
+ */
+ if (m_admin_chat) {
+ if (!m_admin_chat->command_queue.empty()) {
+ MutexAutoLock lock(m_env_mutex);
+ while (!m_admin_chat->command_queue.empty()) {
+ ChatEvent *evt = m_admin_chat->command_queue.pop_frontNoEx();
+ handleChatInterfaceEvent(evt);
+ delete evt;
+ }
+ }
+ m_admin_chat->outgoing_queue.push_back(
+ new ChatEventTimeInfo(m_env->getGameTime(), m_env->getTimeOfDay()));
+ }
+
/*
Do background stuff
*/
Player *player = m_env->getPlayer(client->peer_id);
if(player == NULL) {
// This can happen if the client timeouts somehow
- /*warningstream<<__FUNCTION_NAME<<": Client "
+ /*warningstream<<FUNCTION_NAME<<": Client "
<<client->peer_id
<<" has no associated player"<<std::endl;*/
continue;
// Get object type
u8 type = ACTIVEOBJECT_TYPE_INVALID;
if(obj == NULL)
- warningstream<<__FUNCTION_NAME
+ warningstream<<FUNCTION_NAME
<<": NULL object"<<std::endl;
else
type = obj->getSendType();
void Server::Receive()
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
SharedBuffer<u8> data;
u16 peer_id;
try {
// Send information about joining in chat
{
- std::wstring name = L"unknown";
+ std::string name = "unknown";
Player *player = m_env->getPlayer(peer_id);
if(player != NULL)
- name = narrow_to_wide(player->getName());
+ name = player->getName();
std::wstring message;
message += L"*** ";
- message += name;
+ message += narrow_to_wide(name);
message += L" joined the game.";
SendChatMessage(PEER_ID_INEXISTENT,message);
+ if (m_admin_chat)
+ m_admin_chat->outgoing_queue.push_back(
+ new ChatEventNick(CET_NICK_ADD, name));
}
}
Address addr = getPeerAddress(player->peer_id);
void Server::ProcessData(NetworkPacket *pkt)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
// Environment is locked first.
MutexAutoLock envlock(m_env_mutex);
void Server::peerAdded(con::Peer *peer)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
verbosestream<<"Server::peerAdded(): peer->id="
<<peer->id<<std::endl;
void Server::deletingPeer(con::Peer *peer, bool timeout)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
verbosestream<<"Server::deletingPeer(): peer->id="
<<peer->id<<", timeout="<<timeout<<std::endl;
}
}
+void Server::printToConsoleOnly(const std::string &text)
+{
+ if (m_admin_chat) {
+ m_admin_chat->outgoing_queue.push_back(
+ new ChatEventChat("", utf8_to_wide(text)));
+ } else {
+ std::cout << text << std::endl;
+ }
+}
+
void Server::Send(NetworkPacket* pkt)
{
m_clients.send(pkt->getPeerId(),
void Server::SendMovement(u16 peer_id)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
std::ostringstream os(std::ios_base::binary);
NetworkPacket pkt(TOCLIENT_MOVEMENT, 12 * sizeof(float), peer_id);
void Server::SendHP(u16 peer_id, u8 hp)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
NetworkPacket pkt(TOCLIENT_HP, 1, peer_id);
pkt << hp;
void Server::SendBreath(u16 peer_id, u16 breath)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
NetworkPacket pkt(TOCLIENT_BREATH, 2, peer_id);
pkt << (u16) breath;
void Server::SendAccessDenied_Legacy(u16 peer_id,const std::wstring &reason)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
NetworkPacket pkt(TOCLIENT_ACCESS_DENIED_LEGACY, 0, peer_id);
pkt << reason;
void Server::SendDeathscreen(u16 peer_id,bool set_camera_point_target,
v3f camera_point_target)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
NetworkPacket pkt(TOCLIENT_DEATHSCREEN, 1 + sizeof(v3f), peer_id);
pkt << set_camera_point_target << camera_point_target;
void Server::SendItemDef(u16 peer_id,
IItemDefManager *itemdef, u16 protocol_version)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
NetworkPacket pkt(TOCLIENT_ITEMDEF, 0, peer_id);
void Server::SendNodeDef(u16 peer_id,
INodeDefManager *nodedef, u16 protocol_version)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
NetworkPacket pkt(TOCLIENT_NODEDEF, 0, peer_id);
void Server::SendInventory(PlayerSAO* playerSAO)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
UpdateCrafting(playerSAO->getPlayer());
void Server::SendChatMessage(u16 peer_id, const std::wstring &message)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
NetworkPacket pkt(TOCLIENT_CHAT_MESSAGE, 0, peer_id);
pkt << message;
void Server::SendShowFormspecMessage(u16 peer_id, const std::string &formspec,
const std::string &formname)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
NetworkPacket pkt(TOCLIENT_SHOW_FORMSPEC, 0 , peer_id);
float expirationtime, float size, bool collisiondetection,
bool vertical, std::string texture)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
NetworkPacket pkt(TOCLIENT_SPAWN_PARTICLE, 0, peer_id);
v3f minvel, v3f maxvel, v3f minacc, v3f maxacc, float minexptime, float maxexptime,
float minsize, float maxsize, bool collisiondetection, bool vertical, std::string texture, u32 id)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
NetworkPacket pkt(TOCLIENT_ADD_PARTICLESPAWNER, 0, peer_id);
void Server::SendDeleteParticleSpawner(u16 peer_id, u32 id)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
NetworkPacket pkt(TOCLIENT_DELETE_PARTICLESPAWNER_LEGACY, 2, peer_id);
void Server::SendTimeOfDay(u16 peer_id, u16 time, f32 time_speed)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
NetworkPacket pkt(TOCLIENT_TIME_OF_DAY, 0, peer_id);
pkt << time << time_speed;
void Server::SendPlayerHP(u16 peer_id)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
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
void Server::SendPlayerBreath(u16 peer_id)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
PlayerSAO *playersao = getPlayerSAO(peer_id);
assert(playersao);
void Server::SendMovePlayer(u16 peer_id)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
Player *player = m_env->getPlayer(peer_id);
assert(player);
void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver, u16 net_proto_version)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
v3s16 p = block->getPos();
void Server::SendBlocks(float dtime)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
MutexAutoLock envlock(m_env_mutex);
//TODO check if one big lock could be faster then multiple small ones
void Server::fillMediaCache()
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
infostream<<"Server: Calculating media file checksums"<<std::endl;
void Server::sendMediaAnnouncement(u16 peer_id)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
verbosestream << "Server: Announcing files to id(" << peer_id << ")"
<< std::endl;
void Server::sendRequestedMedia(u16 peer_id,
const std::vector<std::string> &tosend)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
verbosestream<<"Server::sendRequestedMedia(): "
<<"Sending files to client"<<std::endl;
void Server::sendDetachedInventory(const std::string &name, u16 peer_id)
{
if(m_detached_inventories.count(name) == 0) {
- errorstream<<__FUNCTION_NAME<<": \""<<name<<"\" not found"<<std::endl;
+ errorstream<<FUNCTION_NAME<<": \""<<name<<"\" not found"<<std::endl;
return;
}
Inventory *inv = m_detached_inventories[name];
void Server::sendDetachedInventories(u16 peer_id)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
for(std::map<std::string, Inventory*>::iterator
i = m_detached_inventories.begin();
void Server::DiePlayer(u16 peer_id)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
PlayerSAO *playersao = getPlayerSAO(peer_id);
assert(playersao);
void Server::RespawnPlayer(u16 peer_id)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
PlayerSAO *playersao = getPlayerSAO(peer_id);
assert(playersao);
void Server::DenySudoAccess(u16 peer_id)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
NetworkPacket pkt(TOCLIENT_DENY_SUDO_MODE, 0, peer_id);
Send(&pkt);
void Server::DenyAccess(u16 peer_id, AccessDeniedCode reason, const std::string &custom_reason)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
SendAccessDenied(peer_id, reason, custom_reason);
m_clients.event(peer_id, CSE_SetDenied);
// the minimum version for MT users, maybe in 1 year
void Server::DenyAccess_Legacy(u16 peer_id, const std::wstring &reason)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
SendAccessDenied_Legacy(peer_id, reason);
m_clients.event(peer_id, CSE_SetDenied);
void Server::acceptAuth(u16 peer_id, bool forSudoMode)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
if (!forSudoMode) {
RemoteClient* client = getClient(peer_id, CS_Invalid);
void Server::DeleteClient(u16 peer_id, ClientDeletionReason reason)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
std::wstring message;
{
/*
os << player->getName() << " ";
}
- actionstream << player->getName() << " "
+ std::string name = player->getName();
+ actionstream << name << " "
<< (reason == CDR_TIMEOUT ? "times out." : "leaves game.")
<< " List of players: " << os.str() << std::endl;
+ if (m_admin_chat)
+ m_admin_chat->outgoing_queue.push_back(
+ new ChatEventNick(CET_NICK_REMOVE, name));
}
}
{
void Server::UpdateCrafting(Player* player)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
// Get a preview for crafting
ItemStack preview;
plist->changeItem(0, preview);
}
+void Server::handleChatInterfaceEvent(ChatEvent *evt)
+{
+ if (evt->type == CET_NICK_ADD) {
+ // The terminal informed us of its nick choice
+ m_admin_nick = ((ChatEventNick *)evt)->nick;
+ if (!m_script->getAuth(m_admin_nick, NULL, NULL)) {
+ errorstream << "You haven't set up an account." << std::endl
+ << "Please log in using the client as '"
+ << m_admin_nick << "' with a secure password." << std::endl
+ << "Until then, you can't execute admin tasks via the console," << std::endl
+ << "and everybody can claim the user account instead of you," << std::endl
+ << "giving them full control over this server." << std::endl;
+ }
+ } else {
+ assert(evt->type == CET_CHAT);
+ handleAdminChat((ChatEventChat *)evt);
+ }
+}
+
+std::wstring Server::handleChat(const std::string &name, const std::wstring &wname,
+ const std::wstring &wmessage, bool check_shout_priv,
+ u16 peer_id_to_avoid_sending)
+{
+ // If something goes wrong, this player is to blame
+ RollbackScopeActor rollback_scope(m_rollback,
+ std::string("player:") + name);
+
+ // Line to send
+ std::wstring line;
+ // Whether to send line to the player that sent the message, or to all players
+ bool broadcast_line = true;
+
+ // Run script hook
+ bool ate = m_script->on_chat_message(name,
+ wide_to_utf8(wmessage));
+ // If script ate the message, don't proceed
+ if (ate)
+ return L"";
+
+ // Commands are implemented in Lua, so only catch invalid
+ // commands that were not "eaten" and send an error back
+ if (wmessage[0] == L'/') {
+ std::wstring wcmd = wmessage.substr(1);
+ broadcast_line = false;
+ if (wcmd.length() == 0)
+ line += L"-!- Empty command";
+ else
+ line += L"-!- Invalid command: " + str_split(wcmd, L' ')[0];
+ } else {
+ if (check_shout_priv && !checkPriv(name, "shout")) {
+ line += L"-!- You don't have permission to shout.";
+ broadcast_line = false;
+ } else {
+ line += L"<";
+ line += wname;
+ line += L"> ";
+ line += wmessage;
+ }
+ }
+
+ /*
+ Tell calling method to send the message to sender
+ */
+ if (!broadcast_line) {
+ return line;
+ } else {
+ /*
+ Send the message to others
+ */
+ actionstream << "CHAT: " << wide_to_narrow(line) << std::endl;
+
+ std::vector<u16> clients = m_clients.getClientIDs();
+
+ for (u16 i = 0; i < clients.size(); i++) {
+ u16 cid = clients[i];
+ if (cid != peer_id_to_avoid_sending)
+ SendChatMessage(cid, line);
+ }
+ }
+ return L"";
+}
+
+void Server::handleAdminChat(const ChatEventChat *evt)
+{
+ std::string name = evt->nick;
+ std::wstring wname = utf8_to_wide(name);
+ std::wstring wmessage = evt->evt_msg;
+
+ std::wstring answer = handleChat(name, wname, wmessage);
+
+ // If asked to send answer to sender
+ if (!answer.empty()) {
+ m_admin_chat->outgoing_queue.push_back(new ChatEventChat("", answer));
+ }
+}
+
RemoteClient* Server::getClient(u16 peer_id, ClientState state_min)
{
RemoteClient *client = getClientNoEx(peer_id,state_min);
if (!m_env)
return;
+ if (m_admin_nick == name && !m_admin_nick.empty()) {
+ m_admin_chat->outgoing_queue.push_back(new ChatEventChat("", msg));
+ }
+
Player *player = m_env->getPlayer(name);
- if (!player)
+ if (!player) {
return;
+ }
if (player->peer_id == PEER_ID_INEXISTENT)
return;
return false;
SendHUDSetFlags(player->peer_id, flags, mask);
- player->hud_flags = flags;
+ player->hud_flags &= ~mask;
+ player->hud_flags |= flags;
PlayerSAO* playersao = player->getPlayerSAO();
return nodeposf * BS;
}
- s16 water_level = map.getWaterLevel();
-
bool is_good = false;
// Try to find a good place a few times
- for(s32 i = 0; i < 1000 && !is_good; i++) {
+ for(s32 i = 0; i < 4000 && !is_good; i++) {
s32 range = 1 + i;
// We're going to try to throw the player to this position
v2s16 nodepos2d = v2s16(
-range + (myrand() % (range * 2)),
-range + (myrand() % (range * 2)));
- // Get ground height at point
- s16 groundheight = map.findGroundLevel(nodepos2d);
- if (groundheight <= water_level) // Don't go underwater
- continue;
- if (groundheight > water_level + 6) // Don't go to high places
+ // Get spawn level at point
+ s16 spawn_level = m_emerge->getSpawnLevelAtPoint(nodepos2d);
+ // Continue if MAX_MAP_GENERATION_LIMIT was returned by
+ // the mapgen to signify an unsuitable spawn position
+ if (spawn_level == MAX_MAP_GENERATION_LIMIT)
continue;
- v3s16 nodepos(nodepos2d.X, groundheight, nodepos2d.Y);
+ v3s16 nodepos(nodepos2d.X, spawn_level, nodepos2d.Y);
s32 air_count = 0;
for (s32 i = 0; i < 10; i++) {
void dedicated_server_loop(Server &server, bool &kill)
{
- DSTACK(__FUNCTION_NAME);
+ DSTACK(FUNCTION_NAME);
verbosestream<<"dedicated_server_loop()"<<std::endl;